From 1810808acd8148b5da368fc5874de18cd87d53ea Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 28 Jun 2019 16:34:15 +0200 Subject: [PATCH 0001/3170] Fix wrong usage of ynh_mysql_user_exists --- data/helpers.d/mysql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/mysql b/data/helpers.d/mysql index 372819025..e9cf59b3c 100644 --- a/data/helpers.d/mysql +++ b/data/helpers.d/mysql @@ -231,7 +231,7 @@ ynh_mysql_remove_db () { fi # Remove mysql user if it exists - if $(ynh_mysql_user_exists --user=$db_user); then + if ynh_mysql_user_exists --user=$db_user; then ynh_mysql_drop_user $db_user fi } From 2e6932928b80c4a5f01638a68b57a31954aa0696 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 5 Jul 2019 22:24:48 +0200 Subject: [PATCH 0002/3170] Split handling of hook_exec according to hook type --- src/yunohost/hook.py | 71 ++++++++++++++++++++++++++++++-------------- 1 file changed, 49 insertions(+), 22 deletions(-) diff --git a/src/yunohost/hook.py b/src/yunohost/hook.py index 67e77f033..e9cabcbc3 100644 --- a/src/yunohost/hook.py +++ b/src/yunohost/hook.py @@ -311,7 +311,6 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False, user -- User with which to run the command """ - from moulinette.utils.process import call_async_output # Validate hook path if path[0] != '/': @@ -319,6 +318,49 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False, if not os.path.isfile(path): raise YunohostError('file_does_not_exist', path=path) + # Check the type of the hook (bash by default) + hook_type = "bash" + # (non-bash hooks shall start with something like "#!/usr/bin/env language") + hook_ext = os.path.splitext(path)[1] + if hook_ext == ".py": + hook_type = "python" + else: + # TODO / FIXME : if needed in the future, implement support for other + # languages... + assert hook_ext == "", "hook_exec only supports bash and python hooks for now" + + # Define output loggers and call command + loggers = ( + lambda l: logger.debug(l.rstrip()+"\r"), + lambda l: logger.warning(l.rstrip()), + lambda l: logger.info(l.rstrip()) + ) + + if hook_type == "bash": + returncode, returndata = _hook_exec_bash(path, args, no_trace, chdir, env, user, return_format, loggers) + elif hook_type == "python": + returncode, returndata = _hook_exec_python(path, args, env, loggers) + else: + # Doesn't happen ... c.f. previous assertion + returncode, returndata = None, {} + + # Check and return process' return code + if returncode is None: + if raise_on_error: + raise YunohostError('hook_exec_not_terminated', path=path) + else: + logger.error(m18n.n('hook_exec_not_terminated', path=path)) + return 1, {} + elif raise_on_error and returncode != 0: + raise YunohostError('hook_exec_failed', path=path) + + return returncode, returndata + + +def _hook_exec_bash(path, args, no_trace, chdir, env, user, return_format, loggers): + + from moulinette.utils.process import call_async_output + # Construct command variables cmd_args = '' if args and isinstance(args, list): @@ -369,33 +411,13 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False, else: logger.debug(m18n.n('executing_script', script=path)) - # Define output callbacks and call command - callbacks = ( - lambda l: logger.debug(l.rstrip()+"\r"), - lambda l: logger.warning(l.rstrip()), - ) - - if stdinfo: - callbacks = (callbacks[0], callbacks[1], - lambda l: logger.info(l.rstrip())) - logger.debug("About to run the command '%s'" % command) returncode = call_async_output( - command, callbacks, shell=False, cwd=chdir, + command, loggers, shell=False, cwd=chdir, stdinfo=stdinfo ) - # Check and return process' return code - if returncode is None: - if raise_on_error: - raise YunohostError('hook_exec_not_terminated', path=path) - else: - logger.error(m18n.n('hook_exec_not_terminated', path=path)) - return 1, {} - elif raise_on_error and returncode != 0: - raise YunohostError('hook_exec_failed', path=path) - raw_content = None try: with open(stdreturn, 'r') as f: @@ -427,6 +449,11 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False, return returncode, returncontent +def _hook_exec_python(path, args, env, loggers): + + pass + + def _extract_filename_parts(filename): """Extract hook parts from filename""" if '-' in filename: From 491959f16e87c1b411c1c3a01483ca755ee3f13d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 5 Jul 2019 22:27:53 +0200 Subject: [PATCH 0003/3170] Implement Python hooks handling --- src/yunohost/hook.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/yunohost/hook.py b/src/yunohost/hook.py index e9cabcbc3..8c679b601 100644 --- a/src/yunohost/hook.py +++ b/src/yunohost/hook.py @@ -25,8 +25,10 @@ """ import os import re +import sys import tempfile from glob import iglob +from importlib import import_module from moulinette import m18n, msettings from yunohost.utils.error import YunohostError @@ -179,7 +181,7 @@ def hook_list(action, list_by='name', show_info=False): def _append_folder(d, folder): # Iterate over and add hook from a folder for f in os.listdir(folder + action): - if f[0] == '.' or f[-1] == '~': + if f[0] == '.' or f[-1] == '~' or f.endswith(".pyc"): continue path = '%s%s/%s' % (folder, action, f) priority, name = _extract_filename_parts(f) @@ -451,7 +453,16 @@ def _hook_exec_bash(path, args, no_trace, chdir, env, user, return_format, logge def _hook_exec_python(path, args, env, loggers): - pass + dir_ = os.path.dirname(path) + name = os.path.splitext(os.path.basename(path))[0] + + if not dir_ in sys.path: + sys.path = [dir_] + sys.path + module = import_module(name) + + # TODO : We might want to check here that it's a tuple + # containing an int + a dict ? + return module.main(args, env, loggers) def _extract_filename_parts(filename): @@ -461,6 +472,9 @@ def _extract_filename_parts(filename): else: priority = '50' action = filename + + # Remove extension if there's one + action = os.path.splitext(action)[0] return priority, action From ce4fb552aee393fd9a782ce1cbfa5a743c9278fb Mon Sep 17 00:00:00 2001 From: yalh76 Date: Wed, 10 Jul 2019 23:29:38 +0200 Subject: [PATCH 0004/3170] upgrade n version --- data/helpers.d/nodejs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/nodejs b/data/helpers.d/nodejs index 9295c4348..3f4cfd4f3 100644 --- a/data/helpers.d/nodejs +++ b/data/helpers.d/nodejs @@ -16,8 +16,8 @@ ynh_install_n () { ynh_print_info --message="Installation of N - Node.js version management" # Build an app.src for n mkdir -p "../conf" - echo "SOURCE_URL=https://github.com/tj/n/archive/v2.1.7.tar.gz -SOURCE_SUM=2ba3c9d4dd3c7e38885b37e02337906a1ee91febe6d5c9159d89a9050f2eea8f" > "../conf/n.src" + echo "SOURCE_URL=https://github.com/tj/n/archive/v4.1.0.tar.gz +SOURCE_SUM=3983fa3f00d4bf85ba8e21f1a590f6e28938093abe0bb950aeea52b1717471fc" > "../conf/n.src" # Download and extract n ynh_setup_source --dest_dir="$n_install_dir/git" --source_id=n # Install n From 8c4d136a7f30e823b09345c1eb0b2912722024f2 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 13 Jul 2019 18:29:37 +0200 Subject: [PATCH 0005/3170] Check python hook did return the expected format --- src/yunohost/hook.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/yunohost/hook.py b/src/yunohost/hook.py index 8c679b601..a9fd3186c 100644 --- a/src/yunohost/hook.py +++ b/src/yunohost/hook.py @@ -462,7 +462,13 @@ def _hook_exec_python(path, args, env, loggers): # TODO : We might want to check here that it's a tuple # containing an int + a dict ? - return module.main(args, env, loggers) + ret = module.main(args, env, loggers) + assert isinstance(ret, tuple) \ + and len(ret) == 2 \ + and isinstance(ret[0],int) \ + and isinstance(ret[1],dict), \ + "Module %s did not return a (int, dict) tuple !" % module + return ret def _extract_filename_parts(filename): From 6a0959dd1d7ba58b1bfa19de9ae9e2d7881ad556 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sat, 20 Jul 2019 06:32:08 +0200 Subject: [PATCH 0006/3170] [fix] moulinette logs were never displayed #lol --- bin/yunohost | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/yunohost b/bin/yunohost index 10a21a9da..672c1b539 100755 --- a/bin/yunohost +++ b/bin/yunohost @@ -145,7 +145,7 @@ def _init_moulinette(debug=False, quiet=False): }, 'moulinette': { 'level': level, - 'handlers': [], + 'handlers': handlers, 'propagate': True, }, 'moulinette.interface': { From 81843b2b957ff88c4c1b7b8f897bed567510c6bf Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 3 Aug 2019 20:56:36 +0200 Subject: [PATCH 0007/3170] Support .sh as an exception for bash hooks --- src/yunohost/hook.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/hook.py b/src/yunohost/hook.py index a9fd3186c..64b4cc2ee 100644 --- a/src/yunohost/hook.py +++ b/src/yunohost/hook.py @@ -329,7 +329,7 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False, else: # TODO / FIXME : if needed in the future, implement support for other # languages... - assert hook_ext == "", "hook_exec only supports bash and python hooks for now" + assert hook_ext in ["", ".sh"], "hook_exec only supports bash and python hooks for now" # Define output loggers and call command loggers = ( From bee4fb70fda3857bbfef72b95c7d50198034ad10 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 3 Aug 2019 21:21:07 +0200 Subject: [PATCH 0008/3170] [mod] Move this block to have a whole part dedicated to migrations --- src/yunohost/tools.py | 68 +++++++++++++++++++++++-------------------- 1 file changed, 37 insertions(+), 31 deletions(-) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 5e14082b5..8b0b981b2 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -970,6 +970,43 @@ def tools_reboot(operation_logger, force=False): subprocess.check_call(['systemctl', 'reboot']) +def tools_shell(command=None): + """ + Launch an (i)python shell in the YunoHost context. + + This is entirely aim for development. + """ + + from yunohost.utils.ldap import _get_ldap_interface + ldap = _get_ldap_interface() + + if command: + exec(command) + return + + logger.warn("The \033[1;34mldap\033[0m interface is available in this context") + try: + from IPython import embed + embed() + except ImportError: + logger.warn("You don't have IPython installed, consider installing it as it is way better than the standard shell.") + logger.warn("Falling back on the standard shell.") + + import readline # will allow Up/Down/History in the console + readline # to please pyflakes + import code + vars = globals().copy() + vars.update(locals()) + shell = code.InteractiveConsole(vars) + shell.interact() + + +# ############################################ # +# # +# Migrations management # +# # +# ############################################ # + def tools_migrations_list(pending=False, done=False): """ List existing migrations @@ -1155,37 +1192,6 @@ def tools_migrations_state(): return read_json(MIGRATIONS_STATE_PATH) -def tools_shell(command=None): - """ - Launch an (i)python shell in the YunoHost context. - - This is entirely aim for development. - """ - - from yunohost.utils.ldap import _get_ldap_interface - ldap = _get_ldap_interface() - - if command: - exec(command) - return - - logger.warn("The \033[1;34mldap\033[0m interface is available in this context") - try: - from IPython import embed - embed() - except ImportError: - logger.warn("You don't have IPython installed, consider installing it as it is way better than the standard shell.") - logger.warn("Falling back on the standard shell.") - - import readline # will allow Up/Down/History in the console - readline # to please pyflakes - import code - vars = globals().copy() - vars.update(locals()) - shell = code.InteractiveConsole(vars) - shell.interact() - - def _get_migrations_list(): migrations = [] From 2723300dcaa1ec9ba803496ad15ebc5c31397566 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 4 Aug 2019 00:28:14 +0200 Subject: [PATCH 0009/3170] Rework the migration system to have independent, non-ordered migrations, so each migration has a state skipped / done / pending and can be ran independently from others --- data/actionsmap/yunohost.yml | 24 ++-- src/yunohost/tools.py | 228 +++++++++++++++++------------------ 2 files changed, 125 insertions(+), 127 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 6401bdebc..f54cc2254 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1726,22 +1726,26 @@ tools: ### tools_migrations_migrate() migrate: - action_help: Perform migrations + action_help: Run migrations api: POST /migrations/migrate arguments: - -t: - help: target migration number (or 0), latest one by default - type: int - full: --target - -s: - help: skip the migration(s), use it only if you know what you are doing - full: --skip + targets: + help: Migrations to run (all pendings by default) + nargs: "*" + --skip: + help: Skip specified migrations (to be used only if you know what you are doing) + action: store_true + --revert: + help: Attempt to revert already-ran migrations + action: store_true + --force-rerun: + help: Re-run already-ran migrations (to be used only if you know what you are doing) action: store_true --auto: - help: automatic mode, won't run manual migrations, use it only if you know what you are doing + help: Automatic mode, won't run manual migrations (to be used only if you know what you are doing) action: store_true --accept-disclaimer: - help: accept disclaimers of migration (please read them before using this option) + help: Accept disclaimers of migrations (please read them before using this option) action: store_true ### tools_migrations_state() diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 8b0b981b2..f44e98a8d 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -37,7 +37,7 @@ from collections import OrderedDict from moulinette import msignals, m18n from moulinette.utils.log import getActionLogger from moulinette.utils.process import check_output, call_async_output -from moulinette.utils.filesystem import read_json, write_to_json +from moulinette.utils.filesystem import read_json, write_to_json, read_yaml, write_to_yaml from yunohost.app import app_fetchlist, app_info, app_upgrade, app_ssowatconf, app_list, _install_appslist_fetch_cron from yunohost.domain import domain_add, domain_list, _get_maindomain, _set_maindomain from yunohost.dyndns import _dyndns_available, _dyndns_provides @@ -52,7 +52,8 @@ from yunohost.log import is_unit_operation, OperationLogger # FIXME this is a duplicate from apps.py APPS_SETTING_PATH = '/etc/yunohost/apps/' -MIGRATIONS_STATE_PATH = "/etc/yunohost/migrations_state.json" +MIGRATIONS_STATE_PATH = "/etc/yunohost/migrations.yaml" +OLD_MIGRATIONS_STATE_PATH = "/etc/yunohost/migrations_state.json" logger = getActionLogger('yunohost.tools') @@ -1019,131 +1020,126 @@ def tools_migrations_list(pending=False, done=False): # Get all migrations migrations = _get_migrations_list() - # If asked, filter pending or done migrations - if pending or done: - last_migration = tools_migrations_state()["last_run_migration"] - last_migration = last_migration["number"] if last_migration else -1 - if done: - migrations = [m for m in migrations if m.number <= last_migration] - if pending: - migrations = [m for m in migrations if m.number > last_migration] - # Reduce to dictionnaries migrations = [{"id": migration.id, "number": migration.number, "name": migration.name, "mode": migration.mode, + "state": migration.state, "description": migration.description, "disclaimer": migration.disclaimer} for migration in migrations] + # If asked, filter pending or done migrations + if pending or done: + if done: + migrations = [m for m in migrations if m["state"] != "pending"] + if pending: + migrations = [m for m in migrations if m["state"] == "pending"] + return {"migrations": migrations} -def tools_migrations_migrate(target=None, skip=False, auto=False, accept_disclaimer=False): +def tools_migrations_migrate(targets=[], skip=False, auto=False, force_rerun=False, revert=False, accept_disclaimer=False): """ Perform migrations + + targets : a list of migrations to act on (by default : all pending) + auto : automatic mode, run only 'automatic' migrations (compared to manual migrations). Option meant to be used in debian's postinst script during upgades. + skip : skip specified migrations (must explicit which migrations) + revert : to revert already-ran migrations (must explicit which migrations) + force_rerun : to re-run already ran migrations (if you know what you're doing...) (must explicit which migrations) + accept_disclaimer : accept disclaimer for manual migration that requires it (only valid for one migration) """ - # state is a datastructure that represents the last run migration - # it has this form: - # { - # "last_run_migration": { - # "number": "00xx", - # "name": "some name", - # } - # } - state = tools_migrations_state() + all_migrations = _get_migrations_list() - last_run_migration_number = state["last_run_migration"]["number"] if state["last_run_migration"] else 0 + # auto, skip, revert and force are exclusive options + if auto + skip + revert + force_rerun > 1: + raise YunohostError("--auto, --skip, --revert and --force-rerun are exclusive options.") - # load all migrations - migrations = _get_migrations_list() - migrations = sorted(migrations, key=lambda x: x.number) + # If no target specified + if not targets: + # skip, revert or force require explicit targets + if (revert or force_rerun): + raise YunohostError("You must provide explicit targets when using --skip, --revert or --force-rerun") - if not migrations: + # Otherwise, targets are all pending migrations + targets = [m for m in all_migrations if m.state == "pending"] + + # If explicit targets are provided, we shall validate them + else: + def get_matching_migration(target): + for m in all_migrations: + if m.id == target or m.name == target or m.id.split("_")[0] == target: + return m + + raise YunohostError("No such migration called %s" % target) + + targets = [get_matching_migration(t) for t in targets] + done = [t.id for t in targets if t.state != "pending"] + pending = [t.id for t in targets if t.state == "pending"] + + if skip and done: + raise YunohostError("Those migrations are not pending so cannot be skipped: %s" % ', '.join(done)) + if (revert or force_rerun) and pending: + raise YunohostError("Those migrations were not already ran so cannot be reverted or reran: %s" % ', '.join(pending)) + + # So, is there actually something to do ? + if not targets: logger.info(m18n.n('migrations_no_migrations_to_run')) return - all_migration_numbers = [x.number for x in migrations] + # Actually run selected migrations + for migration in targets: - if target is None: - target = migrations[-1].number + # If we are migrating in "automatic mode" (i.e. from debian configure + # during an upgrade of the package) but we are asked to run migrations + # to be ran manually by the user, stop there and ask the user to + # run the migration manually. + if auto and migration.mode == "manual": + logger.warn(m18n.n('migrations_to_be_ran_manually', + number=migration.number, + name=migration.name)) - # validate input, target must be "0" or a valid number - elif target != 0 and target not in all_migration_numbers: - raise YunohostError('migrations_bad_value_for_target', ", ".join(map(str, all_migration_numbers))) + # We go to the next migration + continue - logger.debug(m18n.n('migrations_current_target', target)) - - # no new migrations to run - if target == last_run_migration_number: - logger.info(m18n.n('migrations_no_migrations_to_run')) - return - - logger.debug(m18n.n('migrations_show_last_migration', last_run_migration_number)) - - # we need to run missing migrations - if last_run_migration_number < target: - logger.debug(m18n.n('migrations_forward')) - # drop all already run migrations - migrations = filter(lambda x: target >= x.number > last_run_migration_number, migrations) - mode = "forward" - - # we need to go backward on already run migrations - elif last_run_migration_number > target: - logger.debug(m18n.n('migrations_backward')) - # drop all not already run migrations - migrations = filter(lambda x: target < x.number <= last_run_migration_number, migrations) - mode = "backward" - - else: # can't happen, this case is handle before - raise Exception() - - # effectively run selected migrations - for migration in migrations: - - if not skip: - # If we are migrating in "automatic mode" (i.e. from debian configure - # during an upgrade of the package) but we are asked to run migrations - # to be ran manually by the user, stop there and ask the user to - # run the migration manually. - if auto and migration.mode == "manual": - logger.warn(m18n.n('migrations_to_be_ran_manually', + # If some migrations have disclaimers (and we're not trying to skip them) + if migration.disclaimer and not skip: + # require the --accept-disclaimer option. + # Otherwise, go to the next migration + if not accept_disclaimer: + logger.warn(m18n.n('migrations_need_to_accept_disclaimer', number=migration.number, - name=migration.name)) - break - - # If some migrations have disclaimers, - if migration.disclaimer: - # require the --accept-disclaimer option. Otherwise, stop everything - # here and display the disclaimer - if not accept_disclaimer: - logger.warn(m18n.n('migrations_need_to_accept_disclaimer', - number=migration.number, - name=migration.name, - disclaimer=migration.disclaimer)) - break - # --accept-disclaimer will only work for the first migration - else: - accept_disclaimer = False + name=migration.name, + disclaimer=migration.disclaimer)) + continue + # --accept-disclaimer will only work for the first migration + else: + accept_disclaimer = False # Start register change on system + mode = "backward" if revert else "forward" operation_logger = OperationLogger('tools_migrations_migrate_' + mode) operation_logger.start() - if not skip: + if skip: + logger.warn(m18n.n('migrations_skip_migration', + number=migration.number, + name=migration.name)) + _write_migration_state(migration.id, "skipped") + operation_logger.success() + else: logger.info(m18n.n('migrations_show_currently_running_migration', number=migration.number, name=migration.name)) try: migration.operation_logger = operation_logger - if mode == "forward": - migration.migrate() - elif mode == "backward": + if revert: migration.backward() - else: # can't happen - raise Exception("Illegal state for migration: '%s', should be either 'forward' or 'backward'" % mode) + else: + migration.migrate() except Exception as e: # migration failed, let's stop here but still update state because # we managed to run the previous ones @@ -1153,33 +1149,11 @@ def tools_migrations_migrate(target=None, skip=False, auto=False, accept_disclai name=migration.name) logger.error(msg, exc_info=1) operation_logger.error(msg) - break else: logger.success(m18n.n('migrations_success', number=migration.number, name=migration.name)) - - else: # if skip - logger.warn(m18n.n('migrations_skip_migration', - number=migration.number, - name=migration.name)) - - # update the state to include the latest run migration - state["last_run_migration"] = { - "number": migration.number, - "name": migration.name - } - - operation_logger.success() - - # Skip migrations one at a time - if skip: - break - - # special case where we want to go back from the start - if target == 0: - state["last_run_migration"] = None - - write_to_json(MIGRATIONS_STATE_PATH, state) + _write_migration_state(migration.id, "done") + operation_logger.success() def tools_migrations_state(): @@ -1187,9 +1161,16 @@ def tools_migrations_state(): Show current migration state """ if not os.path.exists(MIGRATIONS_STATE_PATH): - return {"last_run_migration": None} + return {"migrations": {}} - return read_json(MIGRATIONS_STATE_PATH) + return read_yaml(MIGRATIONS_STATE_PATH) + + +def _write_migration_state(migration_id, state): + + current_states = tools_migrations_state() + current_states["migrations"][migration_id] = state + write_to_yaml(MIGRATIONS_STATE_PATH, current_states) def _get_migrations_list(): @@ -1207,8 +1188,21 @@ def _get_migrations_list(): logger.warn(m18n.n('migrations_cant_reach_migration_file', migrations_path)) return migrations + # states is a datastructure that represents the last run migration + # it has this form: + # { + # "0001_foo": "skipped", + # "0004_baz": "done", + # "0002_bar": "skipped", + # "0005_zblerg": "done", + # } + # (in particular, pending migrations / not already ran are not listed + states = tools_migrations_state()["migrations"] + for migration_file in filter(lambda x: re.match("^\d+_[a-zA-Z0-9_]+\.py$", x), os.listdir(migrations_path)): - migrations.append(_load_migration(migration_file)) + m = _load_migration(migration_file) + m.state = states.get(m.id, "pending") + migrations.append(m) return sorted(migrations, key=lambda m: m.id) From da64778b0d73ae9a3b41f92800f62538a7deced7 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 4 Aug 2019 00:57:00 +0200 Subject: [PATCH 0010/3170] Migrate the old migration json to the new yaml format --- src/yunohost/tools.py | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index f44e98a8d..a15b5593e 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -53,7 +53,6 @@ from yunohost.log import is_unit_operation, OperationLogger # FIXME this is a duplicate from apps.py APPS_SETTING_PATH = '/etc/yunohost/apps/' MIGRATIONS_STATE_PATH = "/etc/yunohost/migrations.yaml" -OLD_MIGRATIONS_STATE_PATH = "/etc/yunohost/migrations_state.json" logger = getActionLogger('yunohost.tools') @@ -1160,12 +1159,48 @@ def tools_migrations_state(): """ Show current migration state """ + if os.path.exists("/etc/yunohost/migrations_state.json"): + _migrate_legacy_migration_json() + if not os.path.exists(MIGRATIONS_STATE_PATH): return {"migrations": {}} return read_yaml(MIGRATIONS_STATE_PATH) +def _migrate_legacy_migration_json(): + + from moulinette.utils.filesystem import read_json + + logger.debug("Migrating legacy migration state json to yaml...") + + # We fetch the old state containing the last run migration + old_state = read_json("/etc/yunohost/migrations_state.json")["last_run_migration"] + last_run_migration_id = str(old_state["number"]) + "_" + old_state["name"] + + # Extract the list of migration ids + try: + from . import data_migrations + except ImportError: + # Well ugh what are we supposed to do if we can't do this >.>... + pass + migrations_path = data_migrations.__path__[0] + migration_files = filter(lambda x: re.match("^\d+_[a-zA-Z0-9_]+\.py$", x), os.listdir(migrations_path)) + # (here we remove the .py extension and make sure the ids are sorted) + migration_ids = sorted([f.rsplit(".", 1)[0] for f in migration_files]) + + # So now build the new dict for every id up to the last run migration + migrations = {} + for migration_id in migration_ids: + migrations[migration_id] = "done" + if last_run_migration_id in migration_id: + break + + # Write the new file and rename the old one + write_to_yaml(MIGRATIONS_STATE_PATH, {"migrations": migrations}) + os.rename("/etc/yunohost/migrations_state.json", "/etc/yunohost/migrations_state.json.old") + + def _write_migration_state(migration_id, state): current_states = tools_migrations_state() From add3bd29b014cfe87ce4cc77b5b0eeeebc24ea8a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 5 Aug 2019 21:10:03 +0200 Subject: [PATCH 0011/3170] Add dependencies between migrations --- locales/en.json | 2 +- .../0003_migrate_to_stretch.py | 2 +- .../0004_php5_to_php7_pools.py | 2 ++ .../0005_postgresql_9p4_to_9p6.py | 2 ++ ...0008_ssh_conf_managed_by_yunohost_step2.py | 4 ++- src/yunohost/tools.py | 25 +++++++++++++------ 6 files changed, 27 insertions(+), 10 deletions(-) diff --git a/locales/en.json b/locales/en.json index ecfba2947..14aee512d 100644 --- a/locales/en.json +++ b/locales/en.json @@ -321,6 +321,7 @@ "migrate_tsig_wait_3": "1min…", "migrate_tsig_wait_4": "30 secondes…", "migrate_tsig_not_needed": "You do not appear to use a dyndns domain, so no migration is needed!", + "migration_backward_impossible": "The migration {name} cannot be reverted.", "migration_description_0001_change_cert_group_to_sslcert": "Change certificates group permissions from 'metronome' to 'ssl-cert'", "migration_description_0002_migrate_to_tsig_sha256": "Improve security of dyndns TSIG by using SHA512 instead of MD5", "migration_description_0003_migrate_to_stretch": "Upgrade the system to Debian Stretch and YunoHost 3.0", @@ -332,7 +333,6 @@ "migration_description_0009_decouple_regenconf_from_services": "Decouple the regen-conf mechanism from services", "migration_description_0010_migrate_to_apps_json": "Remove deprecated appslists and use the new unified 'apps.json' list instead", "migration_description_0011_setup_group_permission": "Setup user group and setup permission for apps and services", - "migration_0003_backward_impossible": "The stretch migration cannot be reverted.", "migration_0003_start": "Starting migration to Stretch. The logs will be available in {logfile}.", "migration_0003_patching_sources_list": "Patching the sources.lists…", "migration_0003_main_upgrade": "Starting main upgrade…", diff --git a/src/yunohost/data_migrations/0003_migrate_to_stretch.py b/src/yunohost/data_migrations/0003_migrate_to_stretch.py index 0db719e15..8b7586701 100644 --- a/src/yunohost/data_migrations/0003_migrate_to_stretch.py +++ b/src/yunohost/data_migrations/0003_migrate_to_stretch.py @@ -31,7 +31,7 @@ class MyMigration(Migration): def backward(self): - raise YunohostError("migration_0003_backward_impossible") + raise YunohostError("migration_backward_impossible", name=self.name) def migrate(self): diff --git a/src/yunohost/data_migrations/0004_php5_to_php7_pools.py b/src/yunohost/data_migrations/0004_php5_to_php7_pools.py index 46a5eb91d..faf9ccbbc 100644 --- a/src/yunohost/data_migrations/0004_php5_to_php7_pools.py +++ b/src/yunohost/data_migrations/0004_php5_to_php7_pools.py @@ -22,6 +22,8 @@ class MyMigration(Migration): "Migrate php5-fpm 'pool' conf files to php7 stuff" + dependencies = ["migrate_to_stretch"] + def migrate(self): # Get list of php5 pool files diff --git a/src/yunohost/data_migrations/0005_postgresql_9p4_to_9p6.py b/src/yunohost/data_migrations/0005_postgresql_9p4_to_9p6.py index 5ae729b60..50c0561cb 100644 --- a/src/yunohost/data_migrations/0005_postgresql_9p4_to_9p6.py +++ b/src/yunohost/data_migrations/0005_postgresql_9p4_to_9p6.py @@ -14,6 +14,8 @@ class MyMigration(Migration): "Migrate DBs from Postgresql 9.4 to 9.6 after migrating to Stretch" + dependencies = ["migrate_to_stretch"] + def migrate(self): if not self.package_is_installed("postgresql-9.4"): diff --git a/src/yunohost/data_migrations/0008_ssh_conf_managed_by_yunohost_step2.py b/src/yunohost/data_migrations/0008_ssh_conf_managed_by_yunohost_step2.py index 8984440bd..0ea0ddc3a 100644 --- a/src/yunohost/data_migrations/0008_ssh_conf_managed_by_yunohost_step2.py +++ b/src/yunohost/data_migrations/0008_ssh_conf_managed_by_yunohost_step2.py @@ -33,6 +33,8 @@ class MyMigration(Migration): shown - and the user may also choose to skip this migration. """ + dependencies = ["ssh_conf_managed_by_yunohost_step1"] + def migrate(self): settings_set("service.ssh.allow_deprecated_dsa_hostkey", False) regen_conf(names=['ssh'], force=True) @@ -44,7 +46,7 @@ class MyMigration(Migration): def backward(self): - raise YunohostError("migration_0008_backward_impossible") + raise YunohostError("migration_backward_impossible", name=self.name) @property def mode(self): diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index a15b5593e..9117f42de 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -1052,6 +1052,15 @@ def tools_migrations_migrate(targets=[], skip=False, auto=False, force_rerun=Fal all_migrations = _get_migrations_list() + # Small utility that allows up to get a migration given a name, id or number later + def get_matching_migration(target): + for m in all_migrations: + if m.id == target or m.name == target or m.id.split("_")[0] == target: + return m + + raise YunohostError("No such migration called %s" % target) + + # auto, skip, revert and force are exclusive options if auto + skip + revert + force_rerun > 1: raise YunohostError("--auto, --skip, --revert and --force-rerun are exclusive options.") @@ -1067,13 +1076,6 @@ def tools_migrations_migrate(targets=[], skip=False, auto=False, force_rerun=Fal # If explicit targets are provided, we shall validate them else: - def get_matching_migration(target): - for m in all_migrations: - if m.id == target or m.name == target or m.id.split("_")[0] == target: - return m - - raise YunohostError("No such migration called %s" % target) - targets = [get_matching_migration(t) for t in targets] done = [t.id for t in targets if t.state != "pending"] pending = [t.id for t in targets if t.state == "pending"] @@ -1103,6 +1105,14 @@ def tools_migrations_migrate(targets=[], skip=False, auto=False, force_rerun=Fal # We go to the next migration continue + # Check for migration dependencies + if not revert and not skip: + dependencies = [get_matching_migration(dep) for dep in migration.dependencies] + pending_dependencies = [dep.id for dep in dependencies if dep.state == "pending"] + if pending_dependencies: + logger.error("Can't run migration %s because first you need to run these migrations: %s" % (migration.id, ', '.join(pending_dependencies))) + continue + # If some migrations have disclaimers (and we're not trying to skip them) if migration.disclaimer and not skip: # require the --accept-disclaimer option. @@ -1308,6 +1318,7 @@ class Migration(object): # Those are to be implemented by daughter classes mode = "auto" + dependencies = [] # List of migration ids required before running this migration def forward(self): raise NotImplementedError() From dedc85e52a9b63df171287395e4da96a3a1227dc Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 5 Aug 2019 21:45:45 +0200 Subject: [PATCH 0012/3170] Implement / clean strings + fix some edge cases --- locales/en.json | 30 ++++++++++-------- src/yunohost/tools.py | 73 +++++++++++++++++++++---------------------- 2 files changed, 53 insertions(+), 50 deletions(-) diff --git a/locales/en.json b/locales/en.json index 14aee512d..471f0f9dc 100644 --- a/locales/en.json +++ b/locales/en.json @@ -369,22 +369,26 @@ "migration_0011_rollback_success": "Rollback succeeded.", "migration_0011_update_LDAP_database": "Updating LDAP database...", "migration_0011_update_LDAP_schema": "Updating LDAP schema...", - "migrations_backward": "Migrating backward.", - "migrations_bad_value_for_target": "Invalid number for target argument, available migrations numbers are 0 or {}", + "migrations_already_ran": "Those migrations have already been ran: {ids}", "migrations_cant_reach_migration_file": "Can't access migrations files at path %s", - "migrations_current_target": "Migration target is {}", - "migrations_error_failed_to_load_migration": "ERROR: failed to load migration {number} {name}", - "migrations_forward": "Migrating forward", + "migrations_dependencies_not_satisfied": "Can't run migration {id} because first you need to run these migrations: {dependencies_id}", + "migrations_failed_to_load_migration": "Failed to load migration {id} : {error}", + "migrations_exclusive_options": "--auto, --skip, --revert and --force-rerun are exclusive options.", "migrations_list_conflict_pending_done": "You cannot use both --previous and --done at the same time.", - "migrations_loading_migration": "Loading migration {number} {name}…", - "migrations_migration_has_failed": "Migration {number} {name} has failed with exception {exception}, aborting", + "migrations_loading_migration": "Loading migration {id}…", + "migrations_migration_has_failed": "Migration {id} has failed, aborting. Error: {exception}", + "migrations_must_provide_explicit_targets": "You must provide explicit targets when using --skip, --revert or --force-rerun", + "migrations_need_to_accept_disclaimer": "To run the migration {id}, your must accept the following disclaimer:\n---\n{disclaimer}\n---\nIf you accept to run the migration, please re-run the command with the option --accept-disclaimer.", "migrations_no_migrations_to_run": "No migrations to run", - "migrations_show_currently_running_migration": "Running migration {number} {name}…", - "migrations_show_last_migration": "Last ran migration is {}", - "migrations_skip_migration": "Skipping migration {number} {name}…", - "migrations_success": "Successfully ran migration {number} {name}!", - "migrations_to_be_ran_manually": "Migration {number} {name} has to be ran manually. Please go to Tools > Migrations on the webadmin, or run `yunohost tools migrations migrate`.", - "migrations_need_to_accept_disclaimer": "To run the migration {number} {name}, your must accept the following disclaimer:\n---\n{disclaimer}\n---\nIf you accept to run the migration, please re-run the command with the option --accept-disclaimer.", + "migrations_no_such_migration": "No such migration called {id}", + "migrations_not_pending_cant_skip": "Those migrations are not pending so cannot be skipped: {ids}", + "migrations_pending_cant_revert_or_rerun": "Those migrations are still pending so cannot be reverted or reran: {ids}", + "migrations_running_forward": "Running migration {id}…", + "migrations_running_backward": "Attempting to revert migration {id}…", + "migrations_skip_migration": "Skipping migration {id}…", + "migrations_success_forward": "Successfully ran migration {id}!", + "migrations_success_revert": "Successfully reverted migration {id}!", + "migrations_to_be_ran_manually": "Migration {id} has to be ran manually. Please go to Tools > Migrations on the webadmin, or run `yunohost tools migrations migrate`.", "monitor_disabled": "The server monitoring has been disabled", "monitor_enabled": "The server monitoring has been enabled", "monitor_glances_con_failed": "Unable to connect to Glances server", diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 9117f42de..fa46721c9 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -1042,12 +1042,12 @@ def tools_migrations_migrate(targets=[], skip=False, auto=False, force_rerun=Fal """ Perform migrations - targets : a list of migrations to act on (by default : all pending) - auto : automatic mode, run only 'automatic' migrations (compared to manual migrations). Option meant to be used in debian's postinst script during upgades. - skip : skip specified migrations (must explicit which migrations) - revert : to revert already-ran migrations (must explicit which migrations) - force_rerun : to re-run already ran migrations (if you know what you're doing...) (must explicit which migrations) - accept_disclaimer : accept disclaimer for manual migration that requires it (only valid for one migration) + targets A list migrations to run (all pendings by default) + --skip Skip specified migrations (to be used only if you know what you are doing) (must explicit which migrations) + --auto Automatic mode, won't run manual migrations (to be used only if you know what you are doing) (must explicit which migrations) + --force-rerun Re-run already-ran migrations (to be used only if you know what you are doing)(must explicit which migrations) + --revert Attempt to revert already-ran migrations (must explicit which migrations) + --accept-disclaimer Accept disclaimers of migrations (please read them before using this option) (only valid for one migration) """ all_migrations = _get_migrations_list() @@ -1058,18 +1058,18 @@ def tools_migrations_migrate(targets=[], skip=False, auto=False, force_rerun=Fal if m.id == target or m.name == target or m.id.split("_")[0] == target: return m - raise YunohostError("No such migration called %s" % target) + raise YunohostError("migrations_no_such_migration", id=target) # auto, skip, revert and force are exclusive options if auto + skip + revert + force_rerun > 1: - raise YunohostError("--auto, --skip, --revert and --force-rerun are exclusive options.") + raise YunohostError("migrations_exclusive_options") # If no target specified if not targets: # skip, revert or force require explicit targets if (revert or force_rerun): - raise YunohostError("You must provide explicit targets when using --skip, --revert or --force-rerun") + raise YunohostError("migrations_must_provide_explicit_targets") # Otherwise, targets are all pending migrations targets = [m for m in all_migrations if m.state == "pending"] @@ -1081,9 +1081,11 @@ def tools_migrations_migrate(targets=[], skip=False, auto=False, force_rerun=Fal pending = [t.id for t in targets if t.state == "pending"] if skip and done: - raise YunohostError("Those migrations are not pending so cannot be skipped: %s" % ', '.join(done)) + raise YunohostError("migrations_not_pending_cant_skip", ids=', '.join(done)) if (revert or force_rerun) and pending: - raise YunohostError("Those migrations were not already ran so cannot be reverted or reran: %s" % ', '.join(pending)) + raise YunohostError("migrations_pending_cant_revert_or_rerun", ids=', '.join(pending)) + if not (skip or revert or force_rerun) and done: + raise YunohostError("migrations_already_ran", ids=', '.join(done)) # So, is there actually something to do ? if not targets: @@ -1098,9 +1100,7 @@ def tools_migrations_migrate(targets=[], skip=False, auto=False, force_rerun=Fal # to be ran manually by the user, stop there and ask the user to # run the migration manually. if auto and migration.mode == "manual": - logger.warn(m18n.n('migrations_to_be_ran_manually', - number=migration.number, - name=migration.name)) + logger.warn(m18n.n('migrations_to_be_ran_manually', id=migration.id)) # We go to the next migration continue @@ -1110,17 +1110,18 @@ def tools_migrations_migrate(targets=[], skip=False, auto=False, force_rerun=Fal dependencies = [get_matching_migration(dep) for dep in migration.dependencies] pending_dependencies = [dep.id for dep in dependencies if dep.state == "pending"] if pending_dependencies: - logger.error("Can't run migration %s because first you need to run these migrations: %s" % (migration.id, ', '.join(pending_dependencies))) + logger.error(m18n.n('migrations_dependencies_not_satisfied', + id=migration.id, + dependencies_id=', '.join(pending_dependencies))) continue # If some migrations have disclaimers (and we're not trying to skip them) - if migration.disclaimer and not skip: + if migration.disclaimer and not skip and not revert: # require the --accept-disclaimer option. # Otherwise, go to the next migration if not accept_disclaimer: logger.warn(m18n.n('migrations_need_to_accept_disclaimer', - number=migration.number, - name=migration.name, + id=migration.id, disclaimer=migration.disclaimer)) continue # --accept-disclaimer will only work for the first migration @@ -1133,35 +1134,37 @@ def tools_migrations_migrate(targets=[], skip=False, auto=False, force_rerun=Fal operation_logger.start() if skip: - logger.warn(m18n.n('migrations_skip_migration', - number=migration.number, - name=migration.name)) + logger.warn(m18n.n('migrations_skip_migration', id=migration.id)) + migration.state = "skipped" _write_migration_state(migration.id, "skipped") operation_logger.success() else: - logger.info(m18n.n('migrations_show_currently_running_migration', - number=migration.number, name=migration.name)) - try: migration.operation_logger = operation_logger if revert: + logger.info(m18n.n('migrations_running_backward', id=migration.id)) migration.backward() else: + logger.info(m18n.n('migrations_running_forward', id=migration.id)) migration.migrate() except Exception as e: # migration failed, let's stop here but still update state because # we managed to run the previous ones msg = m18n.n('migrations_migration_has_failed', - exception=e, - number=migration.number, - name=migration.name) + exception=e, id=migration.id) logger.error(msg, exc_info=1) operation_logger.error(msg) else: - logger.success(m18n.n('migrations_success', - number=migration.number, name=migration.name)) - _write_migration_state(migration.id, "done") + if revert: + logger.success(m18n.n('migrations_success_revert', id=migration.id)) + migration.state = "pending" + _write_migration_state(migration.id, "pending") + else: + logger.success(m18n.n('migrations_success_forward', id=migration.id)) + migration.state = "done" + _write_migration_state(migration.id, "done") + operation_logger.success() @@ -1274,10 +1277,7 @@ def _load_migration(migration_file): migration_id = migration_file[:-len(".py")] - number, name = migration_id.split("_", 1) - - logger.debug(m18n.n('migrations_loading_migration', - number=number, name=name)) + logger.debug(m18n.n('migrations_loading_migration', id=migration_id)) try: # this is python builtin method to import a module using a name, we @@ -1285,12 +1285,11 @@ def _load_migration(migration_file): # able to run it in the next loop module = import_module("yunohost.data_migrations.{}".format(migration_id)) return module.MyMigration(migration_id) - except Exception: + except Exception as e: import traceback traceback.print_exc() - raise YunohostError('migrations_error_failed_to_load_migration', - number=number, name=name) + raise YunohostError('migrations_failed_to_load_migration', id=migration_id, error=e) def _skip_all_migrations(): From 74b5ea5982099a7661f169c36e89efb0d6b69df2 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 5 Aug 2019 22:07:48 +0200 Subject: [PATCH 0013/3170] Propagate changes to _skip_all_migrations --- src/yunohost/tools.py | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index fa46721c9..a6379e62b 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -1298,18 +1298,11 @@ def _skip_all_migrations(): This is meant to be used during postinstall to initialize the migration system. """ - state = tools_migrations_state() - - # load all migrations - migrations = _get_migrations_list() - migrations = sorted(migrations, key=lambda x: x.number) - last_migration = migrations[-1] - - state["last_run_migration"] = { - "number": last_migration.number, - "name": last_migration.name - } - write_to_json(MIGRATIONS_STATE_PATH, state) + all_migrations = _get_migrations_list() + new_states = {"migrations": {}} + for migration in all_migrations: + new_states["migrations"][migration.id] = "skipped" + write_to_yaml(MIGRATIONS_STATE_PATH, new_states) class Migration(object): From fc426cff69536417374f334c08dc4e75a59f7941 Mon Sep 17 00:00:00 2001 From: ljf Date: Wed, 14 Aug 2019 12:07:39 +0200 Subject: [PATCH 0014/3170] [fix] Avoid to revert migrations without backward routine --- src/yunohost/data_migrations/0002_migrate_to_tsig_sha256.py | 4 ---- src/yunohost/data_migrations/0003_migrate_to_stretch.py | 6 +----- src/yunohost/data_migrations/0004_php5_to_php7_pools.py | 2 +- src/yunohost/data_migrations/0005_postgresql_9p4_to_9p6.py | 6 +----- .../data_migrations/0006_sync_admin_and_root_passwords.py | 5 +---- .../0007_ssh_conf_managed_by_yunohost_step1.py | 2 +- .../0008_ssh_conf_managed_by_yunohost_step2.py | 6 +----- .../0009_decouple_regenconf_from_services.py | 5 +---- src/yunohost/data_migrations/0010_migrate_to_apps_json.py | 2 +- src/yunohost/data_migrations/0011_setup_group_permission.py | 2 +- src/yunohost/tools.py | 2 +- 11 files changed, 10 insertions(+), 32 deletions(-) diff --git a/src/yunohost/data_migrations/0002_migrate_to_tsig_sha256.py b/src/yunohost/data_migrations/0002_migrate_to_tsig_sha256.py index 824245c82..cc5d4c19b 100644 --- a/src/yunohost/data_migrations/0002_migrate_to_tsig_sha256.py +++ b/src/yunohost/data_migrations/0002_migrate_to_tsig_sha256.py @@ -19,10 +19,6 @@ class MyMigration(Migration): "Migrate Dyndns stuff from MD5 TSIG to SHA512 TSIG" - def backward(self): - # Not possible because that's a non-reversible operation ? - pass - def migrate(self, dyn_host="dyndns.yunohost.org", domain=None, private_key_path=None): if domain is None or private_key_path is None: diff --git a/src/yunohost/data_migrations/0003_migrate_to_stretch.py b/src/yunohost/data_migrations/0003_migrate_to_stretch.py index 8b7586701..0db6cfd6d 100644 --- a/src/yunohost/data_migrations/0003_migrate_to_stretch.py +++ b/src/yunohost/data_migrations/0003_migrate_to_stretch.py @@ -29,11 +29,7 @@ class MyMigration(Migration): mode = "manual" - def backward(self): - - raise YunohostError("migration_backward_impossible", name=self.name) - - def migrate(self): + def forward(self): self.logfile = "/var/log/yunohost/{}.log".format(self.name) diff --git a/src/yunohost/data_migrations/0004_php5_to_php7_pools.py b/src/yunohost/data_migrations/0004_php5_to_php7_pools.py index faf9ccbbc..e916a9cd7 100644 --- a/src/yunohost/data_migrations/0004_php5_to_php7_pools.py +++ b/src/yunohost/data_migrations/0004_php5_to_php7_pools.py @@ -24,7 +24,7 @@ class MyMigration(Migration): dependencies = ["migrate_to_stretch"] - def migrate(self): + def forward(self): # Get list of php5 pool files php5_pool_files = glob.glob("{}/*.conf".format(PHP5_POOLS)) diff --git a/src/yunohost/data_migrations/0005_postgresql_9p4_to_9p6.py b/src/yunohost/data_migrations/0005_postgresql_9p4_to_9p6.py index 50c0561cb..9cb6e51bd 100644 --- a/src/yunohost/data_migrations/0005_postgresql_9p4_to_9p6.py +++ b/src/yunohost/data_migrations/0005_postgresql_9p4_to_9p6.py @@ -16,7 +16,7 @@ class MyMigration(Migration): dependencies = ["migrate_to_stretch"] - def migrate(self): + def forward(self): if not self.package_is_installed("postgresql-9.4"): logger.warning(m18n.n("migration_0005_postgresql_94_not_installed")) @@ -34,10 +34,6 @@ class MyMigration(Migration): subprocess.check_call("pg_dropcluster --stop 9.4 main", shell=True) subprocess.check_call("service postgresql start", shell=True) - def backward(self): - - pass - def package_is_installed(self, package_name): p = subprocess.Popen("dpkg --list | grep '^ii ' | grep -q -w {}".format(package_name), shell=True) diff --git a/src/yunohost/data_migrations/0006_sync_admin_and_root_passwords.py b/src/yunohost/data_migrations/0006_sync_admin_and_root_passwords.py index cd13d680d..953652111 100644 --- a/src/yunohost/data_migrations/0006_sync_admin_and_root_passwords.py +++ b/src/yunohost/data_migrations/0006_sync_admin_and_root_passwords.py @@ -20,16 +20,13 @@ class MyMigration(Migration): "Synchronize admin and root passwords" - def migrate(self): + def forward(self): new_hash = self._get_admin_hash() self._replace_root_hash(new_hash) logger.info(m18n.n("root_password_replaced_by_admin_password")) - def backward(self): - pass - @property def mode(self): diff --git a/src/yunohost/data_migrations/0007_ssh_conf_managed_by_yunohost_step1.py b/src/yunohost/data_migrations/0007_ssh_conf_managed_by_yunohost_step1.py index feffdc27c..7ddcec7fd 100644 --- a/src/yunohost/data_migrations/0007_ssh_conf_managed_by_yunohost_step1.py +++ b/src/yunohost/data_migrations/0007_ssh_conf_managed_by_yunohost_step1.py @@ -34,7 +34,7 @@ class MyMigration(Migration): use the recommended configuration, with an appropriate disclaimer. """ - def migrate(self): + def forward(self): # Check if deprecated DSA Host Key is in config dsa_rgx = r'^[ \t]*HostKey[ \t]+/etc/ssh/ssh_host_dsa_key[ \t]*(?:#.*)?$' diff --git a/src/yunohost/data_migrations/0008_ssh_conf_managed_by_yunohost_step2.py b/src/yunohost/data_migrations/0008_ssh_conf_managed_by_yunohost_step2.py index 0ea0ddc3a..c158192f9 100644 --- a/src/yunohost/data_migrations/0008_ssh_conf_managed_by_yunohost_step2.py +++ b/src/yunohost/data_migrations/0008_ssh_conf_managed_by_yunohost_step2.py @@ -35,7 +35,7 @@ class MyMigration(Migration): dependencies = ["ssh_conf_managed_by_yunohost_step1"] - def migrate(self): + def forward(self): settings_set("service.ssh.allow_deprecated_dsa_hostkey", False) regen_conf(names=['ssh'], force=True) @@ -44,10 +44,6 @@ class MyMigration(Migration): if os.path.isdir(ARCHIVES_PATH): chown(ARCHIVES_PATH, uid="admin", gid="root") - def backward(self): - - raise YunohostError("migration_backward_impossible", name=self.name) - @property def mode(self): diff --git a/src/yunohost/data_migrations/0009_decouple_regenconf_from_services.py b/src/yunohost/data_migrations/0009_decouple_regenconf_from_services.py index d552d7c9c..ffe2279a8 100644 --- a/src/yunohost/data_migrations/0009_decouple_regenconf_from_services.py +++ b/src/yunohost/data_migrations/0009_decouple_regenconf_from_services.py @@ -17,7 +17,7 @@ class MyMigration(Migration): Decouple the regen conf mechanism from the concept of services """ - def migrate(self): + def forward(self): if "conffiles" not in read_file("/etc/yunohost/services.yml") \ or os.path.exists(REGEN_CONF_FILE): @@ -37,6 +37,3 @@ class MyMigration(Migration): # (Actually save the modification of services) _save_services(services) - def backward(self): - - pass diff --git a/src/yunohost/data_migrations/0010_migrate_to_apps_json.py b/src/yunohost/data_migrations/0010_migrate_to_apps_json.py index 43ae9a86f..7092d92c7 100644 --- a/src/yunohost/data_migrations/0010_migrate_to_apps_json.py +++ b/src/yunohost/data_migrations/0010_migrate_to_apps_json.py @@ -15,7 +15,7 @@ class MyMigration(Migration): "Migrate from official.json to apps.json" - def migrate(self): + def forward(self): # Backup current app list json os.system("cp %s %s" % (APPSLISTS_JSON, APPSLISTS_BACKUP)) diff --git a/src/yunohost/data_migrations/0011_setup_group_permission.py b/src/yunohost/data_migrations/0011_setup_group_permission.py index 05c426936..0e371944e 100644 --- a/src/yunohost/data_migrations/0011_setup_group_permission.py +++ b/src/yunohost/data_migrations/0011_setup_group_permission.py @@ -92,7 +92,7 @@ class MyMigration(Migration): app_setting(app, 'allowed_users', delete=True) - def migrate(self): + def forward(self): # Check if the migration can be processed ldap_regen_conf_status = regen_conf(names=['slapd'], dry_run=True) # By this we check if the have been customized diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index a6379e62b..59758acdf 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -1316,7 +1316,7 @@ class Migration(object): raise NotImplementedError() def backward(self): - pass + raise YunohostError("migration_backward_impossible", name=self.name) @property def disclaimer(self): From 81ae41a3e4d5b28b6f44c46ad64a76d2e6cc3bfc Mon Sep 17 00:00:00 2001 From: ljf Date: Wed, 14 Aug 2019 12:27:32 +0200 Subject: [PATCH 0015/3170] [enh] Warn user about --revert option --- data/actionsmap/yunohost.yml | 2 +- src/yunohost/tools.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index f54cc2254..a4dc8d453 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1736,7 +1736,7 @@ tools: help: Skip specified migrations (to be used only if you know what you are doing) action: store_true --revert: - help: Attempt to revert already-ran migrations + help: Attempt to revert already-ran migrations (to be used only if you know what you are doing) action: store_true --force-rerun: help: Re-run already-ran migrations (to be used only if you know what you are doing) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 59758acdf..39333bcaf 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -1046,7 +1046,7 @@ def tools_migrations_migrate(targets=[], skip=False, auto=False, force_rerun=Fal --skip Skip specified migrations (to be used only if you know what you are doing) (must explicit which migrations) --auto Automatic mode, won't run manual migrations (to be used only if you know what you are doing) (must explicit which migrations) --force-rerun Re-run already-ran migrations (to be used only if you know what you are doing)(must explicit which migrations) - --revert Attempt to revert already-ran migrations (must explicit which migrations) + --revert Attempt to revert already-ran migrations (to be used only if you know what you are doing)(must explicit which migrations) --accept-disclaimer Accept disclaimers of migrations (please read them before using this option) (only valid for one migration) """ From c81eab396b5d430b268c1581f942eb234148c272 Mon Sep 17 00:00:00 2001 From: ljf Date: Wed, 14 Aug 2019 12:52:47 +0200 Subject: [PATCH 0016/3170] [enh] Pep8 --- src/yunohost/tools.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 39333bcaf..8c07855a3 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -1060,7 +1060,6 @@ def tools_migrations_migrate(targets=[], skip=False, auto=False, force_rerun=Fal raise YunohostError("migrations_no_such_migration", id=target) - # auto, skip, revert and force are exclusive options if auto + skip + revert + force_rerun > 1: raise YunohostError("migrations_exclusive_options") From 7a0dbf79773785634b0e16720ba6067fb2c034d3 Mon Sep 17 00:00:00 2001 From: ljf Date: Wed, 14 Aug 2019 13:05:23 +0200 Subject: [PATCH 0017/3170] [fix] Ask for specifying migrations to skip --- data/actionsmap/yunohost.yml | 4 ++-- src/yunohost/tools.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index a4dc8d453..054171133 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1736,10 +1736,10 @@ tools: help: Skip specified migrations (to be used only if you know what you are doing) action: store_true --revert: - help: Attempt to revert already-ran migrations (to be used only if you know what you are doing) + help: Attempt to revert already-ran specified migrations (to be used only if you know what you are doing) action: store_true --force-rerun: - help: Re-run already-ran migrations (to be used only if you know what you are doing) + help: Re-run already-ran specified migration (to be used only if you know what you are doing) action: store_true --auto: help: Automatic mode, won't run manual migrations (to be used only if you know what you are doing) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 8c07855a3..9d341ca3d 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -1044,7 +1044,7 @@ def tools_migrations_migrate(targets=[], skip=False, auto=False, force_rerun=Fal targets A list migrations to run (all pendings by default) --skip Skip specified migrations (to be used only if you know what you are doing) (must explicit which migrations) - --auto Automatic mode, won't run manual migrations (to be used only if you know what you are doing) (must explicit which migrations) + --auto Automatic mode, won't run manual migrations (to be used only if you know what you are doing) --force-rerun Re-run already-ran migrations (to be used only if you know what you are doing)(must explicit which migrations) --revert Attempt to revert already-ran migrations (to be used only if you know what you are doing)(must explicit which migrations) --accept-disclaimer Accept disclaimers of migrations (please read them before using this option) (only valid for one migration) @@ -1067,7 +1067,7 @@ def tools_migrations_migrate(targets=[], skip=False, auto=False, force_rerun=Fal # If no target specified if not targets: # skip, revert or force require explicit targets - if (revert or force_rerun): + if (skip or revert or force_rerun): raise YunohostError("migrations_must_provide_explicit_targets") # Otherwise, targets are all pending migrations From f0c0eff41da7a3a1683d6bf4dc4a028a9865f6e9 Mon Sep 17 00:00:00 2001 From: ljf Date: Wed, 14 Aug 2019 13:13:59 +0200 Subject: [PATCH 0018/3170] [enh] english sentence in a comment --- src/yunohost/tools.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 9d341ca3d..d84ca3199 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -1095,9 +1095,9 @@ def tools_migrations_migrate(targets=[], skip=False, auto=False, force_rerun=Fal for migration in targets: # If we are migrating in "automatic mode" (i.e. from debian configure - # during an upgrade of the package) but we are asked to run migrations - # to be ran manually by the user, stop there and ask the user to - # run the migration manually. + # during an upgrade of the package) but we are asked for running + # migrations to be ran manually by the user, stop there and ask the + # user to run the migration manually. if auto and migration.mode == "manual": logger.warn(m18n.n('migrations_to_be_ran_manually', id=migration.id)) From ed3e014feea6abf17f3baab0f5844510c05dba22 Mon Sep 17 00:00:00 2001 From: ljf Date: Wed, 14 Aug 2019 16:46:14 +0200 Subject: [PATCH 0019/3170] [enh] Remove revert mechanism --- data/actionsmap/yunohost.yml | 3 - locales/en.json | 2 - .../0001_change_cert_group_to_sslcert.py | 15 +-- .../0002_migrate_to_tsig_sha256.py | 2 +- .../0003_migrate_to_stretch.py | 2 +- .../0004_php5_to_php7_pools.py | 116 +++++++++--------- .../0005_postgresql_9p4_to_9p6.py | 2 +- .../0006_sync_admin_and_root_passwords.py | 2 +- ...0007_ssh_conf_managed_by_yunohost_step1.py | 28 ++--- ...0008_ssh_conf_managed_by_yunohost_step2.py | 2 +- .../0009_decouple_regenconf_from_services.py | 2 +- .../0010_migrate_to_apps_json.py | 28 +++-- .../0011_setup_group_permission.py | 2 +- src/yunohost/tools.py | 50 +++----- 14 files changed, 118 insertions(+), 138 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 054171133..725f14a03 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1735,9 +1735,6 @@ tools: --skip: help: Skip specified migrations (to be used only if you know what you are doing) action: store_true - --revert: - help: Attempt to revert already-ran specified migrations (to be used only if you know what you are doing) - action: store_true --force-rerun: help: Re-run already-ran specified migration (to be used only if you know what you are doing) action: store_true diff --git a/locales/en.json b/locales/en.json index 471f0f9dc..850d89032 100644 --- a/locales/en.json +++ b/locales/en.json @@ -297,7 +297,6 @@ "log_user_permission_remove": "Update '{}' permission", "log_tools_maindomain": "Make '{}' as main domain", "log_tools_migrations_migrate_forward": "Migrate forward", - "log_tools_migrations_migrate_backward": "Migrate backward", "log_tools_postinstall": "Postinstall your YunoHost server", "log_tools_upgrade": "Upgrade system packages", "log_tools_shutdown": "Shutdown your server", @@ -384,7 +383,6 @@ "migrations_not_pending_cant_skip": "Those migrations are not pending so cannot be skipped: {ids}", "migrations_pending_cant_revert_or_rerun": "Those migrations are still pending so cannot be reverted or reran: {ids}", "migrations_running_forward": "Running migration {id}…", - "migrations_running_backward": "Attempting to revert migration {id}…", "migrations_skip_migration": "Skipping migration {id}…", "migrations_success_forward": "Successfully ran migration {id}!", "migrations_success_revert": "Successfully reverted migration {id}!", diff --git a/src/yunohost/data_migrations/0001_change_cert_group_to_sslcert.py b/src/yunohost/data_migrations/0001_change_cert_group_to_sslcert.py index 6485861b7..4338e10f3 100644 --- a/src/yunohost/data_migrations/0001_change_cert_group_to_sslcert.py +++ b/src/yunohost/data_migrations/0001_change_cert_group_to_sslcert.py @@ -10,10 +10,11 @@ class MyMigration(Migration): all_certificate_files = glob.glob("/etc/yunohost/certs/*/*.pem") - def forward(self): - for filename in self.all_certificate_files: - chown(filename, uid="root", gid="ssl-cert") - - def backward(self): - for filename in self.all_certificate_files: - chown(filename, uid="root", gid="metronome") + def run(self): + try: + for filename in self.all_certificate_files: + chown(filename, uid="root", gid="ssl-cert") + except Exception: + for filename in self.all_certificate_files: + chown(filename, uid="root", gid="metronome") + raise diff --git a/src/yunohost/data_migrations/0002_migrate_to_tsig_sha256.py b/src/yunohost/data_migrations/0002_migrate_to_tsig_sha256.py index cc5d4c19b..65158ba2c 100644 --- a/src/yunohost/data_migrations/0002_migrate_to_tsig_sha256.py +++ b/src/yunohost/data_migrations/0002_migrate_to_tsig_sha256.py @@ -19,7 +19,7 @@ class MyMigration(Migration): "Migrate Dyndns stuff from MD5 TSIG to SHA512 TSIG" - def migrate(self, dyn_host="dyndns.yunohost.org", domain=None, private_key_path=None): + def run(self, dyn_host="dyndns.yunohost.org", domain=None, private_key_path=None): if domain is None or private_key_path is None: try: diff --git a/src/yunohost/data_migrations/0003_migrate_to_stretch.py b/src/yunohost/data_migrations/0003_migrate_to_stretch.py index 0db6cfd6d..19793bbec 100644 --- a/src/yunohost/data_migrations/0003_migrate_to_stretch.py +++ b/src/yunohost/data_migrations/0003_migrate_to_stretch.py @@ -29,7 +29,7 @@ class MyMigration(Migration): mode = "manual" - def forward(self): + def run(self): self.logfile = "/var/log/yunohost/{}.log".format(self.name) diff --git a/src/yunohost/data_migrations/0004_php5_to_php7_pools.py b/src/yunohost/data_migrations/0004_php5_to_php7_pools.py index e916a9cd7..7bb9123ec 100644 --- a/src/yunohost/data_migrations/0004_php5_to_php7_pools.py +++ b/src/yunohost/data_migrations/0004_php5_to_php7_pools.py @@ -24,77 +24,77 @@ class MyMigration(Migration): dependencies = ["migrate_to_stretch"] - def forward(self): + def run(self): + try: + # Get list of php5 pool files + php5_pool_files = glob.glob("{}/*.conf".format(PHP5_POOLS)) - # Get list of php5 pool files - php5_pool_files = glob.glob("{}/*.conf".format(PHP5_POOLS)) + # Keep only basenames + php5_pool_files = [os.path.basename(f) for f in php5_pool_files] - # Keep only basenames - php5_pool_files = [os.path.basename(f) for f in php5_pool_files] + # Ignore the "www.conf" (default stuff, probably don't want to touch it ?) + php5_pool_files = [f for f in php5_pool_files if f != "www.conf"] - # Ignore the "www.conf" (default stuff, probably don't want to touch it ?) - php5_pool_files = [f for f in php5_pool_files if f != "www.conf"] + for f in php5_pool_files: - for f in php5_pool_files: + # Copy the files to the php7 pool + src = "{}/{}".format(PHP5_POOLS, f) + dest = "{}/{}".format(PHP7_POOLS, f) + copy2(src, dest) - # Copy the files to the php7 pool - src = "{}/{}".format(PHP5_POOLS, f) - dest = "{}/{}".format(PHP7_POOLS, f) - copy2(src, dest) + # Replace the socket prefix if it's found + c = "sed -i -e 's@{}@{}@g' {}".format(PHP5_SOCKETS_PREFIX, PHP7_SOCKETS_PREFIX, dest) + os.system(c) - # Replace the socket prefix if it's found - c = "sed -i -e 's@{}@{}@g' {}".format(PHP5_SOCKETS_PREFIX, PHP7_SOCKETS_PREFIX, dest) - os.system(c) + # Also add a comment that it was automatically moved from php5 + # (for human traceability and backward migration) + c = "sed -i '1i {}' {}".format(MIGRATION_COMMENT, dest) + os.system(c) - # Also add a comment that it was automatically moved from php5 - # (for human traceability and backward migration) - c = "sed -i '1i {}' {}".format(MIGRATION_COMMENT, dest) - os.system(c) + # Some old comments starting with '#' instead of ';' are not + # compatible in php7 + c = "sed -i 's/^#/;#/g' {}".format(dest) + os.system(c) - # Some old comments starting with '#' instead of ';' are not - # compatible in php7 - c = "sed -i 's/^#/;#/g' {}".format(dest) - os.system(c) + # Reload/restart the php pools + _run_service_command("restart", "php7.0-fpm") + _run_service_command("enable", "php7.0-fpm") + os.system("systemctl stop php5-fpm") + os.system("systemctl disable php5-fpm") + os.system("rm /etc/logrotate.d/php5-fpm") # We remove this otherwise the logrotate cron will be unhappy - # Reload/restart the php pools - _run_service_command("restart", "php7.0-fpm") - _run_service_command("enable", "php7.0-fpm") - os.system("systemctl stop php5-fpm") - os.system("systemctl disable php5-fpm") - os.system("rm /etc/logrotate.d/php5-fpm") # We remove this otherwise the logrotate cron will be unhappy + # Get list of nginx conf file + nginx_conf_files = glob.glob("/etc/nginx/conf.d/*.d/*.conf") + for f in nginx_conf_files: + # Replace the socket prefix if it's found + c = "sed -i -e 's@{}@{}@g' {}".format(PHP5_SOCKETS_PREFIX, PHP7_SOCKETS_PREFIX, f) + os.system(c) - # Get list of nginx conf file - nginx_conf_files = glob.glob("/etc/nginx/conf.d/*.d/*.conf") - for f in nginx_conf_files: - # Replace the socket prefix if it's found - c = "sed -i -e 's@{}@{}@g' {}".format(PHP5_SOCKETS_PREFIX, PHP7_SOCKETS_PREFIX, f) - os.system(c) + # Reload nginx + _run_service_command("reload", "nginx") + except Exception: - # Reload nginx - _run_service_command("reload", "nginx") + # Get list of php7 pool files + php7_pool_files = glob.glob("{}/*.conf".format(PHP7_POOLS)) - def backward(self): + # Keep only files which have the migration comment + php7_pool_files = [f for f in php7_pool_files if open(f).readline().strip() == MIGRATION_COMMENT] - # Get list of php7 pool files - php7_pool_files = glob.glob("{}/*.conf".format(PHP7_POOLS)) + # Delete those files + for f in php7_pool_files: + os.remove(f) - # Keep only files which have the migration comment - php7_pool_files = [f for f in php7_pool_files if open(f).readline().strip() == MIGRATION_COMMENT] + # Reload/restart the php pools + _run_service_command("stop", "php7.0-fpm") + os.system("systemctl start php5-fpm") - # Delete those files - for f in php7_pool_files: - os.remove(f) + # Get list of nginx conf file + nginx_conf_files = glob.glob("/etc/nginx/conf.d/*.d/*.conf") + for f in nginx_conf_files: + # Replace the socket prefix if it's found + c = "sed -i -e 's@{}@{}@g' {}".format(PHP7_SOCKETS_PREFIX, PHP5_SOCKETS_PREFIX, f) + os.system(c) - # Reload/restart the php pools - _run_service_command("stop", "php7.0-fpm") - os.system("systemctl start php5-fpm") - - # Get list of nginx conf file - nginx_conf_files = glob.glob("/etc/nginx/conf.d/*.d/*.conf") - for f in nginx_conf_files: - # Replace the socket prefix if it's found - c = "sed -i -e 's@{}@{}@g' {}".format(PHP7_SOCKETS_PREFIX, PHP5_SOCKETS_PREFIX, f) - os.system(c) - - # Reload nginx - _run_service_command("reload", "nginx") + # Reload nginx + _run_service_command("reload", "nginx") + raise diff --git a/src/yunohost/data_migrations/0005_postgresql_9p4_to_9p6.py b/src/yunohost/data_migrations/0005_postgresql_9p4_to_9p6.py index 9cb6e51bd..3127f2c65 100644 --- a/src/yunohost/data_migrations/0005_postgresql_9p4_to_9p6.py +++ b/src/yunohost/data_migrations/0005_postgresql_9p4_to_9p6.py @@ -16,7 +16,7 @@ class MyMigration(Migration): dependencies = ["migrate_to_stretch"] - def forward(self): + def run(self): if not self.package_is_installed("postgresql-9.4"): logger.warning(m18n.n("migration_0005_postgresql_94_not_installed")) diff --git a/src/yunohost/data_migrations/0006_sync_admin_and_root_passwords.py b/src/yunohost/data_migrations/0006_sync_admin_and_root_passwords.py index 953652111..844e3028c 100644 --- a/src/yunohost/data_migrations/0006_sync_admin_and_root_passwords.py +++ b/src/yunohost/data_migrations/0006_sync_admin_and_root_passwords.py @@ -20,7 +20,7 @@ class MyMigration(Migration): "Synchronize admin and root passwords" - def forward(self): + def run(self): new_hash = self._get_admin_hash() self._replace_root_hash(new_hash) diff --git a/src/yunohost/data_migrations/0007_ssh_conf_managed_by_yunohost_step1.py b/src/yunohost/data_migrations/0007_ssh_conf_managed_by_yunohost_step1.py index 7ddcec7fd..99eb90b19 100644 --- a/src/yunohost/data_migrations/0007_ssh_conf_managed_by_yunohost_step1.py +++ b/src/yunohost/data_migrations/0007_ssh_conf_managed_by_yunohost_step1.py @@ -34,7 +34,7 @@ class MyMigration(Migration): use the recommended configuration, with an appropriate disclaimer. """ - def forward(self): + def run(self): # Check if deprecated DSA Host Key is in config dsa_rgx = r'^[ \t]*HostKey[ \t]+/etc/ssh/ssh_host_dsa_key[ \t]*(?:#.*)?$' @@ -55,19 +55,13 @@ class MyMigration(Migration): # right after the regenconf, such that it will appear as # "manually modified". if os.path.exists('/etc/yunohost/from_script'): - rm('/etc/yunohost/from_script') - copyfile(SSHD_CONF, '/etc/ssh/sshd_config.bkp') - regen_conf(names=['ssh'], force=True) - copyfile('/etc/ssh/sshd_config.bkp', SSHD_CONF) - - # Restart ssh and backward if it fail - if not _run_service_command('restart', 'ssh'): - self.backward() - raise YunohostError("migration_0007_cancel") - - def backward(self): - - # We don't backward completely but it should be enough - copyfile('/etc/ssh/sshd_config.bkp', SSHD_CONF) - if not _run_service_command('restart', 'ssh'): - raise YunohostError("migration_0007_cannot_restart") + try: + rm('/etc/yunohost/from_script') + copyfile(SSHD_CONF, '/etc/ssh/sshd_config.bkp') + regen_conf(names=['ssh'], force=True) + copyfile('/etc/ssh/sshd_config.bkp', SSHD_CONF) + except Exception: + if os.path.exists('/etc/yunohost/sshd_config.bkp'): + copyfile('/etc/ssh/sshd_config.bkp', SSHD_CONF) + _run_service_command('restart', 'ssh') + raise diff --git a/src/yunohost/data_migrations/0008_ssh_conf_managed_by_yunohost_step2.py b/src/yunohost/data_migrations/0008_ssh_conf_managed_by_yunohost_step2.py index c158192f9..ecc8cfdcb 100644 --- a/src/yunohost/data_migrations/0008_ssh_conf_managed_by_yunohost_step2.py +++ b/src/yunohost/data_migrations/0008_ssh_conf_managed_by_yunohost_step2.py @@ -35,7 +35,7 @@ class MyMigration(Migration): dependencies = ["ssh_conf_managed_by_yunohost_step1"] - def forward(self): + def run(self): settings_set("service.ssh.allow_deprecated_dsa_hostkey", False) regen_conf(names=['ssh'], force=True) diff --git a/src/yunohost/data_migrations/0009_decouple_regenconf_from_services.py b/src/yunohost/data_migrations/0009_decouple_regenconf_from_services.py index ffe2279a8..c190e2aaa 100644 --- a/src/yunohost/data_migrations/0009_decouple_regenconf_from_services.py +++ b/src/yunohost/data_migrations/0009_decouple_regenconf_from_services.py @@ -17,7 +17,7 @@ class MyMigration(Migration): Decouple the regen conf mechanism from the concept of services """ - def forward(self): + def run(self): if "conffiles" not in read_file("/etc/yunohost/services.yml") \ or os.path.exists(REGEN_CONF_FILE): diff --git a/src/yunohost/data_migrations/0010_migrate_to_apps_json.py b/src/yunohost/data_migrations/0010_migrate_to_apps_json.py index 7092d92c7..fbcec5117 100644 --- a/src/yunohost/data_migrations/0010_migrate_to_apps_json.py +++ b/src/yunohost/data_migrations/0010_migrate_to_apps_json.py @@ -15,7 +15,7 @@ class MyMigration(Migration): "Migrate from official.json to apps.json" - def forward(self): + def run(self): # Backup current app list json os.system("cp %s %s" % (APPSLISTS_JSON, APPSLISTS_BACKUP)) @@ -28,17 +28,21 @@ class MyMigration(Migration): "labriqueinter.net/apps/labriqueinternet.json", "labriqueinter.net/internetcube.json" ] + try: + appslists = _read_appslist_list() + for appslist, infos in appslists.items(): + if infos["url"].split("//")[-1] in lists_to_remove: + app_removelist(name=appslist) - appslists = _read_appslist_list() - for appslist, infos in appslists.items(): - if infos["url"].split("//")[-1] in lists_to_remove: - app_removelist(name=appslist) + # Replace by apps.json list + app_fetchlist(name="yunohost", + url="https://app.yunohost.org/apps.json") + except Exception: + if os.path.exists(APPSLISTS_BACKUP): + os.system("cp %s %s" % (APPSLISTS_BACKUP, APPSLISTS_JSON)) + raise + else: + if os.path.exists(APPSLISTS_BACKUP): + os.remove(APPSLISTS_BACKUP) - # Replace by apps.json list - app_fetchlist(name="yunohost", - url="https://app.yunohost.org/apps.json") - def backward(self): - - if os.path.exists(APPSLISTS_BACKUP): - os.system("cp %s %s" % (APPSLISTS_BACKUP, APPSLISTS_JSON)) diff --git a/src/yunohost/data_migrations/0011_setup_group_permission.py b/src/yunohost/data_migrations/0011_setup_group_permission.py index 0e371944e..17fd8f4a5 100644 --- a/src/yunohost/data_migrations/0011_setup_group_permission.py +++ b/src/yunohost/data_migrations/0011_setup_group_permission.py @@ -92,7 +92,7 @@ class MyMigration(Migration): app_setting(app, 'allowed_users', delete=True) - def forward(self): + def run(self): # Check if the migration can be processed ldap_regen_conf_status = regen_conf(names=['slapd'], dry_run=True) # By this we check if the have been customized diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index d84ca3199..1c185e90c 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -1038,7 +1038,7 @@ def tools_migrations_list(pending=False, done=False): return {"migrations": migrations} -def tools_migrations_migrate(targets=[], skip=False, auto=False, force_rerun=False, revert=False, accept_disclaimer=False): +def tools_migrations_migrate(targets=[], skip=False, auto=False, force_rerun=False, accept_disclaimer=False): """ Perform migrations @@ -1046,7 +1046,6 @@ def tools_migrations_migrate(targets=[], skip=False, auto=False, force_rerun=Fal --skip Skip specified migrations (to be used only if you know what you are doing) (must explicit which migrations) --auto Automatic mode, won't run manual migrations (to be used only if you know what you are doing) --force-rerun Re-run already-ran migrations (to be used only if you know what you are doing)(must explicit which migrations) - --revert Attempt to revert already-ran migrations (to be used only if you know what you are doing)(must explicit which migrations) --accept-disclaimer Accept disclaimers of migrations (please read them before using this option) (only valid for one migration) """ @@ -1060,14 +1059,14 @@ def tools_migrations_migrate(targets=[], skip=False, auto=False, force_rerun=Fal raise YunohostError("migrations_no_such_migration", id=target) - # auto, skip, revert and force are exclusive options - if auto + skip + revert + force_rerun > 1: + # auto, skip and force are exclusive options + if auto + skip + force_rerun > 1: raise YunohostError("migrations_exclusive_options") # If no target specified if not targets: # skip, revert or force require explicit targets - if (skip or revert or force_rerun): + if (skip or force_rerun): raise YunohostError("migrations_must_provide_explicit_targets") # Otherwise, targets are all pending migrations @@ -1081,9 +1080,9 @@ def tools_migrations_migrate(targets=[], skip=False, auto=False, force_rerun=Fal if skip and done: raise YunohostError("migrations_not_pending_cant_skip", ids=', '.join(done)) - if (revert or force_rerun) and pending: + if force_rerun and pending: raise YunohostError("migrations_pending_cant_revert_or_rerun", ids=', '.join(pending)) - if not (skip or revert or force_rerun) and done: + if not (skip or force_rerun) and done: raise YunohostError("migrations_already_ran", ids=', '.join(done)) # So, is there actually something to do ? @@ -1105,7 +1104,7 @@ def tools_migrations_migrate(targets=[], skip=False, auto=False, force_rerun=Fal continue # Check for migration dependencies - if not revert and not skip: + if not skip: dependencies = [get_matching_migration(dep) for dep in migration.dependencies] pending_dependencies = [dep.id for dep in dependencies if dep.state == "pending"] if pending_dependencies: @@ -1115,7 +1114,7 @@ def tools_migrations_migrate(targets=[], skip=False, auto=False, force_rerun=Fal continue # If some migrations have disclaimers (and we're not trying to skip them) - if migration.disclaimer and not skip and not revert: + if migration.disclaimer and not skip: # require the --accept-disclaimer option. # Otherwise, go to the next migration if not accept_disclaimer: @@ -1128,8 +1127,7 @@ def tools_migrations_migrate(targets=[], skip=False, auto=False, force_rerun=Fal accept_disclaimer = False # Start register change on system - mode = "backward" if revert else "forward" - operation_logger = OperationLogger('tools_migrations_migrate_' + mode) + operation_logger = OperationLogger('tools_migrations_migrate_forward') operation_logger.start() if skip: @@ -1141,12 +1139,8 @@ def tools_migrations_migrate(targets=[], skip=False, auto=False, force_rerun=Fal try: migration.operation_logger = operation_logger - if revert: - logger.info(m18n.n('migrations_running_backward', id=migration.id)) - migration.backward() - else: logger.info(m18n.n('migrations_running_forward', id=migration.id)) - migration.migrate() + migration.run() except Exception as e: # migration failed, let's stop here but still update state because # we managed to run the previous ones @@ -1155,14 +1149,9 @@ def tools_migrations_migrate(targets=[], skip=False, auto=False, force_rerun=Fal logger.error(msg, exc_info=1) operation_logger.error(msg) else: - if revert: - logger.success(m18n.n('migrations_success_revert', id=migration.id)) - migration.state = "pending" - _write_migration_state(migration.id, "pending") - else: - logger.success(m18n.n('migrations_success_forward', id=migration.id)) - migration.state = "done" - _write_migration_state(migration.id, "done") + logger.success(m18n.n('migrations_success_forward', id=migration.id)) + migration.state = "done" + _write_migration_state(migration.id, "done") operation_logger.success() @@ -1304,6 +1293,9 @@ def _skip_all_migrations(): write_to_yaml(MIGRATIONS_STATE_PATH, new_states) +def _get_revert_dependencies(migration, all_migrations): + + class Migration(object): # Those are to be implemented by daughter classes @@ -1311,20 +1303,14 @@ class Migration(object): mode = "auto" dependencies = [] # List of migration ids required before running this migration - def forward(self): - raise NotImplementedError() - - def backward(self): - raise YunohostError("migration_backward_impossible", name=self.name) - @property def disclaimer(self): return None # The followings shouldn't be overriden - def migrate(self): - self.forward() + def run(self): + raise NotImplementedError() def __init__(self, id_): self.id = id_ From fac44f44d06f120625cbc69fefbb3e28f57093d2 Mon Sep 17 00:00:00 2001 From: ljf Date: Wed, 14 Aug 2019 17:29:24 +0200 Subject: [PATCH 0020/3170] [enh] Avoid to modify untestable migrations --- .../0001_change_cert_group_to_sslcert.py | 9 ++------- .../0007_ssh_conf_managed_by_yunohost_step1.py | 14 ++++---------- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/src/yunohost/data_migrations/0001_change_cert_group_to_sslcert.py b/src/yunohost/data_migrations/0001_change_cert_group_to_sslcert.py index 4338e10f3..5670f3329 100644 --- a/src/yunohost/data_migrations/0001_change_cert_group_to_sslcert.py +++ b/src/yunohost/data_migrations/0001_change_cert_group_to_sslcert.py @@ -11,10 +11,5 @@ class MyMigration(Migration): all_certificate_files = glob.glob("/etc/yunohost/certs/*/*.pem") def run(self): - try: - for filename in self.all_certificate_files: - chown(filename, uid="root", gid="ssl-cert") - except Exception: - for filename in self.all_certificate_files: - chown(filename, uid="root", gid="metronome") - raise + for filename in self.all_certificate_files: + chown(filename, uid="root", gid="ssl-cert") diff --git a/src/yunohost/data_migrations/0007_ssh_conf_managed_by_yunohost_step1.py b/src/yunohost/data_migrations/0007_ssh_conf_managed_by_yunohost_step1.py index 99eb90b19..818760e17 100644 --- a/src/yunohost/data_migrations/0007_ssh_conf_managed_by_yunohost_step1.py +++ b/src/yunohost/data_migrations/0007_ssh_conf_managed_by_yunohost_step1.py @@ -55,13 +55,7 @@ class MyMigration(Migration): # right after the regenconf, such that it will appear as # "manually modified". if os.path.exists('/etc/yunohost/from_script'): - try: - rm('/etc/yunohost/from_script') - copyfile(SSHD_CONF, '/etc/ssh/sshd_config.bkp') - regen_conf(names=['ssh'], force=True) - copyfile('/etc/ssh/sshd_config.bkp', SSHD_CONF) - except Exception: - if os.path.exists('/etc/yunohost/sshd_config.bkp'): - copyfile('/etc/ssh/sshd_config.bkp', SSHD_CONF) - _run_service_command('restart', 'ssh') - raise + rm('/etc/yunohost/from_script') + copyfile(SSHD_CONF, '/etc/ssh/sshd_config.bkp') + regen_conf(names=['ssh'], force=True) + copyfile('/etc/ssh/sshd_config.bkp', SSHD_CONF) From 0a2ba102c883c0a5b96cf451ab8a7a0c6fedb0dd Mon Sep 17 00:00:00 2001 From: ljf Date: Wed, 14 Aug 2019 17:37:43 +0200 Subject: [PATCH 0021/3170] [enh] Avoid to modify untestable migrations --- .../0004_php5_to_php7_pools.py | 77 +++++++++---------- 1 file changed, 38 insertions(+), 39 deletions(-) diff --git a/src/yunohost/data_migrations/0004_php5_to_php7_pools.py b/src/yunohost/data_migrations/0004_php5_to_php7_pools.py index 7bb9123ec..3bc6bacc4 100644 --- a/src/yunohost/data_migrations/0004_php5_to_php7_pools.py +++ b/src/yunohost/data_migrations/0004_php5_to_php7_pools.py @@ -25,54 +25,54 @@ class MyMigration(Migration): dependencies = ["migrate_to_stretch"] def run(self): - try: - # Get list of php5 pool files - php5_pool_files = glob.glob("{}/*.conf".format(PHP5_POOLS)) + # Get list of php5 pool files + php5_pool_files = glob.glob("{}/*.conf".format(PHP5_POOLS)) - # Keep only basenames - php5_pool_files = [os.path.basename(f) for f in php5_pool_files] + # Keep only basenames + php5_pool_files = [os.path.basename(f) for f in php5_pool_files] - # Ignore the "www.conf" (default stuff, probably don't want to touch it ?) - php5_pool_files = [f for f in php5_pool_files if f != "www.conf"] + # Ignore the "www.conf" (default stuff, probably don't want to touch it ?) + php5_pool_files = [f for f in php5_pool_files if f != "www.conf"] - for f in php5_pool_files: + for f in php5_pool_files: - # Copy the files to the php7 pool - src = "{}/{}".format(PHP5_POOLS, f) - dest = "{}/{}".format(PHP7_POOLS, f) - copy2(src, dest) + # Copy the files to the php7 pool + src = "{}/{}".format(PHP5_POOLS, f) + dest = "{}/{}".format(PHP7_POOLS, f) + copy2(src, dest) - # Replace the socket prefix if it's found - c = "sed -i -e 's@{}@{}@g' {}".format(PHP5_SOCKETS_PREFIX, PHP7_SOCKETS_PREFIX, dest) - os.system(c) + # Replace the socket prefix if it's found + c = "sed -i -e 's@{}@{}@g' {}".format(PHP5_SOCKETS_PREFIX, PHP7_SOCKETS_PREFIX, dest) + os.system(c) - # Also add a comment that it was automatically moved from php5 - # (for human traceability and backward migration) - c = "sed -i '1i {}' {}".format(MIGRATION_COMMENT, dest) - os.system(c) + # Also add a comment that it was automatically moved from php5 + # (for human traceability and backward migration) + c = "sed -i '1i {}' {}".format(MIGRATION_COMMENT, dest) + os.system(c) - # Some old comments starting with '#' instead of ';' are not - # compatible in php7 - c = "sed -i 's/^#/;#/g' {}".format(dest) - os.system(c) + # Some old comments starting with '#' instead of ';' are not + # compatible in php7 + c = "sed -i 's/^#/;#/g' {}".format(dest) + os.system(c) - # Reload/restart the php pools - _run_service_command("restart", "php7.0-fpm") - _run_service_command("enable", "php7.0-fpm") - os.system("systemctl stop php5-fpm") - os.system("systemctl disable php5-fpm") - os.system("rm /etc/logrotate.d/php5-fpm") # We remove this otherwise the logrotate cron will be unhappy + # Reload/restart the php pools + _run_service_command("restart", "php7.0-fpm") + _run_service_command("enable", "php7.0-fpm") + os.system("systemctl stop php5-fpm") + os.system("systemctl disable php5-fpm") + os.system("rm /etc/logrotate.d/php5-fpm") # We remove this otherwise the logrotate cron will be unhappy - # Get list of nginx conf file - nginx_conf_files = glob.glob("/etc/nginx/conf.d/*.d/*.conf") - for f in nginx_conf_files: - # Replace the socket prefix if it's found - c = "sed -i -e 's@{}@{}@g' {}".format(PHP5_SOCKETS_PREFIX, PHP7_SOCKETS_PREFIX, f) - os.system(c) + # Get list of nginx conf file + nginx_conf_files = glob.glob("/etc/nginx/conf.d/*.d/*.conf") + for f in nginx_conf_files: + # Replace the socket prefix if it's found + c = "sed -i -e 's@{}@{}@g' {}".format(PHP5_SOCKETS_PREFIX, PHP7_SOCKETS_PREFIX, f) + os.system(c) - # Reload nginx - _run_service_command("reload", "nginx") - except Exception: + # Reload nginx + _run_service_command("reload", "nginx") + + def backward(self): # Get list of php7 pool files php7_pool_files = glob.glob("{}/*.conf".format(PHP7_POOLS)) @@ -97,4 +97,3 @@ class MyMigration(Migration): # Reload nginx _run_service_command("reload", "nginx") - raise From f98ad4f191d7ac7aea84a17df09c36c7d2538e70 Mon Sep 17 00:00:00 2001 From: ljf Date: Wed, 14 Aug 2019 17:39:23 +0200 Subject: [PATCH 0022/3170] [enh] Avoid to modify untestable migrations --- .../0004_php5_to_php7_pools.py | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/yunohost/data_migrations/0004_php5_to_php7_pools.py b/src/yunohost/data_migrations/0004_php5_to_php7_pools.py index 3bc6bacc4..1b90c4ff0 100644 --- a/src/yunohost/data_migrations/0004_php5_to_php7_pools.py +++ b/src/yunohost/data_migrations/0004_php5_to_php7_pools.py @@ -72,28 +72,28 @@ class MyMigration(Migration): # Reload nginx _run_service_command("reload", "nginx") - def backward(self): + def backward(self): - # Get list of php7 pool files - php7_pool_files = glob.glob("{}/*.conf".format(PHP7_POOLS)) + # Get list of php7 pool files + php7_pool_files = glob.glob("{}/*.conf".format(PHP7_POOLS)) - # Keep only files which have the migration comment - php7_pool_files = [f for f in php7_pool_files if open(f).readline().strip() == MIGRATION_COMMENT] + # Keep only files which have the migration comment + php7_pool_files = [f for f in php7_pool_files if open(f).readline().strip() == MIGRATION_COMMENT] - # Delete those files - for f in php7_pool_files: - os.remove(f) + # Delete those files + for f in php7_pool_files: + os.remove(f) - # Reload/restart the php pools - _run_service_command("stop", "php7.0-fpm") - os.system("systemctl start php5-fpm") + # Reload/restart the php pools + _run_service_command("stop", "php7.0-fpm") + os.system("systemctl start php5-fpm") - # Get list of nginx conf file - nginx_conf_files = glob.glob("/etc/nginx/conf.d/*.d/*.conf") - for f in nginx_conf_files: - # Replace the socket prefix if it's found - c = "sed -i -e 's@{}@{}@g' {}".format(PHP7_SOCKETS_PREFIX, PHP5_SOCKETS_PREFIX, f) - os.system(c) + # Get list of nginx conf file + nginx_conf_files = glob.glob("/etc/nginx/conf.d/*.d/*.conf") + for f in nginx_conf_files: + # Replace the socket prefix if it's found + c = "sed -i -e 's@{}@{}@g' {}".format(PHP7_SOCKETS_PREFIX, PHP5_SOCKETS_PREFIX, f) + os.system(c) - # Reload nginx - _run_service_command("reload", "nginx") + # Reload nginx + _run_service_command("reload", "nginx") From b54266cbf3ffb5f3b38ba3280474b01ef1aedcb5 Mon Sep 17 00:00:00 2001 From: ljf Date: Wed, 14 Aug 2019 17:55:17 +0200 Subject: [PATCH 0023/3170] [fix] Unwanted residual code --- src/yunohost/tools.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 1c185e90c..d2015adff 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -1139,8 +1139,8 @@ def tools_migrations_migrate(targets=[], skip=False, auto=False, force_rerun=Fal try: migration.operation_logger = operation_logger - logger.info(m18n.n('migrations_running_forward', id=migration.id)) - migration.run() + logger.info(m18n.n('migrations_running_forward', id=migration.id)) + migration.run() except Exception as e: # migration failed, let's stop here but still update state because # we managed to run the previous ones @@ -1293,9 +1293,6 @@ def _skip_all_migrations(): write_to_yaml(MIGRATIONS_STATE_PATH, new_states) -def _get_revert_dependencies(migration, all_migrations): - - class Migration(object): # Those are to be implemented by daughter classes From 82f48213ab21247fff96ae493cea879accfd9897 Mon Sep 17 00:00:00 2001 From: tufek yamero Date: Tue, 13 Aug 2019 19:40:47 +0000 Subject: [PATCH 0024/3170] Translated using Weblate (French) Currently translated at 89.9% (523 of 582 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index cce49c7a3..e89e999a5 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -17,7 +17,7 @@ "app_manifest_invalid": "Manifeste d’application incorrect : {error}", "app_no_upgrade": "Aucune application à mettre à jour", "app_not_correctly_installed": "{app:s} semble être mal installé", - "app_not_installed": "{app:s} n’est pas installé", + "app_not_installed": "L'application '{app:s}' n’est pas installé. Vous trouverez ici la liste des applications installées: {all_apps}", "app_not_properly_removed": "{app:s} n’a pas été supprimé correctement", "app_package_need_update": "Le paquet de l’application {app} doit être mis à jour pour être en adéquation avec les changements de YunoHost", "app_recent_version_required": "{app:s} nécessite une version plus récente de YunoHost", @@ -561,5 +561,6 @@ "tools_upgrade_cant_unhold_critical_packages": "Impossible de dé-marquer les paquets critiques ...", "tools_upgrade_special_packages_explanation": "Cette opération prendra fin mais la mise à jour spécifique continuera en arrière-plan. Veuillez ne pas lancer d'autre action sur votre serveur dans les 10 prochaines minutes (en fonction de la vitesse de votre matériel). Une fois que c'est fait, vous devrez peut-être vous reconnecter sur le panel d'administration web. Le journal de la mise à jour sera disponible dans Outils > Log (dans le panel d'administration web) ou dans la liste des journaux YunoHost (en ligne de commande).", "update_apt_cache_failed": "Impossible de mettre à jour le cache APT (gestionnaire de paquets Debian). Voici un extrait du fichier sources.list qui pourrait vous aider à identifier les lignes problématiques :\n{sourceslist}", - "update_apt_cache_warning": "Des erreurs se sont produites lors de la mise à jour du cache APT (gestionnaire de paquets Debian). Voici un extrait des lignes du fichier sources.list qui pourrait vous aider à identifier les lignes problématiques :\n{sourceslist}" + "update_apt_cache_warning": "Des erreurs se sont produites lors de la mise à jour du cache APT (gestionnaire de paquets Debian). Voici un extrait des lignes du fichier sources.list qui pourrait vous aider à identifier les lignes problématiques :\n{sourceslist}", + "apps_permission_not_found": "Aucune permission n'a été trouvée pour les applications installées" } From 4c7c740aba53c5dad52e44254c6887d12cbfde80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9lanie=20Chauvel?= Date: Wed, 14 Aug 2019 22:09:21 +0000 Subject: [PATCH 0025/3170] Translated using Weblate (French) Currently translated at 89.9% (523 of 582 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index e89e999a5..f3981335a 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -370,7 +370,7 @@ "dyndns_domain_not_provided": "Le fournisseur DynDNS {provider:s} ne peut pas fournir le domaine {domain:s}.", "app_make_default_location_already_used": "Impossible de configurer l’application '{app}' par défaut pour le domaine {domain} car il est déjà utilisé par l'application '{other_app}'", "app_upgrade_app_name": "Mise à jour de l’application {app} …", - "backup_output_symlink_dir_broken": "Vous avez un lien symbolique cassé à la place de votre dossier d’archives « {path:s} ». Vous pourriez avoir une configuration personnalisée pour sauvegarder vos données sur un autre système de fichiers, dans ce cas, vous avez probablement oublié de monter ou de connecter votre disque dur ou votre clé USB.", + "backup_output_symlink_dir_broken": "Vous avez un lien symbolique cassé à la place de votre dossier d’archives « {path:s} ». Vous pourriez avoir une configuration personnalisée pour sauvegarder vos données sur un autre système de fichiers, dans ce cas vous avez probablement oublié de monter ou de connecter votre disque dur ou votre clé USB.", "migrate_tsig_end": "La migration à hmac-sha512 est terminée", "migrate_tsig_failed": "La migration du domaine DynDNS {domain} à hmac-sha512 a échoué. Annulation des modifications. Erreur : {error_code} - {error}", "migrate_tsig_start": "L’algorithme de génération des clefs n’est pas suffisamment sécurisé pour la signature TSIG du domaine '{domain}', lancement de la migration vers hmac-sha512 qui est plus sécurisé", @@ -418,7 +418,7 @@ "service_description_yunohost-api": "permet les interactions entre l’interface web de YunoHost et le système", "service_description_yunohost-firewall": "gère l'ouverture et la fermeture des ports de connexion aux services", "experimental_feature": "Attention : cette fonctionnalité est expérimentale et ne doit pas être considérée comme stable, vous ne devriez pas l’utiliser à moins que vous ne sachiez ce que vous faites.", - "log_corrupted_md_file": "Le fichier yaml de metadata associé aux logs est corrompu : '{md_file}'", + "log_corrupted_md_file": "Le fichier yaml de métadonnées associé aux logs est corrompu : '{md_file}'\nErreur : {error}", "log_category_404": "Le journal de la catégorie '{category}' n’existe pas", "log_link_to_log": "Journal historisé complet de cette opération : ' {desc} '", "log_help_to_get_log": "Pour voir le journal historisé de cette opération '{desc}', utilisez la commande 'yunohost log display {name}'", From 00e8268c8ac9336e41f5da26d89f943f32216898 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9lanie=20Chauvel?= Date: Wed, 14 Aug 2019 22:11:51 +0000 Subject: [PATCH 0026/3170] Translated using Weblate (French) Currently translated at 90.0% (524 of 582 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index f3981335a..607a2a348 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -17,7 +17,7 @@ "app_manifest_invalid": "Manifeste d’application incorrect : {error}", "app_no_upgrade": "Aucune application à mettre à jour", "app_not_correctly_installed": "{app:s} semble être mal installé", - "app_not_installed": "L'application '{app:s}' n’est pas installé. Vous trouverez ici la liste des applications installées: {all_apps}", + "app_not_installed": "L'application « {app:s} » n’est pas installée. Voici la liste des applications installées: {all_apps}", "app_not_properly_removed": "{app:s} n’a pas été supprimé correctement", "app_package_need_update": "Le paquet de l’application {app} doit être mis à jour pour être en adéquation avec les changements de YunoHost", "app_recent_version_required": "{app:s} nécessite une version plus récente de YunoHost", @@ -562,5 +562,5 @@ "tools_upgrade_special_packages_explanation": "Cette opération prendra fin mais la mise à jour spécifique continuera en arrière-plan. Veuillez ne pas lancer d'autre action sur votre serveur dans les 10 prochaines minutes (en fonction de la vitesse de votre matériel). Une fois que c'est fait, vous devrez peut-être vous reconnecter sur le panel d'administration web. Le journal de la mise à jour sera disponible dans Outils > Log (dans le panel d'administration web) ou dans la liste des journaux YunoHost (en ligne de commande).", "update_apt_cache_failed": "Impossible de mettre à jour le cache APT (gestionnaire de paquets Debian). Voici un extrait du fichier sources.list qui pourrait vous aider à identifier les lignes problématiques :\n{sourceslist}", "update_apt_cache_warning": "Des erreurs se sont produites lors de la mise à jour du cache APT (gestionnaire de paquets Debian). Voici un extrait des lignes du fichier sources.list qui pourrait vous aider à identifier les lignes problématiques :\n{sourceslist}", - "apps_permission_not_found": "Aucune permission n'a été trouvée pour les applications installées" + "apps_permission_not_found": "Aucune permission trouvée pour les applications installées" } From a6607eaf305e82269464eb6292d8c59cfda19130 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 16 Aug 2019 16:26:01 +0200 Subject: [PATCH 0027/3170] Buuuuurn the appslist system --- data/actionsmap/yunohost.yml | 32 ---- locales/en.json | 16 -- src/yunohost/app.py | 309 ----------------------------------- 3 files changed, 357 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 2b87b6daa..cc1cf738c 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -568,38 +568,6 @@ app: category_help: Manage apps actions: - ### app_fetchlist() - fetchlist: - action_help: Fetch application lists from app servers, or register a new one. - api: PUT /appslists - arguments: - -n: - full: --name - help: Name of the list to fetch (fetches all registered lists if empty) - extra: - pattern: &pattern_listname - - !!str ^[a-z0-9_]+$ - - "pattern_listname" - -u: - full: --url - help: URL of a new application list to register. To be specified with -n. - - ### app_listlists() - listlists: - action_help: List registered application lists - api: GET /appslists - - ### app_removelist() - removelist: - action_help: Remove and forget about a given application list - api: DELETE /appslists - arguments: - name: - help: Name of the list to remove - extra: - ask: ask_list_to_remove - pattern: *pattern_listname - ### app_list() list: action_help: List apps diff --git a/locales/en.json b/locales/en.json index b45739149..c018289b1 100644 --- a/locales/en.json +++ b/locales/en.json @@ -51,21 +51,10 @@ "app_upgraded": "{app:s} has been upgraded", "apps_permission_not_found": "No permission found for the installed apps", "apps_permission_restoration_failed": "Permission '{permission:s}' for app {app:s} restoration has failed", - "appslist_corrupted_json": "Could not load the application lists. It looks like {filename:s} is corrupted.", - "appslist_could_not_migrate": "Could not migrate app list {appslist:s}! Unable to parse the url… The old cron job has been kept in {bkp_file:s}.", - "appslist_fetched": "The application list {appslist:s} has been fetched", - "appslist_migrating": "Migrating application list {appslist:s}…", - "appslist_name_already_tracked": "There is already a registered application list with name {name:s}.", - "appslist_removed": "The application list {appslist:s} has been removed", - "appslist_retrieve_bad_format": "Retrieved file for application list {appslist:s} is not valid", - "appslist_retrieve_error": "Unable to retrieve the remote application list {appslist:s}: {error:s}", - "appslist_unknown": "Application list {appslist:s} unknown.", - "appslist_url_already_tracked": "There is already a registered application list with url {url:s}.", "ask_current_admin_password": "Current administration password", "ask_email": "Email address", "ask_firstname": "First name", "ask_lastname": "Last name", - "ask_list_to_remove": "List to remove", "ask_main_domain": "Main domain", "ask_new_admin_password": "New administration password", "ask_new_domain": "New domain", @@ -152,7 +141,6 @@ "confirm_app_install_danger": "WARNING! This application is still experimental (if not explicitly not working) and it is likely to break your system! You should probably NOT install it unless you know what you are doing. Are you willing to take that risk? [{answers:s}] ", "confirm_app_install_thirdparty": "WARNING! Installing 3rd party applications may compromise the integrity and security of your system. You should probably NOT install it unless you know what you are doing. Are you willing to take that risk? [{answers:s}] ", "custom_app_url_required": "You must provide a URL to upgrade your custom app {app:s}", - "custom_appslist_name_required": "You must provide a name for your custom app list", "diagnosis_debian_version_error": "Can't retrieve the Debian version: {error}", "diagnosis_kernel_version_error": "Can't retrieve kernel version: {error}", "diagnosis_monitor_disk_error": "Can't monitor disks: {error}", @@ -263,8 +251,6 @@ "log_app_addaccess": "Add access to '{}'", "log_app_removeaccess": "Remove access to '{}'", "log_app_clearaccess": "Remove all access to '{}'", - "log_app_fetchlist": "Add an application list", - "log_app_removelist": "Remove an application list", "log_app_change_url": "Change the url of '{}' application", "log_app_install": "Install '{}' application", "log_app_remove": "Remove '{}' application", @@ -401,7 +387,6 @@ "network_check_smtp_ko": "Outbound mail (SMTP port 25) seems to be blocked by your network", "network_check_smtp_ok": "Outbound mail (SMTP port 25) is not blocked", "new_domain_required": "You must provide the new main domain", - "no_appslist_found": "No app list found", "no_internet_connection": "Server is not connected to the Internet", "no_ipv6_connectivity": "IPv6 connectivity is not available", "no_restore_script": "No restore script found for the app '{app:s}'", @@ -538,7 +523,6 @@ "system_upgraded": "The system has been upgraded", "system_username_exists": "Username already exists in the system users", "this_action_broke_dpkg": "This action broke dpkg/apt (the system package managers)... You can try to solve this issue by connecting through SSH and running `sudo dpkg --configure -a`.", - "tools_update_failed_to_app_fetchlist": "Failed to update YunoHost's applists because: {error}", "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": "Unable to hold critical packages ...", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 4831f050c..d80144ff1 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -33,8 +33,6 @@ import re import urlparse import subprocess import glob -import pwd -import grp import urllib from collections import OrderedDict from datetime import datetime @@ -68,166 +66,6 @@ re_app_instance_name = re.compile( ) -def app_listlists(): - """ - List fetched lists - - """ - - # Migrate appslist system if needed - # XXX move to a migration when those are implemented - if _using_legacy_appslist_system(): - _migrate_appslist_system() - - # Get the list - appslist_list = _read_appslist_list() - - # Convert 'lastUpdate' timestamp to datetime - for name, infos in appslist_list.items(): - if infos["lastUpdate"] is None: - infos["lastUpdate"] = 0 - infos["lastUpdate"] = datetime.utcfromtimestamp(infos["lastUpdate"]) - - return appslist_list - - -def app_fetchlist(url=None, name=None): - """ - Fetch application list(s) from app server. By default, fetch all lists. - - Keyword argument: - name -- Name of the list - url -- URL of remote JSON list - """ - if url and not url.endswith(".json"): - raise YunohostError("This is not a valid application list url. It should end with .json.") - - # If needed, create folder where actual appslists are stored - if not os.path.exists(REPO_PATH): - os.makedirs(REPO_PATH) - - # Migrate appslist system if needed - # XXX move that to a migration once they are finished - if _using_legacy_appslist_system(): - _migrate_appslist_system() - - # Read the list of appslist... - appslists = _read_appslist_list() - - # Determine the list of appslist to be fetched - appslists_to_be_fetched = [] - - # If a url and and a name is given, try to register new list, - # the fetch only this list - if url is not None: - if name: - operation_logger = OperationLogger('app_fetchlist') - operation_logger.start() - _register_new_appslist(url, name) - # Refresh the appslists dict - appslists = _read_appslist_list() - appslists_to_be_fetched = [name] - operation_logger.success() - else: - raise YunohostError('custom_appslist_name_required') - - # If a name is given, look for an appslist with that name and fetch it - elif name is not None: - if name not in appslists.keys(): - raise YunohostError('appslist_unknown', appslist=name) - else: - appslists_to_be_fetched = [name] - - # Otherwise, fetch all lists - else: - appslists_to_be_fetched = appslists.keys() - - import requests # lazy loading this module for performance reasons - # Fetch all appslists to be fetched - for name in appslists_to_be_fetched: - - url = appslists[name]["url"] - - logger.debug("Attempting to fetch list %s at %s" % (name, url)) - - # Download file - try: - appslist_request = requests.get(url, timeout=30) - except requests.exceptions.SSLError: - logger.error(m18n.n('appslist_retrieve_error', - appslist=name, - error="SSL connection error")) - continue - except Exception as e: - logger.error(m18n.n('appslist_retrieve_error', - appslist=name, - error=str(e))) - continue - if appslist_request.status_code != 200: - logger.error(m18n.n('appslist_retrieve_error', - appslist=name, - error="Server returned code %s " % - str(appslist_request.status_code))) - continue - - # Validate app list format - # TODO / Possible improvement : better validation for app list (check - # that json fields actually look like an app list and not any json - # file) - appslist = appslist_request.text - try: - json.loads(appslist) - except ValueError as e: - logger.error(m18n.n('appslist_retrieve_bad_format', - appslist=name)) - continue - - # Write app list to file - list_file = '%s/%s.json' % (REPO_PATH, name) - try: - with open(list_file, "w") as f: - f.write(appslist) - except Exception as e: - raise YunohostError("Error while writing appslist %s: %s" % (name, str(e)), raw_msg=True) - - now = int(time.time()) - appslists[name]["lastUpdate"] = now - - logger.success(m18n.n('appslist_fetched', appslist=name)) - - # Write updated list of appslist - _write_appslist_list(appslists) - - -@is_unit_operation() -def app_removelist(operation_logger, name): - """ - Remove list from the repositories - - Keyword argument: - name -- Name of the list to remove - - """ - appslists = _read_appslist_list() - - # Make sure we know this appslist - if name not in appslists.keys(): - raise YunohostError('appslist_unknown', appslist=name) - - operation_logger.start() - - # Remove json - json_path = '%s/%s.json' % (REPO_PATH, name) - if os.path.exists(json_path): - os.remove(json_path) - - # Forget about this appslist - del appslists[name] - _write_appslist_list(appslists) - - logger.success(m18n.n('appslist_removed', appslist=name)) - - def app_list(filter=None, raw=False, installed=False, with_backup=False): """ List apps @@ -2704,153 +2542,6 @@ def _parse_app_instance_name(app_instance_name): return (appid, app_instance_nb) -def _using_legacy_appslist_system(): - """ - Return True if we're using the old fetchlist scheme. - This is determined by the presence of some cron job yunohost-applist-foo - """ - - return glob.glob("/etc/cron.d/yunohost-applist-*") != [] - - -def _migrate_appslist_system(): - """ - Migrate from the legacy fetchlist system to the new one - """ - legacy_crons = glob.glob("/etc/cron.d/yunohost-applist-*") - - for cron_path in legacy_crons: - appslist_name = os.path.basename(cron_path).replace("yunohost-applist-", "") - logger.debug(m18n.n('appslist_migrating', appslist=appslist_name)) - - # Parse appslist url in cron - cron_file_content = open(cron_path).read().strip() - appslist_url_parse = re.search("-u (https?://[^ ]+)", cron_file_content) - - # Abort if we did not find an url - if not appslist_url_parse or not appslist_url_parse.groups(): - # Bkp the old cron job somewhere else - bkp_file = "/etc/yunohost/%s.oldlist.bkp" % appslist_name - os.rename(cron_path, bkp_file) - # Notice the user - logger.warning(m18n.n('appslist_could_not_migrate', - appslist=appslist_name, - bkp_file=bkp_file)) - # Otherwise, register the list and remove the legacy cron - else: - appslist_url = appslist_url_parse.groups()[0] - try: - _register_new_appslist(appslist_url, appslist_name) - # Might get an exception if two legacy cron jobs conflict - # in terms of url... - except Exception as e: - logger.error(str(e)) - # Bkp the old cron job somewhere else - bkp_file = "/etc/yunohost/%s.oldlist.bkp" % appslist_name - os.rename(cron_path, bkp_file) - # Notice the user - logger.warning(m18n.n('appslist_could_not_migrate', - appslist=appslist_name, - bkp_file=bkp_file)) - else: - os.remove(cron_path) - - -def _install_appslist_fetch_cron(): - - cron_job_file = "/etc/cron.daily/yunohost-fetch-appslists" - - logger.debug("Installing appslist fetch cron job") - - cron_job = [] - cron_job.append("#!/bin/bash") - # We add a random delay between 0 and 60 min to avoid every instance fetching - # the appslist at the same time every night - cron_job.append("(sleep $((RANDOM%3600));") - cron_job.append("yunohost app fetchlist > /dev/null 2>&1) &") - - with open(cron_job_file, "w") as f: - f.write('\n'.join(cron_job)) - - _set_permissions(cron_job_file, "root", "root", 0o755) - - -# FIXME - Duplicate from certificate.py, should be moved into a common helper -# thing... -def _set_permissions(path, user, group, permissions): - uid = pwd.getpwnam(user).pw_uid - gid = grp.getgrnam(group).gr_gid - - os.chown(path, uid, gid) - os.chmod(path, permissions) - - -def _read_appslist_list(): - """ - Read the json corresponding to the list of appslists - """ - - # If file does not exists yet, return empty dict - if not os.path.exists(APPSLISTS_JSON): - return {} - - # Read file content - with open(APPSLISTS_JSON, "r") as f: - appslists_json = f.read() - - # Parse json, throw exception if what we got from file is not a valid json - try: - appslists = json.loads(appslists_json) - except ValueError: - raise YunohostError('appslist_corrupted_json', filename=APPSLISTS_JSON) - - return appslists - - -def _write_appslist_list(appslist_lists): - """ - Update the json containing list of appslists - """ - - # Write appslist list - try: - with open(APPSLISTS_JSON, "w") as f: - json.dump(appslist_lists, f) - except Exception as e: - raise YunohostError("Error while writing list of appslist %s: %s" % - (APPSLISTS_JSON, str(e)), raw_msg=True) - - -def _register_new_appslist(url, name): - """ - Add a new appslist to be fetched regularly. - Raise an exception if url or name conflicts with an existing list. - """ - - appslist_list = _read_appslist_list() - - # Check if name conflicts with an existing list - if name in appslist_list: - raise YunohostError('appslist_name_already_tracked', name=name) - - # Check if url conflicts with an existing list - known_appslist_urls = [appslist["url"] for _, appslist in appslist_list.items()] - - if url in known_appslist_urls: - raise YunohostError('appslist_url_already_tracked', url=url) - - logger.debug("Registering new appslist %s at %s" % (name, url)) - - appslist_list[name] = { - "url": url, - "lastUpdate": None - } - - _write_appslist_list(appslist_list) - - _install_appslist_fetch_cron() - - def is_true(arg): """ Convert a string into a boolean From 65b81e8677ec64ed6aac440650e852093dc02ddd Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 17 Aug 2019 16:51:47 +0200 Subject: [PATCH 0028/3170] Rewrite appslist system --- locales/en.json | 5 + src/yunohost/app.py | 160 +++++++- src/yunohost/tests/test_appslist.py | 547 ++++++++++++---------------- 3 files changed, 396 insertions(+), 316 deletions(-) diff --git a/locales/en.json b/locales/en.json index c018289b1..fdabad9d0 100644 --- a/locales/en.json +++ b/locales/en.json @@ -51,6 +51,11 @@ "app_upgraded": "{app:s} has been upgraded", "apps_permission_not_found": "No permission found for the installed apps", "apps_permission_restoration_failed": "Permission '{permission:s}' for app {app:s} restoration has failed", + "appslist_init_success": "Appslist system initialized!", + "appslist_updating": "Updating application list...", + "appslist_failed_to_download": "Unable to download the {applist} appslist : {error}", + "appslist_obsolete_cache": "The applist cache is empty or obsolete.", + "appslist_update_success": "The application list has been updated!", "ask_current_admin_password": "Current administration password", "ask_email": "Email address", "ask_firstname": "First name", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index d80144ff1..b2e276fe3 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -39,7 +39,8 @@ from datetime import datetime from moulinette import msignals, m18n, msettings from moulinette.utils.log import getActionLogger -from moulinette.utils.filesystem import read_json, read_toml +from moulinette.utils.network import download_json +from moulinette.utils.filesystem import 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.utils import packages @@ -48,12 +49,16 @@ from yunohost.log import is_unit_operation, OperationLogger logger = getActionLogger('yunohost.app') -REPO_PATH = '/var/cache/yunohost/repo' APPS_PATH = '/usr/share/yunohost/apps' APPS_SETTING_PATH = '/etc/yunohost/apps/' INSTALL_TMP = '/var/cache/yunohost' APP_TMP_FOLDER = INSTALL_TMP + '/from_file' -APPSLISTS_JSON = '/etc/yunohost/appslists.json' + +APPSLISTS_CACHE = '/var/cache/yunohost/repo' +APPSLISTS_CONF = '/etc/yunohost/appslists.yml' +APPSLISTS_CRON_PATH = "/etc/cron.daily/yunohost-fetch-appslists" +APPSLISTS_API_VERSION = 1 +APPSLISTS_DEFAULT_URL = "https://app.yunohost.org/default" re_github_repo = re.compile( r'^(http[s]?://|git@)github.com[/:]' @@ -2542,6 +2547,155 @@ def _parse_app_instance_name(app_instance_name): return (appid, app_instance_nb) +# +# ############################### # +# Applications list management # +# ############################### # +# + + +def _initialize_appslists_system(): + """ + This function is meant to intialize the appslist system with YunoHost's default applist. + + It also creates the cron job that will update the list every day + """ + + default_appslist_list = [{"id": "default", "url": APPSLISTS_DEFAULT_URL}] + + cron_job = [] + cron_job.append("#!/bin/bash") + # We add a random delay between 0 and 60 min to avoid every instance fetching + # the appslist at the same time every night + cron_job.append("(sleep $((RANDOM%3600));") + cron_job.append("yunohost tools update --apps > /dev/null) &") + try: + logger.debug("Initializing appslist system with YunoHost's default app list") + write_to_yaml(APPSLISTS_CONF, default_appslist_list) + + logger.debug("Installing appslist fetch daily cron job") + write_to_file(APPSLISTS_CRON_PATH, '\n'.join(cron_job)) + chown(APPSLISTS_CRON_PATH, uid="root", gid="root") + chmod(APPSLISTS_CRON_PATH, 0o755) + except Exception as e: + raise YunohostError("Could not initialize the appslist system... : %s" % str(e)) + + logger.success(m18n.n("appslist_init_success")) + + +def _read_appslist_list(): + """ + Read the json corresponding to the list of appslists + """ + + try: + list_ = read_yaml(APPSLISTS_CONF) + # Support the case where file exists but is empty + # by returning [] if list_ is None + return list_ if list_ else [] + except Exception as e: + raise YunohostError("Could not read the appslist list ... : %s" % str(e)) + + +def _actual_appslist_api_url(base_url): + + return "{base_url}/v{version}/apps.json".format(base_url=base_url, version=APPSLISTS_API_VERSION) + + +def _update_appslist(): + """ + Fetches the json for each appslist and update the cache + + appslist_list is for example : + [ {"id": "default", "url": "https://app.yunohost.org/default/"} ] + + Then for each appslist, the actual json URL to be fetched is like : + https://app.yunohost.org/default/vX/apps.json + + And store it in : + /var/cache/yunohost/repo/default.json + """ + + appslist_list = _read_appslist_list() + + logger.info(m18n.n("appslist_updating")) + + # Create cache folder if needed + if not os.path.exists(APPSLISTS_CACHE): + logger.debug("Initialize folder for appslist cache") + mkdir(APPSLISTS_CACHE, mode=0o750, parents=True, uid='root') + + for appslist in appslist_list: + applist_id = appslist["id"] + actual_api_url = _actual_appslist_api_url(appslist["url"]) + + # Fetch the json + try: + appslist_content = download_json(actual_api_url) + except Exception as e: + raise YunohostError("appslist_failed_to_download", applist=applist_id, error=str(e)) + + # Remember the appslist api version for later + appslist_content["from_api_version"] = APPSLISTS_API_VERSION + + # Save the appslist data in the cache + cache_file = "{cache_folder}/{list}.json".format(cache_folder=APPSLISTS_CACHE, list=applist_id) + try: + write_to_json(cache_file, appslist_content) + except Exception as e: + raise YunohostError("Unable to write cache data for %s appslist : %s" % (applist_id, str(e))) + + logger.success(m18n.n("appslist_update_success")) + + +def _load_appslist(): + """ + Read all the appslist cache file and build a single dict (app_dict) + corresponding to all known apps in all indexes + """ + + app_dict = {} + + for appslist_id in [L["id"] for L in _read_appslist_list()]: + + # Let's load the json from cache for this appslist + cache_file = "{cache_folder}/{list}.json".format(cache_folder=APPSLISTS_CACHE, list=appslist_id) + + try: + appslist_content = read_json(cache_file) if os.path.exists(cache_file) else None + except Exception as e: + raise ("Unable to read cache for appslist %s : %s" % (appslist_id, str(e))) + + # Check that the version of the data matches version .... + # ... otherwise it means we updated yunohost in the meantime + # and need to update the cache for everything to be consistent + if not appslist_content or appslist_content.get("from_api_version") != APPSLISTS_API_VERSION: + logger.info(m18n.n("appslist_obsolete_cache")) + _update_appslist() + appslist_content = read_json(cache_file) + + del appslist_content["from_api_version"] + + # Add apps from this applist to the output + for app, info in appslist_content.items(): + + # (N.B. : there's a small edge case where multiple appslist could be listing the same apps ... + # in which case we keep only the first one found) + if app in app_dict: + logger.warning("Duplicate app %s found between appslist %s and %s" % (app, appslist_id, app_dict[app]['repository'])) + continue + + info['repository'] = appslist_id + app_dict[app] = info + + return app_dict + +# +# ############################### # +# Small utilities # +# ############################### # +# + def is_true(arg): """ Convert a string into a boolean diff --git a/src/yunohost/tests/test_appslist.py b/src/yunohost/tests/test_appslist.py index 817807ed9..057a5f3fe 100644 --- a/src/yunohost/tests/test_appslist.py +++ b/src/yunohost/tests/test_appslist.py @@ -3,34 +3,49 @@ import pytest import requests import requests_mock import glob -import time +import shutil + +from moulinette import m18n +from moulinette.utils.filesystem import read_json, write_to_json, write_to_yaml from yunohost.utils.error import YunohostError +from yunohost.app import (_initialize_appslists_system, + _read_appslist_list, + _update_appslist, + _actual_appslist_api_url, + _load_appslist, + logger, + APPSLISTS_CACHE, + APPSLISTS_CONF, + APPSLISTS_CRON_PATH, + APPSLISTS_API_VERSION, + APPSLISTS_DEFAULT_URL) -from yunohost.app import app_fetchlist, app_removelist, app_listlists, _using_legacy_appslist_system, _migrate_appslist_system, _register_new_appslist +APPSLISTS_DEFAULT_URL_FULL = _actual_appslist_api_url(APPSLISTS_DEFAULT_URL) +CRON_FOLDER, CRON_NAME = APPSLISTS_CRON_PATH.rsplit("/", 1) -URL_OFFICIAL_APP_LIST = "https://app.yunohost.org/official.json" -REPO_PATH = '/var/cache/yunohost/repo' -APPSLISTS_JSON = '/etc/yunohost/appslists.json' +DUMMY_APPLIST = """{ + "foo": {"id": "foo", "level": 4}, + "bar": {"id": "bar", "level": 7} +} +""" +class AnyStringWith(str): + def __eq__(self, other): + return self in other def setup_function(function): - # Clear all appslist - files = glob.glob(REPO_PATH + "/*") - for f in files: - os.remove(f) + # Clear applist cache + shutil.rmtree(APPSLISTS_CACHE, ignore_errors=True) - # Clear appslist crons - files = glob.glob("/etc/cron.d/yunohost-applist-*") - for f in files: - os.remove(f) + # Clear appslist cron + if os.path.exists(APPSLISTS_CRON_PATH): + os.remove(APPSLISTS_CRON_PATH) - if os.path.exists("/etc/cron.daily/yunohost-fetch-appslists"): - os.remove("/etc/cron.daily/yunohost-fetch-appslists") - - if os.path.exists(APPSLISTS_JSON): - os.remove(APPSLISTS_JSON) + # Clear appslist conf + if os.path.exists(APPSLISTS_CONF): + os.remove(APPSLISTS_CONF) def teardown_function(function): @@ -38,352 +53,258 @@ def teardown_function(function): def cron_job_is_there(): - r = os.system("run-parts -v --test /etc/cron.daily/ | grep yunohost-fetch-appslists") + r = os.system("run-parts -v --test %s | grep %s" % (CRON_FOLDER, CRON_NAME)) return r == 0 - # -# Test listing of appslists and registering of appslists # +# ################################################ # -def test_appslist_list_empty(): - """ - Calling app_listlists() with no registered list should return empty dict - """ +def test_appslist_init(mocker): - assert app_listlists() == {} + # Cache is empty + assert not glob.glob(APPSLISTS_CACHE + "/*") + # Conf doesn't exist yet + assert not os.path.exists(APPSLISTS_CONF) + # Conf doesn't exist yet + assert not os.path.exists(APPSLISTS_CRON_PATH) + # Initialize ... + mocker.spy(m18n, "n") + _initialize_appslists_system() + m18n.n.assert_any_call('appslist_init_success') -def test_appslist_list_register(): - """ - Register a new list - """ - - # Assume we're starting with an empty app list - assert app_listlists() == {} - - # Register a new dummy list - _register_new_appslist("https://lol.com/appslist.json", "dummy") - - appslist_dict = app_listlists() - assert "dummy" in appslist_dict.keys() - assert appslist_dict["dummy"]["url"] == "https://lol.com/appslist.json" - + # Then there's a cron enabled assert cron_job_is_there() + # And a conf with at least one list + assert os.path.exists(APPSLISTS_CONF) + appslist_list = _read_appslist_list() + assert len(appslist_list) -def test_appslist_list_register_conflict_name(): - """ - Attempt to register a new list with conflicting name - """ - - _register_new_appslist("https://lol.com/appslist.json", "dummy") - with pytest.raises(YunohostError): - _register_new_appslist("https://lol.com/appslist2.json", "dummy") - - appslist_dict = app_listlists() - - assert "dummy" in appslist_dict.keys() - assert "dummy2" not in appslist_dict.keys() + # Cache is expected to still be empty though + # (if we did update the appslist during init, + # we couldn't differentiate easily exceptions + # related to lack of network connectivity) + assert not glob.glob(APPSLISTS_CACHE + "/*") -def test_appslist_list_register_conflict_url(): - """ - Attempt to register a new list with conflicting url - """ +def test_appslist_emptylist(): - _register_new_appslist("https://lol.com/appslist.json", "dummy") - with pytest.raises(YunohostError): - _register_new_appslist("https://lol.com/appslist.json", "plopette") + # Initialize ... + _initialize_appslists_system() - appslist_dict = app_listlists() + # Let's imagine somebody removed the default applist because uh idk they dont want to use our default applist + os.system("rm %s" % APPSLISTS_CONF) + os.system("touch %s" % APPSLISTS_CONF) - assert "dummy" in appslist_dict.keys() - assert "plopette" not in appslist_dict.keys() + appslist_list = _read_appslist_list() + assert not len(appslist_list) -# -# Test fetching of appslists # -# +def test_appslist_update_success(mocker): + + # Initialize ... + _initialize_appslists_system() + + # Cache is empty + assert not glob.glob(APPSLISTS_CACHE + "/*") + + # Update + with requests_mock.Mocker() as m: + + _actual_appslist_api_url, + # Mock the server response with a dummy applist + m.register_uri("GET", APPSLISTS_DEFAULT_URL_FULL, text=DUMMY_APPLIST) + + mocker.spy(m18n, "n") + _update_appslist() + m18n.n.assert_any_call("appslist_updating") + m18n.n.assert_any_call("appslist_update_success") + + # Cache shouldn't be empty anymore empty + assert glob.glob(APPSLISTS_CACHE + "/*") + + app_dict = _load_appslist() + assert "foo" in app_dict.keys() + assert "bar" in app_dict.keys() -def test_appslist_fetch(): - """ - Do a fetchlist and test the .json got updated. - """ - assert app_listlists() == {} +def test_appslist_update_404(mocker): - _register_new_appslist(URL_OFFICIAL_APP_LIST, "yunohost") + # Initialize ... + _initialize_appslists_system() with requests_mock.Mocker() as m: - # Mock the server response with a valid (well, empty, yep) json - m.register_uri("GET", URL_OFFICIAL_APP_LIST, text='{ }') + # 404 error + m.register_uri("GET", APPSLISTS_DEFAULT_URL_FULL, + status_code=404) - official_lastUpdate = app_listlists()["yunohost"]["lastUpdate"] - app_fetchlist() - new_official_lastUpdate = app_listlists()["yunohost"]["lastUpdate"] + with pytest.raises(YunohostError): + mocker.spy(m18n, "n") + _update_appslist() + m18n.n.assert_any_call("appslist_failed_to_download") - assert new_official_lastUpdate > official_lastUpdate +def test_appslist_update_timeout(mocker): - -def test_appslist_fetch_single_appslist(): - """ - Register several lists but only fetch one. Check only one got updated. - """ - - assert app_listlists() == {} - _register_new_appslist(URL_OFFICIAL_APP_LIST, "yunohost") - _register_new_appslist("https://lol.com/appslist.json", "dummy") - - time.sleep(1) + # Initialize ... + _initialize_appslists_system() with requests_mock.Mocker() as m: - # Mock the server response with a valid (well, empty, yep) json - m.register_uri("GET", URL_OFFICIAL_APP_LIST, text='{ }') - - official_lastUpdate = app_listlists()["yunohost"]["lastUpdate"] - dummy_lastUpdate = app_listlists()["dummy"]["lastUpdate"] - app_fetchlist(name="yunohost") - new_official_lastUpdate = app_listlists()["yunohost"]["lastUpdate"] - new_dummy_lastUpdate = app_listlists()["dummy"]["lastUpdate"] - - assert new_official_lastUpdate > official_lastUpdate - assert new_dummy_lastUpdate == dummy_lastUpdate - - -def test_appslist_fetch_unknownlist(): - """ - Attempt to fetch an unknown list - """ - - assert app_listlists() == {} - - with pytest.raises(YunohostError): - app_fetchlist(name="swag") - - -def test_appslist_fetch_url_but_no_name(): - """ - Do a fetchlist with url given, but no name given - """ - - with pytest.raises(YunohostError): - app_fetchlist(url=URL_OFFICIAL_APP_LIST) - - -def test_appslist_fetch_badurl(): - """ - Do a fetchlist with a bad url - """ - - app_fetchlist(url="https://not.a.valid.url/plop.json", name="plop") - - -def test_appslist_fetch_badfile(): - """ - Do a fetchlist and mock a response with a bad json - """ - assert app_listlists() == {} - - _register_new_appslist(URL_OFFICIAL_APP_LIST, "yunohost") - - with requests_mock.Mocker() as m: - - m.register_uri("GET", URL_OFFICIAL_APP_LIST, text='{ not json lol }') - - app_fetchlist() - - -def test_appslist_fetch_404(): - """ - Do a fetchlist and mock a 404 response - """ - assert app_listlists() == {} - - _register_new_appslist(URL_OFFICIAL_APP_LIST, "yunohost") - - with requests_mock.Mocker() as m: - - m.register_uri("GET", URL_OFFICIAL_APP_LIST, status_code=404) - - app_fetchlist() - - -def test_appslist_fetch_sslerror(): - """ - Do a fetchlist and mock an SSL error - """ - assert app_listlists() == {} - - _register_new_appslist(URL_OFFICIAL_APP_LIST, "yunohost") - - with requests_mock.Mocker() as m: - - m.register_uri("GET", URL_OFFICIAL_APP_LIST, - exc=requests.exceptions.SSLError) - - app_fetchlist() - - -def test_appslist_fetch_timeout(): - """ - Do a fetchlist and mock a timeout - """ - assert app_listlists() == {} - - _register_new_appslist(URL_OFFICIAL_APP_LIST, "yunohost") - - with requests_mock.Mocker() as m: - - m.register_uri("GET", URL_OFFICIAL_APP_LIST, + # Timeout + m.register_uri("GET", APPSLISTS_DEFAULT_URL_FULL, exc=requests.exceptions.ConnectTimeout) - app_fetchlist() + with pytest.raises(YunohostError): + mocker.spy(m18n, "n") + _update_appslist() + m18n.n.assert_any_call("appslist_failed_to_download") -# -# Test remove of appslist # -# +def test_appslist_update_sslerror(mocker): + + # Initialize ... + _initialize_appslists_system() + + with requests_mock.Mocker() as m: + + # SSL error + m.register_uri("GET", APPSLISTS_DEFAULT_URL_FULL, + exc=requests.exceptions.SSLError) + + with pytest.raises(YunohostError): + mocker.spy(m18n, "n") + _update_appslist() + m18n.n.assert_any_call("appslist_failed_to_download") -def test_appslist_remove(): - """ - Register a new appslist, then remove it - """ +def test_appslist_update_corrupted(mocker): - # Assume we're starting with an empty app list - assert app_listlists() == {} + # Initialize ... + _initialize_appslists_system() - # Register a new dummy list - _register_new_appslist("https://lol.com/appslist.json", "dummy") - app_removelist("dummy") + with requests_mock.Mocker() as m: - # Should end up with no list registered - assert app_listlists() == {} + # Corrupted json + m.register_uri("GET", APPSLISTS_DEFAULT_URL_FULL, + text=DUMMY_APPLIST[:-2]) + + with pytest.raises(YunohostError): + mocker.spy(m18n, "n") + _update_appslist() + m18n.n.assert_any_call("appslist_failed_to_download") -def test_appslist_remove_unknown(): - """ - Attempt to remove an unknown list - """ +def test_appslist_load_with_empty_cache(mocker): - with pytest.raises(YunohostError): - app_removelist("dummy") + # Initialize ... + _initialize_appslists_system() + + # Cache is empty + assert not glob.glob(APPSLISTS_CACHE + "/*") + + # Update + with requests_mock.Mocker() as m: + + # Mock the server response with a dummy applist + m.register_uri("GET", APPSLISTS_DEFAULT_URL_FULL, text=DUMMY_APPLIST) + + # Try to load the applist + # This should implicitly trigger an update in the background + mocker.spy(m18n, "n") + app_dict = _load_appslist() + m18n.n.assert_any_call("appslist_obsolete_cache") + m18n.n.assert_any_call("appslist_update_success") -# -# Test migration from legacy appslist system # -# + # Cache shouldn't be empty anymore empty + assert glob.glob(APPSLISTS_CACHE + "/*") + + assert "foo" in app_dict.keys() + assert "bar" in app_dict.keys() -def add_legacy_cron(name, url): - with open("/etc/cron.d/yunohost-applist-%s" % name, "w") as f: - f.write('00 00 * * * root yunohost app fetchlist -u %s -n %s > /dev/null 2>&1\n' % (url, name)) +def test_appslist_load_with_conflicts_between_lists(mocker): + + # Initialize ... + _initialize_appslists_system() + + conf = [{"id": "default", "url": APPSLISTS_DEFAULT_URL}, + {"id": "default2", "url": APPSLISTS_DEFAULT_URL.replace("yunohost.org", "yolohost.org")}] + + write_to_yaml(APPSLISTS_CONF, conf) + + # Update + with requests_mock.Mocker() as m: + + # Mock the server response with a dummy applist + # + the same applist for the second list + m.register_uri("GET", APPSLISTS_DEFAULT_URL_FULL, text=DUMMY_APPLIST) + m.register_uri("GET", APPSLISTS_DEFAULT_URL_FULL.replace("yunohost.org", "yolohost.org"), text=DUMMY_APPLIST) + + # Try to load the applist + # This should implicitly trigger an update in the background + mocker.spy(logger, "warning") + app_dict = _load_appslist() + logger.warning.assert_any_call(AnyStringWith("Duplicate")) + + # Cache shouldn't be empty anymore empty + assert glob.glob(APPSLISTS_CACHE + "/*") + + assert "foo" in app_dict.keys() + assert "bar" in app_dict.keys() -def test_appslist_check_using_legacy_system_testFalse(): - """ - If no legacy cron job is there, the check should return False - """ - assert glob.glob("/etc/cron.d/yunohost-applist-*") == [] - assert _using_legacy_appslist_system() is False +def test_appslist_load_with_oudated_api_version(mocker): + + # Initialize ... + _initialize_appslists_system() + + # Update + with requests_mock.Mocker() as m: + + mocker.spy(m18n, "n") + m.register_uri("GET", APPSLISTS_DEFAULT_URL_FULL, text=DUMMY_APPLIST) + _update_appslist() + + # Cache shouldn't be empty anymore empty + assert glob.glob(APPSLISTS_CACHE + "/*") + + # Tweak the cache to replace the from_api_version with a different one + for cache_file in glob.glob(APPSLISTS_CACHE + "/*"): + cache_json = read_json(cache_file) + assert cache_json["from_api_version"] == APPSLISTS_API_VERSION + cache_json["from_api_version"] = 0 + write_to_json(cache_file, cache_json) + + # Update + with requests_mock.Mocker() as m: + + # Mock the server response with a dummy applist + m.register_uri("GET", APPSLISTS_DEFAULT_URL_FULL, text=DUMMY_APPLIST) + + mocker.spy(m18n, "n") + app_dict = _load_appslist() + m18n.n.assert_any_call("appslist_update_success") + + assert "foo" in app_dict.keys() + assert "bar" in app_dict.keys() + + # Check that we indeed have the new api number in cache + for cache_file in glob.glob(APPSLISTS_CACHE + "/*"): + cache_json = read_json(cache_file) + assert cache_json["from_api_version"] == APPSLISTS_API_VERSION -def test_appslist_check_using_legacy_system_testTrue(): - """ - If there's a legacy cron job, the check should return True - """ - assert glob.glob("/etc/cron.d/yunohost-applist-*") == [] - add_legacy_cron("yunohost", "https://app.yunohost.org/official.json") - assert _using_legacy_appslist_system() is True + +def test_appslist_migrate_legacy_explicitly(): + + raise NotImplementedError -def test_appslist_system_migration(): - """ - Test that legacy cron jobs get migrated correctly when calling app_listlists - """ +def test_appslist_migrate_legacy_implicitly(): - # Start with no legacy cron, no appslist registered - assert glob.glob("/etc/cron.d/yunohost-applist-*") == [] - assert app_listlists() == {} - assert not os.path.exists("/etc/cron.daily/yunohost-fetch-appslists") - - # Add a few legacy crons - add_legacy_cron("yunohost", "https://app.yunohost.org/official.json") - add_legacy_cron("dummy", "https://swiggitty.swaggy.lol/yolo.json") - - # Migrate - assert _using_legacy_appslist_system() is True - _migrate_appslist_system() - assert _using_legacy_appslist_system() is False - - # No legacy cron job should remain - assert glob.glob("/etc/cron.d/yunohost-applist-*") == [] - - # Check they are in app_listlists anyway - appslist_dict = app_listlists() - assert "yunohost" in appslist_dict.keys() - assert appslist_dict["yunohost"]["url"] == "https://app.yunohost.org/official.json" - assert "dummy" in appslist_dict.keys() - assert appslist_dict["dummy"]["url"] == "https://swiggitty.swaggy.lol/yolo.json" - - assert cron_job_is_there() - - -def test_appslist_system_migration_badcron(): - """ - Test the migration on a bad legacy cron (no url found inside cron job) - """ - - # Start with no legacy cron, no appslist registered - assert glob.glob("/etc/cron.d/yunohost-applist-*") == [] - assert app_listlists() == {} - assert not os.path.exists("/etc/cron.daily/yunohost-fetch-appslists") - - # Add a "bad" legacy cron - add_legacy_cron("wtflist", "ftp://the.fuck.is.this") - - # Migrate - assert _using_legacy_appslist_system() is True - _migrate_appslist_system() - assert _using_legacy_appslist_system() is False - - # No legacy cron should remain, but it should be backuped in /etc/yunohost - assert glob.glob("/etc/cron.d/yunohost-applist-*") == [] - assert os.path.exists("/etc/yunohost/wtflist.oldlist.bkp") - - # Appslist should still be empty - assert app_listlists() == {} - - -def test_appslist_system_migration_conflict(): - """ - Test migration of conflicting cron job (in terms of url) - """ - - # Start with no legacy cron, no appslist registered - assert glob.glob("/etc/cron.d/yunohost-applist-*") == [] - assert app_listlists() == {} - assert not os.path.exists("/etc/cron.daily/yunohost-fetch-appslists") - - # Add a few legacy crons - add_legacy_cron("yunohost", "https://app.yunohost.org/official.json") - add_legacy_cron("dummy", "https://app.yunohost.org/official.json") - - # Migrate - assert _using_legacy_appslist_system() is True - _migrate_appslist_system() - assert _using_legacy_appslist_system() is False - - # No legacy cron job should remain - assert glob.glob("/etc/cron.d/yunohost-applist-*") == [] - - # Only one among "dummy" and "yunohost" should be listed - appslist_dict = app_listlists() - assert (len(appslist_dict.keys()) == 1) - assert ("dummy" in appslist_dict.keys()) or ("yunohost" in appslist_dict.keys()) - - assert cron_job_is_there() + raise NotImplementedError From 050750b7c986f064e4a8c1f54a9c23911f3dc9f7 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 17 Aug 2019 17:43:38 +0200 Subject: [PATCH 0029/3170] Propagate the changes on other parts of the code relying on the appslist system --- src/yunohost/app.py | 22 ++-------------------- src/yunohost/tools.py | 20 +++++++++++--------- 2 files changed, 13 insertions(+), 29 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index b2e276fe3..aa2163cdf 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -86,28 +86,10 @@ def app_list(filter=None, raw=False, installed=False, with_backup=False): """ installed = with_backup or installed - app_dict = {} list_dict = {} if raw else [] - appslists = _read_appslist_list() - - for appslist in appslists.keys(): - - json_path = "%s/%s.json" % (REPO_PATH, appslist) - - # If we don't have the json yet, try to fetch it - if not os.path.exists(json_path): - app_fetchlist(name=appslist) - - # If it now exist - if os.path.exists(json_path): - appslist_content = read_json(json_path) - for app, info in appslist_content.items(): - if app not in app_dict: - info['repository'] = appslist - app_dict[app] = info - else: - logger.warning("Uh there's no data for applist '%s' ... (That should be just a temporary issue?)" % appslist) + # Get app list from applist cache + app_dict = _load_appslist() # Get app list from the app settings directory for app in os.listdir(APPS_SETTING_PATH): diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index c63f1ed33..d9b50280c 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -38,7 +38,7 @@ from moulinette import msignals, m18n from moulinette.utils.log import getActionLogger from moulinette.utils.process import check_output, call_async_output from moulinette.utils.filesystem import read_json, write_to_json -from yunohost.app import app_fetchlist, app_info, app_upgrade, app_ssowatconf, app_list, _install_appslist_fetch_cron +from yunohost.app import _update_appslist, app_info, app_upgrade, app_ssowatconf, app_list from yunohost.domain import domain_add, domain_list, _get_maindomain, _set_maindomain from yunohost.dyndns import _dyndns_available, _dyndns_provides from yunohost.firewall import firewall_upnp @@ -411,15 +411,17 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False, # Enable UPnP silently and reload firewall firewall_upnp('enable', no_refresh=True) - # Setup the default apps list with cron job + # Initialize the appslist system + _initialize_appslist_system() + + # Try to update the appslist ... + # we don't fail miserably if this fails, + # because that could be for example an offline installation... try: - app_fetchlist(name="yunohost", - url="https://app.yunohost.org/apps.json") + _update_appslist() except Exception as e: logger.warning(str(e)) - _install_appslist_fetch_cron() - # Init migrations (skip them, no need to run them on a fresh system) _skip_all_migrations() @@ -465,6 +467,7 @@ def tools_update(apps=False, system=False): Keyword arguments: system -- Fetch available system packages upgrades (equivalent to apt update) apps -- Fetch the application list to check which apps can be upgraded + appslist -- Just update the application list cache """ # If neither --apps nor --system specified, do both @@ -510,11 +513,10 @@ def tools_update(apps=False, system=False): upgradable_apps = [] if apps: - logger.info(m18n.n('updating_app_lists')) try: - app_fetchlist() + _update_appslist() except YunohostError as e: - logger.error(m18n.n('tools_update_failed_to_app_fetchlist'), error=e) + logger.error(str(e)) upgradable_apps = list(_list_upgradable_apps()) From 8dd590e6fcd05f4a340511a4c0d7f7ea14088413 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 17 Aug 2019 18:43:34 +0200 Subject: [PATCH 0030/3170] Implement explicit and implicit migrations --- locales/en.json | 3 +- src/yunohost/app.py | 6 ++ .../0010_migrate_to_apps_json.py | 34 ++--------- src/yunohost/tests/test_appslist.py | 56 +++++++++++++++++-- 4 files changed, 64 insertions(+), 35 deletions(-) diff --git a/locales/en.json b/locales/en.json index fdabad9d0..0e3667749 100644 --- a/locales/en.json +++ b/locales/en.json @@ -320,8 +320,9 @@ "migration_description_0007_ssh_conf_managed_by_yunohost_step1": "Let the SSH configuration be managed by YunoHost (step 1, automatic)", "migration_description_0008_ssh_conf_managed_by_yunohost_step2": "Let the SSH configuration be managed by YunoHost (step 2, manual)", "migration_description_0009_decouple_regenconf_from_services": "Decouple the regen-conf mechanism from services", - "migration_description_0010_migrate_to_apps_json": "Remove deprecated appslists and use the new unified 'apps.json' list instead", + "migration_description_0010_migrate_to_apps_json": "Remove deprecated appslists and use the new unified 'apps.json' list instead (outdated, replaced by migration 12)", "migration_description_0011_setup_group_permission": "Setup user group and setup permission for apps and services", + "migration_description_0012_futureproof_appslist_system": "Migrate to the new future-proof appslist system", "migration_0003_backward_impossible": "The stretch migration cannot be reverted.", "migration_0003_start": "Starting migration to Stretch. The logs will be available in {logfile}.", "migration_0003_patching_sources_list": "Patching the sources.lists…", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index aa2163cdf..3a6d2216a 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -2570,6 +2570,12 @@ def _read_appslist_list(): Read the json corresponding to the list of appslists """ + # Legacy code - can be removed after moving to buster (if the migration got merged before buster) + if os.path.exists('/etc/yunohost/appslists.json'): + from yunohost.tools import _get_migration_by_name + migration = _get_migration_by_name("futureproof_appslist_system") + migration.migrate() + try: list_ = read_yaml(APPSLISTS_CONF) # Support the case where file exists but is empty diff --git a/src/yunohost/data_migrations/0010_migrate_to_apps_json.py b/src/yunohost/data_migrations/0010_migrate_to_apps_json.py index 43ae9a86f..1333e54e8 100644 --- a/src/yunohost/data_migrations/0010_migrate_to_apps_json.py +++ b/src/yunohost/data_migrations/0010_migrate_to_apps_json.py @@ -1,44 +1,18 @@ -import os - from moulinette.utils.log import getActionLogger -from yunohost.app import app_fetchlist, app_removelist, _read_appslist_list, APPSLISTS_JSON from yunohost.tools import Migration logger = getActionLogger('yunohost.migration') -BASE_CONF_PATH = '/home/yunohost.conf' -BACKUP_CONF_DIR = os.path.join(BASE_CONF_PATH, 'backup') -APPSLISTS_BACKUP = os.path.join(BACKUP_CONF_DIR, "appslist_before_migration_to_unified_list.json") - class MyMigration(Migration): - "Migrate from official.json to apps.json" + "Migrate from official.json to apps.json (outdated, replaced by migration 12)" def migrate(self): - # Backup current app list json - os.system("cp %s %s" % (APPSLISTS_JSON, APPSLISTS_BACKUP)) - - # Remove all the deprecated lists - lists_to_remove = [ - "app.yunohost.org/list.json", # Old list on old installs, alias to official.json - "app.yunohost.org/official.json", - "app.yunohost.org/community.json", - "labriqueinter.net/apps/labriqueinternet.json", - "labriqueinter.net/internetcube.json" - ] - - appslists = _read_appslist_list() - for appslist, infos in appslists.items(): - if infos["url"].split("//")[-1] in lists_to_remove: - app_removelist(name=appslist) - - # Replace by apps.json list - app_fetchlist(name="yunohost", - url="https://app.yunohost.org/apps.json") + logger.info("This is migration is oudated and doesn't do anything anymore. The migration 12 will handle this instead.") + pass def backward(self): - if os.path.exists(APPSLISTS_BACKUP): - os.system("cp %s %s" % (APPSLISTS_BACKUP, APPSLISTS_JSON)) + pass diff --git a/src/yunohost/tests/test_appslist.py b/src/yunohost/tests/test_appslist.py index 057a5f3fe..d7b8e429b 100644 --- a/src/yunohost/tests/test_appslist.py +++ b/src/yunohost/tests/test_appslist.py @@ -6,7 +6,7 @@ import glob import shutil from moulinette import m18n -from moulinette.utils.filesystem import read_json, write_to_json, write_to_yaml +from moulinette.utils.filesystem import read_json, write_to_json, write_to_yaml, mkdir from yunohost.utils.error import YunohostError from yunohost.app import (_initialize_appslists_system, @@ -49,7 +49,11 @@ def setup_function(function): def teardown_function(function): - pass + + # Clear applist cache + # Otherwise when using apps stuff after running the test, + # we'll still have the dummy unusable list + shutil.rmtree(APPSLISTS_CACHE, ignore_errors=True) def cron_job_is_there(): @@ -302,9 +306,53 @@ def test_appslist_load_with_oudated_api_version(mocker): def test_appslist_migrate_legacy_explicitly(): - raise NotImplementedError + open("/etc/yunohost/appslists.json", "w").write('{"yunohost": {"yolo":"swag"}}') + mkdir(APPSLISTS_CACHE, 0o750, parents=True) + open(APPSLISTS_CACHE+"/yunohost_old.json", "w").write('{"foo":{}, "bar": {}}') + open(APPSLISTS_CRON_PATH, "w").write("# Some old cron") + + from yunohost.tools import _get_migration_by_name + migration = _get_migration_by_name("futureproof_appslist_system") + + with requests_mock.Mocker() as m: + + # Mock the server response with a dummy applist + m.register_uri("GET", APPSLISTS_DEFAULT_URL_FULL, text=DUMMY_APPLIST) + migration.migrate() + + # Old conf shouldnt be there anymore (got renamed to .old) + assert not os.path.exists("/etc/yunohost/appslists.json") + # Old cache should have been removed + assert not os.path.exists(APPSLISTS_CACHE+"/yunohost_old.json") + # Cron should have been changed + assert "/bin/bash" in open(APPSLISTS_CRON_PATH, "r").read() + assert cron_job_is_there() + + # Reading the appslist should work + app_dict = _load_appslist() + assert "foo" in app_dict.keys() + assert "bar" in app_dict.keys() def test_appslist_migrate_legacy_implicitly(): - raise NotImplementedError + open("/etc/yunohost/appslists.json", "w").write('{"yunohost": {"yolo":"swag"}}') + mkdir(APPSLISTS_CACHE, 0o750, parents=True) + open(APPSLISTS_CACHE+"/yunohost_old.json", "w").write('{"old_foo":{}, "old_bar": {}}') + open(APPSLISTS_CRON_PATH, "w").write("# Some old cron") + + with requests_mock.Mocker() as m: + m.register_uri("GET", APPSLISTS_DEFAULT_URL_FULL, text=DUMMY_APPLIST) + app_dict = _load_appslist() + + assert "foo" in app_dict.keys() + assert "bar" in app_dict.keys() + + # Old conf shouldnt be there anymore (got renamed to .old) + assert not os.path.exists("/etc/yunohost/appslists.json") + # Old cache should have been removed + assert not os.path.exists(APPSLISTS_CACHE+"/yunohost_old.json") + # Cron should have been changed + assert "/bin/bash" in open(APPSLISTS_CRON_PATH, "r").read() + assert cron_job_is_there() + From 005ccea610a6fa263f182069e3238976078d18a3 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 17 Aug 2019 19:02:38 +0200 Subject: [PATCH 0031/3170] Make the PEP8 happy even though that's unrelated code to the current PR but meh ... --- src/yunohost/app.py | 59 +++++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 31 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 3a6d2216a..f4f645f2c 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -230,7 +230,8 @@ def app_map(app=None, raw=False, user=None): app -- Specific app to map """ - from yunohost.permission import user_permission_list + # TODO / FIXME : Aleks to Josue : not sure why this was included but not used ... + # from yunohost.permission import user_permission_list from yunohost.utils.ldap import _get_ldap_interface apps = [] @@ -327,7 +328,7 @@ def app_change_url(operation_logger, app, domain, path): # Retrieve arguments list for change_url script # TODO: Allow to specify arguments args_odict = _parse_args_from_manifest(manifest, 'change_url') - args_list = [ value[0] for value in args_odict.values() ] + args_list = [value[0] for value in args_odict.values()] args_list.append(app) # Prepare env. var. to pass to script @@ -380,7 +381,7 @@ def app_change_url(operation_logger, app, domain, path): app_setting(app, 'domain', value=domain) app_setting(app, 'path', value=path) - permission_update(app, permission="main", add_url=[domain+path], remove_url=[old_domain+old_path], sync_perm=True) + permission_update(app, permission="main", add_url=[domain + path], remove_url=[old_domain + old_path], sync_perm=True) # avoid common mistakes if _run_service_command("reload", "nginx") is False: @@ -415,9 +416,6 @@ def app_upgrade(app=[], url=None, file=None): from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback from yunohost.permission import permission_sync_to_user - # Retrieve interface - is_api = msettings.get('interface') == 'api' - try: app_list() except YunohostError: @@ -430,15 +428,15 @@ def app_upgrade(app=[], url=None, file=None): if not apps: # FIXME : not sure what's supposed to happen if there is a url and a file but no apps... if not url and not file: - apps = [app["id"] for app in app_list(installed=True)["apps"]] + apps = [app_["id"] for app_ in app_list(installed=True)["apps"]] elif not isinstance(app, list): apps = [app] # Remove possible duplicates - apps = [app for i,app in enumerate(apps) if apps not in apps[:i]] + apps = [app_ for i, app_ in enumerate(apps) if app_ not in apps[:i]] # Abort if any of those app is in fact not installed.. - for app in [app for app in apps if not _is_installed(app)]: + for app in [app_ for app_ in apps if not _is_installed(app_)]: raise YunohostError('app_not_installed', app=app, all_apps=_get_all_installed_apps_id()) if len(apps) == 0: @@ -477,7 +475,7 @@ def app_upgrade(app=[], url=None, file=None): # Retrieve arguments list for upgrade script # TODO: Allow to specify arguments args_odict = _parse_args_from_manifest(manifest, 'upgrade') - args_list = [ value[0] for value in args_odict.values() ] + args_list = [value[0] for value in args_odict.values()] args_list.append(app_instance_name) # Prepare env. var. to pass to script @@ -585,7 +583,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu return answer = msignals.prompt(m18n.n('confirm_app_install_' + confirm, - answers='Y/N')) + answers='Y/N')) if answer.upper() != "Y": raise YunohostError("aborting") @@ -639,7 +637,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu args_dict = {} if not args else \ dict(urlparse.parse_qsl(args, keep_blank_values=True)) args_odict = _parse_args_from_manifest(manifest, 'install', args=args_dict) - args_list = [ value[0] for value in args_odict.values() ] + args_list = [value[0] for value in args_odict.values()] args_list.append(app_instance_name) # Prepare env. var. to pass to script @@ -654,8 +652,8 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu # Tell the operation_logger to redact all password-type args # Also redact the % escaped version of the password that might appear in # the 'args' section of metadata (relevant for password with non-alphanumeric char) - data_to_redact = [ value[0] for value in args_odict.values() if value[1] == "password" ] - data_to_redact += [ urllib.quote(data) for data in data_to_redact if urllib.quote(data) != data ] + data_to_redact = [value[0] for value in args_odict.values() if value[1] == "password"] + data_to_redact += [urllib.quote(data) for data in data_to_redact if urllib.quote(data) != data] operation_logger.data_to_redact.extend(data_to_redact) operation_logger.related_to = [s for s in operation_logger.related_to if s[0] != "app"] @@ -736,7 +734,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu )[0] # Remove all permission in LDAP result = ldap.search(base='ou=permission,dc=yunohost,dc=org', - filter='(&(objectclass=permissionYnh)(cn=*.%s))' % app_instance_name, attrs=['cn']) + filter='(&(objectclass=permissionYnh)(cn=*.%s))' % app_instance_name, attrs=['cn']) permission_list = [p['cn'][0] for p in result] for l in permission_list: permission_remove(app_instance_name, l.split('.')[0], force=True) @@ -785,7 +783,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu domain = app_settings.get('domain', None) path = app_settings.get('path', None) if domain and path: - permission_update(app_instance_name, permission="main", add_url=[domain+path], sync_perm=False) + permission_update(app_instance_name, permission="main", add_url=[domain + path], sync_perm=False) permission_sync_to_user() @@ -818,7 +816,7 @@ def app_remove(operation_logger, app): # TODO: display fail messages from script try: shutil.rmtree('/tmp/yunohost_remove') - except: + except Exception: pass # Apply dirty patch to make php5 apps compatible with php7 (e.g. the remove @@ -864,7 +862,7 @@ def app_remove(operation_logger, app): raise YunohostError("this_action_broke_dpkg") -@is_unit_operation(['permission','app']) +@is_unit_operation(['permission', 'app']) def app_addaccess(operation_logger, apps, users=[]): """ Grant access right to users (everyone by default) @@ -878,12 +876,12 @@ def app_addaccess(operation_logger, apps, users=[]): permission = user_permission_update(operation_logger, app=apps, permission="main", add_username=users) - result = {p : v['main']['allowed_users'] for p, v in permission['permissions'].items()} + result = {p: v['main']['allowed_users'] for p, v in permission['permissions'].items()} return {'allowed_users': result} -@is_unit_operation(['permission','app']) +@is_unit_operation(['permission', 'app']) def app_removeaccess(operation_logger, apps, users=[]): """ Revoke access right to users (everyone by default) @@ -897,12 +895,12 @@ def app_removeaccess(operation_logger, apps, users=[]): permission = user_permission_update(operation_logger, app=apps, permission="main", del_username=users) - result = {p : v['main']['allowed_users'] for p, v in permission['permissions'].items()} + result = {p: v['main']['allowed_users'] for p, v in permission['permissions'].items()} return {'allowed_users': result} -@is_unit_operation(['permission','app']) +@is_unit_operation(['permission', 'app']) def app_clearaccess(operation_logger, apps): """ Reset access rights for the app @@ -915,10 +913,11 @@ def app_clearaccess(operation_logger, apps): permission = user_permission_clear(operation_logger, app=apps, permission="main") - result = {p : v['main']['allowed_users'] for p, v in permission['permissions'].items()} + result = {p: v['main']['allowed_users'] for p, v in permission['permissions'].items()} return {'allowed_users': result} + def app_debug(app): """ Display debug informations for an app @@ -1686,8 +1685,7 @@ def _get_app_config_panel(app_id): "panel": [], } - panels = filter(lambda (key, value): key not in ("name", "version") - and isinstance(value, OrderedDict), + panels = filter(lambda (key, value): key not in ("name", "version") and isinstance(value, OrderedDict), toml_config_panel.items()) for key, value in panels: @@ -1697,8 +1695,7 @@ def _get_app_config_panel(app_id): "sections": [], } - sections = filter(lambda (k, v): k not in ("name",) - and isinstance(v, OrderedDict), + sections = filter(lambda (k, v): k not in ("name",) and isinstance(v, OrderedDict), value.items()) for section_key, section_value in sections: @@ -1708,8 +1705,7 @@ def _get_app_config_panel(app_id): "options": [], } - options = filter(lambda (k, v): k not in ("name",) - and isinstance(v, OrderedDict), + options = filter(lambda (k, v): k not in ("name",) and isinstance(v, OrderedDict), section_value.items()) for option_key, option_value in options: @@ -2455,8 +2451,8 @@ def _parse_args_in_yunohost_format(args, action_args): # If there's only one "domain" and "path", validate that domain/path # is an available url and normalize the path. - domain_args = [ (name, value[0]) for name, value in args_dict.items() if value[1] == "domain" ] - path_args = [ (name, value[0]) for name, value in args_dict.items() if value[1] == "path" ] + domain_args = [(name, value[0]) for name, value in args_dict.items() if value[1] == "domain"] + path_args = [(name, value[0]) for name, value in args_dict.items() if value[1] == "path"] if len(domain_args) == 1 and len(path_args) == 1: @@ -2684,6 +2680,7 @@ def _load_appslist(): # ############################### # # + def is_true(arg): """ Convert a string into a boolean From ee67ebfd86bd4dca98e23ae037a71f3b92dba8ff Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 18 Aug 2019 01:40:48 +0200 Subject: [PATCH 0032/3170] Remove deprecated helpers while still supporting them through hacky patching... --- data/actionsmap/yunohost.yml | 55 +--------- data/helpers.d/network | 25 +++++ locales/en.json | 7 -- src/yunohost/app.py | 191 ++++++++++++++++------------------- src/yunohost/backup.py | 5 +- src/yunohost/tools.py | 19 ---- 6 files changed, 116 insertions(+), 186 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 2b87b6daa..778029775 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -738,30 +738,6 @@ app: help: Delete the key action: store_true - ### app_checkport() - checkport: - action_help: Check availability of a local port - api: GET /tools/checkport - deprecated: true - arguments: - port: - help: Port to check - extra: - pattern: &pattern_port - - !!str ^([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$ - - "pattern_port" - - ### app_checkurl() - checkurl: - action_help: Check availability of a web path - api: GET /tools/checkurl - deprecated: True - arguments: - url: - help: Url to check - -a: - full: --app - help: Write domain & path to app settings for further checks ### app_register_url() register-url: @@ -776,24 +752,6 @@ app: help: The path to be registered (e.g. /coffee) - ### app_initdb() - initdb: - action_help: Create database and initialize it with optionnal attached script - api: POST /tools/initdb - deprecated: true - arguments: - user: - help: Name of the DB user - -p: - full: --password - help: Password of the DB (generated unless set) - -d: - full: --db - help: DB name (user unless set) - -s: - full: --sql - help: Initial SQL file - ### app_debug() debug: action_help: Display all debug informations for an application @@ -802,6 +760,7 @@ app: app: help: App name + ### app_makedefault() makedefault: action_help: Redirect domain root to an app @@ -1645,16 +1604,6 @@ tools: help: Show private data (domain, IP) action: store_true - ### tools_port_available() - port-available: - action_help: Check availability of a local port - api: GET /tools/portavailable - arguments: - port: - help: Port to check - extra: - pattern: *pattern_port - ### tools_shell() shell: action_help: Launch a development shell @@ -1863,7 +1812,7 @@ log: api: GET /logs arguments: category: - help: Log category to display (default operations), could be operation, history, package, system, access, service or app + help: Log category to display (default operations), could be operation, history, package, system, access, service or app nargs: "*" -l: full: --limit diff --git a/data/helpers.d/network b/data/helpers.d/network index 0f75cb165..d43c4ec13 100644 --- a/data/helpers.d/network +++ b/data/helpers.d/network @@ -24,6 +24,31 @@ ynh_find_port () { echo $port } +# Test if a port is available +# +# example: ynh_port_available --port=1234 || ynh_die "Port 1234 is needs to be available for this app" +# +# usage: ynh_find_port --port=XYZ +# | arg: -p, --port - port to check +# +# Requires YunoHost version 3.7.x or higher. +ynh_port_available () { + # Declare an array to define the options of this helper. + local legacy_args=p + declare -Ar args_array=( [p]=port= ) + local port + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + if netcat -z 127.0.0.1 $port; + then + return 1 + else + return 0 + fi +} + + # Validate an IP address # # usage: ynh_validate_ip --family=family --ip_address=ip_address diff --git a/locales/en.json b/locales/en.json index b45739149..d0071edb9 100644 --- a/locales/en.json +++ b/locales/en.json @@ -22,9 +22,7 @@ "app_id_invalid": "Invalid app id", "app_incompatible": "The app {app} is incompatible with your YunoHost version", "app_install_files_invalid": "Invalid installation files", - "app_location_already_used": "The app '{app}' is already installed on that location ({path})", "app_make_default_location_already_used": "Can't make the app '{app}' the default on the domain {domain} is already used by the other app '{other_app}'", - "app_location_install_failed": "Unable to install the app in this location because it conflit with the app '{other_app}' already installed on '{other_path}'", "app_location_unavailable": "This url is not available or conflicts with the already installed app(s):\n{apps:s}", "app_manifest_invalid": "Invalid app manifest: {error}", "app_no_upgrade": "No apps to upgrade", @@ -393,9 +391,6 @@ "monitor_stats_no_update": "No monitoring statistics to update", "monitor_stats_period_unavailable": "No available statistics for the period", "mountpoint_unknown": "Unknown mountpoint", - "mysql_db_creation_failed": "MySQL database creation failed", - "mysql_db_init_failed": "MySQL database init failed", - "mysql_db_initialized": "The MySQL database has been initialized", "need_define_permission_before": "You need to redefine the permission using 'yunohost user permission add -u USER' before removing an allowed group", "network_check_mx_ko": "DNS MX record is not set", "network_check_smtp_ko": "Outbound mail (SMTP port 25) seems to be blocked by your network", @@ -444,8 +439,6 @@ "permission_update_nothing_to_do": "No permissions to update", "port_already_closed": "Port {port:d} is already closed for {ip_version:s} connections", "port_already_opened": "Port {port:d} is already opened for {ip_version:s} connections", - "port_available": "Port {port:d} is available", - "port_unavailable": "Port {port:d} is not available", "recommend_to_add_first_user": "The post-install is finished but YunoHost needs at least one user to work correctly, you should add one using 'yunohost user create $username' or the admin interface.", "remove_main_permission_not_allowed": "Removing the main permission is not allowed", "remove_user_of_group_not_allowed": "You are not allowed to remove the user {user:s} in the group {group:s}", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 4831f050c..e403592a2 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -41,7 +41,7 @@ from datetime import datetime from moulinette import msignals, m18n, msettings from moulinette.utils.log import getActionLogger -from moulinette.utils.filesystem import read_json, read_toml +from moulinette.utils.filesystem import read_file, read_json, read_toml, write_to_file from yunohost.service import service_log, service_status, _run_service_command from yunohost.utils import packages @@ -315,6 +315,7 @@ def app_list(filter=None, raw=False, installed=False, with_backup=False): # dirty: we used to have manifest containing multi_instance value in form of a string # but we've switched to bool, this line ensure retrocompatibility + app_info_dict["manifest"]["multi_instance"] = is_true(app_info_dict["manifest"].get("multi_instance", False)) list_dict[app_id] = app_info_dict @@ -667,6 +668,9 @@ def app_upgrade(app=[], url=None, file=None): operation_logger = OperationLogger('app_upgrade', related_to, env=env_dict) operation_logger.start() + # Attempt to patch legacy helpers ... + _patch_legacy_helpers(extracted_app_folder) + # Apply dirty patch to make php5 apps compatible with php7 _patch_php5(extracted_app_folder) @@ -854,6 +858,9 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu app_settings['install_time'] = status['installed_at'] _set_app_settings(app_instance_name, app_settings) + # Attempt to patch legacy helpers ... + _patch_legacy_helpers(extracted_app_folder) + # Apply dirty patch to make php5 apps compatible with php7 _patch_php5(extracted_app_folder) @@ -996,6 +1003,9 @@ def app_remove(operation_logger, app): except: pass + # Attempt to patch legacy helpers ... + _patch_legacy_helpers(app_setting_path) + # Apply dirty patch to make php5 apps compatible with php7 (e.g. the remove # script might date back from jessie install) _patch_php5(app_setting_path) @@ -1197,24 +1207,6 @@ def app_setting(app, key, value=None, delete=False): _set_app_settings(app, app_settings) -def app_checkport(port): - """ - Check availability of a local port - - Keyword argument: - port -- Port to check - - """ - - # This import cannot be moved on top of file because it create a recursive - # import... - from yunohost.tools import tools_port_available - if tools_port_available(port): - logger.success(m18n.n('port_available', port=int(port))) - else: - raise YunohostError('port_unavailable', port=int(port)) - - def app_register_url(app, domain, path): """ Book/register a web path for a given app @@ -1258,93 +1250,6 @@ def app_register_url(app, domain, path): app_setting(app, 'path', value=path) -def app_checkurl(url, app=None): - """ - Check availability of a web path - - Keyword argument: - url -- Url to check - app -- Write domain & path to app settings for further checks - - """ - - logger.error("Packagers /!\\ : 'app checkurl' is deprecated ! Please use the helper 'ynh_webpath_register' instead !") - - from yunohost.domain import domain_list, _normalize_domain_path - - if "https://" == url[:8]: - url = url[8:] - elif "http://" == url[:7]: - url = url[7:] - - if url[-1:] != '/': - url = url + '/' - - domain = url[:url.index('/')] - path = url[url.index('/'):] - installed = False - - domain, path = _normalize_domain_path(domain, path) - - apps_map = app_map(raw=True) - - if domain not in domain_list()['domains']: - raise YunohostError('domain_unknown') - - if domain in apps_map: - # Loop through apps - for p, a in apps_map[domain].items(): - # Skip requested app checking - if app is not None and a['id'] == app: - installed = True - continue - if path == p: - raise YunohostError('app_location_already_used', app=a["id"], path=path) - # can't install "/a/b/" if "/a/" exists - elif path.startswith(p) or p.startswith(path): - raise YunohostError('app_location_install_failed', other_path=p, other_app=a['id']) - - if app is not None and not installed: - app_setting(app, 'domain', value=domain) - app_setting(app, 'path', value=path) - - -def app_initdb(user, password=None, db=None, sql=None): - """ - Create database and initialize it with optionnal attached script - - Keyword argument: - db -- DB name (user unless set) - user -- Name of the DB user - password -- Password of the DB (generated unless set) - sql -- Initial SQL file - - """ - - logger.error("Packagers /!\\ : 'app initdb' is deprecated ! Please use the helper 'ynh_mysql_setup_db' instead !") - - if db is None: - db = user - - return_pwd = False - if password is None: - password = random_password(12) - return_pwd = True - - mysql_root_pwd = open('/etc/yunohost/mysql').read().rstrip() - mysql_command = 'mysql -u root -p%s -e "CREATE DATABASE %s ; GRANT ALL PRIVILEGES ON %s.* TO \'%s\'@localhost IDENTIFIED BY \'%s\';"' % (mysql_root_pwd, db, db, user, password) - if os.system(mysql_command) != 0: - raise YunohostError('mysql_db_creation_failed') - if sql is not None: - if os.system('mysql -u %s -p%s %s < %s' % (user, password, db, sql)) != 0: - raise YunohostError('mysql_db_init_failed') - - if return_pwd: - return password - - logger.success(m18n.n('mysql_db_initialized')) - - def app_ssowatconf(): """ Regenerate SSOwat configuration file @@ -2951,3 +2856,77 @@ def _patch_php5(app_folder): "-e 's@php5@php7.0@g' " \ "%s" % filename os.system(c) + +def _patch_legacy_helpers(app_folder): + + files_to_patch = [] + files_to_patch.extend(glob.glob("%s/scripts/*" % app_folder)) + files_to_patch.extend(glob.glob("%s/scripts/.*" % app_folder)) + + stuff_to_replace = { + # Replace + # sudo yunohost app initdb $db_user -p $db_pwd + # by + # ynh_mysql_setup_db --db_user=$db_user --db_name=$db_user --db_pwd=$db_pwd + "yunohost app initdb": ( + r"(sudo )?yunohost app initdb \"?(\$\{?\w+\}?)\"?\s+-p\s\"?(\$\{?\w+\}?)\"?", + r"ynh_mysql_setup_db --db_user=\2 --db_name=\2 --db_pwd=\3"), + # Replace + # sudo yunohost app checkport whaterver + # by + # ynh_port_available whatever + "yunohost app checkport": ( + r"(sudo )?yunohost app checkport", + r"ynh_port_available"), + # We can't migrate easily port-available + # .. but at the time of writing this code, only two non-working apps are using it. + "yunohost tools port-available": (None, None), + # Replace + # yunohost app checkurl "${domain}${path_url}" -a "${app}" + # by + # ynh_webpath_register --app=${app} --domain=${domain} --path_url=${path_url} + "yunohost app checkurl": ( + r"(sudo )?yunohost app checkurl \"?(\$\{?\w+\}?)\/?(\$\{?\w+\}?)\"?\s+-a\s\"?(\$\{?\w+\}?)\"?", + r"ynh_webpath_register --app=\4 --domain=\2 --path_url=\3"), + } + + stuff_to_replace_compiled = {h: (re.compile(r[0]), r[1]) if r[0] else (None,None) for h, r in stuff_to_replace.items()} + + for filename in files_to_patch: + + # Ignore non-regular files + if not os.path.isfile(filename): + continue + + content = read_file(filename) + replaced_stuff = False + + for helper, regexes in stuff_to_replace_compiled.items(): + pattern, replace = regexes + # If helper is used, attempt to patch the file + if helper in content and pattern != "": + try: + content = pattern.sub(replace, content) + replaced_stuff = True + except Exception as e: + import pdb; pdb.set_trace() + + # If we couldn't patch the deprecated helper, abort the install or whichever step is performed + if helper in content: + raise YunohostError("This app is likely pretty old and uses deprecated / outdated helpers that can't be migrated easily. It can't be installed anymore.") + + if replaced_stuff: + + # Check the app do load the helper + # If it doesn't, add the instruction ourselve (making sure it's after the #!/bin/bash if it's there... + if filename.split("/")[-1] in ["install", "remove", "upgrade", "backup", "restore"]: + source_helpers = "source /usr/share/yunohost/helpers" + if source_helpers not in content: + content.replace("#!/bin/bash", "#!/bin/bash\n"+source_helpers) + if source_helpers not in content: + content = source_helpers + "\n" + content + + # Actually write the new content in the file + write_to_file(filename, content) + # And complain about those damn deprecated helpers + logger.error("/!\ Packagers ! This app uses a very old deprecated helpers ... Yunohost automatically patched the helpers to use the new recommended practice, but please do consider fixing the upstream code right now ...") diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index bd5d5750d..9c585275f 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -43,7 +43,7 @@ from moulinette.utils.log import getActionLogger from moulinette.utils.filesystem import read_file, mkdir from yunohost.app import ( - app_info, _is_installed, _parse_app_instance_name, _patch_php5 + app_info, _is_installed, _parse_app_instance_name, _patch_php5, _patch_legacy_helpers ) from yunohost.hook import ( hook_list, hook_info, hook_callback, hook_exec, CUSTOM_HOOK_FOLDER @@ -1335,6 +1335,9 @@ class RestoreManager(): app_settings_in_archive = os.path.join(app_dir_in_archive, 'settings') app_scripts_in_archive = os.path.join(app_settings_in_archive, 'scripts') + # Attempt to patch legacy helpers... + _patch_legacy_helpers(app_settings_in_archive) + # Apply dirty patch to make php5 apps compatible with php7 _patch_php5(app_settings_in_archive) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index c63f1ed33..6eb7e4d62 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -907,25 +907,6 @@ def _check_if_vulnerable_to_meltdown(): return CVEs[0]["VULNERABLE"] -def tools_port_available(port): - """ - Check availability of a local port - - Keyword argument: - port -- Port to check - - """ - try: - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.settimeout(1) - s.connect(("localhost", int(port))) - s.close() - except socket.error: - return True - else: - return False - - @is_unit_operation() def tools_shutdown(operation_logger, force=False): shutdown = force From b3398e75682f97960d30f12946cf0cc53db3ebf5 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 18 Aug 2019 13:13:56 +0200 Subject: [PATCH 0033/3170] Update README.md --- README.md | 41 ++++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 4bd070bea..900b58930 100644 --- a/README.md +++ b/README.md @@ -1,42 +1,45 @@ # YunoHost core -- [YunoHost project website](https://yunohost.org) - This repository is the core of YunoHost code. - -Translation status - +- [YunoHost project website](https://yunohost.org) +- [Butracker](https://github.com/YunoHost/issues). -## Issues -- [Please report issues on YunoHost bugtracker](https://github.com/YunoHost/issues). +## Contributing -## Contribute -- You can develop on this repository using [ynh-dev tool](https://github.com/YunoHost/ynh-dev) with `use-git` sub-command. -- On this repository we are [following this workflow](https://yunohost.org/#/build_system_en): `stable <— testing <— branch`. +- You can develop on this repository using [ynh-dev](https://github.com/YunoHost/ynh-dev) with `use-git` sub-command. +- On this repository we are [following this workflow](https://yunohost.org/#/build_system_en): `stable <- testing <- unstable <- your_branch`. - Note: if you modify python scripts, you will have to modifiy the actions map. +- You can help with translation on [our translation platform](https://translate.yunohost.org/engage/yunohost/?utm_source=widget) + +Translation status + ## Repository content -- [YunoHost core Python 2.7 scripts](https://github.com/YunoHost/yunohost/tree/stable/src/yunohost). -- [An actionsmap](https://github.com/YunoHost/yunohost/blob/stable/data/actionsmap/yunohost.yml) used by moulinette. -- [Services configuration templates](https://github.com/YunoHost/yunohost/tree/stable/data/templates). -- [Hooks](https://github.com/YunoHost/yunohost/tree/stable/data/hooks). -- [Locales](https://github.com/YunoHost/yunohost/tree/stable/locales) for translations of `yunohost` command. -- [Shell helpers](https://github.com/YunoHost/yunohost/tree/stable/data/helpers.d) for [application packaging](https://yunohost.org/#/packaging_apps_helpers_en). -- [Modules for the XMPP server Metronome](https://github.com/YunoHost/yunohost/tree/stable/lib/metronome/modules). -- [Debian files](https://github.com/YunoHost/yunohost/tree/stable/debian) for package creation. + +- [YunoHost core Python 2.7 scripts](./src/yunohost). +- [An actionsmap](./data/actionsmap/yunohost.yml) used by moulinette. +- [Services configuration templates](./data/templates). +- [Hooks](./data/hooks). +- [Locales](./locales) for translations of `yunohost` command. +- [Shell helpers](./helpers.d) for [application packaging](https://yunohost.org/#/packaging_apps_helpers_en). +- [Modules for the XMPP server Metronome](./lib/metronome/modules). +- [Debian files](./debian) for package creation. ## How does it work? + - Python core scripts are accessible through two interfaces thanks to the [moulinette framework](https://github.com/YunoHost/moulinette): - [CLI](https://en.wikipedia.org/wiki/Command-line_interface) for `yunohost` command. - [API](https://en.wikipedia.org/wiki/Application_programming_interface) for [web administration module](https://github.com/YunoHost/yunohost-admin) (other modules could be implemented). - You can find more details about how YunoHost works on this [documentation (in french)](https://yunohost.org/#/package_list_fr). ## Dependencies + - [Python 2.7](https://www.python.org/download/releases/2.7) - [Moulinette](https://github.com/YunoHost/moulinette) - [Bash](https://www.gnu.org/software/bash/bash.html) -- [Debian Jessie](https://www.debian.org/releases/jessie) +- [Debian Stretch](https://www.debian.org/releases/stretch) ## License + As [other components of YunoHost core code](https://yunohost.org/#/faq_en), this repository is under GNU AGPL v.3 license. From 63d364e54758a97b607ae464a45d1a96063d7aed Mon Sep 17 00:00:00 2001 From: Kayou Date: Sun, 18 Aug 2019 16:56:18 +0200 Subject: [PATCH 0034/3170] [Fix] Upgrade: Not enough arguments for format string --- 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 4831f050c..4a14c5e4b 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -695,7 +695,7 @@ def app_upgrade(app=[], url=None, file=None): json.dump(status, f) # Replace scripts and manifest and conf (if exists) - os.system('rm -rf "%s/scripts" "%s/manifest.toml %s/manifest.json %s/conf"' % (app_setting_path, app_setting_path, app_setting_path)) + os.system('rm -rf "%s/scripts" "%s/manifest.toml %s/manifest.json %s/conf"' % (app_setting_path, app_setting_path, app_setting_path, app_setting_path)) if os.path.exists(os.path.join(extracted_app_folder, "manifest.json")): os.system('mv "%s/manifest.json" "%s/scripts" %s' % (extracted_app_folder, extracted_app_folder, app_setting_path)) From 98423a7f914a953f83a9bdd2e312800feb19e5f6 Mon Sep 17 00:00:00 2001 From: yalh76 Date: Tue, 20 Aug 2019 20:23:45 +0200 Subject: [PATCH 0035/3170] adding token to not be logged --- src/yunohost/log.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/log.py b/src/yunohost/log.py index 8f8c92010..cf44ec21f 100644 --- a/src/yunohost/log.py +++ b/src/yunohost/log.py @@ -310,7 +310,7 @@ class RedactingFormatter(Formatter): try: # This matches stuff like db_pwd=the_secret or admin_password=other_secret # (the secret part being at least 3 chars to avoid catching some lines like just "db_pwd=") - match = re.search(r'(pwd|pass|password|secret|key)=(\S{3,})$', record.strip()) + match = re.search(r'(pwd|pass|password|secret|key|token)=(\S{3,})$', record.strip()) if match and match.group(2) not in self.data_to_redact: self.data_to_redact.append(match.group(2)) except Exception as e: From b22ad09e63c5b0096aaac4ff1ca3b0401b3eaade Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 24 Aug 2019 12:52:09 +0200 Subject: [PATCH 0036/3170] Update src/yunohost/data_migrations/0010_migrate_to_apps_json.py Co-Authored-By: decentral1se --- src/yunohost/data_migrations/0010_migrate_to_apps_json.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/data_migrations/0010_migrate_to_apps_json.py b/src/yunohost/data_migrations/0010_migrate_to_apps_json.py index 1333e54e8..4358a7476 100644 --- a/src/yunohost/data_migrations/0010_migrate_to_apps_json.py +++ b/src/yunohost/data_migrations/0010_migrate_to_apps_json.py @@ -10,7 +10,7 @@ class MyMigration(Migration): def migrate(self): - logger.info("This is migration is oudated and doesn't do anything anymore. The migration 12 will handle this instead.") + logger.info("This migration is oudated and doesn't do anything anymore. The migration 12 will handle this instead.") pass def backward(self): From 2ffb413a46142e97e976ff4e4be9cbb56760dfbf Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 24 Aug 2019 15:49:25 +0200 Subject: [PATCH 0037/3170] This TODO is implemented now --- src/yunohost/hook.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/yunohost/hook.py b/src/yunohost/hook.py index 64b4cc2ee..8831370bd 100644 --- a/src/yunohost/hook.py +++ b/src/yunohost/hook.py @@ -460,9 +460,8 @@ def _hook_exec_python(path, args, env, loggers): sys.path = [dir_] + sys.path module = import_module(name) - # TODO : We might want to check here that it's a tuple - # containing an int + a dict ? ret = module.main(args, env, loggers) + # # Assert that the return is a (int, dict) tuple assert isinstance(ret, tuple) \ and len(ret) == 2 \ and isinstance(ret[0],int) \ From ff2caae18300449c0dcb3264e9bb9635dcc2af45 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 24 Aug 2019 15:56:52 +0200 Subject: [PATCH 0038/3170] Now guess hook type using mimetypes lib --- src/yunohost/hook.py | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/src/yunohost/hook.py b/src/yunohost/hook.py index 8831370bd..05c5a6b6b 100644 --- a/src/yunohost/hook.py +++ b/src/yunohost/hook.py @@ -27,6 +27,7 @@ import os import re import sys import tempfile +import mimetypes from glob import iglob from importlib import import_module @@ -320,17 +321,6 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False, if not os.path.isfile(path): raise YunohostError('file_does_not_exist', path=path) - # Check the type of the hook (bash by default) - hook_type = "bash" - # (non-bash hooks shall start with something like "#!/usr/bin/env language") - hook_ext = os.path.splitext(path)[1] - if hook_ext == ".py": - hook_type = "python" - else: - # TODO / FIXME : if needed in the future, implement support for other - # languages... - assert hook_ext in ["", ".sh"], "hook_exec only supports bash and python hooks for now" - # Define output loggers and call command loggers = ( lambda l: logger.debug(l.rstrip()+"\r"), @@ -338,13 +328,13 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False, lambda l: logger.info(l.rstrip()) ) - if hook_type == "bash": - returncode, returndata = _hook_exec_bash(path, args, no_trace, chdir, env, user, return_format, loggers) - elif hook_type == "python": + # Check the type of the hook (bash by default) + # For now we support only python and bash hooks. + hook_type = mimetypes.MimeTypes().guess_type(path)[0] + if hook_type == 'text/x-python': returncode, returndata = _hook_exec_python(path, args, env, loggers) else: - # Doesn't happen ... c.f. previous assertion - returncode, returndata = None, {} + returncode, returndata = _hook_exec_bash(path, args, no_trace, chdir, env, user, return_format, loggers) # Check and return process' return code if returncode is None: From 21c6d1b159d7b477e6e111298d0ee36fa89de5d1 Mon Sep 17 00:00:00 2001 From: madtibo Date: Wed, 24 Jul 2019 16:11:07 +0200 Subject: [PATCH 0039/3170] change PostgreSQL -password' authentication to 'md5' --- data/helpers.d/postgresql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/postgresql b/data/helpers.d/postgresql index a76580b11..d252ae2dc 100644 --- a/data/helpers.d/postgresql +++ b/data/helpers.d/postgresql @@ -283,11 +283,11 @@ ynh_psql_test_if_first_run() { sudo --login --user=postgres psql -c"ALTER user postgres WITH PASSWORD '$psql_root_password'" postgres - # force all user to connect to local database using passwords + # 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\3password" --target_file="$pg_hba" + 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" From 2fc13a7af01a0c979386c571deadb68eeb3824db Mon Sep 17 00:00:00 2001 From: madtibo Date: Fri, 26 Jul 2019 13:45:33 +0200 Subject: [PATCH 0040/3170] switch from password to md5 authentication in postgresql --- ...stgresql_password_to_md5_authentication.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/yunohost/data_migrations/0011_postgresql_password_to_md5_authentication.py diff --git a/src/yunohost/data_migrations/0011_postgresql_password_to_md5_authentication.py b/src/yunohost/data_migrations/0011_postgresql_password_to_md5_authentication.py new file mode 100644 index 000000000..19f6ada69 --- /dev/null +++ b/src/yunohost/data_migrations/0011_postgresql_password_to_md5_authentication.py @@ -0,0 +1,21 @@ +import glob +import re +from yunohost.tools import Migration +from moulinette.utils.filesystem import chown + + +class MyMigration(Migration): + + "Force authentication in md5 for local connexions" + + all_hba_files = glob.glob("/etc/postgresql/*/*/pg_hba.conf") + + def forward(self): + for filename in self.all_hba_files: + pg_hba_in = read_file(filename) + write_to_file(filename, re.sub(r"local(\s*)all(\s*)all(\s*)password", "local\\1all\\2all\\3md5", pg_hba_in)) + + def backward(self): + for filename in self.all_hba_files: + pg_hba_in = read_file(filename) + write_to_file(filename, re.sub(r"local(\s*)all(\s*)all(\s*)md5", "local\\1all\\2all\\3password", pg_hba_in)) From d764b02701f22e6e007e4fb2de6b57c37bcc5306 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 24 Aug 2019 17:53:32 +0200 Subject: [PATCH 0041/3170] Annnnd I forgot to add the migration --- .../0012_futureproof_appslist_system.py | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 src/yunohost/data_migrations/0012_futureproof_appslist_system.py diff --git a/src/yunohost/data_migrations/0012_futureproof_appslist_system.py b/src/yunohost/data_migrations/0012_futureproof_appslist_system.py new file mode 100644 index 000000000..e0bf70d04 --- /dev/null +++ b/src/yunohost/data_migrations/0012_futureproof_appslist_system.py @@ -0,0 +1,46 @@ + +import os +import shutil + +from moulinette.utils.log import getActionLogger +from moulinette.utils.filesystem import read_json + +from yunohost.tools import Migration +from yunohost.app import (_initialize_appslists_system, + _update_appslist, + APPSLISTS_CACHE, + APPSLISTS_CONF) + +logger = getActionLogger('yunohost.migration') + +LEGACY_APPSLISTS_CONF = '/etc/yunohost/appslists.json' +LEGACY_APPSLISTS_CONF_BACKUP = LEGACY_APPSLISTS_CONF + ".old" + + +class MyMigration(Migration): + + "Migrate to the new future-proof appslist system" + + def migrate(self): + + if not os.path.exists(LEGACY_APPSLISTS_CONF): + logger.info("No need to do anything") + + # Destroy old lecacy cache + if os.path.exists(APPSLISTS_CACHE): + shutil.rmtree(APPSLISTS_CACHE) + + # Backup the legacy file + try: + legacy_list = read_json(LEGACY_APPSLISTS_CONF) + # If there's only one list, we assume it's just the old official list + # Otherwise, warn the (power-?)users that they should migrate their old list manually + if len(legacy_list) > 1: + logger.warning("It looks like you had additional appslist in the configuration file %s! YunoHost now uses %s instead, but it won't migrate your custom appslist. You should do this manually. The old file has been backuped in %s." % (LEGACY_APPSLISTS_CONF, APPSLISTS_CONF, LEGACY_APPSLISTS_CONF_BACKUP)) + except Exception as e: + logger.warning("Unable to parse the legacy conf %s (error : %s) ... migrating anyway" % (LEGACY_APPSLISTS_CONF, str(e))) + + os.rename(LEGACY_APPSLISTS_CONF, LEGACY_APPSLISTS_CONF_BACKUP) + + _initialize_appslists_system() + _update_appslist() From fcae50a6e0e8ab5bcbd6f5e86462c7efd6d086d0 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 24 Aug 2019 18:14:16 +0200 Subject: [PATCH 0042/3170] Move / test / fix migration for postgresql auth --- locales/en.json | 1 + ...ion.py => 0012_postgresql_password_to_md5_authentication.py} | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) rename src/yunohost/data_migrations/{0011_postgresql_password_to_md5_authentication.py => 0012_postgresql_password_to_md5_authentication.py} (91%) diff --git a/locales/en.json b/locales/en.json index b45739149..903aa325a 100644 --- a/locales/en.json +++ b/locales/en.json @@ -331,6 +331,7 @@ "migration_description_0009_decouple_regenconf_from_services": "Decouple the regen-conf mechanism from services", "migration_description_0010_migrate_to_apps_json": "Remove deprecated appslists and use the new unified 'apps.json' list instead", "migration_description_0011_setup_group_permission": "Setup user group and setup permission for apps and services", + "migration_description_0012_postgresql_password_to_md5_authentication": "Force postgresql authentication to use md5 for local connections", "migration_0003_backward_impossible": "The stretch migration cannot be reverted.", "migration_0003_start": "Starting migration to Stretch. The logs will be available in {logfile}.", "migration_0003_patching_sources_list": "Patching the sources.lists…", diff --git a/src/yunohost/data_migrations/0011_postgresql_password_to_md5_authentication.py b/src/yunohost/data_migrations/0012_postgresql_password_to_md5_authentication.py similarity index 91% rename from src/yunohost/data_migrations/0011_postgresql_password_to_md5_authentication.py rename to src/yunohost/data_migrations/0012_postgresql_password_to_md5_authentication.py index 19f6ada69..5d36b3e23 100644 --- a/src/yunohost/data_migrations/0011_postgresql_password_to_md5_authentication.py +++ b/src/yunohost/data_migrations/0012_postgresql_password_to_md5_authentication.py @@ -1,7 +1,7 @@ import glob import re from yunohost.tools import Migration -from moulinette.utils.filesystem import chown +from moulinette.utils.filesystem import read_file, write_to_file class MyMigration(Migration): From 68e97245175e846e7c737394f5d1577273d8b496 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 25 Aug 2019 16:40:41 +0200 Subject: [PATCH 0043/3170] [mod] remove catchall bad exception --- locales/en.json | 2 +- src/yunohost/dyndns.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/locales/en.json b/locales/en.json index b45739149..9cdb14483 100644 --- a/locales/en.json +++ b/locales/en.json @@ -185,7 +185,7 @@ "dyndns_could_not_check_provide": "Could not check if {provider:s} can provide {domain:s}.", "dyndns_could_not_check_available": "Could not check if {domain:s} is available on {provider:s}.", "dyndns_cron_installed": "The DynDNS cron job has been installed", - "dyndns_cron_remove_failed": "Unable to remove the DynDNS cron job", + "dyndns_cron_remove_failed": "Unable to remove the DynDNS cron job because: {error}", "dyndns_cron_removed": "The DynDNS cron job has been removed", "dyndns_ip_update_failed": "Unable to update IP address on DynDNS", "dyndns_ip_updated": "Your IP address has been updated on DynDNS", diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index 2dadcef52..c4558d79c 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -325,8 +325,8 @@ def dyndns_removecron(): """ try: os.remove("/etc/cron.d/yunohost-dyndns") - except: - raise YunohostError('dyndns_cron_remove_failed') + except Exception as e: + raise YunohostError('dyndns_cron_remove_failed', error=e) logger.success(m18n.n('dyndns_cron_removed')) From 0849adbee14d576d365adee538f7689db177b730 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 25 Aug 2019 16:48:21 +0200 Subject: [PATCH 0044/3170] [mod] remove useless weird comment --- src/yunohost/utils/network.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/yunohost/utils/network.py b/src/yunohost/utils/network.py index 1f82a87b0..af63a0e7d 100644 --- a/src/yunohost/utils/network.py +++ b/src/yunohost/utils/network.py @@ -71,9 +71,6 @@ def get_gateway(): return addr.popitem()[1] if len(addr) == 1 else None -# - - def _extract_inet(string, skip_netmask=False, skip_loopback=True): """ Extract IP addresses (v4 and/or v6) from a string limited to one From 19dbe87167a96c81ce20e4b36838b51e30c0892a Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 25 Aug 2019 16:50:30 +0200 Subject: [PATCH 0045/3170] [mod] regex must be stored in raw strings --- src/yunohost/utils/network.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/yunohost/utils/network.py b/src/yunohost/utils/network.py index af63a0e7d..4e23516c3 100644 --- a/src/yunohost/utils/network.py +++ b/src/yunohost/utils/network.py @@ -48,9 +48,9 @@ def get_network_interfaces(): # Get network devices and their addresses (raw infos from 'ip addr') devices_raw = {} output = subprocess.check_output('ip addr show'.split()) - for d in re.split('^(?:[0-9]+: )', output, flags=re.MULTILINE): + for d in re.split(r'^(?:[0-9]+: )', output, flags=re.MULTILINE): # Extract device name (1) and its addresses (2) - m = re.match('([^\s@]+)(?:@[\S]+)?: (.*)', d, flags=re.DOTALL) + m = re.match(r'([^\s@]+)(?:@[\S]+)?: (.*)', d, flags=re.DOTALL) if m: devices_raw[m.group(1)] = m.group(2) @@ -63,7 +63,7 @@ def get_network_interfaces(): def get_gateway(): output = subprocess.check_output('ip route show'.split()) - m = re.search('default via (.*) dev ([a-z]+[0-9]?)', output) + m = re.search(r'default via (.*) dev ([a-z]+[0-9]?)', output) if not m: return None @@ -86,10 +86,10 @@ def _extract_inet(string, skip_netmask=False, skip_loopback=True): A dict of {protocol: address} with protocol one of 'ipv4' or 'ipv6' """ - ip4_pattern = '((25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}' - ip6_pattern = '(((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)' - ip4_pattern += '/[0-9]{1,2})' if not skip_netmask else ')' - ip6_pattern += '/[0-9]{1,3})' if not skip_netmask else ')' + ip4_pattern = r'((25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}' + ip6_pattern = r'(((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)' + ip4_pattern += r'/[0-9]{1,2})' if not skip_netmask else ')' + ip6_pattern += r'/[0-9]{1,3})' if not skip_netmask else ')' result = {} for m in re.finditer(ip4_pattern, string): From b8f27b75934c15f616d1580613218aad0cac04b2 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Mon, 26 Aug 2019 17:35:18 +0200 Subject: [PATCH 0046/3170] [enh] add --force to 'dyndns update' --- data/actionsmap/yunohost.yml | 4 ++++ src/yunohost/dyndns.py | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 2b87b6daa..454ccc76e 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1533,6 +1533,10 @@ dyndns: -i: full: --ipv4 help: IP address to send + -f: + full: --force + help: Force the update (for debugging only) + action: store_true -6: full: --ipv6 help: IPv6 address to send diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index c4558d79c..0a5b8c180 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -176,7 +176,7 @@ def dyndns_subscribe(operation_logger, subscribe_host="dyndns.yunohost.org", dom @is_unit_operation() def dyndns_update(operation_logger, dyn_host="dyndns.yunohost.org", domain=None, key=None, - ipv4=None, ipv6=None): + ipv4=None, ipv6=None, force=False): """ Update IP on DynDNS platform @@ -249,7 +249,7 @@ def dyndns_update(operation_logger, dyn_host="dyndns.yunohost.org", domain=None, logger.debug("Requested IPv4/v6 are (%s, %s)" % (ipv4, ipv6)) # no need to update - if old_ipv4 == ipv4 and old_ipv6 == ipv6: + if not force and (old_ipv4 == ipv4 and old_ipv6 == ipv6): logger.info("No updated needed.") return else: From 87435775ee6208b3773288f879e8b79ead1430b6 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Tue, 27 Aug 2019 19:45:33 +0200 Subject: [PATCH 0047/3170] [enh] add --dry-run option to dyndns update --- data/actionsmap/yunohost.yml | 4 ++++ src/yunohost/dyndns.py | 23 ++++++++++++++--------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 454ccc76e..7c864cce0 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1537,6 +1537,10 @@ dyndns: full: --force help: Force the update (for debugging only) action: store_true + -D: + full: --dry-run + help: Only display the generated zone + action: store_true -6: full: --ipv6 help: IPv6 address to send diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index 0a5b8c180..70b964b7c 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -33,7 +33,7 @@ import subprocess from moulinette import m18n from moulinette.core import MoulinetteError from moulinette.utils.log import getActionLogger -from moulinette.utils.filesystem import write_to_file +from moulinette.utils.filesystem import write_to_file, read_file from moulinette.utils.network import download_json from moulinette.utils.process import check_output @@ -176,7 +176,7 @@ def dyndns_subscribe(operation_logger, subscribe_host="dyndns.yunohost.org", dom @is_unit_operation() def dyndns_update(operation_logger, dyn_host="dyndns.yunohost.org", domain=None, key=None, - ipv4=None, ipv6=None, force=False): + ipv4=None, ipv6=None, force=False, dry_run=False): """ Update IP on DynDNS platform @@ -249,7 +249,7 @@ def dyndns_update(operation_logger, dyn_host="dyndns.yunohost.org", domain=None, logger.debug("Requested IPv4/v6 are (%s, %s)" % (ipv4, ipv6)) # no need to update - if not force and (old_ipv4 == ipv4 and old_ipv6 == ipv6): + if (not force and not dry_run) and (old_ipv4 == ipv4 and old_ipv6 == ipv6): logger.info("No updated needed.") return else: @@ -296,13 +296,18 @@ def dyndns_update(operation_logger, dyn_host="dyndns.yunohost.org", domain=None, logger.debug("Now pushing new conf to DynDNS host...") - try: - command = ["/usr/bin/nsupdate", "-k", key, DYNDNS_ZONE] - subprocess.check_call(command) - except subprocess.CalledProcessError: - raise YunohostError('dyndns_ip_update_failed') + if not dry_run: + try: + command = ["/usr/bin/nsupdate", "-k", key, DYNDNS_ZONE] + subprocess.check_call(command) + except subprocess.CalledProcessError: + raise YunohostError('dyndns_ip_update_failed') - logger.success(m18n.n('dyndns_ip_updated')) + logger.success(m18n.n('dyndns_ip_updated')) + else: + print(read_file(DYNDNS_ZONE)) + print("") + print("Warning: dry run, this is only the generated config, it won't be applied") def dyndns_installcron(): From 2aa5e7f6400a97a04bf69dfdd024db7dbe302c8a Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Tue, 27 Aug 2019 19:45:45 +0200 Subject: [PATCH 0048/3170] [mod] raw string for correct escape sequence --- src/yunohost/dyndns.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index 70b964b7c..a35d39736 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -279,7 +279,7 @@ def dyndns_update(operation_logger, dyn_host="dyndns.yunohost.org", domain=None, # should be muc.the.domain.tld. or the.domain.tld if record["value"] == "@": record["value"] = domain - record["value"] = record["value"].replace(";", "\;") + record["value"] = record["value"].replace(";", r"\;") action = "update add {name}.{domain}. {ttl} {type} {value}".format(domain=domain, **record) action = action.replace(" @.", " ") From 6365a26cd3f705ff8600f6dacb9beaf84a030f58 Mon Sep 17 00:00:00 2001 From: Luke Murphy Date: Wed, 28 Aug 2019 18:16:39 +0200 Subject: [PATCH 0049/3170] Add basic Travis CI configuration --- .travis.yml | 19 +++++++++++++++---- README.md | 3 +++ pytest.ini | 4 ++++ setup.cfg | 2 ++ tox.ini | 18 ++++++++++++++++++ 5 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 pytest.ini create mode 100644 setup.cfg create mode 100644 tox.ini diff --git a/.travis.yml b/.travis.yml index 25fe0e5fc..8674d4d03 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,16 @@ language: python -install: "pip install pytest pyyaml" -python: - - "2.7" -script: "py.test tests" + +matrix: + allow_failures: + - env: TOXENV=lint + include: + - python: 2.7 + env: TOXENV=py27 + - python: 2.7 + env: TOXENV=lint + +install: + - pip install tox + +script: + - tox diff --git a/README.md b/README.md index 900b58930..d06e97c45 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ +[![Build Status](https://travis-ci.org/YunoHost/yunohost.svg?branch=stretch-unstable)](https://travis-ci.org/YunoHost/yunohost) +[![GitHub license](https://img.shields.io/github/license/YunoHost/yunohost)](https://github.com/YunoHost/yunohost/blob/stretch-unstable/LICENSE) + # YunoHost core This repository is the core of YunoHost code. diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 000000000..f9200ab9c --- /dev/null +++ b/pytest.ini @@ -0,0 +1,4 @@ +[pytest] +addopts = -s -v +norecursedirs = dist doc build .tox .eggs +testpaths = tests/ diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 000000000..db1dde69d --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[flake8] +ignore = E501,E128,E731,E722 diff --git a/tox.ini b/tox.ini new file mode 100644 index 000000000..ac109609c --- /dev/null +++ b/tox.ini @@ -0,0 +1,18 @@ +[tox] +envlist = + py27 + lint +skipdist = True + +[testenv] +skip_install=True +deps = + pytest >= 4.6.3, < 5.0 + pyyaml >= 5.1.2, < 6.0 +commands = + pytest {posargs} + +[testenv:lint] +skip_install=True +commands = flake8 src doc data tests +deps = flake8 From ae920866b6882d1d7564a2dc3e57e95ecdd080c2 Mon Sep 17 00:00:00 2001 From: ppr Date: Fri, 16 Aug 2019 08:08:20 +0000 Subject: [PATCH 0050/3170] Translated using Weblate (French) Currently translated at 92.8% (540 of 582 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 607a2a348..8d81a5cab 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -120,7 +120,7 @@ "ldap_initialized": "L’annuaire LDAP a été initialisé", "license_undefined": "indéfinie", "mail_alias_remove_failed": "Impossible de supprimer l’alias de courriel '{mail:s}'", - "mail_domain_unknown": "Le domaine d'adresse du courriel '{domain:s}' est inconnu", + "mail_domain_unknown": "Le domaine '{domain:s}' pour l'adresse de courriel est inconnu", "mail_forward_remove_failed": "Impossible de supprimer le courriel de transfert '{mail:s}'", "maindomain_change_failed": "Impossible de modifier le domaine principal", "maindomain_changed": "Le domaine principal a été modifié", @@ -465,7 +465,7 @@ "migration_description_0004_php5_to_php7_pools": "Reconfiguration des groupes PHP pour utiliser PHP 7 au lieu de PHP 5", "migration_description_0005_postgresql_9p4_to_9p6": "Migration des bases de données de PostgreSQL 9.4 vers PostgreSQL 9.6", "migration_0005_postgresql_94_not_installed": "PostgreSQL n’a pas été installé sur votre système. Rien à faire !", - "migration_0005_postgresql_96_not_installed": "PostgreSQL 9.4 a été trouvé et installé, mais pas PostgreSQL 9.6 !? Quelque chose d’étrange a dû arriver à votre système :( …", + "migration_0005_postgresql_96_not_installed": "PostgreSQL 9.4 a été trouvé et installé, mais pas PostgreSQL 9.6 !? Quelque chose d’étrange a dû arriver à votre système :(", "migration_0005_not_enough_space": "Il n’y a pas assez d’espace libre de disponible sur {path} pour lancer maintenant la migration :(.", "recommend_to_add_first_user": "La post-installation est terminée mais YunoHost a besoin d’au moins un utilisateur pour fonctionner correctement. Vous devez en ajouter un en utilisant la commande 'yunohost user create $nomdutilisateur' ou bien via l’interface d’administration web.", "service_description_php7.0-fpm": "exécute des applications écrites en PHP avec Nginx", From 57756d1d9a600777d011389927e69b7b86107872 Mon Sep 17 00:00:00 2001 From: troll Date: Fri, 16 Aug 2019 20:26:15 +0000 Subject: [PATCH 0051/3170] Translated using Weblate (French) Currently translated at 92.8% (540 of 582 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/locales/fr.json b/locales/fr.json index 8d81a5cab..e17a2c6d5 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -562,5 +562,16 @@ "tools_upgrade_special_packages_explanation": "Cette opération prendra fin mais la mise à jour spécifique continuera en arrière-plan. Veuillez ne pas lancer d'autre action sur votre serveur dans les 10 prochaines minutes (en fonction de la vitesse de votre matériel). Une fois que c'est fait, vous devrez peut-être vous reconnecter sur le panel d'administration web. Le journal de la mise à jour sera disponible dans Outils > Log (dans le panel d'administration web) ou dans la liste des journaux YunoHost (en ligne de commande).", "update_apt_cache_failed": "Impossible de mettre à jour le cache APT (gestionnaire de paquets Debian). Voici un extrait du fichier sources.list qui pourrait vous aider à identifier les lignes problématiques :\n{sourceslist}", "update_apt_cache_warning": "Des erreurs se sont produites lors de la mise à jour du cache APT (gestionnaire de paquets Debian). Voici un extrait des lignes du fichier sources.list qui pourrait vous aider à identifier les lignes problématiques :\n{sourceslist}", - "apps_permission_not_found": "Aucune permission trouvée pour les applications installées" + "apps_permission_not_found": "Aucune permission trouvée pour les applications installées", + "apps_permission_restoration_failed": "L'autorisation '{permission:s}' pour la restauration de l'application {app:s} a échoué", + "backup_permission": "Autorisation de sauvegarde pour l'application {app:s}", + "edit_group_not_allowed": "Vous n'êtes pas autorisé à modifier le groupe {group:s}", + "error_when_removing_sftpuser_group": "Erreur en essayant de supprimer le groupe sftpusers", + "group_created": "Le groupe '{group}' a créé avec succès", + "group_deleted": "Le groupe '{group}' a été supprimé", + "group_deletion_not_allowed": "Le groupe {group:s} ne peut pas être supprimé manuellement.", + "group_info_failed": "L'information sur le groupe a échoué", + "group_unknown": "Le groupe {group:s} est inconnu", + "group_updated": "Le groupe '{group}' a été mis à jour", + "group_update_failed": "La mise à jour du groupe '{group}' a échoué" } From c562b05edfe748c12a9c7ef01e152e5811d06196 Mon Sep 17 00:00:00 2001 From: htsr Date: Fri, 16 Aug 2019 20:42:20 +0000 Subject: [PATCH 0052/3170] Translated using Weblate (French) Currently translated at 92.8% (540 of 582 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/locales/fr.json b/locales/fr.json index e17a2c6d5..93ebef20f 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -573,5 +573,10 @@ "group_info_failed": "L'information sur le groupe a échoué", "group_unknown": "Le groupe {group:s} est inconnu", "group_updated": "Le groupe '{group}' a été mis à jour", - "group_update_failed": "La mise à jour du groupe '{group}' a échoué" + "group_update_failed": "La mise à jour du groupe '{group}' a échoué", + "group_already_allowed": "Le groupe '{group:s}' a déjà la permission '{permission:s}' activée pour l'application '{app:s}'", + "group_already_disallowed": "Le groupe '{group:s}' a déjà la permission '{permission:s}' désactivée pour l'application '{app:s}'", + "group_name_already_exist": "Le groupe {name:s} existe déjà", + "group_creation_failed": "Échec de la création du groupe '{group}'", + "group_deletion_failed": "Échec de la suppression du groupe '{group}'" } From 9bea4ee3048bb7cfd9bc22ca7b3ac581cfa805ca Mon Sep 17 00:00:00 2001 From: xaloc33 Date: Wed, 21 Aug 2019 08:25:42 +0000 Subject: [PATCH 0053/3170] Translated using Weblate (Catalan) Currently translated at 100.0% (582 of 582 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ca/ --- locales/ca.json | 66 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 62 insertions(+), 4 deletions(-) diff --git a/locales/ca.json b/locales/ca.json index 824745c12..a9a0c2e0f 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -25,7 +25,7 @@ "app_manifest_invalid": "Manifest d'aplicació incorrecte: {error}", "app_no_upgrade": "No hi ha cap aplicació per actualitzar", "app_not_correctly_installed": "{app:s} sembla estar mal instal·lada", - "app_not_installed": "{app:s} no està instal·lada", + "app_not_installed": "L'aplicació «{app:s}» no està instal·lada. Aquí hi ha la llista d'aplicacions instal·lades: {all_apps}", "app_not_properly_removed": "{app:s} no s'ha pogut suprimir correctament", "app_package_need_update": "El paquet de l'aplicació {app} ha de ser actualitzat per poder seguir els canvis de YunoHost", "app_removed": "{app:s} ha estat suprimida", @@ -175,7 +175,7 @@ "domain_dyndns_dynette_is_unreachable": "No s'ha pogut abastar la dynette YunoHost, o bé YunoHost no està connectat a internet correctament o bé el servidor dynette està caigut. Error: {error}", "domain_dyndns_invalid": "Domini no vàlid per utilitzar amb DynDNS", "domain_dyndns_root_unknown": "Domini DynDNS principal desconegut", - "domain_hostname_failed": "No s'ha pogut establir un nou nom d'amfitrió", + "domain_hostname_failed": "No s'ha pogut establir un nou nom d'amfitrió. Això podria causar problemes més tard (no és segur ... 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_unknown": "Domini desconegut", "domain_zone_exists": "El fitxer de zona DNS ja existeix", @@ -236,7 +236,7 @@ "invalid_url_format": "Format d'URL invàlid", "ip6tables_unavailable": "No podeu modificar les ip6tables aquí. O bé sou en un contenidor o bé el vostre nucli no és compatible amb aquesta opció", "iptables_unavailable": "No podeu modificar les iptables aquí. O bé sou en un contenidor o bé el vostre nucli no és compatible amb aquesta opció", - "log_corrupted_md_file": "El fitxer de metadades yaml associat amb els registres està malmès: « {md_file} »", + "log_corrupted_md_file": "El fitxer de metadades yaml associat amb els registres està malmès: « {md_file} »\nError: {error}", "log_category_404": "La categoria de registres « {category} » no existeix", "log_link_to_log": "El registre complet d'aquesta operació: «{desc}»", "log_help_to_get_log": "Per veure el registre de l'operació « {desc} », utilitzeu l'ordre « yunohost log display {name} »", @@ -523,5 +523,63 @@ "yunohost_ca_creation_success": "S'ha creat l'autoritat de certificació local.", "yunohost_configured": "S'ha configurat YunoHost", "yunohost_installing": "Instal·lació de YunoHost…", - "yunohost_not_installed": "YunoHost no està instal·lat o no està instal·lat correctament. Executeu «yunohost tools postinstall»" + "yunohost_not_installed": "YunoHost no està instal·lat o no està instal·lat correctament. Executeu «yunohost tools postinstall»", + "apps_permission_not_found": "No s'ha trobat cap permís per les aplicacions instal·lades", + "apps_permission_restoration_failed": "Ha fallat el permís «{permission:s}» per la restauració de l'aplicació {app:s}", + "backup_permission": "Permís de còpia de seguretat per l'aplicació {app:s}", + "edit_group_not_allowed": "No teniu autorització per modificar el grup {group:s}", + "edit_permission_with_group_all_users_not_allowed": "No podeu modificar els permisos del grup «all_users», s'ha d'utlilitzar «yunohost user permission clear APP» o «yunohost user permission add APP -u USER».", + "error_when_removing_sftpuser_group": "Error intentant eliminar el gruo sftpusers", + "group_already_allowed": "El grup «{group:s}» ja té el permís «{permission:s}» activat per l'aplicació «{app:s}»", + "group_already_disallowed": "El grup «{group:s}» ja té els permisos «{permission:s}» desactivats per l'aplicació «{app:s}»", + "group_name_already_exist": "El grup {name:s} ja existeix", + "group_created": "S'ha creat el grup «{group}»", + "group_creation_failed": "No s'ha pogut crear el grup «{group}»", + "group_deleted": "S'ha eliminat el grup «{group}»", + "group_deletion_failed": "No s'ha pogut eliminar el grup «{group}»", + "group_deletion_not_allowed": "El grup {group:s} no es pot eliminar manualment.", + "group_info_failed": "Ha fallat la informació del grup", + "group_unknown": "Grup {group:s} desconegut", + "group_updated": "S'ha actualitzat el grup «{group}»", + "group_update_failed": "No s'ha pogut actualitzat el grup «{group}»", + "log_permission_add": "Afegir el permís «{}» per l'aplicació «{}»", + "log_permission_remove": "Suprimir el permís «{}»", + "log_permission_update": "Actualitzar el permís «{}» per l'aplicació «{}»", + "log_user_group_add": "Afegir grup «{}»", + "log_user_group_delete": "Eliminar grup «{}»", + "log_user_group_update": "Actualitzar grup «{}»", + "log_user_permission_add": "Actualitzar el permís «{}»", + "log_user_permission_remove": "Actualitzar el permís «{}»", + "mailbox_disabled": "La bústia de correu està desactivada per als usuaris {user:s}", + "migration_description_0011_setup_group_permission": "Configurar el grup d'usuaris i els permisos per les aplicacions i els serveis", + "migration_0011_backup_before_migration": "Creant una còpia de seguretat de la base de dades LDAP i la configuració de les aplicacions abans d'efectuar la migració.", + "migration_0011_can_not_backup_before_migration": "No s'ha pogut fer la còpia de seguretat abans de la migració. No s'ha pogut fer la migració. Error: {error:s}", + "migration_0011_create_group": "Creant un grup per a cada usuari…", + "migration_0011_done": "Migració completa. Ja podeu gestionar grups d'usuaris.", + "migration_0011_LDAP_config_dirty": "Sembla que heu modificat manualment la configuració LDAP. Per fer aquesta migració s'ha d'actualitzar la configuració LDAP.\nGuardeu la configuració actual, reinicieu la configuració original amb l'ordre «yunohost tools regen-conf -f» i torneu a intentar la migració", + "migration_0011_LDAP_update_failed": "Ha fallat l'actualització de LDAP. Error: {error:s}", + "migration_0011_migrate_permission": "Fent la migració dels permisos de la configuració de les aplicacions a LDAP…", + "migration_0011_migration_failed_trying_to_rollback": "La migració ha fallat … s'intenta tornar el sistema a l'estat anterior.", + "migration_0011_rollback_success": "S'ha tornat el sistema a l'estat anterior.", + "migration_0011_update_LDAP_database": "Actualitzant la base de dades LDAP…", + "migration_0011_update_LDAP_schema": "Actualitzant l'esquema LDAP…", + "need_define_permission_before": "Heu de tornar a redefinir els permisos utilitzant «yunohost user permission add -u USER» abans d'eliminar un grup permès", + "permission_already_clear": "Ja s'ha donat el permís «{permission:s}» per l'aplicació {app:s}", + "permission_already_exist": "El permís «{permission:s}» ja existeix per l'aplicació {app:s}", + "permission_created": "S'ha creat el permís «{permission:s}» per l'aplicació {app:s}", + "permission_creation_failed": "La creació del permís ha fallat", + "permission_deleted": "S'ha eliminat el permís «{permission:s}» per l'aplicació {app:s}", + "permission_deletion_failed": "L'eliminació del permís «{permission:s}» per l'aplicació {app:s} ha fallat", + "permission_not_found": "No s'ha trobat el permís «{permission:s}» per l'aplicació {app:s}", + "permission_name_not_valid": "El nom del permís «{permission:s}» no és vàlid", + "permission_update_failed": "L'actualització del permís ha fallat", + "permission_generated": "S'ha actualitzat la base de dades del permís", + "permission_updated": "S'ha actualitzat el permís «{permission:s}» per l'aplicació {app:s}", + "permission_update_nothing_to_do": "No hi ha cap permís per actualitzar", + "remove_main_permission_not_allowed": "No es pot eliminar el permís principal", + "remove_user_of_group_not_allowed": "No es pot eliminar l'usuari {user:s} del grup {group:s}", + "system_groupname_exists": "El nom de grup ja existeix en el sistema de grups", + "tools_update_failed_to_app_fetchlist": "No s'ha pogut actualitzar la llista d'aplicacions de YunoHost a causa de: {error}", + "user_already_in_group": "L'usuari {user:s} ja és en el grup {group:s}", + "user_not_in_group": "L'usuari {user:s} no és en el grup {group:s}" } From abee5583266e6c5b106ee55535708a570fffb266 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quent=C3=AD?= Date: Mon, 19 Aug 2019 19:57:38 +0000 Subject: [PATCH 0054/3170] Translated using Weblate (Occitan) Currently translated at 93.5% (544 of 582 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/oc/ --- locales/oc.json | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/locales/oc.json b/locales/oc.json index fc6f6946c..a65154c35 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -9,7 +9,7 @@ "app_install_files_invalid": "Fichièrs d’installacion incorrèctes", "app_no_upgrade": "Pas cap d’aplicacion de metre a jorn", "app_not_correctly_installed": "{app:s} sembla pas ben installat", - "app_not_installed": "{app:s} es pas installat", + "app_not_installed": "L’aplicacion {app:s} es pas installat. Vaquí la lista de las aplicacions installadas : {all_apps}", "app_not_properly_removed": "{app:s} es pas estat corrèctament suprimit", "app_removed": "{app:s} es estat suprimit", "app_unknown": "Aplicacion desconeguda", @@ -148,7 +148,7 @@ "domain_dyndns_invalid": "Domeni incorrècte per una utilizacion amb DynDNS", "domain_dyndns_root_unknown": "Domeni DynDNS màger desconegut", "domain_exists": "Lo domeni existís ja", - "domain_hostname_failed": "Fracàs de la creacion d’un nòu nom d’òst", + "domain_hostname_failed": "Fracàs de la creacion d’un nòu nom d’òst. Aquò poirà provocar de problèmas mai tard (mas es pas segur… benlèu que coparà pas res).", "domain_unknown": "Domeni desconegut", "domain_zone_exists": "Lo fichièr zòna DNS existís ja", "domain_zone_not_found": "Fichèr de zòna DNS introbable pel domeni {:s}", @@ -404,7 +404,7 @@ "backup_no_uncompress_archive_dir": "Lo repertòri de l’archiu descomprimit existís pas", "pattern_username": "Deu èsser compausat solament de caractèrs alfanumerics en letras minusculas e de tirets basses", "experimental_feature": "Atencion : aquesta foncionalitat es experimentala e deu pas èsser considerada coma establa, deuriatz pas l’utilizar levat que sapiatz çò que fasètz.", - "log_corrupted_md_file": "Lo fichièr yaml de metadonada amb los jornals d’audit es corromput : « {md_file} »", + "log_corrupted_md_file": "Lo fichièr yaml de metadonada amb los jornals d’audit es corromput : « {md_file} »\nError : {error:s}", "log_category_404": "La categoria de jornals d’audit « {category} » existís pas", "log_link_to_log": "Jornal complèt d’aquesta operacion : {desc}", "log_help_to_get_log": "Per veire lo jornal d’aquesta operacion « {desc} », utilizatz la comanda « yunohost log display {name} »", @@ -547,5 +547,25 @@ "tools_upgrade_special_packages": "Actualizacion dels paquets « especials » (ligats a YunoHost)…", "tools_upgrade_special_packages_explanation": "Aquesta accion s’acabarà mas l’actualizacion especiala actuala contunharà en rèire-plan. Comencetz pas cap d’autra accion sul servidor dins las ~ 10 minutas que venon (depend de la velocitat de la maquina). Un còp acabat, benlèu que vos calrà vos tornar connectar a l’interfàcia d’administracion. Los jornals d’audit de l’actualizacion seràn disponibles a Aisinas > Jornals d’audit (dins l’interfàcia d’administracion) o amb « yunohost log list » (en linha de comanda).", "update_apt_cache_failed": "I a agut d’errors en actualizar la memòria cache d’APT (lo gestionari de paquets de Debian). Aquí avètz las linhas de sources.list que pòdon vos ajudar a identificar las linhas problematicas : \n{sourceslist}", - "update_apt_cache_warning": "I a agut d’errors en actualizar la memòria cache d’APT (lo gestionari de paquets de Debian). Aquí avètz las linhas de sources.list que pòdon vos ajudar a identificar las linhas problematicas : \n{sourceslist}" + "update_apt_cache_warning": "I a agut d’errors en actualizar la memòria cache d’APT (lo gestionari de paquets de Debian). Aquí avètz las linhas de sources.list que pòdon vos ajudar a identificar las linhas problematicas : \n{sourceslist}", + "apps_permission_not_found": "Cap d’autorizacion pas trobada per las aplicacions installadas", + "backup_permission": "Autorizacion de salvagarda per l’aplicacion {app:s}", + "edit_group_not_allowed": "Sètz pas autorizat a cambiar lo grop {group:s}", + "error_when_removing_sftpuser_group": "Error en ensajar de suprimir lo grop sftpusers", + "group_name_already_exist": "Lo grop {name:s} existís ja", + "group_created": "Lo grop « {group} » es estat corrèctament creat", + "group_creation_failed": "Fracàs de la creacion del grop « {group} »", + "group_deleted": "Lo grop « {group} » es estat suprimit", + "group_deletion_failed": "Fracàs de la supression del grop « {group} »", + "group_deletion_not_allowed": "Lo grop « {group} » pòt pas èsser suprimir manualament.", + "group_info_failed": "Recuperacion de las informacions del grop « {group} » impossibla", + "group_unknown": "Lo grop « {group} » es desconegut", + "log_user_group_add": "Ajustar lo grop « {} »", + "log_user_group_delete": "Suprimir lo grop « {} »", + "migration_0011_backup_before_migration": "Creacion d’una còpia de seguretat de la basa de donadas LDAP e de la configuracion de las aplicacions abans d’efectuar la migracion.", + "migration_0011_create_group": "Creacion d’un grop per cada utilizaire…", + "migration_0011_done": "Migracion complèta. Ara podètz gerir de grops d’utilizaires.", + "migration_0011_LDAP_update_failed": "Fracàs de la mesa a jorn de LDAP. Error : {error:s}", + "migration_0011_migration_failed_trying_to_rollback": "La migracion a fracassat… ensag de tornar lo sistèma a l’estat anterio.", + "migration_0011_rollback_success": "Restauracion del sistèma reüssida." } From 6563fdda8629bad18e4d2e48185abdc483fcd8db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9lanie=20Chauvel?= Date: Mon, 19 Aug 2019 17:03:02 +0000 Subject: [PATCH 0055/3170] Translated using Weblate (Esperanto) Currently translated at 7.0% (41 of 582 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/eo/ --- locales/eo.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/locales/eo.json b/locales/eo.json index b9d973557..1a6a2ea9a 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -32,5 +32,12 @@ "service_description_yunohost-api": "mastrumas interagojn inter la YunoHost retinterfaco kaj la sistemo", "service_description_yunohost-firewall": "mastrumas malfermitajn kaj fermitajn konektejojn al servoj", "service_disable_failed": "Neebla malaktivigi servon '{service:s}'\n\nFreŝaj protokoloj de la servo : {logs:s}", - "service_disabled": "Servo '{service:s}' estas malaktivigita" + "service_disabled": "Servo '{service:s}' estas malaktivigita", + "action_invalid": "Nevalida ago « {action:s} »", + "admin_password": "Pasvorto de la estro", + "admin_password_too_long": "Bonvolu elekti pasvorton pli mallonga ol 127 signoj", + "already_up_to_date": "Neniu estas farenda! Ĉiu jam estas ĝisdata!", + "app_argument_choice_invalid": "Nevalida elekto por argumento « {name:s} », ĝi devas esti unu el {choices:s}", + "app_argument_invalid": "Nevalida valoro por argumento « {name:s} » : {error:s}", + "app_change_url_failed_nginx_reload": "Reŝargi nginx malsuksesis. Jen la eligo de « nginx -t » :\n{nginx_errors:s}" } From 340d276219af764467fce346092c90909cf962d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9lanie=20Chauvel?= Date: Mon, 19 Aug 2019 16:57:03 +0000 Subject: [PATCH 0056/3170] Translated using Weblate (French) Currently translated at 94.0% (547 of 582 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 93ebef20f..d164ca5f5 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -281,7 +281,7 @@ "certmanager_http_check_timeout": "Expiration du délai lorsque le serveur a essayé de se contacter lui-même via HTTP en utilisant l'adresse IP public {ip:s} du domaine {domain:s}. Vous rencontrez peut-être un problème d’hairpinning ou alors le pare-feu/routeur en amont de votre serveur est mal configuré.", "certmanager_couldnt_fetch_intermediate_cert": "Expiration du délai lors de la tentative de récupération du certificat intermédiaire depuis Let’s Encrypt. L’installation ou le renouvellement du certificat a été annulé. Veuillez réessayer plus tard.", "appslist_retrieve_bad_format": "Le fichier récupéré pour la liste d’applications {appslist:s} n’est pas valide", - "domain_hostname_failed": "Échec de la création d’un nouveau nom d’hôte", + "domain_hostname_failed": "Échec de l’utilisation d’un nouveau nom d’hôte. Cela pourrait causer des soucis plus tard (mais ce n’est pas sûr… peut-être que ça n’en causera pas).", "yunohost_ca_creation_success": "L’autorité de certification locale a été créée.", "appslist_name_already_tracked": "Il y a déjà une liste d’applications enregistrée avec le nom {name:s}.", "appslist_url_already_tracked": "Il y a déjà une liste d’applications enregistrée avec l’URL {url:s}.", @@ -465,7 +465,7 @@ "migration_description_0004_php5_to_php7_pools": "Reconfiguration des groupes PHP pour utiliser PHP 7 au lieu de PHP 5", "migration_description_0005_postgresql_9p4_to_9p6": "Migration des bases de données de PostgreSQL 9.4 vers PostgreSQL 9.6", "migration_0005_postgresql_94_not_installed": "PostgreSQL n’a pas été installé sur votre système. Rien à faire !", - "migration_0005_postgresql_96_not_installed": "PostgreSQL 9.4 a été trouvé et installé, mais pas PostgreSQL 9.6 !? Quelque chose d’étrange a dû arriver à votre système :(", + "migration_0005_postgresql_96_not_installed": "PostgreSQL 9.4 a été trouvé et installé, mais pas PostgreSQL 9.6 !? Quelque chose d’étrange a dû arriver à votre système… :(", "migration_0005_not_enough_space": "Il n’y a pas assez d’espace libre de disponible sur {path} pour lancer maintenant la migration :(.", "recommend_to_add_first_user": "La post-installation est terminée mais YunoHost a besoin d’au moins un utilisateur pour fonctionner correctement. Vous devez en ajouter un en utilisant la commande 'yunohost user create $nomdutilisateur' ou bien via l’interface d’administration web.", "service_description_php7.0-fpm": "exécute des applications écrites en PHP avec Nginx", From 7af9ad4991155672a87b55f2626563f5c87e1cda Mon Sep 17 00:00:00 2001 From: ppr Date: Mon, 19 Aug 2019 17:58:34 +0000 Subject: [PATCH 0057/3170] Translated using Weblate (French) Currently translated at 94.0% (547 of 582 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/locales/fr.json b/locales/fr.json index d164ca5f5..2f22f1397 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -578,5 +578,15 @@ "group_already_disallowed": "Le groupe '{group:s}' a déjà la permission '{permission:s}' désactivée pour l'application '{app:s}'", "group_name_already_exist": "Le groupe {name:s} existe déjà", "group_creation_failed": "Échec de la création du groupe '{group}'", - "group_deletion_failed": "Échec de la suppression du groupe '{group}'" + "group_deletion_failed": "Échec de la suppression du groupe '{group}'", + "edit_permission_with_group_all_users_not_allowed": "Vous n'êtes pas autorisé à modifier les permissions pour le groupe 'all_users', utilisez'yunohost user permission clear APP' ou 'yunohost user permission add APP -u USER'.", + "log_permission_add": "Ajouter l'autorisation '{}' pour l'application '{}'", + "log_permission_remove": "Supprimer l'autorisation '{}'", + "log_permission_update": "Mise à jour de l'autorisation '{}' pour l'application '{}'", + "log_user_group_add": "Ajouter '{}' au groupe", + "log_user_group_delete": "Supprimer le groupe '{}'", + "log_user_group_update": "Mettre à jour '{}' pour le groupe", + "log_user_permission_add": "Mettre à jour l'autorisation pour '{}'", + "log_user_permission_remove": "Mettre à jour l'autorisation pour '{}'", + "mailbox_disabled": "La boîte aux lettres est désactivée pour l'utilisateur {user:s}" } From 0cb81512f32cc7e7b37f17fe95408a44cb175f05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quent=C3=AD?= Date: Wed, 28 Aug 2019 18:02:28 +0000 Subject: [PATCH 0058/3170] Translated using Weblate (Occitan) Currently translated at 99.1% (577 of 582 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/oc/ --- locales/oc.json | 65 +++++++++++++++++++++++++++++++++++++------------ 1 file changed, 49 insertions(+), 16 deletions(-) diff --git a/locales/oc.json b/locales/oc.json index a65154c35..d65a71d1e 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -7,16 +7,16 @@ "installation_complete": "Installacion acabada", "app_id_invalid": "Id d’aplicacion incorrècte", "app_install_files_invalid": "Fichièrs d’installacion incorrèctes", - "app_no_upgrade": "Pas cap d’aplicacion de metre a jorn", + "app_no_upgrade": "Pas cap d’aplicacion d’actualizar", "app_not_correctly_installed": "{app:s} sembla pas ben installat", "app_not_installed": "L’aplicacion {app:s} es pas installat. Vaquí la lista de las aplicacions installadas : {all_apps}", "app_not_properly_removed": "{app:s} es pas estat corrèctament suprimit", "app_removed": "{app:s} es estat suprimit", "app_unknown": "Aplicacion desconeguda", - "app_upgrade_app_name": "Mesa a jorn de l’aplicacion {app}…", - "app_upgrade_failed": "Impossible de metre a jorn {app:s}", - "app_upgrade_some_app_failed": "D’aplicacions se pòdon pas metre a jorn", - "app_upgraded": "{app:s} es estat mes a jorn", + "app_upgrade_app_name": "Actualizacion de l’aplicacion {app}…", + "app_upgrade_failed": "Impossible d’actualizar {app:s}", + "app_upgrade_some_app_failed": "D’aplicacions se pòdon pas actualizar", + "app_upgraded": "{app:s} es estada actualizada", "appslist_fetched": "Recuperacion de la lista d’aplicacions {appslist:s} corrèctament realizada", "appslist_migrating": "Migracion de la lista d’aplicacion{appslist:s}…", "appslist_name_already_tracked": "I a ja una lista d’aplicacion enregistrada amb lo nom {name:s}.", @@ -51,7 +51,7 @@ "app_incompatible": "L’aplicacion {app} es pas compatibla amb vòstra version de YunoHost", "app_location_already_used": "L’aplicacion « {app} » es ja installada a aqueste emplaçament ({path})", "app_manifest_invalid": "Manifest d’aplicacion incorrècte : {error}", - "app_package_need_update": "Lo paquet de l’aplicacion {app} deu èsser mes a jorn per seguir los cambiaments de YunoHost", + "app_package_need_update": "Lo paquet de l’aplicacion {app} deu èsser actualizat per poder seguir los cambiaments de YunoHost", "app_requirements_checking": "Verificacion dels paquets requesits per {app}…", "app_sources_fetch_failed": "Recuperacion dels fichièrs fonts impossibla, l’URL es corrècta ?", "app_unsupported_remote_type": "Lo tipe alonhat utilizat per l’aplicacion es pas suportat", @@ -67,8 +67,8 @@ "backup_creating_archive": "Creacion de l’archiu de salvagarda…", "backup_creation_failed": "Impossible de crear la salvagarda", "app_already_installed_cant_change_url": "Aquesta aplicacion es ja installada. Aquesta foncion pòt pas simplament cambiar l’URL. Agachatz « app changeurl » s’es disponible.", - "app_change_no_change_url_script": "L’aplicacion {app_name:s} pren pas en compte lo cambiament d’URL, poiretz aver de la metre a jorn.", - "app_change_url_no_script": "L’aplicacion {app_name:s} pren pas en compte lo cambiament d’URL, benlèu que vos cal la metre a jorn.", + "app_change_no_change_url_script": "L’aplicacion {app_name:s} pren pas en compte lo cambiament d’URL, poiretz aver de l’actualizar.", + "app_change_url_no_script": "L’aplicacion {app_name:s} pren pas en compte lo cambiament d’URL, benlèu que vos cal l’actualizar.", "app_make_default_location_already_used": "Impossible de configurar l’aplicacion « {app} » per defaut pel domeni {domain} perque es ja utilizat per l’aplicacion {other_app}", "app_location_install_failed": "Impossible d’installar l’aplicacion a aqueste emplaçament per causa de conflicte amb l’aplicacion {other_app} qu’es ja installada sus {other_path}", "app_location_unavailable": "Aquesta URL es pas disponibla o en conflicte amb una aplicacion existenta :\n{apps:s}", @@ -264,7 +264,7 @@ "migrate_tsig_failed": "La migracion del domeni dyndns {domain} cap a hmac-sha512 a pas capitat, anullacion de las modificacions. Error : {error_code} - {error}", "migrate_tsig_wait": "Esperem 3 minutas que lo servidor dyndns prenga en compte la novèla clau…", "migrate_tsig_not_needed": "Sembla qu’utilizatz pas un domeni dyndns, donc cap de migracion es pas necessària !", - "migration_0003_yunohost_upgrade": "Aviada de la mesa a nivèl del paquet YunoHost… La migracion acabarà, mas la mesa a jorn reala se realizarà tot bèl aprèp. Un còp acabada, poiretz vos reconnectar a l’administracion web.", + "migration_0003_yunohost_upgrade": "Aviada de la mesa a nivèl del paquet YunoHost… La migracion acabarà, mas l’actualizacion reala se realizarà tot bèl aprèp. Un còp acabada, poiretz vos reconnectar a l’administracion web.", "migration_0003_system_not_fully_up_to_date": "Lo sistèma es pas complètament a jorn. Mercés de lançar una mesa a jorn classica abans de començar la migracion per Stretch.", "migration_0003_modified_files": "Mercés de notar que los fichièrs seguents son estats detectats coma modificats manualament e poiràn èsser escafats a la fin de la mesa a nivèl : {manually_modified_files}", "monitor_period_invalid": "Lo periòde de temps es incorrècte", @@ -421,7 +421,7 @@ "log_app_change_url": "Cambiar l’URL de l’aplicacion « {} »", "log_app_install": "Installar l’aplicacion « {} »", "log_app_remove": "Levar l’aplicacion « {} »", - "log_app_upgrade": "Metre a jorn l’aplicacion « {} »", + "log_app_upgrade": "Actualizacion de l’aplicacion « {} »", "log_app_makedefault": "Far venir « {} » l’aplicacion per defaut", "log_available_on_yunopaste": "Lo jornal es ara disponible via {url}", "log_backup_restore_system": "Restaurar lo sistèma a partir d’una salvagarda", @@ -431,7 +431,7 @@ "log_domain_add": "Ajustar lo domeni « {} » dins la configuracion sistèma", "log_domain_remove": "Tirar lo domeni « {} » d’a la configuracion sistèma", "log_dyndns_subscribe": "S’abonar al subdomeni YunoHost « {} »", - "log_dyndns_update": "Metre a jorn l’adreça IP ligada a vòstre jos-domeni YunoHost « {} »", + "log_dyndns_update": "Actualizar l’adreça IP ligada a vòstre jos-domeni YunoHost « {} »", "log_letsencrypt_cert_install": "Installar lo certificat Let's encrypt sul domeni « {} »", "log_selfsigned_cert_install": "Installar lo certificat auto-signat sul domeni « {} »", "log_letsencrypt_cert_renew": "Renovar lo certificat Let's encrypt de « {} »", @@ -439,12 +439,12 @@ "log_service_regen_conf": "Regenerar la configuracion sistèma de « {} »", "log_user_create": "Ajustar l’utilizaire « {} »", "log_user_delete": "Levar l’utilizaire « {} »", - "log_user_update": "Metre a jorn las informacions a l’utilizaire « {} »", + "log_user_update": "Actualizar las informacions a l’utilizaire « {} »", "log_tools_maindomain": "Far venir « {} » lo domeni màger", "log_tools_migrations_migrate_forward": "Migrar", "log_tools_migrations_migrate_backward": "Tornar en arrièr", "log_tools_postinstall": "Realizar la post installacion del servidor YunoHost", - "log_tools_upgrade": "Mesa a jorn dels paquets sistèma", + "log_tools_upgrade": "Actualizacion dels paquets sistèma", "log_tools_shutdown": "Atudar lo servidor", "log_tools_reboot": "Reaviar lo servidor", "mail_unavailable": "Aquesta adreça electronica es reservada e deu èsser automaticament atribuida al tot bèl just primièr utilizaire", @@ -473,7 +473,7 @@ "app_start_remove": "Supression de l’aplicacion {app}…", "app_start_backup": "Recuperacion dels fichièrs de salvagardar per {app}…", "app_start_restore": "Restauracion de l’aplicacion {app}…", - "app_upgrade_several_apps": "Las aplicacions seguentas seràn mesas a jorn : {apps}", + "app_upgrade_several_apps": "Las aplicacions seguentas seràn actualizadas : {apps}", "ask_new_domain": "Nòu domeni", "ask_new_path": "Nòu camin", "backup_actually_backuping": "Creacion d’un archiu de seguretat a partir dels fichièrs recuperats…", @@ -565,7 +565,40 @@ "migration_0011_backup_before_migration": "Creacion d’una còpia de seguretat de la basa de donadas LDAP e de la configuracion de las aplicacions abans d’efectuar la migracion.", "migration_0011_create_group": "Creacion d’un grop per cada utilizaire…", "migration_0011_done": "Migracion complèta. Ara podètz gerir de grops d’utilizaires.", - "migration_0011_LDAP_update_failed": "Fracàs de la mesa a jorn de LDAP. Error : {error:s}", + "migration_0011_LDAP_update_failed": "Actualizacion impossibla de LDAP. Error : {error:s}", "migration_0011_migration_failed_trying_to_rollback": "La migracion a fracassat… ensag de tornar lo sistèma a l’estat anterio.", - "migration_0011_rollback_success": "Restauracion del sistèma reüssida." + "migration_0011_rollback_success": "Restauracion del sistèma reüssida.", + "apps_permission_restoration_failed": "Fracàs de la permission « {permission:s} » per la restauracion de l’aplicacion {app:s}", + "group_already_allowed": "Lo grop « {group:s} » a ja la permission « {permission:s} » activada per l’aplicacion « {app:s} »", + "group_already_disallowed": "Lo grop « {group:s} »a ja las permissions « {permission:s} » desactivadas per l’aplicacion « {app:s} »", + "group_updated": "Lo grop « {group} » es estat actualizat", + "group_update_failed": "Actualizacion impossibla del grop « {group} »", + "log_permission_add": "Ajustar la permission « {} » per l’aplicacion « {} »", + "log_permission_remove": "Suprimir la permission « {} »", + "log_permission_update": "Actualizacion de la permission « {} » per l’aplicacion « {} »", + "log_user_group_update": "Actualizar lo grop « {} »", + "log_user_permission_add": "Actualizar la permission « {} »", + "log_user_permission_remove": "Actualizar la permission « {} »", + "migration_description_0011_setup_group_permission": "Configurar lo grop d’utilizaire e las permission de las aplicacions e dels servicis", + "migration_0011_can_not_backup_before_migration": "La salvagarda del sistèma abans la migracion a pas capitat. La migracion a fracassat. Error : {error:s}", + "migration_0011_migrate_permission": "Migracion de las permission dels paramètres d’aplicacion a LDAP…", + "migration_0011_update_LDAP_database": "Actualizacion de la basa de donadas LDAP…", + "migration_0011_update_LDAP_schema": "Actualizacion de l’esquèma LDAP…", + "permission_already_exist": "La permission « {permission:s} » per l’aplicacion {app:s} existís ja", + "permission_created": "Permission creada « {permission:s} » per l’aplicacion{app:s}", + "permission_creation_failed": "Creacion impossibla de la permission", + "permission_deleted": "Permission « {permission:s} » per l’aplicacion {app:s} suprimida", + "permission_deletion_failed": "Fracàs de la supression de la permission « {permission:s} » per l’aplicacion {app:s}", + "permission_not_found": "Permission « {permission:s} » pas trobada per l’aplicacion {app:s}", + "permission_name_not_valid": "Lo nom de la permission « {permission:s} » es pas valid", + "permission_update_failed": "Fracàs de l’actualizacion de la permission", + "permission_generated": "La basa de donadas de las permission es estada actualizada", + "permission_updated": "La permission « {permission:s} » per l’aplicacion {app:s} es estada actualizada", + "permission_update_nothing_to_do": "Cap de permission d’actualizar", + "remove_main_permission_not_allowed": "Se pòt pas suprimir la permission màger", + "remove_user_of_group_not_allowed": "Sètz pas autorizat a suprimir {user:s} del grop {group:s}", + "system_groupname_exists": "Lo nom del grop existís ja dins lo sistèma de grops", + "tools_update_failed_to_app_fetchlist": "Fracàs de l’actualizacion de la lista d’aplicacions de YunoHost a causa de : {error}", + "user_already_in_group": "L’utilizaire {user:} es ja dins lo grop {group:s}", + "user_not_in_group": "L’utilizaire {user:} es pas dins lo grop {group:s}" } From 42ccb0973be72f847de4195d6ea7249c62c90f51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quent=C3=AD?= Date: Fri, 30 Aug 2019 19:08:56 +0000 Subject: [PATCH 0059/3170] Translated using Weblate (Occitan) Currently translated at 99.8% (581 of 582 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/oc/ --- locales/oc.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/locales/oc.json b/locales/oc.json index d65a71d1e..8981fbadd 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -600,5 +600,9 @@ "system_groupname_exists": "Lo nom del grop existís ja dins lo sistèma de grops", "tools_update_failed_to_app_fetchlist": "Fracàs de l’actualizacion de la lista d’aplicacions de YunoHost a causa de : {error}", "user_already_in_group": "L’utilizaire {user:} es ja dins lo grop {group:s}", - "user_not_in_group": "L’utilizaire {user:} es pas dins lo grop {group:s}" + "user_not_in_group": "L’utilizaire {user:} es pas dins lo grop {group:s}", + "edit_permission_with_group_all_users_not_allowed": "Podètz pas modificar las permissions del grop « all_users », utilizatz « yunohost user permission clear APP » o « yunohost user permission add APP -u USER ».", + "mailbox_disabled": "La bóstia de las letras es desactivada per l’utilizaire {user:s}", + "migration_0011_LDAP_config_dirty": "Sembla qu’avètz modificat manualament la configuracion LDAP. Per far aquesta migracion cal actualizar la configuracion LDAP.\nSalvagardatz la configuracion actuala, reïnicializatz la configuracion originala amb la comanda « yunohost tools regen-conf -f » e tornatz ensajar la migracion", + "need_define_permission_before": "Vos cal tornar definir las permission en utilizant « yunohost user permission add -u USER » abans de suprimir un grop permés" } From 70cf0db0ad9073c0f7ca27b5dab52c26414ce59b Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Wed, 4 Sep 2019 13:35:06 +0200 Subject: [PATCH 0060/3170] [enh] Rename permission http api route in plural --- data/actionsmap/yunohost.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 7c864cce0..45f003b78 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -274,7 +274,7 @@ user: ### user_permission_list() list: action_help: List access to user and group - api: GET /users/permission/ + api: GET /users/permissions/ arguments: -a: full: --app @@ -300,7 +300,7 @@ user: ### user_permission_add() add: action_help: Grant access right to users and group - api: POST /users/permission/ + api: POST /users/permissions/ arguments: app: help: Application to manage the permission @@ -328,7 +328,7 @@ user: ### user_permission_remove() remove: action_help: Revoke access right to users and group - api: PUT /users/permission/ + api: PUT /users/permissions/ arguments: app: help: Application to manage the permission @@ -356,7 +356,7 @@ user: ## user_permission_clear() clear: action_help: Reset access rights for the app - api: DELETE /users/permission/ + api: DELETE /users/permissions/ arguments: app: help: Application to manage the permission From 8a92727fb1b625faa0d5e150291c6aa3bae57f70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Sat, 7 Sep 2019 13:01:45 +0200 Subject: [PATCH 0061/3170] Check that exist before the calculate the checksum --- data/helpers.d/backup | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/backup b/data/helpers.d/backup index dcf306085..d3ffffcd3 100644 --- a/data/helpers.d/backup +++ b/data/helpers.d/backup @@ -339,7 +339,7 @@ ynh_backup_if_checksum_is_different () { backup_file_checksum="" if [ -n "$checksum_value" ] then # Proceed only if a value was stored into the app settings - if ! echo "$checksum_value $file" | sudo md5sum -c --status + if [ -e $file ] && ! echo "$checksum_value $file" | sudo md5sum -c --status then # If the checksum is now different backup_file_checksum="/home/yunohost.conf/backup/$file.backup.$(date '+%Y%m%d.%H%M%S')" sudo mkdir -p "$(dirname "$backup_file_checksum")" From 92fa21641bfa8c3c323fe87a4dbb27086a9c8b38 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 9 Sep 2019 12:07:44 +0200 Subject: [PATCH 0062/3170] Fix some stuff about removing the --revert option --- locales/en.json | 8 +++----- .../0007_ssh_conf_managed_by_yunohost_step1.py | 9 +++++++++ src/yunohost/tools.py | 6 +++--- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/locales/en.json b/locales/en.json index 850d89032..a4596941f 100644 --- a/locales/en.json +++ b/locales/en.json @@ -320,7 +320,6 @@ "migrate_tsig_wait_3": "1min…", "migrate_tsig_wait_4": "30 secondes…", "migrate_tsig_not_needed": "You do not appear to use a dyndns domain, so no migration is needed!", - "migration_backward_impossible": "The migration {name} cannot be reverted.", "migration_description_0001_change_cert_group_to_sslcert": "Change certificates group permissions from 'metronome' to 'ssl-cert'", "migration_description_0002_migrate_to_tsig_sha256": "Improve security of dyndns TSIG by using SHA512 instead of MD5", "migration_description_0003_migrate_to_stretch": "Upgrade the system to Debian Stretch and YunoHost 3.0", @@ -372,20 +371,19 @@ "migrations_cant_reach_migration_file": "Can't access migrations files at path %s", "migrations_dependencies_not_satisfied": "Can't run migration {id} because first you need to run these migrations: {dependencies_id}", "migrations_failed_to_load_migration": "Failed to load migration {id} : {error}", - "migrations_exclusive_options": "--auto, --skip, --revert and --force-rerun are exclusive options.", + "migrations_exclusive_options": "--auto, --skip, and --force-rerun are exclusive options.", "migrations_list_conflict_pending_done": "You cannot use both --previous and --done at the same time.", "migrations_loading_migration": "Loading migration {id}…", "migrations_migration_has_failed": "Migration {id} has failed, aborting. Error: {exception}", - "migrations_must_provide_explicit_targets": "You must provide explicit targets when using --skip, --revert or --force-rerun", + "migrations_must_provide_explicit_targets": "You must provide explicit targets when using --skip or --force-rerun", "migrations_need_to_accept_disclaimer": "To run the migration {id}, your must accept the following disclaimer:\n---\n{disclaimer}\n---\nIf you accept to run the migration, please re-run the command with the option --accept-disclaimer.", "migrations_no_migrations_to_run": "No migrations to run", "migrations_no_such_migration": "No such migration called {id}", "migrations_not_pending_cant_skip": "Those migrations are not pending so cannot be skipped: {ids}", - "migrations_pending_cant_revert_or_rerun": "Those migrations are still pending so cannot be reverted or reran: {ids}", + "migrations_pending_cant_rerun": "Those migrations are still pending so cannot be reran: {ids}", "migrations_running_forward": "Running migration {id}…", "migrations_skip_migration": "Skipping migration {id}…", "migrations_success_forward": "Successfully ran migration {id}!", - "migrations_success_revert": "Successfully reverted migration {id}!", "migrations_to_be_ran_manually": "Migration {id} has to be ran manually. Please go to Tools > Migrations on the webadmin, or run `yunohost tools migrations migrate`.", "monitor_disabled": "The server monitoring has been disabled", "monitor_enabled": "The server monitoring has been enabled", diff --git a/src/yunohost/data_migrations/0007_ssh_conf_managed_by_yunohost_step1.py b/src/yunohost/data_migrations/0007_ssh_conf_managed_by_yunohost_step1.py index 818760e17..624288210 100644 --- a/src/yunohost/data_migrations/0007_ssh_conf_managed_by_yunohost_step1.py +++ b/src/yunohost/data_migrations/0007_ssh_conf_managed_by_yunohost_step1.py @@ -59,3 +59,12 @@ class MyMigration(Migration): copyfile(SSHD_CONF, '/etc/ssh/sshd_config.bkp') regen_conf(names=['ssh'], force=True) copyfile('/etc/ssh/sshd_config.bkp', SSHD_CONF) + + # Restart ssh and rollback if it failed + if not _run_service_command('restart', 'ssh'): + # We don't rollback completely but it should be enough + copyfile('/etc/ssh/sshd_config.bkp', SSHD_CONF) + if not _run_service_command('restart', 'ssh'): + raise YunohostError("migration_0007_cannot_restart") + else: + raise YunohostError("migration_0007_cancelled") diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index d2015adff..8e2635e2c 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -1081,7 +1081,7 @@ def tools_migrations_migrate(targets=[], skip=False, auto=False, force_rerun=Fal if skip and done: raise YunohostError("migrations_not_pending_cant_skip", ids=', '.join(done)) if force_rerun and pending: - raise YunohostError("migrations_pending_cant_revert_or_rerun", ids=', '.join(pending)) + raise YunohostError("migrations_pending_cant_rerun", ids=', '.join(pending)) if not (skip or force_rerun) and done: raise YunohostError("migrations_already_ran", ids=', '.join(done)) @@ -1304,11 +1304,11 @@ class Migration(object): def disclaimer(self): return None - # The followings shouldn't be overriden - def run(self): raise NotImplementedError() + # The followings shouldn't be overriden + def __init__(self, id_): self.id = id_ self.number = int(self.id.split("_", 1)[0]) From b29731c28b2688547dbbd518cd3897cdede94102 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 10 Sep 2019 14:25:51 +0200 Subject: [PATCH 0063/3170] We don't need this try/except --- src/yunohost/tools.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 8e2635e2c..7b2ee8526 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -1180,11 +1180,7 @@ def _migrate_legacy_migration_json(): last_run_migration_id = str(old_state["number"]) + "_" + old_state["name"] # Extract the list of migration ids - try: - from . import data_migrations - except ImportError: - # Well ugh what are we supposed to do if we can't do this >.>... - pass + from . import data_migrations migrations_path = data_migrations.__path__[0] migration_files = filter(lambda x: re.match("^\d+_[a-zA-Z0-9_]+\.py$", x), os.listdir(migrations_path)) # (here we remove the .py extension and make sure the ids are sorted) From ec3856a25c532b8b552a428c869bc8a47000b1de Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 10 Sep 2019 17:15:01 +0200 Subject: [PATCH 0064/3170] Propagate changes about revert mechanism and naming on migration 12 --- .../0012_postgresql_password_to_md5_authentication.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/yunohost/data_migrations/0012_postgresql_password_to_md5_authentication.py b/src/yunohost/data_migrations/0012_postgresql_password_to_md5_authentication.py index 5d36b3e23..636b4f12b 100644 --- a/src/yunohost/data_migrations/0012_postgresql_password_to_md5_authentication.py +++ b/src/yunohost/data_migrations/0012_postgresql_password_to_md5_authentication.py @@ -10,12 +10,7 @@ class MyMigration(Migration): all_hba_files = glob.glob("/etc/postgresql/*/*/pg_hba.conf") - def forward(self): + def run(self): for filename in self.all_hba_files: pg_hba_in = read_file(filename) write_to_file(filename, re.sub(r"local(\s*)all(\s*)all(\s*)password", "local\\1all\\2all\\3md5", pg_hba_in)) - - def backward(self): - for filename in self.all_hba_files: - pg_hba_in = read_file(filename) - write_to_file(filename, re.sub(r"local(\s*)all(\s*)all(\s*)md5", "local\\1all\\2all\\3password", pg_hba_in)) From 84fe23786c21e343a0925b1846bea1a460a7482d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Tue, 3 Sep 2019 14:47:08 +0200 Subject: [PATCH 0065/3170] Fix permission sychronization --- src/yunohost/permission.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 3beb3ac2b..897ffadd8 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -484,8 +484,9 @@ def permission_sync_to_user(force=False): for per in ldap.search('ou=permission,dc=yunohost,dc=org', '(objectclass=permissionYnh)', ['cn', 'inheritPermission', 'groupPermission', 'memberUid']): + print(per) if 'groupPermission' not in per: - continue + per['groupPermission'] = [] user_permission = set() for group in per['groupPermission']: group = group.split("=")[1].split(",")[0] From a4ec76e77bb25abfc12be17889c949a4beacb627 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 10 Sep 2019 14:36:28 +0200 Subject: [PATCH 0066/3170] Remove debug print --- src/yunohost/permission.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 897ffadd8..f7fa307da 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -484,7 +484,7 @@ def permission_sync_to_user(force=False): for per in ldap.search('ou=permission,dc=yunohost,dc=org', '(objectclass=permissionYnh)', ['cn', 'inheritPermission', 'groupPermission', 'memberUid']): - print(per) + if 'groupPermission' not in per: per['groupPermission'] = [] user_permission = set() From fd99ef0d6c3ce870211debc36da44e9dd4b1e41f Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Wed, 11 Sep 2019 03:56:53 +0200 Subject: [PATCH 0067/3170] [ux] make dns-conf help sentence obvious that it's a sample conf --- data/actionsmap/yunohost.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 45f003b78..def754220 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -468,7 +468,7 @@ domain: ### domain_dns_conf() dns-conf: - action_help: Generate DNS configuration for a domain + action_help: Generate sample DNS configuration for a domain api: GET /domains//dns arguments: domain: From 92a6315514b42e125024303ea1cc91cd77e2c461 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Wed, 11 Sep 2019 05:20:08 +0200 Subject: [PATCH 0068/3170] [ux] apparently is super nerdy and you should use --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index fd62a6b91..962f52562 100644 --- a/locales/en.json +++ b/locales/en.json @@ -447,7 +447,7 @@ "port_already_opened": "Port {port:d} is already opened for {ip_version:s} connections", "port_available": "Port {port:d} is available", "port_unavailable": "Port {port:d} is not available", - "recommend_to_add_first_user": "The post-install is finished but YunoHost needs at least one user to work correctly, you should add one using 'yunohost user create $username' or the admin interface.", + "recommend_to_add_first_user": "The post-install is finished but YunoHost needs at least one user to work correctly, you should add one using 'yunohost user create ' or the admin interface.", "remove_main_permission_not_allowed": "Removing the main permission is not allowed", "remove_user_of_group_not_allowed": "You are not allowed to remove the user {user:s} in the group {group:s}", "regenconf_file_backed_up": "The configuration file '{conf}' has been backed up to '{backup}'", From 6599ae1ad00e4b499e03340e0badba4ba635c2b8 Mon Sep 17 00:00:00 2001 From: ButterflyOfFire Date: Tue, 10 Sep 2019 11:24:28 +0000 Subject: [PATCH 0069/3170] Translated using Weblate (Arabic) Currently translated at 65.5% (382 of 583 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ar/ --- locales/ar.json | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/locales/ar.json b/locales/ar.json index 285a0f819..c8a1bba7c 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -301,7 +301,7 @@ "service_add_failed": "تعذرت إضافة خدمة '{service:s}'", "service_added": "The service '{service:s}' has been added", "service_already_started": "Service '{service:s}' has already been started", - "service_already_stopped": "Service '{service:s}' has already been stopped", + "service_already_stopped": "إنّ خدمة '{service:s}' متوقفة مِن قبلُ", "service_cmd_exec_failed": "Unable to execute command '{command:s}'", "service_conf_file_backed_up": "The configuration file '{conf}' has been backed up to '{backup}'", "service_conf_file_copy_failed": "Unable to copy the new configuration file '{new}' to '{conf}'", @@ -329,7 +329,7 @@ "service_started": "تم إطلاق تشغيل خدمة '{service:s}'", "service_status_failed": "Unable to determine status of service '{service:s}'", "service_stop_failed": "", - "service_stopped": "The service '{service:s}' has been stopped", + "service_stopped": "تمّ إيقاف خدمة '{service:s}'", "service_unknown": "Unknown service '{service:s}'", "ssowat_conf_generated": "The SSOwat configuration has been generated", "ssowat_conf_updated": "The SSOwat configuration has been updated", @@ -343,7 +343,7 @@ "unlimit": "دون تحديد الحصة", "unrestore_app": "App '{app:s}' will not be restored", "update_cache_failed": "Unable to update APT cache", - "updating_apt_cache": "جارٍ تحديث قائمة الحُزم المتوفرة …", + "updating_apt_cache": "جارٍ جلب قائمة حُزم النظام المحدّثة المتوفرة…", "upgrade_complete": "إكتملت عملية الترقية و التحديث", "upgrading_packages": "عملية ترقية الحُزم جارية …", "upnp_dev_not_found": "No UPnP device found", @@ -412,7 +412,7 @@ "service_description_dnsmasq": "مُكلَّف بتحليل أسماء النطاقات (DNS)", "service_description_mysql": "يقوم بتخزين بيانات التطبيقات (قواعد بيانات SQL)", "service_description_rspamd": "يقوم بتصفية البريد المزعج و إدارة ميزات أخرى للبريد", - "service_description_yunohost-firewall": "يريد فتح و غلق منافذ الإتصال إلى الخدمات", + "service_description_yunohost-firewall": "يُدير فتح وإغلاق منافذ الإتصال إلى الخدمات", "users_available": "المستخدمون المتوفرون:", "aborting": "إلغاء.", "admin_password_too_long": "يرجى اختيار كلمة سرية أقصر مِن 127 حرف", @@ -426,5 +426,8 @@ "global_settings_setting_security_password_admin_strength": "قوة الكلمة السرية الإدارية", "global_settings_setting_security_password_user_strength": "قوة الكلمة السرية للمستخدم", "log_app_addaccess": "إضافة ترخيص بالنفاذ إلى '{}'", - "password_too_simple_1": "يجب أن يكون طول الكلمة السرية على الأقل 8 حروف" + "password_too_simple_1": "يجب أن يكون طول الكلمة السرية على الأقل 8 حروف", + "service_description_php7.0-fpm": "يُشغّل التطبيقات المكتوبة بلغة الـ PHP على Nginx", + "updating_app_lists": "جارٍ جلب التحديثات المتوفرة الخاصة بالتطبيقات…", + "already_up_to_date": "كل شيء على ما يرام! ليس هناك ما يتطلّب تحديثًا!" } From 4204f418900dcc09deae4cc721f4be142ca04310 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carles=20Sadurn=C3=AD=20Anguita?= Date: Sun, 8 Sep 2019 09:07:26 +0000 Subject: [PATCH 0070/3170] Translated using Weblate (Catalan) Currently translated at 100.0% (583 of 583 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ca/ --- locales/ca.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/locales/ca.json b/locales/ca.json index a9a0c2e0f..f5c040670 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -198,7 +198,7 @@ "executing_script": "Execució de l'script « {script:s} »…", "extracting": "Extracció en curs…", "dyndns_cron_installed": "S'ha instal·lat la tasca cron pel DynDNS", - "dyndns_cron_remove_failed": "No s'ha pogut eliminar la tasca cron pel DynDNS", + "dyndns_cron_remove_failed": "No s'ha pogut eliminar la tasca cron per a DynDNS: {error}", "dyndns_cron_removed": "S'ha eliminat la tasca cron pel DynDNS", "experimental_feature": "Atenció: aquesta funcionalitat és experimental i no es considera estable, no s'ha d'utilitzar a excepció de saber el que esteu fent.", "field_invalid": "Camp incorrecte « {:s} »", @@ -581,5 +581,6 @@ "system_groupname_exists": "El nom de grup ja existeix en el sistema de grups", "tools_update_failed_to_app_fetchlist": "No s'ha pogut actualitzar la llista d'aplicacions de YunoHost a causa de: {error}", "user_already_in_group": "L'usuari {user:s} ja és en el grup {group:s}", - "user_not_in_group": "L'usuari {user:s} no és en el grup {group:s}" + "user_not_in_group": "L'usuari {user:s} no és en el grup {group:s}", + "migration_description_0012_postgresql_password_to_md5_authentication": "Força l'autenticació postgresql a fer servir md5 per a les connexions locals" } From 8de950a4369baa812e0954dac73e937ab293b663 Mon Sep 17 00:00:00 2001 From: Aksel Kiesling Date: Tue, 3 Sep 2019 06:29:42 +0000 Subject: [PATCH 0071/3170] Translated using Weblate (German) Currently translated at 55.6% (324 of 583 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 55 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/locales/de.json b/locales/de.json index a4a6c236b..cef591411 100644 --- a/locales/de.json +++ b/locales/de.json @@ -301,5 +301,58 @@ "backup_archive_writing_error": "Die Dateien konnten nicht in der komprimierte Archiv-Backup hinzugefügt werden", "app_change_url_success": "Erfolgreiche Änderung der URL von {app:s} zu {domain:s}{path:s}", "backup_applying_method_borg": "Sende alle Dateien zur Sicherung ins borg-backup repository...", - "invalid_url_format": "ungültiges URL Format" + "invalid_url_format": "ungültiges URL Format", + "global_settings_bad_type_for_setting": "Falscher Typ für Einstellung {setting: s}. Empfangen: {receive_type: s}, aber erwartet: {expected_type: s}", + "global_settings_bad_choice_for_enum": "Falsche Wahl für die Einstellung {setting: s}. Habe '{choice: s}' erhalten, aber es stehen nur folgende Auswahlmöglichkeiten zur Verfügung: {available_choices: s}", + "file_does_not_exist": "Die Datei {path: s} existiert nicht.", + "experimental_feature": "Warnung: Diese Funktion ist experimentell und gilt nicht als stabil. Sie sollten sie nur verwenden, wenn Sie wissen, was Sie tun.", + "error_when_removing_sftpuser_group": "Fehler beim Versuch, die Gruppe sftpusers zu entfernen", + "edit_permission_with_group_all_users_not_allowed": "Sie dürfen die Berechtigung für die Gruppe \"all_users\" nicht bearbeiten. Verwenden Sie stattdessen \"yunohost user permission clear APP\" oder \"yunohost user permission add APP -u USER\".", + "edit_group_not_allowed": "Du bist nicht berechtigt zum Bearbeiten der Gruppe {group: s}", + "dyndns_domain_not_provided": "Der Dyndns-Anbieter {provider: s} kann die Domain(s) {domain: s} nicht bereitstellen.", + "dyndns_could_not_check_available": "Konnte nicht überprüfen, ob {domain: s} auf {provider: s} verfügbar ist.", + "dyndns_could_not_check_provide": "Konnte nicht überprüft, ob {provider: s} die Domain(s) {domain: s} bereitstellen kann.", + "domain_dyndns_dynette_is_unreachable": "YunoHost dynette kann nicht erreicht werden, entweder ist Ihr YunoHost nicht korrekt mit dem Internet verbunden oder der dynette-Server ist inaktiv. Fehler: {error}", + "domain_dns_conf_is_just_a_recommendation": "Dieser Befehl zeigt Ihnen, was die * empfohlene * Konfiguration ist. Die DNS-Konfiguration wird NICHT für Sie eingerichtet. Es liegt in Ihrer Verantwortung, Ihre DNS-Zone in Ihrem Registrar gemäß dieser Empfehlung zu konfigurieren.", + "dpkg_lock_not_available": "Dieser Befehl kann momentan nicht ausgeführt werden, da anscheinend ein anderes Programm die Sperre von dpkg (dem Systempaket-Manager) verwendet", + "confirm_app_install_thirdparty": "WARNUNG! Das Installieren von Anwendungen von Drittanbietern kann die Integrität und Sicherheit Deines Systems beeinträchtigen. Du solltest es wahrscheinlich NICHT installieren, es sei denn, Du weisst, was Du tust. Bist du bereit, dieses Risiko einzugehen? [{answers: s}] ", + "confirm_app_install_danger": "WARNUNG! Diese Anwendung ist noch experimentell (wenn nicht ausdrücklich \"not working\"/\"funktioniert nicht\") und es ist wahrscheinlich, dass Dein System Schaden nimmt! Du solltest es wahrscheinlich NICHT installieren, es sei denn, Du weisst, was Du tust. Bist du bereit, dieses Risiko einzugehen? [{answers: s}] ", + "confirm_app_install_warning": "Warnung: Diese Anwendung funktioniert möglicherweise, ist jedoch nicht gut in YunoHost integriert. Einige Funktionen wie Single Sign-On und Backup / Restore sind möglicherweise nicht verfügbar. Trotzdem installieren? [{answers: s}] ", + "backup_with_no_restore_script_for_app": "App {app: s} hat kein Wiederherstellungsskript. Das Backup dieser App kann nicht automatisch wiederhergestellt werden.", + "backup_with_no_backup_script_for_app": "App {app: s} hat kein Sicherungsskript. Ignoriere es.", + "backup_unable_to_organize_files": "Dateien im Archiv können mit der schnellen Methode nicht organisiert werden", + "backup_system_part_failed": "Der Systemteil '{part: s}' kann nicht gesichert werden", + "backup_permission": "Sicherungsberechtigung für App {app: s}", + "backup_output_symlink_dir_broken": "Sie haben einen fehlerhaften Symlink anstelle Ihres Archivverzeichnisses '{path: s}'. Möglicherweise haben Sie ein spezielles Setup, um Ihre Daten auf einem anderen Dateisystem zu sichern. In diesem Fall haben Sie wahrscheinlich vergessen, Ihre Festplatte oder Ihren USB-Schlüssel erneut einzuhängen oder anzuschließen.", + "backup_mount_archive_for_restore": "Archiv für Wiederherstellung vorbereiten…", + "backup_method_tar_finished": "Sicherungs-Tar-Archiv erstellt", + "backup_method_custom_finished": "Benutzerdefinierte Sicherungsmethode '{method: s}' beendet", + "backup_method_copy_finished": "Sicherungskopie beendet", + "backup_method_borg_finished": "Backup in Borg beendet", + "backup_custom_need_mount_error": "Bei der benutzerdefinierten Sicherungsmethode ist beim Arbeitsschritt \"Braucht ein Einhängen/Verbinden\" (need_mount) ein Fehler aufgetreten", + "backup_custom_mount_error": "Bei der benutzerdefinierten Sicherungsmethode ist beim Arbeitsschritt \"Einhängen/Verbinden\" ein Fehler aufgetreten", + "backup_custom_backup_error": "Bei der benutzerdefinierten Sicherungsmethode ist beim Arbeitsschritt \"Sicherung\" ein Fehler aufgetreten", + "backup_csv_creation_failed": "Die CSV-Datei, die für zukünftige Wiederherstellungsvorgänge erforderlich ist, kann nicht erstellt werden", + "backup_couldnt_bind": "{Src: s} konnte nicht an {dest: s} angebunden werden.", + "backup_borg_not_implemented": "Die Borg-Sicherungsmethode ist noch nicht implementiert", + "backup_ask_for_copying_if_needed": "Einige Dateien konnten mit der Methode, die es vermeidet vorübergehend Speicherplatz auf dem System zu verschwenden, nicht gesichert werden. Zur Durchführung der Sicherung sollten vorübergehend {size: s} MB verwendet werden. Sind Sie einverstanden?", + "backup_actually_backuping": "Erstelle nun ein Backup-Archiv aus den gesammelten Dateien …", + "ask_path": "Pfad", + "ask_new_path": "Neuer Pfad", + "ask_new_domain": "Neue Domain", + "apps_permission_restoration_failed": "Die Berechtigung '{permission: s}' für die Wiederherstellung der App {app: s} ist fehlgeschlagen", + "apps_permission_not_found": "Keine Berechtigung für die installierten Apps gefunden", + "app_upgrade_some_app_failed": "Einige Anwendungen können nicht aktualisiert werden", + "app_upgrade_app_name": "App {App} wird jetzt aktualisiert…", + "app_upgrade_several_apps": "Die folgenden Apps werden aktualisiert: {apps}", + "app_start_restore": "Anwendung {app} wird wiederhergestellt…", + "app_start_backup": "Sammeln von Dateien, die für {app} gesichert werden sollen…", + "app_start_remove": "Anwendung {app} wird entfernt…", + "app_start_install": "Anwendung {app} wird installiert…", + "app_not_upgraded": "Die folgenden Apps wurden nicht aktualisiert: {apps}", + "app_make_default_location_already_used": "Die App \"{app}\" kann nicht als Standard für die Domain \"{domain}\" festgelegt werden. Sie wird bereits von der anderen App \"{other_app}\" verwendet", + "aborting": "Breche ab.", + "app_action_cannot_be_ran_because_required_services_down": "Diese App erfordert einige Dienste, die derzeit nicht verfügbar sind. Bevor Sie fortfahren, sollten Sie versuchen, die folgenden Dienste neu zu starten (und möglicherweise untersuchen, warum sie nicht verfügbar sind): {services}", + "already_up_to_date": "Nichts zu tun! Alles ist bereits auf dem neusten Stand!", + "admin_password_too_long": "Bitte ein Passwort kürzer als 127 Zeichen wählen." } From ad417057270e1d547444822e6bd4f1e051840915 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quent=C3=AD?= Date: Wed, 4 Sep 2019 18:47:59 +0000 Subject: [PATCH 0072/3170] Translated using Weblate (Occitan) Currently translated at 99.8% (582 of 583 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/oc/ --- locales/oc.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/locales/oc.json b/locales/oc.json index 8981fbadd..2cb33f4bd 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -157,7 +157,7 @@ "downloading": "Telecargament…", "dyndns_could_not_check_provide": "Impossible de verificar se {provider:s} pòt provesir {domain:s}.", "dyndns_cron_installed": "La tasca cron pel domeni DynDNS es installada", - "dyndns_cron_remove_failed": "Impossible de levar la tasca cron pel domeni DynDNS", + "dyndns_cron_remove_failed": "Impossible de levar la tasca cron pel domeni DynDNS a causa de {error}", "dyndns_cron_removed": "La tasca cron pel domeni DynDNS es levada", "dyndns_ip_update_failed": "Impossible d’actualizar l’adreça IP sul domeni DynDNS", "dyndns_ip_updated": "Vòstra adreça IP es estada actualizada pel domeni DynDNS", @@ -604,5 +604,7 @@ "edit_permission_with_group_all_users_not_allowed": "Podètz pas modificar las permissions del grop « all_users », utilizatz « yunohost user permission clear APP » o « yunohost user permission add APP -u USER ».", "mailbox_disabled": "La bóstia de las letras es desactivada per l’utilizaire {user:s}", "migration_0011_LDAP_config_dirty": "Sembla qu’avètz modificat manualament la configuracion LDAP. Per far aquesta migracion cal actualizar la configuracion LDAP.\nSalvagardatz la configuracion actuala, reïnicializatz la configuracion originala amb la comanda « yunohost tools regen-conf -f » e tornatz ensajar la migracion", - "need_define_permission_before": "Vos cal tornar definir las permission en utilizant « yunohost user permission add -u USER » abans de suprimir un grop permés" + "need_define_permission_before": "Vos cal tornar definir las permission en utilizant « yunohost user permission add -u USER » abans de suprimir un grop permés", + "permission_already_clear": "La permission « {permission:s} » ja levada per l’aplicacion {app:s}", + "migration_description_0012_postgresql_password_to_md5_authentication": "Forçar l’autentificacion postgresql a utilizar md5 per las connexions localas" } From d1c54fc2ca4e52e5dcaf714d051c0487be46649d Mon Sep 17 00:00:00 2001 From: ButterflyOfFire Date: Wed, 11 Sep 2019 10:04:08 +0000 Subject: [PATCH 0073/3170] Translated using Weblate (Arabic) Currently translated at 67.6% (394 of 583 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ar/ --- locales/ar.json | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/locales/ar.json b/locales/ar.json index c8a1bba7c..52e86d90b 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -429,5 +429,17 @@ "password_too_simple_1": "يجب أن يكون طول الكلمة السرية على الأقل 8 حروف", "service_description_php7.0-fpm": "يُشغّل التطبيقات المكتوبة بلغة الـ PHP على Nginx", "updating_app_lists": "جارٍ جلب التحديثات المتوفرة الخاصة بالتطبيقات…", - "already_up_to_date": "كل شيء على ما يرام! ليس هناك ما يتطلّب تحديثًا!" + "already_up_to_date": "كل شيء على ما يرام! ليس هناك ما يتطلّب تحديثًا!", + "service_description_nslcd": "يدير اتصال متسخدمي واي يونوهوست عبر طرفية سطر الأوامر", + "service_description_slapd": "يخزّن المستخدمين والنطاقات والمعلومات المتعلقة بها", + "service_reloaded": "تم إعادة تحميل خدمة '{service:s}'", + "service_restarted": "تم إعادة تشغيل خدمة '{service:s}'", + "group_unknown": "الفريق {group:s} مجهول", + "group_deletion_failed": "فشلت عملية حذف الفريق '{group}'", + "group_deleted": "تم حذف الفريق '{group}'", + "group_created": "تم إنشاء الفريق '{group}' بنجاح", + "group_name_already_exist": "الفريق {name:s} موجود بالفعل", + "error_when_removing_sftpuser_group": "حدث خطأ أثناء محاولة حذف فريق sftpusers", + "dyndns_could_not_check_available": "لا يمكن التحقق مِن أنّ {domain:s} متوفر على {provider:s}.", + "backup_mount_archive_for_restore": "جارٍ تهيئة النسخة الاحتياطية للاسترجاع…" } From 777b7248cc4c3de88d97da32946ea1088bfea048 Mon Sep 17 00:00:00 2001 From: Emmanuel Date: Wed, 11 Sep 2019 12:20:30 +0000 Subject: [PATCH 0074/3170] Translated using Weblate (French) Currently translated at 93.8% (547 of 583 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 2f22f1397..c94406a07 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -89,7 +89,7 @@ "done": "Terminé", "downloading": "Téléchargement en cours …", "dyndns_cron_installed": "La tâche cron pour le domaine DynDNS a été installée", - "dyndns_cron_remove_failed": "Impossible de supprimer la tâche cron pour le domaine DynDNS", + "dyndns_cron_remove_failed": "Impossible de supprimer la tâche cron DynDNS parce que: {error}", "dyndns_cron_removed": "La tâche cron pour le domaine DynDNS a été enlevée", "dyndns_ip_update_failed": "Impossible de mettre à jour l’adresse IP sur le domaine DynDNS", "dyndns_ip_updated": "Votre adresse IP a été mise à jour pour le domaine DynDNS", @@ -579,7 +579,7 @@ "group_name_already_exist": "Le groupe {name:s} existe déjà", "group_creation_failed": "Échec de la création du groupe '{group}'", "group_deletion_failed": "Échec de la suppression du groupe '{group}'", - "edit_permission_with_group_all_users_not_allowed": "Vous n'êtes pas autorisé à modifier les permissions pour le groupe 'all_users', utilisez'yunohost user permission clear APP' ou 'yunohost user permission add APP -u USER'.", + "edit_permission_with_group_all_users_not_allowed": "Vous n'êtes pas autorisé à modifier les permissions pour le groupe 'all_users', utilisez 'yunohost user permission clear APP' ou 'yunohost user permission add APP -u USER' à la place.", "log_permission_add": "Ajouter l'autorisation '{}' pour l'application '{}'", "log_permission_remove": "Supprimer l'autorisation '{}'", "log_permission_update": "Mise à jour de l'autorisation '{}' pour l'application '{}'", From 51171b84bf0a8c30b46170ee7a040bc6d655f54b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 10 Sep 2019 22:24:49 +0200 Subject: [PATCH 0075/3170] main.metronome -> main.xmpp --- data/other/ldap_scheme.yml | 4 ++-- data/templates/metronome/domain.tpl.cfg.lua | 2 +- src/yunohost/backup.py | 4 ++-- src/yunohost/tests/test_permission.py | 14 +++++++------- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/data/other/ldap_scheme.yml b/data/other/ldap_scheme.yml index 11504bbe8..d013149af 100644 --- a/data/other/ldap_scheme.yml +++ b/data/other/ldap_scheme.yml @@ -67,8 +67,8 @@ depends_children: - permissionYnh groupPermission: - "cn=all_users,ou=groups,dc=yunohost,dc=org" - cn=main.metronome,ou=permission: - cn: main.metronome + cn=main.xmpp,ou=permission: + cn: main.xmpp gidNumber: "5002" objectClass: - posixGroup diff --git a/data/templates/metronome/domain.tpl.cfg.lua b/data/templates/metronome/domain.tpl.cfg.lua index 2ee9cfaae..d523365db 100644 --- a/data/templates/metronome/domain.tpl.cfg.lua +++ b/data/templates/metronome/domain.tpl.cfg.lua @@ -8,7 +8,7 @@ VirtualHost "{{ domain }}" hostname = "localhost", user = { basedn = "ou=users,dc=yunohost,dc=org", - filter = "(&(objectClass=posixAccount)(mail=*@{{ domain }})(permission=cn=main.metronome,ou=permission,dc=yunohost,dc=org))", + filter = "(&(objectClass=posixAccount)(mail=*@{{ domain }})(permission=cn=main.xmpp,ou=permission,dc=yunohost,dc=org))", usernamefield = "mail", namefield = "cn", }, diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index bd5d5750d..55b6678b8 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -1191,7 +1191,7 @@ class RestoreManager(): old_apps_permission = [] try: old_apps_permission = ldap.search('ou=permission,dc=yunohost,dc=org', - '(&(objectClass=permissionYnh)(!(cn=main.mail))(!(cn=main.metronome))(!(cn=main.sftp)))', + '(&(objectClass=permissionYnh)(!(cn=main.mail))(!(cn=main.xmpp))(!(cn=main.sftp)))', ['cn', 'objectClass', 'groupPermission', 'URL', 'gidNumber']) except: logger.info(m18n.n('apps_permission_not_found')) @@ -1247,7 +1247,7 @@ class RestoreManager(): # Remove all permission for all app which sill in the LDAP for per in ldap.search('ou=permission,dc=yunohost,dc=org', - '(&(objectClass=permissionYnh)(!(cn=main.mail))(!(cn=main.metronome))(!(cn=main.sftp)))', + '(&(objectClass=permissionYnh)(!(cn=main.mail))(!(cn=main.xmpp))(!(cn=main.sftp)))', ['cn']): if not ldap.remove('cn=%s,ou=permission' % per['cn'][0]): raise YunohostError('permission_deletion_failed', diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index d309a8211..3b9815f63 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -135,7 +135,7 @@ def check_LDAP_db_integrity(): def check_permission_for_apps(): # We check that the for each installed apps we have at last the "main" permission # and we don't have any permission linked to no apps. The only exception who is not liked to an app - # is mail, metronome, and sftp + # is mail, xmpp, and sftp from yunohost.utils.ldap import _get_ldap_interface ldap = _get_ldap_interface() @@ -146,7 +146,7 @@ def check_permission_for_apps(): installed_apps = {app['id'] for app in app_list(installed=True)['apps']} permission_list_set = {permission['cn'][0].split(".")[1] for permission in permission_search} - extra_service_permission = set(['mail', 'metronome']) + extra_service_permission = set(['mail', 'xmpp']) if 'sftp' in permission_list_set: extra_service_permission.add('sftp') assert installed_apps == permission_list_set - extra_service_permission @@ -164,8 +164,8 @@ def test_list_permission(): assert "main" in res['blog'] assert "mail" in res assert "main" in res['mail'] - assert "metronome" in res - assert "main" in res['metronome'] + assert "xmpp" in res + assert "main" in res['xmpp'] assert ["all_users"] == res['wiki']['main']['allowed_groups'] assert ["alice"] == res['blog']['main']['allowed_groups'] assert set(["alice", "bob"]) == set(res['wiki']['main']['allowed_users']) @@ -220,9 +220,9 @@ def test_remove_bad_permission(): assert "blog" in res assert "main" in res['blog'] assert "mail" in res - assert "main" in res ['mail'] - assert "metronome" in res - assert "main" in res['metronome'] + assert "main" in res['mail'] + assert "xmpp" in res + assert "main" in res['xmpp'] def test_remove_main_permission(): with pytest.raises(YunohostError): From 0f688caccd33f425059081d0c283afce075e328b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 10 Sep 2019 22:55:37 +0200 Subject: [PATCH 0076/3170] Swap 'main' in permission namespace --- data/other/ldap_scheme.yml | 8 ++++---- data/templates/dovecot/dovecot-ldap.conf | 4 ++-- data/templates/metronome/domain.tpl.cfg.lua | 2 +- data/templates/postfix/plain/ldap-accounts.cf | 2 +- data/templates/postfix/plain/ldap-aliases.cf | 2 +- src/yunohost/app.py | 2 +- src/yunohost/backup.py | 2 +- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/data/other/ldap_scheme.yml b/data/other/ldap_scheme.yml index d013149af..caa8fffb2 100644 --- a/data/other/ldap_scheme.yml +++ b/data/other/ldap_scheme.yml @@ -59,16 +59,16 @@ children: - groupOfNamesYnh depends_children: - cn=main.mail,ou=permission: - cn: main.mail + cn=mail.main,ou=permission: + cn: mail.main gidNumber: "5001" objectClass: - posixGroup - permissionYnh groupPermission: - "cn=all_users,ou=groups,dc=yunohost,dc=org" - cn=main.xmpp,ou=permission: - cn: main.xmpp + cn=xmpp.main,ou=permission: + cn: xmpp.main gidNumber: "5002" objectClass: - posixGroup diff --git a/data/templates/dovecot/dovecot-ldap.conf b/data/templates/dovecot/dovecot-ldap.conf index c7c9785fd..3a80ba47f 100644 --- a/data/templates/dovecot/dovecot-ldap.conf +++ b/data/templates/dovecot/dovecot-ldap.conf @@ -3,7 +3,7 @@ auth_bind = yes ldap_version = 3 base = ou=users,dc=yunohost,dc=org user_attrs = uidNumber=500,gidNumber=8,mailuserquota=quota_rule=*:bytes=%$ -user_filter = (&(objectClass=inetOrgPerson)(uid=%n)(permission=cn=main.mail,ou=permission,dc=yunohost,dc=org)) -pass_filter = (&(objectClass=inetOrgPerson)(uid=%n)(permission=cn=main.mail,ou=permission,dc=yunohost,dc=org)) +user_filter = (&(objectClass=inetOrgPerson)(uid=%n)(permission=cn=mail.main,ou=permission,dc=yunohost,dc=org)) +pass_filter = (&(objectClass=inetOrgPerson)(uid=%n)(permission=cn=mail.main,ou=permission,dc=yunohost,dc=org)) default_pass_scheme = SSHA diff --git a/data/templates/metronome/domain.tpl.cfg.lua b/data/templates/metronome/domain.tpl.cfg.lua index d523365db..e7f6bcef7 100644 --- a/data/templates/metronome/domain.tpl.cfg.lua +++ b/data/templates/metronome/domain.tpl.cfg.lua @@ -8,7 +8,7 @@ VirtualHost "{{ domain }}" hostname = "localhost", user = { basedn = "ou=users,dc=yunohost,dc=org", - filter = "(&(objectClass=posixAccount)(mail=*@{{ domain }})(permission=cn=main.xmpp,ou=permission,dc=yunohost,dc=org))", + filter = "(&(objectClass=posixAccount)(mail=*@{{ domain }})(permission=cn=xmpp.main,ou=permission,dc=yunohost,dc=org))", usernamefield = "mail", namefield = "cn", }, diff --git a/data/templates/postfix/plain/ldap-accounts.cf b/data/templates/postfix/plain/ldap-accounts.cf index 9f6f94e6d..75f38cf58 100644 --- a/data/templates/postfix/plain/ldap-accounts.cf +++ b/data/templates/postfix/plain/ldap-accounts.cf @@ -1,5 +1,5 @@ server_host = localhost server_port = 389 search_base = dc=yunohost,dc=org -query_filter = (&(objectClass=mailAccount)(mail=%s)(permission=cn=main.mail,ou=permission,dc=yunohost,dc=org)) +query_filter = (&(objectClass=mailAccount)(mail=%s)(permission=cn=mail.main,ou=permission,dc=yunohost,dc=org)) result_attribute = uid diff --git a/data/templates/postfix/plain/ldap-aliases.cf b/data/templates/postfix/plain/ldap-aliases.cf index 5e7d3a6c1..46563ae22 100644 --- a/data/templates/postfix/plain/ldap-aliases.cf +++ b/data/templates/postfix/plain/ldap-aliases.cf @@ -1,5 +1,5 @@ server_host = localhost server_port = 389 search_base = dc=yunohost,dc=org -query_filter = (&(objectClass=mailAccount)(mail=%s)(permission=cn=main.mail,ou=permission,dc=yunohost,dc=org)) +query_filter = (&(objectClass=mailAccount)(mail=%s)(permission=cn=mail.main,ou=permission,dc=yunohost,dc=org)) result_attribute = maildrop diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 4a14c5e4b..105d4faf7 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -432,7 +432,7 @@ def app_map(app=None, raw=False, user=None): if user is not None: ldap = _get_ldap_interface() if not ldap.search(base='ou=permission,dc=yunohost,dc=org', - filter='(&(objectclass=permissionYnh)(cn=main.%s)(inheritPermission=uid=%s,ou=users,dc=yunohost,dc=org))' % (app_id, user), + filter='(&(objectclass=permissionYnh)(cn=%s.main)(inheritPermission=uid=%s,ou=users,dc=yunohost,dc=org))' % (app_id, user), attrs=['cn']): continue diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 55b6678b8..9a27031ae 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -1247,7 +1247,7 @@ class RestoreManager(): # Remove all permission for all app which sill in the LDAP for per in ldap.search('ou=permission,dc=yunohost,dc=org', - '(&(objectClass=permissionYnh)(!(cn=main.mail))(!(cn=main.xmpp))(!(cn=main.sftp)))', + '(&(objectClass=permissionYnh)(!(cn=mail.main))(!(cn=xmpp.main))(!(cn=sftp.main)))', ['cn']): if not ldap.remove('cn=%s,ou=permission' % per['cn'][0]): raise YunohostError('permission_deletion_failed', From 34df84e222e04a975e0315e8155f5045ba9b9f0c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 11 Sep 2019 00:57:32 +0200 Subject: [PATCH 0077/3170] group add -> group create, to be consistent with user create/delete --- data/actionsmap/yunohost.yml | 4 ++-- locales/en.json | 2 +- .../data_migrations/0011_setup_group_permission.py | 4 ++-- src/yunohost/tests/test_user-group.py | 12 ++++++------ src/yunohost/user.py | 4 ++-- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index cbb7756b0..8a6c10b5f 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -212,8 +212,8 @@ user: help: fields to fetch nargs: "+" - ### user_group_add() - add: + ### user_group_create() + create: action_help: Create group api: POST /users/groups arguments: diff --git a/locales/en.json b/locales/en.json index be00d5b1e..815c40b75 100644 --- a/locales/en.json +++ b/locales/en.json @@ -288,7 +288,7 @@ "log_regen_conf": "Regenerate system configurations '{}'", "log_user_create": "Add '{}' user", "log_user_delete": "Delete '{}' user", - "log_user_group_add": "Add '{}' group", + "log_user_group_create": "Create '{}' group", "log_user_group_delete": "Delete '{}' group", "log_user_group_update": "Update '{}' group", "log_user_update": "Update information of '{}' user", diff --git a/src/yunohost/data_migrations/0011_setup_group_permission.py b/src/yunohost/data_migrations/0011_setup_group_permission.py index 17fd8f4a5..9699d2cd9 100644 --- a/src/yunohost/data_migrations/0011_setup_group_permission.py +++ b/src/yunohost/data_migrations/0011_setup_group_permission.py @@ -7,7 +7,7 @@ from yunohost.utils.error import YunohostError from moulinette.utils.log import getActionLogger from yunohost.tools import Migration -from yunohost.user import user_group_add, user_group_update +from yunohost.user import user_group_create, user_group_update from yunohost.app import app_setting, app_list from yunohost.regenconf import regen_conf from yunohost.permission import permission_add, permission_sync_to_user @@ -65,7 +65,7 @@ class MyMigration(Migration): username = user_info['uid'][0] ldap.update('uid=%s,ou=users' % username, {'objectClass': ['mailAccount', 'inetOrgPerson', 'posixAccount', 'userPermissionYnh']}) - user_group_add(username, gid=user_info['uidNumber'][0], sync_perm=False) + user_group_create(username, gid=user_info['uidNumber'][0], sync_perm=False) user_group_update(groupname=username, add_user=username, force=True, sync_perm=False) user_group_update(groupname='all_users', add_user=username, force=True, sync_perm=False) diff --git a/src/yunohost/tests/test_user-group.py b/src/yunohost/tests/test_user-group.py index 3973f0c7d..1defce466 100644 --- a/src/yunohost/tests/test_user-group.py +++ b/src/yunohost/tests/test_user-group.py @@ -1,7 +1,7 @@ import pytest from moulinette.core import MoulinetteError -from yunohost.user import user_list, user_info, user_group_list, user_create, user_delete, user_update, user_group_add, user_group_delete, user_group_update, user_group_info +from yunohost.user import user_list, user_info, user_group_list, user_create, user_delete, user_update, user_group_create, user_group_delete, user_group_update, user_group_info from yunohost.domain import _get_maindomain from yunohost.utils.error import YunohostError from yunohost.tests.test_permission import check_LDAP_db_integrity @@ -24,8 +24,8 @@ def setup_function(function): user_create("bob", "Bob", "Snow", "bob@" + maindomain, "test123Ynh") user_create("jack", "Jack", "Black", "jack@" + maindomain, "test123Ynh") - user_group_add("dev") - user_group_add("apps") + user_group_create("dev") + user_group_create("apps") user_group_update("dev", add_user=["alice"]) user_group_update("apps", add_user=["bob"]) @@ -83,7 +83,7 @@ def test_del_user(): assert "alice" not in group_res['all_users']['members'] def test_add_group(): - user_group_add("adminsys") + user_group_create("adminsys") group_res = user_group_list()['groups'] assert "adminsys" in group_res @@ -122,12 +122,12 @@ def test_del_bad_user_1(): def test_add_bad_group_1(): # Check groups already exist with special group "all_users" with pytest.raises(YunohostError): - user_group_add("all_users") + user_group_create("all_users") def test_add_bad_group_2(): # Check groups already exist (for standard groups) with pytest.raises(MoulinetteError): - user_group_add("dev") + user_group_create("dev") def test_del_bad_group_1(): # Check not allowed to remove this groups diff --git a/src/yunohost/user.py b/src/yunohost/user.py index a6c262ed7..e5480ca92 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -218,7 +218,7 @@ def user_create(operation_logger, username, firstname, lastname, mail, password, exc_info=1) # Create group for user and add to group 'all_users' - user_group_add(groupname=username, gid=uid, sync_perm=False) + user_group_create(groupname=username, gid=uid, sync_perm=False) user_group_update(groupname=username, add_user=username, force=True, sync_perm=False) user_group_update(groupname='all_users', add_user=username, force=True, sync_perm=True) @@ -554,7 +554,7 @@ def user_group_list(fields=None): @is_unit_operation([('groupname', 'user')]) -def user_group_add(operation_logger, groupname, gid=None, sync_perm=True): +def user_group_create(operation_logger, groupname, gid=None, sync_perm=True): """ Create group From f60af2053f959d02fe958310ff9eaa40a9877be3 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 11 Sep 2019 01:06:20 +0200 Subject: [PATCH 0078/3170] permission_add/remove becomes create/delete to be consistent with user and group create/delete. In the context of permissions, add/remove shall instead be related to adding/removing an existing permission for a user or group. --- data/helpers.d/setting | 4 ++-- src/yunohost/app.py | 10 +++++----- src/yunohost/backup.py | 4 ++-- .../0011_setup_group_permission.py | 4 ++-- src/yunohost/permission.py | 4 ++-- src/yunohost/tests/test_permission.py | 20 +++++++++---------- 6 files changed, 23 insertions(+), 23 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index da711b4bd..e002afa57 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -249,7 +249,7 @@ ynh_permission_create() { if [[ -n ${urls:-} ]]; then urls=",urls=['${urls//';'/"','"}']" fi - yunohost tools shell -c "from yunohost.permission import permission_add; permission_add('$app', '$permission' ${defaultdisallow:-} ${urls:-}, sync_perm=False)" + yunohost tools shell -c "from yunohost.permission import permission_create; permission_create('$app', '$permission' ${defaultdisallow:-} ${urls:-}, sync_perm=False)" } # Remove a permission for the app (note that when the app is removed all permission is automatically removed) @@ -263,7 +263,7 @@ ynh_permission_remove() { local permission ynh_handle_getopts_args "$@" - yunohost tools shell -c "from yunohost.permission import permission_remove; permission_remove('$app', '$permission', sync_perm=False)" + yunohost tools shell -c "from yunohost.permission import permission_delete; permission_delete('$app', '$permission', sync_perm=False)" } # Add a path managed by the SSO diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 105d4faf7..c2fb87acf 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -738,7 +738,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu from yunohost.utils.ldap import _get_ldap_interface from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback from yunohost.log import OperationLogger - from yunohost.permission import permission_add, permission_update, permission_remove, permission_sync_to_user + from yunohost.permission import permission_create, permission_update, permission_delete, permission_sync_to_user ldap = _get_ldap_interface() # Fetch or extract sources @@ -875,7 +875,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu # Create permission before the install (useful if the install script redefine the permission) # Note that sync_perm is disabled to avoid triggering a whole bunch of code and messages # can't be sure that we don't have one case when it's needed - permission_add(app=app_instance_name, permission="main", sync_perm=False) + permission_create(app=app_instance_name, permission="main", sync_perm=False) # Execute the app install script install_retcode = 1 @@ -914,7 +914,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu filter='(&(objectclass=permissionYnh)(cn=*.%s))' % app_instance_name, attrs=['cn']) permission_list = [p['cn'][0] for p in result] for l in permission_list: - permission_remove(app_instance_name, l.split('.')[0], force=True) + permission_delete(app_instance_name, l.split('.')[0], force=True) if remove_retcode != 0: msg = m18n.n('app_not_properly_removed', @@ -980,7 +980,7 @@ def app_remove(operation_logger, app): """ from yunohost.utils.ldap import _get_ldap_interface from yunohost.hook import hook_exec, hook_remove, hook_callback - from yunohost.permission import permission_remove, permission_sync_to_user + from yunohost.permission import permission_delete, permission_sync_to_user if not _is_installed(app): raise YunohostError('app_not_installed', app=app, all_apps=_get_all_installed_apps_id()) @@ -1031,7 +1031,7 @@ def app_remove(operation_logger, app): filter='(&(objectclass=permissionYnh)(cn=*.%s))' % app, attrs=['cn']) permission_list = [p['cn'][0] for p in result] for l in permission_list: - permission_remove(app, l.split('.')[0], force=True, sync_perm=False) + permission_delete(app, l.split('.')[0], force=True, sync_perm=False) permission_sync_to_user() diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 9a27031ae..9e90ece2a 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -1303,7 +1303,7 @@ class RestoreManager(): """ from moulinette.utils.filesystem import read_ldif from yunohost.user import user_group_list - from yunohost.permission import permission_remove + from yunohost.permission import permission_delete from yunohost.utils.ldap import _get_ldap_interface ldap = _get_ldap_interface() @@ -1441,7 +1441,7 @@ class RestoreManager(): filter='(&(objectclass=permissionYnh)(cn=*.%s))' % app_instance_name, attrs=['cn']) permission_list = [p['cn'][0] for p in result] for l in permission_list: - permission_remove(app_instance_name, l.split('.')[0], force=True) + permission_delete(app_instance_name, l.split('.')[0], force=True) # TODO Cleaning app hooks else: diff --git a/src/yunohost/data_migrations/0011_setup_group_permission.py b/src/yunohost/data_migrations/0011_setup_group_permission.py index 9699d2cd9..d0bae2d95 100644 --- a/src/yunohost/data_migrations/0011_setup_group_permission.py +++ b/src/yunohost/data_migrations/0011_setup_group_permission.py @@ -10,7 +10,7 @@ from yunohost.tools import Migration from yunohost.user import user_group_create, user_group_update from yunohost.app import app_setting, app_list from yunohost.regenconf import regen_conf -from yunohost.permission import permission_add, permission_sync_to_user +from yunohost.permission import permission_create, permission_sync_to_user from yunohost.user import user_permission_add logger = getActionLogger('yunohost.migration') @@ -85,7 +85,7 @@ class MyMigration(Migration): domain = app_setting(app, 'domain') urls = [domain + path] if domain and path else None - permission_add(app, permission='main', urls=urls, default_allow=True, sync_perm=False) + permission_create(app, permission='main', urls=urls, default_allow=True, sync_perm=False) if permission: allowed_group = permission.split(',') user_permission_add([app], permission='main', group=allowed_group, sync_perm=False) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index f7fa307da..3f9131018 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -317,7 +317,7 @@ def user_permission_clear(operation_logger, app=[], permission=None, sync_perm=T @is_unit_operation(['permission', 'app']) -def permission_add(operation_logger, app, permission, urls=None, default_allow=True, sync_perm=True): +def permission_create(operation_logger, app, permission, urls=None, default_allow=True, sync_perm=True): """ Create a new permission for a specific application @@ -431,7 +431,7 @@ def permission_update(operation_logger, app, permission, add_url=None, remove_ur @is_unit_operation(['permission', 'app']) -def permission_remove(operation_logger, app, permission, force=False, sync_perm=True): +def permission_delete(operation_logger, app, permission, force=False, sync_perm=True): """ Remove a permission for a specific application diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index 3b9815f63..8222248b1 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -3,7 +3,7 @@ import pytest from moulinette.core import MoulinetteError from yunohost.app import app_install, app_remove, app_change_url, app_list from yunohost.user import user_list, user_create, user_permission_list, user_delete, user_group_list, user_group_delete, user_permission_add, user_permission_remove, user_permission_clear -from yunohost.permission import permission_add, permission_update, permission_remove +from yunohost.permission import permission_create, permission_update, permission_delete from yunohost.domain import _get_maindomain from yunohost.utils.error import YunohostError @@ -21,15 +21,15 @@ def clean_user_groups_permission(): for a, per in user_permission_list()['permissions'].items(): if a in ['wiki', 'blog', 'site']: for p in per: - permission_remove(a, p, force=True, sync_perm=False) + permission_delete(a, p, force=True, sync_perm=False) def setup_function(function): clean_user_groups_permission() user_create("alice", "Alice", "White", "alice@" + maindomain, "test123Ynh") user_create("bob", "Bob", "Snow", "bob@" + maindomain, "test123Ynh") - permission_add("wiki", "main", [maindomain + "/wiki"], sync_perm=False) - permission_add("blog", "main", sync_perm=False) + permission_create("wiki", "main", [maindomain + "/wiki"], sync_perm=False) + permission_create("blog", "main", sync_perm=False) user_permission_add(["blog"], "main", group="alice") @@ -177,7 +177,7 @@ def test_list_permission(): # def test_add_permission_1(): - permission_add("site", "test") + permission_create("site", "test") res = user_permission_list()['permissions'] assert "site" in res @@ -186,7 +186,7 @@ def test_add_permission_1(): assert set(["alice", "bob"]) == set(res['site']['test']['allowed_users']) def test_add_permission_2(): - permission_add("site", "main", default_allow=False) + permission_create("site", "main", default_allow=False) res = user_permission_list()['permissions'] assert "site" in res @@ -195,7 +195,7 @@ def test_add_permission_2(): assert [] == res['site']['main']['allowed_users'] def test_remove_permission(): - permission_remove("wiki", "main", force=True) + permission_delete("wiki", "main", force=True) res = user_permission_list()['permissions'] assert "wiki" not in res @@ -207,12 +207,12 @@ def test_remove_permission(): def test_add_bad_permission(): # Create permission with same name with pytest.raises(YunohostError): - permission_add("wiki", "main") + permission_create("wiki", "main") def test_remove_bad_permission(): # Remove not existant permission with pytest.raises(MoulinetteError): - permission_remove("non_exit", "main", force=True) + permission_delete("non_exit", "main", force=True) res = user_permission_list()['permissions'] assert "wiki" in res @@ -226,7 +226,7 @@ def test_remove_bad_permission(): def test_remove_main_permission(): with pytest.raises(YunohostError): - permission_remove("blog", "main") + permission_delete("blog", "main") res = user_permission_list()['permissions'] assert "mail" in res From a6d68c76c4eaa998db3fa77e7ece0c811e85e8ad Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 11 Sep 2019 01:31:22 +0200 Subject: [PATCH 0079/3170] permission_update -> permission_urls (+ tweak the helper name) so that it's more differentiable from user_permission_update --- data/helpers.d/setting | 10 +++++----- src/yunohost/permission.py | 18 ++++++++++++++++-- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index e002afa57..e6311fc1f 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -268,18 +268,18 @@ ynh_permission_remove() { # Add a path managed by the SSO # -# usage: ynh_permission_add_path --app "app" --permission "permission" --url "url" ["url" ...] +# usage: ynh_permission_add_url --app "app" --permission "permission" --url "url" ["url" ...] # | arg: app - the application id # | arg: permission - the name for the permission # | arg: url - the FULL url for the the permission (ex domain.tld/apps/admin) -ynh_permission_add_path() { +ynh_permission_add_url() { declare -Ar args_array=( [a]=app= [p]=permission= [u]=url= ) local app local permission local url ynh_handle_getopts_args "$@" - yunohost tools shell -c "from yunohost.permission import permission_update; permission_update('$app', '$permission', add_url=['${url//';'/"','"}'], sync_perm=False)" + yunohost tools shell -c "from yunohost.permission import permission_urls; permission_urls('$app', '$permission', add_url=['${url//';'/"','"}'], sync_perm=False)" } # Remove a path managed by the SSO @@ -288,12 +288,12 @@ ynh_permission_add_path() { # | arg: app - the application id # | arg: permission - the name for the permission # | arg: url - the FULL url for the the permission (ex domain.tld/apps/admin) -ynh_permission_del_path() { +ynh_permission_remove_url() { declare -Ar args_array=( [a]=app= [p]=permission= [u]=url= ) local app local permission local url ynh_handle_getopts_args "$@" - yunohost tools shell -c "from yunohost.permission import permission_update; permission_update('$app', '$permission', remove_url=['${url//';'/"','"}'], sync_perm=False)" + yunohost tools shell -c "from yunohost.permission import permission_urls; permission_urls('$app', '$permission', remove_url=['${url//';'/"','"}'], sync_perm=False)" } diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 3f9131018..1a5465674 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -35,6 +35,12 @@ from yunohost.log import is_unit_operation logger = getActionLogger('yunohost.user') +# +# +# The followings are the methods exposed through the "yunohost user permission" interface +# +# + def user_permission_list(app=None, permission=None, username=None, group=None): """ @@ -315,6 +321,14 @@ def user_permission_clear(operation_logger, app=[], permission=None, sync_perm=T return user_permission_list(app, permission) +# +# +# The followings methods are *not* directly exposed. +# They are used to create/delete the permissions (e.g. during app install/remove) +# and by some app helpers to possibly add additional permissions and tweak the urls +# +# + @is_unit_operation(['permission', 'app']) def permission_create(operation_logger, app, permission, urls=None, default_allow=True, sync_perm=True): @@ -374,9 +388,9 @@ def permission_create(operation_logger, app, permission, urls=None, default_allo @is_unit_operation(['permission', 'app']) -def permission_update(operation_logger, app, permission, add_url=None, remove_url=None, sync_perm=True): +def permission_urls(operation_logger, app, permission, add_url=None, remove_url=None, sync_perm=True): """ - Update a permission for a specific application + Update urls related to a permission for a specific application Keyword argument: app -- an application OR sftp, xmpp (metronome), mail From 0f7b8c3515e80afe648d55445517db60d848f609 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 11 Sep 2019 02:08:46 +0200 Subject: [PATCH 0080/3170] Simplify group list interface and code --- data/actionsmap/yunohost.yml | 11 ++++-- src/yunohost/backup.py | 2 +- src/yunohost/permission.py | 12 +++--- src/yunohost/user.py | 76 +++++++++++++----------------------- src/yunohost/utils/ldap.py | 14 +++++++ 5 files changed, 55 insertions(+), 60 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 8a6c10b5f..b463df646 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -208,9 +208,14 @@ user: action_help: List group api: GET /users/groups arguments: - --fields: - help: fields to fetch - nargs: "+" + -n: + full: --names-only + help: Only list the name of the groups without any additional info + action: store_true + -f: + full: --full + help: List all the info available for each groups + action: store_true ### user_group_create() create: diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 9e90ece2a..f96146ea0 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -1374,7 +1374,7 @@ class RestoreManager(): filtred_entries = ['entryUUID', 'creatorsName', 'createTimestamp', 'entryCSN', 'structuralObjectClass', 'modifiersName', 'modifyTimestamp', 'inheritPermission', 'memberUid'] entries = read_ldif('%s/permission.ldif' % app_settings_in_archive, filtred_entries) - group_list = user_group_list(['cn'])['groups'] + group_list = user_group_list()['groups'] for dn, entry in entries: # Remove the group which has been removed for group in entry['groupPermission']: diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 1a5465674..0b77a3e5c 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -169,13 +169,13 @@ def user_permission_update(operation_logger, app=[], permission=None, add_userna # Validate that the group exist for g in add_group: - if g not in user_group_list(['cn'])['groups']: + if g not in user_group_list()['groups']: raise YunohostError('group_unknown', group=g) for u in add_username: if u not in user_list(['uid'])['users']: raise YunohostError('user_unknown', user=u) for g in del_group: - if g not in user_group_list(['cn'])['groups']: + if g not in user_group_list()['groups']: raise YunohostError('group_unknown', group=g) for u in del_username: if u not in user_list(['uid'])['users']: @@ -244,14 +244,12 @@ def user_permission_update(operation_logger, app=[], permission=None, add_userna for a in app: allowed_users = set() disallowed_users = set() - group_list = user_group_list(['member'])['groups'] + group_list = user_group_list()['groups'] for g in add_group: - if 'members' in group_list[g]: - allowed_users.union(group_list[g]['members']) + allowed_users.union(group_list[g]['members']) for g in del_group: - if 'members' in group_list[g]: - disallowed_users.union(group_list[g]['members']) + disallowed_users.union(group_list[g]['members']) allowed_users = ','.join(allowed_users) disallowed_users = ','.join(disallowed_users) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index e5480ca92..787058fbc 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -489,66 +489,44 @@ def user_info(username): # # Group subcategory # -def user_group_list(fields=None): +def user_group_list(names_only=False, full=False): """ List users Keyword argument: - filter -- LDAP filter used to search - offset -- Starting number for user fetching - limit -- Maximum number of user fetched - fields -- fields to fetch - + names-only -- Only list the name of the groups without any additional info + full -- List all the info available for each groups """ - from yunohost.utils.ldap import _get_ldap_interface + + # Fetch relevant informations + + from yunohost.utils.ldap import _get_ldap_interface, _ldap_path_extract ldap = _get_ldap_interface() - group_attr = { - 'cn': 'groupname', - 'member': 'members', - 'permission': 'permission' - } - attrs = ['cn'] - groups = {} - if fields: - keys = group_attr.keys() - for attr in fields: - if attr in keys: - attrs.append(attr) - else: - raise YunohostError('field_invalid', attr) + if names_only: + fields_to_fetch = ["cn"] + elif full: + fields_to_fetch = ["cn", "member", "permission"] else: - attrs = ['cn', 'member'] + fields_to_fetch = ["cn", "member"] - result = ldap.search('ou=groups,dc=yunohost,dc=org', - '(objectclass=groupOfNamesYnh)', - attrs) + groups_infos = ldap.search('ou=groups,dc=yunohost,dc=org', + '(objectclass=groupOfNamesYnh)', + fields_to_fetch) - for group in result: - # The group "admins" should be hidden for the user - if group_attr['cn'] == "admins": - continue - entry = {} - for attr, values in group.items(): - if values: - if attr == "member": - entry[group_attr[attr]] = [] - for v in values: - entry[group_attr[attr]].append(v.split("=")[1].split(",")[0]) - elif attr == "permission": - entry[group_attr[attr]] = {} - for v in values: - permission = v.split("=")[1].split(",")[0].split(".")[1] - pType = v.split("=")[1].split(",")[0].split(".")[0] - if permission in entry[group_attr[attr]]: - entry[group_attr[attr]][permission].append(pType) - else: - entry[group_attr[attr]][permission] = [pType] - else: - entry[group_attr[attr]] = values[0] + # Parse / organize information to be outputed - groupname = entry[group_attr['cn']] - groups[groupname] = entry + groups = {} + for infos in groups_infos: + name = infos["cn"][0] + groups[name] = {} + if "member" in fields_to_fetch: + groups[name]["members"] = [_ldap_path_extract(p, "uid") for p in infos.get("member", [])] + if "permission" in fields_to_fetch: + groups[name]["permissions"] = [_ldap_path_extract(p, "cn") for p in infos.get("permission", [])] + + if names_only: + groups = groups.keys() return {'groups': groups} diff --git a/src/yunohost/utils/ldap.py b/src/yunohost/utils/ldap.py index 186cdbdec..c3b5065a1 100644 --- a/src/yunohost/utils/ldap.py +++ b/src/yunohost/utils/ldap.py @@ -40,6 +40,20 @@ def _get_ldap_interface(): return _ldap_interface + +# We regularly want to extract stuff like 'bar' in ldap path like +# foo=bar,dn=users.example.org,ou=example.org,dc=org so this small helper allow +# to do this without relying of dozens of mysterious string.split()[0] +# +# e.g. using _ldap_path_extract(path, "foo") on the previous example will +# return bar + +def _ldap_path_extract(path, info): + for element in path.split(","): + if element.startswith(info + "="): + return element[len(info + "="):] + + # Add this to properly close / delete the ldap interface / authenticator # when Python exits ... # Otherwise there's a risk that some funky error appears at the very end From c5d0a270980188a0a2d04f8f6aece63a727d4206 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 11 Sep 2019 03:00:29 +0200 Subject: [PATCH 0081/3170] Simplify group info and group update interface and code --- data/actionsmap/yunohost.yml | 8 +- .../0011_setup_group_permission.py | 4 +- src/yunohost/tests/test_user-group.py | 20 ++-- src/yunohost/user.py | 101 ++++++++---------- 4 files changed, 58 insertions(+), 75 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index b463df646..e727b87ef 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -249,15 +249,15 @@ user: extra: pattern: *pattern_groupname -a: - full: --add-user - help: User to add in group + full: --add + help: User(s) to add in the group nargs: "*" metavar: USERNAME extra: pattern: *pattern_username -r: - full: --remove-user - help: User to remove in group + full: --remove + help: User(s) to remove in the group nargs: "*" metavar: USERNAME extra: diff --git a/src/yunohost/data_migrations/0011_setup_group_permission.py b/src/yunohost/data_migrations/0011_setup_group_permission.py index d0bae2d95..d2924f0af 100644 --- a/src/yunohost/data_migrations/0011_setup_group_permission.py +++ b/src/yunohost/data_migrations/0011_setup_group_permission.py @@ -66,8 +66,8 @@ class MyMigration(Migration): ldap.update('uid=%s,ou=users' % username, {'objectClass': ['mailAccount', 'inetOrgPerson', 'posixAccount', 'userPermissionYnh']}) user_group_create(username, gid=user_info['uidNumber'][0], sync_perm=False) - user_group_update(groupname=username, add_user=username, force=True, sync_perm=False) - user_group_update(groupname='all_users', add_user=username, force=True, sync_perm=False) + user_group_update(groupname=username, add=username, force=True, sync_perm=False) + user_group_update(groupname='all_users', add=username, force=True, sync_perm=False) def migrate_app_permission(self, app=None): diff --git a/src/yunohost/tests/test_user-group.py b/src/yunohost/tests/test_user-group.py index 1defce466..34e515ea0 100644 --- a/src/yunohost/tests/test_user-group.py +++ b/src/yunohost/tests/test_user-group.py @@ -26,8 +26,8 @@ def setup_function(function): user_group_create("dev") user_group_create("apps") - user_group_update("dev", add_user=["alice"]) - user_group_update("apps", add_user=["bob"]) + user_group_update("dev", add=["alice"]) + user_group_update("apps", add=["bob"]) def teardown_function(function): clean_user_groups() @@ -151,28 +151,28 @@ def test_update_user_1(): assert "NewLast" == info['lastname'] def test_update_group_1(): - user_group_update("dev", add_user=["bob"]) + user_group_update("dev", add=["bob"]) group_res = user_group_list()['groups'] assert set(["alice", "bob"]) == set(group_res['dev']['members']) def test_update_group_2(): # Try to add a user in a group when the user is already in - user_group_update("apps", add_user=["bob"]) + user_group_update("apps", add=["bob"]) group_res = user_group_list()['groups'] assert ["bob"] == group_res['apps']['members'] def test_update_group_3(): # Try to remove a user in a group - user_group_update("apps", remove_user=["bob"]) + user_group_update("apps", remove=["bob"]) group_res = user_group_list()['groups'] assert "members" not in group_res['apps'] def test_update_group_4(): # Try to remove a user in a group when it is not already in - user_group_update("apps", remove_user=["jack"]) + user_group_update("apps", remove=["jack"]) group_res = user_group_list()['groups'] assert ["bob"] == group_res['apps']['members'] @@ -190,21 +190,21 @@ def test_bad_update_user_1(): def bad_update_group_1(): # Check groups not found with pytest.raises(YunohostError): - user_group_update("not_exit", add_user=["alice"]) + user_group_update("not_exit", add=["alice"]) def test_bad_update_group_2(): # Check remove user in groups "all_users" not allowed with pytest.raises(YunohostError): - user_group_update("all_users", remove_user=["alice"]) + user_group_update("all_users", remove=["alice"]) def test_bad_update_group_3(): # Check remove user in it own group not allowed with pytest.raises(YunohostError): - user_group_update("alice", remove_user=["alice"]) + user_group_update("alice", remove=["alice"]) def test_bad_update_group_1(): # Check add bad user in group with pytest.raises(YunohostError): - user_group_update("dev", add_user=["not_exist"]) + user_group_update("dev", add=["not_exist"]) assert "not_exist" not in user_group_list()["groups"]["dev"] diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 787058fbc..a1a671ccf 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -32,6 +32,7 @@ import crypt import random import string import subprocess +import copy from moulinette import m18n from yunohost.utils.error import YunohostError @@ -219,8 +220,8 @@ def user_create(operation_logger, username, firstname, lastname, mail, password, # Create group for user and add to group 'all_users' user_group_create(groupname=username, gid=uid, sync_perm=False) - user_group_update(groupname=username, add_user=username, force=True, sync_perm=False) - user_group_update(groupname='all_users', add_user=username, force=True, sync_perm=True) + user_group_update(groupname=username, add=username, force=True, sync_perm=False) + user_group_update(groupname='all_users', add=username, force=True, sync_perm=True) # TODO: Send a welcome mail to user logger.success(m18n.n('user_created')) @@ -610,84 +611,67 @@ def user_group_delete(operation_logger, groupname, force=False, sync_perm=True): @is_unit_operation([('groupname', 'user')]) -def user_group_update(operation_logger, groupname, add_user=None, remove_user=None, force=False, sync_perm=True): +def user_group_update(operation_logger, groupname, add=None, remove=None, force=False, sync_perm=True): """ Update user informations Keyword argument: groupname -- Groupname to update - add_user -- User to add in group - remove_user -- User to remove in group + add -- User(s) to add in group + remove -- User(s) to remove in group """ from yunohost.permission import permission_sync_to_user from yunohost.utils.ldap import _get_ldap_interface + # FIXME : we should also refuse to edit the main group of a user (e.g. group 'sam' related to user 'sam') + if (groupname == 'all_users' or groupname == 'admins') and not force: raise YunohostError('edit_group_not_allowed', group=groupname) ldap = _get_ldap_interface() - # Populate group informations - attrs_to_fetch = ['member'] - result = ldap.search(base='ou=groups,dc=yunohost,dc=org', - filter='cn=' + groupname, attrs=attrs_to_fetch) - if not result: - raise YunohostError('group_unknown', group=groupname) - group = result[0] + # We extract the uid for each member of the group to keep a simple flat list of members + current_group = user_group_info(groupname)["members"] + new_group = copy.copy(current_group) - new_group_list = {'member': set(), 'memberUid': set()} - if 'member' in group: - new_group_list['member'] = set(group['member']) - else: - group['member'] = [] + existing_users = user_list()['users'].keys() - existing_users = user_list(fields=['uid'])['users'].keys() + if add: + users_to_add = [add] if not isinstance(add, list) else add - if add_user: - if not isinstance(add_user, list): - add_user = [add_user] - - for user in add_user: + for user in users_to_add: if user not in existing_users: raise YunohostError('user_unknown', user=user) - for user in add_user: - userDN = "uid=" + user + ",ou=users,dc=yunohost,dc=org" - if userDN in group['member']: + if user in current_group: logger.warning(m18n.n('user_already_in_group', user=user, group=groupname)) - new_group_list['member'].add(userDN) - if remove_user: - if not isinstance(remove_user, list): - remove_user = [remove_user] + new_group += users_to_add - for user in remove_user: + if remove: + users_to_remove = [remove] if not isinstance(remove, list) else remove + + for user in users_to_remove: if user == groupname: + # FIXME : well if the user equals the group, why pass the two info... + # anyway we should just forbid this from the very beginning ... (editing a user-related group) raise YunohostError('remove_user_of_group_not_allowed', user=user, group=groupname) - for user in remove_user: - userDN = "uid=" + user + ",ou=users,dc=yunohost,dc=org" - if 'member' in group and userDN in group['member']: - new_group_list['member'].remove(userDN) - else: + if user not in current_group: logger.warning(m18n.n('user_not_in_group', user=user, group=groupname)) - # Sychronise memberUid with member (to keep the posix group structure) - # In posixgroup the main group of each user is only written in the gid number of the user - for member in new_group_list['member']: - member_Uid = member.split("=")[1].split(",")[0] - # Don't add main user in the group. - # Note that in the Unix system the main user of the group is linked by the gid in the user attribute. - # So the main user need to be not in the memberUid list of his own group. - if member_Uid != groupname: - new_group_list['memberUid'].add(member_Uid) + # Remove users_to_remove from new_group + # Kinda like a new_group -= users_to_remove + new_group = [u for u in new_group if u not in users_to_remove] + + new_group_dns = ["uid=" + user + ",ou=users,dc=yunohost,dc=org" for user in new_group] operation_logger.start() - if new_group_list['member'] != set(group['member']): - if not ldap.update('cn=%s,ou=groups' % groupname, new_group_list): + if set(new_group) != set(current_group): + if not ldap.update('cn=%s,ou=groups' % groupname, {"member": set(new_group_dns), "memberUid": set(new_group)}): raise YunohostError('group_update_failed', group=groupname) logger.success(m18n.n('group_updated', group=groupname)) @@ -705,26 +689,25 @@ def user_group_info(groupname): """ - from yunohost.utils.ldap import _get_ldap_interface + from yunohost.utils.ldap import _get_ldap_interface, _ldap_path_extract ldap = _get_ldap_interface() - group_attrs = [ - 'cn', 'member', 'permission' - ] - result = ldap.search('ou=groups,dc=yunohost,dc=org', "cn=" + groupname, group_attrs) + # Fetch info for this group + result = ldap.search('ou=groups,dc=yunohost,dc=org', + "cn=" + groupname, + ["cn", "member", "permission"]) if not result: raise YunohostError('group_unknown', group=groupname) - group = result[0] + infos = result[0] - result_dict = { - 'groupname': group['cn'][0], - 'member': None + # Format data + + return { + 'members': [_ldap_path_extract(p, "uid") for p in infos.get("member", [])], + 'permissions': [_ldap_path_extract(p, "cn") for p in infos.get("permission", [])] } - if 'member' in group: - result_dict['member'] = {m.split("=")[1].split(",")[0] for m in group['member']} - return result_dict # From 97c637f44c4b3d1d2625c1c34bdeece51ace0ea4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 11 Sep 2019 03:00:59 +0200 Subject: [PATCH 0082/3170] Fix group command descriptions in the actionmap --- data/actionsmap/yunohost.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index e727b87ef..d4940c043 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -201,11 +201,11 @@ user: subcategories: group: - subcategory_help: Manage group + subcategory_help: Manage user groups actions: ### user_group_list() list: - action_help: List group + action_help: List existing groups api: GET /users/groups arguments: -n: @@ -223,7 +223,7 @@ user: api: POST /users/groups arguments: groupname: - help: The unique group name to add + help: Name of the group to be created extra: pattern: &pattern_groupname - !!str ^[a-z0-9_]+$ @@ -235,7 +235,7 @@ user: api: DELETE /users/groups/ arguments: groupname: - help: Username to delete + help: Name of the group to be deleted extra: pattern: *pattern_groupname @@ -245,7 +245,7 @@ user: api: PUT /users/groups/ arguments: groupname: - help: Username to update + help: Name of the group to be updated extra: pattern: *pattern_groupname -a: @@ -265,11 +265,11 @@ user: ### user_group_info() info: - action_help: Get group information + action_help: Get information for a specific group api: GET /users/groups/ arguments: groupname: - help: Groupname to get information + help: Name of the group to get info about extra: pattern: *pattern_username From 112976f8ee17e1ebb0c74edb2fbe5c9dc6441fa3 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 11 Sep 2019 03:30:51 +0200 Subject: [PATCH 0083/3170] Refuse to edit user primary groups --- locales/en.json | 5 ++--- src/yunohost/user.py | 43 ++++++++++++++++++++++++------------------- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/locales/en.json b/locales/en.json index 815c40b75..d3abf4fd0 100644 --- a/locales/en.json +++ b/locales/en.json @@ -196,7 +196,6 @@ "dyndns_registration_failed": "Unable to register DynDNS domain: {error:s}", "dyndns_domain_not_provided": "Dyndns provider {provider:s} cannot provide domain {domain:s}.", "dyndns_unavailable": "Domain {domain:s} is not available.", - "edit_group_not_allowed": "You are not allowed to edit the group {group:s}", "edit_permission_with_group_all_users_not_allowed": "You are not allowed to edit permission for group 'all_users', use 'yunohost user permission clear APP' or 'yunohost user permission add APP -u USER' instead.", "error_when_removing_sftpuser_group": "Error when trying remove sftpusers group", "executing_command": "Executing command '{command:s}'…", @@ -235,9 +234,10 @@ "group_name_already_exist": "Group {name:s} already exist", "group_created": "Group '{group}' successfully created", "group_creation_failed": "Group creation failed for group '{group}'", + "group_cannot_be_edited": "The group {group:s} cannot be edited manually.", + "group_cannot_be_deleted": "The group {group:s} cannot be deleted manually.", "group_deleted": "Group '{group}' deleted", "group_deletion_failed": "Group '{group} 'deletion failed", - "group_deletion_not_allowed": "The group {group:s} cannot be deleted manually.", "group_info_failed": "Group info failed", "group_unknown": "Group {group:s} unknown", "group_updated": "Group '{group}' updated", @@ -449,7 +449,6 @@ "port_unavailable": "Port {port:d} is not available", "recommend_to_add_first_user": "The post-install is finished but YunoHost needs at least one user to work correctly, you should add one using 'yunohost user create ' or the admin interface.", "remove_main_permission_not_allowed": "Removing the main permission is not allowed", - "remove_user_of_group_not_allowed": "You are not allowed to remove the user {user:s} in the group {group:s}", "regenconf_file_backed_up": "The configuration file '{conf}' has been backed up to '{backup}'", "regenconf_file_copy_failed": "Unable to copy the new configuration file '{new}' to '{conf}'", "regenconf_file_kept_back": "The configuration file '{conf}' is expected to be deleted by regen-conf (category {category}) but has been kept back.", diff --git a/src/yunohost/user.py b/src/yunohost/user.py index a1a671ccf..8427cbd42 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -219,8 +219,7 @@ def user_create(operation_logger, username, firstname, lastname, mail, password, exc_info=1) # Create group for user and add to group 'all_users' - user_group_create(groupname=username, gid=uid, sync_perm=False) - user_group_update(groupname=username, add=username, force=True, sync_perm=False) + user_group_create(groupname=username, gid=uid, primary_group=True, sync_perm=False) user_group_update(groupname='all_users', add=username, force=True, sync_perm=True) # TODO: Send a welcome mail to user @@ -533,7 +532,7 @@ def user_group_list(names_only=False, full=False): @is_unit_operation([('groupname', 'user')]) -def user_group_create(operation_logger, groupname, gid=None, sync_perm=True): +def user_group_create(operation_logger, groupname, gid=None, primary_group=False, sync_perm=True): """ Create group @@ -575,6 +574,12 @@ def user_group_create(operation_logger, groupname, gid=None, sync_perm=True): 'gidNumber': gid, } + # Here we handle the creation of a primary group + # We want to initialize this group to contain the corresponding user + # (then we won't be able to add/remove any user in this group) + if primary_group: + attr_dict["member"] = ["uid=" + groupname + ",ou=users,dc=yunohost,dc=org"] + if ldap.add('cn=%s,ou=groups' % groupname, attr_dict): logger.success(m18n.n('group_created', group=groupname)) if sync_perm: @@ -596,9 +601,14 @@ def user_group_delete(operation_logger, groupname, force=False, sync_perm=True): from yunohost.permission import permission_sync_to_user from yunohost.utils.ldap import _get_ldap_interface - forbidden_groups = ["all_users", "admins"] + user_list(fields=['uid'])['users'].keys() - if not force and groupname in forbidden_groups: - raise YunohostError('group_deletion_not_allowed', group=groupname) + # Refuse to delete primary groups of a user (e.g. group 'sam' related to user 'sam') + # without the force option... + # + # We also can't delete "all_users" because that's a special group... + existing_users = user_list()['users'].keys() + undeletable_groups = existing_users + ["all_users", "admins"] + if groupname in undeletable_groups and not force: + raise YunohostError('group_cannot_be_deleted', group=groupname) operation_logger.start() ldap = _get_ldap_interface() @@ -625,19 +635,18 @@ def user_group_update(operation_logger, groupname, add=None, remove=None, force= from yunohost.permission import permission_sync_to_user from yunohost.utils.ldap import _get_ldap_interface - # FIXME : we should also refuse to edit the main group of a user (e.g. group 'sam' related to user 'sam') - - if (groupname == 'all_users' or groupname == 'admins') and not force: - raise YunohostError('edit_group_not_allowed', group=groupname) - - ldap = _get_ldap_interface() + # Refuse to edit a primary group of a user (e.g. group 'sam' related to user 'sam') + # Those kind of group should only ever contain the user (e.g. sam) and only this one. + # We also can't edit "all_users" without the force option because that's a special group... + existing_users = user_list()['users'].keys() + uneditable_groups = existing_users + ["all_users", "admins"] + if groupname in uneditable_groups and not force: + raise YunohostError('group_cannot_be_edited', group=groupname) # We extract the uid for each member of the group to keep a simple flat list of members current_group = user_group_info(groupname)["members"] new_group = copy.copy(current_group) - existing_users = user_list()['users'].keys() - if add: users_to_add = [add] if not isinstance(add, list) else add @@ -654,11 +663,6 @@ def user_group_update(operation_logger, groupname, add=None, remove=None, force= users_to_remove = [remove] if not isinstance(remove, list) else remove for user in users_to_remove: - if user == groupname: - # FIXME : well if the user equals the group, why pass the two info... - # anyway we should just forbid this from the very beginning ... (editing a user-related group) - raise YunohostError('remove_user_of_group_not_allowed', user=user, group=groupname) - if user not in current_group: logger.warning(m18n.n('user_not_in_group', user=user, group=groupname)) @@ -671,6 +675,7 @@ def user_group_update(operation_logger, groupname, add=None, remove=None, force= operation_logger.start() if set(new_group) != set(current_group): + ldap = _get_ldap_interface() if not ldap.update('cn=%s,ou=groups' % groupname, {"member": set(new_group_dns), "memberUid": set(new_group)}): raise YunohostError('group_update_failed', group=groupname) From 6276485665977aae1e6407714988ca354fad5779 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 11 Sep 2019 04:06:12 +0200 Subject: [PATCH 0084/3170] Simplify permission_list ... it really sounds like we don't need all these options --- data/actionsmap/yunohost.yml | 27 ++---------- locales/en.json | 1 - src/yunohost/backup.py | 6 +-- src/yunohost/permission.py | 79 +++++++----------------------------- src/yunohost/user.py | 6 +-- 5 files changed, 23 insertions(+), 96 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index d4940c043..2bca684cd 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -274,33 +274,12 @@ user: pattern: *pattern_username permission: - subcategory_help: Manage user permission + subcategory_help: Manage permissions actions: ### user_permission_list() list: - action_help: List access to user and group - api: GET /users/permissions/ - arguments: - -a: - full: --app - help: Application to manage the permission - nargs: "*" - metavar: APP - -p: - full: --permission - help: Name of permission (main by default) - nargs: "*" - metavar: PERMISSION - -u: - full: --username - help: Username - nargs: "*" - metavar: USER - -g: - full: --group - help: Group name - nargs: "*" - metavar: GROUP + action_help: List permissions and corresponding accesses + api: GET /users/permissions/ ### user_permission_add() add: diff --git a/locales/en.json b/locales/en.json index d3abf4fd0..b02bf2238 100644 --- a/locales/en.json +++ b/locales/en.json @@ -438,7 +438,6 @@ "permission_deleted": "Permission '{permission:s}' for app {app:s} deleted", "permission_deletion_failed": "Permission '{permission:s}' for app {app:s} deletion failed", "permission_not_found": "Permission '{permission:s}' not found for application {app:s}", - "permission_name_not_valid": "Permission name '{permission:s}' not valid", "permission_update_failed": "Permission update failed", "permission_generated": "The permission database has been updated", "permission_updated": "Permission '{permission:s}' for app {app:s} updated", diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index f96146ea0..fdbd8c62c 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -1256,10 +1256,8 @@ class RestoreManager(): # Restore permission for the app which is installed for per in old_apps_permission: - try: - permission_name, app_name = per['cn'][0].split('.') - except: - logger.warning(m18n.n('permission_name_not_valid', permission=per['cn'][0])) + # FIXME : will come here later to fix this following previous commits ... + permission_name, app_name = per['cn'][0].split('.') if _is_installed(app_name): if not ldap.add('cn=%s,ou=permission' % per['cn'][0], per): raise YunohostError('apps_permission_restoration_failed', permission=permission_name, app=app_name) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 0b77a3e5c..fbb43e8b3 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -42,79 +42,30 @@ logger = getActionLogger('yunohost.user') # -def user_permission_list(app=None, permission=None, username=None, group=None): +def user_permission_list(): """ - List permission for specific application - - Keyword argument: - app -- an application OR sftp, xmpp (metronome), mail - permission -- name of the permission ("main" by default) - username -- Username to get informations - group -- Groupname to get informations + List permissions and corresponding accesses """ - from yunohost.utils.ldap import _get_ldap_interface + from yunohost.utils.ldap import _get_ldap_interface, _ldap_path_extract + + # Fetch all permissions objects ldap = _get_ldap_interface() - - permission_attrs = [ - 'cn', - 'groupPermission', - 'inheritPermission', - 'URL', - ] - - # Normally app is alway defined but it should be possible to set it - if app and not isinstance(app, list): - app = [app] - if permission and not isinstance(permission, list): - permission = [permission] - if not isinstance(username, list): - username = [username] - if not isinstance(group, list): - group = [group] + permissions_infos = ldap.search('ou=permission,dc=yunohost,dc=org', + '(objectclass=permissionYnh)', + ['cn', 'groupPermission', 'inheritPermission', 'URL']) permissions = {} + for infos in permissions_infos: - result = ldap.search('ou=permission,dc=yunohost,dc=org', - '(objectclass=permissionYnh)', permission_attrs) + name = infos['cn'][0] - for res in result: - try: - permission_name, app_name = res['cn'][0].split('.') - except: - logger.warning(m18n.n('permission_name_not_valid', permission=res['cn'][0])) - group_name = [] - if 'groupPermission' in res: - for g in res['groupPermission']: - group_name.append(g.split("=")[1].split(",")[0]) - user_name = [] - if 'inheritPermission' in res: - for u in res['inheritPermission']: - user_name.append(u.split("=")[1].split(",")[0]) - - # Don't show the result if the user defined a specific permission, user or group - if app and app_name not in app: - continue - if permission and permission_name not in permission: - continue - if username[0] and not set(username) & set(user_name): - continue - if group[0] and not set(group) & set(group_name): - continue - - if app_name not in permissions: - permissions[app_name] = {} - - permissions[app_name][permission_name] = {'allowed_users': [], 'allowed_groups': []} - for g in group_name: - permissions[app_name][permission_name]['allowed_groups'].append(g) - for u in user_name: - permissions[app_name][permission_name]['allowed_users'].append(u) - if 'URL' in res: - permissions[app_name][permission_name]['URL'] = [] - for u in res['URL']: - permissions[app_name][permission_name]['URL'].append(u) + permissions[name] = { + "allowed_users": [_ldap_path_extract(p, "uid") for p in infos.get('inheritPermission', [])], + "allowed_groups": [_ldap_path_extract(p, "cn") for p in infos.get('groupPermission', [])], + "urls": infos.get("URL", []) + } return {'permissions': permissions} diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 8427cbd42..3eb329f4e 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -453,7 +453,7 @@ def user_info(username): if service_status("dovecot")["status"] != "running": logger.warning(m18n.n('mailbox_used_space_dovecot_down')) - elif not user_permission_list(app="mail", permission="main", username=username)['permissions']: + elif username not in user_permission_list()["permissions"]["mail.main"]["allowed_users"]: logger.warning(m18n.n('mailbox_disabled', user=username)) else: cmd = 'doveadm -f flow quota get -u %s' % user['uid'][0] @@ -719,9 +719,9 @@ def user_group_info(groupname): # Permission subcategory # -def user_permission_list(app=None, permission=None, username=None, group=None, sync_perm=True): +def user_permission_list(): import yunohost.permission - return yunohost.permission.user_permission_list(app, permission, username, group) + return yunohost.permission.user_permission_list() @is_unit_operation([('app', 'user')]) From 41e6f1b81cdf032949f960fb7095a697d4af4256 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 11 Sep 2019 14:37:15 +0200 Subject: [PATCH 0085/3170] Simplify permission_add/remove to just permission_update with --add and --remove, similar to what's done for groups --- data/actionsmap/yunohost.yml | 63 +++--------- locales/en.json | 6 +- src/yunohost/permission.py | 180 ++++++++++++----------------------- src/yunohost/user.py | 18 +--- 4 files changed, 84 insertions(+), 183 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 2bca684cd..ebdd2b982 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -276,64 +276,31 @@ user: permission: subcategory_help: Manage permissions actions: + ### user_permission_list() list: action_help: List permissions and corresponding accesses api: GET /users/permissions/ - ### user_permission_add() - add: - action_help: Grant access right to users and group - api: POST /users/permissions/ + ### user_permission_update() + update: + action_help: Grant / remove permissions to groups or users + api: POST /users/permissions/ arguments: - app: - help: Application to manage the permission - nargs: "+" - -p: - full: --permission - help: Name of permission (main by default) + permission: + help: Permission to manage (e.g. mail.main or wordpress.editors) + -a: + full: --add + help: Group or user names to add to this permission nargs: "*" - metavar: PERMISSION - -u: - full: --username - help: Username - nargs: "*" - metavar: USER + metavar: GROUP_OR_USER extra: pattern: *pattern_username - -g: - full: --group - help: Group name + -r: + full: --remove + help: Group or user names to remove from this permission nargs: "*" - metavar: GROUP - extra: - pattern: *pattern_username - - ### user_permission_remove() - remove: - action_help: Revoke access right to users and group - api: PUT /users/permissions/ - arguments: - app: - help: Application to manage the permission - nargs: "+" - -p: - full: --permission - help: Name of permission (main by default) - nargs: "*" - metavar: PERMISSION - -u: - full: --username - help: Username - nargs: "*" - metavar: USER - extra: - pattern: *pattern_username - -g: - full: --group - help: Group name - nargs: "*" - metavar: GROUP + metavar: GROUP_OR_USER extra: pattern: *pattern_username diff --git a/locales/en.json b/locales/en.json index b02bf2238..725bb1f8c 100644 --- a/locales/en.json +++ b/locales/en.json @@ -196,7 +196,6 @@ "dyndns_registration_failed": "Unable to register DynDNS domain: {error:s}", "dyndns_domain_not_provided": "Dyndns provider {provider:s} cannot provide domain {domain:s}.", "dyndns_unavailable": "Domain {domain:s} is not available.", - "edit_permission_with_group_all_users_not_allowed": "You are not allowed to edit permission for group 'all_users', use 'yunohost user permission clear APP' or 'yunohost user permission add APP -u USER' instead.", "error_when_removing_sftpuser_group": "Error when trying remove sftpusers group", "executing_command": "Executing command '{command:s}'…", "executing_script": "Executing script '{script:s}'…", @@ -229,8 +228,8 @@ "global_settings_unknown_type": "Unexpected situation, the setting {setting:s} appears to have the type {unknown_type:s} but it's not a type supported by the system.", "good_practices_about_admin_password": "You are now about to define a new administration password. The password should be at least 8 characters - though it is good practice to use longer password (i.e. a passphrase) and/or to use various kind of characters (uppercase, lowercase, digits and special characters).", "good_practices_about_user_password": "You are now about to define a new user password. The password should be at least 8 characters - though it is good practice to use longer password (i.e. a passphrase) and/or to use various kind of characters (uppercase, lowercase, digits and special characters).", - "group_already_allowed": "Group '{group:s}' already has permission '{permission:s}' enabled for app '{app:s}'", - "group_already_disallowed": "Group '{group:s}' already has permissions '{permission:s}' disabled for app '{app:s}'", + "group_already_allowed": "Group '{group:s}' already has permission '{permission:s}' enabled'", + "group_already_disallowed": "Group '{group:s}' already has permission '{permission:s}' disabled'", "group_name_already_exist": "Group {name:s} already exist", "group_created": "Group '{group}' successfully created", "group_creation_failed": "Group creation failed for group '{group}'", @@ -397,7 +396,6 @@ "mysql_db_creation_failed": "MySQL database creation failed", "mysql_db_init_failed": "MySQL database init failed", "mysql_db_initialized": "The MySQL database has been initialized", - "need_define_permission_before": "You need to redefine the permission using 'yunohost user permission add -u USER' before removing an allowed group", "network_check_mx_ko": "DNS MX record is not set", "network_check_smtp_ko": "Outbound mail (SMTP port 25) seems to be blocked by your network", "network_check_smtp_ok": "Outbound mail (SMTP port 25) is not blocked", diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index fbb43e8b3..20c34ada8 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -24,6 +24,7 @@ Manage permissions """ +import copy import grp import random @@ -45,7 +46,6 @@ logger = getActionLogger('yunohost.user') def user_permission_list(): """ List permissions and corresponding accesses - """ from yunohost.utils.ldap import _get_ldap_interface, _ldap_path_extract @@ -70,146 +70,92 @@ def user_permission_list(): return {'permissions': permissions} -def user_permission_update(operation_logger, app=[], permission=None, add_username=None, add_group=None, del_username=None, del_group=None, sync_perm=True): +def user_permission_update(operation_logger, permission, add=None, remove=None, sync_perm=True): """ Allow or Disallow a user or group to a permission for a specific application Keyword argument: - app -- an application OR sftp, xmpp (metronome), mail - permission -- name of the permission ("main" by default) - add_username -- Username to allow - add_group -- Groupname to allow - del_username -- Username to disallow - del_group -- Groupname to disallow - + permission -- Name of the permission (e.g. mail.mail or wordpress.editors) + add -- List of groups or usernames to add to this permission + remove -- List of groups or usernames to remove from to this permission """ from yunohost.hook import hook_callback from yunohost.user import user_group_list - from yunohost.utils.ldap import _get_ldap_interface + from yunohost.utils.ldap import _get_ldap_interface, _ldap_path_extract ldap = _get_ldap_interface() - if permission: - if not isinstance(permission, list): - permission = [permission] - else: - permission = ["main"] + # Fetch currently allowed groups for this permission - if add_group: - if not isinstance(add_group, list): - add_group = [add_group] - else: - add_group = [] - - if add_username: - if not isinstance(add_username, list): - add_username = [add_username] - else: - add_username = [] - - if del_group: - if not isinstance(del_group, list): - del_group = [del_group] - else: - del_group = [] - - if del_username: - if not isinstance(del_username, list): - del_username = [del_username] - else: - del_username = [] - - # Validate that the group exist - for g in add_group: - if g not in user_group_list()['groups']: - raise YunohostError('group_unknown', group=g) - for u in add_username: - if u not in user_list(['uid'])['users']: - raise YunohostError('user_unknown', user=u) - for g in del_group: - if g not in user_group_list()['groups']: - raise YunohostError('group_unknown', group=g) - for u in del_username: - if u not in user_list(['uid'])['users']: - raise YunohostError('user_unknown', user=u) - - # Merge user and group (note that we consider all user as a group) - add_group.extend(add_username) - del_group.extend(del_username) - - if 'all_users' in add_group or 'all_users' in del_group: - raise YunohostError('edit_permission_with_group_all_users_not_allowed') - - # Populate permission informations - permission_attrs = [ - 'cn', - 'groupPermission', - ] result = ldap.search('ou=permission,dc=yunohost,dc=org', - '(objectclass=permissionYnh)', permission_attrs) + '(objectclass=permissionYnh)', + ["cn", "groupPermission"]) result = {p['cn'][0]: p for p in result} + if permission not in result: + raise YunohostError('permission_not_found', permission=permission) - new_per_dict = {} + current_allowed_groups = [_ldap_path_extract(p, "cn") for p in result[permission].get("groupPermission", [])] - for a in app: - for per in permission: - permission_name = per + '.' + a - if permission_name not in result: - raise YunohostError('permission_not_found', permission=per, app=a) - new_per_dict[permission_name] = set() - if 'groupPermission' in result[permission_name]: - new_per_dict[permission_name] = set(result[permission_name]['groupPermission']) + # Compute new allowed group list (and make sure what we're doing make sense) - for g in del_group: - if 'cn=all_users,ou=groups,dc=yunohost,dc=org' in new_per_dict[permission_name]: - raise YunohostError('need_define_permission_before') - group_name = 'cn=' + g + ',ou=groups,dc=yunohost,dc=org' - if group_name not in new_per_dict[permission_name]: - logger.warning(m18n.n('group_already_disallowed', permission=per, app=a, group=g)) - else: - new_per_dict[permission_name].remove(group_name) + new_allowed_groups = copy.copy(current_allowed_groups) - if 'cn=all_users,ou=groups,dc=yunohost,dc=org' in new_per_dict[permission_name]: - new_per_dict[permission_name].remove('cn=all_users,ou=groups,dc=yunohost,dc=org') - for g in add_group: - group_name = 'cn=' + g + ',ou=groups,dc=yunohost,dc=org' - if group_name in new_per_dict[permission_name]: - logger.warning(m18n.n('group_already_allowed', permission=per, app=a, group=g)) - else: - new_per_dict[permission_name].add(group_name) + if add: + existing_groups = user_group_list()['groups'].keys() + groups_to_add = [add] if not isinstance(add, list) else add + for group in groups_to_add: + if group not in existing_groups: + raise YunohostError('group_unknown', group=group) + if group in current_allowed_groups: + logger.warning(m18n.n('group_already_allowed', permission=permission, group=group)) + new_allowed_groups += groups_to_add + + if remove: + groups_to_remove = [remove] if not isinstance(remove, list) else remove + for group in groups_to_remove: + if group not in existing_groups: + raise YunohostError('group_unknown', group=group) + if group not in current_allowed_groups: + logger.warning(m18n.n('group_already_disallowed', permission=permission, group=group)) + + new_allowed_groups = [g for g in new_allowed_groups if g not in groups_to_remove] + + # If we end up with something like allowed groups is ["all_users", "volunteers"] + # we shall warn the users that they should probably choose between one or the other, + # because the current situation is probably not what they expect / is temporary ? + + if len(new_allowed_groups) > 1 and "all_users" in new_allowed_groups: + # FIXME : write a better explanation + logger.warning("This permission is currently enabled for all users in addition to other groups. You probably want to either remove the 'all_users' permission or remove the specific groups currently allowed.") + + # Commit the new allowed group list operation_logger.start() - for per, val in new_per_dict.items(): - # Don't update LDAP if we update exactly the same values - if val == set(result[per]['groupPermission'] if 'groupPermission' in result[per] else []): - continue - if ldap.update('cn=%s,ou=permission' % per, {'groupPermission': val}): - p = per.split('.') - logger.debug(m18n.n('permission_updated', permission=p[0], app=p[1])) - else: - raise YunohostError('permission_update_failed') + # Don't update LDAP if we update exactly the same values + if set(new_allowed_groups) == set(current_allowed_groups): + logger.warning("No change was applied because not relevant modification were found") + elif ldap.update('cn=%s,ou=permission' % permission, + {'groupPermission': ['cn=' + g + ',ou=groups,dc=yunohost,dc=org' for g in new_allowed_groups]}): + logger.debug(m18n.n('permission_updated', permission=permission)) - if sync_perm: - permission_sync_to_user() + # Trigger permission sync if asked - for a in app: - allowed_users = set() - disallowed_users = set() - group_list = user_group_list()['groups'] + if sync_perm: + permission_sync_to_user() - for g in add_group: - allowed_users.union(group_list[g]['members']) - for g in del_group: - disallowed_users.union(group_list[g]['members']) + # Trigger app callbacks - allowed_users = ','.join(allowed_users) - disallowed_users = ','.join(disallowed_users) - if add_group: - hook_callback('post_app_addaccess', args=[app, allowed_users]) - if del_group: - hook_callback('post_app_removeaccess', args=[app, disallowed_users]) + # FIXME FIXME FIXME - return user_permission_list(app, permission) + #if groups_to_add: + # hook_callback('post_app_addaccess', args=[app, allowed_users]) + #if groups_to_remove: + # hook_callback('post_app_removeaccess', args=[app, disallowed_users]) + + else: + raise YunohostError('permission_update_failed') + + return user_permission_list()["permissions"][permission] def user_permission_clear(operation_logger, app=[], permission=None, sync_perm=True): diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 3eb329f4e..92bdcf7a4 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -724,21 +724,11 @@ def user_permission_list(): return yunohost.permission.user_permission_list() -@is_unit_operation([('app', 'user')]) -def user_permission_add(operation_logger, app, permission="main", username=None, group=None, sync_perm=True): +@is_unit_operation([('permission', 'user')]) +def user_permission_update(operation_logger, permission, add=None, remove=None, sync_perm=True): import yunohost.permission - return yunohost.permission.user_permission_update(operation_logger, app, permission=permission, - add_username=username, add_group=group, - del_username=None, del_group=None, - sync_perm=sync_perm) - - -@is_unit_operation([('app', 'user')]) -def user_permission_remove(operation_logger, app, permission="main", username=None, group=None, sync_perm=True): - import yunohost.permission - return yunohost.permission.user_permission_update(operation_logger, app, permission=permission, - add_username=None, add_group=None, - del_username=username, del_group=group, + return yunohost.permission.user_permission_update(operation_logger, permission, + add=add, remove=remove, sync_perm=sync_perm) From 45483f4116e487be9d344fadff222007087a28e7 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 11 Sep 2019 15:16:09 +0200 Subject: [PATCH 0086/3170] --short and --full options for group_list and permission_list --- data/actionsmap/yunohost.yml | 18 ++++++++++++++---- src/yunohost/permission.py | 24 +++++++++++++++--------- src/yunohost/user.py | 28 +++++++++++----------------- 3 files changed, 40 insertions(+), 30 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index ebdd2b982..ece642d0d 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -208,13 +208,13 @@ user: action_help: List existing groups api: GET /users/groups arguments: - -n: - full: --names-only - help: Only list the name of the groups without any additional info + -s: + full: --short + help: List only the names of groups action: store_true -f: full: --full - help: List all the info available for each groups + help: Display all informations known about each groups action: store_true ### user_group_create() @@ -281,6 +281,16 @@ user: list: action_help: List permissions and corresponding accesses api: GET /users/permissions/ + arguments: + -s: + full: --short + help: List only the names of permissions + action: store_true + -f: + full: --full + help: Display all informations known about each permissions, including the full list of users corresponding to allowed groups. + action: store_true + ### user_permission_update() update: diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 20c34ada8..ab79ff7ed 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -43,29 +43,35 @@ logger = getActionLogger('yunohost.user') # -def user_permission_list(): +def user_permission_list(short=False, full=False): """ List permissions and corresponding accesses """ - from yunohost.utils.ldap import _get_ldap_interface, _ldap_path_extract + # Fetch relevant informations - # Fetch all permissions objects + from yunohost.utils.ldap import _get_ldap_interface, _ldap_path_extract ldap = _get_ldap_interface() permissions_infos = ldap.search('ou=permission,dc=yunohost,dc=org', '(objectclass=permissionYnh)', - ['cn', 'groupPermission', 'inheritPermission', 'URL']) + ["cn", 'groupPermission', 'inheritPermission', 'URL']) + + # Parse / organize information to be outputed permissions = {} for infos in permissions_infos: name = infos['cn'][0] + permissions[name] = {} - permissions[name] = { - "allowed_users": [_ldap_path_extract(p, "uid") for p in infos.get('inheritPermission', [])], - "allowed_groups": [_ldap_path_extract(p, "cn") for p in infos.get('groupPermission', [])], - "urls": infos.get("URL", []) - } + permissions[name]["allowed"] = [_ldap_path_extract(p, "cn") for p in infos.get('groupPermission', [])] + + if full: + permissions[name]["corresponding_users"] = [_ldap_path_extract(p, "uid") for p in infos.get('inheritPermission', [])], + permissions[name]["urls"] = infos.get("URL", []) + + if short: + permissions = permissions.keys() return {'permissions': permissions} diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 92bdcf7a4..80f558809 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -489,12 +489,12 @@ def user_info(username): # # Group subcategory # -def user_group_list(names_only=False, full=False): +def user_group_list(short=False, full=False): """ List users Keyword argument: - names-only -- Only list the name of the groups without any additional info + short -- Only list the name of the groups without any additional info full -- List all the info available for each groups """ @@ -502,30 +502,24 @@ def user_group_list(names_only=False, full=False): from yunohost.utils.ldap import _get_ldap_interface, _ldap_path_extract ldap = _get_ldap_interface() - - if names_only: - fields_to_fetch = ["cn"] - elif full: - fields_to_fetch = ["cn", "member", "permission"] - else: - fields_to_fetch = ["cn", "member"] - groups_infos = ldap.search('ou=groups,dc=yunohost,dc=org', '(objectclass=groupOfNamesYnh)', - fields_to_fetch) + ["cn", "member", "permission"]) # Parse / organize information to be outputed groups = {} for infos in groups_infos: + name = infos["cn"][0] groups[name] = {} - if "member" in fields_to_fetch: - groups[name]["members"] = [_ldap_path_extract(p, "uid") for p in infos.get("member", [])] - if "permission" in fields_to_fetch: + + groups[name]["members"] = [_ldap_path_extract(p, "uid") for p in infos.get("member", [])] + + if full: groups[name]["permissions"] = [_ldap_path_extract(p, "cn") for p in infos.get("permission", [])] - if names_only: + if short: groups = groups.keys() return {'groups': groups} @@ -719,9 +713,9 @@ def user_group_info(groupname): # Permission subcategory # -def user_permission_list(): +def user_permission_list(short=False, full=False): import yunohost.permission - return yunohost.permission.user_permission_list() + return yunohost.permission.user_permission_list(short, full) @is_unit_operation([('permission', 'user')]) From e5676c4b30db38f63ec740ac987aa697f473173a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 11 Sep 2019 15:16:26 +0200 Subject: [PATCH 0087/3170] Propagate change in permission_list to permission_update --- src/yunohost/permission.py | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index ab79ff7ed..3a6bde077 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -92,33 +92,31 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, # Fetch currently allowed groups for this permission - result = ldap.search('ou=permission,dc=yunohost,dc=org', - '(objectclass=permissionYnh)', - ["cn", "groupPermission"]) - result = {p['cn'][0]: p for p in result} - if permission not in result: + permissions = user_permission_list(full=True)["permissions"] + if permission not in permissions: raise YunohostError('permission_not_found', permission=permission) - current_allowed_groups = [_ldap_path_extract(p, "cn") for p in result[permission].get("groupPermission", [])] + current_allowed_groups = permissions[permission]["allowed"] + all_existing_groups = user_group_list()['groups'].keys() # Compute new allowed group list (and make sure what we're doing make sense) new_allowed_groups = copy.copy(current_allowed_groups) if add: - existing_groups = user_group_list()['groups'].keys() groups_to_add = [add] if not isinstance(add, list) else add for group in groups_to_add: - if group not in existing_groups: + if group not in all_existing_groups: raise YunohostError('group_unknown', group=group) if group in current_allowed_groups: logger.warning(m18n.n('group_already_allowed', permission=permission, group=group)) + new_allowed_groups += groups_to_add if remove: groups_to_remove = [remove] if not isinstance(remove, list) else remove for group in groups_to_remove: - if group not in existing_groups: + if group not in all_existing_groups: raise YunohostError('group_unknown', group=group) if group not in current_allowed_groups: logger.warning(m18n.n('group_already_disallowed', permission=permission, group=group)) @@ -130,7 +128,8 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, # because the current situation is probably not what they expect / is temporary ? if len(new_allowed_groups) > 1 and "all_users" in new_allowed_groups: - # FIXME : write a better explanation + # FIXME : i18n + # FIXME : write a better explanation ? logger.warning("This permission is currently enabled for all users in addition to other groups. You probably want to either remove the 'all_users' permission or remove the specific groups currently allowed.") # Commit the new allowed group list @@ -139,6 +138,7 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, # Don't update LDAP if we update exactly the same values if set(new_allowed_groups) == set(current_allowed_groups): + # FIXME : i18n logger.warning("No change was applied because not relevant modification were found") elif ldap.update('cn=%s,ou=permission' % permission, {'groupPermission': ['cn=' + g + ',ou=groups,dc=yunohost,dc=org' for g in new_allowed_groups]}): @@ -149,20 +149,20 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, if sync_perm: permission_sync_to_user() + new_permission = user_permission_list(full=True)["permissions"][permission] + # Trigger app callbacks + app = permission.split(".")[0] + if add: + hook_callback('post_app_addaccess', args=[app, new_permission["corresponding_users"]]) + if remove: + hook_callback('post_app_removeaccess', args=[app, new_permission["corresponding_users"]]) - # FIXME FIXME FIXME - - #if groups_to_add: - # hook_callback('post_app_addaccess', args=[app, allowed_users]) - #if groups_to_remove: - # hook_callback('post_app_removeaccess', args=[app, disallowed_users]) + return new_permission else: raise YunohostError('permission_update_failed') - return user_permission_list()["permissions"][permission] - def user_permission_clear(operation_logger, app=[], permission=None, sync_perm=True): """ From a1d3376613993a8420373eec1b05bddc08a7720f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 11 Sep 2019 15:49:07 +0200 Subject: [PATCH 0088/3170] Simplify permission_clear, now named permission_reset --- data/actionsmap/yunohost.yml | 16 +++----- locales/en.json | 1 - src/yunohost/permission.py | 75 +++++++++++++----------------------- src/yunohost/user.py | 4 +- 4 files changed, 34 insertions(+), 62 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index ece642d0d..49dde373b 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -314,19 +314,13 @@ user: extra: pattern: *pattern_username - ## user_permission_clear() - clear: - action_help: Reset access rights for the app + ## user_permission_reset() + reset: + action_help: Reset allowed groups to the default (all_users) for a given permission api: DELETE /users/permissions/ arguments: - app: - help: Application to manage the permission - nargs: "+" - -p: - full: --permission - help: Name of permission (main by default) - nargs: "*" - metavar: PERMISSION + permission: + help: Permission to be resetted (e.g. mail.main or wordpress.editors) ssh: subcategory_help: Manage ssh access diff --git a/locales/en.json b/locales/en.json index 725bb1f8c..ebbb89fa8 100644 --- a/locales/en.json +++ b/locales/en.json @@ -429,7 +429,6 @@ "pattern_positive_number": "Must be a positive number", "pattern_username": "Must be lower-case alphanumeric and underscore characters only", "pattern_password_app": "Sorry, passwords should not contain the following characters: {forbidden_chars}", - "permission_already_clear": "Permission '{permission:s}' already clear for app {app:s}", "permission_already_exist": "Permission '{permission:s}' for app {app:s} already exist", "permission_created": "Permission '{permission:s}' for app {app:s} created", "permission_creation_failed": "Permission creation failed", diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 3a6bde077..8816ad950 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -152,11 +152,13 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, new_permission = user_permission_list(full=True)["permissions"][permission] # Trigger app callbacks - app = permission.split(".")[0] - if add: - hook_callback('post_app_addaccess', args=[app, new_permission["corresponding_users"]]) - if remove: - hook_callback('post_app_removeaccess', args=[app, new_permission["corresponding_users"]]) + # FIXME : this is not how this hook works... gotta compute the list of user actually added / removed + + #app = permission.split(".")[0] + #if add: + # hook_callback('post_app_addaccess', args=[app, new_permission["corresponding_users"]]) + #if remove: + # hook_callback('post_app_removeaccess', args=[app, new_permission["corresponding_users"]]) return new_permission @@ -164,63 +166,40 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, raise YunohostError('permission_update_failed') -def user_permission_clear(operation_logger, app=[], permission=None, sync_perm=True): +def user_permission_reset(operation_logger, permission, sync_perm=True): """ - Reset the permission for a specific application + Reset a given permission to just 'all_users' Keyword argument: - app -- an application OR sftp, xmpp (metronome), mail - permission -- name of the permission ("main" by default) - username -- Username to get informations (all by default) - group -- Groupname to get informations (all by default) - + permission -- The name of the permission to be reseted """ from yunohost.hook import hook_callback from yunohost.utils.ldap import _get_ldap_interface ldap = _get_ldap_interface() - if permission: - if not isinstance(permission, list): - permission = [permission] - else: - permission = ["main"] + # Fetch existing permission + + existing_permission = user_permission_list(full=True)["permissions"].get(permission, None) + if existing_permission is None: + raise YunohostError('permission_not_found', permission=permission) + + # Update permission with default (all_users) default_permission = {'groupPermission': ['cn=all_users,ou=groups,dc=yunohost,dc=org']} + if ldap.update('cn=%s,ou=permission' % permission, default_permission): + logger.debug(m18n.n('permission_updated', permission=permission)) + else: + raise YunohostError('permission_update_failed') - # Populate permission informations - permission_attrs = [ - 'cn', - 'groupPermission', - ] - result = ldap.search('ou=permission,dc=yunohost,dc=org', - '(objectclass=permissionYnh)', permission_attrs) - result = {p['cn'][0]: p for p in result} + if sync_perm: + permission_sync_to_user() - for a in app: - for per in permission: - permission_name = per + '.' + a - if permission_name not in result: - raise YunohostError('permission_not_found', permission=per, app=a) - if 'groupPermission' in result[permission_name] and 'cn=all_users,ou=groups,dc=yunohost,dc=org' in result[permission_name]['groupPermission']: - logger.warning(m18n.n('permission_already_clear', permission=per, app=a)) - continue - if ldap.update('cn=%s,ou=permission' % permission_name, default_permission): - logger.debug(m18n.n('permission_updated', permission=per, app=a)) - else: - raise YunohostError('permission_update_failed') + new_permission = user_permission_list(full=True)["permissions"][permission] - permission_sync_to_user() + # FIXME : trigger app callbacks + # app = permission.split(".")[0] - for a in app: - permission_name = 'main.' + a - result = ldap.search('ou=permission,dc=yunohost,dc=org', - filter='cn=' + permission_name, attrs=['inheritPermission']) - if result: - allowed_users = result[0]['inheritPermission'] - new_user_list = ','.join(allowed_users) - hook_callback('post_app_removeaccess', args=[app, new_user_list]) - - return user_permission_list(app, permission) + return new_permission # # diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 80f558809..2bf36cfd6 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -727,9 +727,9 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, @is_unit_operation([('app', 'user')]) -def user_permission_clear(operation_logger, app, permission=None, sync_perm=True): +def user_permission_reset(operation_logger, permission, sync_perm=True): import yunohost.permission - return yunohost.permission.user_permission_clear(operation_logger, app, permission, + return yunohost.permission.user_permission_reset(operation_logger, permission, sync_perm=sync_perm) From 3535cb655f17be09ede5157473b28cdc09060b2b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 11 Sep 2019 16:36:17 +0200 Subject: [PATCH 0089/3170] Fix call of app add/remove access hooks --- src/yunohost/permission.py | 42 +++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 8816ad950..fbfaa43a2 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -67,7 +67,7 @@ def user_permission_list(short=False, full=False): permissions[name]["allowed"] = [_ldap_path_extract(p, "cn") for p in infos.get('groupPermission', [])] if full: - permissions[name]["corresponding_users"] = [_ldap_path_extract(p, "uid") for p in infos.get('inheritPermission', [])], + permissions[name]["corresponding_users"] = [_ldap_path_extract(p, "uid") for p in infos.get('inheritPermission', [])] permissions[name]["urls"] = infos.get("URL", []) if short: @@ -92,11 +92,11 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, # Fetch currently allowed groups for this permission - permissions = user_permission_list(full=True)["permissions"] - if permission not in permissions: + existing_permission = user_permission_list(full=True)["permissions"].get(permission, None) + if existing_permission is None: raise YunohostError('permission_not_found', permission=permission) - current_allowed_groups = permissions[permission]["allowed"] + current_allowed_groups = existing_permission["allowed"] all_existing_groups = user_group_list()['groups'].keys() # Compute new allowed group list (and make sure what we're doing make sense) @@ -152,13 +152,19 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, new_permission = user_permission_list(full=True)["permissions"][permission] # Trigger app callbacks - # FIXME : this is not how this hook works... gotta compute the list of user actually added / removed - #app = permission.split(".")[0] - #if add: - # hook_callback('post_app_addaccess', args=[app, new_permission["corresponding_users"]]) - #if remove: - # hook_callback('post_app_removeaccess', args=[app, new_permission["corresponding_users"]]) + app = permission.split(".")[0] + + old_allowed_users = set(existing_permission["corresponding_users"]) + new_allowed_users = set(new_permission["corresponding_users"]) + + effectively_added_users = new_allowed_users - old_allowed_users + effectively_removed_users = old_allowed_users - new_allowed_users + + if effectively_added_users: + hook_callback('post_app_addaccess', args=[app, ','.join(effectively_added_users)]) + if effectively_removed_users: + hook_callback('post_app_removeaccess', args=[app, ','.join(effectively_removed_users)]) return new_permission @@ -196,8 +202,20 @@ def user_permission_reset(operation_logger, permission, sync_perm=True): new_permission = user_permission_list(full=True)["permissions"][permission] - # FIXME : trigger app callbacks - # app = permission.split(".")[0] + # Trigger app callbacks + + app = permission.split(".")[0] + + old_allowed_users = set(existing_permission["corresponding_users"]) + new_allowed_users = set(new_permission["corresponding_users"]) + + effectively_added_users = new_allowed_users - old_allowed_users + effectively_removed_users = old_allowed_users - new_allowed_users + + if effectively_added_users: + hook_callback('post_app_addaccess', args=[app, ','.join(effectively_added_users)]) + if effectively_removed_users: + hook_callback('post_app_removeaccess', args=[app, ','.join(effectively_removed_users)]) return new_permission From 574e9aea44a8e25078b506240bc7cedf155dd8e0 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 11 Sep 2019 18:13:30 +0200 Subject: [PATCH 0090/3170] Simplify permission_create/urls/delete interface and code --- data/helpers.d/setting | 57 +++++++-------- src/yunohost/permission.py | 138 ++++++++++++++++++------------------- 2 files changed, 93 insertions(+), 102 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index e6311fc1f..0e432d916 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -230,70 +230,63 @@ ynh_webpath_register () { # Create a new permission for the app # -# usage: ynh_permission_create --app "app" --permission "permission" --defaultdisallow [--urls "url" ["url" ...]] -# | arg: app - the application id +# usage: ynh_permission_create --permission "permission" [--urls "url" ["url" ...]] # | arg: permission - the name for the permission (by default a permission named "main" already exist) -# | arg: defaultdisallow - define if all user will be allowed by default -# | arg: urls - the list of urls for the the permission +# | arg: urls - (optional) a list of FULL urls for the permission (e.g. domain.tld/apps/admin) +# +# example: ynh_permission_create --permission admin --urls domain.tld/blog/admin ynh_permission_create() { - declare -Ar args_array=( [a]=app= [p]=permission= [d]=defaultdisallow [u]=urls= ) - local app + declare -Ar args_array=( [p]=permission= [u]=urls= ) local permission - local defaultdisallow local urls ynh_handle_getopts_args "$@" - if [[ -n ${defaultdisallow:-} ]]; then - defaultdisallow=",default_allow=False" - fi if [[ -n ${urls:-} ]]; then urls=",urls=['${urls//';'/"','"}']" fi - yunohost tools shell -c "from yunohost.permission import permission_create; permission_create('$app', '$permission' ${defaultdisallow:-} ${urls:-}, sync_perm=False)" + yunohost tools shell -c "from yunohost.permission import permission_create; permission_create('$app.$permission' ${urls:-}, sync_perm=False)" } # Remove a permission for the app (note that when the app is removed all permission is automatically removed) # -# usage: ynh_permission_remove --app "app" --permission "permission" -# | arg: app - the application id +# usage: ynh_permission_remove --permission "permission" # | arg: permission - the name for the permission (by default a permission named "main" is removed automatically when the app is removed) -ynh_permission_remove() { - declare -Ar args_array=( [a]=app= [p]=permission= ) - local app +# +# example: ynh_permission_delete --permission editors +ynh_permission_delete() { + declare -Ar args_array=( [p]=permission= ) local permission ynh_handle_getopts_args "$@" - yunohost tools shell -c "from yunohost.permission import permission_delete; permission_delete('$app', '$permission', sync_perm=False)" + yunohost tools shell -c "from yunohost.permission import permission_delete; permission_delete('$app.$permission', sync_perm=False)" } # Add a path managed by the SSO # -# usage: ynh_permission_add_url --app "app" --permission "permission" --url "url" ["url" ...] -# | arg: app - the application id -# | arg: permission - the name for the permission -# | arg: url - the FULL url for the the permission (ex domain.tld/apps/admin) +# usage: ynh_permission_add_url --permission "permission" --url "url" ["url" ...] +# | arg: permission - the name for the permission (by default a permission named "main" is removed automatically when the app is removed) +# | arg: urls - (optional) a list of FULL urls for the permission (e.g. domain.tld/apps/admin) +# ynh_permission_add_url() { - declare -Ar args_array=( [a]=app= [p]=permission= [u]=url= ) - local app + declare -Ar args_array=([p]=permission= [u]=urls= ) local permission - local url + local urls ynh_handle_getopts_args "$@" - yunohost tools shell -c "from yunohost.permission import permission_urls; permission_urls('$app', '$permission', add_url=['${url//';'/"','"}'], sync_perm=False)" + yunohost tools shell -c "from yunohost.permission import permission_urls; permission_urls('$app.$permission', add=['${urls//';'/"','"}'], sync_perm=False)" } # Remove a path managed by the SSO # # usage: ynh_permission_del_path --app "app" --permission "permission" --url "url" ["url" ...] -# | arg: app - the application id -# | arg: permission - the name for the permission -# | arg: url - the FULL url for the the permission (ex domain.tld/apps/admin) +# | arg: permission - the name for the permission (by default a permission named "main" is removed automatically when the app is removed) +# | arg: urls - (optional) a list of FULL urls for the permission (e.g. domain.tld/apps/admin) +# ynh_permission_remove_url() { - declare -Ar args_array=( [a]=app= [p]=permission= [u]=url= ) - local app + declare -Ar args_array=([p]=permission= [u]=urls= ) local permission - local url + local urls ynh_handle_getopts_args "$@" - yunohost tools shell -c "from yunohost.permission import permission_urls; permission_urls('$app', '$permission', remove_url=['${url//';'/"','"}'], sync_perm=False)" + yunohost tools shell -c "from yunohost.permission import permission_urls; permission_urls('$app.$permission', remove=['${url//';'/"','"}'], sync_perm=False)" } diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index fbfaa43a2..960c0853c 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -229,27 +229,22 @@ def user_permission_reset(operation_logger, permission, sync_perm=True): @is_unit_operation(['permission', 'app']) -def permission_create(operation_logger, app, permission, urls=None, default_allow=True, sync_perm=True): +def permission_create(operation_logger, permission, urls=None, sync_perm=True): """ Create a new permission for a specific application Keyword argument: - app -- an application OR sftp, xmpp (metronome), mail - permission -- name of the permission ("main" by default) + permission -- Name of the permission (e.g. nextcloud.main or wordpress.editors) urls -- list of urls to specify for the permission - """ - from yunohost.domain import _normalize_domain_path + from yunohost.utils.ldap import _get_ldap_interface ldap = _get_ldap_interface() # Validate uniqueness of permission in LDAP - permission_name = str(permission + '.' + app) # str(...) Fix encoding issue - conflict = ldap.get_conflict({ - 'cn': permission_name - }, base_dn='ou=permission,dc=yunohost,dc=org') - if conflict: - raise YunohostError('permission_already_exist', permission=permission, app=app) + if ldap.get_conflict({'cn': permission}, + base_dn='ou=permission,dc=yunohost,dc=org'): + raise YunohostError('permission_already_exist', permission=permission) # Get random GID all_gid = {x.gr_gid for x in grp.getgrall()} @@ -261,110 +256,106 @@ def permission_create(operation_logger, app, permission, urls=None, default_allo attr_dict = { 'objectClass': ['top', 'permissionYnh', 'posixGroup'], - 'cn': permission_name, + 'cn': permission, 'gidNumber': gid, } - if default_allow: + + # For main permission, we add all users by default + if permission.endswith(".main"): attr_dict['groupPermission'] = 'cn=all_users,ou=groups,dc=yunohost,dc=org' if urls: - attr_dict['URL'] = [] - for url in urls: - domain = url[:url.index('/')] - path = url[url.index('/'):] - domain, path = _normalize_domain_path(domain, path) - attr_dict['URL'].append(domain + path) + attr_dict['URL'] = [_normalize_url(url) for url in urls] operation_logger.start() - if ldap.add('cn=%s,ou=permission' % permission_name, attr_dict): + if ldap.add('cn=%s,ou=permission' % permission, attr_dict): if sync_perm: permission_sync_to_user() - logger.debug(m18n.n('permission_created', permission=permission, app=app)) - return user_permission_list(app, permission) - - raise YunohostError('permission_creation_failed') + logger.debug(m18n.n('permission_created', permission=permission)) + return user_permission_list(full=True)["permissions"][permission] + else: + raise YunohostError('permission_creation_failed') @is_unit_operation(['permission', 'app']) -def permission_urls(operation_logger, app, permission, add_url=None, remove_url=None, sync_perm=True): +def permission_urls(operation_logger, permission, add=None, remove=None, sync_perm=True): """ Update urls related to a permission for a specific application Keyword argument: - app -- an application OR sftp, xmpp (metronome), mail - permission -- name of the permission ("main" by default) - add_url -- Add a new url for a permission - remove_url -- Remove a url for a permission + permission -- Name of the permission (e.g. nextcloud.main or wordpress.editors) + add -- List of urls to add + remove -- List of urls to remove """ - from yunohost.domain import _normalize_domain_path from yunohost.utils.ldap import _get_ldap_interface ldap = _get_ldap_interface() - permission_name = str(permission + '.' + app) # str(...) Fix encoding issue + # Fetch existing permission - # Populate permission informations - result = ldap.search(base='ou=permission,dc=yunohost,dc=org', - filter='cn=' + permission_name, attrs=['URL']) - if not result: - raise YunohostError('permission_not_found', permission=permission, app=app) - permission_obj = result[0] + existing_permission = user_permission_list(full=True)["permissions"].get(permission, None) + if not existing_permission: + raise YunohostError('permission_not_found', permission=permission) - if 'URL' not in permission_obj: - permission_obj['URL'] = [] + # Compute new url list - url = set(permission_obj['URL']) + new_urls = copy.copy(existing_permission["urls"]) - if add_url: - for u in add_url: - domain = u[:u.index('/')] - path = u[u.index('/'):] - domain, path = _normalize_domain_path(domain, path) - url.add(domain + path) - if remove_url: - for u in remove_url: - domain = u[:u.index('/')] - path = u[u.index('/'):] - domain, path = _normalize_domain_path(domain, path) - url.discard(domain + path) + if add: + urls_to_add = [add] if not isinstance(add, list) else add + urls_to_add = [_normalize_url(url) for url in urls_to_add] + new_urls += urls_to_add + if remove: + urls_to_remove = [remove] if not isinstance(remove, list) else remove + urls_to_remove = [_normalize_url(url) for url in urls_to_remove] + new_urls = [u for u in new_urls if u not in urls_to_remove] - if url == set(permission_obj['URL']): + if set(new_urls) == set(existing_permission["urls"]): logger.warning(m18n.n('permission_update_nothing_to_do')) - return user_permission_list(app, permission) + return existing_permission + + # Actually commit the change operation_logger.start() - if ldap.update('cn=%s,ou=permission' % permission_name, {'cn': permission_name, 'URL': url}): + if ldap.update('cn=%s,ou=permission' % permission, {'URL': new_urls}): if sync_perm: permission_sync_to_user() - logger.debug(m18n.n('permission_updated', permission=permission, app=app)) - return user_permission_list(app, permission) - - raise YunohostError('premission_update_failed') + logger.debug(m18n.n('permission_updated', permission=permission)) + return user_permission_list(full=True)["permissions"][permission] + else: + raise YunohostError('premission_update_failed') @is_unit_operation(['permission', 'app']) -def permission_delete(operation_logger, app, permission, force=False, sync_perm=True): +def permission_delete(operation_logger, permission, force=False, sync_perm=True): """ - Remove a permission for a specific application + Delete a permission Keyword argument: - app -- an application OR sftp, xmpp (metronome), mail - permission -- name of the permission ("main" by default) - + permission -- Name of the permission (e.g. nextcloud.main or wordpress.editors) """ - if permission == "main" and not force: + if permission.endswith("main") and not force: raise YunohostError('remove_main_permission_not_allowed') from yunohost.utils.ldap import _get_ldap_interface ldap = _get_ldap_interface() + # Make sure this permission exists + + existing_permission = user_permission_list(full=True)["permissions"].get(permission, None) + if not existing_permission: + raise YunohostError('permission_not_found', permission=permission) + + # Actually delete the permission + operation_logger.start() - if not ldap.remove('cn=%s,ou=permission' % str(permission + '.' + app)): - raise YunohostError('permission_deletion_failed', permission=permission, app=app) - if sync_perm: - permission_sync_to_user() - logger.debug(m18n.n('permission_deleted', permission=permission, app=app)) + if ldap.remove('cn=%s,ou=permission' % permission): + if sync_perm: + permission_sync_to_user() + logger.debug(m18n.n('permission_deleted', permission=permission)) + else: + raise YunohostError('permission_deletion_failed', permission=permission) def permission_sync_to_user(force=False): @@ -438,3 +429,10 @@ def permission_sync_to_user(force=False): # Reload unscd, otherwise the group ain't propagated to the LDAP database os.system('nscd --invalidate=passwd') os.system('nscd --invalidate=group') + +def _normalize_url(url): + from yunohost.domain import _normalize_domain_path + domain = url[:url.index('/')] + path = url[url.index('/'):] + domain, path = _normalize_domain_path(domain, path) + return domain + path From 853c6a161a07ca2a935b1eeb3424094f1f8e6a7c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 11 Sep 2019 20:56:56 +0200 Subject: [PATCH 0091/3170] Simplify permission_sync_to_user ... force is never set to True so I dropped it... --- src/yunohost/permission.py | 71 +++++++++++--------------------------- 1 file changed, 21 insertions(+), 50 deletions(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 960c0853c..54aa25e23 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -358,70 +358,41 @@ def permission_delete(operation_logger, permission, force=False, sync_perm=True) raise YunohostError('permission_deletion_failed', permission=permission) -def permission_sync_to_user(force=False): +def permission_sync_to_user(): """ Sychronise the inheritPermission attribut in the permission object from the user<->group link and the group<->permission link - - Keyword argument: - force -- Force to recreate all attributes. Used generally with the - backup which uses "slapadd" which doesnt' use the memberOf overlay. - Note that by removing all value and adding a new time, we force the - overlay to update all attributes """ - # Note that a LDAP operation with the same value that is in LDAP crash SLAP. - # So we need to check before each ldap operation that we really change something in LDAP import os from yunohost.app import app_ssowatconf + from yunohost.user import user_group_list from yunohost.utils.ldap import _get_ldap_interface ldap = _get_ldap_interface() - permission_attrs = [ - 'cn', - 'member', - ] - group_info = ldap.search('ou=groups,dc=yunohost,dc=org', - '(objectclass=groupOfNamesYnh)', permission_attrs) - group_info = {g['cn'][0]: g for g in group_info} + groups = user_group_list(full=True)["groups"] + permissions = user_permission_list(full=True)["permissions"] - for per in ldap.search('ou=permission,dc=yunohost,dc=org', - '(objectclass=permissionYnh)', - ['cn', 'inheritPermission', 'groupPermission', 'memberUid']): + for permission_name, permission_infos in permissions.items(): - if 'groupPermission' not in per: - per['groupPermission'] = [] - user_permission = set() - for group in per['groupPermission']: - group = group.split("=")[1].split(",")[0] - if 'member' not in group_info[group]: - continue - for user in group_info[group]['member']: - user_permission.add(user) + # These are the users currently allowed because there's an 'inheritPermission' object corresponding to it + currently_allowed_users = set(permission_infos["corresponding_users"]) - if 'inheritPermission' not in per: - per['inheritPermission'] = [] - if 'memberUid' not in per: - per['memberUid'] = [] + # These are the users that should be allowed because they are member of a group that is allowed for this permission ... + should_be_allowed_users = set([user for group in permission_infos["allowed"] for user in groups[group]["members"]]) - uid_val = [v.split("=")[1].split(",")[0] for v in user_permission] - if user_permission == set(per['inheritPermission']) and set(uid_val) == set(per['memberUid']) and not force: + # Note that a LDAP operation with the same value that is in LDAP crash SLAP. + # So we need to check before each ldap operation that we really change something in LDAP + if currently_allowed_users == should_be_allowed_users: + # We're all good, this permission is already correctly synchronized ! continue - inheritPermission = {'inheritPermission': user_permission, 'memberUid': uid_val} - if force: - if per['groupPermission']: - if not ldap.update('cn=%s,ou=permission' % per['cn'][0], {'groupPermission': []}): - raise YunohostError('permission_update_failed_clear') - if not ldap.update('cn=%s,ou=permission' % per['cn'][0], {'groupPermission': per['groupPermission']}): - raise YunohostError('permission_update_failed_populate') - if per['inheritPermission']: - if not ldap.update('cn=%s,ou=permission' % per['cn'][0], {'inheritPermission': []}): - raise YunohostError('permission_update_failed_clear') - if user_permission: - if not ldap.update('cn=%s,ou=permission' % per['cn'][0], inheritPermission): - raise YunohostError('permission_update_failed') - else: - if not ldap.update('cn=%s,ou=permission' % per['cn'][0], inheritPermission): - raise YunohostError('permission_update_failed') + + new_inherited_perms = {'inheritPermission': ["uid=%s,ou=users,dc=yunohost,dc=org" % u for u in should_be_allowed_users], + 'memberUid': should_be_allowed_users} + + # Commit the change with the new inherited stuff + if not ldap.update('cn=%s,ou=permission' % permission_name, new_inherited_perms): + raise YunohostError('permission_update_failed') + logger.debug(m18n.n('permission_generated')) app_ssowatconf() From 98b1c30330af31c7e4bd9e81a0a436f1a3b3c5e2 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 11 Sep 2019 21:40:04 +0200 Subject: [PATCH 0092/3170] Simplify app_ssowatconf code related to permissions --- src/yunohost/app.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index c2fb87acf..1a41e2a01 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1414,12 +1414,10 @@ def app_ssowatconf(): skipped_regex.append("^[^/]*/%.well%-known/acme%-challenge/.*$") skipped_regex.append("^[^/]*/%.well%-known/autoconfig/mail/config%-v1%.1%.xml.*$") - permission = {} - for a in user_permission_list()['permissions'].values(): - for p in a.values(): - if 'URL' in p: - for u in p['URL']: - permission[u] = p['allowed_users'] + permissions_per_url = {} + for permission_name, permission_infos in user_permission_list(full=True)['permissions'].items(): + for url in permission_infos["urls"]: + permissions_per_url[url] = permission_infos['corresponding_users'] conf_dict = { 'portal_domain': main_domain, @@ -1441,7 +1439,7 @@ def app_ssowatconf(): 'redirected_regex': redirected_regex, 'users': {username: app_map(user=username) for username in user_list()['users'].keys()}, - 'permission': permission, + 'permissions': permissions_per_url, } with open('/etc/ssowat/conf.json', 'w+') as f: From 38c43f4b9a6f899985fb7bb67d1c9ce7b1cafad9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 11 Sep 2019 22:48:54 +0200 Subject: [PATCH 0093/3170] Fix the whole operation logger / related to thing + propagate on the legacy addaccess --- locales/en.json | 13 +++++-------- src/yunohost/app.py | 37 +++++++++++++++++++------------------ src/yunohost/log.py | 4 ++-- src/yunohost/permission.py | 31 +++++++++++++++++++++++-------- src/yunohost/user.py | 26 +++++++++++++------------- 5 files changed, 62 insertions(+), 49 deletions(-) diff --git a/locales/en.json b/locales/en.json index ebbb89fa8..2c6363608 100644 --- a/locales/en.json +++ b/locales/en.json @@ -259,9 +259,6 @@ "log_help_to_get_failed_log": "The operation '{desc}' has failed! To get help, please share the full log of this operation using the command 'yunohost log display {name} --share'", "log_does_exists": "There is not operation log with the name '{log}', use 'yunohost log list to see all available operation logs'", "log_operation_unit_unclosed_properly": "Operation unit has not been closed properly", - "log_app_addaccess": "Add access to '{}'", - "log_app_removeaccess": "Remove access to '{}'", - "log_app_clearaccess": "Remove all access to '{}'", "log_app_fetchlist": "Add an application list", "log_app_removelist": "Remove an application list", "log_app_change_url": "Change the url of '{}' application", @@ -279,9 +276,9 @@ "log_dyndns_subscribe": "Subscribe to a YunoHost subdomain '{}'", "log_dyndns_update": "Update the ip associated with your YunoHost subdomain '{}'", "log_letsencrypt_cert_install": "Install Let's encrypt certificate on '{}' domain", - "log_permission_add": "Add permission '{}' for app '{}'", - "log_permission_remove": "Remove permission '{}'", - "log_permission_update": "Update permission '{}' for app '{}'", + "log_permission_create": "Create permission '{permission}'", + "log_permission_delete": "Delete permission '{permission}'", + "log_permission_urls": "Update urls related to permission '{permission}'", "log_selfsigned_cert_install": "Install self signed certificate on '{}' domain", "log_letsencrypt_cert_renew": "Renew '{}' Let's encrypt certificate", "log_regen_conf": "Regenerate system configurations '{}'", @@ -291,8 +288,8 @@ "log_user_group_delete": "Delete '{}' group", "log_user_group_update": "Update '{}' group", "log_user_update": "Update information of '{}' user", - "log_user_permission_add": "Update '{}' permission", - "log_user_permission_remove": "Update '{}' permission", + "log_user_permission_update": "Update accesses for permission '{permission}'", + "log_user_permission_reset": "Reset permission '{permission}'", "log_tools_maindomain": "Make '{}' as main domain", "log_tools_migrations_migrate_forward": "Migrate forward", "log_tools_postinstall": "Postinstall your YunoHost server", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 1a41e2a01..14396b1cd 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1039,8 +1039,7 @@ def app_remove(operation_logger, app): raise YunohostError("this_action_broke_dpkg") -@is_unit_operation(['permission','app']) -def app_addaccess(operation_logger, apps, users=[]): +def app_addaccess(apps, users=[]): """ Grant access right to users (everyone by default) @@ -1051,15 +1050,15 @@ def app_addaccess(operation_logger, apps, users=[]): """ from yunohost.permission import user_permission_update - permission = user_permission_update(operation_logger, app=apps, permission="main", add_username=users) + output = {} + for app in apps: + permission = user_permission_update(app+".main", add=users) + output[app] = permission["corresponding_users"] - result = {p : v['main']['allowed_users'] for p, v in permission['permissions'].items()} - - return {'allowed_users': result} + return {'allowed_users': output} -@is_unit_operation(['permission','app']) -def app_removeaccess(operation_logger, apps, users=[]): +def app_removeaccess(apps, users=[]): """ Revoke access right to users (everyone by default) @@ -1070,15 +1069,15 @@ def app_removeaccess(operation_logger, apps, users=[]): """ from yunohost.permission import user_permission_update - permission = user_permission_update(operation_logger, app=apps, permission="main", del_username=users) + output = {} + for app in apps: + permission = user_permission_update(app+".main", remove=users) + output[app] = permission["corresponding_users"] - result = {p : v['main']['allowed_users'] for p, v in permission['permissions'].items()} - - return {'allowed_users': result} + return {'allowed_users': output} -@is_unit_operation(['permission','app']) -def app_clearaccess(operation_logger, apps): +def app_clearaccess(apps): """ Reset access rights for the app @@ -1086,13 +1085,15 @@ def app_clearaccess(operation_logger, apps): apps """ - from yunohost.permission import user_permission_clear + from yunohost.permission import user_permission_reset - permission = user_permission_clear(operation_logger, app=apps, permission="main") + output = {} + for app in apps: + permission = user_permission_reset(app+".main") + output[app] = permission["corresponding_users"] - result = {p : v['main']['allowed_users'] for p, v in permission['permissions'].items()} + return {'allowed_users': output} - return {'allowed_users': result} def app_debug(app): """ diff --git a/src/yunohost/log.py b/src/yunohost/log.py index cbb850e44..8b0f893e8 100644 --- a/src/yunohost/log.py +++ b/src/yunohost/log.py @@ -44,7 +44,7 @@ CATEGORIES = ['operation', 'history', 'package', 'system', 'access', 'service', 'app'] METADATA_FILE_EXT = '.yml' LOG_FILE_EXT = '.log' -RELATED_CATEGORIES = ['app', 'domain', 'service', 'user'] +RELATED_CATEGORIES = ['app', 'domain', 'group', 'service', 'user'] logger = getActionLogger('yunohost.log') @@ -213,7 +213,7 @@ def log_display(path, number=None, share=False): return infos -def is_unit_operation(entities=['app', 'domain', 'service', 'user'], +def is_unit_operation(entities=['app', 'domain', 'group', 'service', 'user'], exclude=['password'], operation_key=None): """ Configure quickly a unit operation diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 54aa25e23..a4ff9fb15 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -76,6 +76,7 @@ def user_permission_list(short=False, full=False): return {'permissions': permissions} +@is_unit_operation() def user_permission_update(operation_logger, permission, add=None, remove=None, sync_perm=True): """ Allow or Disallow a user or group to a permission for a specific application @@ -98,6 +99,7 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, current_allowed_groups = existing_permission["allowed"] all_existing_groups = user_group_list()['groups'].keys() + operation_logger.related_to.append(('app', permission.split(".")[0])) # Compute new allowed group list (and make sure what we're doing make sense) @@ -110,6 +112,8 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, raise YunohostError('group_unknown', group=group) if group in current_allowed_groups: logger.warning(m18n.n('group_already_allowed', permission=permission, group=group)) + else: + operation_logger.related_to.append(('group', group)) new_allowed_groups += groups_to_add @@ -120,6 +124,8 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, raise YunohostError('group_unknown', group=group) if group not in current_allowed_groups: logger.warning(m18n.n('group_already_disallowed', permission=permission, group=group)) + else: + operation_logger.related_to.append(('group', group)) new_allowed_groups = [g for g in new_allowed_groups if g not in groups_to_remove] @@ -132,15 +138,17 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, # FIXME : write a better explanation ? logger.warning("This permission is currently enabled for all users in addition to other groups. You probably want to either remove the 'all_users' permission or remove the specific groups currently allowed.") - # Commit the new allowed group list - - operation_logger.start() - # Don't update LDAP if we update exactly the same values if set(new_allowed_groups) == set(current_allowed_groups): # FIXME : i18n logger.warning("No change was applied because not relevant modification were found") - elif ldap.update('cn=%s,ou=permission' % permission, + return + + # Commit the new allowed group list + + operation_logger.start() + + if ldap.update('cn=%s,ou=permission' % permission, {'groupPermission': ['cn=' + g + ',ou=groups,dc=yunohost,dc=org' for g in new_allowed_groups]}): logger.debug(m18n.n('permission_updated', permission=permission)) @@ -172,6 +180,7 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, raise YunohostError('permission_update_failed') +@is_unit_operation() def user_permission_reset(operation_logger, permission, sync_perm=True): """ Reset a given permission to just 'all_users' @@ -191,6 +200,9 @@ def user_permission_reset(operation_logger, permission, sync_perm=True): # Update permission with default (all_users) + operation_logger.related_to.append(('app', permission.split(".")[0])) + operation_logger.start() + default_permission = {'groupPermission': ['cn=all_users,ou=groups,dc=yunohost,dc=org']} if ldap.update('cn=%s,ou=permission' % permission, default_permission): logger.debug(m18n.n('permission_updated', permission=permission)) @@ -228,7 +240,7 @@ def user_permission_reset(operation_logger, permission, sync_perm=True): # -@is_unit_operation(['permission', 'app']) +@is_unit_operation() def permission_create(operation_logger, permission, urls=None, sync_perm=True): """ Create a new permission for a specific application @@ -267,6 +279,7 @@ def permission_create(operation_logger, permission, urls=None, sync_perm=True): if urls: attr_dict['URL'] = [_normalize_url(url) for url in urls] + operation_logger.related_to.append(('app', permission.split(".")[0])) operation_logger.start() if ldap.add('cn=%s,ou=permission' % permission, attr_dict): if sync_perm: @@ -277,7 +290,7 @@ def permission_create(operation_logger, permission, urls=None, sync_perm=True): raise YunohostError('permission_creation_failed') -@is_unit_operation(['permission', 'app']) +@is_unit_operation() def permission_urls(operation_logger, permission, add=None, remove=None, sync_perm=True): """ Update urls related to a permission for a specific application @@ -316,6 +329,7 @@ def permission_urls(operation_logger, permission, add=None, remove=None, sync_pe # Actually commit the change + operation_logger.related_to.append(('app', permission.split(".")[0])) operation_logger.start() if ldap.update('cn=%s,ou=permission' % permission, {'URL': new_urls}): if sync_perm: @@ -326,7 +340,7 @@ def permission_urls(operation_logger, permission, add=None, remove=None, sync_pe raise YunohostError('premission_update_failed') -@is_unit_operation(['permission', 'app']) +@is_unit_operation() def permission_delete(operation_logger, permission, force=False, sync_perm=True): """ Delete a permission @@ -349,6 +363,7 @@ def permission_delete(operation_logger, permission, force=False, sync_perm=True) # Actually delete the permission + operation_logger.related_to.append(('app', permission.split(".")[0])) operation_logger.start() if ldap.remove('cn=%s,ou=permission' % permission): if sync_perm: diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 2bf36cfd6..5631a2204 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -525,7 +525,7 @@ def user_group_list(short=False, full=False): return {'groups': groups} -@is_unit_operation([('groupname', 'user')]) +@is_unit_operation([('groupname', 'group')]) def user_group_create(operation_logger, groupname, gid=None, primary_group=False, sync_perm=True): """ Create group @@ -537,8 +537,6 @@ def user_group_create(operation_logger, groupname, gid=None, primary_group=False from yunohost.permission import permission_sync_to_user from yunohost.utils.ldap import _get_ldap_interface - operation_logger.start() - ldap = _get_ldap_interface() # Validate uniqueness of groupname in LDAP @@ -574,6 +572,7 @@ def user_group_create(operation_logger, groupname, gid=None, primary_group=False if primary_group: attr_dict["member"] = ["uid=" + groupname + ",ou=users,dc=yunohost,dc=org"] + operation_logger.start() if ldap.add('cn=%s,ou=groups' % groupname, attr_dict): logger.success(m18n.n('group_created', group=groupname)) if sync_perm: @@ -583,7 +582,7 @@ def user_group_create(operation_logger, groupname, gid=None, primary_group=False raise YunohostError('group_creation_failed', group=groupname) -@is_unit_operation([('groupname', 'user')]) +@is_unit_operation([('groupname', 'group')]) def user_group_delete(operation_logger, groupname, force=False, sync_perm=True): """ Delete user @@ -614,7 +613,7 @@ def user_group_delete(operation_logger, groupname, force=False, sync_perm=True): permission_sync_to_user() -@is_unit_operation([('groupname', 'user')]) +@is_unit_operation([('groupname', 'group')]) def user_group_update(operation_logger, groupname, add=None, remove=None, force=False, sync_perm=True): """ Update user informations @@ -650,6 +649,8 @@ def user_group_update(operation_logger, groupname, add=None, remove=None, force= if user in current_group: logger.warning(m18n.n('user_already_in_group', user=user, group=groupname)) + else: + operation_logger.related_to.append(('user', user)) new_group += users_to_add @@ -659,6 +660,8 @@ def user_group_update(operation_logger, groupname, add=None, remove=None, force= for user in users_to_remove: if user not in current_group: logger.warning(m18n.n('user_not_in_group', user=user, group=groupname)) + else: + operation_logger.related_to.append(('user', user)) # Remove users_to_remove from new_group # Kinda like a new_group -= users_to_remove @@ -666,9 +669,8 @@ def user_group_update(operation_logger, groupname, add=None, remove=None, force= new_group_dns = ["uid=" + user + ",ou=users,dc=yunohost,dc=org" for user in new_group] - operation_logger.start() - if set(new_group) != set(current_group): + operation_logger.start() ldap = _get_ldap_interface() if not ldap.update('cn=%s,ou=groups' % groupname, {"member": set(new_group_dns), "memberUid": set(new_group)}): raise YunohostError('group_update_failed', group=groupname) @@ -718,18 +720,16 @@ def user_permission_list(short=False, full=False): return yunohost.permission.user_permission_list(short, full) -@is_unit_operation([('permission', 'user')]) -def user_permission_update(operation_logger, permission, add=None, remove=None, sync_perm=True): +def user_permission_update(permission, add=None, remove=None, sync_perm=True): import yunohost.permission - return yunohost.permission.user_permission_update(operation_logger, permission, + return yunohost.permission.user_permission_update(permission, add=add, remove=remove, sync_perm=sync_perm) -@is_unit_operation([('app', 'user')]) -def user_permission_reset(operation_logger, permission, sync_perm=True): +def user_permission_reset(permission, sync_perm=True): import yunohost.permission - return yunohost.permission.user_permission_reset(operation_logger, permission, + return yunohost.permission.user_permission_reset(permission, sync_perm=sync_perm) From d5b2fb7a7126e80492d38e455bcb41f11e0b32b8 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 11 Sep 2019 23:18:23 +0200 Subject: [PATCH 0094/3170] Misc fixes/improvements for i18n strings --- locales/en.json | 38 ++++++++++++++++++-------------------- src/yunohost/permission.py | 16 ++++++++-------- src/yunohost/user.py | 8 ++++---- 3 files changed, 30 insertions(+), 32 deletions(-) diff --git a/locales/en.json b/locales/en.json index 2c6363608..e41250759 100644 --- a/locales/en.json +++ b/locales/en.json @@ -228,19 +228,19 @@ "global_settings_unknown_type": "Unexpected situation, the setting {setting:s} appears to have the type {unknown_type:s} but it's not a type supported by the system.", "good_practices_about_admin_password": "You are now about to define a new administration password. The password should be at least 8 characters - though it is good practice to use longer password (i.e. a passphrase) and/or to use various kind of characters (uppercase, lowercase, digits and special characters).", "good_practices_about_user_password": "You are now about to define a new user password. The password should be at least 8 characters - though it is good practice to use longer password (i.e. a passphrase) and/or to use various kind of characters (uppercase, lowercase, digits and special characters).", - "group_already_allowed": "Group '{group:s}' already has permission '{permission:s}' enabled'", - "group_already_disallowed": "Group '{group:s}' already has permission '{permission:s}' disabled'", - "group_name_already_exist": "Group {name:s} already exist", + "group_already_exist": "Group {group} already exist", + "group_already_exist_on_system": "Group {group} already exists in the system group", "group_created": "Group '{group}' successfully created", "group_creation_failed": "Group creation failed for group '{group}'", - "group_cannot_be_edited": "The group {group:s} cannot be edited manually.", - "group_cannot_be_deleted": "The group {group:s} cannot be deleted manually.", + "group_cannot_be_edited": "The group {group} cannot be edited manually.", + "group_cannot_be_deleted": "The group {group} cannot be deleted manually.", "group_deleted": "Group '{group}' deleted", "group_deletion_failed": "Group '{group} 'deletion failed", - "group_info_failed": "Group info failed", - "group_unknown": "Group {group:s} unknown", + "group_unknown": "Group {group} unknown", "group_updated": "Group '{group}' updated", "group_update_failed": "Group update failed for group '{group}'", + "group_user_already_in_group": "User {user} is already in group {group}", + "group_user_not_in_group": "User {user} is not in group {group}", "hook_exec_failed": "Script execution failed: {path:s}", "hook_exec_not_terminated": "Script execution did not finish properly: {path:s}", "hook_json_return_error": "Failed to read return from hook {path:s}. Error: {msg:s}. Raw content: {raw_content}", @@ -426,22 +426,23 @@ "pattern_positive_number": "Must be a positive number", "pattern_username": "Must be lower-case alphanumeric and underscore characters only", "pattern_password_app": "Sorry, passwords should not contain the following characters: {forbidden_chars}", - "permission_already_exist": "Permission '{permission:s}' for app {app:s} already exist", - "permission_created": "Permission '{permission:s}' for app {app:s} created", - "permission_creation_failed": "Permission creation failed", - "permission_deleted": "Permission '{permission:s}' for app {app:s} deleted", - "permission_deletion_failed": "Permission '{permission:s}' for app {app:s} deletion failed", - "permission_not_found": "Permission '{permission:s}' not found for application {app:s}", - "permission_update_failed": "Permission update failed", - "permission_generated": "The permission database has been updated", - "permission_updated": "Permission '{permission:s}' for app {app:s} updated", + "permission_already_allowed": "Group '{group}' already has permission '{permission}' enabled'", + "permission_already_disallowed": "Group '{group}' already has permission '{permission}' disabled'", + "permission_already_exist": "Permission '{permission}' already exists", + "permission_cannot_remove_main": "Removing a main permission is not allowed", + "permission_created": "Permission '{permission}' created", + "permission_creation_failed": "Failed to create permission '{permission}'", + "permission_deleted": "Permission '{permission}' deleted", + "permission_deletion_failed": "Failed to delete permission '{permission}'", + "permission_not_found": "Permission '{permission}' does not seem to exist ?", + "permission_update_failed": "Failed to update permission '{permission}'", + "permission_updated": "Permission '{permission}' updated", "permission_update_nothing_to_do": "No permissions to update", "port_already_closed": "Port {port:d} is already closed for {ip_version:s} connections", "port_already_opened": "Port {port:d} is already opened for {ip_version:s} connections", "port_available": "Port {port:d} is available", "port_unavailable": "Port {port:d} is not available", "recommend_to_add_first_user": "The post-install is finished but YunoHost needs at least one user to work correctly, you should add one using 'yunohost user create ' or the admin interface.", - "remove_main_permission_not_allowed": "Removing the main permission is not allowed", "regenconf_file_backed_up": "The configuration file '{conf}' has been backed up to '{backup}'", "regenconf_file_copy_failed": "Unable to copy the new configuration file '{new}' to '{conf}'", "regenconf_file_kept_back": "The configuration file '{conf}' is expected to be deleted by regen-conf (category {category}) but has been kept back.", @@ -527,7 +528,6 @@ "ssowat_conf_updated": "The SSOwat configuration has been updated", "ssowat_persistent_conf_read_error": "Error while reading SSOwat persistent configuration: {error:s}. Edit /etc/ssowat/conf.json.persistent file to fix the JSON syntax", "ssowat_persistent_conf_write_error": "Error while saving SSOwat persistent configuration: {error:s}. Edit /etc/ssowat/conf.json.persistent file to fix the JSON syntax", - "system_groupname_exists": "Groupname already exists in the system group", "system_upgraded": "The system has been upgraded", "system_username_exists": "Username already exists in the system users", "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`.", @@ -556,14 +556,12 @@ "upnp_disabled": "UPnP has been disabled", "upnp_enabled": "UPnP has been enabled", "upnp_port_open_failed": "Unable to open UPnP ports", - "user_already_in_group": "User {user:} already in group {group:s}", "user_created": "The user has been created", "user_creation_failed": "Unable to create user", "user_deleted": "The user has been deleted", "user_deletion_failed": "Unable to delete user", "user_home_creation_failed": "Unable to create user home folder", "user_info_failed": "Unable to retrieve user information", - "user_not_in_group": "User {user:s} not in group {group:s}", "user_unknown": "Unknown user: {user:s}", "user_update_failed": "Unable to update user", "user_updated": "The user has been updated", diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index a4ff9fb15..21ee960fa 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -111,7 +111,7 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, if group not in all_existing_groups: raise YunohostError('group_unknown', group=group) if group in current_allowed_groups: - logger.warning(m18n.n('group_already_allowed', permission=permission, group=group)) + logger.warning(m18n.n('permission_already_allowed', permission=permission, group=group)) else: operation_logger.related_to.append(('group', group)) @@ -123,7 +123,7 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, if group not in all_existing_groups: raise YunohostError('group_unknown', group=group) if group not in current_allowed_groups: - logger.warning(m18n.n('group_already_disallowed', permission=permission, group=group)) + logger.warning(m18n.n('permission_already_disallowed', permission=permission, group=group)) else: operation_logger.related_to.append(('group', group)) @@ -177,7 +177,7 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, return new_permission else: - raise YunohostError('permission_update_failed') + raise YunohostError('permission_update_failed', permission=permission) @is_unit_operation() @@ -207,7 +207,7 @@ def user_permission_reset(operation_logger, permission, sync_perm=True): if ldap.update('cn=%s,ou=permission' % permission, default_permission): logger.debug(m18n.n('permission_updated', permission=permission)) else: - raise YunohostError('permission_update_failed') + raise YunohostError('permission_update_failed', permission=permission) if sync_perm: permission_sync_to_user() @@ -337,7 +337,7 @@ def permission_urls(operation_logger, permission, add=None, remove=None, sync_pe logger.debug(m18n.n('permission_updated', permission=permission)) return user_permission_list(full=True)["permissions"][permission] else: - raise YunohostError('premission_update_failed') + raise YunohostError('permission_update_failed', permission=permission) @is_unit_operation() @@ -350,7 +350,7 @@ def permission_delete(operation_logger, permission, force=False, sync_perm=True) """ if permission.endswith("main") and not force: - raise YunohostError('remove_main_permission_not_allowed') + raise YunohostError('permission_cannot_remove_main') from yunohost.utils.ldap import _get_ldap_interface ldap = _get_ldap_interface() @@ -406,9 +406,9 @@ def permission_sync_to_user(): # Commit the change with the new inherited stuff if not ldap.update('cn=%s,ou=permission' % permission_name, new_inherited_perms): - raise YunohostError('permission_update_failed') + raise YunohostError('permission_update_failed', permission=permission_name) - logger.debug(m18n.n('permission_generated')) + logger.debug("The permission database has been resynchronized") app_ssowatconf() diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 5631a2204..e1719d3a6 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -544,12 +544,12 @@ def user_group_create(operation_logger, groupname, gid=None, primary_group=False 'cn': groupname }, base_dn='ou=groups,dc=yunohost,dc=org') if conflict: - raise YunohostError('group_name_already_exist', name=groupname) + raise YunohostError('group_already_exist', group=groupname) # Validate uniqueness of groupname in system group all_existing_groupnames = {x.gr_name for x in grp.getgrall()} if groupname in all_existing_groupnames: - raise YunohostError('system_groupname_exists') + raise YunohostError('group_already_exist_on_system', group=groupname) if not gid: # Get random GID @@ -648,7 +648,7 @@ def user_group_update(operation_logger, groupname, add=None, remove=None, force= raise YunohostError('user_unknown', user=user) if user in current_group: - logger.warning(m18n.n('user_already_in_group', user=user, group=groupname)) + logger.warning(m18n.n('group_user_already_in_group', user=user, group=groupname)) else: operation_logger.related_to.append(('user', user)) @@ -659,7 +659,7 @@ def user_group_update(operation_logger, groupname, add=None, remove=None, force= for user in users_to_remove: if user not in current_group: - logger.warning(m18n.n('user_not_in_group', user=user, group=groupname)) + logger.warning(m18n.n('group_user_not_in_group', user=user, group=groupname)) else: operation_logger.related_to.append(('user', user)) From a92ff5307774d657b601051b31e0e52634472180 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 12 Sep 2019 00:20:22 +0200 Subject: [PATCH 0095/3170] Propagate changes to other parts of the code relying on groups and permissions --- src/yunohost/app.py | 29 +++++++----------- src/yunohost/backup.py | 61 ++++++++++++++++---------------------- src/yunohost/permission.py | 9 ++++-- 3 files changed, 44 insertions(+), 55 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 14396b1cd..f505dd088 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -465,7 +465,7 @@ def app_change_url(operation_logger, app, domain, path): """ from yunohost.hook import hook_exec, hook_callback from yunohost.domain import _normalize_domain_path, _get_conflicting_apps - from yunohost.permission import permission_update + from yunohost.permission import permission_urls installed = _is_installed(app) if not installed: @@ -555,7 +555,7 @@ def app_change_url(operation_logger, app, domain, path): app_setting(app, 'domain', value=domain) app_setting(app, 'path', value=path) - permission_update(app, permission="main", add_url=[domain+path], remove_url=[old_domain+old_path], sync_perm=True) + permission_urls(app+".main", add=[domain+path], remove=[old_domain+old_path], sync_perm=True) # avoid common mistakes if _run_service_command("reload", "nginx") is False: @@ -738,7 +738,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu from yunohost.utils.ldap import _get_ldap_interface from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback from yunohost.log import OperationLogger - from yunohost.permission import permission_create, permission_update, permission_delete, permission_sync_to_user + from yunohost.permission import permission_create, permission_urls, permission_delete, permission_sync_to_user ldap = _get_ldap_interface() # Fetch or extract sources @@ -875,7 +875,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu # Create permission before the install (useful if the install script redefine the permission) # Note that sync_perm is disabled to avoid triggering a whole bunch of code and messages # can't be sure that we don't have one case when it's needed - permission_create(app=app_instance_name, permission="main", sync_perm=False) + permission_create(app_instance_name+".main", sync_perm=False) # Execute the app install script install_retcode = 1 @@ -910,11 +910,9 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu args=[app_instance_name], env=env_dict_remove )[0] # Remove all permission in LDAP - result = ldap.search(base='ou=permission,dc=yunohost,dc=org', - filter='(&(objectclass=permissionYnh)(cn=*.%s))' % app_instance_name, attrs=['cn']) - permission_list = [p['cn'][0] for p in result] - for l in permission_list: - permission_delete(app_instance_name, l.split('.')[0], force=True) + for permission_name in user_permission_list()["permissions"].keys(): + if permission_name.startswith(app_instance_name+"."): + permission_delete(permission_name, force=True) if remove_retcode != 0: msg = m18n.n('app_not_properly_removed', @@ -960,8 +958,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu domain = app_settings.get('domain', None) path = app_settings.get('path', None) if domain and path: - permission_update(app_instance_name, permission="main", add_url=[domain+path], sync_perm=False) - + permission_urls(app_instance_name+".main", add=[domain+path], sync_perm=False) permission_sync_to_user() logger.success(m18n.n('installation_complete')) @@ -978,7 +975,6 @@ def app_remove(operation_logger, app): app -- App(s) to delete """ - from yunohost.utils.ldap import _get_ldap_interface from yunohost.hook import hook_exec, hook_remove, hook_callback from yunohost.permission import permission_delete, permission_sync_to_user if not _is_installed(app): @@ -1026,12 +1022,9 @@ def app_remove(operation_logger, app): hook_remove(app) # Remove all permission in LDAP - ldap = _get_ldap_interface() - result = ldap.search(base='ou=permission,dc=yunohost,dc=org', - filter='(&(objectclass=permissionYnh)(cn=*.%s))' % app, attrs=['cn']) - permission_list = [p['cn'][0] for p in result] - for l in permission_list: - permission_delete(app, l.split('.')[0], force=True, sync_perm=False) + for permission_name in user_permission_list()["permissions"].keys(): + if permission_name.startswith(app+"."): + permission_delete(permission_name, force=True, sync_perm=False) permission_sync_to_user() diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index fdbd8c62c..0527f89bf 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -703,6 +703,10 @@ class BackupManager(): self._import_to_list_to_backup(env_dict["YNH_BACKUP_CSV"]) # backup permissions + # + # FIXME : why can't we instead use a simple yaml file to store the + # relevant info and recreate the permission object from it ... + # logger.debug(m18n.n('backup_permission', app=app)) ldap_url = "ldap:///dc=yunohost,dc=org???(&(objectClass=permissionYnh)(cn=*.%s))" % app os.system("slapcat -b dc=yunohost,dc=org -H '%s' -l '%s/permission.ldif'" % (ldap_url, settings_dir)) @@ -919,7 +923,7 @@ class RestoreManager(): successfull_apps = self.targets.list("apps", include=["Success", "Warning"]) - permission_sync_to_user(force=False) + permission_sync_to_user() if os.path.ismount(self.work_dir): ret = subprocess.call(["umount", self.work_dir]) @@ -1183,18 +1187,11 @@ class RestoreManager(): if system_targets == []: return - from yunohost.utils.ldap import _get_ldap_interface - ldap = _get_ldap_interface() + from yunohost.permission import permission_create, user_permission_update, user_permission_list # Backup old permission for apps # We need to do that because in case of an app is installed we can't remove the permission for this app - old_apps_permission = [] - try: - old_apps_permission = ldap.search('ou=permission,dc=yunohost,dc=org', - '(&(objectClass=permissionYnh)(!(cn=main.mail))(!(cn=main.xmpp))(!(cn=main.sftp)))', - ['cn', 'objectClass', 'groupPermission', 'URL', 'gidNumber']) - except: - logger.info(m18n.n('apps_permission_not_found')) + old_apps_permission = user_permission_list(ignore_system_perms=True)["permissions"] # Start register change on system operation_logger = OperationLogger('backup_restore_system') @@ -1232,12 +1229,11 @@ class RestoreManager(): regen_conf() - # Check if we need to do the migration 0009 : setup group and permission + # Check that at least a group exists (all_users) to know if we need to + # do the migration 0011 : setup group and permission + # # Legacy code - result = ldap.search('ou=groups,dc=yunohost,dc=org', - '(&(objectclass=groupOfNamesYnh)(cn=all_users))', - ['cn']) - if not result: + if not user_group_list["groups"]: from yunohost.tools import _get_migration_by_name setup_group_permission = _get_migration_by_name("setup_group_permission") # Update LDAP schema restart slapd @@ -1245,22 +1241,16 @@ class RestoreManager(): regen_conf(names=['slapd'], force=True) setup_group_permission.migrate_LDAP_db() - # Remove all permission for all app which sill in the LDAP - for per in ldap.search('ou=permission,dc=yunohost,dc=org', - '(&(objectClass=permissionYnh)(!(cn=mail.main))(!(cn=xmpp.main))(!(cn=sftp.main)))', - ['cn']): - if not ldap.remove('cn=%s,ou=permission' % per['cn'][0]): - raise YunohostError('permission_deletion_failed', - permission=per['cn'][0].split('.')[0], - app=per['cn'][0].split('.')[1]) + # Remove all permission for all app which is still in the LDAP + for permission_name in user_permission_list(ignore_system_perms=True)["permissions"].keys(): + permission_delete(permission_name) # Restore permission for the app which is installed - for per in old_apps_permission: - # FIXME : will come here later to fix this following previous commits ... - permission_name, app_name = per['cn'][0].split('.') + for permission_name, permission_infos in old_apps_permission.items(): + app_name = permission_name.split(".")[0] if _is_installed(app_name): - if not ldap.add('cn=%s,ou=permission' % per['cn'][0], per): - raise YunohostError('apps_permission_restoration_failed', permission=permission_name, app=app_name) + permission_create(permission_name, urls=permission_infos["urls"], sync_perm=False) + user_permission_update(permission_name, remove="all_users", add=permission_infos["allowed"]) def _restore_apps(self): @@ -1368,6 +1358,10 @@ class RestoreManager(): restore_script = os.path.join(tmp_folder_for_app_restore, 'restore') # Restore permissions + # + # FIXME : why can't we instead use a simple yaml file to store the + # relevant info and recreate the permission object from it ... + # if os.path.isfile(app_settings_in_archive + '/permission.ldif'): filtred_entries = ['entryUUID', 'creatorsName', 'createTimestamp', 'entryCSN', 'structuralObjectClass', 'modifiersName', 'modifyTimestamp', 'inheritPermission', 'memberUid'] @@ -1422,7 +1416,6 @@ class RestoreManager(): operation_logger.start() # Execute remove script - # TODO: call app_remove instead if hook_exec(remove_script, args=[app_instance_name], env=env_dict_remove)[0] != 0: msg = m18n.n('app_not_properly_removed', app=app_instance_name) @@ -1434,12 +1427,10 @@ class RestoreManager(): # Cleaning app directory shutil.rmtree(app_settings_new_path, ignore_errors=True) - # Remove all permission in LDAP - result = ldap.search(base='ou=permission,dc=yunohost,dc=org', - filter='(&(objectclass=permissionYnh)(cn=*.%s))' % app_instance_name, attrs=['cn']) - permission_list = [p['cn'][0] for p in result] - for l in permission_list: - permission_delete(app_instance_name, l.split('.')[0], force=True) + # Remove all permission in LDAP for this app + for permission_name in user_permission_list()["permissions"].keys(): + if permission_name.startswith(app_instance_name+"."): + permission_delete(permission_name, force=True) # TODO Cleaning app hooks else: diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 21ee960fa..4d935d3c0 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -36,6 +36,8 @@ from yunohost.log import is_unit_operation logger = getActionLogger('yunohost.user') +SYSTEM_PERMS = ["mail", "xmpp", "stfp"] + # # # The followings are the methods exposed through the "yunohost user permission" interface @@ -43,7 +45,7 @@ logger = getActionLogger('yunohost.user') # -def user_permission_list(short=False, full=False): +def user_permission_list(short=False, full=False, ignore_system_perms=True): """ List permissions and corresponding accesses """ @@ -62,8 +64,11 @@ def user_permission_list(short=False, full=False): for infos in permissions_infos: name = infos['cn'][0] - permissions[name] = {} + if ignore_system_perms and name.split(".")[0] in SYSTEM_PERMS: + continue + + permissions[name] = {} permissions[name]["allowed"] = [_ldap_path_extract(p, "cn") for p in infos.get('groupPermission', [])] if full: From bbfc62cf3efc298fc297fa4da52d45e420cc0c0c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 12 Sep 2019 00:41:38 +0200 Subject: [PATCH 0096/3170] Backup/restore app permissions using yaml files which are much simpler to handle... --- locales/en.json | 1 - src/yunohost/backup.py | 55 +++++++++++++++++++----------------------- 2 files changed, 25 insertions(+), 31 deletions(-) diff --git a/locales/en.json b/locales/en.json index e41250759..e69e06201 100644 --- a/locales/en.json +++ b/locales/en.json @@ -50,7 +50,6 @@ "app_upgrade_some_app_failed": "Unable to upgrade some applications", "app_upgraded": "{app:s} has been upgraded", "apps_permission_not_found": "No permission found for the installed apps", - "apps_permission_restoration_failed": "Permission '{permission:s}' for app {app:s} restoration has failed", "appslist_corrupted_json": "Could not load the application lists. It looks like {filename:s} is corrupted.", "appslist_could_not_migrate": "Could not migrate app list {appslist:s}! Unable to parse the url… The old cron job has been kept in {bkp_file:s}.", "appslist_fetched": "The application list {appslist:s} has been fetched", diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 0527f89bf..f1ac7ee9c 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -40,7 +40,7 @@ from moulinette import msignals, m18n from yunohost.utils.error import YunohostError from moulinette.utils import filesystem from moulinette.utils.log import getActionLogger -from moulinette.utils.filesystem import read_file, mkdir +from moulinette.utils.filesystem import read_file, mkdir, write_to_yaml, read_yaml from yunohost.app import ( app_info, _is_installed, _parse_app_instance_name, _patch_php5 @@ -677,6 +677,8 @@ class BackupManager(): backup_app_failed -- Raised at the end if the app backup script execution failed """ + from yunohost.permission import user_permission_list + app_setting_path = os.path.join('/etc/yunohost/apps/', app) # Prepare environment @@ -703,13 +705,10 @@ class BackupManager(): self._import_to_list_to_backup(env_dict["YNH_BACKUP_CSV"]) # backup permissions - # - # FIXME : why can't we instead use a simple yaml file to store the - # relevant info and recreate the permission object from it ... - # logger.debug(m18n.n('backup_permission', app=app)) - ldap_url = "ldap:///dc=yunohost,dc=org???(&(objectClass=permissionYnh)(cn=*.%s))" % app - os.system("slapcat -b dc=yunohost,dc=org -H '%s' -l '%s/permission.ldif'" % (ldap_url, settings_dir)) + permissions = user_permission_list(full=True)["permissions"] + this_app_permissions = {name: infos for name, infos in permissions.items() if name.startswith(app + ".")} + write_to_yaml("%s/permissions.yml" % settings_dir, this_app_permissions) except: abs_tmp_app_dir = os.path.join(self.work_dir, 'apps/', app) @@ -1289,11 +1288,8 @@ class RestoreManager(): name already exists restore_app_failed -- Raised if the restore bash script failed """ - from moulinette.utils.filesystem import read_ldif from yunohost.user import user_group_list - from yunohost.permission import permission_delete - from yunohost.utils.ldap import _get_ldap_interface - ldap = _get_ldap_interface() + from yunohost.permission import permission_create, permission_delete, user_permission_list, user_permission_update def copytree(src, dst, symlinks=False, ignore=None): for item in os.listdir(src): @@ -1358,26 +1354,25 @@ class RestoreManager(): restore_script = os.path.join(tmp_folder_for_app_restore, 'restore') # Restore permissions - # - # FIXME : why can't we instead use a simple yaml file to store the - # relevant info and recreate the permission object from it ... - # - if os.path.isfile(app_settings_in_archive + '/permission.ldif'): - filtred_entries = ['entryUUID', 'creatorsName', 'createTimestamp', 'entryCSN', 'structuralObjectClass', - 'modifiersName', 'modifyTimestamp', 'inheritPermission', 'memberUid'] - entries = read_ldif('%s/permission.ldif' % app_settings_in_archive, filtred_entries) - group_list = user_group_list()['groups'] - for dn, entry in entries: - # Remove the group which has been removed - for group in entry['groupPermission']: - group_name = group.split(',')[0].split('=')[1] - if group_name not in group_list: - entry['groupPermission'].remove(group) - if not ldap.add('cn=%s,ou=permission' % entry['cn'][0], entry): - raise YunohostError('apps_permission_restoration_failed', - permission=entry['cn'][0].split('.')[0], - app=entry['cn'][0].split('.')[1]) + if os.path.isfile('%s/permissions.yml' % app_settings_new_path): + + permissions = read_yaml('%s/permissions.yml' % app_settings_new_path) + existing_groups = user_group_list()['groups'] + + for permission_name, permission_infos in permissions: + + permission_create(permission_name, urls=permission_infos.get("urls", [])) + + if "allowed" not in permissions_infos: + logger.warning("'allowed' key corresponding to allowed groups for permission %s not found when restoring app %s ... You might need to reconfigure permissions yourself!" % (permission_name, app_instance_name)) + else: + groups = [g for g in permission_infos["allowed"] if g in existing_groups] + user_permission_update(permission_name, remove="all_users", add=groups) + + os.remove('%s/permissions.yml' % app_settings_new_path) else: + # Otherwise, we need to migrate the legacy permissions of this + # app (included in its settings.yml) from yunohost.tools import _get_migration_by_name setup_group_permission = _get_migration_by_name("setup_group_permission") setup_group_permission.migrate_app_permission(app=app_instance_name) From e40698ef2062346638a4492924a4dacf32f081f2 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 12 Sep 2019 02:25:52 +0200 Subject: [PATCH 0097/3170] Propagate changes on migration --- locales/en.json | 2 +- .../0011_setup_group_permission.py | 20 ++++++++----------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/locales/en.json b/locales/en.json index e69e06201..c370f821e 100644 --- a/locales/en.json +++ b/locales/en.json @@ -195,7 +195,6 @@ "dyndns_registration_failed": "Unable to register DynDNS domain: {error:s}", "dyndns_domain_not_provided": "Dyndns provider {provider:s} cannot provide domain {domain:s}.", "dyndns_unavailable": "Domain {domain:s} is not available.", - "error_when_removing_sftpuser_group": "Error when trying remove sftpusers group", "executing_command": "Executing command '{command:s}'…", "executing_script": "Executing script '{script:s}'…", "extracting": "Extracting…", @@ -355,6 +354,7 @@ "migration_0011_can_not_backup_before_migration": "The backup of the system before the migration failed. Migration failed. Error: {error:s}", "migration_0011_create_group": "Creating a group for each user...", "migration_0011_done": "Migration successful. You are now able to manage groups of users.", + "migration_0011_error_when_removing_sftpuser_group": "Error when trying remove sftpusers group", "migration_0011_LDAP_config_dirty": "It look like that you customized your LDAP configuration. For this migration the LDAP configuration need to be updated.\nYou need to save your actual configuration, reintialize the original configuration by the command 'yunohost tools regen-conf -f' and after retry the migration", "migration_0011_LDAP_update_failed": "LDAP update failed. Error: {error:s}", "migration_0011_migrate_permission": "Migrating permissions from apps settings to LDAP...", diff --git a/src/yunohost/data_migrations/0011_setup_group_permission.py b/src/yunohost/data_migrations/0011_setup_group_permission.py index d2924f0af..720e4ac36 100644 --- a/src/yunohost/data_migrations/0011_setup_group_permission.py +++ b/src/yunohost/data_migrations/0011_setup_group_permission.py @@ -1,17 +1,16 @@ -import yaml import time import os from moulinette import m18n from yunohost.utils.error import YunohostError from moulinette.utils.log import getActionLogger +from moulinette.utils.filesystem import read_yaml from yunohost.tools import Migration from yunohost.user import user_group_create, user_group_update from yunohost.app import app_setting, app_list from yunohost.regenconf import regen_conf -from yunohost.permission import permission_create, permission_sync_to_user -from yunohost.user import user_permission_add +from yunohost.permission import permission_create, user_permission_update, permission_sync_to_user logger = getActionLogger('yunohost.migration') @@ -19,6 +18,7 @@ logger = getActionLogger('yunohost.migration') # Tools used also for restoration ################################################### + class MyMigration(Migration): """ Update the LDAP DB to be able to store the permission @@ -38,10 +38,9 @@ class MyMigration(Migration): try: ldap.remove('cn=sftpusers,ou=groups') except: - logger.warn(m18n.n("error_when_removing_sftpuser_group")) + logger.warn(m18n.n("migration_0011_error_when_removing_sftpuser_group")) - with open('/usr/share/yunohost/yunohost-config/moulinette/ldap_scheme.yml') as f: - ldap_map = yaml.load(f) + ldap_map = read_yaml('/usr/share/yunohost/yunohost-config/moulinette/ldap_scheme.yml') try: attr_dict = ldap_map['parents']['ou=permission'] @@ -65,11 +64,9 @@ class MyMigration(Migration): username = user_info['uid'][0] ldap.update('uid=%s,ou=users' % username, {'objectClass': ['mailAccount', 'inetOrgPerson', 'posixAccount', 'userPermissionYnh']}) - user_group_create(username, gid=user_info['uidNumber'][0], sync_perm=False) - user_group_update(groupname=username, add=username, force=True, sync_perm=False) + user_group_create(username, gid=user_info['uidNumber'][0], primary_group=True, sync_perm=False) user_group_update(groupname='all_users', add=username, force=True, sync_perm=False) - def migrate_app_permission(self, app=None): logger.info(m18n.n("migration_0011_migrate_permission")) @@ -85,13 +82,12 @@ class MyMigration(Migration): domain = app_setting(app, 'domain') urls = [domain + path] if domain and path else None - permission_create(app, permission='main', urls=urls, default_allow=True, sync_perm=False) + permission_create(app+".main", urls=urls, sync_perm=False) if permission: allowed_group = permission.split(',') - user_permission_add([app], permission='main', group=allowed_group, sync_perm=False) + user_permission_update(app+".main", remove="all_users", add=allowed_group, sync_perm=False) app_setting(app, 'allowed_users', delete=True) - def run(self): # Check if the migration can be processed ldap_regen_conf_status = regen_conf(names=['slapd'], dry_run=True) From c0361430e26d42e0b8f167a330a20abff854655d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 12 Sep 2019 04:02:03 +0200 Subject: [PATCH 0098/3170] Try to simplify + comment the code of check_LDAP_db_integrity --- src/yunohost/tests/test_permission.py | 79 +++++++++++++++------------ 1 file changed, 43 insertions(+), 36 deletions(-) diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index 8222248b1..0373a6cbf 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -2,8 +2,10 @@ import pytest from moulinette.core import MoulinetteError from yunohost.app import app_install, app_remove, app_change_url, app_list -from yunohost.user import user_list, user_create, user_permission_list, user_delete, user_group_list, user_group_delete, user_permission_add, user_permission_remove, user_permission_clear -from yunohost.permission import permission_create, permission_update, permission_delete + +from yunohost.user import user_list, user_info, user_create, user_delete, user_update, \ + user_group_list, user_group_create, user_group_delete, user_group_update, user_group_info +from yunohost.permission import user_permission_update, user_permission_list, permission_create, permission_urls, permission_delete from yunohost.domain import _get_maindomain from yunohost.utils.error import YunohostError @@ -57,7 +59,7 @@ def check_LDAP_db_integrity(): # One part should be done automatically by the "memberOf" overlay of LDAP. # The other part is done by the the "permission_sync_to_user" function of the permission module - from yunohost.utils.ldap import _get_ldap_interface + from yunohost.utils.ldap import _get_ldap_interface, _ldap_path_extract ldap = _get_ldap_interface() user_search = ldap.search('ou=users,dc=yunohost,dc=org', @@ -76,60 +78,65 @@ def check_LDAP_db_integrity(): for user in user_search: user_dn = 'uid=' + user['uid'][0] + ',ou=users,dc=yunohost,dc=org' - group_list = [m.split("=")[1].split(",")[0] for m in user['memberOf']] - permission_list = [] - if 'permission' in user: - permission_list = [m.split("=")[1].split(",")[0] for m in user['permission']] + group_list = [_ldap_path_extract(m, "cn") for m in user['memberOf']] + permission_list = [_ldap_path_extract(m, "cn") for m in user.get('permission', [])] + # This user's DN sould be found in all groups it is a member of for group in group_list: assert user_dn in group_map[group]['member'] + + # This user's DN should be found in all perms it has access to for permission in permission_list: assert user_dn in permission_map[permission]['inheritPermission'] for permission in permission_search: permission_dn = 'cn=' + permission['cn'][0] + ',ou=permission,dc=yunohost,dc=org' - user_list = [] - group_list = [] - if 'inheritPermission' in permission: - user_list = [m.split("=")[1].split(",")[0] for m in permission['inheritPermission']] - assert set(user_list) == set(permission['memberUid']) - if 'groupPermission' in permission: - group_list = [m.split("=")[1].split(",")[0] for m in permission['groupPermission']] + # inheritPermission uid's should match memberUids + user_list = [_ldap_path_extract(m, "uid") for m in permission.get('inheritPermission', [])] + assert set(user_list) == set(permission.get('memberUid', [])) + + # This perm's DN should be found on all related users it is related to for user in user_list: assert permission_dn in user_map[user]['permission'] + + # Same for groups : we should find the permission's DN for all related groups + group_list = [_ldap_path_extract(m, "cn") for m in permission.get('groupPermission', [])] for group in group_list: assert permission_dn in group_map[group]['permission'] - if 'member' in group_map[group]: - user_list_in_group = [m.split("=")[1].split(",")[0] for m in group_map[group]['member']] - assert set(user_list_in_group) <= set(user_list) + + # The list of user in the group should be a subset of all users related to the current permission + users_in_group = [_ldap_path_extract(m, "uid") for m in group_map[group].get("member", [])] + assert set(users_in_group) <= set(user_list) for group in group_search: group_dn = 'cn=' + group['cn'][0] + ',ou=groups,dc=yunohost,dc=org' - user_list = [] - permission_list = [] - if 'member' in group: - user_list = [m.split("=")[1].split(",")[0] for m in group['member']] - if group['cn'][0] in user_list: - # If it's the main group of the user it's normal that it is not in the memberUid - g_list = list(user_list) - g_list.remove(group['cn'][0]) - if 'memberUid' in group: - assert set(g_list) == set(group['memberUid']) - else: - assert g_list == [] - else: - assert set(user_list) == set(group['memberUid']) - if 'permission' in group: - permission_list = [m.split("=")[1].split(",")[0] for m in group['permission']] + user_list = [_ldap_path_extract(m, "uid") for m in group.get("member", [])] + # For primary groups, we should find that : + # - len(user_list) is 1 (a primary group has only 1 member) + # - the group name should be an existing yunohost user + # - memberUid is empty (meaning no other member than the corresponding user) + if group['cn'][0] in user_list: + assert len(user_list) == 1 + assert group["cn"][0] in user_map + assert group.get('memberUid', []) == [] + # Otherwise, user_list and memberUid should have the same content + else: + assert set(user_list) == set(group.get('memberUid', [])) + + # For all users members, this group should be in the "memberOf" on the other side for user in user_list: assert group_dn in user_map[user]['memberOf'] + + # For all the permissions of this group, the group should be among the "groupPermission" on the other side + permission_list = [_ldap_path_extract(m, "cn") for m in group.get('permission', [])] for permission in permission_list: assert group_dn in permission_map[permission]['groupPermission'] - if 'inheritPermission' in permission_map: - allowed_user_list = [m.split("=")[1].split(",")[0] for m in permission_map[permission]['inheritPermission']] - assert set(user_list) <= set(allowed_user_list) + + # And the list of user of this group (user_list) should be a subset of all allowed users for this perm... + allowed_user_list = [_ldap_path_extract(m, "uid") for m in permission_map[permission].get('inheritPermission', [])] + assert set(user_list) <= set(allowed_user_list) def check_permission_for_apps(): From f1f65137965039bca3cc4d7b531b10babfadcd61 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 12 Sep 2019 04:02:36 +0200 Subject: [PATCH 0099/3170] Small tweaks for user group tests --- src/yunohost/tests/test_user-group.py | 86 ++++++++++++--------------- 1 file changed, 39 insertions(+), 47 deletions(-) diff --git a/src/yunohost/tests/test_user-group.py b/src/yunohost/tests/test_user-group.py index 34e515ea0..88644c3e6 100644 --- a/src/yunohost/tests/test_user-group.py +++ b/src/yunohost/tests/test_user-group.py @@ -1,7 +1,8 @@ import pytest from moulinette.core import MoulinetteError -from yunohost.user import user_list, user_info, user_group_list, user_create, user_delete, user_update, user_group_create, user_group_delete, user_group_update, user_group_info +from yunohost.user import user_list, user_info, user_create, user_delete, user_update, \ + user_group_list, user_group_create, user_group_delete, user_group_update, user_group_info from yunohost.domain import _get_maindomain from yunohost.utils.error import YunohostError from yunohost.tests.test_permission import check_LDAP_db_integrity @@ -82,12 +83,13 @@ def test_del_user(): assert "alice" not in group_res assert "alice" not in group_res['all_users']['members'] -def test_add_group(): +def test_create_group(): user_group_create("adminsys") group_res = user_group_list()['groups'] assert "adminsys" in group_res - assert "members" not in group_res['adminsys'] + assert "members" in group_res['adminsys'].keys() + assert group_res["adminsys"]["members"] == [] def test_del_group(): user_group_delete("dev") @@ -99,112 +101,102 @@ def test_del_group(): # Error on create / remove function # -def test_add_bad_user_1(): - # Check email already exist +def test_create_user_with_mail_address_already_taken(): with pytest.raises(MoulinetteError): user_create("alice2", "Alice", "White", "alice@" + maindomain, "test123Ynh") -def test_add_bad_user_2(): - # Check to short password +def test_create_user_with_password_too_simple(): with pytest.raises(MoulinetteError): user_create("other", "Alice", "White", "other@" + maindomain, "12") -def test_add_bad_user_3(): - # Check user already exist +def test_create_user_already_exists(): with pytest.raises(MoulinetteError): user_create("alice", "Alice", "White", "other@" + maindomain, "test123Ynh") -def test_del_bad_user_1(): - # Check user not found +def test_del_user_that_does_not_exist(): with pytest.raises(MoulinetteError): - user_delete("not_exit") + user_delete("doesnt_exist") -def test_add_bad_group_1(): +def test_create_group_all_users(): # Check groups already exist with special group "all_users" with pytest.raises(YunohostError): user_group_create("all_users") -def test_add_bad_group_2(): - # Check groups already exist (for standard groups) +def test_create_group_already_exists(): + # Check groups already exist (regular groups) with pytest.raises(MoulinetteError): user_group_create("dev") -def test_del_bad_group_1(): - # Check not allowed to remove this groups +def test_del_group_all_users(): with pytest.raises(YunohostError): user_group_delete("all_users") -def test_del_bad_group_2(): - # Check groups not found +def test_del_group_that_does_not_exist(): with pytest.raises(MoulinetteError): - user_group_delete("not_exit") + user_group_delete("doesnt_exist") # # Update function # -def test_update_user_1(): +def test_update_user(): user_update("alice", firstname="NewName", lastname="NewLast") info = user_info("alice") - assert "NewName" == info['firstname'] - assert "NewLast" == info['lastname'] + assert info['firstname'] == "NewName" + assert info['lastname'] == "NewLast" -def test_update_group_1(): +def test_update_group_add_user(): user_group_update("dev", add=["bob"]) group_res = user_group_list()['groups'] - assert set(["alice", "bob"]) == set(group_res['dev']['members']) + assert set(group_res['dev']['members']) == set(["alice", "bob"]) -def test_update_group_2(): - # Try to add a user in a group when the user is already in +def test_update_group_add_user_already_in(): user_group_update("apps", add=["bob"]) group_res = user_group_list()['groups'] - assert ["bob"] == group_res['apps']['members'] + assert group_res['apps']['members'] == ["bob"] -def test_update_group_3(): - # Try to remove a user in a group +def test_update_group_remove_user(): user_group_update("apps", remove=["bob"]) group_res = user_group_list()['groups'] - assert "members" not in group_res['apps'] + assert group_res['apps']['members'] == [] -def test_update_group_4(): - # Try to remove a user in a group when it is not already in +def test_update_group_remove_user_not_already_in(): user_group_update("apps", remove=["jack"]) group_res = user_group_list()['groups'] - assert ["bob"] == group_res['apps']['members'] + assert group_res['apps']['members'] == ["bob"] # # Error on update functions # -def test_bad_update_user_1(): - # Check user not found +def test_update_user_that_doesnt_exist(): with pytest.raises(YunohostError): - user_update("not_exit", firstname="NewName", lastname="NewLast") + user_update("doesnt_exist", firstname="NewName", lastname="NewLast") - -def bad_update_group_1(): +def test_update_group_that_doesnt_exist(): # Check groups not found with pytest.raises(YunohostError): - user_group_update("not_exit", add=["alice"]) + user_group_update("doesnt_exist", add=["alice"]) -def test_bad_update_group_2(): - # Check remove user in groups "all_users" not allowed +def test_update_group_all_users_manually(): with pytest.raises(YunohostError): user_group_update("all_users", remove=["alice"]) -def test_bad_update_group_3(): - # Check remove user in it own group not allowed + assert "alice" in user_group_list()["groups"]["all_users"]["members"] + +def test_update_group_primary_manually(): with pytest.raises(YunohostError): user_group_update("alice", remove=["alice"]) + assert "alice" in user_group_list()["groups"]["alice"]["members"] -def test_bad_update_group_1(): +def test_update_group_add_user_that_doesnt_exist(): # Check add bad user in group with pytest.raises(YunohostError): - user_group_update("dev", add=["not_exist"]) + user_group_update("dev", add=["doesnt_exist"]) - assert "not_exist" not in user_group_list()["groups"]["dev"] + assert "doesnt_exist" not in user_group_list()["groups"]["dev"]["members"] From 68db93cd635a8ce5720f3e8b1ca105e6fa4a27d4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 12 Sep 2019 04:03:04 +0200 Subject: [PATCH 0100/3170] Fix an issue about groups not being properly cleaned and perms synced when deleting a user --- src/yunohost/user.py | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index e1719d3a6..ef2a7d523 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -245,9 +245,18 @@ def user_delete(operation_logger, username, purge=False): """ from yunohost.hook import hook_callback from yunohost.utils.ldap import _get_ldap_interface + from yunohost.permission import permission_sync_to_user operation_logger.start() + user_group_update("all_users", remove=username, force=True, sync_perm=False) + for group, infos in user_group_list()["groups"].items(): + # If the user is in this group (and it's not the primary group), + # remove the member from the group + if username != group and username in infos["members"]: + user_group_update(group, remove=username, sync_perm=False) + user_group_delete(username, force=True, sync_perm=True) + ldap = _get_ldap_interface() if ldap.remove('uid=%s,ou=users' % username): # Invalidate passwd to take user deletion into account @@ -259,19 +268,6 @@ def user_delete(operation_logger, username, purge=False): else: raise YunohostError('user_deletion_failed') - user_group_delete(username, force=True, sync_perm=True) - - group_list = ldap.search('ou=groups,dc=yunohost,dc=org', - '(&(objectclass=groupOfNamesYnh)(memberUid=%s))' - % username, ['cn']) - for group in group_list: - user_list = ldap.search('ou=groups,dc=yunohost,dc=org', - 'cn=' + group['cn'][0], - ['memberUid'])[0] - user_list['memberUid'].remove(username) - if not ldap.update('cn=%s,ou=groups' % group['cn'][0], user_list): - raise YunohostError('group_update_failed') - hook_callback('post_user_delete', args=[username, purge]) logger.success(m18n.n('user_deleted')) From fe8f7f2210d9cc398dc27f9fa16b20cc11bd79b9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 12 Sep 2019 17:49:14 +0200 Subject: [PATCH 0101/3170] Update permission helper : have a single helper to manage urls, and a helper to add/remove groups to permission --- data/helpers.d/setting | 49 +++++++++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index 0e432d916..502da1ed7 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -261,32 +261,51 @@ ynh_permission_delete() { yunohost tools shell -c "from yunohost.permission import permission_delete; permission_delete('$app.$permission', sync_perm=False)" } -# Add a path managed by the SSO +# Manage urls related to a permission # -# usage: ynh_permission_add_url --permission "permission" --url "url" ["url" ...] +# usage: ynh_permission_urls --permission "permission" --add "url" ["url" ...] --remove "url" ["url" ...] # | arg: permission - the name for the permission (by default a permission named "main" is removed automatically when the app is removed) -# | arg: urls - (optional) a list of FULL urls for the permission (e.g. domain.tld/apps/admin) +# | arg: add - (optional) a list of FULL urls to add to the permission (e.g. domain.tld/apps/admin) +# | arg: remove - (optional) a list of FULL urls to remove from the permission (e.g. other.tld/apps/admin) # -ynh_permission_add_url() { - declare -Ar args_array=([p]=permission= [u]=urls= ) +ynh_permission_urls() { + declare -Ar args_array=([p]=permission= [a]=add= [r]=remove=) local permission - local urls + local add + local remove ynh_handle_getopts_args "$@" - yunohost tools shell -c "from yunohost.permission import permission_urls; permission_urls('$app.$permission', add=['${urls//';'/"','"}'], sync_perm=False)" + if [[ -n ${add:-} ]]; then + add=",add=['${add//';'/"','"}']" + fi + if [[ -n ${remove:-} ]]; then + remove=",remove=['${remove//';'/"','"}']" + fi + + yunohost tools shell -c "from yunohost.permission import permission_urls; permission_urls('$app.$permission' ${add:-} ${remove:-})" } -# Remove a path managed by the SSO +# Update a permission for the app # -# usage: ynh_permission_del_path --app "app" --permission "permission" --url "url" ["url" ...] -# | arg: permission - the name for the permission (by default a permission named "main" is removed automatically when the app is removed) -# | arg: urls - (optional) a list of FULL urls for the permission (e.g. domain.tld/apps/admin) +# usage: ynh_permission_update --permission "permission" --add "group" ["group" ...] --remove "group" ["group" ...] +# | arg: permission - the name for the permission (by default a permission named "main" already exist) +# | arg: add - the list of group or users to enable add to the permission +# | arg: remove - the list of group or users to remove from the permission # -ynh_permission_remove_url() { - declare -Ar args_array=([p]=permission= [u]=urls= ) +# example: ynh_permission_update --permission admin --add samdoe --remove all_users +ynh_permission_update() { + declare -Ar args_array=( [p]=permission= [a]=add= [r]=remove= ) local permission - local urls + local add + local remove ynh_handle_getopts_args "$@" - yunohost tools shell -c "from yunohost.permission import permission_urls; permission_urls('$app.$permission', remove=['${url//';'/"','"}'], sync_perm=False)" + if [[ -n ${add:-} ]]; then + add="--add ${add//';'/" "}" + fi + if [[ -n ${remove:-} ]]; then + remove="--remove ${remove//';'/" "} " + fi + + yunohost user permission update "$app.$permission" ${add:-} ${remove:-} } From b912cd0aecd48ec36d87d611447c91c97d293939 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 12 Sep 2019 17:49:35 +0200 Subject: [PATCH 0102/3170] Propagate all changes to tests --- src/yunohost/tests/test_permission.py | 353 +++++++++++--------------- 1 file changed, 153 insertions(+), 200 deletions(-) diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index 0373a6cbf..2df6362a9 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -5,7 +5,8 @@ from yunohost.app import app_install, app_remove, app_change_url, app_list from yunohost.user import user_list, user_info, user_create, user_delete, user_update, \ user_group_list, user_group_create, user_group_delete, user_group_update, user_group_info -from yunohost.permission import user_permission_update, user_permission_list, permission_create, permission_urls, permission_delete +from yunohost.permission import user_permission_update, user_permission_list, user_permission_reset, \ + permission_create, permission_urls, permission_delete from yunohost.domain import _get_maindomain from yunohost.utils.error import YunohostError @@ -20,20 +21,18 @@ def clean_user_groups_permission(): if g != "all_users": user_group_delete(g) - for a, per in user_permission_list()['permissions'].items(): - if a in ['wiki', 'blog', 'site']: - for p in per: - permission_delete(a, p, force=True, sync_perm=False) + for p in user_permission_list()['permissions']: + if any(p.startswith(name) for name in ["wiki", "blog", "site", "permissions_app"]): + permission_delete(p, force=True, sync_perm=False) def setup_function(function): clean_user_groups_permission() user_create("alice", "Alice", "White", "alice@" + maindomain, "test123Ynh") user_create("bob", "Bob", "Snow", "bob@" + maindomain, "test123Ynh") - permission_create("wiki", "main", [maindomain + "/wiki"], sync_perm=False) - permission_create("blog", "main", sync_perm=False) - - user_permission_add(["blog"], "main", group="alice") + permission_create("wiki.main", urls=[maindomain + "/wiki"], sync_perm=False) + permission_create("blog.main", sync_perm=False) + user_permission_update("blog.main", remove="all_users", add="alice") def teardown_function(function): clean_user_groups_permission() @@ -144,100 +143,89 @@ def check_permission_for_apps(): # and we don't have any permission linked to no apps. The only exception who is not liked to an app # is mail, xmpp, and sftp - from yunohost.utils.ldap import _get_ldap_interface - ldap = _get_ldap_interface() - permission_search = ldap.search('ou=permission,dc=yunohost,dc=org', - '(objectclass=permissionYnh)', - ['cn', 'groupPermission', 'inheritPermission', 'memberUid']) + app_perms = user_permission_list(ignore_system_perms=True)["permissions"].keys() + + # Keep only the prefix so that + # ["foo.main", "foo.pwet", "bar.main"] + # becomes + # {"bar", "foo"} + # and compare this to the list of installed apps ... + + app_perms_prefix = set(p.split(".")[0] for p in app_perms) installed_apps = {app['id'] for app in app_list(installed=True)['apps']} - permission_list_set = {permission['cn'][0].split(".")[1] for permission in permission_search} - extra_service_permission = set(['mail', 'xmpp']) - if 'sftp' in permission_list_set: - extra_service_permission.add('sftp') - assert installed_apps == permission_list_set - extra_service_permission + assert installed_apps == app_perms_prefix # # List functions # -def test_list_permission(): - res = user_permission_list()['permissions'] +def test_permission_list(): + res = user_permission_list(full=True)['permissions'] - assert "wiki" in res - assert "main" in res['wiki'] - assert "blog" in res - assert "main" in res['blog'] - assert "mail" in res - assert "main" in res['mail'] - assert "xmpp" in res - assert "main" in res['xmpp'] - assert ["all_users"] == res['wiki']['main']['allowed_groups'] - assert ["alice"] == res['blog']['main']['allowed_groups'] - assert set(["alice", "bob"]) == set(res['wiki']['main']['allowed_users']) - assert ["alice"] == res['blog']['main']['allowed_users'] - assert [maindomain + "/wiki"] == res['wiki']['main']['URL'] + assert "wiki.main" in res + assert "blog.main" in res + assert "mail.main" in res + assert "xmpp.main" in res + assert res['wiki.main']['allowed'] == ["all_users"] + assert res['blog.main']['allowed'] == ["alice"] + assert set(res['wiki.main']['corresponding_users']) == set(["alice", "bob"]) + assert res['blog.main']['corresponding_users'] == ["alice"] + assert res['wiki.main']['urls'] == [maindomain + "/wiki"] # # Create - Remove functions # -def test_add_permission_1(): - permission_create("site", "test") +def test_permission_create_main(): + permission_create("site.main") + + res = user_permission_list(full=True)['permissions'] + assert "site.main" in res + assert res['site.main']['allowed'] == ["all_users"] + assert set(res['site.main']['corresponding_users']) == set(["alice", "bob"]) + + +def test_permission_create_extra(): + permission_create("site.test") + + res = user_permission_list(full=True)['permissions'] + assert "site.test" in res + # all_users is only enabled by default on .main perms + assert "all_users" not in res['site.test']['allowed'] + assert res['site.test']['corresponding_users'] == [] + +def test_permission_delete(): + permission_delete("wiki.main", force=True) res = user_permission_list()['permissions'] - assert "site" in res - assert "test" in res['site'] - assert "all_users" in res['site']['test']['allowed_groups'] - assert set(["alice", "bob"]) == set(res['site']['test']['allowed_users']) - -def test_add_permission_2(): - permission_create("site", "main", default_allow=False) - - res = user_permission_list()['permissions'] - assert "site" in res - assert "main" in res['site'] - assert [] == res['site']['main']['allowed_groups'] - assert [] == res['site']['main']['allowed_users'] - -def test_remove_permission(): - permission_delete("wiki", "main", force=True) - - res = user_permission_list()['permissions'] - assert "wiki" not in res + assert "wiki.main" not in res # # Error on create - remove function # -def test_add_bad_permission(): - # Create permission with same name +def test_permission_create_already_existing(): with pytest.raises(YunohostError): - permission_create("wiki", "main") + permission_create("wiki.main") -def test_remove_bad_permission(): - # Remove not existant permission +def test_permission_delete_doesnt_existing(): with pytest.raises(MoulinetteError): - permission_delete("non_exit", "main", force=True) + permission_delete("doesnt.exist", force=True) res = user_permission_list()['permissions'] - assert "wiki" in res - assert "main" in res['wiki'] - assert "blog" in res - assert "main" in res['blog'] - assert "mail" in res - assert "main" in res['mail'] - assert "xmpp" in res - assert "main" in res['xmpp'] + assert "wiki.main" in res + assert "blog.main" in res + assert "mail.main" in res + assert "xmpp.main" in res -def test_remove_main_permission(): +def test_permission_delete_main_without_force(): with pytest.raises(YunohostError): - permission_delete("blog", "main") + permission_delete("blog.main") res = user_permission_list()['permissions'] - assert "mail" in res - assert "main" in res['mail'] + assert "blog.main" in res # # Update functions @@ -245,137 +233,100 @@ def test_remove_main_permission(): # user side functions -def test_allow_first_group(): - # Remove permission to all_users and define per users - user_permission_add(["wiki"], "main", group="alice") +def test_permission_add_group(): + user_permission_update("wiki.main", add="alice") - res = user_permission_list()['permissions'] - assert ['alice'] == res['wiki']['main']['allowed_users'] - assert ['alice'] == res['wiki']['main']['allowed_groups'] + res = user_permission_list(full=True)['permissions'] + assert set(res['wiki.main']['allowed']) == set(["all_users", "alice"]) + assert set(res['wiki.main']['corresponding_users']) == set(["alice", "bob"]) -def test_allow_other_group(): - # Allow new user in a permission - user_permission_add(["blog"], "main", group="bob") +def test_permission_remove_group(): + user_permission_update("blog.main", remove="alice") - res = user_permission_list()['permissions'] - assert set(["alice", "bob"]) == set(res['blog']['main']['allowed_users']) - assert set(["alice", "bob"]) == set(res['blog']['main']['allowed_groups']) + res = user_permission_list(full=True)['permissions'] + assert res['blog.main']['allowed'] == [] + assert res['blog.main']['corresponding_users'] == [] -def test_disallow_group_1(): - # Disallow a user in a permission - user_permission_remove(["blog"], "main", group="alice") +def test_permission_add_and_remove_group(): + user_permission_update("wiki.main", add="alice", remove="all_users") - res = user_permission_list()['permissions'] - assert [] == res['blog']['main']['allowed_users'] - assert [] == res['blog']['main']['allowed_groups'] + res = user_permission_list(full=True)['permissions'] + assert res['wiki.main']['allowed'] == ["alice"] + assert res['wiki.main']['corresponding_users'] == ["alice"] -def test_allow_group_1(): - # Allow a user when he is already allowed - user_permission_add(["blog"], "main", group="alice") +def test_permission_add_group_already_allowed(): + user_permission_update("blog.main", add="alice") - res = user_permission_list()['permissions'] - assert ["alice"] == res['blog']['main']['allowed_users'] - assert ["alice"] == res['blog']['main']['allowed_groups'] + res = user_permission_list(full=True)['permissions'] + assert res['blog.main']['allowed'] == ["alice"] + assert res['blog.main']['corresponding_users'] == ["alice"] -def test_disallow_group_1(): - # Disallow a user when he is already disallowed - user_permission_remove(["blog"], "main", group="bob") +def test_permission_remove_group_already_not_allowed(): + user_permission_update("blog.main", remove="bob") - res = user_permission_list()['permissions'] - assert ["alice"] == res['blog']['main']['allowed_users'] - assert ["alice"] == res['blog']['main']['allowed_groups'] + res = user_permission_list(full=True)['permissions'] + assert res['blog.main']['allowed'] == ["alice"] + assert res['blog.main']['corresponding_users'] == ["alice"] -def test_reset_permission(): +def test_permission_reset(): # Reset permission - user_permission_clear(["blog"], "main") + user_permission_reset("blog.main") - res = user_permission_list()['permissions'] - assert set(["alice", "bob"]) == set(res['blog']['main']['allowed_users']) - assert ["all_users"] == res['blog']['main']['allowed_groups'] - -# internal functions - -def test_add_url_1(): - # Add URL in permission which hasn't any URL defined - permission_update("blog", "main", add_url=[maindomain + "/testA"]) - - res = user_permission_list()['permissions'] - assert [maindomain + "/testA"] == res['blog']['main']['URL'] - -def test_add_url_2(): - # Add a second URL in a permission - permission_update("wiki", "main", add_url=[maindomain + "/testA"]) - - res = user_permission_list()['permissions'] - assert set([maindomain + "/testA", maindomain + "/wiki"]) == set(res['wiki']['main']['URL']) - -def test_remove_url_1(): - permission_update("wiki", "main", remove_url=[maindomain + "/wiki"]) - - res = user_permission_list()['permissions'] - assert 'URL' not in res['wiki']['main'] - -def test_add_url_3(): - # Add a url already added - permission_update("wiki", "main", add_url=[maindomain + "/wiki"]) - - res = user_permission_list()['permissions'] - assert [maindomain + "/wiki"] == res['wiki']['main']['URL'] - -def test_remove_url_2(): - # Remove a url not added (with a permission which contain some URL) - permission_update("wiki", "main", remove_url=[maindomain + "/not_exist"]) - - res = user_permission_list()['permissions'] - assert [maindomain + "/wiki"] == res['wiki']['main']['URL'] - -def test_remove_url_2(): - # Remove a url not added (with a permission which contain no URL) - permission_update("blog", "main", remove_url=[maindomain + "/not_exist"]) - - res = user_permission_list()['permissions'] - assert 'URL' not in res['blog']['main'] + res = user_permission_list(full=True)['permissions'] + assert res['blog.main']['allowed'] == ["all_users"] + assert set(res['blog.main']['corresponding_users']) == set(["alice", "bob"]) # # Error on update function # -def test_disallow_bad_group_1(): - # Disallow a group when the group all_users is allowed +def test_permission_add_group_that_doesnt_exist(): with pytest.raises(YunohostError): - user_permission_remove("wiki", "main", group="alice") + user_permission_update("blog.main", add="doesnt_exist") - res = user_permission_list()['permissions'] - assert ["all_users"] == res['wiki']['main']['allowed_groups'] - assert set(["alice", "bob"]) == set(res['wiki']['main']['allowed_users']) + res = user_permission_list(full=True)['permissions'] + assert res['blog.main']['allowed'] == ["alice"] + assert res['blog.main']['corresponding_users'] == ["alice"] -def test_allow_bad_user(): - # Allow a non existant group +def test_permission_update_permission_that_doesnt_exist(): with pytest.raises(YunohostError): - user_permission_add(["blog"], "main", group="not_exist") + user_permission_update("doesnt.exist", add="alice") - res = user_permission_list()['permissions'] - assert ["alice"] == res['blog']['main']['allowed_groups'] - assert ["alice"] == res['blog']['main']['allowed_users'] -def test_disallow_bad_group_2(): - # Disallow a non existant group - with pytest.raises(YunohostError): - user_permission_remove(["blog"], "main", group="not_exist") +# Permission url management - res = user_permission_list()['permissions'] - assert ["alice"] == res['blog']['main']['allowed_groups'] - assert ["alice"] == res['blog']['main']['allowed_users'] +def test_permission_add_url(): + permission_urls("blog.main", add=[maindomain + "/testA"]) -def test_allow_bad_permission_1(): - # Allow a user to a non existant permission - with pytest.raises(YunohostError): - user_permission_add(["wiki"], "not_exit", group="alice") + res = user_permission_list(full=True)['permissions'] + assert res["blog.main"]["urls"] == [maindomain + "/testA"] -def test_allow_bad_permission_2(): - # Allow a user to a non existant permission - with pytest.raises(YunohostError): - user_permission_add(["not_exit"], "main", group="alice") +def test_permission_add_second_url(): + permission_urls("wiki.main", add=[maindomain + "/testA"]) + + res = user_permission_list(full=True)['permissions'] + assert set(res["wiki.main"]["urls"]) == set([maindomain + "/testA", maindomain + "/wiki"]) + +def test_permission_remove_url(): + permission_urls("wiki.main", remove=[maindomain + "/wiki"]) + + res = user_permission_list(full=True)['permissions'] + assert res["wiki.main"]["urls"] == [] + +def test_permission_add_url_already_added(): + res = user_permission_list(full=True)['permissions'] + assert res["wiki.main"]["urls"] == [maindomain + "/wiki"] + + permission_urls("wiki.main", add=[maindomain + "/wiki"]) + + res = user_permission_list(full=True)['permissions'] + assert res["wiki.main"]["urls"] == [maindomain + "/wiki"] + +def test_permission_remove_url_not_added(): + permission_urls("wiki.main", remove=[maindomain + "/doesnt_exist"]) + + res = user_permission_list(full=True)['permissions'] + assert res['wiki.main']['urls'] == [maindomain + "/wiki"] # # Application interaction @@ -385,42 +336,44 @@ def test_install_app(): app_install("./tests/apps/permissions_app_ynh", args="domain=%s&path=%s&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True) - res = user_permission_list()['permissions'] - assert "permissions_app" in res - assert "main" in res['permissions_app'] - assert [maindomain + "/urlpermissionapp"] == res['permissions_app']['main']['URL'] - assert [maindomain + "/urlpermissionapp/admin"] == res['permissions_app']['admin']['URL'] - assert [maindomain + "/urlpermissionapp/dev"] == res['permissions_app']['dev']['URL'] + res = user_permission_list(full=True)['permissions'] + assert "permissions_app.main" in res + assert "permissions_app.admin" in res + assert "permissions_app.dev" in res + assert res['permissions_app.main']['urls'] == [maindomain + "/urlpermissionapp"] + assert res['permissions_app.admin']['urls'] == [maindomain + "/urlpermissionapp/admin"] + assert res['permissions_app.dev']['urls'] == [maindomain + "/urlpermissionapp/dev"] - assert ["all_users"] == res['permissions_app']['main']['allowed_groups'] - assert set(["alice", "bob"]) == set(res['permissions_app']['main']['allowed_users']) + assert res['permissions_app.main']['allowed'] == ["all_users"] + assert set(res['permissions_app.main']['corresponding_users']) == set(["alice", "bob"]) - assert ["alice"] == res['permissions_app']['admin']['allowed_groups'] - assert ["alice"] == res['permissions_app']['admin']['allowed_users'] + assert res['permissions_app.admin']['allowed'] == ["alice"] + assert res['permissions_app.admin']['corresponding_users'] == ["alice"] - assert ["all_users"] == res['permissions_app']['dev']['allowed_groups'] - assert set(["alice", "bob"]) == set(res['permissions_app']['dev']['allowed_users']) + assert res['permissions_app.dev']['allowed'] == [] + assert set(res['permissions_app.dev']['corresponding_users']) == set() def test_remove_app(): app_install("./tests/apps/permissions_app_ynh", args="domain=%s&path=%s&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True) app_remove("permissions_app") - res = user_permission_list()['permissions'] - assert "permissions_app" not in res + # Check all permissions for this app got deleted + res = user_permission_list(full=True)['permissions'] + assert not any(p.startswith("permissions_app.") for p in res.keys()) def test_change_url(): app_install("./tests/apps/permissions_app_ynh", args="domain=%s&path=%s&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True) - res = user_permission_list()['permissions'] - assert [maindomain + "/urlpermissionapp"] == res['permissions_app']['main']['URL'] - assert [maindomain + "/urlpermissionapp/admin"] == res['permissions_app']['admin']['URL'] - assert [maindomain + "/urlpermissionapp/dev"] == res['permissions_app']['dev']['URL'] + res = user_permission_list(full=True)['permissions'] + assert res['permissions_app.main']['urls'] == [maindomain + "/urlpermissionapp"] + assert res['permissions_app.admin']['urls'] == [maindomain + "/urlpermissionapp/admin"] + assert res['permissions_app.dev']['urls'] == [maindomain + "/urlpermissionapp/dev"] app_change_url("permissions_app", maindomain, "/newchangeurl") - res = user_permission_list()['permissions'] - assert [maindomain + "/newchangeurl"] == res['permissions_app']['main']['URL'] - assert [maindomain + "/newchangeurl/admin"] == res['permissions_app']['admin']['URL'] - assert [maindomain + "/newchangeurl/dev"] == res['permissions_app']['dev']['URL'] + res = user_permission_list(full=True)['permissions'] + assert res['permissions_app.main']['urls'] == [maindomain + "/newchangeurl"] + assert res['permissions_app.admin']['urls'] == [maindomain + "/newchangeurl/admin"] + assert res['permissions_app.dev']['urls'] == [maindomain + "/newchangeurl/dev"] From 2e14834e6b98ed29277c57d8f2e4ffce88b04cd8 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 12 Sep 2019 17:50:52 +0200 Subject: [PATCH 0103/3170] Misc fixes following tests --- locales/en.json | 10 +++++----- src/yunohost/app.py | 6 ++---- src/yunohost/permission.py | 6 +++--- src/yunohost/user.py | 2 +- 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/locales/en.json b/locales/en.json index c370f821e..7a16ebd0c 100644 --- a/locales/en.json +++ b/locales/en.json @@ -274,9 +274,9 @@ "log_dyndns_subscribe": "Subscribe to a YunoHost subdomain '{}'", "log_dyndns_update": "Update the ip associated with your YunoHost subdomain '{}'", "log_letsencrypt_cert_install": "Install Let's encrypt certificate on '{}' domain", - "log_permission_create": "Create permission '{permission}'", - "log_permission_delete": "Delete permission '{permission}'", - "log_permission_urls": "Update urls related to permission '{permission}'", + "log_permission_create": "Create permission '{}'", + "log_permission_delete": "Delete permission '{}'", + "log_permission_urls": "Update urls related to permission '{}'", "log_selfsigned_cert_install": "Install self signed certificate on '{}' domain", "log_letsencrypt_cert_renew": "Renew '{}' Let's encrypt certificate", "log_regen_conf": "Regenerate system configurations '{}'", @@ -286,8 +286,8 @@ "log_user_group_delete": "Delete '{}' group", "log_user_group_update": "Update '{}' group", "log_user_update": "Update information of '{}' user", - "log_user_permission_update": "Update accesses for permission '{permission}'", - "log_user_permission_reset": "Reset permission '{permission}'", + "log_user_permission_update": "Update accesses for permission '{}'", + "log_user_permission_reset": "Reset permission '{}'", "log_tools_maindomain": "Make '{}' as main domain", "log_tools_migrations_migrate_forward": "Migrate forward", "log_tools_postinstall": "Postinstall your YunoHost server", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index f505dd088..b3c36d059 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -735,11 +735,9 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu if packages.dpkg_is_broken(): raise YunohostError("dpkg_is_broken") - from yunohost.utils.ldap import _get_ldap_interface from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback from yunohost.log import OperationLogger - from yunohost.permission import permission_create, permission_urls, permission_delete, permission_sync_to_user - ldap = _get_ldap_interface() + from yunohost.permission import user_permission_list, permission_create, permission_urls, permission_delete, permission_sync_to_user # Fetch or extract sources if not os.path.exists(INSTALL_TMP): @@ -976,7 +974,7 @@ def app_remove(operation_logger, app): """ from yunohost.hook import hook_exec, hook_remove, hook_callback - from yunohost.permission import permission_delete, permission_sync_to_user + from yunohost.permission import user_permission_list, permission_delete, permission_sync_to_user if not _is_installed(app): raise YunohostError('app_not_installed', app=app, all_apps=_get_all_installed_apps_id()) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 4d935d3c0..e5035b0ad 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -45,7 +45,7 @@ SYSTEM_PERMS = ["mail", "xmpp", "stfp"] # -def user_permission_list(short=False, full=False, ignore_system_perms=True): +def user_permission_list(short=False, full=False, ignore_system_perms=False): """ List permissions and corresponding accesses """ @@ -273,13 +273,13 @@ def permission_create(operation_logger, permission, urls=None, sync_perm=True): attr_dict = { 'objectClass': ['top', 'permissionYnh', 'posixGroup'], - 'cn': permission, + 'cn': str(permission), 'gidNumber': gid, } # For main permission, we add all users by default if permission.endswith(".main"): - attr_dict['groupPermission'] = 'cn=all_users,ou=groups,dc=yunohost,dc=org' + attr_dict['groupPermission'] = ['cn=all_users,ou=groups,dc=yunohost,dc=org'] if urls: attr_dict['URL'] = [_normalize_url(url) for url in urls] diff --git a/src/yunohost/user.py b/src/yunohost/user.py index ef2a7d523..bb4d6aed2 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -449,7 +449,7 @@ def user_info(username): if service_status("dovecot")["status"] != "running": logger.warning(m18n.n('mailbox_used_space_dovecot_down')) - elif username not in user_permission_list()["permissions"]["mail.main"]["allowed_users"]: + 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] From bdad4ffd7114602aea0303793ebdc912da054905 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 12 Sep 2019 18:34:17 +0200 Subject: [PATCH 0104/3170] c.f. issue 1405 ... those 'if ldap.stuff()' are complete bullshit from the very beginning since they never return False : instead they trigger an exception which means the current error management is completely meaningless ... so this refactorize all the places if found those + add proper error messages --- locales/en.json | 22 ++++---- src/yunohost/domain.py | 16 ++++-- src/yunohost/permission.py | 109 +++++++++++++++++++++---------------- src/yunohost/user.py | 109 ++++++++++++++++++++----------------- 4 files changed, 144 insertions(+), 112 deletions(-) diff --git a/locales/en.json b/locales/en.json index 7a16ebd0c..d60a432ab 100644 --- a/locales/en.json +++ b/locales/en.json @@ -164,9 +164,9 @@ "domain_cannot_remove_main": "Cannot remove main domain. Set a new main domain first", "domain_cert_gen_failed": "Unable to generate certificate", "domain_created": "The domain has been created", - "domain_creation_failed": "Unable to create domain", + "domain_creation_failed": "Failed to create domain {domain}: {error}", "domain_deleted": "The domain has been deleted", - "domain_deletion_failed": "Unable to delete domain", + "domain_deletion_failed": "Failed to delete domain {domain}: {error}", "domain_dns_conf_is_just_a_recommendation": "This command shows you what is the *recommended* configuration. It does not actually set up the DNS configuration for you. It is your responsability to configure your DNS zone in your registrar according to this recommendation.", "domain_dyndns_already_subscribed": "You've already subscribed to a DynDNS domain", "domain_dyndns_dynette_is_unreachable": "Unable to reach YunoHost dynette, either your YunoHost is not correctly connected to the internet or the dynette server is down. Error: {error}", @@ -229,14 +229,14 @@ "group_already_exist": "Group {group} already exist", "group_already_exist_on_system": "Group {group} already exists in the system group", "group_created": "Group '{group}' successfully created", - "group_creation_failed": "Group creation failed for group '{group}'", + "group_creation_failed": "Failed to create group {group}: {error}", "group_cannot_be_edited": "The group {group} cannot be edited manually.", "group_cannot_be_deleted": "The group {group} cannot be deleted manually.", "group_deleted": "Group '{group}' deleted", - "group_deletion_failed": "Group '{group} 'deletion failed", + "group_deletion_failed": "Failed to delete group {group}: {error}", "group_unknown": "Group {group} unknown", "group_updated": "Group '{group}' updated", - "group_update_failed": "Group update failed for group '{group}'", + "group_update_failed": "Failed to update group {group}: {error}", "group_user_already_in_group": "User {user} is already in group {group}", "group_user_not_in_group": "User {user} is not in group {group}", "hook_exec_failed": "Script execution failed: {path:s}", @@ -430,11 +430,11 @@ "permission_already_exist": "Permission '{permission}' already exists", "permission_cannot_remove_main": "Removing a main permission is not allowed", "permission_created": "Permission '{permission}' created", - "permission_creation_failed": "Failed to create permission '{permission}'", + "permission_creation_failed": "Failed to create permission '{permission}': {error}", "permission_deleted": "Permission '{permission}' deleted", - "permission_deletion_failed": "Failed to delete permission '{permission}'", + "permission_deletion_failed": "Failed to delete permission '{permission}': {error}", "permission_not_found": "Permission '{permission}' does not seem to exist ?", - "permission_update_failed": "Failed to update permission '{permission}'", + "permission_update_failed": "Failed to update permission '{permission}' : {error}", "permission_updated": "Permission '{permission}' updated", "permission_update_nothing_to_do": "No permissions to update", "port_already_closed": "Port {port:d} is already closed for {ip_version:s} connections", @@ -556,13 +556,13 @@ "upnp_enabled": "UPnP has been enabled", "upnp_port_open_failed": "Unable to open UPnP ports", "user_created": "The user has been created", - "user_creation_failed": "Unable to create user", + "user_creation_failed": "Unable to create user {user}: {error}", "user_deleted": "The user has been deleted", - "user_deletion_failed": "Unable to delete user", + "user_deletion_failed": "Unable to delete user {user}: {error}", "user_home_creation_failed": "Unable to create user home folder", "user_info_failed": "Unable to retrieve user information", "user_unknown": "Unknown user: {user:s}", - "user_update_failed": "Unable to update user", + "user_update_failed": "Unable to update user {user}: {error}", "user_updated": "The user has been updated", "users_available": "Available users:", "yunohost_already_installed": "YunoHost is already installed", diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 42a4881ba..3f906748b 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -112,8 +112,10 @@ def domain_add(operation_logger, domain, dyndns=False): 'virtualdomain': domain, } - if not ldap.add('virtualdomain=%s,ou=domains' % domain, attr_dict): - raise YunohostError('domain_creation_failed') + try: + ldap.add('virtualdomain=%s,ou=domains' % domain, attr_dict) + except Exception as e: + raise YunohostError('domain_creation_failed', domain=domain, error=e) # Don't regen these conf if we're still in postinstall if os.path.exists('/etc/yunohost/installed'): @@ -167,10 +169,12 @@ def domain_remove(operation_logger, domain, force=False): operation_logger.start() ldap = _get_ldap_interface() - if ldap.remove('virtualdomain=' + domain + ',ou=domains') or force: - os.system('rm -rf /etc/yunohost/certs/%s' % domain) - else: - raise YunohostError('domain_deletion_failed') + try: + ldap.remove('virtualdomain=' + domain + ',ou=domains') + except Exception as e: + raise YunohostError('domain_deletion_failed', domain=domain, error=e) + + os.system('rm -rf /etc/yunohost/certs/%s' % domain) regen_conf(names=['nginx', 'metronome', 'dnsmasq', 'postfix']) app_ssowatconf() diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index e5035b0ad..984a5d902 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -153,36 +153,37 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, operation_logger.start() - if ldap.update('cn=%s,ou=permission' % permission, - {'groupPermission': ['cn=' + g + ',ou=groups,dc=yunohost,dc=org' for g in new_allowed_groups]}): - logger.debug(m18n.n('permission_updated', permission=permission)) + try: + ldap.update('cn=%s,ou=permission' % permission, + {'groupPermission': ['cn=' + g + ',ou=groups,dc=yunohost,dc=org' for g in new_allowed_groups]}) + except Exception as e: + raise YunohostError('permission_update_failed', permission=permission, error=e) - # Trigger permission sync if asked + logger.debug(m18n.n('permission_updated', permission=permission)) - if sync_perm: - permission_sync_to_user() + # Trigger permission sync if asked - new_permission = user_permission_list(full=True)["permissions"][permission] + if sync_perm: + permission_sync_to_user() - # Trigger app callbacks + new_permission = user_permission_list(full=True)["permissions"][permission] - app = permission.split(".")[0] + # Trigger app callbacks - old_allowed_users = set(existing_permission["corresponding_users"]) - new_allowed_users = set(new_permission["corresponding_users"]) + app = permission.split(".")[0] - effectively_added_users = new_allowed_users - old_allowed_users - effectively_removed_users = old_allowed_users - new_allowed_users + old_allowed_users = set(existing_permission["corresponding_users"]) + new_allowed_users = set(new_permission["corresponding_users"]) - if effectively_added_users: - hook_callback('post_app_addaccess', args=[app, ','.join(effectively_added_users)]) - if effectively_removed_users: - hook_callback('post_app_removeaccess', args=[app, ','.join(effectively_removed_users)]) + effectively_added_users = new_allowed_users - old_allowed_users + effectively_removed_users = old_allowed_users - new_allowed_users - return new_permission + if effectively_added_users: + hook_callback('post_app_addaccess', args=[app, ','.join(effectively_added_users)]) + if effectively_removed_users: + hook_callback('post_app_removeaccess', args=[app, ','.join(effectively_removed_users)]) - else: - raise YunohostError('permission_update_failed', permission=permission) + return new_permission @is_unit_operation() @@ -209,10 +210,12 @@ def user_permission_reset(operation_logger, permission, sync_perm=True): operation_logger.start() default_permission = {'groupPermission': ['cn=all_users,ou=groups,dc=yunohost,dc=org']} - if ldap.update('cn=%s,ou=permission' % permission, default_permission): - logger.debug(m18n.n('permission_updated', permission=permission)) - else: - raise YunohostError('permission_update_failed', permission=permission) + try: + ldap.update('cn=%s,ou=permission' % permission, default_permission) + except Exception as e: + raise YunohostError('permission_update_failed', permission=permission, error=e) + + logger.debug(m18n.n('permission_updated', permission=permission)) if sync_perm: permission_sync_to_user() @@ -286,13 +289,17 @@ def permission_create(operation_logger, permission, urls=None, sync_perm=True): operation_logger.related_to.append(('app', permission.split(".")[0])) operation_logger.start() - if ldap.add('cn=%s,ou=permission' % permission, attr_dict): - if sync_perm: - permission_sync_to_user() - logger.debug(m18n.n('permission_created', permission=permission)) - return user_permission_list(full=True)["permissions"][permission] - else: - raise YunohostError('permission_creation_failed') + + try: + ldap.add('cn=%s,ou=permission' % permission, attr_dict) + except Exception as e: + raise YunohostError('permission_creation_failed', permission=permission, error=e) + + if sync_perm: + permission_sync_to_user() + + logger.debug(m18n.n('permission_created', permission=permission)) + return user_permission_list(full=True)["permissions"][permission] @is_unit_operation() @@ -336,13 +343,17 @@ def permission_urls(operation_logger, permission, add=None, remove=None, sync_pe operation_logger.related_to.append(('app', permission.split(".")[0])) operation_logger.start() - if ldap.update('cn=%s,ou=permission' % permission, {'URL': new_urls}): - if sync_perm: - permission_sync_to_user() - logger.debug(m18n.n('permission_updated', permission=permission)) - return user_permission_list(full=True)["permissions"][permission] - else: - raise YunohostError('permission_update_failed', permission=permission) + + try: + ldap.update('cn=%s,ou=permission' % permission, {'URL': new_urls}) + except Exception as e: + raise YunohostError('permission_update_failed', permission=permission, error=e) + + if sync_perm: + permission_sync_to_user() + + logger.debug(m18n.n('permission_updated', permission=permission)) + return user_permission_list(full=True)["permissions"][permission] @is_unit_operation() @@ -370,12 +381,15 @@ def permission_delete(operation_logger, permission, force=False, sync_perm=True) operation_logger.related_to.append(('app', permission.split(".")[0])) operation_logger.start() - if ldap.remove('cn=%s,ou=permission' % permission): - if sync_perm: - permission_sync_to_user() - logger.debug(m18n.n('permission_deleted', permission=permission)) - else: - raise YunohostError('permission_deletion_failed', permission=permission) + + try: + ldap.remove('cn=%s,ou=permission' % permission) + except Exception as e: + raise YunohostError('permission_deletion_failed', permission=permission, error=e) + + if sync_perm: + permission_sync_to_user() + logger.debug(m18n.n('permission_deleted', permission=permission)) def permission_sync_to_user(): @@ -410,8 +424,10 @@ def permission_sync_to_user(): 'memberUid': should_be_allowed_users} # Commit the change with the new inherited stuff - if not ldap.update('cn=%s,ou=permission' % permission_name, new_inherited_perms): - raise YunohostError('permission_update_failed', permission=permission_name) + try: + ldap.update('cn=%s,ou=permission' % permission_name, new_inherited_perms) + except Exception as e: + raise YunohostError('permission_update_failed', permission=permission_name, error=e) logger.debug("The permission database has been resynchronized") @@ -421,6 +437,7 @@ def permission_sync_to_user(): os.system('nscd --invalidate=passwd') os.system('nscd --invalidate=group') + def _normalize_url(url): from yunohost.domain import _normalize_domain_path domain = url[:url.index('/')] diff --git a/src/yunohost/user.py b/src/yunohost/user.py index bb4d6aed2..cfb34e44e 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -205,32 +205,34 @@ def user_create(operation_logger, username, firstname, lastname, mail, password, except IOError as e: raise YunohostError('ssowat_persistent_conf_write_error', error=e.strerror) - if ldap.add('uid=%s,ou=users' % username, attr_dict): - # Invalidate passwd to take user creation into account - subprocess.call(['nscd', '-i', 'passwd']) + try: + ldap.add('uid=%s,ou=users' % username, attr_dict) + except Exception as e: + raise YunohostError('user_creation_failed', user=username, error=e) - try: - # Attempt to create user home folder - subprocess.check_call( - ['su', '-', username, '-c', "''"]) - except subprocess.CalledProcessError: - if not os.path.isdir('/home/{0}'.format(username)): - logger.warning(m18n.n('user_home_creation_failed'), - exc_info=1) + # Invalidate passwd to take user creation into account + subprocess.call(['nscd', '-i', 'passwd']) - # Create group for user and add to group 'all_users' - user_group_create(groupname=username, gid=uid, primary_group=True, sync_perm=False) - user_group_update(groupname='all_users', add=username, force=True, sync_perm=True) + try: + # Attempt to create user home folder + subprocess.check_call( + ['su', '-', username, '-c', "''"]) + except subprocess.CalledProcessError: + if not os.path.isdir('/home/{0}'.format(username)): + logger.warning(m18n.n('user_home_creation_failed'), + exc_info=1) - # TODO: Send a welcome mail to user - logger.success(m18n.n('user_created')) + # Create group for user and add to group 'all_users' + user_group_create(groupname=username, gid=uid, primary_group=True, sync_perm=False) + user_group_update(groupname='all_users', add=username, force=True, sync_perm=True) - hook_callback('post_user_create', - args=[username, mail, password, firstname, lastname]) + # TODO: Send a welcome mail to user + logger.success(m18n.n('user_created')) - return {'fullname': fullname, 'username': username, 'mail': mail} + hook_callback('post_user_create', + args=[username, mail, password, firstname, lastname]) - raise YunohostError('user_creation_failed') + return {'fullname': fullname, 'username': username, 'mail': mail} @is_unit_operation([('username', 'user')]) @@ -258,15 +260,17 @@ def user_delete(operation_logger, username, purge=False): user_group_delete(username, force=True, sync_perm=True) ldap = _get_ldap_interface() - if ldap.remove('uid=%s,ou=users' % username): - # Invalidate passwd to take user deletion into account - subprocess.call(['nscd', '-i', 'passwd']) + try: + ldap.remove('uid=%s,ou=users' % username) + except Exception as e: + raise YunohostError('user_deletion_failed', user=username, error=e) - if purge: - subprocess.call(['rm', '-rf', '/home/{0}'.format(username)]) - subprocess.call(['rm', '-rf', '/var/mail/{0}'.format(username)]) - else: - raise YunohostError('user_deletion_failed') + # Invalidate passwd to take user deletion into account + subprocess.call(['nscd', '-i', 'passwd']) + + if purge: + subprocess.call(['rm', '-rf', '/home/{0}'.format(username)]) + subprocess.call(['rm', '-rf', '/var/mail/{0}'.format(username)]) hook_callback('post_user_delete', args=[username, purge]) @@ -387,12 +391,14 @@ def user_update(operation_logger, username, firstname=None, lastname=None, mail= operation_logger.start() - if ldap.update('uid=%s,ou=users' % username, new_attr_dict): - logger.success(m18n.n('user_updated')) - app_ssowatconf() - return user_info(username) - else: - raise YunohostError('user_update_failed') + try: + ldap.update('uid=%s,ou=users' % username, new_attr_dict) + except Exception as e: + raise YunohostError('user_update_failed', user=username, error=e) + + logger.success(m18n.n('user_updated')) + app_ssowatconf() + return user_info(username) def user_info(username): @@ -476,10 +482,7 @@ def user_info(username): 'use': storage_use } - if result: - return result_dict - else: - raise YunohostError('user_info_failed') + return result_dict # @@ -569,13 +572,16 @@ def user_group_create(operation_logger, groupname, gid=None, primary_group=False attr_dict["member"] = ["uid=" + groupname + ",ou=users,dc=yunohost,dc=org"] operation_logger.start() - if ldap.add('cn=%s,ou=groups' % groupname, attr_dict): - logger.success(m18n.n('group_created', group=groupname)) - if sync_perm: - permission_sync_to_user() - return {'name': groupname} + try: + ldap.add('cn=%s,ou=groups' % groupname, attr_dict) + except Exception as e: + raise YunohostError('group_creation_failed', group=groupname, error=e) - raise YunohostError('group_creation_failed', group=groupname) + if sync_perm: + permission_sync_to_user() + + logger.success(m18n.n('group_created', group=groupname)) + return {'name': groupname} @is_unit_operation([('groupname', 'group')]) @@ -601,13 +607,16 @@ def user_group_delete(operation_logger, groupname, force=False, sync_perm=True): operation_logger.start() ldap = _get_ldap_interface() - if not ldap.remove('cn=%s,ou=groups' % groupname): - raise YunohostError('group_deletion_failed', group=groupname) + try: + ldap.remove('cn=%s,ou=groups' % groupname) + except Exception as e: + raise YunohostError('group_deletion_failed', group=groupname, error=e) - logger.success(m18n.n('group_deleted', group=groupname)) if sync_perm: permission_sync_to_user() + logger.success(m18n.n('group_deleted', group=groupname)) + @is_unit_operation([('groupname', 'group')]) def user_group_update(operation_logger, groupname, add=None, remove=None, force=False, sync_perm=True): @@ -668,8 +677,10 @@ def user_group_update(operation_logger, groupname, add=None, remove=None, force= if set(new_group) != set(current_group): operation_logger.start() ldap = _get_ldap_interface() - if not ldap.update('cn=%s,ou=groups' % groupname, {"member": set(new_group_dns), "memberUid": set(new_group)}): - raise YunohostError('group_update_failed', group=groupname) + try: + ldap.update('cn=%s,ou=groups' % groupname, {"member": set(new_group_dns), "memberUid": set(new_group)}) + except Exception as e: + raise YunohostError('group_update_failed', group=groupname, error=e) logger.success(m18n.n('group_updated', group=groupname)) if sync_perm: From ec5069b71cd4765c028c593c3cd02da236e0ae2c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 13 Sep 2019 15:35:27 +0200 Subject: [PATCH 0105/3170] Propagate changes on backup tests + fixes bugs found in the process --- data/hooks/restore/21-conf_ynh_certs | 1 - src/yunohost/backup.py | 15 ++++++------ .../0011_setup_group_permission.py | 5 ++++ src/yunohost/tests/test_backuprestore.py | 23 +++++++++---------- 4 files changed, 24 insertions(+), 20 deletions(-) diff --git a/data/hooks/restore/21-conf_ynh_certs b/data/hooks/restore/21-conf_ynh_certs index d1eb532ed..34e651319 100644 --- a/data/hooks/restore/21-conf_ynh_certs +++ b/data/hooks/restore/21-conf_ynh_certs @@ -3,6 +3,5 @@ backup_dir="$1/conf/ynh/certs" sudo mkdir -p /etc/yunohost/certs/ sudo cp -a $backup_dir/. /etc/yunohost/certs/ -sudo yunohost app ssowatconf sudo service nginx reload sudo service metronome reload diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index f1ac7ee9c..de2b3f76d 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -1134,6 +1134,8 @@ class RestoreManager(): self._restore_system() self._restore_apps() + except Exception as e: + logger.error("The following critical error happened during restoration: %s" % e) finally: self.clean() @@ -1186,11 +1188,12 @@ class RestoreManager(): if system_targets == []: return - from yunohost.permission import permission_create, user_permission_update, user_permission_list + from yunohost.user import user_group_list + from yunohost.permission import permission_create, permission_delete, user_permission_update, user_permission_list # Backup old permission for apps # We need to do that because in case of an app is installed we can't remove the permission for this app - old_apps_permission = user_permission_list(ignore_system_perms=True)["permissions"] + old_apps_permission = user_permission_list(ignore_system_perms=True, full=True)["permissions"] # Start register change on system operation_logger = OperationLogger('backup_restore_system') @@ -1232,7 +1235,7 @@ class RestoreManager(): # do the migration 0011 : setup group and permission # # Legacy code - if not user_group_list["groups"]: + if not "all_users" in user_group_list()["groups"].keys(): from yunohost.tools import _get_migration_by_name setup_group_permission = _get_migration_by_name("setup_group_permission") # Update LDAP schema restart slapd @@ -1251,14 +1254,12 @@ class RestoreManager(): permission_create(permission_name, urls=permission_infos["urls"], sync_perm=False) user_permission_update(permission_name, remove="all_users", add=permission_infos["allowed"]) - def _restore_apps(self): """Restore all apps targeted""" apps_targets = self.targets.list("apps", exclude=["Skipped"]) for app in apps_targets: - print(app) self._restore_app(app) def _restore_app(self, app_instance_name): @@ -1359,11 +1360,11 @@ class RestoreManager(): permissions = read_yaml('%s/permissions.yml' % app_settings_new_path) existing_groups = user_group_list()['groups'] - for permission_name, permission_infos in permissions: + for permission_name, permission_infos in permissions.items(): permission_create(permission_name, urls=permission_infos.get("urls", [])) - if "allowed" not in permissions_infos: + if "allowed" not in permission_infos: logger.warning("'allowed' key corresponding to allowed groups for permission %s not found when restoring app %s ... You might need to reconfigure permissions yourself!" % (permission_name, app_instance_name)) else: groups = [g for g in permission_infos["allowed"] if g in existing_groups] diff --git a/src/yunohost/data_migrations/0011_setup_group_permission.py b/src/yunohost/data_migrations/0011_setup_group_permission.py index 720e4ac36..109757bcc 100644 --- a/src/yunohost/data_migrations/0011_setup_group_permission.py +++ b/src/yunohost/data_migrations/0011_setup_group_permission.py @@ -89,6 +89,11 @@ class MyMigration(Migration): app_setting(app, 'allowed_users', delete=True) def run(self): + + # FIXME : what do we really want to do here ... + # Imho we should just force-regen the conf in all case, and maybe + # just display a warning if we detect that the conf was manually modified + # Check if the migration can be processed ldap_regen_conf_status = regen_conf(names=['slapd'], dry_run=True) # By this we check if the have been customized diff --git a/src/yunohost/tests/test_backuprestore.py b/src/yunohost/tests/test_backuprestore.py index 7d384a46a..d2fd03799 100644 --- a/src/yunohost/tests/test_backuprestore.py +++ b/src/yunohost/tests/test_backuprestore.py @@ -38,10 +38,10 @@ def setup_function(function): add_archive_wordpress_from_2p4() assert len(backup_list()["archives"]) == 1 - if "with_backup_legacy_app_installed" in markers: - assert not app_is_installed("backup_legacy_app") - install_app("backup_legacy_app_ynh", "/yolo") - assert app_is_installed("backup_legacy_app") + if "with_legacy_app_installed" in markers: + assert not app_is_installed("legacy_app") + install_app("legacy_app_ynh", "/yolo") + assert app_is_installed("legacy_app") if "with_backup_recommended_app_installed" in markers: assert not app_is_installed("backup_recommended_app") @@ -105,7 +105,7 @@ def backup_test_dependencies_are_met(): # Dummy test apps (or backup archives) assert os.path.exists("./tests/apps/backup_wordpress_from_2p4") - assert os.path.exists("./tests/apps/backup_legacy_app_ynh") + assert os.path.exists("./tests/apps/legacy_app_ynh") assert os.path.exists("./tests/apps/backup_recommended_app_ynh") return True @@ -155,8 +155,8 @@ def delete_all_backups(): def uninstall_test_apps_if_needed(): - if _is_installed("backup_legacy_app"): - app_remove("backup_legacy_app") + if _is_installed("legacy_app"): + app_remove("legacy_app") if _is_installed("backup_recommended_app"): app_remove("backup_recommended_app") @@ -497,10 +497,10 @@ def test_restore_app_already_installed(mocker): assert _is_installed("wordpress") -@pytest.mark.with_backup_legacy_app_installed +@pytest.mark.with_legacy_app_installed def test_backup_and_restore_legacy_app(): - _test_backup_and_restore_app("backup_legacy_app") + _test_backup_and_restore_app("legacy_app") @pytest.mark.with_backup_recommended_app_installed @@ -531,7 +531,7 @@ def _test_backup_and_restore_app(app): # Uninstall the app app_remove(app) assert not app_is_installed(app) - assert app not in user_permission_list()['permissions'] + assert app+".main" not in user_permission_list()['permissions'] # Restore the app backup_restore(system=None, name=archives[0], @@ -541,8 +541,7 @@ def _test_backup_and_restore_app(app): # Check permission per_list = user_permission_list()['permissions'] - assert app in per_list - assert "main" in per_list[app] + assert app+".main" in per_list # # Some edge cases # From ccc7583ec4e909e4c2d06511f32310e6d3abba6c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 13 Sep 2019 16:02:02 +0200 Subject: [PATCH 0106/3170] Add backup/restore test for permission app, and fix a small related bug --- src/yunohost/backup.py | 6 ++- src/yunohost/tests/test_backuprestore.py | 55 ++++++++++++++++++++---- 2 files changed, 50 insertions(+), 11 deletions(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index de2b3f76d..420c2d4f8 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -1367,8 +1367,10 @@ class RestoreManager(): if "allowed" not in permission_infos: logger.warning("'allowed' key corresponding to allowed groups for permission %s not found when restoring app %s ... You might need to reconfigure permissions yourself!" % (permission_name, app_instance_name)) else: - groups = [g for g in permission_infos["allowed"] if g in existing_groups] - user_permission_update(permission_name, remove="all_users", add=groups) + should_be_allowed = [g for g in permission_infos["allowed"] if g in existing_groups] + current_allowed = user_permission_list()["permissions"][permission_name]["allowed"] + if should_be_allowed != current_allowed: + user_permission_update(permission_name, remove=current_allowed, add=should_be_allowed) os.remove('%s/permissions.yml' % app_settings_new_path) else: diff --git a/src/yunohost/tests/test_backuprestore.py b/src/yunohost/tests/test_backuprestore.py index d2fd03799..bdaf25299 100644 --- a/src/yunohost/tests/test_backuprestore.py +++ b/src/yunohost/tests/test_backuprestore.py @@ -10,7 +10,7 @@ from yunohost.app import _is_installed from yunohost.backup import backup_create, backup_restore, backup_list, backup_info, backup_delete, _recursive_umount from yunohost.domain import _get_maindomain from yunohost.utils.error import YunohostError -from yunohost.user import user_permission_list +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 # Get main domain @@ -59,6 +59,13 @@ def setup_function(function): add_archive_system_from_2p4() assert len(backup_list()["archives"]) == 1 + if "with_permission_app_installed" in markers: + assert not app_is_installed("permissions_app") + user_create("alice", "Alice", "White", "alice@" + maindomain, "test123Ynh") + install_app("permissions_app_ynh", "/urlpermissionapp" + "&admin=alice") + assert app_is_installed("permissions_app") + def teardown_function(function): @@ -73,6 +80,9 @@ def teardown_function(function): if "clean_opt_dir" in markers: shutil.rmtree("/opt/test_backup_output_directory") + if "alice" in user_list()["users"]: + user_delete("alice") + @pytest.fixture(autouse=True) def check_LDAP_db_integrity_call(): @@ -92,6 +102,9 @@ def check_permission_for_apps_call(): def app_is_installed(app): + if app == "permissions_app": + return _is_installed(app) + # These are files we know should be installed by the app app_files = [] app_files.append("/etc/nginx/conf.d/%s.d/%s.conf" % (maindomain, app)) @@ -155,14 +168,9 @@ def delete_all_backups(): def uninstall_test_apps_if_needed(): - if _is_installed("legacy_app"): - app_remove("legacy_app") - - if _is_installed("backup_recommended_app"): - app_remove("backup_recommended_app") - - if _is_installed("wordpress"): - app_remove("wordpress") + for app in ["legacy_app", "backup_recommended_app", "wordpress", "permissions_app"]: + if _is_installed(app): + app_remove(app) def install_app(app, path, additionnal_args=""): @@ -514,6 +522,35 @@ def test_backup_and_restore_with_ynh_restore(): _test_backup_and_restore_app("backup_recommended_app") +@pytest.mark.with_permission_app_installed +def test_backup_and_restore_permission_app(): + + res = user_permission_list(full=True)['permissions'] + assert "permissions_app.main" in res + assert "permissions_app.admin" in res + assert "permissions_app.dev" in res + assert res['permissions_app.main']['urls'] == [maindomain + "/urlpermissionapp"] + assert res['permissions_app.admin']['urls'] == [maindomain + "/urlpermissionapp/admin"] + assert res['permissions_app.dev']['urls'] == [maindomain + "/urlpermissionapp/dev"] + + assert res['permissions_app.main']['allowed'] == ["all_users"] + assert res['permissions_app.admin']['allowed'] == ["alice"] + assert res['permissions_app.dev']['allowed'] == [] + + _test_backup_and_restore_app("permissions_app") + + res = user_permission_list(full=True)['permissions'] + assert "permissions_app.main" in res + assert "permissions_app.admin" in res + assert "permissions_app.dev" in res + assert res['permissions_app.main']['urls'] == [maindomain + "/urlpermissionapp"] + assert res['permissions_app.admin']['urls'] == [maindomain + "/urlpermissionapp/admin"] + assert res['permissions_app.dev']['urls'] == [maindomain + "/urlpermissionapp/dev"] + + assert res['permissions_app.main']['allowed'] == ["all_users"] + assert res['permissions_app.admin']['allowed'] == ["alice"] + assert res['permissions_app.dev']['allowed'] == [] + def _test_backup_and_restore_app(app): From 302e755f48f853cf1362eb577885c990cc434ca7 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 13 Sep 2019 16:50:46 +0200 Subject: [PATCH 0107/3170] Assume we target the .main permission if it's not given explicitly --- data/actionsmap/yunohost.yml | 4 ++-- src/yunohost/permission.py | 32 ++++++++++++++++++++++++-------- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 49dde373b..05f0de048 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -298,7 +298,7 @@ user: api: POST /users/permissions/ arguments: permission: - help: Permission to manage (e.g. mail.main or wordpress.editors) + help: Permission to manage (e.g. mail or nextcloud or wordpress.editors) -a: full: --add help: Group or user names to add to this permission @@ -320,7 +320,7 @@ user: api: DELETE /users/permissions/ arguments: permission: - help: Permission to be resetted (e.g. mail.main or wordpress.editors) + help: Permission to manage (e.g. mail or nextcloud or wordpress.editors) ssh: subcategory_help: Manage ssh access diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 984a5d902..1472f4b88 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -87,15 +87,19 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, Allow or Disallow a user or group to a permission for a specific application Keyword argument: - permission -- Name of the permission (e.g. mail.mail or wordpress.editors) + permission -- Name of the permission (e.g. mail or or wordpress or wordpress.editors) add -- List of groups or usernames to add to this permission remove -- List of groups or usernames to remove from to this permission """ from yunohost.hook import hook_callback from yunohost.user import user_group_list - from yunohost.utils.ldap import _get_ldap_interface, _ldap_path_extract + from yunohost.utils.ldap import _get_ldap_interface ldap = _get_ldap_interface() + # By default, manipulate main permission + if "." not in permission: + permission = permission + ".main" + # Fetch currently allowed groups for this permission existing_permission = user_permission_list(full=True)["permissions"].get(permission, None) @@ -146,7 +150,7 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, # Don't update LDAP if we update exactly the same values if set(new_allowed_groups) == set(current_allowed_groups): # FIXME : i18n - logger.warning("No change was applied because not relevant modification were found") + logger.warning("The permission was not updated all addition/removal requests already match the current state.") return # Commit the new allowed group list @@ -192,12 +196,16 @@ def user_permission_reset(operation_logger, permission, sync_perm=True): Reset a given permission to just 'all_users' Keyword argument: - permission -- The name of the permission to be reseted + permission -- Name of the permission (e.g. mail or nextcloud or wordpress.editors) """ from yunohost.hook import hook_callback from yunohost.utils.ldap import _get_ldap_interface ldap = _get_ldap_interface() + # By default, manipulate main permission + if "." not in permission: + permission = permission + ".main" + # Fetch existing permission existing_permission = user_permission_list(full=True)["permissions"].get(permission, None) @@ -254,13 +262,17 @@ def permission_create(operation_logger, permission, urls=None, sync_perm=True): Create a new permission for a specific application Keyword argument: - permission -- Name of the permission (e.g. nextcloud.main or wordpress.editors) + permission -- Name of the permission (e.g. mail or nextcloud or wordpress.editors) urls -- list of urls to specify for the permission """ from yunohost.utils.ldap import _get_ldap_interface ldap = _get_ldap_interface() + # By default, manipulate main permission + if "." not in permission: + permission = permission + ".main" + # Validate uniqueness of permission in LDAP if ldap.get_conflict({'cn': permission}, base_dn='ou=permission,dc=yunohost,dc=org'): @@ -308,7 +320,7 @@ def permission_urls(operation_logger, permission, add=None, remove=None, sync_pe Update urls related to a permission for a specific application Keyword argument: - permission -- Name of the permission (e.g. nextcloud.main or wordpress.editors) + permission -- Name of the permission (e.g. mail or nextcloud or wordpress.editors) add -- List of urls to add remove -- List of urls to remove @@ -362,10 +374,14 @@ def permission_delete(operation_logger, permission, force=False, sync_perm=True) Delete a permission Keyword argument: - permission -- Name of the permission (e.g. nextcloud.main or wordpress.editors) + permission -- Name of the permission (e.g. mail or nextcloud or wordpress.editors) """ - if permission.endswith("main") and not force: + # By default, manipulate main permission + if "." not in permission: + permission = permission + ".main" + + if permission.endswith(".main") and not force: raise YunohostError('permission_cannot_remove_main') from yunohost.utils.ldap import _get_ldap_interface From f950378c63aec6a2536524e3e02c3cfda86a09b9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 13 Sep 2019 17:39:21 +0200 Subject: [PATCH 0108/3170] Do not display primary groups by default when running yunohost user group list --- data/actionsmap/yunohost.yml | 5 +++++ src/yunohost/user.py | 11 ++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 05f0de048..1f6966f65 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -216,6 +216,11 @@ user: full: --full help: Display all informations known about each groups action: store_true + -p: + full: --include-primary-groups + help: Also display primary groups (each user has an eponym group that only contains itself) + action: store_true + default: false ### user_group_create() create: diff --git a/src/yunohost/user.py b/src/yunohost/user.py index cfb34e44e..4fe8db420 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -488,13 +488,17 @@ def user_info(username): # # Group subcategory # -def user_group_list(short=False, full=False): +def user_group_list(short=False, full=False, include_primary_groups=True): """ List users Keyword argument: short -- Only list the name of the groups without any additional info full -- List all the info available for each groups + include_primary_groups -- Include groups corresponding to users (which should always only contains this user) + This option is set to false by default in the action map because we don't want to have + these displayed when the user runs `yunohost user group list`, but internally we do want + to list them when called from other functions """ # Fetch relevant informations @@ -507,10 +511,15 @@ def user_group_list(short=False, full=False): # Parse / organize information to be outputed + users = user_list()["users"] groups = {} for infos in groups_infos: name = infos["cn"][0] + + if not include_primary_groups and name in users: + continue + groups[name] = {} groups[name]["members"] = [_ldap_path_extract(p, "uid") for p in infos.get("member", [])] From ea8c0cae9431bd294f838448eb297d84ee6fd5a4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 13 Sep 2019 18:34:26 +0200 Subject: [PATCH 0109/3170] Deprecate legacy app access system --- data/actionsmap/yunohost.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 1f6966f65..e51f23a14 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -792,6 +792,7 @@ app: addaccess: action_help: Grant access right to users (everyone by default) api: PUT /access + deprecated: true arguments: apps: nargs: "+" @@ -803,6 +804,7 @@ app: removeaccess: action_help: Revoke access right to users (everyone by default) api: DELETE /access + deprecated: true arguments: apps: nargs: "+" @@ -814,6 +816,7 @@ app: clearaccess: action_help: Reset access rights for the app api: POST /access + deprecated: true arguments: apps: nargs: "+" From b995b3254d95008f51591a76d8bbbcfab7bc260c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 13 Sep 2019 18:41:05 +0200 Subject: [PATCH 0110/3170] Remove some unecessary messages when handling primary groups and all_users --- src/yunohost/user.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 4fe8db420..22bd6d3cf 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -589,7 +589,11 @@ def user_group_create(operation_logger, groupname, gid=None, primary_group=False if sync_perm: permission_sync_to_user() - logger.success(m18n.n('group_created', group=groupname)) + if not primary_group: + logger.success(m18n.n('group_created', group=groupname)) + else: + logger.debug(m18n.n('group_created', group=groupname)) + return {'name': groupname} @@ -624,7 +628,10 @@ def user_group_delete(operation_logger, groupname, force=False, sync_perm=True): if sync_perm: permission_sync_to_user() - logger.success(m18n.n('group_deleted', group=groupname)) + if groupname not in existing_users: + logger.success(m18n.n('group_deleted', group=groupname)) + else: + logger.debug(m18n.n('group_deleted', group=groupname)) @is_unit_operation([('groupname', 'group')]) @@ -691,7 +698,11 @@ def user_group_update(operation_logger, groupname, add=None, remove=None, force= except Exception as e: raise YunohostError('group_update_failed', group=groupname, error=e) - logger.success(m18n.n('group_updated', group=groupname)) + if groupname != "all_users": + logger.success(m18n.n('group_updated', group=groupname)) + else: + logger.debug(m18n.n('group_updated', group=groupname)) + if sync_perm: permission_sync_to_user() return user_group_info(groupname) From 732f8987738bfb585de14a0922d374e7c2c616e4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 13 Sep 2019 19:42:15 +0200 Subject: [PATCH 0111/3170] Small issue when deleting the user --- src/yunohost/user.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 22bd6d3cf..fbd15018c 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -253,6 +253,8 @@ def user_delete(operation_logger, username, purge=False): user_group_update("all_users", remove=username, force=True, sync_perm=False) for group, infos in user_group_list()["groups"].items(): + if group == "all_users": + continue # If the user is in this group (and it's not the primary group), # remove the member from the group if username != group and username in infos["members"]: From 63fa54171de033a1fe82612a97511e0579d3eff1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 13 Sep 2019 20:13:44 +0200 Subject: [PATCH 0112/3170] Ugh we really need to make this raise an exception ... --- src/yunohost/backup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 420c2d4f8..305152865 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -1135,7 +1135,7 @@ class RestoreManager(): self._restore_system() self._restore_apps() except Exception as e: - logger.error("The following critical error happened during restoration: %s" % e) + raise YunohostError("The following critical error happened during restoration: %s" % e) finally: self.clean() @@ -1245,7 +1245,7 @@ class RestoreManager(): # Remove all permission for all app which is still in the LDAP for permission_name in user_permission_list(ignore_system_perms=True)["permissions"].keys(): - permission_delete(permission_name) + permission_delete(permission_name, force=True) # Restore permission for the app which is installed for permission_name, permission_infos in old_apps_permission.items(): From 3df6ce17b6b13f1fe784a3bb9e5b782fc541eb2a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 13 Sep 2019 20:34:30 +0200 Subject: [PATCH 0113/3170] Properly handle all those errors >.> ... --- locales/en.json | 1 + src/yunohost/tests/test_permission.py | 3 +-- src/yunohost/tests/test_user-group.py | 17 ++++++++------ src/yunohost/user.py | 33 +++++++++++++++++++++------ 4 files changed, 38 insertions(+), 16 deletions(-) diff --git a/locales/en.json b/locales/en.json index d60a432ab..c48532ed7 100644 --- a/locales/en.json +++ b/locales/en.json @@ -555,6 +555,7 @@ "upnp_disabled": "UPnP has been disabled", "upnp_enabled": "UPnP has been enabled", "upnp_port_open_failed": "Unable to open UPnP ports", + "user_already_exists": "User {user} already exists", "user_created": "The user has been created", "user_creation_failed": "Unable to create user {user}: {error}", "user_deleted": "The user has been deleted", diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index 2df6362a9..8db1ae825 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -1,6 +1,5 @@ import pytest -from moulinette.core import MoulinetteError from yunohost.app import app_install, app_remove, app_change_url, app_list from yunohost.user import user_list, user_info, user_create, user_delete, user_update, \ @@ -211,7 +210,7 @@ def test_permission_create_already_existing(): permission_create("wiki.main") def test_permission_delete_doesnt_existing(): - with pytest.raises(MoulinetteError): + with pytest.raises(YunohostError): permission_delete("doesnt.exist", force=True) res = user_permission_list()['permissions'] diff --git a/src/yunohost/tests/test_user-group.py b/src/yunohost/tests/test_user-group.py index 88644c3e6..30bdeb017 100644 --- a/src/yunohost/tests/test_user-group.py +++ b/src/yunohost/tests/test_user-group.py @@ -1,6 +1,5 @@ import pytest -from moulinette.core import MoulinetteError from yunohost.user import user_list, user_info, user_create, user_delete, user_update, \ user_group_list, user_group_create, user_group_delete, user_group_update, user_group_info from yunohost.domain import _get_maindomain @@ -102,19 +101,23 @@ def test_del_group(): # def test_create_user_with_mail_address_already_taken(): - with pytest.raises(MoulinetteError): + with pytest.raises(YunohostError): user_create("alice2", "Alice", "White", "alice@" + maindomain, "test123Ynh") def test_create_user_with_password_too_simple(): - with pytest.raises(MoulinetteError): + with pytest.raises(YunohostError): user_create("other", "Alice", "White", "other@" + maindomain, "12") def test_create_user_already_exists(): - with pytest.raises(MoulinetteError): + with pytest.raises(YunohostError): user_create("alice", "Alice", "White", "other@" + maindomain, "test123Ynh") +def test_update_user_with_mail_address_already_taken(): + with pytest.raises(YunohostError): + user_update("bob", add_mailalias="alice@" + maindomain) + def test_del_user_that_does_not_exist(): - with pytest.raises(MoulinetteError): + with pytest.raises(YunohostError): user_delete("doesnt_exist") def test_create_group_all_users(): @@ -124,7 +127,7 @@ def test_create_group_all_users(): def test_create_group_already_exists(): # Check groups already exist (regular groups) - with pytest.raises(MoulinetteError): + with pytest.raises(YunohostError): user_group_create("dev") def test_del_group_all_users(): @@ -132,7 +135,7 @@ def test_del_group_all_users(): user_group_delete("all_users") def test_del_group_that_does_not_exist(): - with pytest.raises(MoulinetteError): + with pytest.raises(YunohostError): user_group_delete("doesnt_exist") # diff --git a/src/yunohost/user.py b/src/yunohost/user.py index fbd15018c..c6413d7e1 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -127,12 +127,18 @@ def user_create(operation_logger, username, firstname, lastname, mail, password, ldap = _get_ldap_interface() + if username in user_list()["users"]: + raise YunohostError("user_already_exists", user=username) + # Validate uniqueness of username and mail in LDAP - ldap.validate_uniqueness({ - 'uid': username, - 'mail': mail, - 'cn': username - }) + try: + ldap.validate_uniqueness({ + 'uid': username, + 'mail': mail, + 'cn': username + }) + except Exception as e: + raise YunohostError('user_creation_failed', user=username, error=e) # Validate uniqueness of username in system users all_existing_usernames = {x.pw_name for x in pwd.getpwall()} @@ -249,6 +255,9 @@ def user_delete(operation_logger, username, purge=False): from yunohost.utils.ldap import _get_ldap_interface from yunohost.permission import permission_sync_to_user + if username not in user_list()["users"]: + raise YunohostError('user_unknown', user=username) + operation_logger.start() user_group_update("all_users", remove=username, force=True, sync_perm=False) @@ -340,7 +349,10 @@ def user_update(operation_logger, username, firstname=None, lastname=None, mail= 'webmaster@' + main_domain, 'postmaster@' + main_domain, ] - ldap.validate_uniqueness({'mail': mail}) + try: + ldap.validate_uniqueness({'mail': mail}) + except Exception as e: + raise YunohostError('user_update_failed', user=username, error=e) if mail[mail.find('@') + 1:] not in domains: raise YunohostError('mail_domain_unknown', domain=mail[mail.find('@') + 1:]) if mail in aliases: @@ -353,7 +365,10 @@ def user_update(operation_logger, username, firstname=None, lastname=None, mail= if not isinstance(add_mailalias, list): add_mailalias = [add_mailalias] for mail in add_mailalias: - ldap.validate_uniqueness({'mail': mail}) + try: + ldap.validate_uniqueness({'mail': mail}) + except Exception as e: + raise YunohostError('user_update_failed', user=username, error=e) if mail[mail.find('@') + 1:] not in domains: raise YunohostError('mail_domain_unknown', domain=mail[mail.find('@') + 1:]) user['mail'].append(mail) @@ -611,6 +626,10 @@ def user_group_delete(operation_logger, groupname, force=False, sync_perm=True): from yunohost.permission import permission_sync_to_user from yunohost.utils.ldap import _get_ldap_interface + existing_groups = user_group_list()['groups'].keys() + if groupname not in existing_groups: + raise YunohostError('group_unknown', group=groupname) + # Refuse to delete primary groups of a user (e.g. group 'sam' related to user 'sam') # without the force option... # From 094a2afe1a7a5eb21c9fdcb51ab79de05c9589a7 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 13 Sep 2019 22:45:31 +0200 Subject: [PATCH 0114/3170] Simplify permission handling in app_map + add tests for it --- src/yunohost/app.py | 10 +++------- src/yunohost/tests/test_permission.py | 15 +++++++++++---- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index b3c36d059..ab290cb4d 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -406,10 +406,10 @@ def app_map(app=None, raw=False, user=None): """ from yunohost.permission import user_permission_list - from yunohost.utils.ldap import _get_ldap_interface apps = [] result = {} + permissions = user_permission_list(full=True)["permissions"] if app is not None: if not _is_installed(app): @@ -429,12 +429,8 @@ def app_map(app=None, raw=False, user=None): continue if 'no_sso' in app_settings: # I don't think we need to check for the value here continue - if user is not None: - ldap = _get_ldap_interface() - if not ldap.search(base='ou=permission,dc=yunohost,dc=org', - filter='(&(objectclass=permissionYnh)(cn=%s.main)(inheritPermission=uid=%s,ou=users,dc=yunohost,dc=org))' % (app_id, user), - attrs=['cn']): - continue + if user and user not in permissions[app_id + ".main"]["corresponding_users"]: + continue domain = app_settings['domain'] path = app_settings['path'] diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index 8db1ae825..94728505d 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -1,6 +1,6 @@ import pytest -from yunohost.app import app_install, app_remove, app_change_url, app_list +from yunohost.app import app_install, app_remove, app_change_url, app_list, app_map from yunohost.user import user_list, user_info, user_create, user_delete, user_update, \ user_group_list, user_group_create, user_group_delete, user_group_update, user_group_info @@ -331,7 +331,7 @@ def test_permission_remove_url_not_added(): # Application interaction # -def test_install_app(): +def test_permission_app_install(): app_install("./tests/apps/permissions_app_ynh", args="domain=%s&path=%s&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True) @@ -352,7 +352,14 @@ def test_install_app(): assert res['permissions_app.dev']['allowed'] == [] assert set(res['permissions_app.dev']['corresponding_users']) == set() -def test_remove_app(): + # Check that we get the right stuff in app_map, which is used to generate the ssowatconf + assert maindomain + "/urlpermissionapp" in app_map(user="alice").keys() + user_permission_update("permissions_app.main", remove="all_users", add="bob") + assert maindomain + "/urlpermissionapp" not in app_map(user="alice").keys() + assert maindomain + "/urlpermissionapp" in app_map(user="bob").keys() + + +def test_permission_app_remove(): app_install("./tests/apps/permissions_app_ynh", args="domain=%s&path=%s&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True) app_remove("permissions_app") @@ -361,7 +368,7 @@ def test_remove_app(): res = user_permission_list(full=True)['permissions'] assert not any(p.startswith("permissions_app.") for p in res.keys()) -def test_change_url(): +def test_permission_app_change_url(): app_install("./tests/apps/permissions_app_ynh", args="domain=%s&path=%s&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True) From 9c383ef06a106505d3f726276d6d2cfba4e4cc41 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 14 Sep 2019 18:21:42 +0200 Subject: [PATCH 0115/3170] Make migration more robust to re-runs --- locales/en.json | 2 +- .../0011_setup_group_permission.py | 31 ++++++++++++++++--- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/locales/en.json b/locales/en.json index c48532ed7..ae349edf3 100644 --- a/locales/en.json +++ b/locales/en.json @@ -354,7 +354,6 @@ "migration_0011_can_not_backup_before_migration": "The backup of the system before the migration failed. Migration failed. Error: {error:s}", "migration_0011_create_group": "Creating a group for each user...", "migration_0011_done": "Migration successful. You are now able to manage groups of users.", - "migration_0011_error_when_removing_sftpuser_group": "Error when trying remove sftpusers group", "migration_0011_LDAP_config_dirty": "It look like that you customized your LDAP configuration. For this migration the LDAP configuration need to be updated.\nYou need to save your actual configuration, reintialize the original configuration by the command 'yunohost tools regen-conf -f' and after retry the migration", "migration_0011_LDAP_update_failed": "LDAP update failed. Error: {error:s}", "migration_0011_migrate_permission": "Migrating permissions from apps settings to LDAP...", @@ -362,6 +361,7 @@ "migration_0011_rollback_success": "Rollback succeeded.", "migration_0011_update_LDAP_database": "Updating LDAP database...", "migration_0011_update_LDAP_schema": "Updating LDAP schema...", + "migration_0011_failed_to_remove_stale_object": "Failed to remove stale object {dn}: {error}", "migrations_already_ran": "Those migrations have already been ran: {ids}", "migrations_cant_reach_migration_file": "Can't access migrations files at path %s", "migrations_dependencies_not_satisfied": "Can't run migration {id} because first you need to run these migrations: {dependencies_id}", diff --git a/src/yunohost/data_migrations/0011_setup_group_permission.py b/src/yunohost/data_migrations/0011_setup_group_permission.py index 109757bcc..8949239e0 100644 --- a/src/yunohost/data_migrations/0011_setup_group_permission.py +++ b/src/yunohost/data_migrations/0011_setup_group_permission.py @@ -28,6 +28,28 @@ class MyMigration(Migration): required = True + def remove_if_exists(self, target): + + from yunohost.utils.ldap import _get_ldap_interface + ldap = _get_ldap_interface() + + try: + objects = ldap.search(target + ",dc=yunohost,dc=org") + # ldap search will raise an exception if no corresponding object is found >.> ... + except Exception as e: + logger.debug("%s does not exist, no need to delete it" % target) + return + + objects.reverse() + for o in objects: + for dn in o["dn"]: + dn = dn.replace(",dc=yunohost,dc=org", "") + logger.debug("Deleting old object %s ..." % dn) + try: + ldap.remove(dn) + except Exception as e: + raise YunohostError("migration_0011_failed_to_remove_stale_object", dn=dn, error=e) + def migrate_LDAP_db(self): logger.info(m18n.n("migration_0011_update_LDAP_database")) @@ -35,14 +57,13 @@ class MyMigration(Migration): from yunohost.utils.ldap import _get_ldap_interface ldap = _get_ldap_interface() - try: - ldap.remove('cn=sftpusers,ou=groups') - except: - logger.warn(m18n.n("migration_0011_error_when_removing_sftpuser_group")) - ldap_map = read_yaml('/usr/share/yunohost/yunohost-config/moulinette/ldap_scheme.yml') try: + self.remove_if_exists("cn=sftpusers,ou=groups") + self.remove_if_exists("ou=permission") + self.remove_if_exists('cn=all_users,ou=groups') + attr_dict = ldap_map['parents']['ou=permission'] ldap.add('ou=permission', attr_dict) From 930b8378a1d39bfd158e1da6c192e3d3c8a34f9e Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Wed, 7 Aug 2019 02:16:43 +0200 Subject: [PATCH 0116/3170] [mod] remove unused variable --- src/yunohost/app.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 4a14c5e4b..1bab5eb31 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -590,9 +590,6 @@ def app_upgrade(app=[], url=None, file=None): from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback from yunohost.permission import permission_sync_to_user - # Retrieve interface - is_api = msettings.get('interface') == 'api' - try: app_list() except YunohostError: From 3130bb59ace5fa2ffdbe8ad275e2c76be55ea74d Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Wed, 7 Aug 2019 02:28:37 +0200 Subject: [PATCH 0117/3170] [mod] stop apps upgrade if one upgrade fail --- locales/en.json | 3 ++- src/yunohost/app.py | 19 +++++++++++++------ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/locales/en.json b/locales/en.json index be00d5b1e..cf15bb6f5 100644 --- a/locales/en.json +++ b/locales/en.json @@ -28,7 +28,8 @@ "app_location_unavailable": "This url is not available or conflicts with the already installed app(s):\n{apps:s}", "app_manifest_invalid": "Invalid app manifest: {error}", "app_no_upgrade": "No apps to upgrade", - "app_not_upgraded": "The following apps were not upgraded: {apps}", + "app_not_upgraded": "The following apps were not upgraded because a the app '{app}' failed to upgrade: {apps}", + "app_upgrade_stoped": "The upgrade of alls applications has been stopped to prevent possible dommages because the previous application failed to upgrade", "app_not_correctly_installed": "{app:s} seems to be incorrectly installed", "app_not_installed": "The application '{app:s}' is not installed. Here is the list of all installed apps: {all_apps}", "app_not_properly_removed": "{app:s} has not been properly removed", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 1bab5eb31..a45766907 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -618,7 +618,7 @@ def app_upgrade(app=[], url=None, file=None): if len(apps) > 1: logger.info(m18n.n("app_upgrade_several_apps", apps=", ".join(apps))) - for app_instance_name in apps: + for number, app_instance_name in enumerate(apps): logger.info(m18n.n('app_upgrade_app_name', app=app_instance_name)) app_dict = app_info(app_instance_name, raw=True) @@ -672,9 +672,19 @@ def app_upgrade(app=[], url=None, file=None): if hook_exec(extracted_app_folder + '/scripts/upgrade', args=args_list, env=env_dict)[0] != 0: msg = m18n.n('app_upgrade_failed', app=app_instance_name) - not_upgraded_apps.append(app_instance_name) - logger.error(msg) operation_logger.error(msg) + + # display this is there are remaining apps + if apps[number + 1:]: + logger.error(m18n.n('app_upgrade_stoped')) + not_upgraded_apps = apps[number:] + # we don't want to continue upgrading apps here in case that breaks + # everything + raise YunohostError('app_not_upgraded', + failed_app=app_instance_name, + apps=', '.join(not_upgraded_apps)) + else: + raise YunohostError(msg) else: now = int(time.time()) # TODO: Move install_time away from app_setting @@ -709,9 +719,6 @@ def app_upgrade(app=[], url=None, file=None): hook_callback('post_app_upgrade', args=args_list, env=env_dict) operation_logger.success() - if not_upgraded_apps: - raise YunohostError('app_not_upgraded', apps=', '.join(not_upgraded_apps)) - permission_sync_to_user() logger.success(m18n.n('upgrade_complete')) From 889e34888dede81aa5532ebbccdedb055a86ab3b Mon Sep 17 00:00:00 2001 From: Bram Date: Wed, 7 Aug 2019 13:04:56 +0200 Subject: [PATCH 0118/3170] [mod] typo Co-Authored-By: decentral1se --- 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 a45766907..0ada585c3 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -674,7 +674,7 @@ def app_upgrade(app=[], url=None, file=None): msg = m18n.n('app_upgrade_failed', app=app_instance_name) operation_logger.error(msg) - # display this is there are remaining apps + # display this if there are remaining apps if apps[number + 1:]: logger.error(m18n.n('app_upgrade_stoped')) not_upgraded_apps = apps[number:] From 22be1a320b357c0ce652124d46bb369e1049f324 Mon Sep 17 00:00:00 2001 From: Bram Date: Wed, 7 Aug 2019 13:05:13 +0200 Subject: [PATCH 0119/3170] [mod] typo Co-Authored-By: decentral1se --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index cf15bb6f5..c8bee8610 100644 --- a/locales/en.json +++ b/locales/en.json @@ -29,7 +29,7 @@ "app_manifest_invalid": "Invalid app manifest: {error}", "app_no_upgrade": "No apps to upgrade", "app_not_upgraded": "The following apps were not upgraded because a the app '{app}' failed to upgrade: {apps}", - "app_upgrade_stoped": "The upgrade of alls applications has been stopped to prevent possible dommages because the previous application failed to upgrade", + "app_upgrade_stoped": "The upgrade of all applications has been stopped to prevent possible damage because the previous application failed to upgrade", "app_not_correctly_installed": "{app:s} seems to be incorrectly installed", "app_not_installed": "The application '{app:s}' is not installed. Here is the list of all installed apps: {all_apps}", "app_not_properly_removed": "{app:s} has not been properly removed", From 7926b761fd9abea76ff843778a81ec643d84f45e Mon Sep 17 00:00:00 2001 From: Bram Date: Wed, 7 Aug 2019 13:05:50 +0200 Subject: [PATCH 0120/3170] [mod] typo Co-Authored-By: decentral1se --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index c8bee8610..741af8c6a 100644 --- a/locales/en.json +++ b/locales/en.json @@ -28,7 +28,7 @@ "app_location_unavailable": "This url is not available or conflicts with the already installed app(s):\n{apps:s}", "app_manifest_invalid": "Invalid app manifest: {error}", "app_no_upgrade": "No apps to upgrade", - "app_not_upgraded": "The following apps were not upgraded because a the app '{app}' failed to upgrade: {apps}", + "app_not_upgraded": "The following apps were not upgraded because the app '{app}' failed to upgrade: {apps}", "app_upgrade_stoped": "The upgrade of all applications has been stopped to prevent possible damage because the previous application failed to upgrade", "app_not_correctly_installed": "{app:s} seems to be incorrectly installed", "app_not_installed": "The application '{app:s}' is not installed. Here is the list of all installed apps: {all_apps}", From a283b436e173f16bd84e46da4669753f4f7c8bbb Mon Sep 17 00:00:00 2001 From: Bram Date: Sun, 18 Aug 2019 18:50:20 +0200 Subject: [PATCH 0121/3170] [mod] typo Co-Authored-By: Alexandre Aubin --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 741af8c6a..22dd59bb2 100644 --- a/locales/en.json +++ b/locales/en.json @@ -29,7 +29,7 @@ "app_manifest_invalid": "Invalid app manifest: {error}", "app_no_upgrade": "No apps to upgrade", "app_not_upgraded": "The following apps were not upgraded because the app '{app}' failed to upgrade: {apps}", - "app_upgrade_stoped": "The upgrade of all applications has been stopped to prevent possible damage because the previous application failed to upgrade", + "app_upgrade_stopped": "The upgrade of all applications has been stopped to prevent possible damage because the previous application failed to upgrade", "app_not_correctly_installed": "{app:s} seems to be incorrectly installed", "app_not_installed": "The application '{app:s}' is not installed. Here is the list of all installed apps: {all_apps}", "app_not_properly_removed": "{app:s} has not been properly removed", From ef38f5e7b5f2a893f416baca7fccf164cf2fed81 Mon Sep 17 00:00:00 2001 From: Bram Date: Sun, 18 Aug 2019 18:51:16 +0200 Subject: [PATCH 0122/3170] [mod] typo 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 0ada585c3..684d83569 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -676,7 +676,7 @@ def app_upgrade(app=[], url=None, file=None): # display this if there are remaining apps if apps[number + 1:]: - logger.error(m18n.n('app_upgrade_stoped')) + logger.error(m18n.n('app_upgrade_stopped')) not_upgraded_apps = apps[number:] # we don't want to continue upgrading apps here in case that breaks # everything From fc85ae010229ac3f8efee0ab5c16f12133edabfc Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 15 Sep 2019 01:42:04 +0200 Subject: [PATCH 0123/3170] Key mismatch was causing an error + ended up reworking the sentence --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 22dd59bb2..e520c442d 100644 --- a/locales/en.json +++ b/locales/en.json @@ -28,7 +28,7 @@ "app_location_unavailable": "This url is not available or conflicts with the already installed app(s):\n{apps:s}", "app_manifest_invalid": "Invalid app manifest: {error}", "app_no_upgrade": "No apps to upgrade", - "app_not_upgraded": "The following apps were not upgraded because the app '{app}' failed to upgrade: {apps}", + "app_not_upgraded": "The app '{failed_app}' failed to upgrade, and as a consequence the following apps upgrades have been cancelled: {apps}", "app_upgrade_stopped": "The upgrade of all applications has been stopped to prevent possible damage because the previous application failed to upgrade", "app_not_correctly_installed": "{app:s} seems to be incorrectly installed", "app_not_installed": "The application '{app:s}' is not installed. Here is the list of all installed apps: {all_apps}", From 8e6ebd7979f4aa133ad477228a777417dc086e04 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 24 Aug 2019 01:31:17 +0200 Subject: [PATCH 0124/3170] Iteration on sanity checks for app operations --- locales/en.json | 1 + src/yunohost/app.py | 52 +++++++++++++++++++++++++++++++-------------- 2 files changed, 37 insertions(+), 16 deletions(-) diff --git a/locales/en.json b/locales/en.json index e520c442d..cc7955204 100644 --- a/locales/en.json +++ b/locales/en.json @@ -7,6 +7,7 @@ "admin_password_too_long": "Please choose a password shorter than 127 characters", "already_up_to_date": "Nothing to do! Everything is already up to date!", "app_action_cannot_be_ran_because_required_services_down": "This app requires some services which are currently down. Before continuing, you should try to restart the following services (and possibly investigate why they are down) : {services}", + "app_action_broke_system": "This action seem to have broke these important services: {services}", "app_already_installed": "{app:s} is already installed", "app_already_installed_cant_change_url": "This app is already installed. The url cannot be changed just by this function. Look into `app changeurl` if it's available.", "app_already_up_to_date": "{app:s} is already up to date", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 684d83569..785613283 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -584,9 +584,6 @@ def app_upgrade(app=[], url=None, file=None): url -- Git url to fetch for upgrade """ - if packages.dpkg_is_broken(): - raise YunohostError("dpkg_is_broken") - from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback from yunohost.permission import permission_sync_to_user @@ -638,7 +635,7 @@ def app_upgrade(app=[], url=None, file=None): # Check requirements _check_manifest_requirements(manifest, app_instance_name=app_instance_name) - _check_services_status_for_app(manifest.get("services", [])) + _assert_system_is_sane_for_app(manifest, "pre") app_setting_path = APPS_SETTING_PATH + '/' + app_instance_name @@ -716,6 +713,7 @@ def app_upgrade(app=[], url=None, file=None): # So much win logger.success(m18n.n('app_upgraded', app=app_instance_name)) + _assert_system_is_sane_for_app(manifest, "post") hook_callback('post_app_upgrade', args=args_list, env=env_dict) operation_logger.success() @@ -736,8 +734,6 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu no_remove_on_failure -- Debug option to avoid removing the app on a failed installation force -- Do not ask for confirmation when installing experimental / low-quality apps """ - if packages.dpkg_is_broken(): - raise YunohostError("dpkg_is_broken") from yunohost.utils.ldap import _get_ldap_interface from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback @@ -801,7 +797,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu # Check requirements _check_manifest_requirements(manifest, app_id) - _check_services_status_for_app(manifest.get("services", [])) + _assert_system_is_sane_for_app(manifest, "pre") # Check if app can be forked instance_number = _installed_instance_number(app_id, last=True) + 1 @@ -894,8 +890,17 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu import traceback logger.exception(m18n.n('unexpected_error', error=u"\n" + traceback.format_exc())) finally: + try: + broke_the_system = False + _assert_system_is_sane_for_app(manifest, "post") + except Exception as e: + broke_the_system = True + error_msg = operation_logger.error(str(e)) + if install_retcode != 0: error_msg = operation_logger.error(m18n.n('unexpected_error', error='shell command return code: %s' % install_retcode)) + + if install_retcode != 0 or broke_the_system: if not no_remove_on_failure: # Setup environment for remove script env_dict_remove = {} @@ -926,6 +931,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu logger.warning(msg) operation_logger_remove.error(msg) else: + _assert_system_is_sane_for_app(manifest, "post") operation_logger_remove.success() # Clean tmp folders @@ -934,9 +940,6 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu app_ssowatconf() - if packages.dpkg_is_broken(): - logger.error(m18n.n("this_action_broke_dpkg")) - if install_retcode == -1: msg = m18n.n('operation_interrupted') + " " + error_msg raise YunohostError(msg, raw_msg=True) @@ -1004,6 +1007,8 @@ def app_remove(operation_logger, app): # script might date back from jessie install) _patch_php5(app_setting_path) + manifest = _get_manifest_of_app(app_setting_path) + os.system('cp -a %s /tmp/yunohost_remove && chown -hR admin: /tmp/yunohost_remove' % app_setting_path) os.system('chown -R admin: /tmp/yunohost_remove') os.system('chmod -R u+rX /tmp/yunohost_remove') @@ -1038,9 +1043,7 @@ def app_remove(operation_logger, app): permission_remove(app, l.split('.')[0], force=True, sync_perm=False) permission_sync_to_user() - - if packages.dpkg_is_broken(): - raise YunohostError("this_action_broke_dpkg") + _assert_system_is_sane_for_app(manifest, "post") @is_unit_operation(['permission','app']) @@ -2910,10 +2913,12 @@ def unstable_apps(): return output -def _check_services_status_for_app(services): +def _assert_system_is_sane_for_app(manifest, when): logger.debug("Checking that required services are up and running...") + services = manifest.get("services", []) + # Some apps use php-fpm or php5-fpm which is now php7.0-fpm def replace_alias(service): if service in ["php-fpm", "php5-fpm"]: @@ -2928,11 +2933,26 @@ def _check_services_status_for_app(services): service_filter = ["nginx", "php7.0-fpm", "mysql", "postfix"] services = [str(s) for s in services if s in service_filter] + if "nginx" not in services: + services = ["nginx"] + services + if "fail2ban" not in services: + services.append("fail2ban") + # List services currently down and raise an exception if any are found faulty_services = [s for s in services if service_status(s)["active"] != "active"] if faulty_services: - raise YunohostError('app_action_cannot_be_ran_because_required_services_down', - services=', '.join(faulty_services)) + if when == "pre": + raise YunohostError('app_action_cannot_be_ran_because_required_services_down', + services=', '.join(faulty_services)) + elif when == "post": + raise YunohostError('app_action_broke_system', + services=', '.join(faulty_services)) + + if packages.dpkg_is_broken(): + if when == "pre": + raise YunohostError("dpkg_is_broken") + elif when == "post": + raise YunohostError("this_action_broke_dpkg") def _patch_php5(app_folder): From 08ecace5ec1eddd048e890a9800068e7d9c605d1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 15 Sep 2019 02:21:26 +0200 Subject: [PATCH 0125/3170] Here we keep need to keep going and only display an error, otherwise the rest of the file ain't properly cleaned up --- src/yunohost/app.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 785613283..833d67402 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -931,8 +931,12 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu logger.warning(msg) operation_logger_remove.error(msg) else: - _assert_system_is_sane_for_app(manifest, "post") - operation_logger_remove.success() + try: + _assert_system_is_sane_for_app(manifest, "post") + except Exception as e: + operation_logger_remove.error(e) + else: + operation_logger_remove.success() # Clean tmp folders shutil.rmtree(app_setting_path) From c530325e293379f685a44e88a95abb7d2e5fce7e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 15 Sep 2019 02:22:29 +0200 Subject: [PATCH 0126/3170] Properly handle the sanity checks right after upgrades (in combination with managing the regular error code...). This is similar to what's done for app_install --- src/yunohost/app.py | 107 +++++++++++++++++++++++++++----------------- 1 file changed, 66 insertions(+), 41 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 833d67402..0784f1ecc 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -666,56 +666,81 @@ def app_upgrade(app=[], url=None, file=None): # Execute App upgrade script os.system('chown -hR admin: %s' % INSTALL_TMP) - if hook_exec(extracted_app_folder + '/scripts/upgrade', - args=args_list, env=env_dict)[0] != 0: - msg = m18n.n('app_upgrade_failed', app=app_instance_name) - operation_logger.error(msg) - # display this if there are remaining apps - if apps[number + 1:]: - logger.error(m18n.n('app_upgrade_stopped')) - not_upgraded_apps = apps[number:] - # we don't want to continue upgrading apps here in case that breaks - # everything - raise YunohostError('app_not_upgraded', - failed_app=app_instance_name, - apps=', '.join(not_upgraded_apps)) + + try: + upgrade_retcode = hook_exec(extracted_app_folder + '/scripts/upgrade', + args=args_list, env=env_dict)[0] + except (KeyboardInterrupt, EOFError): + upgrade_retcode = -1 + except Exception: + import traceback + logger.exception(m18n.n('unexpected_error', error=u"\n" + traceback.format_exc())) + finally: + + # Did the script succeed ? + if upgrade_retcode != 0: + error_msg = m18n.n('app_upgrade_failed', app=app_instance_name) + operation_logger.error(error_msg) + + # Did it broke the system ? + try: + broke_the_system = False + _assert_system_is_sane_for_app(manifest, "post") + except Exception as e: + broke_the_system = True + error_msg = operation_logger.error(str(e)) + + # If upgrade failed or broke the system, + # raise an error and interrupt all other pending upgrades + if upgrade_retcode != 0 or broke_the_system: + + # display this if there are remaining apps + if apps[number + 1:]: + logger.error(m18n.n('app_upgrade_stopped')) + not_upgraded_apps = apps[number:] + # we don't want to continue upgrading apps here in case that breaks + # everything + raise YunohostError('app_not_upgraded', + failed_app=app_instance_name, + apps=', '.join(not_upgraded_apps)) + else: + raise YunohostError(error_msg, raw_msg=True) + + # Otherwise we're good and keep going ! else: - raise YunohostError(msg) - else: - now = int(time.time()) - # TODO: Move install_time away from app_setting - app_setting(app_instance_name, 'update_time', now) - status['upgraded_at'] = now + now = int(time.time()) + # TODO: Move install_time away from app_setting + app_setting(app_instance_name, 'update_time', now) + status['upgraded_at'] = now - # Clean hooks and add new ones - hook_remove(app_instance_name) - if 'hooks' in os.listdir(extracted_app_folder): - for hook in os.listdir(extracted_app_folder + '/hooks'): - hook_add(app_instance_name, extracted_app_folder + '/hooks/' + hook) + # Clean hooks and add new ones + hook_remove(app_instance_name) + if 'hooks' in os.listdir(extracted_app_folder): + for hook in os.listdir(extracted_app_folder + '/hooks'): + hook_add(app_instance_name, extracted_app_folder + '/hooks/' + hook) - # Store app status - with open(app_setting_path + '/status.json', 'w+') as f: - json.dump(status, f) + # Store app status + with open(app_setting_path + '/status.json', 'w+') as f: + json.dump(status, f) - # Replace scripts and manifest and conf (if exists) - os.system('rm -rf "%s/scripts" "%s/manifest.toml %s/manifest.json %s/conf"' % (app_setting_path, app_setting_path, app_setting_path, app_setting_path)) + # Replace scripts and manifest and conf (if exists) + os.system('rm -rf "%s/scripts" "%s/manifest.toml %s/manifest.json %s/conf"' % (app_setting_path, app_setting_path, app_setting_path, app_setting_path)) - if os.path.exists(os.path.join(extracted_app_folder, "manifest.json")): - os.system('mv "%s/manifest.json" "%s/scripts" %s' % (extracted_app_folder, extracted_app_folder, app_setting_path)) - if os.path.exists(os.path.join(extracted_app_folder, "manifest.toml")): - os.system('mv "%s/manifest.toml" "%s/scripts" %s' % (extracted_app_folder, extracted_app_folder, app_setting_path)) + if os.path.exists(os.path.join(extracted_app_folder, "manifest.json")): + os.system('mv "%s/manifest.json" "%s/scripts" %s' % (extracted_app_folder, extracted_app_folder, app_setting_path)) + if os.path.exists(os.path.join(extracted_app_folder, "manifest.toml")): + os.system('mv "%s/manifest.toml" "%s/scripts" %s' % (extracted_app_folder, extracted_app_folder, app_setting_path)) - for file_to_copy in ["actions.json", "actions.toml", "config_panel.json", "config_panel.toml", "conf"]: - if os.path.exists(os.path.join(extracted_app_folder, file_to_copy)): - os.system('cp -R %s/%s %s' % (extracted_app_folder, file_to_copy, app_setting_path)) + for file_to_copy in ["actions.json", "actions.toml", "config_panel.json", "config_panel.toml", "conf"]: + if os.path.exists(os.path.join(extracted_app_folder, file_to_copy)): + os.system('cp -R %s/%s %s' % (extracted_app_folder, file_to_copy, app_setting_path)) - # So much win - logger.success(m18n.n('app_upgraded', app=app_instance_name)) + # So much win + logger.success(m18n.n('app_upgraded', app=app_instance_name)) - _assert_system_is_sane_for_app(manifest, "post") - hook_callback('post_app_upgrade', args=args_list, env=env_dict) - operation_logger.success() + hook_callback('post_app_upgrade', args=args_list, env=env_dict) + operation_logger.success() permission_sync_to_user() From 488275422148a9fc79338c1a16bf22117178da4b Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Wed, 7 Aug 2019 02:28:37 +0200 Subject: [PATCH 0127/3170] [mod] stop apps upgrade if one upgrade fail --- src/yunohost/app.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 0784f1ecc..d64baeaa3 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -667,7 +667,6 @@ def app_upgrade(app=[], url=None, file=None): # Execute App upgrade script os.system('chown -hR admin: %s' % INSTALL_TMP) - try: upgrade_retcode = hook_exec(extracted_app_folder + '/scripts/upgrade', args=args_list, env=env_dict)[0] From 3eb089ffc062b3f3cccd833103b3b218bb35e3ea Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 18 Aug 2019 01:41:34 +0200 Subject: [PATCH 0128/3170] Add unit/functional tests for apps --- src/yunohost/tests/test_apps.py | 248 ++++++++++++++++++++++++++++++++ 1 file changed, 248 insertions(+) create mode 100644 src/yunohost/tests/test_apps.py diff --git a/src/yunohost/tests/test_apps.py b/src/yunohost/tests/test_apps.py new file mode 100644 index 000000000..3407c4f39 --- /dev/null +++ b/src/yunohost/tests/test_apps.py @@ -0,0 +1,248 @@ +import glob +import os +import pytest +import shutil +import requests + +from moulinette import m18n +from moulinette.utils.filesystem import mkdir + +from yunohost.app import app_install, app_remove, app_ssowatconf, _is_installed +from yunohost.domain import _get_maindomain, domain_add, domain_remove, domain_list +from yunohost.utils.error import YunohostError +from yunohost.tests.test_permission import check_LDAP_db_integrity, check_permission_for_apps + + +MAIN_DOMAIN = _get_maindomain() + + +def setup_function(function): + + clean() + +def teardown_function(function): + + clean() + +def clean(): + + # Make sure we have a ssowat + os.system("mkdir -p /etc/ssowat/") + app_ssowatconf() + + if _is_installed("legacy_app"): + app_remove("legacy_app") + + to_remove = [] + to_remove += glob.glob("/etc/nginx/conf.d/*.d/*legacy*") + for filepath in to_remove: + os.remove(filepath) + + to_remove = [] + to_remove += glob.glob("/etc/yunohost/apps/*legacy_app*") + to_remove += glob.glob("/var/www/*legacy*") + for folderpath in to_remove: + shutil.rmtree(folderpath, ignore_errors=True) + + +@pytest.fixture(autouse=True) +def check_LDAP_db_integrity_call(): + check_LDAP_db_integrity() + yield + check_LDAP_db_integrity() + + +@pytest.fixture(autouse=True) +def check_permission_for_apps_call(): + check_permission_for_apps() + yield + check_permission_for_apps() + +@pytest.fixture(scope="session") +def secondary_domain(request): + + if "example.test" not in domain_list()["domains"]: + domain_add("example.test") + + def remove_example_domain(): + domain_remove("example.test") + request.addfinalizer(remove_example_domain) + + return "example.test" + + +# +# Helpers # +# + +def app_expected_files(domain, app): + + yield "/etc/nginx/conf.d/%s.d/%s.conf" % (domain, app) + yield "/var/www/%s/index.html" % app + yield "/etc/yunohost/apps/%s/settings.yml" % app + yield "/etc/yunohost/apps/%s/manifest.json" % app + yield "/etc/yunohost/apps/%s/scripts/install" % app + yield "/etc/yunohost/apps/%s/scripts/remove" % app + yield "/etc/yunohost/apps/%s/scripts/backup" % app + yield "/etc/yunohost/apps/%s/scripts/restore" % app + + +def app_is_installed(domain, app): + + return _is_installed(app) and all(os.path.exists(f) for f in app_expected_files(domain, app)) + + +def app_is_not_installed(domain, app): + + return not _is_installed(app) and not all(os.path.exists(f) for f in app_expected_files(domain, app)) + + +def app_is_exposed_on_http(domain, path, message_in_page): + + try: + r = requests.get("http://127.0.0.1" + path + "/", headers={"Host": domain}, timeout=10) + return r.status_code == 200 and message_in_page in r.text + except Exception: + return False + + +def install_legacy_app(domain, path): + + app_install("./tests/apps/legacy_app_ynh", + args="domain=%s&path=%s" % (domain, path), + force=True) + + +def test_legacy_app_install_main_domain(): + + install_legacy_app(MAIN_DOMAIN, "/legacy") + + assert app_is_installed(MAIN_DOMAIN, "legacy_app") + assert app_is_exposed_on_http(MAIN_DOMAIN, "/legacy", "This is a dummy app") + + app_remove("legacy_app") + + assert app_is_not_installed(MAIN_DOMAIN, "legacy_app") + + +def test_legacy_app_install_secondary_domain(secondary_domain): + + install_legacy_app(secondary_domain, "/legacy") + + assert app_is_installed(secondary_domain, "legacy_app") + assert app_is_exposed_on_http(secondary_domain, "/legacy", "This is a dummy app") + + app_remove("legacy_app") + + assert app_is_not_installed(secondary_domain, "legacy_app") + + +def test_legacy_app_install_secondary_domain_on_root(secondary_domain): + + install_legacy_app(secondary_domain, "/") + + assert app_is_installed(secondary_domain, "legacy_app") + assert app_is_exposed_on_http(secondary_domain, "/", "This is a dummy app") + + app_remove("legacy_app") + + assert app_is_not_installed(secondary_domain, "legacy_app") + + +def test_legacy_app_install_private(secondary_domain): + + install_legacy_app(secondary_domain, "/legacy") + + settings = open("/etc/yunohost/apps/legacy_app/settings.yml", "r").read() + new_settings = settings.replace("\nunprotected_uris: /", "") + assert new_settings != settings + open("/etc/yunohost/apps/legacy_app/settings.yml", "w").write(new_settings) + app_ssowatconf() + + assert app_is_installed(secondary_domain, "legacy_app") + assert not app_is_exposed_on_http(secondary_domain, "/legacy", "This is a dummy app") + + app_remove("legacy_app") + + assert app_is_not_installed(secondary_domain, "legacy_app") + + +def test_legacy_app_install_unknown_domain(): + + with pytest.raises(YunohostError): + install_legacy_app("whatever.nope", "/legacy") + # TODO check error message + + assert app_is_not_installed("whatever.nope", "legacy_app") + + +def test_legacy_app_install_multiple_instances(secondary_domain): + + install_legacy_app(secondary_domain, "/foo") + install_legacy_app(secondary_domain, "/bar") + + assert app_is_installed(secondary_domain, "legacy_app") + assert app_is_exposed_on_http(secondary_domain, "/foo", "This is a dummy app") + + assert app_is_installed(secondary_domain, "legacy_app__2") + assert app_is_exposed_on_http(secondary_domain, "/bar", "This is a dummy app") + + app_remove("legacy_app") + + assert app_is_not_installed(secondary_domain, "legacy_app") + assert app_is_installed(secondary_domain, "legacy_app__2") + + app_remove("legacy_app__2") + + assert app_is_not_installed(secondary_domain, "legacy_app") + assert app_is_not_installed(secondary_domain, "legacy_app__2") + + +def test_legacy_app_install_path_unavailable(secondary_domain): + + # These will be removed in teardown + install_legacy_app(secondary_domain, "/legacy") + + with pytest.raises(YunohostError): + install_legacy_app(secondary_domain, "/") + # TODO check error message + + assert app_is_installed(secondary_domain, "legacy_app") + assert app_is_not_installed(secondary_domain, "legacy_app__2") + + +def test_legacy_app_failed_install(secondary_domain): + + mkdir("/var/www/legacy_app/", 0o750) + + with pytest.raises(YunohostError): + install_legacy_app(secondary_domain, "/legacy") + # TODO check error message + + assert app_is_not_installed(secondary_domain, "legacy_app") + + +def test_legacy_app_install_with_nginx_down(secondary_domain): + + os.system("systemctl stop nginx") + + with pytest.raises(YunohostError): + install_legacy_app(secondary_domain, "/legacy") + + os.system("systemctl start nginx") + + +def test_legacy_app_failed_remove(): + + # FIXME What's supposed to happen lol + raise NotImplementedError + + +def test_legacy_app_install_fucksup_nginx(): + + # FIXME What's supposed to happen lol + raise NotImplementedError + +def test_legacy_app_install_with_dpkg_fuckedup(): + + raise NotImplementedError From 799c68f1a88faa1036c874007c09c6e3e8d40620 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 24 Aug 2019 00:15:42 +0200 Subject: [PATCH 0129/3170] Moar tests for apps breaking the system --- src/yunohost/tests/test_apps.py | 119 +++++++++++++++++++++++++------- 1 file changed, 94 insertions(+), 25 deletions(-) diff --git a/src/yunohost/tests/test_apps.py b/src/yunohost/tests/test_apps.py index 3407c4f39..1dcffbf0e 100644 --- a/src/yunohost/tests/test_apps.py +++ b/src/yunohost/tests/test_apps.py @@ -13,9 +13,6 @@ from yunohost.utils.error import YunohostError from yunohost.tests.test_permission import check_LDAP_db_integrity, check_permission_for_apps -MAIN_DOMAIN = _get_maindomain() - - def setup_function(function): clean() @@ -33,17 +30,24 @@ def clean(): if _is_installed("legacy_app"): app_remove("legacy_app") + if _is_installed("break_yo_system"): + app_remove("break_yo_system") + to_remove = [] to_remove += glob.glob("/etc/nginx/conf.d/*.d/*legacy*") + to_remove += glob.glob("/etc/nginx/conf.d/*.d/*break_yo_system*") for filepath in to_remove: os.remove(filepath) to_remove = [] to_remove += glob.glob("/etc/yunohost/apps/*legacy_app*") + to_remove += glob.glob("/etc/yunohost/apps/*break_yo_system*") to_remove += glob.glob("/var/www/*legacy*") for folderpath in to_remove: shutil.rmtree(folderpath, ignore_errors=True) + os.system("systemctl start nginx") + @pytest.fixture(autouse=True) def check_LDAP_db_integrity_call(): @@ -78,13 +82,12 @@ def secondary_domain(request): def app_expected_files(domain, app): yield "/etc/nginx/conf.d/%s.d/%s.conf" % (domain, app) - yield "/var/www/%s/index.html" % app + if app.startswith("legacy_app"): + yield "/var/www/%s/index.html" % app yield "/etc/yunohost/apps/%s/settings.yml" % app yield "/etc/yunohost/apps/%s/manifest.json" % app yield "/etc/yunohost/apps/%s/scripts/install" % app yield "/etc/yunohost/apps/%s/scripts/remove" % app - yield "/etc/yunohost/apps/%s/scripts/backup" % app - yield "/etc/yunohost/apps/%s/scripts/restore" % app def app_is_installed(domain, app): @@ -113,16 +116,25 @@ def install_legacy_app(domain, path): force=True) +def install_break_yo_system(domain, breakwhat): + + app_install("./tests/apps/break_yo_system_ynh", + args="domain=%s&breakwhat=%s" % (domain, breakwhat), + force=True) + + def test_legacy_app_install_main_domain(): - install_legacy_app(MAIN_DOMAIN, "/legacy") + main_domain = _get_maindomain() - assert app_is_installed(MAIN_DOMAIN, "legacy_app") - assert app_is_exposed_on_http(MAIN_DOMAIN, "/legacy", "This is a dummy app") + install_legacy_app(main_domain, "/legacy") + + assert app_is_installed(main_domain, "legacy_app") + assert app_is_exposed_on_http(main_domain, "/legacy", "This is a dummy app") app_remove("legacy_app") - assert app_is_not_installed(MAIN_DOMAIN, "legacy_app") + assert app_is_not_installed(main_domain, "legacy_app") def test_legacy_app_install_secondary_domain(secondary_domain): @@ -211,15 +223,10 @@ def test_legacy_app_install_path_unavailable(secondary_domain): assert app_is_not_installed(secondary_domain, "legacy_app__2") -def test_legacy_app_failed_install(secondary_domain): - - mkdir("/var/www/legacy_app/", 0o750) +def test_legacy_app_install_bad_args(): with pytest.raises(YunohostError): - install_legacy_app(secondary_domain, "/legacy") - # TODO check error message - - assert app_is_not_installed(secondary_domain, "legacy_app") + install_legacy_app("this.domain.does.not.exists", "/legacy") def test_legacy_app_install_with_nginx_down(secondary_domain): @@ -229,20 +236,82 @@ def test_legacy_app_install_with_nginx_down(secondary_domain): with pytest.raises(YunohostError): install_legacy_app(secondary_domain, "/legacy") - os.system("systemctl start nginx") + +def test_legacy_app_failed_install(secondary_domain): + + # This will conflict with the folder that the app + # attempts to create, making the install fail + mkdir("/var/www/legacy_app/", 0o750) + + with pytest.raises(YunohostError): + install_legacy_app(secondary_domain, "/legacy") + # TODO check error message + + assert app_is_not_installed(secondary_domain, "legacy_app") -def test_legacy_app_failed_remove(): +def test_legacy_app_failed_remove(secondary_domain): - # FIXME What's supposed to happen lol - raise NotImplementedError + install_legacy_app(secondary_domain, "/legacy") + + # The remove script runs with set -eu and attempt to remove this + # file without -f, so will fail if it's not there ;) + os.remove("/etc/nginx/conf.d/%s.d/%s.conf" % (secondary_domain, "legacy_app")) + with pytest.raises(YunohostError): + app_remove("legacy") + + # + # Well here, we hit the classical issue where if an app removal script + # fails, so far there's no obvious way to make sure that all files related + # to this app got removed ... + # + assert app_is_not_installed(secondary_domain, "legacy") -def test_legacy_app_install_fucksup_nginx(): +def test_systemfuckedup_during_app_install(secondary_domain): - # FIXME What's supposed to happen lol - raise NotImplementedError + with pytest.raises(YunohostError): + install_break_yo_system(secondary_domain, breakwhat="install") + os.system("nginx -t") + os.system("systemctl status nginx") -def test_legacy_app_install_with_dpkg_fuckedup(): + assert app_is_not_installed(secondary_domain, "break_yo_system") + + +def test_systemfuckedup_during_app_remove(secondary_domain): + + install_break_yo_system(secondary_domain, breakwhat="remove") + + with pytest.raises(YunohostError): + app_remove("break_yo_system") + os.system("nginx -t") + os.system("systemctl status nginx") + + assert app_is_not_installed(secondary_domain, "break_yo_system") + + +def test_systemfuckedup_during_app_install_and_remove(secondary_domain): + + with pytest.raises(YunohostError): + install_break_yo_system(secondary_domain, breakwhat="everything") + + assert app_is_not_installed(secondary_domain, "break_yo_system") + + +def test_systemfuckedup_during_app_upgrade(secondary_domain): raise NotImplementedError + + install_break_yo_system(secondary_domain, breakwhat="upgrade") + + #app_upgrade("break_yo_system", ...) + + +def test_failed_multiple_app_upgrade(secondary_domain): + + raise NotImplementedError + + install_legacy_app(secondary_domain, "/legacy") + install_break_yo_system(secondary_domain, breakwhat="upgrade") + + app_upgrade(["break_yo_system", "legacy"]) From 28c73cb336aeef910f460308eb89465078ea2aab Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 15 Sep 2019 02:11:37 +0200 Subject: [PATCH 0130/3170] Implement those remaining tests --- src/yunohost/tests/test_apps.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/yunohost/tests/test_apps.py b/src/yunohost/tests/test_apps.py index 1dcffbf0e..9c85df1e9 100644 --- a/src/yunohost/tests/test_apps.py +++ b/src/yunohost/tests/test_apps.py @@ -7,7 +7,7 @@ import requests from moulinette import m18n from moulinette.utils.filesystem import mkdir -from yunohost.app import app_install, app_remove, app_ssowatconf, _is_installed +from yunohost.app import app_install, app_remove, app_ssowatconf, _is_installed, app_upgrade from yunohost.domain import _get_maindomain, domain_add, domain_remove, domain_list from yunohost.utils.error import YunohostError from yunohost.tests.test_permission import check_LDAP_db_integrity, check_permission_for_apps @@ -27,12 +27,15 @@ def clean(): os.system("mkdir -p /etc/ssowat/") app_ssowatconf() - if _is_installed("legacy_app"): - app_remove("legacy_app") - + # Gotta first remove break yo system + # because some remaining stuff might + # make the other app_remove crashs ;P if _is_installed("break_yo_system"): app_remove("break_yo_system") + if _is_installed("legacy_app"): + app_remove("legacy_app") + to_remove = [] to_remove += glob.glob("/etc/nginx/conf.d/*.d/*legacy*") to_remove += glob.glob("/etc/nginx/conf.d/*.d/*break_yo_system*") @@ -46,6 +49,7 @@ def clean(): for folderpath in to_remove: shutil.rmtree(folderpath, ignore_errors=True) + os.system("systemctl reset-failed nginx") # Reset failed quota for service to avoid running into start-limit rate ? os.system("systemctl start nginx") @@ -300,18 +304,18 @@ def test_systemfuckedup_during_app_install_and_remove(secondary_domain): def test_systemfuckedup_during_app_upgrade(secondary_domain): - raise NotImplementedError - install_break_yo_system(secondary_domain, breakwhat="upgrade") - #app_upgrade("break_yo_system", ...) + with pytest.raises(YunohostError): + app_upgrade("break_yo_system", file="./tests/apps/break_yo_system_ynh") def test_failed_multiple_app_upgrade(secondary_domain): - raise NotImplementedError - install_legacy_app(secondary_domain, "/legacy") install_break_yo_system(secondary_domain, breakwhat="upgrade") - app_upgrade(["break_yo_system", "legacy"]) + with pytest.raises(YunohostError): + app_upgrade(["break_yo_system", "legacy_app"], + file={"break_yo_system": "./tests/apps/break_yo_system_ynh", + "legacy": "./tests/apps/legacy_app_ynh"}) From a476deb7fbeb73b23d6282b869e07cbf23f316be Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 15 Sep 2019 02:11:53 +0200 Subject: [PATCH 0131/3170] Tweak test conf for easier debugging --- src/yunohost/tests/conftest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/tests/conftest.py b/src/yunohost/tests/conftest.py index a2dc585bd..ce321933c 100644 --- a/src/yunohost/tests/conftest.py +++ b/src/yunohost/tests/conftest.py @@ -41,11 +41,11 @@ def pytest_cmdline_main(config): root_handlers = set(handlers) # Define loggers level - level = 'INFO' + level = 'DEBUG' if config.option.yunodebug: tty_level = 'DEBUG' else: - tty_level = 'SUCCESS' + tty_level = 'INFO' # Custom logging configuration logging = { From aa3687ba029abae89f9a01d6e45df3ab843e93ea Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 15 Sep 2019 02:13:02 +0200 Subject: [PATCH 0132/3170] Small trick needed to be able to test chained app upgrades --- 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 d64baeaa3..6b75d82d0 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -620,7 +620,10 @@ def app_upgrade(app=[], url=None, file=None): app_dict = app_info(app_instance_name, raw=True) - if file: + if file and isinstance(file, dict): + # We use this dirty hack to test chained upgrades in unit/functional tests + manifest, extracted_app_folder = _extract_app_from_file(file[app_instance_name]) + elif file: manifest, extracted_app_folder = _extract_app_from_file(file) elif url: manifest, extracted_app_folder = _fetch_app_from_git(url) From cc59501b55b269747be983aacc392f9d99aa8522 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 15 Sep 2019 16:59:34 +0200 Subject: [PATCH 0133/3170] Naive implementation of visitors group (without any relation to the ssowat conf yet) --- data/other/ldap_scheme.yml | 6 ++++++ locales/en.json | 3 +++ .../0011_setup_group_permission.py | 4 ++++ src/yunohost/permission.py | 13 +++++++++---- src/yunohost/user.py | 15 ++++++++++----- 5 files changed, 32 insertions(+), 9 deletions(-) diff --git a/data/other/ldap_scheme.yml b/data/other/ldap_scheme.yml index caa8fffb2..660d6fbb5 100644 --- a/data/other/ldap_scheme.yml +++ b/data/other/ldap_scheme.yml @@ -57,6 +57,12 @@ children: objectClass: - posixGroup - groupOfNamesYnh + cn=visitors,ou=groups: + cn: visitors + gidNumber: "4003" + objectClass: + - posixGroup + - groupOfNamesYnh depends_children: cn=mail.main,ou=permission: diff --git a/locales/en.json b/locales/en.json index ae349edf3..5df21b684 100644 --- a/locales/en.json +++ b/locales/en.json @@ -230,6 +230,9 @@ "group_already_exist_on_system": "Group {group} already exists in the system group", "group_created": "Group '{group}' successfully created", "group_creation_failed": "Failed to create group {group}: {error}", + "group_cannot_edit_all_users": "The group 'all_users' cannot be edited manually. It is a special group meant to contain all users registered in Yunohost", + "group_cannot_edit_visitors": "The group 'visitors' cannot be edited manually. It is a special group representing anonymous visitors", + "group_cannot_edit_primary_group": "The group '{group}' cannot be edited manually. It is the primary group meant to contain only one specific user.", "group_cannot_be_edited": "The group {group} cannot be edited manually.", "group_cannot_be_deleted": "The group {group} cannot be deleted manually.", "group_deleted": "Group '{group}' deleted", diff --git a/src/yunohost/data_migrations/0011_setup_group_permission.py b/src/yunohost/data_migrations/0011_setup_group_permission.py index 8949239e0..b3e11cb14 100644 --- a/src/yunohost/data_migrations/0011_setup_group_permission.py +++ b/src/yunohost/data_migrations/0011_setup_group_permission.py @@ -63,6 +63,7 @@ class MyMigration(Migration): self.remove_if_exists("cn=sftpusers,ou=groups") self.remove_if_exists("ou=permission") self.remove_if_exists('cn=all_users,ou=groups') + self.remove_if_exists('cn=visitors,ou=groups') attr_dict = ldap_map['parents']['ou=permission'] ldap.add('ou=permission', attr_dict) @@ -70,6 +71,9 @@ class MyMigration(Migration): attr_dict = ldap_map['children']['cn=all_users,ou=groups'] ldap.add('cn=all_users,ou=groups', attr_dict) + attr_dict = ldap_map['children']['cn=visitors,ou=groups'] + ldap.add('cn=visitors,ou=groups', attr_dict) + for rdn, attr_dict in ldap_map['depends_children'].items(): ldap.add(rdn, attr_dict) except Exception as e: diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 1472f4b88..dbfc6e6f5 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -142,10 +142,15 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, # we shall warn the users that they should probably choose between one or the other, # because the current situation is probably not what they expect / is temporary ? - if len(new_allowed_groups) > 1 and "all_users" in new_allowed_groups: - # FIXME : i18n - # FIXME : write a better explanation ? - logger.warning("This permission is currently enabled for all users in addition to other groups. You probably want to either remove the 'all_users' permission or remove the specific groups currently allowed.") + if len(new_allowed_groups) > 1: + if "all_users" in new_allowed_groups: + # FIXME : i18n + # FIXME : write a better explanation ? + logger.warning("This permission is currently enabled for all users in addition to other groups. You probably want to either remove the 'all_users' permission or remove the other groups currently allowed.") + if "visitors" in new_allowed_groups: + # FIXME : i18n + # FIXME : write a better explanation ? + logger.warning("This permission is currently enabled for visitors in addition to other groups. You probably want to either remove the 'visitors' permission or remove the other groups currently allowed.") # Don't update LDAP if we update exactly the same values if set(new_allowed_groups) == set(current_allowed_groups): diff --git a/src/yunohost/user.py b/src/yunohost/user.py index c6413d7e1..581354f77 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -635,7 +635,7 @@ def user_group_delete(operation_logger, groupname, force=False, sync_perm=True): # # We also can't delete "all_users" because that's a special group... existing_users = user_list()['users'].keys() - undeletable_groups = existing_users + ["all_users", "admins"] + undeletable_groups = existing_users + ["all_users", "visitors"] if groupname in undeletable_groups and not force: raise YunohostError('group_cannot_be_deleted', group=groupname) @@ -670,13 +670,18 @@ def user_group_update(operation_logger, groupname, add=None, remove=None, force= from yunohost.permission import permission_sync_to_user from yunohost.utils.ldap import _get_ldap_interface + existing_users = user_list()['users'].keys() + # Refuse to edit a primary group of a user (e.g. group 'sam' related to user 'sam') # Those kind of group should only ever contain the user (e.g. sam) and only this one. # We also can't edit "all_users" without the force option because that's a special group... - existing_users = user_list()['users'].keys() - uneditable_groups = existing_users + ["all_users", "admins"] - if groupname in uneditable_groups and not force: - raise YunohostError('group_cannot_be_edited', group=groupname) + if not force: + if groupname == "all_users": + raise YunohostError('group_cannot_edit_all_users') + elif groupname == "all_users": + raise YunohostError('group_cannot_edit_visitors') + elif groupname in existing_users: + raise YunohostError('group_cannot_edit_primary_group', group=groupname) # We extract the uid for each member of the group to keep a simple flat list of members current_group = user_group_info(groupname)["members"] From 95a8dfa71c22103152a9241bc508de96f04cfe19 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 15 Sep 2019 16:59:44 +0200 Subject: [PATCH 0134/3170] Simplify part of app_ssowatconf --- src/yunohost/app.py | 49 ++++++++++++++++++++------------------------- 1 file changed, 22 insertions(+), 27 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index ab290cb4d..a9c91aaf5 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -41,7 +41,7 @@ from datetime import datetime from moulinette import msignals, m18n, msettings from moulinette.utils.log import getActionLogger -from moulinette.utils.filesystem import read_json, read_toml +from moulinette.utils.filesystem import read_json, read_toml, read_yaml from yunohost.service import service_log, service_status, _run_service_command from yunohost.utils import packages @@ -1366,34 +1366,29 @@ def app_ssowatconf(): return s.split(',') if s else [] for app in apps_list: - with open(APPS_SETTING_PATH + app['id'] + '/settings.yml') as f: - app_settings = yaml.load(f) - if 'no_sso' in app_settings: - continue + app_settings = read_yaml(APPS_SETTING_PATH + app['id'] + '/settings.yml') - for item in _get_setting(app_settings, 'skipped_uris'): - if item[-1:] == '/': - item = item[:-1] - skipped_urls.append(app_settings['domain'] + app_settings['path'].rstrip('/') + item) - for item in _get_setting(app_settings, 'skipped_regex'): - skipped_regex.append(item) - for item in _get_setting(app_settings, 'unprotected_uris'): - if item[-1:] == '/': - item = item[:-1] - unprotected_urls.append(app_settings['domain'] + app_settings['path'].rstrip('/') + item) - for item in _get_setting(app_settings, 'unprotected_regex'): - unprotected_regex.append(item) - for item in _get_setting(app_settings, 'protected_uris'): - if item[-1:] == '/': - item = item[:-1] - protected_urls.append(app_settings['domain'] + app_settings['path'].rstrip('/') + item) - for item in _get_setting(app_settings, 'protected_regex'): - protected_regex.append(item) - if 'redirected_urls' in app_settings: - redirected_urls.update(app_settings['redirected_urls']) - if 'redirected_regex' in app_settings: - redirected_regex.update(app_settings['redirected_regex']) + if 'no_sso' in app_settings: + continue + + app_root_webpath = app_settings['domain'] + app_settings['path'].rstrip('/') + + # Skipped + skipped_urls += [app_root_webpath + uri.rstrip("/") for uri in _get_setting(app_settings, 'skipped_uris')] + skipped_regex += _get_setting(app_settings, 'skipped_regex') + + # Unprotected + unprotected_urls += [app_root_webpath + uri.rstrip("/") for uri in _get_setting(app_settings, 'unprotected_uris')] + unprotected_regex += _get_setting(app_settings, 'unprotected_regex') + + # Protected + unprotected_urls += [app_root_webpath + uri.rstrip("/") for uri in _get_setting(app_settings, 'protected_uris')] + unprotected_regex += _get_setting(app_settings, 'protected_regex') + + # Redirected + redirected_urls.update(app_settings.get('redirected_urls', {})) + redirected_regex.update(app_settings.get('redirected_regex', {})) for domain in domains: skipped_urls.extend([domain + '/yunohost/admin', domain + '/yunohost/api']) From 8abfd2a6e60bfd53d6fb398261e002e223a0c217 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 15 Sep 2019 17:58:41 +0200 Subject: [PATCH 0135/3170] Naive implementation of protected/unprotected inplementation using the visitors group --- src/yunohost/app.py | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index a9c91aaf5..52371ff29 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1345,6 +1345,7 @@ def app_ssowatconf(): main_domain = _get_maindomain() domains = domain_list()['domains'] + all_permissions = user_permission_list(full=True)['permissions'] skipped_urls = [] skipped_regex = [] @@ -1378,18 +1379,32 @@ def app_ssowatconf(): skipped_urls += [app_root_webpath + uri.rstrip("/") for uri in _get_setting(app_settings, 'skipped_uris')] skipped_regex += _get_setting(app_settings, 'skipped_regex') - # Unprotected - unprotected_urls += [app_root_webpath + uri.rstrip("/") for uri in _get_setting(app_settings, 'unprotected_uris')] - unprotected_regex += _get_setting(app_settings, 'unprotected_regex') - - # Protected - unprotected_urls += [app_root_webpath + uri.rstrip("/") for uri in _get_setting(app_settings, 'protected_uris')] - unprotected_regex += _get_setting(app_settings, 'protected_regex') - # Redirected redirected_urls.update(app_settings.get('redirected_urls', {})) redirected_regex.update(app_settings.get('redirected_regex', {})) + # Legacy permission system using (un)protected_uris and _regex managed in app settings... + unprotected_urls += [app_root_webpath + uri.rstrip("/") for uri in _get_setting(app_settings, 'unprotected_uris')] + unprotected_urls += [app_root_webpath + uri.rstrip("/") for uri in _get_setting(app_settings, 'protected_uris')] + unprotected_regex += _get_setting(app_settings, 'unprotected_regex') + unprotected_regex += _get_setting(app_settings, 'protected_regex') + + # New permission system + this_app_perms = {name: info for name, info in all_permissions.items if name.startswith(app + ".")} + for perm_name, perm_info in this_app_perms: + urls = [url.rstrip("/") for url in perm_info["urls"]] + if "visitors" in perm_info["allowed"]: + unprotected_urls += urls + + # Legacy stuff : we remove now unprotected-urls that might have been declared as protected earlier... + protected_urls = [u for u in protected_urls if u not in urls] + else: + # TODO : small optimization to implement : we don't need to explictly add all the app roots + protected_urls += urls + + # Legacy stuff : we remove now unprotected-urls that might have been declared as protected earlier... + unprotected_urls = [u for u in unprotected_urls if u not in urls] + for domain in domains: skipped_urls.extend([domain + '/yunohost/admin', domain + '/yunohost/api']) @@ -1397,8 +1412,10 @@ def app_ssowatconf(): skipped_regex.append("^[^/]*/%.well%-known/acme%-challenge/.*$") skipped_regex.append("^[^/]*/%.well%-known/autoconfig/mail/config%-v1%.1%.xml.*$") + + permissions_per_url = {} - for permission_name, permission_infos in user_permission_list(full=True)['permissions'].items(): + for permission_name, permission_infos in all_permissions.items(): for url in permission_infos["urls"]: permissions_per_url[url] = permission_infos['corresponding_users'] From c4743398e687738a8b55bd500f219f7a69afe28e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 15 Sep 2019 18:10:58 +0200 Subject: [PATCH 0136/3170] Deprecate (un)protected_uris and _regex settings + more explicit deprecation warning for app_add/remove/clearaccess --- data/helpers.d/setting | 4 +++- src/yunohost/app.py | 8 ++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index 502da1ed7..d083ed563 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -176,6 +176,8 @@ else: elif action == "set": if key in ['redirected_urls', 'redirected_regex']: value = yaml.load(value) + if key in ["unprotected_uris", "unprotected_regex", "protected_uris", "protected_regex"]: + logger.warning("/!\\ Packagers ! This app is using the legacy permission system. Please delete these legacy settings and use the new helpers ynh_permission_{create,urls,update,delete} and the 'visitors' group to manage public/private access.") settings[key] = value else: raise ValueError("action should either be get, set or delete") @@ -249,7 +251,7 @@ ynh_permission_create() { # Remove a permission for the app (note that when the app is removed all permission is automatically removed) # -# usage: ynh_permission_remove --permission "permission" +# usage: ynh_permission_delete --permission "permission" # | arg: permission - the name for the permission (by default a permission named "main" is removed automatically when the app is removed) # # example: ynh_permission_delete --permission editors diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 52371ff29..8b0c99d46 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1037,6 +1037,8 @@ def app_addaccess(apps, users=[]): """ from yunohost.permission import user_permission_update + logger.warning("/!\\ Packagers ! This app is using the legacy permission system. Please use the new helpers ynh_permission_{create,urls,update,delete} and the 'visitors' group to manage permissions.") + output = {} for app in apps: permission = user_permission_update(app+".main", add=users) @@ -1056,6 +1058,8 @@ def app_removeaccess(apps, users=[]): """ from yunohost.permission import user_permission_update + logger.warning("/!\\ Packagers ! This app is using the legacy permission system. Please use the new helpers ynh_permission_{create,urls,update,delete} and the 'visitors' group to manage permissions.") + output = {} for app in apps: permission = user_permission_update(app+".main", remove=users) @@ -1074,6 +1078,8 @@ def app_clearaccess(apps): """ from yunohost.permission import user_permission_reset + logger.warning("/!\\ Packagers ! This app is using the legacy permission system. Please use the new helpers ynh_permission_{create,urls,update,delete} and the 'visitors' group to manage permissions.") + output = {} for app in apps: permission = user_permission_reset(app+".main") @@ -1181,6 +1187,8 @@ def app_setting(app, key, value=None, delete=False): # FIXME: Allow multiple values for some keys? if key in ['redirected_urls', 'redirected_regex']: value = yaml.load(value) + if key in ["unprotected_uris", "unprotected_regex", "protected_uris", "protected_regex"]: + logger.warning("/!\ Packagers ! This app is using the legacy permission system. Please delete these legacy settings and use the new helpers ynh_permission_{create,urls,update,delete} and the 'visitors' group to manage public/private access.") app_settings[key] = value _set_app_settings(app, app_settings) From b2a26a64a74d2f7289d857b7bb780ac2f91741ce Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 15 Sep 2019 18:33:31 +0200 Subject: [PATCH 0137/3170] Naively migrate legacy and classical unprotected_uris = / that sets the app as public --- src/yunohost/app.py | 8 +++++++- .../data_migrations/0011_setup_group_permission.py | 6 ++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 8b0c99d46..537616e68 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -733,7 +733,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback from yunohost.log import OperationLogger - from yunohost.permission import user_permission_list, permission_create, permission_urls, permission_delete, permission_sync_to_user + from yunohost.permission import user_permission_list, permission_create, permission_urls, permission_delete, permission_sync_to_user, user_permission_update # Fetch or extract sources if not os.path.exists(INSTALL_TMP): @@ -952,7 +952,13 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu domain = app_settings.get('domain', None) path = app_settings.get('path', None) if domain and path: + # FIXME : might want to move this to before running the install script because some app need to run install script during initialization etc (idk) ? permission_urls(app_instance_name+".main", add=[domain+path], sync_perm=False) + + # Migrate classic public app still using the legacy unprotected_uris + if app_settings.get("unprotected_uris", None) == "/": + user_permission_update(app_instance_name+".main", remove="all_users", add="visitors", sync_perm=False) + permission_sync_to_user() logger.success(m18n.n('installation_complete')) diff --git a/src/yunohost/data_migrations/0011_setup_group_permission.py b/src/yunohost/data_migrations/0011_setup_group_permission.py index b3e11cb14..c79d80e0c 100644 --- a/src/yunohost/data_migrations/0011_setup_group_permission.py +++ b/src/yunohost/data_migrations/0011_setup_group_permission.py @@ -113,6 +113,12 @@ class MyMigration(Migration): user_permission_update(app+".main", remove="all_users", add=allowed_group, sync_perm=False) app_setting(app, 'allowed_users', delete=True) + # Migrate classic public app still using the legacy unprotected_uris + if app_setting(app, "unprotected_uris") == "/": + user_permission_update(app+".main", remove="all_users", add="visitors", sync_perm=False) + + permission_sync_to_user() + def run(self): # FIXME : what do we really want to do here ... From 821a3ac4ff0f3180ff5b5884f020c02b3a982b34 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 15 Sep 2019 18:53:25 +0200 Subject: [PATCH 0138/3170] Draft tests to check that permissions are actually propagated and effective on the SSO --- src/yunohost/tests/test_permission.py | 55 ++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index 94728505d..1c81e015f 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -333,7 +333,7 @@ def test_permission_remove_url_not_added(): def test_permission_app_install(): app_install("./tests/apps/permissions_app_ynh", - args="domain=%s&path=%s&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True) + args="domain=%s&path=%s&is_public=0&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True) res = user_permission_list(full=True)['permissions'] assert "permissions_app.main" in res @@ -361,7 +361,7 @@ def test_permission_app_install(): def test_permission_app_remove(): app_install("./tests/apps/permissions_app_ynh", - args="domain=%s&path=%s&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True) + args="domain=%s&path=%s&is_public=0&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True) app_remove("permissions_app") # Check all permissions for this app got deleted @@ -383,3 +383,54 @@ def test_permission_app_change_url(): assert res['permissions_app.main']['urls'] == [maindomain + "/newchangeurl"] assert res['permissions_app.admin']['urls'] == [maindomain + "/newchangeurl/admin"] assert res['permissions_app.dev']['urls'] == [maindomain + "/newchangeurl/dev"] + + +def test_permission_app_propagation_on_ssowat(): + + # TODO / FIXME : To be actually implemented later .... + raise NotImplementedError + + app_install("./tests/apps/permissions_app_ynh", + args="domain=%s&path=%s&is_public=1&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True) + + res = user_permission_list(full=True)['permissions'] + assert res['permissions_app.main']['allowed'] == ["all_users"] + + assert can_access(maindomain + "/urlpermissionapp", logged_as=None) + assert can_access(maindomain + "/urlpermissionapp", logged_as="alice") + + user_permission_update("permissions_app.main", remove="visitors", add="bob") + res = user_permission_list(full=True)['permissions'] + + assert cannot_access(maindomain + "/urlpermissionapp", logged_as=None) + assert cannot_access(maindomain + "/urlpermissionapp", logged_as="alice") + assert can_access(maindomain + "/urlpermissionapp", logged_as="bob") + + # Test admin access, as configured during install, only alice should be able to access it + + assert cannot_access(maindomain + "/urlpermissionapp/admin", logged_as=None) + assert cannot_access(maindomain + "/urlpermissionapp/admin", logged_as="alice") + assert can_access(maindomain + "/urlpermissionapp/admin", logged_as="bob") + +def test_permission_legacy_app_propagation_on_ssowat(): + + # TODO / FIXME : To be actually implemented later .... + raise NotImplementedError + + app_install("./tests/apps/legacy_app_ynh", + args="domain=%s&path=%s" % (maindomain, "/legacy"), force=True) + + # App is configured as public by default using the legacy unprotected_uri mechanics + # It should automatically be migrated during the install + assert res['permissions_app.main']['allowed'] == ["visitors"] + + assert can_access(maindomain + "/legacy", logged_as=None) + assert can_access(maindomain + "/legacy", logged_as="alice") + + # Try to update the permission and check that permissions are still consistent + user_permission_update("legacy_app.main", remove="visitors", add="bob") + res = user_permission_list(full=True)['permissions'] + + assert cannot_access(maindomain + "/legacy", logged_as=None) + assert cannot_access(maindomain + "/legacy", logged_as="alice") + assert can_access(maindomain + "/legacy", logged_as="bob") From 875c570c6dbcf406025478054ba09e98b6e09e81 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 16 Sep 2019 00:13:41 +0200 Subject: [PATCH 0139/3170] Check if the upgrade got manually interrupted, c.f. same stuff in app_install --- locales/en.json | 1 + src/yunohost/app.py | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index cc7955204..d584d80d9 100644 --- a/locales/en.json +++ b/locales/en.json @@ -409,6 +409,7 @@ "no_ipv6_connectivity": "IPv6 connectivity is not available", "no_restore_script": "No restore script found for the app '{app:s}'", "not_enough_disk_space": "Not enough free disk space on '{path:s}'", + "operation_interrupted": "The operation was manually interrupted?", "package_not_installed": "Package '{pkgname}' is not installed", "package_unexpected_error": "An unexpected error occurred processing the package '{pkgname}'", "package_unknown": "Unknown package '{pkgname}'", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 0784f1ecc..41c3faed6 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -679,7 +679,10 @@ def app_upgrade(app=[], url=None, file=None): finally: # Did the script succeed ? - if upgrade_retcode != 0: + if upgrade_retcode == -1: + error_msg = m18n.n('operation_interrupted') + operation_logger.error(error_msg) + elif upgrade_retcode != 0: error_msg = m18n.n('app_upgrade_failed', app=app_instance_name) operation_logger.error(error_msg) From a2813bd774c35a7cb89403f86d5251c3a92d653d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Tue, 17 Sep 2019 20:23:36 +0200 Subject: [PATCH 0140/3170] Language reworked --- locales/en.json | 746 ++++++++++++++++++++++++------------------------ 1 file changed, 373 insertions(+), 373 deletions(-) diff --git a/locales/en.json b/locales/en.json index be00d5b1e..cab2afff8 100644 --- a/locales/en.json +++ b/locales/en.json @@ -2,67 +2,67 @@ "aborting": "Aborting.", "action_invalid": "Invalid action '{action:s}'", "admin_password": "Administration password", - "admin_password_change_failed": "Unable to change password", - "admin_password_changed": "The administration password has been changed", + "admin_password_change_failed": "Cannot change password", + "admin_password_changed": "The administration password now changed", "admin_password_too_long": "Please choose a password shorter than 127 characters", - "already_up_to_date": "Nothing to do! Everything is already up to date!", - "app_action_cannot_be_ran_because_required_services_down": "This app requires some services which are currently down. Before continuing, you should try to restart the following services (and possibly investigate why they are down) : {services}", + "already_up_to_date": "Nothing to do. Everything is already up-to-date.", + "app_action_cannot_be_ran_because_required_services_down": "This app requires some services which are currently down. Before continuing, you should try to restart the following services (and possibly investigate why they are down): {services}", "app_already_installed": "{app:s} is already installed", - "app_already_installed_cant_change_url": "This app is already installed. The url cannot be changed just by this function. Look into `app changeurl` if it's available.", - "app_already_up_to_date": "{app:s} is already up to date", - "app_argument_choice_invalid": "Invalid choice for argument '{name:s}', it must be one of {choices:s}", - "app_argument_invalid": "Invalid value for argument '{name:s}': {error:s}", + "app_already_installed_cant_change_url": "This app is already installed. The URL cannot be changed just by this function. Look into `app changeurl` if it's available.", + "app_already_up_to_date": "{app:s} is already up-to-date", + "app_argument_choice_invalid": "Use one of these choices '{choices:s}' for the argument '{name:s}'", + "app_argument_invalid": "Pick a valid value for the argument '{name:s}': {error:s}", "app_argument_required": "Argument '{name:s}' is required", - "app_change_no_change_url_script": "The application {app_name:s} doesn't support changing it's URL yet, you might need to upgrade it.", - "app_change_url_failed_nginx_reload": "Failed to reload nginx. Here is the output of 'nginx -t':\n{nginx_errors:s}", + "app_change_no_change_url_script": "The application {app_name:s} doesn't support changing its URL yet, you might need to upgrade it.", + "app_change_url_failed_nginx_reload": "Could not reload NGINX. Here is the output of 'nginx -t':\n{nginx_errors:s}", "app_change_url_identical_domains": "The old and new domain/url_path are identical ('{domain:s}{path:s}'), nothing to do.", - "app_change_url_no_script": "This application '{app_name:s}' doesn't support url modification yet. Maybe you should upgrade the application.", - "app_change_url_success": "Successfully changed {app:s} url to {domain:s}{path:s}", - "app_extraction_failed": "Unable to extract installation files", - "app_id_invalid": "Invalid app id", + "app_change_url_no_script": "This application '{app_name:s}' doesn't support URL modification yet. Maybe you should upgrade it.", + "app_change_url_success": "{app:s} URL is now {domain:s}{path:s}", + "app_extraction_failed": "Could not extract the installation files", + "app_id_invalid": "Invalid app ID", "app_incompatible": "The app {app} is incompatible with your YunoHost version", - "app_install_files_invalid": "Invalid installation files", - "app_location_already_used": "The app '{app}' is already installed on that location ({path})", - "app_make_default_location_already_used": "Can't make the app '{app}' the default on the domain {domain} is already used by the other app '{other_app}'", - "app_location_install_failed": "Unable to install the app in this location because it conflit with the app '{other_app}' already installed on '{other_path}'", - "app_location_unavailable": "This url is not available or conflicts with the already installed app(s):\n{apps:s}", - "app_manifest_invalid": "Invalid app manifest: {error}", - "app_no_upgrade": "No apps to upgrade", - "app_not_upgraded": "The following apps were not upgraded: {apps}", + "app_install_files_invalid": "These files cannot be installed", + "app_location_already_used": "The app '{app}' is already installed in ({path})", + "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_install_failed": "Cannot install the app there because it conflicts with the app '{other_app}' already installed in '{other_path}'", + "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_no_upgrade": "Everything is up-to-date", + "app_not_upgraded": "These apps were not upgraded: {apps}", "app_not_correctly_installed": "{app:s} seems to be incorrectly installed", - "app_not_installed": "The application '{app:s}' is not installed. Here is the list of all installed apps: {all_apps}", + "app_not_installed": "The application '{app:s}' is not to be found among all installed apps: {all_apps}", "app_not_properly_removed": "{app:s} has not been properly removed", "app_package_need_update": "The app {app} package needs to be updated to follow YunoHost changes", - "app_removed": "{app:s} has been removed", + "app_removed": "{app:s} removed", "app_requirements_checking": "Checking required packages for {app}…", - "app_requirements_failed": "Unable to meet requirements for {app}: {error}", + "app_requirements_failed": "Did not meet requirements for {app}: {error}", "app_requirements_unmeet": "Requirements are not met for {app}, the package {pkgname} ({version}) must be {spec}", - "app_sources_fetch_failed": "Unable to fetch sources files, is the url correct?", + "app_sources_fetch_failed": "Could not fetch sources files, is the URL correct?", "app_start_install": "Installing application {app}…", "app_start_remove": "Removing application {app}…", - "app_start_backup": "Collecting files to be backuped for {app}…", + "app_start_backup": "Collecting files to be backed up for {app}…", "app_start_restore": "Restoring application {app}…", "app_unknown": "Unknown app", "app_unsupported_remote_type": "Unsupported remote type used for the app", - "app_upgrade_several_apps": "The following apps will be upgraded : {apps}", - "app_upgrade_app_name": "Now upgrading app {app}…", - "app_upgrade_failed": "Unable to upgrade {app:s}", - "app_upgrade_some_app_failed": "Unable to upgrade some applications", - "app_upgraded": "{app:s} has been upgraded", + "app_upgrade_several_apps": "The following apps will be upgraded: {apps}", + "app_upgrade_app_name": "Now upgrading {app}…", + "app_upgrade_failed": "Could not upgrade {app:s}", + "app_upgrade_some_app_failed": "Some applications could not be upgraded", + "app_upgraded": "{app:s} upgraded", "apps_permission_not_found": "No permission found for the installed apps", - "apps_permission_restoration_failed": "Permission '{permission:s}' for app {app:s} restoration has failed", - "appslist_corrupted_json": "Could not load the application lists. It looks like {filename:s} is corrupted.", - "appslist_could_not_migrate": "Could not migrate app list {appslist:s}! Unable to parse the url… The old cron job has been kept in {bkp_file:s}.", - "appslist_fetched": "The application list {appslist:s} has been fetched", + "apps_permission_restoration_failed": "Grant the permission permission '{permission:s}' to restore {app:s}", + "appslist_corrupted_json": "Could not load the application lists. It looks like {filename:s} is damaged.", + "appslist_could_not_migrate": "Could not migrate the app list {appslist:s}! Could not parse the URL… The old cron job was kept kept in {bkp_file:s}.", + "appslist_fetched": "Updated application list {appslist:s} fetched", "appslist_migrating": "Migrating application list {appslist:s}…", - "appslist_name_already_tracked": "There is already a registered application list with name {name:s}.", - "appslist_removed": "The application list {appslist:s} has been removed", - "appslist_retrieve_bad_format": "Retrieved file for application list {appslist:s} is not valid", - "appslist_retrieve_error": "Unable to retrieve the remote application list {appslist:s}: {error:s}", + "appslist_name_already_tracked": "A registered application list with name {name:s} already exists.", + "appslist_removed": "{appslist:s} application list removed", + "appslist_retrieve_bad_format": "Could not read the fetched application list {appslist:s}", + "appslist_retrieve_error": "Cannot retrieve the remote application list {appslist:s}: {error:s}", "appslist_unknown": "Application list {appslist:s} unknown.", - "appslist_url_already_tracked": "There is already a registered application list with url {url:s}.", + "appslist_url_already_tracked": "There is already a registered application list with the URL {url:s}.", "ask_current_admin_password": "Current administration password", - "ask_email": "Email address", + "ask_email": "E-mail address", "ask_firstname": "First name", "ask_lastname": "Last name", "ask_list_to_remove": "List to remove", @@ -72,109 +72,109 @@ "ask_new_path": "New path", "ask_password": "Password", "ask_path": "Path", - "backup_abstract_method": "This backup method hasn't yet been implemented", + "backup_abstract_method": "This backup method has yet to be implemented", "backup_action_required": "You must specify something to save", - "backup_actually_backuping": "Now creating a backup archive from the files collected…", - "backup_app_failed": "Unable to back up the app '{app:s}'", + "backup_actually_backuping": "Creating a backup archive from the collected files…", + "backup_app_failed": "Could not back up the app '{app:s}'", "backup_applying_method_borg": "Sending all files to backup into borg-backup repository…", "backup_applying_method_copy": "Copying all files to backup…", "backup_applying_method_custom": "Calling the custom backup method '{method:s}'…", - "backup_applying_method_tar": "Creating the backup tar archive…", - "backup_archive_app_not_found": "App '{app:s}' not found in the backup archive", - "backup_archive_broken_link": "Unable to access backup archive (broken link to {path:s})", - "backup_archive_mount_failed": "Mounting the backup archive failed", - "backup_archive_name_exists": "The backup's archive name already exists", + "backup_applying_method_tar": "Creating the backup TAR archive…", + "backup_archive_app_not_found": "Could not find the app '{app:s}' in the backup archive", + "backup_archive_broken_link": "Could not access the backup archive (broken link to {path:s})", + "backup_archive_mount_failed": "Could not mounting the backup archive", + "backup_archive_name_exists": "A backup archive with this name already exists.", "backup_archive_name_unknown": "Unknown local backup archive named '{name:s}'", - "backup_archive_open_failed": "Unable to open the backup archive", - "backup_archive_system_part_not_available": "System part '{part:s}' not available in this backup", - "backup_archive_writing_error": "Unable to add files '{source:s}' (named in the archive: '{dest:s}') to backup into the compressed archive '{archive:s}'", - "backup_ask_for_copying_if_needed": "Some files couldn't be prepared to be backuped using the method that avoid to temporarily waste space on the system. To perform the backup, {size:s}MB should be used temporarily. Do you agree?", - "backup_borg_not_implemented": "Borg backup method is not yet implemented", - "backup_cant_mount_uncompress_archive": "Unable to mount in readonly mode the uncompress archive directory", - "backup_cleaning_failed": "Unable to clean-up the temporary backup directory", + "backup_archive_open_failed": "Could not open the backup archive", + "backup_archive_system_part_not_available": "System part '{part:s}' unavailable in this backup", + "backup_archive_writing_error": "Could not add the add files '{source:s}' (named in the archive: '{dest:s}') to be backed up into the compressed archive '{archive:s}'", + "backup_ask_for_copying_if_needed": "Some files could not be prepared for bacup using the method that avoids temporarily wasting space on the system. To perform the backup, {size:s}MB will be temporarily. Do you agree?", + "backup_borg_not_implemented": "The Borg backup method is not yet implemented", + "backup_cant_mount_uncompress_archive": "Could not mount the uncompressed archive write protected", + "backup_cleaning_failed": "Could not clean-up the temporary backup folder", "backup_copying_to_organize_the_archive": "Copying {size:s}MB to organize the archive", - "backup_couldnt_bind": "Couldn't bind {src:s} to {dest:s}.", + "backup_couldnt_bind": "Could not bind {src:s} to {dest:s}.", "backup_created": "Backup created", "backup_creating_archive": "Creating the backup archive…", - "backup_creation_failed": "Backup creation failed", - "backup_csv_addition_failed": "Unable to add files to backup into the CSV file", - "backup_csv_creation_failed": "Unable to create the CSV file needed for future restore operations", - "backup_custom_backup_error": "Custom backup method failure on 'backup' step", - "backup_custom_mount_error": "Custom backup method failure on 'mount' step", - "backup_custom_need_mount_error": "Custom backup method failure on 'need_mount' step", - "backup_delete_error": "Unable to delete '{path:s}'", - "backup_deleted": "The backup has been deleted", - "backup_extracting_archive": "Extracting the backup archive…", - "backup_hook_unknown": "Backup hook '{hook:s}' unknown", - "backup_invalid_archive": "Invalid backup archive", - "backup_method_borg_finished": "Backup into borg finished", - "backup_method_copy_finished": "Backup copy finished", + "backup_creation_failed": "Could not create the backup creation", + "backup_csv_addition_failed": "Could not add files to backup into the CSV file", + "backup_csv_creation_failed": "Could not create the CSV file needed for restoration", + "backup_custom_backup_error": "Custom backup method could not get past the 'backup' step", + "backup_custom_mount_error": "Custom backup method could not get past the 'mount' step", + "backup_custom_need_mount_error": "Custom backup method could not get past 'need_mount' step", + "backup_delete_error": "Could not delete '{path:s}'", + "backup_deleted": "Backup deleted", + "backup_extracting_archive": "Extracting backup archive…", + "backup_hook_unknown": "The backup hook '{hook:s}' is unknown", + "backup_invalid_archive": "This is not a backup archive", + "backup_method_borg_finished": "Backup into Borg finished", + "backup_method_copy_finished": "Backup copy finalized", "backup_method_custom_finished": "Custom backup method '{method:s}' finished", - "backup_method_tar_finished": "Backup tar archive created", + "backup_method_tar_finished": "TAR backup archive created", "backup_mount_archive_for_restore": "Preparing archive for restoration…", - "backup_no_uncompress_archive_dir": "Uncompress archive directory doesn't exist", - "backup_nothings_done": "There is nothing to save", - "backup_output_directory_forbidden": "Forbidden output directory. Backups can't be created in /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var or /home/yunohost.backup/archives sub-folders", - "backup_output_directory_not_empty": "The output directory is not empty", + "backup_no_uncompress_archive_dir": "There is no such uncompressed archive directory", + "backup_nothings_done": "Nothing to save", + "backup_output_directory_forbidden": "Pick a different output directory. Backups can not be created in /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var or /home/yunohost.backup/archives sub-folders", + "backup_output_directory_not_empty": "You should pick an empty output directory", "backup_output_directory_required": "You must provide an output directory for the backup", - "backup_output_symlink_dir_broken": "You have a broken symlink instead of your archives directory '{path:s}'. You may have a specific setup to backup your data on an other filesystem, in this case you probably forgot to remount or plug your hard dirve or usb key.", + "backup_output_symlink_dir_broken": "You have a broken symlink in place of your archive directory '{path:s}'. You may have a specific setup to backup your data on another filesystem, in this case you probably forgot to remount or plug in your hard-drive or USB key.", "backup_permission": "Backup permission for app {app:s}", - "backup_php5_to_php7_migration_may_fail": "Could not convert your archive to support php7, your php apps may fail to restore (reason: {error:s})", + "backup_php5_to_php7_migration_may_fail": "Could not convert your archive to support PHP 7, you may be unable to restore your PHP apps (reason: {error:s})", "backup_running_hooks": "Running backup hooks…", - "backup_system_part_failed": "Unable to backup the '{part:s}' system part", - "backup_unable_to_organize_files": "Unable to organize files in the archive with the quick method", - "backup_with_no_backup_script_for_app": "App {app:s} has no backup script. Ignoring.", - "backup_with_no_restore_script_for_app": "App {app:s} has no restore script, you won't be able to automatically restore the backup of this app.", - "certmanager_acme_not_configured_for_domain": "Certificate for domain {domain:s} does not appear to be correctly installed. Please run cert-install for this domain first.", - "certmanager_attempt_to_renew_nonLE_cert": "The certificate for domain {domain:s} is not issued by Let's Encrypt. Cannot renew it automatically!", - "certmanager_attempt_to_renew_valid_cert": "The certificate for domain {domain:s} is not about to expire! (You may use --force if you know what you're doing)", + "backup_system_part_failed": "Could not backup the '{part:s}' system part", + "backup_unable_to_organize_files": "Could not use the quick method to organize files in the archive", + "backup_with_no_backup_script_for_app": "The app '{app:s}' has no backup script. Ignoring.", + "backup_with_no_restore_script_for_app": "The '{app:s}' has no restoration script, you will not be able to automatically restore the backup of this app.", + "certmanager_acme_not_configured_for_domain": "Certificate for the domain '{domain:s}' does not appear to be correctly installed. Please run 'cert-install' for this domain first.", + "certmanager_attempt_to_renew_nonLE_cert": "The certificate for the domain '{domain:s}' is not issued by Let's Encrypt. Cannot renew it automatically!", + "certmanager_attempt_to_renew_valid_cert": "The certificate for the domain '{domain:s}' is not about to expire! (You may use --force if you know what you're doing)", "certmanager_attempt_to_replace_valid_cert": "You are attempting to overwrite a good and valid certificate for domain {domain:s}! (Use --force to bypass)", "certmanager_cannot_read_cert": "Something wrong happened when trying to open current certificate for domain {domain:s} (file: {file:s}), reason: {reason:s}", - "certmanager_cert_install_success": "Successfully installed Let's Encrypt certificate for domain {domain:s}!", - "certmanager_cert_install_success_selfsigned": "Successfully installed a self-signed certificate for domain {domain:s}!", - "certmanager_cert_renew_success": "Successfully renewed Let's Encrypt certificate for domain {domain:s}!", - "certmanager_cert_signing_failed": "Signing the new certificate failed", - "certmanager_certificate_fetching_or_enabling_failed": "Sounds like enabling the new certificate for {domain:s} failed somehow…", - "certmanager_conflicting_nginx_file": "Unable to prepare domain for ACME challenge: the nginx configuration file {filepath:s} is conflicting and should be removed first", - "certmanager_couldnt_fetch_intermediate_cert": "Timed out when trying to fetch intermediate certificate from Let's Encrypt. Certificate installation/renewal aborted - please try again later.", - "certmanager_domain_cert_not_selfsigned": "The certificate for domain {domain:s} is not self-signed. Are you sure you want to replace it? (Use --force)", - "certmanager_domain_dns_ip_differs_from_public_ip": "The DNS 'A' record for domain {domain:s} is different from this server 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 disable those checks.)", - "certmanager_domain_http_not_working": "It seems that the domain {domain:s} cannot be accessed through HTTP. Please check your DNS and nginx configuration is okay", - "certmanager_domain_not_resolved_locally": "The domain {domain:s} cannot be resolved from inside your Yunohost server. This might happen if you recently modified your DNS record. If so, please wait a few hours for it to propagate. If the issue persists, consider adding {domain:s} to /etc/hosts. (If you know what you are doing, use --no-checks to disable 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 disable those checks.)", - "certmanager_hit_rate_limit": "Too many certificates already issued for 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 public IP address (domain {domain:s} with ip {ip:s}). You may be experiencing hairpinning issue or the firewall/router ahead of your server is misconfigured.", - "certmanager_no_cert_file": "Unable to read certificate file for domain {domain:s} (file: {file:s})", - "certmanager_self_ca_conf_file_not_found": "Configuration file not found for self-signing authority (file: {file:s})", - "certmanager_unable_to_parse_self_CA_name": "Unable to parse name of self-signing authority (file: {file:s})", - "confirm_app_install_warning": "Warning: this application may work but is not well-integrated in YunoHost. Some features such as single sign-on and backup/restore might not be available. Install anyway? [{answers:s}] ", + "certmanager_cert_install_success": "Let's Encrypt certificate now installed for the domain '{domain:s}'", + "certmanager_cert_install_success_selfsigned": "Self-signed certificate now installed for the domain '{domain:s}'", + "certmanager_cert_renew_success": "Let's Encrypt certificate renewed for the domain '{domain:s}'", + "certmanager_cert_signing_failed": "Could not sign the new certificate", + "certmanager_certificate_fetching_or_enabling_failed": "It seems actually using the new certificate for {domain:s} did not work…", + "certmanager_conflicting_nginx_file": "Could not prepare domain for ACME challenge: the NGINX configuration file {filepath:s} is conflicting and should be removed first", + "certmanager_couldnt_fetch_intermediate_cert": "Timed out when trying to fetch intermediate certificate from Let's Encrypt. Certificate installation/renewal aborted—please try again later.", + "certmanager_domain_cert_not_selfsigned": "The certificate for domain {domain:s} is not self-signed. Are you sure you want to replace it? (Use '--force' 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 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_not_resolved_locally": "The domain {domain:s} cannot be resolved from inside your YunoHost server. This might happen if you recently modified your DNS record. If so, please wait a few hours for it to propagate. If the issue persists, consider adding {domain:s} to /etc/hosts. (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_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})", + "confirm_app_install_warning": "Warning: This application may work, but is not well-integrated in YunoHost. Some features such as single sign-on and backup/restore might not be available. Install anyway? [{answers:s}] ", "confirm_app_install_danger": "WARNING! This application is still experimental (if not explicitly not working) and it is likely to break your system! You should probably NOT install it unless you know what you are doing. Are you willing to take that risk? [{answers:s}] ", - "confirm_app_install_thirdparty": "WARNING! Installing 3rd party applications may compromise the integrity and security of your system. You should probably NOT install it unless you know what you are doing. Are you willing to take that risk? [{answers:s}] ", + "confirm_app_install_thirdparty": "WARNING! Installing third-party applications may compromise the integrity and security of your system. You should probably NOT install it unless you know what you are doing. Are you willing to take that risk? [{answers:s}] ", "custom_app_url_required": "You must provide a URL to upgrade your custom app {app:s}", "custom_appslist_name_required": "You must provide a name for your custom app list", - "diagnosis_debian_version_error": "Can't retrieve the Debian version: {error}", - "diagnosis_kernel_version_error": "Can't retrieve kernel version: {error}", - "diagnosis_monitor_disk_error": "Can't monitor disks: {error}", - "diagnosis_monitor_network_error": "Can't monitor network: {error}", - "diagnosis_monitor_system_error": "Can't monitor system: {error}", + "diagnosis_debian_version_error": "Could not retrieve the Debian version: {error}", + "diagnosis_kernel_version_error": "Could not retrieve kernel version: {error}", + "diagnosis_monitor_disk_error": "Could not monitor disks: {error}", + "diagnosis_monitor_network_error": "Could not monitor network: {error}", + "diagnosis_monitor_system_error": "Could not monitor system: {error}", "diagnosis_no_apps": "No installed application", - "dpkg_is_broken": "You cannot do this right now because dpkg/apt (the system package managers) seems to be in a broken state... You can try to solve this issue by connecting through SSH and running `sudo dpkg --configure -a`.", + "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_lock_not_available": "This command can't be ran right now because another program seems to be using the lock of dpkg (the system package manager)", - "dnsmasq_isnt_installed": "dnsmasq does not seem to be installed, please run 'apt-get remove bind9 && apt-get install dnsmasq'", - "domain_cannot_remove_main": "Cannot remove main domain. Set a new main domain first", - "domain_cert_gen_failed": "Unable to generate certificate", - "domain_created": "The domain has been created", - "domain_creation_failed": "Unable to create domain", - "domain_deleted": "The domain has been deleted", - "domain_deletion_failed": "Unable to delete domain", - "domain_dns_conf_is_just_a_recommendation": "This command shows you what is the *recommended* configuration. It does not actually set up the DNS configuration for you. It is your responsability to configure your DNS zone in your registrar according to this recommendation.", - "domain_dyndns_already_subscribed": "You've already subscribed to a DynDNS domain", - "domain_dyndns_dynette_is_unreachable": "Unable to reach YunoHost dynette, either your YunoHost is not correctly connected to the internet or the dynette server is down. Error: {error}", - "domain_dyndns_invalid": "Invalid domain to use with DynDNS", + "dnsmasq_isnt_installed": "Dnsmasq does not seem to be installed, please run 'apt-get remove bind9 && apt-get install it'", + "domain_cannot_remove_main": "Cannot remove main domain. Set one first", + "domain_cert_gen_failed": "Could not generate certificate", + "domain_created": "Domain created", + "domain_creation_failed": "Could not create domain", + "domain_deleted": "Domain deleted", + "domain_deletion_failed": "Could not delete domain", + "domain_dns_conf_is_just_a_recommendation": "This command shows you the *recommended* configuration. It does not actually set up the DNS configuration for you. It is your responsability to configure your DNS zone in your registrar according to this recommendation.", + "domain_dyndns_already_subscribed": "You have already subscribed to a DynDNS domain", + "domain_dyndns_dynette_is_unreachable": "Could not reach YunoHost dynette, either your YunoHost is not correctly connected to the Internet, or the dynette server is down. Error: {error}", + "domain_dyndns_invalid": "This domain can not be used with DynDNS", "domain_dyndns_root_unknown": "Unknown DynDNS root domain", - "domain_exists": "Domain already exists", - "domain_hostname_failed": "Failed to set new hostname. This might cause issue later (not sure about it... it might be fine).", + "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_unknown": "Unknown domain", "domain_zone_exists": "DNS zone file already exists", @@ -184,80 +184,80 @@ "downloading": "Downloading…", "dyndns_could_not_check_provide": "Could not check if {provider:s} can provide {domain:s}.", "dyndns_could_not_check_available": "Could not check if {domain:s} is available on {provider:s}.", - "dyndns_cron_installed": "The DynDNS cron job has been installed", - "dyndns_cron_remove_failed": "Unable to remove the DynDNS cron job because: {error}", - "dyndns_cron_removed": "The DynDNS cron job has been removed", - "dyndns_ip_update_failed": "Unable to update IP address on DynDNS", - "dyndns_ip_updated": "Your IP address has been updated on DynDNS", - "dyndns_key_generating": "DNS key is being generated, it may take a while…", + "dyndns_cron_installed": "DynDNS cron job created", + "dyndns_cron_remove_failed": "Could not remove the DynDNS cron job because: {error}", + "dyndns_cron_removed": "DynDNS cron job removed", + "dyndns_ip_update_failed": "Could not update IP address to DynDNS", + "dyndns_ip_updated": "Updated your IP on DynDNS", + "dyndns_key_generating": "Generating DNS key… It may take a while.", "dyndns_key_not_found": "DNS key not found for the domain", - "dyndns_no_domain_registered": "No domain has been registered with DynDNS", - "dyndns_registered": "The DynDNS domain has been registered", - "dyndns_registration_failed": "Unable to register DynDNS domain: {error:s}", - "dyndns_domain_not_provided": "Dyndns provider {provider:s} cannot provide domain {domain:s}.", - "dyndns_unavailable": "Domain {domain:s} is not available.", + "dyndns_no_domain_registered": "No domain registered with DynDNS", + "dyndns_registered": "DynDNS domain registered", + "dyndns_registration_failed": "Could not register DynDNS domain: {error:s}", + "dyndns_domain_not_provided": "DynDNS provider {provider:s} cannot provide domain {domain:s}.", + "dyndns_unavailable": "The domain '{domain:s}' is unavailable.", "edit_group_not_allowed": "You are not allowed to edit the group {group:s}", - "edit_permission_with_group_all_users_not_allowed": "You are not allowed to edit permission for group 'all_users', use 'yunohost user permission clear APP' or 'yunohost user permission add APP -u USER' instead.", - "error_when_removing_sftpuser_group": "Error when trying remove sftpusers group", + "edit_permission_with_group_all_users_not_allowed": "You are not allowed to edit permission for the group 'all_users', use 'yunohost user permission clear APP' or 'yunohost user permission add APP -u USER' instead.", + "error_when_removing_sftpuser_group": "Could not remove the sftpusers group", "executing_command": "Executing command '{command:s}'…", "executing_script": "Executing script '{script:s}'…", "extracting": "Extracting…", - "experimental_feature": "Warning: this feature is experimental and not consider stable, you shouldn't be using it except if you know what you are doing.", + "experimental_feature": "Warning: This feature is experimental and not considered stable, you should not use it unless you know what you are doing.", "field_invalid": "Invalid field '{:s}'", - "file_does_not_exist": "The file {path:s} does not exists.", - "firewall_reload_failed": "Unable to reload the firewall", - "firewall_reloaded": "The firewall has been reloaded", - "firewall_rules_cmd_failed": "Some firewall rules commands have failed. For more information, see the log.", + "file_does_not_exist": "The file {path:s} does not exist.", + "firewall_reload_failed": "Could not reload the firewall", + "firewall_reloaded": "Firewall reloaded", + "firewall_rules_cmd_failed": "Some firewall rules commands have failed. More info in log.", "format_datetime_short": "%m/%d/%Y %I:%M %p", - "global_settings_bad_choice_for_enum": "Bad choice for setting {setting:s}, received '{choice:s}' but available choices are : {available_choices:s}", - "global_settings_bad_type_for_setting": "Bad type for setting {setting:s}, received {received_type:s}, except {expected_type:s}", - "global_settings_cant_open_settings": "Failed to open settings file, reason: {reason:s}", - "global_settings_cant_serialize_settings": "Failed to serialize settings data, reason: {reason:s}", - "global_settings_cant_write_settings": "Failed to write settings file, reason: {reason:s}", - "global_settings_key_doesnt_exists": "The key '{settings_key:s}' doesn't exists in the global settings, you can see all the available keys by doing 'yunohost settings list'", - "global_settings_reset_success": "Success. Your previous settings have been backuped in {path:s}", + "global_settings_bad_choice_for_enum": "Bad choice for setting {setting:s}, received '{choice:s}', but available choices are: {available_choices:s}", + "global_settings_bad_type_for_setting": "Bad type for setting {setting:s}, received {received_type:s}, expected {expected_type:s}", + "global_settings_cant_open_settings": "Could not open settings file, reason: {reason:s}", + "global_settings_cant_serialize_settings": "Could not serialize settings data, reason: {reason:s}", + "global_settings_cant_write_settings": "Could not save settings file, reason: {reason:s}", + "global_settings_key_doesnt_exists": "The key '{settings_key:s}' does not exist in the global settings, you can see all the available keys by running 'yunohost settings list'", + "global_settings_reset_success": "Previous settings now backed up to {path:s}", "global_settings_setting_example_bool": "Example boolean option", "global_settings_setting_example_enum": "Example enum option", "global_settings_setting_example_int": "Example int option", "global_settings_setting_example_string": "Example string option", - "global_settings_setting_security_nginx_compatibility": "Compatibility vs. security tradeoff for the web server nginx. Affects the ciphers (and other security-related aspects)", + "global_settings_setting_security_nginx_compatibility": "Compatibility vs. security tradeoff for the web server NGINX. Affects the ciphers (and other security-related aspects)", "global_settings_setting_security_password_admin_strength": "Admin password strength", "global_settings_setting_security_password_user_strength": "User password strength", "global_settings_setting_security_ssh_compatibility": "Compatibility vs. security tradeoff for the SSH server. Affects the ciphers (and other security-related aspects)", "global_settings_setting_security_postfix_compatibility": "Compatibility vs. security tradeoff for the Postfix server. Affects the ciphers (and other security-related aspects)", - "global_settings_unknown_setting_from_settings_file": "Unknown key in settings: '{setting_key:s}', discarding it and save it in /etc/yunohost/settings-unknown.json", + "global_settings_unknown_setting_from_settings_file": "Unknown key in settings: '{setting_key:s}', discard it and save it in /etc/yunohost/settings-unknown.json", "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Allow the use of (deprecated) DSA hostkey for the SSH daemon configuration", - "global_settings_unknown_type": "Unexpected situation, the setting {setting:s} appears to have the type {unknown_type:s} but it's not a type supported by the system.", - "good_practices_about_admin_password": "You are now about to define a new administration password. The password should be at least 8 characters - though it is good practice to use longer password (i.e. a passphrase) and/or to use various kind of characters (uppercase, lowercase, digits and special characters).", - "good_practices_about_user_password": "You are now about to define a new user password. The password should be at least 8 characters - though it is good practice to use longer password (i.e. a passphrase) and/or to use various kind of characters (uppercase, lowercase, digits and special characters).", - "group_already_allowed": "Group '{group:s}' already has permission '{permission:s}' enabled for app '{app:s}'", - "group_already_disallowed": "Group '{group:s}' already has permissions '{permission:s}' disabled for app '{app:s}'", + "global_settings_unknown_type": "Unexpected situation, the setting {setting:s} appears to have the type {unknown_type:s} but it is not a type supported by the system.", + "good_practices_about_admin_password": "You are now about to define a new administration password. The password should be at-least 8 characters—though it is good practice to use a longer password (i.e. a passphrase) and/or to use a variation of characters (uppercase, lowercase, digits and special characters).", + "good_practices_about_user_password": "You are now about to define a new user password. The password should be at least 8 characters—though it is good practice to use longer password (i.e. a passphrase) and/or to a variation of characters (uppercase, lowercase, digits and special characters).", + "group_already_allowed": "Group '{group:s}' already has permission '{permission:s}' turned on for the app '{app:s}'", + "group_already_disallowed": "Group '{group:s}' already has permissions '{permission:s}' turned off for the app '{app:s}'", "group_name_already_exist": "Group {name:s} already exist", - "group_created": "Group '{group}' successfully created", - "group_creation_failed": "Group creation failed for group '{group}'", + "group_created": "Group '{group}' created", + "group_creation_failed": "Could not create the group '{group}'", "group_deleted": "Group '{group}' deleted", - "group_deletion_failed": "Group '{group} 'deletion failed", + "group_deletion_failed": "Could not delete the group '{group}'", "group_deletion_not_allowed": "The group {group:s} cannot be deleted manually.", - "group_info_failed": "Group info failed", - "group_unknown": "Group {group:s} unknown", + "group_info_failed": "Could not present group info", + "group_unknown": "The group '{group:s}' is unknown", "group_updated": "Group '{group}' updated", - "group_update_failed": "Group update failed for group '{group}'", - "hook_exec_failed": "Script execution failed: {path:s}", - "hook_exec_not_terminated": "Script execution did not finish properly: {path:s}", - "hook_json_return_error": "Failed to read return from hook {path:s}. Error: {msg:s}. Raw content: {raw_content}", - "hook_list_by_invalid": "Invalid property to list hook by", + "group_update_failed": "Could not update the group '{group}'", + "hook_exec_failed": "Could not run script: {path:s}", + "hook_exec_not_terminated": "Script did not finish properly: {path:s}", + "hook_json_return_error": "Could not read return from hook {path:s}. Error: {msg:s}. Raw content: {raw_content}", + "hook_list_by_invalid": "This property can not be used to list hooks", "hook_name_unknown": "Unknown hook name '{name:s}'", "installation_complete": "Installation complete", - "installation_failed": "Installation failed", - "invalid_url_format": "Invalid URL format", + "installation_failed": "Something went wrong with the installation", + "invalid_url_format": "Something is wrong with the URL", "ip6tables_unavailable": "You cannot play with ip6tables here. You are either in a container or your kernel does not support it", "iptables_unavailable": "You cannot play with iptables here. You are either in a container or your kernel does not support it", - "log_corrupted_md_file": "The yaml metadata file associated with logs is corrupted: '{md_file}\nError: {error}'", + "log_corrupted_md_file": "The YAML metadata file associated with logs is damaged: '{md_file}\nError: {error}'", "log_category_404": "The log category '{category}' does not exist", "log_link_to_log": "Full log of this operation: '{desc}'", "log_help_to_get_log": "To view the log of the operation '{desc}', use the command 'yunohost log display {name}'", - "log_link_to_failed_log": "The operation '{desc}' has failed! To get help, please provide the full log of this operation by clicking here", - "log_help_to_get_failed_log": "The operation '{desc}' has failed! To get help, please share the full log of this operation using the command 'yunohost log display {name} --share'", + "log_link_to_failed_log": "Could not complete the operation '{desc}'. Please provide the full log of this operation by clicking here to get help", + "log_help_to_get_failed_log": "The operation '{desc}' could not be completed. Please share the full log of this operation using the command 'yunohost log display {name} --share' to get help", "log_does_exists": "There is not operation log with the name '{log}', use 'yunohost log list to see all available operation logs'", "log_operation_unit_unclosed_properly": "Operation unit has not been closed properly", "log_app_addaccess": "Add access to '{}'", @@ -265,11 +265,11 @@ "log_app_clearaccess": "Remove all access to '{}'", "log_app_fetchlist": "Add an application list", "log_app_removelist": "Remove an application list", - "log_app_change_url": "Change the url of '{}' application", - "log_app_install": "Install '{}' application", - "log_app_remove": "Remove '{}' application", - "log_app_upgrade": "Upgrade '{}' application", - "log_app_makedefault": "Make '{}' as default application", + "log_app_change_url": "Change the URL of '{}' application", + "log_app_install": "Install the '{}' application", + "log_app_remove": "Remove the '{}' application", + "log_app_upgrade": "Upgrade the '{}' application", + "log_app_makedefault": "Make '{}' the default application", "log_available_on_yunopaste": "This log is now available via {url}", "log_backup_restore_system": "Restore system from a backup archive", "log_backup_restore_app": "Restore '{}' from a backup archive", @@ -278,9 +278,9 @@ "log_domain_add": "Add '{}' domain into system configuration", "log_domain_remove": "Remove '{}' domain from system configuration", "log_dyndns_subscribe": "Subscribe to a YunoHost subdomain '{}'", - "log_dyndns_update": "Update the ip associated with your YunoHost subdomain '{}'", - "log_letsencrypt_cert_install": "Install Let's encrypt certificate on '{}' domain", - "log_permission_add": "Add permission '{}' for app '{}'", + "log_dyndns_update": "Update the IP associated with your YunoHost subdomain '{}'", + "log_letsencrypt_cert_install": "Install a Let's encrypt certificate on '{}' domain", + "log_permission_add": "Add the '{}' permission for the app '{}'", "log_permission_remove": "Remove permission '{}'", "log_permission_update": "Update permission '{}' for app '{}'", "log_selfsigned_cert_install": "Install self signed certificate on '{}' domain", @@ -291,104 +291,104 @@ "log_user_group_add": "Add '{}' group", "log_user_group_delete": "Delete '{}' group", "log_user_group_update": "Update '{}' group", - "log_user_update": "Update information of '{}' user", + "log_user_update": "Update info of '{}' user", "log_user_permission_add": "Update '{}' permission", "log_user_permission_remove": "Update '{}' permission", - "log_tools_maindomain": "Make '{}' as main domain", + "log_tools_maindomain": "Make '{}' the main domain", "log_tools_migrations_migrate_forward": "Migrate forward", "log_tools_postinstall": "Postinstall your YunoHost server", "log_tools_upgrade": "Upgrade system packages", "log_tools_shutdown": "Shutdown your server", "log_tools_reboot": "Reboot your server", - "ldap_init_failed_to_create_admin": "LDAP initialization failed to create admin user", - "ldap_initialized": "LDAP has been initialized", + "ldap_init_failed_to_create_admin": "LDAP initialization could not create admin user", + "ldap_initialized": "LDAP initialized", "license_undefined": "undefined", - "mail_alias_remove_failed": "Unable to remove mail alias '{mail:s}'", - "mail_domain_unknown": "Unknown mail address domain '{domain:s}'", - "mail_forward_remove_failed": "Unable to remove mail forward '{mail:s}'", - "mailbox_disabled": "Mailbox disabled for user {user:s}", - "mailbox_used_space_dovecot_down": "Dovecot mailbox service need to be up, if you want to get mailbox used space", - "mail_unavailable": "This email address is reserved and shall be automatically allocated to the very first user", - "maindomain_change_failed": "Unable to change the main domain", - "maindomain_changed": "The main domain has been changed", - "migrate_tsig_end": "Migration to hmac-sha512 finished", - "migrate_tsig_failed": "Migrating the dyndns domain {domain} to hmac-sha512 failed, rolling back. Error: {error_code} - {error}", - "migrate_tsig_start": "Not secure enough key algorithm detected for TSIG signature of domain '{domain}', initiating migration to the more secure one hmac-sha512", - "migrate_tsig_wait": "Let's wait 3min for the dyndns server to take the new key into account…", + "mail_alias_remove_failed": "Could not remove e-mail alias '{mail:s}'", + "mail_domain_unknown": "Unknown e-mail address for domain '{domain:s}'", + "mail_forward_remove_failed": "Could not remove e-mail forwarding '{mail:s}'", + "mailbox_disabled": "E-mail turned off for user {user:s}", + "mailbox_used_space_dovecot_down": "the Dovecot mailbox service needs to be up, if you want to fetch used mailbox space", + "mail_unavailable": "This e-mail address is reserved and shall be automatically allocated to the very first user", + "maindomain_change_failed": "Could not change the main domain", + "maindomain_changed": "The main domain now changed", + "migrate_tsig_end": "Migration to HMAC-SHA-512 finished", + "migrate_tsig_failed": "Could not migrate the DynDNS domain '{domain}' to HMAC-SHA-512, rolling back. Error: {error_code}, {error}", + "migrate_tsig_start": "Insufficiently secure key algorithm detected for TSIG signature of the domain '{domain}', initiating migration to the more secure HMAC-SHA-512", + "migrate_tsig_wait": "Waiting three minutes for the DynDNS server to take the new key into account…", "migrate_tsig_wait_2": "2min…", "migrate_tsig_wait_3": "1min…", - "migrate_tsig_wait_4": "30 secondes…", - "migrate_tsig_not_needed": "You do not appear to use a dyndns domain, so no migration is needed!", + "migrate_tsig_wait_4": "30 seconds…", + "migrate_tsig_not_needed": "You do not appear to use a DynDNS domain, so no migration is needed.", "migration_description_0001_change_cert_group_to_sslcert": "Change certificates group permissions from 'metronome' to 'ssl-cert'", - "migration_description_0002_migrate_to_tsig_sha256": "Improve security of dyndns TSIG by using SHA512 instead of MD5", + "migration_description_0002_migrate_to_tsig_sha256": "Improve security of DynDNS TSIG updates by using SHA-512 instead of MD5", "migration_description_0003_migrate_to_stretch": "Upgrade the system to Debian Stretch and YunoHost 3.0", "migration_description_0004_php5_to_php7_pools": "Reconfigure the PHP pools to use PHP 7 instead of 5", - "migration_description_0005_postgresql_9p4_to_9p6": "Migrate databases from postgresql 9.4 to 9.6", + "migration_description_0005_postgresql_9p4_to_9p6": "Migrate databases from PostgreSQL 9.4 to 9.6", "migration_description_0006_sync_admin_and_root_passwords": "Synchronize admin and root passwords", "migration_description_0007_ssh_conf_managed_by_yunohost_step1": "Let the SSH configuration be managed by YunoHost (step 1, automatic)", "migration_description_0008_ssh_conf_managed_by_yunohost_step2": "Let the SSH configuration be managed by YunoHost (step 2, manual)", "migration_description_0009_decouple_regenconf_from_services": "Decouple the regen-conf mechanism from services", - "migration_description_0010_migrate_to_apps_json": "Remove deprecated appslists and use the new unified 'apps.json' list instead", - "migration_description_0011_setup_group_permission": "Setup user group and setup permission for apps and services", - "migration_description_0012_postgresql_password_to_md5_authentication": "Force postgresql authentication to use md5 for local connections", + "migration_description_0010_migrate_to_apps_json": "Remove deprecated applists and use the new unified 'apps.json' list instead", + "migration_description_0011_setup_group_permission": "Set up user group and set up permission for apps and services", + "migration_description_0012_postgresql_password_to_md5_authentication": "Force PostgreSQL authentication to use MD5 for local connections", "migration_0003_start": "Starting migration to Stretch. The logs will be available in {logfile}.", "migration_0003_patching_sources_list": "Patching the sources.lists…", "migration_0003_main_upgrade": "Starting main upgrade…", - "migration_0003_fail2ban_upgrade": "Starting the fail2ban upgrade…", - "migration_0003_restoring_origin_nginx_conf": "Your file /etc/nginx/nginx.conf was edited somehow. The migration is going to reset back to its original state first… The previous file will be available as {backup_dest}.", - "migration_0003_yunohost_upgrade": "Starting the yunohost package upgrade… The migration will end, but the actual upgrade will happen right after. After the operation is complete, you might have to re-log on the webadmin.", - "migration_0003_not_jessie": "The current debian distribution is not Jessie!", - "migration_0003_system_not_fully_up_to_date": "Your system is not fully up to date. Please perform a regular upgrade before running the migration to stretch.", - "migration_0003_still_on_jessie_after_main_upgrade": "Something wrong happened during the main upgrade: system is still on Jessie!? To investigate the issue, please look at {log}:s…", - "migration_0003_general_warning": "Please note that this migration is a delicate operation. While the YunoHost team did its best to review and test it, the migration might still break parts of the system or apps.\n\nTherefore, we recommend you to:\n - Perform a backup of any critical data or app. More infos on https://yunohost.org/backup;\n - Be patient after launching the migration: depending on your internet connection and hardware, it might take up to a few hours for everything to upgrade.\n\nAdditionally, the port for SMTP, used by external email clients (like Thunderbird or K9-Mail) was changed from 465 (SSL/TLS) to 587 (STARTTLS). The old port 465 will automatically be closed and the new port 587 will be opened in the firewall. You and your users *will* have to adapt the configuration of your email clients accordingly!", - "migration_0003_problematic_apps_warning": "Please note that the following possibly problematic installed apps were detected. It looks like those were not installed from an applist or are not flagged as 'working'. Consequently, we cannot guarantee that they will still work after the upgrade: {problematic_apps}", - "migration_0003_modified_files": "Please note that the following files were found to be manually modified and might be overwritten at the end of the upgrade: {manually_modified_files}", - "migration_0005_postgresql_94_not_installed": "Postgresql was not installed on your system. Nothing to do!", - "migration_0005_postgresql_96_not_installed": "Postgresql 9.4 has been found to be installed, but not postgresql 9.6!? Something weird might have happened on your system:(…", - "migration_0005_not_enough_space": "Not enough space is available in {path} to run the migration right now:(.", + "migration_0003_fail2ban_upgrade": "Starting the Fail2Ban upgrade…", + "migration_0003_restoring_origin_nginx_conf": "Your file /etc/nginx/nginx.conf was edited somehow. The migration is going to reset to its original state first… The previous file will be available as {backup_dest}.", + "migration_0003_yunohost_upgrade": "Starting the YunoHost package upgrade… The migration will end, but the actual upgrade will happen immediately afterwards. After the operation is complete, you might have to log in on the webadmin page again.", + "migration_0003_not_jessie": "The current Debian distribution is not Jessie!", + "migration_0003_system_not_fully_up_to_date": "Your system is not fully up-to-date. Please perform a regular upgrade before running the migration to Stretch.", + "migration_0003_still_on_jessie_after_main_upgrade": "Something went wrong during the main upgrade: Is the system still on Jessie‽ To investigate the issue, please look at {log}:s…", + "migration_0003_general_warning": "Please note that this migration is a delicate operation. The YunoHost team did its best to review and test it, but the migration might still break parts of the system or its apps.\n\nTherefore, it is recommended to:\n - Perform a backup of any critical data or app. More info on https://yunohost.org/backup;\n - Be patient after launching the migration: Depending on your Internet connection and hardware, it might take up to a few hours for everything to upgrade.\n\nAdditionally, the port for SMTP, used by external e-mail clients (like Thunderbird or K9-Mail) was changed from 465 (SSL/TLS) to 587 (STARTTLS). The old port (465) will automatically be closed, and the new port (587) will be opened in the firewall. You and your users *will* have to adapt the configuration of your e-mail clients accordingly.", + "migration_0003_problematic_apps_warning": "Please note that the following possibly problematic installed apps were detected. It looks like those were not installed from an applist, or are not flagged as 'working'. Consequently, it cannot be guaranteed that they will still work after the upgrade: {problematic_apps}", + "migration_0003_modified_files": "Please note that the following files were found to be manually modified and might be overwritten following the upgrade: {manually_modified_files}", + "migration_0005_postgresql_94_not_installed": "PostgreSQL was not installed on your system. Nothing to do.", + "migration_0005_postgresql_96_not_installed": "PostgreSQL 9.4 is installed, but not postgresql 9.6‽ Something weird might have happened on your system:(…", + "migration_0005_not_enough_space": "Make sufficient space available in {path} to run the migration.", "migration_0006_disclaimer": "YunoHost now expects admin and root passwords to be synchronized. By running this migration, your root password is going to be replaced by the admin password.", "migration_0007_cancelled": "YunoHost has failed to improve the way your SSH conf is managed.", "migration_0007_cannot_restart": "SSH can't be restarted after trying to cancel migration number 6.", - "migration_0008_general_disclaimer": "To improve the security of your server, it is recommended to let YunoHost manage the SSH configuration. Your current SSH configuration differs from the recommended configuration. If you let YunoHost reconfigure it, the way you connect to your server through SSH will change in the following way:", - "migration_0008_port": " - you will have to connect using port 22 instead of your current custom SSH port. Feel free to reconfigure it;", - "migration_0008_root": " - you will not be able to connect as root through SSH. Instead you should use the admin user;", - "migration_0008_dsa": " - the DSA key will be disabled. Hence, you might need to invalidate a spooky warning from your SSH client, and recheck the fingerprint of your server;", + "migration_0008_general_disclaimer": "To improve the security of your server, it is recommended to let YunoHost manage the SSH configuration. Your current SSH setup differs from the recommendation. If you let YunoHost reconfigure it, the way you connect to your server through SSH will change thusly:", + "migration_0008_port": "• You will have to connect using port 22 instead of your current custom SSH port. Feel free to reconfigure it;", + "migration_0008_root": "• You will not be able to connect as root through SSH. Instead you should use the admin user;", + "migration_0008_dsa": "• The DSA key will be turned off. Hence, you might need to invalidate a spooky warning from your SSH client, and recheck the fingerprint of your server;", "migration_0008_warning": "If you understand those warnings and agree to let YunoHost override your current configuration, run the migration. Otherwise, you can also skip the migration - though it is not recommended.", - "migration_0008_no_warning": "No major risk has been indentified about overriding your SSH configuration - but we can't be absolutely sure ;)! If you agree to let YunoHost override your current configuration, run the migration. Otherwise, you can also skip the migration - though it is not recommended.", - "migration_0009_not_needed": "This migration already happened somehow ? Skipping.", + "migration_0008_no_warning": "No major risk indentified concerning overriding your SSH configuration—one can however not be absolutely sure ;)! Run the migration to override it. Otherwise, you can also skip the migration - though it is not recommended.", + "migration_0009_not_needed": "This migration already happened somehow… (?) Skipping.", "migration_0011_backup_before_migration": "Creating a backup of LDAP database and apps settings prior to the actual migration.", - "migration_0011_can_not_backup_before_migration": "The backup of the system before the migration failed. Migration failed. Error: {error:s}", - "migration_0011_create_group": "Creating a group for each user...", - "migration_0011_done": "Migration successful. You are now able to manage groups of users.", - "migration_0011_LDAP_config_dirty": "It look like that you customized your LDAP configuration. For this migration the LDAP configuration need to be updated.\nYou need to save your actual configuration, reintialize the original configuration by the command 'yunohost tools regen-conf -f' and after retry the migration", - "migration_0011_LDAP_update_failed": "LDAP update failed. Error: {error:s}", - "migration_0011_migrate_permission": "Migrating permissions from apps settings to LDAP...", - "migration_0011_migration_failed_trying_to_rollback": "Migration failed ... trying to rollback the system.", - "migration_0011_rollback_success": "Rollback succeeded.", - "migration_0011_update_LDAP_database": "Updating LDAP database...", - "migration_0011_update_LDAP_schema": "Updating LDAP schema...", - "migrations_already_ran": "Those migrations have already been ran: {ids}", - "migrations_cant_reach_migration_file": "Can't access migrations files at path %s", - "migrations_dependencies_not_satisfied": "Can't run migration {id} because first you need to run these migrations: {dependencies_id}", - "migrations_failed_to_load_migration": "Failed to load migration {id} : {error}", - "migrations_exclusive_options": "--auto, --skip, and --force-rerun are exclusive options.", - "migrations_list_conflict_pending_done": "You cannot use both --previous and --done at the same time.", + "migration_0011_can_not_backup_before_migration": "Could not back up the system prior to migration. Error: {error:s}", + "migration_0011_create_group": "Creating a group for each user…", + "migration_0011_done": "Migration successful. You are now able to manage usergroups.", + "migration_0011_LDAP_config_dirty": "It look like that you customized your LDAP configuration. For this migration the LDAP configuration needs to be updated.\nYou need to save your actual configuration, reintialize the original configuration by running 'yunohost tools regen-conf -f' and retry the migration", + "migration_0011_LDAP_update_failed": "Could not update LDAP. Error: {error:s}", + "migration_0011_migrate_permission": "Migrating permissions from apps settings to LDAP…", + "migration_0011_migration_failed_trying_to_rollback": "Migration failed… trying to roll back the system.", + "migration_0011_rollback_success": "System rolled back.", + "migration_0011_update_LDAP_database": "Updating LDAP database…", + "migration_0011_update_LDAP_schema": "Updating LDAP schema…", + "migrations_already_ran": "Those migrations are already done: {ids}", + "migrations_cant_reach_migration_file": "Could not access migrations files at path %s", + "migrations_dependencies_not_satisfied": "Cannot run migration {id} because first you need to run these migrations: {dependencies_id}", + "migrations_failed_to_load_migration": "Could not load migration {id}: {error}", + "migrations_exclusive_options": "'--auto', '--skip', and '--force-rerun' are mutually exclusive options.", + "migrations_list_conflict_pending_done": "You cannot use both '--previous' and '--done' at the same time.", "migrations_loading_migration": "Loading migration {id}…", - "migrations_migration_has_failed": "Migration {id} has failed, aborting. Error: {exception}", - "migrations_must_provide_explicit_targets": "You must provide explicit targets when using --skip or --force-rerun", - "migrations_need_to_accept_disclaimer": "To run the migration {id}, your must accept the following disclaimer:\n---\n{disclaimer}\n---\nIf you accept to run the migration, please re-run the command with the option --accept-disclaimer.", + "migrations_migration_has_failed": "Migration {id} did not complete, aborting. Error: {exception}", + "migrations_must_provide_explicit_targets": "You must provide explicit targets when using '--skip' or '--force-rerun'", + "migrations_need_to_accept_disclaimer": "To run the migration {id}, your must accept the following disclaimer:\n---\n{disclaimer}\n---\nIf you accept to run the migration, please re-run the command with the option '--accept-disclaimer'.", "migrations_no_migrations_to_run": "No migrations to run", - "migrations_no_such_migration": "No such migration called {id}", - "migrations_not_pending_cant_skip": "Those migrations are not pending so cannot be skipped: {ids}", - "migrations_pending_cant_rerun": "Those migrations are still pending so cannot be reran: {ids}", + "migrations_no_such_migration": "There is no migration called {id}", + "migrations_not_pending_cant_skip": "Those migrations are not pending, so cannot be skipped: {ids}", + "migrations_pending_cant_rerun": "Those migrations are still pending, so cannot be run again: {ids}", "migrations_running_forward": "Running migration {id}…", "migrations_skip_migration": "Skipping migration {id}…", - "migrations_success_forward": "Successfully ran migration {id}!", - "migrations_to_be_ran_manually": "Migration {id} has to be ran manually. Please go to Tools > Migrations on the webadmin, or run `yunohost tools migrations migrate`.", - "monitor_disabled": "The server monitoring has been disabled", - "monitor_enabled": "The server monitoring has been enabled", - "monitor_glances_con_failed": "Unable to connect to Glances server", - "monitor_not_enabled": "Server monitoring is not enabled", + "migrations_success_forward": "Ran migration {id}", + "migrations_to_be_ran_manually": "Migration {id} has to be run manually. Please go to Tools → Migrations on the webadmin page, or run `yunohost tools migrations migrate`.", + "monitor_disabled": "Server monitoring now turned off", + "monitor_enabled": "Server monitoring now turned on", + "monitor_glances_con_failed": "Could not connect to Glances server", + "monitor_not_enabled": "Server monitoring is off", "monitor_period_invalid": "Invalid time period", "monitor_stats_file_not_found": "Statistics file not found", "monitor_stats_no_update": "No monitoring statistics to update", @@ -396,190 +396,190 @@ "mountpoint_unknown": "Unknown mountpoint", "mysql_db_creation_failed": "MySQL database creation failed", "mysql_db_init_failed": "MySQL database init failed", - "mysql_db_initialized": "The MySQL database has been initialized", - "need_define_permission_before": "You need to redefine the permission using 'yunohost user permission add -u USER' before removing an allowed group", + "mysql_db_initialized": "The MySQL database now initialized", + "need_define_permission_before": "Redefine the permission using 'yunohost user permission add -u USER' before removing an allowed group", "network_check_mx_ko": "DNS MX record is not set", - "network_check_smtp_ko": "Outbound mail (SMTP port 25) seems to be blocked by your network", - "network_check_smtp_ok": "Outbound mail (SMTP port 25) is not blocked", + "network_check_smtp_ko": "Outbound e-mail (SMTP port 25) seems to be blocked by your network", + "network_check_smtp_ok": "Outbound e-mail (SMTP port 25) is not blocked", "new_domain_required": "You must provide the new main domain", "no_appslist_found": "No app list found", - "no_internet_connection": "Server is not connected to the Internet", + "no_internet_connection": "Server not connected to the Internet", "no_ipv6_connectivity": "IPv6 connectivity is not available", "no_restore_script": "No restore script found for the app '{app:s}'", - "not_enough_disk_space": "Not enough free disk space on '{path:s}'", + "not_enough_disk_space": "Not enough free space on '{path:s}'", "package_not_installed": "Package '{pkgname}' is not installed", "package_unexpected_error": "An unexpected error occurred processing the package '{pkgname}'", "package_unknown": "Unknown package '{pkgname}'", "packages_upgrade_critical_later": "Critical packages ({packages:s}) will be upgraded later", - "packages_upgrade_failed": "Unable to upgrade all of the packages", - "password_listed": "This password is among the most used password in the world. Please choose something a bit more unique.", - "password_too_simple_1": "Password needs to be at least 8 characters long", - "password_too_simple_2": "Password needs to be at least 8 characters long and contains digit, upper and lower characters", - "password_too_simple_3": "Password needs to be at least 8 characters long and contains digit, upper, lower and special characters", - "password_too_simple_4": "Password needs to be at least 12 characters long and contains digit, upper, lower and special characters", - "path_removal_failed": "Unable to remove path {:s}", - "pattern_backup_archive_name": "Must be a valid filename with max 30 characters, and alphanumeric and -_. characters only", + "packages_upgrade_failed": "Could not upgrade all the packages", + "password_listed": "This password is among the most used password in the world. Please choose something more unique.", + "password_too_simple_1": "The password needs to be at least 8 characters long", + "password_too_simple_2": "The password needs to be at least 8 characters long and contain a digit, upper and lower characters", + "password_too_simple_3": "The password needs to be at least 8 characters long and contain a digit, upper, lower and special characters", + "password_too_simple_4": "The password needs to be at least 12 characters long and contain a digit, upper, lower and special characters", + "path_removal_failed": "Could not remove path {:s}", + "pattern_backup_archive_name": "Must be a valid filename with max 30 characters, alphanumeric and -_. characters only", "pattern_domain": "Must be a valid domain name (e.g. my-domain.org)", "pattern_email": "Must be a valid email address (e.g. someone@domain.org)", "pattern_firstname": "Must be a valid first name", "pattern_lastname": "Must be a valid last name", "pattern_listname": "Must be alphanumeric and underscore characters only", - "pattern_mailbox_quota": "Must be a size with b/k/M/G/T suffix or 0 to disable the quota", + "pattern_mailbox_quota": "Must be a size with b/k/M/G/T suffix or 0 to not havea quota", "pattern_password": "Must be at least 3 characters long", "pattern_port": "Must be a valid port number (i.e. 0-65535)", "pattern_port_or_range": "Must be a valid port number (i.e. 0-65535) or range of ports (e.g. 100:200)", "pattern_positive_number": "Must be a positive number", "pattern_username": "Must be lower-case alphanumeric and underscore characters only", - "pattern_password_app": "Sorry, passwords should not contain the following characters: {forbidden_chars}", + "pattern_password_app": "Sorry, passwords can not contain the following characters: {forbidden_chars}", "permission_already_clear": "Permission '{permission:s}' already clear for app {app:s}", "permission_already_exist": "Permission '{permission:s}' for app {app:s} already exist", "permission_created": "Permission '{permission:s}' for app {app:s} created", - "permission_creation_failed": "Permission creation failed", + "permission_creation_failed": "Could not grant permission", "permission_deleted": "Permission '{permission:s}' for app {app:s} deleted", - "permission_deletion_failed": "Permission '{permission:s}' for app {app:s} deletion failed", - "permission_not_found": "Permission '{permission:s}' not found for application {app:s}", - "permission_name_not_valid": "Permission name '{permission:s}' not valid", - "permission_update_failed": "Permission update failed", - "permission_generated": "The permission database has been updated", - "permission_updated": "Permission '{permission:s}' for app {app:s} updated", + "permission_deletion_failed": "Missing permission '{permission:s}' to delete the app '{app:s}'", + "permission_not_found": "Permission '{permission:s}' not found for the application '{app:s}'", + "permission_name_not_valid": "Pick an allowed permission name for '{permission:s}'", + "permission_update_failed": "Could not update permission", + "permission_generated": "Permission database updated", + "permission_updated": "Permission '{permission:s}' for the app '{app:s}' updated", "permission_update_nothing_to_do": "No permissions to update", "port_already_closed": "Port {port:d} is already closed for {ip_version:s} connections", "port_already_opened": "Port {port:d} is already opened for {ip_version:s} connections", "port_available": "Port {port:d} is available", "port_unavailable": "Port {port:d} is not available", - "recommend_to_add_first_user": "The post-install is finished but YunoHost needs at least one user to work correctly, you should add one using 'yunohost user create ' or the admin interface.", + "recommend_to_add_first_user": "The post-install is finished, but YunoHost needs at least one user to work correctly, you should add one using 'yunohost user create ' or do it from the admin interface.", "remove_main_permission_not_allowed": "Removing the main permission is not allowed", - "remove_user_of_group_not_allowed": "You are not allowed to remove the user {user:s} in the group {group:s}", - "regenconf_file_backed_up": "The configuration file '{conf}' has been backed up to '{backup}'", - "regenconf_file_copy_failed": "Unable to copy the new configuration file '{new}' to '{conf}'", - "regenconf_file_kept_back": "The configuration file '{conf}' is expected to be deleted by regen-conf (category {category}) but has been kept back.", + "remove_user_of_group_not_allowed": "You are not allowed to remove the user '{user:s}' in the group '{group:s}'", + "regenconf_file_backed_up": "Configuration file '{conf}' backed up to '{backup}'", + "regenconf_file_copy_failed": "Could not copy the new configuration file '{new}' to '{conf}'", + "regenconf_file_kept_back": "The configuration file '{conf}' is expected to be deleted by regen-conf (category {category}) but was kept back.", "regenconf_file_manually_modified": "The configuration file '{conf}' has been manually modified and will not be updated", - "regenconf_file_manually_removed": "The configuration file '{conf}' has been manually removed and will not be created", - "regenconf_file_remove_failed": "Unable to remove the configuration file '{conf}'", - "regenconf_file_removed": "The configuration file '{conf}' has been removed", - "regenconf_file_updated": "The configuration file '{conf}' has been updated", + "regenconf_file_manually_removed": "The configuration file '{conf}' was removed manually, and will not be created", + "regenconf_file_remove_failed": "Could not remove the configuration file '{conf}'", + "regenconf_file_removed": "Configuration file '{conf}' removed", + "regenconf_file_updated": "Configuration file '{conf}' updated", "regenconf_now_managed_by_yunohost": "The configuration file '{conf}' is now managed by YunoHost (category {category}).", "regenconf_up_to_date": "The configuration is already up-to-date for category '{category}'", - "regenconf_updated": "The configuration has been updated for category '{category}'", + "regenconf_updated": "Configuration for category '{category}' updated", "regenconf_would_be_updated": "The configuration would have been updated for category '{category}'", "regenconf_dry_pending_applying": "Checking pending configuration which would have been applied for category '{category}'…", - "regenconf_failed": "Unable to regenerate the configuration for category(s): {categories}", + "regenconf_failed": "Could not regenerate the configuration for category(s): {categories}", "regenconf_pending_applying": "Applying pending configuration for category '{category}'…", - "restore_action_required": "You must specify something to restore", - "restore_already_installed_app": "An app is already installed with the id '{app:s}'", - "restore_app_failed": "Unable to restore the app '{app:s}'", - "restore_cleaning_failed": "Unable to clean-up the temporary restoration directory", - "restore_complete": "Restore complete", + "restore_action_required": "You must pick something to restore", + "restore_already_installed_app": "An app with the ID '{app:s}' is already installed", + "restore_app_failed": "Could not restore the app '{app:s}'", + "restore_cleaning_failed": "Could not clean up the temporary restoration directory", + "restore_complete": "Restored", "restore_confirm_yunohost_installed": "Do you really want to restore an already installed system? [{answers:s}]", "restore_extracting": "Extracting needed files from the archive…", - "restore_failed": "Unable to restore the system", - "restore_hook_unavailable": "Restoration script for '{part:s}' not available on your system and not in the archive either", - "restore_may_be_not_enough_disk_space": "Your system seems not to have enough disk space (freespace: {free_space:d} B, needed space: {needed_space:d} B, security margin: {margin:d} B)", + "restore_failed": "Could not restore system", + "restore_hook_unavailable": "The restoration script for '{part:s}' not available on your system and not in the archive either", + "restore_may_be_not_enough_disk_space": "Your system seems does not have enough space (free: {free_space:d} B, needed space: {needed_space:d} B, security margin: {margin:d} B)", "restore_mounting_archive": "Mounting archive into '{path:s}'", - "restore_not_enough_disk_space": "Not enough disk space (freespace: {free_space:d} B, needed space: {needed_space:d} B, security margin: {margin:d} B)", - "restore_nothings_done": "Nothing has been restored", - "restore_removing_tmp_dir_failed": "Unable to remove an old temporary directory", - "restore_running_app_script": "Running restore script of app '{app:s}'…", + "restore_not_enough_disk_space": "Not enough space (space: {free_space:d} B, needed space: {needed_space:d} B, security margin: {margin:d} B)", + "restore_nothings_done": "Nothing was restored", + "restore_removing_tmp_dir_failed": "Could not remove an old temporary directory", + "restore_running_app_script": "Restoring the app '{app:s}'…", "restore_running_hooks": "Running restoration hooks…", - "restore_system_part_failed": "Unable to restore the '{part:s}' system part", - "root_password_desynchronized": "The admin password has been changed, but YunoHost was unable to propagate this on the root password!", + "restore_system_part_failed": "Could not restore the '{part:s}' system part", + "root_password_desynchronized": "The admin password was changed, but YunoHost could not propagate this to the root password!", "root_password_replaced_by_admin_password": "Your root password have been replaced by your admin password.", - "server_shutdown": "The server will shutdown", + "server_shutdown": "The server will shut down", "server_shutdown_confirm": "The server will shutdown immediatly, are you sure? [{answers:s}]", "server_reboot": "The server will reboot", "server_reboot_confirm": "The server will reboot immediatly, are you sure? [{answers:s}]", - "service_add_failed": "Unable to add service '{service:s}'", - "service_added": "The service '{service:s}' has been added", - "service_already_started": "Service '{service:s}' has already been started", - "service_already_stopped": "Service '{service:s}' has already been stopped", - "service_cmd_exec_failed": "Unable to execute command '{command:s}'", - "service_description_avahi-daemon": "allows to reach your server using yunohost.local on your local network", - "service_description_dnsmasq": "handles domain name resolution (DNS)", - "service_description_dovecot": "allows e-mail client to access/fetch email (via IMAP and POP3)", - "service_description_fail2ban": "protects against bruteforce and other kind of attacks from the Internet", - "service_description_glances": "monitors system information on your server", - "service_description_metronome": "manage XMPP instant messaging accounts", - "service_description_mysql": "stores applications data (SQL database)", - "service_description_nginx": "serves or provides access to all the websites hosted on your server", - "service_description_nslcd": "handles YunoHost user shell connection", - "service_description_php7.0-fpm": "runs applications written in PHP with nginx", - "service_description_postfix": "used to send and receive emails", - "service_description_redis-server": "a specialized database used for rapid data access, task queue and communication between programs", - "service_description_rmilter": "checks various parameters in emails", - "service_description_rspamd": "filters spam, and other email-related features", - "service_description_slapd": "stores users, domains and related information", - "service_description_ssh": "allows you to connect remotely to your server via a terminal (SSH protocol)", - "service_description_yunohost-api": "manages interactions between the YunoHost web interface and the system", - "service_description_yunohost-firewall": "manages open and close connexion ports to services", - "service_disable_failed": "Unable to disable service '{service:s}'\n\nRecent service logs:{logs:s}", - "service_disabled": "The service '{service:s}' has been disabled", - "service_enable_failed": "Unable to enable service '{service:s}'\n\nRecent service logs:{logs:s}", - "service_enabled": "The service '{service:s}' has been enabled", + "service_add_failed": "Could not add the service '{service:s}'", + "service_added": "The service '{service:s}' added", + "service_already_started": "The service '{service:s}' has already been started", + "service_already_stopped": "The service '{service:s}' has already been stopped", + "service_cmd_exec_failed": "Could not execute the command '{command:s}'", + "service_description_avahi-daemon": "allows you to reach your server using 'yunohost.local' in your local network", + "service_description_dnsmasq": "Handles domain name resolution (DNS)", + "service_description_dovecot": "Allows e-mail clients to access/fetch email (via IMAP and POP3)", + "service_description_fail2ban": "Protects against brute-force and other kinds of attacks from the Internet", + "service_description_glances": "Monitors system info on your server", + "service_description_metronome": "Manage XMPP instant messaging accounts", + "service_description_mysql": "Stores applications data (SQL database)", + "service_description_nginx": "Serves or provides access to all the websites hosted on your server", + "service_description_nslcd": "Handles YunoHost user shell connection", + "service_description_php7.0-fpm": "runs applications written in PHP with NGINX", + "service_description_postfix": "Used to send and receive e-mails", + "service_description_redis-server": "A specialized database used for rapid data access, task queue, and communication between programs", + "service_description_rmilter": "Checks various parameters in e-mails", + "service_description_rspamd": "Filters spam, and other e-mail related features", + "service_description_slapd": "Stores users, domains and related info", + "service_description_ssh": "Allows you to connect remotely to your server via a terminal (SSH protocol)", + "service_description_yunohost-api": "Manages interactions between the YunoHost web interface and the system", + "service_description_yunohost-firewall": "Manages open and close connexion ports to services", + "service_disable_failed": "Could not turn off the service '{service:s}'\n\nRecent service logs:{logs:s}", + "service_disabled": "'{service:s}' service turned off", + "service_enable_failed": "Could not turn on the service '{service:s}'\n\nRecent service logs:{logs:s}", + "service_enabled": "'{service:s}' service turned off", "service_no_log": "No log to display for service '{service:s}'", "service_regen_conf_is_deprecated": "'yunohost service regen-conf' is deprecated! Please use 'yunohost tools regen-conf' instead.", - "service_remove_failed": "Unable to remove service '{service:s}'", - "service_removed": "The service '{service:s}' has been removed", - "service_reload_failed": "Unable to reload service '{service:s}'\n\nRecent service logs:{logs:s}", - "service_reloaded": "The service '{service:s}' has been reloaded", - "service_restart_failed": "Unable to restart service '{service:s}'\n\nRecent service logs:{logs:s}", - "service_restarted": "The service '{service:s}' has been restarted", - "service_reload_or_restart_failed": "Unable to reload or restart service '{service:s}'\n\nRecent service logs:{logs:s}", - "service_reloaded_or_restarted": "The service '{service:s}' has been reloaded or restarted", - "service_start_failed": "Unable to start service '{service:s}'\n\nRecent service logs:{logs:s}", - "service_started": "The service '{service:s}' has been started", - "service_status_failed": "Unable to determine status of service '{service:s}'", - "service_stop_failed": "Unable to stop service '{service:s}'\n\nRecent service logs:{logs:s}", - "service_stopped": "The service '{service:s}' has been stopped", + "service_remove_failed": "Could not remove the service '{service:s}'", + "service_removed": "'{service:s}' service removed", + "service_reload_failed": "Could not reload the service '{service:s}'\n\nRecent service logs:{logs:s}", + "service_reloaded": "'{service:s}' service reloaded", + "service_restart_failed": "Could not restart the service '{service:s}'\n\nRecent service logs:{logs:s}", + "service_restarted": "'{service:s}' service restarted", + "service_reload_or_restart_failed": "Could not reload or restart the service '{service:s}'\n\nRecent service logs:{logs:s}", + "service_reloaded_or_restarted": "'{service:s}' service reloaded or restarted", + "service_start_failed": "Could not start the service '{service:s}'\n\nRecent service logs:{logs:s}", + "service_started": "'{service:s}' service started", + "service_status_failed": "Could not determine status of the service '{service:s}'", + "service_stop_failed": "Could not stop the service '{service:s}'\n\nRecent service logs:{logs:s}", + "service_stopped": "'{service:s}' service stopped", "service_unknown": "Unknown service '{service:s}'", - "ssowat_conf_generated": "The SSOwat configuration has been generated", - "ssowat_conf_updated": "The SSOwat configuration has been updated", - "ssowat_persistent_conf_read_error": "Error while reading SSOwat persistent configuration: {error:s}. Edit /etc/ssowat/conf.json.persistent file to fix the JSON syntax", - "ssowat_persistent_conf_write_error": "Error while saving SSOwat persistent configuration: {error:s}. Edit /etc/ssowat/conf.json.persistent file to fix the JSON syntax", + "ssowat_conf_generated": "SSOwat configuration generated", + "ssowat_conf_updated": "SSOwat configuration updated", + "ssowat_persistent_conf_read_error": "Could not read persistent SSOwat configuration: {error:s}. Edit /etc/ssowat/conf.json.persistent file to fix the JSON syntax", + "ssowat_persistent_conf_write_error": "Could not save persistent SSOwat configuration: {error:s}. Edit /etc/ssowat/conf.json.persistent file to fix the JSON syntax", "system_groupname_exists": "Groupname already exists in the system group", - "system_upgraded": "The system has been upgraded", - "system_username_exists": "Username already exists in the system users", - "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`.", - "tools_update_failed_to_app_fetchlist": "Failed to update YunoHost's applists because: {error}", - "tools_upgrade_at_least_one": "Please specify --apps OR --system", + "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`.", + "tools_update_failed_to_app_fetchlist": "Could not update YunoHost's applists because: {error}", + "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": "Unable to hold critical packages ...", - "tools_upgrade_cant_unhold_critical_packages": "Unable to unhold critical packages ...", - "tools_upgrade_regular_packages": "Now upgrading 'regular' (non-yunohost-related) packages ...", - "tools_upgrade_regular_packages_failed": "Unable to upgrade packages: {packages_list}", - "tools_upgrade_special_packages": "Now upgrading 'special' (yunohost-related) packages ...", - "tools_upgrade_special_packages_explanation": "This action will end but the actual special upgrade will continue in background. Please don't start any other action on your server in the next ~10 minutes (depending on your hardware speed). Once it's done, you may have to re-log on the webadmin. The upgrade log will be available in Tools > Log (in the webadmin) or through 'yunohost log list' (in command line).", - "tools_upgrade_special_packages_completed": "YunoHost package upgrade completed !\nPress [Enter] to get the command line back", + "tools_upgrade_cant_hold_critical_packages": "Could not hold critical packages…", + "tools_upgrade_cant_unhold_critical_packages": "Could not to unhold critical packages…", + "tools_upgrade_regular_packages": "Now upgrading 'regular' (non-yunohost-related) packages…", + "tools_upgrade_regular_packages_failed": "Could not upgrade packages: {packages_list}", + "tools_upgrade_special_packages": "Now upgrading 'special' (yunohost-related) packages…", + "tools_upgrade_special_packages_explanation": "This action will end, but the actual special upgrade will continue in background. Please don't start any other action on your server in the next ~10 minutes (depending on your hardware speed). Once it i done, you may have to log in on the webadmin page again. The upgrade log will be available in Tools → Log (on the webadmin page) or through 'yunohost log list' (from the command line).", + "tools_upgrade_special_packages_completed": "YunoHost package upgrade completed.\nPress [Enter] to get the command line back", "unbackup_app": "App '{app:s}' will not be saved", - "unexpected_error": "An unexpected error occured: {error}", + "unexpected_error": "Something unexpected went wrong: {error}", "unit_unknown": "Unknown unit '{unit:s}'", "unlimit": "No quota", "unrestore_app": "App '{app:s}' will not be restored", - "update_apt_cache_failed": "Unable to update the cache of APT (Debian's package manager). Here is a dump of the sources.list lines which might help to identify problematic lines : \n{sourceslist}", - "update_apt_cache_warning": "Some errors happened while updating the cache of APT (Debian's package manager). Here is a dump of the sources.list lines which might help to identify problematic lines : \n{sourceslist}", + "update_apt_cache_failed": "Could not to update the cache of APT (Debian's package manager). Here is a dump of the sources.list lines, which might help identify problematic lines: \n{sourceslist}", + "update_apt_cache_warning": "Something went wrong while updating the cache of APT (Debian's package manager). Here is a dump of the sources.list lines, which might help identify problematic lines: \n{sourceslist}", "updating_apt_cache": "Fetching available upgrades for system packages…", "updating_app_lists": "Fetching available upgrades for applications…", "upgrade_complete": "Upgrade complete", "upgrading_packages": "Upgrading packages…", "upnp_dev_not_found": "No UPnP device found", - "upnp_disabled": "UPnP has been disabled", - "upnp_enabled": "UPnP has been enabled", - "upnp_port_open_failed": "Unable to open UPnP ports", - "user_already_in_group": "User {user:} already in group {group:s}", - "user_created": "The user has been created", - "user_creation_failed": "Unable to create user", - "user_deleted": "The user has been deleted", - "user_deletion_failed": "Unable to delete user", - "user_home_creation_failed": "Unable to create user home folder", - "user_info_failed": "Unable to retrieve user information", - "user_not_in_group": "User {user:s} not in group {group:s}", + "upnp_disabled": "UPnP turned off", + "upnp_enabled": "UPnP turned on", + "upnp_port_open_failed": "Could not open port via UPnP", + "user_already_in_group": "The User '{user:}' already in the group {group:s}", + "user_created": "User created", + "user_creation_failed": "Could not create user", + "user_deleted": "User deleted", + "user_deletion_failed": "Could not delete user", + "user_home_creation_failed": "Could not create 'home' folder for user", + "user_info_failed": "Could not retrieve user info", + "user_not_in_group": "The user '{user:s}' is not in the group {group:s}", "user_unknown": "Unknown user: {user:s}", - "user_update_failed": "Unable to update user", - "user_updated": "The user has been updated", + "user_update_failed": "Could not change user info", + "user_updated": "User info changed", "users_available": "Available users:", "yunohost_already_installed": "YunoHost is already installed", - "yunohost_ca_creation_failed": "Unable to create certificate authority", - "yunohost_ca_creation_success": "The local certification authority has been created.", - "yunohost_configured": "YunoHost has been configured", + "yunohost_ca_creation_failed": "Could not create certificate authority", + "yunohost_ca_creation_success": "Local certification authority created.", + "yunohost_configured": "YunoHost now configured", "yunohost_installing": "Installing YunoHost…", - "yunohost_not_installed": "YunoHost is not or not correctly installed. Please execute 'yunohost tools postinstall'" + "yunohost_not_installed": "YunoHost is incorrectly or not correctly installed. Please run 'yunohost tools postinstall'" } From 16ea8f45d64b92e94af4eb3ffceec1341f16e662 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Tue, 17 Sep 2019 20:32:57 +0200 Subject: [PATCH 0141/3170] Language reworked --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index d06e97c45..b860c27d7 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,19 @@ -[![Build Status](https://travis-ci.org/YunoHost/yunohost.svg?branch=stretch-unstable)](https://travis-ci.org/YunoHost/yunohost) +[![Build status](https://travis-ci.org/YunoHost/yunohost.svg?branch=stretch-unstable)](https://travis-ci.org/YunoHost/yunohost) [![GitHub license](https://img.shields.io/github/license/YunoHost/yunohost)](https://github.com/YunoHost/yunohost/blob/stretch-unstable/LICENSE) # YunoHost core This repository is the core of YunoHost code. -- [YunoHost project website](https://yunohost.org) -- [Butracker](https://github.com/YunoHost/issues). +- [Project website](https://yunohost.org) +- [Bugtracker](https://github.com/YunoHost/issues). ## Contributing - You can develop on this repository using [ynh-dev](https://github.com/YunoHost/ynh-dev) with `use-git` sub-command. -- On this repository we are [following this workflow](https://yunohost.org/#/build_system_en): `stable <- testing <- unstable <- your_branch`. -- Note: if you modify python scripts, you will have to modifiy the actions map. -- You can help with translation on [our translation platform](https://translate.yunohost.org/engage/yunohost/?utm_source=widget) +- On this repository we are [following this workflow](https://yunohost.org/#/build_system_en): `stable ← testing ← unstable ← your_branch`. +- Note: If you modify Python scripts, you will have to modifiy the actions map. +- You can help translate YunoHost on our [translation platform](https://translate.yunohost.org/engage/yunohost/?utm_source=widget) Translation status @@ -34,7 +34,7 @@ This repository is the core of YunoHost code. - Python core scripts are accessible through two interfaces thanks to the [moulinette framework](https://github.com/YunoHost/moulinette): - [CLI](https://en.wikipedia.org/wiki/Command-line_interface) for `yunohost` command. - [API](https://en.wikipedia.org/wiki/Application_programming_interface) for [web administration module](https://github.com/YunoHost/yunohost-admin) (other modules could be implemented). -- You can find more details about how YunoHost works on this [documentation (in french)](https://yunohost.org/#/package_list_fr). +- You can find more details about how YunoHost works on this [documentation (in French)](https://yunohost.org/#/package_list_fr). ## Dependencies @@ -45,4 +45,4 @@ This repository is the core of YunoHost code. ## License -As [other components of YunoHost core code](https://yunohost.org/#/faq_en), this repository is under GNU AGPL v.3 license. +As [other components of YunoHost core code](https://yunohost.org/#/faq_en), this repository is licensed GNU AGPL v3. From 4f9b56c29917d501fd55b1a5a71e31948c7421bc Mon Sep 17 00:00:00 2001 From: ButterflyOfFire Date: Tue, 17 Sep 2019 14:33:24 +0000 Subject: [PATCH 0142/3170] Translated using Weblate (Arabic) Currently translated at 66.2% (386 of 583 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ar/ --- locales/ar.json | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/locales/ar.json b/locales/ar.json index 52e86d90b..b6f3ece23 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -23,7 +23,7 @@ "app_location_install_failed": "Unable to install the app in this location because it conflit with the app '{other_app}' already installed on '{other_path}'", "app_location_unavailable": "This url is not available or conflicts with an already installed app", "app_manifest_invalid": "Invalid app manifest: {error}", - "app_no_upgrade": "البرمجيات لا تحتاج إلى تحديث", + "app_no_upgrade": "ليس هناك أي تطبيق بحاجة إلى تحديث", "app_not_correctly_installed": "يبدو أن التطبيق {app:s} لم يتم تنصيبه بشكل صحيح", "app_not_installed": "إنّ التطبيق {app:s} غير مُنصَّب", "app_not_properly_removed": "لم يتم حذف تطبيق {app:s} بشكلٍ جيّد", @@ -37,17 +37,17 @@ "app_unsupported_remote_type": "Unsupported remote type used for the app", "app_upgrade_app_name": "جارٍ تحديث تطبيق {app}…", "app_upgrade_failed": "تعذرت عملية ترقية {app:s}", - "app_upgrade_some_app_failed": "تعذرت عملية ترقية بعض البرمجيات", + "app_upgrade_some_app_failed": "تعذرت عملية ترقية بعض التطبيقات", "app_upgraded": "تم تحديث التطبيق {app:s}", "appslist_corrupted_json": "Could not load the application lists. It looks like {filename:s} is corrupted.", "appslist_could_not_migrate": "Could not migrate app list {appslist:s} ! Unable to parse the url... The old cron job has been kept in {bkp_file:s}.", "appslist_fetched": "تم جلب قائمة تطبيقات {appslist:s}", "appslist_migrating": "Migrating application list {appslist:s} …", "appslist_name_already_tracked": "There is already a registered application list with name {name:s}.", - "appslist_removed": "تم حذف قائمة البرمجيات {appslist:s}", + "appslist_removed": "تم حذف قائمة التطبيقات {appslist:s}", "appslist_retrieve_bad_format": "Retrieved file for application list {appslist:s} is not valid", "appslist_retrieve_error": "Unable to retrieve the remote application list {appslist:s}: {error:s}", - "appslist_unknown": "قائمة البرمجيات {appslist:s} مجهولة.", + "appslist_unknown": "قائمة التطبيقات {appslist:s} مجهولة.", "appslist_url_already_tracked": "There is already a registered application list with url {url:s}.", "ask_current_admin_password": "كلمة السر الإدارية الحالية", "ask_email": "عنوان البريد الإلكتروني", @@ -114,7 +114,7 @@ "certmanager_attempt_to_replace_valid_cert": "You are attempting to overwrite a good and valid certificate for domain {domain:s}! (Use --force to bypass)", "certmanager_cannot_read_cert": "Something wrong happened when trying to open current certificate for domain {domain:s} (file: {file:s}), reason: {reason:s}", "certmanager_cert_install_success": "تمت عملية تنصيب شهادة Let's Encrypt بنجاح على النطاق {domain:s} !", - "certmanager_cert_install_success_selfsigned": "Successfully installed a self-signed certificate for domain {domain:s}!", + "certmanager_cert_install_success_selfsigned": "نجحت عملية تثبيت الشهادة الموقعة ذاتيا الخاصة بالنطاق {domain:s}!", "certmanager_cert_renew_success": "نجحت عملية تجديد شهادة Let's Encrypt الخاصة باسم النطاق {domain:s} !", "certmanager_cert_signing_failed": "فشل إجراء توقيع الشهادة الجديدة", "certmanager_certificate_fetching_or_enabling_failed": "Sounds like enabling the new certificate for {domain:s} failed somehow…", @@ -232,7 +232,7 @@ "migrations_no_migrations_to_run": "No migrations to run", "migrations_show_currently_running_migration": "Running migration {number} {name}…", "migrations_show_last_migration": "Last ran migration is {}", - "migrations_skip_migration": "جارٍ تجاهل التهجير {number} {name}…", + "migrations_skip_migration": "جارٍ تجاهل التهجير {id}…", "monitor_disabled": "The server monitoring has been disabled", "monitor_enabled": "The server monitoring has been enabled", "monitor_glances_con_failed": "Unable to connect to Glances server", @@ -316,7 +316,7 @@ "service_conf_updated": "The configuration has been updated for service '{service}'", "service_conf_would_be_updated": "The configuration would have been updated for service '{service}'", "service_disable_failed": "", - "service_disabled": "The service '{service:s}' has been disabled", + "service_disabled": "تم تعطيل خدمة '{service:s}'", "service_enable_failed": "", "service_enabled": "تم تنشيط خدمة '{service:s}'", "service_no_log": "ليس لخدمة '{service:s}' أي سِجلّ للعرض", @@ -347,7 +347,7 @@ "upgrade_complete": "إكتملت عملية الترقية و التحديث", "upgrading_packages": "عملية ترقية الحُزم جارية …", "upnp_dev_not_found": "No UPnP device found", - "upnp_disabled": "UPnP has been disabled", + "upnp_disabled": "تم تعطيل UPnP", "upnp_enabled": "UPnP has been enabled", "upnp_port_open_failed": "Unable to open UPnP ports", "user_created": "تم إنشاء المستخدم", @@ -441,5 +441,6 @@ "group_name_already_exist": "الفريق {name:s} موجود بالفعل", "error_when_removing_sftpuser_group": "حدث خطأ أثناء محاولة حذف فريق sftpusers", "dyndns_could_not_check_available": "لا يمكن التحقق مِن أنّ {domain:s} متوفر على {provider:s}.", - "backup_mount_archive_for_restore": "جارٍ تهيئة النسخة الاحتياطية للاسترجاع…" + "backup_mount_archive_for_restore": "جارٍ تهيئة النسخة الاحتياطية للاسترجاع…", + "root_password_replaced_by_admin_password": "لقد تم استبدال كلمة سر الجذر root بالكلمة الإدارية لـ admin." } From 60e602a35e4a587c96a57a46b9cc1e3bf5e7544f Mon Sep 17 00:00:00 2001 From: advocatux Date: Sun, 15 Sep 2019 12:23:09 +0000 Subject: [PATCH 0143/3170] Translated using Weblate (Spanish) Currently translated at 100.0% (583 of 583 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/es/ --- locales/es.json | 334 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 307 insertions(+), 27 deletions(-) diff --git a/locales/es.json b/locales/es.json index 8861c15b8..c34a7e33c 100644 --- a/locales/es.json +++ b/locales/es.json @@ -16,15 +16,15 @@ "app_manifest_invalid": "El manifiesto de la aplicación no es válido: {error}", "app_no_upgrade": "No hay aplicaciones para actualizar", "app_not_correctly_installed": "La aplicación {app:s} 8 parece estar incorrectamente instalada", - "app_not_installed": "{app:s} 9 no está instalada", + "app_not_installed": "La aplicación «{app:s}» no está instalada. Esta es la lista de todas las aplicaciones instaladas: {all_apps}", "app_not_properly_removed": "La {app:s} 0 no ha sido desinstalada correctamente", "app_package_need_update": "El paquete de la aplicación {app} necesita ser actualizada debido a los cambios en YunoHost", "app_recent_version_required": "{:s} requiere una versión más reciente de moulinette ", "app_removed": "{app:s} ha sido eliminada", - "app_requirements_checking": "Comprobando los paquetes requeridos por {app}...", + "app_requirements_checking": "Comprobando los paquetes necesarios para {app}…", "app_requirements_failed": "No se cumplen los requisitos para {app}: {error}", "app_requirements_unmeet": "No se cumplen los requisitos para {app}, el paquete {pkgname} ({version}) debe ser {spec}", - "app_sources_fetch_failed": "No se pudieron descargar los archivos del código fuente", + "app_sources_fetch_failed": "No se pudo obtener los archivos con el código fuente, ¿es la URL correcta?", "app_unknown": "Aplicación desconocida", "app_unsupported_remote_type": "Tipo remoto no soportado por la aplicación", "app_upgrade_failed": "No se pudo actualizar la aplicación {app:s}", @@ -50,11 +50,11 @@ "backup_archive_open_failed": "No se pudo abrir la copia de seguridad", "backup_cleaning_failed": "No se puede limpiar el directorio temporal de copias de seguridad", "backup_created": "Se ha creado la copia de seguridad", - "backup_creating_archive": "Creando copia de seguridad...", + "backup_creating_archive": "Creando el archivo de copia de seguridad…", "backup_creation_failed": "No se pudo crear la copia de seguridad", "backup_delete_error": "No se puede eliminar '{path:s}'", "backup_deleted": "La copia de seguridad ha sido eliminada", - "backup_extracting_archive": "Extrayendo la copia de seguridad...", + "backup_extracting_archive": "Extrayendo el archivo de la copia de seguridad…", "backup_hook_unknown": "Hook de copia de seguridad desconocido '{hook:s}'", "backup_invalid_archive": "La copia de seguridad no es válida", "backup_nothings_done": "No hay nada que guardar", @@ -86,21 +86,21 @@ "domain_zone_exists": "El archivo de zona del DNS ya existe", "domain_zone_not_found": "No se ha encontrado el archivo de zona del DNS para el dominio [:s]", "done": "Hecho.", - "downloading": "Descargando...", + "downloading": "Descargando…", "dyndns_cron_installed": "La tarea cron para DynDNS ha sido instalada", - "dyndns_cron_remove_failed": "No se pudo eliminar la tarea cron DynDNS", + "dyndns_cron_remove_failed": "No se pudo eliminar la tarea cron de DynDNS por: {error}", "dyndns_cron_removed": "La tarea cron DynDNS ha sido eliminada", "dyndns_ip_update_failed": "No se pudo actualizar la dirección IP en el DynDNS", "dyndns_ip_updated": "Su dirección IP ha sido actualizada en el DynDNS", - "dyndns_key_generating": "Se está generando la clave del DNS. Esto podría tardar unos minutos...", + "dyndns_key_generating": "Generando la clave del DNS. Esto podría tardar un rato…", "dyndns_key_not_found": "No se ha encontrado la clave DNS para el dominio", "dyndns_no_domain_registered": "Ningún dominio ha sido registrado con DynDNS", "dyndns_registered": "El dominio DynDNS ha sido registrado", "dyndns_registration_failed": "No se pudo registrar el dominio DynDNS: {error:s}", "dyndns_unavailable": "El dominio {domain:s} no está disponible.", - "executing_command": "Ejecutando el comando '{command:s}'...", - "executing_script": "Ejecutando el script '{script:s}'...", - "extracting": "Extrayendo...", + "executing_command": "Ejecutando la orden «{command:s}»…", + "executing_script": "Ejecutando el guión «{script:s}»…", + "extracting": "Extrayendo…", "field_invalid": "Campo no válido '{:s}'", "firewall_reload_failed": "No se pudo recargar el cortafuegos", "firewall_reloaded": "El cortafuegos ha sido recargado", @@ -176,8 +176,8 @@ "restore_failed": "No se pudo restaurar el sistema", "restore_hook_unavailable": "El script de restauración '{part:s}' no está disponible en su sistema y tampoco en el archivo", "restore_nothings_done": "No se ha restaurado nada", - "restore_running_app_script": "Ejecutando el script de restauración de la aplicación '{app:s}'...", - "restore_running_hooks": "Ejecutando los hooks de restauración...", + "restore_running_app_script": "Ejecutando el guión de restauración de la aplicación «{app:s}»…", + "restore_running_hooks": "Ejecutando los ganchos de restauración…", "service_add_failed": "No se pudo añadir el servicio '{service:s}'", "service_added": "Servicio '{service:s}' ha sido añadido", "service_already_started": "El servicio '{service:s}' ya ha sido inicializado", @@ -220,9 +220,9 @@ "unlimit": "Sin cuota", "unrestore_app": "La aplicación '{app:s}' no será restaurada", "update_cache_failed": "No se pudo actualizar la caché de APT", - "updating_apt_cache": "Actualizando lista de paquetes disponibles...", + "updating_apt_cache": "Obteniendo las actualizaciones disponibles para los paquetes del sistema…", "upgrade_complete": "Actualización finalizada", - "upgrading_packages": "Actualizando paquetes...", + "upgrading_packages": "Actualizando paquetes…", "upnp_dev_not_found": "No se encontró ningún dispositivo UPnP", "upnp_disabled": "UPnP ha sido deshabilitado", "upnp_enabled": "UPnP ha sido habilitado", @@ -239,7 +239,7 @@ "yunohost_already_installed": "YunoHost ya está instalado", "yunohost_ca_creation_failed": "No se pudo crear el certificado de autoridad", "yunohost_configured": "YunoHost ha sido configurado", - "yunohost_installing": "Instalando YunoHost...", + "yunohost_installing": "Instalando YunoHost…", "yunohost_not_installed": "YunoHost no está instalado o ha habido errores en la instalación. Ejecute 'yunohost tools postinstall'", "ldap_init_failed_to_create_admin": "La inicialización de LDAP falló al crear el usuario administrador", "mailbox_used_space_dovecot_down": "El servicio de e-mail Dovecot debe estar funcionando si desea obtener el espacio utilizado por el buzón de correo", @@ -248,9 +248,9 @@ "certmanager_attempt_to_replace_valid_cert": "Está intentando sobrescribir un certificado correcto y válido para el dominio {domain:s}! (Use --force para omitir este mensaje)", "certmanager_domain_unknown": "Dominio desconocido {domain:s}", "certmanager_domain_cert_not_selfsigned": "El certificado para el dominio {domain:s} no es un certificado autofirmado. ¿Está seguro de que quiere reemplazarlo? (Use --force para omitir este mensaje)", - "certmanager_certificate_fetching_or_enabling_failed": "Parece que al habilitar el nuevo certificado para el dominio {domain:s} ha fallado de alguna manera...", + "certmanager_certificate_fetching_or_enabling_failed": "Suena como que habilitar el nuevo certificado para {domain:s} fallara de algún modo…", "certmanager_attempt_to_renew_nonLE_cert": "El certificado para el dominio {domain:s} no ha sido emitido por Let's Encrypt. ¡No se puede renovar automáticamente!", - "certmanager_attempt_to_renew_valid_cert": "El certificado para el dominio {domain:s} no está a punto de expirar! Utilice --force para omitir este mensaje", + "certmanager_attempt_to_renew_valid_cert": "¡El certificado para el dominio {domain:s} no está a punto de expirar! (Puede usar --force si sabe lo que está haciendo)", "certmanager_domain_http_not_working": "Parece que no se puede acceder al dominio {domain:s} a través de HTTP. Compruebe que la configuración del DNS y de nginx es correcta", "certmanager_error_no_A_record": "No se ha encontrado un registro DNS 'A' para el dominio {domain:s}. Debe hacer que su nombre de dominio apunte a su máquina para poder instalar un certificado Let's Encrypt. (Si sabe lo que está haciendo, use --no-checks para desactivar esas comprobaciones.)", "certmanager_domain_dns_ip_differs_from_public_ip": "El registro DNS 'A' para el dominio {domain:s} es diferente de la IP de este servidor. Si recientemente modificó su registro A, espere a que se propague (existen algunos controladores de propagación DNS disponibles en línea). (Si sabe lo que está haciendo, use --no-checks para desactivar esas comprobaciones.)", @@ -273,7 +273,7 @@ "certmanager_http_check_timeout": "Plazo expirado, el servidor no ha podido contactarse a si mismo a través de HTTP usando su dirección IP pública (dominio {domain:s} con ip {ip:s}). Puede ser debido a hairpinning o a una mala configuración del cortafuego/router al que está conectado su servidor.", "certmanager_couldnt_fetch_intermediate_cert": "Plazo expirado, no se ha podido descargar el certificado intermedio de Let's Encrypt. La instalación/renovación del certificado ha sido cancelada - vuelva a intentarlo más tarde.", "appslist_retrieve_bad_format": "El archivo obtenido para la lista de aplicaciones {appslist:s} no es válido", - "domain_hostname_failed": "Error al establecer nuevo nombre de host", + "domain_hostname_failed": "Error al establecer un nuevo nombre de host («hostname»). Esto podría causar problemas más tarde (no es seguro... podría ir bien).", "yunohost_ca_creation_success": "Se ha creado la autoridad de certificación local.", "app_already_installed_cant_change_url": "Esta aplicación ya está instalada. No se puede cambiar el URL únicamente mediante esta función. Compruebe si está disponible la opción 'app changeurl'.", "app_change_no_change_url_script": "La aplicacion {app_name:s} aún no permite cambiar su URL, es posible que deba actualizarla.", @@ -285,19 +285,19 @@ "app_already_up_to_date": "La aplicación {app:s} ya está actualizada", "appslist_name_already_tracked": "Ya existe una lista de aplicaciones registrada con el nombre {name:s}.", "appslist_url_already_tracked": "Ya existe una lista de aplicaciones registrada con el URL {url:s}.", - "appslist_migrating": "Migrando la lista de aplicaciones {appslist:s} ...", + "appslist_migrating": "Migrando la lista de aplicaciones {appslist:s}…", "appslist_could_not_migrate": "No se pudo migrar la lista de aplicaciones {appslist:s}! No se pudo analizar el URL ... El antiguo cronjob se ha mantenido en {bkp_file:s}.", "appslist_corrupted_json": "No se pudieron cargar las listas de aplicaciones. Parece que {filename:s} está dañado.", "invalid_url_format": "Formato de URL no válido", "app_upgrade_some_app_failed": "No se pudieron actualizar algunas aplicaciones", "app_make_default_location_already_used": "No puede hacer la aplicación '{app}' por defecto en el dominio {domain} dado que está siendo usado por otra aplicación '{other_app}'", - "app_upgrade_app_name": "Actualizando la aplicación {app}...", + "app_upgrade_app_name": "Actualizando la aplicación {app}…", "ask_path": "Camino", "backup_abstract_method": "Este método de backup no ha sido implementado aún", - "backup_applying_method_borg": "Enviando todos los ficheros al backup en el repositorio borg-backup...", - "backup_applying_method_copy": "Copiado todos los ficheros al backup...", - "backup_applying_method_custom": "Llamando el método de backup {method:s} ...", - "backup_applying_method_tar": "Creando el archivo tar de backup...", + "backup_applying_method_borg": "Enviando todos los archivos para la copia de seguridad al repositorio de borg-backup…", + "backup_applying_method_copy": "Copiando todos los archivos a la copia de seguridad…", + "backup_applying_method_custom": "Llamando al método de copia de seguridad personalizado «{method:s}»…", + "backup_applying_method_tar": "Creando el archivo tar de la copia de seguridad…", "backup_archive_mount_failed": "Fallo en el montado del archivo de backup", "backup_archive_system_part_not_available": "La parte del sistema {part:s} no está disponible en este backup", "backup_archive_writing_error": "No se pueden añadir archivos de backup en el archivo comprimido", @@ -305,7 +305,7 @@ "backup_borg_not_implemented": "Método de backup Borg no está implementado aún", "backup_cant_mount_uncompress_archive": "No se puede montar en modo solo lectura el directorio del archivo descomprimido", "backup_copying_to_organize_the_archive": "Copiando {size:s}MB para organizar el archivo", - "backup_couldnt_bind": "No puede enlazar {src:s} con {dest:s}", + "backup_couldnt_bind": "No puede enlazar {src:s} con {dest:s}.", "backup_csv_addition_failed": "No puede añadir archivos al backup en el archivo CSV", "backup_csv_creation_failed": "No se puede crear el archivo CSV necesario para futuras operaciones de restauración", "backup_custom_mount_error": "Fracaso del método de copia de seguridad personalizada en la etapa \"mount\"", @@ -323,5 +323,285 @@ "password_too_simple_1": "La contraseña debe tener al menos 8 caracteres de longitud", "password_too_simple_2": "La contraseña debe tener al menos 8 caracteres de longitud y contiene dígitos, mayúsculas y minúsculas", "password_too_simple_3": "La contraseña debe tener al menos 8 caracteres de longitud y contiene dígitos, mayúsculas, minúsculas y caracteres especiales", - "password_too_simple_4": "La contraseña debe tener al menos 12 caracteres de longitud y contiene dígitos, mayúsculas, minúsculas y caracteres especiales" + "password_too_simple_4": "La contraseña debe tener al menos 12 caracteres de longitud y contiene dígitos, mayúsculas, minúsculas y caracteres especiales", + "users_available": "Usuarios disponibles:", + "user_not_in_group": "Usuario {user:s} no está en el grupo {group:s}", + "user_already_in_group": "Usuario {user:} ya está en el grupo {group:s}", + "updating_app_lists": "Obteniendo actualizaciones disponibles para las aplicaciones…", + "update_apt_cache_warning": "Ocurrieron algunos errores durante la actualización de la caché de APT (gestor de paquetes de Debian). Aquí tiene un volcado de las líneas de sources.list que podría ayudarle a identificar las líneas problemáticas:\n{sourceslist}", + "update_apt_cache_failed": "No se puede actualizar la caché de APT (gestor de paquetes de Debian). Aquí tiene un volcado de las líneas de sources.list que podría ayudarle a identificar las líneas problemáticas:\n{sourceslist}", + "tools_upgrade_special_packages_completed": "¡Actualización de paquetes de YunoHost completada!\nPulse [Intro] para recuperar la línea de órdenes", + "tools_upgrade_special_packages_explanation": "Esta acción terminará pero la actualización especial real continuará en segundo plano. No inicie ninguna otra acción en su servidor en aproximadamente 10 minutos (dependiendo de la velocidad de su hardware). Una vez que esté hecho, podría tener que volver a iniciar sesión en la administración web. El registro de actualización estará disponible en Herramientas > Registro (en la administración web) o mediante «yunohost log list» (en la línea de órdenes).", + "tools_upgrade_special_packages": "Actualizando ahora paquetes «especiales» (relacionados con YunoHost)...", + "tools_upgrade_regular_packages_failed": "No se pueden actualizar los paquetes: {packages_list}", + "tools_upgrade_regular_packages": "Actualizando ahora paquetes «normales» (no relacionados con YunoHost)...", + "tools_upgrade_cant_unhold_critical_packages": "No se pueden liberar los paquetes críticos...", + "tools_upgrade_cant_hold_critical_packages": "No se pueden retener los paquetes críticos...", + "tools_upgrade_cant_both": "No se puede actualizar el sistema y las aplicaciones al mismo tiempo", + "tools_upgrade_at_least_one": "Especifique --apps O --system", + "tools_update_failed_to_app_fetchlist": "Error al actualizar la lista de aplicaciones de YunoHost porque: {error}", + "this_action_broke_dpkg": "Esta acción rompió dpkg/apt (los gestores de paquetes del sistema)... Puede tratar de solucionar este problema conectando mediante SSH y ejecutando `sudo dpkg --configure -a`.", + "system_groupname_exists": "El nombre de grupo ya existe en el grupo del sistema", + "service_reloaded_or_restarted": "El servicio «{service:s}» ha sido recargado o reiniciado", + "service_reload_or_restart_failed": "No se puede recargar o reiniciar el servicio «{service:s}»'\n\nRegistro de servicios reciente:{logs:s}", + "service_restarted": "El servicio «{service:s}» ha sido reiniciado", + "service_restart_failed": "No se puede reiniciar el servicio «{service:s}»'\n\nRegistro de servicios reciente:{logs:s}", + "service_reloaded": "El servicio «{service:s}» ha sido recargado", + "service_reload_failed": "No se puede recargar el servicio «{service:s}»'\n\nRegistro de servicios reciente:{logs:s}", + "service_regen_conf_is_deprecated": "¡«yunohost service regen-conf» está obsoleto! Use «yunohost tools regen-conf» en su lugar.", + "service_description_yunohost-firewall": "gestiona los puertos de conexiones abiertos y cerrados a los servicios", + "service_description_yunohost-api": "gestiona las interacciones entre la interfaz web de YunoHost y el sistema", + "service_description_ssh": "le permite conectar a su servidor remotamente mediante un terminal (protocolo SSH)", + "service_description_slapd": "almacena usuarios, dominios e información relacionada", + "service_description_rspamd": "filtra correo no deseado y otras características relacionadas con el correo", + "service_description_rmilter": "comprueba varios parámetros en el correo", + "service_description_redis-server": "una base de datos especializada usada para el acceso rápido de datos, cola de tareas y comunicación entre programas", + "service_description_postfix": "usado para enviar y recibir correos", + "service_description_php7.0-fpm": "ejecuta aplicaciones escritas en PHP con nginx", + "service_description_nslcd": "maneja la conexión del intérprete («shell») de usuario de YunoHost", + "service_description_nginx": "sirve o proporciona acceso a todos los sitios web alojados en su servidor", + "service_description_mysql": "almacena los datos de las aplicaciones (base de datos SQL)", + "service_description_metronome": "gestionar las cuentas XMPP de mensajería instantánea", + "service_description_glances": "supervisa la información del sistema en su servidor", + "service_description_fail2ban": "protege contra ataques de fuerza bruta y otra clase de ataques desde Internet", + "service_description_dovecot": "permite al cliente de correo acceder/traer correo (vía IMAP y POP3)", + "service_description_dnsmasq": "maneja la resolución de nombres de dominio (DNS)", + "service_description_avahi-daemon": "permite acceder a su servidor usando yunohost.local en su red local", + "server_reboot_confirm": "El servidor se reiniciará inmediatamente ¿está seguro? [{answers:s}]", + "server_reboot": "El servidor se reiniciará", + "server_shutdown_confirm": "El servidor se apagará inmediatamente ¿está seguro? [{answers:s}]", + "server_shutdown": "El servidor se apagará", + "root_password_replaced_by_admin_password": "Su contraseña de root ha sido sustituida por su contraseña de administración.", + "root_password_desynchronized": "La contraseña de administración ha sido cambiada pero ¡YunoHost no pudo propagar esto en la contraseña de root!", + "restore_system_part_failed": "No se puede restaurar la parte del sistema «{part:s}»", + "restore_removing_tmp_dir_failed": "No se puede eliminar un antiguo directorio temporal", + "restore_not_enough_disk_space": "Insuficiente espacio en disco (espacio libre: {free_space:d} B, espacio necesario: {needed_space:d} B, margen de seguridad: {margin:d} B)", + "restore_mounting_archive": "Montando archivo en «{path:s}»", + "restore_may_be_not_enough_disk_space": "Parece que su sistema no tiene suficiente espacio de disco libre (espacio libre: {free_space:d} B, espacio necesario: {needed_space:d} B, margen de seguridad: {margin:d} B)", + "restore_extracting": "Extrayendo los archivos necesarios para el archivo…", + "regenconf_pending_applying": "Aplicando la configuración pendiente para la categoría «{category}»…", + "regenconf_failed": "No se puede regenerar la configuración para la(s) categoría(s): {categories}", + "regenconf_dry_pending_applying": "Comprobando la configuración pendiente que habría sido aplicada para la categoría «{category}»…", + "regenconf_would_be_updated": "La configuración habría sido actualizada para la categoría «{category}»", + "regenconf_updated": "Ha sido actualizada la configuración para la categoría «{category}»", + "regenconf_up_to_date": "Ya está actualizada la configuración para la categoría «{category}»", + "regenconf_now_managed_by_yunohost": "El archivo de configuración «{conf}» está gestionado ahora por YunoHost (categoría {category}).", + "regenconf_file_updated": "El archivo de configuración «{conf}» ha sido actualizado", + "regenconf_file_removed": "El archivo de configuración «{conf}» ha sido eliminado", + "regenconf_file_remove_failed": "No se puede eliminar el archivo de configuración «{conf}»", + "regenconf_file_manually_removed": "El archivo de configuración «{conf}» ha sido eliminado manualmente y no se creará", + "regenconf_file_manually_modified": "El archivo de configuración «{conf}» ha sido modificado manualmente y no será actualizado", + "regenconf_file_kept_back": "Se espera que el archivo de configuración «{conf}» sea eliminado por regen-conf (categoría {category}) pero ha sido retenido.", + "regenconf_file_copy_failed": "No se puede copiar el nuevo archivo de configuración «{new}» a «{conf}»", + "regenconf_file_backed_up": "El archivo de configuración «{conf}» ha sido respaldado en «{backup}»", + "remove_user_of_group_not_allowed": "No tiene permiso para eliminar al usuario {user:s} en el grupo {group:s}", + "remove_main_permission_not_allowed": "No se permite eliminar el permiso principal", + "recommend_to_add_first_user": "La posinstalación ha terminado pero YunoHost necesita al menos un usuario para funcionar correctamente, debe añadir uno ejecutando «yunohost user create » o usando la interfaz de administración.", + "permission_update_nothing_to_do": "No hay permisos para actualizar", + "permission_updated": "Permiso «{permission:s}» para la aplicación {app:s} actualizado", + "permission_generated": "La base de datos de permisos se ha actualizado", + "permission_update_failed": "Actualización de permiso fallida", + "permission_name_not_valid": "Nombre de permiso «{permission:s}» no válido", + "permission_not_found": "Permiso «{permission:s}» no encontrado para la aplicación {app:s}", + "permission_deletion_failed": "Permiso «{permission:s}» para eliminar la aplicación «{app:s}» fallido", + "permission_deleted": "Eliminado el permiso «{permission:s}» para la aplicación {app:s}", + "permission_creation_failed": "Ha fallado la creación del permiso", + "permission_created": "Creado el permiso «{permission:s}» para la aplicación {app:s}", + "permission_already_exist": "El permiso «{permission:s}» para la aplicación {app:s} ya existe", + "permission_already_clear": "El permiso «{permission:s}» ya está definido para la aplicación {app:s}", + "pattern_password_app": "Las contraseñas no deben incluir los siguientes caracteres: {forbidden_chars}", + "need_define_permission_before": "Necesita redefinir los permisos ejecutando «yunohost user permission add -u USUARIO» antes de eliminar un grupo permitido", + "migrations_to_be_ran_manually": "La migración {id} hay que ejecutarla manualmente. Vaya a Herramientas > Migraciones en la web de administración o ejecute `yunohost tools migrations migrate`.", + "migrations_success_forward": "¡Migración {id} ejecutada correctamente!", + "migrations_skip_migration": "Omitiendo migración {id}…", + "migrations_running_forward": "Ejecutando migración {id}…", + "migrations_pending_cant_rerun": "Esas migraciones están aún pendientes así que no se pueden volver a ejecutar: {ids}", + "migrations_not_pending_cant_skip": "Esas migraciones no están pendientes así que no pueden ser omitidas: {ids}", + "migrations_no_such_migration": "No existe una migración llamada {id}", + "migrations_no_migrations_to_run": "No hay migraciones que ejecutar", + "migrations_need_to_accept_disclaimer": "Para ejecutar la migración {id} debe aceptar el siguiente descargo de responsabilidad:\n---\n{disclaimer}\n---\nSi acepta ejecutar la migración, vuelva a ejecutar la orden con la opción --accept-disclaimer.", + "migrations_must_provide_explicit_targets": "Necesita proporcionar objetivos explícitos al usar --skip o --force-rerun", + "migrations_migration_has_failed": "Migración {id} fallida, cancelando. Error: {exception}", + "migrations_loading_migration": "Cargando migración {id}…", + "migrations_list_conflict_pending_done": "No puede usar --previous y --done al mismo tiempo.", + "migrations_exclusive_options": "--auto, --skip, and --force-rerun son opciones excluyentes.", + "migrations_failed_to_load_migration": "Error al cargar la migración {id} : {error}", + "migrations_dependencies_not_satisfied": "No se puede ejecutar la migración {id} porque primero necesita ejecutar estas migraciones: {dependencies_id}", + "migrations_cant_reach_migration_file": "No se pueden acceder los archivos de migración en la ruta %s", + "migrations_already_ran": "Esas migraciones ya se han ejecutado: {ids}", + "migration_0011_update_LDAP_schema": "Actualizando el esquema de LDAP...", + "migration_0011_update_LDAP_database": "Actualizando la base de datos de LDAP...", + "migration_0011_rollback_success": "Revertido correctamente.", + "migration_0011_migration_failed_trying_to_rollback": "Migración fallida... intentando revertir el sistema.", + "migration_0011_migrate_permission": "Migrando permisos desde la configuración de las aplicaciones a LDAP...", + "migration_0011_LDAP_update_failed": "Actualización de LDAP fallida. Error: {error:s}", + "migration_0011_LDAP_config_dirty": "Parece que ha personalizado la configuración de LDAP. Para esta migración se necesita actualizar la configuración de LDAP.\nNecesita guardar su configuración actual, restaurar la configuración original con la orden «yunohost tools regen-conf -f» y reintentar después la migración", + "migration_0011_done": "Migración exitosa. Ahora puede gestionar los grupos de usuarios.", + "migration_0011_create_group": "Creando un grupo para cada usuario...", + "migration_0011_can_not_backup_before_migration": "Falló la copia de seguridad del sistema antes de la migración. Migración fallida. Error: {error:s}", + "migration_0011_backup_before_migration": "Creando un respaldo de la base de datos de LDAP y de la configuración de las aplicaciones antes de la migración real.", + "migration_0009_not_needed": "¿La migración ya ocurrió de algún modo? Omitiendo.", + "migration_0008_no_warning": "No se ha detectado ningún riesgo importante sobre la anulación de su configuración SSH ¡pero no existe una certeza absoluta ;)! Si permite a YunoHost anular su configuración actual, ejecute la migración. Por otra parte puede omitir la migración, aunque no se recomienda.", + "migration_0008_warning": "Si entiende esos avisos y permite a YunoHost anular su configuración actual, ejecute la migración. Por otra parte puede omitir la migración, aunque no se recomienda.", + "migration_0008_dsa": " - se desactivará la clave DSA. Así que podría tener que anular un aviso espeluznante de su cliente SSH y volver a comprobar la huella de su servidor;", + "migration_0008_root": " - no podrá conectarse como «root» a través de SSH. En su lugar debería usar el usuario de administración;", + "migration_0008_port": " - tendrá que conectarse usando el puerto 22 en vez de su actual puerto SSH personalizado. No dude en reconfigurarlo;", + "migration_0008_general_disclaimer": "Para mejorar la seguridad de su servidor, es recomendable permitir a YunoHost gestionar la configuración SSH. Su actual configuración SSH difiere de la configuración recomendada. Si permite a YunoHost reconfigurarla, la manera en la que conecta con su servidor a través de SSH cambiará en el siguiente modo:", + "migration_0007_cannot_restart": "No se puede reiniciar SSH después de intentar cancelar la migración número 6.", + "migration_0007_cancelled": "YunoHost no ha podido mejorar el modo en el que se gestiona su configuración de SSH.", + "migration_0006_disclaimer": "YunoHost espera ahora que las contraseñas de «admin» y «root» estén sincronizadas. Al ejecutar esta migración, su contraseña de «root» será reemplazada por la contraseña de administración.", + "migration_0005_not_enough_space": "No hay suficiente espacio libre disponible en {path} para ejecutar la migración en este momento:(.", + "migration_0005_postgresql_96_not_installed": "¿¡Se encontró postgresql 9.4 para ser instalado pero no postgresql 9.6!? Algo raro podría haber ocurrido en su sistema:(…", + "migration_0005_postgresql_94_not_installed": "Postgresql no estaba instalado en su sistema. ¡Nada que hacer!", + "migration_0003_modified_files": "Tenga en cuenta que se encontró que los siguientes archivos fueron modificados manualmente y podrían ser sobrescritos al final de la actualización: {manually_modified_files}", + "migration_0003_problematic_apps_warning": "Tenga en cuenta que se detectaron las siguientes aplicaciones instaladas posiblemente problemáticas. Parece que no fueron instaladas desde una lista de aplicaciones o no estaban etiquetadas como «funciona». Así que no hay garantía de que aún funcionen después de la actualización: {problematic_apps}", + "migration_0003_general_warning": "Tenga en cuenta que esta migración es una operación delicada. Aunque el equipo de YunoHost hizo todo lo posible para revisarla y probarla, la migración aún podría romper parte del sistema o de las aplicaciones.\n\nPor lo tanto le recomendamos que:\n - Realice una copia de seguridad de cualquier dato crítico o aplicación. Más información en https://yunohost.org/backup;\n - Tenga paciencia tras iniciar la migración: dependiendo de su conexión a internet y de su hardware, podría tardar unas cuantas horas hasta que todo se actualice.\n\nAdemás, el puerto para SMTP usado por los clientes de correo externos (como Thunderbird o K9-Mail) cambió de 465 (SSL/TLS) a 587 (STARTTLS). El antiguo puerto 465 se cerrará automáticamente y el nuevo puerto 587 se abrirá en el cortafuegos. ¡Todos los usuarios *tendrán* que adaptar la configuración de sus clientes de correo por lo tanto!", + "migration_0003_still_on_jessie_after_main_upgrade": "Algo fue mal durante la actualización principal: ¿¡el sistema está aún en Jessie!? Para investigar el problema, vea {log}:s…", + "migration_0003_system_not_fully_up_to_date": "Su sistema no está totalmente actualizado. Realice una actualización normal antes de ejecutar la migración a Stretch.", + "migration_0003_not_jessie": "¡La distribución de Debian actual no es Jessie!", + "migration_0003_yunohost_upgrade": "Iniciando la actualización del paquete «yunohost»… La migración finalizará pero la actualización real ocurrirá justo después. Después de que la operación esté completada, podría tener que reiniciar sesión en la administración web.", + "migration_0003_restoring_origin_nginx_conf": "Su archivo /etc/nginx/nginx.conf ha sido editado de algún modo. La migración lo devolverá a su estado original primero… El archivo anterior estará disponible como {backup_dest}.", + "migration_0003_fail2ban_upgrade": "Iniciando la actualización de «fail2ban»…", + "migration_0003_main_upgrade": "Iniciando la actualización principal…", + "migration_0003_patching_sources_list": "Corrigiendo «sources.lists»…", + "migration_0003_start": "Iniciando migración a Stretch. El registro estará disponible en {logfile}.", + "migration_description_0012_postgresql_password_to_md5_authentication": "Forzar a la autentificación de postgresql a usar md5 para las conexiones locales", + "migration_description_0011_setup_group_permission": "Configurar grupo de usuario y configurar permisos para aplicaciones y servicios", + "migration_description_0010_migrate_to_apps_json": "Eliminar la obsoleta «appslists» y usar la nueva lista unificada «apps.json»", + "migration_description_0009_decouple_regenconf_from_services": "Separar el mecanismo «regen-conf» de los servicios", + "migration_description_0008_ssh_conf_managed_by_yunohost_step2": "Permitir que la configuración de SSH la gestione YunoHost (paso 2, manual)", + "migration_description_0007_ssh_conf_managed_by_yunohost_step1": "Permitir que la configuración de SSH la gestione YunoHost (paso 1, automático)", + "migration_description_0006_sync_admin_and_root_passwords": "Sincronizar las contraseñas de «admin» y «root»", + "migration_description_0005_postgresql_9p4_to_9p6": "Migrar las bases de datos de postgresql 9.4 a 9.6", + "migration_description_0004_php5_to_php7_pools": "Reconfigurar los «pools» de PHP para usar PHP 7 en vez de 5", + "migration_description_0003_migrate_to_stretch": "Actualizar el sistema a Debian Stretch y YunoHost 3.0", + "migration_description_0002_migrate_to_tsig_sha256": "Mejorar la seguridad de la TSIG de dyndns usando SHA512 en vez de MD5", + "migration_description_0001_change_cert_group_to_sslcert": "Cambiar los permisos de grupo de certificados de «metronome» a «ssl-cert»", + "migrate_tsig_not_needed": "Parece que no usa un dominio dyndns ¡así que no es necesario migrar!", + "migrate_tsig_wait_4": "30 segundos…", + "migrate_tsig_wait_3": "1 min. …", + "migrate_tsig_wait_2": "2 min. …", + "migrate_tsig_wait": "Esperar 3 min. para que el servidor dyndns tenga en cuenta la nueva clave…", + "migrate_tsig_start": "Detectado algoritmo de clave insuficientemente seguro para la firma TSIG del dominio «{domain}», iniciando migración al más seguro hmac-sha512", + "migrate_tsig_failed": "Error al migrar el dominio de dyndns {domain} a hmac-sha512, revertiendo. Error: {error_code} - {error}", + "migrate_tsig_end": "Terminada la migración a hmac-sha512", + "mail_unavailable": "Esta dirección de correo está reservada y será asignada automáticamente al primer usuario", + "mailbox_disabled": "Mailbox desactivado para usuario {user:s}", + "log_tools_reboot": "Reiniciar el servidor", + "log_tools_shutdown": "Apagar el servidor", + "log_tools_upgrade": "Actualizar paquetes del sistema", + "log_tools_postinstall": "Posinstalación del servidor YunoHost", + "log_tools_migrations_migrate_forward": "Migrar hacia adelante", + "log_tools_maindomain": "Convertir «{}» en dominio principal", + "log_user_permission_remove": "Actualizar permiso «{}»", + "log_user_permission_add": "Actualizar permiso «{}»", + "log_user_update": "Actualizar información del usuario «{}»", + "log_user_group_update": "Actualizar grupo «{}»", + "log_user_group_delete": "Eliminar grupo «{}»", + "log_user_group_add": "Añadir grupo «{}»", + "log_user_delete": "Eliminar usuario «{}»", + "log_user_create": "Añadir usuario «{}»", + "log_regen_conf": "Regenerar la configuración del sistema «{}»", + "log_letsencrypt_cert_renew": "Renovar el certificado «{}» de Let's encrypt", + "log_selfsigned_cert_install": "Instalar certificado autofirmado en el dominio «{}»", + "log_permission_update": "Actualizar permiso «{}» para la aplicación «{}»", + "log_permission_remove": "Eliminar permiso «{}»", + "log_permission_add": "Añadir permiso «{}» para la aplicación «{}»", + "log_letsencrypt_cert_install": "Instalar certificado de Let's encrypt en el dominio «{}»", + "log_dyndns_update": "Actualizar la IP asociada con su subdominio de YunoHost «{}»", + "log_dyndns_subscribe": "Subscribirse a un subdomino de YunoHost «{}»", + "log_domain_remove": "Eliminar el dominio «{}» de la configuración del sistema", + "log_domain_add": "Añadir el dominio «{}» a la configuración del sistema", + "log_remove_on_failed_install": "Eliminar «{}» después de una instalación fallida", + "log_remove_on_failed_restore": "Eliminar «{}» después de una restauración fallida desde un archivo de respaldo", + "log_backup_restore_app": "Restaurar «{}» desde un archivo de respaldo", + "log_backup_restore_system": "Restaurar sistema desde un archivo de respaldo", + "log_available_on_yunopaste": "Este registro está ahora disponible vía {url}", + "log_app_makedefault": "Convertir «{}» en aplicación predeterminada", + "log_app_upgrade": "Actualizar la aplicación «{}»", + "log_app_remove": "Eliminar la aplicación «{}»", + "log_app_install": "Instalar la aplicación «{}»", + "log_app_change_url": "Cambiar la url de la aplicación «{}»", + "log_app_removelist": "Eliminar una lista de aplicaciones", + "log_app_fetchlist": "Añadir una lista de aplicaciones", + "log_app_clearaccess": "Eliminar todos los accesos a «{}»", + "log_app_removeaccess": "Eliminar acceso a «{}»", + "log_app_addaccess": "Añadir acceso a «{}»", + "log_operation_unit_unclosed_properly": "La unidad de operación no se ha cerrado correctamente", + "log_does_exists": "No existe ningún registro de actividades con el nombre «{log}», ejecute «yunohost log list» para ver todos los registros de actividades disponibles", + "log_help_to_get_failed_log": "¡La operación «{desc}» ha fallado! Para obtener ayuda, comparta el registro completo de esta operación ejecutando la orden «yunohost log display {name} --share»", + "log_link_to_failed_log": "¡La operación «{desc}» ha fallado! Para obtener ayuda, proporcione el registro completo de esta operación pulsando aquí", + "log_help_to_get_log": "Para ver el registro de la operación «{desc}», ejecute la orden «yunohost log display {name}»", + "log_link_to_log": "Registro completo de esta operación: «{desc}»", + "log_category_404": "La categoría de registro «{category}» no existe", + "log_corrupted_md_file": "El archivo de metadatos yaml asociado con el registro está dañado: «{md_file}\nError: {error}»", + "hook_json_return_error": "Error al leer la respuesta del gancho {path:s}. Error: {msg:s}. Contenido sin procesar: {raw_content}", + "group_update_failed": "Error en la actualización del grupo «{group}»", + "group_updated": "Grupo «{group}» actualizado", + "group_unknown": "Grupo {group:s} desconocido", + "group_info_failed": "Error en la información del grupo", + "group_deletion_not_allowed": "No se puede eliminar el grupo {group:s} manualmente.", + "group_deletion_failed": "Error al eliminar el grupo «{group}»", + "group_deleted": "Eliminado el grupo «{group}»", + "group_creation_failed": "Error al crear el grupo «{group}»", + "group_created": "Grupo «{group}» creado correctamente", + "group_name_already_exist": "El grupo {name:s} ya existe", + "group_already_disallowed": "El grupo '{group:s}' ya tiene desactivado el permiso «{permission:s}» para la aplicación «{app:s}»", + "group_already_allowed": "El grupo '{group:s}' ya tiene activado el permiso «{permission:s}» para la aplicación «{app:s}»", + "good_practices_about_admin_password": "Va a determinar una nueva contraseña de administración. La contraseña debería tener al menos 8 caracteres, aunque es una buena práctica usar contraseñas más extensas (esto es, una frase) y/o usar caracteres de varias clases (mayúsculas, minúsculas, números y caracteres especiales).", + "global_settings_unknown_type": "Situación imprevista, la configuración {setting:s} parece tener el tipo {unknown_type:s} pero no es un tipo compatible con el sistema.", + "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Permitir el uso de la llave (obsoleta) DSA para la configuración del demonio SSH", + "global_settings_unknown_setting_from_settings_file": "Clave desconocida en la configuración: «{setting_key:s}», desechada y guardada en /etc/yunohost/settings-unknown.json", + "global_settings_setting_security_postfix_compatibility": "Compromiso entre compatibilidad y seguridad para el servidor Postfix. Afecta al cifrado (y otros aspectos relacionados con la seguridad)", + "global_settings_setting_security_ssh_compatibility": "Compromiso entre compatibilidad y seguridad para el servidor SSH. Afecta al cifrado (y otros aspectos relacionados con la seguridad)", + "global_settings_setting_security_password_user_strength": "Seguridad de la contraseña de usuario", + "global_settings_setting_security_password_admin_strength": "Seguridad de la contraseña del administrador", + "global_settings_setting_security_nginx_compatibility": "Compromiso entre compatibilidad y seguridad para el servidor web nginx. Afecta al cifrado (y otros aspectos relacionados con la seguridad)", + "global_settings_setting_example_string": "Ejemplo de opción de cadena", + "global_settings_setting_example_int": "Ejemplo de opción «int»", + "global_settings_setting_example_enum": "Ejemplo de opción «enum»", + "global_settings_setting_example_bool": "Ejemplo de opción booleana", + "global_settings_reset_success": "Éxito. Se ha respaldado su configuración previa en {path:s}", + "global_settings_key_doesnt_exists": "La clave «{settings_key:s}» no existe en la configuración global, puede ver todas las claves disponibles ejecutando «yunohost settings list»", + "global_settings_cant_write_settings": "Error al escribir el archivo de configuración, motivo: {reason:s}", + "global_settings_cant_serialize_settings": "Error al seriar los datos de configuración, motivo: {reason:s}", + "global_settings_cant_open_settings": "Error al abrir el archivo de configuración, motivo: {reason:s}", + "global_settings_bad_type_for_setting": "Tipo erróneo para la configuración {setting:s}, obtuvo {received_type:s}, excepto {expected_type:s}", + "global_settings_bad_choice_for_enum": "Mala elección para la configuración {setting:s}, obtuvo «{choice:s}» pero las opciones disponibles son: {available_choices:s}", + "file_does_not_exist": "El archivo {path:s} no existe.", + "error_when_removing_sftpuser_group": "Error al probar «remove sftpusers group»", + "edit_permission_with_group_all_users_not_allowed": "No puede editar el permiso para el grupo «all_users», utilice «yunohost user permission clear APLICACIÓN» o «yunohost user permission add APLICACIÓN -u USUARIO».", + "edit_group_not_allowed": "No tiene permiso para editar el grupo {group:s}", + "dyndns_could_not_check_available": "No se pudo comprobar si {domain:s} está disponible en {provider:s}.", + "domain_dyndns_dynette_is_unreachable": "No se pudo conectar al dynette de YunoHost, o su YunoHost no está correctamente conectado a internet o el servidor dynette está caído. Error: {error}", + "domain_dns_conf_is_just_a_recommendation": "Esta orden muestra cuál es la configuración *recomendada*. No configura el DNS. Es su responsabilidad configurar la zona de DNS en su registrador según esta recomendación.", + "dpkg_lock_not_available": "Esta orden no se puede ejecutar en este momento porque otro programa parece que está usando el bloqueo de dpkg (el gestor de paquetes del sistema)", + "dpkg_is_broken": "No puede hacer esto en este momento porque dpkg/apt (los gestores de paquetes del sistema) parecen estar en un estado roto... Puede tratar de solucionar este problema conectando a través de SSH y ejecutando `sudo dpkg --configure -a`.", + "confirm_app_install_thirdparty": "¡AVISO! Instalar aplicaciones de terceros podría comprometer la integridad y seguridad de su sistema. Probablemente NO debería instalarlas salvo que sepa lo que está haciendo. ¿Está dispuesto a correr ese riesgo? [{answers:s}] ", + "confirm_app_install_danger": "¡AVISO! Esta aplicación es aún experimental (si no está funcionando expresamente) y ¡es probable que rompa su sistema! Probablemente NO debería instalarla salvo que sepa lo que está haciendo. ¿Está dispuesto a correr ese riesgo? [{answers:s}] ", + "confirm_app_install_warning": "Aviso: esta aplicación puede funcionar pero no está bien integrada en YunoHost. Algunas herramientas como la autentificación única y respaldo/restauración podrían no estar disponibles. ¿Instalar de todos modos? [{answers:s}] ", + "backup_unable_to_organize_files": "No se pueden organizar los archivos en el archivo con el método rápido", + "backup_permission": "Permiso de respaldo para la aplicación {app:s}", + "backup_output_symlink_dir_broken": "Tiene un enlace simbólico roto en vez del directorio «{path:s}» de sus archivos. Puede que tenga una configuración específica para respaldar sus datos en otro sistema de archivos, en este caso probablemente olvidó remontar o conectar su disco duro o clave usb.", + "backup_mount_archive_for_restore": "Preparando el archivo para la restauración…", + "backup_method_tar_finished": "Creado el archivo de respaldo tar", + "backup_method_custom_finished": "Terminado el método «{method:s}» de respaldo personalizado", + "backup_method_copy_finished": "Terminada la copia de seguridad", + "backup_method_borg_finished": "Terminado el respaldo en borg", + "backup_custom_backup_error": "Fallo del método de respaldo personalizado en el paso «copia de seguridad»", + "backup_actually_backuping": "Creando un archivo de respaldo de los archivos obtenidos…", + "ask_new_path": "Nueva ruta", + "ask_new_domain": "Nuevo dominio", + "apps_permission_restoration_failed": "El permiso «{permission:s}» para la restauración de la aplicación {app:s} ha fallado", + "apps_permission_not_found": "No se han encontrado permisos para las aplicaciones instaladas", + "app_upgrade_several_apps": "Las siguientes aplicaciones se actualizarán: {apps}", + "app_start_restore": "Restaurando aplicación {app}…", + "app_start_backup": "Obteniendo archivos de respaldo para {app}…", + "app_start_remove": "Eliminando aplicación {app}…", + "app_start_install": "Instalando aplicación {app}…", + "app_not_upgraded": "Las siguientes aplicaciones no se actualizaron: {apps}", + "app_action_cannot_be_ran_because_required_services_down": "Esta aplicación necesita algunos servicios que no están funcionando ahora. Antes de continuar, debería intentar reiniciar los siguientes servicios (y posiblemente investigar por qué no funcionan): {services}", + "already_up_to_date": "¡Nada que hacer! ¡Todo está actualizado!", + "admin_password_too_long": "Elija una contraseña de menos de 127 caracteres", + "aborting": "Cancelando." } From d11c76713a08c3174afb40b35d8834aa4643c408 Mon Sep 17 00:00:00 2001 From: ButterflyOfFire Date: Tue, 17 Sep 2019 19:02:30 +0000 Subject: [PATCH 0144/3170] Translated using Weblate (Arabic) Currently translated at 66.1% (386 of 584 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ar/ --- locales/ar.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/ar.json b/locales/ar.json index b6f3ece23..46f9315af 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -442,5 +442,6 @@ "error_when_removing_sftpuser_group": "حدث خطأ أثناء محاولة حذف فريق sftpusers", "dyndns_could_not_check_available": "لا يمكن التحقق مِن أنّ {domain:s} متوفر على {provider:s}.", "backup_mount_archive_for_restore": "جارٍ تهيئة النسخة الاحتياطية للاسترجاع…", - "root_password_replaced_by_admin_password": "لقد تم استبدال كلمة سر الجذر root بالكلمة الإدارية لـ admin." + "root_password_replaced_by_admin_password": "لقد تم استبدال كلمة سر الجذر root بالكلمة الإدارية لـ admin.", + "app_upgrade_stopped": "لقد تم إلغاء تحديث كافة التطبيقات لتجنب حادث بسبب فشل تحديث التطبيق السابق" } From abe294079e9dfd838fc7e828ca91305410e8b3c5 Mon Sep 17 00:00:00 2001 From: advocatux Date: Tue, 17 Sep 2019 19:02:19 +0000 Subject: [PATCH 0145/3170] Translated using Weblate (Spanish) Currently translated at 100.0% (584 of 584 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/es/ --- locales/es.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/locales/es.json b/locales/es.json index c34a7e33c..d028a7a8e 100644 --- a/locales/es.json +++ b/locales/es.json @@ -599,9 +599,10 @@ "app_start_backup": "Obteniendo archivos de respaldo para {app}…", "app_start_remove": "Eliminando aplicación {app}…", "app_start_install": "Instalando aplicación {app}…", - "app_not_upgraded": "Las siguientes aplicaciones no se actualizaron: {apps}", + "app_not_upgraded": "Error al actualizar la aplicación «{failed_app}» y como consecuencia se han cancelado las actualizaciones de las siguientes aplicaciones: {apps}", "app_action_cannot_be_ran_because_required_services_down": "Esta aplicación necesita algunos servicios que no están funcionando ahora. Antes de continuar, debería intentar reiniciar los siguientes servicios (y posiblemente investigar por qué no funcionan): {services}", "already_up_to_date": "¡Nada que hacer! ¡Todo está actualizado!", "admin_password_too_long": "Elija una contraseña de menos de 127 caracteres", - "aborting": "Cancelando." + "aborting": "Cancelando.", + "app_upgrade_stopped": "Se ha detenido la actualización de todas las aplicaciones para prevenir un posible daño porque la aplicación anterior no se pudo actualizar" } From 64e388fa7d952690c3f35d8b13f67d52869bb383 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 17 Sep 2019 23:38:17 +0200 Subject: [PATCH 0146/3170] Implement helper function to test if we're able to access a webpage being logged in (or not) as user --- src/yunohost/tests/test_permission.py | 56 ++++++++++++++++++++------- 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index 1c81e015f..51bf6a4c6 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -1,3 +1,4 @@ +import requests import pytest from yunohost.app import app_install, app_remove, app_change_url, app_list, app_map @@ -11,6 +12,7 @@ from yunohost.utils.error import YunohostError # Get main domain maindomain = _get_maindomain() +dummy_password = "test123Ynh" def clean_user_groups_permission(): for u in user_list()['users']: @@ -27,8 +29,8 @@ def clean_user_groups_permission(): def setup_function(function): clean_user_groups_permission() - user_create("alice", "Alice", "White", "alice@" + maindomain, "test123Ynh") - user_create("bob", "Bob", "Snow", "bob@" + maindomain, "test123Ynh") + user_create("alice", "Alice", "White", "alice@" + maindomain, dummy_password) + user_create("bob", "Bob", "Snow", "bob@" + maindomain, dummy_password) permission_create("wiki.main", urls=[maindomain + "/wiki"], sync_perm=False) permission_create("blog.main", sync_perm=False) user_permission_update("blog.main", remove="all_users", add="alice") @@ -156,6 +158,30 @@ def check_permission_for_apps(): assert installed_apps == app_perms_prefix + +def can_access_webpage(webpath, logged_as=None): + + webpath = webpath.rstrip("/") + webroot = webpath.rsplit("/", 1)[0] + sso_url = webroot+"/yunohost/sso" + + # Anonymous access + if not logged_as: + r = requests.get(webpath, verify=False) + # Login as a user using dummy password + else: + with requests.Session() as session: + session.post(sso_url, + data={"user": logged_as, + "password": dummy_password}, + headers={"Referer": sso_url, + "Content-Type": "application/x-www-form-urlencoded"}, + verify=False) + r = session.get(webpath, verify=False) + + # If we can't access it, we got redirected to the sso + return not r.url.startswith(sso_url) + # # List functions # @@ -396,21 +422,21 @@ def test_permission_app_propagation_on_ssowat(): res = user_permission_list(full=True)['permissions'] assert res['permissions_app.main']['allowed'] == ["all_users"] - assert can_access(maindomain + "/urlpermissionapp", logged_as=None) - assert can_access(maindomain + "/urlpermissionapp", logged_as="alice") + assert can_access_webpage(maindomain + "/urlpermissionapp", logged_as=None) + assert can_access_webpage(maindomain + "/urlpermissionapp", logged_as="alice") user_permission_update("permissions_app.main", remove="visitors", add="bob") res = user_permission_list(full=True)['permissions'] - assert cannot_access(maindomain + "/urlpermissionapp", logged_as=None) - assert cannot_access(maindomain + "/urlpermissionapp", logged_as="alice") - assert can_access(maindomain + "/urlpermissionapp", logged_as="bob") + assert not can_access_webpage(maindomain + "/urlpermissionapp", logged_as=None) + assert not can_access_webpage(maindomain + "/urlpermissionapp", logged_as="alice") + assert can_access_webpage(maindomain + "/urlpermissionapp", logged_as="bob") # Test admin access, as configured during install, only alice should be able to access it - assert cannot_access(maindomain + "/urlpermissionapp/admin", logged_as=None) - assert cannot_access(maindomain + "/urlpermissionapp/admin", logged_as="alice") - assert can_access(maindomain + "/urlpermissionapp/admin", logged_as="bob") + assert not can_access_webpage(maindomain + "/urlpermissionapp/admin", logged_as=None) + assert not can_access_webpage(maindomain + "/urlpermissionapp/admin", logged_as="alice") + assert can_access_webpage(maindomain + "/urlpermissionapp/admin", logged_as="bob") def test_permission_legacy_app_propagation_on_ssowat(): @@ -424,13 +450,13 @@ def test_permission_legacy_app_propagation_on_ssowat(): # It should automatically be migrated during the install assert res['permissions_app.main']['allowed'] == ["visitors"] - assert can_access(maindomain + "/legacy", logged_as=None) - assert can_access(maindomain + "/legacy", logged_as="alice") + assert can_access_webpage(maindomain + "/legacy", logged_as=None) + assert can_access_webpage(maindomain + "/legacy", logged_as="alice") # Try to update the permission and check that permissions are still consistent user_permission_update("legacy_app.main", remove="visitors", add="bob") res = user_permission_list(full=True)['permissions'] - assert cannot_access(maindomain + "/legacy", logged_as=None) - assert cannot_access(maindomain + "/legacy", logged_as="alice") - assert can_access(maindomain + "/legacy", logged_as="bob") + assert not can_access_webpage(maindomain + "/legacy", logged_as=None) + assert not can_access_webpage(maindomain + "/legacy", logged_as="alice") + assert can_access_webpage(maindomain + "/legacy", logged_as="bob") From 62e74d937e2a271ad1d56f70b00ce6e4be3b33d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Wed, 18 Sep 2019 02:01:20 +0200 Subject: [PATCH 0147/3170] Could not mount Co-Authored-By: decentral1se --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index cab2afff8..da5da0789 100644 --- a/locales/en.json +++ b/locales/en.json @@ -82,7 +82,7 @@ "backup_applying_method_tar": "Creating the backup TAR archive…", "backup_archive_app_not_found": "Could not find the app '{app:s}' in the backup archive", "backup_archive_broken_link": "Could not access the backup archive (broken link to {path:s})", - "backup_archive_mount_failed": "Could not mounting the backup archive", + "backup_archive_mount_failed": "Could not mount the backup archive", "backup_archive_name_exists": "A backup archive with this name already exists.", "backup_archive_name_unknown": "Unknown local backup archive named '{name:s}'", "backup_archive_open_failed": "Could not open the backup archive", From 2e05a04cb945b21a677e36292b963502b4936eb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Wed, 18 Sep 2019 02:04:57 +0200 Subject: [PATCH 0148/3170] for backup Co-Authored-By: decentral1se --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index da5da0789..29c5e0885 100644 --- a/locales/en.json +++ b/locales/en.json @@ -88,7 +88,7 @@ "backup_archive_open_failed": "Could not open the backup archive", "backup_archive_system_part_not_available": "System part '{part:s}' unavailable in this backup", "backup_archive_writing_error": "Could not add the add files '{source:s}' (named in the archive: '{dest:s}') to be backed up into the compressed archive '{archive:s}'", - "backup_ask_for_copying_if_needed": "Some files could not be prepared for bacup using the method that avoids temporarily wasting space on the system. To perform the backup, {size:s}MB will be temporarily. Do you agree?", + "backup_ask_for_copying_if_needed": "Some files could not be prepared for backup using the method that avoids temporarily wasting space on the system. To perform the backup, {size:s}MB will be temporarily. Do you agree?", "backup_borg_not_implemented": "The Borg backup method is not yet implemented", "backup_cant_mount_uncompress_archive": "Could not mount the uncompressed archive write protected", "backup_cleaning_failed": "Could not clean-up the temporary backup folder", From fa42efe557702a67db1b27bb87a9966760448623 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Wed, 18 Sep 2019 02:08:01 +0200 Subject: [PATCH 0149/3170] The Dovecot Co-Authored-By: decentral1se --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 29c5e0885..77192e069 100644 --- a/locales/en.json +++ b/locales/en.json @@ -307,7 +307,7 @@ "mail_domain_unknown": "Unknown e-mail address for domain '{domain:s}'", "mail_forward_remove_failed": "Could not remove e-mail forwarding '{mail:s}'", "mailbox_disabled": "E-mail turned off for user {user:s}", - "mailbox_used_space_dovecot_down": "the Dovecot mailbox service needs to be up, if you want to fetch used mailbox space", + "mailbox_used_space_dovecot_down": "The Dovecot mailbox service needs to be up, if you want to fetch used mailbox space", "mail_unavailable": "This e-mail address is reserved and shall be automatically allocated to the very first user", "maindomain_change_failed": "Could not change the main domain", "maindomain_changed": "The main domain now changed", From adf2245c204f38807b49e43550063683e394926a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Wed, 18 Sep 2019 02:08:45 +0200 Subject: [PATCH 0150/3170] have a Co-Authored-By: decentral1se --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 77192e069..e1cc95e5a 100644 --- a/locales/en.json +++ b/locales/en.json @@ -424,7 +424,7 @@ "pattern_firstname": "Must be a valid first name", "pattern_lastname": "Must be a valid last name", "pattern_listname": "Must be alphanumeric and underscore characters only", - "pattern_mailbox_quota": "Must be a size with b/k/M/G/T suffix or 0 to not havea quota", + "pattern_mailbox_quota": "Must be a size with b/k/M/G/T suffix or 0 to not have a quota", "pattern_password": "Must be at least 3 characters long", "pattern_port": "Must be a valid port number (i.e. 0-65535)", "pattern_port_or_range": "Must be a valid port number (i.e. 0-65535) or range of ports (e.g. 100:200)", From 01fa0939fd65a88d6652e26f215b91645e1c50e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Wed, 18 Sep 2019 02:13:43 +0200 Subject: [PATCH 0151/3170] Spelling: exists, 'yunohost log list' --- locales/en.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/en.json b/locales/en.json index e1cc95e5a..dc411c054 100644 --- a/locales/en.json +++ b/locales/en.json @@ -232,7 +232,7 @@ "good_practices_about_user_password": "You are now about to define a new user password. The password should be at least 8 characters—though it is good practice to use longer password (i.e. a passphrase) and/or to a variation of characters (uppercase, lowercase, digits and special characters).", "group_already_allowed": "Group '{group:s}' already has permission '{permission:s}' turned on for the app '{app:s}'", "group_already_disallowed": "Group '{group:s}' already has permissions '{permission:s}' turned off for the app '{app:s}'", - "group_name_already_exist": "Group {name:s} already exist", + "group_name_already_exist": "Group {name:s} already exists", "group_created": "Group '{group}' created", "group_creation_failed": "Could not create the group '{group}'", "group_deleted": "Group '{group}' deleted", @@ -258,7 +258,7 @@ "log_help_to_get_log": "To view the log of the operation '{desc}', use the command 'yunohost log display {name}'", "log_link_to_failed_log": "Could not complete the operation '{desc}'. Please provide the full log of this operation by clicking here to get help", "log_help_to_get_failed_log": "The operation '{desc}' could not be completed. Please share the full log of this operation using the command 'yunohost log display {name} --share' to get help", - "log_does_exists": "There is not operation log with the name '{log}', use 'yunohost log list to see all available operation logs'", + "log_does_exists": "There is not operation log with the name '{log}', use 'yunohost log list' to see all available operation logs", "log_operation_unit_unclosed_properly": "Operation unit has not been closed properly", "log_app_addaccess": "Add access to '{}'", "log_app_removeaccess": "Remove access to '{}'", From 00795a7a0156d5d45aeabb63819ab3d6511270e5 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 18 Sep 2019 18:38:47 +0200 Subject: [PATCH 0152/3170] Make migration re-run even more robust --- src/yunohost/data_migrations/0011_setup_group_permission.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/yunohost/data_migrations/0011_setup_group_permission.py b/src/yunohost/data_migrations/0011_setup_group_permission.py index c79d80e0c..a99dfb7c1 100644 --- a/src/yunohost/data_migrations/0011_setup_group_permission.py +++ b/src/yunohost/data_migrations/0011_setup_group_permission.py @@ -62,12 +62,14 @@ class MyMigration(Migration): try: self.remove_if_exists("cn=sftpusers,ou=groups") self.remove_if_exists("ou=permission") - self.remove_if_exists('cn=all_users,ou=groups') - self.remove_if_exists('cn=visitors,ou=groups') + self.remove_if_exists('ou=groups') attr_dict = ldap_map['parents']['ou=permission'] ldap.add('ou=permission', attr_dict) + attr_dict = ldap_map['parents']['ou=groups'] + ldap.add('ou=groups', attr_dict) + attr_dict = ldap_map['children']['cn=all_users,ou=groups'] ldap.add('cn=all_users,ou=groups', attr_dict) From 8d01a816f3cd0fd07f570cd2d9f290d01f100ed9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 18 Sep 2019 18:39:05 +0200 Subject: [PATCH 0153/3170] Typo fixes following tests --- src/yunohost/app.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 537616e68..7938d6786 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1404,8 +1404,8 @@ def app_ssowatconf(): unprotected_regex += _get_setting(app_settings, 'protected_regex') # New permission system - this_app_perms = {name: info for name, info in all_permissions.items if name.startswith(app + ".")} - for perm_name, perm_info in this_app_perms: + this_app_perms = {name: info for name, info in all_permissions.items() if name.startswith(app['id'] + ".")} + for perm_name, perm_info in this_app_perms.items(): urls = [url.rstrip("/") for url in perm_info["urls"]] if "visitors" in perm_info["allowed"]: unprotected_urls += urls @@ -1414,6 +1414,7 @@ def app_ssowatconf(): protected_urls = [u for u in protected_urls if u not in urls] else: # TODO : small optimization to implement : we don't need to explictly add all the app roots + protected_urls += urls # Legacy stuff : we remove now unprotected-urls that might have been declared as protected earlier... From 6d0bf43aef152c6f5b4f4ff8f4d505aa37ef7819 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Thu, 19 Sep 2019 03:51:24 +0200 Subject: [PATCH 0154/3170] Spelling: add the files, as write protected, trying to use --- locales/en.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/locales/en.json b/locales/en.json index dc411c054..54bf5eaba 100644 --- a/locales/en.json +++ b/locales/en.json @@ -87,10 +87,10 @@ "backup_archive_name_unknown": "Unknown local backup archive named '{name:s}'", "backup_archive_open_failed": "Could not open the backup archive", "backup_archive_system_part_not_available": "System part '{part:s}' unavailable in this backup", - "backup_archive_writing_error": "Could not add the add files '{source:s}' (named in the archive: '{dest:s}') to be backed up into the compressed archive '{archive:s}'", + "backup_archive_writing_error": "Could not add the files '{source:s}' (named in the archive '{dest:s}') to be backed up into the compressed archive '{archive:s}'", "backup_ask_for_copying_if_needed": "Some files could not be prepared for backup using the method that avoids temporarily wasting space on the system. To perform the backup, {size:s}MB will be temporarily. Do you agree?", "backup_borg_not_implemented": "The Borg backup method is not yet implemented", - "backup_cant_mount_uncompress_archive": "Could not mount the uncompressed archive write protected", + "backup_cant_mount_uncompress_archive": "Could not mount the uncompressed archive as write protected", "backup_cleaning_failed": "Could not clean-up the temporary backup folder", "backup_copying_to_organize_the_archive": "Copying {size:s}MB to organize the archive", "backup_couldnt_bind": "Could not bind {src:s} to {dest:s}.", @@ -134,7 +134,7 @@ "certmanager_cert_install_success_selfsigned": "Self-signed certificate now installed for the domain '{domain:s}'", "certmanager_cert_renew_success": "Let's Encrypt certificate renewed for the domain '{domain:s}'", "certmanager_cert_signing_failed": "Could not sign the new certificate", - "certmanager_certificate_fetching_or_enabling_failed": "It seems actually using the new certificate for {domain:s} did not work…", + "certmanager_certificate_fetching_or_enabling_failed": "Trying to use the new certificate for {domain:s} did not work…", "certmanager_conflicting_nginx_file": "Could not prepare domain for ACME challenge: the NGINX configuration file {filepath:s} is conflicting and should be removed first", "certmanager_couldnt_fetch_intermediate_cert": "Timed out when trying to fetch intermediate certificate from Let's Encrypt. Certificate installation/renewal aborted—please try again later.", "certmanager_domain_cert_not_selfsigned": "The certificate for domain {domain:s} is not self-signed. Are you sure you want to replace it? (Use '--force' to do so.)", From 35df93bb865b5202deaa757678ae066a2d0b7c9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Thu, 19 Sep 2019 03:54:40 +0200 Subject: [PATCH 0155/3170] Spelling: user info --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 54bf5eaba..2f4e1aadc 100644 --- a/locales/en.json +++ b/locales/en.json @@ -291,7 +291,7 @@ "log_user_group_add": "Add '{}' group", "log_user_group_delete": "Delete '{}' group", "log_user_group_update": "Update '{}' group", - "log_user_update": "Update info of '{}' user", + "log_user_update": "Update user info of '{}'", "log_user_permission_add": "Update '{}' permission", "log_user_permission_remove": "Update '{}' permission", "log_tools_maindomain": "Make '{}' the main domain", From f18cff9dbaa1f1a8449bae114a1577a9a299a5bf Mon Sep 17 00:00:00 2001 From: "J. Doe" Date: Thu, 19 Sep 2019 13:01:22 +0200 Subject: [PATCH 0156/3170] change maxretry of fail2ban from 6 to 10 --- data/templates/fail2ban/yunohost-jails.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/templates/fail2ban/yunohost-jails.conf b/data/templates/fail2ban/yunohost-jails.conf index bf3bcb6e3..fdbd7990b 100644 --- a/data/templates/fail2ban/yunohost-jails.conf +++ b/data/templates/fail2ban/yunohost-jails.conf @@ -29,4 +29,4 @@ protocol = tcp filter = yunohost logpath = /var/log/nginx/*error.log /var/log/nginx/*access.log -maxretry = 6 +maxretry = 10 From 7903fb27f7ffcdd8dc368509fae899357f865a65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Thu, 19 Sep 2019 19:10:09 +0200 Subject: [PATCH 0157/3170] All applications are already Co-Authored-By: Alexandre Aubin --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 2f4e1aadc..7f68ce3d5 100644 --- a/locales/en.json +++ b/locales/en.json @@ -27,7 +27,7 @@ "app_location_install_failed": "Cannot install the app there because it conflicts with the app '{other_app}' already installed in '{other_path}'", "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_no_upgrade": "Everything is up-to-date", + "app_no_upgrade": "All applications are already up-to-date", "app_not_upgraded": "These apps were not upgraded: {apps}", "app_not_correctly_installed": "{app:s} seems to be incorrectly installed", "app_not_installed": "The application '{app:s}' is not to be found among all installed apps: {all_apps}", From 98236ac839fe78d1596f3e4f7415cff4989d4f4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Thu, 19 Sep 2019 19:11:27 +0200 Subject: [PATCH 0158/3170] Some requirements are not met Co-Authored-By: Alexandre Aubin --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 7f68ce3d5..35656cfd9 100644 --- a/locales/en.json +++ b/locales/en.json @@ -35,7 +35,7 @@ "app_package_need_update": "The app {app} package needs to be updated to follow YunoHost changes", "app_removed": "{app:s} removed", "app_requirements_checking": "Checking required packages for {app}…", - "app_requirements_failed": "Did not meet requirements for {app}: {error}", + "app_requirements_failed": "Some requirements are not met for {app}: {error}", "app_requirements_unmeet": "Requirements are not met for {app}, the package {pkgname} ({version}) must be {spec}", "app_sources_fetch_failed": "Could not fetch sources files, is the URL correct?", "app_start_install": "Installing application {app}…", From a8337f1bfd178d04c946428a3823af8b5886d19b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Thu, 19 Sep 2019 19:12:33 +0200 Subject: [PATCH 0159/3170] Allows you to reach Co-Authored-By: Alexandre Aubin --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 35656cfd9..15c8f5e4b 100644 --- a/locales/en.json +++ b/locales/en.json @@ -493,7 +493,7 @@ "service_already_started": "The service '{service:s}' has already been started", "service_already_stopped": "The service '{service:s}' has already been stopped", "service_cmd_exec_failed": "Could not execute the command '{command:s}'", - "service_description_avahi-daemon": "allows you to reach your server using 'yunohost.local' in your local network", + "service_description_avahi-daemon": "Allows you to reach your server using 'yunohost.local' in your local network", "service_description_dnsmasq": "Handles domain name resolution (DNS)", "service_description_dovecot": "Allows e-mail clients to access/fetch email (via IMAP and POP3)", "service_description_fail2ban": "Protects against brute-force and other kinds of attacks from the Internet", From 8ad01c6093eec38915b09c9362133cdac97d6e8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Thu, 19 Sep 2019 19:12:50 +0200 Subject: [PATCH 0160/3170] Runs applications Co-Authored-By: Alexandre Aubin --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 15c8f5e4b..e4c41fc5b 100644 --- a/locales/en.json +++ b/locales/en.json @@ -502,7 +502,7 @@ "service_description_mysql": "Stores applications data (SQL database)", "service_description_nginx": "Serves or provides access to all the websites hosted on your server", "service_description_nslcd": "Handles YunoHost user shell connection", - "service_description_php7.0-fpm": "runs applications written in PHP with NGINX", + "service_description_php7.0-fpm": "Runs applications written in PHP with NGINX", "service_description_postfix": "Used to send and receive e-mails", "service_description_redis-server": "A specialized database used for rapid data access, task queue, and communication between programs", "service_description_rmilter": "Checks various parameters in e-mails", From 929421f81559deaacd861dfe0d60411816a884bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Thu, 19 Sep 2019 19:13:30 +0200 Subject: [PATCH 0161/3170] Migration {id} completed Co-Authored-By: Alexandre Aubin --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index e4c41fc5b..03a750aa5 100644 --- a/locales/en.json +++ b/locales/en.json @@ -383,7 +383,7 @@ "migrations_pending_cant_rerun": "Those migrations are still pending, so cannot be run again: {ids}", "migrations_running_forward": "Running migration {id}…", "migrations_skip_migration": "Skipping migration {id}…", - "migrations_success_forward": "Ran migration {id}", + "migrations_success_forward": "Migration {id} completed", "migrations_to_be_ran_manually": "Migration {id} has to be run manually. Please go to Tools → Migrations on the webadmin page, or run `yunohost tools migrations migrate`.", "monitor_disabled": "Server monitoring now turned off", "monitor_enabled": "Server monitoring now turned on", From 0015db83b9970012d5188bc22944358f1febb22e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Thu, 19 Sep 2019 19:14:17 +0200 Subject: [PATCH 0162/3170] create the backup archive Co-Authored-By: Alexandre Aubin --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 03a750aa5..5078e1026 100644 --- a/locales/en.json +++ b/locales/en.json @@ -96,7 +96,7 @@ "backup_couldnt_bind": "Could not bind {src:s} to {dest:s}.", "backup_created": "Backup created", "backup_creating_archive": "Creating the backup archive…", - "backup_creation_failed": "Could not create the backup creation", + "backup_creation_failed": "Could not create the backup archive", "backup_csv_addition_failed": "Could not add files to backup into the CSV file", "backup_csv_creation_failed": "Could not create the CSV file needed for restoration", "backup_custom_backup_error": "Custom backup method could not get past the 'backup' step", From 87050276b4a217acea21b7f45820bdfaead1194d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 19 Sep 2019 19:26:41 +0200 Subject: [PATCH 0163/3170] Finish to implement first visitor test + fixes following test ... --- src/yunohost/app.py | 12 +++++------ src/yunohost/tests/test_permission.py | 29 +++++++++++++-------------- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 7938d6786..bba5fb104 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -429,8 +429,10 @@ def app_map(app=None, raw=False, user=None): continue if 'no_sso' in app_settings: # I don't think we need to check for the value here continue - if user and user not in permissions[app_id + ".main"]["corresponding_users"]: - continue + if user: + main_perm = permissions[app_id + ".main"] + if user not in main_perm["corresponding_users"] and "visitors" not in main_perm["allowed"]: + continue domain = app_settings['domain'] path = app_settings['path'] @@ -2613,10 +2615,8 @@ def _parse_args_in_yunohost_format(args, action_args): 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': - try: - user_info(arg_value) - except YunohostError as e: - raise YunohostError('app_argument_invalid', name=arg_name, error=e) + 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')) diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index 51bf6a4c6..bef042be1 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -19,7 +19,7 @@ def clean_user_groups_permission(): user_delete(u) for g in user_group_list()['groups']: - if g != "all_users": + if g not in ["all_users", "visitors"]: user_group_delete(g) for p in user_permission_list()['permissions']: @@ -162,8 +162,7 @@ def check_permission_for_apps(): def can_access_webpage(webpath, logged_as=None): webpath = webpath.rstrip("/") - webroot = webpath.rsplit("/", 1)[0] - sso_url = webroot+"/yunohost/sso" + sso_url = "https://"+maindomain+"/yunohost/sso/" # Anonymous access if not logged_as: @@ -177,6 +176,8 @@ def can_access_webpage(webpath, logged_as=None): headers={"Referer": sso_url, "Content-Type": "application/x-www-form-urlencoded"}, verify=False) + # We should have some cookies related to authentication now + assert session.cookies r = session.get(webpath, verify=False) # If we can't access it, we got redirected to the sso @@ -413,30 +414,28 @@ def test_permission_app_change_url(): def test_permission_app_propagation_on_ssowat(): - # TODO / FIXME : To be actually implemented later .... - raise NotImplementedError - app_install("./tests/apps/permissions_app_ynh", args="domain=%s&path=%s&is_public=1&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True) res = user_permission_list(full=True)['permissions'] - assert res['permissions_app.main']['allowed'] == ["all_users"] + assert res['permissions_app.main']['allowed'] == ["visitors"] - assert can_access_webpage(maindomain + "/urlpermissionapp", logged_as=None) - assert can_access_webpage(maindomain + "/urlpermissionapp", logged_as="alice") + app_webroot = "https://%s/urlpermissionapp" % maindomain + assert can_access_webpage(app_webroot, logged_as=None) + assert can_access_webpage(app_webroot, logged_as="alice") user_permission_update("permissions_app.main", remove="visitors", add="bob") res = user_permission_list(full=True)['permissions'] - assert not can_access_webpage(maindomain + "/urlpermissionapp", logged_as=None) - assert not can_access_webpage(maindomain + "/urlpermissionapp", logged_as="alice") - assert can_access_webpage(maindomain + "/urlpermissionapp", logged_as="bob") + assert not can_access_webpage(app_webroot, logged_as=None) + assert not can_access_webpage(app_webroot, logged_as="alice") + assert can_access_webpage(app_webroot, logged_as="bob") # Test admin access, as configured during install, only alice should be able to access it - assert not can_access_webpage(maindomain + "/urlpermissionapp/admin", logged_as=None) - assert not can_access_webpage(maindomain + "/urlpermissionapp/admin", logged_as="alice") - assert can_access_webpage(maindomain + "/urlpermissionapp/admin", logged_as="bob") + assert not can_access_webpage(app_webroot+"/admin", logged_as=None) + assert can_access_webpage(app_webroot+"/admin", logged_as="alice") + assert not can_access_webpage(app_webroot+"/admin", logged_as="bob") def test_permission_legacy_app_propagation_on_ssowat(): From 5a792fc6dbb72d834ccc8eee3a520339c9d1f9d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Thu, 19 Sep 2019 19:27:52 +0200 Subject: [PATCH 0164/3170] password got changed Co-Authored-By: Alexandre Aubin --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 5078e1026..c68141d97 100644 --- a/locales/en.json +++ b/locales/en.json @@ -3,7 +3,7 @@ "action_invalid": "Invalid action '{action:s}'", "admin_password": "Administration password", "admin_password_change_failed": "Cannot change password", - "admin_password_changed": "The administration password now changed", + "admin_password_changed": "The administration password got changed", "admin_password_too_long": "Please choose a password shorter than 127 characters", "already_up_to_date": "Nothing to do. Everything is already up-to-date.", "app_action_cannot_be_ran_because_required_services_down": "This app requires some services which are currently down. Before continuing, you should try to restart the following services (and possibly investigate why they are down): {services}", From d2e4a59a2506f56f463c1881afc02fe21d148312 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Thu, 19 Sep 2019 19:31:03 +0200 Subject: [PATCH 0165/3170] The user, Could not find the, in the list of --- locales/en.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/en.json b/locales/en.json index c68141d97..5093d6e69 100644 --- a/locales/en.json +++ b/locales/en.json @@ -30,7 +30,7 @@ "app_no_upgrade": "All applications are already up-to-date", "app_not_upgraded": "These apps were not upgraded: {apps}", "app_not_correctly_installed": "{app:s} seems to be incorrectly installed", - "app_not_installed": "The application '{app:s}' is not to be found among all installed apps: {all_apps}", + "app_not_installed": "Could not find the application '{app:s}' in the list of installed apps: {all_apps}", "app_not_properly_removed": "{app:s} has not been properly removed", "app_package_need_update": "The app {app} package needs to be updated to follow YunoHost changes", "app_removed": "{app:s} removed", @@ -564,7 +564,7 @@ "upnp_disabled": "UPnP turned off", "upnp_enabled": "UPnP turned on", "upnp_port_open_failed": "Could not open port via UPnP", - "user_already_in_group": "The User '{user:}' already in the group {group:s}", + "user_already_in_group": "The user '{user:}' is already in the '{group:s}' group", "user_created": "User created", "user_creation_failed": "Could not create user", "user_deleted": "User deleted", From eb57a4ad9e982eea6a5dc0c7543c61a437265568 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 19 Sep 2019 19:51:27 +0200 Subject: [PATCH 0166/3170] Get rid of etckeeper --- data/hooks/conf_regen/01-yunohost | 3 -- data/templates/yunohost/etckeeper.conf | 43 -------------------------- debian/control | 2 +- 3 files changed, 1 insertion(+), 47 deletions(-) delete mode 100644 data/templates/yunohost/etckeeper.conf diff --git a/data/hooks/conf_regen/01-yunohost b/data/hooks/conf_regen/01-yunohost index faf041110..f22de7a53 100755 --- a/data/hooks/conf_regen/01-yunohost +++ b/data/hooks/conf_regen/01-yunohost @@ -53,9 +53,6 @@ do_pre_regen() { else sudo cp services.yml /etc/yunohost/services.yml fi - - mkdir -p "$pending_dir"/etc/etckeeper/ - cp etckeeper.conf "$pending_dir"/etc/etckeeper/ } _update_services() { diff --git a/data/templates/yunohost/etckeeper.conf b/data/templates/yunohost/etckeeper.conf deleted file mode 100644 index 2d11c3dc6..000000000 --- a/data/templates/yunohost/etckeeper.conf +++ /dev/null @@ -1,43 +0,0 @@ -# The VCS to use. -#VCS="hg" -VCS="git" -#VCS="bzr" -#VCS="darcs" - -# Options passed to git commit when run by etckeeper. -GIT_COMMIT_OPTIONS="--quiet" - -# Options passed to hg commit when run by etckeeper. -HG_COMMIT_OPTIONS="" - -# Options passed to bzr commit when run by etckeeper. -BZR_COMMIT_OPTIONS="" - -# Options passed to darcs record when run by etckeeper. -DARCS_COMMIT_OPTIONS="-a" - -# Uncomment to avoid etckeeper committing existing changes -# to /etc automatically once per day. -#AVOID_DAILY_AUTOCOMMITS=1 - -# Uncomment the following to avoid special file warning -# (the option is enabled automatically by cronjob regardless). -#AVOID_SPECIAL_FILE_WARNING=1 - -# Uncomment to avoid etckeeper committing existing changes to -# /etc before installation. It will cancel the installation, -# so you can commit the changes by hand. -#AVOID_COMMIT_BEFORE_INSTALL=1 - -# The high-level package manager that's being used. -# (apt, pacman-g2, yum, zypper etc) -HIGHLEVEL_PACKAGE_MANAGER=apt - -# The low-level package manager that's being used. -# (dpkg, rpm, pacman, pacman-g2, etc) -LOWLEVEL_PACKAGE_MANAGER=dpkg - -# To push each commit to a remote, put the name of the remote here. -# (eg, "origin" for git). Space-separated lists of multiple remotes -# also work (eg, "origin gitlab github" for git). -PUSH_REMOTE="" diff --git a/debian/control b/debian/control index 64c7cd31d..c0604d90e 100644 --- a/debian/control +++ b/debian/control @@ -31,7 +31,7 @@ Depends: ${python:Depends}, ${misc:Depends} , equivs, lsof Recommends: yunohost-admin , openssh-server, ntp, inetutils-ping | iputils-ping - , bash-completion, rsyslog, etckeeper + , bash-completion, rsyslog , php-gd, php-curl, php-gettext, php-mcrypt , python-pip , unattended-upgrades From 52b59ff2a41b04107433caa7e1523a03aee8f4d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quent=C3=AD?= Date: Wed, 18 Sep 2019 16:30:18 +0000 Subject: [PATCH 0167/3170] Translated using Weblate (Occitan) Currently translated at 99.0% (578 of 584 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/oc/ --- locales/oc.json | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/locales/oc.json b/locales/oc.json index 2cb33f4bd..320a18341 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -198,7 +198,7 @@ "migrations_current_target": "La cibla de migracion est {}", "migrations_error_failed_to_load_migration": "ERROR : fracàs del cargament de la migracion {number} {name}", "migrations_list_conflict_pending_done": "Podètz pas utilizar --previous e --done a l’encòp.", - "migrations_loading_migration": "Cargament de la migracion{number} {name}…", + "migrations_loading_migration": "Cargament de la migracion {id}…", "migrations_no_migrations_to_run": "Cap de migracion de lançar", "migrations_show_currently_running_migration": "Realizacion de la migracion {number} {name}…", "migrations_show_last_migration": "La darrièra migracion realizada es {}", @@ -376,10 +376,10 @@ "migration_0003_general_warning": "Notatz qu’aquesta migracion es una operacion delicata. Encara que la còla YunoHost aguèsse fach çò melhor per la tornar legir e provar, la migracion poiriá copar de parts del sistèma o de las aplicacions.\n\nEn consequéncia, vos recomandam :\n· · · · - de lançar una salvagarda de vòstras donadas o aplicacions criticas. Mai d’informacions a https://yunohost.org/backup ;\n· · · · - d’èsser pacient aprèp aver lançat la migracion : segon vòstra connexion Internet e material, pòt trigar qualques oras per que tot siá mes al nivèl.\n\nEn mai, lo pòrt per SMTP, utilizat pels clients de corrièls extèrns (coma Thunderbird o K9-Mail per exemple) foguèt cambiat de 465 (SSL/TLS) per 587 (STARTTLS). L’ancian pòrt 465 serà automaticament tampat e lo nòu pòrt 587 serà dobèrt dins lo parafuòc. Vosautres e vòstres utilizaires *auretz* d’adaptar la configuracion de vòstre client de corrièl segon aqueles cambiaments !", "migration_0003_problematic_apps_warning": "Notatz que las aplicacions seguentas, saique problematicas, son estadas desactivadas. Semblan d’aver estadas installadas d’una lista d’aplicacions o que son pas marcadas coma «working ». En consequéncia, podèm pas assegurar que tendràn de foncionar aprèp la mesa a nivèl : {problematic_apps}", "migrations_bad_value_for_target": "Nombre invalid pel paramètre « target », los numèros de migracion son 0 o {}", - "migrations_migration_has_failed": "La migracion {number} {name} a pas capitat amb l’excepcion {exception}, anullacion", - "migrations_skip_migration": "Passatge de la migracion {number} {name}…", - "migrations_to_be_ran_manually": "La migracion {number} {name} deu èsser lançada manualament. Mercés d’anar a Aisinas > Migracion dins l’interfàcia admin, o lançar « yunohost tools migrations migrate ».", - "migrations_need_to_accept_disclaimer": "Per lançar la migracion {number} {name} , avètz d’acceptar aquesta clausa de non-responsabilitat :\n---\n{disclaimer}\n---\nS’acceptatz de lançar la migracion, mercés de tornar executar la comanda amb l’opcion accept-disclaimer.", + "migrations_migration_has_failed": "La migracion {id} a pas capitat, abandon. Error : {exception}", + "migrations_skip_migration": "Passatge de la migracion {id}…", + "migrations_to_be_ran_manually": "La migracion {id} deu èsser lançada manualament. Mercés d’anar a Aisinas > Migracion dins l’interfàcia admin, o lançar « yunohost tools migrations migrate ».", + "migrations_need_to_accept_disclaimer": "Per lançar la migracion {id} , avètz d’acceptar aquesta clausa de non-responsabilitat :\n---\n{disclaimer}\n---\nS’acceptatz de lançar la migracion, mercés de tornar executar la comanda amb l’opcion accept-disclaimer.", "monitor_disabled": "La supervision del servidor es desactivada", "monitor_enabled": "La supervision del servidor es activada", "mysql_db_initialized": "La basa de donadas MySQL es estada inicializada", @@ -453,7 +453,7 @@ "migration_0005_postgresql_94_not_installed": "Postgresql es pas installat sul sistèma. Pas res de far !", "migration_0005_postgresql_96_not_installed": "Avèm trobat que Postgresql 9.4 es installat, mas cap de version de Postgresql 9.6 pas trobada !? Quicòm d’estranh a degut arribar a vòstre sistèma :( …", "migration_0005_not_enough_space": "I a pas pro d’espaci disponible sus {path} per lançar la migracion d’aquela passa :(.", - "recommend_to_add_first_user": "La post installacion es acabada, mas YunoHost fa besonh d’almens un utilizaire per foncionar coma cal. Vos cal n’ajustar un en utilizant la comanda « yunohost user create $username » o ben l’interfàcia d’administracion.", + "recommend_to_add_first_user": "La post installacion es acabada, mas YunoHost fa besonh d’almens un utilizaire per foncionar coma cal. Vos cal n’ajustar un en utilizant la comanda « yunohost user create Date: Tue, 10 Sep 2019 14:08:19 +0200 Subject: [PATCH 0168/3170] Fix / remove stale i18n strings --- locales/en.json | 26 +------------------------- src/yunohost/app.py | 2 +- src/yunohost/tools.py | 2 +- tests/_test_m18nkeys.py | 39 +++++++++++++++++++++++++++++---------- 4 files changed, 32 insertions(+), 37 deletions(-) diff --git a/locales/en.json b/locales/en.json index 2d708279d..a4f5ec9da 100644 --- a/locales/en.json +++ b/locales/en.json @@ -14,7 +14,6 @@ "app_argument_choice_invalid": "Use one of these choices '{choices:s}' for the argument '{name:s}'", "app_argument_invalid": "Pick a valid value for the argument '{name:s}': {error:s}", "app_argument_required": "Argument '{name:s}' is required", - "app_change_no_change_url_script": "The application {app_name:s} doesn't support changing its URL yet, you might need to upgrade it.", "app_change_url_failed_nginx_reload": "Could not reload NGINX. Here is the output of 'nginx -t':\n{nginx_errors:s}", "app_change_url_identical_domains": "The old and new domain/url_path are identical ('{domain:s}{path:s}'), nothing to do.", "app_change_url_no_script": "This application '{app_name:s}' doesn't support URL modification yet. Maybe you should upgrade it.", @@ -75,7 +74,6 @@ "ask_password": "Password", "ask_path": "Path", "backup_abstract_method": "This backup method has yet to be implemented", - "backup_action_required": "You must specify something to save", "backup_actually_backuping": "Creating a backup archive from the collected files…", "backup_app_failed": "Could not back up the app '{app:s}'", "backup_applying_method_borg": "Sending all files to backup into borg-backup repository…", @@ -84,7 +82,6 @@ "backup_applying_method_tar": "Creating the backup TAR archive…", "backup_archive_app_not_found": "Could not find the app '{app:s}' in the backup archive", "backup_archive_broken_link": "Could not access the backup archive (broken link to {path:s})", - "backup_archive_mount_failed": "Could not mount the backup archive", "backup_archive_name_exists": "A backup archive with this name already exists.", "backup_archive_name_unknown": "Unknown local backup archive named '{name:s}'", "backup_archive_open_failed": "Could not open the backup archive", @@ -97,16 +94,13 @@ "backup_copying_to_organize_the_archive": "Copying {size:s}MB to organize the archive", "backup_couldnt_bind": "Could not bind {src:s} to {dest:s}.", "backup_created": "Backup created", - "backup_creating_archive": "Creating the backup archive…", "backup_creation_failed": "Could not create the backup archive", "backup_csv_addition_failed": "Could not add files to backup into the CSV file", "backup_csv_creation_failed": "Could not create the CSV file needed for restoration", "backup_custom_backup_error": "Custom backup method could not get past the 'backup' step", "backup_custom_mount_error": "Custom backup method could not get past the 'mount' step", - "backup_custom_need_mount_error": "Custom backup method could not get past 'need_mount' step", "backup_delete_error": "Could not delete '{path:s}'", "backup_deleted": "Backup deleted", - "backup_extracting_archive": "Extracting backup archive…", "backup_hook_unknown": "The backup hook '{hook:s}' is unknown", "backup_invalid_archive": "This is not a backup archive", "backup_method_borg_finished": "Backup into Borg finished", @@ -142,7 +136,6 @@ "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 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_not_resolved_locally": "The domain {domain:s} cannot be resolved from inside your YunoHost server. This might happen if you recently modified your DNS record. If so, please wait a few hours for it to propagate. If the issue persists, consider adding {domain:s} to /etc/hosts. (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_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", @@ -158,12 +151,10 @@ "diagnosis_debian_version_error": "Could not retrieve the Debian version: {error}", "diagnosis_kernel_version_error": "Could not retrieve kernel version: {error}", "diagnosis_monitor_disk_error": "Could not monitor disks: {error}", - "diagnosis_monitor_network_error": "Could not monitor network: {error}", "diagnosis_monitor_system_error": "Could not monitor system: {error}", "diagnosis_no_apps": "No installed application", "dpkg_is_broken": "You cannot do this right now because dpkg/APT (the system package managers) seems to be in a broken state… You can try to solve this issue by connecting through SSH and running `sudo dpkg --configure -a`.", "dpkg_lock_not_available": "This command can't be ran right now because another program seems to be using the lock of dpkg (the system package manager)", - "dnsmasq_isnt_installed": "Dnsmasq does not seem to be installed, please run 'apt-get remove bind9 && apt-get install it'", "domain_cannot_remove_main": "Cannot remove main domain. Set one first", "domain_cert_gen_failed": "Could not generate certificate", "domain_created": "Domain created", @@ -172,15 +163,11 @@ "domain_deletion_failed": "Could not delete domain", "domain_dns_conf_is_just_a_recommendation": "This command shows you the *recommended* configuration. It does not actually set up the DNS configuration for you. It is your responsability to configure your DNS zone in your registrar according to this recommendation.", "domain_dyndns_already_subscribed": "You have already subscribed to a DynDNS domain", - "domain_dyndns_dynette_is_unreachable": "Could not reach YunoHost dynette, either your YunoHost is not correctly connected to the Internet, or the dynette server is down. Error: {error}", - "domain_dyndns_invalid": "This domain can not be used with DynDNS", "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_unknown": "Unknown domain", - "domain_zone_exists": "DNS zone file already exists", - "domain_zone_not_found": "DNS zone file not found for domain {:s}", "domains_available": "Available domains:", "done": "Done", "downloading": "Downloading…", @@ -194,6 +181,7 @@ "dyndns_key_generating": "Generating DNS key… It may take a while.", "dyndns_key_not_found": "DNS key not found for the domain", "dyndns_no_domain_registered": "No domain registered with DynDNS", + "dyndns_provider_unreachable": "Unable to reach Dyndns provider {provider}: either your YunoHost is not correctly connected to the internet or the dynette server is down.", "dyndns_registered": "DynDNS domain registered", "dyndns_registration_failed": "Could not register DynDNS domain: {error:s}", "dyndns_domain_not_provided": "DynDNS provider {provider:s} cannot provide domain {domain:s}.", @@ -210,7 +198,6 @@ "firewall_reload_failed": "Could not reload the firewall", "firewall_reloaded": "Firewall reloaded", "firewall_rules_cmd_failed": "Some firewall rules commands have failed. More info in log.", - "format_datetime_short": "%m/%d/%Y %I:%M %p", "global_settings_bad_choice_for_enum": "Bad choice for setting {setting:s}, received '{choice:s}', but available choices are: {available_choices:s}", "global_settings_bad_type_for_setting": "Bad type for setting {setting:s}, received {received_type:s}, expected {expected_type:s}", "global_settings_cant_open_settings": "Could not open settings file, reason: {reason:s}", @@ -240,7 +227,6 @@ "group_deleted": "Group '{group}' deleted", "group_deletion_failed": "Could not delete the group '{group}'", "group_deletion_not_allowed": "The group {group:s} cannot be deleted manually.", - "group_info_failed": "Could not present group info", "group_unknown": "The group '{group:s}' is unknown", "group_updated": "Group '{group}' updated", "group_update_failed": "Could not update the group '{group}'", @@ -251,7 +237,6 @@ "hook_name_unknown": "Unknown hook name '{name:s}'", "installation_complete": "Installation complete", "installation_failed": "Something went wrong with the installation", - "invalid_url_format": "Something is wrong with the URL", "ip6tables_unavailable": "You cannot play with ip6tables here. You are either in a container or your kernel does not support it", "iptables_unavailable": "You cannot play with iptables here. You are either in a container or your kernel does not support it", "log_corrupted_md_file": "The YAML metadata file associated with logs is damaged: '{md_file}\nError: {error}'", @@ -403,24 +388,18 @@ "network_check_mx_ko": "DNS MX record is not set", "network_check_smtp_ko": "Outbound e-mail (SMTP port 25) seems to be blocked by your network", "network_check_smtp_ok": "Outbound e-mail (SMTP port 25) is not blocked", - "new_domain_required": "You must provide the new main domain", - "no_appslist_found": "No app list found", "no_internet_connection": "Server not connected to the Internet", - "no_ipv6_connectivity": "IPv6 connectivity is not available", - "no_restore_script": "No restore script found for the app '{app:s}'", "not_enough_disk_space": "Not enough free space on '{path:s}'", "operation_interrupted": "The operation was manually interrupted?", "package_not_installed": "Package '{pkgname}' is not installed", "package_unexpected_error": "An unexpected error occurred processing the package '{pkgname}'", "package_unknown": "Unknown package '{pkgname}'", - "packages_upgrade_critical_later": "Critical packages ({packages:s}) will be upgraded later", "packages_upgrade_failed": "Could not upgrade all the packages", "password_listed": "This password is among the most used password in the world. Please choose something more unique.", "password_too_simple_1": "The password needs to be at least 8 characters long", "password_too_simple_2": "The password needs to be at least 8 characters long and contain a digit, upper and lower characters", "password_too_simple_3": "The password needs to be at least 8 characters long and contain a digit, upper, lower and special characters", "password_too_simple_4": "The password needs to be at least 12 characters long and contain a digit, upper, lower and special characters", - "path_removal_failed": "Could not remove path {:s}", "pattern_backup_archive_name": "Must be a valid filename with max 30 characters, alphanumeric and -_. characters only", "pattern_domain": "Must be a valid domain name (e.g. my-domain.org)", "pattern_email": "Must be a valid email address (e.g. someone@domain.org)", @@ -468,7 +447,6 @@ "regenconf_dry_pending_applying": "Checking pending configuration which would have been applied for category '{category}'…", "regenconf_failed": "Could not regenerate the configuration for category(s): {categories}", "regenconf_pending_applying": "Applying pending configuration for category '{category}'…", - "restore_action_required": "You must pick something to restore", "restore_already_installed_app": "An app with the ID '{app:s}' is already installed", "restore_app_failed": "Could not restore the app '{app:s}'", "restore_cleaning_failed": "Could not clean up the temporary restoration directory", @@ -478,7 +456,6 @@ "restore_failed": "Could not restore system", "restore_hook_unavailable": "The restoration script for '{part:s}' not available on your system and not in the archive either", "restore_may_be_not_enough_disk_space": "Your system seems does not have enough space (free: {free_space:d} B, needed space: {needed_space:d} B, security margin: {margin:d} B)", - "restore_mounting_archive": "Mounting archive into '{path:s}'", "restore_not_enough_disk_space": "Not enough space (space: {free_space:d} B, needed space: {needed_space:d} B, security margin: {margin:d} B)", "restore_nothings_done": "Nothing was restored", "restore_removing_tmp_dir_failed": "Could not remove an old temporary directory", @@ -530,7 +507,6 @@ "service_reloaded_or_restarted": "'{service:s}' service reloaded or restarted", "service_start_failed": "Could not start the service '{service:s}'\n\nRecent service logs:{logs:s}", "service_started": "'{service:s}' service started", - "service_status_failed": "Could not determine status of the service '{service:s}'", "service_stop_failed": "Could not stop the service '{service:s}'\n\nRecent service logs:{logs:s}", "service_stopped": "'{service:s}' service stopped", "service_unknown": "Unknown service '{service:s}'", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index c93c7ba80..0d30a2da3 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -472,7 +472,7 @@ def app_change_url(operation_logger, app, domain, path): raise YunohostError('app_not_installed', app=app, all_apps=_get_all_installed_apps_id()) if not os.path.exists(os.path.join(APPS_SETTING_PATH, app, "scripts", "change_url")): - raise YunohostError("app_change_no_change_url_script", app_name=app) + raise YunohostError("app_change_url_no_script", app_name=app) old_domain = app_setting(app, "domain") old_path = app_setting(app, "path") diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 5ff5d64fc..eb2fc5cc6 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -657,7 +657,7 @@ def tools_upgrade(operation_logger, apps=None, system=False): ) returncode = call_async_output(dist_upgrade, callbacks, shell=True) if returncode != 0: - logger.warning('tools_upgrade_regular_packages_failed', + logger.warning(m18n.n('tools_upgrade_regular_packages_failed'), packages_list=', '.join(noncritical_packages_upgradable)) operation_logger.error(m18n.n('packages_upgrade_failed')) raise YunohostError(m18n.n('packages_upgrade_failed')) diff --git a/tests/_test_m18nkeys.py b/tests/_test_m18nkeys.py index ee8df0dc6..cc6202517 100644 --- a/tests/_test_m18nkeys.py +++ b/tests/_test_m18nkeys.py @@ -5,23 +5,37 @@ import glob import json import yaml +ignore = [ "service_description_", + "migration_description_", + "global_settings_setting_", + "password_too_simple_", + "password_listed", + "backup_method_", + "backup_applying_method_", + "confirm_app_install_", + "log_", + ] + ############################################################################### # Find used keys in python code # ############################################################################### # This regex matches « foo » in patterns like « m18n.n( "foo" » -p = re.compile(r'm18n\.n\(\s*[\"\']([a-zA-Z1-9_]+)[\"\']') +p1 = re.compile(r'm18n\.n\(\s*[\"\']([a-zA-Z0-9_]+)[\"\']') +p2 = re.compile(r'YunohostError\([\'\"]([a-zA-Z0-9_]+)[\'\"]') -python_files = glob.glob("/vagrant/yunohost/src/yunohost/*.py") -python_files.extend(glob.glob("/vagrant/yunohost/src/yunohost/utils/*.py")) -python_files.append("/vagrant/yunohost/bin/yunohost") +python_files = glob.glob("../src/yunohost/*.py") +python_files.extend(glob.glob("../src/yunohost/utils/*.py")) +python_files.extend(glob.glob("../src/yunohost/data_migrations/*.py")) +python_files.append("../bin/yunohost") python_keys = set() for python_file in python_files: - with open(python_file) as f: - keys_in_file = p.findall(f.read()) - for key in keys_in_file: - python_keys.add(key) + content = open(python_file).read() + for match in p1.findall(content): + python_keys.add(match) + for match in p2.findall(content): + python_keys.add(match) ############################################################################### # Find keys used in actionmap # @@ -42,19 +56,20 @@ for _, category in actionmap.items(): actionmap_keys.add(argument["extra"]["password"]) if "ask" in argument["extra"]: actionmap_keys.add(argument["extra"]["ask"]) + if "comment" in argument["extra"]: + actionmap_keys.add(argument["extra"]["comment"]) if "pattern" in argument["extra"]: actionmap_keys.add(argument["extra"]["pattern"][1]) if "help" in argument["extra"]: print argument["extra"]["help"] -# These keys are used but difficult to parse actionmap_keys.add("admin_password") ############################################################################### # Load en locale json keys # ############################################################################### -en_locale_file = "/vagrant/yunohost/locales/en.json" +en_locale_file = "../locales/en.json" with open(en_locale_file) as f: en_locale_json = json.loads(f.read()) @@ -72,11 +87,15 @@ keys_defined_but_not_used = en_locale_keys.difference(used_keys) if len(keys_used_but_not_defined) != 0: print "> Error ! Those keys are used in some files but not defined :" for key in sorted(keys_used_but_not_defined): + if any(key.startswith(i) for i in ignore): + continue print " - %s" % key if len(keys_defined_but_not_used) != 0: print "> Warning ! Those keys are defined but seems unused :" for key in sorted(keys_defined_but_not_used): + if any(key.startswith(i) for i in ignore): + continue print " - %s" % key From 6ed062b41bc106e9b84fbbc6c9828569926e6b89 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 20 Sep 2019 14:49:31 +0200 Subject: [PATCH 0169/3170] app_no_upgrade -> apps_already_up_to_date --- locales/en.json | 2 +- src/yunohost/app.py | 4 ++-- src/yunohost/tools.py | 5 +---- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/locales/en.json b/locales/en.json index a4f5ec9da..61fdcfa9b 100644 --- a/locales/en.json +++ b/locales/en.json @@ -27,7 +27,6 @@ "app_location_install_failed": "Cannot install the app there because it conflicts with the app '{other_app}' already installed in '{other_path}'", "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_no_upgrade": "All applications are already up-to-date", "app_not_upgraded": "The app '{failed_app}' failed to upgrade, and as a consequence the following apps upgrades have been cancelled: {apps}", "app_upgrade_stopped": "The upgrade of all applications has been stopped to prevent possible damage because the previous application failed to upgrade", "app_not_correctly_installed": "{app:s} seems to be incorrectly installed", @@ -50,6 +49,7 @@ "app_upgrade_failed": "Could not upgrade {app:s}", "app_upgrade_some_app_failed": "Some applications could not be upgraded", "app_upgraded": "{app:s} upgraded", + "apps_already_up_to_date": "All applications are already up-to-date", "apps_permission_not_found": "No permission found for the installed apps", "apps_permission_restoration_failed": "Grant the permission permission '{permission:s}' to restore {app:s}", "appslist_corrupted_json": "Could not load the application lists. It looks like {filename:s} is damaged.", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 0d30a2da3..5a51e57bb 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -590,7 +590,7 @@ def app_upgrade(app=[], url=None, file=None): try: app_list() except YunohostError: - raise YunohostError('app_no_upgrade') + raise YunohostError('apps_already_up_to_date') not_upgraded_apps = [] @@ -611,7 +611,7 @@ def app_upgrade(app=[], url=None, file=None): raise YunohostError('app_not_installed', app=app, all_apps=_get_all_installed_apps_id()) if len(apps) == 0: - raise YunohostError('app_no_upgrade') + raise YunohostError('apps_already_up_to_date') if len(apps) > 1: logger.info(m18n.n("app_upgrade_several_apps", apps=", ".join(apps))) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index eb2fc5cc6..64689fe0c 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -584,10 +584,7 @@ def tools_upgrade(operation_logger, apps=None, system=False): upgradable_apps = [app["id"] for app in _list_upgradable_apps()] - if not upgradable_apps: - logger.info(m18n.n("app_no_upgrade")) - return - elif len(apps) and all(app not in upgradable_apps for app in apps): + if not upgradable_apps or (len(apps) and all(app not in upgradable_apps for app in apps)): logger.info(m18n.n("apps_already_up_to_date")) return From 379c28de909774bac44e10af34ecd6d91e281ab1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 20 Sep 2019 15:00:31 +0200 Subject: [PATCH 0170/3170] Update src/yunohost/backup.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Allan Nordhøy --- src/yunohost/backup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 305152865..c28160342 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -1365,7 +1365,7 @@ class RestoreManager(): permission_create(permission_name, urls=permission_infos.get("urls", [])) if "allowed" not in permission_infos: - logger.warning("'allowed' key corresponding to allowed groups for permission %s not found when restoring app %s ... You might need to reconfigure permissions yourself!" % (permission_name, app_instance_name)) + logger.warning("'allowed' key corresponding to allowed groups for permission %s not found when restoring app %s … You might have to reconfigure permissions yourself." % (permission_name, app_instance_name)) else: should_be_allowed = [g for g in permission_infos["allowed"] if g in existing_groups] current_allowed = user_permission_list()["permissions"][permission_name]["allowed"] From 6284ad09c65a3fe377e0a9b542658773aeade1f0 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 10 Sep 2019 14:06:18 +0200 Subject: [PATCH 0171/3170] Simplify madness code about checking requirements --- locales/en.json | 3 -- src/yunohost/app.py | 28 ++----------------- src/yunohost/utils/packages.py | 51 ++++++---------------------------- 3 files changed, 11 insertions(+), 71 deletions(-) diff --git a/locales/en.json b/locales/en.json index 2d708279d..d471f72ca 100644 --- a/locales/en.json +++ b/locales/en.json @@ -21,7 +21,6 @@ "app_change_url_success": "{app:s} URL is now {domain:s}{path:s}", "app_extraction_failed": "Could not extract the installation files", "app_id_invalid": "Invalid app ID", - "app_incompatible": "The app {app} is incompatible with your YunoHost version", "app_install_files_invalid": "These files cannot be installed", "app_location_already_used": "The app '{app}' is already installed in ({path})", "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}'", @@ -410,8 +409,6 @@ "no_restore_script": "No restore script found for the app '{app:s}'", "not_enough_disk_space": "Not enough free space on '{path:s}'", "operation_interrupted": "The operation was manually interrupted?", - "package_not_installed": "Package '{pkgname}' is not installed", - "package_unexpected_error": "An unexpected error occurred processing the package '{pkgname}'", "package_unknown": "Unknown package '{pkgname}'", "packages_upgrade_critical_later": "Critical packages ({packages:s}) will be upgraded later", "packages_upgrade_failed": "Could not upgrade all the packages", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index c93c7ba80..dcc46878b 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -2461,38 +2461,14 @@ def _check_manifest_requirements(manifest, app_instance_name): """Check if required packages are met from the manifest""" requirements = manifest.get('requirements', dict()) - # FIXME: Deprecate min_version key - if 'min_version' in manifest: - requirements['yunohost'] = '>> {0}'.format(manifest['min_version']) - logger.debug("the manifest key 'min_version' is deprecated, " - "use 'requirements' instead.") - - # Validate multi-instance app - if is_true(manifest.get('multi_instance', False)): - # Handle backward-incompatible change introduced in yunohost >= 2.3.6 - # See https://github.com/YunoHost/issues/issues/156 - yunohost_req = requirements.get('yunohost', None) - if (not yunohost_req or - not packages.SpecifierSet(yunohost_req) & '>= 2.3.6'): - raise YunohostError('{0}{1}'.format( - m18n.g('colon', m18n.n('app_incompatible'), app=app_instance_name), - m18n.n('app_package_need_update', app=app_instance_name))) - elif not requirements: + if not requirements: return logger.debug(m18n.n('app_requirements_checking', app=app_instance_name)) - # Retrieve versions of each required package - try: - versions = packages.get_installed_version( - *requirements.keys(), strict=True, as_dict=True) - except packages.PackageException as e: - raise YunohostError('app_requirements_failed', error=str(e), app=app_instance_name) - # Iterate over requirements for pkgname, spec in requirements.items(): - version = versions[pkgname] - if version not in packages.SpecifierSet(spec): + if not packages.meets_version_specifier(pkgname, spec): raise YunohostError('app_requirements_unmeet', pkgname=pkgname, version=version, spec=spec, app=app_instance_name) diff --git a/src/yunohost/utils/packages.py b/src/yunohost/utils/packages.py index 84901bbff..6df736432 100644 --- a/src/yunohost/utils/packages.py +++ b/src/yunohost/utils/packages.py @@ -33,36 +33,6 @@ logger = logging.getLogger('yunohost.utils.packages') # Exceptions ----------------------------------------------------------------- -class PackageException(Exception): - - """Base exception related to a package - - Represent an exception related to the package named `pkgname`. If no - `message` is provided, it will first try to use the translation key - `message_key` if defined by the derived class. Otherwise, a standard - message will be used. - - """ - message_key = 'package_unexpected_error' - - def __init__(self, pkgname, message=None): - super(PackageException, self).__init__( - message or m18n.n(self.message_key, pkgname=pkgname)) - self.pkgname = pkgname - - -class UnknownPackage(PackageException): - - """The package is not found in the cache.""" - message_key = 'package_unknown' - - -class UninstalledPackage(PackageException): - - """The package is not installed.""" - message_key = 'package_not_installed' - - class InvalidSpecifier(ValueError): """An invalid specifier was found.""" @@ -402,41 +372,38 @@ def get_installed_version(*pkgnames, **kwargs): """Get the installed version of package(s) Retrieve one or more packages named `pkgnames` and return their installed - version as a dict or as a string if only one is requested and `as_dict` is - `False`. If `strict` is `True`, an exception will be raised if a package - is unknown or not installed. + version as a dict or as a string if only one is requested. """ versions = OrderedDict() cache = apt.Cache() # Retrieve options - as_dict = kwargs.get('as_dict', False) - strict = kwargs.get('strict', False) with_repo = kwargs.get('with_repo', False) for pkgname in pkgnames: try: pkg = cache[pkgname] except KeyError: - if strict: - raise UnknownPackage(pkgname) 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: - if strict: - raise UninstalledPackage(pkgname) version = None try: # stable, testing, unstable repo = pkg.installed.origins[0].component except AttributeError: - if strict: - raise UninstalledPackage(pkgname) repo = "" if with_repo: @@ -449,7 +416,7 @@ def get_installed_version(*pkgnames, **kwargs): else: versions[pkgname] = version - if len(pkgnames) == 1 and not as_dict: + if len(pkgnames) == 1: return versions[pkgnames[0]] return versions From ebf2fb9a141da65954d1855bc025f0cf362c6f99 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 20 Sep 2019 20:13:51 +0200 Subject: [PATCH 0172/3170] Use relative urls by default for permissions while still supporting absolute urls ... --- src/yunohost/app.py | 18 +++++------- .../0011_setup_group_permission.py | 2 +- src/yunohost/permission.py | 29 ++++++++++--------- 3 files changed, 23 insertions(+), 26 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index bba5fb104..231568439 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -553,8 +553,6 @@ def app_change_url(operation_logger, app, domain, path): app_setting(app, 'domain', value=domain) app_setting(app, 'path', value=path) - permission_urls(app+".main", add=[domain+path], remove=[old_domain+old_path], sync_perm=True) - # avoid common mistakes if _run_service_command("reload", "nginx") is False: # grab nginx errors @@ -868,10 +866,9 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu if os.path.exists(os.path.join(extracted_app_folder, file_to_copy)): os.system('cp -R %s/%s %s' % (extracted_app_folder, file_to_copy, app_setting_path)) - # Create permission before the install (useful if the install script redefine the permission) - # Note that sync_perm is disabled to avoid triggering a whole bunch of code and messages - # can't be sure that we don't have one case when it's needed - permission_create(app_instance_name+".main", sync_perm=False) + # Initialize the main permission for the app + # After the install, if apps don't have a domain and path defined, the default url '/' is removed from the permission + permission_create(app_instance_name+".main", urls=["/"]) # Execute the app install script install_retcode = 1 @@ -949,17 +946,16 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu os.system('chown -R root: %s' % app_setting_path) os.system('chown -R admin: %s/scripts' % app_setting_path) - # Add path in permission if it's defined in the app install script + # If an app doesn't have at least a domain and a path, assume it's not a webapp and remove the default "/" permission app_settings = _get_app_settings(app_instance_name) domain = app_settings.get('domain', None) path = app_settings.get('path', None) - if domain and path: - # FIXME : might want to move this to before running the install script because some app need to run install script during initialization etc (idk) ? - permission_urls(app_instance_name+".main", add=[domain+path], sync_perm=False) + if not (domain and path): + permission_urls(app_instance_name + ".main", remove=["/"], sync_perm=False) # Migrate classic public app still using the legacy unprotected_uris if app_settings.get("unprotected_uris", None) == "/": - user_permission_update(app_instance_name+".main", remove="all_users", add="visitors", sync_perm=False) + user_permission_update(app_instance_name + ".main", remove="all_users", add="visitors", sync_perm=False) permission_sync_to_user() diff --git a/src/yunohost/data_migrations/0011_setup_group_permission.py b/src/yunohost/data_migrations/0011_setup_group_permission.py index a99dfb7c1..dd5b3c274 100644 --- a/src/yunohost/data_migrations/0011_setup_group_permission.py +++ b/src/yunohost/data_migrations/0011_setup_group_permission.py @@ -108,7 +108,7 @@ class MyMigration(Migration): path = app_setting(app, 'path') domain = app_setting(app, 'domain') - urls = [domain + path] if domain and path else None + urls = "/" if domain and path else None permission_create(app+".main", urls=urls, sync_perm=False) if permission: allowed_group = permission.split(',') diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index dbfc6e6f5..5f9a88e11 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -268,7 +268,18 @@ def permission_create(operation_logger, permission, urls=None, sync_perm=True): Keyword argument: permission -- Name of the permission (e.g. mail or nextcloud or wordpress.editors) - urls -- list of urls to specify for the permission + urls -- list of urls to specify for the permission. + + Urls are assumed to be relative to the app domain/path if they start with '/'. + For example: + / -> domain.tld/app + /admin -> domain.tld/app/admin + domain.tld/app/api -> domain.tld/app/api + + Urls can be later treated as regexes when they start with "re:". + For example: + re:/api/[A-Z]*$ -> domain.tld/app/api/[A-Z]*$ + re:domain.tld/app/api/[A-Z]*$ -> domain.tld/app/api/[A-Z]*$ """ from yunohost.utils.ldap import _get_ldap_interface @@ -302,7 +313,7 @@ def permission_create(operation_logger, permission, urls=None, sync_perm=True): attr_dict['groupPermission'] = ['cn=all_users,ou=groups,dc=yunohost,dc=org'] if urls: - attr_dict['URL'] = [_normalize_url(url) for url in urls] + attr_dict['URL'] = urls operation_logger.related_to.append(('app', permission.split(".")[0])) operation_logger.start() @@ -326,8 +337,8 @@ def permission_urls(operation_logger, permission, add=None, remove=None, sync_pe Keyword argument: permission -- Name of the permission (e.g. mail or nextcloud or wordpress.editors) - add -- List of urls to add - remove -- List of urls to remove + add -- List of urls to add (c.f. permission_create for documentation about their format) + remove -- List of urls to remove (c.f. permission_create for documentation about their format) """ from yunohost.utils.ldap import _get_ldap_interface @@ -345,11 +356,9 @@ def permission_urls(operation_logger, permission, add=None, remove=None, sync_pe if add: urls_to_add = [add] if not isinstance(add, list) else add - urls_to_add = [_normalize_url(url) for url in urls_to_add] new_urls += urls_to_add if remove: urls_to_remove = [remove] if not isinstance(remove, list) else remove - urls_to_remove = [_normalize_url(url) for url in urls_to_remove] new_urls = [u for u in new_urls if u not in urls_to_remove] if set(new_urls) == set(existing_permission["urls"]): @@ -457,11 +466,3 @@ def permission_sync_to_user(): # Reload unscd, otherwise the group ain't propagated to the LDAP database os.system('nscd --invalidate=passwd') os.system('nscd --invalidate=group') - - -def _normalize_url(url): - from yunohost.domain import _normalize_domain_path - domain = url[:url.index('/')] - path = url[url.index('/'):] - domain, path = _normalize_domain_path(domain, path) - return domain + path From 2b51d247fb5a1a66c152c13a6ad83e97fdaceee3 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 20 Sep 2019 20:14:14 +0200 Subject: [PATCH 0173/3170] Propagate changes on app helpers + tests --- data/helpers.d/setting | 22 +++++++++--- src/yunohost/tests/test_backuprestore.py | 12 +++---- src/yunohost/tests/test_permission.py | 45 ++++++++++++------------ 3 files changed, 47 insertions(+), 32 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index d083ed563..9deba2dc0 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -232,11 +232,24 @@ ynh_webpath_register () { # Create a new permission for the app # +# example: ynh_permission_create --permission admin --urls /admin +# # usage: ynh_permission_create --permission "permission" [--urls "url" ["url" ...]] # | arg: permission - the name for the permission (by default a permission named "main" already exist) # | arg: urls - (optional) a list of FULL urls for the permission (e.g. domain.tld/apps/admin) +# | arg: urls - (optional) a list of urls to specify for the permission. +# +# Urls are assumed to be relative to the app domain/path if they start with '/'. +# For example: +# / -> domain.tld/app +# /admin -> domain.tld/app/admin +# domain.tld/app/api -> domain.tld/app/api +# +# Urls can be treated as regexes when they start with "re:". +# For example: +# re:/api/[A-Z]*$ -> domain.tld/app/api/[A-Z]*$ +# re:domain.tld/app/api/[A-Z]*$ -> domain.tld/app/api/[A-Z]*$ # -# example: ynh_permission_create --permission admin --urls domain.tld/blog/admin ynh_permission_create() { declare -Ar args_array=( [p]=permission= [u]=urls= ) local permission @@ -251,10 +264,11 @@ ynh_permission_create() { # Remove a permission for the app (note that when the app is removed all permission is automatically removed) # +# example: ynh_permission_delete --permission editors +# # usage: ynh_permission_delete --permission "permission" # | arg: permission - the name for the permission (by default a permission named "main" is removed automatically when the app is removed) # -# example: ynh_permission_delete --permission editors ynh_permission_delete() { declare -Ar args_array=( [p]=permission= ) local permission @@ -267,8 +281,8 @@ ynh_permission_delete() { # # usage: ynh_permission_urls --permission "permission" --add "url" ["url" ...] --remove "url" ["url" ...] # | arg: permission - the name for the permission (by default a permission named "main" is removed automatically when the app is removed) -# | arg: add - (optional) a list of FULL urls to add to the permission (e.g. domain.tld/apps/admin) -# | arg: remove - (optional) a list of FULL urls to remove from the permission (e.g. other.tld/apps/admin) +# | arg: add - (optional) a list of urls to add to the permission (see permission_create for details regarding their format) +# | arg: remove - (optional) a list of urls to remove from the permission (see permission_create for details regarding their format) # ynh_permission_urls() { declare -Ar args_array=([p]=permission= [a]=add= [r]=remove=) diff --git a/src/yunohost/tests/test_backuprestore.py b/src/yunohost/tests/test_backuprestore.py index bdaf25299..cab98089b 100644 --- a/src/yunohost/tests/test_backuprestore.py +++ b/src/yunohost/tests/test_backuprestore.py @@ -529,9 +529,9 @@ def test_backup_and_restore_permission_app(): assert "permissions_app.main" in res assert "permissions_app.admin" in res assert "permissions_app.dev" in res - assert res['permissions_app.main']['urls'] == [maindomain + "/urlpermissionapp"] - assert res['permissions_app.admin']['urls'] == [maindomain + "/urlpermissionapp/admin"] - assert res['permissions_app.dev']['urls'] == [maindomain + "/urlpermissionapp/dev"] + assert res['permissions_app.main']['urls'] == ["/"] + assert res['permissions_app.admin']['urls'] == ["/admin"] + assert res['permissions_app.dev']['urls'] == ["/dev"] assert res['permissions_app.main']['allowed'] == ["all_users"] assert res['permissions_app.admin']['allowed'] == ["alice"] @@ -543,9 +543,9 @@ def test_backup_and_restore_permission_app(): assert "permissions_app.main" in res assert "permissions_app.admin" in res assert "permissions_app.dev" in res - assert res['permissions_app.main']['urls'] == [maindomain + "/urlpermissionapp"] - assert res['permissions_app.admin']['urls'] == [maindomain + "/urlpermissionapp/admin"] - assert res['permissions_app.dev']['urls'] == [maindomain + "/urlpermissionapp/dev"] + assert res['permissions_app.main']['urls'] == ["/"] + assert res['permissions_app.admin']['urls'] == ["/admin"] + assert res['permissions_app.dev']['urls'] == ["/dev"] assert res['permissions_app.main']['allowed'] == ["all_users"] assert res['permissions_app.admin']['allowed'] == ["alice"] diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index bef042be1..a8b6b8258 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -31,7 +31,7 @@ def setup_function(function): user_create("alice", "Alice", "White", "alice@" + maindomain, dummy_password) user_create("bob", "Bob", "Snow", "bob@" + maindomain, dummy_password) - permission_create("wiki.main", urls=[maindomain + "/wiki"], sync_perm=False) + permission_create("wiki.main", urls=["/"], sync_perm=False) permission_create("blog.main", sync_perm=False) user_permission_update("blog.main", remove="all_users", add="alice") @@ -198,7 +198,7 @@ def test_permission_list(): assert res['blog.main']['allowed'] == ["alice"] assert set(res['wiki.main']['corresponding_users']) == set(["alice", "bob"]) assert res['blog.main']['corresponding_users'] == ["alice"] - assert res['wiki.main']['urls'] == [maindomain + "/wiki"] + assert res['wiki.main']['urls'] == ["/"] # # Create - Remove functions @@ -322,37 +322,37 @@ def test_permission_update_permission_that_doesnt_exist(): # Permission url management def test_permission_add_url(): - permission_urls("blog.main", add=[maindomain + "/testA"]) + permission_urls("blog.main", add=["/testA"]) res = user_permission_list(full=True)['permissions'] - assert res["blog.main"]["urls"] == [maindomain + "/testA"] + assert res["blog.main"]["urls"] == ["/testA"] -def test_permission_add_second_url(): - permission_urls("wiki.main", add=[maindomain + "/testA"]) +def test_permission_add_another_url(): + permission_urls("wiki.main", add=["/testA"]) res = user_permission_list(full=True)['permissions'] - assert set(res["wiki.main"]["urls"]) == set([maindomain + "/testA", maindomain + "/wiki"]) + assert set(res["wiki.main"]["urls"]) == set(["/", "/testA"]) def test_permission_remove_url(): - permission_urls("wiki.main", remove=[maindomain + "/wiki"]) + permission_urls("wiki.main", remove=["/"]) res = user_permission_list(full=True)['permissions'] assert res["wiki.main"]["urls"] == [] def test_permission_add_url_already_added(): res = user_permission_list(full=True)['permissions'] - assert res["wiki.main"]["urls"] == [maindomain + "/wiki"] + assert res["wiki.main"]["urls"] == ["/"] - permission_urls("wiki.main", add=[maindomain + "/wiki"]) + permission_urls("wiki.main", add=["/"]) res = user_permission_list(full=True)['permissions'] - assert res["wiki.main"]["urls"] == [maindomain + "/wiki"] + assert res["wiki.main"]["urls"] == ["/"] def test_permission_remove_url_not_added(): - permission_urls("wiki.main", remove=[maindomain + "/doesnt_exist"]) + permission_urls("wiki.main", remove=["/doesnt_exist"]) res = user_permission_list(full=True)['permissions'] - assert res['wiki.main']['urls'] == [maindomain + "/wiki"] + assert res['wiki.main']['urls'] == ["/"] # # Application interaction @@ -366,9 +366,9 @@ def test_permission_app_install(): assert "permissions_app.main" in res assert "permissions_app.admin" in res assert "permissions_app.dev" in res - assert res['permissions_app.main']['urls'] == [maindomain + "/urlpermissionapp"] - assert res['permissions_app.admin']['urls'] == [maindomain + "/urlpermissionapp/admin"] - assert res['permissions_app.dev']['urls'] == [maindomain + "/urlpermissionapp/dev"] + assert res['permissions_app.main']['urls'] == ["/"] + assert res['permissions_app.admin']['urls'] == ["/admin"] + assert res['permissions_app.dev']['urls'] == ["/dev"] assert res['permissions_app.main']['allowed'] == ["all_users"] assert set(res['permissions_app.main']['corresponding_users']) == set(["alice", "bob"]) @@ -399,17 +399,18 @@ def test_permission_app_change_url(): app_install("./tests/apps/permissions_app_ynh", args="domain=%s&path=%s&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True) + # FIXME : should rework this test to look for differences in the generated app map / app tiles ... res = user_permission_list(full=True)['permissions'] - assert res['permissions_app.main']['urls'] == [maindomain + "/urlpermissionapp"] - assert res['permissions_app.admin']['urls'] == [maindomain + "/urlpermissionapp/admin"] - assert res['permissions_app.dev']['urls'] == [maindomain + "/urlpermissionapp/dev"] + assert res['permissions_app.main']['urls'] == ["/"] + assert res['permissions_app.admin']['urls'] == ["/admin"] + assert res['permissions_app.dev']['urls'] == ["/dev"] app_change_url("permissions_app", maindomain, "/newchangeurl") res = user_permission_list(full=True)['permissions'] - assert res['permissions_app.main']['urls'] == [maindomain + "/newchangeurl"] - assert res['permissions_app.admin']['urls'] == [maindomain + "/newchangeurl/admin"] - assert res['permissions_app.dev']['urls'] == [maindomain + "/newchangeurl/dev"] + assert res['permissions_app.main']['urls'] == ["/"] + assert res['permissions_app.admin']['urls'] == ["/admin"] + assert res['permissions_app.dev']['urls'] == ["/dev"] def test_permission_app_propagation_on_ssowat(): From 7102c5d0ca1ce36a3ff90e3fcb688d4a5be0d221 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 20 Sep 2019 20:14:44 +0200 Subject: [PATCH 0174/3170] Propagate the new relative url stuff to app_ssowatconf and actuall implement the whole permission system thing in app_map (related to ssowatconf) --- src/yunohost/app.py | 126 ++++++++++++++++++++++---- src/yunohost/tests/test_permission.py | 3 + 2 files changed, 113 insertions(+), 16 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 231568439..51030021f 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -427,25 +427,97 @@ def app_map(app=None, raw=False, user=None): if 'path' not in app_settings: # we assume that an app that doesn't have a path doesn't have an HTTP api continue + # This 'no_sso' settings sound redundant to not having $path defined .... + # At least from what I can see, all apps using it don't have a path defined ... if 'no_sso' in app_settings: # I don't think we need to check for the value here continue + # Users must at least have access to the main permission to have access to extra permissions if user: main_perm = permissions[app_id + ".main"] if user not in main_perm["corresponding_users"] and "visitors" not in main_perm["allowed"]: continue domain = app_settings['domain'] - path = app_settings['path'] + path = app_settings['path'].rstrip('/') + label = app_settings['label'] - if raw: - if domain not in result: - result[domain] = {} - result[domain][path] = { - 'label': app_settings['label'], - 'id': app_settings['id'] - } - else: - result[domain + path] = app_settings['label'] + def _sanitized_absolute_url(perm_url): + # Nominal case : url is relative to the app's path + if perm_url.startswith("/"): + perm_domain = domain + perm_path = path + perm_url.rstrip("/") + # Otherwise, the urls starts with a domain name, like domain.tld/foo/bar + # We want perm_domain = domain.tld and perm_path = "/foo/bar" + else: + perm_domain, perm_path = perm_url.split("/", 1) + perm_path = "/" + perm_path.rstrip("/") + + return perm_domain, perm_path + + this_app_perms = {p: i for p, i in permissions.items() if p.startswith(app_id + ".") and i["urls"]} + for perm_name, perm_info in this_app_perms.items(): + # If we're building the map for a specific user, check the user + # actually is allowed for this specific perm + if user and user not in perm_info["corresponding_users"] and "visitors" not in perm_info["allowed"]: + continue + if len(perm_info["urls"]) > 1 or perm_info["urls"][0].startswith("re:"): + # + # Here we have a big conceptual issue about the sso ... + # Let me take a sip of coffee and turn off the music... + # + # Let's say we have an app foo which created a permission + # 'foo.admin' and added as url "/admin" and "/api" This + # permission got defined somehow as only accessible for group + # "admins". So both "/admin" and "/api" are protected. Good! + # + # Now if we really want users in group "admins" to access those + # uris, then each users in group "admins" need to have these + # urls in the ssowat dict for this user. Which corresponds to a + # tile. To put it otherwise : in the current code of ssowat, a + # permission = a tile = a url ! + # + # We also have an issue if the url define is a regex, because + # the url we want to add to the dict is going to be turned into + # a clickable link (or analyzed by other parts of yunohost + # code...). To put it otherwise : in the current code of ssowat, + # you can't give access a user to a regex + # + # Instead, as drafted by Josue, we could rework the ssowat logic + # about how routes and their permissions are defined. So for example, + # have a dict of + # { "/route1": ["visitors", "user1", "user2", ...], # Public route + # "/route2_with_a_regex$": ["user1", "user2"], # Private route + # "/route3": None, # Skipped route idk + # } + # then each time a user try to request and url, we only keep the + # longest matching rule and check the user is allowed etc... + # + # The challenge with this is (beside actually implementing it) + # is that it creates a whole new mechanism that ultimately + # replace all the existing logic about + # protected/unprotected/skipped uris and regexes and we gotta + # handle / migrate all the legacy stuff somehow if we don't + # want to end up with a total mess in the future idk + logger.error("Permission %s can't be added to the SSOwat configuration because it uses multiple urls and/or uses a regex url" % perm_name) + continue + + perm_domain, perm_path = _sanitized_absolute_url(perm_info["urls"][0]) + + if perm_name.endswith(".main"): + perm_label = label + else: + # e.g. if perm_name is wordpress.admin, we want "Blog (Admin)" (where Blog is the label of this app) + perm_label = "%s (%s)" % (label, perm_name.rsplit(".")[-1].replace("_", " ").title()) + + if raw: + if domain not in result: + result[perm_domain] = {} + result[perm_domain][perm_path] = { + 'label': perm_label, + 'id': app_id + } + else: + result[perm_domain + perm_path] = perm_label return result @@ -1382,13 +1454,34 @@ def app_ssowatconf(): app_settings = read_yaml(APPS_SETTING_PATH + app['id'] + '/settings.yml') + if 'domain' not in app_settings: + continue + if 'path' not in app_settings: + continue + + # This 'no_sso' settings sound redundant to not having $path defined .... + # At least from what I can see, all apps using it don't have a path defined ... if 'no_sso' in app_settings: continue - app_root_webpath = app_settings['domain'] + app_settings['path'].rstrip('/') + domain = app_settings['domain'] + path = app_settings['path'].rstrip('/') + + def _sanitized_absolute_url(perm_url): + # Nominal case : url is relative to the app's path + if perm_url.startswith("/"): + perm_domain = domain + perm_path = path + perm_url.rstrip("/") + # Otherwise, the urls starts with a domain name, like domain.tld/foo/bar + # We want perm_domain = domain.tld and perm_path = "/foo/bar" + else: + perm_domain, perm_path = perm_url.split("/", 1) + perm_path = "/" + perm_path.rstrip("/") + + return perm_domain + perm_path # Skipped - skipped_urls += [app_root_webpath + uri.rstrip("/") for uri in _get_setting(app_settings, 'skipped_uris')] + skipped_urls += [_sanitized_absolute_url(uri) for uri in _get_setting(app_settings, 'skipped_uris')] skipped_regex += _get_setting(app_settings, 'skipped_regex') # Redirected @@ -1396,15 +1489,16 @@ def app_ssowatconf(): redirected_regex.update(app_settings.get('redirected_regex', {})) # Legacy permission system using (un)protected_uris and _regex managed in app settings... - unprotected_urls += [app_root_webpath + uri.rstrip("/") for uri in _get_setting(app_settings, 'unprotected_uris')] - unprotected_urls += [app_root_webpath + uri.rstrip("/") for uri in _get_setting(app_settings, 'protected_uris')] + unprotected_urls += [_sanitized_absolute_url(uri) for uri in _get_setting(app_settings, 'unprotected_uris')] + protected_urls += [_sanitized_absolute_url(uri) for uri in _get_setting(app_settings, 'protected_uris')] unprotected_regex += _get_setting(app_settings, 'unprotected_regex') - unprotected_regex += _get_setting(app_settings, 'protected_regex') + protected_regex += _get_setting(app_settings, 'protected_regex') # New permission system this_app_perms = {name: info for name, info in all_permissions.items() if name.startswith(app['id'] + ".")} for perm_name, perm_info in this_app_perms.items(): - urls = [url.rstrip("/") for url in perm_info["urls"]] + # FIXME : gotta handle regex-urls here... meh + urls = [_sanitized_absolute_url(url) for url in perm_info["urls"]] if "visitors" in perm_info["allowed"]: unprotected_urls += urls diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index a8b6b8258..4dc021496 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -434,6 +434,9 @@ def test_permission_app_propagation_on_ssowat(): # Test admin access, as configured during install, only alice should be able to access it + # alice gotta be allowed on the main permission to access the admin tho + user_permission_update("permissions_app.main", remove="bob", add="all_users") + assert not can_access_webpage(app_webroot+"/admin", logged_as=None) assert can_access_webpage(app_webroot+"/admin", logged_as="alice") assert not can_access_webpage(app_webroot+"/admin", logged_as="bob") From 545f697df2378d138ce7af73ef985df170f445e1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 20 Sep 2019 21:56:44 +0200 Subject: [PATCH 0175/3170] When using the legacy adduser function, remove all_users for backward compatibility --- 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 ab290cb4d..ae6d27b20 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1039,7 +1039,7 @@ def app_addaccess(apps, users=[]): output = {} for app in apps: - permission = user_permission_update(app+".main", add=users) + permission = user_permission_update(app+".main", add=users, remove="all_users") output[app] = permission["corresponding_users"] return {'allowed_users': output} From e2e960175b5a9839cb1c30587a16f7c1d4a44dd2 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 21 Sep 2019 13:16:28 +0200 Subject: [PATCH 0176/3170] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update descriptions for action map Co-Authored-By: Allan Nordhøy --- data/actionsmap/yunohost.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index e51f23a14..97489a841 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -270,11 +270,11 @@ user: ### user_group_info() info: - action_help: Get information for a specific group + action_help: Get information about a specific group api: GET /users/groups/ arguments: groupname: - help: Name of the group to get info about + help: Name of the group to fetch info about extra: pattern: *pattern_username @@ -289,11 +289,11 @@ user: arguments: -s: full: --short - help: List only the names of permissions + help: Only list permission names action: store_true -f: full: --full - help: Display all informations known about each permissions, including the full list of users corresponding to allowed groups. + help: Display all info known about each permission, including the full user list of each group it is granted to. action: store_true @@ -306,14 +306,14 @@ user: help: Permission to manage (e.g. mail or nextcloud or wordpress.editors) -a: full: --add - help: Group or user names to add to this permission + help: Group or usernames to grant this permission to nargs: "*" metavar: GROUP_OR_USER extra: pattern: *pattern_username -r: full: --remove - help: Group or user names to remove from this permission + help: Group or usernames revoke this permission from nargs: "*" metavar: GROUP_OR_USER extra: From 2a5053b66bfc7bd48cd8f7668db1ee93b1103fe7 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 21 Sep 2019 13:32:40 +0200 Subject: [PATCH 0177/3170] Misc wording and orthotypography... MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Allan Nordhøy --- data/helpers.d/setting | 8 ++++---- locales/en.json | 2 +- src/yunohost/permission.py | 12 ++++++------ src/yunohost/tests/test_permission.py | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index 9deba2dc0..b2a647993 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -177,7 +177,7 @@ else: if key in ['redirected_urls', 'redirected_regex']: value = yaml.load(value) if key in ["unprotected_uris", "unprotected_regex", "protected_uris", "protected_regex"]: - logger.warning("/!\\ Packagers ! This app is using the legacy permission system. Please delete these legacy settings and use the new helpers ynh_permission_{create,urls,update,delete} and the 'visitors' group to manage public/private access.") + logger.warning("/!\\ Packagers! This app is using the legacy permission system. Please delete these legacy settings and use the new helpers 'ynh_permission_{create,urls,update,delete}' and the 'visitors' group to manage public/private access.") settings[key] = value else: raise ValueError("action should either be get, set or delete") @@ -237,15 +237,15 @@ ynh_webpath_register () { # usage: ynh_permission_create --permission "permission" [--urls "url" ["url" ...]] # | arg: permission - the name for the permission (by default a permission named "main" already exist) # | arg: urls - (optional) a list of FULL urls for the permission (e.g. domain.tld/apps/admin) -# | arg: urls - (optional) a list of urls to specify for the permission. +# | arg: urls - (optional) a list of URLs to specify for the permission. # -# Urls are assumed to be relative to the app domain/path if they start with '/'. +# URLs are assumed to be relative to the app domain/path if they start with '/'. # For example: # / -> domain.tld/app # /admin -> domain.tld/app/admin # domain.tld/app/api -> domain.tld/app/api # -# Urls can be treated as regexes when they start with "re:". +# URLs can be treated as regexes when they start with "re:". # For example: # re:/api/[A-Z]*$ -> domain.tld/app/api/[A-Z]*$ # re:domain.tld/app/api/[A-Z]*$ -> domain.tld/app/api/[A-Z]*$ diff --git a/locales/en.json b/locales/en.json index 5df21b684..70b9fa626 100644 --- a/locales/en.json +++ b/locales/en.json @@ -230,7 +230,7 @@ "group_already_exist_on_system": "Group {group} already exists in the system group", "group_created": "Group '{group}' successfully created", "group_creation_failed": "Failed to create group {group}: {error}", - "group_cannot_edit_all_users": "The group 'all_users' cannot be edited manually. It is a special group meant to contain all users registered in Yunohost", + "group_cannot_edit_all_users": "The group 'all_users' cannot be edited manually. It is a special group meant to contain all users registered in YunoHost", "group_cannot_edit_visitors": "The group 'visitors' cannot be edited manually. It is a special group representing anonymous visitors", "group_cannot_edit_primary_group": "The group '{group}' cannot be edited manually. It is the primary group meant to contain only one specific user.", "group_cannot_be_edited": "The group {group} cannot be edited manually.", diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 5f9a88e11..75e3f6037 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -146,11 +146,11 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, if "all_users" in new_allowed_groups: # FIXME : i18n # FIXME : write a better explanation ? - logger.warning("This permission is currently enabled for all users in addition to other groups. You probably want to either remove the 'all_users' permission or remove the other groups currently allowed.") + logger.warning("This permission is currently granted to all users in addition to other groups. You probably want to either remove the 'all_users' permission or remove the other groups it is currently granted to.") if "visitors" in new_allowed_groups: # FIXME : i18n # FIXME : write a better explanation ? - logger.warning("This permission is currently enabled for visitors in addition to other groups. You probably want to either remove the 'visitors' permission or remove the other groups currently allowed.") + logger.warning("This permission is currently granted to visitors in addition to other groups. You probably want to either remove the 'visitors' permission or remove the other groups it is currently granted to.") # Don't update LDAP if we update exactly the same values if set(new_allowed_groups) == set(current_allowed_groups): @@ -268,7 +268,7 @@ def permission_create(operation_logger, permission, urls=None, sync_perm=True): Keyword argument: permission -- Name of the permission (e.g. mail or nextcloud or wordpress.editors) - urls -- list of urls to specify for the permission. + urls -- list of URLs to specify for the permission. Urls are assumed to be relative to the app domain/path if they start with '/'. For example: @@ -276,7 +276,7 @@ def permission_create(operation_logger, permission, urls=None, sync_perm=True): /admin -> domain.tld/app/admin domain.tld/app/api -> domain.tld/app/api - Urls can be later treated as regexes when they start with "re:". + URLs can be later treated as regexes when they start with "re:". For example: re:/api/[A-Z]*$ -> domain.tld/app/api/[A-Z]*$ re:domain.tld/app/api/[A-Z]*$ -> domain.tld/app/api/[A-Z]*$ @@ -337,8 +337,8 @@ def permission_urls(operation_logger, permission, add=None, remove=None, sync_pe Keyword argument: permission -- Name of the permission (e.g. mail or nextcloud or wordpress.editors) - add -- List of urls to add (c.f. permission_create for documentation about their format) - remove -- List of urls to remove (c.f. permission_create for documentation about their format) + add -- List of URLs to add (c.f. permission_create for documentation about their format) + remove -- List of URLs to remove (c.f. permission_create for documentation about their format) """ from yunohost.utils.ldap import _get_ldap_interface diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index 4dc021496..f17313fa1 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -180,7 +180,7 @@ def can_access_webpage(webpath, logged_as=None): assert session.cookies r = session.get(webpath, verify=False) - # If we can't access it, we got redirected to the sso + # If we can't access it, we got redirected to the SSO return not r.url.startswith(sso_url) # From adc92388ab9b73bc9fab2436632745d469e88923 Mon Sep 17 00:00:00 2001 From: nr 458 h Date: Sat, 21 Sep 2019 18:39:59 +0000 Subject: [PATCH 0178/3170] Translated using Weblate (German) Currently translated at 24.4% (143 of 586 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 50 ++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/locales/de.json b/locales/de.json index cef591411..5a3fa183e 100644 --- a/locales/de.json +++ b/locales/de.json @@ -2,22 +2,22 @@ "action_invalid": "Ungültige Aktion '{action:s}'", "admin_password": "Administrator-Passwort", "admin_password_change_failed": "Passwort kann nicht geändert werden", - "admin_password_changed": "Das Administrator-Kennwort wurde erfolgreich geändert", + "admin_password_changed": "Das Administrator-Kennwort wurde geändert", "app_already_installed": "{app:s} ist schon installiert", "app_argument_choice_invalid": "Ungültige Auswahl für Argument '{name:s}'. Es muss einer der folgenden Werte sein {choices:s}", "app_argument_invalid": "Das Argument '{name:s}' hat einen falschen Wert: {error:s}", "app_argument_required": "Argument '{name:s}' wird benötigt", "app_extraction_failed": "Installationsdateien konnten nicht entpackt werden", "app_id_invalid": "Falsche App-ID", - "app_install_files_invalid": "Ungültige Installationsdateien", + "app_install_files_invalid": "Diese Dateien können nicht installiert werden", "app_location_already_used": "Eine andere App ({app}) ist bereits an diesem Ort ({path}) installiert", "app_location_install_failed": "Die App kann nicht an diesem Ort installiert werden, da es mit der App {other_app} die bereits in diesem Pfad ({other_path}) installiert ist Probleme geben würde", "app_manifest_invalid": "Ungültiges App-Manifest: {error}", "app_no_upgrade": "Keine Aktualisierungen für Apps verfügbar", - "app_not_installed": "{app:s} ist nicht installiert", + "app_not_installed": "Die App {app:s} konnte nicht in der Liste installierter Apps gefunden werden: {all_apps}", "app_recent_version_required": "Für {:s} benötigt eine aktuellere Version von moulinette", - "app_removed": "{app:s} wurde erfolgreich entfernt", - "app_sources_fetch_failed": "Quelldateien konnten nicht abgerufen werden", + "app_removed": "{app:s} wurde entfernt", + "app_sources_fetch_failed": "Quelldateien konnten nicht abgerufen werden, ist die URL korrekt?", "app_unknown": "Unbekannte App", "app_upgrade_failed": "{app:s} konnte nicht aktualisiert werden", "app_upgraded": "{app:s} wurde erfolgreich aktualisiert", @@ -42,7 +42,7 @@ "backup_archive_open_failed": "Kann Sicherungsarchiv nicht öfnen", "backup_cleaning_failed": "Temporäres Sicherungsverzeichnis konnte nicht geleert werden", "backup_created": "Datensicherung komplett", - "backup_creating_archive": "Datensicherung wird erstellt...", + "backup_creating_archive": "Datensicherung wird erstellt…", "backup_delete_error": "Pfad '{path:s}' konnte nicht gelöscht werden", "backup_deleted": "Datensicherung wurde entfernt", "backup_extracting_archive": "Entpacke Sicherungsarchiv...", @@ -53,7 +53,7 @@ "backup_output_directory_not_empty": "Ausgabeordner ist nicht leer", "backup_output_directory_required": "Für die Datensicherung muss ein Zielverzeichnis angegeben werden", "backup_running_app_script": "Datensicherung für App '{app:s}' wurd durchgeführt...", - "backup_running_hooks": "Datensicherunghook wird ausgeführt...", + "backup_running_hooks": "Datensicherunghook wird ausgeführt…", "custom_app_url_required": "Es muss eine URL angegeben werden, um deine benutzerdefinierte App {app:s} zu aktualisieren", "custom_appslist_name_required": "Du musst einen Namen für deine benutzerdefinierte Appliste angeben", "dnsmasq_isnt_installed": "dnsmasq scheint nicht installiert zu sein. Bitte führe 'apt-get remove bind9 && apt-get install dnsmasq' aus", @@ -71,7 +71,7 @@ "domain_zone_exists": "DNS Zonen Datei existiert bereits", "domain_zone_not_found": "DNS Zonen Datei kann nicht für Domäne {:s} gefunden werden", "done": "Erledigt", - "downloading": "Wird heruntergeladen...", + "downloading": "Wird heruntergeladen…", "dyndns_cron_installed": "DynDNS Cronjob erfolgreich angelegt", "dyndns_cron_remove_failed": "Der DynDNS Cronjob konnte nicht entfernt werden", "dyndns_cron_removed": "Der DynDNS Cronjob wurde gelöscht", @@ -81,9 +81,9 @@ "dyndns_registered": "Deine DynDNS Domain wurde registriert", "dyndns_registration_failed": "DynDNS Domain konnte nicht registriert werden: {error:s}", "dyndns_unavailable": "DynDNS Subdomain ist nicht verfügbar", - "executing_command": "Führe den Behfehl '{command:s}' aus...", - "executing_script": "Skript '{script:s}' wird ausgeührt...", - "extracting": "Wird entpackt...", + "executing_command": "Führe den Behfehl '{command:s}' aus…", + "executing_script": "Skript '{script:s}' wird ausgeührt…", + "extracting": "Wird entpackt…", "field_invalid": "Feld '{:s}' ist unbekannt", "firewall_reload_failed": "Die Firewall konnte nicht neu geladen werden", "firewall_reloaded": "Die Firewall wurde neu geladen", @@ -156,7 +156,7 @@ "restore_hook_unavailable": "Das Wiederherstellungsskript für '{part:s}' steht weder in deinem System noch im Archiv zur Verfügung", "restore_nothings_done": "Es wurde nicht wiederhergestellt", "restore_running_app_script": "Wiederherstellung wird ausfeührt für App '{app:s}'...", - "restore_running_hooks": "Wiederherstellung wird gestartet...", + "restore_running_hooks": "Wiederherstellung wird gestartet…", "service_add_configuration": "Füge Konfigurationsdatei {file:s} hinzu", "service_add_failed": "Der Dienst '{service:s}' kann nicht hinzugefügt werden", "service_added": "Der Service '{service:s}' wurde erfolgreich hinzugefügt", @@ -189,9 +189,9 @@ "unlimit": "Kein Kontingent", "unrestore_app": "App '{app:s}' kann nicht Wiederhergestellt werden", "update_cache_failed": "Konnte APT cache nicht aktualisieren", - "updating_apt_cache": "Die Liste der verfügbaren Pakete wird aktualisiert...", + "updating_apt_cache": "Die Liste der verfügbaren Pakete wird aktualisiert…", "upgrade_complete": "Upgrade vollständig", - "upgrading_packages": "Pakete werden aktualisiert...", + "upgrading_packages": "Pakete werden aktualisiert…", "upnp_dev_not_found": "Es konnten keine UPnP Geräte gefunden werden", "upnp_disabled": "UPnP wurde deaktiviert", "upnp_enabled": "UPnP wurde aktiviert", @@ -208,7 +208,7 @@ "yunohost_already_installed": "YunoHost ist bereits installiert", "yunohost_ca_creation_failed": "Zertifikatsstelle konnte nicht erstellt werden", "yunohost_configured": "YunoHost wurde konfiguriert", - "yunohost_installing": "YunoHost wird installiert...", + "yunohost_installing": "YunoHost wird installiert…", "yunohost_not_installed": "YunoHost ist nicht oder unvollständig installiert worden. Bitte 'yunohost tools postinstall' ausführen", "app_not_properly_removed": "{app:s} wurde nicht ordnungsgemäß entfernt", "service_regenconf_failed": "Konnte die Konfiguration für folgende Dienste nicht neu erzeugen: {services}", @@ -221,7 +221,7 @@ "package_unexpected_error": "Ein unerwarteter Fehler trat bei der Verarbeitung des Pakets '{pkgname}' auf", "app_incompatible": "Die Anwendung {app} ist nicht mit deiner YunoHost-Version kompatibel", "app_not_correctly_installed": "{app:s} scheint nicht korrekt installiert zu sein", - "app_requirements_checking": "Überprüfe notwendige Pakete für {app}...", + "app_requirements_checking": "Überprüfe notwendige Pakete für {app}…", "app_requirements_failed": "Anforderungen für {app} werden nicht erfüllt: {error}", "app_requirements_unmeet": "Anforderungen für {app} werden nicht erfüllt, das Paket {pkgname} ({version}) muss {spec} sein", "app_unsupported_remote_type": "Für die App wurde ein nicht unterstützer Steuerungstyp verwendet", @@ -281,7 +281,7 @@ "domain_hostname_failed": "Erstellen des neuen Hostnamens fehlgeschlagen", "appslist_name_already_tracked": "Es gibt bereits eine registrierte App-Liste mit Namen {name:s}.", "appslist_url_already_tracked": "Es gibt bereits eine registrierte Anwendungsliste mit dem URL {url:s}.", - "appslist_migrating": "Migriere Anwendungsliste {appslist:s} ...", + "appslist_migrating": "Migriere Anwendungsliste {appslist:s} …", "appslist_could_not_migrate": "Konnte Anwendungsliste {appslist:s} nicht migrieren. Konnte die URL nicht verarbeiten... Der alte Cron-Job wurde unter {bkp_file:s} beibehalten.", "appslist_corrupted_json": "Konnte die Anwendungslisten. Es scheint, dass {filename:s} beschädigt ist.", "yunohost_ca_creation_success": "Die lokale Zertifizierungs-Authorität wurde angelegt.", @@ -292,15 +292,15 @@ "app_already_up_to_date": "{app:s} ist schon aktuell", "backup_abstract_method": "Diese Backup-Methode wird noch nicht unterstützt", "backup_applying_method_tar": "Erstellen des Backup-tar Archives...", - "backup_applying_method_copy": "Kopiere alle Dateien ins Backup...", - "app_change_url_no_script": "Die Anwendung '{app_name:s}' unterstützt bisher keine URL-Modufikation. Vielleicht gibt es eine Aktualisierung der Anwendung.", + "backup_applying_method_copy": "Kopiere alle Dateien ins Backup…", + "app_change_url_no_script": "Die Anwendung '{app_name:s}' unterstützt bisher keine URL-Modufikation. Vielleicht gibt es eine Aktualisierung.", "app_location_unavailable": "Diese URL ist nicht verfügbar oder wird von einer installierten Anwendung genutzt:\n{apps:s}", - "backup_applying_method_custom": "Rufe die benutzerdefinierte Backup-Methode '{method:s}' auf...", + "backup_applying_method_custom": "Rufe die benutzerdefinierte Backup-Methode '{method:s}' auf…", "backup_archive_system_part_not_available": "Der System-Teil '{part:s}' ist in diesem Backup nicht enthalten", "backup_archive_mount_failed": "Das Einbinden des Backup-Archives ist fehlgeschlagen", "backup_archive_writing_error": "Die Dateien konnten nicht in der komprimierte Archiv-Backup hinzugefügt werden", - "app_change_url_success": "Erfolgreiche Änderung der URL von {app:s} zu {domain:s}{path:s}", - "backup_applying_method_borg": "Sende alle Dateien zur Sicherung ins borg-backup repository...", + "app_change_url_success": "{app:s} URL ist nun {domain:s}{path:s}", + "backup_applying_method_borg": "Sende alle Dateien zur Sicherung ins borg-backup repository…", "invalid_url_format": "ungültiges URL Format", "global_settings_bad_type_for_setting": "Falscher Typ für Einstellung {setting: s}. Empfangen: {receive_type: s}, aber erwartet: {expected_type: s}", "global_settings_bad_choice_for_enum": "Falsche Wahl für die Einstellung {setting: s}. Habe '{choice: s}' erhalten, aber es stehen nur folgende Auswahlmöglichkeiten zur Verfügung: {available_choices: s}", @@ -343,7 +343,7 @@ "apps_permission_restoration_failed": "Die Berechtigung '{permission: s}' für die Wiederherstellung der App {app: s} ist fehlgeschlagen", "apps_permission_not_found": "Keine Berechtigung für die installierten Apps gefunden", "app_upgrade_some_app_failed": "Einige Anwendungen können nicht aktualisiert werden", - "app_upgrade_app_name": "App {App} wird jetzt aktualisiert…", + "app_upgrade_app_name": "{App} wird jetzt aktualisiert…", "app_upgrade_several_apps": "Die folgenden Apps werden aktualisiert: {apps}", "app_start_restore": "Anwendung {app} wird wiederhergestellt…", "app_start_backup": "Sammeln von Dateien, die für {app} gesichert werden sollen…", @@ -353,6 +353,6 @@ "app_make_default_location_already_used": "Die App \"{app}\" kann nicht als Standard für die Domain \"{domain}\" festgelegt werden. Sie wird bereits von der anderen App \"{other_app}\" verwendet", "aborting": "Breche ab.", "app_action_cannot_be_ran_because_required_services_down": "Diese App erfordert einige Dienste, die derzeit nicht verfügbar sind. Bevor Sie fortfahren, sollten Sie versuchen, die folgenden Dienste neu zu starten (und möglicherweise untersuchen, warum sie nicht verfügbar sind): {services}", - "already_up_to_date": "Nichts zu tun! Alles ist bereits auf dem neusten Stand!", - "admin_password_too_long": "Bitte ein Passwort kürzer als 127 Zeichen wählen." + "already_up_to_date": "Nichts zu tun. Alles ist bereits auf dem neusten Stand.", + "admin_password_too_long": "Bitte ein Passwort kürzer als 127 Zeichen wählen" } From 4bc59cf8cbde0f09f26390adfcd179b94a0ba1d5 Mon Sep 17 00:00:00 2001 From: Aksel Kiesling Date: Sun, 22 Sep 2019 05:34:02 +0000 Subject: [PATCH 0179/3170] Translated using Weblate (German) Currently translated at 24.4% (143 of 586 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/locales/de.json b/locales/de.json index 5a3fa183e..10087e12f 100644 --- a/locales/de.json +++ b/locales/de.json @@ -4,16 +4,16 @@ "admin_password_change_failed": "Passwort kann nicht geändert werden", "admin_password_changed": "Das Administrator-Kennwort wurde geändert", "app_already_installed": "{app:s} ist schon installiert", - "app_argument_choice_invalid": "Ungültige Auswahl für Argument '{name:s}'. Es muss einer der folgenden Werte sein {choices:s}", - "app_argument_invalid": "Das Argument '{name:s}' hat einen falschen Wert: {error:s}", + "app_argument_choice_invalid": "Verwende einen der folgenden Werte {choices:s}", + "app_argument_invalid": "Wähle einen gültigen Wert für das Argument '{name: s}': {error: s}", "app_argument_required": "Argument '{name:s}' wird benötigt", "app_extraction_failed": "Installationsdateien konnten nicht entpackt werden", "app_id_invalid": "Falsche App-ID", "app_install_files_invalid": "Diese Dateien können nicht installiert werden", - "app_location_already_used": "Eine andere App ({app}) ist bereits an diesem Ort ({path}) installiert", - "app_location_install_failed": "Die App kann nicht an diesem Ort installiert werden, da es mit der App {other_app} die bereits in diesem Pfad ({other_path}) installiert ist Probleme geben würde", - "app_manifest_invalid": "Ungültiges App-Manifest: {error}", - "app_no_upgrade": "Keine Aktualisierungen für Apps verfügbar", + "app_location_already_used": "Die App ({app}) ist bereits hier ({path}) installiert", + "app_location_install_failed": "Die App kann dort nicht installiert werden, da ein Konflikt mit der App '{other_app}' besteht, die bereits in '{other_path}' installiert ist", + "app_manifest_invalid": "Mit dem App-Manifest stimmt etwas nicht: {error}", + "app_no_upgrade": "Alle Apps sind bereits aktuell", "app_not_installed": "Die App {app:s} konnte nicht in der Liste installierter Apps gefunden werden: {all_apps}", "app_recent_version_required": "Für {:s} benötigt eine aktuellere Version von moulinette", "app_removed": "{app:s} wurde entfernt", @@ -294,7 +294,7 @@ "backup_applying_method_tar": "Erstellen des Backup-tar Archives...", "backup_applying_method_copy": "Kopiere alle Dateien ins Backup…", "app_change_url_no_script": "Die Anwendung '{app_name:s}' unterstützt bisher keine URL-Modufikation. Vielleicht gibt es eine Aktualisierung.", - "app_location_unavailable": "Diese URL ist nicht verfügbar oder wird von einer installierten Anwendung genutzt:\n{apps:s}", + "app_location_unavailable": "Diese URL ist entweder nicht verfügbar oder steht in Konflikt mit den bereits installierten Apps:\n{apps: s}", "backup_applying_method_custom": "Rufe die benutzerdefinierte Backup-Methode '{method:s}' auf…", "backup_archive_system_part_not_available": "Der System-Teil '{part:s}' ist in diesem Backup nicht enthalten", "backup_archive_mount_failed": "Das Einbinden des Backup-Archives ist fehlgeschlagen", @@ -349,7 +349,7 @@ "app_start_backup": "Sammeln von Dateien, die für {app} gesichert werden sollen…", "app_start_remove": "Anwendung {app} wird entfernt…", "app_start_install": "Anwendung {app} wird installiert…", - "app_not_upgraded": "Die folgenden Apps wurden nicht aktualisiert: {apps}", + "app_not_upgraded": "Die App '{failed_app}' konnte nicht aktualisiert werden. Infolgedessen wurden die folgenden App-Upgrades abgebrochen: {apps}", "app_make_default_location_already_used": "Die App \"{app}\" kann nicht als Standard für die Domain \"{domain}\" festgelegt werden. Sie wird bereits von der anderen App \"{other_app}\" verwendet", "aborting": "Breche ab.", "app_action_cannot_be_ran_because_required_services_down": "Diese App erfordert einige Dienste, die derzeit nicht verfügbar sind. Bevor Sie fortfahren, sollten Sie versuchen, die folgenden Dienste neu zu starten (und möglicherweise untersuchen, warum sie nicht verfügbar sind): {services}", From 0da83270e23d5796302244f53dd2486a04745902 Mon Sep 17 00:00:00 2001 From: advocatux Date: Fri, 20 Sep 2019 12:35:42 +0000 Subject: [PATCH 0180/3170] Translated using Weblate (Spanish) Currently translated at 100.0% (586 of 586 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/es/ --- locales/es.json | 658 ++++++++++++++++++++++++------------------------ 1 file changed, 330 insertions(+), 328 deletions(-) diff --git a/locales/es.json b/locales/es.json index d028a7a8e..d219b36e1 100644 --- a/locales/es.json +++ b/locales/es.json @@ -2,36 +2,36 @@ "action_invalid": "Acción no válida '{action:s} 1'", "admin_password": "Contraseña administrativa", "admin_password_change_failed": "No se puede cambiar la contraseña", - "admin_password_changed": "La contraseña administrativa ha sido cambiada", - "app_already_installed": "{app:s} 2 ya está instalada", - "app_argument_choice_invalid": "Opción no válida para el argumento '{name:s} 3', deber una de {choices:s} 4", - "app_argument_invalid": "Valor no válido para el argumento '{name:s} 5': {error:s} 6", + "admin_password_changed": "La contraseña de administración ha sido cambiada", + "app_already_installed": "{app:s} ya está instalada", + "app_argument_choice_invalid": "Use una de estas opciones «{choices:s}» para el argumento «{name:s}»", + "app_argument_invalid": "Elija un valor válido para el argumento «{name:s}»: {error:s}", "app_argument_required": "Se requiere el argumento '{name:s} 7'", "app_extraction_failed": "No se pudieron extraer los archivos de instalación", - "app_id_invalid": "Id de la aplicación no válida", + "app_id_invalid": "ID de la aplicación no válida", "app_incompatible": "La aplicación {app} no es compatible con su versión de YunoHost", - "app_install_files_invalid": "Los archivos de instalación no son válidos", - "app_location_already_used": "La aplicación {app} ya está instalada en esta localización ({path})", - "app_location_install_failed": "No se puede instalar la aplicación en esta localización porque entra en conflicto con la aplicación '{other_app}' ya instalada en '{other_path}'", - "app_manifest_invalid": "El manifiesto de la aplicación no es válido: {error}", - "app_no_upgrade": "No hay aplicaciones para actualizar", + "app_install_files_invalid": "Estos archivos no se pueden instalar", + "app_location_already_used": "La aplicación «{app}» ya está instalada en ({path})", + "app_location_install_failed": "No se puede instalar la aplicación ahí porque entra en conflicto con la aplicación «{other_app}» ya instalada en «{other_path}»", + "app_manifest_invalid": "Algo va mal con el manifiesto de la aplicación: {error}", + "app_no_upgrade": "Todas las aplicaciones están ya actualizadas", "app_not_correctly_installed": "La aplicación {app:s} 8 parece estar incorrectamente instalada", - "app_not_installed": "La aplicación «{app:s}» no está instalada. Esta es la lista de todas las aplicaciones instaladas: {all_apps}", + "app_not_installed": "No se pudo encontrar la aplicación «{app:s}» en la lista de aplicaciones instaladas: {all_apps}", "app_not_properly_removed": "La {app:s} 0 no ha sido desinstalada correctamente", "app_package_need_update": "El paquete de la aplicación {app} necesita ser actualizada debido a los cambios en YunoHost", "app_recent_version_required": "{:s} requiere una versión más reciente de moulinette ", - "app_removed": "{app:s} ha sido eliminada", + "app_removed": "Eliminado {app:s}", "app_requirements_checking": "Comprobando los paquetes necesarios para {app}…", - "app_requirements_failed": "No se cumplen los requisitos para {app}: {error}", + "app_requirements_failed": "No se cumplen algunos requisitos para {app}: {error}", "app_requirements_unmeet": "No se cumplen los requisitos para {app}, el paquete {pkgname} ({version}) debe ser {spec}", - "app_sources_fetch_failed": "No se pudo obtener los archivos con el código fuente, ¿es la URL correcta?", + "app_sources_fetch_failed": "No se pudieron obtener los archivos con el código fuente, ¿es el URL correcto?", "app_unknown": "Aplicación desconocida", "app_unsupported_remote_type": "Tipo remoto no soportado por la aplicación", - "app_upgrade_failed": "No se pudo actualizar la aplicación {app:s}", - "app_upgraded": "{app:s} ha sido actualizada", - "appslist_fetched": "La lista de aplicaciones {appslist:s} ha sido descargada", - "appslist_removed": "La lista de aplicaciones {appslist:s} ha sido eliminada", - "appslist_retrieve_error": "No se pudo recuperar la lista remota de aplicaciones {appslist:s} : {error:s}", + "app_upgrade_failed": "No se pudo actualizar {app:s}", + "app_upgraded": "Actualizado {app:s}", + "appslist_fetched": "Obtenida lista de aplicaciones {appslist:s} actualizada", + "appslist_removed": "Eliminada la lista de aplicaciones {appslist:s}", + "appslist_retrieve_error": "No se puede recuperar la lista remota de aplicaciones {appslist:s}: {error:s}", "appslist_unknown": "Lista de aplicaciones {appslist:s} desconocida.", "ask_current_admin_password": "Contraseña administrativa actual", "ask_email": "Dirección de correo electrónico", @@ -42,43 +42,43 @@ "ask_new_admin_password": "Nueva contraseña administrativa", "ask_password": "Contraseña", "backup_action_required": "Debe especificar algo que guardar", - "backup_app_failed": "No es posible realizar la copia de seguridad de la aplicación '{app:s}'", - "backup_archive_app_not_found": "La aplicación '{app:s}' no ha sido encontrada en la copia de seguridad", + "backup_app_failed": "No se pudo respaldar la aplicación «{app:s}»", + "backup_archive_app_not_found": "No se pudo encontrar la aplicación «{app:s}» en el archivo de respaldo", "backup_archive_hook_not_exec": "El hook {hook:s} no ha sido ejecutado en esta copia de seguridad", - "backup_archive_name_exists": "Ya existe una copia de seguridad con ese nombre", + "backup_archive_name_exists": "Ya existe un archivo de respaldo con este nombre.", "backup_archive_name_unknown": "Copia de seguridad local desconocida '{name:s}'", - "backup_archive_open_failed": "No se pudo abrir la copia de seguridad", - "backup_cleaning_failed": "No se puede limpiar el directorio temporal de copias de seguridad", + "backup_archive_open_failed": "No se pudo abrir el archivo de respaldo", + "backup_cleaning_failed": "No se pudo limpiar la carpeta de respaldo temporal", "backup_created": "Se ha creado la copia de seguridad", "backup_creating_archive": "Creando el archivo de copia de seguridad…", - "backup_creation_failed": "No se pudo crear la copia de seguridad", - "backup_delete_error": "No se puede eliminar '{path:s}'", - "backup_deleted": "La copia de seguridad ha sido eliminada", - "backup_extracting_archive": "Extrayendo el archivo de la copia de seguridad…", - "backup_hook_unknown": "Hook de copia de seguridad desconocido '{hook:s}'", - "backup_invalid_archive": "La copia de seguridad no es válida", - "backup_nothings_done": "No hay nada que guardar", - "backup_output_directory_forbidden": "Directorio de salida no permitido. Las copias de seguridad no pueden ser creadas en /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var o en los subdirectorios de /home/yunohost.backup/archives", - "backup_output_directory_not_empty": "El directorio de salida no está vacío", + "backup_creation_failed": "No se pudo crear el archivo de respaldo", + "backup_delete_error": "No se pudo eliminar «{path:s}»", + "backup_deleted": "Eliminada la copia de seguridad", + "backup_extracting_archive": "Extrayendo el archivo de respaldo…", + "backup_hook_unknown": "El gancho «{hook:s}» de la copia de seguridad es desconocido", + "backup_invalid_archive": "Esto no es un archivo de respaldo", + "backup_nothings_done": "Nada que guardar", + "backup_output_directory_forbidden": "Elija un directorio de salida diferente. No se pueden crear copias de seguridad en las subcarpetas de /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var o /home/yunohost.backup/archives", + "backup_output_directory_not_empty": "Debe elegir un directorio de salida vacío", "backup_output_directory_required": "Debe proporcionar un directorio de salida para la copia de seguridad", "backup_running_app_script": "Ejecutando la script de copia de seguridad de la aplicación '{app:s}'...", "backup_running_hooks": "Ejecutando los hooks de copia de seguridad...", "custom_app_url_required": "Debe proporcionar una URL para actualizar su aplicación personalizada {app:s}", "custom_appslist_name_required": "Debe proporcionar un nombre para su lista de aplicaciones personalizadas", - "diagnosis_debian_version_error": "No se puede obtener la versión de Debian: {error}", - "diagnosis_kernel_version_error": "No se puede obtener la versión del kernel: {error}", - "diagnosis_monitor_disk_error": "No se pueden monitorizar los discos: {error}", - "diagnosis_monitor_network_error": "No se puede monitorizar la red: {error}", - "diagnosis_monitor_system_error": "No se puede monitorizar el sistema: {error}", + "diagnosis_debian_version_error": "No se pudo obtener la versión de Debian: {error}", + "diagnosis_kernel_version_error": "No se pudo obtener la versión del núcleo: {error}", + "diagnosis_monitor_disk_error": "No se pudieron monitorizar los discos: {error}", + "diagnosis_monitor_network_error": "No se pudo monitorizar la red: {error}", + "diagnosis_monitor_system_error": "No se pudo monitorizar el sistema: {error}", "diagnosis_no_apps": "Aplicación no instalada", - "dnsmasq_isnt_installed": "Parece que dnsmasq no está instalado, ejecute 'apt-get remove bind9 && apt-get install dnsmasq'", - "domain_cert_gen_failed": "No se pudo crear el certificado", - "domain_created": "El dominio ha sido creado", + "dnsmasq_isnt_installed": "Parece que dnsmasq no está instalado, ejecute «apt-get remove bind9 && apt-get install it»", + "domain_cert_gen_failed": "No se pudo generar el certificado", + "domain_created": "Dominio creado", "domain_creation_failed": "No se pudo crear el dominio", - "domain_deleted": "El dominio ha sido eliminado", - "domain_deletion_failed": "No se pudo borrar el dominio", - "domain_dyndns_already_subscribed": "Ya está suscrito a un dominio DynDNS", - "domain_dyndns_invalid": "Dominio no válido para usar con DynDNS", + "domain_deleted": "Dominio eliminado", + "domain_deletion_failed": "No se pudo eliminar el dominio", + "domain_dyndns_already_subscribed": "Ya se ha suscrito a un dominio de DynDNS", + "domain_dyndns_invalid": "Este dominio no se puede usar con DynDNS", "domain_dyndns_root_unknown": "Dominio raíz de DynDNS desconocido", "domain_exists": "El dominio ya existe", "domain_uninstall_app_first": "Una o más aplicaciones están instaladas en este dominio. Debe desinstalarlas antes de eliminar el dominio", @@ -87,63 +87,63 @@ "domain_zone_not_found": "No se ha encontrado el archivo de zona del DNS para el dominio [:s]", "done": "Hecho.", "downloading": "Descargando…", - "dyndns_cron_installed": "La tarea cron para DynDNS ha sido instalada", - "dyndns_cron_remove_failed": "No se pudo eliminar la tarea cron de DynDNS por: {error}", - "dyndns_cron_removed": "La tarea cron DynDNS ha sido eliminada", - "dyndns_ip_update_failed": "No se pudo actualizar la dirección IP en el DynDNS", - "dyndns_ip_updated": "Su dirección IP ha sido actualizada en el DynDNS", - "dyndns_key_generating": "Generando la clave del DNS. Esto podría tardar un rato…", + "dyndns_cron_installed": "Creado el trabajo de cron de DynDNS", + "dyndns_cron_remove_failed": "No se pudo eliminar el trabajo de cron de DynDNS por: {error}", + "dyndns_cron_removed": "Eliminado el trabajo de cron de DynDNS", + "dyndns_ip_update_failed": "No se pudo actualizar la dirección IP en DynDNS", + "dyndns_ip_updated": "Actualizada su IP en DynDNS", + "dyndns_key_generating": "Generando la clave del DNS. Esto podría tardar un rato.", "dyndns_key_not_found": "No se ha encontrado la clave DNS para el dominio", - "dyndns_no_domain_registered": "Ningún dominio ha sido registrado con DynDNS", - "dyndns_registered": "El dominio DynDNS ha sido registrado", - "dyndns_registration_failed": "No se pudo registrar el dominio DynDNS: {error:s}", - "dyndns_unavailable": "El dominio {domain:s} no está disponible.", + "dyndns_no_domain_registered": "Ningún dominio registrado con DynDNS", + "dyndns_registered": "Registrado dominio de DynDNS", + "dyndns_registration_failed": "No se pudo registrar el dominio de DynDNS: {error:s}", + "dyndns_unavailable": "El dominio «{domain:s}» no está disponible.", "executing_command": "Ejecutando la orden «{command:s}»…", "executing_script": "Ejecutando el guión «{script:s}»…", "extracting": "Extrayendo…", "field_invalid": "Campo no válido '{:s}'", "firewall_reload_failed": "No se pudo recargar el cortafuegos", - "firewall_reloaded": "El cortafuegos ha sido recargado", - "firewall_rules_cmd_failed": "No se pudieron aplicar algunas reglas del cortafuegos. Para más información consulte el registro.", + "firewall_reloaded": "Cortafuegos recargado", + "firewall_rules_cmd_failed": "Algunas órdenes de las reglas del cortafuegos han fallado. Más información en el registro.", "format_datetime_short": "%d/%m/%Y %I:%M %p", "hook_argument_missing": "Falta un parámetro '{:s}'", "hook_choice_invalid": "Selección inválida '{:s}'", - "hook_exec_failed": "No se puede ejecutar el script: {path:s}", - "hook_exec_not_terminated": "La ejecución del script no ha terminado: {path:s}", - "hook_list_by_invalid": "Enumerar los hooks por validez", + "hook_exec_failed": "No se pudo ejecutar el guión: {path:s}", + "hook_exec_not_terminated": "El guión no terminó correctamente:{path:s}", + "hook_list_by_invalid": "Esta propiedad no se puede usar para enumerar ganchos («hooks»)", "hook_name_unknown": "Nombre de hook desconocido '{name:s}'", "installation_complete": "Instalación finalizada", - "installation_failed": "No se pudo realizar la instalación", + "installation_failed": "Algo ha ido mal con la instalación", "ip6tables_unavailable": "No puede modificar ip6tables aquí. O bien está en un 'container' o su kernel no soporta esta opción", "iptables_unavailable": "No puede modificar iptables aquí. O bien está en un 'container' o su kernel no soporta esta opción", - "ldap_initialized": "Se ha inicializado LDAP", + "ldap_initialized": "Inicializado LDAP", "license_undefined": "indefinido", - "mail_alias_remove_failed": "No se pudo eliminar el alias de correo '{mail:s}'", - "mail_domain_unknown": "El dominio de correo '{domain:s}' es desconocido", - "mail_forward_remove_failed": "No se pudo eliminar el reenvío de correo '{mail:s}'", + "mail_alias_remove_failed": "No se pudo eliminar el alias de correo «{mail:s}»", + "mail_domain_unknown": "Dirección de correo desconocida para el dominio «{domain:s}»", + "mail_forward_remove_failed": "No se pudo eliminar el reenvío de correo «{mail:s}»", "maindomain_change_failed": "No se pudo cambiar el dominio principal", - "maindomain_changed": "Se ha cambiado el dominio principal", - "monitor_disabled": "La monitorización del sistema ha sido deshabilitada", - "monitor_enabled": "La monitorización del sistema ha sido habilitada", - "monitor_glances_con_failed": "No se pudo conectar al servidor Glances", - "monitor_not_enabled": "La monitorización del sistema no está habilitada", + "maindomain_changed": "El dominio principal ha cambiado", + "monitor_disabled": "Desactivada la monitorización del servidor", + "monitor_enabled": "Activada la monitorización del servidor", + "monitor_glances_con_failed": "No se pudo conectar al servidor de Glances", + "monitor_not_enabled": "La monitorización del servidor está apagada", "monitor_period_invalid": "Período de tiempo no válido", "monitor_stats_file_not_found": "No se pudo encontrar el archivo de estadísticas", "monitor_stats_no_update": "No hay estadísticas de monitorización para actualizar", "monitor_stats_period_unavailable": "No hay estadísticas para el período", "mountpoint_unknown": "Punto de montaje desconocido", - "mysql_db_creation_failed": "No se pudo crear la base de datos MySQL", - "mysql_db_init_failed": "No se pudo iniciar la base de datos MySQL", - "mysql_db_initialized": "La base de datos MySQL ha sido inicializada", + "mysql_db_creation_failed": "Error al crear la base de datos de MySQL", + "mysql_db_init_failed": "Error al iniciar la base de datos de MySQL", + "mysql_db_initialized": "Inicializada la base de datos MySQL", "network_check_mx_ko": "El registro DNS MX no está configurado", - "network_check_smtp_ko": "El puerto 25 (SMTP) para el correo saliente parece estar bloqueado por su red", - "network_check_smtp_ok": "El puerto de salida del correo electrónico (25, SMTP) no está bloqueado", + "network_check_smtp_ko": "El correo saliente (SMTP puerto 25) parece estar bloqueado por su red", + "network_check_smtp_ok": "El correo saliente (SMTP puerto 25) no está bloqueado", "new_domain_required": "Debe proporcionar el nuevo dominio principal", "no_appslist_found": "No se ha encontrado ninguna lista de aplicaciones", "no_internet_connection": "El servidor no está conectado a Internet", "no_ipv6_connectivity": "La conexión por IPv6 no está disponible", "no_restore_script": "No se ha encontrado un script de restauración para la aplicación '{app:s}'", - "not_enough_disk_space": "No hay suficiente espacio en '{path:s}'", + "not_enough_disk_space": "No hay espacio libre suficiente en «{path:s}»", "package_not_installed": "El paquete '{pkgname}' no está instalado", "package_unexpected_error": "Ha ocurrido un error inesperado procesando el paquete '{pkgname}'", "package_unknown": "Paquete desconocido '{pkgname}'", @@ -151,13 +151,13 @@ "packages_upgrade_critical_later": "Los paquetes críticos ({packages:s}) serán actualizados más tarde", "packages_upgrade_failed": "No se pudieron actualizar todos los paquetes", "path_removal_failed": "No se pudo eliminar la ruta {:s}", - "pattern_backup_archive_name": "Debe ser un nombre de archivo válido con un máximo de 30 caracteres, solo se admiten caracteres alfanuméricos, los guiones -_ y el punto", + "pattern_backup_archive_name": "Debe ser un nombre de archivo válido con un máximo de 30 caracteres, solo se admiten caracteres alfanuméricos y los caracteres -_. (guiones y punto)", "pattern_domain": "El nombre de dominio debe ser válido (por ejemplo mi-dominio.org)", "pattern_email": "Debe ser una dirección de correo electrónico válida (por ejemplo, alguien@dominio.org)", "pattern_firstname": "Debe ser un nombre válido", "pattern_lastname": "Debe ser un apellido válido", "pattern_listname": "Solo se pueden usar caracteres alfanuméricos y el guion bajo", - "pattern_mailbox_quota": "El tamaño de cuota debe tener uno de los sufijos b/k/M/G/T. Usar 0 para cuota ilimitada", + "pattern_mailbox_quota": "Debe ser un tamaño con el sufijo «b/k/M/G/T» o «0» para no tener una cuota", "pattern_password": "Debe contener al menos 3 caracteres", "pattern_port": "Debe ser un número de puerto válido (es decir, entre 0-65535)", "pattern_port_or_range": "Debe ser un número de puerto válido (es decir entre 0-65535) o un intervalo de puertos (por ejemplo 100:200)", @@ -167,22 +167,22 @@ "port_already_opened": "El puerto {port:d} ya está abierto para las conexiones {ip_version:s}", "port_available": "El puerto {port:d} está disponible", "port_unavailable": "El puerto {port:d} no está disponible", - "restore_action_required": "Debe especificar algo que restaurar", - "restore_already_installed_app": "Una aplicación con la id '{app:s}' ya está instalada", - "restore_app_failed": "No se puede restaurar la aplicación '{app:s}'", - "restore_cleaning_failed": "No se puede limpiar el directorio temporal de restauración", - "restore_complete": "Restauración finalizada", + "restore_action_required": "Debe escoger algo que restaurar", + "restore_already_installed_app": "Una aplicación con el ID «{app:s}» ya está instalada", + "restore_app_failed": "No se pudo restaurar la aplicación «{app:s}»", + "restore_cleaning_failed": "No se pudo limpiar el directorio temporal de restauración", + "restore_complete": "Restaurada", "restore_confirm_yunohost_installed": "¿Realmente desea restaurar un sistema ya instalado? [{answers:s}]", "restore_failed": "No se pudo restaurar el sistema", - "restore_hook_unavailable": "El script de restauración '{part:s}' no está disponible en su sistema y tampoco en el archivo", + "restore_hook_unavailable": "El guión de restauración para «{part:s}» no está disponible en su sistema y tampoco en el archivo", "restore_nothings_done": "No se ha restaurado nada", - "restore_running_app_script": "Ejecutando el guión de restauración de la aplicación «{app:s}»…", + "restore_running_app_script": "Restaurando la aplicación «{app:s}»…", "restore_running_hooks": "Ejecutando los ganchos de restauración…", - "service_add_failed": "No se pudo añadir el servicio '{service:s}'", - "service_added": "Servicio '{service:s}' ha sido añadido", - "service_already_started": "El servicio '{service:s}' ya ha sido inicializado", - "service_already_stopped": "El servicio '{service:s}' ya ha sido detenido", - "service_cmd_exec_failed": "No se pudo ejecutar el comando '{command:s}'", + "service_add_failed": "No se pudo añadir el servicio «{service:s}»", + "service_added": "Añadido el servicio «{service:s}»", + "service_already_started": "El servicio «{service:s}» ya ha sido iniciado", + "service_already_stopped": "El servicio «{service:s}» ya ha sido detenido", + "service_cmd_exec_failed": "No se pudo ejecutar la orden «{command:s}»", "service_conf_file_backed_up": "Se ha realizado una copia de seguridad del archivo de configuración '{conf}' en '{backup}'", "service_conf_file_copy_failed": "No se puede copiar el nuevo archivo de configuración '{new}' a {conf}", "service_conf_file_manually_modified": "El archivo de configuración '{conf}' ha sido modificado manualmente y no será actualizado", @@ -194,28 +194,28 @@ "service_conf_up_to_date": "La configuración del servicio '{service}' ya está actualizada", "service_conf_updated": "La configuración ha sido actualizada para el servicio '{service}'", "service_conf_would_be_updated": "La configuración podría haber sido actualizada para el servicio '{service} 1'", - "service_disable_failed": "No se pudo deshabilitar el servicio '{service:s}'", - "service_disabled": "El servicio '{service:s}' ha sido deshabilitado", - "service_enable_failed": "No se pudo habilitar el servicio '{service:s}'", - "service_enabled": "El servicio '{service:s}' ha sido habilitado", + "service_disable_failed": "No se pudo desactivar el servicio «{service:s}»\n\nRegistro de servicios recientes:{logs:s}", + "service_disabled": "Desactivado el servicio «{service:s}»", + "service_enable_failed": "No se pudo activar el servicio «{service:s}»\n\nRegistro de servicios recientes:{logs:s}", + "service_enabled": "Activado el servicio «{service:s}»", "service_no_log": "No hay ningún registro para el servicio '{service:s}'", "service_regenconf_dry_pending_applying": "Comprobando configuración pendiente que podría haber sido aplicada al servicio '{service}'...", "service_regenconf_failed": "No se puede regenerar la configuración para el servicio(s): {services}", "service_regenconf_pending_applying": "Aplicando la configuración pendiente para el servicio '{service}'...", - "service_remove_failed": "No se pudo desinstalar el servicio '{service:s}'", - "service_removed": "El servicio '{service:s}' ha sido desinstalado", - "service_start_failed": "No se pudo iniciar el servicio '{service:s}'\n\nRegistros de servicio recientes : {logs:s}", - "service_started": "El servicio '{service:s}' ha sido iniciado", - "service_status_failed": "No se pudo determinar el estado del servicio '{service:s}'", - "service_stop_failed": "No se pudo detener el servicio '{service:s}'", - "service_stopped": "El servicio '{service:s}' ha sido detenido", + "service_remove_failed": "No se pudo eliminar el servicio «{service:s}»", + "service_removed": "Eliminado el servicio «{service:s}»", + "service_start_failed": "No se pudo iniciar el servicio «{service:s}»\n\nRegistro de servicios recientes:{logs:s}", + "service_started": "Iniciado el servicio «{service:s}»", + "service_status_failed": "No se pudo determinar el estado del servicio «{service:s}»", + "service_stop_failed": "No se pudo detener el servicio «{service:s}»\n\nRegistro de servicios recientes:{logs:s}", + "service_stopped": "Detenido el servicio «{service:s}»", "service_unknown": "Servicio desconocido '{service:s}'", - "ssowat_conf_generated": "Se ha generado la configuración de SSOwat", - "ssowat_conf_updated": "La configuración de SSOwat ha sido actualizada", - "system_upgraded": "El sistema ha sido actualizado", - "system_username_exists": "El nombre de usuario ya existe en el sistema", + "ssowat_conf_generated": "Generada la configuración de SSOwat", + "ssowat_conf_updated": "Actualizada la configuración de SSOwat", + "system_upgraded": "Sistema actualizado", + "system_username_exists": "El nombre de usuario ya existe en la lista de usuarios del sistema", "unbackup_app": "La aplicación '{app:s}' no se guardará", - "unexpected_error": "Ha ocurrido un error inesperado", + "unexpected_error": "Algo inesperado salió mal: {error}", "unit_unknown": "Unidad desconocida '{unit:s}'", "unlimit": "Sin cuota", "unrestore_app": "La aplicación '{app:s}' no será restaurada", @@ -224,278 +224,278 @@ "upgrade_complete": "Actualización finalizada", "upgrading_packages": "Actualizando paquetes…", "upnp_dev_not_found": "No se encontró ningún dispositivo UPnP", - "upnp_disabled": "UPnP ha sido deshabilitado", - "upnp_enabled": "UPnP ha sido habilitado", - "upnp_port_open_failed": "No se pudieron abrir puertos por UPnP", - "user_created": "El usuario ha sido creado", + "upnp_disabled": "UPnP desactivado", + "upnp_enabled": "UPnP activado", + "upnp_port_open_failed": "No se pudo abrir el puerto vía UPnP", + "user_created": "Usuario creado", "user_creation_failed": "No se pudo crear el usuario", - "user_deleted": "El usuario ha sido eliminado", + "user_deleted": "Usuario eliminado", "user_deletion_failed": "No se pudo eliminar el usuario", - "user_home_creation_failed": "No se puede crear el directorio de usuario 'home'", - "user_info_failed": "No se pudo extraer la información del usuario", + "user_home_creation_failed": "No se pudo crear la carpeta «home» para el usuario", + "user_info_failed": "No se pudo obtener la información del usuario", "user_unknown": "Usuario desconocido: {user:s}", - "user_update_failed": "No se pudo actualizar el usuario", - "user_updated": "El usuario ha sido actualizado", + "user_update_failed": "No se pudo cambiar la información del usuario", + "user_updated": "Cambiada la información de usuario", "yunohost_already_installed": "YunoHost ya está instalado", - "yunohost_ca_creation_failed": "No se pudo crear el certificado de autoridad", - "yunohost_configured": "YunoHost ha sido configurado", + "yunohost_ca_creation_failed": "No se pudo crear la autoridad de certificación", + "yunohost_configured": "YunoHost está configurado", "yunohost_installing": "Instalando YunoHost…", - "yunohost_not_installed": "YunoHost no está instalado o ha habido errores en la instalación. Ejecute 'yunohost tools postinstall'", - "ldap_init_failed_to_create_admin": "La inicialización de LDAP falló al crear el usuario administrador", - "mailbox_used_space_dovecot_down": "El servicio de e-mail Dovecot debe estar funcionando si desea obtener el espacio utilizado por el buzón de correo", - "ssowat_persistent_conf_read_error": "Error al leer la configuración persistente de SSOwat: {error:s}. Edite el archivo /etc/ssowat/conf.json.persistent para corregir la sintaxis de JSON", - "ssowat_persistent_conf_write_error": "Error al guardar la configuración persistente de SSOwat: {error:s}. Edite el archivo /etc/ssowat/conf.json.persistent para corregir la sintaxis de JSON", + "yunohost_not_installed": "YunoHost no está correctamente instalado. Ejecute «yunohost tools postinstall»", + "ldap_init_failed_to_create_admin": "La inicialización de LDAP no pudo crear el usuario «admin»", + "mailbox_used_space_dovecot_down": "El servicio de correo Dovecot debe estar funcionando si desea obtener el espacio usado por el buzón de correo", + "ssowat_persistent_conf_read_error": "No se pudo leer la configuración persistente de SSOwat: {error:s}. Edite el archivo /etc/ssowat/conf.json.persistent para corregir la sintaxis de JSON", + "ssowat_persistent_conf_write_error": "No se pudo guardar la configuración persistente de SSOwat: {error:s}. Edite el archivo /etc/ssowat/conf.json.persistent para corregir la sintaxis de JSON", "certmanager_attempt_to_replace_valid_cert": "Está intentando sobrescribir un certificado correcto y válido para el dominio {domain:s}! (Use --force para omitir este mensaje)", - "certmanager_domain_unknown": "Dominio desconocido {domain:s}", - "certmanager_domain_cert_not_selfsigned": "El certificado para el dominio {domain:s} no es un certificado autofirmado. ¿Está seguro de que quiere reemplazarlo? (Use --force para omitir este mensaje)", - "certmanager_certificate_fetching_or_enabling_failed": "Suena como que habilitar el nuevo certificado para {domain:s} fallara de algún modo…", - "certmanager_attempt_to_renew_nonLE_cert": "El certificado para el dominio {domain:s} no ha sido emitido por Let's Encrypt. ¡No se puede renovar automáticamente!", - "certmanager_attempt_to_renew_valid_cert": "¡El certificado para el dominio {domain:s} no está a punto de expirar! (Puede usar --force si sabe lo que está haciendo)", - "certmanager_domain_http_not_working": "Parece que no se puede acceder al dominio {domain:s} a través de HTTP. Compruebe que la configuración del DNS y de nginx es correcta", - "certmanager_error_no_A_record": "No se ha encontrado un registro DNS 'A' para el dominio {domain:s}. Debe hacer que su nombre de dominio apunte a su máquina para poder instalar un certificado Let's Encrypt. (Si sabe lo que está haciendo, use --no-checks para desactivar esas comprobaciones.)", - "certmanager_domain_dns_ip_differs_from_public_ip": "El registro DNS 'A' para el dominio {domain:s} es diferente de la IP de este servidor. Si recientemente modificó su registro A, espere a que se propague (existen algunos controladores de propagación DNS disponibles en línea). (Si sabe lo que está haciendo, use --no-checks para desactivar esas comprobaciones.)", + "certmanager_domain_unknown": "Dominio desconocido «{domain:s}»", + "certmanager_domain_cert_not_selfsigned": "El certificado para el dominio {domain:s} no es un certificado autofirmado. ¿Está seguro de que quiere reemplazarlo? (Use «--force» para hacerlo)", + "certmanager_certificate_fetching_or_enabling_failed": "El intento de usar el nuevo certificado para {domain:s} no ha funcionado…", + "certmanager_attempt_to_renew_nonLE_cert": "El certificado para el dominio «{domain:s}» no ha sido emitido por Let's Encrypt. ¡No se puede renovar automáticamente!", + "certmanager_attempt_to_renew_valid_cert": "¡El certificado para el dominio «{domain:s}» no está a punto de expirar! (Puede usar --force si sabe lo que está haciendo)", + "certmanager_domain_http_not_working": "Parece que no se puede acceder al dominio {domain:s} a través de HTTP. Compruebe que la configuración del DNS y de NGINX es correcta", + "certmanager_error_no_A_record": "No se ha encontrado un registro DNS «A» para el dominio {domain:s}. Debe hacer que su nombre de dominio apunte a su máquina para poder instalar un certificado de Let's Encrypt. (Si sabe lo que está haciendo, use «--no-checks» para desactivar esas comprobaciones.)", + "certmanager_domain_dns_ip_differs_from_public_ip": "El registro «A» del DNS para el dominio «{domain:s}» es diferente de la IP de este servidor. Si recientemente modificó su registro A, espere a que se propague (existen algunos verificadores de propagación de DNS disponibles en línea). (Si sabe lo que está haciendo, use «--no-checks» para desactivar esas comprobaciones.)", "certmanager_cannot_read_cert": "Se ha producido un error al intentar abrir el certificado actual para el dominio {domain:s} (archivo: {file:s}), razón: {reason:s}", - "certmanager_cert_install_success_selfsigned": "¡Se ha instalado correctamente un certificado autofirmado para el dominio {domain:s}!", - "certmanager_cert_install_success": "¡Se ha instalado correctamente un certificado Let's Encrypt para el dominio {domain:s}!", - "certmanager_cert_renew_success": "¡Se ha renovado correctamente el certificado Let's Encrypt para el dominio {domain:s}!", + "certmanager_cert_install_success_selfsigned": "Instalado correctamente un certificado autofirmado para el dominio «{domain:s}»", + "certmanager_cert_install_success": "Instalado correctamente un certificado de Let's Encrypt para el dominio «{domain:s}»", + "certmanager_cert_renew_success": "Renovado correctamente el certificado de Let's Encrypt para el dominio «{domain:s}»", "certmanager_old_letsencrypt_app_detected": "\nYunohost ha detectado que la aplicación 'letsencrypt' está instalada, esto produce conflictos con las nuevas funciones de administración de certificados integradas en Yunohost. Si desea utilizar las nuevas funciones integradas, ejecute los siguientes comandos para migrar su instalación:\n\n Yunohost app remove letsencrypt\n Yunohost domain cert-install\n\nP.D.: esto intentará reinstalar los certificados para todos los dominios con un certificado Let's Encrypt o con un certificado autofirmado", - "certmanager_hit_rate_limit": "Se han emitido demasiados certificados recientemente para el conjunto de dominios {domain:s}. Por favor, inténtelo de nuevo más tarde. Consulte https://letsencrypt.org/docs/rate-limits/ para obtener más detalles", + "certmanager_hit_rate_limit": "Se han emitido demasiados certificados recientemente para este conjunto exacto de dominios {domain:s}. Pruebe de nuevo más tarde. Vea para más detalles https://letsencrypt.org/docs/rate-limits/", "certmanager_cert_signing_failed": "No se pudo firmar el nuevo certificado", - "certmanager_no_cert_file": "No se puede leer el certificado para el dominio {domain:s} (archivo: {file:s})", - "certmanager_conflicting_nginx_file": "No se puede preparar el dominio para el desafío ACME: el archivo de configuración nginx {filepath:s} está en conflicto y debe ser eliminado primero", - "domain_cannot_remove_main": "No se puede eliminar el dominio principal. Primero debe establecer un nuevo dominio principal", - "certmanager_self_ca_conf_file_not_found": "No se ha encontrado el archivo de configuración para la autoridad de autofirma (file: {file:s})", - "certmanager_unable_to_parse_self_CA_name": "No se puede procesar el nombre de la autoridad de autofirma (file: {file:s} 1)", + "certmanager_no_cert_file": "No se pudo leer el certificado para el dominio {domain:s} (archivo: {file:s})", + "certmanager_conflicting_nginx_file": "No se pudo preparar el dominio para el desafío ACME: el archivo de configuración de NGINX {filepath:s} está en conflicto y debe ser eliminado primero", + "domain_cannot_remove_main": "No se puede eliminar el dominio principal. Configure uno primero", + "certmanager_self_ca_conf_file_not_found": "No se pudo encontrar el archivo de configuración para la autoridad de autofirma (archivo: {file:s})", + "certmanager_unable_to_parse_self_CA_name": "No se pudo procesar el nombre de la autoridad de autofirma (archivo: {file:s})", "domains_available": "Dominios disponibles:", - "backup_archive_broken_link": "Imposible acceder a la copia de seguridad (enlace roto {path:s})", - "certmanager_domain_not_resolved_locally": "Su servidor Yunohost no consigue resolver el dominio {domain:s}. Esto puede suceder si ha modificado su registro DNS. Si es el caso, espere unas horas hasta que se propague la modificación. Si el problema persiste, considere añadir {domain:s} a /etc/hosts. (Si sabe lo que está haciendo, use --no-checks para deshabilitar estas verificaciones.)", - "certmanager_acme_not_configured_for_domain": "El certificado para el dominio {domain:s} no parece instalado correctamente. Ejecute primero cert-install para este dominio.", - "certmanager_http_check_timeout": "Plazo expirado, el servidor no ha podido contactarse a si mismo a través de HTTP usando su dirección IP pública (dominio {domain:s} con ip {ip:s}). Puede ser debido a hairpinning o a una mala configuración del cortafuego/router al que está conectado su servidor.", - "certmanager_couldnt_fetch_intermediate_cert": "Plazo expirado, no se ha podido descargar el certificado intermedio de Let's Encrypt. La instalación/renovación del certificado ha sido cancelada - vuelva a intentarlo más tarde.", - "appslist_retrieve_bad_format": "El archivo obtenido para la lista de aplicaciones {appslist:s} no es válido", - "domain_hostname_failed": "Error al establecer un nuevo nombre de host («hostname»). Esto podría causar problemas más tarde (no es seguro... podría ir bien).", - "yunohost_ca_creation_success": "Se ha creado la autoridad de certificación local.", - "app_already_installed_cant_change_url": "Esta aplicación ya está instalada. No se puede cambiar el URL únicamente mediante esta función. Compruebe si está disponible la opción 'app changeurl'.", + "backup_archive_broken_link": "No se pudo acceder al archivo de respaldo (enlace roto a {path:s})", + "certmanager_domain_not_resolved_locally": "El dominio {domain:s} no puede ser resuelto desde su servidor de YunoHost. Esto puede suceder si ha modificado su registro DNS recientemente. De ser así, espere unas horas para que se propague. Si el problema continúa, considere añadir {domain:s} a /etc/hosts. (Si sabe lo que está haciendo, use «--no-checks» para desactivar esas comprobaciones.)", + "certmanager_acme_not_configured_for_domain": "El certificado para el dominio «{domain:s}» no parece que esté instalado correctamente. Ejecute primero «cert-install» para este dominio.", + "certmanager_http_check_timeout": "Tiempo de espera agotado cuando el servidor intentaba conectarse consigo mismo a través de HTTP usando una dirección IP pública (dominio «{domain:s}» con IP «{ip:s}»). Puede que esté experimentando un problema de redirección («hairpinning»), o que el cortafuegos o el enrutador de su servidor esté mal configurado.", + "certmanager_couldnt_fetch_intermediate_cert": "Tiempo de espera agotado intentando obtener el certificado intermedio de Let's Encrypt. Cancelada la instalación o renovación del certificado. Vuelva a intentarlo más tarde.", + "appslist_retrieve_bad_format": "No se pudo leer la lista de aplicaciones obtenida {appslist:s}", + "domain_hostname_failed": "No se pudo establecer un nuevo nombre de anfitrión («hostname»). Esto podría causar problemas más tarde (no es seguro... podría ir bien).", + "yunohost_ca_creation_success": "Creada la autoridad de certificación local.", + "app_already_installed_cant_change_url": "Esta aplicación ya está instalada. No se puede cambiar el URL únicamente mediante esta función. Compruebe si está disponible la opción `app changeurl`.", "app_change_no_change_url_script": "La aplicacion {app_name:s} aún no permite cambiar su URL, es posible que deba actualizarla.", - "app_change_url_failed_nginx_reload": "No se pudo recargar nginx. Compruebe la salida de 'nginx -t':\n{nginx_errors:s}", + "app_change_url_failed_nginx_reload": "No se pudo recargar NGINX. Esta es la salida de «nginx -t»:\n{nginx_errors:s}", "app_change_url_identical_domains": "El antiguo y nuevo dominio/url_path son idénticos ('{domain:s} {path:s}'), no se realizarán cambios.", - "app_change_url_no_script": "Esta aplicación '{app_name:s}' aún no permite modificar su URL. Quizás debería actualizar la aplicación.", - "app_change_url_success": "El URL de la aplicación {app:s} ha sido cambiado correctamente a {domain:s} {path:s}", - "app_location_unavailable": "Este URL no está disponible o está en conflicto con otra aplicación instalada:\n{apps:s}", + "app_change_url_no_script": "Esta aplicación «{app_name:s}» aún no permite la modificación de URLs. Quizás debería actualizarla.", + "app_change_url_success": "El URL de la aplicación {app:s} es ahora {domain:s} {path:s}", + "app_location_unavailable": "Este URL o no está disponible o está en conflicto con otra(s) aplicación(es) instalada(s):\n{apps:s}", "app_already_up_to_date": "La aplicación {app:s} ya está actualizada", - "appslist_name_already_tracked": "Ya existe una lista de aplicaciones registrada con el nombre {name:s}.", - "appslist_url_already_tracked": "Ya existe una lista de aplicaciones registrada con el URL {url:s}.", + "appslist_name_already_tracked": "Ya existe una lista de aplicaciones registradas con el nombre {name:s}.", + "appslist_url_already_tracked": "Ya existe una lista de aplicaciones registradas con el URL {url:s}.", "appslist_migrating": "Migrando la lista de aplicaciones {appslist:s}…", - "appslist_could_not_migrate": "No se pudo migrar la lista de aplicaciones {appslist:s}! No se pudo analizar el URL ... El antiguo cronjob se ha mantenido en {bkp_file:s}.", + "appslist_could_not_migrate": "¡No se pudo migrar la lista de aplicaciones {appslist:s}! No se pudo analizar el URL… El antiguo trabajo de cron se mantuvo en {bkp_file:s}.", "appslist_corrupted_json": "No se pudieron cargar las listas de aplicaciones. Parece que {filename:s} está dañado.", - "invalid_url_format": "Formato de URL no válido", + "invalid_url_format": "Algo va mal con el URL", "app_upgrade_some_app_failed": "No se pudieron actualizar algunas aplicaciones", - "app_make_default_location_already_used": "No puede hacer la aplicación '{app}' por defecto en el dominio {domain} dado que está siendo usado por otra aplicación '{other_app}'", - "app_upgrade_app_name": "Actualizando la aplicación {app}…", + "app_make_default_location_already_used": "No puede hacer que la aplicación «{app}» sea la predeterminada en el dominio, {domain} ya está siendo usado por otra aplicación «{other_app}»", + "app_upgrade_app_name": "Actualizando ahora {app}…", "ask_path": "Camino", - "backup_abstract_method": "Este método de backup no ha sido implementado aún", + "backup_abstract_method": "Este método de respaldo aún no se ha implementado", "backup_applying_method_borg": "Enviando todos los archivos para la copia de seguridad al repositorio de borg-backup…", "backup_applying_method_copy": "Copiando todos los archivos a la copia de seguridad…", "backup_applying_method_custom": "Llamando al método de copia de seguridad personalizado «{method:s}»…", - "backup_applying_method_tar": "Creando el archivo tar de la copia de seguridad…", - "backup_archive_mount_failed": "Fallo en el montado del archivo de backup", - "backup_archive_system_part_not_available": "La parte del sistema {part:s} no está disponible en este backup", - "backup_archive_writing_error": "No se pueden añadir archivos de backup en el archivo comprimido", - "backup_ask_for_copying_if_needed": "Algunos ficheros no pudieron ser preparados para hacer backup usando el método que evita el gasto de espacio temporal en el sistema. Para hacer el backup, {size:s} MB deberían ser usados temporalmente. ¿Está de acuerdo?", - "backup_borg_not_implemented": "Método de backup Borg no está implementado aún", - "backup_cant_mount_uncompress_archive": "No se puede montar en modo solo lectura el directorio del archivo descomprimido", + "backup_applying_method_tar": "Creando el archivo TAR de respaldo…", + "backup_archive_mount_failed": "No se pudo montar el archivo de respaldo", + "backup_archive_system_part_not_available": "La parte del sistema «{part:s}» no está disponible en esta copia de seguridad", + "backup_archive_writing_error": "No se pudieron añadir los archivos «{source:s}» (llamados en el archivo «{dest:s}») para ser respaldados en el archivo comprimido «{archive:s}»", + "backup_ask_for_copying_if_needed": "No se pudieron preparar algunos archivos para la copia de seguridad usando el método que evita desperdiciar espacio temporalmente en el sistema. Para hacer la copia de seguridad, {size:s}MB se usarán temporalmente. ¿Está de acuerdo?", + "backup_borg_not_implemented": "El método de respaldo de Borg aún no ha sido implementado", + "backup_cant_mount_uncompress_archive": "No se pudo montar el archivo descomprimido como protegido contra escritura", "backup_copying_to_organize_the_archive": "Copiando {size:s}MB para organizar el archivo", - "backup_couldnt_bind": "No puede enlazar {src:s} con {dest:s}.", - "backup_csv_addition_failed": "No puede añadir archivos al backup en el archivo CSV", - "backup_csv_creation_failed": "No se puede crear el archivo CSV necesario para futuras operaciones de restauración", - "backup_custom_mount_error": "Fracaso del método de copia de seguridad personalizada en la etapa \"mount\"", - "backup_custom_need_mount_error": "Fracaso del método de copia de seguridad personalizada en la étapa \"need_mount\"", - "backup_no_uncompress_archive_dir": "El directorio del archivo descomprimido no existe", - "backup_php5_to_php7_migration_may_fail": "No se ha podido convertir su archivo para soportar php7, la restauración de sus aplicaciones php puede fallar (razón : {error:s})", - "backup_system_part_failed": "No se puede hacer una copia de seguridad de la parte \"{part:s}\" del sistema", - "backup_with_no_backup_script_for_app": "La aplicación {app:s} no tiene script de respaldo. Se ha ignorado.", - "backup_with_no_restore_script_for_app": "La aplicación {app:s} no tiene script de restauración, no podrá restaurar automáticamente la copia de seguridad de esta aplicación.", + "backup_couldnt_bind": "No se pudo enlazar {src:s} con {dest:s}.", + "backup_csv_addition_failed": "No se pudo añadir archivos para respaldar en el archivo CSV", + "backup_csv_creation_failed": "No se pudo crear el archivo CSV necesario para la restauración", + "backup_custom_mount_error": "El método de respaldo personalizado no pudo superar el paso «mount»", + "backup_custom_need_mount_error": "El método de respaldo personalizado no pudo superar el paso «need_mount»", + "backup_no_uncompress_archive_dir": "No existe tal directorio de archivos sin comprimir", + "backup_php5_to_php7_migration_may_fail": "No se pudo convertir su archivo para que sea compatible con PHP 7, puede que no pueda restaurar sus aplicaciones de PHP (motivo: {error:s})", + "backup_system_part_failed": "No se pudo respaldar la parte del sistema «{part:s}»", + "backup_with_no_backup_script_for_app": "La aplicación «{app:s}» no tiene un guión de respaldo. Omitiendo.", + "backup_with_no_restore_script_for_app": "La aplicación «{app:s}» no tiene un guión de restauración, no podrá restaurar automáticamente la copia de seguridad de esta aplicación.", "dyndns_could_not_check_provide": "No se pudo verificar si {provider:s} puede ofrecer {domain:s}.", - "dyndns_domain_not_provided": "El proveedor Dyndns {provider:s} no puede proporcionar el dominio {domain:s}.", - "experimental_feature": "Cuidado : esta funcionalidad es experimental y no es considerada estable, no debería usarla excepto si sabe lo que hace.", - "good_practices_about_user_password": "Está a punto de establecer una nueva contraseña de usuario. La contraseña debería de ser de al menos 8 caracteres, aunque es una buena práctica usar una contraseña más larga (es decir, una frase de paso) y/o usar varias clases de caracteres (mayúsculas, minúsculas, dígitos y caracteres especiales).", - "password_listed": "Esta contraseña es una de las más usadas en el mundo. Elija algo un poco más único.", - "password_too_simple_1": "La contraseña debe tener al menos 8 caracteres de longitud", - "password_too_simple_2": "La contraseña debe tener al menos 8 caracteres de longitud y contiene dígitos, mayúsculas y minúsculas", - "password_too_simple_3": "La contraseña debe tener al menos 8 caracteres de longitud y contiene dígitos, mayúsculas, minúsculas y caracteres especiales", - "password_too_simple_4": "La contraseña debe tener al menos 12 caracteres de longitud y contiene dígitos, mayúsculas, minúsculas y caracteres especiales", + "dyndns_domain_not_provided": "El proveedor de DynDNS {provider:s} no puede proporcionar el dominio {domain:s}.", + "experimental_feature": "Aviso : esta funcionalidad es experimental y no se considera estable, no debería usarla a menos que sepa lo que está haciendo.", + "good_practices_about_user_password": "Está a punto de establecer una nueva contraseña de usuario. La contraseña debería de ser de al menos 8 caracteres, aunque es una buena práctica usar una contraseña más extensa (básicamente una frase) y/o usar caracteres de varias clases (mayúsculas, minúsculas, números y caracteres especiales).", + "password_listed": "Esta contraseña es una de las más usadas en el mundo. Elija algo más único.", + "password_too_simple_1": "La contraseña tiene que ser de al menos 8 caracteres de longitud", + "password_too_simple_2": "La contraseña tiene que ser de al menos 8 caracteres de longitud e incluir un número y caracteres en mayúsculas y minúsculas", + "password_too_simple_3": "La contraseña tiene que ser de al menos 8 caracteres de longitud e incluir un número, mayúsculas, minúsculas y caracteres especiales", + "password_too_simple_4": "La contraseña tiene que ser de al menos 12 caracteres de longitud e incluir un número, mayúsculas, minúsculas y caracteres especiales", "users_available": "Usuarios disponibles:", - "user_not_in_group": "Usuario {user:s} no está en el grupo {group:s}", - "user_already_in_group": "Usuario {user:} ya está en el grupo {group:s}", + "user_not_in_group": "El usuario «{user:s}» no está en el grupo «{group:s}»", + "user_already_in_group": "El usuario «{user:}» ya está en el grupo «{group:s}»", "updating_app_lists": "Obteniendo actualizaciones disponibles para las aplicaciones…", - "update_apt_cache_warning": "Ocurrieron algunos errores durante la actualización de la caché de APT (gestor de paquetes de Debian). Aquí tiene un volcado de las líneas de sources.list que podría ayudarle a identificar las líneas problemáticas:\n{sourceslist}", - "update_apt_cache_failed": "No se puede actualizar la caché de APT (gestor de paquetes de Debian). Aquí tiene un volcado de las líneas de sources.list que podría ayudarle a identificar las líneas problemáticas:\n{sourceslist}", - "tools_upgrade_special_packages_completed": "¡Actualización de paquetes de YunoHost completada!\nPulse [Intro] para recuperar la línea de órdenes", - "tools_upgrade_special_packages_explanation": "Esta acción terminará pero la actualización especial real continuará en segundo plano. No inicie ninguna otra acción en su servidor en aproximadamente 10 minutos (dependiendo de la velocidad de su hardware). Una vez que esté hecho, podría tener que volver a iniciar sesión en la administración web. El registro de actualización estará disponible en Herramientas > Registro (en la administración web) o mediante «yunohost log list» (en la línea de órdenes).", - "tools_upgrade_special_packages": "Actualizando ahora paquetes «especiales» (relacionados con YunoHost)...", - "tools_upgrade_regular_packages_failed": "No se pueden actualizar los paquetes: {packages_list}", - "tools_upgrade_regular_packages": "Actualizando ahora paquetes «normales» (no relacionados con YunoHost)...", - "tools_upgrade_cant_unhold_critical_packages": "No se pueden liberar los paquetes críticos...", - "tools_upgrade_cant_hold_critical_packages": "No se pueden retener los paquetes críticos...", + "update_apt_cache_warning": "Algo fue mal durante la actualización de la caché de APT (gestor de paquetes de Debian). Aquí tiene un volcado de las líneas de sources.list que podría ayudarle a identificar las líneas problemáticas:\n{sourceslist}", + "update_apt_cache_failed": "No se pudo actualizar la caché de APT (gestor de paquetes de Debian). Aquí tiene un volcado de las líneas de sources.list que podría ayudarle a identificar las líneas problemáticas:\n{sourceslist}", + "tools_upgrade_special_packages_completed": "Actualización de paquetes de YunoHost completada.\nPulse [Intro] para regresar a la línea de órdenes", + "tools_upgrade_special_packages_explanation": "Esta acción terminará pero la actualización especial real continuará en segundo plano. No inicie ninguna otra acción en su servidor en aproximadamente 10 minutos (dependiendo de la velocidad de su hardware). Una vez que esté hecho, podría tener que volver a iniciar sesión en la administración web. El registro de actualización estará disponible en Herramientas → Registro (en la página de administración web) o mediante «yunohost log list» (desde la línea de órdenes).", + "tools_upgrade_special_packages": "Actualizando ahora paquetes «especiales» (relacionados con YunoHost)…", + "tools_upgrade_regular_packages_failed": "No se pudieron actualizar los paquetes: {packages_list}", + "tools_upgrade_regular_packages": "Actualizando ahora paquetes «normales» (no relacionados con YunoHost)…", + "tools_upgrade_cant_unhold_critical_packages": "No se pudieron liberar los paquetes críticos…", + "tools_upgrade_cant_hold_critical_packages": "No se pudieron retener los paquetes críticos…", "tools_upgrade_cant_both": "No se puede actualizar el sistema y las aplicaciones al mismo tiempo", - "tools_upgrade_at_least_one": "Especifique --apps O --system", - "tools_update_failed_to_app_fetchlist": "Error al actualizar la lista de aplicaciones de YunoHost porque: {error}", - "this_action_broke_dpkg": "Esta acción rompió dpkg/apt (los gestores de paquetes del sistema)... Puede tratar de solucionar este problema conectando mediante SSH y ejecutando `sudo dpkg --configure -a`.", + "tools_upgrade_at_least_one": "Especifique «--apps», o «--system»", + "tools_update_failed_to_app_fetchlist": "No se pudo actualizar la lista de aplicaciones de YunoHost porque: {error}", + "this_action_broke_dpkg": "Esta acción rompió dpkg/APT(los gestores de paquetes del sistema)… Puede tratar de solucionar este problema conectando mediante SSH y ejecutando `sudo dpkg --configure -a`.", "system_groupname_exists": "El nombre de grupo ya existe en el grupo del sistema", - "service_reloaded_or_restarted": "El servicio «{service:s}» ha sido recargado o reiniciado", - "service_reload_or_restart_failed": "No se puede recargar o reiniciar el servicio «{service:s}»'\n\nRegistro de servicios reciente:{logs:s}", - "service_restarted": "El servicio «{service:s}» ha sido reiniciado", - "service_restart_failed": "No se puede reiniciar el servicio «{service:s}»'\n\nRegistro de servicios reciente:{logs:s}", - "service_reloaded": "El servicio «{service:s}» ha sido recargado", - "service_reload_failed": "No se puede recargar el servicio «{service:s}»'\n\nRegistro de servicios reciente:{logs:s}", + "service_reloaded_or_restarted": "Recargado o reiniciado el servicio «{service:s}»", + "service_reload_or_restart_failed": "No se pudo recargar o reiniciar el servicio «{service:s}»\n\nRegistro de servicios recientes:{logs:s}", + "service_restarted": "Reiniciado el servicio «{service:s}»", + "service_restart_failed": "No se pudo reiniciar el servicio «{service:s}»\n\nRegistro de servicios recientes:{logs:s}", + "service_reloaded": "Recargado el servicio «{service:s}»", + "service_reload_failed": "No se pudo recargar el servicio «{service:s}»\n\nRegistro de servicios recientes:{logs:s}", "service_regen_conf_is_deprecated": "¡«yunohost service regen-conf» está obsoleto! Use «yunohost tools regen-conf» en su lugar.", - "service_description_yunohost-firewall": "gestiona los puertos de conexiones abiertos y cerrados a los servicios", - "service_description_yunohost-api": "gestiona las interacciones entre la interfaz web de YunoHost y el sistema", - "service_description_ssh": "le permite conectar a su servidor remotamente mediante un terminal (protocolo SSH)", - "service_description_slapd": "almacena usuarios, dominios e información relacionada", - "service_description_rspamd": "filtra correo no deseado y otras características relacionadas con el correo", - "service_description_rmilter": "comprueba varios parámetros en el correo", - "service_description_redis-server": "una base de datos especializada usada para el acceso rápido de datos, cola de tareas y comunicación entre programas", - "service_description_postfix": "usado para enviar y recibir correos", - "service_description_php7.0-fpm": "ejecuta aplicaciones escritas en PHP con nginx", - "service_description_nslcd": "maneja la conexión del intérprete («shell») de usuario de YunoHost", - "service_description_nginx": "sirve o proporciona acceso a todos los sitios web alojados en su servidor", - "service_description_mysql": "almacena los datos de las aplicaciones (base de datos SQL)", - "service_description_metronome": "gestionar las cuentas XMPP de mensajería instantánea", - "service_description_glances": "supervisa la información del sistema en su servidor", - "service_description_fail2ban": "protege contra ataques de fuerza bruta y otra clase de ataques desde Internet", - "service_description_dovecot": "permite al cliente de correo acceder/traer correo (vía IMAP y POP3)", - "service_description_dnsmasq": "maneja la resolución de nombres de dominio (DNS)", - "service_description_avahi-daemon": "permite acceder a su servidor usando yunohost.local en su red local", + "service_description_yunohost-firewall": "Gestiona los puertos de conexiones abiertos y cerrados a los servicios", + "service_description_yunohost-api": "Gestiona las interacciones entre la interfaz web de YunoHost y el sistema", + "service_description_ssh": "Permite conectar a su servidor remotamente mediante un terminal (protocolo SSH)", + "service_description_slapd": "Almacena usuarios, dominios e información relacionada", + "service_description_rspamd": "Filtra correo no deseado y otras características relacionadas con el correo", + "service_description_rmilter": "Comprueba varios parámetros en el correo", + "service_description_redis-server": "Una base de datos especializada usada para el acceso rápido de datos, cola de tareas y comunicación entre programas", + "service_description_postfix": "Usado para enviar y recibir correos", + "service_description_php7.0-fpm": "Ejecuta aplicaciones escritas en PHP con NGINX", + "service_description_nslcd": "Maneja la conexión del intérprete de órdenes («shell») de usuario de YunoHost", + "service_description_nginx": "Sirve o proporciona acceso a todos los sitios web alojados en su servidor", + "service_description_mysql": "Almacena los datos de las aplicaciones (base de datos SQL)", + "service_description_metronome": "Gestionar las cuentas XMPP de mensajería instantánea", + "service_description_glances": "Supervisa la información del sistema en su servidor", + "service_description_fail2ban": "Protege contra ataques de fuerza bruta y otras clases de ataques desde Internet", + "service_description_dovecot": "Permite a los clientes de correo acceder/obtener correo (vía IMAP y POP3)", + "service_description_dnsmasq": "Maneja la resolución de nombres de dominio (DNS)", + "service_description_avahi-daemon": "Permite acceder a su servidor usando «yunohost.local» en su red local", "server_reboot_confirm": "El servidor se reiniciará inmediatamente ¿está seguro? [{answers:s}]", "server_reboot": "El servidor se reiniciará", "server_shutdown_confirm": "El servidor se apagará inmediatamente ¿está seguro? [{answers:s}]", "server_shutdown": "El servidor se apagará", "root_password_replaced_by_admin_password": "Su contraseña de root ha sido sustituida por su contraseña de administración.", - "root_password_desynchronized": "La contraseña de administración ha sido cambiada pero ¡YunoHost no pudo propagar esto en la contraseña de root!", - "restore_system_part_failed": "No se puede restaurar la parte del sistema «{part:s}»", - "restore_removing_tmp_dir_failed": "No se puede eliminar un antiguo directorio temporal", - "restore_not_enough_disk_space": "Insuficiente espacio en disco (espacio libre: {free_space:d} B, espacio necesario: {needed_space:d} B, margen de seguridad: {margin:d} B)", + "root_password_desynchronized": "La contraseña de administración ha sido cambiada pero ¡YunoHost no pudo propagar esto a la contraseña de root!", + "restore_system_part_failed": "No se pudo restaurar la parte del sistema «{part:s}»", + "restore_removing_tmp_dir_failed": "No se pudo eliminar un directorio temporal antiguo", + "restore_not_enough_disk_space": "Espacio insuficiente (espacio: {free_space:d} B, espacio necesario: {needed_space:d} B, margen de seguridad: {margin:d} B)", "restore_mounting_archive": "Montando archivo en «{path:s}»", - "restore_may_be_not_enough_disk_space": "Parece que su sistema no tiene suficiente espacio de disco libre (espacio libre: {free_space:d} B, espacio necesario: {needed_space:d} B, margen de seguridad: {margin:d} B)", + "restore_may_be_not_enough_disk_space": "Parece que su sistema no tiene suficiente espacio (libre: {free_space:d} B, espacio necesario: {needed_space:d} B, margen de seguridad: {margin:d} B)", "restore_extracting": "Extrayendo los archivos necesarios para el archivo…", "regenconf_pending_applying": "Aplicando la configuración pendiente para la categoría «{category}»…", - "regenconf_failed": "No se puede regenerar la configuración para la(s) categoría(s): {categories}", + "regenconf_failed": "No se pudo regenerar la configuración para la(s) categoría(s): {categories}", "regenconf_dry_pending_applying": "Comprobando la configuración pendiente que habría sido aplicada para la categoría «{category}»…", "regenconf_would_be_updated": "La configuración habría sido actualizada para la categoría «{category}»", - "regenconf_updated": "Ha sido actualizada la configuración para la categoría «{category}»", + "regenconf_updated": "Actualizada la configuración para la categoría «{category}»", "regenconf_up_to_date": "Ya está actualizada la configuración para la categoría «{category}»", "regenconf_now_managed_by_yunohost": "El archivo de configuración «{conf}» está gestionado ahora por YunoHost (categoría {category}).", - "regenconf_file_updated": "El archivo de configuración «{conf}» ha sido actualizado", - "regenconf_file_removed": "El archivo de configuración «{conf}» ha sido eliminado", - "regenconf_file_remove_failed": "No se puede eliminar el archivo de configuración «{conf}»", + "regenconf_file_updated": "Actualizado el archivo de configuración «{conf}»", + "regenconf_file_removed": "Eliminado el archivo de configuración «{conf}»", + "regenconf_file_remove_failed": "No se pudo eliminar el archivo de configuración «{conf}»", "regenconf_file_manually_removed": "El archivo de configuración «{conf}» ha sido eliminado manualmente y no se creará", "regenconf_file_manually_modified": "El archivo de configuración «{conf}» ha sido modificado manualmente y no será actualizado", "regenconf_file_kept_back": "Se espera que el archivo de configuración «{conf}» sea eliminado por regen-conf (categoría {category}) pero ha sido retenido.", - "regenconf_file_copy_failed": "No se puede copiar el nuevo archivo de configuración «{new}» a «{conf}»", - "regenconf_file_backed_up": "El archivo de configuración «{conf}» ha sido respaldado en «{backup}»", - "remove_user_of_group_not_allowed": "No tiene permiso para eliminar al usuario {user:s} en el grupo {group:s}", + "regenconf_file_copy_failed": "No se pudo copiar el nuevo archivo de configuración «{new}» a «{conf}»", + "regenconf_file_backed_up": "Archivo de configuración «{conf}» respaldado en «{backup}»", + "remove_user_of_group_not_allowed": "No tiene permiso para eliminar al usuario «{user:s}» en el grupo «{group:s}»", "remove_main_permission_not_allowed": "No se permite eliminar el permiso principal", "recommend_to_add_first_user": "La posinstalación ha terminado pero YunoHost necesita al menos un usuario para funcionar correctamente, debe añadir uno ejecutando «yunohost user create » o usando la interfaz de administración.", "permission_update_nothing_to_do": "No hay permisos para actualizar", - "permission_updated": "Permiso «{permission:s}» para la aplicación {app:s} actualizado", - "permission_generated": "La base de datos de permisos se ha actualizado", - "permission_update_failed": "Actualización de permiso fallida", - "permission_name_not_valid": "Nombre de permiso «{permission:s}» no válido", - "permission_not_found": "Permiso «{permission:s}» no encontrado para la aplicación {app:s}", - "permission_deletion_failed": "Permiso «{permission:s}» para eliminar la aplicación «{app:s}» fallido", + "permission_updated": "Actualizado el permiso «{permission:s}» para la aplicación «{app:s}»", + "permission_generated": "Actualizada la base de datos de permisos", + "permission_update_failed": "No se pudo actualizar el permiso", + "permission_name_not_valid": "Elija un nombre de permiso permitido para «{permission:s}", + "permission_not_found": "No se encontró el permiso «{permission:s}» para la aplicación «{app:s}»", + "permission_deletion_failed": "Falta el permiso «{permission:s}» para eliminar la aplicación «{app:s}»", "permission_deleted": "Eliminado el permiso «{permission:s}» para la aplicación {app:s}", - "permission_creation_failed": "Ha fallado la creación del permiso", + "permission_creation_failed": "No se pudo conceder el permiso", "permission_created": "Creado el permiso «{permission:s}» para la aplicación {app:s}", "permission_already_exist": "El permiso «{permission:s}» para la aplicación {app:s} ya existe", "permission_already_clear": "El permiso «{permission:s}» ya está definido para la aplicación {app:s}", - "pattern_password_app": "Las contraseñas no deben incluir los siguientes caracteres: {forbidden_chars}", - "need_define_permission_before": "Necesita redefinir los permisos ejecutando «yunohost user permission add -u USUARIO» antes de eliminar un grupo permitido", - "migrations_to_be_ran_manually": "La migración {id} hay que ejecutarla manualmente. Vaya a Herramientas > Migraciones en la web de administración o ejecute `yunohost tools migrations migrate`.", - "migrations_success_forward": "¡Migración {id} ejecutada correctamente!", + "pattern_password_app": "Las contraseñas no pueden incluir los siguientes caracteres: {forbidden_chars}", + "need_define_permission_before": "Redefina los permisos ejecutando «yunohost user permission add -u USUARIO» antes de eliminar un grupo permitido", + "migrations_to_be_ran_manually": "La migración {id} hay que ejecutarla manualmente. Vaya a Herramientas → Migraciones en la página web de administración o ejecute `yunohost tools migrations migrate`.", + "migrations_success_forward": "Migración {id} completada", "migrations_skip_migration": "Omitiendo migración {id}…", "migrations_running_forward": "Ejecutando migración {id}…", - "migrations_pending_cant_rerun": "Esas migraciones están aún pendientes así que no se pueden volver a ejecutar: {ids}", - "migrations_not_pending_cant_skip": "Esas migraciones no están pendientes así que no pueden ser omitidas: {ids}", - "migrations_no_such_migration": "No existe una migración llamada {id}", + "migrations_pending_cant_rerun": "Esas migraciones están aún pendientes, así que no se pueden volver a ejecutar: {ids}", + "migrations_not_pending_cant_skip": "Esas migraciones no están pendientes, así que no pueden ser omitidas: {ids}", + "migrations_no_such_migration": "No hay ninguna migración llamada {id}", "migrations_no_migrations_to_run": "No hay migraciones que ejecutar", - "migrations_need_to_accept_disclaimer": "Para ejecutar la migración {id} debe aceptar el siguiente descargo de responsabilidad:\n---\n{disclaimer}\n---\nSi acepta ejecutar la migración, vuelva a ejecutar la orden con la opción --accept-disclaimer.", - "migrations_must_provide_explicit_targets": "Necesita proporcionar objetivos explícitos al usar --skip o --force-rerun", - "migrations_migration_has_failed": "Migración {id} fallida, cancelando. Error: {exception}", + "migrations_need_to_accept_disclaimer": "Para ejecutar la migración {id} debe aceptar el siguiente descargo de responsabilidad:\n---\n{disclaimer}\n---\nSi acepta ejecutar la migración, vuelva a ejecutar la orden con la opción «--accept-disclaimer».", + "migrations_must_provide_explicit_targets": "Necesita proporcionar objetivos explícitos al usar «--skip» or «--force-rerun»", + "migrations_migration_has_failed": "La migración {id} no se ha completado, cancelando. Error: {exception}", "migrations_loading_migration": "Cargando migración {id}…", - "migrations_list_conflict_pending_done": "No puede usar --previous y --done al mismo tiempo.", - "migrations_exclusive_options": "--auto, --skip, and --force-rerun son opciones excluyentes.", - "migrations_failed_to_load_migration": "Error al cargar la migración {id} : {error}", + "migrations_list_conflict_pending_done": "No puede usar «--previous» y «--done» al mismo tiempo.", + "migrations_exclusive_options": "«--auto», «--skip», and «--force-rerun» son opciones mutuamente excluyentes.", + "migrations_failed_to_load_migration": "No se pudo cargar la migración {id}: {error}", "migrations_dependencies_not_satisfied": "No se puede ejecutar la migración {id} porque primero necesita ejecutar estas migraciones: {dependencies_id}", - "migrations_cant_reach_migration_file": "No se pueden acceder los archivos de migración en la ruta %s", - "migrations_already_ran": "Esas migraciones ya se han ejecutado: {ids}", - "migration_0011_update_LDAP_schema": "Actualizando el esquema de LDAP...", - "migration_0011_update_LDAP_database": "Actualizando la base de datos de LDAP...", - "migration_0011_rollback_success": "Revertido correctamente.", - "migration_0011_migration_failed_trying_to_rollback": "Migración fallida... intentando revertir el sistema.", - "migration_0011_migrate_permission": "Migrando permisos desde la configuración de las aplicaciones a LDAP...", - "migration_0011_LDAP_update_failed": "Actualización de LDAP fallida. Error: {error:s}", - "migration_0011_LDAP_config_dirty": "Parece que ha personalizado la configuración de LDAP. Para esta migración se necesita actualizar la configuración de LDAP.\nNecesita guardar su configuración actual, restaurar la configuración original con la orden «yunohost tools regen-conf -f» y reintentar después la migración", - "migration_0011_done": "Migración exitosa. Ahora puede gestionar los grupos de usuarios.", - "migration_0011_create_group": "Creando un grupo para cada usuario...", - "migration_0011_can_not_backup_before_migration": "Falló la copia de seguridad del sistema antes de la migración. Migración fallida. Error: {error:s}", + "migrations_cant_reach_migration_file": "No se pudo acceder los archivos de migración en la ruta %s", + "migrations_already_ran": "Esas migraciones ya se han realizado: {ids}", + "migration_0011_update_LDAP_schema": "Actualizando el esquema de LDAP…", + "migration_0011_update_LDAP_database": "Actualizando la base de datos de LDAP…", + "migration_0011_rollback_success": "Sistema revertido.", + "migration_0011_migration_failed_trying_to_rollback": "Migración fallida… intentando revertir el sistema.", + "migration_0011_migrate_permission": "Migrando permisos desde la configuración de las aplicaciones a LDAP…", + "migration_0011_LDAP_update_failed": "No se pudo actualizar LDAP. Error: {error:s}", + "migration_0011_LDAP_config_dirty": "Parece que ha personalizado la configuración de LDAP. Para esta migración se necesita actualizar la configuración de LDAP.\nNecesita guardar su configuración actual, reiniciar la configuración original ejecutando «yunohost tools regen-conf -f» y reintentar la migración", + "migration_0011_done": "Migración correcta. Ahora puede gestionar los grupos de usuarios.", + "migration_0011_create_group": "Creando un grupo para cada usuario…", + "migration_0011_can_not_backup_before_migration": "No se pudo respaldar el sistema antes de la migración. Error: {error:s}", "migration_0011_backup_before_migration": "Creando un respaldo de la base de datos de LDAP y de la configuración de las aplicaciones antes de la migración real.", - "migration_0009_not_needed": "¿La migración ya ocurrió de algún modo? Omitiendo.", - "migration_0008_no_warning": "No se ha detectado ningún riesgo importante sobre la anulación de su configuración SSH ¡pero no existe una certeza absoluta ;)! Si permite a YunoHost anular su configuración actual, ejecute la migración. Por otra parte puede omitir la migración, aunque no se recomienda.", + "migration_0009_not_needed": "La migración ya ocurrió de algún modo… (?) Omitiendo.", + "migration_0008_no_warning": "No se ha detectado ningún riesgo importante con respecto a la anulación de su configuración SSH ¡sin embargo uno nunca puede estar absolutamente seguro ;)! Ejecute la migración para anularla. Por otra parte, puede omitir la migración aunque no esté recomendado.", "migration_0008_warning": "Si entiende esos avisos y permite a YunoHost anular su configuración actual, ejecute la migración. Por otra parte puede omitir la migración, aunque no se recomienda.", - "migration_0008_dsa": " - se desactivará la clave DSA. Así que podría tener que anular un aviso espeluznante de su cliente SSH y volver a comprobar la huella de su servidor;", - "migration_0008_root": " - no podrá conectarse como «root» a través de SSH. En su lugar debería usar el usuario de administración;", - "migration_0008_port": " - tendrá que conectarse usando el puerto 22 en vez de su actual puerto SSH personalizado. No dude en reconfigurarlo;", - "migration_0008_general_disclaimer": "Para mejorar la seguridad de su servidor, es recomendable permitir a YunoHost gestionar la configuración SSH. Su actual configuración SSH difiere de la configuración recomendada. Si permite a YunoHost reconfigurarla, la manera en la que conecta con su servidor a través de SSH cambiará en el siguiente modo:", + "migration_0008_dsa": "• Se desactivará la clave DSA. Así que podría tener que anular un aviso espeluznante de su cliente SSH y volver a comprobar la huella de su servidor;", + "migration_0008_root": "• No podrá conectarse como «root» a través de SSH. En su lugar debe usar el usuario «admin»;", + "migration_0008_port": "• Tendrá que conectarse usando el puerto 22 en vez de su actual puerto SSH personalizado. No dude en reconfigurarlo;", + "migration_0008_general_disclaimer": "Para mejorar la seguridad de su servidor, es recomendable permitir a YunoHost gestionar la configuración de SSH. Su actual configuración de SSH difiere de la recomendación. Si permite a YunoHost reconfigurarla, la manera en la que conecta con su servidor a través de SSH cambiará así:", "migration_0007_cannot_restart": "No se puede reiniciar SSH después de intentar cancelar la migración número 6.", "migration_0007_cancelled": "YunoHost no ha podido mejorar el modo en el que se gestiona su configuración de SSH.", "migration_0006_disclaimer": "YunoHost espera ahora que las contraseñas de «admin» y «root» estén sincronizadas. Al ejecutar esta migración, su contraseña de «root» será reemplazada por la contraseña de administración.", - "migration_0005_not_enough_space": "No hay suficiente espacio libre disponible en {path} para ejecutar la migración en este momento:(.", - "migration_0005_postgresql_96_not_installed": "¿¡Se encontró postgresql 9.4 para ser instalado pero no postgresql 9.6!? Algo raro podría haber ocurrido en su sistema:(…", - "migration_0005_postgresql_94_not_installed": "Postgresql no estaba instalado en su sistema. ¡Nada que hacer!", - "migration_0003_modified_files": "Tenga en cuenta que se encontró que los siguientes archivos fueron modificados manualmente y podrían ser sobrescritos al final de la actualización: {manually_modified_files}", + "migration_0005_not_enough_space": "Tenga suficiente espacio libre disponible en {path} para ejecutar la migración.", + "migration_0005_postgresql_96_not_installed": "⸘PostgreSQL 9.4 está instalado pero no PostgreSQL 9.6‽ Algo raro podría haber ocurrido en su sistema:(…", + "migration_0005_postgresql_94_not_installed": "PostgreSQL no estaba instalado en su sistema. Nada que hacer.", + "migration_0003_modified_files": "Tenga en cuenta que se encontró que los siguientes archivos fueron modificados manualmente y podrían ser sobrescritos después de la actualización: {manually_modified_files}", "migration_0003_problematic_apps_warning": "Tenga en cuenta que se detectaron las siguientes aplicaciones instaladas posiblemente problemáticas. Parece que no fueron instaladas desde una lista de aplicaciones o no estaban etiquetadas como «funciona». Así que no hay garantía de que aún funcionen después de la actualización: {problematic_apps}", - "migration_0003_general_warning": "Tenga en cuenta que esta migración es una operación delicada. Aunque el equipo de YunoHost hizo todo lo posible para revisarla y probarla, la migración aún podría romper parte del sistema o de las aplicaciones.\n\nPor lo tanto le recomendamos que:\n - Realice una copia de seguridad de cualquier dato crítico o aplicación. Más información en https://yunohost.org/backup;\n - Tenga paciencia tras iniciar la migración: dependiendo de su conexión a internet y de su hardware, podría tardar unas cuantas horas hasta que todo se actualice.\n\nAdemás, el puerto para SMTP usado por los clientes de correo externos (como Thunderbird o K9-Mail) cambió de 465 (SSL/TLS) a 587 (STARTTLS). El antiguo puerto 465 se cerrará automáticamente y el nuevo puerto 587 se abrirá en el cortafuegos. ¡Todos los usuarios *tendrán* que adaptar la configuración de sus clientes de correo por lo tanto!", - "migration_0003_still_on_jessie_after_main_upgrade": "Algo fue mal durante la actualización principal: ¿¡el sistema está aún en Jessie!? Para investigar el problema, vea {log}:s…", + "migration_0003_general_warning": "Tenga en cuenta que esta migración es una operación delicada. El equipo de YunoHost ha hecho todo lo posible para revisarla y probarla, pero la migración aún podría romper parte del sistema o de sus aplicaciones.\n\nPor lo tanto, se recomienda que:\n - Realice una copia de seguridad de cualquier dato crítico o aplicación. Más información en https://yunohost.org/backup;\n - Tenga paciencia tras iniciar la migración: dependiendo de su conexión a Internet y de su hardware, podría tardar unas cuantas horas hasta que todo se actualice.\n\nAdemás, el puerto para SMTP usado por los clientes de correo externos (como Thunderbird o K9-Mail) cambió de 465 (SSL/TLS) a 587 (STARTTLS). El antiguo puerto (465) se cerrará automáticamente y el nuevo puerto (587) se abrirá en el cortafuegos. Todos los usuarios *tendrán* que adaptar la configuración de sus clientes de correo por lo tanto.", + "migration_0003_still_on_jessie_after_main_upgrade": "Algo fue mal durante la actualización principal: ⸘el sistema está aún en Jessie‽ Para investigar el problema, vea {log}:s…", "migration_0003_system_not_fully_up_to_date": "Su sistema no está totalmente actualizado. Realice una actualización normal antes de ejecutar la migración a Stretch.", "migration_0003_not_jessie": "¡La distribución de Debian actual no es Jessie!", - "migration_0003_yunohost_upgrade": "Iniciando la actualización del paquete «yunohost»… La migración finalizará pero la actualización real ocurrirá justo después. Después de que la operación esté completada, podría tener que reiniciar sesión en la administración web.", + "migration_0003_yunohost_upgrade": "Iniciando la actualización del paquete YunoHost… La migración finalizará pero la actualización real ocurrirá inmediatamente después. Después de que la operación esté completada, podría tener que iniciar sesión en la página de administración de nuevo.", "migration_0003_restoring_origin_nginx_conf": "Su archivo /etc/nginx/nginx.conf ha sido editado de algún modo. La migración lo devolverá a su estado original primero… El archivo anterior estará disponible como {backup_dest}.", - "migration_0003_fail2ban_upgrade": "Iniciando la actualización de «fail2ban»…", + "migration_0003_fail2ban_upgrade": "Iniciando la actualización de Fail2Ban…", "migration_0003_main_upgrade": "Iniciando la actualización principal…", "migration_0003_patching_sources_list": "Corrigiendo «sources.lists»…", "migration_0003_start": "Iniciando migración a Stretch. El registro estará disponible en {logfile}.", - "migration_description_0012_postgresql_password_to_md5_authentication": "Forzar a la autentificación de postgresql a usar md5 para las conexiones locales", + "migration_description_0012_postgresql_password_to_md5_authentication": "Forzar a la autentificación de PostgreSQL a usar MD5 para las conexiones locales", "migration_description_0011_setup_group_permission": "Configurar grupo de usuario y configurar permisos para aplicaciones y servicios", - "migration_description_0010_migrate_to_apps_json": "Eliminar la obsoleta «appslists» y usar la nueva lista unificada «apps.json»", + "migration_description_0010_migrate_to_apps_json": "Eliminar las listas de aplicaciones («appslists») obsoletas y usar en su lugar la nueva lista unificada «apps.json»", "migration_description_0009_decouple_regenconf_from_services": "Separar el mecanismo «regen-conf» de los servicios", "migration_description_0008_ssh_conf_managed_by_yunohost_step2": "Permitir que la configuración de SSH la gestione YunoHost (paso 2, manual)", "migration_description_0007_ssh_conf_managed_by_yunohost_step1": "Permitir que la configuración de SSH la gestione YunoHost (paso 1, automático)", "migration_description_0006_sync_admin_and_root_passwords": "Sincronizar las contraseñas de «admin» y «root»", - "migration_description_0005_postgresql_9p4_to_9p6": "Migrar las bases de datos de postgresql 9.4 a 9.6", + "migration_description_0005_postgresql_9p4_to_9p6": "Migrar las bases de datos de PostgreSQL 9.4 a 9.6", "migration_description_0004_php5_to_php7_pools": "Reconfigurar los «pools» de PHP para usar PHP 7 en vez de 5", "migration_description_0003_migrate_to_stretch": "Actualizar el sistema a Debian Stretch y YunoHost 3.0", - "migration_description_0002_migrate_to_tsig_sha256": "Mejorar la seguridad de la TSIG de dyndns usando SHA512 en vez de MD5", + "migration_description_0002_migrate_to_tsig_sha256": "Mejore la seguridad de las actualizaciones de TSIG de DynDNS usando SHA-512 en vez de MD5", "migration_description_0001_change_cert_group_to_sslcert": "Cambiar los permisos de grupo de certificados de «metronome» a «ssl-cert»", - "migrate_tsig_not_needed": "Parece que no usa un dominio dyndns ¡así que no es necesario migrar!", + "migrate_tsig_not_needed": "Parece que no usa un dominio de DynDNS, así que no es necesario migrar.", "migrate_tsig_wait_4": "30 segundos…", "migrate_tsig_wait_3": "1 min. …", "migrate_tsig_wait_2": "2 min. …", - "migrate_tsig_wait": "Esperar 3 min. para que el servidor dyndns tenga en cuenta la nueva clave…", - "migrate_tsig_start": "Detectado algoritmo de clave insuficientemente seguro para la firma TSIG del dominio «{domain}», iniciando migración al más seguro hmac-sha512", - "migrate_tsig_failed": "Error al migrar el dominio de dyndns {domain} a hmac-sha512, revertiendo. Error: {error_code} - {error}", - "migrate_tsig_end": "Terminada la migración a hmac-sha512", + "migrate_tsig_wait": "Esperando tres minutos para que el servidor de DynDNS tenga en cuenta la nueva clave…", + "migrate_tsig_start": "Detectado algoritmo de clave insuficientemente seguro para la firma TSIG del dominio «{domain}», iniciando migración al más seguro HMAC-SHA-512", + "migrate_tsig_failed": "No se pudo migrar el dominio de DynDNS «{domain}» a HMAC-SHA-512, revertiendo. Error: {error_code}, {error}", + "migrate_tsig_end": "Terminada la migración a HMAC-SHA-512", "mail_unavailable": "Esta dirección de correo está reservada y será asignada automáticamente al primer usuario", - "mailbox_disabled": "Mailbox desactivado para usuario {user:s}", + "mailbox_disabled": "Correo desactivado para usuario {user:s}", "log_tools_reboot": "Reiniciar el servidor", "log_tools_shutdown": "Apagar el servidor", "log_tools_upgrade": "Actualizar paquetes del sistema", "log_tools_postinstall": "Posinstalación del servidor YunoHost", "log_tools_migrations_migrate_forward": "Migrar hacia adelante", - "log_tools_maindomain": "Convertir «{}» en dominio principal", + "log_tools_maindomain": "Convertir «{}» en el dominio principal", "log_user_permission_remove": "Actualizar permiso «{}»", "log_user_permission_add": "Actualizar permiso «{}»", - "log_user_update": "Actualizar información del usuario «{}»", + "log_user_update": "Actualizar la información de usuario de «{}»", "log_user_group_update": "Actualizar grupo «{}»", "log_user_group_delete": "Eliminar grupo «{}»", "log_user_group_add": "Añadir grupo «{}»", @@ -506,8 +506,8 @@ "log_selfsigned_cert_install": "Instalar certificado autofirmado en el dominio «{}»", "log_permission_update": "Actualizar permiso «{}» para la aplicación «{}»", "log_permission_remove": "Eliminar permiso «{}»", - "log_permission_add": "Añadir permiso «{}» para la aplicación «{}»", - "log_letsencrypt_cert_install": "Instalar certificado de Let's encrypt en el dominio «{}»", + "log_permission_add": "Añadir el permiso «{}» para la aplicación «{}»", + "log_letsencrypt_cert_install": "Instalar un certificado de Let's encrypt en el dominio «{}»", "log_dyndns_update": "Actualizar la IP asociada con su subdominio de YunoHost «{}»", "log_dyndns_subscribe": "Subscribirse a un subdomino de YunoHost «{}»", "log_domain_remove": "Eliminar el dominio «{}» de la configuración del sistema", @@ -521,7 +521,7 @@ "log_app_upgrade": "Actualizar la aplicación «{}»", "log_app_remove": "Eliminar la aplicación «{}»", "log_app_install": "Instalar la aplicación «{}»", - "log_app_change_url": "Cambiar la url de la aplicación «{}»", + "log_app_change_url": "Cambiar el URL de la aplicación «{}»", "log_app_removelist": "Eliminar una lista de aplicaciones", "log_app_fetchlist": "Añadir una lista de aplicaciones", "log_app_clearaccess": "Eliminar todos los accesos a «{}»", @@ -529,26 +529,26 @@ "log_app_addaccess": "Añadir acceso a «{}»", "log_operation_unit_unclosed_properly": "La unidad de operación no se ha cerrado correctamente", "log_does_exists": "No existe ningún registro de actividades con el nombre «{log}», ejecute «yunohost log list» para ver todos los registros de actividades disponibles", - "log_help_to_get_failed_log": "¡La operación «{desc}» ha fallado! Para obtener ayuda, comparta el registro completo de esta operación ejecutando la orden «yunohost log display {name} --share»", - "log_link_to_failed_log": "¡La operación «{desc}» ha fallado! Para obtener ayuda, proporcione el registro completo de esta operación pulsando aquí", + "log_help_to_get_failed_log": "No se pudo completar la operación «{desc}». Para obtener ayuda, comparta el registro completo de esta operación ejecutando la orden «yunohost log display {name} --share»", + "log_link_to_failed_log": "No se pudo completar la operación «{desc}». Para obtener ayuda, proporcione el registro completo de esta operación pulsando aquí", "log_help_to_get_log": "Para ver el registro de la operación «{desc}», ejecute la orden «yunohost log display {name}»", "log_link_to_log": "Registro completo de esta operación: «{desc}»", "log_category_404": "La categoría de registro «{category}» no existe", - "log_corrupted_md_file": "El archivo de metadatos yaml asociado con el registro está dañado: «{md_file}\nError: {error}»", - "hook_json_return_error": "Error al leer la respuesta del gancho {path:s}. Error: {msg:s}. Contenido sin procesar: {raw_content}", - "group_update_failed": "Error en la actualización del grupo «{group}»", + "log_corrupted_md_file": "El archivo de metadatos YAML asociado con el registro está dañado: «{md_file}\nError: {error}»", + "hook_json_return_error": "No se pudo leer la respuesta del gancho {path:s}. Error: {msg:s}. Contenido sin procesar: {raw_content}", + "group_update_failed": "No se pudo actualizar el grupo «{group}»", "group_updated": "Grupo «{group}» actualizado", - "group_unknown": "Grupo {group:s} desconocido", - "group_info_failed": "Error en la información del grupo", + "group_unknown": "El grupo «{group:s}» es desconocido", + "group_info_failed": "No se pudo mostrar la información del grupo", "group_deletion_not_allowed": "No se puede eliminar el grupo {group:s} manualmente.", - "group_deletion_failed": "Error al eliminar el grupo «{group}»", + "group_deletion_failed": "No se pudo eliminar el grupo «{group}»", "group_deleted": "Eliminado el grupo «{group}»", - "group_creation_failed": "Error al crear el grupo «{group}»", - "group_created": "Grupo «{group}» creado correctamente", + "group_creation_failed": "No se pudo crear el grupo «{group}»", + "group_created": "Creado el grupo «{group}»", "group_name_already_exist": "El grupo {name:s} ya existe", - "group_already_disallowed": "El grupo '{group:s}' ya tiene desactivado el permiso «{permission:s}» para la aplicación «{app:s}»", - "group_already_allowed": "El grupo '{group:s}' ya tiene activado el permiso «{permission:s}» para la aplicación «{app:s}»", - "good_practices_about_admin_password": "Va a determinar una nueva contraseña de administración. La contraseña debería tener al menos 8 caracteres, aunque es una buena práctica usar contraseñas más extensas (esto es, una frase) y/o usar caracteres de varias clases (mayúsculas, minúsculas, números y caracteres especiales).", + "group_already_disallowed": "El grupo «{group:s}» ya tiene desactivado el permiso «{permission:s}» para la aplicación «{app:s}»", + "group_already_allowed": "El grupo «{group:s}» ya tiene activado el permiso «{permission:s}» para la aplicación «{app:s}»", + "good_practices_about_admin_password": "Va a establecer una nueva contraseña de administración. La contraseña debería tener al menos 8 caracteres, aunque es una buena práctica usar una contraseña más extensa (básicamente una frase) y/o usar caracteres de varias clases (mayúsculas, minúsculas, números y caracteres especiales).", "global_settings_unknown_type": "Situación imprevista, la configuración {setting:s} parece tener el tipo {unknown_type:s} pero no es un tipo compatible con el sistema.", "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Permitir el uso de la llave (obsoleta) DSA para la configuración del demonio SSH", "global_settings_unknown_setting_from_settings_file": "Clave desconocida en la configuración: «{setting_key:s}», desechada y guardada en /etc/yunohost/settings-unknown.json", @@ -556,53 +556,55 @@ "global_settings_setting_security_ssh_compatibility": "Compromiso entre compatibilidad y seguridad para el servidor SSH. Afecta al cifrado (y otros aspectos relacionados con la seguridad)", "global_settings_setting_security_password_user_strength": "Seguridad de la contraseña de usuario", "global_settings_setting_security_password_admin_strength": "Seguridad de la contraseña del administrador", - "global_settings_setting_security_nginx_compatibility": "Compromiso entre compatibilidad y seguridad para el servidor web nginx. Afecta al cifrado (y otros aspectos relacionados con la seguridad)", + "global_settings_setting_security_nginx_compatibility": "Compromiso entre compatibilidad y seguridad para el servidor web NGINX. Afecta al cifrado (y otros aspectos relacionados con la seguridad)", "global_settings_setting_example_string": "Ejemplo de opción de cadena", "global_settings_setting_example_int": "Ejemplo de opción «int»", "global_settings_setting_example_enum": "Ejemplo de opción «enum»", "global_settings_setting_example_bool": "Ejemplo de opción booleana", - "global_settings_reset_success": "Éxito. Se ha respaldado su configuración previa en {path:s}", + "global_settings_reset_success": "Respaldada la configuración previa en {path:s}", "global_settings_key_doesnt_exists": "La clave «{settings_key:s}» no existe en la configuración global, puede ver todas las claves disponibles ejecutando «yunohost settings list»", - "global_settings_cant_write_settings": "Error al escribir el archivo de configuración, motivo: {reason:s}", - "global_settings_cant_serialize_settings": "Error al seriar los datos de configuración, motivo: {reason:s}", - "global_settings_cant_open_settings": "Error al abrir el archivo de configuración, motivo: {reason:s}", - "global_settings_bad_type_for_setting": "Tipo erróneo para la configuración {setting:s}, obtuvo {received_type:s}, excepto {expected_type:s}", - "global_settings_bad_choice_for_enum": "Mala elección para la configuración {setting:s}, obtuvo «{choice:s}» pero las opciones disponibles son: {available_choices:s}", + "global_settings_cant_write_settings": "No se pudo guardar el archivo de configuración, motivo: {reason:s}", + "global_settings_cant_serialize_settings": "No se pudo seriar los datos de configuración, motivo: {reason:s}", + "global_settings_cant_open_settings": "No se pudo abrir el archivo de configuración, motivo: {reason:s}", + "global_settings_bad_type_for_setting": "Tipo erróneo para la configuración {setting:s}, obtuvo {received_type:s}, esperado {expected_type:s}", + "global_settings_bad_choice_for_enum": "Opción errónea para la configuración {setting:s}, obtuvo «{choice:s}» pero las opciones disponibles son: {available_choices:s}", "file_does_not_exist": "El archivo {path:s} no existe.", - "error_when_removing_sftpuser_group": "Error al probar «remove sftpusers group»", + "error_when_removing_sftpuser_group": "No se pudo eliminar el grupo sftpusers", "edit_permission_with_group_all_users_not_allowed": "No puede editar el permiso para el grupo «all_users», utilice «yunohost user permission clear APLICACIÓN» o «yunohost user permission add APLICACIÓN -u USUARIO».", "edit_group_not_allowed": "No tiene permiso para editar el grupo {group:s}", "dyndns_could_not_check_available": "No se pudo comprobar si {domain:s} está disponible en {provider:s}.", - "domain_dyndns_dynette_is_unreachable": "No se pudo conectar al dynette de YunoHost, o su YunoHost no está correctamente conectado a internet o el servidor dynette está caído. Error: {error}", - "domain_dns_conf_is_just_a_recommendation": "Esta orden muestra cuál es la configuración *recomendada*. No configura el DNS. Es su responsabilidad configurar la zona de DNS en su registrador según esta recomendación.", + "domain_dyndns_dynette_is_unreachable": "No se pudo conectar a dynette de YunoHost. O su YunoHost no está correctamente conectado a Internet o el servidor dynette está caído. Error: {error}", + "domain_dns_conf_is_just_a_recommendation": "Esta orden muestra la configuración *recomendada*. No configura el DNS en realidad. Es su responsabilidad configurar la zona de DNS en su registrador según esta recomendación.", "dpkg_lock_not_available": "Esta orden no se puede ejecutar en este momento porque otro programa parece que está usando el bloqueo de dpkg (el gestor de paquetes del sistema)", "dpkg_is_broken": "No puede hacer esto en este momento porque dpkg/apt (los gestores de paquetes del sistema) parecen estar en un estado roto... Puede tratar de solucionar este problema conectando a través de SSH y ejecutando `sudo dpkg --configure -a`.", "confirm_app_install_thirdparty": "¡AVISO! Instalar aplicaciones de terceros podría comprometer la integridad y seguridad de su sistema. Probablemente NO debería instalarlas salvo que sepa lo que está haciendo. ¿Está dispuesto a correr ese riesgo? [{answers:s}] ", "confirm_app_install_danger": "¡AVISO! Esta aplicación es aún experimental (si no está funcionando expresamente) y ¡es probable que rompa su sistema! Probablemente NO debería instalarla salvo que sepa lo que está haciendo. ¿Está dispuesto a correr ese riesgo? [{answers:s}] ", "confirm_app_install_warning": "Aviso: esta aplicación puede funcionar pero no está bien integrada en YunoHost. Algunas herramientas como la autentificación única y respaldo/restauración podrían no estar disponibles. ¿Instalar de todos modos? [{answers:s}] ", - "backup_unable_to_organize_files": "No se pueden organizar los archivos en el archivo con el método rápido", + "backup_unable_to_organize_files": "No se pudo usar el método rápido de organización de los archivos en el archivo", "backup_permission": "Permiso de respaldo para la aplicación {app:s}", - "backup_output_symlink_dir_broken": "Tiene un enlace simbólico roto en vez del directorio «{path:s}» de sus archivos. Puede que tenga una configuración específica para respaldar sus datos en otro sistema de archivos, en este caso probablemente olvidó remontar o conectar su disco duro o clave usb.", + "backup_output_symlink_dir_broken": "Tiene un enlace simbólico roto en vez del directorio «{path:s}» de su archivo. Puede que tenga una configuración específica para respaldar sus datos en otro sistema de archivos, en este caso probablemente olvidó remontar o conectar su disco duro o clave USB.", "backup_mount_archive_for_restore": "Preparando el archivo para la restauración…", - "backup_method_tar_finished": "Creado el archivo de respaldo tar", + "backup_method_tar_finished": "Creado el archivo TAR de respaldo", "backup_method_custom_finished": "Terminado el método «{method:s}» de respaldo personalizado", "backup_method_copy_finished": "Terminada la copia de seguridad", - "backup_method_borg_finished": "Terminado el respaldo en borg", - "backup_custom_backup_error": "Fallo del método de respaldo personalizado en el paso «copia de seguridad»", + "backup_method_borg_finished": "Terminado el respaldo en Borg", + "backup_custom_backup_error": "El método de respaldo personalizado no pudo superar el paso de «copia de seguridad»", "backup_actually_backuping": "Creando un archivo de respaldo de los archivos obtenidos…", "ask_new_path": "Nueva ruta", "ask_new_domain": "Nuevo dominio", - "apps_permission_restoration_failed": "El permiso «{permission:s}» para la restauración de la aplicación {app:s} ha fallado", + "apps_permission_restoration_failed": "Otorgar el permiso «{permission:s}» para restaurar {app:s}", "apps_permission_not_found": "No se han encontrado permisos para las aplicaciones instaladas", "app_upgrade_several_apps": "Las siguientes aplicaciones se actualizarán: {apps}", "app_start_restore": "Restaurando aplicación {app}…", - "app_start_backup": "Obteniendo archivos de respaldo para {app}…", + "app_start_backup": "Obteniendo archivos para el respaldo de {app}…", "app_start_remove": "Eliminando aplicación {app}…", "app_start_install": "Instalando aplicación {app}…", "app_not_upgraded": "Error al actualizar la aplicación «{failed_app}» y como consecuencia se han cancelado las actualizaciones de las siguientes aplicaciones: {apps}", "app_action_cannot_be_ran_because_required_services_down": "Esta aplicación necesita algunos servicios que no están funcionando ahora. Antes de continuar, debería intentar reiniciar los siguientes servicios (y posiblemente investigar por qué no funcionan): {services}", - "already_up_to_date": "¡Nada que hacer! ¡Todo está actualizado!", + "already_up_to_date": "Nada que hacer. Todo está actualizado.", "admin_password_too_long": "Elija una contraseña de menos de 127 caracteres", "aborting": "Cancelando.", - "app_upgrade_stopped": "Se ha detenido la actualización de todas las aplicaciones para prevenir un posible daño porque la aplicación anterior no se pudo actualizar" + "app_upgrade_stopped": "Se ha detenido la actualización de todas las aplicaciones para prevenir un posible daño porque la aplicación anterior no se pudo actualizar", + "app_action_broke_system": "Esta acción parece que ha roto estos importantes servicios: {services}", + "operation_interrupted": "¿Ha sido interrumpida la operación manualmente?" } From 61fb0be7735dcc8369c03de4e066776aefad83e6 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 23 Sep 2019 20:57:59 +0200 Subject: [PATCH 0181/3170] More accurate tests with explicit exception/message excepted to be triggered --- src/yunohost/tests/conftest.py | 23 +++ src/yunohost/tests/test_backuprestore.py | 184 ++++++++++------------- src/yunohost/tests/test_permission.py | 128 ++++++++++------ src/yunohost/tests/test_user-group.py | 146 +++++++++++------- src/yunohost/utils/error.py | 4 +- 5 files changed, 285 insertions(+), 200 deletions(-) diff --git a/src/yunohost/tests/conftest.py b/src/yunohost/tests/conftest.py index a2dc585bd..e23110d1a 100644 --- a/src/yunohost/tests/conftest.py +++ b/src/yunohost/tests/conftest.py @@ -1,9 +1,32 @@ +import pytest import sys import moulinette +from moulinette import m18n +from yunohost.utils.error import YunohostError +from contextlib import contextmanager + sys.path.append("..") + +@contextmanager +def message(mocker, key, **kwargs): + mocker.spy(m18n, "n") + yield + m18n.n.assert_any_call(key, **kwargs) + + +@contextmanager +def raiseYunohostError(mocker, key, **kwargs): + with pytest.raises(YunohostError) as e_info: + yield + assert e_info._excinfo[1].key == key + if kwargs: + assert e_info._excinfo[1].kwargs == kwargs + + + def pytest_addoption(parser): parser.addoption("--yunodebug", action="store_true", default=False) diff --git a/src/yunohost/tests/test_backuprestore.py b/src/yunohost/tests/test_backuprestore.py index cab98089b..ce3e28401 100644 --- a/src/yunohost/tests/test_backuprestore.py +++ b/src/yunohost/tests/test_backuprestore.py @@ -2,14 +2,13 @@ import pytest import os import shutil import subprocess -from mock import ANY -from moulinette import m18n +from conftest import message, raiseYunohostError + from yunohost.app import app_install, app_remove, app_ssowatconf from yunohost.app import _is_installed from yunohost.backup import backup_create, backup_restore, backup_list, backup_info, backup_delete, _recursive_umount from yunohost.domain import _get_maindomain -from yunohost.utils.error import YunohostError 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 @@ -206,10 +205,11 @@ def add_archive_system_from_2p4(): # -def test_backup_only_ldap(): +def test_backup_only_ldap(mocker): # Create the backup - backup_create(system=["conf_ldap"], apps=None) + with message(mocker, "backup_created"): + backup_create(system=["conf_ldap"], apps=None) archives = backup_list()["archives"] assert len(archives) == 1 @@ -222,24 +222,22 @@ def test_backup_only_ldap(): def test_backup_system_part_that_does_not_exists(mocker): - mocker.spy(m18n, "n") - # Create the backup - with pytest.raises(YunohostError): - backup_create(system=["yolol"], apps=None) + with message(mocker, 'backup_hook_unknown', hook="doesnt_exist"): + with raiseYunohostError(mocker, "backup_nothings_done"): + backup_create(system=["doesnt_exist"], apps=None) - m18n.n.assert_any_call('backup_hook_unknown', hook="yolol") - m18n.n.assert_any_call('backup_nothings_done') # # System backup and restore # # -def test_backup_and_restore_all_sys(): +def test_backup_and_restore_all_sys(mocker): # Create the backup - backup_create(system=[], apps=None) + with message(mocker, "backup_created"): + backup_create(system=[], apps=None) archives = backup_list()["archives"] assert len(archives) == 1 @@ -255,8 +253,9 @@ def test_backup_and_restore_all_sys(): assert not os.path.exists("/etc/ssowat/conf.json") # Restore the backup - backup_restore(name=archives[0], force=True, - system=[], apps=None) + with message(mocker, "restore_complete"): + backup_restore(name=archives[0], force=True, + system=[], apps=None) # Check ssowat conf is back assert os.path.exists("/etc/ssowat/conf.json") @@ -270,16 +269,18 @@ def test_backup_and_restore_all_sys(): def test_restore_system_from_Ynh2p4(monkeypatch, mocker): # Backup current system - backup_create(system=[], apps=None) + with message(mocker, "backup_created"): + backup_create(system=[], apps=None) archives = backup_list()["archives"] assert len(archives) == 2 # Restore system archive from 2.4 try: - backup_restore(name=backup_list()["archives"][1], - system=[], - apps=None, - force=True) + with message(mocker, "restore_complete"): + backup_restore(name=backup_list()["archives"][1], + system=[], + apps=None, + force=True) finally: # Restore system as it was backup_restore(name=backup_list()["archives"][0], @@ -306,12 +307,10 @@ def test_backup_script_failure_handling(monkeypatch, mocker): # call with monkeypatch). We also patch m18n to check later it's been called # with the expected error message key monkeypatch.setattr("yunohost.backup.hook_exec", custom_hook_exec) - mocker.spy(m18n, "n") - with pytest.raises(YunohostError): - backup_create(system=None, apps=["backup_recommended_app"]) - - m18n.n.assert_any_call('backup_app_failed', app='backup_recommended_app') + with message(mocker, 'backup_app_failed', app='backup_recommended_app'): + with raiseYunohostError(mocker, 'backup_nothings_done'): + backup_create(system=None, apps=["backup_recommended_app"]) @pytest.mark.with_backup_recommended_app_installed @@ -327,25 +326,17 @@ def test_backup_not_enough_free_space(monkeypatch, mocker): monkeypatch.setattr("yunohost.backup.free_space_in_directory", custom_free_space_in_directory) - mocker.spy(m18n, "n") - - with pytest.raises(YunohostError): + with raiseYunohostError(mocker, 'not_enough_disk_space'): backup_create(system=None, apps=["backup_recommended_app"]) - m18n.n.assert_any_call('not_enough_disk_space', path=ANY) - def test_backup_app_not_installed(mocker): assert not _is_installed("wordpress") - mocker.spy(m18n, "n") - - with pytest.raises(YunohostError): - backup_create(system=None, apps=["wordpress"]) - - m18n.n.assert_any_call("unbackup_app", app="wordpress") - m18n.n.assert_any_call('backup_nothings_done') + with message(mocker, "unbackup_app", app="wordpress"): + with raiseYunohostError(mocker, 'backup_nothings_done'): + backup_create(system=None, apps=["wordpress"]) @pytest.mark.with_backup_recommended_app_installed @@ -355,13 +346,9 @@ def test_backup_app_with_no_backup_script(mocker): os.system("rm %s" % backup_script) assert not os.path.exists(backup_script) - mocker.spy(m18n, "n") - - with pytest.raises(YunohostError): - backup_create(system=None, apps=["backup_recommended_app"]) - - m18n.n.assert_any_call("backup_with_no_backup_script_for_app", app="backup_recommended_app") - m18n.n.assert_any_call('backup_nothings_done') + with message(mocker, "backup_with_no_backup_script_for_app", app="backup_recommended_app"): + with raiseYunohostError(mocker, 'backup_nothings_done'): + backup_create(system=None, apps=["backup_recommended_app"]) @pytest.mark.with_backup_recommended_app_installed @@ -371,23 +358,21 @@ def test_backup_app_with_no_restore_script(mocker): os.system("rm %s" % restore_script) assert not os.path.exists(restore_script) - mocker.spy(m18n, "n") - # Backuping an app with no restore script will only display a warning to the # user... - backup_create(system=None, apps=["backup_recommended_app"]) - - m18n.n.assert_any_call("backup_with_no_restore_script_for_app", app="backup_recommended_app") + with message(mocker, "backup_with_no_restore_script_for_app", app="backup_recommended_app"): + backup_create(system=None, apps=["backup_recommended_app"]) @pytest.mark.clean_opt_dir -def test_backup_with_different_output_directory(): +def test_backup_with_different_output_directory(mocker): # Create the backup - backup_create(system=["conf_ssh"], apps=None, - output_directory="/opt/test_backup_output_directory", - name="backup") + with message(mocker, "backup_created"): + backup_create(system=["conf_ssh"], apps=None, + output_directory="/opt/test_backup_output_directory", + name="backup") assert os.path.exists("/opt/test_backup_output_directory/backup.tar.gz") @@ -401,12 +386,14 @@ def test_backup_with_different_output_directory(): @pytest.mark.clean_opt_dir -def test_backup_with_no_compress(): +def test_backup_with_no_compress(mocker): + # Create the backup - backup_create(system=["conf_nginx"], apps=None, - output_directory="/opt/test_backup_output_directory", - no_compress=True, - name="backup") + with message(mocker, "backup_created"): + backup_create(system=["conf_nginx"], apps=None, + output_directory="/opt/test_backup_output_directory", + no_compress=True, + name="backup") assert os.path.exists("/opt/test_backup_output_directory/info.json") @@ -416,10 +403,11 @@ def test_backup_with_no_compress(): # @pytest.mark.with_wordpress_archive_from_2p4 -def test_restore_app_wordpress_from_Ynh2p4(): +def test_restore_app_wordpress_from_Ynh2p4(mocker): - backup_restore(system=None, name=backup_list()["archives"][0], - apps=["wordpress"]) + with message(mocker, "restore_complete"): + backup_restore(system=None, name=backup_list()["archives"][0], + apps=["wordpress"]) @pytest.mark.with_wordpress_archive_from_2p4 @@ -431,16 +419,14 @@ def test_restore_app_script_failure_handling(monkeypatch, mocker): raise Exception monkeypatch.setattr("yunohost.backup.hook_exec", custom_hook_exec) - mocker.spy(m18n, "n") assert not _is_installed("wordpress") - with pytest.raises(YunohostError): - backup_restore(system=None, name=backup_list()["archives"][0], - apps=["wordpress"]) + with message(mocker, 'restore_app_failed', app='wordpress'): + with raiseYunohostError(mocker, 'restore_nothings_done'): + backup_restore(system=None, name=backup_list()["archives"][0], + apps=["wordpress"]) - m18n.n.assert_any_call('restore_app_failed', app='wordpress') - m18n.n.assert_any_call('restore_nothings_done') assert not _is_installed("wordpress") @@ -452,18 +438,13 @@ def test_restore_app_not_enough_free_space(monkeypatch, mocker): monkeypatch.setattr("yunohost.backup.free_space_in_directory", custom_free_space_in_directory) - mocker.spy(m18n, "n") assert not _is_installed("wordpress") - with pytest.raises(YunohostError): + with raiseYunohostError(mocker, 'restore_not_enough_disk_space'): backup_restore(system=None, name=backup_list()["archives"][0], apps=["wordpress"]) - m18n.n.assert_any_call('restore_not_enough_disk_space', - free_space=0, - margin=ANY, - needed_space=ANY) assert not _is_installed("wordpress") @@ -473,13 +454,11 @@ def test_restore_app_not_in_backup(mocker): assert not _is_installed("wordpress") assert not _is_installed("yoloswag") - mocker.spy(m18n, "n") + with message(mocker, 'backup_archive_app_not_found', app="yoloswag"): + with raiseYunohostError(mocker, 'restore_nothings_done'): + backup_restore(system=None, name=backup_list()["archives"][0], + apps=["yoloswag"]) - with pytest.raises(YunohostError): - backup_restore(system=None, name=backup_list()["archives"][0], - apps=["yoloswag"]) - - m18n.n.assert_any_call('backup_archive_app_not_found', app="yoloswag") assert not _is_installed("wordpress") assert not _is_installed("yoloswag") @@ -489,38 +468,36 @@ def test_restore_app_already_installed(mocker): assert not _is_installed("wordpress") - backup_restore(system=None, name=backup_list()["archives"][0], - apps=["wordpress"]) - - assert _is_installed("wordpress") - - mocker.spy(m18n, "n") - with pytest.raises(YunohostError): + with message(mocker, "restore_complete"): backup_restore(system=None, name=backup_list()["archives"][0], apps=["wordpress"]) - m18n.n.assert_any_call('restore_already_installed_app', app="wordpress") - m18n.n.assert_any_call('restore_nothings_done') + 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"]) assert _is_installed("wordpress") @pytest.mark.with_legacy_app_installed -def test_backup_and_restore_legacy_app(): +def test_backup_and_restore_legacy_app(mocker): - _test_backup_and_restore_app("legacy_app") + _test_backup_and_restore_app(mocker, "legacy_app") @pytest.mark.with_backup_recommended_app_installed -def test_backup_and_restore_recommended_app(): +def test_backup_and_restore_recommended_app(mocker): - _test_backup_and_restore_app("backup_recommended_app") + _test_backup_and_restore_app(mocker, "backup_recommended_app") @pytest.mark.with_backup_recommended_app_installed_with_ynh_restore -def test_backup_and_restore_with_ynh_restore(): +def test_backup_and_restore_with_ynh_restore(mocker): - _test_backup_and_restore_app("backup_recommended_app") + _test_backup_and_restore_app(mocker, "backup_recommended_app") @pytest.mark.with_permission_app_installed def test_backup_and_restore_permission_app(): @@ -552,10 +529,11 @@ def test_backup_and_restore_permission_app(): assert res['permissions_app.dev']['allowed'] == [] -def _test_backup_and_restore_app(app): +def _test_backup_and_restore_app(mocker, app): # Create a backup of this app - backup_create(system=None, apps=[app]) + with message(mocker, "backup_created"): + backup_create(system=None, apps=[app]) archives = backup_list()["archives"] assert len(archives) == 1 @@ -571,8 +549,9 @@ def _test_backup_and_restore_app(app): assert app+".main" not in user_permission_list()['permissions'] # Restore the app - backup_restore(system=None, name=archives[0], - apps=[app]) + with message(mocker, "restore_complete"): + backup_restore(system=None, name=archives[0], + apps=[app]) assert app_is_installed(app) @@ -593,13 +572,11 @@ def test_restore_archive_with_no_json(mocker): assert "badbackup" in backup_list()["archives"] - mocker.spy(m18n, "n") - with pytest.raises(YunohostError): + with raiseYunohostError(mocker, 'backup_invalid_archive'): backup_restore(name="badbackup", force=True) - m18n.n.assert_any_call('backup_invalid_archive') -def test_backup_binds_are_readonly(monkeypatch): +def test_backup_binds_are_readonly(mocker, monkeypatch): def custom_mount_and_backup(self, backup_manager): self.manager = backup_manager @@ -620,4 +597,5 @@ def test_backup_binds_are_readonly(monkeypatch): custom_mount_and_backup) # Create the backup - backup_create(system=[]) + with message(mocker, "backup_created"): + backup_create(system=[]) diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index f17313fa1..0f3fb63e0 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -1,19 +1,20 @@ import requests import pytest -from yunohost.app import app_install, app_remove, app_change_url, app_list, app_map +from conftest import message, raiseYunohostError -from yunohost.user import user_list, user_info, user_create, user_delete, user_update, \ - user_group_list, user_group_create, user_group_delete, user_group_update, user_group_info +from yunohost.app import app_install, app_remove, app_change_url, app_list, app_map +from yunohost.user import user_list, user_create, user_delete, \ + user_group_list, user_group_delete from yunohost.permission import user_permission_update, user_permission_list, user_permission_reset, \ permission_create, permission_urls, permission_delete from yunohost.domain import _get_maindomain -from yunohost.utils.error import YunohostError # Get main domain maindomain = _get_maindomain() dummy_password = "test123Ynh" + def clean_user_groups_permission(): for u in user_list()['users']: user_delete(u) @@ -26,6 +27,7 @@ def clean_user_groups_permission(): if any(p.startswith(name) for name in ["wiki", "blog", "site", "permissions_app"]): permission_delete(p, force=True, sync_perm=False) + def setup_function(function): clean_user_groups_permission() @@ -35,6 +37,7 @@ def setup_function(function): permission_create("blog.main", sync_perm=False) user_permission_update("blog.main", remove="all_users", add="alice") + def teardown_function(function): clean_user_groups_permission() try: @@ -42,12 +45,14 @@ def teardown_function(function): except: pass + @pytest.fixture(autouse=True) def check_LDAP_db_integrity_call(): check_LDAP_db_integrity() yield check_LDAP_db_integrity() + def check_LDAP_db_integrity(): # Here we check that all attributes in all object are sychronized. # Here is the list of attributes per object: @@ -162,7 +167,7 @@ def check_permission_for_apps(): def can_access_webpage(webpath, logged_as=None): webpath = webpath.rstrip("/") - sso_url = "https://"+maindomain+"/yunohost/sso/" + sso_url = "https://" + maindomain + "/yunohost/sso/" # Anonymous access if not logged_as: @@ -183,6 +188,7 @@ def can_access_webpage(webpath, logged_as=None): # If we can't access it, we got redirected to the SSO return not r.url.startswith(sso_url) + # # List functions # @@ -204,8 +210,10 @@ def test_permission_list(): # Create - Remove functions # -def test_permission_create_main(): - permission_create("site.main") + +def test_permission_create_main(mocker): + with message(mocker, "permission_created", permission="site.main"): + permission_create("site.main") res = user_permission_list(full=True)['permissions'] assert "site.main" in res @@ -213,8 +221,9 @@ def test_permission_create_main(): assert set(res['site.main']['corresponding_users']) == set(["alice", "bob"]) -def test_permission_create_extra(): - permission_create("site.test") +def test_permission_create_extra(mocker): + with message(mocker, "permission_created", permission="site.test"): + permission_create("site.test") res = user_permission_list(full=True)['permissions'] assert "site.test" in res @@ -222,8 +231,10 @@ def test_permission_create_extra(): assert "all_users" not in res['site.test']['allowed'] assert res['site.test']['corresponding_users'] == [] -def test_permission_delete(): - permission_delete("wiki.main", force=True) + +def test_permission_delete(mocker): + with message(mocker, "permission_deleted", permission="wiki.main"): + permission_delete("wiki.main", force=True) res = user_permission_list()['permissions'] assert "wiki.main" not in res @@ -232,12 +243,14 @@ def test_permission_delete(): # Error on create - remove function # -def test_permission_create_already_existing(): - with pytest.raises(YunohostError): + +def test_permission_create_already_existing(mocker): + with raiseYunohostError(mocker, "permission_already_exist"): permission_create("wiki.main") -def test_permission_delete_doesnt_existing(): - with pytest.raises(YunohostError): + +def test_permission_delete_doesnt_existing(mocker): + with raiseYunohostError(mocker, "permission_not_found"): permission_delete("doesnt.exist", force=True) res = user_permission_list()['permissions'] @@ -246,8 +259,9 @@ def test_permission_delete_doesnt_existing(): assert "mail.main" in res assert "xmpp.main" in res -def test_permission_delete_main_without_force(): - with pytest.raises(YunohostError): + +def test_permission_delete_main_without_force(mocker): + with raiseYunohostError(mocker, "permission_cannot_remove_main"): permission_delete("blog.main") res = user_permission_list()['permissions'] @@ -259,44 +273,55 @@ def test_permission_delete_main_without_force(): # user side functions -def test_permission_add_group(): - user_permission_update("wiki.main", add="alice") + +def test_permission_add_group(mocker): + with message(mocker, "permission_updated", permission="wiki.main"): + user_permission_update("wiki.main", add="alice") res = user_permission_list(full=True)['permissions'] assert set(res['wiki.main']['allowed']) == set(["all_users", "alice"]) assert set(res['wiki.main']['corresponding_users']) == set(["alice", "bob"]) -def test_permission_remove_group(): - user_permission_update("blog.main", remove="alice") + +def test_permission_remove_group(mocker): + with message(mocker, "permission_updated", permission="blog.main"): + user_permission_update("blog.main", remove="alice") res = user_permission_list(full=True)['permissions'] assert res['blog.main']['allowed'] == [] assert res['blog.main']['corresponding_users'] == [] -def test_permission_add_and_remove_group(): - user_permission_update("wiki.main", add="alice", remove="all_users") + +def test_permission_add_and_remove_group(mocker): + with message(mocker, "permission_updated", permission="wiki.main"): + user_permission_update("wiki.main", add="alice", remove="all_users") res = user_permission_list(full=True)['permissions'] assert res['wiki.main']['allowed'] == ["alice"] assert res['wiki.main']['corresponding_users'] == ["alice"] -def test_permission_add_group_already_allowed(): - user_permission_update("blog.main", add="alice") + +def test_permission_add_group_already_allowed(mocker): + with message(mocker, "permission_already_allowed", permission="blog.main", group="alice"): + user_permission_update("blog.main", add="alice") res = user_permission_list(full=True)['permissions'] assert res['blog.main']['allowed'] == ["alice"] assert res['blog.main']['corresponding_users'] == ["alice"] -def test_permission_remove_group_already_not_allowed(): - user_permission_update("blog.main", remove="bob") + +def test_permission_remove_group_already_not_allowed(mocker): + with message(mocker, "permission_already_disallowed", permission="blog.main", group="bob"): + user_permission_update("blog.main", remove="bob") res = user_permission_list(full=True)['permissions'] assert res['blog.main']['allowed'] == ["alice"] assert res['blog.main']['corresponding_users'] == ["alice"] -def test_permission_reset(): - # Reset permission - user_permission_reset("blog.main") + +def test_permission_reset(mocker): + with message(mocker, "permission_updated", permission="blog.main"): + user_permission_reset("blog.main") res = user_permission_list(full=True)['permissions'] assert res['blog.main']['allowed'] == ["all_users"] @@ -306,50 +331,62 @@ def test_permission_reset(): # Error on update function # -def test_permission_add_group_that_doesnt_exist(): - with pytest.raises(YunohostError): + +def test_permission_add_group_that_doesnt_exist(mocker): + with raiseYunohostError(mocker, "group_unknown"): user_permission_update("blog.main", add="doesnt_exist") res = user_permission_list(full=True)['permissions'] assert res['blog.main']['allowed'] == ["alice"] assert res['blog.main']['corresponding_users'] == ["alice"] -def test_permission_update_permission_that_doesnt_exist(): - with pytest.raises(YunohostError): + +def test_permission_update_permission_that_doesnt_exist(mocker): + with raiseYunohostError(mocker, "permission_not_found"): user_permission_update("doesnt.exist", add="alice") # Permission url management -def test_permission_add_url(): - permission_urls("blog.main", add=["/testA"]) + +def test_permission_add_url(mocker): + with message(mocker, "permission_updated", permission="blog.main"): + permission_urls("blog.main", add=["/testA"]) res = user_permission_list(full=True)['permissions'] assert res["blog.main"]["urls"] == ["/testA"] -def test_permission_add_another_url(): - permission_urls("wiki.main", add=["/testA"]) + +def test_permission_add_another_url(mocker): + with message(mocker, "permission_updated", permission="wiki.main"): + permission_urls("wiki.main", add=["/testA"]) res = user_permission_list(full=True)['permissions'] assert set(res["wiki.main"]["urls"]) == set(["/", "/testA"]) -def test_permission_remove_url(): - permission_urls("wiki.main", remove=["/"]) + +def test_permission_remove_url(mocker): + with message(mocker, "permission_updated", permission="wiki.main"): + permission_urls("wiki.main", remove=["/"]) res = user_permission_list(full=True)['permissions'] assert res["wiki.main"]["urls"] == [] -def test_permission_add_url_already_added(): + +def test_permission_add_url_already_added(mocker): res = user_permission_list(full=True)['permissions'] assert res["wiki.main"]["urls"] == ["/"] - permission_urls("wiki.main", add=["/"]) + with message(mocker, "permission_update_nothing_to_do"): + permission_urls("wiki.main", add=["/"]) res = user_permission_list(full=True)['permissions'] assert res["wiki.main"]["urls"] == ["/"] -def test_permission_remove_url_not_added(): - permission_urls("wiki.main", remove=["/doesnt_exist"]) + +def test_permission_remove_url_not_added(mocker): + with message(mocker, "permission_update_nothing_to_do"): + permission_urls("wiki.main", remove=["/doesnt_exist"]) res = user_permission_list(full=True)['permissions'] assert res['wiki.main']['urls'] == ["/"] @@ -358,6 +395,7 @@ def test_permission_remove_url_not_added(): # Application interaction # + def test_permission_app_install(): app_install("./tests/apps/permissions_app_ynh", args="domain=%s&path=%s&is_public=0&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True) @@ -395,6 +433,7 @@ def test_permission_app_remove(): res = user_permission_list(full=True)['permissions'] assert not any(p.startswith("permissions_app.") for p in res.keys()) + def test_permission_app_change_url(): app_install("./tests/apps/permissions_app_ynh", args="domain=%s&path=%s&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True) @@ -441,6 +480,7 @@ def test_permission_app_propagation_on_ssowat(): assert can_access_webpage(app_webroot+"/admin", logged_as="alice") assert not can_access_webpage(app_webroot+"/admin", logged_as="bob") + def test_permission_legacy_app_propagation_on_ssowat(): # TODO / FIXME : To be actually implemented later .... diff --git a/src/yunohost/tests/test_user-group.py b/src/yunohost/tests/test_user-group.py index 30bdeb017..695f09477 100644 --- a/src/yunohost/tests/test_user-group.py +++ b/src/yunohost/tests/test_user-group.py @@ -1,22 +1,25 @@ import pytest +from conftest import message, raiseYunohostError + from yunohost.user import user_list, user_info, user_create, user_delete, user_update, \ - user_group_list, user_group_create, user_group_delete, user_group_update, user_group_info + user_group_list, user_group_create, user_group_delete, user_group_update from yunohost.domain import _get_maindomain -from yunohost.utils.error import YunohostError from yunohost.tests.test_permission import check_LDAP_db_integrity # Get main domain maindomain = _get_maindomain() + def clean_user_groups(): for u in user_list()['users']: user_delete(u) for g in user_group_list()['groups']: - if g != "all_users": + if g not in ["all_users", "visitors"]: user_group_delete(g) + def setup_function(function): clean_user_groups() @@ -29,9 +32,11 @@ def setup_function(function): user_group_update("dev", add=["alice"]) user_group_update("apps", add=["bob"]) + def teardown_function(function): clean_user_groups() + @pytest.fixture(autouse=True) def check_LDAP_db_integrity_call(): check_LDAP_db_integrity() @@ -42,6 +47,7 @@ def check_LDAP_db_integrity_call(): # List functions # + def test_list_users(): res = user_list()['users'] @@ -49,6 +55,7 @@ def test_list_users(): assert "bob" in res assert "jack" in res + def test_list_groups(): res = user_group_list()['groups'] @@ -65,8 +72,11 @@ def test_list_groups(): # Create - Remove functions # -def test_create_user(): - user_create("albert", "Albert", "Good", "alber@" + maindomain, "test123Ynh") + +def test_create_user(mocker): + + with message(mocker, "user_created"): + user_create("albert", "Albert", "Good", "alber@" + maindomain, "test123Ynh") group_res = user_group_list()['groups'] assert "albert" in user_list()['users'] @@ -74,24 +84,33 @@ def test_create_user(): assert "albert" in group_res['albert']['members'] assert "albert" in group_res['all_users']['members'] -def test_del_user(): - user_delete("alice") + +def test_del_user(mocker): + + with message(mocker, "user_deleted"): + user_delete("alice") group_res = user_group_list()['groups'] assert "alice" not in user_list() assert "alice" not in group_res assert "alice" not in group_res['all_users']['members'] -def test_create_group(): - user_group_create("adminsys") + +def test_create_group(mocker): + + with message(mocker, "group_created", group="adminsys"): + user_group_create("adminsys") group_res = user_group_list()['groups'] assert "adminsys" in group_res assert "members" in group_res['adminsys'].keys() assert group_res["adminsys"]["members"] == [] -def test_del_group(): - user_group_delete("dev") + +def test_del_group(mocker): + + with message(mocker, "group_deleted", group="dev"): + user_group_delete("dev") group_res = user_group_list()['groups'] assert "dev" not in group_res @@ -100,75 +119,94 @@ def test_del_group(): # Error on create / remove function # -def test_create_user_with_mail_address_already_taken(): - with pytest.raises(YunohostError): + +def test_create_user_with_mail_address_already_taken(mocker): + with raiseYunohostError(mocker, "user_creation_failed"): user_create("alice2", "Alice", "White", "alice@" + maindomain, "test123Ynh") -def test_create_user_with_password_too_simple(): - with pytest.raises(YunohostError): + +def test_create_user_with_password_too_simple(mocker): + with raiseYunohostError(mocker, "password_listed"): user_create("other", "Alice", "White", "other@" + maindomain, "12") -def test_create_user_already_exists(): - with pytest.raises(YunohostError): + +def test_create_user_already_exists(mocker): + with raiseYunohostError(mocker, "user_already_exists"): user_create("alice", "Alice", "White", "other@" + maindomain, "test123Ynh") -def test_update_user_with_mail_address_already_taken(): - with pytest.raises(YunohostError): - user_update("bob", add_mailalias="alice@" + maindomain) -def test_del_user_that_does_not_exist(): - with pytest.raises(YunohostError): +def test_update_user_with_mail_address_already_taken(mocker): + with raiseYunohostError(mocker, "user_update_failed"): + user_update("bob", add_mailalias="alice@" + maindomain) + + +def test_del_user_that_does_not_exist(mocker): + with raiseYunohostError(mocker, "user_unknown"): user_delete("doesnt_exist") -def test_create_group_all_users(): + +def test_create_group_all_users(mocker): # Check groups already exist with special group "all_users" - with pytest.raises(YunohostError): + with raiseYunohostError(mocker, "group_already_exist"): user_group_create("all_users") -def test_create_group_already_exists(): + +def test_create_group_already_exists(mocker): # Check groups already exist (regular groups) - with pytest.raises(YunohostError): + with raiseYunohostError(mocker, "group_already_exist"): user_group_create("dev") -def test_del_group_all_users(): - with pytest.raises(YunohostError): + +def test_del_group_all_users(mocker): + with raiseYunohostError(mocker, "group_cannot_be_deleted"): user_group_delete("all_users") -def test_del_group_that_does_not_exist(): - with pytest.raises(YunohostError): + +def test_del_group_that_does_not_exist(mocker): + with raiseYunohostError(mocker, "group_unknown"): user_group_delete("doesnt_exist") # # Update function # -def test_update_user(): - user_update("alice", firstname="NewName", lastname="NewLast") + +def test_update_user(mocker): + with message(mocker, "user_updated"): + user_update("alice", firstname="NewName", lastname="NewLast") info = user_info("alice") assert info['firstname'] == "NewName" assert info['lastname'] == "NewLast" -def test_update_group_add_user(): - user_group_update("dev", add=["bob"]) + +def test_update_group_add_user(mocker): + with message(mocker, "group_updated", group="dev"): + user_group_update("dev", add=["bob"]) group_res = user_group_list()['groups'] assert set(group_res['dev']['members']) == set(["alice", "bob"]) -def test_update_group_add_user_already_in(): - user_group_update("apps", add=["bob"]) + +def test_update_group_add_user_already_in(mocker): + with message(mocker, "group_user_already_in_group", user="bob", group="apps"): + user_group_update("apps", add=["bob"]) group_res = user_group_list()['groups'] assert group_res['apps']['members'] == ["bob"] -def test_update_group_remove_user(): - user_group_update("apps", remove=["bob"]) + +def test_update_group_remove_user(mocker): + with message(mocker, "group_updated", group="apps"): + user_group_update("apps", remove=["bob"]) group_res = user_group_list()['groups'] assert group_res['apps']['members'] == [] -def test_update_group_remove_user_not_already_in(): - user_group_update("apps", remove=["jack"]) + +def test_update_group_remove_user_not_already_in(mocker): + with message(mocker, "group_user_not_in_group", user="jack", group="apps"): + user_group_update("apps", remove=["jack"]) group_res = user_group_list()['groups'] assert group_res['apps']['members'] == ["bob"] @@ -177,29 +215,33 @@ def test_update_group_remove_user_not_already_in(): # Error on update functions # -def test_update_user_that_doesnt_exist(): - with pytest.raises(YunohostError): + +def test_update_user_that_doesnt_exist(mocker): + with raiseYunohostError(mocker, "user_unknown"): user_update("doesnt_exist", firstname="NewName", lastname="NewLast") -def test_update_group_that_doesnt_exist(): - # Check groups not found - with pytest.raises(YunohostError): + +def test_update_group_that_doesnt_exist(mocker): + with raiseYunohostError(mocker, "group_unknown"): user_group_update("doesnt_exist", add=["alice"]) -def test_update_group_all_users_manually(): - with pytest.raises(YunohostError): + +def test_update_group_all_users_manually(mocker): + with raiseYunohostError(mocker, "group_cannot_edit_all_users"): user_group_update("all_users", remove=["alice"]) assert "alice" in user_group_list()["groups"]["all_users"]["members"] -def test_update_group_primary_manually(): - with pytest.raises(YunohostError): + +def test_update_group_primary_manually(mocker): + with raiseYunohostError(mocker, "group_cannot_edit_primary_group"): user_group_update("alice", remove=["alice"]) + assert "alice" in user_group_list()["groups"]["alice"]["members"] -def test_update_group_add_user_that_doesnt_exist(): - # Check add bad user in group - with pytest.raises(YunohostError): + +def test_update_group_add_user_that_doesnt_exist(mocker): + with raiseYunohostError(mocker, "user_unknown"): user_group_update("dev", add=["doesnt_exist"]) assert "doesnt_exist" not in user_group_list()["groups"]["dev"]["members"] diff --git a/src/yunohost/utils/error.py b/src/yunohost/utils/error.py index aeffabcf0..75cf35093 100644 --- a/src/yunohost/utils/error.py +++ b/src/yunohost/utils/error.py @@ -27,12 +27,14 @@ class YunohostError(MoulinetteError): """ Yunohost base exception - + The (only?) main difference with MoulinetteError being that keys are translated via m18n.n (namespace) instead of m18n.g (global?) """ def __init__(self, key, raw_msg=False, *args, **kwargs): + self.key = key # Saving the key is useful for unit testing + self.kwargs = kwargs # Saving the key is useful for unit testing if raw_msg: msg = key else: From 50fbfb0372f4478cf373ea67b883cbaa1ba9c6c7 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 24 Sep 2019 23:18:05 +0200 Subject: [PATCH 0182/3170] Fucking ugly workaround for the goddamn dependency nighmare from sury djeezus kraiste --- data/helpers.d/apt | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index b4bf60c1f..f5590b38d 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -218,6 +218,27 @@ ynh_install_app_dependencies () { fi local dep_app=${app//_/-} # Replace all '_' by '-' + # + # Epic ugly hack to fix the goddamn dependency nightmare of sury + # Sponsored by the "Djeezusse Fokin Kraiste Why Do Adminsys Has To Be So Fucking Complicated I Should Go Grow Potatoes Instead Of This Shit" collective + # https://github.com/YunoHost/issues/issues/1407 + # + # If we require to install php dependency + if echo $dependencies | grep -q 'php'; + then + # And we have packages from sury installed (7.0.33-10+weirdshiftafter instead of 7.0.33+1 on debian) + if dpkg --list | grep php | grep -q "7.0.33-10" + then + # And sury ain't already installed + if ! grep -nrq "sury" /etc/apt/sources.list* + then + # Re-add sury + echo "deb https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/sury.list + wget -O /etc/apt/trusted.gpg.d/sury.gpg https://packages.sury.org/php/apt.gpg + fi + fi + fi + cat > /tmp/${dep_app}-ynh-deps.control << EOF # Make a control file for equivs-build Section: misc Priority: optional From ea9a93cec097a14f043b9ecc295ea23b7d14629a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 25 Sep 2019 00:07:50 +0200 Subject: [PATCH 0183/3170] Update data/actionsmap/yunohost.yml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Allan Nordhøy --- data/actionsmap/yunohost.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 97489a841..22037f05f 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -299,7 +299,7 @@ user: ### user_permission_update() update: - action_help: Grant / remove permissions to groups or users + action_help: Manage group or user permissions api: POST /users/permissions/ arguments: permission: From 35bfe97d50b1a6bb44f16ccddaf376723e304c57 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 25 Sep 2019 22:08:47 +0200 Subject: [PATCH 0184/3170] Copy pasta typo : all_users -> visitors Co-Authored-By: Josue-T --- src/yunohost/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 581354f77..312b131b9 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -678,7 +678,7 @@ def user_group_update(operation_logger, groupname, add=None, remove=None, force= if not force: if groupname == "all_users": raise YunohostError('group_cannot_edit_all_users') - elif groupname == "all_users": + elif groupname == "visitors": raise YunohostError('group_cannot_edit_visitors') elif groupname in existing_users: raise YunohostError('group_cannot_edit_primary_group', group=groupname) From 24cc26d85a35ccd38c5af369e7bd9c739fca441c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 26 Sep 2019 14:23:01 +0200 Subject: [PATCH 0185/3170] Support logfiles not ending with .log in logrotate ... --- data/helpers.d/logrotate | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/data/helpers.d/logrotate b/data/helpers.d/logrotate index 47ce46cf6..82cdee6a5 100644 --- a/data/helpers.d/logrotate +++ b/data/helpers.d/logrotate @@ -40,10 +40,13 @@ ynh_use_logrotate () { fi if [ $# -gt 0 ] && [ "$(echo ${1:0:1})" != "-" ]; then - if [ "$(echo ${1##*.})" == "log" ]; then # Keep only the extension to check if it's a logfile - local logfile=$1 # In this case, focus logrotate on the logfile + # If the given logfile parameter already exists as a file, or if it ends up with ".log", + # we just want to manage a single file + if [ -f "$1" ] || [ "$(echo ${1##*.})" == "log" ]; then + local logfile=$1 + # Otherwise we assume we want to manage a directory and all its .log file inside else - local logfile=$1/*.log # Else, uses the directory and all logfile into it. + local logfile=$1/*.log fi fi # LEGACY CODE @@ -54,7 +57,7 @@ ynh_use_logrotate () { fi if [ -n "$logfile" ] then - if [ "$(echo ${logfile##*.})" != "log" ]; then # Keep only the extension to check if it's a logfile + if [ ! -f "$1" ] && [ "$(echo ${logfile##*.})" != "log" ]; then # Keep only the extension to check if it's a logfile local logfile="$logfile/*.log" # Else, uses the directory and all logfile into it. fi else From 274888c79fce9ee52a45a7a82db73fb118505a64 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 26 Sep 2019 16:19:43 +0200 Subject: [PATCH 0186/3170] Better handling of remove failure (and in particular, catch manual interrupts to still perform the rest of the cleaning) --- src/yunohost/app.py | 38 +++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 5a51e57bb..05e1bdf4b 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -944,10 +944,21 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu env=env_dict_remove) operation_logger_remove.start() - remove_retcode = hook_exec( - os.path.join(extracted_app_folder, 'scripts/remove'), - args=[app_instance_name], env=env_dict_remove - )[0] + # Try to remove the app + try: + remove_retcode = hook_exec( + os.path.join(extracted_app_folder, 'scripts/remove'), + args=[app_instance_name], env=env_dict_remove + )[0] + # Here again, calling hook_exec could failed miserably, or get + # manually interrupted (by mistake or because script was stuck) + # In that case we still want to proceed with the rest of the + # removal (permissions, /etc/yunohost/apps/{app} ...) + except (KeyboardInterrupt, EOFError, Exception): + remove_retcode = -1 + import traceback + logger.exception(m18n.n('unexpected_error', error=u"\n" + traceback.format_exc())) + # Remove all permission in LDAP result = ldap.search(base='ou=permission,dc=yunohost,dc=org', filter='(&(objectclass=permissionYnh)(cn=*.%s))' % app_instance_name, attrs=['cn']) @@ -1057,11 +1068,24 @@ def app_remove(operation_logger, app): operation_logger.extra.update({'env': env_dict}) operation_logger.flush() - if hook_exec('/tmp/yunohost_remove/scripts/remove', args=args_list, - env=env_dict)[0] == 0: - logger.success(m18n.n('app_removed', app=app)) + try: + ret = hook_exec('/tmp/yunohost_remove/scripts/remove', + args=args_list, + env=env_dict)[0] + # Here again, calling hook_exec could failed miserably, or get + # manually interrupted (by mistake or because script was stuck) + # In that case we still want to proceed with the rest of the + # removal (permissions, /etc/yunohost/apps/{app} ...) + except (KeyboardInterrupt, EOFError, Exception): + ret = -1 + import traceback + logger.exception(m18n.n('unexpected_error', error=u"\n" + traceback.format_exc())) + if ret == 0: + logger.success(m18n.n('app_removed', app=app)) hook_callback('post_app_remove', args=args_list, env=env_dict) + else: + logger.warning(m18n.n('app_not_properly_removed', app=app)) if os.path.exists(app_setting_path): shutil.rmtree(app_setting_path) From 47bdfd8654548d1ddde537fa1d8cf1594a860265 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 26 Sep 2019 16:21:22 +0200 Subject: [PATCH 0187/3170] Clarify the handling of install script failures... --- locales/en.json | 1 + src/yunohost/app.py | 37 +++++++++++++++++++++++-------------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/locales/en.json b/locales/en.json index 61fdcfa9b..a182c7559 100644 --- a/locales/en.json +++ b/locales/en.json @@ -22,6 +22,7 @@ "app_id_invalid": "Invalid app ID", "app_incompatible": "The app {app} is incompatible with your YunoHost version", "app_install_files_invalid": "These files cannot be installed", + "app_install_failed": "Could not install {app}", "app_location_already_used": "The app '{app}' is already installed in ({path})", "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_install_failed": "Cannot install the app there because it conflicts with the app '{other_app}' already installed in '{other_path}'", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 05e1bdf4b..af6c5d522 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -908,29 +908,42 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu permission_add(app=app_instance_name, permission="main", sync_perm=False) # Execute the app install script - install_retcode = 1 + install_failed = True try: install_retcode = hook_exec( os.path.join(extracted_app_folder, 'scripts/install'), args=args_list, env=env_dict )[0] + # "Common" app install failure : the script failed and returned exit code != 0 + install_failed = (install_retcode != 0) + if install_failed: + error = m18n.n('unexpected_error', error='shell command return code: %s' % install_retcode) + logger.exception(error) + operation_logger.error(error) + # Script got manually interrupted ... N.B. : KeyboardInterrupt does not inherit from Exception except (KeyboardInterrupt, EOFError): - install_retcode = -1 - except Exception: + error = m18n.n('operation_interrupted') + logger.exception(error) + operation_logger.error(error) + # Something wrong happened in Yunohost's code (most probably hook_exec) + except Exception as e : import traceback - logger.exception(m18n.n('unexpected_error', error=u"\n" + traceback.format_exc())) + error = m18n.n('unexpected_error', error=u"\n" + traceback.format_exc()) + logger.exception(error) + 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 - error_msg = operation_logger.error(str(e)) + logger.exception(str(e)) + operation_logger.error(str(e)) - if install_retcode != 0: - error_msg = operation_logger.error(m18n.n('unexpected_error', error='shell command return code: %s' % install_retcode)) - - if install_retcode != 0 or broke_the_system: + # If the install failed or broke the system, we remove it + if install_failed or broke_the_system: if not no_remove_on_failure: # Setup environment for remove script env_dict_remove = {} @@ -985,11 +998,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu app_ssowatconf() - if install_retcode == -1: - msg = m18n.n('operation_interrupted') + " " + error_msg - raise YunohostError(msg, raw_msg=True) - msg = error_msg - raise YunohostError(msg, raw_msg=True) + raise YunohostError("app_install_failed", app=app_id) # Clean hooks and add new ones hook_remove(app_instance_name) From 9331f44b343ae192bd86ec5ccf87f855bee7ba16 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 26 Sep 2019 16:33:15 +0200 Subject: [PATCH 0188/3170] This message about shell command return code is too technical and uninformative. Let's explain what happen, which is that some error occured inside the install script (and details are in the debug log). --- locales/en.json | 1 + src/yunohost/app.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index a182c7559..ba0d7e3cd 100644 --- a/locales/en.json +++ b/locales/en.json @@ -23,6 +23,7 @@ "app_incompatible": "The app {app} is incompatible with your YunoHost version", "app_install_files_invalid": "These files cannot be installed", "app_install_failed": "Could not install {app}", + "app_install_script_failed": "An error occured inside the app installation script.", "app_location_already_used": "The app '{app}' is already installed in ({path})", "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_install_failed": "Cannot install the app there because it conflicts with the app '{other_app}' already installed in '{other_path}'", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index af6c5d522..57df36713 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -917,7 +917,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu # "Common" app install failure : the script failed and returned exit code != 0 install_failed = (install_retcode != 0) if install_failed: - error = m18n.n('unexpected_error', error='shell command return code: %s' % install_retcode) + error = m18n.n('app_install_script_failed') logger.exception(error) operation_logger.error(error) # Script got manually interrupted ... N.B. : KeyboardInterrupt does not inherit from Exception From ccc49a2b2824d8aedfb642e4e9e273fa85431eac Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 26 Sep 2019 16:34:14 +0200 Subject: [PATCH 0189/3170] Simplify that indentation madness --- src/yunohost/app.py | 90 +++++++++++++++++++++++---------------------- 1 file changed, 47 insertions(+), 43 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 57df36713..f2eea5646 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -944,53 +944,57 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu # If the install failed or broke the system, we remove it if install_failed or broke_the_system: - if not no_remove_on_failure: - # Setup environment for remove script - env_dict_remove = {} - 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) - # Execute remove script - operation_logger_remove = OperationLogger('remove_on_failed_install', - [('app', app_instance_name)], - env=env_dict_remove) - operation_logger_remove.start() + # This option is meant for packagers to debug their apps more easily + if no_remove_on_failure: + raise YunohostError("The installation of %s failed, but was not cleaned up as requested by --no-remove-on-failure." % app_id, raw_msg=True) - # Try to remove the app + # Setup environment for remove script + env_dict_remove = {} + 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) + + # Execute remove script + operation_logger_remove = OperationLogger('remove_on_failed_install', + [('app', app_instance_name)], + env=env_dict_remove) + operation_logger_remove.start() + + # Try to remove the app + try: + remove_retcode = hook_exec( + os.path.join(extracted_app_folder, 'scripts/remove'), + args=[app_instance_name], env=env_dict_remove + )[0] + # Here again, calling hook_exec could failed miserably, or get + # manually interrupted (by mistake or because script was stuck) + # In that case we still want to proceed with the rest of the + # removal (permissions, /etc/yunohost/apps/{app} ...) + except (KeyboardInterrupt, EOFError, Exception): + remove_retcode = -1 + import traceback + logger.exception(m18n.n('unexpected_error', error=u"\n" + traceback.format_exc())) + + # Remove all permission in LDAP + result = ldap.search(base='ou=permission,dc=yunohost,dc=org', + filter='(&(objectclass=permissionYnh)(cn=*.%s))' % app_instance_name, attrs=['cn']) + permission_list = [p['cn'][0] for p in result] + for l in permission_list: + permission_remove(app_instance_name, l.split('.')[0], force=True) + + if remove_retcode != 0: + msg = m18n.n('app_not_properly_removed', + app=app_instance_name) + logger.warning(msg) + operation_logger_remove.error(msg) + else: try: - remove_retcode = hook_exec( - os.path.join(extracted_app_folder, 'scripts/remove'), - args=[app_instance_name], env=env_dict_remove - )[0] - # Here again, calling hook_exec could failed miserably, or get - # manually interrupted (by mistake or because script was stuck) - # In that case we still want to proceed with the rest of the - # removal (permissions, /etc/yunohost/apps/{app} ...) - except (KeyboardInterrupt, EOFError, Exception): - remove_retcode = -1 - import traceback - logger.exception(m18n.n('unexpected_error', error=u"\n" + traceback.format_exc())) - - # Remove all permission in LDAP - result = ldap.search(base='ou=permission,dc=yunohost,dc=org', - filter='(&(objectclass=permissionYnh)(cn=*.%s))' % app_instance_name, attrs=['cn']) - permission_list = [p['cn'][0] for p in result] - for l in permission_list: - permission_remove(app_instance_name, l.split('.')[0], force=True) - - if remove_retcode != 0: - msg = m18n.n('app_not_properly_removed', - app=app_instance_name) - logger.warning(msg) - operation_logger_remove.error(msg) + _assert_system_is_sane_for_app(manifest, "post") + except Exception as e: + operation_logger_remove.error(e) else: - try: - _assert_system_is_sane_for_app(manifest, "post") - except Exception as e: - operation_logger_remove.error(e) - else: - operation_logger_remove.success() + operation_logger_remove.success() # Clean tmp folders shutil.rmtree(app_setting_path) From c59c3fa43889fafc5732f89422c5efcc8be672af Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Thu, 26 Sep 2019 19:25:06 +0200 Subject: [PATCH 0190/3170] [enh] Add some advice about a strange locale problem with postgresql --- data/helpers.d/postgresql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/postgresql b/data/helpers.d/postgresql index d252ae2dc..a4cb50393 100644 --- a/data/helpers.d/postgresql +++ b/data/helpers.d/postgresql @@ -276,7 +276,7 @@ ynh_psql_test_if_first_run() { local pg_hba=/etc/postgresql/9.6/main/pg_hba.conf local logfile=/var/log/postgresql/postgresql-9.6-main.log else - ynh_die "postgresql shoud be 9.4 or 9.6" + ynh_die "postgresql shoud be 9.4 or 9.6 or it could be a problem of locale see https://serverfault.com/questions/426989/postgresql-etc-postgresql-doesnt-exist" fi ynh_systemd_action --service_name=postgresql --action=start From 4500f56c32f93ca30698b905d384aca04e580a78 Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Fri, 27 Sep 2019 11:54:44 +0200 Subject: [PATCH 0191/3170] [fix] Psql user should own the database --- data/helpers.d/postgresql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/postgresql b/data/helpers.d/postgresql index d252ae2dc..e40553f9e 100644 --- a/data/helpers.d/postgresql +++ b/data/helpers.d/postgresql @@ -87,7 +87,7 @@ ynh_psql_create_db() { # grant all privilegies to user if [ -n "$user" ]; then - sql+="GRANT ALL PRIVILEGES ON DATABASE ${db} TO ${user} WITH GRANT OPTION;" + sql+="OWNER ${user} GRANT ALL PRIVILEGES ON DATABASE ${db} TO ${user} WITH GRANT OPTION;" fi ynh_psql_execute_as_root --sql="$sql" From b41d7b47a48dc3e3fb44284a43831da86e8efec6 Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Fri, 27 Sep 2019 11:59:11 +0200 Subject: [PATCH 0192/3170] [fix] SQL error --- data/helpers.d/postgresql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/postgresql b/data/helpers.d/postgresql index e40553f9e..1dac6715d 100644 --- a/data/helpers.d/postgresql +++ b/data/helpers.d/postgresql @@ -87,7 +87,7 @@ ynh_psql_create_db() { # grant all privilegies to user if [ -n "$user" ]; then - sql+="OWNER ${user} GRANT ALL PRIVILEGES ON DATABASE ${db} TO ${user} WITH GRANT OPTION;" + sql+="ALTER DATABASE ${db} OWNER TO ${user}; GRANT ALL PRIVILEGES ON DATABASE ${db} TO ${user} WITH GRANT OPTION;" fi ynh_psql_execute_as_root --sql="$sql" From c0e3d600b264eab6b5f817a9c83154edda892cd1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 27 Sep 2019 17:17:03 +0200 Subject: [PATCH 0193/3170] If we got fed an app url, extract the name of the app to test if we do know it --- src/yunohost/app.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 5a51e57bb..061113b4b 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -797,9 +797,19 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu raw_app_list = app_list(raw=True) if app in raw_app_list or ('@' in app) or ('http://' in app) or ('https://' in app): + + # If we got an app name directly (e.g. just "wordpress"), we gonna test this name if app in raw_app_list: - state = raw_app_list[app].get("state", "notworking") - level = raw_app_list[app].get("level", None) + app_name_to_test = app + # If we got an url like "https://github.com/foo/bar_ynh, we want to + # extract "bar" and test if we know this app + elif ('http://' in app) or ('https://' in app): + app_name_to_test = app.strip("/").split("/")[-1].replace("_ynh","") + + if app_name_to_test in raw_app_list: + + state = raw_app_list[app_name_to_test].get("state", "notworking") + level = raw_app_list[app_name_to_test].get("level", None) confirm = "danger" if state in ["working", "validated"]: if isinstance(level, int) and level >= 3: From a2ecbb9d8bc4e1bd8c08fc6ac4a8bad27e8efacc Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 27 Sep 2019 17:38:00 +0200 Subject: [PATCH 0194/3170] Make the warning spooky for notworking and thirdparty apps ... --- locales/en.json | 4 ++-- src/yunohost/app.py | 19 +++++++++++++++---- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/locales/en.json b/locales/en.json index 61fdcfa9b..a4ee33abf 100644 --- a/locales/en.json +++ b/locales/en.json @@ -144,8 +144,8 @@ "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})", "confirm_app_install_warning": "Warning: This application may work, but is not well-integrated in YunoHost. Some features such as single sign-on and backup/restore might not be available. Install anyway? [{answers:s}] ", - "confirm_app_install_danger": "WARNING! This application is still experimental (if not explicitly not working) and it is likely to break your system! You should probably NOT install it unless you know what you are doing. Are you willing to take that risk? [{answers:s}] ", - "confirm_app_install_thirdparty": "WARNING! Installing third-party applications may compromise the integrity and security of your system. You should probably NOT install it unless you know what you are doing. Are you willing to take that risk? [{answers:s}] ", + "confirm_app_install_danger": "DANGER! This application is known to be still experimental (if not explicitly not working)! You should probably NOT install it unless you know what you are doing. NO SUPPORT will be provided if this app doesn't work or break your system... If you are willing to take that risk anyway, type '{answers:s}'", + "confirm_app_install_thirdparty": "DANGER! This application is not part of Yunohost's application catalog. Installing third-party applications may compromise the integrity and security of your system. You should probably NOT install it unless you know what you are doing. NO SUPPORT will be provided if this app doesn't work or break your system... If you are willing to take that risk anyway, type '{answers:s}'", "custom_app_url_required": "You must provide a URL to upgrade your custom app {app:s}", "custom_appslist_name_required": "You must provide a name for your custom app list", "diagnosis_debian_version_error": "Could not retrieve the Debian version: {error}", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 061113b4b..c85a017e4 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -789,10 +789,21 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu if confirm is None or force or msettings.get('interface') == 'api': return - answer = msignals.prompt(m18n.n('confirm_app_install_' + confirm, - answers='Y/N')) - if answer.upper() != "Y": - raise YunohostError("aborting") + if confirm in ["danger", "thirdparty"]: + answer = msignals.prompt(m18n.n('confirm_app_install_' + confirm, + answers='Yes, I understand'), + color="red") + if answer != "Yes, I understand": + raise YunohostError("aborting") + + else: + answer = msignals.prompt(m18n.n('confirm_app_install_' + confirm, + answers='Y/N'), + color="yellow") + if answer.upper() != "Y": + raise YunohostError("aborting") + + raw_app_list = app_list(raw=True) From babaf541b6df58143762b40177f7766f2e981776 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 27 Sep 2019 17:42:56 +0200 Subject: [PATCH 0195/3170] Decent quality is now at least level 5 --- 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 c85a017e4..1246b889e 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -823,7 +823,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu level = raw_app_list[app_name_to_test].get("level", None) confirm = "danger" if state in ["working", "validated"]: - if isinstance(level, int) and level >= 3: + if isinstance(level, int) and level >= 5: confirm = None elif isinstance(level, int) and level > 0: confirm = "warning" From 6aebec4a3450920956892b26152b20ee0cbc611a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 27 Sep 2019 20:37:13 +0200 Subject: [PATCH 0196/3170] Residual .migrate() -> .run() --- src/yunohost/dyndns.py | 2 +- src/yunohost/regenconf.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index a35d39736..70817b3fe 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -212,7 +212,7 @@ def dyndns_update(operation_logger, dyn_host="dyndns.yunohost.org", domain=None, from yunohost.tools import _get_migration_by_name migration = _get_migration_by_name("migrate_to_tsig_sha256") try: - migration.migrate(dyn_host, domain, key) + migration.run(dyn_host, domain, key) except Exception as e: logger.error(m18n.n('migrations_migration_has_failed', exception=e, diff --git a/src/yunohost/regenconf.py b/src/yunohost/regenconf.py index 48129634a..b7a42dd9d 100644 --- a/src/yunohost/regenconf.py +++ b/src/yunohost/regenconf.py @@ -70,7 +70,7 @@ def regen_conf(operation_logger, names=[], with_diff=False, force=False, dry_run or not os.path.exists(REGEN_CONF_FILE)): from yunohost.tools import _get_migration_by_name migration = _get_migration_by_name("decouple_regenconf_from_services") - migration.migrate() + migration.run() result = {} From 5ff9ff01cf04dd8886f00bbd77bfb532ab87e16f Mon Sep 17 00:00:00 2001 From: nr 458 h Date: Sun, 22 Sep 2019 12:33:25 +0000 Subject: [PATCH 0197/3170] Translated using Weblate (German) Currently translated at 29.4% (165 of 562 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/locales/de.json b/locales/de.json index 10087e12f..62dd97aaf 100644 --- a/locales/de.json +++ b/locales/de.json @@ -4,7 +4,7 @@ "admin_password_change_failed": "Passwort kann nicht geändert werden", "admin_password_changed": "Das Administrator-Kennwort wurde geändert", "app_already_installed": "{app:s} ist schon installiert", - "app_argument_choice_invalid": "Verwende einen der folgenden Werte {choices:s}", + "app_argument_choice_invalid": "Wähle einen der folgenden Werte '{choices:s}' für das Argument '{name:s}'", "app_argument_invalid": "Wähle einen gültigen Wert für das Argument '{name: s}': {error: s}", "app_argument_required": "Argument '{name:s}' wird benötigt", "app_extraction_failed": "Installationsdateien konnten nicht entpackt werden", @@ -20,9 +20,9 @@ "app_sources_fetch_failed": "Quelldateien konnten nicht abgerufen werden, ist die URL korrekt?", "app_unknown": "Unbekannte App", "app_upgrade_failed": "{app:s} konnte nicht aktualisiert werden", - "app_upgraded": "{app:s} wurde erfolgreich aktualisiert", - "appslist_fetched": "Appliste {appslist:s} wurde erfolgreich heruntergelanden", - "appslist_removed": "Appliste {appslist:s} wurde erfolgreich entfernt", + "app_upgraded": "{app:s} aktualisiert", + "appslist_fetched": "Appliste {appslist:s} wurde erfolgreich gelanden", + "appslist_removed": "Appliste {appslist:s} wurde entfernt", "appslist_retrieve_error": "Entfernte Appliste {appslist:s} kann nicht empfangen werden: {error:s}", "appslist_unknown": "Appliste {appslist:s} ist unbekannt.", "ask_current_admin_password": "Derzeitiges Administrator-Kennwort", @@ -34,20 +34,20 @@ "ask_new_admin_password": "Neues Verwaltungskennwort", "ask_password": "Passwort", "backup_action_required": "Du musst etwas zum Speichern auswählen", - "backup_app_failed": "Konnte keine Sicherung für '{app:s}' erstellen", + "backup_app_failed": "Konnte keine Sicherung für die App '{app:s}' erstellen", "backup_archive_app_not_found": "App '{app:s}' konnte in keiner Datensicherung gefunden werden", "backup_archive_hook_not_exec": "Hook '{hook:s}' konnte für diese Datensicherung nicht ausgeführt werden", - "backup_archive_name_exists": "Datensicherung mit dem selben Namen existiert bereits", + "backup_archive_name_exists": "Datensicherung mit dem selben Namen existiert bereits.", "backup_archive_name_unknown": "Unbekanntes lokale Datensicherung mit Namen '{name:s}' gefunden", "backup_archive_open_failed": "Kann Sicherungsarchiv nicht öfnen", "backup_cleaning_failed": "Temporäres Sicherungsverzeichnis konnte nicht geleert werden", "backup_created": "Datensicherung komplett", "backup_creating_archive": "Datensicherung wird erstellt…", "backup_delete_error": "Pfad '{path:s}' konnte nicht gelöscht werden", - "backup_deleted": "Datensicherung wurde entfernt", + "backup_deleted": "Backup wurde entfernt", "backup_extracting_archive": "Entpacke Sicherungsarchiv...", "backup_hook_unknown": "Datensicherungshook '{hook:s}' unbekannt", - "backup_invalid_archive": "Ungültige Datensicherung", + "backup_invalid_archive": "Dies ist kein Backup-Archiv", "backup_nothings_done": "Es gibt keine Änderungen zur Speicherung", "backup_output_directory_forbidden": "Verbotenes Ausgabeverzeichnis. Datensicherung können nicht in /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var oder in Unterordnern von /home/yunohost.backup/archives erstellt werden", "backup_output_directory_not_empty": "Ausgabeordner ist nicht leer", @@ -213,7 +213,7 @@ "app_not_properly_removed": "{app:s} wurde nicht ordnungsgemäß entfernt", "service_regenconf_failed": "Konnte die Konfiguration für folgende Dienste nicht neu erzeugen: {services}", "not_enough_disk_space": "Zu wenig freier Speicherplatz unter '{path:s}' verfügbar", - "backup_creation_failed": "Erstellen des Backups fehlgeschlagen", + "backup_creation_failed": "Konnte Backup-Archiv nicht erstellen", "service_conf_up_to_date": "Die Konfiguration für den Dienst '{service}' ist bereits aktuell", "package_not_installed": "Das Paket '{pkgname}' ist nicht installiert", "pattern_positive_number": "Muss eine positive Zahl sein", @@ -277,28 +277,28 @@ "service_regenconf_pending_applying": "Überprüfe ausstehende Konfigurationen, die für den Server '{service}' notwendig sind...", "certmanager_http_check_timeout": "Eine Zeitüberschreitung ist aufgetreten als der Server versuchte sich selbst über HTTP mit der öffentlichen IP (Domain {domain:s} mit der IP {ip:s}) zu erreichen. Möglicherweise ist dafür hairpinning oder eine falsch konfigurierte Firewall/Router deines Servers dafür verantwortlich.", "certmanager_couldnt_fetch_intermediate_cert": "Eine Zeitüberschreitung ist aufgetreten als der Server versuchte die Teilzertifikate von Let's Encrypt zusammenzusetzen. Die Installation/Erneuerung des Zertifikats wurde abgebrochen - bitte versuche es später erneut.", - "appslist_retrieve_bad_format": "Die empfangene Datei der Appliste {appslist:s} ist ungültig", + "appslist_retrieve_bad_format": "Die geladene Appliste {appslist:s} ist ungültig", "domain_hostname_failed": "Erstellen des neuen Hostnamens fehlgeschlagen", "appslist_name_already_tracked": "Es gibt bereits eine registrierte App-Liste mit Namen {name:s}.", - "appslist_url_already_tracked": "Es gibt bereits eine registrierte Anwendungsliste mit dem URL {url:s}.", + "appslist_url_already_tracked": "Es gibt bereits eine registrierte Anwendungsliste mit der URL {url:s}.", "appslist_migrating": "Migriere Anwendungsliste {appslist:s} …", "appslist_could_not_migrate": "Konnte Anwendungsliste {appslist:s} nicht migrieren. Konnte die URL nicht verarbeiten... Der alte Cron-Job wurde unter {bkp_file:s} beibehalten.", - "appslist_corrupted_json": "Konnte die Anwendungslisten. Es scheint, dass {filename:s} beschädigt ist.", + "appslist_corrupted_json": "Anwendungslisten konnte nicht geladen werden. Es scheint, dass {filename:s} beschädigt ist.", "yunohost_ca_creation_success": "Die lokale Zertifizierungs-Authorität wurde angelegt.", "app_already_installed_cant_change_url": "Diese Application ist bereits installiert. Die URL kann durch diese Funktion nicht modifiziert werden. Überprüfe ob `app changeurl` verfügbar ist.", "app_change_no_change_url_script": "Die Application {app_name:s} unterstützt das anpassen der URL noch nicht. Sie muss gegebenenfalls erweitert werden.", "app_change_url_failed_nginx_reload": "NGINX konnte nicht neu gestartet werden. Hier ist der Output von 'nginx -t':\n{nginx_errors:s}", "app_change_url_identical_domains": "Die alte und neue domain/url_path sind identisch: ('{domain:s} {path:s}'). Es gibt nichts zu tun.", - "app_already_up_to_date": "{app:s} ist schon aktuell", + "app_already_up_to_date": "{app:s} ist bereits aktuell", "backup_abstract_method": "Diese Backup-Methode wird noch nicht unterstützt", - "backup_applying_method_tar": "Erstellen des Backup-tar Archives...", + "backup_applying_method_tar": "Erstellen des Backup-tar Archives…", "backup_applying_method_copy": "Kopiere alle Dateien ins Backup…", "app_change_url_no_script": "Die Anwendung '{app_name:s}' unterstützt bisher keine URL-Modufikation. Vielleicht gibt es eine Aktualisierung.", "app_location_unavailable": "Diese URL ist entweder nicht verfügbar oder steht in Konflikt mit den bereits installierten Apps:\n{apps: s}", "backup_applying_method_custom": "Rufe die benutzerdefinierte Backup-Methode '{method:s}' auf…", "backup_archive_system_part_not_available": "Der System-Teil '{part:s}' ist in diesem Backup nicht enthalten", "backup_archive_mount_failed": "Das Einbinden des Backup-Archives ist fehlgeschlagen", - "backup_archive_writing_error": "Die Dateien konnten nicht in der komprimierte Archiv-Backup hinzugefügt werden", + "backup_archive_writing_error": "Die Dateien '{source:s} (im Ordner '{dest:s}') konnten nicht in das komprimierte Archiv-Backup '{archive:s}' hinzugefügt werden", "app_change_url_success": "{app:s} URL ist nun {domain:s}{path:s}", "backup_applying_method_borg": "Sende alle Dateien zur Sicherung ins borg-backup repository…", "invalid_url_format": "ungültiges URL Format", @@ -332,15 +332,15 @@ "backup_custom_need_mount_error": "Bei der benutzerdefinierten Sicherungsmethode ist beim Arbeitsschritt \"Braucht ein Einhängen/Verbinden\" (need_mount) ein Fehler aufgetreten", "backup_custom_mount_error": "Bei der benutzerdefinierten Sicherungsmethode ist beim Arbeitsschritt \"Einhängen/Verbinden\" ein Fehler aufgetreten", "backup_custom_backup_error": "Bei der benutzerdefinierten Sicherungsmethode ist beim Arbeitsschritt \"Sicherung\" ein Fehler aufgetreten", - "backup_csv_creation_failed": "Die CSV-Datei, die für zukünftige Wiederherstellungsvorgänge erforderlich ist, kann nicht erstellt werden", + "backup_csv_creation_failed": "Die zur Wiederherstellung erforderliche CSV-Datei kann nicht erstellt werden", "backup_couldnt_bind": "{Src: s} konnte nicht an {dest: s} angebunden werden.", "backup_borg_not_implemented": "Die Borg-Sicherungsmethode ist noch nicht implementiert", "backup_ask_for_copying_if_needed": "Einige Dateien konnten mit der Methode, die es vermeidet vorübergehend Speicherplatz auf dem System zu verschwenden, nicht gesichert werden. Zur Durchführung der Sicherung sollten vorübergehend {size: s} MB verwendet werden. Sind Sie einverstanden?", - "backup_actually_backuping": "Erstelle nun ein Backup-Archiv aus den gesammelten Dateien …", + "backup_actually_backuping": "Erstellt ein Backup-Archiv aus den gesammelten Dateien …", "ask_path": "Pfad", "ask_new_path": "Neuer Pfad", "ask_new_domain": "Neue Domain", - "apps_permission_restoration_failed": "Die Berechtigung '{permission: s}' für die Wiederherstellung der App {app: s} ist fehlgeschlagen", + "apps_permission_restoration_failed": "Erteilen der Berechtigung '{permission: s}' für die Wiederherstellung der App {app: s} erforderlich", "apps_permission_not_found": "Keine Berechtigung für die installierten Apps gefunden", "app_upgrade_some_app_failed": "Einige Anwendungen können nicht aktualisiert werden", "app_upgrade_app_name": "{App} wird jetzt aktualisiert…", @@ -354,5 +354,7 @@ "aborting": "Breche ab.", "app_action_cannot_be_ran_because_required_services_down": "Diese App erfordert einige Dienste, die derzeit nicht verfügbar sind. Bevor Sie fortfahren, sollten Sie versuchen, die folgenden Dienste neu zu starten (und möglicherweise untersuchen, warum sie nicht verfügbar sind): {services}", "already_up_to_date": "Nichts zu tun. Alles ist bereits auf dem neusten Stand.", - "admin_password_too_long": "Bitte ein Passwort kürzer als 127 Zeichen wählen" + "admin_password_too_long": "Bitte ein Passwort kürzer als 127 Zeichen wählen", + "app_action_broke_system": "Diese Aktion hat anscheinend diese wichtigen Services gestört: {services}", + "apps_already_up_to_date": "Alle Apps sind bereits aktuell" } From b769111247f0b06fb99d17e86eb82eddc30c450b Mon Sep 17 00:00:00 2001 From: Aksel Kiesling Date: Sun, 22 Sep 2019 14:36:19 +0000 Subject: [PATCH 0198/3170] Translated using Weblate (German) Currently translated at 29.4% (165 of 562 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/locales/de.json b/locales/de.json index 62dd97aaf..f9e9bbdbd 100644 --- a/locales/de.json +++ b/locales/de.json @@ -294,7 +294,7 @@ "backup_applying_method_tar": "Erstellen des Backup-tar Archives…", "backup_applying_method_copy": "Kopiere alle Dateien ins Backup…", "app_change_url_no_script": "Die Anwendung '{app_name:s}' unterstützt bisher keine URL-Modufikation. Vielleicht gibt es eine Aktualisierung.", - "app_location_unavailable": "Diese URL ist entweder nicht verfügbar oder steht in Konflikt mit den bereits installierten Apps:\n{apps: s}", + "app_location_unavailable": "Diese URL ist nicht verfügbar oder wird von einer installierten Anwendung genutzt:\n{apps:s}", "backup_applying_method_custom": "Rufe die benutzerdefinierte Backup-Methode '{method:s}' auf…", "backup_archive_system_part_not_available": "Der System-Teil '{part:s}' ist in diesem Backup nicht enthalten", "backup_archive_mount_failed": "Das Einbinden des Backup-Archives ist fehlgeschlagen", @@ -356,5 +356,6 @@ "already_up_to_date": "Nichts zu tun. Alles ist bereits auf dem neusten Stand.", "admin_password_too_long": "Bitte ein Passwort kürzer als 127 Zeichen wählen", "app_action_broke_system": "Diese Aktion hat anscheinend diese wichtigen Services gestört: {services}", - "apps_already_up_to_date": "Alle Apps sind bereits aktuell" + "apps_already_up_to_date": "Alle Apps sind bereits aktuell", + "backup_copying_to_organize_the_archive": "Kopieren von {size: s} MB, um das Archiv zu organisieren" } From bea512c7a6bd453b578ef9e7a244dc0e03a23b4b Mon Sep 17 00:00:00 2001 From: amirale qt Date: Wed, 25 Sep 2019 06:55:50 +0000 Subject: [PATCH 0199/3170] Translated using Weblate (French) Currently translated at 71.9% (404 of 562 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 81 +++++++++++++++++++++++++------------------------ 1 file changed, 41 insertions(+), 40 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index c94406a07..5350f3073 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -17,11 +17,11 @@ "app_manifest_invalid": "Manifeste d’application incorrect : {error}", "app_no_upgrade": "Aucune application à mettre à jour", "app_not_correctly_installed": "{app:s} semble être mal installé", - "app_not_installed": "L'application « {app:s} » n’est pas installée. Voici la liste des applications installées: {all_apps}", + "app_not_installed": "Nous n’avons pas trouvé l’application « {app:s} » dans la liste des applications installées: {all_apps}", "app_not_properly_removed": "{app:s} n’a pas été supprimé correctement", "app_package_need_update": "Le paquet de l’application {app} doit être mis à jour pour être en adéquation avec les changements de YunoHost", "app_recent_version_required": "{app:s} nécessite une version plus récente de YunoHost", - "app_removed": "{app:s} a été supprimé", + "app_removed": "{app:s} supprimé", "app_requirements_checking": "Vérification des paquets requis pour {app} …", "app_requirements_failed": "Impossible de satisfaire les pré-requis pour {app} : {error}", "app_requirements_unmeet": "Les pré-requis de {app} ne sont pas satisfaits, le paquet {pkgname} ({version}) doit être {spec}", @@ -29,8 +29,8 @@ "app_unknown": "Application inconnue", "app_unsupported_remote_type": "Ce type de commande à distance utilisé pour cette application n'est pas supporté", "app_upgrade_failed": "Impossible de mettre à jour {app:s}", - "app_upgraded": "{app:s} a été mis à jour", - "appslist_fetched": "La liste d’applications {appslist:s} a été récupérée", + "app_upgraded": "{app:s} mis à jour", + "appslist_fetched": "La liste d’applications {appslist:s} récupérée", "appslist_removed": "La liste d’applications {appslist:s} a été supprimée", "appslist_retrieve_error": "Impossible de récupérer la liste d’applications distante {appslist:s} : {error:s}", "appslist_unknown": "La liste d’applications {appslist:s} est inconnue.", @@ -93,7 +93,7 @@ "dyndns_cron_removed": "La tâche cron pour le domaine DynDNS a été enlevée", "dyndns_ip_update_failed": "Impossible de mettre à jour l’adresse IP sur le domaine DynDNS", "dyndns_ip_updated": "Votre adresse IP a été mise à jour pour le domaine DynDNS", - "dyndns_key_generating": "La clé DNS est en cours de génération, cela peut prendre un certain temps …", + "dyndns_key_generating": "Génération de la clé DNS ... , cela peut prendre un certain temps.", "dyndns_key_not_found": "Clé DNS introuvable pour le domaine", "dyndns_no_domain_registered": "Aucun domaine n’a été enregistré avec DynDNS", "dyndns_registered": "Le domaine DynDNS a été enregistré", @@ -182,7 +182,7 @@ "restore_running_hooks": "Exécution des scripts de restauration …", "service_add_configuration": "Ajout du fichier de configuration {file:s}", "service_add_failed": "Impossible d’ajouter le service '{service:s}'", - "service_added": "Le service '{service:s}' a été ajouté", + "service_added": "Le service '{service:s}' ajouté", "service_already_started": "Le service '{service:s}' est déjà démarré", "service_already_stopped": "Le service '{service:s}' est déjà arrêté", "service_cmd_exec_failed": "Impossible d’exécuter la commande '{command:s}'", @@ -280,14 +280,14 @@ "certmanager_domain_not_resolved_locally": "Le domaine {domain:s} ne peut être résolu depuis votre serveur YunoHost. Cela peut se produire si vous avez récemment modifié votre enregistrement DNS. Si c'est le cas, merci d’attendre quelques heures qu’il se propage. Si le problème persiste, envisager d’ajouter {domain:s} au fichier /etc/hosts. (Si vous savez ce que vous faites, utilisez --no-checks pour désactiver ces vérifications.)", "certmanager_http_check_timeout": "Expiration du délai lorsque le serveur a essayé de se contacter lui-même via HTTP en utilisant l'adresse IP public {ip:s} du domaine {domain:s}. Vous rencontrez peut-être un problème d’hairpinning ou alors le pare-feu/routeur en amont de votre serveur est mal configuré.", "certmanager_couldnt_fetch_intermediate_cert": "Expiration du délai lors de la tentative de récupération du certificat intermédiaire depuis Let’s Encrypt. L’installation ou le renouvellement du certificat a été annulé. Veuillez réessayer plus tard.", - "appslist_retrieve_bad_format": "Le fichier récupéré pour la liste d’applications {appslist:s} n’est pas valide", - "domain_hostname_failed": "Échec de l’utilisation d’un nouveau nom d’hôte. Cela pourrait causer des soucis plus tard (mais ce n’est pas sûr… peut-être que ça n’en causera pas).", + "appslist_retrieve_bad_format": "Impossible de lire la liste des applications extraites {appslist: s}", + "domain_hostname_failed": "Échec de l’utilisation d’un nouveau nom d’hôte. Cela pourrait causer des soucis plus tard (peut-être que ça n’en causera pas).", "yunohost_ca_creation_success": "L’autorité de certification locale a été créée.", - "appslist_name_already_tracked": "Il y a déjà une liste d’applications enregistrée avec le nom {name:s}.", + "appslist_name_already_tracked": "Il y a déjà une liste d’applications enregistrée avec le nom {name:s} existe déjà.", "appslist_url_already_tracked": "Il y a déjà une liste d’applications enregistrée avec l’URL {url:s}.", "appslist_migrating": "Migration de la liste d’applications {appslist:s} …", "appslist_could_not_migrate": "Impossible de migrer la liste {appslist:s} ! Impossible d’exploiter l’URL. L’ancienne tâche programmée a été conservée dans {bkp_file:s}.", - "appslist_corrupted_json": "Impossible de charger la liste d’applications. Il semble que {filename:s} soit corrompu.", + "appslist_corrupted_json": "Impossible de charger la liste d’applications. Il semble que {filename:s} soit endommager.", "app_already_installed_cant_change_url": "Cette application est déjà installée. L’URL ne peut pas être changé simplement par cette fonction. Regardez si cela est disponible avec `app changeurl`.", "app_change_no_change_url_script": "L’application {app_name:s} ne prend pas encore en charge le changement d’URL, vous pourriez avoir besoin de la mettre à jour.", "app_change_url_failed_nginx_reload": "Le redémarrage de Nginx a échoué. Voici la sortie de 'nginx -t' :\n{nginx_errors:s}", @@ -297,13 +297,13 @@ "app_location_unavailable": "Cette URL n’est pas disponible ou est en conflit avec une application existante :\n{apps:s}", "app_already_up_to_date": "{app:s} est déjà à jour", "invalid_url_format": "Format d’URL non valide", - "global_settings_bad_choice_for_enum": "Valeur du paramètre {setting:s} incorrecte. Reçu : {received_type:s}, mais les valeurs possibles sont : {expected_type:s}", + "global_settings_bad_choice_for_enum": "Valeur du paramètre {setting:s} incorrecte. Reçu : {choice:s}, mais les valeurs possibles sont : {available_choices:s}", "global_settings_bad_type_for_setting": "Le type du paramètre {setting:s} est incorrect. Reçu {received_type:s} alors que {expected_type:s} était attendu", "global_settings_cant_open_settings": "Échec de l’ouverture du ficher de configurations car : {reason:s}", "global_settings_cant_serialize_setings": "Échec de sérialisation des données de configurations, cause : {reason:s}", "global_settings_cant_write_settings": "Échec d’écriture du fichier de configurations car : {reason:s}", "global_settings_key_doesnt_exists": "La clef '{settings_key:s}' n’existe pas dans les configurations générales, vous pouvez voir toutes les clefs disponibles en saisissant 'yunohost settings list'", - "global_settings_reset_success": "Super ! Vos configurations précédentes ont été sauvegardées dans {path:s}", + "global_settings_reset_success": "Vos configurations précédentes ont été sauvegardées dans {path:s}", "global_settings_setting_example_bool": "Exemple d’option booléenne", "global_settings_setting_example_int": "Exemple d’option de type entier", "global_settings_setting_example_string": "Exemple d’option de type chaîne", @@ -313,7 +313,7 @@ "service_conf_new_managed_file": "Le fichier de configuration « {conf} » est désormais géré par le service {service}.", "service_conf_file_kept_back": "Le fichier de configuration '{conf}' devait être supprimé par le service {service} mais a été conservé.", "backup_abstract_method": "Cette méthode de sauvegarde n’a pas encore été implémentée", - "backup_applying_method_tar": "Création de l’archive tar de la sauvegarde …", + "backup_applying_method_tar": "Création de l’archive TAR de la sauvegarde …", "backup_applying_method_copy": "Copie de tous les fichiers à sauvegarder …", "backup_applying_method_borg": "Envoi de tous les fichiers à sauvegarder dans le répertoire borg-backup…", "backup_applying_method_custom": "Appel de la méthode de sauvegarde personnalisée '{method:s}' …", @@ -354,17 +354,17 @@ "migrations_current_target": "La cible de migration est {}", "migrations_error_failed_to_load_migration": "ERREUR : échec du chargement de migration {number} {name}", "migrations_forward": "Migration en avant", - "migrations_loading_migration": "Chargement de la migration {number} {name} …", - "migrations_migration_has_failed": "La migration {number} {name} a échoué avec l’exception {exception} : annulation", + "migrations_loading_migration": "Chargement de la migration {id} …", + "migrations_migration_has_failed": "La migration {id} a échoué avec l’exception {exception} : annulation", "migrations_no_migrations_to_run": "Aucune migration à lancer", "migrations_show_currently_running_migration": "Application de la migration {number} {name} …", "migrations_show_last_migration": "La dernière migration appliquée est {}", - "migrations_skip_migration": "Ignorer et passer la migration {number} {name}…", + "migrations_skip_migration": "Ignorer et passer la migration {id}…", "server_shutdown": "Le serveur va éteindre", "server_shutdown_confirm": "Le serveur va être éteint immédiatement, le voulez-vous vraiment ? [{answers:s}]", "server_reboot": "Le serveur va redémarrer", "server_reboot_confirm": "Le serveur va redémarrer immédiatement, le voulez-vous vraiment ? [{answers:s}]", - "app_upgrade_some_app_failed": "Impossible de mettre à jour certaines applications", + "app_upgrade_some_app_failed": "Certaines applications n’ont pas été mises à jour", "ask_path": "Chemin", "dyndns_could_not_check_provide": "Impossible de vérifier si {provider:s} peut fournir {domain:s}.", "dyndns_domain_not_provided": "Le fournisseur DynDNS {provider:s} ne peut pas fournir le domaine {domain:s}.", @@ -373,12 +373,12 @@ "backup_output_symlink_dir_broken": "Vous avez un lien symbolique cassé à la place de votre dossier d’archives « {path:s} ». Vous pourriez avoir une configuration personnalisée pour sauvegarder vos données sur un autre système de fichiers, dans ce cas vous avez probablement oublié de monter ou de connecter votre disque dur ou votre clé USB.", "migrate_tsig_end": "La migration à hmac-sha512 est terminée", "migrate_tsig_failed": "La migration du domaine DynDNS {domain} à hmac-sha512 a échoué. Annulation des modifications. Erreur : {error_code} - {error}", - "migrate_tsig_start": "L’algorithme de génération des clefs n’est pas suffisamment sécurisé pour la signature TSIG du domaine '{domain}', lancement de la migration vers hmac-sha512 qui est plus sécurisé", - "migrate_tsig_wait": "Attendre 3 minutes pour que le serveur DynDNS prenne en compte la nouvelle clef …", + "migrate_tsig_start": "L’algorithme de génération des clefs n’est pas suffisamment sécurisé pour la signature TSIG du domaine '{domain}', lancement de la migration vers HMAC-SHA-512 qui est plus sécurisé", + "migrate_tsig_wait": "Attendre trois minutes pour que le serveur DynDNS prenne en compte la nouvelle clef …", "migrate_tsig_wait_2": "2 minutes …", "migrate_tsig_wait_3": "1 minute …", "migrate_tsig_wait_4": "30 secondes …", - "migrate_tsig_not_needed": "Il ne semble pas que vous utilisez un domaine DynDNS, donc aucune migration n’est nécessaire !", + "migrate_tsig_not_needed": "Il ne semble pas que vous utilisez un domaine DynDNS, donc aucune migration n’est nécessaire.", "app_checkurl_is_deprecated": "Packagers /!\\ 'app checkurl' est obsolète ! Utilisez 'app register-url' en remplacement !", "migration_description_0001_change_cert_group_to_sslcert": "Changement des permissions de groupe des certificats de « metronome » à « ssl-cert »", "migration_description_0002_migrate_to_tsig_sha256": "Amélioration de la sécurité de DynDNS TSIG en utilisant SHA512 au lieu de MD5", @@ -397,10 +397,10 @@ "migration_0003_problematic_apps_warning": "Veuillez noter que des applications possiblement problématiques ont été détectées. Il semble qu’elles n’aient pas été installées depuis une liste d’application ou qu’elles ne soit pas marquées comme « fonctionnelles ». En conséquence, nous ne pouvons pas garantir qu’elles fonctionneront après la mise à niveau : {problematic_apps}", "migration_0003_modified_files": "Veuillez noter que les fichiers suivants ont été détectés comme modifiés manuellement et pourraient être écrasés à la fin de la mise à niveau : {manually_modified_files}", "migrations_list_conflict_pending_done": "Vous ne pouvez pas utiliser --previous et --done simultanément.", - "migrations_to_be_ran_manually": "La migration {number} {name} doit être lancée manuellement. Veuillez aller dans Outils > Migrations dans l’interface admin, ou lancer `yunohost tools migrations migrate`.", + "migrations_to_be_ran_manually": "La migration {id} doit être lancée manuellement. Veuillez aller dans Outils > Migrations dans l’interface admin, ou lancer `yunohost tools migrations migrate`.", "migrations_need_to_accept_disclaimer": "Pour lancer la migration {number} {name}, vous devez accepter cette clause de non-responsabilité :\n---\n{disclaimer}\n---\nSi vous acceptez de lancer la migration, veuillez relancer la commande avec l’option --accept-disclaimer.", "service_description_avahi-daemon": "permet d’atteindre votre serveur via yunohost.local sur votre réseau local", - "service_description_dnsmasq": "gère la résolution des noms de domaine (DNS)", + "service_description_dnsmasq": "Gère la résolution des noms de domaine (DNS)", "service_description_dovecot": "permet aux clients de messagerie d’accéder/récupérer les courriels (via IMAP et POP3)", "service_description_fail2ban": "protège contre les attaques brute-force et autres types d’attaques venant d’Internet", "service_description_glances": "surveille les informations système de votre serveur", @@ -410,19 +410,19 @@ "service_description_nslcd": "gère la connexion en ligne de commande des utilisateurs YunoHost", "service_description_php5-fpm": "exécute des applications écrites en PHP avec nginx", "service_description_postfix": "utilisé pour envoyer et recevoir des courriels", - "service_description_redis-server": "une base de données spécialisée utilisée pour l’accès rapide aux données, les files d’attentes et la communication entre les programmes", + "service_description_redis-server": "Une base de données spécialisée utilisée pour l’accès rapide aux données, les files d’attentes et la communication entre les programmes", "service_description_rmilter": "vérifie divers paramètres dans les courriels", "service_description_rspamd": "filtre le pourriel, et d’autres fonctionnalités liées au courriel", "service_description_slapd": "stocke les utilisateurs, domaines et leurs informations liées", "service_description_ssh": "vous permet de vous connecter à distance à votre serveur via un terminal (protocole SSH)", "service_description_yunohost-api": "permet les interactions entre l’interface web de YunoHost et le système", - "service_description_yunohost-firewall": "gère l'ouverture et la fermeture des ports de connexion aux services", + "service_description_yunohost-firewall": "Gère l'ouverture et la fermeture des ports de connexion aux services", "experimental_feature": "Attention : cette fonctionnalité est expérimentale et ne doit pas être considérée comme stable, vous ne devriez pas l’utiliser à moins que vous ne sachiez ce que vous faites.", - "log_corrupted_md_file": "Le fichier yaml de métadonnées associé aux logs est corrompu : '{md_file}'\nErreur : {error}", + "log_corrupted_md_file": "Le fichier YAML de métadonnées associé aux logs est corrompu : '{md_file}'\nErreur : {error}", "log_category_404": "Le journal de la catégorie '{category}' n’existe pas", "log_link_to_log": "Journal historisé complet de cette opération : ' {desc} '", "log_help_to_get_log": "Pour voir le journal historisé de cette opération '{desc}', utilisez la commande 'yunohost log display {name}'", - "log_link_to_failed_log": "L’opération '{desc}' a échouée ! Pour avoir de l’aide, merci de fournir le journal historisé complet de l’opération en cliquant ici", + "log_link_to_failed_log": "L’opération '{desc}' a échouée ! Pour avoir de l’aide, merci cliqué ici pour avoir de l'aide", "backup_php5_to_php7_migration_may_fail": "Impossible de convertir votre archive pour prendre en charge php7, vos applications php pourraient ne pas être restaurées (reason: {error:s})", "log_help_to_get_failed_log": "L’opération '{desc}' a échouée ! Pour avoir de l’aide, merci de partager le journal historisé de cette opération en utilisant la commande 'yunohost log display {name} --share'", "log_does_exists": "Il n’existe pas de journal historisé de l’opération ayant pour nom '{log}', utiliser 'yunohost log list pour voir tous les fichiers de journaux historisés disponibles'", @@ -468,7 +468,7 @@ "migration_0005_postgresql_96_not_installed": "PostgreSQL 9.4 a été trouvé et installé, mais pas PostgreSQL 9.6 !? Quelque chose d’étrange a dû arriver à votre système… :(", "migration_0005_not_enough_space": "Il n’y a pas assez d’espace libre de disponible sur {path} pour lancer maintenant la migration :(.", "recommend_to_add_first_user": "La post-installation est terminée mais YunoHost a besoin d’au moins un utilisateur pour fonctionner correctement. Vous devez en ajouter un en utilisant la commande 'yunohost user create $nomdutilisateur' ou bien via l’interface d’administration web.", - "service_description_php7.0-fpm": "exécute des applications écrites en PHP avec Nginx", + "service_description_php7.0-fpm": "Exécute des applications écrites en PHP avec NGINX", "users_available": "Liste des utilisateurs disponibles :", "good_practices_about_admin_password": "Vous êtes maintenant sur le point de définir un nouveau mot de passe d’administration. Le mot de passe doit comporter au moins 8 caractères – bien qu’il soit recommandé d’utiliser un mot de passe plus long (c’est-à-dire une phrase secrète) et/ou d’utiliser différents types de caractères (majuscules, minuscules, chiffres et caractères spéciaux).", "good_practices_about_user_password": "Vous êtes maintenant sur le point de définir un nouveau mot de passe utilisateur. Le mot de passe doit comporter au moins 8 caractères - bien qu’il soit recommandé d’utiliser un mot de passe plus long (c’est-à-dire une phrase secrète) et/ou d’utiliser différents types de caractères tels que : majuscules, minuscules, chiffres et caractères spéciaux.", @@ -481,8 +481,8 @@ "password_too_simple_3": "Le mot de passe doit comporter au moins 8 caractères et contenir des chiffres, des majuscules, des minuscules et des caractères spéciaux", "password_too_simple_4": "Le mot de passe doit comporter au moins 12 caractères et contenir des chiffres, des majuscules, des minuscules et des caractères spéciaux", "root_password_desynchronized": "Le mot de passe administrateur a été changé, mais YunoHost n’a pas pu le propager au mot de passe root !", - "aborting": "Opération annulée.", - "app_not_upgraded": "Les applications suivantes n'ont pas été mises à jour : {apps}", + "aborting": "Annulation.", + "app_not_upgraded": "L’application {failed_app} n’a pas été mise à jour et par conséquence les applications suivantes n’ont pas été mises à jour : {apps}", "app_start_install": "Installation de l'application {app} …", "app_start_remove": "Suppression de l'application {app} …", "app_start_backup": "Collecte des fichiers devant être sauvegardés pour {app} …", @@ -507,9 +507,9 @@ "migration_0007_cancelled": "YunoHost n'a pas réussi à améliorer la façon dont est gérée votre configuration SSH.", "migration_0007_cannot_restart": "SSH ne peut pas être redémarré après avoir essayé d'annuler la migration numéro 6.", "migration_0008_general_disclaimer": "Pour améliorer la sécurité de votre serveur, il est recommandé de laisser YunoHost gérer la configuration SSH. Votre configuration SSH actuelle diffère de la configuration recommandée. Si vous laissez YunoHost la reconfigurer, la façon dont vous vous connectez à votre serveur via SSH changera comme suit :", - "migration_0008_port": " - vous devrez vous connecter en utilisant le port 22 au lieu de votre actuel port SSH personnalisé. N'hésitez pas à le reconfigurer ;", - "migration_0008_root": " - vous ne pourrez pas vous connecter en tant que root via SSH. Au lieu de cela, vous devrez utiliser l'utilisateur admin ;", - "migration_0008_dsa": " - la clé DSA sera désactivée. Par conséquent, il se peut que vous ayez besoin d'invalider un avertissement effrayant de votre client SSH afin de revérifier l'empreinte de votre serveur ;", + "migration_0008_port": "- Vous devrez vous connecter en utilisant le port 22 au lieu de votre actuel port SSH personnalisé. N'hésitez pas à le reconfigurer ;", + "migration_0008_root": "- Vous ne pourrez pas vous connecter en tant que root via SSH. Au lieu de cela, vous devrez utiliser l'utilisateur admin ;", + "migration_0008_dsa": "- La clé DSA sera désactivée. Par conséquent, il se peut que vous ayez besoin d'invalider un avertissement effrayant de votre client SSH afin de revérifier l'empreinte de votre serveur ;", "migration_0008_warning": "Si vous comprenez ces avertissements et que vous acceptez de laisser YunoHost remplacer votre configuration actuelle, exécutez la migration. Sinon, vous pouvez également passer la migration, bien que cela ne soit pas recommandé.", "migration_0008_no_warning": "Aucun risque majeur n'a été identifié concernant l'écrasement de votre configuration SSH - mais nous ne pouvons pas en être absolument sûrs ;) ! Si vous acceptez de laisser YunoHost remplacer votre configuration actuelle, exécutez la migration. Sinon, vous pouvez également passer la migration, bien que cela ne soit pas recommandé.", "migrations_success": "Migration {number} {name} réussie !", @@ -549,16 +549,16 @@ "regenconf_failed": "Impossible de régénérer la configuration pour la ou les catégorie(s) : '{categories}'", "regenconf_pending_applying": "Applique la configuration en attente pour la catégorie '{category}' …", "service_regen_conf_is_deprecated": "'yunohost service regen-conf' est obsolète ! Veuillez plutôt utiliser 'yunohost tools regen-conf' à la place.", - "tools_upgrade_at_least_one": "Veuillez spécifier --apps OU --system", + "tools_upgrade_at_least_one": "Veuillez spécifier '--apps' OU '--system'", "tools_upgrade_cant_both": "Impossible de mettre à niveau le système et les applications en même temps", - "tools_upgrade_cant_hold_critical_packages": "Impossibilité de maintenir les paquets critiques...", - "tools_upgrade_regular_packages": "Mise à jour des paquets du système (non liés a YunoHost) ...", + "tools_upgrade_cant_hold_critical_packages": "Impossibilité de maintenir les paquets critiques…", + "tools_upgrade_regular_packages": "Mise à jour des paquets du système (non liés a YunoHost) …", "tools_upgrade_regular_packages_failed": "Impossible de mettre à jour les paquets suivants : {packages_list}", - "tools_upgrade_special_packages": "Mise à jour des paquets 'spécifiques' (liés a YunoHost) ...", + "tools_upgrade_special_packages": "Mise à jour des paquets 'spécifiques' (liés a YunoHost) …", "tools_upgrade_special_packages_completed": "La mise à jour des paquets de YunoHost est finie!\nPressez [Entrée] pour revenir à la ligne de commande", "updating_app_lists": "Récupération des mises à jour des applications disponibles…", "dpkg_lock_not_available": "Cette commande ne peut être lancée maintenant car il semblerai qu'un autre programme utilise déjà le verrou dpkg du gestionnaire de paquets du système", - "tools_upgrade_cant_unhold_critical_packages": "Impossible de dé-marquer les paquets critiques ...", + "tools_upgrade_cant_unhold_critical_packages": "Impossible de dé-marquer les paquets critiques …", "tools_upgrade_special_packages_explanation": "Cette opération prendra fin mais la mise à jour spécifique continuera en arrière-plan. Veuillez ne pas lancer d'autre action sur votre serveur dans les 10 prochaines minutes (en fonction de la vitesse de votre matériel). Une fois que c'est fait, vous devrez peut-être vous reconnecter sur le panel d'administration web. Le journal de la mise à jour sera disponible dans Outils > Log (dans le panel d'administration web) ou dans la liste des journaux YunoHost (en ligne de commande).", "update_apt_cache_failed": "Impossible de mettre à jour le cache APT (gestionnaire de paquets Debian). Voici un extrait du fichier sources.list qui pourrait vous aider à identifier les lignes problématiques :\n{sourceslist}", "update_apt_cache_warning": "Des erreurs se sont produites lors de la mise à jour du cache APT (gestionnaire de paquets Debian). Voici un extrait des lignes du fichier sources.list qui pourrait vous aider à identifier les lignes problématiques :\n{sourceslist}", @@ -566,8 +566,8 @@ "apps_permission_restoration_failed": "L'autorisation '{permission:s}' pour la restauration de l'application {app:s} a échoué", "backup_permission": "Autorisation de sauvegarde pour l'application {app:s}", "edit_group_not_allowed": "Vous n'êtes pas autorisé à modifier le groupe {group:s}", - "error_when_removing_sftpuser_group": "Erreur en essayant de supprimer le groupe sftpusers", - "group_created": "Le groupe '{group}' a créé avec succès", + "error_when_removing_sftpuser_group": "Vous ne pouvez pas supprimer le groupe sftpusers", + "group_created": "Le groupe '{group}' a été créé", "group_deleted": "Le groupe '{group}' a été supprimé", "group_deletion_not_allowed": "Le groupe {group:s} ne peut pas être supprimé manuellement.", "group_info_failed": "L'information sur le groupe a échoué", @@ -588,5 +588,6 @@ "log_user_group_update": "Mettre à jour '{}' pour le groupe", "log_user_permission_add": "Mettre à jour l'autorisation pour '{}'", "log_user_permission_remove": "Mettre à jour l'autorisation pour '{}'", - "mailbox_disabled": "La boîte aux lettres est désactivée pour l'utilisateur {user:s}" + "mailbox_disabled": "La boîte aux lettres est désactivée pour l'utilisateur {user:s}", + "app_action_broke_system": "Cette action semble avoir cassé des services important : {services}" } From 42b85356926f1908785615d3c43ce867d244b1c5 Mon Sep 17 00:00:00 2001 From: htsr Date: Wed, 25 Sep 2019 13:50:42 +0000 Subject: [PATCH 0200/3170] Translated using Weblate (French) Currently translated at 71.9% (404 of 562 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 5350f3073..87f806a21 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -524,7 +524,7 @@ "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`.", "app_action_cannot_be_ran_because_required_services_down": "Cette application requiert certains services qui sont actuellement arrêtés. Avant de continuer, vous devriez essayer de redémarrer les services suivants (et éventuellement rechercher pourquoi ils sont arrêtés) : {services}", - "admin_password_too_long": "Choisissez un mot de passe plus court que 127 caractères", + "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 '{}'", "migration_0009_not_needed": "Cette migration semble avoir déjà été jouée ? On l'ignore.", "regenconf_file_backed_up": "Le fichier de configuration '{conf}' a été sauvegardé sous '{backup}'", From 50e9ac0087b94efcb3f9f0fce267c572d3be4b12 Mon Sep 17 00:00:00 2001 From: advocatux Date: Sun, 22 Sep 2019 16:43:19 +0000 Subject: [PATCH 0201/3170] Translated using Weblate (Spanish) Currently translated at 100.0% (562 of 562 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/es/ --- locales/es.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/locales/es.json b/locales/es.json index d219b36e1..ff4a230f9 100644 --- a/locales/es.json +++ b/locales/es.json @@ -606,5 +606,7 @@ "aborting": "Cancelando.", "app_upgrade_stopped": "Se ha detenido la actualización de todas las aplicaciones para prevenir un posible daño porque la aplicación anterior no se pudo actualizar", "app_action_broke_system": "Esta acción parece que ha roto estos importantes servicios: {services}", - "operation_interrupted": "¿Ha sido interrumpida la operación manualmente?" + "operation_interrupted": "¿Ha sido interrumpida la operación manualmente?", + "apps_already_up_to_date": "Todas las aplicaciones están ya actualizadas", + "dyndns_provider_unreachable": "No se puede conectar con el proveedor de Dyndns {provider}: o su YunoHost no está correctamente conectado a Internet o el servidor de dynette está caído." } From ab3c159318a4208f58a8f8a3cd39a4835e4024fc Mon Sep 17 00:00:00 2001 From: htsr Date: Wed, 25 Sep 2019 13:52:15 +0000 Subject: [PATCH 0202/3170] Translated using Weblate (French) Currently translated at 76.2% (428 of 562 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 87f806a21..cdb952387 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -462,7 +462,7 @@ "log_tools_shutdown": "Éteindre votre serveur", "log_tools_reboot": "Redémarrer votre serveur", "mail_unavailable": "Cette adresse de courriel est réservée et doit être automatiquement attribuée au tout premier utilisateur", - "migration_description_0004_php5_to_php7_pools": "Reconfiguration des groupes PHP pour utiliser PHP 7 au lieu de PHP 5", + "migration_description_0004_php5_to_php7_pools": "Reconfigurez les groupes PHP pour utiliser PHP 7 au lieu de PHP 5", "migration_description_0005_postgresql_9p4_to_9p6": "Migration des bases de données de PostgreSQL 9.4 vers PostgreSQL 9.6", "migration_0005_postgresql_94_not_installed": "PostgreSQL n’a pas été installé sur votre système. Rien à faire !", "migration_0005_postgresql_96_not_installed": "PostgreSQL 9.4 a été trouvé et installé, mais pas PostgreSQL 9.6 !? Quelque chose d’étrange a dû arriver à votre système… :(", @@ -564,11 +564,11 @@ "update_apt_cache_warning": "Des erreurs se sont produites lors de la mise à jour du cache APT (gestionnaire de paquets Debian). Voici un extrait des lignes du fichier sources.list qui pourrait vous aider à identifier les lignes problématiques :\n{sourceslist}", "apps_permission_not_found": "Aucune permission trouvée pour les applications installées", "apps_permission_restoration_failed": "L'autorisation '{permission:s}' pour la restauration de l'application {app:s} a échoué", - "backup_permission": "Autorisation de sauvegarde pour l'application {app:s}", + "backup_permission": "Permission de sauvegarde pour l'application {app:s}", "edit_group_not_allowed": "Vous n'êtes pas autorisé à modifier le groupe {group:s}", "error_when_removing_sftpuser_group": "Vous ne pouvez pas supprimer le groupe sftpusers", "group_created": "Le groupe '{group}' a été créé", - "group_deleted": "Le groupe '{group}' a été supprimé", + "group_deleted": "Suppression du groupe '{group}'", "group_deletion_not_allowed": "Le groupe {group:s} ne peut pas être supprimé manuellement.", "group_info_failed": "L'information sur le groupe a échoué", "group_unknown": "Le groupe {group:s} est inconnu", From d5999a122594bf0772c11ffd16d40405c6c8eff7 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Wed, 25 Sep 2019 13:53:22 +0000 Subject: [PATCH 0203/3170] Translated using Weblate (French) Currently translated at 76.2% (428 of 562 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 cdb952387..03c274f5a 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -557,7 +557,7 @@ "tools_upgrade_special_packages": "Mise à jour des paquets 'spécifiques' (liés a YunoHost) …", "tools_upgrade_special_packages_completed": "La mise à jour des paquets de YunoHost est finie!\nPressez [Entrée] pour revenir à la ligne de commande", "updating_app_lists": "Récupération des mises à jour des applications disponibles…", - "dpkg_lock_not_available": "Cette commande ne peut être lancée maintenant car il semblerai qu'un autre programme utilise déjà le verrou dpkg du gestionnaire de paquets du système", + "dpkg_lock_not_available": "Cette commande ne peut être exécutée actuellement car un autre programme semble utiliser le verrou de dpkg (gestionnaire de paquets).", "tools_upgrade_cant_unhold_critical_packages": "Impossible de dé-marquer les paquets critiques …", "tools_upgrade_special_packages_explanation": "Cette opération prendra fin mais la mise à jour spécifique continuera en arrière-plan. Veuillez ne pas lancer d'autre action sur votre serveur dans les 10 prochaines minutes (en fonction de la vitesse de votre matériel). Une fois que c'est fait, vous devrez peut-être vous reconnecter sur le panel d'administration web. Le journal de la mise à jour sera disponible dans Outils > Log (dans le panel d'administration web) ou dans la liste des journaux YunoHost (en ligne de commande).", "update_apt_cache_failed": "Impossible de mettre à jour le cache APT (gestionnaire de paquets Debian). Voici un extrait du fichier sources.list qui pourrait vous aider à identifier les lignes problématiques :\n{sourceslist}", From d26fa71d97de9d4c92edc3aacaf3f826871d45d5 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Holcroft Date: Wed, 25 Sep 2019 19:14:11 +0000 Subject: [PATCH 0204/3170] Translated using Weblate (French) Currently translated at 76.2% (428 of 562 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 03c274f5a..54ea1db46 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -46,13 +46,13 @@ "backup_app_failed": "Impossible de sauvegarder l’application '{app:s}'", "backup_archive_app_not_found": "L’application '{app:s}' n’a pas été trouvée dans l’archive de la sauvegarde", "backup_archive_hook_not_exec": "Le script « {hook:s} » n'a pas été exécuté dans cette sauvegarde", - "backup_archive_name_exists": "Une archive de sauvegarde avec ce nom existe déjà", + "backup_archive_name_exists": "Une archive de sauvegarde avec ce nom existe déjà.", "backup_archive_name_unknown": "L’archive locale de sauvegarde nommée '{name:s}' est inconnue", - "backup_archive_open_failed": "Impossible d’ouvrir l’archive de sauvegarde", + "backup_archive_open_failed": "Impossible d’ouvrir l’archive de la sauvegarde", "backup_cleaning_failed": "Impossible de nettoyer le dossier temporaire de sauvegarde", "backup_created": "Sauvegarde terminée", "backup_creating_archive": "Création de l’archive de sauvegarde …", - "backup_creation_failed": "Impossible de créer la sauvegarde", + "backup_creation_failed": "Impossible de créer l'archive de la sauvegarde", "backup_delete_error": "Impossible de supprimer '{path:s}'", "backup_deleted": "La sauvegarde a été supprimée", "backup_extracting_archive": "Extraction de l’archive de sauvegarde …", @@ -259,8 +259,8 @@ "certmanager_error_no_A_record": "Aucun enregistrement DNS 'A' n’a été trouvé pour {domain:s}. Vous devez faire pointer votre nom de domaine vers votre machine pour être en mesure d’installer un certificat Let’s Encrypt ! (Si vous savez ce que vous faites, utilisez --no-checks pour désactiver ces contrôles)", "certmanager_domain_dns_ip_differs_from_public_ip": "L’enregistrement DNS 'A' du domaine {domain:s} est différent de l’adresse IP de ce serveur. Si vous avez récemment modifié votre enregistrement 'A', veuillez attendre sa propagation (quelques vérificateur de propagation DNS sont disponibles en ligne). (Si vous savez ce que vous faites, utilisez --no-checks pour désactiver ces contrôles)", "certmanager_cannot_read_cert": "Quelque chose s’est mal passé lors de la tentative d’ouverture du certificat actuel pour le domaine {domain:s} (fichier : {file:s}), la cause est : {reason:s}", - "certmanager_cert_install_success_selfsigned": "Installation avec succès d’un certificat auto-signé pour le domaine {domain:s} !", - "certmanager_cert_install_success": "Installation avec succès d’un certificat Let’s Encrypt pour le domaine {domain:s} !", + "certmanager_cert_install_success_selfsigned": "Le certificat auto-signé est maintenant installé pour le domaine « {domain:s} »", + "certmanager_cert_install_success": "Le certificat Let’s Encrypt est maintenant installé pour le domaine « {domain:s} »", "certmanager_cert_renew_success": "Renouvellement avec succès d’un certificat Let’s Encrypt pour le domaine {domain:s} !", "certmanager_old_letsencrypt_app_detected": "\nYunoHost a détecté que l’application « letsencrypt » est installé, ce qui est en conflit avec les nouvelles fonctionnalités de gestion intégrée de certificats dans YunoHost. Si vous souhaitez utiliser ces nouvelles fonctionnalités intégrées, veuillez lancer les commandes suivantes pour migrer votre installation :\n\n yunohost app remove letsencrypt\n yunohost domain cert-install\n\nN.B. : cela tentera de réinstaller les certificats de tous les domaines avec un certificat Let's Encrypt ou ceux auto-signés", "certmanager_cert_signing_failed": "La signature du nouveau certificat a échoué", @@ -312,8 +312,8 @@ "global_settings_unknown_setting_from_settings_file": "Clé inconnue dans les paramètres : '{setting_key:s}', rejet de cette clé et sauvegarde de celle-ci dans /etc/yunohost/unkown_settings.json", "service_conf_new_managed_file": "Le fichier de configuration « {conf} » est désormais géré par le service {service}.", "service_conf_file_kept_back": "Le fichier de configuration '{conf}' devait être supprimé par le service {service} mais a été conservé.", - "backup_abstract_method": "Cette méthode de sauvegarde n’a pas encore été implémentée", - "backup_applying_method_tar": "Création de l’archive TAR de la sauvegarde …", + "backup_abstract_method": "Cette méthode de sauvegarde reste à implémenter", + "backup_applying_method_tar": "Création de l’archive TAR de la sauvegarde…", "backup_applying_method_copy": "Copie de tous les fichiers à sauvegarder …", "backup_applying_method_borg": "Envoi de tous les fichiers à sauvegarder dans le répertoire borg-backup…", "backup_applying_method_custom": "Appel de la méthode de sauvegarde personnalisée '{method:s}' …", @@ -324,20 +324,20 @@ "backup_borg_not_implemented": "La méthode de sauvegarde Borg n’est pas encore implémentée", "backup_cant_mount_uncompress_archive": "Impossible de monter en lecture seule le dossier de l’archive décompressée", "backup_copying_to_organize_the_archive": "Copie de {size:s} Mo pour organiser l’archive", - "backup_csv_creation_failed": "Impossible de créer le fichier CSV nécessaire aux opérations futures de restauration", + "backup_csv_creation_failed": "Impossible de créer le fichier CSV nécessaire à la restauration", "backup_csv_addition_failed": "Impossible d’ajouter des fichiers à sauvegarder dans le fichier CSV", "backup_custom_need_mount_error": "Échec de la méthode de sauvegarde personnalisée à l’étape 'need_mount'", "backup_custom_backup_error": "Échec de la méthode de sauvegarde personnalisée à l’étape 'backup'", "backup_custom_mount_error": "Échec de la méthode de sauvegarde personnalisée à l’étape 'mount'", - "backup_no_uncompress_archive_dir": "Le dossier de l’archive décompressée n’existe pas", - "backup_method_tar_finished": "L’archive tar de la sauvegarde a été créée", + "backup_no_uncompress_archive_dir": "Ce dossier d’archive décompressée n’existe pas", + "backup_method_tar_finished": "L’archive TAR de la sauvegarde a été créée", "backup_method_copy_finished": "La copie de la sauvegarde est terminée", "backup_method_borg_finished": "La sauvegarde dans Borg est terminée", "backup_method_custom_finished": "La méthode de sauvegarde personnalisée '{method:s}' est terminée", "backup_system_part_failed": "Impossible de sauvegarder la partie '{part:s}' du système", - "backup_unable_to_organize_files": "Impossible d’organiser les fichiers dans l’archive avec la méthode rapide", + "backup_unable_to_organize_files": "Impossible d’utiliser la méthode rapide pour organiser les fichiers dans l’archive", "backup_with_no_backup_script_for_app": "L’application {app:s} n’a pas de script de sauvegarde. Ignorer.", - "backup_with_no_restore_script_for_app": "L’application {app:s} n’a pas de script de restauration, vous ne pourrez pas restaurer automatiquement la sauvegarde de cette application.", + "backup_with_no_restore_script_for_app": "L’application « {app:s} » n’a pas de script de restauration, vous ne pourrez pas restaurer automatiquement la sauvegarde de cette application.", "global_settings_cant_serialize_settings": "Échec de la sérialisation des données de paramétrage car : {reason:s}", "restore_removing_tmp_dir_failed": "Impossible de sauvegarder un ancien dossier temporaire", "restore_extracting": "Extraction des fichiers nécessaires depuis l’archive …", @@ -423,7 +423,7 @@ "log_link_to_log": "Journal historisé complet de cette opération : ' {desc} '", "log_help_to_get_log": "Pour voir le journal historisé de cette opération '{desc}', utilisez la commande 'yunohost log display {name}'", "log_link_to_failed_log": "L’opération '{desc}' a échouée ! Pour avoir de l’aide, merci cliqué ici pour avoir de l'aide", - "backup_php5_to_php7_migration_may_fail": "Impossible de convertir votre archive pour prendre en charge php7, vos applications php pourraient ne pas être restaurées (reason: {error:s})", + "backup_php5_to_php7_migration_may_fail": "Impossible de convertir votre archive pour prendre en charge PHP 7, vous pourriez ne plus pouvoir restaurer vos applications PHP (cause : {error:s})", "log_help_to_get_failed_log": "L’opération '{desc}' a échouée ! Pour avoir de l’aide, merci de partager le journal historisé de cette opération en utilisant la commande 'yunohost log display {name} --share'", "log_does_exists": "Il n’existe pas de journal historisé de l’opération ayant pour nom '{log}', utiliser 'yunohost log list pour voir tous les fichiers de journaux historisés disponibles'", "log_operation_unit_unclosed_properly": "L’opération ne s’est pas terminée correctement", @@ -589,5 +589,7 @@ "log_user_permission_add": "Mettre à jour l'autorisation pour '{}'", "log_user_permission_remove": "Mettre à jour l'autorisation pour '{}'", "mailbox_disabled": "La boîte aux lettres est désactivée pour l'utilisateur {user:s}", - "app_action_broke_system": "Cette action semble avoir cassé des services important : {services}" + "app_action_broke_system": "Cette action semble avoir cassé des services important : {services}", + "apps_already_up_to_date": "Toutes les applications sont déjà à jour", + "app_upgrade_stopped": "La mise à jour de toutes les applications a été arrêtée afin d’éviter d’éventuels dommages dus à l’échec de la mise à jour de l’application précédente" } From 81bb7bffd90994ae36f9c9795a0163bef5fe8b71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20D=C3=B6ring?= Date: Mon, 23 Sep 2019 17:51:54 +0000 Subject: [PATCH 0205/3170] Translated using Weblate (German) Currently translated at 40.7% (229 of 562 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 58 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/locales/de.json b/locales/de.json index f9e9bbdbd..2e3777207 100644 --- a/locales/de.json +++ b/locales/de.json @@ -357,5 +357,61 @@ "admin_password_too_long": "Bitte ein Passwort kürzer als 127 Zeichen wählen", "app_action_broke_system": "Diese Aktion hat anscheinend diese wichtigen Services gestört: {services}", "apps_already_up_to_date": "Alle Apps sind bereits aktuell", - "backup_copying_to_organize_the_archive": "Kopieren von {size: s} MB, um das Archiv zu organisieren" + "backup_copying_to_organize_the_archive": "Kopieren von {size: s} MB, um das Archiv zu organisieren", + "app_upgrade_stopped": "Das Upgrade aller Anwendungen wurde gestoppt, um mögliche Schäden zu vermeiden, da das Upgrade der vorherigen Anwendung fehlgeschlagen ist", + "group_already_disallowed": "Die Gruppe '{group:s}' hat bereits die Berechtigungen '{permission:s}' für die App '{app:s}' deaktiviert", + "global_settings_setting_security_ssh_compatibility": "Kompatibilität vs. Sicherheitskompromiss für den SSH-Server. Beeinflusst die Chiffren (und andere sicherheitsrelevante Aspekte)", + "group_deleted": "Gruppe '{group}' gelöscht", + "group_deletion_failed": "Kann Gruppe '{group}' nicht löschen", + "group_deletion_not_allowed": "Die Gruppe {group:s} kann nicht manuell gelöscht werden.", + "dyndns_provider_unreachable": "Dyndns-Anbieter {provider} kann nicht erreicht werden: Entweder ist dein YunoHost nicht korrekt mit dem Internet verbunden oder der Dynette-Server ist ausgefallen.", + "group_already_allowed": "Gruppe '{group:s}' hat bereits die Berechtigung '{permission:s}' für die App '{app:s}' eingeschaltet", + "group_name_already_exist": "Gruppe {name:s} existiert bereits", + "group_created": "Gruppe '{group}' angelegt", + "group_creation_failed": "Kann Gruppe '{group}' nicht anlegen", + "group_unknown": "Die Gruppe '{group:s}' ist unbekannt", + "group_updated": "Gruppe '{group:s}' erneuert", + "group_update_failed": "Kann Gruppe '{group:s}' nicht anpassen", + "log_does_exists": "Es gibt kein Operationsprotokoll mit dem Namen'{log}', verwende'yunohost log list', um alle verfügbaren Operationsprotokolle anzuzeigen", + "log_app_removelist": "Entferne eine Applikationsliste", + "log_operation_unit_unclosed_properly": "Die Operationseinheit wurde nicht richtig geschlossen", + "log_app_removeaccess": "Entziehe Zugriff auf '{}'", + "global_settings_setting_security_postfix_compatibility": "Kompatibilität vs. Sicherheitskompromiss für den Postfix-Server. Beeinflusst die Chiffren (und andere sicherheitsrelevante Aspekte)", + "log_category_404": "Die Log-Kategorie '{category}' existiert nicht", + "global_settings_unknown_type": "Unerwartete Situation, die Einstellung {setting:s} scheint den Typ {unknown_type:s} zu haben, ist aber kein vom System unterstützter Typ.", + "dpkg_is_broken": "Du kannst das gerade nicht tun, weil dpkg/APT (der Systempaketmanager) in einem defekten Zustand zu sein scheint.... Du kannst versuchen, dieses Problem zu lösen, indem du dich über SSH verbindest und `sudo dpkg --configure -a` ausführst.", + "global_settings_unknown_setting_from_settings_file": "Unbekannter Schlüssel in den Einstellungen: '{setting_key:s}', verwerfen und speichern in /etc/yunohost/settings-unknown.json", + "log_link_to_log": "Vollständiges Log dieser Operation: '{desc}'", + "global_settings_setting_example_bool": "Beispiel einer booleschen Option", + "log_app_fetchlist": "Füge eine Applikationsliste hinzu", + "log_help_to_get_log": "Um das Protokoll der Operation '{desc}' anzuzeigen, verwende den Befehl 'yunohost log display {name}'", + "global_settings_setting_security_nginx_compatibility": "Kompatibilität vs. Sicherheitskompromiss für den Webserver NGINX. Beeinflusst die Chiffren (und andere sicherheitsrelevante Aspekte)", + "backup_php5_to_php7_migration_may_fail": "Dein Archiv konnte nicht für PHP 7 konvertiert werden, Du kannst deine PHP-Anwendungen möglicherweise nicht wiederherstellen (Grund: {error:s})", + "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Erlaubt die Verwendung eines (veralteten) DSA-Hostkeys für die SSH-Daemon-Konfiguration", + "global_settings_setting_example_string": "Beispiel einer string Option", + "log_app_addaccess": "Füge Zugriff auf '{}' hinzu", + "log_app_remove": "Entferne die Anwendung '{}'", + "global_settings_setting_example_int": "Beispiel einer int Option", + "global_settings_cant_open_settings": "Einstellungsdatei konnte nicht geöffnet werden, Grund: {reason:s}", + "global_settings_cant_write_settings": "Einstellungsdatei konnte nicht gespeichert werden, Grund: {reason:s}", + "log_app_install": "Installiere die Anwendung '{}'", + "global_settings_reset_success": "Frühere Einstellungen werden nun auf {path:s} gesichert", + "log_app_upgrade": "Upgrade der Anwendung '{}'", + "good_practices_about_admin_password": "Sie sind nun dabei, ein neues Administrationspasswort zu definieren. Das Passwort sollte mindestens 8 Zeichen lang sein - obwohl es sinnvoll ist, ein längeres Passwort (z.B. eine Passphrase) und/oder eine Variation von Zeichen (Groß- und Kleinschreibung, Ziffern und Sonderzeichen) zu verwenden.", + "log_corrupted_md_file": "Die mit Protokollen verknüpfte YAML-Metadatendatei ist beschädigt: '{md_file}\nFehler: {error}''", + "global_settings_cant_serialize_settings": "Einstellungsdaten konnten nicht serialisiert werden, Grund: {reason:s}", + "log_help_to_get_failed_log": "Der Vorgang'{desc}' konnte nicht abgeschlossen werden. Bitte teile das vollständige Protokoll dieser Operation mit dem Befehl 'yunohost log display {name} --share', um Hilfe zu erhalten", + "backup_no_uncompress_archive_dir": "Dieses unkomprimierte Archivverzeichnis gibt es nicht", + "log_app_change_url": "Ändere die URL der Anwendung '{}'", + "global_settings_setting_security_password_user_strength": "Stärke des Benutzerpassworts", + "good_practices_about_user_password": "Du bist nun dabei, ein neues Benutzerpasswort zu definieren. Das Passwort sollte mindestens 8 Zeichen lang sein - obwohl es ratsam ist, ein längeres Passwort (z.B. eine Passphrase) und/oder eine Variation von Zeichen (Groß- und Kleinschreibung, Ziffern und Sonderzeichen) zu verwenden.", + "global_settings_setting_example_enum": "Beispiel einer enum Option", + "log_link_to_failed_log": "Der Vorgang konnte nicht abgeschlossen werden '{desc}'. Bitte gib das vollständige Protokoll dieser Operation mit Klicken Sie hier an, um Hilfe zu erhalten", + "backup_cant_mount_uncompress_archive": "Das unkomprimierte Archiv konnte nicht als schreibgeschützt gemountet werden", + "backup_csv_addition_failed": "Es konnten keine Dateien zur Sicherung in die CSV-Datei hinzugefügt werden", + "log_app_clearaccess": "Entziehe alle Zugriffe auf '{}'", + "global_settings_setting_security_password_admin_strength": "Stärke des Admin-Passworts", + "global_settings_key_doesnt_exists": "Der Schlüssel'{settings_key:s}' existiert nicht in den globalen Einstellungen, du kannst alle verfügbaren Schlüssel sehen, indem du 'yunohost settings list' ausführst", + "log_app_makedefault": "Mache '{}' zur Standard-Anwendung", + "hook_json_return_error": "Konnte die Rückkehr vom Einsprungpunkt {path:s} nicht lesen. Fehler: {msg:s}. Unformatierter Inhalt: {raw_content}" } From cb3a2a3adb2d5ccf6194a7aae06dbdf009d11043 Mon Sep 17 00:00:00 2001 From: nr 458 h Date: Tue, 24 Sep 2019 08:33:31 +0000 Subject: [PATCH 0206/3170] Translated using Weblate (German) Currently translated at 40.7% (229 of 562 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/locales/de.json b/locales/de.json index 2e3777207..d03226187 100644 --- a/locales/de.json +++ b/locales/de.json @@ -46,11 +46,11 @@ "backup_delete_error": "Pfad '{path:s}' konnte nicht gelöscht werden", "backup_deleted": "Backup wurde entfernt", "backup_extracting_archive": "Entpacke Sicherungsarchiv...", - "backup_hook_unknown": "Datensicherungshook '{hook:s}' unbekannt", + "backup_hook_unknown": "Der Datensicherungshook '{hook:s}' unbekannt", "backup_invalid_archive": "Dies ist kein Backup-Archiv", - "backup_nothings_done": "Es gibt keine Änderungen zur Speicherung", - "backup_output_directory_forbidden": "Verbotenes Ausgabeverzeichnis. Datensicherung können nicht in /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var oder in Unterordnern von /home/yunohost.backup/archives erstellt werden", - "backup_output_directory_not_empty": "Ausgabeordner ist nicht leer", + "backup_nothings_done": "Keine Änderungen zur Speicherung", + "backup_output_directory_forbidden": "Wähle ein anderes Ausgabeverzeichnis. Datensicherung können nicht in /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var oder in Unterordnern von /home/yunohost.backup/archives erstellt werden", + "backup_output_directory_not_empty": "Der gewählte Ausgabeordner sollte leer sein", "backup_output_directory_required": "Für die Datensicherung muss ein Zielverzeichnis angegeben werden", "backup_running_app_script": "Datensicherung für App '{app:s}' wurd durchgeführt...", "backup_running_hooks": "Datensicherunghook wird ausgeführt…", @@ -325,7 +325,7 @@ "backup_permission": "Sicherungsberechtigung für App {app: s}", "backup_output_symlink_dir_broken": "Sie haben einen fehlerhaften Symlink anstelle Ihres Archivverzeichnisses '{path: s}'. Möglicherweise haben Sie ein spezielles Setup, um Ihre Daten auf einem anderen Dateisystem zu sichern. In diesem Fall haben Sie wahrscheinlich vergessen, Ihre Festplatte oder Ihren USB-Schlüssel erneut einzuhängen oder anzuschließen.", "backup_mount_archive_for_restore": "Archiv für Wiederherstellung vorbereiten…", - "backup_method_tar_finished": "Sicherungs-Tar-Archiv erstellt", + "backup_method_tar_finished": "Tar-Backup-Archiv erstellt", "backup_method_custom_finished": "Benutzerdefinierte Sicherungsmethode '{method: s}' beendet", "backup_method_copy_finished": "Sicherungskopie beendet", "backup_method_borg_finished": "Backup in Borg beendet", From e1ca92234aeac72fe2758b76acfdf29361158310 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Tue, 24 Sep 2019 00:14:38 +0000 Subject: [PATCH 0207/3170] =?UTF-8?q?Translated=20using=20Weblate=20(Norwe?= =?UTF-8?q?gian=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 24.9% (140 of 562 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/nb_NO/ --- locales/nb_NO.json | 170 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 169 insertions(+), 1 deletion(-) diff --git a/locales/nb_NO.json b/locales/nb_NO.json index 0967ef424..4fe62eebb 100644 --- a/locales/nb_NO.json +++ b/locales/nb_NO.json @@ -1 +1,169 @@ -{} +{ + "aborting": "Avbryter…", + "admin_password": "Administrasjonspassord", + "admin_password_change_failed": "Kan ikke endre passord", + "admin_password_changed": "Administrasjonspassord endret", + "admin_password_too_long": "Velg et passord kortere enn 127 tegn", + "app_already_installed": "{app:s} er allerede installert", + "app_already_up_to_date": "{app:s} er allerede oppdatert", + "app_argument_invalid": "Velg en gydlig verdi for argumentet '{name:s}': {error:s}", + "app_argument_required": "Argumentet '{name:s}' er påkrevd", + "diagnosis_no_apps": "Inget program installert", + "app_id_invalid": "Ugyldig program-ID", + "dyndns_cron_remove_failed": "Kunne ikke fjerne cron-jobb for DynDNS: {error}", + "dyndns_key_not_found": "Fant ikke DNS-nøkkel for domenet", + "app_not_correctly_installed": "{app:s} ser ikke ut til å ha blitt installert på riktig måte", + "dyndns_provider_unreachable": "Kunne ikke nå DynDNS-tilbyder {provider}: Enten har du ikke satt opp din YunoHost rett, dynette-tjeneren er nede, eller du mangler nett.", + "app_not_properly_removed": "{app:s} har ikke blitt fjernet på riktig måte", + "app_package_need_update": "Programmet {app}-pakken må oppdateres for å holde følge med YunoHost sine endringer", + "app_removed": "{app:s} fjernet", + "app_requirements_checking": "Sjekker påkrevde pakker for {app:s}…", + "app_requirements_failed": "Noen krav er ikke oppfylt for {app:s}: {error}", + "app_start_install": "Installerer programmet '{app}'…", + "action_invalid": "Ugyldig handling '{action:s}'", + "app_start_restore": "Gjenoppretter programmet '{app}'…", + "backup_created": "Sikkerhetskopi opprettet", + "backup_archive_name_exists": "En sikkerhetskopi med dette navnet finnes allerede.", + "backup_archive_name_unknown": "Ukjent lokalt sikkerhetskopiarkiv ved navn '{name:s}'", + "already_up_to_date": "Ingenting å gjøre. Alt er oppdatert.", + "backup_method_copy_finished": "Sikkerhetskopi fullført", + "backup_method_tar_finished": "TAR-sikkerhetskopiarkiv opprettet", + "app_action_cannot_be_ran_because_required_services_down": "Dette programmet krever noen tjenester som ikke kjører. Før du fortsetter, du bør prøve å starte følgende tjenester på ny (og antagelig undersøke hvorfor de er nede): {services}", + "app_already_installed_cant_change_url": "Dette programmet er allerede installert. Nettadressen kan ikke endres kun med denne funksjonen. Ta en titt på `app changeurl` hvis den er tilgjengelig.", + "diagnosis_monitor_disk_error": "Kunne ikke holde oppsyn med disker: {error}", + "diagnosis_monitor_system_error": "Kunne ikke holde oppsyn med systemet: {error}", + "domain_exists": "Domenet finnes allerede", + "app_change_url_failed_nginx_reload": "Kunne ikke gjeninnlaste NGINX. Her har du utdataen for 'nginx -t'\n{nginx_errors:s}", + "domains_available": "Tilgjengelige domener:", + "done": "Ferdig", + "downloading": "Laster ned…", + "dyndns_could_not_check_provide": "Kunne ikke sjekke om {provider:s} kan tilby {domain:s}.", + "dyndns_could_not_check_available": "Kunne ikke sjekke om {domain:s} er tilgjengelig på {provider:s}.", + "log_app_removeaccess": "Fjern tilgang til '{}'", + "license_undefined": "udefinert", + "mail_domain_unknown": "Ukjent e-postadresse for domenet '{domain:s}'", + "migrate_tsig_wait_2": "2 min…", + "log_remove_on_failed_restore": "Fjern '{}' etter mislykket gjenoppretting fra sikkerhetskopiarkiv", + "log_letsencrypt_cert_install": "Installer et Let's Encrypt-sertifikat på '{}'-domenet", + "log_permission_update": "Oppdater tilgang '{}' for programmet '{}'", + "log_letsencrypt_cert_renew": "Forny '{}'-Let's Encrypt-sertifikat", + "log_user_update": "Oppdater brukerinfo for '{}'", + "mail_alias_remove_failed": "Kunne ikke fjerne e-postaliaset '{mail:s}'", + "app_action_broke_system": "Denne handlingen ser ut til å ha knekt disse viktige tjenestene: {services}", + "app_argument_choice_invalid": "Bruk én av disse valgene '{choices:s}' for argumentet '{name:s}'", + "app_extraction_failed": "Kunne ikke pakke ut installasjonsfilene", + "app_incompatible": "Programmet {app} er ikke kompatibelt med din YunoHost-versjon", + "app_install_files_invalid": "Disse filene kan ikke installeres", + "app_location_already_used": "Programmet '{app}' er allerede installert i ({path})", + "ask_path": "Sti", + "backup_abstract_method": "Denne sikkerhetskopimetoden er ikke implementert enda", + "backup_actually_backuping": "Oppretter sikkerhetskopiarkiv fra innsamlede filer…", + "backup_app_failed": "Kunne ikke sikkerhetskopiere programmet '{app:s}'", + "backup_applying_method_tar": "Lager TAR-sikkerhetskopiarkiv…", + "backup_archive_app_not_found": "Fant ikke programmet '{app:s}' i sikkerhetskopiarkivet", + "backup_archive_open_failed": "Kunne ikke åpne sikkerhetskopiarkivet", + "app_start_remove": "Fjerner programmet '{app}'…", + "app_start_backup": "Samler inn filer for sikkerhetskopiering for {app}…", + "backup_applying_method_copy": "Kopier alle filer til sikkerhetskopi…", + "backup_borg_not_implemented": "Borg-sikkerhetskopimetoden er ikke implementert enda", + "backup_creation_failed": "Kunne ikke opprette sikkerhetskopiarkiv", + "backup_couldnt_bind": "Kunne ikke binde {src:s} til {dest:s}.", + "backup_csv_addition_failed": "Kunne ikke legge til filer for sikkerhetskopi inn i CSV-filen", + "backup_deleted": "Sikkerhetskopi slettet", + "backup_no_uncompress_archive_dir": "Det finnes ingen slik utpakket arkivmappe", + "backup_delete_error": "Kunne ikke slette '{path:s}'", + "certmanager_domain_unknown": "Ukjent domene '{domain:s}'", + "certmanager_cert_signing_failed": "Kunne ikke signere det nye sertifikatet", + "diagnosis_debian_version_error": "Kunne ikke hente Debian-versjon: {error}", + "diagnosis_kernel_version_error": "Kunne ikke hente kjerneversjon: {error}", + "error_when_removing_sftpuser_group": "Kunne ikke fjerne sftpusers-gruppen", + "executing_command": "Kjører kommendoen '{command:s}'…", + "executing_script": "Kjører skriptet '{script:s}'…", + "extracting": "Pakker ut…", + "edit_group_not_allowed": "Du tillates ikke å redigere gruppen {group:s}", + "log_domain_add": "Legg til '{}'-domenet i systemoppsett", + "log_domain_remove": "Fjern '{}'-domenet fra systemoppsett", + "log_dyndns_subscribe": "Abonner på YunoHost-underdomenet '{}'", + "log_dyndns_update": "Oppdater IP-adressen tilknyttet ditt YunoHost-underdomene '{}'", + "migrate_tsig_wait_3": "1 min…", + "migrate_tsig_wait_4": "30 sekunder…", + "apps_permission_restoration_failed": "Innvilg tilgangen '{permission:s}' for å gjenopprette {app:}", + "apps_permission_not_found": "Fant ingen tilgang for de installerte programmene", + "backup_invalid_archive": "Dette er ikke et sikkerhetskopiarkiv", + "backup_nothings_done": "Ingenting å lagre", + "backup_method_borg_finished": "Sikkerhetskopi inn i Borg fullført", + "field_invalid": "Ugyldig felt '{:s}'", + "firewall_reloaded": "Brannmur gjeninnlastet", + "log_app_removelist": "Fjern en programliste", + "log_app_change_url": "Endre nettadresse for '{}'-programmet", + "log_app_install": "Installer '{}'-programmet", + "log_app_remove": "Fjern '{}'-programmet", + "log_app_upgrade": "Oppgrader '{}'-programmet", + "log_app_makedefault": "Gjør '{}' til forvalgt program", + "log_available_on_yunopaste": "Denne loggen er nå tilgjengelig via {url}", + "log_tools_maindomain": "Gjør '{}' til hoveddomene", + "log_tools_shutdown": "Slå av tjeneren din", + "log_tools_reboot": "Utfør omstart av tjeneren din", + "apps_already_up_to_date": "Alle programmer allerede oppdatert", + "backup_mount_archive_for_restore": "Forbereder arkiv for gjenopprettelse…", + "backup_copying_to_organize_the_archive": "Kopierer {size:s} MB for å organisere arkivet", + "domain_cannot_remove_main": "Kan ikke fjerne hoveddomene. Sett et først", + "domain_cert_gen_failed": "Kunne ikke opprette sertifikat", + "domain_created": "Domene opprettet", + "domain_creation_failed": "Kunne ikke opprette domene", + "domain_dyndns_root_unknown": "Ukjent DynDNS-rotdomene", + "domain_unknown": "Ukjent domene", + "dyndns_cron_installed": "Opprettet cron-jobb for DynDNS", + "dyndns_cron_removed": "Fjernet cron-jobb for DynDNS", + "dyndns_ip_update_failed": "Kunne ikke oppdatere IP-adresse til DynDNS", + "dyndns_ip_updated": "Oppdaterte din IP på DynDNS", + "dyndns_key_generating": "Oppretter DNS-nøkkel… Dette kan ta en stund.", + "dyndns_no_domain_registered": "Inget domene registrert med DynDNS", + "dyndns_registered": "DynDNS-domene registrert", + "global_settings_setting_security_password_admin_strength": "Admin-passordets styrke", + "dyndns_registration_failed": "Kunne ikke registrere DynDNS-domene: {error:s}", + "global_settings_setting_security_password_user_strength": "Brukerpassordets styrke", + "log_app_fetchlist": "Legg til en programliste", + "log_backup_restore_app": "Gjenopprett '{}' fra sikkerhetskopiarkiv", + "log_remove_on_failed_install": "Fjern '{}' etter mislykket installasjon", + "log_permission_add": "Legg til '{}'-tilgangen for programmet '{}'", + "log_permission_remove": "Fjern tilgangen '{}'", + "log_selfsigned_cert_install": "Installer selvsignert sertifikat på '{}'-domenet", + "log_user_delete": "Slett '{}' bruker", + "log_user_group_add": "Legg til '{}' gruppe", + "log_user_group_delete": "Slett '{}' gruppe", + "log_user_group_update": "Oppdater '{}' gruppe", + "log_user_permission_add": "Oppdater '{}' tilgang", + "log_user_permission_remove": "Oppdater '{}' tilgang", + "ldap_init_failed_to_create_admin": "LDAP-igangsettelse kunne ikke opprette admin-bruker", + "ldap_initialized": "LDAP-igangsatt", + "maindomain_changed": "Hoveddomenet er nå endret", + "migration_description_0003_migrate_to_stretch": "Oppgrader systemet til Debian Stretch og YunoHost 3.0", + "app_unknown": "Ukjent program", + "app_upgrade_app_name": "Oppgraderer {app}…", + "app_upgrade_failed": "Kunne ikke oppgradere {app:s}", + "app_upgrade_some_app_failed": "Noen programmer kunne ikke oppgraderes", + "app_upgraded": "{app:s} oppgradert", + "ask_email": "E-postadresse", + "ask_firstname": "Fornavn", + "ask_lastname": "Etternavn", + "ask_list_to_remove": "Liste å fjerne", + "ask_main_domain": "Hoveddomene", + "ask_new_admin_password": "Nytt administrasjonspassord", + "app_upgrade_several_apps": "Følgende programmer vil oppgraderes: {apps}", + "appslist_removed": "{appslist:s}-programliste fjernet", + "appslist_url_already_tracked": "Dette er allerede en registrert programliste med nettadressen {url:s}.", + "ask_current_admin_password": "Nåværende administrasjonspassord", + "appslist_unknown": "Programlisten {appslist:s} er ukjent.", + "ask_new_domain": "Nytt domene", + "ask_new_path": "Ny sti", + "ask_password": "Passord", + "domain_deleted": "Domene slettet", + "domain_deletion_failed": "Kunne ikke slette domene", + "domain_dyndns_already_subscribed": "Du har allerede abonnement på et DynDNS-domene", + "log_category_404": "Loggkategorien '{category}' finnes ikke", + "log_link_to_log": "Full logg for denne operasjonen: '{desc}'", + "log_help_to_get_log": "For å vise loggen for operasjonen '{desc}', bruk kommandoen 'yunohost log display {name}'", + "log_app_clearaccess": "Fjern all tilgang til '{}'", + "log_user_create": "Legg til '{}' bruker" +} From d159f7ff07d20734d50594cf1e8eacde7f77f1ad Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 28 Sep 2019 16:11:44 +0200 Subject: [PATCH 0208/3170] Misc typo / wording / readability Co-Authored-By: decentral1se --- locales/en.json | 2 +- src/yunohost/app.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/locales/en.json b/locales/en.json index ba0d7e3cd..f9194bb42 100644 --- a/locales/en.json +++ b/locales/en.json @@ -23,7 +23,7 @@ "app_incompatible": "The app {app} is incompatible with your YunoHost version", "app_install_files_invalid": "These files cannot be installed", "app_install_failed": "Could not install {app}", - "app_install_script_failed": "An error occured inside the app installation script.", + "app_install_script_failed": "An error occured inside the app installation script", "app_location_already_used": "The app '{app}' is already installed in ({path})", "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_install_failed": "Cannot install the app there because it conflicts with the app '{other_app}' already installed in '{other_path}'", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index f2eea5646..8c7d37f92 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -915,7 +915,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu args=args_list, env=env_dict )[0] # "Common" app install failure : the script failed and returned exit code != 0 - install_failed = (install_retcode != 0) + install_failed = True if install_retcode != 0 else False if install_failed: error = m18n.n('app_install_script_failed') logger.exception(error) @@ -967,7 +967,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu os.path.join(extracted_app_folder, 'scripts/remove'), args=[app_instance_name], env=env_dict_remove )[0] - # Here again, calling hook_exec could failed miserably, or get + # Here again, calling hook_exec could fail miserably, or get # manually interrupted (by mistake or because script was stuck) # In that case we still want to proceed with the rest of the # removal (permissions, /etc/yunohost/apps/{app} ...) @@ -1085,7 +1085,7 @@ def app_remove(operation_logger, app): ret = hook_exec('/tmp/yunohost_remove/scripts/remove', args=args_list, env=env_dict)[0] - # Here again, calling hook_exec could failed miserably, or get + # Here again, calling hook_exec could fail miserably, or get # manually interrupted (by mistake or because script was stuck) # In that case we still want to proceed with the rest of the # removal (permissions, /etc/yunohost/apps/{app} ...) From a98d7f4750da8e62fc1839ea86c5247ee023c440 Mon Sep 17 00:00:00 2001 From: advocatux Date: Thu, 3 Oct 2019 19:28:23 +0000 Subject: [PATCH 0209/3170] Translated using Weblate (Spanish) Currently translated at 100.0% (562 of 562 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/es/ --- locales/es.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/es.json b/locales/es.json index ff4a230f9..02f46652b 100644 --- a/locales/es.json +++ b/locales/es.json @@ -320,7 +320,7 @@ "experimental_feature": "Aviso : esta funcionalidad es experimental y no se considera estable, no debería usarla a menos que sepa lo que está haciendo.", "good_practices_about_user_password": "Está a punto de establecer una nueva contraseña de usuario. La contraseña debería de ser de al menos 8 caracteres, aunque es una buena práctica usar una contraseña más extensa (básicamente una frase) y/o usar caracteres de varias clases (mayúsculas, minúsculas, números y caracteres especiales).", "password_listed": "Esta contraseña es una de las más usadas en el mundo. Elija algo más único.", - "password_too_simple_1": "La contraseña tiene que ser de al menos 8 caracteres de longitud", + "password_too_simple_1": "La contraseña debe tener al menos 8 caracteres de longitud", "password_too_simple_2": "La contraseña tiene que ser de al menos 8 caracteres de longitud e incluir un número y caracteres en mayúsculas y minúsculas", "password_too_simple_3": "La contraseña tiene que ser de al menos 8 caracteres de longitud e incluir un número, mayúsculas, minúsculas y caracteres especiales", "password_too_simple_4": "La contraseña tiene que ser de al menos 12 caracteres de longitud e incluir un número, mayúsculas, minúsculas y caracteres especiales", From bd8a91fff7cdc29ed58fb5a649b8e93b6081e7f5 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Wed, 2 Oct 2019 05:49:03 +0000 Subject: [PATCH 0210/3170] Translated using Weblate (French) Currently translated at 100.0% (562 of 562 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 136 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 90 insertions(+), 46 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 54ea1db46..8bffec8b2 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -88,15 +88,15 @@ "domain_zone_not_found": "Fichier de zone DNS introuvable pour le domaine {:s}", "done": "Terminé", "downloading": "Téléchargement en cours …", - "dyndns_cron_installed": "La tâche cron pour le domaine DynDNS a été installée", + "dyndns_cron_installed": "La tâche cron pour le domaine DynDNS a été créée", "dyndns_cron_remove_failed": "Impossible de supprimer la tâche cron DynDNS parce que: {error}", - "dyndns_cron_removed": "La tâche cron pour le domaine DynDNS a été enlevée", + "dyndns_cron_removed": "La tâche cron pour le domaine DynDNS enlevée", "dyndns_ip_update_failed": "Impossible de mettre à jour l’adresse IP sur le domaine DynDNS", - "dyndns_ip_updated": "Votre adresse IP a été mise à jour pour le domaine DynDNS", + "dyndns_ip_updated": "Mise à jour de votre IP pour le domaine DynDNS", "dyndns_key_generating": "Génération de la clé DNS ... , cela peut prendre un certain temps.", "dyndns_key_not_found": "Clé DNS introuvable pour le domaine", - "dyndns_no_domain_registered": "Aucun domaine n’a été enregistré avec DynDNS", - "dyndns_registered": "Le domaine DynDNS a été enregistré", + "dyndns_no_domain_registered": "Aucun domaine enregistré avec DynDNS", + "dyndns_registered": "Domaine DynDNS enregistré", "dyndns_registration_failed": "Impossible d’enregistrer le domaine DynDNS : {error:s}", "dyndns_unavailable": "Le domaine {domain:s} est indisponible.", "executing_command": "Exécution de la commande '{command:s}' …", @@ -104,8 +104,8 @@ "extracting": "Extraction en cours …", "field_invalid": "Champ incorrect : '{:s}'", "firewall_reload_failed": "Impossible de recharger le pare-feu", - "firewall_reloaded": "Le pare-feu a été rechargé", - "firewall_rules_cmd_failed": "Certaines règles du pare-feu n’ont pas pu être appliquées. Pour plus d’informations, consultez le journal.", + "firewall_reloaded": "Pare-feu rechargé", + "firewall_rules_cmd_failed": "Certaines règles du pare-feu n’ont pas pu être appliquées. Plus d'info dans le journal de log.", "format_datetime_short": "%d/%m/%Y %H:%M", "hook_argument_missing": "Argument manquant : '{:s}'", "hook_choice_invalid": "Choix incorrect : '{:s}'", @@ -114,16 +114,16 @@ "hook_list_by_invalid": "Propriété invalide pour lister les actions par celle-ci", "hook_name_unknown": "Nom de l'action '{name:s}' inconnu", "installation_complete": "Installation terminée", - "installation_failed": "Échec de l’installation", + "installation_failed": "Quelque chose s'est mal passé lors de l'installation", "ip6tables_unavailable": "Vous ne pouvez pas jouer avec ip6tables ici. Vous êtes soit dans un conteneur, soit votre noyau ne le prend pas en charge", "iptables_unavailable": "Vous ne pouvez pas jouer avec iptables ici. Vous êtes soit dans un conteneur, soit votre noyau ne le prend pas en charge", - "ldap_initialized": "L’annuaire LDAP a été initialisé", + "ldap_initialized": "L’annuaire LDAP initialisé", "license_undefined": "indéfinie", "mail_alias_remove_failed": "Impossible de supprimer l’alias de courriel '{mail:s}'", "mail_domain_unknown": "Le domaine '{domain:s}' pour l'adresse de courriel est inconnu", "mail_forward_remove_failed": "Impossible de supprimer le courriel de transfert '{mail:s}'", "maindomain_change_failed": "Impossible de modifier le domaine principal", - "maindomain_changed": "Le domaine principal a été modifié", + "maindomain_changed": "Le domaine principal modifié", "monitor_disabled": "La supervision du serveur a été désactivé", "monitor_enabled": "La supervision du serveur a été activé", "monitor_glances_con_failed": "Impossible de se connecter au serveur Glances", @@ -173,7 +173,7 @@ "restore_already_installed_app": "Une application est déjà installée avec l’identifiant '{app:s}'", "restore_app_failed": "Impossible de restaurer l’application '{app:s}'", "restore_cleaning_failed": "Impossible de nettoyer le dossier temporaire de restauration", - "restore_complete": "Restauration terminée", + "restore_complete": "Restauré", "restore_confirm_yunohost_installed": "Voulez-vous vraiment restaurer un système déjà installé ? [{answers:s}]", "restore_failed": "Impossible de restaurer le système", "restore_hook_unavailable": "Le script de restauration '{part:s}' n’est pas disponible sur votre système, et ne l'est pas non plus dans l’archive", @@ -218,9 +218,9 @@ "service_unknown": "Le service '{service:s}' est inconnu", "services_configured": "La configuration a été générée avec succès", "show_diff": "Voici les différences :\n{diff:s}", - "ssowat_conf_generated": "La configuration de SSOwat a été générée", - "ssowat_conf_updated": "La configuration de SSOwat a été mise à jour", - "system_upgraded": "Le système a été mis à jour", + "ssowat_conf_generated": "La configuration de SSOwat générée", + "ssowat_conf_updated": "La configuration de SSOwat mise à jour", + "system_upgraded": "Système mis à jour", "system_username_exists": "Ce nom d’utilisateur existe déjà dans les utilisateurs système", "unbackup_app": "L’application '{app:s}' ne sera pas sauvegardée", "unexpected_error": "Une erreur inattendue est survenue : {error}", @@ -232,12 +232,12 @@ "upgrade_complete": "Mise à jour terminée", "upgrading_packages": "Mise à jour des paquets en cours …", "upnp_dev_not_found": "Aucun périphérique compatible UPnP n’a été trouvé", - "upnp_disabled": "UPnP a été désactivé", - "upnp_enabled": "UPnP a été activé", + "upnp_disabled": "UPnP désactivé", + "upnp_enabled": "UPnP activé", "upnp_port_open_failed": "Impossible d’ouvrir les ports UPnP", - "user_created": "L’utilisateur a été créé", + "user_created": "L’utilisateur créé", "user_creation_failed": "Impossible de créer l’utilisateur", - "user_deleted": "L’utilisateur a été supprimé", + "user_deleted": "L’utilisateur supprimé", "user_deletion_failed": "Impossible de supprimer l’utilisateur", "user_home_creation_failed": "Impossible de créer le dossier personnel de l’utilisateur", "user_info_failed": "Impossible de récupérer les informations de l’utilisateur", @@ -246,7 +246,7 @@ "user_updated": "L’utilisateur a été modifié", "yunohost_already_installed": "YunoHost est déjà installé", "yunohost_ca_creation_failed": "Impossible de créer l’autorité de certification", - "yunohost_configured": "YunoHost a été configuré", + "yunohost_configured": "YunoHost maintenant configuré", "yunohost_installing": "L'installation de YunoHost est en cours …", "yunohost_not_installed": "YunoHost n’est pas ou pas correctement installé. Veuillez exécuter 'yunohost tools postinstall'", "certmanager_attempt_to_replace_valid_cert": "Vous êtes en train de vouloir remplacer un certificat correct et valide pour le domaine {domain:s} ! (Utilisez --force pour contourner cela)", @@ -261,15 +261,15 @@ "certmanager_cannot_read_cert": "Quelque chose s’est mal passé lors de la tentative d’ouverture du certificat actuel pour le domaine {domain:s} (fichier : {file:s}), la cause est : {reason:s}", "certmanager_cert_install_success_selfsigned": "Le certificat auto-signé est maintenant installé pour le domaine « {domain:s} »", "certmanager_cert_install_success": "Le certificat Let’s Encrypt est maintenant installé pour le domaine « {domain:s} »", - "certmanager_cert_renew_success": "Renouvellement avec succès d’un certificat Let’s Encrypt pour le domaine {domain:s} !", + "certmanager_cert_renew_success": "Certificat Let's Encrypt renouvelé pour le domaine '{domain: s}'", "certmanager_old_letsencrypt_app_detected": "\nYunoHost a détecté que l’application « letsencrypt » est installé, ce qui est en conflit avec les nouvelles fonctionnalités de gestion intégrée de certificats dans YunoHost. Si vous souhaitez utiliser ces nouvelles fonctionnalités intégrées, veuillez lancer les commandes suivantes pour migrer votre installation :\n\n yunohost app remove letsencrypt\n yunohost domain cert-install\n\nN.B. : cela tentera de réinstaller les certificats de tous les domaines avec un certificat Let's Encrypt ou ceux auto-signés", - "certmanager_cert_signing_failed": "La signature du nouveau certificat a échoué", + "certmanager_cert_signing_failed": "Impossible de signer le nouveau certificat", "certmanager_no_cert_file": "Impossible de lire le fichier du certificat pour le domaine {domain:s} (fichier : {file:s})", - "certmanager_conflicting_nginx_file": "Impossible de préparer le domaine pour le défi ACME : le fichier de configuration Nginx {filepath:s} est en conflit et doit être préalablement retiré", + "certmanager_conflicting_nginx_file": "Impossible de préparer le domaine pour le défi ACME : le fichier de configuration NGINX {filepath:s} est en conflit et doit être préalablement retiré", "certmanager_hit_rate_limit": "Trop de certificats ont déjà été émis récemment pour ce même ensemble de domaines {domain:s}. Veuillez réessayer plus tard. Lisez https://letsencrypt.org/docs/rate-limits/ pour obtenir plus de détails sur les ratios et limitations", "ldap_init_failed_to_create_admin": "L’initialisation de l'annuaire LDAP n’a pas réussi à créer l’utilisateur admin", - "ssowat_persistent_conf_read_error": "Erreur lors de la lecture de la configuration persistante de SSOwat : {error:s}. Modifiez le fichier /etc/ssowat/conf.json.persistent pour réparer la syntaxe JSON", - "ssowat_persistent_conf_write_error": "Erreur lors de la sauvegarde de la configuration persistante de SSOwat : {error:s}. Modifiez le fichier /etc/ssowat/conf.json.persistent pour réparer la syntaxe JSON", + "ssowat_persistent_conf_read_error": "Impossible de lire la configuration persistante de SSOwat : {error:s}. Modifiez le fichier /etc/ssowat/conf.json.persistent pour réparer la syntaxe JSON", + "ssowat_persistent_conf_write_error": "Impossible de sauvegarder de la configuration persistante de SSOwat : {error:s}. Modifiez le fichier /etc/ssowat/conf.json.persistent pour réparer la syntaxe JSON", "domain_cannot_remove_main": "Impossible de supprimer le domaine principal. Définissez d'abord un nouveau domaine principal", "certmanager_self_ca_conf_file_not_found": "Le fichier de configuration pour l’autorité du certificat auto-signé est introuvable (fichier : {file:s})", "certmanager_unable_to_parse_self_CA_name": "Impossible d’analyser le nom de l’autorité du certificat auto-signé (fichier : {file:s})", @@ -282,7 +282,7 @@ "certmanager_couldnt_fetch_intermediate_cert": "Expiration du délai lors de la tentative de récupération du certificat intermédiaire depuis Let’s Encrypt. L’installation ou le renouvellement du certificat a été annulé. Veuillez réessayer plus tard.", "appslist_retrieve_bad_format": "Impossible de lire la liste des applications extraites {appslist: s}", "domain_hostname_failed": "Échec de l’utilisation d’un nouveau nom d’hôte. Cela pourrait causer des soucis plus tard (peut-être que ça n’en causera pas).", - "yunohost_ca_creation_success": "L’autorité de certification locale a été créée.", + "yunohost_ca_creation_success": "L’autorité de certification locale créée.", "appslist_name_already_tracked": "Il y a déjà une liste d’applications enregistrée avec le nom {name:s} existe déjà.", "appslist_url_already_tracked": "Il y a déjà une liste d’applications enregistrée avec l’URL {url:s}.", "appslist_migrating": "Migration de la liste d’applications {appslist:s} …", @@ -350,7 +350,7 @@ "domain_dyndns_dynette_is_unreachable": "Impossible de contacter la dynette YunoHost. Soit YunoHost n’est pas correctement connecté à internet, soit le serveur de dynette est en panne. Erreur : {error}", "migrations_backward": "Migration en arrière.", "migrations_bad_value_for_target": "Nombre invalide pour le paramètre target, les numéros de migration sont 0 ou {}", - "migrations_cant_reach_migration_file": "Impossible d’accéder aux fichiers de migrations avec le chemin %s", + "migrations_cant_reach_migration_file": "Impossible d'accéder aux fichiers de migration sur le chemin% s", "migrations_current_target": "La cible de migration est {}", "migrations_error_failed_to_load_migration": "ERREUR : échec du chargement de migration {number} {name}", "migrations_forward": "Migration en avant", @@ -371,7 +371,7 @@ "app_make_default_location_already_used": "Impossible de configurer l’application '{app}' par défaut pour le domaine {domain} car il est déjà utilisé par l'application '{other_app}'", "app_upgrade_app_name": "Mise à jour de l’application {app} …", "backup_output_symlink_dir_broken": "Vous avez un lien symbolique cassé à la place de votre dossier d’archives « {path:s} ». Vous pourriez avoir une configuration personnalisée pour sauvegarder vos données sur un autre système de fichiers, dans ce cas vous avez probablement oublié de monter ou de connecter votre disque dur ou votre clé USB.", - "migrate_tsig_end": "La migration à hmac-sha512 est terminée", + "migrate_tsig_end": "La migration à HMAC-SHA-512 est terminée", "migrate_tsig_failed": "La migration du domaine DynDNS {domain} à hmac-sha512 a échoué. Annulation des modifications. Erreur : {error_code} - {error}", "migrate_tsig_start": "L’algorithme de génération des clefs n’est pas suffisamment sécurisé pour la signature TSIG du domaine '{domain}', lancement de la migration vers HMAC-SHA-512 qui est plus sécurisé", "migrate_tsig_wait": "Attendre trois minutes pour que le serveur DynDNS prenne en compte la nouvelle clef …", @@ -393,29 +393,29 @@ "migration_0003_not_jessie": "La distribution Debian actuelle n’est pas Jessie !", "migration_0003_system_not_fully_up_to_date": "Votre système n’est pas complètement à jour. Veuillez mener une mise à jour classique avant de lancer à migration à Stretch.", "migration_0003_still_on_jessie_after_main_upgrade": "Quelque chose s’est mal passé pendant la mise à niveau principale : le système est toujours sur Debian Jessie !? Pour investiguer sur le problème, veuillez regarder les journaux {log}:s …", - "migration_0003_general_warning": "Veuillez noter que cette migration est une opération délicate. Si l’équipe YunoHost a fait de son mieux pour la relire et la tester, la migration pourrait tout de même casser des parties de votre système ou de vos applications.\n\nEn conséquence, nous vous recommandons :\n - de lancer une sauvegarde de vos données ou applications critiques. Plus d’informations sur https://yunohost.org/backup ;\n - d’être patient après avoir lancé la migration : selon votre connexion internet et matériel, cela pourrait prendre jusqu’à quelques heures pour que tout soit à niveau.\n\nEn outre, le port SMTP utilisé par les clients de messagerie externes comme (Thunderbird ou K9-Mail) a été changé de 465 (SSL/TLS) à 587 (STARTTLS). L’ancien port 465 sera automatiquement fermé et le nouveau port 587 sera ouvert dans le pare-feu. Vous et vos utilisateurs *devront* adapter la configuration de vos clients de messagerie en conséquence !", + "migration_0003_general_warning": "Veuillez noter que cette migration est une opération délicate. Si l’équipe YunoHost a fait de son mieux pour la relire et la tester, la migration pourrait tout de même casser des parties de votre système ou de vos applications.\n\nEn conséquence, nous vous recommandons :\n - de lancer une sauvegarde de vos données ou applications critiques. Plus d’informations sur https://yunohost.org/backup ;\n - d’être patient après avoir lancé la migration : selon votre connexion internet et matériel, cela pourrait prendre jusqu’à quelques heures pour que tout soit à niveau.\n\nEn outre, le port SMTP utilisé par les clients de messagerie externes comme (Thunderbird ou K9-Mail) a été changé de 465 (SSL/TLS) à 587 (STARTTLS). L’ancien port 465 sera automatiquement fermé et le nouveau port 587 sera ouvert dans le pare-feu. Vous et vos utilisateurs *devront* adapter la configuration de vos clients de messagerie en conséquence.", "migration_0003_problematic_apps_warning": "Veuillez noter que des applications possiblement problématiques ont été détectées. Il semble qu’elles n’aient pas été installées depuis une liste d’application ou qu’elles ne soit pas marquées comme « fonctionnelles ». En conséquence, nous ne pouvons pas garantir qu’elles fonctionneront après la mise à niveau : {problematic_apps}", "migration_0003_modified_files": "Veuillez noter que les fichiers suivants ont été détectés comme modifiés manuellement et pourraient être écrasés à la fin de la mise à niveau : {manually_modified_files}", "migrations_list_conflict_pending_done": "Vous ne pouvez pas utiliser --previous et --done simultanément.", "migrations_to_be_ran_manually": "La migration {id} doit être lancée manuellement. Veuillez aller dans Outils > Migrations dans l’interface admin, ou lancer `yunohost tools migrations migrate`.", - "migrations_need_to_accept_disclaimer": "Pour lancer la migration {number} {name}, vous devez accepter cette clause de non-responsabilité :\n---\n{disclaimer}\n---\nSi vous acceptez de lancer la migration, veuillez relancer la commande avec l’option --accept-disclaimer.", - "service_description_avahi-daemon": "permet d’atteindre votre serveur via yunohost.local sur votre réseau local", + "migrations_need_to_accept_disclaimer": "Pour lancer la migration {id}, vous devez accepter cette clause de non-responsabilité :\n---\n{disclaimer}\n---\nSi vous acceptez de lancer la migration, veuillez relancer la commande avec l’option --accept-disclaimer.", + "service_description_avahi-daemon": "Vous permet d’atteindre votre serveur en utilisant «yunohost.local» sur votre réseau local", "service_description_dnsmasq": "Gère la résolution des noms de domaine (DNS)", - "service_description_dovecot": "permet aux clients de messagerie d’accéder/récupérer les courriels (via IMAP et POP3)", - "service_description_fail2ban": "protège contre les attaques brute-force et autres types d’attaques venant d’Internet", - "service_description_glances": "surveille les informations système de votre serveur", - "service_description_metronome": "gère les comptes de messagerie instantanée XMPP", - "service_description_mysql": "stocke les données des applications (bases de données SQL)", - "service_description_nginx": "sert ou permet l’accès à tous les sites web hébergés sur votre serveur", - "service_description_nslcd": "gère la connexion en ligne de commande des utilisateurs YunoHost", + "service_description_dovecot": "Permet aux clients de messagerie d’accéder/récupérer les courriels (via IMAP et POP3)", + "service_description_fail2ban": "Protège contre les attaques brute-force et autres types d’attaques venant d’Internet", + "service_description_glances": "Surveille les info système de votre serveur", + "service_description_metronome": "Gère les comptes de messagerie instantanée XMPP", + "service_description_mysql": "Stocke les données des applications (bases de données SQL)", + "service_description_nginx": "Sert ou permet l’accès à tous les sites web hébergés sur votre serveur", + "service_description_nslcd": "Gère la connexion en ligne de commande des utilisateurs YunoHost", "service_description_php5-fpm": "exécute des applications écrites en PHP avec nginx", - "service_description_postfix": "utilisé pour envoyer et recevoir des courriels", + "service_description_postfix": "Utilisé pour envoyer et recevoir des courriels", "service_description_redis-server": "Une base de données spécialisée utilisée pour l’accès rapide aux données, les files d’attentes et la communication entre les programmes", - "service_description_rmilter": "vérifie divers paramètres dans les courriels", - "service_description_rspamd": "filtre le pourriel, et d’autres fonctionnalités liées au courriel", - "service_description_slapd": "stocke les utilisateurs, domaines et leurs informations liées", - "service_description_ssh": "vous permet de vous connecter à distance à votre serveur via un terminal (protocole SSH)", - "service_description_yunohost-api": "permet les interactions entre l’interface web de YunoHost et le système", + "service_description_rmilter": "Vérifie divers paramètres dans les courriels", + "service_description_rspamd": "Filtre le pourriel, et d’autres fonctionnalités liées au courriel", + "service_description_slapd": "Stocke les utilisateurs, domaines et leurs informations liées", + "service_description_ssh": "Vous permet de vous connecter à distance à votre serveur via un terminal (protocole SSH)", + "service_description_yunohost-api": "Permet les interactions entre l’interface web de YunoHost et le système", "service_description_yunohost-firewall": "Gère l'ouverture et la fermeture des ports de connexion aux services", "experimental_feature": "Attention : cette fonctionnalité est expérimentale et ne doit pas être considérée comme stable, vous ne devriez pas l’utiliser à moins que vous ne sachiez ce que vous faites.", "log_corrupted_md_file": "Le fichier YAML de métadonnées associé aux logs est corrompu : '{md_file}'\nErreur : {error}", @@ -466,7 +466,7 @@ "migration_description_0005_postgresql_9p4_to_9p6": "Migration des bases de données de PostgreSQL 9.4 vers PostgreSQL 9.6", "migration_0005_postgresql_94_not_installed": "PostgreSQL n’a pas été installé sur votre système. Rien à faire !", "migration_0005_postgresql_96_not_installed": "PostgreSQL 9.4 a été trouvé et installé, mais pas PostgreSQL 9.6 !? Quelque chose d’étrange a dû arriver à votre système… :(", - "migration_0005_not_enough_space": "Il n’y a pas assez d’espace libre de disponible sur {path} pour lancer maintenant la migration :(.", + "migration_0005_not_enough_space": "Laissez suffisamment d'espace disponible dans {chemin} pour exécuter la migration.", "recommend_to_add_first_user": "La post-installation est terminée mais YunoHost a besoin d’au moins un utilisateur pour fonctionner correctement. Vous devez en ajouter un en utilisant la commande 'yunohost user create $nomdutilisateur' ou bien via l’interface d’administration web.", "service_description_php7.0-fpm": "Exécute des applications écrites en PHP avec NGINX", "users_available": "Liste des utilisateurs disponibles :", @@ -557,7 +557,7 @@ "tools_upgrade_special_packages": "Mise à jour des paquets 'spécifiques' (liés a YunoHost) …", "tools_upgrade_special_packages_completed": "La mise à jour des paquets de YunoHost est finie!\nPressez [Entrée] pour revenir à la ligne de commande", "updating_app_lists": "Récupération des mises à jour des applications disponibles…", - "dpkg_lock_not_available": "Cette commande ne peut être exécutée actuellement car un autre programme semble utiliser le verrou de dpkg (gestionnaire de paquets).", + "dpkg_lock_not_available": "Cette commande ne peut être exécutée actuellement car un autre programme semble utiliser le verrou de dpkg (gestionnaire de paquets)", "tools_upgrade_cant_unhold_critical_packages": "Impossible de dé-marquer les paquets critiques …", "tools_upgrade_special_packages_explanation": "Cette opération prendra fin mais la mise à jour spécifique continuera en arrière-plan. Veuillez ne pas lancer d'autre action sur votre serveur dans les 10 prochaines minutes (en fonction de la vitesse de votre matériel). Une fois que c'est fait, vous devrez peut-être vous reconnecter sur le panel d'administration web. Le journal de la mise à jour sera disponible dans Outils > Log (dans le panel d'administration web) ou dans la liste des journaux YunoHost (en ligne de commande).", "update_apt_cache_failed": "Impossible de mettre à jour le cache APT (gestionnaire de paquets Debian). Voici un extrait du fichier sources.list qui pourrait vous aider à identifier les lignes problématiques :\n{sourceslist}", @@ -591,5 +591,49 @@ "mailbox_disabled": "La boîte aux lettres est désactivée pour l'utilisateur {user:s}", "app_action_broke_system": "Cette action semble avoir cassé des services important : {services}", "apps_already_up_to_date": "Toutes les applications sont déjà à jour", - "app_upgrade_stopped": "La mise à jour de toutes les applications a été arrêtée afin d’éviter d’éventuels dommages dus à l’échec de la mise à jour de l’application précédente" + "app_upgrade_stopped": "La mise à jour de toutes les applications a été arrêtée afin d’éviter d’éventuels dommages dus à l’échec de la mise à jour de l’application précédente", + "migration_0011_create_group": "Créer un groupe pour chaque utilisateur…", + "migration_0011_done": "Migration réussie. Vous êtes maintenant en mesure de gérer des groupes d'utilisateurs.", + "migrations_must_provide_explicit_targets": "Vous devez fournir des cibles explicites lorsque vous utilisez '--skip' ou '--force-rerun'", + "migrations_no_such_migration": "Il n'y a pas de migration appelée {id}", + "migrations_pending_cant_rerun": "Ces migrations étant toujours en attente, vous ne pouvez pas les exécuter à nouveau: {ids}", + "migration_description_0012_postgresql_password_to_md5_authentication": "Forcer l'authentification PostgreSQL à utiliser MD5 pour les connexions locales", + "migrations_exclusive_options": "'auto', '--skip' et '--force-rerun' sont des options mutuellement exclusives.", + "migrations_not_pending_cant_skip": "Ces migrations ne sont pas en attente et ne peuvent donc pas être ignorées: {ids}", + "migration_0011_can_not_backup_before_migration": "Impossible de sauvegarder le système avant la migration. Erreur: {error: s}", + "migration_0011_migrate_permission": "Migration des autorisations des paramètres des applications vers LDAP…", + "migration_0011_migration_failed_trying_to_rollback": "La migration a échoué… essayait de restauration du système.", + "migration_0011_rollback_success": "Système restauré.", + "migration_0011_update_LDAP_database": "Mise à jour de la base de données LDAP…", + "system_groupname_exists": "Le nom de groupe existe déjà dans le groupe du systèmes", + "tools_update_failed_to_app_fetchlist": "Impossible de mettre à jour les applications de YunoHost car: {error}", + "user_already_in_group": "L'utilisateur '{user:}' est déjà dans le groupe '{group: s}'", + "user_not_in_group": "L'utilisateur '{user: s}' ne fait pas partie du groupe {group: s}", + "migration_0011_backup_before_migration": "Création d'une sauvegarde des paramètres de la base de données LDAP et des applications avant la migration.", + "permission_not_found": "Autorisation '{permission: s}' non trouvée pour l'application '{app: s}'", + "permission_name_not_valid": "Choisissez un nom d'autorisation autorisé pour '{permission: s}'", + "permission_update_failed": "Impossible de mettre à jour la permission", + "permission_generated": "Base de données des autorisations mise à jour", + "permission_updated": "Permission '{permission: s}' pour l'application '{app: s}' mise à jour", + "permission_update_nothing_to_do": "Aucune autorisation pour mettre à jour", + "remove_main_permission_not_allowed": "Supprimer l'autorisation principale n'est pas autorisé", + "dyndns_provider_unreachable": "Impossible d’atteindre le fournisseur Dyndns {provider}: votre YunoHost n’est pas correctement connecté à Internet ou le serveur Dynette est en panne.", + "migration_0011_update_LDAP_schema": "Mise à jour du schéma LDAP…", + "migrations_already_ran": "Ces migrations sont déjà effectuées: {ids}", + "migrations_dependencies_not_satisfied": "Impossible d'exécuter la migration {id} car vous devez d'abord exécuter ces migrations: {dependencies_id}", + "migrations_failed_to_load_migration": "Impossible de charger la migration {id}: {error}", + "migrations_running_forward": "Exécution de la migration {id}…", + "migrations_success_forward": "Migration {id} terminée", + "need_define_permission_before": "Redéfinissez l'autorisation à l'aide de 'yunohost user permission add -u USER' avant de supprimer un groupe autorisé", + "operation_interrupted": "L'opération a été interrompue manuellement", + "permission_already_clear": "L'autorisation '{permission: s}' est déjà vide pour l'application {app: s}", + "permission_already_exist": "L'autorisation '{permission: s}' pour l'application {app: s} existe déjà", + "permission_created": "Permission '{permission: s}' pour l'application {app: s} créée", + "permission_creation_failed": "Impossible d'accorder la permission", + "permission_deleted": "Permission '{permission: s}' pour app {app: s} supprimée", + "permission_deletion_failed": "Autorisation manquante '{permission: s}' pour supprimer l'application '{app: s}'", + "remove_user_of_group_not_allowed": "Vous n'êtes pas autorisé à supprimer l'utilisateur '{utilisateur: s}' dans le groupe '{groupe: s}'", + "migration_description_0011_setup_group_permission": "Configurer le groupe d'utilisateurs et configurer les autorisations pour les applications et les services", + "migration_0011_LDAP_config_dirty": "Il semble que vous ayez personnalisé votre configuration LDAP. Pour cette migration, la configuration LDAP doit être mise à jour.\nVous devez enregistrer votre configuration actuelle, réintialiser la configuration d'origine en exécutant 'yunohost tools regen-conf -f', puis réessayer la migration", + "migration_0011_LDAP_update_failed": "Impossible de mettre à jour LDAP. Erreur: {error: s}" } From 7533dbd6500f9a28fc95905ea458b4da7a7045cf Mon Sep 17 00:00:00 2001 From: amirale qt Date: Wed, 2 Oct 2019 11:49:23 +0000 Subject: [PATCH 0211/3170] Translated using Weblate (Esperanto) Currently translated at 24.2% (136 of 562 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/eo/ --- locales/eo.json | 157 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 126 insertions(+), 31 deletions(-) diff --git a/locales/eo.json b/locales/eo.json index 1a6a2ea9a..1d367260d 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -1,43 +1,138 @@ { - "admin_password_change_failed": "Malebla ŝanĝi pasvorton", - "admin_password_changed": "Pasvorto de la estro estas ŝanĝita", + "admin_password_change_failed": "Ne eblas ŝanĝi pasvorton", + "admin_password_changed": "La pasvorto de administrado ŝanĝiĝis", "app_already_installed": "{app:s} estas jam instalita", - "app_already_up_to_date": "{app:s} estas ĝisdata", + "app_already_up_to_date": "{app:s} estas jam ĝisdata", "app_argument_required": "Parametro {name:s} estas bezonata", "app_change_url_identical_domains": "Malnovaj kaj novaj domajno/URL estas la sama ('{domain:s}{path:s}'), nenio fareblas.", - "app_change_url_success": "URL de appo {app:s} ŝanĝita al {domain:s}{path:s}", - "app_extraction_failed": "Malebla malkompaktigi instaldosierojn", - "app_id_invalid": "Nevalida apo id", + "app_change_url_success": "{app:s} URL nun estas {domain:s} {path:s}", + "app_extraction_failed": "Ne povis ĉerpi la instalajn dosierojn", + "app_id_invalid": "Nevalida apo ID", "app_incompatible": "Apo {app} ne estas kongrua kun via YunoHost versio", - "app_install_files_invalid": "Nevalidaj instaldosieroj", - "app_location_already_used": "Apo {app} jam estas instalita al tiu loco ({path})", - "user_updated": "Uzanto estas ĝisdatita", + "app_install_files_invalid": "Ĉi tiuj dosieroj ne povas esti instalitaj", + "app_location_already_used": "La app '{app}' jam estas instalita en ({path})", + "user_updated": "Uzantinformoj ŝanĝis", "users_available": "Uzantoj disponeblaj :", "yunohost_already_installed": "YunoHost estas jam instalita", - "yunohost_ca_creation_failed": "Ne eblas krei atestan aŭtoritaton", - "yunohost_ca_creation_success": "Loka atesta aŭtoritato estas kreita.", + "yunohost_ca_creation_failed": "Ne povis krei atestan aŭtoritaton", + "yunohost_ca_creation_success": "Loka atestila aŭtoritato kreiĝis.", "yunohost_installing": "Instalante YunoHost…", - "service_description_glances": "monitoras sisteminformojn de via servilo", - "service_description_metronome": "mastrumas XMPP tujmesaĝilon kontojn", - "service_description_mysql": "stokas aplikaĵojn datojn (SQL datumbazo)", - "service_description_nginx": "servas aŭ permesas atingi ĉiujn retejojn gastigita sur via servilo", - "service_description_nslcd": "mastrumas Yunohost uzantojn konektojn per komanda linio", - "service_description_php7.0-fpm": "rulas aplikaĵojn skibita en PHP kun nginx", - "service_description_postfix": "uzita por sendi kaj ricevi retpoŝtojn", - "service_description_redis-server": "specialita datumbazo uzita por rapida datumo atingo, atendovicoj kaj komunikadoj inter programoj", - "service_description_rmilter": "kontrolas diversajn parametrojn en retpoŝtoj", - "service_description_rspamd": "filtras trudmesaĝojn, kaj aliaj funkcioj rilate al retpoŝto", - "service_description_slapd": "stokas uzantojn, domajnojn kaj rilatajn informojn", - "service_description_ssh": "permesas al vi konekti al via servilo kun fora terminalo (SSH protokolo)", - "service_description_yunohost-api": "mastrumas interagojn inter la YunoHost retinterfaco kaj la sistemo", - "service_description_yunohost-firewall": "mastrumas malfermitajn kaj fermitajn konektejojn al servoj", - "service_disable_failed": "Neebla malaktivigi servon '{service:s}'\n\nFreŝaj protokoloj de la servo : {logs:s}", - "service_disabled": "Servo '{service:s}' estas malaktivigita", + "service_description_glances": "Monitoras sistemajn informojn en via servilo", + "service_description_metronome": "Mastrumas XMPP tujmesaĝilon kontojn", + "service_description_mysql": "Stokas aplikaĵojn datojn (SQL datumbazo)", + "service_description_nginx": "Servas aŭ permesas atingi ĉiujn retejojn gastigita sur via servilo", + "service_description_nslcd": "Mastrumas Yunohost uzantojn konektojn per komanda linio", + "service_description_php7.0-fpm": "Rulas aplikaĵojn skibita en PHP kun nginx", + "service_description_postfix": "Uzita por sendi kaj ricevi retpoŝtojn", + "service_description_redis-server": "Specialita datumbazo uzita por rapida datumo atingo, atendovicoj kaj komunikadoj inter programoj", + "service_description_rmilter": "Kontrolas diversajn parametrojn en retpoŝtoj", + "service_description_rspamd": "Filtras trudmesaĝojn, kaj aliaj funkcioj rilate al retpoŝto", + "service_description_slapd": "Stokas uzantojn, domajnojn kaj rilatajn informojn", + "service_description_ssh": "Permesas al vi konekti al via servilo kun fora terminalo (SSH protokolo)", + "service_description_yunohost-api": "Mastrumas interagojn inter la YunoHost retinterfaco kaj la sistemo", + "service_description_yunohost-firewall": "Mastrumas malfermitajn kaj fermitajn konektejojn al servoj", + "service_disable_failed": "Ne povis malŝalti la servon '{service:s}'\n\nFreŝaj protokoloj de la servo : {logs:s}", + "service_disabled": "'{service: s}' servo malŝaltita", "action_invalid": "Nevalida ago « {action:s} »", "admin_password": "Pasvorto de la estro", "admin_password_too_long": "Bonvolu elekti pasvorton pli mallonga ol 127 signoj", - "already_up_to_date": "Neniu estas farenda! Ĉiu jam estas ĝisdata!", - "app_argument_choice_invalid": "Nevalida elekto por argumento « {name:s} », ĝi devas esti unu el {choices:s}", - "app_argument_invalid": "Nevalida valoro por argumento « {name:s} » : {error:s}", - "app_change_url_failed_nginx_reload": "Reŝargi nginx malsuksesis. Jen la eligo de « nginx -t » :\n{nginx_errors:s}" + "already_up_to_date": "Nenio por fari. Ĉio estas jam ĝisdatigita.", + "app_argument_choice_invalid": "Uzu unu el ĉi tiuj elektoj '{choices:s}' por la argumento '{name:s}'", + "app_argument_invalid": "Elektu validan valoron por la argumento '{name:s}': {error:s}", + "app_change_url_failed_nginx_reload": "Ne eblis reŝarĝi NGINX. Jen la eligo de 'nginx -t':\n{nginx_errors:s}", + "appslist_url_already_tracked": "Estas jam registrita aplika listo kun la URL {url:s}.", + "ask_new_admin_password": "Nova administrada pasvorto", + "app_action_broke_system": "Ĉi tiu ago ŝajne rompis ĉi tiujn gravajn servojn: {services}", + "app_unsupported_remote_type": "Malkontrolita fora speco uzita por la apliko", + "backup_archive_system_part_not_available": "Sistemo parto '{part:s}' ne haveblas en ĉi tiu rezervo", + "apps_permission_not_found": "Neniu permeso trovita por la instalitaj programoj", + "apps_permission_restoration_failed": "Donu la rajtigan permeson '{permission:s}' por restarigi {app:s}", + "backup_abstract_method": "Ĉi tiu rezerva metodo ankoraŭ efektiviĝis", + "apps_already_up_to_date": "Ĉiuj aplikoj estas jam ĝisdatigitaj", + "backup_borg_not_implemented": "La kopia metodo de Borg ankoraŭ ne estas efektivigita", + "app_upgrade_stopped": "La ĝisdatigo de ĉiuj aplikoj estis ĉesigita por eviti eblajn damaĝojn ĉar la antaŭa apliko ne sukcesis ĝisdatigi", + "app_location_unavailable": "Ĉi tiu URL aŭ ne haveblas, aŭ konfliktas kun la jam instalita (j) apliko (j):\n{apps:s}", + "backup_archive_app_not_found": "Ne povis trovi la programon '{app:s}' en la rezerva ar archiveivo", + "backup_actually_backuping": "Krei rezervan ar archiveivon el la kolektitaj dosieroj …", + "backup_method_borg_finished": "Sekurkopio en Borg finiĝis", + "appslist_removed": "{appslist:s} aplika listo forigita", + "app_change_url_no_script": "Ĉi tiu apliko '{app_name:s}' ankoraŭ ne subtenas URL-modifon. Eble vi devus altgradigi ĝin.", + "app_start_install": "Instalanta aplikon {app} …", + "backup_created": "Sekurkopio kreita", + "app_make_default_location_already_used": "Ne povas igi la aplikon '{app}' defaŭlta sur la domajno, {domain} jam uziĝas de la alia app '{other_app}'", + "backup_method_copy_finished": "Rezerva kopio finis", + "app_not_properly_removed": "{app:s} ne estis ĝuste forigita", + "backup_archive_broken_link": "Ne povis aliri la rezervan ar archiveivon (rompita ligilo al {path:s})", + "app_requirements_checking": "Kontrolante postulatajn pakaĵojn por {app} …", + "app_not_installed": "Ne povis trovi la aplikon '{app:s}' en la listo de instalitaj programoj: {all_apps}", + "app_location_install_failed": "Ne eblas instali la aplikon tie ĉar ĝi konfliktas kun la '{other_app}' jam instalita en '{other_path}'", + "ask_new_path": "Nova vojo", + "backup_custom_mount_error": "Propra rezerva metodo ne povis preterpasi la paŝon 'monto'", + "app_upgrade_app_name": "Nun ĝisdatiganta {app} …", + "app_manifest_invalid": "Io misas pri la aplika manifesto: {error}", + "backup_cleaning_failed": "Ne povis purigi la provizoran rezervan dosierujon", + "backup_invalid_archive": "Ĉi tio ne estas rezerva ar archiveivo", + "ask_current_admin_password": "Pasvorto pri aktuala administrado", + "backup_creation_failed": "Ne povis krei la rezervan ar archiveivon", + "backup_hook_unknown": "La rezerva hoko '{hoko:s}' estas nekonata", + "backup_custom_backup_error": "Propra rezerva metodo ne povis preterpasi la paŝon \"sekurkopio\"", + "ask_main_domain": "Ĉefa domajno", + "backup_method_tar_finished": "TAR-rezerva ar archiveivo kreita", + "appslist_unknown": "Aplika listo {appslist:s} nekonata.", + "ask_list_to_remove": "Listo por forigi", + "backup_cant_mount_uncompress_archive": "Ne povis munti la nekompresitan ar archiveivon kiel protektita kontraŭ skribo", + "appslist_retrieve_bad_format": "Ne povis legi la elprenitan liston {appslist:s}", + "appslist_corrupted_json": "Ne povis ŝarĝi la aplikajn listojn. Ĝi aspektas kiel {filename:s} estas damaĝita.", + "app_action_cannot_be_ran_because_required_services_down": "Ĉi tiu app postulas iujn servojn, kiuj nuntempe malleviĝas. Antaŭ ol daŭrigi, vi provu rekomenci la jenajn servojn (kaj eventuale esploru kial ili malsukcesas): {services}", + "backup_copying_to_organize_the_archive": "Kopiante {size:s} MB por organizi la ar archiveivon", + "backup_output_directory_forbidden": "Elektu malsaman elirejan dosierujon. Sekurkopioj ne povas esti kreitaj en sub-dosierujoj / bin, / boot, / dev, / ktp, / lib, / root, / run, / sbin, / sys, / usr, / var aŭ /home/yunohost.backup/archives", + "appslist_could_not_migrate": "Ne povis migri la liston de aplikoj {appslist:s}! Ne eblis analizi la URL ... La malnova cron-laboro konserviĝis en {bkp_file:s}.", + "app_requirements_failed": "Certaines exigences ne sont pas remplies pour {app}: {error}", + "backup_no_uncompress_archive_dir": "Ne ekzistas tia nekompremita arkiva dosierujo", + "password_too_simple_1": "Pasvorto devas esti almenaŭ 8 signojn longa", + "app_upgrade_failed": "Ne povis ĝisdatigi {app:s}", + "app_upgrade_several_apps": "La sekvaj apliko estos altgradigitaj: {apps}", + "backup_archive_open_failed": "Ne povis malfermi la rezervan ar archiveivon", + "ask_lastname": "Familia nomo", + "app_start_backup": "Kolekti dosierojn por esti subtenata por {app} …", + "backup_archive_name_exists": "Rezerva arkivo kun ĉi tiu nomo jam ekzistas.", + "backup_applying_method_tar": "Krei la rezervan TAR-ar archiveivon …", + "backup_method_custom_finished": "Propra rezerva metodo '{metodo:s}' finiĝis", + "appslist_retrieve_error": "Ne eblas retrovi la forajn aplikajn listojn {appslist:s}: {eraro:s}", + "app_already_installed_cant_change_url": "Ĉi tiu app estas jam instalita. La URL ne povas esti ŝanĝita nur per ĉi tiu funkcio. Rigardu \"app changeurl\" se ĝi haveblas.", + "app_not_correctly_installed": "{app:s} ŝajnas esti malĝuste instalita", + "app_removed": "{app:s} forigita", + "backup_delete_error": "Ne povis forigi '{path: s}'", + "app_package_need_update": "La pakaĵo {app} devas esti ĝisdatigita por sekvi YunoHost-ŝanĝojn", + "backup_nothings_done": "Nenio por ŝpari", + "backup_applying_method_custom": "Nomante la kutiman rezervan metodon '{metodo:s}' …", + "appslist_fetched": "Ĝisdatigita aplika listo {appslist:s} elprenita", + "backup_app_failed": "Ne eblis rezervi la programon '{app:s}'", + "app_upgrade_some_app_failed": "Iuj aplikoj ne povis esti altgradigitaj", + "app_start_remove": "Forigo de apliko {app} …", + "backup_output_directory_not_empty": "Vi devas elekti malplenan eligitan dosierujon", + "backup_archive_writing_error": "Ne povis aldoni la dosierojn '{source:s}' (nomitaj en la ar theivo '{dest:s}') por esti rezervitaj en la kunpremita arkivo '{archive:s}'", + "ask_email": "Retpoŝta adreso", + "app_start_restore": "Restarigi aplikon {app} …", + "backup_applying_method_copy": "Kopiante ĉiujn dosierojn al sekurkopio …", + "backup_couldnt_bind": "Ne povis ligi {src:s} al {dest:s}.", + "ask_password": "Pasvorto", + "app_requirements_unmeet": "Postuloj ne estas renkontitaj por {app}, la pakaĵo {pkgname} ({version}) devas esti {spec}", + "ask_firstname": "Antaŭnomo", + "backup_ask_for_copying_if_needed": "Iuj dosieroj ne povus esti pretigitaj por sekurkopio uzante la metodon, kiu evitas portempe malŝpari spacon en la sistemo. Por plenumi la sekurkopion, {size:s} MB estos provizore. Ĉu vi konsentas?", + "backup_mount_archive_for_restore": "Preparante arkivon por restarigo …", + "appslist_migrating": "Migra aplika listo {appslist:s} …", + "backup_csv_creation_failed": "Ne povis krei la CSV-dosieron bezonatan por restarigo", + "backup_archive_name_unknown": "Nekonata loka rezerva ar archiveivo nomata '{name:s}'", + "backup_applying_method_borg": "Sendado de ĉiuj dosieroj al sekurkopio en borg-rezerva deponejo …", + "app_sources_fetch_failed": "Ne povis akiri fontajn dosierojn, ĉu la URL estas ĝusta?", + "appslist_name_already_tracked": "Registrita aplika listo kun nomo {name:s} jam ekzistas.", + "ask_new_domain": "Nova domajno", + "app_unknown": "Nekonata apliko", + "app_not_upgraded": "La aplikaĵo '{failed_app}' ne ĝisdatigis, kaj pro tio la sekvaj ĝisdatigoj de aplikoj estis nuligitaj: {apps}", + "aborting": "Aborti.", + "ask_path": "Pado", + "app_upgraded": "{app:s} altgradigita", + "backup_deleted": "Rezerva forigita", + "backup_csv_addition_failed": "Ne povis aldoni dosierojn al sekurkopio en la CSV-dosiero" } From 3bc4945ccf12c1bbc0c47a5c609f8ab1baf161c4 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Wed, 11 Sep 2019 04:29:04 +0200 Subject: [PATCH 0212/3170] [ux] 'new-domain' argument of maindomain command was confusing --- data/actionsmap/yunohost.yml | 2 +- src/yunohost/tools.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 22037f05f..e18a45276 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1547,7 +1547,7 @@ tools: - PUT /domains/main arguments: -n: - full: --new-domain + full: --new-main-domain help: Change the current main domain extra: pattern: *pattern_domain diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 64689fe0c..e4d4c4274 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -165,7 +165,7 @@ def tools_adminpw(new_password, check_strength=True): @is_unit_operation() -def tools_maindomain(operation_logger, new_domain=None): +def tools_maindomain(operation_logger, new_main_domain=None): """ Check the current main domain, or change it From 877cfc1fe550a305a624ba258bf0d30469afab10 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Wed, 11 Sep 2019 04:31:22 +0200 Subject: [PATCH 0213/3170] [ux] move 'maindomain' command from 'tools' to 'domain' section --- data/actionsmap/yunohost.yml | 14 +++++++++ src/yunohost/domain.py | 58 ++++++++++++++++++++++++++++++++++++ src/yunohost/tools.py | 56 ++-------------------------------- 3 files changed, 75 insertions(+), 53 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index e18a45276..b7b4eaf88 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -441,6 +441,19 @@ domain: - !!str ^[0-9]+$ - "pattern_positive_number" + ### domain_maindomain() + maindomain: + action_help: Check the current main domain, or change it + api: + - GET /domains/main + - PUT /domains/main + arguments: + -n: + full: --new-main-domain + help: Change the current main domain + extra: + pattern: *pattern_domain + ### certificate_status() cert-status: action_help: List status of current certificates (all by default). @@ -1542,6 +1555,7 @@ tools: ### tools_maindomain() maindomain: action_help: Check the current main domain, or change it + deprecated: true api: - GET /domains/main - PUT /domains/main diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 3f906748b..8529433ee 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -34,10 +34,12 @@ from moulinette.utils.log import getActionLogger import yunohost.certificate +from yunohost.app import app_ssowatconf from yunohost.regenconf import regen_conf from yunohost.utils.network import get_public_ip from yunohost.log import is_unit_operation from yunohost.hook import hook_callback +from yunohost.tools import _set_hostname logger = getActionLogger('yunohost.domain') @@ -233,6 +235,62 @@ def domain_dns_conf(domain, ttl=None): return result +@is_unit_operation() +def domain_maindomain(operation_logger, new_main_domain=None): + """ + Check the current main domain, or change it + + Keyword argument: + new_main_domain -- The new domain to be set as the main domain + + """ + + # If no new domain specified, we return the current main domain + if not new_main_domain: + return {'current_main_domain': _get_maindomain()} + + # Check domain exists + if new_main_domain not in domain_list()['domains']: + raise YunohostError('domain_unknown') + + operation_logger.related_to.append(('domain', new_main_domain)) + operation_logger.start() + + # Apply changes to ssl certs + ssl_key = "/etc/ssl/private/yunohost_key.pem" + ssl_crt = "/etc/ssl/private/yunohost_crt.pem" + new_ssl_key = "/etc/yunohost/certs/%s/key.pem" % new_main_domain + new_ssl_crt = "/etc/yunohost/certs/%s/crt.pem" % new_main_domain + + try: + if os.path.exists(ssl_key) or os.path.lexists(ssl_key): + os.remove(ssl_key) + if os.path.exists(ssl_crt) or os.path.lexists(ssl_crt): + os.remove(ssl_crt) + + os.symlink(new_ssl_key, ssl_key) + os.symlink(new_ssl_crt, ssl_crt) + + _set_maindomain(new_main_domain) + except Exception as e: + logger.warning("%s" % e, exc_info=1) + raise YunohostError('maindomain_change_failed') + + _set_hostname(new_main_domain) + + # Generate SSOwat configuration file + app_ssowatconf() + + # Regen configurations + try: + with open('/etc/yunohost/installed', 'r'): + regen_conf() + except IOError: + pass + + logger.success(m18n.n('maindomain_changed')) + + def domain_cert_status(domain_list, full=False): return yunohost.certificate.certificate_status(domain_list, full) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index e4d4c4274..5feb04dec 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -39,7 +39,7 @@ from moulinette.utils.log import getActionLogger from moulinette.utils.process import check_output, call_async_output from moulinette.utils.filesystem import read_json, write_to_json, read_yaml, write_to_yaml from yunohost.app import app_fetchlist, app_info, app_upgrade, app_ssowatconf, app_list, _install_appslist_fetch_cron -from yunohost.domain import domain_add, domain_list, _get_maindomain, _set_maindomain +from yunohost.domain import domain_add, domain_list from yunohost.dyndns import _dyndns_available, _dyndns_provides from yunohost.firewall import firewall_upnp from yunohost.service import service_status, service_start, service_enable @@ -166,58 +166,8 @@ def tools_adminpw(new_password, check_strength=True): @is_unit_operation() def tools_maindomain(operation_logger, new_main_domain=None): - """ - Check the current main domain, or change it - - Keyword argument: - new_domain -- The new domain to be set as the main domain - - """ - - # If no new domain specified, we return the current main domain - if not new_domain: - return {'current_main_domain': _get_maindomain()} - - # Check domain exists - if new_domain not in domain_list()['domains']: - raise YunohostError('domain_unknown') - - operation_logger.related_to.append(('domain', new_domain)) - operation_logger.start() - - # Apply changes to ssl certs - ssl_key = "/etc/ssl/private/yunohost_key.pem" - ssl_crt = "/etc/ssl/private/yunohost_crt.pem" - new_ssl_key = "/etc/yunohost/certs/%s/key.pem" % new_domain - new_ssl_crt = "/etc/yunohost/certs/%s/crt.pem" % new_domain - - try: - if os.path.exists(ssl_key) or os.path.lexists(ssl_key): - os.remove(ssl_key) - if os.path.exists(ssl_crt) or os.path.lexists(ssl_crt): - os.remove(ssl_crt) - - os.symlink(new_ssl_key, ssl_key) - os.symlink(new_ssl_crt, ssl_crt) - - _set_maindomain(new_domain) - except Exception as e: - logger.warning("%s" % e, exc_info=1) - raise YunohostError('maindomain_change_failed') - - _set_hostname(new_domain) - - # Generate SSOwat configuration file - app_ssowatconf() - - # Regen configurations - try: - with open('/etc/yunohost/installed', 'r'): - regen_conf() - except IOError: - pass - - logger.success(m18n.n('maindomain_changed')) + from yunohost.domain import domain_maindomain + return domain_main_domain(new_main_domain=new_main_domain) def _set_hostname(hostname, pretty_hostname=None): From f732085d3fb0ada7bc628a034dc5ed5a30774e1d Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Wed, 11 Sep 2019 04:48:53 +0200 Subject: [PATCH 0214/3170] [ux] rename 'yunohost domain maindomain' to 'yunohost domain main-domain' --- data/actionsmap/yunohost.yml | 4 +++- src/yunohost/domain.py | 2 +- src/yunohost/tools.py | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index b7b4eaf88..76500ac13 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -442,8 +442,10 @@ domain: - "pattern_positive_number" ### domain_maindomain() - maindomain: + main-domain: action_help: Check the current main domain, or change it + deprecated_alias: + - maindomain api: - GET /domains/main - PUT /domains/main diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 8529433ee..bed8e6883 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -236,7 +236,7 @@ def domain_dns_conf(domain, ttl=None): @is_unit_operation() -def domain_maindomain(operation_logger, new_main_domain=None): +def domain_main_domain(operation_logger, new_main_domain=None): """ Check the current main domain, or change it diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 5feb04dec..e33ef8e82 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -166,7 +166,8 @@ def tools_adminpw(new_password, check_strength=True): @is_unit_operation() def tools_maindomain(operation_logger, new_main_domain=None): - from yunohost.domain import domain_maindomain + from yunohost.domain import domain_main_domain + logger.warning(m18n.g("deprecated_command_alias", prog="yunohost", old="tools maindomain", new="domain main-domain")) return domain_main_domain(new_main_domain=new_main_domain) From 1584c907005332405c9f92811297efa658360d61 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Wed, 11 Sep 2019 04:50:50 +0200 Subject: [PATCH 0215/3170] [ux] more useful deprecated warning that mention the new command --- data/actionsmap/yunohost.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 76500ac13..0fbca6efc 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1557,7 +1557,6 @@ tools: ### tools_maindomain() maindomain: action_help: Check the current main domain, or change it - deprecated: true api: - GET /domains/main - PUT /domains/main From 94ba47d171784a4ff755015cfc48c8a5343533c7 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Wed, 11 Sep 2019 05:11:33 +0200 Subject: [PATCH 0216/3170] [ux] better error messages when trying to remove the main domain --- locales/en.json | 7 ++++--- src/yunohost/domain.py | 9 ++++++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/locales/en.json b/locales/en.json index 98cbf22e9..3964c17af 100644 --- a/locales/en.json +++ b/locales/en.json @@ -154,12 +154,13 @@ "diagnosis_no_apps": "No installed application", "dpkg_is_broken": "You cannot do this right now because dpkg/APT (the system package managers) seems to be in a broken state… You can try to solve this issue by connecting through SSH and running `sudo dpkg --configure -a`.", "dpkg_lock_not_available": "This command can't be ran right now because another program seems to be using the lock of dpkg (the system package manager)", - "domain_cannot_remove_main": "Cannot remove main domain. Set one first", + "domain_cannot_remove_main": "You cannot remove '{domain:s}' since it's the main domain, you need first to set another domain as the main domain using 'yunohost domain main-domain -n ', here is the list of candidate domains: {other_domains:s}", + "domain_cannot_remove_main_add_new_one": "You cannot remove '{domain:s}' since it's the main domain and your only domain, you need to first add another domain using 'yunohost domain add ', then set is as the main domain using 'yunohost domain main-domain -n ' and then you can remove the domain '{domain:s}' using 'yunohost domain remove {domain:s}'.'", "domain_cert_gen_failed": "Could not generate certificate", "domain_created": "Domain created", - "domain_creation_failed": "Could not create domain {domain}: {error}", + "domain_creation_failed": "Unable to create domain {domain}: {error}", "domain_deleted": "Domain deleted", - "domain_deletion_failed": "Could not delete domain {domain}: {error}", + "domain_deletion_failed": "Unable to delete domain {domain}: {error}", "domain_dns_conf_is_just_a_recommendation": "This command shows you the *recommended* configuration. It does not actually set up the DNS configuration for you. It is your responsability to configure your DNS zone in your registrar according to this recommendation.", "domain_dyndns_already_subscribed": "You have already subscribed to a DynDNS domain", "domain_dyndns_root_unknown": "Unknown DynDNS root domain", diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index bed8e6883..f26a19e8d 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -156,7 +156,14 @@ def domain_remove(operation_logger, domain, force=False): # Check domain is not the main domain if domain == _get_maindomain(): - raise YunohostError('domain_cannot_remove_main') + other_domains = domain_list()["domains"] + other_domains.remove(domain) + + if other_domains: + raise YunohostError('domain_cannot_remove_main', + domain=domain, other_domains="\n * " + ("\n * ".join(other_domains))) + else: + raise YunohostError('domain_cannot_remove_main_add_new_one', domain=domain) # Check if apps are installed on the domain for app in os.listdir('/etc/yunohost/apps/'): From 01ad8ec9643220fd835b3c20e39d14176f2c1770 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sat, 14 Sep 2019 17:06:20 +0200 Subject: [PATCH 0217/3170] [mod] remove now useless decorator --- src/yunohost/tools.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index e33ef8e82..bae17e15a 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -164,8 +164,7 @@ def tools_adminpw(new_password, check_strength=True): logger.success(m18n.n('admin_password_changed')) -@is_unit_operation() -def tools_maindomain(operation_logger, new_main_domain=None): +def tools_maindomain(new_main_domain=None): from yunohost.domain import domain_main_domain logger.warning(m18n.g("deprecated_command_alias", prog="yunohost", old="tools maindomain", new="domain main-domain")) return domain_main_domain(new_main_domain=new_main_domain) From 1cfe32f6f31918f92c095c2d6b400a82b9a16133 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sat, 14 Sep 2019 17:19:09 +0200 Subject: [PATCH 0218/3170] [fix] circular import --- 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 f26a19e8d..64c2d9927 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -39,7 +39,6 @@ from yunohost.regenconf import regen_conf from yunohost.utils.network import get_public_ip from yunohost.log import is_unit_operation from yunohost.hook import hook_callback -from yunohost.tools import _set_hostname logger = getActionLogger('yunohost.domain') @@ -251,6 +250,7 @@ def domain_main_domain(operation_logger, new_main_domain=None): new_main_domain -- The new domain to be set as the main domain """ + from yunohost.tools import _set_hostname # If no new domain specified, we return the current main domain if not new_main_domain: From 6eb4b3f89e76223241491019e55fd7a68b53d40a Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 15 Sep 2019 03:11:52 +0200 Subject: [PATCH 0219/3170] [mod] use renamed domain_main_domain function in postinstall --- src/yunohost/tools.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index bae17e15a..fe75bcf61 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -231,6 +231,7 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False, """ from yunohost.utils.password import assert_password_is_strong_enough + from yunohost.domain import domain_main_domain dyndns_provider = "dyndns.yunohost.org" @@ -353,7 +354,7 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False, # New domain config regen_conf(['nsswitch'], force=True) domain_add(domain, dyndns) - tools_maindomain(domain) + domain_main_domain(domain) # Change LDAP admin password tools_adminpw(password, check_strength=not force_password) From f18252d82e6a932dc4d86494d6048668152a5cf5 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 15 Sep 2019 03:39:04 +0200 Subject: [PATCH 0220/3170] [i18n] change translation key to match new function name --- locales/ar.json | 6 +++--- locales/ca.json | 6 +++--- locales/de.json | 4 ++-- locales/en.json | 6 +++--- locales/es.json | 4 ++-- locales/fr.json | 6 +++--- locales/it.json | 6 +++--- locales/oc.json | 6 +++--- locales/pt.json | 4 ++-- src/yunohost/domain.py | 4 ++-- 10 files changed, 26 insertions(+), 26 deletions(-) diff --git a/locales/ar.json b/locales/ar.json index 46f9315af..fba086bc4 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -211,8 +211,8 @@ "mail_domain_unknown": "Unknown mail address domain '{domain:s}'", "mail_forward_remove_failed": "Unable to remove mail forward '{mail:s}'", "mailbox_used_space_dovecot_down": "Dovecot mailbox service need to be up, if you want to get mailbox used space", - "maindomain_change_failed": "Unable to change the main domain", - "maindomain_changed": "The main domain has been changed", + "main_domain_change_failed": "Unable to change the main domain", + "main_domain_changed": "The main domain has been changed", "migrate_tsig_end": "Migration to hmac-sha512 finished", "migrate_tsig_failed": "Migrating the dyndns domain {domain} to hmac-sha512 failed, rolling back. Error: {error_code} - {error}", "migrate_tsig_start": "Not secure enough key algorithm detected for TSIG signature of domain '{domain}', initiating migration to the more secure one hmac-sha512", @@ -404,7 +404,7 @@ "log_user_create": "إضافة المستخدم '{}'", "log_user_delete": "حذف المستخدم '{}'", "log_user_update": "تحديث معلومات المستخدم '{}'", - "log_tools_maindomain": "جعل '{}' كنطاق أساسي", + "log_domain_main_domain": "جعل '{}' كنطاق أساسي", "log_tools_upgrade": "تحديث حُزم ديبيان", "log_tools_shutdown": "إطفاء الخادم", "log_tools_reboot": "إعادة تشغيل الخادم", diff --git a/locales/ca.json b/locales/ca.json index f5c040670..6ec3c0890 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -271,7 +271,7 @@ "log_user_create": "Afegeix l'usuari « {} »", "log_user_delete": "Elimina l'usuari « {} »", "log_user_update": "Actualitza la informació de l'usuari « {} »", - "log_tools_maindomain": "Fes de « {} » el domini principal", + "log_domain_main_domain": "Fes de « {} » el domini principal", "log_tools_migrations_migrate_forward": "Migrar", "log_tools_migrations_migrate_backward": "Migrar endarrera", "log_tools_postinstall": "Fer la post instal·lació del servidor YunoHost", @@ -289,8 +289,8 @@ "mail_forward_remove_failed": "No s'han pogut eliminar el reenviament de correu «{mail:s}»", "mailbox_used_space_dovecot_down": "S'ha d'engegar el servei de correu Dovecot per poder obtenir l'espai utilitzat per la bústia de correu", "mail_unavailable": "Aquesta adreça de correu esta reservada i ha de ser atribuïda automàticament el primer usuari", - "maindomain_change_failed": "No s'ha pogut canviar el domini principal", - "maindomain_changed": "S'ha canviat el domini principal", + "main_domain_change_failed": "No s'ha pogut canviar el domini principal", + "main_domain_changed": "S'ha canviat el domini principal", "migrate_tsig_end": "La migració cap a hmac-sha512 s'ha acabat", "migrate_tsig_failed": "Ha fallat la migració del domini dyndns {domain} cap a hmac-sha512, anul·lant les modificacions. Error: {error_code} - {error}", "migrate_tsig_start": "L'algoritme de generació de claus no es prou segur per a la signatura TSIG del domini «{domain}», començant la migració cap a un de més segur hmac-sha512", diff --git a/locales/de.json b/locales/de.json index d03226187..dc9ea70f1 100644 --- a/locales/de.json +++ b/locales/de.json @@ -104,8 +104,8 @@ "mail_alias_remove_failed": "E-Mail Alias '{mail:s}' konnte nicht entfernt werden", "mail_domain_unknown": "Unbekannte Mail Domain '{domain:s}'", "mail_forward_remove_failed": "Mailweiterleitung '{mail:s}' konnte nicht entfernt werden", - "maindomain_change_failed": "Die Hauptdomain konnte nicht geändert werden", - "maindomain_changed": "Die Hauptdomain wurde geändert", + "main_domain_change_failed": "Die Hauptdomain konnte nicht geändert werden", + "main_domain_changed": "Die Hauptdomain wurde geändert", "monitor_disabled": "Das Servermonitoring wurde erfolgreich deaktiviert", "monitor_enabled": "Das Servermonitoring wurde aktiviert", "monitor_glances_con_failed": "Verbindung mit Glances nicht möglich", diff --git a/locales/en.json b/locales/en.json index 3964c17af..c5c468703 100644 --- a/locales/en.json +++ b/locales/en.json @@ -277,7 +277,7 @@ "log_user_update": "Update user info of '{}'", "log_user_permission_update": "Update accesses for permission '{}'", "log_user_permission_reset": "Reset permission '{}'", - "log_tools_maindomain": "Make '{}' the main domain", + "log_domain_main_domain": "Make '{}' as main domain", "log_tools_migrations_migrate_forward": "Migrate forward", "log_tools_postinstall": "Postinstall your YunoHost server", "log_tools_upgrade": "Upgrade system packages", @@ -292,8 +292,8 @@ "mailbox_disabled": "E-mail turned off for user {user:s}", "mailbox_used_space_dovecot_down": "The Dovecot mailbox service needs to be up, if you want to fetch used mailbox space", "mail_unavailable": "This e-mail address is reserved and shall be automatically allocated to the very first user", - "maindomain_change_failed": "Could not change the main domain", - "maindomain_changed": "The main domain now changed", + "main_domain_change_failed": "Unable to change the main domain", + "main_domain_changed": "The main domain has been changed", "migrate_tsig_end": "Migration to HMAC-SHA-512 finished", "migrate_tsig_failed": "Could not migrate the DynDNS domain '{domain}' to HMAC-SHA-512, rolling back. Error: {error_code}, {error}", "migrate_tsig_start": "Insufficiently secure key algorithm detected for TSIG signature of the domain '{domain}', initiating migration to the more secure HMAC-SHA-512", diff --git a/locales/es.json b/locales/es.json index 02f46652b..a2dadc31c 100644 --- a/locales/es.json +++ b/locales/es.json @@ -121,8 +121,8 @@ "mail_alias_remove_failed": "No se pudo eliminar el alias de correo «{mail:s}»", "mail_domain_unknown": "Dirección de correo desconocida para el dominio «{domain:s}»", "mail_forward_remove_failed": "No se pudo eliminar el reenvío de correo «{mail:s}»", - "maindomain_change_failed": "No se pudo cambiar el dominio principal", - "maindomain_changed": "El dominio principal ha cambiado", + "main_domain_change_failed": "No se pudo cambiar el dominio principal", + "main_domain_changed": "El dominio principal ha cambiado", "monitor_disabled": "Desactivada la monitorización del servidor", "monitor_enabled": "Activada la monitorización del servidor", "monitor_glances_con_failed": "No se pudo conectar al servidor de Glances", diff --git a/locales/fr.json b/locales/fr.json index 8bffec8b2..b09dd54b7 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -122,8 +122,8 @@ "mail_alias_remove_failed": "Impossible de supprimer l’alias de courriel '{mail:s}'", "mail_domain_unknown": "Le domaine '{domain:s}' pour l'adresse de courriel est inconnu", "mail_forward_remove_failed": "Impossible de supprimer le courriel de transfert '{mail:s}'", - "maindomain_change_failed": "Impossible de modifier le domaine principal", - "maindomain_changed": "Le domaine principal modifié", + "main_domain_change_failed": "Impossible de modifier le domaine principal", + "main_domain_changed": "Le domaine principal a été modifié", "monitor_disabled": "La supervision du serveur a été désactivé", "monitor_enabled": "La supervision du serveur a été activé", "monitor_glances_con_failed": "Impossible de se connecter au serveur Glances", @@ -454,7 +454,7 @@ "log_user_create": "Ajouter l’utilisateur '{}'", "log_user_delete": "Supprimer l’utilisateur '{}'", "log_user_update": "Mettre à jour les informations de l’utilisateur '{}'", - "log_tools_maindomain": "Faire de '{}' le domaine principal", + "log_domain_main_domain": "Faire de '{}' le domaine principal", "log_tools_migrations_migrate_forward": "Migrer vers", "log_tools_migrations_migrate_backward": "Revenir en arrière", "log_tools_postinstall": "Faire la post-installation de votre serveur YunoHost", diff --git a/locales/it.json b/locales/it.json index 2c194d5a6..22cf9e2b0 100644 --- a/locales/it.json +++ b/locales/it.json @@ -136,8 +136,8 @@ "mail_domain_unknown": "Dominio d'indirizzo mail '{domain:s}' sconosciuto", "mail_forward_remove_failed": "Impossibile rimuovere la mail inoltrata '{mail:s}'", "mailbox_used_space_dovecot_down": "Il servizio di posta elettronica Dovecot deve essere attivato se vuoi riportare lo spazio usato dalla posta elettronica", - "maindomain_change_failed": "Impossibile cambiare il dominio principale", - "maindomain_changed": "Il dominio principale è stato cambiato", + "main_domain_change_failed": "Impossibile cambiare il dominio principale", + "main_domain_changed": "Il dominio principale è stato cambiato", "monitor_disabled": "Il monitoraggio del sistema è stato disattivato", "monitor_enabled": "Il monitoraggio del sistema è stato attivato", "monitor_glances_con_failed": "Impossibile collegarsi al server Glances", @@ -402,7 +402,7 @@ "log_user_create": "Aggiungi l'utente '{}'", "log_user_delete": "Elimina l'utente '{}'", "log_user_update": "Aggiornate le informazioni dell'utente '{}'", - "log_tools_maindomain": "Rendi '{}' dominio principale", + "log_domain_main_domain": "Rendi '{}' dominio principale", "log_tools_migrations_migrate_forward": "Migra avanti", "log_tools_migrations_migrate_backward": "Migra indietro", "log_tools_postinstall": "Postinstallazione del tuo server YunoHost", diff --git a/locales/oc.json b/locales/oc.json index 320a18341..49063e829 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -180,8 +180,8 @@ "invalid_url_format": "Format d’URL pas valid", "ldap_initialized": "L’annuari LDAP es inicializat", "license_undefined": "indefinida", - "maindomain_change_failed": "Modificacion impossibla del domeni màger", - "maindomain_changed": "Lo domeni màger es estat modificat", + "main_domain_change_failed": "Modificacion impossibla del domeni màger", + "main_domain_changed": "Lo domeni màger es estat modificat", "migrate_tsig_end": "La migracion cap a hmac-sha512 es acabada", "migrate_tsig_wait_2": "2 minutas…", "migrate_tsig_wait_3": "1 minuta…", @@ -440,7 +440,7 @@ "log_user_create": "Ajustar l’utilizaire « {} »", "log_user_delete": "Levar l’utilizaire « {} »", "log_user_update": "Actualizar las informacions a l’utilizaire « {} »", - "log_tools_maindomain": "Far venir « {} » lo domeni màger", + "log_domain_main_domain": "Far venir « {} » lo domeni màger", "log_tools_migrations_migrate_forward": "Migrar", "log_tools_migrations_migrate_backward": "Tornar en arrièr", "log_tools_postinstall": "Realizar la post installacion del servidor YunoHost", diff --git a/locales/pt.json b/locales/pt.json index 80a0d5ddd..c0ff0284e 100644 --- a/locales/pt.json +++ b/locales/pt.json @@ -74,8 +74,8 @@ "mail_alias_remove_failed": "Não foi possível remover a etiqueta de correio '{mail:s}'", "mail_domain_unknown": "Domínio de endereço de correio desconhecido '{domain:s}'", "mail_forward_remove_failed": "Não foi possível remover o reencaminhamento de correio '{mail:s}'", - "maindomain_change_failed": "Incapaz alterar o domínio raiz", - "maindomain_changed": "Domínio raiz alterado com êxito", + "main_domain_change_failed": "Incapaz alterar o domínio raiz", + "main_domain_changed": "Domínio raiz alterado com êxito", "monitor_disabled": "Monitorização do servidor parada com êxito", "monitor_enabled": "Monitorização do servidor ativada com êxito", "monitor_glances_con_failed": "Não foi possível ligar ao servidor Glances", diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 64c2d9927..8f8a68812 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -281,7 +281,7 @@ def domain_main_domain(operation_logger, new_main_domain=None): _set_maindomain(new_main_domain) except Exception as e: logger.warning("%s" % e, exc_info=1) - raise YunohostError('maindomain_change_failed') + raise YunohostError('main_domain_change_failed') _set_hostname(new_main_domain) @@ -295,7 +295,7 @@ def domain_main_domain(operation_logger, new_main_domain=None): except IOError: pass - logger.success(m18n.n('maindomain_changed')) + logger.success(m18n.n('main_domain_changed')) def domain_cert_status(domain_list, full=False): From f93e2b307904d1ac5c6dd86c4bf52cc22fda48d2 Mon Sep 17 00:00:00 2001 From: Julien Jershon Date: Sat, 5 Oct 2019 09:55:03 +0200 Subject: [PATCH 0221/3170] Better error message for invalid email domain. Fix https://github.com/YunoHost/issues/issues/1365. --- locales/en.json | 2 +- locales/fr.json | 2 +- locales/pt.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/locales/en.json b/locales/en.json index 98cbf22e9..2069ee6a6 100644 --- a/locales/en.json +++ b/locales/en.json @@ -286,7 +286,7 @@ "ldap_initialized": "LDAP initialized", "license_undefined": "undefined", "mail_alias_remove_failed": "Could not remove e-mail alias '{mail:s}'", - "mail_domain_unknown": "Unknown e-mail address for domain '{domain:s}'", + "mail_domain_unknown": "Invalid e-mail address for domain '{domain:s}'. Please, use a domain administrated by this server.", "mail_forward_remove_failed": "Could not remove e-mail forwarding '{mail:s}'", "mailbox_disabled": "E-mail turned off for user {user:s}", "mailbox_used_space_dovecot_down": "The Dovecot mailbox service needs to be up, if you want to fetch used mailbox space", diff --git a/locales/fr.json b/locales/fr.json index 8bffec8b2..7a2d299f8 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -120,7 +120,7 @@ "ldap_initialized": "L’annuaire LDAP initialisé", "license_undefined": "indéfinie", "mail_alias_remove_failed": "Impossible de supprimer l’alias de courriel '{mail:s}'", - "mail_domain_unknown": "Le domaine '{domain:s}' pour l'adresse de courriel est inconnu", + "mail_domain_unknown": "Le domaine '{domain:s}' de cette adress de courriel n'est pas valide. Merci d'utiliser un domain administré par ce serveur.", "mail_forward_remove_failed": "Impossible de supprimer le courriel de transfert '{mail:s}'", "maindomain_change_failed": "Impossible de modifier le domaine principal", "maindomain_changed": "Le domaine principal modifié", diff --git a/locales/pt.json b/locales/pt.json index 80a0d5ddd..b8c9d2eb3 100644 --- a/locales/pt.json +++ b/locales/pt.json @@ -72,7 +72,7 @@ "ldap_initialized": "LDAP inicializada com êxito", "license_undefined": "indefinido", "mail_alias_remove_failed": "Não foi possível remover a etiqueta de correio '{mail:s}'", - "mail_domain_unknown": "Domínio de endereço de correio desconhecido '{domain:s}'", + "mail_domain_unknown": "Domínio de endereço de correio '{domain:s}' inválido. Por favor, usa um domínio administrado per esse servidor.", "mail_forward_remove_failed": "Não foi possível remover o reencaminhamento de correio '{mail:s}'", "maindomain_change_failed": "Incapaz alterar o domínio raiz", "maindomain_changed": "Domínio raiz alterado com êxito", From a1822e2f42aa1a7ff516ff76ea5dee1def233a20 Mon Sep 17 00:00:00 2001 From: Luke Murphy Date: Sun, 6 Oct 2019 11:25:01 +0200 Subject: [PATCH 0222/3170] Use str instead of strerror (not present) See https://forum.yunohost.org/t/cant-create-a-user-after-post-intsallation/9190. --- src/yunohost/user.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index c6413d7e1..fe27492f4 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -199,7 +199,7 @@ def user_create(operation_logger, username, firstname, lastname, mail, password, with open('/etc/ssowat/conf.json.persistent') as json_conf: ssowat_conf = json.loads(str(json_conf.read())) except ValueError as e: - raise YunohostError('ssowat_persistent_conf_read_error', error=e.strerror) + raise YunohostError('ssowat_persistent_conf_read_error', error=str(e)) except IOError: ssowat_conf = {} @@ -209,7 +209,7 @@ def user_create(operation_logger, username, firstname, lastname, mail, password, with open('/etc/ssowat/conf.json.persistent', 'w+') as f: json.dump(ssowat_conf, f, sort_keys=True, indent=4) except IOError as e: - raise YunohostError('ssowat_persistent_conf_write_error', error=e.strerror) + raise YunohostError('ssowat_persistent_conf_write_error', error=str(e)) try: ldap.add('uid=%s,ou=users' % username, attr_dict) From 2642b64af5bccb6f93a8612a42365edd68e9b118 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 16 Sep 2019 23:17:46 +0200 Subject: [PATCH 0223/3170] Detect and warn early about unavailable full domain requirement... --- locales/en.json | 1 + src/yunohost/app.py | 37 +++++++++++++++++++++++++++++++------ 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/locales/en.json b/locales/en.json index 61fdcfa9b..4e9675cac 100644 --- a/locales/en.json +++ b/locales/en.json @@ -19,6 +19,7 @@ "app_change_url_no_script": "This application '{app_name:s}' doesn't support URL modification yet. Maybe you should upgrade it.", "app_change_url_success": "{app:s} URL is now {domain:s}{path:s}", "app_extraction_failed": "Could not extract the installation files", + "app_full_domain_unavailable": "Sorry, this application requires a full domain to be installed on, but some other apps are already installed on {domain}. One possible solution is to add a subdomain dedicated to this application.", "app_id_invalid": "Invalid app ID", "app_incompatible": "The app {app} is incompatible with your YunoHost version", "app_install_files_invalid": "These files cannot be installed", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 5a51e57bb..6f5c1dabe 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -847,6 +847,9 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu args_list = [ value[0] for value in args_odict.values() ] args_list.append(app_instance_name) + # Validate domain / path availability for webapps + _validate_and_normalize_webpath(manifest, args_odict, extracted_app_folder) + # Prepare env. var. to pass to script env_dict = _make_environment_dict(args_odict) env_dict["YNH_APP_ID"] = app_id @@ -2547,8 +2550,7 @@ def _parse_args_for_action(action, args={}): 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, - _get_conflicting_apps, _normalize_domain_path) + from yunohost.domain import domain_list, _get_maindomain from yunohost.user import user_info, user_list args_dict = OrderedDict() @@ -2666,13 +2668,18 @@ def _parse_args_in_yunohost_format(args, action_args): assert_password_is_strong_enough('user', arg_value) args_dict[arg_name] = (arg_value, arg_type) - # END loop over action_args... + return args_dict + + +def _validate_and_normalize_webpath(manifest, args_dict, app_folder): + + from yunohost.domain import _get_conflicting_apps, _normalize_domain_path # If there's only one "domain" and "path", validate that domain/path # is an available url and normalize the path. - domain_args = [ (name, value[0]) for name, value in args_dict.items() if value[1] == "domain" ] - path_args = [ (name, value[0]) for name, value in args_dict.items() if value[1] == "path" ] + domain_args = [(name, value[0]) for name, value in args_dict.items() if value[1] == "domain"] + path_args = [(name, value[0]) for name, value in args_dict.items() if value[1] == "path"] if len(domain_args) == 1 and len(path_args) == 1: @@ -2698,7 +2705,25 @@ def _parse_args_in_yunohost_format(args, action_args): # standard path format to deal with no matter what the user inputted) args_dict[path_args[0][0]] = (path, "path") - return args_dict + # This is likely to be a full-domain app... + elif len(domain_args) == 1 and len(path_args) == 0: + + # Confirm that this is a full-domain app This should cover most cases + # ... though anyway the proper solution is to implement some mechanism + # in the manifest for app to declare that they require a full domain + # (among other thing) so that we can dynamically check/display this + # requirement on the webadmin form and not miserably fail at submit time + + # Full-domain apps typically declare something like path_url="/" or path=/ + # and use ynh_webpath_register or yunohost_app_checkurl inside the install script + install_script_content = open(os.path.join(app_folder, 'scripts/install')).read() + if re.search(r"\npath(_url)?=[\"']?/[\"']?\n", install_script_content) \ + and re.search(r"(ynh_webpath_register|yunohost app checkurl)"): + + domain = domain_args[0][1] + conflicts = _get_conflicting_apps(domain, "/") + + raise YunohostError('app_full_domain_unavailable', domain) def _make_environment_dict(args_dict, prefix="APP_ARG_"): From 0d90133bb7a0181621824477a855f01256885ae6 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 8 Oct 2019 18:18:27 +0200 Subject: [PATCH 0224/3170] Improve message --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 4e9675cac..1be7d1151 100644 --- a/locales/en.json +++ b/locales/en.json @@ -19,7 +19,7 @@ "app_change_url_no_script": "This application '{app_name:s}' doesn't support URL modification yet. Maybe you should upgrade it.", "app_change_url_success": "{app:s} URL is now {domain:s}{path:s}", "app_extraction_failed": "Could not extract the installation files", - "app_full_domain_unavailable": "Sorry, this application requires a full domain to be installed on, but some other apps are already installed on {domain}. One possible solution is to add a subdomain dedicated to this application.", + "app_full_domain_unavailable": "Sorry, this application requires a full domain to be installed on, but some other apps are already installed on domain '{domain}'. One possible solution is to add and use a subdomain dedicated to this application instead.", "app_id_invalid": "Invalid app ID", "app_incompatible": "The app {app} is incompatible with your YunoHost version", "app_install_files_invalid": "These files cannot be installed", From 342fe2d4be0a1300dddf0e747906cb4e16b9b091 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 8 Oct 2019 18:19:50 +0200 Subject: [PATCH 0225/3170] Add unit test for full-domain apps --- src/yunohost/tests/test_apps.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/yunohost/tests/test_apps.py b/src/yunohost/tests/test_apps.py index 9c85df1e9..fc44ef105 100644 --- a/src/yunohost/tests/test_apps.py +++ b/src/yunohost/tests/test_apps.py @@ -36,16 +36,22 @@ def clean(): if _is_installed("legacy_app"): app_remove("legacy_app") + if _is_installed("full_domain_app"): + app_remove("full_domain_app") + to_remove = [] to_remove += glob.glob("/etc/nginx/conf.d/*.d/*legacy*") + to_remove += glob.glob("/etc/nginx/conf.d/*.d/*full_domain*") to_remove += glob.glob("/etc/nginx/conf.d/*.d/*break_yo_system*") for filepath in to_remove: os.remove(filepath) to_remove = [] to_remove += glob.glob("/etc/yunohost/apps/*legacy_app*") + to_remove += glob.glob("/etc/yunohost/apps/*full_domain_app*") to_remove += glob.glob("/etc/yunohost/apps/*break_yo_system*") to_remove += glob.glob("/var/www/*legacy*") + to_remove += glob.glob("/var/www/*full_domain*") for folderpath in to_remove: shutil.rmtree(folderpath, ignore_errors=True) @@ -120,6 +126,13 @@ def install_legacy_app(domain, path): force=True) +def install_full_domain_app(domain): + + app_install("./tests/apps/full_domain_app_ynh", + args="domain=%s" % domain, + force=True) + + def install_break_yo_system(domain, breakwhat): app_install("./tests/apps/break_yo_system_ynh", @@ -272,6 +285,22 @@ def test_legacy_app_failed_remove(secondary_domain): assert app_is_not_installed(secondary_domain, "legacy") +def test_full_domain_app(secondary_domain): + + install_full_domain_app(secondary_domain) + + assert app_is_exposed_on_http(secondary_domain, "/", "This is a dummy app") + + +def test_full_domain_app_with_conflicts(secondary_domain): + + install_legacy_app(secondary_domain, "/legacy") + + # TODO : once #808 is merged, add test that the message raised is 'app_full_domain_unavailable' + with pytest.raises(YunohostError): + install_full_domain_app(secondary_domain) + + def test_systemfuckedup_during_app_install(secondary_domain): with pytest.raises(YunohostError): From c70418c4b25952f9308b69d88e47b8a2490781c1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 8 Oct 2019 18:21:04 +0200 Subject: [PATCH 0226/3170] Fixes following tests --- src/yunohost/app.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 6f5c1dabe..8596f7297 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -2717,13 +2717,14 @@ def _validate_and_normalize_webpath(manifest, args_dict, app_folder): # Full-domain apps typically declare something like path_url="/" or path=/ # and use ynh_webpath_register or yunohost_app_checkurl inside the install script install_script_content = open(os.path.join(app_folder, 'scripts/install')).read() + if re.search(r"\npath(_url)?=[\"']?/[\"']?\n", install_script_content) \ - and re.search(r"(ynh_webpath_register|yunohost app checkurl)"): + and re.search(r"(ynh_webpath_register|yunohost app checkurl)", install_script_content): domain = domain_args[0][1] conflicts = _get_conflicting_apps(domain, "/") - raise YunohostError('app_full_domain_unavailable', domain) + raise YunohostError('app_full_domain_unavailable', domain=domain) def _make_environment_dict(args_dict, prefix="APP_ARG_"): From fc787009041069f7ffd83b1d7f8467a8d98e1c44 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 8 Oct 2019 18:30:18 +0200 Subject: [PATCH 0227/3170] More accurate greps to identify that sury packages are installed --- data/helpers.d/apt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index f5590b38d..d84520daf 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -226,8 +226,8 @@ ynh_install_app_dependencies () { # If we require to install php dependency if echo $dependencies | grep -q 'php'; then - # And we have packages from sury installed (7.0.33-10+weirdshiftafter instead of 7.0.33+1 on debian) - if dpkg --list | grep php | grep -q "7.0.33-10" + # And we have packages from sury installed (7.0.33-10+weirdshiftafter instead of 7.0.33-0 on debian) + if dpkg --list | grep "php7.0" | grep -q -v "7.0.33-0+deb9u5" then # And sury ain't already installed if ! grep -nrq "sury" /etc/apt/sources.list* From 077e5c463c8c4d6befc28d9c0c79bdc21ed2b3cb Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 24 Sep 2019 23:18:05 +0200 Subject: [PATCH 0228/3170] Fucking ugly workaround for the goddamn dependency nighmare from sury djeezus kraiste --- data/helpers.d/apt | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index 9d5ad3ac2..fed585d95 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -218,6 +218,27 @@ ynh_install_app_dependencies () { fi local dep_app=${app//_/-} # Replace all '_' by '-' + # + # Epic ugly hack to fix the goddamn dependency nightmare of sury + # Sponsored by the "Djeezusse Fokin Kraiste Why Do Adminsys Has To Be So Fucking Complicated I Should Go Grow Potatoes Instead Of This Shit" collective + # https://github.com/YunoHost/issues/issues/1407 + # + # If we require to install php dependency + if echo $dependencies | grep -q 'php'; + then + # And we have packages from sury installed (7.0.33-10+weirdshiftafter instead of 7.0.33+1 on debian) + if dpkg --list | grep php | grep -q "7.0.33-10" + then + # And sury ain't already installed + if ! grep -nrq "sury" /etc/apt/sources.list* + then + # Re-add sury + echo "deb https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/sury.list + wget -O /etc/apt/trusted.gpg.d/sury.gpg https://packages.sury.org/php/apt.gpg + fi + fi + fi + cat > /tmp/${dep_app}-ynh-deps.control << EOF # Make a control file for equivs-build Section: misc Priority: optional From 0e3a131095afe4893d10629271c1ce6c6b177624 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 8 Oct 2019 18:30:18 +0200 Subject: [PATCH 0229/3170] More accurate greps to identify that sury packages are installed --- data/helpers.d/apt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index fed585d95..d772c6855 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -226,8 +226,8 @@ ynh_install_app_dependencies () { # If we require to install php dependency if echo $dependencies | grep -q 'php'; then - # And we have packages from sury installed (7.0.33-10+weirdshiftafter instead of 7.0.33+1 on debian) - if dpkg --list | grep php | grep -q "7.0.33-10" + # And we have packages from sury installed (7.0.33-10+weirdshiftafter instead of 7.0.33-0 on debian) + if dpkg --list | grep "php7.0" | grep -q -v "7.0.33-0+deb9u5" then # And sury ain't already installed if ! grep -nrq "sury" /etc/apt/sources.list* From 1c5220f7cbe029b4cf03011cb451426d755697f8 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 26 Sep 2019 14:23:01 +0200 Subject: [PATCH 0230/3170] Support logfiles not ending with .log in logrotate ... --- data/helpers.d/logrotate | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/data/helpers.d/logrotate b/data/helpers.d/logrotate index 47ce46cf6..82cdee6a5 100644 --- a/data/helpers.d/logrotate +++ b/data/helpers.d/logrotate @@ -40,10 +40,13 @@ ynh_use_logrotate () { fi if [ $# -gt 0 ] && [ "$(echo ${1:0:1})" != "-" ]; then - if [ "$(echo ${1##*.})" == "log" ]; then # Keep only the extension to check if it's a logfile - local logfile=$1 # In this case, focus logrotate on the logfile + # If the given logfile parameter already exists as a file, or if it ends up with ".log", + # we just want to manage a single file + if [ -f "$1" ] || [ "$(echo ${1##*.})" == "log" ]; then + local logfile=$1 + # Otherwise we assume we want to manage a directory and all its .log file inside else - local logfile=$1/*.log # Else, uses the directory and all logfile into it. + local logfile=$1/*.log fi fi # LEGACY CODE @@ -54,7 +57,7 @@ ynh_use_logrotate () { fi if [ -n "$logfile" ] then - if [ "$(echo ${logfile##*.})" != "log" ]; then # Keep only the extension to check if it's a logfile + if [ ! -f "$1" ] && [ "$(echo ${logfile##*.})" != "log" ]; then # Keep only the extension to check if it's a logfile local logfile="$logfile/*.log" # Else, uses the directory and all logfile into it. fi else From 5623689a2728a875880dd41b614fffd818a0597d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 16 Sep 2019 23:17:46 +0200 Subject: [PATCH 0231/3170] Detect and warn early about unavailable full domain requirement... --- locales/en.json | 1 + src/yunohost/app.py | 37 +++++++++++++++++++++++++++++++------ 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/locales/en.json b/locales/en.json index d1203c757..55735d760 100644 --- a/locales/en.json +++ b/locales/en.json @@ -19,6 +19,7 @@ "app_change_url_no_script": "This application '{app_name:s}' doesn't support url modification yet. Maybe you should upgrade the application.", "app_change_url_success": "Successfully changed {app:s} url to {domain:s}{path:s}", "app_extraction_failed": "Unable to extract installation files", + "app_full_domain_unavailable": "Sorry, this application requires a full domain to be installed on, but some other apps are already installed on {domain}. One possible solution is to add a subdomain dedicated to this application.", "app_id_invalid": "Invalid app id", "app_incompatible": "The app {app} is incompatible with your YunoHost version", "app_install_files_invalid": "Invalid installation files", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index d9a349579..c982c3418 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -802,6 +802,9 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu args_list = [ value[0] for value in args_odict.values() ] args_list.append(app_instance_name) + # Validate domain / path availability for webapps + _validate_and_normalize_webpath(manifest, args_odict, extracted_app_folder) + # Prepare env. var. to pass to script env_dict = _make_environment_dict(args_odict) env_dict["YNH_APP_ID"] = app_id @@ -2394,8 +2397,7 @@ def _parse_args_for_action(action, args={}): 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, - _get_conflicting_apps, _normalize_domain_path) + from yunohost.domain import domain_list, _get_maindomain from yunohost.user import user_info, user_list args_dict = OrderedDict() @@ -2513,13 +2515,18 @@ def _parse_args_in_yunohost_format(args, action_args): assert_password_is_strong_enough('user', arg_value) args_dict[arg_name] = (arg_value, arg_type) - # END loop over action_args... + return args_dict + + +def _validate_and_normalize_webpath(manifest, args_dict, app_folder): + + from yunohost.domain import _get_conflicting_apps, _normalize_domain_path # If there's only one "domain" and "path", validate that domain/path # is an available url and normalize the path. - domain_args = [ (name, value[0]) for name, value in args_dict.items() if value[1] == "domain" ] - path_args = [ (name, value[0]) for name, value in args_dict.items() if value[1] == "path" ] + domain_args = [(name, value[0]) for name, value in args_dict.items() if value[1] == "domain"] + path_args = [(name, value[0]) for name, value in args_dict.items() if value[1] == "path"] if len(domain_args) == 1 and len(path_args) == 1: @@ -2545,7 +2552,25 @@ def _parse_args_in_yunohost_format(args, action_args): # standard path format to deal with no matter what the user inputted) args_dict[path_args[0][0]] = (path, "path") - return args_dict + # This is likely to be a full-domain app... + elif len(domain_args) == 1 and len(path_args) == 0: + + # Confirm that this is a full-domain app This should cover most cases + # ... though anyway the proper solution is to implement some mechanism + # in the manifest for app to declare that they require a full domain + # (among other thing) so that we can dynamically check/display this + # requirement on the webadmin form and not miserably fail at submit time + + # Full-domain apps typically declare something like path_url="/" or path=/ + # and use ynh_webpath_register or yunohost_app_checkurl inside the install script + install_script_content = open(os.path.join(app_folder, 'scripts/install')).read() + if re.search(r"\npath(_url)?=[\"']?/[\"']?\n", install_script_content) \ + and re.search(r"(ynh_webpath_register|yunohost app checkurl)"): + + domain = domain_args[0][1] + conflicts = _get_conflicting_apps(domain, "/") + + raise YunohostError('app_full_domain_unavailable', domain) def _make_environment_dict(args_dict, prefix="APP_ARG_"): From 75742216ea93e45c6679310fa9a02724775dd838 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 8 Oct 2019 18:18:27 +0200 Subject: [PATCH 0232/3170] Improve message --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 55735d760..4bb049db3 100644 --- a/locales/en.json +++ b/locales/en.json @@ -19,7 +19,7 @@ "app_change_url_no_script": "This application '{app_name:s}' doesn't support url modification yet. Maybe you should upgrade the application.", "app_change_url_success": "Successfully changed {app:s} url to {domain:s}{path:s}", "app_extraction_failed": "Unable to extract installation files", - "app_full_domain_unavailable": "Sorry, this application requires a full domain to be installed on, but some other apps are already installed on {domain}. One possible solution is to add a subdomain dedicated to this application.", + "app_full_domain_unavailable": "Sorry, this application requires a full domain to be installed on, but some other apps are already installed on domain '{domain}'. One possible solution is to add and use a subdomain dedicated to this application instead.", "app_id_invalid": "Invalid app id", "app_incompatible": "The app {app} is incompatible with your YunoHost version", "app_install_files_invalid": "Invalid installation files", From 7ecefaf8dc78cc0d4ddb1f0fabc5eab6ff2bb176 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 8 Oct 2019 18:21:04 +0200 Subject: [PATCH 0233/3170] Fixes following tests --- src/yunohost/app.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index c982c3418..421be9b60 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -2564,13 +2564,14 @@ def _validate_and_normalize_webpath(manifest, args_dict, app_folder): # Full-domain apps typically declare something like path_url="/" or path=/ # and use ynh_webpath_register or yunohost_app_checkurl inside the install script install_script_content = open(os.path.join(app_folder, 'scripts/install')).read() + if re.search(r"\npath(_url)?=[\"']?/[\"']?\n", install_script_content) \ - and re.search(r"(ynh_webpath_register|yunohost app checkurl)"): + and re.search(r"(ynh_webpath_register|yunohost app checkurl)", install_script_content): domain = domain_args[0][1] conflicts = _get_conflicting_apps(domain, "/") - raise YunohostError('app_full_domain_unavailable', domain) + raise YunohostError('app_full_domain_unavailable', domain=domain) def _make_environment_dict(args_dict, prefix="APP_ARG_"): From bf1ad164dafc996c0bf9d1a3b19de7dc2d089003 Mon Sep 17 00:00:00 2001 From: "J. Doe" Date: Thu, 19 Sep 2019 13:01:22 +0200 Subject: [PATCH 0234/3170] change maxretry of fail2ban from 6 to 10 --- data/templates/fail2ban/yunohost-jails.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/templates/fail2ban/yunohost-jails.conf b/data/templates/fail2ban/yunohost-jails.conf index bf3bcb6e3..fdbd7990b 100644 --- a/data/templates/fail2ban/yunohost-jails.conf +++ b/data/templates/fail2ban/yunohost-jails.conf @@ -29,4 +29,4 @@ protocol = tcp filter = yunohost logpath = /var/log/nginx/*error.log /var/log/nginx/*access.log -maxretry = 6 +maxretry = 10 From 115513c6503de63a7f970d1f2dae216839b7f46d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 8 Oct 2019 19:03:32 +0200 Subject: [PATCH 0235/3170] Update changelog for 3.6.5 --- debian/changelog | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/debian/changelog b/debian/changelog index 3eb347456..4b8c26471 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,12 @@ +yunohost (3.6.5) stable; urgency=low + + - [enh] Detect and warn early about unavailable full domains... (#798) + - [mod] Change maxretry of fail2ban from 6 to 10 (#802) + - [fix] Epicly ugly workaround for the goddamn dependency nighmare about sury fucking up php7.0 dependencies (#809) + - [fix] Support logfiles not ending with .log in logrotate ... (#810) + + -- Alexandre Aubin Mon, 08 Oct 2019 19:00:00 +0000 + yunohost (3.6.4.6) stable; urgency=low - [fix] Hopefully fix the issue about corrupted logs metadata files (d507d447, 1cec9d78) From fe8fd1b2c58993211e016fecb74d6fc026482bd1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 8 Oct 2019 20:04:08 +0200 Subject: [PATCH 0236/3170] Change from #802 was only about the yunohost jail ... this should be global >.> --- data/templates/fail2ban/jail.conf | 2 +- data/templates/fail2ban/yunohost-jails.conf | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/data/templates/fail2ban/jail.conf b/data/templates/fail2ban/jail.conf index 9b4d39f17..bd522c4ba 100644 --- a/data/templates/fail2ban/jail.conf +++ b/data/templates/fail2ban/jail.conf @@ -63,7 +63,7 @@ bantime = 600 findtime = 600 # "maxretry" is the number of failures before a host get banned. -maxretry = 5 +maxretry = 10 # "backend" specifies the backend used to get files modification. # Available options are "pyinotify", "gamin", "polling", "systemd" and "auto". diff --git a/data/templates/fail2ban/yunohost-jails.conf b/data/templates/fail2ban/yunohost-jails.conf index fdbd7990b..e1e464b1a 100644 --- a/data/templates/fail2ban/yunohost-jails.conf +++ b/data/templates/fail2ban/yunohost-jails.conf @@ -29,4 +29,3 @@ protocol = tcp filter = yunohost logpath = /var/log/nginx/*error.log /var/log/nginx/*access.log -maxretry = 10 From 826429cf0b5c8dc72cb2c3898c42cf0f20971cb4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 8 Oct 2019 20:04:08 +0200 Subject: [PATCH 0237/3170] Change from #802 was only about the yunohost jail ... this should be global >.> --- data/templates/fail2ban/jail.conf | 2 +- data/templates/fail2ban/yunohost-jails.conf | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/data/templates/fail2ban/jail.conf b/data/templates/fail2ban/jail.conf index 9b4d39f17..bd522c4ba 100644 --- a/data/templates/fail2ban/jail.conf +++ b/data/templates/fail2ban/jail.conf @@ -63,7 +63,7 @@ bantime = 600 findtime = 600 # "maxretry" is the number of failures before a host get banned. -maxretry = 5 +maxretry = 10 # "backend" specifies the backend used to get files modification. # Available options are "pyinotify", "gamin", "polling", "systemd" and "auto". diff --git a/data/templates/fail2ban/yunohost-jails.conf b/data/templates/fail2ban/yunohost-jails.conf index fdbd7990b..e1e464b1a 100644 --- a/data/templates/fail2ban/yunohost-jails.conf +++ b/data/templates/fail2ban/yunohost-jails.conf @@ -29,4 +29,3 @@ protocol = tcp filter = yunohost logpath = /var/log/nginx/*error.log /var/log/nginx/*access.log -maxretry = 10 From c45b0edd39e47d66201c9b9223923abfd93f3a58 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 8 Oct 2019 20:21:11 +0200 Subject: [PATCH 0238/3170] Update changelog for 3.6.5.1 --- debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian/changelog b/debian/changelog index 4b8c26471..6d5a16f2c 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +yunohost (3.6.5.1) stable; urgency=low + + - [mod] Change maxretry of fail2ban from 6 to 10 (fe8fd1b) + + -- Alexandre Aubin Mon, 08 Oct 2019 20:00:00 +0000 + yunohost (3.6.5) stable; urgency=low - [enh] Detect and warn early about unavailable full domains... (#798) From 4a14cbd6e0bc092c7bf3ed94f994d9330dc1c1a0 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 9 Oct 2019 18:42:17 +0200 Subject: [PATCH 0239/3170] Fix / implement remaining test --- src/yunohost/tests/test_permission.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index f17313fa1..a9e16cfc6 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -41,6 +41,10 @@ def teardown_function(function): app_remove("permissions_app") except: pass + try: + app_remove("legacy_app") + except: + pass @pytest.fixture(autouse=True) def check_LDAP_db_integrity_call(): @@ -443,23 +447,22 @@ def test_permission_app_propagation_on_ssowat(): def test_permission_legacy_app_propagation_on_ssowat(): - # TODO / FIXME : To be actually implemented later .... - raise NotImplementedError - app_install("./tests/apps/legacy_app_ynh", args="domain=%s&path=%s" % (maindomain, "/legacy"), force=True) # App is configured as public by default using the legacy unprotected_uri mechanics # It should automatically be migrated during the install - assert res['permissions_app.main']['allowed'] == ["visitors"] + res = user_permission_list(full=True)['permissions'] + assert res['legacy_app.main']['allowed'] == ["visitors"] - assert can_access_webpage(maindomain + "/legacy", logged_as=None) - assert can_access_webpage(maindomain + "/legacy", logged_as="alice") + app_webroot = "https://%s/legacy" % maindomain + + assert can_access_webpage(app_webroot, logged_as=None) + assert can_access_webpage(app_webroot, logged_as="alice") # Try to update the permission and check that permissions are still consistent user_permission_update("legacy_app.main", remove="visitors", add="bob") - res = user_permission_list(full=True)['permissions'] - assert not can_access_webpage(maindomain + "/legacy", logged_as=None) - assert not can_access_webpage(maindomain + "/legacy", logged_as="alice") - assert can_access_webpage(maindomain + "/legacy", logged_as="bob") + assert not can_access_webpage(app_webroot, logged_as=None) + assert not can_access_webpage(app_webroot, logged_as="alice") + assert can_access_webpage(app_webroot, logged_as="bob") From df49af0ad001b99086083798a2b6e888c9352a80 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 9 Oct 2019 18:55:11 +0200 Subject: [PATCH 0240/3170] Redundant operation considering we're deleting all groups right after --- src/yunohost/data_migrations/0011_setup_group_permission.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/yunohost/data_migrations/0011_setup_group_permission.py b/src/yunohost/data_migrations/0011_setup_group_permission.py index dd5b3c274..ae5a8bfb9 100644 --- a/src/yunohost/data_migrations/0011_setup_group_permission.py +++ b/src/yunohost/data_migrations/0011_setup_group_permission.py @@ -60,7 +60,6 @@ class MyMigration(Migration): ldap_map = read_yaml('/usr/share/yunohost/yunohost-config/moulinette/ldap_scheme.yml') try: - self.remove_if_exists("cn=sftpusers,ou=groups") self.remove_if_exists("ou=permission") self.remove_if_exists('ou=groups') From 96bc95656c6ad29fa59ae337a0f9f4f04c097261 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 9 Oct 2019 19:22:31 +0200 Subject: [PATCH 0241/3170] Allow the migration to proceed if slapd config was manually modified, warn the user about where the conf will be backuped --- locales/en.json | 2 +- src/yunohost/data_migrations/0011_setup_group_permission.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/locales/en.json b/locales/en.json index 5c3595782..3e1054069 100644 --- a/locales/en.json +++ b/locales/en.json @@ -346,7 +346,7 @@ "migration_0011_can_not_backup_before_migration": "The backup of the system before the migration failed. Migration failed. Error: {error:s}", "migration_0011_create_group": "Creating a group for each user…", "migration_0011_done": "Migration successful. You are now able to manage usergroups.", - "migration_0011_LDAP_config_dirty": "It look like that you customized your LDAP configuration. For this migration the LDAP configuration needs to be updated.\nYou need to save your current configuration, reintialize the original configuration by running 'yunohost tools regen-conf -f' and retry the migration", + "migration_0011_slapd_config_will_be_overwritten": "It looks like you manually edited the slapd configuration. For this critical migration, YunoHost needs to force the update of the slapd configuration. The original files will be backuped in {conf_backup_folder}.", "migration_0011_LDAP_update_failed": "Could not update LDAP. Error: {error:s}", "migration_0011_migrate_permission": "Migrating permissions from apps settings to LDAP…", "migration_0011_migration_failed_trying_to_rollback": "Migration failed… trying to roll back the system.", diff --git a/src/yunohost/data_migrations/0011_setup_group_permission.py b/src/yunohost/data_migrations/0011_setup_group_permission.py index ae5a8bfb9..de28a3ad7 100644 --- a/src/yunohost/data_migrations/0011_setup_group_permission.py +++ b/src/yunohost/data_migrations/0011_setup_group_permission.py @@ -9,7 +9,7 @@ from moulinette.utils.filesystem import read_yaml from yunohost.tools import Migration from yunohost.user import user_group_create, user_group_update from yunohost.app import app_setting, app_list -from yunohost.regenconf import regen_conf +from yunohost.regenconf import regen_conf, BACKUP_CONF_DIR from yunohost.permission import permission_create, user_permission_update, permission_sync_to_user logger = getActionLogger('yunohost.migration') @@ -130,7 +130,7 @@ class MyMigration(Migration): ldap_regen_conf_status = regen_conf(names=['slapd'], dry_run=True) # By this we check if the have been customized if ldap_regen_conf_status and ldap_regen_conf_status['slapd']['pending']: - raise YunohostError("migration_0011_LDAP_config_dirty") + logger.warning("migration_0011_slapd_config_will_be_overwritten", conf_backup_folder=BACKUP_CONF_DIR) # Backup LDAP and the apps settings before to do the migration logger.info(m18n.n("migration_0011_backup_before_migration")) From 9cecd71437d050696a5e98e676532c21e8396749 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 9 Oct 2019 19:39:37 +0200 Subject: [PATCH 0242/3170] Fix permission_reset idempotency --- src/yunohost/permission.py | 4 ++++ src/yunohost/tests/test_permission.py | 11 +++++++++++ 2 files changed, 15 insertions(+) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 75e3f6037..97e5b4122 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -217,6 +217,10 @@ def user_permission_reset(operation_logger, permission, sync_perm=True): if existing_permission is None: raise YunohostError('permission_not_found', permission=permission) + if existing_permission["allowed"] == ["all_users"]: + logger.warning("The permission was not updated all addition/removal requests already match the current state.") + return + # Update permission with default (all_users) operation_logger.related_to.append(('app', permission.split(".")[0])) diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index a9e16cfc6..0ddda4cec 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -306,6 +306,17 @@ def test_permission_reset(): assert res['blog.main']['allowed'] == ["all_users"] assert set(res['blog.main']['corresponding_users']) == set(["alice", "bob"]) + +def test_permission_reset_idempotency(): + # Reset permission + user_permission_reset("blog.main") + user_permission_reset("blog.main") + + res = user_permission_list(full=True)['permissions'] + assert res['blog.main']['allowed'] == ["all_users"] + assert set(res['blog.main']['corresponding_users']) == set(["alice", "bob"]) + + # # Error on update function # From 88794805eba3ececc5bc04aac2d41b4d09241bf7 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 9 Oct 2019 22:08:12 +0200 Subject: [PATCH 0243/3170] We probably don't need to have multiple urls per permissions ... --- data/helpers.d/setting | 62 ++++++++--------- locales/en.json | 2 +- src/yunohost/app.py | 68 ++++++++----------- src/yunohost/backup.py | 6 +- .../0011_setup_group_permission.py | 4 +- src/yunohost/permission.py | 38 ++++------- src/yunohost/tests/test_backuprestore.py | 16 ++--- src/yunohost/tests/test_permission.py | 56 +++++---------- 8 files changed, 105 insertions(+), 147 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index b2a647993..c911c5811 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -232,34 +232,36 @@ ynh_webpath_register () { # Create a new permission for the app # -# example: ynh_permission_create --permission admin --urls /admin +# example: ynh_permission_create --permission admin --url /admin # -# usage: ynh_permission_create --permission "permission" [--urls "url" ["url" ...]] +# usage: ynh_permission_create --permission "permission" [--url "url"] # | arg: permission - the name for the permission (by default a permission named "main" already exist) -# | arg: urls - (optional) a list of FULL urls for the permission (e.g. domain.tld/apps/admin) -# | arg: urls - (optional) a list of URLs to specify for the permission. +# | arg: url - (optional) URL for which access will be allowed/forbidden # -# URLs are assumed to be relative to the app domain/path if they start with '/'. -# For example: -# / -> domain.tld/app -# /admin -> domain.tld/app/admin -# domain.tld/app/api -> domain.tld/app/api +# If provided, 'url' is assumed to be relative to the app domain/path if they +# start with '/'. For example: +# / -> domain.tld/app +# /admin -> domain.tld/app/admin +# domain.tld/app/api -> domain.tld/app/api # -# URLs can be treated as regexes when they start with "re:". -# For example: -# re:/api/[A-Z]*$ -> domain.tld/app/api/[A-Z]*$ -# re:domain.tld/app/api/[A-Z]*$ -> domain.tld/app/api/[A-Z]*$ +# 'url' can be later treated as a regex if it starts with "re:". +# For example: +# re:/api/[A-Z]*$ -> domain.tld/app/api/[A-Z]*$ +# re:domain.tld/app/api/[A-Z]*$ -> domain.tld/app/api/[A-Z]*$ # ynh_permission_create() { - declare -Ar args_array=( [p]=permission= [u]=urls= ) + declare -Ar args_array=( [p]=permission= [u]=url= ) local permission local urls ynh_handle_getopts_args "$@" - if [[ -n ${urls:-} ]]; then - urls=",urls=['${urls//';'/"','"}']" + if [[ -n ${url:-} ]]; then + url="'$url'" + else + url="None" fi - yunohost tools shell -c "from yunohost.permission import permission_create; permission_create('$app.$permission' ${urls:-}, sync_perm=False)" + + yunohost tools shell -c "from yunohost.permission import permission_create; permission_create('$app.$permission', url=$url, sync_perm=False)" } # Remove a permission for the app (note that when the app is removed all permission is automatically removed) @@ -277,30 +279,28 @@ ynh_permission_delete() { yunohost tools shell -c "from yunohost.permission import permission_delete; permission_delete('$app.$permission', sync_perm=False)" } -# Manage urls related to a permission +# Redefine the url associated to a permission # -# usage: ynh_permission_urls --permission "permission" --add "url" ["url" ...] --remove "url" ["url" ...] +# usage: ynh_permission_url --permission "permission" --url "url" # | arg: permission - the name for the permission (by default a permission named "main" is removed automatically when the app is removed) -# | arg: add - (optional) a list of urls to add to the permission (see permission_create for details regarding their format) -# | arg: remove - (optional) a list of urls to remove from the permission (see permission_create for details regarding their format) +# | arg: url - (optional) URL for which access will be allowed/forbidden # -ynh_permission_urls() { - declare -Ar args_array=([p]=permission= [a]=add= [r]=remove=) +ynh_permission_url() { + declare -Ar args_array=([p]=permission= [u]=url=) local permission - local add - local remove + local url ynh_handle_getopts_args "$@" - if [[ -n ${add:-} ]]; then - add=",add=['${add//';'/"','"}']" - fi - if [[ -n ${remove:-} ]]; then - remove=",remove=['${remove//';'/"','"}']" + if [[ -n ${url:-} ]]; then + url="'$url'" + else + url="None" fi - yunohost tools shell -c "from yunohost.permission import permission_urls; permission_urls('$app.$permission' ${add:-} ${remove:-})" + yunohost tools shell -c "from yunohost.permission import permission_url; permission_url('$app.$permission', url=$url)" } + # Update a permission for the app # # usage: ynh_permission_update --permission "permission" --add "group" ["group" ...] --remove "group" ["group" ...] diff --git a/locales/en.json b/locales/en.json index 3e1054069..e3911a334 100644 --- a/locales/en.json +++ b/locales/en.json @@ -268,7 +268,7 @@ "log_letsencrypt_cert_install": "Install a Let's encrypt certificate on '{}' domain", "log_permission_create": "Create permission '{}'", "log_permission_delete": "Delete permission '{}'", - "log_permission_urls": "Update urls related to permission '{}'", + "log_permission_url": "Update url related to permission '{}'", "log_selfsigned_cert_install": "Install self signed certificate on '{}' domain", "log_letsencrypt_cert_renew": "Renew '{}' Let's encrypt certificate", "log_regen_conf": "Regenerate system configurations '{}'", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 4bda9ccf6..abb4387e5 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -454,33 +454,18 @@ def app_map(app=None, raw=False, user=None): return perm_domain, perm_path - this_app_perms = {p: i for p, i in permissions.items() if p.startswith(app_id + ".") and i["urls"]} + this_app_perms = {p: i for p, i in permissions.items() if p.startswith(app_id + ".") and i["url"]} for perm_name, perm_info in this_app_perms.items(): # If we're building the map for a specific user, check the user # actually is allowed for this specific perm if user and user not in perm_info["corresponding_users"] and "visitors" not in perm_info["allowed"]: continue - if len(perm_info["urls"]) > 1 or perm_info["urls"][0].startswith("re:"): - # - # Here we have a big conceptual issue about the sso ... - # Let me take a sip of coffee and turn off the music... - # - # Let's say we have an app foo which created a permission - # 'foo.admin' and added as url "/admin" and "/api" This - # permission got defined somehow as only accessible for group - # "admins". So both "/admin" and "/api" are protected. Good! - # - # Now if we really want users in group "admins" to access those - # uris, then each users in group "admins" need to have these - # urls in the ssowat dict for this user. Which corresponds to a - # tile. To put it otherwise : in the current code of ssowat, a - # permission = a tile = a url ! - # - # We also have an issue if the url define is a regex, because + if perm_info["url"].startswith("re:"): + # Here, we have an issue if the chosen url is a regex, because # the url we want to add to the dict is going to be turned into # a clickable link (or analyzed by other parts of yunohost # code...). To put it otherwise : in the current code of ssowat, - # you can't give access a user to a regex + # you can't give access a user to a regex. # # Instead, as drafted by Josue, we could rework the ssowat logic # about how routes and their permissions are defined. So for example, @@ -498,10 +483,10 @@ def app_map(app=None, raw=False, user=None): # protected/unprotected/skipped uris and regexes and we gotta # handle / migrate all the legacy stuff somehow if we don't # want to end up with a total mess in the future idk - logger.error("Permission %s can't be added to the SSOwat configuration because it uses multiple urls and/or uses a regex url" % perm_name) + logger.error("Permission %s can't be added to the SSOwat configuration because it doesn't support regexes so far..." % perm_name) continue - perm_domain, perm_path = _sanitized_absolute_url(perm_info["urls"][0]) + perm_domain, perm_path = _sanitized_absolute_url(perm_info["url"]) if perm_name.endswith(".main"): perm_label = label @@ -535,7 +520,6 @@ def app_change_url(operation_logger, app, domain, path): """ from yunohost.hook import hook_exec, hook_callback from yunohost.domain import _normalize_domain_path, _get_conflicting_apps - from yunohost.permission import permission_urls installed = _is_installed(app) if not installed: @@ -835,7 +819,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback from yunohost.log import OperationLogger - from yunohost.permission import user_permission_list, permission_create, permission_urls, permission_delete, permission_sync_to_user, user_permission_update + from yunohost.permission import user_permission_list, permission_create, permission_url, permission_delete, permission_sync_to_user, user_permission_update # Fetch or extract sources if not os.path.exists(INSTALL_TMP): @@ -994,7 +978,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu # Initialize the main permission for the app # After the install, if apps don't have a domain and path defined, the default url '/' is removed from the permission - permission_create(app_instance_name+".main", urls=["/"]) + permission_create(app_instance_name+".main", url="/") # Execute the app install script install_retcode = 1 @@ -1088,7 +1072,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu domain = app_settings.get('domain', None) path = app_settings.get('path', None) if not (domain and path): - permission_urls(app_instance_name + ".main", remove=["/"], sync_perm=False) + permission_url(app_instance_name + ".main", url=None, sync_perm=False) # Migrate classic public app still using the legacy unprotected_uris if app_settings.get("unprotected_uris", None) == "/": @@ -1178,7 +1162,7 @@ def app_addaccess(apps, users=[]): """ from yunohost.permission import user_permission_update - logger.warning("/!\\ Packagers ! This app is using the legacy permission system. Please use the new helpers ynh_permission_{create,urls,update,delete} and the 'visitors' group to manage permissions.") + logger.warning("/!\\ Packagers ! This app is using the legacy permission system. Please use the new helpers ynh_permission_{create,url,update,delete} and the 'visitors' group to manage permissions.") output = {} for app in apps: @@ -1199,7 +1183,7 @@ def app_removeaccess(apps, users=[]): """ from yunohost.permission import user_permission_update - logger.warning("/!\\ Packagers ! This app is using the legacy permission system. Please use the new helpers ynh_permission_{create,urls,update,delete} and the 'visitors' group to manage permissions.") + logger.warning("/!\\ Packagers ! This app is using the legacy permission system. Please use the new helpers ynh_permission_{create,url,update,delete} and the 'visitors' group to manage permissions.") output = {} for app in apps: @@ -1219,7 +1203,7 @@ def app_clearaccess(apps): """ from yunohost.permission import user_permission_reset - logger.warning("/!\\ Packagers ! This app is using the legacy permission system. Please use the new helpers ynh_permission_{create,urls,update,delete} and the 'visitors' group to manage permissions.") + logger.warning("/!\\ Packagers ! This app is using the legacy permission system. Please use the new helpers ynh_permission_{create,url,update,delete} and the 'visitors' group to manage permissions.") output = {} for app in apps: @@ -1329,7 +1313,7 @@ def app_setting(app, key, value=None, delete=False): if key in ['redirected_urls', 'redirected_regex']: value = yaml.load(value) if key in ["unprotected_uris", "unprotected_regex", "protected_uris", "protected_regex"]: - logger.warning("/!\ Packagers ! This app is using the legacy permission system. Please delete these legacy settings and use the new helpers ynh_permission_{create,urls,update,delete} and the 'visitors' group to manage public/private access.") + logger.warning("/!\ Packagers ! This app is using the legacy permission system. Please delete these legacy settings and use the new helpers ynh_permission_{create,url,update,delete} and the 'visitors' group to manage public/private access.") app_settings[key] = value _set_app_settings(app, app_settings) @@ -1562,20 +1546,24 @@ def app_ssowatconf(): # New permission system this_app_perms = {name: info for name, info in all_permissions.items() if name.startswith(app['id'] + ".")} for perm_name, perm_info in this_app_perms.items(): + + # Ignore permissions for which there's no url defined + if not perm_info["url"]: + continue + # FIXME : gotta handle regex-urls here... meh - urls = [_sanitized_absolute_url(url) for url in perm_info["urls"]] + url = _sanitized_absolute_url(perm_info["url"]) if "visitors" in perm_info["allowed"]: - unprotected_urls += urls + unprotected_urls.append(url) # Legacy stuff : we remove now unprotected-urls that might have been declared as protected earlier... - protected_urls = [u for u in protected_urls if u not in urls] + protected_urls = [u for u in protected_urls if u != url] else: # TODO : small optimization to implement : we don't need to explictly add all the app roots - - protected_urls += urls + protected_urls.append(url) # Legacy stuff : we remove now unprotected-urls that might have been declared as protected earlier... - unprotected_urls = [u for u in unprotected_urls if u not in urls] + unprotected_urls = [u for u in unprotected_urls if u != url] for domain in domains: skipped_urls.extend([domain + '/yunohost/admin', domain + '/yunohost/api']) @@ -1585,11 +1573,13 @@ def app_ssowatconf(): skipped_regex.append("^[^/]*/%.well%-known/autoconfig/mail/config%-v1%.1%.xml.*$") - permissions_per_url = {} - for permission_name, permission_infos in all_permissions.items(): - for url in permission_infos["urls"]: - permissions_per_url[url] = permission_infos['corresponding_users'] + for perm_name, perm_info in all_permissions.items(): + # Ignore permissions for which there's no url defined + if not perm_info["url"]: + continue + permissions_per_url[perm_info["url"]] = perm_info['corresponding_users'] + conf_dict = { 'portal_domain': main_domain, diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index c28160342..dcdb1adec 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -1189,7 +1189,7 @@ class RestoreManager(): return from yunohost.user import user_group_list - from yunohost.permission import permission_create, permission_delete, user_permission_update, user_permission_list + from yunohost.permission import permission_create, permission_delete, user_permission_update, user_permission_list, permission_sync_to_user # Backup old permission for apps # We need to do that because in case of an app is installed we can't remove the permission for this app @@ -1251,7 +1251,7 @@ class RestoreManager(): for permission_name, permission_infos in old_apps_permission.items(): app_name = permission_name.split(".")[0] if _is_installed(app_name): - permission_create(permission_name, urls=permission_infos["urls"], sync_perm=False) + permission_create(permission_name, url=permission_infos["url"], sync_perm=False) user_permission_update(permission_name, remove="all_users", add=permission_infos["allowed"]) def _restore_apps(self): @@ -1362,7 +1362,7 @@ class RestoreManager(): for permission_name, permission_infos in permissions.items(): - permission_create(permission_name, urls=permission_infos.get("urls", [])) + permission_create(permission_name, url=permission_infos.get("url", None)) if "allowed" not in permission_infos: logger.warning("'allowed' key corresponding to allowed groups for permission %s not found when restoring app %s … You might have to reconfigure permissions yourself." % (permission_name, app_instance_name)) diff --git a/src/yunohost/data_migrations/0011_setup_group_permission.py b/src/yunohost/data_migrations/0011_setup_group_permission.py index de28a3ad7..880c5f54b 100644 --- a/src/yunohost/data_migrations/0011_setup_group_permission.py +++ b/src/yunohost/data_migrations/0011_setup_group_permission.py @@ -107,8 +107,8 @@ class MyMigration(Migration): path = app_setting(app, 'path') domain = app_setting(app, 'domain') - urls = "/" if domain and path else None - permission_create(app+".main", urls=urls, sync_perm=False) + url = "/" if domain and path else None + permission_create(app+".main", url=url, sync_perm=False) if permission: allowed_group = permission.split(',') user_permission_update(app+".main", remove="all_users", add=allowed_group, sync_perm=False) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 97e5b4122..6f9d63d69 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -73,7 +73,7 @@ def user_permission_list(short=False, full=False, ignore_system_perms=False): if full: permissions[name]["corresponding_users"] = [_ldap_path_extract(p, "uid") for p in infos.get('inheritPermission', [])] - permissions[name]["urls"] = infos.get("URL", []) + permissions[name]["url"] = infos.get("URL", [None])[0] if short: permissions = permissions.keys() @@ -260,27 +260,27 @@ def user_permission_reset(operation_logger, permission, sync_perm=True): # # The followings methods are *not* directly exposed. # They are used to create/delete the permissions (e.g. during app install/remove) -# and by some app helpers to possibly add additional permissions and tweak the urls +# and by some app helpers to possibly add additional permissions # # @is_unit_operation() -def permission_create(operation_logger, permission, urls=None, sync_perm=True): +def permission_create(operation_logger, permission, url=None, sync_perm=True): """ Create a new permission for a specific application Keyword argument: permission -- Name of the permission (e.g. mail or nextcloud or wordpress.editors) - urls -- list of URLs to specify for the permission. + url -- (optional) URL for which access will be allowed/forbidden - Urls are assumed to be relative to the app domain/path if they start with '/'. - For example: + If provided, 'url' is assumed to be relative to the app domain/path if they + start with '/'. For example: / -> domain.tld/app /admin -> domain.tld/app/admin domain.tld/app/api -> domain.tld/app/api - URLs can be later treated as regexes when they start with "re:". + 'url' can be later treated as a regex if it starts with "re:". For example: re:/api/[A-Z]*$ -> domain.tld/app/api/[A-Z]*$ re:domain.tld/app/api/[A-Z]*$ -> domain.tld/app/api/[A-Z]*$ @@ -316,8 +316,8 @@ def permission_create(operation_logger, permission, urls=None, sync_perm=True): if permission.endswith(".main"): attr_dict['groupPermission'] = ['cn=all_users,ou=groups,dc=yunohost,dc=org'] - if urls: - attr_dict['URL'] = urls + if url: + attr_dict['URL'] = url operation_logger.related_to.append(('app', permission.split(".")[0])) operation_logger.start() @@ -335,15 +335,13 @@ def permission_create(operation_logger, permission, urls=None, sync_perm=True): @is_unit_operation() -def permission_urls(operation_logger, permission, add=None, remove=None, sync_perm=True): +def permission_url(operation_logger, permission, url=None, sync_perm=True): """ Update urls related to a permission for a specific application Keyword argument: permission -- Name of the permission (e.g. mail or nextcloud or wordpress.editors) - add -- List of URLs to add (c.f. permission_create for documentation about their format) - remove -- List of URLs to remove (c.f. permission_create for documentation about their format) - + url -- (optional) URL for which access will be allowed/forbidden """ from yunohost.utils.ldap import _get_ldap_interface ldap = _get_ldap_interface() @@ -355,17 +353,9 @@ def permission_urls(operation_logger, permission, add=None, remove=None, sync_pe raise YunohostError('permission_not_found', permission=permission) # Compute new url list + old_url = existing_permission["url"] - new_urls = copy.copy(existing_permission["urls"]) - - if add: - urls_to_add = [add] if not isinstance(add, list) else add - new_urls += urls_to_add - if remove: - urls_to_remove = [remove] if not isinstance(remove, list) else remove - new_urls = [u for u in new_urls if u not in urls_to_remove] - - if set(new_urls) == set(existing_permission["urls"]): + if old_url == url: logger.warning(m18n.n('permission_update_nothing_to_do')) return existing_permission @@ -375,7 +365,7 @@ def permission_urls(operation_logger, permission, add=None, remove=None, sync_pe operation_logger.start() try: - ldap.update('cn=%s,ou=permission' % permission, {'URL': new_urls}) + ldap.update('cn=%s,ou=permission' % permission, {'URL': [url]}) except Exception as e: raise YunohostError('permission_update_failed', permission=permission, error=e) diff --git a/src/yunohost/tests/test_backuprestore.py b/src/yunohost/tests/test_backuprestore.py index cab98089b..82d3da660 100644 --- a/src/yunohost/tests/test_backuprestore.py +++ b/src/yunohost/tests/test_backuprestore.py @@ -529,11 +529,11 @@ def test_backup_and_restore_permission_app(): assert "permissions_app.main" in res assert "permissions_app.admin" in res assert "permissions_app.dev" in res - assert res['permissions_app.main']['urls'] == ["/"] - assert res['permissions_app.admin']['urls'] == ["/admin"] - assert res['permissions_app.dev']['urls'] == ["/dev"] + assert res['permissions_app.main']['url'] == "/" + assert res['permissions_app.admin']['url'] == "/admin" + assert res['permissions_app.dev']['url'] == "/dev" - assert res['permissions_app.main']['allowed'] == ["all_users"] + assert res['permissions_app.main']['allowed'] == ["visitors"] assert res['permissions_app.admin']['allowed'] == ["alice"] assert res['permissions_app.dev']['allowed'] == [] @@ -543,11 +543,11 @@ def test_backup_and_restore_permission_app(): assert "permissions_app.main" in res assert "permissions_app.admin" in res assert "permissions_app.dev" in res - assert res['permissions_app.main']['urls'] == ["/"] - assert res['permissions_app.admin']['urls'] == ["/admin"] - assert res['permissions_app.dev']['urls'] == ["/dev"] + assert res['permissions_app.main']['url'] == "/" + assert res['permissions_app.admin']['url'] == "/admin" + assert res['permissions_app.dev']['url'] == "/dev" - assert res['permissions_app.main']['allowed'] == ["all_users"] + assert res['permissions_app.main']['allowed'] == ["visitors"] assert res['permissions_app.admin']['allowed'] == ["alice"] assert res['permissions_app.dev']['allowed'] == [] diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index 0ddda4cec..8e536ec9e 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -6,7 +6,7 @@ from yunohost.app import app_install, app_remove, app_change_url, app_list, app_ from yunohost.user import user_list, user_info, user_create, user_delete, user_update, \ user_group_list, user_group_create, user_group_delete, user_group_update, user_group_info from yunohost.permission import user_permission_update, user_permission_list, user_permission_reset, \ - permission_create, permission_urls, permission_delete + permission_create, permission_delete, permission_url from yunohost.domain import _get_maindomain from yunohost.utils.error import YunohostError @@ -31,7 +31,7 @@ def setup_function(function): user_create("alice", "Alice", "White", "alice@" + maindomain, dummy_password) user_create("bob", "Bob", "Snow", "bob@" + maindomain, dummy_password) - permission_create("wiki.main", urls=["/"], sync_perm=False) + permission_create("wiki.main", url="/", sync_perm=False) permission_create("blog.main", sync_perm=False) user_permission_update("blog.main", remove="all_users", add="alice") @@ -202,7 +202,7 @@ def test_permission_list(): assert res['blog.main']['allowed'] == ["alice"] assert set(res['wiki.main']['corresponding_users']) == set(["alice", "bob"]) assert res['blog.main']['corresponding_users'] == ["alice"] - assert res['wiki.main']['urls'] == ["/"] + assert res['wiki.main']['url'] == "/" # # Create - Remove functions @@ -333,41 +333,19 @@ def test_permission_update_permission_that_doesnt_exist(): with pytest.raises(YunohostError): user_permission_update("doesnt.exist", add="alice") - # Permission url management -def test_permission_add_url(): - permission_urls("blog.main", add=["/testA"]) +def test_permission_redefine_url(): + permission_url("blog.main", url="/pwet") res = user_permission_list(full=True)['permissions'] - assert res["blog.main"]["urls"] == ["/testA"] - -def test_permission_add_another_url(): - permission_urls("wiki.main", add=["/testA"]) - - res = user_permission_list(full=True)['permissions'] - assert set(res["wiki.main"]["urls"]) == set(["/", "/testA"]) + assert res["blog.main"]["url"] == "/pwet" def test_permission_remove_url(): - permission_urls("wiki.main", remove=["/"]) + permission_url("blog.main", url=None) res = user_permission_list(full=True)['permissions'] - assert res["wiki.main"]["urls"] == [] - -def test_permission_add_url_already_added(): - res = user_permission_list(full=True)['permissions'] - assert res["wiki.main"]["urls"] == ["/"] - - permission_urls("wiki.main", add=["/"]) - - res = user_permission_list(full=True)['permissions'] - assert res["wiki.main"]["urls"] == ["/"] - -def test_permission_remove_url_not_added(): - permission_urls("wiki.main", remove=["/doesnt_exist"]) - - res = user_permission_list(full=True)['permissions'] - assert res['wiki.main']['urls'] == ["/"] + assert res["blog.main"]["url"] is None # # Application interaction @@ -381,9 +359,9 @@ def test_permission_app_install(): assert "permissions_app.main" in res assert "permissions_app.admin" in res assert "permissions_app.dev" in res - assert res['permissions_app.main']['urls'] == ["/"] - assert res['permissions_app.admin']['urls'] == ["/admin"] - assert res['permissions_app.dev']['urls'] == ["/dev"] + assert res['permissions_app.main']['url'] == "/" + assert res['permissions_app.admin']['url'] == "/admin" + assert res['permissions_app.dev']['url'] == "/dev" assert res['permissions_app.main']['allowed'] == ["all_users"] assert set(res['permissions_app.main']['corresponding_users']) == set(["alice", "bob"]) @@ -416,16 +394,16 @@ def test_permission_app_change_url(): # FIXME : should rework this test to look for differences in the generated app map / app tiles ... res = user_permission_list(full=True)['permissions'] - assert res['permissions_app.main']['urls'] == ["/"] - assert res['permissions_app.admin']['urls'] == ["/admin"] - assert res['permissions_app.dev']['urls'] == ["/dev"] + assert res['permissions_app.main']['url'] == "/" + assert res['permissions_app.admin']['url'] == "/admin" + assert res['permissions_app.dev']['url'] == "/dev" app_change_url("permissions_app", maindomain, "/newchangeurl") res = user_permission_list(full=True)['permissions'] - assert res['permissions_app.main']['urls'] == ["/"] - assert res['permissions_app.admin']['urls'] == ["/admin"] - assert res['permissions_app.dev']['urls'] == ["/dev"] + assert res['permissions_app.main']['url'] == "/" + assert res['permissions_app.admin']['url'] == "/admin" + assert res['permissions_app.dev']['url'] == "/dev" def test_permission_app_propagation_on_ssowat(): From 2617fd2487d8940309b7a19cb3011985eff3a327 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 9 Oct 2019 22:11:19 +0200 Subject: [PATCH 0244/3170] Fix issues related to regerating ssowat conf while hacking permissions... --- src/yunohost/backup.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index dcdb1adec..90f795ea5 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -1245,14 +1245,17 @@ class RestoreManager(): # Remove all permission for all app which is still in the LDAP for permission_name in user_permission_list(ignore_system_perms=True)["permissions"].keys(): - permission_delete(permission_name, force=True) + permission_delete(permission_name, force=True, sync_perm=False) # Restore permission for the app which is installed for permission_name, permission_infos in old_apps_permission.items(): app_name = permission_name.split(".")[0] if _is_installed(app_name): permission_create(permission_name, url=permission_infos["url"], sync_perm=False) - user_permission_update(permission_name, remove="all_users", add=permission_infos["allowed"]) + user_permission_update(permission_name, remove="all_users", add=permission_infos["allowed"], sync_perm=False) + + permission_sync_to_user() + def _restore_apps(self): """Restore all apps targeted""" @@ -1290,7 +1293,7 @@ class RestoreManager(): restore_app_failed -- Raised if the restore bash script failed """ from yunohost.user import user_group_list - from yunohost.permission import permission_create, permission_delete, user_permission_list, user_permission_update + from yunohost.permission import permission_create, permission_delete, user_permission_list, user_permission_update, permission_sync_to_user def copytree(src, dst, symlinks=False, ignore=None): for item in os.listdir(src): @@ -1362,7 +1365,7 @@ class RestoreManager(): for permission_name, permission_infos in permissions.items(): - permission_create(permission_name, url=permission_infos.get("url", None)) + permission_create(permission_name, url=permission_infos.get("url", None), sync_perm=False) if "allowed" not in permission_infos: logger.warning("'allowed' key corresponding to allowed groups for permission %s not found when restoring app %s … You might have to reconfigure permissions yourself." % (permission_name, app_instance_name)) @@ -1370,7 +1373,9 @@ class RestoreManager(): should_be_allowed = [g for g in permission_infos["allowed"] if g in existing_groups] current_allowed = user_permission_list()["permissions"][permission_name]["allowed"] if should_be_allowed != current_allowed: - user_permission_update(permission_name, remove=current_allowed, add=should_be_allowed) + user_permission_update(permission_name, remove=current_allowed, add=should_be_allowed, sync_perm=False) + + permission_sync_to_user() os.remove('%s/permissions.yml' % app_settings_new_path) else: From c315df926999376fd79b81dd32072d8d3f06e4a9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 9 Oct 2019 22:48:47 +0200 Subject: [PATCH 0245/3170] Wokay, getting tired of breaking the entire permission/group ecosystem because of bugs when developing. --- src/yunohost/app.py | 3 +++ src/yunohost/user.py | 7 ++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index abb4387e5..75bf12f3d 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -433,6 +433,9 @@ def app_map(app=None, raw=False, user=None): continue # Users must at least have access to the main permission to have access to extra permissions if user: + if not app_id + ".main" in permissions: + logger.warning("Uhoh, no main permission was found for app %s ... sounds like an app was only partially removed due to another bug :/" % app_id) + continue main_perm = permissions[app_id + ".main"] if user not in main_perm["corresponding_users"] and "visitors" not in main_perm["allowed"]: continue diff --git a/src/yunohost/user.py b/src/yunohost/user.py index f4e550230..72aa36184 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -268,7 +268,12 @@ def user_delete(operation_logger, username, purge=False): # remove the member from the group if username != group and username in infos["members"]: user_group_update(group, remove=username, sync_perm=False) - user_group_delete(username, force=True, sync_perm=True) + + # Delete primary group if it exists (why wouldnt it exists ? because some + # epic bug happened somewhere else and only a partial removal was + # performed...) + if username in user_group_list()['groups'].keys(): + user_group_delete(username, force=True, sync_perm=True) ldap = _get_ldap_interface() try: From e7d1cc5f9449f77bf575c82f9faae5f2f2168b41 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 9 Oct 2019 22:55:06 +0200 Subject: [PATCH 0246/3170] Allow to specify right away what groups to allow for a permission when creating it --- data/helpers.d/setting | 16 +++++++++++----- src/yunohost/app.py | 2 +- src/yunohost/backup.py | 11 ++++------- .../0011_setup_group_permission.py | 8 +++++--- src/yunohost/permission.py | 16 ++++++++++++++-- src/yunohost/tests/test_permission.py | 9 +++++++++ 6 files changed, 44 insertions(+), 18 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index c911c5811..a8d2919a4 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -232,11 +232,12 @@ ynh_webpath_register () { # Create a new permission for the app # -# example: ynh_permission_create --permission admin --url /admin +# example: ynh_permission_create --permission admin --url /admin --allowed alice bob # -# usage: ynh_permission_create --permission "permission" [--url "url"] +# usage: ynh_permission_create --permission "permission" [--url "url"] [--allowed group1 group2] # | arg: permission - the name for the permission (by default a permission named "main" already exist) # | arg: url - (optional) URL for which access will be allowed/forbidden +# | arg: allowed - (optional) A list of group/user to allow for the permission # # If provided, 'url' is assumed to be relative to the app domain/path if they # start with '/'. For example: @@ -250,9 +251,10 @@ ynh_webpath_register () { # re:domain.tld/app/api/[A-Z]*$ -> domain.tld/app/api/[A-Z]*$ # ynh_permission_create() { - declare -Ar args_array=( [p]=permission= [u]=url= ) + declare -Ar args_array=( [p]=permission= [u]=url= [a]=allowed= ) local permission - local urls + local url + local allowed ynh_handle_getopts_args "$@" if [[ -n ${url:-} ]]; then @@ -261,7 +263,11 @@ ynh_permission_create() { url="None" fi - yunohost tools shell -c "from yunohost.permission import permission_create; permission_create('$app.$permission', url=$url, sync_perm=False)" + if [[ -n ${allowed:-} ]]; then + allowed=",allowed=['${allowed//';'/"','"}']" + fi + + yunohost tools shell -c "from yunohost.permission import permission_create; permission_create('$app.$permission', url=$url ${allowed:-} , sync_perm=False)" } # Remove a permission for the app (note that when the app is removed all permission is automatically removed) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 75bf12f3d..7235535cd 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -981,7 +981,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu # Initialize the main permission for the app # After the install, if apps don't have a domain and path defined, the default url '/' is removed from the permission - permission_create(app_instance_name+".main", url="/") + permission_create(app_instance_name+".main", url="/", allowed=["all_users"]) # Execute the app install script install_retcode = 1 diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 90f795ea5..c57ab6685 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -1251,8 +1251,7 @@ class RestoreManager(): for permission_name, permission_infos in old_apps_permission.items(): app_name = permission_name.split(".")[0] if _is_installed(app_name): - permission_create(permission_name, url=permission_infos["url"], sync_perm=False) - user_permission_update(permission_name, remove="all_users", add=permission_infos["allowed"], sync_perm=False) + permission_create(permission_name, url=permission_infos["url"], allowed=permission_infos["allowed"], sync_perm=False) permission_sync_to_user() @@ -1365,15 +1364,13 @@ class RestoreManager(): for permission_name, permission_infos in permissions.items(): - permission_create(permission_name, url=permission_infos.get("url", None), sync_perm=False) - if "allowed" not in permission_infos: logger.warning("'allowed' key corresponding to allowed groups for permission %s not found when restoring app %s … You might have to reconfigure permissions yourself." % (permission_name, app_instance_name)) + should_be_allowed = ["all_users"] else: should_be_allowed = [g for g in permission_infos["allowed"] if g in existing_groups] - current_allowed = user_permission_list()["permissions"][permission_name]["allowed"] - if should_be_allowed != current_allowed: - user_permission_update(permission_name, remove=current_allowed, add=should_be_allowed, sync_perm=False) + + permission_create(permission_name, url=permission_infos.get("url", None), allowed=should_be_allowed, sync_perm=False) permission_sync_to_user() diff --git a/src/yunohost/data_migrations/0011_setup_group_permission.py b/src/yunohost/data_migrations/0011_setup_group_permission.py index 880c5f54b..3114817b9 100644 --- a/src/yunohost/data_migrations/0011_setup_group_permission.py +++ b/src/yunohost/data_migrations/0011_setup_group_permission.py @@ -108,10 +108,12 @@ class MyMigration(Migration): domain = app_setting(app, 'domain') url = "/" if domain and path else None - permission_create(app+".main", url=url, sync_perm=False) if permission: - allowed_group = permission.split(',') - user_permission_update(app+".main", remove="all_users", add=allowed_group, sync_perm=False) + allowed_groups = permission.split(',') + else: + allowed_groups = ["all_users"] + permission_create(app+".main", url=url, allowed=allowed_groups, sync_perm=False) + app_setting(app, 'allowed_users', delete=True) # Migrate classic public app still using the legacy unprotected_uris diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 6f9d63d69..426ecd10f 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -266,13 +266,14 @@ def user_permission_reset(operation_logger, permission, sync_perm=True): @is_unit_operation() -def permission_create(operation_logger, permission, url=None, sync_perm=True): +def permission_create(operation_logger, permission, url=None, allowed=None, sync_perm=True): """ Create a new permission for a specific application Keyword argument: permission -- Name of the permission (e.g. mail or nextcloud or wordpress.editors) url -- (optional) URL for which access will be allowed/forbidden + allowed -- (optional) A list of group/user to allow for the permission If provided, 'url' is assumed to be relative to the app domain/path if they start with '/'. For example: @@ -286,6 +287,7 @@ def permission_create(operation_logger, permission, url=None, sync_perm=True): re:domain.tld/app/api/[A-Z]*$ -> domain.tld/app/api/[A-Z]*$ """ + from yunohost.user import user_group_list from yunohost.utils.ldap import _get_ldap_interface ldap = _get_ldap_interface() @@ -312,8 +314,18 @@ def permission_create(operation_logger, permission, url=None, sync_perm=True): 'gidNumber': gid, } + # If who should be allowed is explicitly provided, use this info + if allowed: + if not isinstance(allowed, list): + allowed = [allowed] + # (though first we validate that the targets actually exist) + all_existing_groups = user_group_list()['groups'].keys() + for g in allowed: + if g not in all_existing_groups: + raise YunohostError('group_unknown', group=g) + attr_dict['groupPermission'] = ['cn=%s,ou=groups,dc=yunohost,dc=org' % g for g in allowed] # For main permission, we add all users by default - if permission.endswith(".main"): + elif permission.endswith(".main"): attr_dict['groupPermission'] = ['cn=all_users,ou=groups,dc=yunohost,dc=org'] if url: diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index 8e536ec9e..5e1246793 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -226,6 +226,15 @@ def test_permission_create_extra(): assert "all_users" not in res['site.test']['allowed'] assert res['site.test']['corresponding_users'] == [] + +def test_permission_create_with_allowed(): + permission_create("site.test", allowed=["alice"]) + + res = user_permission_list(full=True)['permissions'] + assert "site.test" in res + assert res['site.test']['allowed'] == ["alice"] + + def test_permission_delete(): permission_delete("wiki.main", force=True) From 4bdcfb4373bfc0ce44931df209fb5d949a3d3768 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 9 Oct 2019 23:16:07 +0200 Subject: [PATCH 0247/3170] Implement / fix i18n strings --- locales/en.json | 3 +++ .../data_migrations/0011_setup_group_permission.py | 2 +- src/yunohost/permission.py | 13 ++++--------- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/locales/en.json b/locales/en.json index e3911a334..c6a6a440e 100644 --- a/locales/en.json +++ b/locales/en.json @@ -415,9 +415,12 @@ "permission_already_allowed": "Group '{group}' already has permission '{permission}' enabled'", "permission_already_disallowed": "Group '{group}' already has permission '{permission}' disabled'", "permission_already_exist": "Permission '{permission}' already exists", + "permission_already_up_to_date": "The permission was not updated because the addition/removal requests already match the current state.", "permission_cannot_remove_main": "Removing a main permission is not allowed", "permission_created": "Permission '{permission:s}' created", "permission_creation_failed": "Could not create permission '{permission}': {error}", + "permission_currently_allowed_for_visitors": "This permission is currently granted to visitors in addition to other groups. You probably want to either remove the 'visitors' permission or remove the other groups it is currently granted to.", + "permission_currently_allowed_for_all_users": "This permission is currently granted to all users in addition to other groups. You probably want to either remove the 'all_users' permission or remove the other groups it is currently granted to.", "permission_deleted": "Permission '{permission:s}' deleted", "permission_deletion_failed": "Could not delete permission '{permission}': {error}", "permission_not_found": "Permission '{permission:s}' not found", diff --git a/src/yunohost/data_migrations/0011_setup_group_permission.py b/src/yunohost/data_migrations/0011_setup_group_permission.py index 3114817b9..9ba2268d9 100644 --- a/src/yunohost/data_migrations/0011_setup_group_permission.py +++ b/src/yunohost/data_migrations/0011_setup_group_permission.py @@ -132,7 +132,7 @@ class MyMigration(Migration): ldap_regen_conf_status = regen_conf(names=['slapd'], dry_run=True) # By this we check if the have been customized if ldap_regen_conf_status and ldap_regen_conf_status['slapd']['pending']: - logger.warning("migration_0011_slapd_config_will_be_overwritten", conf_backup_folder=BACKUP_CONF_DIR) + logger.warning(m18n.n("migration_0011_slapd_config_will_be_overwritten", conf_backup_folder=BACKUP_CONF_DIR)) # Backup LDAP and the apps settings before to do the migration logger.info(m18n.n("migration_0011_backup_before_migration")) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 426ecd10f..4cfbc214f 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -144,18 +144,13 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, if len(new_allowed_groups) > 1: if "all_users" in new_allowed_groups: - # FIXME : i18n - # FIXME : write a better explanation ? - logger.warning("This permission is currently granted to all users in addition to other groups. You probably want to either remove the 'all_users' permission or remove the other groups it is currently granted to.") + logger.warning(m18n.n("permission_currently_allowed_for_all_users")) if "visitors" in new_allowed_groups: - # FIXME : i18n - # FIXME : write a better explanation ? - logger.warning("This permission is currently granted to visitors in addition to other groups. You probably want to either remove the 'visitors' permission or remove the other groups it is currently granted to.") + logger.warning(m18n.n("permission_currently_allowed_for_visitors")) # Don't update LDAP if we update exactly the same values if set(new_allowed_groups) == set(current_allowed_groups): - # FIXME : i18n - logger.warning("The permission was not updated all addition/removal requests already match the current state.") + logger.warning("permission_already_up_to_date") return # Commit the new allowed group list @@ -218,7 +213,7 @@ def user_permission_reset(operation_logger, permission, sync_perm=True): raise YunohostError('permission_not_found', permission=permission) if existing_permission["allowed"] == ["all_users"]: - logger.warning("The permission was not updated all addition/removal requests already match the current state.") + logger.warning(m18n.n("permission_already_up_to_date")) return # Update permission with default (all_users) From e4163136bbe6c7eff26102767400c0a99cb702c0 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 9 Oct 2019 23:40:50 +0200 Subject: [PATCH 0248/3170] Don't attempt to delete the 'visitors' group during user/group tests --- src/yunohost/tests/test_user-group.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/tests/test_user-group.py b/src/yunohost/tests/test_user-group.py index 30bdeb017..53fded94c 100644 --- a/src/yunohost/tests/test_user-group.py +++ b/src/yunohost/tests/test_user-group.py @@ -14,7 +14,7 @@ def clean_user_groups(): user_delete(u) for g in user_group_list()['groups']: - if g != "all_users": + if g not in ["all_users", "visitors"]: user_group_delete(g) def setup_function(function): From e48036a0829ed2754d2c39e033c78dd8b6d07c84 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 10 Oct 2019 00:05:20 +0200 Subject: [PATCH 0249/3170] Fix test about private app installs --- src/yunohost/tests/test_apps.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/yunohost/tests/test_apps.py b/src/yunohost/tests/test_apps.py index fc44ef105..fb2f13c3f 100644 --- a/src/yunohost/tests/test_apps.py +++ b/src/yunohost/tests/test_apps.py @@ -119,10 +119,10 @@ def app_is_exposed_on_http(domain, path, message_in_page): return False -def install_legacy_app(domain, path): +def install_legacy_app(domain, path, public=True): app_install("./tests/apps/legacy_app_ynh", - args="domain=%s&path=%s" % (domain, path), + args="domain=%s&path=%s&is_public=%s" % (domain, path, 1 if public else 0), force=True) @@ -180,13 +180,7 @@ def test_legacy_app_install_secondary_domain_on_root(secondary_domain): def test_legacy_app_install_private(secondary_domain): - install_legacy_app(secondary_domain, "/legacy") - - settings = open("/etc/yunohost/apps/legacy_app/settings.yml", "r").read() - new_settings = settings.replace("\nunprotected_uris: /", "") - assert new_settings != settings - open("/etc/yunohost/apps/legacy_app/settings.yml", "w").write(new_settings) - app_ssowatconf() + install_legacy_app(secondary_domain, "/legacy", public=False) assert app_is_installed(secondary_domain, "legacy_app") assert not app_is_exposed_on_http(secondary_domain, "/legacy", "This is a dummy app") From 2623d38567d18980265b8b30cdf1786f89355ad5 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 10 Oct 2019 00:06:36 +0200 Subject: [PATCH 0250/3170] Annnnnd Alex was drunk and released an epic stupid bug in stable --- src/yunohost/app.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 421be9b60..88204f3ff 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -2569,9 +2569,8 @@ def _validate_and_normalize_webpath(manifest, args_dict, app_folder): and re.search(r"(ynh_webpath_register|yunohost app checkurl)", install_script_content): domain = domain_args[0][1] - conflicts = _get_conflicting_apps(domain, "/") - - raise YunohostError('app_full_domain_unavailable', domain=domain) + if _get_conflicting_apps(domain, "/"): + raise YunohostError('app_full_domain_unavailable', domain=domain) def _make_environment_dict(args_dict, prefix="APP_ARG_"): From f2db3d34dd21fdf8d170fbd3eaea42ddb11283c9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 10 Oct 2019 00:08:47 +0200 Subject: [PATCH 0251/3170] Update changelog for 3.6.5.2 --- debian/changelog | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/debian/changelog b/debian/changelog index 6d5a16f2c..1d13b6290 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,8 +1,14 @@ +yunohost (3.6.5.2) stable; urgency=low + + - [fix] Alex was drunk and released an epic stupid bug in stable (2623d385) + + -- Alexandre Aubin Thu, 10 Oct 2019 01:00:00 +0000 + yunohost (3.6.5.1) stable; urgency=low - [mod] Change maxretry of fail2ban from 6 to 10 (fe8fd1b) - -- Alexandre Aubin Mon, 08 Oct 2019 20:00:00 +0000 + -- Alexandre Aubin Tue, 08 Oct 2019 20:00:00 +0000 yunohost (3.6.5) stable; urgency=low @@ -11,7 +17,7 @@ yunohost (3.6.5) stable; urgency=low - [fix] Epicly ugly workaround for the goddamn dependency nighmare about sury fucking up php7.0 dependencies (#809) - [fix] Support logfiles not ending with .log in logrotate ... (#810) - -- Alexandre Aubin Mon, 08 Oct 2019 19:00:00 +0000 + -- Alexandre Aubin Tue, 08 Oct 2019 19:00:00 +0000 yunohost (3.6.4.6) stable; urgency=low From 38fd969f94c90a64e9119ee3b78c8f2c62c169e0 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Mon, 7 Oct 2019 05:19:53 +0000 Subject: [PATCH 0252/3170] Translated using Weblate (Esperanto) Currently translated at 100.0% (553 of 553 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/eo/ --- locales/eo.json | 422 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 420 insertions(+), 2 deletions(-) diff --git a/locales/eo.json b/locales/eo.json index 1d367260d..d27c0171c 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -106,7 +106,7 @@ "app_package_need_update": "La pakaĵo {app} devas esti ĝisdatigita por sekvi YunoHost-ŝanĝojn", "backup_nothings_done": "Nenio por ŝpari", "backup_applying_method_custom": "Nomante la kutiman rezervan metodon '{metodo:s}' …", - "appslist_fetched": "Ĝisdatigita aplika listo {appslist:s} elprenita", + "appslist_fetched": "Ĝisdatigita aplika listo {appslist:s}", "backup_app_failed": "Ne eblis rezervi la programon '{app:s}'", "app_upgrade_some_app_failed": "Iuj aplikoj ne povis esti altgradigitaj", "app_start_remove": "Forigo de apliko {app} …", @@ -134,5 +134,423 @@ "ask_path": "Pado", "app_upgraded": "{app:s} altgradigita", "backup_deleted": "Rezerva forigita", - "backup_csv_addition_failed": "Ne povis aldoni dosierojn al sekurkopio en la CSV-dosiero" + "backup_csv_addition_failed": "Ne povis aldoni dosierojn al sekurkopio en la CSV-dosiero", + "dpkg_lock_not_available": "Ĉi tiu komando ne povas funkcii nun ĉar alia programo uzas la seruron de dpkg (la administrilo de paka sistemo)", + "migration_0003_yunohost_upgrade": "Komenci la ĝisdatigon de YunoHost-pako ... La migrado finiĝos, sed la efektiva ĝisdatigo okazos tuj poste. Post kiam la operacio finiĝos, vi eble devos ensaluti denove sur la retpaĝo.", + "domain_dyndns_root_unknown": "Nekonata radika domajno DynDNS", + "field_invalid": "Nevalida kampo '{:s}'", + "log_app_makedefault": "Faru '{}' la defaŭlta apliko", + "migration_0003_still_on_jessie_after_main_upgrade": "Io okazis malbone dum la ĉefa ĝisdatigo: Ĉu la sistemo ankoraŭ estas en Jessie‽ Por esplori la aferon, bonvolu rigardi {log}:s …", + "migration_0011_can_not_backup_before_migration": "La sekurkopio de la sistemo antaŭ la migrado malsukcesis. Migrado malsukcesis. Eraro: {error:s}", + "migration_0011_create_group": "Krei grupon por ĉiu uzanto…", + "backup_system_part_failed": "Ne eblis sekurkopi la sistemon de '{part:s}'", + "global_settings_setting_security_postfix_compatibility": "Kongruo vs sekureca kompromiso por la Postfix-servilo. Afektas la ĉifradojn (kaj aliajn aspektojn pri sekureco)", + "group_unknown": "La grupo '{group:s}' estas nekonata", + "mailbox_disabled": "Retpoŝto malŝaltita por uzanto {user:s}", + "migration_description_0011_setup_group_permission": "Agordu uzantogrupon kaj starigu permeson por programoj kaj servoj", + "migration_0011_backup_before_migration": "Krei sekurkopion de LDAP-datumbazo kaj agordojn antaŭ la efektiva migrado.", + "migration_0011_LDAP_config_dirty": "Similas ke vi agordis vian LDAP-agordon. Por ĉi tiu migrado la LDAP-agordo bezonas esti ĝisdatigita.\nVi devas konservi vian aktualan agordon, reintaligi la originalan agordon per funkciado de \"yunohost iloj regen-conf -f\" kaj reprovi la migradon", + "migration_0011_migrate_permission": "Migrado de permesoj de agordoj al aplikoj al LDAP…", + "migration_0011_migration_failed_trying_to_rollback": "Migrado malsukcesis ... provante reverti la sistemon.", + "migrations_dependencies_not_satisfied": "Ne eblas kuri migradon {id} ĉar unue vi devas ruli ĉi tiujn migradojn: {dependencies_id}", + "migrations_failed_to_load_migration": "Ne povis ŝarĝi migradon {id}: {error}", + "migrations_exclusive_options": "'--auto', '--skip' kaj '--force-rerun' estas reciproke ekskluzivaj ebloj.", + "migrations_must_provide_explicit_targets": "Vi devas provizi eksplicitajn celojn kiam vi uzas '--skip' aŭ '--force-rerun'", + "permission_update_failed": "Ne povis ĝisdatigi permeson '{permission}': {error}", + "permission_updated": "Ĝisdatigita \"{permission:s}\" rajtigita", + "permission_update_nothing_to_do": "Neniuj permesoj ĝisdatigi", + "tools_upgrade_cant_hold_critical_packages": "Ne povis teni kritikajn pakojn…", + "upnp_dev_not_found": "Neniu UPnP-aparato trovita", + "migration_description_0012_postgresql_password_to_md5_authentication": "Devigu PostgreSQL-aŭtentigon uzi MD5 por lokaj ligoj", + "migration_0011_done": "Migrado sukcesis. Vi nun kapablas administri uzantajn grupojn.", + "migration_0011_LDAP_update_failed": "Ne povis ĝisdatigi LDAP. Eraro: {error:s}", + "pattern_password": "Devas esti almenaŭ 3 signoj longaj", + "root_password_desynchronized": "La pasvorta administranto estis ŝanĝita, sed YunoHost ne povis propagandi ĉi tion al la radika pasvorto!", + "service_remove_failed": "Ne povis forigi la servon '{service:s}'", + "migration_0003_fail2ban_upgrade": "Komenci la ĝisdatigon Fail2Ban…", + "backup_permission": "Rezerva permeso por app {app:s}", + "log_user_group_delete": "Forigi grupon '{}'", + "log_user_group_update": "Ĝisdatigi grupon '{}'", + "migration_0005_postgresql_94_not_installed": "PostgreSQL ne estis instalita en via sistemo. Nenio por fari.", + "dyndns_provider_unreachable": "Ne povas atingi Dyndns-provizanton {provider}: ĉu via YunoHost ne estas ĝuste konektita al la interreto aŭ la dynette-servilo malŝaltiĝas.", + "good_practices_about_user_password": "Vi nun estas por difini novan uzantan pasvorton. La pasvorto devas esti almenaŭ 8 signoj - kvankam estas bone praktiki uzi pli longan pasvorton (t.e. pasfrazon) kaj / aŭ variaĵon de signoj (majuskloj, minuskloj, ciferoj kaj specialaj signoj).", + "group_updated": "Ĝisdatigita \"{group}\" grupo", + "group_already_exist": "Grupo {group} jam ekzistas", + "group_already_exist_on_system": "Grupo {group} jam ekzistas en la sistemaj grupoj", + "group_cannot_be_edited": "La grupo {group} ne povas esti redaktita permane.", + "group_cannot_be_deleted": "La grupo {group} ne povas esti forigita permane.", + "group_update_failed": "Ne povis ĝisdatigi la grupon '{group}': {error}", + "group_user_already_in_group": "Uzanto {user} jam estas en grupo {group}", + "group_user_not_in_group": "Uzanto {user} ne estas en grupo {group}", + "installation_complete": "Kompleta instalado", + "log_category_404": "La loga kategorio '{category}' ne ekzistas", + "log_permission_create": "Krei permeson '{}'", + "log_permission_delete": "Forigi permeson '{}'", + "log_permission_urls": "Ĝisdatigu URLojn rilatajn al permeso '{}'", + "log_user_group_create": "Krei grupon '{}'", + "log_user_permission_update": "Mise à jour des accès pour la permission '{}'", + "log_user_permission_reset": "Restarigi permeson '{}'", + "mail_forward_remove_failed": "Ne povis forigi retpoŝton plusendante '{mail:s}'", + "migration_0011_rollback_success": "Sistemo ruliĝis reen.", + "migration_0011_update_LDAP_database": "Ĝisdatigante LDAP-datumbazon…", + "migration_0011_update_LDAP_schema": "Ĝisdatigante LDAP-skemon…", + "migration_0011_failed_to_remove_stale_object": "Malsukcesis forigi neokazan objekton {dn}: {error}", + "migrations_already_ran": "Tiuj migradoj estas jam faritaj: {ids}", + "migrations_no_such_migration": "Estas neniu migrado nomata {id}", + "permission_already_allowed": "Grupo '{group}' jam havas permeson '{permission}' ebligita'", + "permission_already_disallowed": "Grupo '{group}' jam havas permeson '{permission}' malebligita'", + "permission_cannot_remove_main": "Forigo de ĉefa permeso ne rajtas", + "permission_creation_failed": "Ne povis krei permeson '{permission}': {error}", + "tools_update_failed_to_app_fetchlist": "Ne povis ĝisdatigi la aparatojn de YunoHost ĉar: {error}", + "user_already_exists": "Uzanto {uzanto} jam ekzistas", + "migrations_pending_cant_rerun": "Tiuj migradoj ankoraŭ estas pritraktataj, do ne plu rajtas esti ekzekutitaj: {ids}", + "migrations_running_forward": "Kuranta migrado {id}…", + "migrations_success_forward": "Migrado {id} kompletigita", + "operation_interrupted": "La operacio estis permane interrompita?", + "permission_created": "Permesita '{permission:s}' kreita", + "permission_deleted": "Permesita \"{permission:s}\" forigita", + "permission_deletion_failed": "Ne povis forigi permeson '{permission}': {error}", + "permission_not_found": "Permesita \"{permission:s}\" ne trovita", + "restore_not_enough_disk_space": "Ne sufiĉa spaco (spaco: {free_space:d} B, necesa spaco: {needed_space:d} B, sekureca marĝeno: {margin:d} B)", + "tools_upgrade_regular_packages": "Nun ĝisdatigi 'regulajn' (ne-yunohost-rilatajn) pakojn …", + "tools_upgrade_special_packages_explanation": "Ĉi tiu ago finiĝos, sed la fakta speciala ĝisdatigo daŭros en fono. Bonvolu ne komenci iun alian agon en via servilo en la sekvaj ~ 10 minutoj (depende de via aparata rapideco). Unufoje mi plenumis, vi eble devos ensaluti en la retpaĝo. La ĝisdatiga registro estos havebla en Iloj → Madero (sur la retpaĝo) aŭ tra 'yunohost-registro-listo' (el la komandlinio).", + "unrestore_app": "App '{app:s}' ne restarigos", + "group_created": "Grupo '{group}' kreita", + "group_creation_failed": "Ne povis krei la grupon '{group}': {error}", + "group_deleted": "Grupo '{group}' forigita", + "group_deletion_failed": "Ne povis forigi la grupon '{group}': {error}", + "migrations_not_pending_cant_skip": "Tiuj migradoj ankoraŭ ne estas pritraktataj, do ne eblas preterlasi: {ids}", + "permission_already_exist": "Permesita '{permission}' jam ekzistas", + "domain_created": "Domajno kreita", + "migrate_tsig_wait_2": "2 minutoj …", + "log_user_create": "Aldonu uzanton '{}'", + "ip6tables_unavailable": "Vi ne povas ludi kun ip6tabloj ĉi tie. Vi estas en ujo aŭ via kerno ne subtenas ĝin", + "mail_unavailable": "Ĉi tiu retpoŝta adreso estas rezervita kaj aŭtomate estos atribuita al la unua uzanto", + "certmanager_domain_dns_ip_differs_from_public_ip": "La DNS 'A' rekordo por la domajno '{domain:s}' diferencas de ĉi tiu IP-servilo. Se vi lastatempe modifis vian A-registron, bonvolu atendi ĝin propagandi (iuj DNS-disvastigaj kontroliloj estas disponeblaj interrete). (Se vi scias, kion vi faras, uzu '--no-checks' por malŝalti tiujn ĉekojn.)", + "tools_upgrade_special_packages_completed": "Plenumis la ĝisdatigon de pakaĵoj de YunoHost.\nPremu [Enter] por retrovi la komandlinion", + "log_remove_on_failed_install": "Forigu '{}' post malsukcesa instalado", + "regenconf_file_manually_modified": "La agorddosiero '{conf}' estis modifita permane kaj ne estos ĝisdatigita", + "regenconf_would_be_updated": "La agordo estus aktualigita por la kategorio '{category}'", + "certmanager_cert_install_success_selfsigned": "Mem-subskribita atestilo nun instalita por la domajno '{domain:s}'", + "global_settings_unknown_setting_from_settings_file": "Nekonata ŝlosilo en agordoj: '{setting_key:s}', forĵetu ĝin kaj konservu ĝin en /etc/yunohost/settings-unknown.json", + "regenconf_file_backed_up": "Agordodosiero '{conf}' estis rezervita al '{backup}'", + "migration_0007_cannot_restart": "SSH ne rekomencas post provi nuligi la migradan numeron 6.", + "migration_description_0006_sync_admin_and_root_passwords": "Sinkronigu admin kaj radikajn pasvortojn", + "updating_app_lists": "Akirante haveblajn ĝisdatigojn por aplikoj…", + "iptables_unavailable": "Vi ne povas ludi kun iptables ĉi tie. Vi estas en ujo aŭ via kerno ne subtenas ĝin", + "global_settings_cant_write_settings": "Ne eblis konservi agordojn, tial: {reason:s}", + "service_added": "La servo '{service:s}' aldonis", + "upnp_disabled": "UPnP malŝaltis", + "service_started": "Servo '{service:s}' komenciĝis", + "port_already_opened": "Haveno {port:d} estas jam malfermita por {ip_version:s} rilatoj", + "installation_failed": "Io okazis malbone kun la instalado", + "network_check_mx_ko": "DNS MX-rekordo ne estas agordita", + "migrate_tsig_wait_3": "1 minuto …", + "certmanager_conflicting_nginx_file": "Ne povis prepari domajnon por ACME-defio: la agordo de NGINX {filepath:s} konfliktas kaj unue devas esti forigita", + "upgrading_packages": "Ĝisdatigi pakojn…", + "custom_app_url_required": "Vi devas provizi URL por altgradigi vian kutimon app {app: s}", + "service_reload_failed": "Ne povis reŝargi la servon '{service:s}'\n\nLastatempaj servaj protokoloj: {logs:s}", + "packages_upgrade_failed": "Ne povis ĝisdatigi ĉiujn pakojn", + "hook_json_return_error": "Ne povis legi revenon de hoko {path:s}. Eraro: {msg:s}. Kruda enhavo: {raw_content}", + "dyndns_cron_removed": "DynDNS cron-laboro forigita", + "dyndns_key_not_found": "DNS-ŝlosilo ne trovita por la domajno", + "custom_appslist_name_required": "Vi devas doni nomon por via kutima app-listo", + "tools_upgrade_regular_packages_failed": "Ne povis ĝisdatigi pakojn: {packages_list}", + "service_start_failed": "Ne povis komenci la servon '{service:s}'\n\nLastatempaj servaj protokoloj: {logs:s}", + "service_reloaded": "Servo '{service:s}' reŝargita", + "system_upgraded": "Sistemo ĝisdatigita", + "domain_deleted": "Domajno forigita", + "certmanager_acme_not_configured_for_domain": "Atestilo por la domajno '{domain:s}' ne ŝajnas esti ĝuste instalita. Bonvolu ekzekuti 'cert-instali' por ĉi tiu regado unue.", + "migration_description_0009_decouple_regenconf_from_services": "Malkonstruu la regen-konf-mekanismon de servoj", + "user_update_failed": "Ne povis ĝisdatigi uzanton {user}: {error}", + "migration_description_0008_ssh_conf_managed_by_yunohost_step2": "Lasu la SSH-agordon estu administrata de YunoHost (paŝo 2, manlibro)", + "restore_confirm_yunohost_installed": "Ĉu vi vere volas restarigi jam instalitan sistemon? [{answers:s}]", + "pattern_positive_number": "Devas esti pozitiva nombro", + "monitor_stats_file_not_found": "Statistika dosiero ne trovita", + "certmanager_error_no_A_record": "Neniu DNS 'A' rekordo trovita por '{domain:s}'. Vi bezonas atentigi vian domajnan nomon al via maŝino por povi instali atestilon Lasu-Ĉifri. (Se vi scias, kion vi faras, uzu '--no-checks' por malŝalti tiujn ĉekojn.)", + "update_apt_cache_failed": "Ne eblis ĝisdatigi la kaŝmemoron de APT (paka administranto de Debian). Jen rubujo de la sources.list-linioj, kiuj povus helpi identigi problemajn liniojn:\n{sourcelist}", + "migrations_no_migrations_to_run": "Neniuj migradoj por funkcii", + "executing_command": "Plenumanta komandon '{command:s}' …", + "diagnosis_no_apps": "Neniu instalita apliko", + "certmanager_attempt_to_renew_nonLE_cert": "La atestilo por la domajno '{domain:s}' ne estas elsendita de Let's Encrypt. Ne eblas renovigi ĝin aŭtomate!", + "global_settings_setting_example_bool": "Ekzemplo bulea elekto", + "domain_dyndns_already_subscribed": "Vi jam abonis DynDNS-domajnon", + "log_letsencrypt_cert_renew": "Renovigu '{}' Ni ĉifru atestilon", + "migrate_tsig_start": "Detektita ŝlosila algoritmo nesufiĉa por TSIG-subskribo de la domajno '{domain}', komencanta migradon al la pli sekura HMAC-SHA-512", + "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 malhelpi kritikajn pakojn…", + "diagnosis_monitor_disk_error": "Ne povis monitori diskojn: {error}", + "log_link_to_log": "Plena ŝtipo de ĉi tiu operacio: ' {desc} '", + "service_no_log": "Neniu registro por montri por servo '{service:s}'", + "global_settings_cant_serialize_settings": "Ne eblis serialigi datumojn pri agordoj, motivo: {reason:s}", + "backup_running_hooks": "Kurado de apogaj hokoj …", + "package_not_installed": "Pako '{pkgname}' ne estas instalita", + "certmanager_domain_unknown": "Nekonata domajno '{domain:s}'", + "unexpected_error": "Io neatendita iris malbone: {error}", + "password_listed": "Ĉi tiu pasvorto estas inter la plej uzataj pasvortoj en la mondo. Bonvolu elekti ion pli unikan.", + "ssowat_persistent_conf_write_error": "Ne povis konservi konstantan SSOwat-agordon: {error:s}. Redakti /etc/ssowat/conf.json.persistent dosiero por ripari la Jaks-sintakson", + "migration_description_0007_ssh_conf_managed_by_yunohost_step1": "Lasu la SSH-agordon estu administrata de YunoHost (paŝo 1, aŭtomata)", + "migration_0009_not_needed": "Ĉi tiu migrado jam iel okazis ... (?) Saltado.", + "ssowat_conf_generated": "SSOwat-agordo generita", + "migrate_tsig_wait": "Atendante tri minutojn por ke la servilo DynDNS enkalkulu la novan ŝlosilon …", + "log_remove_on_failed_restore": "Forigu '{}' post malsukcesa restarigo de rezerva ar archiveivo", + "dpkg_is_broken": "Vi ne povas fari ĉi tion nun ĉar dpkg/APT (la administrantoj pri pakaĵaj sistemoj) ŝajnas esti rompita stato ... Vi povas provi solvi ĉi tiun problemon per konekto per SSH kaj funkcianta `sudo dpkg --configure -a`.", + "recommend_to_add_first_user": "La postinstalo finiĝis, sed YunoHost bezonas almenaŭ unu uzanton por funkcii ĝuste, vi devas aldoni unu uzante 'yunohost user create ' aŭ fari ĝin de la administra interfaco.", + "certmanager_cert_signing_failed": "Ne povis subskribi la novan atestilon", + "migration_description_0003_migrate_to_stretch": "Altgradigu la sistemon al Debian Stretch kaj YunoHost 3.0", + "log_tools_upgrade": "Ĝisdatigu sistemajn pakaĵojn", + "network_check_smtp_ko": "Ekstera retpoŝto (SMTP-haveno 25) ŝajnas esti blokita de via reto", + "log_available_on_yunopaste": "Ĉi tiu protokolo nun haveblas per {url}", + "certmanager_http_check_timeout": "Ekdifinita kiam servilo provis kontakti sin per HTTP per publika IP-adreso (domajno '{domain:s}' kun IP '{ip:s}'). Vi eble spertas haŭtoproblemon, aŭ la fajroŝirmilo / enkursigilo antaŭ via servilo miskonfiguras.", + "pattern_port_or_range": "Devas esti valida haveno-nombro (t.e. 0-65535) aŭ gamo da havenoj (t.e. 100:200)", + "migrations_loading_migration": "Ŝarĝante migradon {id}…", + "port_available": "Haveno {port:d} estas havebla", + "pattern_mailbox_quota": "Devas esti grandeco kun la sufikso b/k/M/G/T aŭ 0 por ne havi kvoton", + "migration_0008_general_disclaimer": "Por plibonigi la sekurecon de via servilo, rekomendas lasi YunoHost administri la SSH-agordon. Via nuna SSH-aranĝo diferencas de la rekomendo. Se vi lasas YunoHost agordi ĝin, la maniero per kiu vi konektas al via servilo per SSH ŝanĝiĝos tiel:", + "user_deletion_failed": "Ne povis forigi uzanton {user}: {error}", + "backup_with_no_backup_script_for_app": "La app '{app:s}' ne havas sekretan skripton. Ignorante.", + "service_regen_conf_is_deprecated": "'yunohost service regen-conf' malakceptas! Bonvolu uzi anstataŭe 'yunohost tools regen-conf'.", + "global_settings_key_doesnt_exists": "La ŝlosilo '{settings_key:s}' ne ekzistas en la tutmondaj agordoj, vi povas vidi ĉiujn disponeblajn klavojn per uzado de 'yunohost settings list'", + "dyndns_no_domain_registered": "Neniu domajno registrita ĉe DynDNS", + "dyndns_could_not_check_available": "Ne povis kontroli ĉu {domain:s} haveblas sur {provider:s}.", + "log_app_removelist": "Forigu aplikan liston", + "global_settings_setting_example_enum": "Ekzemplo enum elekto", + "hook_exec_not_terminated": "Skripto ne finiĝis ĝuste: {path:s}", + "service_stopped": "'{service:s}' servo ĉesis", + "restore_failed": "Ne povis restarigi sistemon", + "confirm_app_install_danger": "Danĝero! Ĉi tiu apliko estas konata ankoraŭ eksperimenta (se ne eksplicite ne funkcias)! Vi probable ne devas instali ĝin krom se vi scias kion vi faras. NENIU SUBTENO estos provizita se ĉi tiu app ne funkcias aŭ rompas vian sistemon ... Se vi pretas riski ĉiuokaze, tajpu '{answers: s}'", + "log_operation_unit_unclosed_properly": "Operaciumo ne estis fermita ĝuste", + "upgrade_complete": "Ĝisdatigo kompleta", + "upnp_enabled": "UPnP ŝaltis", + "mailbox_used_space_dovecot_down": "La retpoŝta servo de Dovecot devas funkcii, se vi volas akcepti uzitan poŝtan spacon", + "restore_system_part_failed": "Ne povis restarigi la sisteman parton '{part:s}'", + "diagnosis_monitor_system_error": "Ne povis monitori sistemon: {error}", + "service_stop_failed": "Ne povis maldaŭrigi la servon '{service:s}'\n\nLastatempaj servaj protokoloj: {logs:s}", + "unbackup_app": "App '{app:s}' ne konserviĝos", + "updating_apt_cache": "Akirante haveblajn ĝisdatigojn por sistemaj pakoj…", + "tools_upgrade_at_least_one": "Bonvolu specifi '--apps' aŭ '--system'", + "service_already_stopped": "La servo '{service:s}' jam ĉesis", + "unit_unknown": "Nekonata unuo '{unit:s}'", + "migration_0003_modified_files": "Bonvolu noti, ke la jenaj dosieroj estis trovitaj mane kaj modifitaj kaj povus esti anstataŭigitaj sekve de la ĝisdatigo: {manual_modified_files}", + "tools_upgrade_cant_both": "Ne eblas ĝisdatigi ambaŭ sistemon kaj programojn samtempe", + "restore_extracting": "Eltirante bezonatajn dosierojn el la ar theivo…", + "upnp_port_open_failed": "Ne povis malfermi havenon per UPnP", + "log_app_upgrade": "Ĝisdatigu la aplikon '{}'", + "log_help_to_get_failed_log": "La operacio '{desc}' ne povis finiĝi. Bonvolu dividi la plenan ŝtipon de ĉi tiu operacio per la komando 'yunohost log display {name} --share' por akiri helpon", + "migration_description_0002_migrate_to_tsig_sha256": "Plibonigu sekurecon de DynDNS TSIG-ĝisdatigoj per SHA-512 anstataŭ MD5", + "monitor_disabled": "Servila monitorado nun malŝaltis", + "pattern_port": "Devas esti valida havena numero (t.e. 0-65535)", + "port_already_closed": "Haveno {port:d} estas jam fermita por {ip_version:s} rilatoj", + "hook_name_unknown": "Nekonata hoko-nomo '{name:s}'", + "migration_0003_system_not_fully_up_to_date": "Via sistemo ne estas plene ĝisdata. Bonvolu plenumi regulan ĝisdatigon antaŭ ol ruli la migradon al Stretch.", + "dyndns_could_not_check_provide": "Ne povis kontroli ĉu {provider:s} povas provizi {domain:s}.", + "dyndns_cron_remove_failed": "Ne povis forigi la cron-laboron DynDNS ĉar: {error}", + "pattern_listname": "Devas esti nur alfanumeraj kaj substrekaj signoj", + "restore_nothings_done": "Nenio estis restarigita", + "log_tools_postinstall": "Afiŝu vian servilon YunoHost", + "dyndns_unavailable": "La domajno '{domain:s}' ne haveblas.", + "experimental_feature": "Averto: Ĉi tiu funkcio estas eksperimenta kaj ne konsiderata stabila, vi ne uzu ĝin krom se vi scias kion vi faras.", + "root_password_replaced_by_admin_password": "Via radika pasvorto estis anstataŭigita per via administra pasvorto.", + "ssowat_persistent_conf_read_error": "Ne povis legi konstantan SSOwat-agordon: {error:s}. Redakti /etc/ssowat/conf.json.persistent dosiero por ripari la Jaks-sintakson", + "migration_description_0005_postgresql_9p4_to_9p6": "Migru datumbazojn de PostgreSQL 9.4 al 9.6", + "migration_0008_root": "• Vi ne povos konekti kiel radiko per SSH. Anstataŭe vi uzu la administran uzanton;", + "package_unknown": "Nekonata pako '{pkgname}'", + "domain_unknown": "Nekonata domajno", + "global_settings_setting_security_password_user_strength": "Uzanto pasvorta forto", + "restore_may_be_not_enough_disk_space": "Via sistemo ŝajnas ne havi sufiĉe da spaco (free:{free_space:d} B, necesa spaco: {needed_space:d} B, sekureca marĝeno: {margin:d} B)", + "log_corrupted_md_file": "La YAD-metadata dosiero asociita kun protokoloj estas damaĝita: '{md_file}\nEraro: {error} '", + "downloading": "Elŝutante …", + "user_deleted": "Uzanto forigita", + "service_enable_failed": "Ne eblis ŝalti la servon '{service:s}'\n\nLastatempaj servaj protokoloj: {logs:s}", + "tools_upgrade_special_packages": "Nun ĝisdatigi 'specialajn' (rilatajn al yunohost)…", + "domains_available": "Haveblaj domajnoj:", + "dyndns_registered": "Registrita domajno DynDNS", + "service_description_fail2ban": "Protektas kontraŭ bruta forto kaj aliaj specoj de atakoj de la interreto", + "file_does_not_exist": "La dosiero {path:s} ne ekzistas.", + "yunohost_not_installed": "YunoHost estas malĝuste aŭ ne ĝuste instalita. Bonvolu prilabori 'yunohost tools postinstall'", + "migration_0005_postgresql_96_not_installed": "PostgreSQL 9.4 estas instalita, sed ne postgresql 9.6‽ Io stranga eble okazis en via sistemo: (…", + "restore_removing_tmp_dir_failed": "Ne povis forigi malnovan provizoran dosierujon", + "certmanager_cannot_read_cert": "Io malbona okazis, kiam mi provis malfermi aktualan atestilon por domajno {domain:s} (dosiero: {file:s}), kialo: {reason:s}", + "service_removed": "'{service:s}' servo forigita", + "certmanager_hit_rate_limit": "Tro multaj atestiloj jam eldonitaj por ĉi tiu ĝusta aro de domajnoj {domain:s} antaŭ nelonge. Bonvolu reprovi poste. Vidu https://letsencrypt.org/docs/rate-limits/ por pliaj detaloj", + "migration_0005_not_enough_space": "Disponigu sufiĉan spacon en {path} por ruli la migradon.", + "pattern_firstname": "Devas esti valida antaŭnomo", + "migration_description_0010_migrate_to_apps_json": "Forigu malvalorigitajn aparatojn kaj uzu anstataŭe la novan unuigitan liston \"apps.json\"", + "domain_cert_gen_failed": "Ne povis generi atestilon", + "regenconf_file_kept_back": "La agorda dosiero '{conf}' estas atendita forigi per regen-conf (kategorio {category}), sed ĝi estis konservita.", + "migrate_tsig_wait_4": "30 sekundoj …", + "backup_with_no_restore_script_for_app": "La apliko \"{app:s}\" ne havas restarigan skripton, vi ne povos aŭtomate restarigi la sekurkopion de ĉi tiu apliko.", + "log_letsencrypt_cert_install": "Instalu atestilon Ni ĉifru sur '{}' regado", + "log_dyndns_update": "Ĝisdatigu la IP asociita kun via subdominio YunoHost '{}'", + "firewall_reload_failed": "Ne eblis reŝargi la firewall", + "confirm_app_install_warning": "Averto: Ĉi tiu aplikaĵo povas funkcii, sed ne bone integras en YunoHost. Iuj funkcioj kiel ekzemple aliĝilo kaj sekurkopio / restarigo eble ne haveblos. Instali ĉiuokaze? [{answers: s}] ", + "log_user_delete": "Forigi uzanton '{}'", + "dyndns_ip_updated": "Ĝisdatigis vian IP sur DynDNS", + "regenconf_up_to_date": "La agordo jam estas ĝisdatigita por kategorio '{category}'", + "migration_0003_patching_sources_list": "Patching the sources.lists …", + "global_settings_setting_security_ssh_compatibility": "Kongruo vs sekureca kompromiso por la SSH-servilo. Afektas la ĉifradojn (kaj aliajn aspektojn pri sekureco)", + "migrations_need_to_accept_disclaimer": "Por funkciigi la migradon {id}, via devas akcepti la sekvan malakcepton:\n---\n{malavantaĝo}\n---\nSe vi akceptas funkcii la migradon, bonvolu rekonduki la komandon kun la opcio '--accept-disclaimer'.", + "regenconf_file_remove_failed": "Ne povis forigi la agordodosieron '{conf}'", + "not_enough_disk_space": "Ne sufiĉe libera spaco sur '{path:s}'", + "migration_0006_disclaimer": "YunoHost nun atendas ke pasvortoj kaj administrantoj estu sinkronigitaj. Per ekzekuto de ĉi tiu migrado, via radika pasvorto estos anstataŭigita per administra pasvorto.", + "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", + "monitor_glances_con_failed": "Ne povis konektiĝi al servilo de Glances", + "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_app_fetchlist": "Aldonu liston de aplikoj", + "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", + "dyndns_registration_failed": "Ne povis registri DynDNS-domajnon: {error:s}", + "migration_0003_not_jessie": "La nuna Debian-distribuo ne estas Jessie!", + "user_unknown": "Nekonata uzanto: {user:s}", + "migrations_to_be_ran_manually": "Migrado {id} devas funkcii permane. Bonvolu iri al Iloj → Migradoj en la retpaĝa paĝo, aŭ kuri `yunohost tools migrations migrate`.", + "migration_0008_warning": "Se vi komprenas tiujn avertojn kaj konsentas lasi YunoHost pretervidi vian nunan agordon, faru la migradon. Alie, vi ankaŭ povas salti la migradon - kvankam ĝi ne rekomendas.", + "certmanager_cert_renew_success": "Ni Ĉifru atestilon renovigitan por la domajno '{domain:s}'", + "global_settings_reset_success": "Antaŭaj agordoj nun estas rezervitaj al {path:s}", + "pattern_domain": "Devas esti valida domajna nomo (t.e. mia-domino.org)", + "package_unexpected_error": "Neatendita eraro okazis prilaborante la pakon '{pkgname}'", + "dyndns_key_generating": "Generi DNS-ŝlosilon ... Eble daŭros iom da tempo.", + "restore_running_app_script": "Restarigi la programon '{app:s}'…", + "migrations_skip_migration": "Salti migradon {id}…", + "mysql_db_init_failed": "MysQL-datumbazo init malsukcesis", + "regenconf_file_removed": "Agordodosiero '{conf}' forigita", + "log_tools_shutdown": "Enŝaltu vian servilon", + "password_too_simple_3": "La pasvorto bezonas almenaŭ 8 signojn kaj enhavas ciferon, majusklon, pli malaltan kaj specialajn signojn", + "migration_0003_general_warning": "Bonvolu noti, ke ĉi tiu migrado estas delikata operacio. La teamo de YunoHost faris sian plej bonan revizii kaj testi ĝin, sed la migrado eble ankoraŭ rompos partojn de la sistemo aŭ ĝiaj programoj.\n\nTial oni rekomendas al:\n - Elfari kopion de iuj kritikaj datumoj aŭ app. Pliaj informoj pri https://yunohost.org/backup;\n - Paciencu post lanĉo de la migrado: Depende de via interreta konekto kaj aparataro, eble daŭros kelkaj horoj ĝis ĉio ĝisdatigi.\n\nAldone, la haveno por SMTP, uzata de eksteraj retpoŝtaj klientoj (kiel Thunderbird aŭ K9-Mail) estis ŝanĝita de 465 (SSL / TLS) al 587 (STARTTLS). La malnova haveno (465) aŭtomate fermiĝos, kaj la nova haveno (587) malfermiĝos en la fajrejo. Vi kaj viaj uzantoj * devos adapti la agordon de viaj retpoŝtaj klientoj laŭe.", + "diagnosis_kernel_version_error": "Ne povis akiri la kernan version: {error}", + "global_settings_setting_example_int": "Ekzemple int elekto", + "backup_output_symlink_dir_broken": "Vi havas rompitan simbolon anstataŭ via arkiva dosierujo '{path:s}'. Vi eble havas specifan agordon por sekurkopi viajn datumojn en alia dosiersistemo, ĉi-kaze vi probable forgesis remeti aŭ enŝovi vian malmolan disko aŭ ŝlosilon USB.", + "good_practices_about_admin_password": "Vi nun estas por difini novan administran pasvorton. La pasvorto devas esti almenaŭ 8 signoj - kvankam estas bone praktiki uzi pli longan pasvorton (t.e. pasfrazon) kaj / aŭ uzi variaĵon de signoj (majuskloj, minuskloj, ciferoj kaj specialaj signoj).", + "certmanager_attempt_to_renew_valid_cert": "La atestilo por la domajno '{domain:s}' ne finiĝos! (Vi eble uzos --force se vi scias kion vi faras)", + "restore_running_hooks": "Kurantaj restarigaj hokoj…", + "regenconf_pending_applying": "Aplikante pritraktata agordo por kategorio '{category}'…", + "service_description_dovecot": "Permesas al retpoŝtaj klientoj aliri / serĉi retpoŝton (per IMAP kaj POP3)", + "domain_dns_conf_is_just_a_recommendation": "Ĉi tiu komando montras al vi la *rekomenditan* agordon. Ĝi efektive ne agordas la DNS-agordon por vi. Via respondeco agordi vian DNS-zonon en via registristo laŭ ĉi tiu rekomendo.", + "backup_php5_to_php7_migration_may_fail": "Ne povis konverti vian ar archiveivon por subteni PHP 7, vi eble ne povas restarigi viajn PHP-programojn (kialo: {error:s})", + "log_backup_restore_system": "Restarigi sistemon de rezerva arkivo", + "log_app_change_url": "Ŝanĝu la URL de apliko '{}'", + "service_already_started": "La servo '{service:s}' estas jam komencita", + "license_undefined": "nedifinita", + "global_settings_setting_security_password_admin_strength": "Admin pasvorta forto", + "service_reload_or_restart_failed": "Ne povis reŝargi aŭ rekomenci la servon '{service:s}'\n\nLastatempaj servaj protokoloj: {logs:s}", + "migrations_list_conflict_pending_done": "Vi ne povas uzi ambaŭ '--previous' kaj '--done' samtempe.", + "maindomain_changed": "La ĉefa domajno nun ŝanĝiĝis", + "server_shutdown_confirm": "La servilo haltos tuj, ĉu vi certas? [{answers:s}]", + "monitor_period_invalid": "Nevalida tempoperiodo", + "log_backup_restore_app": "Restarigu '{}' de rezerva ar archiveivo", + "log_does_exists": "Ne estas operacio-registro kun la nomo '{log}', uzu 'yunohost loglist' por vidi ĉiujn disponeblajn operaciojn", + "service_add_failed": "Ne povis aldoni la servon '{service:s}'", + "pattern_password_app": "Bedaŭrinde, pasvortoj ne povas enhavi jenajn signojn: {forbidden_chars}", + "this_action_broke_dpkg": "Ĉi tiu ago rompis dpkg / APT (la administrantoj pri la paka sistemo) ... Vi povas provi solvi ĉi tiun problemon per konekto per SSH kaj funkcianta `sudo dpkg --configure -a`.", + "log_regen_conf": "Regeneri sistemajn agordojn '{}'", + "restore_hook_unavailable": "La restariga skripto por '{part:s}' ne haveblas en via sistemo kaj ankaŭ ne en la ar theivo", + "network_check_smtp_ok": "Eksteren retpoŝto (SMTP-haveno 25) ne estas blokita", + "log_dyndns_subscribe": "Aboni al YunoHost-subdominio '{}'", + "password_too_simple_4": "La pasvorto bezonas almenaŭ 12 signojn kaj enhavas ciferon, majuskle, pli malaltan kaj specialajn signojn", + "migration_0003_main_upgrade": "Komencanta ĉefa ĝisdatigo …", + "user_info_failed": "Ne povis akiri informojn pri uzanto", + "regenconf_file_updated": "Agordodosiero '{conf}' ĝisdatigita", + "log_help_to_get_log": "Por vidi la protokolon de la operacio '{desc}', uzu la komandon 'yunohost log display {name}'", + "global_settings_setting_security_nginx_compatibility": "Kongruo vs sekureca kompromiso por la TTT-servilo NGINX. Afektas la ĉifradojn (kaj aliajn aspektojn pri sekureco)", + "no_internet_connection": "Servilo ne konektita al la interreto", + "migration_0008_dsa": "• La DSA-ŝlosilo estos malŝaltita. Tial vi eble bezonos nuligi spuran averton de via SSH-kliento kaj revizii la fingrospuron de via servilo;", + "migration_0003_restoring_origin_nginx_conf": "Fileia dosiero /etc/nginx/nginx.conf estis iel redaktita. La migrado reaperos unue al sia originala stato ... La antaŭa dosiero estos havebla kiel {backup_dest}.", + "migrate_tsig_end": "Migrado al HMAC-SHA-512 finiĝis", + "restore_complete": "Restarigita", + "certmanager_couldnt_fetch_intermediate_cert": "Ekvilibrigita kiam vi provis ricevi interajn atestilojn de Let's Encrypt. Atestita instalado / renovigo nuligita - bonvolu reprovi poste.", + "hook_exec_failed": "Ne povis funkcii skripto: {path:s}", + "global_settings_cant_open_settings": "Ne eblis malfermi agordojn, tial: {reason:s}", + "user_created": "Uzanto kreita", + "service_description_avahi-daemon": "Permesas al vi atingi vian servilon uzante 'yunohost.local' en via loka reto", + "certmanager_attempt_to_replace_valid_cert": "Vi provas anstataŭigi bonan kaj validan atestilon por domajno {domajno:s}! (Uzu --forte pretervidi)", + "monitor_stats_period_unavailable": "Ne ekzistas disponeblaj statistikoj por la periodo", + "regenconf_updated": "Agordo por kategorio '{category}' ĝisdatigita", + "update_apt_cache_warning": "Io iris malbone dum la ĝisdatigo de la kaŝmemoro de APT (paka administranto de Debian). Jen rubujo de la sources.list-linioj, kiuj povus helpi identigi problemajn liniojn:\n{sourcelist}", + "regenconf_dry_pending_applying": "Kontrolado de pritraktata agordo, kiu estus aplikita por kategorio '{category}'…", + "regenconf_file_copy_failed": "Ne povis kopii la novan agordodosieron '{new}' al '{conf}'", + "global_settings_setting_example_string": "Ekzemple korda elekto", + "restore_already_installed_app": "App kun la ID '{app:s}' estas jam instalita", + "mountpoint_unknown": "Nekonata montpunkto", + "log_tools_maindomain": "Faru de '{}' la ĉefa domajno", + "maindomain_change_failed": "Ne povis ŝanĝi la ĉefan domajnon", + "mail_domain_unknown": "Nekonata retpoŝtadreso por domajno '{domain:s}'", + "migrations_cant_reach_migration_file": "Ne povis aliri migrajn dosierojn ĉe la vojo% s", + "pattern_email": "Devas esti valida retpoŝtadreso (t.e.iu@domain.org)", + "mail_alias_remove_failed": "Ne povis forigi retpoŝton alias '{mail:s}'", + "regenconf_file_manually_removed": "La dosiero de agordo '{conf}' estis forigita permane, kaj ne estos kreita", + "monitor_enabled": "Servila monitorado nun ŝaltis", + "domain_exists": "La domajno jam ekzistas", + "migration_description_0001_change_cert_group_to_sslcert": "Ŝanĝu grupajn permesojn de 'metronomo' al 'ssl-cert'", + "mysql_db_creation_failed": "MySQL-datumbazkreado malsukcesis", + "ldap_initialized": "LDAP inicializis", + "migrate_tsig_not_needed": "Vi ne ŝajnas uzi DynDNS-domajnon, do neniu migrado necesas.", + "certmanager_domain_cert_not_selfsigned": "La atestilo por domajno {domajno:s} ne estas mem-subskribita. Ĉu vi certas, ke vi volas anstataŭigi ĝin? (Uzu '--force' por fari tion.)", + "certmanager_unable_to_parse_self_CA_name": "Ne povis trapasi nomon de mem-subskribinta aŭtoritato (dosiero: {file: s})", + "log_selfsigned_cert_install": "Instalu mem-subskribitan atestilon sur '{}' domajno", + "log_tools_reboot": "Reklamu vian servilon", + "certmanager_cert_install_success": "Ni Ĉifru atestilon nun instalitan por la domajno '{domain:s}'", + "global_settings_bad_choice_for_enum": "Malbona elekto por agordo {setting:s}, ricevita '{choice:s}', sed disponeblaj elektoj estas: {available_choices:s}", + "server_shutdown": "La servilo haltos", + "log_tools_migrations_migrate_forward": "Migri antaŭen", + "migration_0008_no_warning": "Neniu grava risko identigita pri superregado de via SSH-agordo, tamen oni ne povas esti absolute certa;)! Ekfunkciu la migradon por superregi ĝin. Alie, vi ankaŭ povas salti la migradon - kvankam ĝi ne rekomendas.", + "regenconf_now_managed_by_yunohost": "La agorda dosiero '{conf}' nun estas administrata de YunoHost (kategorio {category}).", + "server_reboot_confirm": "Ĉu la servilo rekomencos tuj, ĉu vi certas? [{answers:s}]", + "log_app_install": "Instalu la aplikon '{}'", + "service_description_dnsmasq": "Traktas rezolucion de domajna nomo (DNS)", + "global_settings_unknown_type": "Neatendita situacio, la agordo {setting:s} ŝajnas havi la tipon {unknown_type:s} sed ĝi ne estas tipo subtenata de la sistemo.", + "migration_0003_problematic_apps_warning": "Bonvolu noti, ke la sekvaj eventuale problemaj instalitaj apps estis detektitaj. Ĝi aspektas, ke tiuj ne estis instalitaj de aparato aŭ ne estas markitaj kiel \"funkciantaj\". Tial ne eblas garantii, ke ili ankoraŭ funkcios post la ĝisdatigo: {problematic_apps}", + "domain_hostname_failed": "Ne povis agordi novan gastigilon. Ĉi tio eble kaŭzos problemon poste (eble bone).", + "server_reboot": "La servilo rekomenciĝos", + "regenconf_failed": "Ne povis regeneri la agordon por kategorio(j): {categories}", + "domain_uninstall_app_first": "Unu aŭ pluraj programoj estas instalitaj en ĉi tiu domajno. Bonvolu malinstali ilin antaŭ ol daŭrigi la domajnan forigon", + "port_unavailable": "Haveno {port:d} ne haveblas", + "service_unknown": "Nekonata servo '{service:s}'", + "migration_0003_start": "Komencante migradon al Stretch. La protokoloj haveblos en {logfile}.", + "monitor_stats_no_update": "Neniuj monitoradaj statistikoj ĝisdatigi", + "domain_deletion_failed": "Ne povis forigi domajnon {domain}: {error}", + "log_user_update": "Ĝisdatigu uzantinformojn de '{}'", + "user_creation_failed": "Ne povis krei uzanton {user}: {error}", + "migrations_migration_has_failed": "Migrado {id} ne kompletigis, abolis. Eraro: {exception}", + "done": "Farita", + "log_domain_remove": "Forigi domon '{}' de agordo de sistemo", + "monitor_not_enabled": "Servila monitorado estas malŝaltita", + "diagnosis_debian_version_error": "Ne povis retrovi la Debianan version: {error}", + "hook_list_by_invalid": "Ĉi tiu posedaĵo ne povas esti uzata por listigi hokojn", + "confirm_app_install_thirdparty": "Danĝero! Ĉi tiu apliko ne estas parto de la aplika katalogo de Yunohost. Instali triajn aplikojn povas kompromiti la integrecon kaj sekurecon de via sistemo. Vi probable ne devas instali ĝin krom se vi scias kion vi faras. NENIU SUBTENO estos provizita se ĉi tiu app ne funkcias aŭ rompas vian sistemon ... Se vi pretas riski ĉiuokaze, tajpu '{answers: s}'", + "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Permesu uzon de (malaktuala) DSA-hostkey por la agordo de daemon SSH", + "dyndns_domain_not_provided": "Provizanto DynDNS {provider:s} ne povas provizi domajnon {domain:s}.", + "backup_unable_to_organize_files": "Ne povis uzi la rapidan metodon por organizi dosierojn en la ar archiveivo", + "password_too_simple_2": "La pasvorto bezonas almenaŭ 8 signojn kaj enhavas ciferon, majusklojn kaj minusklojn", + "executing_script": "Plenumanta skripto '{script:s}' …", + "service_cmd_exec_failed": "Ne povis plenumi la komandon '{command:s}'", + "migration_0007_cancelled": "YunoHost ne plibonigis la administradon de via SSH-konf.", + "migrate_tsig_failed": "Ne povis migri la DynDNS-domajnon '{domain}' al HMAC-SHA-512, ruliĝante. Eraro: {error_code}, {error}", + "pattern_lastname": "Devas esti valida familinomo", + "service_enabled": "'{service:s}' servo malŝaltita", + "certmanager_no_cert_file": "Ne povis legi la atestan dosieron por la domajno {domain:s} (dosiero: {file:s})", + "migration_0008_port": "• Vi devos konekti uzante la havenon 22 anstataŭ via nuna kutimo SSH-haveno. Sentu vin libera reconfiguri ĝin;", + "domain_creation_failed": "Ne povis krei domajnon {domain}: {error}", + "certmanager_domain_http_not_working": "Ŝajnas ke la domajno {domain:s} ne atingeblas per HTTP. Kontrolu, ke via DNS kaj NGINX-agordo ĝustas", + "domain_cannot_remove_main": "Ne eblas forigi ĉefan domajnon. Fiksu unu unue", + "service_reloaded_or_restarted": "'{service:s}' servo reŝarĝis aŭ rekomencis", + "mysql_db_initialized": "La datumbazo MySQL jam estas pravalorizita", + "log_domain_add": "Aldonu '{}' domajnon en sisteman agordon", + "global_settings_bad_type_for_setting": "Malbona tipo por agordo {setting:s}, ricevita {received_type:s}, atendata {expected_type:s}", + "unlimit": "Neniu kvoto", + "dyndns_cron_installed": "Kreita laboro DynDNS cron", + "system_username_exists": "Uzantnomo jam ekzistas en la listo de uzantoj de sistemo", + "firewall_reloaded": "Fajroŝirmilo reŝarĝis", + "service_restarted": "'{service:s}' servo rekomencis", + "pattern_username": "Devas esti minuskulaj literoj kaj minuskloj nur", + "extracting": "Eltirante…", + "restore_app_failed": "Ne povis restarigi la programon '{app:s}'", + "yunohost_configured": "YunoHost nun agordis", + "certmanager_self_ca_conf_file_not_found": "Ne povis trovi agorddosieron por mem-subskriba aŭtoritato (dosiero: {file:s})", + "log_app_remove": "Forigu la aplikon '{}'", + "service_restart_failed": "Ne povis rekomenci la servon '{service:s}'\n\nLastatempaj servaj protokoloj: {logs:s}", + "firewall_rules_cmd_failed": "Iuj komandoj pri fajroŝirmilo malsukcesis. Pliaj informoj en ensaluto.", + "certmanager_certificate_fetching_or_enabling_failed": "Provante uzi la novan atestilon por {domain:s} ne funkciis …" } From 8cdea18991b60e4489d93d2b861434ca3103bd53 Mon Sep 17 00:00:00 2001 From: advocatux Date: Fri, 4 Oct 2019 18:14:15 +0000 Subject: [PATCH 0253/3170] Translated using Weblate (Spanish) Currently translated at 100.0% (553 of 553 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/es/ --- locales/es.json | 59 +++++++++++++++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 21 deletions(-) diff --git a/locales/es.json b/locales/es.json index 02f46652b..aeee0ff7a 100644 --- a/locales/es.json +++ b/locales/es.json @@ -29,7 +29,7 @@ "app_unsupported_remote_type": "Tipo remoto no soportado por la aplicación", "app_upgrade_failed": "No se pudo actualizar {app:s}", "app_upgraded": "Actualizado {app:s}", - "appslist_fetched": "Obtenida lista de aplicaciones {appslist:s} actualizada", + "appslist_fetched": "Lista de aplicaciones {appslist:s} actualizada", "appslist_removed": "Eliminada la lista de aplicaciones {appslist:s}", "appslist_retrieve_error": "No se puede recuperar la lista remota de aplicaciones {appslist:s}: {error:s}", "appslist_unknown": "Lista de aplicaciones {appslist:s} desconocida.", @@ -74,9 +74,9 @@ "dnsmasq_isnt_installed": "Parece que dnsmasq no está instalado, ejecute «apt-get remove bind9 && apt-get install it»", "domain_cert_gen_failed": "No se pudo generar el certificado", "domain_created": "Dominio creado", - "domain_creation_failed": "No se pudo crear el dominio", + "domain_creation_failed": "No se pudo crear el dominio {domain}: {error}", "domain_deleted": "Dominio eliminado", - "domain_deletion_failed": "No se pudo eliminar el dominio", + "domain_deletion_failed": "No se pudo eliminar el dominio {domain}: {error}", "domain_dyndns_already_subscribed": "Ya se ha suscrito a un dominio de DynDNS", "domain_dyndns_invalid": "Este dominio no se puede usar con DynDNS", "domain_dyndns_root_unknown": "Dominio raíz de DynDNS desconocido", @@ -228,13 +228,13 @@ "upnp_enabled": "UPnP activado", "upnp_port_open_failed": "No se pudo abrir el puerto vía UPnP", "user_created": "Usuario creado", - "user_creation_failed": "No se pudo crear el usuario", + "user_creation_failed": "No se pudo crear el usuario {user}: {error}", "user_deleted": "Usuario eliminado", - "user_deletion_failed": "No se pudo eliminar el usuario", + "user_deletion_failed": "No se pudo eliminar el usuario {user}: {error}", "user_home_creation_failed": "No se pudo crear la carpeta «home» para el usuario", "user_info_failed": "No se pudo obtener la información del usuario", "user_unknown": "Usuario desconocido: {user:s}", - "user_update_failed": "No se pudo cambiar la información del usuario", + "user_update_failed": "No se pudo actualizar el usuario {user}: {error}", "user_updated": "Cambiada la información de usuario", "yunohost_already_installed": "YunoHost ya está instalado", "yunohost_ca_creation_failed": "No se pudo crear la autoridad de certificación", @@ -398,16 +398,16 @@ "remove_main_permission_not_allowed": "No se permite eliminar el permiso principal", "recommend_to_add_first_user": "La posinstalación ha terminado pero YunoHost necesita al menos un usuario para funcionar correctamente, debe añadir uno ejecutando «yunohost user create » o usando la interfaz de administración.", "permission_update_nothing_to_do": "No hay permisos para actualizar", - "permission_updated": "Actualizado el permiso «{permission:s}» para la aplicación «{app:s}»", + "permission_updated": "Actualizado el permiso «{permission:s}»", "permission_generated": "Actualizada la base de datos de permisos", - "permission_update_failed": "No se pudo actualizar el permiso", + "permission_update_failed": "No se pudo actualizar el permiso «{permission}» : {error}", "permission_name_not_valid": "Elija un nombre de permiso permitido para «{permission:s}", - "permission_not_found": "No se encontró el permiso «{permission:s}» para la aplicación «{app:s}»", - "permission_deletion_failed": "Falta el permiso «{permission:s}» para eliminar la aplicación «{app:s}»", - "permission_deleted": "Eliminado el permiso «{permission:s}» para la aplicación {app:s}", - "permission_creation_failed": "No se pudo conceder el permiso", - "permission_created": "Creado el permiso «{permission:s}» para la aplicación {app:s}", - "permission_already_exist": "El permiso «{permission:s}» para la aplicación {app:s} ya existe", + "permission_not_found": "No se encontró el permiso «{permission:s}»", + "permission_deletion_failed": "No se pudo eliminar el permiso «{permission}»: {error}", + "permission_deleted": "Eliminado el permiso «{permission:s}»", + "permission_creation_failed": "No se pudo crear el permiso «{permission}»: {error}", + "permission_created": "Creado el permiso «{permission:s}»", + "permission_already_exist": "El permiso «{permission}» ya existe", "permission_already_clear": "El permiso «{permission:s}» ya está definido para la aplicación {app:s}", "pattern_password_app": "Las contraseñas no pueden incluir los siguientes caracteres: {forbidden_chars}", "need_define_permission_before": "Redefina los permisos ejecutando «yunohost user permission add -u USUARIO» antes de eliminar un grupo permitido", @@ -438,7 +438,7 @@ "migration_0011_LDAP_config_dirty": "Parece que ha personalizado la configuración de LDAP. Para esta migración se necesita actualizar la configuración de LDAP.\nNecesita guardar su configuración actual, reiniciar la configuración original ejecutando «yunohost tools regen-conf -f» y reintentar la migración", "migration_0011_done": "Migración correcta. Ahora puede gestionar los grupos de usuarios.", "migration_0011_create_group": "Creando un grupo para cada usuario…", - "migration_0011_can_not_backup_before_migration": "No se pudo respaldar el sistema antes de la migración. Error: {error:s}", + "migration_0011_can_not_backup_before_migration": "Falló el respaldo del sistema antes de la migración. Fallo de migración. Error: {error:s}", "migration_0011_backup_before_migration": "Creando un respaldo de la base de datos de LDAP y de la configuración de las aplicaciones antes de la migración real.", "migration_0009_not_needed": "La migración ya ocurrió de algún modo… (?) Omitiendo.", "migration_0008_no_warning": "No se ha detectado ningún riesgo importante con respecto a la anulación de su configuración SSH ¡sin embargo uno nunca puede estar absolutamente seguro ;)! Ejecute la migración para anularla. Por otra parte, puede omitir la migración aunque no esté recomendado.", @@ -536,14 +536,14 @@ "log_category_404": "La categoría de registro «{category}» no existe", "log_corrupted_md_file": "El archivo de metadatos YAML asociado con el registro está dañado: «{md_file}\nError: {error}»", "hook_json_return_error": "No se pudo leer la respuesta del gancho {path:s}. Error: {msg:s}. Contenido sin procesar: {raw_content}", - "group_update_failed": "No se pudo actualizar el grupo «{group}»", + "group_update_failed": "No se pudo actualizar el grupo «{group}»: {error}", "group_updated": "Grupo «{group}» actualizado", "group_unknown": "El grupo «{group:s}» es desconocido", "group_info_failed": "No se pudo mostrar la información del grupo", "group_deletion_not_allowed": "No se puede eliminar el grupo {group:s} manualmente.", - "group_deletion_failed": "No se pudo eliminar el grupo «{group}»", + "group_deletion_failed": "No se pudo eliminar el grupo «{group}»: {error}", "group_deleted": "Eliminado el grupo «{group}»", - "group_creation_failed": "No se pudo crear el grupo «{group}»", + "group_creation_failed": "No se pudo crear el grupo «{group}»: {error}", "group_created": "Creado el grupo «{group}»", "group_name_already_exist": "El grupo {name:s} ya existe", "group_already_disallowed": "El grupo «{group:s}» ya tiene desactivado el permiso «{permission:s}» para la aplicación «{app:s}»", @@ -577,8 +577,8 @@ "domain_dns_conf_is_just_a_recommendation": "Esta orden muestra la configuración *recomendada*. No configura el DNS en realidad. Es su responsabilidad configurar la zona de DNS en su registrador según esta recomendación.", "dpkg_lock_not_available": "Esta orden no se puede ejecutar en este momento porque otro programa parece que está usando el bloqueo de dpkg (el gestor de paquetes del sistema)", "dpkg_is_broken": "No puede hacer esto en este momento porque dpkg/apt (los gestores de paquetes del sistema) parecen estar en un estado roto... Puede tratar de solucionar este problema conectando a través de SSH y ejecutando `sudo dpkg --configure -a`.", - "confirm_app_install_thirdparty": "¡AVISO! Instalar aplicaciones de terceros podría comprometer la integridad y seguridad de su sistema. Probablemente NO debería instalarlas salvo que sepa lo que está haciendo. ¿Está dispuesto a correr ese riesgo? [{answers:s}] ", - "confirm_app_install_danger": "¡AVISO! Esta aplicación es aún experimental (si no está funcionando expresamente) y ¡es probable que rompa su sistema! Probablemente NO debería instalarla salvo que sepa lo que está haciendo. ¿Está dispuesto a correr ese riesgo? [{answers:s}] ", + "confirm_app_install_thirdparty": "¡PELIGRO! Esta aplicación no forma parte del catálogo de aplicaciones de YunoHost. Instalar aplicaciones de terceros podría comprometer la integridad y seguridad de su sistema. Probablemente NO debería instalarla salvo que sepa lo que está haciendo. No tendrá NINGUNA AYUDA si esta aplicación no funciona o rompe su sistema... Si está dispuesto a aceptar ese riesgo de todas formas, escriba «{answers:s}»", + "confirm_app_install_danger": "¡PELIGRO! ¡Esta aplicación es conocida por ser aún experimental (o no funciona explícitamente)! Probablemente NO debería instalarla salvo que sepa lo que está haciendo. No tendrá NINGUNA AYUDA si esta aplicación no funciona o rompe su sistema... Si está dispuesto a aceptar ese riesgo de todas formas, escriba «{answers:s}»", "confirm_app_install_warning": "Aviso: esta aplicación puede funcionar pero no está bien integrada en YunoHost. Algunas herramientas como la autentificación única y respaldo/restauración podrían no estar disponibles. ¿Instalar de todos modos? [{answers:s}] ", "backup_unable_to_organize_files": "No se pudo usar el método rápido de organización de los archivos en el archivo", "backup_permission": "Permiso de respaldo para la aplicación {app:s}", @@ -608,5 +608,22 @@ "app_action_broke_system": "Esta acción parece que ha roto estos importantes servicios: {services}", "operation_interrupted": "¿Ha sido interrumpida la operación manualmente?", "apps_already_up_to_date": "Todas las aplicaciones están ya actualizadas", - "dyndns_provider_unreachable": "No se puede conectar con el proveedor de Dyndns {provider}: o su YunoHost no está correctamente conectado a Internet o el servidor de dynette está caído." + "dyndns_provider_unreachable": "No se puede conectar con el proveedor de Dyndns {provider}: o su YunoHost no está correctamente conectado a Internet o el servidor de dynette está caído.", + "group_already_exist": "El grupo {group} ya existe", + "group_already_exist_on_system": "El grupo {group} ya existe en los grupos del sistema", + "group_cannot_be_edited": "El grupo {group} no se puede editar manualmente.", + "group_cannot_be_deleted": "El grupo {group} no se puede eliminar manualmente.", + "group_user_already_in_group": "El usuario {user} ya está en el grupo {group}", + "group_user_not_in_group": "El usuario {user} no está en el grupo {group}", + "log_permission_create": "Crear permiso «{}»", + "log_permission_delete": "Eliminar permiso «{}»", + "log_permission_urls": "Actualizar URLs relacionadas con el permiso «{}»", + "log_user_group_create": "Crear grupo «{}»", + "log_user_permission_update": "Actualizar los accesos para el permiso «{}»", + "log_user_permission_reset": "Restablecer permiso «{}»", + "migration_0011_failed_to_remove_stale_object": "No se pudo eliminar el objeto obsoleto {dn}: {error}", + "permission_already_allowed": "El grupo «{group}» ya tiene el permiso «{permission}» activado", + "permission_already_disallowed": "El grupo «{group}» ya tiene el permiso «{permission}» desactivado", + "permission_cannot_remove_main": "No está permitido eliminar un permiso principal", + "user_already_exists": "El usuario {user} ya existe" } From 3e6ae5fb6835246141575c140897e7fd7a4db6c9 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Sun, 6 Oct 2019 10:07:41 +0000 Subject: [PATCH 0254/3170] Translated using Weblate (French) Currently translated at 100.0% (553 of 553 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 61 +++++++++++++++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 8bffec8b2..6b5e2a790 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -30,7 +30,7 @@ "app_unsupported_remote_type": "Ce type de commande à distance utilisé pour cette application n'est pas supporté", "app_upgrade_failed": "Impossible de mettre à jour {app:s}", "app_upgraded": "{app:s} mis à jour", - "appslist_fetched": "La liste d’applications {appslist:s} récupérée", + "appslist_fetched": "La liste d’applications mise à jour {appslist:s}", "appslist_removed": "La liste d’applications {appslist:s} a été supprimée", "appslist_retrieve_error": "Impossible de récupérer la liste d’applications distante {appslist:s} : {error:s}", "appslist_unknown": "La liste d’applications {appslist:s} est inconnue.", @@ -75,9 +75,9 @@ "dnsmasq_isnt_installed": "dnsmasq ne semble pas être installé, veuillez lancer 'apt-get remove bind9 && apt-get install dnsmasq'", "domain_cert_gen_failed": "Impossible de générer le certificat", "domain_created": "Le domaine a été créé", - "domain_creation_failed": "Impossible de créer le domaine", + "domain_creation_failed": "Impossible de créer le domaine {domain}: {error}", "domain_deleted": "Le domaine a été supprimé", - "domain_deletion_failed": "Impossible de supprimer le domaine", + "domain_deletion_failed": "Impossible de supprimer le domaine {domain}: {error}", "domain_dyndns_already_subscribed": "Vous avez déjà souscris à un domaine DynDNS", "domain_dyndns_invalid": "Domaine incorrect pour un usage avec DynDNS", "domain_dyndns_root_unknown": "Domaine DynDNS principal inconnu", @@ -236,13 +236,13 @@ "upnp_enabled": "UPnP activé", "upnp_port_open_failed": "Impossible d’ouvrir les ports UPnP", "user_created": "L’utilisateur créé", - "user_creation_failed": "Impossible de créer l’utilisateur", + "user_creation_failed": "Impossible de créer l’utilisateur {user}: {error}", "user_deleted": "L’utilisateur supprimé", - "user_deletion_failed": "Impossible de supprimer l’utilisateur", + "user_deletion_failed": "Impossible de supprimer l’utilisateur {user}: {error}", "user_home_creation_failed": "Impossible de créer le dossier personnel de l’utilisateur", "user_info_failed": "Impossible de récupérer les informations de l’utilisateur", "user_unknown": "L'utilisateur {user:s} est inconnu", - "user_update_failed": "Impossible de modifier l’utilisateur", + "user_update_failed": "Impossible de mettre à jour l'utilisateur {utilisateur}: {erreur}", "user_updated": "L’utilisateur a été modifié", "yunohost_already_installed": "YunoHost est déjà installé", "yunohost_ca_creation_failed": "Impossible de créer l’autorité de certification", @@ -493,8 +493,8 @@ "backup_actually_backuping": "Création d'une archive de sauvegarde à partir des fichiers collectés …", "backup_mount_archive_for_restore": "Préparation de l'archive pour restauration …", "confirm_app_install_warning": "Avertissement : cette application peut fonctionner mais n'est pas bien intégrée dans YunoHost. Certaines fonctionnalités telles que l'authentification unique et la sauvegarde/restauration peuvent ne pas être disponibles. L'installer quand même ? [{answers:s}] ", - "confirm_app_install_danger": "AVERTISSEMENT ! Cette application est encore expérimentale (explicitement, elle ne fonctionne pas) et risque de casser votre système ! Vous ne devriez probablement PAS l'installer sans savoir ce que vous faites. Êtes-vous prêt à prendre ce risque ? [{answers:s}] ", - "confirm_app_install_thirdparty": "AVERTISSEMENT ! L'installation d'applications tierces peut compromettre l'intégrité et la sécurité de votre système. Vous ne devriez probablement PAS l'installer si vous ne savez pas ce que vous faites. Êtes-vous prêt à prendre ce risque ? [{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'.", "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.", @@ -573,12 +573,12 @@ "group_info_failed": "L'information sur le groupe a échoué", "group_unknown": "Le groupe {group:s} est inconnu", "group_updated": "Le groupe '{group}' a été mis à jour", - "group_update_failed": "La mise à jour du groupe '{group}' a échoué", + "group_update_failed": "La mise à jour du groupe '{group}' a échoué : {error}", "group_already_allowed": "Le groupe '{group:s}' a déjà la permission '{permission:s}' activée pour l'application '{app:s}'", "group_already_disallowed": "Le groupe '{group:s}' a déjà la permission '{permission:s}' désactivée pour l'application '{app:s}'", "group_name_already_exist": "Le groupe {name:s} existe déjà", - "group_creation_failed": "Échec de la création du groupe '{group}'", - "group_deletion_failed": "Échec de la suppression du groupe '{group}'", + "group_creation_failed": "Échec de la création du groupe '{group}': {error}", + "group_deletion_failed": "Échec de la suppression du groupe '{group}': {error}", "edit_permission_with_group_all_users_not_allowed": "Vous n'êtes pas autorisé à modifier les permissions pour le groupe 'all_users', utilisez 'yunohost user permission clear APP' ou 'yunohost user permission add APP -u USER' à la place.", "log_permission_add": "Ajouter l'autorisation '{}' pour l'application '{}'", "log_permission_remove": "Supprimer l'autorisation '{}'", @@ -600,7 +600,7 @@ "migration_description_0012_postgresql_password_to_md5_authentication": "Forcer l'authentification PostgreSQL à utiliser MD5 pour les connexions locales", "migrations_exclusive_options": "'auto', '--skip' et '--force-rerun' sont des options mutuellement exclusives.", "migrations_not_pending_cant_skip": "Ces migrations ne sont pas en attente et ne peuvent donc pas être ignorées: {ids}", - "migration_0011_can_not_backup_before_migration": "Impossible de sauvegarder le système avant la migration. Erreur: {error: s}", + "migration_0011_can_not_backup_before_migration": "La sauvegarde du système avant la migration a échoué. La migration a échoué. Erreur: {error: s}", "migration_0011_migrate_permission": "Migration des autorisations des paramètres des applications vers LDAP…", "migration_0011_migration_failed_trying_to_rollback": "La migration a échoué… essayait de restauration du système.", "migration_0011_rollback_success": "Système restauré.", @@ -610,11 +610,11 @@ "user_already_in_group": "L'utilisateur '{user:}' est déjà dans le groupe '{group: s}'", "user_not_in_group": "L'utilisateur '{user: s}' ne fait pas partie du groupe {group: s}", "migration_0011_backup_before_migration": "Création d'une sauvegarde des paramètres de la base de données LDAP et des applications avant la migration.", - "permission_not_found": "Autorisation '{permission: s}' non trouvée pour l'application '{app: s}'", + "permission_not_found": "Autorisation '{permission:s}' introuvable", "permission_name_not_valid": "Choisissez un nom d'autorisation autorisé pour '{permission: s}'", - "permission_update_failed": "Impossible de mettre à jour la permission", + "permission_update_failed": "Impossible de mettre à jour la permission '{permission}': {error}", "permission_generated": "Base de données des autorisations mise à jour", - "permission_updated": "Permission '{permission: s}' pour l'application '{app: s}' mise à jour", + "permission_updated": "Permission '{permission:s}' mise à jour", "permission_update_nothing_to_do": "Aucune autorisation pour mettre à jour", "remove_main_permission_not_allowed": "Supprimer l'autorisation principale n'est pas autorisé", "dyndns_provider_unreachable": "Impossible d’atteindre le fournisseur Dyndns {provider}: votre YunoHost n’est pas correctement connecté à Internet ou le serveur Dynette est en panne.", @@ -627,13 +627,30 @@ "need_define_permission_before": "Redéfinissez l'autorisation à l'aide de 'yunohost user permission add -u USER' avant de supprimer un groupe autorisé", "operation_interrupted": "L'opération a été interrompue manuellement", "permission_already_clear": "L'autorisation '{permission: s}' est déjà vide pour l'application {app: s}", - "permission_already_exist": "L'autorisation '{permission: s}' pour l'application {app: s} existe déjà", - "permission_created": "Permission '{permission: s}' pour l'application {app: s} créée", - "permission_creation_failed": "Impossible d'accorder la permission", - "permission_deleted": "Permission '{permission: s}' pour app {app: s} supprimée", - "permission_deletion_failed": "Autorisation manquante '{permission: s}' pour supprimer l'application '{app: s}'", + "permission_already_exist": "L'autorisation '{permission}' existe déjà", + "permission_created": "Permission '{permission:s}' créée", + "permission_creation_failed": "Impossible de créer l'autorisation '{permission}': {erreur}", + "permission_deleted": "Permission '{permission:s}' supprimée", + "permission_deletion_failed": "Impossible de supprimer la permission '{permission}': {error}", "remove_user_of_group_not_allowed": "Vous n'êtes pas autorisé à supprimer l'utilisateur '{utilisateur: s}' dans le groupe '{groupe: s}'", "migration_description_0011_setup_group_permission": "Configurer le groupe d'utilisateurs et configurer les autorisations pour les applications et les services", - "migration_0011_LDAP_config_dirty": "Il semble que vous ayez personnalisé votre configuration LDAP. Pour cette migration, la configuration LDAP doit être mise à jour.\nVous devez enregistrer votre configuration actuelle, réintialiser la configuration d'origine en exécutant 'yunohost tools regen-conf -f', puis réessayer la migration", - "migration_0011_LDAP_update_failed": "Impossible de mettre à jour LDAP. Erreur: {error: s}" + "migration_0011_LDAP_config_dirty": "Il semble que vous ayez personnalisé votre configuration LDAP. Pour cette migration, la configuration LDAP doit être mise à jour.\nVous devez enregistrer votre configuration actuelle, réintialiser la configuration d'origine en exécutant 'yunohost tools regen-conf -f', puis réessayer la migration.", + "migration_0011_LDAP_update_failed": "Impossible de mettre à jour LDAP. Erreur: {error: s}", + "group_already_exist": "Le groupe {group} existe déjà", + "group_already_exist_on_system": "Le groupe {group} existe déjà dans les groupes système", + "group_cannot_be_edited": "Le groupe {group} ne peut pas être édité manuellement.", + "group_cannot_be_deleted": "Le groupe {group} ne peut pas être supprimé manuellement.", + "group_user_already_in_group": "L'utilisateur {user} est déjà dans le groupe {group}", + "group_user_not_in_group": "L'utilisateur {user} n'est pas dans le groupe {group}", + "log_permission_create": "Créer permission '{}'", + "log_permission_delete": "supprimer permission '{}'", + "log_permission_urls": "Mettre à jour les URL liées à la permission '{}'", + "log_user_group_create": "Créer '{}' groupe", + "log_user_permission_update": "Mise à jour des accès pour la permission '{}'", + "log_user_permission_reset": "Réinitialiser la permission '{}'", + "migration_0011_failed_to_remove_stale_object": "Impossible de supprimer un objet obsolète {dn}: {error}", + "permission_already_allowed": "Le groupe '{group}' a déjà l'autorisation '{permission}' activée '", + "permission_already_disallowed": "Le groupe '{group}' a déjà l'autorisation '{permission}' désactivé '", + "permission_cannot_remove_main": "Supprimer une autorisation principale n'est pas autorisé", + "user_already_exists": "L'utilisateur {user} existe déjà" } From d8468f77bc0d8554fcc17e61d228d3466007ee5c Mon Sep 17 00:00:00 2001 From: advocatux Date: Tue, 8 Oct 2019 17:07:51 +0000 Subject: [PATCH 0255/3170] Translated using Weblate (Spanish) Currently translated at 100.0% (554 of 554 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/es/ --- locales/es.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/es.json b/locales/es.json index aeee0ff7a..80b17673a 100644 --- a/locales/es.json +++ b/locales/es.json @@ -625,5 +625,6 @@ "permission_already_allowed": "El grupo «{group}» ya tiene el permiso «{permission}» activado", "permission_already_disallowed": "El grupo «{group}» ya tiene el permiso «{permission}» desactivado", "permission_cannot_remove_main": "No está permitido eliminar un permiso principal", - "user_already_exists": "El usuario {user} ya existe" + "user_already_exists": "El usuario {user} ya existe", + "app_full_domain_unavailable": "Lamentablemente esta aplicación necesita un dominio completo para ser instalada pero ya hay otras aplicaciones instaladas en el dominio «{domain}». Una solución posible es añadir y usar un subdominio dedicado a esta aplicación." } From 5e477dd3f2d8df0ad74dcc9d7e7c96a8e1bb830f Mon Sep 17 00:00:00 2001 From: Lukas Dohn Date: Wed, 9 Oct 2019 08:31:31 +0000 Subject: [PATCH 0256/3170] Translated using Weblate (German) Currently translated at 39.2% (217 of 554 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/de.json b/locales/de.json index d03226187..1fe279d6b 100644 --- a/locales/de.json +++ b/locales/de.json @@ -282,7 +282,7 @@ "appslist_name_already_tracked": "Es gibt bereits eine registrierte App-Liste mit Namen {name:s}.", "appslist_url_already_tracked": "Es gibt bereits eine registrierte Anwendungsliste mit der URL {url:s}.", "appslist_migrating": "Migriere Anwendungsliste {appslist:s} …", - "appslist_could_not_migrate": "Konnte Anwendungsliste {appslist:s} nicht migrieren. Konnte die URL nicht verarbeiten... Der alte Cron-Job wurde unter {bkp_file:s} beibehalten.", + "appslist_could_not_migrate": "Konnte die Anwendungsliste {appslist:s} nicht migrieren. Konnte die URL nicht verarbeiten... Der alte Cron-Job wurde unter {bkp_file:s} beibehalten.", "appslist_corrupted_json": "Anwendungslisten konnte nicht geladen werden. Es scheint, dass {filename:s} beschädigt ist.", "yunohost_ca_creation_success": "Die lokale Zertifizierungs-Authorität wurde angelegt.", "app_already_installed_cant_change_url": "Diese Application ist bereits installiert. Die URL kann durch diese Funktion nicht modifiziert werden. Überprüfe ob `app changeurl` verfügbar ist.", From a6a104677cb0ef723645734bd4b67c0c95dbc66a Mon Sep 17 00:00:00 2001 From: amirale qt Date: Wed, 9 Oct 2019 11:20:08 +0000 Subject: [PATCH 0257/3170] Translated using Weblate (French) Currently translated at 100.0% (554 of 554 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 6b5e2a790..62f6d6965 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -634,7 +634,7 @@ "permission_deletion_failed": "Impossible de supprimer la permission '{permission}': {error}", "remove_user_of_group_not_allowed": "Vous n'êtes pas autorisé à supprimer l'utilisateur '{utilisateur: s}' dans le groupe '{groupe: s}'", "migration_description_0011_setup_group_permission": "Configurer le groupe d'utilisateurs et configurer les autorisations pour les applications et les services", - "migration_0011_LDAP_config_dirty": "Il semble que vous ayez personnalisé votre configuration LDAP. Pour cette migration, la configuration LDAP doit être mise à jour.\nVous devez enregistrer votre configuration actuelle, réintialiser la configuration d'origine en exécutant 'yunohost tools regen-conf -f', puis réessayer la migration.", + "migration_0011_LDAP_config_dirty": "Il semble que vous ayez personnalisé votre configuration LDAP. Pour cette migration, la configuration LDAP doit être mise à jour.\nVous devez enregistrer votre configuration actuelle, réintialiser la configuration d'origine en exécutant 'yunohost tools regen-conf -f', puis réessayer la migration", "migration_0011_LDAP_update_failed": "Impossible de mettre à jour LDAP. Erreur: {error: s}", "group_already_exist": "Le groupe {group} existe déjà", "group_already_exist_on_system": "Le groupe {group} existe déjà dans les groupes système", @@ -652,5 +652,6 @@ "permission_already_allowed": "Le groupe '{group}' a déjà l'autorisation '{permission}' activée '", "permission_already_disallowed": "Le groupe '{group}' a déjà l'autorisation '{permission}' désactivé '", "permission_cannot_remove_main": "Supprimer une autorisation principale n'est pas autorisé", - "user_already_exists": "L'utilisateur {user} existe déjà" + "user_already_exists": "L'utilisateur {user} existe déjà", + "app_full_domain_unavailable": "Désolé, cette application nécessite l'installation d'un domaine complet, mais d'autres applications sont déjà installées sur le domaine '{domain}'. Une solution possible consiste à ajouter et à utiliser un sous-domaine dédié à cette application." } From 5ac863c906f6858f3433fbc2082fc3f544faee1a Mon Sep 17 00:00:00 2001 From: amirale qt Date: Wed, 9 Oct 2019 11:21:54 +0000 Subject: [PATCH 0258/3170] Translated using Weblate (Esperanto) Currently translated at 100.0% (554 of 554 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/eo/ --- locales/eo.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/eo.json b/locales/eo.json index d27c0171c..0d8d13fe8 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -552,5 +552,6 @@ "log_app_remove": "Forigu la aplikon '{}'", "service_restart_failed": "Ne povis rekomenci la servon '{service:s}'\n\nLastatempaj servaj protokoloj: {logs:s}", "firewall_rules_cmd_failed": "Iuj komandoj pri fajroŝirmilo malsukcesis. Pliaj informoj en ensaluto.", - "certmanager_certificate_fetching_or_enabling_failed": "Provante uzi la novan atestilon por {domain:s} ne funkciis …" + "certmanager_certificate_fetching_or_enabling_failed": "Provante uzi la novan atestilon por {domain:s} ne funkciis …", + "app_full_domain_unavailable": "Bedaŭrinde, ĉi tiu apliko postulas plenan domajnon esti instalita, sed iuj aliaj programoj jam estas instalitaj sur '{domain}'. Unu ebla solvo estas aldoni kaj uzi subdomajnon dediĉitan al ĉi tiu aplikaĵo anstataŭe." } From f987e7872c9f09bc7320bed2aa16360a72a1ccb1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 28 Aug 2018 23:33:22 +0000 Subject: [PATCH 0259/3170] Skeleton / draft of API --- data/actionsmap/yunohost.yml | 50 ++++++++++++++++++++++++++++++++++++ src/yunohost/diagnosis.py | 44 +++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 src/yunohost/diagnosis.py diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 22037f05f..4f849160f 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1861,3 +1861,53 @@ log: --share: help: Share the full log using yunopaste action: store_true + + +############################# +# Diagnosis # +############################# +diagnosis: + category_help: Look for possible issues on the server + actions: + + list: + action_help: List diagnosis categories + api: GET /diagnosis/list + + report: + action_help: Show most recents diagnosis results + api: GET /diagnosis/report + arguments: + categories: + help: Diagnosis categories to display (all by default) + nargs: "*" + --full: + help: Display additional information + action: store_true + + run: + action_help: Show most recents diagnosis results + api: POST /diagnosis/run + arguments: + categories: + help: Diagnosis categories to run (all by default) + nargs: "*" + --force: + help: Display additional information + action: store_true + -a: + help: Serialized arguments for diagnosis scripts (e.g. "domain=domain.tld") + full: --args + + ignore: + action_help: Configure some diagnosis results to be ignored + api: PUT /diagnosis/ignore + arguments: + category: + help: Diagnosis category to be affected + -a: + help: Arguments, to be used to ignore only some parts of a report (e.g. "domain=domain.tld") + full: --args + --unignore: + help: Unignore a previously ignored report + action: store_true diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py new file mode 100644 index 000000000..aafbdcec3 --- /dev/null +++ b/src/yunohost/diagnosis.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- + +""" License + + Copyright (C) 2018 YunoHost + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program; if not, see http://www.gnu.org/licenses + +""" + +""" diagnosis.py + + Look for possible issues on the server +""" + +from moulinette import m18n +from moulinette.core import MoulinetteError +from moulinette.utils import log + +logger = log.getActionLogger('yunohost.diagnosis') + +def diagnosis_list(): + pass + +def diagnosis_report(categories=[], full=False): + pass + +def diagnosis_run(categories=[], force=False, args=""): + pass + +def diagnosis_ignore(category, args="", unignore=False): + pass + From 1d946ad073ed298185da49a6a5f111332c3daf25 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 29 Aug 2018 00:33:02 +0000 Subject: [PATCH 0260/3170] Implement diagnosis categories listing --- src/yunohost/diagnosis.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index aafbdcec3..0d312a7c1 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -28,10 +28,13 @@ from moulinette import m18n from moulinette.core import MoulinetteError from moulinette.utils import log +from yunohost.hook import hook_list + logger = log.getActionLogger('yunohost.diagnosis') def diagnosis_list(): - pass + all_categories_names = [ h for h, _ in _list_diagnosis_categories() ] + return { "categories": all_categories_names } def diagnosis_report(categories=[], full=False): pass @@ -42,3 +45,13 @@ def diagnosis_run(categories=[], force=False, args=""): def diagnosis_ignore(category, args="", unignore=False): pass +############################################################ + +def _list_diagnosis_categories(): + hooks_raw = hook_list("diagnosis", list_by="priority", show_info=True)["hooks"] + hooks = [] + for _, some_hooks in sorted(hooks_raw.items(), key=lambda h:int(h[0])): + for name, info in some_hooks.items(): + hooks.append((name, info["path"])) + + return hooks From b42bd20311797f59feb9ff7476ed19e49d20f8e5 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 29 Aug 2018 01:34:15 +0000 Subject: [PATCH 0261/3170] First draft for diagnosis_run --- data/actionsmap/yunohost.yml | 2 +- locales/en.json | 1 + src/yunohost/diagnosis.py | 38 +++++++++++++++++++++++++++++++++--- 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 4f849160f..aa85fdf70 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1893,7 +1893,7 @@ diagnosis: help: Diagnosis categories to run (all by default) nargs: "*" --force: - help: Display additional information + help: Ignore the cached report even if it is still 'fresh' action: store_true -a: help: Serialized arguments for diagnosis scripts (e.g. "domain=domain.tld") diff --git a/locales/en.json b/locales/en.json index f681fc4ea..a91da4fe9 100644 --- a/locales/en.json +++ b/locales/en.json @@ -547,6 +547,7 @@ "user_update_failed": "Could not update user {user}: {error}", "user_updated": "User info changed", "users_available": "Available users:", + "unknown_categories": "The following categories are unknown : {categories}", "yunohost_already_installed": "YunoHost is already installed", "yunohost_ca_creation_failed": "Could not create certificate authority", "yunohost_ca_creation_success": "Local certification authority created.", diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index 0d312a7c1..10f09a576 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -24,14 +24,18 @@ Look for possible issues on the server """ +import errno + from moulinette import m18n from moulinette.core import MoulinetteError from moulinette.utils import log -from yunohost.hook import hook_list +from yunohost.hook import hook_list, hook_exec logger = log.getActionLogger('yunohost.diagnosis') +DIAGNOSIS_CACHE = "/var/cache/yunohost/diagnosis/" + def diagnosis_list(): all_categories_names = [ h for h, _ in _list_diagnosis_categories() ] return { "categories": all_categories_names } @@ -39,8 +43,36 @@ def diagnosis_list(): def diagnosis_report(categories=[], full=False): pass -def diagnosis_run(categories=[], force=False, args=""): - pass +def diagnosis_run(categories=[], force=False, args=None): + + # Get all the categories + all_categories = _list_diagnosis_categories() + all_categories_names = [ category for category, _ in all_categories ] + + # Check the requested category makes sense + if categories == []: + categories = all_categories_names + else: + unknown_categories = [ c for c in categories if c not in all_categories_names ] + if unknown_categories: + raise MoulinetteError(m18n.n('unknown_categories', categories=", ".join(categories))) + + # Transform "arg1=val1&arg2=val2" to { "arg1": "val1", "arg2": "val2" } + if args is not None: + args = { arg.split("=")[0]: arg.split("=")[1] for arg in args.split("&") } + else: + args = {} + args["force"] = force + + + # Call the hook ... + for category in categories: + logger.debug("Running diagnosis for %s ..." % category) + path = [p for n, p in all_categories if n == category ][0] + + # TODO : get the return value and do something with it + hook_exec(path, args=args, env=None) + def diagnosis_ignore(category, args="", unignore=False): pass From 7fb694dbccb62bcac206ffdf0688f7e1a52bd251 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 29 Aug 2018 23:48:16 +0000 Subject: [PATCH 0262/3170] Add diagnoser example for ip --- data/hooks/diagnosis/10-ip.py | 55 +++++++++++++++++++++++++++++++++++ src/yunohost/diagnosis.py | 15 +++++++++- 2 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 data/hooks/diagnosis/10-ip.py diff --git a/data/hooks/diagnosis/10-ip.py b/data/hooks/diagnosis/10-ip.py new file mode 100644 index 000000000..d8ab53c56 --- /dev/null +++ b/data/hooks/diagnosis/10-ip.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python + +from moulinette import m18n +from moulinette.utils.network import download_text +from yunohost.diagnosis import Diagnoser + +class IPDiagnoser(Diagnoser): + + def validate_args(self, args): + if "version" not in args.keys(): + return { "versions" : [4, 6] } + else: + if str(args["version"]) not in ["4", "6"]: + raise MoulinetteError(1, "Invalid version, should be 4 or 6.") + return { "versions" : [int(args["version"])] } + + def run(self): + + versions = self.args["versions"] + + if 4 in versions: + ipv4 = self.get_public_ip(4) + yield dict(meta = {"version": "4"}, + result = ipv4, + report = ("SUCCESS", m18n.n("diagnosis_network_connected_ipv4")) if ipv4 \ + else ("ERROR", m18n.n("diagnosis_network_no_ipv4"))) + + if 6 in versions: + ipv6 = self.get_public_ip(6) + yield dict(meta = {"version": "6"}, + result = ipv6, + report = ("SUCCESS", m18n.n("diagnosis_network_connected_ipv6")) if ipv6 \ + else ("WARNING", m18n.n("diagnosis_network_no_ipv6"))) + + + def get_public_ip(self, protocol=4): + + if protocol == 4: + url = 'https://ip.yunohost.org' + elif protocol == 6: + url = 'https://ip6.yunohost.org' + else: + raise ValueError("invalid protocol version") + + try: + return download_text(url, timeout=30).strip() + except Exception as e: + self.logger_debug("Could not get public IPv%s : %s" % (str(protocol), str(e))) + return None + + +def main(args, env, loggers): + + return IPDiagnoser(args, env, loggers).report() + diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index 10f09a576..b5e0fa05a 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -71,7 +71,7 @@ def diagnosis_run(categories=[], force=False, args=None): path = [p for n, p in all_categories if n == category ][0] # TODO : get the return value and do something with it - hook_exec(path, args=args, env=None) + return {"report": hook_exec(path, args=args, env=None) } def diagnosis_ignore(category, args="", unignore=False): @@ -79,6 +79,19 @@ def diagnosis_ignore(category, args="", unignore=False): ############################################################ +class Diagnoser(): + + def __init__(self, args, env, loggers): + + self.logger_debug, self.logger_warning, self.logger_info = loggers + self.env = env + self.args = self.validate_args(args) + + def report(self): + + # TODO : implement some caching mecanism in there + return list(self.run()) + def _list_diagnosis_categories(): hooks_raw = hook_list("diagnosis", list_by="priority", show_info=True)["hooks"] hooks = [] From d34ddcaaf2964200b0a1edef167bb792b22cde26 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 30 Aug 2018 13:36:43 +0000 Subject: [PATCH 0263/3170] Implement caching mechanism --- data/hooks/diagnosis/10-ip.py | 6 ++++++ src/yunohost/diagnosis.py | 38 +++++++++++++++++++++++++++++++++-- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/data/hooks/diagnosis/10-ip.py b/data/hooks/diagnosis/10-ip.py index d8ab53c56..a9485d7f4 100644 --- a/data/hooks/diagnosis/10-ip.py +++ b/data/hooks/diagnosis/10-ip.py @@ -1,11 +1,17 @@ #!/usr/bin/env python +import os + from moulinette import m18n from moulinette.utils.network import download_text from yunohost.diagnosis import Diagnoser class IPDiagnoser(Diagnoser): + id_ = os.path.splitext(os.path.basename(__file__))[0] + description = m18n.n("internet_connectivity") + cache_duration = 60 + def validate_args(self, args): if "version" not in args.keys(): return { "versions" : [4, 6] } diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index b5e0fa05a..f0ffcd619 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -25,10 +25,13 @@ """ import errno +import os +import time from moulinette import m18n from moulinette.core import MoulinetteError from moulinette.utils import log +from moulinette.utils.filesystem import read_json, write_to_json from yunohost.hook import hook_list, hook_exec @@ -87,10 +90,41 @@ class Diagnoser(): self.env = env self.args = self.validate_args(args) + @property + def cache_file(self): + return os.path.join(DIAGNOSIS_CACHE, "%s.json" % self.id_) + + def cached_time_ago(self): + + if not os.path.exists(self.cache_file): + return 99999999 + return time.time() - os.path.getmtime(self.cache_file) + + def get_cached_report(self): + return read_json(self.cache_file) + + def write_cache(self, report): + if not os.path.exists(DIAGNOSIS_CACHE): + os.makedirs(DIAGNOSIS_CACHE) + return write_to_json(self.cache_file, report) + def report(self): - # TODO : implement some caching mecanism in there - return list(self.run()) + print(self.cached_time_ago()) + + if self.args.get("force", False) or self.cached_time_ago() < self.cache_duration: + self.logger_debug("Using cached report from %s" % self.cache_file) + return self.get_cached_report() + + new_report = list(self.run()) + + # TODO / FIXME : should handle the case where we only did a partial diagnosis + self.logger_debug("Updating cache %s" % self.cache_file) + self.write_cache(new_report) + + return new_report + + def _list_diagnosis_categories(): hooks_raw = hook_list("diagnosis", list_by="priority", show_info=True)["hooks"] From f11206c0fa16a77aaabb441f33ab77f5c3446f2a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 30 Aug 2018 13:48:14 +0000 Subject: [PATCH 0264/3170] Turns out it's not really a good idea to do the internationalization right here as the strings will be kept already translated in the cache --- data/hooks/diagnosis/10-ip.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/data/hooks/diagnosis/10-ip.py b/data/hooks/diagnosis/10-ip.py index a9485d7f4..eefa7798d 100644 --- a/data/hooks/diagnosis/10-ip.py +++ b/data/hooks/diagnosis/10-ip.py @@ -9,7 +9,7 @@ from yunohost.diagnosis import Diagnoser class IPDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0] - description = m18n.n("internet_connectivity") + description = "internet_connectivity" cache_duration = 60 def validate_args(self, args): @@ -26,17 +26,17 @@ class IPDiagnoser(Diagnoser): if 4 in versions: ipv4 = self.get_public_ip(4) - yield dict(meta = {"version": "4"}, + yield dict(meta = {"version": 4}, result = ipv4, - report = ("SUCCESS", m18n.n("diagnosis_network_connected_ipv4")) if ipv4 \ - else ("ERROR", m18n.n("diagnosis_network_no_ipv4"))) + report = ("SUCCESS", "diagnosis_network_connected_ipv4", {}) if ipv4 \ + else ("ERROR", "diagnosis_network_no_ipv4", {})) if 6 in versions: ipv6 = self.get_public_ip(6) - yield dict(meta = {"version": "6"}, + yield dict(meta = {"version": 6}, result = ipv6, - report = ("SUCCESS", m18n.n("diagnosis_network_connected_ipv6")) if ipv6 \ - else ("WARNING", m18n.n("diagnosis_network_no_ipv6"))) + report = ("SUCCESS", "diagnosis_network_connected_ipv6", {}) if ipv6 \ + else ("WARNING", "diagnosis_network_no_ipv6", {})) def get_public_ip(self, protocol=4): From 12df96f33e24404ea578e99e6c6dc370e7cc51b8 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 30 Aug 2018 14:05:48 +0000 Subject: [PATCH 0265/3170] Wrap the report with meta infos --- data/hooks/diagnosis/10-ip.py | 1 - src/yunohost/diagnosis.py | 7 ++++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/hooks/diagnosis/10-ip.py b/data/hooks/diagnosis/10-ip.py index eefa7798d..bde96b22e 100644 --- a/data/hooks/diagnosis/10-ip.py +++ b/data/hooks/diagnosis/10-ip.py @@ -38,7 +38,6 @@ class IPDiagnoser(Diagnoser): report = ("SUCCESS", "diagnosis_network_connected_ipv6", {}) if ipv6 \ else ("WARNING", "diagnosis_network_no_ipv6", {})) - def get_public_ip(self, protocol=4): if protocol == 4: diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index f0ffcd619..8fc46967b 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -110,13 +110,14 @@ class Diagnoser(): def report(self): - print(self.cached_time_ago()) - if self.args.get("force", False) or self.cached_time_ago() < self.cache_duration: self.logger_debug("Using cached report from %s" % self.cache_file) return self.get_cached_report() - new_report = list(self.run()) + new_report = { "id": self.id_, + "cached_for": self.cache_duration, + "reports": list(self.run()) + } # TODO / FIXME : should handle the case where we only did a partial diagnosis self.logger_debug("Updating cache %s" % self.cache_file) From cb6f53fc2bac2f215a6fbd0d43db08ad8d65a76d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 30 Aug 2018 14:18:44 +0000 Subject: [PATCH 0266/3170] Fix --force mechanism --- src/yunohost/diagnosis.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index 8fc46967b..93c2dfc67 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -88,7 +88,8 @@ class Diagnoser(): self.logger_debug, self.logger_warning, self.logger_info = loggers self.env = env - self.args = self.validate_args(args) + self.args = args + self.args.update(self.validate_args(args)) @property def cache_file(self): @@ -110,10 +111,12 @@ class Diagnoser(): def report(self): - if self.args.get("force", False) or self.cached_time_ago() < self.cache_duration: + if not self.args.get("force", False) and self.cached_time_ago() < self.cache_duration: self.logger_debug("Using cached report from %s" % self.cache_file) return self.get_cached_report() + self.logger_debug("Running diagnostic for %s" % self.id_) + new_report = { "id": self.id_, "cached_for": self.cache_duration, "reports": list(self.run()) From faa4682d77cb512f21b12e901a78c2789ab28b3c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 30 Aug 2018 14:53:36 +0000 Subject: [PATCH 0267/3170] Forgot to keep the description --- src/yunohost/diagnosis.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index 93c2dfc67..09fcd8dcd 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -118,6 +118,7 @@ class Diagnoser(): self.logger_debug("Running diagnostic for %s" % self.id_) new_report = { "id": self.id_, + "description": self.description, "cached_for": self.cache_duration, "reports": list(self.run()) } From abffba960747378f1f714514ba932a9f0222a5f5 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 30 Aug 2018 15:08:46 +0000 Subject: [PATCH 0268/3170] Remove the priority in the id of the diagnoser --- data/hooks/diagnosis/10-ip.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/hooks/diagnosis/10-ip.py b/data/hooks/diagnosis/10-ip.py index bde96b22e..b0a3ca1e9 100644 --- a/data/hooks/diagnosis/10-ip.py +++ b/data/hooks/diagnosis/10-ip.py @@ -8,7 +8,7 @@ from yunohost.diagnosis import Diagnoser class IPDiagnoser(Diagnoser): - id_ = os.path.splitext(os.path.basename(__file__))[0] + id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] description = "internet_connectivity" cache_duration = 60 From 8a415579bf54d1515040fbe6f8d84f1d889effc4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 30 Aug 2018 16:01:01 +0000 Subject: [PATCH 0269/3170] Implement diagnosis_show --- data/actionsmap/yunohost.yml | 4 +-- src/yunohost/diagnosis.py | 51 +++++++++++++++++++++++++++++------- 2 files changed, 43 insertions(+), 12 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index aa85fdf70..47a858b27 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1874,9 +1874,9 @@ diagnosis: action_help: List diagnosis categories api: GET /diagnosis/list - report: + show: action_help: Show most recents diagnosis results - api: GET /diagnosis/report + api: GET /diagnosis/show arguments: categories: help: Diagnosis categories to display (all by default) diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index 09fcd8dcd..5144e9c06 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -43,8 +43,33 @@ def diagnosis_list(): all_categories_names = [ h for h, _ in _list_diagnosis_categories() ] return { "categories": all_categories_names } -def diagnosis_report(categories=[], full=False): - pass +def diagnosis_show(categories=[], full=False): + + # Get all the categories + all_categories = _list_diagnosis_categories() + all_categories_names = [ category for category, _ in all_categories ] + + # Check the requested category makes sense + if categories == []: + categories = all_categories_names + else: + unknown_categories = [ c for c in categories if c not in all_categories_names ] + if unknown_categories: + raise MoulinetteError(m18n.n('unknown_categories', categories=", ".join(categories))) + + # Fetch all reports + all_reports = [ Diagnoser.get_cached_report(c) for c in categories ] + + # "Render" the strings with m18n.n + for report in all_reports: + + report["description"] = m18n.n(report["description"]) + + for r in report["reports"]: + type_, message_key, message_args = r["report"] + r["report"] = (type_, m18n.n(message_key, **message_args)) + + return {"reports": all_reports} def diagnosis_run(categories=[], force=False, args=None): @@ -82,6 +107,7 @@ def diagnosis_ignore(category, args="", unignore=False): ############################################################ + class Diagnoser(): def __init__(self, args, env, loggers): @@ -90,10 +116,7 @@ class Diagnoser(): self.env = env self.args = args self.args.update(self.validate_args(args)) - - @property - def cache_file(self): - return os.path.join(DIAGNOSIS_CACHE, "%s.json" % self.id_) + self.cache_file = Diagnoser.cache_file(self.id_) def cached_time_ago(self): @@ -101,9 +124,6 @@ class Diagnoser(): return 99999999 return time.time() - os.path.getmtime(self.cache_file) - def get_cached_report(self): - return read_json(self.cache_file) - def write_cache(self, report): if not os.path.exists(DIAGNOSIS_CACHE): os.makedirs(DIAGNOSIS_CACHE) @@ -113,7 +133,7 @@ class Diagnoser(): if not self.args.get("force", False) and self.cached_time_ago() < self.cache_duration: self.logger_debug("Using cached report from %s" % self.cache_file) - return self.get_cached_report() + return Diagnoser.get_cached_report(self.id_) self.logger_debug("Running diagnostic for %s" % self.id_) @@ -129,6 +149,17 @@ class Diagnoser(): return new_report + @staticmethod + def cache_file(id_): + return os.path.join(DIAGNOSIS_CACHE, "%s.json" % id_) + + @staticmethod + def get_cached_report(id_): + filename = Diagnoser.cache_file(id_) + report = read_json(filename) + report["timestamp"] = int(os.path.getmtime(filename)) + return report + def _list_diagnosis_categories(): From b03e3a487e8c54d116be839f46217ab8044371fe Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 30 Aug 2018 17:49:42 +0000 Subject: [PATCH 0270/3170] Handle cases where some category might fail for some reason --- data/hooks/diagnosis/10-ip.py | 4 ++-- src/yunohost/diagnosis.py | 26 +++++++++++++++++--------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/data/hooks/diagnosis/10-ip.py b/data/hooks/diagnosis/10-ip.py index b0a3ca1e9..898a6ac0f 100644 --- a/data/hooks/diagnosis/10-ip.py +++ b/data/hooks/diagnosis/10-ip.py @@ -55,6 +55,6 @@ class IPDiagnoser(Diagnoser): def main(args, env, loggers): - - return IPDiagnoser(args, env, loggers).report() + IPDiagnoser(args, env, loggers).diagnose() + return 0 diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index 5144e9c06..805ac0b97 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -58,7 +58,12 @@ def diagnosis_show(categories=[], full=False): raise MoulinetteError(m18n.n('unknown_categories', categories=", ".join(categories))) # Fetch all reports - all_reports = [ Diagnoser.get_cached_report(c) for c in categories ] + all_reports = [] + for category in categories: + try: + all_reports.append(Diagnoser.get_cached_report(category)) + except Exception as e: + logger.error("Failed to fetch diagnosis result for category '%s' : %s" % (category, str(e))) # FIXME : i18n # "Render" the strings with m18n.n for report in all_reports: @@ -83,7 +88,7 @@ def diagnosis_run(categories=[], force=False, args=None): else: unknown_categories = [ c for c in categories if c not in all_categories_names ] if unknown_categories: - raise MoulinetteError(m18n.n('unknown_categories', categories=", ".join(categories))) + raise MoulinetteError(m18n.n('unknown_categories', categories=", ".join(unknown_categories))) # Transform "arg1=val1&arg2=val2" to { "arg1": "val1", "arg2": "val2" } if args is not None: @@ -92,15 +97,20 @@ def diagnosis_run(categories=[], force=False, args=None): args = {} args["force"] = force - # Call the hook ... + successes = [] for category in categories: logger.debug("Running diagnosis for %s ..." % category) path = [p for n, p in all_categories if n == category ][0] - # TODO : get the return value and do something with it - return {"report": hook_exec(path, args=args, env=None) } + try: + hook_exec(path, args=args, env=None) + successes.append(category) + except Exception as e: + # FIXME / TODO : add stacktrace here ? + logger.error("Diagnosis failed for category '%s' : %s" % (category, str(e))) # FIXME : i18n + return diagnosis_show(successes) def diagnosis_ignore(category, args="", unignore=False): pass @@ -132,8 +142,8 @@ class Diagnoser(): def report(self): if not self.args.get("force", False) and self.cached_time_ago() < self.cache_duration: - self.logger_debug("Using cached report from %s" % self.cache_file) - return Diagnoser.get_cached_report(self.id_) + self.logger_debug("Cache still valid : %s" % self.cache_file) + return self.logger_debug("Running diagnostic for %s" % self.id_) @@ -147,8 +157,6 @@ class Diagnoser(): self.logger_debug("Updating cache %s" % self.cache_file) self.write_cache(new_report) - return new_report - @staticmethod def cache_file(id_): return os.path.join(DIAGNOSIS_CACHE, "%s.json" % id_) From 77b0920dac4d15b6ba70405f63ba023f229be0ae Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 31 Aug 2018 01:35:12 +0000 Subject: [PATCH 0271/3170] Forgot to change this --- src/yunohost/diagnosis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index 805ac0b97..48e9977c1 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -139,7 +139,7 @@ class Diagnoser(): os.makedirs(DIAGNOSIS_CACHE) return write_to_json(self.cache_file, report) - def report(self): + def diagnose(self): if not self.args.get("force", False) and self.cached_time_ago() < self.cache_duration: self.logger_debug("Cache still valid : %s" % self.cache_file) From 85930163a09653fec43336f965d5fa9a2bc20497 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 31 Aug 2018 16:36:10 +0000 Subject: [PATCH 0272/3170] First draft of DNS diagnoser --- data/hooks/diagnosis/12-dns.py | 93 ++++++++++++++++++++++++++++++++++ locales/en.json | 4 +- 2 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 data/hooks/diagnosis/12-dns.py diff --git a/data/hooks/diagnosis/12-dns.py b/data/hooks/diagnosis/12-dns.py new file mode 100644 index 000000000..b4cedebad --- /dev/null +++ b/data/hooks/diagnosis/12-dns.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python + +import os + +from moulinette import m18n +from moulinette.utils.network import download_text +from moulinette.core import MoulinetteError, init_authenticator +from moulinette.utils.process import check_output + +from yunohost.diagnosis import Diagnoser +from yunohost.domain import domain_list, _build_dns_conf, _get_maindomain + +# Instantiate LDAP Authenticator +auth_identifier = ('ldap', 'ldap-anonymous') +auth_parameters = {'uri': 'ldap://localhost:389', 'base_dn': 'dc=yunohost,dc=org'} +auth = init_authenticator(auth_identifier, auth_parameters) + +class DNSDiagnoser(Diagnoser): + + id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] + description = "dns_configurations" + cache_duration = 3600*24 + + def validate_args(self, args): + all_domains = domain_list(auth)["domains"] + if "domain" not in args.keys(): + return { "domains" : all_domains } + else: + if args["domain"] not in all_domains: + raise MoulinetteError(errno.EINVAL, m18n.n('domain_unknown')) + return { "domains" : [ args["domain"] ] } + + def run(self): + + self.resolver = check_output('grep "$nameserver" /etc/resolv.dnsmasq.conf').split("\n")[0].split(" ")[1] + + main_domain = _get_maindomain() + + for domain in self.args["domains"]: + self.logger_info("Diagnosing DNS conf for %s" % domain) + for report in self.check_domain(domain, domain==main_domain): + yield report + + def check_domain(self, domain, is_main_domain): + + expected_configuration = _build_dns_conf(domain) + + # Here if there are no AAAA record, we should add something to expect "no" AAAA record + # to properly diagnose situations where people have a AAAA record but no IPv6 + + for category, records in expected_configuration.items(): + + discrepancies = [] + + for r in records: + current_value = self.get_current_record(domain, r["name"], r["type"]) or "None" + expected_value = r["value"] if r["value"] != "@" else domain+"." + + if current_value != expected_value: + discrepancies.append((r, expected_value, current_value)) + + if discrepancies: + if category == "basic" or is_main_domain: + level = "ERROR" + else: + level = "WARNING" + report = (level, "diagnosis_dns_bad_conf", {"domain": domain, "category": category}) + else: + level = "SUCCESS" + report = ("SUCCESS", "diagnosis_dns_good_conf", {"domain": domain, "category": category}) + + # FIXME : add management of details of what's wrong if there are discrepancies + yield dict(meta = {"domain": domain, "category": category}, + result = level, report = report ) + + + + def get_current_record(self, domain, name, type_): + if name == "@": + command = "dig +short @%s %s %s" % (self.resolver, type_, domain) + else: + command = "dig +short @%s %s %s.%s" % (self.resolver, type_, name, domain) + output = check_output(command).strip() + output = output.replace("\;",";") + if output.startswith('"') and output.endswith('"'): + output = '"' + ' '.join(output.replace('"',' ').split()) + '"' + return output + + +def main(args, env, loggers): + DNSDiagnoser(args, env, loggers).diagnose() + return 0 + diff --git a/locales/en.json b/locales/en.json index a91da4fe9..77701478c 100644 --- a/locales/en.json +++ b/locales/en.json @@ -155,7 +155,9 @@ "diagnosis_no_apps": "No installed application", "dpkg_is_broken": "You cannot do this right now because dpkg/APT (the system package managers) seems to be in a broken state… You can try to solve this issue by connecting through SSH and running `sudo dpkg --configure -a`.", "dpkg_lock_not_available": "This command can't be ran right now because another program seems to be using the lock of dpkg (the system package manager)", - "domain_cannot_remove_main": "Cannot remove main domain. Set one first", + "diagnosis_dns_good_conf": "Good DNS configuration for {domain} : {category}.", + "diagnosis_dns_bad_conf": "Bad DNS configuration for {domain} : {category}.", + "domain_cannot_remove_main": "Cannot remove main domain. Set a new main domain first", "domain_cert_gen_failed": "Could not generate certificate", "domain_created": "Domain created", "domain_creation_failed": "Could not create domain {domain}: {error}", From ded4895b7e590ab60b5349f6a22e983782191adb Mon Sep 17 00:00:00 2001 From: Bram Date: Sat, 1 Sep 2018 02:50:22 +0200 Subject: [PATCH 0273/3170] [mod] misc, better error message I'm using repr to be able to detect if it's a string or a number since it's an error I'm expecting --- data/hooks/diagnosis/10-ip.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/hooks/diagnosis/10-ip.py b/data/hooks/diagnosis/10-ip.py index 898a6ac0f..574741da9 100644 --- a/data/hooks/diagnosis/10-ip.py +++ b/data/hooks/diagnosis/10-ip.py @@ -45,7 +45,7 @@ class IPDiagnoser(Diagnoser): elif protocol == 6: url = 'https://ip6.yunohost.org' else: - raise ValueError("invalid protocol version") + raise ValueError("invalid protocol version, it should be either 4 or 6 and was '%s'" % repr(protocol)) try: return download_text(url, timeout=30).strip() From 2b2ff08f08e8e62761e3a126090ebebd3bce7877 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 2 May 2019 17:59:35 +0200 Subject: [PATCH 0274/3170] Fix error handling (Yunohost / Moulinette / Asserts) --- data/hooks/diagnosis/10-ip.py | 6 +++--- data/hooks/diagnosis/12-dns.py | 6 ++---- src/yunohost/diagnosis.py | 9 ++++----- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/data/hooks/diagnosis/10-ip.py b/data/hooks/diagnosis/10-ip.py index 574741da9..d229eea8f 100644 --- a/data/hooks/diagnosis/10-ip.py +++ b/data/hooks/diagnosis/10-ip.py @@ -4,6 +4,7 @@ import os from moulinette import m18n from moulinette.utils.network import download_text + from yunohost.diagnosis import Diagnoser class IPDiagnoser(Diagnoser): @@ -16,8 +17,7 @@ class IPDiagnoser(Diagnoser): if "version" not in args.keys(): return { "versions" : [4, 6] } else: - if str(args["version"]) not in ["4", "6"]: - raise MoulinetteError(1, "Invalid version, should be 4 or 6.") + assert str(args["version"]) in ["4", "6"], "Invalid version, should be 4 or 6." return { "versions" : [int(args["version"])] } def run(self): @@ -30,7 +30,7 @@ class IPDiagnoser(Diagnoser): result = ipv4, report = ("SUCCESS", "diagnosis_network_connected_ipv4", {}) if ipv4 \ else ("ERROR", "diagnosis_network_no_ipv4", {})) - + if 6 in versions: ipv6 = self.get_public_ip(6) yield dict(meta = {"version": 6}, diff --git a/data/hooks/diagnosis/12-dns.py b/data/hooks/diagnosis/12-dns.py index b4cedebad..9bf6a13a3 100644 --- a/data/hooks/diagnosis/12-dns.py +++ b/data/hooks/diagnosis/12-dns.py @@ -2,9 +2,8 @@ import os -from moulinette import m18n from moulinette.utils.network import download_text -from moulinette.core import MoulinetteError, init_authenticator +from moulinette.core import init_authenticator from moulinette.utils.process import check_output from yunohost.diagnosis import Diagnoser @@ -26,8 +25,7 @@ class DNSDiagnoser(Diagnoser): if "domain" not in args.keys(): return { "domains" : all_domains } else: - if args["domain"] not in all_domains: - raise MoulinetteError(errno.EINVAL, m18n.n('domain_unknown')) + assert args["domain"] in all_domains, "Unknown domain" return { "domains" : [ args["domain"] ] } def run(self): diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index 48e9977c1..22770ce87 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -24,15 +24,14 @@ Look for possible issues on the server """ -import errno import os import time from moulinette import m18n -from moulinette.core import MoulinetteError from moulinette.utils import log from moulinette.utils.filesystem import read_json, write_to_json +from yunohost.utils.error import YunohostError from yunohost.hook import hook_list, hook_exec logger = log.getActionLogger('yunohost.diagnosis') @@ -55,7 +54,7 @@ def diagnosis_show(categories=[], full=False): else: unknown_categories = [ c for c in categories if c not in all_categories_names ] if unknown_categories: - raise MoulinetteError(m18n.n('unknown_categories', categories=", ".join(categories))) + raise YunohostError('unknown_categories', categories=", ".join(categories)) # Fetch all reports all_reports = [] @@ -88,7 +87,7 @@ def diagnosis_run(categories=[], force=False, args=None): else: unknown_categories = [ c for c in categories if c not in all_categories_names ] if unknown_categories: - raise MoulinetteError(m18n.n('unknown_categories', categories=", ".join(unknown_categories))) + raise YunohostError('unknown_categories', categories=", ".join(unknown_categories)) # Transform "arg1=val1&arg2=val2" to { "arg1": "val1", "arg2": "val2" } if args is not None: @@ -108,7 +107,7 @@ def diagnosis_run(categories=[], force=False, args=None): successes.append(category) except Exception as e: # FIXME / TODO : add stacktrace here ? - logger.error("Diagnosis failed for category '%s' : %s" % (category, str(e))) # FIXME : i18n + logger.error("Diagnosis failed for category '%s' : %s" % (category, str(e))) # FIXME : i18n return diagnosis_show(successes) From 3200fef39c3fb5966031d62153b02baa82f38dfb Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 21 May 2019 20:08:09 +0200 Subject: [PATCH 0275/3170] Implement detail mechanism for DNS category --- data/hooks/diagnosis/12-dns.py | 17 ++++++++++++----- src/yunohost/diagnosis.py | 3 +++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/data/hooks/diagnosis/12-dns.py b/data/hooks/diagnosis/12-dns.py index 9bf6a13a3..e6370ba05 100644 --- a/data/hooks/diagnosis/12-dns.py +++ b/data/hooks/diagnosis/12-dns.py @@ -54,8 +54,10 @@ class DNSDiagnoser(Diagnoser): current_value = self.get_current_record(domain, r["name"], r["type"]) or "None" expected_value = r["value"] if r["value"] != "@" else domain+"." - if current_value != expected_value: - discrepancies.append((r, expected_value, current_value)) + if current_value == "None": + discrepancies.append(("diagnosis_dns_missing_record", (r["type"], r["name"], expected_value))) + elif current_value != expected_value: + discrepancies.append(("diagnosis_dns_discrepancy", (r["type"], r["name"], expected_value, current_value))) if discrepancies: if category == "basic" or is_main_domain: @@ -66,11 +68,16 @@ class DNSDiagnoser(Diagnoser): else: level = "SUCCESS" report = ("SUCCESS", "diagnosis_dns_good_conf", {"domain": domain, "category": category}) + details = None - # FIXME : add management of details of what's wrong if there are discrepancies - yield dict(meta = {"domain": domain, "category": category}, - result = level, report = report ) + output = dict(meta = {"domain": domain, "category": category}, + result = level, + report = report ) + if discrepancies: + output["details"] = discrepancies + + yield output def get_current_record(self, domain, name, type_): diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index 22770ce87..a8fae4124 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -73,6 +73,9 @@ def diagnosis_show(categories=[], full=False): type_, message_key, message_args = r["report"] r["report"] = (type_, m18n.n(message_key, **message_args)) + if "details" in r: + r["details"] = [ m18n.n(key, *values) for key, values in r["details"] ] + return {"reports": all_reports} def diagnosis_run(categories=[], force=False, args=None): From aafef0a8efad86a1c0b223bc7111133d388ce971 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 21 May 2019 20:08:31 +0200 Subject: [PATCH 0276/3170] Add i18n messages --- locales/en.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/locales/en.json b/locales/en.json index 77701478c..0245bf49d 100644 --- a/locales/en.json +++ b/locales/en.json @@ -155,8 +155,15 @@ "diagnosis_no_apps": "No installed application", "dpkg_is_broken": "You cannot do this right now because dpkg/APT (the system package managers) seems to be in a broken state… You can try to solve this issue by connecting through SSH and running `sudo dpkg --configure -a`.", "dpkg_lock_not_available": "This command can't be ran right now because another program seems to be using the lock of dpkg (the system package manager)", + "diagnosis_network_connected_ipv4": "The server is connected to the Internet through IPv4 !", + "diagnosis_network_no_ipv4": "The server does not have a working IPv4.", + "diagnosis_network_connected_ipv6": "The server is connect to the Internet through IPv6 !", + "diagnosis_network_no_ipv6": "The server does not have a working IPv6.", "diagnosis_dns_good_conf": "Good DNS configuration for {domain} : {category}.", "diagnosis_dns_bad_conf": "Bad DNS configuration for {domain} : {category}.", + "diagnosis_dns_missing_record": "According to the recommended DNS configuration, you should add a DNS record with type {0}, name {1} and value {2}", + "diagnosis_dns_discrepancy": "According to the recommended DNS configuration, the value for the DNS record with type {0} and name {1} should be {2}, not {3}.", + "dns_configurations": "Domain name configuration (DNS)", "domain_cannot_remove_main": "Cannot remove main domain. Set a new main domain first", "domain_cert_gen_failed": "Could not generate certificate", "domain_created": "Domain created", @@ -238,6 +245,7 @@ "hook_name_unknown": "Unknown hook name '{name:s}'", "installation_complete": "Installation complete", "installation_failed": "Something went wrong with the installation", + "internet_connectivity": "Internet connectivity", "ip6tables_unavailable": "You cannot play with ip6tables here. You are either in a container or your kernel does not support it", "iptables_unavailable": "You cannot play with iptables here. You are either in a container or your kernel does not support it", "log_corrupted_md_file": "The YAML metadata file associated with logs is damaged: '{md_file}\nError: {error}'", From 06e02de548f4735ab9a9944ac784918700df9bb5 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 13 Jul 2019 18:19:16 +0200 Subject: [PATCH 0277/3170] Add traceback for easier debugging --- src/yunohost/diagnosis.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index a8fae4124..99767e1b8 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -109,8 +109,7 @@ def diagnosis_run(categories=[], force=False, args=None): hook_exec(path, args=args, env=None) successes.append(category) except Exception as e: - # FIXME / TODO : add stacktrace here ? - logger.error("Diagnosis failed for category '%s' : %s" % (category, str(e))) # FIXME : i18n + logger.error("Diagnosis failed for category '%s' : %s" % (category, str(e)), exc_info=True) # FIXME : i18n return diagnosis_show(successes) From 1105b7d943d20dbe2939e5b4310803e9da744ec2 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 13 Jul 2019 18:19:50 +0200 Subject: [PATCH 0278/3170] We don't need this auth madness anymore --- data/hooks/diagnosis/12-dns.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/data/hooks/diagnosis/12-dns.py b/data/hooks/diagnosis/12-dns.py index e6370ba05..3a61b0503 100644 --- a/data/hooks/diagnosis/12-dns.py +++ b/data/hooks/diagnosis/12-dns.py @@ -3,17 +3,11 @@ import os from moulinette.utils.network import download_text -from moulinette.core import init_authenticator from moulinette.utils.process import check_output from yunohost.diagnosis import Diagnoser from yunohost.domain import domain_list, _build_dns_conf, _get_maindomain -# Instantiate LDAP Authenticator -auth_identifier = ('ldap', 'ldap-anonymous') -auth_parameters = {'uri': 'ldap://localhost:389', 'base_dn': 'dc=yunohost,dc=org'} -auth = init_authenticator(auth_identifier, auth_parameters) - class DNSDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] @@ -21,7 +15,7 @@ class DNSDiagnoser(Diagnoser): cache_duration = 3600*24 def validate_args(self, args): - all_domains = domain_list(auth)["domains"] + all_domains = domain_list()["domains"] if "domain" not in args.keys(): return { "domains" : all_domains } else: From bd3a378d285edd907858963d671ebd63cf76c210 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 13 Jul 2019 18:22:29 +0200 Subject: [PATCH 0279/3170] Use only ipv4 resolver for DNS records diagnosis --- data/hooks/diagnosis/12-dns.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/data/hooks/diagnosis/12-dns.py b/data/hooks/diagnosis/12-dns.py index 3a61b0503..90f52c82d 100644 --- a/data/hooks/diagnosis/12-dns.py +++ b/data/hooks/diagnosis/12-dns.py @@ -4,6 +4,7 @@ import os from moulinette.utils.network import download_text from moulinette.utils.process import check_output +from moulinette.utils.filesystem import read_file from yunohost.diagnosis import Diagnoser from yunohost.domain import domain_list, _build_dns_conf, _get_maindomain @@ -24,8 +25,12 @@ class DNSDiagnoser(Diagnoser): def run(self): - self.resolver = check_output('grep "$nameserver" /etc/resolv.dnsmasq.conf').split("\n")[0].split(" ")[1] + resolvers = read_file("/etc/resolv.dnsmasq.conf").split("\n") + ipv4_resolvers = [r.split(" ")[1] for r in resolvers if r.startswith("nameserver") and ":" not in r] + # FIXME some day ... handle ipv4-only and ipv6-only servers. For now we assume we have at least ipv4 + assert ipv4_resolvers != [], "Uhoh, need at least one IPv4 DNS resolver ..." + self.resolver = ipv4_resolvers[0] main_domain = _get_maindomain() for domain in self.args["domains"]: From 0ce4eb0a27a8c6e128835bc9787d14d7ac294f04 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 13 Jul 2019 19:23:49 +0200 Subject: [PATCH 0280/3170] Fix the return interface of diagnosis hooks --- data/hooks/diagnosis/10-ip.py | 3 +-- data/hooks/diagnosis/12-dns.py | 3 +-- src/yunohost/diagnosis.py | 13 ++++++++----- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/data/hooks/diagnosis/10-ip.py b/data/hooks/diagnosis/10-ip.py index d229eea8f..19e4806f6 100644 --- a/data/hooks/diagnosis/10-ip.py +++ b/data/hooks/diagnosis/10-ip.py @@ -55,6 +55,5 @@ class IPDiagnoser(Diagnoser): def main(args, env, loggers): - IPDiagnoser(args, env, loggers).diagnose() - return 0 + return IPDiagnoser(args, env, loggers).diagnose() diff --git a/data/hooks/diagnosis/12-dns.py b/data/hooks/diagnosis/12-dns.py index 90f52c82d..09f8cd4bf 100644 --- a/data/hooks/diagnosis/12-dns.py +++ b/data/hooks/diagnosis/12-dns.py @@ -92,6 +92,5 @@ class DNSDiagnoser(Diagnoser): def main(args, env, loggers): - DNSDiagnoser(args, env, loggers).diagnose() - return 0 + return DNSDiagnoser(args, env, loggers).diagnose() diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index 99767e1b8..38c59793f 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -100,18 +100,19 @@ def diagnosis_run(categories=[], force=False, args=None): args["force"] = force # Call the hook ... - successes = [] + diagnosed_categories = [] for category in categories: logger.debug("Running diagnosis for %s ..." % category) path = [p for n, p in all_categories if n == category ][0] try: hook_exec(path, args=args, env=None) - successes.append(category) except Exception as e: logger.error("Diagnosis failed for category '%s' : %s" % (category, str(e)), exc_info=True) # FIXME : i18n + else: + diagnosed_categories.append(category) - return diagnosis_show(successes) + return diagnosis_show(diagnosed_categories) def diagnosis_ignore(category, args="", unignore=False): pass @@ -125,8 +126,8 @@ class Diagnoser(): self.logger_debug, self.logger_warning, self.logger_info = loggers self.env = env - self.args = args - self.args.update(self.validate_args(args)) + self.args = args or {} + self.args.update(self.validate_args(self.args)) self.cache_file = Diagnoser.cache_file(self.id_) def cached_time_ago(self): @@ -158,6 +159,8 @@ class Diagnoser(): self.logger_debug("Updating cache %s" % self.cache_file) self.write_cache(new_report) + return 0, new_report + @staticmethod def cache_file(id_): return os.path.join(DIAGNOSIS_CACHE, "%s.json" % id_) From af23f53d8295affdc54cd3c7fc435e2d1c6bb205 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 13 Jul 2019 19:52:04 +0200 Subject: [PATCH 0281/3170] Simplify / reorganize i18n management for report and description --- data/hooks/diagnosis/10-ip.py | 1 - .../diagnosis/{12-dns.py => 12-dnsrecords.py} | 7 ++- locales/en.json | 4 +- src/yunohost/diagnosis.py | 44 +++++++++++++------ 4 files changed, 36 insertions(+), 20 deletions(-) rename data/hooks/diagnosis/{12-dns.py => 12-dnsrecords.py} (94%) diff --git a/data/hooks/diagnosis/10-ip.py b/data/hooks/diagnosis/10-ip.py index 19e4806f6..665c0ff0d 100644 --- a/data/hooks/diagnosis/10-ip.py +++ b/data/hooks/diagnosis/10-ip.py @@ -10,7 +10,6 @@ from yunohost.diagnosis import Diagnoser class IPDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] - description = "internet_connectivity" cache_duration = 60 def validate_args(self, args): diff --git a/data/hooks/diagnosis/12-dns.py b/data/hooks/diagnosis/12-dnsrecords.py similarity index 94% rename from data/hooks/diagnosis/12-dns.py rename to data/hooks/diagnosis/12-dnsrecords.py index 09f8cd4bf..5edfc2d41 100644 --- a/data/hooks/diagnosis/12-dns.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -9,10 +9,9 @@ from moulinette.utils.filesystem import read_file from yunohost.diagnosis import Diagnoser from yunohost.domain import domain_list, _build_dns_conf, _get_maindomain -class DNSDiagnoser(Diagnoser): +class DNSRecordsDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] - description = "dns_configurations" cache_duration = 3600*24 def validate_args(self, args): @@ -34,7 +33,7 @@ class DNSDiagnoser(Diagnoser): main_domain = _get_maindomain() for domain in self.args["domains"]: - self.logger_info("Diagnosing DNS conf for %s" % domain) + self.logger_debug("Diagnosing DNS conf for %s" % domain) for report in self.check_domain(domain, domain==main_domain): yield report @@ -92,5 +91,5 @@ class DNSDiagnoser(Diagnoser): def main(args, env, loggers): - return DNSDiagnoser(args, env, loggers).diagnose() + return DNSRecordsDiagnoser(args, env, loggers).diagnose() diff --git a/locales/en.json b/locales/en.json index 0245bf49d..0bb6d7275 100644 --- a/locales/en.json +++ b/locales/en.json @@ -163,7 +163,8 @@ "diagnosis_dns_bad_conf": "Bad DNS configuration for {domain} : {category}.", "diagnosis_dns_missing_record": "According to the recommended DNS configuration, you should add a DNS record with type {0}, name {1} and value {2}", "diagnosis_dns_discrepancy": "According to the recommended DNS configuration, the value for the DNS record with type {0} and name {1} should be {2}, not {3}.", - "dns_configurations": "Domain name configuration (DNS)", + "diagnosis_description_ip": "Internet connectivity", + "diagnosis_description_dnsrecords": "DNS records", "domain_cannot_remove_main": "Cannot remove main domain. Set a new main domain first", "domain_cert_gen_failed": "Could not generate certificate", "domain_created": "Domain created", @@ -245,7 +246,6 @@ "hook_name_unknown": "Unknown hook name '{name:s}'", "installation_complete": "Installation complete", "installation_failed": "Something went wrong with the installation", - "internet_connectivity": "Internet connectivity", "ip6tables_unavailable": "You cannot play with ip6tables here. You are either in a container or your kernel does not support it", "iptables_unavailable": "You cannot play with iptables here. You are either in a container or your kernel does not support it", "log_corrupted_md_file": "The YAML metadata file associated with logs is damaged: '{md_file}\nError: {error}'", diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index 38c59793f..fb5220679 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -64,18 +64,6 @@ def diagnosis_show(categories=[], full=False): except Exception as e: logger.error("Failed to fetch diagnosis result for category '%s' : %s" % (category, str(e))) # FIXME : i18n - # "Render" the strings with m18n.n - for report in all_reports: - - report["description"] = m18n.n(report["description"]) - - for r in report["reports"]: - type_, message_key, message_args = r["report"] - r["report"] = (type_, m18n.n(message_key, **message_args)) - - if "details" in r: - r["details"] = [ m18n.n(key, *values) for key, values in r["details"] ] - return {"reports": all_reports} def diagnosis_run(categories=[], force=False, args=None): @@ -130,6 +118,13 @@ class Diagnoser(): self.args.update(self.validate_args(self.args)) self.cache_file = Diagnoser.cache_file(self.id_) + descr_key = "diagnosis_description_" + self.id_ + self.description = m18n.n(descr_key) + # If no description available, fallback to id + if self.description == descr_key: + self.description = report["id"] + + def cached_time_ago(self): if not os.path.exists(self.cache_file): @@ -145,12 +140,12 @@ class Diagnoser(): if not self.args.get("force", False) and self.cached_time_ago() < self.cache_duration: self.logger_debug("Cache still valid : %s" % self.cache_file) + # FIXME uhoh that's not consistent with the other return later return self.logger_debug("Running diagnostic for %s" % self.id_) new_report = { "id": self.id_, - "description": self.description, "cached_for": self.cache_duration, "reports": list(self.run()) } @@ -158,6 +153,7 @@ class Diagnoser(): # TODO / FIXME : should handle the case where we only did a partial diagnosis self.logger_debug("Updating cache %s" % self.cache_file) self.write_cache(new_report) + Diagnoser.i18n(new_report) return 0, new_report @@ -170,8 +166,30 @@ class Diagnoser(): filename = Diagnoser.cache_file(id_) report = read_json(filename) report["timestamp"] = int(os.path.getmtime(filename)) + Diagnoser.i18n(report) return report + @staticmethod + def i18n(report): + + # "Render" the strings with m18n.n + # N.B. : we do those m18n.n right now instead of saving the already-translated report + # because we can't be sure we'll redisplay the infos with the same locale as it + # was generated ... e.g. if the diagnosing happened inside a cron job with locale EN + # instead of FR used by the actual admin... + + descr_key = "diagnosis_description_" + report["id"] + report["description"] = m18n.n(descr_key) + # If no description available, fallback to id + if report["description"] == descr_key: + report["description"] = report["id"] + + for r in report["reports"]: + type_, message_key, message_args = r["report"] + r["report"] = (type_, m18n.n(message_key, **message_args)) + + if "details" in r: + r["details"] = [ m18n.n(key, *values) for key, values in r["details"] ] def _list_diagnosis_categories(): From 9405362caff317f5d90423c21803b0a00e94d66a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 13 Jul 2019 21:00:00 +0200 Subject: [PATCH 0282/3170] Cooler messages summarizing what's found, instead of displaying a huge unreadable wall of json/yaml --- src/yunohost/diagnosis.py | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index fb5220679..7297e6d4b 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -27,7 +27,7 @@ import os import time -from moulinette import m18n +from moulinette import m18n, msettings from moulinette.utils import log from moulinette.utils.filesystem import read_json, write_to_json @@ -87,6 +87,7 @@ def diagnosis_run(categories=[], force=False, args=None): args = {} args["force"] = force + found_issues = False # Call the hook ... diagnosed_categories = [] for category in categories: @@ -94,13 +95,23 @@ def diagnosis_run(categories=[], force=False, args=None): path = [p for n, p in all_categories if n == category ][0] try: - hook_exec(path, args=args, env=None) + code, report = hook_exec(path, args=args, env=None) except Exception as e: logger.error("Diagnosis failed for category '%s' : %s" % (category, str(e)), exc_info=True) # FIXME : i18n else: diagnosed_categories.append(category) + if report != {}: + issues = [r for r in report["reports"] if r["report"][0] in ["ERROR", "WARNING"]] + if issues: + found_issues = True - return diagnosis_show(diagnosed_categories) + if found_issues: + if msettings.get("interface") == "api": + logger.info("You can go to the Diagnosis section (in the home screen) to see the issues found.") + else: + logger.info("You can run 'yunohost diagnosis show --issues' to display the issues found.") + + return def diagnosis_ignore(category, args="", unignore=False): pass @@ -140,8 +151,8 @@ class Diagnoser(): if not self.args.get("force", False) and self.cached_time_ago() < self.cache_duration: self.logger_debug("Cache still valid : %s" % self.cache_file) - # FIXME uhoh that's not consistent with the other return later - return + logger.info("(Cache still valid for %s diagnosis. Not re-diagnosing yet!)" % self.description) + return 0, {} self.logger_debug("Running diagnostic for %s" % self.id_) @@ -155,6 +166,17 @@ class Diagnoser(): self.write_cache(new_report) Diagnoser.i18n(new_report) + errors = [r for r in new_report["reports"] if r["report"][0] == "ERROR"] + warnings = [r for r in new_report["reports"] if r["report"][0] == "WARNING"] + + # FIXME : i18n + if errors: + logger.error("Found %s significant issue(s) related to %s!" % (len(errors), new_report["description"])) + elif warnings: + logger.warning("Found %s item(s) that could be improved for %s." % (len(warnings), new_report["description"])) + else: + logger.success("Everything looks good for %s!" % new_report["description"]) + return 0, new_report @staticmethod From 1d8ba7fa95305cf440d3a3888813bde13d5cc564 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 13 Jul 2019 21:38:35 +0200 Subject: [PATCH 0283/3170] Implement diagnosis show --full and --issues --- data/actionsmap/yunohost.yml | 3 +++ src/yunohost/diagnosis.py | 18 ++++++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 47a858b27..6b89a819b 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1884,6 +1884,9 @@ diagnosis: --full: help: Display additional information action: store_true + --issues: + help: Only display issues + action: store_true run: action_help: Show most recents diagnosis results diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index 7297e6d4b..de73bd680 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -42,7 +42,7 @@ def diagnosis_list(): all_categories_names = [ h for h, _ in _list_diagnosis_categories() ] return { "categories": all_categories_names } -def diagnosis_show(categories=[], full=False): +def diagnosis_show(categories=[], issues=False, full=False): # Get all the categories all_categories = _list_diagnosis_categories() @@ -60,9 +60,23 @@ def diagnosis_show(categories=[], full=False): all_reports = [] for category in categories: try: - all_reports.append(Diagnoser.get_cached_report(category)) + cat_report = Diagnoser.get_cached_report(category) except Exception as e: logger.error("Failed to fetch diagnosis result for category '%s' : %s" % (category, str(e))) # FIXME : i18n + else: + if not full: + del cat_report["timestamp"] + del cat_report["cached_for"] + for report in cat_report["reports"]: + del report["meta"] + del report["result"] + if issues: + cat_report["reports"] = [ r for r in cat_report["reports"] if r["report"][0] != "SUCCESS" ] + if not cat_report["reports"]: + continue + + all_reports.append(cat_report) + return {"reports": all_reports} From 41c3b054baf7640d5f97164643e9fe779885c843 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 13 Jul 2019 22:07:07 +0200 Subject: [PATCH 0284/3170] Fix semantic, way too many things called 'report' ... --- data/hooks/diagnosis/10-ip.py | 14 ++++---- data/hooks/diagnosis/12-dnsrecords.py | 16 ++++----- src/yunohost/diagnosis.py | 48 ++++++++++++++------------- 3 files changed, 39 insertions(+), 39 deletions(-) diff --git a/data/hooks/diagnosis/10-ip.py b/data/hooks/diagnosis/10-ip.py index 665c0ff0d..f38d1fadf 100644 --- a/data/hooks/diagnosis/10-ip.py +++ b/data/hooks/diagnosis/10-ip.py @@ -26,16 +26,18 @@ class IPDiagnoser(Diagnoser): if 4 in versions: ipv4 = self.get_public_ip(4) yield dict(meta = {"version": 4}, - result = ipv4, - report = ("SUCCESS", "diagnosis_network_connected_ipv4", {}) if ipv4 \ - else ("ERROR", "diagnosis_network_no_ipv4", {})) + data = ipv4, + status = "SUCCESS" if ipv4 else "ERROR", + summary = ("diagnosis_network_connected_ipv4", {}) if ipv4 \ + else ("diagnosis_network_no_ipv4", {})) if 6 in versions: ipv6 = self.get_public_ip(6) yield dict(meta = {"version": 6}, - result = ipv6, - report = ("SUCCESS", "diagnosis_network_connected_ipv6", {}) if ipv6 \ - else ("WARNING", "diagnosis_network_no_ipv6", {})) + data = ipv6, + status = "SUCCESS" if ipv6 else "WARNING", + summary = ("diagnosis_network_connected_ipv6", {}) if ipv6 \ + else ("diagnosis_network_no_ipv6", {})) def get_public_ip(self, protocol=4): diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py index 5edfc2d41..3ba64445d 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -58,19 +58,15 @@ class DNSRecordsDiagnoser(Diagnoser): discrepancies.append(("diagnosis_dns_discrepancy", (r["type"], r["name"], expected_value, current_value))) if discrepancies: - if category == "basic" or is_main_domain: - level = "ERROR" - else: - level = "WARNING" - report = (level, "diagnosis_dns_bad_conf", {"domain": domain, "category": category}) + status = "ERROR" if (category == "basic" or is_main_domain) else "WARNING" + summary = ("diagnosis_dns_bad_conf", {"domain": domain, "category": category}) else: - level = "SUCCESS" - report = ("SUCCESS", "diagnosis_dns_good_conf", {"domain": domain, "category": category}) - details = None + status = "SUCCESS" + summary = ("diagnosis_dns_good_conf", {"domain": domain, "category": category}) output = dict(meta = {"domain": domain, "category": category}, - result = level, - report = report ) + status = status, + summary = summary) if discrepancies: output["details"] = discrepancies diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index de73bd680..523a5c891 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -60,22 +60,24 @@ def diagnosis_show(categories=[], issues=False, full=False): all_reports = [] for category in categories: try: - cat_report = Diagnoser.get_cached_report(category) + report = Diagnoser.get_cached_report(category) except Exception as e: logger.error("Failed to fetch diagnosis result for category '%s' : %s" % (category, str(e))) # FIXME : i18n else: if not full: - del cat_report["timestamp"] - del cat_report["cached_for"] - for report in cat_report["reports"]: - del report["meta"] - del report["result"] + del report["timestamp"] + del report["cached_for"] + for item in report["items"]: + del item["meta"] + if "data" in item: + del item["data"] if issues: - cat_report["reports"] = [ r for r in cat_report["reports"] if r["report"][0] != "SUCCESS" ] - if not cat_report["reports"]: + report["items"] = [ item for item in report["items"] if item["status"] != "SUCCESS" ] + # Ignore this category if no issue was found + if not report["items"]: continue - all_reports.append(cat_report) + all_reports.append(report) return {"reports": all_reports} @@ -101,7 +103,7 @@ def diagnosis_run(categories=[], force=False, args=None): args = {} args["force"] = force - found_issues = False + issues = [] # Call the hook ... diagnosed_categories = [] for category in categories: @@ -115,11 +117,9 @@ def diagnosis_run(categories=[], force=False, args=None): else: diagnosed_categories.append(category) if report != {}: - issues = [r for r in report["reports"] if r["report"][0] in ["ERROR", "WARNING"]] - if issues: - found_issues = True + issues.extend([item for item in report["items"] if item["status"] != "SUCCESS"]) - if found_issues: + if issues: if msettings.get("interface") == "api": logger.info("You can go to the Diagnosis section (in the home screen) to see the issues found.") else: @@ -147,7 +147,7 @@ class Diagnoser(): self.description = m18n.n(descr_key) # If no description available, fallback to id if self.description == descr_key: - self.description = report["id"] + self.description = self.id_ def cached_time_ago(self): @@ -170,9 +170,11 @@ class Diagnoser(): self.logger_debug("Running diagnostic for %s" % self.id_) + items = list(self.run()) + new_report = { "id": self.id_, "cached_for": self.cache_duration, - "reports": list(self.run()) + "items": items } # TODO / FIXME : should handle the case where we only did a partial diagnosis @@ -180,8 +182,8 @@ class Diagnoser(): self.write_cache(new_report) Diagnoser.i18n(new_report) - errors = [r for r in new_report["reports"] if r["report"][0] == "ERROR"] - warnings = [r for r in new_report["reports"] if r["report"][0] == "WARNING"] + errors = [item for item in new_report["items"] if item["status"] == "ERROR"] + warnings = [item for item in new_report["items"] if item["status"] == "WARNING"] # FIXME : i18n if errors: @@ -220,12 +222,12 @@ class Diagnoser(): if report["description"] == descr_key: report["description"] = report["id"] - for r in report["reports"]: - type_, message_key, message_args = r["report"] - r["report"] = (type_, m18n.n(message_key, **message_args)) + for item in report["items"]: + summary_key, summary_args = item["summary"] + item["summary"] = m18n.n(summary_key, **summary_args) - if "details" in r: - r["details"] = [ m18n.n(key, *values) for key, values in r["details"] ] + if "details" in item: + item["details"] = [ m18n.n(key, *values) for key, values in item["details"] ] def _list_diagnosis_categories(): From 4d5ace06dbc3466013838c61c2f4efc8c15bfa69 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 13 Jul 2019 23:11:28 +0200 Subject: [PATCH 0285/3170] Add test that we can ping outside before talking to ip.yunohost.org --- data/hooks/diagnosis/10-ip.py | 54 ++++++++++++++++++++++++++- data/hooks/diagnosis/12-dnsrecords.py | 3 ++ locales/en.json | 4 +- 3 files changed, 57 insertions(+), 4 deletions(-) diff --git a/data/hooks/diagnosis/10-ip.py b/data/hooks/diagnosis/10-ip.py index f38d1fadf..3259c6a4a 100644 --- a/data/hooks/diagnosis/10-ip.py +++ b/data/hooks/diagnosis/10-ip.py @@ -1,9 +1,12 @@ #!/usr/bin/env python import os +import random from moulinette import m18n from moulinette.utils.network import download_text +from moulinette.utils.process import check_output +from moulinette.utils.filesystem import read_file from yunohost.diagnosis import Diagnoser @@ -24,7 +27,12 @@ class IPDiagnoser(Diagnoser): versions = self.args["versions"] if 4 in versions: - ipv4 = self.get_public_ip(4) + + if not self.can_ping_outside(4): + ipv4 = None + else: + ipv4 = self.get_public_ip(4) + yield dict(meta = {"version": 4}, data = ipv4, status = "SUCCESS" if ipv4 else "ERROR", @@ -32,15 +40,57 @@ class IPDiagnoser(Diagnoser): else ("diagnosis_network_no_ipv4", {})) if 6 in versions: - ipv6 = self.get_public_ip(6) + + if not self.can_ping_outside(4): + ipv6 = None + else: + ipv6 = self.get_public_ip(6) + yield dict(meta = {"version": 6}, data = ipv6, status = "SUCCESS" if ipv6 else "WARNING", summary = ("diagnosis_network_connected_ipv6", {}) if ipv6 \ else ("diagnosis_network_no_ipv6", {})) + + def can_ping_outside(self, protocol=4): + + assert protocol in [4, 6], "Invalid protocol version, it should be either 4 or 6 and was '%s'" % repr(protocol) + + # We can know that ipv6 is not available directly if this file does not exists + if protocol == 6 and not os.path.exists("/proc/net/if_inet6"): + return False + + # If we are indeed connected in ipv4 or ipv6, we should find a default route + routes = check_output("ip -%s route" % protocol).split("\n") + if not [r for r in routes if r.startswith("default")]: + return False + + # We use the resolver file as a list of well-known, trustable (ie not google ;)) IPs that we can ping + resolver_file = "/usr/share/yunohost/templates/dnsmasq/plain/resolv.dnsmasq.conf" + resolvers = [r.split(" ")[1] for r in read_file(resolver_file).split("\n") if r.startswith("nameserver")] + + if protocol == 4: + resolvers = [r for r in resolvers if ":" not in r] + if protocol == 6: + resolvers = [r for r in resolvers if ":" in r] + + assert resolvers != [], "Uhoh, need at least one IPv%s DNS resolver in %s ..." % (protocol, resolver_file) + + # So let's try to ping the first 4~5 resolvers (shuffled) + # If we succesfully ping any of them, we conclude that we are indeed connected + def ping(protocol, target): + return os.system("ping -c1 -%s -W 3 %s >/dev/null 2>/dev/null" % (protocol, target)) == 0 + + random.shuffle(resolvers) + return any(ping(protocol, resolver) for resolver in resolvers[:5]) + def get_public_ip(self, protocol=4): + # FIXME - TODO : here we assume that DNS resolution for ip.yunohost.org is working + # but if we want to be able to diagnose DNS resolution issues independently from + # internet connectivity, we gotta rely on fixed IPs first.... + if protocol == 4: url = 'https://ip.yunohost.org' elif protocol == 6: diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py index 3ba64445d..c8b81fd2c 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -79,6 +79,9 @@ class DNSRecordsDiagnoser(Diagnoser): command = "dig +short @%s %s %s" % (self.resolver, type_, domain) else: command = "dig +short @%s %s %s.%s" % (self.resolver, type_, name, domain) + # FIXME : gotta handle case where this command fails ... + # e.g. no internet connectivity (dependency mechanism to good result from 'ip' diagosis ?) + # or the resolver is unavailable for some reason output = check_output(command).strip() output = output.replace("\;",";") if output.startswith('"') and output.endswith('"'): diff --git a/locales/en.json b/locales/en.json index 0bb6d7275..ae5e4dc53 100644 --- a/locales/en.json +++ b/locales/en.json @@ -159,8 +159,8 @@ "diagnosis_network_no_ipv4": "The server does not have a working IPv4.", "diagnosis_network_connected_ipv6": "The server is connect to the Internet through IPv6 !", "diagnosis_network_no_ipv6": "The server does not have a working IPv6.", - "diagnosis_dns_good_conf": "Good DNS configuration for {domain} : {category}.", - "diagnosis_dns_bad_conf": "Bad DNS configuration for {domain} : {category}.", + "diagnosis_dns_good_conf": "Good DNS configuration for domain {domain} (category {category})", + "diagnosis_dns_bad_conf": "Bad DNS configuration for domain {domain} (category {category})", "diagnosis_dns_missing_record": "According to the recommended DNS configuration, you should add a DNS record with type {0}, name {1} and value {2}", "diagnosis_dns_discrepancy": "According to the recommended DNS configuration, the value for the DNS record with type {0} and name {1} should be {2}, not {3}.", "diagnosis_description_ip": "Internet connectivity", From 5f4450ab87f4a0985a877539cba5fc40231c8555 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 14 Jul 2019 00:35:42 +0200 Subject: [PATCH 0286/3170] Add DNS resolution tests --- data/hooks/diagnosis/10-ip.py | 52 +++++++++++++++++++++++++++++++---- locales/en.json | 11 +++++--- 2 files changed, 54 insertions(+), 9 deletions(-) diff --git a/data/hooks/diagnosis/10-ip.py b/data/hooks/diagnosis/10-ip.py index 3259c6a4a..1835927a2 100644 --- a/data/hooks/diagnosis/10-ip.py +++ b/data/hooks/diagnosis/10-ip.py @@ -28,16 +28,44 @@ class IPDiagnoser(Diagnoser): if 4 in versions: + # If we can't ping, there's not much else we can do if not self.can_ping_outside(4): ipv4 = None + # If we do ping, check that we can resolv domain name else: - ipv4 = self.get_public_ip(4) + can_resolve_dns = self.can_resolve_dns() + # And if we do, then we can fetch the public ip + if can_resolve_dns: + ipv4 = self.get_public_ip(4) + # In every case, we can check that resolvconf seems to be okay + # (symlink managed by resolvconf service + pointing to dnsmasq) + good_resolvconf = self.resolvconf_is_symlink() and self.resolvconf_points_to_localhost() + + # If we can't resolve domain names at all, that's a pretty big issue ... + # If it turns out that at the same time, resolvconf is bad, that's probably + # the cause of this, so we use a different message in that case + if not can_resolve_dns: + yield dict(meta = {"name": "dnsresolution"}, + status = "ERROR", + summary = ("diagnosis_ip_broken_dnsresolution", {}) if good_resolvconf + else ("diagnosis_ip_broken_resolvconf", {})) + # Otherwise, if the resolv conf is bad but we were able to resolve domain name, + # still warn that we're using a weird resolv conf ... + elif not good_resolvconf: + yield dict(meta = {"name": "dnsresolution"}, + status = "WARNING", + summary = ("diagnosis_ip_weird_resolvconf", {})) + else: + # Well, maybe we could report a "success", "dns resolution is working", idk if it's worth it + pass + + # And finally, we actually report the ipv4 connectivity stuff yield dict(meta = {"version": 4}, data = ipv4, status = "SUCCESS" if ipv4 else "ERROR", - summary = ("diagnosis_network_connected_ipv4", {}) if ipv4 \ - else ("diagnosis_network_no_ipv4", {})) + summary = ("diagnosis_ip_connected_ipv4", {}) if ipv4 \ + else ("diagnosis_ip_no_ipv4", {})) if 6 in versions: @@ -49,8 +77,8 @@ class IPDiagnoser(Diagnoser): yield dict(meta = {"version": 6}, data = ipv6, status = "SUCCESS" if ipv6 else "WARNING", - summary = ("diagnosis_network_connected_ipv6", {}) if ipv6 \ - else ("diagnosis_network_no_ipv6", {})) + summary = ("diagnosis_ip_connected_ipv6", {}) if ipv6 \ + else ("diagnosis_ip_no_ipv6", {})) def can_ping_outside(self, protocol=4): @@ -85,6 +113,20 @@ class IPDiagnoser(Diagnoser): random.shuffle(resolvers) return any(ping(protocol, resolver) for resolver in resolvers[:5]) + + def can_resolve_dns(self): + return os.system("dig +short ip.yunohost.org >/dev/null 2>/dev/null") == 0 + + + def resolvconf_is_symlink(self): + return os.path.realpath("/etc/resolv.conf") == "/run/resolvconf/resolv.conf" + + def resolvconf_points_to_localhost(self): + file_ = "/etc/resolv.conf" + resolvers = [r.split(" ")[1] for r in read_file(file_).split("\n") if r.startswith("nameserver")] + return resolvers == ["127.0.0.1"] + + def get_public_ip(self, protocol=4): # FIXME - TODO : here we assume that DNS resolution for ip.yunohost.org is working diff --git a/locales/en.json b/locales/en.json index ae5e4dc53..515993884 100644 --- a/locales/en.json +++ b/locales/en.json @@ -155,10 +155,13 @@ "diagnosis_no_apps": "No installed application", "dpkg_is_broken": "You cannot do this right now because dpkg/APT (the system package managers) seems to be in a broken state… You can try to solve this issue by connecting through SSH and running `sudo dpkg --configure -a`.", "dpkg_lock_not_available": "This command can't be ran right now because another program seems to be using the lock of dpkg (the system package manager)", - "diagnosis_network_connected_ipv4": "The server is connected to the Internet through IPv4 !", - "diagnosis_network_no_ipv4": "The server does not have a working IPv4.", - "diagnosis_network_connected_ipv6": "The server is connect to the Internet through IPv6 !", - "diagnosis_network_no_ipv6": "The server does not have a working IPv6.", + "diagnosis_ip_connected_ipv4": "The server is connected to the Internet through IPv4 !", + "diagnosis_ip_no_ipv4": "The server does not have a working IPv4.", + "diagnosis_ip_connected_ipv6": "The server is connected to the Internet through IPv6 !", + "diagnosis_ip_no_ipv6": "The server does not have a working IPv6.", + "diagnosis_ip_broken_dnsresolution": "Domain name resolution seems to be broken for some reason ... Is a firewall blocking DNS requests ?", + "diagnosis_ip_broken_resolvconf": "Domain name resolution seems to be broken on your server, which seems related to /etc/resolv.conf not pointing to 127.0.0.1.", + "diagnosis_ip_weird_resolvconf": "Be careful that you seem to be using a custom /etc/resolv.conf. Instead, this file should be a symlink to /etc/resolvconf/run/resolv.conf itself pointing to 127.0.0.1 (dnsmasq).", "diagnosis_dns_good_conf": "Good DNS configuration for domain {domain} (category {category})", "diagnosis_dns_bad_conf": "Bad DNS configuration for domain {domain} (category {category})", "diagnosis_dns_missing_record": "According to the recommended DNS configuration, you should add a DNS record with type {0}, name {1} and value {2}", From aed53786f2f6e3cac78af79205d956a366b4efc3 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 14 Jul 2019 00:45:09 +0200 Subject: [PATCH 0287/3170] Make the PEP8 gods less angry --- data/hooks/diagnosis/10-ip.py | 45 ++++++++++++--------------- data/hooks/diagnosis/12-dnsrecords.py | 25 +++++++-------- src/yunohost/diagnosis.py | 39 ++++++++++++----------- 3 files changed, 51 insertions(+), 58 deletions(-) diff --git a/data/hooks/diagnosis/10-ip.py b/data/hooks/diagnosis/10-ip.py index 1835927a2..1f6c31f50 100644 --- a/data/hooks/diagnosis/10-ip.py +++ b/data/hooks/diagnosis/10-ip.py @@ -3,13 +3,13 @@ import os import random -from moulinette import m18n from moulinette.utils.network import download_text from moulinette.utils.process import check_output from moulinette.utils.filesystem import read_file from yunohost.diagnosis import Diagnoser + class IPDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] @@ -17,10 +17,10 @@ class IPDiagnoser(Diagnoser): def validate_args(self, args): if "version" not in args.keys(): - return { "versions" : [4, 6] } + return {"versions": [4, 6]} else: assert str(args["version"]) in ["4", "6"], "Invalid version, should be 4 or 6." - return { "versions" : [int(args["version"])] } + return {"versions": [int(args["version"])]} def run(self): @@ -46,26 +46,26 @@ class IPDiagnoser(Diagnoser): # If it turns out that at the same time, resolvconf is bad, that's probably # the cause of this, so we use a different message in that case if not can_resolve_dns: - yield dict(meta = {"name": "dnsresolution"}, - status = "ERROR", - summary = ("diagnosis_ip_broken_dnsresolution", {}) if good_resolvconf - else ("diagnosis_ip_broken_resolvconf", {})) + yield dict(meta={"name": "dnsresolution"}, + status="ERROR", + summary=("diagnosis_ip_broken_dnsresolution", {}) if good_resolvconf + else ("diagnosis_ip_broken_resolvconf", {})) # Otherwise, if the resolv conf is bad but we were able to resolve domain name, # still warn that we're using a weird resolv conf ... elif not good_resolvconf: - yield dict(meta = {"name": "dnsresolution"}, - status = "WARNING", - summary = ("diagnosis_ip_weird_resolvconf", {})) + yield dict(meta={"name": "dnsresolution"}, + status="WARNING", + summary=("diagnosis_ip_weird_resolvconf", {})) else: # Well, maybe we could report a "success", "dns resolution is working", idk if it's worth it pass # And finally, we actually report the ipv4 connectivity stuff - yield dict(meta = {"version": 4}, - data = ipv4, - status = "SUCCESS" if ipv4 else "ERROR", - summary = ("diagnosis_ip_connected_ipv4", {}) if ipv4 \ - else ("diagnosis_ip_no_ipv4", {})) + yield dict(meta={"version": 4}, + data=ipv4, + status="SUCCESS" if ipv4 else "ERROR", + summary=("diagnosis_ip_connected_ipv4", {}) if ipv4 + else ("diagnosis_ip_no_ipv4", {})) if 6 in versions: @@ -74,12 +74,11 @@ class IPDiagnoser(Diagnoser): else: ipv6 = self.get_public_ip(6) - yield dict(meta = {"version": 6}, - data = ipv6, - status = "SUCCESS" if ipv6 else "WARNING", - summary = ("diagnosis_ip_connected_ipv6", {}) if ipv6 \ - else ("diagnosis_ip_no_ipv6", {})) - + yield dict(meta={"version": 6}, + data=ipv6, + status="SUCCESS" if ipv6 else "WARNING", + summary=("diagnosis_ip_connected_ipv6", {}) if ipv6 + else ("diagnosis_ip_no_ipv6", {})) def can_ping_outside(self, protocol=4): @@ -113,11 +112,9 @@ class IPDiagnoser(Diagnoser): random.shuffle(resolvers) return any(ping(protocol, resolver) for resolver in resolvers[:5]) - def can_resolve_dns(self): return os.system("dig +short ip.yunohost.org >/dev/null 2>/dev/null") == 0 - def resolvconf_is_symlink(self): return os.path.realpath("/etc/resolv.conf") == "/run/resolvconf/resolv.conf" @@ -126,7 +123,6 @@ class IPDiagnoser(Diagnoser): resolvers = [r.split(" ")[1] for r in read_file(file_).split("\n") if r.startswith("nameserver")] return resolvers == ["127.0.0.1"] - def get_public_ip(self, protocol=4): # FIXME - TODO : here we assume that DNS resolution for ip.yunohost.org is working @@ -149,4 +145,3 @@ class IPDiagnoser(Diagnoser): def main(args, env, loggers): return IPDiagnoser(args, env, loggers).diagnose() - diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py index c8b81fd2c..493010c59 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -2,25 +2,25 @@ import os -from moulinette.utils.network import download_text from moulinette.utils.process import check_output from moulinette.utils.filesystem import read_file from yunohost.diagnosis import Diagnoser from yunohost.domain import domain_list, _build_dns_conf, _get_maindomain + class DNSRecordsDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] - cache_duration = 3600*24 + cache_duration = 3600 * 24 def validate_args(self, args): all_domains = domain_list()["domains"] if "domain" not in args.keys(): - return { "domains" : all_domains } + return {"domains": all_domains} else: assert args["domain"] in all_domains, "Unknown domain" - return { "domains" : [ args["domain"] ] } + return {"domains": [args["domain"]]} def run(self): @@ -34,7 +34,7 @@ class DNSRecordsDiagnoser(Diagnoser): for domain in self.args["domains"]: self.logger_debug("Diagnosing DNS conf for %s" % domain) - for report in self.check_domain(domain, domain==main_domain): + for report in self.check_domain(domain, domain == main_domain): yield report def check_domain(self, domain, is_main_domain): @@ -44,13 +44,13 @@ class DNSRecordsDiagnoser(Diagnoser): # Here if there are no AAAA record, we should add something to expect "no" AAAA record # to properly diagnose situations where people have a AAAA record but no IPv6 - for category, records in expected_configuration.items(): + for category, records in expected_configuration.items(): discrepancies = [] for r in records: current_value = self.get_current_record(domain, r["name"], r["type"]) or "None" - expected_value = r["value"] if r["value"] != "@" else domain+"." + expected_value = r["value"] if r["value"] != "@" else domain + "." if current_value == "None": discrepancies.append(("diagnosis_dns_missing_record", (r["type"], r["name"], expected_value))) @@ -64,16 +64,15 @@ class DNSRecordsDiagnoser(Diagnoser): status = "SUCCESS" summary = ("diagnosis_dns_good_conf", {"domain": domain, "category": category}) - output = dict(meta = {"domain": domain, "category": category}, - status = status, - summary = summary) + output = dict(meta={"domain": domain, "category": category}, + status=status, + summary=summary) if discrepancies: output["details"] = discrepancies yield output - def get_current_record(self, domain, name, type_): if name == "@": command = "dig +short @%s %s %s" % (self.resolver, type_, domain) @@ -83,12 +82,10 @@ class DNSRecordsDiagnoser(Diagnoser): # e.g. no internet connectivity (dependency mechanism to good result from 'ip' diagosis ?) # or the resolver is unavailable for some reason output = check_output(command).strip() - output = output.replace("\;",";") if output.startswith('"') and output.endswith('"'): - output = '"' + ' '.join(output.replace('"',' ').split()) + '"' + output = '"' + ' '.join(output.replace('"', ' ').split()) + '"' return output def main(args, env, loggers): return DNSRecordsDiagnoser(args, env, loggers).diagnose() - diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index 523a5c891..9b17a7457 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -38,21 +38,23 @@ logger = log.getActionLogger('yunohost.diagnosis') DIAGNOSIS_CACHE = "/var/cache/yunohost/diagnosis/" + def diagnosis_list(): - all_categories_names = [ h for h, _ in _list_diagnosis_categories() ] - return { "categories": all_categories_names } + all_categories_names = [h for h, _ in _list_diagnosis_categories()] + return {"categories": all_categories_names} + def diagnosis_show(categories=[], issues=False, full=False): # Get all the categories all_categories = _list_diagnosis_categories() - all_categories_names = [ category for category, _ in all_categories ] + all_categories_names = [category for category, _ in all_categories] # Check the requested category makes sense if categories == []: categories = all_categories_names else: - unknown_categories = [ c for c in categories if c not in all_categories_names ] + unknown_categories = [c for c in categories if c not in all_categories_names] if unknown_categories: raise YunohostError('unknown_categories', categories=", ".join(categories)) @@ -62,7 +64,7 @@ def diagnosis_show(categories=[], issues=False, full=False): try: report = Diagnoser.get_cached_report(category) except Exception as e: - logger.error("Failed to fetch diagnosis result for category '%s' : %s" % (category, str(e))) # FIXME : i18n + logger.error("Failed to fetch diagnosis result for category '%s' : %s" % (category, str(e))) # FIXME : i18n else: if not full: del report["timestamp"] @@ -72,33 +74,33 @@ def diagnosis_show(categories=[], issues=False, full=False): if "data" in item: del item["data"] if issues: - report["items"] = [ item for item in report["items"] if item["status"] != "SUCCESS" ] + report["items"] = [item for item in report["items"] if item["status"] != "SUCCESS"] # Ignore this category if no issue was found if not report["items"]: continue all_reports.append(report) - return {"reports": all_reports} + def diagnosis_run(categories=[], force=False, args=None): # Get all the categories all_categories = _list_diagnosis_categories() - all_categories_names = [ category for category, _ in all_categories ] + all_categories_names = [category for category, _ in all_categories] # Check the requested category makes sense if categories == []: categories = all_categories_names else: - unknown_categories = [ c for c in categories if c not in all_categories_names ] + unknown_categories = [c for c in categories if c not in all_categories_names] if unknown_categories: raise YunohostError('unknown_categories', categories=", ".join(unknown_categories)) # Transform "arg1=val1&arg2=val2" to { "arg1": "val1", "arg2": "val2" } if args is not None: - args = { arg.split("=")[0]: arg.split("=")[1] for arg in args.split("&") } + args = {arg.split("=")[0]: arg.split("=")[1] for arg in args.split("&")} else: args = {} args["force"] = force @@ -108,12 +110,12 @@ def diagnosis_run(categories=[], force=False, args=None): diagnosed_categories = [] for category in categories: logger.debug("Running diagnosis for %s ..." % category) - path = [p for n, p in all_categories if n == category ][0] + path = [p for n, p in all_categories if n == category][0] try: code, report = hook_exec(path, args=args, env=None) except Exception as e: - logger.error("Diagnosis failed for category '%s' : %s" % (category, str(e)), exc_info=True) # FIXME : i18n + logger.error("Diagnosis failed for category '%s' : %s" % (category, str(e)), exc_info=True) # FIXME : i18n else: diagnosed_categories.append(category) if report != {}: @@ -127,6 +129,7 @@ def diagnosis_run(categories=[], force=False, args=None): return + def diagnosis_ignore(category, args="", unignore=False): pass @@ -149,7 +152,6 @@ class Diagnoser(): if self.description == descr_key: self.description = self.id_ - def cached_time_ago(self): if not os.path.exists(self.cache_file): @@ -172,10 +174,9 @@ class Diagnoser(): items = list(self.run()) - new_report = { "id": self.id_, - "cached_for": self.cache_duration, - "items": items - } + new_report = {"id": self.id_, + "cached_for": self.cache_duration, + "items": items} # TODO / FIXME : should handle the case where we only did a partial diagnosis self.logger_debug("Updating cache %s" % self.cache_file) @@ -227,13 +228,13 @@ class Diagnoser(): item["summary"] = m18n.n(summary_key, **summary_args) if "details" in item: - item["details"] = [ m18n.n(key, *values) for key, values in item["details"] ] + item["details"] = [m18n.n(key, *values) for key, values in item["details"]] def _list_diagnosis_categories(): hooks_raw = hook_list("diagnosis", list_by="priority", show_info=True)["hooks"] hooks = [] - for _, some_hooks in sorted(hooks_raw.items(), key=lambda h:int(h[0])): + for _, some_hooks in sorted(hooks_raw.items(), key=lambda h: int(h[0])): for name, info in some_hooks.items(): hooks.append((name, info["path"])) From 1019e95b1d6d4d094c884d2c902a66a3d3deafa4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 20 Jul 2019 18:44:32 +0200 Subject: [PATCH 0288/3170] Implement a first version for services status check --- data/hooks/diagnosis/30-services.py | 54 +++++++++++++++++++++++++++++ locales/en.json | 3 ++ 2 files changed, 57 insertions(+) create mode 100644 data/hooks/diagnosis/30-services.py diff --git a/data/hooks/diagnosis/30-services.py b/data/hooks/diagnosis/30-services.py new file mode 100644 index 000000000..4f08247f1 --- /dev/null +++ b/data/hooks/diagnosis/30-services.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python + +import os + +from yunohost.diagnosis import Diagnoser +from yunohost.service import service_status + +# TODO : all these are arbitrary, should be collectively validated +services_ignored = {"glances"} +services_critical = {"dnsmasq", "fail2ban", "yunohost-firewall", "nginx", "slapd", "ssh"} +# TODO / FIXME : we should do something about this postfix thing +# The nominal value is to be "exited" ... some daemon is actually running +# in a different thread that the thing started by systemd, which is fine +# but somehow sometimes it gets killed and there's no easy way to detect it +# Just randomly restarting it will fix ths issue. We should find some trick +# to identify the PID of the process and check it's still up or idk +services_expected_to_be_exited = {"postfix", "yunohost-firewall"} + +class ServicesDiagnoser(Diagnoser): + + id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] + cache_duration = 300 + + def validate_args(self, args): + # TODO / FIXME Ugh do we really need this arg system + return {} + + def run(self): + + all_result = service_status() + + for service, result in all_result.items(): + + if service in services_ignored: + continue + + item = dict(meta={"service": service}) + expected_status = "running" if service not in services_expected_to_be_exited else "exited" + + # TODO / FIXME : might also want to check that services are enabled + + if result["active"] != "active" or result["status"] != expected_status: + item["status"] = "WARNING" if service not in services_critical else "ERROR" + item["summary"] = ("diagnosis_services_bad_status", {"service": service, "status": result["active"] + "/" + result["status"]}) + + # TODO : could try to append the tail of the service log to the "details" key ... + else: + item["status"] = "SUCCESS" + item["summary"] = ("diagnosis_services_good_status", {"service": service, "status": result["active"] + "/" + result["status"]}) + + yield item + +def main(args, env, loggers): + return ServicesDiagnoser(args, env, loggers).diagnose() diff --git a/locales/en.json b/locales/en.json index 515993884..8fcb0e773 100644 --- a/locales/en.json +++ b/locales/en.json @@ -166,8 +166,11 @@ "diagnosis_dns_bad_conf": "Bad DNS configuration for domain {domain} (category {category})", "diagnosis_dns_missing_record": "According to the recommended DNS configuration, you should add a DNS record with type {0}, name {1} and value {2}", "diagnosis_dns_discrepancy": "According to the recommended DNS configuration, the value for the DNS record with type {0} and name {1} should be {2}, not {3}.", + "diagnosis_services_good_status": "Service {service} is {status} as expected!", + "diagnosis_services_bad_status": "Service {service} is {status} :/", "diagnosis_description_ip": "Internet connectivity", "diagnosis_description_dnsrecords": "DNS records", + "diagnosis_description_services": "Services status check", "domain_cannot_remove_main": "Cannot remove main domain. Set a new main domain first", "domain_cert_gen_failed": "Could not generate certificate", "domain_created": "Domain created", From 24f9d475b8d79fbf5c57034cba49d4bee013fea5 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 20 Jul 2019 18:44:53 +0200 Subject: [PATCH 0289/3170] Implement a first version for disk usage check --- data/hooks/diagnosis/50-diskusage.py | 42 ++++++++++++++++++++++++++++ locales/en.json | 4 +++ 2 files changed, 46 insertions(+) create mode 100644 data/hooks/diagnosis/50-diskusage.py diff --git a/data/hooks/diagnosis/50-diskusage.py b/data/hooks/diagnosis/50-diskusage.py new file mode 100644 index 000000000..84ce3845c --- /dev/null +++ b/data/hooks/diagnosis/50-diskusage.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python +import os +import psutil + +from yunohost.diagnosis import Diagnoser + +class DiskUsageDiagnoser(Diagnoser): + + id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] + cache_duration = 3600 * 24 + + def validate_args(self, args): + # TODO / FIXME Ugh do we really need this arg system + return {} + + def run(self): + + disk_partitions = psutil.disk_partitions() + + for disk_partition in disk_partitions: + device = disk_partition.device + mountpoint = disk_partition.mountpoint + + usage = psutil.disk_usage(mountpoint) + free_Go = usage.free / (1024 ** 3) + free_percent = 100 - usage.percent + + item = dict(meta={"mountpoint": mountpoint, "device": device}) + if free_Go < 1 or free_percent < 5: + item["status"] = "ERROR" + item["summary"] = ("diagnosis_diskusage_verylow", {"mountpoint": mountpoint, "device": device, "free_percent": free_percent}) + elif free_Go < 2 or free_percent < 10: + item["status"] = "WARNING" + item["summary"] = ("diagnosis_diskusage_low", {"mountpoint": mountpoint, "device": device, "free_percent": free_percent}) + else: + item["status"] = "SUCCESS" + item["summary"] = ("diagnosis_diskusage_ok", {"mountpoint": mountpoint, "device": device, "free_percent": free_percent}) + + yield item + +def main(args, env, loggers): + return DiskUsageDiagnoser(args, env, loggers).diagnose() diff --git a/locales/en.json b/locales/en.json index 8fcb0e773..2e93e367f 100644 --- a/locales/en.json +++ b/locales/en.json @@ -168,9 +168,13 @@ "diagnosis_dns_discrepancy": "According to the recommended DNS configuration, the value for the DNS record with type {0} and name {1} should be {2}, not {3}.", "diagnosis_services_good_status": "Service {service} is {status} as expected!", "diagnosis_services_bad_status": "Service {service} is {status} :/", + "diagnosis_diskusage_verylow": "Storage {mountpoint} (on device {device}) has only {free_percent}% space remaining. You should really consider cleaning up some space.", + "diagnosis_diskusage_low": "Storage {mountpoint} (on device {device}) has only {free_percent}% space remaining. Be careful", + "diagnosis_diskusage_ok": "Storage {mountpoint} (on device {device}) still has {free_percent}% space left!", "diagnosis_description_ip": "Internet connectivity", "diagnosis_description_dnsrecords": "DNS records", "diagnosis_description_services": "Services status check", + "diagnosis_description_diskusage": "Disk usage", "domain_cannot_remove_main": "Cannot remove main domain. Set a new main domain first", "domain_cert_gen_failed": "Could not generate certificate", "domain_created": "Domain created", From d2bbb5a2b31718054365a0ee5d63c2298776f32e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 20 Jul 2019 19:02:11 +0200 Subject: [PATCH 0290/3170] This 'args' things sounds like a big YAGNI after all --- data/actionsmap/yunohost.yml | 3 - data/hooks/diagnosis/10-ip.py | 105 ++++++++++++-------------- data/hooks/diagnosis/12-dnsrecords.py | 11 +-- data/hooks/diagnosis/30-services.py | 4 - data/hooks/diagnosis/50-diskusage.py | 4 - src/yunohost/diagnosis.py | 12 +-- 6 files changed, 54 insertions(+), 85 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 6b89a819b..3d72bb57a 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1898,9 +1898,6 @@ diagnosis: --force: help: Ignore the cached report even if it is still 'fresh' action: store_true - -a: - help: Serialized arguments for diagnosis scripts (e.g. "domain=domain.tld") - full: --args ignore: action_help: Configure some diagnosis results to be ignored diff --git a/data/hooks/diagnosis/10-ip.py b/data/hooks/diagnosis/10-ip.py index 1f6c31f50..4a6ee75ce 100644 --- a/data/hooks/diagnosis/10-ip.py +++ b/data/hooks/diagnosis/10-ip.py @@ -15,70 +15,65 @@ class IPDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] cache_duration = 60 - def validate_args(self, args): - if "version" not in args.keys(): - return {"versions": [4, 6]} - else: - assert str(args["version"]) in ["4", "6"], "Invalid version, should be 4 or 6." - return {"versions": [int(args["version"])]} - def run(self): - versions = self.args["versions"] + # + # IPv4 Diagnosis + # - if 4 in versions: + # If we can't ping, there's not much else we can do + if not self.can_ping_outside(4): + ipv4 = None + # If we do ping, check that we can resolv domain name + else: + can_resolve_dns = self.can_resolve_dns() + # And if we do, then we can fetch the public ip + if can_resolve_dns: + ipv4 = self.get_public_ip(4) - # If we can't ping, there's not much else we can do - if not self.can_ping_outside(4): - ipv4 = None - # If we do ping, check that we can resolv domain name - else: - can_resolve_dns = self.can_resolve_dns() - # And if we do, then we can fetch the public ip - if can_resolve_dns: - ipv4 = self.get_public_ip(4) + # In every case, we can check that resolvconf seems to be okay + # (symlink managed by resolvconf service + pointing to dnsmasq) + good_resolvconf = self.resolvconf_is_symlink() and self.resolvconf_points_to_localhost() - # In every case, we can check that resolvconf seems to be okay - # (symlink managed by resolvconf service + pointing to dnsmasq) - good_resolvconf = self.resolvconf_is_symlink() and self.resolvconf_points_to_localhost() + # If we can't resolve domain names at all, that's a pretty big issue ... + # If it turns out that at the same time, resolvconf is bad, that's probably + # the cause of this, so we use a different message in that case + if not can_resolve_dns: + yield dict(meta={"name": "dnsresolution"}, + status="ERROR", + summary=("diagnosis_ip_broken_dnsresolution", {}) if good_resolvconf + else ("diagnosis_ip_broken_resolvconf", {})) + # Otherwise, if the resolv conf is bad but we were able to resolve domain name, + # still warn that we're using a weird resolv conf ... + elif not good_resolvconf: + yield dict(meta={"name": "dnsresolution"}, + status="WARNING", + summary=("diagnosis_ip_weird_resolvconf", {})) + else: + # Well, maybe we could report a "success", "dns resolution is working", idk if it's worth it + pass - # If we can't resolve domain names at all, that's a pretty big issue ... - # If it turns out that at the same time, resolvconf is bad, that's probably - # the cause of this, so we use a different message in that case - if not can_resolve_dns: - yield dict(meta={"name": "dnsresolution"}, - status="ERROR", - summary=("diagnosis_ip_broken_dnsresolution", {}) if good_resolvconf - else ("diagnosis_ip_broken_resolvconf", {})) - # Otherwise, if the resolv conf is bad but we were able to resolve domain name, - # still warn that we're using a weird resolv conf ... - elif not good_resolvconf: - yield dict(meta={"name": "dnsresolution"}, - status="WARNING", - summary=("diagnosis_ip_weird_resolvconf", {})) - else: - # Well, maybe we could report a "success", "dns resolution is working", idk if it's worth it - pass + # And finally, we actually report the ipv4 connectivity stuff + yield dict(meta={"version": 4}, + data=ipv4, + status="SUCCESS" if ipv4 else "ERROR", + summary=("diagnosis_ip_connected_ipv4", {}) if ipv4 + else ("diagnosis_ip_no_ipv4", {})) - # And finally, we actually report the ipv4 connectivity stuff - yield dict(meta={"version": 4}, - data=ipv4, - status="SUCCESS" if ipv4 else "ERROR", - summary=("diagnosis_ip_connected_ipv4", {}) if ipv4 - else ("diagnosis_ip_no_ipv4", {})) + # + # IPv6 Diagnosis + # - if 6 in versions: + if not self.can_ping_outside(4): + ipv6 = None + else: + ipv6 = self.get_public_ip(6) - if not self.can_ping_outside(4): - ipv6 = None - else: - ipv6 = self.get_public_ip(6) - - yield dict(meta={"version": 6}, - data=ipv6, - status="SUCCESS" if ipv6 else "WARNING", - summary=("diagnosis_ip_connected_ipv6", {}) if ipv6 - else ("diagnosis_ip_no_ipv6", {})) + yield dict(meta={"version": 6}, + data=ipv6, + status="SUCCESS" if ipv6 else "WARNING", + summary=("diagnosis_ip_connected_ipv6", {}) if ipv6 + else ("diagnosis_ip_no_ipv6", {})) def can_ping_outside(self, protocol=4): diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py index 493010c59..0f47ff136 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -14,14 +14,6 @@ class DNSRecordsDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] cache_duration = 3600 * 24 - def validate_args(self, args): - all_domains = domain_list()["domains"] - if "domain" not in args.keys(): - return {"domains": all_domains} - else: - assert args["domain"] in all_domains, "Unknown domain" - return {"domains": [args["domain"]]} - def run(self): resolvers = read_file("/etc/resolv.dnsmasq.conf").split("\n") @@ -32,7 +24,8 @@ class DNSRecordsDiagnoser(Diagnoser): self.resolver = ipv4_resolvers[0] main_domain = _get_maindomain() - for domain in self.args["domains"]: + all_domains = domain_list()["domains"] + for domain in all_domains: self.logger_debug("Diagnosing DNS conf for %s" % domain) for report in self.check_domain(domain, domain == main_domain): yield report diff --git a/data/hooks/diagnosis/30-services.py b/data/hooks/diagnosis/30-services.py index 4f08247f1..5029e0a5d 100644 --- a/data/hooks/diagnosis/30-services.py +++ b/data/hooks/diagnosis/30-services.py @@ -21,10 +21,6 @@ class ServicesDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] cache_duration = 300 - def validate_args(self, args): - # TODO / FIXME Ugh do we really need this arg system - return {} - def run(self): all_result = service_status() diff --git a/data/hooks/diagnosis/50-diskusage.py b/data/hooks/diagnosis/50-diskusage.py index 84ce3845c..2c6fe387b 100644 --- a/data/hooks/diagnosis/50-diskusage.py +++ b/data/hooks/diagnosis/50-diskusage.py @@ -9,10 +9,6 @@ class DiskUsageDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] cache_duration = 3600 * 24 - def validate_args(self, args): - # TODO / FIXME Ugh do we really need this arg system - return {} - def run(self): disk_partitions = psutil.disk_partitions() diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index 9b17a7457..e7aca585f 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -84,7 +84,7 @@ def diagnosis_show(categories=[], issues=False, full=False): return {"reports": all_reports} -def diagnosis_run(categories=[], force=False, args=None): +def diagnosis_run(categories=[], force=False): # Get all the categories all_categories = _list_diagnosis_categories() @@ -98,13 +98,6 @@ def diagnosis_run(categories=[], force=False, args=None): if unknown_categories: raise YunohostError('unknown_categories', categories=", ".join(unknown_categories)) - # Transform "arg1=val1&arg2=val2" to { "arg1": "val1", "arg2": "val2" } - if args is not None: - args = {arg.split("=")[0]: arg.split("=")[1] for arg in args.split("&")} - else: - args = {} - args["force"] = force - issues = [] # Call the hook ... diagnosed_categories = [] @@ -113,7 +106,7 @@ def diagnosis_run(categories=[], force=False, args=None): path = [p for n, p in all_categories if n == category][0] try: - code, report = hook_exec(path, args=args, env=None) + code, report = hook_exec(path, args={"force": force}, env=None) except Exception as e: logger.error("Diagnosis failed for category '%s' : %s" % (category, str(e)), exc_info=True) # FIXME : i18n else: @@ -143,7 +136,6 @@ class Diagnoser(): self.logger_debug, self.logger_warning, self.logger_info = loggers self.env = env self.args = args or {} - self.args.update(self.validate_args(self.args)) self.cache_file = Diagnoser.cache_file(self.id_) descr_key = "diagnosis_description_" + self.id_ From 35f6b778956b3755fec10718be0091d416cfdadc Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 20 Jul 2019 19:30:09 +0200 Subject: [PATCH 0291/3170] Reclarify ip diagnoser --- data/hooks/diagnosis/10-ip.py | 72 ++++++++++++++++++----------------- locales/en.json | 2 + 2 files changed, 40 insertions(+), 34 deletions(-) diff --git a/data/hooks/diagnosis/10-ip.py b/data/hooks/diagnosis/10-ip.py index 4a6ee75ce..a4cfc0a48 100644 --- a/data/hooks/diagnosis/10-ip.py +++ b/data/hooks/diagnosis/10-ip.py @@ -17,19 +17,26 @@ class IPDiagnoser(Diagnoser): def run(self): - # - # IPv4 Diagnosis - # + # ############################################################ # + # PING : Check that we can ping outside at least in ipv4 or v6 # + # ############################################################ # - # If we can't ping, there's not much else we can do - if not self.can_ping_outside(4): - ipv4 = None - # If we do ping, check that we can resolv domain name - else: - can_resolve_dns = self.can_resolve_dns() - # And if we do, then we can fetch the public ip - if can_resolve_dns: - ipv4 = self.get_public_ip(4) + can_ping_ipv4 = self.can_ping_outside(4) + can_ping_ipv6 = self.can_ping_outside(6) + + if not can_ping_ipv4 and not can_ping_ipv6: + yield dict(meta={"test": "ping"}, + status="ERROR", + summary=("diagnosis_ip_not_connected_at_all", {})) + # Not much else we can do if there's no internet at all + return + + # ###################################################### # + # DNS RESOLUTION : Check that we can resolve domain name # + # (later needed to talk to ip. and ip6.yunohost.org) # + # ###################################################### # + + can_resolve_dns = self.can_resolve_dns() # In every case, we can check that resolvconf seems to be okay # (symlink managed by resolvconf service + pointing to dnsmasq) @@ -39,37 +46,37 @@ class IPDiagnoser(Diagnoser): # If it turns out that at the same time, resolvconf is bad, that's probably # the cause of this, so we use a different message in that case if not can_resolve_dns: - yield dict(meta={"name": "dnsresolution"}, + yield dict(meta={"test": "dnsresolv"}, status="ERROR", summary=("diagnosis_ip_broken_dnsresolution", {}) if good_resolvconf else ("diagnosis_ip_broken_resolvconf", {})) + return # Otherwise, if the resolv conf is bad but we were able to resolve domain name, # still warn that we're using a weird resolv conf ... elif not good_resolvconf: - yield dict(meta={"name": "dnsresolution"}, + yield dict(meta={"test": "dnsresolv"}, status="WARNING", summary=("diagnosis_ip_weird_resolvconf", {})) else: - # Well, maybe we could report a "success", "dns resolution is working", idk if it's worth it - pass + yield dict(meta={"test": "dnsresolv"}, + status="SUCCESS", + summary=("diagnosis_ip_dnsresolution_working", {})) - # And finally, we actually report the ipv4 connectivity stuff - yield dict(meta={"version": 4}, + # ##################################################### # + # IP DIAGNOSIS : Check that we're actually able to talk # + # to a web server to fetch current IPv4 and v6 # + # ##################################################### # + + ipv4 = self.get_public_ip(4) if can_ping_ipv4 else None + ipv6 = self.get_public_ip(6) if can_ping_ipv6 else None + + yield dict(meta={"test": "ip", "version": 4}, data=ipv4, status="SUCCESS" if ipv4 else "ERROR", summary=("diagnosis_ip_connected_ipv4", {}) if ipv4 else ("diagnosis_ip_no_ipv4", {})) - # - # IPv6 Diagnosis - # - - if not self.can_ping_outside(4): - ipv6 = None - else: - ipv6 = self.get_public_ip(6) - - yield dict(meta={"version": 6}, + yield dict(meta={"test": "ip", "version": 6}, data=ipv6, status="SUCCESS" if ipv6 else "WARNING", summary=("diagnosis_ip_connected_ipv6", {}) if ipv6 @@ -124,12 +131,9 @@ class IPDiagnoser(Diagnoser): # but if we want to be able to diagnose DNS resolution issues independently from # internet connectivity, we gotta rely on fixed IPs first.... - if protocol == 4: - url = 'https://ip.yunohost.org' - elif protocol == 6: - url = 'https://ip6.yunohost.org' - else: - raise ValueError("invalid protocol version, it should be either 4 or 6 and was '%s'" % repr(protocol)) + assert protocol in [4, 6], "Invalid protocol version, it should be either 4 or 6 and was '%s'" % repr(protocol) + + url = 'https://ip%s.yunohost.org' % ('6' if protocol == 6 else '') try: return download_text(url, timeout=30).strip() diff --git a/locales/en.json b/locales/en.json index 2e93e367f..8d6828979 100644 --- a/locales/en.json +++ b/locales/en.json @@ -159,6 +159,8 @@ "diagnosis_ip_no_ipv4": "The server does not have a working IPv4.", "diagnosis_ip_connected_ipv6": "The server is connected to the Internet through IPv6 !", "diagnosis_ip_no_ipv6": "The server does not have a working IPv6.", + "diagnosis_ip_not_connected_at_all": "The server does not seem to be connected to the Internet at all!?", + "diagnosis_ip_dnsresolution_working": "Domain name resolution is working!", "diagnosis_ip_broken_dnsresolution": "Domain name resolution seems to be broken for some reason ... Is a firewall blocking DNS requests ?", "diagnosis_ip_broken_resolvconf": "Domain name resolution seems to be broken on your server, which seems related to /etc/resolv.conf not pointing to 127.0.0.1.", "diagnosis_ip_weird_resolvconf": "Be careful that you seem to be using a custom /etc/resolv.conf. Instead, this file should be a symlink to /etc/resolvconf/run/resolv.conf itself pointing to 127.0.0.1 (dnsmasq).", From f690ff6e1e5ed6f80e185f8b0d5af0248ff025d8 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 30 Jul 2019 18:53:17 +0200 Subject: [PATCH 0292/3170] First version of port exposure diagnosis --- data/hooks/diagnosis/14-ports.py | 53 ++++++++++++++++++++++++++++++++ locales/en.json | 4 +++ 2 files changed, 57 insertions(+) create mode 100644 data/hooks/diagnosis/14-ports.py diff --git a/data/hooks/diagnosis/14-ports.py b/data/hooks/diagnosis/14-ports.py new file mode 100644 index 000000000..6b260f3e0 --- /dev/null +++ b/data/hooks/diagnosis/14-ports.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python + +import os +import requests + +from yunohost.diagnosis import Diagnoser + + +class PortsDiagnoser(Diagnoser): + + id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] + cache_duration = 3600 + + def run(self): + + # FIXME / TODO : in the future, maybe we want to report different + # things per port depending on how important they are + # (e.g. XMPP sounds to me much less important than other ports) + # Ideally, a port could be related to a service... + # FIXME / TODO : for now this list of port is hardcoded, might want + # to fetch this from the firewall.yml in /etc/yunohost/ + ports = [ 22, 25, 53, 80, 443, 587, 993, 5222, 5269 ] + + try: + r = requests.post('https://ynhdiagnoser.netlib.re/check-ports', json={'ports': ports}).json() + if not "status" in r.keys(): + raise Exception("Bad syntax for response ? Raw json: %s" % str(r)) + elif r["status"] == "error": + if "content" in r.keys(): + raise Exception(r["content"]) + else: + raise Exception("Bad syntax for response ? Raw json: %s" % str(r)) + elif r["status"] != "ok" or "ports" not in r.keys() or not isinstance(r["ports"], dict): + raise Exception("Bad syntax for response ? Raw json: %s" % str(r)) + except Exception as e: + raise YunohostError("diagnosis_ports_could_not_diagnose", error=e) + + found_issues = False + for port in ports: + if r["ports"].get(str(port), None) != True: + found_issues = True + yield dict(meta={"port": port}, + status="ERROR", + summary=("diagnosis_ports_unreachable", {"port":port})) + + if not found_issues: + yield dict(meta={}, + status="SUCCESS", + summary=("diagnosis_ports_ok",{})) + + +def main(args, env, loggers): + return PortsDiagnoser(args, env, loggers).diagnose() diff --git a/locales/en.json b/locales/en.json index 8d6828979..0a2204725 100644 --- a/locales/en.json +++ b/locales/en.json @@ -177,6 +177,10 @@ "diagnosis_description_dnsrecords": "DNS records", "diagnosis_description_services": "Services status check", "diagnosis_description_diskusage": "Disk usage", + "diagnosis_description_ports": "Ports exposure", + "diagnosis_ports_could_not_diagnose": "Could not diagnose if ports are reachable from outside. Error: {error}", + "diagnosis_ports_unreachable": "Port {port} is not reachable from outside.", + "diagnosis_ports_ok": "Relevant ports are reachable from outside!", "domain_cannot_remove_main": "Cannot remove main domain. Set a new main domain first", "domain_cert_gen_failed": "Could not generate certificate", "domain_created": "Domain created", From 6c48c131a8cb12b56566a09c68d2e59de68182ef Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 31 Jul 2019 01:02:31 +0200 Subject: [PATCH 0293/3170] Fix small issues in port diagnoser --- data/hooks/diagnosis/14-ports.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/data/hooks/diagnosis/14-ports.py b/data/hooks/diagnosis/14-ports.py index 6b260f3e0..8206474f8 100644 --- a/data/hooks/diagnosis/14-ports.py +++ b/data/hooks/diagnosis/14-ports.py @@ -4,6 +4,7 @@ import os import requests from yunohost.diagnosis import Diagnoser +from yunohost.utils.error import YunohostError class PortsDiagnoser(Diagnoser): @@ -19,11 +20,11 @@ class PortsDiagnoser(Diagnoser): # Ideally, a port could be related to a service... # FIXME / TODO : for now this list of port is hardcoded, might want # to fetch this from the firewall.yml in /etc/yunohost/ - ports = [ 22, 25, 53, 80, 443, 587, 993, 5222, 5269 ] + ports = [22, 25, 53, 80, 443, 587, 993, 5222, 5269] try: - r = requests.post('https://ynhdiagnoser.netlib.re/check-ports', json={'ports': ports}).json() - if not "status" in r.keys(): + r = requests.post('https://ynhdiagnoser.netlib.re/check-ports', json={'ports': ports}, timeout=30).json() + if "status" not in r.keys(): raise Exception("Bad syntax for response ? Raw json: %s" % str(r)) elif r["status"] == "error": if "content" in r.keys(): @@ -37,16 +38,16 @@ class PortsDiagnoser(Diagnoser): found_issues = False for port in ports: - if r["ports"].get(str(port), None) != True: + if r["ports"].get(str(port), None) is not True: found_issues = True yield dict(meta={"port": port}, status="ERROR", - summary=("diagnosis_ports_unreachable", {"port":port})) + summary=("diagnosis_ports_unreachable", {"port": port})) if not found_issues: yield dict(meta={}, status="SUCCESS", - summary=("diagnosis_ports_ok",{})) + summary=("diagnosis_ports_ok", {})) def main(args, env, loggers): From f050b3c5b86bf6c844fc67597d6949324d75be3d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 31 Jul 2019 01:08:21 +0200 Subject: [PATCH 0294/3170] First version of http exposure diagnosis --- data/hooks/diagnosis/16-http.py | 54 ++++++++++++++++++++++++++++ data/templates/nginx/server.tpl.conf | 4 +++ locales/en.json | 4 +++ src/yunohost/app.py | 3 +- 4 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 data/hooks/diagnosis/16-http.py diff --git a/data/hooks/diagnosis/16-http.py b/data/hooks/diagnosis/16-http.py new file mode 100644 index 000000000..b6b92fc77 --- /dev/null +++ b/data/hooks/diagnosis/16-http.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python + +import os +import random +import requests + +from yunohost.diagnosis import Diagnoser +from yunohost.domain import domain_list +from yunohost.utils.error import YunohostError + + +class HttpDiagnoser(Diagnoser): + + id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] + cache_duration = 3600 + + def run(self): + + nonce_digits = "0123456789abcedf" + + all_domains = domain_list()["domains"] + for domain in all_domains: + + nonce = ''.join(random.choice(nonce_digits) for i in range(16)) + os.system("rm -rf /tmp/.well-known/ynh-diagnosis/") + os.system("mkdir -p /tmp/.well-known/ynh-diagnosis/") + os.system("touch /tmp/.well-known/ynh-diagnosis/%s" % nonce) + + try: + r = requests.post('https://ynhdiagnoser.netlib.re/check-http', json={'domain': domain, "nonce": nonce}, timeout=30).json() + print(r) + if "status" not in r.keys(): + raise Exception("Bad syntax for response ? Raw json: %s" % str(r)) + elif r["status"] == "error" and ("code" not in r.keys() or r["code"] not in ["error_http_check_connection_error", "error_http_check_unknown_error"]): + if "content" in r.keys(): + raise Exception(r["content"]) + else: + raise Exception("Bad syntax for response ? Raw json: %s" % str(r)) + except Exception as e: + print(e) + raise YunohostError("diagnosis_http_could_not_diagnose", error=e) + + if r["status"] == "ok": + yield dict(meta={"domain": domain}, + status="SUCCESS", + summary=("diagnosis_http_ok", {"domain": domain})) + else: + yield dict(meta={"domain": domain}, + status="ERROR", + summary=("diagnosis_http_unreachable", {"domain": domain})) + + +def main(args, env, loggers): + return HttpDiagnoser(args, env, loggers).diagnose() diff --git a/data/templates/nginx/server.tpl.conf b/data/templates/nginx/server.tpl.conf index 4a5e91557..9acc6c0fd 100644 --- a/data/templates/nginx/server.tpl.conf +++ b/data/templates/nginx/server.tpl.conf @@ -16,6 +16,10 @@ server { return 301 https://$http_host$request_uri; } + location /.well-known/ynh-diagnosis/ { + alias /tmp/.well-known/ynh-diagnosis/; + } + location /.well-known/autoconfig/mail/ { alias /var/www/.well-known/{{ domain }}/autoconfig/mail/; } diff --git a/locales/en.json b/locales/en.json index 0a2204725..ac44122fe 100644 --- a/locales/en.json +++ b/locales/en.json @@ -178,9 +178,13 @@ "diagnosis_description_services": "Services status check", "diagnosis_description_diskusage": "Disk usage", "diagnosis_description_ports": "Ports exposure", + "diagnosis_description_http": "HTTP exposure", "diagnosis_ports_could_not_diagnose": "Could not diagnose if ports are reachable from outside. Error: {error}", "diagnosis_ports_unreachable": "Port {port} is not reachable from outside.", "diagnosis_ports_ok": "Relevant ports are reachable from outside!", + "diagnosis_http_could_not_diagnose": "Could not diagnose if domain is reachable from outside. Error: {error}", + "diagnosis_http_ok": "Domain {domain} is reachable from outside.", + "diagnosis_http_unreachable": "Domain {domain} is unreachable through HTTP from outside.", "domain_cannot_remove_main": "Cannot remove main domain. Set a new main domain first", "domain_cert_gen_failed": "Could not generate certificate", "domain_created": "Domain created", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index e9e6ce14e..b4962d5f6 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1463,7 +1463,8 @@ def app_ssowatconf(): for domain in domains: skipped_urls.extend([domain + '/yunohost/admin', domain + '/yunohost/api']) - # Authorize ACME challenge url + # Authorize ynh remote diagnosis, ACME challenge and mail autoconfig urls + skipped_regex.append("^[^/]*/%.well%-known/ynh%-diagnosis/.*$") skipped_regex.append("^[^/]*/%.well%-known/acme%-challenge/.*$") skipped_regex.append("^[^/]*/%.well%-known/autoconfig/mail/config%-v1%.1%.xml.*$") From 91ec775ebb695b7a4e3a58951b51a6bd343dfc20 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 31 Jul 2019 16:54:25 +0200 Subject: [PATCH 0295/3170] Implement basic dependency system between diagnoser --- data/hooks/diagnosis/10-ip.py | 1 + data/hooks/diagnosis/12-dnsrecords.py | 1 + data/hooks/diagnosis/14-ports.py | 1 + data/hooks/diagnosis/16-http.py | 3 +-- data/hooks/diagnosis/30-services.py | 1 + data/hooks/diagnosis/50-diskusage.py | 1 + src/yunohost/diagnosis.py | 29 +++++++++++++++++---------- 7 files changed, 24 insertions(+), 13 deletions(-) diff --git a/data/hooks/diagnosis/10-ip.py b/data/hooks/diagnosis/10-ip.py index a4cfc0a48..b29076467 100644 --- a/data/hooks/diagnosis/10-ip.py +++ b/data/hooks/diagnosis/10-ip.py @@ -14,6 +14,7 @@ class IPDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] cache_duration = 60 + dependencies = [] def run(self): diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py index 0f47ff136..0e8aaa07e 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -13,6 +13,7 @@ class DNSRecordsDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] cache_duration = 3600 * 24 + dependencies = ["ip"] def run(self): diff --git a/data/hooks/diagnosis/14-ports.py b/data/hooks/diagnosis/14-ports.py index 8206474f8..82a44384a 100644 --- a/data/hooks/diagnosis/14-ports.py +++ b/data/hooks/diagnosis/14-ports.py @@ -11,6 +11,7 @@ class PortsDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] cache_duration = 3600 + dependencies = ["ip"] def run(self): diff --git a/data/hooks/diagnosis/16-http.py b/data/hooks/diagnosis/16-http.py index b6b92fc77..cc335df8b 100644 --- a/data/hooks/diagnosis/16-http.py +++ b/data/hooks/diagnosis/16-http.py @@ -13,6 +13,7 @@ class HttpDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] cache_duration = 3600 + dependencies = ["ip"] def run(self): @@ -28,7 +29,6 @@ class HttpDiagnoser(Diagnoser): try: r = requests.post('https://ynhdiagnoser.netlib.re/check-http', json={'domain': domain, "nonce": nonce}, timeout=30).json() - print(r) if "status" not in r.keys(): raise Exception("Bad syntax for response ? Raw json: %s" % str(r)) elif r["status"] == "error" and ("code" not in r.keys() or r["code"] not in ["error_http_check_connection_error", "error_http_check_unknown_error"]): @@ -37,7 +37,6 @@ class HttpDiagnoser(Diagnoser): else: raise Exception("Bad syntax for response ? Raw json: %s" % str(r)) except Exception as e: - print(e) raise YunohostError("diagnosis_http_could_not_diagnose", error=e) if r["status"] == "ok": diff --git a/data/hooks/diagnosis/30-services.py b/data/hooks/diagnosis/30-services.py index 5029e0a5d..6589d83f2 100644 --- a/data/hooks/diagnosis/30-services.py +++ b/data/hooks/diagnosis/30-services.py @@ -20,6 +20,7 @@ class ServicesDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] cache_duration = 300 + dependencies = [] def run(self): diff --git a/data/hooks/diagnosis/50-diskusage.py b/data/hooks/diagnosis/50-diskusage.py index 2c6fe387b..74b8eb4b9 100644 --- a/data/hooks/diagnosis/50-diskusage.py +++ b/data/hooks/diagnosis/50-diskusage.py @@ -8,6 +8,7 @@ class DiskUsageDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] cache_duration = 3600 * 24 + dependencies = [] def run(self): diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index e7aca585f..14b332fe3 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -137,12 +137,7 @@ class Diagnoser(): self.env = env self.args = args or {} self.cache_file = Diagnoser.cache_file(self.id_) - - descr_key = "diagnosis_description_" + self.id_ - self.description = m18n.n(descr_key) - # If no description available, fallback to id - if self.description == descr_key: - self.description = self.id_ + self.description = Diagnoser.get_description(self.id_) def cached_time_ago(self): @@ -159,9 +154,18 @@ class Diagnoser(): if not self.args.get("force", False) and self.cached_time_ago() < self.cache_duration: self.logger_debug("Cache still valid : %s" % self.cache_file) + # FIXME : i18n logger.info("(Cache still valid for %s diagnosis. Not re-diagnosing yet!)" % self.description) return 0, {} + for dependency in self.dependencies: + dep_report = Diagnoser.get_cached_report(dependency) + dep_errors = [item for item in dep_report["items"] if item["status"] == "ERROR"] + if dep_errors: + # FIXME : i18n + logger.error("Can't run diagnosis for %s while there are important issues related to %s." % (self.description, Diagnoser.get_description(dependency))) + return 1, {} + self.logger_debug("Running diagnostic for %s" % self.id_) items = list(self.run()) @@ -200,6 +204,13 @@ class Diagnoser(): Diagnoser.i18n(report) return report + @staticmethod + def get_description(id_): + key = "diagnosis_description_" + id_ + descr = m18n.n(key) + # If no description available, fallback to id + return descr if descr != key else id_ + @staticmethod def i18n(report): @@ -209,11 +220,7 @@ class Diagnoser(): # was generated ... e.g. if the diagnosing happened inside a cron job with locale EN # instead of FR used by the actual admin... - descr_key = "diagnosis_description_" + report["id"] - report["description"] = m18n.n(descr_key) - # If no description available, fallback to id - if report["description"] == descr_key: - report["description"] = report["id"] + report["description"] = Diagnoser.get_description(report["id"]) for item in report["items"]: summary_key, summary_args = item["summary"] From 612a96e1e2410eabf677aec2c75194b477fe3cc0 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 31 Jul 2019 17:36:51 +0200 Subject: [PATCH 0296/3170] Yield one item per port open to be consistent with other diagnosers --- data/hooks/diagnosis/14-ports.py | 11 ++++------- locales/en.json | 2 +- src/yunohost/diagnosis.py | 2 ++ 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/data/hooks/diagnosis/14-ports.py b/data/hooks/diagnosis/14-ports.py index 82a44384a..b953f35a9 100644 --- a/data/hooks/diagnosis/14-ports.py +++ b/data/hooks/diagnosis/14-ports.py @@ -37,18 +37,15 @@ class PortsDiagnoser(Diagnoser): except Exception as e: raise YunohostError("diagnosis_ports_could_not_diagnose", error=e) - found_issues = False for port in ports: if r["ports"].get(str(port), None) is not True: - found_issues = True yield dict(meta={"port": port}, status="ERROR", summary=("diagnosis_ports_unreachable", {"port": port})) - - if not found_issues: - yield dict(meta={}, - status="SUCCESS", - summary=("diagnosis_ports_ok", {})) + else: + yield dict(meta={}, + status="SUCCESS", + summary=("diagnosis_ports_ok", {"port": port})) def main(args, env, loggers): diff --git a/locales/en.json b/locales/en.json index ac44122fe..6dce769f1 100644 --- a/locales/en.json +++ b/locales/en.json @@ -181,7 +181,7 @@ "diagnosis_description_http": "HTTP exposure", "diagnosis_ports_could_not_diagnose": "Could not diagnose if ports are reachable from outside. Error: {error}", "diagnosis_ports_unreachable": "Port {port} is not reachable from outside.", - "diagnosis_ports_ok": "Relevant ports are reachable from outside!", + "diagnosis_ports_ok": "Port {port} is reachable from outside.", "diagnosis_http_could_not_diagnose": "Could not diagnose if domain is reachable from outside. Error: {error}", "diagnosis_http_ok": "Domain {domain} is reachable from outside.", "diagnosis_http_unreachable": "Domain {domain} is unreachable through HTTP from outside.", diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index 14b332fe3..88316a15f 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -116,8 +116,10 @@ def diagnosis_run(categories=[], force=False): if issues: if msettings.get("interface") == "api": + # FIXME: i18n logger.info("You can go to the Diagnosis section (in the home screen) to see the issues found.") else: + # FIXME: i18n logger.info("You can run 'yunohost diagnosis show --issues' to display the issues found.") return From 0dc1909c68b6ef594b992764711c81f0ec169ad1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 31 Jul 2019 20:16:22 +0200 Subject: [PATCH 0297/3170] Misc small UX stuff --- data/hooks/diagnosis/10-ip.py | 3 ++- data/hooks/diagnosis/12-dnsrecords.py | 2 +- locales/en.json | 5 +++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/data/hooks/diagnosis/10-ip.py b/data/hooks/diagnosis/10-ip.py index b29076467..8c8dbe95b 100644 --- a/data/hooks/diagnosis/10-ip.py +++ b/data/hooks/diagnosis/10-ip.py @@ -57,7 +57,8 @@ class IPDiagnoser(Diagnoser): elif not good_resolvconf: yield dict(meta={"test": "dnsresolv"}, status="WARNING", - summary=("diagnosis_ip_weird_resolvconf", {})) + summary=("diagnosis_ip_weird_resolvconf", {}), + details=[("diagnosis_ip_weird_resolvconf_details", ())]) else: yield dict(meta={"test": "dnsresolv"}, status="SUCCESS", diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py index 0e8aaa07e..b59ffbd54 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -52,7 +52,7 @@ class DNSRecordsDiagnoser(Diagnoser): discrepancies.append(("diagnosis_dns_discrepancy", (r["type"], r["name"], expected_value, current_value))) if discrepancies: - status = "ERROR" if (category == "basic" or is_main_domain) else "WARNING" + status = "ERROR" if (category == "basic" or (is_main_domain and category != "extra")) else "WARNING" summary = ("diagnosis_dns_bad_conf", {"domain": domain, "category": category}) else: status = "SUCCESS" diff --git a/locales/en.json b/locales/en.json index 6dce769f1..65b3ef64d 100644 --- a/locales/en.json +++ b/locales/en.json @@ -163,9 +163,10 @@ "diagnosis_ip_dnsresolution_working": "Domain name resolution is working!", "diagnosis_ip_broken_dnsresolution": "Domain name resolution seems to be broken for some reason ... Is a firewall blocking DNS requests ?", "diagnosis_ip_broken_resolvconf": "Domain name resolution seems to be broken on your server, which seems related to /etc/resolv.conf not pointing to 127.0.0.1.", - "diagnosis_ip_weird_resolvconf": "Be careful that you seem to be using a custom /etc/resolv.conf. Instead, this file should be a symlink to /etc/resolvconf/run/resolv.conf itself pointing to 127.0.0.1 (dnsmasq).", + "diagnosis_ip_weird_resolvconf": "DNS resolution seems to be working, but be careful that you seem to be using a custom /etc/resolv.conf.", + "diagnosis_ip_weird_resolvconf_details": "Instead, this file should be a symlink to /etc/resolvconf/run/resolv.conf itself pointing to 127.0.0.1 (dnsmasq). The actual resolvers should be configured via /etc/resolv.dnsmasq.conf.", "diagnosis_dns_good_conf": "Good DNS configuration for domain {domain} (category {category})", - "diagnosis_dns_bad_conf": "Bad DNS configuration for domain {domain} (category {category})", + "diagnosis_dns_bad_conf": "Bad / missing DNS configuration for domain {domain} (category {category})", "diagnosis_dns_missing_record": "According to the recommended DNS configuration, you should add a DNS record with type {0}, name {1} and value {2}", "diagnosis_dns_discrepancy": "According to the recommended DNS configuration, the value for the DNS record with type {0} and name {1} should be {2}, not {3}.", "diagnosis_services_good_status": "Service {service} is {status} as expected!", From 4cbd1b06c2d572c908a3d7a6d4e82b6738f7a3da Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 31 Jul 2019 21:25:44 +0200 Subject: [PATCH 0298/3170] Add a regenconf diagnoser to report manually modified files... --- data/hooks/diagnosis/70-regenconf.py | 42 +++++++++++++++++++++++++++ locales/en.json | 5 ++++ src/yunohost/regenconf.py | 43 ++++++++++++++-------------- 3 files changed, 69 insertions(+), 21 deletions(-) create mode 100644 data/hooks/diagnosis/70-regenconf.py diff --git a/data/hooks/diagnosis/70-regenconf.py b/data/hooks/diagnosis/70-regenconf.py new file mode 100644 index 000000000..94c41feb5 --- /dev/null +++ b/data/hooks/diagnosis/70-regenconf.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python + +import os + +from yunohost.diagnosis import Diagnoser +from yunohost.regenconf import manually_modified_files, manually_modified_files_compared_to_debian_default + + +class RegenconfDiagnoser(Diagnoser): + + id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] + cache_duration = 300 + dependencies = [] + + def run(self): + + regenconf_modified_files = manually_modified_files() + debian_modified_files = manually_modified_files_compared_to_debian_default(ignore_handled_by_regenconf=True) + + if regenconf_modified_files == []: + yield dict(meta={"test": "regenconf"}, + status="SUCCESS", + summary=("diagnosis_regenconf_allgood", {}) + ) + else: + for f in regenconf_modified_files: + yield dict(meta={"test": "regenconf", "file": f}, + status="WARNING", + summary=("diagnosis_regenconf_manually_modified", {"file": f}), + details=[("diagnosis_regenconf_manually_modified_details", {})] + ) + + for f in debian_modified_files: + yield dict(meta={"test": "debian", "file": f}, + status="WARNING", + summary=("diagnosis_regenconf_manually_modified_debian", {"file": f}), + details=[("diagnosis_regenconf_manually_modified_debian_details", {})] + ) + + +def main(args, env, loggers): + return RegenconfDiagnoser(args, env, loggers).diagnose() diff --git a/locales/en.json b/locales/en.json index 65b3ef64d..105891571 100644 --- a/locales/en.json +++ b/locales/en.json @@ -174,6 +174,11 @@ "diagnosis_diskusage_verylow": "Storage {mountpoint} (on device {device}) has only {free_percent}% space remaining. You should really consider cleaning up some space.", "diagnosis_diskusage_low": "Storage {mountpoint} (on device {device}) has only {free_percent}% space remaining. Be careful", "diagnosis_diskusage_ok": "Storage {mountpoint} (on device {device}) still has {free_percent}% space left!", + "diagnosis_regenconf_allgood": "All configurations files are in line with the recommended configuration!", + "diagnosis_regenconf_manually_modified": "Configuration file {file} was manually modified.", + "diagnosis_regenconf_manually_modified_details": "This is probably OK as long as you know what you're doing ;) !", + "diagnosis_regenconf_manually_modified_debian": "Configuration file {file} was manually modified compared to Debian's default.", + "diagnosis_regenconf_manually_modified_debian_details": "This may probably be OK, but gotta keep an eye on it...", "diagnosis_description_ip": "Internet connectivity", "diagnosis_description_dnsrecords": "DNS records", "diagnosis_description_services": "Services status check", diff --git a/src/yunohost/regenconf.py b/src/yunohost/regenconf.py index b7a42dd9d..b09824d58 100644 --- a/src/yunohost/regenconf.py +++ b/src/yunohost/regenconf.py @@ -525,31 +525,32 @@ def _process_regen_conf(system_conf, new_conf=None, save=True): def manually_modified_files(): - # We do this to have --quiet, i.e. don't throw a whole bunch of logs - # just to fetch this... - # Might be able to optimize this by looking at what the regen conf does - # and only do the part that checks file hashes... - cmd = "yunohost tools regen-conf --dry-run --output-as json --quiet" - j = json.loads(subprocess.check_output(cmd.split())) - - # j is something like : - # {"postfix": {"applied": {}, "pending": {"/etc/postfix/main.cf": {"status": "modified"}}} - output = [] - for app, actions in j.items(): - for action, files in actions.items(): - for filename, infos in files.items(): - if infos["status"] == "modified": - output.append(filename) + regenconf_categories = _get_regenconf_infos() + for category, infos in regenconf_categories.items(): + conffiles = infos["conffiles"] + for path, hash_ in conffiles.items(): + if hash_ != _calculate_hash(path): + output.append(path) return output -def manually_modified_files_compared_to_debian_default(): +def manually_modified_files_compared_to_debian_default(ignore_handled_by_regenconf=False): # from https://serverfault.com/a/90401 - r = subprocess.check_output("dpkg-query -W -f='${Conffiles}\n' '*' \ - | awk 'OFS=\" \"{print $2,$1}' \ - | md5sum -c 2>/dev/null \ - | awk -F': ' '$2 !~ /OK/{print $1}'", shell=True) - return r.strip().split("\n") + files = subprocess.check_output("dpkg-query -W -f='${Conffiles}\n' '*' \ + | awk 'OFS=\" \"{print $2,$1}' \ + | md5sum -c 2>/dev/null \ + | awk -F': ' '$2 !~ /OK/{print $1}'", shell=True) + files = files.strip().split("\n") + + if ignore_handled_by_regenconf: + regenconf_categories = _get_regenconf_infos() + regenconf_files = [] + for infos in regenconf_categories.values(): + regenconf_files.extend(infos["conffiles"].keys()) + + files = [f for f in files if f not in regenconf_files] + + return files From cee3b4de27dd03fb58f3d4400592c4bf0ec3e017 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 31 Jul 2019 22:04:55 +0200 Subject: [PATCH 0299/3170] Add nginx -t check to regenconf diagnoser --- data/hooks/diagnosis/70-regenconf.py | 14 ++++++++++++++ locales/en.json | 2 ++ 2 files changed, 16 insertions(+) diff --git a/data/hooks/diagnosis/70-regenconf.py b/data/hooks/diagnosis/70-regenconf.py index 94c41feb5..105d43fa3 100644 --- a/data/hooks/diagnosis/70-regenconf.py +++ b/data/hooks/diagnosis/70-regenconf.py @@ -2,6 +2,7 @@ import os +import subprocess from yunohost.diagnosis import Diagnoser from yunohost.regenconf import manually_modified_files, manually_modified_files_compared_to_debian_default @@ -14,6 +15,19 @@ class RegenconfDiagnoser(Diagnoser): def run(self): + # nginx -t + p = subprocess.Popen("nginx -t".split(), + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + out, _ = p.communicate() + + if p.returncode != 0: + yield dict(meta={"test": "nginx-t"}, + status="ERROR", + summary=("diagnosis_regenconf_nginx_conf_broken", {}), + details=[(out, ())] + ) + regenconf_modified_files = manually_modified_files() debian_modified_files = manually_modified_files_compared_to_debian_default(ignore_handled_by_regenconf=True) diff --git a/locales/en.json b/locales/en.json index 105891571..f06310679 100644 --- a/locales/en.json +++ b/locales/en.json @@ -179,12 +179,14 @@ "diagnosis_regenconf_manually_modified_details": "This is probably OK as long as you know what you're doing ;) !", "diagnosis_regenconf_manually_modified_debian": "Configuration file {file} was manually modified compared to Debian's default.", "diagnosis_regenconf_manually_modified_debian_details": "This may probably be OK, but gotta keep an eye on it...", + "diagnosis_regenconf_nginx_conf_broken": "The nginx configuration appears to be broken!", "diagnosis_description_ip": "Internet connectivity", "diagnosis_description_dnsrecords": "DNS records", "diagnosis_description_services": "Services status check", "diagnosis_description_diskusage": "Disk usage", "diagnosis_description_ports": "Ports exposure", "diagnosis_description_http": "HTTP exposure", + "diagnosis_description_regenconf": "System configurations", "diagnosis_ports_could_not_diagnose": "Could not diagnose if ports are reachable from outside. Error: {error}", "diagnosis_ports_unreachable": "Port {port} is not reachable from outside.", "diagnosis_ports_ok": "Port {port} is reachable from outside.", From b81cd4fc68ceb0ed63ff9526958bef60c558e1ab Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 31 Jul 2019 22:10:00 +0200 Subject: [PATCH 0300/3170] Add security diagnoser with meltdown checks --- data/hooks/diagnosis/90-security.py | 98 +++++++++++++++++++++++++++++ locales/en.json | 4 ++ 2 files changed, 102 insertions(+) create mode 100644 data/hooks/diagnosis/90-security.py diff --git a/data/hooks/diagnosis/90-security.py b/data/hooks/diagnosis/90-security.py new file mode 100644 index 000000000..0b1b61226 --- /dev/null +++ b/data/hooks/diagnosis/90-security.py @@ -0,0 +1,98 @@ +#!/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 f06310679..c4330b08a 100644 --- a/locales/en.json +++ b/locales/en.json @@ -180,6 +180,9 @@ "diagnosis_regenconf_manually_modified_debian": "Configuration file {file} was manually modified compared to Debian's default.", "diagnosis_regenconf_manually_modified_debian_details": "This may probably be OK, but gotta keep an eye on it...", "diagnosis_regenconf_nginx_conf_broken": "The nginx configuration appears to be broken!", + "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_ip": "Internet connectivity", "diagnosis_description_dnsrecords": "DNS records", "diagnosis_description_services": "Services status check", @@ -187,6 +190,7 @@ "diagnosis_description_ports": "Ports exposure", "diagnosis_description_http": "HTTP exposure", "diagnosis_description_regenconf": "System configurations", + "diagnosis_description_security": "Security checks", "diagnosis_ports_could_not_diagnose": "Could not diagnose if ports are reachable from outside. Error: {error}", "diagnosis_ports_unreachable": "Port {port} is not reachable from outside.", "diagnosis_ports_ok": "Port {port} is reachable from outside.", From 0c232b6cb5eb397f09d4d5024218b687a5cfcf46 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 1 Aug 2019 19:22:01 +0200 Subject: [PATCH 0301/3170] Implement diagnosis show --share --- data/actionsmap/yunohost.yml | 3 +++ src/yunohost/diagnosis.py | 32 ++++++++++++++++++++++++++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 3d72bb57a..20eb8a0f8 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1887,6 +1887,9 @@ diagnosis: --issues: help: Only display issues action: store_true + --share: + help: Share the logs using yunopaste + action: store_true run: action_help: Show most recents diagnosis results diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index 88316a15f..99a798b91 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -44,7 +44,7 @@ def diagnosis_list(): return {"categories": all_categories_names} -def diagnosis_show(categories=[], issues=False, full=False): +def diagnosis_show(categories=[], issues=False, full=False, share=False): # Get all the categories all_categories = _list_diagnosis_categories() @@ -81,7 +81,35 @@ def diagnosis_show(categories=[], issues=False, full=False): all_reports.append(report) - return {"reports": all_reports} + if share: + from yunohost.utils.yunopaste import yunopaste + content = _dump_human_readable_reports(all_reports) + url = yunopaste(content) + + logger.info(m18n.n("log_available_on_yunopaste", url=url)) + if msettings.get('interface') == 'api': + return {"url": url} + else: + return + else: + return {"reports": all_reports} + +def _dump_human_readable_reports(reports): + + output = "" + + for report in reports: + output += "=================================\n" + output += "{description} ({id})\n".format(**report) + output += "=================================\n\n" + for item in report["items"]: + output += "[{status}] {summary}\n".format(**item) + for detail in item.get("details", []): + output += " - " + detail + "\n" + output += "\n" + output += "\n\n" + + return(output) def diagnosis_run(categories=[], force=False): From c4ba8534c5dbc7b214afaedbae5b704a6bcf4339 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 1 Aug 2019 19:54:46 +0200 Subject: [PATCH 0302/3170] Implement i18n stuff --- locales/en.json | 9 +++++++++ src/yunohost/diagnosis.py | 25 ++++++++++--------------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/locales/en.json b/locales/en.json index c4330b08a..979edbbef 100644 --- a/locales/en.json +++ b/locales/en.json @@ -155,6 +155,15 @@ "diagnosis_no_apps": "No installed application", "dpkg_is_broken": "You cannot do this right now because dpkg/APT (the system package managers) seems to be in a broken state… You can try to solve this issue by connecting through SSH and running `sudo dpkg --configure -a`.", "dpkg_lock_not_available": "This command can't be ran right now because another program seems to be using the lock of dpkg (the system package manager)", + "diagnosis_display_tip_web": "You can go to the Diagnosis section (in the home screen) to see the issues found.", + "diagnosis_display_tip_cli": "You can run 'yunohost diagnosis show --issues' to display the issues found.", + "diagnosis_failed_for_category": "Diagnosis failed for category '{category}' : {error}", + "diagnosis_cache_still_valid": "(Cache still valid for {category} diagnosis. Not re-diagnosing yet!)", + "diagnosis_cant_run_because_of_dep": "Can't run diagnosis for {category} while there are important issues related to {dep}.", + "diagnosis_found_issues": "Found {errors} significant issue(s) related to {category}!", + "diagnosis_found_warnings": "Found {warnings} item(s) that could be improved for {category}.", + "diagnosis_everything_ok": "Everything looks good for {category}!", + "diagnosis_failed": "Failed to fetch diagnosis result for category '{category}' : {error}", "diagnosis_ip_connected_ipv4": "The server is connected to the Internet through IPv4 !", "diagnosis_ip_no_ipv4": "The server does not have a working IPv4.", "diagnosis_ip_connected_ipv6": "The server is connected to the Internet through IPv6 !", diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index 99a798b91..6cf207282 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -64,7 +64,7 @@ def diagnosis_show(categories=[], issues=False, full=False, share=False): try: report = Diagnoser.get_cached_report(category) except Exception as e: - logger.error("Failed to fetch diagnosis result for category '%s' : %s" % (category, str(e))) # FIXME : i18n + logger.error(m18n.n("diagnosis_failed", category=category, error=str(e))) else: if not full: del report["timestamp"] @@ -136,7 +136,7 @@ def diagnosis_run(categories=[], force=False): try: code, report = hook_exec(path, args={"force": force}, env=None) except Exception as e: - logger.error("Diagnosis failed for category '%s' : %s" % (category, str(e)), exc_info=True) # FIXME : i18n + logger.error(m18n.n("diagnosis_failed_for_category", category=category, error=str(e)), exc_info=True) else: diagnosed_categories.append(category) if report != {}: @@ -144,11 +144,9 @@ def diagnosis_run(categories=[], force=False): if issues: if msettings.get("interface") == "api": - # FIXME: i18n - logger.info("You can go to the Diagnosis section (in the home screen) to see the issues found.") + logger.info(m18n.n("diagnosis_display_tip_web")) else: - # FIXME: i18n - logger.info("You can run 'yunohost diagnosis show --issues' to display the issues found.") + logger.info(m18n.n("diagnosis_display_tip_cli")) return @@ -163,6 +161,7 @@ class Diagnoser(): def __init__(self, args, env, loggers): + # FIXME ? That stuff with custom loggers is weird ... (mainly inherited from the bash hooks, idk) self.logger_debug, self.logger_warning, self.logger_info = loggers self.env = env self.args = args or {} @@ -184,16 +183,14 @@ class Diagnoser(): if not self.args.get("force", False) and self.cached_time_ago() < self.cache_duration: self.logger_debug("Cache still valid : %s" % self.cache_file) - # FIXME : i18n - logger.info("(Cache still valid for %s diagnosis. Not re-diagnosing yet!)" % self.description) + logger.info(m18n.n("diagnosis_cache_still_valid", category=self.description)) return 0, {} for dependency in self.dependencies: dep_report = Diagnoser.get_cached_report(dependency) dep_errors = [item for item in dep_report["items"] if item["status"] == "ERROR"] if dep_errors: - # FIXME : i18n - logger.error("Can't run diagnosis for %s while there are important issues related to %s." % (self.description, Diagnoser.get_description(dependency))) + logger.error(m18n.n("diagnosis_cant_run_because_of_dep", category=self.description, dep=Diagnoser.get_description(dependency))) return 1, {} self.logger_debug("Running diagnostic for %s" % self.id_) @@ -204,7 +201,6 @@ class Diagnoser(): "cached_for": self.cache_duration, "items": items} - # TODO / FIXME : should handle the case where we only did a partial diagnosis self.logger_debug("Updating cache %s" % self.cache_file) self.write_cache(new_report) Diagnoser.i18n(new_report) @@ -212,13 +208,12 @@ class Diagnoser(): errors = [item for item in new_report["items"] if item["status"] == "ERROR"] warnings = [item for item in new_report["items"] if item["status"] == "WARNING"] - # FIXME : i18n if errors: - logger.error("Found %s significant issue(s) related to %s!" % (len(errors), new_report["description"])) + logger.error(m18n.n("diagnosis_found_issues", errors=len(errors), category=new_report["description"])) elif warnings: - logger.warning("Found %s item(s) that could be improved for %s." % (len(warnings), new_report["description"])) + logger.warning(m18n.n("diagnosis_found_warnings", warnings=len(warnings), category=new_report["description"])) else: - logger.success("Everything looks good for %s!" % new_report["description"]) + logger.success(m18n.n("diagnosis_everything_ok", category=new_report["description"])) return 0, new_report From 33180d0947118c53f7e6c96da2882483a9be6df9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 1 Aug 2019 21:11:13 +0200 Subject: [PATCH 0303/3170] Add base system diagnostic --- data/hooks/diagnosis/00-basesystem.py | 54 +++++++++++++++++++++++++++ locales/en.json | 6 +++ src/yunohost/diagnosis.py | 4 +- 3 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 data/hooks/diagnosis/00-basesystem.py diff --git a/data/hooks/diagnosis/00-basesystem.py b/data/hooks/diagnosis/00-basesystem.py new file mode 100644 index 000000000..8fa90e65e --- /dev/null +++ b/data/hooks/diagnosis/00-basesystem.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python + +import os + +from moulinette.utils.filesystem import read_file +from yunohost.diagnosis import Diagnoser +from yunohost.utils.packages import ynh_packages_version + + +class BaseSystemDiagnoser(Diagnoser): + + id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] + cache_duration = 3600 * 24 + dependencies = [] + + def run(self): + + # Kernel version + kernel_version = read_file('/proc/sys/kernel/osrelease').strip() + yield dict(meta={"test": "kernel"}, + status="INFO", + summary=("diagnosis_basesystem_kernel", {"kernel_version": kernel_version})) + + # Debian release + debian_version = read_file("/etc/debian_version").strip() + yield dict(meta={"test": "host"}, + status="INFO", + summary=("diagnosis_basesystem_host", {"debian_version": debian_version})) + + # Yunohost packages versions + ynh_packages = ynh_packages_version() + # We check if versions are consistent (e.g. all 3.6 and not 3 packages with 3.6 and the other with 3.5) + # This is a classical issue for upgrades that failed in the middle + # (or people upgrading half of the package because they did 'apt upgrade' instead of 'dist-upgrade') + # Here, ynh_core_version is for example "3.5.4.12", so [:3] is "3.5" and we check it's the same for all packages + ynh_core_version = ynh_packages["yunohost"]["version"] + consistent_versions = all(infos["version"][:3] == ynh_core_version[:3] for infos in ynh_packages.values()) + ynh_version_details = [("diagnosis_basesystem_ynh_single_version", (package, infos["version"])) + for package, infos in ynh_packages.items()] + + if consistent_versions: + yield dict(meta={"test": "ynh_versions"}, + status="INFO", + summary=("diagnosis_basesystem_ynh_main_version", {"main_version": ynh_core_version[:3]}), + details=ynh_version_details) + else: + yield dict(meta={"test": "ynh_versions"}, + status="ERROR", + summary=("diagnosis_basesystem_ynh_inconsistent_versions", {}), + details=ynh_version_details) + + +def main(args, env, loggers): + return BaseSystemDiagnoser(args, env, loggers).diagnose() diff --git a/locales/en.json b/locales/en.json index 979edbbef..f942d3dc4 100644 --- a/locales/en.json +++ b/locales/en.json @@ -150,6 +150,11 @@ "custom_appslist_name_required": "You must provide a name for your custom app list", "diagnosis_debian_version_error": "Could not retrieve the Debian version: {error}", "diagnosis_kernel_version_error": "Could not retrieve kernel version: {error}", + "diagnosis_basesystem_host": "Server is running Debian {debian_version}.", + "diagnosis_basesystem_kernel": "Server is running Linux kernel {kernel_version}", + "diagnosis_basesystem_ynh_single_version": "{0} version: {1}", + "diagnosis_basesystem_ynh_main_version": "Server is running YunoHost {main_version}", + "diagnosis_basesystem_ynh_inconsistent_versions": "You are running inconsistents versions of the YunoHost packages ... most probably because of a failed or partial upgrade.", "diagnosis_monitor_disk_error": "Could not monitor disks: {error}", "diagnosis_monitor_system_error": "Could not monitor system: {error}", "diagnosis_no_apps": "No installed application", @@ -192,6 +197,7 @@ "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", "diagnosis_description_ip": "Internet connectivity", "diagnosis_description_dnsrecords": "DNS records", "diagnosis_description_services": "Services status check", diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index 6cf207282..b9fe111ed 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -74,7 +74,7 @@ def diagnosis_show(categories=[], issues=False, full=False, share=False): if "data" in item: del item["data"] if issues: - report["items"] = [item for item in report["items"] if item["status"] != "SUCCESS"] + report["items"] = [item for item in report["items"] if item["status"] in ["WARNING", "ERROR"]] # Ignore this category if no issue was found if not report["items"]: continue @@ -140,7 +140,7 @@ def diagnosis_run(categories=[], force=False): else: diagnosed_categories.append(category) if report != {}: - issues.extend([item for item in report["items"] if item["status"] != "SUCCESS"]) + issues.extend([item for item in report["items"] if item["status"] in ["WARNING", "ERROR"]]) if issues: if msettings.get("interface") == "api": From 47c7c72455bbcfaded9a5da0d753ce961511c628 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 1 Aug 2019 21:53:31 +0200 Subject: [PATCH 0304/3170] Add RAM and swap diagnosis + improve message for disk usage --- data/hooks/diagnosis/50-diskusage.py | 39 ---------- data/hooks/diagnosis/50-systemresources.py | 87 ++++++++++++++++++++++ locales/en.json | 14 +++- 3 files changed, 97 insertions(+), 43 deletions(-) delete mode 100644 data/hooks/diagnosis/50-diskusage.py create mode 100644 data/hooks/diagnosis/50-systemresources.py diff --git a/data/hooks/diagnosis/50-diskusage.py b/data/hooks/diagnosis/50-diskusage.py deleted file mode 100644 index 74b8eb4b9..000000000 --- a/data/hooks/diagnosis/50-diskusage.py +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env python -import os -import psutil - -from yunohost.diagnosis import Diagnoser - -class DiskUsageDiagnoser(Diagnoser): - - id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] - cache_duration = 3600 * 24 - dependencies = [] - - def run(self): - - disk_partitions = psutil.disk_partitions() - - for disk_partition in disk_partitions: - device = disk_partition.device - mountpoint = disk_partition.mountpoint - - usage = psutil.disk_usage(mountpoint) - free_Go = usage.free / (1024 ** 3) - free_percent = 100 - usage.percent - - item = dict(meta={"mountpoint": mountpoint, "device": device}) - if free_Go < 1 or free_percent < 5: - item["status"] = "ERROR" - item["summary"] = ("diagnosis_diskusage_verylow", {"mountpoint": mountpoint, "device": device, "free_percent": free_percent}) - elif free_Go < 2 or free_percent < 10: - item["status"] = "WARNING" - item["summary"] = ("diagnosis_diskusage_low", {"mountpoint": mountpoint, "device": device, "free_percent": free_percent}) - else: - item["status"] = "SUCCESS" - item["summary"] = ("diagnosis_diskusage_ok", {"mountpoint": mountpoint, "device": device, "free_percent": free_percent}) - - yield item - -def main(args, env, loggers): - return DiskUsageDiagnoser(args, env, loggers).diagnose() diff --git a/data/hooks/diagnosis/50-systemresources.py b/data/hooks/diagnosis/50-systemresources.py new file mode 100644 index 000000000..3399c4682 --- /dev/null +++ b/data/hooks/diagnosis/50-systemresources.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python +import os +import psutil + +from yunohost.diagnosis import Diagnoser + +class SystemResourcesDiagnoser(Diagnoser): + + id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] + cache_duration = 3600 * 24 + dependencies = [] + + def run(self): + + # + # RAM + # + + ram = psutil.virtual_memory() + ram_total_abs_MB = ram.total / (1024**2) + ram_available_abs_MB = ram.available / (1024**2) + ram_available_percent = round(100 * ram.available / ram.total) + item = dict(meta={"test": "ram"}) + infos = {"total_abs_MB": ram_total_abs_MB, "available_abs_MB": ram_available_abs_MB, "available_percent": ram_available_percent} + if ram_available_abs_MB < 100 or ram_available_percent < 5: + item["status"] = "ERROR" + item["summary"] = ("diagnosis_ram_verylow", infos) + elif ram_available_abs_MB < 200 or ram_available_percent < 10: + item["status"] = "WARNING" + item["summary"] = ("diagnosis_ram_low", infos) + else: + item["status"] = "SUCCESS" + item["summary"] = ("diagnosis_ram_ok", infos) + print(item) + yield item + + # + # Swap + # + + swap = psutil.swap_memory() + swap_total_abs_MB = swap.total / (1024*1024) + item = dict(meta={"test": "swap"}) + infos = {"total_MB": swap_total_abs_MB} + if swap_total_abs_MB <= 0: + item["status"] = "ERROR" + item["summary"] = ("diagnosis_swap_none", infos) + elif swap_total_abs_MB <= 256: + item["status"] = "WARNING" + item["summary"] = ("diagnosis_swap_notsomuch", infos) + else: + item["status"] = "SUCCESS" + item["summary"] = ("diagnosis_swap_ok", infos) + print(item) + yield item + + # + # Disks usage + # + + disk_partitions = psutil.disk_partitions() + + for disk_partition in disk_partitions: + device = disk_partition.device + mountpoint = disk_partition.mountpoint + + usage = psutil.disk_usage(mountpoint) + free_abs_GB = usage.free / (1024 ** 3) + free_percent = 100 - usage.percent + + item = dict(meta={"mountpoint": mountpoint, "device": device}) + infos = {"mountpoint": mountpoint, "device": device, "free_abs_GB": free_abs_GB, "free_percent": free_percent} + if free_abs_GB < 1 or free_percent < 5: + item["status"] = "ERROR" + item["summary"] = ("diagnosis_diskusage_verylow", infos) + elif free_abs_GB < 2 or free_percent < 10: + item["status"] = "WARNING" + item["summary"] = ("diagnosis_diskusage_low", infos) + else: + item["status"] = "SUCCESS" + item["summary"] = ("diagnosis_diskusage_ok", infos) + + yield item + + +def main(args, env, loggers): + return SystemResourcesDiagnoser(args, env, loggers).diagnose() diff --git a/locales/en.json b/locales/en.json index f942d3dc4..40edb1425 100644 --- a/locales/en.json +++ b/locales/en.json @@ -185,9 +185,15 @@ "diagnosis_dns_discrepancy": "According to the recommended DNS configuration, the value for the DNS record with type {0} and name {1} should be {2}, not {3}.", "diagnosis_services_good_status": "Service {service} is {status} as expected!", "diagnosis_services_bad_status": "Service {service} is {status} :/", - "diagnosis_diskusage_verylow": "Storage {mountpoint} (on device {device}) has only {free_percent}% space remaining. You should really consider cleaning up some space.", - "diagnosis_diskusage_low": "Storage {mountpoint} (on device {device}) has only {free_percent}% space remaining. Be careful", - "diagnosis_diskusage_ok": "Storage {mountpoint} (on device {device}) still has {free_percent}% space left!", + "diagnosis_diskusage_verylow": "Storage {mountpoint} (on device {device}) has only {free_abs_GB} GB ({free_percent}%) space remaining. You should really consider cleaning up some space.", + "diagnosis_diskusage_low": "Storage {mountpoint} (on device {device}) has only {free_abs_GB} GB ({free_percent}%) space remaining. Be careful.", + "diagnosis_diskusage_ok": "Storage {mountpoint} (on device {device}) still has {free_abs_GB} GB ({free_percent}%) space left!", + "diagnosis_ram_verylow": "The system has only {available_abs_MB} MB ({available_percent}%) RAM left! (out of {total_abs_MB} MB)", + "diagnosis_ram_low": "The system has {available_abs_MB} MB ({available_percent}%) RAM left out of {total_abs_MB} MB. Be careful.", + "diagnosis_ram_ok": "The system still has {available_abs_MB} MB ({available_percent}%) RAM left out of {total_abs_MB} MB.", + "diagnosis_swap_none": "The system has no swap at all. You should consider adding at least 256 MB of swap to avoid situations where the system runs out of memory.", + "diagnosis_swap_notsomuch": "The system has only {total_MB} MB swap. You should consider having at least 256 MB to avoid situations where the system runs out of memory.", + "diagnosis_swap_ok": "The system has {total_MB} MB of swap!", "diagnosis_regenconf_allgood": "All configurations files are in line with the recommended configuration!", "diagnosis_regenconf_manually_modified": "Configuration file {file} was manually modified.", "diagnosis_regenconf_manually_modified_details": "This is probably OK as long as you know what you're doing ;) !", @@ -201,7 +207,7 @@ "diagnosis_description_ip": "Internet connectivity", "diagnosis_description_dnsrecords": "DNS records", "diagnosis_description_services": "Services status check", - "diagnosis_description_diskusage": "Disk usage", + "diagnosis_description_systemresources": "System resources", "diagnosis_description_ports": "Ports exposure", "diagnosis_description_http": "HTTP exposure", "diagnosis_description_regenconf": "System configurations", From 94f3557aeb3c6d2c95728692d59f027a2f7fe793 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 1 Aug 2019 22:02:08 +0200 Subject: [PATCH 0305/3170] Remove old 'tools diagnosis', superseded by the new diagnosis system --- data/actionsmap/yunohost.yml | 10 -- src/yunohost/tools.py | 188 +---------------------------------- 2 files changed, 3 insertions(+), 195 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 20eb8a0f8..1c96ce3e8 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1606,16 +1606,6 @@ tools: help: Upgrade only the system packages action: store_true - ### tools_diagnosis() - diagnosis: - action_help: YunoHost diagnosis - api: GET /diagnosis - arguments: - -p: - full: --private - help: Show private data (domain, IP) - action: store_true - ### tools_port_available() port-available: action_help: Check availability of a local port diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 64689fe0c..034157e3a 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -30,23 +30,19 @@ import json import subprocess import pwd import socket -from xmlrpclib import Fault from importlib import import_module -from collections import OrderedDict from moulinette import msignals, m18n from moulinette.utils.log import getActionLogger from moulinette.utils.process import check_output, call_async_output from moulinette.utils.filesystem import read_json, write_to_json, read_yaml, write_to_yaml -from yunohost.app import app_fetchlist, app_info, app_upgrade, app_ssowatconf, app_list, _install_appslist_fetch_cron +from yunohost.app import app_fetchlist, app_info, app_upgrade, app_ssowatconf, _install_appslist_fetch_cron from yunohost.domain import domain_add, domain_list, _get_maindomain, _set_maindomain from yunohost.dyndns import _dyndns_available, _dyndns_provides from yunohost.firewall import firewall_upnp -from yunohost.service import service_status, service_start, service_enable +from yunohost.service import service_start, service_enable from yunohost.regenconf import regen_conf -from yunohost.monitor import monitor_disk, monitor_system -from yunohost.utils.packages import ynh_packages_version, _dump_sources_list, _list_upgradable_apt_packages -from yunohost.utils.network import get_public_ip +from yunohost.utils.packages import _dump_sources_list, _list_upgradable_apt_packages from yunohost.utils.error import YunohostError from yunohost.log import is_unit_operation, OperationLogger @@ -726,184 +722,6 @@ def tools_upgrade(operation_logger, apps=None, system=False): operation_logger.success() -def tools_diagnosis(private=False): - """ - Return global info about current yunohost instance to help debugging - - """ - diagnosis = OrderedDict() - - # Debian release - try: - with open('/etc/debian_version', 'r') as f: - debian_version = f.read().rstrip() - except IOError as e: - logger.warning(m18n.n('diagnosis_debian_version_error', error=format(e)), exc_info=1) - else: - diagnosis['host'] = "Debian %s" % debian_version - - # Kernel version - try: - with open('/proc/sys/kernel/osrelease', 'r') as f: - kernel_version = f.read().rstrip() - except IOError as e: - logger.warning(m18n.n('diagnosis_kernel_version_error', error=format(e)), exc_info=1) - else: - diagnosis['kernel'] = kernel_version - - # Packages version - diagnosis['packages'] = ynh_packages_version() - - diagnosis["backports"] = check_output("dpkg -l |awk '/^ii/ && $3 ~ /bpo[6-8]/ {print $2}'").split() - - # Server basic monitoring - diagnosis['system'] = OrderedDict() - try: - disks = monitor_disk(units=['filesystem'], human_readable=True) - except (YunohostError, Fault) as e: - logger.warning(m18n.n('diagnosis_monitor_disk_error', error=format(e)), exc_info=1) - else: - diagnosis['system']['disks'] = {} - for disk in disks: - if isinstance(disks[disk], str): - diagnosis['system']['disks'][disk] = disks[disk] - else: - diagnosis['system']['disks'][disk] = 'Mounted on %s, %s (%s free)' % ( - disks[disk]['mnt_point'], - disks[disk]['size'], - disks[disk]['avail'] - ) - - try: - system = monitor_system(units=['cpu', 'memory'], human_readable=True) - except YunohostError as e: - logger.warning(m18n.n('diagnosis_monitor_system_error', error=format(e)), exc_info=1) - else: - diagnosis['system']['memory'] = { - 'ram': '%s (%s free)' % (system['memory']['ram']['total'], system['memory']['ram']['free']), - 'swap': '%s (%s free)' % (system['memory']['swap']['total'], system['memory']['swap']['free']), - } - - # nginx -t - p = subprocess.Popen("nginx -t".split(), - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT) - out, _ = p.communicate() - diagnosis["nginx"] = out.strip().split("\n") - if p.returncode != 0: - logger.error(out) - - # Services status - services = service_status() - diagnosis['services'] = {} - - for service in services: - diagnosis['services'][service] = "%s (%s)" % (services[service]['status'], services[service]['loaded']) - - # YNH Applications - try: - applications = app_list()['apps'] - except YunohostError as e: - diagnosis['applications'] = m18n.n('diagnosis_no_apps') - else: - diagnosis['applications'] = {} - for application in applications: - if application['installed']: - diagnosis['applications'][application['id']] = application['label'] if application['label'] else application['name'] - - # Private data - if private: - diagnosis['private'] = OrderedDict() - - # Public IP - diagnosis['private']['public_ip'] = {} - diagnosis['private']['public_ip']['IPv4'] = get_public_ip(4) - diagnosis['private']['public_ip']['IPv6'] = get_public_ip(6) - - # Domains - diagnosis['private']['domains'] = domain_list()['domains'] - - diagnosis['private']['regen_conf'] = regen_conf(with_diff=True, dry_run=True) - - try: - diagnosis['security'] = { - "CVE-2017-5754": { - "name": "meltdown", - "vulnerable": _check_if_vulnerable_to_meltdown(), - } - } - except Exception as e: - import traceback - traceback.print_exc() - logger.warning("Unable to check for meltdown vulnerability: %s" % e) - - return diagnosis - - -def _check_if_vulnerable_to_meltdown(): - # meltdown CVE: https://security-tracker.debian.org/tracker/CVE-2017-5754 - - # We use a cache file to avoid re-running the script so many times, - # which can be expensive (up to around 5 seconds on ARM) - # and make the admin appear to be slow (c.f. the calls to diagnosis - # from the webadmin) - # - # The cache is in /tmp and shall disappear upon reboot - # *or* we compare it to dpkg.log modification time - # such that it's re-ran if there was package upgrades - # (e.g. from yunohost) - cache_file = "/tmp/yunohost-meltdown-diagnosis" - dpkg_log = "/var/log/dpkg.log" - if os.path.exists(cache_file): - if not os.path.exists(dpkg_log) or os.path.getmtime(cache_file) > os.path.getmtime(dpkg_log): - logger.debug("Using cached results for meltdown checker, from %s" % cache_file) - return read_json(cache_file)[0]["VULNERABLE"] - - # script taken from https://github.com/speed47/spectre-meltdown-checker - # script commit id is store directly in the script - file_dir = os.path.split(__file__)[0] - SCRIPT_PATH = os.path.join(file_dir, "./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: - 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: - 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() - logger.warning("Something wrong happened when trying to diagnose Meltdown vunerability, exception: %s" % e) - raise Exception("Command output for failed meltdown check: '%s'" % output) - - logger.debug("Writing results from meltdown checker to cache file, %s" % cache_file) - write_to_json(cache_file, CVEs) - return CVEs[0]["VULNERABLE"] - - def tools_port_available(port): """ Check availability of a local port From d113b6a53f126c289d4148f5a210c1bbf21ae118 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 5 Aug 2019 23:53:32 +0200 Subject: [PATCH 0306/3170] Adding some notes about diagnosis items to be implemented --- data/hooks/diagnosis/12-dnsrecords.py | 2 ++ data/hooks/diagnosis/18-mail.py | 28 +++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 data/hooks/diagnosis/18-mail.py diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py index b59ffbd54..8c6565da9 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -31,6 +31,8 @@ class DNSRecordsDiagnoser(Diagnoser): for report in self.check_domain(domain, domain == main_domain): yield report + # FIXME : somewhere, should implement a check for reverse DNS ... + def check_domain(self, domain, is_main_domain): expected_configuration = _build_dns_conf(domain) diff --git a/data/hooks/diagnosis/18-mail.py b/data/hooks/diagnosis/18-mail.py new file mode 100644 index 000000000..5cf897e72 --- /dev/null +++ b/data/hooks/diagnosis/18-mail.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python + +import os + +from yunohost.diagnosis import Diagnoser + + +class MailDiagnoser(Diagnoser): + + id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] + cache_duration = 3600 + dependencies = ["ip"] + + def run(self): + + return # TODO / FIXME TO BE IMPLEMETED in the future ... + + # Mail blacklist using dig requests (c.f. ljf's code) + + # Outgoing port 25 (c.f. code in monitor.py, a simple 'nc -zv yunohost.org 25' IIRC) + + # SMTP reachability (c.f. check-smtp to be implemented on yunohost's remote diagnoser) + + # ideally, SPF / DMARC / DKIM validation ... (c.f. https://github.com/alexAubin/yunoScripts/blob/master/yunoDKIM.py possibly though that looks horrible) + + +def main(args, env, loggers): + return MailDiagnoser(args, env, loggers).diagnose() From 339b6d9cbe2c97ffe249b99fb6b4c7f8c06437d7 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 11 Aug 2019 16:23:47 +0200 Subject: [PATCH 0307/3170] Moar notes about what could be implemented for mail diagnoser --- data/hooks/diagnosis/18-mail.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/data/hooks/diagnosis/18-mail.py b/data/hooks/diagnosis/18-mail.py index 5cf897e72..100ace22f 100644 --- a/data/hooks/diagnosis/18-mail.py +++ b/data/hooks/diagnosis/18-mail.py @@ -23,6 +23,12 @@ class MailDiagnoser(Diagnoser): # ideally, SPF / DMARC / DKIM validation ... (c.f. https://github.com/alexAubin/yunoScripts/blob/master/yunoDKIM.py possibly though that looks horrible) + # check that the mail queue is not filled with hundreds of email pending + + # check that the recent mail logs are not filled with thousand of email sending (unusual number of mail sent) + + # check for unusual failed sending attempt being refused in the logs ? + def main(args, env, loggers): return MailDiagnoser(args, env, loggers).diagnose() From e0fa87cb364cd6d76bf4f80fd5da2c3ae51c9ca5 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 18 Aug 2019 04:53:32 +0200 Subject: [PATCH 0308/3170] Note for the future about trying to diagnose hairpinning --- data/hooks/diagnosis/16-http.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/data/hooks/diagnosis/16-http.py b/data/hooks/diagnosis/16-http.py index cc335df8b..7ca258628 100644 --- a/data/hooks/diagnosis/16-http.py +++ b/data/hooks/diagnosis/16-http.py @@ -48,6 +48,10 @@ class HttpDiagnoser(Diagnoser): status="ERROR", summary=("diagnosis_http_unreachable", {"domain": domain})) + # In there or idk where else ... + # try to diagnose hairpinning situation by crafting a request for the + # global ip (from within local network) and seeing if we're getting the right page ? + def main(args, env, loggers): return HttpDiagnoser(args, env, loggers).diagnose() From 356f2b9ec1362b33b03ca8254d9d25c2bfcc22f1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 22 Aug 2019 12:42:25 +0200 Subject: [PATCH 0309/3170] Moar ideas --- data/hooks/diagnosis/00-basesystem.py | 2 ++ data/hooks/diagnosis/10-ip.py | 2 ++ data/hooks/diagnosis/12-dnsrecords.py | 2 ++ 3 files changed, 6 insertions(+) diff --git a/data/hooks/diagnosis/00-basesystem.py b/data/hooks/diagnosis/00-basesystem.py index 8fa90e65e..8bd522ee7 100644 --- a/data/hooks/diagnosis/00-basesystem.py +++ b/data/hooks/diagnosis/00-basesystem.py @@ -21,6 +21,8 @@ class BaseSystemDiagnoser(Diagnoser): status="INFO", summary=("diagnosis_basesystem_kernel", {"kernel_version": kernel_version})) + # FIXME / TODO : add virt/vm technology using systemd-detect-virt and/or machine arch + # Debian release debian_version = read_file("/etc/debian_version").strip() yield dict(meta={"test": "host"}, diff --git a/data/hooks/diagnosis/10-ip.py b/data/hooks/diagnosis/10-ip.py index 8c8dbe95b..e09dd343b 100644 --- a/data/hooks/diagnosis/10-ip.py +++ b/data/hooks/diagnosis/10-ip.py @@ -84,6 +84,8 @@ class IPDiagnoser(Diagnoser): summary=("diagnosis_ip_connected_ipv6", {}) if ipv6 else ("diagnosis_ip_no_ipv6", {})) + # TODO / FIXME : add some attempt to detect ISP (using whois ?) ? + def can_ping_outside(self, protocol=4): assert protocol in [4, 6], "Invalid protocol version, it should be either 4 or 6 and was '%s'" % repr(protocol) diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py index 8c6565da9..e2f7bcc2d 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -33,6 +33,8 @@ class DNSRecordsDiagnoser(Diagnoser): # FIXME : somewhere, should implement a check for reverse DNS ... + # FIXME / TODO : somewhere, could also implement a check for domain expiring soon + def check_domain(self, domain, is_main_domain): expected_configuration = _build_dns_conf(domain) From 3d7f37176cd8839e0455d414e1eb1aeb31274f2e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 10 Oct 2019 16:23:38 +0200 Subject: [PATCH 0310/3170] Remove debug prints --- data/hooks/diagnosis/50-systemresources.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/data/hooks/diagnosis/50-systemresources.py b/data/hooks/diagnosis/50-systemresources.py index 3399c4682..7e93a9ec0 100644 --- a/data/hooks/diagnosis/50-systemresources.py +++ b/data/hooks/diagnosis/50-systemresources.py @@ -31,7 +31,6 @@ class SystemResourcesDiagnoser(Diagnoser): else: item["status"] = "SUCCESS" item["summary"] = ("diagnosis_ram_ok", infos) - print(item) yield item # @@ -51,7 +50,6 @@ class SystemResourcesDiagnoser(Diagnoser): else: item["status"] = "SUCCESS" item["summary"] = ("diagnosis_swap_ok", infos) - print(item) yield item # From 02d6a0212f508fcd1df78bf2656447f9bd544f6d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 10 Oct 2019 16:40:06 +0200 Subject: [PATCH 0311/3170] Remove debug prints --- data/hooks/diagnosis/10-ip.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/data/hooks/diagnosis/10-ip.py b/data/hooks/diagnosis/10-ip.py index e09dd343b..9c4257306 100644 --- a/data/hooks/diagnosis/10-ip.py +++ b/data/hooks/diagnosis/10-ip.py @@ -113,8 +113,10 @@ class IPDiagnoser(Diagnoser): # So let's try to ping the first 4~5 resolvers (shuffled) # If we succesfully ping any of them, we conclude that we are indeed connected def ping(protocol, target): + print("ping -c1 -%s -W 3 %s >/dev/null 2>/dev/null" % (protocol, target)) return os.system("ping -c1 -%s -W 3 %s >/dev/null 2>/dev/null" % (protocol, target)) == 0 + random.shuffle(resolvers) return any(ping(protocol, resolver) for resolver in resolvers[:5]) From e67e9e27ba281913cd0e6b518a252e7c4c536feb Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 10 Oct 2019 16:48:58 +0200 Subject: [PATCH 0312/3170] Hmm somehow there seem to be different version of ping supporting or not the -4 / -6 ... let's see if this workaroud works in all contexts --- data/hooks/diagnosis/10-ip.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/data/hooks/diagnosis/10-ip.py b/data/hooks/diagnosis/10-ip.py index 9c4257306..552092fe3 100644 --- a/data/hooks/diagnosis/10-ip.py +++ b/data/hooks/diagnosis/10-ip.py @@ -113,9 +113,7 @@ class IPDiagnoser(Diagnoser): # So let's try to ping the first 4~5 resolvers (shuffled) # If we succesfully ping any of them, we conclude that we are indeed connected def ping(protocol, target): - print("ping -c1 -%s -W 3 %s >/dev/null 2>/dev/null" % (protocol, target)) - return os.system("ping -c1 -%s -W 3 %s >/dev/null 2>/dev/null" % (protocol, target)) == 0 - + return os.system("ping%s -c1 -W 3 %s >/dev/null 2>/dev/null" % ("" if protocol == 4 else "6", target)) == 0 random.shuffle(resolvers) return any(ping(protocol, resolver) for resolver in resolvers[:5]) From d6eb55d2a2107e217935256667d4aef52bd64593 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 11 Oct 2019 20:04:53 +0200 Subject: [PATCH 0313/3170] Add tmp dummy mail report so that the diagnoser kinda works instead of failing miserably --- data/hooks/diagnosis/18-mail.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/data/hooks/diagnosis/18-mail.py b/data/hooks/diagnosis/18-mail.py index 100ace22f..c12c15cff 100644 --- a/data/hooks/diagnosis/18-mail.py +++ b/data/hooks/diagnosis/18-mail.py @@ -13,7 +13,11 @@ class MailDiagnoser(Diagnoser): def run(self): - return # TODO / FIXME TO BE IMPLEMETED in the future ... + # TODO / FIXME TO BE IMPLEMETED in the future ... + + yield dict(meta={}, + status="WARNING", + summary=("nothing_implemented_yet", {})) # Mail blacklist using dig requests (c.f. ljf's code) From f75cd82593cc0feaab0f86f0d57b2f53c895ab5e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 11 Oct 2019 20:05:46 +0200 Subject: [PATCH 0314/3170] First part of implementing the ignore mechanism --- data/actionsmap/yunohost.yml | 19 ++--- src/yunohost/diagnosis.py | 132 +++++++++++++++++++++++++++++++++-- 2 files changed, 139 insertions(+), 12 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 1c96ce3e8..9b694c853 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1893,14 +1893,17 @@ diagnosis: action: store_true ignore: - action_help: Configure some diagnosis results to be ignored + action_help: Configure some diagnosis results to be ignored and therefore not considered as actual issues api: PUT /diagnosis/ignore arguments: - category: - help: Diagnosis category to be affected - -a: - help: Arguments, to be used to ignore only some parts of a report (e.g. "domain=domain.tld") - full: --args - --unignore: - help: Unignore a previously ignored report + --add-filter: + help: "Add a filter. The first element should be a diagnosis category, and other criterias can be provided using the infos from the 'meta' sections in 'yunohost diagnosis show'. For example: 'dnsrecords domain=yolo.test category=xmpp'" + nargs: "*" + metavar: CRITERIA + --remove-filter: + help: Remove a filter (it should be an existing filter as listed with --list) + nargs: "*" + metavar: CRITERIA + --list: + help: List active ignore filters action: store_true diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index b9fe111ed..da69e5d5e 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -29,7 +29,7 @@ import time from moulinette import m18n, msettings from moulinette.utils import log -from moulinette.utils.filesystem import read_json, write_to_json +from moulinette.utils.filesystem import read_json, write_to_json, read_yaml, write_to_yaml from yunohost.utils.error import YunohostError from yunohost.hook import hook_list, hook_exec @@ -37,7 +37,7 @@ from yunohost.hook import hook_list, hook_exec logger = log.getActionLogger('yunohost.diagnosis') DIAGNOSIS_CACHE = "/var/cache/yunohost/diagnosis/" - +DIAGNOSIS_CONFIG_FILE = '/etc/yunohost/diagnosis.yml' def diagnosis_list(): all_categories_names = [h for h, _ in _list_diagnosis_categories()] @@ -151,8 +151,132 @@ def diagnosis_run(categories=[], force=False): return -def diagnosis_ignore(category, args="", unignore=False): - pass +def diagnosis_ignore(add_filter=None, remove_filter=None, list=False): + """ + This action is meant for the admin to ignore issues reported by the + diagnosis system if they are known and understood by the admin. For + example, the lack of ipv6 on an instance, or badly configured XMPP dns + records if the admin doesn't care so much about XMPP. The point being that + the diagnosis shouldn't keep complaining about those known and "expected" + issues, and instead focus on new unexpected issues that could arise. + + For example, to ignore badly XMPP dnsrecords for domain yolo.test: + + yunohost diagnosis ignore --add-filter dnsrecords domain=yolo.test category=xmpp + ^ ^ ^ + the general additional other + diagnosis criterias criteria + category to to target to target + act on specific specific + reports reports + Or to ignore all dnsrecords issues: + + yunohost diagnosis ignore --add-filter dnsrecords + + The filters are stored in the diagnosis configuration in a data structure like: + + ignore_filters: { + "ip": [ + {"version": 6} # Ignore all issues related to ipv6 + ], + "dnsrecords": [ + {"domain": "yolo.test", "category": "xmpp"}, # Ignore all issues related to DNS xmpp records for yolo.test + {} # Ignore all issues about dnsrecords + ] + } + """ + + # Ignore filters are stored in + configuration = _diagnosis_read_configuration() + + if list: + return {"ignore_filters": configuration.get("ignore_filters", {})} + + def validate_filter_criterias(filter_): + + # Get all the categories + all_categories = _list_diagnosis_categories() + all_categories_names = [category for category, _ in all_categories] + + # Sanity checks for the provided arguments + if len(filter_) == 0: + raise YunohostError("You should provide at least one criteria being the diagnosis category to ignore") + category = filter_[0] + if category not in all_categories_names: + raise YunohostError("%s is not a diagnosis category" % category) + if any("=" not in criteria for criteria in filter_[1:]): + raise YunohostError("Extra criterias should be of the form key=value (e.g. domain=yolo.test)") + + # Convert the provided criteria into a nice dict + criterias = {c.split("=")[0]: c.split("=")[1] for c in filter_[1:]} + + return category, criterias + + if add_filter: + + category, criterias = validate_filter_criterias(add_filter) + + # Fetch current issues for the requested category + current_issues_for_this_category = diagnosis_show(categories=[category], issues=True, full=True) + current_issues_for_this_category = current_issues_for_this_category["reports"][0].get("items", {}) + + # Accept the given filter only if the criteria effectively match an existing issue + if not any(issue_matches_criterias(i, criterias) for i in current_issues_for_this_category): + raise YunohostError("No issues was found matching the given criteria.") + + # Make sure the subdicts/lists exists + if "ignore_filters" not in configuration: + configuration["ignore_filters"] = {} + if category not in configuration["ignore_filters"]: + configuration["ignore_filters"][category] = [] + + if criterias in configuration["ignore_filters"][category]: + logger.warning("This filter already exists.") + return + + configuration["ignore_filters"][category].append(criterias) + _diagnosis_write_configuration(configuration) + logger.success("Filter added") + return + + if remove_filter: + + category, criterias = validate_filter_criterias(remove_filter) + + # Make sure the subdicts/lists exists + if "ignore_filters" not in configuration: + configuration["ignore_filters"] = {} + if category not in configuration["ignore_filters"]: + configuration["ignore_filters"][category] = [] + + if criterias not in configuration["ignore_filters"][category]: + raise YunohostError("This filter does not exists.") + + configuration["ignore_filters"][category].remove(criterias) + _diagnosis_write_configuration(configuration) + logger.success("Filter removed") + return + + +def _diagnosis_read_configuration(): + if not os.path.exists(DIAGNOSIS_CONFIG_FILE): + return {} + + return read_yaml(DIAGNOSIS_CONFIG_FILE) + + +def _diagnosis_write_configuration(conf): + write_to_yaml(DIAGNOSIS_CONFIG_FILE, conf) + + +def issue_matches_criterias(issues, criterias): + for key, value in criterias.items(): + if key not in issues["meta"]: + return False + if str(issues["meta"][key]) != value: + return False + return True + ############################################################ From 97f9d3ea3753db40622b20df967e1644ac678c04 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 11 Oct 2019 22:42:21 +0200 Subject: [PATCH 0315/3170] Integrate the ignore mechanism with the rest of the code --- locales/en.json | 4 ++- src/yunohost/diagnosis.py | 55 ++++++++++++++++++++++++++++++++------- 2 files changed, 49 insertions(+), 10 deletions(-) diff --git a/locales/en.json b/locales/en.json index 40edb1425..afcb44edb 100644 --- a/locales/en.json +++ b/locales/en.json @@ -165,7 +165,9 @@ "diagnosis_failed_for_category": "Diagnosis failed for category '{category}' : {error}", "diagnosis_cache_still_valid": "(Cache still valid for {category} diagnosis. Not re-diagnosing yet!)", "diagnosis_cant_run_because_of_dep": "Can't run diagnosis for {category} while there are important issues related to {dep}.", - "diagnosis_found_issues": "Found {errors} significant issue(s) related to {category}!", + "diagnosis_ignored_issues": "(+ {nb_ignored} ignored issue(s))", + "diagnosis_found_errors": "Found {errors} significant issue(s) related to {category}!", + "diagnosis_found_errors_and_warnings": "Found {errors} significant issue(s) (and {warnings} warning(s)) related to {category}!", "diagnosis_found_warnings": "Found {warnings} item(s) that could be improved for {category}.", "diagnosis_everything_ok": "Everything looks good for {category}!", "diagnosis_failed": "Failed to fetch diagnosis result for category '{category}' : {error}", diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index da69e5d5e..19dd03042 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -66,11 +66,14 @@ def diagnosis_show(categories=[], issues=False, full=False, share=False): except Exception as e: logger.error(m18n.n("diagnosis_failed", category=category, error=str(e))) else: + add_ignore_flag_to_issues(report) if not full: del report["timestamp"] del report["cached_for"] + report["items"] = [item for item in report["items"] if not item["ignored"]] for item in report["items"]: del item["meta"] + del item["ignored"] if "data" in item: del item["data"] if issues: @@ -269,14 +272,42 @@ def _diagnosis_write_configuration(conf): write_to_yaml(DIAGNOSIS_CONFIG_FILE, conf) -def issue_matches_criterias(issues, criterias): +def issue_matches_criterias(issue, criterias): + """ + e.g. an issue with: + meta: + domain: yolo.test + category: xmpp + + matches the criterias {"domain": "yolo.test"} + """ for key, value in criterias.items(): - if key not in issues["meta"]: + if key not in issue["meta"]: return False - if str(issues["meta"][key]) != value: + if str(issue["meta"][key]) != value: 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 + ignored filter from the configuration + + N.B. : for convenience. we want to make sure the "ignored" key is set for + every item in the report + """ + + ignore_filters = _diagnosis_read_configuration().get("ignore_filters", {}).get(report["id"], []) + + for report_item in report["items"]: + report_item["ignored"] = False + if report_item["status"] not in ["WARNING", "ERROR"]: + continue + for criterias in ignore_filters: + if issue_matches_criterias(report_item, criterias): + report_item["ignored"] = True + break + ############################################################ @@ -328,16 +359,22 @@ class Diagnoser(): self.logger_debug("Updating cache %s" % self.cache_file) self.write_cache(new_report) Diagnoser.i18n(new_report) + add_ignore_flag_to_issues(new_report) - errors = [item for item in new_report["items"] if item["status"] == "ERROR"] - warnings = [item for item in new_report["items"] if item["status"] == "WARNING"] + errors = [item for item in new_report["items"] if item["status"] == "ERROR" and not item["ignored"]] + warnings = [item for item in new_report["items"] if item["status"] == "WARNING" and not item["ignored"]] + errors_ignored = [item for item in new_report["items"] if item["status"] == "ERROR" and item["ignored"]] + warning_ignored = [item for item in new_report["items"] if item["status"] == "WARNING" and item["ignored"]] + ignored_msg = " " + m18n.n("diagnosis_ignored_issues", nb_ignored=len(errors_ignored+warning_ignored)) if errors_ignored or warning_ignored else "" - if errors: - logger.error(m18n.n("diagnosis_found_issues", errors=len(errors), category=new_report["description"])) + if errors and warnings: + logger.error(m18n.n("diagnosis_found_errors_and_warnings", errors=len(errors), warnings=len(warnings), category=new_report["description"]) + ignored_msg) + elif errors: + logger.error(m18n.n("diagnosis_found_errors", errors=len(errors), category=new_report["description"]) + ignored_msg) elif warnings: - logger.warning(m18n.n("diagnosis_found_warnings", warnings=len(warnings), category=new_report["description"])) + logger.warning(m18n.n("diagnosis_found_warnings", warnings=len(warnings), category=new_report["description"]) + ignored_msg) else: - logger.success(m18n.n("diagnosis_everything_ok", category=new_report["description"])) + logger.success(m18n.n("diagnosis_everything_ok", category=new_report["description"]) + ignored_msg) return 0, new_report From 51e7a56522e49edc498677aeb5ec08fa174d713c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 13 Oct 2019 18:42:45 +0200 Subject: [PATCH 0316/3170] Improve metadata for diskusage tests --- 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 7e93a9ec0..95f58ddb7 100644 --- a/data/hooks/diagnosis/50-systemresources.py +++ b/data/hooks/diagnosis/50-systemresources.py @@ -66,7 +66,7 @@ class SystemResourcesDiagnoser(Diagnoser): free_abs_GB = usage.free / (1024 ** 3) free_percent = 100 - usage.percent - item = dict(meta={"mountpoint": mountpoint, "device": device}) + item = dict(meta={"test": "diskusage", "mountpoint": mountpoint}) infos = {"mountpoint": mountpoint, "device": device, "free_abs_GB": free_abs_GB, "free_percent": free_percent} if free_abs_GB < 1 or free_percent < 5: item["status"] = "ERROR" From 0839de2d6a3684056e0d0bf692c4391b1ac153ef Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 13 Oct 2019 23:02:46 +0200 Subject: [PATCH 0317/3170] Switching to POST method because it's more practical than PUT, idk what im doing --- data/actionsmap/yunohost.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 9b694c853..e7a1d1ad2 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1894,7 +1894,7 @@ diagnosis: ignore: action_help: Configure some diagnosis results to be ignored and therefore not considered as actual issues - api: PUT /diagnosis/ignore + api: POST /diagnosis/ignore arguments: --add-filter: help: "Add a filter. The first element should be a diagnosis category, and other criterias can be provided using the infos from the 'meta' sections in 'yunohost diagnosis show'. For example: 'dnsrecords domain=yolo.test category=xmpp'" From 5818de3a824a1c869c12cc4760b4511d53baa83d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 14 Oct 2019 04:48:56 +0200 Subject: [PATCH 0318/3170] Remove the whole monitoring / glances stuff --- data/actionsmap/yunohost.yml | 141 ----- data/templates/glances/glances.default | 5 - data/templates/yunohost/services.yml | 2 +- debian/control | 2 +- locales/en.json | 14 - src/yunohost/monitor.py | 740 ------------------------- 6 files changed, 2 insertions(+), 902 deletions(-) delete mode 100644 data/templates/glances/glances.default delete mode 100644 src/yunohost/monitor.py diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index e7a1d1ad2..4b76fcb0b 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -968,147 +968,6 @@ backup: pattern: *pattern_backup_archive_name -############################# -# Monitor # -############################# -monitor: - category_help: Monitor the server - actions: - - ### monitor_disk() - disk: - action_help: Monitor disk space and usage - api: GET /monitor/disk - arguments: - -f: - full: --filesystem - help: Show filesystem disk space - action: append_const - const: filesystem - dest: units - -t: - full: --io - help: Show I/O throughput - action: append_const - const: io - dest: units - -m: - full: --mountpoint - help: Monitor only the device mounted on MOUNTPOINT - action: store - -H: - full: --human-readable - help: Print sizes in human readable format - action: store_true - - ### monitor_network() - network: - action_help: Monitor network interfaces - api: GET /monitor/network - arguments: - -u: - full: --usage - help: Show interfaces bit rates - action: append_const - const: usage - dest: units - -i: - full: --infos - help: Show network informations - action: append_const - const: infos - dest: units - -c: - full: --check - help: Check network configuration - action: append_const - const: check - dest: units - -H: - full: --human-readable - help: Print sizes in human readable format - action: store_true - - ### monitor_system() - system: - action_help: Monitor system informations and usage - api: GET /monitor/system - arguments: - -m: - full: --memory - help: Show memory usage - action: append_const - const: memory - dest: units - -c: - full: --cpu - help: Show CPU usage and load - action: append_const - const: cpu - dest: units - -p: - full: --process - help: Show processes summary - action: append_const - const: process - dest: units - -u: - full: --uptime - help: Show the system uptime - action: append_const - const: uptime - dest: units - -i: - full: --infos - help: Show system informations - action: append_const - const: infos - dest: units - -H: - full: --human-readable - help: Print sizes in human readable format - action: store_true - - ### monitor_updatestats() - update-stats: - action_help: Update monitoring statistics - api: POST /monitor/stats - arguments: - period: - help: Time period to update - choices: - - day - - week - - month - - ### monitor_showstats() - show-stats: - action_help: Show monitoring statistics - api: GET /monitor/stats - arguments: - period: - help: Time period to show - choices: - - day - - week - - month - - ### monitor_enable() - enable: - action_help: Enable server monitoring - api: PUT /monitor - arguments: - -s: - full: --with-stats - help: Enable monitoring statistics - action: store_true - - ### monitor_disable() - disable: - api: DELETE /monitor - action_help: Disable server monitoring - - ############################# # Settings # ############################# diff --git a/data/templates/glances/glances.default b/data/templates/glances/glances.default deleted file mode 100644 index 22337a0d9..000000000 --- a/data/templates/glances/glances.default +++ /dev/null @@ -1,5 +0,0 @@ -# Default is to launch glances with '-s' option. -DAEMON_ARGS="-s -B 127.0.0.1" - -# Change to 'true' to have glances running at startup -RUN="true" diff --git a/data/templates/yunohost/services.yml b/data/templates/yunohost/services.yml index 0d79b182f..1c0ee031f 100644 --- a/data/templates/yunohost/services.yml +++ b/data/templates/yunohost/services.yml @@ -17,7 +17,6 @@ redis-server: mysql: log: [/var/log/mysql.log,/var/log/mysql.err] alternates: ['mariadb'] -glances: {} ssh: log: /var/log/auth.log metronome: @@ -32,6 +31,7 @@ yunohost-firewall: need_lock: true nslcd: log: /var/log/syslog +glances: null nsswitch: null ssl: null yunohost: null diff --git a/debian/control b/debian/control index c0604d90e..3b8c257d0 100644 --- a/debian/control +++ b/debian/control @@ -15,7 +15,7 @@ Depends: ${python:Depends}, ${misc:Depends} , python-psutil, python-requests, python-dnspython, python-openssl , python-apt, python-miniupnpc, python-dbus, python-jinja2 , python-toml - , glances, apt-transport-https + , apt-transport-https , dnsutils, bind9utils, unzip, git, curl, cron, wget, jq , ca-certificates, netcat-openbsd, iproute , mariadb-server, php-mysql | php-mysqlnd diff --git a/locales/en.json b/locales/en.json index afcb44edb..e04446ce1 100644 --- a/locales/en.json +++ b/locales/en.json @@ -434,21 +434,9 @@ "migrations_skip_migration": "Skipping migration {id}…", "migrations_success_forward": "Migration {id} completed", "migrations_to_be_ran_manually": "Migration {id} has to be run manually. Please go to Tools → Migrations on the webadmin page, or run `yunohost tools migrations migrate`.", - "monitor_disabled": "Server monitoring now turned off", - "monitor_enabled": "Server monitoring now turned on", - "monitor_glances_con_failed": "Could not connect to Glances server", - "monitor_not_enabled": "Server monitoring is off", - "monitor_period_invalid": "Invalid time period", - "monitor_stats_file_not_found": "Statistics file not found", - "monitor_stats_no_update": "No monitoring statistics to update", - "monitor_stats_period_unavailable": "No available statistics for the period", - "mountpoint_unknown": "Unknown mountpoint", "mysql_db_creation_failed": "MySQL database creation failed", "mysql_db_init_failed": "MySQL database init failed", "mysql_db_initialized": "The MySQL database now initialized", - "network_check_mx_ko": "DNS MX record is not set", - "network_check_smtp_ko": "Outbound e-mail (SMTP port 25) seems to be blocked by your network", - "network_check_smtp_ok": "Outbound e-mail (SMTP port 25) is not blocked", "no_internet_connection": "Server not connected to the Internet", "not_enough_disk_space": "Not enough free space on '{path:s}'", "operation_interrupted": "The operation was manually interrupted?", @@ -536,7 +524,6 @@ "service_description_dnsmasq": "Handles domain name resolution (DNS)", "service_description_dovecot": "Allows e-mail clients to access/fetch email (via IMAP and POP3)", "service_description_fail2ban": "Protects against brute-force and other kinds of attacks from the Internet", - "service_description_glances": "Monitors system info on your server", "service_description_metronome": "Manage XMPP instant messaging accounts", "service_description_mysql": "Stores applications data (SQL database)", "service_description_nginx": "Serves or provides access to all the websites hosted on your server", @@ -588,7 +575,6 @@ "tools_upgrade_special_packages_completed": "YunoHost package upgrade completed.\nPress [Enter] to get the command line back", "unbackup_app": "App '{app:s}' will not be saved", "unexpected_error": "Something unexpected went wrong: {error}", - "unit_unknown": "Unknown unit '{unit:s}'", "unlimit": "No quota", "unrestore_app": "App '{app:s}' will not be restored", "update_apt_cache_failed": "Could not to update the cache of APT (Debian's package manager). Here is a dump of the sources.list lines, which might help identify problematic lines: \n{sourceslist}", diff --git a/src/yunohost/monitor.py b/src/yunohost/monitor.py deleted file mode 100644 index 7af55f287..000000000 --- a/src/yunohost/monitor.py +++ /dev/null @@ -1,740 +0,0 @@ -# -*- coding: utf-8 -*- - -""" License - - Copyright (C) 2013 YunoHost - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program; if not, see http://www.gnu.org/licenses - -""" - -""" yunohost_monitor.py - - Monitoring functions -""" -import re -import json -import time -import psutil -import calendar -import subprocess -import xmlrpclib -import os.path -import os -import dns.resolver -import cPickle as pickle -from datetime import datetime - -from moulinette import m18n -from yunohost.utils.error import YunohostError -from moulinette.utils.log import getActionLogger - -from yunohost.utils.network import get_public_ip -from yunohost.domain import _get_maindomain - -logger = getActionLogger('yunohost.monitor') - -GLANCES_URI = 'http://127.0.0.1:61209' -STATS_PATH = '/var/lib/yunohost/stats' -CRONTAB_PATH = '/etc/cron.d/yunohost-monitor' - - -def monitor_disk(units=None, mountpoint=None, human_readable=False): - """ - Monitor disk space and usage - - Keyword argument: - units -- Unit(s) to monitor - mountpoint -- Device mountpoint - human_readable -- Print sizes in human readable format - - """ - glances = _get_glances_api() - result_dname = None - result = {} - - if units is None: - units = ['io', 'filesystem'] - - _format_dname = lambda d: (os.path.realpath(d)).replace('/dev/', '') - - # Get mounted devices - devices = {} - for p in psutil.disk_partitions(all=True): - if not p.device.startswith('/dev/') or not p.mountpoint: - continue - if mountpoint is None: - devices[_format_dname(p.device)] = p.mountpoint - elif mountpoint == p.mountpoint: - dn = _format_dname(p.device) - devices[dn] = p.mountpoint - result_dname = dn - if len(devices) == 0: - if mountpoint is not None: - raise YunohostError('mountpoint_unknown') - return result - - # Retrieve monitoring for unit(s) - for u in units: - if u == 'io': - # Define setter - if len(units) > 1: - def _set(dn, dvalue): - try: - result[dn][u] = dvalue - except KeyError: - result[dn] = {u: dvalue} - else: - def _set(dn, dvalue): - result[dn] = dvalue - - # Iterate over values - devices_names = devices.keys() - for d in json.loads(glances.getDiskIO()): - dname = d.pop('disk_name') - try: - devices_names.remove(dname) - except: - continue - else: - _set(dname, d) - for dname in devices_names: - _set(dname, 'not-available') - elif u == 'filesystem': - # Define setter - if len(units) > 1: - def _set(dn, dvalue): - try: - result[dn][u] = dvalue - except KeyError: - result[dn] = {u: dvalue} - else: - def _set(dn, dvalue): - result[dn] = dvalue - - # Iterate over values - devices_names = devices.keys() - for d in json.loads(glances.getFs()): - dname = _format_dname(d.pop('device_name')) - try: - devices_names.remove(dname) - except: - continue - else: - d['avail'] = d['size'] - d['used'] - if human_readable: - for i in ['used', 'avail', 'size']: - d[i] = binary_to_human(d[i]) + 'B' - _set(dname, d) - for dname in devices_names: - _set(dname, 'not-available') - else: - raise YunohostError('unit_unknown', unit=u) - - if result_dname is not None: - return result[result_dname] - return result - - -def monitor_network(units=None, human_readable=False): - """ - Monitor network interfaces - - Keyword argument: - units -- Unit(s) to monitor - human_readable -- Print sizes in human readable format - - """ - glances = _get_glances_api() - result = {} - - if units is None: - units = ['check', 'usage', 'infos'] - - # Get network devices and their addresses - # TODO / FIXME : use functions in utils/network.py to manage this - devices = {} - output = subprocess.check_output('ip addr show'.split()) - for d in re.split('^(?:[0-9]+: )', output, flags=re.MULTILINE): - # Extract device name (1) and its addresses (2) - m = re.match('([^\s@]+)(?:@[\S]+)?: (.*)', d, flags=re.DOTALL) - if m: - devices[m.group(1)] = m.group(2) - - # Retrieve monitoring for unit(s) - for u in units: - if u == 'check': - result[u] = {} - domain = _get_maindomain() - cmd_check_smtp = os.system('/bin/nc -z -w1 yunohost.org 25') - if cmd_check_smtp == 0: - smtp_check = m18n.n('network_check_smtp_ok') - else: - smtp_check = m18n.n('network_check_smtp_ko') - - try: - answers = dns.resolver.query(domain, 'MX') - mx_check = {} - i = 0 - for server in answers: - mx_id = 'mx%s' % i - mx_check[mx_id] = server - i = i + 1 - except: - mx_check = m18n.n('network_check_mx_ko') - result[u] = { - 'smtp_check': smtp_check, - 'mx_check': mx_check - } - elif u == 'usage': - result[u] = {} - for i in json.loads(glances.getNetwork()): - iname = i['interface_name'] - if iname in devices.keys(): - del i['interface_name'] - if human_readable: - for k in i.keys(): - if k != 'time_since_update': - i[k] = binary_to_human(i[k]) + 'B' - result[u][iname] = i - else: - logger.debug('interface name %s was not found', iname) - elif u == 'infos': - p_ipv4 = get_public_ip() or 'unknown' - - # TODO / FIXME : use functions in utils/network.py to manage this - l_ip = 'unknown' - for name, addrs in devices.items(): - if name == 'lo': - continue - if not isinstance(l_ip, dict): - l_ip = {} - l_ip[name] = _extract_inet(addrs) - - gateway = 'unknown' - output = subprocess.check_output('ip route show'.split()) - m = re.search('default via (.*) dev ([a-z]+[0-9]?)', output) - if m: - addr = _extract_inet(m.group(1), True) - if len(addr) == 1: - proto, gateway = addr.popitem() - - result[u] = { - 'public_ip': p_ipv4, - 'local_ip': l_ip, - 'gateway': gateway, - } - else: - raise YunohostError('unit_unknown', unit=u) - - if len(units) == 1: - return result[units[0]] - return result - - -def monitor_system(units=None, human_readable=False): - """ - Monitor system informations and usage - - Keyword argument: - units -- Unit(s) to monitor - human_readable -- Print sizes in human readable format - - """ - glances = _get_glances_api() - result = {} - - if units is None: - units = ['memory', 'cpu', 'process', 'uptime', 'infos'] - - # Retrieve monitoring for unit(s) - for u in units: - if u == 'memory': - ram = json.loads(glances.getMem()) - swap = json.loads(glances.getMemSwap()) - if human_readable: - for i in ram.keys(): - if i != 'percent': - ram[i] = binary_to_human(ram[i]) + 'B' - for i in swap.keys(): - if i != 'percent': - swap[i] = binary_to_human(swap[i]) + 'B' - result[u] = { - 'ram': ram, - 'swap': swap - } - elif u == 'cpu': - result[u] = { - 'load': json.loads(glances.getLoad()), - 'usage': json.loads(glances.getCpu()) - } - elif u == 'process': - result[u] = json.loads(glances.getProcessCount()) - elif u == 'uptime': - result[u] = (str(datetime.now() - datetime.fromtimestamp(psutil.boot_time())).split('.')[0]) - elif u == 'infos': - result[u] = json.loads(glances.getSystem()) - else: - raise YunohostError('unit_unknown', unit=u) - - if len(units) == 1 and not isinstance(result[units[0]], str): - return result[units[0]] - return result - - -def monitor_update_stats(period): - """ - Update monitoring statistics - - Keyword argument: - period -- Time period to update (day, week, month) - - """ - if period not in ['day', 'week', 'month']: - raise YunohostError('monitor_period_invalid') - - stats = _retrieve_stats(period) - if not stats: - stats = {'disk': {}, 'network': {}, 'system': {}, 'timestamp': []} - - monitor = None - # Get monitoring stats - if period == 'day': - monitor = _monitor_all('day') - else: - t = stats['timestamp'] - p = 'day' if period == 'week' else 'week' - if len(t) > 0: - monitor = _monitor_all(p, t[len(t) - 1]) - else: - monitor = _monitor_all(p, 0) - if not monitor: - raise YunohostError('monitor_stats_no_update') - - stats['timestamp'].append(time.time()) - - # Append disk stats - for dname, units in monitor['disk'].items(): - disk = {} - # Retrieve current stats for disk name - if dname in stats['disk'].keys(): - disk = stats['disk'][dname] - - for unit, values in units.items(): - # Continue if unit doesn't contain stats - if not isinstance(values, dict): - continue - - # Retrieve current stats for unit and append new ones - curr = disk[unit] if unit in disk.keys() else {} - if unit == 'io': - disk[unit] = _append_to_stats(curr, values, 'time_since_update') - elif unit == 'filesystem': - disk[unit] = _append_to_stats(curr, values, ['fs_type', 'mnt_point']) - stats['disk'][dname] = disk - - # Append network stats - net_usage = {} - for iname, values in monitor['network']['usage'].items(): - # Continue if units doesn't contain stats - if not isinstance(values, dict): - continue - - # Retrieve current stats and append new ones - curr = {} - if 'usage' in stats['network'] and iname in stats['network']['usage']: - curr = stats['network']['usage'][iname] - net_usage[iname] = _append_to_stats(curr, values, 'time_since_update') - stats['network'] = {'usage': net_usage, 'infos': monitor['network']['infos']} - - # Append system stats - for unit, values in monitor['system'].items(): - # Continue if units doesn't contain stats - if not isinstance(values, dict): - continue - - # Set static infos unit - if unit == 'infos': - stats['system'][unit] = values - continue - - # Retrieve current stats and append new ones - curr = stats['system'][unit] if unit in stats['system'].keys() else {} - stats['system'][unit] = _append_to_stats(curr, values) - - _save_stats(stats, period) - - -def monitor_show_stats(period, date=None): - """ - Show monitoring statistics - - Keyword argument: - period -- Time period to show (day, week, month) - - """ - if period not in ['day', 'week', 'month']: - raise YunohostError('monitor_period_invalid') - - result = _retrieve_stats(period, date) - if result is False: - raise YunohostError('monitor_stats_file_not_found') - elif result is None: - raise YunohostError('monitor_stats_period_unavailable') - return result - - -def monitor_enable(with_stats=False): - """ - Enable server monitoring - - Keyword argument: - with_stats -- Enable monitoring statistics - - """ - from yunohost.service import (service_status, service_enable, - service_start) - - glances = service_status('glances') - if glances['status'] != 'running': - service_start('glances') - if glances['loaded'] != 'enabled': - service_enable('glances') - - # Install crontab - if with_stats: - # day: every 5 min # week: every 1 h # month: every 4 h # - rules = ('*/5 * * * * root {cmd} day >> /dev/null\n' - '3 * * * * root {cmd} week >> /dev/null\n' - '6 */4 * * * root {cmd} month >> /dev/null').format( - cmd='/usr/bin/yunohost --quiet monitor update-stats') - with open(CRONTAB_PATH, 'w') as f: - f.write(rules) - - logger.success(m18n.n('monitor_enabled')) - - -def monitor_disable(): - """ - Disable server monitoring - - """ - from yunohost.service import (service_status, service_disable, - service_stop) - - glances = service_status('glances') - if glances['status'] != 'inactive': - service_stop('glances') - if glances['loaded'] != 'disabled': - try: - service_disable('glances') - except YunohostError as e: - logger.warning(e.strerror) - - # Remove crontab - try: - os.remove(CRONTAB_PATH) - except: - pass - - logger.success(m18n.n('monitor_disabled')) - - -def _get_glances_api(): - """ - Retrieve Glances API running on the local server - - """ - try: - p = xmlrpclib.ServerProxy(GLANCES_URI) - p.system.methodHelp('getAll') - except (xmlrpclib.ProtocolError, IOError): - pass - else: - return p - - from yunohost.service import service_status - - if service_status('glances')['status'] != 'running': - raise YunohostError('monitor_not_enabled') - raise YunohostError('monitor_glances_con_failed') - - -def _extract_inet(string, skip_netmask=False, skip_loopback=True): - """ - Extract IP addresses (v4 and/or v6) from a string limited to one - address by protocol - - Keyword argument: - string -- String to search in - skip_netmask -- True to skip subnet mask extraction - skip_loopback -- False to include addresses reserved for the - loopback interface - - Returns: - A dict of {protocol: address} with protocol one of 'ipv4' or 'ipv6' - - """ - ip4_pattern = '((25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}' - ip6_pattern = '(((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)' - ip4_pattern += '/[0-9]{1,2})' if not skip_netmask else ')' - ip6_pattern += '/[0-9]{1,3})' if not skip_netmask else ')' - result = {} - - for m in re.finditer(ip4_pattern, string): - addr = m.group(1) - if skip_loopback and addr.startswith('127.'): - continue - - # Limit to only one result - result['ipv4'] = addr - break - - for m in re.finditer(ip6_pattern, string): - addr = m.group(1) - if skip_loopback and addr == '::1': - continue - - # Limit to only one result - result['ipv6'] = addr - break - - return result - - -def binary_to_human(n, customary=False): - """ - Convert bytes or bits into human readable format with binary prefix - - Keyword argument: - n -- Number to convert - customary -- Use customary symbol instead of IEC standard - - """ - symbols = ('Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi') - if customary: - symbols = ('K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y') - prefix = {} - for i, s in enumerate(symbols): - prefix[s] = 1 << (i + 1) * 10 - for s in reversed(symbols): - if n >= prefix[s]: - value = float(n) / prefix[s] - return '%.1f%s' % (value, s) - return "%s" % n - - -def _retrieve_stats(period, date=None): - """ - Retrieve statistics from pickle file - - Keyword argument: - period -- Time period to retrieve (day, week, month) - date -- Date of stats to retrieve - - """ - pkl_file = None - - # Retrieve pickle file - if date is not None: - timestamp = calendar.timegm(date) - pkl_file = '%s/%d_%s.pkl' % (STATS_PATH, timestamp, period) - else: - pkl_file = '%s/%s.pkl' % (STATS_PATH, period) - if not os.path.isfile(pkl_file): - return False - - # Read file and process its content - with open(pkl_file, 'r') as f: - result = pickle.load(f) - if not isinstance(result, dict): - return None - return result - - -def _save_stats(stats, period, date=None): - """ - Save statistics to pickle file - - Keyword argument: - stats -- Stats dict to save - period -- Time period of stats (day, week, month) - date -- Date of stats - - """ - pkl_file = None - - # Set pickle file name - if date is not None: - timestamp = calendar.timegm(date) - pkl_file = '%s/%d_%s.pkl' % (STATS_PATH, timestamp, period) - else: - pkl_file = '%s/%s.pkl' % (STATS_PATH, period) - if not os.path.isdir(STATS_PATH): - os.makedirs(STATS_PATH) - - # Limit stats - if date is None: - t = stats['timestamp'] - limit = {'day': 86400, 'week': 604800, 'month': 2419200} - if (t[len(t) - 1] - t[0]) > limit[period]: - begin = t[len(t) - 1] - limit[period] - stats = _filter_stats(stats, begin) - - # Write file content - with open(pkl_file, 'w') as f: - pickle.dump(stats, f) - return True - - -def _monitor_all(period=None, since=None): - """ - Monitor all units (disk, network and system) for the given period - If since is None, real-time monitoring is returned. Otherwise, the - mean of stats since this timestamp is calculated and returned. - - Keyword argument: - period -- Time period to monitor (day, week, month) - since -- Timestamp of the stats beginning - - """ - result = {'disk': {}, 'network': {}, 'system': {}} - - # Real-time stats - if period == 'day' and since is None: - result['disk'] = monitor_disk() - result['network'] = monitor_network() - result['system'] = monitor_system() - return result - - # Retrieve stats and calculate mean - stats = _retrieve_stats(period) - if not stats: - return None - stats = _filter_stats(stats, since) - if not stats: - return None - result = _calculate_stats_mean(stats) - - return result - - -def _filter_stats(stats, t_begin=None, t_end=None): - """ - Filter statistics by beginning and/or ending timestamp - - Keyword argument: - stats -- Dict stats to filter - t_begin -- Beginning timestamp - t_end -- Ending timestamp - - """ - if t_begin is None and t_end is None: - return stats - - i_begin = i_end = None - # Look for indexes of timestamp interval - for i, t in enumerate(stats['timestamp']): - if t_begin and i_begin is None and t >= t_begin: - i_begin = i - if t_end and i != 0 and i_end is None and t > t_end: - i_end = i - # Check indexes - if i_begin is None: - if t_begin and t_begin > stats['timestamp'][0]: - return None - i_begin = 0 - if i_end is None: - if t_end and t_end < stats['timestamp'][0]: - return None - i_end = len(stats['timestamp']) - if i_begin == 0 and i_end == len(stats['timestamp']): - return stats - - # Filter function - def _filter(s, i, j): - for k, v in s.items(): - if isinstance(v, dict): - s[k] = _filter(v, i, j) - elif isinstance(v, list): - s[k] = v[i:j] - return s - - stats = _filter(stats, i_begin, i_end) - return stats - - -def _calculate_stats_mean(stats): - """ - Calculate the weighted mean for each statistic - - Keyword argument: - stats -- Stats dict to process - - """ - timestamp = stats['timestamp'] - t_sum = sum(timestamp) - del stats['timestamp'] - - # Weighted mean function - def _mean(s, t, ts): - for k, v in s.items(): - if isinstance(v, dict): - s[k] = _mean(v, t, ts) - elif isinstance(v, list): - try: - nums = [float(x * t[i]) for i, x in enumerate(v)] - except: - pass - else: - s[k] = sum(nums) / float(ts) - return s - - stats = _mean(stats, timestamp, t_sum) - return stats - - -def _append_to_stats(stats, monitor, statics=[]): - """ - Append monitoring statistics to current statistics - - Keyword argument: - stats -- Current stats dict - monitor -- Monitoring statistics - statics -- List of stats static keys - - """ - if isinstance(statics, str): - statics = [statics] - - # Appending function - def _append(s, m, st): - for k, v in m.items(): - if k in st: - s[k] = v - elif isinstance(v, dict): - if k not in s: - s[k] = {} - s[k] = _append(s[k], v, st) - else: - if k not in s: - s[k] = [] - if isinstance(v, list): - s[k].extend(v) - else: - s[k].append(v) - return s - - stats = _append(stats, monitor, statics) - return stats From 08d97172369f0bb959398e539aba880a95dd7330 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 14 Oct 2019 20:26:59 +0200 Subject: [PATCH 0319/3170] Improve test accuracy for apps --- src/yunohost/tests/test_apps.py | 78 ++++++++++++++++----------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/src/yunohost/tests/test_apps.py b/src/yunohost/tests/test_apps.py index fb2f13c3f..5a6db43c9 100644 --- a/src/yunohost/tests/test_apps.py +++ b/src/yunohost/tests/test_apps.py @@ -4,6 +4,8 @@ import pytest import shutil import requests +from conftest import message, raiseYunohostError + from moulinette import m18n from moulinette.utils.filesystem import mkdir @@ -113,9 +115,9 @@ def app_is_not_installed(domain, app): def app_is_exposed_on_http(domain, path, message_in_page): try: - r = requests.get("http://127.0.0.1" + path + "/", headers={"Host": domain}, timeout=10) + r = requests.get("http://127.0.0.1" + path + "/", headers={"Host": domain}, timeout=10, verify=False) return r.status_code == 200 and message_in_page in r.text - except Exception: + except Exception as e: return False @@ -190,11 +192,11 @@ def test_legacy_app_install_private(secondary_domain): assert app_is_not_installed(secondary_domain, "legacy_app") -def test_legacy_app_install_unknown_domain(): +def test_legacy_app_install_unknown_domain(mocker): with pytest.raises(YunohostError): - install_legacy_app("whatever.nope", "/legacy") - # TODO check error message + with message(mocker, "app_argument_invalid"): + install_legacy_app("whatever.nope", "/legacy") assert app_is_not_installed("whatever.nope", "legacy_app") @@ -221,55 +223,51 @@ def test_legacy_app_install_multiple_instances(secondary_domain): assert app_is_not_installed(secondary_domain, "legacy_app__2") -def test_legacy_app_install_path_unavailable(secondary_domain): +def test_legacy_app_install_path_unavailable(mocker, secondary_domain): # These will be removed in teardown install_legacy_app(secondary_domain, "/legacy") with pytest.raises(YunohostError): - install_legacy_app(secondary_domain, "/") - # TODO check error message + with message(mocker, "app_location_unavailable"): + install_legacy_app(secondary_domain, "/") assert app_is_installed(secondary_domain, "legacy_app") assert app_is_not_installed(secondary_domain, "legacy_app__2") -def test_legacy_app_install_bad_args(): - - with pytest.raises(YunohostError): - install_legacy_app("this.domain.does.not.exists", "/legacy") - - -def test_legacy_app_install_with_nginx_down(secondary_domain): +def test_legacy_app_install_with_nginx_down(mocker, secondary_domain): os.system("systemctl stop nginx") - with pytest.raises(YunohostError): + with raiseYunohostError(mocker, "app_action_cannot_be_ran_because_required_services_down"): install_legacy_app(secondary_domain, "/legacy") -def test_legacy_app_failed_install(secondary_domain): +def test_legacy_app_failed_install(mocker, secondary_domain): # This will conflict with the folder that the app # attempts to create, making the install fail mkdir("/var/www/legacy_app/", 0o750) with pytest.raises(YunohostError): - install_legacy_app(secondary_domain, "/legacy") - # TODO check error message + with message(mocker, 'app_install_script_failed'): + install_legacy_app(secondary_domain, "/legacy") assert app_is_not_installed(secondary_domain, "legacy_app") -def test_legacy_app_failed_remove(secondary_domain): +def test_legacy_app_failed_remove(mocker, secondary_domain): install_legacy_app(secondary_domain, "/legacy") # The remove script runs with set -eu and attempt to remove this # file without -f, so will fail if it's not there ;) os.remove("/etc/nginx/conf.d/%s.d/%s.conf" % (secondary_domain, "legacy_app")) - with pytest.raises(YunohostError): - app_remove("legacy") + + # TODO / FIXME : can't easily validate that 'app_not_properly_removed' + # is triggered for weird reasons ... + app_remove("legacy_app") # # Well here, we hit the classical issue where if an app removal script @@ -286,59 +284,61 @@ def test_full_domain_app(secondary_domain): assert app_is_exposed_on_http(secondary_domain, "/", "This is a dummy app") -def test_full_domain_app_with_conflicts(secondary_domain): +def test_full_domain_app_with_conflicts(mocker, secondary_domain): install_legacy_app(secondary_domain, "/legacy") - # TODO : once #808 is merged, add test that the message raised is 'app_full_domain_unavailable' - with pytest.raises(YunohostError): + with raiseYunohostError(mocker, "app_full_domain_unavailable"): install_full_domain_app(secondary_domain) -def test_systemfuckedup_during_app_install(secondary_domain): +def test_systemfuckedup_during_app_install(mocker, secondary_domain): with pytest.raises(YunohostError): - install_break_yo_system(secondary_domain, breakwhat="install") - os.system("nginx -t") - os.system("systemctl status nginx") + with message(mocker, "app_install_failed"): + with message(mocker, 'app_action_broke_system'): + install_break_yo_system(secondary_domain, breakwhat="install") assert app_is_not_installed(secondary_domain, "break_yo_system") -def test_systemfuckedup_during_app_remove(secondary_domain): +def test_systemfuckedup_during_app_remove(mocker, secondary_domain): install_break_yo_system(secondary_domain, breakwhat="remove") with pytest.raises(YunohostError): - app_remove("break_yo_system") - os.system("nginx -t") - os.system("systemctl status nginx") + with message(mocker, 'app_action_broke_system'): + with message(mocker, 'app_removed'): + app_remove("break_yo_system") assert app_is_not_installed(secondary_domain, "break_yo_system") -def test_systemfuckedup_during_app_install_and_remove(secondary_domain): +def test_systemfuckedup_during_app_install_and_remove(mocker, secondary_domain): with pytest.raises(YunohostError): - install_break_yo_system(secondary_domain, breakwhat="everything") + with message(mocker, "app_install_failed"): + with message(mocker, 'app_action_broke_system'): + install_break_yo_system(secondary_domain, breakwhat="everything") assert app_is_not_installed(secondary_domain, "break_yo_system") -def test_systemfuckedup_during_app_upgrade(secondary_domain): +def test_systemfuckedup_during_app_upgrade(mocker, secondary_domain): install_break_yo_system(secondary_domain, breakwhat="upgrade") with pytest.raises(YunohostError): - app_upgrade("break_yo_system", file="./tests/apps/break_yo_system_ynh") + with message(mocker, 'app_action_broke_system'): + app_upgrade("break_yo_system", file="./tests/apps/break_yo_system_ynh") -def test_failed_multiple_app_upgrade(secondary_domain): +def test_failed_multiple_app_upgrade(mocker, secondary_domain): install_legacy_app(secondary_domain, "/legacy") install_break_yo_system(secondary_domain, breakwhat="upgrade") - with pytest.raises(YunohostError): + with raiseYunohostError(mocker, 'app_not_upgraded'): app_upgrade(["break_yo_system", "legacy_app"], file={"break_yo_system": "./tests/apps/break_yo_system_ynh", "legacy": "./tests/apps/legacy_app_ynh"}) From 61931f2c4b47f1e08479b3e46e09e8b3d7ab5f13 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 14 Oct 2019 20:28:16 +0200 Subject: [PATCH 0320/3170] We don't want this to call .error() for legit logs already completed --- src/yunohost/log.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/yunohost/log.py b/src/yunohost/log.py index 8b0f893e8..0f5ff784c 100644 --- a/src/yunohost/log.py +++ b/src/yunohost/log.py @@ -502,7 +502,10 @@ class OperationLogger(object): The missing of the message below could help to see an electrical shortage. """ - self.error(m18n.n('log_operation_unit_unclosed_properly')) + if self.ended_at is not None or self.started_at is None: + return + else: + self.error(m18n.n('log_operation_unit_unclosed_properly')) def _get_description_from_name(name): From f12ff6ade8388917d6be6aae437023d3734bd610 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Mon, 14 Oct 2019 20:04:59 +0000 Subject: [PATCH 0321/3170] Language reworked 2 --- locales/en.json | 160 ++++++++++++++++++++++++------------------------ 1 file changed, 80 insertions(+), 80 deletions(-) diff --git a/locales/en.json b/locales/en.json index a341a6b4f..54966135f 100644 --- a/locales/en.json +++ b/locales/en.json @@ -6,7 +6,7 @@ "admin_password_changed": "The administration password got changed", "admin_password_too_long": "Please choose a password shorter than 127 characters", "already_up_to_date": "Nothing to do. Everything is already up-to-date.", - "app_action_cannot_be_ran_because_required_services_down": "This app requires some services which are currently down. Before continuing, you should try to restart the following services (and possibly investigate why they are down): {services}", + "app_action_cannot_be_ran_because_required_services_down": "This app requires more services to run. Before continuing, try to restart the following services (and possibly investigate why they are down): {services}", "app_action_broke_system": "This action seem to have broke these important services: {services}", "app_already_installed": "{app:s} is already installed", "app_already_installed_cant_change_url": "This app is already installed. The URL cannot be changed just by this function. Look into `app changeurl` if it's available.", @@ -16,24 +16,24 @@ "app_argument_required": "Argument '{name:s}' is required", "app_change_url_failed_nginx_reload": "Could not reload NGINX. Here is the output of 'nginx -t':\n{nginx_errors:s}", "app_change_url_identical_domains": "The old and new domain/url_path are identical ('{domain:s}{path:s}'), nothing to do.", - "app_change_url_no_script": "This application '{app_name:s}' doesn't support URL modification yet. Maybe you should upgrade it.", + "app_change_url_no_script": "The app '{app_name:s}' doesn't support URL modification yet. Maybe you should upgrade it.", "app_change_url_success": "{app:s} URL is now {domain:s}{path:s}", "app_extraction_failed": "Could not extract the installation files", - "app_full_domain_unavailable": "Sorry, this application requires a full domain to be installed on, but some other apps are already installed on domain '{domain}'. One possible solution is to add and use a subdomain dedicated to this application instead.", + "app_full_domain_unavailable": "Sorry, this app must be installed on a domain of its own, but other apps are already installed on the domain '{domain}'. You could use a subdomain dedicated to this app instead.", "app_id_invalid": "Invalid app ID", "app_incompatible": "The app {app} is incompatible with your YunoHost version", "app_install_files_invalid": "These files cannot be installed", "app_install_failed": "Could not install {app}", - "app_install_script_failed": "An error occured inside the app installation script", + "app_install_script_failed": "An error occurred inside the app installation script", "app_location_already_used": "The app '{app}' is already installed in ({path})", - "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_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_install_failed": "Cannot install the app there because it conflicts with the app '{other_app}' already installed in '{other_path}'", "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_not_upgraded": "The app '{failed_app}' failed to upgrade, and as a consequence the following apps upgrades have been cancelled: {apps}", - "app_upgrade_stopped": "The upgrade of all applications has been stopped to prevent possible damage because the previous application failed to upgrade", + "app_upgrade_stopped": "Upgrading all apps was stopped to prevent possible damage because one app could not be upgraded", "app_not_correctly_installed": "{app:s} seems to be incorrectly installed", - "app_not_installed": "Could not find the application '{app:s}' in the list of installed apps: {all_apps}", + "app_not_installed": "Could not find the app '{app:s}' in the list of installed apps: {all_apps}", "app_not_properly_removed": "{app:s} has not been properly removed", "app_package_need_update": "The app {app} package needs to be updated to follow YunoHost changes", "app_removed": "{app:s} removed", @@ -41,29 +41,29 @@ "app_requirements_failed": "Some requirements are not met for {app}: {error}", "app_requirements_unmeet": "Requirements are not met for {app}, the package {pkgname} ({version}) must be {spec}", "app_sources_fetch_failed": "Could not fetch sources files, is the URL correct?", - "app_start_install": "Installing application {app}…", - "app_start_remove": "Removing application {app}…", - "app_start_backup": "Collecting files to be backed up for {app}…", - "app_start_restore": "Restoring application {app}…", + "app_start_install": "Installing the app '{app}'…", + "app_start_remove": "Removing the app '{app}'…", + "app_start_backup": "Collecting files to be backed up for the app '{app}'…", + "app_start_restore": "Restoring the app '{app}'…", "app_unknown": "Unknown app", "app_unsupported_remote_type": "Unsupported remote type used for the app", "app_upgrade_several_apps": "The following apps will be upgraded: {apps}", "app_upgrade_app_name": "Now upgrading {app}…", "app_upgrade_failed": "Could not upgrade {app:s}", - "app_upgrade_some_app_failed": "Some applications could not be upgraded", + "app_upgrade_some_app_failed": "Some apps could not be upgraded", "app_upgraded": "{app:s} upgraded", - "apps_already_up_to_date": "All applications are already up-to-date", + "apps_already_up_to_date": "All apps are already up-to-date", "apps_permission_not_found": "No permission found for the installed apps", - "appslist_corrupted_json": "Could not load the application lists. It looks like {filename:s} is damaged.", - "appslist_could_not_migrate": "Could not migrate the app list {appslist:s}! Could not parse the URL… The old cron job was kept kept in {bkp_file:s}.", - "appslist_fetched": "Updated application list {appslist:s}", - "appslist_migrating": "Migrating application list {appslist:s}…", - "appslist_name_already_tracked": "A registered application list with name {name:s} already exists.", - "appslist_removed": "{appslist:s} application list removed", - "appslist_retrieve_bad_format": "Could not read the fetched application list {appslist:s}", - "appslist_retrieve_error": "Cannot retrieve the remote application list {appslist:s}: {error:s}", - "appslist_unknown": "Application list {appslist:s} unknown.", - "appslist_url_already_tracked": "There is already a registered application list with the URL {url:s}.", + "appslist_corrupted_json": "Could not load the app lists. It looks like {filename:s} is damaged.", + "appslist_could_not_migrate": "Could not migrate the app list '{appslist:s}'! Could not parse the URL… The old cron job was kept kept in {bkp_file:s}.", + "appslist_fetched": "Updated the app list '{appslist:s}'", + "appslist_migrating": "Migrating the app list '{appslist:s}'…", + "appslist_name_already_tracked": "A registered app list with the name {name:s} already exists.", + "appslist_removed": "The '{appslist:s}' app list was removed", + "appslist_retrieve_bad_format": "Could not read the fetched app list '{appslist:s}'", + "appslist_retrieve_error": "Cannot retrieve the remote app list '{appslist:s}': {error:s}", + "appslist_unknown": "The app list '{appslist:s}' is unknown.", + "appslist_url_already_tracked": "There is already a registered app list with the URL {url:s}.", "ask_current_admin_password": "Current administration password", "ask_email": "E-mail address", "ask_firstname": "First name", @@ -89,7 +89,7 @@ "backup_archive_open_failed": "Could not open the backup archive", "backup_archive_system_part_not_available": "System part '{part:s}' unavailable in this backup", "backup_archive_writing_error": "Could not add the files '{source:s}' (named in the archive '{dest:s}') to be backed up into the compressed archive '{archive:s}'", - "backup_ask_for_copying_if_needed": "Some files could not be prepared for backup using the method that avoids temporarily wasting space on the system. To perform the backup, {size:s}MB will be temporarily. Do you agree?", + "backup_ask_for_copying_if_needed": "Do you want to perform the backup using {size:s} MB temporarily due to inability to prepare some files for backup using a more efficient method?", "backup_borg_not_implemented": "The Borg backup method is not yet implemented", "backup_cant_mount_uncompress_archive": "Could not mount the uncompressed archive as write protected", "backup_cleaning_failed": "Could not clean-up the temporary backup folder", @@ -115,7 +115,7 @@ "backup_output_directory_forbidden": "Pick a different output directory. Backups can not be created in /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var or /home/yunohost.backup/archives sub-folders", "backup_output_directory_not_empty": "You should pick an empty output directory", "backup_output_directory_required": "You must provide an output directory for the backup", - "backup_output_symlink_dir_broken": "You have a broken symlink in place of your archive directory '{path:s}'. You may have a specific setup to backup your data on another filesystem, in this case you probably forgot to remount or plug in your hard-drive or USB key.", + "backup_output_symlink_dir_broken": "Your archive directory '{path:s}' only contains a broken symlink. Maybe you forgot to re/mount or plug in the storage medium it points to.", "backup_permission": "Backup permission for app {app:s}", "backup_php5_to_php7_migration_may_fail": "Could not convert your archive to support PHP 7, you may be unable to restore your PHP apps (reason: {error:s})", "backup_running_hooks": "Running backup hooks…", @@ -145,16 +145,16 @@ "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})", - "confirm_app_install_warning": "Warning: This application may work, but is not well-integrated in YunoHost. Some features such as single sign-on and backup/restore might not be available. Install anyway? [{answers:s}] ", - "confirm_app_install_danger": "DANGER! This application is known to be still experimental (if not explicitly not working)! You should probably NOT install it unless you know what you are doing. NO SUPPORT will be provided if this app doesn't work or break your system... If you are willing to take that risk anyway, type '{answers:s}'", - "confirm_app_install_thirdparty": "DANGER! This application is not part of Yunohost's application catalog. Installing third-party applications may compromise the integrity and security of your system. You should probably NOT install it unless you know what you are doing. NO SUPPORT will be provided if this app doesn't work or break your system... If you are willing to take that risk anyway, type '{answers:s}'", + "confirm_app_install_warning": "Warning: This app may work, but is not well-integrated in YunoHost. Some features such as single sign-on and backup/restore might not be available. Install anyway? [{answers:s}] ", + "confirm_app_install_danger": "DANGER! This app is known to be still experimental (if not explicitly not working)! You should probably NOT install it unless you know what you are doing. NO SUPPORT will be provided if this app doesn't work or break your system… If you are willing to take that risk anyway, type '{answers:s}'", + "confirm_app_install_thirdparty": "DANGER! This app is not part of Yunohost's app catalog. Installing third-party apps may compromise the integrity and security of your system. You should probably NOT install it unless you know what you are doing. NO SUPPORT will be provided if this app doesn't work or break your system… If you are willing to take that risk anyway, type '{answers:s}'", "custom_app_url_required": "You must provide a URL to upgrade your custom app {app:s}", "custom_appslist_name_required": "You must provide a name for your custom app list", "diagnosis_debian_version_error": "Could not retrieve the Debian version: {error}", "diagnosis_kernel_version_error": "Could not retrieve kernel version: {error}", "diagnosis_monitor_disk_error": "Could not monitor disks: {error}", "diagnosis_monitor_system_error": "Could not monitor system: {error}", - "diagnosis_no_apps": "No installed application", + "diagnosis_no_apps": "No such installed app", "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_lock_not_available": "This command can't be ran right now because another program seems to be using the lock of dpkg (the system package manager)", "domain_cannot_remove_main": "Cannot remove main domain. Set one first", @@ -236,7 +236,7 @@ "hook_json_return_error": "Could not read return from hook {path:s}. Error: {msg:s}. Raw content: {raw_content}", "hook_list_by_invalid": "This property can not be used to list hooks", "hook_name_unknown": "Unknown hook name '{name:s}'", - "installation_complete": "Installation complete", + "installation_complete": "Installation completed", "installation_failed": "Something went wrong with the installation", "ip6tables_unavailable": "You cannot play with ip6tables here. You are either in a container or your kernel does not support it", "iptables_unavailable": "You cannot play with iptables here. You are either in a container or your kernel does not support it", @@ -248,13 +248,13 @@ "log_help_to_get_failed_log": "The operation '{desc}' could not be completed. Please share the full log of this operation using the command 'yunohost log display {name} --share' to get help", "log_does_exists": "There is not operation log with the name '{log}', use 'yunohost log list' to see all available operation logs", "log_operation_unit_unclosed_properly": "Operation unit has not been closed properly", - "log_app_fetchlist": "Add an application list", - "log_app_removelist": "Remove an application list", - "log_app_change_url": "Change the URL of '{}' application", - "log_app_install": "Install the '{}' application", - "log_app_remove": "Remove the '{}' application", - "log_app_upgrade": "Upgrade the '{}' application", - "log_app_makedefault": "Make '{}' the default application", + "log_app_fetchlist": "Add an app list", + "log_app_removelist": "Remove an app list", + "log_app_change_url": "Change the URL of the '{}' app", + "log_app_install": "Install the '{}' app", + "log_app_remove": "Remove the '{}' app", + "log_app_upgrade": "Upgrade the '{}' app", + "log_app_makedefault": "Make '{}' the default app", "log_available_on_yunopaste": "This log is now available via {url}", "log_backup_restore_system": "Restore system from a backup archive", "log_backup_restore_app": "Restore '{}' from a backup archive", @@ -264,12 +264,12 @@ "log_domain_remove": "Remove '{}' domain from system configuration", "log_dyndns_subscribe": "Subscribe to a YunoHost subdomain '{}'", "log_dyndns_update": "Update the IP associated with your YunoHost subdomain '{}'", - "log_letsencrypt_cert_install": "Install a Let's encrypt certificate on '{}' domain", + "log_letsencrypt_cert_install": "Install a Let's Encrypt certificate on '{}' domain", "log_permission_create": "Create permission '{}'", "log_permission_delete": "Delete permission '{}'", "log_permission_urls": "Update urls related to permission '{}'", "log_selfsigned_cert_install": "Install self signed certificate on '{}' domain", - "log_letsencrypt_cert_renew": "Renew '{}' Let's encrypt certificate", + "log_letsencrypt_cert_renew": "Renew '{}' Let's Encrypt certificate", "log_regen_conf": "Regenerate system configurations '{}'", "log_user_create": "Add '{}' user", "log_user_delete": "Delete '{}' user", @@ -331,31 +331,31 @@ "migration_0005_postgresql_94_not_installed": "PostgreSQL was not installed on your system. Nothing to do.", "migration_0005_postgresql_96_not_installed": "PostgreSQL 9.4 is installed, but not postgresql 9.6‽ Something weird might have happened on your system:(…", "migration_0005_not_enough_space": "Make sufficient space available in {path} to run the migration.", - "migration_0006_disclaimer": "YunoHost now expects admin and root passwords to be synchronized. By running this migration, your root password is going to be replaced by the admin password.", - "migration_0007_cancelled": "YunoHost has failed to improve the way your SSH conf is managed.", + "migration_0006_disclaimer": "YunoHost now expects the admin and root passwords to be synchronized. This migration, replaces your root password with the admin password.", + "migration_0007_cancelled": "Could not improve the way your SSH configuration is managed.", "migration_0007_cannot_restart": "SSH can't be restarted after trying to cancel migration number 6.", "migration_0008_general_disclaimer": "To improve the security of your server, it is recommended to let YunoHost manage the SSH configuration. Your current SSH setup differs from the recommendation. If you let YunoHost reconfigure it, the way you connect to your server through SSH will change thusly:", "migration_0008_port": "• You will have to connect using port 22 instead of your current custom SSH port. Feel free to reconfigure it;", "migration_0008_root": "• You will not be able to connect as root through SSH. Instead you should use the admin user;", "migration_0008_dsa": "• The DSA key will be turned off. Hence, you might need to invalidate a spooky warning from your SSH client, and recheck the fingerprint of your server;", - "migration_0008_warning": "If you understand those warnings and agree to let YunoHost override your current configuration, run the migration. Otherwise, you can also skip the migration - though it is not recommended.", - "migration_0008_no_warning": "No major risk indentified concerning overriding your SSH configuration—one can however not be absolutely sure ;)! Run the migration to override it. Otherwise, you can also skip the migration - though it is not recommended.", + "migration_0008_warning": "If you understand those warnings and want YunoHost to override your current configuration, run the migration. Otherwise, you can also skip the migration, though it is not recommended.", + "migration_0008_no_warning": "Overriding your SSH configuration should be safe, though this can not be promised! Run the migration to override it. Otherwise, you can also skip the migration, though it is not recommended.", "migration_0009_not_needed": "This migration already happened somehow… (?) Skipping.", "migration_0011_backup_before_migration": "Creating a backup of LDAP database and apps settings prior to the actual migration.", - "migration_0011_can_not_backup_before_migration": "The backup of the system before the migration failed. Migration failed. Error: {error:s}", + "migration_0011_can_not_backup_before_migration": "The backup of the system could not be completed before the migration failed. Error: {error:s}", "migration_0011_create_group": "Creating a group for each user…", - "migration_0011_done": "Migration successful. You are now able to manage usergroups.", - "migration_0011_LDAP_config_dirty": "It look like that you customized your LDAP configuration. For this migration the LDAP configuration needs to be updated.\nYou need to save your current configuration, reintialize the original configuration by running 'yunohost tools regen-conf -f' and retry the migration", + "migration_0011_done": "Migration completed. You are now able to manage usergroups.", + "migration_0011_LDAP_config_dirty": "Save your current custom LDAP conguration, and reintialize the original one by running 'yunohost tools regen-conf -f' and retry the migration.", "migration_0011_LDAP_update_failed": "Could not update LDAP. Error: {error:s}", "migration_0011_migrate_permission": "Migrating permissions from apps settings to LDAP…", - "migration_0011_migration_failed_trying_to_rollback": "Migration failed… trying to roll back the system.", + "migration_0011_migration_failed_trying_to_rollback": "Could not migrate… trying to roll back the system.", "migration_0011_rollback_success": "System rolled back.", "migration_0011_update_LDAP_database": "Updating LDAP database…", "migration_0011_update_LDAP_schema": "Updating LDAP schema…", - "migration_0011_failed_to_remove_stale_object": "Failed to remove stale object {dn}: {error}", + "migration_0011_failed_to_remove_stale_object": "Could not remove stale object {dn}: {error}", "migrations_already_ran": "Those migrations are already done: {ids}", - "migrations_cant_reach_migration_file": "Could not access migrations files at path %s", - "migrations_dependencies_not_satisfied": "Cannot run migration {id} because first you need to run these migrations: {dependencies_id}", + "migrations_cant_reach_migration_file": "Could not access migrations files at the path '%s'", + "migrations_dependencies_not_satisfied": "Run these migrations: '{dependencies_id}', before migration {id}.", "migrations_failed_to_load_migration": "Could not load migration {id}: {error}", "migrations_exclusive_options": "'--auto', '--skip', and '--force-rerun' are mutually exclusive options.", "migrations_list_conflict_pending_done": "You cannot use both '--previous' and '--done' at the same time.", @@ -364,32 +364,32 @@ "migrations_must_provide_explicit_targets": "You must provide explicit targets when using '--skip' or '--force-rerun'", "migrations_need_to_accept_disclaimer": "To run the migration {id}, your must accept the following disclaimer:\n---\n{disclaimer}\n---\nIf you accept to run the migration, please re-run the command with the option '--accept-disclaimer'.", "migrations_no_migrations_to_run": "No migrations to run", - "migrations_no_such_migration": "There is no migration called {id}", + "migrations_no_such_migration": "There is no migration called '{id}'", "migrations_not_pending_cant_skip": "Those migrations are not pending, so cannot be skipped: {ids}", "migrations_pending_cant_rerun": "Those migrations are still pending, so cannot be run again: {ids}", "migrations_running_forward": "Running migration {id}…", "migrations_skip_migration": "Skipping migration {id}…", "migrations_success_forward": "Migration {id} completed", "migrations_to_be_ran_manually": "Migration {id} has to be run manually. Please go to Tools → Migrations on the webadmin page, or run `yunohost tools migrations migrate`.", - "monitor_disabled": "Server monitoring now turned off", - "monitor_enabled": "Server monitoring now turned on", + "monitor_disabled": "Server monitoring now off", + "monitor_enabled": "Server monitoring now on", "monitor_glances_con_failed": "Could not connect to Glances server", "monitor_not_enabled": "Server monitoring is off", "monitor_period_invalid": "Invalid time period", - "monitor_stats_file_not_found": "Statistics file not found", + "monitor_stats_file_not_found": "Could not find the statistics file", "monitor_stats_no_update": "No monitoring statistics to update", "monitor_stats_period_unavailable": "No available statistics for the period", "mountpoint_unknown": "Unknown mountpoint", - "mysql_db_creation_failed": "MySQL database creation failed", - "mysql_db_init_failed": "MySQL database init failed", - "mysql_db_initialized": "The MySQL database now initialized", + "mysql_db_creation_failed": "Could not create MySQL database", + "mysql_db_init_failed": "Could not initialize MySQL database", + "mysql_db_initialized": "The MySQL database is now initialized", "network_check_mx_ko": "DNS MX record is not set", "network_check_smtp_ko": "Outbound e-mail (SMTP port 25) seems to be blocked by your network", "network_check_smtp_ok": "Outbound e-mail (SMTP port 25) is not blocked", - "no_internet_connection": "Server not connected to the Internet", + "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_not_installed": "Package '{pkgname}' is not installed", + "operation_interrupted": "Was the operation manually interrupted?", + "package_not_installed": "The package '{pkgname}' is not installed", "package_unexpected_error": "An unexpected error occurred processing the package '{pkgname}'", "package_unknown": "Unknown package '{pkgname}'", "packages_upgrade_failed": "Could not upgrade all the packages", @@ -400,7 +400,7 @@ "password_too_simple_4": "The password needs to be at least 12 characters long and contain a digit, upper, lower and special characters", "pattern_backup_archive_name": "Must be a valid filename with max 30 characters, alphanumeric and -_. characters only", "pattern_domain": "Must be a valid domain name (e.g. my-domain.org)", - "pattern_email": "Must be a valid email address (e.g. someone@domain.org)", + "pattern_email": "Must be a valid e-mail address (e.g. someone@example.com)", "pattern_firstname": "Must be a valid first name", "pattern_lastname": "Must be a valid last name", "pattern_listname": "Must be alphanumeric and underscore characters only", @@ -466,7 +466,7 @@ "server_reboot_confirm": "The server will reboot immediatly, are you sure? [{answers:s}]", "service_add_failed": "Could not add the service '{service:s}'", "service_added": "The service '{service:s}' added", - "service_already_started": "The service '{service:s}' has already been started", + "service_already_started": "The service '{service:s}' is running already", "service_already_stopped": "The service '{service:s}' has already been stopped", "service_cmd_exec_failed": "Could not execute the command '{command:s}'", "service_description_avahi-daemon": "Allows you to reach your server using 'yunohost.local' in your local network", @@ -475,10 +475,10 @@ "service_description_fail2ban": "Protects against brute-force and other kinds of attacks from the Internet", "service_description_glances": "Monitors system info on your server", "service_description_metronome": "Manage XMPP instant messaging accounts", - "service_description_mysql": "Stores applications data (SQL database)", + "service_description_mysql": "Stores app data (SQL database)", "service_description_nginx": "Serves or provides access to all the websites hosted on your server", "service_description_nslcd": "Handles YunoHost user shell connection", - "service_description_php7.0-fpm": "Runs applications written in PHP with NGINX", + "service_description_php7.0-fpm": "Runs apps written in PHP with NGINX", "service_description_postfix": "Used to send and receive e-mails", "service_description_redis-server": "A specialized database used for rapid data access, task queue, and communication between programs", "service_description_rmilter": "Checks various parameters in e-mails", @@ -486,42 +486,42 @@ "service_description_slapd": "Stores users, domains and related info", "service_description_ssh": "Allows you to connect remotely to your server via a terminal (SSH protocol)", "service_description_yunohost-api": "Manages interactions between the YunoHost web interface and the system", - "service_description_yunohost-firewall": "Manages open and close connexion ports to services", + "service_description_yunohost-firewall": "Manages open and close connection ports to services", "service_disable_failed": "Could not turn off the service '{service:s}'\n\nRecent service logs:{logs:s}", - "service_disabled": "'{service:s}' service turned off", + "service_disabled": "The '{service:s}' service was turned off", "service_enable_failed": "Could not turn on the service '{service:s}'\n\nRecent service logs:{logs:s}", - "service_enabled": "'{service:s}' service turned off", - "service_no_log": "No log to display for service '{service:s}'", + "service_enabled": "The '{service:s}' service was turned off", + "service_no_log": "No logs to display for the service '{service:s}'", "service_regen_conf_is_deprecated": "'yunohost service regen-conf' is deprecated! Please use 'yunohost tools regen-conf' instead.", "service_remove_failed": "Could not remove the service '{service:s}'", "service_removed": "'{service:s}' service removed", "service_reload_failed": "Could not reload the service '{service:s}'\n\nRecent service logs:{logs:s}", - "service_reloaded": "'{service:s}' service reloaded", + "service_reloaded": "The '{service:s}' service was reloaded", "service_restart_failed": "Could not restart the service '{service:s}'\n\nRecent service logs:{logs:s}", "service_restarted": "'{service:s}' service restarted", "service_reload_or_restart_failed": "Could not reload or restart the service '{service:s}'\n\nRecent service logs:{logs:s}", - "service_reloaded_or_restarted": "'{service:s}' service reloaded or restarted", + "service_reloaded_or_restarted": "The '{service:s}' service was reloaded or restarted", "service_start_failed": "Could not start the service '{service:s}'\n\nRecent service logs:{logs:s}", "service_started": "'{service:s}' service started", "service_stop_failed": "Could not stop the service '{service:s}'\n\nRecent service logs:{logs:s}", - "service_stopped": "'{service:s}' service stopped", + "service_stopped": "The '{service:s}' service stopped", "service_unknown": "Unknown service '{service:s}'", "ssowat_conf_generated": "SSOwat configuration generated", "ssowat_conf_updated": "SSOwat configuration updated", - "ssowat_persistent_conf_read_error": "Could not read persistent SSOwat configuration: {error:s}. Edit /etc/ssowat/conf.json.persistent file to fix the JSON syntax", - "ssowat_persistent_conf_write_error": "Could not save persistent SSOwat configuration: {error:s}. Edit /etc/ssowat/conf.json.persistent file to fix the JSON syntax", + "ssowat_persistent_conf_read_error": "Could not read persistent SSOwat configuration: {error:s}. Edit the /etc/ssowat/conf.json.persistent file to fix the JSON syntax", + "ssowat_persistent_conf_write_error": "Could not save persistent SSOwat configuration: {error:s}. Edit the /etc/ssowat/conf.json.persistent file to fix the JSON syntax", "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`.", - "tools_update_failed_to_app_fetchlist": "Could not update YunoHost's applists because: {error}", + "tools_update_failed_to_app_fetchlist": "Could not update YunoHost's app lists because: {error}", "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…", - "tools_upgrade_cant_unhold_critical_packages": "Could not to unhold critical packages…", + "tools_upgrade_cant_unhold_critical_packages": "Could not unhold critical packages…", "tools_upgrade_regular_packages": "Now upgrading 'regular' (non-yunohost-related) packages…", "tools_upgrade_regular_packages_failed": "Could not upgrade packages: {packages_list}", "tools_upgrade_special_packages": "Now upgrading 'special' (yunohost-related) packages…", - "tools_upgrade_special_packages_explanation": "This action will end, but the actual special upgrade will continue in background. Please don't start any other action on your server in the next ~10 minutes (depending on your hardware speed). Once it i done, you may have to log in on the webadmin page again. The upgrade log will be available in Tools → Log (on the webadmin page) or through 'yunohost log list' (from the command line).", + "tools_upgrade_special_packages_explanation": "This action will end, but the actual special upgrade will continue in background. Please don't start any other actions on your server the next ~10 minutes (depending on hardware speed). Once done, you may have to log in on the webadmin page again. The upgrade log will be available in Tools → Log (on the webadmin page) or through 'yunohost log list' (from the command-line).", "tools_upgrade_special_packages_completed": "YunoHost package upgrade completed.\nPress [Enter] to get the command line back", "unbackup_app": "App '{app:s}' will not be saved", "unexpected_error": "Something unexpected went wrong: {error}", @@ -531,14 +531,14 @@ "update_apt_cache_failed": "Could not to update the cache of APT (Debian's package manager). Here is a dump of the sources.list lines, which might help identify problematic lines: \n{sourceslist}", "update_apt_cache_warning": "Something went wrong while updating the cache of APT (Debian's package manager). Here is a dump of the sources.list lines, which might help identify problematic lines: \n{sourceslist}", "updating_apt_cache": "Fetching available upgrades for system packages…", - "updating_app_lists": "Fetching available upgrades for applications…", + "updating_app_lists": "Fetching available upgrades for apps…", "upgrade_complete": "Upgrade complete", "upgrading_packages": "Upgrading packages…", "upnp_dev_not_found": "No UPnP device found", "upnp_disabled": "UPnP turned off", "upnp_enabled": "UPnP turned on", "upnp_port_open_failed": "Could not open port via UPnP", - "user_already_exists": "User {user} already exists", + "user_already_exists": "The user '{user}' already exists", "user_created": "User created", "user_creation_failed": "Could not create user {user}: {error}", "user_deleted": "User deleted", @@ -552,7 +552,7 @@ "yunohost_already_installed": "YunoHost is already installed", "yunohost_ca_creation_failed": "Could not create certificate authority", "yunohost_ca_creation_success": "Local certification authority created.", - "yunohost_configured": "YunoHost now configured", + "yunohost_configured": "YunoHost is now configured", "yunohost_installing": "Installing YunoHost…", - "yunohost_not_installed": "YunoHost is incorrectly or not correctly installed. Please run 'yunohost tools postinstall'" + "yunohost_not_installed": "YunoHost is not correctly installed. Please run 'yunohost tools postinstall'" } From dcea6ae5fafcd9415ff8df34542286416797c859 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Mon, 14 Oct 2019 21:08:55 +0000 Subject: [PATCH 0322/3170] This migration, Co-Authored-By: Alexandre Aubin --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 54966135f..5859dca6c 100644 --- a/locales/en.json +++ b/locales/en.json @@ -331,7 +331,7 @@ "migration_0005_postgresql_94_not_installed": "PostgreSQL was not installed on your system. Nothing to do.", "migration_0005_postgresql_96_not_installed": "PostgreSQL 9.4 is installed, but not postgresql 9.6‽ Something weird might have happened on your system:(…", "migration_0005_not_enough_space": "Make sufficient space available in {path} to run the migration.", - "migration_0006_disclaimer": "YunoHost now expects the admin and root passwords to be synchronized. This migration, replaces your root password with the admin password.", + "migration_0006_disclaimer": "YunoHost now expects the admin and root passwords to be synchronized. This migration replaces your root password with the admin password.", "migration_0007_cancelled": "Could not improve the way your SSH configuration is managed.", "migration_0007_cannot_restart": "SSH can't be restarted after trying to cancel migration number 6.", "migration_0008_general_disclaimer": "To improve the security of your server, it is recommended to let YunoHost manage the SSH configuration. Your current SSH setup differs from the recommendation. If you let YunoHost reconfigure it, the way you connect to your server through SSH will change thusly:", From aef8a38071433e0b528bfe14e3319ae007a35541 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Mon, 14 Oct 2019 21:10:46 +0000 Subject: [PATCH 0323/3170] is a broken symlink --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 5859dca6c..27b3bcaef 100644 --- a/locales/en.json +++ b/locales/en.json @@ -115,7 +115,7 @@ "backup_output_directory_forbidden": "Pick a different output directory. Backups can not be created in /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var or /home/yunohost.backup/archives sub-folders", "backup_output_directory_not_empty": "You should pick an empty output directory", "backup_output_directory_required": "You must provide an output directory for the backup", - "backup_output_symlink_dir_broken": "Your archive directory '{path:s}' only contains a broken symlink. Maybe you forgot to re/mount or plug in the storage medium it points to.", + "backup_output_symlink_dir_broken": "Your archive directory '{path:s}' is a broken symlink. Maybe you forgot to re/mount or plug in the storage medium it points to.", "backup_permission": "Backup permission for app {app:s}", "backup_php5_to_php7_migration_may_fail": "Could not convert your archive to support PHP 7, you may be unable to restore your PHP apps (reason: {error:s})", "backup_running_hooks": "Running backup hooks…", From af44f4c73e2d3689ec5355b132581ca871d358b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Mon, 14 Oct 2019 21:13:19 +0000 Subject: [PATCH 0324/3170] This way is used, could not be prepared --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 27b3bcaef..9f9cb05ba 100644 --- a/locales/en.json +++ b/locales/en.json @@ -89,7 +89,7 @@ "backup_archive_open_failed": "Could not open the backup archive", "backup_archive_system_part_not_available": "System part '{part:s}' unavailable in this backup", "backup_archive_writing_error": "Could not add the files '{source:s}' (named in the archive '{dest:s}') to be backed up into the compressed archive '{archive:s}'", - "backup_ask_for_copying_if_needed": "Do you want to perform the backup using {size:s} MB temporarily due to inability to prepare some files for backup using a more efficient method?", + "backup_ask_for_copying_if_needed": "Do you want to perform the backup using {size:s} MB temporarily? (This way is used since some files could not be prepared using a more efficient method.)", "backup_borg_not_implemented": "The Borg backup method is not yet implemented", "backup_cant_mount_uncompress_archive": "Could not mount the uncompressed archive as write protected", "backup_cleaning_failed": "Could not clean-up the temporary backup folder", From a8deabc36976947be492ea2857bbfd25a6b17e7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Mon, 14 Oct 2019 21:17:11 +0000 Subject: [PATCH 0325/3170] Start all required --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 9f9cb05ba..ed7f08efd 100644 --- a/locales/en.json +++ b/locales/en.json @@ -6,7 +6,7 @@ "admin_password_changed": "The administration password got changed", "admin_password_too_long": "Please choose a password shorter than 127 characters", "already_up_to_date": "Nothing to do. Everything is already up-to-date.", - "app_action_cannot_be_ran_because_required_services_down": "This app requires more services to run. Before continuing, try to restart the following services (and possibly investigate why they are down): {services}", + "app_action_cannot_be_ran_because_required_services_down": "Start all required services to run this app. Try to restart the following ones (and possibly investigate why they are down): {services}", "app_action_broke_system": "This action seem to have broke these important services: {services}", "app_already_installed": "{app:s} is already installed", "app_already_installed_cant_change_url": "This app is already installed. The URL cannot be changed just by this function. Look into `app changeurl` if it's available.", From 7d0119ade48e9627be25e5857f9d0ff91bd65747 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 15 Oct 2019 01:06:04 +0200 Subject: [PATCH 0326/3170] Fix backup info.json format... --- src/yunohost/backup.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index c28160342..4bf8d8afc 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -602,10 +602,10 @@ class BackupManager(): env=env_dict, chdir=self.work_dir) - ret_succeed = {hook: {path:result["state"] for path, result in infos.items()} + ret_succeed = {hook: [path for path, result in infos.items() if result["state"] == "succeed"] for hook, infos in ret.items() if any(result["state"] == "succeed" for result in infos.values())} - ret_failed = {hook: {path:result["state"] for path, result in infos.items.items()} + ret_failed = {hook: [path for path, result in infos.items.items() if result["state"] == "failed"] for hook, infos in ret.items() if any(result["state"] == "failed" for result in infos.values())} @@ -2371,6 +2371,13 @@ def backup_info(name, with_details=False, human_readable=False): if "size_details" in info.keys(): for category in ["apps", "system"]: for name, key_info in info[category].items(): + + # Stupid legacy fix for weird format between 3.5 and 3.6 + if isinstance(key_info, dict): + key_info = key_info.keys() + + info[category][name] = key_info = {"paths": key_info} + if name in info["size_details"][category].keys(): key_info["size"] = info["size_details"][category][name] if human_readable: From 6dc720f3cfbf5945d89614587f6333038d8dffa6 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 15 Oct 2019 02:36:12 +0200 Subject: [PATCH 0327/3170] [yolo] Use read_json / write_to_json helpers to read/write ssowat conf.json.persistent --- locales/en.json | 2 -- src/yunohost/app.py | 22 +++++++++------------- src/yunohost/tools.py | 16 ++++------------ src/yunohost/user.py | 21 +++++++++------------ 4 files changed, 22 insertions(+), 39 deletions(-) diff --git a/locales/en.json b/locales/en.json index a341a6b4f..cc73d658a 100644 --- a/locales/en.json +++ b/locales/en.json @@ -508,8 +508,6 @@ "service_unknown": "Unknown service '{service:s}'", "ssowat_conf_generated": "SSOwat configuration generated", "ssowat_conf_updated": "SSOwat configuration updated", - "ssowat_persistent_conf_read_error": "Could not read persistent SSOwat configuration: {error:s}. Edit /etc/ssowat/conf.json.persistent file to fix the JSON syntax", - "ssowat_persistent_conf_write_error": "Could not save persistent SSOwat configuration: {error:s}. Edit /etc/ssowat/conf.json.persistent file to fix the JSON syntax", "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`.", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index f0b4d3c25..8ac2bcb11 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -41,7 +41,7 @@ from datetime import datetime from moulinette import msignals, m18n, msettings from moulinette.utils.log import getActionLogger -from moulinette.utils.filesystem import read_json, read_toml +from moulinette.utils.filesystem import read_json, read_toml, write_to_json from yunohost.service import service_log, service_status, _run_service_command from yunohost.utils import packages @@ -1233,25 +1233,21 @@ def app_makedefault(operation_logger, app, domain=None): raise YunohostError('app_make_default_location_already_used', app=app, domain=app_domain, other_app=app_map(raw=True)[domain]["/"]["id"]) - try: - with open('/etc/ssowat/conf.json.persistent') as json_conf: - ssowat_conf = json.loads(str(json_conf.read())) - except ValueError as e: - raise YunohostError('ssowat_persistent_conf_read_error', error=e) - except IOError: + # TODO / FIXME : current trick is to add this to conf.json.persisten + # This is really not robust and should be improved + # e.g. have a flag in /etc/yunohost/apps/$app/ to say that this is the + # default app or idk... + if not os.path.exists('/etc/ssowat/conf.json.persistent'): ssowat_conf = {} + else: + ssowat_conf = read_json('/etc/ssowat/conf.json.persistent') if 'redirected_urls' not in ssowat_conf: ssowat_conf['redirected_urls'] = {} ssowat_conf['redirected_urls'][domain + '/'] = app_domain + app_path - try: - with open('/etc/ssowat/conf.json.persistent', 'w+') as f: - json.dump(ssowat_conf, f, sort_keys=True, indent=4) - except IOError as e: - raise YunohostError('ssowat_persistent_conf_write_error', error=e) - + write_to_json('/etc/ssowat/conf.json.persistent', ssowat_conf) os.system('chmod 644 /etc/ssowat/conf.json.persistent') logger.success(m18n.n('ssowat_conf_updated')) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 64689fe0c..679bf1190 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -350,25 +350,17 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False, os.system('hostname yunohost.yunohost.org') # Add a temporary SSOwat rule to redirect SSO to admin page - try: - with open('/etc/ssowat/conf.json.persistent') as json_conf: - ssowat_conf = json.loads(str(json_conf.read())) - except ValueError as e: - raise YunohostError('ssowat_persistent_conf_read_error', error=str(e)) - except IOError: + if not os.path.exists('/etc/ssowat/conf.json.persistent'): ssowat_conf = {} + else: + ssowat_conf = read_json('/etc/ssowat/conf.json.persistent') if 'redirected_urls' not in ssowat_conf: ssowat_conf['redirected_urls'] = {} ssowat_conf['redirected_urls']['/'] = domain + '/yunohost/admin' - try: - with open('/etc/ssowat/conf.json.persistent', 'w+') as f: - json.dump(ssowat_conf, f, sort_keys=True, indent=4) - except IOError as e: - raise YunohostError('ssowat_persistent_conf_write_error', error=str(e)) - + write_to_json('/etc/ssowat/conf.json.persistent', ssowat_conf) os.system('chmod 644 /etc/ssowat/conf.json.persistent') # Create SSL CA diff --git a/src/yunohost/user.py b/src/yunohost/user.py index fe27492f4..e12cccb9b 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -35,8 +35,10 @@ import subprocess import copy from moulinette import m18n -from yunohost.utils.error import YunohostError from moulinette.utils.log import getActionLogger +from moulinette.utils.filesystem import read_json, write_to_json, read_yaml, write_to_yaml + +from yunohost.utils.error import YunohostError from yunohost.service import service_status from yunohost.log import is_unit_operation @@ -195,21 +197,16 @@ def user_create(operation_logger, username, firstname, lastname, mail, password, attr_dict['mail'] = [attr_dict['mail']] + aliases # If exists, remove the redirection from the SSO - try: - with open('/etc/ssowat/conf.json.persistent') as json_conf: - ssowat_conf = json.loads(str(json_conf.read())) - except ValueError as e: - raise YunohostError('ssowat_persistent_conf_read_error', error=str(e)) - except IOError: + if not os.path.exists('/etc/ssowat/conf.json.persistent'): ssowat_conf = {} + else: + ssowat_conf = read_json('/etc/ssowat/conf.json.persistent') if 'redirected_urls' in ssowat_conf and '/' in ssowat_conf['redirected_urls']: del ssowat_conf['redirected_urls']['/'] - try: - with open('/etc/ssowat/conf.json.persistent', 'w+') as f: - json.dump(ssowat_conf, f, sort_keys=True, indent=4) - except IOError as e: - raise YunohostError('ssowat_persistent_conf_write_error', error=str(e)) + + write_to_json('/etc/ssowat/conf.json.persistent', ssowat_conf) + os.system('chmod 644 /etc/ssowat/conf.json.persistent') try: ldap.add('uid=%s,ou=users' % username, attr_dict) From 4def4dfa6a7cef4137498eaece55b4732f0559a8 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 15 Oct 2019 14:54:52 +0200 Subject: [PATCH 0328/3170] [yolofix] Should have a list of string to be able to join() later --- src/yunohost/tools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 679bf1190..28b507707 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -608,8 +608,8 @@ def tools_upgrade(operation_logger, apps=None, system=False): # randomly from yunohost itself... upgrading them is likely to critical_packages = ("moulinette", "yunohost", "yunohost-admin", "ssowat", "python") - critical_packages_upgradable = [p for p in upgradables if p["name"] in critical_packages] - noncritical_packages_upgradable = [p for p in upgradables if p["name"] not in critical_packages] + critical_packages_upgradable = [p["name"] for p in upgradables if p["name"] in critical_packages] + noncritical_packages_upgradable = [p["name"] for p in upgradables if p["name"] not in critical_packages] # Prepare dist-upgrade command dist_upgrade = "DEBIAN_FRONTEND=noninteractive" From f31c8c7475f054482852ac44d7b263fa5371d095 Mon Sep 17 00:00:00 2001 From: pitfd Date: Mon, 14 Oct 2019 09:14:40 +0000 Subject: [PATCH 0329/3170] Translated using Weblate (German) Currently translated at 39.4% (218 of 554 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/de.json b/locales/de.json index 1fe279d6b..6699f508c 100644 --- a/locales/de.json +++ b/locales/de.json @@ -413,5 +413,6 @@ "global_settings_setting_security_password_admin_strength": "Stärke des Admin-Passworts", "global_settings_key_doesnt_exists": "Der Schlüssel'{settings_key:s}' existiert nicht in den globalen Einstellungen, du kannst alle verfügbaren Schlüssel sehen, indem du 'yunohost settings list' ausführst", "log_app_makedefault": "Mache '{}' zur Standard-Anwendung", - "hook_json_return_error": "Konnte die Rückkehr vom Einsprungpunkt {path:s} nicht lesen. Fehler: {msg:s}. Unformatierter Inhalt: {raw_content}" + "hook_json_return_error": "Konnte die Rückkehr vom Einsprungpunkt {path:s} nicht lesen. Fehler: {msg:s}. Unformatierter Inhalt: {raw_content}", + "app_full_domain_unavailable": "Es tut uns leid, aber diese Anwendung erfordert die Installation einer vollständigen Domäne, aber einige andere Anwendungen sind bereits auf der Domäne'{domain}' installiert. Eine mögliche Lösung ist das Hinzufügen und Verwenden einer Subdomain, die dieser Anwendung zugeordnet ist." } From 3aea7f6b04b26de1c634b8029fbe63da63a388ff Mon Sep 17 00:00:00 2001 From: xaloc33 Date: Thu, 10 Oct 2019 08:24:02 +0000 Subject: [PATCH 0330/3170] Translated using Weblate (Catalan) Currently translated at 100.0% (554 of 554 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ca/ --- locales/ca.json | 385 ++++++++++++++++++++++++++---------------------- 1 file changed, 209 insertions(+), 176 deletions(-) diff --git a/locales/ca.json b/locales/ca.json index f5c040670..1476d5fb5 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -1,31 +1,31 @@ { "action_invalid": "Acció '{action:s}' invàlida", "admin_password": "Contrasenya d'administració", - "admin_password_change_failed": "No s'ha pogut canviar la contrasenya", + "admin_password_change_failed": "No es pot canviar la contrasenya", "admin_password_changed": "S'ha canviat la contrasenya d'administració", "app_already_installed": "{app:s} ja està instal·lada", "app_already_installed_cant_change_url": "Aquesta aplicació ja està instal·lada. La URL no és pot canviar únicament amb aquesta funció. Mireu a \"app changeurl\" si està disponible.", "app_already_up_to_date": "{app:s} ja està actualitzada", - "app_argument_choice_invalid": "Aquesta opció no és vàlida per l'argument '{name:s}', ha de ser una de {choices:s}", - "app_argument_invalid": "Valor invàlid per l'argument '{name:s}':{error:s}", + "app_argument_choice_invalid": "Utilitzeu una de les opcions «{choices:s}» per l'argument «{name:s}»", + "app_argument_invalid": "Escolliu un valor vàlid per l'argument «{name:s}»: {error:s}", "app_argument_required": "Es necessita l'argument '{name:s}'", "app_change_no_change_url_script": "L'aplicació {app_name:s} encara no permet canviar la seva URL, es possible que s'hagi d'actualitzar.", - "app_change_url_failed_nginx_reload": "No s'ha pogut tornar a carregar nginx. Aquí teniu el resultat de \"nginx -t\":\n{nginx_errors:s}", + "app_change_url_failed_nginx_reload": "No s'ha pogut tornar a carregar NGINX. Aquí teniu el resultat de \"nginx -t\":\n{nginx_errors:s}", "app_change_url_identical_domains": "L'antic i el nou domini/camí són idèntics ('{domain:s}{path:s}'), no hi ha res per fer.", - "app_change_url_no_script": "Aquesta aplicació '{app_name:s}' encara no permet modificar la URL. Potser s'ha d'actualitzar l'aplicació.", - "app_change_url_success": "La URL de {app:s} s'ha canviat correctament a {domain:s}{path:s}", + "app_change_url_no_script": "Aquesta aplicació '{app_name:s}' encara no permet modificar la URL. Potser s'ha d'actualitzar.", + "app_change_url_success": "La URL de {app:s} ara és {domain:s}{path:s}", "app_extraction_failed": "No s'han pogut extreure els fitxers d'instal·lació", - "app_id_invalid": "Id de l'aplicació incorrecte", + "app_id_invalid": "ID de l'aplicació incorrecte", "app_incompatible": "L'aplicació {app} no és compatible amb la teva versió de YunoHost", - "app_install_files_invalid": "Fitxers d'instal·lació invàlids", - "app_location_already_used": "L'aplicació '{app}' ja està instal·lada en aquest camí ({path})", + "app_install_files_invalid": "Aquests fitxers no es poden instal·lar", + "app_location_already_used": "L'aplicació «{app}» ja està instal·lada en ({path})", "app_make_default_location_already_used": "No es pot fer l'aplicació '{app}' per defecte en el domini {domain} ja que ja és utilitzat per una altra aplicació '{other_app}'", - "app_location_install_failed": "No s'ha pogut instal·lar l'aplicació en aquest camí ja que entra en conflicte amb l'aplicació '{other_app}' ja instal·lada a '{other_path}'", - "app_location_unavailable": "Aquesta url no està disponible o entra en conflicte amb aplicacions ja instal·lades:\n{apps:s}", - "app_manifest_invalid": "Manifest d'aplicació incorrecte: {error}", + "app_location_install_failed": "No s'ha pogut instal·lar l'aplicació aquí ja que entra en conflicte amb l'aplicació «{other_app}» ja instal·lada a «{other_path}»", + "app_location_unavailable": "Aquesta URL no està disponible o entra en conflicte amb aplicacions ja instal·lades:\n{apps:s}", + "app_manifest_invalid": "Hi ha algun error amb el manifest de l'aplicació: {error}", "app_no_upgrade": "No hi ha cap aplicació per actualitzar", "app_not_correctly_installed": "{app:s} sembla estar mal instal·lada", - "app_not_installed": "L'aplicació «{app:s}» no està instal·lada. Aquí hi ha la llista d'aplicacions instal·lades: {all_apps}", + "app_not_installed": "No s'ha trobat l'aplicació «{app:s}» en la llista d'aplicacions instal·lades: {all_apps}", "app_not_properly_removed": "{app:s} no s'ha pogut suprimir correctament", "app_package_need_update": "El paquet de l'aplicació {app} ha de ser actualitzat per poder seguir els canvis de YunoHost", "app_removed": "{app:s} ha estat suprimida", @@ -35,22 +35,22 @@ "app_sources_fetch_failed": "No s'han pogut carregar els fitxers font, l'URL és correcta?", "app_unknown": "Aplicació desconeguda", "app_unsupported_remote_type": "El tipus remot utilitzat per l'aplicació no està suportat", - "app_upgrade_app_name": "Actualitzant l'aplicació {app}…", + "app_upgrade_app_name": "Actualitzant {app}…", "app_upgrade_failed": "No s'ha pogut actualitzar {app:s}", "app_upgrade_some_app_failed": "No s'han pogut actualitzar algunes aplicacions", - "app_upgraded": "{app:s} ha estat actualitzada", + "app_upgraded": "S'ha actualitzat {app:s}", "appslist_corrupted_json": "No s'han pogut carregar les llistes d'aplicacions. Sembla que {filename:s} està danyat.", "appslist_could_not_migrate": "No s'ha pogut migrar la llista d'aplicacions {appslist:s}! No s'ha pogut analitzar la URL... L'antic cronjob s'ha guardat a {bkp_file:s}.", - "appslist_fetched": "S'ha descarregat la llista d'aplicacions {appslist:s} correctament", + "appslist_fetched": "S'ha actualitzat la llista d'aplicacions {appslist:s}", "appslist_migrating": "Migrant la llista d'aplicacions {appslist:s}…", "appslist_name_already_tracked": "Ja hi ha una llista d'aplicacions registrada amb el nom {name:s}.", "appslist_removed": "S'ha eliminat la llista d'aplicacions {appslist:s}", - "appslist_retrieve_bad_format": "L'arxiu obtingut per la llista d'aplicacions {appslist:s} no és vàlid", + "appslist_retrieve_bad_format": "No s'ha pogut llegir la llista d'aplicacions obtinguda {appslist:s}", "appslist_retrieve_error": "No s'ha pogut obtenir la llista d'aplicacions remota {appslist:s}: {error:s}", "appslist_unknown": "La llista d'aplicacions {appslist:s} es desconeguda.", "appslist_url_already_tracked": "Ja hi ha una llista d'aplicacions registrada amb al URL {url:s}.", "ask_current_admin_password": "Contrasenya d'administrador actual", - "ask_email": "Correu electrònic", + "ask_email": "Adreça de correu electrònic", "ask_firstname": "Nom", "ask_lastname": "Cognom", "ask_list_to_remove": "Llista per a suprimir", @@ -58,31 +58,31 @@ "ask_new_admin_password": "Nova contrasenya d'administrador", "ask_password": "Contrasenya", "ask_path": "Camí", - "backup_abstract_method": "Encara no s'ha implementat aquest mètode de copia de seguretat", + "backup_abstract_method": "Encara està per implementar aquest mètode de còpia de seguretat", "backup_action_required": "S'ha d'especificar què s'ha de guardar", "backup_app_failed": "No s'ha pogut fer la còpia de seguretat de l'aplicació \"{app:s}\"", "backup_applying_method_borg": "Enviant tots els fitxers de la còpia de seguretat al repositori borg-backup…", "backup_applying_method_copy": "Còpia de tots els fitxers a la còpia de seguretat…", "backup_applying_method_custom": "Crida del mètode de còpia de seguretat personalitzat \"{method:s}\"…", - "backup_applying_method_tar": "Creació de l'arxiu tar de la còpia de seguretat…", - "backup_archive_app_not_found": "L'aplicació \"{app:s}\" no es troba dins l'arxiu de la còpia de seguretat", + "backup_applying_method_tar": "Creació de l'arxiu TAR de la còpia de seguretat…", + "backup_archive_app_not_found": "No s'ha pogut trobar l'aplicació «{app:s}» dins l'arxiu de la còpia de seguretat", "backup_archive_broken_link": "No s'ha pogut accedir a l'arxiu de la còpia de seguretat (enllaç invàlid cap a {path:s})", "backup_archive_mount_failed": "No s'ha pogut carregar l'arxiu de la còpia de seguretat", - "backup_archive_name_exists": "Ja hi ha una còpia de seguretat amb aquest nom", + "backup_archive_name_exists": "Ja hi ha una còpia de seguretat amb aquest nom.", "backup_archive_name_unknown": "Còpia de seguretat local \"{name:s}\" desconeguda", "backup_archive_open_failed": "No s'ha pogut obrir l'arxiu de la còpia de seguretat", - "backup_archive_system_part_not_available": "La part \"{part:s}\" del sistema no està disponible en aquesta copia de seguretat", - "backup_archive_writing_error": "No es poden afegir arxius a l'arxiu comprimit de la còpia de seguretat", + "backup_archive_system_part_not_available": "La part «{part:s}» del sistema no està disponible en aquesta copia de seguretat", + "backup_archive_writing_error": "No es poden afegir els arxius «{source:s}» (anomenats en l'arxiu «{dest:s}») a l'arxiu comprimit de la còpia de seguretat «{archive:s}»", "backup_ask_for_copying_if_needed": "Alguns fitxers no s'han pogut preparar per la còpia de seguretat utilitzant el mètode que evita malgastar espai del sistema temporalment. Per fer la còpia de seguretat, s'han d'utilitzar {size:s}MB temporalment. Hi esteu d'acord?", "backup_borg_not_implemented": "El mètode de còpia de seguretat Borg encara no està implementat", - "backup_cant_mount_uncompress_archive": "No es pot carregar en mode de lectura només el directori de l'arxiu descomprimit", + "backup_cant_mount_uncompress_archive": "No es pot carregar l'arxiu descomprimit com a protegit contra escriptura", "backup_cleaning_failed": "No s'ha pogut netejar el directori temporal de la còpia de seguretat", "backup_copying_to_organize_the_archive": "Copiant {size:s}MB per organitzar l'arxiu", "backup_couldnt_bind": "No es pot lligar {src:s} amb {dest:s}.", "backup_created": "S'ha creat la còpia de seguretat", "backup_creating_archive": "Creant l'arxiu de la còpia de seguretat…", "aborting": "Avortant.", - "app_not_upgraded": "Les següents aplicacions no s'han actualitzat: {apps}", + "app_not_upgraded": "L'aplicació «{failed_app}» no s'ha pogut actualitzar, i com a conseqüència l'actualització de les següents aplicacions ha estat cancel·lada: {apps}", "app_start_install": "instal·lant l'aplicació {app}…", "app_start_remove": "Eliminant l'aplicació {app}…", "app_start_backup": "Recuperant els fitxers pels que s'ha de fer una còpia de seguretat per {app}…", @@ -90,24 +90,24 @@ "app_upgrade_several_apps": "S'actualitzaran les següents aplicacions: {apps}", "ask_new_domain": "Nou domini", "ask_new_path": "Nou camí", - "backup_actually_backuping": "S'està creant un arxiu de còpia de seguretat a partir dels fitxers recuperats…", - "backup_creation_failed": "Ha fallat la creació de la còpia de seguretat", + "backup_actually_backuping": "Creant un arxiu de còpia de seguretat a partir dels fitxers recuperats…", + "backup_creation_failed": "No s'ha pogut crear l'arxiu de la còpia de seguretat", "backup_csv_addition_failed": "No s'han pogut afegir fitxers per a fer-ne la còpia de seguretat al fitxer CSV", - "backup_csv_creation_failed": "No s'ha pogut crear el fitxer CSV necessari per a futures operacions de recuperació", - "backup_custom_backup_error": "El mètode de còpia de seguretat personalitzat ha fallat a l'etapa \"backup\"", - "backup_custom_mount_error": "El mètode de còpia de seguretat personalitzat ha fallat a l'etapa \"mount\"", + "backup_csv_creation_failed": "No s'ha pogut crear el fitxer CSV necessari per a la restauració", + "backup_custom_backup_error": "El mètode de còpia de seguretat personalitzat ha fallat a l'etapa «backup»", + "backup_custom_mount_error": "El mètode de còpia de seguretat personalitzat ha fallat a l'etapa «mount»", "backup_custom_need_mount_error": "El mètode de còpia de seguretat personalitzat ha fallat a l'etapa \"need_mount\"", - "backup_delete_error": "No s'ha pogut suprimir \"{path:s}\"", + "backup_delete_error": "No s'ha pogut suprimir «{path:s}»", "backup_deleted": "S'ha suprimit la còpia de seguretat", "backup_extracting_archive": "Extraient l'arxiu de la còpia de seguretat…", - "backup_hook_unknown": "Script de còpia de seguretat \"{hook:s}\" desconegut", - "backup_invalid_archive": "Arxiu de còpia de seguretat no vàlid", - "backup_method_borg_finished": "La còpia de seguretat a borg ha acabat", + "backup_hook_unknown": "Script de còpia de seguretat «{hook:s}» desconegut", + "backup_invalid_archive": "Aquest no és un arxiu de còpia de seguretat", + "backup_method_borg_finished": "La còpia de seguretat a Borg ha acabat", "backup_method_copy_finished": "La còpia de la còpia de seguretat ha acabat", "backup_method_custom_finished": "El mètode de còpia de seguretat personalitzat \"{method:s}\" ha acabat", - "backup_method_tar_finished": "S'ha creat l'arxiu de còpia de seguretat tar", + "backup_method_tar_finished": "S'ha creat l'arxiu de còpia de seguretat TAR", "backup_mount_archive_for_restore": "Preparant l'arxiu per la restauració…", - "good_practices_about_user_password": "Esteu a punt de definir una nova contrasenya d'usuari. La contrasenya ha de tenir un mínim de 8 caràcters ; tot i que és de bona pràctica utilitzar una contrasenya més llarga (és a dir una frase de contrasenya) i/o utilitzar diferents tipus de caràcters (majúscules, minúscules, dígits i caràcters especials).", + "good_practices_about_user_password": "Esteu a punt de definir una nova contrasenya d'usuari. La contrasenya ha de tenir un mínim de 8 caràcters; tot i que és de bona pràctica utilitzar una contrasenya més llarga (és a dir una frase de contrasenya) i/o utilitzar diferents tipus de caràcters (majúscules, minúscules, dígits i caràcters especials).", "password_listed": "Aquesta contrasenya és una de les més utilitzades en el món. Si us plau utilitzeu-ne una més única.", "password_too_simple_1": "La contrasenya ha de tenir un mínim de 8 caràcters", "password_too_simple_2": "La contrasenya ha de tenir un mínim de 8 caràcters i ha de contenir dígits, majúscules i minúscules", @@ -115,42 +115,42 @@ "password_too_simple_4": "La contrasenya ha de tenir un mínim de 12 caràcters i tenir dígits, majúscules, minúscules i caràcters especials", "backup_no_uncompress_archive_dir": "El directori de l'arxiu descomprimit no existeix", "backup_nothings_done": "No hi ha res a guardar", - "backup_output_directory_forbidden": "Directori de sortida no permès. Les còpies de seguretat no es poden crear ni dins els directoris /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var ni dins els subdirectoris /home/yunohost.backup/archives", - "backup_output_directory_not_empty": "El directori de sortida no està buit", + "backup_output_directory_forbidden": "Escolliu un directori de sortida different. Les còpies de seguretat no es poden crear ni dins els directoris /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var ni dins els subdirectoris /home/yunohost.backup/archives", + "backup_output_directory_not_empty": "Heu d'escollir un directori de sortida buit", "backup_output_directory_required": "Heu d'especificar un directori de sortida per la còpia de seguretat", - "backup_output_symlink_dir_broken": "Teniu un enllaç simbòlic trencat en lloc del directori dels arxius '{path:s}'. Pot ser teniu una configuració per la còpia de seguretat específica en un altre sistema de fitxers, si és el cas segurament heu oblidat muntar o connectar el disc dur o la clau USB.", - "backup_php5_to_php7_migration_may_fail": "No s'ha pogut convertir l'arxiu per suportar php7, la restauració de les vostres aplicacions pot fallar (raó: {error:s})", + "backup_output_symlink_dir_broken": "Teniu un enllaç simbòlic trencat en lloc del directori del arxiu «{path:s}». Pot ser teniu una configuració per la còpia de seguretat específica en un altre sistema de fitxers, si és el cas segurament heu oblidat muntar o connectar el disc dur o la clau USB.", + "backup_php5_to_php7_migration_may_fail": "No s'ha pogut convertir l'arxiu per suportar PHP 7, pot ser que no es puguin restaurar les vostres aplicacions PHP (raó: {error:s})", "backup_running_hooks": "Executant els scripts de la còpia de seguretat…", "backup_system_part_failed": "No s'ha pogut fer la còpia de seguretat de la part \"{part:s}\" del sistema", - "backup_unable_to_organize_files": "No s'han pogut organitzar els fitxers dins de l'arxiu amb el mètode ràpid", - "backup_with_no_backup_script_for_app": "L'aplicació {app:s} no té un script de còpia de seguretat. Serà ignorat.", - "backup_with_no_restore_script_for_app": "L'aplicació {app:s} no té un script de restauració, no podreu restaurar automàticament la còpia de seguretat d'aquesta aplicació.", - "certmanager_acme_not_configured_for_domain": "El certificat pel domini {domain:s} sembla que no està instal·lat correctament. Si us plau executeu primer cert-install per aquest domini.", - "certmanager_attempt_to_renew_nonLE_cert": "El certificat pel domini {domain:s} no ha estat emès per Let's Encrypt. No es pot renovar automàticament!", - "certmanager_attempt_to_renew_valid_cert": "El certificat pel domini {domain:s} està a punt de caducar! (Utilitzeu --force si sabeu el que esteu fent)", + "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_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)", "certmanager_cannot_read_cert": "S'ha produït un error al intentar obrir el certificat actual pel domini {domain:s} (arxiu: {file:s}), raó: {reason:s}", - "certmanager_cert_install_success": "S'ha instal·lat correctament un certificat Let's Encrypt pel domini {domain:s}!", - "certmanager_cert_install_success_selfsigned": "S'ha instal·lat correctament un certificat auto-signat pel domini {domain:s}!", - "certmanager_cert_renew_success": "S'ha renovat correctament el certificat Let's Encrypt pel domini {domain:s}!", + "certmanager_cert_install_success": "S'ha instal·lat correctament un certificat Let's Encrypt pel domini «{domain:s}»", + "certmanager_cert_install_success_selfsigned": "S'ha instal·lat correctament un certificat auto-signat pel domini «{domain:s}»", + "certmanager_cert_renew_success": "S'ha renovat correctament el certificat Let's Encrypt pel domini «{domain:s}»", "certmanager_cert_signing_failed": "No s'ha pogut firmar el nou certificat", - "certmanager_certificate_fetching_or_enabling_failed": "Sembla que l'activació del nou certificat per {domain:s} ha fallat…", - "certmanager_conflicting_nginx_file": "No s'ha pogut preparar el domini per al desafiament ACME: l'arxiu de configuració nginx {filepath:s} entra en conflicte i s'ha d'eliminar primer", + "certmanager_certificate_fetching_or_enabling_failed": "Sembla que utilitzar el nou certificat per {domain:s} ha fallat…", + "certmanager_conflicting_nginx_file": "No s'ha pogut preparar el domini per al desafiament ACME: l'arxiu de configuració NGINX {filepath:s} entra en conflicte i s'ha d'eliminar primer", "certmanager_couldnt_fetch_intermediate_cert": "S'ha exhaurit el temps d'esperar al intentar recollir el certificat intermedi des de Let's Encrypt. La instal·lació/renovació del certificat s'ha cancel·lat - torneu a intentar-ho més tard.", - "certmanager_domain_cert_not_selfsigned": "El certificat pel domini {domain:s} no és auto-signat Esteu segur de voler canviar-lo? (Utilitzeu --force per fer-ho)", - "certmanager_domain_dns_ip_differs_from_public_ip": "El registre DNS \"A\" pel domini {domain:s} és diferent a l'adreça IP d'aquest servidor. Si heu modificat recentment el registre A, si us plau espereu a que es propagui (hi ha eines per verificar la propagació disponibles a internet). (Si sabeu el que esteu fent, podeu utilitzar --no-checks per desactivar aquestes comprovacions.)", - "certmanager_domain_http_not_working": "Sembla que el domini {domain:s} no és accessible via HTTP. Si us plau verifiqueu que les configuracions DNS i nginx siguin correctes", + "certmanager_domain_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_not_resolved_locally": "El domini {domain:s} no es pot resoldre dins del vostre servidor YunoHost. Això pot passar si heu modificat recentment el registre DNS. Si és així, si us plau espereu unes hores per a que es propagui. Si el problema continua, considereu afegir {domain:s} a /etc/hosts. (Si sabeu el que esteu fent, podeu utilitzar --no-checks per desactivar aquestes comprovacions.)", - "certmanager_domain_unknown": "Domini desconegut {domain:s}", - "certmanager_error_no_A_record": "No s'ha trobat cap registre DNS \"A\" per {domain:s}. Heu de fer que el vostre nom de domini apunti cap a la vostra màquina per tal de poder instal·lar un certificat Let's Encrypt! (Si sabeu el que esteu fent, podeu utilitzar --no-checks per desactivar aquestes comprovacions.)", + "certmanager_domain_unknown": "Domini desconegut «{domain:s}»", + "certmanager_error_no_A_record": "No s'ha trobat cap registre DNS «A» per «{domain:s}». Heu de fer que el vostre nom de domini apunti cap a la vostra màquina per tal de poder instal·lar un certificat Let's Encrypt. (Si sabeu el que esteu fent, podeu utilitzar «--no-checks» per desactivar aquestes comprovacions.)", "certmanager_hit_rate_limit": "S'han emès massa certificats recentment per aquest mateix conjunt de dominis {domain:s}. Si us plau torneu-ho a intentar més tard. Consulteu https://letsencrypt.org/docs/rate-limits/ per obtenir més detalls", - "certmanager_http_check_timeout": "S'ha exhaurit el temps d'espera quan el servidor ha intentat contactar amb ell mateix via HTTP utilitzant la seva adreça IP pública (domini domain:s} amb IP {ip:s}). Pot ser degut a hairpinning o a que el talla focs/router al que està connectat el servidor estan mal configurats.", + "certmanager_http_check_timeout": "S'ha exhaurit el temps d'espera quan el servidor ha intentat contactar amb ell mateix via HTTP utilitzant la seva adreça IP pública (domini «{domain:s}» amb IP «{ip:s}»). Pot ser degut a hairpinning o a que el talla focs/router al que està connectat el servidor estan mal configurats.", "certmanager_no_cert_file": "No s'ha pogut llegir l'arxiu del certificat pel domini {domain:s} (fitxer: {file:s})", "certmanager_self_ca_conf_file_not_found": "No s'ha trobat el fitxer de configuració per l'autoritat del certificat auto-signat (fitxer: {file:s})", "certmanager_unable_to_parse_self_CA_name": "No s'ha pogut analitzar el nom de l'autoritat del certificat auto-signat (fitxer: {file:s})", - "confirm_app_install_warning": "Atenció: aquesta aplicació funciona però no està ben integrada amb YunoHost. Algunes característiques com la autenticació única i la còpia de seguretat/restauració poden no estar disponibles. Voleu instal·lar-la de totes maneres? [{answers:s}] ", - "confirm_app_install_danger": "ATENCIÓ! Aquesta aplicació encara és experimental (si no és que no funciona directament) i és probable que trenqui el sistema! No hauríeu d'instal·lar-la a no ser que sapigueu el que feu. Esteu segurs de voler córrer aquest risc? [{answers:s}] ", - "confirm_app_install_thirdparty": "ATENCIÓ! La instal·lació d'aplicacions de terceres parts pot comprometre la integritat i seguretat del seu sistema. Faci-ho sota la seva responsabilitat.No hauríeu d'instal·lar-ne a no ser que sapigueu el que feu. Esteu segurs de voler córrer aquest risc? [{answers:s}] ", + "confirm_app_install_warning": "Atenció: Aquesta aplicació funciona, però no està ben integrada amb YunoHost. Algunes característiques com la autenticació única i la còpia de seguretat/restauració poden no estar disponibles. Voleu instal·lar-la de totes maneres? [{answers:s}] ", + "confirm_app_install_danger": "PERILL! Aquesta aplicació encara és experimental (si no és que no funciona directament)! No hauríeu d'instal·lar-la a no ser que sapigueu el que feu. No obtindreu CAP AJUDA si l'aplicació no funciona o trenca el sistema... Si accepteu el risc, escriviu «{answers:s}»", + "confirm_app_install_thirdparty": "PERILL! Aquesta aplicació no es part del catàleg d'aplicacions de YunoHost. La instal·lació d'aplicacions de terceres parts pot comprometre la integritat i seguretat del seu sistema. No hauríeu d'instal·lar-ne a no ser que sapigueu el que feu. No obtindreu CAP AJUDA si l'aplicació no funciona o trenca el sistema… Si accepteu el risc, escriviu «{answers:s}»", "custom_app_url_required": "Heu de especificar una URL per actualitzar la vostra aplicació personalitzada {app:s}", "custom_appslist_name_required": "Heu d'especificar un nom per la vostra llista d'aplicacions personalitzada", "diagnosis_debian_version_error": "No s'ha pogut obtenir la versió Debian: {error}", @@ -160,22 +160,22 @@ "diagnosis_monitor_system_error": "No es pot monitorar el sistema: {error}", "diagnosis_no_apps": "No hi ha cap aplicació instal·lada", "admin_password_too_long": "Trieu una contrasenya de menys de 127 caràcters", - "dpkg_is_broken": "No es pot fer això en aquest instant perquè dpkg/apt (els gestors de paquets del sistema) sembla estar mal configurat... Podeu intentar solucionar-ho connectant-vos per ssh i executant \"sudo dpkg --configure -a\".", + "dpkg_is_broken": "No es pot fer això en aquest instant perquè dpkg/APT (els gestors de paquets del sistema) sembla estar mal configurat… Podeu intentar solucionar-ho connectant-vos per SSH i executant «sudo dpkg --configure -a».", "dnsmasq_isnt_installed": "sembla que dnsmasq no està instal·lat, executeu \"apt-get remove bind9 && apt-get install dnsmasq\"", "domain_cannot_remove_main": "No es pot eliminar el domini principal. S'ha d'establir un nou domini primer", "domain_cert_gen_failed": "No s'ha pogut generar el certificat", "domain_created": "S'ha creat el domini", - "domain_creation_failed": "No s'ha pogut crear el domini", + "domain_creation_failed": "No s'ha pogut crear el domini {domain}: {error}", "domain_deleted": "S'ha eliminat el domini", - "domain_deletion_failed": "No s'ha pogut eliminar el domini", + "domain_deletion_failed": "No s'ha pogut eliminar el domini {domini}: {error}", "domain_exists": "El domini ja existeix", - "app_action_cannot_be_ran_because_required_services_down": "Aquesta aplicació necessita serveis que estan aturats. Abans de continuar, hauríeu d'intentar arrancar de nou els serveis següents (i també investigar perquè estan aturats) : {services}", + "app_action_cannot_be_ran_because_required_services_down": "Aquesta aplicació necessita serveis que estan aturats. Abans de continuar, hauríeu d'intentar arrancar de nou els serveis següents (i també investigar perquè estan aturats): {services}", "domain_dns_conf_is_just_a_recommendation": "Aquesta ordre mostra la configuració *recomanada*. En cap cas fa la configuració del DNS. És la vostra responsabilitat configurar la zona DNS en el vostre registrar en acord amb aquesta recomanació.", "domain_dyndns_already_subscribed": "Ja us heu subscrit a un domini DynDNS", "domain_dyndns_dynette_is_unreachable": "No s'ha pogut abastar la dynette YunoHost, o bé YunoHost no està connectat a internet correctament o bé el servidor dynette està caigut. Error: {error}", "domain_dyndns_invalid": "Domini no vàlid per utilitzar amb 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 (no és segur ... podria no passar res).", + "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_unknown": "Domini desconegut", "domain_zone_exists": "El fitxer de zona DNS ja existeix", @@ -187,61 +187,61 @@ "dyndns_could_not_check_available": "No s'ha pogut verificar la disponibilitat de {domain:s} a {provider:s}.", "dyndns_ip_update_failed": "No s'ha pogut actualitzar l'adreça IP al DynDNS", "dyndns_ip_updated": "S'ha actualitzat l'adreça IP al DynDNS", - "dyndns_key_generating": "S'està generant la clau DNS, això pot trigar una estona…", + "dyndns_key_generating": "S'està generant la clau DNS… això pot trigar una estona.", "dyndns_key_not_found": "No s'ha trobat la clau DNS pel domini", "dyndns_no_domain_registered": "No hi ha cap domini registrat amb DynDNS", "dyndns_registered": "S'ha registrat el domini DynDNS", "dyndns_registration_failed": "No s'ha pogut registrar el domini DynDNS: {error:s}", - "dyndns_domain_not_provided": "El proveïdor {provider:s} no pot oferir el domini {domain:s}.", + "dyndns_domain_not_provided": "El proveïdor de DynDNS {provider:s} no pot oferir el domini {domain:s}.", "dyndns_unavailable": "El domini {domain:s} no està disponible.", "executing_command": "Execució de l'ordre « {command:s} »…", "executing_script": "Execució de l'script « {script:s} »…", "extracting": "Extracció en curs…", - "dyndns_cron_installed": "S'ha instal·lat la tasca cron pel DynDNS", + "dyndns_cron_installed": "S'ha creat la tasca cron pel DynDNS", "dyndns_cron_remove_failed": "No s'ha pogut eliminar la tasca cron per a DynDNS: {error}", "dyndns_cron_removed": "S'ha eliminat la tasca cron pel DynDNS", - "experimental_feature": "Atenció: aquesta funcionalitat és experimental i no es considera estable, no s'ha d'utilitzar a excepció de saber el que esteu fent.", + "experimental_feature": "Atenció: Aquesta funcionalitat és experimental i no es considera estable, no s'ha d'utilitzar a excepció de saber el que esteu fent.", "field_invalid": "Camp incorrecte « {:s} »", "file_does_not_exist": "El camí {path:s} no existeix.", - "firewall_reload_failed": "No s'ha pogut tornar a carregar el tallafoc", - "firewall_reloaded": "S'ha tornat a carregar el tallafoc", - "firewall_rules_cmd_failed": "No s'han pogut aplicar algunes regles del tallafoc. Mireu el registre per a més informació.", + "firewall_reload_failed": "No s'ha pogut tornar a carregar el tallafocs", + "firewall_reloaded": "S'ha tornat a carregar el tallafocs", + "firewall_rules_cmd_failed": "No s'han pogut aplicar algunes regles del tallafocs. Més informació en el registre.", "format_datetime_short": "%d/%m/%Y %H:%M", - "global_settings_bad_choice_for_enum": "Opció pel paràmetre {setting:s} incorrecta, s'ha rebut «{choice:s}» però les opcions disponibles són: {available_choices:s}", + "global_settings_bad_choice_for_enum": "Opció pel paràmetre {setting:s} incorrecta, s'ha rebut «{choice:s}», però les opcions disponibles són: {available_choices:s}", "global_settings_bad_type_for_setting": "El tipus del paràmetre {setting:s} és incorrecte. S'ha rebut {received_type:s}, però s'esperava {expected_type:s}", "global_settings_cant_open_settings": "No s'ha pogut obrir el fitxer de configuració, raó: {reason:s}", "global_settings_cant_serialize_settings": "No s'ha pogut serialitzar les dades de configuració, raó: {reason:s}", "global_settings_cant_write_settings": "No s'ha pogut escriure el fitxer de configuració, raó: {reason:s}", "global_settings_key_doesnt_exists": "La clau « {settings_key:s} » no existeix en la configuració global, podeu veure totes les claus disponibles executant « yunohost settings list »", - "global_settings_reset_success": "Èxit. S'ha fet una còpia de seguretat de la configuració anterior a {path:s}", + "global_settings_reset_success": "S'ha fet una còpia de seguretat de la configuració anterior a {path:s}", "global_settings_setting_example_bool": "Exemple d'opció booleana", "global_settings_setting_example_enum": "Exemple d'opció de tipus enumeració", "global_settings_setting_example_int": "Exemple d'opció de tipus enter", "global_settings_setting_example_string": "Exemple d'opció de tipus cadena", - "global_settings_setting_security_nginx_compatibility": "Solució de compromís entre compatibilitat i seguretat pel servidor web nginx. Afecta els criptògrafs (i altres aspectes relacionats amb la seguretat)", + "global_settings_setting_security_nginx_compatibility": "Solució de compromís entre compatibilitat i seguretat pel servidor web NGINX. Afecta els criptògrafs (i altres aspectes relacionats amb la seguretat)", "global_settings_setting_security_password_admin_strength": "Robustesa de la contrasenya d'administrador", "global_settings_setting_security_password_user_strength": "Robustesa de la contrasenya de l'usuari", "global_settings_setting_security_ssh_compatibility": "Solució de compromís entre compatibilitat i seguretat pel servidor SSH. Afecta els criptògrafs (i altres aspectes relacionats amb la seguretat)", - "global_settings_unknown_setting_from_settings_file": "Clau de configuració desconeguda: «{setting_key:s}», refusant-la i guardant-la a /etc/yunohost/settings-unknown.json", + "global_settings_unknown_setting_from_settings_file": "Clau de configuració desconeguda: «{setting_key:s}», refusada i guardada a /etc/yunohost/settings-unknown.json", "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Permetre la clau d'hoste DSA (obsolet) per la configuració del servei SSH", "global_settings_unknown_type": "Situació inesperada, la configuració {setting:s} sembla tenir el tipus {unknown_type:s} però no és un tipus reconegut pel sistema.", - "good_practices_about_admin_password": "Esteu a punt de definir una nova contrasenya d'administrador. La contrasenya ha de tenir un mínim de 8 caràcters ; tot i que és de bona pràctica utilitzar una contrasenya més llarga (és a dir una frase de contrasenya) i/o utilitzar diferents tipus de caràcters (majúscules, minúscules, dígits i caràcters especials).", - "hook_exec_failed": "No s'ha pogut executar l'script: {path:s}", - "hook_exec_not_terminated": "L'execució de l'script « {path:s} » no s'ha acabat correctament", - "hook_json_return_error": "No s'ha pogut llegir el retorn de l'script {path:s}. Error: {msg:s}. Contingut en brut: {raw_content}", - "hook_list_by_invalid": "Propietat per llistar les accions invàlida", + "good_practices_about_admin_password": "Esteu a punt de definir una nova contrasenya d'administrador. La contrasenya ha de tenir un mínim de 8 caràcters; tot i que és de bona pràctica utilitzar una contrasenya més llarga (és a dir una frase de contrasenya) i/o utilitzar diferents tipus de caràcters (majúscules, minúscules, dígits i caràcters especials).", + "hook_exec_failed": "No s'ha pogut executar el script: {path:s}", + "hook_exec_not_terminated": "El script no s'ha acabat correctament: {path:s}", + "hook_json_return_error": "No s'ha pogut llegir el retorn del script {path:s}. Error: {msg:s}. Contingut en brut: {raw_content}", + "hook_list_by_invalid": "Aquesta propietat no es pot utilitzar per llistar els hooks", "hook_name_unknown": "Nom de script « {name:s} » desconegut", "installation_complete": "Instal·lació completada", - "installation_failed": "Ha fallat la instal·lació", + "installation_failed": "Ha fallat alguna cosa amb la instal·lació", "invalid_url_format": "Format d'URL invàlid", "ip6tables_unavailable": "No podeu modificar les ip6tables aquí. O bé sou en un contenidor o bé el vostre nucli no és compatible amb aquesta opció", "iptables_unavailable": "No podeu modificar les iptables aquí. O bé sou en un contenidor o bé el vostre nucli no és compatible amb aquesta opció", - "log_corrupted_md_file": "El fitxer de metadades yaml associat amb els registres està malmès: « {md_file} »\nError: {error}", + "log_corrupted_md_file": "El fitxer de metadades YAML associat amb els registres està malmès: « {md_file} »\nError: {error}", "log_category_404": "La categoria de registres « {category} » no existeix", "log_link_to_log": "El registre complet d'aquesta operació: «{desc}»", "log_help_to_get_log": "Per veure el registre de l'operació « {desc} », utilitzeu l'ordre « yunohost log display {name} »", - "log_link_to_failed_log": "L'operació « {dec} » ha fallat! Per obtenir ajuda, proveïu el registre complete de l'operació clicant aquí", - "log_help_to_get_failed_log": "L'operació « {dec} » ha fallat! Per obtenir ajuda, compartiu el registre complete de l'operació utilitzant l'ordre « yunohost log display {name} --share »", + "log_link_to_failed_log": "No s'ha pogut completar l'operació « {desc} ». Per obtenir ajuda, proveïu el registre complete de l'operació clicant aquí", + "log_help_to_get_failed_log": "No s'ha pogut completar l'operació « {desc} ». Per obtenir ajuda, compartiu el registre complete de l'operació utilitzant l'ordre « yunohost log display {name} --share »", "log_does_exists": "No hi ha cap registre per l'operació amb el nom« {log} », utilitzeu « yunohost log list » per veure tots els registre d'operació disponibles", "log_operation_unit_unclosed_properly": "L'operació no s'ha tancat de forma correcta", "log_app_addaccess": "Afegir accés a « {} »", @@ -263,7 +263,7 @@ "log_domain_remove": "Elimina el domini « {} » de la configuració del sistema", "log_dyndns_subscribe": "Subscriure's a un subdomini YunoHost « {} »", "log_dyndns_update": "Actualitza la IP associada al subdomini YunoHost « {} »", - "log_letsencrypt_cert_install": "Instal·la el certificat Let's Encrypt al domini « {} »", + "log_letsencrypt_cert_install": "Instal·la un certificat Let's Encrypt al domini « {} »", "log_selfsigned_cert_install": "Instal·la el certificat autosignat al domini « {} »", "log_letsencrypt_cert_renew": "Renova el certificat Let's Encrypt de « {} »", "log_service_enable": "Activa el servei « {} »", @@ -278,79 +278,79 @@ "log_tools_upgrade": "Actualitza els paquets del sistema", "log_tools_shutdown": "Apaga el servidor", "log_tools_reboot": "Reinicia el servidor", - "already_up_to_date": "No hi ha res a fer! Tot està al dia!", + "already_up_to_date": "No hi ha res a fer. Tot està actualitzat.", "dpkg_lock_not_available": "No es pot utilitzar aquesta comanda en aquest moment ja que sembla que un altre programa està utilitzant el lock de dpkg (el gestor de paquets del sistema)", "global_settings_setting_security_postfix_compatibility": "Solució de compromís entre compatibilitat i seguretat pel servidor Postfix. Afecta els criptògrafs (i altres aspectes relacionats amb la seguretat)", "ldap_init_failed_to_create_admin": "La inicialització de LDAP no ha pogut crear l'usuari admin", "ldap_initialized": "S'ha iniciat LDAP", "license_undefined": "indefinit", - "mail_alias_remove_failed": "No s'han pogut eliminar els alias del correu «{mail:s}»", - "mail_domain_unknown": "Domini d'adreça de correu «{domain:s}» desconegut", + "mail_alias_remove_failed": "No s'han pogut eliminar els àlies del correu «{mail:s}»", + "mail_domain_unknown": "Domini d'adreça de correu per «{domain:s}» desconegut", "mail_forward_remove_failed": "No s'han pogut eliminar el reenviament de correu «{mail:s}»", - "mailbox_used_space_dovecot_down": "S'ha d'engegar el servei de correu Dovecot per poder obtenir l'espai utilitzat per la bústia de correu", - "mail_unavailable": "Aquesta adreça de correu esta reservada i ha de ser atribuïda automàticament el primer usuari", + "mailbox_used_space_dovecot_down": "S'ha d'engegar el servei de correu Dovecot, per poder obtenir l'espai utilitzat per la bústia de correu", + "mail_unavailable": "Aquesta adreça de correu està reservada i ha de ser atribuïda automàticament el primer usuari", "maindomain_change_failed": "No s'ha pogut canviar el domini principal", "maindomain_changed": "S'ha canviat el domini principal", - "migrate_tsig_end": "La migració cap a hmac-sha512 s'ha acabat", - "migrate_tsig_failed": "Ha fallat la migració del domini dyndns {domain} cap a hmac-sha512, anul·lant les modificacions. Error: {error_code} - {error}", - "migrate_tsig_start": "L'algoritme de generació de claus no es prou segur per a la signatura TSIG del domini «{domain}», començant la migració cap a un de més segur hmac-sha512", - "migrate_tsig_wait": "Esperar 3 minuts per a que el servidor dyndns tingui en compte la nova clau…", + "migrate_tsig_end": "La migració cap a HMAC-SHA-512 s'ha acabat", + "migrate_tsig_failed": "Ha fallat la migració del domini DynDNS «{domain}» cap a HMAC-SHA-512, anul·lant les modificacions. Error: {error_code}, {error}", + "migrate_tsig_start": "L'algoritme de generació de claus no es prou segur per a la signatura TSIG del domini «{domain}», començant la migració cap a un de més segur HMAC-SHA-512", + "migrate_tsig_wait": "Esperant tres minuts per a que el servidor DynDNS tingui en compte la nova clau…", "migrate_tsig_wait_2": "2 minuts…", "migrate_tsig_wait_3": "1 minut…", "migrate_tsig_wait_4": "30 segons…", - "migrate_tsig_not_needed": "Sembla que no s'utilitza cap domini dyndns, no és necessari fer cap migració!", + "migrate_tsig_not_needed": "Sembla que no s'utilitza cap domini DynDNS, no és necessari fer cap migració.", "migration_description_0001_change_cert_group_to_sslcert": "Canvia els permisos del grup dels certificats de «metronome» a «ssl-cert»", - "migration_description_0002_migrate_to_tsig_sha256": "Millora la seguretat de dyndns TSIG utilitzant SHA512 en lloc de MD5", + "migration_description_0002_migrate_to_tsig_sha256": "Millora la seguretat de DynDNS TSIG utilitzant SHA-512 en lloc de MD5", "migration_description_0003_migrate_to_stretch": "Actualització del sistema a Debian Stretch i YunoHost 3.0", "migration_description_0004_php5_to_php7_pools": "Tornar a configurar els pools PHP per utilitzar PHP 7 en lloc de PHP 5", - "migration_description_0005_postgresql_9p4_to_9p6": "Migració de les bases de dades de postgresql 9.4 a 9.6", + "migration_description_0005_postgresql_9p4_to_9p6": "Migració de les bases de dades de PostgreSQL 9.4 a 9.6", "migration_description_0006_sync_admin_and_root_passwords": "Sincronitzar les contrasenyes admin i root", "migration_description_0007_ssh_conf_managed_by_yunohost_step1": "La configuració SSH serà gestionada per YunoHost (pas 1, automàtic)", "migration_description_0008_ssh_conf_managed_by_yunohost_step2": "La configuració SSH serà gestionada per YunoHost (pas 2, manual)", "migration_description_0009_decouple_regenconf_from_services": "Desvincula el mecanisme regen-conf dels serveis", - "migration_description_0010_migrate_to_apps_json": "Elimina la appslists (desfasat) i utilitza la nova llista unificada «apps.json» en el seu lloc", + "migration_description_0010_migrate_to_apps_json": "Elimina les llistes d'aplicacions obsoletes i utilitza la nova llista unificada «apps.json» en el seu lloc", "migration_0003_backward_impossible": "La migració Stretch no és reversible.", "migration_0003_start": "Ha començat la migració a Stretch. Els registres estaran disponibles a {logfile}.", "migration_0003_patching_sources_list": "Modificant el fitxer sources.lists…", "migration_0003_main_upgrade": "Començant l'actualització principal…", - "migration_0003_fail2ban_upgrade": "Començant l'actualització de fail2ban…", + "migration_0003_fail2ban_upgrade": "Començant l'actualització de Fail2Ban…", "migration_0003_restoring_origin_nginx_conf": "El fitxer /etc/nginx/nginx.conf ha estat editat. La migració el tornarà al seu estat original... El fitxer anterior estarà disponible com a {backup_dest}.", - "migration_0003_yunohost_upgrade": "Començant l'actualització del paquet yunohost... La migració acabarà, però l'actualització actual es farà just després. Després de completar aquesta operació, pot ser que us hagueu de tornar a connectar a la web d'administració.", + "migration_0003_yunohost_upgrade": "Començant l'actualització del paquet YunoHost... La migració acabarà, però l'actualització actual es farà just després. Després de completar aquesta operació, pot ser que us hagueu de tornar a connectar a la web d'administració.", "migration_0003_not_jessie": "La distribució Debian actual no és Jessie!", "migration_0003_system_not_fully_up_to_date": "El vostre sistema no està completament actualitzat. S'ha de fer una actualització normal abans de fer la migració a Stretch.", - "migration_0003_still_on_jessie_after_main_upgrade": "Hi ha hagut un problema durant l'actualització principal: el sistema encara està amb Jessie!? Per investigar el problema, mireu el registres a {log}:s…", - "migration_0003_general_warning": "Tingueu en compte que la migració és una operació delicada. Tot i que l'equip de YunoHost a fet els possibles per revisar-la i provar-la, la migració pot provocar errors en parts del sistema o aplicacions.\n\nPer tant, recomanem:\n - Fer una còpia de seguretat de les dades o aplicacions importants. Més informació a https://yunohost.org/backup;\n - Sigueu pacient un cop llençada la migració: en funció de la connexió a internet i el maquinari, pot trigar fins a unes hores per actualitzar-ho tot.\n\nD'altra banda, el port per SMTP, utilitzat per clients de correu externs (com Thunderbird o K9-Mail) ha canviat de 465 (SSL/TLS) a 587 (STARTTLS). L'antic port 465 serà tancat automàticament i el nou port 587 serà obert en el tallafocs. Tots els usuaris *hauran* d'adaptar la configuració dels clients de correu en acord amb aquests canvis!", - "migration_0003_problematic_apps_warning": "Tingueu en compte que s'han detectat les aplicacions, possiblement, problemàtiques següents. Sembla que aquestes no s'han instal·lat des d'una applist o que no estan marcades com a «working». Per conseqüent, no podem garantir que segueixin funcionant després de l'actualització: {problematic_apps}", + "migration_0003_still_on_jessie_after_main_upgrade": "Hi ha hagut un problema durant l'actualització principal: El sistema encara està amb Jessie? Per investigar el problema, mireu el registres a {log}:s…", + "migration_0003_general_warning": "Tingueu en compte que la migració és una operació delicada. L'equip de YunoHost a fet els possibles per revisar-la i provar-la, però la migració pot provocar errors en parts del sistema o aplicacions.\n\nPer tant, es recomana:\n - Fer una còpia de seguretat de les dades o aplicacions importants. Més informació a https://yunohost.org/backup;\n - Sigueu pacient un cop llençada la migració: en funció de la connexió a internet i el maquinari, pot trigar fins a unes hores per actualitzar-ho tot.\n\nD'altra banda, el port per SMTP, utilitzat per clients de correu externs (com Thunderbird o K9-Mail) ha canviat de 465 (SSL/TLS) a 587 (STARTTLS). L'antic port (465) serà tancat automàticament, i el nou port (587) serà obert en el tallafocs. Tots els usuaris *hauran* d'adaptar la configuració dels clients de correu en acord amb aquests canvis.", + "migration_0003_problematic_apps_warning": "Tingueu en compte que s'han detectat les aplicacions, possiblement, problemàtiques següents. Sembla que aquestes no s'han instal·lat des d'una applist, o que no estan marcades com a «working». Per conseqüent, no podem garantir que segueixin funcionant després de l'actualització: {problematic_apps}", "migration_0003_modified_files": "Tingueu en compte que s'han detectat els següents fitxers que han estat modificats manualment i podrien sobreescriure's al final de l'actualització: {manually_modified_files}", - "migration_0005_postgresql_94_not_installed": "Postgresql no està instal·lat en el sistema. No hi ha res per fer!", - "migration_0005_postgresql_96_not_installed": "S'ha trobat Postgresql 9.4 instal·lat, però no Postgresql 9.6!? Alguna cosa estranya a passat en el sistema :( …", - "migration_0005_not_enough_space": "No hi ha prou espai disponible en {path} per fer la migració en aquest moment :(.", + "migration_0005_postgresql_94_not_installed": "PostgreSQL no està instal·lat en el sistema. No hi ha res per fer.", + "migration_0005_postgresql_96_not_installed": "PostgreSQL 9.4 està instal·lat, però no PostgreSQL 9.6? Alguna cosa estranya a passat en el sistema :( …", + "migration_0005_not_enough_space": "Creu espai disponible en {path} per executar la migració.", "migration_0006_disclaimer": "YunoHost esperar que les contrasenyes admin i root estiguin sincronitzades. Fent aquesta migració, la contrasenya root serà reemplaçada per la contrasenya admin.", "migration_0007_cancelled": "YunoHost no ha pogut millorar la gestió de la configuració SSH.", "migration_0007_cannot_restart": "No es pot reiniciar SSH després d'haver intentat cancel·lar la migració numero 6.", "migration_0008_general_disclaimer": "Per millorar la seguretat del servidor, es recomana que sigui YunoHost qui gestioni la configuració SSH. La configuració SSH actual és diferent a la configuració recomanada. Si deixeu que YunoHost ho reconfiguri, la manera de connectar-se al servidor mitjançant SSH canviarà de la següent manera:", - "migration_0008_port": " - la connexió es farà utilitzant el port 22 en lloc del port SSH personalitzat actual. Es pot reconfigurar;", - "migration_0008_root": " - no es podrà connectar com a root a través de SSH. S'haurà d'utilitzar l'usuari admin per fer-ho;", - "migration_0008_dsa": " - es desactivarà la clau DSA. Per tant, es podria haver d'invalidar un missatge esgarrifós del client SSH, i tornar a verificar l'empremta digital del servidor;", + "migration_0008_port": "• La connexió es farà utilitzant el port 22 en lloc del port SSH personalitzat actual. Es pot reconfigurar;", + "migration_0008_root": "• No es podrà connectar com a root a través de SSH. S'haurà d'utilitzar l'usuari admin per fer-ho;", + "migration_0008_dsa": "• Es desactivarà la clau DSA. Per tant, es podria haver d'invalidar un missatge esgarrifós del client SSH, i tornar a verificar l'empremta digital del servidor;", "migration_0008_warning": "Si heu entès els avisos i accepteu que YunoHost sobreescrigui la configuració actual, comenceu la migració. Sinó, podeu saltar-vos la migració, tot i que no està recomanat.", - "migration_0008_no_warning": "No s'han detectat riscs importants per sobreescriure la configuració SSH, però no es pot estar del tot segur ;)! Si accepteu que YunoHost sobreescrigui la configuració actual, comenceu la migració. Sinó, podeu saltar-vos la migració, tot i que no està recomanat.", - "migration_0009_not_needed": "Sembla que ja s'ha fet aquesta migració? Ometent.", + "migration_0008_no_warning": "No s'han identificat riscs importants per sobreescriure la configuració SSH, però no es pot estar del tot segur ;)! Executetu la migració per sobreescriure-la. Sinó, podeu saltar-vos la migració, tot i que no està recomanat.", + "migration_0009_not_needed": "Sembla que ja s'ha fet aquesta migració… (?) Ometent.", "migrations_backward": "Migració cap enrere.", "migrations_bad_value_for_target": "Nombre invàlid pel paràmetre target, els nombres de migració disponibles són 0 o {}", "migrations_cant_reach_migration_file": "No s'ha pogut accedir als fitxers de migració al camí %s", "migrations_current_target": "La migració objectiu és {}", "migrations_error_failed_to_load_migration": "ERROR: no s'ha pogut carregar la migració {number} {name}", "migrations_forward": "Migració endavant", - "migrations_list_conflict_pending_done": "No es pot utilitzar --previous i --done al mateix temps.", - "migrations_loading_migration": "Carregant la migració {number} {name}…", - "migrations_migration_has_failed": "La migració {number} {name} ha fallat amb l'excepció {exception}, cancel·lant", + "migrations_list_conflict_pending_done": "No es pot utilitzar «--previous» i «--done» al mateix temps.", + "migrations_loading_migration": "Carregant la migració {id}…", + "migrations_migration_has_failed": "La migració {id} ha fallat, cancel·lant. Error: {exception}", "migrations_no_migrations_to_run": "No hi ha cap migració a fer", "migrations_show_currently_running_migration": "Fent la migració {number} {name}…", "migrations_show_last_migration": "L'última migració feta és {}", - "migrations_skip_migration": "Saltant migració {number} {name}…", + "migrations_skip_migration": "Saltant migració {id}…", "migrations_success": "S'ha completat la migració {number} {name} amb èxit!", - "migrations_to_be_ran_manually": "La migració {number} {name} s'ha de fer manualment. Aneu a Eines > Migracions a la interfície admin, o executeu «yunohost tools migrations migrate».", - "migrations_need_to_accept_disclaimer": "Per fer la migració {number} {name}, heu d'acceptar aquesta clàusula de no responsabilitat:\n---\n{disclaimer}\n---\nSi accepteu fer la migració, torneu a executar l'ordre amb l'opció --accept-disclaimer.", + "migrations_to_be_ran_manually": "La migració {id} s'ha de fer manualment. Aneu a Eines → Migracions a la interfície admin, o executeu «yunohost tools migrations migrate».", + "migrations_need_to_accept_disclaimer": "Per fer la migració {id}, heu d'acceptar aquesta clàusula de no responsabilitat:\n---\n{disclaimer}\n---\nSi accepteu fer la migració, torneu a executar l'ordre amb l'opció «--accept-disclaimer».", "monitor_disabled": "El monitoratge del servidor ha estat desactivat", "monitor_enabled": "El monitoratge del servidor ha estat activat", "monitor_glances_con_failed": "No s'ha pogut connectar al servidor Glances", @@ -384,18 +384,18 @@ "pattern_firstname": "Ha de ser un nom vàlid", "pattern_lastname": "Ha de ser un cognom vàlid", "pattern_listname": "Ha d'estar compost per caràcters alfanumèrics i guió baix exclusivament", - "pattern_mailbox_quota": "Ha de ser una mida amb el sufix b/k/M/G/T o 0 per desactivar la quota", + "pattern_mailbox_quota": "Ha de ser una mida amb el sufix b/k/M/G/T o 0 per no tenir quota", "pattern_password": "Ha de tenir un mínim de 3 caràcters", "pattern_port": "Ha de ser un número de port vàlid (i.e. 0-65535)", "pattern_port_or_range": "Ha de ser un número de port vàlid (i.e. 0-65535) o un interval de ports (ex. 100:200)", "pattern_positive_number": "Ha de ser un nombre positiu", "pattern_username": "Ha d'estar compost per caràcters alfanumèrics en minúscula i guió baix exclusivament", - "pattern_password_app": "Les contrasenyes no haurien de tenir els següents caràcters: {forbidden_chars}", + "pattern_password_app": "Les contrasenyes no poden de tenir els següents caràcters: {forbidden_chars}", "port_already_closed": "El port {port:d} ja està tancat per les connexions {ip_version:s}", "port_already_opened": "El port {port:d} ja està obert per les connexions {ip_version:s}", "port_available": "El port {port:d} està disponible", "port_unavailable": "El port {port:d} no està disponible", - "recommend_to_add_first_user": "La post instal·lació s'ha acabat, però YunoHost necessita com a mínim un usuari per funcionar correctament, hauríeu d'afegir un usuari executant «yunohost user create $username» o amb la interfície d'administració.", + "recommend_to_add_first_user": "La post instal·lació s'ha acabat, però YunoHost necessita com a mínim un usuari per funcionar correctament, hauríeu d'afegir un usuari executant «yunohost user create »; o fer-ho des de la interfície d'administració.", "regenconf_file_backed_up": "S'ha guardat una còpia de seguretat del fitxer de configuració «{conf}» a «{backup}»", "regenconf_file_copy_failed": "No s'ha pogut copiar el nou fitxer de configuració «{new}» a «{conf}»", "regenconf_file_kept_back": "S'espera que el fitxer de configuració «{conf}» sigui suprimit per regen-conf (categoria {category}) però s'ha mantingut.", @@ -406,26 +406,26 @@ "regenconf_file_updated": "El fitxer de configuració «{conf}» ha estat actualitzat", "regenconf_now_managed_by_yunohost": "El fitxer de configuració «{conf}» serà gestionat per YunoHost a partir d'ara (categoria {category}).", "regenconf_up_to_date": "La configuració ja està al dia per la categoria «{category}»", - "regenconf_updated": "La configuració ha estat actualitzada per la categoria «{category}»", + "regenconf_updated": "La configuració per la categoria «{category}» ha estat actualitzada", "regenconf_would_be_updated": "La configuració hagués estat actualitzada per la categoria «{category}»", "regenconf_dry_pending_applying": "Verificació de la configuració pendent que s'hauria d'haver aplicat per la categoria «{category}»…", "regenconf_failed": "No s'ha pogut regenerar la configuració per la/les categoria/es : {categories}", "regenconf_pending_applying": "Aplicació de la configuració pendent per la categoria «{category}»…", "restore_action_required": "S'ha d'especificar quelcom a restaurar", - "restore_already_installed_app": "Ja hi ha una aplicació instal·lada amb l'id «{app:s}»", + "restore_already_installed_app": "Una aplicació amb la ID «{app:s}» ja està instal·lada", "restore_app_failed": "No s'ha pogut restaurar l'aplicació «{app:s}»", "restore_cleaning_failed": "No s'ha pogut netejar el directori temporal de restauració", "restore_complete": "Restauració completada", "restore_confirm_yunohost_installed": "Esteu segur de voler restaurar un sistema ja instal·lat? [{answers:s}]", "restore_extracting": "Extracció dels fitxers necessaris de l'arxiu…", "restore_failed": "No s'ha pogut restaurar el sistema", - "restore_hook_unavailable": "L'script de restauració «{part:s}» no està disponible en el sistema i tampoc és en l'arxiu", - "restore_may_be_not_enough_disk_space": "Sembla que no hi ha prou espai disponible en el disc (espai lliure: {free_space:d} B, espai necessari: {needed_space:d} B, marge de seguretat: {margin:d} B)", + "restore_hook_unavailable": "El script de restauració «{part:s}» no està disponible en el sistema i tampoc és en l'arxiu", + "restore_may_be_not_enough_disk_space": "Sembla que no hi ha prou espai disponible en el sistema (lliure: {free_space:d} B, espai necessari: {needed_space:d} B, marge de seguretat: {margin:d} B)", "restore_mounting_archive": "Muntatge de l'arxiu a «{path:s}»", - "restore_not_enough_disk_space": "No hi ha prou espai disponible en el disc (espai lliure: {free_space:d} B, espai necessari: {needed_space:d} B, marge de seguretat: {margin:d} B)", + "restore_not_enough_disk_space": "No hi ha prou espai disponible (espai: {free_space:d} B, espai necessari: {needed_space:d} B, marge de seguretat: {margin:d} B)", "restore_nothings_done": "No s'ha restaurat res", "restore_removing_tmp_dir_failed": "No s'ha pogut eliminar un directori temporal antic", - "restore_running_app_script": "Execució de l'script de restauració de l'aplicació «{app:s}»…", + "restore_running_app_script": "Restaurant l'aplicació «{app:s}»…", "restore_running_hooks": "Execució dels hooks de restauració…", "restore_system_part_failed": "No s'ha pogut restaurar la part «{part:s}» del sistema", "root_password_desynchronized": "S'ha canviat la contrasenya d'administració, però YunoHost no ha pogut propagar-ho cap a la contrasenya root!", @@ -439,24 +439,24 @@ "service_already_started": "Ja s'ha iniciat el servei «{service:s}»", "service_already_stopped": "Ja s'ha aturat el servei «{service:s}»", "service_cmd_exec_failed": "No s'ha pogut executar l'ordre «{command:s}»", - "service_description_avahi-daemon": "permet accedir al servidor via yunohost.local en la xarxa local", - "service_description_dnsmasq": "gestiona la resolució del nom de domini (DNS)", - "service_description_dovecot": "permet als clients de correu accedir/recuperar correus (via IMAP i POP3)", - "service_description_fail2ban": "protegeix contra els atacs de força bruta i a altres atacs provinents d'Internet", - "service_description_glances": "monitora la informació del sistema en el servidor", - "service_description_metronome": "gestiona els comptes de missatgeria instantània XMPP", - "service_description_mysql": "guarda les dades de les aplicacions (base de dades SQL)", - "service_description_nginx": "serveix o permet l'accés a totes les pàgines web allotjades en el servidor", - "service_description_nslcd": "gestiona les connexions shell dels usuaris YunoHost", - "service_description_php7.0-fpm": "executa les aplicacions escrites en PHP amb nginx", - "service_description_postfix": "utilitzat per enviar i rebre correus", - "service_description_redis-server": "una base de dades especialitzada per l'accés ràpid a dades, files d'espera i comunicació entre programes", - "service_description_rmilter": "verifica diferents paràmetres en els correus", - "service_description_rspamd": "filtra el correu brossa, i altres funcionalitats relacionades al correu", - "service_description_slapd": "guarda el usuaris, dominis i informació relacionada", - "service_description_ssh": "permet la connexió remota al servidor via terminal (protocol SSH)", - "service_description_yunohost-api": "gestiona les interaccions entre la interfície web de YunoHost i el sistema", - "service_description_yunohost-firewall": "gestiona els ports de connexió oberts i tancats als serveis", + "service_description_avahi-daemon": "Permet accedir al servidor via «yunohost.local» en la xarxa local", + "service_description_dnsmasq": "Gestiona la resolució del nom de domini (DNS)", + "service_description_dovecot": "Permet als clients de correu accedir/recuperar correus (via IMAP i POP3)", + "service_description_fail2ban": "Protegeix contra els atacs de força bruta i a altres atacs provinents d'Internet", + "service_description_glances": "Monitora la informació del sistema en el servidor", + "service_description_metronome": "Gestiona els comptes de missatgeria instantània XMPP", + "service_description_mysql": "Guarda les dades de les aplicacions (base de dades SQL)", + "service_description_nginx": "Serveix o permet l'accés a totes les pàgines web allotjades en el servidor", + "service_description_nslcd": "Gestiona les connexions shell dels usuaris YunoHost", + "service_description_php7.0-fpm": "Executa les aplicacions escrites en PHP amb NGINX", + "service_description_postfix": "Utilitzat per enviar i rebre correus", + "service_description_redis-server": "Una base de dades especialitzada per l'accés ràpid a dades, files d'espera i comunicació entre programes", + "service_description_rmilter": "Verifica diferents paràmetres en els correus", + "service_description_rspamd": "Filtra el correu brossa, i altres funcionalitats relacionades amb el correu", + "service_description_slapd": "Guarda el usuaris, dominis i informació relacionada", + "service_description_ssh": "Permet la connexió remota al servidor via terminal (protocol SSH)", + "service_description_yunohost-api": "Gestiona les interaccions entre la interfície web de YunoHost i el sistema", + "service_description_yunohost-firewall": "Gestiona els ports de connexió oberts i tancats als serveis", "service_disable_failed": "No s'han pogut deshabilitar el servei «{service:s}»\n\nRegistres recents: {logs:s}", "service_disabled": "S'ha deshabilitat el servei {service:s}", "service_enable_failed": "No s'ha pogut activar el servei «{service:s}»\n\nRegistres recents: {log:s}", @@ -479,26 +479,26 @@ "service_unknown": "Servei «{service:s}» desconegut", "ssowat_conf_generated": "S'ha generat la configuració SSOwat", "ssowat_conf_updated": "S'ha actualitzat la configuració SSOwat", - "ssowat_persistent_conf_read_error": "Error en llegir la configuració persistent de SSOwat: {error:s}. Modifiqueu el fitxer /etc/ssowat/conf.json.persistent per arreglar la sintaxi JSON", - "ssowat_persistent_conf_write_error": "Error guardant la configuració persistent de SSOwat: {error:s}. Modifiqueu el fitxer /etc/ssowat/conf.json.persistent per arreglar la sintaxi JSON", + "ssowat_persistent_conf_read_error": "No s'ha pogut llegir la configuració persistent de SSOwat: {error:s}. Modifiqueu el fitxer /etc/ssowat/conf.json.persistent per arreglar la sintaxi JSON", + "ssowat_persistent_conf_write_error": "No s'ha pogut guardar la configuració persistent de SSOwat: {error:s}. Modifiqueu el fitxer /etc/ssowat/conf.json.persistent per arreglar la sintaxi JSON", "system_upgraded": "S'ha actualitzat el sistema", - "system_username_exists": "El nom d'usuari ja existeix en els usuaris de sistema", - "this_action_broke_dpkg": "Aquesta acció a trencat dpkg/apt (els gestors de paquets del sistema)… Podeu intentar resoldre el problema connectant-vos amb SSH i executant «sudo dpkg --configure -a».", - "tools_upgrade_at_least_one": "Especifiqueu --apps O --system", + "system_username_exists": "El nom d'usuari ja existeix en la llista d'usuaris de sistema", + "this_action_broke_dpkg": "Aquesta acció a trencat dpkg/APT (els gestors de paquets del sistema)… Podeu intentar resoldre el problema connectant-vos amb SSH i executant «sudo dpkg --configure -a».", + "tools_upgrade_at_least_one": "Especifiqueu «--apps», o «--system»", "tools_upgrade_cant_both": "No es poden actualitzar tant el sistema com les aplicacions al mateix temps", "tools_upgrade_cant_hold_critical_packages": "No es poden mantenir els paquets crítics…", "tools_upgrade_cant_unhold_critical_packages": "No es poden deixar de mantenir els paquets crítics…", "tools_upgrade_regular_packages": "Actualitzant els paquets «normals» (no relacionats amb YunoHost)…", "tools_upgrade_regular_packages_failed": "No s'han pogut actualitzar els paquets següents: {packages_list}", "tools_upgrade_special_packages": "Actualitzant els paquets «especials» (relacionats amb YunoHost)…", - "tools_upgrade_special_packages_explanation": "Aquesta acció s'acabarà, però l'actualització especial continuarà en segon pla. No comenceu cap altra acció al servidor en els pròxims ~10 minuts (depèn de la velocitat del maquinari). Un cop acabat, pot ser que us hagueu de tornar a connectar a la interfície d'administració. Els registres de l'actualització estaran disponibles a Eines > Registres (a la interfície d'administració) o amb «yunohost log list» (a la línia d'ordres).", - "tools_upgrade_special_packages_completed": "Actualització dels paquets YunoHost acabada!\nPremeu [Enter] per tornar a la línia d'ordres", + "tools_upgrade_special_packages_explanation": "Aquesta acció s'acabarà, però l'actualització especial continuarà en segon pla. No comenceu cap altra acció al servidor en els pròxims ~10 minuts (depèn de la velocitat del maquinari). Un cop acabat, pot ser que us hagueu de tornar a connectar a la interfície d'administració. Els registres de l'actualització estaran disponibles a Eines → Registres (a la interfície d'administració) o amb «yunohost log list» (des de la línia d'ordres).", + "tools_upgrade_special_packages_completed": "Actualització dels paquets YunoHost acabada.\nPremeu [Enter] per tornar a la línia d'ordres", "unbackup_app": "L'aplicació «{app:s}» no serà guardada", "unexpected_error": "Hi ha hagut un error inesperat: {error}", "unit_unknown": "Unitat desconeguda «{unit:s}»", "unlimit": "Sense quota", "unrestore_app": "L'aplicació «{app:s} no serà restaurada", - "update_apt_cache_failed": "No s'ha pogut actualitzar la memòria cau d'APT (el gestor de paquets de Debian). Aquí teniu les línies de sources.list que poden ajudar-vos a identificar les línies problemàtiques:\n{sourceslist}", + "update_apt_cache_failed": "No s'ha pogut actualitzar la memòria cau d'APT (el gestor de paquets de Debian). Aquí teniu les línies de sources.list, que poden ajudar-vos a identificar les línies problemàtiques:\n{sourceslist}", "update_apt_cache_warning": "Hi ha hagut errors al actualitzar la memòria cau d'APT (el gestor de paquets de Debian). Aquí teniu les línies de sources.list que poden ajudar-vos a identificar les línies problemàtiques:\n{sourceslist}", "updating_apt_cache": "Obtenció de les actualitzacions disponibles per als paquets del sistema…", "updating_app_lists": "Obtenció de les actualitzacions disponibles per a les aplicacions…", @@ -507,21 +507,21 @@ "upnp_dev_not_found": "No s'ha trobat cap dispositiu UPnP", "upnp_disabled": "S'ha desactivat UPnP", "upnp_enabled": "S'ha activat UPnP", - "upnp_port_open_failed": "No s'han pogut obrir els ports UPnP", + "upnp_port_open_failed": "No s'ha pogut obrir el port UPnP", "user_created": "S'ha creat l'usuari", - "user_creation_failed": "No s'ha pogut crear l'usuari", + "user_creation_failed": "No s'ha pogut crear l'usuari {user}: {error}", "user_deleted": "S'ha suprimit l'usuari", - "user_deletion_failed": "No s'ha pogut suprimir l'usuari", - "user_home_creation_failed": "No s'ha pogut crear la carpeta personal («home») de l'usuari", + "user_deletion_failed": "No s'ha pogut suprimir l'usuari {user}: {error}", + "user_home_creation_failed": "No s'ha pogut crear la carpeta personal «home» per l'usuari", "user_info_failed": "No s'ha pogut obtenir la informació de l'usuari", "user_unknown": "Usuari desconegut: {user:s}", - "user_update_failed": "No s'ha pogut actualitzar l'usuari", - "user_updated": "S'ha actualitzat l'usuari", + "user_update_failed": "No s'ha pogut actualitzar l'usuari {user}: {error}", + "user_updated": "S'ha canviat la informació de l'usuari", "users_available": "Usuaris disponibles:", "yunohost_already_installed": "YunoHost ja està instal·lat", "yunohost_ca_creation_failed": "No s'ha pogut crear l'autoritat de certificació", "yunohost_ca_creation_success": "S'ha creat l'autoritat de certificació local.", - "yunohost_configured": "S'ha configurat YunoHost", + "yunohost_configured": "YunoHost està configurat", "yunohost_installing": "Instal·lació de YunoHost…", "yunohost_not_installed": "YunoHost no està instal·lat o no està instal·lat correctament. Executeu «yunohost tools postinstall»", "apps_permission_not_found": "No s'ha trobat cap permís per les aplicacions instal·lades", @@ -534,14 +534,14 @@ "group_already_disallowed": "El grup «{group:s}» ja té els permisos «{permission:s}» desactivats per l'aplicació «{app:s}»", "group_name_already_exist": "El grup {name:s} ja existeix", "group_created": "S'ha creat el grup «{group}»", - "group_creation_failed": "No s'ha pogut crear el grup «{group}»", + "group_creation_failed": "No s'ha pogut crear el grup «{group}»: {error}", "group_deleted": "S'ha eliminat el grup «{group}»", - "group_deletion_failed": "No s'ha pogut eliminar el grup «{group}»", + "group_deletion_failed": "No s'ha pogut eliminar el grup «{group}»: {error}", "group_deletion_not_allowed": "El grup {group:s} no es pot eliminar manualment.", "group_info_failed": "Ha fallat la informació del grup", "group_unknown": "Grup {group:s} desconegut", "group_updated": "S'ha actualitzat el grup «{group}»", - "group_update_failed": "No s'ha pogut actualitzat el grup «{group}»", + "group_update_failed": "No s'ha pogut actualitzat el grup «{group}»: {error}", "log_permission_add": "Afegir el permís «{}» per l'aplicació «{}»", "log_permission_remove": "Suprimir el permís «{}»", "log_permission_update": "Actualitzar el permís «{}» per l'aplicació «{}»", @@ -550,31 +550,31 @@ "log_user_group_update": "Actualitzar grup «{}»", "log_user_permission_add": "Actualitzar el permís «{}»", "log_user_permission_remove": "Actualitzar el permís «{}»", - "mailbox_disabled": "La bústia de correu està desactivada per als usuaris {user:s}", + "mailbox_disabled": "La bústia de correu està desactivada per al usuari {user:s}", "migration_description_0011_setup_group_permission": "Configurar el grup d'usuaris i els permisos per les aplicacions i els serveis", "migration_0011_backup_before_migration": "Creant una còpia de seguretat de la base de dades LDAP i la configuració de les aplicacions abans d'efectuar la migració.", "migration_0011_can_not_backup_before_migration": "No s'ha pogut fer la còpia de seguretat abans de la migració. No s'ha pogut fer la migració. Error: {error:s}", "migration_0011_create_group": "Creant un grup per a cada usuari…", "migration_0011_done": "Migració completa. Ja podeu gestionar grups d'usuaris.", - "migration_0011_LDAP_config_dirty": "Sembla que heu modificat manualment la configuració LDAP. Per fer aquesta migració s'ha d'actualitzar la configuració LDAP.\nGuardeu la configuració actual, reinicieu la configuració original amb l'ordre «yunohost tools regen-conf -f» i torneu a intentar la migració", + "migration_0011_LDAP_config_dirty": "Sembla que heu modificat manualment la configuració LDAP. Per fer aquesta migració s'ha d'actualitzar la configuració LDAP.\nGuardeu la configuració actual, reinicieu la configuració original executant l'ordre «yunohost tools regen-conf -f» i torneu a intentar la migració", "migration_0011_LDAP_update_failed": "Ha fallat l'actualització de LDAP. Error: {error:s}", "migration_0011_migrate_permission": "Fent la migració dels permisos de la configuració de les aplicacions a LDAP…", - "migration_0011_migration_failed_trying_to_rollback": "La migració ha fallat … s'intenta tornar el sistema a l'estat anterior.", + "migration_0011_migration_failed_trying_to_rollback": "La migració ha fallat… s'intenta tornar el sistema a l'estat anterior.", "migration_0011_rollback_success": "S'ha tornat el sistema a l'estat anterior.", "migration_0011_update_LDAP_database": "Actualitzant la base de dades LDAP…", "migration_0011_update_LDAP_schema": "Actualitzant l'esquema LDAP…", "need_define_permission_before": "Heu de tornar a redefinir els permisos utilitzant «yunohost user permission add -u USER» abans d'eliminar un grup permès", "permission_already_clear": "Ja s'ha donat el permís «{permission:s}» per l'aplicació {app:s}", - "permission_already_exist": "El permís «{permission:s}» ja existeix per l'aplicació {app:s}", - "permission_created": "S'ha creat el permís «{permission:s}» per l'aplicació {app:s}", - "permission_creation_failed": "La creació del permís ha fallat", - "permission_deleted": "S'ha eliminat el permís «{permission:s}» per l'aplicació {app:s}", - "permission_deletion_failed": "L'eliminació del permís «{permission:s}» per l'aplicació {app:s} ha fallat", - "permission_not_found": "No s'ha trobat el permís «{permission:s}» per l'aplicació {app:s}", + "permission_already_exist": "El permís «{permission:s}» ja existeix", + "permission_created": "S'ha creat el permís «{permission:s}»", + "permission_creation_failed": "No s'ha pogut crear el permís «{permission}»: {error}", + "permission_deleted": "S'ha eliminat el permís «{permission:s}»", + "permission_deletion_failed": "No s'ha pogut eliminar el permís «{permission:s}»: {error}", + "permission_not_found": "No s'ha trobat el permís «{permission:s}»", "permission_name_not_valid": "El nom del permís «{permission:s}» no és vàlid", - "permission_update_failed": "L'actualització del permís ha fallat", + "permission_update_failed": "No s'ha pogut actualitzar el permís «{permission}»: {error}", "permission_generated": "S'ha actualitzat la base de dades del permís", - "permission_updated": "S'ha actualitzat el permís «{permission:s}» per l'aplicació {app:s}", + "permission_updated": "S'ha actualitzat el permís «{permission:s}»", "permission_update_nothing_to_do": "No hi ha cap permís per actualitzar", "remove_main_permission_not_allowed": "No es pot eliminar el permís principal", "remove_user_of_group_not_allowed": "No es pot eliminar l'usuari {user:s} del grup {group:s}", @@ -582,5 +582,38 @@ "tools_update_failed_to_app_fetchlist": "No s'ha pogut actualitzar la llista d'aplicacions de YunoHost a causa de: {error}", "user_already_in_group": "L'usuari {user:s} ja és en el grup {group:s}", "user_not_in_group": "L'usuari {user:s} no és en el grup {group:s}", - "migration_description_0012_postgresql_password_to_md5_authentication": "Força l'autenticació postgresql a fer servir md5 per a les connexions locals" + "migration_description_0012_postgresql_password_to_md5_authentication": "Força l'autenticació PostgreSQL a fer servir MD5 per a les connexions locals", + "app_full_domain_unavailable": "Aquesta aplicació requereix un domini sencer per ser instal·lada, però ja hi ha altres aplicacions instal·lades al domini «{domain}». Una possible solució és afegir i utilitzar un subdomini dedicat a aquesta aplicació.", + "migrations_not_pending_cant_skip": "Aquestes migracions no estan pendents, així que no poden ser omeses: {ids}", + "app_action_broke_system": "Aquesta acció sembla haver trencat els següents serveis importants: {services}", + "log_permission_urls": "Actualitzar les URLs relacionades amb el permís «{}»", + "log_user_group_create": "Crear grup «{}»", + "log_user_permission_update": "Actualitzar els accessos per al permís «{}»", + "log_user_permission_reset": "Restablir el permís «{}»", + "permission_already_disallowed": "El grup «{group}» ja té el permís «{permission}» desactivat", + "migrations_already_ran": "Aquestes migracions ja s'han fet: {ids}", + "migrations_dependencies_not_satisfied": "No s'ha pogut executar la migració {id} perquè s'han d'executar primer les següents migracions: {dependencies_id}", + "migrations_failed_to_load_migration": "No s'ha pogut carregar la migració {id}: {error}", + "migrations_exclusive_options": "«--auto», «--skip», i «--force-rerun» són opcions mútuament excloents.", + "migrations_must_provide_explicit_targets": "Heu de proporcionar objectius explícits al utilitzar «--skip» o «--force-rerun»", + "migrations_no_such_migration": "No hi ha cap migració anomenada {id}", + "migrations_pending_cant_rerun": "Aquestes migracions encara estan pendents, així que no es poden tornar a executar: {ids}", + "migrations_running_forward": "Executant la migració {id}…", + "migrations_success_forward": "Migració {id} completada", + "apps_already_up_to_date": "Ja estan actualitzades totes les aplicacions", + "dyndns_provider_unreachable": "No s'ha pogut connectar amb el proveïdor Dyndns {provider}: o el vostre YunoHost no està ben connectat a Internet o el servidor dynette està caigut.", + "operation_interrupted": "S'ha interromput manualment l'operació?", + "group_already_exist": "El grup {group} ja existeix", + "group_already_exist_on_system": "El grup {group} ja existeix en els grups del sistema", + "group_cannot_be_edited": "El grup {group} no es pot editar manualment.", + "group_cannot_be_deleted": "El grup {group} no es pot eliminar manualment.", + "group_user_already_in_group": "L'usuari {user} ja està en el grup {group}", + "group_user_not_in_group": "L'usuari {user} no està en el grup {group}", + "log_permission_create": "Crear el permís «{}»", + "log_permission_delete": "Eliminar el permís «{}»", + "migration_0011_failed_to_remove_stale_object": "No s'ha pogut eliminar l'objecte obsolet {dn}: {error}", + "permission_already_allowed": "El grup «{group}» ja té el permís «{permission}» activat", + "permission_cannot_remove_main": "No es permet eliminar un permís principal", + "user_already_exists": "L'usuari {user} ja existeix", + "app_upgrade_stopped": "S'ha aturat l'actualització de totes les aplicacions per prevenir els possibles danys ja que hi ha hagut un error en l'actualització de l'anterior aplicació" } From 3bd90c63b3ce9a352c74c295ef056cd650429741 Mon Sep 17 00:00:00 2001 From: advocatux Date: Mon, 14 Oct 2019 17:43:39 +0000 Subject: [PATCH 0331/3170] Translated using Weblate (Spanish) Currently translated at 100.0% (556 of 556 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/es/ --- locales/es.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/locales/es.json b/locales/es.json index 80b17673a..4f136460c 100644 --- a/locales/es.json +++ b/locales/es.json @@ -626,5 +626,7 @@ "permission_already_disallowed": "El grupo «{group}» ya tiene el permiso «{permission}» desactivado", "permission_cannot_remove_main": "No está permitido eliminar un permiso principal", "user_already_exists": "El usuario {user} ya existe", - "app_full_domain_unavailable": "Lamentablemente esta aplicación necesita un dominio completo para ser instalada pero ya hay otras aplicaciones instaladas en el dominio «{domain}». Una solución posible es añadir y usar un subdominio dedicado a esta aplicación." + "app_full_domain_unavailable": "Lamentablemente esta aplicación necesita un dominio completo para ser instalada pero ya hay otras aplicaciones instaladas en el dominio «{domain}». Una solución posible es añadir y usar un subdominio dedicado a esta aplicación.", + "app_install_failed": "No se pudo instalar {app}", + "app_install_script_failed": "Ha ocurrido un error en el guión de instalación de la aplicación" } From 57af2c7c45a3c621a7e52359e5e8fb11a1e608f8 Mon Sep 17 00:00:00 2001 From: xaloc33 Date: Mon, 14 Oct 2019 20:51:25 +0000 Subject: [PATCH 0332/3170] Translated using Weblate (Catalan) Currently translated at 100.0% (556 of 556 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 1476d5fb5..592bc4592 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -615,5 +615,7 @@ "permission_already_allowed": "El grup «{group}» ja té el permís «{permission}» activat", "permission_cannot_remove_main": "No es permet eliminar un permís principal", "user_already_exists": "L'usuari {user} ja existeix", - "app_upgrade_stopped": "S'ha aturat l'actualització de totes les aplicacions per prevenir els possibles danys ja que hi ha hagut un error en l'actualització de l'anterior aplicació" + "app_upgrade_stopped": "S'ha aturat l'actualització de totes les aplicacions per prevenir els possibles danys ja que hi ha hagut un error en l'actualització de l'anterior aplicació", + "app_install_failed": "No s'ha pogut instal·lar {app}", + "app_install_script_failed": "Hi ha hagut un error en el script d'instal·lació de l'aplicació" } From a74ba4f112a824e15fc9aaac49470ff4d532df8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Wed, 16 Oct 2019 00:03:50 +0000 Subject: [PATCH 0333/3170] services required, the action that failed --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 7fcaf0d04..c2aa0191b 100644 --- a/locales/en.json +++ b/locales/en.json @@ -6,7 +6,7 @@ "admin_password_changed": "The administration password got changed", "admin_password_too_long": "Please choose a password shorter than 127 characters", "already_up_to_date": "Nothing to do. Everything is already up-to-date.", - "app_action_cannot_be_ran_because_required_services_down": "Start all required services to run this app. Try to restart the following ones (and possibly investigate why they are down): {services}", + "app_action_cannot_be_ran_because_required_services_down": "Start all services required to run the action that failed. Try restarting the following ones (and possibly investigate why they are down): {services}", "app_action_broke_system": "This action seem to have broke these important services: {services}", "app_already_installed": "{app:s} is already installed", "app_already_installed_cant_change_url": "This app is already installed. The URL cannot be changed just by this function. Look into `app changeurl` if it's available.", From 77960fd40535df3ab7e9f5f19e815742b5bcb502 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 16 Oct 2019 16:40:26 +0200 Subject: [PATCH 0334/3170] Create FUNDING.yml --- .github/FUNDING.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..c3b460087 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,4 @@ +# These are supported funding model platforms + +custom: https://donate.yunohost.org +liberapay: YunoHost From 6be15a62880338c007bb426007bfba626112e5bf Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 16 Oct 2019 17:32:19 +0200 Subject: [PATCH 0335/3170] Simplify the README, add cool shiny logo, badges and screenshots --- README.md | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index b860c27d7..5fb72e260 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,27 @@ +# YunoHost + [![Build status](https://travis-ci.org/YunoHost/yunohost.svg?branch=stretch-unstable)](https://travis-ci.org/YunoHost/yunohost) [![GitHub license](https://img.shields.io/github/license/YunoHost/yunohost)](https://github.com/YunoHost/yunohost/blob/stretch-unstable/LICENSE) +![Mastodon Follow](https://img.shields.io/mastodon/follow/28084) -# YunoHost core +This repository corresponds to the core code of YunoHost, mainly written in Python and Bash. -This repository is the core of YunoHost code. +You can learn more about what's YunoHost and its features [here](https://yunohost.org/#/whatsyunohost)! - [Project website](https://yunohost.org) -- [Bugtracker](https://github.com/YunoHost/issues). +- [Installation documentation](https://yunohost.org/install) +- [Issue tracker](https://github.com/YunoHost/issues) + +# Screenshots + +Webadmin ([Yunohost-Admin](https://github.com/YunoHost/yunohost-admin)) | Single-sign on user portal ([SSOwat](https://github.com/YunoHost/ssowat)) +--- | --- +![](https://raw.githubusercontent.com/YunoHost/doc/master/images/webadmin.png) | ![](https://raw.githubusercontent.com/YunoHost/doc/master/images/user_panel.png) + ## Contributing -- You can develop on this repository using [ynh-dev](https://github.com/YunoHost/ynh-dev) with `use-git` sub-command. -- On this repository we are [following this workflow](https://yunohost.org/#/build_system_en): `stable ← testing ← unstable ← your_branch`. -- Note: If you modify Python scripts, you will have to modifiy the actions map. +- You can learn how to get started with developing on YunoHost by reading [this piece of documentation](https://yunohost.org/dev). - You can help translate YunoHost on our [translation platform](https://translate.yunohost.org/engage/yunohost/?utm_source=widget) Translation status @@ -21,7 +30,7 @@ This repository is the core of YunoHost code. ## Repository content - [YunoHost core Python 2.7 scripts](./src/yunohost). -- [An actionsmap](./data/actionsmap/yunohost.yml) used by moulinette. +- [An actionsmap](./data/actionsmap/yunohost.yml) describing the CLI and API - [Services configuration templates](./data/templates). - [Hooks](./data/hooks). - [Locales](./locales) for translations of `yunohost` command. @@ -32,17 +41,10 @@ This repository is the core of YunoHost code. ## How does it work? - Python core scripts are accessible through two interfaces thanks to the [moulinette framework](https://github.com/YunoHost/moulinette): - - [CLI](https://en.wikipedia.org/wiki/Command-line_interface) for `yunohost` command. - - [API](https://en.wikipedia.org/wiki/Application_programming_interface) for [web administration module](https://github.com/YunoHost/yunohost-admin) (other modules could be implemented). + - the [CLI](https://en.wikipedia.org/wiki/Command-line_interface) corresponding to the `yunohost` command. + - the [API](https://en.wikipedia.org/wiki/Application_programming_interface) used by the [web administration interface](https://github.com/YunoHost/yunohost-admin) (other interfaces could be implemented). - You can find more details about how YunoHost works on this [documentation (in French)](https://yunohost.org/#/package_list_fr). -## Dependencies - -- [Python 2.7](https://www.python.org/download/releases/2.7) -- [Moulinette](https://github.com/YunoHost/moulinette) -- [Bash](https://www.gnu.org/software/bash/bash.html) -- [Debian Stretch](https://www.debian.org/releases/stretch) - ## License -As [other components of YunoHost core code](https://yunohost.org/#/faq_en), this repository is licensed GNU AGPL v3. +As [other components of YunoHost](https://yunohost.org/#/faq_en), this repository is licensed GNU AGPL v3. From a0febb0b2110041d58fdfdc61468fe62712ad496 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 16 Oct 2019 17:35:52 +0200 Subject: [PATCH 0336/3170] Uuuh small fixes for the README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5fb72e260..588878261 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Build status](https://travis-ci.org/YunoHost/yunohost.svg?branch=stretch-unstable)](https://travis-ci.org/YunoHost/yunohost) [![GitHub license](https://img.shields.io/github/license/YunoHost/yunohost)](https://github.com/YunoHost/yunohost/blob/stretch-unstable/LICENSE) -![Mastodon Follow](https://img.shields.io/mastodon/follow/28084) +[![Mastodon Follow](https://img.shields.io/mastodon/follow/28084)](https://mastodon.social/@yunohost) This repository corresponds to the core code of YunoHost, mainly written in Python and Bash. @@ -14,7 +14,7 @@ You can learn more about what's YunoHost and its features [here](https://yunohos # Screenshots -Webadmin ([Yunohost-Admin](https://github.com/YunoHost/yunohost-admin)) | Single-sign on user portal ([SSOwat](https://github.com/YunoHost/ssowat)) +Webadmin ([Yunohost-Admin](https://github.com/YunoHost/yunohost-admin)) | Single sign-on user portal ([SSOwat](https://github.com/YunoHost/ssowat)) --- | --- ![](https://raw.githubusercontent.com/YunoHost/doc/master/images/webadmin.png) | ![](https://raw.githubusercontent.com/YunoHost/doc/master/images/user_panel.png) From d9990cd818ad0c8056ecb1c0f966f2a180be683c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 16 Oct 2019 18:59:23 +0200 Subject: [PATCH 0337/3170] Smarter regex to avoid redacting all --key=stuff when using setting helpers for example --- src/yunohost/log.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/yunohost/log.py b/src/yunohost/log.py index 0f5ff784c..72e497b5d 100644 --- a/src/yunohost/log.py +++ b/src/yunohost/log.py @@ -315,7 +315,8 @@ class RedactingFormatter(Formatter): try: # This matches stuff like db_pwd=the_secret or admin_password=other_secret # (the secret part being at least 3 chars to avoid catching some lines like just "db_pwd=") - match = re.search(r'(pwd|pass|password|secret|key|token)=(\S{3,})$', record.strip()) + # For 'key', we require to at least have one word char [a-zA-Z0-9_] before it to avoid catching "--key" used in many helpers + match = re.search(r'(pwd|pass|password|secret|\wkey|token)=(\S{3,})$', record.strip()) if match and match.group(2) not in self.data_to_redact: self.data_to_redact.append(match.group(2)) except Exception as e: From e2731955041b6cd37d4eeebf94b96edb66c0ce7a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 16 Oct 2019 20:22:29 +0200 Subject: [PATCH 0338/3170] [fix] Bad copypasta: logger doesn't exists in that context... --- data/helpers.d/setting | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index a8d2919a4..fd2824997 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -159,7 +159,7 @@ ynh_add_protected_uris() { ynh_app_setting() { ACTION="$1" APP="$2" KEY="$3" VALUE="${4:-}" python - < Date: Fri, 18 Oct 2019 20:20:22 +0200 Subject: [PATCH 0339/3170] Remove app_debug, unused stuff, not really relevant, now basically superseded by the new log system... --- data/actionsmap/yunohost.yml | 8 -------- src/yunohost/app.py | 22 ---------------------- 2 files changed, 30 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 22037f05f..400599c48 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -754,14 +754,6 @@ app: full: --sql help: Initial SQL file - ### app_debug() - debug: - action_help: Display all debug informations for an application - api: GET /apps//debug - arguments: - app: - help: App name - ### app_makedefault() makedefault: action_help: Redirect domain root to an app diff --git a/src/yunohost/app.py b/src/yunohost/app.py index f4c504505..d8715b278 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1253,28 +1253,6 @@ def app_clearaccess(apps): return {'allowed_users': output} -def app_debug(app): - """ - Display debug informations for an app - - Keyword argument: - app - """ - manifest = _get_manifest_of_app(os.path.join(APPS_SETTING_PATH, app)) - - return { - 'name': manifest['id'], - 'label': manifest['name'], - 'services': [{ - "name": x, - "logs": [{ - "file_name": y, - "file_content": "\n".join(z), - } for (y, z) in sorted(service_log(x).items(), key=lambda x: x[0])], - } for x in sorted(manifest.get("services", []))] - } - - @is_unit_operation() def app_makedefault(operation_logger, app, domain=None): """ From b8ce8e93923f450bfbb364758bdb37136d739ed2 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 19 Oct 2019 05:34:42 +0200 Subject: [PATCH 0340/3170] Try to make those debug messages sound less like an error --- 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 05c5a6b6b..40d3d114f 100644 --- a/src/yunohost/hook.py +++ b/src/yunohost/hook.py @@ -196,7 +196,7 @@ def hook_list(action, list_by='name', show_info=False): else: _append_folder(result, HOOK_FOLDER) except OSError: - logger.debug("system hook folder not found for action '%s' in %s", + logger.debug("No default hook for action '%s' in %s", action, HOOK_FOLDER) try: @@ -207,7 +207,7 @@ def hook_list(action, list_by='name', show_info=False): else: _append_folder(result, CUSTOM_HOOK_FOLDER) except OSError: - logger.debug("custom hook folder not found for action '%s' in %s", + logger.debug("No custom hook for action '%s' in %s", action, CUSTOM_HOOK_FOLDER) return {'hooks': result} From 7c99aff94e3bbbf66af8a2a6cd3cfa20b523e05a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 19 Oct 2019 18:04:55 +0200 Subject: [PATCH 0341/3170] Forgot the i18n --- src/yunohost/permission.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 4cfbc214f..67d115bc7 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -150,7 +150,7 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, # Don't update LDAP if we update exactly the same values if set(new_allowed_groups) == set(current_allowed_groups): - logger.warning("permission_already_up_to_date") + logger.warning(m18n.n("permission_already_up_to_date")) return # Commit the new allowed group list From 9362261d345b255eebf094b9adb14b271d851b23 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 19 Oct 2019 18:05:32 +0200 Subject: [PATCH 0342/3170] Ugh some apps uses skipped_uris sometimes instead of unprotected_uris... --- src/yunohost/app.py | 2 +- src/yunohost/data_migrations/0011_setup_group_permission.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index f4c504505..9074c90fd 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1102,7 +1102,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu permission_url(app_instance_name + ".main", url=None, sync_perm=False) # Migrate classic public app still using the legacy unprotected_uris - if app_settings.get("unprotected_uris", None) == "/": + if app_settings.get("unprotected_uris", None) == "/" or app_settings.get("skipped_uris", None) == "/": user_permission_update(app_instance_name + ".main", remove="all_users", add="visitors", sync_perm=False) permission_sync_to_user() diff --git a/src/yunohost/data_migrations/0011_setup_group_permission.py b/src/yunohost/data_migrations/0011_setup_group_permission.py index 9ba2268d9..7a987899c 100644 --- a/src/yunohost/data_migrations/0011_setup_group_permission.py +++ b/src/yunohost/data_migrations/0011_setup_group_permission.py @@ -117,7 +117,7 @@ class MyMigration(Migration): app_setting(app, 'allowed_users', delete=True) # Migrate classic public app still using the legacy unprotected_uris - if app_setting(app, "unprotected_uris") == "/": + if app_setting(app, "unprotected_uris") == "/" or app_setting(app, "skipped_uris") == "/": user_permission_update(app+".main", remove="all_users", add="visitors", sync_perm=False) permission_sync_to_user() From 98d60a888bf19987cbb956648e777dcbf79dd047 Mon Sep 17 00:00:00 2001 From: ljf Date: Thu, 17 Oct 2019 02:30:23 +0200 Subject: [PATCH 0343/3170] [fix] HTTP API for permissions --- data/actionsmap/yunohost.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 22037f05f..245b3615d 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -285,7 +285,7 @@ user: ### user_permission_list() list: action_help: List permissions and corresponding accesses - api: GET /users/permissions/ + api: GET /users/permissions arguments: -s: full: --short @@ -300,7 +300,7 @@ user: ### user_permission_update() update: action_help: Manage group or user permissions - api: POST /users/permissions/ + api: PUT /users/permissions/ arguments: permission: help: Permission to manage (e.g. mail or nextcloud or wordpress.editors) From e005d94f82c2ef85d47083fd3ee76ed4ff4ffd37 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 19 Oct 2019 19:08:38 +0200 Subject: [PATCH 0344/3170] Messed up the message UX with a previous PR (did not have the message explaining how to debug at the very end) --- locales/en.json | 2 +- src/yunohost/app.py | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/locales/en.json b/locales/en.json index 03ecdbccd..1ad7361e9 100644 --- a/locales/en.json +++ b/locales/en.json @@ -23,7 +23,7 @@ "app_id_invalid": "Invalid app ID", "app_incompatible": "The app {app} is incompatible with your YunoHost version", "app_install_files_invalid": "These files cannot be installed", - "app_install_failed": "Could not install {app}", + "app_install_failed": "Could not install {app}: {error}", "app_install_script_failed": "An error occured inside the app installation script", "app_location_already_used": "The app '{app}' is already installed in ({path})", "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}'", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 9074c90fd..5b7202362 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -994,19 +994,19 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu install_failed = True if install_retcode != 0 else False if install_failed: error = m18n.n('app_install_script_failed') - logger.exception(error) - operation_logger.error(error) + logger.exception(m18n.n("app_install_failed", app=app_id, error=error)) + failure_message_with_debug_instructions = operation_logger.error(error) # Script got manually interrupted ... N.B. : KeyboardInterrupt does not inherit from Exception except (KeyboardInterrupt, EOFError): error = m18n.n('operation_interrupted') - logger.exception(error) - operation_logger.error(error) + logger.exception(m18n.n("app_install_failed", app=app_id, error=error)) + failure_message_with_debug_instructions = operation_logger.error(error) # Something wrong happened in Yunohost's code (most probably hook_exec) - except Exception as e : + except Exception as e: import traceback error = m18n.n('unexpected_error', error=u"\n" + traceback.format_exc()) - logger.exception(error) - operation_logger.error(error) + logger.exception(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 @@ -1015,8 +1015,8 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu _assert_system_is_sane_for_app(manifest, "post") except Exception as e: broke_the_system = True - logger.exception(str(e)) - operation_logger.error(str(e)) + logger.exception(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: @@ -1076,7 +1076,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu app_ssowatconf() - raise YunohostError("app_install_failed", app=app_id) + raise YunohostError(failure_message_with_debug_instructions, raw_msg=True) # Clean hooks and add new ones hook_remove(app_instance_name) From 9beeb16077277ba5c181baad94bcdf70510ed3de Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 19 Oct 2019 19:18:19 +0200 Subject: [PATCH 0345/3170] Don't sync permission right away when deleting them, otherwise we get annoying warning because app_map thinks the app is still installed and expected a main permission --- src/yunohost/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 5b7202362..5cf812871 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1055,7 +1055,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu # Remove all permission in LDAP for permission_name in user_permission_list()["permissions"].keys(): if permission_name.startswith(app_instance_name+"."): - permission_delete(permission_name, force=True) + permission_delete(permission_name, force=True, sync_perm=False) if remove_retcode != 0: msg = m18n.n('app_not_properly_removed', @@ -1074,7 +1074,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu shutil.rmtree(app_setting_path) shutil.rmtree(extracted_app_folder) - app_ssowatconf() + permission_sync_to_user() raise YunohostError(failure_message_with_debug_instructions, raw_msg=True) From 25afdd4d4109faebf8e2aeef78eb2f53587f3dfb Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 22 Oct 2019 15:50:41 +0200 Subject: [PATCH 0346/3170] Misc README improvements --- README.md | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 588878261..5b7d04750 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,24 @@ -# YunoHost +

+ YunoHost +

+ +

YunoHost

+ +
[![Build status](https://travis-ci.org/YunoHost/yunohost.svg?branch=stretch-unstable)](https://travis-ci.org/YunoHost/yunohost) [![GitHub license](https://img.shields.io/github/license/YunoHost/yunohost)](https://github.com/YunoHost/yunohost/blob/stretch-unstable/LICENSE) [![Mastodon Follow](https://img.shields.io/mastodon/follow/28084)](https://mastodon.social/@yunohost) +
+ +YunoHost is an operating system aiming to simplify as much as possible the administration of a server. + This repository corresponds to the core code of YunoHost, mainly written in Python and Bash. -You can learn more about what's YunoHost and its features [here](https://yunohost.org/#/whatsyunohost)! - +- [Project features](https://yunohost.org/#/whatsyunohost) - [Project website](https://yunohost.org) -- [Installation documentation](https://yunohost.org/install) +- [Install documentation](https://yunohost.org/install) - [Issue tracker](https://github.com/YunoHost/issues) # Screenshots @@ -22,6 +31,7 @@ Webadmin ([Yunohost-Admin](https://github.com/YunoHost/yunohost-admin)) | Single ## Contributing - You can learn how to get started with developing on YunoHost by reading [this piece of documentation](https://yunohost.org/dev). +- Come chat with us on the [dev chatroom](https://yunohost.org/#/chat_rooms) ! - You can help translate YunoHost on our [translation platform](https://translate.yunohost.org/engage/yunohost/?utm_source=widget) Translation status From 73741f6cd937061329f0b9a2bbd54c3699a1a7e4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 22 Oct 2019 16:44:20 +0200 Subject: [PATCH 0347/3170] Simplify README, all those explanations are now in the dev documentation --- README.md | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 5b7d04750..aec5300e2 100644 --- a/README.md +++ b/README.md @@ -34,27 +34,10 @@ Webadmin ([Yunohost-Admin](https://github.com/YunoHost/yunohost-admin)) | Single - Come chat with us on the [dev chatroom](https://yunohost.org/#/chat_rooms) ! - You can help translate YunoHost on our [translation platform](https://translate.yunohost.org/engage/yunohost/?utm_source=widget) +

Translation status - - -## Repository content - -- [YunoHost core Python 2.7 scripts](./src/yunohost). -- [An actionsmap](./data/actionsmap/yunohost.yml) describing the CLI and API -- [Services configuration templates](./data/templates). -- [Hooks](./data/hooks). -- [Locales](./locales) for translations of `yunohost` command. -- [Shell helpers](./helpers.d) for [application packaging](https://yunohost.org/#/packaging_apps_helpers_en). -- [Modules for the XMPP server Metronome](./lib/metronome/modules). -- [Debian files](./debian) for package creation. - -## How does it work? - -- Python core scripts are accessible through two interfaces thanks to the [moulinette framework](https://github.com/YunoHost/moulinette): - - the [CLI](https://en.wikipedia.org/wiki/Command-line_interface) corresponding to the `yunohost` command. - - the [API](https://en.wikipedia.org/wiki/Application_programming_interface) used by the [web administration interface](https://github.com/YunoHost/yunohost-admin) (other interfaces could be implemented). -- You can find more details about how YunoHost works on this [documentation (in French)](https://yunohost.org/#/package_list_fr). +

## License -As [other components of YunoHost](https://yunohost.org/#/faq_en), this repository is licensed GNU AGPL v3. +As [other components of YunoHost](https://yunohost.org/#/faq_en), this repository is licensed under GNU AGPL v3. From ef65f82e4437b0c22249b8aebb88f8de9ef685b8 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 22 Oct 2019 20:32:08 +0200 Subject: [PATCH 0348/3170] Improve string app_action_cannot_be_ran_because_required_services_down --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 34d5f1ab7..1d68fb334 100644 --- a/locales/en.json +++ b/locales/en.json @@ -6,7 +6,7 @@ "admin_password_changed": "The administration password got changed", "admin_password_too_long": "Please choose a password shorter than 127 characters", "already_up_to_date": "Nothing to do. Everything is already up-to-date.", - "app_action_cannot_be_ran_because_required_services_down": "Start all services required to run the action that failed. Try restarting the following ones (and possibly investigate why they are down): {services}", + "app_action_cannot_be_ran_because_required_services_down": "These required services should be running to run this action: {services}. Try restarting them to continue (and possibly investigate why they are down).", "app_action_broke_system": "This action seem to have broke these important services: {services}", "app_already_installed": "{app:s} is already installed", "app_already_installed_cant_change_url": "This app is already installed. The URL cannot be changed just by this function. Look into `app changeurl` if it's available.", From 4f3fac60057ed537fb86f7b0e8c9ee53663ef4d3 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Wed, 16 Oct 2019 06:21:31 +0000 Subject: [PATCH 0349/3170] Translated using Weblate (French) Currently translated at 100.0% (560 of 560 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/locales/fr.json b/locales/fr.json index 9abeb585c..9e64bcff5 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -653,5 +653,15 @@ "permission_already_disallowed": "Le groupe '{group}' a déjà l'autorisation '{permission}' désactivé '", "permission_cannot_remove_main": "Supprimer une autorisation principale n'est pas autorisé", "user_already_exists": "L'utilisateur {user} existe déjà", - "app_full_domain_unavailable": "Désolé, cette application nécessite l'installation d'un domaine complet, mais d'autres applications sont déjà installées sur le domaine '{domain}'. Une solution possible consiste à ajouter et à utiliser un sous-domaine dédié à cette application." + "app_full_domain_unavailable": "Désolé, cette application nécessite l'installation d'un domaine complet, mais d'autres applications sont déjà installées sur le domaine '{domain}'. Une solution possible consiste à ajouter et à utiliser un sous-domaine dédié à cette application.", + "group_cannot_edit_all_users": "Le groupe 'all_users' ne peut pas être édité manuellement. C'est un groupe spécial destiné à contenir tous les utilisateurs enregistrés dans YunoHost", + "group_cannot_edit_visitors": "Le groupe 'visiteurs' ne peut pas être édité manuellement. C'est un groupe spécial représentant les visiteurs anonymes", + "group_cannot_edit_primary_group": "Le groupe '{group}' ne peut pas être édité manuellement. C'est le groupe principal destiné à ne contenir qu'un utilisateur spécifique.", + "log_permission_url": "Mise à jour de l'URL associée à l'autorisation '{}'", + "migration_0011_slapd_config_will_be_overwritten": "Il semble que vous ayez modifié manuellement la configuration de slapd. Pour cette migration critique, YunoHost doit forcer la mise à jour de la configuration de slapd. Les fichiers originaux seront sauvegardés dans {conf_backup_folder}.", + "permission_already_up_to_date": "L'autorisation n'a pas été mise à jour car les demandes d'ajout/suppression correspondent déjà à l'état actuel.", + "permission_currently_allowed_for_visitors": "Cette autorisation est actuellement accordée aux visiteurs en plus d'autres groupes. Vous voudrez probablement supprimer l'autorisation \"visiteurs\" ou supprimer les autres groupes auxquels il est actuellement attribué.", + "permission_currently_allowed_for_all_users": "Cette autorisation est actuellement accordée à tous les utilisateurs en plus des autres groupes. Vous voudrez probablement soit supprimer l'autorisation 'all_users', soit supprimer les autres groupes auxquels il est actuellement autorisé.", + "app_install_failed": "Impossible d'installer {app}", + "app_install_script_failed": "Une erreur est survenue dans le script d'installation de l'application" } From 2a30a97142f78bcec645f4db21cb63effcb0bdd0 Mon Sep 17 00:00:00 2001 From: xaloc33 Date: Wed, 16 Oct 2019 07:47:01 +0000 Subject: [PATCH 0350/3170] Translated using Weblate (Catalan) Currently translated at 100.0% (560 of 560 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ca/ --- locales/ca.json | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/locales/ca.json b/locales/ca.json index 592bc4592..b3831d929 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -285,7 +285,7 @@ "ldap_initialized": "S'ha iniciat LDAP", "license_undefined": "indefinit", "mail_alias_remove_failed": "No s'han pogut eliminar els àlies del correu «{mail:s}»", - "mail_domain_unknown": "Domini d'adreça de correu per «{domain:s}» desconegut", + "mail_domain_unknown": "El domini «{domain:s}» de l'adreça de correu no és vàlid. Utilitzeu un domini administrat per aquest servidor.", "mail_forward_remove_failed": "No s'han pogut eliminar el reenviament de correu «{mail:s}»", "mailbox_used_space_dovecot_down": "S'ha d'engegar el servei de correu Dovecot, per poder obtenir l'espai utilitzat per la bústia de correu", "mail_unavailable": "Aquesta adreça de correu està reservada i ha de ser atribuïda automàticament el primer usuari", @@ -617,5 +617,13 @@ "user_already_exists": "L'usuari {user} ja existeix", "app_upgrade_stopped": "S'ha aturat l'actualització de totes les aplicacions per prevenir els possibles danys ja que hi ha hagut un error en l'actualització de l'anterior aplicació", "app_install_failed": "No s'ha pogut instal·lar {app}", - "app_install_script_failed": "Hi ha hagut un error en el script d'instal·lació de l'aplicació" + "app_install_script_failed": "Hi ha hagut un error en el script d'instal·lació de l'aplicació", + "group_cannot_edit_all_users": "El grup «all_users» no es pot editar manualment. És un grup especial destinat a contenir els usuaris registrats a YunoHost", + "group_cannot_edit_visitors": "El grup «visitors» no es pot editar manualment. És un grup especial que representa els visitants anònims", + "group_cannot_edit_primary_group": "El grup «{group}» no es pot editar manualment. És el grup principal destinat a contenir un usuari específic.", + "log_permission_url": "Actualització de la URL associada al permís «{}»", + "migration_0011_slapd_config_will_be_overwritten": "Sembla que heu modificat manualment la configuració de sldap. Per aquesta migració crítica, YunoHost ha de forçar l'actualització de la configuració sldap. Es farà una còpia de seguretat a {conf_backup_folder}.", + "permission_already_up_to_date": "No s'ha actualitzat el permís perquè la petició d'afegir/eliminar ja corresponent a l'estat actual.", + "permission_currently_allowed_for_visitors": "El permís ja el tenen el grup de visitants a més d'altres grups. Segurament s'hauria de revocar el permís al grup dels visitants o eliminar els altres grups als que s'ha atribuït.", + "permission_currently_allowed_for_all_users": "El permís ha el té el grup de tots els usuaris (all_users) a més d'altres grups. Segurament s'hauria de revocar el permís a «all_users» o eliminar els altres grups als que s'ha atribuït." } From 889f4ef9ea7f35fe2ebf090decc9907e5f466c75 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Wed, 16 Oct 2019 06:28:28 +0000 Subject: [PATCH 0351/3170] Translated using Weblate (Esperanto) Currently translated at 100.0% (560 of 560 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/eo/ --- locales/eo.json | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/locales/eo.json b/locales/eo.json index 0d8d13fe8..237bdd1df 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -476,7 +476,7 @@ "mountpoint_unknown": "Nekonata montpunkto", "log_tools_maindomain": "Faru de '{}' la ĉefa domajno", "maindomain_change_failed": "Ne povis ŝanĝi la ĉefan domajnon", - "mail_domain_unknown": "Nekonata retpoŝtadreso por domajno '{domain:s}'", + "mail_domain_unknown": "Nevalida retadreso por domajno '{domain:s}'. Bonvolu uzi domajnon administritan de ĉi tiu servilo.", "migrations_cant_reach_migration_file": "Ne povis aliri migrajn dosierojn ĉe la vojo% s", "pattern_email": "Devas esti valida retpoŝtadreso (t.e.iu@domain.org)", "mail_alias_remove_failed": "Ne povis forigi retpoŝton alias '{mail:s}'", @@ -553,5 +553,15 @@ "service_restart_failed": "Ne povis rekomenci la servon '{service:s}'\n\nLastatempaj servaj protokoloj: {logs:s}", "firewall_rules_cmd_failed": "Iuj komandoj pri fajroŝirmilo malsukcesis. Pliaj informoj en ensaluto.", "certmanager_certificate_fetching_or_enabling_failed": "Provante uzi la novan atestilon por {domain:s} ne funkciis …", - "app_full_domain_unavailable": "Bedaŭrinde, ĉi tiu apliko postulas plenan domajnon esti instalita, sed iuj aliaj programoj jam estas instalitaj sur '{domain}'. Unu ebla solvo estas aldoni kaj uzi subdomajnon dediĉitan al ĉi tiu aplikaĵo anstataŭe." + "app_full_domain_unavailable": "Bedaŭrinde, ĉi tiu apliko postulas plenan domajnon esti instalita, sed iuj aliaj programoj jam estas instalitaj sur '{domain}'. Unu ebla solvo estas aldoni kaj uzi subdomajnon dediĉitan al ĉi tiu aplikaĵo anstataŭe.", + "migration_0011_slapd_config_will_be_overwritten": "Ŝajnas ke vi permane redaktis la slapd-agordon. Por ĉi tiu kritika migrado, YunoHost bezonas devigi la ĝisdatigon de la slapd-agordo. La originalaj dosieroj estos rezervitaj en {conf_backup_folder}.", + "group_cannot_edit_all_users": "La grupo 'all_users' ne povas esti redaktita permane. Ĝi estas speciala grupo celita enhavi ĉiujn uzantojn registritajn en YunoHost", + "group_cannot_edit_visitors": "La grupo 'vizitantoj' ne povas esti redaktita permane. Ĝi estas speciala grupo reprezentanta anonimajn vizitantojn", + "group_cannot_edit_primary_group": "La grupo '{group}' ne povas esti redaktita permane. Ĝi estas la primara grupo celita enhavi nur unu specifan uzanton.", + "log_permission_url": "Ĝisdatigu url-rilataj al permeso '{}'", + "permission_already_up_to_date": "La permeso ne estis ĝisdatigita ĉar la petoj pri aldono/forigo jam kongruas kun la aktuala stato.", + "permission_currently_allowed_for_visitors": "Ĉi tiu permeso estas nuntempe donita al vizitantoj aldone al aliaj grupoj. Vi probable volas aŭ forigi la permeson de \"vizitantoj\" aŭ forigi la aliajn grupojn al kiuj ĝi nun estas koncedita.", + "permission_currently_allowed_for_all_users": "Ĉi tiu permeso estas nuntempe donita al ĉiuj uzantoj aldone al aliaj grupoj. Vi probable volas aŭ forigi la permeson \"all_users\" aŭ forigi la aliajn grupojn, kiujn ĝi nuntempe donas.", + "app_install_failed": "Ne povis instali {app}", + "app_install_script_failed": "Eraro okazis en la skripto de instalado de la app" } From 82662167fc55becbc766fe4ac00dedc96ca5f7f2 Mon Sep 17 00:00:00 2001 From: advocatux Date: Thu, 17 Oct 2019 16:16:02 +0000 Subject: [PATCH 0352/3170] Translated using Weblate (Spanish) Currently translated at 100.0% (560 of 560 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/es/ --- locales/es.json | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/locales/es.json b/locales/es.json index 4f136460c..17c2998fa 100644 --- a/locales/es.json +++ b/locales/es.json @@ -119,7 +119,7 @@ "ldap_initialized": "Inicializado LDAP", "license_undefined": "indefinido", "mail_alias_remove_failed": "No se pudo eliminar el alias de correo «{mail:s}»", - "mail_domain_unknown": "Dirección de correo desconocida para el dominio «{domain:s}»", + "mail_domain_unknown": "Dirección de correo no válida para el dominio «{domain:s}». Use un dominio administrado por este servidor.", "mail_forward_remove_failed": "No se pudo eliminar el reenvío de correo «{mail:s}»", "maindomain_change_failed": "No se pudo cambiar el dominio principal", "maindomain_changed": "El dominio principal ha cambiado", @@ -628,5 +628,13 @@ "user_already_exists": "El usuario {user} ya existe", "app_full_domain_unavailable": "Lamentablemente esta aplicación necesita un dominio completo para ser instalada pero ya hay otras aplicaciones instaladas en el dominio «{domain}». Una solución posible es añadir y usar un subdominio dedicado a esta aplicación.", "app_install_failed": "No se pudo instalar {app}", - "app_install_script_failed": "Ha ocurrido un error en el guión de instalación de la aplicación" + "app_install_script_failed": "Ha ocurrido un error en el guión de instalación de la aplicación", + "group_cannot_edit_all_users": "El grupo «all_users» no se puede editar manualmente. Es un grupo especial destinado a contener todos los usuarios registrados en YunoHost", + "group_cannot_edit_visitors": "El grupo «visitors» no se puede editar manualmente. Es un grupo especial que representa a los visitantes anónimos", + "group_cannot_edit_primary_group": "El grupo «{group}» no se puede editar manualmente. Es el grupo primario destinado a contener solo un usuario específico.", + "log_permission_url": "Actualizar la URL relacionada con el permiso «{}»", + "migration_0011_slapd_config_will_be_overwritten": "Parece que ha editado manualmente la configuración de slapd. Para esta migración crítica, YunoHost necesita forzar la actualización de la configuración de slapd. Los archivos originales se respaldarán en {conf_backup_folder}.", + "permission_already_up_to_date": "El permiso no se ha actualizado porque las peticiones de incorporación o eliminación ya coinciden con el estado actual.", + "permission_currently_allowed_for_visitors": "Este permiso se concede actualmente a los visitantes además de otros grupos. Probablemente quiere o eliminar el permiso de «visitors» o eliminar los otros grupos a los que está otorgado actualmente.", + "permission_currently_allowed_for_all_users": "Este permiso se concede actualmente a todos los usuarios además de los otros grupos. Probablemente quiere o eliminar el permiso de «all_users» o eliminar los otros grupos a los que está otorgado actualmente." } From 88c8df2046997ddabbcf487c2a1dd5b622d29a25 Mon Sep 17 00:00:00 2001 From: advocatux Date: Mon, 21 Oct 2019 09:36:01 +0000 Subject: [PATCH 0353/3170] Translated using Weblate (Spanish) Currently translated at 100.0% (560 of 560 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/es/ --- locales/es.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/es.json b/locales/es.json index 17c2998fa..57e6de99d 100644 --- a/locales/es.json +++ b/locales/es.json @@ -627,7 +627,7 @@ "permission_cannot_remove_main": "No está permitido eliminar un permiso principal", "user_already_exists": "El usuario {user} ya existe", "app_full_domain_unavailable": "Lamentablemente esta aplicación necesita un dominio completo para ser instalada pero ya hay otras aplicaciones instaladas en el dominio «{domain}». Una solución posible es añadir y usar un subdominio dedicado a esta aplicación.", - "app_install_failed": "No se pudo instalar {app}", + "app_install_failed": "No se pudo instalar {app}: {error}", "app_install_script_failed": "Ha ocurrido un error en el guión de instalación de la aplicación", "group_cannot_edit_all_users": "El grupo «all_users» no se puede editar manualmente. Es un grupo especial destinado a contener todos los usuarios registrados en YunoHost", "group_cannot_edit_visitors": "El grupo «visitors» no se puede editar manualmente. Es un grupo especial que representa a los visitantes anónimos", From fd4e89e22cbed23b24af132ede6413a90218d5e5 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Mon, 21 Oct 2019 07:44:05 +0000 Subject: [PATCH 0354/3170] Translated using Weblate (Esperanto) Currently translated at 100.0% (560 of 560 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/eo/ --- locales/eo.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/eo.json b/locales/eo.json index 237bdd1df..3931c27d0 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -562,6 +562,6 @@ "permission_already_up_to_date": "La permeso ne estis ĝisdatigita ĉar la petoj pri aldono/forigo jam kongruas kun la aktuala stato.", "permission_currently_allowed_for_visitors": "Ĉi tiu permeso estas nuntempe donita al vizitantoj aldone al aliaj grupoj. Vi probable volas aŭ forigi la permeson de \"vizitantoj\" aŭ forigi la aliajn grupojn al kiuj ĝi nun estas koncedita.", "permission_currently_allowed_for_all_users": "Ĉi tiu permeso estas nuntempe donita al ĉiuj uzantoj aldone al aliaj grupoj. Vi probable volas aŭ forigi la permeson \"all_users\" aŭ forigi la aliajn grupojn, kiujn ĝi nuntempe donas.", - "app_install_failed": "Ne povis instali {app}", + "app_install_failed": "Ne povis instali {app} : {error}", "app_install_script_failed": "Eraro okazis en la skripto de instalado de la app" } From d52cbd127b296fc2a64c135a47c0b73e25eacf35 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Mon, 21 Oct 2019 07:42:00 +0000 Subject: [PATCH 0355/3170] Translated using Weblate (French) Currently translated at 100.0% (560 of 560 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 9e64bcff5..90eed4bee 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -662,6 +662,6 @@ "permission_already_up_to_date": "L'autorisation n'a pas été mise à jour car les demandes d'ajout/suppression correspondent déjà à l'état actuel.", "permission_currently_allowed_for_visitors": "Cette autorisation est actuellement accordée aux visiteurs en plus d'autres groupes. Vous voudrez probablement supprimer l'autorisation \"visiteurs\" ou supprimer les autres groupes auxquels il est actuellement attribué.", "permission_currently_allowed_for_all_users": "Cette autorisation est actuellement accordée à tous les utilisateurs en plus des autres groupes. Vous voudrez probablement soit supprimer l'autorisation 'all_users', soit supprimer les autres groupes auxquels il est actuellement autorisé.", - "app_install_failed": "Impossible d'installer {app}", + "app_install_failed": "Impossible d'installer {app}: {error}", "app_install_script_failed": "Une erreur est survenue dans le script d'installation de l'application" } From 5695e4d25e1b4d9bbb5ae3a7d9e9f124128239d9 Mon Sep 17 00:00:00 2001 From: xaloc33 Date: Sun, 20 Oct 2019 14:13:21 +0000 Subject: [PATCH 0356/3170] Translated using Weblate (Catalan) Currently translated at 100.0% (560 of 560 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ca/ --- locales/ca.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/ca.json b/locales/ca.json index b3831d929..04ee413b9 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -616,7 +616,7 @@ "permission_cannot_remove_main": "No es permet eliminar un permís principal", "user_already_exists": "L'usuari {user} ja existeix", "app_upgrade_stopped": "S'ha aturat l'actualització de totes les aplicacions per prevenir els possibles danys ja que hi ha hagut un error en l'actualització de l'anterior aplicació", - "app_install_failed": "No s'ha pogut instal·lar {app}", + "app_install_failed": "No s'ha pogut instal·lar {app}: {error}", "app_install_script_failed": "Hi ha hagut un error en el script d'instal·lació de l'aplicació", "group_cannot_edit_all_users": "El grup «all_users» no es pot editar manualment. És un grup especial destinat a contenir els usuaris registrats a YunoHost", "group_cannot_edit_visitors": "El grup «visitors» no es pot editar manualment. És un grup especial que representa els visitants anònims", From bd02678275320e52a82082fb3286ba05ecb85386 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 24 Oct 2019 17:47:43 +0200 Subject: [PATCH 0357/3170] Refuse to add visitors to mail / xmpp / ... permission as it doesnt make sense --- locales/en.json | 1 + src/yunohost/permission.py | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/locales/en.json b/locales/en.json index 1d68fb334..73b5c46fb 100644 --- a/locales/en.json +++ b/locales/en.json @@ -429,6 +429,7 @@ "permission_update_failed": "Could not update permission '{permission}' : {error}", "permission_updated": "Permission '{permission:s}' updated", "permission_update_nothing_to_do": "No permissions to update", + "permission_require_account": "Permission {permission} only makes sense for users having an account, and therefore cannot be enabled for visitors.", "port_already_closed": "Port {port:d} is already closed for {ip_version:s} connections", "port_already_opened": "Port {port:d} is already opened for {ip_version:s} connections", "port_available": "Port {port:d} is available", diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 67d115bc7..226cc9050 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -100,6 +100,10 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, if "." not in permission: permission = permission + ".main" + # Refuse to add "visitors" to mail, xmpp ... they require an account to make sense. + if add and "visitors" in add and permission.split(".")[0] in SYSTEM_PERMS: + raise YunohostError('permission_require_account', permission=permission) + # Fetch currently allowed groups for this permission existing_permission = user_permission_list(full=True)["permissions"].get(permission, None) From a9317487b3fb4fd0020dfd35b73d43693f6f9338 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 24 Oct 2019 17:58:34 +0200 Subject: [PATCH 0358/3170] Typo in string --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 73b5c46fb..202650edb 100644 --- a/locales/en.json +++ b/locales/en.json @@ -414,7 +414,7 @@ "pattern_positive_number": "Must be a positive number", "pattern_username": "Must be lower-case alphanumeric and underscore characters only", "pattern_password_app": "Sorry, passwords can not contain the following characters: {forbidden_chars}", - "permission_already_allowed": "Group '{group}' already has permission '{permission}' enabled'", + "permission_already_allowed": "Group '{group}' already has permission '{permission}' enabled", "permission_already_disallowed": "Group '{group}' already has permission '{permission}' disabled'", "permission_already_exist": "Permission '{permission}' already exists", "permission_already_up_to_date": "The permission was not updated because the addition/removal requests already match the current state.", From 936da7aa7948ddd9d27e1c7a5e520337b71a44e8 Mon Sep 17 00:00:00 2001 From: advocatux Date: Wed, 23 Oct 2019 09:03:22 +0000 Subject: [PATCH 0359/3170] Translated using Weblate (Spanish) Currently translated at 100.0% (560 of 560 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/es/ --- locales/es.json | 104 ++++++++++++++++++++++++------------------------ 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/locales/es.json b/locales/es.json index 57e6de99d..afa323269 100644 --- a/locales/es.json +++ b/locales/es.json @@ -29,10 +29,10 @@ "app_unsupported_remote_type": "Tipo remoto no soportado por la aplicación", "app_upgrade_failed": "No se pudo actualizar {app:s}", "app_upgraded": "Actualizado {app:s}", - "appslist_fetched": "Lista de aplicaciones {appslist:s} actualizada", - "appslist_removed": "Eliminada la lista de aplicaciones {appslist:s}", - "appslist_retrieve_error": "No se puede recuperar la lista remota de aplicaciones {appslist:s}: {error:s}", - "appslist_unknown": "Lista de aplicaciones {appslist:s} desconocida.", + "appslist_fetched": "Lista de aplicaciones «{appslist:s}» actualizada", + "appslist_removed": "Eliminada la lista de aplicaciones «{appslist:s}»", + "appslist_retrieve_error": "No se puede recuperar la lista remota de aplicaciones «{appslist:s}»: {error:s}", + "appslist_unknown": "Lista de aplicaciones «{appslist:s}» desconocida.", "ask_current_admin_password": "Contraseña administrativa actual", "ask_email": "Dirección de correo electrónico", "ask_firstname": "Nombre", @@ -70,7 +70,7 @@ "diagnosis_monitor_disk_error": "No se pudieron monitorizar los discos: {error}", "diagnosis_monitor_network_error": "No se pudo monitorizar la red: {error}", "diagnosis_monitor_system_error": "No se pudo monitorizar el sistema: {error}", - "diagnosis_no_apps": "Aplicación no instalada", + "diagnosis_no_apps": "No hay tal aplicación instalada", "dnsmasq_isnt_installed": "Parece que dnsmasq no está instalado, ejecute «apt-get remove bind9 && apt-get install it»", "domain_cert_gen_failed": "No se pudo generar el certificado", "domain_created": "Dominio creado", @@ -123,8 +123,8 @@ "mail_forward_remove_failed": "No se pudo eliminar el reenvío de correo «{mail:s}»", "maindomain_change_failed": "No se pudo cambiar el dominio principal", "maindomain_changed": "El dominio principal ha cambiado", - "monitor_disabled": "Desactivada la monitorización del servidor", - "monitor_enabled": "Activada la monitorización del servidor", + "monitor_disabled": "La monitorización del servidor está ahora desactivada", + "monitor_enabled": "La monitorización del servidor está ahora activada", "monitor_glances_con_failed": "No se pudo conectar al servidor de Glances", "monitor_not_enabled": "La monitorización del servidor está apagada", "monitor_period_invalid": "Período de tiempo no válido", @@ -132,9 +132,9 @@ "monitor_stats_no_update": "No hay estadísticas de monitorización para actualizar", "monitor_stats_period_unavailable": "No hay estadísticas para el período", "mountpoint_unknown": "Punto de montaje desconocido", - "mysql_db_creation_failed": "Error al crear la base de datos de MySQL", - "mysql_db_init_failed": "Error al iniciar la base de datos de MySQL", - "mysql_db_initialized": "Inicializada la base de datos MySQL", + "mysql_db_creation_failed": "No se pudo crear la base de datos MySQL", + "mysql_db_init_failed": "No se pudo inicializar la base de datos MySQL", + "mysql_db_initialized": "La base de datos MySQL está ahora inicializada", "network_check_mx_ko": "El registro DNS MX no está configurado", "network_check_smtp_ko": "El correo saliente (SMTP puerto 25) parece estar bloqueado por su red", "network_check_smtp_ok": "El correo saliente (SMTP puerto 25) no está bloqueado", @@ -144,7 +144,7 @@ "no_ipv6_connectivity": "La conexión por IPv6 no está disponible", "no_restore_script": "No se ha encontrado un script de restauración para la aplicación '{app:s}'", "not_enough_disk_space": "No hay espacio libre suficiente en «{path:s}»", - "package_not_installed": "El paquete '{pkgname}' no está instalado", + "package_not_installed": "El paquete «{pkgname}» no está instalado", "package_unexpected_error": "Ha ocurrido un error inesperado procesando el paquete '{pkgname}'", "package_unknown": "Paquete desconocido '{pkgname}'", "packages_no_upgrade": "No hay paquetes para actualizar", @@ -153,7 +153,7 @@ "path_removal_failed": "No se pudo eliminar la ruta {:s}", "pattern_backup_archive_name": "Debe ser un nombre de archivo válido con un máximo de 30 caracteres, solo se admiten caracteres alfanuméricos y los caracteres -_. (guiones y punto)", "pattern_domain": "El nombre de dominio debe ser válido (por ejemplo mi-dominio.org)", - "pattern_email": "Debe ser una dirección de correo electrónico válida (por ejemplo, alguien@dominio.org)", + "pattern_email": "Debe ser una dirección de correo electrónico válida (p.ej. alguien@example.com)", "pattern_firstname": "Debe ser un nombre válido", "pattern_lastname": "Debe ser un apellido válido", "pattern_listname": "Solo se pueden usar caracteres alfanuméricos y el guion bajo", @@ -180,7 +180,7 @@ "restore_running_hooks": "Ejecutando los ganchos de restauración…", "service_add_failed": "No se pudo añadir el servicio «{service:s}»", "service_added": "Añadido el servicio «{service:s}»", - "service_already_started": "El servicio «{service:s}» ya ha sido iniciado", + "service_already_started": "El servicio «{service:s}» ya está funcionando", "service_already_stopped": "El servicio «{service:s}» ya ha sido detenido", "service_cmd_exec_failed": "No se pudo ejecutar la orden «{command:s}»", "service_conf_file_backed_up": "Se ha realizado una copia de seguridad del archivo de configuración '{conf}' en '{backup}'", @@ -195,10 +195,10 @@ "service_conf_updated": "La configuración ha sido actualizada para el servicio '{service}'", "service_conf_would_be_updated": "La configuración podría haber sido actualizada para el servicio '{service} 1'", "service_disable_failed": "No se pudo desactivar el servicio «{service:s}»\n\nRegistro de servicios recientes:{logs:s}", - "service_disabled": "Desactivado el servicio «{service:s}»", + "service_disabled": "El servicio «{service:s}» ha sido desactivado", "service_enable_failed": "No se pudo activar el servicio «{service:s}»\n\nRegistro de servicios recientes:{logs:s}", - "service_enabled": "Activado el servicio «{service:s}»", - "service_no_log": "No hay ningún registro para el servicio '{service:s}'", + "service_enabled": "El servicio «{service:s}» ha sido desactivado", + "service_no_log": "No hay ningún registro que mostrar para el servicio «{service:s}»", "service_regenconf_dry_pending_applying": "Comprobando configuración pendiente que podría haber sido aplicada al servicio '{service}'...", "service_regenconf_failed": "No se puede regenerar la configuración para el servicio(s): {services}", "service_regenconf_pending_applying": "Aplicando la configuración pendiente para el servicio '{service}'...", @@ -208,7 +208,7 @@ "service_started": "Iniciado el servicio «{service:s}»", "service_status_failed": "No se pudo determinar el estado del servicio «{service:s}»", "service_stop_failed": "No se pudo detener el servicio «{service:s}»\n\nRegistro de servicios recientes:{logs:s}", - "service_stopped": "Detenido el servicio «{service:s}»", + "service_stopped": "El servicio «{service:s}» se detuvo", "service_unknown": "Servicio desconocido '{service:s}'", "ssowat_conf_generated": "Generada la configuración de SSOwat", "ssowat_conf_updated": "Actualizada la configuración de SSOwat", @@ -238,7 +238,7 @@ "user_updated": "Cambiada la información de usuario", "yunohost_already_installed": "YunoHost ya está instalado", "yunohost_ca_creation_failed": "No se pudo crear la autoridad de certificación", - "yunohost_configured": "YunoHost está configurado", + "yunohost_configured": "YunoHost está ahora configurado", "yunohost_installing": "Instalando YunoHost…", "yunohost_not_installed": "YunoHost no está correctamente instalado. Ejecute «yunohost tools postinstall»", "ldap_init_failed_to_create_admin": "La inicialización de LDAP no pudo crear el usuario «admin»", @@ -272,25 +272,25 @@ "certmanager_acme_not_configured_for_domain": "El certificado para el dominio «{domain:s}» no parece que esté instalado correctamente. Ejecute primero «cert-install» para este dominio.", "certmanager_http_check_timeout": "Tiempo de espera agotado cuando el servidor intentaba conectarse consigo mismo a través de HTTP usando una dirección IP pública (dominio «{domain:s}» con IP «{ip:s}»). Puede que esté experimentando un problema de redirección («hairpinning»), o que el cortafuegos o el enrutador de su servidor esté mal configurado.", "certmanager_couldnt_fetch_intermediate_cert": "Tiempo de espera agotado intentando obtener el certificado intermedio de Let's Encrypt. Cancelada la instalación o renovación del certificado. Vuelva a intentarlo más tarde.", - "appslist_retrieve_bad_format": "No se pudo leer la lista de aplicaciones obtenida {appslist:s}", + "appslist_retrieve_bad_format": "No se pudo leer la lista de aplicaciones obtenida «{appslist:s}»", "domain_hostname_failed": "No se pudo establecer un nuevo nombre de anfitrión («hostname»). Esto podría causar problemas más tarde (no es seguro... podría ir bien).", "yunohost_ca_creation_success": "Creada la autoridad de certificación local.", "app_already_installed_cant_change_url": "Esta aplicación ya está instalada. No se puede cambiar el URL únicamente mediante esta función. Compruebe si está disponible la opción `app changeurl`.", "app_change_no_change_url_script": "La aplicacion {app_name:s} aún no permite cambiar su URL, es posible que deba actualizarla.", "app_change_url_failed_nginx_reload": "No se pudo recargar NGINX. Esta es la salida de «nginx -t»:\n{nginx_errors:s}", "app_change_url_identical_domains": "El antiguo y nuevo dominio/url_path son idénticos ('{domain:s} {path:s}'), no se realizarán cambios.", - "app_change_url_no_script": "Esta aplicación «{app_name:s}» aún no permite la modificación de URLs. Quizás debería actualizarla.", + "app_change_url_no_script": "La aplicación «{app_name:s}» aún no permite la modificación de URLs. Quizás debería actualizarla.", "app_change_url_success": "El URL de la aplicación {app:s} es ahora {domain:s} {path:s}", "app_location_unavailable": "Este URL o no está disponible o está en conflicto con otra(s) aplicación(es) instalada(s):\n{apps:s}", "app_already_up_to_date": "La aplicación {app:s} ya está actualizada", "appslist_name_already_tracked": "Ya existe una lista de aplicaciones registradas con el nombre {name:s}.", "appslist_url_already_tracked": "Ya existe una lista de aplicaciones registradas con el URL {url:s}.", - "appslist_migrating": "Migrando la lista de aplicaciones {appslist:s}…", - "appslist_could_not_migrate": "¡No se pudo migrar la lista de aplicaciones {appslist:s}! No se pudo analizar el URL… El antiguo trabajo de cron se mantuvo en {bkp_file:s}.", + "appslist_migrating": "Migrando la lista de aplicaciones «{appslist:s}»…", + "appslist_could_not_migrate": "¡No se pudo migrar la lista de aplicaciones «{appslist:s}»! No se pudo analizar el URL… El antiguo trabajo de cron se mantuvo en {bkp_file:s}.", "appslist_corrupted_json": "No se pudieron cargar las listas de aplicaciones. Parece que {filename:s} está dañado.", "invalid_url_format": "Algo va mal con el URL", "app_upgrade_some_app_failed": "No se pudieron actualizar algunas aplicaciones", - "app_make_default_location_already_used": "No puede hacer que la aplicación «{app}» sea la predeterminada en el dominio, {domain} ya está siendo usado por otra aplicación «{other_app}»", + "app_make_default_location_already_used": "No puede hacer que la aplicación «{app}» sea la predeterminada en el dominio, «{domain}» ya está siendo usado por otra aplicación «{other_app}»", "app_upgrade_app_name": "Actualizando ahora {app}…", "ask_path": "Camino", "backup_abstract_method": "Este método de respaldo aún no se ha implementado", @@ -301,7 +301,7 @@ "backup_archive_mount_failed": "No se pudo montar el archivo de respaldo", "backup_archive_system_part_not_available": "La parte del sistema «{part:s}» no está disponible en esta copia de seguridad", "backup_archive_writing_error": "No se pudieron añadir los archivos «{source:s}» (llamados en el archivo «{dest:s}») para ser respaldados en el archivo comprimido «{archive:s}»", - "backup_ask_for_copying_if_needed": "No se pudieron preparar algunos archivos para la copia de seguridad usando el método que evita desperdiciar espacio temporalmente en el sistema. Para hacer la copia de seguridad, {size:s}MB se usarán temporalmente. ¿Está de acuerdo?", + "backup_ask_for_copying_if_needed": "¿Quiere realizar la copia de seguridad usando {size:s} MB temporalmente? (Se usa este modo ya que algunos archivos no se pudieron preparar usando un método más eficiente.)", "backup_borg_not_implemented": "El método de respaldo de Borg aún no ha sido implementado", "backup_cant_mount_uncompress_archive": "No se pudo montar el archivo descomprimido como protegido contra escritura", "backup_copying_to_organize_the_archive": "Copiando {size:s}MB para organizar el archivo", @@ -331,22 +331,22 @@ "update_apt_cache_warning": "Algo fue mal durante la actualización de la caché de APT (gestor de paquetes de Debian). Aquí tiene un volcado de las líneas de sources.list que podría ayudarle a identificar las líneas problemáticas:\n{sourceslist}", "update_apt_cache_failed": "No se pudo actualizar la caché de APT (gestor de paquetes de Debian). Aquí tiene un volcado de las líneas de sources.list que podría ayudarle a identificar las líneas problemáticas:\n{sourceslist}", "tools_upgrade_special_packages_completed": "Actualización de paquetes de YunoHost completada.\nPulse [Intro] para regresar a la línea de órdenes", - "tools_upgrade_special_packages_explanation": "Esta acción terminará pero la actualización especial real continuará en segundo plano. No inicie ninguna otra acción en su servidor en aproximadamente 10 minutos (dependiendo de la velocidad de su hardware). Una vez que esté hecho, podría tener que volver a iniciar sesión en la administración web. El registro de actualización estará disponible en Herramientas → Registro (en la página de administración web) o mediante «yunohost log list» (desde la línea de órdenes).", + "tools_upgrade_special_packages_explanation": "Esta acción terminará pero la actualización especial real continuará en segundo plano. No inicie ninguna otra acción en su servidor en aproximadamente 10 minutos (dependiendo de la velocidad de su hardware). Una vez hecho, podría tener que volver a iniciar sesión en la administración web. El registro de actualización estará disponible en Herramientas → Registro (en la página de administración web) o mediante «yunohost log list» (desde la línea de órdenes).", "tools_upgrade_special_packages": "Actualizando ahora paquetes «especiales» (relacionados con YunoHost)…", "tools_upgrade_regular_packages_failed": "No se pudieron actualizar los paquetes: {packages_list}", "tools_upgrade_regular_packages": "Actualizando ahora paquetes «normales» (no relacionados con YunoHost)…", - "tools_upgrade_cant_unhold_critical_packages": "No se pudieron liberar los paquetes críticos…", + "tools_upgrade_cant_unhold_critical_packages": "No se pudo liberar los paquetes críticos…", "tools_upgrade_cant_hold_critical_packages": "No se pudieron retener los paquetes críticos…", "tools_upgrade_cant_both": "No se puede actualizar el sistema y las aplicaciones al mismo tiempo", "tools_upgrade_at_least_one": "Especifique «--apps», o «--system»", "tools_update_failed_to_app_fetchlist": "No se pudo actualizar la lista de aplicaciones de YunoHost porque: {error}", "this_action_broke_dpkg": "Esta acción rompió dpkg/APT(los gestores de paquetes del sistema)… Puede tratar de solucionar este problema conectando mediante SSH y ejecutando `sudo dpkg --configure -a`.", "system_groupname_exists": "El nombre de grupo ya existe en el grupo del sistema", - "service_reloaded_or_restarted": "Recargado o reiniciado el servicio «{service:s}»", + "service_reloaded_or_restarted": "El servicio «{service:s}» ha sido recargado o reiniciado", "service_reload_or_restart_failed": "No se pudo recargar o reiniciar el servicio «{service:s}»\n\nRegistro de servicios recientes:{logs:s}", "service_restarted": "Reiniciado el servicio «{service:s}»", "service_restart_failed": "No se pudo reiniciar el servicio «{service:s}»\n\nRegistro de servicios recientes:{logs:s}", - "service_reloaded": "Recargado el servicio «{service:s}»", + "service_reloaded": "El servicio «{service:s}» ha sido recargado", "service_reload_failed": "No se pudo recargar el servicio «{service:s}»\n\nRegistro de servicios recientes:{logs:s}", "service_regen_conf_is_deprecated": "¡«yunohost service regen-conf» está obsoleto! Use «yunohost tools regen-conf» en su lugar.", "service_description_yunohost-firewall": "Gestiona los puertos de conexiones abiertos y cerrados a los servicios", @@ -360,7 +360,7 @@ "service_description_php7.0-fpm": "Ejecuta aplicaciones escritas en PHP con NGINX", "service_description_nslcd": "Maneja la conexión del intérprete de órdenes («shell») de usuario de YunoHost", "service_description_nginx": "Sirve o proporciona acceso a todos los sitios web alojados en su servidor", - "service_description_mysql": "Almacena los datos de las aplicaciones (base de datos SQL)", + "service_description_mysql": "Almacena los datos de la aplicación (base de datos SQL)", "service_description_metronome": "Gestionar las cuentas XMPP de mensajería instantánea", "service_description_glances": "Supervisa la información del sistema en su servidor", "service_description_fail2ban": "Protege contra ataques de fuerza bruta y otras clases de ataques desde Internet", @@ -417,7 +417,7 @@ "migrations_running_forward": "Ejecutando migración {id}…", "migrations_pending_cant_rerun": "Esas migraciones están aún pendientes, así que no se pueden volver a ejecutar: {ids}", "migrations_not_pending_cant_skip": "Esas migraciones no están pendientes, así que no pueden ser omitidas: {ids}", - "migrations_no_such_migration": "No hay ninguna migración llamada {id}", + "migrations_no_such_migration": "No hay ninguna migración llamada «{id}»", "migrations_no_migrations_to_run": "No hay migraciones que ejecutar", "migrations_need_to_accept_disclaimer": "Para ejecutar la migración {id} debe aceptar el siguiente descargo de responsabilidad:\n---\n{disclaimer}\n---\nSi acepta ejecutar la migración, vuelva a ejecutar la orden con la opción «--accept-disclaimer».", "migrations_must_provide_explicit_targets": "Necesita proporcionar objetivos explícitos al usar «--skip» or «--force-rerun»", @@ -426,30 +426,30 @@ "migrations_list_conflict_pending_done": "No puede usar «--previous» y «--done» al mismo tiempo.", "migrations_exclusive_options": "«--auto», «--skip», and «--force-rerun» son opciones mutuamente excluyentes.", "migrations_failed_to_load_migration": "No se pudo cargar la migración {id}: {error}", - "migrations_dependencies_not_satisfied": "No se puede ejecutar la migración {id} porque primero necesita ejecutar estas migraciones: {dependencies_id}", - "migrations_cant_reach_migration_file": "No se pudo acceder los archivos de migración en la ruta %s", + "migrations_dependencies_not_satisfied": "Ejecutar estas migraciones: «{dependencies_id}» antes de migrar {id}.", + "migrations_cant_reach_migration_file": "No se pudo acceder a los archivos de migración en la ruta «%s»", "migrations_already_ran": "Esas migraciones ya se han realizado: {ids}", "migration_0011_update_LDAP_schema": "Actualizando el esquema de LDAP…", "migration_0011_update_LDAP_database": "Actualizando la base de datos de LDAP…", "migration_0011_rollback_success": "Sistema revertido.", - "migration_0011_migration_failed_trying_to_rollback": "Migración fallida… intentando revertir el sistema.", + "migration_0011_migration_failed_trying_to_rollback": "No se pudo migrar… intentando revertir el sistema.", "migration_0011_migrate_permission": "Migrando permisos desde la configuración de las aplicaciones a LDAP…", "migration_0011_LDAP_update_failed": "No se pudo actualizar LDAP. Error: {error:s}", "migration_0011_LDAP_config_dirty": "Parece que ha personalizado la configuración de LDAP. Para esta migración se necesita actualizar la configuración de LDAP.\nNecesita guardar su configuración actual, reiniciar la configuración original ejecutando «yunohost tools regen-conf -f» y reintentar la migración", - "migration_0011_done": "Migración correcta. Ahora puede gestionar los grupos de usuarios.", + "migration_0011_done": "Migración finalizada. Ahora puede gestionar los grupos de usuarios.", "migration_0011_create_group": "Creando un grupo para cada usuario…", - "migration_0011_can_not_backup_before_migration": "Falló el respaldo del sistema antes de la migración. Fallo de migración. Error: {error:s}", + "migration_0011_can_not_backup_before_migration": "El respaldo del sistema no se pudo completar antes de que la migración fallase. Error: {error:s}", "migration_0011_backup_before_migration": "Creando un respaldo de la base de datos de LDAP y de la configuración de las aplicaciones antes de la migración real.", "migration_0009_not_needed": "La migración ya ocurrió de algún modo… (?) Omitiendo.", - "migration_0008_no_warning": "No se ha detectado ningún riesgo importante con respecto a la anulación de su configuración SSH ¡sin embargo uno nunca puede estar absolutamente seguro ;)! Ejecute la migración para anularla. Por otra parte, puede omitir la migración aunque no esté recomendado.", - "migration_0008_warning": "Si entiende esos avisos y permite a YunoHost anular su configuración actual, ejecute la migración. Por otra parte puede omitir la migración, aunque no se recomienda.", + "migration_0008_no_warning": "Ignorar su configuración SSH debería ser seguro ¡aunque esto no se puede prometer! Ejecute la migración para ignorarla. Por otra parte puede omitir la migración, aunque no se recomienda.", + "migration_0008_warning": "Si entiende esos avisos y quiere que YunoHost ignore su configuración actual, ejecute la migración. Por otra parte puede omitir la migración, aunque no se recomienda.", "migration_0008_dsa": "• Se desactivará la clave DSA. Así que podría tener que anular un aviso espeluznante de su cliente SSH y volver a comprobar la huella de su servidor;", "migration_0008_root": "• No podrá conectarse como «root» a través de SSH. En su lugar debe usar el usuario «admin»;", "migration_0008_port": "• Tendrá que conectarse usando el puerto 22 en vez de su actual puerto SSH personalizado. No dude en reconfigurarlo;", "migration_0008_general_disclaimer": "Para mejorar la seguridad de su servidor, es recomendable permitir a YunoHost gestionar la configuración de SSH. Su actual configuración de SSH difiere de la recomendación. Si permite a YunoHost reconfigurarla, la manera en la que conecta con su servidor a través de SSH cambiará así:", "migration_0007_cannot_restart": "No se puede reiniciar SSH después de intentar cancelar la migración número 6.", - "migration_0007_cancelled": "YunoHost no ha podido mejorar el modo en el que se gestiona su configuración de SSH.", - "migration_0006_disclaimer": "YunoHost espera ahora que las contraseñas de «admin» y «root» estén sincronizadas. Al ejecutar esta migración, su contraseña de «root» será reemplazada por la contraseña de administración.", + "migration_0007_cancelled": "No se pudo mejorar el modo en el que se gestiona su configuración de SSH.", + "migration_0006_disclaimer": "YunoHost espera ahora que las contraseñas de «admin» y «root» estén sincronizadas. Esta migración reemplaza su contraseña de «root» por la contraseña de «admin».", "migration_0005_not_enough_space": "Tenga suficiente espacio libre disponible en {path} para ejecutar la migración.", "migration_0005_postgresql_96_not_installed": "⸘PostgreSQL 9.4 está instalado pero no PostgreSQL 9.6‽ Algo raro podría haber ocurrido en su sistema:(…", "migration_0005_postgresql_94_not_installed": "PostgreSQL no estaba instalado en su sistema. Nada que hacer.", @@ -502,12 +502,12 @@ "log_user_delete": "Eliminar usuario «{}»", "log_user_create": "Añadir usuario «{}»", "log_regen_conf": "Regenerar la configuración del sistema «{}»", - "log_letsencrypt_cert_renew": "Renovar el certificado «{}» de Let's encrypt", + "log_letsencrypt_cert_renew": "Renovar el certificado «{}» de Let's Encrypt", "log_selfsigned_cert_install": "Instalar certificado autofirmado en el dominio «{}»", "log_permission_update": "Actualizar permiso «{}» para la aplicación «{}»", "log_permission_remove": "Eliminar permiso «{}»", "log_permission_add": "Añadir el permiso «{}» para la aplicación «{}»", - "log_letsencrypt_cert_install": "Instalar un certificado de Let's encrypt en el dominio «{}»", + "log_letsencrypt_cert_install": "Instalar un certificado de Let's Encrypt en el dominio «{}»", "log_dyndns_update": "Actualizar la IP asociada con su subdominio de YunoHost «{}»", "log_dyndns_subscribe": "Subscribirse a un subdomino de YunoHost «{}»", "log_domain_remove": "Eliminar el dominio «{}» de la configuración del sistema", @@ -577,12 +577,12 @@ "domain_dns_conf_is_just_a_recommendation": "Esta orden muestra la configuración *recomendada*. No configura el DNS en realidad. Es su responsabilidad configurar la zona de DNS en su registrador según esta recomendación.", "dpkg_lock_not_available": "Esta orden no se puede ejecutar en este momento porque otro programa parece que está usando el bloqueo de dpkg (el gestor de paquetes del sistema)", "dpkg_is_broken": "No puede hacer esto en este momento porque dpkg/apt (los gestores de paquetes del sistema) parecen estar en un estado roto... Puede tratar de solucionar este problema conectando a través de SSH y ejecutando `sudo dpkg --configure -a`.", - "confirm_app_install_thirdparty": "¡PELIGRO! Esta aplicación no forma parte del catálogo de aplicaciones de YunoHost. Instalar aplicaciones de terceros podría comprometer la integridad y seguridad de su sistema. Probablemente NO debería instalarla salvo que sepa lo que está haciendo. No tendrá NINGUNA AYUDA si esta aplicación no funciona o rompe su sistema... Si está dispuesto a aceptar ese riesgo de todas formas, escriba «{answers:s}»", - "confirm_app_install_danger": "¡PELIGRO! ¡Esta aplicación es conocida por ser aún experimental (o no funciona explícitamente)! Probablemente NO debería instalarla salvo que sepa lo que está haciendo. No tendrá NINGUNA AYUDA si esta aplicación no funciona o rompe su sistema... Si está dispuesto a aceptar ese riesgo de todas formas, escriba «{answers:s}»", + "confirm_app_install_thirdparty": "¡PELIGRO! Esta aplicación no forma parte del catálogo de aplicaciones de YunoHost. Instalar aplicaciones de terceros podría comprometer la integridad y seguridad de su sistema. Probablemente NO debería instalarla salvo que sepa lo que está haciendo. No tendrá NINGUNA AYUDA si esta aplicación no funciona o rompe su sistema… Si está dispuesto a aceptar ese riesgo de todas formas, escriba «{answers:s}»", + "confirm_app_install_danger": "¡PELIGRO! ¡Esta aplicación es conocida por ser aún experimental (o no funciona explícitamente)! Probablemente NO debería instalarla salvo que sepa lo que está haciendo. No tendrá NINGUNA AYUDA si esta aplicación no funciona o rompe su sistema… Si está dispuesto a aceptar ese riesgo de todas formas, escriba «{answers:s}»", "confirm_app_install_warning": "Aviso: esta aplicación puede funcionar pero no está bien integrada en YunoHost. Algunas herramientas como la autentificación única y respaldo/restauración podrían no estar disponibles. ¿Instalar de todos modos? [{answers:s}] ", "backup_unable_to_organize_files": "No se pudo usar el método rápido de organización de los archivos en el archivo", "backup_permission": "Permiso de respaldo para la aplicación {app:s}", - "backup_output_symlink_dir_broken": "Tiene un enlace simbólico roto en vez del directorio «{path:s}» de su archivo. Puede que tenga una configuración específica para respaldar sus datos en otro sistema de archivos, en este caso probablemente olvidó remontar o conectar su disco duro o clave USB.", + "backup_output_symlink_dir_broken": "El directorio de su archivo «{path:s}» es un enlace simbólico roto. Tal vez olvidó (re)montarlo o conectarlo al medio de almacenamiento al que apunta.", "backup_mount_archive_for_restore": "Preparando el archivo para la restauración…", "backup_method_tar_finished": "Creado el archivo TAR de respaldo", "backup_method_custom_finished": "Terminado el método «{method:s}» de respaldo personalizado", @@ -595,16 +595,16 @@ "apps_permission_restoration_failed": "Otorgar el permiso «{permission:s}» para restaurar {app:s}", "apps_permission_not_found": "No se han encontrado permisos para las aplicaciones instaladas", "app_upgrade_several_apps": "Las siguientes aplicaciones se actualizarán: {apps}", - "app_start_restore": "Restaurando aplicación {app}…", - "app_start_backup": "Obteniendo archivos para el respaldo de {app}…", - "app_start_remove": "Eliminando aplicación {app}…", - "app_start_install": "Instalando aplicación {app}…", + "app_start_restore": "Restaurando aplicación «{app}»…", + "app_start_backup": "Obteniendo archivos para el respaldo de «{app}»…", + "app_start_remove": "Eliminando aplicación «{app}»…", + "app_start_install": "Instalando aplicación «{app}»…", "app_not_upgraded": "Error al actualizar la aplicación «{failed_app}» y como consecuencia se han cancelado las actualizaciones de las siguientes aplicaciones: {apps}", - "app_action_cannot_be_ran_because_required_services_down": "Esta aplicación necesita algunos servicios que no están funcionando ahora. Antes de continuar, debería intentar reiniciar los siguientes servicios (y posiblemente investigar por qué no funcionan): {services}", + "app_action_cannot_be_ran_because_required_services_down": "Estos servicios necesarios deberían estar funcionando para ejecutar esta acción: {services}. Pruebe a reiniciarlos para continuar (y posiblemente investigar por qué están caídos).", "already_up_to_date": "Nada que hacer. Todo está actualizado.", "admin_password_too_long": "Elija una contraseña de menos de 127 caracteres", "aborting": "Cancelando.", - "app_upgrade_stopped": "Se ha detenido la actualización de todas las aplicaciones para prevenir un posible daño porque la aplicación anterior no se pudo actualizar", + "app_upgrade_stopped": "Se ha detenido la actualización de todas las aplicaciones para prevenir un posible daño porque una aplicación no se pudo actualizar", "app_action_broke_system": "Esta acción parece que ha roto estos importantes servicios: {services}", "operation_interrupted": "¿Ha sido interrumpida la operación manualmente?", "apps_already_up_to_date": "Todas las aplicaciones están ya actualizadas", @@ -625,8 +625,8 @@ "permission_already_allowed": "El grupo «{group}» ya tiene el permiso «{permission}» activado", "permission_already_disallowed": "El grupo «{group}» ya tiene el permiso «{permission}» desactivado", "permission_cannot_remove_main": "No está permitido eliminar un permiso principal", - "user_already_exists": "El usuario {user} ya existe", - "app_full_domain_unavailable": "Lamentablemente esta aplicación necesita un dominio completo para ser instalada pero ya hay otras aplicaciones instaladas en el dominio «{domain}». Una solución posible es añadir y usar un subdominio dedicado a esta aplicación.", + "user_already_exists": "El usuario «{user}» ya existe", + "app_full_domain_unavailable": "Lamentablemente esta aplicación tiene que instalarse en un dominio propio pero ya hay otras aplicaciones instaladas en el dominio «{domain}». Podría usar un subdomino dedicado a esta aplicación en su lugar.", "app_install_failed": "No se pudo instalar {app}: {error}", "app_install_script_failed": "Ha ocurrido un error en el guión de instalación de la aplicación", "group_cannot_edit_all_users": "El grupo «all_users» no se puede editar manualmente. Es un grupo especial destinado a contener todos los usuarios registrados en YunoHost", From 345747d2af3fd54599e6592b2a27820bfa3b3526 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Thu, 24 Oct 2019 12:27:11 +0000 Subject: [PATCH 0360/3170] Translated using Weblate (Esperanto) Currently translated at 86.6% (485 of 560 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/eo/ --- locales/eo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/eo.json b/locales/eo.json index 3931c27d0..c78bf6269 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -56,7 +56,7 @@ "backup_actually_backuping": "Krei rezervan ar archiveivon el la kolektitaj dosieroj …", "backup_method_borg_finished": "Sekurkopio en Borg finiĝis", "appslist_removed": "{appslist:s} aplika listo forigita", - "app_change_url_no_script": "Ĉi tiu apliko '{app_name:s}' ankoraŭ ne subtenas URL-modifon. Eble vi devus altgradigi ĝin.", + "app_change_url_no_script": "La app '{app_name:s}' ankoraŭ ne subtenas URL-modifon. Eble vi devus altgradigi ĝin.", "app_start_install": "Instalanta aplikon {app} …", "backup_created": "Sekurkopio kreita", "app_make_default_location_already_used": "Ne povas igi la aplikon '{app}' defaŭlta sur la domajno, {domain} jam uziĝas de la alia app '{other_app}'", @@ -83,7 +83,7 @@ "backup_cant_mount_uncompress_archive": "Ne povis munti la nekompresitan ar archiveivon kiel protektita kontraŭ skribo", "appslist_retrieve_bad_format": "Ne povis legi la elprenitan liston {appslist:s}", "appslist_corrupted_json": "Ne povis ŝarĝi la aplikajn listojn. Ĝi aspektas kiel {filename:s} estas damaĝita.", - "app_action_cannot_be_ran_because_required_services_down": "Ĉi tiu app postulas iujn servojn, kiuj nuntempe malleviĝas. Antaŭ ol daŭrigi, vi provu rekomenci la jenajn servojn (kaj eventuale esploru kial ili malsukcesas): {services}", + "app_action_cannot_be_ran_because_required_services_down": "Ĉi tiuj postulataj servoj devas funkcii por funkciigi ĉi tiun agon: {services}. Provu rekomenci ilin por daŭrigi (kaj eble esploru, kial ili malsupreniras).", "backup_copying_to_organize_the_archive": "Kopiante {size:s} MB por organizi la ar archiveivon", "backup_output_directory_forbidden": "Elektu malsaman elirejan dosierujon. Sekurkopioj ne povas esti kreitaj en sub-dosierujoj / bin, / boot, / dev, / ktp, / lib, / root, / run, / sbin, / sys, / usr, / var aŭ /home/yunohost.backup/archives", "appslist_could_not_migrate": "Ne povis migri la liston de aplikoj {appslist:s}! Ne eblis analizi la URL ... La malnova cron-laboro konserviĝis en {bkp_file:s}.", From 258209bcd08406fcf55d0d8361e2d2cd6db0691e Mon Sep 17 00:00:00 2001 From: amirale qt Date: Thu, 24 Oct 2019 06:33:17 +0000 Subject: [PATCH 0361/3170] Translated using Weblate (French) Currently translated at 100.0% (560 of 560 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 82 ++++++++++++++++++++++++------------------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 90eed4bee..37e5f95c1 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -30,10 +30,10 @@ "app_unsupported_remote_type": "Ce type de commande à distance utilisé pour cette application n'est pas supporté", "app_upgrade_failed": "Impossible de mettre à jour {app:s}", "app_upgraded": "{app:s} mis à jour", - "appslist_fetched": "La liste d’applications mise à jour {appslist:s}", - "appslist_removed": "La liste d’applications {appslist:s} a été supprimée", - "appslist_retrieve_error": "Impossible de récupérer la liste d’applications distante {appslist:s} : {error:s}", - "appslist_unknown": "La liste d’applications {appslist:s} est inconnue.", + "appslist_fetched": "La liste d’applications mise à jour '{appslist:s}'", + "appslist_removed": "La liste d'applications '{appslist:s}' a été supprimée", + "appslist_retrieve_error": "Impossible de récupérer la liste d’applications distante '{appslist:s}' : {error:s}", + "appslist_unknown": "La liste d’applications '{appslist:s}' est inconnue.", "ask_current_admin_password": "Mot de passe d’administration actuel", "ask_email": "Adresse de courriel", "ask_firstname": "Prénom", @@ -124,18 +124,18 @@ "mail_forward_remove_failed": "Impossible de supprimer le courriel de transfert '{mail:s}'", "maindomain_change_failed": "Impossible de modifier le domaine principal", "maindomain_changed": "Le domaine principal modifié", - "monitor_disabled": "La supervision du serveur a été désactivé", - "monitor_enabled": "La supervision du serveur a été activé", + "monitor_disabled": "Surveillance du serveur est maintenant arrêté", + "monitor_enabled": "La supervision du serveur est maintenant allumée", "monitor_glances_con_failed": "Impossible de se connecter au serveur Glances", "monitor_not_enabled": "Le suivi de l’état du serveur n’est pas activé", "monitor_period_invalid": "Période de temps incorrecte", - "monitor_stats_file_not_found": "Le fichier de statistiques est introuvable", + "monitor_stats_file_not_found": "Impossible de trouver le fichier de statistiques", "monitor_stats_no_update": "Aucune donnée de l’état du serveur à mettre à jour", "monitor_stats_period_unavailable": "Aucune statistique n’est disponible pour la période", "mountpoint_unknown": "Point de montage inconnu", "mysql_db_creation_failed": "Impossible de créer la base de données MySQL", - "mysql_db_init_failed": "Impossible d’initialiser la base de données MySQL", - "mysql_db_initialized": "La base de données MySQL a été initialisée", + "mysql_db_init_failed": "Impossible d'initialiser la base de données MySQL", + "mysql_db_initialized": "La base de données MySQL est maintenant initialisée", "network_check_mx_ko": "L’enregistrement DNS MX n’est pas défini", "network_check_smtp_ko": "Le trafic courriel sortant (port 25 SMTP) semble bloqué par votre réseau", "network_check_smtp_ok": "Le trafic courriel sortant (port 25 SMTP) n’est pas bloqué", @@ -155,7 +155,7 @@ "path_removal_failed": "Impossible de supprimer le chemin {:s}", "pattern_backup_archive_name": "Doit être un nom de fichier valide avec un maximum de 30 caractères, et composé de caractères alphanumériques et -_. uniquement", "pattern_domain": "Doit être un nom de domaine valide (ex : mon-domaine.fr)", - "pattern_email": "Doit être une adresse de courriel valide (ex. : pseudo@domaine.fr)", + "pattern_email": "Doit être une adresse de courriel valide (ex. : pseudo@example.com)", "pattern_firstname": "Doit être un prénom valide", "pattern_lastname": "Doit être un nom valide", "pattern_listname": "Doit être composé uniquement de caractères alphanumériques et de tirets bas (aussi appelé tiret du 8 ou underscore)", @@ -183,7 +183,7 @@ "service_add_configuration": "Ajout du fichier de configuration {file:s}", "service_add_failed": "Impossible d’ajouter le service '{service:s}'", "service_added": "Le service '{service:s}' ajouté", - "service_already_started": "Le service '{service:s}' est déjà démarré", + "service_already_started": "Le service '{service:s}' est déjà en cours d'exécution", "service_already_stopped": "Le service '{service:s}' est déjà arrêté", "service_cmd_exec_failed": "Impossible d’exécuter la commande '{command:s}'", "service_conf_file_backed_up": "Le fichier de configuration '{conf}' a été sauvegardé dans '{backup}'", @@ -204,7 +204,7 @@ "service_disabled": "Le service '{service:s}' a été désactivé", "service_enable_failed": "Impossible d’activer le service '{service:s}'\n\nJournaux historisés récents : {logs:s}", "service_enabled": "Le service '{service:s}' a été activé", - "service_no_log": "Aucun journal historisé à afficher pour le service '{service:s}'", + "service_no_log": "Aucun journal à afficher pour le service '{service:s}'", "service_regenconf_dry_pending_applying": "Vérification des configurations en attentes qui pourraient être appliquées au le service '{service}' …", "service_regenconf_failed": "Impossible de régénérer la configuration pour les services : {services}", "service_regenconf_pending_applying": "Application des configurations en attentes pour le service '{service}' …", @@ -246,9 +246,9 @@ "user_updated": "L’utilisateur a été modifié", "yunohost_already_installed": "YunoHost est déjà installé", "yunohost_ca_creation_failed": "Impossible de créer l’autorité de certification", - "yunohost_configured": "YunoHost maintenant configuré", + "yunohost_configured": "YunoHost est maintenant configuré", "yunohost_installing": "L'installation de YunoHost est en cours …", - "yunohost_not_installed": "YunoHost n’est pas ou pas correctement installé. Veuillez exécuter 'yunohost tools postinstall'", + "yunohost_not_installed": "YunoHost n'est pas correctement installé. S'il vous plaît exécuter 'yunohost tools postinstall'", "certmanager_attempt_to_replace_valid_cert": "Vous êtes en train de vouloir remplacer un certificat correct et valide pour le domaine {domain:s} ! (Utilisez --force pour contourner cela)", "certmanager_domain_unknown": "Domaine {domain:s} inconnu", "certmanager_domain_cert_not_selfsigned": "Le certificat du domaine {domain:s} n’est pas auto-signé. Voulez-vous vraiment le remplacer ? (Utilisez --force pour cela)", @@ -280,13 +280,13 @@ "certmanager_domain_not_resolved_locally": "Le domaine {domain:s} ne peut être résolu depuis votre serveur YunoHost. Cela peut se produire si vous avez récemment modifié votre enregistrement DNS. Si c'est le cas, merci d’attendre quelques heures qu’il se propage. Si le problème persiste, envisager d’ajouter {domain:s} au fichier /etc/hosts. (Si vous savez ce que vous faites, utilisez --no-checks pour désactiver ces vérifications.)", "certmanager_http_check_timeout": "Expiration du délai lorsque le serveur a essayé de se contacter lui-même via HTTP en utilisant l'adresse IP public {ip:s} du domaine {domain:s}. Vous rencontrez peut-être un problème d’hairpinning ou alors le pare-feu/routeur en amont de votre serveur est mal configuré.", "certmanager_couldnt_fetch_intermediate_cert": "Expiration du délai lors de la tentative de récupération du certificat intermédiaire depuis Let’s Encrypt. L’installation ou le renouvellement du certificat a été annulé. Veuillez réessayer plus tard.", - "appslist_retrieve_bad_format": "Impossible de lire la liste des applications extraites {appslist: s}", + "appslist_retrieve_bad_format": "Impossible de lire la liste des applications extraites '{appslist: s}'", "domain_hostname_failed": "Échec de l’utilisation d’un nouveau nom d’hôte. Cela pourrait causer des soucis plus tard (peut-être que ça n’en causera pas).", "yunohost_ca_creation_success": "L’autorité de certification locale créée.", - "appslist_name_already_tracked": "Il y a déjà une liste d’applications enregistrée avec le nom {name:s} existe déjà.", + "appslist_name_already_tracked": "Une liste d'applications enregistrées portant le nom {name:s} existe déjà.", "appslist_url_already_tracked": "Il y a déjà une liste d’applications enregistrée avec l’URL {url:s}.", - "appslist_migrating": "Migration de la liste d’applications {appslist:s} …", - "appslist_could_not_migrate": "Impossible de migrer la liste {appslist:s} ! Impossible d’exploiter l’URL. L’ancienne tâche programmée a été conservée dans {bkp_file:s}.", + "appslist_migrating": "Migration de la liste d’applications '{appslist:s}' …", + "appslist_could_not_migrate": "Impossible de migrer la liste '{appslist:s}' ! Impossible d’exploiter l’URL. L’ancienne tâche programmée a été conservée dans {bkp_file:s}.", "appslist_corrupted_json": "Impossible de charger la liste d’applications. Il semble que {filename:s} soit endommager.", "app_already_installed_cant_change_url": "Cette application est déjà installée. L’URL ne peut pas être changé simplement par cette fonction. Regardez si cela est disponible avec `app changeurl`.", "app_change_no_change_url_script": "L’application {app_name:s} ne prend pas encore en charge le changement d’URL, vous pourriez avoir besoin de la mettre à jour.", @@ -320,7 +320,7 @@ "backup_archive_system_part_not_available": "La partie '{part:s}' du système n’est pas disponible dans cette sauvegarde", "backup_archive_mount_failed": "Le montage de l’archive de sauvegarde a échoué", "backup_archive_writing_error": "Impossible d'ajouter des fichiers '{source:s}' (nommés dans l'archive : '{dest:s}') à sauvegarder dans l'archive compressée '{archive:s}'", - "backup_ask_for_copying_if_needed": "Certains fichiers n’ont pas pu être préparés pour être sauvegardés en utilisant la méthode qui évite temporairement de gaspiller de l’espace sur le système. Pour réaliser la sauvegarde, {size:s} Mo doivent être temporairement utilisés. Acceptez-vous ?", + "backup_ask_for_copying_if_needed": "Voulez-vous effectuer la sauvegarde en utilisant {taille:s} temporairement? (Cette méthode est utilisée car certains fichiers n'ont pas pu être préparés avec une méthode plus efficace.)", "backup_borg_not_implemented": "La méthode de sauvegarde Borg n’est pas encore implémentée", "backup_cant_mount_uncompress_archive": "Impossible de monter en lecture seule le dossier de l’archive décompressée", "backup_copying_to_organize_the_archive": "Copie de {size:s} Mo pour organiser l’archive", @@ -350,7 +350,7 @@ "domain_dyndns_dynette_is_unreachable": "Impossible de contacter la dynette YunoHost. Soit YunoHost n’est pas correctement connecté à internet, soit le serveur de dynette est en panne. Erreur : {error}", "migrations_backward": "Migration en arrière.", "migrations_bad_value_for_target": "Nombre invalide pour le paramètre target, les numéros de migration sont 0 ou {}", - "migrations_cant_reach_migration_file": "Impossible d'accéder aux fichiers de migration sur le chemin% s", + "migrations_cant_reach_migration_file": "Impossible d'accéder aux fichiers de migration via le chemin '%s'", "migrations_current_target": "La cible de migration est {}", "migrations_error_failed_to_load_migration": "ERREUR : échec du chargement de migration {number} {name}", "migrations_forward": "Migration en avant", @@ -368,9 +368,9 @@ "ask_path": "Chemin", "dyndns_could_not_check_provide": "Impossible de vérifier si {provider:s} peut fournir {domain:s}.", "dyndns_domain_not_provided": "Le fournisseur DynDNS {provider:s} ne peut pas fournir le domaine {domain:s}.", - "app_make_default_location_already_used": "Impossible de configurer l’application '{app}' par défaut pour le domaine {domain} car il est déjà utilisé par l'application '{other_app}'", + "app_make_default_location_already_used": "Impossible de configurer l’application '{app}' par défaut pour le domaine '{domain}' car il est déjà utilisé par l'application '{other_app}'", "app_upgrade_app_name": "Mise à jour de l’application {app} …", - "backup_output_symlink_dir_broken": "Vous avez un lien symbolique cassé à la place de votre dossier d’archives « {path:s} ». Vous pourriez avoir une configuration personnalisée pour sauvegarder vos données sur un autre système de fichiers, dans ce cas vous avez probablement oublié de monter ou de connecter votre disque dur ou votre clé USB.", + "backup_output_symlink_dir_broken": "Votre répertoire d'archivage '{chemin:s}' est un lien symbolique brisé. Peut-être avez-vous oublié de re/monter ou de brancher le support de stockage sur lequel il pointe.", "migrate_tsig_end": "La migration à HMAC-SHA-512 est terminée", "migrate_tsig_failed": "La migration du domaine DynDNS {domain} à hmac-sha512 a échoué. Annulation des modifications. Erreur : {error_code} - {error}", "migrate_tsig_start": "L’algorithme de génération des clefs n’est pas suffisamment sécurisé pour la signature TSIG du domaine '{domain}', lancement de la migration vers HMAC-SHA-512 qui est plus sécurisé", @@ -473,7 +473,7 @@ "good_practices_about_admin_password": "Vous êtes maintenant sur le point de définir un nouveau mot de passe d’administration. Le mot de passe doit comporter au moins 8 caractères – bien qu’il soit recommandé d’utiliser un mot de passe plus long (c’est-à-dire une phrase secrète) et/ou d’utiliser différents types de caractères (majuscules, minuscules, chiffres et caractères spéciaux).", "good_practices_about_user_password": "Vous êtes maintenant sur le point de définir un nouveau mot de passe utilisateur. Le mot de passe doit comporter au moins 8 caractères - bien qu’il soit recommandé d’utiliser un mot de passe plus long (c’est-à-dire une phrase secrète) et/ou d’utiliser différents types de caractères tels que : majuscules, minuscules, chiffres et caractères spéciaux.", "migration_description_0006_sync_admin_and_root_passwords": "Synchroniser les mots de passe admin et root", - "migration_0006_disclaimer": "YunoHost s’attendra désormais à ce que les mots de passe admin et root soient synchronisés. En exécutant cette migration, votre mot de passe root sera remplacé par le mot de passe administrateur.", + "migration_0006_disclaimer": "YunoHost s'attend maintenant à ce que les mots de passe administrateur et racine soient synchronisés. Cette migration remplace votre mot de passe root par le mot de passe administrateur.", "migration_0006_done": "Votre mot de passe root a été remplacé par celui de votre adminitrateur.", "password_listed": "Ce mot de passe est l'un des mots de passe les plus utilisés dans le monde. Veuillez choisir quelque chose d'un peu plus singulier.", "password_too_simple_1": "Le mot de passe doit comporter au moins 8 caractères", @@ -485,7 +485,7 @@ "app_not_upgraded": "L’application {failed_app} n’a pas été mise à jour et par conséquence les applications suivantes n’ont pas été mises à jour : {apps}", "app_start_install": "Installation de l'application {app} …", "app_start_remove": "Suppression de l'application {app} …", - "app_start_backup": "Collecte des fichiers devant être sauvegardés pour {app} …", + "app_start_backup": "Collecte des fichiers devant être sauvegardés pour l'application {app} …", "app_start_restore": "Restauration de l'application {app} …", "app_upgrade_several_apps": "Les applications suivantes seront mises à jour : {apps}", "ask_new_domain": "Nouveau domaine", @@ -504,14 +504,14 @@ "hook_json_return_error": "Échec de la lecture au retour du script {path:s}. Erreur : {msg:s}. Contenu brut : {raw_content}", "migration_description_0007_ssh_conf_managed_by_yunohost_step1": "La configuration SSH sera gérée par YunoHost (étape 1, automatique)", "migration_description_0008_ssh_conf_managed_by_yunohost_step2": "La configuration SSH sera gérée par YunoHost (étape 2, manuelle)", - "migration_0007_cancelled": "YunoHost n'a pas réussi à améliorer la façon dont est gérée votre configuration SSH.", + "migration_0007_cancelled": "Impossible d'améliorer la gestion de votre configuration SSH.", "migration_0007_cannot_restart": "SSH ne peut pas être redémarré après avoir essayé d'annuler la migration numéro 6.", "migration_0008_general_disclaimer": "Pour améliorer la sécurité de votre serveur, il est recommandé de laisser YunoHost gérer la configuration SSH. Votre configuration SSH actuelle diffère de la configuration recommandée. Si vous laissez YunoHost la reconfigurer, la façon dont vous vous connectez à votre serveur via SSH changera comme suit :", "migration_0008_port": "- Vous devrez vous connecter en utilisant le port 22 au lieu de votre actuel port SSH personnalisé. N'hésitez pas à le reconfigurer ;", "migration_0008_root": "- Vous ne pourrez pas vous connecter en tant que root via SSH. Au lieu de cela, vous devrez utiliser l'utilisateur admin ;", "migration_0008_dsa": "- La clé DSA sera désactivée. Par conséquent, il se peut que vous ayez besoin d'invalider un avertissement effrayant de votre client SSH afin de revérifier l'empreinte de votre serveur ;", - "migration_0008_warning": "Si vous comprenez ces avertissements et que vous acceptez de laisser YunoHost remplacer votre configuration actuelle, exécutez la migration. Sinon, vous pouvez également passer la migration, bien que cela ne soit pas recommandé.", - "migration_0008_no_warning": "Aucun risque majeur n'a été identifié concernant l'écrasement de votre configuration SSH - mais nous ne pouvons pas en être absolument sûrs ;) ! Si vous acceptez de laisser YunoHost remplacer votre configuration actuelle, exécutez la migration. Sinon, vous pouvez également passer la migration, bien que cela ne soit pas recommandé.", + "migration_0008_warning": "Si vous comprenez ces avertissements et souhaitez que YunoHost écrase votre configuration actuelle, exécutez la migration. Sinon, vous pouvez également ignorer la migration, bien que cela ne soit pas recommandé.", + "migration_0008_no_warning": "Remplacer votre configuration SSH devrait être sûr, bien que cela ne puisse être promis! Exécutez la migration pour la remplacer. Sinon, vous pouvez également ignorer la migration, bien que cela ne soit pas recommandé.", "migrations_success": "Migration {number} {name} réussie !", "pattern_password_app": "Désolé, les mots de passe ne doivent pas contenir les caractères suivants : {forbidden_chars}", "root_password_replaced_by_admin_password": "Votre mot de passe root a été remplacé par votre mot de passe administrateur.", @@ -523,7 +523,7 @@ "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`.", - "app_action_cannot_be_ran_because_required_services_down": "Cette application requiert certains services qui sont actuellement arrêtés. Avant de continuer, vous devriez essayer de redémarrer les services suivants (et éventuellement rechercher pourquoi ils sont arrêtés) : {services}", + "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 '{}'", "migration_0009_not_needed": "Cette migration semble avoir déjà été jouée ? On l'ignore.", @@ -558,8 +558,8 @@ "tools_upgrade_special_packages_completed": "La mise à jour des paquets de YunoHost est finie!\nPressez [Entrée] pour revenir à la ligne de commande", "updating_app_lists": "Récupération des mises à jour des applications disponibles…", "dpkg_lock_not_available": "Cette commande ne peut être exécutée actuellement car un autre programme semble utiliser le verrou de dpkg (gestionnaire de paquets)", - "tools_upgrade_cant_unhold_critical_packages": "Impossible de dé-marquer les paquets critiques …", - "tools_upgrade_special_packages_explanation": "Cette opération prendra fin mais la mise à jour spécifique continuera en arrière-plan. Veuillez ne pas lancer d'autre action sur votre serveur dans les 10 prochaines minutes (en fonction de la vitesse de votre matériel). Une fois que c'est fait, vous devrez peut-être vous reconnecter sur le panel d'administration web. Le journal de la mise à jour sera disponible dans Outils > Log (dans le panel d'administration web) ou dans la liste des journaux YunoHost (en ligne de commande).", + "tools_upgrade_cant_unhold_critical_packages": "Impossible de conserver les paquets critiques…", + "tools_upgrade_special_packages_explanation": "Cette action se terminera, mais la mise à niveau spéciale réelle continuera en arrière-plan. Veuillez ne pas lancer d’autres actions sur votre serveur au cours des 10 prochaines minutes (en fonction de la vitesse du matériel). Une fois cela fait, vous devrez peut-être vous reconnecter à la page Webadmin. Le journal de mise à niveau sera disponible dans Outils → Journal (sur la page Webadmin) ou dans la \"liste des journaux yunohost\" (à partir de la ligne de commande).", "update_apt_cache_failed": "Impossible de mettre à jour le cache APT (gestionnaire de paquets Debian). Voici un extrait du fichier sources.list qui pourrait vous aider à identifier les lignes problématiques :\n{sourceslist}", "update_apt_cache_warning": "Des erreurs se sont produites lors de la mise à jour du cache APT (gestionnaire de paquets Debian). Voici un extrait des lignes du fichier sources.list qui pourrait vous aider à identifier les lignes problématiques :\n{sourceslist}", "apps_permission_not_found": "Aucune permission trouvée pour les applications installées", @@ -591,22 +591,22 @@ "mailbox_disabled": "La boîte aux lettres est désactivée pour l'utilisateur {user:s}", "app_action_broke_system": "Cette action semble avoir cassé des services important : {services}", "apps_already_up_to_date": "Toutes les applications sont déjà à jour", - "app_upgrade_stopped": "La mise à jour de toutes les applications a été arrêtée afin d’éviter d’éventuels dommages dus à l’échec de la mise à jour de l’application précédente", + "app_upgrade_stopped": "La mise à niveau de toutes les applications s'est arrêtée pour éviter tout dommage, car une application n'a pas pu être mise à niveau.", "migration_0011_create_group": "Créer un groupe pour chaque utilisateur…", - "migration_0011_done": "Migration réussie. Vous êtes maintenant en mesure de gérer des groupes d'utilisateurs.", + "migration_0011_done": "Migration terminée. Vous êtes maintenant en mesure de gérer des groupes d'utilisateurs.", "migrations_must_provide_explicit_targets": "Vous devez fournir des cibles explicites lorsque vous utilisez '--skip' ou '--force-rerun'", - "migrations_no_such_migration": "Il n'y a pas de migration appelée {id}", + "migrations_no_such_migration": "Il n'y a pas de migration appelée '{id}'", "migrations_pending_cant_rerun": "Ces migrations étant toujours en attente, vous ne pouvez pas les exécuter à nouveau: {ids}", "migration_description_0012_postgresql_password_to_md5_authentication": "Forcer l'authentification PostgreSQL à utiliser MD5 pour les connexions locales", "migrations_exclusive_options": "'auto', '--skip' et '--force-rerun' sont des options mutuellement exclusives.", "migrations_not_pending_cant_skip": "Ces migrations ne sont pas en attente et ne peuvent donc pas être ignorées: {ids}", - "migration_0011_can_not_backup_before_migration": "La sauvegarde du système avant la migration a échoué. La migration a échoué. Erreur: {error: s}", + "migration_0011_can_not_backup_before_migration": "La sauvegarde du système n'a pas pu être terminée avant l'échec de la migration. Erreur: {erreur:s}", "migration_0011_migrate_permission": "Migration des autorisations des paramètres des applications vers LDAP…", - "migration_0011_migration_failed_trying_to_rollback": "La migration a échoué… essayait de restauration du système.", + "migration_0011_migration_failed_trying_to_rollback": "Impossible de migrer… en essayant de restaurer le système.", "migration_0011_rollback_success": "Système restauré.", "migration_0011_update_LDAP_database": "Mise à jour de la base de données LDAP…", "system_groupname_exists": "Le nom de groupe existe déjà dans le groupe du systèmes", - "tools_update_failed_to_app_fetchlist": "Impossible de mettre à jour les applications de YunoHost car: {error}", + "tools_update_failed_to_app_fetchlist": "Impossible de mettre à jour les listes d'applications de YunoHost car: {error}", "user_already_in_group": "L'utilisateur '{user:}' est déjà dans le groupe '{group: s}'", "user_not_in_group": "L'utilisateur '{user: s}' ne fait pas partie du groupe {group: s}", "migration_0011_backup_before_migration": "Création d'une sauvegarde des paramètres de la base de données LDAP et des applications avant la migration.", @@ -620,12 +620,12 @@ "dyndns_provider_unreachable": "Impossible d’atteindre le fournisseur Dyndns {provider}: votre YunoHost n’est pas correctement connecté à Internet ou le serveur Dynette est en panne.", "migration_0011_update_LDAP_schema": "Mise à jour du schéma LDAP…", "migrations_already_ran": "Ces migrations sont déjà effectuées: {ids}", - "migrations_dependencies_not_satisfied": "Impossible d'exécuter la migration {id} car vous devez d'abord exécuter ces migrations: {dependencies_id}", + "migrations_dependencies_not_satisfied": "Exécutez ces migrations: '{dependencies_id}', avant migration {id}.", "migrations_failed_to_load_migration": "Impossible de charger la migration {id}: {error}", "migrations_running_forward": "Exécution de la migration {id}…", "migrations_success_forward": "Migration {id} terminée", "need_define_permission_before": "Redéfinissez l'autorisation à l'aide de 'yunohost user permission add -u USER' avant de supprimer un groupe autorisé", - "operation_interrupted": "L'opération a été interrompue manuellement", + "operation_interrupted": "L'opération a-t-elle été interrompue manuellement ?", "permission_already_clear": "L'autorisation '{permission: s}' est déjà vide pour l'application {app: s}", "permission_already_exist": "L'autorisation '{permission}' existe déjà", "permission_created": "Permission '{permission:s}' créée", @@ -648,12 +648,12 @@ "log_user_group_create": "Créer '{}' groupe", "log_user_permission_update": "Mise à jour des accès pour la permission '{}'", "log_user_permission_reset": "Réinitialiser la permission '{}'", - "migration_0011_failed_to_remove_stale_object": "Impossible de supprimer un objet obsolète {dn}: {error}", + "migration_0011_failed_to_remove_stale_object": "Impossible de supprimer un objet périmé {dn}: {error}", "permission_already_allowed": "Le groupe '{group}' a déjà l'autorisation '{permission}' activée '", "permission_already_disallowed": "Le groupe '{group}' a déjà l'autorisation '{permission}' désactivé '", "permission_cannot_remove_main": "Supprimer une autorisation principale n'est pas autorisé", - "user_already_exists": "L'utilisateur {user} existe déjà", - "app_full_domain_unavailable": "Désolé, cette application nécessite l'installation d'un domaine complet, mais d'autres applications sont déjà installées sur le domaine '{domain}'. Une solution possible consiste à ajouter et à utiliser un sous-domaine dédié à cette application.", + "user_already_exists": "L'utilisateur '{user}' existe déjà", + "app_full_domain_unavailable": "Désolé, cette application doit être installée sur un domaine qui lui est propre, mais d'autres applications sont déjà installées sur le domaine '{domain}'. Vous pouvez utiliser un sous-domaine dédié à cette application à la place.", "group_cannot_edit_all_users": "Le groupe 'all_users' ne peut pas être édité manuellement. C'est un groupe spécial destiné à contenir tous les utilisateurs enregistrés dans YunoHost", "group_cannot_edit_visitors": "Le groupe 'visiteurs' ne peut pas être édité manuellement. C'est un groupe spécial représentant les visiteurs anonymes", "group_cannot_edit_primary_group": "Le groupe '{group}' ne peut pas être édité manuellement. C'est le groupe principal destiné à ne contenir qu'un utilisateur spécifique.", From 7c95b8d5082109abfd53146fa21d2789116a9dbc Mon Sep 17 00:00:00 2001 From: Kayou Date: Fri, 25 Oct 2019 19:39:02 +0900 Subject: [PATCH 0362/3170] fix find udp port --- data/helpers.d/network | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/network b/data/helpers.d/network index 0f75cb165..a4a679d3a 100644 --- a/data/helpers.d/network +++ b/data/helpers.d/network @@ -17,7 +17,7 @@ ynh_find_port () { ynh_handle_getopts_args "$@" test -n "$port" || ynh_die --message="The argument of ynh_find_port must be a valid port." - while netcat -z 127.0.0.1 $port # Check if the port is free + while netstat -ltu | grep -q -w :$port # Check if the port is free do port=$((port+1)) # Else, pass to next port done From 6cd12d450afea7f53d1f40eda83db04b88cecf26 Mon Sep 17 00:00:00 2001 From: Kayou Date: Fri, 25 Oct 2019 19:55:33 +0900 Subject: [PATCH 0363/3170] Update data/helpers.d/network Co-Authored-By: Alexandre Aubin --- data/helpers.d/network | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/network b/data/helpers.d/network index a4a679d3a..5fc41fa50 100644 --- a/data/helpers.d/network +++ b/data/helpers.d/network @@ -17,7 +17,7 @@ ynh_find_port () { ynh_handle_getopts_args "$@" test -n "$port" || ynh_die --message="The argument of ynh_find_port must be a valid port." - while netstat -ltu | grep -q -w :$port # Check if the port is free + while netstat -nltu | grep -q -w :$port # Check if the port is free do port=$((port+1)) # Else, pass to next port done From 89ab4bd4dc7083d43666ce8ad260fd6dd4c77b48 Mon Sep 17 00:00:00 2001 From: Rafi59 Date: Sat, 26 Oct 2019 13:25:38 +0200 Subject: [PATCH 0364/3170] Use ss instead of netstat --- data/helpers.d/network | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/network b/data/helpers.d/network index 5fc41fa50..948a327b0 100644 --- a/data/helpers.d/network +++ b/data/helpers.d/network @@ -17,7 +17,7 @@ ynh_find_port () { ynh_handle_getopts_args "$@" test -n "$port" || ynh_die --message="The argument of ynh_find_port must be a valid port." - while netstat -nltu | grep -q -w :$port # Check if the port is free + while ss -nltu | grep -q -w :$port # Check if the port is free do port=$((port+1)) # Else, pass to next port done From db7d68cf926455d22219aee65a561c6f6c5edd6f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 26 Oct 2019 14:43:54 +0200 Subject: [PATCH 0365/3170] Fix weird fr string.. --- locales/fr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/fr.json b/locales/fr.json index 37e5f95c1..53a3f051e 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -248,7 +248,7 @@ "yunohost_ca_creation_failed": "Impossible de créer l’autorité de certification", "yunohost_configured": "YunoHost est maintenant configuré", "yunohost_installing": "L'installation de YunoHost est en cours …", - "yunohost_not_installed": "YunoHost n'est pas correctement installé. S'il vous plaît exécuter 'yunohost tools postinstall'", + "yunohost_not_installed": "YunoHost n'est pas correctement installé. Veuillez exécuter 'yunohost tools postinstall'", "certmanager_attempt_to_replace_valid_cert": "Vous êtes en train de vouloir remplacer un certificat correct et valide pour le domaine {domain:s} ! (Utilisez --force pour contourner cela)", "certmanager_domain_unknown": "Domaine {domain:s} inconnu", "certmanager_domain_cert_not_selfsigned": "Le certificat du domaine {domain:s} n’est pas auto-signé. Voulez-vous vraiment le remplacer ? (Utilisez --force pour cela)", From c2e5412a995fcbac02abc997314bcb501e124706 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Fri, 25 Oct 2019 09:59:46 +0000 Subject: [PATCH 0366/3170] Translated using Weblate (French) Currently translated at 100.0% (561 of 561 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 53a3f051e..3cb3c8fa9 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -649,7 +649,7 @@ "log_user_permission_update": "Mise à jour des accès pour la permission '{}'", "log_user_permission_reset": "Réinitialiser la permission '{}'", "migration_0011_failed_to_remove_stale_object": "Impossible de supprimer un objet périmé {dn}: {error}", - "permission_already_allowed": "Le groupe '{group}' a déjà l'autorisation '{permission}' activée '", + "permission_already_allowed": "Le groupe '{group}' a déjà l'autorisation '{permission}' activée", "permission_already_disallowed": "Le groupe '{group}' a déjà l'autorisation '{permission}' désactivé '", "permission_cannot_remove_main": "Supprimer une autorisation principale n'est pas autorisé", "user_already_exists": "L'utilisateur '{user}' existe déjà", @@ -663,5 +663,6 @@ "permission_currently_allowed_for_visitors": "Cette autorisation est actuellement accordée aux visiteurs en plus d'autres groupes. Vous voudrez probablement supprimer l'autorisation \"visiteurs\" ou supprimer les autres groupes auxquels il est actuellement attribué.", "permission_currently_allowed_for_all_users": "Cette autorisation est actuellement accordée à tous les utilisateurs en plus des autres groupes. Vous voudrez probablement soit supprimer l'autorisation 'all_users', soit supprimer les autres groupes auxquels il est actuellement autorisé.", "app_install_failed": "Impossible d'installer {app}: {error}", - "app_install_script_failed": "Une erreur est survenue dans le script d'installation de l'application" + "app_install_script_failed": "Une erreur est survenue dans le script d'installation de l'application", + "permission_require_account": "Permission {permission} n'a de sens que pour les utilisateurs ayant un compte et ne peut donc pas être activé pour les visiteurs." } From af703bce47dce5d4925e553798504e9e37a361a7 Mon Sep 17 00:00:00 2001 From: advocatux Date: Thu, 24 Oct 2019 16:53:15 +0000 Subject: [PATCH 0367/3170] Translated using Weblate (Spanish) Currently translated at 100.0% (561 of 561 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/es/ --- locales/es.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/es.json b/locales/es.json index afa323269..d216b8a9a 100644 --- a/locales/es.json +++ b/locales/es.json @@ -636,5 +636,6 @@ "migration_0011_slapd_config_will_be_overwritten": "Parece que ha editado manualmente la configuración de slapd. Para esta migración crítica, YunoHost necesita forzar la actualización de la configuración de slapd. Los archivos originales se respaldarán en {conf_backup_folder}.", "permission_already_up_to_date": "El permiso no se ha actualizado porque las peticiones de incorporación o eliminación ya coinciden con el estado actual.", "permission_currently_allowed_for_visitors": "Este permiso se concede actualmente a los visitantes además de otros grupos. Probablemente quiere o eliminar el permiso de «visitors» o eliminar los otros grupos a los que está otorgado actualmente.", - "permission_currently_allowed_for_all_users": "Este permiso se concede actualmente a todos los usuarios además de los otros grupos. Probablemente quiere o eliminar el permiso de «all_users» o eliminar los otros grupos a los que está otorgado actualmente." + "permission_currently_allowed_for_all_users": "Este permiso se concede actualmente a todos los usuarios además de los otros grupos. Probablemente quiere o eliminar el permiso de «all_users» o eliminar los otros grupos a los que está otorgado actualmente.", + "permission_require_account": "El permiso {permission} solo tiene sentido para usuarios con una cuenta y, por lo tanto, no se puede activar para visitantes." } From 6930fa82e71b07865c9549b3b7a11a46322e27e2 Mon Sep 17 00:00:00 2001 From: Filip Bengtsson Date: Fri, 25 Oct 2019 20:49:31 +0000 Subject: [PATCH 0368/3170] Translated using Weblate (Swedish) Currently translated at 0.2% (1 of 561 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/sv/ --- locales/sv.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/locales/sv.json b/locales/sv.json index 0967ef424..4960d43aa 100644 --- a/locales/sv.json +++ b/locales/sv.json @@ -1 +1,3 @@ -{} +{ + "password_too_simple_1": "Lösenordet måste bestå av minst åtta tecken" +} From 1b5c1c39bc5216dfb08173f0794aef94d5dac5c9 Mon Sep 17 00:00:00 2001 From: xaloc33 Date: Fri, 25 Oct 2019 17:27:26 +0000 Subject: [PATCH 0369/3170] Translated using Weblate (Catalan) Currently translated at 100.0% (561 of 561 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ca/ --- locales/ca.json | 77 +++++++++++++++++++++++++------------------------ 1 file changed, 39 insertions(+), 38 deletions(-) diff --git a/locales/ca.json b/locales/ca.json index 04ee413b9..32bbfb50f 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -12,14 +12,14 @@ "app_change_no_change_url_script": "L'aplicació {app_name:s} encara no permet canviar la seva URL, es possible que s'hagi d'actualitzar.", "app_change_url_failed_nginx_reload": "No s'ha pogut tornar a carregar NGINX. Aquí teniu el resultat de \"nginx -t\":\n{nginx_errors:s}", "app_change_url_identical_domains": "L'antic i el nou domini/camí són idèntics ('{domain:s}{path:s}'), no hi ha res per fer.", - "app_change_url_no_script": "Aquesta aplicació '{app_name:s}' encara no permet modificar la URL. Potser s'ha d'actualitzar.", + "app_change_url_no_script": "L'aplicació '{app_name:s}' encara no permet modificar la URL. Potser s'ha d'actualitzar.", "app_change_url_success": "La URL de {app:s} ara és {domain:s}{path:s}", "app_extraction_failed": "No s'han pogut extreure els fitxers d'instal·lació", "app_id_invalid": "ID de l'aplicació incorrecte", "app_incompatible": "L'aplicació {app} no és compatible amb la teva versió de YunoHost", "app_install_files_invalid": "Aquests fitxers no es poden instal·lar", "app_location_already_used": "L'aplicació «{app}» ja està instal·lada en ({path})", - "app_make_default_location_already_used": "No es pot fer l'aplicació '{app}' per defecte en el domini {domain} ja que ja és utilitzat per una altra aplicació '{other_app}'", + "app_make_default_location_already_used": "No es pot fer l'aplicació '{app}' per defecte en el domini «{domain}» ja que ja és utilitzat per una altra aplicació '{other_app}'", "app_location_install_failed": "No s'ha pogut instal·lar l'aplicació aquí ja que entra en conflicte amb l'aplicació «{other_app}» ja instal·lada a «{other_path}»", "app_location_unavailable": "Aquesta URL no està disponible o entra en conflicte amb aplicacions ja instal·lades:\n{apps:s}", "app_manifest_invalid": "Hi ha algun error amb el manifest de l'aplicació: {error}", @@ -40,14 +40,14 @@ "app_upgrade_some_app_failed": "No s'han pogut actualitzar algunes aplicacions", "app_upgraded": "S'ha actualitzat {app:s}", "appslist_corrupted_json": "No s'han pogut carregar les llistes d'aplicacions. Sembla que {filename:s} està danyat.", - "appslist_could_not_migrate": "No s'ha pogut migrar la llista d'aplicacions {appslist:s}! No s'ha pogut analitzar la URL... L'antic cronjob s'ha guardat a {bkp_file:s}.", - "appslist_fetched": "S'ha actualitzat la llista d'aplicacions {appslist:s}", - "appslist_migrating": "Migrant la llista d'aplicacions {appslist:s}…", + "appslist_could_not_migrate": "No s'ha pogut migrar la llista d'aplicacions «{appslist:s}»! No s'ha pogut analitzar la URL... L'antic cronjob s'ha guardat a {bkp_file:s}.", + "appslist_fetched": "S'ha actualitzat la llista d'aplicacions «{appslist:s}»", + "appslist_migrating": "Migrant la llista d'aplicacions «{appslist:s}»…", "appslist_name_already_tracked": "Ja hi ha una llista d'aplicacions registrada amb el nom {name:s}.", - "appslist_removed": "S'ha eliminat la llista d'aplicacions {appslist:s}", - "appslist_retrieve_bad_format": "No s'ha pogut llegir la llista d'aplicacions obtinguda {appslist:s}", - "appslist_retrieve_error": "No s'ha pogut obtenir la llista d'aplicacions remota {appslist:s}: {error:s}", - "appslist_unknown": "La llista d'aplicacions {appslist:s} es desconeguda.", + "appslist_removed": "S'ha eliminat la llista d'aplicacions «{appslist:s}»", + "appslist_retrieve_bad_format": "No s'ha pogut llegir la llista d'aplicacions obtinguda «{appslist:s}»", + "appslist_retrieve_error": "No s'ha pogut obtenir la llista d'aplicacions remota «{appslist:s}»: {error:s}", + "appslist_unknown": "La llista d'aplicacions «{appslist:s}» es desconeguda.", "appslist_url_already_tracked": "Ja hi ha una llista d'aplicacions registrada amb al URL {url:s}.", "ask_current_admin_password": "Contrasenya d'administrador actual", "ask_email": "Adreça de correu electrònic", @@ -73,7 +73,7 @@ "backup_archive_open_failed": "No s'ha pogut obrir l'arxiu de la còpia de seguretat", "backup_archive_system_part_not_available": "La part «{part:s}» del sistema no està disponible en aquesta copia de seguretat", "backup_archive_writing_error": "No es poden afegir els arxius «{source:s}» (anomenats en l'arxiu «{dest:s}») a l'arxiu comprimit de la còpia de seguretat «{archive:s}»", - "backup_ask_for_copying_if_needed": "Alguns fitxers no s'han pogut preparar per la còpia de seguretat utilitzant el mètode que evita malgastar espai del sistema temporalment. Per fer la còpia de seguretat, s'han d'utilitzar {size:s}MB temporalment. Hi esteu d'acord?", + "backup_ask_for_copying_if_needed": "Voleu fer la còpia de seguretat utilitzant {size:s} MB temporalment? (S'utilitza aquest mètode ja que alguns dels fitxers no s'han pogut preparar utilitzar un mètode més eficient.)", "backup_borg_not_implemented": "El mètode de còpia de seguretat Borg encara no està implementat", "backup_cant_mount_uncompress_archive": "No es pot carregar l'arxiu descomprimit com a protegit contra escriptura", "backup_cleaning_failed": "No s'ha pogut netejar el directori temporal de la còpia de seguretat", @@ -83,10 +83,10 @@ "backup_creating_archive": "Creant l'arxiu de la còpia de seguretat…", "aborting": "Avortant.", "app_not_upgraded": "L'aplicació «{failed_app}» no s'ha pogut actualitzar, i com a conseqüència l'actualització de les següents aplicacions ha estat cancel·lada: {apps}", - "app_start_install": "instal·lant l'aplicació {app}…", - "app_start_remove": "Eliminant l'aplicació {app}…", - "app_start_backup": "Recuperant els fitxers pels que s'ha de fer una còpia de seguretat per {app}…", - "app_start_restore": "Recuperant l'aplicació {app}…", + "app_start_install": "instal·lant l'aplicació «{app}»…", + "app_start_remove": "Eliminant l'aplicació «{app}»…", + "app_start_backup": "Recuperant els fitxers pels que s'ha de fer una còpia de seguretat per «{app}»…", + "app_start_restore": "Recuperant l'aplicació «{app}»…", "app_upgrade_several_apps": "S'actualitzaran les següents aplicacions: {apps}", "ask_new_domain": "Nou domini", "ask_new_path": "Nou camí", @@ -118,7 +118,7 @@ "backup_output_directory_forbidden": "Escolliu un directori de sortida different. Les còpies de seguretat no es poden crear ni dins els directoris /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var ni dins els subdirectoris /home/yunohost.backup/archives", "backup_output_directory_not_empty": "Heu d'escollir un directori de sortida buit", "backup_output_directory_required": "Heu d'especificar un directori de sortida per la còpia de seguretat", - "backup_output_symlink_dir_broken": "Teniu un enllaç simbòlic trencat en lloc del directori del arxiu «{path:s}». Pot ser teniu una configuració per la còpia de seguretat específica en un altre sistema de fitxers, si és el cas segurament heu oblidat muntar o connectar el disc dur o la clau USB.", + "backup_output_symlink_dir_broken": "El directori del arxiu «{path:s}» es un enllaç simbòlic trencat. Pot ser heu oblidat muntar, tornar a muntar o connectar el mitja d'emmagatzematge al que apunta.", "backup_php5_to_php7_migration_may_fail": "No s'ha pogut convertir l'arxiu per suportar PHP 7, pot ser que no es puguin restaurar les vostres aplicacions PHP (raó: {error:s})", "backup_running_hooks": "Executant els scripts de la còpia de seguretat…", "backup_system_part_failed": "No s'ha pogut fer la còpia de seguretat de la part \"{part:s}\" del sistema", @@ -149,7 +149,7 @@ "certmanager_self_ca_conf_file_not_found": "No s'ha trobat el fitxer de configuració per l'autoritat del certificat auto-signat (fitxer: {file:s})", "certmanager_unable_to_parse_self_CA_name": "No s'ha pogut analitzar el nom de l'autoritat del certificat auto-signat (fitxer: {file:s})", "confirm_app_install_warning": "Atenció: Aquesta aplicació funciona, però no està ben integrada amb YunoHost. Algunes característiques com la autenticació única i la còpia de seguretat/restauració poden no estar disponibles. Voleu instal·lar-la de totes maneres? [{answers:s}] ", - "confirm_app_install_danger": "PERILL! Aquesta aplicació encara és experimental (si no és que no funciona directament)! No hauríeu d'instal·lar-la a no ser que sapigueu el que feu. No obtindreu CAP AJUDA si l'aplicació no funciona o trenca el sistema... Si accepteu el risc, escriviu «{answers:s}»", + "confirm_app_install_danger": "PERILL! Aquesta aplicació encara és experimental (si no és que no funciona directament)! No hauríeu d'instal·lar-la a no ser que sapigueu el que feu. No obtindreu CAP AJUDA si l'aplicació no funciona o trenca el sistema… Si accepteu el risc, escriviu «{answers:s}»", "confirm_app_install_thirdparty": "PERILL! Aquesta aplicació no es part del catàleg d'aplicacions de YunoHost. La instal·lació d'aplicacions de terceres parts pot comprometre la integritat i seguretat del seu sistema. No hauríeu d'instal·lar-ne a no ser que sapigueu el que feu. No obtindreu CAP AJUDA si l'aplicació no funciona o trenca el sistema… Si accepteu el risc, escriviu «{answers:s}»", "custom_app_url_required": "Heu de especificar una URL per actualitzar la vostra aplicació personalitzada {app:s}", "custom_appslist_name_required": "Heu d'especificar un nom per la vostra llista d'aplicacions personalitzada", @@ -158,7 +158,7 @@ "diagnosis_monitor_disk_error": "No es poden monitorar els discs: {error}", "diagnosis_monitor_network_error": "No es pot monitorar la xarxa: {error}", "diagnosis_monitor_system_error": "No es pot monitorar el sistema: {error}", - "diagnosis_no_apps": "No hi ha cap aplicació instal·lada", + "diagnosis_no_apps": "Aquesta aplicació no està instal·lada", "admin_password_too_long": "Trieu una contrasenya de menys de 127 caràcters", "dpkg_is_broken": "No es pot fer això en aquest instant perquè dpkg/APT (els gestors de paquets del sistema) sembla estar mal configurat… Podeu intentar solucionar-ho connectant-vos per SSH i executant «sudo dpkg --configure -a».", "dnsmasq_isnt_installed": "sembla que dnsmasq no està instal·lat, executeu \"apt-get remove bind9 && apt-get install dnsmasq\"", @@ -169,7 +169,7 @@ "domain_deleted": "S'ha eliminat el domini", "domain_deletion_failed": "No s'ha pogut eliminar el domini {domini}: {error}", "domain_exists": "El domini ja existeix", - "app_action_cannot_be_ran_because_required_services_down": "Aquesta aplicació necessita serveis que estan aturats. Abans de continuar, hauríeu d'intentar arrancar de nou els serveis següents (i també investigar perquè estan aturats): {services}", + "app_action_cannot_be_ran_because_required_services_down": "Aquests serveis necessaris haurien d'estar funcionant per poder executar aquesta acció: {services} Intenteu reiniciar-los per continuar (i possiblement investigar perquè estan aturats).", "domain_dns_conf_is_just_a_recommendation": "Aquesta ordre mostra la configuració *recomanada*. En cap cas fa la configuració del DNS. És la vostra responsabilitat configurar la zona DNS en el vostre registrar en acord amb aquesta recomanació.", "domain_dyndns_already_subscribed": "Ja us heu subscrit a un domini DynDNS", "domain_dyndns_dynette_is_unreachable": "No s'ha pogut abastar la dynette YunoHost, o bé YunoHost no està connectat a internet correctament o bé el servidor dynette està caigut. Error: {error}", @@ -325,19 +325,19 @@ "migration_0005_postgresql_94_not_installed": "PostgreSQL no està instal·lat en el sistema. No hi ha res per fer.", "migration_0005_postgresql_96_not_installed": "PostgreSQL 9.4 està instal·lat, però no PostgreSQL 9.6? Alguna cosa estranya a passat en el sistema :( …", "migration_0005_not_enough_space": "Creu espai disponible en {path} per executar la migració.", - "migration_0006_disclaimer": "YunoHost esperar que les contrasenyes admin i root estiguin sincronitzades. Fent aquesta migració, la contrasenya root serà reemplaçada per la contrasenya admin.", - "migration_0007_cancelled": "YunoHost no ha pogut millorar la gestió de la configuració SSH.", + "migration_0006_disclaimer": "YunoHost esperar que les contrasenyes de admin i root estiguin sincronitzades. Aquesta migració canvia la contrasenya root per la contrasenya admin.", + "migration_0007_cancelled": "No s'ha pogut millorar la gestió de la configuració SSH.", "migration_0007_cannot_restart": "No es pot reiniciar SSH després d'haver intentat cancel·lar la migració numero 6.", "migration_0008_general_disclaimer": "Per millorar la seguretat del servidor, es recomana que sigui YunoHost qui gestioni la configuració SSH. La configuració SSH actual és diferent a la configuració recomanada. Si deixeu que YunoHost ho reconfiguri, la manera de connectar-se al servidor mitjançant SSH canviarà de la següent manera:", "migration_0008_port": "• La connexió es farà utilitzant el port 22 en lloc del port SSH personalitzat actual. Es pot reconfigurar;", "migration_0008_root": "• No es podrà connectar com a root a través de SSH. S'haurà d'utilitzar l'usuari admin per fer-ho;", "migration_0008_dsa": "• Es desactivarà la clau DSA. Per tant, es podria haver d'invalidar un missatge esgarrifós del client SSH, i tornar a verificar l'empremta digital del servidor;", - "migration_0008_warning": "Si heu entès els avisos i accepteu que YunoHost sobreescrigui la configuració actual, comenceu la migració. Sinó, podeu saltar-vos la migració, tot i que no està recomanat.", - "migration_0008_no_warning": "No s'han identificat riscs importants per sobreescriure la configuració SSH, però no es pot estar del tot segur ;)! Executetu la migració per sobreescriure-la. Sinó, podeu saltar-vos la migració, tot i que no està recomanat.", + "migration_0008_warning": "Si heu entès els avisos i voleu que YunoHost sobreescrigui la configuració actual, comenceu la migració. Sinó, podeu saltar-vos la migració, tot i que no està recomanat.", + "migration_0008_no_warning": "Hauria de ser segur sobreescriure la configuració SSH, però no es pot estar del tot segur! Executetu la migració per sobreescriure-la. Sinó, podeu saltar-vos la migració, tot i que no està recomanat.", "migration_0009_not_needed": "Sembla que ja s'ha fet aquesta migració… (?) Ometent.", "migrations_backward": "Migració cap enrere.", "migrations_bad_value_for_target": "Nombre invàlid pel paràmetre target, els nombres de migració disponibles són 0 o {}", - "migrations_cant_reach_migration_file": "No s'ha pogut accedir als fitxers de migració al camí %s", + "migrations_cant_reach_migration_file": "No s'ha pogut accedir als fitxers de migració al camí «%s»", "migrations_current_target": "La migració objectiu és {}", "migrations_error_failed_to_load_migration": "ERROR: no s'ha pogut carregar la migració {number} {name}", "migrations_forward": "Migració endavant", @@ -351,8 +351,8 @@ "migrations_success": "S'ha completat la migració {number} {name} amb èxit!", "migrations_to_be_ran_manually": "La migració {id} s'ha de fer manualment. Aneu a Eines → Migracions a la interfície admin, o executeu «yunohost tools migrations migrate».", "migrations_need_to_accept_disclaimer": "Per fer la migració {id}, heu d'acceptar aquesta clàusula de no responsabilitat:\n---\n{disclaimer}\n---\nSi accepteu fer la migració, torneu a executar l'ordre amb l'opció «--accept-disclaimer».", - "monitor_disabled": "El monitoratge del servidor ha estat desactivat", - "monitor_enabled": "El monitoratge del servidor ha estat activat", + "monitor_disabled": "S'ha desactivat el monitoratge del servidor", + "monitor_enabled": "S'ha activat el monitoratge del sistema", "monitor_glances_con_failed": "No s'ha pogut connectar al servidor Glances", "monitor_not_enabled": "El monitoratge del servidor no està activat", "monitor_period_invalid": "Període de temps invàlid", @@ -436,7 +436,7 @@ "server_reboot_confirm": "Es reiniciarà el servidor immediatament, n'esteu segur? [{answers:s}]", "service_add_failed": "No s'ha pogut afegir el servei «{service:s}»", "service_added": "S'ha afegit el servei «{service:s}»", - "service_already_started": "Ja s'ha iniciat el servei «{service:s}»", + "service_already_started": "El servei «{service:s}» ja està funcionant", "service_already_stopped": "Ja s'ha aturat el servei «{service:s}»", "service_cmd_exec_failed": "No s'ha pogut executar l'ordre «{command:s}»", "service_description_avahi-daemon": "Permet accedir al servidor via «yunohost.local» en la xarxa local", @@ -458,9 +458,9 @@ "service_description_yunohost-api": "Gestiona les interaccions entre la interfície web de YunoHost i el sistema", "service_description_yunohost-firewall": "Gestiona els ports de connexió oberts i tancats als serveis", "service_disable_failed": "No s'han pogut deshabilitar el servei «{service:s}»\n\nRegistres recents: {logs:s}", - "service_disabled": "S'ha deshabilitat el servei {service:s}", + "service_disabled": "S'ha deshabilitat el servei «{service:s}»", "service_enable_failed": "No s'ha pogut activar el servei «{service:s}»\n\nRegistres recents: {log:s}", - "service_enabled": "S'ha activat el servei {service:s}", + "service_enabled": "S'ha activat el servei «{service:s}»", "service_no_log": "No hi ha cap registre pel servei «{service:s}»", "service_regen_conf_is_deprecated": "«yunohost service regen-conf» està desfasat! Utilitzeu «yunohost tools regen-conf» en el seu lloc.", "service_remove_failed": "No s'ha pogut eliminar el servei «{service:s}»", @@ -523,7 +523,7 @@ "yunohost_ca_creation_success": "S'ha creat l'autoritat de certificació local.", "yunohost_configured": "YunoHost està configurat", "yunohost_installing": "Instal·lació de YunoHost…", - "yunohost_not_installed": "YunoHost no està instal·lat o no està instal·lat correctament. Executeu «yunohost tools postinstall»", + "yunohost_not_installed": "YunoHost no està instal·lat correctament. Executeu «yunohost tools postinstall»", "apps_permission_not_found": "No s'ha trobat cap permís per les aplicacions instal·lades", "apps_permission_restoration_failed": "Ha fallat el permís «{permission:s}» per la restauració de l'aplicació {app:s}", "backup_permission": "Permís de còpia de seguretat per l'aplicació {app:s}", @@ -553,13 +553,13 @@ "mailbox_disabled": "La bústia de correu està desactivada per al usuari {user:s}", "migration_description_0011_setup_group_permission": "Configurar el grup d'usuaris i els permisos per les aplicacions i els serveis", "migration_0011_backup_before_migration": "Creant una còpia de seguretat de la base de dades LDAP i la configuració de les aplicacions abans d'efectuar la migració.", - "migration_0011_can_not_backup_before_migration": "No s'ha pogut fer la còpia de seguretat abans de la migració. No s'ha pogut fer la migració. Error: {error:s}", + "migration_0011_can_not_backup_before_migration": "No s'ha pogut completar la còpia de seguretat abans de que la migració fallés. Error: {error:s}", "migration_0011_create_group": "Creant un grup per a cada usuari…", - "migration_0011_done": "Migració completa. Ja podeu gestionar grups d'usuaris.", + "migration_0011_done": "Migració completada. Ja podeu gestionar grups d'usuaris.", "migration_0011_LDAP_config_dirty": "Sembla que heu modificat manualment la configuració LDAP. Per fer aquesta migració s'ha d'actualitzar la configuració LDAP.\nGuardeu la configuració actual, reinicieu la configuració original executant l'ordre «yunohost tools regen-conf -f» i torneu a intentar la migració", "migration_0011_LDAP_update_failed": "Ha fallat l'actualització de LDAP. Error: {error:s}", "migration_0011_migrate_permission": "Fent la migració dels permisos de la configuració de les aplicacions a LDAP…", - "migration_0011_migration_failed_trying_to_rollback": "La migració ha fallat… s'intenta tornar el sistema a l'estat anterior.", + "migration_0011_migration_failed_trying_to_rollback": "No s'ha pogut fer la migració… s'intenta tornar el sistema a l'estat anterior.", "migration_0011_rollback_success": "S'ha tornat el sistema a l'estat anterior.", "migration_0011_update_LDAP_database": "Actualitzant la base de dades LDAP…", "migration_0011_update_LDAP_schema": "Actualitzant l'esquema LDAP…", @@ -583,7 +583,7 @@ "user_already_in_group": "L'usuari {user:s} ja és en el grup {group:s}", "user_not_in_group": "L'usuari {user:s} no és en el grup {group:s}", "migration_description_0012_postgresql_password_to_md5_authentication": "Força l'autenticació PostgreSQL a fer servir MD5 per a les connexions locals", - "app_full_domain_unavailable": "Aquesta aplicació requereix un domini sencer per ser instal·lada, però ja hi ha altres aplicacions instal·lades al domini «{domain}». Una possible solució és afegir i utilitzar un subdomini dedicat a aquesta aplicació.", + "app_full_domain_unavailable": "Aquesta aplicació ha de ser instal·lada en el seu propi domini, però ja hi ha altres aplicacions instal·lades en el domini «{domain}». Podeu utilitzar un subdomini dedicat a aquesta aplicació.", "migrations_not_pending_cant_skip": "Aquestes migracions no estan pendents, així que no poden ser omeses: {ids}", "app_action_broke_system": "Aquesta acció sembla haver trencat els següents serveis importants: {services}", "log_permission_urls": "Actualitzar les URLs relacionades amb el permís «{}»", @@ -592,11 +592,11 @@ "log_user_permission_reset": "Restablir el permís «{}»", "permission_already_disallowed": "El grup «{group}» ja té el permís «{permission}» desactivat", "migrations_already_ran": "Aquestes migracions ja s'han fet: {ids}", - "migrations_dependencies_not_satisfied": "No s'ha pogut executar la migració {id} perquè s'han d'executar primer les següents migracions: {dependencies_id}", + "migrations_dependencies_not_satisfied": "Executeu aquestes migracions: «{dependencies_id}», abans la migració {id}.", "migrations_failed_to_load_migration": "No s'ha pogut carregar la migració {id}: {error}", "migrations_exclusive_options": "«--auto», «--skip», i «--force-rerun» són opcions mútuament excloents.", "migrations_must_provide_explicit_targets": "Heu de proporcionar objectius explícits al utilitzar «--skip» o «--force-rerun»", - "migrations_no_such_migration": "No hi ha cap migració anomenada {id}", + "migrations_no_such_migration": "No hi ha cap migració anomenada «{id}»", "migrations_pending_cant_rerun": "Aquestes migracions encara estan pendents, així que no es poden tornar a executar: {ids}", "migrations_running_forward": "Executant la migració {id}…", "migrations_success_forward": "Migració {id} completada", @@ -614,8 +614,8 @@ "migration_0011_failed_to_remove_stale_object": "No s'ha pogut eliminar l'objecte obsolet {dn}: {error}", "permission_already_allowed": "El grup «{group}» ja té el permís «{permission}» activat", "permission_cannot_remove_main": "No es permet eliminar un permís principal", - "user_already_exists": "L'usuari {user} ja existeix", - "app_upgrade_stopped": "S'ha aturat l'actualització de totes les aplicacions per prevenir els possibles danys ja que hi ha hagut un error en l'actualització de l'anterior aplicació", + "user_already_exists": "L'usuari «{user}» ja existeix", + "app_upgrade_stopped": "S'ha aturat l'actualització de totes les aplicacions per prevenir possibles danys ja que no s'ha pogut actualitzar una aplicació", "app_install_failed": "No s'ha pogut instal·lar {app}: {error}", "app_install_script_failed": "Hi ha hagut un error en el script d'instal·lació de l'aplicació", "group_cannot_edit_all_users": "El grup «all_users» no es pot editar manualment. És un grup especial destinat a contenir els usuaris registrats a YunoHost", @@ -625,5 +625,6 @@ "migration_0011_slapd_config_will_be_overwritten": "Sembla que heu modificat manualment la configuració de sldap. Per aquesta migració crítica, YunoHost ha de forçar l'actualització de la configuració sldap. Es farà una còpia de seguretat a {conf_backup_folder}.", "permission_already_up_to_date": "No s'ha actualitzat el permís perquè la petició d'afegir/eliminar ja corresponent a l'estat actual.", "permission_currently_allowed_for_visitors": "El permís ja el tenen el grup de visitants a més d'altres grups. Segurament s'hauria de revocar el permís al grup dels visitants o eliminar els altres grups als que s'ha atribuït.", - "permission_currently_allowed_for_all_users": "El permís ha el té el grup de tots els usuaris (all_users) a més d'altres grups. Segurament s'hauria de revocar el permís a «all_users» o eliminar els altres grups als que s'ha atribuït." + "permission_currently_allowed_for_all_users": "El permís ha el té el grup de tots els usuaris (all_users) a més d'altres grups. Segurament s'hauria de revocar el permís a «all_users» o eliminar els altres grups als que s'ha atribuït.", + "permission_require_account": "El permís {permission} només té sentit per als usuaris que tenen un compte, i per tant no es pot activar per als visitants." } From ec1fa46c9f3986e34e7695d9e691ac74d685dfb3 Mon Sep 17 00:00:00 2001 From: Kayou Date: Sat, 26 Oct 2019 23:12:11 +0900 Subject: [PATCH 0370/3170] iproute2 instead of iproute --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index c0604d90e..b0de9032b 100644 --- a/debian/control +++ b/debian/control @@ -17,7 +17,7 @@ Depends: ${python:Depends}, ${misc:Depends} , python-toml , glances, apt-transport-https , dnsutils, bind9utils, unzip, git, curl, cron, wget, jq - , ca-certificates, netcat-openbsd, iproute + , ca-certificates, netcat-openbsd, iproute2 , mariadb-server, php-mysql | php-mysqlnd , slapd, ldap-utils, sudo-ldap, libnss-ldapd, unscd, libpam-ldapd , postfix-ldap, postfix-policyd-spf-perl, postfix-pcre, procmail, mailutils, postsrsd From 17ce7bd95c097f8fceeee6f35873c89cd4b4b91c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 26 Oct 2019 17:35:00 +0200 Subject: [PATCH 0371/3170] Rework depreciation warning about legacy permission stuff --- data/helpers.d/setting | 4 ++-- src/yunohost/app.py | 21 +++++++++++---------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index fd2824997..d905b61dd 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -176,8 +176,8 @@ else: elif action == "set": if key in ['redirected_urls', 'redirected_regex']: value = yaml.load(value) - if key in ["unprotected_uris", "unprotected_regex", "protected_uris", "protected_regex"]: - sys.stderr.write("/!\\ Packagers! This app is using the legacy permission system. Please delete these legacy settings and use the new helpers 'ynh_permission_{create,urls,update,delete}' and the 'visitors' group to manage public/private access.\n") + if any(key.startswith(word+"_") for word in ["unprotected", "protected", "skipped"]): + sys.stderr.write("/!\\ Packagers! This app is still using the skipped/protected/unprotected_uris/regex settings which are now obsolete and deprecated... Instead, you should use the new helpers 'ynh_permission_{create,urls,update,delete}' and the 'visitors' group to initialize the public/private access. Check out the documentation at the bottom of yunohost.org/groups_and_permissions to learn how to use the new permission mechanism.\n") settings[key] = value else: raise ValueError("action should either be get, set or delete") diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 5cf812871..b1ad0f40c 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1341,17 +1341,18 @@ def app_setting(app, key, value=None, delete=False): except Exception as e: logger.debug("cannot get app setting '%s' for '%s' (%s)", key, app, e) return None + + if delete and key in app_settings: + del app_settings[key] else: - if delete and key in app_settings: - del app_settings[key] - else: - # FIXME: Allow multiple values for some keys? - if key in ['redirected_urls', 'redirected_regex']: - value = yaml.load(value) - if key in ["unprotected_uris", "unprotected_regex", "protected_uris", "protected_regex"]: - logger.warning("/!\ Packagers ! This app is using the legacy permission system. Please delete these legacy settings and use the new helpers ynh_permission_{create,url,update,delete} and the 'visitors' group to manage public/private access.") - app_settings[key] = value - _set_app_settings(app, app_settings) + # FIXME: Allow multiple values for some keys? + if key in ['redirected_urls', 'redirected_regex']: + value = yaml.load(value) + if any(key.startswith(word+"_") for word in ["unprotected", "protected", "skipped"]): + logger.warning("/!\\ Packagers! This app is still using the skipped/protected/unprotected_uris/regex settings which are now obsolete and deprecated... Instead, you should use the new helpers 'ynh_permission_{create,urls,update,delete}' and the 'visitors' group to initialize the public/private access. Check out the documentation at the bottom of yunohost.org/groups_and_permissions to learn how to use the new permission mechanism.") + + app_settings[key] = value + _set_app_settings(app, app_settings) def app_checkport(port): From 9294664d6c60269a5c2dc3048d6994bd2944cb3d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 26 Oct 2019 17:39:09 +0200 Subject: [PATCH 0372/3170] Fix permission backward compatibility for the case where an app needs to make the app temporarily public during install script... --- data/helpers.d/setting | 7 +++++++ src/yunohost/app.py | 38 +++++++++++++++++++++++++++++++++++--- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index d905b61dd..185e6111f 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -184,6 +184,13 @@ else: with open(setting_file, "w") as f: yaml.safe_dump(settings, f, default_flow_style=False) EOF + + # Fucking legacy permission management. + # We need this because app temporarily set the app as unprotected to configure it with curl... + if [[ "$3" =~ ^(unprotected|skipped)_ ]] && [[ "${4:-}" == "/" ]] + then + ynh_permission_update --permission "main" --remove "all_users" --add "visitors" + fi } # Check availability of a web path diff --git a/src/yunohost/app.py b/src/yunohost/app.py index b1ad0f40c..6d3da405f 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1101,9 +1101,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu if not (domain and path): permission_url(app_instance_name + ".main", url=None, sync_perm=False) - # Migrate classic public app still using the legacy unprotected_uris - if app_settings.get("unprotected_uris", None) == "/" or app_settings.get("skipped_uris", None) == "/": - user_permission_update(app_instance_name + ".main", remove="all_users", add="visitors", sync_perm=False) + _migrate_legacy_permissions(app_instance_name) permission_sync_to_user() @@ -1112,6 +1110,34 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu hook_callback('post_app_install', args=args_list, env=env_dict) +def _migrate_legacy_permissions(app): + + from yunohost.permission import user_permission_list, user_permission_update + + # Check if app is apparently using the legacy permission management, defined by the presence of something like + # ynh_app_setting_set on unprotected_uris (or yunohost app setting) + install_script_path = os.path.join(APPS_SETTING_PATH, app, 'scripts/install') + install_script_content = open(install_script_path, "r").read() + if not re.search(r"(yunohost app setting|ynh_app_setting_set) .*(unprotected|skipped)_uris", install_script_content): + return + + app_settings = _get_app_settings(app) + app_perm_currently_allowed = user_permission_list()["permissions"][app + ".main"]["allowed"] + + # If the current permission says app is protected, but there are legacy rules saying it should be public... + if app_perm_currently_allowed == ["all_users"] \ + and (app_settings.get("unprotected_uris", None) == "/" + or app_settings.get("skipped_uris", None) == "/"): + # Make it public + user_permission_update(app + ".main", remove="all_users", add="visitors", sync_perm=False) + # If the current permission says app is public, but there are no setting saying it should be public... + if app_perm_currently_allowed == ["visitors"] \ + and (app_settings.get("unprotected_uris", None) is None + and app_settings.get("skipped_uris", None) is None): + # Make is private + user_permission_update(app + ".main", remove="visitors", add="all_users", sync_perm=False) + + @is_unit_operation() def app_remove(operation_logger, app): """ @@ -1354,6 +1380,12 @@ def app_setting(app, key, value=None, delete=False): app_settings[key] = value _set_app_settings(app, app_settings) + # Fucking legacy permission management. + # We need this because app temporarily set the app as unprotected to configure it with curl... + if key.startswith("unprotected_") or key.startswith("skipped_") and value == "/": + from permission import user_permission_update + user_permission_update(app + ".main", remove="all_users", add="visitors") + def app_checkport(port): """ From 854e52c21e0d5a6e10d122f8954b34c182c4de44 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 29 Oct 2019 15:46:22 +0100 Subject: [PATCH 0373/3170] More inclusive rule for this php/sury hack because php version got updated to deb9u6 --- data/helpers.d/apt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index d772c6855..cbf4e3e59 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -227,7 +227,7 @@ ynh_install_app_dependencies () { if echo $dependencies | grep -q 'php'; then # And we have packages from sury installed (7.0.33-10+weirdshiftafter instead of 7.0.33-0 on debian) - if dpkg --list | grep "php7.0" | grep -q -v "7.0.33-0+deb9u5" + if dpkg --list | grep "php7.0" | grep -q -v "7.0.33-0+deb9" then # And sury ain't already installed if ! grep -nrq "sury" /etc/apt/sources.list* From 5a599f2ebf08245b9673487ce949d15543bd5f0f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 29 Oct 2019 15:49:31 +0100 Subject: [PATCH 0374/3170] Update changelog for 3.6.5.3 --- debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian/changelog b/debian/changelog index 1d13b6290..45dbadef5 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +yunohost (3.6.5.3) stable; urgency=low + + - [fix] More general grep for the php/sury dependency nightmare fix (followup of #809) + + -- Alexandre Aubin Tue, 29 Oct 2019 03:48:00 +0000 + yunohost (3.6.5.2) stable; urgency=low - [fix] Alex was drunk and released an epic stupid bug in stable (2623d385) From 79627d79ccfbc4e5a23691e3fd7da80620a707cd Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 29 Oct 2019 16:18:31 +0100 Subject: [PATCH 0375/3170] [yolo] Cosmetic improvement for logs during system package upgrades --- src/yunohost/tools.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 28b507707..f4bb83c15 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -640,8 +640,11 @@ def tools_upgrade(operation_logger, apps=None, system=False): logger.debug("Running apt command :\n{}".format(dist_upgrade)) + def is_relevant(l): + return "Reading database ..." not in l.rstrip() + callbacks = ( - lambda l: logger.info("+" + l.rstrip() + "\r"), + lambda l: logger.info("+ " + l.rstrip() + "\r") if is_relevant(l) else logger.debug(l.rstrip() + "\r"), lambda l: logger.warning(l.rstrip()), ) returncode = call_async_output(dist_upgrade, callbacks, shell=True) From 71bc6a0fafce76e03de00133a4df0e570512f138 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 29 Oct 2019 16:38:15 +0100 Subject: [PATCH 0376/3170] We gotta regen the ssowat conf to propagate the change --- src/yunohost/app.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 6d3da405f..1eeb38924 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -612,6 +612,8 @@ def app_change_url(operation_logger, app, domain, path): app_setting(app, 'domain', value=domain) app_setting(app, 'path', value=path) + app_ssowatconf() + # avoid common mistakes if _run_service_command("reload", "nginx") is False: # grab nginx errors From 572b003e299428238546813f956ebbb23f800946 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 29 Oct 2019 17:17:43 +0100 Subject: [PATCH 0377/3170] We gotta return a permission structure here in all case, otherwise stuff like app_addaccess will miserably fail in these case --- src/yunohost/permission.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 226cc9050..c53804d49 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -155,7 +155,7 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, # Don't update LDAP if we update exactly the same values if set(new_allowed_groups) == set(current_allowed_groups): logger.warning(m18n.n("permission_already_up_to_date")) - return + return existing_permission # Commit the new allowed group list From 83b45d7894f49878d608071e7f27b3981ae896cc Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 29 Oct 2019 17:38:05 +0100 Subject: [PATCH 0378/3170] Fix the legacy permission fix after app install, sometimes the setting ain't None --- src/yunohost/app.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 1eeb38924..1e82b78ba 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1126,16 +1126,16 @@ def _migrate_legacy_permissions(app): app_settings = _get_app_settings(app) app_perm_currently_allowed = user_permission_list()["permissions"][app + ".main"]["allowed"] + settings_say_it_should_be_public = (app_settings.get("unprotected_uris", None) == "/" + or app_settings.get("skipped_uris", None) == "/") + # If the current permission says app is protected, but there are legacy rules saying it should be public... - if app_perm_currently_allowed == ["all_users"] \ - and (app_settings.get("unprotected_uris", None) == "/" - or app_settings.get("skipped_uris", None) == "/"): + if app_perm_currently_allowed == ["all_users"] and settings_say_it_should_be_public: # Make it public user_permission_update(app + ".main", remove="all_users", add="visitors", sync_perm=False) + # If the current permission says app is public, but there are no setting saying it should be public... - if app_perm_currently_allowed == ["visitors"] \ - and (app_settings.get("unprotected_uris", None) is None - and app_settings.get("skipped_uris", None) is None): + if app_perm_currently_allowed == ["visitors"] and not settings_say_it_should_be_public: # Make is private user_permission_update(app + ".main", remove="visitors", add="all_users", sync_perm=False) From 9ee3d234e8db1080dd5d87f3bec0b802a1035943 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 29 Oct 2019 19:19:04 +0100 Subject: [PATCH 0379/3170] [ux] Add a message to explain that the app is being removed if the install fails --- locales/en.json | 1 + src/yunohost/app.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/locales/en.json b/locales/en.json index 202650edb..27f25e095 100644 --- a/locales/en.json +++ b/locales/en.json @@ -40,6 +40,7 @@ "app_requirements_checking": "Checking required packages for {app}…", "app_requirements_failed": "Some requirements are not met for {app}: {error}", "app_requirements_unmeet": "Requirements are not met for {app}, the package {pkgname} ({version}) must be {spec}", + "app_remove_after_failed_install": "Removing the app following the installation failure…", "app_sources_fetch_failed": "Could not fetch sources files, is the URL correct?", "app_start_install": "Installing the app '{app}'…", "app_start_remove": "Removing the app '{app}'…", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 1e82b78ba..a4185b880 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1026,6 +1026,8 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu # This option is meant for packagers to debug their apps more easily if no_remove_on_failure: raise YunohostError("The installation of %s failed, but was not cleaned up as requested by --no-remove-on-failure." % app_id, raw_msg=True) + else: + logger.warning(m18n.n("app_remove_after_failed_install")) # Setup environment for remove script env_dict_remove = {} From 4dadb9eb906eaef566b1cd08aa6755d6e8d9637c Mon Sep 17 00:00:00 2001 From: amirale qt Date: Mon, 28 Oct 2019 08:27:21 +0000 Subject: [PATCH 0380/3170] Translated using Weblate (French) Currently translated at 100.0% (561 of 561 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 3cb3c8fa9..15f82baf1 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -370,7 +370,7 @@ "dyndns_domain_not_provided": "Le fournisseur DynDNS {provider:s} ne peut pas fournir le domaine {domain:s}.", "app_make_default_location_already_used": "Impossible de configurer l’application '{app}' par défaut pour le domaine '{domain}' car il est déjà utilisé par l'application '{other_app}'", "app_upgrade_app_name": "Mise à jour de l’application {app} …", - "backup_output_symlink_dir_broken": "Votre répertoire d'archivage '{chemin:s}' est un lien symbolique brisé. Peut-être avez-vous oublié de re/monter ou de brancher le support de stockage sur lequel il pointe.", + "backup_output_symlink_dir_broken": "Votre répertoire d'archivage '{path:s}' est un lien symbolique brisé. Peut-être avez-vous oublié de re/monter ou de brancher le support de stockage sur lequel il pointe.", "migrate_tsig_end": "La migration à HMAC-SHA-512 est terminée", "migrate_tsig_failed": "La migration du domaine DynDNS {domain} à hmac-sha512 a échoué. Annulation des modifications. Erreur : {error_code} - {error}", "migrate_tsig_start": "L’algorithme de génération des clefs n’est pas suffisamment sécurisé pour la signature TSIG du domaine '{domain}', lancement de la migration vers HMAC-SHA-512 qui est plus sécurisé", From dd9f1944af994ec2a44081c4d22a6798f5bd2047 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Mon, 28 Oct 2019 08:04:16 +0000 Subject: [PATCH 0381/3170] Translated using Weblate (Esperanto) Currently translated at 93.0% (522 of 561 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/eo/ --- locales/eo.json | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/locales/eo.json b/locales/eo.json index c78bf6269..720485ba6 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -40,7 +40,7 @@ "app_argument_choice_invalid": "Uzu unu el ĉi tiuj elektoj '{choices:s}' por la argumento '{name:s}'", "app_argument_invalid": "Elektu validan valoron por la argumento '{name:s}': {error:s}", "app_change_url_failed_nginx_reload": "Ne eblis reŝarĝi NGINX. Jen la eligo de 'nginx -t':\n{nginx_errors:s}", - "appslist_url_already_tracked": "Estas jam registrita aplika listo kun la URL {url:s}.", + "appslist_url_already_tracked": "Jam ekzistas registrita app-listo kun la URL {url:s}.", "ask_new_admin_password": "Nova administrada pasvorto", "app_action_broke_system": "Ĉi tiu ago ŝajne rompis ĉi tiujn gravajn servojn: {services}", "app_unsupported_remote_type": "Malkontrolita fora speco uzita por la apliko", @@ -50,16 +50,16 @@ "backup_abstract_method": "Ĉi tiu rezerva metodo ankoraŭ efektiviĝis", "apps_already_up_to_date": "Ĉiuj aplikoj estas jam ĝisdatigitaj", "backup_borg_not_implemented": "La kopia metodo de Borg ankoraŭ ne estas efektivigita", - "app_upgrade_stopped": "La ĝisdatigo de ĉiuj aplikoj estis ĉesigita por eviti eblajn damaĝojn ĉar la antaŭa apliko ne sukcesis ĝisdatigi", + "app_upgrade_stopped": "Ĝisdatigi ĉiujn aplikaĵojn estis ĉesigita por eviti eblajn damaĝojn ĉar unu app ne povis esti altgradigita", "app_location_unavailable": "Ĉi tiu URL aŭ ne haveblas, aŭ konfliktas kun la jam instalita (j) apliko (j):\n{apps:s}", "backup_archive_app_not_found": "Ne povis trovi la programon '{app:s}' en la rezerva ar archiveivo", "backup_actually_backuping": "Krei rezervan ar archiveivon el la kolektitaj dosieroj …", "backup_method_borg_finished": "Sekurkopio en Borg finiĝis", - "appslist_removed": "{appslist:s} aplika listo forigita", + "appslist_removed": "La listo de '{appslist:s}' estis forigita", "app_change_url_no_script": "La app '{app_name:s}' ankoraŭ ne subtenas URL-modifon. Eble vi devus altgradigi ĝin.", - "app_start_install": "Instalanta aplikon {app} …", + "app_start_install": "Instali la programon '{app}' …", "backup_created": "Sekurkopio kreita", - "app_make_default_location_already_used": "Ne povas igi la aplikon '{app}' defaŭlta sur la domajno, {domain} jam uziĝas de la alia app '{other_app}'", + "app_make_default_location_already_used": "Ne povas igi la aplikon '{app}' defaŭlta sur la domajno, '{domain}' jam uziĝas de la alia app '{other_app}'", "backup_method_copy_finished": "Rezerva kopio finis", "app_not_properly_removed": "{app:s} ne estis ĝuste forigita", "backup_archive_broken_link": "Ne povis aliri la rezervan ar archiveivon (rompita ligilo al {path:s})", @@ -78,15 +78,15 @@ "backup_custom_backup_error": "Propra rezerva metodo ne povis preterpasi la paŝon \"sekurkopio\"", "ask_main_domain": "Ĉefa domajno", "backup_method_tar_finished": "TAR-rezerva ar archiveivo kreita", - "appslist_unknown": "Aplika listo {appslist:s} nekonata.", + "appslist_unknown": "La app-listo '{appslist:s}' estas nekonata.", "ask_list_to_remove": "Listo por forigi", "backup_cant_mount_uncompress_archive": "Ne povis munti la nekompresitan ar archiveivon kiel protektita kontraŭ skribo", - "appslist_retrieve_bad_format": "Ne povis legi la elprenitan liston {appslist:s}", + "appslist_retrieve_bad_format": "Ne povis legi la elprenitan liston '{appslist:s}'", "appslist_corrupted_json": "Ne povis ŝarĝi la aplikajn listojn. Ĝi aspektas kiel {filename:s} estas damaĝita.", "app_action_cannot_be_ran_because_required_services_down": "Ĉi tiuj postulataj servoj devas funkcii por funkciigi ĉi tiun agon: {services}. Provu rekomenci ilin por daŭrigi (kaj eble esploru, kial ili malsupreniras).", "backup_copying_to_organize_the_archive": "Kopiante {size:s} MB por organizi la ar archiveivon", "backup_output_directory_forbidden": "Elektu malsaman elirejan dosierujon. Sekurkopioj ne povas esti kreitaj en sub-dosierujoj / bin, / boot, / dev, / ktp, / lib, / root, / run, / sbin, / sys, / usr, / var aŭ /home/yunohost.backup/archives", - "appslist_could_not_migrate": "Ne povis migri la liston de aplikoj {appslist:s}! Ne eblis analizi la URL ... La malnova cron-laboro konserviĝis en {bkp_file:s}.", + "appslist_could_not_migrate": "Ne povis migri la liston de aplikoj '{appslist:s}'! Ne eblis analizi la URL ... La malnova cron-laboro konserviĝis en {bkp_file:s}.", "app_requirements_failed": "Certaines exigences ne sont pas remplies pour {app}: {error}", "backup_no_uncompress_archive_dir": "Ne ekzistas tia nekompremita arkiva dosierujo", "password_too_simple_1": "Pasvorto devas esti almenaŭ 8 signojn longa", @@ -94,11 +94,11 @@ "app_upgrade_several_apps": "La sekvaj apliko estos altgradigitaj: {apps}", "backup_archive_open_failed": "Ne povis malfermi la rezervan ar archiveivon", "ask_lastname": "Familia nomo", - "app_start_backup": "Kolekti dosierojn por esti subtenata por {app} …", + "app_start_backup": "Kolekti dosierojn por esti subtenata por la '{app}' …", "backup_archive_name_exists": "Rezerva arkivo kun ĉi tiu nomo jam ekzistas.", "backup_applying_method_tar": "Krei la rezervan TAR-ar archiveivon …", "backup_method_custom_finished": "Propra rezerva metodo '{metodo:s}' finiĝis", - "appslist_retrieve_error": "Ne eblas retrovi la forajn aplikajn listojn {appslist:s}: {eraro:s}", + "appslist_retrieve_error": "Ne eblas akiri la forajn listojn '{appslist:s}': {eraro:s}", "app_already_installed_cant_change_url": "Ĉi tiu app estas jam instalita. La URL ne povas esti ŝanĝita nur per ĉi tiu funkcio. Rigardu \"app changeurl\" se ĝi haveblas.", "app_not_correctly_installed": "{app:s} ŝajnas esti malĝuste instalita", "app_removed": "{app:s} forigita", @@ -106,27 +106,27 @@ "app_package_need_update": "La pakaĵo {app} devas esti ĝisdatigita por sekvi YunoHost-ŝanĝojn", "backup_nothings_done": "Nenio por ŝpari", "backup_applying_method_custom": "Nomante la kutiman rezervan metodon '{metodo:s}' …", - "appslist_fetched": "Ĝisdatigita aplika listo {appslist:s}", + "appslist_fetched": "Ĝisdatigis la liston de aplikoj '{appslist:s}'", "backup_app_failed": "Ne eblis rezervi la programon '{app:s}'", "app_upgrade_some_app_failed": "Iuj aplikoj ne povis esti altgradigitaj", - "app_start_remove": "Forigo de apliko {app} …", + "app_start_remove": "Forigo de la apliko '{app}' …", "backup_output_directory_not_empty": "Vi devas elekti malplenan eligitan dosierujon", "backup_archive_writing_error": "Ne povis aldoni la dosierojn '{source:s}' (nomitaj en la ar theivo '{dest:s}') por esti rezervitaj en la kunpremita arkivo '{archive:s}'", "ask_email": "Retpoŝta adreso", - "app_start_restore": "Restarigi aplikon {app} …", + "app_start_restore": "Restarigi la programon '{app}' …", "backup_applying_method_copy": "Kopiante ĉiujn dosierojn al sekurkopio …", "backup_couldnt_bind": "Ne povis ligi {src:s} al {dest:s}.", "ask_password": "Pasvorto", "app_requirements_unmeet": "Postuloj ne estas renkontitaj por {app}, la pakaĵo {pkgname} ({version}) devas esti {spec}", "ask_firstname": "Antaŭnomo", - "backup_ask_for_copying_if_needed": "Iuj dosieroj ne povus esti pretigitaj por sekurkopio uzante la metodon, kiu evitas portempe malŝpari spacon en la sistemo. Por plenumi la sekurkopion, {size:s} MB estos provizore. Ĉu vi konsentas?", + "backup_ask_for_copying_if_needed": "Ĉu vi volas realigi la sekurkopion uzante {size:s} MB provizore? (Ĉi tiu maniero estas uzata ĉar iuj dosieroj ne povus esti pretigitaj per pli efika metodo.)", "backup_mount_archive_for_restore": "Preparante arkivon por restarigo …", - "appslist_migrating": "Migra aplika listo {appslist:s} …", + "appslist_migrating": "Migrado de la aplika listo '{appslist:s}' …", "backup_csv_creation_failed": "Ne povis krei la CSV-dosieron bezonatan por restarigo", "backup_archive_name_unknown": "Nekonata loka rezerva ar archiveivo nomata '{name:s}'", "backup_applying_method_borg": "Sendado de ĉiuj dosieroj al sekurkopio en borg-rezerva deponejo …", "app_sources_fetch_failed": "Ne povis akiri fontajn dosierojn, ĉu la URL estas ĝusta?", - "appslist_name_already_tracked": "Registrita aplika listo kun nomo {name:s} jam ekzistas.", + "appslist_name_already_tracked": "Registrita aplika listo kun la nomo {name:s} jam ekzistas.", "ask_new_domain": "Nova domajno", "app_unknown": "Nekonata apliko", "app_not_upgraded": "La aplikaĵo '{failed_app}' ne ĝisdatigis, kaj pro tio la sekvaj ĝisdatigoj de aplikoj estis nuligitaj: {apps}", @@ -271,11 +271,11 @@ "update_apt_cache_failed": "Ne eblis ĝisdatigi la kaŝmemoron de APT (paka administranto de Debian). Jen rubujo de la sources.list-linioj, kiuj povus helpi identigi problemajn liniojn:\n{sourcelist}", "migrations_no_migrations_to_run": "Neniuj migradoj por funkcii", "executing_command": "Plenumanta komandon '{command:s}' …", - "diagnosis_no_apps": "Neniu instalita apliko", + "diagnosis_no_apps": "Neniu tia instalita app", "certmanager_attempt_to_renew_nonLE_cert": "La atestilo por la domajno '{domain:s}' ne estas elsendita de Let's Encrypt. Ne eblas renovigi ĝin aŭtomate!", "global_settings_setting_example_bool": "Ekzemplo bulea elekto", "domain_dyndns_already_subscribed": "Vi jam abonis DynDNS-domajnon", - "log_letsencrypt_cert_renew": "Renovigu '{}' Ni ĉifru atestilon", + "log_letsencrypt_cert_renew": "Renovigu '{}' Let's Encrypt atestilon", "migrate_tsig_start": "Detektita ŝlosila algoritmo nesufiĉa por TSIG-subskribo de la domajno '{domain}', komencanta migradon al la pli sekura HMAC-SHA-512", "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", @@ -381,7 +381,7 @@ "regenconf_file_kept_back": "La agorda dosiero '{conf}' estas atendita forigi per regen-conf (kategorio {category}), sed ĝi estis konservita.", "migrate_tsig_wait_4": "30 sekundoj …", "backup_with_no_restore_script_for_app": "La apliko \"{app:s}\" ne havas restarigan skripton, vi ne povos aŭtomate restarigi la sekurkopion de ĉi tiu apliko.", - "log_letsencrypt_cert_install": "Instalu atestilon Ni ĉifru sur '{}' regado", + "log_letsencrypt_cert_install": "Instalu atestilon Let's Encrypt sur '{}' regado", "log_dyndns_update": "Ĝisdatigu la IP asociita kun via subdominio YunoHost '{}'", "firewall_reload_failed": "Ne eblis reŝargi la firewall", "confirm_app_install_warning": "Averto: Ĉi tiu aplikaĵo povas funkcii, sed ne bone integras en YunoHost. Iuj funkcioj kiel ekzemple aliĝilo kaj sekurkopio / restarigo eble ne haveblos. Instali ĉiuokaze? [{answers: s}] ", @@ -393,7 +393,7 @@ "migrations_need_to_accept_disclaimer": "Por funkciigi la migradon {id}, via devas akcepti la sekvan malakcepton:\n---\n{malavantaĝo}\n---\nSe vi akceptas funkcii la migradon, bonvolu rekonduki la komandon kun la opcio '--accept-disclaimer'.", "regenconf_file_remove_failed": "Ne povis forigi la agordodosieron '{conf}'", "not_enough_disk_space": "Ne sufiĉe libera spaco sur '{path:s}'", - "migration_0006_disclaimer": "YunoHost nun atendas ke pasvortoj kaj administrantoj estu sinkronigitaj. Per ekzekuto de ĉi tiu migrado, via radika pasvorto estos anstataŭigita per administra pasvorto.", + "migration_0006_disclaimer": "YunoHost nun atendas, ke la pasvortoj de admin kaj radiko estos sinkronigitaj. Ĉi tiu migrado anstataŭigas vian radikan pasvorton kun la administran pasvorton.", "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", "monitor_glances_con_failed": "Ne povis konektiĝi al servilo de Glances", @@ -422,7 +422,7 @@ "migration_0003_general_warning": "Bonvolu noti, ke ĉi tiu migrado estas delikata operacio. La teamo de YunoHost faris sian plej bonan revizii kaj testi ĝin, sed la migrado eble ankoraŭ rompos partojn de la sistemo aŭ ĝiaj programoj.\n\nTial oni rekomendas al:\n - Elfari kopion de iuj kritikaj datumoj aŭ app. Pliaj informoj pri https://yunohost.org/backup;\n - Paciencu post lanĉo de la migrado: Depende de via interreta konekto kaj aparataro, eble daŭros kelkaj horoj ĝis ĉio ĝisdatigi.\n\nAldone, la haveno por SMTP, uzata de eksteraj retpoŝtaj klientoj (kiel Thunderbird aŭ K9-Mail) estis ŝanĝita de 465 (SSL / TLS) al 587 (STARTTLS). La malnova haveno (465) aŭtomate fermiĝos, kaj la nova haveno (587) malfermiĝos en la fajrejo. Vi kaj viaj uzantoj * devos adapti la agordon de viaj retpoŝtaj klientoj laŭe.", "diagnosis_kernel_version_error": "Ne povis akiri la kernan version: {error}", "global_settings_setting_example_int": "Ekzemple int elekto", - "backup_output_symlink_dir_broken": "Vi havas rompitan simbolon anstataŭ via arkiva dosierujo '{path:s}'. Vi eble havas specifan agordon por sekurkopi viajn datumojn en alia dosiersistemo, ĉi-kaze vi probable forgesis remeti aŭ enŝovi vian malmolan disko aŭ ŝlosilon USB.", + "backup_output_symlink_dir_broken": "Via arkiva dosierujo '{path:s}' estas rompita ligilo. Eble vi forgesis restarigi aŭ munti aŭ enŝovi la stokadon, al kiu ĝi notas.", "good_practices_about_admin_password": "Vi nun estas por difini novan administran pasvorton. La pasvorto devas esti almenaŭ 8 signoj - kvankam estas bone praktiki uzi pli longan pasvorton (t.e. pasfrazon) kaj / aŭ uzi variaĵon de signoj (majuskloj, minuskloj, ciferoj kaj specialaj signoj).", "certmanager_attempt_to_renew_valid_cert": "La atestilo por la domajno '{domain:s}' ne finiĝos! (Vi eble uzos --force se vi scias kion vi faras)", "restore_running_hooks": "Kurantaj restarigaj hokoj…", @@ -431,7 +431,7 @@ "domain_dns_conf_is_just_a_recommendation": "Ĉi tiu komando montras al vi la *rekomenditan* agordon. Ĝi efektive ne agordas la DNS-agordon por vi. Via respondeco agordi vian DNS-zonon en via registristo laŭ ĉi tiu rekomendo.", "backup_php5_to_php7_migration_may_fail": "Ne povis konverti vian ar archiveivon por subteni PHP 7, vi eble ne povas restarigi viajn PHP-programojn (kialo: {error:s})", "log_backup_restore_system": "Restarigi sistemon de rezerva arkivo", - "log_app_change_url": "Ŝanĝu la URL de apliko '{}'", + "log_app_change_url": "Ŝanĝu la URL de la apliko '{}'", "service_already_started": "La servo '{service:s}' estas jam komencita", "license_undefined": "nedifinita", "global_settings_setting_security_password_admin_strength": "Admin pasvorta forto", @@ -526,7 +526,7 @@ "password_too_simple_2": "La pasvorto bezonas almenaŭ 8 signojn kaj enhavas ciferon, majusklojn kaj minusklojn", "executing_script": "Plenumanta skripto '{script:s}' …", "service_cmd_exec_failed": "Ne povis plenumi la komandon '{command:s}'", - "migration_0007_cancelled": "YunoHost ne plibonigis la administradon de via SSH-konf.", + "migration_0007_cancelled": "Ne povis plibonigi la manieron kiel via SSH-agordo estas administrita.", "migrate_tsig_failed": "Ne povis migri la DynDNS-domajnon '{domain}' al HMAC-SHA-512, ruliĝante. Eraro: {error_code}, {error}", "pattern_lastname": "Devas esti valida familinomo", "service_enabled": "'{service:s}' servo malŝaltita", @@ -553,7 +553,7 @@ "service_restart_failed": "Ne povis rekomenci la servon '{service:s}'\n\nLastatempaj servaj protokoloj: {logs:s}", "firewall_rules_cmd_failed": "Iuj komandoj pri fajroŝirmilo malsukcesis. Pliaj informoj en ensaluto.", "certmanager_certificate_fetching_or_enabling_failed": "Provante uzi la novan atestilon por {domain:s} ne funkciis …", - "app_full_domain_unavailable": "Bedaŭrinde, ĉi tiu apliko postulas plenan domajnon esti instalita, sed iuj aliaj programoj jam estas instalitaj sur '{domain}'. Unu ebla solvo estas aldoni kaj uzi subdomajnon dediĉitan al ĉi tiu aplikaĵo anstataŭe.", + "app_full_domain_unavailable": "Bedaŭrinde, ĉi tiu app devas esti instalita sur propra domajno, sed aliaj programoj jam estas instalitaj sur la domajno '{domain}'. Vi povus uzi subdominon dediĉitan al ĉi tiu app anstataŭe.", "migration_0011_slapd_config_will_be_overwritten": "Ŝajnas ke vi permane redaktis la slapd-agordon. Por ĉi tiu kritika migrado, YunoHost bezonas devigi la ĝisdatigon de la slapd-agordo. La originalaj dosieroj estos rezervitaj en {conf_backup_folder}.", "group_cannot_edit_all_users": "La grupo 'all_users' ne povas esti redaktita permane. Ĝi estas speciala grupo celita enhavi ĉiujn uzantojn registritajn en YunoHost", "group_cannot_edit_visitors": "La grupo 'vizitantoj' ne povas esti redaktita permane. Ĝi estas speciala grupo reprezentanta anonimajn vizitantojn", From 40f48ff25ce1b76294d2d830b7237b898ed83d50 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 29 Oct 2019 21:38:43 +0100 Subject: [PATCH 0382/3170] zzzzz previous commit broke the info for apps, only system infos were affected --- src/yunohost/backup.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 3f253c7ff..213f2cec1 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -2374,11 +2374,13 @@ def backup_info(name, with_details=False, human_readable=False): for category in ["apps", "system"]: for name, key_info in info[category].items(): - # Stupid legacy fix for weird format between 3.5 and 3.6 - if isinstance(key_info, dict): - key_info = key_info.keys() - - info[category][name] = key_info = {"paths": key_info} + if category == "system": + # Stupid legacy fix for weird format between 3.5 and 3.6 + if isinstance(key_info, dict): + key_info = key_info.keys() + info[category][name] = key_info = {"paths": key_info} + else: + info[category][name] = key_info if name in info["size_details"][category].keys(): key_info["size"] = info["size_details"][category][name] From 3f80fe4e932708820ccd180f861e99f52f331592 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 31 Oct 2019 18:14:20 +0100 Subject: [PATCH 0383/3170] Update changelog for 3.7.0 testing --- debian/changelog | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/debian/changelog b/debian/changelog index 45dbadef5..15b3c049c 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,46 @@ +yunohost (3.7.0) testing; urgency=low + + # ~ Major stuff + + - [enh] Add group and permission mechanism (YunoHost#585, YunoHost#763, YunoHost#789, YunoHost#790, YunoHost#795, YunoHost#797, SSOwat#147, Moulinette#189, YunoHost-admin#257) + - [mod] Rework migration system to have independent migrations (YunoHost#768, YunoHost#774, YunoHost-admin#258) + - [enh] Many improvements in the way app action failures are handled (YunoHost#769, YunoHost#811) + - [enh] Improve checks for system anomalies after app operations (YunoHost#785) + - [mod] Spookier warnings for dangerous app installs (YunoHost#814, Moulinette/808f620) + - [enh] Support app manifests in toml (YunoHost#748, Moulinette#204, Moulinette/55515cb) + - [mod] Get rid of etckeeper (YunoHost#803) + - [enh] Quite a lot of messages improvements, string cleaning, language rework... (YunoHost#793, YunoHost#799, YunoHost#823, SSOwat#143, YunoHost#766, YunoHost#767, YunoHost/fd99ef0, YunoHost/92a6315, YunoHost-admin/10ea04a, Moulinette/599bec3, Moulinette#208, Moulinette#213, Moulinette/b7d415d, Moulinette/a8966b8, Moulinette/fdf9a71, Moulinette/d895ae3, Moulinette/bdf0a1c, YunoHost#817, YunoHost#823, YunoHost/79627d7, YunoHost/9ee3d23, YunoHost-admin#265) + - [i18n] Improved translations for Catalan, Occitan, French, Esperanto, Arabic, German, Spanish, Norwegian Bokmål, Portuguese + + # Smaller or pretty technical fix/enh + + - [enh] Add unit/functional tests for apps + improve other tests (YunoHost#779, YunoHost#808) + - [enh] Preparations for moulinette Python3 migration (Tox, Pytest and unit tests) (Moulinette#203, Moulinette#206, Moulinette#207, Moulinette#210, Moulinette#211 Moulinette#212, Moulinette/2403ee1, Moulinette/69b0d49, Moulinette/49c749c, Moulinette/2c84ee1, Moulinette/cef72f7, YunoHost/6365a26) + - [enh] Support python hooks (YunoHost#747) + - [enh] Upgrade n version + compatibility with arm64 (YunoHost#753) + - [enh] Add OpenLDAP TLS support (YunoHost#755, YunoHost/0a2d1c7, YunoHost/2dc8095) + - [enh] Improve PostgreSQL password security (YunoHost#762) + - [enh] Integrate actions/config-panel into operation logs (YunoHost#764) + - [mod] Assume that apps without any 'path' setting defined aren't webapps (YunoHost#765) + - [fix] Set dpkg vendor to YunoHost (YunoHost#749, YunoHost#772) + - [enh] Adding variable 'token' to data to redact from logs (YunoHost#783) + - [enh] Add --force and --dry-run options to 'yunohost dyndns update' (YunoHost#786) + - [fix] Don't throw a fatal error if we can't change the hostname (YunoHost/fe3ecd7) + - [enh] Dynamically evaluate proper mariadb-server- (YunoHost/f0440fb) + - [fix] Bad format for backup info.json ... (YunoHost/7d0119a) + - [fix] Inline buttons responsiveness on migration screen (YunoHost-admin#259) + - [enh] Add debug logs to SSOwat (SSOwat#145) + - [enh] Add a write_to_yaml utility similar to write_to_json (Moulinette/2e2e627) + - [enh] Warn the user about long locks (Moulinette#205) + - [mod] Tweak stuff about setuptools and moulinette deps? (Moulinette/b739f27, Moulinette/da00fc9, Moulinette/d8cbbb0) + - [fix] Misc micro bugfixes or improvements (YunoHost#743, YunoHost#792, YunoHost/6f48d1d, YunoHost/d516cf8, YunoHost#819, Moulinette/83d9e77, YunoHost/63d364e, YunoHost/68e9724, YunoHost/0849adb, YunoHost/19dbe87, YunoHost/61931f2, YunoHost/6dc720f, YunoHost/4def4df, SSOwat#140, SSOwat#141, YunoHost#829) + - [doc] Fix doc building + add doc build tests with Tox (Moulinette/f1ac5b8, Moulinette/df7d478, Moulinette/74c8f79, Moulinette/bcf92c7, Moulinette/af2c80c, Moulinette/d52a574, Moulinette/307f660, Moulinette/dced104, Moulinette/ed3823b) + - [enh] READMEs improvements (YunoHost/b3398e7, SSOwat/ee67b6f, Moulinette/1541b74, Moulinette/ad1eeef, YunoHost/25afdd4, YunoHost/73741f6) + + Thanks to all contributors <3 ! (accross all repo: Yunohost, Moulinette, SSOwat, Yunohost-admin) : advocatux, Aksel K., Aleks, Allan N., amirale qt, Armin P., Bram, ButterflyOfFire, Carles S. A., chema o. r., decentral1se, Emmanuel V., Etienne M., Filip B., Geoff M., htsr, Jibec, Josué, Julien J., Kayou, liberodark, ljf, lucaskev, Lukas D., madtibo, Martin D., Mélanie C., nr 458 h, pitfd, ppr, Quentí, sidddy, troll, tufek yamero, xaloc33, yalh76 + + -- Alexandre Aubin Thu, 31 Oct 2019 18:00:00 +0000 + yunohost (3.6.5.3) stable; urgency=low - [fix] More general grep for the php/sury dependency nightmare fix (followup of #809) From 089b3cf9739bbce8dfdf38a0a7fe7b88de4d31fc Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 31 Oct 2019 20:36:25 +0100 Subject: [PATCH 0384/3170] [fix] Sync permissions only after we're done migrating them, otherwise this triggers a shitload of warnings --- src/yunohost/data_migrations/0011_setup_group_permission.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/data_migrations/0011_setup_group_permission.py b/src/yunohost/data_migrations/0011_setup_group_permission.py index 7a987899c..e9ca32294 100644 --- a/src/yunohost/data_migrations/0011_setup_group_permission.py +++ b/src/yunohost/data_migrations/0011_setup_group_permission.py @@ -120,7 +120,7 @@ class MyMigration(Migration): if app_setting(app, "unprotected_uris") == "/" or app_setting(app, "skipped_uris") == "/": user_permission_update(app+".main", remove="all_users", add="visitors", sync_perm=False) - permission_sync_to_user() + permission_sync_to_user() def run(self): From 67a11b5b79689fb4c3ca784f6f0691988d48d523 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 31 Oct 2019 20:37:51 +0100 Subject: [PATCH 0385/3170] Update changelog for 3.7.0.1 --- debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian/changelog b/debian/changelog index 15b3c049c..578a8e314 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +yunohost (3.7.0.1) testing; urgency=low + + - Hotfix to avoid having a shitload of warnings displayed during the permission migration + + -- Alexandre Aubin Thu, 31 Oct 2019 20:35:00 +0000 + yunohost (3.7.0) testing; urgency=low # ~ Major stuff From e91429ad49266b289f7eca7c62d52f35f2012c7d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 4 Nov 2019 19:23:30 +0100 Subject: [PATCH 0386/3170] Improve app_upgrade error management --- locales/en.json | 4 +- src/yunohost/app.py | 96 +++++++++++++++++++++++---------------------- 2 files changed, 52 insertions(+), 48 deletions(-) diff --git a/locales/en.json b/locales/en.json index 27f25e095..6c7d0b42d 100644 --- a/locales/en.json +++ b/locales/en.json @@ -31,7 +31,6 @@ "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_not_upgraded": "The app '{failed_app}' failed to upgrade, and as a consequence the following apps upgrades have been cancelled: {apps}", - "app_upgrade_stopped": "Upgrading all apps was stopped to prevent possible damage because one app could not be upgraded", "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}", "app_not_properly_removed": "{app:s} has not been properly removed", @@ -50,7 +49,8 @@ "app_unsupported_remote_type": "Unsupported remote type used for the app", "app_upgrade_several_apps": "The following apps will be upgraded: {apps}", "app_upgrade_app_name": "Now upgrading {app}…", - "app_upgrade_failed": "Could not upgrade {app:s}", + "app_upgrade_failed": "Could not upgrade {app:s}: {error}", + "app_upgrade_script_failed": "An error occurred inside the app upgrade script", "app_upgrade_some_app_failed": "Some apps could not be upgraded", "app_upgraded": "{app:s} upgraded", "apps_already_up_to_date": "All apps are already up-to-date", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index a4185b880..f92adf9e4 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -727,82 +727,86 @@ def app_upgrade(app=[], url=None, file=None): # Execute App upgrade script os.system('chown -hR admin: %s' % INSTALL_TMP) + # Execute the app upgrade script + upgrade_failed = True try: upgrade_retcode = hook_exec(extracted_app_folder + '/scripts/upgrade', args=args_list, env=env_dict)[0] + + upgrade_failed = True if upgrade_retcode != 0 else False + if upgrade_failed: + error = m18n.n('app_upgrade_script_failed') + logger.exception(m18n.n("app_upgrade_failed", app=app_instance_name, error=error)) + failure_message_with_debug_instructions = operation_logger.error(error) + # Script got manually interrupted ... N.B. : KeyboardInterrupt does not inherit from Exception except (KeyboardInterrupt, EOFError): upgrade_retcode = -1 + error = m18n.n('operation_interrupted') + logger.exception(m18n.n("app_upgrade_failed", app=app_instance_name, error=error)) + failure_message_with_debug_instructions = operation_logger.error(error) + # Something wrong happened in Yunohost's code (most probably hook_exec) except Exception: import traceback - logger.exception(m18n.n('unexpected_error', error=u"\n" + traceback.format_exc())) + error = m18n.n('unexpected_error', error=u"\n" + traceback.format_exc()) + logger.exception(m18n.n("app_install_failed", app=app_instance_name, error=error)) + failure_message_with_debug_instructions = operation_logger.error(error) finally: - - # Did the script succeed ? - if upgrade_retcode == -1: - error_msg = m18n.n('operation_interrupted') - operation_logger.error(error_msg) - elif upgrade_retcode != 0: - error_msg = m18n.n('app_upgrade_failed', app=app_instance_name) - operation_logger.error(error_msg) - - # Did it broke the system ? + # 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 - error_msg = operation_logger.error(str(e)) + logger.exception(m18n.n("app_upgrade_failed", app=app_instance_name, error=str(e))) + failure_message_with_debug_instructions = operation_logger.error(str(e)) # If upgrade failed or broke the system, # raise an error and interrupt all other pending upgrades - if upgrade_retcode != 0 or broke_the_system: + if upgrade_failed or broke_the_system: # display this if there are remaining apps if apps[number + 1:]: - logger.error(m18n.n('app_upgrade_stopped')) not_upgraded_apps = apps[number:] - # we don't want to continue upgrading apps here in case that breaks - # everything - raise YunohostError('app_not_upgraded', + logger.error(m18n.n('app_not_upgraded', failed_app=app_instance_name, - apps=', '.join(not_upgraded_apps)) - else: - raise YunohostError(error_msg, raw_msg=True) + apps=', '.join(not_upgraded_apps))) + + raise YunohostError(failure_message_with_debug_instructions, raw_msg=True) # Otherwise we're good and keep going ! - else: - now = int(time.time()) - # TODO: Move install_time away from app_setting - app_setting(app_instance_name, 'update_time', now) - status['upgraded_at'] = now + now = int(time.time()) + # TODO: Move install_time away from app_setting + app_setting(app_instance_name, 'update_time', now) + status['upgraded_at'] = now - # Clean hooks and add new ones - hook_remove(app_instance_name) - if 'hooks' in os.listdir(extracted_app_folder): - for hook in os.listdir(extracted_app_folder + '/hooks'): - hook_add(app_instance_name, extracted_app_folder + '/hooks/' + hook) + # Clean hooks and add new ones + hook_remove(app_instance_name) + if 'hooks' in os.listdir(extracted_app_folder): + for hook in os.listdir(extracted_app_folder + '/hooks'): + hook_add(app_instance_name, extracted_app_folder + '/hooks/' + hook) - # Store app status - with open(app_setting_path + '/status.json', 'w+') as f: - json.dump(status, f) + # Store app status + with open(app_setting_path + '/status.json', 'w+') as f: + json.dump(status, f) - # Replace scripts and manifest and conf (if exists) - os.system('rm -rf "%s/scripts" "%s/manifest.toml %s/manifest.json %s/conf"' % (app_setting_path, app_setting_path, app_setting_path, app_setting_path)) + # Replace scripts and manifest and conf (if exists) + os.system('rm -rf "%s/scripts" "%s/manifest.toml %s/manifest.json %s/conf"' % (app_setting_path, app_setting_path, app_setting_path, app_setting_path)) - if os.path.exists(os.path.join(extracted_app_folder, "manifest.json")): - os.system('mv "%s/manifest.json" "%s/scripts" %s' % (extracted_app_folder, extracted_app_folder, app_setting_path)) - if os.path.exists(os.path.join(extracted_app_folder, "manifest.toml")): - os.system('mv "%s/manifest.toml" "%s/scripts" %s' % (extracted_app_folder, extracted_app_folder, app_setting_path)) + if os.path.exists(os.path.join(extracted_app_folder, "manifest.json")): + os.system('mv "%s/manifest.json" "%s/scripts" %s' % (extracted_app_folder, extracted_app_folder, app_setting_path)) + if os.path.exists(os.path.join(extracted_app_folder, "manifest.toml")): + os.system('mv "%s/manifest.toml" "%s/scripts" %s' % (extracted_app_folder, extracted_app_folder, app_setting_path)) - for file_to_copy in ["actions.json", "actions.toml", "config_panel.json", "config_panel.toml", "conf"]: - if os.path.exists(os.path.join(extracted_app_folder, file_to_copy)): - os.system('cp -R %s/%s %s' % (extracted_app_folder, file_to_copy, app_setting_path)) + for file_to_copy in ["actions.json", "actions.toml", "config_panel.json", "config_panel.toml", "conf"]: + if os.path.exists(os.path.join(extracted_app_folder, file_to_copy)): + os.system('cp -R %s/%s %s' % (extracted_app_folder, file_to_copy, app_setting_path)) - # So much win - logger.success(m18n.n('app_upgraded', app=app_instance_name)) + # So much win + logger.success(m18n.n('app_upgraded', app=app_instance_name)) - hook_callback('post_app_upgrade', args=args_list, env=env_dict) - operation_logger.success() + hook_callback('post_app_upgrade', args=args_list, env=env_dict) + operation_logger.success() permission_sync_to_user() From 717ec04f547db285060f503ed6686002f290daa5 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 4 Nov 2019 20:20:12 +0100 Subject: [PATCH 0387/3170] Move debug log dump from ynh_exit_properly to the core after failed app operation --- data/helpers.d/utils | 37 ------------------------------------- src/yunohost/app.py | 31 +++++++++++++++++++++++++++++++ src/yunohost/backup.py | 7 +++++-- 3 files changed, 36 insertions(+), 39 deletions(-) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index e1feed6b1..a359423bb 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -28,43 +28,6 @@ ynh_exit_properly () { # Small tempo to avoid the next message being mixed up with other DEBUG messages sleep 0.5 - ynh_print_err --message="!!\n $app's script has encountered an error. Its execution was cancelled.\n!!" - - # If the script is executed from the CLI, dump the end of the log that precedes the crash. - if [ "$YNH_INTERFACE" == "cli" ] - then - # Unset xtrace to not spoil the log - set +x - - local ynh_log="/var/log/yunohost/yunohost-cli.log" - - # Wait for the log to be fill with the data until the crash. - local timeout=0 - while ! tail --lines=20 "$ynh_log" | grep --quiet "+ ynh_exit_properly" - do - ((timeout++)) - if [ $timeout -eq 500 ]; then - break - fi - done - - echo -e "\e[34m\e[1mPlease find here an extract of the log before the crash:\e[0m" >&2 - # Tail the last 30 lines of log of YunoHost - # But remove all lines after "ynh_exit_properly" - # Remove the timestamp at the beginning of the line - # Remove "yunohost.hook..." - # Add DEBUG and color it at the beginning of each log line. - echo -e "$(tail --lines=30 "$ynh_log" \ - | sed '1,/+ ynh_exit_properly/!d' \ - | sed 's/^[[:digit:]: ,-]*//g' \ - | sed 's/ *yunohost.hook.*\]/ -/g' \ - | sed 's/^WARNING /&/g' \ - | sed 's/^DEBUG /& /g' \ - | sed 's/^INFO /& /g' \ - | sed 's/^/\\e[34m\\e[1m[DEBUG]\\e[0m: /g')" >&2 - set -x - fi - if type -t ynh_clean_setup > /dev/null; then # Check if the function exist in the app script. ynh_clean_setup # Call the function to do specific cleaning for the app. fi diff --git a/src/yunohost/app.py b/src/yunohost/app.py index f92adf9e4..2d32fc0cc 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -738,6 +738,8 @@ def app_upgrade(app=[], url=None, file=None): error = m18n.n('app_upgrade_script_failed') logger.exception(m18n.n("app_upgrade_failed", app=app_instance_name, error=error)) failure_message_with_debug_instructions = operation_logger.error(error) + if msettings.get('interface') != 'api': + dump_app_log_extract_for_debugging(operation_logger) # Script got manually interrupted ... N.B. : KeyboardInterrupt does not inherit from Exception except (KeyboardInterrupt, EOFError): upgrade_retcode = -1 @@ -1002,6 +1004,8 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu error = m18n.n('app_install_script_failed') logger.exception(m18n.n("app_install_failed", app=app_id, error=error)) failure_message_with_debug_instructions = operation_logger.error(error) + if msettings.get('interface') != 'api': + dump_app_log_extract_for_debugging(operation_logger) # Script got manually interrupted ... N.B. : KeyboardInterrupt does not inherit from Exception except (KeyboardInterrupt, EOFError): error = m18n.n('operation_interrupted') @@ -1118,6 +1122,33 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu hook_callback('post_app_install', args=args_list, env=env_dict) +def dump_app_log_extract_for_debugging(operation_logger): + + with open(operation_logger.log_path, "r") as f: + lines = f.readlines() + + lines_to_display = [] + for line in lines: + + if not ": " in line.strip(): + continue + + # A line typically looks like + # 2019-10-19 16:10:27,611: DEBUG - + mysql -u piwigo --password=********** -B piwigo + # And we just want the part starting by "DEBUG - " + line = line.strip().split(": ", 1)[1] + lines_to_display.append(line) + + if line.endswith("+ ynh_exit_properly"): + break + elif len(lines_to_display) > 20: + lines_to_display.pop(0) + + logger.warning("Here's an extract of the logs before the crash. It might help debugging the error:") + for line in lines_to_display: + logger.info(line) + + def _migrate_legacy_permissions(app): from yunohost.permission import user_permission_list, user_permission_update diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 213f2cec1..3344d2807 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -36,14 +36,14 @@ from datetime import datetime from glob import glob from collections import OrderedDict -from moulinette import msignals, m18n +from moulinette import msignals, m18n, msettings from yunohost.utils.error import YunohostError from moulinette.utils import filesystem from moulinette.utils.log import getActionLogger from moulinette.utils.filesystem import read_file, mkdir, write_to_yaml, read_yaml from yunohost.app import ( - app_info, _is_installed, _parse_app_instance_name, _patch_php5 + app_info, _is_installed, _parse_app_instance_name, _patch_php5, dump_app_log_extract_for_debugging ) from yunohost.hook import ( hook_list, hook_info, hook_callback, hook_exec, CUSTOM_HOOK_FOLDER @@ -1399,6 +1399,9 @@ class RestoreManager(): logger.exception(msg) operation_logger.error(msg) + if msettings.get('interface') != 'api': + dump_app_log_extract_for_debugging(operation_logger) + self.targets.set_result("apps", app_instance_name, "Error") remove_script = os.path.join(app_scripts_in_archive, 'remove') From cb247a8140d9ee8a71c663a2da9b3d106ef969bf Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 4 Nov 2019 21:53:21 +0100 Subject: [PATCH 0388/3170] Get rid of app's status.json --- locales/en.json | 1 + src/yunohost/app.py | 95 ++----------------- .../0013_remove_app_status_json.py | 31 ++++++ src/yunohost/tools.py | 5 +- 4 files changed, 43 insertions(+), 89 deletions(-) create mode 100644 src/yunohost/data_migrations/0013_remove_app_status_json.py diff --git a/locales/en.json b/locales/en.json index 6c7d0b42d..9b322b322 100644 --- a/locales/en.json +++ b/locales/en.json @@ -320,6 +320,7 @@ "migration_description_0010_migrate_to_apps_json": "Remove deprecated applists and use the new unified 'apps.json' list instead", "migration_description_0011_setup_group_permission": "Set up user group and set up permission for apps and services", "migration_description_0012_postgresql_password_to_md5_authentication": "Force PostgreSQL authentication to use MD5 for local connections", + "migration_description_0013_remove_app_status_json": "Remove legacy status.json app files", "migration_0003_start": "Starting migration to Stretch. The logs will be available in {logfile}.", "migration_0003_patching_sources_list": "Patching the sources.lists…", "migration_0003_main_upgrade": "Starting main upgrade…", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index f92adf9e4..29c55d276 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -234,8 +234,6 @@ def app_list(filter=None, raw=False, installed=False, with_backup=False): Keyword argument: filter -- Name filter of app_id or app_name - offset -- Starting number for app fetching - limit -- Maximum number of app fetched raw -- Return the full app_dict installed -- Return only installed apps with_backup -- Return only apps with backup feature (force --installed filter) @@ -310,8 +308,6 @@ def app_list(filter=None, raw=False, installed=False, with_backup=False): if raw: app_info_dict['installed'] = app_installed - if app_installed: - app_info_dict['status'] = _get_app_status(app_id) # dirty: we used to have manifest containing multi_instance value in form of a string # but we've switched to bool, this line ensure retrocompatibility @@ -338,13 +334,12 @@ def app_list(filter=None, raw=False, installed=False, with_backup=False): return {'apps': list_dict} if not raw else list_dict -def app_info(app, show_status=False, raw=False): +def app_info(app, raw=False): """ Get app info Keyword argument: app -- Specific app ID - show_status -- Show app installation status raw -- Return the full app_dict """ @@ -353,6 +348,9 @@ def app_info(app, show_status=False, raw=False): app_setting_path = APPS_SETTING_PATH + app + # Retrieve manifest and status + manifest = _get_manifest_of_app(app_setting_path) + if raw: ret = app_list(filter=app, raw=True)[app] ret['settings'] = _get_app_settings(app) @@ -370,28 +368,16 @@ def app_info(app, show_status=False, raw=False): ret['upgradable'] = upgradable ret['change_url'] = os.path.exists(os.path.join(app_setting_path, "scripts", "change_url")) - - manifest = _get_manifest_of_app(os.path.join(APPS_SETTING_PATH, app)) - ret['version'] = manifest.get('version', '-') return ret - # Retrieve manifest and status - manifest = _get_manifest_of_app(app_setting_path) - status = _get_app_status(app, format_date=True) - info = { 'name': manifest['name'], 'description': _value_for_locale(manifest['description']), - # FIXME: Temporarly allow undefined license 'license': manifest.get('license', m18n.n('license_undefined')), - # FIXME: Temporarly allow undefined version 'version': manifest.get('version', '-'), - # TODO: Add more info } - if show_status: - info['status'] = status return info @@ -699,10 +685,6 @@ def app_upgrade(app=[], url=None, file=None): app_setting_path = APPS_SETTING_PATH + '/' + app_instance_name - # Retrieve current app status - status = _get_app_status(app_instance_name) - status['remote'] = manifest.get('remote', None) - # Retrieve arguments list for upgrade script # TODO: Allow to specify arguments args_odict = _parse_args_from_manifest(manifest, 'upgrade') @@ -776,9 +758,8 @@ def app_upgrade(app=[], url=None, file=None): # Otherwise we're good and keep going ! now = int(time.time()) - # TODO: Move install_time away from app_setting app_setting(app_instance_name, 'update_time', now) - status['upgraded_at'] = now + app_setting(app_instance_name, 'current_revision', manifest.get('remote', {}).get('revision', "?")) # Clean hooks and add new ones hook_remove(app_instance_name) @@ -786,10 +767,6 @@ def app_upgrade(app=[], url=None, file=None): for hook in os.listdir(extracted_app_folder + '/hooks'): hook_add(app_instance_name, extracted_app_folder + '/hooks/' + hook) - # Store app status - with open(app_setting_path + '/status.json', 'w+') as f: - json.dump(status, f) - # Replace scripts and manifest and conf (if exists) os.system('rm -rf "%s/scripts" "%s/manifest.toml %s/manifest.json %s/conf"' % (app_setting_path, app_setting_path, app_setting_path, app_setting_path)) @@ -834,14 +811,6 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu if not os.path.exists(INSTALL_TMP): os.makedirs(INSTALL_TMP) - status = { - 'installed_at': int(time.time()), - 'upgraded_at': None, - 'remote': { - 'type': None, - }, - } - def confirm_install(confirm): # Ignore if there's nothing for confirm (good quality app), if --force is used # or if request on the API (confirm already implemented on the API side) @@ -897,7 +866,6 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu manifest, extracted_app_folder = _extract_app_from_file(app) else: raise YunohostError('app_unknown') - status['remote'] = manifest.get('remote', {}) # Check ID if 'id' not in manifest or '__' in manifest['id']: @@ -962,9 +930,9 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu app_settings = { 'id': app_instance_name, 'label': label if label else manifest['name'], + 'install_time': int(time.time()), + 'current_revision': manifest.get('remote', {}).get('revision', "?") } - # TODO: Move install_time away from app settings - app_settings['install_time'] = status['installed_at'] _set_app_settings(app_instance_name, app_settings) # Apply dirty patch to make php5 apps compatible with php7 @@ -1092,10 +1060,6 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu for file in os.listdir(extracted_app_folder + '/hooks'): hook_add(app_instance_name, extracted_app_folder + '/hooks/' + file) - # Store app status - with open(app_setting_path + '/status.json', 'w+') as f: - json.dump(status, f) - # Clean and set permissions shutil.rmtree(extracted_app_folder) os.system('chmod -R 400 %s' % app_setting_path) @@ -2180,51 +2144,6 @@ def _set_app_settings(app_id, settings): yaml.safe_dump(settings, f, default_flow_style=False) -def _get_app_status(app_id, format_date=False): - """ - Get app status or create it if needed - - Keyword arguments: - app_id -- The app id - format_date -- Format date fields - - """ - app_setting_path = APPS_SETTING_PATH + app_id - if not os.path.isdir(app_setting_path): - raise YunohostError('app_unknown') - status = {} - - regen_status = True - try: - with open(app_setting_path + '/status.json') as f: - status = json.loads(str(f.read())) - regen_status = False - except IOError: - logger.debug("status file not found for '%s'", app_id, - exc_info=1) - except Exception as e: - logger.warning("could not open or decode %s : %s ... regenerating.", app_setting_path + '/status.json', str(e)) - - if regen_status: - # Create app status - status = { - 'installed_at': app_setting(app_id, 'install_time'), - 'upgraded_at': app_setting(app_id, 'update_time'), - 'remote': {'type': None}, - } - with open(app_setting_path + '/status.json', 'w+') as f: - json.dump(status, f) - - if format_date: - for f in ['installed_at', 'upgraded_at']: - v = status.get(f, None) - if not v: - status[f] = '-' - else: - status[f] = datetime.utcfromtimestamp(v) - return status - - def _extract_app_from_file(path, remove=False): """ Unzip or untar application tarball in APP_TMP_FOLDER, or copy it from a directory diff --git a/src/yunohost/data_migrations/0013_remove_app_status_json.py b/src/yunohost/data_migrations/0013_remove_app_status_json.py new file mode 100644 index 000000000..1cb5bc002 --- /dev/null +++ b/src/yunohost/data_migrations/0013_remove_app_status_json.py @@ -0,0 +1,31 @@ +import os + +from moulinette.utils.log import getActionLogger +from moulinette.utils.filesystem import read_json + +from yunohost.tools import Migration +from yunohost.app import app_setting, APPS_SETTING_PATH + +logger = getActionLogger('yunohost.migration') + +class MyMigration(Migration): + + """Remove legacy app status.json files""" + + def run(self): + + apps = os.listdir(APPS_SETTING_PATH) + + for app in apps: + status_file = os.path.join(APPS_SETTING_PATH, app, "status.json") + if not os.path.exists(status_file): + continue + + try: + status = read_json(status_file) + current_revision = status.get("remote", {}).get("revision", "?") + app_setting(app, 'current_revision', current_revision) + except Exception as e: + logger.warning("Could not migrate status.json from app %s: %s", (app, str(e))) + else: + os.system("rm %s" % status_file) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index f4bb83c15..df462781b 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -525,8 +525,11 @@ def _list_upgradable_apps(): if app_dict["upgradable"] == "yes": + # FIXME : would make more sense for these infos to be computed + # directly in app_info and used to check the upgradability of + # the app... current_version = app_dict.get("version", "?") - current_commit = app_dict.get("status", {}).get("remote", {}).get("revision", "?")[:7] + current_commit = app_dict.get("settings", {}).get("current_revision", "?")[:7] new_version = app_dict.get("manifest",{}).get("version","?") new_commit = app_dict.get("git", {}).get("revision", "?")[:7] From b630724ec9f71190419d92b73e9ac8b1ee93e301 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 4 Nov 2019 22:32:30 +0100 Subject: [PATCH 0389/3170] Can't have duplicated routes --- data/actionsmap/yunohost.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index b7a87ea75..75d51b04e 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1549,9 +1549,6 @@ tools: ### tools_maindomain() maindomain: action_help: Check the current main domain, or change it - api: - - GET /domains/main - - PUT /domains/main arguments: -n: full: --new-main-domain From 9bcbedc356243e78fa3f741f778185f8489c3d75 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 4 Nov 2019 23:02:04 +0100 Subject: [PATCH 0390/3170] Revert "[fix] moulinette logs were never displayed #lol" This reverts commit 6a0959dd1d7ba58b1bfa19de9ae9e2d7881ad556. --- bin/yunohost | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/yunohost b/bin/yunohost index 672c1b539..10a21a9da 100755 --- a/bin/yunohost +++ b/bin/yunohost @@ -145,7 +145,7 @@ def _init_moulinette(debug=False, quiet=False): }, 'moulinette': { 'level': level, - 'handlers': handlers, + 'handlers': [], 'propagate': True, }, 'moulinette.interface': { From 0e98d37c49500182cddff26ba4dad60174bf52f2 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 14 Oct 2019 04:48:56 +0200 Subject: [PATCH 0391/3170] Remove the whole monitoring / glances stuff --- data/actionsmap/yunohost.yml | 141 ----- data/templates/glances/glances.default | 5 - data/templates/yunohost/services.yml | 2 +- debian/control | 2 +- locales/en.json | 14 - src/yunohost/monitor.py | 740 ------------------------- 6 files changed, 2 insertions(+), 902 deletions(-) delete mode 100644 data/templates/glances/glances.default delete mode 100644 src/yunohost/monitor.py diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 75d51b04e..d7de45bfe 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -975,147 +975,6 @@ backup: pattern: *pattern_backup_archive_name -############################# -# Monitor # -############################# -monitor: - category_help: Monitor the server - actions: - - ### monitor_disk() - disk: - action_help: Monitor disk space and usage - api: GET /monitor/disk - arguments: - -f: - full: --filesystem - help: Show filesystem disk space - action: append_const - const: filesystem - dest: units - -t: - full: --io - help: Show I/O throughput - action: append_const - const: io - dest: units - -m: - full: --mountpoint - help: Monitor only the device mounted on MOUNTPOINT - action: store - -H: - full: --human-readable - help: Print sizes in human readable format - action: store_true - - ### monitor_network() - network: - action_help: Monitor network interfaces - api: GET /monitor/network - arguments: - -u: - full: --usage - help: Show interfaces bit rates - action: append_const - const: usage - dest: units - -i: - full: --infos - help: Show network informations - action: append_const - const: infos - dest: units - -c: - full: --check - help: Check network configuration - action: append_const - const: check - dest: units - -H: - full: --human-readable - help: Print sizes in human readable format - action: store_true - - ### monitor_system() - system: - action_help: Monitor system informations and usage - api: GET /monitor/system - arguments: - -m: - full: --memory - help: Show memory usage - action: append_const - const: memory - dest: units - -c: - full: --cpu - help: Show CPU usage and load - action: append_const - const: cpu - dest: units - -p: - full: --process - help: Show processes summary - action: append_const - const: process - dest: units - -u: - full: --uptime - help: Show the system uptime - action: append_const - const: uptime - dest: units - -i: - full: --infos - help: Show system informations - action: append_const - const: infos - dest: units - -H: - full: --human-readable - help: Print sizes in human readable format - action: store_true - - ### monitor_updatestats() - update-stats: - action_help: Update monitoring statistics - api: POST /monitor/stats - arguments: - period: - help: Time period to update - choices: - - day - - week - - month - - ### monitor_showstats() - show-stats: - action_help: Show monitoring statistics - api: GET /monitor/stats - arguments: - period: - help: Time period to show - choices: - - day - - week - - month - - ### monitor_enable() - enable: - action_help: Enable server monitoring - api: PUT /monitor - arguments: - -s: - full: --with-stats - help: Enable monitoring statistics - action: store_true - - ### monitor_disable() - disable: - api: DELETE /monitor - action_help: Disable server monitoring - - ############################# # Settings # ############################# diff --git a/data/templates/glances/glances.default b/data/templates/glances/glances.default deleted file mode 100644 index 22337a0d9..000000000 --- a/data/templates/glances/glances.default +++ /dev/null @@ -1,5 +0,0 @@ -# Default is to launch glances with '-s' option. -DAEMON_ARGS="-s -B 127.0.0.1" - -# Change to 'true' to have glances running at startup -RUN="true" diff --git a/data/templates/yunohost/services.yml b/data/templates/yunohost/services.yml index 0d79b182f..1c0ee031f 100644 --- a/data/templates/yunohost/services.yml +++ b/data/templates/yunohost/services.yml @@ -17,7 +17,6 @@ redis-server: mysql: log: [/var/log/mysql.log,/var/log/mysql.err] alternates: ['mariadb'] -glances: {} ssh: log: /var/log/auth.log metronome: @@ -32,6 +31,7 @@ yunohost-firewall: need_lock: true nslcd: log: /var/log/syslog +glances: null nsswitch: null ssl: null yunohost: null diff --git a/debian/control b/debian/control index b0de9032b..a6d64e0b7 100644 --- a/debian/control +++ b/debian/control @@ -15,7 +15,7 @@ Depends: ${python:Depends}, ${misc:Depends} , python-psutil, python-requests, python-dnspython, python-openssl , python-apt, python-miniupnpc, python-dbus, python-jinja2 , python-toml - , glances, apt-transport-https + , apt-transport-https , dnsutils, bind9utils, unzip, git, curl, cron, wget, jq , ca-certificates, netcat-openbsd, iproute2 , mariadb-server, php-mysql | php-mysqlnd diff --git a/locales/en.json b/locales/en.json index 16c900c85..61c758df1 100644 --- a/locales/en.json +++ b/locales/en.json @@ -375,21 +375,9 @@ "migrations_skip_migration": "Skipping migration {id}…", "migrations_success_forward": "Migration {id} completed", "migrations_to_be_ran_manually": "Migration {id} has to be run manually. Please go to Tools → Migrations on the webadmin page, or run `yunohost tools migrations migrate`.", - "monitor_disabled": "Server monitoring now off", - "monitor_enabled": "Server monitoring now on", - "monitor_glances_con_failed": "Could not connect to Glances server", - "monitor_not_enabled": "Server monitoring is off", - "monitor_period_invalid": "Invalid time period", - "monitor_stats_file_not_found": "Could not find the statistics file", - "monitor_stats_no_update": "No monitoring statistics to update", - "monitor_stats_period_unavailable": "No available statistics for the period", - "mountpoint_unknown": "Unknown mountpoint", "mysql_db_creation_failed": "Could not create MySQL database", "mysql_db_init_failed": "Could not initialize MySQL database", "mysql_db_initialized": "The MySQL database is now initialized", - "network_check_mx_ko": "DNS MX record is not set", - "network_check_smtp_ko": "Outbound e-mail (SMTP port 25) seems to be blocked by your network", - "network_check_smtp_ok": "Outbound e-mail (SMTP port 25) is not blocked", "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?", @@ -479,7 +467,6 @@ "service_description_dnsmasq": "Handles domain name resolution (DNS)", "service_description_dovecot": "Allows e-mail clients to access/fetch email (via IMAP and POP3)", "service_description_fail2ban": "Protects against brute-force and other kinds of attacks from the Internet", - "service_description_glances": "Monitors system info on your server", "service_description_metronome": "Manage XMPP instant messaging accounts", "service_description_mysql": "Stores app data (SQL database)", "service_description_nginx": "Serves or provides access to all the websites hosted on your server", @@ -529,7 +516,6 @@ "tools_upgrade_special_packages_completed": "YunoHost package upgrade completed.\nPress [Enter] to get the command line back", "unbackup_app": "App '{app:s}' will not be saved", "unexpected_error": "Something unexpected went wrong: {error}", - "unit_unknown": "Unknown unit '{unit:s}'", "unlimit": "No quota", "unrestore_app": "App '{app:s}' will not be restored", "update_apt_cache_failed": "Could not to update the cache of APT (Debian's package manager). Here is a dump of the sources.list lines, which might help identify problematic lines: \n{sourceslist}", diff --git a/src/yunohost/monitor.py b/src/yunohost/monitor.py deleted file mode 100644 index 7af55f287..000000000 --- a/src/yunohost/monitor.py +++ /dev/null @@ -1,740 +0,0 @@ -# -*- coding: utf-8 -*- - -""" License - - Copyright (C) 2013 YunoHost - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program; if not, see http://www.gnu.org/licenses - -""" - -""" yunohost_monitor.py - - Monitoring functions -""" -import re -import json -import time -import psutil -import calendar -import subprocess -import xmlrpclib -import os.path -import os -import dns.resolver -import cPickle as pickle -from datetime import datetime - -from moulinette import m18n -from yunohost.utils.error import YunohostError -from moulinette.utils.log import getActionLogger - -from yunohost.utils.network import get_public_ip -from yunohost.domain import _get_maindomain - -logger = getActionLogger('yunohost.monitor') - -GLANCES_URI = 'http://127.0.0.1:61209' -STATS_PATH = '/var/lib/yunohost/stats' -CRONTAB_PATH = '/etc/cron.d/yunohost-monitor' - - -def monitor_disk(units=None, mountpoint=None, human_readable=False): - """ - Monitor disk space and usage - - Keyword argument: - units -- Unit(s) to monitor - mountpoint -- Device mountpoint - human_readable -- Print sizes in human readable format - - """ - glances = _get_glances_api() - result_dname = None - result = {} - - if units is None: - units = ['io', 'filesystem'] - - _format_dname = lambda d: (os.path.realpath(d)).replace('/dev/', '') - - # Get mounted devices - devices = {} - for p in psutil.disk_partitions(all=True): - if not p.device.startswith('/dev/') or not p.mountpoint: - continue - if mountpoint is None: - devices[_format_dname(p.device)] = p.mountpoint - elif mountpoint == p.mountpoint: - dn = _format_dname(p.device) - devices[dn] = p.mountpoint - result_dname = dn - if len(devices) == 0: - if mountpoint is not None: - raise YunohostError('mountpoint_unknown') - return result - - # Retrieve monitoring for unit(s) - for u in units: - if u == 'io': - # Define setter - if len(units) > 1: - def _set(dn, dvalue): - try: - result[dn][u] = dvalue - except KeyError: - result[dn] = {u: dvalue} - else: - def _set(dn, dvalue): - result[dn] = dvalue - - # Iterate over values - devices_names = devices.keys() - for d in json.loads(glances.getDiskIO()): - dname = d.pop('disk_name') - try: - devices_names.remove(dname) - except: - continue - else: - _set(dname, d) - for dname in devices_names: - _set(dname, 'not-available') - elif u == 'filesystem': - # Define setter - if len(units) > 1: - def _set(dn, dvalue): - try: - result[dn][u] = dvalue - except KeyError: - result[dn] = {u: dvalue} - else: - def _set(dn, dvalue): - result[dn] = dvalue - - # Iterate over values - devices_names = devices.keys() - for d in json.loads(glances.getFs()): - dname = _format_dname(d.pop('device_name')) - try: - devices_names.remove(dname) - except: - continue - else: - d['avail'] = d['size'] - d['used'] - if human_readable: - for i in ['used', 'avail', 'size']: - d[i] = binary_to_human(d[i]) + 'B' - _set(dname, d) - for dname in devices_names: - _set(dname, 'not-available') - else: - raise YunohostError('unit_unknown', unit=u) - - if result_dname is not None: - return result[result_dname] - return result - - -def monitor_network(units=None, human_readable=False): - """ - Monitor network interfaces - - Keyword argument: - units -- Unit(s) to monitor - human_readable -- Print sizes in human readable format - - """ - glances = _get_glances_api() - result = {} - - if units is None: - units = ['check', 'usage', 'infos'] - - # Get network devices and their addresses - # TODO / FIXME : use functions in utils/network.py to manage this - devices = {} - output = subprocess.check_output('ip addr show'.split()) - for d in re.split('^(?:[0-9]+: )', output, flags=re.MULTILINE): - # Extract device name (1) and its addresses (2) - m = re.match('([^\s@]+)(?:@[\S]+)?: (.*)', d, flags=re.DOTALL) - if m: - devices[m.group(1)] = m.group(2) - - # Retrieve monitoring for unit(s) - for u in units: - if u == 'check': - result[u] = {} - domain = _get_maindomain() - cmd_check_smtp = os.system('/bin/nc -z -w1 yunohost.org 25') - if cmd_check_smtp == 0: - smtp_check = m18n.n('network_check_smtp_ok') - else: - smtp_check = m18n.n('network_check_smtp_ko') - - try: - answers = dns.resolver.query(domain, 'MX') - mx_check = {} - i = 0 - for server in answers: - mx_id = 'mx%s' % i - mx_check[mx_id] = server - i = i + 1 - except: - mx_check = m18n.n('network_check_mx_ko') - result[u] = { - 'smtp_check': smtp_check, - 'mx_check': mx_check - } - elif u == 'usage': - result[u] = {} - for i in json.loads(glances.getNetwork()): - iname = i['interface_name'] - if iname in devices.keys(): - del i['interface_name'] - if human_readable: - for k in i.keys(): - if k != 'time_since_update': - i[k] = binary_to_human(i[k]) + 'B' - result[u][iname] = i - else: - logger.debug('interface name %s was not found', iname) - elif u == 'infos': - p_ipv4 = get_public_ip() or 'unknown' - - # TODO / FIXME : use functions in utils/network.py to manage this - l_ip = 'unknown' - for name, addrs in devices.items(): - if name == 'lo': - continue - if not isinstance(l_ip, dict): - l_ip = {} - l_ip[name] = _extract_inet(addrs) - - gateway = 'unknown' - output = subprocess.check_output('ip route show'.split()) - m = re.search('default via (.*) dev ([a-z]+[0-9]?)', output) - if m: - addr = _extract_inet(m.group(1), True) - if len(addr) == 1: - proto, gateway = addr.popitem() - - result[u] = { - 'public_ip': p_ipv4, - 'local_ip': l_ip, - 'gateway': gateway, - } - else: - raise YunohostError('unit_unknown', unit=u) - - if len(units) == 1: - return result[units[0]] - return result - - -def monitor_system(units=None, human_readable=False): - """ - Monitor system informations and usage - - Keyword argument: - units -- Unit(s) to monitor - human_readable -- Print sizes in human readable format - - """ - glances = _get_glances_api() - result = {} - - if units is None: - units = ['memory', 'cpu', 'process', 'uptime', 'infos'] - - # Retrieve monitoring for unit(s) - for u in units: - if u == 'memory': - ram = json.loads(glances.getMem()) - swap = json.loads(glances.getMemSwap()) - if human_readable: - for i in ram.keys(): - if i != 'percent': - ram[i] = binary_to_human(ram[i]) + 'B' - for i in swap.keys(): - if i != 'percent': - swap[i] = binary_to_human(swap[i]) + 'B' - result[u] = { - 'ram': ram, - 'swap': swap - } - elif u == 'cpu': - result[u] = { - 'load': json.loads(glances.getLoad()), - 'usage': json.loads(glances.getCpu()) - } - elif u == 'process': - result[u] = json.loads(glances.getProcessCount()) - elif u == 'uptime': - result[u] = (str(datetime.now() - datetime.fromtimestamp(psutil.boot_time())).split('.')[0]) - elif u == 'infos': - result[u] = json.loads(glances.getSystem()) - else: - raise YunohostError('unit_unknown', unit=u) - - if len(units) == 1 and not isinstance(result[units[0]], str): - return result[units[0]] - return result - - -def monitor_update_stats(period): - """ - Update monitoring statistics - - Keyword argument: - period -- Time period to update (day, week, month) - - """ - if period not in ['day', 'week', 'month']: - raise YunohostError('monitor_period_invalid') - - stats = _retrieve_stats(period) - if not stats: - stats = {'disk': {}, 'network': {}, 'system': {}, 'timestamp': []} - - monitor = None - # Get monitoring stats - if period == 'day': - monitor = _monitor_all('day') - else: - t = stats['timestamp'] - p = 'day' if period == 'week' else 'week' - if len(t) > 0: - monitor = _monitor_all(p, t[len(t) - 1]) - else: - monitor = _monitor_all(p, 0) - if not monitor: - raise YunohostError('monitor_stats_no_update') - - stats['timestamp'].append(time.time()) - - # Append disk stats - for dname, units in monitor['disk'].items(): - disk = {} - # Retrieve current stats for disk name - if dname in stats['disk'].keys(): - disk = stats['disk'][dname] - - for unit, values in units.items(): - # Continue if unit doesn't contain stats - if not isinstance(values, dict): - continue - - # Retrieve current stats for unit and append new ones - curr = disk[unit] if unit in disk.keys() else {} - if unit == 'io': - disk[unit] = _append_to_stats(curr, values, 'time_since_update') - elif unit == 'filesystem': - disk[unit] = _append_to_stats(curr, values, ['fs_type', 'mnt_point']) - stats['disk'][dname] = disk - - # Append network stats - net_usage = {} - for iname, values in monitor['network']['usage'].items(): - # Continue if units doesn't contain stats - if not isinstance(values, dict): - continue - - # Retrieve current stats and append new ones - curr = {} - if 'usage' in stats['network'] and iname in stats['network']['usage']: - curr = stats['network']['usage'][iname] - net_usage[iname] = _append_to_stats(curr, values, 'time_since_update') - stats['network'] = {'usage': net_usage, 'infos': monitor['network']['infos']} - - # Append system stats - for unit, values in monitor['system'].items(): - # Continue if units doesn't contain stats - if not isinstance(values, dict): - continue - - # Set static infos unit - if unit == 'infos': - stats['system'][unit] = values - continue - - # Retrieve current stats and append new ones - curr = stats['system'][unit] if unit in stats['system'].keys() else {} - stats['system'][unit] = _append_to_stats(curr, values) - - _save_stats(stats, period) - - -def monitor_show_stats(period, date=None): - """ - Show monitoring statistics - - Keyword argument: - period -- Time period to show (day, week, month) - - """ - if period not in ['day', 'week', 'month']: - raise YunohostError('monitor_period_invalid') - - result = _retrieve_stats(period, date) - if result is False: - raise YunohostError('monitor_stats_file_not_found') - elif result is None: - raise YunohostError('monitor_stats_period_unavailable') - return result - - -def monitor_enable(with_stats=False): - """ - Enable server monitoring - - Keyword argument: - with_stats -- Enable monitoring statistics - - """ - from yunohost.service import (service_status, service_enable, - service_start) - - glances = service_status('glances') - if glances['status'] != 'running': - service_start('glances') - if glances['loaded'] != 'enabled': - service_enable('glances') - - # Install crontab - if with_stats: - # day: every 5 min # week: every 1 h # month: every 4 h # - rules = ('*/5 * * * * root {cmd} day >> /dev/null\n' - '3 * * * * root {cmd} week >> /dev/null\n' - '6 */4 * * * root {cmd} month >> /dev/null').format( - cmd='/usr/bin/yunohost --quiet monitor update-stats') - with open(CRONTAB_PATH, 'w') as f: - f.write(rules) - - logger.success(m18n.n('monitor_enabled')) - - -def monitor_disable(): - """ - Disable server monitoring - - """ - from yunohost.service import (service_status, service_disable, - service_stop) - - glances = service_status('glances') - if glances['status'] != 'inactive': - service_stop('glances') - if glances['loaded'] != 'disabled': - try: - service_disable('glances') - except YunohostError as e: - logger.warning(e.strerror) - - # Remove crontab - try: - os.remove(CRONTAB_PATH) - except: - pass - - logger.success(m18n.n('monitor_disabled')) - - -def _get_glances_api(): - """ - Retrieve Glances API running on the local server - - """ - try: - p = xmlrpclib.ServerProxy(GLANCES_URI) - p.system.methodHelp('getAll') - except (xmlrpclib.ProtocolError, IOError): - pass - else: - return p - - from yunohost.service import service_status - - if service_status('glances')['status'] != 'running': - raise YunohostError('monitor_not_enabled') - raise YunohostError('monitor_glances_con_failed') - - -def _extract_inet(string, skip_netmask=False, skip_loopback=True): - """ - Extract IP addresses (v4 and/or v6) from a string limited to one - address by protocol - - Keyword argument: - string -- String to search in - skip_netmask -- True to skip subnet mask extraction - skip_loopback -- False to include addresses reserved for the - loopback interface - - Returns: - A dict of {protocol: address} with protocol one of 'ipv4' or 'ipv6' - - """ - ip4_pattern = '((25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}' - ip6_pattern = '(((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)' - ip4_pattern += '/[0-9]{1,2})' if not skip_netmask else ')' - ip6_pattern += '/[0-9]{1,3})' if not skip_netmask else ')' - result = {} - - for m in re.finditer(ip4_pattern, string): - addr = m.group(1) - if skip_loopback and addr.startswith('127.'): - continue - - # Limit to only one result - result['ipv4'] = addr - break - - for m in re.finditer(ip6_pattern, string): - addr = m.group(1) - if skip_loopback and addr == '::1': - continue - - # Limit to only one result - result['ipv6'] = addr - break - - return result - - -def binary_to_human(n, customary=False): - """ - Convert bytes or bits into human readable format with binary prefix - - Keyword argument: - n -- Number to convert - customary -- Use customary symbol instead of IEC standard - - """ - symbols = ('Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi') - if customary: - symbols = ('K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y') - prefix = {} - for i, s in enumerate(symbols): - prefix[s] = 1 << (i + 1) * 10 - for s in reversed(symbols): - if n >= prefix[s]: - value = float(n) / prefix[s] - return '%.1f%s' % (value, s) - return "%s" % n - - -def _retrieve_stats(period, date=None): - """ - Retrieve statistics from pickle file - - Keyword argument: - period -- Time period to retrieve (day, week, month) - date -- Date of stats to retrieve - - """ - pkl_file = None - - # Retrieve pickle file - if date is not None: - timestamp = calendar.timegm(date) - pkl_file = '%s/%d_%s.pkl' % (STATS_PATH, timestamp, period) - else: - pkl_file = '%s/%s.pkl' % (STATS_PATH, period) - if not os.path.isfile(pkl_file): - return False - - # Read file and process its content - with open(pkl_file, 'r') as f: - result = pickle.load(f) - if not isinstance(result, dict): - return None - return result - - -def _save_stats(stats, period, date=None): - """ - Save statistics to pickle file - - Keyword argument: - stats -- Stats dict to save - period -- Time period of stats (day, week, month) - date -- Date of stats - - """ - pkl_file = None - - # Set pickle file name - if date is not None: - timestamp = calendar.timegm(date) - pkl_file = '%s/%d_%s.pkl' % (STATS_PATH, timestamp, period) - else: - pkl_file = '%s/%s.pkl' % (STATS_PATH, period) - if not os.path.isdir(STATS_PATH): - os.makedirs(STATS_PATH) - - # Limit stats - if date is None: - t = stats['timestamp'] - limit = {'day': 86400, 'week': 604800, 'month': 2419200} - if (t[len(t) - 1] - t[0]) > limit[period]: - begin = t[len(t) - 1] - limit[period] - stats = _filter_stats(stats, begin) - - # Write file content - with open(pkl_file, 'w') as f: - pickle.dump(stats, f) - return True - - -def _monitor_all(period=None, since=None): - """ - Monitor all units (disk, network and system) for the given period - If since is None, real-time monitoring is returned. Otherwise, the - mean of stats since this timestamp is calculated and returned. - - Keyword argument: - period -- Time period to monitor (day, week, month) - since -- Timestamp of the stats beginning - - """ - result = {'disk': {}, 'network': {}, 'system': {}} - - # Real-time stats - if period == 'day' and since is None: - result['disk'] = monitor_disk() - result['network'] = monitor_network() - result['system'] = monitor_system() - return result - - # Retrieve stats and calculate mean - stats = _retrieve_stats(period) - if not stats: - return None - stats = _filter_stats(stats, since) - if not stats: - return None - result = _calculate_stats_mean(stats) - - return result - - -def _filter_stats(stats, t_begin=None, t_end=None): - """ - Filter statistics by beginning and/or ending timestamp - - Keyword argument: - stats -- Dict stats to filter - t_begin -- Beginning timestamp - t_end -- Ending timestamp - - """ - if t_begin is None and t_end is None: - return stats - - i_begin = i_end = None - # Look for indexes of timestamp interval - for i, t in enumerate(stats['timestamp']): - if t_begin and i_begin is None and t >= t_begin: - i_begin = i - if t_end and i != 0 and i_end is None and t > t_end: - i_end = i - # Check indexes - if i_begin is None: - if t_begin and t_begin > stats['timestamp'][0]: - return None - i_begin = 0 - if i_end is None: - if t_end and t_end < stats['timestamp'][0]: - return None - i_end = len(stats['timestamp']) - if i_begin == 0 and i_end == len(stats['timestamp']): - return stats - - # Filter function - def _filter(s, i, j): - for k, v in s.items(): - if isinstance(v, dict): - s[k] = _filter(v, i, j) - elif isinstance(v, list): - s[k] = v[i:j] - return s - - stats = _filter(stats, i_begin, i_end) - return stats - - -def _calculate_stats_mean(stats): - """ - Calculate the weighted mean for each statistic - - Keyword argument: - stats -- Stats dict to process - - """ - timestamp = stats['timestamp'] - t_sum = sum(timestamp) - del stats['timestamp'] - - # Weighted mean function - def _mean(s, t, ts): - for k, v in s.items(): - if isinstance(v, dict): - s[k] = _mean(v, t, ts) - elif isinstance(v, list): - try: - nums = [float(x * t[i]) for i, x in enumerate(v)] - except: - pass - else: - s[k] = sum(nums) / float(ts) - return s - - stats = _mean(stats, timestamp, t_sum) - return stats - - -def _append_to_stats(stats, monitor, statics=[]): - """ - Append monitoring statistics to current statistics - - Keyword argument: - stats -- Current stats dict - monitor -- Monitoring statistics - statics -- List of stats static keys - - """ - if isinstance(statics, str): - statics = [statics] - - # Appending function - def _append(s, m, st): - for k, v in m.items(): - if k in st: - s[k] = v - elif isinstance(v, dict): - if k not in s: - s[k] = {} - s[k] = _append(s[k], v, st) - else: - if k not in s: - s[k] = [] - if isinstance(v, list): - s[k].extend(v) - else: - s[k].append(v) - return s - - stats = _append(stats, monitor, statics) - return stats From e463e282f28d408d3a1d53fe74000fd238ba0ef8 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 6 Nov 2019 17:19:28 +0100 Subject: [PATCH 0392/3170] Yolo fix to avoid regenerating glances' conf during upgrade to 3.8 --- src/yunohost/regenconf.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/yunohost/regenconf.py b/src/yunohost/regenconf.py index b7a42dd9d..193b23435 100644 --- a/src/yunohost/regenconf.py +++ b/src/yunohost/regenconf.py @@ -131,6 +131,15 @@ def regen_conf(operation_logger, names=[], with_diff=False, force=False, dry_run show_info=False)['hooks'] names.remove('ssh') + # Dirty hack for legacy code : avoid attempting to regen the conf for + # glances because it got removed ... This is only needed *once* + # during the upgrade from 3.7 to 3.8 because Yunohost will attempt to + # regen glance's conf *before* it gets automatically removed from + # services.yml (which will happens only during the regen-conf of + # 'yunohost', so at the very end of the regen-conf cycle) Anyway, + # this can be safely removed once we're in >= 4.0 + names.remove("glances") + pre_result = hook_callback('conf_regen', names, pre_callback=_pre_call) # Keep only the hook names with at least one success From aee62064bf57b91210be3bc858930b1101deba2a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 6 Nov 2019 18:20:14 +0100 Subject: [PATCH 0393/3170] Salvage binary_to_human from old monitor.py, needed for backup stuff --- src/yunohost/backup.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 213f2cec1..ddf64774e 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -48,7 +48,6 @@ from yunohost.app import ( from yunohost.hook import ( hook_list, hook_info, hook_callback, hook_exec, CUSTOM_HOOK_FOLDER ) -from yunohost.monitor import binary_to_human from yunohost.tools import tools_postinstall from yunohost.regenconf import regen_conf from yunohost.log import OperationLogger @@ -2492,3 +2491,23 @@ def disk_usage(path): du_output = subprocess.check_output(['du', '-sb', path]) return int(du_output.split()[0].decode('utf-8')) + + +def binary_to_human(n, customary=False): + """ + Convert bytes or bits into human readable format with binary prefix + Keyword argument: + n -- Number to convert + customary -- Use customary symbol instead of IEC standard + """ + symbols = ('Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi') + if customary: + symbols = ('K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y') + prefix = {} + for i, s in enumerate(symbols): + prefix[s] = 1 << (i + 1) * 10 + for s in reversed(symbols): + if n >= prefix[s]: + value = float(n) / prefix[s] + return '%.1f%s' % (value, s) + return "%s" % n From 76cbad0a9a75c06308505eabe58e6f50752da437 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 6 Nov 2019 18:35:21 +0100 Subject: [PATCH 0394/3170] Make sure the users actually exists when migrating legacy custom permissions --- .../data_migrations/0011_setup_group_permission.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/yunohost/data_migrations/0011_setup_group_permission.py b/src/yunohost/data_migrations/0011_setup_group_permission.py index e9ca32294..c80686344 100644 --- a/src/yunohost/data_migrations/0011_setup_group_permission.py +++ b/src/yunohost/data_migrations/0011_setup_group_permission.py @@ -7,7 +7,7 @@ from moulinette.utils.log import getActionLogger from moulinette.utils.filesystem import read_yaml from yunohost.tools import Migration -from yunohost.user import user_group_create, user_group_update +from yunohost.user import user_list, user_group_create, user_group_update from yunohost.app import app_setting, app_list from yunohost.regenconf import regen_conf, BACKUP_CONF_DIR from yunohost.permission import permission_create, user_permission_update, permission_sync_to_user @@ -109,10 +109,11 @@ class MyMigration(Migration): url = "/" if domain and path else None if permission: - allowed_groups = permission.split(',') + known_users = user_list()["users"].keys() + allowed = [user for user in permission.split(',') if user in known_users] else: - allowed_groups = ["all_users"] - permission_create(app+".main", url=url, allowed=allowed_groups, sync_perm=False) + allowed = ["all_users"] + permission_create(app+".main", url=url, allowed=allowed, sync_perm=False) app_setting(app, 'allowed_users', delete=True) From ddc42e5c8c120a50b30b8c6fc5ae61088732169f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 6 Nov 2019 18:35:21 +0100 Subject: [PATCH 0395/3170] Make sure the users actually exists when migrating legacy custom permissions --- .../data_migrations/0011_setup_group_permission.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/yunohost/data_migrations/0011_setup_group_permission.py b/src/yunohost/data_migrations/0011_setup_group_permission.py index e9ca32294..c80686344 100644 --- a/src/yunohost/data_migrations/0011_setup_group_permission.py +++ b/src/yunohost/data_migrations/0011_setup_group_permission.py @@ -7,7 +7,7 @@ from moulinette.utils.log import getActionLogger from moulinette.utils.filesystem import read_yaml from yunohost.tools import Migration -from yunohost.user import user_group_create, user_group_update +from yunohost.user import user_list, user_group_create, user_group_update from yunohost.app import app_setting, app_list from yunohost.regenconf import regen_conf, BACKUP_CONF_DIR from yunohost.permission import permission_create, user_permission_update, permission_sync_to_user @@ -109,10 +109,11 @@ class MyMigration(Migration): url = "/" if domain and path else None if permission: - allowed_groups = permission.split(',') + known_users = user_list()["users"].keys() + allowed = [user for user in permission.split(',') if user in known_users] else: - allowed_groups = ["all_users"] - permission_create(app+".main", url=url, allowed=allowed_groups, sync_perm=False) + allowed = ["all_users"] + permission_create(app+".main", url=url, allowed=allowed, sync_perm=False) app_setting(app, 'allowed_users', delete=True) From 1372ab916c8db501042397db58a4b91310fff3ab Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 6 Nov 2019 19:05:43 +0100 Subject: [PATCH 0396/3170] Stale strings + try to keep the namespace-like tidy --- locales/en.json | 11 +++-------- src/yunohost/diagnosis.py | 4 ++-- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/locales/en.json b/locales/en.json index 4a432345f..ebe6b4571 100644 --- a/locales/en.json +++ b/locales/en.json @@ -150,18 +150,11 @@ "confirm_app_install_thirdparty": "DANGER! This app is not part of Yunohost's app catalog. Installing third-party apps may compromise the integrity and security of your system. You should probably NOT install it unless you know what you are doing. NO SUPPORT will be provided if this app doesn't work or break your system… If you are willing to take that risk anyway, type '{answers:s}'", "custom_app_url_required": "You must provide a URL to upgrade your custom app {app:s}", "custom_appslist_name_required": "You must provide a name for your custom app list", - "diagnosis_debian_version_error": "Could not retrieve the Debian version: {error}", - "diagnosis_kernel_version_error": "Could not retrieve kernel version: {error}", "diagnosis_basesystem_host": "Server is running Debian {debian_version}.", "diagnosis_basesystem_kernel": "Server is running Linux kernel {kernel_version}", "diagnosis_basesystem_ynh_single_version": "{0} version: {1}", "diagnosis_basesystem_ynh_main_version": "Server is running YunoHost {main_version}", "diagnosis_basesystem_ynh_inconsistent_versions": "You are running inconsistents versions of the YunoHost packages ... most probably because of a failed or partial upgrade.", - "diagnosis_monitor_disk_error": "Could not monitor disks: {error}", - "diagnosis_monitor_system_error": "Could not monitor system: {error}", - "diagnosis_no_apps": "No such installed app", - "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_lock_not_available": "This command can't be ran right now because another program seems to be using the lock of dpkg (the system package manager)", "diagnosis_display_tip_web": "You can go to the Diagnosis section (in the home screen) to see the issues found.", "diagnosis_display_tip_cli": "You can run 'yunohost diagnosis show --issues' to display the issues found.", "diagnosis_failed_for_category": "Diagnosis failed for category '{category}' : {error}", @@ -222,6 +215,7 @@ "diagnosis_http_could_not_diagnose": "Could not diagnose if domain is reachable from outside. Error: {error}", "diagnosis_http_ok": "Domain {domain} is reachable from outside.", "diagnosis_http_unreachable": "Domain {domain} is unreachable through HTTP from outside.", + "diagnosis_unknown_categories": "The following categories are unknown : {categories}", "domain_cannot_remove_main": "You cannot remove '{domain:s}' since it's the main domain, you need first to set another domain as the main domain using 'yunohost domain main-domain -n ', here is the list of candidate domains: {other_domains:s}", "domain_cannot_remove_main_add_new_one": "You cannot remove '{domain:s}' since it's the main domain and your only domain, you need to first add another domain using 'yunohost domain add ', then set is as the main domain using 'yunohost domain main-domain -n ' and then you can remove the domain '{domain:s}' using 'yunohost domain remove {domain:s}'.'", "domain_cert_gen_failed": "Could not generate certificate", @@ -239,6 +233,8 @@ "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_lock_not_available": "This command can't be ran 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}.", "dyndns_cron_installed": "DynDNS cron job created", @@ -604,7 +600,6 @@ "user_update_failed": "Could not update user {user}: {error}", "user_updated": "User info changed", "users_available": "Available users:", - "unknown_categories": "The following categories are unknown : {categories}", "yunohost_already_installed": "YunoHost is already installed", "yunohost_ca_creation_failed": "Could not create certificate authority", "yunohost_ca_creation_success": "Local certification authority created.", diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index 19dd03042..121a0c2ae 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -56,7 +56,7 @@ def diagnosis_show(categories=[], issues=False, full=False, share=False): else: unknown_categories = [c for c in categories if c not in all_categories_names] if unknown_categories: - raise YunohostError('unknown_categories', categories=", ".join(categories)) + raise YunohostError('diagnosis_unknown_categories', categories=", ".join(categories)) # Fetch all reports all_reports = [] @@ -127,7 +127,7 @@ def diagnosis_run(categories=[], force=False): else: unknown_categories = [c for c in categories if c not in all_categories_names] if unknown_categories: - raise YunohostError('unknown_categories', categories=", ".join(unknown_categories)) + raise YunohostError('diagnosis_unknown_categories', categories=", ".join(unknown_categories)) issues = [] # Call the hook ... From da7ac5aacb3a70630fd46c76b1303254fe4bbe30 Mon Sep 17 00:00:00 2001 From: Dominik Roesli Date: Wed, 30 Oct 2019 08:27:33 +0000 Subject: [PATCH 0397/3170] Translated using Weblate (German) Currently translated at 34.3% (193 of 562 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/locales/de.json b/locales/de.json index ec7233973..08fdff7e4 100644 --- a/locales/de.json +++ b/locales/de.json @@ -293,7 +293,7 @@ "backup_abstract_method": "Diese Backup-Methode wird noch nicht unterstützt", "backup_applying_method_tar": "Erstellen des Backup-tar Archives…", "backup_applying_method_copy": "Kopiere alle Dateien ins Backup…", - "app_change_url_no_script": "Die Anwendung '{app_name:s}' unterstützt bisher keine URL-Modufikation. Vielleicht gibt es eine Aktualisierung.", + "app_change_url_no_script": "Die Anwendung '{app_name:s}' unterstützt bisher keine URL-Modifikation. Vielleicht sollte sie aktualisiert werden.", "app_location_unavailable": "Diese URL ist nicht verfügbar oder wird von einer installierten Anwendung genutzt:\n{apps:s}", "backup_applying_method_custom": "Rufe die benutzerdefinierte Backup-Methode '{method:s}' auf…", "backup_archive_system_part_not_available": "Der System-Teil '{part:s}' ist in diesem Backup nicht enthalten", @@ -350,7 +350,7 @@ "app_start_remove": "Anwendung {app} wird entfernt…", "app_start_install": "Anwendung {app} wird installiert…", "app_not_upgraded": "Die App '{failed_app}' konnte nicht aktualisiert werden. Infolgedessen wurden die folgenden App-Upgrades abgebrochen: {apps}", - "app_make_default_location_already_used": "Die App \"{app}\" kann nicht als Standard für die Domain \"{domain}\" festgelegt werden. Sie wird bereits von der anderen App \"{other_app}\" verwendet", + "app_make_default_location_already_used": "Die App \"{app}\" kann nicht als Standard für die Domain \"{domain}\" festgelegt werden. Sie wird bereits von der App \"{other_app}\" verwendet", "aborting": "Breche ab.", "app_action_cannot_be_ran_because_required_services_down": "Diese App erfordert einige Dienste, die derzeit nicht verfügbar sind. Bevor Sie fortfahren, sollten Sie versuchen, die folgenden Dienste neu zu starten (und möglicherweise untersuchen, warum sie nicht verfügbar sind): {services}", "already_up_to_date": "Nichts zu tun. Alles ist bereits auf dem neusten Stand.", @@ -414,5 +414,8 @@ "global_settings_key_doesnt_exists": "Der Schlüssel'{settings_key:s}' existiert nicht in den globalen Einstellungen, du kannst alle verfügbaren Schlüssel sehen, indem du 'yunohost settings list' ausführst", "log_app_makedefault": "Mache '{}' zur Standard-Anwendung", "hook_json_return_error": "Konnte die Rückkehr vom Einsprungpunkt {path:s} nicht lesen. Fehler: {msg:s}. Unformatierter Inhalt: {raw_content}", - "app_full_domain_unavailable": "Es tut uns leid, aber diese Anwendung erfordert die Installation einer vollständigen Domäne, aber einige andere Anwendungen sind bereits auf der Domäne'{domain}' installiert. Eine mögliche Lösung ist das Hinzufügen und Verwenden einer Subdomain, die dieser Anwendung zugeordnet ist." + "app_full_domain_unavailable": "Es tut uns leid, aber diese Anwendung erfordert die Installation auf einer eigenen Domain, aber einige andere Anwendungen sind bereits auf der Domäne'{domain}' installiert. Eine mögliche Lösung ist das Hinzufügen und Verwenden einer Subdomain, die dieser Anwendung zugeordnet ist.", + "app_install_failed": "Installation von {app} fehlgeschlagen: {error}", + "app_install_script_failed": "Im Installationsscript ist ein Fehler aufgetreten", + "app_remove_after_failed_install": "Entfernen der App nach fehlgeschlagener Installation…" } From d4dde0e5d76d350d7969dea55e38eeb85327665e Mon Sep 17 00:00:00 2001 From: xaloc33 Date: Sun, 3 Nov 2019 22:26:53 +0000 Subject: [PATCH 0398/3170] Translated using Weblate (Catalan) Currently translated at 100.0% (562 of 562 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ca/ --- locales/ca.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/ca.json b/locales/ca.json index e171726b0..c0e500a3c 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -626,5 +626,6 @@ "permission_already_up_to_date": "No s'ha actualitzat el permís perquè la petició d'afegir/eliminar ja corresponent a l'estat actual.", "permission_currently_allowed_for_visitors": "El permís ja el tenen el grup de visitants a més d'altres grups. Segurament s'hauria de revocar el permís al grup dels visitants o eliminar els altres grups als que s'ha atribuït.", "permission_currently_allowed_for_all_users": "El permís ha el té el grup de tots els usuaris (all_users) a més d'altres grups. Segurament s'hauria de revocar el permís a «all_users» o eliminar els altres grups als que s'ha atribuït.", - "permission_require_account": "El permís {permission} només té sentit per als usuaris que tenen un compte, i per tant no es pot activar per als visitants." + "permission_require_account": "El permís {permission} només té sentit per als usuaris que tenen un compte, i per tant no es pot activar per als visitants.", + "app_remove_after_failed_install": "Eliminant l'aplicació després que hagi fallat la instal·lació…" } From e8c98335f48f83bf5ca8c6a078f190af644fce55 Mon Sep 17 00:00:00 2001 From: Filip Bengtsson Date: Thu, 31 Oct 2019 04:34:15 +0000 Subject: [PATCH 0399/3170] Translated using Weblate (Swedish) Currently translated at 1.6% (9 of 562 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/sv/ --- locales/sv.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/locales/sv.json b/locales/sv.json index 4960d43aa..85572756d 100644 --- a/locales/sv.json +++ b/locales/sv.json @@ -1,3 +1,11 @@ { - "password_too_simple_1": "Lösenordet måste bestå av minst åtta tecken" + "password_too_simple_1": "Lösenordet måste bestå av minst åtta tecken", + "app_action_broke_system": "Åtgärden verkar ha fått följande viktiga tjänster att haverera: {services}", + "already_up_to_date": "Ingenting att göra. Allt är redan uppdaterat.", + "admin_password": "Administratörslösenord", + "admin_password_too_long": "Välj gärna ett lösenord som inte innehåller fler än 127 tecken", + "admin_password_change_failed": "Kan inte byta lösenord", + "action_invalid": "Ej tillåten åtgärd '{action:s}'", + "admin_password_changed": "Administratörskontots lösenord ändrades", + "aborting": "Avbryter." } From ab0deddbb86923d65fa08f8e48e0e9a970cb4b3a Mon Sep 17 00:00:00 2001 From: advocatux Date: Wed, 30 Oct 2019 15:30:49 +0000 Subject: [PATCH 0400/3170] Translated using Weblate (Spanish) Currently translated at 100.0% (562 of 562 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/es/ --- locales/es.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/es.json b/locales/es.json index fe3e99dbf..3cdeebf56 100644 --- a/locales/es.json +++ b/locales/es.json @@ -637,5 +637,6 @@ "permission_already_up_to_date": "El permiso no se ha actualizado porque las peticiones de incorporación o eliminación ya coinciden con el estado actual.", "permission_currently_allowed_for_visitors": "Este permiso se concede actualmente a los visitantes además de otros grupos. Probablemente quiere o eliminar el permiso de «visitors» o eliminar los otros grupos a los que está otorgado actualmente.", "permission_currently_allowed_for_all_users": "Este permiso se concede actualmente a todos los usuarios además de los otros grupos. Probablemente quiere o eliminar el permiso de «all_users» o eliminar los otros grupos a los que está otorgado actualmente.", - "permission_require_account": "El permiso {permission} solo tiene sentido para usuarios con una cuenta y, por lo tanto, no se puede activar para visitantes." + "permission_require_account": "El permiso {permission} solo tiene sentido para usuarios con una cuenta y, por lo tanto, no se puede activar para visitantes.", + "app_remove_after_failed_install": "Eliminando la aplicación tras el fallo de instalación…" } From da43f025fd56598997afdc4e0ccc15ac6a0592a5 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Tue, 5 Nov 2019 08:45:33 +0000 Subject: [PATCH 0401/3170] Translated using Weblate (Turkish) Currently translated at 0.2% (1 of 562 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/tr/ --- locales/tr.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/locales/tr.json b/locales/tr.json index 0967ef424..c6eb58ed1 100644 --- a/locales/tr.json +++ b/locales/tr.json @@ -1 +1,3 @@ -{} +{ + "password_too_simple_1": "Şifre en az 8 karakter uzunluğunda olmalı" +} From 42430e427756be42f8e6d7212bb2d6f35001f0ec Mon Sep 17 00:00:00 2001 From: amirale qt Date: Tue, 5 Nov 2019 07:32:56 +0000 Subject: [PATCH 0402/3170] Translated using Weblate (Basque) Currently translated at 0.2% (1 of 562 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/eu/ --- locales/eu.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/locales/eu.json b/locales/eu.json index 0967ef424..539fb9157 100644 --- a/locales/eu.json +++ b/locales/eu.json @@ -1 +1,3 @@ -{} +{ + "password_too_simple_1": "Pasahitzak gutxienez 8 karaktere izan behar ditu" +} From b56044ea18d84c08e77e9775f85cd7ebcff1521c Mon Sep 17 00:00:00 2001 From: amirale qt Date: Tue, 5 Nov 2019 13:33:02 +0000 Subject: [PATCH 0403/3170] Translated using Weblate (French) Currently translated at 100.0% (562 of 562 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/fr.json b/locales/fr.json index 563c09a1f..866b24281 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -664,5 +664,6 @@ "permission_currently_allowed_for_all_users": "Cette autorisation est actuellement accordée à tous les utilisateurs en plus des autres groupes. Vous voudrez probablement soit supprimer l'autorisation 'all_users', soit supprimer les autres groupes auxquels il est actuellement autorisé.", "app_install_failed": "Impossible d'installer {app}: {error}", "app_install_script_failed": "Une erreur est survenue dans le script d'installation de l'application", - "permission_require_account": "Permission {permission} n'a de sens que pour les utilisateurs ayant un compte et ne peut donc pas être activé pour les visiteurs." + "permission_require_account": "Permission {permission} n'a de sens que pour les utilisateurs ayant un compte et ne peut donc pas être activé pour les visiteurs.", + "app_remove_after_failed_install": "Supprimer l'application après l'échec de l'installation…" } From 01eaea9fbaabed842556d93f001d96e58afaefbd Mon Sep 17 00:00:00 2001 From: amirale qt Date: Tue, 5 Nov 2019 13:33:34 +0000 Subject: [PATCH 0404/3170] Translated using Weblate (Esperanto) Currently translated at 95.6% (537 of 562 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/eo/ --- locales/eo.json | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/locales/eo.json b/locales/eo.json index 720485ba6..a25fa505e 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -141,7 +141,7 @@ "field_invalid": "Nevalida kampo '{:s}'", "log_app_makedefault": "Faru '{}' la defaŭlta apliko", "migration_0003_still_on_jessie_after_main_upgrade": "Io okazis malbone dum la ĉefa ĝisdatigo: Ĉu la sistemo ankoraŭ estas en Jessie‽ Por esplori la aferon, bonvolu rigardi {log}:s …", - "migration_0011_can_not_backup_before_migration": "La sekurkopio de la sistemo antaŭ la migrado malsukcesis. Migrado malsukcesis. Eraro: {error:s}", + "migration_0011_can_not_backup_before_migration": "La sekurkopio de la sistemo ne povis finiĝi antaŭ ol la migrado malsukcesis. Eraro: {error:s}", "migration_0011_create_group": "Krei grupon por ĉiu uzanto…", "backup_system_part_failed": "Ne eblis sekurkopi la sistemon de '{part:s}'", "global_settings_setting_security_postfix_compatibility": "Kongruo vs sekureca kompromiso por la Postfix-servilo. Afektas la ĉifradojn (kaj aliajn aspektojn pri sekureco)", @@ -151,8 +151,8 @@ "migration_0011_backup_before_migration": "Krei sekurkopion de LDAP-datumbazo kaj agordojn antaŭ la efektiva migrado.", "migration_0011_LDAP_config_dirty": "Similas ke vi agordis vian LDAP-agordon. Por ĉi tiu migrado la LDAP-agordo bezonas esti ĝisdatigita.\nVi devas konservi vian aktualan agordon, reintaligi la originalan agordon per funkciado de \"yunohost iloj regen-conf -f\" kaj reprovi la migradon", "migration_0011_migrate_permission": "Migrado de permesoj de agordoj al aplikoj al LDAP…", - "migration_0011_migration_failed_trying_to_rollback": "Migrado malsukcesis ... provante reverti la sistemon.", - "migrations_dependencies_not_satisfied": "Ne eblas kuri migradon {id} ĉar unue vi devas ruli ĉi tiujn migradojn: {dependencies_id}", + "migration_0011_migration_failed_trying_to_rollback": "Ne povis migri ... provante redakti la sistemon.", + "migrations_dependencies_not_satisfied": "Rulu ĉi tiujn migradojn: '{dependencies_id}', antaŭ migrado {id}.", "migrations_failed_to_load_migration": "Ne povis ŝarĝi migradon {id}: {error}", "migrations_exclusive_options": "'--auto', '--skip' kaj '--force-rerun' estas reciproke ekskluzivaj ebloj.", "migrations_must_provide_explicit_targets": "Vi devas provizi eksplicitajn celojn kiam vi uzas '--skip' aŭ '--force-rerun'", @@ -162,7 +162,7 @@ "tools_upgrade_cant_hold_critical_packages": "Ne povis teni kritikajn pakojn…", "upnp_dev_not_found": "Neniu UPnP-aparato trovita", "migration_description_0012_postgresql_password_to_md5_authentication": "Devigu PostgreSQL-aŭtentigon uzi MD5 por lokaj ligoj", - "migration_0011_done": "Migrado sukcesis. Vi nun kapablas administri uzantajn grupojn.", + "migration_0011_done": "Migrado finiĝis. Vi nun kapablas administri uzantajn grupojn.", "migration_0011_LDAP_update_failed": "Ne povis ĝisdatigi LDAP. Eraro: {error:s}", "pattern_password": "Devas esti almenaŭ 3 signoj longaj", "root_password_desynchronized": "La pasvorta administranto estis ŝanĝita, sed YunoHost ne povis propagandi ĉi tion al la radika pasvorto!", @@ -194,9 +194,9 @@ "migration_0011_rollback_success": "Sistemo ruliĝis reen.", "migration_0011_update_LDAP_database": "Ĝisdatigante LDAP-datumbazon…", "migration_0011_update_LDAP_schema": "Ĝisdatigante LDAP-skemon…", - "migration_0011_failed_to_remove_stale_object": "Malsukcesis forigi neokazan objekton {dn}: {error}", + "migration_0011_failed_to_remove_stale_object": "Ne povis forigi neuzatan objekton {dn}: {error}", "migrations_already_ran": "Tiuj migradoj estas jam faritaj: {ids}", - "migrations_no_such_migration": "Estas neniu migrado nomata {id}", + "migrations_no_such_migration": "Estas neniu migrado nomata '{id}'", "permission_already_allowed": "Grupo '{group}' jam havas permeson '{permission}' ebligita'", "permission_already_disallowed": "Grupo '{group}' jam havas permeson '{permission}' malebligita'", "permission_cannot_remove_main": "Forigo de ĉefa permeso ne rajtas", @@ -266,7 +266,7 @@ "migration_description_0008_ssh_conf_managed_by_yunohost_step2": "Lasu la SSH-agordon estu administrata de YunoHost (paŝo 2, manlibro)", "restore_confirm_yunohost_installed": "Ĉu vi vere volas restarigi jam instalitan sistemon? [{answers:s}]", "pattern_positive_number": "Devas esti pozitiva nombro", - "monitor_stats_file_not_found": "Statistika dosiero ne trovita", + "monitor_stats_file_not_found": "Ne povis trovi la statistikan dosieron", "certmanager_error_no_A_record": "Neniu DNS 'A' rekordo trovita por '{domain:s}'. Vi bezonas atentigi vian domajnan nomon al via maŝino por povi instali atestilon Lasu-Ĉifri. (Se vi scias, kion vi faras, uzu '--no-checks' por malŝalti tiujn ĉekojn.)", "update_apt_cache_failed": "Ne eblis ĝisdatigi la kaŝmemoron de APT (paka administranto de Debian). Jen rubujo de la sources.list-linioj, kiuj povus helpi identigi problemajn liniojn:\n{sourcelist}", "migrations_no_migrations_to_run": "Neniuj migradoj por funkcii", @@ -339,7 +339,7 @@ "log_app_upgrade": "Ĝisdatigu la aplikon '{}'", "log_help_to_get_failed_log": "La operacio '{desc}' ne povis finiĝi. Bonvolu dividi la plenan ŝtipon de ĉi tiu operacio per la komando 'yunohost log display {name} --share' por akiri helpon", "migration_description_0002_migrate_to_tsig_sha256": "Plibonigu sekurecon de DynDNS TSIG-ĝisdatigoj per SHA-512 anstataŭ MD5", - "monitor_disabled": "Servila monitorado nun malŝaltis", + "monitor_disabled": "Servilo-monitorado nun malŝaltita", "pattern_port": "Devas esti valida havena numero (t.e. 0-65535)", "port_already_closed": "Haveno {port:d} estas jam fermita por {ip_version:s} rilatoj", "hook_name_unknown": "Nekonata hoko-nomo '{name:s}'", @@ -407,7 +407,7 @@ "migration_0003_not_jessie": "La nuna Debian-distribuo ne estas Jessie!", "user_unknown": "Nekonata uzanto: {user:s}", "migrations_to_be_ran_manually": "Migrado {id} devas funkcii permane. Bonvolu iri al Iloj → Migradoj en la retpaĝa paĝo, aŭ kuri `yunohost tools migrations migrate`.", - "migration_0008_warning": "Se vi komprenas tiujn avertojn kaj konsentas lasi YunoHost pretervidi vian nunan agordon, faru la migradon. Alie, vi ankaŭ povas salti la migradon - kvankam ĝi ne rekomendas.", + "migration_0008_warning": "Se vi komprenas tiujn avertojn kaj volas ke YunoHost preterlasu vian nunan agordon, faru la migradon. Alie, vi ankaŭ povas salti la migradon, kvankam ĝi ne rekomendas.", "certmanager_cert_renew_success": "Ni Ĉifru atestilon renovigitan por la domajno '{domain:s}'", "global_settings_reset_success": "Antaŭaj agordoj nun estas rezervitaj al {path:s}", "pattern_domain": "Devas esti valida domajna nomo (t.e. mia-domino.org)", @@ -477,14 +477,14 @@ "log_tools_maindomain": "Faru de '{}' la ĉefa domajno", "maindomain_change_failed": "Ne povis ŝanĝi la ĉefan domajnon", "mail_domain_unknown": "Nevalida retadreso por domajno '{domain:s}'. Bonvolu uzi domajnon administritan de ĉi tiu servilo.", - "migrations_cant_reach_migration_file": "Ne povis aliri migrajn dosierojn ĉe la vojo% s", + "migrations_cant_reach_migration_file": "Ne povis aliri migrajn dosierojn ĉe la vojo '% s'", "pattern_email": "Devas esti valida retpoŝtadreso (t.e.iu@domain.org)", "mail_alias_remove_failed": "Ne povis forigi retpoŝton alias '{mail:s}'", "regenconf_file_manually_removed": "La dosiero de agordo '{conf}' estis forigita permane, kaj ne estos kreita", - "monitor_enabled": "Servila monitorado nun ŝaltis", + "monitor_enabled": "Servilo-monitorado nun", "domain_exists": "La domajno jam ekzistas", "migration_description_0001_change_cert_group_to_sslcert": "Ŝanĝu grupajn permesojn de 'metronomo' al 'ssl-cert'", - "mysql_db_creation_failed": "MySQL-datumbazkreado malsukcesis", + "mysql_db_creation_failed": "Ne povis krei MySQL-datumbazon", "ldap_initialized": "LDAP inicializis", "migrate_tsig_not_needed": "Vi ne ŝajnas uzi DynDNS-domajnon, do neniu migrado necesas.", "certmanager_domain_cert_not_selfsigned": "La atestilo por domajno {domajno:s} ne estas mem-subskribita. Ĉu vi certas, ke vi volas anstataŭigi ĝin? (Uzu '--force' por fari tion.)", @@ -495,7 +495,7 @@ "global_settings_bad_choice_for_enum": "Malbona elekto por agordo {setting:s}, ricevita '{choice:s}', sed disponeblaj elektoj estas: {available_choices:s}", "server_shutdown": "La servilo haltos", "log_tools_migrations_migrate_forward": "Migri antaŭen", - "migration_0008_no_warning": "Neniu grava risko identigita pri superregado de via SSH-agordo, tamen oni ne povas esti absolute certa;)! Ekfunkciu la migradon por superregi ĝin. Alie, vi ankaŭ povas salti la migradon - kvankam ĝi ne rekomendas.", + "migration_0008_no_warning": "Supersalti vian SSH-agordon estu sekura, kvankam ĉi tio ne povas esti promesita! Ekfunkciu la migradon por superregi ĝin. Alie, vi ankaŭ povas salti la migradon, kvankam ĝi ne rekomendas.", "regenconf_now_managed_by_yunohost": "La agorda dosiero '{conf}' nun estas administrata de YunoHost (kategorio {category}).", "server_reboot_confirm": "Ĉu la servilo rekomencos tuj, ĉu vi certas? [{answers:s}]", "log_app_install": "Instalu la aplikon '{}'", @@ -563,5 +563,6 @@ "permission_currently_allowed_for_visitors": "Ĉi tiu permeso estas nuntempe donita al vizitantoj aldone al aliaj grupoj. Vi probable volas aŭ forigi la permeson de \"vizitantoj\" aŭ forigi la aliajn grupojn al kiuj ĝi nun estas koncedita.", "permission_currently_allowed_for_all_users": "Ĉi tiu permeso estas nuntempe donita al ĉiuj uzantoj aldone al aliaj grupoj. Vi probable volas aŭ forigi la permeson \"all_users\" aŭ forigi la aliajn grupojn, kiujn ĝi nuntempe donas.", "app_install_failed": "Ne povis instali {app} : {error}", - "app_install_script_failed": "Eraro okazis en la skripto de instalado de la app" + "app_install_script_failed": "Eraro okazis en la skripto de instalado de la app", + "app_remove_after_failed_install": "Forigado de la app post la instala fiasko …" } From 08c23599e2e1a6989d29fe6ab6c10d1b993b5bd1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 6 Nov 2019 23:48:58 +0100 Subject: [PATCH 0405/3170] Improve yunohost package version diagnosis --- data/hooks/diagnosis/00-basesystem.py | 8 ++++++-- locales/en.json | 4 ++-- src/yunohost/utils/packages.py | 3 +++ 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/data/hooks/diagnosis/00-basesystem.py b/data/hooks/diagnosis/00-basesystem.py index 8bd522ee7..4add48fb2 100644 --- a/data/hooks/diagnosis/00-basesystem.py +++ b/data/hooks/diagnosis/00-basesystem.py @@ -37,16 +37,20 @@ class BaseSystemDiagnoser(Diagnoser): # Here, ynh_core_version is for example "3.5.4.12", so [:3] is "3.5" and we check it's the same for all packages ynh_core_version = ynh_packages["yunohost"]["version"] consistent_versions = all(infos["version"][:3] == ynh_core_version[:3] for infos in ynh_packages.values()) - ynh_version_details = [("diagnosis_basesystem_ynh_single_version", (package, infos["version"])) + ynh_version_details = [("diagnosis_basesystem_ynh_single_version", (package, infos["version"], infos["repo"])) for package, infos in ynh_packages.items()] if consistent_versions: yield dict(meta={"test": "ynh_versions"}, + data={"main_version": ynh_core_version, "repo": ynh_packages["yunohost"]["repo"]}, status="INFO", - summary=("diagnosis_basesystem_ynh_main_version", {"main_version": ynh_core_version[:3]}), + summary=("diagnosis_basesystem_ynh_main_version", + {"main_version": ynh_core_version, + "repo": ynh_packages["yunohost"]["repo"]}), details=ynh_version_details) else: yield dict(meta={"test": "ynh_versions"}, + data={"main_version": ynh_core_version, "repo": ynh_packages["yunohost"]["repo"]}, status="ERROR", summary=("diagnosis_basesystem_ynh_inconsistent_versions", {}), details=ynh_version_details) diff --git a/locales/en.json b/locales/en.json index ebe6b4571..751180a37 100644 --- a/locales/en.json +++ b/locales/en.json @@ -152,8 +152,8 @@ "custom_appslist_name_required": "You must provide a name for your custom app list", "diagnosis_basesystem_host": "Server is running Debian {debian_version}.", "diagnosis_basesystem_kernel": "Server is running Linux kernel {kernel_version}", - "diagnosis_basesystem_ynh_single_version": "{0} version: {1}", - "diagnosis_basesystem_ynh_main_version": "Server is running YunoHost {main_version}", + "diagnosis_basesystem_ynh_single_version": "{0} version: {1} ({2})", + "diagnosis_basesystem_ynh_main_version": "Server is running YunoHost {main_version} ({repo})", "diagnosis_basesystem_ynh_inconsistent_versions": "You are running inconsistents versions of the YunoHost packages ... most probably because of a failed or partial upgrade.", "diagnosis_display_tip_web": "You can go to the Diagnosis section (in the home screen) to see the issues found.", "diagnosis_display_tip_cli": "You can run 'yunohost diagnosis show --issues' to display the issues found.", diff --git a/src/yunohost/utils/packages.py b/src/yunohost/utils/packages.py index 6df736432..debba70f4 100644 --- a/src/yunohost/utils/packages.py +++ b/src/yunohost/utils/packages.py @@ -406,6 +406,9 @@ def get_installed_version(*pkgnames, **kwargs): except AttributeError: repo = "" + if repo == "now": + repo = "local" + if with_repo: versions[pkgname] = { "version": version, From d879d27208a985886d0943d549d1c33a98df2d03 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 5 Nov 2019 21:47:33 +0100 Subject: [PATCH 0406/3170] Add test-status to have a custom status check for service like postfix and yunohost-firewall --- data/templates/yunohost/services.yml | 2 ++ src/yunohost/service.py | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/data/templates/yunohost/services.yml b/data/templates/yunohost/services.yml index 1c0ee031f..a9948fc69 100644 --- a/data/templates/yunohost/services.yml +++ b/data/templates/yunohost/services.yml @@ -10,6 +10,7 @@ dovecot: log: [/var/log/mail.log,/var/log/mail.err] postfix: log: [/var/log/mail.log,/var/log/mail.err] + test-status: systemctl show postfix@- | grep -q "^SubState=running" rspamd: log: /var/log/rspamd/rspamd.log redis-server: @@ -29,6 +30,7 @@ yunohost-api: log: /var/log/yunohost/yunohost-api.log yunohost-firewall: need_lock: true + test-status: iptables -S | grep "^-A INPUT" | grep " --dport" | grep -q ACCEPT nslcd: log: /var/log/syslog glances: null diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 17a3cc83e..96a7061e3 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -330,6 +330,11 @@ def service_status(names=[]): else: result[name]['active_at'] = "unknown" + # 'test-status' is an optional field to test the status of the service using a custom command + if "test-status" in services[name]: + status = os.system(services[name]["test-status"]) + result[name]["status"] = "running" if status == 0 else "failed" + if len(names) == 1: return result[names[0]] return result From e15d8e72624b225840713abf5e801be7e2c07ccf Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 5 Nov 2019 21:52:35 +0100 Subject: [PATCH 0407/3170] Add test about configuration validity --- data/templates/yunohost/services.yml | 3 +++ src/yunohost/service.py | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/data/templates/yunohost/services.yml b/data/templates/yunohost/services.yml index a9948fc69..fd9d06eac 100644 --- a/data/templates/yunohost/services.yml +++ b/data/templates/yunohost/services.yml @@ -1,5 +1,6 @@ nginx: log: /var/log/nginx + test-conf: nginx -t avahi-daemon: log: /var/log/daemon.log dnsmasq: @@ -20,12 +21,14 @@ mysql: alternates: ['mariadb'] ssh: log: /var/log/auth.log + test-conf: sshd -t metronome: log: [/var/log/metronome/metronome.log,/var/log/metronome/metronome.err] slapd: log: /var/log/syslog php7.0-fpm: log: /var/log/php7.0-fpm.log + test-conf: php-fpm7.0 --test yunohost-api: log: /var/log/yunohost/yunohost-api.log yunohost-firewall: diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 96a7061e3..8f1e55669 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -297,6 +297,7 @@ def service_status(names=[]): 'active_at': "unknown", 'description': "Error: failed to get information for this service, it doesn't exists for systemd", 'service_file_path': "unknown", + 'configuration': "unknown", } else: @@ -318,6 +319,7 @@ def service_status(names=[]): 'active': str(status.get("ActiveState", "unknown")), 'description': description, 'service_file_path': str(status.get("FragmentPath", "unknown")), + 'configuration': "unknown", } # Fun stuff™ : to obtain the enabled/disabled status for sysv services, @@ -335,6 +337,11 @@ def service_status(names=[]): status = os.system(services[name]["test-status"]) result[name]["status"] = "running" if status == 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]: + conf = os.system(services[name]["test-conf"] + " &>/dev/null") + result[name]["configuration"] = "valid" if conf == 0 else "broken" + if len(names) == 1: return result[names[0]] return result From 9b77123938f6590d01deedb7d549bf85b3b3b2f0 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 5 Nov 2019 21:54:05 +0100 Subject: [PATCH 0408/3170] loaded -> start_on_boot, which is more meaningful ... --- src/yunohost/service.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 8f1e55669..abaf58f09 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -292,7 +292,7 @@ def service_status(names=[]): logger.error("Failed to get status information via dbus for service %s, systemctl didn't recognize this service ('NoSuchUnit')." % name) result[name] = { 'status': "unknown", - 'loaded': "unknown", + 'start_on_boot': "unknown", 'active': "unknown", 'active_at': "unknown", 'description': "Error: failed to get information for this service, it doesn't exists for systemd", @@ -315,7 +315,7 @@ def service_status(names=[]): result[name] = { 'status': str(status.get("SubState", "unknown")), - 'loaded': str(status.get("UnitFileState", "unknown")), + 'start_on_boot': str(status.get("UnitFileState", "unknown")), 'active': str(status.get("ActiveState", "unknown")), 'description': description, 'service_file_path': str(status.get("FragmentPath", "unknown")), @@ -324,8 +324,8 @@ 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]["loaded"] == "generated": - result[name]["loaded"] = "enabled" if glob("/etc/rc[S5].d/S??"+name) else "disabled" + if result[name]["start_on_boot"] == "generated": + result[name]["start_on_boot"] = "enabled" if glob("/etc/rc[S5].d/S??"+name) else "disabled" if "ActiveEnterTimestamp" in status: result[name]['active_at'] = datetime.utcfromtimestamp(status["ActiveEnterTimestamp"] / 1000000) From 5d46f3ef8883e451fe36af03907e5df5cad76998 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 5 Nov 2019 21:57:22 +0100 Subject: [PATCH 0409/3170] Imho we don't need those 'active' and 'service_file_path' info... --- src/yunohost/service.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index abaf58f09..253e87d78 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -293,10 +293,8 @@ def service_status(names=[]): result[name] = { 'status': "unknown", 'start_on_boot': "unknown", - 'active': "unknown", 'active_at': "unknown", 'description': "Error: failed to get information for this service, it doesn't exists for systemd", - 'service_file_path': "unknown", 'configuration': "unknown", } @@ -316,9 +314,8 @@ def service_status(names=[]): result[name] = { 'status': str(status.get("SubState", "unknown")), 'start_on_boot': str(status.get("UnitFileState", "unknown")), - 'active': str(status.get("ActiveState", "unknown")), + 'active_at': "unknown", 'description': description, - 'service_file_path': str(status.get("FragmentPath", "unknown")), 'configuration': "unknown", } @@ -329,8 +326,6 @@ def service_status(names=[]): if "ActiveEnterTimestamp" in status: result[name]['active_at'] = datetime.utcfromtimestamp(status["ActiveEnterTimestamp"] / 1000000) - else: - result[name]['active_at'] = "unknown" # 'test-status' is an optional field to test the status of the service using a custom command if "test-status" in services[name]: From acba0c4a1089f228d1c839c0ca2829a7409fe570 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 7 Nov 2019 12:25:19 +0100 Subject: [PATCH 0410/3170] Use a proper subprocess for conf test instead of dirty os.system + store errors if there are --- src/yunohost/service.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 253e87d78..e3a584c41 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -334,8 +334,17 @@ def service_status(names=[]): # 'test-status' is an optional field to test the status of the service using a custom command if "test-conf" in services[name]: - conf = os.system(services[name]["test-conf"] + " &>/dev/null") - result[name]["configuration"] = "valid" if conf == 0 else "broken" + p = subprocess.Popen(services[name]["test-conf"], + shell=True, + 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") if len(names) == 1: return result[names[0]] From 4b3a4c7e4e997998e9e83ec8c8b29cc5649c2620 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 7 Nov 2019 13:48:42 +0100 Subject: [PATCH 0411/3170] Fetch the timestamp of the latest state change, not just the last time it got up and running... --- src/yunohost/service.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index e3a584c41..850899ecc 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -293,7 +293,7 @@ def service_status(names=[]): result[name] = { 'status': "unknown", 'start_on_boot': "unknown", - 'active_at': "unknown", + 'last_state_change': "unknown", 'description': "Error: failed to get information for this service, it doesn't exists for systemd", 'configuration': "unknown", } @@ -314,7 +314,7 @@ def service_status(names=[]): result[name] = { 'status': str(status.get("SubState", "unknown")), 'start_on_boot': str(status.get("UnitFileState", "unknown")), - 'active_at': "unknown", + 'last_state_change': "unknown", 'description': description, 'configuration': "unknown", } @@ -324,8 +324,8 @@ def service_status(names=[]): if result[name]["start_on_boot"] == "generated": result[name]["start_on_boot"] = "enabled" if glob("/etc/rc[S5].d/S??"+name) else "disabled" - if "ActiveEnterTimestamp" in status: - result[name]['active_at'] = datetime.utcfromtimestamp(status["ActiveEnterTimestamp"] / 1000000) + if "StateChangeTimestamp" in status: + 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]: From 51f42e5ad4ed731438959272f5a3810dd5791162 Mon Sep 17 00:00:00 2001 From: Kayou Date: Thu, 7 Nov 2019 22:32:24 +0900 Subject: [PATCH 0412/3170] Add arg in permission callback --- src/yunohost/permission.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index c53804d49..7866e7277 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -179,6 +179,7 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, # Trigger app callbacks app = permission.split(".")[0] + sub_permission = permission.split(".")[1] old_allowed_users = set(existing_permission["corresponding_users"]) new_allowed_users = set(new_permission["corresponding_users"]) @@ -187,9 +188,9 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, effectively_removed_users = old_allowed_users - new_allowed_users if effectively_added_users: - hook_callback('post_app_addaccess', args=[app, ','.join(effectively_added_users)]) + hook_callback('post_app_addaccess', args=[app, ','.join(effectively_added_users), sub_permission]) if effectively_removed_users: - hook_callback('post_app_removeaccess', args=[app, ','.join(effectively_removed_users)]) + hook_callback('post_app_removeaccess', args=[app, ','.join(effectively_removed_users), sub_permission]) return new_permission @@ -241,6 +242,7 @@ def user_permission_reset(operation_logger, permission, sync_perm=True): # Trigger app callbacks app = permission.split(".")[0] + sub_permission = permission.split(".")[1] old_allowed_users = set(existing_permission["corresponding_users"]) new_allowed_users = set(new_permission["corresponding_users"]) @@ -249,9 +251,9 @@ def user_permission_reset(operation_logger, permission, sync_perm=True): effectively_removed_users = old_allowed_users - new_allowed_users if effectively_added_users: - hook_callback('post_app_addaccess', args=[app, ','.join(effectively_added_users)]) + hook_callback('post_app_addaccess', args=[app, ','.join(effectively_added_users), sub_permission]) if effectively_removed_users: - hook_callback('post_app_removeaccess', args=[app, ','.join(effectively_removed_users)]) + hook_callback('post_app_removeaccess', args=[app, ','.join(effectively_removed_users), sub_permission]) return new_permission From 55e198d8b8cd62b9d9ede5a431a913071ab62cd2 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Thu, 7 Nov 2019 23:32:08 +0900 Subject: [PATCH 0413/3170] Refactor group permission --- src/yunohost/permission.py | 156 ++++++++++++++++++------------------- 1 file changed, 78 insertions(+), 78 deletions(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index c53804d49..a35a0c4e5 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -92,7 +92,6 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, remove -- List of groups or usernames to remove from to this permission """ from yunohost.hook import hook_callback - from yunohost.user import user_group_list from yunohost.utils.ldap import _get_ldap_interface ldap = _get_ldap_interface() @@ -111,7 +110,6 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, raise YunohostError('permission_not_found', permission=permission) current_allowed_groups = existing_permission["allowed"] - all_existing_groups = user_group_list()['groups'].keys() operation_logger.related_to.append(('app', permission.split(".")[0])) # Compute new allowed group list (and make sure what we're doing make sense) @@ -121,8 +119,6 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, if add: groups_to_add = [add] if not isinstance(add, list) else add for group in groups_to_add: - if group not in all_existing_groups: - raise YunohostError('group_unknown', group=group) if group in current_allowed_groups: logger.warning(m18n.n('permission_already_allowed', permission=permission, group=group)) else: @@ -133,8 +129,6 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, if remove: groups_to_remove = [remove] if not isinstance(remove, list) else remove for group in groups_to_remove: - if group not in all_existing_groups: - raise YunohostError('group_unknown', group=group) if group not in current_allowed_groups: logger.warning(m18n.n('permission_already_disallowed', permission=permission, group=group)) else: @@ -161,36 +155,9 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, operation_logger.start() - try: - ldap.update('cn=%s,ou=permission' % permission, - {'groupPermission': ['cn=' + g + ',ou=groups,dc=yunohost,dc=org' for g in new_allowed_groups]}) - except Exception as e: - raise YunohostError('permission_update_failed', permission=permission, error=e) + new_permission = _update_ldap_group_permission(permission=permission, allowed=new_allowed_groups, sync_perm=sync_perm) logger.debug(m18n.n('permission_updated', permission=permission)) - - # Trigger permission sync if asked - - if sync_perm: - permission_sync_to_user() - - new_permission = user_permission_list(full=True)["permissions"][permission] - - # Trigger app callbacks - - app = permission.split(".")[0] - - old_allowed_users = set(existing_permission["corresponding_users"]) - new_allowed_users = set(new_permission["corresponding_users"]) - - effectively_added_users = new_allowed_users - old_allowed_users - effectively_removed_users = old_allowed_users - new_allowed_users - - if effectively_added_users: - hook_callback('post_app_addaccess', args=[app, ','.join(effectively_added_users)]) - if effectively_removed_users: - hook_callback('post_app_removeaccess', args=[app, ','.join(effectively_removed_users)]) - return new_permission @@ -225,34 +192,9 @@ def user_permission_reset(operation_logger, permission, sync_perm=True): operation_logger.related_to.append(('app', permission.split(".")[0])) operation_logger.start() - default_permission = {'groupPermission': ['cn=all_users,ou=groups,dc=yunohost,dc=org']} - try: - ldap.update('cn=%s,ou=permission' % permission, default_permission) - except Exception as e: - raise YunohostError('permission_update_failed', permission=permission, error=e) + new_permission = _update_ldap_group_permission(permission=permission, allowed="all_users", sync_perm=sync_perm) logger.debug(m18n.n('permission_updated', permission=permission)) - - if sync_perm: - permission_sync_to_user() - - new_permission = user_permission_list(full=True)["permissions"][permission] - - # Trigger app callbacks - - app = permission.split(".")[0] - - old_allowed_users = set(existing_permission["corresponding_users"]) - new_allowed_users = set(new_permission["corresponding_users"]) - - effectively_added_users = new_allowed_users - old_allowed_users - effectively_removed_users = old_allowed_users - new_allowed_users - - if effectively_added_users: - hook_callback('post_app_addaccess', args=[app, ','.join(effectively_added_users)]) - if effectively_removed_users: - hook_callback('post_app_removeaccess', args=[app, ','.join(effectively_removed_users)]) - return new_permission # @@ -286,7 +228,6 @@ def permission_create(operation_logger, permission, url=None, allowed=None, sync re:domain.tld/app/api/[A-Z]*$ -> domain.tld/app/api/[A-Z]*$ """ - from yunohost.user import user_group_list from yunohost.utils.ldap import _get_ldap_interface ldap = _get_ldap_interface() @@ -313,20 +254,6 @@ def permission_create(operation_logger, permission, url=None, allowed=None, sync 'gidNumber': gid, } - # If who should be allowed is explicitly provided, use this info - if allowed: - if not isinstance(allowed, list): - allowed = [allowed] - # (though first we validate that the targets actually exist) - all_existing_groups = user_group_list()['groups'].keys() - for g in allowed: - if g not in all_existing_groups: - raise YunohostError('group_unknown', group=g) - attr_dict['groupPermission'] = ['cn=%s,ou=groups,dc=yunohost,dc=org' % g for g in allowed] - # For main permission, we add all users by default - elif permission.endswith(".main"): - attr_dict['groupPermission'] = ['cn=all_users,ou=groups,dc=yunohost,dc=org'] - if url: attr_dict['URL'] = url @@ -338,11 +265,20 @@ def permission_create(operation_logger, permission, url=None, allowed=None, sync except Exception as e: raise YunohostError('permission_creation_failed', permission=permission, error=e) - if sync_perm: - permission_sync_to_user() + to_add = None + + # If who should be allowed is explicitly provided, use this info + if allowed: + if not isinstance(allowed, list): + to_add = [allowed] + # For main permission, we add all users by default + elif permission.endswith(".main"): + to_add = "all_users" + + new_permission = _update_ldap_group_permission(permission=permission, allowed=to_add, sync_perm=sync_perm) logger.debug(m18n.n('permission_created', permission=permission)) - return user_permission_list(full=True)["permissions"][permission] + return new_permission @is_unit_operation() @@ -471,3 +407,67 @@ def permission_sync_to_user(): # Reload unscd, otherwise the group ain't propagated to the LDAP database os.system('nscd --invalidate=passwd') os.system('nscd --invalidate=group') + +def _update_ldap_group_permission(permission, allowed, sync_perm=True): + """ + Internal function that will rewrite user permission + + permission -- Name of the permission (e.g. mail or nextcloud or wordpress.editors) + allowed -- A list of group/user to allow for the permission + """ + + from yunohost.hook import hook_callback + from yunohost.user import user_group_list + from yunohost.utils.ldap import _get_ldap_interface + ldap = _get_ldap_interface() + + # Fetch currently allowed groups for this permission + + existing_permission = user_permission_list(full=True)["permissions"].get(permission, None) + if existing_permission is None: + raise YunohostError('permission_not_found', permission=permission) + + all_existing_groups = user_group_list()['groups'].keys() + + if allowed: + if not isinstance(allowed, list): + allowed = [allowed] + for group in allowed: + if group not in all_existing_groups: + raise YunohostError('group_unknown', group=group) + else: + if sync_perm: + permission_sync_to_user() + + return user_permission_list(full=True)["permissions"][permission] + + try: + ldap.update('cn=%s,ou=permission' % permission, + {'groupPermission': ['cn=' + g + ',ou=groups,dc=yunohost,dc=org' for g in allowed]}) + except Exception as e: + raise YunohostError('permission_update_failed', permission=permission, error=e) + + # Trigger permission sync if asked + + if sync_perm: + permission_sync_to_user() + + new_permission = user_permission_list(full=True)["permissions"][permission] + + # Trigger app callbacks + + app = permission.split(".")[0] + + old_allowed_users = set(existing_permission["corresponding_users"]) + new_allowed_users = set(new_permission["corresponding_users"]) + + effectively_added_users = new_allowed_users - old_allowed_users + effectively_removed_users = old_allowed_users - new_allowed_users + + if effectively_added_users: + hook_callback('post_app_addaccess', args=[app, ','.join(effectively_added_users)]) + if effectively_removed_users: + hook_callback('post_app_removeaccess', args=[app, ','.join(effectively_removed_users)]) + + return new_permission + \ No newline at end of file From c39a1f010ea17b623c6cc0815cf8da7b45b4f14a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 7 Nov 2019 22:19:07 +0100 Subject: [PATCH 0414/3170] Mistakes were made --- src/yunohost/regenconf.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/yunohost/regenconf.py b/src/yunohost/regenconf.py index 5681f12a4..665b906d6 100644 --- a/src/yunohost/regenconf.py +++ b/src/yunohost/regenconf.py @@ -138,7 +138,8 @@ def regen_conf(operation_logger, names=[], with_diff=False, force=False, dry_run # services.yml (which will happens only during the regen-conf of # 'yunohost', so at the very end of the regen-conf cycle) Anyway, # this can be safely removed once we're in >= 4.0 - names.remove("glances") + if "glances" in names: + names.remove("glances") pre_result = hook_callback('conf_regen', names, pre_callback=_pre_call) From e578140172378d2e48e7499dc2e4e2ee10d5887a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 7 Nov 2019 15:57:26 +0100 Subject: [PATCH 0415/3170] Improve service diagnoser, report details related to configuration tests --- data/hooks/diagnosis/30-services.py | 30 +++++++++------------------- data/hooks/diagnosis/70-regenconf.py | 13 ------------ locales/en.json | 5 +++-- 3 files changed, 12 insertions(+), 36 deletions(-) diff --git a/data/hooks/diagnosis/30-services.py b/data/hooks/diagnosis/30-services.py index 6589d83f2..fed0a1156 100644 --- a/data/hooks/diagnosis/30-services.py +++ b/data/hooks/diagnosis/30-services.py @@ -5,17 +5,6 @@ import os from yunohost.diagnosis import Diagnoser from yunohost.service import service_status -# TODO : all these are arbitrary, should be collectively validated -services_ignored = {"glances"} -services_critical = {"dnsmasq", "fail2ban", "yunohost-firewall", "nginx", "slapd", "ssh"} -# TODO / FIXME : we should do something about this postfix thing -# The nominal value is to be "exited" ... some daemon is actually running -# in a different thread that the thing started by systemd, which is fine -# but somehow sometimes it gets killed and there's no easy way to detect it -# Just randomly restarting it will fix ths issue. We should find some trick -# to identify the PID of the process and check it's still up or idk -services_expected_to_be_exited = {"postfix", "yunohost-firewall"} - class ServicesDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] @@ -28,23 +17,22 @@ class ServicesDiagnoser(Diagnoser): for service, result in all_result.items(): - if service in services_ignored: - continue - item = dict(meta={"service": service}) - expected_status = "running" if service not in services_expected_to_be_exited else "exited" - # TODO / FIXME : might also want to check that services are enabled + if result["status"] != "running": + item["status"] = "ERROR" + item["summary"] = ("diagnosis_services_bad_status", {"service": service, "status": result["status"]}) - if result["active"] != "active" or result["status"] != expected_status: - item["status"] = "WARNING" if service not in services_critical else "ERROR" - item["summary"] = ("diagnosis_services_bad_status", {"service": service, "status": result["active"] + "/" + result["status"]}) + elif result["configuration"] == "broken": + item["status"] = "WARNING" + item["summary"] = ("diagnosis_services_conf_broken", {"service": service}) - # TODO : could try to append the tail of the service log to the "details" key ... else: item["status"] = "SUCCESS" - item["summary"] = ("diagnosis_services_good_status", {"service": service, "status": result["active"] + "/" + result["status"]}) + item["summary"] = ("diagnosis_services_running", {"service": service, "status": result["status"]}) + if result["configuration"] == "broken": + item["details"] = [(d, tuple()) for d in result["configuration-details"]] yield item def main(args, env, loggers): diff --git a/data/hooks/diagnosis/70-regenconf.py b/data/hooks/diagnosis/70-regenconf.py index 105d43fa3..a04f5f98d 100644 --- a/data/hooks/diagnosis/70-regenconf.py +++ b/data/hooks/diagnosis/70-regenconf.py @@ -15,19 +15,6 @@ class RegenconfDiagnoser(Diagnoser): def run(self): - # nginx -t - p = subprocess.Popen("nginx -t".split(), - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT) - out, _ = p.communicate() - - if p.returncode != 0: - yield dict(meta={"test": "nginx-t"}, - status="ERROR", - summary=("diagnosis_regenconf_nginx_conf_broken", {}), - details=[(out, ())] - ) - regenconf_modified_files = manually_modified_files() debian_modified_files = manually_modified_files_compared_to_debian_default(ignore_handled_by_regenconf=True) diff --git a/locales/en.json b/locales/en.json index 751180a37..505bb44fd 100644 --- a/locales/en.json +++ b/locales/en.json @@ -180,8 +180,9 @@ "diagnosis_dns_bad_conf": "Bad / missing DNS configuration for domain {domain} (category {category})", "diagnosis_dns_missing_record": "According to the recommended DNS configuration, you should add a DNS record with type {0}, name {1} and value {2}", "diagnosis_dns_discrepancy": "According to the recommended DNS configuration, the value for the DNS record with type {0} and name {1} should be {2}, not {3}.", - "diagnosis_services_good_status": "Service {service} is {status} as expected!", - "diagnosis_services_bad_status": "Service {service} is {status} :/", + "diagnosis_services_running": "Service {service} is running!", + "diagnosis_services_conf_broken": "Configuration is broken for service {service}!", + "diagnosis_services_bad_status": "Service {service} is {status} :(", "diagnosis_diskusage_verylow": "Storage {mountpoint} (on device {device}) has only {free_abs_GB} GB ({free_percent}%) space remaining. You should really consider cleaning up some space.", "diagnosis_diskusage_low": "Storage {mountpoint} (on device {device}) has only {free_abs_GB} GB ({free_percent}%) space remaining. Be careful.", "diagnosis_diskusage_ok": "Storage {mountpoint} (on device {device}) still has {free_abs_GB} GB ({free_percent}%) space left!", From 3b4ad59cd7abf57a86b008a8d4aa4550da1f3b67 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 7 Nov 2019 15:59:31 +0100 Subject: [PATCH 0416/3170] Propagate removal of 'active' to that piece of code that uses it --- 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 443b14565..4e49c64dd 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -3140,7 +3140,7 @@ def _assert_system_is_sane_for_app(manifest, when): services.append("fail2ban") # List services currently down and raise an exception if any are found - faulty_services = [s for s in services if service_status(s)["active"] != "active"] + faulty_services = [s for s in services if service_status(s)["status"] != "running"] if faulty_services: if when == "pre": raise YunohostError('app_action_cannot_be_ran_because_required_services_down', From 463112de12485be123dc1716a066085e5606266b Mon Sep 17 00:00:00 2001 From: Kay0u Date: Fri, 8 Nov 2019 21:22:28 +0900 Subject: [PATCH 0417/3170] add subcategories --- data/actionsmap/yunohost_completion.py | 83 +++++++++++++++++++------- 1 file changed, 61 insertions(+), 22 deletions(-) diff --git a/data/actionsmap/yunohost_completion.py b/data/actionsmap/yunohost_completion.py index a4c17c4d6..45d15f16c 100644 --- a/data/actionsmap/yunohost_completion.py +++ b/data/actionsmap/yunohost_completion.py @@ -3,7 +3,7 @@ Simple automated generation of a bash_completion file for yunohost command from the actionsmap. Generates a bash completion file assuming the structure -`yunohost domain action` +`yunohost category action` adds `--help` at the end if one presses [tab] again. author: Christophe Vuillot @@ -15,18 +15,39 @@ THIS_SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) ACTIONSMAP_FILE = THIS_SCRIPT_DIR + '/yunohost.yml' BASH_COMPLETION_FILE = THIS_SCRIPT_DIR + '/../bash-completion.d/yunohost' +def get_dict_actions(OPTION_SUBTREE, category): + ACTIONS = [action for action in OPTION_SUBTREE[category]["actions"].keys() + if not action.startswith('_')] + ACTIONS_STR = '{}'.format(' '.join(ACTIONS)) + + DICT = { "actions_str": ACTIONS_STR } + + return DICT + with open(ACTIONSMAP_FILE, 'r') as stream: - # Getting the dictionary containning what actions are possible per domain + # Getting the dictionary containning what actions are possible per category OPTION_TREE = yaml.load(stream) - DOMAINS = [str for str in OPTION_TREE.keys() if not str.startswith('_')] - DOMAINS_STR = '"{}"'.format(' '.join(DOMAINS)) + + CATEGORY = [category for category in OPTION_TREE.keys() if not category.startswith('_')] + + CATEGORY_STR = '{}'.format(' '.join(CATEGORY)) ACTIONS_DICT = {} - for domain in DOMAINS: - ACTIONS = [str for str in OPTION_TREE[domain]['actions'].keys() - if not str.startswith('_')] - ACTIONS_STR = '"{}"'.format(' '.join(ACTIONS)) - ACTIONS_DICT[domain] = ACTIONS_STR + for category in CATEGORY: + ACTIONS_DICT[category] = get_dict_actions(OPTION_TREE, category) + + ACTIONS_DICT[category]["subcategories"] = {} + ACTIONS_DICT[category]["subcategories_str"] = "" + + if "subcategories" in OPTION_TREE[category].keys(): + SUBCATEGORIES = [ subcategory for subcategory in OPTION_TREE[category]["subcategories"].keys() ] + + SUBCATEGORIES_STR = '{}'.format(' '.join(SUBCATEGORIES)) + + ACTIONS_DICT[category]["subcategories_str"] = SUBCATEGORIES_STR + + for subcategory in SUBCATEGORIES: + ACTIONS_DICT[category]["subcategories"][subcategory] = get_dict_actions(OPTION_TREE[category]["subcategories"], subcategory) with open(BASH_COMPLETION_FILE, 'w') as generated_file: @@ -47,31 +68,49 @@ with open(ACTIONSMAP_FILE, 'r') as stream: generated_file.write('\tnarg=${#COMP_WORDS[@]}\n\n') generated_file.write('\t# the current word being typed\n') generated_file.write('\tcur="${COMP_WORDS[COMP_CWORD]}"\n\n') - generated_file.write('\t# the last typed word\n') - generated_file.write('\tprev="${COMP_WORDS[COMP_CWORD-1]}"\n\n') - # If one is currently typing a domain then match with the domain list - generated_file.write('\t# If one is currently typing a domain,\n') - generated_file.write('\t# match with domains\n') + # If one is currently typing a category then match with the category list + generated_file.write('\t# If one is currently typing a category,\n') + generated_file.write('\t# match with categorys\n') generated_file.write('\tif [[ $narg == 2 ]]; then\n') - generated_file.write('\t\topts={}\n'.format(DOMAINS_STR)) + generated_file.write('\t\topts="{}"\n'.format(CATEGORY_STR)) generated_file.write('\tfi\n\n') # If one is currently typing an action then match with the action list - # of the previously typed domain - generated_file.write('\t# If one already typed a domain,\n') - generated_file.write('\t# match the actions of that domain\n') + # of the previously typed category + generated_file.write('\t# If one already typed a category,\n') + generated_file.write('\t# match the actions or the subcategories of that category\n') generated_file.write('\tif [[ $narg == 3 ]]; then\n') - for domain in DOMAINS: - generated_file.write('\t\tif [[ $prev == "{}" ]]; then\n'.format(domain)) - generated_file.write('\t\t\topts={}\n'.format(ACTIONS_DICT[domain])) + generated_file.write('\t\t# the category typed\n') + generated_file.write('\t\tcategory="${COMP_WORDS[1]}"\n\n') + for category in CATEGORY: + generated_file.write('\t\tif [[ $category == "{}" ]]; then\n'.format(category)) + generated_file.write('\t\t\topts="{} {}"\n'.format(ACTIONS_DICT[category]["actions_str"], ACTIONS_DICT[category]["subcategories_str"])) generated_file.write('\t\tfi\n') generated_file.write('\tfi\n\n') - # If both domain and action have been typed or the domain + generated_file.write('\t# If one already typed an action or a subcategory,\n') + generated_file.write('\t# match the actions of that subcategory\n') + generated_file.write('\tif [[ $narg == 4 ]]; then\n') + generated_file.write('\t\t# the category typed\n') + generated_file.write('\t\tcategory="${COMP_WORDS[1]}"\n\n') + generated_file.write('\t\t# the action or the subcategory typed\n') + generated_file.write('\t\taction_or_subcategory="${COMP_WORDS[2]}"\n\n') + for category in CATEGORY: + if len(ACTIONS_DICT[category]["subcategories"]): + generated_file.write('\t\tif [[ $category == "{}" ]]; then\n'.format(category)) + for subcategory in ACTIONS_DICT[category]["subcategories"]: + generated_file.write('\t\t\tif [[ $action_or_subcategory == "{}" ]]; then\n'.format(subcategory)) + generated_file.write('\t\t\t\topts="{}"\n'.format(ACTIONS_DICT[category]["subcategories"][subcategory]["actions_str"])) + generated_file.write('\t\t\tfi\n') + generated_file.write('\t\tfi\n') + generated_file.write('\tfi\n\n') + + # If both category and action have been typed or the category # was not recognized propose --help (only once) generated_file.write('\t# If no options were found propose --help\n') generated_file.write('\tif [ -z "$opts" ]; then\n') + generated_file.write('\t\tprev="${COMP_WORDS[COMP_CWORD-1]}"\n\n') generated_file.write('\t\tif [[ $prev != "--help" ]]; then\n') generated_file.write('\t\t\topts=( --help )\n') generated_file.write('\t\tfi\n') From 65d6b02b5604421a9df30206ba29656d2a36e4e9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 8 Nov 2019 19:22:54 +0100 Subject: [PATCH 0418/3170] Implement basic outgoing port 25 check for email stack --- data/hooks/diagnosis/18-mail.py | 16 ++++++++++------ locales/en.json | 3 +++ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/data/hooks/diagnosis/18-mail.py b/data/hooks/diagnosis/18-mail.py index c12c15cff..f0060df52 100644 --- a/data/hooks/diagnosis/18-mail.py +++ b/data/hooks/diagnosis/18-mail.py @@ -13,16 +13,20 @@ class MailDiagnoser(Diagnoser): def run(self): - # TODO / FIXME TO BE IMPLEMETED in the future ... + # Is outgoing port 25 filtered somehow ? + if os.system('/bin/nc -z -w2 yunohost.org 25') == 0: + yield dict(meta={"test": "ougoing_port_25"}, + status="SUCCESS", + summary=("diagnosis_mail_ougoing_port_25_ok",{})) + else: + yield dict(meta={"test": "outgoing_port_25"}, + status="ERROR", + summary=("diagnosis_mail_ougoing_port_25_blocked",{})) + - yield dict(meta={}, - status="WARNING", - summary=("nothing_implemented_yet", {})) # Mail blacklist using dig requests (c.f. ljf's code) - # Outgoing port 25 (c.f. code in monitor.py, a simple 'nc -zv yunohost.org 25' IIRC) - # SMTP reachability (c.f. check-smtp to be implemented on yunohost's remote diagnoser) # ideally, SPF / DMARC / DKIM validation ... (c.f. https://github.com/alexAubin/yunoScripts/blob/master/yunoDKIM.py possibly though that looks horrible) diff --git a/locales/en.json b/locales/en.json index 751180a37..9a9855116 100644 --- a/locales/en.json +++ b/locales/en.json @@ -191,6 +191,8 @@ "diagnosis_swap_none": "The system has no swap at all. You should consider adding at least 256 MB of swap to avoid situations where the system runs out of memory.", "diagnosis_swap_notsomuch": "The system has only {total_MB} MB swap. You should consider having at least 256 MB to avoid situations where the system runs out of memory.", "diagnosis_swap_ok": "The system has {total_MB} MB of swap!", + "diagnosis_mail_ougoing_port_25_ok": "Outgoing port 25 is not blocked and email can be sent to other servers.", + "diagnosis_mail_ougoing_port_25_blocked": "Outgoing port 25 appears to be blocked. You should try to unblock it in your internet service provider (or hoster) configuration panel. Meanwhile, the server won't be able to send emails to other servers.", "diagnosis_regenconf_allgood": "All configurations files are in line with the recommended configuration!", "diagnosis_regenconf_manually_modified": "Configuration file {file} was manually modified.", "diagnosis_regenconf_manually_modified_details": "This is probably OK as long as you know what you're doing ;) !", @@ -207,6 +209,7 @@ "diagnosis_description_systemresources": "System resources", "diagnosis_description_ports": "Ports exposure", "diagnosis_description_http": "HTTP exposure", + "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. Error: {error}", From 104bba3dd86e995a5d80c17954a5c515b0da60be Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 8 Nov 2019 20:44:27 +0100 Subject: [PATCH 0419/3170] Sort services during diagnosis to avoid random order --- 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 6589d83f2..32f99c84d 100644 --- a/data/hooks/diagnosis/30-services.py +++ b/data/hooks/diagnosis/30-services.py @@ -26,7 +26,7 @@ class ServicesDiagnoser(Diagnoser): all_result = service_status() - for service, result in all_result.items(): + for service, result in sorted(all_result.items()): if service in services_ignored: continue From a9dd70182494eea2b13931cfe81d7009162f44ea Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 8 Nov 2019 22:29:21 +0100 Subject: [PATCH 0420/3170] Improve port diagnosis by adding a relation between ports and services --- data/hooks/diagnosis/14-ports.py | 32 ++++++++++++++++------------ data/templates/yunohost/services.yml | 5 +++++ locales/en.json | 2 ++ 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/data/hooks/diagnosis/14-ports.py b/data/hooks/diagnosis/14-ports.py index b953f35a9..a845174b6 100644 --- a/data/hooks/diagnosis/14-ports.py +++ b/data/hooks/diagnosis/14-ports.py @@ -5,7 +5,7 @@ import requests from yunohost.diagnosis import Diagnoser from yunohost.utils.error import YunohostError - +from yunohost.service import _get_services class PortsDiagnoser(Diagnoser): @@ -15,16 +15,18 @@ class PortsDiagnoser(Diagnoser): def run(self): - # FIXME / TODO : in the future, maybe we want to report different - # things per port depending on how important they are - # (e.g. XMPP sounds to me much less important than other ports) - # Ideally, a port could be related to a service... - # FIXME / TODO : for now this list of port is hardcoded, might want - # to fetch this from the firewall.yml in /etc/yunohost/ - ports = [22, 25, 53, 80, 443, 587, 993, 5222, 5269] + # This dict is something like : + # { 80: "nginx", + # 25: "postfix", + # 443: "nginx" + # ... } + ports = {} + for service, infos in _get_services().items(): + for port in infos.get("needs_exposed_ports", []): + ports[port] = service try: - r = requests.post('https://ynhdiagnoser.netlib.re/check-ports', json={'ports': ports}, timeout=30).json() + r = requests.post('https://ynhdiagnoser.netlib.re/check-ports', json={'ports': ports.keys()}, timeout=30).json() if "status" not in r.keys(): raise Exception("Bad syntax for response ? Raw json: %s" % str(r)) elif r["status"] == "error": @@ -37,15 +39,17 @@ class PortsDiagnoser(Diagnoser): except Exception as e: raise YunohostError("diagnosis_ports_could_not_diagnose", error=e) - for port in ports: + for port, service in ports.items(): if r["ports"].get(str(port), None) is not True: - yield dict(meta={"port": port}, + yield dict(meta={"port": port, "needed_by": service}, status="ERROR", - summary=("diagnosis_ports_unreachable", {"port": port})) + summary=("diagnosis_ports_unreachable", {"port": port}), + details=[("diagnosis_ports_needed_by", (service,)), ("diagnosis_ports_forwarding_tip", ())]) else: - yield dict(meta={}, + yield dict(meta={"port": port, "needed_by": service}, status="SUCCESS", - summary=("diagnosis_ports_ok", {"port": port})) + summary=("diagnosis_ports_ok", {"port": port}), + details=[("diagnosis_ports_needed_by", (service))]) def main(args, env, loggers): diff --git a/data/templates/yunohost/services.yml b/data/templates/yunohost/services.yml index fd9d06eac..986beb271 100644 --- a/data/templates/yunohost/services.yml +++ b/data/templates/yunohost/services.yml @@ -1,6 +1,7 @@ nginx: log: /var/log/nginx test-conf: nginx -t + needs_exposed_ports: [80, 443] avahi-daemon: log: /var/log/daemon.log dnsmasq: @@ -9,9 +10,11 @@ fail2ban: log: /var/log/fail2ban.log dovecot: log: [/var/log/mail.log,/var/log/mail.err] + needs_exposed_ports: [993] postfix: log: [/var/log/mail.log,/var/log/mail.err] test-status: systemctl show postfix@- | grep -q "^SubState=running" + needs_exposed_ports: [25, 587] rspamd: log: /var/log/rspamd/rspamd.log redis-server: @@ -22,8 +25,10 @@ mysql: ssh: log: /var/log/auth.log test-conf: sshd -t + needs_exposed_ports: [22] metronome: log: [/var/log/metronome/metronome.log,/var/log/metronome/metronome.err] + needs_exposed_ports: [5222, 5269] slapd: log: /var/log/syslog php7.0-fpm: diff --git a/locales/en.json b/locales/en.json index f3a6c662e..581f38bef 100644 --- a/locales/en.json +++ b/locales/en.json @@ -216,6 +216,8 @@ "diagnosis_ports_could_not_diagnose": "Could not diagnose if ports are reachable from outside. Error: {error}", "diagnosis_ports_unreachable": "Port {port} is not reachable from outside.", "diagnosis_ports_ok": "Port {port} is reachable from outside.", + "diagnosis_ports_needed_by": "Exposing this port is needed for service {0}", + "diagnosis_ports_forwarding_tip": "To fix this issue, most probably you need to configure port forwarding on your internet router as described in https://yunohost.org/port_forwarding", "diagnosis_http_could_not_diagnose": "Could not diagnose if domain is reachable from outside. Error: {error}", "diagnosis_http_ok": "Domain {domain} is reachable from outside.", "diagnosis_http_unreachable": "Domain {domain} is unreachable through HTTP from outside.", From 5a682503220bf4cf3a68684eb513fa2a4dbbcd94 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 8 Nov 2019 22:35:25 +0100 Subject: [PATCH 0421/3170] test-conf -> test_conf, and test-status -> test_status --- data/templates/yunohost/services.yml | 10 +++++----- src/yunohost/service.py | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/data/templates/yunohost/services.yml b/data/templates/yunohost/services.yml index 986beb271..351120a7d 100644 --- a/data/templates/yunohost/services.yml +++ b/data/templates/yunohost/services.yml @@ -1,6 +1,6 @@ nginx: log: /var/log/nginx - test-conf: nginx -t + test_conf: nginx -t needs_exposed_ports: [80, 443] avahi-daemon: log: /var/log/daemon.log @@ -13,7 +13,7 @@ dovecot: needs_exposed_ports: [993] postfix: log: [/var/log/mail.log,/var/log/mail.err] - test-status: systemctl show postfix@- | grep -q "^SubState=running" + test_status: systemctl show postfix@- | grep -q "^SubState=running" needs_exposed_ports: [25, 587] rspamd: log: /var/log/rspamd/rspamd.log @@ -24,7 +24,7 @@ mysql: alternates: ['mariadb'] ssh: log: /var/log/auth.log - test-conf: sshd -t + test_conf: sshd -t needs_exposed_ports: [22] metronome: log: [/var/log/metronome/metronome.log,/var/log/metronome/metronome.err] @@ -33,12 +33,12 @@ slapd: log: /var/log/syslog php7.0-fpm: log: /var/log/php7.0-fpm.log - test-conf: php-fpm7.0 --test + test_conf: php-fpm7.0 --test yunohost-api: log: /var/log/yunohost/yunohost-api.log yunohost-firewall: need_lock: true - test-status: iptables -S | grep "^-A INPUT" | grep " --dport" | grep -q ACCEPT + test_status: iptables -S | grep "^-A INPUT" | grep " --dport" | grep -q ACCEPT nslcd: log: /var/log/syslog glances: null diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 850899ecc..612e73f6c 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -327,14 +327,14 @@ def service_status(names=[]): if "StateChangeTimestamp" in status: 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]: - status = os.system(services[name]["test-status"]) + # 'test_status' is an optional field to test the status of the service using a custom command + if "test_status" in services[name]: + status = os.system(services[name]["test_status"]) result[name]["status"] = "running" if status == 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"], + # '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"], shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) From 8671a4c23018cb59dce8ae9111967ebc32a57743 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 8 Nov 2019 22:50:47 +0100 Subject: [PATCH 0422/3170] Remove pdb used for debug, and improve comment --- src/yunohost/app.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index e403592a2..cb6ef251f 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -2905,13 +2905,12 @@ def _patch_legacy_helpers(app_folder): pattern, replace = regexes # If helper is used, attempt to patch the file if helper in content and pattern != "": - try: - content = pattern.sub(replace, content) - replaced_stuff = True - except Exception as e: - import pdb; pdb.set_trace() + content = pattern.sub(replace, content) + replaced_stuff = True - # If we couldn't patch the deprecated helper, abort the install or whichever step is performed + # If the helpert is *still* in the content, it means that we + # couldn't patch the deprecated helper in the previous lines. In + # that case, abort the install or whichever step is performed if helper in content: raise YunohostError("This app is likely pretty old and uses deprecated / outdated helpers that can't be migrated easily. It can't be installed anymore.") From a89fd44ab6b4759cc1f0d9900ef678d2f2d4eb38 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 8 Nov 2019 23:04:32 +0100 Subject: [PATCH 0423/3170] Use ss instead of netcat to check if a port is already used ... c.f. PR #827 --- data/helpers.d/network | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/network b/data/helpers.d/network index d43c4ec13..e0f815fc4 100644 --- a/data/helpers.d/network +++ b/data/helpers.d/network @@ -40,7 +40,7 @@ ynh_port_available () { # Manage arguments with getopts ynh_handle_getopts_args "$@" - if netcat -z 127.0.0.1 $port; + if ss -nltu | grep -q -w :$port then return 1 else From 66c5ad18c9f66ba0fcea99683d69de98a31a86b0 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 8 Nov 2019 23:17:32 +0100 Subject: [PATCH 0424/3170] Migration 12 -> 13 --- locales/en.json | 2 +- src/yunohost/data_migrations/0010_migrate_to_apps_json.py | 6 +++--- ...pslist_system.py => 0013_futureproof_appslist_system.py} | 0 3 files changed, 4 insertions(+), 4 deletions(-) rename src/yunohost/data_migrations/{0012_futureproof_appslist_system.py => 0013_futureproof_appslist_system.py} (100%) diff --git a/locales/en.json b/locales/en.json index 6f4df6cd8..4ad404da7 100644 --- a/locales/en.json +++ b/locales/en.json @@ -313,7 +313,7 @@ "migration_description_0010_migrate_to_apps_json": "Remove deprecated applists and use the new unified 'apps.json' list instead (outdated, replaced by migration 12)", "migration_description_0011_setup_group_permission": "Set up user group and set up permission for apps and services", "migration_description_0012_postgresql_password_to_md5_authentication": "Force PostgreSQL authentication to use MD5 for local connections", - "migration_description_0012_futureproof_appslist_system": "Migrate to the new future-proof appslist system", + "migration_description_0013_futureproof_appslist_system": "Migrate to the new future-proof appslist system", "migration_0003_start": "Starting migration to Stretch. The logs will be available in {logfile}.", "migration_0003_patching_sources_list": "Patching the sources.lists…", "migration_0003_main_upgrade": "Starting main upgrade…", diff --git a/src/yunohost/data_migrations/0010_migrate_to_apps_json.py b/src/yunohost/data_migrations/0010_migrate_to_apps_json.py index c83408ce8..e5ce65608 100644 --- a/src/yunohost/data_migrations/0010_migrate_to_apps_json.py +++ b/src/yunohost/data_migrations/0010_migrate_to_apps_json.py @@ -6,8 +6,8 @@ logger = getActionLogger('yunohost.migration') class MyMigration(Migration): - "Migrate from official.json to apps.json (outdated, replaced by migration 12)" + "Migrate from official.json to apps.json (outdated, replaced by migration 13)" def run(self): - logger.info("This migration is oudated and doesn't do anything anymore. The migration 12 will handle this instead.") - pass \ No newline at end of file + logger.info("This migration is oudated and doesn't do anything anymore. The migration 13 will handle this instead.") + pass diff --git a/src/yunohost/data_migrations/0012_futureproof_appslist_system.py b/src/yunohost/data_migrations/0013_futureproof_appslist_system.py similarity index 100% rename from src/yunohost/data_migrations/0012_futureproof_appslist_system.py rename to src/yunohost/data_migrations/0013_futureproof_appslist_system.py From 3b7899db8cd0482ac606837468ff8cc35b285451 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 9 Nov 2019 00:06:50 +0100 Subject: [PATCH 0425/3170] appslists -> apps catalog --- locales/en.json | 17 +- src/yunohost/app.py | 128 +++---- .../0013_futureproof_apps_catalog_system.py | 46 +++ .../0013_futureproof_appslist_system.py | 46 --- src/yunohost/tests/test_appscatalog.py | 358 ++++++++++++++++++ src/yunohost/tests/test_appslist.py | 358 ------------------ src/yunohost/tools.py | 13 +- 7 files changed, 482 insertions(+), 484 deletions(-) create mode 100644 src/yunohost/data_migrations/0013_futureproof_apps_catalog_system.py delete mode 100644 src/yunohost/data_migrations/0013_futureproof_appslist_system.py create mode 100644 src/yunohost/tests/test_appscatalog.py delete mode 100644 src/yunohost/tests/test_appslist.py diff --git a/locales/en.json b/locales/en.json index c3d636c2f..027d9fc0b 100644 --- a/locales/en.json +++ b/locales/en.json @@ -55,11 +55,11 @@ "apps_already_up_to_date": "All apps are already up-to-date", "apps_permission_not_found": "No permission found for the installed apps", "apps_permission_restoration_failed": "Permission '{permission:s}' for app {app:s} restoration has failed", - "appslist_init_success": "Appslist system initialized!", - "appslist_updating": "Updating application list...", - "appslist_failed_to_download": "Unable to download the {applist} appslist : {error}", - "appslist_obsolete_cache": "The applist cache is empty or obsolete.", - "appslist_update_success": "The application list has been updated!", + "apps_catalog_init_success": "Apps catalog system initialized!", + "apps_catalog_updating": "Updating applications catalog...", + "apps_catalog_failed_to_download": "Unable to download the {apps_catalog} apps catalog: {error}", + "apps_catalog_obsolete_cache": "The apps catalog cache is empty or obsolete.", + "apps_catalog_update_success": "The application catalog has been updated!", "ask_current_admin_password": "Current administration password", "ask_email": "E-mail address", "ask_firstname": "First name", @@ -144,7 +144,6 @@ "confirm_app_install_danger": "DANGER! This app is known to be still experimental (if not explicitly not working)! You should probably NOT install it unless you know what you are doing. NO SUPPORT will be provided if this app doesn't work or break your system… If you are willing to take that risk anyway, type '{answers:s}'", "confirm_app_install_thirdparty": "DANGER! This app is not part of Yunohost's app catalog. Installing third-party apps may compromise the integrity and security of your system. You should probably NOT install it unless you know what you are doing. NO SUPPORT will be provided if this app doesn't work or break your system… If you are willing to take that risk anyway, type '{answers:s}'", "custom_app_url_required": "You must provide a URL to upgrade your custom app {app:s}", - "custom_appslist_name_required": "You must provide a name for your custom app list", "diagnosis_basesystem_host": "Server is running Debian {debian_version}.", "diagnosis_basesystem_kernel": "Server is running Linux kernel {kernel_version}", "diagnosis_basesystem_ynh_single_version": "{0} version: {1} ({2})", @@ -374,10 +373,10 @@ "migration_description_0007_ssh_conf_managed_by_yunohost_step1": "Let the SSH configuration be managed by YunoHost (step 1, automatic)", "migration_description_0008_ssh_conf_managed_by_yunohost_step2": "Let the SSH configuration be managed by YunoHost (step 2, manual)", "migration_description_0009_decouple_regenconf_from_services": "Decouple the regen-conf mechanism from services", - "migration_description_0010_migrate_to_apps_json": "Remove deprecated applists and use the new unified 'apps.json' list instead (outdated, replaced by migration 12)", + "migration_description_0010_migrate_to_apps_json": "Remove deprecated apps catalogs and use the new unified 'apps.json' list instead (outdated, replaced by migration 13)", "migration_description_0011_setup_group_permission": "Set up user group and set up permission for apps and services", "migration_description_0012_postgresql_password_to_md5_authentication": "Force PostgreSQL authentication to use MD5 for local connections", - "migration_description_0013_futureproof_appslist_system": "Migrate to the new future-proof appslist system", + "migration_description_0013_futureproof_apps_catalog_system": "Migrate to the new future-proof apps catalog system", "migration_0003_start": "Starting migration to Stretch. The logs will be available in {logfile}.", "migration_0003_patching_sources_list": "Patching the sources.lists…", "migration_0003_main_upgrade": "Starting main upgrade…", @@ -388,7 +387,7 @@ "migration_0003_system_not_fully_up_to_date": "Your system is not fully up-to-date. Please perform a regular upgrade before running the migration to Stretch.", "migration_0003_still_on_jessie_after_main_upgrade": "Something went wrong during the main upgrade: Is the system still on Jessie‽ To investigate the issue, please look at {log}:s…", "migration_0003_general_warning": "Please note that this migration is a delicate operation. The YunoHost team did its best to review and test it, but the migration might still break parts of the system or its apps.\n\nTherefore, it is recommended to:\n - Perform a backup of any critical data or app. More info on https://yunohost.org/backup;\n - Be patient after launching the migration: Depending on your Internet connection and hardware, it might take up to a few hours for everything to upgrade.\n\nAdditionally, the port for SMTP, used by external e-mail clients (like Thunderbird or K9-Mail) was changed from 465 (SSL/TLS) to 587 (STARTTLS). The old port (465) will automatically be closed, and the new port (587) will be opened in the firewall. You and your users *will* have to adapt the configuration of your e-mail clients accordingly.", - "migration_0003_problematic_apps_warning": "Please note that the following possibly problematic installed apps were detected. It looks like those were not installed from an applist, or are not flagged as 'working'. Consequently, it cannot be guaranteed that they will still work after the upgrade: {problematic_apps}", + "migration_0003_problematic_apps_warning": "Please note that the following possibly problematic installed apps were detected. It looks like those were not installed from an apps_catalog, or are not flagged as 'working'. Consequently, it cannot be guaranteed that they will still work after the upgrade: {problematic_apps}", "migration_0003_modified_files": "Please note that the following files were found to be manually modified and might be overwritten following the upgrade: {manually_modified_files}", "migration_0005_postgresql_94_not_installed": "PostgreSQL was not installed on your system. Nothing to do.", "migration_0005_postgresql_96_not_installed": "PostgreSQL 9.4 is installed, but not postgresql 9.6‽ Something weird might have happened on your system:(…", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 73f96ba0b..d17ea8424 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -54,11 +54,11 @@ APPS_SETTING_PATH = '/etc/yunohost/apps/' INSTALL_TMP = '/var/cache/yunohost' APP_TMP_FOLDER = INSTALL_TMP + '/from_file' -APPSLISTS_CACHE = '/var/cache/yunohost/repo' -APPSLISTS_CONF = '/etc/yunohost/appslists.yml' -APPSLISTS_CRON_PATH = "/etc/cron.daily/yunohost-fetch-appslists" -APPSLISTS_API_VERSION = 1 -APPSLISTS_DEFAULT_URL = "https://app.yunohost.org/default" +APPS_CATALOG_CACHE = '/var/cache/yunohost/repo' +APPS_CATALOG_CONF = '/etc/yunohost/apps_catalog.yml' +APPS_CATALOG_CRON_PATH = "/etc/cron.daily/yunohost-fetch-apps-catalog" +APPS_CATALOG_API_VERSION = 1 +APPS_CATALOG_DEFAULT_URL = "https://app.yunohost.org/default" re_github_repo = re.compile( r'^(http[s]?://|git@)github.com[/:]' @@ -88,8 +88,8 @@ def app_list(filter=None, raw=False, installed=False, with_backup=False): list_dict = {} if raw else [] - # Get app list from applist cache - app_dict = _load_appslist() + # Get app list from catalog cache + app_dict = _load_apps_catalog() # Get app list from the app settings directory for app in os.listdir(APPS_SETTING_PATH): @@ -2740,144 +2740,144 @@ def _parse_app_instance_name(app_instance_name): # -def _initialize_appslists_system(): +def _initialize_apps_catalog_system(): """ - This function is meant to intialize the appslist system with YunoHost's default applist. + This function is meant to intialize the apps_catalog system with YunoHost's default app catalog. It also creates the cron job that will update the list every day """ - default_appslist_list = [{"id": "default", "url": APPSLISTS_DEFAULT_URL}] + default_apps_catalog_list = [{"id": "default", "url": APPS_CATALOG_DEFAULT_URL}] cron_job = [] cron_job.append("#!/bin/bash") # We add a random delay between 0 and 60 min to avoid every instance fetching - # the appslist at the same time every night + # the apps catalog at the same time every night cron_job.append("(sleep $((RANDOM%3600));") cron_job.append("yunohost tools update --apps > /dev/null) &") try: - logger.debug("Initializing appslist system with YunoHost's default app list") - write_to_yaml(APPSLISTS_CONF, default_appslist_list) + logger.debug("Initializing apps catalog system with YunoHost's default app list") + write_to_yaml(APPS_CATALOG_CONF, default_apps_catalog_list) - logger.debug("Installing appslist fetch daily cron job") - write_to_file(APPSLISTS_CRON_PATH, '\n'.join(cron_job)) - chown(APPSLISTS_CRON_PATH, uid="root", gid="root") - chmod(APPSLISTS_CRON_PATH, 0o755) + logger.debug("Installing apps catalog fetch daily cron job") + write_to_file(APPS_CATALOG_CRON_PATH, '\n'.join(cron_job)) + chown(APPS_CATALOG_CRON_PATH, uid="root", gid="root") + chmod(APPS_CATALOG_CRON_PATH, 0o755) except Exception as e: - raise YunohostError("Could not initialize the appslist system... : %s" % str(e)) + raise YunohostError("Could not initialize the apps catalog system... : %s" % str(e)) - logger.success(m18n.n("appslist_init_success")) + logger.success(m18n.n("apps_catalog_init_success")) -def _read_appslist_list(): +def _read_apps_catalog_list(): """ - Read the json corresponding to the list of appslists + Read the json corresponding to the list of apps catalogs """ # Legacy code - can be removed after moving to buster (if the migration got merged before buster) if os.path.exists('/etc/yunohost/appslists.json'): from yunohost.tools import _get_migration_by_name - migration = _get_migration_by_name("futureproof_appslist_system") + migration = _get_migration_by_name("futureproof_apps_catalog_system") migration.migrate() try: - list_ = read_yaml(APPSLISTS_CONF) + list_ = read_yaml(APPS_CATALOG_CONF) # Support the case where file exists but is empty # by returning [] if list_ is None return list_ if list_ else [] except Exception as e: - raise YunohostError("Could not read the appslist list ... : %s" % str(e)) + raise YunohostError("Could not read the apps_catalog list ... : %s" % str(e)) -def _actual_appslist_api_url(base_url): +def _actual_apps_catalog_api_url(base_url): - return "{base_url}/v{version}/apps.json".format(base_url=base_url, version=APPSLISTS_API_VERSION) + return "{base_url}/v{version}/apps.json".format(base_url=base_url, version=APPS_CATALOG_API_VERSION) -def _update_appslist(): +def _update_apps_catalog(): """ - Fetches the json for each appslist and update the cache + Fetches the json for each apps_catalog and update the cache - appslist_list is for example : + apps_catalog_list is for example : [ {"id": "default", "url": "https://app.yunohost.org/default/"} ] - Then for each appslist, the actual json URL to be fetched is like : + Then for each apps_catalog, the actual json URL to be fetched is like : https://app.yunohost.org/default/vX/apps.json And store it in : /var/cache/yunohost/repo/default.json """ - appslist_list = _read_appslist_list() + apps_catalog_list = _read_apps_catalog_list() - logger.info(m18n.n("appslist_updating")) + logger.info(m18n.n("apps_catalog_updating")) # Create cache folder if needed - if not os.path.exists(APPSLISTS_CACHE): - logger.debug("Initialize folder for appslist cache") - mkdir(APPSLISTS_CACHE, mode=0o750, parents=True, uid='root') + if not os.path.exists(APPS_CATALOG_CACHE): + logger.debug("Initialize folder for apps catalog cache") + mkdir(APPS_CATALOG_CACHE, mode=0o750, parents=True, uid='root') - for appslist in appslist_list: - applist_id = appslist["id"] - actual_api_url = _actual_appslist_api_url(appslist["url"]) + for apps_catalog in apps_catalog_list: + apps_catalog_id = apps_catalog["id"] + actual_api_url = _actual_apps_catalog_api_url(apps_catalog["url"]) # Fetch the json try: - appslist_content = download_json(actual_api_url) + apps_catalog_content = download_json(actual_api_url) except Exception as e: - raise YunohostError("appslist_failed_to_download", applist=applist_id, error=str(e)) + raise YunohostError("apps_catalog_failed_to_download", apps_catalog=apps_catalog_id, error=str(e)) - # Remember the appslist api version for later - appslist_content["from_api_version"] = APPSLISTS_API_VERSION + # Remember the apps_catalog api version for later + apps_catalog_content["from_api_version"] = APPS_CATALOG_API_VERSION - # Save the appslist data in the cache - cache_file = "{cache_folder}/{list}.json".format(cache_folder=APPSLISTS_CACHE, list=applist_id) + # Save the apps_catalog data in the cache + cache_file = "{cache_folder}/{list}.json".format(cache_folder=APPS_CATALOG_CACHE, list=apps_catalog_id) try: - write_to_json(cache_file, appslist_content) + write_to_json(cache_file, apps_catalog_content) except Exception as e: - raise YunohostError("Unable to write cache data for %s appslist : %s" % (applist_id, str(e))) + raise YunohostError("Unable to write cache data for %s apps_catalog : %s" % (apps_catalog_id, str(e))) - logger.success(m18n.n("appslist_update_success")) + logger.success(m18n.n("apps_catalog_update_success")) -def _load_appslist(): +def _load_apps_catalog(): """ - Read all the appslist cache file and build a single dict (app_dict) + Read all the apps catalog cache files and build a single dict (app_dict) corresponding to all known apps in all indexes """ app_dict = {} - for appslist_id in [L["id"] for L in _read_appslist_list()]: + for apps_catalog_id in [L["id"] for L in _read_apps_catalog_list()]: - # Let's load the json from cache for this appslist - cache_file = "{cache_folder}/{list}.json".format(cache_folder=APPSLISTS_CACHE, list=appslist_id) + # Let's load the json from cache for this catalog + cache_file = "{cache_folder}/{list}.json".format(cache_folder=APPS_CATALOG_CACHE, list=apps_catalog_id) try: - appslist_content = read_json(cache_file) if os.path.exists(cache_file) else None + apps_catalog_content = read_json(cache_file) if os.path.exists(cache_file) else None except Exception as e: - raise ("Unable to read cache for appslist %s : %s" % (appslist_id, str(e))) + raise ("Unable to read cache for apps_catalog %s : %s" % (apps_catalog_id, str(e))) # Check that the version of the data matches version .... # ... otherwise it means we updated yunohost in the meantime # and need to update the cache for everything to be consistent - if not appslist_content or appslist_content.get("from_api_version") != APPSLISTS_API_VERSION: - logger.info(m18n.n("appslist_obsolete_cache")) - _update_appslist() - appslist_content = read_json(cache_file) + if not apps_catalog_content or apps_catalog_content.get("from_api_version") != APPS_CATALOG_API_VERSION: + logger.info(m18n.n("apps_catalog_obsolete_cache")) + _update_apps_catalog() + apps_catalog_content = read_json(cache_file) - del appslist_content["from_api_version"] + del apps_catalog_content["from_api_version"] - # Add apps from this applist to the output - for app, info in appslist_content.items(): + # Add apps from this catalog to the output + for app, info in apps_catalog_content.items(): - # (N.B. : there's a small edge case where multiple appslist could be listing the same apps ... + # (N.B. : there's a small edge case where multiple apps catalog could be listing the same apps ... # in which case we keep only the first one found) if app in app_dict: - logger.warning("Duplicate app %s found between appslist %s and %s" % (app, appslist_id, app_dict[app]['repository'])) + logger.warning("Duplicate app %s found between apps catalog %s and %s" % (app, apps_catalog_id, app_dict[app]['repository'])) continue - info['repository'] = appslist_id + info['repository'] = apps_catalog_id app_dict[app] = info return app_dict diff --git a/src/yunohost/data_migrations/0013_futureproof_apps_catalog_system.py b/src/yunohost/data_migrations/0013_futureproof_apps_catalog_system.py new file mode 100644 index 000000000..2215d4681 --- /dev/null +++ b/src/yunohost/data_migrations/0013_futureproof_apps_catalog_system.py @@ -0,0 +1,46 @@ + +import os +import shutil + +from moulinette.utils.log import getActionLogger +from moulinette.utils.filesystem import read_json + +from yunohost.tools import Migration +from yunohost.app import (_initialize_apps_catalog_system, + _update_apps_catalog, + APPS_CATALOG_CACHE, + APPS_CATALOG_CONF) + +logger = getActionLogger('yunohost.migration') + +LEGACY_APPS_CATALOG_CONF = '/etc/yunohost/appslists.json' +LEGACY_APPS_CATALOG_CONF_BACKUP = LEGACY_APPS_CATALOG_CONF + ".old" + + +class MyMigration(Migration): + + "Migrate to the new future-proof apps catalog system" + + def migrate(self): + + if not os.path.exists(LEGACY_APPS_CATALOG_CONF): + logger.info("No need to do anything") + + # Destroy old lecacy cache + if os.path.exists(APPS_CATALOG_CACHE): + shutil.rmtree(APPS_CATALOG_CACHE) + + # Backup the legacy file + try: + legacy_catalogs = read_json(LEGACY_APPS_CATALOG_CONF) + # If there's only one catalog, we assume it's just the old official catalog + # Otherwise, warn the (power-?)users that they should migrate their old catalogs manually + if len(legacy_catalogs) > 1: + logger.warning("It looks like you had additional apps_catalog in the configuration file %s! YunoHost now uses %s instead, but it won't migrate your custom apps_catalog. You should do this manually. The old file has been backuped in %s." % (LEGACY_APPS_CATALOG_CONF, APPS_CATALOG_CONF, LEGACY_APPS_CATALOG_CONF_BACKUP)) + except Exception as e: + logger.warning("Unable to parse the legacy conf %s (error : %s) ... migrating anyway" % (LEGACY_APPS_CATALOG_CONF, str(e))) + + os.rename(LEGACY_APPS_CATALOG_CONF, LEGACY_APPS_CATALOG_CONF_BACKUP) + + _initialize_apps_catalog_system() + _update_apps_catalog() diff --git a/src/yunohost/data_migrations/0013_futureproof_appslist_system.py b/src/yunohost/data_migrations/0013_futureproof_appslist_system.py deleted file mode 100644 index e0bf70d04..000000000 --- a/src/yunohost/data_migrations/0013_futureproof_appslist_system.py +++ /dev/null @@ -1,46 +0,0 @@ - -import os -import shutil - -from moulinette.utils.log import getActionLogger -from moulinette.utils.filesystem import read_json - -from yunohost.tools import Migration -from yunohost.app import (_initialize_appslists_system, - _update_appslist, - APPSLISTS_CACHE, - APPSLISTS_CONF) - -logger = getActionLogger('yunohost.migration') - -LEGACY_APPSLISTS_CONF = '/etc/yunohost/appslists.json' -LEGACY_APPSLISTS_CONF_BACKUP = LEGACY_APPSLISTS_CONF + ".old" - - -class MyMigration(Migration): - - "Migrate to the new future-proof appslist system" - - def migrate(self): - - if not os.path.exists(LEGACY_APPSLISTS_CONF): - logger.info("No need to do anything") - - # Destroy old lecacy cache - if os.path.exists(APPSLISTS_CACHE): - shutil.rmtree(APPSLISTS_CACHE) - - # Backup the legacy file - try: - legacy_list = read_json(LEGACY_APPSLISTS_CONF) - # If there's only one list, we assume it's just the old official list - # Otherwise, warn the (power-?)users that they should migrate their old list manually - if len(legacy_list) > 1: - logger.warning("It looks like you had additional appslist in the configuration file %s! YunoHost now uses %s instead, but it won't migrate your custom appslist. You should do this manually. The old file has been backuped in %s." % (LEGACY_APPSLISTS_CONF, APPSLISTS_CONF, LEGACY_APPSLISTS_CONF_BACKUP)) - except Exception as e: - logger.warning("Unable to parse the legacy conf %s (error : %s) ... migrating anyway" % (LEGACY_APPSLISTS_CONF, str(e))) - - os.rename(LEGACY_APPSLISTS_CONF, LEGACY_APPSLISTS_CONF_BACKUP) - - _initialize_appslists_system() - _update_appslist() diff --git a/src/yunohost/tests/test_appscatalog.py b/src/yunohost/tests/test_appscatalog.py new file mode 100644 index 000000000..450c2846e --- /dev/null +++ b/src/yunohost/tests/test_appscatalog.py @@ -0,0 +1,358 @@ +import os +import pytest +import requests +import requests_mock +import glob +import shutil + +from moulinette import m18n +from moulinette.utils.filesystem import read_json, write_to_json, write_to_yaml, mkdir + +from yunohost.utils.error import YunohostError +from yunohost.app import (_initialize_apps_catalog_system, + _read_apps_catalog_list, + _update_apps_catalog, + _actual_apps_catalog_api_url, + _load_apps_catalog, + logger, + APPS_CATALOG_CACHE, + APPS_CATALOG_CONF, + APPS_CATALOG_CRON_PATH, + APPS_CATALOG_API_VERSION, + APPS_CATALOG_DEFAULT_URL) + +APPS_CATALOG_DEFAULT_URL_FULL = _actual_apps_catalog_api_url(APPS_CATALOG_DEFAULT_URL) +CRON_FOLDER, CRON_NAME = APPS_CATALOG_CRON_PATH.rsplit("/", 1) + +DUMMY_APP_CATALOG = """{ + "foo": {"id": "foo", "level": 4}, + "bar": {"id": "bar", "level": 7} +} +""" + +class AnyStringWith(str): + def __eq__(self, other): + return self in other + +def setup_function(function): + + # Clear apps catalog cache + shutil.rmtree(APPS_CATALOG_CACHE, ignore_errors=True) + + # Clear apps_catalog cron + if os.path.exists(APPS_CATALOG_CRON_PATH): + os.remove(APPS_CATALOG_CRON_PATH) + + # Clear apps_catalog conf + if os.path.exists(APPS_CATALOG_CONF): + os.remove(APPS_CATALOG_CONF) + + +def teardown_function(function): + + # Clear apps catalog cache + # Otherwise when using apps stuff after running the test, + # we'll still have the dummy unusable list + shutil.rmtree(APPS_CATALOG_CACHE, ignore_errors=True) + + +def cron_job_is_there(): + r = os.system("run-parts -v --test %s | grep %s" % (CRON_FOLDER, CRON_NAME)) + return r == 0 + +# +# ################################################ +# + + +def test_apps_catalog_init(mocker): + + # Cache is empty + assert not glob.glob(APPS_CATALOG_CACHE + "/*") + # Conf doesn't exist yet + assert not os.path.exists(APPS_CATALOG_CONF) + # Conf doesn't exist yet + assert not os.path.exists(APPS_CATALOG_CRON_PATH) + + # Initialize ... + mocker.spy(m18n, "n") + _initialize_apps_catalog_system() + m18n.n.assert_any_call('apps_catalog_init_success') + + # Then there's a cron enabled + assert cron_job_is_there() + + # And a conf with at least one list + assert os.path.exists(APPS_CATALOG_CONF) + apps_catalog_list = _read_apps_catalog_list() + assert len(apps_catalog_list) + + # Cache is expected to still be empty though + # (if we did update the apps_catalog during init, + # we couldn't differentiate easily exceptions + # related to lack of network connectivity) + assert not glob.glob(APPS_CATALOG_CACHE + "/*") + + +def test_apps_catalog_emptylist(): + + # Initialize ... + _initialize_apps_catalog_system() + + # Let's imagine somebody removed the default apps catalog because uh idk they dont want to use our default apps catalog + os.system("rm %s" % APPS_CATALOG_CONF) + os.system("touch %s" % APPS_CATALOG_CONF) + + apps_catalog_list = _read_apps_catalog_list() + assert not len(apps_catalog_list) + + +def test_apps_catalog_update_success(mocker): + + # Initialize ... + _initialize_apps_catalog_system() + + # Cache is empty + assert not glob.glob(APPS_CATALOG_CACHE + "/*") + + # Update + with requests_mock.Mocker() as m: + + _actual_apps_catalog_api_url, + # Mock the server response with a dummy apps catalog + m.register_uri("GET", APPS_CATALOG_DEFAULT_URL_FULL, text=DUMMY_APP_CATALOG) + + mocker.spy(m18n, "n") + _update_apps_catalog() + m18n.n.assert_any_call("apps_catalog_updating") + m18n.n.assert_any_call("apps_catalog_update_success") + + # Cache shouldn't be empty anymore empty + assert glob.glob(APPS_CATALOG_CACHE + "/*") + + app_dict = _load_apps_catalog() + assert "foo" in app_dict.keys() + assert "bar" in app_dict.keys() + + +def test_apps_catalog_update_404(mocker): + + # Initialize ... + _initialize_apps_catalog_system() + + with requests_mock.Mocker() as m: + + # 404 error + m.register_uri("GET", APPS_CATALOG_DEFAULT_URL_FULL, + status_code=404) + + with pytest.raises(YunohostError): + mocker.spy(m18n, "n") + _update_apps_catalog() + m18n.n.assert_any_call("apps_catalog_failed_to_download") + +def test_apps_catalog_update_timeout(mocker): + + # Initialize ... + _initialize_apps_catalog_system() + + with requests_mock.Mocker() as m: + + # Timeout + m.register_uri("GET", APPS_CATALOG_DEFAULT_URL_FULL, + exc=requests.exceptions.ConnectTimeout) + + with pytest.raises(YunohostError): + mocker.spy(m18n, "n") + _update_apps_catalog() + m18n.n.assert_any_call("apps_catalog_failed_to_download") + + +def test_apps_catalog_update_sslerror(mocker): + + # Initialize ... + _initialize_apps_catalog_system() + + with requests_mock.Mocker() as m: + + # SSL error + m.register_uri("GET", APPS_CATALOG_DEFAULT_URL_FULL, + exc=requests.exceptions.SSLError) + + with pytest.raises(YunohostError): + mocker.spy(m18n, "n") + _update_apps_catalog() + m18n.n.assert_any_call("apps_catalog_failed_to_download") + + +def test_apps_catalog_update_corrupted(mocker): + + # Initialize ... + _initialize_apps_catalog_system() + + with requests_mock.Mocker() as m: + + # Corrupted json + m.register_uri("GET", APPS_CATALOG_DEFAULT_URL_FULL, + text=DUMMY_APP_CATALOG[:-2]) + + with pytest.raises(YunohostError): + mocker.spy(m18n, "n") + _update_apps_catalog() + m18n.n.assert_any_call("apps_catalog_failed_to_download") + + +def test_apps_catalog_load_with_empty_cache(mocker): + + # Initialize ... + _initialize_apps_catalog_system() + + # Cache is empty + assert not glob.glob(APPS_CATALOG_CACHE + "/*") + + # Update + with requests_mock.Mocker() as m: + + # Mock the server response with a dummy apps catalog + m.register_uri("GET", APPS_CATALOG_DEFAULT_URL_FULL, text=DUMMY_APP_CATALOG) + + # Try to load the apps catalog + # This should implicitly trigger an update in the background + mocker.spy(m18n, "n") + app_dict = _load_apps_catalog() + m18n.n.assert_any_call("apps_catalog_obsolete_cache") + m18n.n.assert_any_call("apps_catalog_update_success") + + + # Cache shouldn't be empty anymore empty + assert glob.glob(APPS_CATALOG_CACHE + "/*") + + assert "foo" in app_dict.keys() + assert "bar" in app_dict.keys() + + +def test_apps_catalog_load_with_conflicts_between_lists(mocker): + + # Initialize ... + _initialize_apps_catalog_system() + + conf = [{"id": "default", "url": APPS_CATALOG_DEFAULT_URL}, + {"id": "default2", "url": APPS_CATALOG_DEFAULT_URL.replace("yunohost.org", "yolohost.org")}] + + write_to_yaml(APPS_CATALOG_CONF, conf) + + # Update + with requests_mock.Mocker() as m: + + # Mock the server response with a dummy apps catalog + # + the same apps catalog for the second list + m.register_uri("GET", APPS_CATALOG_DEFAULT_URL_FULL, text=DUMMY_APP_CATALOG) + m.register_uri("GET", APPS_CATALOG_DEFAULT_URL_FULL.replace("yunohost.org", "yolohost.org"), text=DUMMY_APP_CATALOG) + + # Try to load the apps catalog + # This should implicitly trigger an update in the background + mocker.spy(logger, "warning") + app_dict = _load_apps_catalog() + logger.warning.assert_any_call(AnyStringWith("Duplicate")) + + # Cache shouldn't be empty anymore empty + assert glob.glob(APPS_CATALOG_CACHE + "/*") + + assert "foo" in app_dict.keys() + assert "bar" in app_dict.keys() + + +def test_apps_catalog_load_with_oudated_api_version(mocker): + + # Initialize ... + _initialize_apps_catalog_system() + + # Update + with requests_mock.Mocker() as m: + + mocker.spy(m18n, "n") + m.register_uri("GET", APPS_CATALOG_DEFAULT_URL_FULL, text=DUMMY_APP_CATALOG) + _update_apps_catalog() + + # Cache shouldn't be empty anymore empty + assert glob.glob(APPS_CATALOG_CACHE + "/*") + + # Tweak the cache to replace the from_api_version with a different one + for cache_file in glob.glob(APPS_CATALOG_CACHE + "/*"): + cache_json = read_json(cache_file) + assert cache_json["from_api_version"] == APPS_CATALOG_API_VERSION + cache_json["from_api_version"] = 0 + write_to_json(cache_file, cache_json) + + # Update + with requests_mock.Mocker() as m: + + # Mock the server response with a dummy apps catalog + m.register_uri("GET", APPS_CATALOG_DEFAULT_URL_FULL, text=DUMMY_APP_CATALOG) + + mocker.spy(m18n, "n") + app_dict = _load_apps_catalog() + m18n.n.assert_any_call("apps_catalog_update_success") + + assert "foo" in app_dict.keys() + assert "bar" in app_dict.keys() + + # Check that we indeed have the new api number in cache + for cache_file in glob.glob(APPS_CATALOG_CACHE + "/*"): + cache_json = read_json(cache_file) + assert cache_json["from_api_version"] == APPS_CATALOG_API_VERSION + + + +def test_apps_catalog_migrate_legacy_explicitly(): + + open("/etc/yunohost/appslists.json", "w").write('{"yunohost": {"yolo":"swag"}}') + mkdir(APPS_CATALOG_CACHE, 0o750, parents=True) + open(APPS_CATALOG_CACHE+"/yunohost_old.json", "w").write('{"foo":{}, "bar": {}}') + open(APPS_CATALOG_CRON_PATH, "w").write("# Some old cron") + + from yunohost.tools import _get_migration_by_name + migration = _get_migration_by_name("futureproof_apps_catalog_system") + + with requests_mock.Mocker() as m: + + # Mock the server response with a dummy apps catalog + m.register_uri("GET", APPS_CATALOG_DEFAULT_URL_FULL, text=DUMMY_APP_CATALOG) + migration.migrate() + + # Old conf shouldnt be there anymore (got renamed to .old) + assert not os.path.exists("/etc/yunohost/appslists.json") + # Old cache should have been removed + assert not os.path.exists(APPS_CATALOG_CACHE+"/yunohost_old.json") + # Cron should have been changed + assert "/bin/bash" in open(APPS_CATALOG_CRON_PATH, "r").read() + assert cron_job_is_there() + + # Reading the apps_catalog should work + app_dict = _load_apps_catalog() + assert "foo" in app_dict.keys() + assert "bar" in app_dict.keys() + + +def test_apps_catalog_migrate_legacy_implicitly(): + + open("/etc/yunohost/appslists.json", "w").write('{"yunohost": {"yolo":"swag"}}') + mkdir(APPS_CATALOG_CACHE, 0o750, parents=True) + open(APPS_CATALOG_CACHE+"/yunohost_old.json", "w").write('{"old_foo":{}, "old_bar": {}}') + open(APPS_CATALOG_CRON_PATH, "w").write("# Some old cron") + + with requests_mock.Mocker() as m: + m.register_uri("GET", APPS_CATALOG_DEFAULT_URL_FULL, text=DUMMY_APP_CATALOG) + app_dict = _load_apps_catalog() + + assert "foo" in app_dict.keys() + assert "bar" in app_dict.keys() + + # Old conf shouldnt be there anymore (got renamed to .old) + assert not os.path.exists("/etc/yunohost/appslists.json") + # Old cache should have been removed + assert not os.path.exists(APPS_CATALOG_CACHE+"/yunohost_old.json") + # Cron should have been changed + assert "/bin/bash" in open(APPS_CATALOG_CRON_PATH, "r").read() + assert cron_job_is_there() + diff --git a/src/yunohost/tests/test_appslist.py b/src/yunohost/tests/test_appslist.py deleted file mode 100644 index d7b8e429b..000000000 --- a/src/yunohost/tests/test_appslist.py +++ /dev/null @@ -1,358 +0,0 @@ -import os -import pytest -import requests -import requests_mock -import glob -import shutil - -from moulinette import m18n -from moulinette.utils.filesystem import read_json, write_to_json, write_to_yaml, mkdir - -from yunohost.utils.error import YunohostError -from yunohost.app import (_initialize_appslists_system, - _read_appslist_list, - _update_appslist, - _actual_appslist_api_url, - _load_appslist, - logger, - APPSLISTS_CACHE, - APPSLISTS_CONF, - APPSLISTS_CRON_PATH, - APPSLISTS_API_VERSION, - APPSLISTS_DEFAULT_URL) - -APPSLISTS_DEFAULT_URL_FULL = _actual_appslist_api_url(APPSLISTS_DEFAULT_URL) -CRON_FOLDER, CRON_NAME = APPSLISTS_CRON_PATH.rsplit("/", 1) - -DUMMY_APPLIST = """{ - "foo": {"id": "foo", "level": 4}, - "bar": {"id": "bar", "level": 7} -} -""" - -class AnyStringWith(str): - def __eq__(self, other): - return self in other - -def setup_function(function): - - # Clear applist cache - shutil.rmtree(APPSLISTS_CACHE, ignore_errors=True) - - # Clear appslist cron - if os.path.exists(APPSLISTS_CRON_PATH): - os.remove(APPSLISTS_CRON_PATH) - - # Clear appslist conf - if os.path.exists(APPSLISTS_CONF): - os.remove(APPSLISTS_CONF) - - -def teardown_function(function): - - # Clear applist cache - # Otherwise when using apps stuff after running the test, - # we'll still have the dummy unusable list - shutil.rmtree(APPSLISTS_CACHE, ignore_errors=True) - - -def cron_job_is_there(): - r = os.system("run-parts -v --test %s | grep %s" % (CRON_FOLDER, CRON_NAME)) - return r == 0 - -# -# ################################################ -# - - -def test_appslist_init(mocker): - - # Cache is empty - assert not glob.glob(APPSLISTS_CACHE + "/*") - # Conf doesn't exist yet - assert not os.path.exists(APPSLISTS_CONF) - # Conf doesn't exist yet - assert not os.path.exists(APPSLISTS_CRON_PATH) - - # Initialize ... - mocker.spy(m18n, "n") - _initialize_appslists_system() - m18n.n.assert_any_call('appslist_init_success') - - # Then there's a cron enabled - assert cron_job_is_there() - - # And a conf with at least one list - assert os.path.exists(APPSLISTS_CONF) - appslist_list = _read_appslist_list() - assert len(appslist_list) - - # Cache is expected to still be empty though - # (if we did update the appslist during init, - # we couldn't differentiate easily exceptions - # related to lack of network connectivity) - assert not glob.glob(APPSLISTS_CACHE + "/*") - - -def test_appslist_emptylist(): - - # Initialize ... - _initialize_appslists_system() - - # Let's imagine somebody removed the default applist because uh idk they dont want to use our default applist - os.system("rm %s" % APPSLISTS_CONF) - os.system("touch %s" % APPSLISTS_CONF) - - appslist_list = _read_appslist_list() - assert not len(appslist_list) - - -def test_appslist_update_success(mocker): - - # Initialize ... - _initialize_appslists_system() - - # Cache is empty - assert not glob.glob(APPSLISTS_CACHE + "/*") - - # Update - with requests_mock.Mocker() as m: - - _actual_appslist_api_url, - # Mock the server response with a dummy applist - m.register_uri("GET", APPSLISTS_DEFAULT_URL_FULL, text=DUMMY_APPLIST) - - mocker.spy(m18n, "n") - _update_appslist() - m18n.n.assert_any_call("appslist_updating") - m18n.n.assert_any_call("appslist_update_success") - - # Cache shouldn't be empty anymore empty - assert glob.glob(APPSLISTS_CACHE + "/*") - - app_dict = _load_appslist() - assert "foo" in app_dict.keys() - assert "bar" in app_dict.keys() - - -def test_appslist_update_404(mocker): - - # Initialize ... - _initialize_appslists_system() - - with requests_mock.Mocker() as m: - - # 404 error - m.register_uri("GET", APPSLISTS_DEFAULT_URL_FULL, - status_code=404) - - with pytest.raises(YunohostError): - mocker.spy(m18n, "n") - _update_appslist() - m18n.n.assert_any_call("appslist_failed_to_download") - -def test_appslist_update_timeout(mocker): - - # Initialize ... - _initialize_appslists_system() - - with requests_mock.Mocker() as m: - - # Timeout - m.register_uri("GET", APPSLISTS_DEFAULT_URL_FULL, - exc=requests.exceptions.ConnectTimeout) - - with pytest.raises(YunohostError): - mocker.spy(m18n, "n") - _update_appslist() - m18n.n.assert_any_call("appslist_failed_to_download") - - -def test_appslist_update_sslerror(mocker): - - # Initialize ... - _initialize_appslists_system() - - with requests_mock.Mocker() as m: - - # SSL error - m.register_uri("GET", APPSLISTS_DEFAULT_URL_FULL, - exc=requests.exceptions.SSLError) - - with pytest.raises(YunohostError): - mocker.spy(m18n, "n") - _update_appslist() - m18n.n.assert_any_call("appslist_failed_to_download") - - -def test_appslist_update_corrupted(mocker): - - # Initialize ... - _initialize_appslists_system() - - with requests_mock.Mocker() as m: - - # Corrupted json - m.register_uri("GET", APPSLISTS_DEFAULT_URL_FULL, - text=DUMMY_APPLIST[:-2]) - - with pytest.raises(YunohostError): - mocker.spy(m18n, "n") - _update_appslist() - m18n.n.assert_any_call("appslist_failed_to_download") - - -def test_appslist_load_with_empty_cache(mocker): - - # Initialize ... - _initialize_appslists_system() - - # Cache is empty - assert not glob.glob(APPSLISTS_CACHE + "/*") - - # Update - with requests_mock.Mocker() as m: - - # Mock the server response with a dummy applist - m.register_uri("GET", APPSLISTS_DEFAULT_URL_FULL, text=DUMMY_APPLIST) - - # Try to load the applist - # This should implicitly trigger an update in the background - mocker.spy(m18n, "n") - app_dict = _load_appslist() - m18n.n.assert_any_call("appslist_obsolete_cache") - m18n.n.assert_any_call("appslist_update_success") - - - # Cache shouldn't be empty anymore empty - assert glob.glob(APPSLISTS_CACHE + "/*") - - assert "foo" in app_dict.keys() - assert "bar" in app_dict.keys() - - -def test_appslist_load_with_conflicts_between_lists(mocker): - - # Initialize ... - _initialize_appslists_system() - - conf = [{"id": "default", "url": APPSLISTS_DEFAULT_URL}, - {"id": "default2", "url": APPSLISTS_DEFAULT_URL.replace("yunohost.org", "yolohost.org")}] - - write_to_yaml(APPSLISTS_CONF, conf) - - # Update - with requests_mock.Mocker() as m: - - # Mock the server response with a dummy applist - # + the same applist for the second list - m.register_uri("GET", APPSLISTS_DEFAULT_URL_FULL, text=DUMMY_APPLIST) - m.register_uri("GET", APPSLISTS_DEFAULT_URL_FULL.replace("yunohost.org", "yolohost.org"), text=DUMMY_APPLIST) - - # Try to load the applist - # This should implicitly trigger an update in the background - mocker.spy(logger, "warning") - app_dict = _load_appslist() - logger.warning.assert_any_call(AnyStringWith("Duplicate")) - - # Cache shouldn't be empty anymore empty - assert glob.glob(APPSLISTS_CACHE + "/*") - - assert "foo" in app_dict.keys() - assert "bar" in app_dict.keys() - - -def test_appslist_load_with_oudated_api_version(mocker): - - # Initialize ... - _initialize_appslists_system() - - # Update - with requests_mock.Mocker() as m: - - mocker.spy(m18n, "n") - m.register_uri("GET", APPSLISTS_DEFAULT_URL_FULL, text=DUMMY_APPLIST) - _update_appslist() - - # Cache shouldn't be empty anymore empty - assert glob.glob(APPSLISTS_CACHE + "/*") - - # Tweak the cache to replace the from_api_version with a different one - for cache_file in glob.glob(APPSLISTS_CACHE + "/*"): - cache_json = read_json(cache_file) - assert cache_json["from_api_version"] == APPSLISTS_API_VERSION - cache_json["from_api_version"] = 0 - write_to_json(cache_file, cache_json) - - # Update - with requests_mock.Mocker() as m: - - # Mock the server response with a dummy applist - m.register_uri("GET", APPSLISTS_DEFAULT_URL_FULL, text=DUMMY_APPLIST) - - mocker.spy(m18n, "n") - app_dict = _load_appslist() - m18n.n.assert_any_call("appslist_update_success") - - assert "foo" in app_dict.keys() - assert "bar" in app_dict.keys() - - # Check that we indeed have the new api number in cache - for cache_file in glob.glob(APPSLISTS_CACHE + "/*"): - cache_json = read_json(cache_file) - assert cache_json["from_api_version"] == APPSLISTS_API_VERSION - - - -def test_appslist_migrate_legacy_explicitly(): - - open("/etc/yunohost/appslists.json", "w").write('{"yunohost": {"yolo":"swag"}}') - mkdir(APPSLISTS_CACHE, 0o750, parents=True) - open(APPSLISTS_CACHE+"/yunohost_old.json", "w").write('{"foo":{}, "bar": {}}') - open(APPSLISTS_CRON_PATH, "w").write("# Some old cron") - - from yunohost.tools import _get_migration_by_name - migration = _get_migration_by_name("futureproof_appslist_system") - - with requests_mock.Mocker() as m: - - # Mock the server response with a dummy applist - m.register_uri("GET", APPSLISTS_DEFAULT_URL_FULL, text=DUMMY_APPLIST) - migration.migrate() - - # Old conf shouldnt be there anymore (got renamed to .old) - assert not os.path.exists("/etc/yunohost/appslists.json") - # Old cache should have been removed - assert not os.path.exists(APPSLISTS_CACHE+"/yunohost_old.json") - # Cron should have been changed - assert "/bin/bash" in open(APPSLISTS_CRON_PATH, "r").read() - assert cron_job_is_there() - - # Reading the appslist should work - app_dict = _load_appslist() - assert "foo" in app_dict.keys() - assert "bar" in app_dict.keys() - - -def test_appslist_migrate_legacy_implicitly(): - - open("/etc/yunohost/appslists.json", "w").write('{"yunohost": {"yolo":"swag"}}') - mkdir(APPSLISTS_CACHE, 0o750, parents=True) - open(APPSLISTS_CACHE+"/yunohost_old.json", "w").write('{"old_foo":{}, "old_bar": {}}') - open(APPSLISTS_CRON_PATH, "w").write("# Some old cron") - - with requests_mock.Mocker() as m: - m.register_uri("GET", APPSLISTS_DEFAULT_URL_FULL, text=DUMMY_APPLIST) - app_dict = _load_appslist() - - assert "foo" in app_dict.keys() - assert "bar" in app_dict.keys() - - # Old conf shouldnt be there anymore (got renamed to .old) - assert not os.path.exists("/etc/yunohost/appslists.json") - # Old cache should have been removed - assert not os.path.exists(APPSLISTS_CACHE+"/yunohost_old.json") - # Cron should have been changed - assert "/bin/bash" in open(APPSLISTS_CRON_PATH, "r").read() - assert cron_job_is_there() - diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 6aab198e2..bcd39b0e2 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -37,7 +37,7 @@ from moulinette.utils.log import getActionLogger from moulinette.utils.process import check_output, call_async_output from moulinette.utils.filesystem import read_json, write_to_json, read_yaml, write_to_yaml -from yunohost.app import _update_appslist, app_info, app_upgrade, app_ssowatconf, app_list +from yunohost.app import _update_apps_catalog, app_info, app_upgrade, app_ssowatconf, app_list from yunohost.domain import domain_add, domain_list from yunohost.dyndns import _dyndns_available, _dyndns_provides from yunohost.firewall import firewall_upnp @@ -351,14 +351,14 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False, # Enable UPnP silently and reload firewall firewall_upnp('enable', no_refresh=True) - # Initialize the appslist system - _initialize_appslist_system() + # Initialize the apps catalog system + _initialize_apps_catalog_system() - # Try to update the appslist ... + # Try to update the apps catalog ... # we don't fail miserably if this fails, # because that could be for example an offline installation... try: - _update_appslist() + _update_apps_catalog() except Exception as e: logger.warning(str(e)) @@ -407,7 +407,6 @@ def tools_update(apps=False, system=False): Keyword arguments: system -- Fetch available system packages upgrades (equivalent to apt update) apps -- Fetch the application list to check which apps can be upgraded - appslist -- Just update the application list cache """ # If neither --apps nor --system specified, do both @@ -454,7 +453,7 @@ def tools_update(apps=False, system=False): upgradable_apps = [] if apps: try: - _update_appslist() + _update_apps_catalog() except YunohostError as e: logger.error(str(e)) From 35a7c1db9fd0d7cbd312071a4160c6ad23ab2fdc Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 9 Nov 2019 19:48:48 +0100 Subject: [PATCH 0426/3170] Remove --show-status from app_info in actionsmap as well --- data/actionsmap/yunohost.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 85bc82cf6..b4f969163 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -571,10 +571,6 @@ app: arguments: app: help: Specific app ID - -s: - full: --show-status - help: Show app installation status - action: store_true -r: full: --raw help: Return the full app_dict From 464621f275254d7f956fdcfca8324b6464fa9d51 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Sun, 10 Nov 2019 21:34:57 +0900 Subject: [PATCH 0427/3170] Improve permission helpers --- data/helpers.d/setting | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index 185e6111f..bf4e804ec 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -257,6 +257,7 @@ ynh_webpath_register () { # re:/api/[A-Z]*$ -> domain.tld/app/api/[A-Z]*$ # re:domain.tld/app/api/[A-Z]*$ -> domain.tld/app/api/[A-Z]*$ # +# Requires YunoHost version 3.7.0 or higher. ynh_permission_create() { declare -Ar args_array=( [p]=permission= [u]=url= [a]=allowed= ) local permission @@ -274,7 +275,18 @@ ynh_permission_create() { allowed=",allowed=['${allowed//';'/"','"}']" fi - yunohost tools shell -c "from yunohost.permission import permission_create; permission_create('$app.$permission', url=$url ${allowed:-} , sync_perm=False)" + # Check if permission already exists + if ynh_permission_exists --permission $permission; then + # If permission exits, update it + local add + if [[ -n ${add:-} ]]; then + add="--add ${allowed//';'/" "}" + fi + ynh_exec_warn_less ynh_permission_update --permission $permission ${add:-} + else + # If not, create it + yunohost tools shell -c "from yunohost.permission import permission_create; permission_create('$app.$permission', url=$url ${allowed:-} , sync_perm=False)" + fi } # Remove a permission for the app (note that when the app is removed all permission is automatically removed) @@ -284,6 +296,7 @@ ynh_permission_create() { # usage: ynh_permission_delete --permission "permission" # | arg: permission - the name for the permission (by default a permission named "main" is removed automatically when the app is removed) # +# Requires YunoHost version 3.7.0 or higher. ynh_permission_delete() { declare -Ar args_array=( [p]=permission= ) local permission @@ -292,12 +305,27 @@ ynh_permission_delete() { yunohost tools shell -c "from yunohost.permission import permission_delete; permission_delete('$app.$permission', sync_perm=False)" } +# Check if a permission exists +# +# usage: ynh_permission_exists --permission=permission +# | arg: -p, --permission - the permission to check +# +# Requires YunoHost version 3.7.0 or higher. +ynh_permission_exists() { + declare -Ar args_array=( [p]=permission= ) + local permission + ynh_handle_getopts_args "$@" + + yunohost user permission list -s | grep -w -q "$app.$permission" +} + # Redefine the url associated to a permission # # usage: ynh_permission_url --permission "permission" --url "url" # | arg: permission - the name for the permission (by default a permission named "main" is removed automatically when the app is removed) # | arg: url - (optional) URL for which access will be allowed/forbidden # +# Requires YunoHost version 3.7.0 or higher. ynh_permission_url() { declare -Ar args_array=([p]=permission= [u]=url=) local permission @@ -322,6 +350,7 @@ ynh_permission_url() { # | arg: remove - the list of group or users to remove from the permission # # example: ynh_permission_update --permission admin --add samdoe --remove all_users +# Requires YunoHost version 3.7.0 or higher. ynh_permission_update() { declare -Ar args_array=( [p]=permission= [a]=add= [r]=remove= ) local permission From 59879634da526523e17c27aea138069d1c928b8e Mon Sep 17 00:00:00 2001 From: Kay0u Date: Sun, 10 Nov 2019 22:08:33 +0900 Subject: [PATCH 0428/3170] Fix parameters --- data/helpers.d/setting | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index bf4e804ec..adc318bef 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -271,20 +271,18 @@ ynh_permission_create() { url="None" fi - if [[ -n ${allowed:-} ]]; then - allowed=",allowed=['${allowed//';'/"','"}']" - fi - # Check if permission already exists if ynh_permission_exists --permission $permission; then # If permission exits, update it - local add - if [[ -n ${add:-} ]]; then - add="--add ${allowed//';'/" "}" + if [[ -n ${allowed:-} ]]; then + allowed="--add ${allowed//';'/" "}" fi - ynh_exec_warn_less ynh_permission_update --permission $permission ${add:-} + ynh_exec_warn_less ynh_permission_update --permission $permission ${allowed:-} else # If not, create it + if [[ -n ${allowed:-} ]]; then + allowed=",allowed=['${allowed//';'/"','"}']" + fi yunohost tools shell -c "from yunohost.permission import permission_create; permission_create('$app.$permission', url=$url ${allowed:-} , sync_perm=False)" fi } From 83b4be5345fcb604f8f497ef3b185f99f649292c Mon Sep 17 00:00:00 2001 From: Luke Murphy Date: Fri, 6 Sep 2019 03:30:52 +0200 Subject: [PATCH 0429/3170] Add setting to configure pop3 for dovecot --- data/hooks/conf_regen/25-dovecot | 11 ++++++----- data/templates/dovecot/dovecot.conf | 3 +-- locales/en.json | 1 + src/yunohost/settings.py | 28 ++++++++++++++++++++++++++++ 4 files changed, 36 insertions(+), 7 deletions(-) diff --git a/data/hooks/conf_regen/25-dovecot b/data/hooks/conf_regen/25-dovecot index 4c5ae24c1..d7136df4d 100755 --- a/data/hooks/conf_regen/25-dovecot +++ b/data/hooks/conf_regen/25-dovecot @@ -2,6 +2,8 @@ set -e +. /usr/share/yunohost/helpers + do_pre_regen() { pending_dir=$1 @@ -14,11 +16,10 @@ do_pre_regen() { cp dovecot-ldap.conf "${dovecot_dir}/dovecot-ldap.conf" cp dovecot.sieve "${dovecot_dir}/global_script/dovecot.sieve" - # prepare dovecot.conf conf file - main_domain=$(cat /etc/yunohost/current_host) - cat dovecot.conf \ - | sed "s/{{ main_domain }}/${main_domain}/g" \ - > "${dovecot_dir}/dovecot.conf" + export pop3_enabled="$(yunohost settings get 'pop3.enabled')" + export main_domain=$(cat /etc/yunohost/current_host) + + ynh_render_template "dovecot.conf" "${dovecot_dir}/dovecot.conf" # adapt it for IPv4-only hosts if [ ! -f /proc/net/if_inet6 ]; then diff --git a/data/templates/dovecot/dovecot.conf b/data/templates/dovecot/dovecot.conf index 116bb2db7..477ccbfb1 100644 --- a/data/templates/dovecot/dovecot.conf +++ b/data/templates/dovecot/dovecot.conf @@ -8,11 +8,10 @@ mail_home = /var/mail/%n mail_location = maildir:/var/mail/%n mail_uid = 500 -protocols = imap sieve +protocols = imap sieve {% if pop3_enabled == "True" %}pop3{% endif %} mail_plugins = $mail_plugins quota - ssl = yes ssl_cert = Date: Wed, 11 Sep 2019 01:46:50 +0100 Subject: [PATCH 0430/3170] Fix typos --- src/yunohost/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/settings.py b/src/yunohost/settings.py index df7221db5..59135814c 100644 --- a/src/yunohost/settings.py +++ b/src/yunohost/settings.py @@ -28,7 +28,7 @@ SETTINGS_PATH_OTHER_LOCATION = "/etc/yunohost/settings-%s.json" # * bool # * int # * string -# * enum (in form a python list) +# * enum (in the form of a python list) DEFAULTS = OrderedDict([ ("example.bool", {"type": "bool", "default": True}), From b470c9192c00a6f03456110594c621fc41ea7bb5 Mon Sep 17 00:00:00 2001 From: Luke Murphy Date: Wed, 11 Sep 2019 01:47:16 +0100 Subject: [PATCH 0431/3170] Add "on" to valid boolean and refactor is_true --- src/yunohost/app.py | 4 ++-- src/yunohost/tests/test_settings.py | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index d17ea8424..8c7b441b4 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -2903,9 +2903,9 @@ def is_true(arg): if isinstance(arg, bool): return arg elif isinstance(arg, basestring): - true_list = ['yes', 'Yes', 'true', 'True'] + true_list = ['yes', 'true', 'on'] for string in true_list: - if arg == string: + if arg.lower() == string: return True return False else: diff --git a/src/yunohost/tests/test_settings.py b/src/yunohost/tests/test_settings.py index 0da12597f..ea6c6922c 100644 --- a/src/yunohost/tests/test_settings.py +++ b/src/yunohost/tests/test_settings.py @@ -62,6 +62,8 @@ def test_settings_set(): settings_set("example.bool", False) assert settings_get("example.bool") == False + settings_set("example.bool", "on") + assert settings_get("example.bool") == True def test_settings_set_int(): settings_set("example.int", 21) From 25f270298f51a93068d868af070284f23ab3daa6 Mon Sep 17 00:00:00 2001 From: Luke Murphy Date: Wed, 11 Sep 2019 01:48:08 +0100 Subject: [PATCH 0432/3170] Add is_boolean and use when calling settings_set The CLI always passes the value as a string so we need to manage this type casting step by hand. --- src/yunohost/settings.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/yunohost/settings.py b/src/yunohost/settings.py index 59135814c..c52752fcc 100644 --- a/src/yunohost/settings.py +++ b/src/yunohost/settings.py @@ -15,6 +15,25 @@ logger = getActionLogger('yunohost.settings') SETTINGS_PATH = "/etc/yunohost/settings.json" SETTINGS_PATH_OTHER_LOCATION = "/etc/yunohost/settings-%s.json" +def is_boolean(value): + """ + Ensure a string value is intended as a boolean + + Keyword arguments: + arg -- The string to check + + Returns: + Boolean + + """ + if isinstance(value, bool): + return True + elif isinstance(value, basestring): + return str(value).lower() in ['true', 'on', 'yes', 'false', 'off', 'no'] + else: + return False + + # a settings entry is in the form of: # namespace.subnamespace.name: {type, value, default, description, [choices]} # choices is only for enum @@ -95,7 +114,7 @@ def settings_set(key, value): key_type = settings[key]["type"] if key_type == "bool": - if not isinstance(value, bool): + if not is_boolean(value): raise YunohostError('global_settings_bad_type_for_setting', setting=key, received_type=type(value).__name__, expected_type=key_type) elif key_type == "int": From 4f583aadc20405077dfe0fc31ba2bef419c2d5cc Mon Sep 17 00:00:00 2001 From: Luke Murphy Date: Wed, 11 Sep 2019 01:49:18 +0100 Subject: [PATCH 0433/3170] Remove comment This isn't an issue AFAICT, since the hooks manage this themselves already. Hence, I assume this is an old comment and out of date. --- src/yunohost/settings.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/yunohost/settings.py b/src/yunohost/settings.py index c52752fcc..2fc6915f3 100644 --- a/src/yunohost/settings.py +++ b/src/yunohost/settings.py @@ -147,8 +147,6 @@ def settings_set(key, value): settings[key]["value"] = value _save_settings(settings) - # TODO : whatdo if the old value is the same as - # the new value... try: trigger_post_change_hook(key, old_value, value) except Exception as e: From 74a5979e9128ea14a1a6436a5ca50df3b8dfa54e Mon Sep 17 00:00:00 2001 From: Luke Murphy Date: Tue, 17 Sep 2019 17:14:42 +0200 Subject: [PATCH 0434/3170] Fix typo in copy/pasta'd function --- src/yunohost/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/settings.py b/src/yunohost/settings.py index 2fc6915f3..2427f8677 100644 --- a/src/yunohost/settings.py +++ b/src/yunohost/settings.py @@ -315,7 +315,7 @@ def reconfigure_ssh(setting_name, old_value, new_value): service_regen_conf(names=['ssh']) @post_change_hook("security.postfix.compatibility") -def reconfigure_ssh(setting_name, old_value, new_value): +def reconfigure_postfix(setting_name, old_value, new_value): if old_value != new_value: service_regen_conf(names=['postfix']) From c15311f7e725218d31783e3d9e319fada96968ff Mon Sep 17 00:00:00 2001 From: Luke Murphy Date: Mon, 23 Sep 2019 13:36:50 +0200 Subject: [PATCH 0435/3170] Carry over fixes from @alexAubin Lost during the rebasing but revived! --- src/yunohost/app.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 8c7b441b4..fb233fde5 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -2903,11 +2903,7 @@ def is_true(arg): if isinstance(arg, bool): return arg elif isinstance(arg, basestring): - true_list = ['yes', 'true', 'on'] - for string in true_list: - if arg.lower() == string: - return True - return False + return arg.lower() in ['yes', 'true', 'on'] else: logger.debug('arg should be a boolean or a string, got %r', arg) return True if arg else False From 4b602f28b48ae3f96d372a0f968cf1c886afb4e1 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Mon, 11 Nov 2019 09:56:09 +0900 Subject: [PATCH 0436/3170] fail if permission already exists --- data/helpers.d/setting | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index adc318bef..8046dfab4 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -271,20 +271,11 @@ ynh_permission_create() { url="None" fi - # Check if permission already exists - if ynh_permission_exists --permission $permission; then - # If permission exits, update it - if [[ -n ${allowed:-} ]]; then - allowed="--add ${allowed//';'/" "}" - fi - ynh_exec_warn_less ynh_permission_update --permission $permission ${allowed:-} - else - # If not, create it - if [[ -n ${allowed:-} ]]; then - allowed=",allowed=['${allowed//';'/"','"}']" - fi - yunohost tools shell -c "from yunohost.permission import permission_create; permission_create('$app.$permission', url=$url ${allowed:-} , sync_perm=False)" + if [[ -n ${allowed:-} ]]; then + allowed=",allowed=['${allowed//';'/"','"}']" fi + + yunohost tools shell -c "from yunohost.permission import permission_create; permission_create('$app.$permission', url=$url ${allowed:-} , sync_perm=False)" } # Remove a permission for the app (note that when the app is removed all permission is automatically removed) From cfabb83c0e1abeb10688d6eb3d6beae683f359f2 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 14 Nov 2019 15:40:48 +0100 Subject: [PATCH 0437/3170] Use Yunohost's server instead of tmp server --- data/hooks/diagnosis/14-ports.py | 2 +- data/hooks/diagnosis/16-http.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/hooks/diagnosis/14-ports.py b/data/hooks/diagnosis/14-ports.py index b953f35a9..2da72d83d 100644 --- a/data/hooks/diagnosis/14-ports.py +++ b/data/hooks/diagnosis/14-ports.py @@ -24,7 +24,7 @@ class PortsDiagnoser(Diagnoser): ports = [22, 25, 53, 80, 443, 587, 993, 5222, 5269] try: - r = requests.post('https://ynhdiagnoser.netlib.re/check-ports', json={'ports': ports}, timeout=30).json() + r = requests.post('https://diagnosis.yunohost.org/check-ports', json={'ports': ports}, timeout=30).json() if "status" not in r.keys(): raise Exception("Bad syntax for response ? Raw json: %s" % str(r)) elif r["status"] == "error": diff --git a/data/hooks/diagnosis/16-http.py b/data/hooks/diagnosis/16-http.py index 7ca258628..11d1c33dc 100644 --- a/data/hooks/diagnosis/16-http.py +++ b/data/hooks/diagnosis/16-http.py @@ -28,7 +28,7 @@ class HttpDiagnoser(Diagnoser): os.system("touch /tmp/.well-known/ynh-diagnosis/%s" % nonce) try: - r = requests.post('https://ynhdiagnoser.netlib.re/check-http', json={'domain': domain, "nonce": nonce}, timeout=30).json() + r = requests.post('https://diagnosis.yunohost.org/check-http', json={'domain': domain, "nonce": nonce}, timeout=30).json() if "status" not in r.keys(): raise Exception("Bad syntax for response ? Raw json: %s" % str(r)) elif r["status"] == "error" and ("code" not in r.keys() or r["code"] not in ["error_http_check_connection_error", "error_http_check_unknown_error"]): From 7b6cd6add23002ab36e3ce2536ca26b8836c5c2f Mon Sep 17 00:00:00 2001 From: advocatux Date: Thu, 7 Nov 2019 09:12:30 +0000 Subject: [PATCH 0438/3170] Translated using Weblate (Spanish) Currently translated at 89.0% (540 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/es/ --- locales/es.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/locales/es.json b/locales/es.json index 3cdeebf56..eb4c51973 100644 --- a/locales/es.json +++ b/locales/es.json @@ -638,5 +638,10 @@ "permission_currently_allowed_for_visitors": "Este permiso se concede actualmente a los visitantes además de otros grupos. Probablemente quiere o eliminar el permiso de «visitors» o eliminar los otros grupos a los que está otorgado actualmente.", "permission_currently_allowed_for_all_users": "Este permiso se concede actualmente a todos los usuarios además de los otros grupos. Probablemente quiere o eliminar el permiso de «all_users» o eliminar los otros grupos a los que está otorgado actualmente.", "permission_require_account": "El permiso {permission} solo tiene sentido para usuarios con una cuenta y, por lo tanto, no se puede activar para visitantes.", - "app_remove_after_failed_install": "Eliminando la aplicación tras el fallo de instalación…" + "app_remove_after_failed_install": "Eliminando la aplicación tras el fallo de instalación…", + "diagnosis_basesystem_host": "El servidor está ejecutando Debian {debian_version}.", + "diagnosis_basesystem_kernel": "El servidor está ejecutando el núcleo de Linux {kernel_version}", + "diagnosis_basesystem_ynh_single_version": "{0} versión: {1} ({2})", + "diagnosis_basesystem_ynh_main_version": "El servidor está ejecutando YunoHost {main_version} ({repo})", + "diagnosis_basesystem_ynh_inconsistent_versions": "Está ejecutando versiones incoherentes de los paquetes de YunoHost... probablemente por una actualización errónea o parcial." } From fae69ee319a1a13a209a9f5f8e23e5b3787cb521 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quent=C3=AD?= Date: Sun, 10 Nov 2019 16:49:52 +0000 Subject: [PATCH 0439/3170] Translated using Weblate (Occitan) Currently translated at 39.9% (242 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/oc/ --- locales/oc.json | 84 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 78 insertions(+), 6 deletions(-) diff --git a/locales/oc.json b/locales/oc.json index 49063e829..265012fe4 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -5,7 +5,7 @@ "app_already_installed": "{app:s} es ja installat", "app_already_up_to_date": "{app:s} es ja a jorn", "installation_complete": "Installacion acabada", - "app_id_invalid": "Id d’aplicacion incorrècte", + "app_id_invalid": "ID d’aplicacion incorrècte", "app_install_files_invalid": "Fichièrs d’installacion incorrèctes", "app_no_upgrade": "Pas cap d’aplicacion d’actualizar", "app_not_correctly_installed": "{app:s} sembla pas ben installat", @@ -41,15 +41,15 @@ "backup_archive_name_unknown": "L’archiu local de salvagarda apelat « {name:s} » es desconegut", "action_invalid": "Accion « {action:s} » incorrècta", "app_argument_choice_invalid": "Causida invalida pel paramètre « {name:s} », cal que siá un de {choices:s}", - "app_argument_invalid": "Valor invalida pel paramètre « {name:s} » : {error:s}", + "app_argument_invalid": "Causissètz una valor invalida pel paramètre « {name:s} » : {error:s}", "app_argument_required": "Lo paramètre « {name:s} » es requesit", - "app_change_url_failed_nginx_reload": "La reaviada de nginx a fracassat. Vaquí la sortida de « nginx -t » :\n{nginx_errors:s}", + "app_change_url_failed_nginx_reload": "Reaviada de NGINX impossibla. Vaquí la sortida de « nginx -t » :\n{nginx_errors:s}", "app_change_url_identical_domains": "L’ancian e lo novèl coble domeni/camin son identics per {domain:s}{path:s}, pas res a far.", - "app_change_url_success": "L’URL de l’aplicacion {app:s} a cambiat per {domain:s}{path:s}", + "app_change_url_success": "L’URL de l’aplicacion {app:s} es ara {domain:s}{path:s}", "app_checkurl_is_deprecated": "Packagers /!\\ ’app checkurl’ es obsolèt ! Utilizatz ’app register-url’ a la plaça !", "app_extraction_failed": "Extraccion dels fichièrs d’installacion impossibla", "app_incompatible": "L’aplicacion {app} es pas compatibla amb vòstra version de YunoHost", - "app_location_already_used": "L’aplicacion « {app} » es ja installada a aqueste emplaçament ({path})", + "app_location_already_used": "L’aplicacion « {app} » es ja installada dins ({path})", "app_manifest_invalid": "Manifest d’aplicacion incorrècte : {error}", "app_package_need_update": "Lo paquet de l’aplicacion {app} deu èsser actualizat per poder seguir los cambiaments de YunoHost", "app_requirements_checking": "Verificacion dels paquets requesits per {app}…", @@ -612,5 +612,77 @@ "migrations_must_provide_explicit_targets": "Devètz fornir una cibla explicita quand utilizatz using --skip o --force-rerun", "migrations_exclusive_options": "--auto, --skip, e --force-rerun son las opcions exclusivas.", "migrations_failed_to_load_migration": "Cargament impossible de la migracion {id} : {error}", - "migrations_already_ran": "Aquelas migracions s’executèron ja : {ids}" + "migrations_already_ran": "Aquelas migracions s’executèron ja : {ids}", + "diagnosis_basesystem_ynh_main_version": "Lo servidor fonciona amb YunoHost {main_version} ({repo})", + "migrations_dependencies_not_satisfied": "Executatz aquestas migracions : « {dependencies_id} », abans la migracion {id}.", + "migrations_no_such_migration": "I a pas cap de migracion apelada « {id} »", + "migrations_not_pending_cant_skip": "Aquestas migracions son pas en espèra, las podètz pas doncas ignorar : {ids}", + "app_action_broke_system": "Aquesta accion sembla aver copat de servicis importants : {services}", + "diagnosis_display_tip_web": "Podètz anar a la seccion Diagnostic (dins l’ecran d’acuèlh) per veire los problèmas trobats.", + "diagnosis_ip_no_ipv6": "Lo servidor a pas d’adreça IPv5 activa.", + "diagnosis_ip_not_connected_at_all": "Lo servidor sembla pas connectat a Internet ?!", + "diagnosis_security_all_good": "Cap de vulnerabilitat de seguretat critica pas trobada.", + "diagnosis_description_regenconf": "Configuracion sistèma", + "diagnosis_http_ok": "Lo domeni {domain} accessible de l’exterior.", + "app_full_domain_unavailable": "Aquesta aplicacion a d’èsser installada sul seu pròpri domeni, mas i a d’autras aplicacions installadas sus aqueste domeni « {domain} ». Podètz utilizar allòc un josdomeni dedicat a aquesta aplicacion.", + "app_upgrade_stopped": "L’actualizacion de totas las aplicacions s‘es arrestada per evitar de possibles damatges pr’amor qu’èra pas possible d’actualizar una aplicacion", + "diagnosis_dns_bad_conf": "Configuracion DNS incorrècta o inexistenta pel domeni {domain} (categoria {category})", + "diagnosis_ram_verylow": "Lo sistèma a solament {available_abs_MB} Mo ({available_percent}%) de memòria RAM disponibla ! (d’un total de {total_abs_MB} MB)", + "diagnosis_ram_ok": "Lo sistèma a encara {available_abs_MB} Mo ({available_percent}%) de memòria RAM disponibla d’un total de {total_abs_MB} MB).", + "permission_already_allowed": "Lo grop « {group} » a ja la permission « {permission} » activada", + "permission_already_disallowed": "Lo grop « {group} » a ja la permission « {permission} » desactivada", + "permission_cannot_remove_main": "La supression d’una permission màger es pas autorizada", + "log_permission_url": "Actualizacion de l’URL ligada a la permission « {} »", + "app_install_failed": "Installacion impossibla de {app} : {error}", + "app_install_script_failed": "Una error s’es producha en installar lo script de l’aplicacion", + "migration_0011_failed_to_remove_stale_object": "Supression impossibla d’un objècte obsolèt {dn} : {error}", + "apps_already_up_to_date": "Totas las aplicacions son ja al jorn", + "app_remove_after_failed_install": "Supression de l’aplicacion aprèp fracàs de l’installacion…", + "group_already_exist": "Lo grop {group} existís ja", + "group_already_exist_on_system": "Lo grop {group} existís ja dins lo sistèma de grops", + "group_user_not_in_group": "L’utilizaire {user} es pas dins lo grop {group}", + "log_user_permission_reset": "Restablir la permission « {} »", + "user_already_exists": "L’utilizaire {user} existís ja", + "diagnosis_basesystem_host": "Lo servidor fonciona amb Debian {debian_version}.", + "diagnosis_basesystem_kernel": "Lo servidor fonciona amb lo nuclèu Linuxl {kernel_version}", + "diagnosis_basesystem_ynh_single_version": "{0} version : {1} ({2})", + "diagnosis_basesystem_ynh_inconsistent_versions": "Utilizatz de versions inconsistentas dels paquets de YunoHost… probablament a causa d'una actualizacion fracassada o parciala.", + "diagnosis_display_tip_cli": "Podètz executar « yunohost diagnosis show --issues » per mostrar las errors trobadas.", + "diagnosis_ignored_issues": "(+ {nb_ignored} problèma(es) ignorat(s))", + "diagnosis_everything_ok": "Tot sembla corrècte per {category} !", + "diagnosis_ip_connected_ipv4": "Lo servidor es connectat a Internet via IPv4 !", + "diagnosis_ip_no_ipv4": "Lo servidor a pas d’adreça IPv4 activa.", + "diagnosis_ip_connected_ipv6": "Lo servidor es connectat a Internet via IPv6 !", + "diagnosis_ip_dnsresolution_working": "La resolucion del nom de domeni fonciona !", + "diagnosis_dns_good_conf": "Bona configuracion DNS pel domeni {domain} (categoria {category})", + "diagnosis_failed_for_category": "Lo diagnostic a reüssit per la categoria « {category} » : {error}", + "diagnosis_cache_still_valid": "(Memòria cache totjorn valida pel diagnostic {category}. Cap d’autre diagnostic pel moment !)", + "diagnosis_found_errors": "{errors} errors importantas trobadas ligadas a {category} !", + "diagnosis_services_good_status": "Lo servici {service} es {status} coma previst !", + "diagnosis_services_bad_status": "Lo servici {service} es {status} :/", + "diagnosis_swap_ok": "Lo sistèma a {total_MB} MB d’escambi !", + "diagnosis_regenconf_allgood": "Totes los fichièrs de configuracion son confòrmes a la configuracion recomandada !", + "diagnosis_regenconf_manually_modified": "Lo fichièr de configuracion {file} foguèt modificat manualament.", + "diagnosis_regenconf_manually_modified_details": "Es probablament bon tan que sabètz çò que fasètz ;) !", + "diagnosis_regenconf_nginx_conf_broken": "La configuracion de nginx sembla èsser copada !", + "diagnosis_security_vulnerable_to_meltdown": "Semblatz èsser vulnerable a la vulnerabilitat de seguretat critica de Meltdown", + "diagnosis_description_basesystem": "Sistèma de basa", + "diagnosis_description_ip": "Connectivitat Internet", + "diagnosis_description_dnsrecords": "Enregistraments DNS", + "diagnosis_description_services": "Verificacion d’estat de servicis", + "diagnosis_description_systemresources": "Resorgas sistèma", + "diagnosis_description_ports": "Exposicion dels pòrts", + "diagnosis_description_http": "Exposicion HTTP", + "diagnosis_description_security": "Verificacion de seguretat", + "diagnosis_ports_unreachable": "Lo pòrt {port} es pas accessible de l’exterior.", + "diagnosis_ports_ok": "Lo pòrt {port} es accessible de l’exterior.", + "diagnosis_http_unreachable": "Lo domeni {domain} es pas accessible via HTTP de l’exterior.", + "diagnosis_unknown_categories": "La categorias seguentas son desconegudas : {categories}", + "diagnosis_ram_low": "Lo sistèma a {available_abs_MB} Mo ({available_percent}%) de memòria RAM disponibla d’un total de {total_abs_MB} MB). Atencion.", + "diagnosis_regenconf_manually_modified_debian": "Lo fichier de configuracion {file} foguèt modificat manualament respècte al fichièr per defaut de Debian.", + "log_permission_create": "Crear la permission « {} »", + "log_permission_delete": "Suprimir la permission « {} »", + "log_user_group_create": "Crear lo grop « {} »", + "log_user_permission_update": "Actualizacion dels accèsses per la permission « {} »", + "operation_interrupted": "L’operacion es estada interrompuda manualament ?" } From 74fd219e27b233e1df8478648b30bfbe710d5545 Mon Sep 17 00:00:00 2001 From: xaloc33 Date: Thu, 7 Nov 2019 10:42:27 +0000 Subject: [PATCH 0440/3170] Translated using Weblate (Catalan) Currently translated at 100.0% (607 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ca/ --- locales/ca.json | 71 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 69 insertions(+), 2 deletions(-) diff --git a/locales/ca.json b/locales/ca.json index c0e500a3c..b4e2abaf8 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -162,7 +162,7 @@ "admin_password_too_long": "Trieu una contrasenya de menys de 127 caràcters", "dpkg_is_broken": "No es pot fer això en aquest instant perquè dpkg/APT (els gestors de paquets del sistema) sembla estar mal configurat… Podeu intentar solucionar-ho connectant-vos per SSH i executant «sudo dpkg --configure -a».", "dnsmasq_isnt_installed": "sembla que dnsmasq no està instal·lat, executeu \"apt-get remove bind9 && apt-get install dnsmasq\"", - "domain_cannot_remove_main": "No es pot eliminar el domini principal. S'ha d'establir un nou domini primer", + "domain_cannot_remove_main": "No es pot eliminar «{domain:s}» ja que és el domini principal, primer s'ha d'establir un nou domini principal utilitzant «yunohost domain main-domain -n », aquí hi ha una llista dels possibles dominis: {other_domains:s}", "domain_cert_gen_failed": "No s'ha pogut generar el certificat", "domain_created": "S'ha creat el domini", "domain_creation_failed": "No s'ha pogut crear el domini {domain}: {error}", @@ -627,5 +627,72 @@ "permission_currently_allowed_for_visitors": "El permís ja el tenen el grup de visitants a més d'altres grups. Segurament s'hauria de revocar el permís al grup dels visitants o eliminar els altres grups als que s'ha atribuït.", "permission_currently_allowed_for_all_users": "El permís ha el té el grup de tots els usuaris (all_users) a més d'altres grups. Segurament s'hauria de revocar el permís a «all_users» o eliminar els altres grups als que s'ha atribuït.", "permission_require_account": "El permís {permission} només té sentit per als usuaris que tenen un compte, i per tant no es pot activar per als visitants.", - "app_remove_after_failed_install": "Eliminant l'aplicació després que hagi fallat la instal·lació…" + "app_remove_after_failed_install": "Eliminant l'aplicació després que hagi fallat la instal·lació…", + "diagnosis_basesystem_ynh_main_version": "El servidor funciona amb YunoHost {main_version} ({repo})", + "diagnosis_ram_low": "El sistema només té {available_abs_MB} MB ({available_percent}%) de memòria RAM disponibles d'un total de {total_abs_MB} MB. Aneu amb compte.", + "diagnosis_swap_none": "El sistema no té swap. Hauríeu de considerar afegir un mínim de 256 MB 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_nginx_conf_broken": "Sembla que s'ha trencat la configuració NGINX!", + "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. Error: {error}", + "domain_cannot_remove_main_add_new_one": "No es pot eliminar «{domain:s}» ja que és el domini principal i únic domini, primer s'ha d'afegir un altre domini utilitzant «yunohost domain add », i després fer-lo el domini principal amb «yunohost domain main-domain -n » i després es pot eliminar el domini «{domain:s}» utilitzant «yunohost domain remove {domain:s}».", + "diagnosis_basesystem_host": "El servidor funciona amb Debian {debian_version}.", + "diagnosis_basesystem_kernel": "El servidor funciona amb el nucli de Linux {kernel_version}", + "diagnosis_basesystem_ynh_single_version": "{0} versió: {1}({2})", + "diagnosis_basesystem_ynh_inconsistent_versions": "Esteu utilitzant versions inconsistents dels paquets de YunoHost… probablement a causa d'una actualització fallida o parcial.", + "diagnosis_display_tip_web": "Podeu anar a la secció de Diagnòstics (en la pantalla principal) per veure els errors que s'han trobat.", + "diagnosis_failed_for_category": "Ha fallat el diagnòstic per la categoria «{category}» : {error}", + "diagnosis_display_tip_cli": "Podeu executar «yunohost diagnosis show --issues» per mostrar els errors que s'han trobat.", + "diagnosis_cache_still_valid": "(La memòria cau encara és vàlida pel diagnòstic de {category}. No es tornar a diagnosticar de moment!)", + "diagnosis_cant_run_because_of_dep": "No es pot fer el diagnòstic per {category} mentre hi ha problemes importants relacionats amb {dep}.", + "diagnosis_ignored_issues": "(+ {nb_ignored} problema(es) ignorat(s))", + "diagnosis_found_errors": "S'ha trobat problema(es) important(s) {errors} relacionats amb {category}!", + "diagnosis_found_errors_and_warnings": "S'ha trobat problema(es) important(s) {errors} (i avis(os) {warnings}) relacionats amb {category}!", + "diagnosis_found_warnings": "S'han trobat ítems {warnings} que es podrien millorar per {category}.", + "diagnosis_everything_ok": "Tot sembla correcte per {category}!", + "diagnosis_failed": "No s'han pogut obtenir els resultats del diagnòstic per la categoria «{category}» : {error}", + "diagnosis_ip_connected_ipv4": "El servidor està connectat a Internet amb IPv4!", + "diagnosis_ip_no_ipv4": "El servidor no té una IPv4 que funcioni.", + "diagnosis_ip_connected_ipv6": "El servidor està connectat a Internet amb IPv6!", + "diagnosis_ip_no_ipv6": "El servidor no té una IPv6 que funcioni.", + "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 via /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 de tipus {0}, nom {1} i valor {2}", + "diagnosis_dns_discrepancy": "Segons la configuració DNS recomanada, el valor pel registre DNS de tipus {0} i nom {1} hauria de ser {2}, en comptes de {3}.", + "diagnosis_services_good_status": "El servei {service} està {status} tal i com s'esperava!", + "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_abs_GB} GB ({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_abs_GB} GB ({free_percent}%). Aneu amb compte.", + "diagnosis_diskusage_ok": "El lloc d'emmagatzematge {mountpoint} (en l'aparell {device}) encara té {free_abs_GB} GB ({free_percent}%) lliures!", + "diagnosis_ram_verylow": "El sistema només té {available_abs_MB} MB ({available_percent}%) de memòria RAM disponibles! (d'un total de {total_abs_MB} MB)", + "diagnosis_ram_ok": "El sistema encara té {available_abs_MB} MB ({available_percent}%) de memòria RAM disponibles d'un total de {total_abs_MB} MB.", + "diagnosis_swap_notsomuch": "El sistema només té {total_MB} MB de swap. Hauríeu de considerar tenir un mínim de 256 MB per evitar situacions en les que el sistema es queda sense memòria.", + "diagnosis_swap_ok": "El sistema té {total_MB} MB 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_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.", + "diagnosis_security_vulnerable_to_meltdown": "Sembla que el sistema és vulnerable a la vulnerabilitat de seguretat crítica Meltdown", + "diagnosis_description_basesystem": "Sistema de base", + "diagnosis_description_ip": "Connectivitat a Internet", + "diagnosis_description_dnsrecords": "Registres DNS", + "diagnosis_description_services": "Verificació de l'estat dels serveis", + "diagnosis_description_systemresources": "Recursos del sistema", + "diagnosis_description_ports": "Exposició dels ports", + "diagnosis_description_http": "Exposició HTTP", + "diagnosis_description_regenconf": "Configuració del sistema", + "diagnosis_description_security": "Verificacions de seguretat", + "diagnosis_ports_could_not_diagnose": "No s'ha pogut diagnosticar si els ports són accessibles des de l'exterior. Error: {error}", + "diagnosis_ports_unreachable": "El port {port} no és accessible des de l'exterior.", + "diagnosis_ports_ok": "El port {port} és accessible des de l'exterior.", + "diagnosis_http_ok": "El domini {domain} és accessible des de l'exterior.", + "diagnosis_http_unreachable": "El domini {domain} no és accessible a través de HTTP des de l'exterior.", + "diagnosis_unknown_categories": "Les següents categories són desconegudes: {categories}" } From e0c0eb0bd42f5ad2d37d3c3dc7e5ec334542f1e7 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Thu, 7 Nov 2019 12:48:53 +0000 Subject: [PATCH 0441/3170] Translated using Weblate (French) Currently translated at 96.4% (585 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 51 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/locales/fr.json b/locales/fr.json index 866b24281..da46e367c 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -665,5 +665,54 @@ "app_install_failed": "Impossible d'installer {app}: {error}", "app_install_script_failed": "Une erreur est survenue dans le script d'installation de l'application", "permission_require_account": "Permission {permission} n'a de sens que pour les utilisateurs ayant un compte et ne peut donc pas être activé pour les visiteurs.", - "app_remove_after_failed_install": "Supprimer l'application après l'échec de l'installation…" + "app_remove_after_failed_install": "Supprimer l'application après l'échec de l'installation…", + "diagnosis_display_tip_web": "Vous pouvez aller à la section Diagnostic (dans l'écran d'accueil) pour voir les problèmes rencontrés.", + "diagnosis_cant_run_because_of_dep": "Impossible d'exécuter le diagnostic pour {category} alors qu'il existe des problèmes importants liés à {dep}.", + "diagnosis_found_errors": "Trouvé {errors} problème(s) significatif(s) lié(s) à {category} !", + "diagnosis_found_errors_and_warnings": "Trouvé {errors} problème(s) significatif(s) (et {warnings} (avertissement(s)) en relation avec {category} !", + "diagnosis_ip_not_connected_at_all": "Le serveur ne semble pas du tout connecté à Internet !?", + "diagnosis_ip_weird_resolvconf": "La résolution DNS semble fonctionner, mais soyer prudent en utilisant un fichier /etc/resolv.conf personnalisé.", + "diagnosis_ip_weird_resolvconf_details": "Au lieu de cela, ce fichier devrait être un lien symbolique vers /etc/resolvconf/run/resolv.conf lui-même pointant vers 127.0.0.1 (dnsmasq). Les résolveurs réels doivent être configurés via /etc/resolv.dnsmasq.conf.", + "diagnosis_dns_missing_record": "Selon la configuration DNS recommandée, vous devez ajouter un enregistrement DNS de type {0}, nom {1} et valeur {2}", + "diagnosis_diskusage_ok": "Le stockage {mountpoint} (sur le périphérique {device}) a toujours {espace libre {free_abs_GB} GB ({free_percent}%) !", + "diagnosis_ram_ok": "Le système dispose toujours de {available_abs_MB} MB ({available_percent}%) de RAM sur {total_abs_MB} MB.", + "diagnosis_regenconf_allgood": "Tous les fichiers de configuration sont conformes à la configuration recommandée !", + "diagnosis_security_vulnerable_to_meltdown": "Vous semblez vulnérable à la vulnérabilité de sécurité critique de Meltdown", + "diagnosis_basesystem_host": "Le serveur exécute Debian {debian_version}.", + "diagnosis_basesystem_kernel": "Le serveur exécute le noyau Linux {kernel_version}", + "diagnosis_basesystem_ynh_single_version": "{0} version: {1} ({2})", + "diagnosis_basesystem_ynh_main_version": "Le serveur exécute YunoHost {main_version} ({repo})", + "diagnosis_basesystem_ynh_inconsistent_versions": "Vous exécutez des versions incohérentes des packages YunoHost ... probablement à cause d'une mise à niveau partielle ou échouée.", + "diagnosis_display_tip_cli": "Vous pouvez exécuter 'yunohost diagnosis show --issues' pour afficher les problèmes détectés.", + "diagnosis_failed_for_category": "Échec du diagnostic pour la catégorie '{category}' : {error}", + "diagnosis_cache_still_valid": "(Le cache est toujours valide pour le diagnostic {category}. Pas re-diagnostiquer pour le moment!)", + "diagnosis_ignored_issues": "(+ {nb_ignored} questions ignorée(s))", + "diagnosis_found_warnings": "Trouvé {warnings} objet(s) pouvant être amélioré(s) pour {category}.", + "diagnosis_everything_ok": "Tout semble bien pour {category} !", + "diagnosis_failed": "Impossible d'extraire le résultat du diagnostic pour la catégorie '{category}': {error}", + "diagnosis_ip_connected_ipv4": "Le serveur est connecté à Internet via IPv4 !", + "diagnosis_ip_no_ipv4": "Le serveur ne dispose pas d’une adresse IPv4 active.", + "diagnosis_ip_connected_ipv6": "Le serveur est connecté à Internet via IPv6 !", + "diagnosis_ip_no_ipv6": "Le serveur ne dispose pas d'une adresse IPv6 active.", + "diagnosis_ip_dnsresolution_working": "La résolution de nom de domaine fonctionne !", + "diagnosis_ip_broken_dnsresolution": "La résolution du nom de domaine semble interrompue pour une raison quelconque ... Un pare-feu bloque-t-il les requêtes DNS ?", + "diagnosis_ip_broken_resolvconf": "La résolution du nom de domaine semble cassée sur votre serveur, ce qui semble lié au fait que /etc/resolv.conf ne pointe pas vers 127.0.0.1.", + "diagnosis_dns_good_conf": "Bonne configuration DNS pour le domaine {domain} (catégorie {category})", + "diagnosis_dns_bad_conf": "Configuration DNS incorrecte/manquante pour le domaine {domain} (catégorie {category})", + "diagnosis_dns_discrepancy": "Selon la configuration DNS recommandée, la valeur de l'enregistrement DNS de type {0} et nom {1} doit être {2} et non {3}.", + "diagnosis_services_bad_status": "Le service {service} est {status} :/", + "diagnosis_services_good_status": "Le service {service} est {status} comme prévu !", + "diagnosis_diskusage_verylow": "Le stockage {mountpoint} (sur le périphérique {device}) ne dispose que de {free_abs_GB} Go ({free_percent}%). Vous devriez vraiment envisager de nettoyer un peu d'espace.", + "diagnosis_diskusage_low": "Le stockage {mountpoint} (sur le périphérique {device}) ne dispose que de {free_abs_GB} Go ({free_percent}%). Faites attention.", + "diagnosis_ram_verylow": "Le système ne dispose plus que de {available_abs_MB} MB ({available_percent}%)! (sur {total_abs_MB} Mo)", + "diagnosis_ram_low": "Le système n'a plus de {available_abs_MB} MB ({available_percent}%) RAM sur {total_abs_MB} MB. Faites attention.", + "diagnosis_swap_none": "Le système n'a aucun échange. Vous devez envisager d’ajouter au moins 256 Mo de swap pour éviter les situations où le système manque de mémoire.", + "diagnosis_swap_notsomuch": "Le système ne dispose que de {total_MB} Mo de swap. Vous devez envisager d'avoir au moins 256 Mo pour éviter les situations où le système manque de mémoire.", + "diagnosis_swap_ok": "Le système dispose de {total_MB} Mo de swap !", + "diagnosis_regenconf_manually_modified": "Le fichier de configuration {file} a été modifié manuellement.", + "diagnosis_regenconf_manually_modified_debian": "Le fichier de configuration {file} a été modifié manuellement par rapport à celui par défaut de Debian.", + "diagnosis_regenconf_manually_modified_details": "C'est probablement OK tant que vous savez ce que vous faites;) !", + "diagnosis_regenconf_manually_modified_debian_details": "Cela peut probablement être OK, mais il faut garder un œil dessus ...", + "diagnosis_regenconf_nginx_conf_broken": "La configuration de nginx semble être cassée !", + "diagnosis_security_all_good": "Aucune vulnérabilité de sécurité critique n'a été trouvée." } From 92715693a148e07d03bd72dfbaf5876ccf26ef57 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Wed, 13 Nov 2019 21:00:09 +0000 Subject: [PATCH 0442/3170] Translated using Weblate (Esperanto) Currently translated at 83.9% (506 of 603 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/eo/ --- locales/eo.json | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/locales/eo.json b/locales/eo.json index a25fa505e..7fe36db64 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -564,5 +564,16 @@ "permission_currently_allowed_for_all_users": "Ĉi tiu permeso estas nuntempe donita al ĉiuj uzantoj aldone al aliaj grupoj. Vi probable volas aŭ forigi la permeson \"all_users\" aŭ forigi la aliajn grupojn, kiujn ĝi nuntempe donas.", "app_install_failed": "Ne povis instali {app} : {error}", "app_install_script_failed": "Eraro okazis en la skripto de instalado de la app", - "app_remove_after_failed_install": "Forigado de la app post la instala fiasko …" + "app_remove_after_failed_install": "Forigado de la app post la instala fiasko …", + "diagnosis_basesystem_host": "Servilo funkcias Debian {debian_version}.", + "apps_catalog_init_success": "Aplikoj katalogsistemo inicializita !", + "apps_catalog_updating": "Ĝisdatigante katalogo de aplikoj ...", + "apps_catalog_failed_to_download": "Ne eblas elŝuti la katalogon de {apps_catalog}: {error}", + "apps_catalog_obsolete_cache": "La kaŝmemoro de la katalogo de programoj estas malplena aŭ malaktuala.", + "apps_catalog_update_success": "La aplika katalogo estis ĝisdatigita!", + "diagnosis_basesystem_kernel": "Servilo funkcias Linuksan kernon {kernel_version}", + "diagnosis_basesystem_ynh_single_version": "{0} versio: {1} ({2})", + "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." } From e95d8a10ab33aea606b4f8525bec32b47d6c67db Mon Sep 17 00:00:00 2001 From: xaloc33 Date: Wed, 13 Nov 2019 22:44:19 +0000 Subject: [PATCH 0443/3170] Translated using Weblate (Catalan) Currently translated at 100.0% (603 of 603 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ca/ --- locales/ca.json | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/locales/ca.json b/locales/ca.json index b4e2abaf8..91aaa8667 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -308,7 +308,7 @@ "migration_description_0007_ssh_conf_managed_by_yunohost_step1": "La configuració SSH serà gestionada per YunoHost (pas 1, automàtic)", "migration_description_0008_ssh_conf_managed_by_yunohost_step2": "La configuració SSH serà gestionada per YunoHost (pas 2, manual)", "migration_description_0009_decouple_regenconf_from_services": "Desvincula el mecanisme regen-conf dels serveis", - "migration_description_0010_migrate_to_apps_json": "Elimina les llistes d'aplicacions obsoletes i utilitza la nova llista unificada «apps.json» en el seu lloc", + "migration_description_0010_migrate_to_apps_json": "Elimina els catàlegs d'aplicacions obsolets i utilitza la nova llista unificada «apps.json» en el seu lloc (obsolet, substituït per la migració 13)", "migration_0003_backward_impossible": "La migració Stretch no és reversible.", "migration_0003_start": "Ha començat la migració a Stretch. Els registres estaran disponibles a {logfile}.", "migration_0003_patching_sources_list": "Modificant el fitxer sources.lists…", @@ -320,7 +320,7 @@ "migration_0003_system_not_fully_up_to_date": "El vostre sistema no està completament actualitzat. S'ha de fer una actualització normal abans de fer la migració a Stretch.", "migration_0003_still_on_jessie_after_main_upgrade": "Hi ha hagut un problema durant l'actualització principal: El sistema encara està amb Jessie? Per investigar el problema, mireu el registres a {log}:s…", "migration_0003_general_warning": "Tingueu en compte que la migració és una operació delicada. L'equip de YunoHost a fet els possibles per revisar-la i provar-la, però la migració pot provocar errors en parts del sistema o aplicacions.\n\nPer tant, es recomana:\n - Fer una còpia de seguretat de les dades o aplicacions importants. Més informació a https://yunohost.org/backup;\n - Sigueu pacient un cop llençada la migració: en funció de la connexió a internet i el maquinari, pot trigar fins a unes hores per actualitzar-ho tot.\n\nD'altra banda, el port per SMTP, utilitzat per clients de correu externs (com Thunderbird o K9-Mail) ha canviat de 465 (SSL/TLS) a 587 (STARTTLS). L'antic port (465) serà tancat automàticament, i el nou port (587) serà obert en el tallafocs. Tots els usuaris *hauran* d'adaptar la configuració dels clients de correu en acord amb aquests canvis.", - "migration_0003_problematic_apps_warning": "Tingueu en compte que s'han detectat les aplicacions, possiblement, problemàtiques següents. Sembla que aquestes no s'han instal·lat des d'una applist, o que no estan marcades com a «working». Per conseqüent, no podem garantir que segueixin funcionant després de l'actualització: {problematic_apps}", + "migration_0003_problematic_apps_warning": "Tingueu en compte que s'han detectat les aplicacions, possiblement, problemàtiques següents. Sembla que aquestes no s'han instal·lat des d'un catàleg d'aplicacions, o que no estan marcades com a «working». Per conseqüent, no podem garantir que segueixin funcionant després de l'actualització: {problematic_apps}", "migration_0003_modified_files": "Tingueu en compte que s'han detectat els següents fitxers que han estat modificats manualment i podrien sobreescriure's al final de l'actualització: {manually_modified_files}", "migration_0005_postgresql_94_not_installed": "PostgreSQL no està instal·lat en el sistema. No hi ha res per fer.", "migration_0005_postgresql_96_not_installed": "PostgreSQL 9.4 està instal·lat, però no PostgreSQL 9.6? Alguna cosa estranya a passat en el sistema :( …", @@ -694,5 +694,14 @@ "diagnosis_ports_ok": "El port {port} és accessible des de l'exterior.", "diagnosis_http_ok": "El domini {domain} és accessible des de l'exterior.", "diagnosis_http_unreachable": "El domini {domain} no és accessible a través de HTTP des de l'exterior.", - "diagnosis_unknown_categories": "Les següents categories són desconegudes: {categories}" + "diagnosis_unknown_categories": "Les següents categories són desconegudes: {categories}", + "apps_catalog_init_success": "S'ha iniciat el sistema de catàleg d'aplicacions!", + "apps_catalog_updating": "S'està actualitzant el catàleg d'aplicacions…", + "apps_catalog_failed_to_download": "No s'ha pogut descarregar el catàleg d'aplicacions {apps_catalog}: {error}", + "apps_catalog_obsolete_cache": "La memòria cau del catàleg d'aplicacions és buida o obsoleta.", + "apps_catalog_update_success": "S'ha actualitzat el catàleg d'aplicacions!", + "diagnosis_mail_ougoing_port_25_ok": "El port de sortida 25 no està bloquejat i els correus es poden enviar a altres servidors.", + "diagnosis_mail_ougoing_port_25_blocked": "Sembla que el port de sortida 25 està bloquejat. Hauríeu d'intentar desbloquejar-lo al panell de configuració del proveïdor d'accés a internet (o allotjador). Mentrestant, el servidor no podrà enviar correus a altres servidors.", + "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" } From d21b6d084fce653fe526843d760161bd7a37e4b0 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Wed, 13 Nov 2019 20:18:22 +0000 Subject: [PATCH 0444/3170] Translated using Weblate (French) Currently translated at 100.0% (603 of 603 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 39 +++++++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index da46e367c..bf2c305c6 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -77,7 +77,7 @@ "domain_created": "Le domaine a été créé", "domain_creation_failed": "Impossible de créer le domaine {domain}: {error}", "domain_deleted": "Le domaine a été supprimé", - "domain_deletion_failed": "Impossible de supprimer le domaine {domain}: {error}", + "domain_deletion_failed": "Impossible de supprimer le domaine {domain}:{error}", "domain_dyndns_already_subscribed": "Vous avez déjà souscris à un domaine DynDNS", "domain_dyndns_invalid": "Domaine incorrect pour un usage avec DynDNS", "domain_dyndns_root_unknown": "Domaine DynDNS principal inconnu", @@ -270,7 +270,7 @@ "ldap_init_failed_to_create_admin": "L’initialisation de l'annuaire LDAP n’a pas réussi à créer l’utilisateur admin", "ssowat_persistent_conf_read_error": "Impossible de lire la configuration persistante de SSOwat : {error:s}. Modifiez le fichier /etc/ssowat/conf.json.persistent pour réparer la syntaxe JSON", "ssowat_persistent_conf_write_error": "Impossible de sauvegarder de la configuration persistante de SSOwat : {error:s}. Modifiez le fichier /etc/ssowat/conf.json.persistent pour réparer la syntaxe JSON", - "domain_cannot_remove_main": "Impossible de supprimer le domaine principal. Définissez d'abord un nouveau domaine principal", + "domain_cannot_remove_main": "Vous ne pouvez pas supprimer '{domain: s}' car il s'agit du domaine principal. Vous devez d'abord définir un autre domaine comme domaine principal à l'aide de 'yunohost domain main-domain -n ', voici la liste des domaines candidats. : {other_domains: s}", "certmanager_self_ca_conf_file_not_found": "Le fichier de configuration pour l’autorité du certificat auto-signé est introuvable (fichier : {file:s})", "certmanager_unable_to_parse_self_CA_name": "Impossible d’analyser le nom de l’autorité du certificat auto-signé (fichier : {file:s})", "mailbox_used_space_dovecot_down": "Le service de courriel Dovecot doit être démarré, si vous souhaitez voir l’espace disque occupé par la messagerie", @@ -394,7 +394,7 @@ "migration_0003_system_not_fully_up_to_date": "Votre système n’est pas complètement à jour. Veuillez mener une mise à jour classique avant de lancer à migration à Stretch.", "migration_0003_still_on_jessie_after_main_upgrade": "Quelque chose s’est mal passé pendant la mise à niveau principale : le système est toujours sur Debian Jessie !? Pour investiguer sur le problème, veuillez regarder les journaux {log}:s …", "migration_0003_general_warning": "Veuillez noter que cette migration est une opération délicate. Si l’équipe YunoHost a fait de son mieux pour la relire et la tester, la migration pourrait tout de même casser des parties de votre système ou de vos applications.\n\nEn conséquence, nous vous recommandons :\n - de lancer une sauvegarde de vos données ou applications critiques. Plus d’informations sur https://yunohost.org/backup ;\n - d’être patient après avoir lancé la migration : selon votre connexion internet et matériel, cela pourrait prendre jusqu’à quelques heures pour que tout soit à niveau.\n\nEn outre, le port SMTP utilisé par les clients de messagerie externes comme (Thunderbird ou K9-Mail) a été changé de 465 (SSL/TLS) à 587 (STARTTLS). L’ancien port 465 sera automatiquement fermé et le nouveau port 587 sera ouvert dans le pare-feu. Vous et vos utilisateurs *devront* adapter la configuration de vos clients de messagerie en conséquence.", - "migration_0003_problematic_apps_warning": "Veuillez noter que des applications possiblement problématiques ont été détectées. Il semble qu’elles n’aient pas été installées depuis une liste d’application ou qu’elles ne soit pas marquées comme « fonctionnelles ». En conséquence, nous ne pouvons pas garantir qu’elles fonctionneront après la mise à niveau : {problematic_apps}", + "migration_0003_problematic_apps_warning": "Veuillez noter que les applications installées potentiellement problématiques suivantes ont été détectées. Il semble que celles-ci n'ont pas été installées à partir d'un catalogue d'applications, ou ne sont pas marquées comme \"working \". Par conséquent, il ne peut pas être garanti qu'ils fonctionneront toujours après la mise à niveau: {problematic_apps}", "migration_0003_modified_files": "Veuillez noter que les fichiers suivants ont été détectés comme modifiés manuellement et pourraient être écrasés à la fin de la mise à niveau : {manually_modified_files}", "migrations_list_conflict_pending_done": "Vous ne pouvez pas utiliser --previous et --done simultanément.", "migrations_to_be_ran_manually": "La migration {id} doit être lancée manuellement. Veuillez aller dans Outils > Migrations dans l’interface admin, ou lancer `yunohost tools migrations migrate`.", @@ -541,7 +541,7 @@ "global_settings_setting_security_ssh_compatibility": "Compatibilité versus compromis sécuritaire pour le serveur SSH. Affecte les cryptogrammes (et d'autres aspects liés à la sécurité)", "global_settings_setting_security_postfix_compatibility": "Compatibilité versus compromis sécuritaire pour le serveur Postfix. Affecte les cryptogrammes (et d'autres aspects liés à la sécurité)", "migration_description_0009_decouple_regenconf_from_services": "Dissocier le mécanisme « regen-conf » des services", - "migration_description_0010_migrate_to_apps_json": "Supprimer les listes d'applications obsolètes et utiliser la nouvelle liste unifiée 'apps.json' à la place", + "migration_description_0010_migrate_to_apps_json": "Supprimez les catalogues d'applications obsolètes et utilisez à la place la nouvelle liste unifiée 'apps.json' (obsolète, remplacée par la migration 13).", "regenconf_file_kept_back": "Le fichier de configuration '{conf}' devait être supprimé par « regen-conf » (catégorie {category}) mais a été conservé.", "regenconf_updated": "La configuration a été mise à jour pour la catégorie '{category}'", "regenconf_would_be_updated": "La configuration aurait dû être mise à jour pour la catégorie '{category}'", @@ -625,7 +625,7 @@ "migrations_running_forward": "Exécution de la migration {id}…", "migrations_success_forward": "Migration {id} terminée", "need_define_permission_before": "Redéfinissez l'autorisation à l'aide de 'yunohost user permission add -u USER' avant de supprimer un groupe autorisé", - "operation_interrupted": "L'opération a-t-elle été interrompue manuellement ?", + "operation_interrupted": "L'opération a été interrompue manuellement ?", "permission_already_clear": "L'autorisation '{permission: s}' est déjà vide pour l'application {app: s}", "permission_already_exist": "L'autorisation '{permission}' existe déjà", "permission_created": "Permission '{permission:s}' créée", @@ -714,5 +714,32 @@ "diagnosis_regenconf_manually_modified_details": "C'est probablement OK tant que vous savez ce que vous faites;) !", "diagnosis_regenconf_manually_modified_debian_details": "Cela peut probablement être OK, mais il faut garder un œil dessus ...", "diagnosis_regenconf_nginx_conf_broken": "La configuration de nginx semble être cassée !", - "diagnosis_security_all_good": "Aucune vulnérabilité de sécurité critique n'a été trouvée." + "diagnosis_security_all_good": "Aucune vulnérabilité de sécurité critique n'a été trouvée.", + "apps_catalog_init_success": "Système de catalogue d'applications initialisé !", + "apps_catalog_failed_to_download": "Impossible de télécharger le catalogue des applications {apps_catalog}:{error}", + "diagnosis_mail_ougoing_port_25_blocked": "Le port sortant 25 semble être bloqué. Vous devriez essayer de le débloquer dans le panneau de configuration de votre fournisseur de services Internet (ou hébergeur). En attendant, le serveur ne pourra pas envoyer de courrier électronique à d'autres serveurs.", + "domain_cannot_remove_main_add_new_one": "Vous ne pouvez pas supprimer '{domain:s}' car il s'agit du domaine principal et de votre seul domaine. Vous devez d'abord ajouter un autre domaine à l'aide de 'yunohost domain add ', puis définir comme domaine principal à l'aide de ' yunohost domain main-domain -n ' et vous pouvez ensuite supprimer le domaine '{domaine: s}' à l'aide de 'yunohost domain remove {domain:s}'.'", + "diagnosis_security_vulnerable_to_meltdown_details": "Pour résoudre ce problème, vous devez mettre à niveau votre système et redémarrer pour charger le nouveau noyau Linux (ou contacter votre fournisseur de serveur si cela ne fonctionne pas). Voir https://meltdownattack.com/ pour plus d'informations.", + "diagnosis_description_basesystem": "Système de base", + "diagnosis_description_ip": "Connectivité Internet", + "diagnosis_description_dnsrecords": "Enregistrements DNS", + "diagnosis_description_services": "Vérification de l'état des services", + "diagnosis_description_systemresources": "Ressources système", + "diagnosis_description_ports": "Exposition des ports", + "diagnosis_description_http": "Exposition HTTP", + "diagnosis_description_regenconf": "Configurations système", + "diagnosis_description_security": "Contrôles de sécurité", + "diagnosis_ports_could_not_diagnose": "Impossible de diagnostiquer si les ports sont accessibles de l'extérieur. Erreur: {error}", + "apps_catalog_updating": "Mise à jour du catalogue d'applications...", + "apps_catalog_obsolete_cache": "Le cache du catalogue d'applications est vide ou obsolète.", + "apps_catalog_update_success": "Le catalogue des applications a été mis à jour !", + "diagnosis_mail_ougoing_port_25_ok": "Le port sortant 25 n'est pas bloqué et le courrier électronique peut être envoyé à d'autres serveurs.", + "diagnosis_description_mail": "Email", + "diagnosis_ports_unreachable": "Le port {port} n'est pas accessible de l'extérieur.", + "diagnosis_ports_ok": "Le port {port} est accessible de l'extérieur.", + "diagnosis_http_could_not_diagnose": "Impossible de diagnostiquer si le domaine est accessible de l'extérieur. Erreur: {error}", + "diagnosis_http_ok": "Le domaine {domain} est accessible de l'extérieur.", + "diagnosis_http_unreachable": "Le domaine {domain} est inaccessible via HTTP de l'extérieur.", + "diagnosis_unknown_categories": "Les catégories suivantes sont inconnues: {categories}", + "migration_description_0013_futureproof_apps_catalog_system": "Migrer vers le nouveau système de catalogue d'applications à l'épreuve du temps" } From 6c570295be63ba8441b048248c9f36af8179a9cb Mon Sep 17 00:00:00 2001 From: advocatux Date: Wed, 13 Nov 2019 12:16:01 +0000 Subject: [PATCH 0445/3170] Translated using Weblate (Spanish) Currently translated at 89.6% (540 of 603 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/es/ --- locales/es.json | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/locales/es.json b/locales/es.json index eb4c51973..d1daf16f7 100644 --- a/locales/es.json +++ b/locales/es.json @@ -643,5 +643,20 @@ "diagnosis_basesystem_kernel": "El servidor está ejecutando el núcleo de Linux {kernel_version}", "diagnosis_basesystem_ynh_single_version": "{0} versión: {1} ({2})", "diagnosis_basesystem_ynh_main_version": "El servidor está ejecutando YunoHost {main_version} ({repo})", - "diagnosis_basesystem_ynh_inconsistent_versions": "Está ejecutando versiones incoherentes de los paquetes de YunoHost... probablemente por una actualización errónea o parcial." + "diagnosis_basesystem_ynh_inconsistent_versions": "Está ejecutando versiones incoherentes de los paquetes de YunoHost... probablemente por una actualización errónea o parcial.", + "diagnosis_failed_for_category": "Diagnóstico fallido para la categoría «{category}» : {error}", + "diagnosis_cache_still_valid": "(Caché aún válida para el diagnóstico de {category}. ¡Aún no se ha rediagnosticado!)", + "diagnosis_found_errors_and_warnings": "¡Encontrado(s) error(es) significativo(s) {errors} (y aviso(s) {warnings}) relacionado(s) con {category}!", + "diagnosis_display_tip_web": "Puede ir a la sección de diagnóstico (en la pantalla principal) para ver los problemas encontrados.", + "diagnosis_display_tip_cli": "Puede ejecutar «yunohost diagnosis show --issues» para mostrar los problemas encontrados.", + "apps_catalog_init_success": "¡Sistema de catálogo de aplicaciones inicializado!", + "apps_catalog_updating": "Actualizando catálogo de aplicaciones...", + "apps_catalog_failed_to_download": "No se pudo descargar el catálogo de aplicaciones {apps_catalog}: {error}", + "apps_catalog_obsolete_cache": "La caché del catálogo de aplicaciones está vacía u obsoleta.", + "apps_catalog_update_success": "¡El catálogo de aplicaciones ha sido actualizado!", + "diagnosis_cant_run_because_of_dep": "No se puede ejecutar el diagnóstico para {category} mientras haya problemas importantes relacionados con {dep}.", + "diagnosis_ignored_issues": "(+ {nb_ignored} problema(s) ignorado(s))", + "diagnosis_found_errors": "¡Encontrado(s) error(es) significativo(s) {errors} relacionado(s) con {category}!", + "diagnosis_found_warnings": "Encontrado elemento(s) {warnings} que puede(n) ser mejorado(s) para {category}.", + "diagnosis_everything_ok": "¡Todo se ve bien para {category}!" } From e70530a9dbe5ebb43e24134b3b24cd49906b9266 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 14 Nov 2019 17:08:10 +0100 Subject: [PATCH 0446/3170] Remove deprecated / useless option from 'service add', and add new ones for status and conf check and port exposure --- data/actionsmap/yunohost.yml | 33 ++++++++++++++++++------------- src/yunohost/service.py | 38 +++++++++++++++++++++--------------- 2 files changed, 41 insertions(+), 30 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index f067cc3c3..887c7f9d6 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1038,24 +1038,13 @@ service: arguments: name: help: Service name to add - -s: - full: --status - help: Custom status command + -d: + full: --description + help: Description of the service -l: full: --log help: Absolute path to log file to display nargs: "+" - -r: - full: --runlevel - help: Runlevel priority of the service - type: int - -n: - full: --need_lock - help: Use this option to prevent deadlocks if the service does invoke yunohost commands. - action: store_true - -d: - full: --description - help: Description of the service -t: full: --log_type help: Type of the log (file or systemd) @@ -1064,6 +1053,22 @@ service: - 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: + help: Specify a custom bash command to check if the configuration of the service is valid or broken, similar to nginx -t. + --needs_exposed_ports: + help: A list of ports that needs to be publicly exposed for the service to work as intended. + nargs: "+" + type: int + metavar: PORT + -n: + full: --need_lock + help: Use this option to prevent deadlocks if the service does invoke yunohost commands. + action: store_true + -s: + full: --status + help: Deprecated, old option. Does nothing anymore. Possibly check the --test_status option. ### service_remove() remove: diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 612e73f6c..0125fd7e4 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -40,25 +40,24 @@ MOULINETTE_LOCK = "/var/run/moulinette_yunohost.lock" logger = log.getActionLogger('yunohost.service') -def service_add(name, status=None, log=None, runlevel=None, need_lock=False, description=None, log_type="file"): +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): """ Add a custom service Keyword argument: name -- Service name to add - status -- Custom status command - log -- Absolute path to log file to display - runlevel -- Runlevel priority of the service - need_lock -- Use this option to prevent deadlocks if the service does invoke yunohost commands. description -- description of the service - log_type -- Precise if the corresponding log is a file or a systemd log + log -- Absolute path to log file to display + log_type -- 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. + need_lock -- Use this option to prevent deadlocks if the service does invoke yunohost commands. + status -- Deprecated, doesn't do anything anymore. Use test_status instead. """ services = _get_services() - if not status: - services[name] = {'status': 'service'} - else: - services[name] = {'status': status} + services[name] = {} if log is not None: if not isinstance(log, list): @@ -77,15 +76,22 @@ def service_add(name, status=None, log=None, runlevel=None, need_lock=False, des else: raise YunohostError('service_add_failed', service=name) - - if runlevel is not None: - services[name]['runlevel'] = runlevel + if description: + services[name]['description'] = description + else: + logger.warning("/!\\ Packager ! You added a custom service without specifying a description. Please add --description to explain what the service does in a similar fashion to existing services.") if need_lock: services[name]['need_lock'] = True - if description is not None: - services[name]['description'] = description + if test_status: + services[name]["test_status"] = test_status + + if test_conf: + services[name]["test_conf"] = test_conf + + if needs_exposed_ports: + services[name]["needs_exposed_ports"] = needs_exposed_ports try: _save_services(services) @@ -277,7 +283,7 @@ 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 "status" in services[name] and services[name]["status"] is None: + if services[name].get("status", "") is None: continue status = _get_service_information_from_systemd(name) From 0beec2e0cb338597dbdecd8d82c43689c3c14145 Mon Sep 17 00:00:00 2001 From: Kayou Date: Thu, 7 Nov 2019 22:32:24 +0900 Subject: [PATCH 0447/3170] Add arg in permission callback --- src/yunohost/permission.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index c53804d49..7866e7277 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -179,6 +179,7 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, # Trigger app callbacks app = permission.split(".")[0] + sub_permission = permission.split(".")[1] old_allowed_users = set(existing_permission["corresponding_users"]) new_allowed_users = set(new_permission["corresponding_users"]) @@ -187,9 +188,9 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, effectively_removed_users = old_allowed_users - new_allowed_users if effectively_added_users: - hook_callback('post_app_addaccess', args=[app, ','.join(effectively_added_users)]) + hook_callback('post_app_addaccess', args=[app, ','.join(effectively_added_users), sub_permission]) if effectively_removed_users: - hook_callback('post_app_removeaccess', args=[app, ','.join(effectively_removed_users)]) + hook_callback('post_app_removeaccess', args=[app, ','.join(effectively_removed_users), sub_permission]) return new_permission @@ -241,6 +242,7 @@ def user_permission_reset(operation_logger, permission, sync_perm=True): # Trigger app callbacks app = permission.split(".")[0] + sub_permission = permission.split(".")[1] old_allowed_users = set(existing_permission["corresponding_users"]) new_allowed_users = set(new_permission["corresponding_users"]) @@ -249,9 +251,9 @@ def user_permission_reset(operation_logger, permission, sync_perm=True): effectively_removed_users = old_allowed_users - new_allowed_users if effectively_added_users: - hook_callback('post_app_addaccess', args=[app, ','.join(effectively_added_users)]) + hook_callback('post_app_addaccess', args=[app, ','.join(effectively_added_users), sub_permission]) if effectively_removed_users: - hook_callback('post_app_removeaccess', args=[app, ','.join(effectively_removed_users)]) + hook_callback('post_app_removeaccess', args=[app, ','.join(effectively_removed_users), sub_permission]) return new_permission From 6d5b90cc236c82bc83ec3ffa8e86254dcdfe0eff Mon Sep 17 00:00:00 2001 From: Kay0u Date: Thu, 7 Nov 2019 23:32:08 +0900 Subject: [PATCH 0448/3170] Refactor group permission --- src/yunohost/permission.py | 157 +++++++++++++++++++------------------ 1 file changed, 79 insertions(+), 78 deletions(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 7866e7277..8e1be4451 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -92,7 +92,6 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, remove -- List of groups or usernames to remove from to this permission """ from yunohost.hook import hook_callback - from yunohost.user import user_group_list from yunohost.utils.ldap import _get_ldap_interface ldap = _get_ldap_interface() @@ -111,7 +110,6 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, raise YunohostError('permission_not_found', permission=permission) current_allowed_groups = existing_permission["allowed"] - all_existing_groups = user_group_list()['groups'].keys() operation_logger.related_to.append(('app', permission.split(".")[0])) # Compute new allowed group list (and make sure what we're doing make sense) @@ -121,8 +119,6 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, if add: groups_to_add = [add] if not isinstance(add, list) else add for group in groups_to_add: - if group not in all_existing_groups: - raise YunohostError('group_unknown', group=group) if group in current_allowed_groups: logger.warning(m18n.n('permission_already_allowed', permission=permission, group=group)) else: @@ -133,8 +129,6 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, if remove: groups_to_remove = [remove] if not isinstance(remove, list) else remove for group in groups_to_remove: - if group not in all_existing_groups: - raise YunohostError('group_unknown', group=group) if group not in current_allowed_groups: logger.warning(m18n.n('permission_already_disallowed', permission=permission, group=group)) else: @@ -161,37 +155,10 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, operation_logger.start() - try: - ldap.update('cn=%s,ou=permission' % permission, - {'groupPermission': ['cn=' + g + ',ou=groups,dc=yunohost,dc=org' for g in new_allowed_groups]}) - except Exception as e: - raise YunohostError('permission_update_failed', permission=permission, error=e) + new_permission = _update_ldap_group_permission(permission=permission, allowed=new_allowed_groups, sync_perm=sync_perm) logger.debug(m18n.n('permission_updated', permission=permission)) - # Trigger permission sync if asked - - if sync_perm: - permission_sync_to_user() - - new_permission = user_permission_list(full=True)["permissions"][permission] - - # Trigger app callbacks - - app = permission.split(".")[0] - sub_permission = permission.split(".")[1] - - old_allowed_users = set(existing_permission["corresponding_users"]) - new_allowed_users = set(new_permission["corresponding_users"]) - - effectively_added_users = new_allowed_users - old_allowed_users - effectively_removed_users = old_allowed_users - new_allowed_users - - if effectively_added_users: - hook_callback('post_app_addaccess', args=[app, ','.join(effectively_added_users), sub_permission]) - if effectively_removed_users: - hook_callback('post_app_removeaccess', args=[app, ','.join(effectively_removed_users), sub_permission]) - return new_permission @@ -226,35 +193,10 @@ def user_permission_reset(operation_logger, permission, sync_perm=True): operation_logger.related_to.append(('app', permission.split(".")[0])) operation_logger.start() - default_permission = {'groupPermission': ['cn=all_users,ou=groups,dc=yunohost,dc=org']} - try: - ldap.update('cn=%s,ou=permission' % permission, default_permission) - except Exception as e: - raise YunohostError('permission_update_failed', permission=permission, error=e) + new_permission = _update_ldap_group_permission(permission=permission, allowed="all_users", sync_perm=sync_perm) logger.debug(m18n.n('permission_updated', permission=permission)) - if sync_perm: - permission_sync_to_user() - - new_permission = user_permission_list(full=True)["permissions"][permission] - - # Trigger app callbacks - - app = permission.split(".")[0] - sub_permission = permission.split(".")[1] - - old_allowed_users = set(existing_permission["corresponding_users"]) - new_allowed_users = set(new_permission["corresponding_users"]) - - effectively_added_users = new_allowed_users - old_allowed_users - effectively_removed_users = old_allowed_users - new_allowed_users - - if effectively_added_users: - hook_callback('post_app_addaccess', args=[app, ','.join(effectively_added_users), sub_permission]) - if effectively_removed_users: - hook_callback('post_app_removeaccess', args=[app, ','.join(effectively_removed_users), sub_permission]) - return new_permission # @@ -288,7 +230,6 @@ def permission_create(operation_logger, permission, url=None, allowed=None, sync re:domain.tld/app/api/[A-Z]*$ -> domain.tld/app/api/[A-Z]*$ """ - from yunohost.user import user_group_list from yunohost.utils.ldap import _get_ldap_interface ldap = _get_ldap_interface() @@ -315,20 +256,6 @@ def permission_create(operation_logger, permission, url=None, allowed=None, sync 'gidNumber': gid, } - # If who should be allowed is explicitly provided, use this info - if allowed: - if not isinstance(allowed, list): - allowed = [allowed] - # (though first we validate that the targets actually exist) - all_existing_groups = user_group_list()['groups'].keys() - for g in allowed: - if g not in all_existing_groups: - raise YunohostError('group_unknown', group=g) - attr_dict['groupPermission'] = ['cn=%s,ou=groups,dc=yunohost,dc=org' % g for g in allowed] - # For main permission, we add all users by default - elif permission.endswith(".main"): - attr_dict['groupPermission'] = ['cn=all_users,ou=groups,dc=yunohost,dc=org'] - if url: attr_dict['URL'] = url @@ -340,11 +267,20 @@ def permission_create(operation_logger, permission, url=None, allowed=None, sync except Exception as e: raise YunohostError('permission_creation_failed', permission=permission, error=e) - if sync_perm: - permission_sync_to_user() + to_add = None + + # If who should be allowed is explicitly provided, use this info + if allowed: + if not isinstance(allowed, list): + to_add = [allowed] + # For main permission, we add all users by default + elif permission.endswith(".main"): + to_add = "all_users" + + new_permission = _update_ldap_group_permission(permission=permission, allowed=to_add, sync_perm=sync_perm) logger.debug(m18n.n('permission_created', permission=permission)) - return user_permission_list(full=True)["permissions"][permission] + return new_permission @is_unit_operation() @@ -473,3 +409,68 @@ def permission_sync_to_user(): # Reload unscd, otherwise the group ain't propagated to the LDAP database os.system('nscd --invalidate=passwd') os.system('nscd --invalidate=group') + +def _update_ldap_group_permission(permission, allowed, sync_perm=True): + """ + Internal function that will rewrite user permission + + permission -- Name of the permission (e.g. mail or nextcloud or wordpress.editors) + allowed -- A list of group/user to allow for the permission + """ + + from yunohost.hook import hook_callback + from yunohost.user import user_group_list + from yunohost.utils.ldap import _get_ldap_interface + ldap = _get_ldap_interface() + + # Fetch currently allowed groups for this permission + + existing_permission = user_permission_list(full=True)["permissions"].get(permission, None) + if existing_permission is None: + raise YunohostError('permission_not_found', permission=permission) + + all_existing_groups = user_group_list()['groups'].keys() + + if allowed: + if not isinstance(allowed, list): + allowed = [allowed] + for group in allowed: + if group not in all_existing_groups: + raise YunohostError('group_unknown', group=group) + else: + if sync_perm: + permission_sync_to_user() + + return user_permission_list(full=True)["permissions"][permission] + + try: + ldap.update('cn=%s,ou=permission' % permission, + {'groupPermission': ['cn=' + g + ',ou=groups,dc=yunohost,dc=org' for g in allowed]}) + except Exception as e: + raise YunohostError('permission_update_failed', permission=permission, error=e) + + # Trigger permission sync if asked + + if sync_perm: + permission_sync_to_user() + + new_permission = user_permission_list(full=True)["permissions"][permission] + + # Trigger app callbacks + + app = permission.split(".")[0] + sub_permission = permission.split(".")[1] + + old_allowed_users = set(existing_permission["corresponding_users"]) + new_allowed_users = set(new_permission["corresponding_users"]) + + effectively_added_users = new_allowed_users - old_allowed_users + effectively_removed_users = old_allowed_users - new_allowed_users + + if effectively_added_users: + hook_callback('post_app_addaccess', args=[app, ','.join(effectively_added_users, sub_permission)]) + if effectively_removed_users: + hook_callback('post_app_removeaccess', args=[app, ','.join(effectively_removed_users, sub_permission)]) + + return new_permission + From c826a37aaace0459a2f3eff322008cbbe669e424 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Sun, 10 Nov 2019 21:34:57 +0900 Subject: [PATCH 0449/3170] Improve permission helpers --- data/helpers.d/setting | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index 185e6111f..bf4e804ec 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -257,6 +257,7 @@ ynh_webpath_register () { # re:/api/[A-Z]*$ -> domain.tld/app/api/[A-Z]*$ # re:domain.tld/app/api/[A-Z]*$ -> domain.tld/app/api/[A-Z]*$ # +# Requires YunoHost version 3.7.0 or higher. ynh_permission_create() { declare -Ar args_array=( [p]=permission= [u]=url= [a]=allowed= ) local permission @@ -274,7 +275,18 @@ ynh_permission_create() { allowed=",allowed=['${allowed//';'/"','"}']" fi - yunohost tools shell -c "from yunohost.permission import permission_create; permission_create('$app.$permission', url=$url ${allowed:-} , sync_perm=False)" + # Check if permission already exists + if ynh_permission_exists --permission $permission; then + # If permission exits, update it + local add + if [[ -n ${add:-} ]]; then + add="--add ${allowed//';'/" "}" + fi + ynh_exec_warn_less ynh_permission_update --permission $permission ${add:-} + else + # If not, create it + yunohost tools shell -c "from yunohost.permission import permission_create; permission_create('$app.$permission', url=$url ${allowed:-} , sync_perm=False)" + fi } # Remove a permission for the app (note that when the app is removed all permission is automatically removed) @@ -284,6 +296,7 @@ ynh_permission_create() { # usage: ynh_permission_delete --permission "permission" # | arg: permission - the name for the permission (by default a permission named "main" is removed automatically when the app is removed) # +# Requires YunoHost version 3.7.0 or higher. ynh_permission_delete() { declare -Ar args_array=( [p]=permission= ) local permission @@ -292,12 +305,27 @@ ynh_permission_delete() { yunohost tools shell -c "from yunohost.permission import permission_delete; permission_delete('$app.$permission', sync_perm=False)" } +# Check if a permission exists +# +# usage: ynh_permission_exists --permission=permission +# | arg: -p, --permission - the permission to check +# +# Requires YunoHost version 3.7.0 or higher. +ynh_permission_exists() { + declare -Ar args_array=( [p]=permission= ) + local permission + ynh_handle_getopts_args "$@" + + yunohost user permission list -s | grep -w -q "$app.$permission" +} + # Redefine the url associated to a permission # # usage: ynh_permission_url --permission "permission" --url "url" # | arg: permission - the name for the permission (by default a permission named "main" is removed automatically when the app is removed) # | arg: url - (optional) URL for which access will be allowed/forbidden # +# Requires YunoHost version 3.7.0 or higher. ynh_permission_url() { declare -Ar args_array=([p]=permission= [u]=url=) local permission @@ -322,6 +350,7 @@ ynh_permission_url() { # | arg: remove - the list of group or users to remove from the permission # # example: ynh_permission_update --permission admin --add samdoe --remove all_users +# Requires YunoHost version 3.7.0 or higher. ynh_permission_update() { declare -Ar args_array=( [p]=permission= [a]=add= [r]=remove= ) local permission From cbc1c82df7fbe221fa3262c9f6aa50893662b657 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Sun, 10 Nov 2019 22:08:33 +0900 Subject: [PATCH 0450/3170] Fix parameters --- data/helpers.d/setting | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index bf4e804ec..adc318bef 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -271,20 +271,18 @@ ynh_permission_create() { url="None" fi - if [[ -n ${allowed:-} ]]; then - allowed=",allowed=['${allowed//';'/"','"}']" - fi - # Check if permission already exists if ynh_permission_exists --permission $permission; then # If permission exits, update it - local add - if [[ -n ${add:-} ]]; then - add="--add ${allowed//';'/" "}" + if [[ -n ${allowed:-} ]]; then + allowed="--add ${allowed//';'/" "}" fi - ynh_exec_warn_less ynh_permission_update --permission $permission ${add:-} + ynh_exec_warn_less ynh_permission_update --permission $permission ${allowed:-} else # If not, create it + if [[ -n ${allowed:-} ]]; then + allowed=",allowed=['${allowed//';'/"','"}']" + fi yunohost tools shell -c "from yunohost.permission import permission_create; permission_create('$app.$permission', url=$url ${allowed:-} , sync_perm=False)" fi } From e8bf6eec80d15999ef4f3f1374b973e3785b1295 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Mon, 11 Nov 2019 09:56:09 +0900 Subject: [PATCH 0451/3170] fail if permission already exists --- data/helpers.d/setting | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index adc318bef..8046dfab4 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -271,20 +271,11 @@ ynh_permission_create() { url="None" fi - # Check if permission already exists - if ynh_permission_exists --permission $permission; then - # If permission exits, update it - if [[ -n ${allowed:-} ]]; then - allowed="--add ${allowed//';'/" "}" - fi - ynh_exec_warn_less ynh_permission_update --permission $permission ${allowed:-} - else - # If not, create it - if [[ -n ${allowed:-} ]]; then - allowed=",allowed=['${allowed//';'/"','"}']" - fi - yunohost tools shell -c "from yunohost.permission import permission_create; permission_create('$app.$permission', url=$url ${allowed:-} , sync_perm=False)" + if [[ -n ${allowed:-} ]]; then + allowed=",allowed=['${allowed//';'/"','"}']" fi + + yunohost tools shell -c "from yunohost.permission import permission_create; permission_create('$app.$permission', url=$url ${allowed:-} , sync_perm=False)" } # Remove a permission for the app (note that when the app is removed all permission is automatically removed) From 8c17d7368f35fdcd44baaf27e52ed5f1ec012c8b Mon Sep 17 00:00:00 2001 From: amirale qt Date: Fri, 15 Nov 2019 08:51:30 +0000 Subject: [PATCH 0452/3170] Translated using Weblate (Esperanto) Currently translated at 84.7% (505 of 596 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/eo/ --- locales/eo.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/locales/eo.json b/locales/eo.json index 7fe36db64..4b8a76d3c 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -575,5 +575,9 @@ "diagnosis_basesystem_ynh_single_version": "{0} versio: {1} ({2})", "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_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_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}" } From c38c47c70da436e9c5597b24c46a63b143452732 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quent=C3=AD?= Date: Thu, 14 Nov 2019 16:53:08 +0000 Subject: [PATCH 0453/3170] Translated using Weblate (Occitan) Currently translated at 42.1% (251 of 596 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/oc/ --- locales/oc.json | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/locales/oc.json b/locales/oc.json index 265012fe4..bbe30ac05 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -684,5 +684,19 @@ "log_permission_delete": "Suprimir la permission « {} »", "log_user_group_create": "Crear lo grop « {} »", "log_user_permission_update": "Actualizacion dels accèsses per la permission « {} »", - "operation_interrupted": "L’operacion es estada interrompuda manualament ?" + "operation_interrupted": "L’operacion es estada interrompuda manualament ?", + "group_cannot_be_edited": "Lo grop « {group} » pòt pas èsser modificat manualament.", + "group_cannot_be_deleted": "Lo grop « {group} » pòt pas èsser suprimit manualament.", + "diagnosis_found_warnings": "Trobat {warnings} element(s) que se poirián melhorar per {category}.", + "diagnosis_dns_missing_record": "Segon la configuracion DNS recomandada, vos calriá ajustar un enregistrament DNS de tipe {0}, nom {1} e valor {2}", + "diagnosis_dns_discrepancy": "Segon la configuracion DNS recomandada, la valor per l’enregistrament DNS de tipe {0} e nom {1} deuriá èsser {2} allòc de {3}.", + "diagnosis_regenconf_manually_modified_debian_details": "Es pas problematic, mas car téner d’agacher...", + "diagnosis_ports_could_not_diagnose": "Impossible de diagnosticar se los pòrts son accessibles de l’exterior. Error : {error}", + "diagnosis_http_could_not_diagnose": "Impossible de diagnosticar se lo domeni es accessible de l’exterior. Error : {error}", + "apps_catalog_updating": "Actualizacion del catalòg d’aplicacion…", + "apps_catalog_failed_to_download": "Telecargament impossible del catalòg d’aplicacions {apps_catalog} : {error}", + "apps_catalog_obsolete_cache": "La memòria cache del catalòg d’aplicacion es voida o obsolèta.", + "apps_catalog_update_success": "Lo catalòg d’aplicacions es a jorn !", + "diagnosis_mail_ougoing_port_25_ok": "Lo pòrt de sortida 25 es pas blocat e lo corrièr electronic pòt partir als autres servidors.", + "diagnosis_description_mail": "Corrièl" } From 6ff99f7cba7da1fcbbd5091830d70d867f748c1c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 4 Nov 2019 19:23:30 +0100 Subject: [PATCH 0454/3170] Improve app_upgrade error management --- locales/en.json | 4 +- src/yunohost/app.py | 96 +++++++++++++++++++++++---------------------- 2 files changed, 52 insertions(+), 48 deletions(-) diff --git a/locales/en.json b/locales/en.json index 27f25e095..6c7d0b42d 100644 --- a/locales/en.json +++ b/locales/en.json @@ -31,7 +31,6 @@ "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_not_upgraded": "The app '{failed_app}' failed to upgrade, and as a consequence the following apps upgrades have been cancelled: {apps}", - "app_upgrade_stopped": "Upgrading all apps was stopped to prevent possible damage because one app could not be upgraded", "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}", "app_not_properly_removed": "{app:s} has not been properly removed", @@ -50,7 +49,8 @@ "app_unsupported_remote_type": "Unsupported remote type used for the app", "app_upgrade_several_apps": "The following apps will be upgraded: {apps}", "app_upgrade_app_name": "Now upgrading {app}…", - "app_upgrade_failed": "Could not upgrade {app:s}", + "app_upgrade_failed": "Could not upgrade {app:s}: {error}", + "app_upgrade_script_failed": "An error occurred inside the app upgrade script", "app_upgrade_some_app_failed": "Some apps could not be upgraded", "app_upgraded": "{app:s} upgraded", "apps_already_up_to_date": "All apps are already up-to-date", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index a4185b880..f92adf9e4 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -727,82 +727,86 @@ def app_upgrade(app=[], url=None, file=None): # Execute App upgrade script os.system('chown -hR admin: %s' % INSTALL_TMP) + # Execute the app upgrade script + upgrade_failed = True try: upgrade_retcode = hook_exec(extracted_app_folder + '/scripts/upgrade', args=args_list, env=env_dict)[0] + + upgrade_failed = True if upgrade_retcode != 0 else False + if upgrade_failed: + error = m18n.n('app_upgrade_script_failed') + logger.exception(m18n.n("app_upgrade_failed", app=app_instance_name, error=error)) + failure_message_with_debug_instructions = operation_logger.error(error) + # Script got manually interrupted ... N.B. : KeyboardInterrupt does not inherit from Exception except (KeyboardInterrupt, EOFError): upgrade_retcode = -1 + error = m18n.n('operation_interrupted') + logger.exception(m18n.n("app_upgrade_failed", app=app_instance_name, error=error)) + failure_message_with_debug_instructions = operation_logger.error(error) + # Something wrong happened in Yunohost's code (most probably hook_exec) except Exception: import traceback - logger.exception(m18n.n('unexpected_error', error=u"\n" + traceback.format_exc())) + error = m18n.n('unexpected_error', error=u"\n" + traceback.format_exc()) + logger.exception(m18n.n("app_install_failed", app=app_instance_name, error=error)) + failure_message_with_debug_instructions = operation_logger.error(error) finally: - - # Did the script succeed ? - if upgrade_retcode == -1: - error_msg = m18n.n('operation_interrupted') - operation_logger.error(error_msg) - elif upgrade_retcode != 0: - error_msg = m18n.n('app_upgrade_failed', app=app_instance_name) - operation_logger.error(error_msg) - - # Did it broke the system ? + # 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 - error_msg = operation_logger.error(str(e)) + logger.exception(m18n.n("app_upgrade_failed", app=app_instance_name, error=str(e))) + failure_message_with_debug_instructions = operation_logger.error(str(e)) # If upgrade failed or broke the system, # raise an error and interrupt all other pending upgrades - if upgrade_retcode != 0 or broke_the_system: + if upgrade_failed or broke_the_system: # display this if there are remaining apps if apps[number + 1:]: - logger.error(m18n.n('app_upgrade_stopped')) not_upgraded_apps = apps[number:] - # we don't want to continue upgrading apps here in case that breaks - # everything - raise YunohostError('app_not_upgraded', + logger.error(m18n.n('app_not_upgraded', failed_app=app_instance_name, - apps=', '.join(not_upgraded_apps)) - else: - raise YunohostError(error_msg, raw_msg=True) + apps=', '.join(not_upgraded_apps))) + + raise YunohostError(failure_message_with_debug_instructions, raw_msg=True) # Otherwise we're good and keep going ! - else: - now = int(time.time()) - # TODO: Move install_time away from app_setting - app_setting(app_instance_name, 'update_time', now) - status['upgraded_at'] = now + now = int(time.time()) + # TODO: Move install_time away from app_setting + app_setting(app_instance_name, 'update_time', now) + status['upgraded_at'] = now - # Clean hooks and add new ones - hook_remove(app_instance_name) - if 'hooks' in os.listdir(extracted_app_folder): - for hook in os.listdir(extracted_app_folder + '/hooks'): - hook_add(app_instance_name, extracted_app_folder + '/hooks/' + hook) + # Clean hooks and add new ones + hook_remove(app_instance_name) + if 'hooks' in os.listdir(extracted_app_folder): + for hook in os.listdir(extracted_app_folder + '/hooks'): + hook_add(app_instance_name, extracted_app_folder + '/hooks/' + hook) - # Store app status - with open(app_setting_path + '/status.json', 'w+') as f: - json.dump(status, f) + # Store app status + with open(app_setting_path + '/status.json', 'w+') as f: + json.dump(status, f) - # Replace scripts and manifest and conf (if exists) - os.system('rm -rf "%s/scripts" "%s/manifest.toml %s/manifest.json %s/conf"' % (app_setting_path, app_setting_path, app_setting_path, app_setting_path)) + # Replace scripts and manifest and conf (if exists) + os.system('rm -rf "%s/scripts" "%s/manifest.toml %s/manifest.json %s/conf"' % (app_setting_path, app_setting_path, app_setting_path, app_setting_path)) - if os.path.exists(os.path.join(extracted_app_folder, "manifest.json")): - os.system('mv "%s/manifest.json" "%s/scripts" %s' % (extracted_app_folder, extracted_app_folder, app_setting_path)) - if os.path.exists(os.path.join(extracted_app_folder, "manifest.toml")): - os.system('mv "%s/manifest.toml" "%s/scripts" %s' % (extracted_app_folder, extracted_app_folder, app_setting_path)) + if os.path.exists(os.path.join(extracted_app_folder, "manifest.json")): + os.system('mv "%s/manifest.json" "%s/scripts" %s' % (extracted_app_folder, extracted_app_folder, app_setting_path)) + if os.path.exists(os.path.join(extracted_app_folder, "manifest.toml")): + os.system('mv "%s/manifest.toml" "%s/scripts" %s' % (extracted_app_folder, extracted_app_folder, app_setting_path)) - for file_to_copy in ["actions.json", "actions.toml", "config_panel.json", "config_panel.toml", "conf"]: - if os.path.exists(os.path.join(extracted_app_folder, file_to_copy)): - os.system('cp -R %s/%s %s' % (extracted_app_folder, file_to_copy, app_setting_path)) + for file_to_copy in ["actions.json", "actions.toml", "config_panel.json", "config_panel.toml", "conf"]: + if os.path.exists(os.path.join(extracted_app_folder, file_to_copy)): + os.system('cp -R %s/%s %s' % (extracted_app_folder, file_to_copy, app_setting_path)) - # So much win - logger.success(m18n.n('app_upgraded', app=app_instance_name)) + # So much win + logger.success(m18n.n('app_upgraded', app=app_instance_name)) - hook_callback('post_app_upgrade', args=args_list, env=env_dict) - operation_logger.success() + hook_callback('post_app_upgrade', args=args_list, env=env_dict) + operation_logger.success() permission_sync_to_user() From 71878f6bbecbb6868916ad11e10d4a603bf9a31d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 4 Nov 2019 20:20:12 +0100 Subject: [PATCH 0455/3170] Move debug log dump from ynh_exit_properly to the core after failed app operation --- data/helpers.d/utils | 37 ------------------------------------- src/yunohost/app.py | 31 +++++++++++++++++++++++++++++++ src/yunohost/backup.py | 7 +++++-- 3 files changed, 36 insertions(+), 39 deletions(-) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index e1feed6b1..a359423bb 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -28,43 +28,6 @@ ynh_exit_properly () { # Small tempo to avoid the next message being mixed up with other DEBUG messages sleep 0.5 - ynh_print_err --message="!!\n $app's script has encountered an error. Its execution was cancelled.\n!!" - - # If the script is executed from the CLI, dump the end of the log that precedes the crash. - if [ "$YNH_INTERFACE" == "cli" ] - then - # Unset xtrace to not spoil the log - set +x - - local ynh_log="/var/log/yunohost/yunohost-cli.log" - - # Wait for the log to be fill with the data until the crash. - local timeout=0 - while ! tail --lines=20 "$ynh_log" | grep --quiet "+ ynh_exit_properly" - do - ((timeout++)) - if [ $timeout -eq 500 ]; then - break - fi - done - - echo -e "\e[34m\e[1mPlease find here an extract of the log before the crash:\e[0m" >&2 - # Tail the last 30 lines of log of YunoHost - # But remove all lines after "ynh_exit_properly" - # Remove the timestamp at the beginning of the line - # Remove "yunohost.hook..." - # Add DEBUG and color it at the beginning of each log line. - echo -e "$(tail --lines=30 "$ynh_log" \ - | sed '1,/+ ynh_exit_properly/!d' \ - | sed 's/^[[:digit:]: ,-]*//g' \ - | sed 's/ *yunohost.hook.*\]/ -/g' \ - | sed 's/^WARNING /&/g' \ - | sed 's/^DEBUG /& /g' \ - | sed 's/^INFO /& /g' \ - | sed 's/^/\\e[34m\\e[1m[DEBUG]\\e[0m: /g')" >&2 - set -x - fi - if type -t ynh_clean_setup > /dev/null; then # Check if the function exist in the app script. ynh_clean_setup # Call the function to do specific cleaning for the app. fi diff --git a/src/yunohost/app.py b/src/yunohost/app.py index f92adf9e4..2d32fc0cc 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -738,6 +738,8 @@ def app_upgrade(app=[], url=None, file=None): error = m18n.n('app_upgrade_script_failed') logger.exception(m18n.n("app_upgrade_failed", app=app_instance_name, error=error)) failure_message_with_debug_instructions = operation_logger.error(error) + if msettings.get('interface') != 'api': + dump_app_log_extract_for_debugging(operation_logger) # Script got manually interrupted ... N.B. : KeyboardInterrupt does not inherit from Exception except (KeyboardInterrupt, EOFError): upgrade_retcode = -1 @@ -1002,6 +1004,8 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu error = m18n.n('app_install_script_failed') logger.exception(m18n.n("app_install_failed", app=app_id, error=error)) failure_message_with_debug_instructions = operation_logger.error(error) + if msettings.get('interface') != 'api': + dump_app_log_extract_for_debugging(operation_logger) # Script got manually interrupted ... N.B. : KeyboardInterrupt does not inherit from Exception except (KeyboardInterrupt, EOFError): error = m18n.n('operation_interrupted') @@ -1118,6 +1122,33 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu hook_callback('post_app_install', args=args_list, env=env_dict) +def dump_app_log_extract_for_debugging(operation_logger): + + with open(operation_logger.log_path, "r") as f: + lines = f.readlines() + + lines_to_display = [] + for line in lines: + + if not ": " in line.strip(): + continue + + # A line typically looks like + # 2019-10-19 16:10:27,611: DEBUG - + mysql -u piwigo --password=********** -B piwigo + # And we just want the part starting by "DEBUG - " + line = line.strip().split(": ", 1)[1] + lines_to_display.append(line) + + if line.endswith("+ ynh_exit_properly"): + break + elif len(lines_to_display) > 20: + lines_to_display.pop(0) + + logger.warning("Here's an extract of the logs before the crash. It might help debugging the error:") + for line in lines_to_display: + logger.info(line) + + def _migrate_legacy_permissions(app): from yunohost.permission import user_permission_list, user_permission_update diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 213f2cec1..3344d2807 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -36,14 +36,14 @@ from datetime import datetime from glob import glob from collections import OrderedDict -from moulinette import msignals, m18n +from moulinette import msignals, m18n, msettings from yunohost.utils.error import YunohostError from moulinette.utils import filesystem from moulinette.utils.log import getActionLogger from moulinette.utils.filesystem import read_file, mkdir, write_to_yaml, read_yaml from yunohost.app import ( - app_info, _is_installed, _parse_app_instance_name, _patch_php5 + app_info, _is_installed, _parse_app_instance_name, _patch_php5, dump_app_log_extract_for_debugging ) from yunohost.hook import ( hook_list, hook_info, hook_callback, hook_exec, CUSTOM_HOOK_FOLDER @@ -1399,6 +1399,9 @@ class RestoreManager(): logger.exception(msg) operation_logger.error(msg) + if msettings.get('interface') != 'api': + dump_app_log_extract_for_debugging(operation_logger) + self.targets.set_result("apps", app_instance_name, "Error") remove_script = os.path.join(app_scripts_in_archive, 'remove') From fe56354f369203a1da569d044535ff7a8ebe2aae Mon Sep 17 00:00:00 2001 From: Dominik Roesli Date: Wed, 30 Oct 2019 08:27:33 +0000 Subject: [PATCH 0456/3170] Translated using Weblate (German) Currently translated at 34.3% (193 of 562 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/locales/de.json b/locales/de.json index 6699f508c..3c0d00ce5 100644 --- a/locales/de.json +++ b/locales/de.json @@ -293,7 +293,7 @@ "backup_abstract_method": "Diese Backup-Methode wird noch nicht unterstützt", "backup_applying_method_tar": "Erstellen des Backup-tar Archives…", "backup_applying_method_copy": "Kopiere alle Dateien ins Backup…", - "app_change_url_no_script": "Die Anwendung '{app_name:s}' unterstützt bisher keine URL-Modufikation. Vielleicht gibt es eine Aktualisierung.", + "app_change_url_no_script": "Die Anwendung '{app_name:s}' unterstützt bisher keine URL-Modifikation. Vielleicht sollte sie aktualisiert werden.", "app_location_unavailable": "Diese URL ist nicht verfügbar oder wird von einer installierten Anwendung genutzt:\n{apps:s}", "backup_applying_method_custom": "Rufe die benutzerdefinierte Backup-Methode '{method:s}' auf…", "backup_archive_system_part_not_available": "Der System-Teil '{part:s}' ist in diesem Backup nicht enthalten", @@ -350,7 +350,7 @@ "app_start_remove": "Anwendung {app} wird entfernt…", "app_start_install": "Anwendung {app} wird installiert…", "app_not_upgraded": "Die App '{failed_app}' konnte nicht aktualisiert werden. Infolgedessen wurden die folgenden App-Upgrades abgebrochen: {apps}", - "app_make_default_location_already_used": "Die App \"{app}\" kann nicht als Standard für die Domain \"{domain}\" festgelegt werden. Sie wird bereits von der anderen App \"{other_app}\" verwendet", + "app_make_default_location_already_used": "Die App \"{app}\" kann nicht als Standard für die Domain \"{domain}\" festgelegt werden. Sie wird bereits von der App \"{other_app}\" verwendet", "aborting": "Breche ab.", "app_action_cannot_be_ran_because_required_services_down": "Diese App erfordert einige Dienste, die derzeit nicht verfügbar sind. Bevor Sie fortfahren, sollten Sie versuchen, die folgenden Dienste neu zu starten (und möglicherweise untersuchen, warum sie nicht verfügbar sind): {services}", "already_up_to_date": "Nichts zu tun. Alles ist bereits auf dem neusten Stand.", @@ -414,5 +414,8 @@ "global_settings_key_doesnt_exists": "Der Schlüssel'{settings_key:s}' existiert nicht in den globalen Einstellungen, du kannst alle verfügbaren Schlüssel sehen, indem du 'yunohost settings list' ausführst", "log_app_makedefault": "Mache '{}' zur Standard-Anwendung", "hook_json_return_error": "Konnte die Rückkehr vom Einsprungpunkt {path:s} nicht lesen. Fehler: {msg:s}. Unformatierter Inhalt: {raw_content}", - "app_full_domain_unavailable": "Es tut uns leid, aber diese Anwendung erfordert die Installation einer vollständigen Domäne, aber einige andere Anwendungen sind bereits auf der Domäne'{domain}' installiert. Eine mögliche Lösung ist das Hinzufügen und Verwenden einer Subdomain, die dieser Anwendung zugeordnet ist." + "app_full_domain_unavailable": "Es tut uns leid, aber diese Anwendung erfordert die Installation auf einer eigenen Domain, aber einige andere Anwendungen sind bereits auf der Domäne'{domain}' installiert. Eine mögliche Lösung ist das Hinzufügen und Verwenden einer Subdomain, die dieser Anwendung zugeordnet ist.", + "app_install_failed": "Installation von {app} fehlgeschlagen: {error}", + "app_install_script_failed": "Im Installationsscript ist ein Fehler aufgetreten", + "app_remove_after_failed_install": "Entfernen der App nach fehlgeschlagener Installation…" } From 434fef5d8cb0f578654068ed7df6aa41f79f6cf0 Mon Sep 17 00:00:00 2001 From: xaloc33 Date: Sun, 3 Nov 2019 22:26:53 +0000 Subject: [PATCH 0457/3170] Translated using Weblate (Catalan) Currently translated at 100.0% (562 of 562 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ca/ --- locales/ca.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/ca.json b/locales/ca.json index 32bbfb50f..9a00b5c07 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -626,5 +626,6 @@ "permission_already_up_to_date": "No s'ha actualitzat el permís perquè la petició d'afegir/eliminar ja corresponent a l'estat actual.", "permission_currently_allowed_for_visitors": "El permís ja el tenen el grup de visitants a més d'altres grups. Segurament s'hauria de revocar el permís al grup dels visitants o eliminar els altres grups als que s'ha atribuït.", "permission_currently_allowed_for_all_users": "El permís ha el té el grup de tots els usuaris (all_users) a més d'altres grups. Segurament s'hauria de revocar el permís a «all_users» o eliminar els altres grups als que s'ha atribuït.", - "permission_require_account": "El permís {permission} només té sentit per als usuaris que tenen un compte, i per tant no es pot activar per als visitants." + "permission_require_account": "El permís {permission} només té sentit per als usuaris que tenen un compte, i per tant no es pot activar per als visitants.", + "app_remove_after_failed_install": "Eliminant l'aplicació després que hagi fallat la instal·lació…" } From 930181afd875e0c2e47286b356a1f74ba158e6bb Mon Sep 17 00:00:00 2001 From: Filip Bengtsson Date: Thu, 31 Oct 2019 04:34:15 +0000 Subject: [PATCH 0458/3170] Translated using Weblate (Swedish) Currently translated at 1.6% (9 of 562 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/sv/ --- locales/sv.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/locales/sv.json b/locales/sv.json index 4960d43aa..85572756d 100644 --- a/locales/sv.json +++ b/locales/sv.json @@ -1,3 +1,11 @@ { - "password_too_simple_1": "Lösenordet måste bestå av minst åtta tecken" + "password_too_simple_1": "Lösenordet måste bestå av minst åtta tecken", + "app_action_broke_system": "Åtgärden verkar ha fått följande viktiga tjänster att haverera: {services}", + "already_up_to_date": "Ingenting att göra. Allt är redan uppdaterat.", + "admin_password": "Administratörslösenord", + "admin_password_too_long": "Välj gärna ett lösenord som inte innehåller fler än 127 tecken", + "admin_password_change_failed": "Kan inte byta lösenord", + "action_invalid": "Ej tillåten åtgärd '{action:s}'", + "admin_password_changed": "Administratörskontots lösenord ändrades", + "aborting": "Avbryter." } From a7dc8ad10ff0f2091712e507db70606b898132b4 Mon Sep 17 00:00:00 2001 From: advocatux Date: Wed, 30 Oct 2019 15:30:49 +0000 Subject: [PATCH 0459/3170] Translated using Weblate (Spanish) Currently translated at 100.0% (562 of 562 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/es/ --- locales/es.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/es.json b/locales/es.json index d216b8a9a..e40a168a7 100644 --- a/locales/es.json +++ b/locales/es.json @@ -637,5 +637,6 @@ "permission_already_up_to_date": "El permiso no se ha actualizado porque las peticiones de incorporación o eliminación ya coinciden con el estado actual.", "permission_currently_allowed_for_visitors": "Este permiso se concede actualmente a los visitantes además de otros grupos. Probablemente quiere o eliminar el permiso de «visitors» o eliminar los otros grupos a los que está otorgado actualmente.", "permission_currently_allowed_for_all_users": "Este permiso se concede actualmente a todos los usuarios además de los otros grupos. Probablemente quiere o eliminar el permiso de «all_users» o eliminar los otros grupos a los que está otorgado actualmente.", - "permission_require_account": "El permiso {permission} solo tiene sentido para usuarios con una cuenta y, por lo tanto, no se puede activar para visitantes." + "permission_require_account": "El permiso {permission} solo tiene sentido para usuarios con una cuenta y, por lo tanto, no se puede activar para visitantes.", + "app_remove_after_failed_install": "Eliminando la aplicación tras el fallo de instalación…" } From 5aa5c1140f2614ba149a60492a6a951f60bd678f Mon Sep 17 00:00:00 2001 From: amirale qt Date: Tue, 5 Nov 2019 08:45:33 +0000 Subject: [PATCH 0460/3170] Translated using Weblate (Turkish) Currently translated at 0.2% (1 of 562 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/tr/ --- locales/tr.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/locales/tr.json b/locales/tr.json index 0967ef424..c6eb58ed1 100644 --- a/locales/tr.json +++ b/locales/tr.json @@ -1 +1,3 @@ -{} +{ + "password_too_simple_1": "Şifre en az 8 karakter uzunluğunda olmalı" +} From 473c7a9c2d9318006691b10fcb77fed32819caa6 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Tue, 5 Nov 2019 07:32:56 +0000 Subject: [PATCH 0461/3170] Translated using Weblate (Basque) Currently translated at 0.2% (1 of 562 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/eu/ --- locales/eu.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/locales/eu.json b/locales/eu.json index 0967ef424..539fb9157 100644 --- a/locales/eu.json +++ b/locales/eu.json @@ -1 +1,3 @@ -{} +{ + "password_too_simple_1": "Pasahitzak gutxienez 8 karaktere izan behar ditu" +} From 230059602470c1b06bc14d6b99b052c093983c0f Mon Sep 17 00:00:00 2001 From: amirale qt Date: Tue, 5 Nov 2019 13:33:02 +0000 Subject: [PATCH 0462/3170] Translated using Weblate (French) Currently translated at 100.0% (562 of 562 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/fr.json b/locales/fr.json index 15f82baf1..645e285ad 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -664,5 +664,6 @@ "permission_currently_allowed_for_all_users": "Cette autorisation est actuellement accordée à tous les utilisateurs en plus des autres groupes. Vous voudrez probablement soit supprimer l'autorisation 'all_users', soit supprimer les autres groupes auxquels il est actuellement autorisé.", "app_install_failed": "Impossible d'installer {app}: {error}", "app_install_script_failed": "Une erreur est survenue dans le script d'installation de l'application", - "permission_require_account": "Permission {permission} n'a de sens que pour les utilisateurs ayant un compte et ne peut donc pas être activé pour les visiteurs." + "permission_require_account": "Permission {permission} n'a de sens que pour les utilisateurs ayant un compte et ne peut donc pas être activé pour les visiteurs.", + "app_remove_after_failed_install": "Supprimer l'application après l'échec de l'installation…" } From be598a0610303569251bf50a91b4d00740185a28 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Tue, 5 Nov 2019 13:33:34 +0000 Subject: [PATCH 0463/3170] Translated using Weblate (Esperanto) Currently translated at 95.6% (537 of 562 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/eo/ --- locales/eo.json | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/locales/eo.json b/locales/eo.json index 720485ba6..a25fa505e 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -141,7 +141,7 @@ "field_invalid": "Nevalida kampo '{:s}'", "log_app_makedefault": "Faru '{}' la defaŭlta apliko", "migration_0003_still_on_jessie_after_main_upgrade": "Io okazis malbone dum la ĉefa ĝisdatigo: Ĉu la sistemo ankoraŭ estas en Jessie‽ Por esplori la aferon, bonvolu rigardi {log}:s …", - "migration_0011_can_not_backup_before_migration": "La sekurkopio de la sistemo antaŭ la migrado malsukcesis. Migrado malsukcesis. Eraro: {error:s}", + "migration_0011_can_not_backup_before_migration": "La sekurkopio de la sistemo ne povis finiĝi antaŭ ol la migrado malsukcesis. Eraro: {error:s}", "migration_0011_create_group": "Krei grupon por ĉiu uzanto…", "backup_system_part_failed": "Ne eblis sekurkopi la sistemon de '{part:s}'", "global_settings_setting_security_postfix_compatibility": "Kongruo vs sekureca kompromiso por la Postfix-servilo. Afektas la ĉifradojn (kaj aliajn aspektojn pri sekureco)", @@ -151,8 +151,8 @@ "migration_0011_backup_before_migration": "Krei sekurkopion de LDAP-datumbazo kaj agordojn antaŭ la efektiva migrado.", "migration_0011_LDAP_config_dirty": "Similas ke vi agordis vian LDAP-agordon. Por ĉi tiu migrado la LDAP-agordo bezonas esti ĝisdatigita.\nVi devas konservi vian aktualan agordon, reintaligi la originalan agordon per funkciado de \"yunohost iloj regen-conf -f\" kaj reprovi la migradon", "migration_0011_migrate_permission": "Migrado de permesoj de agordoj al aplikoj al LDAP…", - "migration_0011_migration_failed_trying_to_rollback": "Migrado malsukcesis ... provante reverti la sistemon.", - "migrations_dependencies_not_satisfied": "Ne eblas kuri migradon {id} ĉar unue vi devas ruli ĉi tiujn migradojn: {dependencies_id}", + "migration_0011_migration_failed_trying_to_rollback": "Ne povis migri ... provante redakti la sistemon.", + "migrations_dependencies_not_satisfied": "Rulu ĉi tiujn migradojn: '{dependencies_id}', antaŭ migrado {id}.", "migrations_failed_to_load_migration": "Ne povis ŝarĝi migradon {id}: {error}", "migrations_exclusive_options": "'--auto', '--skip' kaj '--force-rerun' estas reciproke ekskluzivaj ebloj.", "migrations_must_provide_explicit_targets": "Vi devas provizi eksplicitajn celojn kiam vi uzas '--skip' aŭ '--force-rerun'", @@ -162,7 +162,7 @@ "tools_upgrade_cant_hold_critical_packages": "Ne povis teni kritikajn pakojn…", "upnp_dev_not_found": "Neniu UPnP-aparato trovita", "migration_description_0012_postgresql_password_to_md5_authentication": "Devigu PostgreSQL-aŭtentigon uzi MD5 por lokaj ligoj", - "migration_0011_done": "Migrado sukcesis. Vi nun kapablas administri uzantajn grupojn.", + "migration_0011_done": "Migrado finiĝis. Vi nun kapablas administri uzantajn grupojn.", "migration_0011_LDAP_update_failed": "Ne povis ĝisdatigi LDAP. Eraro: {error:s}", "pattern_password": "Devas esti almenaŭ 3 signoj longaj", "root_password_desynchronized": "La pasvorta administranto estis ŝanĝita, sed YunoHost ne povis propagandi ĉi tion al la radika pasvorto!", @@ -194,9 +194,9 @@ "migration_0011_rollback_success": "Sistemo ruliĝis reen.", "migration_0011_update_LDAP_database": "Ĝisdatigante LDAP-datumbazon…", "migration_0011_update_LDAP_schema": "Ĝisdatigante LDAP-skemon…", - "migration_0011_failed_to_remove_stale_object": "Malsukcesis forigi neokazan objekton {dn}: {error}", + "migration_0011_failed_to_remove_stale_object": "Ne povis forigi neuzatan objekton {dn}: {error}", "migrations_already_ran": "Tiuj migradoj estas jam faritaj: {ids}", - "migrations_no_such_migration": "Estas neniu migrado nomata {id}", + "migrations_no_such_migration": "Estas neniu migrado nomata '{id}'", "permission_already_allowed": "Grupo '{group}' jam havas permeson '{permission}' ebligita'", "permission_already_disallowed": "Grupo '{group}' jam havas permeson '{permission}' malebligita'", "permission_cannot_remove_main": "Forigo de ĉefa permeso ne rajtas", @@ -266,7 +266,7 @@ "migration_description_0008_ssh_conf_managed_by_yunohost_step2": "Lasu la SSH-agordon estu administrata de YunoHost (paŝo 2, manlibro)", "restore_confirm_yunohost_installed": "Ĉu vi vere volas restarigi jam instalitan sistemon? [{answers:s}]", "pattern_positive_number": "Devas esti pozitiva nombro", - "monitor_stats_file_not_found": "Statistika dosiero ne trovita", + "monitor_stats_file_not_found": "Ne povis trovi la statistikan dosieron", "certmanager_error_no_A_record": "Neniu DNS 'A' rekordo trovita por '{domain:s}'. Vi bezonas atentigi vian domajnan nomon al via maŝino por povi instali atestilon Lasu-Ĉifri. (Se vi scias, kion vi faras, uzu '--no-checks' por malŝalti tiujn ĉekojn.)", "update_apt_cache_failed": "Ne eblis ĝisdatigi la kaŝmemoron de APT (paka administranto de Debian). Jen rubujo de la sources.list-linioj, kiuj povus helpi identigi problemajn liniojn:\n{sourcelist}", "migrations_no_migrations_to_run": "Neniuj migradoj por funkcii", @@ -339,7 +339,7 @@ "log_app_upgrade": "Ĝisdatigu la aplikon '{}'", "log_help_to_get_failed_log": "La operacio '{desc}' ne povis finiĝi. Bonvolu dividi la plenan ŝtipon de ĉi tiu operacio per la komando 'yunohost log display {name} --share' por akiri helpon", "migration_description_0002_migrate_to_tsig_sha256": "Plibonigu sekurecon de DynDNS TSIG-ĝisdatigoj per SHA-512 anstataŭ MD5", - "monitor_disabled": "Servila monitorado nun malŝaltis", + "monitor_disabled": "Servilo-monitorado nun malŝaltita", "pattern_port": "Devas esti valida havena numero (t.e. 0-65535)", "port_already_closed": "Haveno {port:d} estas jam fermita por {ip_version:s} rilatoj", "hook_name_unknown": "Nekonata hoko-nomo '{name:s}'", @@ -407,7 +407,7 @@ "migration_0003_not_jessie": "La nuna Debian-distribuo ne estas Jessie!", "user_unknown": "Nekonata uzanto: {user:s}", "migrations_to_be_ran_manually": "Migrado {id} devas funkcii permane. Bonvolu iri al Iloj → Migradoj en la retpaĝa paĝo, aŭ kuri `yunohost tools migrations migrate`.", - "migration_0008_warning": "Se vi komprenas tiujn avertojn kaj konsentas lasi YunoHost pretervidi vian nunan agordon, faru la migradon. Alie, vi ankaŭ povas salti la migradon - kvankam ĝi ne rekomendas.", + "migration_0008_warning": "Se vi komprenas tiujn avertojn kaj volas ke YunoHost preterlasu vian nunan agordon, faru la migradon. Alie, vi ankaŭ povas salti la migradon, kvankam ĝi ne rekomendas.", "certmanager_cert_renew_success": "Ni Ĉifru atestilon renovigitan por la domajno '{domain:s}'", "global_settings_reset_success": "Antaŭaj agordoj nun estas rezervitaj al {path:s}", "pattern_domain": "Devas esti valida domajna nomo (t.e. mia-domino.org)", @@ -477,14 +477,14 @@ "log_tools_maindomain": "Faru de '{}' la ĉefa domajno", "maindomain_change_failed": "Ne povis ŝanĝi la ĉefan domajnon", "mail_domain_unknown": "Nevalida retadreso por domajno '{domain:s}'. Bonvolu uzi domajnon administritan de ĉi tiu servilo.", - "migrations_cant_reach_migration_file": "Ne povis aliri migrajn dosierojn ĉe la vojo% s", + "migrations_cant_reach_migration_file": "Ne povis aliri migrajn dosierojn ĉe la vojo '% s'", "pattern_email": "Devas esti valida retpoŝtadreso (t.e.iu@domain.org)", "mail_alias_remove_failed": "Ne povis forigi retpoŝton alias '{mail:s}'", "regenconf_file_manually_removed": "La dosiero de agordo '{conf}' estis forigita permane, kaj ne estos kreita", - "monitor_enabled": "Servila monitorado nun ŝaltis", + "monitor_enabled": "Servilo-monitorado nun", "domain_exists": "La domajno jam ekzistas", "migration_description_0001_change_cert_group_to_sslcert": "Ŝanĝu grupajn permesojn de 'metronomo' al 'ssl-cert'", - "mysql_db_creation_failed": "MySQL-datumbazkreado malsukcesis", + "mysql_db_creation_failed": "Ne povis krei MySQL-datumbazon", "ldap_initialized": "LDAP inicializis", "migrate_tsig_not_needed": "Vi ne ŝajnas uzi DynDNS-domajnon, do neniu migrado necesas.", "certmanager_domain_cert_not_selfsigned": "La atestilo por domajno {domajno:s} ne estas mem-subskribita. Ĉu vi certas, ke vi volas anstataŭigi ĝin? (Uzu '--force' por fari tion.)", @@ -495,7 +495,7 @@ "global_settings_bad_choice_for_enum": "Malbona elekto por agordo {setting:s}, ricevita '{choice:s}', sed disponeblaj elektoj estas: {available_choices:s}", "server_shutdown": "La servilo haltos", "log_tools_migrations_migrate_forward": "Migri antaŭen", - "migration_0008_no_warning": "Neniu grava risko identigita pri superregado de via SSH-agordo, tamen oni ne povas esti absolute certa;)! Ekfunkciu la migradon por superregi ĝin. Alie, vi ankaŭ povas salti la migradon - kvankam ĝi ne rekomendas.", + "migration_0008_no_warning": "Supersalti vian SSH-agordon estu sekura, kvankam ĉi tio ne povas esti promesita! Ekfunkciu la migradon por superregi ĝin. Alie, vi ankaŭ povas salti la migradon, kvankam ĝi ne rekomendas.", "regenconf_now_managed_by_yunohost": "La agorda dosiero '{conf}' nun estas administrata de YunoHost (kategorio {category}).", "server_reboot_confirm": "Ĉu la servilo rekomencos tuj, ĉu vi certas? [{answers:s}]", "log_app_install": "Instalu la aplikon '{}'", @@ -563,5 +563,6 @@ "permission_currently_allowed_for_visitors": "Ĉi tiu permeso estas nuntempe donita al vizitantoj aldone al aliaj grupoj. Vi probable volas aŭ forigi la permeson de \"vizitantoj\" aŭ forigi la aliajn grupojn al kiuj ĝi nun estas koncedita.", "permission_currently_allowed_for_all_users": "Ĉi tiu permeso estas nuntempe donita al ĉiuj uzantoj aldone al aliaj grupoj. Vi probable volas aŭ forigi la permeson \"all_users\" aŭ forigi la aliajn grupojn, kiujn ĝi nuntempe donas.", "app_install_failed": "Ne povis instali {app} : {error}", - "app_install_script_failed": "Eraro okazis en la skripto de instalado de la app" + "app_install_script_failed": "Eraro okazis en la skripto de instalado de la app", + "app_remove_after_failed_install": "Forigado de la app post la instala fiasko …" } From 242ccc3c3d6774a7f9c05c255268d170d2cf7df4 Mon Sep 17 00:00:00 2001 From: advocatux Date: Thu, 7 Nov 2019 09:12:30 +0000 Subject: [PATCH 0464/3170] Translated using Weblate (Spanish) Currently translated at 89.0% (540 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/es/ --- locales/es.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/locales/es.json b/locales/es.json index e40a168a7..d526f442f 100644 --- a/locales/es.json +++ b/locales/es.json @@ -638,5 +638,10 @@ "permission_currently_allowed_for_visitors": "Este permiso se concede actualmente a los visitantes además de otros grupos. Probablemente quiere o eliminar el permiso de «visitors» o eliminar los otros grupos a los que está otorgado actualmente.", "permission_currently_allowed_for_all_users": "Este permiso se concede actualmente a todos los usuarios además de los otros grupos. Probablemente quiere o eliminar el permiso de «all_users» o eliminar los otros grupos a los que está otorgado actualmente.", "permission_require_account": "El permiso {permission} solo tiene sentido para usuarios con una cuenta y, por lo tanto, no se puede activar para visitantes.", - "app_remove_after_failed_install": "Eliminando la aplicación tras el fallo de instalación…" + "app_remove_after_failed_install": "Eliminando la aplicación tras el fallo de instalación…", + "diagnosis_basesystem_host": "El servidor está ejecutando Debian {debian_version}.", + "diagnosis_basesystem_kernel": "El servidor está ejecutando el núcleo de Linux {kernel_version}", + "diagnosis_basesystem_ynh_single_version": "{0} versión: {1} ({2})", + "diagnosis_basesystem_ynh_main_version": "El servidor está ejecutando YunoHost {main_version} ({repo})", + "diagnosis_basesystem_ynh_inconsistent_versions": "Está ejecutando versiones incoherentes de los paquetes de YunoHost... probablemente por una actualización errónea o parcial." } From 31957a6296b9110e159aa9306dd7618bef7f262e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quent=C3=AD?= Date: Sun, 10 Nov 2019 16:49:52 +0000 Subject: [PATCH 0465/3170] Translated using Weblate (Occitan) Currently translated at 39.9% (242 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/oc/ --- locales/oc.json | 84 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 78 insertions(+), 6 deletions(-) diff --git a/locales/oc.json b/locales/oc.json index 320a18341..f848bae1b 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -5,7 +5,7 @@ "app_already_installed": "{app:s} es ja installat", "app_already_up_to_date": "{app:s} es ja a jorn", "installation_complete": "Installacion acabada", - "app_id_invalid": "Id d’aplicacion incorrècte", + "app_id_invalid": "ID d’aplicacion incorrècte", "app_install_files_invalid": "Fichièrs d’installacion incorrèctes", "app_no_upgrade": "Pas cap d’aplicacion d’actualizar", "app_not_correctly_installed": "{app:s} sembla pas ben installat", @@ -41,15 +41,15 @@ "backup_archive_name_unknown": "L’archiu local de salvagarda apelat « {name:s} » es desconegut", "action_invalid": "Accion « {action:s} » incorrècta", "app_argument_choice_invalid": "Causida invalida pel paramètre « {name:s} », cal que siá un de {choices:s}", - "app_argument_invalid": "Valor invalida pel paramètre « {name:s} » : {error:s}", + "app_argument_invalid": "Causissètz una valor invalida pel paramètre « {name:s} » : {error:s}", "app_argument_required": "Lo paramètre « {name:s} » es requesit", - "app_change_url_failed_nginx_reload": "La reaviada de nginx a fracassat. Vaquí la sortida de « nginx -t » :\n{nginx_errors:s}", + "app_change_url_failed_nginx_reload": "Reaviada de NGINX impossibla. Vaquí la sortida de « nginx -t » :\n{nginx_errors:s}", "app_change_url_identical_domains": "L’ancian e lo novèl coble domeni/camin son identics per {domain:s}{path:s}, pas res a far.", - "app_change_url_success": "L’URL de l’aplicacion {app:s} a cambiat per {domain:s}{path:s}", + "app_change_url_success": "L’URL de l’aplicacion {app:s} es ara {domain:s}{path:s}", "app_checkurl_is_deprecated": "Packagers /!\\ ’app checkurl’ es obsolèt ! Utilizatz ’app register-url’ a la plaça !", "app_extraction_failed": "Extraccion dels fichièrs d’installacion impossibla", "app_incompatible": "L’aplicacion {app} es pas compatibla amb vòstra version de YunoHost", - "app_location_already_used": "L’aplicacion « {app} » es ja installada a aqueste emplaçament ({path})", + "app_location_already_used": "L’aplicacion « {app} » es ja installada dins ({path})", "app_manifest_invalid": "Manifest d’aplicacion incorrècte : {error}", "app_package_need_update": "Lo paquet de l’aplicacion {app} deu èsser actualizat per poder seguir los cambiaments de YunoHost", "app_requirements_checking": "Verificacion dels paquets requesits per {app}…", @@ -612,5 +612,77 @@ "migrations_must_provide_explicit_targets": "Devètz fornir una cibla explicita quand utilizatz using --skip o --force-rerun", "migrations_exclusive_options": "--auto, --skip, e --force-rerun son las opcions exclusivas.", "migrations_failed_to_load_migration": "Cargament impossible de la migracion {id} : {error}", - "migrations_already_ran": "Aquelas migracions s’executèron ja : {ids}" + "migrations_already_ran": "Aquelas migracions s’executèron ja : {ids}", + "diagnosis_basesystem_ynh_main_version": "Lo servidor fonciona amb YunoHost {main_version} ({repo})", + "migrations_dependencies_not_satisfied": "Executatz aquestas migracions : « {dependencies_id} », abans la migracion {id}.", + "migrations_no_such_migration": "I a pas cap de migracion apelada « {id} »", + "migrations_not_pending_cant_skip": "Aquestas migracions son pas en espèra, las podètz pas doncas ignorar : {ids}", + "app_action_broke_system": "Aquesta accion sembla aver copat de servicis importants : {services}", + "diagnosis_display_tip_web": "Podètz anar a la seccion Diagnostic (dins l’ecran d’acuèlh) per veire los problèmas trobats.", + "diagnosis_ip_no_ipv6": "Lo servidor a pas d’adreça IPv5 activa.", + "diagnosis_ip_not_connected_at_all": "Lo servidor sembla pas connectat a Internet ?!", + "diagnosis_security_all_good": "Cap de vulnerabilitat de seguretat critica pas trobada.", + "diagnosis_description_regenconf": "Configuracion sistèma", + "diagnosis_http_ok": "Lo domeni {domain} accessible de l’exterior.", + "app_full_domain_unavailable": "Aquesta aplicacion a d’èsser installada sul seu pròpri domeni, mas i a d’autras aplicacions installadas sus aqueste domeni « {domain} ». Podètz utilizar allòc un josdomeni dedicat a aquesta aplicacion.", + "app_upgrade_stopped": "L’actualizacion de totas las aplicacions s‘es arrestada per evitar de possibles damatges pr’amor qu’èra pas possible d’actualizar una aplicacion", + "diagnosis_dns_bad_conf": "Configuracion DNS incorrècta o inexistenta pel domeni {domain} (categoria {category})", + "diagnosis_ram_verylow": "Lo sistèma a solament {available_abs_MB} Mo ({available_percent}%) de memòria RAM disponibla ! (d’un total de {total_abs_MB} MB)", + "diagnosis_ram_ok": "Lo sistèma a encara {available_abs_MB} Mo ({available_percent}%) de memòria RAM disponibla d’un total de {total_abs_MB} MB).", + "permission_already_allowed": "Lo grop « {group} » a ja la permission « {permission} » activada", + "permission_already_disallowed": "Lo grop « {group} » a ja la permission « {permission} » desactivada", + "permission_cannot_remove_main": "La supression d’una permission màger es pas autorizada", + "log_permission_url": "Actualizacion de l’URL ligada a la permission « {} »", + "app_install_failed": "Installacion impossibla de {app} : {error}", + "app_install_script_failed": "Una error s’es producha en installar lo script de l’aplicacion", + "migration_0011_failed_to_remove_stale_object": "Supression impossibla d’un objècte obsolèt {dn} : {error}", + "apps_already_up_to_date": "Totas las aplicacions son ja al jorn", + "app_remove_after_failed_install": "Supression de l’aplicacion aprèp fracàs de l’installacion…", + "group_already_exist": "Lo grop {group} existís ja", + "group_already_exist_on_system": "Lo grop {group} existís ja dins lo sistèma de grops", + "group_user_not_in_group": "L’utilizaire {user} es pas dins lo grop {group}", + "log_user_permission_reset": "Restablir la permission « {} »", + "user_already_exists": "L’utilizaire {user} existís ja", + "diagnosis_basesystem_host": "Lo servidor fonciona amb Debian {debian_version}.", + "diagnosis_basesystem_kernel": "Lo servidor fonciona amb lo nuclèu Linuxl {kernel_version}", + "diagnosis_basesystem_ynh_single_version": "{0} version : {1} ({2})", + "diagnosis_basesystem_ynh_inconsistent_versions": "Utilizatz de versions inconsistentas dels paquets de YunoHost… probablament a causa d'una actualizacion fracassada o parciala.", + "diagnosis_display_tip_cli": "Podètz executar « yunohost diagnosis show --issues » per mostrar las errors trobadas.", + "diagnosis_ignored_issues": "(+ {nb_ignored} problèma(es) ignorat(s))", + "diagnosis_everything_ok": "Tot sembla corrècte per {category} !", + "diagnosis_ip_connected_ipv4": "Lo servidor es connectat a Internet via IPv4 !", + "diagnosis_ip_no_ipv4": "Lo servidor a pas d’adreça IPv4 activa.", + "diagnosis_ip_connected_ipv6": "Lo servidor es connectat a Internet via IPv6 !", + "diagnosis_ip_dnsresolution_working": "La resolucion del nom de domeni fonciona !", + "diagnosis_dns_good_conf": "Bona configuracion DNS pel domeni {domain} (categoria {category})", + "diagnosis_failed_for_category": "Lo diagnostic a reüssit per la categoria « {category} » : {error}", + "diagnosis_cache_still_valid": "(Memòria cache totjorn valida pel diagnostic {category}. Cap d’autre diagnostic pel moment !)", + "diagnosis_found_errors": "{errors} errors importantas trobadas ligadas a {category} !", + "diagnosis_services_good_status": "Lo servici {service} es {status} coma previst !", + "diagnosis_services_bad_status": "Lo servici {service} es {status} :/", + "diagnosis_swap_ok": "Lo sistèma a {total_MB} MB d’escambi !", + "diagnosis_regenconf_allgood": "Totes los fichièrs de configuracion son confòrmes a la configuracion recomandada !", + "diagnosis_regenconf_manually_modified": "Lo fichièr de configuracion {file} foguèt modificat manualament.", + "diagnosis_regenconf_manually_modified_details": "Es probablament bon tan que sabètz çò que fasètz ;) !", + "diagnosis_regenconf_nginx_conf_broken": "La configuracion de nginx sembla èsser copada !", + "diagnosis_security_vulnerable_to_meltdown": "Semblatz èsser vulnerable a la vulnerabilitat de seguretat critica de Meltdown", + "diagnosis_description_basesystem": "Sistèma de basa", + "diagnosis_description_ip": "Connectivitat Internet", + "diagnosis_description_dnsrecords": "Enregistraments DNS", + "diagnosis_description_services": "Verificacion d’estat de servicis", + "diagnosis_description_systemresources": "Resorgas sistèma", + "diagnosis_description_ports": "Exposicion dels pòrts", + "diagnosis_description_http": "Exposicion HTTP", + "diagnosis_description_security": "Verificacion de seguretat", + "diagnosis_ports_unreachable": "Lo pòrt {port} es pas accessible de l’exterior.", + "diagnosis_ports_ok": "Lo pòrt {port} es accessible de l’exterior.", + "diagnosis_http_unreachable": "Lo domeni {domain} es pas accessible via HTTP de l’exterior.", + "diagnosis_unknown_categories": "La categorias seguentas son desconegudas : {categories}", + "diagnosis_ram_low": "Lo sistèma a {available_abs_MB} Mo ({available_percent}%) de memòria RAM disponibla d’un total de {total_abs_MB} MB). Atencion.", + "diagnosis_regenconf_manually_modified_debian": "Lo fichier de configuracion {file} foguèt modificat manualament respècte al fichièr per defaut de Debian.", + "log_permission_create": "Crear la permission « {} »", + "log_permission_delete": "Suprimir la permission « {} »", + "log_user_group_create": "Crear lo grop « {} »", + "log_user_permission_update": "Actualizacion dels accèsses per la permission « {} »", + "operation_interrupted": "L’operacion es estada interrompuda manualament ?" } From eaf9bfafcc631628172a4b0a3180dcc9aae510fc Mon Sep 17 00:00:00 2001 From: xaloc33 Date: Thu, 7 Nov 2019 10:42:27 +0000 Subject: [PATCH 0466/3170] Translated using Weblate (Catalan) Currently translated at 100.0% (607 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ca/ --- locales/ca.json | 71 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 69 insertions(+), 2 deletions(-) diff --git a/locales/ca.json b/locales/ca.json index 9a00b5c07..3a40712ca 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -162,7 +162,7 @@ "admin_password_too_long": "Trieu una contrasenya de menys de 127 caràcters", "dpkg_is_broken": "No es pot fer això en aquest instant perquè dpkg/APT (els gestors de paquets del sistema) sembla estar mal configurat… Podeu intentar solucionar-ho connectant-vos per SSH i executant «sudo dpkg --configure -a».", "dnsmasq_isnt_installed": "sembla que dnsmasq no està instal·lat, executeu \"apt-get remove bind9 && apt-get install dnsmasq\"", - "domain_cannot_remove_main": "No es pot eliminar el domini principal. S'ha d'establir un nou domini primer", + "domain_cannot_remove_main": "No es pot eliminar «{domain:s}» ja que és el domini principal, primer s'ha d'establir un nou domini principal utilitzant «yunohost domain main-domain -n », aquí hi ha una llista dels possibles dominis: {other_domains:s}", "domain_cert_gen_failed": "No s'ha pogut generar el certificat", "domain_created": "S'ha creat el domini", "domain_creation_failed": "No s'ha pogut crear el domini {domain}: {error}", @@ -627,5 +627,72 @@ "permission_currently_allowed_for_visitors": "El permís ja el tenen el grup de visitants a més d'altres grups. Segurament s'hauria de revocar el permís al grup dels visitants o eliminar els altres grups als que s'ha atribuït.", "permission_currently_allowed_for_all_users": "El permís ha el té el grup de tots els usuaris (all_users) a més d'altres grups. Segurament s'hauria de revocar el permís a «all_users» o eliminar els altres grups als que s'ha atribuït.", "permission_require_account": "El permís {permission} només té sentit per als usuaris que tenen un compte, i per tant no es pot activar per als visitants.", - "app_remove_after_failed_install": "Eliminant l'aplicació després que hagi fallat la instal·lació…" + "app_remove_after_failed_install": "Eliminant l'aplicació després que hagi fallat la instal·lació…", + "diagnosis_basesystem_ynh_main_version": "El servidor funciona amb YunoHost {main_version} ({repo})", + "diagnosis_ram_low": "El sistema només té {available_abs_MB} MB ({available_percent}%) de memòria RAM disponibles d'un total de {total_abs_MB} MB. Aneu amb compte.", + "diagnosis_swap_none": "El sistema no té swap. Hauríeu de considerar afegir un mínim de 256 MB 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_nginx_conf_broken": "Sembla que s'ha trencat la configuració NGINX!", + "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. Error: {error}", + "domain_cannot_remove_main_add_new_one": "No es pot eliminar «{domain:s}» ja que és el domini principal i únic domini, primer s'ha d'afegir un altre domini utilitzant «yunohost domain add », i després fer-lo el domini principal amb «yunohost domain main-domain -n » i després es pot eliminar el domini «{domain:s}» utilitzant «yunohost domain remove {domain:s}».", + "diagnosis_basesystem_host": "El servidor funciona amb Debian {debian_version}.", + "diagnosis_basesystem_kernel": "El servidor funciona amb el nucli de Linux {kernel_version}", + "diagnosis_basesystem_ynh_single_version": "{0} versió: {1}({2})", + "diagnosis_basesystem_ynh_inconsistent_versions": "Esteu utilitzant versions inconsistents dels paquets de YunoHost… probablement a causa d'una actualització fallida o parcial.", + "diagnosis_display_tip_web": "Podeu anar a la secció de Diagnòstics (en la pantalla principal) per veure els errors que s'han trobat.", + "diagnosis_failed_for_category": "Ha fallat el diagnòstic per la categoria «{category}» : {error}", + "diagnosis_display_tip_cli": "Podeu executar «yunohost diagnosis show --issues» per mostrar els errors que s'han trobat.", + "diagnosis_cache_still_valid": "(La memòria cau encara és vàlida pel diagnòstic de {category}. No es tornar a diagnosticar de moment!)", + "diagnosis_cant_run_because_of_dep": "No es pot fer el diagnòstic per {category} mentre hi ha problemes importants relacionats amb {dep}.", + "diagnosis_ignored_issues": "(+ {nb_ignored} problema(es) ignorat(s))", + "diagnosis_found_errors": "S'ha trobat problema(es) important(s) {errors} relacionats amb {category}!", + "diagnosis_found_errors_and_warnings": "S'ha trobat problema(es) important(s) {errors} (i avis(os) {warnings}) relacionats amb {category}!", + "diagnosis_found_warnings": "S'han trobat ítems {warnings} que es podrien millorar per {category}.", + "diagnosis_everything_ok": "Tot sembla correcte per {category}!", + "diagnosis_failed": "No s'han pogut obtenir els resultats del diagnòstic per la categoria «{category}» : {error}", + "diagnosis_ip_connected_ipv4": "El servidor està connectat a Internet amb IPv4!", + "diagnosis_ip_no_ipv4": "El servidor no té una IPv4 que funcioni.", + "diagnosis_ip_connected_ipv6": "El servidor està connectat a Internet amb IPv6!", + "diagnosis_ip_no_ipv6": "El servidor no té una IPv6 que funcioni.", + "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 via /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 de tipus {0}, nom {1} i valor {2}", + "diagnosis_dns_discrepancy": "Segons la configuració DNS recomanada, el valor pel registre DNS de tipus {0} i nom {1} hauria de ser {2}, en comptes de {3}.", + "diagnosis_services_good_status": "El servei {service} està {status} tal i com s'esperava!", + "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_abs_GB} GB ({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_abs_GB} GB ({free_percent}%). Aneu amb compte.", + "diagnosis_diskusage_ok": "El lloc d'emmagatzematge {mountpoint} (en l'aparell {device}) encara té {free_abs_GB} GB ({free_percent}%) lliures!", + "diagnosis_ram_verylow": "El sistema només té {available_abs_MB} MB ({available_percent}%) de memòria RAM disponibles! (d'un total de {total_abs_MB} MB)", + "diagnosis_ram_ok": "El sistema encara té {available_abs_MB} MB ({available_percent}%) de memòria RAM disponibles d'un total de {total_abs_MB} MB.", + "diagnosis_swap_notsomuch": "El sistema només té {total_MB} MB de swap. Hauríeu de considerar tenir un mínim de 256 MB per evitar situacions en les que el sistema es queda sense memòria.", + "diagnosis_swap_ok": "El sistema té {total_MB} MB 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_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.", + "diagnosis_security_vulnerable_to_meltdown": "Sembla que el sistema és vulnerable a la vulnerabilitat de seguretat crítica Meltdown", + "diagnosis_description_basesystem": "Sistema de base", + "diagnosis_description_ip": "Connectivitat a Internet", + "diagnosis_description_dnsrecords": "Registres DNS", + "diagnosis_description_services": "Verificació de l'estat dels serveis", + "diagnosis_description_systemresources": "Recursos del sistema", + "diagnosis_description_ports": "Exposició dels ports", + "diagnosis_description_http": "Exposició HTTP", + "diagnosis_description_regenconf": "Configuració del sistema", + "diagnosis_description_security": "Verificacions de seguretat", + "diagnosis_ports_could_not_diagnose": "No s'ha pogut diagnosticar si els ports són accessibles des de l'exterior. Error: {error}", + "diagnosis_ports_unreachable": "El port {port} no és accessible des de l'exterior.", + "diagnosis_ports_ok": "El port {port} és accessible des de l'exterior.", + "diagnosis_http_ok": "El domini {domain} és accessible des de l'exterior.", + "diagnosis_http_unreachable": "El domini {domain} no és accessible a través de HTTP des de l'exterior.", + "diagnosis_unknown_categories": "Les següents categories són desconegudes: {categories}" } From cba136d42d4f5c4a223d4cbc5f8d35fff48cb1dd Mon Sep 17 00:00:00 2001 From: amirale qt Date: Thu, 7 Nov 2019 12:48:53 +0000 Subject: [PATCH 0467/3170] Translated using Weblate (French) Currently translated at 96.4% (585 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 51 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/locales/fr.json b/locales/fr.json index 645e285ad..5fb72d679 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -665,5 +665,54 @@ "app_install_failed": "Impossible d'installer {app}: {error}", "app_install_script_failed": "Une erreur est survenue dans le script d'installation de l'application", "permission_require_account": "Permission {permission} n'a de sens que pour les utilisateurs ayant un compte et ne peut donc pas être activé pour les visiteurs.", - "app_remove_after_failed_install": "Supprimer l'application après l'échec de l'installation…" + "app_remove_after_failed_install": "Supprimer l'application après l'échec de l'installation…", + "diagnosis_display_tip_web": "Vous pouvez aller à la section Diagnostic (dans l'écran d'accueil) pour voir les problèmes rencontrés.", + "diagnosis_cant_run_because_of_dep": "Impossible d'exécuter le diagnostic pour {category} alors qu'il existe des problèmes importants liés à {dep}.", + "diagnosis_found_errors": "Trouvé {errors} problème(s) significatif(s) lié(s) à {category} !", + "diagnosis_found_errors_and_warnings": "Trouvé {errors} problème(s) significatif(s) (et {warnings} (avertissement(s)) en relation avec {category} !", + "diagnosis_ip_not_connected_at_all": "Le serveur ne semble pas du tout connecté à Internet !?", + "diagnosis_ip_weird_resolvconf": "La résolution DNS semble fonctionner, mais soyer prudent en utilisant un fichier /etc/resolv.conf personnalisé.", + "diagnosis_ip_weird_resolvconf_details": "Au lieu de cela, ce fichier devrait être un lien symbolique vers /etc/resolvconf/run/resolv.conf lui-même pointant vers 127.0.0.1 (dnsmasq). Les résolveurs réels doivent être configurés via /etc/resolv.dnsmasq.conf.", + "diagnosis_dns_missing_record": "Selon la configuration DNS recommandée, vous devez ajouter un enregistrement DNS de type {0}, nom {1} et valeur {2}", + "diagnosis_diskusage_ok": "Le stockage {mountpoint} (sur le périphérique {device}) a toujours {espace libre {free_abs_GB} GB ({free_percent}%) !", + "diagnosis_ram_ok": "Le système dispose toujours de {available_abs_MB} MB ({available_percent}%) de RAM sur {total_abs_MB} MB.", + "diagnosis_regenconf_allgood": "Tous les fichiers de configuration sont conformes à la configuration recommandée !", + "diagnosis_security_vulnerable_to_meltdown": "Vous semblez vulnérable à la vulnérabilité de sécurité critique de Meltdown", + "diagnosis_basesystem_host": "Le serveur exécute Debian {debian_version}.", + "diagnosis_basesystem_kernel": "Le serveur exécute le noyau Linux {kernel_version}", + "diagnosis_basesystem_ynh_single_version": "{0} version: {1} ({2})", + "diagnosis_basesystem_ynh_main_version": "Le serveur exécute YunoHost {main_version} ({repo})", + "diagnosis_basesystem_ynh_inconsistent_versions": "Vous exécutez des versions incohérentes des packages YunoHost ... probablement à cause d'une mise à niveau partielle ou échouée.", + "diagnosis_display_tip_cli": "Vous pouvez exécuter 'yunohost diagnosis show --issues' pour afficher les problèmes détectés.", + "diagnosis_failed_for_category": "Échec du diagnostic pour la catégorie '{category}' : {error}", + "diagnosis_cache_still_valid": "(Le cache est toujours valide pour le diagnostic {category}. Pas re-diagnostiquer pour le moment!)", + "diagnosis_ignored_issues": "(+ {nb_ignored} questions ignorée(s))", + "diagnosis_found_warnings": "Trouvé {warnings} objet(s) pouvant être amélioré(s) pour {category}.", + "diagnosis_everything_ok": "Tout semble bien pour {category} !", + "diagnosis_failed": "Impossible d'extraire le résultat du diagnostic pour la catégorie '{category}': {error}", + "diagnosis_ip_connected_ipv4": "Le serveur est connecté à Internet via IPv4 !", + "diagnosis_ip_no_ipv4": "Le serveur ne dispose pas d’une adresse IPv4 active.", + "diagnosis_ip_connected_ipv6": "Le serveur est connecté à Internet via IPv6 !", + "diagnosis_ip_no_ipv6": "Le serveur ne dispose pas d'une adresse IPv6 active.", + "diagnosis_ip_dnsresolution_working": "La résolution de nom de domaine fonctionne !", + "diagnosis_ip_broken_dnsresolution": "La résolution du nom de domaine semble interrompue pour une raison quelconque ... Un pare-feu bloque-t-il les requêtes DNS ?", + "diagnosis_ip_broken_resolvconf": "La résolution du nom de domaine semble cassée sur votre serveur, ce qui semble lié au fait que /etc/resolv.conf ne pointe pas vers 127.0.0.1.", + "diagnosis_dns_good_conf": "Bonne configuration DNS pour le domaine {domain} (catégorie {category})", + "diagnosis_dns_bad_conf": "Configuration DNS incorrecte/manquante pour le domaine {domain} (catégorie {category})", + "diagnosis_dns_discrepancy": "Selon la configuration DNS recommandée, la valeur de l'enregistrement DNS de type {0} et nom {1} doit être {2} et non {3}.", + "diagnosis_services_bad_status": "Le service {service} est {status} :/", + "diagnosis_services_good_status": "Le service {service} est {status} comme prévu !", + "diagnosis_diskusage_verylow": "Le stockage {mountpoint} (sur le périphérique {device}) ne dispose que de {free_abs_GB} Go ({free_percent}%). Vous devriez vraiment envisager de nettoyer un peu d'espace.", + "diagnosis_diskusage_low": "Le stockage {mountpoint} (sur le périphérique {device}) ne dispose que de {free_abs_GB} Go ({free_percent}%). Faites attention.", + "diagnosis_ram_verylow": "Le système ne dispose plus que de {available_abs_MB} MB ({available_percent}%)! (sur {total_abs_MB} Mo)", + "diagnosis_ram_low": "Le système n'a plus de {available_abs_MB} MB ({available_percent}%) RAM sur {total_abs_MB} MB. Faites attention.", + "diagnosis_swap_none": "Le système n'a aucun échange. Vous devez envisager d’ajouter au moins 256 Mo de swap pour éviter les situations où le système manque de mémoire.", + "diagnosis_swap_notsomuch": "Le système ne dispose que de {total_MB} Mo de swap. Vous devez envisager d'avoir au moins 256 Mo pour éviter les situations où le système manque de mémoire.", + "diagnosis_swap_ok": "Le système dispose de {total_MB} Mo de swap !", + "diagnosis_regenconf_manually_modified": "Le fichier de configuration {file} a été modifié manuellement.", + "diagnosis_regenconf_manually_modified_debian": "Le fichier de configuration {file} a été modifié manuellement par rapport à celui par défaut de Debian.", + "diagnosis_regenconf_manually_modified_details": "C'est probablement OK tant que vous savez ce que vous faites;) !", + "diagnosis_regenconf_manually_modified_debian_details": "Cela peut probablement être OK, mais il faut garder un œil dessus ...", + "diagnosis_regenconf_nginx_conf_broken": "La configuration de nginx semble être cassée !", + "diagnosis_security_all_good": "Aucune vulnérabilité de sécurité critique n'a été trouvée." } From f28f990349c7f929f156f071322575bddc636da1 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Wed, 13 Nov 2019 21:00:09 +0000 Subject: [PATCH 0468/3170] Translated using Weblate (Esperanto) Currently translated at 83.9% (506 of 603 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/eo/ --- locales/eo.json | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/locales/eo.json b/locales/eo.json index a25fa505e..7fe36db64 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -564,5 +564,16 @@ "permission_currently_allowed_for_all_users": "Ĉi tiu permeso estas nuntempe donita al ĉiuj uzantoj aldone al aliaj grupoj. Vi probable volas aŭ forigi la permeson \"all_users\" aŭ forigi la aliajn grupojn, kiujn ĝi nuntempe donas.", "app_install_failed": "Ne povis instali {app} : {error}", "app_install_script_failed": "Eraro okazis en la skripto de instalado de la app", - "app_remove_after_failed_install": "Forigado de la app post la instala fiasko …" + "app_remove_after_failed_install": "Forigado de la app post la instala fiasko …", + "diagnosis_basesystem_host": "Servilo funkcias Debian {debian_version}.", + "apps_catalog_init_success": "Aplikoj katalogsistemo inicializita !", + "apps_catalog_updating": "Ĝisdatigante katalogo de aplikoj ...", + "apps_catalog_failed_to_download": "Ne eblas elŝuti la katalogon de {apps_catalog}: {error}", + "apps_catalog_obsolete_cache": "La kaŝmemoro de la katalogo de programoj estas malplena aŭ malaktuala.", + "apps_catalog_update_success": "La aplika katalogo estis ĝisdatigita!", + "diagnosis_basesystem_kernel": "Servilo funkcias Linuksan kernon {kernel_version}", + "diagnosis_basesystem_ynh_single_version": "{0} versio: {1} ({2})", + "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." } From bce2dfae265ca43db3cbe931144e7e9ca522e599 Mon Sep 17 00:00:00 2001 From: xaloc33 Date: Wed, 13 Nov 2019 22:44:19 +0000 Subject: [PATCH 0469/3170] Translated using Weblate (Catalan) Currently translated at 100.0% (603 of 603 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ca/ --- locales/ca.json | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/locales/ca.json b/locales/ca.json index 3a40712ca..848838bda 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -308,7 +308,7 @@ "migration_description_0007_ssh_conf_managed_by_yunohost_step1": "La configuració SSH serà gestionada per YunoHost (pas 1, automàtic)", "migration_description_0008_ssh_conf_managed_by_yunohost_step2": "La configuració SSH serà gestionada per YunoHost (pas 2, manual)", "migration_description_0009_decouple_regenconf_from_services": "Desvincula el mecanisme regen-conf dels serveis", - "migration_description_0010_migrate_to_apps_json": "Elimina les llistes d'aplicacions obsoletes i utilitza la nova llista unificada «apps.json» en el seu lloc", + "migration_description_0010_migrate_to_apps_json": "Elimina els catàlegs d'aplicacions obsolets i utilitza la nova llista unificada «apps.json» en el seu lloc (obsolet, substituït per la migració 13)", "migration_0003_backward_impossible": "La migració Stretch no és reversible.", "migration_0003_start": "Ha començat la migració a Stretch. Els registres estaran disponibles a {logfile}.", "migration_0003_patching_sources_list": "Modificant el fitxer sources.lists…", @@ -320,7 +320,7 @@ "migration_0003_system_not_fully_up_to_date": "El vostre sistema no està completament actualitzat. S'ha de fer una actualització normal abans de fer la migració a Stretch.", "migration_0003_still_on_jessie_after_main_upgrade": "Hi ha hagut un problema durant l'actualització principal: El sistema encara està amb Jessie? Per investigar el problema, mireu el registres a {log}:s…", "migration_0003_general_warning": "Tingueu en compte que la migració és una operació delicada. L'equip de YunoHost a fet els possibles per revisar-la i provar-la, però la migració pot provocar errors en parts del sistema o aplicacions.\n\nPer tant, es recomana:\n - Fer una còpia de seguretat de les dades o aplicacions importants. Més informació a https://yunohost.org/backup;\n - Sigueu pacient un cop llençada la migració: en funció de la connexió a internet i el maquinari, pot trigar fins a unes hores per actualitzar-ho tot.\n\nD'altra banda, el port per SMTP, utilitzat per clients de correu externs (com Thunderbird o K9-Mail) ha canviat de 465 (SSL/TLS) a 587 (STARTTLS). L'antic port (465) serà tancat automàticament, i el nou port (587) serà obert en el tallafocs. Tots els usuaris *hauran* d'adaptar la configuració dels clients de correu en acord amb aquests canvis.", - "migration_0003_problematic_apps_warning": "Tingueu en compte que s'han detectat les aplicacions, possiblement, problemàtiques següents. Sembla que aquestes no s'han instal·lat des d'una applist, o que no estan marcades com a «working». Per conseqüent, no podem garantir que segueixin funcionant després de l'actualització: {problematic_apps}", + "migration_0003_problematic_apps_warning": "Tingueu en compte que s'han detectat les aplicacions, possiblement, problemàtiques següents. Sembla que aquestes no s'han instal·lat des d'un catàleg d'aplicacions, o que no estan marcades com a «working». Per conseqüent, no podem garantir que segueixin funcionant després de l'actualització: {problematic_apps}", "migration_0003_modified_files": "Tingueu en compte que s'han detectat els següents fitxers que han estat modificats manualment i podrien sobreescriure's al final de l'actualització: {manually_modified_files}", "migration_0005_postgresql_94_not_installed": "PostgreSQL no està instal·lat en el sistema. No hi ha res per fer.", "migration_0005_postgresql_96_not_installed": "PostgreSQL 9.4 està instal·lat, però no PostgreSQL 9.6? Alguna cosa estranya a passat en el sistema :( …", @@ -694,5 +694,14 @@ "diagnosis_ports_ok": "El port {port} és accessible des de l'exterior.", "diagnosis_http_ok": "El domini {domain} és accessible des de l'exterior.", "diagnosis_http_unreachable": "El domini {domain} no és accessible a través de HTTP des de l'exterior.", - "diagnosis_unknown_categories": "Les següents categories són desconegudes: {categories}" + "diagnosis_unknown_categories": "Les següents categories són desconegudes: {categories}", + "apps_catalog_init_success": "S'ha iniciat el sistema de catàleg d'aplicacions!", + "apps_catalog_updating": "S'està actualitzant el catàleg d'aplicacions…", + "apps_catalog_failed_to_download": "No s'ha pogut descarregar el catàleg d'aplicacions {apps_catalog}: {error}", + "apps_catalog_obsolete_cache": "La memòria cau del catàleg d'aplicacions és buida o obsoleta.", + "apps_catalog_update_success": "S'ha actualitzat el catàleg d'aplicacions!", + "diagnosis_mail_ougoing_port_25_ok": "El port de sortida 25 no està bloquejat i els correus es poden enviar a altres servidors.", + "diagnosis_mail_ougoing_port_25_blocked": "Sembla que el port de sortida 25 està bloquejat. Hauríeu d'intentar desbloquejar-lo al panell de configuració del proveïdor d'accés a internet (o allotjador). Mentrestant, el servidor no podrà enviar correus a altres servidors.", + "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" } From b40ccc6063628e4edcd506488ef29ced18b0c655 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Wed, 13 Nov 2019 20:18:22 +0000 Subject: [PATCH 0470/3170] Translated using Weblate (French) Currently translated at 100.0% (603 of 603 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 39 +++++++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 5fb72d679..1300657cf 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -77,7 +77,7 @@ "domain_created": "Le domaine a été créé", "domain_creation_failed": "Impossible de créer le domaine {domain}: {error}", "domain_deleted": "Le domaine a été supprimé", - "domain_deletion_failed": "Impossible de supprimer le domaine {domain}: {error}", + "domain_deletion_failed": "Impossible de supprimer le domaine {domain}:{error}", "domain_dyndns_already_subscribed": "Vous avez déjà souscris à un domaine DynDNS", "domain_dyndns_invalid": "Domaine incorrect pour un usage avec DynDNS", "domain_dyndns_root_unknown": "Domaine DynDNS principal inconnu", @@ -270,7 +270,7 @@ "ldap_init_failed_to_create_admin": "L’initialisation de l'annuaire LDAP n’a pas réussi à créer l’utilisateur admin", "ssowat_persistent_conf_read_error": "Impossible de lire la configuration persistante de SSOwat : {error:s}. Modifiez le fichier /etc/ssowat/conf.json.persistent pour réparer la syntaxe JSON", "ssowat_persistent_conf_write_error": "Impossible de sauvegarder de la configuration persistante de SSOwat : {error:s}. Modifiez le fichier /etc/ssowat/conf.json.persistent pour réparer la syntaxe JSON", - "domain_cannot_remove_main": "Impossible de supprimer le domaine principal. Définissez d'abord un nouveau domaine principal", + "domain_cannot_remove_main": "Vous ne pouvez pas supprimer '{domain: s}' car il s'agit du domaine principal. Vous devez d'abord définir un autre domaine comme domaine principal à l'aide de 'yunohost domain main-domain -n ', voici la liste des domaines candidats. : {other_domains: s}", "certmanager_self_ca_conf_file_not_found": "Le fichier de configuration pour l’autorité du certificat auto-signé est introuvable (fichier : {file:s})", "certmanager_unable_to_parse_self_CA_name": "Impossible d’analyser le nom de l’autorité du certificat auto-signé (fichier : {file:s})", "mailbox_used_space_dovecot_down": "Le service de courriel Dovecot doit être démarré, si vous souhaitez voir l’espace disque occupé par la messagerie", @@ -394,7 +394,7 @@ "migration_0003_system_not_fully_up_to_date": "Votre système n’est pas complètement à jour. Veuillez mener une mise à jour classique avant de lancer à migration à Stretch.", "migration_0003_still_on_jessie_after_main_upgrade": "Quelque chose s’est mal passé pendant la mise à niveau principale : le système est toujours sur Debian Jessie !? Pour investiguer sur le problème, veuillez regarder les journaux {log}:s …", "migration_0003_general_warning": "Veuillez noter que cette migration est une opération délicate. Si l’équipe YunoHost a fait de son mieux pour la relire et la tester, la migration pourrait tout de même casser des parties de votre système ou de vos applications.\n\nEn conséquence, nous vous recommandons :\n - de lancer une sauvegarde de vos données ou applications critiques. Plus d’informations sur https://yunohost.org/backup ;\n - d’être patient après avoir lancé la migration : selon votre connexion internet et matériel, cela pourrait prendre jusqu’à quelques heures pour que tout soit à niveau.\n\nEn outre, le port SMTP utilisé par les clients de messagerie externes comme (Thunderbird ou K9-Mail) a été changé de 465 (SSL/TLS) à 587 (STARTTLS). L’ancien port 465 sera automatiquement fermé et le nouveau port 587 sera ouvert dans le pare-feu. Vous et vos utilisateurs *devront* adapter la configuration de vos clients de messagerie en conséquence.", - "migration_0003_problematic_apps_warning": "Veuillez noter que des applications possiblement problématiques ont été détectées. Il semble qu’elles n’aient pas été installées depuis une liste d’application ou qu’elles ne soit pas marquées comme « fonctionnelles ». En conséquence, nous ne pouvons pas garantir qu’elles fonctionneront après la mise à niveau : {problematic_apps}", + "migration_0003_problematic_apps_warning": "Veuillez noter que les applications installées potentiellement problématiques suivantes ont été détectées. Il semble que celles-ci n'ont pas été installées à partir d'un catalogue d'applications, ou ne sont pas marquées comme \"working \". Par conséquent, il ne peut pas être garanti qu'ils fonctionneront toujours après la mise à niveau: {problematic_apps}", "migration_0003_modified_files": "Veuillez noter que les fichiers suivants ont été détectés comme modifiés manuellement et pourraient être écrasés à la fin de la mise à niveau : {manually_modified_files}", "migrations_list_conflict_pending_done": "Vous ne pouvez pas utiliser --previous et --done simultanément.", "migrations_to_be_ran_manually": "La migration {id} doit être lancée manuellement. Veuillez aller dans Outils > Migrations dans l’interface admin, ou lancer `yunohost tools migrations migrate`.", @@ -541,7 +541,7 @@ "global_settings_setting_security_ssh_compatibility": "Compatibilité versus compromis sécuritaire pour le serveur SSH. Affecte les cryptogrammes (et d'autres aspects liés à la sécurité)", "global_settings_setting_security_postfix_compatibility": "Compatibilité versus compromis sécuritaire pour le serveur Postfix. Affecte les cryptogrammes (et d'autres aspects liés à la sécurité)", "migration_description_0009_decouple_regenconf_from_services": "Dissocier le mécanisme « regen-conf » des services", - "migration_description_0010_migrate_to_apps_json": "Supprimer les listes d'applications obsolètes et utiliser la nouvelle liste unifiée 'apps.json' à la place", + "migration_description_0010_migrate_to_apps_json": "Supprimez les catalogues d'applications obsolètes et utilisez à la place la nouvelle liste unifiée 'apps.json' (obsolète, remplacée par la migration 13).", "regenconf_file_kept_back": "Le fichier de configuration '{conf}' devait être supprimé par « regen-conf » (catégorie {category}) mais a été conservé.", "regenconf_updated": "La configuration a été mise à jour pour la catégorie '{category}'", "regenconf_would_be_updated": "La configuration aurait dû être mise à jour pour la catégorie '{category}'", @@ -625,7 +625,7 @@ "migrations_running_forward": "Exécution de la migration {id}…", "migrations_success_forward": "Migration {id} terminée", "need_define_permission_before": "Redéfinissez l'autorisation à l'aide de 'yunohost user permission add -u USER' avant de supprimer un groupe autorisé", - "operation_interrupted": "L'opération a-t-elle été interrompue manuellement ?", + "operation_interrupted": "L'opération a été interrompue manuellement ?", "permission_already_clear": "L'autorisation '{permission: s}' est déjà vide pour l'application {app: s}", "permission_already_exist": "L'autorisation '{permission}' existe déjà", "permission_created": "Permission '{permission:s}' créée", @@ -714,5 +714,32 @@ "diagnosis_regenconf_manually_modified_details": "C'est probablement OK tant que vous savez ce que vous faites;) !", "diagnosis_regenconf_manually_modified_debian_details": "Cela peut probablement être OK, mais il faut garder un œil dessus ...", "diagnosis_regenconf_nginx_conf_broken": "La configuration de nginx semble être cassée !", - "diagnosis_security_all_good": "Aucune vulnérabilité de sécurité critique n'a été trouvée." + "diagnosis_security_all_good": "Aucune vulnérabilité de sécurité critique n'a été trouvée.", + "apps_catalog_init_success": "Système de catalogue d'applications initialisé !", + "apps_catalog_failed_to_download": "Impossible de télécharger le catalogue des applications {apps_catalog}:{error}", + "diagnosis_mail_ougoing_port_25_blocked": "Le port sortant 25 semble être bloqué. Vous devriez essayer de le débloquer dans le panneau de configuration de votre fournisseur de services Internet (ou hébergeur). En attendant, le serveur ne pourra pas envoyer de courrier électronique à d'autres serveurs.", + "domain_cannot_remove_main_add_new_one": "Vous ne pouvez pas supprimer '{domain:s}' car il s'agit du domaine principal et de votre seul domaine. Vous devez d'abord ajouter un autre domaine à l'aide de 'yunohost domain add ', puis définir comme domaine principal à l'aide de ' yunohost domain main-domain -n ' et vous pouvez ensuite supprimer le domaine '{domaine: s}' à l'aide de 'yunohost domain remove {domain:s}'.'", + "diagnosis_security_vulnerable_to_meltdown_details": "Pour résoudre ce problème, vous devez mettre à niveau votre système et redémarrer pour charger le nouveau noyau Linux (ou contacter votre fournisseur de serveur si cela ne fonctionne pas). Voir https://meltdownattack.com/ pour plus d'informations.", + "diagnosis_description_basesystem": "Système de base", + "diagnosis_description_ip": "Connectivité Internet", + "diagnosis_description_dnsrecords": "Enregistrements DNS", + "diagnosis_description_services": "Vérification de l'état des services", + "diagnosis_description_systemresources": "Ressources système", + "diagnosis_description_ports": "Exposition des ports", + "diagnosis_description_http": "Exposition HTTP", + "diagnosis_description_regenconf": "Configurations système", + "diagnosis_description_security": "Contrôles de sécurité", + "diagnosis_ports_could_not_diagnose": "Impossible de diagnostiquer si les ports sont accessibles de l'extérieur. Erreur: {error}", + "apps_catalog_updating": "Mise à jour du catalogue d'applications...", + "apps_catalog_obsolete_cache": "Le cache du catalogue d'applications est vide ou obsolète.", + "apps_catalog_update_success": "Le catalogue des applications a été mis à jour !", + "diagnosis_mail_ougoing_port_25_ok": "Le port sortant 25 n'est pas bloqué et le courrier électronique peut être envoyé à d'autres serveurs.", + "diagnosis_description_mail": "Email", + "diagnosis_ports_unreachable": "Le port {port} n'est pas accessible de l'extérieur.", + "diagnosis_ports_ok": "Le port {port} est accessible de l'extérieur.", + "diagnosis_http_could_not_diagnose": "Impossible de diagnostiquer si le domaine est accessible de l'extérieur. Erreur: {error}", + "diagnosis_http_ok": "Le domaine {domain} est accessible de l'extérieur.", + "diagnosis_http_unreachable": "Le domaine {domain} est inaccessible via HTTP de l'extérieur.", + "diagnosis_unknown_categories": "Les catégories suivantes sont inconnues: {categories}", + "migration_description_0013_futureproof_apps_catalog_system": "Migrer vers le nouveau système de catalogue d'applications à l'épreuve du temps" } From c6dbb6292b07cf7f3f6d03585ab51d40f4659891 Mon Sep 17 00:00:00 2001 From: advocatux Date: Wed, 13 Nov 2019 12:16:01 +0000 Subject: [PATCH 0471/3170] Translated using Weblate (Spanish) Currently translated at 89.6% (540 of 603 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/es/ --- locales/es.json | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/locales/es.json b/locales/es.json index d526f442f..e33d2c3ae 100644 --- a/locales/es.json +++ b/locales/es.json @@ -643,5 +643,20 @@ "diagnosis_basesystem_kernel": "El servidor está ejecutando el núcleo de Linux {kernel_version}", "diagnosis_basesystem_ynh_single_version": "{0} versión: {1} ({2})", "diagnosis_basesystem_ynh_main_version": "El servidor está ejecutando YunoHost {main_version} ({repo})", - "diagnosis_basesystem_ynh_inconsistent_versions": "Está ejecutando versiones incoherentes de los paquetes de YunoHost... probablemente por una actualización errónea o parcial." + "diagnosis_basesystem_ynh_inconsistent_versions": "Está ejecutando versiones incoherentes de los paquetes de YunoHost... probablemente por una actualización errónea o parcial.", + "diagnosis_failed_for_category": "Diagnóstico fallido para la categoría «{category}» : {error}", + "diagnosis_cache_still_valid": "(Caché aún válida para el diagnóstico de {category}. ¡Aún no se ha rediagnosticado!)", + "diagnosis_found_errors_and_warnings": "¡Encontrado(s) error(es) significativo(s) {errors} (y aviso(s) {warnings}) relacionado(s) con {category}!", + "diagnosis_display_tip_web": "Puede ir a la sección de diagnóstico (en la pantalla principal) para ver los problemas encontrados.", + "diagnosis_display_tip_cli": "Puede ejecutar «yunohost diagnosis show --issues» para mostrar los problemas encontrados.", + "apps_catalog_init_success": "¡Sistema de catálogo de aplicaciones inicializado!", + "apps_catalog_updating": "Actualizando catálogo de aplicaciones...", + "apps_catalog_failed_to_download": "No se pudo descargar el catálogo de aplicaciones {apps_catalog}: {error}", + "apps_catalog_obsolete_cache": "La caché del catálogo de aplicaciones está vacía u obsoleta.", + "apps_catalog_update_success": "¡El catálogo de aplicaciones ha sido actualizado!", + "diagnosis_cant_run_because_of_dep": "No se puede ejecutar el diagnóstico para {category} mientras haya problemas importantes relacionados con {dep}.", + "diagnosis_ignored_issues": "(+ {nb_ignored} problema(s) ignorado(s))", + "diagnosis_found_errors": "¡Encontrado(s) error(es) significativo(s) {errors} relacionado(s) con {category}!", + "diagnosis_found_warnings": "Encontrado elemento(s) {warnings} que puede(n) ser mejorado(s) para {category}.", + "diagnosis_everything_ok": "¡Todo se ve bien para {category}!" } From ee226ff2634f343e4400b0f2eb3bb0f4e30b78d5 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Fri, 15 Nov 2019 08:51:30 +0000 Subject: [PATCH 0472/3170] Translated using Weblate (Esperanto) Currently translated at 84.7% (505 of 596 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/eo/ --- locales/eo.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/locales/eo.json b/locales/eo.json index 7fe36db64..4b8a76d3c 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -575,5 +575,9 @@ "diagnosis_basesystem_ynh_single_version": "{0} versio: {1} ({2})", "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_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_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}" } From 174d1d6b98ddc4d3e42a9cdae91e5f403433f715 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quent=C3=AD?= Date: Thu, 14 Nov 2019 16:53:08 +0000 Subject: [PATCH 0473/3170] Translated using Weblate (Occitan) Currently translated at 42.1% (251 of 596 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/oc/ --- locales/oc.json | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/locales/oc.json b/locales/oc.json index f848bae1b..aa233d8b8 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -684,5 +684,19 @@ "log_permission_delete": "Suprimir la permission « {} »", "log_user_group_create": "Crear lo grop « {} »", "log_user_permission_update": "Actualizacion dels accèsses per la permission « {} »", - "operation_interrupted": "L’operacion es estada interrompuda manualament ?" + "operation_interrupted": "L’operacion es estada interrompuda manualament ?", + "group_cannot_be_edited": "Lo grop « {group} » pòt pas èsser modificat manualament.", + "group_cannot_be_deleted": "Lo grop « {group} » pòt pas èsser suprimit manualament.", + "diagnosis_found_warnings": "Trobat {warnings} element(s) que se poirián melhorar per {category}.", + "diagnosis_dns_missing_record": "Segon la configuracion DNS recomandada, vos calriá ajustar un enregistrament DNS de tipe {0}, nom {1} e valor {2}", + "diagnosis_dns_discrepancy": "Segon la configuracion DNS recomandada, la valor per l’enregistrament DNS de tipe {0} e nom {1} deuriá èsser {2} allòc de {3}.", + "diagnosis_regenconf_manually_modified_debian_details": "Es pas problematic, mas car téner d’agacher...", + "diagnosis_ports_could_not_diagnose": "Impossible de diagnosticar se los pòrts son accessibles de l’exterior. Error : {error}", + "diagnosis_http_could_not_diagnose": "Impossible de diagnosticar se lo domeni es accessible de l’exterior. Error : {error}", + "apps_catalog_updating": "Actualizacion del catalòg d’aplicacion…", + "apps_catalog_failed_to_download": "Telecargament impossible del catalòg d’aplicacions {apps_catalog} : {error}", + "apps_catalog_obsolete_cache": "La memòria cache del catalòg d’aplicacion es voida o obsolèta.", + "apps_catalog_update_success": "Lo catalòg d’aplicacions es a jorn !", + "diagnosis_mail_ougoing_port_25_ok": "Lo pòrt de sortida 25 es pas blocat e lo corrièr electronic pòt partir als autres servidors.", + "diagnosis_description_mail": "Corrièl" } From 68a67a9ac14b1f787a8a73fbd17d66cd9e414958 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 15 Nov 2019 16:48:54 +0100 Subject: [PATCH 0474/3170] Update changelog for 3.7.0.2 --- debian/changelog | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/debian/changelog b/debian/changelog index 578a8e314..e7926cd95 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,15 @@ +yunohost (3.7.0.2) testing; urgency=low + + - [fix] Make sure the users actually exists when migrating legacy custom permissions + - [mod] Move debug log dump from ynh_exit_properly to the core after failed app operation (#833) + - [enh] Improve app_upgrade error management (#832) + - [mod] Refactor group permission (#837) + - [enh] Add permission name in permission callback when adding/removing allowed users (#836) + - [enh] Improve permission helpers (#840) + - [i18n] Improve translations for German, Catalan, Swedish, Spanish, Turkish, Basque, French, Esperanto, Occitan + + -- Alexandre Aubin Fri, 15 Nov 2019 16:45:00 +0000 + yunohost (3.7.0.1) testing; urgency=low - Hotfix to avoid having a shitload of warnings displayed during the permission migration From 6124d1b8994b0a1f2ebc9f33453bfda8901afb05 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 16 Nov 2019 15:33:51 +0100 Subject: [PATCH 0475/3170] Only run these commands if the variables are not empty --- data/helpers.d/systemd | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/data/helpers.d/systemd b/data/helpers.d/systemd index 4b3b5a289..105678b88 100644 --- a/data/helpers.d/systemd +++ b/data/helpers.d/systemd @@ -165,9 +165,15 @@ ynh_systemd_action() { # # usage: ynh_clean_check_starting ynh_clean_check_starting () { - # Stop the execution of tail. - kill -s 15 $pid_tail 2>&1 - ynh_secure_remove "$templog" 2>&1 + if [ -n "$pid_tail" ] + then + # Stop the execution of tail. + kill -s 15 $pid_tail 2>&1 + fi + if [ -n "$templog" ] + then + ynh_secure_remove "$templog" 2>&1 + fi } From 4e0dbe313421500a174abf5e4bc4c6adb2ba0680 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 16 Nov 2019 15:46:23 +0100 Subject: [PATCH 0476/3170] Improve ynh_secure_remove to detect empty args instead of miserably saying 'Avoid deleting .' --- data/helpers.d/utils | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index a359423bb..d449f0c39 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -312,22 +312,23 @@ ynh_secure_remove () { ynh_print_warn --message="/!\ Packager ! You provided more than one argument to ynh_secure_remove but it will be ignored... Use this helper with one argument at time." fi - if [[ "$forbidden_path" =~ "$file" \ + if [[ -z "$file" ]] + then + ynh_print_warn --message="ynh_secure_remove called with empty argument, ignoring." + else if [[ "$forbidden_path" =~ "$file" \ # Match all paths or subpaths in $forbidden_path || "$file" =~ ^/[[:alnum:]]+$ \ # Match all first level paths from / (Like /var, /root, etc...) || "${file:${#file}-1}" = "/" ]] # Match if the path finishes by /. Because it seems there is an empty variable then - ynh_print_warn --message="Avoid deleting $file." + ynh_print_warn --message="Not deleting '$file' because it is not an acceptable path to delete." + else if [ -e "$file" ] + then + rm -R "$file" else - if [ -e "$file" ] - then - sudo rm -R "$file" - else - ynh_print_info --message="$file wasn't deleted because it doesn't exist." - fi - fi + ynh_print_info --message="'$file' wasn't deleted because it doesn't exist." + fi fi fi } # Extract a key from a plain command output From 61e6840ed3c913cc7ecf84b01727a09e48ae40e0 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 16 Nov 2019 15:33:51 +0100 Subject: [PATCH 0477/3170] Only run these commands if the variables are not empty --- data/helpers.d/systemd | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/data/helpers.d/systemd b/data/helpers.d/systemd index 4b3b5a289..105678b88 100644 --- a/data/helpers.d/systemd +++ b/data/helpers.d/systemd @@ -165,9 +165,15 @@ ynh_systemd_action() { # # usage: ynh_clean_check_starting ynh_clean_check_starting () { - # Stop the execution of tail. - kill -s 15 $pid_tail 2>&1 - ynh_secure_remove "$templog" 2>&1 + if [ -n "$pid_tail" ] + then + # Stop the execution of tail. + kill -s 15 $pid_tail 2>&1 + fi + if [ -n "$templog" ] + then + ynh_secure_remove "$templog" 2>&1 + fi } From 551ff807923eb74a1783e8abdd3e41cc22f3bfad Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 16 Nov 2019 15:46:23 +0100 Subject: [PATCH 0478/3170] Improve ynh_secure_remove to detect empty args instead of miserably saying 'Avoid deleting .' --- data/helpers.d/utils | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index a359423bb..d449f0c39 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -312,22 +312,23 @@ ynh_secure_remove () { ynh_print_warn --message="/!\ Packager ! You provided more than one argument to ynh_secure_remove but it will be ignored... Use this helper with one argument at time." fi - if [[ "$forbidden_path" =~ "$file" \ + if [[ -z "$file" ]] + then + ynh_print_warn --message="ynh_secure_remove called with empty argument, ignoring." + else if [[ "$forbidden_path" =~ "$file" \ # Match all paths or subpaths in $forbidden_path || "$file" =~ ^/[[:alnum:]]+$ \ # Match all first level paths from / (Like /var, /root, etc...) || "${file:${#file}-1}" = "/" ]] # Match if the path finishes by /. Because it seems there is an empty variable then - ynh_print_warn --message="Avoid deleting $file." + ynh_print_warn --message="Not deleting '$file' because it is not an acceptable path to delete." + else if [ -e "$file" ] + then + rm -R "$file" else - if [ -e "$file" ] - then - sudo rm -R "$file" - else - ynh_print_info --message="$file wasn't deleted because it doesn't exist." - fi - fi + ynh_print_info --message="'$file' wasn't deleted because it doesn't exist." + fi fi fi } # Extract a key from a plain command output From bb8b1b052df4d90a86b993616dc0c70249523fb9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 17 Nov 2019 16:29:12 +0100 Subject: [PATCH 0479/3170] Using /var/log/daemon.log or /var/log/syslog is pointless, these files logs many different things. Instead, we shall always return the logs from journalctl --- data/hooks/conf_regen/01-yunohost | 6 ++++++ data/templates/yunohost/services.yml | 12 ++++-------- locales/en.json | 1 - src/yunohost/service.py | 8 ++++---- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/data/hooks/conf_regen/01-yunohost b/data/hooks/conf_regen/01-yunohost index f22de7a53..7b9644c2a 100755 --- a/data/hooks/conf_regen/01-yunohost +++ b/data/hooks/conf_regen/01-yunohost @@ -101,6 +101,12 @@ for service, conf in new_services.items(): if conffiles: services[service]['conffiles'] = conffiles + # Remove legacy /var/log/daemon.log and /var/log/syslog from log entries + # because they are too general. Instead, now the journalctl log is + # returned by default which is more relevant. + if "log" in services[service]: + if services[service]["log"] in ["/var/log/syslog", "/var/log/daemon.log"]: + del services[service]["log"] if updated: with open('/etc/yunohost/services.yml-new', 'w') as f: diff --git a/data/templates/yunohost/services.yml b/data/templates/yunohost/services.yml index 351120a7d..540224f3a 100644 --- a/data/templates/yunohost/services.yml +++ b/data/templates/yunohost/services.yml @@ -2,10 +2,8 @@ nginx: log: /var/log/nginx test_conf: nginx -t needs_exposed_ports: [80, 443] -avahi-daemon: - log: /var/log/daemon.log -dnsmasq: - log: /var/log/daemon.log +avahi-daemon: {} +dnsmasq: {} fail2ban: log: /var/log/fail2ban.log dovecot: @@ -29,8 +27,7 @@ ssh: metronome: log: [/var/log/metronome/metronome.log,/var/log/metronome/metronome.err] needs_exposed_ports: [5222, 5269] -slapd: - log: /var/log/syslog +slapd: {} php7.0-fpm: log: /var/log/php7.0-fpm.log test_conf: php-fpm7.0 --test @@ -39,8 +36,7 @@ yunohost-api: yunohost-firewall: need_lock: true test_status: iptables -S | grep "^-A INPUT" | grep " --dport" | grep -q ACCEPT -nslcd: - log: /var/log/syslog +nslcd: {} glances: null nsswitch: null ssl: null diff --git a/locales/en.json b/locales/en.json index 7cdd6b667..6a6f629b4 100644 --- a/locales/en.json +++ b/locales/en.json @@ -544,7 +544,6 @@ "service_disabled": "The '{service:s}' service was turned off", "service_enable_failed": "Could not turn on the service '{service:s}'\n\nRecent service logs:{logs:s}", "service_enabled": "The '{service:s}' service was turned off", - "service_no_log": "No logs to display for the service '{service:s}'", "service_regen_conf_is_deprecated": "'yunohost service regen-conf' is deprecated! Please use 'yunohost tools regen-conf' instead.", "service_remove_failed": "Could not remove the service '{service:s}'", "service_removed": "'{service:s}' service removed", diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 0125fd7e4..f43d7cca5 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -395,10 +395,7 @@ def service_log(name, number=50): if name not in services.keys(): raise YunohostError('service_unknown', service=name) - if 'log' not in services[name]: - raise YunohostError('service_no_log', service=name) - - log_list = services[name]['log'] + log_list = services[name].get('log', []) log_type_list = services[name].get('log_type', []) if not isinstance(log_list, list): @@ -408,6 +405,9 @@ def service_log(name, number=50): result = {} + # First we always add the logs from journalctl / systemd + result["journalctl"] = _get_journalctl_logs(name, int(number)).splitlines() + for index, log_path in enumerate(log_list): log_type = log_type_list[index] From a7a3e7b6baccf80035b0e95c6d15fb33beb5cbd1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 17 Nov 2019 16:39:41 +0100 Subject: [PATCH 0480/3170] Try to keep this service list in alphabetic order or something --- data/templates/yunohost/services.yml | 40 ++++++++++++++-------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/data/templates/yunohost/services.yml b/data/templates/yunohost/services.yml index 540224f3a..c2f57c245 100644 --- a/data/templates/yunohost/services.yml +++ b/data/templates/yunohost/services.yml @@ -1,42 +1,42 @@ +avahi-daemon: {} +dnsmasq: {} +dovecot: + log: [/var/log/mail.log,/var/log/mail.err] + needs_exposed_ports: [993] +fail2ban: + log: /var/log/fail2ban.log +metronome: + log: [/var/log/metronome/metronome.log,/var/log/metronome/metronome.err] + needs_exposed_ports: [5222, 5269] +mysql: + log: [/var/log/mysql.log,/var/log/mysql.err] + alternates: ['mariadb'] nginx: log: /var/log/nginx test_conf: nginx -t needs_exposed_ports: [80, 443] -avahi-daemon: {} -dnsmasq: {} -fail2ban: - log: /var/log/fail2ban.log -dovecot: - log: [/var/log/mail.log,/var/log/mail.err] - needs_exposed_ports: [993] +nslcd: {} +php7.0-fpm: + log: /var/log/php7.0-fpm.log + test_conf: php-fpm7.0 --test postfix: log: [/var/log/mail.log,/var/log/mail.err] test_status: systemctl show postfix@- | grep -q "^SubState=running" needs_exposed_ports: [25, 587] -rspamd: - log: /var/log/rspamd/rspamd.log redis-server: log: /var/log/redis/redis-server.log -mysql: - log: [/var/log/mysql.log,/var/log/mysql.err] - alternates: ['mariadb'] +rspamd: + log: /var/log/rspamd/rspamd.log +slapd: {} ssh: log: /var/log/auth.log test_conf: sshd -t needs_exposed_ports: [22] -metronome: - log: [/var/log/metronome/metronome.log,/var/log/metronome/metronome.err] - needs_exposed_ports: [5222, 5269] -slapd: {} -php7.0-fpm: - log: /var/log/php7.0-fpm.log - test_conf: php-fpm7.0 --test yunohost-api: log: /var/log/yunohost/yunohost-api.log yunohost-firewall: need_lock: true test_status: iptables -S | grep "^-A INPUT" | grep " --dport" | grep -q ACCEPT -nslcd: {} glances: null nsswitch: null ssl: null From 7986f61b14b68d8cad505e0ead49971ad984514f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 17 Nov 2019 16:59:35 +0100 Subject: [PATCH 0481/3170] Specific shit for mysql --- data/templates/yunohost/services.yml | 2 +- src/yunohost/service.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/data/templates/yunohost/services.yml b/data/templates/yunohost/services.yml index c2f57c245..b3c406f0f 100644 --- a/data/templates/yunohost/services.yml +++ b/data/templates/yunohost/services.yml @@ -9,7 +9,7 @@ metronome: log: [/var/log/metronome/metronome.log,/var/log/metronome/metronome.err] needs_exposed_ports: [5222, 5269] mysql: - log: [/var/log/mysql.log,/var/log/mysql.err] + log: [/var/log/mysql.log,/var/log/mysql.err,/var/log/mysql/error.log] alternates: ['mariadb'] nginx: log: /var/log/nginx diff --git a/src/yunohost/service.py b/src/yunohost/service.py index f43d7cca5..548d1efda 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -408,6 +408,10 @@ def service_log(name, number=50): # First we always add the logs from journalctl / systemd result["journalctl"] = _get_journalctl_logs(name, int(number)).splitlines() + # Mysql and journalctl are fucking annoying, we gotta explictly fetch mariadb ... + if name == "mysql": + result["journalctl"] = _get_journalctl_logs("mariadb", int(number)).splitlines() + for index, log_path in enumerate(log_list): log_type = log_type_list[index] From ccc06865c868e67ffe535ce483b5fe5ef6d30b59 Mon Sep 17 00:00:00 2001 From: xaloc33 Date: Fri, 15 Nov 2019 21:34:25 +0000 Subject: [PATCH 0482/3170] Translated using Weblate (Catalan) Currently translated at 100.0% (596 of 596 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ca/ --- locales/ca.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/locales/ca.json b/locales/ca.json index 91aaa8667..3b1b73a36 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -36,7 +36,7 @@ "app_unknown": "Aplicació desconeguda", "app_unsupported_remote_type": "El tipus remot utilitzat per l'aplicació no està suportat", "app_upgrade_app_name": "Actualitzant {app}…", - "app_upgrade_failed": "No s'ha pogut actualitzar {app:s}", + "app_upgrade_failed": "No s'ha pogut actualitzar {app:s}: {error}", "app_upgrade_some_app_failed": "No s'han pogut actualitzar algunes aplicacions", "app_upgraded": "S'ha actualitzat {app:s}", "appslist_corrupted_json": "No s'han pogut carregar les llistes d'aplicacions. Sembla que {filename:s} està danyat.", @@ -703,5 +703,6 @@ "diagnosis_mail_ougoing_port_25_ok": "El port de sortida 25 no està bloquejat i els correus es poden enviar a altres servidors.", "diagnosis_mail_ougoing_port_25_blocked": "Sembla que el port de sortida 25 està bloquejat. Hauríeu d'intentar desbloquejar-lo al panell de configuració del proveïdor d'accés a internet (o allotjador). Mentrestant, el servidor no podrà enviar correus a altres servidors.", "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" + "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ó" } From 9e9215cc78b597a11e361996451b253f6c75156b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 23 Aug 2019 18:15:07 +0200 Subject: [PATCH 0483/3170] Simpler LDAP configuration --- src/yunohost/utils/ldap.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/yunohost/utils/ldap.py b/src/yunohost/utils/ldap.py index c3b5065a1..22e95ad07 100644 --- a/src/yunohost/utils/ldap.py +++ b/src/yunohost/utils/ldap.py @@ -20,7 +20,7 @@ """ import atexit -from moulinette.core import init_authenticator +from moulinette.authenticators import ldap # We use a global variable to do some caching # to avoid re-authenticating in case we call _get_ldap_authenticator multiple times @@ -31,12 +31,16 @@ def _get_ldap_interface(): global _ldap_interface if _ldap_interface is None: - # Instantiate LDAP Authenticator - AUTH_IDENTIFIER = ('ldap', 'as-root') - AUTH_PARAMETERS = {'uri': 'ldapi://%2Fvar%2Frun%2Fslapd%2Fldapi', - 'base_dn': 'dc=yunohost,dc=org', - 'user_rdn': 'gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth'} - _ldap_interface = init_authenticator(AUTH_IDENTIFIER, AUTH_PARAMETERS) + + conf = { "vendor": "ldap", + "name": "as-root", + "parameters": { 'uri': 'ldapi://%2Fvar%2Frun%2Fslapd%2Fldapi', + 'base_dn': 'dc=yunohost,dc=org', + 'user_rdn': 'gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth' }, + "extra": {} + } + + _ldap_interface = ldap.Authenticator(**conf) return _ldap_interface From a959a97e8c7ba736bd87cf1c8a8e46ea8181dcee Mon Sep 17 00:00:00 2001 From: Kay0u Date: Wed, 20 Nov 2019 13:28:10 +0900 Subject: [PATCH 0484/3170] [Fix] permission create --- src/yunohost/permission.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 170019ee5..3f187a48c 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -273,9 +273,8 @@ def permission_create(operation_logger, permission, url=None, allowed=None, sync if allowed: if not isinstance(allowed, list): to_add = [allowed] - # For main permission, we add all users by default - elif permission.endswith(".main"): - to_add = "all_users" + else: + to_add = allowed new_permission = _update_ldap_group_permission(permission=permission, allowed=to_add, sync_perm=sync_perm) @@ -295,6 +294,10 @@ def permission_url(operation_logger, permission, url=None, sync_perm=True): from yunohost.utils.ldap import _get_ldap_interface ldap = _get_ldap_interface() + # By default, manipulate main permission + if "." not in permission: + permission = permission + ".main" + # Fetch existing permission existing_permission = user_permission_list(full=True)["permissions"].get(permission, None) From ff577886010079579dcf97448074cad220809618 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 20 Nov 2019 15:31:55 +0100 Subject: [PATCH 0485/3170] Improve messages wording ? More consistent service 'X' vs. 'X' service --- locales/en.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/locales/en.json b/locales/en.json index 0affa5da7..5102cabe6 100644 --- a/locales/en.json +++ b/locales/en.json @@ -486,7 +486,7 @@ "regenconf_file_updated": "Configuration file '{conf}' updated", "regenconf_now_managed_by_yunohost": "The configuration file '{conf}' is now managed by YunoHost (category {category}).", "regenconf_up_to_date": "The configuration is already up-to-date for category '{category}'", - "regenconf_updated": "Configuration for category '{category}' updated", + "regenconf_updated": "Configuration updated for '{category}'", "regenconf_would_be_updated": "The configuration would have been updated for category '{category}'", "regenconf_dry_pending_applying": "Checking pending configuration which would have been applied for category '{category}'…", "regenconf_failed": "Could not regenerate the configuration for category(s): {categories}", @@ -534,23 +534,23 @@ "service_description_ssh": "Allows you to connect remotely to your server via a terminal (SSH protocol)", "service_description_yunohost-api": "Manages interactions between the YunoHost web interface and the system", "service_description_yunohost-firewall": "Manages open and close connection ports to services", - "service_disable_failed": "Could not turn off the service '{service:s}'\n\nRecent service logs:{logs:s}", - "service_disabled": "The '{service:s}' service was turned off", - "service_enable_failed": "Could not turn on the service '{service:s}'\n\nRecent service logs:{logs:s}", - "service_enabled": "The '{service:s}' service was turned off", + "service_disable_failed": "Could not make the service '{service:s}' not start at boot.\n\nRecent service logs:{logs:s}", + "service_disabled": "The service '{service:s}' will not be started anymore when system boots.", + "service_enable_failed": "Could not make the service '{service:s}' automatically start at boot.\n\nRecent service logs:{logs:s}", + "service_enabled": "The service '{service:s}' will now be automatically started during system boots.", "service_regen_conf_is_deprecated": "'yunohost service regen-conf' is deprecated! Please use 'yunohost tools regen-conf' instead.", "service_remove_failed": "Could not remove the service '{service:s}'", - "service_removed": "'{service:s}' service removed", + "service_removed": "Service '{service:s}' removed", "service_reload_failed": "Could not reload the service '{service:s}'\n\nRecent service logs:{logs:s}", - "service_reloaded": "The '{service:s}' service was reloaded", + "service_reloaded": "Service '{service:s}' reloaded", "service_restart_failed": "Could not restart the service '{service:s}'\n\nRecent service logs:{logs:s}", - "service_restarted": "'{service:s}' service restarted", + "service_restarted": "Service '{service:s}' restarted", "service_reload_or_restart_failed": "Could not reload or restart the service '{service:s}'\n\nRecent service logs:{logs:s}", - "service_reloaded_or_restarted": "The '{service:s}' service was reloaded or restarted", + "service_reloaded_or_restarted": "The service '{service:s}' was reloaded or restarted", "service_start_failed": "Could not start the service '{service:s}'\n\nRecent service logs:{logs:s}", - "service_started": "'{service:s}' service started", + "service_started": "Service '{service:s}' started", "service_stop_failed": "Could not stop the service '{service:s}'\n\nRecent service logs:{logs:s}", - "service_stopped": "The '{service:s}' service stopped", + "service_stopped": "Service '{service:s}' stopped", "service_unknown": "Unknown service '{service:s}'", "ssowat_conf_generated": "SSOwat configuration generated", "ssowat_conf_updated": "SSOwat configuration updated", From 02d00eece001c98b96522c2d5e96f79b69f70212 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 20 Nov 2019 15:43:09 +0100 Subject: [PATCH 0486/3170] migrate -> run --- src/yunohost/app.py | 2 +- .../data_migrations/0013_futureproof_apps_catalog_system.py | 2 +- src/yunohost/tests/test_appscatalog.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 58676532c..b0606788b 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -2637,7 +2637,7 @@ def _read_apps_catalog_list(): if os.path.exists('/etc/yunohost/appslists.json'): from yunohost.tools import _get_migration_by_name migration = _get_migration_by_name("futureproof_apps_catalog_system") - migration.migrate() + migration.run() try: list_ = read_yaml(APPS_CATALOG_CONF) diff --git a/src/yunohost/data_migrations/0013_futureproof_apps_catalog_system.py b/src/yunohost/data_migrations/0013_futureproof_apps_catalog_system.py index 2215d4681..e5e1f43de 100644 --- a/src/yunohost/data_migrations/0013_futureproof_apps_catalog_system.py +++ b/src/yunohost/data_migrations/0013_futureproof_apps_catalog_system.py @@ -21,7 +21,7 @@ class MyMigration(Migration): "Migrate to the new future-proof apps catalog system" - def migrate(self): + def run(self): if not os.path.exists(LEGACY_APPS_CATALOG_CONF): logger.info("No need to do anything") diff --git a/src/yunohost/tests/test_appscatalog.py b/src/yunohost/tests/test_appscatalog.py index 450c2846e..613b59012 100644 --- a/src/yunohost/tests/test_appscatalog.py +++ b/src/yunohost/tests/test_appscatalog.py @@ -318,7 +318,7 @@ def test_apps_catalog_migrate_legacy_explicitly(): # Mock the server response with a dummy apps catalog m.register_uri("GET", APPS_CATALOG_DEFAULT_URL_FULL, text=DUMMY_APP_CATALOG) - migration.migrate() + migration.run() # Old conf shouldnt be there anymore (got renamed to .old) assert not os.path.exists("/etc/yunohost/appslists.json") From 4636bd20e19928febce9b5ca8f817302594d0e18 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 20 Nov 2019 17:12:23 +0100 Subject: [PATCH 0487/3170] Add diagnosis details for issues detected for HTTP exposure --- data/hooks/diagnosis/16-http.py | 6 ++++-- locales/en.json | 4 ++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/data/hooks/diagnosis/16-http.py b/data/hooks/diagnosis/16-http.py index 11d1c33dc..c7955c805 100644 --- a/data/hooks/diagnosis/16-http.py +++ b/data/hooks/diagnosis/16-http.py @@ -31,7 +31,7 @@ class HttpDiagnoser(Diagnoser): r = requests.post('https://diagnosis.yunohost.org/check-http', json={'domain': domain, "nonce": nonce}, timeout=30).json() if "status" not in r.keys(): raise Exception("Bad syntax for response ? Raw json: %s" % str(r)) - elif r["status"] == "error" and ("code" not in r.keys() or r["code"] not in ["error_http_check_connection_error", "error_http_check_unknown_error"]): + elif r["status"] == "error" and ("code" not in r.keys() or not r["code"].startswith("error_http_check_")): if "content" in r.keys(): raise Exception(r["content"]) else: @@ -44,9 +44,11 @@ class HttpDiagnoser(Diagnoser): status="SUCCESS", summary=("diagnosis_http_ok", {"domain": domain})) else: + detail = r["code"].replace("error_http_check", "diagnosis_http") if "code" in r else "diagnosis_http_unknown_error" yield dict(meta={"domain": domain}, status="ERROR", - summary=("diagnosis_http_unreachable", {"domain": domain})) + summary=("diagnosis_http_unreachable", {"domain": domain}), + details=[(detail,())]) # In there or idk where else ... # try to diagnose hairpinning situation by crafting a request for the diff --git a/locales/en.json b/locales/en.json index 5102cabe6..8643d8716 100644 --- a/locales/en.json +++ b/locales/en.json @@ -212,6 +212,10 @@ "diagnosis_ports_forwarding_tip": "To fix this issue, most probably you need to configure port forwarding on your internet router as described in https://yunohost.org/port_forwarding", "diagnosis_http_could_not_diagnose": "Could not diagnose if domain is reachable from outside. Error: {error}", "diagnosis_http_ok": "Domain {domain} is reachable from outside.", + "diagnosis_http_timeout": "Timed-out while trying to contact your server from outside. It appears to be unreachable. You should check that you're correctly forwarding port 80, that nginx is running, and that a firewall is not interfering.", + "diagnosis_http_connection_error": "Connection error: could not connect to the requested domain, it's very likely unreachable.", + "diagnosis_http_unknown_error": "An error happened while trying to reach your domain, it's very likely unreachable.", + "diagnosis_http_bad_status_code": "Could not reach your server as expected, it returned a bad status code. It might be that another machine answered instead of your server. You should check that you're correctly forwarding port 80, that your nginx configuration is up to date, and that a reverse-proxy is not interfering.", "diagnosis_http_unreachable": "Domain {domain} is unreachable through HTTP from outside.", "diagnosis_unknown_categories": "The following categories are unknown : {categories}", "domain_cannot_remove_main": "You cannot remove '{domain:s}' since it's the main domain, you need first to set another domain as the main domain using 'yunohost domain main-domain -n ', here is the list of candidate domains: {other_domains:s}", From f5c40512dba8606a27f415c08f98b011d0525794 Mon Sep 17 00:00:00 2001 From: ppr Date: Mon, 18 Nov 2019 19:32:40 +0000 Subject: [PATCH 0488/3170] Translated using Weblate (French) Currently translated at 99.8% (598 of 599 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index bf2c305c6..49973b489 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -28,7 +28,7 @@ "app_sources_fetch_failed": "Impossible de récupérer les fichiers sources, l'URL est-elle correcte ?", "app_unknown": "Application inconnue", "app_unsupported_remote_type": "Ce type de commande à distance utilisé pour cette application n'est pas supporté", - "app_upgrade_failed": "Impossible de mettre à jour {app:s}", + "app_upgrade_failed": "Impossible de mettre à jour {app:s} : {error}", "app_upgraded": "{app:s} mis à jour", "appslist_fetched": "La liste d’applications mise à jour '{appslist:s}'", "appslist_removed": "La liste d'applications '{appslist:s}' a été supprimée", @@ -462,7 +462,7 @@ "log_tools_shutdown": "Éteindre votre serveur", "log_tools_reboot": "Redémarrer votre serveur", "mail_unavailable": "Cette adresse de courriel est réservée et doit être automatiquement attribuée au tout premier utilisateur", - "migration_description_0004_php5_to_php7_pools": "Reconfigurez les groupes PHP pour utiliser PHP 7 au lieu de PHP 5", + "migration_description_0004_php5_to_php7_pools": "Reconfigurer les espaces utilisateurs PHP pour utiliser PHP 7 au lieu de PHP 5", "migration_description_0005_postgresql_9p4_to_9p6": "Migration des bases de données de PostgreSQL 9.4 vers PostgreSQL 9.6", "migration_0005_postgresql_94_not_installed": "PostgreSQL n’a pas été installé sur votre système. Rien à faire !", "migration_0005_postgresql_96_not_installed": "PostgreSQL 9.4 a été trouvé et installé, mais pas PostgreSQL 9.6 !? Quelque chose d’étrange a dû arriver à votre système… :(", @@ -541,7 +541,7 @@ "global_settings_setting_security_ssh_compatibility": "Compatibilité versus compromis sécuritaire pour le serveur SSH. Affecte les cryptogrammes (et d'autres aspects liés à la sécurité)", "global_settings_setting_security_postfix_compatibility": "Compatibilité versus compromis sécuritaire pour le serveur Postfix. Affecte les cryptogrammes (et d'autres aspects liés à la sécurité)", "migration_description_0009_decouple_regenconf_from_services": "Dissocier le mécanisme « regen-conf » des services", - "migration_description_0010_migrate_to_apps_json": "Supprimez les catalogues d'applications obsolètes et utilisez à la place la nouvelle liste unifiée 'apps.json' (obsolète, remplacée par la migration 13).", + "migration_description_0010_migrate_to_apps_json": "Supprimer les catalogues d'applications obsolètes afin d'utiliser la nouvelle liste unifiée 'apps.json' à la place (les anciens catalogues seront remplacés durant la migration 13)", "regenconf_file_kept_back": "Le fichier de configuration '{conf}' devait être supprimé par « regen-conf » (catégorie {category}) mais a été conservé.", "regenconf_updated": "La configuration a été mise à jour pour la catégorie '{category}'", "regenconf_would_be_updated": "La configuration aurait dû être mise à jour pour la catégorie '{category}'", @@ -589,7 +589,7 @@ "log_user_permission_add": "Mettre à jour l'autorisation pour '{}'", "log_user_permission_remove": "Mettre à jour l'autorisation pour '{}'", "mailbox_disabled": "La boîte aux lettres est désactivée pour l'utilisateur {user:s}", - "app_action_broke_system": "Cette action semble avoir cassé des services important : {services}", + "app_action_broke_system": "Cette action semble avoir cassé des services importants : {services}", "apps_already_up_to_date": "Toutes les applications sont déjà à jour", "app_upgrade_stopped": "La mise à niveau de toutes les applications s'est arrêtée pour éviter tout dommage, car une application n'a pas pu être mise à niveau.", "migration_0011_create_group": "Créer un groupe pour chaque utilisateur…", @@ -700,7 +700,7 @@ "diagnosis_dns_good_conf": "Bonne configuration DNS pour le domaine {domain} (catégorie {category})", "diagnosis_dns_bad_conf": "Configuration DNS incorrecte/manquante pour le domaine {domain} (catégorie {category})", "diagnosis_dns_discrepancy": "Selon la configuration DNS recommandée, la valeur de l'enregistrement DNS de type {0} et nom {1} doit être {2} et non {3}.", - "diagnosis_services_bad_status": "Le service {service} est {status} :/", + "diagnosis_services_bad_status": "Le service {service} est {status} :-(", "diagnosis_services_good_status": "Le service {service} est {status} comme prévu !", "diagnosis_diskusage_verylow": "Le stockage {mountpoint} (sur le périphérique {device}) ne dispose que de {free_abs_GB} Go ({free_percent}%). Vous devriez vraiment envisager de nettoyer un peu d'espace.", "diagnosis_diskusage_low": "Le stockage {mountpoint} (sur le périphérique {device}) ne dispose que de {free_abs_GB} Go ({free_percent}%). Faites attention.", @@ -741,5 +741,11 @@ "diagnosis_http_ok": "Le domaine {domain} est accessible de l'extérieur.", "diagnosis_http_unreachable": "Le domaine {domain} est inaccessible via HTTP de l'extérieur.", "diagnosis_unknown_categories": "Les catégories suivantes sont inconnues: {categories}", - "migration_description_0013_futureproof_apps_catalog_system": "Migrer vers le nouveau système de catalogue d'applications à l'épreuve du temps" + "migration_description_0013_futureproof_apps_catalog_system": "Migrer vers le nouveau système de catalogue d'applications à l'épreuve du temps", + "app_upgrade_script_failed": "Une erreur s'est produite durant l’exécution du script de mise à niveau de l'application", + "migration_description_0014_remove_app_status_json": "Supprimer les fichiers d'application status.json hérités", + "diagnosis_services_running": "Le service {service} s'exécute correctement !", + "diagnosis_services_conf_broken": "La configuration est cassée pour le service {service} !", + "diagnosis_ports_needed_by": "La mise en évidence de ce port est nécessaire pour le service {0}", + "diagnosis_ports_forwarding_tip": "Pour résoudre ce problème, vous devez très probablement configurer le transfert de port sur votre routeur Internet comme décrit dans https://yunohost.org/port_forwarding" } From 09bbd733b16328e6e7df01132091c6e527f25a93 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 20 Nov 2019 17:41:47 +0100 Subject: [PATCH 0489/3170] Disabling this report for now because it returns many warnings which are probably not important... --- data/hooks/diagnosis/70-regenconf.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/data/hooks/diagnosis/70-regenconf.py b/data/hooks/diagnosis/70-regenconf.py index a04f5f98d..a3e284f90 100644 --- a/data/hooks/diagnosis/70-regenconf.py +++ b/data/hooks/diagnosis/70-regenconf.py @@ -4,7 +4,8 @@ import os import subprocess from yunohost.diagnosis import Diagnoser -from yunohost.regenconf import manually_modified_files, manually_modified_files_compared_to_debian_default +from yunohost.regenconf import manually_modified_files +#from yunohost.regenconf import manually_modified_files, manually_modified_files_compared_to_debian_default class RegenconfDiagnoser(Diagnoser): @@ -16,7 +17,7 @@ class RegenconfDiagnoser(Diagnoser): def run(self): regenconf_modified_files = manually_modified_files() - debian_modified_files = manually_modified_files_compared_to_debian_default(ignore_handled_by_regenconf=True) + #debian_modified_files = manually_modified_files_compared_to_debian_default(ignore_handled_by_regenconf=True) if regenconf_modified_files == []: yield dict(meta={"test": "regenconf"}, @@ -31,12 +32,12 @@ class RegenconfDiagnoser(Diagnoser): details=[("diagnosis_regenconf_manually_modified_details", {})] ) - for f in debian_modified_files: - yield dict(meta={"test": "debian", "file": f}, - status="WARNING", - summary=("diagnosis_regenconf_manually_modified_debian", {"file": f}), - details=[("diagnosis_regenconf_manually_modified_debian_details", {})] - ) + #for f in debian_modified_files: + # yield dict(meta={"test": "debian", "file": f}, + # status="WARNING", + # summary=("diagnosis_regenconf_manually_modified_debian", {"file": f}), + # details=[("diagnosis_regenconf_manually_modified_debian_details", {})] + # ) def main(args, env, loggers): From 16f6d500a3b5d8a2ab04c5856283dd6daf05c04c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 20 Nov 2019 18:37:59 +0100 Subject: [PATCH 0490/3170] Better handle case where diagnosis cache is missing --- locales/en.json | 1 + src/yunohost/diagnosis.py | 49 +++++++++++++++++++++++---------------- 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/locales/en.json b/locales/en.json index 8643d8716..c3fd4f837 100644 --- a/locales/en.json +++ b/locales/en.json @@ -158,6 +158,7 @@ "diagnosis_found_warnings": "Found {warnings} item(s) that could be improved for {category}.", "diagnosis_everything_ok": "Everything looks good for {category}!", "diagnosis_failed": "Failed to fetch diagnosis result for category '{category}' : {error}", + "diagnosis_no_cache": "No diagnosis cache yet for category '{category}'", "diagnosis_ip_connected_ipv4": "The server is connected to the Internet through IPv4 !", "diagnosis_ip_no_ipv4": "The server does not have a working IPv4.", "diagnosis_ip_connected_ipv6": "The server is connected to the Internet through IPv6 !", diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index 121a0c2ae..018140a49 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -61,28 +61,37 @@ def diagnosis_show(categories=[], issues=False, full=False, share=False): # Fetch all reports all_reports = [] for category in categories: - try: - report = Diagnoser.get_cached_report(category) - except Exception as e: - logger.error(m18n.n("diagnosis_failed", category=category, error=str(e))) + if not os.path.exists(Diagnoser.cache_file(category)): + logger.warning(m18n.n("diagnosis_no_cache", category=category)) + report = {"id": category, + "cached_for": -1, + "timestamp": -1, + "items": []} + Diagnoser.i18n(report) else: - add_ignore_flag_to_issues(report) - if not full: - del report["timestamp"] - del report["cached_for"] - report["items"] = [item for item in report["items"] if not item["ignored"]] - for item in report["items"]: - del item["meta"] - del item["ignored"] - if "data" in item: - del item["data"] - if issues: - report["items"] = [item for item in report["items"] if item["status"] in ["WARNING", "ERROR"]] - # Ignore this category if no issue was found - if not report["items"]: - continue + try: + report = Diagnoser.get_cached_report(category) + except Exception as e: + logger.error(m18n.n("diagnosis_failed", category=category, error=str(e))) + continue - all_reports.append(report) + add_ignore_flag_to_issues(report) + if not full: + del report["timestamp"] + del report["cached_for"] + report["items"] = [item for item in report["items"] if not item["ignored"]] + for item in report["items"]: + del item["meta"] + del item["ignored"] + if "data" in item: + del item["data"] + if issues: + report["items"] = [item for item in report["items"] if item["status"] in ["WARNING", "ERROR"]] + # Ignore this category if no issue was found + if not report["items"]: + continue + + all_reports.append(report) if share: from yunohost.utils.yunopaste import yunopaste From 7c09982d3d3861b02bd9ff3dbbcc73a9aa7d518c Mon Sep 17 00:00:00 2001 From: Matthew DeAbreu Date: Wed, 20 Nov 2019 09:52:01 -0800 Subject: [PATCH 0491/3170] ensure metronome owns domain dir When adding new domains to Yunohost a directory for each newly added domain is created in `/var/lib/metronome` unfortunately since the directory is created with `sudo mkdir` that means `root:root` owns the directory. Metronome will now fail to write to the directory. --- data/hooks/conf_regen/12-metronome | 1 + 1 file changed, 1 insertion(+) diff --git a/data/hooks/conf_regen/12-metronome b/data/hooks/conf_regen/12-metronome index 4214722fc..f3df22317 100755 --- a/data/hooks/conf_regen/12-metronome +++ b/data/hooks/conf_regen/12-metronome @@ -51,6 +51,7 @@ do_post_regen() { # create metronome directories for domains for domain in $domain_list; do sudo mkdir -p "/var/lib/metronome/${domain//./%2e}/pep" + sudo chown -R metronome: /var/lib/metronome/${domain//./%2e}/ done [[ -z "$regen_conf_files" ]] \ From fa5c0e9a7053dcd9ba4ce0360dc0a09b590b69ed Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 20 Nov 2019 20:16:28 +0100 Subject: [PATCH 0492/3170] Ugh had some weird issue because for some reason in some context subprocess uses /bin/sh ... --- src/yunohost/service.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 548d1efda..9eb14012e 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -342,6 +342,7 @@ def service_status(names=[]): if "test_conf" in services[name]: p = subprocess.Popen(services[name]["test_conf"], shell=True, + executable='/bin/bash', stdout=subprocess.PIPE, stderr=subprocess.STDOUT) From a858a2072bac3fdcfd2e0a0df61faf51146c274c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 20 Nov 2019 20:17:11 +0100 Subject: [PATCH 0493/3170] Also remove the legacy cron job, since there was a name change --- .../data_migrations/0013_futureproof_apps_catalog_system.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/yunohost/data_migrations/0013_futureproof_apps_catalog_system.py b/src/yunohost/data_migrations/0013_futureproof_apps_catalog_system.py index e5e1f43de..90c46ea5b 100644 --- a/src/yunohost/data_migrations/0013_futureproof_apps_catalog_system.py +++ b/src/yunohost/data_migrations/0013_futureproof_apps_catalog_system.py @@ -30,6 +30,10 @@ class MyMigration(Migration): if os.path.exists(APPS_CATALOG_CACHE): shutil.rmtree(APPS_CATALOG_CACHE) + # and legacy cron + if os.path.exists("/etc/cron.daily/yunohost-fetch-appslists"): + os.remove("/etc/cron.daily/yunohost-fetch-appslists") + # Backup the legacy file try: legacy_catalogs = read_json(LEGACY_APPS_CATALOG_CONF) From 23489155fdca734a9de953a5516785aaea3dfa20 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Thu, 21 Nov 2019 18:13:20 +0900 Subject: [PATCH 0494/3170] symplify premission_create --- src/yunohost/permission.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 3f187a48c..489c6da77 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -267,16 +267,7 @@ def permission_create(operation_logger, permission, url=None, allowed=None, sync except Exception as e: raise YunohostError('permission_creation_failed', permission=permission, error=e) - to_add = None - - # If who should be allowed is explicitly provided, use this info - if allowed: - if not isinstance(allowed, list): - to_add = [allowed] - else: - to_add = allowed - - new_permission = _update_ldap_group_permission(permission=permission, allowed=to_add, sync_perm=sync_perm) + new_permission = _update_ldap_group_permission(permission=permission, allowed=allowed, sync_perm=sync_perm) logger.debug(m18n.n('permission_created', permission=permission)) return new_permission From a4160443766e353cee2ae6a79236718dd6d551f3 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Nov 2019 14:58:11 +0100 Subject: [PATCH 0495/3170] Add diagnosis cron job to be ran every 12 hours, managed from the regenconf --- data/hooks/conf_regen/01-yunohost | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/data/hooks/conf_regen/01-yunohost b/data/hooks/conf_regen/01-yunohost index 7b9644c2a..5528236cf 100755 --- a/data/hooks/conf_regen/01-yunohost +++ b/data/hooks/conf_regen/01-yunohost @@ -53,6 +53,16 @@ do_pre_regen() { else sudo cp services.yml /etc/yunohost/services.yml fi + + # add cron job for diagnosis to be ran at 7h and 19h + a random delay between + # 0 and 10min, meant to avoid every instances running their diagnosis at + # exactly the same time, which may overload the diagnosis server. + 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 +EOF + } _update_services() { From dd92a34202b6e1176c4fb35de443c9c71a1c7d0c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Nov 2019 15:10:44 +0100 Subject: [PATCH 0496/3170] Uh idk we also need to run this explicitly with /bin/bash --- src/yunohost/service.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 9eb14012e..f8553fbf7 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -335,8 +335,15 @@ def service_status(names=[]): # 'test_status' is an optional field to test the status of the service using a custom command if "test_status" in services[name]: - status = os.system(services[name]["test_status"]) - result[name]["status"] = "running" if status == 0 else "failed" + p = subprocess.Popen(services[name]["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" # 'test_status' is an optional field to test the status of the service using a custom command if "test_conf" in services[name]: From a719ab05a2b97ecc0659137111eb2f94f6979d96 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Wed, 20 Nov 2019 13:28:10 +0900 Subject: [PATCH 0497/3170] [Fix] permission create --- src/yunohost/permission.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 8e1be4451..87891e9d6 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -273,9 +273,8 @@ def permission_create(operation_logger, permission, url=None, allowed=None, sync if allowed: if not isinstance(allowed, list): to_add = [allowed] - # For main permission, we add all users by default - elif permission.endswith(".main"): - to_add = "all_users" + else: + to_add = allowed new_permission = _update_ldap_group_permission(permission=permission, allowed=to_add, sync_perm=sync_perm) @@ -295,6 +294,10 @@ def permission_url(operation_logger, permission, url=None, sync_perm=True): from yunohost.utils.ldap import _get_ldap_interface ldap = _get_ldap_interface() + # By default, manipulate main permission + if "." not in permission: + permission = permission + ".main" + # Fetch existing permission existing_permission = user_permission_list(full=True)["permissions"].get(permission, None) From 1b8e52896579335c77a1d47c841826d68372b753 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Thu, 21 Nov 2019 18:13:20 +0900 Subject: [PATCH 0498/3170] symplify premission_create --- src/yunohost/permission.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 87891e9d6..cfd806b1b 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -267,16 +267,7 @@ def permission_create(operation_logger, permission, url=None, allowed=None, sync except Exception as e: raise YunohostError('permission_creation_failed', permission=permission, error=e) - to_add = None - - # If who should be allowed is explicitly provided, use this info - if allowed: - if not isinstance(allowed, list): - to_add = [allowed] - else: - to_add = allowed - - new_permission = _update_ldap_group_permission(permission=permission, allowed=to_add, sync_perm=sync_perm) + new_permission = _update_ldap_group_permission(permission=permission, allowed=allowed, sync_perm=sync_perm) logger.debug(m18n.n('permission_created', permission=permission)) return new_permission From 4ab3653dfb6afcd45c20d37bc5c8e78c80e0d42e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Nov 2019 15:47:26 +0100 Subject: [PATCH 0499/3170] Add tip about how to fix / investigate broken services --- data/hooks/diagnosis/30-services.py | 4 ++-- locales/en.json | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/data/hooks/diagnosis/30-services.py b/data/hooks/diagnosis/30-services.py index 3649fdecc..a46fa735d 100644 --- a/data/hooks/diagnosis/30-services.py +++ b/data/hooks/diagnosis/30-services.py @@ -22,17 +22,17 @@ class ServicesDiagnoser(Diagnoser): if result["status"] != "running": item["status"] = "ERROR" item["summary"] = ("diagnosis_services_bad_status", {"service": service, "status": result["status"]}) + item["details"] = [("diagnosis_services_bad_status_tip", (service,))] elif result["configuration"] == "broken": item["status"] = "WARNING" item["summary"] = ("diagnosis_services_conf_broken", {"service": service}) + item["details"] = [(d, tuple()) for d in result["configuration-details"]] else: item["status"] = "SUCCESS" item["summary"] = ("diagnosis_services_running", {"service": service, "status": result["status"]}) - if result["configuration"] == "broken": - item["details"] = [(d, tuple()) for d in result["configuration-details"]] yield item def main(args, env, loggers): diff --git a/locales/en.json b/locales/en.json index c3fd4f837..b4b05900f 100644 --- a/locales/en.json +++ b/locales/en.json @@ -176,6 +176,7 @@ "diagnosis_services_running": "Service {service} is running!", "diagnosis_services_conf_broken": "Configuration is broken for service {service}!", "diagnosis_services_bad_status": "Service {service} is {status} :(", + "diagnosis_services_bad_status_tip": "You can try to restart the service, and if it doesn't work, have a look at the service logs using 'yunohost service log {0}' or through the 'Services' section of the webadmin.", "diagnosis_diskusage_verylow": "Storage {mountpoint} (on device {device}) has only {free_abs_GB} GB ({free_percent}%) space remaining. You should really consider cleaning up some space.", "diagnosis_diskusage_low": "Storage {mountpoint} (on device {device}) has only {free_abs_GB} GB ({free_percent}%) space remaining. Be careful.", "diagnosis_diskusage_ok": "Storage {mountpoint} (on device {device}) still has {free_abs_GB} GB ({free_percent}%) space left!", From bd78f93ff0d2804c9134a84693ef6f41ed1c5a5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9lanie=20Chauvel?= Date: Wed, 20 Nov 2019 18:12:16 +0000 Subject: [PATCH 0500/3170] Translated using Weblate (French) Currently translated at 99.5% (601 of 604 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 49973b489..e468ecc55 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -200,21 +200,21 @@ "service_configuration_conflict": "Le fichier {file:s} a été modifié depuis sa dernière génération. Veuillez y appliquer les modifications manuellement ou utiliser l’option --force (ce qui écrasera toutes les modifications effectuées sur le fichier).", "service_configured": "La configuration du service « {service:s} » a été générée avec succès", "service_configured_all": "La configuration de tous les services a été générée avec succès", - "service_disable_failed": "Impossible de désactiver le service '{service:s}'\n\nJournaux historisés récents : {logs:s}", - "service_disabled": "Le service '{service:s}' a été désactivé", - "service_enable_failed": "Impossible d’activer le service '{service:s}'\n\nJournaux historisés récents : {logs:s}", - "service_enabled": "Le service '{service:s}' a été activé", + "service_disable_failed": "Impossible de ne pas lancer le service « {service:s} » au démarrage.\n\nJournaux récents du service : {logs:s}", + "service_disabled": "Le service « {service:s} » ne sera plus lancé au démarrage du système.", + "service_enable_failed": "Impossible de lancer automatiquement le service « {service:s} » au démarrage.\n\nJournaux récents du service : {logs:s}", + "service_enabled": "Le service « {service:s} » sera désormais lancé automatiquement au démarrage du système.", "service_no_log": "Aucun journal à afficher pour le service '{service:s}'", "service_regenconf_dry_pending_applying": "Vérification des configurations en attentes qui pourraient être appliquées au le service '{service}' …", "service_regenconf_failed": "Impossible de régénérer la configuration pour les services : {services}", "service_regenconf_pending_applying": "Application des configurations en attentes pour le service '{service}' …", "service_remove_failed": "Impossible de supprimer le service '{service:s}'", - "service_removed": "Le service '{service:s}' a été supprimé", + "service_removed": "Le service « {service:s} » a été supprimé", "service_start_failed": "Impossible de démarrer le service '{service:s}'\n\nJournaux historisés récents : {logs:s}", - "service_started": "Le service '{service:s}' a été démarré", + "service_started": "Le service « {service:s} » a été démarré", "service_status_failed": "Impossible de déterminer le statut du service '{service:s}'", "service_stop_failed": "Impossible d’arrêter le service '{service:s}'\n\nJournaux historisés récents : {logs:s}", - "service_stopped": "Le service '{service:s}' a été arrêté", + "service_stopped": "Le service « {service:s} » a été arrêté", "service_unknown": "Le service '{service:s}' est inconnu", "services_configured": "La configuration a été générée avec succès", "show_diff": "Voici les différences :\n{diff:s}", @@ -517,11 +517,11 @@ "root_password_replaced_by_admin_password": "Votre mot de passe root a été remplacé par votre mot de passe administrateur.", "service_conf_now_managed_by_yunohost": "Le fichier de configuration '{conf}' est maintenant géré par YunoHost.", "service_reload_failed": "Impossible de recharger le service '{service:s}'.\n\nJournaux historisés récents de ce service : {logs:s}", - "service_reloaded": "Le service '{service:s}' a été rechargé", + "service_reloaded": "Le service « {service:s} » a été rechargé", "service_restart_failed": "Impossible de redémarrer le service '{service:s}'\n\nJournaux historisés récents de ce service : {logs:s}", - "service_restarted": "Le service '{service:s}' a été redémarré", + "service_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é", + "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`.", "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", @@ -543,7 +543,7 @@ "migration_description_0009_decouple_regenconf_from_services": "Dissocier le mécanisme « regen-conf » des services", "migration_description_0010_migrate_to_apps_json": "Supprimer les catalogues d'applications obsolètes afin d'utiliser la nouvelle liste unifiée 'apps.json' à la place (les anciens catalogues seront remplacés durant la migration 13)", "regenconf_file_kept_back": "Le fichier de configuration '{conf}' devait être supprimé par « regen-conf » (catégorie {category}) mais a été conservé.", - "regenconf_updated": "La configuration a été mise à jour pour la catégorie '{category}'", + "regenconf_updated": "La configuration a été mise à jour pour '{category}'", "regenconf_would_be_updated": "La configuration aurait dû être mise à jour pour la catégorie '{category}'", "regenconf_dry_pending_applying": "Vérification de la configuration en attente qui aurait été appliquée pour la catégorie '{category}' …", "regenconf_failed": "Impossible de régénérer la configuration pour la ou les catégorie(s) : '{categories}'", @@ -746,6 +746,9 @@ "migration_description_0014_remove_app_status_json": "Supprimer les fichiers d'application status.json hérités", "diagnosis_services_running": "Le service {service} s'exécute correctement !", "diagnosis_services_conf_broken": "La configuration est cassée pour le service {service} !", - "diagnosis_ports_needed_by": "La mise en évidence de ce port est nécessaire pour le service {0}", - "diagnosis_ports_forwarding_tip": "Pour résoudre ce problème, vous devez très probablement configurer le transfert de port sur votre routeur Internet comme décrit dans https://yunohost.org/port_forwarding" + "diagnosis_ports_needed_by": "Rendre ce port accessible est nécessaire pour le service {0}", + "diagnosis_ports_forwarding_tip": "Pour résoudre ce problème, vous devez très probablement configurer le transfert de port sur votre routeur Internet comme décrit dans https://yunohost.org/port_forwarding", + "diagnosis_http_connection_error": "Erreur de connexion : impossible de se connecter au domaine demandé, il est probablement injoignable.", + "diagnosis_no_cache": "Pas encore de diagnostique de cache pour la catégorie « {category} »", + "diagnosis_http_unknown_error": "Une erreur est survenue en essayant de joindre votre domaine, il est probablement injoignable." } From 9c9ea6d530afa5d461f0952cd342d54e67f42457 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Nov 2019 16:23:45 +0100 Subject: [PATCH 0501/3170] This fix disappeared in the merge mess at some point ... --- 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 b0606788b..5b4b72c3e 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -425,7 +425,7 @@ def app_change_url(operation_logger, app, domain, path): app_setting(app, 'domain', value=domain) app_setting(app, 'path', value=path) - permission_update(app, permission="main", add_url=[domain + path], remove_url=[old_domain + old_path], sync_perm=True) + app_ssowatconf() # avoid common mistakes if _run_service_command("reload", "nginx") is False: From a354425e3d18a321fe4add5eeef11e35e2f79da6 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Nov 2019 16:30:31 +0100 Subject: [PATCH 0502/3170] Improve / fix tip about port forwarding and DNS records --- locales/en.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/locales/en.json b/locales/en.json index b4b05900f..abf4831aa 100644 --- a/locales/en.json +++ b/locales/en.json @@ -171,8 +171,8 @@ "diagnosis_ip_weird_resolvconf_details": "Instead, this file should be a symlink to /etc/resolvconf/run/resolv.conf itself pointing to 127.0.0.1 (dnsmasq). The actual resolvers should be configured via /etc/resolv.dnsmasq.conf.", "diagnosis_dns_good_conf": "Good DNS configuration for domain {domain} (category {category})", "diagnosis_dns_bad_conf": "Bad / missing DNS configuration for domain {domain} (category {category})", - "diagnosis_dns_missing_record": "According to the recommended DNS configuration, you should add a DNS record with type {0}, name {1} and value {2}", - "diagnosis_dns_discrepancy": "According to the recommended DNS configuration, the value for the DNS record with type {0} and name {1} should be {2}, not {3}.", + "diagnosis_dns_missing_record": "According to the recommended DNS configuration, you should add a DNS record with type {0}, name {1} and value {2}. You can check https://yunohost.org/dns_config for more info.", + "diagnosis_dns_discrepancy": "The DNS record with type {0} and name {1} does not match the recommended configuration. Current value: {2}. Excepted value: {3}. You can check https://yunohost.org/dns_config for more info.", "diagnosis_services_running": "Service {service} is running!", "diagnosis_services_conf_broken": "Configuration is broken for service {service}!", "diagnosis_services_bad_status": "Service {service} is {status} :(", @@ -211,7 +211,7 @@ "diagnosis_ports_unreachable": "Port {port} is not reachable from outside.", "diagnosis_ports_ok": "Port {port} is reachable from outside.", "diagnosis_ports_needed_by": "Exposing this port is needed for service {0}", - "diagnosis_ports_forwarding_tip": "To fix this issue, most probably you need to configure port forwarding on your internet router as described in https://yunohost.org/port_forwarding", + "diagnosis_ports_forwarding_tip": "To fix this issue, most probably you need to configure port forwarding on your internet router as described in https://yunohost.org/isp_box_config", "diagnosis_http_could_not_diagnose": "Could not diagnose if domain is reachable from outside. Error: {error}", "diagnosis_http_ok": "Domain {domain} is reachable from outside.", "diagnosis_http_timeout": "Timed-out while trying to contact your server from outside. It appears to be unreachable. You should check that you're correctly forwarding port 80, that nginx is running, and that a firewall is not interfering.", From 6eb8efb6f08996cc532aa70b28a1a7bd91d725ab Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Nov 2019 16:41:43 +0100 Subject: [PATCH 0503/3170] Adjust tests because now all_users ain't added automatically by permission_create for .main perms --- src/yunohost/tests/test_permission.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index 5780ac455..b3fa9fefb 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -33,8 +33,8 @@ def setup_function(function): user_create("alice", "Alice", "White", "alice@" + maindomain, dummy_password) user_create("bob", "Bob", "Snow", "bob@" + maindomain, dummy_password) - permission_create("wiki.main", url="/", sync_perm=False) - permission_create("blog.main", sync_perm=False) + permission_create("wiki.main", url="/", allowed=["all_users"] , sync_perm=False) + permission_create("blog.main", allowed=["all_users"], sync_perm=False) user_permission_update("blog.main", remove="all_users", add="alice") @@ -217,7 +217,7 @@ def test_permission_list(): def test_permission_create_main(mocker): with message(mocker, "permission_created", permission="site.main"): - permission_create("site.main") + permission_create("site.main", allowed=["all_users"]) res = user_permission_list(full=True)['permissions'] assert "site.main" in res @@ -236,7 +236,7 @@ def test_permission_create_extra(mocker): assert res['site.test']['corresponding_users'] == [] -def test_permission_create_with_allowed(): +def test_permission_create_with_specific_user(): permission_create("site.test", allowed=["alice"]) res = user_permission_list(full=True)['permissions'] From 3b9481ae2d2d4d0b458baea3436e0969ff988d84 Mon Sep 17 00:00:00 2001 From: Matthew DeAbreu Date: Fri, 22 Nov 2019 09:02:01 -0800 Subject: [PATCH 0504/3170] Update 12-metronome simplify change by reordering operations --- data/hooks/conf_regen/12-metronome | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/data/hooks/conf_regen/12-metronome b/data/hooks/conf_regen/12-metronome index f3df22317..7047af660 100755 --- a/data/hooks/conf_regen/12-metronome +++ b/data/hooks/conf_regen/12-metronome @@ -41,19 +41,18 @@ do_pre_regen() { do_post_regen() { regen_conf_files=$1 - # fix some permissions - sudo chown -R metronome: /var/lib/metronome/ - sudo chown -R metronome: /etc/metronome/conf.d/ - # retrieve variables domain_list=$(sudo yunohost domain list --output-as plain --quiet) # create metronome directories for domains for domain in $domain_list; do sudo mkdir -p "/var/lib/metronome/${domain//./%2e}/pep" - sudo chown -R metronome: /var/lib/metronome/${domain//./%2e}/ done + # fix some permissions + sudo chown -R metronome: /var/lib/metronome/ + sudo chown -R metronome: /etc/metronome/conf.d/ + [[ -z "$regen_conf_files" ]] \ || sudo service metronome restart } From fdf5f16739a46b3441a1ec7ce58eb7b6ab5fe384 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Nov 2019 17:15:44 +0100 Subject: [PATCH 0505/3170] Warn early about unexisting user/groups (otherwise this triggers a journal entry for nothing) + simplify code, explicit assumptions --- src/yunohost/permission.py | 48 ++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 362025238..0b88254ce 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -91,9 +91,7 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, add -- List of groups or usernames to add to this permission remove -- List of groups or usernames to remove from to this permission """ - from yunohost.hook import hook_callback - from yunohost.utils.ldap import _get_ldap_interface - ldap = _get_ldap_interface() + from yunohost.user import user_group_list # By default, manipulate main permission if "." not in permission: @@ -115,10 +113,13 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, # Compute new allowed group list (and make sure what we're doing make sense) new_allowed_groups = copy.copy(current_allowed_groups) + all_existing_groups = user_group_list()['groups'].keys() if add: groups_to_add = [add] if not isinstance(add, list) else add for group in groups_to_add: + if group not in all_existing_groups: + raise YunohostError('group_unknown', group=group) if group in current_allowed_groups: logger.warning(m18n.n('permission_already_allowed', permission=permission, group=group)) else: @@ -170,9 +171,6 @@ def user_permission_reset(operation_logger, permission, sync_perm=True): Keyword argument: permission -- Name of the permission (e.g. mail or nextcloud or wordpress.editors) """ - from yunohost.hook import hook_callback - from yunohost.utils.ldap import _get_ldap_interface - ldap = _get_ldap_interface() # By default, manipulate main permission if "." not in permission: @@ -231,6 +229,7 @@ def permission_create(operation_logger, permission, url=None, allowed=None, sync """ from yunohost.utils.ldap import _get_ldap_interface + from yunohost.user import user_group_list ldap = _get_ldap_interface() # By default, manipulate main permission @@ -259,6 +258,13 @@ def permission_create(operation_logger, permission, url=None, allowed=None, sync if url: attr_dict['URL'] = url + # Validate that the groups to add actually exist + all_existing_groups = user_group_list()['groups'].keys() + allowed_ = [] if allowed is None else [allowed] if not isinstance(allowed, list) else allowed + for group in allowed_: + if group not in all_existing_groups: + raise YunohostError('group_unknown', group=group) + operation_logger.related_to.append(('app', permission.split(".")[0])) operation_logger.start() @@ -404,38 +410,34 @@ def permission_sync_to_user(): os.system('nscd --invalidate=passwd') os.system('nscd --invalidate=group') + def _update_ldap_group_permission(permission, allowed, sync_perm=True): """ Internal function that will rewrite user permission permission -- Name of the permission (e.g. mail or nextcloud or wordpress.editors) allowed -- A list of group/user to allow for the permission + + + Assumptions made, that should be checked before calling this function: + - the permission does currently exists ... + - the 'allowed' list argument is *different* from the current + permission state ... otherwise ldap will miserably fail in such + case... + - the 'allowed' list contains *existing* groups. """ from yunohost.hook import hook_callback - from yunohost.user import user_group_list from yunohost.utils.ldap import _get_ldap_interface ldap = _get_ldap_interface() # Fetch currently allowed groups for this permission + existing_permission = user_permission_list(full=True)["permissions"][permission] - existing_permission = user_permission_list(full=True)["permissions"].get(permission, None) - if existing_permission is None: - raise YunohostError('permission_not_found', permission=permission) + if allowed is None: + return existing_permission - all_existing_groups = user_group_list()['groups'].keys() - - if allowed: - if not isinstance(allowed, list): - allowed = [allowed] - for group in allowed: - if group not in all_existing_groups: - raise YunohostError('group_unknown', group=group) - else: - if sync_perm: - permission_sync_to_user() - - return user_permission_list(full=True)["permissions"][permission] + allowed = [allowed] if not isinstance(allowed, list) else allowed try: ldap.update('cn=%s,ou=permission' % permission, From 625df818724aeb522def585cec518488f9d0b1b1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Nov 2019 18:15:50 +0100 Subject: [PATCH 0506/3170] Postinstall was broken because of missing import --- src/yunohost/tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index d10ed0f34..61113392c 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -37,7 +37,7 @@ from moulinette.utils.log import getActionLogger from moulinette.utils.process import check_output, call_async_output from moulinette.utils.filesystem import read_json, write_to_json, read_yaml, write_to_yaml -from yunohost.app import _update_apps_catalog, app_info, app_upgrade, app_ssowatconf, app_list +from yunohost.app import _update_apps_catalog, app_info, app_upgrade, app_ssowatconf, app_list, _initialize_apps_catalog_system from yunohost.domain import domain_add, domain_list from yunohost.dyndns import _dyndns_available, _dyndns_provides from yunohost.firewall import firewall_upnp From 63e756b9f544118cb7661e8dca9d2070196db1ea Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Nov 2019 16:41:43 +0100 Subject: [PATCH 0507/3170] Adjust tests because now all_users ain't added automatically by permission_create for .main perms --- src/yunohost/tests/test_permission.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index 5780ac455..b3fa9fefb 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -33,8 +33,8 @@ def setup_function(function): user_create("alice", "Alice", "White", "alice@" + maindomain, dummy_password) user_create("bob", "Bob", "Snow", "bob@" + maindomain, dummy_password) - permission_create("wiki.main", url="/", sync_perm=False) - permission_create("blog.main", sync_perm=False) + permission_create("wiki.main", url="/", allowed=["all_users"] , sync_perm=False) + permission_create("blog.main", allowed=["all_users"], sync_perm=False) user_permission_update("blog.main", remove="all_users", add="alice") @@ -217,7 +217,7 @@ def test_permission_list(): def test_permission_create_main(mocker): with message(mocker, "permission_created", permission="site.main"): - permission_create("site.main") + permission_create("site.main", allowed=["all_users"]) res = user_permission_list(full=True)['permissions'] assert "site.main" in res @@ -236,7 +236,7 @@ def test_permission_create_extra(mocker): assert res['site.test']['corresponding_users'] == [] -def test_permission_create_with_allowed(): +def test_permission_create_with_specific_user(): permission_create("site.test", allowed=["alice"]) res = user_permission_list(full=True)['permissions'] From 0277ec4f12b679c4dc835ff50eade204ff93b1af Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Nov 2019 17:15:44 +0100 Subject: [PATCH 0508/3170] Warn early about unexisting user/groups (otherwise this triggers a journal entry for nothing) + simplify code, explicit assumptions --- src/yunohost/permission.py | 48 ++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index cfd806b1b..dabd2b054 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -91,9 +91,7 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, add -- List of groups or usernames to add to this permission remove -- List of groups or usernames to remove from to this permission """ - from yunohost.hook import hook_callback - from yunohost.utils.ldap import _get_ldap_interface - ldap = _get_ldap_interface() + from yunohost.user import user_group_list # By default, manipulate main permission if "." not in permission: @@ -115,10 +113,13 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, # Compute new allowed group list (and make sure what we're doing make sense) new_allowed_groups = copy.copy(current_allowed_groups) + all_existing_groups = user_group_list()['groups'].keys() if add: groups_to_add = [add] if not isinstance(add, list) else add for group in groups_to_add: + if group not in all_existing_groups: + raise YunohostError('group_unknown', group=group) if group in current_allowed_groups: logger.warning(m18n.n('permission_already_allowed', permission=permission, group=group)) else: @@ -170,9 +171,6 @@ def user_permission_reset(operation_logger, permission, sync_perm=True): Keyword argument: permission -- Name of the permission (e.g. mail or nextcloud or wordpress.editors) """ - from yunohost.hook import hook_callback - from yunohost.utils.ldap import _get_ldap_interface - ldap = _get_ldap_interface() # By default, manipulate main permission if "." not in permission: @@ -231,6 +229,7 @@ def permission_create(operation_logger, permission, url=None, allowed=None, sync """ from yunohost.utils.ldap import _get_ldap_interface + from yunohost.user import user_group_list ldap = _get_ldap_interface() # By default, manipulate main permission @@ -259,6 +258,13 @@ def permission_create(operation_logger, permission, url=None, allowed=None, sync if url: attr_dict['URL'] = url + # Validate that the groups to add actually exist + all_existing_groups = user_group_list()['groups'].keys() + allowed_ = [] if allowed is None else [allowed] if not isinstance(allowed, list) else allowed + for group in allowed_: + if group not in all_existing_groups: + raise YunohostError('group_unknown', group=group) + operation_logger.related_to.append(('app', permission.split(".")[0])) operation_logger.start() @@ -404,38 +410,34 @@ def permission_sync_to_user(): os.system('nscd --invalidate=passwd') os.system('nscd --invalidate=group') + def _update_ldap_group_permission(permission, allowed, sync_perm=True): """ Internal function that will rewrite user permission permission -- Name of the permission (e.g. mail or nextcloud or wordpress.editors) allowed -- A list of group/user to allow for the permission + + + Assumptions made, that should be checked before calling this function: + - the permission does currently exists ... + - the 'allowed' list argument is *different* from the current + permission state ... otherwise ldap will miserably fail in such + case... + - the 'allowed' list contains *existing* groups. """ from yunohost.hook import hook_callback - from yunohost.user import user_group_list from yunohost.utils.ldap import _get_ldap_interface ldap = _get_ldap_interface() # Fetch currently allowed groups for this permission + existing_permission = user_permission_list(full=True)["permissions"][permission] - existing_permission = user_permission_list(full=True)["permissions"].get(permission, None) - if existing_permission is None: - raise YunohostError('permission_not_found', permission=permission) + if allowed is None: + return existing_permission - all_existing_groups = user_group_list()['groups'].keys() - - if allowed: - if not isinstance(allowed, list): - allowed = [allowed] - for group in allowed: - if group not in all_existing_groups: - raise YunohostError('group_unknown', group=group) - else: - if sync_perm: - permission_sync_to_user() - - return user_permission_list(full=True)["permissions"][permission] + allowed = [allowed] if not isinstance(allowed, list) else allowed try: ldap.update('cn=%s,ou=permission' % permission, From 978d9d5dfd41b27b0ab8e30bcdb70c0e2da154c8 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Nov 2019 19:05:14 +0100 Subject: [PATCH 0509/3170] Burn this fucking madness .. there is no reason to mess with yunohost-firewall during postinst, and it doesnt even actually restart it because $2 is always empty, so what the actual fuck. --- debian/postinst | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/debian/postinst b/debian/postinst index 7b7080513..4633bc314 100644 --- a/debian/postinst +++ b/debian/postinst @@ -16,12 +16,6 @@ do_configure() { echo "Launching migrations.." yunohost tools migrations migrate --auto - - # restart yunohost-firewall if it's running - service yunohost-firewall status >/dev/null \ - && restart_yunohost_firewall \ - || echo "yunohost-firewall service is not running, you should " \ - "consider to start it by doing 'service yunohost-firewall start'." fi # Change dpkg vendor @@ -39,24 +33,6 @@ do_configure() { pam-auth-update --package } -restart_yunohost_firewall() { - echo "Restarting YunoHost firewall..." - - deb-systemd-helper unmask yunohost-firewall.service >/dev/null || true - if deb-systemd-helper --quiet was-enabled yunohost-firewall.service; then - deb-systemd-helper enable yunohost-firewall.service >/dev/null || true - else - deb-systemd-helper update-state yunohost-firewall.service >/dev/null || true - fi - - if [ -x /etc/init.d/yunohost-firewall ]; then - update-rc.d yunohost-firewall enable >/dev/null - if [ -n "$2" ]; then - invoke-rc.d yunohost-firewall restart >/dev/null || exit $? - fi - fi -} - # summary of how this script can be called: # * `configure' # * `abort-upgrade' From decb372b7ac985bf67a896ae36f8f2a15b0261b0 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Nov 2019 19:18:24 +0100 Subject: [PATCH 0510/3170] Run a full diagnosis after each yunohost upgrade --- debian/postinst | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/debian/postinst b/debian/postinst index 4633bc314..9c78c8432 100644 --- a/debian/postinst +++ b/debian/postinst @@ -14,11 +14,14 @@ do_configure() { echo "Regenerating configuration, this might take a while..." yunohost tools regen-conf --output-as none - echo "Launching migrations.." + echo "Launching migrations..." yunohost tools migrations migrate --auto + + echo "Re-diagnosing server health..." + yunohost diagnosis run --force fi - - # Change dpkg vendor + + # Change dpkg vendor # see https://wiki.debian.org/Derivatives/Guidelines#Vendor readlink -f /etc/dpkg/origins/default | grep -q debian \ && rm -f /etc/dpkg/origins/default \ From e686dc68666b9119e90fe29b44e25983fae360be Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Nov 2019 19:38:06 +0100 Subject: [PATCH 0511/3170] Tweak tip at the end of postinstall to also point to diagnosis and admindoc --- locales/en.json | 4 ++-- src/yunohost/tools.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/locales/en.json b/locales/en.json index abf4831aa..29db5428e 100644 --- a/locales/en.json +++ b/locales/en.json @@ -481,7 +481,6 @@ "permission_require_account": "Permission {permission} only makes sense for users having an account, and therefore cannot be enabled for visitors.", "port_already_closed": "Port {port:d} is already closed for {ip_version:s} connections", "port_already_opened": "Port {port:d} is already opened for {ip_version:s} connections", - "recommend_to_add_first_user": "The post-install is finished, but YunoHost needs at least one user to work correctly, you should add one using 'yunohost user create ' or do it from the admin interface.", "regenconf_file_backed_up": "Configuration file '{conf}' backed up to '{backup}'", "regenconf_file_copy_failed": "Could not copy the new configuration file '{new}' to '{conf}'", "regenconf_file_kept_back": "The configuration file '{conf}' is expected to be deleted by regen-conf (category {category}) but was kept back.", @@ -603,5 +602,6 @@ "yunohost_ca_creation_success": "Local certification authority created.", "yunohost_configured": "YunoHost is now configured", "yunohost_installing": "Installing YunoHost…", - "yunohost_not_installed": "YunoHost is not correctly installed. Please run 'yunohost tools postinstall'" + "yunohost_not_installed": "YunoHost is not correctly installed. Please run 'yunohost tools postinstall'", + "yunohost_postinstall_end_tip": "The post-install completed! To finalize your setup, please consider:\n - adding a first user through the 'Users' section of the webadmin (or 'yunohost user create ' in command-line) ;\n - diagnose issues waiting to be solved for your server to be running as smoothly as possible through the 'Diagnosis' section of the webadmin (or 'yunohost diagnosis run' in command-line) ;\n - reading the 'Finalizing your setup' and 'Getting to know Yunohost' parts in the admin documentation : https://yunohost.org/admindoc." } diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 61113392c..c05933dc0 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -392,7 +392,7 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False, logger.success(m18n.n('yunohost_configured')) - logger.warning(m18n.n('recommend_to_add_first_user')) + logger.warning(m18n.n('yunohost_postinstall_end_tip')) def tools_regen_conf(names=[], with_diff=False, force=False, dry_run=False, From 8b0c9e5b794eabe9f8722566b5389d90196cb67f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Nov 2019 21:16:32 +0100 Subject: [PATCH 0512/3170] Try to improve the wording of those messages... --- locales/fr.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index e468ecc55..3fc111e94 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -422,9 +422,9 @@ "log_category_404": "Le journal de la catégorie '{category}' n’existe pas", "log_link_to_log": "Journal historisé complet de cette opération : ' {desc} '", "log_help_to_get_log": "Pour voir le journal historisé de cette opération '{desc}', utilisez la commande 'yunohost log display {name}'", - "log_link_to_failed_log": "L’opération '{desc}' a échouée ! Pour avoir de l’aide, merci cliqué ici pour avoir de l'aide", + "log_link_to_failed_log": "L’opération '{desc}' a échouée ! Pour obtenir de l’aide, merci de partager le journal de l'opération en cliquant ici", "backup_php5_to_php7_migration_may_fail": "Impossible de convertir votre archive pour prendre en charge PHP 7, vous pourriez ne plus pouvoir restaurer vos applications PHP (cause : {error:s})", - "log_help_to_get_failed_log": "L’opération '{desc}' a échouée ! Pour avoir de l’aide, merci de partager le journal historisé de cette opération en utilisant la commande 'yunohost log display {name} --share'", + "log_help_to_get_failed_log": "L’opération '{desc}' a échouée ! Pour obtenir de l’aide, merci de partager le journal de l'opération en utilisant la commande 'yunohost log display {name} --share'", "log_does_exists": "Il n’existe pas de journal historisé de l’opération ayant pour nom '{log}', utiliser 'yunohost log list pour voir tous les fichiers de journaux historisés disponibles'", "log_operation_unit_unclosed_properly": "L’opération ne s’est pas terminée correctement", "log_app_addaccess": "Ajouter l’accès à '{}'", From 6edba30eb91a2bb33b96f9d07c4a08df99a8ab8b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Nov 2019 21:16:32 +0100 Subject: [PATCH 0513/3170] Try to improve the wording of those messages... --- locales/fr.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 1300657cf..a52be20c7 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -422,9 +422,9 @@ "log_category_404": "Le journal de la catégorie '{category}' n’existe pas", "log_link_to_log": "Journal historisé complet de cette opération : ' {desc} '", "log_help_to_get_log": "Pour voir le journal historisé de cette opération '{desc}', utilisez la commande 'yunohost log display {name}'", - "log_link_to_failed_log": "L’opération '{desc}' a échouée ! Pour avoir de l’aide, merci cliqué ici pour avoir de l'aide", + "log_link_to_failed_log": "L’opération '{desc}' a échouée ! Pour obtenir de l’aide, merci de partager le journal de l'opération en cliquant ici", "backup_php5_to_php7_migration_may_fail": "Impossible de convertir votre archive pour prendre en charge PHP 7, vous pourriez ne plus pouvoir restaurer vos applications PHP (cause : {error:s})", - "log_help_to_get_failed_log": "L’opération '{desc}' a échouée ! Pour avoir de l’aide, merci de partager le journal historisé de cette opération en utilisant la commande 'yunohost log display {name} --share'", + "log_help_to_get_failed_log": "L’opération '{desc}' a échouée ! Pour obtenir de l’aide, merci de partager le journal de l'opération en utilisant la commande 'yunohost log display {name} --share'", "log_does_exists": "Il n’existe pas de journal historisé de l’opération ayant pour nom '{log}', utiliser 'yunohost log list pour voir tous les fichiers de journaux historisés disponibles'", "log_operation_unit_unclosed_properly": "L’opération ne s’est pas terminée correctement", "log_app_addaccess": "Ajouter l’accès à '{}'", From 999006dbda7780bc95f12620f0a3e2042f0e0d32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9lanie=20Chauvel?= Date: Wed, 20 Nov 2019 18:12:16 +0000 Subject: [PATCH 0514/3170] Translated using Weblate (French) Currently translated at 99.5% (601 of 604 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index a52be20c7..2de198614 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -200,21 +200,21 @@ "service_configuration_conflict": "Le fichier {file:s} a été modifié depuis sa dernière génération. Veuillez y appliquer les modifications manuellement ou utiliser l’option --force (ce qui écrasera toutes les modifications effectuées sur le fichier).", "service_configured": "La configuration du service « {service:s} » a été générée avec succès", "service_configured_all": "La configuration de tous les services a été générée avec succès", - "service_disable_failed": "Impossible de désactiver le service '{service:s}'\n\nJournaux historisés récents : {logs:s}", - "service_disabled": "Le service '{service:s}' a été désactivé", - "service_enable_failed": "Impossible d’activer le service '{service:s}'\n\nJournaux historisés récents : {logs:s}", - "service_enabled": "Le service '{service:s}' a été activé", + "service_disable_failed": "Impossible de ne pas lancer le service « {service:s} » au démarrage.\n\nJournaux récents du service : {logs:s}", + "service_disabled": "Le service « {service:s} » ne sera plus lancé au démarrage du système.", + "service_enable_failed": "Impossible de lancer automatiquement le service « {service:s} » au démarrage.\n\nJournaux récents du service : {logs:s}", + "service_enabled": "Le service « {service:s} » sera désormais lancé automatiquement au démarrage du système.", "service_no_log": "Aucun journal à afficher pour le service '{service:s}'", "service_regenconf_dry_pending_applying": "Vérification des configurations en attentes qui pourraient être appliquées au le service '{service}' …", "service_regenconf_failed": "Impossible de régénérer la configuration pour les services : {services}", "service_regenconf_pending_applying": "Application des configurations en attentes pour le service '{service}' …", "service_remove_failed": "Impossible de supprimer le service '{service:s}'", - "service_removed": "Le service '{service:s}' a été supprimé", + "service_removed": "Le service « {service:s} » a été supprimé", "service_start_failed": "Impossible de démarrer le service '{service:s}'\n\nJournaux historisés récents : {logs:s}", - "service_started": "Le service '{service:s}' a été démarré", + "service_started": "Le service « {service:s} » a été démarré", "service_status_failed": "Impossible de déterminer le statut du service '{service:s}'", "service_stop_failed": "Impossible d’arrêter le service '{service:s}'\n\nJournaux historisés récents : {logs:s}", - "service_stopped": "Le service '{service:s}' a été arrêté", + "service_stopped": "Le service « {service:s} » a été arrêté", "service_unknown": "Le service '{service:s}' est inconnu", "services_configured": "La configuration a été générée avec succès", "show_diff": "Voici les différences :\n{diff:s}", @@ -517,11 +517,11 @@ "root_password_replaced_by_admin_password": "Votre mot de passe root a été remplacé par votre mot de passe administrateur.", "service_conf_now_managed_by_yunohost": "Le fichier de configuration '{conf}' est maintenant géré par YunoHost.", "service_reload_failed": "Impossible de recharger le service '{service:s}'.\n\nJournaux historisés récents de ce service : {logs:s}", - "service_reloaded": "Le service '{service:s}' a été rechargé", + "service_reloaded": "Le service « {service:s} » a été rechargé", "service_restart_failed": "Impossible de redémarrer le service '{service:s}'\n\nJournaux historisés récents de ce service : {logs:s}", - "service_restarted": "Le service '{service:s}' a été redémarré", + "service_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é", + "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`.", "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", @@ -543,7 +543,7 @@ "migration_description_0009_decouple_regenconf_from_services": "Dissocier le mécanisme « regen-conf » des services", "migration_description_0010_migrate_to_apps_json": "Supprimez les catalogues d'applications obsolètes et utilisez à la place la nouvelle liste unifiée 'apps.json' (obsolète, remplacée par la migration 13).", "regenconf_file_kept_back": "Le fichier de configuration '{conf}' devait être supprimé par « regen-conf » (catégorie {category}) mais a été conservé.", - "regenconf_updated": "La configuration a été mise à jour pour la catégorie '{category}'", + "regenconf_updated": "La configuration a été mise à jour pour '{category}'", "regenconf_would_be_updated": "La configuration aurait dû être mise à jour pour la catégorie '{category}'", "regenconf_dry_pending_applying": "Vérification de la configuration en attente qui aurait été appliquée pour la catégorie '{category}' …", "regenconf_failed": "Impossible de régénérer la configuration pour la ou les catégorie(s) : '{categories}'", @@ -741,5 +741,14 @@ "diagnosis_http_ok": "Le domaine {domain} est accessible de l'extérieur.", "diagnosis_http_unreachable": "Le domaine {domain} est inaccessible via HTTP de l'extérieur.", "diagnosis_unknown_categories": "Les catégories suivantes sont inconnues: {categories}", - "migration_description_0013_futureproof_apps_catalog_system": "Migrer vers le nouveau système de catalogue d'applications à l'épreuve du temps" + "migration_description_0013_futureproof_apps_catalog_system": "Migrer vers le nouveau système de catalogue d'applications à l'épreuve du temps", + "app_upgrade_script_failed": "Une erreur s'est produite durant l’exécution du script de mise à niveau de l'application", + "migration_description_0014_remove_app_status_json": "Supprimer les fichiers d'application status.json hérités", + "diagnosis_services_running": "Le service {service} s'exécute correctement !", + "diagnosis_services_conf_broken": "La configuration est cassée pour le service {service} !", + "diagnosis_ports_needed_by": "Rendre ce port accessible est nécessaire pour le service {0}", + "diagnosis_ports_forwarding_tip": "Pour résoudre ce problème, vous devez très probablement configurer le transfert de port sur votre routeur Internet comme décrit dans https://yunohost.org/port_forwarding", + "diagnosis_http_connection_error": "Erreur de connexion : impossible de se connecter au domaine demandé, il est probablement injoignable.", + "diagnosis_no_cache": "Pas encore de diagnostique de cache pour la catégorie « {category} »", + "diagnosis_http_unknown_error": "Une erreur est survenue en essayant de joindre votre domaine, il est probablement injoignable." } From 65fee9c300eb1f621083f893345a4fd872d99b77 Mon Sep 17 00:00:00 2001 From: ppr Date: Mon, 18 Nov 2019 19:32:40 +0000 Subject: [PATCH 0515/3170] Translated using Weblate (French) Currently translated at 99.8% (598 of 599 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 2de198614..a03c4a61f 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -28,7 +28,7 @@ "app_sources_fetch_failed": "Impossible de récupérer les fichiers sources, l'URL est-elle correcte ?", "app_unknown": "Application inconnue", "app_unsupported_remote_type": "Ce type de commande à distance utilisé pour cette application n'est pas supporté", - "app_upgrade_failed": "Impossible de mettre à jour {app:s}", + "app_upgrade_failed": "Impossible de mettre à jour {app:s} : {error}", "app_upgraded": "{app:s} mis à jour", "appslist_fetched": "La liste d’applications mise à jour '{appslist:s}'", "appslist_removed": "La liste d'applications '{appslist:s}' a été supprimée", @@ -462,7 +462,7 @@ "log_tools_shutdown": "Éteindre votre serveur", "log_tools_reboot": "Redémarrer votre serveur", "mail_unavailable": "Cette adresse de courriel est réservée et doit être automatiquement attribuée au tout premier utilisateur", - "migration_description_0004_php5_to_php7_pools": "Reconfigurez les groupes PHP pour utiliser PHP 7 au lieu de PHP 5", + "migration_description_0004_php5_to_php7_pools": "Reconfigurer les espaces utilisateurs PHP pour utiliser PHP 7 au lieu de PHP 5", "migration_description_0005_postgresql_9p4_to_9p6": "Migration des bases de données de PostgreSQL 9.4 vers PostgreSQL 9.6", "migration_0005_postgresql_94_not_installed": "PostgreSQL n’a pas été installé sur votre système. Rien à faire !", "migration_0005_postgresql_96_not_installed": "PostgreSQL 9.4 a été trouvé et installé, mais pas PostgreSQL 9.6 !? Quelque chose d’étrange a dû arriver à votre système… :(", @@ -541,7 +541,7 @@ "global_settings_setting_security_ssh_compatibility": "Compatibilité versus compromis sécuritaire pour le serveur SSH. Affecte les cryptogrammes (et d'autres aspects liés à la sécurité)", "global_settings_setting_security_postfix_compatibility": "Compatibilité versus compromis sécuritaire pour le serveur Postfix. Affecte les cryptogrammes (et d'autres aspects liés à la sécurité)", "migration_description_0009_decouple_regenconf_from_services": "Dissocier le mécanisme « regen-conf » des services", - "migration_description_0010_migrate_to_apps_json": "Supprimez les catalogues d'applications obsolètes et utilisez à la place la nouvelle liste unifiée 'apps.json' (obsolète, remplacée par la migration 13).", + "migration_description_0010_migrate_to_apps_json": "Supprimer les catalogues d'applications obsolètes afin d'utiliser la nouvelle liste unifiée 'apps.json' à la place (les anciens catalogues seront remplacés durant la migration 13)", "regenconf_file_kept_back": "Le fichier de configuration '{conf}' devait être supprimé par « regen-conf » (catégorie {category}) mais a été conservé.", "regenconf_updated": "La configuration a été mise à jour pour '{category}'", "regenconf_would_be_updated": "La configuration aurait dû être mise à jour pour la catégorie '{category}'", @@ -589,7 +589,7 @@ "log_user_permission_add": "Mettre à jour l'autorisation pour '{}'", "log_user_permission_remove": "Mettre à jour l'autorisation pour '{}'", "mailbox_disabled": "La boîte aux lettres est désactivée pour l'utilisateur {user:s}", - "app_action_broke_system": "Cette action semble avoir cassé des services important : {services}", + "app_action_broke_system": "Cette action semble avoir cassé des services importants : {services}", "apps_already_up_to_date": "Toutes les applications sont déjà à jour", "app_upgrade_stopped": "La mise à niveau de toutes les applications s'est arrêtée pour éviter tout dommage, car une application n'a pas pu être mise à niveau.", "migration_0011_create_group": "Créer un groupe pour chaque utilisateur…", @@ -700,7 +700,7 @@ "diagnosis_dns_good_conf": "Bonne configuration DNS pour le domaine {domain} (catégorie {category})", "diagnosis_dns_bad_conf": "Configuration DNS incorrecte/manquante pour le domaine {domain} (catégorie {category})", "diagnosis_dns_discrepancy": "Selon la configuration DNS recommandée, la valeur de l'enregistrement DNS de type {0} et nom {1} doit être {2} et non {3}.", - "diagnosis_services_bad_status": "Le service {service} est {status} :/", + "diagnosis_services_bad_status": "Le service {service} est {status} :-(", "diagnosis_services_good_status": "Le service {service} est {status} comme prévu !", "diagnosis_diskusage_verylow": "Le stockage {mountpoint} (sur le périphérique {device}) ne dispose que de {free_abs_GB} Go ({free_percent}%). Vous devriez vraiment envisager de nettoyer un peu d'espace.", "diagnosis_diskusage_low": "Le stockage {mountpoint} (sur le périphérique {device}) ne dispose que de {free_abs_GB} Go ({free_percent}%). Faites attention.", From 1b26675f18b8a6ab16e8964a4e3077df55e9b7f4 Mon Sep 17 00:00:00 2001 From: xaloc33 Date: Fri, 15 Nov 2019 21:34:25 +0000 Subject: [PATCH 0516/3170] Translated using Weblate (Catalan) Currently translated at 100.0% (596 of 596 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ca/ --- locales/ca.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/locales/ca.json b/locales/ca.json index 848838bda..26fd8c889 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -36,7 +36,7 @@ "app_unknown": "Aplicació desconeguda", "app_unsupported_remote_type": "El tipus remot utilitzat per l'aplicació no està suportat", "app_upgrade_app_name": "Actualitzant {app}…", - "app_upgrade_failed": "No s'ha pogut actualitzar {app:s}", + "app_upgrade_failed": "No s'ha pogut actualitzar {app:s}: {error}", "app_upgrade_some_app_failed": "No s'han pogut actualitzar algunes aplicacions", "app_upgraded": "S'ha actualitzat {app:s}", "appslist_corrupted_json": "No s'han pogut carregar les llistes d'aplicacions. Sembla que {filename:s} està danyat.", @@ -703,5 +703,6 @@ "diagnosis_mail_ougoing_port_25_ok": "El port de sortida 25 no està bloquejat i els correus es poden enviar a altres servidors.", "diagnosis_mail_ougoing_port_25_blocked": "Sembla que el port de sortida 25 està bloquejat. Hauríeu d'intentar desbloquejar-lo al panell de configuració del proveïdor d'accés a internet (o allotjador). Mentrestant, el servidor no podrà enviar correus a altres servidors.", "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" + "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ó" } From 7fa61b49f403e8894622640fb1f5c5d47495521c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 23 Nov 2019 19:27:18 +0100 Subject: [PATCH 0517/3170] Typo fix lost in the merges... --- src/yunohost/permission.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index dabd2b054..ad06c0487 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -464,9 +464,9 @@ def _update_ldap_group_permission(permission, allowed, sync_perm=True): effectively_removed_users = old_allowed_users - new_allowed_users if effectively_added_users: - hook_callback('post_app_addaccess', args=[app, ','.join(effectively_added_users, sub_permission)]) + hook_callback('post_app_addaccess', args=[app, ','.join(effectively_added_users), sub_permission]) if effectively_removed_users: - hook_callback('post_app_removeaccess', args=[app, ','.join(effectively_removed_users, sub_permission)]) + hook_callback('post_app_removeaccess', args=[app, ','.join(effectively_removed_users), sub_permission]) return new_permission From e3474f1a8ce88d6f59034e1aefa8068f698a621c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 23 Nov 2019 19:32:51 +0100 Subject: [PATCH 0518/3170] Update changelog for 3.7.0.3 --- debian/changelog | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/debian/changelog b/debian/changelog index e7926cd95..f8a83d0b3 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,11 @@ +yunohost (3.7.0.3) testing; urgency=low + + - [mod] Some refactoring for permissions create/update/reset (#837) + - [fix] Fix some edge cases for ynh_secure_remove and ynh_clean_check_starting + - [i18n] Improve translations for French, Catalan + + -- Alexandre Aubin Sat, 23 Nov 2019 19:30:00 +0000 + yunohost (3.7.0.2) testing; urgency=low - [fix] Make sure the users actually exists when migrating legacy custom permissions From 9fe43b17f03932f4ca4ba12c962022450f2d5c51 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 23 Nov 2019 19:53:39 +0100 Subject: [PATCH 0519/3170] Try to make this message a bit less weird? --- locales/en.json | 2 +- locales/fr.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/en.json b/locales/en.json index 29db5428e..df50cdcf6 100644 --- a/locales/en.json +++ b/locales/en.json @@ -570,7 +570,7 @@ "tools_upgrade_regular_packages": "Now upgrading 'regular' (non-yunohost-related) packages…", "tools_upgrade_regular_packages_failed": "Could not upgrade packages: {packages_list}", "tools_upgrade_special_packages": "Now upgrading 'special' (yunohost-related) packages…", - "tools_upgrade_special_packages_explanation": "This action will end, but the actual special upgrade will continue in background. Please don't start any other actions on your server the next ~10 minutes (depending on hardware speed). Once done, you may have to log in on the webadmin page again. The upgrade log will be available in Tools → Log (on the webadmin page) or through 'yunohost log list' (from the command-line).", + "tools_upgrade_special_packages_explanation": "The special upgrade will continue in background. Please don't start any other actions on your server the next ~10 minutes (depending on hardware speed). After this, you may have to re-log on the webadmin. The upgrade log will be available in Tools → Log (in the webadmin) or using 'yunohost log list' (from the command-line).", "tools_upgrade_special_packages_completed": "YunoHost package upgrade completed.\nPress [Enter] to get the command line back", "unbackup_app": "App '{app:s}' will not be saved", "unexpected_error": "Something unexpected went wrong: {error}", diff --git a/locales/fr.json b/locales/fr.json index 3fc111e94..6ddb16de7 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -559,7 +559,7 @@ "updating_app_lists": "Récupération des mises à jour des applications disponibles…", "dpkg_lock_not_available": "Cette commande ne peut être exécutée actuellement car un autre programme semble utiliser le verrou de dpkg (gestionnaire de paquets)", "tools_upgrade_cant_unhold_critical_packages": "Impossible de conserver les paquets critiques…", - "tools_upgrade_special_packages_explanation": "Cette action se terminera, mais la mise à niveau spéciale réelle continuera en arrière-plan. Veuillez ne pas lancer d’autres actions sur votre serveur au cours des 10 prochaines minutes (en fonction de la vitesse du matériel). Une fois cela fait, vous devrez peut-être vous reconnecter à la page Webadmin. Le journal de mise à niveau sera disponible dans Outils → Journal (sur la page Webadmin) ou dans la \"liste des journaux yunohost\" (à partir de la ligne de commande).", + "tools_upgrade_special_packages_explanation": "La mise à jour spéciale va continuer en arrière-plan. Veuillez ne pas lancer d’autres actions sur votre serveur pendant environ 10 minutes (en fonction de la vitesse du matériel). Après cela, il vous faudra peut-être vous reconnecter à la webadmin. Le journal de mise à niveau sera disponible dans Outils → Journal (dans la webadmin) ou via \"yunohost log list\" (en ligne de commande).", "update_apt_cache_failed": "Impossible de mettre à jour le cache APT (gestionnaire de paquets Debian). Voici un extrait du fichier sources.list qui pourrait vous aider à identifier les lignes problématiques :\n{sourceslist}", "update_apt_cache_warning": "Des erreurs se sont produites lors de la mise à jour du cache APT (gestionnaire de paquets Debian). Voici un extrait des lignes du fichier sources.list qui pourrait vous aider à identifier les lignes problématiques :\n{sourceslist}", "apps_permission_not_found": "Aucune permission trouvée pour les applications installées", From 07103092c5ea89ad68f1207a7833f06e3f35e7bb Mon Sep 17 00:00:00 2001 From: xaloc33 Date: Fri, 22 Nov 2019 23:10:49 +0000 Subject: [PATCH 0520/3170] Translated using Weblate (Catalan) Currently translated at 100.0% (605 of 605 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ca/ --- locales/ca.json | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/locales/ca.json b/locales/ca.json index 3b1b73a36..00b9b9e19 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -406,7 +406,7 @@ "regenconf_file_updated": "El fitxer de configuració «{conf}» ha estat actualitzat", "regenconf_now_managed_by_yunohost": "El fitxer de configuració «{conf}» serà gestionat per YunoHost a partir d'ara (categoria {category}).", "regenconf_up_to_date": "La configuració ja està al dia per la categoria «{category}»", - "regenconf_updated": "La configuració per la categoria «{category}» ha estat actualitzada", + "regenconf_updated": "S'ha actualitzat la configuració per la categoria «{category}»", "regenconf_would_be_updated": "La configuració hagués estat actualitzada per la categoria «{category}»", "regenconf_dry_pending_applying": "Verificació de la configuració pendent que s'hauria d'haver aplicat per la categoria «{category}»…", "regenconf_failed": "No s'ha pogut regenerar la configuració per la/les categoria/es : {categories}", @@ -457,10 +457,10 @@ "service_description_ssh": "Permet la connexió remota al servidor via terminal (protocol SSH)", "service_description_yunohost-api": "Gestiona les interaccions entre la interfície web de YunoHost i el sistema", "service_description_yunohost-firewall": "Gestiona els ports de connexió oberts i tancats als serveis", - "service_disable_failed": "No s'han pogut deshabilitar el servei «{service:s}»\n\nRegistres recents: {logs:s}", - "service_disabled": "S'ha deshabilitat el servei «{service:s}»", - "service_enable_failed": "No s'ha pogut activar el servei «{service:s}»\n\nRegistres recents: {log:s}", - "service_enabled": "S'ha activat el servei «{service:s}»", + "service_disable_failed": "No s'han pogut fer que el servei «{service:s}» no comenci a l'arrancada.\n\nRegistres recents: {logs:s}", + "service_disabled": "El servei «{service:s}» ja no començarà al arrancar el sistema.", + "service_enable_failed": "No s'ha pogut fer que el servei «{service:s}» comenci automàticament a l'arrancada.\n\nRegistres recents: {log:s}", + "service_enabled": "El servei «{service:s}» començarà automàticament durant l'arrancada del sistema.", "service_no_log": "No hi ha cap registre pel servei «{service:s}»", "service_regen_conf_is_deprecated": "«yunohost service regen-conf» està desfasat! Utilitzeu «yunohost tools regen-conf» en el seu lloc.", "service_remove_failed": "No s'ha pogut eliminar el servei «{service:s}»", @@ -663,10 +663,10 @@ "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 via /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 de tipus {0}, nom {1} i valor {2}", - "diagnosis_dns_discrepancy": "Segons la configuració DNS recomanada, el valor pel registre DNS de tipus {0} i nom {1} hauria de ser {2}, en comptes de {3}.", + "diagnosis_dns_missing_record": "Segons la configuració DNS recomanada, hauríeu d'afegir un registre DNS de tipus {0}, nom {1} i valor {2}. Hi ha més informació a https://yunohost.org/dns_config.", + "diagnosis_dns_discrepancy": "El registre DNS de tipus {0} i nom {1} no concorda amb la configuració recomanada. Valor actual: {2}. Valor esperat: {3}. Més informació a https://yunohost.org/dns_config.", "diagnosis_services_good_status": "El servei {service} està {status} tal i com s'esperava!", - "diagnosis_services_bad_status": "El servei {service} està {status} :/", + "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_abs_GB} GB ({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_abs_GB} GB ({free_percent}%). Aneu amb compte.", "diagnosis_diskusage_ok": "El lloc d'emmagatzematge {mountpoint} (en l'aparell {device}) encara té {free_abs_GB} GB ({free_percent}%) lliures!", @@ -704,5 +704,17 @@ "diagnosis_mail_ougoing_port_25_blocked": "Sembla que el port de sortida 25 està bloquejat. Hauríeu d'intentar desbloquejar-lo al panell de configuració del proveïdor d'accés a internet (o allotjador). Mentrestant, el servidor no podrà enviar correus a altres servidors.", "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ó" + "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 {0}» 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 s'explica a https://yunohost.org/isp_box_config", + "diagnosis_http_bad_status_code": "No s'ha pogut connectar al servidor com esperat, ha retornat un codi d'estat erroni. 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_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_connection_error": "Error de connexió: no s'ha pogut connectar amb el domini demanat, segurament és inaccessible.", + "diagnosis_http_unknown_error": "Hi ha hagut un error intentant accedir al domini, 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 els problemes esperant a ser resolts per un correcte funcionament del servidor 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", + "diagnosis_services_running": "El servei {service} s'està executant!", + "diagnosis_services_conf_broken": "La configuració pel servei {service} està trencada!", + "diagnosis_ports_needed_by": "És necessari exposar aquest port pel servei {0}" } From c4590ab8cab7f8c8e601031e502e16d910309749 Mon Sep 17 00:00:00 2001 From: Kayou Date: Mon, 25 Nov 2019 23:01:44 +0900 Subject: [PATCH 0521/3170] Fix test_backup_and_restore_permission_app --- src/yunohost/tests/test_backuprestore.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/tests/test_backuprestore.py b/src/yunohost/tests/test_backuprestore.py index d67ccfe37..48fe7f5d8 100644 --- a/src/yunohost/tests/test_backuprestore.py +++ b/src/yunohost/tests/test_backuprestore.py @@ -500,7 +500,7 @@ def test_backup_and_restore_with_ynh_restore(mocker): _test_backup_and_restore_app(mocker, "backup_recommended_app") @pytest.mark.with_permission_app_installed -def test_backup_and_restore_permission_app(): +def test_backup_and_restore_permission_app(mocker): res = user_permission_list(full=True)['permissions'] assert "permissions_app.main" in res @@ -514,7 +514,7 @@ def test_backup_and_restore_permission_app(): assert res['permissions_app.admin']['allowed'] == ["alice"] assert res['permissions_app.dev']['allowed'] == [] - _test_backup_and_restore_app("permissions_app") + _test_backup_and_restore_app(mocker, "permissions_app") res = user_permission_list(full=True)['permissions'] assert "permissions_app.main" in res From 101d3beebf19f3657466da69aba65358e67bcf30 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 25 Nov 2019 18:26:26 +0100 Subject: [PATCH 0522/3170] Simplify app_list, don't call app_info (which itself calls app_list...) just to fetch the label from the settings --- src/yunohost/app.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index e2541d03e..0e4a473b4 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -142,15 +142,10 @@ def app_list(filter=None, raw=False, installed=False, with_backup=False): list_dict[app_id] = app_info_dict else: - label = None - if app_installed: - app_info_dict_raw = app_info(app=app_id, raw=True) - label = app_info_dict_raw['settings']['label'] - list_dict.append({ 'id': app_id, 'name': app_info_dict['manifest']['name'], - 'label': label, + 'label': _get_app_settings(app_id).get("label", "?") if app_installed else None, 'description': _value_for_locale(app_info_dict['manifest']['description']), # FIXME: Temporarly allow undefined license 'license': app_info_dict['manifest'].get('license', m18n.n('license_undefined')), From 67bb386c390dae9f5c356f2537b2a5a3af9ffee8 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 25 Nov 2019 18:27:01 +0100 Subject: [PATCH 0523/3170] Explicit that we wanna run this python code in 2.7, otherwise if you're in a python3 venv it may not find the yaml module --- data/helpers.d/setting | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index 8046dfab4..f0963444a 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -158,7 +158,7 @@ ynh_add_protected_uris() { # ynh_app_setting() { - ACTION="$1" APP="$2" KEY="$3" VALUE="${4:-}" python - < Date: Mon, 25 Nov 2019 19:07:52 +0100 Subject: [PATCH 0524/3170] Try to remove as many app_list() call as possible, replace them with a simple _installed_apps() --- src/yunohost/app.py | 34 +++++++------------ .../0011_setup_group_permission.py | 17 ++++++---- src/yunohost/tests/test_permission.py | 6 ++-- 3 files changed, 24 insertions(+), 33 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 0e4a473b4..4ecdc0db6 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -90,7 +90,7 @@ def app_list(filter=None, raw=False, installed=False, with_backup=False): app_dict = _load_apps_catalog() # Get app list from the app settings directory - for app in os.listdir(APPS_SETTING_PATH): + for app in _installed_apps(): if app not in app_dict: # Handle multi-instance case like wordpress__2 if '__' in app: @@ -452,19 +452,12 @@ def app_upgrade(app=[], url=None, file=None): from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback from yunohost.permission import permission_sync_to_user - try: - app_list() - except YunohostError: - raise YunohostError('apps_already_up_to_date') - - not_upgraded_apps = [] - apps = app # If no app is specified, upgrade all apps if not apps: # FIXME : not sure what's supposed to happen if there is a url and a file but no apps... if not url and not file: - apps = [app_["id"] for app_ in app_list(installed=True)["apps"]] + apps = _installed_apps() elif not isinstance(app, list): apps = [app] @@ -668,7 +661,10 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu # If we got an url like "https://github.com/foo/bar_ynh, we want to # extract "bar" and test if we know this app elif ('http://' in app) or ('https://' in app): - app_name_to_test = app.strip("/").split("/")[-1].replace("_ynh","") + app_name_to_test = app.strip("/").split("/")[-1].replace("_ynh", "") + else: + # FIXME : watdo if '@' in app ? + app_name_to_test = None if app_name_to_test in raw_app_list: @@ -1216,8 +1212,7 @@ def app_register_url(app, domain, path): # We cannot change the url of an app already installed simply by changing # the settings... - installed = app in app_list(installed=True, raw=True).keys() - if installed: + if _is_installed(app): settings = _get_app_settings(app) if "path" in settings.keys() and "domain" in settings.keys(): raise YunohostError('app_already_installed_cant_change_url') @@ -1263,19 +1258,13 @@ def app_ssowatconf(): redirected_regex = {main_domain + '/yunohost[\/]?$': 'https://' + main_domain + '/yunohost/sso/'} redirected_urls = {} - try: - apps_list = app_list(installed=True)['apps'] - except Exception as e: - logger.debug("cannot get installed app list because %s", e) - apps_list = [] - def _get_setting(settings, name): s = settings.get(name, None) return s.split(',') if s else [] - for app in apps_list: + for app in _installed_apps(): - app_settings = read_yaml(APPS_SETTING_PATH + app['id'] + '/settings.yml') + app_settings = read_yaml(APPS_SETTING_PATH + app + '/settings.yml') if 'domain' not in app_settings: continue @@ -1622,8 +1611,7 @@ def _get_all_installed_apps_id(): * ...' """ - all_apps_ids = [x["id"] for x in app_list(installed=True)["apps"]] - all_apps_ids = sorted(all_apps_ids) + all_apps_ids = sorted(_installed_apps()) all_apps_ids_formatted = "\n * ".join(all_apps_ids) all_apps_ids_formatted = "\n * " + all_apps_ids_formatted @@ -2269,6 +2257,8 @@ def _is_installed(app): """ return os.path.isdir(APPS_SETTING_PATH + app) +def _installed_apps(): + return os.listdir(APPS_SETTING_PATH) def _value_for_locale(values): """ diff --git a/src/yunohost/data_migrations/0011_setup_group_permission.py b/src/yunohost/data_migrations/0011_setup_group_permission.py index c80686344..e054915cf 100644 --- a/src/yunohost/data_migrations/0011_setup_group_permission.py +++ b/src/yunohost/data_migrations/0011_setup_group_permission.py @@ -8,7 +8,7 @@ from moulinette.utils.filesystem import read_yaml from yunohost.tools import Migration from yunohost.user import user_list, user_group_create, user_group_update -from yunohost.app import app_setting, app_list +from yunohost.app import app_setting, _installed_apps from yunohost.regenconf import regen_conf, BACKUP_CONF_DIR from yunohost.permission import permission_create, user_permission_update, permission_sync_to_user @@ -96,13 +96,16 @@ class MyMigration(Migration): def migrate_app_permission(self, app=None): logger.info(m18n.n("migration_0011_migrate_permission")) - if app: - apps = app_list(installed=True, filter=app)['apps'] - else: - apps = app_list(installed=True)['apps'] + apps = _installed_apps() - for app_info in apps: - app = app_info['id'] + if app: + if app not in apps: + logger.error("Can't migrate permission for app %s because it ain't installed..." % app) + apps = [] + else: + apps = [app] + + for app in apps: permission = app_setting(app, 'allowed_users') path = app_setting(app, 'path') domain = app_setting(app, 'domain') diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index b3fa9fefb..6d194d520 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -3,7 +3,7 @@ import pytest from conftest import message, raiseYunohostError -from yunohost.app import app_install, app_remove, app_change_url, app_list, app_map +from yunohost.app import app_install, app_remove, app_change_url, app_list, app_map, _installed_apps from yunohost.user import user_list, user_create, user_delete, \ user_group_list, user_group_delete from yunohost.permission import user_permission_update, user_permission_list, user_permission_reset, \ @@ -163,9 +163,7 @@ def check_permission_for_apps(): app_perms_prefix = set(p.split(".")[0] for p in app_perms) - installed_apps = {app['id'] for app in app_list(installed=True)['apps']} - - assert installed_apps == app_perms_prefix + assert set(_installed_apps()) == app_perms_prefix def can_access_webpage(webpath, logged_as=None): From 7518beaf2f8a02482b4b89e350d8fb6bdb65f263 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 25 Nov 2019 19:16:46 +0100 Subject: [PATCH 0525/3170] Fuck it we don't need these options --- data/actionsmap/yunohost.yml | 7 ------- src/yunohost/app.py | 25 +++++-------------------- 2 files changed, 5 insertions(+), 27 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 4cb108e3a..fbdd8aff6 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -548,9 +548,6 @@ app: action_help: List apps api: GET /apps arguments: - -f: - full: --filter - help: Name filter of app_id or app_name -r: full: --raw help: Return the full app_dict @@ -559,10 +556,6 @@ app: full: --installed help: Return only installed apps action: store_true - -b: - full: --with-backup - help: Return only apps with backup feature (force --installed filter) - action: store_true ### app_info() info: diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 4ecdc0db6..2b0bba33e 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -71,18 +71,14 @@ re_app_instance_name = re.compile( ) -def app_list(filter=None, raw=False, installed=False, with_backup=False): +def app_list(raw=False, installed=False): """ List apps Keyword argument: - filter -- Name filter of app_id or app_name raw -- Return the full app_dict installed -- Return only installed apps - with_backup -- Return only apps with backup feature (force --installed filter) - """ - installed = with_backup or installed list_dict = {} if raw else [] @@ -112,27 +108,16 @@ def app_list(filter=None, raw=False, installed=False, with_backup=False): app_info_dict = app_dict[app_id] - # Apply filter if there's one - if (filter and - (filter not in app_id) and - (filter not in app_info_dict['manifest']['name'])): - continue - # Ignore non-installed app if user wants only installed apps app_installed = _is_installed(app_id) if installed and not app_installed: continue - # Ignore apps which don't have backup/restore script if user wants - # only apps with backup features - if with_backup and ( - not os.path.isfile(APPS_SETTING_PATH + app_id + '/scripts/backup') or - not os.path.isfile(APPS_SETTING_PATH + app_id + '/scripts/restore') - ): - continue - if raw: app_info_dict['installed'] = app_installed + app_info_dict['supports_backup_restore'] = (app_installed and + os.path.isfile(APPS_SETTING_PATH + app_id + '/scripts/backup') and + os.path.isfile(APPS_SETTING_PATH + app_id + '/scripts/restore')) # dirty: we used to have manifest containing multi_instance value in form of a string # but we've switched to bool, this line ensure retrocompatibility @@ -173,7 +158,7 @@ def app_info(app, raw=False): manifest = _get_manifest_of_app(app_setting_path) if raw: - ret = app_list(filter=app, raw=True)[app] + ret = app_list(raw=True)[app] ret['settings'] = _get_app_settings(app) # Determine upgradability From 3951f5e1b8c6d76e54b7387d3124abe7930a2437 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 25 Nov 2019 21:05:00 +0100 Subject: [PATCH 0526/3170] Refactor the whole app_list and app_info madness ... --- data/actionsmap/yunohost.yml | 32 +++--- src/yunohost/app.py | 186 +++++++++++++++-------------------- src/yunohost/tools.py | 8 +- 3 files changed, 101 insertions(+), 125 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index fbdd8aff6..cf98ca8c8 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -543,35 +543,41 @@ app: category_help: Manage apps actions: + catalog: + action_help: Show the catalog of installable application + api: GET /appscatalog + arguments: + -f: + full: --full + help: Display all details, including the app manifest and various other infos + action: store_true + + ### app_list() list: - action_help: List apps + action_help: List installed apps api: GET /apps arguments: - -r: - full: --raw - help: Return the full app_dict - action: store_true - -i: - full: --installed - help: Return only installed apps + -f: + full: --full + help: Display all details, including the app manifest and various other infos action: store_true ### app_info() info: - action_help: Get information about an installed app + action_help: Show infos about a specific installed app api: GET /apps/ arguments: app: help: Specific app ID - -r: - full: --raw - help: Return the full app_dict + -f: + full: --full + help: Display all details, including the app manifest and various other infos action: store_true ### app_map() map: - action_help: List apps by domain + action_help: Show the mapping between urls and apps api: GET /appsmap arguments: -a: diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 2b0bba33e..1c4073893 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -71,76 +71,41 @@ re_app_instance_name = re.compile( ) -def app_list(raw=False, installed=False): +def app_catalog(full=False, all=False): """ - List apps - - Keyword argument: - raw -- Return the full app_dict - installed -- Return only installed apps + Return a dict of apps available to installation from Yunohost's app catalog """ - list_dict = {} if raw else [] - # Get app list from catalog cache - app_dict = _load_apps_catalog() + catalog = _load_apps_catalog() + installed_apps = set(_installed_apps()) - # Get app list from the app settings directory - for app in _installed_apps(): - if app not in app_dict: - # Handle multi-instance case like wordpress__2 - if '__' in app: - original_app = app[:app.index('__')] - if original_app in app_dict: - app_dict[app] = app_dict[original_app] - continue - # FIXME : What if it's not !?!? + for app, infos in catalog.items(): + infos["installed"] = app in installed_apps - manifest = _get_manifest_of_app(os.path.join(APPS_SETTING_PATH, app)) - app_dict[app] = {"manifest": manifest} + if not full: + catalog[app] = { + "description": _value_for_locale(infos['manifest']['description']), + "level": infos["level"], + } - app_dict[app]['repository'] = None - - # Sort app list - sorted_app_list = sorted(app_dict.keys()) - - for app_id in sorted_app_list: - - app_info_dict = app_dict[app_id] - - # Ignore non-installed app if user wants only installed apps - app_installed = _is_installed(app_id) - if installed and not app_installed: - continue - - if raw: - app_info_dict['installed'] = app_installed - app_info_dict['supports_backup_restore'] = (app_installed and - os.path.isfile(APPS_SETTING_PATH + app_id + '/scripts/backup') and - os.path.isfile(APPS_SETTING_PATH + app_id + '/scripts/restore')) - - # dirty: we used to have manifest containing multi_instance value in form of a string - # but we've switched to bool, this line ensure retrocompatibility - - app_info_dict["manifest"]["multi_instance"] = is_true(app_info_dict["manifest"].get("multi_instance", False)) - - list_dict[app_id] = app_info_dict - - else: - list_dict.append({ - 'id': app_id, - 'name': app_info_dict['manifest']['name'], - 'label': _get_app_settings(app_id).get("label", "?") if app_installed else None, - 'description': _value_for_locale(app_info_dict['manifest']['description']), - # FIXME: Temporarly allow undefined license - 'license': app_info_dict['manifest'].get('license', m18n.n('license_undefined')), - 'installed': app_installed - }) - - return {'apps': list_dict} if not raw else list_dict + return {"apps": catalog} -def app_info(app, raw=False): +def app_list(full=False): + """ + List installed apps + """ + out = [] + for app_id in sorted(_installed_apps()): + app_info_dict = app_info(app_id, full=full) + app_info_dict["id"] = app_id + out.append(app_info_dict) + + return {'apps': out} + + +def app_info(app, full=False): """ Get app info @@ -152,39 +117,47 @@ def app_info(app, raw=False): if not _is_installed(app): raise YunohostError('app_not_installed', app=app, all_apps=_get_all_installed_apps_id()) - app_setting_path = APPS_SETTING_PATH + app + local_manifest = _get_manifest_of_app(os.path.join(APPS_SETTING_PATH, app)) + settings = _get_app_settings(app) - # Retrieve manifest and status - manifest = _get_manifest_of_app(app_setting_path) - - if raw: - ret = app_list(raw=True)[app] - ret['settings'] = _get_app_settings(app) - - # Determine upgradability - # In case there is neither update_time nor install_time, we assume the app can/has to be upgraded - local_update_time = ret['settings'].get('update_time', ret['settings'].get('install_time', 0)) - - if 'lastUpdate' not in ret or 'git' not in ret: - upgradable = "url_required" - elif ret['lastUpdate'] > local_update_time: - upgradable = "yes" - else: - upgradable = "no" - - ret['upgradable'] = upgradable - ret['change_url'] = os.path.exists(os.path.join(app_setting_path, "scripts", "change_url")) - ret['version'] = manifest.get('version', '-') + ret = { + 'description': _value_for_locale(local_manifest['description']), + 'name': local_manifest['name'], + 'version': local_manifest.get('version', '-'), + } + if not full: return ret - info = { - 'name': manifest['name'], - 'description': _value_for_locale(manifest['description']), - 'license': manifest.get('license', m18n.n('license_undefined')), - 'version': manifest.get('version', '-'), - } - return info + 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) + ret["from_catalog"] = _load_apps_catalog().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")) + ret['supports_backup_restore'] = (os.path.exists(os.path.join(APPS_SETTING_PATH, app, "scripts", "backup")) and + os.path.exists(os.path.join(APPS_SETTING_PATH, app, "scripts", "restore"))) + ret['supports_multi_instance'] = is_true(local_manifest.get("multi_instance", False)) + return ret + + +def _app_upgradable(app_infos): + + # Determine upgradability + # In case there is neither update_time nor install_time, we assume the app can/has to be upgraded + + 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"): + return "url_required" + + settings = app_infos["settings"] + local_update_time = settings.get('update_time', settings.get('install_time', 0)) + if app_infos["from_catalog"]['lastUpdate'] > local_update_time: + return "yes" + else: + return "no" def app_map(app=None, raw=False, user=None): @@ -461,7 +434,7 @@ def app_upgrade(app=[], url=None, file=None): for number, app_instance_name in enumerate(apps): logger.info(m18n.n('app_upgrade_app_name', app=app_instance_name)) - app_dict = app_info(app_instance_name, raw=True) + app_dict = app_info(app_instance_name, full=True) if file and isinstance(file, dict): # We use this dirty hack to test chained upgrades in unit/functional tests @@ -636,7 +609,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu if answer.upper() != "Y": raise YunohostError("aborting") - raw_app_list = app_list(raw=True) + raw_app_list = _load_apps_catalog() if app in raw_app_list or ('@' in app) or ('http://' in app) or ('https://' in app): @@ -1292,7 +1265,7 @@ def app_ssowatconf(): protected_regex += _get_setting(app_settings, 'protected_regex') # New permission system - this_app_perms = {name: info for name, info in all_permissions.items() if name.startswith(app['id'] + ".")} + this_app_perms = {name: info for name, info in all_permissions.items() if name.startswith(app + ".")} for perm_name, perm_info in this_app_perms.items(): # Ignore permissions for which there's no url defined @@ -2135,17 +2108,16 @@ def _fetch_app_from_git(app): else: manifest['remote']['revision'] = revision else: - app_dict = app_list(raw=True) + app_dict = _load_apps_catalog() - if app in app_dict: - app_info = app_dict[app] - app_info['manifest']['lastUpdate'] = app_info['lastUpdate'] - manifest = app_info['manifest'] - else: + if app not in app_dict: raise YunohostError('app_unknown') - - if 'git' not in app_info: + elif 'git' not in app_dict[app]: raise YunohostError('app_unsupported_remote_type') + + app_info = app_dict[app] + app_info['manifest']['lastUpdate'] = app_info['lastUpdate'] + manifest = app_info['manifest'] url = app_info['git']['url'] if 'github.com' in url: @@ -2242,9 +2214,11 @@ def _is_installed(app): """ return os.path.isdir(APPS_SETTING_PATH + app) + def _installed_apps(): return os.listdir(APPS_SETTING_PATH) + def _value_for_locale(values): """ Return proper value for current locale @@ -2755,16 +2729,12 @@ def random_password(length=8): def unstable_apps(): - raw_app_installed = app_list(installed=True, raw=True) output = [] - for app, infos in raw_app_installed.items(): + for infos in app_list(full=True): - repo = infos.get("repository", None) - state = infos.get("state", None) - - if repo is None or state in ["inprogress", "notworking"]: - output.append(app) + if not infos.get("from_catalog") or infos.get("from_catalog").get("state") in ["inprogress", "notworking"]: + output.append(infos["id"]) return output diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index c05933dc0..520dab822 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -470,17 +470,17 @@ def _list_upgradable_apps(): app_list_installed = os.listdir(APPS_SETTING_PATH) for app_id in app_list_installed: - app_dict = app_info(app_id, raw=True) + app_dict = app_info(app_id, full=True) if app_dict["upgradable"] == "yes": # FIXME : would make more sense for these infos to be computed # directly in app_info and used to check the upgradability of # the app... - current_version = app_dict.get("version", "?") + current_version = app_dict.get("manifest", {}).get("version", "?") current_commit = app_dict.get("settings", {}).get("current_revision", "?")[:7] - new_version = app_dict.get("manifest",{}).get("version","?") - new_commit = app_dict.get("git", {}).get("revision", "?")[:7] + new_version = app_dict.get("from_catalog", {}).get("manifest", {}).get("version", "?") + new_commit = app_dict.get("from_catalog", {}).get("git", {}).get("revision", "?")[:7] if current_version == new_version: current_version += " (" + current_commit + ")" From 06fe3504b338e6ffc5e9e2ff0fe0f5b174d36a9a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 25 Nov 2019 21:40:21 +0100 Subject: [PATCH 0527/3170] Change app catalog API to support categories --- data/actionsmap/yunohost.yml | 5 +- src/yunohost/app.py | 64 +++++++++++++++++--------- src/yunohost/tests/test_appscatalog.py | 36 ++++++++++----- 3 files changed, 70 insertions(+), 35 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index cf98ca8c8..3a4c9db97 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -551,7 +551,10 @@ app: full: --full help: Display all details, including the app manifest and various other infos action: store_true - + -c: + full: --with-categories + help: Also return a list of app categories + action: store_true ### app_list() list: diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 1c4073893..a54a18c6f 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -57,7 +57,7 @@ APP_TMP_FOLDER = INSTALL_TMP + '/from_file' APPS_CATALOG_CACHE = '/var/cache/yunohost/repo' APPS_CATALOG_CONF = '/etc/yunohost/apps_catalog.yml' APPS_CATALOG_CRON_PATH = "/etc/cron.daily/yunohost-fetch-apps-catalog" -APPS_CATALOG_API_VERSION = 1 +APPS_CATALOG_API_VERSION = 2 APPS_CATALOG_DEFAULT_URL = "https://app.yunohost.org/default" re_github_repo = re.compile( @@ -71,7 +71,7 @@ re_app_instance_name = re.compile( ) -def app_catalog(full=False, all=False): +def app_catalog(full=False, with_categories=False): """ Return a dict of apps available to installation from Yunohost's app catalog """ @@ -80,16 +80,32 @@ def app_catalog(full=False, all=False): catalog = _load_apps_catalog() installed_apps = set(_installed_apps()) - for app, infos in catalog.items(): + # Trim info for apps if not using --full + for app, infos in catalog["apps"].items(): infos["installed"] = app in installed_apps + infos["manifest"]["description"] = _value_for_locale(infos['manifest']['description']) + if not full: - catalog[app] = { - "description": _value_for_locale(infos['manifest']['description']), + catalog["apps"][app] = { + "description": infos['manifest']['description'], "level": infos["level"], } - return {"apps": catalog} + # Trim info for categories if not using --full + for category in catalog["categories"]: + category["title"] = _value_for_locale(category["title"]) + category["description"] = _value_for_locale(category["description"]) + + if not full: + catalog["categories"] = [{"id": c["id"], + "description": c["description"]} + for c in catalog["categories"]] + + if not with_categories: + return {"apps": catalog["apps"]} + else: + return {"apps": catalog["apps"], "categories": catalog["categories"]} def app_list(full=False): @@ -107,12 +123,7 @@ def app_list(full=False): def app_info(app, full=False): """ - Get app info - - Keyword argument: - app -- Specific app ID - raw -- Return the full app_dict - + Get info for a specific app """ if not _is_installed(app): raise YunohostError('app_not_installed', app=app, all_apps=_get_all_installed_apps_id()) @@ -133,7 +144,7 @@ def app_info(app, full=False): 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) - ret["from_catalog"] = _load_apps_catalog().get(absolute_app_name, {}) + 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")) ret['supports_backup_restore'] = (os.path.exists(os.path.join(APPS_SETTING_PATH, app, "scripts", "backup")) and @@ -609,7 +620,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu if answer.upper() != "Y": raise YunohostError("aborting") - raw_app_list = _load_apps_catalog() + raw_app_list = _load_apps_catalog()["apps"] if app in raw_app_list or ('@' in app) or ('http://' in app) or ('https://' in app): @@ -2108,7 +2119,7 @@ def _fetch_app_from_git(app): else: manifest['remote']['revision'] = revision else: - app_dict = _load_apps_catalog() + app_dict = _load_apps_catalog()["apps"] if app not in app_dict: raise YunohostError('app_unknown') @@ -2645,11 +2656,14 @@ def _update_apps_catalog(): def _load_apps_catalog(): """ - Read all the apps catalog cache files and build a single dict (app_dict) - corresponding to all known apps in all indexes + Read all the apps catalog cache files and build a single dict (merged_catalog) + corresponding to all known apps and categories """ - app_dict = {} + merged_catalog = { + "apps": {}, + "categories": [] + } for apps_catalog_id in [L["id"] for L in _read_apps_catalog_list()]: @@ -2672,18 +2686,22 @@ def _load_apps_catalog(): del apps_catalog_content["from_api_version"] # Add apps from this catalog to the output - for app, info in apps_catalog_content.items(): + for app, info in apps_catalog_content["apps"].items(): # (N.B. : there's a small edge case where multiple apps catalog could be listing the same apps ... # in which case we keep only the first one found) - if app in app_dict: - logger.warning("Duplicate app %s found between apps catalog %s and %s" % (app, apps_catalog_id, app_dict[app]['repository'])) + if app in merged_catalog["apps"]: + logger.warning("Duplicate app %s found between apps catalog %s and %s" + % (app, apps_catalog_id, merged_catalog["apps"][app]['repository'])) continue info['repository'] = apps_catalog_id - app_dict[app] = info + merged_catalog["apps"][app] = info - return app_dict + # Annnnd categories + merged_catalog["categories"] += apps_catalog_content["categories"] + + return merged_catalog # # ############################### # diff --git a/src/yunohost/tests/test_appscatalog.py b/src/yunohost/tests/test_appscatalog.py index 613b59012..39a0be206 100644 --- a/src/yunohost/tests/test_appscatalog.py +++ b/src/yunohost/tests/test_appscatalog.py @@ -14,6 +14,7 @@ from yunohost.app import (_initialize_apps_catalog_system, _update_apps_catalog, _actual_apps_catalog_api_url, _load_apps_catalog, + app_catalog, logger, APPS_CATALOG_CACHE, APPS_CATALOG_CONF, @@ -25,8 +26,14 @@ APPS_CATALOG_DEFAULT_URL_FULL = _actual_apps_catalog_api_url(APPS_CATALOG_DEFAUL CRON_FOLDER, CRON_NAME = APPS_CATALOG_CRON_PATH.rsplit("/", 1) DUMMY_APP_CATALOG = """{ - "foo": {"id": "foo", "level": 4}, - "bar": {"id": "bar", "level": 7} + "apps": { + "foo": {"id": "foo", "level": 4, "category": "yolo", "manifest":{"description": "Foo"}}, + "bar": {"id": "bar", "level": 7, "category": "swag", "manifest":{"description": "Bar"}} + }, + "categories": [ + {"id": "yolo", "description": "YoLo"}, + {"id": "swag", "description": "sWaG"} + ] } """ @@ -107,7 +114,7 @@ def test_apps_catalog_emptylist(): assert not len(apps_catalog_list) -def test_apps_catalog_update_success(mocker): +def test_apps_catalog_update_nominal(mocker): # Initialize ... _initialize_apps_catalog_system() @@ -130,9 +137,16 @@ def test_apps_catalog_update_success(mocker): # Cache shouldn't be empty anymore empty assert glob.glob(APPS_CATALOG_CACHE + "/*") - app_dict = _load_apps_catalog() - assert "foo" in app_dict.keys() - assert "bar" in app_dict.keys() + # And if we load the catalog, we sould find + # - foo and bar as apps (unordered), + # - yolo and swag as categories (ordered) + catalog = app_catalog(with_categories=True) + + assert "apps" in catalog + assert set(catalog["apps"].keys()) == set(["foo", "bar"]) + + assert "categories" in catalog + assert [c["id"] for c in catalog["categories"]] == ["yolo", "swag"] def test_apps_catalog_update_404(mocker): @@ -219,7 +233,7 @@ def test_apps_catalog_load_with_empty_cache(mocker): # Try to load the apps catalog # This should implicitly trigger an update in the background mocker.spy(m18n, "n") - app_dict = _load_apps_catalog() + app_dict = _load_apps_catalog()["apps"] m18n.n.assert_any_call("apps_catalog_obsolete_cache") m18n.n.assert_any_call("apps_catalog_update_success") @@ -252,7 +266,7 @@ def test_apps_catalog_load_with_conflicts_between_lists(mocker): # Try to load the apps catalog # This should implicitly trigger an update in the background mocker.spy(logger, "warning") - app_dict = _load_apps_catalog() + app_dict = _load_apps_catalog()["apps"] logger.warning.assert_any_call(AnyStringWith("Duplicate")) # Cache shouldn't be empty anymore empty @@ -291,7 +305,7 @@ def test_apps_catalog_load_with_oudated_api_version(mocker): m.register_uri("GET", APPS_CATALOG_DEFAULT_URL_FULL, text=DUMMY_APP_CATALOG) mocker.spy(m18n, "n") - app_dict = _load_apps_catalog() + app_dict = _load_apps_catalog()["apps"] m18n.n.assert_any_call("apps_catalog_update_success") assert "foo" in app_dict.keys() @@ -329,7 +343,7 @@ def test_apps_catalog_migrate_legacy_explicitly(): assert cron_job_is_there() # Reading the apps_catalog should work - app_dict = _load_apps_catalog() + app_dict = _load_apps_catalog()["apps"] assert "foo" in app_dict.keys() assert "bar" in app_dict.keys() @@ -343,7 +357,7 @@ def test_apps_catalog_migrate_legacy_implicitly(): with requests_mock.Mocker() as m: m.register_uri("GET", APPS_CATALOG_DEFAULT_URL_FULL, text=DUMMY_APP_CATALOG) - app_dict = _load_apps_catalog() + app_dict = _load_apps_catalog()["apps"] assert "foo" in app_dict.keys() assert "bar" in app_dict.keys() From b74c7c888ce01d516738234b66bf93b1e2a750c2 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 27 Nov 2019 15:37:22 +0100 Subject: [PATCH 0528/3170] Improve robustness / idempotency(?) of new app catalog migration --- .../data_migrations/0013_futureproof_apps_catalog_system.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/yunohost/data_migrations/0013_futureproof_apps_catalog_system.py b/src/yunohost/data_migrations/0013_futureproof_apps_catalog_system.py index 90c46ea5b..ff4925183 100644 --- a/src/yunohost/data_migrations/0013_futureproof_apps_catalog_system.py +++ b/src/yunohost/data_migrations/0013_futureproof_apps_catalog_system.py @@ -44,7 +44,8 @@ class MyMigration(Migration): except Exception as e: logger.warning("Unable to parse the legacy conf %s (error : %s) ... migrating anyway" % (LEGACY_APPS_CATALOG_CONF, str(e))) - os.rename(LEGACY_APPS_CATALOG_CONF, LEGACY_APPS_CATALOG_CONF_BACKUP) + if os.path.exists(LEGACY_APPS_CATALOG_CONF): + os.rename(LEGACY_APPS_CATALOG_CONF, LEGACY_APPS_CATALOG_CONF_BACKUP) _initialize_apps_catalog_system() _update_apps_catalog() From 97e5d3b992fbe25f84946060c9a4e40922e4558f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 27 Nov 2019 23:58:36 +0100 Subject: [PATCH 0529/3170] Remove those random sudo which are useless yet triggers LDAP warning when LDAP is in bad state --- data/helpers.d/apt | 2 +- data/helpers.d/backup | 24 +++++++++++----------- data/helpers.d/logging | 4 ++-- data/helpers.d/logrotate | 6 +++--- data/helpers.d/mysql | 8 ++++---- data/helpers.d/nginx | 2 +- data/helpers.d/php | 8 ++++---- data/helpers.d/postgresql | 10 ++++----- data/helpers.d/setting | 4 ++-- data/helpers.d/string | 2 +- data/helpers.d/systemd | 8 ++++---- data/helpers.d/user | 6 +++--- data/hooks/backup/05-conf_ldap | 4 ++-- data/hooks/conf_regen/01-yunohost | 14 ++++++------- data/hooks/conf_regen/02-ssl | 6 +++--- data/hooks/conf_regen/06-slapd | 2 +- data/hooks/conf_regen/09-nslcd | 2 +- data/hooks/conf_regen/12-metronome | 12 +++++------ data/hooks/conf_regen/15-nginx | 8 ++++---- data/hooks/conf_regen/19-postfix | 4 ++-- data/hooks/conf_regen/25-dovecot | 20 +++++++++--------- data/hooks/conf_regen/31-rspamd | 24 +++++++++++----------- data/hooks/conf_regen/34-mysql | 16 +++++++-------- data/hooks/conf_regen/37-avahi-daemon | 2 +- data/hooks/conf_regen/40-glances | 2 +- data/hooks/conf_regen/43-dnsmasq | 4 ++-- data/hooks/conf_regen/46-nsswitch | 2 +- data/hooks/conf_regen/52-fail2ban | 2 +- data/hooks/restore/05-conf_ldap | 2 +- data/hooks/restore/08-conf_ssh | 4 ++-- data/hooks/restore/11-conf_ynh_mysql | 16 +++++++-------- data/hooks/restore/14-conf_ssowat | 2 +- data/hooks/restore/17-data_home | 2 +- data/hooks/restore/20-conf_ynh_firewall | 4 ++-- data/hooks/restore/21-conf_ynh_certs | 8 ++++---- data/hooks/restore/23-data_mail | 8 ++++---- data/hooks/restore/26-conf_xmpp | 6 +++--- data/hooks/restore/29-conf_nginx | 4 ++-- data/hooks/restore/32-conf_cron | 4 ++-- data/hooks/restore/40-conf_ynh_currenthost | 2 +- src/yunohost/tools.py | 6 +++--- 41 files changed, 138 insertions(+), 138 deletions(-) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index da2740d01..55c85c90b 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -13,7 +13,7 @@ ynh_wait_dpkg_free() { for try in `seq 1 17` do # Check if /var/lib/dpkg/lock is used by another process - if sudo lsof /var/lib/dpkg/lock > /dev/null + if lsof /var/lib/dpkg/lock > /dev/null then echo "apt is already in use..." # Sleep an exponential time at each round diff --git a/data/helpers.d/backup b/data/helpers.d/backup index d3ffffcd3..590e951a5 100644 --- a/data/helpers.d/backup +++ b/data/helpers.d/backup @@ -179,7 +179,7 @@ ynh_restore () { # usage: _get_archive_path ORIGIN_PATH _get_archive_path () { # For security reasons we use csv python library to read the CSV - sudo python -c " + python -c " import sys import csv with open(sys.argv[1], 'r') as backup_file: @@ -302,7 +302,7 @@ ynh_store_file_checksum () { ynh_handle_getopts_args "$@" local checksum_setting_name=checksum_${file//[\/ ]/_} # Replace all '/' and ' ' by '_' - ynh_app_setting_set --app=$app --key=$checksum_setting_name --value=$(sudo md5sum "$file" | cut -d' ' -f1) + ynh_app_setting_set --app=$app --key=$checksum_setting_name --value=$(md5sum "$file" | cut -d' ' -f1) # If backup_file_checksum isn't empty, ynh_backup_if_checksum_is_different has made a backup if [ -n "${backup_file_checksum-}" ] @@ -339,11 +339,11 @@ ynh_backup_if_checksum_is_different () { backup_file_checksum="" if [ -n "$checksum_value" ] then # Proceed only if a value was stored into the app settings - if [ -e $file ] && ! echo "$checksum_value $file" | sudo md5sum -c --status + if [ -e $file ] && ! echo "$checksum_value $file" | md5sum -c --status then # If the checksum is now different backup_file_checksum="/home/yunohost.conf/backup/$file.backup.$(date '+%Y%m%d.%H%M%S')" - sudo mkdir -p "$(dirname "$backup_file_checksum")" - sudo cp -a "$file" "$backup_file_checksum" # Backup the current file + mkdir -p "$(dirname "$backup_file_checksum")" + cp -a "$file" "$backup_file_checksum" # Backup the current file ynh_print_warn "File $file has been manually modified since the installation or last upgrade. So it has been duplicated in $backup_file_checksum" echo "$backup_file_checksum" # Return the name of the backup file fi @@ -394,7 +394,7 @@ ynh_backup_before_upgrade () { if [ "$NO_BACKUP_UPGRADE" -eq 0 ] then # Check if a backup already exists with the prefix 1 - if sudo yunohost backup list | grep -q $app_bck-pre-upgrade1 + if yunohost backup list | grep -q $app_bck-pre-upgrade1 then # Prefix becomes 2 to preserve the previous backup backup_number=2 @@ -402,14 +402,14 @@ ynh_backup_before_upgrade () { fi # Create backup - sudo BACKUP_CORE_ONLY=1 yunohost backup create --apps $app --name $app_bck-pre-upgrade$backup_number --debug + BACKUP_CORE_ONLY=1 yunohost backup create --apps $app --name $app_bck-pre-upgrade$backup_number --debug if [ "$?" -eq 0 ] then # If the backup succeeded, remove the previous backup - if sudo yunohost backup list | grep -q $app_bck-pre-upgrade$old_backup_number + if yunohost backup list | grep -q $app_bck-pre-upgrade$old_backup_number then # Remove the previous backup only if it exists - sudo yunohost backup delete $app_bck-pre-upgrade$old_backup_number > /dev/null + yunohost backup delete $app_bck-pre-upgrade$old_backup_number > /dev/null fi else ynh_die --message="Backup failed, the upgrade process was aborted." @@ -438,12 +438,12 @@ ynh_restore_upgradebackup () { if [ "$NO_BACKUP_UPGRADE" -eq 0 ] then # Check if an existing backup can be found before removing and restoring the application. - if sudo yunohost backup list | grep -q $app_bck-pre-upgrade$backup_number + if yunohost backup list | grep -q $app_bck-pre-upgrade$backup_number then # Remove the application then restore it - sudo yunohost app remove $app + yunohost app remove $app # Restore the backup - sudo yunohost backup restore $app_bck-pre-upgrade$backup_number --apps $app --force --debug + yunohost backup restore $app_bck-pre-upgrade$backup_number --apps $app --force --debug ynh_die --message="The app was restored to the way it was before the failed upgrade." fi else diff --git a/data/helpers.d/logging b/data/helpers.d/logging index be33b75a5..89fb89c6e 100644 --- a/data/helpers.d/logging +++ b/data/helpers.d/logging @@ -46,10 +46,10 @@ ynh_print_info() { # Requires YunoHost version 2.6.4 or higher. ynh_no_log() { local ynh_cli_log=/var/log/yunohost/yunohost-cli.log - sudo cp -a ${ynh_cli_log} ${ynh_cli_log}-move + cp -a ${ynh_cli_log} ${ynh_cli_log}-move eval $@ local exit_code=$? - sudo mv ${ynh_cli_log}-move ${ynh_cli_log} + mv ${ynh_cli_log}-move ${ynh_cli_log} return $? } diff --git a/data/helpers.d/logrotate b/data/helpers.d/logrotate index 82cdee6a5..9e2429218 100644 --- a/data/helpers.d/logrotate +++ b/data/helpers.d/logrotate @@ -90,8 +90,8 @@ $logfile { $su_directive } EOF - sudo mkdir -p $(dirname "$logfile") # Create the log directory, if not exist - cat ${app}-logrotate | sudo $customtee /etc/logrotate.d/$app > /dev/null # Append this config to the existing config file, or replace the whole config file (depending on $customtee) + mkdir -p $(dirname "$logfile") # Create the log directory, if not exist + cat ${app}-logrotate | $customtee /etc/logrotate.d/$app > /dev/null # Append this config to the existing config file, or replace the whole config file (depending on $customtee) } # Remove the app's logrotate config. @@ -101,6 +101,6 @@ EOF # Requires YunoHost version 2.6.4 or higher. ynh_remove_logrotate () { if [ -e "/etc/logrotate.d/$app" ]; then - sudo rm "/etc/logrotate.d/$app" + rm "/etc/logrotate.d/$app" fi } diff --git a/data/helpers.d/mysql b/data/helpers.d/mysql index e9cf59b3c..91d4abcd2 100644 --- a/data/helpers.d/mysql +++ b/data/helpers.d/mysql @@ -44,7 +44,7 @@ ynh_mysql_execute_as_root() { ynh_handle_getopts_args "$@" database="${database:-}" - ynh_mysql_connect_as --user="root" --password="$(sudo cat $MYSQL_ROOT_PWD_FILE)" \ + ynh_mysql_connect_as --user="root" --password="$(cat $MYSQL_ROOT_PWD_FILE)" \ --database="$database" <<< "$sql" } @@ -65,7 +65,7 @@ ynh_mysql_execute_file_as_root() { ynh_handle_getopts_args "$@" database="${database:-}" - ynh_mysql_connect_as --user="root" --password="$(sudo cat $MYSQL_ROOT_PWD_FILE)" \ + ynh_mysql_connect_as --user="root" --password="$(cat $MYSQL_ROOT_PWD_FILE)" \ --database="$database" < "$file" } @@ -126,7 +126,7 @@ ynh_mysql_dump_db() { # Manage arguments with getopts ynh_handle_getopts_args "$@" - mysqldump -u "root" -p"$(sudo cat $MYSQL_ROOT_PWD_FILE)" --single-transaction --skip-dump-date "$database" + mysqldump -u "root" -p"$(cat $MYSQL_ROOT_PWD_FILE)" --single-transaction --skip-dump-date "$database" } # Create a user @@ -223,7 +223,7 @@ ynh_mysql_remove_db () { # Manage arguments with getopts ynh_handle_getopts_args "$@" - local mysql_root_password=$(sudo cat $MYSQL_ROOT_PWD_FILE) + local mysql_root_password=$(cat $MYSQL_ROOT_PWD_FILE) if mysqlshow -u root -p$mysql_root_password | grep -q "^| $db_name"; then # Check if the database exists ynh_mysql_drop_db $db_name # Remove the database else diff --git a/data/helpers.d/nginx b/data/helpers.d/nginx index ce6b61d3c..e3e45d2d4 100644 --- a/data/helpers.d/nginx +++ b/data/helpers.d/nginx @@ -22,7 +22,7 @@ ynh_add_nginx_config () { finalnginxconf="/etc/nginx/conf.d/$domain.d/$app.conf" local others_var=${1:-} ynh_backup_if_checksum_is_different --file="$finalnginxconf" - sudo cp ../conf/nginx.conf "$finalnginxconf" + cp ../conf/nginx.conf "$finalnginxconf" # To avoid a break by set -u, use a void substitution ${var:-}. If the variable is not set, it's simply set with an empty variable. # Substitute in a nginx config file only if the variable is not empty diff --git a/data/helpers.d/php b/data/helpers.d/php index c9e3ba9ed..41af467c5 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -28,12 +28,12 @@ ynh_add_fpm_config () { ynh_app_setting_set --app=$app --key=fpm_service --value="$fpm_service" finalphpconf="$fpm_config_dir/pool.d/$app.conf" ynh_backup_if_checksum_is_different --file="$finalphpconf" - sudo cp ../conf/php-fpm.conf "$finalphpconf" + cp ../conf/php-fpm.conf "$finalphpconf" ynh_replace_string --match_string="__NAMETOCHANGE__" --replace_string="$app" --target_file="$finalphpconf" ynh_replace_string --match_string="__FINALPATH__" --replace_string="$final_path" --target_file="$finalphpconf" ynh_replace_string --match_string="__USER__" --replace_string="$app" --target_file="$finalphpconf" ynh_replace_string --match_string="__PHPVERSION__" --replace_string="$phpversion" --target_file="$finalphpconf" - sudo chown root: "$finalphpconf" + chown root: "$finalphpconf" ynh_store_file_checksum --file="$finalphpconf" if [ -e "../conf/php-fpm.ini" ] @@ -41,8 +41,8 @@ ynh_add_fpm_config () { echo "Packagers ! Please do not use a separate php ini file, merge your directives in the pool file instead." >&2 finalphpini="$fpm_config_dir/conf.d/20-$app.ini" ynh_backup_if_checksum_is_different "$finalphpini" - sudo cp ../conf/php-fpm.ini "$finalphpini" - sudo chown root: "$finalphpini" + cp ../conf/php-fpm.ini "$finalphpini" + chown root: "$finalphpini" ynh_store_file_checksum "$finalphpini" fi ynh_systemd_action --service_name=$fpm_service --action=reload diff --git a/data/helpers.d/postgresql b/data/helpers.d/postgresql index d252ae2dc..6d8524e54 100644 --- a/data/helpers.d/postgresql +++ b/data/helpers.d/postgresql @@ -45,7 +45,7 @@ ynh_psql_execute_as_root() { ynh_handle_getopts_args "$@" database="${database:-}" - ynh_psql_connect_as --user="postgres" --password="$(sudo cat $PSQL_ROOT_PWD_FILE)" \ + ynh_psql_connect_as --user="postgres" --password="$(cat $PSQL_ROOT_PWD_FILE)" \ --database="$database" <<<"$sql" } @@ -66,7 +66,7 @@ ynh_psql_execute_file_as_root() { ynh_handle_getopts_args "$@" database="${database:-}" - ynh_psql_connect_as --user="postgres" --password="$(sudo cat $PSQL_ROOT_PWD_FILE)" \ + ynh_psql_connect_as --user="postgres" --password="$(cat $PSQL_ROOT_PWD_FILE)" \ --database="$database" <"$file" } @@ -160,7 +160,7 @@ ynh_psql_user_exists() { # Manage arguments with getopts ynh_handle_getopts_args "$@" - if ! sudo --login --user=postgres PGUSER="postgres" PGPASSWORD="$(sudo cat $PSQL_ROOT_PWD_FILE)" psql -tAc "SELECT rolname FROM pg_roles WHERE rolname='$user';" | grep --quiet "$user" ; then + if ! sudo --login --user=postgres PGUSER="postgres" PGPASSWORD="$(cat $PSQL_ROOT_PWD_FILE)" psql -tAc "SELECT rolname FROM pg_roles WHERE rolname='$user';" | grep --quiet "$user" ; then return 1 else return 0 @@ -179,7 +179,7 @@ ynh_psql_database_exists() { # Manage arguments with getopts ynh_handle_getopts_args "$@" - if ! sudo --login --user=postgres PGUSER="postgres" PGPASSWORD="$(sudo cat $PSQL_ROOT_PWD_FILE)" psql -tAc "SELECT datname FROM pg_database WHERE datname='$database';" | grep --quiet "$database"; then + if ! sudo --login --user=postgres PGUSER="postgres" PGPASSWORD="$(cat $PSQL_ROOT_PWD_FILE)" psql -tAc "SELECT datname FROM pg_database WHERE datname='$database';" | grep --quiet "$database"; then return 1 else return 0 @@ -243,7 +243,7 @@ ynh_psql_remove_db() { # Manage arguments with getopts ynh_handle_getopts_args "$@" - local psql_root_password=$(sudo cat $PSQL_ROOT_PWD_FILE) + local psql_root_password=$(cat $PSQL_ROOT_PWD_FILE) if ynh_psql_database_exists --database=$db_name; then # Check if the database exists ynh_psql_drop_db $db_name # Remove the database else diff --git a/data/helpers.d/setting b/data/helpers.d/setting index f0963444a..0e5ae15cc 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -211,7 +211,7 @@ ynh_webpath_available () { # Manage arguments with getopts ynh_handle_getopts_args "$@" - sudo yunohost domain url-available $domain $path_url + yunohost domain url-available $domain $path_url } # Register/book a web path for an app @@ -234,7 +234,7 @@ ynh_webpath_register () { # Manage arguments with getopts ynh_handle_getopts_args "$@" - sudo yunohost app register-url $app $domain $path_url + yunohost app register-url $app $domain $path_url } # Create a new permission for the app diff --git a/data/helpers.d/string b/data/helpers.d/string index fcbc5190d..e50f781fe 100644 --- a/data/helpers.d/string +++ b/data/helpers.d/string @@ -49,7 +49,7 @@ ynh_replace_string () { match_string=${match_string//${delimit}/"\\${delimit}"} replace_string=${replace_string//${delimit}/"\\${delimit}"} - sudo sed --in-place "s${delimit}${match_string}${delimit}${replace_string}${delimit}g" "$target_file" + sed --in-place "s${delimit}${match_string}${delimit}${replace_string}${delimit}g" "$target_file" } # Substitute/replace a special string by another in a file diff --git a/data/helpers.d/systemd b/data/helpers.d/systemd index 105678b88..960382f8f 100644 --- a/data/helpers.d/systemd +++ b/data/helpers.d/systemd @@ -28,7 +28,7 @@ ynh_add_systemd_config () { finalsystemdconf="/etc/systemd/system/$service.service" ynh_backup_if_checksum_is_different --file="$finalsystemdconf" - sudo cp ../conf/$template "$finalsystemdconf" + cp ../conf/$template "$finalsystemdconf" # To avoid a break by set -u, use a void substitution ${var:-}. If the variable is not set, it's simply set with an empty variable. # Substitute in a nginx config file only if the variable is not empty @@ -40,9 +40,9 @@ ynh_add_systemd_config () { fi ynh_store_file_checksum --file="$finalsystemdconf" - sudo chown root: "$finalsystemdconf" - sudo systemctl enable $service - sudo systemctl daemon-reload + chown root: "$finalsystemdconf" + systemctl enable $service + systemctl daemon-reload } # Remove the dedicated systemd config diff --git a/data/helpers.d/user b/data/helpers.d/user index e7890ccb2..7051ed4c0 100644 --- a/data/helpers.d/user +++ b/data/helpers.d/user @@ -16,7 +16,7 @@ ynh_user_exists() { # Manage arguments with getopts ynh_handle_getopts_args "$@" - sudo yunohost user list --output-as json | grep -q "\"username\": \"${username}\"" + yunohost user list --output-as json | grep -q "\"username\": \"${username}\"" } # Retrieve a YunoHost user information @@ -38,7 +38,7 @@ ynh_user_get_info() { # Manage arguments with getopts ynh_handle_getopts_args "$@" - sudo yunohost user info "$username" --output-as plain | ynh_get_plain_key "$key" + yunohost user info "$username" --output-as plain | ynh_get_plain_key "$key" } # Get the list of YunoHost users @@ -50,7 +50,7 @@ ynh_user_get_info() { # # Requires YunoHost version 2.4.0 or higher. ynh_user_list() { - sudo yunohost user list --output-as plain --quiet \ + yunohost user list --output-as plain --quiet \ | awk '/^##username$/{getline; print}' } diff --git a/data/hooks/backup/05-conf_ldap b/data/hooks/backup/05-conf_ldap index 9ae22095e..75b4c2075 100755 --- a/data/hooks/backup/05-conf_ldap +++ b/data/hooks/backup/05-conf_ldap @@ -11,7 +11,7 @@ backup_dir="${1}/conf/ldap" # Backup the configuration ynh_backup "/etc/ldap/slapd.conf" "${backup_dir}/slapd.conf" -sudo slapcat -b cn=config -l "${backup_dir}/cn=config.master.ldif" +slapcat -b cn=config -l "${backup_dir}/cn=config.master.ldif" # Backup the database -sudo slapcat -b dc=yunohost,dc=org -l "${backup_dir}/dc=yunohost-dc=org.ldif" +slapcat -b dc=yunohost,dc=org -l "${backup_dir}/dc=yunohost-dc=org.ldif" diff --git a/data/hooks/conf_regen/01-yunohost b/data/hooks/conf_regen/01-yunohost index 5528236cf..236619079 100755 --- a/data/hooks/conf_regen/01-yunohost +++ b/data/hooks/conf_regen/01-yunohost @@ -38,20 +38,20 @@ do_pre_regen() { if [[ -f $services_path ]]; then tmp_services_path="${services_path}-tmp" new_services_path="${services_path}-new" - sudo cp "$services_path" "$tmp_services_path" + cp "$services_path" "$tmp_services_path" _update_services "$new_services_path" || { - sudo mv "$tmp_services_path" "$services_path" + mv "$tmp_services_path" "$services_path" exit 1 } if [[ -f $new_services_path ]]; then # replace services.yml with new one - sudo mv "$new_services_path" "$services_path" - sudo mv "$tmp_services_path" "${services_path}-old" + mv "$new_services_path" "$services_path" + mv "$tmp_services_path" "${services_path}-old" else - sudo rm -f "$tmp_services_path" + rm -f "$tmp_services_path" fi else - sudo cp services.yml /etc/yunohost/services.yml + cp services.yml /etc/yunohost/services.yml fi # add cron job for diagnosis to be ran at 7h and 19h + a random delay between @@ -66,7 +66,7 @@ EOF } _update_services() { - sudo python2 - << EOF + python2 - << EOF import yaml diff --git a/data/hooks/conf_regen/02-ssl b/data/hooks/conf_regen/02-ssl index 1df3a3260..a893b21e1 100755 --- a/data/hooks/conf_regen/02-ssl +++ b/data/hooks/conf_regen/02-ssl @@ -99,13 +99,13 @@ do_post_regen() { [[ -f "${index_txt}" ]] || { if [[ -f "${index_txt}.saved" ]]; then # use saved database from 2.2 - sudo cp "${index_txt}.saved" "${index_txt}" + cp "${index_txt}.saved" "${index_txt}" elif [[ -f "${index_txt}.old" ]]; then # ... or use the state-1 database - sudo cp "${index_txt}.old" "${index_txt}" + cp "${index_txt}.old" "${index_txt}" else # ... or create an empty one - sudo touch "${index_txt}" + touch "${index_txt}" fi } diff --git a/data/hooks/conf_regen/06-slapd b/data/hooks/conf_regen/06-slapd index 4f7adda78..35a8fcf2e 100755 --- a/data/hooks/conf_regen/06-slapd +++ b/data/hooks/conf_regen/06-slapd @@ -126,7 +126,7 @@ do_post_regen() { # wait a maximum time of 5 minutes # yes, force-reload behave like a restart number_of_wait=0 - while ! sudo su admin -c '' && ((number_of_wait < 60)) + while ! su admin -c '' && ((number_of_wait < 60)) do sleep 5 ((number_of_wait += 1)) diff --git a/data/hooks/conf_regen/09-nslcd b/data/hooks/conf_regen/09-nslcd index 5071ac1fd..7090fc758 100755 --- a/data/hooks/conf_regen/09-nslcd +++ b/data/hooks/conf_regen/09-nslcd @@ -14,7 +14,7 @@ do_post_regen() { regen_conf_files=$1 [[ -z "$regen_conf_files" ]] \ - || sudo service nslcd restart + || service nslcd restart } FORCE=${2:-0} diff --git a/data/hooks/conf_regen/12-metronome b/data/hooks/conf_regen/12-metronome index 7047af660..fbd956e7c 100755 --- a/data/hooks/conf_regen/12-metronome +++ b/data/hooks/conf_regen/12-metronome @@ -14,7 +14,7 @@ do_pre_regen() { # retrieve variables main_domain=$(cat /etc/yunohost/current_host) - domain_list=$(sudo yunohost domain list --output-as plain --quiet) + domain_list=$(yunohost domain list --output-as plain --quiet) # install main conf file cat metronome.cfg.lua \ @@ -42,19 +42,19 @@ do_post_regen() { regen_conf_files=$1 # retrieve variables - domain_list=$(sudo yunohost domain list --output-as plain --quiet) + domain_list=$(yunohost domain list --output-as plain --quiet) # create metronome directories for domains for domain in $domain_list; do - sudo mkdir -p "/var/lib/metronome/${domain//./%2e}/pep" + mkdir -p "/var/lib/metronome/${domain//./%2e}/pep" done # fix some permissions - sudo chown -R metronome: /var/lib/metronome/ - sudo chown -R metronome: /etc/metronome/conf.d/ + chown -R metronome: /var/lib/metronome/ + chown -R metronome: /etc/metronome/conf.d/ [[ -z "$regen_conf_files" ]] \ - || sudo service metronome restart + || service metronome restart } FORCE=${2:-0} diff --git a/data/hooks/conf_regen/15-nginx b/data/hooks/conf_regen/15-nginx index 59654a771..55a5494b2 100755 --- a/data/hooks/conf_regen/15-nginx +++ b/data/hooks/conf_regen/15-nginx @@ -45,7 +45,7 @@ do_pre_regen() { # retrieve variables main_domain=$(cat /etc/yunohost/current_host) - domain_list=$(sudo yunohost domain list --output-as plain --quiet) + domain_list=$(yunohost domain list --output-as plain --quiet) # Support different strategy for security configurations export compatibility="$(yunohost settings get 'security.nginx.compatibility')" @@ -102,15 +102,15 @@ do_post_regen() { [ -z "$regen_conf_files" ] && exit 0 # retrieve variables - domain_list=$(sudo yunohost domain list --output-as plain --quiet) + domain_list=$(yunohost domain list --output-as plain --quiet) # create NGINX conf directories for domains for domain in $domain_list; do - sudo mkdir -p "/etc/nginx/conf.d/${domain}.d" + mkdir -p "/etc/nginx/conf.d/${domain}.d" done # Reload nginx configuration - pgrep nginx && sudo service nginx reload + pgrep nginx && service nginx reload } FORCE=${2:-0} diff --git a/data/hooks/conf_regen/19-postfix b/data/hooks/conf_regen/19-postfix index b37425984..0f09f0299 100755 --- a/data/hooks/conf_regen/19-postfix +++ b/data/hooks/conf_regen/19-postfix @@ -20,7 +20,7 @@ do_pre_regen() { # prepare main.cf conf file main_domain=$(cat /etc/yunohost/current_host) - domain_list=$(sudo yunohost domain list --output-as plain --quiet | tr '\n' ' ') + domain_list=$(yunohost domain list --output-as plain --quiet | tr '\n' ' ') # Support different strategy for security configurations export compatibility="$(yunohost settings get 'security.postfix.compatibility')" @@ -49,7 +49,7 @@ do_post_regen() { regen_conf_files=$1 [[ -z "$regen_conf_files" ]] \ - || { sudo service postfix restart && sudo service postsrsd restart; } + || { service postfix restart && service postsrsd restart; } } diff --git a/data/hooks/conf_regen/25-dovecot b/data/hooks/conf_regen/25-dovecot index d7136df4d..46c9bdf3e 100755 --- a/data/hooks/conf_regen/25-dovecot +++ b/data/hooks/conf_regen/25-dovecot @@ -36,28 +36,28 @@ do_pre_regen() { do_post_regen() { regen_conf_files=$1 - sudo mkdir -p "/etc/dovecot/yunohost.d/pre-ext.d" - sudo mkdir -p "/etc/dovecot/yunohost.d/post-ext.d" + mkdir -p "/etc/dovecot/yunohost.d/pre-ext.d" + mkdir -p "/etc/dovecot/yunohost.d/post-ext.d" # create vmail user id vmail > /dev/null 2>&1 \ - || sudo adduser --system --ingroup mail --uid 500 vmail + || adduser --system --ingroup mail --uid 500 vmail # fix permissions - sudo chown -R vmail:mail /etc/dovecot/global_script - sudo chmod 770 /etc/dovecot/global_script - sudo chown root:mail /var/mail - sudo chmod 1775 /var/mail + chown -R vmail:mail /etc/dovecot/global_script + chmod 770 /etc/dovecot/global_script + chown root:mail /var/mail + chmod 1775 /var/mail [ -z "$regen_conf_files" ] && exit 0 # compile sieve script [[ "$regen_conf_files" =~ dovecot\.sieve ]] && { - sudo sievec /etc/dovecot/global_script/dovecot.sieve - sudo chown -R vmail:mail /etc/dovecot/global_script + sievec /etc/dovecot/global_script/dovecot.sieve + chown -R vmail:mail /etc/dovecot/global_script } - sudo service dovecot restart + service dovecot restart } FORCE=${2:-0} diff --git a/data/hooks/conf_regen/31-rspamd b/data/hooks/conf_regen/31-rspamd index d263d9cc9..26fea4336 100755 --- a/data/hooks/conf_regen/31-rspamd +++ b/data/hooks/conf_regen/31-rspamd @@ -22,11 +22,11 @@ do_post_regen() { ## # create DKIM directory with proper permission - sudo mkdir -p /etc/dkim - sudo chown _rspamd /etc/dkim + mkdir -p /etc/dkim + chown _rspamd /etc/dkim # retrieve domain list - domain_list=$(sudo yunohost domain list --output-as plain --quiet) + domain_list=$(yunohost domain list --output-as plain --quiet) # create DKIM key for domains for domain in $domain_list; do @@ -34,30 +34,30 @@ do_post_regen() { [ ! -f "$domain_key" ] && { # We use a 1024 bit size because nsupdate doesn't seem to be able to # handle 2048... - sudo opendkim-genkey --domain="$domain" \ + opendkim-genkey --domain="$domain" \ --selector=mail --directory=/etc/dkim -b 1024 - sudo mv /etc/dkim/mail.private "$domain_key" - sudo mv /etc/dkim/mail.txt "/etc/dkim/${domain}.mail.txt" + mv /etc/dkim/mail.private "$domain_key" + mv /etc/dkim/mail.txt "/etc/dkim/${domain}.mail.txt" } done # fix DKIM keys permissions - sudo chown _rspamd /etc/dkim/*.mail.key - sudo chmod 400 /etc/dkim/*.mail.key + chown _rspamd /etc/dkim/*.mail.key + chmod 400 /etc/dkim/*.mail.key regen_conf_files=$1 [ -z "$regen_conf_files" ] && exit 0 # compile sieve script [[ "$regen_conf_files" =~ rspamd\.sieve ]] && { - sudo sievec /etc/dovecot/global_script/rspamd.sieve - sudo chown -R vmail:mail /etc/dovecot/global_script - sudo systemctl restart dovecot + sievec /etc/dovecot/global_script/rspamd.sieve + chown -R vmail:mail /etc/dovecot/global_script + systemctl restart dovecot } # Restart rspamd due to the upgrade # https://rspamd.com/announce/2016/08/01/rspamd-1.3.1.html - sudo systemctl -q restart rspamd.service + systemctl -q restart rspamd.service } FORCE=${2:-0} diff --git a/data/hooks/conf_regen/34-mysql b/data/hooks/conf_regen/34-mysql index 8f7b5455e..43f9fdde1 100755 --- a/data/hooks/conf_regen/34-mysql +++ b/data/hooks/conf_regen/34-mysql @@ -18,12 +18,12 @@ do_post_regen() { if [ ! -f /etc/yunohost/mysql ]; then # ensure that mysql is running - sudo systemctl -q is-active mysql.service \ - || sudo service mysql start + systemctl -q is-active mysql.service \ + || service mysql start # generate and set new root password mysql_password=$(ynh_string_random 10) - sudo mysqladmin -s -u root -pyunohost password "$mysql_password" || { + mysqladmin -s -u root -pyunohost password "$mysql_password" || { if [ $FORCE -eq 1 ]; then echo "It seems that you have already configured MySQL." \ "YunoHost needs to have a root access to MySQL to runs its" \ @@ -31,13 +31,13 @@ do_post_regen() { "You can find this new password in /etc/yunohost/mysql." >&2 # set new password with debconf - sudo debconf-set-selections << EOF + debconf-set-selections << EOF $MYSQL_PKG mysql-server/root_password password $mysql_password $MYSQL_PKG mysql-server/root_password_again password $mysql_password EOF # reconfigure Debian package - sudo dpkg-reconfigure -freadline -u "$MYSQL_PKG" 2>&1 + dpkg-reconfigure -freadline -u "$MYSQL_PKG" 2>&1 else echo "It seems that you have already configured MySQL." \ "YunoHost needs to have a root access to MySQL to runs its" \ @@ -49,12 +49,12 @@ EOF } # store new root password - echo "$mysql_password" | sudo tee /etc/yunohost/mysql - sudo chmod 400 /etc/yunohost/mysql + echo "$mysql_password" | tee /etc/yunohost/mysql + chmod 400 /etc/yunohost/mysql fi [[ -z "$regen_conf_files" ]] \ - || sudo service mysql restart + || service mysql restart } FORCE=${2:-0} diff --git a/data/hooks/conf_regen/37-avahi-daemon b/data/hooks/conf_regen/37-avahi-daemon index 655a2e054..239c3ad0c 100755 --- a/data/hooks/conf_regen/37-avahi-daemon +++ b/data/hooks/conf_regen/37-avahi-daemon @@ -15,7 +15,7 @@ do_post_regen() { regen_conf_files=$1 [[ -z "$regen_conf_files" ]] \ - || sudo service avahi-daemon restart + || service avahi-daemon restart } FORCE=${2:-0} diff --git a/data/hooks/conf_regen/40-glances b/data/hooks/conf_regen/40-glances index a19d35d56..70b8f4b5a 100755 --- a/data/hooks/conf_regen/40-glances +++ b/data/hooks/conf_regen/40-glances @@ -14,7 +14,7 @@ do_post_regen() { regen_conf_files=$1 [[ -z "$regen_conf_files" ]] \ - || sudo service glances restart + || service glances restart } FORCE=${2:-0} diff --git a/data/hooks/conf_regen/43-dnsmasq b/data/hooks/conf_regen/43-dnsmasq index ed795c058..90e96a04c 100755 --- a/data/hooks/conf_regen/43-dnsmasq +++ b/data/hooks/conf_regen/43-dnsmasq @@ -26,7 +26,7 @@ do_pre_regen() { ynh_validate_ip4 "$ipv4" || ipv4='127.0.0.1' ipv6=$(curl -s -6 https://ip6.yunohost.org 2>/dev/null || true) ynh_validate_ip6 "$ipv6" || ipv6='' - domain_list=$(sudo yunohost domain list --output-as plain --quiet) + domain_list=$(yunohost domain list --output-as plain --quiet) # add domain conf files for domain in $domain_list; do @@ -51,7 +51,7 @@ do_post_regen() { regen_conf_files=$1 [[ -z "$regen_conf_files" ]] \ - || sudo service dnsmasq restart + || service dnsmasq restart } FORCE=${2:-0} diff --git a/data/hooks/conf_regen/46-nsswitch b/data/hooks/conf_regen/46-nsswitch index 06a596e44..fa9b07511 100755 --- a/data/hooks/conf_regen/46-nsswitch +++ b/data/hooks/conf_regen/46-nsswitch @@ -14,7 +14,7 @@ do_post_regen() { regen_conf_files=$1 [[ -z "$regen_conf_files" ]] \ - || sudo service unscd restart + || service unscd restart } FORCE=${2:-0} diff --git a/data/hooks/conf_regen/52-fail2ban b/data/hooks/conf_regen/52-fail2ban index 950f27b5b..3cb499db7 100755 --- a/data/hooks/conf_regen/52-fail2ban +++ b/data/hooks/conf_regen/52-fail2ban @@ -20,7 +20,7 @@ do_post_regen() { regen_conf_files=$1 [[ -z "$regen_conf_files" ]] \ - || sudo service fail2ban restart + || service fail2ban restart } FORCE=${2:-0} diff --git a/data/hooks/restore/05-conf_ldap b/data/hooks/restore/05-conf_ldap index eb6824993..74093136d 100644 --- a/data/hooks/restore/05-conf_ldap +++ b/data/hooks/restore/05-conf_ldap @@ -5,7 +5,7 @@ if [[ $EUID -ne 0 ]]; then # We need to execute this script as root, since the ldap # service will be shut down during the operation (and sudo # won't be available) - sudo /bin/bash $(readlink -f $0) $1 + /bin/bash $(readlink -f $0) $1 else diff --git a/data/hooks/restore/08-conf_ssh b/data/hooks/restore/08-conf_ssh index 0c0f9bf9b..4b69d1696 100644 --- a/data/hooks/restore/08-conf_ssh +++ b/data/hooks/restore/08-conf_ssh @@ -1,8 +1,8 @@ backup_dir="$1/conf/ssh" if [ -d /etc/ssh/ ]; then - sudo cp -a $backup_dir/. /etc/ssh - sudo service ssh restart + cp -a $backup_dir/. /etc/ssh + service ssh restart else echo "SSH is not installed" fi diff --git a/data/hooks/restore/11-conf_ynh_mysql b/data/hooks/restore/11-conf_ynh_mysql index 24cdb1e79..f54641d6f 100644 --- a/data/hooks/restore/11-conf_ynh_mysql +++ b/data/hooks/restore/11-conf_ynh_mysql @@ -9,15 +9,15 @@ service mysql status >/dev/null 2>&1 \ # retrieve current and new password [ -f /etc/yunohost/mysql ] \ - && curr_pwd=$(sudo cat /etc/yunohost/mysql) -new_pwd=$(sudo cat "${backup_dir}/root_pwd" || sudo cat "${backup_dir}/mysql") + && curr_pwd=$(cat /etc/yunohost/mysql) +new_pwd=$(cat "${backup_dir}/root_pwd" || cat "${backup_dir}/mysql") [ -z "$curr_pwd" ] && curr_pwd="yunohost" [ -z "$new_pwd" ] && { new_pwd=$(ynh_string_random 10) } # attempt to change it -sudo mysqladmin -s -u root -p"$curr_pwd" password "$new_pwd" || { +mysqladmin -s -u root -p"$curr_pwd" password "$new_pwd" || { echo "It seems that you have already configured MySQL." \ "YunoHost needs to have a root access to MySQL to runs its" \ @@ -25,18 +25,18 @@ sudo mysqladmin -s -u root -p"$curr_pwd" password "$new_pwd" || { "You can find this new password in /etc/yunohost/mysql." >&2 # set new password with debconf - sudo debconf-set-selections << EOF + debconf-set-selections << EOF $MYSQL_PKG mysql-server/root_password password $new_pwd $MYSQL_PKG mysql-server/root_password_again password $new_pwd EOF # reconfigure Debian package - sudo dpkg-reconfigure -freadline -u "$MYSQL_PKG" 2>&1 + dpkg-reconfigure -freadline -u "$MYSQL_PKG" 2>&1 } # store new root password -echo "$new_pwd" | sudo tee /etc/yunohost/mysql -sudo chmod 400 /etc/yunohost/mysql +echo "$new_pwd" | tee /etc/yunohost/mysql +chmod 400 /etc/yunohost/mysql # reload the grant tables -sudo mysqladmin -s -u root -p"$new_pwd" reload +mysqladmin -s -u root -p"$new_pwd" reload diff --git a/data/hooks/restore/14-conf_ssowat b/data/hooks/restore/14-conf_ssowat index 01ac787ee..71a011488 100644 --- a/data/hooks/restore/14-conf_ssowat +++ b/data/hooks/restore/14-conf_ssowat @@ -1,3 +1,3 @@ backup_dir="$1/conf/ssowat" -sudo cp -a $backup_dir/. /etc/ssowat +cp -a $backup_dir/. /etc/ssowat diff --git a/data/hooks/restore/17-data_home b/data/hooks/restore/17-data_home index a7ba2733c..6226eab6d 100644 --- a/data/hooks/restore/17-data_home +++ b/data/hooks/restore/17-data_home @@ -1,3 +1,3 @@ backup_dir="$1/data/home" -sudo cp -a $backup_dir/. /home +cp -a $backup_dir/. /home diff --git a/data/hooks/restore/20-conf_ynh_firewall b/data/hooks/restore/20-conf_ynh_firewall index c0ee18818..1789aed1e 100644 --- a/data/hooks/restore/20-conf_ynh_firewall +++ b/data/hooks/restore/20-conf_ynh_firewall @@ -1,4 +1,4 @@ backup_dir="$1/conf/ynh/firewall" -sudo cp -a $backup_dir/. /etc/yunohost -sudo yunohost firewall reload +cp -a $backup_dir/. /etc/yunohost +yunohost firewall reload diff --git a/data/hooks/restore/21-conf_ynh_certs b/data/hooks/restore/21-conf_ynh_certs index 34e651319..983bfb5a1 100644 --- a/data/hooks/restore/21-conf_ynh_certs +++ b/data/hooks/restore/21-conf_ynh_certs @@ -1,7 +1,7 @@ backup_dir="$1/conf/ynh/certs" -sudo mkdir -p /etc/yunohost/certs/ +mkdir -p /etc/yunohost/certs/ -sudo cp -a $backup_dir/. /etc/yunohost/certs/ -sudo service nginx reload -sudo service metronome reload +cp -a $backup_dir/. /etc/yunohost/certs/ +service nginx reload +service metronome reload diff --git a/data/hooks/restore/23-data_mail b/data/hooks/restore/23-data_mail index 81b9b923f..f9fd6e699 100644 --- a/data/hooks/restore/23-data_mail +++ b/data/hooks/restore/23-data_mail @@ -1,8 +1,8 @@ backup_dir="$1/data/mail" -sudo cp -a $backup_dir/. /var/mail/ || echo 'No mail found' -sudo chown -R vmail:mail /var/mail/ +cp -a $backup_dir/. /var/mail/ || echo 'No mail found' +chown -R vmail:mail /var/mail/ # Restart services to use migrated certs -sudo service postfix restart -sudo service dovecot restart +service postfix restart +service dovecot restart diff --git a/data/hooks/restore/26-conf_xmpp b/data/hooks/restore/26-conf_xmpp index 61692b316..a300a7268 100644 --- a/data/hooks/restore/26-conf_xmpp +++ b/data/hooks/restore/26-conf_xmpp @@ -1,7 +1,7 @@ backup_dir="$1/conf/xmpp" -sudo cp -a $backup_dir/etc/. /etc/metronome -sudo cp -a $backup_dir/var/. /var/lib/metronome +cp -a $backup_dir/etc/. /etc/metronome +cp -a $backup_dir/var/. /var/lib/metronome # Restart to apply new conf and certs -sudo service metronome restart +service metronome restart diff --git a/data/hooks/restore/29-conf_nginx b/data/hooks/restore/29-conf_nginx index 0795f53df..7288f52f3 100644 --- a/data/hooks/restore/29-conf_nginx +++ b/data/hooks/restore/29-conf_nginx @@ -1,7 +1,7 @@ backup_dir="$1/conf/nginx" # Copy all conf except apps specific conf located in DOMAIN.d -sudo find $backup_dir/ -mindepth 1 -maxdepth 1 -name '*.d' -or -exec sudo cp -a {} /etc/nginx/conf.d/ \; +find $backup_dir/ -mindepth 1 -maxdepth 1 -name '*.d' -or -exec cp -a {} /etc/nginx/conf.d/ \; # Restart to use new conf and certs -sudo service nginx restart +service nginx restart diff --git a/data/hooks/restore/32-conf_cron b/data/hooks/restore/32-conf_cron index 68657963e..59a2bde61 100644 --- a/data/hooks/restore/32-conf_cron +++ b/data/hooks/restore/32-conf_cron @@ -1,6 +1,6 @@ backup_dir="$1/conf/cron" -sudo cp -a $backup_dir/. /etc/cron.d +cp -a $backup_dir/. /etc/cron.d # Restart just in case -sudo service cron restart +service cron restart diff --git a/data/hooks/restore/40-conf_ynh_currenthost b/data/hooks/restore/40-conf_ynh_currenthost index a0bdf94d3..700e806b4 100644 --- a/data/hooks/restore/40-conf_ynh_currenthost +++ b/data/hooks/restore/40-conf_ynh_currenthost @@ -1,3 +1,3 @@ backup_dir="$1/conf/ynh" -sudo cp -a "${backup_dir}/current_host" /etc/yunohost/current_host +cp -a "${backup_dir}/current_host" /etc/yunohost/current_host diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index c05933dc0..e081f1b3a 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -180,9 +180,9 @@ def _set_hostname(hostname, pretty_hostname=None): # Then call hostnamectl commands = [ - "sudo hostnamectl --static set-hostname".split() + [hostname], - "sudo hostnamectl --transient set-hostname".split() + [hostname], - "sudo hostnamectl --pretty set-hostname".split() + [pretty_hostname] + "hostnamectl --static set-hostname".split() + [hostname], + "hostnamectl --transient set-hostname".split() + [hostname], + "hostnamectl --pretty set-hostname".split() + [pretty_hostname] ] for command in commands: From 32b6c2eccfdecc16269c32cb4fc916fbcf5d4b52 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Fri, 29 Nov 2019 20:39:18 +0900 Subject: [PATCH 0530/3170] Visitors permission needs All Users --- data/helpers.d/setting | 2 +- locales/en.json | 2 +- src/yunohost/app.py | 6 +++--- src/yunohost/permission.py | 18 +++++++++++++----- src/yunohost/tests/test_backuprestore.py | 4 ++-- src/yunohost/tests/test_permission.py | 8 ++++---- 6 files changed, 24 insertions(+), 16 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index f0963444a..93386c9dd 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -189,7 +189,7 @@ EOF # We need this because app temporarily set the app as unprotected to configure it with curl... if [[ "$3" =~ ^(unprotected|skipped)_ ]] && [[ "${4:-}" == "/" ]] then - ynh_permission_update --permission "main" --remove "all_users" --add "visitors" + ynh_permission_update --permission "main" --add "visitors" fi } diff --git a/locales/en.json b/locales/en.json index b3e79ca92..71e17a7c8 100644 --- a/locales/en.json +++ b/locales/en.json @@ -471,7 +471,7 @@ "permission_cannot_remove_main": "Removing a main permission is not allowed", "permission_created": "Permission '{permission:s}' created", "permission_creation_failed": "Could not create permission '{permission}': {error}", - "permission_currently_allowed_for_visitors": "This permission is currently granted to visitors in addition to other groups. You probably want to either remove the 'visitors' permission or remove the other groups it is currently granted to.", + "permission_allowed_for_visitors_but_not_for_all_users": "Visitors can't be granted if all users is not already granted.", "permission_currently_allowed_for_all_users": "This permission is currently granted to all users in addition to other groups. You probably want to either remove the 'all_users' permission or remove the other groups it is currently granted to.", "permission_deleted": "Permission '{permission:s}' deleted", "permission_deletion_failed": "Could not delete permission '{permission}': {error}", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 0e4a473b4..5ccca1394 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -959,12 +959,12 @@ def _migrate_legacy_permissions(app): # If the current permission says app is protected, but there are legacy rules saying it should be public... if app_perm_currently_allowed == ["all_users"] and settings_say_it_should_be_public: # Make it public - user_permission_update(app + ".main", remove="all_users", add="visitors", sync_perm=False) + user_permission_update(app + ".main", add="visitors", sync_perm=False) # If the current permission says app is public, but there are no setting saying it should be public... if app_perm_currently_allowed == ["visitors"] and not settings_say_it_should_be_public: # Make is private - user_permission_update(app + ".main", remove="visitors", add="all_users", sync_perm=False) + user_permission_update(app + ".main", remove="visitors", sync_perm=False) @is_unit_operation() @@ -1194,7 +1194,7 @@ def app_setting(app, key, value=None, delete=False): # We need this because app temporarily set the app as unprotected to configure it with curl... if key.startswith("unprotected_") or key.startswith("skipped_") and value == "/": from permission import user_permission_update - user_permission_update(app + ".main", remove="all_users", add="visitors") + user_permission_update(app + ".main", add="visitors") def app_register_url(app, domain, path): diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 0b88254ce..90f1bb9f0 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -140,12 +140,14 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, # If we end up with something like allowed groups is ["all_users", "volunteers"] # we shall warn the users that they should probably choose between one or the other, # because the current situation is probably not what they expect / is temporary ? - - if len(new_allowed_groups) > 1: - if "all_users" in new_allowed_groups: + + if "all_users" in new_allowed_groups: + if len(new_allowed_groups) > 1 and "visitors" not in new_allowed_groups or len(new_allowed_groups) > 2: logger.warning(m18n.n("permission_currently_allowed_for_all_users")) - if "visitors" in new_allowed_groups: - logger.warning(m18n.n("permission_currently_allowed_for_visitors")) + + # If visitors are allowed, but not all users, it can break some applications, so we prohibit it. + if "visitors" in new_allowed_groups and "all_users" not in new_allowed_groups: + raise YunohostError('permission_allowed_for_visitors_but_not_for_all_users') # Don't update LDAP if we update exactly the same values if set(new_allowed_groups) == set(current_allowed_groups): @@ -258,6 +260,12 @@ def permission_create(operation_logger, permission, url=None, allowed=None, sync if url: attr_dict['URL'] = url + if allowed is not None: + if "visitors" in allowed and "all_users" not in allowed: + if not isinstance(allowed, list): + allowed = [allowed] + allowed.append("all_users") + # Validate that the groups to add actually exist all_existing_groups = user_group_list()['groups'].keys() allowed_ = [] if allowed is None else [allowed] if not isinstance(allowed, list) else allowed diff --git a/src/yunohost/tests/test_backuprestore.py b/src/yunohost/tests/test_backuprestore.py index 48fe7f5d8..d6bc1bb31 100644 --- a/src/yunohost/tests/test_backuprestore.py +++ b/src/yunohost/tests/test_backuprestore.py @@ -510,7 +510,7 @@ def test_backup_and_restore_permission_app(mocker): assert res['permissions_app.admin']['url'] == "/admin" assert res['permissions_app.dev']['url'] == "/dev" - assert res['permissions_app.main']['allowed'] == ["visitors"] + assert "visitors" in res['permissions_app.main']['allowed'] and "all_users" in res['permissions_app.main']['allowed'] assert res['permissions_app.admin']['allowed'] == ["alice"] assert res['permissions_app.dev']['allowed'] == [] @@ -524,7 +524,7 @@ def test_backup_and_restore_permission_app(mocker): assert res['permissions_app.admin']['url'] == "/admin" assert res['permissions_app.dev']['url'] == "/dev" - assert res['permissions_app.main']['allowed'] == ["visitors"] + assert "visitors" in res['permissions_app.main']['allowed'] and "all_users" in res['permissions_app.main']['allowed'] assert res['permissions_app.admin']['allowed'] == ["alice"] assert res['permissions_app.dev']['allowed'] == [] diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index b3fa9fefb..f11d215f1 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -460,13 +460,13 @@ def test_permission_app_propagation_on_ssowat(): args="domain=%s&path=%s&is_public=1&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True) res = user_permission_list(full=True)['permissions'] - assert res['permissions_app.main']['allowed'] == ["visitors"] + assert "visitors" in res['permissions_app.main']['allowed'] and "all_users" in res['permissions_app.main']['allowed'] app_webroot = "https://%s/urlpermissionapp" % maindomain assert can_access_webpage(app_webroot, logged_as=None) assert can_access_webpage(app_webroot, logged_as="alice") - user_permission_update("permissions_app.main", remove="visitors", add="bob") + user_permission_update("permissions_app.main", remove=["visitors", "all_users"], add="bob") res = user_permission_list(full=True)['permissions'] assert not can_access_webpage(app_webroot, logged_as=None) @@ -491,7 +491,7 @@ def test_permission_legacy_app_propagation_on_ssowat(): # App is configured as public by default using the legacy unprotected_uri mechanics # It should automatically be migrated during the install res = user_permission_list(full=True)['permissions'] - assert res['legacy_app.main']['allowed'] == ["visitors"] + assert "visitors" in res['legacy_app.main']['allowed'] and "all_users" in res['legacy_app.main']['allowed'] app_webroot = "https://%s/legacy" % maindomain @@ -499,7 +499,7 @@ def test_permission_legacy_app_propagation_on_ssowat(): assert can_access_webpage(app_webroot, logged_as="alice") # Try to update the permission and check that permissions are still consistent - user_permission_update("legacy_app.main", remove="visitors", add="bob") + user_permission_update("legacy_app.main", remove=["visitors", "all_users"], add="bob") assert not can_access_webpage(app_webroot, logged_as=None) assert not can_access_webpage(app_webroot, logged_as="alice") From 1dda8afc9322bc1a08643ad6d016a9da2ebba103 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Fri, 29 Nov 2019 23:14:28 +0900 Subject: [PATCH 0531/3170] fix test_failed_multiple_app_upgrade --- src/yunohost/tests/test_apps.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/yunohost/tests/test_apps.py b/src/yunohost/tests/test_apps.py index 5a6db43c9..d9102edbe 100644 --- a/src/yunohost/tests/test_apps.py +++ b/src/yunohost/tests/test_apps.py @@ -338,7 +338,8 @@ def test_failed_multiple_app_upgrade(mocker, secondary_domain): install_legacy_app(secondary_domain, "/legacy") install_break_yo_system(secondary_domain, breakwhat="upgrade") - with raiseYunohostError(mocker, 'app_not_upgraded'): - app_upgrade(["break_yo_system", "legacy_app"], - file={"break_yo_system": "./tests/apps/break_yo_system_ynh", - "legacy": "./tests/apps/legacy_app_ynh"}) + with pytest.raises(YunohostError): + with message(mocker, "app_not_upgraded"): + app_upgrade(["break_yo_system", "legacy_app"], + file={"break_yo_system": "./tests/apps/break_yo_system_ynh", + "legacy": "./tests/apps/legacy_app_ynh"}) From 9697ca8e4b730b3c9ad1187d8fdce8b0f03d14ee Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 29 Nov 2019 15:51:43 +0100 Subject: [PATCH 0532/3170] Let's convert this in list in all cases (+ simplify later core) --- src/yunohost/permission.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 90f1bb9f0..6603d6bdc 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -140,11 +140,11 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, # If we end up with something like allowed groups is ["all_users", "volunteers"] # we shall warn the users that they should probably choose between one or the other, # because the current situation is probably not what they expect / is temporary ? - + if "all_users" in new_allowed_groups: if len(new_allowed_groups) > 1 and "visitors" not in new_allowed_groups or len(new_allowed_groups) > 2: logger.warning(m18n.n("permission_currently_allowed_for_all_users")) - + # If visitors are allowed, but not all users, it can break some applications, so we prohibit it. if "visitors" in new_allowed_groups and "all_users" not in new_allowed_groups: raise YunohostError('permission_allowed_for_visitors_but_not_for_all_users') @@ -261,15 +261,14 @@ def permission_create(operation_logger, permission, url=None, allowed=None, sync attr_dict['URL'] = url if allowed is not None: + if not isinstance(allowed, list): + allowed = [allowed] if "visitors" in allowed and "all_users" not in allowed: - if not isinstance(allowed, list): - allowed = [allowed] allowed.append("all_users") # Validate that the groups to add actually exist all_existing_groups = user_group_list()['groups'].keys() - allowed_ = [] if allowed is None else [allowed] if not isinstance(allowed, list) else allowed - for group in allowed_: + for group in allowed or []: if group not in all_existing_groups: raise YunohostError('group_unknown', group=group) From 7247c9f7aa7c3870e41e9954f93e52284322010c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 29 Nov 2019 16:06:26 +0100 Subject: [PATCH 0533/3170] Let's keep one thread per line for readability + easier to indentify the issue --- src/yunohost/tests/test_backuprestore.py | 6 ++++-- src/yunohost/tests/test_permission.py | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/yunohost/tests/test_backuprestore.py b/src/yunohost/tests/test_backuprestore.py index d6bc1bb31..bcba21bb6 100644 --- a/src/yunohost/tests/test_backuprestore.py +++ b/src/yunohost/tests/test_backuprestore.py @@ -510,7 +510,8 @@ def test_backup_and_restore_permission_app(mocker): assert res['permissions_app.admin']['url'] == "/admin" assert res['permissions_app.dev']['url'] == "/dev" - assert "visitors" in res['permissions_app.main']['allowed'] and "all_users" in res['permissions_app.main']['allowed'] + assert "visitors" in res['permissions_app.main']['allowed'] + assert "all_users" in res['permissions_app.main']['allowed'] assert res['permissions_app.admin']['allowed'] == ["alice"] assert res['permissions_app.dev']['allowed'] == [] @@ -524,7 +525,8 @@ def test_backup_and_restore_permission_app(mocker): assert res['permissions_app.admin']['url'] == "/admin" assert res['permissions_app.dev']['url'] == "/dev" - assert "visitors" in res['permissions_app.main']['allowed'] and "all_users" in res['permissions_app.main']['allowed'] + assert "visitors" in res['permissions_app.main']['allowed'] + assert "all_users" in res['permissions_app.main']['allowed'] assert res['permissions_app.admin']['allowed'] == ["alice"] assert res['permissions_app.dev']['allowed'] == [] diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index f11d215f1..a4bd570e5 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -460,7 +460,8 @@ def test_permission_app_propagation_on_ssowat(): args="domain=%s&path=%s&is_public=1&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True) res = user_permission_list(full=True)['permissions'] - assert "visitors" in res['permissions_app.main']['allowed'] and "all_users" in res['permissions_app.main']['allowed'] + assert "visitors" in res['permissions_app.main']['allowed'] + assert "all_users" in res['permissions_app.main']['allowed'] app_webroot = "https://%s/urlpermissionapp" % maindomain assert can_access_webpage(app_webroot, logged_as=None) @@ -491,7 +492,8 @@ def test_permission_legacy_app_propagation_on_ssowat(): # App is configured as public by default using the legacy unprotected_uri mechanics # It should automatically be migrated during the install res = user_permission_list(full=True)['permissions'] - assert "visitors" in res['legacy_app.main']['allowed'] and "all_users" in res['legacy_app.main']['allowed'] + assert "visitors" in res['legacy_app.main']['allowed'] + assert "all_users" in res['legacy_app.main']['allowed'] app_webroot = "https://%s/legacy" % maindomain From 54a5ecebd763c8999f038c756e6ff915e110b80c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 29 Nov 2019 16:41:50 +0100 Subject: [PATCH 0534/3170] Try to improve readability for these conditions --- src/yunohost/permission.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 6603d6bdc..75a00d6c5 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -138,11 +138,12 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, new_allowed_groups = [g for g in new_allowed_groups if g not in groups_to_remove] # If we end up with something like allowed groups is ["all_users", "volunteers"] - # we shall warn the users that they should probably choose between one or the other, - # because the current situation is probably not what they expect / is temporary ? - - if "all_users" in new_allowed_groups: - if len(new_allowed_groups) > 1 and "visitors" not in new_allowed_groups or len(new_allowed_groups) > 2: + # we shall warn the users that they should probably choose between one or + # the other, because the current situation is probably not what they expect + # / is temporary ? Note that it's fine to have ["all_users", "visitors"] + # though, but it's not fine to have ["all_users", "visitors", "volunteers"] + if "all_users" in new_allowed_groups and len(new_allowed_groups) >= 2: + if "visitors" not in new_allowed_groups or len(new_allowed_groups) >= 3: logger.warning(m18n.n("permission_currently_allowed_for_all_users")) # If visitors are allowed, but not all users, it can break some applications, so we prohibit it. From e0fa39ad01abd0b58c6db7c43c4081dcb934c2d6 Mon Sep 17 00:00:00 2001 From: Augustin Trancart Date: Sat, 30 Nov 2019 15:52:00 +0100 Subject: [PATCH 0535/3170] =?UTF-8?q?[fix]=20prevent=20firefox=20to=20mix?= =?UTF-8?q?=20CA=C2=A0and=20server=20certificate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #1479: yunohost was using the exact same Distinguished Name for the CA certificate and the main domain server certificate. When creating alternate domain name, firefox thought the CA for this second domain was the server certificate for the first domain. As the key mismatches, Firefox raised a bad key usage error, which is not bypassable. To fix this, we "simply" need to make sure the DN for the CA is distinct for any other DN. I did so by adding a Organization to it, and I decided to just remove the last part of the domain and use that as an organization name. It is certainly possible to do something else, as long as we end up having a distinct DN. So yolo.test gives a yolo organization for instance. More info here https://bugzilla.mozilla.org/show_bug.cgi?id=1590217 --- src/yunohost/tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index c05933dc0..ce219c4bc 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -321,7 +321,7 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False, 'touch %s/index.txt' % ssl_dir, 'cp %s/openssl.cnf %s/openssl.ca.cnf' % (ssl_dir, ssl_dir), 'sed -i s/yunohost.org/%s/g %s/openssl.ca.cnf ' % (domain, ssl_dir), - 'openssl req -x509 -new -config %s/openssl.ca.cnf -days 3650 -out %s/ca/cacert.pem -keyout %s/ca/cakey.pem -nodes -batch' % (ssl_dir, ssl_dir, ssl_dir), + 'openssl req -x509 -new -config %s/openssl.ca.cnf -days 3650 -out %s/ca/cacert.pem -keyout %s/ca/cakey.pem -nodes -batch -subj /CN=%s/O=%s' % (ssl_dir, ssl_dir, ssl_dir, domain, os.path.splitext(domain)[0]), 'cp %s/ca/cacert.pem /etc/ssl/certs/ca-yunohost_crt.pem' % ssl_dir, 'update-ca-certificates' ] From df3051f2de14bd6adb7f109ae1b62f114f889b66 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 30 Nov 2019 18:10:39 +0100 Subject: [PATCH 0536/3170] Handle subtag title internationalization also --- src/yunohost/app.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index a54a18c6f..276897eb8 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -96,6 +96,8 @@ def app_catalog(full=False, with_categories=False): for category in catalog["categories"]: category["title"] = _value_for_locale(category["title"]) category["description"] = _value_for_locale(category["description"]) + for subtags in category.get("subtags", []): + subtags["title"] = _value_for_locale(subtags["title"]) if not full: catalog["categories"] = [{"id": c["id"], From 6c24755e73d6e764b6fca463e6dc07f966240d90 Mon Sep 17 00:00:00 2001 From: Kayou Date: Sun, 1 Dec 2019 16:51:12 +0900 Subject: [PATCH 0537/3170] Create .gitlab-ci.yml --- .gitlab-ci.yml | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 .gitlab-ci.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 000000000..bb7754e56 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,86 @@ +stages: + - postinstall + - tests + +.tests: + variables: + SNAPSHOT_NAME: "after-postinstall" + before_script: + - apt-get install python-pip -y + - pip2 install pytest pytest-sugar pytest-mock requests-mock mock + - pushd src/yunohost/tests + - git clone https://github.com/YunoHost/test_apps ./apps + - cd apps + - git pull > /dev/null 2>&1 + - popd + - export PYTEST_ADDOPTS="--color=yes" + + +postinstall: + variables: + SNAPSHOT_NAME: "before-postinstall" + stage: postinstall + script: + - yunohost tools postinstall -d domain.tld -p the_password --ignore-dyndns + +root-tests: + extends: .tests + stage: tests + script: + - py.test tests + +test-apps: + extends: .tests + stage: tests + script: + - cd src/yunohost + - py.test tests/test_apps.py + +test-appscatalog: + extends: .tests + stage: tests + script: + - cd src/yunohost + - py.test tests/test_appscatalog.py + +test-appurl: + extends: .tests + stage: tests + script: + - cd src/yunohost + - py.test tests/test_appurl.py + +test-backuprestore: + extends: .tests + stage: tests + script: + - cd src/yunohost + - py.test tests/test_backuprestore.py + +test-changeurl: + extends: .tests + stage: tests + script: + - cd src/yunohost + - py.test tests/test_changeurl.py + +test-permission: + extends: .tests + stage: tests + script: + - cd src/yunohost + - py.test tests/test_permission.py + +test-settings: + extends: .tests + stage: tests + script: + - cd src/yunohost + - py.test tests/test_settings.py + +test-user-group: + extends: .tests + stage: tests + script: + - cd src/yunohost + - py.test tests/test_user-group.py From c97a839630e0011442a588b7f56eb0ca8f7a974b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 1 Dec 2019 18:41:47 +0100 Subject: [PATCH 0538/3170] Custom service description: let's be a bit smarter and use the one from the systemd service if it exists --- src/yunohost/service.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index f8553fbf7..748037df6 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -79,7 +79,16 @@ def service_add(name, description=None, log=None, log_type="file", test_status=N if description: services[name]['description'] = description else: - logger.warning("/!\\ Packager ! You added a custom service without specifying a description. Please add --description to explain what the service does in a similar fashion to existing services.") + # Try to get the description from systemd service + out = subprocess.check_output("systemctl show %s | grep '^Description='" % name, shell=True) + out = out.replace("Description=", "") + # 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("/!\\ 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 if need_lock: services[name]['need_lock'] = True From 99e70af08ce6a6f1bf94e8c27af70a6e209902fe Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 2 Dec 2019 18:09:43 +0100 Subject: [PATCH 0539/3170] Additonal cleaning of legacy stuff when using new permission system + avoid duplicated entries in (un)protected_urls --- src/yunohost/app.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 0e4a473b4..8897cb427 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1328,16 +1328,18 @@ def app_ssowatconf(): # FIXME : gotta handle regex-urls here... meh url = _sanitized_absolute_url(perm_info["url"]) if "visitors" in perm_info["allowed"]: - unprotected_urls.append(url) + if url not in unprotected_urls: + unprotected_urls.append(url) - # Legacy stuff : we remove now unprotected-urls that might have been declared as protected earlier... + # Legacy stuff : we remove now protected-urls that might have been declared as unprotected earlier... protected_urls = [u for u in protected_urls if u != url] else: - # TODO : small optimization to implement : we don't need to explictly add all the app roots - protected_urls.append(url) + if url not in protected_urls: + protected_urls.append(url) - # Legacy stuff : we remove now unprotected-urls that might have been declared as protected earlier... + # Legacy stuff : we remove now unprotected-urls / skipped-urls that might have been declared as protected earlier... unprotected_urls = [u for u in unprotected_urls if u != url] + skipped_urls = [u for u in skipped_urls if u != url] for domain in domains: skipped_urls.extend([domain + '/yunohost/admin', domain + '/yunohost/api']) From 442cec170d68f7d068816695b45663d1b612185d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 2 Dec 2019 19:04:07 +0100 Subject: [PATCH 0540/3170] Implicitly add all_users when adding visitors group --- locales/en.json | 3 ++- src/yunohost/permission.py | 12 +++++++++--- src/yunohost/tests/test_permission.py | 21 +++++++++++++++++++++ 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/locales/en.json b/locales/en.json index 71e17a7c8..08fc6b74e 100644 --- a/locales/en.json +++ b/locales/en.json @@ -464,14 +464,15 @@ "pattern_positive_number": "Must be a positive number", "pattern_username": "Must be lower-case alphanumeric and underscore characters only", "pattern_password_app": "Sorry, passwords can not contain the following characters: {forbidden_chars}", + "permission_all_users_implicitly_added": "The permission was also implicitly granted to 'all_users' because it is required to allow the special group 'visitors'", "permission_already_allowed": "Group '{group}' already has permission '{permission}' enabled", "permission_already_disallowed": "Group '{group}' already has permission '{permission}' disabled'", "permission_already_exist": "Permission '{permission}' already exists", "permission_already_up_to_date": "The permission was not updated because the addition/removal requests already match the current state.", + "permission_cannot_remove_all_users_while_visitors_allowed": "You can't remove this permission for 'all_users' while it is still allowed for 'visitors'", "permission_cannot_remove_main": "Removing a main permission is not allowed", "permission_created": "Permission '{permission:s}' created", "permission_creation_failed": "Could not create permission '{permission}': {error}", - "permission_allowed_for_visitors_but_not_for_all_users": "Visitors can't be granted if all users is not already granted.", "permission_currently_allowed_for_all_users": "This permission is currently granted to all users in addition to other groups. You probably want to either remove the 'all_users' permission or remove the other groups it is currently granted to.", "permission_deleted": "Permission '{permission:s}' deleted", "permission_deletion_failed": "Could not delete permission '{permission}': {error}", diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 75a00d6c5..5fe9f327f 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -146,9 +146,15 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, if "visitors" not in new_allowed_groups or len(new_allowed_groups) >= 3: logger.warning(m18n.n("permission_currently_allowed_for_all_users")) - # If visitors are allowed, but not all users, it can break some applications, so we prohibit it. - if "visitors" in new_allowed_groups and "all_users" not in new_allowed_groups: - raise YunohostError('permission_allowed_for_visitors_but_not_for_all_users') + # If visitors are to be added, we shall make sure that "all_users" are also allowed + # (e.g. if visitors are allowed to visit nextcloud, you still want to allow people to log in ...) + if add and "visitors" in groups_to_add and "all_users" not in new_allowed_groups: + new_allowed_groups.append("all_users") + logger.warning(m18n.n("permission_all_users_implicitly_added")) + # If all_users are to be added, yet visitors are still to allowed, then we + # refuse it (c.f. previous comment...) + if remove and "all_users" in groups_to_remove and "visitors" in new_allowed_groups: + raise YunohostError('permission_cannot_remove_all_users_while_visitors_allowed') # Don't update LDAP if we update exactly the same values if set(new_allowed_groups) == set(current_allowed_groups): diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index a4bd570e5..2e53f13a7 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -313,6 +313,27 @@ def test_permission_add_and_remove_group(mocker): assert res['wiki.main']['corresponding_users'] == ["alice"] +def test_permission_adding_visitors_implicitly_add_all_users(mocker): + + res = user_permission_list(full=True)['permissions'] + assert res['blog.main']['allowed'] == ["alice"] + + with message(mocker, "permission_updated", permission="blog.main"): + user_permission_update("blog.main", add="visitors") + + res = user_permission_list(full=True)['permissions'] + assert set(res['blog.main']['allowed']) == set(["alice", "visitors", "all_users"]) + + +def test_permission_cant_remove_all_users_if_visitors_allowed(mocker): + + with message(mocker, "permission_updated", permission="blog.main"): + user_permission_update("blog.main", add=["visitors", "all_users"]) + + with raiseYunohostError(mocker, 'permission_cannot_remove_all_users_while_visitors_allowed'): + user_permission_update("blog.main", remove="all_users") + + def test_permission_add_group_already_allowed(mocker): with message(mocker, "permission_already_allowed", permission="blog.main", group="alice"): user_permission_update("blog.main", add="alice") From 7b6d6d78723d88a7267618dc1648f160a247127b Mon Sep 17 00:00:00 2001 From: xaloc33 Date: Fri, 22 Nov 2019 23:10:49 +0000 Subject: [PATCH 0541/3170] Translated using Weblate (Catalan) Currently translated at 100.0% (605 of 605 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ca/ --- locales/ca.json | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/locales/ca.json b/locales/ca.json index 26fd8c889..a079c519f 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -406,7 +406,7 @@ "regenconf_file_updated": "El fitxer de configuració «{conf}» ha estat actualitzat", "regenconf_now_managed_by_yunohost": "El fitxer de configuració «{conf}» serà gestionat per YunoHost a partir d'ara (categoria {category}).", "regenconf_up_to_date": "La configuració ja està al dia per la categoria «{category}»", - "regenconf_updated": "La configuració per la categoria «{category}» ha estat actualitzada", + "regenconf_updated": "S'ha actualitzat la configuració per la categoria «{category}»", "regenconf_would_be_updated": "La configuració hagués estat actualitzada per la categoria «{category}»", "regenconf_dry_pending_applying": "Verificació de la configuració pendent que s'hauria d'haver aplicat per la categoria «{category}»…", "regenconf_failed": "No s'ha pogut regenerar la configuració per la/les categoria/es : {categories}", @@ -457,10 +457,10 @@ "service_description_ssh": "Permet la connexió remota al servidor via terminal (protocol SSH)", "service_description_yunohost-api": "Gestiona les interaccions entre la interfície web de YunoHost i el sistema", "service_description_yunohost-firewall": "Gestiona els ports de connexió oberts i tancats als serveis", - "service_disable_failed": "No s'han pogut deshabilitar el servei «{service:s}»\n\nRegistres recents: {logs:s}", - "service_disabled": "S'ha deshabilitat el servei «{service:s}»", - "service_enable_failed": "No s'ha pogut activar el servei «{service:s}»\n\nRegistres recents: {log:s}", - "service_enabled": "S'ha activat el servei «{service:s}»", + "service_disable_failed": "No s'han pogut fer que el servei «{service:s}» no comenci a l'arrancada.\n\nRegistres recents: {logs:s}", + "service_disabled": "El servei «{service:s}» ja no començarà al arrancar el sistema.", + "service_enable_failed": "No s'ha pogut fer que el servei «{service:s}» comenci automàticament a l'arrancada.\n\nRegistres recents: {log:s}", + "service_enabled": "El servei «{service:s}» començarà automàticament durant l'arrancada del sistema.", "service_no_log": "No hi ha cap registre pel servei «{service:s}»", "service_regen_conf_is_deprecated": "«yunohost service regen-conf» està desfasat! Utilitzeu «yunohost tools regen-conf» en el seu lloc.", "service_remove_failed": "No s'ha pogut eliminar el servei «{service:s}»", @@ -663,10 +663,10 @@ "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 via /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 de tipus {0}, nom {1} i valor {2}", - "diagnosis_dns_discrepancy": "Segons la configuració DNS recomanada, el valor pel registre DNS de tipus {0} i nom {1} hauria de ser {2}, en comptes de {3}.", + "diagnosis_dns_missing_record": "Segons la configuració DNS recomanada, hauríeu d'afegir un registre DNS de tipus {0}, nom {1} i valor {2}. Hi ha més informació a https://yunohost.org/dns_config.", + "diagnosis_dns_discrepancy": "El registre DNS de tipus {0} i nom {1} no concorda amb la configuració recomanada. Valor actual: {2}. Valor esperat: {3}. Més informació a https://yunohost.org/dns_config.", "diagnosis_services_good_status": "El servei {service} està {status} tal i com s'esperava!", - "diagnosis_services_bad_status": "El servei {service} està {status} :/", + "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_abs_GB} GB ({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_abs_GB} GB ({free_percent}%). Aneu amb compte.", "diagnosis_diskusage_ok": "El lloc d'emmagatzematge {mountpoint} (en l'aparell {device}) encara té {free_abs_GB} GB ({free_percent}%) lliures!", @@ -704,5 +704,17 @@ "diagnosis_mail_ougoing_port_25_blocked": "Sembla que el port de sortida 25 està bloquejat. Hauríeu d'intentar desbloquejar-lo al panell de configuració del proveïdor d'accés a internet (o allotjador). Mentrestant, el servidor no podrà enviar correus a altres servidors.", "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ó" + "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 {0}» 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 s'explica a https://yunohost.org/isp_box_config", + "diagnosis_http_bad_status_code": "No s'ha pogut connectar al servidor com esperat, ha retornat un codi d'estat erroni. 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_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_connection_error": "Error de connexió: no s'ha pogut connectar amb el domini demanat, segurament és inaccessible.", + "diagnosis_http_unknown_error": "Hi ha hagut un error intentant accedir al domini, 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 els problemes esperant a ser resolts per un correcte funcionament del servidor 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", + "diagnosis_services_running": "El servei {service} s'està executant!", + "diagnosis_services_conf_broken": "La configuració pel servei {service} està trencada!", + "diagnosis_ports_needed_by": "És necessari exposar aquest port pel servei {0}" } From 6e606cc64dbb0860f94b2bb31c7db2918b90bf6d Mon Sep 17 00:00:00 2001 From: Kay0u Date: Fri, 29 Nov 2019 20:39:18 +0900 Subject: [PATCH 0542/3170] Visitors permission needs All Users --- data/helpers.d/setting | 2 +- locales/en.json | 2 +- src/yunohost/app.py | 6 +++--- src/yunohost/permission.py | 18 +++++++++++++----- src/yunohost/tests/test_backuprestore.py | 4 ++-- src/yunohost/tests/test_permission.py | 8 ++++---- 6 files changed, 24 insertions(+), 16 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index 8046dfab4..9dbbe93fa 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -189,7 +189,7 @@ EOF # We need this because app temporarily set the app as unprotected to configure it with curl... if [[ "$3" =~ ^(unprotected|skipped)_ ]] && [[ "${4:-}" == "/" ]] then - ynh_permission_update --permission "main" --remove "all_users" --add "visitors" + ynh_permission_update --permission "main" --add "visitors" fi } diff --git a/locales/en.json b/locales/en.json index 6c7d0b42d..d93d2b5f4 100644 --- a/locales/en.json +++ b/locales/en.json @@ -422,7 +422,7 @@ "permission_cannot_remove_main": "Removing a main permission is not allowed", "permission_created": "Permission '{permission:s}' created", "permission_creation_failed": "Could not create permission '{permission}': {error}", - "permission_currently_allowed_for_visitors": "This permission is currently granted to visitors in addition to other groups. You probably want to either remove the 'visitors' permission or remove the other groups it is currently granted to.", + "permission_allowed_for_visitors_but_not_for_all_users": "Visitors can't be granted if all users is not already granted.", "permission_currently_allowed_for_all_users": "This permission is currently granted to all users in addition to other groups. You probably want to either remove the 'all_users' permission or remove the other groups it is currently granted to.", "permission_deleted": "Permission '{permission:s}' deleted", "permission_deletion_failed": "Could not delete permission '{permission}': {error}", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 2d32fc0cc..063d0f97d 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1169,12 +1169,12 @@ def _migrate_legacy_permissions(app): # If the current permission says app is protected, but there are legacy rules saying it should be public... if app_perm_currently_allowed == ["all_users"] and settings_say_it_should_be_public: # Make it public - user_permission_update(app + ".main", remove="all_users", add="visitors", sync_perm=False) + user_permission_update(app + ".main", add="visitors", sync_perm=False) # If the current permission says app is public, but there are no setting saying it should be public... if app_perm_currently_allowed == ["visitors"] and not settings_say_it_should_be_public: # Make is private - user_permission_update(app + ".main", remove="visitors", add="all_users", sync_perm=False) + user_permission_update(app + ".main", remove="visitors", sync_perm=False) @is_unit_operation() @@ -1423,7 +1423,7 @@ def app_setting(app, key, value=None, delete=False): # We need this because app temporarily set the app as unprotected to configure it with curl... if key.startswith("unprotected_") or key.startswith("skipped_") and value == "/": from permission import user_permission_update - user_permission_update(app + ".main", remove="all_users", add="visitors") + user_permission_update(app + ".main", add="visitors") def app_checkport(port): diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index ad06c0487..011e8265d 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -140,12 +140,14 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, # If we end up with something like allowed groups is ["all_users", "volunteers"] # we shall warn the users that they should probably choose between one or the other, # because the current situation is probably not what they expect / is temporary ? - - if len(new_allowed_groups) > 1: - if "all_users" in new_allowed_groups: + + if "all_users" in new_allowed_groups: + if len(new_allowed_groups) > 1 and "visitors" not in new_allowed_groups or len(new_allowed_groups) > 2: logger.warning(m18n.n("permission_currently_allowed_for_all_users")) - if "visitors" in new_allowed_groups: - logger.warning(m18n.n("permission_currently_allowed_for_visitors")) + + # If visitors are allowed, but not all users, it can break some applications, so we prohibit it. + if "visitors" in new_allowed_groups and "all_users" not in new_allowed_groups: + raise YunohostError('permission_allowed_for_visitors_but_not_for_all_users') # Don't update LDAP if we update exactly the same values if set(new_allowed_groups) == set(current_allowed_groups): @@ -258,6 +260,12 @@ def permission_create(operation_logger, permission, url=None, allowed=None, sync if url: attr_dict['URL'] = url + if allowed is not None: + if "visitors" in allowed and "all_users" not in allowed: + if not isinstance(allowed, list): + allowed = [allowed] + allowed.append("all_users") + # Validate that the groups to add actually exist all_existing_groups = user_group_list()['groups'].keys() allowed_ = [] if allowed is None else [allowed] if not isinstance(allowed, list) else allowed diff --git a/src/yunohost/tests/test_backuprestore.py b/src/yunohost/tests/test_backuprestore.py index d67ccfe37..3662226e1 100644 --- a/src/yunohost/tests/test_backuprestore.py +++ b/src/yunohost/tests/test_backuprestore.py @@ -510,7 +510,7 @@ def test_backup_and_restore_permission_app(): assert res['permissions_app.admin']['url'] == "/admin" assert res['permissions_app.dev']['url'] == "/dev" - assert res['permissions_app.main']['allowed'] == ["visitors"] + assert "visitors" in res['permissions_app.main']['allowed'] and "all_users" in res['permissions_app.main']['allowed'] assert res['permissions_app.admin']['allowed'] == ["alice"] assert res['permissions_app.dev']['allowed'] == [] @@ -524,7 +524,7 @@ def test_backup_and_restore_permission_app(): assert res['permissions_app.admin']['url'] == "/admin" assert res['permissions_app.dev']['url'] == "/dev" - assert res['permissions_app.main']['allowed'] == ["visitors"] + assert "visitors" in res['permissions_app.main']['allowed'] and "all_users" in res['permissions_app.main']['allowed'] assert res['permissions_app.admin']['allowed'] == ["alice"] assert res['permissions_app.dev']['allowed'] == [] diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index b3fa9fefb..f11d215f1 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -460,13 +460,13 @@ def test_permission_app_propagation_on_ssowat(): args="domain=%s&path=%s&is_public=1&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True) res = user_permission_list(full=True)['permissions'] - assert res['permissions_app.main']['allowed'] == ["visitors"] + assert "visitors" in res['permissions_app.main']['allowed'] and "all_users" in res['permissions_app.main']['allowed'] app_webroot = "https://%s/urlpermissionapp" % maindomain assert can_access_webpage(app_webroot, logged_as=None) assert can_access_webpage(app_webroot, logged_as="alice") - user_permission_update("permissions_app.main", remove="visitors", add="bob") + user_permission_update("permissions_app.main", remove=["visitors", "all_users"], add="bob") res = user_permission_list(full=True)['permissions'] assert not can_access_webpage(app_webroot, logged_as=None) @@ -491,7 +491,7 @@ def test_permission_legacy_app_propagation_on_ssowat(): # App is configured as public by default using the legacy unprotected_uri mechanics # It should automatically be migrated during the install res = user_permission_list(full=True)['permissions'] - assert res['legacy_app.main']['allowed'] == ["visitors"] + assert "visitors" in res['legacy_app.main']['allowed'] and "all_users" in res['legacy_app.main']['allowed'] app_webroot = "https://%s/legacy" % maindomain @@ -499,7 +499,7 @@ def test_permission_legacy_app_propagation_on_ssowat(): assert can_access_webpage(app_webroot, logged_as="alice") # Try to update the permission and check that permissions are still consistent - user_permission_update("legacy_app.main", remove="visitors", add="bob") + user_permission_update("legacy_app.main", remove=["visitors", "all_users"], add="bob") assert not can_access_webpage(app_webroot, logged_as=None) assert not can_access_webpage(app_webroot, logged_as="alice") From df3c7688981ff06cb1a39fb26548535e76864e25 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 29 Nov 2019 15:51:43 +0100 Subject: [PATCH 0543/3170] Let's convert this in list in all cases (+ simplify later core) --- src/yunohost/permission.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 011e8265d..049c62729 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -140,11 +140,11 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, # If we end up with something like allowed groups is ["all_users", "volunteers"] # we shall warn the users that they should probably choose between one or the other, # because the current situation is probably not what they expect / is temporary ? - + if "all_users" in new_allowed_groups: if len(new_allowed_groups) > 1 and "visitors" not in new_allowed_groups or len(new_allowed_groups) > 2: logger.warning(m18n.n("permission_currently_allowed_for_all_users")) - + # If visitors are allowed, but not all users, it can break some applications, so we prohibit it. if "visitors" in new_allowed_groups and "all_users" not in new_allowed_groups: raise YunohostError('permission_allowed_for_visitors_but_not_for_all_users') @@ -261,15 +261,14 @@ def permission_create(operation_logger, permission, url=None, allowed=None, sync attr_dict['URL'] = url if allowed is not None: + if not isinstance(allowed, list): + allowed = [allowed] if "visitors" in allowed and "all_users" not in allowed: - if not isinstance(allowed, list): - allowed = [allowed] allowed.append("all_users") # Validate that the groups to add actually exist all_existing_groups = user_group_list()['groups'].keys() - allowed_ = [] if allowed is None else [allowed] if not isinstance(allowed, list) else allowed - for group in allowed_: + for group in allowed or []: if group not in all_existing_groups: raise YunohostError('group_unknown', group=group) From af054b3b85384c27d83b4eb17f1ad76fe2113ad9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 29 Nov 2019 16:06:26 +0100 Subject: [PATCH 0544/3170] Let's keep one thread per line for readability + easier to indentify the issue --- src/yunohost/tests/test_backuprestore.py | 6 ++++-- src/yunohost/tests/test_permission.py | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/yunohost/tests/test_backuprestore.py b/src/yunohost/tests/test_backuprestore.py index 3662226e1..3265fa228 100644 --- a/src/yunohost/tests/test_backuprestore.py +++ b/src/yunohost/tests/test_backuprestore.py @@ -510,7 +510,8 @@ def test_backup_and_restore_permission_app(): assert res['permissions_app.admin']['url'] == "/admin" assert res['permissions_app.dev']['url'] == "/dev" - assert "visitors" in res['permissions_app.main']['allowed'] and "all_users" in res['permissions_app.main']['allowed'] + assert "visitors" in res['permissions_app.main']['allowed'] + assert "all_users" in res['permissions_app.main']['allowed'] assert res['permissions_app.admin']['allowed'] == ["alice"] assert res['permissions_app.dev']['allowed'] == [] @@ -524,7 +525,8 @@ def test_backup_and_restore_permission_app(): assert res['permissions_app.admin']['url'] == "/admin" assert res['permissions_app.dev']['url'] == "/dev" - assert "visitors" in res['permissions_app.main']['allowed'] and "all_users" in res['permissions_app.main']['allowed'] + assert "visitors" in res['permissions_app.main']['allowed'] + assert "all_users" in res['permissions_app.main']['allowed'] assert res['permissions_app.admin']['allowed'] == ["alice"] assert res['permissions_app.dev']['allowed'] == [] diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index f11d215f1..a4bd570e5 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -460,7 +460,8 @@ def test_permission_app_propagation_on_ssowat(): args="domain=%s&path=%s&is_public=1&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True) res = user_permission_list(full=True)['permissions'] - assert "visitors" in res['permissions_app.main']['allowed'] and "all_users" in res['permissions_app.main']['allowed'] + assert "visitors" in res['permissions_app.main']['allowed'] + assert "all_users" in res['permissions_app.main']['allowed'] app_webroot = "https://%s/urlpermissionapp" % maindomain assert can_access_webpage(app_webroot, logged_as=None) @@ -491,7 +492,8 @@ def test_permission_legacy_app_propagation_on_ssowat(): # App is configured as public by default using the legacy unprotected_uri mechanics # It should automatically be migrated during the install res = user_permission_list(full=True)['permissions'] - assert "visitors" in res['legacy_app.main']['allowed'] and "all_users" in res['legacy_app.main']['allowed'] + assert "visitors" in res['legacy_app.main']['allowed'] + assert "all_users" in res['legacy_app.main']['allowed'] app_webroot = "https://%s/legacy" % maindomain From a5866e67b96f3e189ec43042d94cff97d0c5df83 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 29 Nov 2019 16:41:50 +0100 Subject: [PATCH 0545/3170] Try to improve readability for these conditions --- src/yunohost/permission.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 049c62729..b4abf4ca7 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -138,11 +138,12 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, new_allowed_groups = [g for g in new_allowed_groups if g not in groups_to_remove] # If we end up with something like allowed groups is ["all_users", "volunteers"] - # we shall warn the users that they should probably choose between one or the other, - # because the current situation is probably not what they expect / is temporary ? - - if "all_users" in new_allowed_groups: - if len(new_allowed_groups) > 1 and "visitors" not in new_allowed_groups or len(new_allowed_groups) > 2: + # we shall warn the users that they should probably choose between one or + # the other, because the current situation is probably not what they expect + # / is temporary ? Note that it's fine to have ["all_users", "visitors"] + # though, but it's not fine to have ["all_users", "visitors", "volunteers"] + if "all_users" in new_allowed_groups and len(new_allowed_groups) >= 2: + if "visitors" not in new_allowed_groups or len(new_allowed_groups) >= 3: logger.warning(m18n.n("permission_currently_allowed_for_all_users")) # If visitors are allowed, but not all users, it can break some applications, so we prohibit it. From a044f3ad7ea87bbeb12fefccae1804d59ce55ac0 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 2 Dec 2019 19:04:07 +0100 Subject: [PATCH 0546/3170] Implicitly add all_users when adding visitors group --- locales/en.json | 3 ++- src/yunohost/permission.py | 12 +++++++++--- src/yunohost/tests/test_permission.py | 21 +++++++++++++++++++++ 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/locales/en.json b/locales/en.json index d93d2b5f4..17014cad0 100644 --- a/locales/en.json +++ b/locales/en.json @@ -415,14 +415,15 @@ "pattern_positive_number": "Must be a positive number", "pattern_username": "Must be lower-case alphanumeric and underscore characters only", "pattern_password_app": "Sorry, passwords can not contain the following characters: {forbidden_chars}", + "permission_all_users_implicitly_added": "The permission was also implicitly granted to 'all_users' because it is required to allow the special group 'visitors'", "permission_already_allowed": "Group '{group}' already has permission '{permission}' enabled", "permission_already_disallowed": "Group '{group}' already has permission '{permission}' disabled'", "permission_already_exist": "Permission '{permission}' already exists", "permission_already_up_to_date": "The permission was not updated because the addition/removal requests already match the current state.", + "permission_cannot_remove_all_users_while_visitors_allowed": "You can't remove this permission for 'all_users' while it is still allowed for 'visitors'", "permission_cannot_remove_main": "Removing a main permission is not allowed", "permission_created": "Permission '{permission:s}' created", "permission_creation_failed": "Could not create permission '{permission}': {error}", - "permission_allowed_for_visitors_but_not_for_all_users": "Visitors can't be granted if all users is not already granted.", "permission_currently_allowed_for_all_users": "This permission is currently granted to all users in addition to other groups. You probably want to either remove the 'all_users' permission or remove the other groups it is currently granted to.", "permission_deleted": "Permission '{permission:s}' deleted", "permission_deletion_failed": "Could not delete permission '{permission}': {error}", diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index b4abf4ca7..9cc7c7534 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -146,9 +146,15 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, if "visitors" not in new_allowed_groups or len(new_allowed_groups) >= 3: logger.warning(m18n.n("permission_currently_allowed_for_all_users")) - # If visitors are allowed, but not all users, it can break some applications, so we prohibit it. - if "visitors" in new_allowed_groups and "all_users" not in new_allowed_groups: - raise YunohostError('permission_allowed_for_visitors_but_not_for_all_users') + # If visitors are to be added, we shall make sure that "all_users" are also allowed + # (e.g. if visitors are allowed to visit nextcloud, you still want to allow people to log in ...) + if add and "visitors" in groups_to_add and "all_users" not in new_allowed_groups: + new_allowed_groups.append("all_users") + logger.warning(m18n.n("permission_all_users_implicitly_added")) + # If all_users are to be added, yet visitors are still to allowed, then we + # refuse it (c.f. previous comment...) + if remove and "all_users" in groups_to_remove and "visitors" in new_allowed_groups: + raise YunohostError('permission_cannot_remove_all_users_while_visitors_allowed') # Don't update LDAP if we update exactly the same values if set(new_allowed_groups) == set(current_allowed_groups): diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index a4bd570e5..2e53f13a7 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -313,6 +313,27 @@ def test_permission_add_and_remove_group(mocker): assert res['wiki.main']['corresponding_users'] == ["alice"] +def test_permission_adding_visitors_implicitly_add_all_users(mocker): + + res = user_permission_list(full=True)['permissions'] + assert res['blog.main']['allowed'] == ["alice"] + + with message(mocker, "permission_updated", permission="blog.main"): + user_permission_update("blog.main", add="visitors") + + res = user_permission_list(full=True)['permissions'] + assert set(res['blog.main']['allowed']) == set(["alice", "visitors", "all_users"]) + + +def test_permission_cant_remove_all_users_if_visitors_allowed(mocker): + + with message(mocker, "permission_updated", permission="blog.main"): + user_permission_update("blog.main", add=["visitors", "all_users"]) + + with raiseYunohostError(mocker, 'permission_cannot_remove_all_users_while_visitors_allowed'): + user_permission_update("blog.main", remove="all_users") + + def test_permission_add_group_already_allowed(mocker): with message(mocker, "permission_already_allowed", permission="blog.main", group="alice"): user_permission_update("blog.main", add="alice") From e9c01c2f893587cd7f65f020f6f5ca693ccce3e4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 2 Dec 2019 18:09:43 +0100 Subject: [PATCH 0547/3170] Additonal cleaning of legacy stuff when using new permission system + avoid duplicated entries in (un)protected_urls --- src/yunohost/app.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 063d0f97d..8ce5ed783 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1662,16 +1662,18 @@ def app_ssowatconf(): # FIXME : gotta handle regex-urls here... meh url = _sanitized_absolute_url(perm_info["url"]) if "visitors" in perm_info["allowed"]: - unprotected_urls.append(url) + if url not in unprotected_urls: + unprotected_urls.append(url) - # Legacy stuff : we remove now unprotected-urls that might have been declared as protected earlier... + # Legacy stuff : we remove now protected-urls that might have been declared as unprotected earlier... protected_urls = [u for u in protected_urls if u != url] else: - # TODO : small optimization to implement : we don't need to explictly add all the app roots - protected_urls.append(url) + if url not in protected_urls: + protected_urls.append(url) - # Legacy stuff : we remove now unprotected-urls that might have been declared as protected earlier... + # Legacy stuff : we remove now unprotected-urls / skipped-urls that might have been declared as protected earlier... unprotected_urls = [u for u in unprotected_urls if u != url] + skipped_urls = [u for u in skipped_urls if u != url] for domain in domains: skipped_urls.extend([domain + '/yunohost/admin', domain + '/yunohost/api']) From d966407ff03d492c096d273bd0c1bb087217f17c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 2 Dec 2019 20:41:42 +0100 Subject: [PATCH 0548/3170] Let's not remove all_users here since it shall be added if visitors are allowed --- src/yunohost/data_migrations/0011_setup_group_permission.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/data_migrations/0011_setup_group_permission.py b/src/yunohost/data_migrations/0011_setup_group_permission.py index c80686344..b07e5d21b 100644 --- a/src/yunohost/data_migrations/0011_setup_group_permission.py +++ b/src/yunohost/data_migrations/0011_setup_group_permission.py @@ -119,7 +119,7 @@ class MyMigration(Migration): # Migrate classic public app still using the legacy unprotected_uris if app_setting(app, "unprotected_uris") == "/" or app_setting(app, "skipped_uris") == "/": - user_permission_update(app+".main", remove="all_users", add="visitors", sync_perm=False) + user_permission_update(app+".main", add="visitors", sync_perm=False) permission_sync_to_user() From 8f4a1757f49fdf1402452485af8e33c6392b29c1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 2 Dec 2019 20:45:03 +0100 Subject: [PATCH 0549/3170] Update changelog for 3.7.0.4 --- debian/changelog | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/debian/changelog b/debian/changelog index f8a83d0b3..25a7469de 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,11 @@ +yunohost (3.7.0.4) testing; urgency=low + + - [fix] Also add all_users when allowing visitors (#855) + - [fix] Fix handling of skipped_uris (c.f. also SSOwat#149) + - [i18n] Improve translations for Catalan + + -- Alexandre Aubin Mon, 2 Dev 2019 20:44:00 +0000 + yunohost (3.7.0.3) testing; urgency=low - [mod] Some refactoring for permissions create/update/reset (#837) From 5c2748ba85aa3a80244355c2e552e06b4dc71d79 Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Mon, 2 Dec 2019 22:32:59 +0100 Subject: [PATCH 0550/3170] [fix] This DNS resolver in ipv6 is unreachable --- data/templates/dnsmasq/plain/resolv.dnsmasq.conf | 1 - 1 file changed, 1 deletion(-) diff --git a/data/templates/dnsmasq/plain/resolv.dnsmasq.conf b/data/templates/dnsmasq/plain/resolv.dnsmasq.conf index 6b3bb95d3..ce8515054 100644 --- a/data/templates/dnsmasq/plain/resolv.dnsmasq.conf +++ b/data/templates/dnsmasq/plain/resolv.dnsmasq.conf @@ -32,7 +32,6 @@ nameserver 85.214.20.141 nameserver 195.160.173.53 # (DE) AS250 nameserver 194.150.168.168 -nameserver 2001:4ce8::53 # (DE) Ideal-Hosting nameserver 84.200.69.80 nameserver 2001:1608:10:25::1c04:b12f From 5d4f62b2220759d1428104f65b3baf53b31a858d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Sun, 22 Dec 2019 14:44:10 +0100 Subject: [PATCH 0551/3170] Update LDAP schema for permission protection --- data/templates/slapd/yunohost.schema | 5 ++++- src/yunohost/user.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/data/templates/slapd/yunohost.schema b/data/templates/slapd/yunohost.schema index 7da60a20c..e7398e621 100644 --- a/data/templates/slapd/yunohost.schema +++ b/data/templates/slapd/yunohost.schema @@ -15,6 +15,9 @@ attributetype ( 1.3.6.1.4.1.17953.9.1.3 NAME 'inheritPermission' attributetype ( 1.3.6.1.4.1.17953.9.1.4 NAME 'URL' DESC 'Yunohost application URL' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} ) +attributetype ( 1.3.6.1.4.1.17953.9.1.5 NAME 'isProtected' + DESC 'Yunohost application permission protection' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE ) # OBJECTCLASS # For Applications objectclass ( 1.3.6.1.4.1.17953.9.2.1 NAME 'groupOfNamesYnh' @@ -25,7 +28,7 @@ objectclass ( 1.3.6.1.4.1.17953.9.2.2 NAME 'permissionYnh' DESC 'a Yunohost application' SUP top AUXILIARY MUST cn - MAY ( groupPermission $ inheritPermission $ URL ) ) + MAY ( groupPermission $ inheritPermission $ URL $ isProtected ) ) # For User objectclass ( 1.3.6.1.4.1.17953.9.2.3 NAME 'userPermissionYnh' DESC 'a Yunohost application' diff --git a/src/yunohost/user.py b/src/yunohost/user.py index fdcac658d..fdd990658 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -718,7 +718,7 @@ def user_group_update(operation_logger, groupname, add=None, remove=None, force= new_group_dns = ["uid=" + user + ",ou=users,dc=yunohost,dc=org" for user in new_group] - if set(new_group) != set(current_group): + if set(new_group) != set(current_group) or True: operation_logger.start() ldap = _get_ldap_interface() try: From fe8a0cbcbd39fbf96d42b37f443f34b44a67e16f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Sun, 22 Dec 2019 15:03:29 +0100 Subject: [PATCH 0552/3170] Update helper for permission protection support --- data/helpers.d/setting | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index c862d4ef6..ef909b7d7 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -241,10 +241,12 @@ ynh_webpath_register () { # # example: ynh_permission_create --permission admin --url /admin --allowed alice bob # -# usage: ynh_permission_create --permission "permission" [--url "url"] [--allowed group1 group2] +# usage: ynh_permission_create --permission "permission" [--url "url"] [--allowed group1 group2] [--is_protected "true"|"false"] # | arg: permission - the name for the permission (by default a permission named "main" already exist) # | arg: url - (optional) URL for which access will be allowed/forbidden # | arg: allowed - (optional) A list of group/user to allow for the permission +# | arg: is_protected - (optional) Define if this permission is protected. If it is protected the administrator +# | won't be able to add or remove the visitors group of this permission. By default it's 'false' # # If provided, 'url' is assumed to be relative to the app domain/path if they # start with '/'. For example: @@ -259,10 +261,11 @@ ynh_webpath_register () { # # Requires YunoHost version 3.7.0 or higher. ynh_permission_create() { - declare -Ar args_array=( [p]=permission= [u]=url= [a]=allowed= ) + declare -Ar args_array=( [p]=permission= [u]=url= [a]=allowed= [p]=is_protected= ) local permission local url local allowed + local is_protected ynh_handle_getopts_args "$@" if [[ -n ${url:-} ]]; then @@ -270,12 +273,18 @@ ynh_permission_create() { else url="None" fi - if [[ -n ${allowed:-} ]]; then allowed=",allowed=['${allowed//';'/"','"}']" fi + if [ -n ${is_protected} ]; then + if [ $is_protected == "true" ]; then + is_protected=",is_protected=$is_protected=True" + else + is_protected=",is_protected=$is_protected=False" + fi + fi - yunohost tools shell -c "from yunohost.permission import permission_create; permission_create('$app.$permission', url=$url ${allowed:-} , sync_perm=False)" + yunohost tools shell -c "from yunohost.permission import permission_create; permission_create('$app.$permission', url=$url ${allowed:-} ${is_protected:-} , sync_perm=False)" } # Remove a permission for the app (note that when the app is removed all permission is automatically removed) @@ -333,26 +342,36 @@ ynh_permission_url() { # Update a permission for the app # -# usage: ynh_permission_update --permission "permission" --add "group" ["group" ...] --remove "group" ["group" ...] +# usage: ynh_permission_update --permission "permission" --add "group" ["group" ...] --remove "group" ["group" ...] [--is_protected "true"|"false"] # | arg: permission - the name for the permission (by default a permission named "main" already exist) # | arg: add - the list of group or users to enable add to the permission # | arg: remove - the list of group or users to remove from the permission +# | arg: is_protected - (optional) Define if this permission is protected. If it is protected the administrator +# | won't be able to add or remove the visitors group of this permission. By default it's 'false' # # example: ynh_permission_update --permission admin --add samdoe --remove all_users # Requires YunoHost version 3.7.0 or higher. ynh_permission_update() { - declare -Ar args_array=( [p]=permission= [a]=add= [r]=remove= ) + declare -Ar args_array=( [p]=permission= [a]=add= [r]=remove= [p]=is_protected= ) local permission local add local remove + local is_protected ynh_handle_getopts_args "$@" if [[ -n ${add:-} ]]; then - add="--add ${add//';'/" "}" + add=",add=['${add//';'/"','"}']" fi if [[ -n ${remove:-} ]]; then - remove="--remove ${remove//';'/" "} " + remove=",remove=['${remove//';'/"','"}']" + fi + if [ -n ${is_protected} ]; then + if [ $is_protected == "true" ]; then + is_protected=",is_protected=$is_protected=True" + else + is_protected=",is_protected=$is_protected=False" + fi fi - yunohost user permission update "$app.$permission" ${add:-} ${remove:-} + yunohost tools shell -c "from yunohost.permission import user_permission_update; user_permission_update('$app.$permission', ${add:-} ${remove} ${is_protected:-} , sync_perm=False)" } From e5eed1fa1783f2cd0a63f6f986057f454cfbc01f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Sun, 22 Dec 2019 15:44:48 +0100 Subject: [PATCH 0553/3170] Make mandatory the protected attribute in LDAP permission --- data/templates/slapd/yunohost.schema | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/templates/slapd/yunohost.schema b/data/templates/slapd/yunohost.schema index e7398e621..472518f35 100644 --- a/data/templates/slapd/yunohost.schema +++ b/data/templates/slapd/yunohost.schema @@ -27,8 +27,8 @@ objectclass ( 1.3.6.1.4.1.17953.9.2.1 NAME 'groupOfNamesYnh' objectclass ( 1.3.6.1.4.1.17953.9.2.2 NAME 'permissionYnh' DESC 'a Yunohost application' SUP top AUXILIARY - MUST cn - MAY ( groupPermission $ inheritPermission $ URL $ isProtected ) ) + MUST ( cn $ isProtected ) + MAY ( groupPermission $ inheritPermission $ URL ) ) # For User objectclass ( 1.3.6.1.4.1.17953.9.2.3 NAME 'userPermissionYnh' DESC 'a Yunohost application' From 0e3293a47e2d862a381636d6729ddd16bc094dec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Sun, 22 Dec 2019 15:45:20 +0100 Subject: [PATCH 0554/3170] Add possibility to protect the modification of a permission --- locales/en.json | 2 +- src/yunohost/permission.py | 34 +++++++++++++++++++++------------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/locales/en.json b/locales/en.json index 08fc6b74e..ce437ba89 100644 --- a/locales/en.json +++ b/locales/en.json @@ -480,7 +480,7 @@ "permission_update_failed": "Could not update permission '{permission}' : {error}", "permission_updated": "Permission '{permission:s}' updated", "permission_update_nothing_to_do": "No permissions to update", - "permission_require_account": "Permission {permission} only makes sense for users having an account, and therefore cannot be enabled for visitors.", + "permission_protected": "Permission {permission} protected. You can't modify the visitors group to access to this permission.", "port_already_closed": "Port {port:d} is already closed for {ip_version:s} connections", "port_already_opened": "Port {port:d} is already opened for {ip_version:s} connections", "regenconf_file_backed_up": "Configuration file '{conf}' backed up to '{backup}'", diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 5fe9f327f..2fc963d57 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -56,7 +56,7 @@ def user_permission_list(short=False, full=False, ignore_system_perms=False): ldap = _get_ldap_interface() permissions_infos = ldap.search('ou=permission,dc=yunohost,dc=org', '(objectclass=permissionYnh)', - ["cn", 'groupPermission', 'inheritPermission', 'URL']) + ["cn", 'groupPermission', 'inheritPermission', 'URL', 'isProtected']) # Parse / organize information to be outputed @@ -74,15 +74,15 @@ def user_permission_list(short=False, full=False, ignore_system_perms=False): if full: permissions[name]["corresponding_users"] = [_ldap_path_extract(p, "uid") for p in infos.get('inheritPermission', [])] permissions[name]["url"] = infos.get("URL", [None])[0] + permissions[name]["protected"] = False if infos.get("isProtected", [False])[0] == "FALSE" else True if short: permissions = permissions.keys() return {'permissions': permissions} - @is_unit_operation() -def user_permission_update(operation_logger, permission, add=None, remove=None, sync_perm=True): +def user_permission_update(operation_logger, permission, add=None, remove=None, is_protected=None, sync_perm=True): """ Allow or Disallow a user or group to a permission for a specific application @@ -90,6 +90,7 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, permission -- Name of the permission (e.g. mail or or wordpress or wordpress.editors) add -- List of groups or usernames to add to this permission remove -- List of groups or usernames to remove from to this permission + is_protected -- (optional) Define if the permission can be added/removed to the visitor group """ from yunohost.user import user_group_list @@ -97,13 +98,15 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, if "." not in permission: permission = permission + ".main" - # Refuse to add "visitors" to mail, xmpp ... they require an account to make sense. - if add and "visitors" in add and permission.split(".")[0] in SYSTEM_PERMS: - raise YunohostError('permission_require_account', permission=permission) + existing_permission = user_permission_list(full=True)["permissions"].get(permission, None) + + # Refuse to add "visitors" to protected permission + if (add and "visitors" in add and existing_permission["protected"]) or \ + (remove and "visitors" in remove and existing_permission["protected"]): + raise YunohostError('permission_protected', permission=permission) # Fetch currently allowed groups for this permission - existing_permission = user_permission_list(full=True)["permissions"].get(permission, None) if existing_permission is None: raise YunohostError('permission_not_found', permission=permission) @@ -165,7 +168,7 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, operation_logger.start() - new_permission = _update_ldap_group_permission(permission=permission, allowed=new_allowed_groups, sync_perm=sync_perm) + new_permission = _update_ldap_group_permission(permission=permission, allowed=new_allowed_groups, is_protected=is_protected, sync_perm=sync_perm) logger.debug(m18n.n('permission_updated', permission=permission)) @@ -216,7 +219,7 @@ def user_permission_reset(operation_logger, permission, sync_perm=True): @is_unit_operation() -def permission_create(operation_logger, permission, url=None, allowed=None, sync_perm=True): +def permission_create(operation_logger, permission, url=None, allowed=None, is_protected=False, sync_perm=True): """ Create a new permission for a specific application @@ -224,6 +227,7 @@ def permission_create(operation_logger, permission, url=None, allowed=None, sync permission -- Name of the permission (e.g. mail or nextcloud or wordpress.editors) url -- (optional) URL for which access will be allowed/forbidden allowed -- (optional) A list of group/user to allow for the permission + is_protected -- (optional) Define if the permission can be added/removed to the visitor group If provided, 'url' is assumed to be relative to the app domain/path if they start with '/'. For example: @@ -261,7 +265,7 @@ def permission_create(operation_logger, permission, url=None, allowed=None, sync attr_dict = { 'objectClass': ['top', 'permissionYnh', 'posixGroup'], 'cn': str(permission), - 'gidNumber': gid, + 'gidNumber': gid } if url: @@ -287,7 +291,7 @@ def permission_create(operation_logger, permission, url=None, allowed=None, sync except Exception as e: raise YunohostError('permission_creation_failed', permission=permission, error=e) - new_permission = _update_ldap_group_permission(permission=permission, allowed=allowed, sync_perm=sync_perm) + new_permission = _update_ldap_group_permission(permission=permission, allowed=allowed, is_protected=is_protected, sync_perm=sync_perm) logger.debug(m18n.n('permission_created', permission=permission)) return new_permission @@ -425,7 +429,7 @@ def permission_sync_to_user(): os.system('nscd --invalidate=group') -def _update_ldap_group_permission(permission, allowed, sync_perm=True): +def _update_ldap_group_permission(permission, allowed, is_protected=None, sync_perm=True): """ Internal function that will rewrite user permission @@ -453,9 +457,13 @@ def _update_ldap_group_permission(permission, allowed, sync_perm=True): allowed = [allowed] if not isinstance(allowed, list) else allowed + if is_protected is None: + is_protected = existing_permission["protected"] + try: ldap.update('cn=%s,ou=permission' % permission, - {'groupPermission': ['cn=' + g + ',ou=groups,dc=yunohost,dc=org' for g in allowed]}) + {'groupPermission': ['cn=' + g + ',ou=groups,dc=yunohost,dc=org' for g in allowed], + 'isProtected': "TRUE" if is_protected else "FALSE"}) except Exception as e: raise YunohostError('permission_update_failed', permission=permission, error=e) From 3ffa82661222ec628db0b34bf3ac3dcf85db1b38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Mon, 23 Dec 2019 11:03:27 +0100 Subject: [PATCH 0555/3170] Remove dirty code for test --- src/yunohost/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index fdd990658..fdcac658d 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -718,7 +718,7 @@ def user_group_update(operation_logger, groupname, add=None, remove=None, force= new_group_dns = ["uid=" + user + ",ou=users,dc=yunohost,dc=org" for user in new_group] - if set(new_group) != set(current_group) or True: + if set(new_group) != set(current_group): operation_logger.start() ldap = _get_ldap_interface() try: From 0c6392a5f725006bbbf42ba8d01a3a1bee094494 Mon Sep 17 00:00:00 2001 From: Josue-T Date: Mon, 23 Dec 2019 11:03:31 +0100 Subject: [PATCH 0556/3170] Fix helper Co-Authored-By: Kayou --- data/helpers.d/setting | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index ef909b7d7..b6768e1c3 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -278,7 +278,7 @@ ynh_permission_create() { fi if [ -n ${is_protected} ]; then if [ $is_protected == "true" ]; then - is_protected=",is_protected=$is_protected=True" + is_protected=",is_protected=True" else is_protected=",is_protected=$is_protected=False" fi From cad6f7101607babaa46a28d9cb95e084f2080863 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Mon, 23 Dec 2019 11:05:19 +0100 Subject: [PATCH 0557/3170] Fix helper --- data/helpers.d/setting | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index b6768e1c3..3fddf6e27 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -280,7 +280,7 @@ ynh_permission_create() { if [ $is_protected == "true" ]; then is_protected=",is_protected=True" else - is_protected=",is_protected=$is_protected=False" + is_protected=",is_protected=False" fi fi @@ -367,9 +367,9 @@ ynh_permission_update() { fi if [ -n ${is_protected} ]; then if [ $is_protected == "true" ]; then - is_protected=",is_protected=$is_protected=True" + is_protected=",is_protected=True" else - is_protected=",is_protected=$is_protected=False" + is_protected=",is_protected=False" fi fi From b38d1a495e194792e65540eae05b1eb25820adb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Mon, 23 Dec 2019 11:21:28 +0100 Subject: [PATCH 0558/3170] Add force argument in permission update and change default value in permission creation --- data/helpers.d/setting | 7 ++++--- locales/en.json | 1 + src/yunohost/app.py | 2 +- src/yunohost/permission.py | 14 ++++++++++---- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index 3fddf6e27..f35496a1d 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -246,7 +246,8 @@ ynh_webpath_register () { # | arg: url - (optional) URL for which access will be allowed/forbidden # | arg: allowed - (optional) A list of group/user to allow for the permission # | arg: is_protected - (optional) Define if this permission is protected. If it is protected the administrator -# | won't be able to add or remove the visitors group of this permission. By default it's 'false' +# | won't be able to add or remove the visitors group of this permission. +# | By default it's 'true' (for the permission different than 'main'). # # If provided, 'url' is assumed to be relative to the app domain/path if they # start with '/'. For example: @@ -347,7 +348,7 @@ ynh_permission_url() { # | arg: add - the list of group or users to enable add to the permission # | arg: remove - the list of group or users to remove from the permission # | arg: is_protected - (optional) Define if this permission is protected. If it is protected the administrator -# | won't be able to add or remove the visitors group of this permission. By default it's 'false' +# | won't be able to add or remove the visitors group of this permission. # # example: ynh_permission_update --permission admin --add samdoe --remove all_users # Requires YunoHost version 3.7.0 or higher. @@ -373,5 +374,5 @@ ynh_permission_update() { fi fi - yunohost tools shell -c "from yunohost.permission import user_permission_update; user_permission_update('$app.$permission', ${add:-} ${remove} ${is_protected:-} , sync_perm=False)" + yunohost tools shell -c "from yunohost.permission import user_permission_update; user_permission_update('$app.$permission', ${add:-} ${remove} ${is_protected:-} , force=True, sync_perm=False)" } diff --git a/locales/en.json b/locales/en.json index ce437ba89..015655e5b 100644 --- a/locales/en.json +++ b/locales/en.json @@ -481,6 +481,7 @@ "permission_updated": "Permission '{permission:s}' updated", "permission_update_nothing_to_do": "No permissions to update", "permission_protected": "Permission {permission} protected. You can't modify the visitors group to access to this permission.", + "permission_require_account": "Permission {permission} only makes sense for users having an account, and therefore cannot be enabled for visitors.", "port_already_closed": "Port {port:d} is already closed for {ip_version:s} connections", "port_already_opened": "Port {port:d} is already opened for {ip_version:s} connections", "regenconf_file_backed_up": "Configuration file '{conf}' backed up to '{backup}'", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 30d3ab31b..37d6fc9db 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -750,7 +750,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu # Initialize the main permission for the app # After the install, if apps don't have a domain and path defined, the default url '/' is removed from the permission - permission_create(app_instance_name+".main", url="/", allowed=["all_users"]) + permission_create(app_instance_name+".main", url="/", allowed=["all_users"], is_protected=False) # Execute the app install script install_failed = True diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 2fc963d57..b5c59907e 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -82,7 +82,7 @@ def user_permission_list(short=False, full=False, ignore_system_perms=False): return {'permissions': permissions} @is_unit_operation() -def user_permission_update(operation_logger, permission, add=None, remove=None, is_protected=None, sync_perm=True): +def user_permission_update(operation_logger, permission, add=None, remove=None, is_protected=None, force=False, sync_perm=True): """ Allow or Disallow a user or group to a permission for a specific application @@ -91,6 +91,7 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, add -- List of groups or usernames to add to this permission remove -- List of groups or usernames to remove from to this permission is_protected -- (optional) Define if the permission can be added/removed to the visitor group + force -- (optional) Give the possibility to add/remove access from the visitor group to a protected permission """ from yunohost.user import user_group_list @@ -100,9 +101,14 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, existing_permission = user_permission_list(full=True)["permissions"].get(permission, None) + # Refuse to add "visitors" to mail, xmpp ... they require an account to make sense. + existing_permission = user_permission_list(full=True)["permissions"].get(permission, None) + if add and "visitors" in add and permission.split(".")[0] in SYSTEM_PERMS: + raise YunohostError('permission_require_account', permission=permission) + # Refuse to add "visitors" to protected permission - if (add and "visitors" in add and existing_permission["protected"]) or \ - (remove and "visitors" in remove and existing_permission["protected"]): + if ((add and "visitors" in add and existing_permission["protected"]) or \ + (remove and "visitors" in remove and existing_permission["protected"])) and not force: raise YunohostError('permission_protected', permission=permission) # Fetch currently allowed groups for this permission @@ -219,7 +225,7 @@ def user_permission_reset(operation_logger, permission, sync_perm=True): @is_unit_operation() -def permission_create(operation_logger, permission, url=None, allowed=None, is_protected=False, sync_perm=True): +def permission_create(operation_logger, permission, url=None, allowed=None, is_protected=True, sync_perm=True): """ Create a new permission for a specific application From 9aecacd99525f4640faefd8afdf4c55e339984a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Mon, 23 Dec 2019 11:25:20 +0100 Subject: [PATCH 0559/3170] Rename variables --- data/helpers.d/setting | 40 +++++++++++++++++++------------------- src/yunohost/app.py | 2 +- src/yunohost/permission.py | 20 +++++++++---------- 3 files changed, 31 insertions(+), 31 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index f35496a1d..bb71ad1a3 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -241,13 +241,13 @@ ynh_webpath_register () { # # example: ynh_permission_create --permission admin --url /admin --allowed alice bob # -# usage: ynh_permission_create --permission "permission" [--url "url"] [--allowed group1 group2] [--is_protected "true"|"false"] +# usage: ynh_permission_create --permission "permission" [--url "url"] [--allowed group1 group2] [--protected "true"|"false"] # | arg: permission - the name for the permission (by default a permission named "main" already exist) # | arg: url - (optional) URL for which access will be allowed/forbidden # | arg: allowed - (optional) A list of group/user to allow for the permission -# | arg: is_protected - (optional) Define if this permission is protected. If it is protected the administrator -# | won't be able to add or remove the visitors group of this permission. -# | By default it's 'true' (for the permission different than 'main'). +# | arg: protected - (optional) Define if this permission is protected. If it is protected the administrator +# | won't be able to add or remove the visitors group of this permission. +# | By default it's 'true' (for the permission different than 'main'). # # If provided, 'url' is assumed to be relative to the app domain/path if they # start with '/'. For example: @@ -262,11 +262,11 @@ ynh_webpath_register () { # # Requires YunoHost version 3.7.0 or higher. ynh_permission_create() { - declare -Ar args_array=( [p]=permission= [u]=url= [a]=allowed= [p]=is_protected= ) + declare -Ar args_array=( [p]=permission= [u]=url= [a]=allowed= [p]=protected= ) local permission local url local allowed - local is_protected + local protected ynh_handle_getopts_args "$@" if [[ -n ${url:-} ]]; then @@ -277,15 +277,15 @@ ynh_permission_create() { if [[ -n ${allowed:-} ]]; then allowed=",allowed=['${allowed//';'/"','"}']" fi - if [ -n ${is_protected} ]; then - if [ $is_protected == "true" ]; then - is_protected=",is_protected=True" + if [ -n ${protected} ]; then + if [ $protected == "true" ]; then + protected=",protected=True" else - is_protected=",is_protected=False" + protected=",protected=False" fi fi - yunohost tools shell -c "from yunohost.permission import permission_create; permission_create('$app.$permission', url=$url ${allowed:-} ${is_protected:-} , sync_perm=False)" + yunohost tools shell -c "from yunohost.permission import permission_create; permission_create('$app.$permission', url=$url ${allowed:-} ${protected:-} , sync_perm=False)" } # Remove a permission for the app (note that when the app is removed all permission is automatically removed) @@ -343,21 +343,21 @@ ynh_permission_url() { # Update a permission for the app # -# usage: ynh_permission_update --permission "permission" --add "group" ["group" ...] --remove "group" ["group" ...] [--is_protected "true"|"false"] +# usage: ynh_permission_update --permission "permission" --add "group" ["group" ...] --remove "group" ["group" ...] [--protected "true"|"false"] # | arg: permission - the name for the permission (by default a permission named "main" already exist) # | arg: add - the list of group or users to enable add to the permission # | arg: remove - the list of group or users to remove from the permission -# | arg: is_protected - (optional) Define if this permission is protected. If it is protected the administrator +# | arg: protected - (optional) Define if this permission is protected. If it is protected the administrator # | won't be able to add or remove the visitors group of this permission. # # example: ynh_permission_update --permission admin --add samdoe --remove all_users # Requires YunoHost version 3.7.0 or higher. ynh_permission_update() { - declare -Ar args_array=( [p]=permission= [a]=add= [r]=remove= [p]=is_protected= ) + declare -Ar args_array=( [p]=permission= [a]=add= [r]=remove= [p]=protected= ) local permission local add local remove - local is_protected + local protected ynh_handle_getopts_args "$@" if [[ -n ${add:-} ]]; then @@ -366,13 +366,13 @@ ynh_permission_update() { if [[ -n ${remove:-} ]]; then remove=",remove=['${remove//';'/"','"}']" fi - if [ -n ${is_protected} ]; then - if [ $is_protected == "true" ]; then - is_protected=",is_protected=True" + if [ -n ${protected} ]; then + if [ $protected == "true" ]; then + protected=",protected=True" else - is_protected=",is_protected=False" + protected=",protected=False" fi fi - yunohost tools shell -c "from yunohost.permission import user_permission_update; user_permission_update('$app.$permission', ${add:-} ${remove} ${is_protected:-} , force=True, sync_perm=False)" + yunohost tools shell -c "from yunohost.permission import user_permission_update; user_permission_update('$app.$permission', ${add:-} ${remove} ${protected:-} , force=True, sync_perm=False)" } diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 37d6fc9db..d1f341d8d 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -750,7 +750,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu # Initialize the main permission for the app # After the install, if apps don't have a domain and path defined, the default url '/' is removed from the permission - permission_create(app_instance_name+".main", url="/", allowed=["all_users"], is_protected=False) + permission_create(app_instance_name+".main", url="/", allowed=["all_users"], protected=False) # Execute the app install script install_failed = True diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index b5c59907e..36d228ada 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -82,7 +82,7 @@ def user_permission_list(short=False, full=False, ignore_system_perms=False): return {'permissions': permissions} @is_unit_operation() -def user_permission_update(operation_logger, permission, add=None, remove=None, is_protected=None, force=False, sync_perm=True): +def user_permission_update(operation_logger, permission, add=None, remove=None, protected=None, force=False, sync_perm=True): """ Allow or Disallow a user or group to a permission for a specific application @@ -90,7 +90,7 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, permission -- Name of the permission (e.g. mail or or wordpress or wordpress.editors) add -- List of groups or usernames to add to this permission remove -- List of groups or usernames to remove from to this permission - is_protected -- (optional) Define if the permission can be added/removed to the visitor group + protected -- (optional) Define if the permission can be added/removed to the visitor group force -- (optional) Give the possibility to add/remove access from the visitor group to a protected permission """ from yunohost.user import user_group_list @@ -174,7 +174,7 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, operation_logger.start() - new_permission = _update_ldap_group_permission(permission=permission, allowed=new_allowed_groups, is_protected=is_protected, sync_perm=sync_perm) + new_permission = _update_ldap_group_permission(permission=permission, allowed=new_allowed_groups, protected=protected, sync_perm=sync_perm) logger.debug(m18n.n('permission_updated', permission=permission)) @@ -225,7 +225,7 @@ def user_permission_reset(operation_logger, permission, sync_perm=True): @is_unit_operation() -def permission_create(operation_logger, permission, url=None, allowed=None, is_protected=True, sync_perm=True): +def permission_create(operation_logger, permission, url=None, allowed=None, protected=True, sync_perm=True): """ Create a new permission for a specific application @@ -233,7 +233,7 @@ def permission_create(operation_logger, permission, url=None, allowed=None, is_p permission -- Name of the permission (e.g. mail or nextcloud or wordpress.editors) url -- (optional) URL for which access will be allowed/forbidden allowed -- (optional) A list of group/user to allow for the permission - is_protected -- (optional) Define if the permission can be added/removed to the visitor group + protected -- (optional) Define if the permission can be added/removed to the visitor group If provided, 'url' is assumed to be relative to the app domain/path if they start with '/'. For example: @@ -297,7 +297,7 @@ def permission_create(operation_logger, permission, url=None, allowed=None, is_p except Exception as e: raise YunohostError('permission_creation_failed', permission=permission, error=e) - new_permission = _update_ldap_group_permission(permission=permission, allowed=allowed, is_protected=is_protected, sync_perm=sync_perm) + new_permission = _update_ldap_group_permission(permission=permission, allowed=allowed, protected=protected, sync_perm=sync_perm) logger.debug(m18n.n('permission_created', permission=permission)) return new_permission @@ -435,7 +435,7 @@ def permission_sync_to_user(): os.system('nscd --invalidate=group') -def _update_ldap_group_permission(permission, allowed, is_protected=None, sync_perm=True): +def _update_ldap_group_permission(permission, allowed, protected=None, sync_perm=True): """ Internal function that will rewrite user permission @@ -463,13 +463,13 @@ def _update_ldap_group_permission(permission, allowed, is_protected=None, sync_p allowed = [allowed] if not isinstance(allowed, list) else allowed - if is_protected is None: - is_protected = existing_permission["protected"] + if protected is None: + protected = existing_permission["protected"] try: ldap.update('cn=%s,ou=permission' % permission, {'groupPermission': ['cn=' + g + ',ou=groups,dc=yunohost,dc=org' for g in allowed], - 'isProtected': "TRUE" if is_protected else "FALSE"}) + 'isProtected': "TRUE" if protected else "FALSE"}) except Exception as e: raise YunohostError('permission_update_failed', permission=permission, error=e) From 2b0711b1f4f6d35ca5ad43ac0b08465fb12b67e8 Mon Sep 17 00:00:00 2001 From: Kayou Date: Mon, 23 Dec 2019 20:27:57 +0800 Subject: [PATCH 0560/3170] Use image --- .gitlab-ci.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index bb7754e56..d28f46ba5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,8 +3,7 @@ stages: - tests .tests: - variables: - SNAPSHOT_NAME: "after-postinstall" + image: stretch:after-postinstall before_script: - apt-get install python-pip -y - pip2 install pytest pytest-sugar pytest-mock requests-mock mock @@ -17,8 +16,7 @@ stages: postinstall: - variables: - SNAPSHOT_NAME: "before-postinstall" + image: stretch:before-postinstall stage: postinstall script: - yunohost tools postinstall -d domain.tld -p the_password --ignore-dyndns From e3a3b00034a77695a2af5a89fa50084d6b1d808b Mon Sep 17 00:00:00 2001 From: Kayou Date: Mon, 23 Dec 2019 20:28:24 +0800 Subject: [PATCH 0561/3170] Use cache --- .gitlab-ci.yml | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d28f46ba5..c52092c5c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -6,14 +6,24 @@ stages: image: stretch:after-postinstall before_script: - apt-get install python-pip -y - - pip2 install pytest pytest-sugar pytest-mock requests-mock mock + - mkdir -p .pip + - pip install -U pip + - hash -d pip + - pip --cache-dir=.pip install pytest pytest-sugar pytest-mock requests-mock mock - pushd src/yunohost/tests - - git clone https://github.com/YunoHost/test_apps ./apps + - > + if [ ! -d "./apps" ]; then + git clone https://github.com/YunoHost/test_apps ./apps + fi - cd apps - git pull > /dev/null 2>&1 - popd - export PYTEST_ADDOPTS="--color=yes" - + cache: + paths: + - .pip + - src/yunohost/tests/apps + key: "$CI_JOB_STAGE-$CI_COMMIT_REF_SLUG" postinstall: image: stretch:before-postinstall From 175a87bd30380e857cc6d3203ec3696c38690bbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Mon, 23 Dec 2019 16:07:06 +0100 Subject: [PATCH 0562/3170] Implement restore permission --- src/yunohost/backup.py | 6 ++++-- src/yunohost/data_migrations/0011_setup_group_permission.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index c254c2ab5..257797d04 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -1250,7 +1250,8 @@ class RestoreManager(): for permission_name, permission_infos in old_apps_permission.items(): app_name = permission_name.split(".")[0] if _is_installed(app_name): - permission_create(permission_name, url=permission_infos["url"], allowed=permission_infos["allowed"], sync_perm=False) + permission_create(permission_name, url=permission_infos["url"], allowed=permission_infos["allowed"], + protected=permission_infos["protected"], sync_perm=False) permission_sync_to_user() @@ -1372,7 +1373,8 @@ class RestoreManager(): else: should_be_allowed = [g for g in permission_infos["allowed"] if g in existing_groups] - permission_create(permission_name, url=permission_infos.get("url", None), allowed=should_be_allowed, sync_perm=False) + permission_create(permission_name, url=permission_infos.get("url", None), allowed=should_be_allowed, + protected=permission_infos.get("protected", True), sync_perm=False) permission_sync_to_user() diff --git a/src/yunohost/data_migrations/0011_setup_group_permission.py b/src/yunohost/data_migrations/0011_setup_group_permission.py index c55e33cab..501dadaf4 100644 --- a/src/yunohost/data_migrations/0011_setup_group_permission.py +++ b/src/yunohost/data_migrations/0011_setup_group_permission.py @@ -116,7 +116,7 @@ class MyMigration(Migration): allowed = [user for user in permission.split(',') if user in known_users] else: allowed = ["all_users"] - permission_create(app+".main", url=url, allowed=allowed, sync_perm=False) + permission_create(app+".main", url=url, allowed=allowed, protected=False, sync_perm=False) app_setting(app, 'allowed_users', delete=True) From ca4c4c2e621bf64bf511def64637c6387fd4309f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Mon, 23 Dec 2019 16:10:13 +0100 Subject: [PATCH 0563/3170] Remove unused import --- src/yunohost/backup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 257797d04..dc85ab762 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -1188,7 +1188,7 @@ class RestoreManager(): return from yunohost.user import user_group_list - from yunohost.permission import permission_create, permission_delete, user_permission_update, user_permission_list, permission_sync_to_user + from yunohost.permission import permission_create, permission_delete, user_permission_list, permission_sync_to_user # Backup old permission for apps # We need to do that because in case of an app is installed we can't remove the permission for this app @@ -1292,7 +1292,7 @@ class RestoreManager(): restore_app_failed -- Raised if the restore bash script failed """ from yunohost.user import user_group_list - from yunohost.permission import permission_create, permission_delete, user_permission_list, user_permission_update, permission_sync_to_user + from yunohost.permission import permission_create, permission_delete, user_permission_list, permission_sync_to_user def copytree(src, dst, symlinks=False, ignore=None): for item in os.listdir(src): From 1e3410be4471daf38ebb5fe2ace7bc7828d6dd80 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Fri, 6 Dec 2019 10:05:08 +0000 Subject: [PATCH 0564/3170] Translated using Weblate (French) Currently translated at 100.0% (607 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 6ddb16de7..11f171347 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -673,7 +673,7 @@ "diagnosis_ip_not_connected_at_all": "Le serveur ne semble pas du tout connecté à Internet !?", "diagnosis_ip_weird_resolvconf": "La résolution DNS semble fonctionner, mais soyer prudent en utilisant un fichier /etc/resolv.conf personnalisé.", "diagnosis_ip_weird_resolvconf_details": "Au lieu de cela, ce fichier devrait être un lien symbolique vers /etc/resolvconf/run/resolv.conf lui-même pointant vers 127.0.0.1 (dnsmasq). Les résolveurs réels doivent être configurés via /etc/resolv.dnsmasq.conf.", - "diagnosis_dns_missing_record": "Selon la configuration DNS recommandée, vous devez ajouter un enregistrement DNS de type {0}, nom {1} et valeur {2}", + "diagnosis_dns_missing_record": "Selon la configuration DNS recommandée, vous devez ajouter un enregistrement DNS de type {0}, nom {1} et valeur {2}. Vous pouvez consulter https://yunohost.org/dns_config pour plus d'informations.", "diagnosis_diskusage_ok": "Le stockage {mountpoint} (sur le périphérique {device}) a toujours {espace libre {free_abs_GB} GB ({free_percent}%) !", "diagnosis_ram_ok": "Le système dispose toujours de {available_abs_MB} MB ({available_percent}%) de RAM sur {total_abs_MB} MB.", "diagnosis_regenconf_allgood": "Tous les fichiers de configuration sont conformes à la configuration recommandée !", @@ -699,7 +699,7 @@ "diagnosis_ip_broken_resolvconf": "La résolution du nom de domaine semble cassée sur votre serveur, ce qui semble lié au fait que /etc/resolv.conf ne pointe pas vers 127.0.0.1.", "diagnosis_dns_good_conf": "Bonne configuration DNS pour le domaine {domain} (catégorie {category})", "diagnosis_dns_bad_conf": "Configuration DNS incorrecte/manquante pour le domaine {domain} (catégorie {category})", - "diagnosis_dns_discrepancy": "Selon la configuration DNS recommandée, la valeur de l'enregistrement DNS de type {0} et nom {1} doit être {2} et non {3}.", + "diagnosis_dns_discrepancy": "L'enregistrement DNS de type {0} et nom {1} ne correspond pas à la configuration recommandée. Valeur actuelle: {2}. Valeur exceptée: {3}. Vous pouvez consulter https://yunohost.org/dns_config pour plus d'informations.", "diagnosis_services_bad_status": "Le service {service} est {status} :-(", "diagnosis_services_good_status": "Le service {service} est {status} comme prévu !", "diagnosis_diskusage_verylow": "Le stockage {mountpoint} (sur le périphérique {device}) ne dispose que de {free_abs_GB} Go ({free_percent}%). Vous devriez vraiment envisager de nettoyer un peu d'espace.", @@ -747,8 +747,15 @@ "diagnosis_services_running": "Le service {service} s'exécute correctement !", "diagnosis_services_conf_broken": "La configuration est cassée pour le service {service} !", "diagnosis_ports_needed_by": "Rendre ce port accessible est nécessaire pour le service {0}", - "diagnosis_ports_forwarding_tip": "Pour résoudre ce problème, vous devez très probablement configurer le transfert de port sur votre routeur Internet comme décrit dans https://yunohost.org/port_forwarding", + "diagnosis_ports_forwarding_tip": "Pour résoudre ce problème, vous devez probablement configurer la redirection de port sur votre routeur Internet, comme décrit dans https://yunohost.org/isp_box_config.", "diagnosis_http_connection_error": "Erreur de connexion : impossible de se connecter au domaine demandé, il est probablement injoignable.", "diagnosis_no_cache": "Pas encore de diagnostique de cache pour la catégorie « {category} »", - "diagnosis_http_unknown_error": "Une erreur est survenue en essayant de joindre votre domaine, il est probablement injoignable." + "diagnosis_http_unknown_error": "Une erreur est survenue en essayant de joindre votre domaine, il est probablement injoignable.", + "permission_all_users_implicitly_added": "La permission a également été implicitement accordée à 'all_users' car il est nécessaire pour permettre au groupe spécial 'visiteurs'", + "permission_cannot_remove_all_users_while_visitors_allowed": "Vous ne pouvez pas supprimer cette autorisation pour 'all_users' alors qu'elle est toujours autorisée pour 'visiteurs'", + "yunohost_postinstall_end_tip": "La post-installation terminée! Pour finaliser votre configuration, veuillez considérer:\n - ajout d'un premier utilisateur via la section \"Utilisateurs\" de l'administrateur Web (ou \"utilisateur yunohost créer \" en ligne de commande);\n - diagnostiquez les problèmes en attente de résolution du problème afin que votre serveur fonctionne le mieux possible dans la section \"Diagnostic\" de l'administrateur Web (ou \"Exécution du diagnostic yunohost\" en ligne de commande);\n - lecture des parties \"Finalisation de votre configuration\" et \"Découverte de Yunohost\" dans la documentation de l'administrateur: https://yunohost.org/admindoc.", + "diagnosis_services_bad_status_tip": "Vous pouvez essayer de redémarrer le service. Si cela ne fonctionne pas, consultez les journaux de service à l'aide de 'yunohost service log {0}' ou de la section 'Services' de l'administrateur Web.", + "diagnosis_http_bad_status_code": "N'a pas pu atteindre votre serveur comme prévu, il a renvoyé un code d'état incorrect. Il se peut qu'une autre machine réponde à la place de votre serveur. Vérifiez que vous transférez correctement le port 80, que votre configuration nginx est à jour et qu’un proxy inverse n’interfère pas.", + "diagnosis_http_timeout": "Expiration du délai en essayant de contacter votre serveur de l'extérieur. Il semble être inaccessible. Vérifiez que vous transférez correctement le port 80, que nginx est en cours d’exécution et qu’un pare-feu n’interfère pas.", + "global_settings_setting_pop3_enabled": "Activer le protocole POP3 pour le serveur de messagerie" } From 40c6846ef32b9d43ece167c22e1949d261f60226 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Thu, 19 Dec 2019 09:38:30 +0000 Subject: [PATCH 0565/3170] Translated using Weblate (Portuguese) Currently translated at 8.4% (51 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/pt/ --- locales/pt.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/pt.json b/locales/pt.json index a23265ab4..e068a2284 100644 --- a/locales/pt.json +++ b/locales/pt.json @@ -193,5 +193,6 @@ "backup_borg_not_implemented": "O método de backup Borg ainda não foi implementado.", "backup_cant_mount_uncompress_archive": "Não foi possível montar em modo leitura o diretorio de arquivos não comprimido", "backup_copying_to_organize_the_archive": "Copiando {size:s}MB para organizar o arquivo", - "app_change_url_identical_domains": "O antigo e o novo domínio / url_path são idênticos ('{domain:s}{path:s}'), nada para fazer." + "app_change_url_identical_domains": "O antigo e o novo domínio / url_path são idênticos ('{domain:s}{path:s}'), nada para fazer.", + "password_too_simple_1": "A senha precisa ter pelo menos 8 caracteres" } From 871d7705ae75c72ec5a455c8740515aad638000f Mon Sep 17 00:00:00 2001 From: elie gavoty Date: Wed, 4 Dec 2019 13:50:24 +0000 Subject: [PATCH 0566/3170] Translated using Weblate (German) Currently translated at 33.6% (204 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/locales/de.json b/locales/de.json index 08fdff7e4..b2d0f3673 100644 --- a/locales/de.json +++ b/locales/de.json @@ -417,5 +417,18 @@ "app_full_domain_unavailable": "Es tut uns leid, aber diese Anwendung erfordert die Installation auf einer eigenen Domain, aber einige andere Anwendungen sind bereits auf der Domäne'{domain}' installiert. Eine mögliche Lösung ist das Hinzufügen und Verwenden einer Subdomain, die dieser Anwendung zugeordnet ist.", "app_install_failed": "Installation von {app} fehlgeschlagen: {error}", "app_install_script_failed": "Im Installationsscript ist ein Fehler aufgetreten", - "app_remove_after_failed_install": "Entfernen der App nach fehlgeschlagener Installation…" + "app_remove_after_failed_install": "Entfernen der App nach fehlgeschlagener Installation…", + "app_upgrade_script_failed": "Es ist ein Fehler im App-Upgrade-Skript aufgetreten", + "diagnosis_basesystem_host": "Server läuft unter Debian {debian_version}.", + "diagnosis_basesystem_kernel": "Server läuft unter Linux-Kernel {kernel_version}", + "diagnosis_basesystem_ynh_single_version": "{0} Version: {1} ({2})", + "diagnosis_basesystem_ynh_main_version": "Server läuft YunoHost {main_version} ({repo})", + "diagnosis_basesystem_ynh_inconsistent_versions": "Sie verwenden inkonsistente Versionen der YunoHost-Pakete... wahrscheinlich wegen eines fehlgeschlagenen oder teilweisen Upgrades.", + "diagnosis_display_tip_web": "Sie können den Abschnitt Diagnose (im Startbildschirm) aufrufen, um die gefundenen Probleme anzuzeigen.", + "apps_catalog_init_success": "Apps-Katalogsystem initialisiert!", + "apps_catalog_updating": "Aktualisierung des Applikationskatalogs...", + "apps_catalog_failed_to_download": "Der {apps_catalog} Apps-Katalog kann nicht heruntergeladen werden: {error}", + "apps_catalog_obsolete_cache": "Der Cache des Apps-Katalogs ist leer oder veraltet.", + "apps_catalog_update_success": "Der Apps-Katalog wurde aktualisiert!", + "password_too_simple_1": "Das Passwort muss mindestens 8 Zeichen lang sein" } From 32b571691452dffe7257937aeb1b543f89aeb5ad Mon Sep 17 00:00:00 2001 From: Yasss Gurl Date: Sat, 21 Dec 2019 16:48:09 +0000 Subject: [PATCH 0567/3170] Translated using Weblate (German) Currently translated at 33.6% (204 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/locales/de.json b/locales/de.json index b2d0f3673..83b4b1210 100644 --- a/locales/de.json +++ b/locales/de.json @@ -254,7 +254,7 @@ "certmanager_domain_cert_not_selfsigned": "Das Zertifikat der Domain {domain:s} is kein selbstsigniertes Zertifikat. Bist du dir sicher, dass du es ersetzen willst? (Benutze --force)", "certmanager_certificate_fetching_or_enabling_failed": "Es scheint so als wäre die Aktivierung des Zertifikats für die Domain {domain:s} fehlgeschlagen...", "certmanager_attempt_to_renew_nonLE_cert": "Das Zertifikat der Domain {domain:s} wurde nicht von Let's Encrypt ausgestellt. Es kann nicht automatisch erneuert werden!", - "certmanager_attempt_to_renew_valid_cert": "Das Zertifikat der Domain {domain:s} läuft in Kürze ab! Benutze --force um diese Nachricht zu umgehen", + "certmanager_attempt_to_renew_valid_cert": "Das Zertifikat der Domain {domain:s} läuft nicht in Kürze ab! (Benutze --force um diese Nachricht zu umgehen)", "certmanager_domain_http_not_working": "Es scheint so, dass die Domain {domain:s} nicht über HTTP erreicht werden kann. Bitte überprüfe, ob deine DNS und nginx Konfiguration in Ordnung ist", "certmanager_error_no_A_record": "Kein DNS 'A' Eintrag für die Domain {domain:s} gefunden. Dein Domainname muss auf diese Maschine weitergeleitet werden, um ein Let's Encrypt Zertifikat installieren zu können! (Wenn du weißt was du tust, kannst du --no-checks benutzen, um diese Überprüfung zu überspringen. )", "certmanager_domain_dns_ip_differs_from_public_ip": "Der DNS 'A' Eintrag der Domain {domain:s} unterscheidet sich von dieser Server-IP. Wenn du gerade deinen A Eintrag verändert hast, warte bitte etwas, damit die Änderungen wirksam werden (du kannst die DNS Propagation mittels Website überprüfen) (Wenn du weißt was du tust, kannst du --no-checks benutzen, um diese Überprüfung zu überspringen. )", @@ -318,12 +318,12 @@ "confirm_app_install_thirdparty": "WARNUNG! Das Installieren von Anwendungen von Drittanbietern kann die Integrität und Sicherheit Deines Systems beeinträchtigen. Du solltest es wahrscheinlich NICHT installieren, es sei denn, Du weisst, was Du tust. Bist du bereit, dieses Risiko einzugehen? [{answers: s}] ", "confirm_app_install_danger": "WARNUNG! Diese Anwendung ist noch experimentell (wenn nicht ausdrücklich \"not working\"/\"funktioniert nicht\") und es ist wahrscheinlich, dass Dein System Schaden nimmt! Du solltest es wahrscheinlich NICHT installieren, es sei denn, Du weisst, was Du tust. Bist du bereit, dieses Risiko einzugehen? [{answers: s}] ", "confirm_app_install_warning": "Warnung: Diese Anwendung funktioniert möglicherweise, ist jedoch nicht gut in YunoHost integriert. Einige Funktionen wie Single Sign-On und Backup / Restore sind möglicherweise nicht verfügbar. Trotzdem installieren? [{answers: s}] ", - "backup_with_no_restore_script_for_app": "App {app: s} hat kein Wiederherstellungsskript. Das Backup dieser App kann nicht automatisch wiederhergestellt werden.", - "backup_with_no_backup_script_for_app": "App {app: s} hat kein Sicherungsskript. Ignoriere es.", - "backup_unable_to_organize_files": "Dateien im Archiv können mit der schnellen Methode nicht organisiert werden", - "backup_system_part_failed": "Der Systemteil '{part: s}' kann nicht gesichert werden", + "backup_with_no_restore_script_for_app": "Die App {app: s} hat kein Wiederherstellungsskript. Das Backup dieser App kann nicht automatisch wiederhergestellt werden.", + "backup_with_no_backup_script_for_app": "Die App {app: s} hat kein Sicherungsskript. Ignoriere es.", + "backup_unable_to_organize_files": "Dateien im Archiv konnten nicht mit der schnellen Methode organisiert werden", + "backup_system_part_failed": "Der Systemteil '{part: s}' konnte nicht gesichert werden", "backup_permission": "Sicherungsberechtigung für App {app: s}", - "backup_output_symlink_dir_broken": "Sie haben einen fehlerhaften Symlink anstelle Ihres Archivverzeichnisses '{path: s}'. Möglicherweise haben Sie ein spezielles Setup, um Ihre Daten auf einem anderen Dateisystem zu sichern. In diesem Fall haben Sie wahrscheinlich vergessen, Ihre Festplatte oder Ihren USB-Schlüssel erneut einzuhängen oder anzuschließen.", + "backup_output_symlink_dir_broken": "Ihr Archivverzeichnis '{path:s}' ist ein fehlerhafter Symlink. Vielleicht haben Sie vergessen, das Speichermedium, auf das er verweist, neu zu mounten oder einzustecken.", "backup_mount_archive_for_restore": "Archiv für Wiederherstellung vorbereiten…", "backup_method_tar_finished": "Tar-Backup-Archiv erstellt", "backup_method_custom_finished": "Benutzerdefinierte Sicherungsmethode '{method: s}' beendet", @@ -335,7 +335,7 @@ "backup_csv_creation_failed": "Die zur Wiederherstellung erforderliche CSV-Datei kann nicht erstellt werden", "backup_couldnt_bind": "{Src: s} konnte nicht an {dest: s} angebunden werden.", "backup_borg_not_implemented": "Die Borg-Sicherungsmethode ist noch nicht implementiert", - "backup_ask_for_copying_if_needed": "Einige Dateien konnten mit der Methode, die es vermeidet vorübergehend Speicherplatz auf dem System zu verschwenden, nicht gesichert werden. Zur Durchführung der Sicherung sollten vorübergehend {size: s} MB verwendet werden. Sind Sie einverstanden?", + "backup_ask_for_copying_if_needed": "Möchten Sie die Sicherung mit {size:s} MB temporär durchführen? (Dieser Weg wird verwendet, da einige Dateien nicht mit einer effizienteren Methode vorbereitet werden konnten).", "backup_actually_backuping": "Erstellt ein Backup-Archiv aus den gesammelten Dateien …", "ask_path": "Pfad", "ask_new_path": "Neuer Pfad", @@ -352,10 +352,10 @@ "app_not_upgraded": "Die App '{failed_app}' konnte nicht aktualisiert werden. Infolgedessen wurden die folgenden App-Upgrades abgebrochen: {apps}", "app_make_default_location_already_used": "Die App \"{app}\" kann nicht als Standard für die Domain \"{domain}\" festgelegt werden. Sie wird bereits von der App \"{other_app}\" verwendet", "aborting": "Breche ab.", - "app_action_cannot_be_ran_because_required_services_down": "Diese App erfordert einige Dienste, die derzeit nicht verfügbar sind. Bevor Sie fortfahren, sollten Sie versuchen, die folgenden Dienste neu zu starten (und möglicherweise untersuchen, warum sie nicht verfügbar sind): {services}", + "app_action_cannot_be_ran_because_required_services_down": "Diese erforderlichen Dienste sollten zur Durchführung dieser Aktion laufen: {services}. Versuchen Sie, sie neu zu starten, um fortzufahren (und möglicherweise zu untersuchen, warum sie nicht verfügbar sind).", "already_up_to_date": "Nichts zu tun. Alles ist bereits auf dem neusten Stand.", "admin_password_too_long": "Bitte ein Passwort kürzer als 127 Zeichen wählen", - "app_action_broke_system": "Diese Aktion hat anscheinend diese wichtigen Services gestört: {services}", + "app_action_broke_system": "Diese Aktion scheint diese wichtigen Dienste unterbrochen zu haben: {services}", "apps_already_up_to_date": "Alle Apps sind bereits aktuell", "backup_copying_to_organize_the_archive": "Kopieren von {size: s} MB, um das Archiv zu organisieren", "app_upgrade_stopped": "Das Upgrade aller Anwendungen wurde gestoppt, um mögliche Schäden zu vermeiden, da das Upgrade der vorherigen Anwendung fehlgeschlagen ist", From e87797dacf03629301319088c537967107cd57c7 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Thu, 19 Dec 2019 08:59:44 +0000 Subject: [PATCH 0568/3170] Translated using Weblate (Italian) Currently translated at 19.4% (118 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/it/ --- locales/it.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/it.json b/locales/it.json index 22cf9e2b0..fc2355e7f 100644 --- a/locales/it.json +++ b/locales/it.json @@ -316,7 +316,7 @@ "certmanager_cert_signing_failed": "Firma del nuovo certificato fallita", "good_practices_about_user_password": "Ora stai per impostare una nuova password utente. La password dovrebbe essere di almeno 8 caratteri - anche se è buona pratica utilizzare password più lunghe (es. una sequenza di parole) e/o utilizzare vari tipi di caratteri (maiuscole, minuscole, numeri e simboli).", "password_listed": "Questa password è una tra le più utilizzate al mondo. Per favore scegline una più unica.", - "password_too_simple_1": "La password deve essere lunga almeno 8 caratteri", + "password_too_simple_1": "La password deve contenere almeno 8 caratteri", "password_too_simple_2": "La password deve essere lunga almeno 8 caratteri e contenere numeri, maiuscole e minuscole", "password_too_simple_3": "La password deve essere lunga almeno 8 caratteri e contenere numeri, maiuscole e minuscole e simboli", "password_too_simple_4": "La password deve essere lunga almeno 12 caratteri e contenere numeri, maiuscole e minuscole", From 3859db213a59a1b6d4ae48f814945a5c01818541 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Thu, 19 Dec 2019 09:57:05 +0000 Subject: [PATCH 0569/3170] Translated using Weblate (Dutch) Currently translated at 6.6% (40 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/nl/ --- locales/nl.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/nl.json b/locales/nl.json index 166df89ff..df554f7e2 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -138,5 +138,6 @@ "backup_deleted": "Backup werd verwijderd", "backup_extracting_archive": "Backup archief uitpakken...", "backup_hook_unknown": "backup hook '{hook:s}' onbekend", - "backup_nothings_done": "Niets om op te slaan" + "backup_nothings_done": "Niets om op te slaan", + "password_too_simple_1": "Het wachtwoord moet minimaal 8 tekens lang zijn" } From b540e56c4680343a3baed89555b293d30fbe226a Mon Sep 17 00:00:00 2001 From: xaloc33 Date: Wed, 4 Dec 2019 21:42:12 +0000 Subject: [PATCH 0570/3170] Translated using Weblate (Catalan) Currently translated at 100.0% (607 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ca/ --- locales/ca.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/locales/ca.json b/locales/ca.json index 00b9b9e19..40977469c 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -491,7 +491,7 @@ "tools_upgrade_regular_packages": "Actualitzant els paquets «normals» (no relacionats amb YunoHost)…", "tools_upgrade_regular_packages_failed": "No s'han pogut actualitzar els paquets següents: {packages_list}", "tools_upgrade_special_packages": "Actualitzant els paquets «especials» (relacionats amb YunoHost)…", - "tools_upgrade_special_packages_explanation": "Aquesta acció s'acabarà, però l'actualització especial continuarà en segon pla. No comenceu cap altra acció al servidor en els pròxims ~10 minuts (depèn de la velocitat del maquinari). Un cop acabat, pot ser que us hagueu de tornar a connectar a la interfície d'administració. Els registres de l'actualització estaran disponibles a Eines → Registres (a la interfície d'administració) o amb «yunohost log list» (des de la línia d'ordres).", + "tools_upgrade_special_packages_explanation": "Aquesta actualització especial continuarà en segon pla. No comenceu cap altra acció al servidor en els pròxims ~10 minuts (depèn de la velocitat del maquinari). Després d'això, pot ser que us hagueu de tornar a connectar a la interfície d'administració. Els registres de l'actualització estaran disponibles a Eines → Registres (a la interfície d'administració) o utilitzant «yunohost log list» (des de la línia d'ordres).", "tools_upgrade_special_packages_completed": "Actualització dels paquets YunoHost acabada.\nPremeu [Enter] per tornar a la línia d'ordres", "unbackup_app": "L'aplicació «{app:s}» no serà guardada", "unexpected_error": "Hi ha hagut un error inesperat: {error}", @@ -716,5 +716,8 @@ "migration_description_0014_remove_app_status_json": "Eliminar els fitxers d'aplicació status.json heretats", "diagnosis_services_running": "El servei {service} s'està executant!", "diagnosis_services_conf_broken": "La configuració pel servei {service} està trencada!", - "diagnosis_ports_needed_by": "És necessari exposar aquest port pel servei {0}" + "diagnosis_ports_needed_by": "És necessari exposar aquest port pel servei {0}", + "permission_all_users_implicitly_added": "El permís també s'ha donat implícitament a «all_users» ja que és necessari per atorgar-lo al grup «visitors»", + "permission_cannot_remove_all_users_while_visitors_allowed": "No podeu retirar el permís a «all_users» mentre encara el tingui el grup «visitors»", + "global_settings_setting_pop3_enabled": "Activa el protocol POP3 per al servidor de correu" } From b5d494decde5fa515bb8ba5d44bbc0757ab7b1d9 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Thu, 19 Dec 2019 09:06:40 +0000 Subject: [PATCH 0571/3170] Translated using Weblate (Russian) Currently translated at 1.8% (11 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ru/ --- locales/ru.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/ru.json b/locales/ru.json index 306a8763a..ed0a4c183 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -42,5 +42,6 @@ "appslist_retrieve_error": "Невозможно получить список удаленных приложений {appslist:s}: {error:s}", "appslist_unknown": "Список приложений {appslist:s} неизвестен.", "appslist_url_already_tracked": "Это уже зарегистрированный список приложений с url{url:s}.", - "installation_complete": "Установка завершена" + "installation_complete": "Установка завершена", + "password_too_simple_1": "Пароль должен быть не менее 8 символов" } From aed4e8c0d245d7501ca1b0c7f25a8b7e3b11f3f8 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Thu, 19 Dec 2019 08:31:58 +0000 Subject: [PATCH 0572/3170] Translated using Weblate (Hungarian) Currently translated at 1.0% (6 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/hu/ --- locales/hu.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/hu.json b/locales/hu.json index a6df4d680..b39882148 100644 --- a/locales/hu.json +++ b/locales/hu.json @@ -9,5 +9,6 @@ "app_already_up_to_date": "{app:s} napra kész", "app_argument_choice_invalid": "{name:s} érvénytelen választás, csak egyike lehet {choices:s} közül", "app_argument_invalid": "'{name:s}' hibás paraméter érték :{error:s}", - "app_argument_required": "Parameter '{name:s}' kötelező" + "app_argument_required": "Parameter '{name:s}' kötelező", + "password_too_simple_1": "A jelszónak legalább 8 karakter hosszúnak kell lennie" } From 419913066612099cfb8e72bc2e2aa6c4f7c7283c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quent=C3=AD?= Date: Sun, 15 Dec 2019 12:06:43 +0000 Subject: [PATCH 0573/3170] Translated using Weblate (Occitan) Currently translated at 41.5% (252 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/oc/ --- locales/oc.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/locales/oc.json b/locales/oc.json index bbe30ac05..d3eb33e12 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -698,5 +698,12 @@ "apps_catalog_obsolete_cache": "La memòria cache del catalòg d’aplicacion es voida o obsolèta.", "apps_catalog_update_success": "Lo catalòg d’aplicacions es a jorn !", "diagnosis_mail_ougoing_port_25_ok": "Lo pòrt de sortida 25 es pas blocat e lo corrièr electronic pòt partir als autres servidors.", - "diagnosis_description_mail": "Corrièl" + "diagnosis_description_mail": "Corrièl", + "app_upgrade_script_failed": "Una error s’es producha pendent l’execucion de l’script de mesa a nivèl de l’aplicacion", + "diagnosis_cant_run_because_of_dep": "Execucion impossibla del diagnostic per {category} mentre que i a de problèmas importants ligats amb {dep}.", + "diagnosis_found_errors_and_warnings": "Avèm trobat {errors} problèma(s) important(s) (e {warnings} avis(es)) ligats a {category} !", + "diagnosis_failed": "Recuperacion impossibla dels resultats del diagnostic per la categoria « {category} » : {error}", + "diagnosis_ip_broken_dnsresolution": "La resolucion del nom de domeni es copada per una rason… Lo parafuòc bloca las requèstas DNS ?", + "diagnosis_no_cache": "I a pas encara de diagnostic de cache per la categoria « {category} »", + "apps_catalog_init_success": "Sistèma de catalòg d’aplicacion iniciat !" } From d52e4ad176fe647d299e9b09ab890a5f65dfe796 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Fri, 6 Dec 2019 10:13:07 +0000 Subject: [PATCH 0574/3170] Translated using Weblate (Esperanto) Currently translated at 100.0% (607 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/eo/ --- locales/eo.json | 140 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 110 insertions(+), 30 deletions(-) diff --git a/locales/eo.json b/locales/eo.json index 4b8a76d3c..f303a57ee 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -19,10 +19,10 @@ "yunohost_installing": "Instalante YunoHost…", "service_description_glances": "Monitoras sistemajn informojn en via servilo", "service_description_metronome": "Mastrumas XMPP tujmesaĝilon kontojn", - "service_description_mysql": "Stokas aplikaĵojn datojn (SQL datumbazo)", + "service_description_mysql": "Butikigas datumojn de programoj (SQL datumbazo)", "service_description_nginx": "Servas aŭ permesas atingi ĉiujn retejojn gastigita sur via servilo", "service_description_nslcd": "Mastrumas Yunohost uzantojn konektojn per komanda linio", - "service_description_php7.0-fpm": "Rulas aplikaĵojn skibita en PHP kun nginx", + "service_description_php7.0-fpm": "Ekzekutas programojn skribitajn en PHP kun NGINX", "service_description_postfix": "Uzita por sendi kaj ricevi retpoŝtojn", "service_description_redis-server": "Specialita datumbazo uzita por rapida datumo atingo, atendovicoj kaj komunikadoj inter programoj", "service_description_rmilter": "Kontrolas diversajn parametrojn en retpoŝtoj", @@ -30,9 +30,9 @@ "service_description_slapd": "Stokas uzantojn, domajnojn kaj rilatajn informojn", "service_description_ssh": "Permesas al vi konekti al via servilo kun fora terminalo (SSH protokolo)", "service_description_yunohost-api": "Mastrumas interagojn inter la YunoHost retinterfaco kaj la sistemo", - "service_description_yunohost-firewall": "Mastrumas malfermitajn kaj fermitajn konektejojn al servoj", - "service_disable_failed": "Ne povis malŝalti la servon '{service:s}'\n\nFreŝaj protokoloj de la servo : {logs:s}", - "service_disabled": "'{service: s}' servo malŝaltita", + "service_description_yunohost-firewall": "Administras malfermajn kaj fermajn konektajn havenojn al servoj", + "service_disable_failed": "Ne povis fari la servon '{service:s}' ne komenci ĉe la ekkuro.\n\nLastatempaj servaj protokoloj: {logs:s}", + "service_disabled": "La servo '{service:s}' ne plu komenciĝos kiam sistemo ekos.", "action_invalid": "Nevalida ago « {action:s} »", "admin_password": "Pasvorto de la estro", "admin_password_too_long": "Bonvolu elekti pasvorton pli mallonga ol 127 signoj", @@ -90,7 +90,7 @@ "app_requirements_failed": "Certaines exigences ne sont pas remplies pour {app}: {error}", "backup_no_uncompress_archive_dir": "Ne ekzistas tia nekompremita arkiva dosierujo", "password_too_simple_1": "Pasvorto devas esti almenaŭ 8 signojn longa", - "app_upgrade_failed": "Ne povis ĝisdatigi {app:s}", + "app_upgrade_failed": "Ne povis ĝisdatigi {app:s}: {error}", "app_upgrade_several_apps": "La sekvaj apliko estos altgradigitaj: {apps}", "backup_archive_open_failed": "Ne povis malfermi la rezervan ar archiveivon", "ask_lastname": "Familia nomo", @@ -197,12 +197,12 @@ "migration_0011_failed_to_remove_stale_object": "Ne povis forigi neuzatan objekton {dn}: {error}", "migrations_already_ran": "Tiuj migradoj estas jam faritaj: {ids}", "migrations_no_such_migration": "Estas neniu migrado nomata '{id}'", - "permission_already_allowed": "Grupo '{group}' jam havas permeson '{permission}' ebligita'", + "permission_already_allowed": "Grupo '{group}' jam havas rajtigitan permeson '{permission}'", "permission_already_disallowed": "Grupo '{group}' jam havas permeson '{permission}' malebligita'", "permission_cannot_remove_main": "Forigo de ĉefa permeso ne rajtas", "permission_creation_failed": "Ne povis krei permeson '{permission}': {error}", - "tools_update_failed_to_app_fetchlist": "Ne povis ĝisdatigi la aparatojn de YunoHost ĉar: {error}", - "user_already_exists": "Uzanto {uzanto} jam ekzistas", + "tools_update_failed_to_app_fetchlist": "Ne povis ĝisdatigi la listojn de aplikoj de YunoHost ĉar: {error}", + "user_already_exists": "La uzanto '{user}' jam ekzistas", "migrations_pending_cant_rerun": "Tiuj migradoj ankoraŭ estas pritraktataj, do ne plu rajtas esti ekzekutitaj: {ids}", "migrations_running_forward": "Kuranta migrado {id}…", "migrations_success_forward": "Migrado {id} kompletigita", @@ -213,7 +213,7 @@ "permission_not_found": "Permesita \"{permission:s}\" ne trovita", "restore_not_enough_disk_space": "Ne sufiĉa spaco (spaco: {free_space:d} B, necesa spaco: {needed_space:d} B, sekureca marĝeno: {margin:d} B)", "tools_upgrade_regular_packages": "Nun ĝisdatigi 'regulajn' (ne-yunohost-rilatajn) pakojn …", - "tools_upgrade_special_packages_explanation": "Ĉi tiu ago finiĝos, sed la fakta speciala ĝisdatigo daŭros en fono. Bonvolu ne komenci iun alian agon en via servilo en la sekvaj ~ 10 minutoj (depende de via aparata rapideco). Unufoje mi plenumis, vi eble devos ensaluti en la retpaĝo. La ĝisdatiga registro estos havebla en Iloj → Madero (sur la retpaĝo) aŭ tra 'yunohost-registro-listo' (el la komandlinio).", + "tools_upgrade_special_packages_explanation": "La speciala ĝisdatigo daŭros en fono. Bonvolu ne komenci aliajn agojn en via servilo la sekvajn ~ 10 minutojn (depende de la aparata rapideco). Post tio, vi eble devos re-ensaluti sur la retadreso. La ĝisdatiga registro estos havebla en Iloj → Ensaluto (en la retadreso) aŭ uzante 'yunohost-logliston' (el la komandlinio).", "unrestore_app": "App '{app:s}' ne restarigos", "group_created": "Grupo '{group}' kreita", "group_creation_failed": "Ne povis krei la grupon '{group}': {error}", @@ -236,7 +236,7 @@ "regenconf_file_backed_up": "Agordodosiero '{conf}' estis rezervita al '{backup}'", "migration_0007_cannot_restart": "SSH ne rekomencas post provi nuligi la migradan numeron 6.", "migration_description_0006_sync_admin_and_root_passwords": "Sinkronigu admin kaj radikajn pasvortojn", - "updating_app_lists": "Akirante haveblajn ĝisdatigojn por aplikoj…", + "updating_app_lists": "Akirante haveblajn ĝisdatigojn por programoj…", "iptables_unavailable": "Vi ne povas ludi kun iptables ĉi tie. Vi estas en ujo aŭ via kerno ne subtenas ĝin", "global_settings_cant_write_settings": "Ne eblis konservi agordojn, tial: {reason:s}", "service_added": "La servo '{service:s}' aldonis", @@ -279,7 +279,7 @@ "migrate_tsig_start": "Detektita ŝlosila algoritmo nesufiĉa por TSIG-subskribo de la domajno '{domain}', komencanta migradon al la pli sekura HMAC-SHA-512", "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 malhelpi kritikajn pakojn…", + "tools_upgrade_cant_unhold_critical_packages": "Ne povis malŝalti kritikajn pakojn…", "diagnosis_monitor_disk_error": "Ne povis monitori diskojn: {error}", "log_link_to_log": "Plena ŝtipo de ĉi tiu operacio: ' {desc} '", "service_no_log": "Neniu registro por montri por servo '{service:s}'", @@ -317,7 +317,7 @@ "log_app_removelist": "Forigu aplikan liston", "global_settings_setting_example_enum": "Ekzemplo enum elekto", "hook_exec_not_terminated": "Skripto ne finiĝis ĝuste: {path:s}", - "service_stopped": "'{service:s}' servo ĉesis", + "service_stopped": "Servo '{service:s}' ĉesis", "restore_failed": "Ne povis restarigi sistemon", "confirm_app_install_danger": "Danĝero! Ĉi tiu apliko estas konata ankoraŭ eksperimenta (se ne eksplicite ne funkcias)! Vi probable ne devas instali ĝin krom se vi scias kion vi faras. NENIU SUBTENO estos provizita se ĉi tiu app ne funkcias aŭ rompas vian sistemon ... Se vi pretas riski ĉiuokaze, tajpu '{answers: s}'", "log_operation_unit_unclosed_properly": "Operaciumo ne estis fermita ĝuste", @@ -362,21 +362,21 @@ "log_corrupted_md_file": "La YAD-metadata dosiero asociita kun protokoloj estas damaĝita: '{md_file}\nEraro: {error} '", "downloading": "Elŝutante …", "user_deleted": "Uzanto forigita", - "service_enable_failed": "Ne eblis ŝalti la servon '{service:s}'\n\nLastatempaj servaj protokoloj: {logs:s}", + "service_enable_failed": "Ne povis fari la servon '{service:s}' aŭtomate komenci ĉe la ekkuro.\n\nLastatempaj servaj protokoloj: {logs:s}", "tools_upgrade_special_packages": "Nun ĝisdatigi 'specialajn' (rilatajn al yunohost)…", "domains_available": "Haveblaj domajnoj:", "dyndns_registered": "Registrita domajno DynDNS", "service_description_fail2ban": "Protektas kontraŭ bruta forto kaj aliaj specoj de atakoj de la interreto", "file_does_not_exist": "La dosiero {path:s} ne ekzistas.", - "yunohost_not_installed": "YunoHost estas malĝuste aŭ ne ĝuste instalita. Bonvolu prilabori 'yunohost tools postinstall'", + "yunohost_not_installed": "YunoHost ne estas ĝuste instalita. Bonvolu prilabori 'yunohost tools postinstall'", "migration_0005_postgresql_96_not_installed": "PostgreSQL 9.4 estas instalita, sed ne postgresql 9.6‽ Io stranga eble okazis en via sistemo: (…", "restore_removing_tmp_dir_failed": "Ne povis forigi malnovan provizoran dosierujon", "certmanager_cannot_read_cert": "Io malbona okazis, kiam mi provis malfermi aktualan atestilon por domajno {domain:s} (dosiero: {file:s}), kialo: {reason:s}", - "service_removed": "'{service:s}' servo forigita", + "service_removed": "Servo '{service:s}' forigita", "certmanager_hit_rate_limit": "Tro multaj atestiloj jam eldonitaj por ĉi tiu ĝusta aro de domajnoj {domain:s} antaŭ nelonge. Bonvolu reprovi poste. Vidu https://letsencrypt.org/docs/rate-limits/ por pliaj detaloj", "migration_0005_not_enough_space": "Disponigu sufiĉan spacon en {path} por ruli la migradon.", "pattern_firstname": "Devas esti valida antaŭnomo", - "migration_description_0010_migrate_to_apps_json": "Forigu malvalorigitajn aparatojn kaj uzu anstataŭe la novan unuigitan liston \"apps.json\"", + "migration_description_0010_migrate_to_apps_json": "Forigu malvalorigitajn katalogajn programojn kaj uzu anstataŭe la novan unuigitan liston de \"apps.json\" (malaktuale anstataŭita per migrado 13)", "domain_cert_gen_failed": "Ne povis generi atestilon", "regenconf_file_kept_back": "La agorda dosiero '{conf}' estas atendita forigi per regen-conf (kategorio {category}), sed ĝi estis konservita.", "migrate_tsig_wait_4": "30 sekundoj …", @@ -432,7 +432,7 @@ "backup_php5_to_php7_migration_may_fail": "Ne povis konverti vian ar archiveivon por subteni PHP 7, vi eble ne povas restarigi viajn PHP-programojn (kialo: {error:s})", "log_backup_restore_system": "Restarigi sistemon de rezerva arkivo", "log_app_change_url": "Ŝanĝu la URL de la apliko '{}'", - "service_already_started": "La servo '{service:s}' estas jam komencita", + "service_already_started": "La servo '{service:s}' jam funkcias", "license_undefined": "nedifinita", "global_settings_setting_security_password_admin_strength": "Admin pasvorta forto", "service_reload_or_restart_failed": "Ne povis reŝargi aŭ rekomenci la servon '{service:s}'\n\nLastatempaj servaj protokoloj: {logs:s}", @@ -455,7 +455,7 @@ "regenconf_file_updated": "Agordodosiero '{conf}' ĝisdatigita", "log_help_to_get_log": "Por vidi la protokolon de la operacio '{desc}', uzu la komandon 'yunohost log display {name}'", "global_settings_setting_security_nginx_compatibility": "Kongruo vs sekureca kompromiso por la TTT-servilo NGINX. Afektas la ĉifradojn (kaj aliajn aspektojn pri sekureco)", - "no_internet_connection": "Servilo ne konektita al la interreto", + "no_internet_connection": "La servilo ne estas konektita al la interreto", "migration_0008_dsa": "• La DSA-ŝlosilo estos malŝaltita. Tial vi eble bezonos nuligi spuran averton de via SSH-kliento kaj revizii la fingrospuron de via servilo;", "migration_0003_restoring_origin_nginx_conf": "Fileia dosiero /etc/nginx/nginx.conf estis iel redaktita. La migrado reaperos unue al sia originala stato ... La antaŭa dosiero estos havebla kiel {backup_dest}.", "migrate_tsig_end": "Migrado al HMAC-SHA-512 finiĝis", @@ -467,7 +467,7 @@ "service_description_avahi-daemon": "Permesas al vi atingi vian servilon uzante 'yunohost.local' en via loka reto", "certmanager_attempt_to_replace_valid_cert": "Vi provas anstataŭigi bonan kaj validan atestilon por domajno {domajno:s}! (Uzu --forte pretervidi)", "monitor_stats_period_unavailable": "Ne ekzistas disponeblaj statistikoj por la periodo", - "regenconf_updated": "Agordo por kategorio '{category}' ĝisdatigita", + "regenconf_updated": "Agordo ĝisdatigita por '{category}'", "update_apt_cache_warning": "Io iris malbone dum la ĝisdatigo de la kaŝmemoro de APT (paka administranto de Debian). Jen rubujo de la sources.list-linioj, kiuj povus helpi identigi problemajn liniojn:\n{sourcelist}", "regenconf_dry_pending_applying": "Kontrolado de pritraktata agordo, kiu estus aplikita por kategorio '{category}'…", "regenconf_file_copy_failed": "Ne povis kopii la novan agordodosieron '{new}' al '{conf}'", @@ -478,7 +478,7 @@ "maindomain_change_failed": "Ne povis ŝanĝi la ĉefan domajnon", "mail_domain_unknown": "Nevalida retadreso por domajno '{domain:s}'. Bonvolu uzi domajnon administritan de ĉi tiu servilo.", "migrations_cant_reach_migration_file": "Ne povis aliri migrajn dosierojn ĉe la vojo '% s'", - "pattern_email": "Devas esti valida retpoŝtadreso (t.e.iu@domain.org)", + "pattern_email": "Devas esti valida retpoŝta adreso (t.e. iu@ekzemple.com)", "mail_alias_remove_failed": "Ne povis forigi retpoŝton alias '{mail:s}'", "regenconf_file_manually_removed": "La dosiero de agordo '{conf}' estis forigita permane, kaj ne estos kreita", "monitor_enabled": "Servilo-monitorado nun", @@ -501,7 +501,7 @@ "log_app_install": "Instalu la aplikon '{}'", "service_description_dnsmasq": "Traktas rezolucion de domajna nomo (DNS)", "global_settings_unknown_type": "Neatendita situacio, la agordo {setting:s} ŝajnas havi la tipon {unknown_type:s} sed ĝi ne estas tipo subtenata de la sistemo.", - "migration_0003_problematic_apps_warning": "Bonvolu noti, ke la sekvaj eventuale problemaj instalitaj apps estis detektitaj. Ĝi aspektas, ke tiuj ne estis instalitaj de aparato aŭ ne estas markitaj kiel \"funkciantaj\". Tial ne eblas garantii, ke ili ankoraŭ funkcios post la ĝisdatigo: {problematic_apps}", + "migration_0003_problematic_apps_warning": "Bonvolu noti, ke la sekvaj eventuale problemaj instalitaj programoj estis detektitaj. Ŝajnas, ke tiuj ne estis instalitaj el app_katalogo aŭ ne estas markitaj kiel \"funkciantaj\". Tial ne eblas garantii, ke ili ankoraŭ funkcios post la ĝisdatigo: {problematic_apps}", "domain_hostname_failed": "Ne povis agordi novan gastigilon. Ĉi tio eble kaŭzos problemon poste (eble bone).", "server_reboot": "La servilo rekomenciĝos", "regenconf_failed": "Ne povis regeneri la agordon por kategorio(j): {categories}", @@ -510,7 +510,7 @@ "service_unknown": "Nekonata servo '{service:s}'", "migration_0003_start": "Komencante migradon al Stretch. La protokoloj haveblos en {logfile}.", "monitor_stats_no_update": "Neniuj monitoradaj statistikoj ĝisdatigi", - "domain_deletion_failed": "Ne povis forigi domajnon {domain}: {error}", + "domain_deletion_failed": "Ne eblas forigi domajnon {domain}: {error}", "log_user_update": "Ĝisdatigu uzantinformojn de '{}'", "user_creation_failed": "Ne povis krei uzanton {user}: {error}", "migrations_migration_has_failed": "Migrado {id} ne kompletigis, abolis. Eraro: {exception}", @@ -529,13 +529,13 @@ "migration_0007_cancelled": "Ne povis plibonigi la manieron kiel via SSH-agordo estas administrita.", "migrate_tsig_failed": "Ne povis migri la DynDNS-domajnon '{domain}' al HMAC-SHA-512, ruliĝante. Eraro: {error_code}, {error}", "pattern_lastname": "Devas esti valida familinomo", - "service_enabled": "'{service:s}' servo malŝaltita", + "service_enabled": "La servo '{service:s}' nun aŭtomate komenciĝos dum sistemaj botoj.", "certmanager_no_cert_file": "Ne povis legi la atestan dosieron por la domajno {domain:s} (dosiero: {file:s})", "migration_0008_port": "• Vi devos konekti uzante la havenon 22 anstataŭ via nuna kutimo SSH-haveno. Sentu vin libera reconfiguri ĝin;", - "domain_creation_failed": "Ne povis krei domajnon {domain}: {error}", + "domain_creation_failed": "Ne eblas krei domajnon {domain}: {error}", "certmanager_domain_http_not_working": "Ŝajnas ke la domajno {domain:s} ne atingeblas per HTTP. Kontrolu, ke via DNS kaj NGINX-agordo ĝustas", - "domain_cannot_remove_main": "Ne eblas forigi ĉefan domajnon. Fiksu unu unue", - "service_reloaded_or_restarted": "'{service:s}' servo reŝarĝis aŭ rekomencis", + "domain_cannot_remove_main": "Vi ne povas forigi '{domain:s}' ĉar ĝi estas la ĉefa domajno, vi bezonas unue agordi alian domajnon kiel la ĉefan domajnon per uzado de 'yunohost domain main-domain -n ', jen la listo de kandidataj domajnoj. : {other_domainsj:s}", + "service_reloaded_or_restarted": "La servo '{service:s}' estis reŝarĝita aŭ rekomencita", "mysql_db_initialized": "La datumbazo MySQL jam estas pravalorizita", "log_domain_add": "Aldonu '{}' domajnon en sisteman agordon", "global_settings_bad_type_for_setting": "Malbona tipo por agordo {setting:s}, ricevita {received_type:s}, atendata {expected_type:s}", @@ -543,11 +543,11 @@ "dyndns_cron_installed": "Kreita laboro DynDNS cron", "system_username_exists": "Uzantnomo jam ekzistas en la listo de uzantoj de sistemo", "firewall_reloaded": "Fajroŝirmilo reŝarĝis", - "service_restarted": "'{service:s}' servo rekomencis", + "service_restarted": "Servo '{service:s}' rekomencis", "pattern_username": "Devas esti minuskulaj literoj kaj minuskloj nur", "extracting": "Eltirante…", "restore_app_failed": "Ne povis restarigi la programon '{app:s}'", - "yunohost_configured": "YunoHost nun agordis", + "yunohost_configured": "YunoHost nun estas agordita", "certmanager_self_ca_conf_file_not_found": "Ne povis trovi agorddosieron por mem-subskriba aŭtoritato (dosiero: {file:s})", "log_app_remove": "Forigu la aplikon '{}'", "service_restart_failed": "Ne povis rekomenci la servon '{service:s}'\n\nLastatempaj servaj protokoloj: {logs:s}", @@ -579,5 +579,85 @@ "diagnosis_cache_still_valid": "(Kaŝmemoro ankoraŭ validas por {category} diagnozo. Ankoraŭ ne re-diagnoza!)", "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}" + "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_abs_GB} GB ({free_percent}%) spaco. Vi vere konsideru purigi iom da spaco.", + "diagnosis_ram_verylow": "La sistemo nur restas {available_abs_MB} MB ({available_percent}%) RAM! (el {total_abs_MB} MB)", + "diagnosis_mail_ougoing_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": "Ne povis atingi vian servilon kiel atendite, ĝi redonis malbonan statuskodon. Povas esti, ke alia maŝino respondis anstataŭ via servilo. Vi devus kontroli, ke vi ĝuste redonas la havenon 80, ke via nginx-agordo ĝisdatigas kaj ke reverso-prokuro ne interbatalas.", + "main_domain_changed": "La ĉefa domajno estis ŝanĝita", + "permission_all_users_implicitly_added": "La permeso ankaŭ estis implicite donita al 'all_users' ĉar ĝi bezonas por permesi al la 'visitors' de la speciala grupo", + "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ŭ 'yunohost user create ' en komandlinio);\n - diagnozi problemojn atendantajn solvi por ke via servilo funkciu kiel eble plej glate tra la sekcio 'Diagnosis' de la retadministrado (aŭ 'yunohost diagnosis run' en komandlinio);\n - legante la partojn 'Finigi vian agordon' kaj 'Ekkoni Yunohost' en la administra dokumentado: https://yunohost.org/admindoc.", + "permission_cannot_remove_all_users_while_visitors_allowed": "Vi ne povas forigi ĉi tiun permeson por 'all_users' dum ĝi tamen estas permesita por 'visitors'", + "migration_description_0014_remove_app_status_json": "Forigi heredajn dosierojn", + "diagnosis_ip_connected_ipv4": "La servilo estas konektita al la interreto per IPv4 !", + "diagnosis_ip_no_ipv4": "La servilo ne havas funkciantan IPv4.", + "diagnosis_ip_connected_ipv6": "La servilo estas konektita al la interreto per IPv6 !", + "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 per /etc/resolv.dnsmasq.conf.", + "diagnosis_dns_good_conf": "Bona DNS-agordo por domajno {domain} (kategorio {category})", + "diagnosis_dns_bad_conf": "Malbona / mankas DNS-agordo por domajno {domain} (kategorio {category})", + "diagnosis_ram_ok": "La sistemo ankoraŭ havas {available_abs_MB} MB ({available_percent}%) RAM forlasita de {total_abs_MB} MB.", + "diagnosis_swap_none": "La sistemo tute ne havas interŝanĝon. Vi devus pripensi aldoni almenaŭ 256 MB da interŝanĝo por eviti situaciojn en kiuj la sistemo restas sen memoro.", + "diagnosis_swap_notsomuch": "La sistemo havas nur {total_MB} MB-interŝanĝon. Vi konsideru havi almenaŭ 256 MB 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_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_regenconf_nginx_conf_broken": "La agordo de nginx ŝajnas rompi !", + "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_dns_missing_record": "Laŭ la rekomendita DNS-agordo, vi devas aldoni DNS-registron kun tipo {0}, nomo {1} kaj valoro {2}. Vi povas kontroli https://yunohost.org/dns_config por pliaj informoj.", + "diagnosis_dns_discrepancy": "La DNS-registro kun tipo {0} kaj nomo {1} ne kongruas kun la rekomendita agordo. Nuna valoro: {2}. Esceptita valoro: {3}. Vi povas kontroli https://yunohost.org/dns_config por pliaj informoj.", + "diagnosis_services_conf_broken": "Agordo estas rompita por servo {service} !", + "diagnosis_services_bad_status": "Servo {service} estas {status} :(", + "diagnosis_ram_low": "La sistemo havas {available_abs_MB} MB ({available_percent}%) RAM forlasita de {total_abs_MB} MB. Estu zorgema.", + "diagnosis_swap_ok": "La sistemo havas {total_MB} MB 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_description_ip": "Interreta konektebleco", + "diagnosis_description_dnsrecords": "Registroj DNS", + "diagnosis_description_services": "Servo kontrolas staton", + "diagnosis_description_systemresources": "Rimedaj sistemoj", + "diagnosis_description_security": "Sekurecaj kontroloj", + "diagnosis_ports_could_not_diagnose": "Ne povis diagnozi, ĉu haveblaj havenoj de ekstere. 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 {0}' aŭ tra la sekcio 'Servoj' de la retadreso.", + "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 '{}' kiel ĉ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_connection_error": "Rilata eraro: ne povis konektiĝi al la petita domajno, tre probable ĝi estas neatingebla.", + "diagnosis_http_unknown_error": "Eraro okazis dum provado atingi vian domajnon, 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_abs_GB} GB ({free_percent}%) spaco. Estu zorgema.", + "diagnosis_diskusage_ok": "Stokado {mountpoint} (sur aparato {device) ankoraŭ restas {free_abs_GB} GB ({free_percent}%) spaco!", + "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 servo {0}", + "diagnosis_ports_forwarding_tip": "Por solvi ĉi tiun problemon, plej probable vi 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. Eraro: {error}", + "diagnosis_http_ok": "Domajno {domain} atingeblas de ekstere.", + "diagnosis_http_unreachable": "Domajno {domain} estas atingebla per HTTP de ekstere.", + "domain_cannot_remove_main_add_new_one": "Vi ne povas forigi '{domain:s}' ĉar ĝi estas la ĉefa domajno kaj via sola domajno, vi devas unue aldoni alian domajnon uzante ''yunohost domain add ', tiam agordi kiel ĉefan domajnon uzante 'yunohost domain main-domain -n ' kaj tiam vi povas forigi la domajnon' {domain: s} 'uzante' yunohost domain remove {domain:s} '.'", + "permission_require_account": "Permesilo {permission} nur havas sencon por uzantoj, kiuj havas konton, kaj tial ne rajtas esti ebligitaj por vizitantoj.", + "diagnosis_found_warnings": "Trovitaj {warnings} ero (j) kiuj povus esti plibonigitaj por {category}.", + "diagnosis_everything_ok": "Ĉio aspektas bone por {category}!", + "diagnosis_failed": "Malsukcesis preni la diagnozan rezulton por kategorio '{category}': {error}", + "diagnosis_description_ports": "Ekspoziciaj havenoj", + "diagnosis_description_http": "HTTP-ekspozicio", + "diagnosis_description_mail": "Retpoŝto" } From 93adb03fa296c31e91ffed9495196fd5300cce69 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Thu, 19 Dec 2019 08:51:49 +0000 Subject: [PATCH 0575/3170] Translated using Weblate (Hindi) Currently translated at 3.8% (23 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/hi/ --- locales/hi.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/hi.json b/locales/hi.json index 015fd4e5e..23d391c47 100644 --- a/locales/hi.json +++ b/locales/hi.json @@ -77,5 +77,6 @@ "domain_deleted": "डोमेन डिलीट कर दिया गया है", "domain_deletion_failed": "डोमेन डिलीट करने में असमर्थ", "domain_dyndns_already_subscribed": "DynDNS डोमेन पहले ही सब्स्क्राइड है", - "domain_dyndns_invalid": "DynDNS के साथ इनवैलिड डोमिन इस्तेमाल किया गया" + "domain_dyndns_invalid": "DynDNS के साथ इनवैलिड डोमिन इस्तेमाल किया गया", + "password_too_simple_1": "पासवर्ड को कम से कम 8 वर्ण लंबा होना चाहिए" } From 73c3d25a5b61ae72ac82d75553dcaa20662a22ab Mon Sep 17 00:00:00 2001 From: amirale qt Date: Thu, 19 Dec 2019 09:45:20 +0000 Subject: [PATCH 0576/3170] Translated using Weblate (Polish) Currently translated at 0.2% (1 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/pl/ --- locales/pl.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/locales/pl.json b/locales/pl.json index 0967ef424..f1417a80c 100644 --- a/locales/pl.json +++ b/locales/pl.json @@ -1 +1,3 @@ -{} +{ + "password_too_simple_1": "Hasło musi mieć co najmniej 8 znaków" +} From 171f37a589cd2b5ca5874a9f89b4fb220ed251e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Mon, 23 Dec 2019 16:13:48 +0100 Subject: [PATCH 0577/3170] Implement migration --- .../0015_add_permission_protection.py | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 src/yunohost/data_migrations/0015_add_permission_protection.py diff --git a/src/yunohost/data_migrations/0015_add_permission_protection.py b/src/yunohost/data_migrations/0015_add_permission_protection.py new file mode 100644 index 000000000..1b2965fb0 --- /dev/null +++ b/src/yunohost/data_migrations/0015_add_permission_protection.py @@ -0,0 +1,37 @@ +import time +import os + +from moulinette import m18n +from yunohost.utils.error import YunohostError +from moulinette.utils.log import getActionLogger + +from yunohost.tools import Migration +from yunohost.permission import user_permission_list, SYSTEM_PERMS + +logger = getActionLogger('yunohost.migration') + +################################################### +# Tools used also for restoration +################################################### + +class MyMigration(Migration): + """ + Add protected attribute in LDAP permission + """ + + required = True + + def run(self): + + from yunohost.utils.ldap import _get_ldap_interface + ldap = _get_ldap_interface() + + permission_list = user_permission_list(short=True) + + for permission in permission_list: + if permission in SYSTEM_PERMS: + ldap.update('cn=%s,ou=permission' % permission, 'isProtected': "TRUE"}) + elif permission.end_with(".main"): + ldap.update('cn=%s,ou=permission' % permission, 'isProtected': "FALSE"}) + else: + ldap.update('cn=%s,ou=permission' % permission, 'isProtected': "TRUE"}) From 672eb6f512f9d3e4c2a16f88db5e09beacbc91ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Fri, 27 Dec 2019 16:07:31 +0100 Subject: [PATCH 0578/3170] Fix permission creation --- src/yunohost/permission.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 36d228ada..4e0507423 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -271,7 +271,8 @@ def permission_create(operation_logger, permission, url=None, allowed=None, prot attr_dict = { 'objectClass': ['top', 'permissionYnh', 'posixGroup'], 'cn': str(permission), - 'gidNumber': gid + 'gidNumber': gid, + 'isProtected': 'FALSE' # Dummy value, it will be fixed when we call '_update_ldap_group_permission' } if url: From 45966c8ebd8539dd47d0cc63d38ca86a8a4be96f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Fri, 27 Dec 2019 16:17:24 +0100 Subject: [PATCH 0579/3170] Also add isProtected attribute in ldap_scheme --- data/other/ldap_scheme.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/data/other/ldap_scheme.yml b/data/other/ldap_scheme.yml index 660d6fbb5..620a36a1c 100644 --- a/data/other/ldap_scheme.yml +++ b/data/other/ldap_scheme.yml @@ -73,6 +73,7 @@ depends_children: - permissionYnh groupPermission: - "cn=all_users,ou=groups,dc=yunohost,dc=org" + isProtected: TRUE cn=xmpp.main,ou=permission: cn: xmpp.main gidNumber: "5002" @@ -81,3 +82,4 @@ depends_children: - permissionYnh groupPermission: - "cn=all_users,ou=groups,dc=yunohost,dc=org" + isProtected: TRUE From 57c65c9d411ee14a9faf8848e5b681dd0709f2ab Mon Sep 17 00:00:00 2001 From: ButterflyOfFire Date: Thu, 26 Dec 2019 20:34:46 +0000 Subject: [PATCH 0580/3170] Translated using Weblate (Arabic) Currently translated at 14.2% (86 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ar/ --- locales/ar.json | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/locales/ar.json b/locales/ar.json index fba086bc4..828f41a8f 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -211,8 +211,8 @@ "mail_domain_unknown": "Unknown mail address domain '{domain:s}'", "mail_forward_remove_failed": "Unable to remove mail forward '{mail:s}'", "mailbox_used_space_dovecot_down": "Dovecot mailbox service need to be up, if you want to get mailbox used space", - "main_domain_change_failed": "Unable to change the main domain", - "main_domain_changed": "The main domain has been changed", + "main_domain_change_failed": "تعذّر تغيير النطاق الأساسي", + "main_domain_changed": "تم تغيير النطاق الأساسي", "migrate_tsig_end": "Migration to hmac-sha512 finished", "migrate_tsig_failed": "Migrating the dyndns domain {domain} to hmac-sha512 failed, rolling back. Error: {error_code} - {error}", "migrate_tsig_start": "Not secure enough key algorithm detected for TSIG signature of domain '{domain}', initiating migration to the more secure one hmac-sha512", @@ -443,5 +443,19 @@ "dyndns_could_not_check_available": "لا يمكن التحقق مِن أنّ {domain:s} متوفر على {provider:s}.", "backup_mount_archive_for_restore": "جارٍ تهيئة النسخة الاحتياطية للاسترجاع…", "root_password_replaced_by_admin_password": "لقد تم استبدال كلمة سر الجذر root بالكلمة الإدارية لـ admin.", - "app_upgrade_stopped": "لقد تم إلغاء تحديث كافة التطبيقات لتجنب حادث بسبب فشل تحديث التطبيق السابق" + "app_upgrade_stopped": "لقد تم إلغاء تحديث كافة التطبيقات لتجنب حادث بسبب فشل تحديث التطبيق السابق", + "app_action_broke_system": "يبدو أنّ هذا الإجراء أدّى إلى تحطيم خدمات مهمّة: {services}", + "diagnosis_basesystem_host": "هذا الخادم يُشغّل ديبيان {debian_version}.", + "diagnosis_basesystem_kernel": "هذا الخادم يُشغّل نواة لينكس {kernel_version}", + "diagnosis_basesystem_ynh_single_version": "{0} الإصدار: {1} ({2})", + "diagnosis_basesystem_ynh_main_version": "هذا الخادم يُشغّل YunoHost {main_version} ({repo})", + "diagnosis_everything_ok": "كل شيء على ما يرام في {category}!", + "diagnosis_ip_connected_ipv4": "الخادم مُتّصل بالإنترنت عبر IPv4!", + "diagnosis_ip_connected_ipv6": "الخادم مُتّصل بالإنترنت عبر IPv6!", + "diagnosis_ip_not_connected_at_all": "يبدو أنّ الخادم غير مُتّصل بتاتا بالإنترنت!؟", + "app_install_failed": "لا يمكن تنصيب {app}: {error}", + "apps_already_up_to_date": "كافة التطبيقات مُحدّثة", + "app_remove_after_failed_install": "جارٍ حذف التطبيق بعدما فشل تنصيبها…", + "apps_catalog_updating": "جارٍ تحديث فهرس التطبيقات…", + "apps_catalog_update_success": "تم تحديث فهرس التطبيقات!" } From 414fe1926a480e1fbf3a3cc4ddcb0b47207a8ca9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Fri, 27 Dec 2019 16:22:57 +0100 Subject: [PATCH 0581/3170] Fix helper --- data/helpers.d/setting | 10 +++++----- src/yunohost/tests/test_permission.py | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index bb71ad1a3..73da89bb0 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -262,7 +262,7 @@ ynh_webpath_register () { # # Requires YunoHost version 3.7.0 or higher. ynh_permission_create() { - declare -Ar args_array=( [p]=permission= [u]=url= [a]=allowed= [p]=protected= ) + declare -Ar args_array=( [p]=permission= [u]=url= [a]=allowed= [P]=protected= ) local permission local url local allowed @@ -277,7 +277,7 @@ ynh_permission_create() { if [[ -n ${allowed:-} ]]; then allowed=",allowed=['${allowed//';'/"','"}']" fi - if [ -n ${protected} ]; then + if [[ -n ${protected:-} ]]; then if [ $protected == "true" ]; then protected=",protected=True" else @@ -353,7 +353,7 @@ ynh_permission_url() { # example: ynh_permission_update --permission admin --add samdoe --remove all_users # Requires YunoHost version 3.7.0 or higher. ynh_permission_update() { - declare -Ar args_array=( [p]=permission= [a]=add= [r]=remove= [p]=protected= ) + declare -Ar args_array=( [p]=permission= [a]=add= [r]=remove= [P]=protected= ) local permission local add local remove @@ -366,7 +366,7 @@ ynh_permission_update() { if [[ -n ${remove:-} ]]; then remove=",remove=['${remove//';'/"','"}']" fi - if [ -n ${protected} ]; then + if [[ -n ${protected:-} ]]; then if [ $protected == "true" ]; then protected=",protected=True" else @@ -374,5 +374,5 @@ ynh_permission_update() { fi fi - yunohost tools shell -c "from yunohost.permission import user_permission_update; user_permission_update('$app.$permission', ${add:-} ${remove} ${protected:-} , force=True, sync_perm=False)" + yunohost tools shell -c "from yunohost.permission import user_permission_update; user_permission_update('$app.$permission' ${add:-} ${remove:-} ${protected:-} , force=True, sync_perm=False)" } diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index 636d9b4b1..066d60d7d 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -33,8 +33,8 @@ def setup_function(function): user_create("alice", "Alice", "White", "alice@" + maindomain, dummy_password) user_create("bob", "Bob", "Snow", "bob@" + maindomain, dummy_password) - permission_create("wiki.main", url="/", allowed=["all_users"] , sync_perm=False) - permission_create("blog.main", allowed=["all_users"], sync_perm=False) + permission_create("wiki.main", url="/", allowed=["all_users"], protected=False, sync_perm=False) + permission_create("blog.main", allowed=["all_users"], protected=False, sync_perm=False) user_permission_update("blog.main", remove="all_users", add="alice") @@ -326,7 +326,7 @@ def test_permission_adding_visitors_implicitly_add_all_users(mocker): def test_permission_cant_remove_all_users_if_visitors_allowed(mocker): with message(mocker, "permission_updated", permission="blog.main"): - user_permission_update("blog.main", add=["visitors", "all_users"]) + user_permission_update("blog.main", add=["visitors", "all_users"], ) with raiseYunohostError(mocker, 'permission_cannot_remove_all_users_while_visitors_allowed'): user_permission_update("blog.main", remove="all_users") From 97c3a0fb9b2763cf297ea691c78d44f7c503b9ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Sat, 28 Dec 2019 22:29:52 +0100 Subject: [PATCH 0582/3170] Fix migration --- data/other/ldap_scheme.yml | 4 ++-- .../0015_add_permission_protection.py | 24 +++++++++++++------ 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/data/other/ldap_scheme.yml b/data/other/ldap_scheme.yml index 620a36a1c..769de0b2e 100644 --- a/data/other/ldap_scheme.yml +++ b/data/other/ldap_scheme.yml @@ -73,7 +73,7 @@ depends_children: - permissionYnh groupPermission: - "cn=all_users,ou=groups,dc=yunohost,dc=org" - isProtected: TRUE + isProtected: "TRUE" cn=xmpp.main,ou=permission: cn: xmpp.main gidNumber: "5002" @@ -82,4 +82,4 @@ depends_children: - permissionYnh groupPermission: - "cn=all_users,ou=groups,dc=yunohost,dc=org" - isProtected: TRUE + isProtected: "TRUE" diff --git a/src/yunohost/data_migrations/0015_add_permission_protection.py b/src/yunohost/data_migrations/0015_add_permission_protection.py index 1b2965fb0..5f6ccc936 100644 --- a/src/yunohost/data_migrations/0015_add_permission_protection.py +++ b/src/yunohost/data_migrations/0015_add_permission_protection.py @@ -24,14 +24,24 @@ class MyMigration(Migration): def run(self): from yunohost.utils.ldap import _get_ldap_interface + from yunohost.regenconf import regen_conf, BACKUP_CONF_DIR + + # Check if the migration can be processed + ldap_regen_conf_status = regen_conf(names=['slapd'], dry_run=True) + # By this we check if the have been customized + if ldap_regen_conf_status and ldap_regen_conf_status['slapd']['pending']: + logger.warning(m18n.n("migration_0011_slapd_config_will_be_overwritten", conf_backup_folder=BACKUP_CONF_DIR)) + + regen_conf(names=['slapd'], force=True) + ldap = _get_ldap_interface() - permission_list = user_permission_list(short=True) - + permission_list = user_permission_list(short=True)["permissions"] + for permission in permission_list: - if permission in SYSTEM_PERMS: - ldap.update('cn=%s,ou=permission' % permission, 'isProtected': "TRUE"}) - elif permission.end_with(".main"): - ldap.update('cn=%s,ou=permission' % permission, 'isProtected': "FALSE"}) + if permission.split('.')[0] in SYSTEM_PERMS: + ldap.update('cn=%s,ou=permission' % permission, {'isProtected': "TRUE"}) + elif permission.endswith(".main"): + ldap.update('cn=%s,ou=permission' % permission, {'isProtected': "FALSE"}) else: - ldap.update('cn=%s,ou=permission' % permission, 'isProtected': "TRUE"}) + ldap.update('cn=%s,ou=permission' % permission, {'isProtected': "TRUE"}) From 8d3d055484760d16a6a9589c41d6e7f4dc9781b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Sun, 29 Dec 2019 22:11:28 +0100 Subject: [PATCH 0583/3170] Fix permission update --- src/yunohost/permission.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 4e0507423..8275a96a0 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -166,7 +166,7 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, raise YunohostError('permission_cannot_remove_all_users_while_visitors_allowed') # Don't update LDAP if we update exactly the same values - if set(new_allowed_groups) == set(current_allowed_groups): + if set(new_allowed_groups) == set(current_allowed_groups) and protected is None: logger.warning(m18n.n("permission_already_up_to_date")) return existing_permission From b3b5d84f051886ec408ee289c58002edb73162e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Sun, 29 Dec 2019 22:11:41 +0100 Subject: [PATCH 0584/3170] Add tests for permission protection --- src/yunohost/tests/test_permission.py | 37 ++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index 066d60d7d..a24b5cfc6 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -3,7 +3,7 @@ import pytest from conftest import message, raiseYunohostError -from yunohost.app import app_install, app_remove, app_change_url, app_list, app_map, _installed_apps +from yunohost.app import app_install, app_upgrade, app_remove, app_change_url, app_list, app_map, _installed_apps from yunohost.user import user_list, user_create, user_delete, \ user_group_list, user_group_delete from yunohost.permission import user_permission_update, user_permission_list, user_permission_reset, \ @@ -35,6 +35,7 @@ def setup_function(function): user_create("bob", "Bob", "Snow", "bob@" + maindomain, dummy_password) permission_create("wiki.main", url="/", allowed=["all_users"], protected=False, sync_perm=False) permission_create("blog.main", allowed=["all_users"], protected=False, sync_perm=False) + permission_create("blog.api", allowed=["visitors"], protected=True, sync_perm=False) user_permission_update("blog.main", remove="all_users", add="alice") @@ -398,6 +399,23 @@ def test_permission_update_permission_that_doesnt_exist(mocker): user_permission_update("doesnt.exist", add="alice") +def test_permission_protected_update(mocker): + res = user_permission_list(full=True)['permissions'] + assert res['blog.api']['allowed'] == ["visitors", "all_users"] + + with raiseYunohostError(mocker, "permission_protected"): + user_permission_update("blog.api", remove="visitors") + + res = user_permission_list(full=True)['permissions'] + assert res['blog.api']['allowed'] == ["visitors", "all_users"] + + user_permission_update("blog.api", remove="visitors", force=True) + with raiseYunohostError(mocker, "permission_protected"): + user_permission_update("blog.api", add="visitors") + + res = user_permission_list(full=True)['permissions'] + assert res['blog.api']['allowed'] == ["all_users"] + # Permission url management def test_permission_redefine_url(): @@ -473,6 +491,23 @@ def test_permission_app_change_url(): assert res['permissions_app.dev']['url'] == "/dev" +def test_permission_protection_management_by_helper(): + app_install("./tests/apps/permissions_app_ynh", + args="domain=%s&path=%s&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True) + + res = user_permission_list(full=True)['permissions'] + assert res['permissions_app.main']['protected'] == False + assert res['permissions_app.admin']['protected'] == True + assert res['permissions_app.dev']['protected'] == False + + app_upgrade(["permissions_app"], file="./tests/apps/permissions_app_ynh") + + res = user_permission_list(full=True)['permissions'] + assert res['permissions_app.main']['protected'] == False + assert res['permissions_app.admin']['protected'] == False + assert res['permissions_app.dev']['protected'] == True + + def test_permission_app_propagation_on_ssowat(): app_install("./tests/apps/permissions_app_ynh", From ce33592996411b4ce972767120678773cc958040 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Sun, 29 Dec 2019 22:19:08 +0100 Subject: [PATCH 0585/3170] Remove bad comment --- .../data_migrations/0015_add_permission_protection.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/yunohost/data_migrations/0015_add_permission_protection.py b/src/yunohost/data_migrations/0015_add_permission_protection.py index 5f6ccc936..75a31def4 100644 --- a/src/yunohost/data_migrations/0015_add_permission_protection.py +++ b/src/yunohost/data_migrations/0015_add_permission_protection.py @@ -10,10 +10,6 @@ from yunohost.permission import user_permission_list, SYSTEM_PERMS logger = getActionLogger('yunohost.migration') -################################################### -# Tools used also for restoration -################################################### - class MyMigration(Migration): """ Add protected attribute in LDAP permission From 43073b77c5d5e9998c29a80a3596c7be6fe1a846 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Sun, 29 Dec 2019 22:22:51 +0100 Subject: [PATCH 0586/3170] Add more assert --- src/yunohost/tests/test_permission.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index a24b5cfc6..d4aa9f47f 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -410,6 +410,9 @@ def test_permission_protected_update(mocker): assert res['blog.api']['allowed'] == ["visitors", "all_users"] user_permission_update("blog.api", remove="visitors", force=True) + res = user_permission_list(full=True)['permissions'] + assert res['blog.api']['allowed'] == ["all_users"] + with raiseYunohostError(mocker, "permission_protected"): user_permission_update("blog.api", add="visitors") From dc39b9a5aff9fddcdda75254112818872ce43591 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Sun, 29 Dec 2019 22:24:21 +0100 Subject: [PATCH 0587/3170] Clean code --- src/yunohost/tests/test_permission.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index d4aa9f47f..4bafcdf32 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -327,7 +327,7 @@ def test_permission_adding_visitors_implicitly_add_all_users(mocker): def test_permission_cant_remove_all_users_if_visitors_allowed(mocker): with message(mocker, "permission_updated", permission="blog.main"): - user_permission_update("blog.main", add=["visitors", "all_users"], ) + user_permission_update("blog.main", add=["visitors", "all_users"]) with raiseYunohostError(mocker, 'permission_cannot_remove_all_users_while_visitors_allowed'): user_permission_update("blog.main", remove="all_users") From e2def749970481144b6a2e52f11c4f8df2e39998 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Mon, 30 Dec 2019 13:28:56 +0100 Subject: [PATCH 0588/3170] Improve check before permission update --- src/yunohost/permission.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 8275a96a0..4e7bd8a5b 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -166,7 +166,8 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, raise YunohostError('permission_cannot_remove_all_users_while_visitors_allowed') # Don't update LDAP if we update exactly the same values - if set(new_allowed_groups) == set(current_allowed_groups) and protected is None: + if set(new_allowed_groups) == set(current_allowed_groups) and \ + (protected is None or protected == existing_permission["protected"]): logger.warning(m18n.n("permission_already_up_to_date")) return existing_permission 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 0589/3170] 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 0590/3170] 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 882ed5700d8e563a5015169f0eaa2f367f80339e Mon Sep 17 00:00:00 2001 From: Alvaro Date: Tue, 24 Dec 2019 11:25:23 +0000 Subject: [PATCH 0591/3170] Translated using Weblate (Spanish) Currently translated at 89.1% (541 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/es/ --- locales/es.json | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/locales/es.json b/locales/es.json index d1daf16f7..729e262b5 100644 --- a/locales/es.json +++ b/locales/es.json @@ -27,7 +27,7 @@ "app_sources_fetch_failed": "No se pudieron obtener los archivos con el código fuente, ¿es el URL correcto?", "app_unknown": "Aplicación desconocida", "app_unsupported_remote_type": "Tipo remoto no soportado por la aplicación", - "app_upgrade_failed": "No se pudo actualizar {app:s}", + "app_upgrade_failed": "No se pudo actualizar {app:s}: {error}", "app_upgraded": "Actualizado {app:s}", "appslist_fetched": "Lista de aplicaciones «{appslist:s}» actualizada", "appslist_removed": "Eliminada la lista de aplicaciones «{appslist:s}»", @@ -658,5 +658,29 @@ "diagnosis_ignored_issues": "(+ {nb_ignored} problema(s) ignorado(s))", "diagnosis_found_errors": "¡Encontrado(s) error(es) significativo(s) {errors} relacionado(s) con {category}!", "diagnosis_found_warnings": "Encontrado elemento(s) {warnings} que puede(n) ser mejorado(s) para {category}.", - "diagnosis_everything_ok": "¡Todo se ve bien para {category}!" + "diagnosis_everything_ok": "¡Todo se ve bien para {category}!", + "app_upgrade_script_failed": "Ha ocurrido un error en el script de actualización de la app", + "diagnosis_no_cache": "Todavía no hay una caché de diagnóstico para la categoría '{category}'", + "diagnosis_ip_no_ipv4": "IPv4 en el servidor no está funcionando.", + "diagnosis_ip_not_connected_at_all": "¿¡Está conectado el servidor a internet!?", + "diagnosis_ip_broken_resolvconf": "DNS parece no funcionar en tu servidor, lo que parece estar relacionado con /etc/resolv.conf no apuntando a 127.0.0.1.", + "diagnosis_dns_missing_record": "Según la configuración DNS recomendada, deberías añadir un registro DNS de tipo {0}, nombre {1} y valor {2}. Puedes consultar https://yunohost.org/dns_config para más información.", + "diagnosis_diskusage_low": "El almacenamiento {mountpoint} (en dispositivo {device}) solo tiene {free_abs_GB} GB ({free_percent}%) de espacio disponible. Ten cuidado.", + "diagnosis_services_bad_status_tip": "Puedes intentar reiniciar el servicio, y si no funciona, echar un vistazo a los logs del servicio usando 'yunohost service log {0}' o a través de la sección 'Servicios' en webadmin.", + "diagnosis_ip_connected_ipv6": "¡El servidor está conectado a internet a través de IPv6!", + "diagnosis_ip_no_ipv6": "IPv6 en el servidor no está funcionando.", + "diagnosis_ip_dnsresolution_working": "¡DNS no está funcionando!", + "diagnosis_ip_broken_dnsresolution": "DNS parece que no funciona por alguna razón... ¿Hay algún firewall bloqueando peticiones DNS?", + "diagnosis_ip_weird_resolvconf": "Parece que DNS funciona, pero ten cuidado, porque estás utilizando /etc/resolv.conf modificado.", + "diagnosis_ip_weird_resolvconf_details": "En su lugar, este fichero debería ser un enlace simbólico a /etc/resolvconf/run/resolv.conf apuntando a 127.0.0.1 (dnsmasq). Los resolvedores reales deben configurarse a través de /etc/resolv.dnsmasq.conf.", + "diagnosis_dns_good_conf": "Buena configuración DNS para el dominio {domain} (categoría {category})", + "diagnosis_dns_bad_conf": "Mala configuración DNS o configuración DNS faltante para el dominio {domain} (categoría {category})", + "diagnosis_dns_discrepancy": "El registro DNS con tipo {0} y nombre {1} no se corresponde a la configuración recomendada. Valor actual: {2}. Valor esperado: {3}. Puedes consultar https://yunohost.org/dns_config para más información.", + "diagnosis_services_bad_status": "El servicio {service} está {status} :(", + "diagnosis_diskusage_verylow": "El almacenamiento {mountpoint} (en el dispositivo {device}) sólo tiene {free_abs_GB} GB ({free_percent}%) de espacio disponible. Deberías considerar la posibilidad de limpiar algo de espacio.", + "diagnosis_diskusage_ok": "¡El almacenamiento {mountpoint} (en el dispositivo {device}) todavía tiene {free_abs_GB} GB ({free_percent}%) de espacio libre!", + "diagnosis_services_conf_broken": "¡Mala configuración para el servicio {service}!", + "diagnosis_services_running": "¡El servicio {service} está en ejecución!", + "diagnosis_failed": "No se ha podido obtener el resultado del diagnóstico para la categoría '{category}': {error}", + "diagnosis_ip_connected_ipv4": "¡El servidor está conectado a internet a través de IPv4!" } From 70405bb6485f3b9fb2b6b695faa6957987e08bb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quent=C3=AD?= Date: Sat, 28 Dec 2019 17:45:54 +0000 Subject: [PATCH 0592/3170] Translated using Weblate (Occitan) Currently translated at 49.9% (303 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/oc/ --- locales/oc.json | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/locales/oc.json b/locales/oc.json index d3eb33e12..7979a22df 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -6,15 +6,15 @@ "app_already_up_to_date": "{app:s} es ja a jorn", "installation_complete": "Installacion acabada", "app_id_invalid": "ID d’aplicacion incorrècte", - "app_install_files_invalid": "Fichièrs d’installacion incorrèctes", + "app_install_files_invalid": "Installacion impossibla d’aquestes fichièrs", "app_no_upgrade": "Pas cap d’aplicacion d’actualizar", "app_not_correctly_installed": "{app:s} sembla pas ben installat", - "app_not_installed": "L’aplicacion {app:s} es pas installat. Vaquí la lista de las aplicacions installadas : {all_apps}", + "app_not_installed": "Impossible de trobar l’aplicacion {app:s}. Vaquí la lista de las aplicacions installadas : {all_apps}", "app_not_properly_removed": "{app:s} es pas estat corrèctament suprimit", - "app_removed": "{app:s} es estat suprimit", + "app_removed": "{app:s} es estada suprimida", "app_unknown": "Aplicacion desconeguda", "app_upgrade_app_name": "Actualizacion de l’aplicacion {app}…", - "app_upgrade_failed": "Impossible d’actualizar {app:s}", + "app_upgrade_failed": "Impossible d’actualizar {app:s} : {error}", "app_upgrade_some_app_failed": "D’aplicacions se pòdon pas actualizar", "app_upgraded": "{app:s} es estada actualizada", "appslist_fetched": "Recuperacion de la lista d’aplicacions {appslist:s} corrèctament realizada", @@ -36,11 +36,11 @@ "backup_action_required": "Devètz precisar çò que cal salvagardar", "backup_app_failed": "Impossible de salvagardar l’aplicacion « {app:s} »", "backup_applying_method_copy": "Còpia de totes los fichièrs dins la salvagarda…", - "backup_applying_method_tar": "Creacion de l’archiu tar de la salvagarda…", - "backup_archive_name_exists": "Un archiu de salvagarda amb aquesta nom existís ja", + "backup_applying_method_tar": "Creacion de l’archiu TAR de la salvagarda…", + "backup_archive_name_exists": "Un archiu de salvagarda amb aquesta nom existís ja.", "backup_archive_name_unknown": "L’archiu local de salvagarda apelat « {name:s} » es desconegut", "action_invalid": "Accion « {action:s} » incorrècta", - "app_argument_choice_invalid": "Causida invalida pel paramètre « {name:s} », cal que siá un de {choices:s}", + "app_argument_choice_invalid": "Utilizatz una de las opcions « {choices:s} » per l’argument « {name:s} »", "app_argument_invalid": "Causissètz una valor invalida pel paramètre « {name:s} » : {error:s}", "app_argument_required": "Lo paramètre « {name:s} » es requesit", "app_change_url_failed_nginx_reload": "Reaviada de NGINX impossibla. Vaquí la sortida de « nginx -t » :\n{nginx_errors:s}", @@ -50,14 +50,14 @@ "app_extraction_failed": "Extraccion dels fichièrs d’installacion impossibla", "app_incompatible": "L’aplicacion {app} es pas compatibla amb vòstra version de YunoHost", "app_location_already_used": "L’aplicacion « {app} » es ja installada dins ({path})", - "app_manifest_invalid": "Manifest d’aplicacion incorrècte : {error}", + "app_manifest_invalid": "I a quicòm que truca amb lo manifest de l’aplicacion : {error}", "app_package_need_update": "Lo paquet de l’aplicacion {app} deu èsser actualizat per poder seguir los cambiaments de YunoHost", "app_requirements_checking": "Verificacion dels paquets requesits per {app}…", "app_sources_fetch_failed": "Recuperacion dels fichièrs fonts impossibla, l’URL es corrècta ?", "app_unsupported_remote_type": "Lo tipe alonhat utilizat per l’aplicacion es pas suportat", "appslist_retrieve_error": "Impossible de recuperar la lista d’aplicacions alonhadas {appslist:s} : {error:s}", "backup_archive_app_not_found": "L’aplicacion « {app:s} » es pas estada trobada dins l’archiu de la salvagarda", - "backup_archive_broken_link": "Impossible d‘accedir a l’archiu de salvagarda (ligam invalid cap a {path:s})", + "backup_archive_broken_link": "Impossible d’accedir a l’archiu de salvagarda (ligam invalid cap a {path:s})", "backup_archive_mount_failed": "Lo montatge de l’archiu de salvagarda a fracassat", "backup_archive_open_failed": "Impossible de dobrir l’archiu de salvagarda", "backup_archive_system_part_not_available": "La part « {part:s} » del sistèma es pas disponibla dins aquesta salvagarda", @@ -65,7 +65,7 @@ "backup_copying_to_organize_the_archive": "Còpia de {size:s} Mio per organizar l’archiu", "backup_created": "Salvagarda acabada", "backup_creating_archive": "Creacion de l’archiu de salvagarda…", - "backup_creation_failed": "Impossible de crear la salvagarda", + "backup_creation_failed": "Creacion impossibla de l’archiu de salvagarda", "app_already_installed_cant_change_url": "Aquesta aplicacion es ja installada. Aquesta foncion pòt pas simplament cambiar l’URL. Agachatz « app changeurl » s’es disponible.", "app_change_no_change_url_script": "L’aplicacion {app_name:s} pren pas en compte lo cambiament d’URL, poiretz aver de l’actualizar.", "app_change_url_no_script": "L’aplicacion {app_name:s} pren pas en compte lo cambiament d’URL, benlèu que vos cal l’actualizar.", @@ -73,19 +73,19 @@ "app_location_install_failed": "Impossible d’installar l’aplicacion a aqueste emplaçament per causa de conflicte amb l’aplicacion {other_app} qu’es ja installada sus {other_path}", "app_location_unavailable": "Aquesta URL es pas disponibla o en conflicte amb una aplicacion existenta :\n{apps:s}", "appslist_corrupted_json": "Cargament impossible de la lista d’aplicacion. Sembla que {filename:s} siá gastat.", - "backup_delete_error": "Impossible de suprimir « {path:s} »", + "backup_delete_error": "Supression impossibla de « {path:s} »", "backup_deleted": "La salvagarda es estada suprimida", "backup_hook_unknown": "Script de salvagarda « {hook:s} » desconegut", - "backup_invalid_archive": "Archiu de salvagarda incorrècte", + "backup_invalid_archive": "Aquò es pas un archiu de salvagarda", "backup_method_borg_finished": "La salvagarda dins Borg es acabada", "backup_method_copy_finished": "La còpia de salvagarda es acabada", - "backup_method_tar_finished": "L’archiu tar de la salvagarda es estat creat", + "backup_method_tar_finished": "L’archiu TAR de la salvagarda es estat creat", "backup_output_directory_not_empty": "Lo dorsièr de sortida es pas void", "backup_output_directory_required": "Vos cal especificar un dorsièr de sortida per la salvagarda", "backup_running_app_script": "Lançament de l’escript de salvagarda de l’aplicacion « {app:s} »...", "backup_running_hooks": "Execucion dels scripts de salvagarda…", "backup_system_part_failed": "Impossible de salvagardar la part « {part:s} » del sistèma", - "app_requirements_failed": "Impossible de complir las condicions requesidas per {app} : {error}", + "app_requirements_failed": "Impossible de complir unas condicions requesidas per {app} : {error}", "app_requirements_unmeet": "Las condicions requesidas per {app} son pas complidas, lo paquet {pkgname} ({version}) deu èsser {spec}", "appslist_could_not_migrate": "Migracion de la lista impossibla {appslist:s} ! Impossible d’analizar l’URL… L’anciana tasca cron es estada servada dins {bkp_file:s}.", "backup_abstract_method": "Aqueste metòde de salvagarda es pas encara implementat", @@ -399,7 +399,7 @@ "network_check_smtp_ko": "Lo trafic de corrièl sortent (pòrt 25 SMTP) sembla blocat per vòstra ret", "network_check_smtp_ok": "Lo trafic de corrièl sortent (pòrt 25 SMTP) es pas blocat", "pattern_mailbox_quota": "Deu èsser una talha amb lo sufixe b/k/M/G/T o 0 per desactivar la quòta", - "backup_archive_writing_error": "Impossible d’ajustar los fichièrs a la salvagarda dins l’archiu comprimit", + "backup_archive_writing_error": "Impossible d’ajustar los fichièrs « {source:s} » a la salvagarda (nomenats dins l’archiu « {dest:s} »)dins l’archiu comprimit « {archive:s} »", "backup_cant_mount_uncompress_archive": "Impossible de montar en lectura sola lo repertòri de l’archiu descomprimit", "backup_no_uncompress_archive_dir": "Lo repertòri de l’archiu descomprimit existís pas", "pattern_username": "Deu èsser compausat solament de caractèrs alfanumerics en letras minusculas e de tirets basses", @@ -493,7 +493,7 @@ "service_conf_now_managed_by_yunohost": "Lo fichièr de configuracion « {conf} » es ara gerit per YunoHost.", "service_reloaded": "Lo servici « {servici:s} » es estat tornat cargar", "already_up_to_date": "I a pas res a far ! Tot es ja a jorn !", - "app_action_cannot_be_ran_because_required_services_down": "Aquesta aplicacion necessita unes servicis que son actualament encalats. Abans de contunhar deuriatz ensajar de reaviar los servicis seguents (e tanben cercar perque son tombats en pana) : {services}", + "app_action_cannot_be_ran_because_required_services_down": "Aquestas aplicacions necessitan d’èsser lançadas per poder executar aquesta accion : {services}. Abans de contunhar deuriatz ensajar de reaviar los servicis seguents (e tanben cercar perque son tombats en pana) : {services}", "confirm_app_install_warning": "Atencion : aquesta aplicacion fonciona mas non es pas ben integrada amb YunoHost. Unas foncionalitats coma l’autentificacion unica e la còpia de seguretat/restauracion pòdon èsser indisponiblas. volètz l’installar de totas manièras ? [{answers:s}] ", "confirm_app_install_danger": "ATENCION ! Aquesta aplicacion es encara experimentala (autrament dich, fonciona pas) e es possible que còpe lo sistèma ! Deuriatz PAS l’installar se non sabètz çò que fasètz. Volètz vertadièrament córrer aqueste risc ? [{answers:s}] ", "confirm_app_install_thirdparty": "ATENCION ! L’installacion d’aplicacions tèrças pòt comprometre l’integralitat e la seguretat del sistèma. Deuriatz PAS l’installar se non sabètz pas çò que fasètz. Volètz vertadièrament córrer aqueste risc ? [{answers:s}] ", @@ -705,5 +705,8 @@ "diagnosis_failed": "Recuperacion impossibla dels resultats del diagnostic per la categoria « {category} » : {error}", "diagnosis_ip_broken_dnsresolution": "La resolucion del nom de domeni es copada per una rason… Lo parafuòc bloca las requèstas DNS ?", "diagnosis_no_cache": "I a pas encara de diagnostic de cache per la categoria « {category} »", - "apps_catalog_init_success": "Sistèma de catalòg d’aplicacion iniciat !" + "apps_catalog_init_success": "Sistèma de catalòg d’aplicacion iniciat !", + "diagnosis_services_running": "Lo servici {service} es lançat !", + "diagnosis_services_conf_broken": "La configuracion es copada pel servici {service} !", + "diagnosis_ports_needed_by": "Es necessari qu’aqueste pòrt siá accessible pel servici {0}" } From 39563448efefb3bec76e8d53ce5f5ee640762c56 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Mon, 30 Dec 2019 14:01:52 +0000 Subject: [PATCH 0593/3170] Translated using Weblate (Greek) Currently translated at 0.2% (1 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/el/ --- locales/el.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/locales/el.json b/locales/el.json index 0967ef424..615dfdd48 100644 --- a/locales/el.json +++ b/locales/el.json @@ -1 +1,3 @@ -{} +{ + "password_too_simple_1": "Ο κωδικός πρόσβασης πρέπει να έχει μήκος τουλάχιστον 8 χαρακτήρων" +} From c3f2e97e8d1f735000aee9edc5530ad1777f988e Mon Sep 17 00:00:00 2001 From: amirale qt Date: Mon, 30 Dec 2019 14:15:22 +0000 Subject: [PATCH 0594/3170] Translated using Weblate (Bengali (Bangladesh)) Currently translated at 0.2% (1 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/bn_BD/ --- locales/bn_BD.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/locales/bn_BD.json b/locales/bn_BD.json index 0967ef424..b5425128d 100644 --- a/locales/bn_BD.json +++ b/locales/bn_BD.json @@ -1 +1,3 @@ -{} +{ + "password_too_simple_1": "পাসওয়ার্ডটি কমপক্ষে 8 টি অক্ষরের দীর্ঘ হওয়া দরকার" +} From d35dcbe968562b8a33dbd017fab1a6bcc3c7c1eb Mon Sep 17 00:00:00 2001 From: yalh76 Date: Wed, 8 Jan 2020 19:57:32 +0100 Subject: [PATCH 0595/3170] adaptation for various actions --- data/helpers.d/systemd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/systemd b/data/helpers.d/systemd index 960382f8f..47e905f0f 100644 --- a/data/helpers.d/systemd +++ b/data/helpers.d/systemd @@ -135,7 +135,7 @@ ynh_systemd_action() { # Read the log until the sentence is found, that means the app finished to start. Or run until the timeout if grep --quiet "$line_match" "$templog" then - ynh_print_info --message="The service $service_name has correctly started." + ynh_print_info --message="The service $service_name has correctly executed the action ${action}." break fi if [ $i -eq 3 ]; then @@ -151,7 +151,7 @@ ynh_systemd_action() { fi if [ $i -eq $timeout ] then - ynh_print_warn --message="The service $service_name didn't fully started before the timeout." + ynh_print_warn --message="The service $service_name didn't fully executed the action ${action} before the timeout." ynh_print_warn --message="Please find here an extract of the end of the log of the service $service_name:" journalctl --no-pager --lines=$length -u $service_name >&2 test -e "$log_path" && echo "--" >&2 && tail --lines=$length "$log_path" >&2 From 133ae3a22e12407ab55c2923306ffb80d0382c37 Mon Sep 17 00:00:00 2001 From: Mario Date: Tue, 31 Dec 2019 15:29:37 +0000 Subject: [PATCH 0596/3170] Translated using Weblate (Italian) Currently translated at 19.4% (118 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/it/ --- locales/it.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/it.json b/locales/it.json index fc2355e7f..deaed8d8f 100644 --- a/locales/it.json +++ b/locales/it.json @@ -435,5 +435,6 @@ "migration_0003_not_jessie": "La distribuzione attuale non è Jessie!", "migration_0003_system_not_fully_up_to_date": "Il tuo sistema non è completamente aggiornato. Per favore prima esegui un aggiornamento normale prima di migrare a stretch.", "this_action_broke_dpkg": "Questa azione ha danneggiato dpkg/apt (i gestori di pacchetti del sistema)… Puoi provare a risolvere questo problema connettendoti via SSH ed eseguendo `sudo dpkg --configure -a`.", - "updating_app_lists": "Recupero degli aggiornamenti disponibili per le applicazioni…" + "updating_app_lists": "Recupero degli aggiornamenti disponibili per le applicazioni…", + "app_action_broke_system": "Questa azione sembra avere roto servizi importanti: {services}" } From 684e8cf1ff5580acb83a6600c39bc2b1259104d6 Mon Sep 17 00:00:00 2001 From: Jeroen Franssen Date: Fri, 10 Jan 2020 15:54:03 +0000 Subject: [PATCH 0597/3170] Translated using Weblate (Dutch) Currently translated at 7.4% (45 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/nl/ --- locales/nl.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/locales/nl.json b/locales/nl.json index df554f7e2..832ca4ea2 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -1,7 +1,7 @@ { "action_invalid": "Ongeldige actie '{action:s}'", "admin_password": "Administrator wachtwoord", - "admin_password_changed": "Het administratie wachtwoord is gewijzigd", + "admin_password_changed": "Het administratie wachtwoord werd gewijzigd", "app_already_installed": "{app:s} is al geïnstalleerd", "app_argument_invalid": "'{name:s}' bevat ongeldige waarde: {error:s}", "app_argument_required": "Het '{name:s}' moet ingevuld worden", @@ -139,5 +139,9 @@ "backup_extracting_archive": "Backup archief uitpakken...", "backup_hook_unknown": "backup hook '{hook:s}' onbekend", "backup_nothings_done": "Niets om op te slaan", - "password_too_simple_1": "Het wachtwoord moet minimaal 8 tekens lang zijn" + "password_too_simple_1": "Het wachtwoord moet minimaal 8 tekens lang zijn", + "already_up_to_date": "Er is niets te doen, alles is al up-to-date.", + "admin_password_too_long": "Gelieve een wachtwoord te kiezen met minder dan 127 karakters", + "app_action_cannot_be_ran_because_required_services_down": "De volgende diensten moeten actief zijn om deze actie uit te voeren: {services}. Probeer om deze te herstarten om verder te gaan (en om eventueel te onderzoeken waarom ze niet werken).", + "aborting": "Annulatie." } From b968dff2ee469f966922cdabfbb98b7c80d9ad5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quent=C3=AD?= Date: Thu, 2 Jan 2020 11:24:20 +0000 Subject: [PATCH 0598/3170] Translated using Weblate (Occitan) Currently translated at 70.5% (428 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/oc/ --- locales/oc.json | 134 ++++++++++++++++++++++++++---------------------- 1 file changed, 73 insertions(+), 61 deletions(-) diff --git a/locales/oc.json b/locales/oc.json index 7979a22df..4251a3307 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -80,7 +80,7 @@ "backup_method_borg_finished": "La salvagarda dins Borg es acabada", "backup_method_copy_finished": "La còpia de salvagarda es acabada", "backup_method_tar_finished": "L’archiu TAR de la salvagarda es estat creat", - "backup_output_directory_not_empty": "Lo dorsièr de sortida es pas void", + "backup_output_directory_not_empty": "Devètz causir un dorsièr de sortida void", "backup_output_directory_required": "Vos cal especificar un dorsièr de sortida per la salvagarda", "backup_running_app_script": "Lançament de l’escript de salvagarda de l’aplicacion « {app:s} »...", "backup_running_hooks": "Execucion dels scripts de salvagarda…", @@ -116,23 +116,23 @@ "backup_applying_method_borg": "Mandadís de totes los fichièrs a la salvagarda dins lo repertòri borg-backup…", "backup_csv_creation_failed": "Creacion impossibla del fichièr CSV necessari a las operacions futuras de restauracion", "backup_extracting_archive": "Extraccion de l’archiu de salvagarda…", - "backup_output_symlink_dir_broken": "Avètz un ligam simbolic copat allòc de vòstre repertòri d’archiu « {path:s} ». Poiriatz aver una configuracion personalizada per salvagardar vòstras donadas sus un autre sistèma de fichièrs, en aquel cas, saique oblidèretz de montar o de connectar lo disc o la clau USB.", + "backup_output_symlink_dir_broken": "Vòstre repertòri d’archiu « {path:s} » es un ligam simbolic copat. Saique oblidèretz de re/montar o de connectar supòrt.", "backup_with_no_backup_script_for_app": "L’aplicacion {app:s} a pas cap de script de salvagarda. I fasèm pas cas.", "backup_with_no_restore_script_for_app": "L’aplicacion {app:s} a pas cap de script de restauracion, poiretz pas restaurar automaticament la salvagarda d’aquesta aplicacion.", - "certmanager_acme_not_configured_for_domain": "Lo certificat del domeni {domain:s} sembla pas corrèctament installat. Mercés de lançar d’en primièr cert-install per aqueste domeni.", + "certmanager_acme_not_configured_for_domain": "Lo certificat pel domeni {domain:s} sembla pas corrèctament installat. Mercés de lançar d’en primièr « cert-install » per aqueste domeni.", "certmanager_attempt_to_renew_nonLE_cert": "Lo certificat pel domeni {domain:s} es pas provesit per Let’s Encrypt. Impossible de lo renovar automaticament !", "certmanager_attempt_to_renew_valid_cert": "Lo certificat pel domeni {domain:s} es a man d’expirar ! (Podètz utilizar --force se sabètz çò que fasètz)", "certmanager_cannot_read_cert": "Quicòm a trucat en ensajar de dobrir lo certificat actual pel domeni {domain:s} (fichièr : {file:s}), rason : {reason:s}", - "certmanager_cert_install_success": "Installacion capitada del certificat Let’s Encrypt pel domeni {domain:s} !", - "certmanager_cert_install_success_selfsigned": "Installacion capitada del certificat auto-signat pel domeni {domain:s} !", - "certmanager_cert_signing_failed": "Fracàs de la signatura del nòu certificat", - "certmanager_domain_cert_not_selfsigned": "Lo certificat del domeni {domain:s} es pas auto-signat. Volètz vertadièrament lo remplaçar ? (Utiliatz --force)", - "certmanager_domain_dns_ip_differs_from_public_ip": "L’enregistrament DNS « A » del domeni {domain:s} es diferent de l’adreça IP d’aqueste servidor. Se fa pauc qu’avètz modificat l’enregistrament « A », mercés d’esperar l’espandiment (qualques verificadors d’espandiment son disponibles en linha). (Se sabètz çò que fasèm, utilizatz --no-checks per desactivar aqueles contraròtles)", - "certmanager_domain_http_not_working": "Sembla que lo domeni {domain:s} es pas accessible via HTTP. Mercés de verificar que las configuracions DNS e nginx son corrèctas", - "certmanager_domain_unknown": "Domeni desconegut {domain:s}", + "certmanager_cert_install_success": "Lo certificat Let’s Encrypt es ara installat pel domeni « {domain:s} »", + "certmanager_cert_install_success_selfsigned": "Lo certificat auto-signat es ara installat pel domeni « {domain:s} »", + "certmanager_cert_signing_failed": "Signatura impossibla del nòu certificat", + "certmanager_domain_cert_not_selfsigned": "Lo certificat pel domeni {domain:s} es pas auto-signat. Volètz vertadièrament lo remplaçar ? (Utilizatz « --force » per o far)", + "certmanager_domain_dns_ip_differs_from_public_ip": "L’enregistrament DNS « A » pel domeni {domain:s} es diferent de l’adreça IP d’aqueste servidor. Se fa pauc qu’avètz modificat l’enregistrament « A », mercés d’esperar l’espandiment (qualques verificadors d’espandiment son disponibles en linha). (Se sabètz çò que fasèm, utilizatz --no-checks per desactivar aqueles contraròtles)", + "certmanager_domain_http_not_working": "Sembla que lo domeni {domain:s} es pas accessible via HTTP. Mercés de verificar que las configuracions DNS e NGINK son corrèctas", + "certmanager_domain_unknown": "Domeni desconegut « {domain:s} »", "certmanager_no_cert_file": "Lectura impossibla del fichièr del certificat pel domeni {domain:s} (fichièr : {file:s})", - "certmanager_self_ca_conf_file_not_found": "Lo fichièr de configuracion per l’autoritat del certificat auto-signat es introbabla (fichièr : {file:s})", - "certmanager_unable_to_parse_self_CA_name": "Analisi impossible lo nom de l’autoritat del certificat auto-signat (fichièr : {file:s})", + "certmanager_self_ca_conf_file_not_found": "Impossible de trobar lo fichièr de configuracion per l’autoritat del certificat auto-signat (fichièr : {file:s})", + "certmanager_unable_to_parse_self_CA_name": "Analisi impossibla del nom de l’autoritat del certificat auto-signat (fichièr : {file:s})", "custom_app_url_required": "Cal que donetz una URL per actualizar vòstra aplicacion personalizada {app:s}", "custom_appslist_name_required": "Cal que nomenetz vòstra lista d’aplicacions personalizadas", "diagnosis_debian_version_error": "Impossible de determinar la version de Debian : {error}", @@ -141,10 +141,10 @@ "dnsmasq_isnt_installed": "dnsmasq sembla pas èsser installat, mercés de lançar « apt-get remove bind9 && apt-get install dnsmasq »", "domain_cannot_remove_main": "Impossible de levar lo domeni màger. Definissètz un novèl domeni màger d’en primièr", "domain_cert_gen_failed": "Generacion del certificat impossibla", - "domain_created": "Lo domeni es creat", - "domain_creation_failed": "Creacion del certificat impossibla", - "domain_deleted": "Lo domeni es suprimit", - "domain_deletion_failed": "Supression impossibla del domeni", + "domain_created": "Domeni creat", + "domain_creation_failed": "Creacion del domeni {domain}: impossibla", + "domain_deleted": "Domeni suprimit", + "domain_deletion_failed": "Supression impossibla del domeni {domini}: {error}", "domain_dyndns_invalid": "Domeni incorrècte per una utilizacion amb DynDNS", "domain_dyndns_root_unknown": "Domeni DynDNS màger desconegut", "domain_exists": "Lo domeni existís ja", @@ -156,33 +156,33 @@ "done": "Acabat", "downloading": "Telecargament…", "dyndns_could_not_check_provide": "Impossible de verificar se {provider:s} pòt provesir {domain:s}.", - "dyndns_cron_installed": "La tasca cron pel domeni DynDNS es installada", - "dyndns_cron_remove_failed": "Impossible de levar la tasca cron pel domeni DynDNS a causa de {error}", - "dyndns_cron_removed": "La tasca cron pel domeni DynDNS es levada", + "dyndns_cron_installed": "Tasca cron pel domeni DynDNS creada", + "dyndns_cron_remove_failed": "Impossible de levar la tasca cron pel domeni DynDNS : {error}", + "dyndns_cron_removed": "Tasca cron pel domeni DynDNS levada", "dyndns_ip_update_failed": "Impossible d’actualizar l’adreça IP sul domeni DynDNS", - "dyndns_ip_updated": "Vòstra adreça IP es estada actualizada pel domeni DynDNS", - "dyndns_key_generating": "La clau DNS es a se generar, pòt trigar una estona…", + "dyndns_ip_updated": "Vòstra adreça IP actualizada pel domeni DynDNS", + "dyndns_key_generating": "La clau DNS es a se generar… pòt trigar una estona.", "dyndns_key_not_found": "Clau DNS introbabla pel domeni", "dyndns_no_domain_registered": "Cap de domeni pas enregistrat amb DynDNS", - "dyndns_registered": "Lo domeni DynDNS es enregistrat", - "dyndns_registration_failed": "Enregistrament del domeni DynDNS impossibla : {error:s}", + "dyndns_registered": "Domeni DynDNS enregistrat", + "dyndns_registration_failed": "Enregistrament del domeni DynDNS impossible : {error:s}", "dyndns_domain_not_provided": "Lo provesidor DynDNS {provider:s} pòt pas fornir lo domeni {domain:s}.", "dyndns_unavailable": "Lo domeni {domain:s} es pas disponible.", "extracting": "Extraccion…", "field_invalid": "Camp incorrècte : « {:s} »", "format_datetime_short": "%d/%m/%Y %H:%M", "global_settings_cant_open_settings": "Fracàs de la dobertura del fichièr de configuracion, rason : {reason:s}", - "global_settings_key_doesnt_exists": "La clau « {settings_key:s} » existís pas dins las configuracions globalas, podètz veire totas las claus disponiblas en picant « yunohost settings list »", - "global_settings_reset_success": "Capitada ! Vòstra configuracion precedenta es estada salvagarda dins {path:s}", + "global_settings_key_doesnt_exists": "La clau « {settings_key:s} » existís pas dins las configuracions globalas, podètz veire totas las claus disponiblas en executant « yunohost settings list »", + "global_settings_reset_success": "Configuracion precedenta ara salvagarda dins {path:s}", "global_settings_setting_example_bool": "Exemple d’opcion booleana", "global_settings_unknown_setting_from_settings_file": "Clau desconeguda dins los paramètres : {setting_key:s}, apartada e salvagardada dins /etc/yunohost/settings-unknown.json", - "installation_failed": "Fracàs de l’installacion", + "installation_failed": "Quicòm a trucat e l’installacion a pas reüssit", "invalid_url_format": "Format d’URL pas valid", "ldap_initialized": "L’annuari LDAP es inicializat", "license_undefined": "indefinida", "main_domain_change_failed": "Modificacion impossibla del domeni màger", "main_domain_changed": "Lo domeni màger es estat modificat", - "migrate_tsig_end": "La migracion cap a hmac-sha512 es acabada", + "migrate_tsig_end": "La migracion cap a HMAC-SHA512 es acabada", "migrate_tsig_wait_2": "2 minutas…", "migrate_tsig_wait_3": "1 minuta…", "migrate_tsig_wait_4": "30 segondas…", @@ -192,7 +192,7 @@ "migration_0003_start": "Aviada de la migracion cap a Stretech. Los jornals seràn disponibles dins {logfile}.", "migration_0003_patching_sources_list": "Petaçatge de sources.lists…", "migration_0003_main_upgrade": "Aviada de la mesa a nivèl màger…", - "migration_0003_fail2ban_upgrade": "Aviada de la mesa a nivèl de fail2ban…", + "migration_0003_fail2ban_upgrade": "Aviada de la mesa a nivèl de Fail2Ban…", "migration_0003_not_jessie": "La distribucion Debian actuala es pas Jessie !", "migrations_cant_reach_migration_file": "Impossible d’accedir als fichièrs de migracion amb lo camin %s", "migrations_current_target": "La cibla de migracion est {}", @@ -228,13 +228,13 @@ "port_unavailable": "Lo pòrt {port:d} es pas disponible", "restore_already_installed_app": "Una aplicacion es ja installada amb l’id « {app:s} »", "restore_app_failed": "Impossible de restaurar l’aplicacion « {app:s} »", - "backup_ask_for_copying_if_needed": "D’unes fichièrs an pas pogut èsser preparatz per la salvagarda en utilizar lo metòde qu’evita de gastar d’espaci sul sistèma de manièra temporària. Per lançar la salvagarda, cal utilizar temporàriament {size:s} Mo. Acceptatz ?", + "backup_ask_for_copying_if_needed": "Volètz far una salvagarda en utilizant {size:s} Mo temporàriament ? (Aqueste biais de far es emplegat perque unes fichièrs an pas pogut èsser preparats amb un metòde mai eficaç.)", "yunohost_not_installed": "YunoHost es pas installat o corrèctament installat. Mercés d’executar « yunohost tools postinstall »", - "backup_output_directory_forbidden": "Repertòri de destinacion defendut. Las salvagardas pòdon pas se realizar dins los repertòris bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var ou /home/yunohost.backup/archives", + "backup_output_directory_forbidden": "Causissètz un repertòri de destinacion deferent. Las salvagardas pòdon pas se realizar dins los repertòris bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var ou /home/yunohost.backup/archives", "certmanager_attempt_to_replace_valid_cert": "Sètz a remplaçar un certificat corrècte e valid pel domeni {domain:s} ! (Utilizatz --force per cortcircuitar)", - "certmanager_cert_renew_success": "Renovèlament capitat d’un certificat Let’s Encrypt pel domeni {domain:s} !", - "certmanager_certificate_fetching_or_enabling_failed": "Sembla d’aver fracassat l’activacion d’un nòu certificat per {domain:s}…", - "certmanager_conflicting_nginx_file": "Impossible de preparar lo domeni pel desfís ACME : lo fichièr de configuracion nginx {filepath:s} es en conflicte e deu èsser levat d’en primièr", + "certmanager_cert_renew_success": "Renovèlament capitat d’un certificat Let’s Encrypt pel domeni « {domain:s} »", + "certmanager_certificate_fetching_or_enabling_failed": "Sembla qu’utilizar lo nòu certificat per {domain:s} fonciona pas…", + "certmanager_conflicting_nginx_file": "Impossible de preparar lo domeni pel desfís ACME : lo fichièr de configuracion NGINX {filepath:s} es en conflicte e deu èsser levat d’en primièr", "certmanager_couldnt_fetch_intermediate_cert": "Expiracion del relambi pendent l’ensag de recuperacion del certificat intermediari dins de Let’s Encrypt. L’installacion / lo renovèlament es estat interromput - tornatz ensajar mai tard.", "certmanager_domain_not_resolved_locally": "Lo domeni {domain:s} pòt pas èsser determinat dins de vòstre servidor YunoHost. Pòt arribar s’avètz recentament modificat vòstre enregistrament DNS. Dins aqueste cas, mercés d’esperar unas oras per l’espandiment. Se lo problèma dura, consideratz ajustar {domain:s} a /etc/hosts. (Se sabètz çò que fasètz, utilizatz --no-checks per desactivar las verificacions.)", "certmanager_error_no_A_record": "Cap d’enregistrament DNS « A » pas trobat per {domain:s}. Vos cal indicar que lo nom de domeni mene a vòstra maquina per poder installar un certificat Let’S Encrypt ! (Se sabètz çò que fasètz, utilizatz --no-checks per desactivar las verificacions.)", @@ -245,25 +245,25 @@ "domain_dyndns_dynette_is_unreachable": "Impossible de contactar la dynette YunoHost, siá YunoHost pas es pas corrèctament connectat a Internet, siá lo servidor de la dynett es arrestat. Error : {error}", "domain_uninstall_app_first": "Una o mantuna aplicacions son installadas sus aqueste domeni. Mercés de las desinstallar d’en primièr abans de suprimir aqueste domeni", "firewall_reload_failed": "Impossible de recargar lo parafuòc", - "firewall_reloaded": "Lo parafuòc es estat recargat", + "firewall_reloaded": "Parafuòc recargat", "firewall_rules_cmd_failed": "Unas règlas del parafuòc an fracassat. Per mai informacions, consultatz lo jornal.", "global_settings_bad_choice_for_enum": "La valor del paramètre {setting:s} es incorrècta. Recebut : {received_type:s}, mas las opcions esperadas son : {expected_type:s}", - "global_settings_bad_type_for_setting": "Lo tipe del paramètre {setting:s} es incorrècte. Recebut : {received_type:s}, esperat {expected_type:s}", + "global_settings_bad_type_for_setting": "Lo tipe del paramètre {setting:s} es incorrècte, recebut : {received_type:s}, esperat {expected_type:s}", "global_settings_cant_write_settings": "Fracàs de l’escritura del fichièr de configuracion, rason : {reason:s}", "global_settings_setting_example_enum": "Exemple d’opcion de tipe enumeracion", "global_settings_setting_example_int": "Exemple d’opcion de tipe entièr", "global_settings_setting_example_string": "Exemple d’opcion de tipe cadena", "global_settings_unknown_type": "Situacion inesperada, la configuracion {setting:s} sembla d’aver lo tipe {unknown_type:s} mas es pas un tipe pres en carga pel sistèma.", - "hook_exec_failed": "Fracàs de l’execucion del script « {path:s} »", - "hook_exec_not_terminated": "L’execucion del escript « {path:s} » es pas acabada", + "hook_exec_failed": "Fracàs de l’execucion del script : « {path:s} »", + "hook_exec_not_terminated": "Lo escript « {path:s} » a pas acabat corrèctament", "hook_list_by_invalid": "La proprietat de tria de las accions es invalida", "hook_name_unknown": "Nom de script « {name:s} » desconegut", "ldap_init_failed_to_create_admin": "L’inicializacion de LDAP a pas pogut crear l’utilizaire admin", "mail_domain_unknown": "Lo domeni de corrièl « {domain:s} » es desconegut", "mailbox_used_space_dovecot_down": "Lo servici corrièl Dovecot deu èsser aviat, se volètz conéisser l’espaci ocupat per la messatjariá", - "migrate_tsig_failed": "La migracion del domeni dyndns {domain} cap a hmac-sha512 a pas capitat, anullacion de las modificacions. Error : {error_code} - {error}", - "migrate_tsig_wait": "Esperem 3 minutas que lo servidor dyndns prenga en compte la novèla clau…", - "migrate_tsig_not_needed": "Sembla qu’utilizatz pas un domeni dyndns, donc cap de migracion es pas necessària !", + "migrate_tsig_failed": "La migracion del domeni DynDNS {domain} cap a HMAC-SHA512 a pas capitat, anullacion de las modificacions. Error : {error_code} - {error}", + "migrate_tsig_wait": "Esperem 3 minutas que lo servidor DynDNS prenga en compte la novèla clau…", + "migrate_tsig_not_needed": "Sembla qu’utilizatz pas un domeni DynDNS, donc cap de migracion es pas necessària.", "migration_0003_yunohost_upgrade": "Aviada de la mesa a nivèl del paquet YunoHost… La migracion acabarà, mas l’actualizacion reala se realizarà tot bèl aprèp. Un còp acabada, poiretz vos reconnectar a l’administracion web.", "migration_0003_system_not_fully_up_to_date": "Lo sistèma es pas complètament a jorn. Mercés de lançar una mesa a jorn classica abans de començar la migracion per Stretch.", "migration_0003_modified_files": "Mercés de notar que los fichièrs seguents son estats detectats coma modificats manualament e poiràn èsser escafats a la fin de la mesa a nivèl : {manually_modified_files}", @@ -369,7 +369,7 @@ "update_cache_failed": "Impossible d’actualizar lo cache de l’APT", "mail_alias_remove_failed": "Supression impossibla de l’alias de corrièl « {mail:s} »", "mail_forward_remove_failed": "Supression impossibla del corrièl de transferiment « {mail:s} »", - "migrate_tsig_start": "L’algorisme de generacion de claus es pas pro securizat per la signatura TSIG del domeni « {domain} », lançament de la migracion cap a hmac-sha512 que’s mai securizat", + "migrate_tsig_start": "L’algorisme de generacion de claus es pas pro securizat per la signatura TSIG del domeni « {domain} », lançament de la migracion cap a un mai segur HMAC-SHA-512", "migration_description_0001_change_cert_group_to_sslcert": "Càmbia las permissions de grop dels certificats de « metronome » per « ssl-cert »", "migration_0003_restoring_origin_nginx_conf": "Vòstre fichièr /etc/nginx/nginx.conf es estat modificat manualament. La migracion reïnicializarà d’en primièr son estat origina… Lo fichièr precedent serà disponible coma {backup_dest}.", "migration_0003_still_on_jessie_after_main_upgrade": "Quicòm a trucat pendent la mesa a nivèl màger : lo sistèma es encara jos Jessie ?!? Per trobar lo problèma, agachatz {log}…", @@ -404,11 +404,11 @@ "backup_no_uncompress_archive_dir": "Lo repertòri de l’archiu descomprimit existís pas", "pattern_username": "Deu èsser compausat solament de caractèrs alfanumerics en letras minusculas e de tirets basses", "experimental_feature": "Atencion : aquesta foncionalitat es experimentala e deu pas èsser considerada coma establa, deuriatz pas l’utilizar levat que sapiatz çò que fasètz.", - "log_corrupted_md_file": "Lo fichièr yaml de metadonada amb los jornals d’audit es corromput : « {md_file} »\nError : {error:s}", + "log_corrupted_md_file": "Lo fichièr YAML de metadonadas ligat als jornals d’audit es damatjat : « {md_file} »\nError : {error:s}", "log_category_404": "La categoria de jornals d’audit « {category} » existís pas", "log_link_to_log": "Jornal complèt d’aquesta operacion : {desc}", "log_help_to_get_log": "Per veire lo jornal d’aquesta operacion « {desc} », utilizatz la comanda « yunohost log display {name} »", - "backup_php5_to_php7_migration_may_fail": "Impossible de convertir vòstre archiu per prendre en carga PHP 7, la restauracion de vòstras aplicacions PHP pòt reüssir pas (rason : {error:s})", + "backup_php5_to_php7_migration_may_fail": "Impossible de convertir vòstre archiu per prendre en carga PHP 7, la restauracion de vòstras aplicacions PHP pòt reüssir pas a restaurar vòstras aplicacions PHP (rason : {error:s})", "log_link_to_failed_log": "L’operacion « {desc} » a pas capitat ! Per obténer d’ajuda, mercés de fornir lo jornal complèt de l’operacion", "log_help_to_get_failed_log": "L’operacion « {desc} » a pas reüssit ! Per obténer d’ajuda, mercés de partejar lo jornal d’audit complèt d’aquesta operacion en utilizant la comanda « yunohost log display {name} --share »", "log_does_exists": "I a pas cap de jornal d’audit per l’operacion amb lo nom « {log} », utilizatz « yunohost log list » per veire totes los jornals d’operacion disponibles", @@ -421,7 +421,7 @@ "log_app_change_url": "Cambiar l’URL de l’aplicacion « {} »", "log_app_install": "Installar l’aplicacion « {} »", "log_app_remove": "Levar l’aplicacion « {} »", - "log_app_upgrade": "Actualizacion de l’aplicacion « {} »", + "log_app_upgrade": "Actualizar l’aplicacion « {} »", "log_app_makedefault": "Far venir « {} » l’aplicacion per defaut", "log_available_on_yunopaste": "Lo jornal es ara disponible via {url}", "log_backup_restore_system": "Restaurar lo sistèma a partir d’una salvagarda", @@ -432,14 +432,14 @@ "log_domain_remove": "Tirar lo domeni « {} » d’a la configuracion sistèma", "log_dyndns_subscribe": "S’abonar al subdomeni YunoHost « {} »", "log_dyndns_update": "Actualizar l’adreça IP ligada a vòstre jos-domeni YunoHost « {} »", - "log_letsencrypt_cert_install": "Installar lo certificat Let's encrypt sul domeni « {} »", + "log_letsencrypt_cert_install": "Installar un certificat Let's Encrypt sul domeni « {} »", "log_selfsigned_cert_install": "Installar lo certificat auto-signat sul domeni « {} »", - "log_letsencrypt_cert_renew": "Renovar lo certificat Let's encrypt de « {} »", + "log_letsencrypt_cert_renew": "Renovar lo certificat Let's Encrypt de « {} »", "log_service_enable": "Activar lo servici « {} »", "log_service_regen_conf": "Regenerar la configuracion sistèma de « {} »", "log_user_create": "Ajustar l’utilizaire « {} »", "log_user_delete": "Levar l’utilizaire « {} »", - "log_user_update": "Actualizar las informacions a l’utilizaire « {} »", + "log_user_update": "Actualizar las informacions de l’utilizaire « {} »", "log_domain_main_domain": "Far venir « {} » lo domeni màger", "log_tools_migrations_migrate_forward": "Migrar", "log_tools_migrations_migrate_backward": "Tornar en arrièr", @@ -449,8 +449,8 @@ "log_tools_reboot": "Reaviar lo servidor", "mail_unavailable": "Aquesta adreça electronica es reservada e deu èsser automaticament atribuida al tot bèl just primièr utilizaire", "migration_description_0004_php5_to_php7_pools": "Tornar configurar lo pools PHP per utilizar PHP 7 allòc del 5", - "migration_description_0005_postgresql_9p4_to_9p6": "Migracion de las basas de donadas de postgresql 9.4 cap a 9.6", - "migration_0005_postgresql_94_not_installed": "Postgresql es pas installat sul sistèma. Pas res de far !", + "migration_description_0005_postgresql_9p4_to_9p6": "Migracion de las basas de donadas de PostgreSQL9.4 cap a 9.6", + "migration_0005_postgresql_94_not_installed": "PostgreSQL es pas installat sul sistèma. I a pas res per far.", "migration_0005_postgresql_96_not_installed": "Avèm trobat que Postgresql 9.4 es installat, mas cap de version de Postgresql 9.6 pas trobada !? Quicòm d’estranh a degut arribar a vòstre sistèma :( …", "migration_0005_not_enough_space": "I a pas pro d’espaci disponible sus {path} per lançar la migracion d’aquela passa :(.", "recommend_to_add_first_user": "La post installacion es acabada, mas YunoHost fa besonh d’almens un utilizaire per foncionar coma cal. Vos cal n’ajustar un en utilizant la comanda « yunohost user create Date: Mon, 6 Jan 2020 07:34:40 +0000 Subject: [PATCH 0599/3170] Translated using Weblate (French) Currently translated at 100.0% (607 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 11f171347..f69dea8f9 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -420,12 +420,12 @@ "experimental_feature": "Attention : cette fonctionnalité est expérimentale et ne doit pas être considérée comme stable, vous ne devriez pas l’utiliser à moins que vous ne sachiez ce que vous faites.", "log_corrupted_md_file": "Le fichier YAML de métadonnées associé aux logs est corrompu : '{md_file}'\nErreur : {error}", "log_category_404": "Le journal de la catégorie '{category}' n’existe pas", - "log_link_to_log": "Journal historisé complet de cette opération : ' {desc} '", - "log_help_to_get_log": "Pour voir le journal historisé de cette opération '{desc}', utilisez la commande 'yunohost log display {name}'", + "log_link_to_log": "Journal complet de cette opération : ' {desc} '", + "log_help_to_get_log": "Pour voir le journal de cette opération '{desc}', utilisez la commande 'yunohost log display {name}'", "log_link_to_failed_log": "L’opération '{desc}' a échouée ! Pour obtenir de l’aide, merci de partager le journal de l'opération en cliquant ici", "backup_php5_to_php7_migration_may_fail": "Impossible de convertir votre archive pour prendre en charge PHP 7, vous pourriez ne plus pouvoir restaurer vos applications PHP (cause : {error:s})", "log_help_to_get_failed_log": "L’opération '{desc}' a échouée ! Pour obtenir de l’aide, merci de partager le journal de l'opération en utilisant la commande 'yunohost log display {name} --share'", - "log_does_exists": "Il n’existe pas de journal historisé de l’opération ayant pour nom '{log}', utiliser 'yunohost log list pour voir tous les fichiers de journaux historisés disponibles'", + "log_does_exists": "Il n’existe pas de journal de l’opération ayant pour nom '{log}', utiliser 'yunohost log list' pour voir tous les fichiers de journaux disponibles", "log_operation_unit_unclosed_properly": "L’opération ne s’est pas terminée correctement", "log_app_addaccess": "Ajouter l’accès à '{}'", "log_app_removeaccess": "Enlever l’accès à '{}'", @@ -437,7 +437,7 @@ "log_app_remove": "Enlever l’application '{}'", "log_app_upgrade": "Mettre à jour l’application '{}'", "log_app_makedefault": "Faire de '{}' l’application par défaut", - "log_available_on_yunopaste": "Le journal historisé est désormais disponible via {url}", + "log_available_on_yunopaste": "Le journal est désormais disponible via {url}", "log_backup_restore_system": "Restaurer le système depuis une archive de sauvegarde", "log_backup_restore_app": "Restaurer '{}' depuis une sauvegarde", "log_remove_on_failed_restore": "Retirer '{}' après un échec de restauration depuis une archive de sauvegarde", From cdbbef769e6d5a9e488816afcae9f0eb7c972333 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Tue, 14 Jan 2020 01:04:20 +0000 Subject: [PATCH 0600/3170] =?UTF-8?q?Translated=20using=20Weblate=20(Norwe?= =?UTF-8?q?gian=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 17.6% (107 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/nb_NO/ --- locales/nb_NO.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/locales/nb_NO.json b/locales/nb_NO.json index 4fe62eebb..f15388941 100644 --- a/locales/nb_NO.json +++ b/locales/nb_NO.json @@ -165,5 +165,7 @@ "log_link_to_log": "Full logg for denne operasjonen: '{desc}'", "log_help_to_get_log": "For å vise loggen for operasjonen '{desc}', bruk kommandoen 'yunohost log display {name}'", "log_app_clearaccess": "Fjern all tilgang til '{}'", - "log_user_create": "Legg til '{}' bruker" + "log_user_create": "Legg til '{}' bruker", + "app_change_url_success": "{app:s} nettadressen er nå {domain:s}{path:s}", + "app_install_failed": "Kunne ikke installere {app}: {error}" } From 6e427374ec614db7f0ae946af87da30c0f57e534 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Thu, 16 Jan 2020 00:34:11 +0700 Subject: [PATCH 0601/3170] fix legacy permission management --- data/helpers.d/setting | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index c862d4ef6..b07b552af 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -187,9 +187,15 @@ EOF # Fucking legacy permission management. # We need this because app temporarily set the app as unprotected to configure it with curl... - if [[ "$3" =~ ^(unprotected|skipped)_ ]] && [[ "${4:-}" == "/" ]] + if [[ "$3" =~ ^(unprotected|skipped)_ ]] then - ynh_permission_update --permission "main" --add "visitors" + if [[ "$1" == "set" ]] && [[ "${4:-}" == "/" ]] + then + ynh_permission_update --permission "main" --add "visitors" + elif [[ "$1" == "delete" ]] + then + ynh_permission_update --permission "main" --remove "visitors" + fi fi } From 9eaabe25decda204b6b4188479a2a1a2d4ef8d05 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Thu, 16 Jan 2020 01:21:14 +0700 Subject: [PATCH 0602/3170] add operation logger for config panel --- locales/en.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/locales/en.json b/locales/en.json index 08fc6b74e..2d446e191 100644 --- a/locales/en.json +++ b/locales/en.json @@ -323,6 +323,9 @@ "log_app_remove": "Remove the '{}' app", "log_app_upgrade": "Upgrade the '{}' app", "log_app_makedefault": "Make '{}' the default app", + "log_app_action_run": "Run action of the '{}' app", + "log_app_config_show_panel": "Show the config panel of the '{}' app", + "log_app_config_apply": "Apply config to the '{}' app", "log_available_on_yunopaste": "This log is now available via {url}", "log_backup_restore_system": "Restore system from a backup archive", "log_backup_restore_app": "Restore '{}' from a backup archive", From 87a3ceb983620002b50a19c87cf4f97479ae11ad Mon Sep 17 00:00:00 2001 From: Kay0u Date: Sat, 25 Jan 2020 20:49:29 +0700 Subject: [PATCH 0603/3170] [FIX] bad response from the server --- data/hooks/diagnosis/14-ports.py | 22 +++++++++++++--------- data/hooks/diagnosis/16-http.py | 18 +++++++++++------- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/data/hooks/diagnosis/14-ports.py b/data/hooks/diagnosis/14-ports.py index f9694a9de..aaf31d561 100644 --- a/data/hooks/diagnosis/14-ports.py +++ b/data/hooks/diagnosis/14-ports.py @@ -26,16 +26,20 @@ class PortsDiagnoser(Diagnoser): ports[port] = service try: - r = requests.post('https://diagnosis.yunohost.org/check-ports', json={'ports': ports.keys()}, timeout=30).json() - if "status" not in r.keys(): - raise Exception("Bad syntax for response ? Raw json: %s" % str(r)) - elif r["status"] == "error": - if "content" in r.keys(): - raise Exception(r["content"]) - else: + r = requests.post('https://diagnosis.yunohost.org/check-ports', json={'ports': ports.keys()}, timeout=30) + if r.status_code == 200: + r = r.json() + if "status" not in r.keys(): raise Exception("Bad syntax for response ? Raw json: %s" % str(r)) - elif r["status"] != "ok" or "ports" not in r.keys() or not isinstance(r["ports"], dict): - raise Exception("Bad syntax for response ? Raw json: %s" % str(r)) + elif r["status"] == "error": + if "content" in r.keys(): + raise Exception(r["content"]) + else: + raise Exception("Bad syntax for response ? Raw json: %s" % str(r)) + elif r["status"] != "ok" or "ports" not in r.keys() or not isinstance(r["ports"], dict): + raise Exception("Bad syntax for response ? Raw json: %s" % str(r)) + else: + raise Exception("Bad response from the server https://diagnosis.yunohost.org : %s" % str(r.status_code)) except Exception as e: raise YunohostError("diagnosis_ports_could_not_diagnose", error=e) diff --git a/data/hooks/diagnosis/16-http.py b/data/hooks/diagnosis/16-http.py index c7955c805..ac30dad78 100644 --- a/data/hooks/diagnosis/16-http.py +++ b/data/hooks/diagnosis/16-http.py @@ -28,14 +28,18 @@ class HttpDiagnoser(Diagnoser): os.system("touch /tmp/.well-known/ynh-diagnosis/%s" % nonce) try: - r = requests.post('https://diagnosis.yunohost.org/check-http', json={'domain': domain, "nonce": nonce}, timeout=30).json() - if "status" not in r.keys(): - raise Exception("Bad syntax for response ? Raw json: %s" % str(r)) - elif r["status"] == "error" and ("code" not in r.keys() or not r["code"].startswith("error_http_check_")): - if "content" in r.keys(): - raise Exception(r["content"]) - else: + r = requests.post('https://diagnosis.yunohost.org/check-http', json={'domain': domain, "nonce": nonce}, timeout=30) + if r.status_code == 200: + r = r.json() + if "status" not in r.keys(): raise Exception("Bad syntax for response ? Raw json: %s" % str(r)) + elif r["status"] == "error" and ("code" not in r.keys() or not r["code"].startswith("error_http_check_")): + if "content" in r.keys(): + raise Exception(r["content"]) + else: + raise Exception("Bad syntax for response ? Raw json: %s" % str(r)) + else: + raise Exception("Bad response from the server https://diagnosis.yunohost.org : %s" % str(r.status_code)) except Exception as e: raise YunohostError("diagnosis_http_could_not_diagnose", error=e) From 026c666d7e2e8a826e0e7c986ec51977c685950c Mon Sep 17 00:00:00 2001 From: Kay0u Date: Wed, 29 Jan 2020 14:24:59 +0700 Subject: [PATCH 0604/3170] remove visitors only for if current value is / --- data/helpers.d/setting | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index b07b552af..384fdc399 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -158,6 +158,11 @@ ynh_add_protected_uris() { # ynh_app_setting() { + if [[ "$1" == "delete" ]] && [[ "$3" =~ ^(unprotected|skipped)_ ]] + then + current_value=$(ynh_app_setting_get --app=$app --key=$3) + fi + ACTION="$1" APP="$2" KEY="$3" VALUE="${4:-}" python2.7 - < Date: Wed, 29 Jan 2020 21:17:14 +0700 Subject: [PATCH 0605/3170] more informations in hooks permission --- src/yunohost/permission.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 5fe9f327f..b9edb317d 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -471,15 +471,21 @@ def _update_ldap_group_permission(permission, allowed, sync_perm=True): app = permission.split(".")[0] sub_permission = permission.split(".")[1] - old_allowed_users = set(existing_permission["corresponding_users"]) - new_allowed_users = set(new_permission["corresponding_users"]) + old_corresponding_users = set(existing_permission["corresponding_users"]) + new_corresponding_users = set(new_permission["corresponding_users"]) - effectively_added_users = new_allowed_users - old_allowed_users - effectively_removed_users = old_allowed_users - new_allowed_users + old_allowed_users = set(existing_permission["allowed"]) + new_allowed_users = set(new_permission["allowed"]) - if effectively_added_users: - hook_callback('post_app_addaccess', args=[app, ','.join(effectively_added_users), sub_permission]) - if effectively_removed_users: - hook_callback('post_app_removeaccess', args=[app, ','.join(effectively_removed_users), sub_permission]) + effectively_added_users = new_corresponding_users - old_corresponding_users + effectively_removed_users = old_corresponding_users - new_corresponding_users + + effectively_added_group = new_allowed_users - old_allowed_users - effectively_added_users + effectively_removed_group = old_allowed_users - new_allowed_users - effectively_removed_users + + if effectively_added_users or effectively_added_group: + hook_callback('post_app_addaccess', args=[app, ','.join(effectively_added_users), sub_permission, ','.join(effectively_added_group)]) + if effectively_removed_users or effectively_removed_group: + hook_callback('post_app_removeaccess', args=[app, ','.join(effectively_removed_users), sub_permission, ','.join(effectively_removed_group)]) return new_permission From 1255b9d7c74dfff2a60371189a4a7cf115071c5e Mon Sep 17 00:00:00 2001 From: Arthur Lutz Date: Mon, 27 Jan 2020 13:31:33 +0100 Subject: [PATCH 0606/3170] [debian/copyright] fix URL --- debian/copyright | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/copyright b/debian/copyright index 8dd627ca5..59483b81e 100644 --- a/debian/copyright +++ b/debian/copyright @@ -1,5 +1,5 @@ Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Source: https://github.com/YunoHost/moulinette-yunohost +Source: https://github.com/YunoHost/yunohost Files: * Copyright: 2015 YUNOHOST.ORG From 62a98eb0733bbc4e396a071cf07d6a38fc2cd1a3 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Mon, 20 Jan 2020 22:55:53 +0700 Subject: [PATCH 0607/3170] Full permission url --- src/yunohost/app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 30d3ab31b..b05d7b818 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1287,6 +1287,7 @@ def app_ssowatconf(): # FIXME : gotta handle regex-urls here... meh url = _sanitized_absolute_url(perm_info["url"]) + perm_info["url"] = url if "visitors" in perm_info["allowed"]: if url not in unprotected_urls: unprotected_urls.append(url) From f69ab4c7e29e130ff94187164a425485e7b1e19b Mon Sep 17 00:00:00 2001 From: Damien Picard Date: Mon, 3 Feb 2020 23:05:35 +0100 Subject: [PATCH 0608/3170] English locale: spelling --- locales/en.json | 90 ++++++++++++++++++++++++------------------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/locales/en.json b/locales/en.json index 2d446e191..df1fbe3a0 100644 --- a/locales/en.json +++ b/locales/en.json @@ -3,13 +3,13 @@ "action_invalid": "Invalid action '{action:s}'", "admin_password": "Administration password", "admin_password_change_failed": "Cannot change password", - "admin_password_changed": "The administration password got changed", + "admin_password_changed": "The administration password was changed", "admin_password_too_long": "Please choose a password shorter than 127 characters", "already_up_to_date": "Nothing to do. Everything is already up-to-date.", "app_action_cannot_be_ran_because_required_services_down": "These required services should be running to run this action: {services}. Try restarting them to continue (and possibly investigate why they are down).", - "app_action_broke_system": "This action seem to have broke these important services: {services}", + "app_action_broke_system": "This action seems to have broken these important services: {services}", "app_already_installed": "{app:s} is already installed", - "app_already_installed_cant_change_url": "This app is already installed. The URL cannot be changed just by this function. Look into `app changeurl` if it's available.", + "app_already_installed_cant_change_url": "This app is already installed. The URL cannot be changed just by this function. Check in `app changeurl` if it's available.", "app_already_up_to_date": "{app:s} is already up-to-date", "app_argument_choice_invalid": "Use one of these choices '{choices:s}' for the argument '{name:s}'", "app_argument_invalid": "Pick a valid value for the argument '{name:s}': {error:s}", @@ -27,7 +27,7 @@ "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_not_upgraded": "The app '{failed_app}' failed to upgrade, and as a consequence the following apps upgrades have been cancelled: {apps}", + "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}", "app_not_properly_removed": "{app:s} has not been properly removed", @@ -53,10 +53,10 @@ "apps_already_up_to_date": "All apps are already up-to-date", "apps_permission_not_found": "No permission found for the installed apps", "apps_permission_restoration_failed": "Permission '{permission:s}' for app {app:s} restoration has failed", - "apps_catalog_init_success": "Apps catalog system initialized!", - "apps_catalog_updating": "Updating applications catalog...", - "apps_catalog_failed_to_download": "Unable to download the {apps_catalog} apps catalog: {error}", - "apps_catalog_obsolete_cache": "The apps catalog cache is empty or obsolete.", + "apps_catalog_init_success": "App catalog system initialized!", + "apps_catalog_updating": "Updating application catalog...", + "apps_catalog_failed_to_download": "Unable to download the {apps_catalog} app catalog: {error}", + "apps_catalog_obsolete_cache": "The app catalog cache is empty or obsolete.", "apps_catalog_update_success": "The application catalog has been updated!", "ask_current_admin_password": "Current administration password", "ask_email": "E-mail address", @@ -85,7 +85,7 @@ "backup_ask_for_copying_if_needed": "Do you want to perform the backup using {size:s} MB temporarily? (This way is used since some files could not be prepared using a more efficient method.)", "backup_borg_not_implemented": "The Borg backup method is not yet implemented", "backup_cant_mount_uncompress_archive": "Could not mount the uncompressed archive as write protected", - "backup_cleaning_failed": "Could not clean-up the temporary backup folder", + "backup_cleaning_failed": "Could not clean up the temporary backup folder", "backup_copying_to_organize_the_archive": "Copying {size:s}MB to organize the archive", "backup_couldnt_bind": "Could not bind {src:s} to {dest:s}.", "backup_created": "Backup created", @@ -105,7 +105,7 @@ "backup_mount_archive_for_restore": "Preparing archive for restoration…", "backup_no_uncompress_archive_dir": "There is no such uncompressed archive directory", "backup_nothings_done": "Nothing to save", - "backup_output_directory_forbidden": "Pick a different output directory. Backups can not be created in /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var or /home/yunohost.backup/archives sub-folders", + "backup_output_directory_forbidden": "Pick a different output directory. Backups cannot be created in /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var or /home/yunohost.backup/archives sub-folders", "backup_output_directory_not_empty": "You should pick an empty output directory", "backup_output_directory_required": "You must provide an output directory for the backup", "backup_output_symlink_dir_broken": "Your archive directory '{path:s}' is a broken symlink. Maybe you forgot to re/mount or plug in the storage medium it points to.", @@ -129,7 +129,7 @@ "certmanager_conflicting_nginx_file": "Could not prepare domain for ACME challenge: the NGINX configuration file {filepath:s} is conflicting and should be removed first", "certmanager_couldnt_fetch_intermediate_cert": "Timed out when trying to fetch intermediate certificate from Let's Encrypt. Certificate installation/renewal aborted—please try again later.", "certmanager_domain_cert_not_selfsigned": "The certificate for domain {domain:s} is not self-signed. Are you sure you want to replace it? (Use '--force' 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 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_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_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.)", @@ -139,17 +139,17 @@ "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})", "confirm_app_install_warning": "Warning: This app may work, but is not well-integrated in YunoHost. Some features such as single sign-on and backup/restore might not be available. Install anyway? [{answers:s}] ", - "confirm_app_install_danger": "DANGER! This app is known to be still experimental (if not explicitly not working)! You should probably NOT install it unless you know what you are doing. NO SUPPORT will be provided if this app doesn't work or break your system… If you are willing to take that risk anyway, type '{answers:s}'", - "confirm_app_install_thirdparty": "DANGER! This app is not part of Yunohost's app catalog. Installing third-party apps may compromise the integrity and security of your system. You should probably NOT install it unless you know what you are doing. NO SUPPORT will be provided if this app doesn't work or break your system… If you are willing to take that risk anyway, type '{answers:s}'", + "confirm_app_install_danger": "DANGER! This app is known to be still experimental (if not explicitly not working)! You should probably NOT install it unless you know what you are doing. NO SUPPORT will be provided if this app doesn't work or breaks your system… If you are willing to take that risk anyway, type '{answers:s}'", + "confirm_app_install_thirdparty": "DANGER! This app is not part of Yunohost's app catalog. Installing third-party apps may compromise the integrity and security of your system. You should probably NOT install it unless you know what you are doing. NO SUPPORT will be provided if this app doesn't work or breaks your system… If you are willing to take that risk anyway, type '{answers:s}'", "custom_app_url_required": "You must provide a URL to upgrade your custom app {app:s}", "diagnosis_basesystem_host": "Server is running Debian {debian_version}.", "diagnosis_basesystem_kernel": "Server is running Linux kernel {kernel_version}", "diagnosis_basesystem_ynh_single_version": "{0} version: {1} ({2})", "diagnosis_basesystem_ynh_main_version": "Server is running YunoHost {main_version} ({repo})", - "diagnosis_basesystem_ynh_inconsistent_versions": "You are running inconsistents versions of the YunoHost packages ... most probably because of a failed or partial upgrade.", + "diagnosis_basesystem_ynh_inconsistent_versions": "You are running inconsistent versions of the YunoHost packages... most probably because of a failed or partial upgrade.", "diagnosis_display_tip_web": "You can go to the Diagnosis section (in the home screen) to see the issues found.", "diagnosis_display_tip_cli": "You can run 'yunohost diagnosis show --issues' to display the issues found.", - "diagnosis_failed_for_category": "Diagnosis failed for category '{category}' : {error}", + "diagnosis_failed_for_category": "Diagnosis failed for category '{category}': {error}", "diagnosis_cache_still_valid": "(Cache still valid for {category} diagnosis. Not re-diagnosing yet!)", "diagnosis_cant_run_because_of_dep": "Can't run diagnosis for {category} while there are important issues related to {dep}.", "diagnosis_ignored_issues": "(+ {nb_ignored} ignored issue(s))", @@ -157,15 +157,15 @@ "diagnosis_found_errors_and_warnings": "Found {errors} significant issue(s) (and {warnings} warning(s)) related to {category}!", "diagnosis_found_warnings": "Found {warnings} item(s) that could be improved for {category}.", "diagnosis_everything_ok": "Everything looks good for {category}!", - "diagnosis_failed": "Failed to fetch diagnosis result for category '{category}' : {error}", + "diagnosis_failed": "Failed to fetch diagnosis result for category '{category}': {error}", "diagnosis_no_cache": "No diagnosis cache yet for category '{category}'", "diagnosis_ip_connected_ipv4": "The server is connected to the Internet through IPv4 !", - "diagnosis_ip_no_ipv4": "The server does not have a working IPv4.", + "diagnosis_ip_no_ipv4": "The server does not have working IPv4.", "diagnosis_ip_connected_ipv6": "The server is connected to the Internet through IPv6 !", - "diagnosis_ip_no_ipv6": "The server does not have a working IPv6.", + "diagnosis_ip_no_ipv6": "The server does not have working IPv6.", "diagnosis_ip_not_connected_at_all": "The server does not seem to be connected to the Internet at all!?", "diagnosis_ip_dnsresolution_working": "Domain name resolution is working!", - "diagnosis_ip_broken_dnsresolution": "Domain name resolution seems to be broken for some reason ... Is a firewall blocking DNS requests ?", + "diagnosis_ip_broken_dnsresolution": "Domain name resolution seems to be broken for some reason... Is a firewall blocking DNS requests ?", "diagnosis_ip_broken_resolvconf": "Domain name resolution seems to be broken on your server, which seems related to /etc/resolv.conf not pointing to 127.0.0.1.", "diagnosis_ip_weird_resolvconf": "DNS resolution seems to be working, but be careful that you seem to be using a custom /etc/resolv.conf.", "diagnosis_ip_weird_resolvconf_details": "Instead, this file should be a symlink to /etc/resolvconf/run/resolv.conf itself pointing to 127.0.0.1 (dnsmasq). The actual resolvers should be configured via /etc/resolv.dnsmasq.conf.", @@ -187,7 +187,7 @@ "diagnosis_swap_notsomuch": "The system has only {total_MB} MB swap. You should consider having at least 256 MB to avoid situations where the system runs out of memory.", "diagnosis_swap_ok": "The system has {total_MB} MB of swap!", "diagnosis_mail_ougoing_port_25_ok": "Outgoing port 25 is not blocked and email can be sent to other servers.", - "diagnosis_mail_ougoing_port_25_blocked": "Outgoing port 25 appears to be blocked. You should try to unblock it in your internet service provider (or hoster) configuration panel. Meanwhile, the server won't be able to send emails to other servers.", + "diagnosis_mail_ougoing_port_25_blocked": "Outgoing port 25 appears to be blocked. You should try to unblock it in your internet service provider (or hosting provider) configuration panel. Meanwhile, the server won't be able to send emails to other servers.", "diagnosis_regenconf_allgood": "All configurations files are in line with the recommended configuration!", "diagnosis_regenconf_manually_modified": "Configuration file {file} was manually modified.", "diagnosis_regenconf_manually_modified_details": "This is probably OK as long as you know what you're doing ;) !", @@ -211,7 +211,7 @@ "diagnosis_ports_unreachable": "Port {port} is not reachable from outside.", "diagnosis_ports_ok": "Port {port} is reachable from outside.", "diagnosis_ports_needed_by": "Exposing this port is needed for service {0}", - "diagnosis_ports_forwarding_tip": "To fix this issue, most probably you need to configure port forwarding on your internet router as described in https://yunohost.org/isp_box_config", + "diagnosis_ports_forwarding_tip": "To fix this issue, you most probably need to configure port forwarding on your internet router as described in https://yunohost.org/isp_box_config", "diagnosis_http_could_not_diagnose": "Could not diagnose if domain is reachable from outside. Error: {error}", "diagnosis_http_ok": "Domain {domain} is reachable from outside.", "diagnosis_http_timeout": "Timed-out while trying to contact your server from outside. It appears to be unreachable. You should check that you're correctly forwarding port 80, that nginx is running, and that a firewall is not interfering.", @@ -219,8 +219,8 @@ "diagnosis_http_unknown_error": "An error happened while trying to reach your domain, it's very likely unreachable.", "diagnosis_http_bad_status_code": "Could not reach your server as expected, it returned a bad status code. It might be that another machine answered instead of your server. You should check that you're correctly forwarding port 80, that your nginx configuration is up to date, and that a reverse-proxy is not interfering.", "diagnosis_http_unreachable": "Domain {domain} is unreachable through HTTP from outside.", - "diagnosis_unknown_categories": "The following categories are unknown : {categories}", - "domain_cannot_remove_main": "You cannot remove '{domain:s}' since it's the main domain, you need first to set another domain as the main domain using 'yunohost domain main-domain -n ', here is the list of candidate domains: {other_domains:s}", + "diagnosis_unknown_categories": "The following categories are unknown: {categories}", + "domain_cannot_remove_main": "You cannot remove '{domain:s}' since it's the main domain, you first need to set another domain as the main domain using 'yunohost domain main-domain -n '; here is the list of candidate domains: {other_domains:s}", "domain_cannot_remove_main_add_new_one": "You cannot remove '{domain:s}' since it's the main domain and your only domain, you need to first add another domain using 'yunohost domain add ', then set is as the main domain using 'yunohost domain main-domain -n ' and then you can remove the domain '{domain:s}' using 'yunohost domain remove {domain:s}'.'", "domain_cert_gen_failed": "Could not generate certificate", "domain_created": "Domain created", @@ -238,7 +238,7 @@ "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_lock_not_available": "This command can't be ran right now because another program seems to be using the lock of dpkg (the system package manager)", + "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}.", "dyndns_cron_installed": "DynDNS cron job created", @@ -249,7 +249,7 @@ "dyndns_key_generating": "Generating DNS key… It may take a while.", "dyndns_key_not_found": "DNS key not found for the domain", "dyndns_no_domain_registered": "No domain registered with DynDNS", - "dyndns_provider_unreachable": "Unable to reach Dyndns provider {provider}: either your YunoHost is not correctly connected to the internet or the dynette server is down.", + "dyndns_provider_unreachable": "Unable to reach DynDNS provider {provider}: either your YunoHost is not correctly connected to the internet or the dynette server is down.", "dyndns_registered": "DynDNS domain registered", "dyndns_registration_failed": "Could not register DynDNS domain: {error:s}", "dyndns_domain_not_provided": "DynDNS provider {provider:s} cannot provide domain {domain:s}.", @@ -262,7 +262,7 @@ "file_does_not_exist": "The file {path:s} does not exist.", "firewall_reload_failed": "Could not reload the firewall", "firewall_reloaded": "Firewall reloaded", - "firewall_rules_cmd_failed": "Some firewall rules commands have failed. More info in log.", + "firewall_rules_cmd_failed": "Some firewall rule commands have failed. More info in log.", "global_settings_bad_choice_for_enum": "Bad choice for setting {setting:s}, received '{choice:s}', but available choices are: {available_choices:s}", "global_settings_bad_type_for_setting": "Bad type for setting {setting:s}, received {received_type:s}, expected {expected_type:s}", "global_settings_cant_open_settings": "Could not open settings file, reason: {reason:s}", @@ -283,8 +283,8 @@ "global_settings_unknown_setting_from_settings_file": "Unknown key in settings: '{setting_key:s}', discard it and save it in /etc/yunohost/settings-unknown.json", "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Allow the use of (deprecated) DSA hostkey for the SSH daemon configuration", "global_settings_unknown_type": "Unexpected situation, the setting {setting:s} appears to have the type {unknown_type:s} but it is not a type supported by the system.", - "good_practices_about_admin_password": "You are now about to define a new administration password. The password should be at-least 8 characters—though it is good practice to use a longer password (i.e. a passphrase) and/or to use a variation of characters (uppercase, lowercase, digits and special characters).", - "good_practices_about_user_password": "You are now about to define a new user password. The password should be at least 8 characters—though it is good practice to use longer password (i.e. a passphrase) and/or to a variation of characters (uppercase, lowercase, digits and special characters).", + "good_practices_about_admin_password": "You are now about to define a new administration password. The password should be at least 8 characters long—though it is good practice to use a longer password (i.e. a passphrase) and/or to use a variation of characters (uppercase, lowercase, digits and special characters).", + "good_practices_about_user_password": "You are now about to define a new user password. The password should be at least 8 characters long—though it is good practice to use a longer password (i.e. a passphrase) and/or to a variation of characters (uppercase, lowercase, digits and special characters).", "group_already_exist": "Group {group} already exists", "group_already_exist_on_system": "Group {group} already exists in the system groups", "group_created": "Group '{group}' created", @@ -316,7 +316,7 @@ "log_help_to_get_log": "To view the log of the operation '{desc}', use the command 'yunohost log display {name}'", "log_link_to_failed_log": "Could not complete the operation '{desc}'. Please provide the full log of this operation by clicking here to get help", "log_help_to_get_failed_log": "The operation '{desc}' could not be completed. Please share the full log of this operation using the command 'yunohost log display {name} --share' to get help", - "log_does_exists": "There is not operation log with the name '{log}', use 'yunohost log list' to see all available operation logs", + "log_does_exists": "There is no operation log with the name '{log}', use 'yunohost log list' to see all available operation logs", "log_operation_unit_unclosed_properly": "Operation unit has not been closed properly", "log_app_change_url": "Change the URL of the '{}' app", "log_app_install": "Install the '{}' app", @@ -339,7 +339,7 @@ "log_permission_create": "Create permission '{}'", "log_permission_delete": "Delete permission '{}'", "log_permission_url": "Update url related to permission '{}'", - "log_selfsigned_cert_install": "Install self signed certificate on '{}' domain", + "log_selfsigned_cert_install": "Install self-signed certificate on '{}' domain", "log_letsencrypt_cert_renew": "Renew '{}' Let's Encrypt certificate", "log_regen_conf": "Regenerate system configurations '{}'", "log_user_create": "Add '{}' user", @@ -350,7 +350,7 @@ "log_user_update": "Update user info of '{}'", "log_user_permission_update": "Update accesses for permission '{}'", "log_user_permission_reset": "Reset permission '{}'", - "log_domain_main_domain": "Make '{}' as main domain", + "log_domain_main_domain": "Make '{}' the main domain", "log_tools_migrations_migrate_forward": "Migrate forward", "log_tools_postinstall": "Postinstall your YunoHost server", "log_tools_upgrade": "Upgrade system packages", @@ -363,7 +363,7 @@ "mail_domain_unknown": "Invalid e-mail address for domain '{domain:s}'. Please, use a domain administrated by this server.", "mail_forward_remove_failed": "Could not remove e-mail forwarding '{mail:s}'", "mailbox_disabled": "E-mail turned off for user {user:s}", - "mailbox_used_space_dovecot_down": "The Dovecot mailbox service needs to be up, if you want to fetch used mailbox space", + "mailbox_used_space_dovecot_down": "The Dovecot mailbox service needs to be up if you want to fetch used mailbox space", "mail_unavailable": "This e-mail address is reserved and shall be automatically allocated to the very first user", "main_domain_change_failed": "Unable to change the main domain", "main_domain_changed": "The main domain has been changed", @@ -393,16 +393,16 @@ "migration_0003_patching_sources_list": "Patching the sources.lists…", "migration_0003_main_upgrade": "Starting main upgrade…", "migration_0003_fail2ban_upgrade": "Starting the Fail2Ban upgrade…", - "migration_0003_restoring_origin_nginx_conf": "Your file /etc/nginx/nginx.conf was edited somehow. The migration is going to reset to its original state first… The previous file will be available as {backup_dest}.", - "migration_0003_yunohost_upgrade": "Starting the YunoHost package upgrade… The migration will end, but the actual upgrade will happen immediately afterwards. After the operation is complete, you might have to log in on the webadmin page again.", + "migration_0003_restoring_origin_nginx_conf": "Your file /etc/nginx/nginx.conf was edited somehow. The migration is going to reset it to its original state first… The previous file will be available as {backup_dest}.", + "migration_0003_yunohost_upgrade": "Starting the YunoHost package upgrade… The migration will end, but the actual upgrade will happen immediately afterwards. After the operation is complete, you might have to log in to the webadmin page again.", "migration_0003_not_jessie": "The current Debian distribution is not Jessie!", "migration_0003_system_not_fully_up_to_date": "Your system is not fully up-to-date. Please perform a regular upgrade before running the migration to Stretch.", "migration_0003_still_on_jessie_after_main_upgrade": "Something went wrong during the main upgrade: Is the system still on Jessie‽ To investigate the issue, please look at {log}:s…", "migration_0003_general_warning": "Please note that this migration is a delicate operation. The YunoHost team did its best to review and test it, but the migration might still break parts of the system or its apps.\n\nTherefore, it is recommended to:\n - Perform a backup of any critical data or app. More info on https://yunohost.org/backup;\n - Be patient after launching the migration: Depending on your Internet connection and hardware, it might take up to a few hours for everything to upgrade.\n\nAdditionally, the port for SMTP, used by external e-mail clients (like Thunderbird or K9-Mail) was changed from 465 (SSL/TLS) to 587 (STARTTLS). The old port (465) will automatically be closed, and the new port (587) will be opened in the firewall. You and your users *will* have to adapt the configuration of your e-mail clients accordingly.", - "migration_0003_problematic_apps_warning": "Please note that the following possibly problematic installed apps were detected. It looks like those were not installed from an apps_catalog, or are not flagged as 'working'. Consequently, it cannot be guaranteed that they will still work after the upgrade: {problematic_apps}", + "migration_0003_problematic_apps_warning": "Please note that the following possibly problematic installed apps were detected. It looks like those were not installed from an app catalog, or are not flagged as 'working'. Consequently, it cannot be guaranteed that they will still work after the upgrade: {problematic_apps}", "migration_0003_modified_files": "Please note that the following files were found to be manually modified and might be overwritten following the upgrade: {manually_modified_files}", "migration_0005_postgresql_94_not_installed": "PostgreSQL was not installed on your system. Nothing to do.", - "migration_0005_postgresql_96_not_installed": "PostgreSQL 9.4 is installed, but not postgresql 9.6‽ Something weird might have happened on your system:(…", + "migration_0005_postgresql_96_not_installed": "PostgreSQL 9.4 is installed, but not postgresql 9.6‽ Something weird might have happened on your system :(…", "migration_0005_not_enough_space": "Make sufficient space available in {path} to run the migration.", "migration_0006_disclaimer": "YunoHost now expects the admin and root passwords to be synchronized. This migration replaces your root password with the admin password.", "migration_0007_cancelled": "Could not improve the way your SSH configuration is managed.", @@ -412,7 +412,7 @@ "migration_0008_root": "• You will not be able to connect as root through SSH. Instead you should use the admin user;", "migration_0008_dsa": "• The DSA key will be turned off. Hence, you might need to invalidate a spooky warning from your SSH client, and recheck the fingerprint of your server;", "migration_0008_warning": "If you understand those warnings and want YunoHost to override your current configuration, run the migration. Otherwise, you can also skip the migration, though it is not recommended.", - "migration_0008_no_warning": "Overriding your SSH configuration should be safe, though this can not be promised! Run the migration to override it. Otherwise, you can also skip the migration, though it is not recommended.", + "migration_0008_no_warning": "Overriding your SSH configuration should be safe, though this cannot be promised! Run the migration to override it. Otherwise, you can also skip the migration, though it is not recommended.", "migration_0009_not_needed": "This migration already happened somehow… (?) Skipping.", "migration_0011_backup_before_migration": "Creating a backup of LDAP database and apps settings prior to the actual migration.", "migration_0011_can_not_backup_before_migration": "The backup of the system could not be completed before the migration failed. Error: {error:s}", @@ -449,7 +449,7 @@ "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 password in the world. Please choose something more unique.", + "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", "password_too_simple_2": "The password needs to be at least 8 characters long and contain a digit, upper and lower characters", "password_too_simple_3": "The password needs to be at least 8 characters long and contain a digit, upper, lower and special characters", @@ -466,7 +466,7 @@ "pattern_port_or_range": "Must be a valid port number (i.e. 0-65535) or range of ports (e.g. 100:200)", "pattern_positive_number": "Must be a positive number", "pattern_username": "Must be lower-case alphanumeric and underscore characters only", - "pattern_password_app": "Sorry, passwords can not contain the following characters: {forbidden_chars}", + "pattern_password_app": "Sorry, passwords cannot contain the following characters: {forbidden_chars}", "permission_all_users_implicitly_added": "The permission was also implicitly granted to 'all_users' because it is required to allow the special group 'visitors'", "permission_already_allowed": "Group '{group}' already has permission '{permission}' enabled", "permission_already_disallowed": "Group '{group}' already has permission '{permission}' disabled'", @@ -480,7 +480,7 @@ "permission_deleted": "Permission '{permission:s}' deleted", "permission_deletion_failed": "Could not delete permission '{permission}': {error}", "permission_not_found": "Permission '{permission:s}' not found", - "permission_update_failed": "Could not update permission '{permission}' : {error}", + "permission_update_failed": "Could not update permission '{permission}': {error}", "permission_updated": "Permission '{permission:s}' updated", "permission_update_nothing_to_do": "No permissions to update", "permission_require_account": "Permission {permission} only makes sense for users having an account, and therefore cannot be enabled for visitors.", @@ -508,8 +508,8 @@ "restore_confirm_yunohost_installed": "Do you really want to restore an already installed system? [{answers:s}]", "restore_extracting": "Extracting needed files from the archive…", "restore_failed": "Could not restore system", - "restore_hook_unavailable": "The restoration script for '{part:s}' not available on your system and not in the archive either", - "restore_may_be_not_enough_disk_space": "Your system seems does not have enough space (free: {free_space:d} B, needed space: {needed_space:d} B, security margin: {margin:d} B)", + "restore_hook_unavailable": "Restoration script for '{part:s}' not available on your system and not in the archive either", + "restore_may_be_not_enough_disk_space": "Your system does not seem to have enough space (free: {free_space:d} B, needed space: {needed_space:d} B, security margin: {margin:d} B)", "restore_not_enough_disk_space": "Not enough space (space: {free_space:d} B, needed space: {needed_space:d} B, security margin: {margin:d} B)", "restore_nothings_done": "Nothing was restored", "restore_removing_tmp_dir_failed": "Could not remove an old temporary directory", @@ -523,7 +523,7 @@ "server_reboot": "The server will reboot", "server_reboot_confirm": "The server will reboot immediatly, are you sure? [{answers:s}]", "service_add_failed": "Could not add the service '{service:s}'", - "service_added": "The service '{service:s}' added", + "service_added": "The service '{service:s}' was added", "service_already_started": "The service '{service:s}' is running already", "service_already_stopped": "The service '{service:s}' has already been stopped", "service_cmd_exec_failed": "Could not execute the command '{command:s}'", @@ -575,7 +575,7 @@ "tools_upgrade_regular_packages": "Now upgrading 'regular' (non-yunohost-related) packages…", "tools_upgrade_regular_packages_failed": "Could not upgrade packages: {packages_list}", "tools_upgrade_special_packages": "Now upgrading 'special' (yunohost-related) packages…", - "tools_upgrade_special_packages_explanation": "The special upgrade will continue in background. Please don't start any other actions on your server the next ~10 minutes (depending on hardware speed). After this, you may have to re-log on the webadmin. The upgrade log will be available in Tools → Log (in the webadmin) or using 'yunohost log list' (from the command-line).", + "tools_upgrade_special_packages_explanation": "The special upgrade will continue in the background. Please don't start any other actions on your server for the next ~10 minutes (depending on hardware speed). After this, you may have to re-log in to the webadmin. The upgrade log will be available in Tools → Log (in the webadmin) or using 'yunohost log list' (from the command-line).", "tools_upgrade_special_packages_completed": "YunoHost package upgrade completed.\nPress [Enter] to get the command line back", "unbackup_app": "App '{app:s}' will not be saved", "unexpected_error": "Something unexpected went wrong: {error}", @@ -608,5 +608,5 @@ "yunohost_configured": "YunoHost is now configured", "yunohost_installing": "Installing YunoHost…", "yunohost_not_installed": "YunoHost is not correctly installed. Please run 'yunohost tools postinstall'", - "yunohost_postinstall_end_tip": "The post-install completed! To finalize your setup, please consider:\n - adding a first user through the 'Users' section of the webadmin (or 'yunohost user create ' in command-line) ;\n - diagnose issues waiting to be solved for your server to be running as smoothly as possible through the 'Diagnosis' section of the webadmin (or 'yunohost diagnosis run' in command-line) ;\n - reading the 'Finalizing your setup' and 'Getting to know Yunohost' parts in the admin documentation : https://yunohost.org/admindoc." + "yunohost_postinstall_end_tip": "The post-install completed! To finalize your setup, please consider:\n - adding a first user through the 'Users' section of the webadmin (or 'yunohost user create ' in command-line);\n - diagnose issues waiting to be solved for your server to be running as smoothly as possible through the 'Diagnosis' section of the webadmin (or 'yunohost diagnosis run' in command-line);\n - reading the 'Finalizing your setup' and 'Getting to know Yunohost' parts in the admin documentation: https://yunohost.org/admindoc." } From dc5ee76124e016f912bb33bcd3007a067867efd0 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Mon, 20 Jan 2020 22:55:53 +0700 Subject: [PATCH 0609/3170] Full permission url --- src/yunohost/app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 8ce5ed783..0324a116a 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1661,6 +1661,7 @@ def app_ssowatconf(): # FIXME : gotta handle regex-urls here... meh url = _sanitized_absolute_url(perm_info["url"]) + perm_info["url"] = url if "visitors" in perm_info["allowed"]: if url not in unprotected_urls: unprotected_urls.append(url) From c7506fd3a92ec1f4dd77406e4cc58b910b900391 Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Mon, 2 Dec 2019 22:32:59 +0100 Subject: [PATCH 0610/3170] [fix] This DNS resolver in ipv6 is unreachable --- data/templates/dnsmasq/plain/resolv.dnsmasq.conf | 1 - 1 file changed, 1 deletion(-) diff --git a/data/templates/dnsmasq/plain/resolv.dnsmasq.conf b/data/templates/dnsmasq/plain/resolv.dnsmasq.conf index 6b3bb95d3..ce8515054 100644 --- a/data/templates/dnsmasq/plain/resolv.dnsmasq.conf +++ b/data/templates/dnsmasq/plain/resolv.dnsmasq.conf @@ -32,7 +32,6 @@ nameserver 85.214.20.141 nameserver 195.160.173.53 # (DE) AS250 nameserver 194.150.168.168 -nameserver 2001:4ce8::53 # (DE) Ideal-Hosting nameserver 84.200.69.80 nameserver 2001:1608:10:25::1c04:b12f From 0081d988ab635e859a406db3f5ab3203331c0cb5 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sun, 9 Feb 2020 18:45:49 +0100 Subject: [PATCH 0611/3170] Replace __PHPVERSION__ by $YNH_PHP_VERSION in nginx conf files --- data/helpers.d/nginx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/data/helpers.d/nginx b/data/helpers.d/nginx index e3e45d2d4..b34ebb4e1 100644 --- a/data/helpers.d/nginx +++ b/data/helpers.d/nginx @@ -12,6 +12,7 @@ # __PORT__ by $port # __NAME__ by $app # __FINALPATH__ by $final_path +# __PHPVERSION__ by $YNH_PHP_VERSION ($YNH_PHP_VERSION is either the default php version or the version defined for the app) # # And dynamic variables (from the last example) : # __PATH_2__ by $path_2 @@ -44,6 +45,7 @@ ynh_add_nginx_config () { if test -n "${final_path:-}"; then ynh_replace_string --match_string="__FINALPATH__" --replace_string="$final_path" --target_file="$finalnginxconf" fi + ynh_replace_string --match_string="__PHPVERSION__" --replace_string="$YNH_PHP_VERSION" --target_file="$finalnginxconf" # Replace all other variable given as arguments for var_to_replace in $others_var From a489a06daa01e195d35f159658f8805a2af4c349 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sun, 9 Feb 2020 18:49:27 +0100 Subject: [PATCH 0612/3170] Use the default php version into the php helpers --- data/helpers.d/php | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/data/helpers.d/php b/data/helpers.d/php index 41af467c5..56d35cee8 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -1,5 +1,9 @@ #!/bin/bash +# Declare the actual php version to use. +# A packager willing to use another version of php can override the variable into its _common.sh. +YNH_PHP_VERSION=${YNH_PHP_VERSION:-$YNH_DEFAULT_PHP_VERSION} + # Create a dedicated php-fpm config # # usage: ynh_add_fpm_config [--phpversion=7.X] @@ -14,8 +18,8 @@ ynh_add_fpm_config () { # Manage arguments with getopts ynh_handle_getopts_args "$@" - # Configure PHP-FPM 7.0 by default - phpversion="${phpversion:-7.0}" + # Set the default PHP-FPM version by default + phpversion="${phpversion:-$YNH_PHP_VERSION}" local fpm_config_dir="/etc/php/$phpversion/fpm" local fpm_service="php${phpversion}-fpm" @@ -26,6 +30,7 @@ ynh_add_fpm_config () { fi ynh_app_setting_set --app=$app --key=fpm_config_dir --value="$fpm_config_dir" ynh_app_setting_set --app=$app --key=fpm_service --value="$fpm_service" + ynh_app_setting_set --app=$app --key=phpversion --value=$phpversion finalphpconf="$fpm_config_dir/pool.d/$app.conf" ynh_backup_if_checksum_is_different --file="$finalphpconf" cp ../conf/php-fpm.conf "$finalphpconf" @@ -56,10 +61,10 @@ ynh_add_fpm_config () { ynh_remove_fpm_config () { local fpm_config_dir=$(ynh_app_setting_get --app=$app --key=fpm_config_dir) local fpm_service=$(ynh_app_setting_get --app=$app --key=fpm_service) - # Assume php version 7 if not set + # Assume default php version if not set if [ -z "$fpm_config_dir" ]; then - fpm_config_dir="/etc/php/7.0/fpm" - fpm_service="php7.0-fpm" + fpm_config_dir="/etc/php/$YNH_DEFAULT_PHP_VERSION/fpm" + fpm_service="php$YNH_DEFAULT_PHP_VERSION-fpm" fi ynh_secure_remove --file="$fpm_config_dir/pool.d/$app.conf" ynh_secure_remove --file="$fpm_config_dir/conf.d/20-$app.ini" 2>&1 From 940162a31ff6836273ddaa209abb4d3813b8db62 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sun, 9 Feb 2020 18:52:43 +0100 Subject: [PATCH 0613/3170] Set the default version for php And propagate it as an env variable for apps. --- src/yunohost/app.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index b05d7b818..2311ab8e5 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -59,6 +59,7 @@ APPS_CATALOG_CONF = '/etc/yunohost/apps_catalog.yml' APPS_CATALOG_CRON_PATH = "/etc/cron.daily/yunohost-fetch-apps-catalog" APPS_CATALOG_API_VERSION = 2 APPS_CATALOG_DEFAULT_URL = "https://app.yunohost.org/default" +APPS_DEFAULT_PHP_VERSION = "7.0" re_github_repo = re.compile( r'^(http[s]?://|git@)github.com[/:]' @@ -347,6 +348,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_DEFAULT_PHP_VERSION"] = APPS_DEFAULT_PHP_VERSION env_dict["YNH_APP_OLD_DOMAIN"] = old_domain env_dict["YNH_APP_OLD_PATH"] = old_path @@ -483,6 +485,7 @@ def app_upgrade(app=[], url=None, file=None): 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_DEFAULT_PHP_VERSION"] = APPS_DEFAULT_PHP_VERSION # Start register change on system related_to = [('app', app_instance_name)] @@ -695,6 +698,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_DEFAULT_PHP_VERSION"] = APPS_DEFAULT_PHP_VERSION # Start register change on system operation_logger.extra.update({'env': env_dict}) @@ -803,6 +807,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_remove["YNH_DEFAULT_PHP_VERSION"] = APPS_DEFAULT_PHP_VERSION # Execute remove script operation_logger_remove = OperationLogger('remove_on_failed_install', @@ -980,6 +985,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_DEFAULT_PHP_VERSION"] = APPS_DEFAULT_PHP_VERSION operation_logger.extra.update({'env': env_dict}) operation_logger.flush() @@ -1403,6 +1409,7 @@ def app_action_run(operation_logger, app, action, args=None): 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_DEFAULT_PHP_VERSION"] = APPS_DEFAULT_PHP_VERSION env_dict["YNH_ACTION"] = action _, path = tempfile.mkstemp() @@ -1466,6 +1473,7 @@ def app_config_show_panel(operation_logger, app): "YNH_APP_ID": app_id, "YNH_APP_INSTANCE_NAME": app, "YNH_APP_INSTANCE_NUMBER": str(app_instance_nb), + "YNH_DEFAULT_PHP_VERSION": APPS_DEFAULT_PHP_VERSION, } return_code, parsed_values = hook_exec(config_script, @@ -1539,6 +1547,7 @@ def app_config_apply(operation_logger, app, args): "YNH_APP_ID": app_id, "YNH_APP_INSTANCE_NAME": app, "YNH_APP_INSTANCE_NUMBER": str(app_instance_nb), + "YNH_DEFAULT_PHP_VERSION": APPS_DEFAULT_PHP_VERSION, } args = dict(urlparse.parse_qsl(args, keep_blank_values=True)) if args else {} From 55d17a61017378b907a79bbeee5fb614737956e0 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sun, 9 Feb 2020 19:11:06 +0100 Subject: [PATCH 0614/3170] Add the helper ynh_install_php --- data/helpers.d/php | 80 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/data/helpers.d/php b/data/helpers.d/php index 41af467c5..224c0a3d9 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -65,3 +65,83 @@ ynh_remove_fpm_config () { ynh_secure_remove --file="$fpm_config_dir/conf.d/20-$app.ini" 2>&1 ynh_systemd_action --service_name=$fpm_service --action=reload } + +# Install another version of php. +# +# usage: ynh_install_php --phpversion=phpversion [--package=packages] +# | arg: -v, --phpversion - Version of php to install. +# | arg: -p, --package - Additionnal php packages to install +ynh_install_php () { + # Declare an array to define the options of this helper. + local legacy_args=vp + declare -Ar args_array=( [v]=phpversion= [p]=package= ) + local phpversion + local package + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + package=${package:-} + + # Store phpversion into the config of this app + ynh_app_setting_set $app phpversion $phpversion + + if [ "$phpversion" == "7.0" ] + then + ynh_die "Do not use ynh_install_php to install php7.0" + fi + + # Store the ID of this app and the version of php requested for it + echo "$YNH_APP_INSTANCE_NAME:$phpversion" | tee --append "/etc/php/ynh_app_version" + + # Add an extra repository for those packages + ynh_install_extra_repo --repo="https://packages.sury.org/php/ $(lsb_release -sc) main" --key="https://packages.sury.org/php/apt.gpg" --priority=995 --name=extra_php_version + + # Install requested dependencies from this extra repository. + # Install php-fpm first, otherwise php will install apache as a dependency. + ynh_add_app_dependencies --package="php${phpversion}-fpm" + ynh_add_app_dependencies --package="php$phpversion php${phpversion}-common $package" + + # Set php7.0 back as the default version for php-cli. + update-alternatives --set php /usr/bin/php7.0 + + # Pin this extra repository after packages are installed to prevent sury of doing shit + ynh_pin_repo --package="*" --pin="origin \"packages.sury.org\"" 200 --name=extra_php_version + ynh_pin_repo --package="php7.0*" --pin="origin \"packages.sury.org\"" 600 --name=extra_php_version --append + + # Advertise service in admin panel + yunohost service add php${phpversion}-fpm --log "/var/log/php${phpversion}-fpm.log" +} + +# Remove the specific version of php used by the app. +# +# usage: ynh_install_php +ynh_remove_php () { + # Get the version of php used by this app + local phpversion=$(ynh_app_setting_get $app phpversion) + + if [ "$phpversion" == "7.0" ] || [ -z "$phpversion" ] + then + if [ "$phpversion" == "7.0" ] + then + ynh_print_err "Do not use ynh_remove_php to install php7.0" + fi + return 0 + fi + + # Remove the line for this app + sed --in-place "/$YNH_APP_INSTANCE_NAME:$phpversion/d" "/etc/php/ynh_app_version" + + # If no other app uses this version of php, remove it. + if ! grep --quiet "$phpversion" "/etc/php/ynh_app_version" + then + # Purge php dependences for this version. + ynh_package_autopurge "php$phpversion php${phpversion}-fpm php${phpversion}-common" + # Remove the service from the admin panel + yunohost service remove php${phpversion}-fpm + fi + + # If no other app uses alternate php versions, remove the extra repo for php + if [ ! -s "/etc/php/ynh_app_version" ] + then + ynh_secure_remove /etc/php/ynh_app_version + fi +} From 7a5760db55986b2bbf7cc642f70c792c5b3310c4 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sun, 9 Feb 2020 19:55:38 +0100 Subject: [PATCH 0615/3170] Add the helper ynh_install_extra_app_dependencies And the helpers used by this one. --- data/helpers.d/apt | 275 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 272 insertions(+), 3 deletions(-) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index 55c85c90b..0f973dda5 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -205,7 +205,8 @@ ynh_package_install_from_equivs () { # Requires YunoHost version 2.6.4 or higher. ynh_install_app_dependencies () { local dependencies=$@ - local dependencies=${dependencies// /, } + # Add a comma for each space between packages. But not add a comma if the space separate a version specification. (See below) + dependencies="$(echo "$dependencies" | sed 's/\([^\<=\>]\)\ \([^(]\)/\1, \2/g')" local dependencies=${dependencies//|/ | } local manifest_path="../manifest.json" if [ ! -e "$manifest_path" ]; then @@ -218,6 +219,20 @@ ynh_install_app_dependencies () { fi local dep_app=${app//_/-} # Replace all '_' by '-' + # Handle specific versions + if [[ "$dependencies" =~ [\<=\>] ]] + then + # Replace version specifications by relationships syntax + # https://www.debian.org/doc/debian-policy/ch-relationships.html + # Sed clarification + # [^(\<=\>] ignore if it begins by ( or < = >. To not apply twice. + # [\<=\>] matches < = or > + # \+ matches one or more occurence of the previous characters, for >= or >>. + # [^,]\+ matches all characters except ',' + # Ex: 'package>=1.0' will be replaced by 'package (>= 1.0)' + dependencies="$(echo "$dependencies" | sed 's/\([^(\<=\>]\)\([\<=\>]\+\)\([^,]\+\)/\1 (\2 \3)/g')" + fi + # # Epic ugly hack to fix the goddamn dependency nightmare of sury # Sponsored by the "Djeezusse Fokin Kraiste Why Do Adminsys Has To Be So Fucking Complicated I Should Go Grow Potatoes Instead Of This Shit" collective @@ -233,8 +248,11 @@ ynh_install_app_dependencies () { if ! grep -nrq "sury" /etc/apt/sources.list* then # Re-add sury - echo "deb https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/sury.list - wget -O /etc/apt/trusted.gpg.d/sury.gpg https://packages.sury.org/php/apt.gpg + ynh_install_extra_repo --repo="https://packages.sury.org/php/ $(lsb_release -sc) main" --key="https://packages.sury.org/php/apt.gpg" --name=extra_php_version + + # Pin this sury repository to prevent sury of doing shit + ynh_pin_repo --package="*" --pin="origin \"packages.sury.org\"" 200 --name=extra_php_version + ynh_pin_repo --package="php7.0*" --pin="origin \"packages.sury.org\"" 600 --name=extra_php_version --append fi fi fi @@ -255,6 +273,38 @@ EOF ynh_app_setting_set --app=$app --key=apt_dependencies --value="$dependencies" } +# Add dependencies to install with ynh_install_app_dependencies +# +# [internal] +# +# usage: ynh_add_app_dependencies --package=phpversion [--replace] +# | arg: -p, --package - Packages to add as dependencies for the app. +# | arg: -r, --replace - Replace dependencies instead of adding to existing ones. +ynh_add_app_dependencies () { + # Declare an array to define the options of this helper. + local legacy_args=pr + declare -Ar args_array=( [p]=package= [r]=replace) + local package + local replace + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + replace=${replace:-0} + + local current_dependencies="" + if [ $replace -eq 0 ] + then + local dep_app=${app//_/-} # Replace all '_' by '-' + if ynh_package_is_installed --package="${dep_app}-ynh-deps" + then + current_dependencies="$(dpkg-query --show --showformat='${Depends}' ${dep_app}-ynh-deps) " + fi + + current_dependencies=${current_dependencies// | /|} + fi + + ynh_install_app_dependencies "${current_dependencies}${package}" +} + # Remove fake package and its dependencies # # Dependencies will removed only if no other package need them. @@ -266,3 +316,222 @@ ynh_remove_app_dependencies () { local dep_app=${app//_/-} # Replace all '_' by '-' ynh_package_autopurge ${dep_app}-ynh-deps # Remove the fake package and its dependencies if they not still used. } + +#================================================= + +# Install packages from an extra repository properly. +# +# usage: ynh_install_extra_app_dependencies --repo="repo" --package="dep1 dep2" [--key=key_url] [--name=name] +# | arg: -r, --repo - Complete url of the extra repository. +# | arg: -p, --package - The packages to install from this extra repository +# | arg: -k, --key - url to get the public key. +# | arg: -n, --name - Name for the files for this repo, $app as default value. +ynh_install_extra_app_dependencies () { + # Declare an array to define the options of this helper. + local legacy_args=rpkn + declare -Ar args_array=( [r]=repo= [p]=package= [k]=key= [n]=name= ) + local repo + local package + local key + local name + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + name="${name:-$app}" + key=${key:-0} + + # Set a key only if asked + if [ -n "$key" ] + then + key="--key=$key" + fi + # Add an extra repository for those packages + ynh_install_extra_repo --repo="$repo" $key --priority=995 --name=$name + + # Install requested dependencies from this extra repository. + ynh_add_app_dependencies --package="$package" + + # Remove this extra repository after packages are installed + ynh_remove_extra_repo --name=$app +} + +# Add an extra repository correctly, pin it and get the key. +# +# [internal] +# +# usage: ynh_install_extra_repo --repo="repo" [--key=key_url] [--priority=priority_value] [--name=name] [--append] +# | arg: -r, --repo - Complete url of the extra repository. +# | arg: -k, --key - url to get the public key. +# | arg: -p, --priority - Priority for the pin +# | arg: -n, --name - Name for the files for this repo, $app as default value. +# | arg: -a, --append - Do not overwrite existing files. +ynh_install_extra_repo () { + # Declare an array to define the options of this helper. + local legacy_args=rkpna + declare -Ar args_array=( [r]=repo= [k]=key= [p]=priority= [n]=name= [a]=append ) + local repo + local key + local priority + local name + local append + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + name="${name:-$app}" + append=${append:-0} + key=${key:-0} + priority=${priority:-} + + if [ $append -eq 1 ] + then + append="--append" + wget_append="tee -a" + else + append="" + wget_append="tee" + fi + + # Split the repository into uri, suite and components. + # Remove "deb " at the beginning of the repo. + repo="${repo#deb }" + + # Get the uri + local uri="$(echo "$repo" | awk '{ print $1 }')" + + # Get the suite + local suite="$(echo "$repo" | awk '{ print $2 }')" + + # Get the components + local component="${repo##$uri $suite }" + + # Add the repository into sources.list.d + ynh_add_repo --uri="$uri" --suite="$suite" --component="$component" --name="$name" $append + + # Pin the new repo with the default priority, so it won't be used for upgrades. + # Build $pin from the uri without http and any sub path + local pin="${uri#*://}" + pin="${pin%%/*}" + # Set a priority only if asked + if [ -n "$priority" ] + then + priority="--priority=$priority" + fi + ynh_pin_repo --package="*" --pin="origin \"$pin\"" $priority --name="$name" $append + + # Get the public key for the repo + if [ -n "$key" ] + then + mkdir -p "/etc/apt/trusted.gpg.d" + wget -q "$key" -O - | gpg --dearmor | $wget_append /etc/apt/trusted.gpg.d/$name.gpg > /dev/null + fi + + # Update the list of package with the new repo + ynh_package_update +} + +# Remove an extra repository and the assiociated configuration. +# +# [internal] +# +# usage: ynh_remove_extra_repo [--name=name] +# | arg: -n, --name - Name for the files for this repo, $app as default value. +ynh_remove_extra_repo () { + # Declare an array to define the options of this helper. + local legacy_args=n + declare -Ar args_array=( [n]=name= ) + local name + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + name="${name:-$app}" + + ynh_secure_remove "/etc/apt/sources.list.d/$name.list" + ynh_secure_remove "/etc/apt/preferences.d/$name" + ynh_secure_remove "/etc/apt/trusted.gpg.d/$name.gpg" + ynh_secure_remove "/etc/apt/trusted.gpg.d/$name.asc" + + # Update the list of package to exclude the old repo + ynh_package_update +} + +# Add a repository. +# +# [internal] +# +# usage: ynh_add_repo --uri=uri --suite=suite --component=component [--name=name] [--append] +# | arg: -u, --uri - Uri of the repository. +# | arg: -s, --suite - Suite of the repository. +# | arg: -c, --component - Component of the repository. +# | arg: -n, --name - Name for the files for this repo, $app as default value. +# | arg: -a, --append - Do not overwrite existing files. +# +# Example for a repo like deb http://forge.yunohost.org/debian/ stretch stable +# uri suite component +# ynh_add_repo --uri=http://forge.yunohost.org/debian/ --suite=stretch --component=stable +# +ynh_add_repo () { + # Declare an array to define the options of this helper. + local legacy_args=uscna + declare -Ar args_array=( [u]=uri= [s]=suite= [c]=component= [n]=name= [a]=append ) + local uri + local suite + local component + local name + local append + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + name="${name:-$app}" + append=${append:-0} + + if [ $append -eq 1 ] + then + append="tee -a" + else + append="tee" + fi + + mkdir -p "/etc/apt/sources.list.d" + # Add the new repo in sources.list.d + echo "deb $uri $suite $component" \ + | $append "/etc/apt/sources.list.d/$name.list" +} + +# Pin a repository. +# +# [internal] +# +# usage: ynh_pin_repo --package=packages --pin=pin_filter [--priority=priority_value] [--name=name] [--append] +# | arg: -p, --package - Packages concerned by the pin. Or all, *. +# | arg: -i, --pin - Filter for the pin. +# | arg: -p, --priority - Priority for the pin +# | arg: -n, --name - Name for the files for this repo, $app as default value. +# | arg: -a, --append - Do not overwrite existing files. +# +# See https://manpages.debian.org/stretch/apt/apt_preferences.5.en.html for information about pinning. +# +ynh_pin_repo () { + # Declare an array to define the options of this helper. + local legacy_args=pirna + declare -Ar args_array=( [p]=package= [i]=pin= [r]=priority= [n]=name= [a]=append ) + local package + local pin + local priority + local name + local append + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + package="${package:-*}" + priority=${priority:-50} + name="${name:-$app}" + append=${append:-0} + + if [ $append -eq 1 ] + then + append="tee -a" + else + append="tee" + fi + + mkdir -p "/etc/apt/preferences.d" + echo "Package: $package +Pin: $pin +Pin-Priority: $priority" \ + | $append "/etc/apt/preferences.d/$name" +} From 7ba253cb18b4badaa5467101417b5e06327155e6 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sun, 9 Feb 2020 20:08:00 +0100 Subject: [PATCH 0616/3170] Add the helper ynh_get_scalable_phpfpm And adapt ynh_add_fpm_config to generate a fpm config file without a template by using ynh_get_scalable_phpfpm --- data/helpers.d/php | 252 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 244 insertions(+), 8 deletions(-) diff --git a/data/helpers.d/php b/data/helpers.d/php index 224c0a3d9..5e7a7ec78 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -2,18 +2,47 @@ # Create a dedicated php-fpm config # -# usage: ynh_add_fpm_config [--phpversion=7.X] +# usage 1: ynh_add_fpm_config [--phpversion=7.X] [--use_template] # | arg: -v, --phpversion - Version of php to use. +# | arg: -t, --use_template - Use this helper in template mode. +# +# ----------------------------------------------------------------------------- +# +# usage 2: ynh_add_fpm_config [--phpversion=7.X] --usage=usage --footprint=footprint +# | arg: -v, --phpversion - Version of php to use.# +# | arg: -f, --footprint - Memory footprint of the service (low/medium/high). +# low - Less than 20Mb of ram by pool. +# medium - Between 20Mb and 40Mb of ram by pool. +# high - More than 40Mb of ram by pool. +# Or specify exactly the footprint, the load of the service as Mb by pool instead of having a standard value. +# To have this value, use the following command and stress the service. +# watch -n0.5 ps -o user,cmd,%cpu,rss -u APP +# +# | arg: -u, --usage - Expected usage of the service (low/medium/high). +# low - Personal usage, behind the sso. +# medium - Low usage, few people or/and publicly accessible. +# high - High usage, frequently visited website. # # Requires YunoHost version 2.7.2 or higher. ynh_add_fpm_config () { # Declare an array to define the options of this helper. - local legacy_args=v - declare -Ar args_array=( [v]=phpversion= ) + local legacy_args=vtuf + declare -Ar args_array=( [v]=phpversion= [t]=use_template [u]=usage= [f]=footprint= ) local phpversion + local use_template + local usage + local footprint # Manage arguments with getopts ynh_handle_getopts_args "$@" + # The default behaviour is to use the template. + use_template="${use_template:-1}" + usage="${usage:-}" + footprint="${footprint:-}" + if [ -n "$usage" ] || [ -n "$footprint" ]; then + use_template=0 + fi + # Configure PHP-FPM 7.0 by default phpversion="${phpversion:-7.0}" @@ -28,11 +57,65 @@ ynh_add_fpm_config () { ynh_app_setting_set --app=$app --key=fpm_service --value="$fpm_service" finalphpconf="$fpm_config_dir/pool.d/$app.conf" ynh_backup_if_checksum_is_different --file="$finalphpconf" - cp ../conf/php-fpm.conf "$finalphpconf" - ynh_replace_string --match_string="__NAMETOCHANGE__" --replace_string="$app" --target_file="$finalphpconf" - ynh_replace_string --match_string="__FINALPATH__" --replace_string="$final_path" --target_file="$finalphpconf" - ynh_replace_string --match_string="__USER__" --replace_string="$app" --target_file="$finalphpconf" - ynh_replace_string --match_string="__PHPVERSION__" --replace_string="$phpversion" --target_file="$finalphpconf" + + if [ $use_template -eq 1 ] + then + # Usage 1, use the template in ../conf/php-fpm.conf + cp ../conf/php-fpm.conf "$finalphpconf" + ynh_replace_string --match_string="__NAMETOCHANGE__" --replace_string="$app" --target_file="$finalphpconf" + ynh_replace_string --match_string="__FINALPATH__" --replace_string="$final_path" --target_file="$finalphpconf" + ynh_replace_string --match_string="__USER__" --replace_string="$app" --target_file="$finalphpconf" + ynh_replace_string --match_string="__PHPVERSION__" --replace_string="$phpversion" --target_file="$finalphpconf" + + else + # Usage 2, generate a php-fpm config file with ynh_get_scalable_phpfpm + ynh_get_scalable_phpfpm --usage=$usage --footprint=$footprint + + # Copy the default file + cp "$fpm_config_dir/pool.d/www.conf" "$finalphpconf" + + # Replace standard variables into the default file + ynh_replace_string --match_string="^\[www\]" --replace_string="[$app]" --target_file="$finalphpconf" + ynh_replace_string --match_string=".*listen = .*" --replace_string="listen = /var/run/php/php$phpversion-fpm-$app.sock" --target_file="$finalphpconf" + ynh_replace_string --match_string="^user = .*" --replace_string="user = $app" --target_file="$finalphpconf" + ynh_replace_string --match_string="^group = .*" --replace_string="group = $app" --target_file="$finalphpconf" + ynh_replace_string --match_string=".*chdir = .*" --replace_string="chdir = $final_path" --target_file="$finalphpconf" + + # Configure fpm children + ynh_replace_string --match_string=".*pm = .*" --replace_string="pm = $php_pm" --target_file="$finalphpconf" + ynh_replace_string --match_string=".*pm.max_children = .*" --replace_string="pm.max_children = $php_max_children" --target_file="$finalphpconf" + ynh_replace_string --match_string=".*pm.max_requests = .*" --replace_string="pm.max_requests = 500" --target_file="$finalphpconf" + ynh_replace_string --match_string=".*request_terminate_timeout = .*" --replace_string="request_terminate_timeout = 1d" --target_file="$finalphpconf" + if [ "$php_pm" = "dynamic" ] + then + ynh_replace_string --match_string=".*pm.start_servers = .*" --replace_string="pm.start_servers = $php_start_servers" --target_file="$finalphpconf" + ynh_replace_string --match_string=".*pm.min_spare_servers = .*" --replace_string="pm.min_spare_servers = $php_min_spare_servers" --target_file="$finalphpconf" + ynh_replace_string --match_string=".*pm.max_spare_servers = .*" --replace_string="pm.max_spare_servers = $php_max_spare_servers" --target_file="$finalphpconf" + elif [ "$php_pm" = "ondemand" ] + then + ynh_replace_string --match_string=".*pm.process_idle_timeout = .*" --replace_string="pm.process_idle_timeout = 10s" --target_file="$finalphpconf" + fi + + # Comment unused parameters + if [ "$php_pm" != "dynamic" ] + then + ynh_replace_string --match_string=".*\(pm.start_servers = .*\)" --replace_string=";\1" --target_file="$finalphpconf" + ynh_replace_string --match_string=".*\(pm.min_spare_servers = .*\)" --replace_string=";\1" --target_file="$finalphpconf" + ynh_replace_string --match_string=".*\(pm.max_spare_servers = .*\)" --replace_string=";\1" --target_file="$finalphpconf" + fi + if [ "$php_pm" != "ondemand" ] + then + ynh_replace_string --match_string=".*\(pm.process_idle_timeout = .*\)" --replace_string=";\1" --target_file="$finalphpconf" + fi + + # Concatene the extra config. + if [ -e ../conf/extra_php-fpm.conf ]; then + cat ../conf/extra_php-fpm.conf >> "$finalphpconf" + fi + fi + + + chown root: "$finalphpconf" ynh_store_file_checksum --file="$finalphpconf" @@ -45,6 +128,7 @@ ynh_add_fpm_config () { chown root: "$finalphpini" ynh_store_file_checksum "$finalphpini" fi + ynh_systemd_action --service_name=$fpm_service --action=reload } @@ -145,3 +229,155 @@ ynh_remove_php () { ynh_secure_remove /etc/php/ynh_app_version fi } + +# Define the values to configure php-fpm +# +# usage: ynh_get_scalable_phpfpm --usage=usage --footprint=footprint [--print] +# | arg: -f, --footprint - Memory footprint of the service (low/medium/high). +# low - Less than 20Mb of ram by pool. +# medium - Between 20Mb and 40Mb of ram by pool. +# high - More than 40Mb of ram by pool. +# Or specify exactly the footprint, the load of the service as Mb by pool instead of having a standard value. +# To have this value, use the following command and stress the service. +# watch -n0.5 ps -o user,cmd,%cpu,rss -u APP +# +# | arg: -u, --usage - Expected usage of the service (low/medium/high). +# low - Personal usage, behind the sso. +# medium - Low usage, few people or/and publicly accessible. +# high - High usage, frequently visited website. +# +# | arg: -p, --print - Print the result +# +# +# The footprint of the service will be used to defined the maximum footprint we can allow, which is half the maximum RAM. +# So it will be used to defined 'pm.max_children' +# A lower value for the footprint will allow more children for 'pm.max_children'. And so for +# 'pm.start_servers', 'pm.min_spare_servers' and 'pm.max_spare_servers' which are defined from the +# value of 'pm.max_children' +# NOTE: 'pm.max_children' can't exceed 4 times the number of processor's cores. +# +# The usage value will defined the way php will handle the children for the pool. +# A value set as 'low' will set the process manager to 'ondemand'. Children will start only if the +# service is used, otherwise no child will stay alive. This config gives the lower footprint when the +# service is idle. But will use more proc since it has to start a child as soon it's used. +# Set as 'medium', the process manager will be at dynamic. If the service is idle, a number of children +# equal to pm.min_spare_servers will stay alive. So the service can be quick to answer to any request. +# The number of children can grow if needed. The footprint can stay low if the service is idle, but +# not null. The impact on the proc is a little bit less than 'ondemand' as there's always a few +# children already available. +# Set as 'high', the process manager will be set at 'static'. There will be always as many children as +# 'pm.max_children', the footprint is important (but will be set as maximum a quarter of the maximum +# RAM) but the impact on the proc is lower. The service will be quick to answer as there's always many +# children ready to answer. +ynh_get_scalable_phpfpm () { + local legacy_args=ufp + # Declare an array to define the options of this helper. + declare -Ar args_array=( [u]=usage= [f]=footprint= [p]=print ) + local usage + local footprint + local print + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + # Set all characters as lowercase + footprint=${footprint,,} + usage=${usage,,} + print=${print:-0} + + if [ "$footprint" = "low" ] + then + footprint=20 + elif [ "$footprint" = "medium" ] + then + footprint=35 + elif [ "$footprint" = "high" ] + then + footprint=50 + fi + + # Define the way the process manager handle child processes. + if [ "$usage" = "low" ] + then + php_pm=ondemand + elif [ "$usage" = "medium" ] + then + php_pm=dynamic + elif [ "$usage" = "high" ] + then + php_pm=static + else + ynh_die --message="Does not recognize '$usage' as an usage value." + fi + + # Get the total of RAM available, except swap. + local max_ram=$(ynh_check_ram --no_swap) + + less0() { + # Do not allow value below 1 + if [ $1 -le 0 ] + then + echo 1 + else + echo $1 + fi + } + + # Define pm.max_children + # The value of pm.max_children is the total amount of ram divide by 2 and divide again by the footprint of a pool for this app. + # So if php-fpm start the maximum of children, it won't exceed half of the ram. + php_max_children=$(( $max_ram / 2 / $footprint )) + # If process manager is set as static, use half less children. + # Used as static, there's always as many children as the value of pm.max_children + if [ "$php_pm" = "static" ] + then + php_max_children=$(( $php_max_children / 2 )) + fi + php_max_children=$(less0 $php_max_children) + + # To not overload the proc, limit the number of children to 4 times the number of cores. + local core_number=$(nproc) + local max_proc=$(( $core_number * 4 )) + if [ $php_max_children -gt $max_proc ] + then + php_max_children=$max_proc + fi + + if [ "$php_pm" = "dynamic" ] + then + # Define pm.start_servers, pm.min_spare_servers and pm.max_spare_servers for a dynamic process manager + php_min_spare_servers=$(( $php_max_children / 8 )) + php_min_spare_servers=$(less0 $php_min_spare_servers) + + php_max_spare_servers=$(( $php_max_children / 2 )) + php_max_spare_servers=$(less0 $php_max_spare_servers) + + php_start_servers=$(( $php_min_spare_servers + ( $php_max_spare_servers - $php_min_spare_servers ) /2 )) + php_start_servers=$(less0 $php_start_servers) + else + php_min_spare_servers=0 + php_max_spare_servers=0 + php_start_servers=0 + fi + + if [ $print -eq 1 ] + then + ynh_debug --message="Footprint=${footprint}Mb by pool." + ynh_debug --message="Process manager=$php_pm" + ynh_debug --message="Max RAM=${max_ram}Mb" + if [ "$php_pm" != "static" ]; then + ynh_debug --message="\nMax estimated footprint=$(( $php_max_children * $footprint ))" + ynh_debug --message="Min estimated footprint=$(( $php_min_spare_servers * $footprint ))" + fi + if [ "$php_pm" = "dynamic" ]; then + ynh_debug --message="Estimated average footprint=$(( $php_max_spare_servers * $footprint ))" + elif [ "$php_pm" = "static" ]; then + ynh_debug --message="Estimated footprint=$(( $php_max_children * $footprint ))" + fi + ynh_debug --message="\nRaw php-fpm values:" + ynh_debug --message="pm.max_children = $php_max_children" + if [ "$php_pm" = "dynamic" ]; then + ynh_debug --message="pm.start_servers = $php_start_servers" + ynh_debug --message="pm.min_spare_servers = $php_min_spare_servers" + ynh_debug --message="pm.max_spare_servers = $php_max_spare_servers" + fi + fi +} From 96095624f5c506340954a2b86b41a41ba93b0f7f Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sun, 9 Feb 2020 20:10:27 +0100 Subject: [PATCH 0617/3170] Add the helper ynh_check_ram --- data/helpers.d/hardware | 72 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 data/helpers.d/hardware diff --git a/data/helpers.d/hardware b/data/helpers.d/hardware new file mode 100644 index 000000000..11012a3d1 --- /dev/null +++ b/data/helpers.d/hardware @@ -0,0 +1,72 @@ +#!/bin/bash + +# Check the amount of available RAM +# +# usage: ynh_check_ram [--required=RAM required in Mb] [--no_swap|--only_swap] [--free_ram] +# | arg: -r, --required= - Amount of RAM required in Mb. The helper will return 0 is there's enough RAM, or 1 otherwise. +# If --required isn't set, the helper will print the amount of RAM, in Mb. +# | arg: -s, --no_swap - Ignore swap +# | arg: -o, --only_swap - Ignore real RAM, consider only swap. +# | arg: -f, --free_ram - Count only free RAM, not the total amount of RAM available. +ynh_check_ram () { + # Declare an array to define the options of this helper. + declare -Ar args_array=( [r]=required= [s]=no_swap [o]=only_swap [f]=free_ram ) + local required + local no_swap + local only_swap + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + required=${required:-} + no_swap=${no_swap:-0} + only_swap=${only_swap:-0} + + local total_ram=$(vmstat --stats --unit M | grep "total memory" | awk '{print $1}') + local total_swap=$(vmstat --stats --unit M | grep "total swap" | awk '{print $1}') + local total_ram_swap=$(( total_ram + total_swap )) + + local free_ram=$(vmstat --stats --unit M | grep "free memory" | awk '{print $1}') + local free_swap=$(vmstat --stats --unit M | grep "free swap" | awk '{print $1}') + local free_ram_swap=$(( free_ram + free_swap )) + + # Use the total amount of ram + local ram=$total_ram_swap + if [ $free_ram -eq 1 ] + then + # Use the total amount of free ram + ram=$free_ram_swap + if [ $no_swap -eq 1 ] + then + # Use only the amount of free ram + ram=$free_ram + elif [ $only_swap -eq 1 ] + then + # Use only the amount of free swap + ram=$free_swap + fi + else + if [ $no_swap -eq 1 ] + then + # Use only the amount of free ram + ram=$total_ram + elif [ $only_swap -eq 1 ] + then + # Use only the amount of free swap + ram=$total_swap + fi + fi + + if [ -n "$required" ] + then + # Return 1 if the amount of ram isn't enough. + if [ $ram -lt $required ] + then + return 1 + else + return 0 + fi + + # If no RAM is required, return the amount of available ram. + else + echo $ram + fi +} From e3bcc4b4c93053f9c728929b2b7ab7f610f9d0fa Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Mon, 24 Feb 2020 13:54:43 +0100 Subject: [PATCH 0618/3170] Fix pin priority issue --- data/helpers.d/apt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index 0f973dda5..756f077ab 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -251,8 +251,8 @@ ynh_install_app_dependencies () { ynh_install_extra_repo --repo="https://packages.sury.org/php/ $(lsb_release -sc) main" --key="https://packages.sury.org/php/apt.gpg" --name=extra_php_version # Pin this sury repository to prevent sury of doing shit - ynh_pin_repo --package="*" --pin="origin \"packages.sury.org\"" 200 --name=extra_php_version - ynh_pin_repo --package="php7.0*" --pin="origin \"packages.sury.org\"" 600 --name=extra_php_version --append + ynh_pin_repo --package="*" --pin="origin \"packages.sury.org\"" --priority=200 --name=extra_php_version + ynh_pin_repo --package="php7.0*" --pin="origin \"packages.sury.org\"" --priority=600 --name=extra_php_version --append fi fi fi From c163ae2949368ebfaa44cb65102cda0d60c80ad6 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Thu, 16 Jan 2020 00:34:11 +0700 Subject: [PATCH 0619/3170] fix legacy permission management --- data/helpers.d/setting | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index 9dbbe93fa..5bcd7af32 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -187,9 +187,15 @@ EOF # Fucking legacy permission management. # We need this because app temporarily set the app as unprotected to configure it with curl... - if [[ "$3" =~ ^(unprotected|skipped)_ ]] && [[ "${4:-}" == "/" ]] + if [[ "$3" =~ ^(unprotected|skipped)_ ]] then - ynh_permission_update --permission "main" --add "visitors" + if [[ "$1" == "set" ]] && [[ "${4:-}" == "/" ]] + then + ynh_permission_update --permission "main" --add "visitors" + elif [[ "$1" == "delete" ]] + then + ynh_permission_update --permission "main" --remove "visitors" + fi fi } From fc969ae1d448b4209c27c2cae8222fbcc12a5b64 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Wed, 29 Jan 2020 14:24:59 +0700 Subject: [PATCH 0620/3170] remove visitors only for if current value is / --- data/helpers.d/setting | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index 5bcd7af32..9f68cb5d9 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -158,7 +158,12 @@ ynh_add_protected_uris() { # ynh_app_setting() { - ACTION="$1" APP="$2" KEY="$3" VALUE="${4:-}" python - < Date: Wed, 29 Jan 2020 21:17:14 +0700 Subject: [PATCH 0621/3170] more informations in hooks permission --- src/yunohost/permission.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 9cc7c7534..9d3d8feda 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -471,16 +471,22 @@ def _update_ldap_group_permission(permission, allowed, sync_perm=True): app = permission.split(".")[0] sub_permission = permission.split(".")[1] - old_allowed_users = set(existing_permission["corresponding_users"]) - new_allowed_users = set(new_permission["corresponding_users"]) + old_corresponding_users = set(existing_permission["corresponding_users"]) + new_corresponding_users = set(new_permission["corresponding_users"]) - effectively_added_users = new_allowed_users - old_allowed_users - effectively_removed_users = old_allowed_users - new_allowed_users + old_allowed_users = set(existing_permission["allowed"]) + new_allowed_users = set(new_permission["allowed"]) - if effectively_added_users: - hook_callback('post_app_addaccess', args=[app, ','.join(effectively_added_users), sub_permission]) - if effectively_removed_users: - hook_callback('post_app_removeaccess', args=[app, ','.join(effectively_removed_users), sub_permission]) + effectively_added_users = new_corresponding_users - old_corresponding_users + effectively_removed_users = old_corresponding_users - new_corresponding_users + + effectively_added_group = new_allowed_users - old_allowed_users - effectively_added_users + effectively_removed_group = old_allowed_users - new_allowed_users - effectively_removed_users + + if effectively_added_users or effectively_added_group: + hook_callback('post_app_addaccess', args=[app, ','.join(effectively_added_users), sub_permission, ','.join(effectively_added_group)]) + if effectively_removed_users or effectively_removed_group: + hook_callback('post_app_removeaccess', args=[app, ','.join(effectively_removed_users), sub_permission, ','.join(effectively_removed_group)]) return new_permission From e6481b156fe7897b51d60d55ea2f63a22f50ae62 Mon Sep 17 00:00:00 2001 From: Jimmy Monin Date: Sat, 29 Feb 2020 22:07:45 +0100 Subject: [PATCH 0622/3170] Persist cookies between multiple ynh_local_curl calls for the same app --- data/helpers.d/utils | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index d449f0c39..50671dba0 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -198,6 +198,7 @@ ynh_setup_source () { } # Curl abstraction to help with POST requests to local pages (such as installation forms) +# For multiple calls, cookies are persisted between each call for the same app # # $domain and $path_url should be defined externally (and correspond to the domain.tld and the /path (of the app?)) # @@ -238,7 +239,7 @@ ynh_local_curl () { sleep 2 # Curl the URL - curl --silent --show-error -kL -H "Host: $domain" --resolve $domain:443:127.0.0.1 $POST_data "$full_page_url" + curl --silent --show-error -kL -H "Host: $domain" --resolve $domain:443:127.0.0.1 $POST_data "$full_page_url" --cookie-jar /tmp/ynh-$app-cookie.txt --cookie /tmp/ynh-$app-cookie.txt } # Render templates with Jinja2 From 200ff2de3137f5bcff94aced28eaa745304903b7 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 5 Mar 2020 18:15:55 +0100 Subject: [PATCH 0623/3170] Micro issue from previous app list system rework --- 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 b05d7b818..056083b67 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -2754,7 +2754,7 @@ def unstable_apps(): output = [] - for infos in app_list(full=True): + for infos in app_list(full=True)["apps"]: if not infos.get("from_catalog") or infos.get("from_catalog").get("state") in ["inprogress", "notworking"]: output.append(infos["id"]) From 3bf2df16eeecbf804f92d1e30f5d7baa1ebbb4ed Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 5 Mar 2020 20:00:39 +0100 Subject: [PATCH 0624/3170] Fix tests for appscatalog ? --- src/yunohost/tests/test_appscatalog.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/tests/test_appscatalog.py b/src/yunohost/tests/test_appscatalog.py index 39a0be206..a51d1085e 100644 --- a/src/yunohost/tests/test_appscatalog.py +++ b/src/yunohost/tests/test_appscatalog.py @@ -31,8 +31,8 @@ DUMMY_APP_CATALOG = """{ "bar": {"id": "bar", "level": 7, "category": "swag", "manifest":{"description": "Bar"}} }, "categories": [ - {"id": "yolo", "description": "YoLo"}, - {"id": "swag", "description": "sWaG"} + {"id": "yolo", "description": "YoLo", "title": "Yolo"}, + {"id": "swag", "description": "sWaG", "title": "Swag"} ] } """ From 1a7fcd09b0edd9f2907c235d6d4753d15720d0b3 Mon Sep 17 00:00:00 2001 From: Patrick Baeumel Date: Sun, 12 Jan 2020 10:37:39 +0000 Subject: [PATCH 0625/3170] Translated using Weblate (German) Currently translated at 33.8% (205 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/de.json b/locales/de.json index 83b4b1210..e4b3b0c05 100644 --- a/locales/de.json +++ b/locales/de.json @@ -430,5 +430,6 @@ "apps_catalog_failed_to_download": "Der {apps_catalog} Apps-Katalog kann nicht heruntergeladen werden: {error}", "apps_catalog_obsolete_cache": "Der Cache des Apps-Katalogs ist leer oder veraltet.", "apps_catalog_update_success": "Der Apps-Katalog wurde aktualisiert!", - "password_too_simple_1": "Das Passwort muss mindestens 8 Zeichen lang sein" + "password_too_simple_1": "Das Passwort muss mindestens 8 Zeichen lang sein", + "diagnosis_display_tip_cli": "Sie können 'yunohost diagnosis show --issues' ausführen, um die gefundenen Probleme anzuzeigen." } From 3ebad369284e96883542e14991ab8a0d1a3c32c2 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Mon, 30 Dec 2019 14:25:33 +0000 Subject: [PATCH 0626/3170] Translated using Weblate (Chinese (Simplified)) Currently translated at 0.2% (1 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/zh_Hans/ --- locales/zh_Hans.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/locales/zh_Hans.json b/locales/zh_Hans.json index 0967ef424..cb5d7002c 100644 --- a/locales/zh_Hans.json +++ b/locales/zh_Hans.json @@ -1 +1,3 @@ -{} +{ + "password_too_simple_1": "密码长度至少为8个字符" +} From 3774a2c7ad8dbd0efe7d588cb485e67a3e010800 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quent=C3=AD?= Date: Fri, 17 Jan 2020 12:05:49 +0000 Subject: [PATCH 0627/3170] Translated using Weblate (Occitan) Currently translated at 70.8% (430 of 607 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 4251a3307..e0fbaa45c 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -720,5 +720,7 @@ "diagnosis_ip_weird_resolvconf": "La resolucion del nom de domeni sembla foncionar, mas siatz prudent en utilizant un fichièr /etc/resolv.con personalizat.", "diagnosis_diskusage_verylow": "Lo lòc d’emmagazinatge {mountpoint} (sul periferic {device}) a solament {free_abs_GB} Go ({free_percent}%). Deuriatz considerar de liberar un pauc d’espaci.", "global_settings_setting_pop3_enabled": "Activar lo protocòl POP3 pel servidor de corrièr", - "diagnosis_diskusage_ok": "Lo lòc d’emmagazinatge {mountpoint} (sul periferic {device}) a encara {free_abs_GB} Go ({free_percent}%) de liure !" + "diagnosis_diskusage_ok": "Lo lòc d’emmagazinatge {mountpoint} (sul periferic {device}) a encara {free_abs_GB} Go ({free_percent}%) de liure !", + "diagnosis_swap_none": "Lo sistèma a pas cap de memòria d’escambi. Auriatz de considerar d’ajustar almens 256 Mo d’escambi per evitar las situacions ont lo sistèma manca de memòria.", + "diagnosis_swap_notsomuch": "Lo sistèma a solament {total_MB} de memòria d’escambi. Auriatz de considerar d’ajustar almens 256 Mo d’escambi per evitar las situacions ont lo sistèma manca de memòria." } From 45c35fdba58f0b80525ee6d434d00b0a22454a2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9lanie=20Chauvel?= Date: Fri, 17 Jan 2020 23:40:23 +0000 Subject: [PATCH 0628/3170] Translated using Weblate (French) Currently translated at 100.0% (610 of 610 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index f69dea8f9..eda84fabf 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -747,7 +747,7 @@ "diagnosis_services_running": "Le service {service} s'exécute correctement !", "diagnosis_services_conf_broken": "La configuration est cassée pour le service {service} !", "diagnosis_ports_needed_by": "Rendre ce port accessible est nécessaire pour le service {0}", - "diagnosis_ports_forwarding_tip": "Pour résoudre ce problème, vous devez probablement configurer la redirection de port sur votre routeur Internet, comme décrit dans https://yunohost.org/isp_box_config.", + "diagnosis_ports_forwarding_tip": "Pour résoudre ce problème, vous devez probablement configurer la redirection de port sur votre routeur Internet comme décrit sur https://yunohost.org/isp_box_config", "diagnosis_http_connection_error": "Erreur de connexion : impossible de se connecter au domaine demandé, il est probablement injoignable.", "diagnosis_no_cache": "Pas encore de diagnostique de cache pour la catégorie « {category} »", "diagnosis_http_unknown_error": "Une erreur est survenue en essayant de joindre votre domaine, il est probablement injoignable.", @@ -757,5 +757,8 @@ "diagnosis_services_bad_status_tip": "Vous pouvez essayer de redémarrer le service. Si cela ne fonctionne pas, consultez les journaux de service à l'aide de 'yunohost service log {0}' ou de la section 'Services' de l'administrateur Web.", "diagnosis_http_bad_status_code": "N'a pas pu atteindre votre serveur comme prévu, il a renvoyé un code d'état incorrect. Il se peut qu'une autre machine réponde à la place de votre serveur. Vérifiez que vous transférez correctement le port 80, que votre configuration nginx est à jour et qu’un proxy inverse n’interfère pas.", "diagnosis_http_timeout": "Expiration du délai en essayant de contacter votre serveur de l'extérieur. Il semble être inaccessible. Vérifiez que vous transférez correctement le port 80, que nginx est en cours d’exécution et qu’un pare-feu n’interfère pas.", - "global_settings_setting_pop3_enabled": "Activer le protocole POP3 pour le serveur de messagerie" + "global_settings_setting_pop3_enabled": "Activer le protocole POP3 pour le serveur de messagerie", + "log_app_action_run": "Lancer l’action de l’application '{}'", + "log_app_config_show_panel": "Montrer le panneau de configuration de l’application '{}'", + "log_app_config_apply": "Appliquer la configuration à l’application '{}'" } From b13cb78c97acc799be0e12eaea2585ad078f121d Mon Sep 17 00:00:00 2001 From: Armando FEMAT Date: Sat, 25 Jan 2020 12:35:02 +0000 Subject: [PATCH 0629/3170] Translated using Weblate (Spanish) Currently translated at 92.5% (564 of 610 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/es/ --- locales/es.json | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/locales/es.json b/locales/es.json index 729e262b5..767dc6bfa 100644 --- a/locales/es.json +++ b/locales/es.json @@ -682,5 +682,28 @@ "diagnosis_services_conf_broken": "¡Mala configuración para el servicio {service}!", "diagnosis_services_running": "¡El servicio {service} está en ejecución!", "diagnosis_failed": "No se ha podido obtener el resultado del diagnóstico para la categoría '{category}': {error}", - "diagnosis_ip_connected_ipv4": "¡El servidor está conectado a internet a través de IPv4!" + "diagnosis_ip_connected_ipv4": "¡El servidor está conectado a internet a través de IPv4!", + "diagnosis_security_vulnerable_to_meltdown_details": "Para corregir esto, debieras actualizar y reiniciar tu sistema para cargar el nuevo kernel de Linux (o contacta tu proveedor si esto no funciona). Mas información en https://meltdownattack.com/", + "diagnosis_ram_verylow": "Al sistema le queda solamente {available_abs_MB} MB ({available_percent}%) de RAM! (De un total de {total_abs_MB} MB)", + "diagnosis_ram_low": "Al sistema le queda {available_abs_MB} MB ({available_percent}%) de RAM de un total de {total_abs_MB} MB. Cuidado.", + "diagnosis_ram_ok": "El sistema aun tiene {available_abs_MB} MB ({available_percent}%) de RAM de un total de {total_abs_MB} MB.", + "diagnosis_swap_none": "El sistema no tiene mas espacio de intercambio. Considera agregar por lo menos 256 MB de espacio de intercambio para evitar que el sistema se quede sin memoria.", + "diagnosis_swap_notsomuch": "Al sistema le queda solamente {total_MB} MB de espacio de intercambio. Considera agregar al menos 256 MB para evitar que el sistema se quede sin memoria.", + "diagnosis_mail_ougoing_port_25_ok": "El puerto de salida 25 no esta bloqueado y los correos electrónicos pueden ser enviados a otros servidores.", + "diagnosis_mail_ougoing_port_25_blocked": "El puerto de salida 25 parece estar bloqueado. Intenta desbloquearlo con el panel de configuración de tu proveedor de servicios de Internet (o hoster). Mientras tanto, el servidor no podrá enviar correos electrónicos a otros servidores.", + "diagnosis_regenconf_allgood": "Todos los archivos de configuración están en linea con la configuración recomendada!", + "diagnosis_regenconf_manually_modified": "El archivo de configuración {file} fue modificado manualmente.", + "diagnosis_regenconf_manually_modified_details": "Esto este probablemente BIEN siempre y cuando sepas lo que estas haciendo ;) !", + "diagnosis_regenconf_manually_modified_debian": "El archivos de configuración {file} fue modificado manualmente comparado con el valor predeterminado de Debian.", + "diagnosis_regenconf_manually_modified_debian_details": "Esto este probablemente BIEN, pero igual no lo pierdas de vista...", + "diagnosis_regenconf_nginx_conf_broken": "La configuración nginx parece rota!", + "diagnosis_security_all_good": "Ninguna vulnerabilidad critica de seguridad fue encontrada.", + "diagnosis_security_vulnerable_to_meltdown": "Pareces vulnerable a el colapso de vulnerabilidad critica de seguridad.", + "diagnosis_description_basesystem": "Sistema de base", + "diagnosis_description_ip": "Conectividad a Internet", + "diagnosis_description_dnsrecords": "Registro DNS", + "diagnosis_description_services": "Comprobación del estado de los servicios", + "diagnosis_description_ports": "Exposición de puertos", + "diagnosis_description_systemresources": "Recursos del sistema", + "diagnosis_swap_ok": "El sistema tiene {total_MB} MB de espacio de intercambio!" } From e506f93445c14830cfbc574c64c2dc4bcf765bc0 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Fri, 24 Jan 2020 06:42:20 +0000 Subject: [PATCH 0630/3170] Translated using Weblate (Esperanto) Currently translated at 100.0% (610 of 610 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/eo/ --- locales/eo.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/locales/eo.json b/locales/eo.json index f303a57ee..e204af4c6 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -659,5 +659,8 @@ "diagnosis_failed": "Malsukcesis preni la diagnozan rezulton por kategorio '{category}': {error}", "diagnosis_description_ports": "Ekspoziciaj havenoj", "diagnosis_description_http": "HTTP-ekspozicio", - "diagnosis_description_mail": "Retpoŝto" + "diagnosis_description_mail": "Retpoŝto", + "log_app_action_run": "Funkciigu agon de la apliko '{}'", + "log_app_config_show_panel": "Montri la agordan panelon de la apliko '{}'", + "log_app_config_apply": "Apliki agordon al la apliko '{}'" } From ee405971e5458d6061ddb138aa5316b191d318d7 Mon Sep 17 00:00:00 2001 From: Kayou Date: Fri, 31 Jan 2020 13:55:57 +0000 Subject: [PATCH 0631/3170] Translated using Weblate (French) Currently translated at 100.0% (610 of 610 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 eda84fabf..0e4c9bfd8 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -674,7 +674,7 @@ "diagnosis_ip_weird_resolvconf": "La résolution DNS semble fonctionner, mais soyer prudent en utilisant un fichier /etc/resolv.conf personnalisé.", "diagnosis_ip_weird_resolvconf_details": "Au lieu de cela, ce fichier devrait être un lien symbolique vers /etc/resolvconf/run/resolv.conf lui-même pointant vers 127.0.0.1 (dnsmasq). Les résolveurs réels doivent être configurés via /etc/resolv.dnsmasq.conf.", "diagnosis_dns_missing_record": "Selon la configuration DNS recommandée, vous devez ajouter un enregistrement DNS de type {0}, nom {1} et valeur {2}. Vous pouvez consulter https://yunohost.org/dns_config pour plus d'informations.", - "diagnosis_diskusage_ok": "Le stockage {mountpoint} (sur le périphérique {device}) a toujours {espace libre {free_abs_GB} GB ({free_percent}%) !", + "diagnosis_diskusage_ok": "Le stockage {mountpoint} (sur le périphérique {device}) a toujours {free_abs_GB} Go ({free_percent}%) d'espace libre !", "diagnosis_ram_ok": "Le système dispose toujours de {available_abs_MB} MB ({available_percent}%) de RAM sur {total_abs_MB} MB.", "diagnosis_regenconf_allgood": "Tous les fichiers de configuration sont conformes à la configuration recommandée !", "diagnosis_security_vulnerable_to_meltdown": "Vous semblez vulnérable à la vulnérabilité de sécurité critique de Meltdown", From 88be979d4013e7da7975d3c67f2b8df4213a307c Mon Sep 17 00:00:00 2001 From: xaloc33 Date: Sun, 9 Feb 2020 12:13:52 +0000 Subject: [PATCH 0632/3170] Translated using Weblate (Catalan) Currently translated at 100.0% (610 of 610 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ca/ --- locales/ca.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/locales/ca.json b/locales/ca.json index 40977469c..9e36d7009 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -719,5 +719,8 @@ "diagnosis_ports_needed_by": "És necessari exposar aquest port pel servei {0}", "permission_all_users_implicitly_added": "El permís també s'ha donat implícitament a «all_users» ja que és necessari per atorgar-lo al grup «visitors»", "permission_cannot_remove_all_users_while_visitors_allowed": "No podeu retirar el permís a «all_users» mentre encara el tingui el grup «visitors»", - "global_settings_setting_pop3_enabled": "Activa el protocol POP3 per al servidor de correu" + "global_settings_setting_pop3_enabled": "Activa el protocol POP3 per al servidor de correu", + "log_app_action_run": "Executa l'acció de l'aplicació «{}»", + "log_app_config_show_panel": "Mostra el taulell de configuració de l'aplicació «{}»", + "log_app_config_apply": "Afegeix la configuració a l'aplicació «{}»" } From 97e3493aa51500f1fc0c05b8086111e023d466b9 Mon Sep 17 00:00:00 2001 From: tituspijean Date: Mon, 9 Mar 2020 20:00:38 +0000 Subject: [PATCH 0633/3170] Translated using Weblate (French) Currently translated at 100.0% (610 of 610 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 0e4c9bfd8..e10723b6b 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -671,7 +671,7 @@ "diagnosis_found_errors": "Trouvé {errors} problème(s) significatif(s) lié(s) à {category} !", "diagnosis_found_errors_and_warnings": "Trouvé {errors} problème(s) significatif(s) (et {warnings} (avertissement(s)) en relation avec {category} !", "diagnosis_ip_not_connected_at_all": "Le serveur ne semble pas du tout connecté à Internet !?", - "diagnosis_ip_weird_resolvconf": "La résolution DNS semble fonctionner, mais soyer prudent en utilisant un fichier /etc/resolv.conf personnalisé.", + "diagnosis_ip_weird_resolvconf": "La résolution DNS semble fonctionner, mais soyez prudent en utilisant un fichier /etc/resolv.conf personnalisé.", "diagnosis_ip_weird_resolvconf_details": "Au lieu de cela, ce fichier devrait être un lien symbolique vers /etc/resolvconf/run/resolv.conf lui-même pointant vers 127.0.0.1 (dnsmasq). Les résolveurs réels doivent être configurés via /etc/resolv.dnsmasq.conf.", "diagnosis_dns_missing_record": "Selon la configuration DNS recommandée, vous devez ajouter un enregistrement DNS de type {0}, nom {1} et valeur {2}. Vous pouvez consulter https://yunohost.org/dns_config pour plus d'informations.", "diagnosis_diskusage_ok": "Le stockage {mountpoint} (sur le périphérique {device}) a toujours {free_abs_GB} Go ({free_percent}%) d'espace libre !", From 052ade602d2d9d74ea37d373aa5693e44179d66b Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Tue, 10 Mar 2020 21:02:40 +0100 Subject: [PATCH 0634/3170] Fix missing option in ynh_install_php --- data/helpers.d/php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/php b/data/helpers.d/php index 5e7a7ec78..817be7f4d 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -188,8 +188,8 @@ ynh_install_php () { update-alternatives --set php /usr/bin/php7.0 # Pin this extra repository after packages are installed to prevent sury of doing shit - ynh_pin_repo --package="*" --pin="origin \"packages.sury.org\"" 200 --name=extra_php_version - ynh_pin_repo --package="php7.0*" --pin="origin \"packages.sury.org\"" 600 --name=extra_php_version --append + ynh_pin_repo --package="*" --pin="origin \"packages.sury.org\"" --priority=200 --name=extra_php_version + ynh_pin_repo --package="php7.0*" --pin="origin \"packages.sury.org\"" --priority=600 --name=extra_php_version --append # Advertise service in admin panel yunohost service add php${phpversion}-fpm --log "/var/log/php${phpversion}-fpm.log" From b7a5847c30473ae4c180aaee9eabee421d6a29db Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Tue, 10 Mar 2020 21:05:04 +0100 Subject: [PATCH 0635/3170] Add a line between each pin instructions --- data/helpers.d/apt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index 756f077ab..def430055 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -532,6 +532,7 @@ ynh_pin_repo () { mkdir -p "/etc/apt/preferences.d" echo "Package: $package Pin: $pin -Pin-Priority: $priority" \ +Pin-Priority: $priority +" \ | $append "/etc/apt/preferences.d/$name" } From 59859429bc45b0e47cefd4ae285d3ce5d959f0f2 Mon Sep 17 00:00:00 2001 From: Weblate Date: Wed, 11 Mar 2020 09:29:12 +0000 Subject: [PATCH 0636/3170] Added translation using Weblate (Nepali) --- locales/ne.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 locales/ne.json diff --git a/locales/ne.json b/locales/ne.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/locales/ne.json @@ -0,0 +1 @@ +{} From addc4979ee7d2c2c7fe24cbf304b7565aa441f61 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Sun, 15 Mar 2020 15:16:26 +0000 Subject: [PATCH 0637/3170] Update changelog for 3.7.0.5 release --- debian/changelog | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/debian/changelog b/debian/changelog index 25a7469de..a7c0c0c35 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,10 +1,21 @@ +yunohost (3.7.0.5) testing; urgency=low + + - [fix] Permission url (#871) + - [fix] DNS resolver (#859) + - [fix] Legacy permission management (#868, #855) + - [enh] More informations in hooks permission (#877) + + Thanks to all contributors <3 ! (Bram, ljf, Aleks, Josué Maniac, Kay0u) + + -- Kay0u Sun, 15 Mar 2020 15:07:24 +0000 + yunohost (3.7.0.4) testing; urgency=low - [fix] Also add all_users when allowing visitors (#855) - [fix] Fix handling of skipped_uris (c.f. also SSOwat#149) - [i18n] Improve translations for Catalan - -- Alexandre Aubin Mon, 2 Dev 2019 20:44:00 +0000 + -- Alexandre Aubin Mon, 2 Dec 2019 20:44:00 +0000 yunohost (3.7.0.3) testing; urgency=low From 3137e5ffd44b2a574daf6ddd3ffb38b0d59d7c7f Mon Sep 17 00:00:00 2001 From: Kayou Date: Sun, 15 Mar 2020 19:08:22 +0100 Subject: [PATCH 0638/3170] Update changelog --- debian/changelog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/changelog b/debian/changelog index a7c0c0c35..de65b8901 100644 --- a/debian/changelog +++ b/debian/changelog @@ -5,7 +5,7 @@ yunohost (3.7.0.5) testing; urgency=low - [fix] Legacy permission management (#868, #855) - [enh] More informations in hooks permission (#877) - Thanks to all contributors <3 ! (Bram, ljf, Aleks, Josué Maniac, Kay0u) + Thanks to all contributors <3 ! (Bram, ljf, Aleks, Josué, Maniack, Kay0u) -- Kay0u Sun, 15 Mar 2020 15:07:24 +0000 From 1b174563314d3745fc13e8639ca145822868b842 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 15 Mar 2020 22:33:51 +0100 Subject: [PATCH 0639/3170] [fix] Make sure the group permission update contains unique elements --- src/yunohost/permission.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index b9edb317d..f6316424e 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -452,6 +452,9 @@ def _update_ldap_group_permission(permission, allowed, sync_perm=True): return existing_permission allowed = [allowed] if not isinstance(allowed, list) else allowed + + # Guarantee uniqueness of values in allowed, which would otherwise make ldap.update angry. + allowed = set(allowed) try: ldap.update('cn=%s,ou=permission' % permission, From 96be75e1774e7309a22337c015985f8d16e21a6e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 15 Mar 2020 22:33:51 +0100 Subject: [PATCH 0640/3170] [fix] Make sure the group permission update contains unique elements --- src/yunohost/permission.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 9d3d8feda..775a8cd71 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -452,6 +452,9 @@ def _update_ldap_group_permission(permission, allowed, sync_perm=True): return existing_permission allowed = [allowed] if not isinstance(allowed, list) else allowed + + # Guarantee uniqueness of values in allowed, which would otherwise make ldap.update angry. + allowed = set(allowed) try: ldap.update('cn=%s,ou=permission' % permission, From 9d4922eb81544d6f7035cf0b3c1d430274f41afc Mon Sep 17 00:00:00 2001 From: Kayou Date: Sun, 15 Mar 2020 19:08:22 +0100 Subject: [PATCH 0641/3170] Update changelog --- debian/changelog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/changelog b/debian/changelog index a7c0c0c35..de65b8901 100644 --- a/debian/changelog +++ b/debian/changelog @@ -5,7 +5,7 @@ yunohost (3.7.0.5) testing; urgency=low - [fix] Legacy permission management (#868, #855) - [enh] More informations in hooks permission (#877) - Thanks to all contributors <3 ! (Bram, ljf, Aleks, Josué Maniac, Kay0u) + Thanks to all contributors <3 ! (Bram, ljf, Aleks, Josué, Maniack, Kay0u) -- Kay0u Sun, 15 Mar 2020 15:07:24 +0000 From b7a61f35cc46a50fbd9ef5ad5109f16eed6171ed Mon Sep 17 00:00:00 2001 From: kay0u Date: Sun, 15 Mar 2020 22:35:56 +0000 Subject: [PATCH 0642/3170] Update changelog for 3.7.0.6 release --- debian/changelog | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/debian/changelog b/debian/changelog index de65b8901..8f0205f74 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,11 @@ +yunohost (3.7.0.6) testing; urgency=low + + - [fix] Make sure the group permission update contains unique elements + + Thanks to all contributors <3 ! (Aleks) + + -- Kay0u Sun, 15 Mar 2020 22:34:27 +0000 + yunohost (3.7.0.5) testing; urgency=low - [fix] Permission url (#871) From d6568eb25db553ef8c56758a97155b2c30028849 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 16 Mar 2020 00:55:16 +0100 Subject: [PATCH 0643/3170] Placeholder version number to avoid inconsistent build numbers between testing and unstable --- debian/changelog | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/debian/changelog b/debian/changelog index 8f0205f74..c2b37fa64 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,10 @@ +yunohost (3.8.0~alpha) testing; urgency=low + + Placeholder for upcoming 3.8 to avoid funky stuff with version numbers in + builds etc. + + -- Alexandre Aubin Mon, 16 Mar 2020 01:00:00 +0000 + yunohost (3.7.0.6) testing; urgency=low - [fix] Make sure the group permission update contains unique elements From 0decb6477ef50b099eb0a84b01985261763e1c31 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 17 Mar 2020 21:21:28 +0100 Subject: [PATCH 0644/3170] Try to improve a few weird messages and translations --- locales/en.json | 2 +- locales/fr.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/locales/en.json b/locales/en.json index df1fbe3a0..044f145e5 100644 --- a/locales/en.json +++ b/locales/en.json @@ -608,5 +608,5 @@ "yunohost_configured": "YunoHost is now configured", "yunohost_installing": "Installing YunoHost…", "yunohost_not_installed": "YunoHost is not correctly installed. Please run 'yunohost tools postinstall'", - "yunohost_postinstall_end_tip": "The post-install completed! To finalize your setup, please consider:\n - adding a first user through the 'Users' section of the webadmin (or 'yunohost user create ' in command-line);\n - diagnose issues waiting to be solved for your server to be running as smoothly as possible through the 'Diagnosis' section of the webadmin (or 'yunohost diagnosis run' in command-line);\n - reading the 'Finalizing your setup' and 'Getting to know Yunohost' parts in the admin documentation: https://yunohost.org/admindoc." + "yunohost_postinstall_end_tip": "The post-install completed! To finalize your setup, please consider:\n - adding a first user through the 'Users' section of the webadmin (or 'yunohost user create ' in command-line);\n - diagnose potential issues through the 'Diagnosis' section of the webadmin (or 'yunohost diagnosis run' in command-line);\n - reading the 'Finalizing your setup' and 'Getting to know Yunohost' parts in the admin documentation: https://yunohost.org/admindoc." } diff --git a/locales/fr.json b/locales/fr.json index e10723b6b..213e2e9ba 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -749,11 +749,11 @@ "diagnosis_ports_needed_by": "Rendre ce port accessible est nécessaire pour le service {0}", "diagnosis_ports_forwarding_tip": "Pour résoudre ce problème, vous devez probablement configurer la redirection de port sur votre routeur Internet comme décrit sur https://yunohost.org/isp_box_config", "diagnosis_http_connection_error": "Erreur de connexion : impossible de se connecter au domaine demandé, il est probablement injoignable.", - "diagnosis_no_cache": "Pas encore de diagnostique de cache pour la catégorie « {category} »", + "diagnosis_no_cache": "Pas encore de cache de diagnostique pour la catégorie « {category} »", "diagnosis_http_unknown_error": "Une erreur est survenue en essayant de joindre votre domaine, il est probablement injoignable.", "permission_all_users_implicitly_added": "La permission a également été implicitement accordée à 'all_users' car il est nécessaire pour permettre au groupe spécial 'visiteurs'", "permission_cannot_remove_all_users_while_visitors_allowed": "Vous ne pouvez pas supprimer cette autorisation pour 'all_users' alors qu'elle est toujours autorisée pour 'visiteurs'", - "yunohost_postinstall_end_tip": "La post-installation terminée! Pour finaliser votre configuration, veuillez considérer:\n - ajout d'un premier utilisateur via la section \"Utilisateurs\" de l'administrateur Web (ou \"utilisateur yunohost créer \" en ligne de commande);\n - diagnostiquez les problèmes en attente de résolution du problème afin que votre serveur fonctionne le mieux possible dans la section \"Diagnostic\" de l'administrateur Web (ou \"Exécution du diagnostic yunohost\" en ligne de commande);\n - lecture des parties \"Finalisation de votre configuration\" et \"Découverte de Yunohost\" dans la documentation de l'administrateur: https://yunohost.org/admindoc.", + "yunohost_postinstall_end_tip": "La post-installation terminée! Pour finaliser votre configuration, il est recommendé de :\n - ajouter un premier utilisateur depuis la section \"Utilisateurs\" de l'interface web (ou \"yunohost user create \" en ligne de commande);\n - diagnostiquer les potentiels problèmes dans la section \"Diagnostic\" de l'interface web (ou \"yunohost diagnosis run\" en ligne de commande);\n - lire les parties \"Finalisation de votre configuration\" et \"Découverte de Yunohost\" dans le guide de l'administrateur: https://yunohost.org/admindoc.", "diagnosis_services_bad_status_tip": "Vous pouvez essayer de redémarrer le service. Si cela ne fonctionne pas, consultez les journaux de service à l'aide de 'yunohost service log {0}' ou de la section 'Services' de l'administrateur Web.", "diagnosis_http_bad_status_code": "N'a pas pu atteindre votre serveur comme prévu, il a renvoyé un code d'état incorrect. Il se peut qu'une autre machine réponde à la place de votre serveur. Vérifiez que vous transférez correctement le port 80, que votre configuration nginx est à jour et qu’un proxy inverse n’interfère pas.", "diagnosis_http_timeout": "Expiration du délai en essayant de contacter votre serveur de l'extérieur. Il semble être inaccessible. Vérifiez que vous transférez correctement le port 80, que nginx est en cours d’exécution et qu’un pare-feu n’interfère pas.", From b5d18d63c7e28e17415ef8fc210b0030289eaa59 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 18 Mar 2020 01:30:31 +0100 Subject: [PATCH 0645/3170] Better handling of the didn't-run-diagnosis-ever-yet case (c.f. also commit for webadmin) --- locales/en.json | 1 + src/yunohost/diagnosis.py | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/locales/en.json b/locales/en.json index 044f145e5..d6784a78d 100644 --- a/locales/en.json +++ b/locales/en.json @@ -220,6 +220,7 @@ "diagnosis_http_bad_status_code": "Could not reach your server as expected, it returned a bad status code. It might be that another machine answered instead of your server. You should check that you're correctly forwarding port 80, that your nginx configuration is up to date, and that a reverse-proxy is not interfering.", "diagnosis_http_unreachable": "Domain {domain} is unreachable through HTTP from outside.", "diagnosis_unknown_categories": "The following categories are unknown: {categories}", + "diagnosis_never_ran_yet": "It looks like this server was setup recently and there's no diagnosis report to show yet. You should start by running a full diagnosis, either from the webadmin or using 'yunohost diagnosis run' from the command line.", "domain_cannot_remove_main": "You cannot remove '{domain:s}' since it's the main domain, you first need to set another domain as the main domain using 'yunohost domain main-domain -n '; here is the list of candidate domains: {other_domains:s}", "domain_cannot_remove_main_add_new_one": "You cannot remove '{domain:s}' since it's the main domain and your only domain, you need to first add another domain using 'yunohost domain add ', then set is as the main domain using 'yunohost domain main-domain -n ' and then you can remove the domain '{domain:s}' using 'yunohost domain remove {domain:s}'.'", "domain_cert_gen_failed": "Could not generate certificate", diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index 018140a49..db791fcdf 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -58,6 +58,10 @@ def diagnosis_show(categories=[], issues=False, full=False, share=False): if unknown_categories: raise YunohostError('diagnosis_unknown_categories', categories=", ".join(categories)) + if not os.path.exists(DIAGNOSIS_CACHE): + logger.warning(m18n.n("diagnosis_never_ran_yet")) + return + # Fetch all reports all_reports = [] for category in categories: From 2dbd6a5c1c491bbcd5d205f2971a5232b5ddd43c Mon Sep 17 00:00:00 2001 From: xaloc33 Date: Fri, 13 Mar 2020 16:54:19 +0000 Subject: [PATCH 0646/3170] Translated using Weblate (Catalan) Currently translated at 100.0% (610 of 610 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ca/ --- locales/ca.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/locales/ca.json b/locales/ca.json index 9e36d7009..6950152c2 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -162,7 +162,7 @@ "admin_password_too_long": "Trieu una contrasenya de menys de 127 caràcters", "dpkg_is_broken": "No es pot fer això en aquest instant perquè dpkg/APT (els gestors de paquets del sistema) sembla estar mal configurat… Podeu intentar solucionar-ho connectant-vos per SSH i executant «sudo dpkg --configure -a».", "dnsmasq_isnt_installed": "sembla que dnsmasq no està instal·lat, executeu \"apt-get remove bind9 && apt-get install dnsmasq\"", - "domain_cannot_remove_main": "No es pot eliminar «{domain:s}» ja que és el domini principal, primer s'ha d'establir un nou domini principal utilitzant «yunohost domain main-domain -n », aquí hi ha una llista dels possibles dominis: {other_domains:s}", + "domain_cannot_remove_main": "No es pot eliminar «{domain:s}» ja que és el domini principal, primer s'ha d'establir un nou domini principal utilitzant «yunohost domain main-domain -n »; aquí hi ha una llista dels possibles dominis: {other_domains:s}", "domain_cert_gen_failed": "No s'ha pogut generar el certificat", "domain_created": "S'ha creat el domini", "domain_creation_failed": "No s'ha pogut crear el domini {domain}: {error}", @@ -601,7 +601,7 @@ "migrations_running_forward": "Executant la migració {id}…", "migrations_success_forward": "Migració {id} completada", "apps_already_up_to_date": "Ja estan actualitzades totes les aplicacions", - "dyndns_provider_unreachable": "No s'ha pogut connectar amb el proveïdor Dyndns {provider}: o el vostre YunoHost no està ben connectat a Internet o el servidor dynette està caigut.", + "dyndns_provider_unreachable": "No s'ha pogut connectar amb el proveïdor DynDNS {provider}: o el vostre YunoHost no està ben connectat a Internet o el servidor dynette està caigut.", "operation_interrupted": "S'ha interromput manualment l'operació?", "group_already_exist": "El grup {group} ja existeix", "group_already_exist_on_system": "El grup {group} ja existeix en els grups del sistema", @@ -641,7 +641,7 @@ "diagnosis_basesystem_ynh_single_version": "{0} versió: {1}({2})", "diagnosis_basesystem_ynh_inconsistent_versions": "Esteu utilitzant versions inconsistents dels paquets de YunoHost… probablement a causa d'una actualització fallida o parcial.", "diagnosis_display_tip_web": "Podeu anar a la secció de Diagnòstics (en la pantalla principal) per veure els errors que s'han trobat.", - "diagnosis_failed_for_category": "Ha fallat el diagnòstic per la categoria «{category}» : {error}", + "diagnosis_failed_for_category": "Ha fallat el diagnòstic per la categoria «{category}»: {error}", "diagnosis_display_tip_cli": "Podeu executar «yunohost diagnosis show --issues» per mostrar els errors que s'han trobat.", "diagnosis_cache_still_valid": "(La memòria cau encara és vàlida pel diagnòstic de {category}. No es tornar a diagnosticar de moment!)", "diagnosis_cant_run_because_of_dep": "No es pot fer el diagnòstic per {category} mentre hi ha problemes importants relacionats amb {dep}.", @@ -650,7 +650,7 @@ "diagnosis_found_errors_and_warnings": "S'ha trobat problema(es) important(s) {errors} (i avis(os) {warnings}) relacionats amb {category}!", "diagnosis_found_warnings": "S'han trobat ítems {warnings} que es podrien millorar per {category}.", "diagnosis_everything_ok": "Tot sembla correcte per {category}!", - "diagnosis_failed": "No s'han pogut obtenir els resultats del diagnòstic per la categoria «{category}» : {error}", + "diagnosis_failed": "No s'han pogut obtenir els resultats del diagnòstic per la categoria «{category}»: {error}", "diagnosis_ip_connected_ipv4": "El servidor està connectat a Internet amb IPv4!", "diagnosis_ip_no_ipv4": "El servidor no té una IPv4 que funcioni.", "diagnosis_ip_connected_ipv6": "El servidor està connectat a Internet amb IPv6!", @@ -706,7 +706,7 @@ "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 {0}» 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 s'explica a https://yunohost.org/isp_box_config", + "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": "No s'ha pogut connectar al servidor com esperat, ha retornat un codi d'estat erroni. 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_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.", From 0691759190a0abcfc9999df9d7f316115f4262c6 Mon Sep 17 00:00:00 2001 From: yalh76 Date: Sat, 14 Mar 2020 16:22:46 +0000 Subject: [PATCH 0647/3170] Translated using Weblate (French) Currently translated at 96.9% (591 of 610 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 213e2e9ba..156b870ef 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -288,7 +288,7 @@ "appslist_migrating": "Migration de la liste d’applications '{appslist:s}' …", "appslist_could_not_migrate": "Impossible de migrer la liste '{appslist:s}' ! Impossible d’exploiter l’URL. L’ancienne tâche programmée a été conservée dans {bkp_file:s}.", "appslist_corrupted_json": "Impossible de charger la liste d’applications. Il semble que {filename:s} soit endommager.", - "app_already_installed_cant_change_url": "Cette application est déjà installée. L’URL ne peut pas être changé simplement par cette fonction. Regardez si cela est disponible avec `app changeurl`.", + "app_already_installed_cant_change_url": "Cette application est déjà installée. L’URL ne peut pas être changé simplement par cette fonction. Vérifiez si cela est disponible avec `app changeurl`.", "app_change_no_change_url_script": "L’application {app_name:s} ne prend pas encore en charge le changement d’URL, vous pourriez avoir besoin de la mettre à jour.", "app_change_url_failed_nginx_reload": "Le redémarrage de Nginx a échoué. Voici la sortie de 'nginx -t' :\n{nginx_errors:s}", "app_change_url_identical_domains": "L’ancien et le nouveau couple domaine/chemin_de_l'URL sont identiques pour ('{domain:s}{path:s}'), rien à faire.", @@ -617,7 +617,7 @@ "permission_updated": "Permission '{permission:s}' mise à jour", "permission_update_nothing_to_do": "Aucune autorisation pour mettre à jour", "remove_main_permission_not_allowed": "Supprimer l'autorisation principale n'est pas autorisé", - "dyndns_provider_unreachable": "Impossible d’atteindre le fournisseur Dyndns {provider}: votre YunoHost n’est pas correctement connecté à Internet ou le serveur Dynette est en panne.", + "dyndns_provider_unreachable": "Impossible d’atteindre le fournisseur DynDNS {provider}: votre YunoHost n’est pas correctement connecté à Internet ou le serveur Dynette est en panne.", "migration_0011_update_LDAP_schema": "Mise à jour du schéma LDAP…", "migrations_already_ran": "Ces migrations sont déjà effectuées: {ids}", "migrations_dependencies_not_satisfied": "Exécutez ces migrations: '{dependencies_id}', avant migration {id}.", @@ -684,7 +684,7 @@ "diagnosis_basesystem_ynh_main_version": "Le serveur exécute YunoHost {main_version} ({repo})", "diagnosis_basesystem_ynh_inconsistent_versions": "Vous exécutez des versions incohérentes des packages YunoHost ... probablement à cause d'une mise à niveau partielle ou échouée.", "diagnosis_display_tip_cli": "Vous pouvez exécuter 'yunohost diagnosis show --issues' pour afficher les problèmes détectés.", - "diagnosis_failed_for_category": "Échec du diagnostic pour la catégorie '{category}' : {error}", + "diagnosis_failed_for_category": "Échec du diagnostic pour la catégorie '{category}': {error}", "diagnosis_cache_still_valid": "(Le cache est toujours valide pour le diagnostic {category}. Pas re-diagnostiquer pour le moment!)", "diagnosis_ignored_issues": "(+ {nb_ignored} questions ignorée(s))", "diagnosis_found_warnings": "Trouvé {warnings} objet(s) pouvant être amélioré(s) pour {category}.", @@ -695,7 +695,7 @@ "diagnosis_ip_connected_ipv6": "Le serveur est connecté à Internet via IPv6 !", "diagnosis_ip_no_ipv6": "Le serveur ne dispose pas d'une adresse IPv6 active.", "diagnosis_ip_dnsresolution_working": "La résolution de nom de domaine fonctionne !", - "diagnosis_ip_broken_dnsresolution": "La résolution du nom de domaine semble interrompue pour une raison quelconque ... Un pare-feu bloque-t-il les requêtes DNS ?", + "diagnosis_ip_broken_dnsresolution": "La résolution du nom de domaine semble interrompue pour une raison quelconque... Un pare-feu bloque-t-il les requêtes DNS ?", "diagnosis_ip_broken_resolvconf": "La résolution du nom de domaine semble cassée sur votre serveur, ce qui semble lié au fait que /etc/resolv.conf ne pointe pas vers 127.0.0.1.", "diagnosis_dns_good_conf": "Bonne configuration DNS pour le domaine {domain} (catégorie {category})", "diagnosis_dns_bad_conf": "Configuration DNS incorrecte/manquante pour le domaine {domain} (catégorie {category})", From 49f999795382c5c8c75c023cbb9ca3c4da5671b6 Mon Sep 17 00:00:00 2001 From: Gustavo M Date: Thu, 12 Mar 2020 22:55:53 +0000 Subject: [PATCH 0648/3170] Translated using Weblate (Portuguese) Currently translated at 8.7% (53 of 610 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/pt/ --- locales/pt.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/locales/pt.json b/locales/pt.json index e068a2284..417800fd5 100644 --- a/locales/pt.json +++ b/locales/pt.json @@ -1,8 +1,8 @@ { "action_invalid": "Acção Inválida '{action:s}'", "admin_password": "Senha de administração", - "admin_password_change_failed": "Não foi possível alterar a senha", - "admin_password_changed": "A palavra-passe de administração foi alterada com sucesso", + "admin_password_change_failed": "Não é possível alterar a senha", + "admin_password_changed": "A senha da administração foi alterada", "app_already_installed": "{app:s} já está instalada", "app_extraction_failed": "Não foi possível extrair os ficheiros para instalação", "app_id_invalid": "A ID da aplicação é inválida", @@ -194,5 +194,6 @@ "backup_cant_mount_uncompress_archive": "Não foi possível montar em modo leitura o diretorio de arquivos não comprimido", "backup_copying_to_organize_the_archive": "Copiando {size:s}MB para organizar o arquivo", "app_change_url_identical_domains": "O antigo e o novo domínio / url_path são idênticos ('{domain:s}{path:s}'), nada para fazer.", - "password_too_simple_1": "A senha precisa ter pelo menos 8 caracteres" + "password_too_simple_1": "A senha precisa ter pelo menos 8 caracteres", + "admin_password_too_long": "Escolha uma senha que contenha menos de 127 caracteres" } From dc3e37cc79b8c87f51d20973d926040af61fdaec Mon Sep 17 00:00:00 2001 From: Kayou Date: Thu, 19 Mar 2020 23:01:37 +0100 Subject: [PATCH 0649/3170] Update image --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c52092c5c..8e3938ad6 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,7 +3,7 @@ stages: - tests .tests: - image: stretch:after-postinstall + image: after-postinstall before_script: - apt-get install python-pip -y - mkdir -p .pip @@ -26,7 +26,7 @@ stages: key: "$CI_JOB_STAGE-$CI_COMMIT_REF_SLUG" postinstall: - image: stretch:before-postinstall + image: before-postinstall stage: postinstall script: - yunohost tools postinstall -d domain.tld -p the_password --ignore-dyndns From 69bc12454ecb95a7e5a25fdb385290c93f2f6fd5 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 21 Mar 2020 22:25:44 +0100 Subject: [PATCH 0650/3170] Reorder DNS tests to avoid random order in rendering --- data/hooks/diagnosis/12-dnsrecords.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py index e2f7bcc2d..96ac31d55 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -42,8 +42,10 @@ class DNSRecordsDiagnoser(Diagnoser): # Here if there are no AAAA record, we should add something to expect "no" AAAA record # to properly diagnose situations where people have a AAAA record but no IPv6 - for category, records in expected_configuration.items(): + categories = ["basic", "mail", "xmpp", "extra"] + for category in categories: + records = expected_configuration[category] discrepancies = [] for r in records: From 937d3396315d7574f4d133d88c34e3eaf892fed3 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 21 Mar 2020 22:27:52 +0100 Subject: [PATCH 0651/3170] Add category to services to have more meaningful messages in reports about port forwarding checks --- data/hooks/diagnosis/14-ports.py | 10 ++++++---- data/templates/yunohost/services.yml | 15 ++++++++++++++- locales/en.json | 2 +- locales/fr.json | 2 +- 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/data/hooks/diagnosis/14-ports.py b/data/hooks/diagnosis/14-ports.py index f9694a9de..11f26ceba 100644 --- a/data/hooks/diagnosis/14-ports.py +++ b/data/hooks/diagnosis/14-ports.py @@ -21,7 +21,8 @@ class PortsDiagnoser(Diagnoser): # 443: "nginx" # ... } ports = {} - for service, infos in _get_services().items(): + services = _get_services() + for service, infos in services.items(): for port in infos.get("needs_exposed_ports", []): ports[port] = service @@ -39,17 +40,18 @@ class PortsDiagnoser(Diagnoser): except Exception as e: raise YunohostError("diagnosis_ports_could_not_diagnose", error=e) - for port, service in ports.items(): + for port, service in sorted(ports.items()): + category = services[service].get("category", "[?]") if r["ports"].get(str(port), None) is not True: yield dict(meta={"port": port, "needed_by": service}, status="ERROR", summary=("diagnosis_ports_unreachable", {"port": port}), - details=[("diagnosis_ports_needed_by", (service,)), ("diagnosis_ports_forwarding_tip", ())]) + details=[("diagnosis_ports_needed_by", (service, category)), ("diagnosis_ports_forwarding_tip", ())]) else: yield dict(meta={"port": port, "needed_by": service}, status="SUCCESS", summary=("diagnosis_ports_ok", {"port": port}), - details=[("diagnosis_ports_needed_by", (service))]) + details=[("diagnosis_ports_needed_by", (service, category))]) def main(args, env, loggers): diff --git a/data/templates/yunohost/services.yml b/data/templates/yunohost/services.yml index b3c406f0f..fdf278fcf 100644 --- a/data/templates/yunohost/services.yml +++ b/data/templates/yunohost/services.yml @@ -3,40 +3,53 @@ dnsmasq: {} dovecot: log: [/var/log/mail.log,/var/log/mail.err] needs_exposed_ports: [993] + category: email fail2ban: log: /var/log/fail2ban.log + category: security metronome: log: [/var/log/metronome/metronome.log,/var/log/metronome/metronome.err] needs_exposed_ports: [5222, 5269] + category: xmpp mysql: log: [/var/log/mysql.log,/var/log/mysql.err,/var/log/mysql/error.log] alternates: ['mariadb'] + category: database nginx: log: /var/log/nginx test_conf: nginx -t needs_exposed_ports: [80, 443] + category: web nslcd: {} php7.0-fpm: log: /var/log/php7.0-fpm.log test_conf: php-fpm7.0 --test + category: web postfix: log: [/var/log/mail.log,/var/log/mail.err] test_status: systemctl show postfix@- | grep -q "^SubState=running" needs_exposed_ports: [25, 587] + category: email redis-server: log: /var/log/redis/redis-server.log + category: database rspamd: log: /var/log/rspamd/rspamd.log -slapd: {} + category: email +slapd: + category: database ssh: log: /var/log/auth.log test_conf: sshd -t needs_exposed_ports: [22] + category: admin yunohost-api: log: /var/log/yunohost/yunohost-api.log + category: admin yunohost-firewall: need_lock: true test_status: iptables -S | grep "^-A INPUT" | grep " --dport" | grep -q ACCEPT + category: security glances: null nsswitch: null ssl: null diff --git a/locales/en.json b/locales/en.json index d6784a78d..3aa2a7074 100644 --- a/locales/en.json +++ b/locales/en.json @@ -210,7 +210,7 @@ "diagnosis_ports_could_not_diagnose": "Could not diagnose if ports are reachable from outside. Error: {error}", "diagnosis_ports_unreachable": "Port {port} is not reachable from outside.", "diagnosis_ports_ok": "Port {port} is reachable from outside.", - "diagnosis_ports_needed_by": "Exposing this port is needed for service {0}", + "diagnosis_ports_needed_by": "Exposing this port is needed for {1} features (service {0})", "diagnosis_ports_forwarding_tip": "To fix this issue, you most probably need to configure port forwarding on your internet router as described in https://yunohost.org/isp_box_config", "diagnosis_http_could_not_diagnose": "Could not diagnose if domain is reachable from outside. Error: {error}", "diagnosis_http_ok": "Domain {domain} is reachable from outside.", diff --git a/locales/fr.json b/locales/fr.json index 156b870ef..5bcb74b11 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -746,7 +746,7 @@ "migration_description_0014_remove_app_status_json": "Supprimer les fichiers d'application status.json hérités", "diagnosis_services_running": "Le service {service} s'exécute correctement !", "diagnosis_services_conf_broken": "La configuration est cassée pour le service {service} !", - "diagnosis_ports_needed_by": "Rendre ce port accessible est nécessaire pour le service {0}", + "diagnosis_ports_needed_by": "Rendre ce port accessible est nécessaire pour les fonctionnalités de type {1} (service {0})", "diagnosis_ports_forwarding_tip": "Pour résoudre ce problème, vous devez probablement configurer la redirection de port sur votre routeur Internet comme décrit sur https://yunohost.org/isp_box_config", "diagnosis_http_connection_error": "Erreur de connexion : impossible de se connecter au domaine demandé, il est probablement injoignable.", "diagnosis_no_cache": "Pas encore de cache de diagnostique pour la catégorie « {category} »", From 986f38f6ed2230332838499fcc27601aae7280bb Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 21 Mar 2020 22:37:25 +0100 Subject: [PATCH 0652/3170] Try to fix / improve a few messages --- locales/en.json | 6 +++--- locales/fr.json | 24 ++++++++++++------------ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/locales/en.json b/locales/en.json index 3aa2a7074..c6dfb99af 100644 --- a/locales/en.json +++ b/locales/en.json @@ -168,9 +168,9 @@ "diagnosis_ip_broken_dnsresolution": "Domain name resolution seems to be broken for some reason... Is a firewall blocking DNS requests ?", "diagnosis_ip_broken_resolvconf": "Domain name resolution seems to be broken on your server, which seems related to /etc/resolv.conf not pointing to 127.0.0.1.", "diagnosis_ip_weird_resolvconf": "DNS resolution seems to be working, but be careful that you seem to be using a custom /etc/resolv.conf.", - "diagnosis_ip_weird_resolvconf_details": "Instead, this file should be a symlink to /etc/resolvconf/run/resolv.conf itself pointing to 127.0.0.1 (dnsmasq). The actual resolvers should be configured via /etc/resolv.dnsmasq.conf.", + "diagnosis_ip_weird_resolvconf_details": "Instead, this file should be a symlink to /etc/resolvconf/run/resolv.conf itself pointing to 127.0.0.1 (dnsmasq). The actual resolvers should be configured in /etc/resolv.dnsmasq.conf.", "diagnosis_dns_good_conf": "Good DNS configuration for domain {domain} (category {category})", - "diagnosis_dns_bad_conf": "Bad / missing DNS configuration for domain {domain} (category {category})", + "diagnosis_dns_bad_conf": "Bad or missing DNS configuration for domain {domain} (category {category})", "diagnosis_dns_missing_record": "According to the recommended DNS configuration, you should add a DNS record with type {0}, name {1} and value {2}. You can check https://yunohost.org/dns_config for more info.", "diagnosis_dns_discrepancy": "The DNS record with type {0} and name {1} does not match the recommended configuration. Current value: {2}. Excepted value: {3}. You can check https://yunohost.org/dns_config for more info.", "diagnosis_services_running": "Service {service} is running!", @@ -217,7 +217,7 @@ "diagnosis_http_timeout": "Timed-out while trying to contact your server from outside. It appears to be unreachable. You should check that you're correctly forwarding port 80, that nginx is running, and that a firewall is not interfering.", "diagnosis_http_connection_error": "Connection error: could not connect to the requested domain, it's very likely unreachable.", "diagnosis_http_unknown_error": "An error happened while trying to reach your domain, it's very likely unreachable.", - "diagnosis_http_bad_status_code": "Could not reach your server as expected, it returned a bad status code. It might be that another machine answered instead of your server. You should check that you're correctly forwarding port 80, that your nginx configuration is up to date, and that a reverse-proxy is not interfering.", + "diagnosis_http_bad_status_code": "The diagnosis system could not reach your server. It might be that another machine answered instead of your server. You should check that you're correctly forwarding port 80, that your nginx configuration is up to date, and that a reverse-proxy is not interfering.", "diagnosis_http_unreachable": "Domain {domain} is unreachable through HTTP from outside.", "diagnosis_unknown_categories": "The following categories are unknown: {categories}", "diagnosis_never_ran_yet": "It looks like this server was setup recently and there's no diagnosis report to show yet. You should start by running a full diagnosis, either from the webadmin or using 'yunohost diagnosis run' from the command line.", diff --git a/locales/fr.json b/locales/fr.json index 5bcb74b11..50dc2e983 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -674,14 +674,14 @@ "diagnosis_ip_weird_resolvconf": "La résolution DNS semble fonctionner, mais soyez prudent en utilisant un fichier /etc/resolv.conf personnalisé.", "diagnosis_ip_weird_resolvconf_details": "Au lieu de cela, ce fichier devrait être un lien symbolique vers /etc/resolvconf/run/resolv.conf lui-même pointant vers 127.0.0.1 (dnsmasq). Les résolveurs réels doivent être configurés via /etc/resolv.dnsmasq.conf.", "diagnosis_dns_missing_record": "Selon la configuration DNS recommandée, vous devez ajouter un enregistrement DNS de type {0}, nom {1} et valeur {2}. Vous pouvez consulter https://yunohost.org/dns_config pour plus d'informations.", - "diagnosis_diskusage_ok": "Le stockage {mountpoint} (sur le périphérique {device}) a toujours {free_abs_GB} Go ({free_percent}%) d'espace libre !", - "diagnosis_ram_ok": "Le système dispose toujours de {available_abs_MB} MB ({available_percent}%) de RAM sur {total_abs_MB} MB.", + "diagnosis_diskusage_ok": "Le stockage {mountpoint} (sur le périphérique {device}) a encore {free_abs_GB} Go ({free_percent}%) d'espace libre !", + "diagnosis_ram_ok": "Le système dispose encore de {available_abs_MB} MB ({available_percent}%) de RAM sur {total_abs_MB} MB.", "diagnosis_regenconf_allgood": "Tous les fichiers de configuration sont conformes à la configuration recommandée !", "diagnosis_security_vulnerable_to_meltdown": "Vous semblez vulnérable à la vulnérabilité de sécurité critique de Meltdown", - "diagnosis_basesystem_host": "Le serveur exécute Debian {debian_version}.", - "diagnosis_basesystem_kernel": "Le serveur exécute le noyau Linux {kernel_version}", + "diagnosis_basesystem_host": "Le serveur utilise Debian {debian_version}.", + "diagnosis_basesystem_kernel": "Le serveur utilise le noyau Linux {kernel_version}", "diagnosis_basesystem_ynh_single_version": "{0} version: {1} ({2})", - "diagnosis_basesystem_ynh_main_version": "Le serveur exécute YunoHost {main_version} ({repo})", + "diagnosis_basesystem_ynh_main_version": "Le serveur utilise YunoHost {main_version} ({repo})", "diagnosis_basesystem_ynh_inconsistent_versions": "Vous exécutez des versions incohérentes des packages YunoHost ... probablement à cause d'une mise à niveau partielle ou échouée.", "diagnosis_display_tip_cli": "Vous pouvez exécuter 'yunohost diagnosis show --issues' pour afficher les problèmes détectés.", "diagnosis_failed_for_category": "Échec du diagnostic pour la catégorie '{category}': {error}", @@ -690,15 +690,15 @@ "diagnosis_found_warnings": "Trouvé {warnings} objet(s) pouvant être amélioré(s) pour {category}.", "diagnosis_everything_ok": "Tout semble bien pour {category} !", "diagnosis_failed": "Impossible d'extraire le résultat du diagnostic pour la catégorie '{category}': {error}", - "diagnosis_ip_connected_ipv4": "Le serveur est connecté à Internet via IPv4 !", - "diagnosis_ip_no_ipv4": "Le serveur ne dispose pas d’une adresse IPv4 active.", - "diagnosis_ip_connected_ipv6": "Le serveur est connecté à Internet via IPv6 !", - "diagnosis_ip_no_ipv6": "Le serveur ne dispose pas d'une adresse IPv6 active.", + "diagnosis_ip_connected_ipv4": "Le serveur est connecté à Internet en IPv4 !", + "diagnosis_ip_no_ipv4": "Le serveur ne dispose pas d’une adresse IPv4.", + "diagnosis_ip_connected_ipv6": "Le serveur est connecté à Internet en IPv6 !", + "diagnosis_ip_no_ipv6": "Le serveur ne dispose pas d'une adresse IPv6.", "diagnosis_ip_dnsresolution_working": "La résolution de nom de domaine fonctionne !", "diagnosis_ip_broken_dnsresolution": "La résolution du nom de domaine semble interrompue pour une raison quelconque... Un pare-feu bloque-t-il les requêtes DNS ?", "diagnosis_ip_broken_resolvconf": "La résolution du nom de domaine semble cassée sur votre serveur, ce qui semble lié au fait que /etc/resolv.conf ne pointe pas vers 127.0.0.1.", "diagnosis_dns_good_conf": "Bonne configuration DNS pour le domaine {domain} (catégorie {category})", - "diagnosis_dns_bad_conf": "Configuration DNS incorrecte/manquante pour le domaine {domain} (catégorie {category})", + "diagnosis_dns_bad_conf": "Configuration DNS incorrecte ou manquante pour le domaine {domain} (catégorie {category})", "diagnosis_dns_discrepancy": "L'enregistrement DNS de type {0} et nom {1} ne correspond pas à la configuration recommandée. Valeur actuelle: {2}. Valeur exceptée: {3}. Vous pouvez consulter https://yunohost.org/dns_config pour plus d'informations.", "diagnosis_services_bad_status": "Le service {service} est {status} :-(", "diagnosis_services_good_status": "Le service {service} est {status} comme prévu !", @@ -723,7 +723,7 @@ "diagnosis_description_basesystem": "Système de base", "diagnosis_description_ip": "Connectivité Internet", "diagnosis_description_dnsrecords": "Enregistrements DNS", - "diagnosis_description_services": "Vérification de l'état des services", + "diagnosis_description_services": "État des services", "diagnosis_description_systemresources": "Ressources système", "diagnosis_description_ports": "Exposition des ports", "diagnosis_description_http": "Exposition HTTP", @@ -755,7 +755,7 @@ "permission_cannot_remove_all_users_while_visitors_allowed": "Vous ne pouvez pas supprimer cette autorisation pour 'all_users' alors qu'elle est toujours autorisée pour 'visiteurs'", "yunohost_postinstall_end_tip": "La post-installation terminée! Pour finaliser votre configuration, il est recommendé de :\n - ajouter un premier utilisateur depuis la section \"Utilisateurs\" de l'interface web (ou \"yunohost user create \" en ligne de commande);\n - diagnostiquer les potentiels problèmes dans la section \"Diagnostic\" de l'interface web (ou \"yunohost diagnosis run\" en ligne de commande);\n - lire les parties \"Finalisation de votre configuration\" et \"Découverte de Yunohost\" dans le guide de l'administrateur: https://yunohost.org/admindoc.", "diagnosis_services_bad_status_tip": "Vous pouvez essayer de redémarrer le service. Si cela ne fonctionne pas, consultez les journaux de service à l'aide de 'yunohost service log {0}' ou de la section 'Services' de l'administrateur Web.", - "diagnosis_http_bad_status_code": "N'a pas pu atteindre votre serveur comme prévu, il a renvoyé un code d'état incorrect. Il se peut qu'une autre machine réponde à la place de votre serveur. Vérifiez que vous transférez correctement le port 80, que votre configuration nginx est à jour et qu’un proxy inverse n’interfère pas.", + "diagnosis_http_bad_status_code": "Le système de diagnostique n'a pas réussi à contacter votre serveur. Il se peut qu'une autre machine réponde à la place de votre serveur. Vérifiez que le port 80 est correctement redirigé, que votre configuration nginx est à jour et qu’un reverse-proxy n’interfère pas.", "diagnosis_http_timeout": "Expiration du délai en essayant de contacter votre serveur de l'extérieur. Il semble être inaccessible. Vérifiez que vous transférez correctement le port 80, que nginx est en cours d’exécution et qu’un pare-feu n’interfère pas.", "global_settings_setting_pop3_enabled": "Activer le protocole POP3 pour le serveur de messagerie", "log_app_action_run": "Lancer l’action de l’application '{}'", From cc2288cc215e06ce38168d7669c513517af383a2 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 21 Mar 2020 23:08:24 +0100 Subject: [PATCH 0653/3170] Rename http diagnoser to web, should feel slightly less technical --- data/hooks/diagnosis/{16-http.py => 21-web.py} | 4 ++-- data/hooks/diagnosis/{18-mail.py => 24-mail.py} | 0 locales/en.json | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) rename data/hooks/diagnosis/{16-http.py => 21-web.py} (96%) rename data/hooks/diagnosis/{18-mail.py => 24-mail.py} (100%) diff --git a/data/hooks/diagnosis/16-http.py b/data/hooks/diagnosis/21-web.py similarity index 96% rename from data/hooks/diagnosis/16-http.py rename to data/hooks/diagnosis/21-web.py index c7955c805..f7922fdd7 100644 --- a/data/hooks/diagnosis/16-http.py +++ b/data/hooks/diagnosis/21-web.py @@ -9,7 +9,7 @@ from yunohost.domain import domain_list from yunohost.utils.error import YunohostError -class HttpDiagnoser(Diagnoser): +class WebDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] cache_duration = 3600 @@ -56,4 +56,4 @@ class HttpDiagnoser(Diagnoser): def main(args, env, loggers): - return HttpDiagnoser(args, env, loggers).diagnose() + return WebDiagnoser(args, env, loggers).diagnose() diff --git a/data/hooks/diagnosis/18-mail.py b/data/hooks/diagnosis/24-mail.py similarity index 100% rename from data/hooks/diagnosis/18-mail.py rename to data/hooks/diagnosis/24-mail.py diff --git a/locales/en.json b/locales/en.json index c6dfb99af..d70f964d2 100644 --- a/locales/en.json +++ b/locales/en.json @@ -203,7 +203,7 @@ "diagnosis_description_services": "Services status check", "diagnosis_description_systemresources": "System resources", "diagnosis_description_ports": "Ports exposure", - "diagnosis_description_http": "HTTP exposure", + "diagnosis_description_web": "Web", "diagnosis_description_mail": "Email", "diagnosis_description_regenconf": "System configurations", "diagnosis_description_security": "Security checks", @@ -213,12 +213,12 @@ "diagnosis_ports_needed_by": "Exposing this port is needed for {1} features (service {0})", "diagnosis_ports_forwarding_tip": "To fix this issue, you most probably need to configure port forwarding on your internet router as described in https://yunohost.org/isp_box_config", "diagnosis_http_could_not_diagnose": "Could not diagnose if domain is reachable from outside. Error: {error}", - "diagnosis_http_ok": "Domain {domain} is reachable from outside.", + "diagnosis_http_ok": "Domain {domain} is reachable through HTTP from outside the local network.", "diagnosis_http_timeout": "Timed-out while trying to contact your server from outside. It appears to be unreachable. You should check that you're correctly forwarding port 80, that nginx is running, and that a firewall is not interfering.", "diagnosis_http_connection_error": "Connection error: could not connect to the requested domain, it's very likely unreachable.", "diagnosis_http_unknown_error": "An error happened while trying to reach your domain, it's very likely unreachable.", "diagnosis_http_bad_status_code": "The diagnosis system could not reach your server. It might be that another machine answered instead of your server. You should check that you're correctly forwarding port 80, that your nginx configuration is up to date, and that a reverse-proxy is not interfering.", - "diagnosis_http_unreachable": "Domain {domain} is unreachable through HTTP from outside.", + "diagnosis_http_unreachable": "Domain {domain} appears unreachable through HTTP from outside the local network.", "diagnosis_unknown_categories": "The following categories are unknown: {categories}", "diagnosis_never_ran_yet": "It looks like this server was setup recently and there's no diagnosis report to show yet. You should start by running a full diagnosis, either from the webadmin or using 'yunohost diagnosis run' from the command line.", "domain_cannot_remove_main": "You cannot remove '{domain:s}' since it's the main domain, you first need to set another domain as the main domain using 'yunohost domain main-domain -n '; here is the list of candidate domains: {other_domains:s}", From b0b27f14587291cd22b87110d105a6998ed5bd2b Mon Sep 17 00:00:00 2001 From: xaloc33 Date: Wed, 18 Mar 2020 23:09:52 +0000 Subject: [PATCH 0654/3170] Translated using Weblate (Catalan) Currently translated at 100.0% (611 of 611 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ca/ --- locales/ca.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/locales/ca.json b/locales/ca.json index 6950152c2..c09b31e01 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -712,7 +712,7 @@ "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_connection_error": "Error de connexió: no s'ha pogut connectar amb el domini demanat, segurament és inaccessible.", "diagnosis_http_unknown_error": "Hi ha hagut un error intentant accedir al domini, 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 els problemes esperant a ser resolts per un correcte funcionament del servidor 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.", + "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", "diagnosis_services_running": "El servei {service} s'està executant!", "diagnosis_services_conf_broken": "La configuració pel servei {service} està trencada!", @@ -722,5 +722,6 @@ "global_settings_setting_pop3_enabled": "Activa el protocol POP3 per al servidor de correu", "log_app_action_run": "Executa l'acció de l'aplicació «{}»", "log_app_config_show_panel": "Mostra el taulell de configuració de l'aplicació «{}»", - "log_app_config_apply": "Afegeix la configuració a l'aplicació «{}»" + "log_app_config_apply": "Afegeix la configuració a l'aplicació «{}»", + "diagnosis_never_ran_yet": "Sembla que el servidor s'ha configurat recentment i encara no hi cap informe de diagnòstic per mostrar. S'ha d'executar un diagnòstic complet primer, ja sigui des de la pàgina web d'administració o utilitzant la comanda «yunohost diagnosis run» al terminal." } From 2215786d6efc267b80f18e24936ccec0d83309ee Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 22 Mar 2020 01:23:55 +0100 Subject: [PATCH 0655/3170] Attempt to anonymize data pasted to paste.yunohost.org (in particular domain names) --- src/yunohost/utils/yunopaste.py | 45 +++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/yunohost/utils/yunopaste.py b/src/yunohost/utils/yunopaste.py index 89c62d761..530295735 100644 --- a/src/yunohost/utils/yunopaste.py +++ b/src/yunohost/utils/yunopaste.py @@ -2,14 +2,23 @@ import requests import json +import logging +from yunohost.domain import _get_maindomain, domain_list +from yunohost.utils.network import get_public_ip from yunohost.utils.error import YunohostError +logger = logging.getLogger('yunohost.utils.yunopaste') def yunopaste(data): paste_server = "https://paste.yunohost.org" + try: + data = anonymize(data) + except Exception as e: + logger.warning("For some reason, YunoHost was not able to anonymize the pasted data. Sorry about that. Be careful about sharing the link, as it may contain somewhat private infos like domain names or IP addresses. Error: %s" % e) + try: r = requests.post("%s/documents" % paste_server, data=data, timeout=30) except Exception as e: @@ -24,3 +33,39 @@ def yunopaste(data): raise YunohostError("Uhoh, couldn't parse the answer from paste.yunohost.org : %s" % r.text, raw_msg=True) return "%s/raw/%s" % (paste_server, url) + + +def anonymize(data): + + # First, let's replace every occurence of the main domain by "domain.tld" + # This should cover a good fraction of the info leaked + main_domain = _get_maindomain() + data = data.replace(main_domain, "maindomain.tld") + + # Next, let's replace other domains. We do this in increasing lengths, + # because e.g. knowing that the domain is a sub-domain of another domain may + # still be informative. + # So e.g. if there's jitsi.foobar.com as a subdomain of foobar.com, it may + # be interesting to know that the log is about a supposedly dedicated domain + # for jisti (hopefully this explanation make sense). + domains = domain_list()["domains"] + domains = sorted(domains, key=lambda d: len(d)) + + count = 2 + for domain in domains: + if domain not in data: + continue + data = data.replace(domain, "domain%s.tld" % count) + count += 1 + + # We also want to anonymize the ips + ipv4 = get_public_ip() + ipv6 = get_public_ip(6) + + if ipv4: + data = data.replace(str(ipv4), "xx.xx.xx.xx") + + if ipv6: + data = data.replace(str(ipv6), "xx:xx:xx:xx:xx:xx") + + return data From ec80cad64ac7a49fddc76bf3b15e1de5bc0f3a36 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 22 Mar 2020 01:28:37 +0100 Subject: [PATCH 0656/3170] [enh] Tell apt to explain what's wrong when there are unmet dependencies (#889) * Ask apt to explain what's wrong when dependencies fail to install * Add comment explaining the syntax Co-Authored-By: Maniack Crudelis Co-authored-by: Maniack Crudelis --- data/helpers.d/apt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index 55c85c90b..b2c781faf 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -186,7 +186,10 @@ ynh_package_install_from_equivs () { (cd "$TMPDIR" equivs-build ./control 1> /dev/null dpkg --force-depends -i "./${pkgname}_${pkgversion}_all.deb" 2>&1) - ynh_package_install -f || ynh_die --message="Unable to install dependencies" + # If install fails we use "apt-get check" to try to debug and diagnose possible unmet dependencies + # Note the use of { } which allows to group commands without starting a subshell (otherwise the ynh_die wouldn't exit the current shell). + # Be careful with the syntax : the semicolon + space at the end is important! + ynh_package_install -f || { apt-get check 2>&1; ynh_die --message="Unable to install dependencies"; } [[ -n "$TMPDIR" ]] && rm -rf $TMPDIR # Remove the temp dir. # check if the package is actually installed From 4399c9b740b5c4901de76f5b2245df4b349eae89 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 22 Mar 2020 01:40:02 +0100 Subject: [PATCH 0657/3170] Tweak exception messages to hopefully help debugging if this happens --- data/hooks/diagnosis/14-ports.py | 2 +- data/hooks/diagnosis/16-http.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/hooks/diagnosis/14-ports.py b/data/hooks/diagnosis/14-ports.py index aaf31d561..9c0e94721 100644 --- a/data/hooks/diagnosis/14-ports.py +++ b/data/hooks/diagnosis/14-ports.py @@ -39,7 +39,7 @@ class PortsDiagnoser(Diagnoser): elif r["status"] != "ok" or "ports" not in r.keys() or not isinstance(r["ports"], dict): raise Exception("Bad syntax for response ? Raw json: %s" % str(r)) else: - raise Exception("Bad response from the server https://diagnosis.yunohost.org : %s" % str(r.status_code)) + raise Exception("Bad response from the server https://diagnosis.yunohost.org/check-ports : %s - %s" % (str(r.status_code), r.content)) except Exception as e: raise YunohostError("diagnosis_ports_could_not_diagnose", error=e) diff --git a/data/hooks/diagnosis/16-http.py b/data/hooks/diagnosis/16-http.py index ac30dad78..d36e85f41 100644 --- a/data/hooks/diagnosis/16-http.py +++ b/data/hooks/diagnosis/16-http.py @@ -39,7 +39,7 @@ class HttpDiagnoser(Diagnoser): else: raise Exception("Bad syntax for response ? Raw json: %s" % str(r)) else: - raise Exception("Bad response from the server https://diagnosis.yunohost.org : %s" % str(r.status_code)) + raise Exception("Bad response from the server https://diagnosis.yunohost.org/check-http : %s - %s" % (str(r.status_code), r.content)) except Exception as e: raise YunohostError("diagnosis_http_could_not_diagnose", error=e) From fff2fcd67cf66a6dfab02baa9c3ec036e9941394 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 22 Mar 2020 01:41:58 +0100 Subject: [PATCH 0658/3170] Simplify identation (and diff) by reversing the if statement --- data/hooks/diagnosis/14-ports.py | 23 +++++++++++------------ data/hooks/diagnosis/16-http.py | 19 +++++++++---------- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/data/hooks/diagnosis/14-ports.py b/data/hooks/diagnosis/14-ports.py index 9c0e94721..d81fd39cc 100644 --- a/data/hooks/diagnosis/14-ports.py +++ b/data/hooks/diagnosis/14-ports.py @@ -27,19 +27,18 @@ class PortsDiagnoser(Diagnoser): try: r = requests.post('https://diagnosis.yunohost.org/check-ports', json={'ports': ports.keys()}, timeout=30) - if r.status_code == 200: - r = r.json() - if "status" not in r.keys(): - raise Exception("Bad syntax for response ? Raw json: %s" % str(r)) - elif r["status"] == "error": - if "content" in r.keys(): - raise Exception(r["content"]) - else: - raise Exception("Bad syntax for response ? Raw json: %s" % str(r)) - elif r["status"] != "ok" or "ports" not in r.keys() or not isinstance(r["ports"], dict): - raise Exception("Bad syntax for response ? Raw json: %s" % str(r)) - else: + if r.status_code != 200: raise Exception("Bad response from the server https://diagnosis.yunohost.org/check-ports : %s - %s" % (str(r.status_code), r.content)) + r = r.json() + if "status" not in r.keys(): + raise Exception("Bad syntax for response ? Raw json: %s" % str(r)) + elif r["status"] == "error": + if "content" in r.keys(): + raise Exception(r["content"]) + else: + raise Exception("Bad syntax for response ? Raw json: %s" % str(r)) + elif r["status"] != "ok" or "ports" not in r.keys() or not isinstance(r["ports"], dict): + raise Exception("Bad syntax for response ? Raw json: %s" % str(r)) except Exception as e: raise YunohostError("diagnosis_ports_could_not_diagnose", error=e) diff --git a/data/hooks/diagnosis/16-http.py b/data/hooks/diagnosis/16-http.py index d36e85f41..bd862b408 100644 --- a/data/hooks/diagnosis/16-http.py +++ b/data/hooks/diagnosis/16-http.py @@ -29,17 +29,16 @@ class HttpDiagnoser(Diagnoser): try: r = requests.post('https://diagnosis.yunohost.org/check-http', json={'domain': domain, "nonce": nonce}, timeout=30) - if r.status_code == 200: - r = r.json() - if "status" not in r.keys(): - raise Exception("Bad syntax for response ? Raw json: %s" % str(r)) - elif r["status"] == "error" and ("code" not in r.keys() or not r["code"].startswith("error_http_check_")): - if "content" in r.keys(): - raise Exception(r["content"]) - else: - raise Exception("Bad syntax for response ? Raw json: %s" % str(r)) - else: + if r.status_code != 200: raise Exception("Bad response from the server https://diagnosis.yunohost.org/check-http : %s - %s" % (str(r.status_code), r.content)) + r = r.json() + if "status" not in r.keys(): + raise Exception("Bad syntax for response ? Raw json: %s" % str(r)) + elif r["status"] == "error" and ("code" not in r.keys() or not r["code"].startswith("error_http_check_")): + if "content" in r.keys(): + raise Exception(r["content"]) + else: + raise Exception("Bad syntax for response ? Raw json: %s" % str(r)) except Exception as e: raise YunohostError("diagnosis_http_could_not_diagnose", error=e) From c6e8bb5d26bb9bf85ded4902f409204c9d3825bc Mon Sep 17 00:00:00 2001 From: pitchum Date: Wed, 30 Oct 2019 09:07:58 +0100 Subject: [PATCH 0659/3170] Always expect subdomain xmpp-upload.domain.net. This subdomain will be part of Letsencrypt certificate so it MUST be defined in DNS zone otherwise certificate renewal will fail. --- data/templates/ssl/openssl.cnf | 2 +- src/yunohost/certificate.py | 3 +++ src/yunohost/domain.py | 2 ++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/data/templates/ssl/openssl.cnf b/data/templates/ssl/openssl.cnf index fa5d19fa3..3ef7d80c3 100644 --- a/data/templates/ssl/openssl.cnf +++ b/data/templates/ssl/openssl.cnf @@ -192,7 +192,7 @@ authorityKeyIdentifier=keyid,issuer basicConstraints = CA:FALSE keyUsage = nonRepudiation, digitalSignature, keyEncipherment -subjectAltName=DNS:yunohost.org,DNS:www.yunohost.org,DNS:ns.yunohost.org +subjectAltName=DNS:yunohost.org,DNS:www.yunohost.org,DNS:ns.yunohost.org,DNS:xmpp-upload.yunohost.org [ v3_ca ] diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index d141ac8e5..9b50749ea 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -639,6 +639,9 @@ def _prepare_certificate_signing_request(domain, key_file, output_folder): # Set the domain csr.get_subject().CN = domain + # Include xmpp-upload subdomain as subject alternate names + csr.add_extensions([crypto.X509Extension("subjectAltName", False, "DNS:xmpp-upload." + domain)]) + # Set the key with open(key_file, 'rt') as f: key = crypto.load_privatekey(crypto.FILETYPE_PEM, f.read()) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 8f8a68812..5037e9334 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -412,6 +412,7 @@ def _build_dns_conf(domain, ttl=3600): {"type": "CNAME", "name": "muc", "value": "@", "ttl": 3600}, {"type": "CNAME", "name": "pubsub", "value": "@", "ttl": 3600}, {"type": "CNAME", "name": "vjud", "value": "@", "ttl": 3600} + {"type": "CNAME", "name": "xmpp-upload", "value": "@", "ttl": 3600} ], "mail": [ {"type": "MX", "name": "@", "value": "10 domain.tld.", "ttl": 3600}, @@ -453,6 +454,7 @@ def _build_dns_conf(domain, ttl=3600): ["muc", ttl, "CNAME", "@"], ["pubsub", ttl, "CNAME", "@"], ["vjud", ttl, "CNAME", "@"], + ["xmpp-upload", ttl, "CNAME", "@"], ] # SPF record From 994f0ca1efa6439a5338f9fc35b9db31c179b04d Mon Sep 17 00:00:00 2001 From: pitchum Date: Wed, 30 Oct 2019 18:14:25 +0100 Subject: [PATCH 0660/3170] nginx + metronome config for http_upload --- data/hooks/conf_regen/12-metronome | 4 ++ data/templates/metronome/metronome.cfg.lua | 9 ++- data/templates/nginx/server.tpl.conf | 83 +++++++++++++++++++++- 3 files changed, 93 insertions(+), 3 deletions(-) diff --git a/data/hooks/conf_regen/12-metronome b/data/hooks/conf_regen/12-metronome index fbd956e7c..db5910620 100755 --- a/data/hooks/conf_regen/12-metronome +++ b/data/hooks/conf_regen/12-metronome @@ -47,6 +47,10 @@ do_post_regen() { # create metronome directories for domains for domain in $domain_list; do mkdir -p "/var/lib/metronome/${domain//./%2e}/pep" + # http_upload directory must be writable by metronome and readable by nginx + mkdir -p "/var/www/xmpp-upload.${domain}/upload" + chmod g+s "/var/www/xmpp-upload.${domain}/upload" + chown -R metronome:www-data "/var/www/xmpp-upload.${domain}" done # fix some permissions diff --git a/data/templates/metronome/metronome.cfg.lua b/data/templates/metronome/metronome.cfg.lua index 0640ef9d5..c1420c7fb 100644 --- a/data/templates/metronome/metronome.cfg.lua +++ b/data/templates/metronome/metronome.cfg.lua @@ -85,7 +85,7 @@ use_ipv6 = true disco_items = { { "muc.{{ main_domain }}" }, { "pubsub.{{ main_domain }}" }, - { "upload.{{ main_domain }}" }, + { "xmpp-upload.{{ main_domain }}" }, { "vjud.{{ main_domain }}" } }; @@ -141,11 +141,16 @@ Component "pubsub.{{ main_domain }}" "pubsub" unrestricted_node_creation = true -- Anyone can create a PubSub node (from any server) ---Set up a HTTP Upload service -Component "upload.{{ main_domain }}" "http_upload" +Component "xmpp-upload.{{ main_domain }}" "http_upload" name = "{{ main_domain }} Sharing Service" + http_file_path = "/var/www/xmpp-upload.{{ main_domain }}/upload" + http_external_url = "https://xmpp-upload.{{ main_domain }}:443" + http_file_base_path = "/upload" http_file_size_limit = 6*1024*1024 http_file_quota = 60*1024*1024 + http_upload_file_size_limit = 100 * 1024 * 1024 -- bytes + http_upload_quota = 10 * 1024 * 1024 * 1024 -- bytes ---Set up a VJUD service diff --git a/data/templates/nginx/server.tpl.conf b/data/templates/nginx/server.tpl.conf index 9acc6c0fd..dac188ea3 100644 --- a/data/templates/nginx/server.tpl.conf +++ b/data/templates/nginx/server.tpl.conf @@ -6,7 +6,7 @@ map $http_upgrade $connection_upgrade { server { listen 80; listen [::]:80; - server_name {{ domain }}; + server_name {{ domain }} xmpp-upload.{{ domain }}; access_by_lua_file /usr/share/ssowat/access.lua; @@ -97,3 +97,84 @@ server { access_log /var/log/nginx/{{ domain }}-access.log; error_log /var/log/nginx/{{ domain }}-error.log; } + +# vhost dedicated to XMPP http_upload +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + server_name xmpp-upload.{{ domain }}; + + location /upload { + alias /var/www/xmpp-upload.{{ domain }}/upload; + # Pass all requests to metronome, except for GET and HEAD requests. + limit_except GET HEAD { + proxy_pass http://localhost:5290; + } + + include proxy_params; + add_header 'Access-Control-Allow-Origin' '*'; + add_header 'Access-Control-Allow-Methods' 'HEAD, GET, PUT, OPTIONS'; + add_header 'Access-Control-Allow-Headers' 'Authorization'; + add_header 'Access-Control-Allow-Credentials' 'true'; + client_max_body_size 105M; # Choose a value a bit higher than the max upload configured in XMPP server + } + + ssl_certificate /etc/yunohost/certs/{{ domain }}/crt.pem; + ssl_certificate_key /etc/yunohost/certs/{{ domain }}/key.pem; + ssl_session_timeout 5m; + ssl_session_cache shared:SSL:50m; + + {% if compatibility == "modern" %} + # Ciphers with modern compatibility + # https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=nginx-1.6.2&openssl=1.0.1t&hsts=yes&profile=modern + # The following configuration use modern ciphers, but remove compatibility with some old clients (android < 5.0, Internet Explorer < 10, ...) + ssl_protocols TLSv1.2; + ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256'; + ssl_prefer_server_ciphers on; + {% else %} + # As suggested by Mozilla : https://wiki.mozilla.org/Security/Server_Side_TLS and https://en.wikipedia.org/wiki/Curve25519 + ssl_ecdh_curve secp521r1:secp384r1:prime256v1; + ssl_prefer_server_ciphers on; + + # Ciphers with intermediate compatibility + # https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=nginx-1.6.2&openssl=1.0.1t&hsts=yes&profile=intermediate + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS'; + + # Uncomment the following directive after DH generation + # > openssl dhparam -out /etc/ssl/private/dh2048.pem -outform PEM -2 2048 + #ssl_dhparam /etc/ssl/private/dh2048.pem; + {% endif %} + + # Follows the Web Security Directives from the Mozilla Dev Lab and the Mozilla Obervatory + Partners + # https://wiki.mozilla.org/Security/Guidelines/Web_Security + # https://observatory.mozilla.org/ + {% if domain_cert_ca != "Self-signed" %} + more_set_headers "Strict-Transport-Security : max-age=63072000; includeSubDomains; preload"; + {% endif %} + more_set_headers "Content-Security-Policy : upgrade-insecure-requests"; + more_set_headers "Content-Security-Policy-Report-Only : default-src https: data: 'unsafe-inline' 'unsafe-eval'"; + more_set_headers "X-Content-Type-Options : nosniff"; + more_set_headers "X-XSS-Protection : 1; mode=block"; + more_set_headers "X-Download-Options : noopen"; + more_set_headers "X-Permitted-Cross-Domain-Policies : none"; + more_set_headers "X-Frame-Options : SAMEORIGIN"; + + {% if domain_cert_ca == "Let's Encrypt" %} + # OCSP settings + ssl_stapling on; + ssl_stapling_verify on; + ssl_trusted_certificate /etc/yunohost/certs/{{ domain }}/crt.pem; + resolver 127.0.0.1 127.0.1.1 valid=300s; + resolver_timeout 5s; + {% endif %} + + # Disable gzip to protect against BREACH + # Read https://trac.nginx.org/nginx/ticket/1720 (text/html cannot be disabled!) + gzip off; + +# access_by_lua_file /usr/share/ssowat/access.lua; + + access_log /var/log/nginx/xmpp-upload.{{ domain }}-access.log; + error_log /var/log/nginx/xmpp-upload.{{ domain }}-error.log; +} From 0bd717a21e5567269abb7c98a003fa8fd019f020 Mon Sep 17 00:00:00 2001 From: pitchum Date: Sun, 22 Mar 2020 12:17:08 +0100 Subject: [PATCH 0661/3170] Include XMPP subdomain in certificate when possible. --- locales/en.json | 1 + src/yunohost/certificate.py | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/locales/en.json b/locales/en.json index d6784a78d..d2117b7d0 100644 --- a/locales/en.json +++ b/locales/en.json @@ -133,6 +133,7 @@ "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_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})", diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 9b50749ea..e49db9733 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -639,8 +639,14 @@ def _prepare_certificate_signing_request(domain, key_file, output_folder): # Set the domain csr.get_subject().CN = domain - # Include xmpp-upload subdomain as subject alternate names - csr.add_extensions([crypto.X509Extension("subjectAltName", False, "DNS:xmpp-upload." + domain)]) + # Include xmpp-upload subdomain in subject alternate names + subdomain="xmpp-upload." + domain + try: + _check_domain_is_ready_for_ACME(subdomain) + logger.info("Subdmain {} is ready for ACME and will be included in the certificate.".format(subdomain)) + csr.add_extensions([crypto.X509Extension("subjectAltName", False, "DNS:" + subdomain)]) + except YunohostError: + logger.warning(m18n.n('certmanager_warning_subdomain_dns_record', subdomain=subdomain, domain=domain)) # Set the key with open(key_file, 'rt') as f: From b9aa5d143f7b179373f8bc36ec3b7d147d1bb083 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Sun, 22 Mar 2020 20:51:32 +0100 Subject: [PATCH 0662/3170] Allow public apps with no sso tile --- locales/en.json | 2 -- src/yunohost/app.py | 2 +- src/yunohost/permission.py | 12 ------------ src/yunohost/tests/test_permission.py | 21 --------------------- 4 files changed, 1 insertion(+), 36 deletions(-) diff --git a/locales/en.json b/locales/en.json index 17014cad0..d2972fa25 100644 --- a/locales/en.json +++ b/locales/en.json @@ -415,12 +415,10 @@ "pattern_positive_number": "Must be a positive number", "pattern_username": "Must be lower-case alphanumeric and underscore characters only", "pattern_password_app": "Sorry, passwords can not contain the following characters: {forbidden_chars}", - "permission_all_users_implicitly_added": "The permission was also implicitly granted to 'all_users' because it is required to allow the special group 'visitors'", "permission_already_allowed": "Group '{group}' already has permission '{permission}' enabled", "permission_already_disallowed": "Group '{group}' already has permission '{permission}' disabled'", "permission_already_exist": "Permission '{permission}' already exists", "permission_already_up_to_date": "The permission was not updated because the addition/removal requests already match the current state.", - "permission_cannot_remove_all_users_while_visitors_allowed": "You can't remove this permission for 'all_users' while it is still allowed for 'visitors'", "permission_cannot_remove_main": "Removing a main permission is not allowed", "permission_created": "Permission '{permission:s}' created", "permission_creation_failed": "Could not create permission '{permission}': {error}", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 0324a116a..c71162494 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -437,7 +437,7 @@ def app_map(app=None, raw=False, user=None): logger.warning("Uhoh, no main permission was found for app %s ... sounds like an app was only partially removed due to another bug :/" % app_id) continue main_perm = permissions[app_id + ".main"] - if user not in main_perm["corresponding_users"] and "visitors" not in main_perm["allowed"]: + if user not in main_perm["corresponding_users"]: continue domain = app_settings['domain'] diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 775a8cd71..71472eeaf 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -146,16 +146,6 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, if "visitors" not in new_allowed_groups or len(new_allowed_groups) >= 3: logger.warning(m18n.n("permission_currently_allowed_for_all_users")) - # If visitors are to be added, we shall make sure that "all_users" are also allowed - # (e.g. if visitors are allowed to visit nextcloud, you still want to allow people to log in ...) - if add and "visitors" in groups_to_add and "all_users" not in new_allowed_groups: - new_allowed_groups.append("all_users") - logger.warning(m18n.n("permission_all_users_implicitly_added")) - # If all_users are to be added, yet visitors are still to allowed, then we - # refuse it (c.f. previous comment...) - if remove and "all_users" in groups_to_remove and "visitors" in new_allowed_groups: - raise YunohostError('permission_cannot_remove_all_users_while_visitors_allowed') - # Don't update LDAP if we update exactly the same values if set(new_allowed_groups) == set(current_allowed_groups): logger.warning(m18n.n("permission_already_up_to_date")) @@ -270,8 +260,6 @@ def permission_create(operation_logger, permission, url=None, allowed=None, sync if allowed is not None: if not isinstance(allowed, list): allowed = [allowed] - if "visitors" in allowed and "all_users" not in allowed: - allowed.append("all_users") # Validate that the groups to add actually exist all_existing_groups = user_group_list()['groups'].keys() diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index 2e53f13a7..a4bd570e5 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -313,27 +313,6 @@ def test_permission_add_and_remove_group(mocker): assert res['wiki.main']['corresponding_users'] == ["alice"] -def test_permission_adding_visitors_implicitly_add_all_users(mocker): - - res = user_permission_list(full=True)['permissions'] - assert res['blog.main']['allowed'] == ["alice"] - - with message(mocker, "permission_updated", permission="blog.main"): - user_permission_update("blog.main", add="visitors") - - res = user_permission_list(full=True)['permissions'] - assert set(res['blog.main']['allowed']) == set(["alice", "visitors", "all_users"]) - - -def test_permission_cant_remove_all_users_if_visitors_allowed(mocker): - - with message(mocker, "permission_updated", permission="blog.main"): - user_permission_update("blog.main", add=["visitors", "all_users"]) - - with raiseYunohostError(mocker, 'permission_cannot_remove_all_users_while_visitors_allowed'): - user_permission_update("blog.main", remove="all_users") - - def test_permission_add_group_already_allowed(mocker): with message(mocker, "permission_already_allowed", permission="blog.main", group="alice"): user_permission_update("blog.main", add="alice") From 8a1cc89d9787b5c4d39945d7b7ffa72355f11ce7 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Mon, 23 Mar 2020 00:40:22 +0100 Subject: [PATCH 0663/3170] Fix smoll bug --- 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 c71162494..65782f56a 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -461,7 +461,7 @@ def app_map(app=None, raw=False, user=None): for perm_name, perm_info in this_app_perms.items(): # If we're building the map for a specific user, check the user # actually is allowed for this specific perm - if user and user not in perm_info["corresponding_users"] and "visitors" not in perm_info["allowed"]: + if user and user not in perm_info["corresponding_users"]: continue if perm_info["url"].startswith("re:"): # Here, we have an issue if the chosen url is a regex, because From 8085b3a2c2ead2963138a38f43d41aa9faffb19c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 23 Mar 2020 19:35:41 +0100 Subject: [PATCH 0664/3170] When dumping debug info after app script failure, be slightly smarter and stop at ynh_die to have more meaningul lines being shown --- 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 056083b67..0ffb28c8b 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -896,7 +896,7 @@ def dump_app_log_extract_for_debugging(operation_logger): line = line.strip().split(": ", 1)[1] lines_to_display.append(line) - if line.endswith("+ ynh_exit_properly"): + if line.endswith("+ ynh_exit_properly") or " + ynh_die " in line: break elif len(lines_to_display) > 20: lines_to_display.pop(0) From 27f6899b65dc63bcfbf77755af6b0ee064af6821 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 23 Mar 2020 22:15:03 +0100 Subject: [PATCH 0665/3170] /var/www/xmpp-upload.{domain} -> /var/xmpp-upload/{domain} --- data/hooks/conf_regen/12-metronome | 6 +++--- data/templates/metronome/metronome.cfg.lua | 2 +- data/templates/nginx/server.tpl.conf | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/data/hooks/conf_regen/12-metronome b/data/hooks/conf_regen/12-metronome index db5910620..0cfb42fd4 100755 --- a/data/hooks/conf_regen/12-metronome +++ b/data/hooks/conf_regen/12-metronome @@ -48,9 +48,9 @@ do_post_regen() { for domain in $domain_list; do mkdir -p "/var/lib/metronome/${domain//./%2e}/pep" # http_upload directory must be writable by metronome and readable by nginx - mkdir -p "/var/www/xmpp-upload.${domain}/upload" - chmod g+s "/var/www/xmpp-upload.${domain}/upload" - chown -R metronome:www-data "/var/www/xmpp-upload.${domain}" + mkdir -p "/var/xmpp-upload/${domain}/upload" + chmod g+s "/var/xmpp-upload/${domain}/upload" + chown -R metronome:www-data "/var/xmpp-upload/${domain}" done # fix some permissions diff --git a/data/templates/metronome/metronome.cfg.lua b/data/templates/metronome/metronome.cfg.lua index c1420c7fb..b35684add 100644 --- a/data/templates/metronome/metronome.cfg.lua +++ b/data/templates/metronome/metronome.cfg.lua @@ -144,7 +144,7 @@ Component "pubsub.{{ main_domain }}" "pubsub" Component "xmpp-upload.{{ main_domain }}" "http_upload" name = "{{ main_domain }} Sharing Service" - http_file_path = "/var/www/xmpp-upload.{{ main_domain }}/upload" + http_file_path = "/var/xmpp-upload/{{ main_domain }}/upload" http_external_url = "https://xmpp-upload.{{ main_domain }}:443" http_file_base_path = "/upload" http_file_size_limit = 6*1024*1024 diff --git a/data/templates/nginx/server.tpl.conf b/data/templates/nginx/server.tpl.conf index dac188ea3..823e3ce39 100644 --- a/data/templates/nginx/server.tpl.conf +++ b/data/templates/nginx/server.tpl.conf @@ -105,7 +105,7 @@ server { server_name xmpp-upload.{{ domain }}; location /upload { - alias /var/www/xmpp-upload.{{ domain }}/upload; + alias /var/xmpp-upload/{{ domain }}/upload; # Pass all requests to metronome, except for GET and HEAD requests. limit_except GET HEAD { proxy_pass http://localhost:5290; From af415e38e6a57236ac2cac13b70e20e96414a6ab Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 23 Mar 2020 22:43:29 +0100 Subject: [PATCH 0666/3170] Factorize ciphers and headers configuration into a common file for all vhosts --- data/hooks/conf_regen/15-nginx | 1 + data/templates/nginx/server.tpl.conf | 76 +--------------------------- 2 files changed, 3 insertions(+), 74 deletions(-) diff --git a/data/hooks/conf_regen/15-nginx b/data/hooks/conf_regen/15-nginx index 55a5494b2..11e5f596c 100755 --- a/data/hooks/conf_regen/15-nginx +++ b/data/hooks/conf_regen/15-nginx @@ -49,6 +49,7 @@ do_pre_regen() { # Support different strategy for security configurations export compatibility="$(yunohost settings get 'security.nginx.compatibility')" + ynh_render_template "security.conf.inc" "${nginx_conf_dir}/security.conf.inc" # add domain conf files for domain in $domain_list; do diff --git a/data/templates/nginx/server.tpl.conf b/data/templates/nginx/server.tpl.conf index 823e3ce39..0eb64dd8d 100644 --- a/data/templates/nginx/server.tpl.conf +++ b/data/templates/nginx/server.tpl.conf @@ -38,42 +38,11 @@ server { ssl_session_timeout 5m; ssl_session_cache shared:SSL:50m; - {% if compatibility == "modern" %} - # Ciphers with modern compatibility - # https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=nginx-1.6.2&openssl=1.0.1t&hsts=yes&profile=modern - # The following configuration use modern ciphers, but remove compatibility with some old clients (android < 5.0, Internet Explorer < 10, ...) - ssl_protocols TLSv1.2; - ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256'; - ssl_prefer_server_ciphers on; - {% else %} - # As suggested by Mozilla : https://wiki.mozilla.org/Security/Server_Side_TLS and https://en.wikipedia.org/wiki/Curve25519 - ssl_ecdh_curve secp521r1:secp384r1:prime256v1; - ssl_prefer_server_ciphers on; + include /etc/nginx/conf.d/security.conf.inc; - # Ciphers with intermediate compatibility - # https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=nginx-1.6.2&openssl=1.0.1t&hsts=yes&profile=intermediate - ssl_protocols TLSv1 TLSv1.1 TLSv1.2; - ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS'; - - # Uncomment the following directive after DH generation - # > openssl dhparam -out /etc/ssl/private/dh2048.pem -outform PEM -2 2048 - #ssl_dhparam /etc/ssl/private/dh2048.pem; - {% endif %} - - # Follows the Web Security Directives from the Mozilla Dev Lab and the Mozilla Obervatory + Partners - # https://wiki.mozilla.org/Security/Guidelines/Web_Security - # https://observatory.mozilla.org/ {% if domain_cert_ca != "Self-signed" %} more_set_headers "Strict-Transport-Security : max-age=63072000; includeSubDomains; preload"; {% endif %} - more_set_headers "Content-Security-Policy : upgrade-insecure-requests"; - more_set_headers "Content-Security-Policy-Report-Only : default-src https: data: 'unsafe-inline' 'unsafe-eval'"; - more_set_headers "X-Content-Type-Options : nosniff"; - more_set_headers "X-XSS-Protection : 1; mode=block"; - more_set_headers "X-Download-Options : noopen"; - more_set_headers "X-Permitted-Cross-Domain-Policies : none"; - more_set_headers "X-Frame-Options : SAMEORIGIN"; - {% if domain_cert_ca == "Let's Encrypt" %} # OCSP settings ssl_stapling on; @@ -83,10 +52,6 @@ server { resolver_timeout 5s; {% endif %} - # Disable gzip to protect against BREACH - # Read https://trac.nginx.org/nginx/ticket/1720 (text/html cannot be disabled!) - gzip off; - access_by_lua_file /usr/share/ssowat/access.lua; include /etc/nginx/conf.d/{{ domain }}.d/*.conf; @@ -124,42 +89,11 @@ server { ssl_session_timeout 5m; ssl_session_cache shared:SSL:50m; - {% if compatibility == "modern" %} - # Ciphers with modern compatibility - # https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=nginx-1.6.2&openssl=1.0.1t&hsts=yes&profile=modern - # The following configuration use modern ciphers, but remove compatibility with some old clients (android < 5.0, Internet Explorer < 10, ...) - ssl_protocols TLSv1.2; - ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256'; - ssl_prefer_server_ciphers on; - {% else %} - # As suggested by Mozilla : https://wiki.mozilla.org/Security/Server_Side_TLS and https://en.wikipedia.org/wiki/Curve25519 - ssl_ecdh_curve secp521r1:secp384r1:prime256v1; - ssl_prefer_server_ciphers on; + include /etc/nginx/conf.d/security.conf.inc; - # Ciphers with intermediate compatibility - # https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=nginx-1.6.2&openssl=1.0.1t&hsts=yes&profile=intermediate - ssl_protocols TLSv1 TLSv1.1 TLSv1.2; - ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS'; - - # Uncomment the following directive after DH generation - # > openssl dhparam -out /etc/ssl/private/dh2048.pem -outform PEM -2 2048 - #ssl_dhparam /etc/ssl/private/dh2048.pem; - {% endif %} - - # Follows the Web Security Directives from the Mozilla Dev Lab and the Mozilla Obervatory + Partners - # https://wiki.mozilla.org/Security/Guidelines/Web_Security - # https://observatory.mozilla.org/ {% if domain_cert_ca != "Self-signed" %} more_set_headers "Strict-Transport-Security : max-age=63072000; includeSubDomains; preload"; {% endif %} - more_set_headers "Content-Security-Policy : upgrade-insecure-requests"; - more_set_headers "Content-Security-Policy-Report-Only : default-src https: data: 'unsafe-inline' 'unsafe-eval'"; - more_set_headers "X-Content-Type-Options : nosniff"; - more_set_headers "X-XSS-Protection : 1; mode=block"; - more_set_headers "X-Download-Options : noopen"; - more_set_headers "X-Permitted-Cross-Domain-Policies : none"; - more_set_headers "X-Frame-Options : SAMEORIGIN"; - {% if domain_cert_ca == "Let's Encrypt" %} # OCSP settings ssl_stapling on; @@ -169,12 +103,6 @@ server { resolver_timeout 5s; {% endif %} - # Disable gzip to protect against BREACH - # Read https://trac.nginx.org/nginx/ticket/1720 (text/html cannot be disabled!) - gzip off; - -# access_by_lua_file /usr/share/ssowat/access.lua; - access_log /var/log/nginx/xmpp-upload.{{ domain }}-access.log; error_log /var/log/nginx/xmpp-upload.{{ domain }}-error.log; } From db0d748f6218b398fb44f7a400c74d6308fdb685 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Tue, 24 Mar 2020 00:59:16 +0100 Subject: [PATCH 0667/3170] Revoke before drop --- data/helpers.d/postgresql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data/helpers.d/postgresql b/data/helpers.d/postgresql index 6d8524e54..e5df73654 100644 --- a/data/helpers.d/postgresql +++ b/data/helpers.d/postgresql @@ -107,9 +107,9 @@ ynh_psql_create_db() { ynh_psql_drop_db() { local db=$1 # First, force disconnection of all clients connected to the database - # https://stackoverflow.com/questions/5408156/how-to-drop-a-postgresql-database-if-there-are-active-connections-to-it - # https://dba.stackexchange.com/questions/16426/how-to-drop-all-connections-to-a-specific-database-without-stopping-the-server - ynh_psql_execute_as_root --sql="SELECT pg_terminate_backend (pg_stat_activity.pid) FROM pg_stat_activity WHERE pg_stat_activity.datname = '$db';" --database="$db" + # https://stackoverflow.com/questions/17449420/postgresql-unable-to-drop-database-because-of-some-auto-connections-to-db + ynh_psql_execute_as_root --sql="REVOKE CONNECT ON DATABASE $db FROM public;" --database="$db" + ynh_psql_execute_as_root --sql="SELECT pg_terminate_backend (pg_stat_activity.pid) FROM pg_stat_activity WHERE pg_stat_activity.datname = '$db' AND pid <> pg_backend_pid();" --database="$db" sudo --login --user=postgres dropdb $db } From 8da5aa055d46530a65e4cbba6a792a0b63874d37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Sun, 29 Dec 2019 21:06:28 +0100 Subject: [PATCH 0668/3170] Improve stability of unit tests --- src/yunohost/tests/test_apps.py | 28 +++++++++++++++++++++++++++ src/yunohost/tests/test_permission.py | 14 ++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/src/yunohost/tests/test_apps.py b/src/yunohost/tests/test_apps.py index d9102edbe..a85a36061 100644 --- a/src/yunohost/tests/test_apps.py +++ b/src/yunohost/tests/test_apps.py @@ -13,6 +13,7 @@ from yunohost.app import app_install, app_remove, app_ssowatconf, _is_installed, from yunohost.domain import _get_maindomain, domain_add, domain_remove, domain_list from yunohost.utils.error import YunohostError from yunohost.tests.test_permission import check_LDAP_db_integrity, check_permission_for_apps +from yunohost.permission import user_permission_list, permission_delete def setup_function(function): @@ -60,6 +61,33 @@ def clean(): os.system("systemctl reset-failed nginx") # Reset failed quota for service to avoid running into start-limit rate ? os.system("systemctl start nginx") + # Clean permission + for permission_name in user_permission_list(short=True)["permissions"]: + if "legacy_app" in permission_name or \ + "full_domain_app" in permission_name or \ + "break_yo_system" in permission_name: + permission_delete(permission_name, force=True) + + # Clean database + os.system("bash -c \"mysql -u root --password=$(cat /etc/yunohost/mysql) 2>/dev/null <<< 'DROP DATABASE legacy_app' \"") + os.system("bash -c \"mysql -u root --password=$(cat /etc/yunohost/mysql) 2>/dev/null <<< 'DROP USER legacy_app@localhost'\"") + os.system("bash -c \"mysql -u root --password=$(cat /etc/yunohost/mysql) 2>/dev/null <<< 'DROP DATABASE legacy_app__2'\"") + os.system("bash -c \"mysql -u root --password=$(cat /etc/yunohost/mysql) 2>/dev/null <<< 'DROP USER legacy_app__2@localhost'\"") + os.system("bash -c \"mysql -u root --password=$(cat /etc/yunohost/mysql) 2>/dev/null <<< 'DROP DATABASE legacy_app__3'\"") + os.system("bash -c \"mysql -u root --password=$(cat /etc/yunohost/mysql) 2>/dev/null <<< 'DROP USER legacy_app__3@localhost'\"") + os.system("bash -c \"mysql -u root --password=$(cat /etc/yunohost/mysql) 2>/dev/null <<< 'DROP DATABASE full_domain'\"") + os.system("bash -c \"mysql -u root --password=$(cat /etc/yunohost/mysql) 2>/dev/null <<< 'DROP USER full_domain@localhost'\"") + os.system("bash -c \"mysql -u root --password=$(cat /etc/yunohost/mysql) 2>/dev/null <<< 'DROP DATABASE full_domain__2'\"") + os.system("bash -c \"mysql -u root --password=$(cat /etc/yunohost/mysql) 2>/dev/null <<< 'DROP USER full_domain__2@localhost'\"") + os.system("bash -c \"mysql -u root --password=$(cat /etc/yunohost/mysql) 2>/dev/null <<< 'DROP DATABASE full_domain__3'\"") + os.system("bash -c \"mysql -u root --password=$(cat /etc/yunohost/mysql) 2>/dev/null <<< 'DROP USER full_domain__3@localhost'\"") + os.system("bash -c \"mysql -u root --password=$(cat /etc/yunohost/mysql) 2>/dev/null <<< 'DROP DATABASE break_yo_system'\"") + os.system("bash -c \"mysql -u root --password=$(cat /etc/yunohost/mysql) 2>/dev/null <<< 'DROP USER break_yo_system@localhost'\"") + os.system("bash -c \"mysql -u root --password=$(cat /etc/yunohost/mysql) 2>/dev/null <<< 'DROP DATABASE break_yo_system__2'\"") + os.system("bash -c \"mysql -u root --password=$(cat /etc/yunohost/mysql) 2>/dev/null <<< 'DROP USER break_yo_system__2@localhost'\"") + os.system("bash -c \"mysql -u root --password=$(cat /etc/yunohost/mysql) 2>/dev/null <<< 'DROP DATABASE break_yo_system__3'\"") + os.system("bash -c \"mysql -u root --password=$(cat /etc/yunohost/mysql) 2>/dev/null <<< 'DROP USER break_yo_system__3@localhost'\"") + @pytest.fixture(autouse=True) def check_LDAP_db_integrity_call(): diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index 636d9b4b1..c8ff77493 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -14,6 +14,20 @@ from yunohost.domain import _get_maindomain maindomain = _get_maindomain() dummy_password = "test123Ynh" +# Dirty patch of DNS resolution. Force the DNS to 127.0.0.1 address even if dnsmasq have the public address. +# Mainly used for 'can_access_webpage' function +import socket +dns_cache = {(maindomain, 443, 0, 1): [(2, 1, 6, '', ('127.0.0.1', 443))]} +prv_getaddrinfo = socket.getaddrinfo +def new_getaddrinfo(*args): + try: + return dns_cache[args] + except KeyError: + res = prv_getaddrinfo(*args) + dns_cache[args] = res + return res +socket.getaddrinfo = new_getaddrinfo + def clean_user_groups_permission(): for u in user_list()['users']: From 19fc1806a4d4aec584f76cbfc48253eb0022b02b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Sun, 29 Dec 2019 23:12:03 +0100 Subject: [PATCH 0669/3170] Fix app catalog tests --- src/yunohost/tests/test_appscatalog.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/tests/test_appscatalog.py b/src/yunohost/tests/test_appscatalog.py index a51d1085e..c3ece7907 100644 --- a/src/yunohost/tests/test_appscatalog.py +++ b/src/yunohost/tests/test_appscatalog.py @@ -31,8 +31,8 @@ DUMMY_APP_CATALOG = """{ "bar": {"id": "bar", "level": 7, "category": "swag", "manifest":{"description": "Bar"}} }, "categories": [ - {"id": "yolo", "description": "YoLo", "title": "Yolo"}, - {"id": "swag", "description": "sWaG", "title": "Swag"} + {"id": "yolo", "description": "YoLo", "title": {"en": "Yolo"}}, + {"id": "swag", "description": "sWaG", "title": {"en": "Swag"}} ] } """ From 97f50e396cad5a7d0ac31c8cfce18ae0352beba0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Sun, 29 Dec 2019 23:36:43 +0100 Subject: [PATCH 0670/3170] Fix settings boolean value management --- src/yunohost/settings.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/yunohost/settings.py b/src/yunohost/settings.py index 2427f8677..72477e4de 100644 --- a/src/yunohost/settings.py +++ b/src/yunohost/settings.py @@ -23,15 +23,18 @@ def is_boolean(value): arg -- The string to check Returns: - Boolean + (is_boolean, boolean_value) """ if isinstance(value, bool): - return True + return True, value elif isinstance(value, basestring): - return str(value).lower() in ['true', 'on', 'yes', 'false', 'off', 'no'] + if str(value).lower() in ['true', 'on', 'yes', 'false', 'off', 'no']: + return True, str(value).lower() in ['true', 'on', 'yes'] + else: + return False, None else: - return False + return False, None # a settings entry is in the form of: @@ -114,7 +117,10 @@ def settings_set(key, value): key_type = settings[key]["type"] if key_type == "bool": - if not is_boolean(value): + boolean_value = is_boolean(value) + if boolean_value[0]: + value = boolean_value[1] + else: raise YunohostError('global_settings_bad_type_for_setting', setting=key, received_type=type(value).__name__, expected_type=key_type) elif key_type == "int": From edd607402520153c534878f142fe3b7a1d0c8ae5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Mon, 30 Dec 2019 00:02:57 +0100 Subject: [PATCH 0671/3170] Force remove domain --- src/yunohost/tests/test_apps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/tests/test_apps.py b/src/yunohost/tests/test_apps.py index a85a36061..85cd85442 100644 --- a/src/yunohost/tests/test_apps.py +++ b/src/yunohost/tests/test_apps.py @@ -109,7 +109,7 @@ def secondary_domain(request): domain_add("example.test") def remove_example_domain(): - domain_remove("example.test") + domain_remove("example.test", force=True) request.addfinalizer(remove_example_domain) return "example.test" From 7e8a00b9dc2e7b47065cccd22e96a27e96f69bfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Mon, 30 Dec 2019 00:11:01 +0100 Subject: [PATCH 0672/3170] Change scope secondary domain managment --- src/yunohost/tests/test_apps.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/tests/test_apps.py b/src/yunohost/tests/test_apps.py index 85cd85442..fdfcc9176 100644 --- a/src/yunohost/tests/test_apps.py +++ b/src/yunohost/tests/test_apps.py @@ -102,14 +102,14 @@ def check_permission_for_apps_call(): yield check_permission_for_apps() -@pytest.fixture(scope="session") +@pytest.fixture(scope="module") def secondary_domain(request): if "example.test" not in domain_list()["domains"]: domain_add("example.test") def remove_example_domain(): - domain_remove("example.test", force=True) + domain_remove("example.test") request.addfinalizer(remove_example_domain) return "example.test" From 94c066dc53f347af09bdc5058521f0ca0de21abe Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 24 Mar 2020 04:47:21 +0100 Subject: [PATCH 0673/3170] Factorize stuff in cleaning function to avoid repeating so much stuff --- src/yunohost/tests/test_apps.py | 62 ++++++++------------------------- 1 file changed, 14 insertions(+), 48 deletions(-) diff --git a/src/yunohost/tests/test_apps.py b/src/yunohost/tests/test_apps.py index fdfcc9176..6bc625a91 100644 --- a/src/yunohost/tests/test_apps.py +++ b/src/yunohost/tests/test_apps.py @@ -30,65 +30,31 @@ def clean(): os.system("mkdir -p /etc/ssowat/") app_ssowatconf() - # Gotta first remove break yo system - # because some remaining stuff might - # make the other app_remove crashs ;P - if _is_installed("break_yo_system"): - app_remove("break_yo_system") + test_apps = ["break_yo_system", "legacy_app", "legacy_app__2", "full_domain_app"] - if _is_installed("legacy_app"): - app_remove("legacy_app") + for test_app in test_apps: - if _is_installed("full_domain_app"): - app_remove("full_domain_app") + if _is_installed(test_app): + app_remove(test_app) - to_remove = [] - to_remove += glob.glob("/etc/nginx/conf.d/*.d/*legacy*") - to_remove += glob.glob("/etc/nginx/conf.d/*.d/*full_domain*") - to_remove += glob.glob("/etc/nginx/conf.d/*.d/*break_yo_system*") - for filepath in to_remove: - os.remove(filepath) + for filepath in glob.glob("/etc/nginx/conf.d/*.d/*%s*" % test_app): + os.remove(filepath) + for folderpath in glob.glob("/etc/yunohost/apps/*%s*" % test_app): + shutil.rmtree(folderpath, ignore_errors=True) + for folderpath in glob.glob("/var/www/*%s*" % test_app): + shutil.rmtree(folderpath, ignore_errors=True) - to_remove = [] - to_remove += glob.glob("/etc/yunohost/apps/*legacy_app*") - to_remove += glob.glob("/etc/yunohost/apps/*full_domain_app*") - to_remove += glob.glob("/etc/yunohost/apps/*break_yo_system*") - to_remove += glob.glob("/var/www/*legacy*") - to_remove += glob.glob("/var/www/*full_domain*") - for folderpath in to_remove: - shutil.rmtree(folderpath, ignore_errors=True) + os.system("bash -c \"mysql -u root --password=$(cat /etc/yunohost/mysql) 2>/dev/null <<< 'DROP DATABASE %s' \"" % test_app) + os.system("bash -c \"mysql -u root --password=$(cat /etc/yunohost/mysql) 2>/dev/null <<< 'DROP USER %s@localhost'\"" % test_app) os.system("systemctl reset-failed nginx") # Reset failed quota for service to avoid running into start-limit rate ? os.system("systemctl start nginx") - # Clean permission + # Clean permissions for permission_name in user_permission_list(short=True)["permissions"]: - if "legacy_app" in permission_name or \ - "full_domain_app" in permission_name or \ - "break_yo_system" in permission_name: + if any(test_app in permission_name for test_app in test_apps): permission_delete(permission_name, force=True) - # Clean database - os.system("bash -c \"mysql -u root --password=$(cat /etc/yunohost/mysql) 2>/dev/null <<< 'DROP DATABASE legacy_app' \"") - os.system("bash -c \"mysql -u root --password=$(cat /etc/yunohost/mysql) 2>/dev/null <<< 'DROP USER legacy_app@localhost'\"") - os.system("bash -c \"mysql -u root --password=$(cat /etc/yunohost/mysql) 2>/dev/null <<< 'DROP DATABASE legacy_app__2'\"") - os.system("bash -c \"mysql -u root --password=$(cat /etc/yunohost/mysql) 2>/dev/null <<< 'DROP USER legacy_app__2@localhost'\"") - os.system("bash -c \"mysql -u root --password=$(cat /etc/yunohost/mysql) 2>/dev/null <<< 'DROP DATABASE legacy_app__3'\"") - os.system("bash -c \"mysql -u root --password=$(cat /etc/yunohost/mysql) 2>/dev/null <<< 'DROP USER legacy_app__3@localhost'\"") - os.system("bash -c \"mysql -u root --password=$(cat /etc/yunohost/mysql) 2>/dev/null <<< 'DROP DATABASE full_domain'\"") - os.system("bash -c \"mysql -u root --password=$(cat /etc/yunohost/mysql) 2>/dev/null <<< 'DROP USER full_domain@localhost'\"") - os.system("bash -c \"mysql -u root --password=$(cat /etc/yunohost/mysql) 2>/dev/null <<< 'DROP DATABASE full_domain__2'\"") - os.system("bash -c \"mysql -u root --password=$(cat /etc/yunohost/mysql) 2>/dev/null <<< 'DROP USER full_domain__2@localhost'\"") - os.system("bash -c \"mysql -u root --password=$(cat /etc/yunohost/mysql) 2>/dev/null <<< 'DROP DATABASE full_domain__3'\"") - os.system("bash -c \"mysql -u root --password=$(cat /etc/yunohost/mysql) 2>/dev/null <<< 'DROP USER full_domain__3@localhost'\"") - os.system("bash -c \"mysql -u root --password=$(cat /etc/yunohost/mysql) 2>/dev/null <<< 'DROP DATABASE break_yo_system'\"") - os.system("bash -c \"mysql -u root --password=$(cat /etc/yunohost/mysql) 2>/dev/null <<< 'DROP USER break_yo_system@localhost'\"") - os.system("bash -c \"mysql -u root --password=$(cat /etc/yunohost/mysql) 2>/dev/null <<< 'DROP DATABASE break_yo_system__2'\"") - os.system("bash -c \"mysql -u root --password=$(cat /etc/yunohost/mysql) 2>/dev/null <<< 'DROP USER break_yo_system__2@localhost'\"") - os.system("bash -c \"mysql -u root --password=$(cat /etc/yunohost/mysql) 2>/dev/null <<< 'DROP DATABASE break_yo_system__3'\"") - os.system("bash -c \"mysql -u root --password=$(cat /etc/yunohost/mysql) 2>/dev/null <<< 'DROP USER break_yo_system__3@localhost'\"") - - @pytest.fixture(autouse=True) def check_LDAP_db_integrity_call(): check_LDAP_db_integrity() From 6ed3ba97ce8732f76c55d2583da21791e2574f72 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 24 Mar 2020 20:22:28 +0100 Subject: [PATCH 0674/3170] Add permission to stuff to be indexed by slapd to avoid it flooding complains in syslog --- data/templates/slapd/slapd.conf | 1 + 1 file changed, 1 insertion(+) diff --git a/data/templates/slapd/slapd.conf b/data/templates/slapd/slapd.conf index 76f249060..0750b43aa 100644 --- a/data/templates/slapd/slapd.conf +++ b/data/templates/slapd/slapd.conf @@ -75,6 +75,7 @@ index cn,mail eq index gidNumber,uidNumber eq index member,memberUid,uniqueMember eq index virtualdomain eq +index permission eq # Save the time that the entry gets modified, for database #1 lastmod on From d9c09f4649a23961b06e93451fe75058a3929bf8 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 24 Mar 2020 20:22:28 +0100 Subject: [PATCH 0675/3170] Add permission to stuff to be indexed by slapd to avoid it flooding complains in syslog --- data/templates/slapd/slapd.conf | 1 + 1 file changed, 1 insertion(+) diff --git a/data/templates/slapd/slapd.conf b/data/templates/slapd/slapd.conf index 76f249060..0750b43aa 100644 --- a/data/templates/slapd/slapd.conf +++ b/data/templates/slapd/slapd.conf @@ -75,6 +75,7 @@ index cn,mail eq index gidNumber,uidNumber eq index member,memberUid,uniqueMember eq index virtualdomain eq +index permission eq # Save the time that the entry gets modified, for database #1 lastmod on From aaa9805ff26ccdd5cabc93769da83b5087582fd8 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 25 Mar 2020 04:01:33 +0100 Subject: [PATCH 0676/3170] Add detection for virt + arch + board model in basesystem diagnosis --- data/hooks/diagnosis/00-basesystem.py | 18 ++++++++++++++++-- locales/en.json | 4 +++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/data/hooks/diagnosis/00-basesystem.py b/data/hooks/diagnosis/00-basesystem.py index 4add48fb2..bf7a27047 100644 --- a/data/hooks/diagnosis/00-basesystem.py +++ b/data/hooks/diagnosis/00-basesystem.py @@ -2,6 +2,7 @@ import os +from moulinette.utils.process import check_output from moulinette.utils.filesystem import read_file from yunohost.diagnosis import Diagnoser from yunohost.utils.packages import ynh_packages_version @@ -15,14 +16,27 @@ class BaseSystemDiagnoser(Diagnoser): def run(self): + # Detect virt technology (if not bare metal) and arch + # Also possibly the board name + virt = check_output("systemd-detect-virt").strip() or "bare-metal" + arch = check_output("dpkg --print-architecture").strip() + hardware = dict(meta={"test": "hardware"}, + status="INFO", + data={"virt": virt, "arch": arch}, + summary=("diagnosis_basesystem_hardware", {"virt": virt, "arch": arch})) + if os.path.exists("/proc/device-tree/model"): + model = read_file('/proc/device-tree/model').strip() + hardware["data"]["board"] = model + hardware["details"] = [("diagnosis_basesystem_hardware_board", (model,))] + + yield hardware + # Kernel version kernel_version = read_file('/proc/sys/kernel/osrelease').strip() yield dict(meta={"test": "kernel"}, status="INFO", summary=("diagnosis_basesystem_kernel", {"kernel_version": kernel_version})) - # FIXME / TODO : add virt/vm technology using systemd-detect-virt and/or machine arch - # Debian release debian_version = read_file("/etc/debian_version").strip() yield dict(meta={"test": "host"}, diff --git a/locales/en.json b/locales/en.json index d70f964d2..82be4003a 100644 --- a/locales/en.json +++ b/locales/en.json @@ -142,7 +142,9 @@ "confirm_app_install_danger": "DANGER! This app is known to be still experimental (if not explicitly not working)! You should probably NOT install it unless you know what you are doing. NO SUPPORT will be provided if this app doesn't work or breaks your system… If you are willing to take that risk anyway, type '{answers:s}'", "confirm_app_install_thirdparty": "DANGER! This app is not part of Yunohost's app catalog. Installing third-party apps may compromise the integrity and security of your system. You should probably NOT install it unless you know what you are doing. NO SUPPORT will be provided if this app doesn't work or breaks your system… If you are willing to take that risk anyway, type '{answers:s}'", "custom_app_url_required": "You must provide a URL to upgrade your custom app {app:s}", - "diagnosis_basesystem_host": "Server is running Debian {debian_version}.", + "diagnosis_basesystem_hardware": "Server hardware architecture is {virt} {arch}", + "diagnosis_basesystem_hardware_board": "Server board model is {model}", + "diagnosis_basesystem_host": "Server is running Debian {debian_version}", "diagnosis_basesystem_kernel": "Server is running Linux kernel {kernel_version}", "diagnosis_basesystem_ynh_single_version": "{0} version: {1} ({2})", "diagnosis_basesystem_ynh_main_version": "Server is running YunoHost {main_version} ({repo})", From 9e8bc8f7fb8ce269e3cf711d7ad87b45b67fa365 Mon Sep 17 00:00:00 2001 From: xaloc33 Date: Sun, 22 Mar 2020 17:01:08 +0000 Subject: [PATCH 0677/3170] Translated using Weblate (Catalan) Currently translated at 100.0% (611 of 611 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ca/ --- locales/ca.json | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/locales/ca.json b/locales/ca.json index c09b31e01..b5a74af45 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -660,7 +660,7 @@ "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 via /etc/resolv.dnsmaq.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 de tipus {0}, nom {1} i valor {2}. Hi ha més informació a https://yunohost.org/dns_config.", @@ -692,8 +692,8 @@ "diagnosis_ports_could_not_diagnose": "No s'ha pogut diagnosticar si els ports són accessibles des de l'exterior. Error: {error}", "diagnosis_ports_unreachable": "El port {port} no és accessible des de l'exterior.", "diagnosis_ports_ok": "El port {port} és accessible des de l'exterior.", - "diagnosis_http_ok": "El domini {domain} és accessible des de l'exterior.", - "diagnosis_http_unreachable": "El domini {domain} no és accessible a través de HTTP des de l'exterior.", + "diagnosis_http_ok": "El domini {domain} és accessible per mitjà de HTTP des de fora de la xarxa local.", + "diagnosis_http_unreachable": "Sembla que el domini {domain} no és accessible a través de HTTP des de fora de la xarxa local.", "diagnosis_unknown_categories": "Les següents categories són desconegudes: {categories}", "apps_catalog_init_success": "S'ha iniciat el sistema de catàleg d'aplicacions!", "apps_catalog_updating": "S'està actualitzant el catàleg d'aplicacions…", @@ -707,7 +707,7 @@ "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 {0}» 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": "No s'ha pogut connectar al servidor com esperat, ha retornat un codi d'estat erroni. 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_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_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_connection_error": "Error de connexió: no s'ha pogut connectar amb el domini demanat, segurament és inaccessible.", @@ -716,12 +716,13 @@ "migration_description_0014_remove_app_status_json": "Eliminar els fitxers d'aplicació status.json heretats", "diagnosis_services_running": "El servei {service} s'està executant!", "diagnosis_services_conf_broken": "La configuració pel servei {service} està trencada!", - "diagnosis_ports_needed_by": "És necessari exposar aquest port pel servei {0}", + "diagnosis_ports_needed_by": "És necessari exposar aquest port per a les funcions {1} (servei {0})", "permission_all_users_implicitly_added": "El permís també s'ha donat implícitament a «all_users» ja que és necessari per atorgar-lo al grup «visitors»", "permission_cannot_remove_all_users_while_visitors_allowed": "No podeu retirar el permís a «all_users» mentre encara el tingui el grup «visitors»", "global_settings_setting_pop3_enabled": "Activa el protocol POP3 per al servidor de correu", "log_app_action_run": "Executa l'acció de l'aplicació «{}»", "log_app_config_show_panel": "Mostra el taulell de configuració de l'aplicació «{}»", "log_app_config_apply": "Afegeix la configuració a l'aplicació «{}»", - "diagnosis_never_ran_yet": "Sembla que el servidor s'ha configurat recentment i encara no hi cap informe de diagnòstic per mostrar. S'ha d'executar un diagnòstic complet primer, ja sigui des de la pàgina web d'administració o utilitzant la comanda «yunohost diagnosis run» al terminal." + "diagnosis_never_ran_yet": "Sembla que el servidor s'ha configurat recentment i encara no hi cap informe de diagnòstic per mostrar. S'ha d'executar un diagnòstic complet primer, ja sigui des de la pàgina web d'administració o utilitzant la comanda «yunohost diagnosis run» al terminal.", + "diagnosis_description_web": "Web" } From 1f09abfa5108f30f17bbb30341495f6fb197c6ae Mon Sep 17 00:00:00 2001 From: pitchum Date: Wed, 25 Mar 2020 11:51:57 +0100 Subject: [PATCH 0678/3170] Rationalize some nginx config into security.conf.inc. --- data/templates/nginx/security.conf.inc | 33 ++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 data/templates/nginx/security.conf.inc diff --git a/data/templates/nginx/security.conf.inc b/data/templates/nginx/security.conf.inc new file mode 100644 index 000000000..272a29e26 --- /dev/null +++ b/data/templates/nginx/security.conf.inc @@ -0,0 +1,33 @@ +{% if compatibility == "modern" %} +# Ciphers with modern compatibility +# https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=nginx-1.6.2&openssl=1.0.1t&hsts=yes&profile=modern +# The following configuration use modern ciphers, but remove compatibility with some old clients (android < 5.0, Internet Explorer < 10, ...) +ssl_protocols TLSv1.2; +ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256'; +ssl_prefer_server_ciphers on; +{% else %} +# As suggested by Mozilla : https://wiki.mozilla.org/Security/Server_Side_TLS and https://en.wikipedia.org/wiki/Curve25519 +ssl_ecdh_curve secp521r1:secp384r1:prime256v1; +ssl_prefer_server_ciphers on; + +# Ciphers with intermediate compatibility +# https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=nginx-1.6.2&openssl=1.0.1t&hsts=yes&profile=intermediate +ssl_protocols TLSv1 TLSv1.1 TLSv1.2; +ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS'; + +# Uncomment the following directive after DH generation +# > openssl dhparam -out /etc/ssl/private/dh2048.pem -outform PEM -2 2048 +#ssl_dhparam /etc/ssl/private/dh2048.pem; +{% endif %} + +more_set_headers "Content-Security-Policy : upgrade-insecure-requests"; +more_set_headers "Content-Security-Policy-Report-Only : default-src https: data: 'unsafe-inline' 'unsafe-eval'"; +more_set_headers "X-Content-Type-Options : nosniff"; +more_set_headers "X-XSS-Protection : 1; mode=block"; +more_set_headers "X-Download-Options : noopen"; +more_set_headers "X-Permitted-Cross-Domain-Policies : none"; +more_set_headers "X-Frame-Options : SAMEORIGIN"; + +# Disable gzip to protect against BREACH +# Read https://trac.nginx.org/nginx/ticket/1720 (text/html cannot be disabled!) +gzip off; From ada95f8fca57db7cab97b14c4ce03b799632a87a Mon Sep 17 00:00:00 2001 From: pitchum Date: Wed, 25 Mar 2020 12:09:24 +0100 Subject: [PATCH 0679/3170] http-upload only available on maindomain (for the moment). --- data/hooks/conf_regen/12-metronome | 10 ++++++---- src/yunohost/certificate.py | 18 ++++++++++-------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/data/hooks/conf_regen/12-metronome b/data/hooks/conf_regen/12-metronome index 0cfb42fd4..5c9c67f11 100755 --- a/data/hooks/conf_regen/12-metronome +++ b/data/hooks/conf_regen/12-metronome @@ -42,16 +42,18 @@ do_post_regen() { regen_conf_files=$1 # retrieve variables + main_domain=$(cat /etc/yunohost/current_host) domain_list=$(yunohost domain list --output-as plain --quiet) # create metronome directories for domains for domain in $domain_list; do mkdir -p "/var/lib/metronome/${domain//./%2e}/pep" - # http_upload directory must be writable by metronome and readable by nginx - mkdir -p "/var/xmpp-upload/${domain}/upload" - chmod g+s "/var/xmpp-upload/${domain}/upload" - chown -R metronome:www-data "/var/xmpp-upload/${domain}" done + # http_upload directory must be writable by metronome and readable by nginx + mkdir -p "/var/xmpp-upload/${main_domain}/upload" + chmod g+s "/var/xmpp-upload/${main_domain}/upload" + chown -R metronome:www-data "/var/xmpp-upload/${main_domain}" + # fix some permissions chown -R metronome: /var/lib/metronome/ diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index e49db9733..31a4c1200 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -43,6 +43,7 @@ from yunohost.utils.network import get_public_ip from moulinette import m18n from yunohost.app import app_ssowatconf +from yunohost.domain import _get_maindomain from yunohost.service import _run_service_command from yunohost.regenconf import regen_conf from yunohost.log import OperationLogger @@ -639,14 +640,15 @@ def _prepare_certificate_signing_request(domain, key_file, output_folder): # Set the domain csr.get_subject().CN = domain - # Include xmpp-upload subdomain in subject alternate names - subdomain="xmpp-upload." + domain - try: - _check_domain_is_ready_for_ACME(subdomain) - logger.info("Subdmain {} is ready for ACME and will be included in the certificate.".format(subdomain)) - csr.add_extensions([crypto.X509Extension("subjectAltName", False, "DNS:" + subdomain)]) - except YunohostError: - logger.warning(m18n.n('certmanager_warning_subdomain_dns_record', subdomain=subdomain, domain=domain)) + if domain == _get_maindomain(): + # Include xmpp-upload subdomain in subject alternate names + subdomain="xmpp-upload." + domain + try: + _check_domain_is_ready_for_ACME(subdomain) + logger.info("Subdmain {} is ready for ACME and will be included in the certificate.".format(subdomain)) + csr.add_extensions([crypto.X509Extension("subjectAltName", False, "DNS:" + subdomain)]) + except YunohostError: + logger.warning(m18n.n('certmanager_warning_subdomain_dns_record', subdomain=subdomain, domain=domain)) # Set the key with open(key_file, 'rt') as f: From 5e6e53142bb621e0552136dfa16a8c4bf6a1c562 Mon Sep 17 00:00:00 2001 From: pitchum Date: Wed, 25 Mar 2020 12:09:53 +0100 Subject: [PATCH 0680/3170] Improve nginx config for xmpp-upload subdomain. --- data/templates/nginx/server.tpl.conf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data/templates/nginx/server.tpl.conf b/data/templates/nginx/server.tpl.conf index 0eb64dd8d..6316960c4 100644 --- a/data/templates/nginx/server.tpl.conf +++ b/data/templates/nginx/server.tpl.conf @@ -68,8 +68,9 @@ server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name xmpp-upload.{{ domain }}; + root /dev/null; - location /upload { + location /upload/ { alias /var/xmpp-upload/{{ domain }}/upload; # Pass all requests to metronome, except for GET and HEAD requests. limit_except GET HEAD { From ceaacfbd975a3fab117e8940c95d3e86be4a186e Mon Sep 17 00:00:00 2001 From: pitchum Date: Wed, 25 Mar 2020 12:20:23 +0100 Subject: [PATCH 0681/3170] Simplified check for subdomain inclusion in certificate. --- 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 31a4c1200..e4d4874e3 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -643,11 +643,10 @@ def _prepare_certificate_signing_request(domain, key_file, output_folder): if domain == _get_maindomain(): # Include xmpp-upload subdomain in subject alternate names subdomain="xmpp-upload." + domain - try: - _check_domain_is_ready_for_ACME(subdomain) + if _dns_ip_match_public_ip(get_public_ip(), subdomain): logger.info("Subdmain {} is ready for ACME and will be included in the certificate.".format(subdomain)) 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 094cb15b0aaafa8b96d7c3e86f9490352e0d8a1a Mon Sep 17 00:00:00 2001 From: pitchum Date: Wed, 25 Mar 2020 19:53:36 +0100 Subject: [PATCH 0682/3170] Workaround some python loading issue. --- src/yunohost/certificate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index e4d4874e3..5ff88ca4e 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -43,7 +43,6 @@ from yunohost.utils.network import get_public_ip from moulinette import m18n from yunohost.app import app_ssowatconf -from yunohost.domain import _get_maindomain from yunohost.service import _run_service_command from yunohost.regenconf import regen_conf from yunohost.log import OperationLogger @@ -640,6 +639,7 @@ def _prepare_certificate_signing_request(domain, key_file, output_folder): # Set the domain csr.get_subject().CN = domain + from yunohost.domain import _get_maindomain if domain == _get_maindomain(): # Include xmpp-upload subdomain in subject alternate names subdomain="xmpp-upload." + domain From d85bd1f25ab1a17ddb72b73a010d481f31e05d44 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 25 Mar 2020 20:15:34 +0100 Subject: [PATCH 0683/3170] Forbid users from trying to add a domain starting by xmpp-upload. --- locales/en.json | 1 + src/yunohost/domain.py | 3 +++ 2 files changed, 4 insertions(+) diff --git a/locales/en.json b/locales/en.json index d2117b7d0..8fe555744 100644 --- a/locales/en.json +++ b/locales/en.json @@ -223,6 +223,7 @@ "diagnosis_unknown_categories": "The following categories are unknown: {categories}", "diagnosis_never_ran_yet": "It looks like this server was setup recently and there's no diagnosis report to show yet. You should start by running a full diagnosis, either from the webadmin or using 'yunohost diagnosis run' from the command line.", "domain_cannot_remove_main": "You cannot remove '{domain:s}' since it's the main domain, you first need to set another domain as the main domain using 'yunohost domain main-domain -n '; here is the list of candidate domains: {other_domains:s}", + "domain_cannot_add_xmpp_upload": "You cannot add domains starting with 'xmpp-upload.'. This kind of name is reserved for the XMPP upload feature integrated in YunoHost.", "domain_cannot_remove_main_add_new_one": "You cannot remove '{domain:s}' since it's the main domain and your only domain, you need to first add another domain using 'yunohost domain add ', then set is as the main domain using 'yunohost domain main-domain -n ' and then you can remove the domain '{domain:s}' using 'yunohost domain remove {domain:s}'.'", "domain_cert_gen_failed": "Could not generate certificate", "domain_created": "Domain created", diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 5037e9334..eb84f27d0 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -79,6 +79,9 @@ def domain_add(operation_logger, domain, dyndns=False): from yunohost.app import app_ssowatconf from yunohost.utils.ldap import _get_ldap_interface + if domain.startswith("xmpp-upload."): + raise YunohostError("domain_cannot_add_xmpp_upload") + ldap = _get_ldap_interface() try: From e59a38a88a5d117a443be97ceebcf1d6b8315ef1 Mon Sep 17 00:00:00 2001 From: pitchum Date: Wed, 25 Mar 2020 20:31:08 +0100 Subject: [PATCH 0684/3170] Remove useless debug message. --- src/yunohost/certificate.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 5ff88ca4e..6e9c97bca 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -644,7 +644,6 @@ def _prepare_certificate_signing_request(domain, key_file, output_folder): # Include xmpp-upload subdomain in subject alternate names subdomain="xmpp-upload." + domain if _dns_ip_match_public_ip(get_public_ip(), subdomain): - logger.info("Subdmain {} is ready for ACME and will be included in the certificate.".format(subdomain)) csr.add_extensions([crypto.X509Extension("subjectAltName", False, "DNS:" + subdomain)]) else: logger.warning(m18n.n('certmanager_warning_subdomain_dns_record', subdomain=subdomain, domain=domain)) From 526a3a25c93388bf93061f6f9270ec5d3ace09fa Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 26 Mar 2020 19:38:34 +0100 Subject: [PATCH 0685/3170] Remote diagnoser also returns code 400 or 418 when port / http ain't correctly exposed --- data/hooks/diagnosis/14-ports.py | 2 +- data/hooks/diagnosis/21-web.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/hooks/diagnosis/14-ports.py b/data/hooks/diagnosis/14-ports.py index a0c0fa03f..7730ddb57 100644 --- a/data/hooks/diagnosis/14-ports.py +++ b/data/hooks/diagnosis/14-ports.py @@ -28,7 +28,7 @@ class PortsDiagnoser(Diagnoser): try: r = requests.post('https://diagnosis.yunohost.org/check-ports', json={'ports': ports.keys()}, timeout=30) - if r.status_code != 200: + if r.status_code not in [200, 400, 418]: raise Exception("Bad response from the server https://diagnosis.yunohost.org/check-ports : %s - %s" % (str(r.status_code), r.content)) r = r.json() if "status" not in r.keys(): diff --git a/data/hooks/diagnosis/21-web.py b/data/hooks/diagnosis/21-web.py index 1f6547c8c..2a3afba88 100644 --- a/data/hooks/diagnosis/21-web.py +++ b/data/hooks/diagnosis/21-web.py @@ -29,7 +29,7 @@ class WebDiagnoser(Diagnoser): try: r = requests.post('https://diagnosis.yunohost.org/check-http', json={'domain': domain, "nonce": nonce}, timeout=30) - if r.status_code != 200: + if r.status_code not in [200, 400, 418]: raise Exception("Bad response from the server https://diagnosis.yunohost.org/check-http : %s - %s" % (str(r.status_code), r.content)) r = r.json() if "status" not in r.keys(): From 1f46824f1e00b68303f804cec8b02117bdfa1477 Mon Sep 17 00:00:00 2001 From: kay0u Date: Thu, 26 Mar 2020 21:58:03 +0000 Subject: [PATCH 0686/3170] Update changelog for 3.7.0.7 release --- debian/changelog | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/debian/changelog b/debian/changelog index 8f0205f74..b5d43b3fb 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,12 @@ +yunohost (3.7.0.7) stable; urgency=low + + - [fix] Allow public apps with no sso tile (#894) + - [fix] Slapd now index permission to avoid log error + + Thanks to all contributors <3 ! (Aleks, Kay0u) + + -- Kay0u Thu, 26 Mar 2020 21:53:22 +0000 + yunohost (3.7.0.6) testing; urgency=low - [fix] Make sure the group permission update contains unique elements From 0ea6537d78fd2f29ae802ec7ff27e924084e608d Mon Sep 17 00:00:00 2001 From: Kay0u Date: Fri, 27 Mar 2020 01:32:05 +0100 Subject: [PATCH 0687/3170] Quick fix for app_setting delete --- src/yunohost/app.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 65782f56a..3feca796e 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1407,8 +1407,9 @@ def app_setting(app, key, value=None, delete=False): logger.debug("cannot get app setting '%s' for '%s' (%s)", key, app, e) return None - if delete and key in app_settings: - del app_settings[key] + if delete: + if key in app_settings: + del app_settings[key] else: # FIXME: Allow multiple values for some keys? if key in ['redirected_urls', 'redirected_regex']: From 455aa1aacce57cea4704e2ca52e3ed25c1de7dde Mon Sep 17 00:00:00 2001 From: root Date: Fri, 27 Mar 2020 00:38:38 +0000 Subject: [PATCH 0688/3170] Update changelog for 3.7.0.8 release --- debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian/changelog b/debian/changelog index b5d43b3fb..034f3b51c 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +yunohost (3.7.0.8) stable; urgency=low + + - [fix] App_setting delete add if the key doesn't exist + + -- Kay0u Fri, 27 Mar 2020 00:36:46 +0000 + yunohost (3.7.0.7) stable; urgency=low - [fix] Allow public apps with no sso tile (#894) From 908227abd5b7256578f6eb1d1661fcf8a19e5bfc Mon Sep 17 00:00:00 2001 From: xaloc33 Date: Wed, 25 Mar 2020 14:42:25 +0000 Subject: [PATCH 0689/3170] Translated using Weblate (Catalan) Currently translated at 100.0% (613 of 613 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ca/ --- locales/ca.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/locales/ca.json b/locales/ca.json index b5a74af45..471112631 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -636,7 +636,7 @@ "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. Error: {error}", "domain_cannot_remove_main_add_new_one": "No es pot eliminar «{domain:s}» ja que és el domini principal i únic domini, primer s'ha d'afegir un altre domini utilitzant «yunohost domain add », i després fer-lo el domini principal amb «yunohost domain main-domain -n » i després es pot eliminar el domini «{domain:s}» utilitzant «yunohost domain remove {domain:s}».", - "diagnosis_basesystem_host": "El servidor funciona amb Debian {debian_version}.", + "diagnosis_basesystem_host": "El servidor funciona amb Debian {debian_version}", "diagnosis_basesystem_kernel": "El servidor funciona amb el nucli de Linux {kernel_version}", "diagnosis_basesystem_ynh_single_version": "{0} versió: {1}({2})", "diagnosis_basesystem_ynh_inconsistent_versions": "Esteu utilitzant versions inconsistents dels paquets de YunoHost… probablement a causa d'una actualització fallida o parcial.", @@ -724,5 +724,7 @@ "log_app_config_show_panel": "Mostra el taulell de configuració de l'aplicació «{}»", "log_app_config_apply": "Afegeix la configuració a l'aplicació «{}»", "diagnosis_never_ran_yet": "Sembla que el servidor s'ha configurat recentment i encara no hi cap informe de diagnòstic per mostrar. S'ha d'executar un diagnòstic complet primer, ja sigui des de la pàgina web d'administració o utilitzant la comanda «yunohost diagnosis run» al terminal.", - "diagnosis_description_web": "Web" + "diagnosis_description_web": "Web", + "diagnosis_basesystem_hardware_board": "El model de la targeta del servidor és {model}", + "diagnosis_basesystem_hardware": "L'arquitectura del maquinari del servidor és {virt} {arch}" } From ee0dfe3ddb7ae8c642f5817bb5cc9508a8fc88fb Mon Sep 17 00:00:00 2001 From: Aeris One Date: Thu, 26 Mar 2020 17:29:24 +0000 Subject: [PATCH 0690/3170] Translated using Weblate (French) Currently translated at 100.0% (613 of 613 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 50dc2e983..441fd6fd3 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -182,7 +182,7 @@ "restore_running_hooks": "Exécution des scripts de restauration …", "service_add_configuration": "Ajout du fichier de configuration {file:s}", "service_add_failed": "Impossible d’ajouter le service '{service:s}'", - "service_added": "Le service '{service:s}' ajouté", + "service_added": "Le service '{service:s}' a été ajouté", "service_already_started": "Le service '{service:s}' est déjà en cours d'exécution", "service_already_stopped": "Le service '{service:s}' est déjà arrêté", "service_cmd_exec_failed": "Impossible d’exécuter la commande '{command:s}'", @@ -273,7 +273,7 @@ "domain_cannot_remove_main": "Vous ne pouvez pas supprimer '{domain: s}' car il s'agit du domaine principal. Vous devez d'abord définir un autre domaine comme domaine principal à l'aide de 'yunohost domain main-domain -n ', voici la liste des domaines candidats. : {other_domains: s}", "certmanager_self_ca_conf_file_not_found": "Le fichier de configuration pour l’autorité du certificat auto-signé est introuvable (fichier : {file:s})", "certmanager_unable_to_parse_self_CA_name": "Impossible d’analyser le nom de l’autorité du certificat auto-signé (fichier : {file:s})", - "mailbox_used_space_dovecot_down": "Le service de courriel Dovecot doit être démarré, si vous souhaitez voir l’espace disque occupé par la messagerie", + "mailbox_used_space_dovecot_down": "Le service de courriel Dovecot doit être démarré si vous souhaitez voir l’espace disque occupé par la messagerie", "domains_available": "Domaines disponibles :", "backup_archive_broken_link": "Impossible d’accéder à l’archive de sauvegarde (lien invalide vers {path:s})", "certmanager_acme_not_configured_for_domain": "Le certificat du domaine {domain:s} ne semble pas être correctement installé. Veuillez d'abord exécuter cert-install.", @@ -394,7 +394,7 @@ "migration_0003_system_not_fully_up_to_date": "Votre système n’est pas complètement à jour. Veuillez mener une mise à jour classique avant de lancer à migration à Stretch.", "migration_0003_still_on_jessie_after_main_upgrade": "Quelque chose s’est mal passé pendant la mise à niveau principale : le système est toujours sur Debian Jessie !? Pour investiguer sur le problème, veuillez regarder les journaux {log}:s …", "migration_0003_general_warning": "Veuillez noter que cette migration est une opération délicate. Si l’équipe YunoHost a fait de son mieux pour la relire et la tester, la migration pourrait tout de même casser des parties de votre système ou de vos applications.\n\nEn conséquence, nous vous recommandons :\n - de lancer une sauvegarde de vos données ou applications critiques. Plus d’informations sur https://yunohost.org/backup ;\n - d’être patient après avoir lancé la migration : selon votre connexion internet et matériel, cela pourrait prendre jusqu’à quelques heures pour que tout soit à niveau.\n\nEn outre, le port SMTP utilisé par les clients de messagerie externes comme (Thunderbird ou K9-Mail) a été changé de 465 (SSL/TLS) à 587 (STARTTLS). L’ancien port 465 sera automatiquement fermé et le nouveau port 587 sera ouvert dans le pare-feu. Vous et vos utilisateurs *devront* adapter la configuration de vos clients de messagerie en conséquence.", - "migration_0003_problematic_apps_warning": "Veuillez noter que les applications installées potentiellement problématiques suivantes ont été détectées. Il semble que celles-ci n'ont pas été installées à partir d'un catalogue d'applications, ou ne sont pas marquées comme \"working \". Par conséquent, il ne peut pas être garanti qu'ils fonctionneront toujours après la mise à niveau: {problematic_apps}", + "migration_0003_problematic_apps_warning": "Veuillez noter que les applications installées potentiellement problématiques suivantes ont été détectées. Il semble que celles-ci n'ont pas été installées à partir d'un catalogue d'applications, ou ne sont pas marquées comme \"fonctionnelle\". Par conséquent, il ne peut pas être garanti qu'ils fonctionneront toujours après la mise à niveau: {problematic_apps}", "migration_0003_modified_files": "Veuillez noter que les fichiers suivants ont été détectés comme modifiés manuellement et pourraient être écrasés à la fin de la mise à niveau : {manually_modified_files}", "migrations_list_conflict_pending_done": "Vous ne pouvez pas utiliser --previous et --done simultanément.", "migrations_to_be_ran_manually": "La migration {id} doit être lancée manuellement. Veuillez aller dans Outils > Migrations dans l’interface admin, ou lancer `yunohost tools migrations migrate`.", @@ -672,13 +672,13 @@ "diagnosis_found_errors_and_warnings": "Trouvé {errors} problème(s) significatif(s) (et {warnings} (avertissement(s)) en relation avec {category} !", "diagnosis_ip_not_connected_at_all": "Le serveur ne semble pas du tout connecté à Internet !?", "diagnosis_ip_weird_resolvconf": "La résolution DNS semble fonctionner, mais soyez prudent en utilisant un fichier /etc/resolv.conf personnalisé.", - "diagnosis_ip_weird_resolvconf_details": "Au lieu de cela, ce fichier devrait être un lien symbolique vers /etc/resolvconf/run/resolv.conf lui-même pointant vers 127.0.0.1 (dnsmasq). Les résolveurs réels doivent être configurés via /etc/resolv.dnsmasq.conf.", + "diagnosis_ip_weird_resolvconf_details": "Au lieu de cela, ce fichier devrait être un lien symbolique vers /etc/resolvconf/run/resolv.conf lui-même pointant vers 127.0.0.1 (dnsmasq). Les résolveurs réels doivent être configurés dans /etc/resolv.dnsmasq.conf.", "diagnosis_dns_missing_record": "Selon la configuration DNS recommandée, vous devez ajouter un enregistrement DNS de type {0}, nom {1} et valeur {2}. Vous pouvez consulter https://yunohost.org/dns_config pour plus d'informations.", "diagnosis_diskusage_ok": "Le stockage {mountpoint} (sur le périphérique {device}) a encore {free_abs_GB} Go ({free_percent}%) d'espace libre !", "diagnosis_ram_ok": "Le système dispose encore de {available_abs_MB} MB ({available_percent}%) de RAM sur {total_abs_MB} MB.", "diagnosis_regenconf_allgood": "Tous les fichiers de configuration sont conformes à la configuration recommandée !", "diagnosis_security_vulnerable_to_meltdown": "Vous semblez vulnérable à la vulnérabilité de sécurité critique de Meltdown", - "diagnosis_basesystem_host": "Le serveur utilise Debian {debian_version}.", + "diagnosis_basesystem_host": "Le serveur utilise Debian {debian_version}", "diagnosis_basesystem_kernel": "Le serveur utilise le noyau Linux {kernel_version}", "diagnosis_basesystem_ynh_single_version": "{0} version: {1} ({2})", "diagnosis_basesystem_ynh_main_version": "Le serveur utilise YunoHost {main_version} ({repo})", @@ -738,8 +738,8 @@ "diagnosis_ports_unreachable": "Le port {port} n'est pas accessible de l'extérieur.", "diagnosis_ports_ok": "Le port {port} est accessible de l'extérieur.", "diagnosis_http_could_not_diagnose": "Impossible de diagnostiquer si le domaine est accessible de l'extérieur. Erreur: {error}", - "diagnosis_http_ok": "Le domaine {domain} est accessible de l'extérieur.", - "diagnosis_http_unreachable": "Le domaine {domain} est inaccessible via HTTP de l'extérieur.", + "diagnosis_http_ok": "Le domaine {domain} est accessible au travers de HTTP depuis l'extérieur.", + "diagnosis_http_unreachable": "Le domaine {domain} est inaccessible au travers de HTTP depuis l'extérieur.", "diagnosis_unknown_categories": "Les catégories suivantes sont inconnues: {categories}", "migration_description_0013_futureproof_apps_catalog_system": "Migrer vers le nouveau système de catalogue d'applications à l'épreuve du temps", "app_upgrade_script_failed": "Une erreur s'est produite durant l’exécution du script de mise à niveau de l'application", @@ -760,5 +760,9 @@ "global_settings_setting_pop3_enabled": "Activer le protocole POP3 pour le serveur de messagerie", "log_app_action_run": "Lancer l’action de l’application '{}'", "log_app_config_show_panel": "Montrer le panneau de configuration de l’application '{}'", - "log_app_config_apply": "Appliquer la configuration à l’application '{}'" + "log_app_config_apply": "Appliquer la configuration à l’application '{}'", + "diagnosis_never_ran_yet": "Il apparaît que le serveur a été installé récemment et qu'il n'y a pas encore eu de diagnostic. Vous devriez en lancer un depuis le webmin ou en utilisant 'yunohost diagnosis run' depuis la ligne de commande.", + "diagnosis_description_web": "Web", + "diagnosis_basesystem_hardware_board": "Le modèle de carte du serveur est {model}", + "diagnosis_basesystem_hardware": "L'architecture du serveur est {virt} {arch}" } From 8d40c736ddc0ed14c0ce22083f56d3993800ea6d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 27 Mar 2020 19:21:58 +0100 Subject: [PATCH 0691/3170] Try to improve / fix weird wording --- locales/en.json | 2 +- locales/fr.json | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/locales/en.json b/locales/en.json index faeb0a85b..4582b71b9 100644 --- a/locales/en.json +++ b/locales/en.json @@ -388,7 +388,7 @@ "migration_description_0008_ssh_conf_managed_by_yunohost_step2": "Let the SSH configuration be managed by YunoHost (step 2, manual)", "migration_description_0009_decouple_regenconf_from_services": "Decouple the regen-conf mechanism from services", "migration_description_0010_migrate_to_apps_json": "Remove deprecated apps catalogs and use the new unified 'apps.json' list instead (outdated, replaced by migration 13)", - "migration_description_0011_setup_group_permission": "Set up user group and set up permission for apps and services", + "migration_description_0011_setup_group_permission": "Set up user groups and permissions for apps and services", "migration_description_0012_postgresql_password_to_md5_authentication": "Force PostgreSQL authentication to use MD5 for local connections", "migration_description_0013_futureproof_apps_catalog_system": "Migrate to the new future-proof apps catalog system", "migration_description_0014_remove_app_status_json": "Remove legacy status.json app files", diff --git a/locales/fr.json b/locales/fr.json index 441fd6fd3..c2cb7a18c 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -592,7 +592,7 @@ "app_action_broke_system": "Cette action semble avoir cassé des services importants : {services}", "apps_already_up_to_date": "Toutes les applications sont déjà à jour", "app_upgrade_stopped": "La mise à niveau de toutes les applications s'est arrêtée pour éviter tout dommage, car une application n'a pas pu être mise à niveau.", - "migration_0011_create_group": "Créer un groupe pour chaque utilisateur…", + "migration_0011_create_group": "Création d'un groupe pour chaque utilisateur…", "migration_0011_done": "Migration terminée. Vous êtes maintenant en mesure de gérer des groupes d'utilisateurs.", "migrations_must_provide_explicit_targets": "Vous devez fournir des cibles explicites lorsque vous utilisez '--skip' ou '--force-rerun'", "migrations_no_such_migration": "Il n'y a pas de migration appelée '{id}'", @@ -602,7 +602,7 @@ "migrations_not_pending_cant_skip": "Ces migrations ne sont pas en attente et ne peuvent donc pas être ignorées: {ids}", "migration_0011_can_not_backup_before_migration": "La sauvegarde du système n'a pas pu être terminée avant l'échec de la migration. Erreur: {erreur:s}", "migration_0011_migrate_permission": "Migration des autorisations des paramètres des applications vers LDAP…", - "migration_0011_migration_failed_trying_to_rollback": "Impossible de migrer… en essayant de restaurer le système.", + "migration_0011_migration_failed_trying_to_rollback": "La migration a échouée… Tentative de restauration du système.", "migration_0011_rollback_success": "Système restauré.", "migration_0011_update_LDAP_database": "Mise à jour de la base de données LDAP…", "system_groupname_exists": "Le nom de groupe existe déjà dans le groupe du systèmes", @@ -633,7 +633,7 @@ "permission_deleted": "Permission '{permission:s}' supprimée", "permission_deletion_failed": "Impossible de supprimer la permission '{permission}': {error}", "remove_user_of_group_not_allowed": "Vous n'êtes pas autorisé à supprimer l'utilisateur '{utilisateur: s}' dans le groupe '{groupe: s}'", - "migration_description_0011_setup_group_permission": "Configurer le groupe d'utilisateurs et configurer les autorisations pour les applications et les services", + "migration_description_0011_setup_group_permission": "Initialiser les groupes d'utilisateurs et autorisations pour les applications et les services", "migration_0011_LDAP_config_dirty": "Il semble que vous ayez personnalisé votre configuration LDAP. Pour cette migration, la configuration LDAP doit être mise à jour.\nVous devez enregistrer votre configuration actuelle, réintialiser la configuration d'origine en exécutant 'yunohost tools regen-conf -f', puis réessayer la migration", "migration_0011_LDAP_update_failed": "Impossible de mettre à jour LDAP. Erreur: {error: s}", "group_already_exist": "Le groupe {group} existe déjà", From 8041daf334e1b6053b63c41681522d01c94325b1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 27 Mar 2020 19:28:01 +0100 Subject: [PATCH 0692/3170] Require moulinette and ssowat to be at least 3.7 to avoid funky situations where regen-conf fails because moulinette ain't upgraded yet --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index a6d64e0b7..42bafc16c 100644 --- a/debian/control +++ b/debian/control @@ -11,7 +11,7 @@ Package: yunohost Essential: yes Architecture: all Depends: ${python:Depends}, ${misc:Depends} - , moulinette (>= 2.7.1), ssowat (>= 2.7.1) + , moulinette (>= 3.7), ssowat (>= 3.7) , python-psutil, python-requests, python-dnspython, python-openssl , python-apt, python-miniupnpc, python-dbus, python-jinja2 , python-toml From 82680839b1de9bd0cf9a7dd6eb97606949c33643 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 27 Mar 2020 19:52:24 +0100 Subject: [PATCH 0693/3170] Automatically remove existing system group if it exists when creating primary groups --- locales/en.json | 1 + src/yunohost/user.py | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 4582b71b9..124bd79d3 100644 --- a/locales/en.json +++ b/locales/en.json @@ -290,6 +290,7 @@ "good_practices_about_user_password": "You are now about to define a new user password. The password should be at least 8 characters long—though it is good practice to use a longer password (i.e. a passphrase) and/or to a variation of characters (uppercase, lowercase, digits and special characters).", "group_already_exist": "Group {group} already exists", "group_already_exist_on_system": "Group {group} already exists in the system groups", + "group_already_exist_on_system_but_removing_it": "Group {group} already exists in the system groups, but YunoHost will remove it…", "group_created": "Group '{group}' created", "group_creation_failed": "Could not create the group '{group}': {error}", "group_cannot_edit_all_users": "The group 'all_users' cannot be edited manually. It is a special group meant to contain all users registered in YunoHost", diff --git a/src/yunohost/user.py b/src/yunohost/user.py index fdcac658d..34b367d7d 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -576,7 +576,11 @@ def user_group_create(operation_logger, groupname, gid=None, primary_group=False # Validate uniqueness of groupname in system group all_existing_groupnames = {x.gr_name for x in grp.getgrall()} if groupname in all_existing_groupnames: - raise YunohostError('group_already_exist_on_system', group=groupname) + if primary_group: + logger.warning('group_already_exist_on_system_but_removing_it', group=groupname) + subprocess.check_call("sed --in-place '/^%s:/d' /etc/group" % groupname, shell=True) + else: + raise YunohostError('group_already_exist_on_system', group=groupname) if not gid: # Get random GID From 653598a723a117b87ec221c22521e3391fb17895 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Fri, 6 Dec 2019 10:05:08 +0000 Subject: [PATCH 0694/3170] Translated using Weblate (French) Currently translated at 100.0% (607 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index a03c4a61f..70a074120 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -673,7 +673,7 @@ "diagnosis_ip_not_connected_at_all": "Le serveur ne semble pas du tout connecté à Internet !?", "diagnosis_ip_weird_resolvconf": "La résolution DNS semble fonctionner, mais soyer prudent en utilisant un fichier /etc/resolv.conf personnalisé.", "diagnosis_ip_weird_resolvconf_details": "Au lieu de cela, ce fichier devrait être un lien symbolique vers /etc/resolvconf/run/resolv.conf lui-même pointant vers 127.0.0.1 (dnsmasq). Les résolveurs réels doivent être configurés via /etc/resolv.dnsmasq.conf.", - "diagnosis_dns_missing_record": "Selon la configuration DNS recommandée, vous devez ajouter un enregistrement DNS de type {0}, nom {1} et valeur {2}", + "diagnosis_dns_missing_record": "Selon la configuration DNS recommandée, vous devez ajouter un enregistrement DNS de type {0}, nom {1} et valeur {2}. Vous pouvez consulter https://yunohost.org/dns_config pour plus d'informations.", "diagnosis_diskusage_ok": "Le stockage {mountpoint} (sur le périphérique {device}) a toujours {espace libre {free_abs_GB} GB ({free_percent}%) !", "diagnosis_ram_ok": "Le système dispose toujours de {available_abs_MB} MB ({available_percent}%) de RAM sur {total_abs_MB} MB.", "diagnosis_regenconf_allgood": "Tous les fichiers de configuration sont conformes à la configuration recommandée !", @@ -699,7 +699,7 @@ "diagnosis_ip_broken_resolvconf": "La résolution du nom de domaine semble cassée sur votre serveur, ce qui semble lié au fait que /etc/resolv.conf ne pointe pas vers 127.0.0.1.", "diagnosis_dns_good_conf": "Bonne configuration DNS pour le domaine {domain} (catégorie {category})", "diagnosis_dns_bad_conf": "Configuration DNS incorrecte/manquante pour le domaine {domain} (catégorie {category})", - "diagnosis_dns_discrepancy": "Selon la configuration DNS recommandée, la valeur de l'enregistrement DNS de type {0} et nom {1} doit être {2} et non {3}.", + "diagnosis_dns_discrepancy": "L'enregistrement DNS de type {0} et nom {1} ne correspond pas à la configuration recommandée. Valeur actuelle: {2}. Valeur exceptée: {3}. Vous pouvez consulter https://yunohost.org/dns_config pour plus d'informations.", "diagnosis_services_bad_status": "Le service {service} est {status} :-(", "diagnosis_services_good_status": "Le service {service} est {status} comme prévu !", "diagnosis_diskusage_verylow": "Le stockage {mountpoint} (sur le périphérique {device}) ne dispose que de {free_abs_GB} Go ({free_percent}%). Vous devriez vraiment envisager de nettoyer un peu d'espace.", @@ -747,8 +747,15 @@ "diagnosis_services_running": "Le service {service} s'exécute correctement !", "diagnosis_services_conf_broken": "La configuration est cassée pour le service {service} !", "diagnosis_ports_needed_by": "Rendre ce port accessible est nécessaire pour le service {0}", - "diagnosis_ports_forwarding_tip": "Pour résoudre ce problème, vous devez très probablement configurer le transfert de port sur votre routeur Internet comme décrit dans https://yunohost.org/port_forwarding", + "diagnosis_ports_forwarding_tip": "Pour résoudre ce problème, vous devez probablement configurer la redirection de port sur votre routeur Internet, comme décrit dans https://yunohost.org/isp_box_config.", "diagnosis_http_connection_error": "Erreur de connexion : impossible de se connecter au domaine demandé, il est probablement injoignable.", "diagnosis_no_cache": "Pas encore de diagnostique de cache pour la catégorie « {category} »", - "diagnosis_http_unknown_error": "Une erreur est survenue en essayant de joindre votre domaine, il est probablement injoignable." + "diagnosis_http_unknown_error": "Une erreur est survenue en essayant de joindre votre domaine, il est probablement injoignable.", + "permission_all_users_implicitly_added": "La permission a également été implicitement accordée à 'all_users' car il est nécessaire pour permettre au groupe spécial 'visiteurs'", + "permission_cannot_remove_all_users_while_visitors_allowed": "Vous ne pouvez pas supprimer cette autorisation pour 'all_users' alors qu'elle est toujours autorisée pour 'visiteurs'", + "yunohost_postinstall_end_tip": "La post-installation terminée! Pour finaliser votre configuration, veuillez considérer:\n - ajout d'un premier utilisateur via la section \"Utilisateurs\" de l'administrateur Web (ou \"utilisateur yunohost créer \" en ligne de commande);\n - diagnostiquez les problèmes en attente de résolution du problème afin que votre serveur fonctionne le mieux possible dans la section \"Diagnostic\" de l'administrateur Web (ou \"Exécution du diagnostic yunohost\" en ligne de commande);\n - lecture des parties \"Finalisation de votre configuration\" et \"Découverte de Yunohost\" dans la documentation de l'administrateur: https://yunohost.org/admindoc.", + "diagnosis_services_bad_status_tip": "Vous pouvez essayer de redémarrer le service. Si cela ne fonctionne pas, consultez les journaux de service à l'aide de 'yunohost service log {0}' ou de la section 'Services' de l'administrateur Web.", + "diagnosis_http_bad_status_code": "N'a pas pu atteindre votre serveur comme prévu, il a renvoyé un code d'état incorrect. Il se peut qu'une autre machine réponde à la place de votre serveur. Vérifiez que vous transférez correctement le port 80, que votre configuration nginx est à jour et qu’un proxy inverse n’interfère pas.", + "diagnosis_http_timeout": "Expiration du délai en essayant de contacter votre serveur de l'extérieur. Il semble être inaccessible. Vérifiez que vous transférez correctement le port 80, que nginx est en cours d’exécution et qu’un pare-feu n’interfère pas.", + "global_settings_setting_pop3_enabled": "Activer le protocole POP3 pour le serveur de messagerie" } From b2991c00ead23ded8bab17992fca9d61605803f1 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Thu, 19 Dec 2019 09:38:30 +0000 Subject: [PATCH 0695/3170] Translated using Weblate (Portuguese) Currently translated at 8.4% (51 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/pt/ --- locales/pt.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/pt.json b/locales/pt.json index b8c9d2eb3..2905238a1 100644 --- a/locales/pt.json +++ b/locales/pt.json @@ -193,5 +193,6 @@ "backup_borg_not_implemented": "O método de backup Borg ainda não foi implementado.", "backup_cant_mount_uncompress_archive": "Não foi possível montar em modo leitura o diretorio de arquivos não comprimido", "backup_copying_to_organize_the_archive": "Copiando {size:s}MB para organizar o arquivo", - "app_change_url_identical_domains": "O antigo e o novo domínio / url_path são idênticos ('{domain:s}{path:s}'), nada para fazer." + "app_change_url_identical_domains": "O antigo e o novo domínio / url_path são idênticos ('{domain:s}{path:s}'), nada para fazer.", + "password_too_simple_1": "A senha precisa ter pelo menos 8 caracteres" } From d7e34747d09b731168e03b7f534c378cd1c460b3 Mon Sep 17 00:00:00 2001 From: elie gavoty Date: Wed, 4 Dec 2019 13:50:24 +0000 Subject: [PATCH 0696/3170] Translated using Weblate (German) Currently translated at 33.6% (204 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/locales/de.json b/locales/de.json index 3c0d00ce5..2b4cfa0ec 100644 --- a/locales/de.json +++ b/locales/de.json @@ -417,5 +417,18 @@ "app_full_domain_unavailable": "Es tut uns leid, aber diese Anwendung erfordert die Installation auf einer eigenen Domain, aber einige andere Anwendungen sind bereits auf der Domäne'{domain}' installiert. Eine mögliche Lösung ist das Hinzufügen und Verwenden einer Subdomain, die dieser Anwendung zugeordnet ist.", "app_install_failed": "Installation von {app} fehlgeschlagen: {error}", "app_install_script_failed": "Im Installationsscript ist ein Fehler aufgetreten", - "app_remove_after_failed_install": "Entfernen der App nach fehlgeschlagener Installation…" + "app_remove_after_failed_install": "Entfernen der App nach fehlgeschlagener Installation…", + "app_upgrade_script_failed": "Es ist ein Fehler im App-Upgrade-Skript aufgetreten", + "diagnosis_basesystem_host": "Server läuft unter Debian {debian_version}.", + "diagnosis_basesystem_kernel": "Server läuft unter Linux-Kernel {kernel_version}", + "diagnosis_basesystem_ynh_single_version": "{0} Version: {1} ({2})", + "diagnosis_basesystem_ynh_main_version": "Server läuft YunoHost {main_version} ({repo})", + "diagnosis_basesystem_ynh_inconsistent_versions": "Sie verwenden inkonsistente Versionen der YunoHost-Pakete... wahrscheinlich wegen eines fehlgeschlagenen oder teilweisen Upgrades.", + "diagnosis_display_tip_web": "Sie können den Abschnitt Diagnose (im Startbildschirm) aufrufen, um die gefundenen Probleme anzuzeigen.", + "apps_catalog_init_success": "Apps-Katalogsystem initialisiert!", + "apps_catalog_updating": "Aktualisierung des Applikationskatalogs...", + "apps_catalog_failed_to_download": "Der {apps_catalog} Apps-Katalog kann nicht heruntergeladen werden: {error}", + "apps_catalog_obsolete_cache": "Der Cache des Apps-Katalogs ist leer oder veraltet.", + "apps_catalog_update_success": "Der Apps-Katalog wurde aktualisiert!", + "password_too_simple_1": "Das Passwort muss mindestens 8 Zeichen lang sein" } From d98636c44dba3c506b7a9ce373ad9e349c99c90a Mon Sep 17 00:00:00 2001 From: Yasss Gurl Date: Sat, 21 Dec 2019 16:48:09 +0000 Subject: [PATCH 0697/3170] Translated using Weblate (German) Currently translated at 33.6% (204 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/locales/de.json b/locales/de.json index 2b4cfa0ec..4d6fdbff6 100644 --- a/locales/de.json +++ b/locales/de.json @@ -254,7 +254,7 @@ "certmanager_domain_cert_not_selfsigned": "Das Zertifikat der Domain {domain:s} is kein selbstsigniertes Zertifikat. Bist du dir sicher, dass du es ersetzen willst? (Benutze --force)", "certmanager_certificate_fetching_or_enabling_failed": "Es scheint so als wäre die Aktivierung des Zertifikats für die Domain {domain:s} fehlgeschlagen...", "certmanager_attempt_to_renew_nonLE_cert": "Das Zertifikat der Domain {domain:s} wurde nicht von Let's Encrypt ausgestellt. Es kann nicht automatisch erneuert werden!", - "certmanager_attempt_to_renew_valid_cert": "Das Zertifikat der Domain {domain:s} läuft in Kürze ab! Benutze --force um diese Nachricht zu umgehen", + "certmanager_attempt_to_renew_valid_cert": "Das Zertifikat der Domain {domain:s} läuft nicht in Kürze ab! (Benutze --force um diese Nachricht zu umgehen)", "certmanager_domain_http_not_working": "Es scheint so, dass die Domain {domain:s} nicht über HTTP erreicht werden kann. Bitte überprüfe, ob deine DNS und nginx Konfiguration in Ordnung ist", "certmanager_error_no_A_record": "Kein DNS 'A' Eintrag für die Domain {domain:s} gefunden. Dein Domainname muss auf diese Maschine weitergeleitet werden, um ein Let's Encrypt Zertifikat installieren zu können! (Wenn du weißt was du tust, kannst du --no-checks benutzen, um diese Überprüfung zu überspringen. )", "certmanager_domain_dns_ip_differs_from_public_ip": "Der DNS 'A' Eintrag der Domain {domain:s} unterscheidet sich von dieser Server-IP. Wenn du gerade deinen A Eintrag verändert hast, warte bitte etwas, damit die Änderungen wirksam werden (du kannst die DNS Propagation mittels Website überprüfen) (Wenn du weißt was du tust, kannst du --no-checks benutzen, um diese Überprüfung zu überspringen. )", @@ -318,12 +318,12 @@ "confirm_app_install_thirdparty": "WARNUNG! Das Installieren von Anwendungen von Drittanbietern kann die Integrität und Sicherheit Deines Systems beeinträchtigen. Du solltest es wahrscheinlich NICHT installieren, es sei denn, Du weisst, was Du tust. Bist du bereit, dieses Risiko einzugehen? [{answers: s}] ", "confirm_app_install_danger": "WARNUNG! Diese Anwendung ist noch experimentell (wenn nicht ausdrücklich \"not working\"/\"funktioniert nicht\") und es ist wahrscheinlich, dass Dein System Schaden nimmt! Du solltest es wahrscheinlich NICHT installieren, es sei denn, Du weisst, was Du tust. Bist du bereit, dieses Risiko einzugehen? [{answers: s}] ", "confirm_app_install_warning": "Warnung: Diese Anwendung funktioniert möglicherweise, ist jedoch nicht gut in YunoHost integriert. Einige Funktionen wie Single Sign-On und Backup / Restore sind möglicherweise nicht verfügbar. Trotzdem installieren? [{answers: s}] ", - "backup_with_no_restore_script_for_app": "App {app: s} hat kein Wiederherstellungsskript. Das Backup dieser App kann nicht automatisch wiederhergestellt werden.", - "backup_with_no_backup_script_for_app": "App {app: s} hat kein Sicherungsskript. Ignoriere es.", - "backup_unable_to_organize_files": "Dateien im Archiv können mit der schnellen Methode nicht organisiert werden", - "backup_system_part_failed": "Der Systemteil '{part: s}' kann nicht gesichert werden", + "backup_with_no_restore_script_for_app": "Die App {app: s} hat kein Wiederherstellungsskript. Das Backup dieser App kann nicht automatisch wiederhergestellt werden.", + "backup_with_no_backup_script_for_app": "Die App {app: s} hat kein Sicherungsskript. Ignoriere es.", + "backup_unable_to_organize_files": "Dateien im Archiv konnten nicht mit der schnellen Methode organisiert werden", + "backup_system_part_failed": "Der Systemteil '{part: s}' konnte nicht gesichert werden", "backup_permission": "Sicherungsberechtigung für App {app: s}", - "backup_output_symlink_dir_broken": "Sie haben einen fehlerhaften Symlink anstelle Ihres Archivverzeichnisses '{path: s}'. Möglicherweise haben Sie ein spezielles Setup, um Ihre Daten auf einem anderen Dateisystem zu sichern. In diesem Fall haben Sie wahrscheinlich vergessen, Ihre Festplatte oder Ihren USB-Schlüssel erneut einzuhängen oder anzuschließen.", + "backup_output_symlink_dir_broken": "Ihr Archivverzeichnis '{path:s}' ist ein fehlerhafter Symlink. Vielleicht haben Sie vergessen, das Speichermedium, auf das er verweist, neu zu mounten oder einzustecken.", "backup_mount_archive_for_restore": "Archiv für Wiederherstellung vorbereiten…", "backup_method_tar_finished": "Tar-Backup-Archiv erstellt", "backup_method_custom_finished": "Benutzerdefinierte Sicherungsmethode '{method: s}' beendet", @@ -335,7 +335,7 @@ "backup_csv_creation_failed": "Die zur Wiederherstellung erforderliche CSV-Datei kann nicht erstellt werden", "backup_couldnt_bind": "{Src: s} konnte nicht an {dest: s} angebunden werden.", "backup_borg_not_implemented": "Die Borg-Sicherungsmethode ist noch nicht implementiert", - "backup_ask_for_copying_if_needed": "Einige Dateien konnten mit der Methode, die es vermeidet vorübergehend Speicherplatz auf dem System zu verschwenden, nicht gesichert werden. Zur Durchführung der Sicherung sollten vorübergehend {size: s} MB verwendet werden. Sind Sie einverstanden?", + "backup_ask_for_copying_if_needed": "Möchten Sie die Sicherung mit {size:s} MB temporär durchführen? (Dieser Weg wird verwendet, da einige Dateien nicht mit einer effizienteren Methode vorbereitet werden konnten).", "backup_actually_backuping": "Erstellt ein Backup-Archiv aus den gesammelten Dateien …", "ask_path": "Pfad", "ask_new_path": "Neuer Pfad", @@ -352,10 +352,10 @@ "app_not_upgraded": "Die App '{failed_app}' konnte nicht aktualisiert werden. Infolgedessen wurden die folgenden App-Upgrades abgebrochen: {apps}", "app_make_default_location_already_used": "Die App \"{app}\" kann nicht als Standard für die Domain \"{domain}\" festgelegt werden. Sie wird bereits von der App \"{other_app}\" verwendet", "aborting": "Breche ab.", - "app_action_cannot_be_ran_because_required_services_down": "Diese App erfordert einige Dienste, die derzeit nicht verfügbar sind. Bevor Sie fortfahren, sollten Sie versuchen, die folgenden Dienste neu zu starten (und möglicherweise untersuchen, warum sie nicht verfügbar sind): {services}", + "app_action_cannot_be_ran_because_required_services_down": "Diese erforderlichen Dienste sollten zur Durchführung dieser Aktion laufen: {services}. Versuchen Sie, sie neu zu starten, um fortzufahren (und möglicherweise zu untersuchen, warum sie nicht verfügbar sind).", "already_up_to_date": "Nichts zu tun. Alles ist bereits auf dem neusten Stand.", "admin_password_too_long": "Bitte ein Passwort kürzer als 127 Zeichen wählen", - "app_action_broke_system": "Diese Aktion hat anscheinend diese wichtigen Services gestört: {services}", + "app_action_broke_system": "Diese Aktion scheint diese wichtigen Dienste unterbrochen zu haben: {services}", "apps_already_up_to_date": "Alle Apps sind bereits aktuell", "backup_copying_to_organize_the_archive": "Kopieren von {size: s} MB, um das Archiv zu organisieren", "app_upgrade_stopped": "Das Upgrade aller Anwendungen wurde gestoppt, um mögliche Schäden zu vermeiden, da das Upgrade der vorherigen Anwendung fehlgeschlagen ist", From cd3cad1bc3f3cdf3ebba232d62d718b1bfeb0be3 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Thu, 19 Dec 2019 08:59:44 +0000 Subject: [PATCH 0698/3170] Translated using Weblate (Italian) Currently translated at 19.4% (118 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/it/ --- locales/it.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/it.json b/locales/it.json index 2c194d5a6..722add678 100644 --- a/locales/it.json +++ b/locales/it.json @@ -316,7 +316,7 @@ "certmanager_cert_signing_failed": "Firma del nuovo certificato fallita", "good_practices_about_user_password": "Ora stai per impostare una nuova password utente. La password dovrebbe essere di almeno 8 caratteri - anche se è buona pratica utilizzare password più lunghe (es. una sequenza di parole) e/o utilizzare vari tipi di caratteri (maiuscole, minuscole, numeri e simboli).", "password_listed": "Questa password è una tra le più utilizzate al mondo. Per favore scegline una più unica.", - "password_too_simple_1": "La password deve essere lunga almeno 8 caratteri", + "password_too_simple_1": "La password deve contenere almeno 8 caratteri", "password_too_simple_2": "La password deve essere lunga almeno 8 caratteri e contenere numeri, maiuscole e minuscole", "password_too_simple_3": "La password deve essere lunga almeno 8 caratteri e contenere numeri, maiuscole e minuscole e simboli", "password_too_simple_4": "La password deve essere lunga almeno 12 caratteri e contenere numeri, maiuscole e minuscole", From fdef78f8841ee4d8dc48fa286562d619abc33b56 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Thu, 19 Dec 2019 09:57:05 +0000 Subject: [PATCH 0699/3170] Translated using Weblate (Dutch) Currently translated at 6.6% (40 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/nl/ --- locales/nl.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/nl.json b/locales/nl.json index 166df89ff..df554f7e2 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -138,5 +138,6 @@ "backup_deleted": "Backup werd verwijderd", "backup_extracting_archive": "Backup archief uitpakken...", "backup_hook_unknown": "backup hook '{hook:s}' onbekend", - "backup_nothings_done": "Niets om op te slaan" + "backup_nothings_done": "Niets om op te slaan", + "password_too_simple_1": "Het wachtwoord moet minimaal 8 tekens lang zijn" } From 2559588546be1db4ca148a7c469e7a4e38e24c73 Mon Sep 17 00:00:00 2001 From: xaloc33 Date: Wed, 4 Dec 2019 21:42:12 +0000 Subject: [PATCH 0700/3170] Translated using Weblate (Catalan) Currently translated at 100.0% (607 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ca/ --- locales/ca.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/locales/ca.json b/locales/ca.json index a079c519f..acd3df621 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -491,7 +491,7 @@ "tools_upgrade_regular_packages": "Actualitzant els paquets «normals» (no relacionats amb YunoHost)…", "tools_upgrade_regular_packages_failed": "No s'han pogut actualitzar els paquets següents: {packages_list}", "tools_upgrade_special_packages": "Actualitzant els paquets «especials» (relacionats amb YunoHost)…", - "tools_upgrade_special_packages_explanation": "Aquesta acció s'acabarà, però l'actualització especial continuarà en segon pla. No comenceu cap altra acció al servidor en els pròxims ~10 minuts (depèn de la velocitat del maquinari). Un cop acabat, pot ser que us hagueu de tornar a connectar a la interfície d'administració. Els registres de l'actualització estaran disponibles a Eines → Registres (a la interfície d'administració) o amb «yunohost log list» (des de la línia d'ordres).", + "tools_upgrade_special_packages_explanation": "Aquesta actualització especial continuarà en segon pla. No comenceu cap altra acció al servidor en els pròxims ~10 minuts (depèn de la velocitat del maquinari). Després d'això, pot ser que us hagueu de tornar a connectar a la interfície d'administració. Els registres de l'actualització estaran disponibles a Eines → Registres (a la interfície d'administració) o utilitzant «yunohost log list» (des de la línia d'ordres).", "tools_upgrade_special_packages_completed": "Actualització dels paquets YunoHost acabada.\nPremeu [Enter] per tornar a la línia d'ordres", "unbackup_app": "L'aplicació «{app:s}» no serà guardada", "unexpected_error": "Hi ha hagut un error inesperat: {error}", @@ -716,5 +716,8 @@ "migration_description_0014_remove_app_status_json": "Eliminar els fitxers d'aplicació status.json heretats", "diagnosis_services_running": "El servei {service} s'està executant!", "diagnosis_services_conf_broken": "La configuració pel servei {service} està trencada!", - "diagnosis_ports_needed_by": "És necessari exposar aquest port pel servei {0}" + "diagnosis_ports_needed_by": "És necessari exposar aquest port pel servei {0}", + "permission_all_users_implicitly_added": "El permís també s'ha donat implícitament a «all_users» ja que és necessari per atorgar-lo al grup «visitors»", + "permission_cannot_remove_all_users_while_visitors_allowed": "No podeu retirar el permís a «all_users» mentre encara el tingui el grup «visitors»", + "global_settings_setting_pop3_enabled": "Activa el protocol POP3 per al servidor de correu" } From b52b1aee53651861e14ef3a8c70998c57f88764c Mon Sep 17 00:00:00 2001 From: amirale qt Date: Thu, 19 Dec 2019 09:06:40 +0000 Subject: [PATCH 0701/3170] Translated using Weblate (Russian) Currently translated at 1.8% (11 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ru/ --- locales/ru.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/ru.json b/locales/ru.json index 306a8763a..ed0a4c183 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -42,5 +42,6 @@ "appslist_retrieve_error": "Невозможно получить список удаленных приложений {appslist:s}: {error:s}", "appslist_unknown": "Список приложений {appslist:s} неизвестен.", "appslist_url_already_tracked": "Это уже зарегистрированный список приложений с url{url:s}.", - "installation_complete": "Установка завершена" + "installation_complete": "Установка завершена", + "password_too_simple_1": "Пароль должен быть не менее 8 символов" } From 05ade37d55f78a76a2dce8633bfc54123abc90e3 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Thu, 19 Dec 2019 08:31:58 +0000 Subject: [PATCH 0702/3170] Translated using Weblate (Hungarian) Currently translated at 1.0% (6 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/hu/ --- locales/hu.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/hu.json b/locales/hu.json index a6df4d680..b39882148 100644 --- a/locales/hu.json +++ b/locales/hu.json @@ -9,5 +9,6 @@ "app_already_up_to_date": "{app:s} napra kész", "app_argument_choice_invalid": "{name:s} érvénytelen választás, csak egyike lehet {choices:s} közül", "app_argument_invalid": "'{name:s}' hibás paraméter érték :{error:s}", - "app_argument_required": "Parameter '{name:s}' kötelező" + "app_argument_required": "Parameter '{name:s}' kötelező", + "password_too_simple_1": "A jelszónak legalább 8 karakter hosszúnak kell lennie" } From 3ece24700bc46888bb843c7f79cf5c1e08bdff4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quent=C3=AD?= Date: Sun, 15 Dec 2019 12:06:43 +0000 Subject: [PATCH 0703/3170] Translated using Weblate (Occitan) Currently translated at 41.5% (252 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/oc/ --- locales/oc.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/locales/oc.json b/locales/oc.json index aa233d8b8..8d095e079 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -698,5 +698,12 @@ "apps_catalog_obsolete_cache": "La memòria cache del catalòg d’aplicacion es voida o obsolèta.", "apps_catalog_update_success": "Lo catalòg d’aplicacions es a jorn !", "diagnosis_mail_ougoing_port_25_ok": "Lo pòrt de sortida 25 es pas blocat e lo corrièr electronic pòt partir als autres servidors.", - "diagnosis_description_mail": "Corrièl" + "diagnosis_description_mail": "Corrièl", + "app_upgrade_script_failed": "Una error s’es producha pendent l’execucion de l’script de mesa a nivèl de l’aplicacion", + "diagnosis_cant_run_because_of_dep": "Execucion impossibla del diagnostic per {category} mentre que i a de problèmas importants ligats amb {dep}.", + "diagnosis_found_errors_and_warnings": "Avèm trobat {errors} problèma(s) important(s) (e {warnings} avis(es)) ligats a {category} !", + "diagnosis_failed": "Recuperacion impossibla dels resultats del diagnostic per la categoria « {category} » : {error}", + "diagnosis_ip_broken_dnsresolution": "La resolucion del nom de domeni es copada per una rason… Lo parafuòc bloca las requèstas DNS ?", + "diagnosis_no_cache": "I a pas encara de diagnostic de cache per la categoria « {category} »", + "apps_catalog_init_success": "Sistèma de catalòg d’aplicacion iniciat !" } From 05993d9bc24f859d211580315d7c450cca985397 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Fri, 6 Dec 2019 10:13:07 +0000 Subject: [PATCH 0704/3170] Translated using Weblate (Esperanto) Currently translated at 100.0% (607 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/eo/ --- locales/eo.json | 140 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 110 insertions(+), 30 deletions(-) diff --git a/locales/eo.json b/locales/eo.json index 4b8a76d3c..f303a57ee 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -19,10 +19,10 @@ "yunohost_installing": "Instalante YunoHost…", "service_description_glances": "Monitoras sistemajn informojn en via servilo", "service_description_metronome": "Mastrumas XMPP tujmesaĝilon kontojn", - "service_description_mysql": "Stokas aplikaĵojn datojn (SQL datumbazo)", + "service_description_mysql": "Butikigas datumojn de programoj (SQL datumbazo)", "service_description_nginx": "Servas aŭ permesas atingi ĉiujn retejojn gastigita sur via servilo", "service_description_nslcd": "Mastrumas Yunohost uzantojn konektojn per komanda linio", - "service_description_php7.0-fpm": "Rulas aplikaĵojn skibita en PHP kun nginx", + "service_description_php7.0-fpm": "Ekzekutas programojn skribitajn en PHP kun NGINX", "service_description_postfix": "Uzita por sendi kaj ricevi retpoŝtojn", "service_description_redis-server": "Specialita datumbazo uzita por rapida datumo atingo, atendovicoj kaj komunikadoj inter programoj", "service_description_rmilter": "Kontrolas diversajn parametrojn en retpoŝtoj", @@ -30,9 +30,9 @@ "service_description_slapd": "Stokas uzantojn, domajnojn kaj rilatajn informojn", "service_description_ssh": "Permesas al vi konekti al via servilo kun fora terminalo (SSH protokolo)", "service_description_yunohost-api": "Mastrumas interagojn inter la YunoHost retinterfaco kaj la sistemo", - "service_description_yunohost-firewall": "Mastrumas malfermitajn kaj fermitajn konektejojn al servoj", - "service_disable_failed": "Ne povis malŝalti la servon '{service:s}'\n\nFreŝaj protokoloj de la servo : {logs:s}", - "service_disabled": "'{service: s}' servo malŝaltita", + "service_description_yunohost-firewall": "Administras malfermajn kaj fermajn konektajn havenojn al servoj", + "service_disable_failed": "Ne povis fari la servon '{service:s}' ne komenci ĉe la ekkuro.\n\nLastatempaj servaj protokoloj: {logs:s}", + "service_disabled": "La servo '{service:s}' ne plu komenciĝos kiam sistemo ekos.", "action_invalid": "Nevalida ago « {action:s} »", "admin_password": "Pasvorto de la estro", "admin_password_too_long": "Bonvolu elekti pasvorton pli mallonga ol 127 signoj", @@ -90,7 +90,7 @@ "app_requirements_failed": "Certaines exigences ne sont pas remplies pour {app}: {error}", "backup_no_uncompress_archive_dir": "Ne ekzistas tia nekompremita arkiva dosierujo", "password_too_simple_1": "Pasvorto devas esti almenaŭ 8 signojn longa", - "app_upgrade_failed": "Ne povis ĝisdatigi {app:s}", + "app_upgrade_failed": "Ne povis ĝisdatigi {app:s}: {error}", "app_upgrade_several_apps": "La sekvaj apliko estos altgradigitaj: {apps}", "backup_archive_open_failed": "Ne povis malfermi la rezervan ar archiveivon", "ask_lastname": "Familia nomo", @@ -197,12 +197,12 @@ "migration_0011_failed_to_remove_stale_object": "Ne povis forigi neuzatan objekton {dn}: {error}", "migrations_already_ran": "Tiuj migradoj estas jam faritaj: {ids}", "migrations_no_such_migration": "Estas neniu migrado nomata '{id}'", - "permission_already_allowed": "Grupo '{group}' jam havas permeson '{permission}' ebligita'", + "permission_already_allowed": "Grupo '{group}' jam havas rajtigitan permeson '{permission}'", "permission_already_disallowed": "Grupo '{group}' jam havas permeson '{permission}' malebligita'", "permission_cannot_remove_main": "Forigo de ĉefa permeso ne rajtas", "permission_creation_failed": "Ne povis krei permeson '{permission}': {error}", - "tools_update_failed_to_app_fetchlist": "Ne povis ĝisdatigi la aparatojn de YunoHost ĉar: {error}", - "user_already_exists": "Uzanto {uzanto} jam ekzistas", + "tools_update_failed_to_app_fetchlist": "Ne povis ĝisdatigi la listojn de aplikoj de YunoHost ĉar: {error}", + "user_already_exists": "La uzanto '{user}' jam ekzistas", "migrations_pending_cant_rerun": "Tiuj migradoj ankoraŭ estas pritraktataj, do ne plu rajtas esti ekzekutitaj: {ids}", "migrations_running_forward": "Kuranta migrado {id}…", "migrations_success_forward": "Migrado {id} kompletigita", @@ -213,7 +213,7 @@ "permission_not_found": "Permesita \"{permission:s}\" ne trovita", "restore_not_enough_disk_space": "Ne sufiĉa spaco (spaco: {free_space:d} B, necesa spaco: {needed_space:d} B, sekureca marĝeno: {margin:d} B)", "tools_upgrade_regular_packages": "Nun ĝisdatigi 'regulajn' (ne-yunohost-rilatajn) pakojn …", - "tools_upgrade_special_packages_explanation": "Ĉi tiu ago finiĝos, sed la fakta speciala ĝisdatigo daŭros en fono. Bonvolu ne komenci iun alian agon en via servilo en la sekvaj ~ 10 minutoj (depende de via aparata rapideco). Unufoje mi plenumis, vi eble devos ensaluti en la retpaĝo. La ĝisdatiga registro estos havebla en Iloj → Madero (sur la retpaĝo) aŭ tra 'yunohost-registro-listo' (el la komandlinio).", + "tools_upgrade_special_packages_explanation": "La speciala ĝisdatigo daŭros en fono. Bonvolu ne komenci aliajn agojn en via servilo la sekvajn ~ 10 minutojn (depende de la aparata rapideco). Post tio, vi eble devos re-ensaluti sur la retadreso. La ĝisdatiga registro estos havebla en Iloj → Ensaluto (en la retadreso) aŭ uzante 'yunohost-logliston' (el la komandlinio).", "unrestore_app": "App '{app:s}' ne restarigos", "group_created": "Grupo '{group}' kreita", "group_creation_failed": "Ne povis krei la grupon '{group}': {error}", @@ -236,7 +236,7 @@ "regenconf_file_backed_up": "Agordodosiero '{conf}' estis rezervita al '{backup}'", "migration_0007_cannot_restart": "SSH ne rekomencas post provi nuligi la migradan numeron 6.", "migration_description_0006_sync_admin_and_root_passwords": "Sinkronigu admin kaj radikajn pasvortojn", - "updating_app_lists": "Akirante haveblajn ĝisdatigojn por aplikoj…", + "updating_app_lists": "Akirante haveblajn ĝisdatigojn por programoj…", "iptables_unavailable": "Vi ne povas ludi kun iptables ĉi tie. Vi estas en ujo aŭ via kerno ne subtenas ĝin", "global_settings_cant_write_settings": "Ne eblis konservi agordojn, tial: {reason:s}", "service_added": "La servo '{service:s}' aldonis", @@ -279,7 +279,7 @@ "migrate_tsig_start": "Detektita ŝlosila algoritmo nesufiĉa por TSIG-subskribo de la domajno '{domain}', komencanta migradon al la pli sekura HMAC-SHA-512", "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 malhelpi kritikajn pakojn…", + "tools_upgrade_cant_unhold_critical_packages": "Ne povis malŝalti kritikajn pakojn…", "diagnosis_monitor_disk_error": "Ne povis monitori diskojn: {error}", "log_link_to_log": "Plena ŝtipo de ĉi tiu operacio: ' {desc} '", "service_no_log": "Neniu registro por montri por servo '{service:s}'", @@ -317,7 +317,7 @@ "log_app_removelist": "Forigu aplikan liston", "global_settings_setting_example_enum": "Ekzemplo enum elekto", "hook_exec_not_terminated": "Skripto ne finiĝis ĝuste: {path:s}", - "service_stopped": "'{service:s}' servo ĉesis", + "service_stopped": "Servo '{service:s}' ĉesis", "restore_failed": "Ne povis restarigi sistemon", "confirm_app_install_danger": "Danĝero! Ĉi tiu apliko estas konata ankoraŭ eksperimenta (se ne eksplicite ne funkcias)! Vi probable ne devas instali ĝin krom se vi scias kion vi faras. NENIU SUBTENO estos provizita se ĉi tiu app ne funkcias aŭ rompas vian sistemon ... Se vi pretas riski ĉiuokaze, tajpu '{answers: s}'", "log_operation_unit_unclosed_properly": "Operaciumo ne estis fermita ĝuste", @@ -362,21 +362,21 @@ "log_corrupted_md_file": "La YAD-metadata dosiero asociita kun protokoloj estas damaĝita: '{md_file}\nEraro: {error} '", "downloading": "Elŝutante …", "user_deleted": "Uzanto forigita", - "service_enable_failed": "Ne eblis ŝalti la servon '{service:s}'\n\nLastatempaj servaj protokoloj: {logs:s}", + "service_enable_failed": "Ne povis fari la servon '{service:s}' aŭtomate komenci ĉe la ekkuro.\n\nLastatempaj servaj protokoloj: {logs:s}", "tools_upgrade_special_packages": "Nun ĝisdatigi 'specialajn' (rilatajn al yunohost)…", "domains_available": "Haveblaj domajnoj:", "dyndns_registered": "Registrita domajno DynDNS", "service_description_fail2ban": "Protektas kontraŭ bruta forto kaj aliaj specoj de atakoj de la interreto", "file_does_not_exist": "La dosiero {path:s} ne ekzistas.", - "yunohost_not_installed": "YunoHost estas malĝuste aŭ ne ĝuste instalita. Bonvolu prilabori 'yunohost tools postinstall'", + "yunohost_not_installed": "YunoHost ne estas ĝuste instalita. Bonvolu prilabori 'yunohost tools postinstall'", "migration_0005_postgresql_96_not_installed": "PostgreSQL 9.4 estas instalita, sed ne postgresql 9.6‽ Io stranga eble okazis en via sistemo: (…", "restore_removing_tmp_dir_failed": "Ne povis forigi malnovan provizoran dosierujon", "certmanager_cannot_read_cert": "Io malbona okazis, kiam mi provis malfermi aktualan atestilon por domajno {domain:s} (dosiero: {file:s}), kialo: {reason:s}", - "service_removed": "'{service:s}' servo forigita", + "service_removed": "Servo '{service:s}' forigita", "certmanager_hit_rate_limit": "Tro multaj atestiloj jam eldonitaj por ĉi tiu ĝusta aro de domajnoj {domain:s} antaŭ nelonge. Bonvolu reprovi poste. Vidu https://letsencrypt.org/docs/rate-limits/ por pliaj detaloj", "migration_0005_not_enough_space": "Disponigu sufiĉan spacon en {path} por ruli la migradon.", "pattern_firstname": "Devas esti valida antaŭnomo", - "migration_description_0010_migrate_to_apps_json": "Forigu malvalorigitajn aparatojn kaj uzu anstataŭe la novan unuigitan liston \"apps.json\"", + "migration_description_0010_migrate_to_apps_json": "Forigu malvalorigitajn katalogajn programojn kaj uzu anstataŭe la novan unuigitan liston de \"apps.json\" (malaktuale anstataŭita per migrado 13)", "domain_cert_gen_failed": "Ne povis generi atestilon", "regenconf_file_kept_back": "La agorda dosiero '{conf}' estas atendita forigi per regen-conf (kategorio {category}), sed ĝi estis konservita.", "migrate_tsig_wait_4": "30 sekundoj …", @@ -432,7 +432,7 @@ "backup_php5_to_php7_migration_may_fail": "Ne povis konverti vian ar archiveivon por subteni PHP 7, vi eble ne povas restarigi viajn PHP-programojn (kialo: {error:s})", "log_backup_restore_system": "Restarigi sistemon de rezerva arkivo", "log_app_change_url": "Ŝanĝu la URL de la apliko '{}'", - "service_already_started": "La servo '{service:s}' estas jam komencita", + "service_already_started": "La servo '{service:s}' jam funkcias", "license_undefined": "nedifinita", "global_settings_setting_security_password_admin_strength": "Admin pasvorta forto", "service_reload_or_restart_failed": "Ne povis reŝargi aŭ rekomenci la servon '{service:s}'\n\nLastatempaj servaj protokoloj: {logs:s}", @@ -455,7 +455,7 @@ "regenconf_file_updated": "Agordodosiero '{conf}' ĝisdatigita", "log_help_to_get_log": "Por vidi la protokolon de la operacio '{desc}', uzu la komandon 'yunohost log display {name}'", "global_settings_setting_security_nginx_compatibility": "Kongruo vs sekureca kompromiso por la TTT-servilo NGINX. Afektas la ĉifradojn (kaj aliajn aspektojn pri sekureco)", - "no_internet_connection": "Servilo ne konektita al la interreto", + "no_internet_connection": "La servilo ne estas konektita al la interreto", "migration_0008_dsa": "• La DSA-ŝlosilo estos malŝaltita. Tial vi eble bezonos nuligi spuran averton de via SSH-kliento kaj revizii la fingrospuron de via servilo;", "migration_0003_restoring_origin_nginx_conf": "Fileia dosiero /etc/nginx/nginx.conf estis iel redaktita. La migrado reaperos unue al sia originala stato ... La antaŭa dosiero estos havebla kiel {backup_dest}.", "migrate_tsig_end": "Migrado al HMAC-SHA-512 finiĝis", @@ -467,7 +467,7 @@ "service_description_avahi-daemon": "Permesas al vi atingi vian servilon uzante 'yunohost.local' en via loka reto", "certmanager_attempt_to_replace_valid_cert": "Vi provas anstataŭigi bonan kaj validan atestilon por domajno {domajno:s}! (Uzu --forte pretervidi)", "monitor_stats_period_unavailable": "Ne ekzistas disponeblaj statistikoj por la periodo", - "regenconf_updated": "Agordo por kategorio '{category}' ĝisdatigita", + "regenconf_updated": "Agordo ĝisdatigita por '{category}'", "update_apt_cache_warning": "Io iris malbone dum la ĝisdatigo de la kaŝmemoro de APT (paka administranto de Debian). Jen rubujo de la sources.list-linioj, kiuj povus helpi identigi problemajn liniojn:\n{sourcelist}", "regenconf_dry_pending_applying": "Kontrolado de pritraktata agordo, kiu estus aplikita por kategorio '{category}'…", "regenconf_file_copy_failed": "Ne povis kopii la novan agordodosieron '{new}' al '{conf}'", @@ -478,7 +478,7 @@ "maindomain_change_failed": "Ne povis ŝanĝi la ĉefan domajnon", "mail_domain_unknown": "Nevalida retadreso por domajno '{domain:s}'. Bonvolu uzi domajnon administritan de ĉi tiu servilo.", "migrations_cant_reach_migration_file": "Ne povis aliri migrajn dosierojn ĉe la vojo '% s'", - "pattern_email": "Devas esti valida retpoŝtadreso (t.e.iu@domain.org)", + "pattern_email": "Devas esti valida retpoŝta adreso (t.e. iu@ekzemple.com)", "mail_alias_remove_failed": "Ne povis forigi retpoŝton alias '{mail:s}'", "regenconf_file_manually_removed": "La dosiero de agordo '{conf}' estis forigita permane, kaj ne estos kreita", "monitor_enabled": "Servilo-monitorado nun", @@ -501,7 +501,7 @@ "log_app_install": "Instalu la aplikon '{}'", "service_description_dnsmasq": "Traktas rezolucion de domajna nomo (DNS)", "global_settings_unknown_type": "Neatendita situacio, la agordo {setting:s} ŝajnas havi la tipon {unknown_type:s} sed ĝi ne estas tipo subtenata de la sistemo.", - "migration_0003_problematic_apps_warning": "Bonvolu noti, ke la sekvaj eventuale problemaj instalitaj apps estis detektitaj. Ĝi aspektas, ke tiuj ne estis instalitaj de aparato aŭ ne estas markitaj kiel \"funkciantaj\". Tial ne eblas garantii, ke ili ankoraŭ funkcios post la ĝisdatigo: {problematic_apps}", + "migration_0003_problematic_apps_warning": "Bonvolu noti, ke la sekvaj eventuale problemaj instalitaj programoj estis detektitaj. Ŝajnas, ke tiuj ne estis instalitaj el app_katalogo aŭ ne estas markitaj kiel \"funkciantaj\". Tial ne eblas garantii, ke ili ankoraŭ funkcios post la ĝisdatigo: {problematic_apps}", "domain_hostname_failed": "Ne povis agordi novan gastigilon. Ĉi tio eble kaŭzos problemon poste (eble bone).", "server_reboot": "La servilo rekomenciĝos", "regenconf_failed": "Ne povis regeneri la agordon por kategorio(j): {categories}", @@ -510,7 +510,7 @@ "service_unknown": "Nekonata servo '{service:s}'", "migration_0003_start": "Komencante migradon al Stretch. La protokoloj haveblos en {logfile}.", "monitor_stats_no_update": "Neniuj monitoradaj statistikoj ĝisdatigi", - "domain_deletion_failed": "Ne povis forigi domajnon {domain}: {error}", + "domain_deletion_failed": "Ne eblas forigi domajnon {domain}: {error}", "log_user_update": "Ĝisdatigu uzantinformojn de '{}'", "user_creation_failed": "Ne povis krei uzanton {user}: {error}", "migrations_migration_has_failed": "Migrado {id} ne kompletigis, abolis. Eraro: {exception}", @@ -529,13 +529,13 @@ "migration_0007_cancelled": "Ne povis plibonigi la manieron kiel via SSH-agordo estas administrita.", "migrate_tsig_failed": "Ne povis migri la DynDNS-domajnon '{domain}' al HMAC-SHA-512, ruliĝante. Eraro: {error_code}, {error}", "pattern_lastname": "Devas esti valida familinomo", - "service_enabled": "'{service:s}' servo malŝaltita", + "service_enabled": "La servo '{service:s}' nun aŭtomate komenciĝos dum sistemaj botoj.", "certmanager_no_cert_file": "Ne povis legi la atestan dosieron por la domajno {domain:s} (dosiero: {file:s})", "migration_0008_port": "• Vi devos konekti uzante la havenon 22 anstataŭ via nuna kutimo SSH-haveno. Sentu vin libera reconfiguri ĝin;", - "domain_creation_failed": "Ne povis krei domajnon {domain}: {error}", + "domain_creation_failed": "Ne eblas krei domajnon {domain}: {error}", "certmanager_domain_http_not_working": "Ŝajnas ke la domajno {domain:s} ne atingeblas per HTTP. Kontrolu, ke via DNS kaj NGINX-agordo ĝustas", - "domain_cannot_remove_main": "Ne eblas forigi ĉefan domajnon. Fiksu unu unue", - "service_reloaded_or_restarted": "'{service:s}' servo reŝarĝis aŭ rekomencis", + "domain_cannot_remove_main": "Vi ne povas forigi '{domain:s}' ĉar ĝi estas la ĉefa domajno, vi bezonas unue agordi alian domajnon kiel la ĉefan domajnon per uzado de 'yunohost domain main-domain -n ', jen la listo de kandidataj domajnoj. : {other_domainsj:s}", + "service_reloaded_or_restarted": "La servo '{service:s}' estis reŝarĝita aŭ rekomencita", "mysql_db_initialized": "La datumbazo MySQL jam estas pravalorizita", "log_domain_add": "Aldonu '{}' domajnon en sisteman agordon", "global_settings_bad_type_for_setting": "Malbona tipo por agordo {setting:s}, ricevita {received_type:s}, atendata {expected_type:s}", @@ -543,11 +543,11 @@ "dyndns_cron_installed": "Kreita laboro DynDNS cron", "system_username_exists": "Uzantnomo jam ekzistas en la listo de uzantoj de sistemo", "firewall_reloaded": "Fajroŝirmilo reŝarĝis", - "service_restarted": "'{service:s}' servo rekomencis", + "service_restarted": "Servo '{service:s}' rekomencis", "pattern_username": "Devas esti minuskulaj literoj kaj minuskloj nur", "extracting": "Eltirante…", "restore_app_failed": "Ne povis restarigi la programon '{app:s}'", - "yunohost_configured": "YunoHost nun agordis", + "yunohost_configured": "YunoHost nun estas agordita", "certmanager_self_ca_conf_file_not_found": "Ne povis trovi agorddosieron por mem-subskriba aŭtoritato (dosiero: {file:s})", "log_app_remove": "Forigu la aplikon '{}'", "service_restart_failed": "Ne povis rekomenci la servon '{service:s}'\n\nLastatempaj servaj protokoloj: {logs:s}", @@ -579,5 +579,85 @@ "diagnosis_cache_still_valid": "(Kaŝmemoro ankoraŭ validas por {category} diagnozo. Ankoraŭ ne re-diagnoza!)", "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}" + "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_abs_GB} GB ({free_percent}%) spaco. Vi vere konsideru purigi iom da spaco.", + "diagnosis_ram_verylow": "La sistemo nur restas {available_abs_MB} MB ({available_percent}%) RAM! (el {total_abs_MB} MB)", + "diagnosis_mail_ougoing_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": "Ne povis atingi vian servilon kiel atendite, ĝi redonis malbonan statuskodon. Povas esti, ke alia maŝino respondis anstataŭ via servilo. Vi devus kontroli, ke vi ĝuste redonas la havenon 80, ke via nginx-agordo ĝisdatigas kaj ke reverso-prokuro ne interbatalas.", + "main_domain_changed": "La ĉefa domajno estis ŝanĝita", + "permission_all_users_implicitly_added": "La permeso ankaŭ estis implicite donita al 'all_users' ĉar ĝi bezonas por permesi al la 'visitors' de la speciala grupo", + "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ŭ 'yunohost user create ' en komandlinio);\n - diagnozi problemojn atendantajn solvi por ke via servilo funkciu kiel eble plej glate tra la sekcio 'Diagnosis' de la retadministrado (aŭ 'yunohost diagnosis run' en komandlinio);\n - legante la partojn 'Finigi vian agordon' kaj 'Ekkoni Yunohost' en la administra dokumentado: https://yunohost.org/admindoc.", + "permission_cannot_remove_all_users_while_visitors_allowed": "Vi ne povas forigi ĉi tiun permeson por 'all_users' dum ĝi tamen estas permesita por 'visitors'", + "migration_description_0014_remove_app_status_json": "Forigi heredajn dosierojn", + "diagnosis_ip_connected_ipv4": "La servilo estas konektita al la interreto per IPv4 !", + "diagnosis_ip_no_ipv4": "La servilo ne havas funkciantan IPv4.", + "diagnosis_ip_connected_ipv6": "La servilo estas konektita al la interreto per IPv6 !", + "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 per /etc/resolv.dnsmasq.conf.", + "diagnosis_dns_good_conf": "Bona DNS-agordo por domajno {domain} (kategorio {category})", + "diagnosis_dns_bad_conf": "Malbona / mankas DNS-agordo por domajno {domain} (kategorio {category})", + "diagnosis_ram_ok": "La sistemo ankoraŭ havas {available_abs_MB} MB ({available_percent}%) RAM forlasita de {total_abs_MB} MB.", + "diagnosis_swap_none": "La sistemo tute ne havas interŝanĝon. Vi devus pripensi aldoni almenaŭ 256 MB da interŝanĝo por eviti situaciojn en kiuj la sistemo restas sen memoro.", + "diagnosis_swap_notsomuch": "La sistemo havas nur {total_MB} MB-interŝanĝon. Vi konsideru havi almenaŭ 256 MB 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_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_regenconf_nginx_conf_broken": "La agordo de nginx ŝajnas rompi !", + "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_dns_missing_record": "Laŭ la rekomendita DNS-agordo, vi devas aldoni DNS-registron kun tipo {0}, nomo {1} kaj valoro {2}. Vi povas kontroli https://yunohost.org/dns_config por pliaj informoj.", + "diagnosis_dns_discrepancy": "La DNS-registro kun tipo {0} kaj nomo {1} ne kongruas kun la rekomendita agordo. Nuna valoro: {2}. Esceptita valoro: {3}. Vi povas kontroli https://yunohost.org/dns_config por pliaj informoj.", + "diagnosis_services_conf_broken": "Agordo estas rompita por servo {service} !", + "diagnosis_services_bad_status": "Servo {service} estas {status} :(", + "diagnosis_ram_low": "La sistemo havas {available_abs_MB} MB ({available_percent}%) RAM forlasita de {total_abs_MB} MB. Estu zorgema.", + "diagnosis_swap_ok": "La sistemo havas {total_MB} MB 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_description_ip": "Interreta konektebleco", + "diagnosis_description_dnsrecords": "Registroj DNS", + "diagnosis_description_services": "Servo kontrolas staton", + "diagnosis_description_systemresources": "Rimedaj sistemoj", + "diagnosis_description_security": "Sekurecaj kontroloj", + "diagnosis_ports_could_not_diagnose": "Ne povis diagnozi, ĉu haveblaj havenoj de ekstere. 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 {0}' aŭ tra la sekcio 'Servoj' de la retadreso.", + "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 '{}' kiel ĉ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_connection_error": "Rilata eraro: ne povis konektiĝi al la petita domajno, tre probable ĝi estas neatingebla.", + "diagnosis_http_unknown_error": "Eraro okazis dum provado atingi vian domajnon, 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_abs_GB} GB ({free_percent}%) spaco. Estu zorgema.", + "diagnosis_diskusage_ok": "Stokado {mountpoint} (sur aparato {device) ankoraŭ restas {free_abs_GB} GB ({free_percent}%) spaco!", + "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 servo {0}", + "diagnosis_ports_forwarding_tip": "Por solvi ĉi tiun problemon, plej probable vi 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. Eraro: {error}", + "diagnosis_http_ok": "Domajno {domain} atingeblas de ekstere.", + "diagnosis_http_unreachable": "Domajno {domain} estas atingebla per HTTP de ekstere.", + "domain_cannot_remove_main_add_new_one": "Vi ne povas forigi '{domain:s}' ĉar ĝi estas la ĉefa domajno kaj via sola domajno, vi devas unue aldoni alian domajnon uzante ''yunohost domain add ', tiam agordi kiel ĉefan domajnon uzante 'yunohost domain main-domain -n ' kaj tiam vi povas forigi la domajnon' {domain: s} 'uzante' yunohost domain remove {domain:s} '.'", + "permission_require_account": "Permesilo {permission} nur havas sencon por uzantoj, kiuj havas konton, kaj tial ne rajtas esti ebligitaj por vizitantoj.", + "diagnosis_found_warnings": "Trovitaj {warnings} ero (j) kiuj povus esti plibonigitaj por {category}.", + "diagnosis_everything_ok": "Ĉio aspektas bone por {category}!", + "diagnosis_failed": "Malsukcesis preni la diagnozan rezulton por kategorio '{category}': {error}", + "diagnosis_description_ports": "Ekspoziciaj havenoj", + "diagnosis_description_http": "HTTP-ekspozicio", + "diagnosis_description_mail": "Retpoŝto" } From 32d58241bcc8dc91d549639abf23b55f2db00018 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Thu, 19 Dec 2019 08:51:49 +0000 Subject: [PATCH 0705/3170] Translated using Weblate (Hindi) Currently translated at 3.8% (23 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/hi/ --- locales/hi.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/hi.json b/locales/hi.json index 015fd4e5e..23d391c47 100644 --- a/locales/hi.json +++ b/locales/hi.json @@ -77,5 +77,6 @@ "domain_deleted": "डोमेन डिलीट कर दिया गया है", "domain_deletion_failed": "डोमेन डिलीट करने में असमर्थ", "domain_dyndns_already_subscribed": "DynDNS डोमेन पहले ही सब्स्क्राइड है", - "domain_dyndns_invalid": "DynDNS के साथ इनवैलिड डोमिन इस्तेमाल किया गया" + "domain_dyndns_invalid": "DynDNS के साथ इनवैलिड डोमिन इस्तेमाल किया गया", + "password_too_simple_1": "पासवर्ड को कम से कम 8 वर्ण लंबा होना चाहिए" } From ebc98c990e990bd3c0c0046c7116255a02626261 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Thu, 19 Dec 2019 09:45:20 +0000 Subject: [PATCH 0706/3170] Translated using Weblate (Polish) Currently translated at 0.2% (1 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/pl/ --- locales/pl.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/locales/pl.json b/locales/pl.json index 0967ef424..f1417a80c 100644 --- a/locales/pl.json +++ b/locales/pl.json @@ -1 +1,3 @@ -{} +{ + "password_too_simple_1": "Hasło musi mieć co najmniej 8 znaków" +} From 61fd844bfa54133db1802520d77d078c2392c3af Mon Sep 17 00:00:00 2001 From: ButterflyOfFire Date: Thu, 26 Dec 2019 20:34:46 +0000 Subject: [PATCH 0707/3170] Translated using Weblate (Arabic) Currently translated at 14.2% (86 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ar/ --- locales/ar.json | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/locales/ar.json b/locales/ar.json index 46f9315af..936b54d2e 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -211,8 +211,8 @@ "mail_domain_unknown": "Unknown mail address domain '{domain:s}'", "mail_forward_remove_failed": "Unable to remove mail forward '{mail:s}'", "mailbox_used_space_dovecot_down": "Dovecot mailbox service need to be up, if you want to get mailbox used space", - "maindomain_change_failed": "Unable to change the main domain", - "maindomain_changed": "The main domain has been changed", + "maindomain_change_failed": "تعذّر تغيير النطاق الأساسي", + "maindomain_changed": "تم تغيير النطاق الأساسي", "migrate_tsig_end": "Migration to hmac-sha512 finished", "migrate_tsig_failed": "Migrating the dyndns domain {domain} to hmac-sha512 failed, rolling back. Error: {error_code} - {error}", "migrate_tsig_start": "Not secure enough key algorithm detected for TSIG signature of domain '{domain}', initiating migration to the more secure one hmac-sha512", @@ -443,5 +443,19 @@ "dyndns_could_not_check_available": "لا يمكن التحقق مِن أنّ {domain:s} متوفر على {provider:s}.", "backup_mount_archive_for_restore": "جارٍ تهيئة النسخة الاحتياطية للاسترجاع…", "root_password_replaced_by_admin_password": "لقد تم استبدال كلمة سر الجذر root بالكلمة الإدارية لـ admin.", - "app_upgrade_stopped": "لقد تم إلغاء تحديث كافة التطبيقات لتجنب حادث بسبب فشل تحديث التطبيق السابق" + "app_upgrade_stopped": "لقد تم إلغاء تحديث كافة التطبيقات لتجنب حادث بسبب فشل تحديث التطبيق السابق", + "app_action_broke_system": "يبدو أنّ هذا الإجراء أدّى إلى تحطيم خدمات مهمّة: {services}", + "diagnosis_basesystem_host": "هذا الخادم يُشغّل ديبيان {debian_version}.", + "diagnosis_basesystem_kernel": "هذا الخادم يُشغّل نواة لينكس {kernel_version}", + "diagnosis_basesystem_ynh_single_version": "{0} الإصدار: {1} ({2})", + "diagnosis_basesystem_ynh_main_version": "هذا الخادم يُشغّل YunoHost {main_version} ({repo})", + "diagnosis_everything_ok": "كل شيء على ما يرام في {category}!", + "diagnosis_ip_connected_ipv4": "الخادم مُتّصل بالإنترنت عبر IPv4!", + "diagnosis_ip_connected_ipv6": "الخادم مُتّصل بالإنترنت عبر IPv6!", + "diagnosis_ip_not_connected_at_all": "يبدو أنّ الخادم غير مُتّصل بتاتا بالإنترنت!؟", + "app_install_failed": "لا يمكن تنصيب {app}: {error}", + "apps_already_up_to_date": "كافة التطبيقات مُحدّثة", + "app_remove_after_failed_install": "جارٍ حذف التطبيق بعدما فشل تنصيبها…", + "apps_catalog_updating": "جارٍ تحديث فهرس التطبيقات…", + "apps_catalog_update_success": "تم تحديث فهرس التطبيقات!" } From a4415f9ac12ab612a6608a0aa0c03e6aa25c33af Mon Sep 17 00:00:00 2001 From: Alvaro Date: Tue, 24 Dec 2019 11:25:23 +0000 Subject: [PATCH 0708/3170] Translated using Weblate (Spanish) Currently translated at 89.1% (541 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/es/ --- locales/es.json | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/locales/es.json b/locales/es.json index e33d2c3ae..a64408411 100644 --- a/locales/es.json +++ b/locales/es.json @@ -27,7 +27,7 @@ "app_sources_fetch_failed": "No se pudieron obtener los archivos con el código fuente, ¿es el URL correcto?", "app_unknown": "Aplicación desconocida", "app_unsupported_remote_type": "Tipo remoto no soportado por la aplicación", - "app_upgrade_failed": "No se pudo actualizar {app:s}", + "app_upgrade_failed": "No se pudo actualizar {app:s}: {error}", "app_upgraded": "Actualizado {app:s}", "appslist_fetched": "Lista de aplicaciones «{appslist:s}» actualizada", "appslist_removed": "Eliminada la lista de aplicaciones «{appslist:s}»", @@ -658,5 +658,29 @@ "diagnosis_ignored_issues": "(+ {nb_ignored} problema(s) ignorado(s))", "diagnosis_found_errors": "¡Encontrado(s) error(es) significativo(s) {errors} relacionado(s) con {category}!", "diagnosis_found_warnings": "Encontrado elemento(s) {warnings} que puede(n) ser mejorado(s) para {category}.", - "diagnosis_everything_ok": "¡Todo se ve bien para {category}!" + "diagnosis_everything_ok": "¡Todo se ve bien para {category}!", + "app_upgrade_script_failed": "Ha ocurrido un error en el script de actualización de la app", + "diagnosis_no_cache": "Todavía no hay una caché de diagnóstico para la categoría '{category}'", + "diagnosis_ip_no_ipv4": "IPv4 en el servidor no está funcionando.", + "diagnosis_ip_not_connected_at_all": "¿¡Está conectado el servidor a internet!?", + "diagnosis_ip_broken_resolvconf": "DNS parece no funcionar en tu servidor, lo que parece estar relacionado con /etc/resolv.conf no apuntando a 127.0.0.1.", + "diagnosis_dns_missing_record": "Según la configuración DNS recomendada, deberías añadir un registro DNS de tipo {0}, nombre {1} y valor {2}. Puedes consultar https://yunohost.org/dns_config para más información.", + "diagnosis_diskusage_low": "El almacenamiento {mountpoint} (en dispositivo {device}) solo tiene {free_abs_GB} GB ({free_percent}%) de espacio disponible. Ten cuidado.", + "diagnosis_services_bad_status_tip": "Puedes intentar reiniciar el servicio, y si no funciona, echar un vistazo a los logs del servicio usando 'yunohost service log {0}' o a través de la sección 'Servicios' en webadmin.", + "diagnosis_ip_connected_ipv6": "¡El servidor está conectado a internet a través de IPv6!", + "diagnosis_ip_no_ipv6": "IPv6 en el servidor no está funcionando.", + "diagnosis_ip_dnsresolution_working": "¡DNS no está funcionando!", + "diagnosis_ip_broken_dnsresolution": "DNS parece que no funciona por alguna razón... ¿Hay algún firewall bloqueando peticiones DNS?", + "diagnosis_ip_weird_resolvconf": "Parece que DNS funciona, pero ten cuidado, porque estás utilizando /etc/resolv.conf modificado.", + "diagnosis_ip_weird_resolvconf_details": "En su lugar, este fichero debería ser un enlace simbólico a /etc/resolvconf/run/resolv.conf apuntando a 127.0.0.1 (dnsmasq). Los resolvedores reales deben configurarse a través de /etc/resolv.dnsmasq.conf.", + "diagnosis_dns_good_conf": "Buena configuración DNS para el dominio {domain} (categoría {category})", + "diagnosis_dns_bad_conf": "Mala configuración DNS o configuración DNS faltante para el dominio {domain} (categoría {category})", + "diagnosis_dns_discrepancy": "El registro DNS con tipo {0} y nombre {1} no se corresponde a la configuración recomendada. Valor actual: {2}. Valor esperado: {3}. Puedes consultar https://yunohost.org/dns_config para más información.", + "diagnosis_services_bad_status": "El servicio {service} está {status} :(", + "diagnosis_diskusage_verylow": "El almacenamiento {mountpoint} (en el dispositivo {device}) sólo tiene {free_abs_GB} GB ({free_percent}%) de espacio disponible. Deberías considerar la posibilidad de limpiar algo de espacio.", + "diagnosis_diskusage_ok": "¡El almacenamiento {mountpoint} (en el dispositivo {device}) todavía tiene {free_abs_GB} GB ({free_percent}%) de espacio libre!", + "diagnosis_services_conf_broken": "¡Mala configuración para el servicio {service}!", + "diagnosis_services_running": "¡El servicio {service} está en ejecución!", + "diagnosis_failed": "No se ha podido obtener el resultado del diagnóstico para la categoría '{category}': {error}", + "diagnosis_ip_connected_ipv4": "¡El servidor está conectado a internet a través de IPv4!" } From 1fac8bf5b7d51343e1f9d83f991b6a4d6da51a68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quent=C3=AD?= Date: Sat, 28 Dec 2019 17:45:54 +0000 Subject: [PATCH 0709/3170] Translated using Weblate (Occitan) Currently translated at 49.9% (303 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/oc/ --- locales/oc.json | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/locales/oc.json b/locales/oc.json index 8d095e079..ce0a0ed94 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -6,15 +6,15 @@ "app_already_up_to_date": "{app:s} es ja a jorn", "installation_complete": "Installacion acabada", "app_id_invalid": "ID d’aplicacion incorrècte", - "app_install_files_invalid": "Fichièrs d’installacion incorrèctes", + "app_install_files_invalid": "Installacion impossibla d’aquestes fichièrs", "app_no_upgrade": "Pas cap d’aplicacion d’actualizar", "app_not_correctly_installed": "{app:s} sembla pas ben installat", - "app_not_installed": "L’aplicacion {app:s} es pas installat. Vaquí la lista de las aplicacions installadas : {all_apps}", + "app_not_installed": "Impossible de trobar l’aplicacion {app:s}. Vaquí la lista de las aplicacions installadas : {all_apps}", "app_not_properly_removed": "{app:s} es pas estat corrèctament suprimit", - "app_removed": "{app:s} es estat suprimit", + "app_removed": "{app:s} es estada suprimida", "app_unknown": "Aplicacion desconeguda", "app_upgrade_app_name": "Actualizacion de l’aplicacion {app}…", - "app_upgrade_failed": "Impossible d’actualizar {app:s}", + "app_upgrade_failed": "Impossible d’actualizar {app:s} : {error}", "app_upgrade_some_app_failed": "D’aplicacions se pòdon pas actualizar", "app_upgraded": "{app:s} es estada actualizada", "appslist_fetched": "Recuperacion de la lista d’aplicacions {appslist:s} corrèctament realizada", @@ -36,11 +36,11 @@ "backup_action_required": "Devètz precisar çò que cal salvagardar", "backup_app_failed": "Impossible de salvagardar l’aplicacion « {app:s} »", "backup_applying_method_copy": "Còpia de totes los fichièrs dins la salvagarda…", - "backup_applying_method_tar": "Creacion de l’archiu tar de la salvagarda…", - "backup_archive_name_exists": "Un archiu de salvagarda amb aquesta nom existís ja", + "backup_applying_method_tar": "Creacion de l’archiu TAR de la salvagarda…", + "backup_archive_name_exists": "Un archiu de salvagarda amb aquesta nom existís ja.", "backup_archive_name_unknown": "L’archiu local de salvagarda apelat « {name:s} » es desconegut", "action_invalid": "Accion « {action:s} » incorrècta", - "app_argument_choice_invalid": "Causida invalida pel paramètre « {name:s} », cal que siá un de {choices:s}", + "app_argument_choice_invalid": "Utilizatz una de las opcions « {choices:s} » per l’argument « {name:s} »", "app_argument_invalid": "Causissètz una valor invalida pel paramètre « {name:s} » : {error:s}", "app_argument_required": "Lo paramètre « {name:s} » es requesit", "app_change_url_failed_nginx_reload": "Reaviada de NGINX impossibla. Vaquí la sortida de « nginx -t » :\n{nginx_errors:s}", @@ -50,14 +50,14 @@ "app_extraction_failed": "Extraccion dels fichièrs d’installacion impossibla", "app_incompatible": "L’aplicacion {app} es pas compatibla amb vòstra version de YunoHost", "app_location_already_used": "L’aplicacion « {app} » es ja installada dins ({path})", - "app_manifest_invalid": "Manifest d’aplicacion incorrècte : {error}", + "app_manifest_invalid": "I a quicòm que truca amb lo manifest de l’aplicacion : {error}", "app_package_need_update": "Lo paquet de l’aplicacion {app} deu èsser actualizat per poder seguir los cambiaments de YunoHost", "app_requirements_checking": "Verificacion dels paquets requesits per {app}…", "app_sources_fetch_failed": "Recuperacion dels fichièrs fonts impossibla, l’URL es corrècta ?", "app_unsupported_remote_type": "Lo tipe alonhat utilizat per l’aplicacion es pas suportat", "appslist_retrieve_error": "Impossible de recuperar la lista d’aplicacions alonhadas {appslist:s} : {error:s}", "backup_archive_app_not_found": "L’aplicacion « {app:s} » es pas estada trobada dins l’archiu de la salvagarda", - "backup_archive_broken_link": "Impossible d‘accedir a l’archiu de salvagarda (ligam invalid cap a {path:s})", + "backup_archive_broken_link": "Impossible d’accedir a l’archiu de salvagarda (ligam invalid cap a {path:s})", "backup_archive_mount_failed": "Lo montatge de l’archiu de salvagarda a fracassat", "backup_archive_open_failed": "Impossible de dobrir l’archiu de salvagarda", "backup_archive_system_part_not_available": "La part « {part:s} » del sistèma es pas disponibla dins aquesta salvagarda", @@ -65,7 +65,7 @@ "backup_copying_to_organize_the_archive": "Còpia de {size:s} Mio per organizar l’archiu", "backup_created": "Salvagarda acabada", "backup_creating_archive": "Creacion de l’archiu de salvagarda…", - "backup_creation_failed": "Impossible de crear la salvagarda", + "backup_creation_failed": "Creacion impossibla de l’archiu de salvagarda", "app_already_installed_cant_change_url": "Aquesta aplicacion es ja installada. Aquesta foncion pòt pas simplament cambiar l’URL. Agachatz « app changeurl » s’es disponible.", "app_change_no_change_url_script": "L’aplicacion {app_name:s} pren pas en compte lo cambiament d’URL, poiretz aver de l’actualizar.", "app_change_url_no_script": "L’aplicacion {app_name:s} pren pas en compte lo cambiament d’URL, benlèu que vos cal l’actualizar.", @@ -73,19 +73,19 @@ "app_location_install_failed": "Impossible d’installar l’aplicacion a aqueste emplaçament per causa de conflicte amb l’aplicacion {other_app} qu’es ja installada sus {other_path}", "app_location_unavailable": "Aquesta URL es pas disponibla o en conflicte amb una aplicacion existenta :\n{apps:s}", "appslist_corrupted_json": "Cargament impossible de la lista d’aplicacion. Sembla que {filename:s} siá gastat.", - "backup_delete_error": "Impossible de suprimir « {path:s} »", + "backup_delete_error": "Supression impossibla de « {path:s} »", "backup_deleted": "La salvagarda es estada suprimida", "backup_hook_unknown": "Script de salvagarda « {hook:s} » desconegut", - "backup_invalid_archive": "Archiu de salvagarda incorrècte", + "backup_invalid_archive": "Aquò es pas un archiu de salvagarda", "backup_method_borg_finished": "La salvagarda dins Borg es acabada", "backup_method_copy_finished": "La còpia de salvagarda es acabada", - "backup_method_tar_finished": "L’archiu tar de la salvagarda es estat creat", + "backup_method_tar_finished": "L’archiu TAR de la salvagarda es estat creat", "backup_output_directory_not_empty": "Lo dorsièr de sortida es pas void", "backup_output_directory_required": "Vos cal especificar un dorsièr de sortida per la salvagarda", "backup_running_app_script": "Lançament de l’escript de salvagarda de l’aplicacion « {app:s} »...", "backup_running_hooks": "Execucion dels scripts de salvagarda…", "backup_system_part_failed": "Impossible de salvagardar la part « {part:s} » del sistèma", - "app_requirements_failed": "Impossible de complir las condicions requesidas per {app} : {error}", + "app_requirements_failed": "Impossible de complir unas condicions requesidas per {app} : {error}", "app_requirements_unmeet": "Las condicions requesidas per {app} son pas complidas, lo paquet {pkgname} ({version}) deu èsser {spec}", "appslist_could_not_migrate": "Migracion de la lista impossibla {appslist:s} ! Impossible d’analizar l’URL… L’anciana tasca cron es estada servada dins {bkp_file:s}.", "backup_abstract_method": "Aqueste metòde de salvagarda es pas encara implementat", @@ -399,7 +399,7 @@ "network_check_smtp_ko": "Lo trafic de corrièl sortent (pòrt 25 SMTP) sembla blocat per vòstra ret", "network_check_smtp_ok": "Lo trafic de corrièl sortent (pòrt 25 SMTP) es pas blocat", "pattern_mailbox_quota": "Deu èsser una talha amb lo sufixe b/k/M/G/T o 0 per desactivar la quòta", - "backup_archive_writing_error": "Impossible d’ajustar los fichièrs a la salvagarda dins l’archiu comprimit", + "backup_archive_writing_error": "Impossible d’ajustar los fichièrs « {source:s} » a la salvagarda (nomenats dins l’archiu « {dest:s} »)dins l’archiu comprimit « {archive:s} »", "backup_cant_mount_uncompress_archive": "Impossible de montar en lectura sola lo repertòri de l’archiu descomprimit", "backup_no_uncompress_archive_dir": "Lo repertòri de l’archiu descomprimit existís pas", "pattern_username": "Deu èsser compausat solament de caractèrs alfanumerics en letras minusculas e de tirets basses", @@ -493,7 +493,7 @@ "service_conf_now_managed_by_yunohost": "Lo fichièr de configuracion « {conf} » es ara gerit per YunoHost.", "service_reloaded": "Lo servici « {servici:s} » es estat tornat cargar", "already_up_to_date": "I a pas res a far ! Tot es ja a jorn !", - "app_action_cannot_be_ran_because_required_services_down": "Aquesta aplicacion necessita unes servicis que son actualament encalats. Abans de contunhar deuriatz ensajar de reaviar los servicis seguents (e tanben cercar perque son tombats en pana) : {services}", + "app_action_cannot_be_ran_because_required_services_down": "Aquestas aplicacions necessitan d’èsser lançadas per poder executar aquesta accion : {services}. Abans de contunhar deuriatz ensajar de reaviar los servicis seguents (e tanben cercar perque son tombats en pana) : {services}", "confirm_app_install_warning": "Atencion : aquesta aplicacion fonciona mas non es pas ben integrada amb YunoHost. Unas foncionalitats coma l’autentificacion unica e la còpia de seguretat/restauracion pòdon èsser indisponiblas. volètz l’installar de totas manièras ? [{answers:s}] ", "confirm_app_install_danger": "ATENCION ! Aquesta aplicacion es encara experimentala (autrament dich, fonciona pas) e es possible que còpe lo sistèma ! Deuriatz PAS l’installar se non sabètz çò que fasètz. Volètz vertadièrament córrer aqueste risc ? [{answers:s}] ", "confirm_app_install_thirdparty": "ATENCION ! L’installacion d’aplicacions tèrças pòt comprometre l’integralitat e la seguretat del sistèma. Deuriatz PAS l’installar se non sabètz pas çò que fasètz. Volètz vertadièrament córrer aqueste risc ? [{answers:s}] ", @@ -705,5 +705,8 @@ "diagnosis_failed": "Recuperacion impossibla dels resultats del diagnostic per la categoria « {category} » : {error}", "diagnosis_ip_broken_dnsresolution": "La resolucion del nom de domeni es copada per una rason… Lo parafuòc bloca las requèstas DNS ?", "diagnosis_no_cache": "I a pas encara de diagnostic de cache per la categoria « {category} »", - "apps_catalog_init_success": "Sistèma de catalòg d’aplicacion iniciat !" + "apps_catalog_init_success": "Sistèma de catalòg d’aplicacion iniciat !", + "diagnosis_services_running": "Lo servici {service} es lançat !", + "diagnosis_services_conf_broken": "La configuracion es copada pel servici {service} !", + "diagnosis_ports_needed_by": "Es necessari qu’aqueste pòrt siá accessible pel servici {0}" } From 556dd024ef3a7ce83bd3da69f409b2446ab23afe Mon Sep 17 00:00:00 2001 From: amirale qt Date: Mon, 30 Dec 2019 14:01:52 +0000 Subject: [PATCH 0710/3170] Translated using Weblate (Greek) Currently translated at 0.2% (1 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/el/ --- locales/el.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/locales/el.json b/locales/el.json index 0967ef424..615dfdd48 100644 --- a/locales/el.json +++ b/locales/el.json @@ -1 +1,3 @@ -{} +{ + "password_too_simple_1": "Ο κωδικός πρόσβασης πρέπει να έχει μήκος τουλάχιστον 8 χαρακτήρων" +} From 98ebf125019859f4079c3d07692ee80ba45be96d Mon Sep 17 00:00:00 2001 From: amirale qt Date: Mon, 30 Dec 2019 14:15:22 +0000 Subject: [PATCH 0711/3170] Translated using Weblate (Bengali (Bangladesh)) Currently translated at 0.2% (1 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/bn_BD/ --- locales/bn_BD.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/locales/bn_BD.json b/locales/bn_BD.json index 0967ef424..b5425128d 100644 --- a/locales/bn_BD.json +++ b/locales/bn_BD.json @@ -1 +1,3 @@ -{} +{ + "password_too_simple_1": "পাসওয়ার্ডটি কমপক্ষে 8 টি অক্ষরের দীর্ঘ হওয়া দরকার" +} From 6de98737aa7152d841883f6b834f6495bc08b47d Mon Sep 17 00:00:00 2001 From: Mario Date: Tue, 31 Dec 2019 15:29:37 +0000 Subject: [PATCH 0712/3170] Translated using Weblate (Italian) Currently translated at 19.4% (118 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/it/ --- locales/it.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/it.json b/locales/it.json index 722add678..9e26e145b 100644 --- a/locales/it.json +++ b/locales/it.json @@ -435,5 +435,6 @@ "migration_0003_not_jessie": "La distribuzione attuale non è Jessie!", "migration_0003_system_not_fully_up_to_date": "Il tuo sistema non è completamente aggiornato. Per favore prima esegui un aggiornamento normale prima di migrare a stretch.", "this_action_broke_dpkg": "Questa azione ha danneggiato dpkg/apt (i gestori di pacchetti del sistema)… Puoi provare a risolvere questo problema connettendoti via SSH ed eseguendo `sudo dpkg --configure -a`.", - "updating_app_lists": "Recupero degli aggiornamenti disponibili per le applicazioni…" + "updating_app_lists": "Recupero degli aggiornamenti disponibili per le applicazioni…", + "app_action_broke_system": "Questa azione sembra avere roto servizi importanti: {services}" } From 15543fbfe5be2eba9a6590d11d56b823837901a3 Mon Sep 17 00:00:00 2001 From: Jeroen Franssen Date: Fri, 10 Jan 2020 15:54:03 +0000 Subject: [PATCH 0713/3170] Translated using Weblate (Dutch) Currently translated at 7.4% (45 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/nl/ --- locales/nl.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/locales/nl.json b/locales/nl.json index df554f7e2..832ca4ea2 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -1,7 +1,7 @@ { "action_invalid": "Ongeldige actie '{action:s}'", "admin_password": "Administrator wachtwoord", - "admin_password_changed": "Het administratie wachtwoord is gewijzigd", + "admin_password_changed": "Het administratie wachtwoord werd gewijzigd", "app_already_installed": "{app:s} is al geïnstalleerd", "app_argument_invalid": "'{name:s}' bevat ongeldige waarde: {error:s}", "app_argument_required": "Het '{name:s}' moet ingevuld worden", @@ -139,5 +139,9 @@ "backup_extracting_archive": "Backup archief uitpakken...", "backup_hook_unknown": "backup hook '{hook:s}' onbekend", "backup_nothings_done": "Niets om op te slaan", - "password_too_simple_1": "Het wachtwoord moet minimaal 8 tekens lang zijn" + "password_too_simple_1": "Het wachtwoord moet minimaal 8 tekens lang zijn", + "already_up_to_date": "Er is niets te doen, alles is al up-to-date.", + "admin_password_too_long": "Gelieve een wachtwoord te kiezen met minder dan 127 karakters", + "app_action_cannot_be_ran_because_required_services_down": "De volgende diensten moeten actief zijn om deze actie uit te voeren: {services}. Probeer om deze te herstarten om verder te gaan (en om eventueel te onderzoeken waarom ze niet werken).", + "aborting": "Annulatie." } From 3cfdc2f9001dcb9506b467d04f49bfb22d182d96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quent=C3=AD?= Date: Thu, 2 Jan 2020 11:24:20 +0000 Subject: [PATCH 0714/3170] Translated using Weblate (Occitan) Currently translated at 70.5% (428 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/oc/ --- locales/oc.json | 137 +++++++++++++++++++++++++++--------------------- 1 file changed, 77 insertions(+), 60 deletions(-) diff --git a/locales/oc.json b/locales/oc.json index ce0a0ed94..da381c702 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -80,7 +80,7 @@ "backup_method_borg_finished": "La salvagarda dins Borg es acabada", "backup_method_copy_finished": "La còpia de salvagarda es acabada", "backup_method_tar_finished": "L’archiu TAR de la salvagarda es estat creat", - "backup_output_directory_not_empty": "Lo dorsièr de sortida es pas void", + "backup_output_directory_not_empty": "Devètz causir un dorsièr de sortida void", "backup_output_directory_required": "Vos cal especificar un dorsièr de sortida per la salvagarda", "backup_running_app_script": "Lançament de l’escript de salvagarda de l’aplicacion « {app:s} »...", "backup_running_hooks": "Execucion dels scripts de salvagarda…", @@ -116,23 +116,23 @@ "backup_applying_method_borg": "Mandadís de totes los fichièrs a la salvagarda dins lo repertòri borg-backup…", "backup_csv_creation_failed": "Creacion impossibla del fichièr CSV necessari a las operacions futuras de restauracion", "backup_extracting_archive": "Extraccion de l’archiu de salvagarda…", - "backup_output_symlink_dir_broken": "Avètz un ligam simbolic copat allòc de vòstre repertòri d’archiu « {path:s} ». Poiriatz aver una configuracion personalizada per salvagardar vòstras donadas sus un autre sistèma de fichièrs, en aquel cas, saique oblidèretz de montar o de connectar lo disc o la clau USB.", + "backup_output_symlink_dir_broken": "Vòstre repertòri d’archiu « {path:s} » es un ligam simbolic copat. Saique oblidèretz de re/montar o de connectar supòrt.", "backup_with_no_backup_script_for_app": "L’aplicacion {app:s} a pas cap de script de salvagarda. I fasèm pas cas.", "backup_with_no_restore_script_for_app": "L’aplicacion {app:s} a pas cap de script de restauracion, poiretz pas restaurar automaticament la salvagarda d’aquesta aplicacion.", - "certmanager_acme_not_configured_for_domain": "Lo certificat del domeni {domain:s} sembla pas corrèctament installat. Mercés de lançar d’en primièr cert-install per aqueste domeni.", + "certmanager_acme_not_configured_for_domain": "Lo certificat pel domeni {domain:s} sembla pas corrèctament installat. Mercés de lançar d’en primièr « cert-install » per aqueste domeni.", "certmanager_attempt_to_renew_nonLE_cert": "Lo certificat pel domeni {domain:s} es pas provesit per Let’s Encrypt. Impossible de lo renovar automaticament !", "certmanager_attempt_to_renew_valid_cert": "Lo certificat pel domeni {domain:s} es a man d’expirar ! (Podètz utilizar --force se sabètz çò que fasètz)", "certmanager_cannot_read_cert": "Quicòm a trucat en ensajar de dobrir lo certificat actual pel domeni {domain:s} (fichièr : {file:s}), rason : {reason:s}", - "certmanager_cert_install_success": "Installacion capitada del certificat Let’s Encrypt pel domeni {domain:s} !", - "certmanager_cert_install_success_selfsigned": "Installacion capitada del certificat auto-signat pel domeni {domain:s} !", - "certmanager_cert_signing_failed": "Fracàs de la signatura del nòu certificat", - "certmanager_domain_cert_not_selfsigned": "Lo certificat del domeni {domain:s} es pas auto-signat. Volètz vertadièrament lo remplaçar ? (Utiliatz --force)", - "certmanager_domain_dns_ip_differs_from_public_ip": "L’enregistrament DNS « A » del domeni {domain:s} es diferent de l’adreça IP d’aqueste servidor. Se fa pauc qu’avètz modificat l’enregistrament « A », mercés d’esperar l’espandiment (qualques verificadors d’espandiment son disponibles en linha). (Se sabètz çò que fasèm, utilizatz --no-checks per desactivar aqueles contraròtles)", - "certmanager_domain_http_not_working": "Sembla que lo domeni {domain:s} es pas accessible via HTTP. Mercés de verificar que las configuracions DNS e nginx son corrèctas", - "certmanager_domain_unknown": "Domeni desconegut {domain:s}", + "certmanager_cert_install_success": "Lo certificat Let’s Encrypt es ara installat pel domeni « {domain:s} »", + "certmanager_cert_install_success_selfsigned": "Lo certificat auto-signat es ara installat pel domeni « {domain:s} »", + "certmanager_cert_signing_failed": "Signatura impossibla del nòu certificat", + "certmanager_domain_cert_not_selfsigned": "Lo certificat pel domeni {domain:s} es pas auto-signat. Volètz vertadièrament lo remplaçar ? (Utilizatz « --force » per o far)", + "certmanager_domain_dns_ip_differs_from_public_ip": "L’enregistrament DNS « A » pel domeni {domain:s} es diferent de l’adreça IP d’aqueste servidor. Se fa pauc qu’avètz modificat l’enregistrament « A », mercés d’esperar l’espandiment (qualques verificadors d’espandiment son disponibles en linha). (Se sabètz çò que fasèm, utilizatz --no-checks per desactivar aqueles contraròtles)", + "certmanager_domain_http_not_working": "Sembla que lo domeni {domain:s} es pas accessible via HTTP. Mercés de verificar que las configuracions DNS e NGINK son corrèctas", + "certmanager_domain_unknown": "Domeni desconegut « {domain:s} »", "certmanager_no_cert_file": "Lectura impossibla del fichièr del certificat pel domeni {domain:s} (fichièr : {file:s})", - "certmanager_self_ca_conf_file_not_found": "Lo fichièr de configuracion per l’autoritat del certificat auto-signat es introbabla (fichièr : {file:s})", - "certmanager_unable_to_parse_self_CA_name": "Analisi impossible lo nom de l’autoritat del certificat auto-signat (fichièr : {file:s})", + "certmanager_self_ca_conf_file_not_found": "Impossible de trobar lo fichièr de configuracion per l’autoritat del certificat auto-signat (fichièr : {file:s})", + "certmanager_unable_to_parse_self_CA_name": "Analisi impossibla del nom de l’autoritat del certificat auto-signat (fichièr : {file:s})", "custom_app_url_required": "Cal que donetz una URL per actualizar vòstra aplicacion personalizada {app:s}", "custom_appslist_name_required": "Cal que nomenetz vòstra lista d’aplicacions personalizadas", "diagnosis_debian_version_error": "Impossible de determinar la version de Debian : {error}", @@ -141,10 +141,10 @@ "dnsmasq_isnt_installed": "dnsmasq sembla pas èsser installat, mercés de lançar « apt-get remove bind9 && apt-get install dnsmasq »", "domain_cannot_remove_main": "Impossible de levar lo domeni màger. Definissètz un novèl domeni màger d’en primièr", "domain_cert_gen_failed": "Generacion del certificat impossibla", - "domain_created": "Lo domeni es creat", - "domain_creation_failed": "Creacion del certificat impossibla", - "domain_deleted": "Lo domeni es suprimit", - "domain_deletion_failed": "Supression impossibla del domeni", + "domain_created": "Domeni creat", + "domain_creation_failed": "Creacion del domeni {domain}: impossibla", + "domain_deleted": "Domeni suprimit", + "domain_deletion_failed": "Supression impossibla del domeni {domini}: {error}", "domain_dyndns_invalid": "Domeni incorrècte per una utilizacion amb DynDNS", "domain_dyndns_root_unknown": "Domeni DynDNS màger desconegut", "domain_exists": "Lo domeni existís ja", @@ -156,33 +156,33 @@ "done": "Acabat", "downloading": "Telecargament…", "dyndns_could_not_check_provide": "Impossible de verificar se {provider:s} pòt provesir {domain:s}.", - "dyndns_cron_installed": "La tasca cron pel domeni DynDNS es installada", - "dyndns_cron_remove_failed": "Impossible de levar la tasca cron pel domeni DynDNS a causa de {error}", - "dyndns_cron_removed": "La tasca cron pel domeni DynDNS es levada", + "dyndns_cron_installed": "Tasca cron pel domeni DynDNS creada", + "dyndns_cron_remove_failed": "Impossible de levar la tasca cron pel domeni DynDNS : {error}", + "dyndns_cron_removed": "Tasca cron pel domeni DynDNS levada", "dyndns_ip_update_failed": "Impossible d’actualizar l’adreça IP sul domeni DynDNS", - "dyndns_ip_updated": "Vòstra adreça IP es estada actualizada pel domeni DynDNS", - "dyndns_key_generating": "La clau DNS es a se generar, pòt trigar una estona…", + "dyndns_ip_updated": "Vòstra adreça IP actualizada pel domeni DynDNS", + "dyndns_key_generating": "La clau DNS es a se generar… pòt trigar una estona.", "dyndns_key_not_found": "Clau DNS introbabla pel domeni", "dyndns_no_domain_registered": "Cap de domeni pas enregistrat amb DynDNS", - "dyndns_registered": "Lo domeni DynDNS es enregistrat", - "dyndns_registration_failed": "Enregistrament del domeni DynDNS impossibla : {error:s}", + "dyndns_registered": "Domeni DynDNS enregistrat", + "dyndns_registration_failed": "Enregistrament del domeni DynDNS impossible : {error:s}", "dyndns_domain_not_provided": "Lo provesidor DynDNS {provider:s} pòt pas fornir lo domeni {domain:s}.", "dyndns_unavailable": "Lo domeni {domain:s} es pas disponible.", "extracting": "Extraccion…", "field_invalid": "Camp incorrècte : « {:s} »", "format_datetime_short": "%d/%m/%Y %H:%M", "global_settings_cant_open_settings": "Fracàs de la dobertura del fichièr de configuracion, rason : {reason:s}", - "global_settings_key_doesnt_exists": "La clau « {settings_key:s} » existís pas dins las configuracions globalas, podètz veire totas las claus disponiblas en picant « yunohost settings list »", - "global_settings_reset_success": "Capitada ! Vòstra configuracion precedenta es estada salvagarda dins {path:s}", + "global_settings_key_doesnt_exists": "La clau « {settings_key:s} » existís pas dins las configuracions globalas, podètz veire totas las claus disponiblas en executant « yunohost settings list »", + "global_settings_reset_success": "Configuracion precedenta ara salvagarda dins {path:s}", "global_settings_setting_example_bool": "Exemple d’opcion booleana", "global_settings_unknown_setting_from_settings_file": "Clau desconeguda dins los paramètres : {setting_key:s}, apartada e salvagardada dins /etc/yunohost/settings-unknown.json", - "installation_failed": "Fracàs de l’installacion", + "installation_failed": "Quicòm a trucat e l’installacion a pas reüssit", "invalid_url_format": "Format d’URL pas valid", "ldap_initialized": "L’annuari LDAP es inicializat", "license_undefined": "indefinida", "maindomain_change_failed": "Modificacion impossibla del domeni màger", "maindomain_changed": "Lo domeni màger es estat modificat", - "migrate_tsig_end": "La migracion cap a hmac-sha512 es acabada", + "migrate_tsig_end": "La migracion cap a HMAC-SHA512 es acabada", "migrate_tsig_wait_2": "2 minutas…", "migrate_tsig_wait_3": "1 minuta…", "migrate_tsig_wait_4": "30 segondas…", @@ -192,7 +192,7 @@ "migration_0003_start": "Aviada de la migracion cap a Stretech. Los jornals seràn disponibles dins {logfile}.", "migration_0003_patching_sources_list": "Petaçatge de sources.lists…", "migration_0003_main_upgrade": "Aviada de la mesa a nivèl màger…", - "migration_0003_fail2ban_upgrade": "Aviada de la mesa a nivèl de fail2ban…", + "migration_0003_fail2ban_upgrade": "Aviada de la mesa a nivèl de Fail2Ban…", "migration_0003_not_jessie": "La distribucion Debian actuala es pas Jessie !", "migrations_cant_reach_migration_file": "Impossible d’accedir als fichièrs de migracion amb lo camin %s", "migrations_current_target": "La cibla de migracion est {}", @@ -228,13 +228,13 @@ "port_unavailable": "Lo pòrt {port:d} es pas disponible", "restore_already_installed_app": "Una aplicacion es ja installada amb l’id « {app:s} »", "restore_app_failed": "Impossible de restaurar l’aplicacion « {app:s} »", - "backup_ask_for_copying_if_needed": "D’unes fichièrs an pas pogut èsser preparatz per la salvagarda en utilizar lo metòde qu’evita de gastar d’espaci sul sistèma de manièra temporària. Per lançar la salvagarda, cal utilizar temporàriament {size:s} Mo. Acceptatz ?", + "backup_ask_for_copying_if_needed": "Volètz far una salvagarda en utilizant {size:s} Mo temporàriament ? (Aqueste biais de far es emplegat perque unes fichièrs an pas pogut èsser preparats amb un metòde mai eficaç.)", "yunohost_not_installed": "YunoHost es pas installat o corrèctament installat. Mercés d’executar « yunohost tools postinstall »", - "backup_output_directory_forbidden": "Repertòri de destinacion defendut. Las salvagardas pòdon pas se realizar dins los repertòris bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var ou /home/yunohost.backup/archives", + "backup_output_directory_forbidden": "Causissètz un repertòri de destinacion deferent. Las salvagardas pòdon pas se realizar dins los repertòris bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var ou /home/yunohost.backup/archives", "certmanager_attempt_to_replace_valid_cert": "Sètz a remplaçar un certificat corrècte e valid pel domeni {domain:s} ! (Utilizatz --force per cortcircuitar)", - "certmanager_cert_renew_success": "Renovèlament capitat d’un certificat Let’s Encrypt pel domeni {domain:s} !", - "certmanager_certificate_fetching_or_enabling_failed": "Sembla d’aver fracassat l’activacion d’un nòu certificat per {domain:s}…", - "certmanager_conflicting_nginx_file": "Impossible de preparar lo domeni pel desfís ACME : lo fichièr de configuracion nginx {filepath:s} es en conflicte e deu èsser levat d’en primièr", + "certmanager_cert_renew_success": "Renovèlament capitat d’un certificat Let’s Encrypt pel domeni « {domain:s} »", + "certmanager_certificate_fetching_or_enabling_failed": "Sembla qu’utilizar lo nòu certificat per {domain:s} fonciona pas…", + "certmanager_conflicting_nginx_file": "Impossible de preparar lo domeni pel desfís ACME : lo fichièr de configuracion NGINX {filepath:s} es en conflicte e deu èsser levat d’en primièr", "certmanager_couldnt_fetch_intermediate_cert": "Expiracion del relambi pendent l’ensag de recuperacion del certificat intermediari dins de Let’s Encrypt. L’installacion / lo renovèlament es estat interromput - tornatz ensajar mai tard.", "certmanager_domain_not_resolved_locally": "Lo domeni {domain:s} pòt pas èsser determinat dins de vòstre servidor YunoHost. Pòt arribar s’avètz recentament modificat vòstre enregistrament DNS. Dins aqueste cas, mercés d’esperar unas oras per l’espandiment. Se lo problèma dura, consideratz ajustar {domain:s} a /etc/hosts. (Se sabètz çò que fasètz, utilizatz --no-checks per desactivar las verificacions.)", "certmanager_error_no_A_record": "Cap d’enregistrament DNS « A » pas trobat per {domain:s}. Vos cal indicar que lo nom de domeni mene a vòstra maquina per poder installar un certificat Let’S Encrypt ! (Se sabètz çò que fasètz, utilizatz --no-checks per desactivar las verificacions.)", @@ -245,25 +245,25 @@ "domain_dyndns_dynette_is_unreachable": "Impossible de contactar la dynette YunoHost, siá YunoHost pas es pas corrèctament connectat a Internet, siá lo servidor de la dynett es arrestat. Error : {error}", "domain_uninstall_app_first": "Una o mantuna aplicacions son installadas sus aqueste domeni. Mercés de las desinstallar d’en primièr abans de suprimir aqueste domeni", "firewall_reload_failed": "Impossible de recargar lo parafuòc", - "firewall_reloaded": "Lo parafuòc es estat recargat", + "firewall_reloaded": "Parafuòc recargat", "firewall_rules_cmd_failed": "Unas règlas del parafuòc an fracassat. Per mai informacions, consultatz lo jornal.", "global_settings_bad_choice_for_enum": "La valor del paramètre {setting:s} es incorrècta. Recebut : {received_type:s}, mas las opcions esperadas son : {expected_type:s}", - "global_settings_bad_type_for_setting": "Lo tipe del paramètre {setting:s} es incorrècte. Recebut : {received_type:s}, esperat {expected_type:s}", + "global_settings_bad_type_for_setting": "Lo tipe del paramètre {setting:s} es incorrècte, recebut : {received_type:s}, esperat {expected_type:s}", "global_settings_cant_write_settings": "Fracàs de l’escritura del fichièr de configuracion, rason : {reason:s}", "global_settings_setting_example_enum": "Exemple d’opcion de tipe enumeracion", "global_settings_setting_example_int": "Exemple d’opcion de tipe entièr", "global_settings_setting_example_string": "Exemple d’opcion de tipe cadena", "global_settings_unknown_type": "Situacion inesperada, la configuracion {setting:s} sembla d’aver lo tipe {unknown_type:s} mas es pas un tipe pres en carga pel sistèma.", - "hook_exec_failed": "Fracàs de l’execucion del script « {path:s} »", - "hook_exec_not_terminated": "L’execucion del escript « {path:s} » es pas acabada", + "hook_exec_failed": "Fracàs de l’execucion del script : « {path:s} »", + "hook_exec_not_terminated": "Lo escript « {path:s} » a pas acabat corrèctament", "hook_list_by_invalid": "La proprietat de tria de las accions es invalida", "hook_name_unknown": "Nom de script « {name:s} » desconegut", "ldap_init_failed_to_create_admin": "L’inicializacion de LDAP a pas pogut crear l’utilizaire admin", "mail_domain_unknown": "Lo domeni de corrièl « {domain:s} » es desconegut", "mailbox_used_space_dovecot_down": "Lo servici corrièl Dovecot deu èsser aviat, se volètz conéisser l’espaci ocupat per la messatjariá", - "migrate_tsig_failed": "La migracion del domeni dyndns {domain} cap a hmac-sha512 a pas capitat, anullacion de las modificacions. Error : {error_code} - {error}", - "migrate_tsig_wait": "Esperem 3 minutas que lo servidor dyndns prenga en compte la novèla clau…", - "migrate_tsig_not_needed": "Sembla qu’utilizatz pas un domeni dyndns, donc cap de migracion es pas necessària !", + "migrate_tsig_failed": "La migracion del domeni DynDNS {domain} cap a HMAC-SHA512 a pas capitat, anullacion de las modificacions. Error : {error_code} - {error}", + "migrate_tsig_wait": "Esperem 3 minutas que lo servidor DynDNS prenga en compte la novèla clau…", + "migrate_tsig_not_needed": "Sembla qu’utilizatz pas un domeni DynDNS, donc cap de migracion es pas necessària.", "migration_0003_yunohost_upgrade": "Aviada de la mesa a nivèl del paquet YunoHost… La migracion acabarà, mas l’actualizacion reala se realizarà tot bèl aprèp. Un còp acabada, poiretz vos reconnectar a l’administracion web.", "migration_0003_system_not_fully_up_to_date": "Lo sistèma es pas complètament a jorn. Mercés de lançar una mesa a jorn classica abans de començar la migracion per Stretch.", "migration_0003_modified_files": "Mercés de notar que los fichièrs seguents son estats detectats coma modificats manualament e poiràn èsser escafats a la fin de la mesa a nivèl : {manually_modified_files}", @@ -369,7 +369,7 @@ "update_cache_failed": "Impossible d’actualizar lo cache de l’APT", "mail_alias_remove_failed": "Supression impossibla de l’alias de corrièl « {mail:s} »", "mail_forward_remove_failed": "Supression impossibla del corrièl de transferiment « {mail:s} »", - "migrate_tsig_start": "L’algorisme de generacion de claus es pas pro securizat per la signatura TSIG del domeni « {domain} », lançament de la migracion cap a hmac-sha512 que’s mai securizat", + "migrate_tsig_start": "L’algorisme de generacion de claus es pas pro securizat per la signatura TSIG del domeni « {domain} », lançament de la migracion cap a un mai segur HMAC-SHA-512", "migration_description_0001_change_cert_group_to_sslcert": "Càmbia las permissions de grop dels certificats de « metronome » per « ssl-cert »", "migration_0003_restoring_origin_nginx_conf": "Vòstre fichièr /etc/nginx/nginx.conf es estat modificat manualament. La migracion reïnicializarà d’en primièr son estat origina… Lo fichièr precedent serà disponible coma {backup_dest}.", "migration_0003_still_on_jessie_after_main_upgrade": "Quicòm a trucat pendent la mesa a nivèl màger : lo sistèma es encara jos Jessie ?!? Per trobar lo problèma, agachatz {log}…", @@ -404,11 +404,11 @@ "backup_no_uncompress_archive_dir": "Lo repertòri de l’archiu descomprimit existís pas", "pattern_username": "Deu èsser compausat solament de caractèrs alfanumerics en letras minusculas e de tirets basses", "experimental_feature": "Atencion : aquesta foncionalitat es experimentala e deu pas èsser considerada coma establa, deuriatz pas l’utilizar levat que sapiatz çò que fasètz.", - "log_corrupted_md_file": "Lo fichièr yaml de metadonada amb los jornals d’audit es corromput : « {md_file} »\nError : {error:s}", + "log_corrupted_md_file": "Lo fichièr YAML de metadonadas ligat als jornals d’audit es damatjat : « {md_file} »\nError : {error:s}", "log_category_404": "La categoria de jornals d’audit « {category} » existís pas", "log_link_to_log": "Jornal complèt d’aquesta operacion : {desc}", "log_help_to_get_log": "Per veire lo jornal d’aquesta operacion « {desc} », utilizatz la comanda « yunohost log display {name} »", - "backup_php5_to_php7_migration_may_fail": "Impossible de convertir vòstre archiu per prendre en carga PHP 7, la restauracion de vòstras aplicacions PHP pòt reüssir pas (rason : {error:s})", + "backup_php5_to_php7_migration_may_fail": "Impossible de convertir vòstre archiu per prendre en carga PHP 7, la restauracion de vòstras aplicacions PHP pòt reüssir pas a restaurar vòstras aplicacions PHP (rason : {error:s})", "log_link_to_failed_log": "L’operacion « {desc} » a pas capitat ! Per obténer d’ajuda, mercés de fornir lo jornal complèt de l’operacion", "log_help_to_get_failed_log": "L’operacion « {desc} » a pas reüssit ! Per obténer d’ajuda, mercés de partejar lo jornal d’audit complèt d’aquesta operacion en utilizant la comanda « yunohost log display {name} --share »", "log_does_exists": "I a pas cap de jornal d’audit per l’operacion amb lo nom « {log} », utilizatz « yunohost log list » per veire totes los jornals d’operacion disponibles", @@ -421,7 +421,7 @@ "log_app_change_url": "Cambiar l’URL de l’aplicacion « {} »", "log_app_install": "Installar l’aplicacion « {} »", "log_app_remove": "Levar l’aplicacion « {} »", - "log_app_upgrade": "Actualizacion de l’aplicacion « {} »", + "log_app_upgrade": "Actualizar l’aplicacion « {} »", "log_app_makedefault": "Far venir « {} » l’aplicacion per defaut", "log_available_on_yunopaste": "Lo jornal es ara disponible via {url}", "log_backup_restore_system": "Restaurar lo sistèma a partir d’una salvagarda", @@ -432,15 +432,20 @@ "log_domain_remove": "Tirar lo domeni « {} » d’a la configuracion sistèma", "log_dyndns_subscribe": "S’abonar al subdomeni YunoHost « {} »", "log_dyndns_update": "Actualizar l’adreça IP ligada a vòstre jos-domeni YunoHost « {} »", - "log_letsencrypt_cert_install": "Installar lo certificat Let's encrypt sul domeni « {} »", + "log_letsencrypt_cert_install": "Installar un certificat Let's Encrypt sul domeni « {} »", "log_selfsigned_cert_install": "Installar lo certificat auto-signat sul domeni « {} »", - "log_letsencrypt_cert_renew": "Renovar lo certificat Let's encrypt de « {} »", + "log_letsencrypt_cert_renew": "Renovar lo certificat Let's Encrypt de « {} »", "log_service_enable": "Activar lo servici « {} »", "log_service_regen_conf": "Regenerar la configuracion sistèma de « {} »", "log_user_create": "Ajustar l’utilizaire « {} »", "log_user_delete": "Levar l’utilizaire « {} »", +<<<<<<< HEAD "log_user_update": "Actualizar las informacions a l’utilizaire « {} »", "log_tools_maindomain": "Far venir « {} » lo domeni màger", +======= + "log_user_update": "Actualizar las informacions de l’utilizaire « {} »", + "log_domain_main_domain": "Far venir « {} » lo domeni màger", +>>>>>>> b968dff2... Translated using Weblate (Occitan) "log_tools_migrations_migrate_forward": "Migrar", "log_tools_migrations_migrate_backward": "Tornar en arrièr", "log_tools_postinstall": "Realizar la post installacion del servidor YunoHost", @@ -449,8 +454,8 @@ "log_tools_reboot": "Reaviar lo servidor", "mail_unavailable": "Aquesta adreça electronica es reservada e deu èsser automaticament atribuida al tot bèl just primièr utilizaire", "migration_description_0004_php5_to_php7_pools": "Tornar configurar lo pools PHP per utilizar PHP 7 allòc del 5", - "migration_description_0005_postgresql_9p4_to_9p6": "Migracion de las basas de donadas de postgresql 9.4 cap a 9.6", - "migration_0005_postgresql_94_not_installed": "Postgresql es pas installat sul sistèma. Pas res de far !", + "migration_description_0005_postgresql_9p4_to_9p6": "Migracion de las basas de donadas de PostgreSQL9.4 cap a 9.6", + "migration_0005_postgresql_94_not_installed": "PostgreSQL es pas installat sul sistèma. I a pas res per far.", "migration_0005_postgresql_96_not_installed": "Avèm trobat que Postgresql 9.4 es installat, mas cap de version de Postgresql 9.6 pas trobada !? Quicòm d’estranh a degut arribar a vòstre sistèma :( …", "migration_0005_not_enough_space": "I a pas pro d’espaci disponible sus {path} per lançar la migracion d’aquela passa :(.", "recommend_to_add_first_user": "La post installacion es acabada, mas YunoHost fa besonh d’almens un utilizaire per foncionar coma cal. Vos cal n’ajustar un en utilizant la comanda « yunohost user create Date: Mon, 6 Jan 2020 07:34:40 +0000 Subject: [PATCH 0715/3170] Translated using Weblate (French) Currently translated at 100.0% (607 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 70a074120..83ee83c5a 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -420,12 +420,12 @@ "experimental_feature": "Attention : cette fonctionnalité est expérimentale et ne doit pas être considérée comme stable, vous ne devriez pas l’utiliser à moins que vous ne sachiez ce que vous faites.", "log_corrupted_md_file": "Le fichier YAML de métadonnées associé aux logs est corrompu : '{md_file}'\nErreur : {error}", "log_category_404": "Le journal de la catégorie '{category}' n’existe pas", - "log_link_to_log": "Journal historisé complet de cette opération : ' {desc} '", - "log_help_to_get_log": "Pour voir le journal historisé de cette opération '{desc}', utilisez la commande 'yunohost log display {name}'", + "log_link_to_log": "Journal complet de cette opération : ' {desc} '", + "log_help_to_get_log": "Pour voir le journal de cette opération '{desc}', utilisez la commande 'yunohost log display {name}'", "log_link_to_failed_log": "L’opération '{desc}' a échouée ! Pour obtenir de l’aide, merci de partager le journal de l'opération en cliquant ici", "backup_php5_to_php7_migration_may_fail": "Impossible de convertir votre archive pour prendre en charge PHP 7, vous pourriez ne plus pouvoir restaurer vos applications PHP (cause : {error:s})", "log_help_to_get_failed_log": "L’opération '{desc}' a échouée ! Pour obtenir de l’aide, merci de partager le journal de l'opération en utilisant la commande 'yunohost log display {name} --share'", - "log_does_exists": "Il n’existe pas de journal historisé de l’opération ayant pour nom '{log}', utiliser 'yunohost log list pour voir tous les fichiers de journaux historisés disponibles'", + "log_does_exists": "Il n’existe pas de journal de l’opération ayant pour nom '{log}', utiliser 'yunohost log list' pour voir tous les fichiers de journaux disponibles", "log_operation_unit_unclosed_properly": "L’opération ne s’est pas terminée correctement", "log_app_addaccess": "Ajouter l’accès à '{}'", "log_app_removeaccess": "Enlever l’accès à '{}'", @@ -437,7 +437,7 @@ "log_app_remove": "Enlever l’application '{}'", "log_app_upgrade": "Mettre à jour l’application '{}'", "log_app_makedefault": "Faire de '{}' l’application par défaut", - "log_available_on_yunopaste": "Le journal historisé est désormais disponible via {url}", + "log_available_on_yunopaste": "Le journal est désormais disponible via {url}", "log_backup_restore_system": "Restaurer le système depuis une archive de sauvegarde", "log_backup_restore_app": "Restaurer '{}' depuis une sauvegarde", "log_remove_on_failed_restore": "Retirer '{}' après un échec de restauration depuis une archive de sauvegarde", From ddae40e716d9ec664e54fb67e6be6bfc9586b1d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Tue, 14 Jan 2020 01:04:20 +0000 Subject: [PATCH 0716/3170] =?UTF-8?q?Translated=20using=20Weblate=20(Norwe?= =?UTF-8?q?gian=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 17.6% (107 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/nb_NO/ --- locales/nb_NO.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/locales/nb_NO.json b/locales/nb_NO.json index 4fe62eebb..f15388941 100644 --- a/locales/nb_NO.json +++ b/locales/nb_NO.json @@ -165,5 +165,7 @@ "log_link_to_log": "Full logg for denne operasjonen: '{desc}'", "log_help_to_get_log": "For å vise loggen for operasjonen '{desc}', bruk kommandoen 'yunohost log display {name}'", "log_app_clearaccess": "Fjern all tilgang til '{}'", - "log_user_create": "Legg til '{}' bruker" + "log_user_create": "Legg til '{}' bruker", + "app_change_url_success": "{app:s} nettadressen er nå {domain:s}{path:s}", + "app_install_failed": "Kunne ikke installere {app}: {error}" } From 8229973a8366c27e1c80bfb69677055196c29bef Mon Sep 17 00:00:00 2001 From: Patrick Baeumel Date: Sun, 12 Jan 2020 10:37:39 +0000 Subject: [PATCH 0717/3170] Translated using Weblate (German) Currently translated at 33.8% (205 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/de.json b/locales/de.json index 4d6fdbff6..c115e0573 100644 --- a/locales/de.json +++ b/locales/de.json @@ -430,5 +430,6 @@ "apps_catalog_failed_to_download": "Der {apps_catalog} Apps-Katalog kann nicht heruntergeladen werden: {error}", "apps_catalog_obsolete_cache": "Der Cache des Apps-Katalogs ist leer oder veraltet.", "apps_catalog_update_success": "Der Apps-Katalog wurde aktualisiert!", - "password_too_simple_1": "Das Passwort muss mindestens 8 Zeichen lang sein" + "password_too_simple_1": "Das Passwort muss mindestens 8 Zeichen lang sein", + "diagnosis_display_tip_cli": "Sie können 'yunohost diagnosis show --issues' ausführen, um die gefundenen Probleme anzuzeigen." } From 4a6ae2e2aef7c259a672c4effe5e09f1c8c709a6 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Mon, 30 Dec 2019 14:25:33 +0000 Subject: [PATCH 0718/3170] Translated using Weblate (Chinese (Simplified)) Currently translated at 0.2% (1 of 607 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/zh_Hans/ --- locales/zh_Hans.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/locales/zh_Hans.json b/locales/zh_Hans.json index 0967ef424..cb5d7002c 100644 --- a/locales/zh_Hans.json +++ b/locales/zh_Hans.json @@ -1 +1,3 @@ -{} +{ + "password_too_simple_1": "密码长度至少为8个字符" +} From a694619cc5c654d3409e1caae989bdb47f5f7356 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quent=C3=AD?= Date: Fri, 17 Jan 2020 12:05:49 +0000 Subject: [PATCH 0719/3170] Translated using Weblate (Occitan) Currently translated at 70.8% (430 of 607 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 da381c702..55f7a002a 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -725,5 +725,7 @@ "diagnosis_ip_weird_resolvconf": "La resolucion del nom de domeni sembla foncionar, mas siatz prudent en utilizant un fichièr /etc/resolv.con personalizat.", "diagnosis_diskusage_verylow": "Lo lòc d’emmagazinatge {mountpoint} (sul periferic {device}) a solament {free_abs_GB} Go ({free_percent}%). Deuriatz considerar de liberar un pauc d’espaci.", "global_settings_setting_pop3_enabled": "Activar lo protocòl POP3 pel servidor de corrièr", - "diagnosis_diskusage_ok": "Lo lòc d’emmagazinatge {mountpoint} (sul periferic {device}) a encara {free_abs_GB} Go ({free_percent}%) de liure !" + "diagnosis_diskusage_ok": "Lo lòc d’emmagazinatge {mountpoint} (sul periferic {device}) a encara {free_abs_GB} Go ({free_percent}%) de liure !", + "diagnosis_swap_none": "Lo sistèma a pas cap de memòria d’escambi. Auriatz de considerar d’ajustar almens 256 Mo d’escambi per evitar las situacions ont lo sistèma manca de memòria.", + "diagnosis_swap_notsomuch": "Lo sistèma a solament {total_MB} de memòria d’escambi. Auriatz de considerar d’ajustar almens 256 Mo d’escambi per evitar las situacions ont lo sistèma manca de memòria." } From 9141d6ec2d345f4cf8a7b5b2649c4f2b05ae40a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9lanie=20Chauvel?= Date: Fri, 17 Jan 2020 23:40:23 +0000 Subject: [PATCH 0720/3170] Translated using Weblate (French) Currently translated at 100.0% (610 of 610 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 83ee83c5a..6267d0d58 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -747,7 +747,7 @@ "diagnosis_services_running": "Le service {service} s'exécute correctement !", "diagnosis_services_conf_broken": "La configuration est cassée pour le service {service} !", "diagnosis_ports_needed_by": "Rendre ce port accessible est nécessaire pour le service {0}", - "diagnosis_ports_forwarding_tip": "Pour résoudre ce problème, vous devez probablement configurer la redirection de port sur votre routeur Internet, comme décrit dans https://yunohost.org/isp_box_config.", + "diagnosis_ports_forwarding_tip": "Pour résoudre ce problème, vous devez probablement configurer la redirection de port sur votre routeur Internet comme décrit sur https://yunohost.org/isp_box_config", "diagnosis_http_connection_error": "Erreur de connexion : impossible de se connecter au domaine demandé, il est probablement injoignable.", "diagnosis_no_cache": "Pas encore de diagnostique de cache pour la catégorie « {category} »", "diagnosis_http_unknown_error": "Une erreur est survenue en essayant de joindre votre domaine, il est probablement injoignable.", @@ -757,5 +757,8 @@ "diagnosis_services_bad_status_tip": "Vous pouvez essayer de redémarrer le service. Si cela ne fonctionne pas, consultez les journaux de service à l'aide de 'yunohost service log {0}' ou de la section 'Services' de l'administrateur Web.", "diagnosis_http_bad_status_code": "N'a pas pu atteindre votre serveur comme prévu, il a renvoyé un code d'état incorrect. Il se peut qu'une autre machine réponde à la place de votre serveur. Vérifiez que vous transférez correctement le port 80, que votre configuration nginx est à jour et qu’un proxy inverse n’interfère pas.", "diagnosis_http_timeout": "Expiration du délai en essayant de contacter votre serveur de l'extérieur. Il semble être inaccessible. Vérifiez que vous transférez correctement le port 80, que nginx est en cours d’exécution et qu’un pare-feu n’interfère pas.", - "global_settings_setting_pop3_enabled": "Activer le protocole POP3 pour le serveur de messagerie" + "global_settings_setting_pop3_enabled": "Activer le protocole POP3 pour le serveur de messagerie", + "log_app_action_run": "Lancer l’action de l’application '{}'", + "log_app_config_show_panel": "Montrer le panneau de configuration de l’application '{}'", + "log_app_config_apply": "Appliquer la configuration à l’application '{}'" } From e636971a027a3d8785d46944b58ffff4200af2de Mon Sep 17 00:00:00 2001 From: Armando FEMAT Date: Sat, 25 Jan 2020 12:35:02 +0000 Subject: [PATCH 0721/3170] Translated using Weblate (Spanish) Currently translated at 92.5% (564 of 610 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/es/ --- locales/es.json | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/locales/es.json b/locales/es.json index a64408411..15aca2662 100644 --- a/locales/es.json +++ b/locales/es.json @@ -682,5 +682,28 @@ "diagnosis_services_conf_broken": "¡Mala configuración para el servicio {service}!", "diagnosis_services_running": "¡El servicio {service} está en ejecución!", "diagnosis_failed": "No se ha podido obtener el resultado del diagnóstico para la categoría '{category}': {error}", - "diagnosis_ip_connected_ipv4": "¡El servidor está conectado a internet a través de IPv4!" + "diagnosis_ip_connected_ipv4": "¡El servidor está conectado a internet a través de IPv4!", + "diagnosis_security_vulnerable_to_meltdown_details": "Para corregir esto, debieras actualizar y reiniciar tu sistema para cargar el nuevo kernel de Linux (o contacta tu proveedor si esto no funciona). Mas información en https://meltdownattack.com/", + "diagnosis_ram_verylow": "Al sistema le queda solamente {available_abs_MB} MB ({available_percent}%) de RAM! (De un total de {total_abs_MB} MB)", + "diagnosis_ram_low": "Al sistema le queda {available_abs_MB} MB ({available_percent}%) de RAM de un total de {total_abs_MB} MB. Cuidado.", + "diagnosis_ram_ok": "El sistema aun tiene {available_abs_MB} MB ({available_percent}%) de RAM de un total de {total_abs_MB} MB.", + "diagnosis_swap_none": "El sistema no tiene mas espacio de intercambio. Considera agregar por lo menos 256 MB de espacio de intercambio para evitar que el sistema se quede sin memoria.", + "diagnosis_swap_notsomuch": "Al sistema le queda solamente {total_MB} MB de espacio de intercambio. Considera agregar al menos 256 MB para evitar que el sistema se quede sin memoria.", + "diagnosis_mail_ougoing_port_25_ok": "El puerto de salida 25 no esta bloqueado y los correos electrónicos pueden ser enviados a otros servidores.", + "diagnosis_mail_ougoing_port_25_blocked": "El puerto de salida 25 parece estar bloqueado. Intenta desbloquearlo con el panel de configuración de tu proveedor de servicios de Internet (o hoster). Mientras tanto, el servidor no podrá enviar correos electrónicos a otros servidores.", + "diagnosis_regenconf_allgood": "Todos los archivos de configuración están en linea con la configuración recomendada!", + "diagnosis_regenconf_manually_modified": "El archivo de configuración {file} fue modificado manualmente.", + "diagnosis_regenconf_manually_modified_details": "Esto este probablemente BIEN siempre y cuando sepas lo que estas haciendo ;) !", + "diagnosis_regenconf_manually_modified_debian": "El archivos de configuración {file} fue modificado manualmente comparado con el valor predeterminado de Debian.", + "diagnosis_regenconf_manually_modified_debian_details": "Esto este probablemente BIEN, pero igual no lo pierdas de vista...", + "diagnosis_regenconf_nginx_conf_broken": "La configuración nginx parece rota!", + "diagnosis_security_all_good": "Ninguna vulnerabilidad critica de seguridad fue encontrada.", + "diagnosis_security_vulnerable_to_meltdown": "Pareces vulnerable a el colapso de vulnerabilidad critica de seguridad.", + "diagnosis_description_basesystem": "Sistema de base", + "diagnosis_description_ip": "Conectividad a Internet", + "diagnosis_description_dnsrecords": "Registro DNS", + "diagnosis_description_services": "Comprobación del estado de los servicios", + "diagnosis_description_ports": "Exposición de puertos", + "diagnosis_description_systemresources": "Recursos del sistema", + "diagnosis_swap_ok": "El sistema tiene {total_MB} MB de espacio de intercambio!" } From dfb48c4ea36711fb69bd0a62fe9e5fe20ece2a10 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Fri, 24 Jan 2020 06:42:20 +0000 Subject: [PATCH 0722/3170] Translated using Weblate (Esperanto) Currently translated at 100.0% (610 of 610 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/eo/ --- locales/eo.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/locales/eo.json b/locales/eo.json index f303a57ee..e204af4c6 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -659,5 +659,8 @@ "diagnosis_failed": "Malsukcesis preni la diagnozan rezulton por kategorio '{category}': {error}", "diagnosis_description_ports": "Ekspoziciaj havenoj", "diagnosis_description_http": "HTTP-ekspozicio", - "diagnosis_description_mail": "Retpoŝto" + "diagnosis_description_mail": "Retpoŝto", + "log_app_action_run": "Funkciigu agon de la apliko '{}'", + "log_app_config_show_panel": "Montri la agordan panelon de la apliko '{}'", + "log_app_config_apply": "Apliki agordon al la apliko '{}'" } From 4a8c4567fdf9235ddfc58d876894dc4148f7545c Mon Sep 17 00:00:00 2001 From: Kayou Date: Fri, 31 Jan 2020 13:55:57 +0000 Subject: [PATCH 0723/3170] Translated using Weblate (French) Currently translated at 100.0% (610 of 610 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 6267d0d58..d44b65fe8 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -674,7 +674,7 @@ "diagnosis_ip_weird_resolvconf": "La résolution DNS semble fonctionner, mais soyer prudent en utilisant un fichier /etc/resolv.conf personnalisé.", "diagnosis_ip_weird_resolvconf_details": "Au lieu de cela, ce fichier devrait être un lien symbolique vers /etc/resolvconf/run/resolv.conf lui-même pointant vers 127.0.0.1 (dnsmasq). Les résolveurs réels doivent être configurés via /etc/resolv.dnsmasq.conf.", "diagnosis_dns_missing_record": "Selon la configuration DNS recommandée, vous devez ajouter un enregistrement DNS de type {0}, nom {1} et valeur {2}. Vous pouvez consulter https://yunohost.org/dns_config pour plus d'informations.", - "diagnosis_diskusage_ok": "Le stockage {mountpoint} (sur le périphérique {device}) a toujours {espace libre {free_abs_GB} GB ({free_percent}%) !", + "diagnosis_diskusage_ok": "Le stockage {mountpoint} (sur le périphérique {device}) a toujours {free_abs_GB} Go ({free_percent}%) d'espace libre !", "diagnosis_ram_ok": "Le système dispose toujours de {available_abs_MB} MB ({available_percent}%) de RAM sur {total_abs_MB} MB.", "diagnosis_regenconf_allgood": "Tous les fichiers de configuration sont conformes à la configuration recommandée !", "diagnosis_security_vulnerable_to_meltdown": "Vous semblez vulnérable à la vulnérabilité de sécurité critique de Meltdown", From ae23ed1dddfdad0a079e6f8b25ce7aa9adeddc0b Mon Sep 17 00:00:00 2001 From: xaloc33 Date: Sun, 9 Feb 2020 12:13:52 +0000 Subject: [PATCH 0724/3170] Translated using Weblate (Catalan) Currently translated at 100.0% (610 of 610 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ca/ --- locales/ca.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/locales/ca.json b/locales/ca.json index acd3df621..ea9254be1 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -719,5 +719,8 @@ "diagnosis_ports_needed_by": "És necessari exposar aquest port pel servei {0}", "permission_all_users_implicitly_added": "El permís també s'ha donat implícitament a «all_users» ja que és necessari per atorgar-lo al grup «visitors»", "permission_cannot_remove_all_users_while_visitors_allowed": "No podeu retirar el permís a «all_users» mentre encara el tingui el grup «visitors»", - "global_settings_setting_pop3_enabled": "Activa el protocol POP3 per al servidor de correu" + "global_settings_setting_pop3_enabled": "Activa el protocol POP3 per al servidor de correu", + "log_app_action_run": "Executa l'acció de l'aplicació «{}»", + "log_app_config_show_panel": "Mostra el taulell de configuració de l'aplicació «{}»", + "log_app_config_apply": "Afegeix la configuració a l'aplicació «{}»" } From 39bf31411b283225d21d392b603c20d1813119e2 Mon Sep 17 00:00:00 2001 From: tituspijean Date: Mon, 9 Mar 2020 20:00:38 +0000 Subject: [PATCH 0725/3170] Translated using Weblate (French) Currently translated at 100.0% (610 of 610 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 d44b65fe8..55f567e10 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -671,7 +671,7 @@ "diagnosis_found_errors": "Trouvé {errors} problème(s) significatif(s) lié(s) à {category} !", "diagnosis_found_errors_and_warnings": "Trouvé {errors} problème(s) significatif(s) (et {warnings} (avertissement(s)) en relation avec {category} !", "diagnosis_ip_not_connected_at_all": "Le serveur ne semble pas du tout connecté à Internet !?", - "diagnosis_ip_weird_resolvconf": "La résolution DNS semble fonctionner, mais soyer prudent en utilisant un fichier /etc/resolv.conf personnalisé.", + "diagnosis_ip_weird_resolvconf": "La résolution DNS semble fonctionner, mais soyez prudent en utilisant un fichier /etc/resolv.conf personnalisé.", "diagnosis_ip_weird_resolvconf_details": "Au lieu de cela, ce fichier devrait être un lien symbolique vers /etc/resolvconf/run/resolv.conf lui-même pointant vers 127.0.0.1 (dnsmasq). Les résolveurs réels doivent être configurés via /etc/resolv.dnsmasq.conf.", "diagnosis_dns_missing_record": "Selon la configuration DNS recommandée, vous devez ajouter un enregistrement DNS de type {0}, nom {1} et valeur {2}. Vous pouvez consulter https://yunohost.org/dns_config pour plus d'informations.", "diagnosis_diskusage_ok": "Le stockage {mountpoint} (sur le périphérique {device}) a toujours {free_abs_GB} Go ({free_percent}%) d'espace libre !", From a48a86b82a2a5acb347162f9307d9bae27048482 Mon Sep 17 00:00:00 2001 From: xaloc33 Date: Fri, 13 Mar 2020 16:54:19 +0000 Subject: [PATCH 0726/3170] Translated using Weblate (Catalan) Currently translated at 100.0% (610 of 610 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ca/ --- locales/ca.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/locales/ca.json b/locales/ca.json index ea9254be1..e8308d88c 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -162,7 +162,7 @@ "admin_password_too_long": "Trieu una contrasenya de menys de 127 caràcters", "dpkg_is_broken": "No es pot fer això en aquest instant perquè dpkg/APT (els gestors de paquets del sistema) sembla estar mal configurat… Podeu intentar solucionar-ho connectant-vos per SSH i executant «sudo dpkg --configure -a».", "dnsmasq_isnt_installed": "sembla que dnsmasq no està instal·lat, executeu \"apt-get remove bind9 && apt-get install dnsmasq\"", - "domain_cannot_remove_main": "No es pot eliminar «{domain:s}» ja que és el domini principal, primer s'ha d'establir un nou domini principal utilitzant «yunohost domain main-domain -n », aquí hi ha una llista dels possibles dominis: {other_domains:s}", + "domain_cannot_remove_main": "No es pot eliminar «{domain:s}» ja que és el domini principal, primer s'ha d'establir un nou domini principal utilitzant «yunohost domain main-domain -n »; aquí hi ha una llista dels possibles dominis: {other_domains:s}", "domain_cert_gen_failed": "No s'ha pogut generar el certificat", "domain_created": "S'ha creat el domini", "domain_creation_failed": "No s'ha pogut crear el domini {domain}: {error}", @@ -601,7 +601,7 @@ "migrations_running_forward": "Executant la migració {id}…", "migrations_success_forward": "Migració {id} completada", "apps_already_up_to_date": "Ja estan actualitzades totes les aplicacions", - "dyndns_provider_unreachable": "No s'ha pogut connectar amb el proveïdor Dyndns {provider}: o el vostre YunoHost no està ben connectat a Internet o el servidor dynette està caigut.", + "dyndns_provider_unreachable": "No s'ha pogut connectar amb el proveïdor DynDNS {provider}: o el vostre YunoHost no està ben connectat a Internet o el servidor dynette està caigut.", "operation_interrupted": "S'ha interromput manualment l'operació?", "group_already_exist": "El grup {group} ja existeix", "group_already_exist_on_system": "El grup {group} ja existeix en els grups del sistema", @@ -641,7 +641,7 @@ "diagnosis_basesystem_ynh_single_version": "{0} versió: {1}({2})", "diagnosis_basesystem_ynh_inconsistent_versions": "Esteu utilitzant versions inconsistents dels paquets de YunoHost… probablement a causa d'una actualització fallida o parcial.", "diagnosis_display_tip_web": "Podeu anar a la secció de Diagnòstics (en la pantalla principal) per veure els errors que s'han trobat.", - "diagnosis_failed_for_category": "Ha fallat el diagnòstic per la categoria «{category}» : {error}", + "diagnosis_failed_for_category": "Ha fallat el diagnòstic per la categoria «{category}»: {error}", "diagnosis_display_tip_cli": "Podeu executar «yunohost diagnosis show --issues» per mostrar els errors que s'han trobat.", "diagnosis_cache_still_valid": "(La memòria cau encara és vàlida pel diagnòstic de {category}. No es tornar a diagnosticar de moment!)", "diagnosis_cant_run_because_of_dep": "No es pot fer el diagnòstic per {category} mentre hi ha problemes importants relacionats amb {dep}.", @@ -650,7 +650,7 @@ "diagnosis_found_errors_and_warnings": "S'ha trobat problema(es) important(s) {errors} (i avis(os) {warnings}) relacionats amb {category}!", "diagnosis_found_warnings": "S'han trobat ítems {warnings} que es podrien millorar per {category}.", "diagnosis_everything_ok": "Tot sembla correcte per {category}!", - "diagnosis_failed": "No s'han pogut obtenir els resultats del diagnòstic per la categoria «{category}» : {error}", + "diagnosis_failed": "No s'han pogut obtenir els resultats del diagnòstic per la categoria «{category}»: {error}", "diagnosis_ip_connected_ipv4": "El servidor està connectat a Internet amb IPv4!", "diagnosis_ip_no_ipv4": "El servidor no té una IPv4 que funcioni.", "diagnosis_ip_connected_ipv6": "El servidor està connectat a Internet amb IPv6!", @@ -706,7 +706,7 @@ "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 {0}» 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 s'explica a https://yunohost.org/isp_box_config", + "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": "No s'ha pogut connectar al servidor com esperat, ha retornat un codi d'estat erroni. 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_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.", From 9bee226a99dc7eb22205be3fbcd133919cf0b2c7 Mon Sep 17 00:00:00 2001 From: yalh76 Date: Sat, 14 Mar 2020 16:22:46 +0000 Subject: [PATCH 0727/3170] Translated using Weblate (French) Currently translated at 96.9% (591 of 610 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 55f567e10..9d1ee15d4 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -288,7 +288,7 @@ "appslist_migrating": "Migration de la liste d’applications '{appslist:s}' …", "appslist_could_not_migrate": "Impossible de migrer la liste '{appslist:s}' ! Impossible d’exploiter l’URL. L’ancienne tâche programmée a été conservée dans {bkp_file:s}.", "appslist_corrupted_json": "Impossible de charger la liste d’applications. Il semble que {filename:s} soit endommager.", - "app_already_installed_cant_change_url": "Cette application est déjà installée. L’URL ne peut pas être changé simplement par cette fonction. Regardez si cela est disponible avec `app changeurl`.", + "app_already_installed_cant_change_url": "Cette application est déjà installée. L’URL ne peut pas être changé simplement par cette fonction. Vérifiez si cela est disponible avec `app changeurl`.", "app_change_no_change_url_script": "L’application {app_name:s} ne prend pas encore en charge le changement d’URL, vous pourriez avoir besoin de la mettre à jour.", "app_change_url_failed_nginx_reload": "Le redémarrage de Nginx a échoué. Voici la sortie de 'nginx -t' :\n{nginx_errors:s}", "app_change_url_identical_domains": "L’ancien et le nouveau couple domaine/chemin_de_l'URL sont identiques pour ('{domain:s}{path:s}'), rien à faire.", @@ -617,7 +617,7 @@ "permission_updated": "Permission '{permission:s}' mise à jour", "permission_update_nothing_to_do": "Aucune autorisation pour mettre à jour", "remove_main_permission_not_allowed": "Supprimer l'autorisation principale n'est pas autorisé", - "dyndns_provider_unreachable": "Impossible d’atteindre le fournisseur Dyndns {provider}: votre YunoHost n’est pas correctement connecté à Internet ou le serveur Dynette est en panne.", + "dyndns_provider_unreachable": "Impossible d’atteindre le fournisseur DynDNS {provider}: votre YunoHost n’est pas correctement connecté à Internet ou le serveur Dynette est en panne.", "migration_0011_update_LDAP_schema": "Mise à jour du schéma LDAP…", "migrations_already_ran": "Ces migrations sont déjà effectuées: {ids}", "migrations_dependencies_not_satisfied": "Exécutez ces migrations: '{dependencies_id}', avant migration {id}.", @@ -684,7 +684,7 @@ "diagnosis_basesystem_ynh_main_version": "Le serveur exécute YunoHost {main_version} ({repo})", "diagnosis_basesystem_ynh_inconsistent_versions": "Vous exécutez des versions incohérentes des packages YunoHost ... probablement à cause d'une mise à niveau partielle ou échouée.", "diagnosis_display_tip_cli": "Vous pouvez exécuter 'yunohost diagnosis show --issues' pour afficher les problèmes détectés.", - "diagnosis_failed_for_category": "Échec du diagnostic pour la catégorie '{category}' : {error}", + "diagnosis_failed_for_category": "Échec du diagnostic pour la catégorie '{category}': {error}", "diagnosis_cache_still_valid": "(Le cache est toujours valide pour le diagnostic {category}. Pas re-diagnostiquer pour le moment!)", "diagnosis_ignored_issues": "(+ {nb_ignored} questions ignorée(s))", "diagnosis_found_warnings": "Trouvé {warnings} objet(s) pouvant être amélioré(s) pour {category}.", @@ -695,7 +695,7 @@ "diagnosis_ip_connected_ipv6": "Le serveur est connecté à Internet via IPv6 !", "diagnosis_ip_no_ipv6": "Le serveur ne dispose pas d'une adresse IPv6 active.", "diagnosis_ip_dnsresolution_working": "La résolution de nom de domaine fonctionne !", - "diagnosis_ip_broken_dnsresolution": "La résolution du nom de domaine semble interrompue pour une raison quelconque ... Un pare-feu bloque-t-il les requêtes DNS ?", + "diagnosis_ip_broken_dnsresolution": "La résolution du nom de domaine semble interrompue pour une raison quelconque... Un pare-feu bloque-t-il les requêtes DNS ?", "diagnosis_ip_broken_resolvconf": "La résolution du nom de domaine semble cassée sur votre serveur, ce qui semble lié au fait que /etc/resolv.conf ne pointe pas vers 127.0.0.1.", "diagnosis_dns_good_conf": "Bonne configuration DNS pour le domaine {domain} (catégorie {category})", "diagnosis_dns_bad_conf": "Configuration DNS incorrecte/manquante pour le domaine {domain} (catégorie {category})", From c985eca913f511bf6d17f1e7d144e18698f0261e Mon Sep 17 00:00:00 2001 From: Gustavo M Date: Thu, 12 Mar 2020 22:55:53 +0000 Subject: [PATCH 0728/3170] Translated using Weblate (Portuguese) Currently translated at 8.7% (53 of 610 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/pt/ --- locales/pt.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/locales/pt.json b/locales/pt.json index 2905238a1..2e0767e39 100644 --- a/locales/pt.json +++ b/locales/pt.json @@ -1,8 +1,8 @@ { "action_invalid": "Acção Inválida '{action:s}'", "admin_password": "Senha de administração", - "admin_password_change_failed": "Não foi possível alterar a senha", - "admin_password_changed": "A palavra-passe de administração foi alterada com sucesso", + "admin_password_change_failed": "Não é possível alterar a senha", + "admin_password_changed": "A senha da administração foi alterada", "app_already_installed": "{app:s} já está instalada", "app_extraction_failed": "Não foi possível extrair os ficheiros para instalação", "app_id_invalid": "A ID da aplicação é inválida", @@ -194,5 +194,6 @@ "backup_cant_mount_uncompress_archive": "Não foi possível montar em modo leitura o diretorio de arquivos não comprimido", "backup_copying_to_organize_the_archive": "Copiando {size:s}MB para organizar o arquivo", "app_change_url_identical_domains": "O antigo e o novo domínio / url_path são idênticos ('{domain:s}{path:s}'), nada para fazer.", - "password_too_simple_1": "A senha precisa ter pelo menos 8 caracteres" + "password_too_simple_1": "A senha precisa ter pelo menos 8 caracteres", + "admin_password_too_long": "Escolha uma senha que contenha menos de 127 caracteres" } From d5ee47017b89e0f025b6474f6e9f91566b27a262 Mon Sep 17 00:00:00 2001 From: xaloc33 Date: Wed, 18 Mar 2020 23:09:52 +0000 Subject: [PATCH 0729/3170] Translated using Weblate (Catalan) Currently translated at 100.0% (611 of 611 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ca/ --- locales/ca.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/locales/ca.json b/locales/ca.json index e8308d88c..cedb0afae 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -712,7 +712,7 @@ "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_connection_error": "Error de connexió: no s'ha pogut connectar amb el domini demanat, segurament és inaccessible.", "diagnosis_http_unknown_error": "Hi ha hagut un error intentant accedir al domini, 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 els problemes esperant a ser resolts per un correcte funcionament del servidor 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.", + "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", "diagnosis_services_running": "El servei {service} s'està executant!", "diagnosis_services_conf_broken": "La configuració pel servei {service} està trencada!", @@ -722,5 +722,6 @@ "global_settings_setting_pop3_enabled": "Activa el protocol POP3 per al servidor de correu", "log_app_action_run": "Executa l'acció de l'aplicació «{}»", "log_app_config_show_panel": "Mostra el taulell de configuració de l'aplicació «{}»", - "log_app_config_apply": "Afegeix la configuració a l'aplicació «{}»" + "log_app_config_apply": "Afegeix la configuració a l'aplicació «{}»", + "diagnosis_never_ran_yet": "Sembla que el servidor s'ha configurat recentment i encara no hi cap informe de diagnòstic per mostrar. S'ha d'executar un diagnòstic complet primer, ja sigui des de la pàgina web d'administració o utilitzant la comanda «yunohost diagnosis run» al terminal." } From 254d41a5afd147d1f04cc7bf97452a5dfff2e2b9 Mon Sep 17 00:00:00 2001 From: xaloc33 Date: Sun, 22 Mar 2020 17:01:08 +0000 Subject: [PATCH 0730/3170] Translated using Weblate (Catalan) Currently translated at 100.0% (611 of 611 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ca/ --- locales/ca.json | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/locales/ca.json b/locales/ca.json index cedb0afae..d0f31cc1f 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -660,7 +660,7 @@ "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 via /etc/resolv.dnsmaq.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 de tipus {0}, nom {1} i valor {2}. Hi ha més informació a https://yunohost.org/dns_config.", @@ -692,8 +692,8 @@ "diagnosis_ports_could_not_diagnose": "No s'ha pogut diagnosticar si els ports són accessibles des de l'exterior. Error: {error}", "diagnosis_ports_unreachable": "El port {port} no és accessible des de l'exterior.", "diagnosis_ports_ok": "El port {port} és accessible des de l'exterior.", - "diagnosis_http_ok": "El domini {domain} és accessible des de l'exterior.", - "diagnosis_http_unreachable": "El domini {domain} no és accessible a través de HTTP des de l'exterior.", + "diagnosis_http_ok": "El domini {domain} és accessible per mitjà de HTTP des de fora de la xarxa local.", + "diagnosis_http_unreachable": "Sembla que el domini {domain} no és accessible a través de HTTP des de fora de la xarxa local.", "diagnosis_unknown_categories": "Les següents categories són desconegudes: {categories}", "apps_catalog_init_success": "S'ha iniciat el sistema de catàleg d'aplicacions!", "apps_catalog_updating": "S'està actualitzant el catàleg d'aplicacions…", @@ -707,7 +707,7 @@ "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 {0}» 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": "No s'ha pogut connectar al servidor com esperat, ha retornat un codi d'estat erroni. 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_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_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_connection_error": "Error de connexió: no s'ha pogut connectar amb el domini demanat, segurament és inaccessible.", @@ -716,12 +716,13 @@ "migration_description_0014_remove_app_status_json": "Eliminar els fitxers d'aplicació status.json heretats", "diagnosis_services_running": "El servei {service} s'està executant!", "diagnosis_services_conf_broken": "La configuració pel servei {service} està trencada!", - "diagnosis_ports_needed_by": "És necessari exposar aquest port pel servei {0}", + "diagnosis_ports_needed_by": "És necessari exposar aquest port per a les funcions {1} (servei {0})", "permission_all_users_implicitly_added": "El permís també s'ha donat implícitament a «all_users» ja que és necessari per atorgar-lo al grup «visitors»", "permission_cannot_remove_all_users_while_visitors_allowed": "No podeu retirar el permís a «all_users» mentre encara el tingui el grup «visitors»", "global_settings_setting_pop3_enabled": "Activa el protocol POP3 per al servidor de correu", "log_app_action_run": "Executa l'acció de l'aplicació «{}»", "log_app_config_show_panel": "Mostra el taulell de configuració de l'aplicació «{}»", "log_app_config_apply": "Afegeix la configuració a l'aplicació «{}»", - "diagnosis_never_ran_yet": "Sembla que el servidor s'ha configurat recentment i encara no hi cap informe de diagnòstic per mostrar. S'ha d'executar un diagnòstic complet primer, ja sigui des de la pàgina web d'administració o utilitzant la comanda «yunohost diagnosis run» al terminal." + "diagnosis_never_ran_yet": "Sembla que el servidor s'ha configurat recentment i encara no hi cap informe de diagnòstic per mostrar. S'ha d'executar un diagnòstic complet primer, ja sigui des de la pàgina web d'administració o utilitzant la comanda «yunohost diagnosis run» al terminal.", + "diagnosis_description_web": "Web" } From 36f4db14eceba1e52a79b5fe8b9869f326eb5128 Mon Sep 17 00:00:00 2001 From: xaloc33 Date: Wed, 25 Mar 2020 14:42:25 +0000 Subject: [PATCH 0731/3170] Translated using Weblate (Catalan) Currently translated at 100.0% (613 of 613 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ca/ --- locales/ca.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/locales/ca.json b/locales/ca.json index d0f31cc1f..5d9ed318d 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -636,7 +636,7 @@ "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. Error: {error}", "domain_cannot_remove_main_add_new_one": "No es pot eliminar «{domain:s}» ja que és el domini principal i únic domini, primer s'ha d'afegir un altre domini utilitzant «yunohost domain add », i després fer-lo el domini principal amb «yunohost domain main-domain -n » i després es pot eliminar el domini «{domain:s}» utilitzant «yunohost domain remove {domain:s}».", - "diagnosis_basesystem_host": "El servidor funciona amb Debian {debian_version}.", + "diagnosis_basesystem_host": "El servidor funciona amb Debian {debian_version}", "diagnosis_basesystem_kernel": "El servidor funciona amb el nucli de Linux {kernel_version}", "diagnosis_basesystem_ynh_single_version": "{0} versió: {1}({2})", "diagnosis_basesystem_ynh_inconsistent_versions": "Esteu utilitzant versions inconsistents dels paquets de YunoHost… probablement a causa d'una actualització fallida o parcial.", @@ -724,5 +724,7 @@ "log_app_config_show_panel": "Mostra el taulell de configuració de l'aplicació «{}»", "log_app_config_apply": "Afegeix la configuració a l'aplicació «{}»", "diagnosis_never_ran_yet": "Sembla que el servidor s'ha configurat recentment i encara no hi cap informe de diagnòstic per mostrar. S'ha d'executar un diagnòstic complet primer, ja sigui des de la pàgina web d'administració o utilitzant la comanda «yunohost diagnosis run» al terminal.", - "diagnosis_description_web": "Web" + "diagnosis_description_web": "Web", + "diagnosis_basesystem_hardware_board": "El model de la targeta del servidor és {model}", + "diagnosis_basesystem_hardware": "L'arquitectura del maquinari del servidor és {virt} {arch}" } From cf821d99e691ae9db287a13fd7bed605a8f29db4 Mon Sep 17 00:00:00 2001 From: Aeris One Date: Thu, 26 Mar 2020 17:29:24 +0000 Subject: [PATCH 0732/3170] Translated using Weblate (French) Currently translated at 100.0% (613 of 613 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 9d1ee15d4..e8ceca1ab 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -182,7 +182,7 @@ "restore_running_hooks": "Exécution des scripts de restauration …", "service_add_configuration": "Ajout du fichier de configuration {file:s}", "service_add_failed": "Impossible d’ajouter le service '{service:s}'", - "service_added": "Le service '{service:s}' ajouté", + "service_added": "Le service '{service:s}' a été ajouté", "service_already_started": "Le service '{service:s}' est déjà en cours d'exécution", "service_already_stopped": "Le service '{service:s}' est déjà arrêté", "service_cmd_exec_failed": "Impossible d’exécuter la commande '{command:s}'", @@ -273,7 +273,7 @@ "domain_cannot_remove_main": "Vous ne pouvez pas supprimer '{domain: s}' car il s'agit du domaine principal. Vous devez d'abord définir un autre domaine comme domaine principal à l'aide de 'yunohost domain main-domain -n ', voici la liste des domaines candidats. : {other_domains: s}", "certmanager_self_ca_conf_file_not_found": "Le fichier de configuration pour l’autorité du certificat auto-signé est introuvable (fichier : {file:s})", "certmanager_unable_to_parse_self_CA_name": "Impossible d’analyser le nom de l’autorité du certificat auto-signé (fichier : {file:s})", - "mailbox_used_space_dovecot_down": "Le service de courriel Dovecot doit être démarré, si vous souhaitez voir l’espace disque occupé par la messagerie", + "mailbox_used_space_dovecot_down": "Le service de courriel Dovecot doit être démarré si vous souhaitez voir l’espace disque occupé par la messagerie", "domains_available": "Domaines disponibles :", "backup_archive_broken_link": "Impossible d’accéder à l’archive de sauvegarde (lien invalide vers {path:s})", "certmanager_acme_not_configured_for_domain": "Le certificat du domaine {domain:s} ne semble pas être correctement installé. Veuillez d'abord exécuter cert-install.", @@ -394,7 +394,7 @@ "migration_0003_system_not_fully_up_to_date": "Votre système n’est pas complètement à jour. Veuillez mener une mise à jour classique avant de lancer à migration à Stretch.", "migration_0003_still_on_jessie_after_main_upgrade": "Quelque chose s’est mal passé pendant la mise à niveau principale : le système est toujours sur Debian Jessie !? Pour investiguer sur le problème, veuillez regarder les journaux {log}:s …", "migration_0003_general_warning": "Veuillez noter que cette migration est une opération délicate. Si l’équipe YunoHost a fait de son mieux pour la relire et la tester, la migration pourrait tout de même casser des parties de votre système ou de vos applications.\n\nEn conséquence, nous vous recommandons :\n - de lancer une sauvegarde de vos données ou applications critiques. Plus d’informations sur https://yunohost.org/backup ;\n - d’être patient après avoir lancé la migration : selon votre connexion internet et matériel, cela pourrait prendre jusqu’à quelques heures pour que tout soit à niveau.\n\nEn outre, le port SMTP utilisé par les clients de messagerie externes comme (Thunderbird ou K9-Mail) a été changé de 465 (SSL/TLS) à 587 (STARTTLS). L’ancien port 465 sera automatiquement fermé et le nouveau port 587 sera ouvert dans le pare-feu. Vous et vos utilisateurs *devront* adapter la configuration de vos clients de messagerie en conséquence.", - "migration_0003_problematic_apps_warning": "Veuillez noter que les applications installées potentiellement problématiques suivantes ont été détectées. Il semble que celles-ci n'ont pas été installées à partir d'un catalogue d'applications, ou ne sont pas marquées comme \"working \". Par conséquent, il ne peut pas être garanti qu'ils fonctionneront toujours après la mise à niveau: {problematic_apps}", + "migration_0003_problematic_apps_warning": "Veuillez noter que les applications installées potentiellement problématiques suivantes ont été détectées. Il semble que celles-ci n'ont pas été installées à partir d'un catalogue d'applications, ou ne sont pas marquées comme \"fonctionnelle\". Par conséquent, il ne peut pas être garanti qu'ils fonctionneront toujours après la mise à niveau: {problematic_apps}", "migration_0003_modified_files": "Veuillez noter que les fichiers suivants ont été détectés comme modifiés manuellement et pourraient être écrasés à la fin de la mise à niveau : {manually_modified_files}", "migrations_list_conflict_pending_done": "Vous ne pouvez pas utiliser --previous et --done simultanément.", "migrations_to_be_ran_manually": "La migration {id} doit être lancée manuellement. Veuillez aller dans Outils > Migrations dans l’interface admin, ou lancer `yunohost tools migrations migrate`.", @@ -672,14 +672,14 @@ "diagnosis_found_errors_and_warnings": "Trouvé {errors} problème(s) significatif(s) (et {warnings} (avertissement(s)) en relation avec {category} !", "diagnosis_ip_not_connected_at_all": "Le serveur ne semble pas du tout connecté à Internet !?", "diagnosis_ip_weird_resolvconf": "La résolution DNS semble fonctionner, mais soyez prudent en utilisant un fichier /etc/resolv.conf personnalisé.", - "diagnosis_ip_weird_resolvconf_details": "Au lieu de cela, ce fichier devrait être un lien symbolique vers /etc/resolvconf/run/resolv.conf lui-même pointant vers 127.0.0.1 (dnsmasq). Les résolveurs réels doivent être configurés via /etc/resolv.dnsmasq.conf.", + "diagnosis_ip_weird_resolvconf_details": "Au lieu de cela, ce fichier devrait être un lien symbolique vers /etc/resolvconf/run/resolv.conf lui-même pointant vers 127.0.0.1 (dnsmasq). Les résolveurs réels doivent être configurés dans /etc/resolv.dnsmasq.conf.", "diagnosis_dns_missing_record": "Selon la configuration DNS recommandée, vous devez ajouter un enregistrement DNS de type {0}, nom {1} et valeur {2}. Vous pouvez consulter https://yunohost.org/dns_config pour plus d'informations.", "diagnosis_diskusage_ok": "Le stockage {mountpoint} (sur le périphérique {device}) a toujours {free_abs_GB} Go ({free_percent}%) d'espace libre !", "diagnosis_ram_ok": "Le système dispose toujours de {available_abs_MB} MB ({available_percent}%) de RAM sur {total_abs_MB} MB.", "diagnosis_regenconf_allgood": "Tous les fichiers de configuration sont conformes à la configuration recommandée !", "diagnosis_security_vulnerable_to_meltdown": "Vous semblez vulnérable à la vulnérabilité de sécurité critique de Meltdown", - "diagnosis_basesystem_host": "Le serveur exécute Debian {debian_version}.", - "diagnosis_basesystem_kernel": "Le serveur exécute le noyau Linux {kernel_version}", + "diagnosis_basesystem_host": "Le serveur utilise Debian {debian_version}", + "diagnosis_basesystem_kernel": "Le serveur utilise le noyau Linux {kernel_version}", "diagnosis_basesystem_ynh_single_version": "{0} version: {1} ({2})", "diagnosis_basesystem_ynh_main_version": "Le serveur exécute YunoHost {main_version} ({repo})", "diagnosis_basesystem_ynh_inconsistent_versions": "Vous exécutez des versions incohérentes des packages YunoHost ... probablement à cause d'une mise à niveau partielle ou échouée.", @@ -738,8 +738,8 @@ "diagnosis_ports_unreachable": "Le port {port} n'est pas accessible de l'extérieur.", "diagnosis_ports_ok": "Le port {port} est accessible de l'extérieur.", "diagnosis_http_could_not_diagnose": "Impossible de diagnostiquer si le domaine est accessible de l'extérieur. Erreur: {error}", - "diagnosis_http_ok": "Le domaine {domain} est accessible de l'extérieur.", - "diagnosis_http_unreachable": "Le domaine {domain} est inaccessible via HTTP de l'extérieur.", + "diagnosis_http_ok": "Le domaine {domain} est accessible au travers de HTTP depuis l'extérieur.", + "diagnosis_http_unreachable": "Le domaine {domain} est inaccessible au travers de HTTP depuis l'extérieur.", "diagnosis_unknown_categories": "Les catégories suivantes sont inconnues: {categories}", "migration_description_0013_futureproof_apps_catalog_system": "Migrer vers le nouveau système de catalogue d'applications à l'épreuve du temps", "app_upgrade_script_failed": "Une erreur s'est produite durant l’exécution du script de mise à niveau de l'application", @@ -760,5 +760,9 @@ "global_settings_setting_pop3_enabled": "Activer le protocole POP3 pour le serveur de messagerie", "log_app_action_run": "Lancer l’action de l’application '{}'", "log_app_config_show_panel": "Montrer le panneau de configuration de l’application '{}'", - "log_app_config_apply": "Appliquer la configuration à l’application '{}'" + "log_app_config_apply": "Appliquer la configuration à l’application '{}'", + "diagnosis_never_ran_yet": "Il apparaît que le serveur a été installé récemment et qu'il n'y a pas encore eu de diagnostic. Vous devriez en lancer un depuis le webmin ou en utilisant 'yunohost diagnosis run' depuis la ligne de commande.", + "diagnosis_description_web": "Web", + "diagnosis_basesystem_hardware_board": "Le modèle de carte du serveur est {model}", + "diagnosis_basesystem_hardware": "L'architecture du serveur est {virt} {arch}" } From 8fe343aad0da642682278a13bc56696c9b8b05f9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 27 Mar 2020 20:26:07 +0100 Subject: [PATCH 0733/3170] Clumsy wording / translations --- locales/en.json | 2 +- locales/fr.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/en.json b/locales/en.json index 124bd79d3..33b45c4fa 100644 --- a/locales/en.json +++ b/locales/en.json @@ -355,7 +355,7 @@ "log_user_permission_update": "Update accesses for permission '{}'", "log_user_permission_reset": "Reset permission '{}'", "log_domain_main_domain": "Make '{}' the main domain", - "log_tools_migrations_migrate_forward": "Migrate forward", + "log_tools_migrations_migrate_forward": "Run migrations", "log_tools_postinstall": "Postinstall your YunoHost server", "log_tools_upgrade": "Upgrade system packages", "log_tools_shutdown": "Shutdown your server", diff --git a/locales/fr.json b/locales/fr.json index c2cb7a18c..0679e5ad2 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -455,7 +455,7 @@ "log_user_delete": "Supprimer l’utilisateur '{}'", "log_user_update": "Mettre à jour les informations de l’utilisateur '{}'", "log_domain_main_domain": "Faire de '{}' le domaine principal", - "log_tools_migrations_migrate_forward": "Migrer vers", + "log_tools_migrations_migrate_forward": "Éxecuter les migrations", "log_tools_migrations_migrate_backward": "Revenir en arrière", "log_tools_postinstall": "Faire la post-installation de votre serveur YunoHost", "log_tools_upgrade": "Mettre à jour les paquets du système", From 6d1b0502901133e75695239c937b0aed49db4aad Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 27 Mar 2020 19:21:58 +0100 Subject: [PATCH 0734/3170] Try to improve / fix weird wording --- locales/en.json | 2 +- locales/fr.json | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/locales/en.json b/locales/en.json index d2972fa25..c4f82117e 100644 --- a/locales/en.json +++ b/locales/en.json @@ -318,7 +318,7 @@ "migration_description_0008_ssh_conf_managed_by_yunohost_step2": "Let the SSH configuration be managed by YunoHost (step 2, manual)", "migration_description_0009_decouple_regenconf_from_services": "Decouple the regen-conf mechanism from services", "migration_description_0010_migrate_to_apps_json": "Remove deprecated applists and use the new unified 'apps.json' list instead", - "migration_description_0011_setup_group_permission": "Set up user group and set up permission for apps and services", + "migration_description_0011_setup_group_permission": "Set up user groups and permissions for apps and services", "migration_description_0012_postgresql_password_to_md5_authentication": "Force PostgreSQL authentication to use MD5 for local connections", "migration_0003_start": "Starting migration to Stretch. The logs will be available in {logfile}.", "migration_0003_patching_sources_list": "Patching the sources.lists…", diff --git a/locales/fr.json b/locales/fr.json index e8ceca1ab..65e4d1e27 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -592,7 +592,7 @@ "app_action_broke_system": "Cette action semble avoir cassé des services importants : {services}", "apps_already_up_to_date": "Toutes les applications sont déjà à jour", "app_upgrade_stopped": "La mise à niveau de toutes les applications s'est arrêtée pour éviter tout dommage, car une application n'a pas pu être mise à niveau.", - "migration_0011_create_group": "Créer un groupe pour chaque utilisateur…", + "migration_0011_create_group": "Création d'un groupe pour chaque utilisateur…", "migration_0011_done": "Migration terminée. Vous êtes maintenant en mesure de gérer des groupes d'utilisateurs.", "migrations_must_provide_explicit_targets": "Vous devez fournir des cibles explicites lorsque vous utilisez '--skip' ou '--force-rerun'", "migrations_no_such_migration": "Il n'y a pas de migration appelée '{id}'", @@ -602,7 +602,7 @@ "migrations_not_pending_cant_skip": "Ces migrations ne sont pas en attente et ne peuvent donc pas être ignorées: {ids}", "migration_0011_can_not_backup_before_migration": "La sauvegarde du système n'a pas pu être terminée avant l'échec de la migration. Erreur: {erreur:s}", "migration_0011_migrate_permission": "Migration des autorisations des paramètres des applications vers LDAP…", - "migration_0011_migration_failed_trying_to_rollback": "Impossible de migrer… en essayant de restaurer le système.", + "migration_0011_migration_failed_trying_to_rollback": "La migration a échouée… Tentative de restauration du système.", "migration_0011_rollback_success": "Système restauré.", "migration_0011_update_LDAP_database": "Mise à jour de la base de données LDAP…", "system_groupname_exists": "Le nom de groupe existe déjà dans le groupe du systèmes", @@ -633,7 +633,7 @@ "permission_deleted": "Permission '{permission:s}' supprimée", "permission_deletion_failed": "Impossible de supprimer la permission '{permission}': {error}", "remove_user_of_group_not_allowed": "Vous n'êtes pas autorisé à supprimer l'utilisateur '{utilisateur: s}' dans le groupe '{groupe: s}'", - "migration_description_0011_setup_group_permission": "Configurer le groupe d'utilisateurs et configurer les autorisations pour les applications et les services", + "migration_description_0011_setup_group_permission": "Initialiser les groupes d'utilisateurs et autorisations pour les applications et les services", "migration_0011_LDAP_config_dirty": "Il semble que vous ayez personnalisé votre configuration LDAP. Pour cette migration, la configuration LDAP doit être mise à jour.\nVous devez enregistrer votre configuration actuelle, réintialiser la configuration d'origine en exécutant 'yunohost tools regen-conf -f', puis réessayer la migration", "migration_0011_LDAP_update_failed": "Impossible de mettre à jour LDAP. Erreur: {error: s}", "group_already_exist": "Le groupe {group} existe déjà", From 74c1478b741b5b904dd967d4b3cc41fc38821c2d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 27 Mar 2020 19:28:01 +0100 Subject: [PATCH 0735/3170] Require moulinette and ssowat to be at least 3.7 to avoid funky situations where regen-conf fails because moulinette ain't upgraded yet --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index b0de9032b..afd6c71b4 100644 --- a/debian/control +++ b/debian/control @@ -11,7 +11,7 @@ Package: yunohost Essential: yes Architecture: all Depends: ${python:Depends}, ${misc:Depends} - , moulinette (>= 2.7.1), ssowat (>= 2.7.1) + , moulinette (>= 3.7), ssowat (>= 3.7) , python-psutil, python-requests, python-dnspython, python-openssl , python-apt, python-miniupnpc, python-dbus, python-jinja2 , python-toml From 3b670b6cf90f1f95545a8938fd043594c76bb046 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 27 Mar 2020 19:52:24 +0100 Subject: [PATCH 0736/3170] Automatically remove existing system group if it exists when creating primary groups --- locales/en.json | 1 + src/yunohost/user.py | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index c4f82117e..9084bada8 100644 --- a/locales/en.json +++ b/locales/en.json @@ -221,6 +221,7 @@ "good_practices_about_user_password": "You are now about to define a new user password. The password should be at least 8 characters—though it is good practice to use longer password (i.e. a passphrase) and/or to a variation of characters (uppercase, lowercase, digits and special characters).", "group_already_exist": "Group {group} already exists", "group_already_exist_on_system": "Group {group} already exists in the system groups", + "group_already_exist_on_system_but_removing_it": "Group {group} already exists in the system groups, but YunoHost will remove it…", "group_created": "Group '{group}' created", "group_creation_failed": "Could not create the group '{group}': {error}", "group_cannot_edit_all_users": "The group 'all_users' cannot be edited manually. It is a special group meant to contain all users registered in YunoHost", diff --git a/src/yunohost/user.py b/src/yunohost/user.py index fdcac658d..34b367d7d 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -576,7 +576,11 @@ def user_group_create(operation_logger, groupname, gid=None, primary_group=False # Validate uniqueness of groupname in system group all_existing_groupnames = {x.gr_name for x in grp.getgrall()} if groupname in all_existing_groupnames: - raise YunohostError('group_already_exist_on_system', group=groupname) + if primary_group: + logger.warning('group_already_exist_on_system_but_removing_it', group=groupname) + subprocess.check_call("sed --in-place '/^%s:/d' /etc/group" % groupname, shell=True) + else: + raise YunohostError('group_already_exist_on_system', group=groupname) if not gid: # Get random GID From 9a1587303b6d7406a2f5a8b905157056bc141d93 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 27 Mar 2020 20:26:07 +0100 Subject: [PATCH 0737/3170] Clumsy wording / translations --- locales/en.json | 2 +- locales/fr.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/en.json b/locales/en.json index 9084bada8..4bde03919 100644 --- a/locales/en.json +++ b/locales/en.json @@ -285,7 +285,7 @@ "log_user_permission_update": "Update accesses for permission '{}'", "log_user_permission_reset": "Reset permission '{}'", "log_tools_maindomain": "Make '{}' the main domain", - "log_tools_migrations_migrate_forward": "Migrate forward", + "log_tools_migrations_migrate_forward": "Run migrations", "log_tools_postinstall": "Postinstall your YunoHost server", "log_tools_upgrade": "Upgrade system packages", "log_tools_shutdown": "Shutdown your server", diff --git a/locales/fr.json b/locales/fr.json index 65e4d1e27..e76c283e6 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -455,7 +455,7 @@ "log_user_delete": "Supprimer l’utilisateur '{}'", "log_user_update": "Mettre à jour les informations de l’utilisateur '{}'", "log_tools_maindomain": "Faire de '{}' le domaine principal", - "log_tools_migrations_migrate_forward": "Migrer vers", + "log_tools_migrations_migrate_forward": "Éxecuter les migrations", "log_tools_migrations_migrate_backward": "Revenir en arrière", "log_tools_postinstall": "Faire la post-installation de votre serveur YunoHost", "log_tools_upgrade": "Mettre à jour les paquets du système", From c37e3d89b4bc01a57df09c43a33bd251802548e6 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 27 Mar 2020 20:55:36 +0100 Subject: [PATCH 0738/3170] Update changelog for 3.7.0.9 --- debian/changelog | 40 +++++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/debian/changelog b/debian/changelog index 034f3b51c..11395f6af 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,13 @@ +yunohost (3.7.0.9) stable; urgency=low + + - [fix] Automatically remove existing system group if it exists when creating primary groups + - [fix] Require moulinette and ssowat to be at least 3.7 to avoid funky situations where regen-conf fails because moulinette ain't upgraded yet + - [i18n] Improve translations for Arabic, Bengali, Catalan, Chinese, Dutch, Esperanto, French, German, Greek, Hindi, Hungarian, Italian, Norwegian Bokmål, Occitan, Polish, Portuguese, Russian, Spanish + + Thanks to all contributors <3 ! (Aeris One, Allan N., Alvaro, amirale qt, Armando F., ButterflyOfFire, Elie G., Gustavo M., Jeroen F., Kayou, Mario, Mélanie C., Patrick B., Quentí, tituspijean, xaloc33, yalh76, Yasss Gurl) + + -- Alexandre Aubin Fri, 27 Mar 2020 21:00:00 +0000 + yunohost (3.7.0.8) stable; urgency=low - [fix] App_setting delete add if the key doesn't exist @@ -45,7 +55,7 @@ yunohost (3.7.0.3) testing; urgency=low - [mod] Some refactoring for permissions create/update/reset (#837) - [fix] Fix some edge cases for ynh_secure_remove and ynh_clean_check_starting - [i18n] Improve translations for French, Catalan - + -- Alexandre Aubin Sat, 23 Nov 2019 19:30:00 +0000 yunohost (3.7.0.2) testing; urgency=low @@ -122,9 +132,9 @@ yunohost (3.6.5.2) stable; urgency=low -- Alexandre Aubin Thu, 10 Oct 2019 01:00:00 +0000 yunohost (3.6.5.1) stable; urgency=low - + - [mod] Change maxretry of fail2ban from 6 to 10 (fe8fd1b) - + -- Alexandre Aubin Tue, 08 Oct 2019 20:00:00 +0000 yunohost (3.6.5) stable; urgency=low @@ -229,7 +239,7 @@ yunohost (3.6.1.2) testing; urgency=low yunohost (3.6.1.1) testing; urgency=low - [fix] Weird issue in slapd triggered by indexing uidNumber / gidNumber - + -- Alexandre Aubin Tue, 04 Jun 2019 15:10:00 +0000 yunohost (3.6.1) testing; urgency=low @@ -699,19 +709,19 @@ yunohost (3.0.0~beta1.2) testing; urgency=low Removing http2 also from yunohost_admin.conf since there still are some issues with wordpress ? - + -- Alexandre Aubin Tue, 08 May 2018 05:52:00 +0000 yunohost (3.0.0~beta1.1) testing; urgency=low Fixes in the postgresql migration - + -- Alexandre Aubin Sun, 06 May 2018 03:06:00 +0000 yunohost (3.0.0~beta1) testing; urgency=low Beta release for Stretch - + -- Alexandre Aubin Thu, 03 May 2018 03:04:45 +0000 yunohost (2.7.14) stable; urgency=low @@ -750,7 +760,7 @@ yunohost (2.7.13.4) testing; urgency=low * Increase backup filename length (Fixes by Bram <3) - + -- Alexandre Aubin Tue, 05 Jun 2018 18:22:00 +0000 yunohost (2.7.13.3) testing; urgency=low @@ -841,7 +851,7 @@ yunohost (2.7.11) testing; urgency=low * [helpers] Allow for 'or' in dependencies (#381) * [helpers] Tweak the usage of BACKUP_CORE_ONLY (#398) * [helpers] Tweak systemd config helpers (optional service name and template name) (#425) - * [i18n] Improve translations for Arabic, French, German, Occitan, Spanish + * [i18n] Improve translations for Arabic, French, German, Occitan, Spanish Thanks to all contributors (ariasuni, ljf, JimboJoe, frju365, Maniack, J-B Lescher, Josue, Aleks, Bram, jibec) and the several translators (ButterflyOfFire, Eric G., Cedric, J. Keerl, beyercenter, P. Gatzka, Quenti, bjarkan) <3 ! @@ -932,11 +942,11 @@ yunohost (2.7.3) testing; urgency=low Major changes : * [fix] Refactor/clean madness related to DynDNS (#353) - * [i18n] Improve french translation (#355) + * [i18n] Improve french translation (#355) * [fix] Use cryptorandom to generate password (#358) * [enh] Support for single app upgrade from the webadmin (#359) * [enh] Be able to give lock to son processes detached by systemctl (#367) - * [enh] Make MySQL dumps with a single transaction to ensure backup consistency (#370) + * [enh] Make MySQL dumps with a single transaction to ensure backup consistency (#370) Misc fixes/improvements : @@ -944,7 +954,7 @@ yunohost (2.7.3) testing; urgency=low * [fix] Allow dash at the beginning of app settings value (#357) * [enh] Handle root path in nginx conf (#361) * [enh] Add debugging in ldap init (#365) - * [fix] Fix app_upgrade_string with missing key + * [fix] Fix app_upgrade_string with missing key * [fix] Fix for change_url path normalizing with root url (#368) * [fix] Missing 'ask_path' string (#369) * [enh] Remove date from sql dump (#371) @@ -991,7 +1001,7 @@ yunohost (2.7.1) testing; urgency=low * [fix] Make read-only mount bind actually read-only (#343) (ljf) ### dyndns * Regen dnsmasq conf if it's not up to date :| (Alexandre Aubin) - * [fix] timeout on request to avoid blocking process (Laurent Peuch) + * [fix] timeout on request to avoid blocking process (Laurent Peuch) * Put request url in an intermediate variable (Alexandre Aubin) ### other * clean users.py (Laurent Peuch) @@ -1725,7 +1735,7 @@ yunohost (2.3.12) testing; urgency=low * [fix] Check for tty in root_handlers before remove it in bin/yunohost * [fix] Use dyndns.yunohost.org instead of dynhost.yunohost.org * [fix] Set found private key and don't validate it in dyndns_update - * [fix] Update first registered domain with DynDNS instead of current_host + * [fix] Update first registered domain with DynDNS instead of current_host * [i18n] Rename app_requirements_failed err named variable * [i18n] Update translations from Weblate @@ -1733,7 +1743,7 @@ yunohost (2.3.12) testing; urgency=low * [enh] Better message during service regenconf. * [enh] Display hook path on error message. * [enh] Use named arguments when calling m18n in service.py - * [enh] Use named arguments with m18n. + * [enh] Use named arguments with m18n. * [enh] Use named arguments for user_unknown string. -- Jérôme Lebleu Sat, 09 Apr 2016 12:13:10 +0200 From e54ca7206880d8be58c8e105ea2ce2535317a68e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 27 Mar 2020 21:40:22 +0100 Subject: [PATCH 0739/3170] On some weird setup, this folder and content ain't readable by group ... gotta make sure to make rx for group other slapd will explode --- data/hooks/conf_regen/06-slapd | 1 + 1 file changed, 1 insertion(+) diff --git a/data/hooks/conf_regen/06-slapd b/data/hooks/conf_regen/06-slapd index 35a8fcf2e..2fa108baa 100755 --- a/data/hooks/conf_regen/06-slapd +++ b/data/hooks/conf_regen/06-slapd @@ -81,6 +81,7 @@ do_post_regen() { 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/ [ -z "$regen_conf_files" ] && exit 0 From abe9440b1b73581ab634c3710b7cba80581bd333 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 27 Mar 2020 21:40:22 +0100 Subject: [PATCH 0740/3170] On some weird setup, this folder and content ain't readable by group ... gotta make sure to make rx for group other slapd will explode --- data/hooks/conf_regen/06-slapd | 1 + 1 file changed, 1 insertion(+) diff --git a/data/hooks/conf_regen/06-slapd b/data/hooks/conf_regen/06-slapd index 4f7adda78..50149392b 100755 --- a/data/hooks/conf_regen/06-slapd +++ b/data/hooks/conf_regen/06-slapd @@ -81,6 +81,7 @@ do_post_regen() { 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/ [ -z "$regen_conf_files" ] && exit 0 From 7311d05abfd4d182b3796a26dbb99b1228c78fb9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 27 Mar 2020 21:43:32 +0100 Subject: [PATCH 0741/3170] Update changelog for 3.7.0.10 --- debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian/changelog b/debian/changelog index 11395f6af..1f137ba16 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +yunohost (3.7.0.10) stable; urgency=low + + - [fix] On some weird setup, this folder and content ain't readable by group ... gotta make sure to make rx for group other slapd will explode + + -- Alexandre Aubin Fri, 27 Mar 2020 21:45:00 +0000 + yunohost (3.7.0.9) stable; urgency=low - [fix] Automatically remove existing system group if it exists when creating primary groups From 15be3898c5eba22a38b1ad727786e5db8d45d3c9 Mon Sep 17 00:00:00 2001 From: Kayou Date: Fri, 27 Mar 2020 22:12:30 +0000 Subject: [PATCH 0742/3170] Translated using Weblate (French) Currently translated at 99.7% (610 of 612 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 0679e5ad2..73f2804b8 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -261,7 +261,7 @@ "certmanager_cannot_read_cert": "Quelque chose s’est mal passé lors de la tentative d’ouverture du certificat actuel pour le domaine {domain:s} (fichier : {file:s}), la cause est : {reason:s}", "certmanager_cert_install_success_selfsigned": "Le certificat auto-signé est maintenant installé pour le domaine « {domain:s} »", "certmanager_cert_install_success": "Le certificat Let’s Encrypt est maintenant installé pour le domaine « {domain:s} »", - "certmanager_cert_renew_success": "Certificat Let's Encrypt renouvelé pour le domaine '{domain: s}'", + "certmanager_cert_renew_success": "Certificat Let's Encrypt renouvelé pour le domaine '{domain:s}'", "certmanager_old_letsencrypt_app_detected": "\nYunoHost a détecté que l’application « letsencrypt » est installé, ce qui est en conflit avec les nouvelles fonctionnalités de gestion intégrée de certificats dans YunoHost. Si vous souhaitez utiliser ces nouvelles fonctionnalités intégrées, veuillez lancer les commandes suivantes pour migrer votre installation :\n\n yunohost app remove letsencrypt\n yunohost domain cert-install\n\nN.B. : cela tentera de réinstaller les certificats de tous les domaines avec un certificat Let's Encrypt ou ceux auto-signés", "certmanager_cert_signing_failed": "Impossible de signer le nouveau certificat", "certmanager_no_cert_file": "Impossible de lire le fichier du certificat pour le domaine {domain:s} (fichier : {file:s})", @@ -270,7 +270,7 @@ "ldap_init_failed_to_create_admin": "L’initialisation de l'annuaire LDAP n’a pas réussi à créer l’utilisateur admin", "ssowat_persistent_conf_read_error": "Impossible de lire la configuration persistante de SSOwat : {error:s}. Modifiez le fichier /etc/ssowat/conf.json.persistent pour réparer la syntaxe JSON", "ssowat_persistent_conf_write_error": "Impossible de sauvegarder de la configuration persistante de SSOwat : {error:s}. Modifiez le fichier /etc/ssowat/conf.json.persistent pour réparer la syntaxe JSON", - "domain_cannot_remove_main": "Vous ne pouvez pas supprimer '{domain: s}' car il s'agit du domaine principal. Vous devez d'abord définir un autre domaine comme domaine principal à l'aide de 'yunohost domain main-domain -n ', voici la liste des domaines candidats. : {other_domains: s}", + "domain_cannot_remove_main": "Vous ne pouvez pas supprimer '{domain:s}' car il s'agit du domaine principal. Vous devez d'abord définir un autre domaine comme domaine principal à l'aide de 'yunohost domain main-domain -n ', voici la liste des domaines candidats. : {other_domains:s}", "certmanager_self_ca_conf_file_not_found": "Le fichier de configuration pour l’autorité du certificat auto-signé est introuvable (fichier : {file:s})", "certmanager_unable_to_parse_self_CA_name": "Impossible d’analyser le nom de l’autorité du certificat auto-signé (fichier : {file:s})", "mailbox_used_space_dovecot_down": "Le service de courriel Dovecot doit être démarré si vous souhaitez voir l’espace disque occupé par la messagerie", @@ -493,7 +493,7 @@ "backup_actually_backuping": "Création d'une archive de sauvegarde à partir des fichiers collectés …", "backup_mount_archive_for_restore": "Préparation de l'archive pour restauration …", "confirm_app_install_warning": "Avertissement : cette application peut fonctionner mais n'est pas bien intégrée dans YunoHost. Certaines fonctionnalités telles que l'authentification unique et la sauvegarde/restauration peuvent ne pas être disponibles. L'installer quand même ? [{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_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'.", "dyndns_could_not_check_available": "Impossible de vérifier si {domain:s} est disponible chez {provider:s}.", @@ -635,7 +635,7 @@ "remove_user_of_group_not_allowed": "Vous n'êtes pas autorisé à supprimer l'utilisateur '{utilisateur: s}' dans le groupe '{groupe: s}'", "migration_description_0011_setup_group_permission": "Initialiser les groupes d'utilisateurs et autorisations pour les applications et les services", "migration_0011_LDAP_config_dirty": "Il semble que vous ayez personnalisé votre configuration LDAP. Pour cette migration, la configuration LDAP doit être mise à jour.\nVous devez enregistrer votre configuration actuelle, réintialiser la configuration d'origine en exécutant 'yunohost tools regen-conf -f', puis réessayer la migration", - "migration_0011_LDAP_update_failed": "Impossible de mettre à jour LDAP. Erreur: {error: s}", + "migration_0011_LDAP_update_failed": "Impossible de mettre à jour LDAP. Erreur: {error:s}", "group_already_exist": "Le groupe {group} existe déjà", "group_already_exist_on_system": "Le groupe {group} existe déjà dans les groupes système", "group_cannot_be_edited": "Le groupe {group} ne peut pas être édité manuellement.", @@ -718,7 +718,7 @@ "apps_catalog_init_success": "Système de catalogue d'applications initialisé !", "apps_catalog_failed_to_download": "Impossible de télécharger le catalogue des applications {apps_catalog}:{error}", "diagnosis_mail_ougoing_port_25_blocked": "Le port sortant 25 semble être bloqué. Vous devriez essayer de le débloquer dans le panneau de configuration de votre fournisseur de services Internet (ou hébergeur). En attendant, le serveur ne pourra pas envoyer de courrier électronique à d'autres serveurs.", - "domain_cannot_remove_main_add_new_one": "Vous ne pouvez pas supprimer '{domain:s}' car il s'agit du domaine principal et de votre seul domaine. Vous devez d'abord ajouter un autre domaine à l'aide de 'yunohost domain add ', puis définir comme domaine principal à l'aide de ' yunohost domain main-domain -n ' et vous pouvez ensuite supprimer le domaine '{domaine: s}' à l'aide de 'yunohost domain remove {domain:s}'.'", + "domain_cannot_remove_main_add_new_one": "Vous ne pouvez pas supprimer '{domain:s}' car il s'agit du domaine principal et de votre seul domaine. Vous devez d'abord ajouter un autre domaine à l'aide de 'yunohost domain add ', puis définir comme domaine principal à l'aide de ' yunohost domain main-domain -n ' et vous pouvez ensuite supprimer le domaine '{domaine:s}' à l'aide de 'yunohost domain remove {domain:s}'.'", "diagnosis_security_vulnerable_to_meltdown_details": "Pour résoudre ce problème, vous devez mettre à niveau votre système et redémarrer pour charger le nouveau noyau Linux (ou contacter votre fournisseur de serveur si cela ne fonctionne pas). Voir https://meltdownattack.com/ pour plus d'informations.", "diagnosis_description_basesystem": "Système de base", "diagnosis_description_ip": "Connectivité Internet", From 4e822cccf368c9f4bdafb12e01eeff18677d1a61 Mon Sep 17 00:00:00 2001 From: Kayou Date: Fri, 27 Mar 2020 22:16:42 +0000 Subject: [PATCH 0743/3170] Translated using Weblate (German) Currently translated at 31.5% (193 of 612 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/locales/de.json b/locales/de.json index e4b3b0c05..ae3087900 100644 --- a/locales/de.json +++ b/locales/de.json @@ -5,7 +5,7 @@ "admin_password_changed": "Das Administrator-Kennwort wurde geändert", "app_already_installed": "{app:s} ist schon installiert", "app_argument_choice_invalid": "Wähle einen der folgenden Werte '{choices:s}' für das Argument '{name:s}'", - "app_argument_invalid": "Wähle einen gültigen Wert für das Argument '{name: s}': {error: s}", + "app_argument_invalid": "Wähle einen gültigen Wert für das Argument '{name:s}': {error:s}", "app_argument_required": "Argument '{name:s}' wird benötigt", "app_extraction_failed": "Installationsdateien konnten nicht entpackt werden", "app_id_invalid": "Falsche App-ID", @@ -302,45 +302,45 @@ "app_change_url_success": "{app:s} URL ist nun {domain:s}{path:s}", "backup_applying_method_borg": "Sende alle Dateien zur Sicherung ins borg-backup repository…", "invalid_url_format": "ungültiges URL Format", - "global_settings_bad_type_for_setting": "Falscher Typ für Einstellung {setting: s}. Empfangen: {receive_type: s}, aber erwartet: {expected_type: s}", - "global_settings_bad_choice_for_enum": "Falsche Wahl für die Einstellung {setting: s}. Habe '{choice: s}' erhalten, aber es stehen nur folgende Auswahlmöglichkeiten zur Verfügung: {available_choices: s}", - "file_does_not_exist": "Die Datei {path: s} existiert nicht.", + "global_settings_bad_type_for_setting": "Falscher Typ für Einstellung {setting:s}. Empfangen: {receive_type:s}, aber erwartet: {expected_type:s}", + "global_settings_bad_choice_for_enum": "Falsche Wahl für die Einstellung {setting:s}. Habe '{choice:s}' erhalten, aber es stehen nur folgende Auswahlmöglichkeiten zur Verfügung: {available_choices:s}", + "file_does_not_exist": "Die Datei {path:s} existiert nicht.", "experimental_feature": "Warnung: Diese Funktion ist experimentell und gilt nicht als stabil. Sie sollten sie nur verwenden, wenn Sie wissen, was Sie tun.", "error_when_removing_sftpuser_group": "Fehler beim Versuch, die Gruppe sftpusers zu entfernen", "edit_permission_with_group_all_users_not_allowed": "Sie dürfen die Berechtigung für die Gruppe \"all_users\" nicht bearbeiten. Verwenden Sie stattdessen \"yunohost user permission clear APP\" oder \"yunohost user permission add APP -u USER\".", "edit_group_not_allowed": "Du bist nicht berechtigt zum Bearbeiten der Gruppe {group: s}", - "dyndns_domain_not_provided": "Der Dyndns-Anbieter {provider: s} kann die Domain(s) {domain: s} nicht bereitstellen.", - "dyndns_could_not_check_available": "Konnte nicht überprüfen, ob {domain: s} auf {provider: s} verfügbar ist.", - "dyndns_could_not_check_provide": "Konnte nicht überprüft, ob {provider: s} die Domain(s) {domain: s} bereitstellen kann.", + "dyndns_domain_not_provided": "Der DynDNS-Anbieter {provider:s} kann die Domain(s) {domain:s} nicht bereitstellen.", + "dyndns_could_not_check_available": "Konnte nicht überprüfen, ob {domain:s} auf {provider:s} verfügbar ist.", + "dyndns_could_not_check_provide": "Konnte nicht überprüft, ob {provider:s} die Domain(s) {domain:s} bereitstellen kann.", "domain_dyndns_dynette_is_unreachable": "YunoHost dynette kann nicht erreicht werden, entweder ist Ihr YunoHost nicht korrekt mit dem Internet verbunden oder der dynette-Server ist inaktiv. Fehler: {error}", "domain_dns_conf_is_just_a_recommendation": "Dieser Befehl zeigt Ihnen, was die * empfohlene * Konfiguration ist. Die DNS-Konfiguration wird NICHT für Sie eingerichtet. Es liegt in Ihrer Verantwortung, Ihre DNS-Zone in Ihrem Registrar gemäß dieser Empfehlung zu konfigurieren.", "dpkg_lock_not_available": "Dieser Befehl kann momentan nicht ausgeführt werden, da anscheinend ein anderes Programm die Sperre von dpkg (dem Systempaket-Manager) verwendet", - "confirm_app_install_thirdparty": "WARNUNG! Das Installieren von Anwendungen von Drittanbietern kann die Integrität und Sicherheit Deines Systems beeinträchtigen. Du solltest es wahrscheinlich NICHT installieren, es sei denn, Du weisst, was Du tust. Bist du bereit, dieses Risiko einzugehen? [{answers: s}] ", - "confirm_app_install_danger": "WARNUNG! Diese Anwendung ist noch experimentell (wenn nicht ausdrücklich \"not working\"/\"funktioniert nicht\") und es ist wahrscheinlich, dass Dein System Schaden nimmt! Du solltest es wahrscheinlich NICHT installieren, es sei denn, Du weisst, was Du tust. Bist du bereit, dieses Risiko einzugehen? [{answers: s}] ", - "confirm_app_install_warning": "Warnung: Diese Anwendung funktioniert möglicherweise, ist jedoch nicht gut in YunoHost integriert. Einige Funktionen wie Single Sign-On und Backup / Restore sind möglicherweise nicht verfügbar. Trotzdem installieren? [{answers: s}] ", - "backup_with_no_restore_script_for_app": "Die App {app: s} hat kein Wiederherstellungsskript. Das Backup dieser App kann nicht automatisch wiederhergestellt werden.", - "backup_with_no_backup_script_for_app": "Die App {app: s} hat kein Sicherungsskript. Ignoriere es.", + "confirm_app_install_thirdparty": "WARNUNG! Das Installieren von Anwendungen von Drittanbietern kann die Integrität und Sicherheit Deines Systems beeinträchtigen. Du solltest es wahrscheinlich NICHT installieren, es sei denn, Du weisst, was Du tust. Bist du bereit, dieses Risiko einzugehen? [{answers:s}]", + "confirm_app_install_danger": "WARNUNG! Diese Anwendung ist noch experimentell (wenn nicht ausdrücklich \"not working\"/\"funktioniert nicht\") und es ist wahrscheinlich, dass Dein System Schaden nimmt! Du solltest es wahrscheinlich NICHT installieren, es sei denn, Du weisst, was Du tust. Bist du bereit, dieses Risiko einzugehen? [{answers:s}]", + "confirm_app_install_warning": "Warnung: Diese Anwendung funktioniert möglicherweise, ist jedoch nicht gut in YunoHost integriert. Einige Funktionen wie Single Sign-On und Backup / Restore sind möglicherweise nicht verfügbar. Trotzdem installieren? [{answers:s}] ", + "backup_with_no_restore_script_for_app": "Die App {app:s} hat kein Wiederherstellungsskript. Das Backup dieser App kann nicht automatisch wiederhergestellt werden.", + "backup_with_no_backup_script_for_app": "Die App {app:s} hat kein Sicherungsskript. Ignoriere es.", "backup_unable_to_organize_files": "Dateien im Archiv konnten nicht mit der schnellen Methode organisiert werden", - "backup_system_part_failed": "Der Systemteil '{part: s}' konnte nicht gesichert werden", - "backup_permission": "Sicherungsberechtigung für App {app: s}", + "backup_system_part_failed": "Der Systemteil '{part:s}' konnte nicht gesichert werden", + "backup_permission": "Sicherungsberechtigung für App {app:s}", "backup_output_symlink_dir_broken": "Ihr Archivverzeichnis '{path:s}' ist ein fehlerhafter Symlink. Vielleicht haben Sie vergessen, das Speichermedium, auf das er verweist, neu zu mounten oder einzustecken.", "backup_mount_archive_for_restore": "Archiv für Wiederherstellung vorbereiten…", "backup_method_tar_finished": "Tar-Backup-Archiv erstellt", - "backup_method_custom_finished": "Benutzerdefinierte Sicherungsmethode '{method: s}' beendet", + "backup_method_custom_finished": "Benutzerdefinierte Sicherungsmethode '{method:s}' beendet", "backup_method_copy_finished": "Sicherungskopie beendet", "backup_method_borg_finished": "Backup in Borg beendet", "backup_custom_need_mount_error": "Bei der benutzerdefinierten Sicherungsmethode ist beim Arbeitsschritt \"Braucht ein Einhängen/Verbinden\" (need_mount) ein Fehler aufgetreten", "backup_custom_mount_error": "Bei der benutzerdefinierten Sicherungsmethode ist beim Arbeitsschritt \"Einhängen/Verbinden\" ein Fehler aufgetreten", "backup_custom_backup_error": "Bei der benutzerdefinierten Sicherungsmethode ist beim Arbeitsschritt \"Sicherung\" ein Fehler aufgetreten", "backup_csv_creation_failed": "Die zur Wiederherstellung erforderliche CSV-Datei kann nicht erstellt werden", - "backup_couldnt_bind": "{Src: s} konnte nicht an {dest: s} angebunden werden.", + "backup_couldnt_bind": "{Src:s} konnte nicht an {dest:s} angebunden werden.", "backup_borg_not_implemented": "Die Borg-Sicherungsmethode ist noch nicht implementiert", "backup_ask_for_copying_if_needed": "Möchten Sie die Sicherung mit {size:s} MB temporär durchführen? (Dieser Weg wird verwendet, da einige Dateien nicht mit einer effizienteren Methode vorbereitet werden konnten).", "backup_actually_backuping": "Erstellt ein Backup-Archiv aus den gesammelten Dateien …", "ask_path": "Pfad", "ask_new_path": "Neuer Pfad", "ask_new_domain": "Neue Domain", - "apps_permission_restoration_failed": "Erteilen der Berechtigung '{permission: s}' für die Wiederherstellung der App {app: s} erforderlich", + "apps_permission_restoration_failed": "Erteilen der Berechtigung '{permission:s}' für die Wiederherstellung der App {app:s} erforderlich", "apps_permission_not_found": "Keine Berechtigung für die installierten Apps gefunden", "app_upgrade_some_app_failed": "Einige Anwendungen können nicht aktualisiert werden", "app_upgrade_app_name": "{App} wird jetzt aktualisiert…", @@ -357,7 +357,7 @@ "admin_password_too_long": "Bitte ein Passwort kürzer als 127 Zeichen wählen", "app_action_broke_system": "Diese Aktion scheint diese wichtigen Dienste unterbrochen zu haben: {services}", "apps_already_up_to_date": "Alle Apps sind bereits aktuell", - "backup_copying_to_organize_the_archive": "Kopieren von {size: s} MB, um das Archiv zu organisieren", + "backup_copying_to_organize_the_archive": "Kopieren von {size:s} MB, um das Archiv zu organisieren", "app_upgrade_stopped": "Das Upgrade aller Anwendungen wurde gestoppt, um mögliche Schäden zu vermeiden, da das Upgrade der vorherigen Anwendung fehlgeschlagen ist", "group_already_disallowed": "Die Gruppe '{group:s}' hat bereits die Berechtigungen '{permission:s}' für die App '{app:s}' deaktiviert", "global_settings_setting_security_ssh_compatibility": "Kompatibilität vs. Sicherheitskompromiss für den SSH-Server. Beeinflusst die Chiffren (und andere sicherheitsrelevante Aspekte)", From 19d9f4231b09be84e026409d9bf18bbe13441f05 Mon Sep 17 00:00:00 2001 From: Kayou Date: Fri, 27 Mar 2020 22:08:06 +0000 Subject: [PATCH 0744/3170] Translated using Weblate (Esperanto) Currently translated at 90.7% (555 of 612 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/eo/ --- locales/eo.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/locales/eo.json b/locales/eo.json index e204af4c6..906648120 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -102,7 +102,7 @@ "app_already_installed_cant_change_url": "Ĉi tiu app estas jam instalita. La URL ne povas esti ŝanĝita nur per ĉi tiu funkcio. Rigardu \"app changeurl\" se ĝi haveblas.", "app_not_correctly_installed": "{app:s} ŝajnas esti malĝuste instalita", "app_removed": "{app:s} forigita", - "backup_delete_error": "Ne povis forigi '{path: s}'", + "backup_delete_error": "Ne povis forigi '{path:s}'", "app_package_need_update": "La pakaĵo {app} devas esti ĝisdatigita por sekvi YunoHost-ŝanĝojn", "backup_nothings_done": "Nenio por ŝpari", "backup_applying_method_custom": "Nomante la kutiman rezervan metodon '{metodo:s}' …", @@ -248,7 +248,7 @@ "migrate_tsig_wait_3": "1 minuto …", "certmanager_conflicting_nginx_file": "Ne povis prepari domajnon por ACME-defio: la agordo de NGINX {filepath:s} konfliktas kaj unue devas esti forigita", "upgrading_packages": "Ĝisdatigi pakojn…", - "custom_app_url_required": "Vi devas provizi URL por altgradigi vian kutimon app {app: s}", + "custom_app_url_required": "Vi devas provizi URL por altgradigi vian kutimon app {app:s}", "service_reload_failed": "Ne povis reŝargi la servon '{service:s}'\n\nLastatempaj servaj protokoloj: {logs:s}", "packages_upgrade_failed": "Ne povis ĝisdatigi ĉiujn pakojn", "hook_json_return_error": "Ne povis legi revenon de hoko {path:s}. Eraro: {msg:s}. Kruda enhavo: {raw_content}", @@ -319,7 +319,7 @@ "hook_exec_not_terminated": "Skripto ne finiĝis ĝuste: {path:s}", "service_stopped": "Servo '{service:s}' ĉesis", "restore_failed": "Ne povis restarigi sistemon", - "confirm_app_install_danger": "Danĝero! Ĉi tiu apliko estas konata ankoraŭ eksperimenta (se ne eksplicite ne funkcias)! Vi probable ne devas instali ĝin krom se vi scias kion vi faras. NENIU SUBTENO estos provizita se ĉi tiu app ne funkcias aŭ rompas vian sistemon ... Se vi pretas riski ĉiuokaze, tajpu '{answers: s}'", + "confirm_app_install_danger": "Danĝero! Ĉi tiu apliko estas konata ankoraŭ eksperimenta (se ne eksplicite ne funkcias)! Vi probable ne devas instali ĝin krom se vi scias kion vi faras. NENIU SUBTENO estos provizita se ĉi tiu app ne funkcias aŭ rompas vian sistemon ... Se vi pretas riski ĉiuokaze, tajpu '{answers:s}'", "log_operation_unit_unclosed_properly": "Operaciumo ne estis fermita ĝuste", "upgrade_complete": "Ĝisdatigo kompleta", "upnp_enabled": "UPnP ŝaltis", @@ -384,7 +384,7 @@ "log_letsencrypt_cert_install": "Instalu atestilon Let's Encrypt sur '{}' regado", "log_dyndns_update": "Ĝisdatigu la IP asociita kun via subdominio YunoHost '{}'", "firewall_reload_failed": "Ne eblis reŝargi la firewall", - "confirm_app_install_warning": "Averto: Ĉi tiu aplikaĵo povas funkcii, sed ne bone integras en YunoHost. Iuj funkcioj kiel ekzemple aliĝilo kaj sekurkopio / restarigo eble ne haveblos. Instali ĉiuokaze? [{answers: s}] ", + "confirm_app_install_warning": "Averto: Ĉi tiu aplikaĵo povas funkcii, sed ne bone integras en YunoHost. Iuj funkcioj kiel ekzemple aliĝilo kaj sekurkopio / restarigo eble ne haveblos. Instali ĉiuokaze? [{answers:s}] ", "log_user_delete": "Forigi uzanton '{}'", "dyndns_ip_updated": "Ĝisdatigis vian IP sur DynDNS", "regenconf_up_to_date": "La agordo jam estas ĝisdatigita por kategorio '{category}'", @@ -488,7 +488,7 @@ "ldap_initialized": "LDAP inicializis", "migrate_tsig_not_needed": "Vi ne ŝajnas uzi DynDNS-domajnon, do neniu migrado necesas.", "certmanager_domain_cert_not_selfsigned": "La atestilo por domajno {domajno:s} ne estas mem-subskribita. Ĉu vi certas, ke vi volas anstataŭigi ĝin? (Uzu '--force' por fari tion.)", - "certmanager_unable_to_parse_self_CA_name": "Ne povis trapasi nomon de mem-subskribinta aŭtoritato (dosiero: {file: s})", + "certmanager_unable_to_parse_self_CA_name": "Ne povis trapasi nomon de mem-subskribinta aŭtoritato (dosiero: {file:s})", "log_selfsigned_cert_install": "Instalu mem-subskribitan atestilon sur '{}' domajno", "log_tools_reboot": "Reklamu vian servilon", "certmanager_cert_install_success": "Ni Ĉifru atestilon nun instalitan por la domajno '{domain:s}'", @@ -519,7 +519,7 @@ "monitor_not_enabled": "Servila monitorado estas malŝaltita", "diagnosis_debian_version_error": "Ne povis retrovi la Debianan version: {error}", "hook_list_by_invalid": "Ĉi tiu posedaĵo ne povas esti uzata por listigi hokojn", - "confirm_app_install_thirdparty": "Danĝero! Ĉi tiu apliko ne estas parto de la aplika katalogo de Yunohost. Instali triajn aplikojn povas kompromiti la integrecon kaj sekurecon de via sistemo. Vi probable ne devas instali ĝin krom se vi scias kion vi faras. NENIU SUBTENO estos provizita se ĉi tiu app ne funkcias aŭ rompas vian sistemon ... Se vi pretas riski ĉiuokaze, tajpu '{answers: s}'", + "confirm_app_install_thirdparty": "Danĝero! Ĉi tiu apliko ne estas parto de la aplika katalogo de Yunohost. Instali triajn aplikojn povas kompromiti la integrecon kaj sekurecon de via sistemo. Vi probable ne devas instali ĝin krom se vi scias kion vi faras. NENIU SUBTENO estos provizita se ĉi tiu app ne funkcias aŭ rompas vian sistemon ... Se vi pretas riski ĉiuokaze, tajpu '{answers:s}'", "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Permesu uzon de (malaktuala) DSA-hostkey por la agordo de daemon SSH", "dyndns_domain_not_provided": "Provizanto DynDNS {provider:s} ne povas provizi domajnon {domain:s}.", "backup_unable_to_organize_files": "Ne povis uzi la rapidan metodon por organizi dosierojn en la ar archiveivo", @@ -652,7 +652,7 @@ "diagnosis_http_could_not_diagnose": "Ne povis diagnozi, ĉu atingeblas domajno de ekstere. Eraro: {error}", "diagnosis_http_ok": "Domajno {domain} atingeblas de ekstere.", "diagnosis_http_unreachable": "Domajno {domain} estas atingebla per HTTP de ekstere.", - "domain_cannot_remove_main_add_new_one": "Vi ne povas forigi '{domain:s}' ĉar ĝi estas la ĉefa domajno kaj via sola domajno, vi devas unue aldoni alian domajnon uzante ''yunohost domain add ', tiam agordi kiel ĉefan domajnon uzante 'yunohost domain main-domain -n ' kaj tiam vi povas forigi la domajnon' {domain: s} 'uzante' yunohost domain remove {domain:s} '.'", + "domain_cannot_remove_main_add_new_one": "Vi ne povas forigi '{domain:s}' ĉar ĝi estas la ĉefa domajno kaj via sola domajno, vi devas unue aldoni alian domajnon uzante ''yunohost domain add ', tiam agordi kiel ĉefan domajnon uzante 'yunohost domain main-domain -n ' kaj tiam vi povas forigi la domajnon' {domain:s} 'uzante' yunohost domain remove {domain:s} '.'", "permission_require_account": "Permesilo {permission} nur havas sencon por uzantoj, kiuj havas konton, kaj tial ne rajtas esti ebligitaj por vizitantoj.", "diagnosis_found_warnings": "Trovitaj {warnings} ero (j) kiuj povus esti plibonigitaj por {category}.", "diagnosis_everything_ok": "Ĉio aspektas bone por {category}!", From 8f05c898780c033dcb3560c0a0ad3bdc1ee2b192 Mon Sep 17 00:00:00 2001 From: Kayou Date: Fri, 27 Mar 2020 23:34:23 +0100 Subject: [PATCH 0745/3170] Merge pull request #899 from yunohost-bot/weblate-yunohost-core Update from Weblate --- locales/de.json | 36 ++++++++++++++++++------------------ locales/eo.json | 14 +++++++------- locales/fr.json | 10 +++++----- 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/locales/de.json b/locales/de.json index c115e0573..ac9efddb2 100644 --- a/locales/de.json +++ b/locales/de.json @@ -5,7 +5,7 @@ "admin_password_changed": "Das Administrator-Kennwort wurde geändert", "app_already_installed": "{app:s} ist schon installiert", "app_argument_choice_invalid": "Wähle einen der folgenden Werte '{choices:s}' für das Argument '{name:s}'", - "app_argument_invalid": "Wähle einen gültigen Wert für das Argument '{name: s}': {error: s}", + "app_argument_invalid": "Wähle einen gültigen Wert für das Argument '{name:s}': {error:s}", "app_argument_required": "Argument '{name:s}' wird benötigt", "app_extraction_failed": "Installationsdateien konnten nicht entpackt werden", "app_id_invalid": "Falsche App-ID", @@ -302,45 +302,45 @@ "app_change_url_success": "{app:s} URL ist nun {domain:s}{path:s}", "backup_applying_method_borg": "Sende alle Dateien zur Sicherung ins borg-backup repository…", "invalid_url_format": "ungültiges URL Format", - "global_settings_bad_type_for_setting": "Falscher Typ für Einstellung {setting: s}. Empfangen: {receive_type: s}, aber erwartet: {expected_type: s}", - "global_settings_bad_choice_for_enum": "Falsche Wahl für die Einstellung {setting: s}. Habe '{choice: s}' erhalten, aber es stehen nur folgende Auswahlmöglichkeiten zur Verfügung: {available_choices: s}", - "file_does_not_exist": "Die Datei {path: s} existiert nicht.", + "global_settings_bad_type_for_setting": "Falscher Typ für Einstellung {setting:s}. Empfangen: {receive_type:s}, aber erwartet: {expected_type:s}", + "global_settings_bad_choice_for_enum": "Falsche Wahl für die Einstellung {setting:s}. Habe '{choice:s}' erhalten, aber es stehen nur folgende Auswahlmöglichkeiten zur Verfügung: {available_choices:s}", + "file_does_not_exist": "Die Datei {path:s} existiert nicht.", "experimental_feature": "Warnung: Diese Funktion ist experimentell und gilt nicht als stabil. Sie sollten sie nur verwenden, wenn Sie wissen, was Sie tun.", "error_when_removing_sftpuser_group": "Fehler beim Versuch, die Gruppe sftpusers zu entfernen", "edit_permission_with_group_all_users_not_allowed": "Sie dürfen die Berechtigung für die Gruppe \"all_users\" nicht bearbeiten. Verwenden Sie stattdessen \"yunohost user permission clear APP\" oder \"yunohost user permission add APP -u USER\".", "edit_group_not_allowed": "Du bist nicht berechtigt zum Bearbeiten der Gruppe {group: s}", - "dyndns_domain_not_provided": "Der Dyndns-Anbieter {provider: s} kann die Domain(s) {domain: s} nicht bereitstellen.", - "dyndns_could_not_check_available": "Konnte nicht überprüfen, ob {domain: s} auf {provider: s} verfügbar ist.", - "dyndns_could_not_check_provide": "Konnte nicht überprüft, ob {provider: s} die Domain(s) {domain: s} bereitstellen kann.", + "dyndns_domain_not_provided": "Der DynDNS-Anbieter {provider:s} kann die Domain(s) {domain:s} nicht bereitstellen.", + "dyndns_could_not_check_available": "Konnte nicht überprüfen, ob {domain:s} auf {provider:s} verfügbar ist.", + "dyndns_could_not_check_provide": "Konnte nicht überprüft, ob {provider:s} die Domain(s) {domain:s} bereitstellen kann.", "domain_dyndns_dynette_is_unreachable": "YunoHost dynette kann nicht erreicht werden, entweder ist Ihr YunoHost nicht korrekt mit dem Internet verbunden oder der dynette-Server ist inaktiv. Fehler: {error}", "domain_dns_conf_is_just_a_recommendation": "Dieser Befehl zeigt Ihnen, was die * empfohlene * Konfiguration ist. Die DNS-Konfiguration wird NICHT für Sie eingerichtet. Es liegt in Ihrer Verantwortung, Ihre DNS-Zone in Ihrem Registrar gemäß dieser Empfehlung zu konfigurieren.", "dpkg_lock_not_available": "Dieser Befehl kann momentan nicht ausgeführt werden, da anscheinend ein anderes Programm die Sperre von dpkg (dem Systempaket-Manager) verwendet", - "confirm_app_install_thirdparty": "WARNUNG! Das Installieren von Anwendungen von Drittanbietern kann die Integrität und Sicherheit Deines Systems beeinträchtigen. Du solltest es wahrscheinlich NICHT installieren, es sei denn, Du weisst, was Du tust. Bist du bereit, dieses Risiko einzugehen? [{answers: s}] ", - "confirm_app_install_danger": "WARNUNG! Diese Anwendung ist noch experimentell (wenn nicht ausdrücklich \"not working\"/\"funktioniert nicht\") und es ist wahrscheinlich, dass Dein System Schaden nimmt! Du solltest es wahrscheinlich NICHT installieren, es sei denn, Du weisst, was Du tust. Bist du bereit, dieses Risiko einzugehen? [{answers: s}] ", - "confirm_app_install_warning": "Warnung: Diese Anwendung funktioniert möglicherweise, ist jedoch nicht gut in YunoHost integriert. Einige Funktionen wie Single Sign-On und Backup / Restore sind möglicherweise nicht verfügbar. Trotzdem installieren? [{answers: s}] ", - "backup_with_no_restore_script_for_app": "Die App {app: s} hat kein Wiederherstellungsskript. Das Backup dieser App kann nicht automatisch wiederhergestellt werden.", - "backup_with_no_backup_script_for_app": "Die App {app: s} hat kein Sicherungsskript. Ignoriere es.", + "confirm_app_install_thirdparty": "WARNUNG! Das Installieren von Anwendungen von Drittanbietern kann die Integrität und Sicherheit Deines Systems beeinträchtigen. Du solltest es wahrscheinlich NICHT installieren, es sei denn, Du weisst, was Du tust. Bist du bereit, dieses Risiko einzugehen? [{answers:s}]", + "confirm_app_install_danger": "WARNUNG! Diese Anwendung ist noch experimentell (wenn nicht ausdrücklich \"not working\"/\"funktioniert nicht\") und es ist wahrscheinlich, dass Dein System Schaden nimmt! Du solltest es wahrscheinlich NICHT installieren, es sei denn, Du weisst, was Du tust. Bist du bereit, dieses Risiko einzugehen? [{answers:s}]", + "confirm_app_install_warning": "Warnung: Diese Anwendung funktioniert möglicherweise, ist jedoch nicht gut in YunoHost integriert. Einige Funktionen wie Single Sign-On und Backup / Restore sind möglicherweise nicht verfügbar. Trotzdem installieren? [{answers:s}] ", + "backup_with_no_restore_script_for_app": "Die App {app:s} hat kein Wiederherstellungsskript. Das Backup dieser App kann nicht automatisch wiederhergestellt werden.", + "backup_with_no_backup_script_for_app": "Die App {app:s} hat kein Sicherungsskript. Ignoriere es.", "backup_unable_to_organize_files": "Dateien im Archiv konnten nicht mit der schnellen Methode organisiert werden", - "backup_system_part_failed": "Der Systemteil '{part: s}' konnte nicht gesichert werden", - "backup_permission": "Sicherungsberechtigung für App {app: s}", + "backup_system_part_failed": "Der Systemteil '{part:s}' konnte nicht gesichert werden", + "backup_permission": "Sicherungsberechtigung für App {app:s}", "backup_output_symlink_dir_broken": "Ihr Archivverzeichnis '{path:s}' ist ein fehlerhafter Symlink. Vielleicht haben Sie vergessen, das Speichermedium, auf das er verweist, neu zu mounten oder einzustecken.", "backup_mount_archive_for_restore": "Archiv für Wiederherstellung vorbereiten…", "backup_method_tar_finished": "Tar-Backup-Archiv erstellt", - "backup_method_custom_finished": "Benutzerdefinierte Sicherungsmethode '{method: s}' beendet", + "backup_method_custom_finished": "Benutzerdefinierte Sicherungsmethode '{method:s}' beendet", "backup_method_copy_finished": "Sicherungskopie beendet", "backup_method_borg_finished": "Backup in Borg beendet", "backup_custom_need_mount_error": "Bei der benutzerdefinierten Sicherungsmethode ist beim Arbeitsschritt \"Braucht ein Einhängen/Verbinden\" (need_mount) ein Fehler aufgetreten", "backup_custom_mount_error": "Bei der benutzerdefinierten Sicherungsmethode ist beim Arbeitsschritt \"Einhängen/Verbinden\" ein Fehler aufgetreten", "backup_custom_backup_error": "Bei der benutzerdefinierten Sicherungsmethode ist beim Arbeitsschritt \"Sicherung\" ein Fehler aufgetreten", "backup_csv_creation_failed": "Die zur Wiederherstellung erforderliche CSV-Datei kann nicht erstellt werden", - "backup_couldnt_bind": "{Src: s} konnte nicht an {dest: s} angebunden werden.", + "backup_couldnt_bind": "{Src:s} konnte nicht an {dest:s} angebunden werden.", "backup_borg_not_implemented": "Die Borg-Sicherungsmethode ist noch nicht implementiert", "backup_ask_for_copying_if_needed": "Möchten Sie die Sicherung mit {size:s} MB temporär durchführen? (Dieser Weg wird verwendet, da einige Dateien nicht mit einer effizienteren Methode vorbereitet werden konnten).", "backup_actually_backuping": "Erstellt ein Backup-Archiv aus den gesammelten Dateien …", "ask_path": "Pfad", "ask_new_path": "Neuer Pfad", "ask_new_domain": "Neue Domain", - "apps_permission_restoration_failed": "Erteilen der Berechtigung '{permission: s}' für die Wiederherstellung der App {app: s} erforderlich", + "apps_permission_restoration_failed": "Erteilen der Berechtigung '{permission:s}' für die Wiederherstellung der App {app:s} erforderlich", "apps_permission_not_found": "Keine Berechtigung für die installierten Apps gefunden", "app_upgrade_some_app_failed": "Einige Anwendungen können nicht aktualisiert werden", "app_upgrade_app_name": "{App} wird jetzt aktualisiert…", @@ -357,7 +357,7 @@ "admin_password_too_long": "Bitte ein Passwort kürzer als 127 Zeichen wählen", "app_action_broke_system": "Diese Aktion scheint diese wichtigen Dienste unterbrochen zu haben: {services}", "apps_already_up_to_date": "Alle Apps sind bereits aktuell", - "backup_copying_to_organize_the_archive": "Kopieren von {size: s} MB, um das Archiv zu organisieren", + "backup_copying_to_organize_the_archive": "Kopieren von {size:s} MB, um das Archiv zu organisieren", "app_upgrade_stopped": "Das Upgrade aller Anwendungen wurde gestoppt, um mögliche Schäden zu vermeiden, da das Upgrade der vorherigen Anwendung fehlgeschlagen ist", "group_already_disallowed": "Die Gruppe '{group:s}' hat bereits die Berechtigungen '{permission:s}' für die App '{app:s}' deaktiviert", "global_settings_setting_security_ssh_compatibility": "Kompatibilität vs. Sicherheitskompromiss für den SSH-Server. Beeinflusst die Chiffren (und andere sicherheitsrelevante Aspekte)", diff --git a/locales/eo.json b/locales/eo.json index e204af4c6..906648120 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -102,7 +102,7 @@ "app_already_installed_cant_change_url": "Ĉi tiu app estas jam instalita. La URL ne povas esti ŝanĝita nur per ĉi tiu funkcio. Rigardu \"app changeurl\" se ĝi haveblas.", "app_not_correctly_installed": "{app:s} ŝajnas esti malĝuste instalita", "app_removed": "{app:s} forigita", - "backup_delete_error": "Ne povis forigi '{path: s}'", + "backup_delete_error": "Ne povis forigi '{path:s}'", "app_package_need_update": "La pakaĵo {app} devas esti ĝisdatigita por sekvi YunoHost-ŝanĝojn", "backup_nothings_done": "Nenio por ŝpari", "backup_applying_method_custom": "Nomante la kutiman rezervan metodon '{metodo:s}' …", @@ -248,7 +248,7 @@ "migrate_tsig_wait_3": "1 minuto …", "certmanager_conflicting_nginx_file": "Ne povis prepari domajnon por ACME-defio: la agordo de NGINX {filepath:s} konfliktas kaj unue devas esti forigita", "upgrading_packages": "Ĝisdatigi pakojn…", - "custom_app_url_required": "Vi devas provizi URL por altgradigi vian kutimon app {app: s}", + "custom_app_url_required": "Vi devas provizi URL por altgradigi vian kutimon app {app:s}", "service_reload_failed": "Ne povis reŝargi la servon '{service:s}'\n\nLastatempaj servaj protokoloj: {logs:s}", "packages_upgrade_failed": "Ne povis ĝisdatigi ĉiujn pakojn", "hook_json_return_error": "Ne povis legi revenon de hoko {path:s}. Eraro: {msg:s}. Kruda enhavo: {raw_content}", @@ -319,7 +319,7 @@ "hook_exec_not_terminated": "Skripto ne finiĝis ĝuste: {path:s}", "service_stopped": "Servo '{service:s}' ĉesis", "restore_failed": "Ne povis restarigi sistemon", - "confirm_app_install_danger": "Danĝero! Ĉi tiu apliko estas konata ankoraŭ eksperimenta (se ne eksplicite ne funkcias)! Vi probable ne devas instali ĝin krom se vi scias kion vi faras. NENIU SUBTENO estos provizita se ĉi tiu app ne funkcias aŭ rompas vian sistemon ... Se vi pretas riski ĉiuokaze, tajpu '{answers: s}'", + "confirm_app_install_danger": "Danĝero! Ĉi tiu apliko estas konata ankoraŭ eksperimenta (se ne eksplicite ne funkcias)! Vi probable ne devas instali ĝin krom se vi scias kion vi faras. NENIU SUBTENO estos provizita se ĉi tiu app ne funkcias aŭ rompas vian sistemon ... Se vi pretas riski ĉiuokaze, tajpu '{answers:s}'", "log_operation_unit_unclosed_properly": "Operaciumo ne estis fermita ĝuste", "upgrade_complete": "Ĝisdatigo kompleta", "upnp_enabled": "UPnP ŝaltis", @@ -384,7 +384,7 @@ "log_letsencrypt_cert_install": "Instalu atestilon Let's Encrypt sur '{}' regado", "log_dyndns_update": "Ĝisdatigu la IP asociita kun via subdominio YunoHost '{}'", "firewall_reload_failed": "Ne eblis reŝargi la firewall", - "confirm_app_install_warning": "Averto: Ĉi tiu aplikaĵo povas funkcii, sed ne bone integras en YunoHost. Iuj funkcioj kiel ekzemple aliĝilo kaj sekurkopio / restarigo eble ne haveblos. Instali ĉiuokaze? [{answers: s}] ", + "confirm_app_install_warning": "Averto: Ĉi tiu aplikaĵo povas funkcii, sed ne bone integras en YunoHost. Iuj funkcioj kiel ekzemple aliĝilo kaj sekurkopio / restarigo eble ne haveblos. Instali ĉiuokaze? [{answers:s}] ", "log_user_delete": "Forigi uzanton '{}'", "dyndns_ip_updated": "Ĝisdatigis vian IP sur DynDNS", "regenconf_up_to_date": "La agordo jam estas ĝisdatigita por kategorio '{category}'", @@ -488,7 +488,7 @@ "ldap_initialized": "LDAP inicializis", "migrate_tsig_not_needed": "Vi ne ŝajnas uzi DynDNS-domajnon, do neniu migrado necesas.", "certmanager_domain_cert_not_selfsigned": "La atestilo por domajno {domajno:s} ne estas mem-subskribita. Ĉu vi certas, ke vi volas anstataŭigi ĝin? (Uzu '--force' por fari tion.)", - "certmanager_unable_to_parse_self_CA_name": "Ne povis trapasi nomon de mem-subskribinta aŭtoritato (dosiero: {file: s})", + "certmanager_unable_to_parse_self_CA_name": "Ne povis trapasi nomon de mem-subskribinta aŭtoritato (dosiero: {file:s})", "log_selfsigned_cert_install": "Instalu mem-subskribitan atestilon sur '{}' domajno", "log_tools_reboot": "Reklamu vian servilon", "certmanager_cert_install_success": "Ni Ĉifru atestilon nun instalitan por la domajno '{domain:s}'", @@ -519,7 +519,7 @@ "monitor_not_enabled": "Servila monitorado estas malŝaltita", "diagnosis_debian_version_error": "Ne povis retrovi la Debianan version: {error}", "hook_list_by_invalid": "Ĉi tiu posedaĵo ne povas esti uzata por listigi hokojn", - "confirm_app_install_thirdparty": "Danĝero! Ĉi tiu apliko ne estas parto de la aplika katalogo de Yunohost. Instali triajn aplikojn povas kompromiti la integrecon kaj sekurecon de via sistemo. Vi probable ne devas instali ĝin krom se vi scias kion vi faras. NENIU SUBTENO estos provizita se ĉi tiu app ne funkcias aŭ rompas vian sistemon ... Se vi pretas riski ĉiuokaze, tajpu '{answers: s}'", + "confirm_app_install_thirdparty": "Danĝero! Ĉi tiu apliko ne estas parto de la aplika katalogo de Yunohost. Instali triajn aplikojn povas kompromiti la integrecon kaj sekurecon de via sistemo. Vi probable ne devas instali ĝin krom se vi scias kion vi faras. NENIU SUBTENO estos provizita se ĉi tiu app ne funkcias aŭ rompas vian sistemon ... Se vi pretas riski ĉiuokaze, tajpu '{answers:s}'", "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Permesu uzon de (malaktuala) DSA-hostkey por la agordo de daemon SSH", "dyndns_domain_not_provided": "Provizanto DynDNS {provider:s} ne povas provizi domajnon {domain:s}.", "backup_unable_to_organize_files": "Ne povis uzi la rapidan metodon por organizi dosierojn en la ar archiveivo", @@ -652,7 +652,7 @@ "diagnosis_http_could_not_diagnose": "Ne povis diagnozi, ĉu atingeblas domajno de ekstere. Eraro: {error}", "diagnosis_http_ok": "Domajno {domain} atingeblas de ekstere.", "diagnosis_http_unreachable": "Domajno {domain} estas atingebla per HTTP de ekstere.", - "domain_cannot_remove_main_add_new_one": "Vi ne povas forigi '{domain:s}' ĉar ĝi estas la ĉefa domajno kaj via sola domajno, vi devas unue aldoni alian domajnon uzante ''yunohost domain add ', tiam agordi kiel ĉefan domajnon uzante 'yunohost domain main-domain -n ' kaj tiam vi povas forigi la domajnon' {domain: s} 'uzante' yunohost domain remove {domain:s} '.'", + "domain_cannot_remove_main_add_new_one": "Vi ne povas forigi '{domain:s}' ĉar ĝi estas la ĉefa domajno kaj via sola domajno, vi devas unue aldoni alian domajnon uzante ''yunohost domain add ', tiam agordi kiel ĉefan domajnon uzante 'yunohost domain main-domain -n ' kaj tiam vi povas forigi la domajnon' {domain:s} 'uzante' yunohost domain remove {domain:s} '.'", "permission_require_account": "Permesilo {permission} nur havas sencon por uzantoj, kiuj havas konton, kaj tial ne rajtas esti ebligitaj por vizitantoj.", "diagnosis_found_warnings": "Trovitaj {warnings} ero (j) kiuj povus esti plibonigitaj por {category}.", "diagnosis_everything_ok": "Ĉio aspektas bone por {category}!", diff --git a/locales/fr.json b/locales/fr.json index e76c283e6..53aedc1ae 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -261,7 +261,7 @@ "certmanager_cannot_read_cert": "Quelque chose s’est mal passé lors de la tentative d’ouverture du certificat actuel pour le domaine {domain:s} (fichier : {file:s}), la cause est : {reason:s}", "certmanager_cert_install_success_selfsigned": "Le certificat auto-signé est maintenant installé pour le domaine « {domain:s} »", "certmanager_cert_install_success": "Le certificat Let’s Encrypt est maintenant installé pour le domaine « {domain:s} »", - "certmanager_cert_renew_success": "Certificat Let's Encrypt renouvelé pour le domaine '{domain: s}'", + "certmanager_cert_renew_success": "Certificat Let's Encrypt renouvelé pour le domaine '{domain:s}'", "certmanager_old_letsencrypt_app_detected": "\nYunoHost a détecté que l’application « letsencrypt » est installé, ce qui est en conflit avec les nouvelles fonctionnalités de gestion intégrée de certificats dans YunoHost. Si vous souhaitez utiliser ces nouvelles fonctionnalités intégrées, veuillez lancer les commandes suivantes pour migrer votre installation :\n\n yunohost app remove letsencrypt\n yunohost domain cert-install\n\nN.B. : cela tentera de réinstaller les certificats de tous les domaines avec un certificat Let's Encrypt ou ceux auto-signés", "certmanager_cert_signing_failed": "Impossible de signer le nouveau certificat", "certmanager_no_cert_file": "Impossible de lire le fichier du certificat pour le domaine {domain:s} (fichier : {file:s})", @@ -270,7 +270,7 @@ "ldap_init_failed_to_create_admin": "L’initialisation de l'annuaire LDAP n’a pas réussi à créer l’utilisateur admin", "ssowat_persistent_conf_read_error": "Impossible de lire la configuration persistante de SSOwat : {error:s}. Modifiez le fichier /etc/ssowat/conf.json.persistent pour réparer la syntaxe JSON", "ssowat_persistent_conf_write_error": "Impossible de sauvegarder de la configuration persistante de SSOwat : {error:s}. Modifiez le fichier /etc/ssowat/conf.json.persistent pour réparer la syntaxe JSON", - "domain_cannot_remove_main": "Vous ne pouvez pas supprimer '{domain: s}' car il s'agit du domaine principal. Vous devez d'abord définir un autre domaine comme domaine principal à l'aide de 'yunohost domain main-domain -n ', voici la liste des domaines candidats. : {other_domains: s}", + "domain_cannot_remove_main": "Vous ne pouvez pas supprimer '{domain:s}' car il s'agit du domaine principal. Vous devez d'abord définir un autre domaine comme domaine principal à l'aide de 'yunohost domain main-domain -n ', voici la liste des domaines candidats. : {other_domains:s}", "certmanager_self_ca_conf_file_not_found": "Le fichier de configuration pour l’autorité du certificat auto-signé est introuvable (fichier : {file:s})", "certmanager_unable_to_parse_self_CA_name": "Impossible d’analyser le nom de l’autorité du certificat auto-signé (fichier : {file:s})", "mailbox_used_space_dovecot_down": "Le service de courriel Dovecot doit être démarré si vous souhaitez voir l’espace disque occupé par la messagerie", @@ -493,7 +493,7 @@ "backup_actually_backuping": "Création d'une archive de sauvegarde à partir des fichiers collectés …", "backup_mount_archive_for_restore": "Préparation de l'archive pour restauration …", "confirm_app_install_warning": "Avertissement : cette application peut fonctionner mais n'est pas bien intégrée dans YunoHost. Certaines fonctionnalités telles que l'authentification unique et la sauvegarde/restauration peuvent ne pas être disponibles. L'installer quand même ? [{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_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'.", "dyndns_could_not_check_available": "Impossible de vérifier si {domain:s} est disponible chez {provider:s}.", @@ -635,7 +635,7 @@ "remove_user_of_group_not_allowed": "Vous n'êtes pas autorisé à supprimer l'utilisateur '{utilisateur: s}' dans le groupe '{groupe: s}'", "migration_description_0011_setup_group_permission": "Initialiser les groupes d'utilisateurs et autorisations pour les applications et les services", "migration_0011_LDAP_config_dirty": "Il semble que vous ayez personnalisé votre configuration LDAP. Pour cette migration, la configuration LDAP doit être mise à jour.\nVous devez enregistrer votre configuration actuelle, réintialiser la configuration d'origine en exécutant 'yunohost tools regen-conf -f', puis réessayer la migration", - "migration_0011_LDAP_update_failed": "Impossible de mettre à jour LDAP. Erreur: {error: s}", + "migration_0011_LDAP_update_failed": "Impossible de mettre à jour LDAP. Erreur: {error:s}", "group_already_exist": "Le groupe {group} existe déjà", "group_already_exist_on_system": "Le groupe {group} existe déjà dans les groupes système", "group_cannot_be_edited": "Le groupe {group} ne peut pas être édité manuellement.", @@ -718,7 +718,7 @@ "apps_catalog_init_success": "Système de catalogue d'applications initialisé !", "apps_catalog_failed_to_download": "Impossible de télécharger le catalogue des applications {apps_catalog}:{error}", "diagnosis_mail_ougoing_port_25_blocked": "Le port sortant 25 semble être bloqué. Vous devriez essayer de le débloquer dans le panneau de configuration de votre fournisseur de services Internet (ou hébergeur). En attendant, le serveur ne pourra pas envoyer de courrier électronique à d'autres serveurs.", - "domain_cannot_remove_main_add_new_one": "Vous ne pouvez pas supprimer '{domain:s}' car il s'agit du domaine principal et de votre seul domaine. Vous devez d'abord ajouter un autre domaine à l'aide de 'yunohost domain add ', puis définir comme domaine principal à l'aide de ' yunohost domain main-domain -n ' et vous pouvez ensuite supprimer le domaine '{domaine: s}' à l'aide de 'yunohost domain remove {domain:s}'.'", + "domain_cannot_remove_main_add_new_one": "Vous ne pouvez pas supprimer '{domain:s}' car il s'agit du domaine principal et de votre seul domaine. Vous devez d'abord ajouter un autre domaine à l'aide de 'yunohost domain add ', puis définir comme domaine principal à l'aide de ' yunohost domain main-domain -n ' et vous pouvez ensuite supprimer le domaine '{domaine:s}' à l'aide de 'yunohost domain remove {domain:s}'.'", "diagnosis_security_vulnerable_to_meltdown_details": "Pour résoudre ce problème, vous devez mettre à niveau votre système et redémarrer pour charger le nouveau noyau Linux (ou contacter votre fournisseur de serveur si cela ne fonctionne pas). Voir https://meltdownattack.com/ pour plus d'informations.", "diagnosis_description_basesystem": "Système de base", "diagnosis_description_ip": "Connectivité Internet", From 9b698e669d2e3af5f24b23e5127f748f341429f3 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Fri, 27 Mar 2020 23:59:35 +0100 Subject: [PATCH 0746/3170] Fix those damn locales --- locales/de.json | 2 +- locales/fr.json | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/locales/de.json b/locales/de.json index ac9efddb2..5587a4e48 100644 --- a/locales/de.json +++ b/locales/de.json @@ -308,7 +308,7 @@ "experimental_feature": "Warnung: Diese Funktion ist experimentell und gilt nicht als stabil. Sie sollten sie nur verwenden, wenn Sie wissen, was Sie tun.", "error_when_removing_sftpuser_group": "Fehler beim Versuch, die Gruppe sftpusers zu entfernen", "edit_permission_with_group_all_users_not_allowed": "Sie dürfen die Berechtigung für die Gruppe \"all_users\" nicht bearbeiten. Verwenden Sie stattdessen \"yunohost user permission clear APP\" oder \"yunohost user permission add APP -u USER\".", - "edit_group_not_allowed": "Du bist nicht berechtigt zum Bearbeiten der Gruppe {group: s}", + "edit_group_not_allowed": "Du bist nicht berechtigt zum Bearbeiten der Gruppe {group:s}", "dyndns_domain_not_provided": "Der DynDNS-Anbieter {provider:s} kann die Domain(s) {domain:s} nicht bereitstellen.", "dyndns_could_not_check_available": "Konnte nicht überprüfen, ob {domain:s} auf {provider:s} verfügbar ist.", "dyndns_could_not_check_provide": "Konnte nicht überprüft, ob {provider:s} die Domain(s) {domain:s} bereitstellen kann.", diff --git a/locales/fr.json b/locales/fr.json index 53aedc1ae..f175a5704 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -280,7 +280,7 @@ "certmanager_domain_not_resolved_locally": "Le domaine {domain:s} ne peut être résolu depuis votre serveur YunoHost. Cela peut se produire si vous avez récemment modifié votre enregistrement DNS. Si c'est le cas, merci d’attendre quelques heures qu’il se propage. Si le problème persiste, envisager d’ajouter {domain:s} au fichier /etc/hosts. (Si vous savez ce que vous faites, utilisez --no-checks pour désactiver ces vérifications.)", "certmanager_http_check_timeout": "Expiration du délai lorsque le serveur a essayé de se contacter lui-même via HTTP en utilisant l'adresse IP public {ip:s} du domaine {domain:s}. Vous rencontrez peut-être un problème d’hairpinning ou alors le pare-feu/routeur en amont de votre serveur est mal configuré.", "certmanager_couldnt_fetch_intermediate_cert": "Expiration du délai lors de la tentative de récupération du certificat intermédiaire depuis Let’s Encrypt. L’installation ou le renouvellement du certificat a été annulé. Veuillez réessayer plus tard.", - "appslist_retrieve_bad_format": "Impossible de lire la liste des applications extraites '{appslist: s}'", + "appslist_retrieve_bad_format": "Impossible de lire la liste des applications extraites '{appslist:s}'", "domain_hostname_failed": "Échec de l’utilisation d’un nouveau nom d’hôte. Cela pourrait causer des soucis plus tard (peut-être que ça n’en causera pas).", "yunohost_ca_creation_success": "L’autorité de certification locale créée.", "appslist_name_already_tracked": "Une liste d'applications enregistrées portant le nom {name:s} existe déjà.", @@ -607,11 +607,11 @@ "migration_0011_update_LDAP_database": "Mise à jour de la base de données LDAP…", "system_groupname_exists": "Le nom de groupe existe déjà dans le groupe du systèmes", "tools_update_failed_to_app_fetchlist": "Impossible de mettre à jour les listes d'applications de YunoHost car: {error}", - "user_already_in_group": "L'utilisateur '{user:}' est déjà dans le groupe '{group: s}'", - "user_not_in_group": "L'utilisateur '{user: s}' ne fait pas partie du groupe {group: s}", + "user_already_in_group": "L'utilisateur '{user:}' est déjà dans le groupe '{group:s}'", + "user_not_in_group": "L'utilisateur '{user:s}' ne fait pas partie du groupe {group:s}", "migration_0011_backup_before_migration": "Création d'une sauvegarde des paramètres de la base de données LDAP et des applications avant la migration.", "permission_not_found": "Autorisation '{permission:s}' introuvable", - "permission_name_not_valid": "Choisissez un nom d'autorisation autorisé pour '{permission: s}'", + "permission_name_not_valid": "Choisissez un nom d'autorisation autorisé pour '{permission:s}'", "permission_update_failed": "Impossible de mettre à jour la permission '{permission}': {error}", "permission_generated": "Base de données des autorisations mise à jour", "permission_updated": "Permission '{permission:s}' mise à jour", @@ -626,13 +626,13 @@ "migrations_success_forward": "Migration {id} terminée", "need_define_permission_before": "Redéfinissez l'autorisation à l'aide de 'yunohost user permission add -u USER' avant de supprimer un groupe autorisé", "operation_interrupted": "L'opération a été interrompue manuellement ?", - "permission_already_clear": "L'autorisation '{permission: s}' est déjà vide pour l'application {app: s}", + "permission_already_clear": "L'autorisation '{permission:s}' est déjà vide pour l'application {app:s}", "permission_already_exist": "L'autorisation '{permission}' existe déjà", "permission_created": "Permission '{permission:s}' créée", "permission_creation_failed": "Impossible de créer l'autorisation '{permission}': {erreur}", "permission_deleted": "Permission '{permission:s}' supprimée", "permission_deletion_failed": "Impossible de supprimer la permission '{permission}': {error}", - "remove_user_of_group_not_allowed": "Vous n'êtes pas autorisé à supprimer l'utilisateur '{utilisateur: s}' dans le groupe '{groupe: s}'", + "remove_user_of_group_not_allowed": "Vous n'êtes pas autorisé à supprimer l'utilisateur '{utilisateur:s}' dans le groupe '{groupe:s}'", "migration_description_0011_setup_group_permission": "Initialiser les groupes d'utilisateurs et autorisations pour les applications et les services", "migration_0011_LDAP_config_dirty": "Il semble que vous ayez personnalisé votre configuration LDAP. Pour cette migration, la configuration LDAP doit être mise à jour.\nVous devez enregistrer votre configuration actuelle, réintialiser la configuration d'origine en exécutant 'yunohost tools regen-conf -f', puis réessayer la migration", "migration_0011_LDAP_update_failed": "Impossible de mettre à jour LDAP. Erreur: {error:s}", From 5ded6ecbe6677e9de21ca9e9272e1943428a667b Mon Sep 17 00:00:00 2001 From: Kay0u Date: Sat, 28 Mar 2020 00:04:32 +0100 Subject: [PATCH 0747/3170] Merge resolved --- locales/oc.json | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/locales/oc.json b/locales/oc.json index 55f7a002a..a06520ae5 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -439,13 +439,8 @@ "log_service_regen_conf": "Regenerar la configuracion sistèma de « {} »", "log_user_create": "Ajustar l’utilizaire « {} »", "log_user_delete": "Levar l’utilizaire « {} »", -<<<<<<< HEAD - "log_user_update": "Actualizar las informacions a l’utilizaire « {} »", - "log_tools_maindomain": "Far venir « {} » lo domeni màger", -======= "log_user_update": "Actualizar las informacions de l’utilizaire « {} »", - "log_domain_main_domain": "Far venir « {} » lo domeni màger", ->>>>>>> b968dff2... Translated using Weblate (Occitan) + "log_tools_maindomain": "Far venir « {} » lo domeni màger", "log_tools_migrations_migrate_forward": "Migrar", "log_tools_migrations_migrate_backward": "Tornar en arrièr", "log_tools_postinstall": "Realizar la post installacion del servidor YunoHost", From 3574527311eaa3c9a169cd8e6d04283a3a2e47ad Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 28 Mar 2020 00:33:52 +0100 Subject: [PATCH 0748/3170] Fix mess due to automatic translation tools ~_~ --- locales/ar.json | 6 +++--- locales/ca.json | 4 ++-- locales/de.json | 6 +++--- locales/eo.json | 20 ++++++++++---------- locales/fr.json | 12 ++++++------ locales/nl.json | 2 +- locales/oc.json | 18 +++++++++--------- 7 files changed, 34 insertions(+), 34 deletions(-) diff --git a/locales/ar.json b/locales/ar.json index 936b54d2e..6bcbb9333 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -182,7 +182,7 @@ "firewall_reloaded": "The firewall has been reloaded", "firewall_rules_cmd_failed": "Some firewall rules commands have failed. For more information, see the log.", "format_datetime_short": "%m/%d/%Y %I:%M %p", - "global_settings_bad_choice_for_enum": "Bad value for setting {setting:s}, received {received_type:s}, except {expected_type:s}", + "global_settings_bad_choice_for_enum": "Bad value for setting {setting:s}, received {choice:s}, except {available_choices:s}", "global_settings_bad_type_for_setting": "Bad type for setting {setting:s}, received {received_type:s}, except {expected_type:s}", "global_settings_cant_open_settings": "Failed to open settings file, reason: {reason:s}", "global_settings_cant_serialize_settings": "Failed to serialize settings data, reason: {reason:s}", @@ -227,8 +227,8 @@ "migrations_current_target": "Migration target is {}", "migrations_error_failed_to_load_migration": "ERROR: failed to load migration {number} {name}", "migrations_forward": "Migrating forward", - "migrations_loading_migration": "Loading migration {number} {name}…", - "migrations_migration_has_failed": "Migration {number} {name} has failed with exception {exception}, aborting", + "migrations_loading_migration": "Loading migration {id}…", + "migrations_migration_has_failed": "Migration {id} has failed with exception {exception}, aborting", "migrations_no_migrations_to_run": "No migrations to run", "migrations_show_currently_running_migration": "Running migration {number} {name}…", "migrations_show_last_migration": "Last ran migration is {}", diff --git a/locales/ca.json b/locales/ca.json index 5d9ed318d..61d832c30 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -167,7 +167,7 @@ "domain_created": "S'ha creat el domini", "domain_creation_failed": "No s'ha pogut crear el domini {domain}: {error}", "domain_deleted": "S'ha eliminat el domini", - "domain_deletion_failed": "No s'ha pogut eliminar el domini {domini}: {error}", + "domain_deletion_failed": "No s'ha pogut eliminar el domini {domain}: {error}", "domain_exists": "El domini ja existeix", "app_action_cannot_be_ran_because_required_services_down": "Aquests serveis necessaris haurien d'estar funcionant per poder executar aquesta acció: {services} Intenteu reiniciar-los per continuar (i possiblement investigar perquè estan aturats).", "domain_dns_conf_is_just_a_recommendation": "Aquesta ordre mostra la configuració *recomanada*. En cap cas fa la configuració del DNS. És la vostra responsabilitat configurar la zona DNS en el vostre registrar en acord amb aquesta recomanació.", @@ -459,7 +459,7 @@ "service_description_yunohost-firewall": "Gestiona els ports de connexió oberts i tancats als serveis", "service_disable_failed": "No s'han pogut fer que el servei «{service:s}» no comenci a l'arrancada.\n\nRegistres recents: {logs:s}", "service_disabled": "El servei «{service:s}» ja no començarà al arrancar el sistema.", - "service_enable_failed": "No s'ha pogut fer que el servei «{service:s}» comenci automàticament a l'arrancada.\n\nRegistres recents: {log:s}", + "service_enable_failed": "No s'ha pogut fer que el servei «{service:s}» comenci automàticament a l'arrancada.\n\nRegistres recents: {logs:s}", "service_enabled": "El servei «{service:s}» començarà automàticament durant l'arrancada del sistema.", "service_no_log": "No hi ha cap registre pel servei «{service:s}»", "service_regen_conf_is_deprecated": "«yunohost service regen-conf» està desfasat! Utilitzeu «yunohost tools regen-conf» en el seu lloc.", diff --git a/locales/de.json b/locales/de.json index 5587a4e48..d259fb7b9 100644 --- a/locales/de.json +++ b/locales/de.json @@ -302,7 +302,7 @@ "app_change_url_success": "{app:s} URL ist nun {domain:s}{path:s}", "backup_applying_method_borg": "Sende alle Dateien zur Sicherung ins borg-backup repository…", "invalid_url_format": "ungültiges URL Format", - "global_settings_bad_type_for_setting": "Falscher Typ für Einstellung {setting:s}. Empfangen: {receive_type:s}, aber erwartet: {expected_type:s}", + "global_settings_bad_type_for_setting": "Falscher Typ für Einstellung {setting:s}. Empfangen: {received_type:s}, aber erwartet: {expected_type:s}", "global_settings_bad_choice_for_enum": "Falsche Wahl für die Einstellung {setting:s}. Habe '{choice:s}' erhalten, aber es stehen nur folgende Auswahlmöglichkeiten zur Verfügung: {available_choices:s}", "file_does_not_exist": "Die Datei {path:s} existiert nicht.", "experimental_feature": "Warnung: Diese Funktion ist experimentell und gilt nicht als stabil. Sie sollten sie nur verwenden, wenn Sie wissen, was Sie tun.", @@ -333,7 +333,7 @@ "backup_custom_mount_error": "Bei der benutzerdefinierten Sicherungsmethode ist beim Arbeitsschritt \"Einhängen/Verbinden\" ein Fehler aufgetreten", "backup_custom_backup_error": "Bei der benutzerdefinierten Sicherungsmethode ist beim Arbeitsschritt \"Sicherung\" ein Fehler aufgetreten", "backup_csv_creation_failed": "Die zur Wiederherstellung erforderliche CSV-Datei kann nicht erstellt werden", - "backup_couldnt_bind": "{Src:s} konnte nicht an {dest:s} angebunden werden.", + "backup_couldnt_bind": "{src:s} konnte nicht an {dest:s} angebunden werden.", "backup_borg_not_implemented": "Die Borg-Sicherungsmethode ist noch nicht implementiert", "backup_ask_for_copying_if_needed": "Möchten Sie die Sicherung mit {size:s} MB temporär durchführen? (Dieser Weg wird verwendet, da einige Dateien nicht mit einer effizienteren Methode vorbereitet werden konnten).", "backup_actually_backuping": "Erstellt ein Backup-Archiv aus den gesammelten Dateien …", @@ -343,7 +343,7 @@ "apps_permission_restoration_failed": "Erteilen der Berechtigung '{permission:s}' für die Wiederherstellung der App {app:s} erforderlich", "apps_permission_not_found": "Keine Berechtigung für die installierten Apps gefunden", "app_upgrade_some_app_failed": "Einige Anwendungen können nicht aktualisiert werden", - "app_upgrade_app_name": "{App} wird jetzt aktualisiert…", + "app_upgrade_app_name": "{app} wird jetzt aktualisiert…", "app_upgrade_several_apps": "Die folgenden Apps werden aktualisiert: {apps}", "app_start_restore": "Anwendung {app} wird wiederhergestellt…", "app_start_backup": "Sammeln von Dateien, die für {app} gesichert werden sollen…", diff --git a/locales/eo.json b/locales/eo.json index 906648120..5047fff09 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -74,7 +74,7 @@ "backup_invalid_archive": "Ĉi tio ne estas rezerva ar archiveivo", "ask_current_admin_password": "Pasvorto pri aktuala administrado", "backup_creation_failed": "Ne povis krei la rezervan ar archiveivon", - "backup_hook_unknown": "La rezerva hoko '{hoko:s}' estas nekonata", + "backup_hook_unknown": "La rezerva hoko '{hook:s}' estas nekonata", "backup_custom_backup_error": "Propra rezerva metodo ne povis preterpasi la paŝon \"sekurkopio\"", "ask_main_domain": "Ĉefa domajno", "backup_method_tar_finished": "TAR-rezerva ar archiveivo kreita", @@ -97,15 +97,15 @@ "app_start_backup": "Kolekti dosierojn por esti subtenata por la '{app}' …", "backup_archive_name_exists": "Rezerva arkivo kun ĉi tiu nomo jam ekzistas.", "backup_applying_method_tar": "Krei la rezervan TAR-ar archiveivon …", - "backup_method_custom_finished": "Propra rezerva metodo '{metodo:s}' finiĝis", - "appslist_retrieve_error": "Ne eblas akiri la forajn listojn '{appslist:s}': {eraro:s}", + "backup_method_custom_finished": "Propra rezerva metodo '{method:s}' finiĝis", + "appslist_retrieve_error": "Ne eblas akiri la forajn listojn '{appslist:s}': {error:s}", "app_already_installed_cant_change_url": "Ĉi tiu app estas jam instalita. La URL ne povas esti ŝanĝita nur per ĉi tiu funkcio. Rigardu \"app changeurl\" se ĝi haveblas.", "app_not_correctly_installed": "{app:s} ŝajnas esti malĝuste instalita", "app_removed": "{app:s} forigita", "backup_delete_error": "Ne povis forigi '{path:s}'", "app_package_need_update": "La pakaĵo {app} devas esti ĝisdatigita por sekvi YunoHost-ŝanĝojn", "backup_nothings_done": "Nenio por ŝpari", - "backup_applying_method_custom": "Nomante la kutiman rezervan metodon '{metodo:s}' …", + "backup_applying_method_custom": "Nomante la kutiman rezervan metodon '{method:s}' …", "appslist_fetched": "Ĝisdatigis la liston de aplikoj '{appslist:s}'", "backup_app_failed": "Ne eblis rezervi la programon '{app:s}'", "app_upgrade_some_app_failed": "Iuj aplikoj ne povis esti altgradigitaj", @@ -268,7 +268,7 @@ "pattern_positive_number": "Devas esti pozitiva nombro", "monitor_stats_file_not_found": "Ne povis trovi la statistikan dosieron", "certmanager_error_no_A_record": "Neniu DNS 'A' rekordo trovita por '{domain:s}'. Vi bezonas atentigi vian domajnan nomon al via maŝino por povi instali atestilon Lasu-Ĉifri. (Se vi scias, kion vi faras, uzu '--no-checks' por malŝalti tiujn ĉekojn.)", - "update_apt_cache_failed": "Ne eblis ĝisdatigi la kaŝmemoron de APT (paka administranto de Debian). Jen rubujo de la sources.list-linioj, kiuj povus helpi identigi problemajn liniojn:\n{sourcelist}", + "update_apt_cache_failed": "Ne eblis ĝisdatigi la kaŝmemoron de APT (paka administranto de Debian). Jen rubujo de la sources.list-linioj, kiuj povus helpi identigi problemajn liniojn:\n{sourceslist}", "migrations_no_migrations_to_run": "Neniuj migradoj por funkcii", "executing_command": "Plenumanta komandon '{command:s}' …", "diagnosis_no_apps": "Neniu tia instalita app", @@ -332,7 +332,7 @@ "tools_upgrade_at_least_one": "Bonvolu specifi '--apps' aŭ '--system'", "service_already_stopped": "La servo '{service:s}' jam ĉesis", "unit_unknown": "Nekonata unuo '{unit:s}'", - "migration_0003_modified_files": "Bonvolu noti, ke la jenaj dosieroj estis trovitaj mane kaj modifitaj kaj povus esti anstataŭigitaj sekve de la ĝisdatigo: {manual_modified_files}", + "migration_0003_modified_files": "Bonvolu noti, ke la jenaj dosieroj estis trovitaj mane kaj modifitaj kaj povus esti anstataŭigitaj sekve de la ĝisdatigo: {manually_modified_files}", "tools_upgrade_cant_both": "Ne eblas ĝisdatigi ambaŭ sistemon kaj programojn samtempe", "restore_extracting": "Eltirante bezonatajn dosierojn el la ar theivo…", "upnp_port_open_failed": "Ne povis malfermi havenon per UPnP", @@ -390,7 +390,7 @@ "regenconf_up_to_date": "La agordo jam estas ĝisdatigita por kategorio '{category}'", "migration_0003_patching_sources_list": "Patching the sources.lists …", "global_settings_setting_security_ssh_compatibility": "Kongruo vs sekureca kompromiso por la SSH-servilo. Afektas la ĉifradojn (kaj aliajn aspektojn pri sekureco)", - "migrations_need_to_accept_disclaimer": "Por funkciigi la migradon {id}, via devas akcepti la sekvan malakcepton:\n---\n{malavantaĝo}\n---\nSe vi akceptas funkcii la migradon, bonvolu rekonduki la komandon kun la opcio '--accept-disclaimer'.", + "migrations_need_to_accept_disclaimer": "Por funkciigi la migradon {id}, via devas akcepti la sekvan malakcepton:\n---\n{disclaimer}\n---\nSe vi akceptas funkcii la migradon, bonvolu rekonduki la komandon kun la opcio '--accept-disclaimer'.", "regenconf_file_remove_failed": "Ne povis forigi la agordodosieron '{conf}'", "not_enough_disk_space": "Ne sufiĉe libera spaco sur '{path:s}'", "migration_0006_disclaimer": "YunoHost nun atendas, ke la pasvortoj de admin kaj radiko estos sinkronigitaj. Ĉi tiu migrado anstataŭigas vian radikan pasvorton kun la administran pasvorton.", @@ -465,10 +465,10 @@ "global_settings_cant_open_settings": "Ne eblis malfermi agordojn, tial: {reason:s}", "user_created": "Uzanto kreita", "service_description_avahi-daemon": "Permesas al vi atingi vian servilon uzante 'yunohost.local' en via loka reto", - "certmanager_attempt_to_replace_valid_cert": "Vi provas anstataŭigi bonan kaj validan atestilon por domajno {domajno:s}! (Uzu --forte pretervidi)", + "certmanager_attempt_to_replace_valid_cert": "Vi provas anstataŭigi bonan kaj validan atestilon por domajno {domain:s}! (Uzu --forte pretervidi)", "monitor_stats_period_unavailable": "Ne ekzistas disponeblaj statistikoj por la periodo", "regenconf_updated": "Agordo ĝisdatigita por '{category}'", - "update_apt_cache_warning": "Io iris malbone dum la ĝisdatigo de la kaŝmemoro de APT (paka administranto de Debian). Jen rubujo de la sources.list-linioj, kiuj povus helpi identigi problemajn liniojn:\n{sourcelist}", + "update_apt_cache_warning": "Io iris malbone dum la ĝisdatigo de la kaŝmemoro de APT (paka administranto de Debian). Jen rubujo de la sources.list-linioj, kiuj povus helpi identigi problemajn liniojn:\n{sourceslist}", "regenconf_dry_pending_applying": "Kontrolado de pritraktata agordo, kiu estus aplikita por kategorio '{category}'…", "regenconf_file_copy_failed": "Ne povis kopii la novan agordodosieron '{new}' al '{conf}'", "global_settings_setting_example_string": "Ekzemple korda elekto", @@ -487,7 +487,7 @@ "mysql_db_creation_failed": "Ne povis krei MySQL-datumbazon", "ldap_initialized": "LDAP inicializis", "migrate_tsig_not_needed": "Vi ne ŝajnas uzi DynDNS-domajnon, do neniu migrado necesas.", - "certmanager_domain_cert_not_selfsigned": "La atestilo por domajno {domajno:s} ne estas mem-subskribita. Ĉu vi certas, ke vi volas anstataŭigi ĝin? (Uzu '--force' por fari tion.)", + "certmanager_domain_cert_not_selfsigned": "La atestilo por domajno {domain:s} ne estas mem-subskribita. Ĉu vi certas, ke vi volas anstataŭigi ĝin? (Uzu '--force' por fari tion.)", "certmanager_unable_to_parse_self_CA_name": "Ne povis trapasi nomon de mem-subskribinta aŭtoritato (dosiero: {file:s})", "log_selfsigned_cert_install": "Instalu mem-subskribitan atestilon sur '{}' domajno", "log_tools_reboot": "Reklamu vian servilon", diff --git a/locales/fr.json b/locales/fr.json index f175a5704..4ea52c8af 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -242,7 +242,7 @@ "user_home_creation_failed": "Impossible de créer le dossier personnel de l’utilisateur", "user_info_failed": "Impossible de récupérer les informations de l’utilisateur", "user_unknown": "L'utilisateur {user:s} est inconnu", - "user_update_failed": "Impossible de mettre à jour l'utilisateur {utilisateur}: {erreur}", + "user_update_failed": "Impossible de mettre à jour l'utilisateur {user}: {error}", "user_updated": "L’utilisateur a été modifié", "yunohost_already_installed": "YunoHost est déjà installé", "yunohost_ca_creation_failed": "Impossible de créer l’autorité de certification", @@ -320,7 +320,7 @@ "backup_archive_system_part_not_available": "La partie '{part:s}' du système n’est pas disponible dans cette sauvegarde", "backup_archive_mount_failed": "Le montage de l’archive de sauvegarde a échoué", "backup_archive_writing_error": "Impossible d'ajouter des fichiers '{source:s}' (nommés dans l'archive : '{dest:s}') à sauvegarder dans l'archive compressée '{archive:s}'", - "backup_ask_for_copying_if_needed": "Voulez-vous effectuer la sauvegarde en utilisant {taille:s} temporairement? (Cette méthode est utilisée car certains fichiers n'ont pas pu être préparés avec une méthode plus efficace.)", + "backup_ask_for_copying_if_needed": "Voulez-vous effectuer la sauvegarde en utilisant {size:s} temporairement? (Cette méthode est utilisée car certains fichiers n'ont pas pu être préparés avec une méthode plus efficace.)", "backup_borg_not_implemented": "La méthode de sauvegarde Borg n’est pas encore implémentée", "backup_cant_mount_uncompress_archive": "Impossible de monter en lecture seule le dossier de l’archive décompressée", "backup_copying_to_organize_the_archive": "Copie de {size:s} Mo pour organiser l’archive", @@ -466,7 +466,7 @@ "migration_description_0005_postgresql_9p4_to_9p6": "Migration des bases de données de PostgreSQL 9.4 vers PostgreSQL 9.6", "migration_0005_postgresql_94_not_installed": "PostgreSQL n’a pas été installé sur votre système. Rien à faire !", "migration_0005_postgresql_96_not_installed": "PostgreSQL 9.4 a été trouvé et installé, mais pas PostgreSQL 9.6 !? Quelque chose d’étrange a dû arriver à votre système… :(", - "migration_0005_not_enough_space": "Laissez suffisamment d'espace disponible dans {chemin} pour exécuter la migration.", + "migration_0005_not_enough_space": "Laissez suffisamment d'espace disponible dans {path} pour exécuter la migration.", "recommend_to_add_first_user": "La post-installation est terminée mais YunoHost a besoin d’au moins un utilisateur pour fonctionner correctement. Vous devez en ajouter un en utilisant la commande 'yunohost user create $nomdutilisateur' ou bien via l’interface d’administration web.", "service_description_php7.0-fpm": "Exécute des applications écrites en PHP avec NGINX", "users_available": "Liste des utilisateurs disponibles :", @@ -600,7 +600,7 @@ "migration_description_0012_postgresql_password_to_md5_authentication": "Forcer l'authentification PostgreSQL à utiliser MD5 pour les connexions locales", "migrations_exclusive_options": "'auto', '--skip' et '--force-rerun' sont des options mutuellement exclusives.", "migrations_not_pending_cant_skip": "Ces migrations ne sont pas en attente et ne peuvent donc pas être ignorées: {ids}", - "migration_0011_can_not_backup_before_migration": "La sauvegarde du système n'a pas pu être terminée avant l'échec de la migration. Erreur: {erreur:s}", + "migration_0011_can_not_backup_before_migration": "La sauvegarde du système n'a pas pu être terminée avant l'échec de la migration. Erreur: {error:s}", "migration_0011_migrate_permission": "Migration des autorisations des paramètres des applications vers LDAP…", "migration_0011_migration_failed_trying_to_rollback": "La migration a échouée… Tentative de restauration du système.", "migration_0011_rollback_success": "Système restauré.", @@ -629,10 +629,10 @@ "permission_already_clear": "L'autorisation '{permission:s}' est déjà vide pour l'application {app:s}", "permission_already_exist": "L'autorisation '{permission}' existe déjà", "permission_created": "Permission '{permission:s}' créée", - "permission_creation_failed": "Impossible de créer l'autorisation '{permission}': {erreur}", + "permission_creation_failed": "Impossible de créer l'autorisation '{permission}': {error}", "permission_deleted": "Permission '{permission:s}' supprimée", "permission_deletion_failed": "Impossible de supprimer la permission '{permission}': {error}", - "remove_user_of_group_not_allowed": "Vous n'êtes pas autorisé à supprimer l'utilisateur '{utilisateur:s}' dans le groupe '{groupe:s}'", + "remove_user_of_group_not_allowed": "Vous n'êtes pas autorisé à supprimer l'utilisateur '{user:s}' dans le groupe '{group:s}'", "migration_description_0011_setup_group_permission": "Initialiser les groupes d'utilisateurs et autorisations pour les applications et les services", "migration_0011_LDAP_config_dirty": "Il semble que vous ayez personnalisé votre configuration LDAP. Pour cette migration, la configuration LDAP doit être mise à jour.\nVous devez enregistrer votre configuration actuelle, réintialiser la configuration d'origine en exécutant 'yunohost tools regen-conf -f', puis réessayer la migration", "migration_0011_LDAP_update_failed": "Impossible de mettre à jour LDAP. Erreur: {error:s}", diff --git a/locales/nl.json b/locales/nl.json index 832ca4ea2..9406d9bea 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -82,7 +82,7 @@ "port_available": "Poort {port:d} is beschikbaar", "port_unavailable": "Poort {port:d} is niet beschikbaar", "restore_app_failed": "De app '{app:s}' kon niet worden terug gezet", - "restore_hook_unavailable": "De herstel-hook '{hook:s}' is niet beschikbaar op dit systeem", + "restore_hook_unavailable": "De herstel-hook '{part:s}' is niet beschikbaar op dit systeem", "service_add_failed": "Kan service '{service:s}' niet toevoegen", "service_already_started": "Service '{service:s}' draait al", "service_cmd_exec_failed": "Kan '{command:s}' niet uitvoeren", diff --git a/locales/oc.json b/locales/oc.json index a06520ae5..00d7aa5c5 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -144,7 +144,7 @@ "domain_created": "Domeni creat", "domain_creation_failed": "Creacion del domeni {domain}: impossibla", "domain_deleted": "Domeni suprimit", - "domain_deletion_failed": "Supression impossibla del domeni {domini}: {error}", + "domain_deletion_failed": "Supression impossibla del domeni {domain}: {error}", "domain_dyndns_invalid": "Domeni incorrècte per una utilizacion amb DynDNS", "domain_dyndns_root_unknown": "Domeni DynDNS màger desconegut", "domain_exists": "Lo domeni existís ja", @@ -247,7 +247,7 @@ "firewall_reload_failed": "Impossible de recargar lo parafuòc", "firewall_reloaded": "Parafuòc recargat", "firewall_rules_cmd_failed": "Unas règlas del parafuòc an fracassat. Per mai informacions, consultatz lo jornal.", - "global_settings_bad_choice_for_enum": "La valor del paramètre {setting:s} es incorrècta. Recebut : {received_type:s}, mas las opcions esperadas son : {expected_type:s}", + "global_settings_bad_choice_for_enum": "La valor del paramètre {setting:s} es incorrècta. Recebut : {choice:s}, mas las opcions esperadas son : {available_choices:s}", "global_settings_bad_type_for_setting": "Lo tipe del paramètre {setting:s} es incorrècte, recebut : {received_type:s}, esperat {expected_type:s}", "global_settings_cant_write_settings": "Fracàs de l’escritura del fichièr de configuracion, rason : {reason:s}", "global_settings_setting_example_enum": "Exemple d’opcion de tipe enumeracion", @@ -491,7 +491,7 @@ "migration_0007_cannot_restart": "SSH pòt pas èsser reavit aprèp aver ensajat d’anullar la migracion numèro 6.", "migrations_success": "Migracion {number} {name} reüssida !", "service_conf_now_managed_by_yunohost": "Lo fichièr de configuracion « {conf} » es ara gerit per YunoHost.", - "service_reloaded": "Lo servici « {servici:s} » es estat tornat cargar", + "service_reloaded": "Lo servici « {service:s} » es estat tornat cargar", "already_up_to_date": "I a pas res a far ! Tot es ja a jorn !", "app_action_cannot_be_ran_because_required_services_down": "Aquestas aplicacions necessitan d’èsser lançadas per poder executar aquesta accion : {services}. Abans de contunhar deuriatz ensajar de reaviar los servicis seguents (e tanben cercar perque son tombats en pana) : {services}", "confirm_app_install_warning": "Atencion : aquesta aplicacion fonciona mas non es pas ben integrada amb YunoHost. Unas foncionalitats coma l’autentificacion unica e la còpia de seguretat/restauracion pòdon èsser indisponiblas. volètz l’installar de totas manièras ? [{answers:s}] ", @@ -584,16 +584,16 @@ "migration_0011_migrate_permission": "Migracion de las permission dels paramètres d’aplicacion a LDAP…", "migration_0011_update_LDAP_database": "Actualizacion de la basa de donadas LDAP…", "migration_0011_update_LDAP_schema": "Actualizacion de l’esquèma LDAP…", - "permission_already_exist": "La permission « {permission:s} » per l’aplicacion {app:s} existís ja", - "permission_created": "Permission creada « {permission:s} » per l’aplicacion{app:s}", + "permission_already_exist": "La permission « {permission:s} » existís ja", + "permission_created": "Permission « {permission:s} » creada", "permission_creation_failed": "Creacion impossibla de la permission", - "permission_deleted": "Permission « {permission:s} » per l’aplicacion {app:s} suprimida", - "permission_deletion_failed": "Fracàs de la supression de la permission « {permission:s} » per l’aplicacion {app:s}", - "permission_not_found": "Permission « {permission:s} » pas trobada per l’aplicacion {app:s}", + "permission_deleted": "Permission « {permission:s} » suprimida", + "permission_deletion_failed": "Fracàs de la supression de la permission « {permission:s} »", + "permission_not_found": "Permission « {permission:s} » pas trobada", "permission_name_not_valid": "Lo nom de la permission « {permission:s} » es pas valid", "permission_update_failed": "Fracàs de l’actualizacion de la permission", "permission_generated": "La basa de donadas de las permission es estada actualizada", - "permission_updated": "La permission « {permission:s} » per l’aplicacion {app:s} es estada actualizada", + "permission_updated": "La permission « {permission:s} » es estada actualizada", "permission_update_nothing_to_do": "Cap de permission d’actualizar", "remove_main_permission_not_allowed": "Se pòt pas suprimir la permission màger", "remove_user_of_group_not_allowed": "Sètz pas autorizat a suprimir {user:s} del grop {group:s}", From 1d84f17ce0db0d309ab241e69de156029d572d82 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 28 Mar 2020 00:33:52 +0100 Subject: [PATCH 0749/3170] Fix mess due to automatic translation tools ~_~ --- locales/ar.json | 6 +++--- locales/ca.json | 4 ++-- locales/de.json | 6 +++--- locales/eo.json | 20 ++++++++++---------- locales/fr.json | 12 ++++++------ locales/nl.json | 2 +- locales/oc.json | 18 +++++++++--------- 7 files changed, 34 insertions(+), 34 deletions(-) diff --git a/locales/ar.json b/locales/ar.json index 828f41a8f..8b1655e34 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -182,7 +182,7 @@ "firewall_reloaded": "The firewall has been reloaded", "firewall_rules_cmd_failed": "Some firewall rules commands have failed. For more information, see the log.", "format_datetime_short": "%m/%d/%Y %I:%M %p", - "global_settings_bad_choice_for_enum": "Bad value for setting {setting:s}, received {received_type:s}, except {expected_type:s}", + "global_settings_bad_choice_for_enum": "Bad value for setting {setting:s}, received {choice:s}, except {available_choices:s}", "global_settings_bad_type_for_setting": "Bad type for setting {setting:s}, received {received_type:s}, except {expected_type:s}", "global_settings_cant_open_settings": "Failed to open settings file, reason: {reason:s}", "global_settings_cant_serialize_settings": "Failed to serialize settings data, reason: {reason:s}", @@ -227,8 +227,8 @@ "migrations_current_target": "Migration target is {}", "migrations_error_failed_to_load_migration": "ERROR: failed to load migration {number} {name}", "migrations_forward": "Migrating forward", - "migrations_loading_migration": "Loading migration {number} {name}…", - "migrations_migration_has_failed": "Migration {number} {name} has failed with exception {exception}, aborting", + "migrations_loading_migration": "Loading migration {id}…", + "migrations_migration_has_failed": "Migration {id} has failed with exception {exception}, aborting", "migrations_no_migrations_to_run": "No migrations to run", "migrations_show_currently_running_migration": "Running migration {number} {name}…", "migrations_show_last_migration": "Last ran migration is {}", diff --git a/locales/ca.json b/locales/ca.json index 471112631..0f61a2fdb 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -167,7 +167,7 @@ "domain_created": "S'ha creat el domini", "domain_creation_failed": "No s'ha pogut crear el domini {domain}: {error}", "domain_deleted": "S'ha eliminat el domini", - "domain_deletion_failed": "No s'ha pogut eliminar el domini {domini}: {error}", + "domain_deletion_failed": "No s'ha pogut eliminar el domini {domain}: {error}", "domain_exists": "El domini ja existeix", "app_action_cannot_be_ran_because_required_services_down": "Aquests serveis necessaris haurien d'estar funcionant per poder executar aquesta acció: {services} Intenteu reiniciar-los per continuar (i possiblement investigar perquè estan aturats).", "domain_dns_conf_is_just_a_recommendation": "Aquesta ordre mostra la configuració *recomanada*. En cap cas fa la configuració del DNS. És la vostra responsabilitat configurar la zona DNS en el vostre registrar en acord amb aquesta recomanació.", @@ -459,7 +459,7 @@ "service_description_yunohost-firewall": "Gestiona els ports de connexió oberts i tancats als serveis", "service_disable_failed": "No s'han pogut fer que el servei «{service:s}» no comenci a l'arrancada.\n\nRegistres recents: {logs:s}", "service_disabled": "El servei «{service:s}» ja no començarà al arrancar el sistema.", - "service_enable_failed": "No s'ha pogut fer que el servei «{service:s}» comenci automàticament a l'arrancada.\n\nRegistres recents: {log:s}", + "service_enable_failed": "No s'ha pogut fer que el servei «{service:s}» comenci automàticament a l'arrancada.\n\nRegistres recents: {logs:s}", "service_enabled": "El servei «{service:s}» començarà automàticament durant l'arrancada del sistema.", "service_no_log": "No hi ha cap registre pel servei «{service:s}»", "service_regen_conf_is_deprecated": "«yunohost service regen-conf» està desfasat! Utilitzeu «yunohost tools regen-conf» en el seu lloc.", diff --git a/locales/de.json b/locales/de.json index ae3087900..955f6ad67 100644 --- a/locales/de.json +++ b/locales/de.json @@ -302,7 +302,7 @@ "app_change_url_success": "{app:s} URL ist nun {domain:s}{path:s}", "backup_applying_method_borg": "Sende alle Dateien zur Sicherung ins borg-backup repository…", "invalid_url_format": "ungültiges URL Format", - "global_settings_bad_type_for_setting": "Falscher Typ für Einstellung {setting:s}. Empfangen: {receive_type:s}, aber erwartet: {expected_type:s}", + "global_settings_bad_type_for_setting": "Falscher Typ für Einstellung {setting:s}. Empfangen: {received_type:s}, aber erwartet: {expected_type:s}", "global_settings_bad_choice_for_enum": "Falsche Wahl für die Einstellung {setting:s}. Habe '{choice:s}' erhalten, aber es stehen nur folgende Auswahlmöglichkeiten zur Verfügung: {available_choices:s}", "file_does_not_exist": "Die Datei {path:s} existiert nicht.", "experimental_feature": "Warnung: Diese Funktion ist experimentell und gilt nicht als stabil. Sie sollten sie nur verwenden, wenn Sie wissen, was Sie tun.", @@ -333,7 +333,7 @@ "backup_custom_mount_error": "Bei der benutzerdefinierten Sicherungsmethode ist beim Arbeitsschritt \"Einhängen/Verbinden\" ein Fehler aufgetreten", "backup_custom_backup_error": "Bei der benutzerdefinierten Sicherungsmethode ist beim Arbeitsschritt \"Sicherung\" ein Fehler aufgetreten", "backup_csv_creation_failed": "Die zur Wiederherstellung erforderliche CSV-Datei kann nicht erstellt werden", - "backup_couldnt_bind": "{Src:s} konnte nicht an {dest:s} angebunden werden.", + "backup_couldnt_bind": "{src:s} konnte nicht an {dest:s} angebunden werden.", "backup_borg_not_implemented": "Die Borg-Sicherungsmethode ist noch nicht implementiert", "backup_ask_for_copying_if_needed": "Möchten Sie die Sicherung mit {size:s} MB temporär durchführen? (Dieser Weg wird verwendet, da einige Dateien nicht mit einer effizienteren Methode vorbereitet werden konnten).", "backup_actually_backuping": "Erstellt ein Backup-Archiv aus den gesammelten Dateien …", @@ -343,7 +343,7 @@ "apps_permission_restoration_failed": "Erteilen der Berechtigung '{permission:s}' für die Wiederherstellung der App {app:s} erforderlich", "apps_permission_not_found": "Keine Berechtigung für die installierten Apps gefunden", "app_upgrade_some_app_failed": "Einige Anwendungen können nicht aktualisiert werden", - "app_upgrade_app_name": "{App} wird jetzt aktualisiert…", + "app_upgrade_app_name": "{app} wird jetzt aktualisiert…", "app_upgrade_several_apps": "Die folgenden Apps werden aktualisiert: {apps}", "app_start_restore": "Anwendung {app} wird wiederhergestellt…", "app_start_backup": "Sammeln von Dateien, die für {app} gesichert werden sollen…", diff --git a/locales/eo.json b/locales/eo.json index 906648120..5047fff09 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -74,7 +74,7 @@ "backup_invalid_archive": "Ĉi tio ne estas rezerva ar archiveivo", "ask_current_admin_password": "Pasvorto pri aktuala administrado", "backup_creation_failed": "Ne povis krei la rezervan ar archiveivon", - "backup_hook_unknown": "La rezerva hoko '{hoko:s}' estas nekonata", + "backup_hook_unknown": "La rezerva hoko '{hook:s}' estas nekonata", "backup_custom_backup_error": "Propra rezerva metodo ne povis preterpasi la paŝon \"sekurkopio\"", "ask_main_domain": "Ĉefa domajno", "backup_method_tar_finished": "TAR-rezerva ar archiveivo kreita", @@ -97,15 +97,15 @@ "app_start_backup": "Kolekti dosierojn por esti subtenata por la '{app}' …", "backup_archive_name_exists": "Rezerva arkivo kun ĉi tiu nomo jam ekzistas.", "backup_applying_method_tar": "Krei la rezervan TAR-ar archiveivon …", - "backup_method_custom_finished": "Propra rezerva metodo '{metodo:s}' finiĝis", - "appslist_retrieve_error": "Ne eblas akiri la forajn listojn '{appslist:s}': {eraro:s}", + "backup_method_custom_finished": "Propra rezerva metodo '{method:s}' finiĝis", + "appslist_retrieve_error": "Ne eblas akiri la forajn listojn '{appslist:s}': {error:s}", "app_already_installed_cant_change_url": "Ĉi tiu app estas jam instalita. La URL ne povas esti ŝanĝita nur per ĉi tiu funkcio. Rigardu \"app changeurl\" se ĝi haveblas.", "app_not_correctly_installed": "{app:s} ŝajnas esti malĝuste instalita", "app_removed": "{app:s} forigita", "backup_delete_error": "Ne povis forigi '{path:s}'", "app_package_need_update": "La pakaĵo {app} devas esti ĝisdatigita por sekvi YunoHost-ŝanĝojn", "backup_nothings_done": "Nenio por ŝpari", - "backup_applying_method_custom": "Nomante la kutiman rezervan metodon '{metodo:s}' …", + "backup_applying_method_custom": "Nomante la kutiman rezervan metodon '{method:s}' …", "appslist_fetched": "Ĝisdatigis la liston de aplikoj '{appslist:s}'", "backup_app_failed": "Ne eblis rezervi la programon '{app:s}'", "app_upgrade_some_app_failed": "Iuj aplikoj ne povis esti altgradigitaj", @@ -268,7 +268,7 @@ "pattern_positive_number": "Devas esti pozitiva nombro", "monitor_stats_file_not_found": "Ne povis trovi la statistikan dosieron", "certmanager_error_no_A_record": "Neniu DNS 'A' rekordo trovita por '{domain:s}'. Vi bezonas atentigi vian domajnan nomon al via maŝino por povi instali atestilon Lasu-Ĉifri. (Se vi scias, kion vi faras, uzu '--no-checks' por malŝalti tiujn ĉekojn.)", - "update_apt_cache_failed": "Ne eblis ĝisdatigi la kaŝmemoron de APT (paka administranto de Debian). Jen rubujo de la sources.list-linioj, kiuj povus helpi identigi problemajn liniojn:\n{sourcelist}", + "update_apt_cache_failed": "Ne eblis ĝisdatigi la kaŝmemoron de APT (paka administranto de Debian). Jen rubujo de la sources.list-linioj, kiuj povus helpi identigi problemajn liniojn:\n{sourceslist}", "migrations_no_migrations_to_run": "Neniuj migradoj por funkcii", "executing_command": "Plenumanta komandon '{command:s}' …", "diagnosis_no_apps": "Neniu tia instalita app", @@ -332,7 +332,7 @@ "tools_upgrade_at_least_one": "Bonvolu specifi '--apps' aŭ '--system'", "service_already_stopped": "La servo '{service:s}' jam ĉesis", "unit_unknown": "Nekonata unuo '{unit:s}'", - "migration_0003_modified_files": "Bonvolu noti, ke la jenaj dosieroj estis trovitaj mane kaj modifitaj kaj povus esti anstataŭigitaj sekve de la ĝisdatigo: {manual_modified_files}", + "migration_0003_modified_files": "Bonvolu noti, ke la jenaj dosieroj estis trovitaj mane kaj modifitaj kaj povus esti anstataŭigitaj sekve de la ĝisdatigo: {manually_modified_files}", "tools_upgrade_cant_both": "Ne eblas ĝisdatigi ambaŭ sistemon kaj programojn samtempe", "restore_extracting": "Eltirante bezonatajn dosierojn el la ar theivo…", "upnp_port_open_failed": "Ne povis malfermi havenon per UPnP", @@ -390,7 +390,7 @@ "regenconf_up_to_date": "La agordo jam estas ĝisdatigita por kategorio '{category}'", "migration_0003_patching_sources_list": "Patching the sources.lists …", "global_settings_setting_security_ssh_compatibility": "Kongruo vs sekureca kompromiso por la SSH-servilo. Afektas la ĉifradojn (kaj aliajn aspektojn pri sekureco)", - "migrations_need_to_accept_disclaimer": "Por funkciigi la migradon {id}, via devas akcepti la sekvan malakcepton:\n---\n{malavantaĝo}\n---\nSe vi akceptas funkcii la migradon, bonvolu rekonduki la komandon kun la opcio '--accept-disclaimer'.", + "migrations_need_to_accept_disclaimer": "Por funkciigi la migradon {id}, via devas akcepti la sekvan malakcepton:\n---\n{disclaimer}\n---\nSe vi akceptas funkcii la migradon, bonvolu rekonduki la komandon kun la opcio '--accept-disclaimer'.", "regenconf_file_remove_failed": "Ne povis forigi la agordodosieron '{conf}'", "not_enough_disk_space": "Ne sufiĉe libera spaco sur '{path:s}'", "migration_0006_disclaimer": "YunoHost nun atendas, ke la pasvortoj de admin kaj radiko estos sinkronigitaj. Ĉi tiu migrado anstataŭigas vian radikan pasvorton kun la administran pasvorton.", @@ -465,10 +465,10 @@ "global_settings_cant_open_settings": "Ne eblis malfermi agordojn, tial: {reason:s}", "user_created": "Uzanto kreita", "service_description_avahi-daemon": "Permesas al vi atingi vian servilon uzante 'yunohost.local' en via loka reto", - "certmanager_attempt_to_replace_valid_cert": "Vi provas anstataŭigi bonan kaj validan atestilon por domajno {domajno:s}! (Uzu --forte pretervidi)", + "certmanager_attempt_to_replace_valid_cert": "Vi provas anstataŭigi bonan kaj validan atestilon por domajno {domain:s}! (Uzu --forte pretervidi)", "monitor_stats_period_unavailable": "Ne ekzistas disponeblaj statistikoj por la periodo", "regenconf_updated": "Agordo ĝisdatigita por '{category}'", - "update_apt_cache_warning": "Io iris malbone dum la ĝisdatigo de la kaŝmemoro de APT (paka administranto de Debian). Jen rubujo de la sources.list-linioj, kiuj povus helpi identigi problemajn liniojn:\n{sourcelist}", + "update_apt_cache_warning": "Io iris malbone dum la ĝisdatigo de la kaŝmemoro de APT (paka administranto de Debian). Jen rubujo de la sources.list-linioj, kiuj povus helpi identigi problemajn liniojn:\n{sourceslist}", "regenconf_dry_pending_applying": "Kontrolado de pritraktata agordo, kiu estus aplikita por kategorio '{category}'…", "regenconf_file_copy_failed": "Ne povis kopii la novan agordodosieron '{new}' al '{conf}'", "global_settings_setting_example_string": "Ekzemple korda elekto", @@ -487,7 +487,7 @@ "mysql_db_creation_failed": "Ne povis krei MySQL-datumbazon", "ldap_initialized": "LDAP inicializis", "migrate_tsig_not_needed": "Vi ne ŝajnas uzi DynDNS-domajnon, do neniu migrado necesas.", - "certmanager_domain_cert_not_selfsigned": "La atestilo por domajno {domajno:s} ne estas mem-subskribita. Ĉu vi certas, ke vi volas anstataŭigi ĝin? (Uzu '--force' por fari tion.)", + "certmanager_domain_cert_not_selfsigned": "La atestilo por domajno {domain:s} ne estas mem-subskribita. Ĉu vi certas, ke vi volas anstataŭigi ĝin? (Uzu '--force' por fari tion.)", "certmanager_unable_to_parse_self_CA_name": "Ne povis trapasi nomon de mem-subskribinta aŭtoritato (dosiero: {file:s})", "log_selfsigned_cert_install": "Instalu mem-subskribitan atestilon sur '{}' domajno", "log_tools_reboot": "Reklamu vian servilon", diff --git a/locales/fr.json b/locales/fr.json index 73f2804b8..adeeada3b 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -242,7 +242,7 @@ "user_home_creation_failed": "Impossible de créer le dossier personnel de l’utilisateur", "user_info_failed": "Impossible de récupérer les informations de l’utilisateur", "user_unknown": "L'utilisateur {user:s} est inconnu", - "user_update_failed": "Impossible de mettre à jour l'utilisateur {utilisateur}: {erreur}", + "user_update_failed": "Impossible de mettre à jour l'utilisateur {user}: {error}", "user_updated": "L’utilisateur a été modifié", "yunohost_already_installed": "YunoHost est déjà installé", "yunohost_ca_creation_failed": "Impossible de créer l’autorité de certification", @@ -320,7 +320,7 @@ "backup_archive_system_part_not_available": "La partie '{part:s}' du système n’est pas disponible dans cette sauvegarde", "backup_archive_mount_failed": "Le montage de l’archive de sauvegarde a échoué", "backup_archive_writing_error": "Impossible d'ajouter des fichiers '{source:s}' (nommés dans l'archive : '{dest:s}') à sauvegarder dans l'archive compressée '{archive:s}'", - "backup_ask_for_copying_if_needed": "Voulez-vous effectuer la sauvegarde en utilisant {taille:s} temporairement? (Cette méthode est utilisée car certains fichiers n'ont pas pu être préparés avec une méthode plus efficace.)", + "backup_ask_for_copying_if_needed": "Voulez-vous effectuer la sauvegarde en utilisant {size:s} temporairement? (Cette méthode est utilisée car certains fichiers n'ont pas pu être préparés avec une méthode plus efficace.)", "backup_borg_not_implemented": "La méthode de sauvegarde Borg n’est pas encore implémentée", "backup_cant_mount_uncompress_archive": "Impossible de monter en lecture seule le dossier de l’archive décompressée", "backup_copying_to_organize_the_archive": "Copie de {size:s} Mo pour organiser l’archive", @@ -466,7 +466,7 @@ "migration_description_0005_postgresql_9p4_to_9p6": "Migration des bases de données de PostgreSQL 9.4 vers PostgreSQL 9.6", "migration_0005_postgresql_94_not_installed": "PostgreSQL n’a pas été installé sur votre système. Rien à faire !", "migration_0005_postgresql_96_not_installed": "PostgreSQL 9.4 a été trouvé et installé, mais pas PostgreSQL 9.6 !? Quelque chose d’étrange a dû arriver à votre système… :(", - "migration_0005_not_enough_space": "Laissez suffisamment d'espace disponible dans {chemin} pour exécuter la migration.", + "migration_0005_not_enough_space": "Laissez suffisamment d'espace disponible dans {path} pour exécuter la migration.", "recommend_to_add_first_user": "La post-installation est terminée mais YunoHost a besoin d’au moins un utilisateur pour fonctionner correctement. Vous devez en ajouter un en utilisant la commande 'yunohost user create $nomdutilisateur' ou bien via l’interface d’administration web.", "service_description_php7.0-fpm": "Exécute des applications écrites en PHP avec NGINX", "users_available": "Liste des utilisateurs disponibles :", @@ -600,7 +600,7 @@ "migration_description_0012_postgresql_password_to_md5_authentication": "Forcer l'authentification PostgreSQL à utiliser MD5 pour les connexions locales", "migrations_exclusive_options": "'auto', '--skip' et '--force-rerun' sont des options mutuellement exclusives.", "migrations_not_pending_cant_skip": "Ces migrations ne sont pas en attente et ne peuvent donc pas être ignorées: {ids}", - "migration_0011_can_not_backup_before_migration": "La sauvegarde du système n'a pas pu être terminée avant l'échec de la migration. Erreur: {erreur:s}", + "migration_0011_can_not_backup_before_migration": "La sauvegarde du système n'a pas pu être terminée avant l'échec de la migration. Erreur: {error:s}", "migration_0011_migrate_permission": "Migration des autorisations des paramètres des applications vers LDAP…", "migration_0011_migration_failed_trying_to_rollback": "La migration a échouée… Tentative de restauration du système.", "migration_0011_rollback_success": "Système restauré.", @@ -629,10 +629,10 @@ "permission_already_clear": "L'autorisation '{permission: s}' est déjà vide pour l'application {app: s}", "permission_already_exist": "L'autorisation '{permission}' existe déjà", "permission_created": "Permission '{permission:s}' créée", - "permission_creation_failed": "Impossible de créer l'autorisation '{permission}': {erreur}", + "permission_creation_failed": "Impossible de créer l'autorisation '{permission}': {error}", "permission_deleted": "Permission '{permission:s}' supprimée", "permission_deletion_failed": "Impossible de supprimer la permission '{permission}': {error}", - "remove_user_of_group_not_allowed": "Vous n'êtes pas autorisé à supprimer l'utilisateur '{utilisateur: s}' dans le groupe '{groupe: s}'", + "remove_user_of_group_not_allowed": "Vous n'êtes pas autorisé à supprimer l'utilisateur '{user:s}' dans le groupe '{group:s}'", "migration_description_0011_setup_group_permission": "Initialiser les groupes d'utilisateurs et autorisations pour les applications et les services", "migration_0011_LDAP_config_dirty": "Il semble que vous ayez personnalisé votre configuration LDAP. Pour cette migration, la configuration LDAP doit être mise à jour.\nVous devez enregistrer votre configuration actuelle, réintialiser la configuration d'origine en exécutant 'yunohost tools regen-conf -f', puis réessayer la migration", "migration_0011_LDAP_update_failed": "Impossible de mettre à jour LDAP. Erreur: {error:s}", diff --git a/locales/nl.json b/locales/nl.json index 832ca4ea2..9406d9bea 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -82,7 +82,7 @@ "port_available": "Poort {port:d} is beschikbaar", "port_unavailable": "Poort {port:d} is niet beschikbaar", "restore_app_failed": "De app '{app:s}' kon niet worden terug gezet", - "restore_hook_unavailable": "De herstel-hook '{hook:s}' is niet beschikbaar op dit systeem", + "restore_hook_unavailable": "De herstel-hook '{part:s}' is niet beschikbaar op dit systeem", "service_add_failed": "Kan service '{service:s}' niet toevoegen", "service_already_started": "Service '{service:s}' draait al", "service_cmd_exec_failed": "Kan '{command:s}' niet uitvoeren", diff --git a/locales/oc.json b/locales/oc.json index e0fbaa45c..64801e373 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -144,7 +144,7 @@ "domain_created": "Domeni creat", "domain_creation_failed": "Creacion del domeni {domain}: impossibla", "domain_deleted": "Domeni suprimit", - "domain_deletion_failed": "Supression impossibla del domeni {domini}: {error}", + "domain_deletion_failed": "Supression impossibla del domeni {domain}: {error}", "domain_dyndns_invalid": "Domeni incorrècte per una utilizacion amb DynDNS", "domain_dyndns_root_unknown": "Domeni DynDNS màger desconegut", "domain_exists": "Lo domeni existís ja", @@ -247,7 +247,7 @@ "firewall_reload_failed": "Impossible de recargar lo parafuòc", "firewall_reloaded": "Parafuòc recargat", "firewall_rules_cmd_failed": "Unas règlas del parafuòc an fracassat. Per mai informacions, consultatz lo jornal.", - "global_settings_bad_choice_for_enum": "La valor del paramètre {setting:s} es incorrècta. Recebut : {received_type:s}, mas las opcions esperadas son : {expected_type:s}", + "global_settings_bad_choice_for_enum": "La valor del paramètre {setting:s} es incorrècta. Recebut : {choice:s}, mas las opcions esperadas son : {available_choices:s}", "global_settings_bad_type_for_setting": "Lo tipe del paramètre {setting:s} es incorrècte, recebut : {received_type:s}, esperat {expected_type:s}", "global_settings_cant_write_settings": "Fracàs de l’escritura del fichièr de configuracion, rason : {reason:s}", "global_settings_setting_example_enum": "Exemple d’opcion de tipe enumeracion", @@ -491,7 +491,7 @@ "migration_0007_cannot_restart": "SSH pòt pas èsser reavit aprèp aver ensajat d’anullar la migracion numèro 6.", "migrations_success": "Migracion {number} {name} reüssida !", "service_conf_now_managed_by_yunohost": "Lo fichièr de configuracion « {conf} » es ara gerit per YunoHost.", - "service_reloaded": "Lo servici « {servici:s} » es estat tornat cargar", + "service_reloaded": "Lo servici « {service:s} » es estat tornat cargar", "already_up_to_date": "I a pas res a far ! Tot es ja a jorn !", "app_action_cannot_be_ran_because_required_services_down": "Aquestas aplicacions necessitan d’èsser lançadas per poder executar aquesta accion : {services}. Abans de contunhar deuriatz ensajar de reaviar los servicis seguents (e tanben cercar perque son tombats en pana) : {services}", "confirm_app_install_warning": "Atencion : aquesta aplicacion fonciona mas non es pas ben integrada amb YunoHost. Unas foncionalitats coma l’autentificacion unica e la còpia de seguretat/restauracion pòdon èsser indisponiblas. volètz l’installar de totas manièras ? [{answers:s}] ", @@ -584,16 +584,16 @@ "migration_0011_migrate_permission": "Migracion de las permission dels paramètres d’aplicacion a LDAP…", "migration_0011_update_LDAP_database": "Actualizacion de la basa de donadas LDAP…", "migration_0011_update_LDAP_schema": "Actualizacion de l’esquèma LDAP…", - "permission_already_exist": "La permission « {permission:s} » per l’aplicacion {app:s} existís ja", - "permission_created": "Permission creada « {permission:s} » per l’aplicacion{app:s}", + "permission_already_exist": "La permission « {permission:s} » existís ja", + "permission_created": "Permission « {permission:s} » creada", "permission_creation_failed": "Creacion impossibla de la permission", - "permission_deleted": "Permission « {permission:s} » per l’aplicacion {app:s} suprimida", - "permission_deletion_failed": "Fracàs de la supression de la permission « {permission:s} » per l’aplicacion {app:s}", - "permission_not_found": "Permission « {permission:s} » pas trobada per l’aplicacion {app:s}", + "permission_deleted": "Permission « {permission:s} » suprimida", + "permission_deletion_failed": "Fracàs de la supression de la permission « {permission:s} »", + "permission_not_found": "Permission « {permission:s} » pas trobada", "permission_name_not_valid": "Lo nom de la permission « {permission:s} » es pas valid", "permission_update_failed": "Fracàs de l’actualizacion de la permission", "permission_generated": "La basa de donadas de las permission es estada actualizada", - "permission_updated": "La permission « {permission:s} » per l’aplicacion {app:s} es estada actualizada", + "permission_updated": "La permission « {permission:s} » es estada actualizada", "permission_update_nothing_to_do": "Cap de permission d’actualizar", "remove_main_permission_not_allowed": "Se pòt pas suprimir la permission màger", "remove_user_of_group_not_allowed": "Sètz pas autorizat a suprimir {user:s} del grop {group:s}", From 0397aa91d94364a6652987b51af702f258ba1863 Mon Sep 17 00:00:00 2001 From: kay0u Date: Fri, 27 Mar 2020 23:50:50 +0000 Subject: [PATCH 0750/3170] Update changelog for 3.7.0.11 release --- debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian/changelog b/debian/changelog index 1f137ba16..cc026e268 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +yunohost (3.7.0.11) stable; urgency=low + + - [fix] Mess due to automatic translation tools ~_~ + + -- Kay0u Fri, 27 Mar 2020 23:49:45 +0000 + yunohost (3.7.0.10) stable; urgency=low - [fix] On some weird setup, this folder and content ain't readable by group ... gotta make sure to make rx for group other slapd will explode From a2b4e151e4ab016f9d96d698848beaaaf848886d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 28 Mar 2020 14:51:19 +0100 Subject: [PATCH 0751/3170] Ugh, this gotta go into an m18n.n to work... --- src/yunohost/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 34b367d7d..4a047b58f 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -577,7 +577,7 @@ def user_group_create(operation_logger, groupname, gid=None, primary_group=False all_existing_groupnames = {x.gr_name for x in grp.getgrall()} if groupname in all_existing_groupnames: if primary_group: - logger.warning('group_already_exist_on_system_but_removing_it', group=groupname) + logger.warning(m18n.n('group_already_exist_on_system_but_removing_it', group=groupname)) subprocess.check_call("sed --in-place '/^%s:/d' /etc/group" % groupname, shell=True) else: raise YunohostError('group_already_exist_on_system', group=groupname) From f54701eacc7ed8589ab0d9fc90c3d5c751fb90cc Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 28 Mar 2020 14:52:42 +0100 Subject: [PATCH 0752/3170] Update changelog for 3.7.0.12 --- debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian/changelog b/debian/changelog index cc026e268..9bcaea043 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +yunohost (3.7.0.12) stable; urgency=low + + - Fix previous buggy hotfix about deleting existing primary groups ... + + -- Alexandre Aubin Sat, 28 Mar 2020 14:52:00 +0000 + yunohost (3.7.0.11) stable; urgency=low - [fix] Mess due to automatic translation tools ~_~ From 3e0fc7cb04b511616074fe235c7dc5d6cbf794eb Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 28 Mar 2020 14:51:19 +0100 Subject: [PATCH 0753/3170] Ugh, this gotta go into an m18n.n to work... --- src/yunohost/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 34b367d7d..4a047b58f 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -577,7 +577,7 @@ def user_group_create(operation_logger, groupname, gid=None, primary_group=False all_existing_groupnames = {x.gr_name for x in grp.getgrall()} if groupname in all_existing_groupnames: if primary_group: - logger.warning('group_already_exist_on_system_but_removing_it', group=groupname) + logger.warning(m18n.n('group_already_exist_on_system_but_removing_it', group=groupname)) subprocess.check_call("sed --in-place '/^%s:/d' /etc/group" % groupname, shell=True) else: raise YunohostError('group_already_exist_on_system', group=groupname) From 6fbb153272c0a6c6f96f41a4b97038f72c198fbf Mon Sep 17 00:00:00 2001 From: Kay0u Date: Sat, 28 Mar 2020 18:15:46 +0100 Subject: [PATCH 0754/3170] Remove redirected_urls after postinstall --- src/yunohost/tools.py | 2 -- src/yunohost/user.py | 16 ---------------- 2 files changed, 18 deletions(-) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index ea9521020..b3cb5b218 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -306,8 +306,6 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False, if 'redirected_urls' not in ssowat_conf: ssowat_conf['redirected_urls'] = {} - ssowat_conf['redirected_urls']['/'] = domain + '/yunohost/admin' - write_to_json('/etc/ssowat/conf.json.persistent', ssowat_conf) os.system('chmod 644 /etc/ssowat/conf.json.persistent') diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 4a047b58f..82dd72cf7 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -192,22 +192,6 @@ def user_create(operation_logger, username, firstname, lastname, mail, password, 'loginShell': '/bin/false' } - # If it is the first user, add some aliases - if not ldap.search(base='ou=users,dc=yunohost,dc=org', filter='uid=*'): - attr_dict['mail'] = [attr_dict['mail']] + aliases - - # If exists, remove the redirection from the SSO - if not os.path.exists('/etc/ssowat/conf.json.persistent'): - ssowat_conf = {} - else: - ssowat_conf = read_json('/etc/ssowat/conf.json.persistent') - - if 'redirected_urls' in ssowat_conf and '/' in ssowat_conf['redirected_urls']: - del ssowat_conf['redirected_urls']['/'] - - write_to_json('/etc/ssowat/conf.json.persistent', ssowat_conf) - os.system('chmod 644 /etc/ssowat/conf.json.persistent') - try: ldap.add('uid=%s,ou=users' % username, attr_dict) except Exception as e: From b3ccc9273a68af25eaa941d0bb1cb6b5e0519cb5 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Sat, 28 Mar 2020 18:33:00 +0100 Subject: [PATCH 0755/3170] Keep aliases for the first user --- src/yunohost/user.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 82dd72cf7..39a2d8f15 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -192,6 +192,10 @@ def user_create(operation_logger, username, firstname, lastname, mail, password, 'loginShell': '/bin/false' } + # If it is the first user, add some aliases + if not ldap.search(base='ou=users,dc=yunohost,dc=org', filter='uid=*'): + attr_dict['mail'] = [attr_dict['mail']] + aliases + try: ldap.add('uid=%s,ou=users' % username, attr_dict) except Exception as e: From 1815e56cd91725bdc4f09cb850b4409c470ca49d Mon Sep 17 00:00:00 2001 From: Kay0u Date: Sat, 28 Mar 2020 18:33:46 +0100 Subject: [PATCH 0756/3170] Remove unused ssowat conf --- src/yunohost/tools.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index b3cb5b218..77a120d46 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -303,9 +303,6 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False, else: ssowat_conf = read_json('/etc/ssowat/conf.json.persistent') - if 'redirected_urls' not in ssowat_conf: - ssowat_conf['redirected_urls'] = {} - write_to_json('/etc/ssowat/conf.json.persistent', ssowat_conf) os.system('chmod 644 /etc/ssowat/conf.json.persistent') From ff4f644cd073d63ad8bb03b3de671f98039a07e2 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 28 Mar 2020 21:17:28 +0100 Subject: [PATCH 0757/3170] Fix possible security issue with these cookie files --- data/helpers.d/utils | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 50671dba0..133a47247 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -237,9 +237,14 @@ ynh_local_curl () { # Wait untils nginx has fully reloaded (avoid curl fail with http2) sleep 2 + + local cookiefile=/tmp/ynh-$app-cookie.txt + touch $cookiefile + chown root $cookiefile + chmod 700 $cookiefile # Curl the URL - curl --silent --show-error -kL -H "Host: $domain" --resolve $domain:443:127.0.0.1 $POST_data "$full_page_url" --cookie-jar /tmp/ynh-$app-cookie.txt --cookie /tmp/ynh-$app-cookie.txt + curl --silent --show-error -kL -H "Host: $domain" --resolve $domain:443:127.0.0.1 $POST_data "$full_page_url" --cookie-jar $cookiefile --cookie $cookiefile } # Render templates with Jinja2 From f52eef4bc2db4398841e0475a6ce9e13d24e917b Mon Sep 17 00:00:00 2001 From: pitchum Date: Sun, 29 Mar 2020 11:51:12 +0200 Subject: [PATCH 0758/3170] [fix] Don't break the cert renew process, just warn. --- 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 6e9c97bca..5fae59060 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -643,9 +643,10 @@ def _prepare_certificate_signing_request(domain, key_file, output_folder): if domain == _get_maindomain(): # Include xmpp-upload subdomain in subject alternate names subdomain="xmpp-upload." + domain - if _dns_ip_match_public_ip(get_public_ip(), subdomain): + try: + _dns_ip_match_public_ip(get_public_ip(), subdomain) csr.add_extensions([crypto.X509Extension("subjectAltName", False, "DNS:" + subdomain)]) - else: + except YunohostError: logger.warning(m18n.n('certmanager_warning_subdomain_dns_record', subdomain=subdomain, domain=domain)) # Set the key From 711cc35cdf32329e2caad9d38a468fa5fddbdf4b Mon Sep 17 00:00:00 2001 From: Kay0u Date: Sun, 29 Mar 2020 23:13:30 +0200 Subject: [PATCH 0759/3170] fix tests --- src/yunohost/tests/test_changeurl.py | 6 +++--- src/yunohost/tests/test_permission.py | 29 +++++++++++++++++---------- src/yunohost/tests/test_user-group.py | 5 ++++- 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/yunohost/tests/test_changeurl.py b/src/yunohost/tests/test_changeurl.py index cb9b5d290..8888dd9e9 100644 --- a/src/yunohost/tests/test_changeurl.py +++ b/src/yunohost/tests/test_changeurl.py @@ -8,11 +8,11 @@ from yunohost.domain import _get_maindomain from yunohost.utils.error import YunohostError # Get main domain -maindomain = _get_maindomain() - +maindomain = "" def setup_function(function): - pass + global maindomain + maindomain = _get_maindomain() def teardown_function(function): diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index 2f626fa7c..b6b7d9577 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -11,23 +11,14 @@ from yunohost.permission import user_permission_update, user_permission_list, us from yunohost.domain import _get_maindomain # Get main domain -maindomain = _get_maindomain() +maindomain = "" dummy_password = "test123Ynh" # Dirty patch of DNS resolution. Force the DNS to 127.0.0.1 address even if dnsmasq have the public address. # Mainly used for 'can_access_webpage' function import socket -dns_cache = {(maindomain, 443, 0, 1): [(2, 1, 6, '', ('127.0.0.1', 443))]} -prv_getaddrinfo = socket.getaddrinfo -def new_getaddrinfo(*args): - try: - return dns_cache[args] - except KeyError: - res = prv_getaddrinfo(*args) - dns_cache[args] = res - return res -socket.getaddrinfo = new_getaddrinfo +prv_getaddrinfo = socket.getaddrinfo def clean_user_groups_permission(): for u in user_list()['users']: @@ -40,11 +31,27 @@ def clean_user_groups_permission(): for p in user_permission_list()['permissions']: if any(p.startswith(name) for name in ["wiki", "blog", "site", "permissions_app"]): permission_delete(p, force=True, sync_perm=False) + socket.getaddrinfo = prv_getaddrinfo def setup_function(function): clean_user_groups_permission() + global maindomain + maindomain = _get_maindomain() + + # Dirty patch of DNS resolution. Force the DNS to 127.0.0.1 address even if dnsmasq have the public address. + # Mainly used for 'can_access_webpage' function + dns_cache = {(maindomain, 443, 0, 1): [(2, 1, 6, '', ('127.0.0.1', 443))]} + def new_getaddrinfo(*args): + try: + return dns_cache[args] + except KeyError: + res = prv_getaddrinfo(*args) + dns_cache[args] = res + return res + socket.getaddrinfo = new_getaddrinfo + user_create("alice", "Alice", "White", "alice@" + maindomain, dummy_password) user_create("bob", "Bob", "Snow", "bob@" + maindomain, dummy_password) permission_create("wiki.main", url="/", allowed=["all_users"] , sync_perm=False) diff --git a/src/yunohost/tests/test_user-group.py b/src/yunohost/tests/test_user-group.py index 695f09477..f1eae9c4e 100644 --- a/src/yunohost/tests/test_user-group.py +++ b/src/yunohost/tests/test_user-group.py @@ -8,7 +8,7 @@ from yunohost.domain import _get_maindomain from yunohost.tests.test_permission import check_LDAP_db_integrity # Get main domain -maindomain = _get_maindomain() +maindomain = "" def clean_user_groups(): @@ -23,6 +23,9 @@ def clean_user_groups(): def setup_function(function): clean_user_groups() + global maindomain + maindomain = _get_maindomain() + user_create("alice", "Alice", "White", "alice@" + maindomain, "test123Ynh") user_create("bob", "Bob", "Snow", "bob@" + maindomain, "test123Ynh") user_create("jack", "Jack", "Black", "jack@" + maindomain, "test123Ynh") From d2edc162fd6881634d526f250b0ea867d71ded13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Mon, 30 Mar 2020 17:40:01 +0200 Subject: [PATCH 0760/3170] Update LDAP schema for label and tile support, authentication header and multiple URL support --- data/templates/slapd/yunohost.schema | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/data/templates/slapd/yunohost.schema b/data/templates/slapd/yunohost.schema index 472518f35..9e7543378 100644 --- a/data/templates/slapd/yunohost.schema +++ b/data/templates/slapd/yunohost.schema @@ -13,9 +13,21 @@ attributetype ( 1.3.6.1.4.1.17953.9.1.3 NAME 'inheritPermission' DESC 'Yunohost permission for user on permission side' SUP distinguishedName ) attributetype ( 1.3.6.1.4.1.17953.9.1.4 NAME 'URL' - DESC 'Yunohost application URL' + DESC 'Yunohost permission main URL' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} SINGLE-VALUE ) +attributetype ( 1.3.6.1.4.1.17953.9.1.5 NAME 'additionalUrls' + DESC 'Yunohost permission additionnal URL' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} ) -attributetype ( 1.3.6.1.4.1.17953.9.1.5 NAME 'isProtected' +attributetype ( 1.3.6.1.4.1.17953.9.1.6 NAME 'authHeader' + DESC 'Yunohost application, enable authentication header' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE ) +attributetype ( 1.3.6.1.4.1.17953.9.1.7 NAME 'label' + DESC 'Yunohost permission label, also used for the tile name in the SSO' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} SINGLE-VALUE ) +attributetype ( 1.3.6.1.4.1.17953.9.1.8 NAME 'showTile' + DESC 'Yunohost application, show/hide the tile in the SSO for this permission' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE ) +attributetype ( 1.3.6.1.4.1.17953.9.1.9 NAME 'isProtected' DESC 'Yunohost application permission protection' SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE ) # OBJECTCLASS @@ -27,8 +39,8 @@ objectclass ( 1.3.6.1.4.1.17953.9.2.1 NAME 'groupOfNamesYnh' objectclass ( 1.3.6.1.4.1.17953.9.2.2 NAME 'permissionYnh' DESC 'a Yunohost application' SUP top AUXILIARY - MUST ( cn $ isProtected ) - MAY ( groupPermission $ inheritPermission $ URL ) ) + MUST ( cn $ authHeader $ label $ showTile $ isProtected ) + MAY ( groupPermission $ inheritPermission $ URL $ additionalUrls ) ) # For User objectclass ( 1.3.6.1.4.1.17953.9.2.3 NAME 'userPermissionYnh' DESC 'a Yunohost application' From c284954f8f4992a55c40c22938c646697be6d577 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Mon, 30 Mar 2020 17:40:36 +0200 Subject: [PATCH 0761/3170] Update permission code for additional URL, authentication header and label and tile support --- locales/en.json | 2 + src/yunohost/permission.py | 144 ++++++++++++++++++++++++++----------- 2 files changed, 104 insertions(+), 42 deletions(-) diff --git a/locales/en.json b/locales/en.json index 74d021f0a..0c9241381 100644 --- a/locales/en.json +++ b/locales/en.json @@ -1,6 +1,8 @@ { "aborting": "Aborting.", "action_invalid": "Invalid action '{action:s}'", + "additional_urls_already_added": "Additionnal url '{url:s} already added in the additional URL for permission {permission:s}'", + "additional_urls_already_removed": "Additionnal url '{url:s} already removed in the additional URL for permission {permission:s}'", "admin_password": "Administration password", "admin_password_change_failed": "Cannot change password", "admin_password_changed": "The administration password was changed", diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index b53504758..6876b7fdf 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -56,7 +56,8 @@ def user_permission_list(short=False, full=False, ignore_system_perms=False): ldap = _get_ldap_interface() permissions_infos = ldap.search('ou=permission,dc=yunohost,dc=org', '(objectclass=permissionYnh)', - ["cn", 'groupPermission', 'inheritPermission', 'URL', 'isProtected']) + ["cn", 'groupPermission', 'inheritPermission', + 'URL', 'additionalUrls', 'authHeader', 'label', 'showTile', 'isProtected']) # Parse / organize information to be outputed @@ -74,6 +75,10 @@ def user_permission_list(short=False, full=False, ignore_system_perms=False): if full: permissions[name]["corresponding_users"] = [_ldap_path_extract(p, "uid") for p in infos.get('inheritPermission', [])] permissions[name]["url"] = infos.get("URL", [None])[0] + permissions[name]["additional_urls"] = infos.get("additionalUrls", [None]) + permissions[name]["auth_header"] = False if infos.get("authHeader", [False])[0] == "FALSE" else True + permissions[name]["label"] = infos.get("label", [None])[0] + permissions[name]["show_tile"] = False if infos.get("showTile", [False])[0] == "FALSE" else True permissions[name]["protected"] = False if infos.get("isProtected", [False])[0] == "FALSE" else True if short: @@ -82,14 +87,18 @@ def user_permission_list(short=False, full=False, ignore_system_perms=False): return {'permissions': permissions} @is_unit_operation() -def user_permission_update(operation_logger, permission, add=None, remove=None, protected=None, force=False, sync_perm=True): +def user_permission_update(operation_logger, permission, add=None, remove=None, + label=None, show_tile=None, + protected=None, force=False, sync_perm=True): """ Allow or Disallow a user or group to a permission for a specific application Keyword argument: permission -- Name of the permission (e.g. mail or or wordpress or wordpress.editors) - add -- List of groups or usernames to add to this permission - remove -- List of groups or usernames to remove from to this permission + add -- (optional) List of groups or usernames to add to this permission + remove -- (optional) List of groups or usernames to remove from to this permission + label -- (optional) Define a name for the permission. This label will be shown on the SSO and in the admin + show_tile -- (optional) Define if a tile will be shown in the SSO protected -- (optional) Define if the permission can be added/removed to the visitor group force -- (optional) Give the possibility to add/remove access from the visitor group to a protected permission """ @@ -133,8 +142,7 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, logger.warning(m18n.n('permission_already_allowed', permission=permission, group=group)) else: operation_logger.related_to.append(('group', group)) - - new_allowed_groups += groups_to_add + new_allowed_groups += [group] if remove: groups_to_remove = [remove] if not isinstance(remove, list) else remove @@ -155,17 +163,12 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, if "visitors" not in new_allowed_groups or len(new_allowed_groups) >= 3: logger.warning(m18n.n("permission_currently_allowed_for_all_users")) - # Don't update LDAP if we update exactly the same values - if set(new_allowed_groups) == set(current_allowed_groups) and \ - (protected is None or protected == existing_permission["protected"]): - logger.warning(m18n.n("permission_already_up_to_date")) - return existing_permission - # Commit the new allowed group list - operation_logger.start() - new_permission = _update_ldap_group_permission(permission=permission, allowed=new_allowed_groups, protected=protected, sync_perm=sync_perm) + new_permission = _update_ldap_group_permission(permission=permission, allowed=new_allowed_groups, + label=label, show_tile=show_tile, + protected=protected, sync_perm=sync_perm) logger.debug(m18n.n('permission_updated', permission=permission)) @@ -216,15 +219,22 @@ def user_permission_reset(operation_logger, permission, sync_perm=True): @is_unit_operation() -def permission_create(operation_logger, permission, url=None, allowed=None, protected=True, sync_perm=True): +def permission_create(operation_logger, permission, allowed=None, + url=None, additional_urls=None, auth_header=True, + label=None, show_tile=False, + protected=True, sync_perm=True): """ Create a new permission for a specific application Keyword argument: - permission -- Name of the permission (e.g. mail or nextcloud or wordpress.editors) - url -- (optional) URL for which access will be allowed/forbidden - allowed -- (optional) A list of group/user to allow for the permission - protected -- (optional) Define if the permission can be added/removed to the visitor group + permission -- Name of the permission (e.g. mail or nextcloud or wordpress.editors) + allowed -- (optional) List of group/user to allow for the permission + url -- (optional) URL for which access will be allowed/forbidden + additional_urls -- (optional) List of additional URL for which access will be allowed/forbidden + auth_header -- (optional) Define for the URL of this permission, if SSOwat pass the authentication header to the application + label -- (optional) Define a name for the permission. This label will be shown on the SSO and in the admin. Default is "permission name" + show_tile -- (optional) Define if a tile will be shown in the SSO + protected -- (optional) Define if the permission can be added/removed to the visitor group If provided, 'url' is assumed to be relative to the app domain/path if they start with '/'. For example: @@ -263,12 +273,12 @@ def permission_create(operation_logger, permission, url=None, allowed=None, prot 'objectClass': ['top', 'permissionYnh', 'posixGroup'], 'cn': str(permission), 'gidNumber': gid, - 'isProtected': 'FALSE' # Dummy value, it will be fixed when we call '_update_ldap_group_permission' + 'authHeader': ['TRUE'], + 'label': [str(permission)], + 'showTile': ['FALSE'], # Dummy value, it will be fixed when we call '_update_ldap_group_permission' + 'isProtected': ['FALSE'] # Dummy value, it will be fixed when we call '_update_ldap_group_permission' } - if url: - attr_dict['URL'] = url - if allowed is not None: if not isinstance(allowed, list): allowed = [allowed] @@ -287,20 +297,31 @@ def permission_create(operation_logger, permission, url=None, allowed=None, prot except Exception as e: raise YunohostError('permission_creation_failed', permission=permission, error=e) - new_permission = _update_ldap_group_permission(permission=permission, allowed=allowed, protected=protected, sync_perm=sync_perm) + new_permission = _update_ldap_group_permission(permission=permission, allowed=allowed, + label=label, show_tile=show_tile, + protected=protected, sync_perm=False) + + permission_url(permission, url=url, add_url=additional_urls, auth_header=auth_header, + sync_perm=sync_perm) logger.debug(m18n.n('permission_created', permission=permission)) return new_permission @is_unit_operation() -def permission_url(operation_logger, permission, url=None, sync_perm=True): +def permission_url(operation_logger, permission, + url=None, add_url=None, remove_url=None, auth_header=None, + clear_urls=False, sync_perm=True): """ Update urls related to a permission for a specific application Keyword argument: - permission -- Name of the permission (e.g. mail or nextcloud or wordpress.editors) - url -- (optional) URL for which access will be allowed/forbidden + permission -- Name of the permission (e.g. mail or nextcloud or wordpress.editors) + url -- (optional) URL for which access will be allowed/forbidden. + add_url -- (optional) List of additional url to add for which access will be allowed/forbidden + remove_url -- (optional) List of additional url to remove for which access will be allowed/forbidden + auth_header -- (optional) Define for the URL of this permission, if SSOwat pass the authentication header to the application + clear_urls -- (optional) Clean all urls (url and additional_urls) """ from yunohost.utils.ldap import _get_ldap_interface ldap = _get_ldap_interface() @@ -315,12 +336,37 @@ def permission_url(operation_logger, permission, url=None, sync_perm=True): if not existing_permission: raise YunohostError('permission_not_found', permission=permission) - # Compute new url list - old_url = existing_permission["url"] + # TODO -> Check conflict with other app and other URL !! - if old_url == url: - logger.warning(m18n.n('permission_update_nothing_to_do')) - return existing_permission + if url is None: + url = existing_permission["url"] + + current_additional_urls = existing_permission["additional_urls"] + new_additional_urls = copy.copy(current_additional_urls) + + if add_url: + for url in add_url: + if url in current_additional_urls: + logger.warning(m18n.n('additional_urls_already_added', permission=permission, url=url)) + else: + new_additional_urls += [url] + + if remove_url: + for url in remove_url: + if url not in current_additional_urls: + logger.warning(m18n.n('additional_urls_already_removed', permission=permission, url=url)) + + new_additional_urls = [u for u in new_additional_urls if u not in remove_url] + + if auth_header is None: + auth_header = existing_permission['auth_header'] + + if clear_urls: + url = None + new_additional_urls = [] + + # Guarantee uniqueness of all values, which would otherwise make ldap.update angry. + new_additional_urls = set(new_additional_urls) # Actually commit the change @@ -328,7 +374,9 @@ def permission_url(operation_logger, permission, url=None, sync_perm=True): operation_logger.start() try: - ldap.update('cn=%s,ou=permission' % permission, {'URL': [url]}) + ldap.update('cn=%s,ou=permission' % permission, {'URL': [url] if url is not None else [], + 'additionalUrls': new_additional_urls, + 'authHeader': [str(auth_header).upper()]}) except Exception as e: raise YunohostError('permission_update_failed', permission=permission, error=e) @@ -425,12 +473,17 @@ def permission_sync_to_user(): os.system('nscd --invalidate=group') -def _update_ldap_group_permission(permission, allowed, protected=None, sync_perm=True): +def _update_ldap_group_permission(permission, allowed, + label=None, show_tile=None, + protected=None, sync_perm=True): """ Internal function that will rewrite user permission - permission -- Name of the permission (e.g. mail or nextcloud or wordpress.editors) - allowed -- A list of group/user to allow for the permission + permission -- Name of the permission (e.g. mail or nextcloud or wordpress.editors) + allowed -- (optional) A list of group/user to allow for the permission + label -- (optional) Define a name for the permission. This label will be shown on the SSO and in the admin + show_tile -- (optional) Define if a tile will be shown in the SSO + protected -- (optional) Define if the permission can be added/removed to the visitor group Assumptions made, that should be checked before calling this function: @@ -449,20 +502,27 @@ def _update_ldap_group_permission(permission, allowed, protected=None, sync_perm existing_permission = user_permission_list(full=True)["permissions"][permission] if allowed is None: - return existing_permission + allowed = existing_permission['allowed'] - allowed = [allowed] if not isinstance(allowed, list) else allowed - - # Guarantee uniqueness of values in allowed, which would otherwise make ldap.update angry. - allowed = set(allowed) + if label is None: + label = existing_permission["label"] + + if show_tile is None: + show_tile = existing_permission["show_tile"] if protected is None: protected = existing_permission["protected"] + # Guarantee uniqueness of all values, which would otherwise make ldap.update angry. + allowed = set(allowed) + try: ldap.update('cn=%s,ou=permission' % permission, {'groupPermission': ['cn=' + g + ',ou=groups,dc=yunohost,dc=org' for g in allowed], - 'isProtected': "TRUE" if protected else "FALSE"}) + 'label': [str(label)] if label != "" else [], + 'showTile': [str(show_tile).upper()], + 'isProtected': [str(protected).upper()] + }) except Exception as e: raise YunohostError('permission_update_failed', permission=permission, error=e) From 587e902c127bc759ec18cb01783bc5a9611cdd06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Mon, 30 Mar 2020 17:41:25 +0200 Subject: [PATCH 0762/3170] Let's give the possiblity to the user to modify the tile and to show or drop the tile in the SSO --- data/actionsmap/yunohost.yml | 9 +++++++++ src/yunohost/user.py | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 3a4c9db97..892fda8d4 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -318,6 +318,15 @@ user: metavar: GROUP_OR_USER extra: pattern: *pattern_username + -l: + full: --label + help: Label for this permission. This label will be shown on the SSO and in the admin + -s: + full: --show_tile + help: Define if a tile will be shown in the SSO + choices: + - 'True' + - 'False' ## user_permission_reset() reset: diff --git a/src/yunohost/user.py b/src/yunohost/user.py index fdcac658d..eee18aafb 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -775,10 +775,11 @@ def user_permission_list(short=False, full=False): return yunohost.permission.user_permission_list(short, full) -def user_permission_update(permission, add=None, remove=None, sync_perm=True): +def user_permission_update(permission, add=None, remove=None, label=None, show_tile=None, sync_perm=True): import yunohost.permission return yunohost.permission.user_permission_update(permission, add=add, remove=remove, + label=label, show_tile=show_tile, sync_perm=sync_perm) From e82dad18819b8c9578c4fde4c118d3c7963e71e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Mon, 30 Mar 2020 17:42:17 +0200 Subject: [PATCH 0763/3170] Update helper for label and tile support, authentication header and multiple URL support --- data/helpers.d/setting | 115 +++++++++++++++++++++++++++++++++++------ 1 file changed, 98 insertions(+), 17 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index 6d0a5a235..5e5bce6c3 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -250,12 +250,18 @@ ynh_webpath_register () { # Create a new permission for the app # -# example: ynh_permission_create --permission admin --url /admin --allowed alice bob +# example: ynh_permission_create --permission admin --url /admin --additional_urls 'domain.tld/otherurl /superadmin' --allowed alice bob --tile_name 'My app admin' # -# usage: ynh_permission_create --permission "permission" [--url "url"] [--allowed group1 group2] [--protected "true"|"false"] +# usage: ynh_permission_create --permission "permission" [--url "url"] [--additional_urls "second-url" [ "other-url" ]] [--auth_header true|false] +# [--allowed group1 [ group2 ]] [--label "label"] [--show_tile true|false] +# [--protected true|false] # | arg: permission - the name for the permission (by default a permission named "main" already exist) # | arg: url - (optional) URL for which access will be allowed/forbidden +# | arg: additional_urls - (optional) List of additional URL for which access will be allowed/forbidden +# | arg: auth_header - (optional) Define for the URL of this permission, if SSOwat pass the authentication header to the application. Default is true # | arg: allowed - (optional) A list of group/user to allow for the permission +# | arg: label - (optional) Define a name for the permission. This label will be shown on the SSO and in the admin. Default is "APP_LABEL permission name". +# | arg: show_tile - (optional) Define if a tile will be shown in the SSO # | arg: protected - (optional) Define if this permission is protected. If it is protected the administrator # | won't be able to add or remove the visitors group of this permission. # | By default it's 'true' (for the permission different than 'main'). @@ -273,21 +279,51 @@ ynh_webpath_register () { # # Requires YunoHost version 3.7.0 or higher. ynh_permission_create() { - declare -Ar args_array=( [p]=permission= [u]=url= [a]=allowed= [P]=protected= ) + declare -Ar args_array=( [p]=permission= [u]=url= [A]=additional_urls= [h]=auth_header= [a]=allowed= [l]=label= [t]=show_tile= [P]=protected= ) local permission local url + local additional_urls + local auth_header local allowed + local label + local show_tile local protected ynh_handle_getopts_args "$@" if [[ -n ${url:-} ]]; then - url="'$url'" - else - url="None" + url=",url='$url'" fi + + if [[ -n ${additional_urls:-} ]]; then + additional_urls=",additional_urls=['${additional_urls//';'/"','"}']" + fi + + if [[ -n ${auth_header:-} ]]; then + if [ $auth_header == "true" ]; then + auth_header=",auth_header=True" + else + auth_header=",auth_header=False" + fi + fi + if [[ -n ${allowed:-} ]]; then allowed=",allowed=['${allowed//';'/"','"}']" fi + + if [[ -n ${label:-} ]]; then + label=",label='$label'" + else + label=",label='$YNH_APP_LABEL $permission'" + fi + + if [[ -n ${show_tile:-} ]]; then + if [ $show_tile == "true" ]; then + show_tile=",show_tile=True" + else + show_tile=",show_tile=False" + fi + fi + if [[ -n ${protected:-} ]]; then if [ $protected == "true" ]; then protected=",protected=True" @@ -296,7 +332,7 @@ ynh_permission_create() { fi fi - yunohost tools shell -c "from yunohost.permission import permission_create; permission_create('$app.$permission', url=$url ${allowed:-} ${protected:-} , sync_perm=False)" + yunohost tools shell -c "from yunohost.permission import permission_create; permission_create('$app.$permission' ${url:-} ${additional_urls:-} ${auth_header:-} ${allowed:-} ${label:-} ${show_tile:-} ${protected:-} , sync_perm=False)" } # Remove a permission for the app (note that when the app is removed all permission is automatically removed) @@ -331,43 +367,75 @@ ynh_permission_exists() { # Redefine the url associated to a permission # -# usage: ynh_permission_url --permission "permission" --url "url" +# usage: ynh_permission_url --permission "permission" [--url "url"] [--add_url "new-url" [ "other-new-url" ]] [--remove_url "old-url" [ "other-old-url"]] +# [--auth_header true|false][--clear_urls] # | arg: permission - the name for the permission (by default a permission named "main" is removed automatically when the app is removed) -# | arg: url - (optional) URL for which access will be allowed/forbidden +# | arg: url - (optional) URL for which access will be allowed/forbidden. Note that if you want to remove url you can pass an empty sting as arguments (""). +# | arg: add_url - (optional) List of additional url to add for which access will be allowed/forbidden. +# | arg: remove_url - (optional) List of additional url to remove for which access will be allowed/forbidden +# | arg: auth_header - (optional) Define for the URL of this permission, if SSOwat pass the authentication header to the application +# | arg: clear_urls - (optional) Clean all urls (url and additional_urls) # # Requires YunoHost version 3.7.0 or higher. ynh_permission_url() { - declare -Ar args_array=([p]=permission= [u]=url=) + declare -Ar args_array=([p]=permission= [u]=url= [a]=add_url= [r]=remove_url= [h]=auth_header= [c]=clear_urls) local permission local url + local add_url + local remove_url + local auth_header + local clear_urls ynh_handle_getopts_args "$@" if [[ -n ${url:-} ]]; then - url="'$url'" - else - url="None" + url=",url='$url'" fi - yunohost tools shell -c "from yunohost.permission import permission_url; permission_url('$app.$permission', url=$url)" + if [[ -n ${add_url:-} ]]; then + add_url=",add_url=['${add_url//';'/"','"}']" + fi + + if [[ -n ${remove_url:-} ]]; then + remove_url=",remove_url=['${remove_url//';'/"','"}']" + fi + + if [[ -n ${auth_header:-} ]]; then + if [ $auth_header == "true" ]; then + auth_header=",auth_header=True" + else + auth_header=",auth_header=False" + fi + fi + + if [[ -n ${clear_urls:-} ]] && [ $clear_urls -eq 1 ]; then + clear_urls=",clear_urls=True" + fi + + yunohost tools shell -c "from yunohost.permission import permission_url; permission_url('$app.$permission' ${url:-} ${add_url:-} ${remove_url:-} ${auth_header:-} ${clear_urls:-} )" } # Update a permission for the app # -# usage: ynh_permission_update --permission "permission" --add "group" ["group" ...] --remove "group" ["group" ...] [--protected "true"|"false"] +# usage: ynh_permission_update --permission "permission" [--add "group" ["group" ...]] [--remove "group" ["group" ...]] +# [--label "label"] [--show_tile true|false] [--protected true|false] # | arg: permission - the name for the permission (by default a permission named "main" already exist) # | arg: add - the list of group or users to enable add to the permission # | arg: remove - the list of group or users to remove from the permission +# | arg: label - (optional) Define a name for the permission. This label will be shown on the SSO and in the admin. +# | arg: show_tile - (optional) Define if a tile will be shown in the SSO # | arg: protected - (optional) Define if this permission is protected. If it is protected the administrator # | won't be able to add or remove the visitors group of this permission. # # example: ynh_permission_update --permission admin --add samdoe --remove all_users # Requires YunoHost version 3.7.0 or higher. ynh_permission_update() { - declare -Ar args_array=( [p]=permission= [a]=add= [r]=remove= [P]=protected= ) + declare -Ar args_array=( [p]=permission= [a]=add= [r]=remove= [l]=label= [t]=show_tile= [P]=protected= ) local permission local add local remove + local label + local show_tile local protected ynh_handle_getopts_args "$@" @@ -377,6 +445,19 @@ ynh_permission_update() { if [[ -n ${remove:-} ]]; then remove=",remove=['${remove//';'/"','"}']" fi + + if [[ -n ${label:-} ]]; then + label=",label='$label'" + fi + + if [[ -n ${show_tile:-} ]]; then + if [ $show_tile == "true" ]; then + show_tile=",show_tile=True" + else + show_tile=",show_tile=False" + fi + fi + if [[ -n ${protected:-} ]]; then if [ $protected == "true" ]; then protected=",protected=True" @@ -385,5 +466,5 @@ ynh_permission_update() { fi fi - yunohost tools shell -c "from yunohost.permission import user_permission_update; user_permission_update('$app.$permission' ${add:-} ${remove:-} ${protected:-} , force=True, sync_perm=False)" + yunohost tools shell -c "from yunohost.permission import user_permission_update; user_permission_update('$app.$permission' ${add:-} ${remove:-} ${label:-} ${show_tile:-} ${protected:-} , force=True, sync_perm=False)" } From 8ca07ad7d83fd75db760d30bc315a6d445a687a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Mon, 30 Mar 2020 17:44:05 +0200 Subject: [PATCH 0764/3170] Pass as argument to app script the main app label --- src/yunohost/app.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index ede8e761d..a0bc87ddb 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -421,7 +421,7 @@ def app_upgrade(app=[], url=None, file=None): """ from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback - from yunohost.permission import permission_sync_to_user + from yunohost.permission import permission_sync_to_user, user_permission_list apps = app # If no app is specified, upgrade all apps @@ -483,6 +483,7 @@ def app_upgrade(app=[], url=None, file=None): 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_LABEL"] = user_permission_list(full=True, ignore_system_perms=True)['permissions'][app_id+".main"]['label'] # Start register change on system related_to = [('app', app_instance_name)] @@ -664,6 +665,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu raise YunohostError('app_id_invalid') app_id = manifest['id'] + label = label if label else manifest['name'] # Check requirements _check_manifest_requirements(manifest, app_id) @@ -695,6 +697,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_LABEL"] = label # Start register change on system operation_logger.extra.update({'env': env_dict}) @@ -721,7 +724,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu # Set initial app settings app_settings = { 'id': app_instance_name, - 'label': label if label else manifest['name'], + 'label': label, 'install_time': int(time.time()), 'current_revision': manifest.get('remote', {}).get('revision', "?") } @@ -750,7 +753,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu # Initialize the main permission for the app # After the install, if apps don't have a domain and path defined, the default url '/' is removed from the permission - permission_create(app_instance_name+".main", url="/", allowed=["all_users"], protected=False) + permission_create(app_instance_name+".main", url="/", allowed=["all_users"], label=label, show_tile=True, protected=False) # Execute the app install script install_failed = True From 51a0502e9100b10356eb62b6d148a41c79d00f44 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Mon, 30 Mar 2020 19:36:41 +0200 Subject: [PATCH 0765/3170] add ynh_permission_has_user --- data/actionsmap/yunohost.yml | 9 +++++++++ data/helpers.d/setting | 19 +++++++++++++++++++ src/yunohost/permission.py | 22 ++++++++++++++++++++++ src/yunohost/user.py | 6 ++++++ 4 files changed, 56 insertions(+) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 3a4c9db97..c0eca3d03 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -296,6 +296,15 @@ user: help: Display all info known about each permission, including the full user list of each group it is granted to. action: store_true + ### user_permission_info() + info: + action_help: Get information about a specific permission + api: GET /users/permissions/ + arguments: + permission: + help: Name of the permission to fetch info about + extra: + pattern: *pattern_username ### user_permission_update() update: diff --git a/data/helpers.d/setting b/data/helpers.d/setting index 384fdc399..1c1139442 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -367,3 +367,22 @@ ynh_permission_update() { yunohost user permission update "$app.$permission" ${add:-} ${remove:-} } + +# Check if a permission exists +# +# usage: ynh_permission_has_user --permission=permission --user=user +# | arg: -p, --permission - the permission to check +# | arg: -u, --user - the user seek in the permission +# +# Requires YunoHost version 3.7.1 or higher. +ynh_permission_has_user() { + declare -Ar args_array=( [p]=permission= [u]=user) + local permission + ynh_handle_getopts_args "$@" + + if ! ynh_permission_exists --permission $permission + return 1 + fi + + yunohost user permission info $permission | grep -w -q "$user" +} \ No newline at end of file diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 2aea6f4c4..05def2101 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -196,6 +196,28 @@ def user_permission_reset(operation_logger, permission, sync_perm=True): return new_permission + +def user_permission_info(permission, sync_perm=True): + """ + Return informations about a specific permission + + Keyword argument: + permission -- Name of the permission (e.g. mail or nextcloud or wordpress.editors) + """ + + # By default, manipulate main permission + if "." not in permission: + permission = permission + ".main" + + # Fetch existing permission + + existing_permission = user_permission_list(full=True)["permissions"].get(permission, None) + if existing_permission is None: + raise YunohostError('permission_not_found', permission=permission) + + return existing_permission + + # # # The followings methods are *not* directly exposed. diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 39a2d8f15..74ad9f977 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -780,6 +780,12 @@ def user_permission_reset(permission, sync_perm=True): sync_perm=sync_perm) +def user_permission_info(permission, sync_perm=True): + import yunohost.permission + return yunohost.permission.user_permission_info(permission, + sync_perm=sync_perm) + + # # SSH subcategory # From 288a617975cbe06321fcddb5bbf558989925cf6a Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Mon, 30 Mar 2020 19:58:06 +0200 Subject: [PATCH 0766/3170] Let's have a working helper --- data/helpers.d/setting | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index 1c1139442..5e88bf259 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -374,15 +374,22 @@ ynh_permission_update() { # | arg: -p, --permission - the permission to check # | arg: -u, --user - the user seek in the permission # +# example: ynh_permission_has_user --permission=nextcloud.main --user=visitors +# # Requires YunoHost version 3.7.1 or higher. ynh_permission_has_user() { - declare -Ar args_array=( [p]=permission= [u]=user) + local legacy_args=pu + # Declare an array to define the options of this helper. + declare -Ar args_array=( [p]=permission= [u]=user= ) local permission + local user + # Manage arguments with getopts ynh_handle_getopts_args "$@" - if ! ynh_permission_exists --permission $permission + if ! ynh_permission_exists --permission=$permission + then return 1 fi yunohost user permission info $permission | grep -w -q "$user" -} \ No newline at end of file +} From ad22677994399065785b0ffa889a842c284b2f9f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 30 Mar 2020 20:09:26 +0200 Subject: [PATCH 0767/3170] Attempt to simplify permission migration --- data/helpers.d/setting | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index 384fdc399..557afb332 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -197,7 +197,7 @@ EOF if [[ "$1" == "set" ]] && [[ "${4:-}" == "/" ]] then ynh_permission_update --permission "main" --add "visitors" - elif [[ "$1" == "delete" ]] && [[ "${current_value:-}" == "/" ]] + elif [[ "$1" == "delete" ]] && [[ "${current_value:-}" == "/" ]] && [[ -n "$(ynh_app_setting_get --app=$2 --key='is_public' )" ]] then ynh_permission_update --permission "main" --remove "visitors" fi From ffe4a0b1c81b84be2101696b67b6920a3351a131 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 30 Mar 2020 20:54:57 +0200 Subject: [PATCH 0768/3170] Lazy loading might improve performances a bit --- src/yunohost/domain.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index eb84f27d0..456dfa4bf 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -32,8 +32,6 @@ from moulinette.core import MoulinetteError from yunohost.utils.error import YunohostError from moulinette.utils.log import getActionLogger -import yunohost.certificate - from yunohost.app import app_ssowatconf from yunohost.regenconf import regen_conf from yunohost.utils.network import get_public_ip @@ -109,6 +107,7 @@ def domain_add(operation_logger, domain, dyndns=False): dyndns_subscribe(domain=domain) try: + import yunohost.certificate yunohost.certificate._certificate_install_selfsigned([domain], False) attr_dict = { @@ -302,14 +301,17 @@ def domain_main_domain(operation_logger, new_main_domain=None): def domain_cert_status(domain_list, full=False): + import yunohost.certificate return yunohost.certificate.certificate_status(domain_list, full) def domain_cert_install(domain_list, force=False, no_checks=False, self_signed=False, staging=False): + import yunohost.certificate return yunohost.certificate.certificate_install(domain_list, force, no_checks, self_signed, staging) def domain_cert_renew(domain_list, force=False, no_checks=False, email=False, staging=False): + import yunohost.certificate return yunohost.certificate.certificate_renew(domain_list, force, no_checks, email, staging) From 654eb0f583c080e268f4d077c2e6b45cb0fea050 Mon Sep 17 00:00:00 2001 From: xaloc33 Date: Fri, 27 Mar 2020 22:44:39 +0000 Subject: [PATCH 0769/3170] Translated using Weblate (Catalan) Currently translated at 100.0% (612 of 612 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ca/ --- locales/ca.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/locales/ca.json b/locales/ca.json index 0f61a2fdb..2b482ca3a 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -272,7 +272,7 @@ "log_user_delete": "Elimina l'usuari « {} »", "log_user_update": "Actualitza la informació de l'usuari « {} »", "log_domain_main_domain": "Fes de « {} » el domini principal", - "log_tools_migrations_migrate_forward": "Migrar", + "log_tools_migrations_migrate_forward": "Executa les migracions", "log_tools_migrations_migrate_backward": "Migrar endarrera", "log_tools_postinstall": "Fer la post instal·lació del servidor YunoHost", "log_tools_upgrade": "Actualitza els paquets del sistema", @@ -551,7 +551,7 @@ "log_user_permission_add": "Actualitzar el permís «{}»", "log_user_permission_remove": "Actualitzar el permís «{}»", "mailbox_disabled": "La bústia de correu està desactivada per al usuari {user:s}", - "migration_description_0011_setup_group_permission": "Configurar el grup d'usuaris i els permisos per les aplicacions i els serveis", + "migration_description_0011_setup_group_permission": "Configurar els grups d'usuaris i els permisos per les aplicacions i els serveis", "migration_0011_backup_before_migration": "Creant una còpia de seguretat de la base de dades LDAP i la configuració de les aplicacions abans d'efectuar la migració.", "migration_0011_can_not_backup_before_migration": "No s'ha pogut completar la còpia de seguretat abans de que la migració fallés. Error: {error:s}", "migration_0011_create_group": "Creant un grup per a cada usuari…", @@ -726,5 +726,6 @@ "diagnosis_never_ran_yet": "Sembla que el servidor s'ha configurat recentment i encara no hi cap informe de diagnòstic per mostrar. S'ha d'executar un diagnòstic complet primer, ja sigui des de la pàgina web d'administració o utilitzant la comanda «yunohost diagnosis run» al terminal.", "diagnosis_description_web": "Web", "diagnosis_basesystem_hardware_board": "El model de la targeta del servidor és {model}", - "diagnosis_basesystem_hardware": "L'arquitectura del maquinari del servidor és {virt} {arch}" + "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à…" } From b97785c8082e8b79b7bef8efabdf9701a0b6fa1d Mon Sep 17 00:00:00 2001 From: ButterflyOfFire Date: Sat, 28 Mar 2020 21:45:44 +0000 Subject: [PATCH 0770/3170] Translated using Weblate (Arabic) Currently translated at 18.8% (115 of 612 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ar/ --- locales/ar.json | 76 ++++++++++++++++++++++++------------------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/locales/ar.json b/locales/ar.json index 8b1655e34..db77b5cb4 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -1,7 +1,7 @@ { "action_invalid": "إجراء غير صالح '{action:s}'", "admin_password": "كلمة السر الإدارية", - "admin_password_change_failed": "تعذرت عملية تعديل كلمة السر", + "admin_password_change_failed": "لا يمكن تعديل الكلمة السرية", "admin_password_changed": "تم تعديل الكلمة السرية الإدارية", "app_already_installed": "{app:s} تم تنصيبه مِن قبل", "app_already_installed_cant_change_url": "", @@ -10,28 +10,28 @@ "app_argument_invalid": "", "app_argument_required": "المُعامِل '{name:s}' مطلوب", "app_change_no_change_url_script": "إنّ التطبيق {app_name:s} لا يدعم تغيير الرابط، مِن الممكن أنه يتوجب عليكم تحدثيه.", - "app_change_url_failed_nginx_reload": "فشلت عملية إعادة تشغيل nginx. ها هي نتيجة الأمر 'nginx -t':\n{nginx_errors:s}", - "app_change_url_identical_domains": "The old and new domain/url_path are identical ('{domain:s}{path:s}'), nothing to do.", - "app_change_url_no_script": "This application '{app_name:s}' doesn't support url modification yet. Maybe you should upgrade the application.", - "app_change_url_success": "Successfully changed {app:s} url to {domain:s}{path:s}", + "app_change_url_failed_nginx_reload": "فشلت عملية إعادة تشغيل NGINX. ها هي نتيجة الأمر 'nginx -t':\n{nginx_errors:s}", + "app_change_url_identical_domains": "", + "app_change_url_no_script": "", + "app_change_url_success": "", "app_extraction_failed": "تعذر فك الضغط عن ملفات التنصيب", - "app_id_invalid": "Invalid app id", + "app_id_invalid": "", "app_incompatible": "إن التطبيق {app} غير متوافق مع إصدار واي يونوهوست YunoHost الخاص بك", "app_install_files_invalid": "ملفات التنصيب خاطئة", "app_location_already_used": "The app '{app}' is already installed on that location ({path})", - "app_make_default_location_already_used": "Can't make the app '{app}' the default on the domain {domain} is already used by the other app '{other_app}'", + "app_make_default_location_already_used": "", "app_location_install_failed": "Unable to install the app in this location because it conflit with the app '{other_app}' already installed on '{other_path}'", - "app_location_unavailable": "This url is not available or conflicts with an already installed app", - "app_manifest_invalid": "Invalid app manifest: {error}", + "app_location_unavailable": "", + "app_manifest_invalid": "", "app_no_upgrade": "ليس هناك أي تطبيق بحاجة إلى تحديث", "app_not_correctly_installed": "يبدو أن التطبيق {app:s} لم يتم تنصيبه بشكل صحيح", "app_not_installed": "إنّ التطبيق {app:s} غير مُنصَّب", "app_not_properly_removed": "لم يتم حذف تطبيق {app:s} بشكلٍ جيّد", - "app_package_need_update": "The app {app} package needs to be updated to follow YunoHost changes", + "app_package_need_update": "", "app_removed": "تمت إزالة تطبيق {app:s}", "app_requirements_checking": "جار فحص الحزم اللازمة لـ {app}…", - "app_requirements_failed": "Unable to meet requirements for {app}: {error}", - "app_requirements_unmeet": "Requirements are not met for {app}, the package {pkgname} ({version}) must be {spec}", + "app_requirements_failed": "", + "app_requirements_unmeet": "", "app_sources_fetch_failed": "تعذرت عملية جلب مصادر الملفات", "app_unknown": "برنامج مجهول", "app_unsupported_remote_type": "Unsupported remote type used for the app", @@ -64,7 +64,7 @@ "backup_applying_method_borg": "Sending all files to backup into borg-backup repository…", "backup_applying_method_copy": "جارٍ نسخ كافة الملفات إلى النسخة الإحتياطية …", "backup_applying_method_custom": "Calling the custom backup method '{method:s}'…", - "backup_applying_method_tar": "جارٍ إنشاء ملف tar للنسخة الاحتياطية…", + "backup_applying_method_tar": "جارٍ إنشاء ملف TAR للنسخة الاحتياطية…", "backup_archive_app_not_found": "App '{app:s}' not found in the backup archive", "backup_archive_broken_link": "Unable to access backup archive (broken link to {path:s})", "backup_archive_mount_failed": "Mounting the backup archive failed", @@ -114,7 +114,7 @@ "certmanager_attempt_to_replace_valid_cert": "You are attempting to overwrite a good and valid certificate for domain {domain:s}! (Use --force to bypass)", "certmanager_cannot_read_cert": "Something wrong happened when trying to open current certificate for domain {domain:s} (file: {file:s}), reason: {reason:s}", "certmanager_cert_install_success": "تمت عملية تنصيب شهادة Let's Encrypt بنجاح على النطاق {domain:s} !", - "certmanager_cert_install_success_selfsigned": "نجحت عملية تثبيت الشهادة الموقعة ذاتيا الخاصة بالنطاق {domain:s}!", + "certmanager_cert_install_success_selfsigned": "نجحت عملية تثبيت الشهادة الموقعة ذاتيا الخاصة بالنطاق {domain:s}", "certmanager_cert_renew_success": "نجحت عملية تجديد شهادة Let's Encrypt الخاصة باسم النطاق {domain:s} !", "certmanager_cert_signing_failed": "فشل إجراء توقيع الشهادة الجديدة", "certmanager_certificate_fetching_or_enabling_failed": "Sounds like enabling the new certificate for {domain:s} failed somehow…", @@ -182,17 +182,17 @@ "firewall_reloaded": "The firewall has been reloaded", "firewall_rules_cmd_failed": "Some firewall rules commands have failed. For more information, see the log.", "format_datetime_short": "%m/%d/%Y %I:%M %p", - "global_settings_bad_choice_for_enum": "Bad value for setting {setting:s}, received {choice:s}, except {available_choices:s}", - "global_settings_bad_type_for_setting": "Bad type for setting {setting:s}, received {received_type:s}, except {expected_type:s}", - "global_settings_cant_open_settings": "Failed to open settings file, reason: {reason:s}", - "global_settings_cant_serialize_settings": "Failed to serialize settings data, reason: {reason:s}", - "global_settings_cant_write_settings": "Failed to write settings file, reason: {reason:s}", - "global_settings_key_doesnt_exists": "The key '{settings_key:s}' doesn't exists in the global settings, you can see all the available keys by doing 'yunohost settings list'", - "global_settings_reset_success": "Success. Your previous settings have been backuped in {path:s}", - "global_settings_setting_example_bool": "Example boolean option", - "global_settings_setting_example_enum": "Example enum option", - "global_settings_setting_example_int": "Example int option", - "global_settings_setting_example_string": "Example string option", + "global_settings_bad_choice_for_enum": "", + "global_settings_bad_type_for_setting": "", + "global_settings_cant_open_settings": "", + "global_settings_cant_serialize_settings": "", + "global_settings_cant_write_settings": "", + "global_settings_key_doesnt_exists": "", + "global_settings_reset_success": "", + "global_settings_setting_example_bool": "", + "global_settings_setting_example_enum": "", + "global_settings_setting_example_int": "", + "global_settings_setting_example_string": "", "global_settings_unknown_setting_from_settings_file": "Unknown key in settings: '{setting_key:s}', discarding it and save it in /etc/yunohost/unkown_settings.json", "global_settings_unknown_type": "Unexpected situation, the setting {setting:s} appears to have the type {unknown_type:s} but it's not a type supported by the system.", "hook_exec_failed": "Script execution failed: {path:s}", @@ -216,7 +216,7 @@ "migrate_tsig_end": "Migration to hmac-sha512 finished", "migrate_tsig_failed": "Migrating the dyndns domain {domain} to hmac-sha512 failed, rolling back. Error: {error_code} - {error}", "migrate_tsig_start": "Not secure enough key algorithm detected for TSIG signature of domain '{domain}', initiating migration to the more secure one hmac-sha512", - "migrate_tsig_wait": "لننتظر الآن 3 دقائق ريثما يأخذ خادم أسماء النطاقات الديناميكية بعين الاعتبار المفتاح الجديد…", + "migrate_tsig_wait": "لننتظر الآن ثلاثة دقائق ريثما يأخذ خادم أسماء النطاقات الديناميكية بعين الاعتبار المفتاح الجديد…", "migrate_tsig_wait_2": "دقيقتين …", "migrate_tsig_wait_3": "دقيقة واحدة …", "migrate_tsig_wait_4": "30 ثانية …", @@ -316,7 +316,7 @@ "service_conf_updated": "The configuration has been updated for service '{service}'", "service_conf_would_be_updated": "The configuration would have been updated for service '{service}'", "service_disable_failed": "", - "service_disabled": "تم تعطيل خدمة '{service:s}'", + "service_disabled": "لن يتم إطلاق خدمة '{service:s}' أثناء بداية تشغيل النظام.", "service_enable_failed": "", "service_enabled": "تم تنشيط خدمة '{service:s}'", "service_no_log": "ليس لخدمة '{service:s}' أي سِجلّ للعرض", @@ -344,7 +344,7 @@ "unrestore_app": "App '{app:s}' will not be restored", "update_cache_failed": "Unable to update APT cache", "updating_apt_cache": "جارٍ جلب قائمة حُزم النظام المحدّثة المتوفرة…", - "upgrade_complete": "إكتملت عملية الترقية و التحديث", + "upgrade_complete": "اكتملت عملية الترقية و التحديث", "upgrading_packages": "عملية ترقية الحُزم جارية …", "upnp_dev_not_found": "No UPnP device found", "upnp_disabled": "تم تعطيل UPnP", @@ -368,7 +368,7 @@ "migration_description_0003_migrate_to_stretch": "تحديث النظام إلى ديبيان ستريتش و واي يونوهوست 3.0", "migration_0003_patching_sources_list": "عملية تصحيح ملف المصادر sources.lists جارية…", "migration_0003_main_upgrade": "بداية عملية التحديث الأساسية…", - "migration_0003_fail2ban_upgrade": "بداية عملية تحديث fail2ban…", + "migration_0003_fail2ban_upgrade": "بداية عملية تحديث Fail2Ban…", "migration_0003_not_jessie": "إن توزيعة ديبيان الحالية تختلف عن جيسي !", "migration_description_0002_migrate_to_tsig_sha256": "يقوم بتحسين أمان TSIG لنظام أسماء النطاقات الديناميكة باستخدام SHA512 بدلًا مِن MD5", "migration_0003_backward_impossible": "لا يُمكن إلغاء عملية الإنتقال إلى ستريتش.", @@ -412,11 +412,11 @@ "service_description_dnsmasq": "مُكلَّف بتحليل أسماء النطاقات (DNS)", "service_description_mysql": "يقوم بتخزين بيانات التطبيقات (قواعد بيانات SQL)", "service_description_rspamd": "يقوم بتصفية البريد المزعج و إدارة ميزات أخرى للبريد", - "service_description_yunohost-firewall": "يُدير فتح وإغلاق منافذ الإتصال إلى الخدمات", + "service_description_yunohost-firewall": "يُدير فتح وإغلاق منافذ الاتصال إلى الخدمات", "users_available": "المستخدمون المتوفرون:", "aborting": "إلغاء.", "admin_password_too_long": "يرجى اختيار كلمة سرية أقصر مِن 127 حرف", - "app_not_upgraded": "لم يتم تحديث التطبيقات التالية: {apps}", + "app_not_upgraded": "", "app_start_install": "جارٍ تثبيت التطبيق {app}…", "app_start_remove": "جارٍ حذف التطبيق {app}…", "app_start_restore": "جارٍ استرجاع التطبيق {app}…", @@ -427,25 +427,25 @@ "global_settings_setting_security_password_user_strength": "قوة الكلمة السرية للمستخدم", "log_app_addaccess": "إضافة ترخيص بالنفاذ إلى '{}'", "password_too_simple_1": "يجب أن يكون طول الكلمة السرية على الأقل 8 حروف", - "service_description_php7.0-fpm": "يُشغّل التطبيقات المكتوبة بلغة الـ PHP على Nginx", + "service_description_php7.0-fpm": "يُشغّل التطبيقات المكتوبة بلغة الـ PHP على NGINX", "updating_app_lists": "جارٍ جلب التحديثات المتوفرة الخاصة بالتطبيقات…", - "already_up_to_date": "كل شيء على ما يرام! ليس هناك ما يتطلّب تحديثًا!", + "already_up_to_date": "كل شيء على ما يرام. ليس هناك ما يتطلّب تحديثًا.", "service_description_nslcd": "يدير اتصال متسخدمي واي يونوهوست عبر طرفية سطر الأوامر", "service_description_slapd": "يخزّن المستخدمين والنطاقات والمعلومات المتعلقة بها", - "service_reloaded": "تم إعادة تحميل خدمة '{service:s}'", + "service_reloaded": "تم إعادة تشغيل خدمة '{service:s}'", "service_restarted": "تم إعادة تشغيل خدمة '{service:s}'", "group_unknown": "الفريق {group:s} مجهول", - "group_deletion_failed": "فشلت عملية حذف الفريق '{group}'", + "group_deletion_failed": "فشلت عملية حذف الفريق '{group}': {error}", "group_deleted": "تم حذف الفريق '{group}'", - "group_created": "تم إنشاء الفريق '{group}' بنجاح", + "group_created": "تم إنشاء الفريق '{group}'", "group_name_already_exist": "الفريق {name:s} موجود بالفعل", "error_when_removing_sftpuser_group": "حدث خطأ أثناء محاولة حذف فريق sftpusers", "dyndns_could_not_check_available": "لا يمكن التحقق مِن أنّ {domain:s} متوفر على {provider:s}.", "backup_mount_archive_for_restore": "جارٍ تهيئة النسخة الاحتياطية للاسترجاع…", "root_password_replaced_by_admin_password": "لقد تم استبدال كلمة سر الجذر root بالكلمة الإدارية لـ admin.", "app_upgrade_stopped": "لقد تم إلغاء تحديث كافة التطبيقات لتجنب حادث بسبب فشل تحديث التطبيق السابق", - "app_action_broke_system": "يبدو أنّ هذا الإجراء أدّى إلى تحطيم خدمات مهمّة: {services}", - "diagnosis_basesystem_host": "هذا الخادم يُشغّل ديبيان {debian_version}.", + "app_action_broke_system": "يبدو أنّ هذا الإجراء أدّى إلى تحطيم هذه الخدمات المهمة: {services}", + "diagnosis_basesystem_host": "هذا الخادم يُشغّل ديبيان {debian_version}", "diagnosis_basesystem_kernel": "هذا الخادم يُشغّل نواة لينكس {kernel_version}", "diagnosis_basesystem_ynh_single_version": "{0} الإصدار: {1} ({2})", "diagnosis_basesystem_ynh_main_version": "هذا الخادم يُشغّل YunoHost {main_version} ({repo})", From 351cccabfa7a0bd897dbfaea0f1f66b50a4d2a7b Mon Sep 17 00:00:00 2001 From: romain raynaud Date: Sun, 29 Mar 2020 21:04:49 +0000 Subject: [PATCH 0771/3170] Translated using Weblate (Spanish) Currently translated at 87.4% (535 of 612 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/es/ --- locales/es.json | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/locales/es.json b/locales/es.json index 767dc6bfa..4acb8d638 100644 --- a/locales/es.json +++ b/locales/es.json @@ -605,7 +605,7 @@ "admin_password_too_long": "Elija una contraseña de menos de 127 caracteres", "aborting": "Cancelando.", "app_upgrade_stopped": "Se ha detenido la actualización de todas las aplicaciones para prevenir un posible daño porque una aplicación no se pudo actualizar", - "app_action_broke_system": "Esta acción parece que ha roto estos importantes servicios: {services}", + "app_action_broke_system": "Esta acción parece que ha roto estos servicios importantes: {services}", "operation_interrupted": "¿Ha sido interrumpida la operación manualmente?", "apps_already_up_to_date": "Todas las aplicaciones están ya actualizadas", "dyndns_provider_unreachable": "No se puede conectar con el proveedor de Dyndns {provider}: o su YunoHost no está correctamente conectado a Internet o el servidor de dynette está caído.", @@ -670,11 +670,11 @@ "diagnosis_ip_connected_ipv6": "¡El servidor está conectado a internet a través de IPv6!", "diagnosis_ip_no_ipv6": "IPv6 en el servidor no está funcionando.", "diagnosis_ip_dnsresolution_working": "¡DNS no está funcionando!", - "diagnosis_ip_broken_dnsresolution": "DNS parece que no funciona por alguna razón... ¿Hay algún firewall bloqueando peticiones DNS?", + "diagnosis_ip_broken_dnsresolution": "Parece que no funciona resolución de nombre de dominio por alguna razón... ¿Hay algún firewall bloqueando peticiones DNS?", "diagnosis_ip_weird_resolvconf": "Parece que DNS funciona, pero ten cuidado, porque estás utilizando /etc/resolv.conf modificado.", - "diagnosis_ip_weird_resolvconf_details": "En su lugar, este fichero debería ser un enlace simbólico a /etc/resolvconf/run/resolv.conf apuntando a 127.0.0.1 (dnsmasq). Los resolvedores reales deben configurarse a través de /etc/resolv.dnsmasq.conf.", + "diagnosis_ip_weird_resolvconf_details": "En su lugar, este fichero debería ser un enlace simbólico a /etc/resolvconf/run/resolv.conf apuntando a 127.0.0.1 (dnsmasq). Los servidores de nombre de domino deben configurarse a través de /etc/resolv.dnsmasq.conf.", "diagnosis_dns_good_conf": "Buena configuración DNS para el dominio {domain} (categoría {category})", - "diagnosis_dns_bad_conf": "Mala configuración DNS o configuración DNS faltante para el dominio {domain} (categoría {category})", + "diagnosis_dns_bad_conf": "Configuración de los DNS mala o faltante para el dominio {domain} (categoría {category})", "diagnosis_dns_discrepancy": "El registro DNS con tipo {0} y nombre {1} no se corresponde a la configuración recomendada. Valor actual: {2}. Valor esperado: {3}. Puedes consultar https://yunohost.org/dns_config para más información.", "diagnosis_services_bad_status": "El servicio {service} está {status} :(", "diagnosis_diskusage_verylow": "El almacenamiento {mountpoint} (en el dispositivo {device}) sólo tiene {free_abs_GB} GB ({free_percent}%) de espacio disponible. Deberías considerar la posibilidad de limpiar algo de espacio.", @@ -690,7 +690,7 @@ "diagnosis_swap_none": "El sistema no tiene mas espacio de intercambio. Considera agregar por lo menos 256 MB de espacio de intercambio para evitar que el sistema se quede sin memoria.", "diagnosis_swap_notsomuch": "Al sistema le queda solamente {total_MB} MB de espacio de intercambio. Considera agregar al menos 256 MB para evitar que el sistema se quede sin memoria.", "diagnosis_mail_ougoing_port_25_ok": "El puerto de salida 25 no esta bloqueado y los correos electrónicos pueden ser enviados a otros servidores.", - "diagnosis_mail_ougoing_port_25_blocked": "El puerto de salida 25 parece estar bloqueado. Intenta desbloquearlo con el panel de configuración de tu proveedor de servicios de Internet (o hoster). Mientras tanto, el servidor no podrá enviar correos electrónicos a otros servidores.", + "diagnosis_mail_ougoing_port_25_blocked": "El puerto de salida 25 parece estar bloqueado. Intenta desbloquearlo con el panel de configuración de tu proveedor de servicios de Internet (o proveedor de halbergue). Mientras tanto, el servidor no podrá enviar correos electrónicos a otros servidores.", "diagnosis_regenconf_allgood": "Todos los archivos de configuración están en linea con la configuración recomendada!", "diagnosis_regenconf_manually_modified": "El archivo de configuración {file} fue modificado manualmente.", "diagnosis_regenconf_manually_modified_details": "Esto este probablemente BIEN siempre y cuando sepas lo que estas haciendo ;) !", @@ -705,5 +705,15 @@ "diagnosis_description_services": "Comprobación del estado de los servicios", "diagnosis_description_ports": "Exposición de puertos", "diagnosis_description_systemresources": "Recursos del sistema", - "diagnosis_swap_ok": "El sistema tiene {total_MB} MB de espacio de intercambio!" + "diagnosis_swap_ok": "El sistema tiene {total_MB} MB de espacio de intercambio!", + "diagnosis_ports_needed_by": "La apertura de este puerto es requerida para la funcionalidad {1} (service {0})", + "diagnosis_ports_ok": "El puerto {port} es accesible desde internet.", + "diagnosis_ports_unreachable": "El puerto {port} no es accesible desde internet", + "diagnosis_ports_could_not_diagnose": "No se puede comprobar si los puertos están accesibles desde el exterior. Error: {error}", + "diagnosis_description_security": "Validación de seguridad", + "diagnosis_description_regenconf": "Configurationes de systema", + "diagnosis_description_mail": "correo electronico", + "diagnosis_description_web": "web", + "diagnosis_basesystem_hardware_board": "El modelo de placa del servidor es {model}", + "diagnosis_basesystem_hardware": "La arquitectura material del servidor es {virt} {arch}" } From 9b80d4b6d6c6c1b681d3bfd4a1c8268eefc419dc Mon Sep 17 00:00:00 2001 From: romain raynaud Date: Mon, 30 Mar 2020 14:46:13 +0000 Subject: [PATCH 0772/3170] Translated using Weblate (Spanish) Currently translated at 93.5% (572 of 612 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/es/ --- locales/es.json | 57 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/locales/es.json b/locales/es.json index 4acb8d638..dca7f1582 100644 --- a/locales/es.json +++ b/locales/es.json @@ -104,7 +104,7 @@ "field_invalid": "Campo no válido '{:s}'", "firewall_reload_failed": "No se pudo recargar el cortafuegos", "firewall_reloaded": "Cortafuegos recargado", - "firewall_rules_cmd_failed": "Algunas órdenes de las reglas del cortafuegos han fallado. Más información en el registro.", + "firewall_rules_cmd_failed": "Algunos comandos para aplicar reglas del cortafuegos han fallado. Más información en el registro.", "format_datetime_short": "%d/%m/%Y %I:%M %p", "hook_argument_missing": "Falta un parámetro '{:s}'", "hook_choice_invalid": "Selección inválida '{:s}'", @@ -263,7 +263,7 @@ "certmanager_cert_signing_failed": "No se pudo firmar el nuevo certificado", "certmanager_no_cert_file": "No se pudo leer el certificado para el dominio {domain:s} (archivo: {file:s})", "certmanager_conflicting_nginx_file": "No se pudo preparar el dominio para el desafío ACME: el archivo de configuración de NGINX {filepath:s} está en conflicto y debe ser eliminado primero", - "domain_cannot_remove_main": "No se puede eliminar el dominio principal. Configure uno primero", + "domain_cannot_remove_main": "No se puede eliminar el dominio principal. Primero debes configurar otro utilizando la linea de comando 'yunohost domain main-domain -n ' donde es parte de esta lista: {other_domains:s}", "certmanager_self_ca_conf_file_not_found": "No se pudo encontrar el archivo de configuración para la autoridad de autofirma (archivo: {file:s})", "certmanager_unable_to_parse_self_CA_name": "No se pudo procesar el nombre de la autoridad de autofirma (archivo: {file:s})", "domains_available": "Dominios disponibles:", @@ -459,14 +459,14 @@ "migration_0003_still_on_jessie_after_main_upgrade": "Algo fue mal durante la actualización principal: ⸘el sistema está aún en Jessie‽ Para investigar el problema, vea {log}:s…", "migration_0003_system_not_fully_up_to_date": "Su sistema no está totalmente actualizado. Realice una actualización normal antes de ejecutar la migración a Stretch.", "migration_0003_not_jessie": "¡La distribución de Debian actual no es Jessie!", - "migration_0003_yunohost_upgrade": "Iniciando la actualización del paquete YunoHost… La migración finalizará pero la actualización real ocurrirá inmediatamente después. Después de que la operación esté completada, podría tener que iniciar sesión en la página de administración de nuevo.", - "migration_0003_restoring_origin_nginx_conf": "Su archivo /etc/nginx/nginx.conf ha sido editado de algún modo. La migración lo devolverá a su estado original primero… El archivo anterior estará disponible como {backup_dest}.", + "migration_0003_yunohost_upgrade": "Iniciando la actualización del paquete YunoHost… la actualización ocurrirá inmediatamente después de que la migración finalizará. Después de que la operación esté completada, podría tener que iniciar sesión en la página de administración de nuevo.", + "migration_0003_restoring_origin_nginx_conf": "Su archivo /etc/nginx/nginx.conf ha sido editado. La migración lo devolverá a su estado original… El archivo anterior estará disponible como {backup_dest}.", "migration_0003_fail2ban_upgrade": "Iniciando la actualización de Fail2Ban…", "migration_0003_main_upgrade": "Iniciando la actualización principal…", "migration_0003_patching_sources_list": "Corrigiendo «sources.lists»…", "migration_0003_start": "Iniciando migración a Stretch. El registro estará disponible en {logfile}.", "migration_description_0012_postgresql_password_to_md5_authentication": "Forzar a la autentificación de PostgreSQL a usar MD5 para las conexiones locales", - "migration_description_0011_setup_group_permission": "Configurar grupo de usuario y configurar permisos para aplicaciones y servicios", + "migration_description_0011_setup_group_permission": "Configurar grupo de usuario y permisos para aplicaciones y servicios", "migration_description_0010_migrate_to_apps_json": "Eliminar las listas de aplicaciones («appslists») obsoletas y usar en su lugar la nueva lista unificada «apps.json»", "migration_description_0009_decouple_regenconf_from_services": "Separar el mecanismo «regen-conf» de los servicios", "migration_description_0008_ssh_conf_managed_by_yunohost_step2": "Permitir que la configuración de SSH la gestione YunoHost (paso 2, manual)", @@ -491,7 +491,7 @@ "log_tools_shutdown": "Apagar el servidor", "log_tools_upgrade": "Actualizar paquetes del sistema", "log_tools_postinstall": "Posinstalación del servidor YunoHost", - "log_tools_migrations_migrate_forward": "Migrar hacia adelante", + "log_tools_migrations_migrate_forward": "Inicializa la migración", "log_tools_maindomain": "Convertir «{}» en el dominio principal", "log_user_permission_remove": "Actualizar permiso «{}»", "log_user_permission_add": "Actualizar permiso «{}»", @@ -503,7 +503,7 @@ "log_user_create": "Añadir usuario «{}»", "log_regen_conf": "Regenerar la configuración del sistema «{}»", "log_letsencrypt_cert_renew": "Renovar el certificado «{}» de Let's Encrypt", - "log_selfsigned_cert_install": "Instalar certificado autofirmado en el dominio «{}»", + "log_selfsigned_cert_install": "Instalar el certificado auto-firmado en el dominio '{}'", "log_permission_update": "Actualizar permiso «{}» para la aplicación «{}»", "log_permission_remove": "Eliminar permiso «{}»", "log_permission_add": "Añadir el permiso «{}» para la aplicación «{}»", @@ -528,7 +528,7 @@ "log_app_removeaccess": "Eliminar acceso a «{}»", "log_app_addaccess": "Añadir acceso a «{}»", "log_operation_unit_unclosed_properly": "La unidad de operación no se ha cerrado correctamente", - "log_does_exists": "No existe ningún registro de actividades con el nombre «{log}», ejecute «yunohost log list» para ver todos los registros de actividades disponibles", + "log_does_exists": "No existe ningún registro de actividades con el nombre '{log}', ejecute 'yunohost log list' para ver todos los registros de actividades disponibles", "log_help_to_get_failed_log": "No se pudo completar la operación «{desc}». Para obtener ayuda, comparta el registro completo de esta operación ejecutando la orden «yunohost log display {name} --share»", "log_link_to_failed_log": "No se pudo completar la operación «{desc}». Para obtener ayuda, proporcione el registro completo de esta operación pulsando aquí", "log_help_to_get_log": "Para ver el registro de la operación «{desc}», ejecute la orden «yunohost log display {name}»", @@ -575,7 +575,7 @@ "dyndns_could_not_check_available": "No se pudo comprobar si {domain:s} está disponible en {provider:s}.", "domain_dyndns_dynette_is_unreachable": "No se pudo conectar a dynette de YunoHost. O su YunoHost no está correctamente conectado a Internet o el servidor dynette está caído. Error: {error}", "domain_dns_conf_is_just_a_recommendation": "Esta orden muestra la configuración *recomendada*. No configura el DNS en realidad. Es su responsabilidad configurar la zona de DNS en su registrador según esta recomendación.", - "dpkg_lock_not_available": "Esta orden no se puede ejecutar en este momento porque otro programa parece que está usando el bloqueo de dpkg (el gestor de paquetes del sistema)", + "dpkg_lock_not_available": "Esta orden no se puede ejecutar en este momento ,parece que programa está usando el bloqueo de dpkg (el gestor de paquetes del sistema)", "dpkg_is_broken": "No puede hacer esto en este momento porque dpkg/apt (los gestores de paquetes del sistema) parecen estar en un estado roto... Puede tratar de solucionar este problema conectando a través de SSH y ejecutando `sudo dpkg --configure -a`.", "confirm_app_install_thirdparty": "¡PELIGRO! Esta aplicación no forma parte del catálogo de aplicaciones de YunoHost. Instalar aplicaciones de terceros podría comprometer la integridad y seguridad de su sistema. Probablemente NO debería instalarla salvo que sepa lo que está haciendo. No tendrá NINGUNA AYUDA si esta aplicación no funciona o rompe su sistema… Si está dispuesto a aceptar ese riesgo de todas formas, escriba «{answers:s}»", "confirm_app_install_danger": "¡PELIGRO! ¡Esta aplicación es conocida por ser aún experimental (o no funciona explícitamente)! Probablemente NO debería instalarla salvo que sepa lo que está haciendo. No tendrá NINGUNA AYUDA si esta aplicación no funciona o rompe su sistema… Si está dispuesto a aceptar ese riesgo de todas formas, escriba «{answers:s}»", @@ -608,7 +608,7 @@ "app_action_broke_system": "Esta acción parece que ha roto estos servicios importantes: {services}", "operation_interrupted": "¿Ha sido interrumpida la operación manualmente?", "apps_already_up_to_date": "Todas las aplicaciones están ya actualizadas", - "dyndns_provider_unreachable": "No se puede conectar con el proveedor de Dyndns {provider}: o su YunoHost no está correctamente conectado a Internet o el servidor de dynette está caído.", + "dyndns_provider_unreachable": "No se puede conectar con el proveedor de Dyndns {provider}: o su YunoHost no está correctamente conectado a Internet o el servidor dynette está caído.", "group_already_exist": "El grupo {group} ya existe", "group_already_exist_on_system": "El grupo {group} ya existe en los grupos del sistema", "group_cannot_be_edited": "El grupo {group} no se puede editar manualmente.", @@ -661,20 +661,20 @@ "diagnosis_everything_ok": "¡Todo se ve bien para {category}!", "app_upgrade_script_failed": "Ha ocurrido un error en el script de actualización de la app", "diagnosis_no_cache": "Todavía no hay una caché de diagnóstico para la categoría '{category}'", - "diagnosis_ip_no_ipv4": "IPv4 en el servidor no está funcionando.", + "diagnosis_ip_no_ipv4": "El servidor no cuenta con ipv4 funcional.", "diagnosis_ip_not_connected_at_all": "¿¡Está conectado el servidor a internet!?", "diagnosis_ip_broken_resolvconf": "DNS parece no funcionar en tu servidor, lo que parece estar relacionado con /etc/resolv.conf no apuntando a 127.0.0.1.", "diagnosis_dns_missing_record": "Según la configuración DNS recomendada, deberías añadir un registro DNS de tipo {0}, nombre {1} y valor {2}. Puedes consultar https://yunohost.org/dns_config para más información.", "diagnosis_diskusage_low": "El almacenamiento {mountpoint} (en dispositivo {device}) solo tiene {free_abs_GB} GB ({free_percent}%) de espacio disponible. Ten cuidado.", "diagnosis_services_bad_status_tip": "Puedes intentar reiniciar el servicio, y si no funciona, echar un vistazo a los logs del servicio usando 'yunohost service log {0}' o a través de la sección 'Servicios' en webadmin.", "diagnosis_ip_connected_ipv6": "¡El servidor está conectado a internet a través de IPv6!", - "diagnosis_ip_no_ipv6": "IPv6 en el servidor no está funcionando.", + "diagnosis_ip_no_ipv6": "El servidor no cuenta con IPv6 funcional.", "diagnosis_ip_dnsresolution_working": "¡DNS no está funcionando!", - "diagnosis_ip_broken_dnsresolution": "Parece que no funciona resolución de nombre de dominio por alguna razón... ¿Hay algún firewall bloqueando peticiones DNS?", + "diagnosis_ip_broken_dnsresolution": "Parece que no funciona la resolución de nombre de dominio por alguna razón... ¿Hay algún firewall bloqueando peticiones DNS?", "diagnosis_ip_weird_resolvconf": "Parece que DNS funciona, pero ten cuidado, porque estás utilizando /etc/resolv.conf modificado.", "diagnosis_ip_weird_resolvconf_details": "En su lugar, este fichero debería ser un enlace simbólico a /etc/resolvconf/run/resolv.conf apuntando a 127.0.0.1 (dnsmasq). Los servidores de nombre de domino deben configurarse a través de /etc/resolv.dnsmasq.conf.", "diagnosis_dns_good_conf": "Buena configuración DNS para el dominio {domain} (categoría {category})", - "diagnosis_dns_bad_conf": "Configuración de los DNS mala o faltante para el dominio {domain} (categoría {category})", + "diagnosis_dns_bad_conf": "Configuración mala o faltante de los DNS para el dominio {domain} (categoría {category})", "diagnosis_dns_discrepancy": "El registro DNS con tipo {0} y nombre {1} no se corresponde a la configuración recomendada. Valor actual: {2}. Valor esperado: {3}. Puedes consultar https://yunohost.org/dns_config para más información.", "diagnosis_services_bad_status": "El servicio {service} está {status} :(", "diagnosis_diskusage_verylow": "El almacenamiento {mountpoint} (en el dispositivo {device}) sólo tiene {free_abs_GB} GB ({free_percent}%) de espacio disponible. Deberías considerar la posibilidad de limpiar algo de espacio.", @@ -708,12 +708,31 @@ "diagnosis_swap_ok": "El sistema tiene {total_MB} MB de espacio de intercambio!", "diagnosis_ports_needed_by": "La apertura de este puerto es requerida para la funcionalidad {1} (service {0})", "diagnosis_ports_ok": "El puerto {port} es accesible desde internet.", - "diagnosis_ports_unreachable": "El puerto {port} no es accesible desde internet", + "diagnosis_ports_unreachable": "El puerto {port} no es accesible desde internet.", "diagnosis_ports_could_not_diagnose": "No se puede comprobar si los puertos están accesibles desde el exterior. Error: {error}", "diagnosis_description_security": "Validación de seguridad", - "diagnosis_description_regenconf": "Configurationes de systema", - "diagnosis_description_mail": "correo electronico", - "diagnosis_description_web": "web", + "diagnosis_description_regenconf": "Configuraciones de sistema", + "diagnosis_description_mail": "Correo electrónico", + "diagnosis_description_web": "Web", "diagnosis_basesystem_hardware_board": "El modelo de placa del servidor es {model}", - "diagnosis_basesystem_hardware": "La arquitectura material del servidor es {virt} {arch}" + "diagnosis_basesystem_hardware": "La arquitectura material del servidor es {virt} {arch}", + "migration_description_0014_remove_app_status_json": "Supresión del archivo de aplicaciones heredado status.json", + "migration_description_0013_futureproof_apps_catalog_system": "Migración hacia el nuevo sistema de catalogo de aplicación a prueba del futuro", + "log_domain_main_domain": "Hacer de '{}' el dominio principal", + "log_app_config_apply": "Aplica la configuración de la aplicación '{}'", + "log_app_config_show_panel": "Muestra el panel de configuración de la aplicación '{}'", + "log_app_action_run": "Inicializa la acción de la aplicación '{}'", + "group_already_exist_on_system_but_removing_it": "El grupo {group} ya existe en el grupo de sistema, pero YunoHost lo suprimirá …", + "global_settings_setting_pop3_enabled": "Habilita el protocolo POP3 para el servidor de correo electrónico", + "domain_cannot_remove_main_add_new_one": "No se puede remover '{domain:s}' porque es su principal y único dominio. Primero debe agregar un nuevo dominio con la linea de comando 'yunohost domain add ', entonces configurarlo como dominio principal con 'yunohost domain main-domain -n ' y finalmente borrar el dominio '{domain:s}' con 'yunohost domain remove {domain:s}'.", + "diagnosis_never_ran_yet": "Este servidor todavía no tiene reportes de diagnostico. Puede iniciar un diagnostico completo desde la interface administrador web o con la linea de comando 'yunohost diagnosis run'.", + "diagnosis_unknown_categories": "Las siguientes categorías están desconocidas: {categories}", + "diagnosis_http_unreachable": "El dominio {domain} esta fuera de alcance desde internet y a través de HTTP.", + "diagnosis_http_bad_status_code": "El sistema de diagnostico no pudo comunicarse con su servidor. Puede ser otra maquina que contesto en lugar del servidor. Debería verificar en su firewall que el re-direccionamiento del puerto 80 esta correcto.", + "diagnosis_http_unknown_error": "Hubo un error durante la búsqueda de su dominio, parece inalcanzable.", + "diagnosis_http_connection_error": "Error de conexión: Ne se pudo conectar al dominio solicitado,", + "diagnosis_http_timeout": "El intento de contactar a su servidor desde internet corrió fuera de tiempo. Al parece esta incomunicado. Debería verificar que nginx corre en el puerto 80, y que la redireción del puerto 80 no interfiere con en el firewall.", + "diagnosis_http_ok": "El Dominio {domain} es accesible desde internet a través de HTTP.", + "diagnosis_http_could_not_diagnose": "No se pudo verificar si el dominio es accesible desde internet. Error: {error}", + "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" } From de704f4e861fc0b5236e719c42feb70b58217e88 Mon Sep 17 00:00:00 2001 From: romain raynaud Date: Mon, 30 Mar 2020 18:55:03 +0000 Subject: [PATCH 0773/3170] Translated using Weblate (Spanish) Currently translated at 93.2% (572 of 614 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/es/ --- locales/es.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/es.json b/locales/es.json index dca7f1582..51daddf32 100644 --- a/locales/es.json +++ b/locales/es.json @@ -454,7 +454,7 @@ "migration_0005_postgresql_96_not_installed": "⸘PostgreSQL 9.4 está instalado pero no PostgreSQL 9.6‽ Algo raro podría haber ocurrido en su sistema:(…", "migration_0005_postgresql_94_not_installed": "PostgreSQL no estaba instalado en su sistema. Nada que hacer.", "migration_0003_modified_files": "Tenga en cuenta que se encontró que los siguientes archivos fueron modificados manualmente y podrían ser sobrescritos después de la actualización: {manually_modified_files}", - "migration_0003_problematic_apps_warning": "Tenga en cuenta que se detectaron las siguientes aplicaciones instaladas posiblemente problemáticas. Parece que no fueron instaladas desde una lista de aplicaciones o no estaban etiquetadas como «funciona». Así que no hay garantía de que aún funcionen después de la actualización: {problematic_apps}", + "migration_0003_problematic_apps_warning": "Tenga en cuenta que las aplicaciones listadas mas abajo fueron detectadas como 'posiblemente problemáticas'. Parece que no fueron instaladas desde una lista de aplicaciones o no estaban etiquetadas como 'funcional'. Así que no hay garantía de que aún funcionen después de la actualización: {problematic_apps}", "migration_0003_general_warning": "Tenga en cuenta que esta migración es una operación delicada. El equipo de YunoHost ha hecho todo lo posible para revisarla y probarla, pero la migración aún podría romper parte del sistema o de sus aplicaciones.\n\nPor lo tanto, se recomienda que:\n - Realice una copia de seguridad de cualquier dato crítico o aplicación. Más información en https://yunohost.org/backup;\n - Tenga paciencia tras iniciar la migración: dependiendo de su conexión a Internet y de su hardware, podría tardar unas cuantas horas hasta que todo se actualice.\n\nAdemás, el puerto para SMTP usado por los clientes de correo externos (como Thunderbird o K9-Mail) cambió de 465 (SSL/TLS) a 587 (STARTTLS). El antiguo puerto (465) se cerrará automáticamente y el nuevo puerto (587) se abrirá en el cortafuegos. Todos los usuarios *tendrán* que adaptar la configuración de sus clientes de correo por lo tanto.", "migration_0003_still_on_jessie_after_main_upgrade": "Algo fue mal durante la actualización principal: ⸘el sistema está aún en Jessie‽ Para investigar el problema, vea {log}:s…", "migration_0003_system_not_fully_up_to_date": "Su sistema no está totalmente actualizado. Realice una actualización normal antes de ejecutar la migración a Stretch.", From 73d2cf253bbc11d7878db55823240824646f6079 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 28 Mar 2020 00:58:01 +0100 Subject: [PATCH 0774/3170] Add draft of linter script to check locale format consistency --- tests/check_locale_format_consistency.py | 28 ++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 tests/check_locale_format_consistency.py diff --git a/tests/check_locale_format_consistency.py b/tests/check_locale_format_consistency.py new file mode 100644 index 000000000..5ea1d365e --- /dev/null +++ b/tests/check_locale_format_consistency.py @@ -0,0 +1,28 @@ +import re +import json +import glob + +locale_folder = "../locales/" +locale_files = glob.glob(locale_folder + "*.json") +locale_files = [filename.split("/")[-1] for filename in locale_files] +locale_files.remove("en.json") + +reference = json.loads(open(locale_folder + "en.json").read()) + +for locale_file in locale_files: + + this_locale = json.loads(open(locale_folder + locale_file).read()) + + for key, string in reference.items(): + if key in this_locale: + + subkeys_in_ref = set(k[0] for k in re.findall(r"{(\w+)(:\w)?}", string)) + subkeys_in_this_locale = set(k[0] for k in re.findall(r"{(\w+)(:\w)?}", this_locale[key])) + + if any(key not in subkeys_in_ref for key in subkeys_in_this_locale): + print("\n") + print("==========================") + print("Format inconsistency for string %s in %s:" % (key, locale_file)) + print("%s -> %s " % ("en.json", string)) + print("%s -> %s " % (locale_file, this_locale[key])) + From 059f52667ea9569f85d1bf0790359182146d3572 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 28 Mar 2020 00:59:59 +0100 Subject: [PATCH 0775/3170] Fix remaining inconsistencies --- locales/eo.json | 2 +- locales/fr.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/eo.json b/locales/eo.json index 5047fff09..8dc5c1d98 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -534,7 +534,7 @@ "migration_0008_port": "• Vi devos konekti uzante la havenon 22 anstataŭ via nuna kutimo SSH-haveno. Sentu vin libera reconfiguri ĝin;", "domain_creation_failed": "Ne eblas krei domajnon {domain}: {error}", "certmanager_domain_http_not_working": "Ŝajnas ke la domajno {domain:s} ne atingeblas per HTTP. Kontrolu, ke via DNS kaj NGINX-agordo ĝustas", - "domain_cannot_remove_main": "Vi ne povas forigi '{domain:s}' ĉar ĝi estas la ĉefa domajno, vi bezonas unue agordi alian domajnon kiel la ĉefan domajnon per uzado de 'yunohost domain main-domain -n ', jen la listo de kandidataj domajnoj. : {other_domainsj:s}", + "domain_cannot_remove_main": "Vi ne povas forigi '{domain:s}' ĉar ĝi estas la ĉefa domajno, vi bezonas unue agordi alian domajnon kiel la ĉefan domajnon per uzado de 'yunohost domain main-domain -n ', jen la listo de kandidataj domajnoj. : {other_domains:s}", "service_reloaded_or_restarted": "La servo '{service:s}' estis reŝarĝita aŭ rekomencita", "mysql_db_initialized": "La datumbazo MySQL jam estas pravalorizita", "log_domain_add": "Aldonu '{}' domajnon en sisteman agordon", diff --git a/locales/fr.json b/locales/fr.json index adeeada3b..e44d592e0 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -718,7 +718,7 @@ "apps_catalog_init_success": "Système de catalogue d'applications initialisé !", "apps_catalog_failed_to_download": "Impossible de télécharger le catalogue des applications {apps_catalog}:{error}", "diagnosis_mail_ougoing_port_25_blocked": "Le port sortant 25 semble être bloqué. Vous devriez essayer de le débloquer dans le panneau de configuration de votre fournisseur de services Internet (ou hébergeur). En attendant, le serveur ne pourra pas envoyer de courrier électronique à d'autres serveurs.", - "domain_cannot_remove_main_add_new_one": "Vous ne pouvez pas supprimer '{domain:s}' car il s'agit du domaine principal et de votre seul domaine. Vous devez d'abord ajouter un autre domaine à l'aide de 'yunohost domain add ', puis définir comme domaine principal à l'aide de ' yunohost domain main-domain -n ' et vous pouvez ensuite supprimer le domaine '{domaine:s}' à l'aide de 'yunohost domain remove {domain:s}'.'", + "domain_cannot_remove_main_add_new_one": "Vous ne pouvez pas supprimer '{domain:s}' car il s'agit du domaine principal et de votre seul domaine. Vous devez d'abord ajouter un autre domaine à l'aide de 'yunohost domain add ', puis définir comme domaine principal à l'aide de ' yunohost domain main-domain -n ' et vous pouvez ensuite supprimer le domaine '{domain:s}' à l'aide de 'yunohost domain remove {domain:s}'.'", "diagnosis_security_vulnerable_to_meltdown_details": "Pour résoudre ce problème, vous devez mettre à niveau votre système et redémarrer pour charger le nouveau noyau Linux (ou contacter votre fournisseur de serveur si cela ne fonctionne pas). Voir https://meltdownattack.com/ pour plus d'informations.", "diagnosis_description_basesystem": "Système de base", "diagnosis_description_ip": "Connectivité Internet", From a353ad76774c44004256fef8b076f74b6b639ca4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 28 Mar 2020 01:27:51 +0100 Subject: [PATCH 0776/3170] Add script to remove stale i18n strings --- tests/remove_stale_string.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 tests/remove_stale_string.py diff --git a/tests/remove_stale_string.py b/tests/remove_stale_string.py new file mode 100644 index 000000000..0213bc2be --- /dev/null +++ b/tests/remove_stale_string.py @@ -0,0 +1,19 @@ +import re +import json +import glob +from collections import OrderedDict + +locale_folder = "../locales/" +locale_files = glob.glob(locale_folder + "*.json") +locale_files = [filename.split("/")[-1] for filename in locale_files] +locale_files.remove("en.json") + +reference = json.loads(open(locale_folder + "en.json").read()) + +for locale_file in locale_files: + + print(locale_file) + this_locale = json.loads(open(locale_folder + locale_file).read(), object_pairs_hook=OrderedDict) + this_locale_fixed = {k:v for k, v in this_locale.items() if k in reference} + + json.dump(this_locale_fixed, open(locale_folder + locale_file, "w"), indent=4, ensure_ascii=False) From c0fc60aa5fd51ac9a5795017fdc57d5b89b300e7 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 29 Mar 2020 07:26:12 +0200 Subject: [PATCH 0777/3170] Add comments + return 1 if inconsistencies found --- tests/check_locale_format_consistency.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/check_locale_format_consistency.py b/tests/check_locale_format_consistency.py index 5ea1d365e..99107ccc2 100644 --- a/tests/check_locale_format_consistency.py +++ b/tests/check_locale_format_consistency.py @@ -2,6 +2,7 @@ import re import json import glob +# List all locale files (except en.json being the ref) locale_folder = "../locales/" locale_files = glob.glob(locale_folder + "*.json") locale_files = [filename.split("/")[-1] for filename in locale_files] @@ -9,20 +10,31 @@ locale_files.remove("en.json") reference = json.loads(open(locale_folder + "en.json").read()) +found_inconsistencies = False + +# Let's iterate over each locale file for locale_file in locale_files: this_locale = json.loads(open(locale_folder + locale_file).read()) + # We iterate over all keys/string in en.json for key, string in reference.items(): + # If there is a translation available for this key/string if key in this_locale: + # Then we check that every "{stuff}" (for python's .format()) + # should also be in the translated string, otherwise the .format + # will trigger an exception! subkeys_in_ref = set(k[0] for k in re.findall(r"{(\w+)(:\w)?}", string)) subkeys_in_this_locale = set(k[0] for k in re.findall(r"{(\w+)(:\w)?}", this_locale[key])) if any(key not in subkeys_in_ref for key in subkeys_in_this_locale): + found_inconsistencies = True print("\n") print("==========================") print("Format inconsistency for string %s in %s:" % (key, locale_file)) print("%s -> %s " % ("en.json", string)) print("%s -> %s " % (locale_file, this_locale[key])) +if found_inconsistencies: + sys.exit(1) From 9732bbab582c97799b56ddc5dd7b57c108a71b30 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 29 Mar 2020 07:26:31 +0200 Subject: [PATCH 0778/3170] Improve / rework script meant to check that all i18n keys used in code are effectively defined --- tests/_test_m18nkeys.py | 101 ----------------------------- tests/check_m18nkeys.py | 138 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 138 insertions(+), 101 deletions(-) delete mode 100644 tests/_test_m18nkeys.py create mode 100644 tests/check_m18nkeys.py diff --git a/tests/_test_m18nkeys.py b/tests/_test_m18nkeys.py deleted file mode 100644 index cc6202517..000000000 --- a/tests/_test_m18nkeys.py +++ /dev/null @@ -1,101 +0,0 @@ -# -*- coding: utf-8 -*- - -import re -import glob -import json -import yaml - -ignore = [ "service_description_", - "migration_description_", - "global_settings_setting_", - "password_too_simple_", - "password_listed", - "backup_method_", - "backup_applying_method_", - "confirm_app_install_", - "log_", - ] - -############################################################################### -# Find used keys in python code # -############################################################################### - -# This regex matches « foo » in patterns like « m18n.n( "foo" » -p1 = re.compile(r'm18n\.n\(\s*[\"\']([a-zA-Z0-9_]+)[\"\']') -p2 = re.compile(r'YunohostError\([\'\"]([a-zA-Z0-9_]+)[\'\"]') - -python_files = glob.glob("../src/yunohost/*.py") -python_files.extend(glob.glob("../src/yunohost/utils/*.py")) -python_files.extend(glob.glob("../src/yunohost/data_migrations/*.py")) -python_files.append("../bin/yunohost") - -python_keys = set() -for python_file in python_files: - content = open(python_file).read() - for match in p1.findall(content): - python_keys.add(match) - for match in p2.findall(content): - python_keys.add(match) - -############################################################################### -# Find keys used in actionmap # -############################################################################### - -actionmap_keys = set() -actionmap = yaml.load(open("../data/actionsmap/yunohost.yml")) -for _, category in actionmap.items(): - if "actions" not in category.keys(): - continue - for _, action in category["actions"].items(): - if "arguments" not in action.keys(): - continue - for _, argument in action["arguments"].items(): - if "extra" not in argument.keys(): - continue - if "password" in argument["extra"]: - actionmap_keys.add(argument["extra"]["password"]) - if "ask" in argument["extra"]: - actionmap_keys.add(argument["extra"]["ask"]) - if "comment" in argument["extra"]: - actionmap_keys.add(argument["extra"]["comment"]) - if "pattern" in argument["extra"]: - actionmap_keys.add(argument["extra"]["pattern"][1]) - if "help" in argument["extra"]: - print argument["extra"]["help"] - -actionmap_keys.add("admin_password") - -############################################################################### -# Load en locale json keys # -############################################################################### - -en_locale_file = "../locales/en.json" -with open(en_locale_file) as f: - en_locale_json = json.loads(f.read()) - -en_locale_keys = set(en_locale_json.keys()) - -############################################################################### -# Compare keys used and keys defined # -############################################################################### - -used_keys = python_keys.union(actionmap_keys) - -keys_used_but_not_defined = used_keys.difference(en_locale_keys) -keys_defined_but_not_used = en_locale_keys.difference(used_keys) - -if len(keys_used_but_not_defined) != 0: - print "> Error ! Those keys are used in some files but not defined :" - for key in sorted(keys_used_but_not_defined): - if any(key.startswith(i) for i in ignore): - continue - print " - %s" % key - -if len(keys_defined_but_not_used) != 0: - print "> Warning ! Those keys are defined but seems unused :" - for key in sorted(keys_defined_but_not_used): - if any(key.startswith(i) for i in ignore): - continue - print " - %s" % key - - diff --git a/tests/check_m18nkeys.py b/tests/check_m18nkeys.py new file mode 100644 index 000000000..7d712aa3c --- /dev/null +++ b/tests/check_m18nkeys.py @@ -0,0 +1,138 @@ +# -*- coding: utf-8 -*- + +import os +import re +import sys +import glob +import json +import yaml +import subprocess + +ignore = [ "password_too_simple_", + "password_listed", + "backup_method_", + "backup_applying_method_", + "confirm_app_install_", + ] + +############################################################################### +# Find used keys in python code # +############################################################################### + +def find_expected_string_keys(): + + # Try to find : + # m18n.n( "foo" + # YunohostError("foo" + p1 = re.compile(r'm18n\.n\(\s*[\"\'](\w+)[\"\']') + p2 = re.compile(r'YunohostError\([\'\"](\w+)[\'\"]') + + python_files = glob.glob("../src/yunohost/*.py") + python_files.extend(glob.glob("../src/yunohost/utils/*.py")) + python_files.extend(glob.glob("../src/yunohost/data_migrations/*.py")) + python_files.extend(glob.glob("../data/hooks/diagnosis/*.py")) + python_files.append("../bin/yunohost") + + for python_file in python_files: + content = open(python_file).read() + yield from p1.findall(content) + yield from p2.findall(content) + + # 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 + p3 = re.compile(r'[\"\'](diagnosis_[a-z]+_\w+)[\"\']') + for python_file in glob.glob("../data/hooks/diagnosis/*.py"): + content = open(python_file).read() + yield from p3.findall(content) + yield "diagnosis_description_" + os.path.basename(python_file)[:-3].split("-")[-1] + + # For each migration, expect to find "migration_description_" + for path in glob.glob("../src/yunohost/data_migrations/*.py"): + if "__init__" in path: + continue + yield "migration_description_" + os.path.basename(path)[:-3] + + # For each default service, expect to find "service_description_" + for service, info in yaml.safe_load(open("../data/templates/yunohost/services.yml")).items(): + if info is None: + continue + yield "service_description_" + service + + # For all unit operations, expect to find "log_" + # A unit operation is created either using the @is_unit_operation decorator + # or using OperationLogger( + cmd = "grep -hr '@is_unit_operation' ../src/yunohost/ -A3 2>/dev/null | grep '^def' | sed -E 's@^def (\w+)\(.*@\\1@g'" + for funcname in subprocess.check_output(cmd, shell=True).decode("utf-8").split("\n"): + yield "log_"+funcname + + p4 = re.compile(r"OperationLogger\([\"\'](\w+)[\"\']") + for python_file in python_files: + content = open(python_file).read() + yield from ("log_"+match for match in p4.findall(content)) + + # Global settings descriptions + # Will be on a line like : ("service.ssh.allow_deprecated_dsa_hostkey", {"type": "bool", ... + p5 = re.compile(r" \([\"\'](\w[\w\.]+)[\"\'],") + content = open("../src/yunohost/settings.py").read() + yield from ("global_settings_setting_"+s.replace(".", "_") for s in p5.findall(content)) + + # Keys for the actionmap ... + for category in yaml.load(open("../data/actionsmap/yunohost.yml")).values(): + if "actions" not in category.keys(): + continue + for action in category["actions"].values(): + if "arguments" not in action.keys(): + continue + for argument in action["arguments"].values(): + extra = argument.get("extra") + if not extra: + continue + if "password" in extra: + yield extra["password"] + if "ask" in extra: + yield extra["ask"] + if "comment" in extra: + yield extra["comment"] + if "pattern" in extra: + yield extra["pattern"][1] + if "help" in extra: + yield extra["help"] + +expected_string_keys = set(find_expected_string_keys()) + +expected_string_keys.add("admin_password") + +############################################################################### +# Load en locale json keys # +############################################################################### + +en_locale_file = "../locales/en.json" +with open(en_locale_file) as f: + en_locale_json = json.loads(f.read()) + +en_locale_keys = set(en_locale_json.keys()) + +############################################################################### +# Compare keys used and keys defined # +############################################################################### + +keys_used_but_not_defined = expected_string_keys.difference(en_locale_keys) +keys_defined_but_not_used = en_locale_keys.difference(expected_string_keys) + +if len(keys_used_but_not_defined) != 0: + print("> Error ! Those keys are used in some files but not defined :") + for key in sorted(keys_used_but_not_defined): + if any(key.startswith(i) for i in ignore): + continue + print(" - %s" % key) + +if len(keys_defined_but_not_used) != 0: + print("> Warning ! Those keys are defined but seems unused :") + for key in sorted(keys_defined_but_not_used): + if any(key.startswith(i) for i in ignore): + continue + print(" - %s" % key) + + +if len(keys_used_but_not_defined) != 0 or len(keys_defined_but_not_used) != 0: + sys.exit(1) From 9b763f73c54e0937e9ce9b79d326c4d3789da060 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 30 Mar 2020 06:24:41 +0200 Subject: [PATCH 0779/3170] Turn the i18n key checker into tests for tox --- .../{check_m18nkeys.py => test_i18n_keys.py} | 117 +++++++++++------- 1 file changed, 69 insertions(+), 48 deletions(-) rename tests/{check_m18nkeys.py => test_i18n_keys.py} (54%) diff --git a/tests/check_m18nkeys.py b/tests/test_i18n_keys.py similarity index 54% rename from tests/check_m18nkeys.py rename to tests/test_i18n_keys.py index 7d712aa3c..0d5af33f6 100644 --- a/tests/check_m18nkeys.py +++ b/tests/test_i18n_keys.py @@ -2,23 +2,22 @@ import os import re -import sys import glob import json import yaml import subprocess -ignore = [ "password_too_simple_", - "password_listed", - "backup_method_", - "backup_applying_method_", - "confirm_app_install_", - ] +ignore = ["password_too_simple_", + "password_listed", + "backup_method_", + "backup_applying_method_", + "confirm_app_install_"] ############################################################################### # Find used keys in python code # ############################################################################### + def find_expected_string_keys(): # Try to find : @@ -27,33 +26,40 @@ def find_expected_string_keys(): p1 = re.compile(r'm18n\.n\(\s*[\"\'](\w+)[\"\']') p2 = re.compile(r'YunohostError\([\'\"](\w+)[\'\"]') - python_files = glob.glob("../src/yunohost/*.py") - python_files.extend(glob.glob("../src/yunohost/utils/*.py")) - python_files.extend(glob.glob("../src/yunohost/data_migrations/*.py")) - python_files.extend(glob.glob("../data/hooks/diagnosis/*.py")) - python_files.append("../bin/yunohost") + python_files = glob.glob("src/yunohost/*.py") + python_files.extend(glob.glob("src/yunohost/utils/*.py")) + python_files.extend(glob.glob("src/yunohost/data_migrations/*.py")) + python_files.extend(glob.glob("data/hooks/diagnosis/*.py")) + python_files.append("bin/yunohost") for python_file in python_files: content = open(python_file).read() - yield from p1.findall(content) - yield from p2.findall(content) + for m in p1.findall(content): + if m.endswith("_"): + continue + yield m + for m in p2.findall(content): + if m.endswith("_"): + continue + 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 p3 = re.compile(r'[\"\'](diagnosis_[a-z]+_\w+)[\"\']') - for python_file in glob.glob("../data/hooks/diagnosis/*.py"): + for python_file in glob.glob("data/hooks/diagnosis/*.py"): content = open(python_file).read() - yield from p3.findall(content) + for m in p3.findall(content): + yield m yield "diagnosis_description_" + os.path.basename(python_file)[:-3].split("-")[-1] # For each migration, expect to find "migration_description_" - for path in glob.glob("../src/yunohost/data_migrations/*.py"): + for path in glob.glob("src/yunohost/data_migrations/*.py"): if "__init__" in path: continue yield "migration_description_" + os.path.basename(path)[:-3] # For each default service, expect to find "service_description_" - for service, info in yaml.safe_load(open("../data/templates/yunohost/services.yml")).items(): + for service, info in yaml.safe_load(open("data/templates/yunohost/services.yml")).items(): if info is None: continue yield "service_description_" + service @@ -61,23 +67,25 @@ def find_expected_string_keys(): # For all unit operations, expect to find "log_" # A unit operation is created either using the @is_unit_operation decorator # or using OperationLogger( - cmd = "grep -hr '@is_unit_operation' ../src/yunohost/ -A3 2>/dev/null | grep '^def' | sed -E 's@^def (\w+)\(.*@\\1@g'" - for funcname in subprocess.check_output(cmd, shell=True).decode("utf-8").split("\n"): - yield "log_"+funcname + cmd = "grep -hr '@is_unit_operation' src/yunohost/ -A3 2>/dev/null | grep '^def' | sed -E 's@^def (\\w+)\\(.*@\\1@g'" + for funcname in subprocess.check_output(cmd, shell=True).decode("utf-8").strip().split("\n"): + yield "log_" + funcname p4 = re.compile(r"OperationLogger\([\"\'](\w+)[\"\']") for python_file in python_files: content = open(python_file).read() - yield from ("log_"+match for match in p4.findall(content)) + for m in ("log_" + match for match in p4.findall(content)): + yield m # Global settings descriptions # Will be on a line like : ("service.ssh.allow_deprecated_dsa_hostkey", {"type": "bool", ... p5 = re.compile(r" \([\"\'](\w[\w\.]+)[\"\'],") - content = open("../src/yunohost/settings.py").read() - yield from ("global_settings_setting_"+s.replace(".", "_") for s in p5.findall(content)) + content = open("src/yunohost/settings.py").read() + for m in ("global_settings_setting_" + s.replace(".", "_") for s in p5.findall(content)): + yield m # Keys for the actionmap ... - for category in yaml.load(open("../data/actionsmap/yunohost.yml")).values(): + for category in yaml.load(open("data/actionsmap/yunohost.yml")).values(): if "actions" not in category.keys(): continue for action in category["actions"].values(): @@ -98,41 +106,54 @@ def find_expected_string_keys(): if "help" in extra: yield extra["help"] -expected_string_keys = set(find_expected_string_keys()) + # Hardcoded expected keys ... + yield "admin_password" # Not sure that's actually used nowadays... -expected_string_keys.add("admin_password") + for method in ["tar", "copy", "borg", "custom"]: + yield "backup_applying_method_%s" % method + yield "backup_method_%s_finished" % method + + for level in ["danger", "thirdparty", "warning"]: + yield "confirm_app_install_%s" % level + + for errortype in ["bad_status_code", "connection_error", "timeout"]: + yield "diagnosis_http_%s" % errortype + + yield "password_listed" + for i in [1, 2, 3, 4]: + yield "password_too_simple_%s" % i ############################################################################### # Load en locale json keys # ############################################################################### -en_locale_file = "../locales/en.json" -with open(en_locale_file) as f: - en_locale_json = json.loads(f.read()) -en_locale_keys = set(en_locale_json.keys()) +def keys_defined_for_en(): + return json.loads(open("locales/en.json").read()).keys() ############################################################################### # Compare keys used and keys defined # ############################################################################### -keys_used_but_not_defined = expected_string_keys.difference(en_locale_keys) -keys_defined_but_not_used = en_locale_keys.difference(expected_string_keys) -if len(keys_used_but_not_defined) != 0: - print("> Error ! Those keys are used in some files but not defined :") - for key in sorted(keys_used_but_not_defined): - if any(key.startswith(i) for i in ignore): - continue - print(" - %s" % key) - -if len(keys_defined_but_not_used) != 0: - print("> Warning ! Those keys are defined but seems unused :") - for key in sorted(keys_defined_but_not_used): - if any(key.startswith(i) for i in ignore): - continue - print(" - %s" % key) +expected_string_keys = set(find_expected_string_keys()) +keys_defined = set(keys_defined_for_en()) -if len(keys_used_but_not_defined) != 0 or len(keys_defined_but_not_used) != 0: - sys.exit(1) +def test_undefined_i18n_keys(): + undefined_keys = expected_string_keys.difference(keys_defined) + undefined_keys = sorted(undefined_keys) + + if undefined_keys: + raise Exception("Those i18n keys should be defined in en.json:\n" + " - " + "\n - ".join(undefined_keys)) + + +def test_unused_i18n_keys(): + + unused_keys = keys_defined.difference(expected_string_keys) + unused_keys = sorted(unused_keys) + + if unused_keys: + raise Exception("Those i18n keys appears unused:\n" + " - " + "\n - ".join(unused_keys)) From 1795e9e84b96363e716b6f47aff191b73c38c72d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 30 Mar 2020 06:47:28 +0200 Subject: [PATCH 0780/3170] Fixing undefined string, removing old unused strings, and fixing stuff for other false-negatives --- locales/en.json | 17 +-------- src/yunohost/backup.py | 36 +++++++++---------- .../0003_migrate_to_stretch.py | 2 +- src/yunohost/regenconf.py | 16 ++++----- 4 files changed, 27 insertions(+), 44 deletions(-) diff --git a/locales/en.json b/locales/en.json index c2d29af37..567b6a460 100644 --- a/locales/en.json +++ b/locales/en.json @@ -31,10 +31,8 @@ "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}", "app_not_properly_removed": "{app:s} has not been properly removed", - "app_package_need_update": "The app {app} package needs to be updated to follow YunoHost changes", "app_removed": "{app:s} removed", "app_requirements_checking": "Checking required packages for {app}…", - "app_requirements_failed": "Some requirements are not met for {app}: {error}", "app_requirements_unmeet": "Requirements are not met for {app}, the package {pkgname} ({version}) must be {spec}", "app_remove_after_failed_install": "Removing the app following the installation failure…", "app_sources_fetch_failed": "Could not fetch sources files, is the URL correct?", @@ -51,14 +49,11 @@ "app_upgrade_some_app_failed": "Some apps could not be upgraded", "app_upgraded": "{app:s} upgraded", "apps_already_up_to_date": "All apps are already up-to-date", - "apps_permission_not_found": "No permission found for the installed apps", - "apps_permission_restoration_failed": "Permission '{permission:s}' for app {app:s} restoration has failed", "apps_catalog_init_success": "App catalog system initialized!", - "apps_catalog_updating": "Updating application catalog...", + "apps_catalog_updating": "Updating application catalog…", "apps_catalog_failed_to_download": "Unable to download the {apps_catalog} app catalog: {error}", "apps_catalog_obsolete_cache": "The app catalog cache is empty or obsolete.", "apps_catalog_update_success": "The application catalog has been updated!", - "ask_current_admin_password": "Current administration password", "ask_email": "E-mail address", "ask_firstname": "First name", "ask_lastname": "Last name", @@ -67,7 +62,6 @@ "ask_new_domain": "New domain", "ask_new_path": "New path", "ask_password": "Password", - "ask_path": "Path", "backup_abstract_method": "This backup method has yet to be implemented", "backup_actually_backuping": "Creating a backup archive from the collected files…", "backup_app_failed": "Could not back up the app '{app:s}'", @@ -196,7 +190,6 @@ "diagnosis_regenconf_manually_modified_details": "This is probably OK as long as you know what you're doing ;) !", "diagnosis_regenconf_manually_modified_debian": "Configuration file {file} was manually modified compared to Debian's default.", "diagnosis_regenconf_manually_modified_debian_details": "This may probably be OK, but gotta keep an eye on it...", - "diagnosis_regenconf_nginx_conf_broken": "The nginx configuration appears to be broken!", "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.", @@ -298,7 +291,6 @@ "group_cannot_edit_all_users": "The group 'all_users' cannot be edited manually. It is a special group meant to contain all users registered in YunoHost", "group_cannot_edit_visitors": "The group 'visitors' cannot be edited manually. It is a special group representing anonymous visitors", "group_cannot_edit_primary_group": "The group '{group}' cannot be edited manually. It is the primary group meant to contain only one specific user.", - "group_cannot_be_edited": "The group {group} cannot be edited manually.", "group_cannot_be_deleted": "The group {group} cannot be deleted manually.", "group_deleted": "Group '{group}' deleted", "group_deletion_failed": "Could not delete the group '{group}': {error}", @@ -364,7 +356,6 @@ "log_tools_reboot": "Reboot your server", "ldap_init_failed_to_create_admin": "LDAP initialization could not create admin user", "ldap_initialized": "LDAP initialized", - "license_undefined": "undefined", "mail_alias_remove_failed": "Could not remove e-mail alias '{mail:s}'", "mail_domain_unknown": "Invalid e-mail address for domain '{domain:s}'. Please, use a domain administrated by this server.", "mail_forward_remove_failed": "Could not remove e-mail forwarding '{mail:s}'", @@ -465,10 +456,8 @@ "pattern_email": "Must be a valid e-mail address (e.g. someone@example.com)", "pattern_firstname": "Must be a valid first name", "pattern_lastname": "Must be a valid last name", - "pattern_listname": "Must be alphanumeric and underscore characters only", "pattern_mailbox_quota": "Must be a size with b/k/M/G/T suffix or 0 to not have a quota", "pattern_password": "Must be at least 3 characters long", - "pattern_port": "Must be a valid port number (i.e. 0-65535)", "pattern_port_or_range": "Must be a valid port number (i.e. 0-65535) or range of ports (e.g. 100:200)", "pattern_positive_number": "Must be a positive number", "pattern_username": "Must be lower-case alphanumeric and underscore characters only", @@ -542,7 +531,6 @@ "service_description_php7.0-fpm": "Runs apps written in PHP with NGINX", "service_description_postfix": "Used to send and receive e-mails", "service_description_redis-server": "A specialized database used for rapid data access, task queue, and communication between programs", - "service_description_rmilter": "Checks various parameters in e-mails", "service_description_rspamd": "Filters spam, and other e-mail related features", "service_description_slapd": "Stores users, domains and related info", "service_description_ssh": "Allows you to connect remotely to your server via a terminal (SSH protocol)", @@ -571,7 +559,6 @@ "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`.", - "tools_update_failed_to_app_fetchlist": "Could not update YunoHost's app lists because: {error}", "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…", @@ -588,7 +575,6 @@ "update_apt_cache_failed": "Could not to update the cache of APT (Debian's package manager). Here is a dump of the sources.list lines, which might help identify problematic lines: \n{sourceslist}", "update_apt_cache_warning": "Something went wrong while updating the cache of APT (Debian's package manager). Here is a dump of the sources.list lines, which might help identify problematic lines: \n{sourceslist}", "updating_apt_cache": "Fetching available upgrades for system packages…", - "updating_app_lists": "Fetching available upgrades for apps…", "upgrade_complete": "Upgrade complete", "upgrading_packages": "Upgrading packages…", "upnp_dev_not_found": "No UPnP device found", @@ -601,7 +587,6 @@ "user_deleted": "User deleted", "user_deletion_failed": "Could not delete user {user}: {error}", "user_home_creation_failed": "Could not create 'home' folder for user", - "user_info_failed": "Could not retrieve user info", "user_unknown": "Unknown user: {user:s}", "user_update_failed": "Could not update user {user}: {error}", "user_updated": "User info changed", diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index c254c2ab5..8408e7fa3 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -1148,21 +1148,18 @@ class RestoreManager(): if not os.path.isfile(backup_csv): return - try: - contains_php5 = False - with open(backup_csv) as csvfile: - reader = csv.DictReader(csvfile, fieldnames=['source', 'dest']) - newlines = [] - for row in reader: - if 'php5' in row['source']: - contains_php5 = True - row['source'] = row['source'].replace('/etc/php5', '/etc/php/7.0') \ - .replace('/var/run/php5-fpm', '/var/run/php/php7.0-fpm') \ - .replace('php5', 'php7') + contains_php5 = False + with open(backup_csv) as csvfile: + reader = csv.DictReader(csvfile, fieldnames=['source', 'dest']) + newlines = [] + for row in reader: + if 'php5' in row['source']: + contains_php5 = True + row['source'] = row['source'].replace('/etc/php5', '/etc/php/7.0') \ + .replace('/var/run/php5-fpm', '/var/run/php/php7.0-fpm') \ + .replace('php5', 'php7') - newlines.append(row) - except (IOError, OSError, csv.Error) as e: - raise YunohostError('error_reading_file', file=backup_csv, error=str(e)) + newlines.append(row) if not contains_php5: return @@ -1834,11 +1831,12 @@ class CopyBackupMethod(BackupMethod): self.work_dir]) if ret == 0: return - else: - logger.warning(m18n.n("bind_mouting_disable")) - subprocess.call(["mountpoint", "-q", self.work_dir, - "&&", "umount", "-R", self.work_dir]) - raise YunohostError('backup_cant_mount_uncompress_archive') + + logger.warning("Could not mount the backup in readonly mode with --rbind ... Unmounting") + # FIXME : Does this stuff really works ? '&&' is going to be interpreted as an argument for mounpoint here ... Not as a classical '&&' ... + subprocess.call(["mountpoint", "-q", self.work_dir, + "&&", "umount", "-R", self.work_dir]) + raise YunohostError('backup_cant_mount_uncompress_archive') class TarBackupMethod(BackupMethod): diff --git a/src/yunohost/data_migrations/0003_migrate_to_stretch.py b/src/yunohost/data_migrations/0003_migrate_to_stretch.py index 19793bbec..60b26169a 100644 --- a/src/yunohost/data_migrations/0003_migrate_to_stretch.py +++ b/src/yunohost/data_migrations/0003_migrate_to_stretch.py @@ -108,7 +108,7 @@ class MyMigration(Migration): # Have > 1 Go free space on /var/ ? if free_space_in_directory("/var/") / (1024**3) < 1.0: - raise YunohostError("migration_0003_not_enough_free_space") + raise YunohostError("There is not enough free space in /var/ to run the migration. You need at least 1GB free space") # Check system is up to date # (but we don't if 'stretch' is already in the sources.list ... diff --git a/src/yunohost/regenconf.py b/src/yunohost/regenconf.py index 665b906d6..ad84c8164 100644 --- a/src/yunohost/regenconf.py +++ b/src/yunohost/regenconf.py @@ -164,10 +164,10 @@ def regen_conf(operation_logger, names=[], with_diff=False, force=False, dry_run if not dry_run: operation_logger.related_to.append(('configuration', category)) - logger.debug(m18n.n( - 'regenconf_pending_applying' if not dry_run else - 'regenconf_dry_pending_applying', - category=category)) + if dry_run: + logger.debug(m18n.n('regenconf_pending_applying', category=category)) + else: + logger.debug(m18n.n('regenconf_dry_pending_applying', category=category)) conf_hashes = _get_conf_hashes(category) succeed_regen = {} @@ -281,10 +281,10 @@ def regen_conf(operation_logger, names=[], with_diff=False, force=False, dry_run logger.debug(m18n.n('regenconf_up_to_date', category=category)) continue elif not failed_regen: - logger.success(m18n.n( - 'regenconf_updated' if not dry_run else - 'regenconf_would_be_updated', - category=category)) + if not dry_run: + logger.success(m18n.n('regenconf_updated', category=category)) + else: + logger.success(m18n.n('regenconf_would_be_updated', category=category)) if succeed_regen and not dry_run: _update_conf_hashes(category, conf_hashes) From e3ba55eb1bb31ee77d333ffa1a947e5433e98c01 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 30 Mar 2020 06:58:35 +0200 Subject: [PATCH 0781/3170] Removing untranslated string from ar.json with approval from BoF --- locales/ar.json | 256 ------------------------------------------------ 1 file changed, 256 deletions(-) diff --git a/locales/ar.json b/locales/ar.json index db77b5cb4..1e089ec57 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -4,51 +4,28 @@ "admin_password_change_failed": "لا يمكن تعديل الكلمة السرية", "admin_password_changed": "تم تعديل الكلمة السرية الإدارية", "app_already_installed": "{app:s} تم تنصيبه مِن قبل", - "app_already_installed_cant_change_url": "", "app_already_up_to_date": "{app:s} تم تحديثه مِن قَبل", - "app_argument_choice_invalid": "", - "app_argument_invalid": "", "app_argument_required": "المُعامِل '{name:s}' مطلوب", "app_change_no_change_url_script": "إنّ التطبيق {app_name:s} لا يدعم تغيير الرابط، مِن الممكن أنه يتوجب عليكم تحدثيه.", "app_change_url_failed_nginx_reload": "فشلت عملية إعادة تشغيل NGINX. ها هي نتيجة الأمر 'nginx -t':\n{nginx_errors:s}", - "app_change_url_identical_domains": "", - "app_change_url_no_script": "", - "app_change_url_success": "", "app_extraction_failed": "تعذر فك الضغط عن ملفات التنصيب", - "app_id_invalid": "", "app_incompatible": "إن التطبيق {app} غير متوافق مع إصدار واي يونوهوست YunoHost الخاص بك", "app_install_files_invalid": "ملفات التنصيب خاطئة", - "app_location_already_used": "The app '{app}' is already installed on that location ({path})", - "app_make_default_location_already_used": "", - "app_location_install_failed": "Unable to install the app in this location because it conflit with the app '{other_app}' already installed on '{other_path}'", - "app_location_unavailable": "", - "app_manifest_invalid": "", "app_no_upgrade": "ليس هناك أي تطبيق بحاجة إلى تحديث", "app_not_correctly_installed": "يبدو أن التطبيق {app:s} لم يتم تنصيبه بشكل صحيح", "app_not_installed": "إنّ التطبيق {app:s} غير مُنصَّب", "app_not_properly_removed": "لم يتم حذف تطبيق {app:s} بشكلٍ جيّد", - "app_package_need_update": "", "app_removed": "تمت إزالة تطبيق {app:s}", "app_requirements_checking": "جار فحص الحزم اللازمة لـ {app}…", - "app_requirements_failed": "", - "app_requirements_unmeet": "", "app_sources_fetch_failed": "تعذرت عملية جلب مصادر الملفات", "app_unknown": "برنامج مجهول", - "app_unsupported_remote_type": "Unsupported remote type used for the app", "app_upgrade_app_name": "جارٍ تحديث تطبيق {app}…", "app_upgrade_failed": "تعذرت عملية ترقية {app:s}", "app_upgrade_some_app_failed": "تعذرت عملية ترقية بعض التطبيقات", "app_upgraded": "تم تحديث التطبيق {app:s}", - "appslist_corrupted_json": "Could not load the application lists. It looks like {filename:s} is corrupted.", - "appslist_could_not_migrate": "Could not migrate app list {appslist:s} ! Unable to parse the url... The old cron job has been kept in {bkp_file:s}.", "appslist_fetched": "تم جلب قائمة تطبيقات {appslist:s}", - "appslist_migrating": "Migrating application list {appslist:s} …", - "appslist_name_already_tracked": "There is already a registered application list with name {name:s}.", "appslist_removed": "تم حذف قائمة التطبيقات {appslist:s}", - "appslist_retrieve_bad_format": "Retrieved file for application list {appslist:s} is not valid", - "appslist_retrieve_error": "Unable to retrieve the remote application list {appslist:s}: {error:s}", "appslist_unknown": "قائمة التطبيقات {appslist:s} مجهولة.", - "appslist_url_already_tracked": "There is already a registered application list with url {url:s}.", "ask_current_admin_password": "كلمة السر الإدارية الحالية", "ask_email": "عنوان البريد الإلكتروني", "ask_firstname": "الإسم", @@ -58,311 +35,78 @@ "ask_new_admin_password": "كلمة السر الإدارية الجديدة", "ask_password": "كلمة السر", "ask_path": "المسار", - "backup_abstract_method": "This backup method hasn't yet been implemented", - "backup_action_required": "You must specify something to save", - "backup_app_failed": "Unable to back up the app '{app:s}'", - "backup_applying_method_borg": "Sending all files to backup into borg-backup repository…", "backup_applying_method_copy": "جارٍ نسخ كافة الملفات إلى النسخة الإحتياطية …", - "backup_applying_method_custom": "Calling the custom backup method '{method:s}'…", "backup_applying_method_tar": "جارٍ إنشاء ملف TAR للنسخة الاحتياطية…", - "backup_archive_app_not_found": "App '{app:s}' not found in the backup archive", - "backup_archive_broken_link": "Unable to access backup archive (broken link to {path:s})", - "backup_archive_mount_failed": "Mounting the backup archive failed", - "backup_archive_name_exists": "The backup's archive name already exists", - "backup_archive_name_unknown": "Unknown local backup archive named '{name:s}'", - "backup_archive_open_failed": "Unable to open the backup archive", - "backup_archive_system_part_not_available": "System part '{part:s}' not available in this backup", - "backup_archive_writing_error": "Unable to add files to backup into the compressed archive", - "backup_ask_for_copying_if_needed": "Some files couldn't be prepared to be backuped using the method that avoid to temporarily waste space on the system. To perform the backup, {size:s}MB should be used temporarily. Do you agree?", - "backup_borg_not_implemented": "Borg backup method is not yet implemented", - "backup_cant_mount_uncompress_archive": "Unable to mount in readonly mode the uncompress archive directory", - "backup_cleaning_failed": "Unable to clean-up the temporary backup directory", - "backup_copying_to_organize_the_archive": "Copying {size:s}MB to organize the archive", - "backup_couldnt_bind": "Couldn't bind {src:s} to {dest:s}.", "backup_created": "تم إنشاء النسخة الإحتياطية", "backup_creating_archive": "جارٍ إنشاء ملف النسخة الاحتياطية…", - "backup_creation_failed": "Backup creation failed", - "backup_csv_addition_failed": "Unable to add files to backup into the CSV file", - "backup_csv_creation_failed": "Unable to create the CSV file needed for future restore operations", - "backup_custom_backup_error": "Custom backup method failure on 'backup' step", - "backup_custom_mount_error": "Custom backup method failure on 'mount' step", - "backup_custom_need_mount_error": "Custom backup method failure on 'need_mount' step", - "backup_delete_error": "Unable to delete '{path:s}'", - "backup_deleted": "The backup has been deleted", - "backup_extracting_archive": "Extracting the backup archive…", - "backup_hook_unknown": "Backup hook '{hook:s}' unknown", "backup_invalid_archive": "نسخة إحتياطية غير صالحة", - "backup_method_borg_finished": "Backup into borg finished", "backup_method_copy_finished": "إنتهت عملية النسخ الإحتياطي", - "backup_method_custom_finished": "Custom backup method '{method:s}' finished", - "backup_method_tar_finished": "Backup tar archive created", - "backup_no_uncompress_archive_dir": "Uncompress archive directory doesn't exist", "backup_nothings_done": "ليس هناك أي شيء للحفظ", - "backup_output_directory_forbidden": "Forbidden output directory. Backups can't be created in /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var or /home/yunohost.backup/archives sub-folders", - "backup_output_directory_not_empty": "The output directory is not empty", "backup_output_directory_required": "يتوجب عليك تحديد مجلد لتلقي النسخ الإحتياطية", - "backup_output_symlink_dir_broken": "You have a broken symlink instead of your archives directory '{path:s}'. You may have a specific setup to backup your data on an other filesystem, in this case you probably forgot to remount or plug your hard dirve or usb key.", - "backup_running_app_script": "Running backup script of app '{app:s}'...", - "backup_running_hooks": "Running backup hooks…", - "backup_system_part_failed": "Unable to backup the '{part:s}' system part", - "backup_unable_to_organize_files": "Unable to organize files in the archive with the quick method", - "backup_with_no_backup_script_for_app": "App {app:s} has no backup script. Ignoring.", - "backup_with_no_restore_script_for_app": "App {app:s} has no restore script, you won't be able to automatically restore the backup of this app.", - "certmanager_acme_not_configured_for_domain": "Certificate for domain {domain:s} does not appear to be correctly installed. Please run cert-install for this domain first.", - "certmanager_attempt_to_renew_nonLE_cert": "The certificate for domain {domain:s} is not issued by Let's Encrypt. Cannot renew it automatically!", - "certmanager_attempt_to_renew_valid_cert": "The certificate for domain {domain:s} is not about to expire! Use --force to bypass", - "certmanager_attempt_to_replace_valid_cert": "You are attempting to overwrite a good and valid certificate for domain {domain:s}! (Use --force to bypass)", - "certmanager_cannot_read_cert": "Something wrong happened when trying to open current certificate for domain {domain:s} (file: {file:s}), reason: {reason:s}", "certmanager_cert_install_success": "تمت عملية تنصيب شهادة Let's Encrypt بنجاح على النطاق {domain:s} !", "certmanager_cert_install_success_selfsigned": "نجحت عملية تثبيت الشهادة الموقعة ذاتيا الخاصة بالنطاق {domain:s}", "certmanager_cert_renew_success": "نجحت عملية تجديد شهادة Let's Encrypt الخاصة باسم النطاق {domain:s} !", "certmanager_cert_signing_failed": "فشل إجراء توقيع الشهادة الجديدة", - "certmanager_certificate_fetching_or_enabling_failed": "Sounds like enabling the new certificate for {domain:s} failed somehow…", - "certmanager_conflicting_nginx_file": "Unable to prepare domain for ACME challenge: the nginx configuration file {filepath:s} is conflicting and should be removed first", - "certmanager_couldnt_fetch_intermediate_cert": "Timed out when trying to fetch intermediate certificate from Let's Encrypt. Certificate installation/renewal aborted - please try again later.", - "certmanager_domain_cert_not_selfsigned": "The certificate for domain {domain:s} is not self-signed. Are you sure you want to replace it? (Use --force)", - "certmanager_domain_dns_ip_differs_from_public_ip": "The DNS 'A' record for domain {domain:s} is different from this server 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 disable those checks.)", - "certmanager_domain_http_not_working": "It seems that the domain {domain:s} cannot be accessed through HTTP. Please check your DNS and nginx configuration is okay", - "certmanager_domain_not_resolved_locally": "The domain {domain:s} cannot be resolved from inside your Yunohost server. This might happen if you recently modified your DNS record. If so, please wait a few hours for it to propagate. If the issue persists, consider adding {domain:s} to /etc/hosts. (If you know what you are doing, use --no-checks to disable those checks.)", "certmanager_domain_unknown": "النطاق مجهول {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 disable those checks.)", - "certmanager_hit_rate_limit": "Too many certificates already issued for 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 public IP address (domain {domain:s} with ip {ip:s}). You may be experiencing hairpinning issue or the firewall/router ahead of your server is misconfigured.", "certmanager_no_cert_file": "تعذرت عملية قراءة شهادة نطاق {domain:s} (الملف : {file:s})", - "certmanager_old_letsencrypt_app_detected": "", - "certmanager_self_ca_conf_file_not_found": "Configuration file not found for self-signing authority (file: {file:s})", - "certmanager_unable_to_parse_self_CA_name": "Unable to parse name of self-signing authority (file: {file:s})", - "custom_app_url_required": "You must provide a URL to upgrade your custom app {app:s}", - "custom_appslist_name_required": "You must provide a name for your custom app list", "diagnosis_debian_version_error": "لم نتمكن من العثور على إصدار ديبيان : {error}", - "diagnosis_kernel_version_error": "Can't retrieve kernel version: {error}", - "diagnosis_monitor_disk_error": "Can't monitor disks: {error}", - "diagnosis_monitor_network_error": "Can't monitor network: {error}", - "diagnosis_monitor_system_error": "Can't monitor system: {error}", "diagnosis_no_apps": "لم تقم بتنصيب أية تطبيقات بعد", - "dnsmasq_isnt_installed": "dnsmasq does not seem to be installed, please run 'apt-get remove bind9 && apt-get install dnsmasq'", - "domain_cannot_remove_main": "Cannot remove main domain. Set a new main domain first", - "domain_cert_gen_failed": "Unable to generate certificate", "domain_created": "تم إنشاء النطاق", "domain_creation_failed": "تعذرت عملية إنشاء النطاق", "domain_deleted": "تم حذف النطاق", - "domain_deletion_failed": "Unable to delete domain", - "domain_dns_conf_is_just_a_recommendation": "This command shows you what is the *recommended* configuration. It does not actually set up the DNS configuration for you. It is your responsability to configure your DNS zone in your registrar according to this recommendation.", - "domain_dyndns_already_subscribed": "You've already subscribed to a DynDNS domain", - "domain_dyndns_dynette_is_unreachable": "Unable to reach YunoHost dynette, either your YunoHost is not correctly connected to the internet or the dynette server is down. Error: {error}", - "domain_dyndns_invalid": "Invalid domain to use with DynDNS", - "domain_dyndns_root_unknown": "Unknown DynDNS root domain", "domain_exists": "اسم النطاق موجود مِن قبل", - "domain_hostname_failed": "Failed to set new hostname", - "domain_uninstall_app_first": "One or more apps are installed on this domain. Please uninstall them before proceeding to domain removal", "domain_unknown": "النطاق مجهول", "domain_zone_exists": "ملف منطقة أسماء النطاقات موجود مِن قبل", - "domain_zone_not_found": "DNS zone file not found for domain {:s}", "domains_available": "النطاقات المتوفرة :", "done": "تم", "downloading": "عملية التنزيل جارية …", - "dyndns_could_not_check_provide": "Could not check if {provider:s} can provide {domain:s}.", - "dyndns_cron_installed": "The DynDNS cron job has been installed", - "dyndns_cron_remove_failed": "Unable to remove the DynDNS cron job", - "dyndns_cron_removed": "The DynDNS cron job has been removed", - "dyndns_ip_update_failed": "Unable to update IP address on DynDNS", "dyndns_ip_updated": "لقد تم تحديث عنوان الإيبي الخاص بك على نظام أسماء النطاقات الديناميكي", "dyndns_key_generating": "عملية توليد مفتاح نظام أسماء النطاقات جارية. يمكن للعملية أن تستغرق بعضا من الوقت…", "dyndns_key_not_found": "لم يتم العثور على مفتاح DNS الخاص باسم النطاق هذا", - "dyndns_no_domain_registered": "No domain has been registered with DynDNS", - "dyndns_registered": "The DynDNS domain has been registered", - "dyndns_registration_failed": "Unable to register DynDNS domain: {error:s}", - "dyndns_domain_not_provided": "Dyndns provider {provider:s} cannot provide domain {domain:s}.", - "dyndns_unavailable": "Domain {domain:s} is not available.", - "executing_command": "Executing command '{command:s}'…", - "executing_script": "Executing script '{script:s}'…", "extracting": "عملية فك الضغط جارية …", - "field_invalid": "Invalid field '{:s}'", - "firewall_reload_failed": "Unable to reload the firewall", - "firewall_reloaded": "The firewall has been reloaded", - "firewall_rules_cmd_failed": "Some firewall rules commands have failed. For more information, see the log.", - "format_datetime_short": "%m/%d/%Y %I:%M %p", - "global_settings_bad_choice_for_enum": "", - "global_settings_bad_type_for_setting": "", - "global_settings_cant_open_settings": "", - "global_settings_cant_serialize_settings": "", - "global_settings_cant_write_settings": "", - "global_settings_key_doesnt_exists": "", - "global_settings_reset_success": "", - "global_settings_setting_example_bool": "", - "global_settings_setting_example_enum": "", - "global_settings_setting_example_int": "", - "global_settings_setting_example_string": "", - "global_settings_unknown_setting_from_settings_file": "Unknown key in settings: '{setting_key:s}', discarding it and save it in /etc/yunohost/unkown_settings.json", - "global_settings_unknown_type": "Unexpected situation, the setting {setting:s} appears to have the type {unknown_type:s} but it's not a type supported by the system.", - "hook_exec_failed": "Script execution failed: {path:s}", - "hook_exec_not_terminated": "Script execution hasn’t terminated: {path:s}", - "hook_list_by_invalid": "Invalid property to list hook by", - "hook_name_unknown": "Unknown hook name '{name:s}'", "installation_complete": "إكتملت عملية التنصيب", - "installation_failed": "Installation failed", - "invalid_url_format": "Invalid URL format", - "ip6tables_unavailable": "You cannot play with ip6tables here. You are either in a container or your kernel does not support it", - "iptables_unavailable": "You cannot play with iptables here. You are either in a container or your kernel does not support it", - "ldap_init_failed_to_create_admin": "LDAP initialization failed to create admin user", - "ldap_initialized": "LDAP has been initialized", - "license_undefined": "undefined", - "mail_alias_remove_failed": "Unable to remove mail alias '{mail:s}'", - "mail_domain_unknown": "Unknown mail address domain '{domain:s}'", - "mail_forward_remove_failed": "Unable to remove mail forward '{mail:s}'", - "mailbox_used_space_dovecot_down": "Dovecot mailbox service need to be up, if you want to get mailbox used space", "main_domain_change_failed": "تعذّر تغيير النطاق الأساسي", "main_domain_changed": "تم تغيير النطاق الأساسي", - "migrate_tsig_end": "Migration to hmac-sha512 finished", - "migrate_tsig_failed": "Migrating the dyndns domain {domain} to hmac-sha512 failed, rolling back. Error: {error_code} - {error}", - "migrate_tsig_start": "Not secure enough key algorithm detected for TSIG signature of domain '{domain}', initiating migration to the more secure one hmac-sha512", "migrate_tsig_wait": "لننتظر الآن ثلاثة دقائق ريثما يأخذ خادم أسماء النطاقات الديناميكية بعين الاعتبار المفتاح الجديد…", "migrate_tsig_wait_2": "دقيقتين …", "migrate_tsig_wait_3": "دقيقة واحدة …", "migrate_tsig_wait_4": "30 ثانية …", - "migrate_tsig_not_needed": "You do not appear to use a dyndns domain, so no migration is needed !", - "migrations_backward": "Migrating backward.", - "migrations_bad_value_for_target": "Invalid number for target argument, available migrations numbers are 0 or {}", - "migrations_cant_reach_migration_file": "Can't access migrations files at path %s", - "migrations_current_target": "Migration target is {}", - "migrations_error_failed_to_load_migration": "ERROR: failed to load migration {number} {name}", - "migrations_forward": "Migrating forward", - "migrations_loading_migration": "Loading migration {id}…", - "migrations_migration_has_failed": "Migration {id} has failed with exception {exception}, aborting", - "migrations_no_migrations_to_run": "No migrations to run", - "migrations_show_currently_running_migration": "Running migration {number} {name}…", - "migrations_show_last_migration": "Last ran migration is {}", "migrations_skip_migration": "جارٍ تجاهل التهجير {id}…", - "monitor_disabled": "The server monitoring has been disabled", - "monitor_enabled": "The server monitoring has been enabled", - "monitor_glances_con_failed": "Unable to connect to Glances server", - "monitor_not_enabled": "Server monitoring is not enabled", - "monitor_period_invalid": "Invalid time period", - "monitor_stats_file_not_found": "Statistics file not found", - "monitor_stats_no_update": "No monitoring statistics to update", - "monitor_stats_period_unavailable": "No available statistics for the period", - "mountpoint_unknown": "Unknown mountpoint", - "mysql_db_creation_failed": "MySQL database creation failed", - "mysql_db_init_failed": "MySQL database init failed", - "mysql_db_initialized": "The MySQL database has been initialized", - "network_check_mx_ko": "DNS MX record is not set", - "network_check_smtp_ko": "Outbound mail (SMTP port 25) seems to be blocked by your network", - "network_check_smtp_ok": "Outbound mail (SMTP port 25) is not blocked", - "new_domain_required": "You must provide the new main domain", - "no_appslist_found": "No app list found", - "no_internet_connection": "Server is not connected to the Internet", - "no_ipv6_connectivity": "IPv6 connectivity is not available", - "no_restore_script": "No restore script found for the app '{app:s}'", - "not_enough_disk_space": "Not enough free disk space on '{path:s}'", - "package_not_installed": "Package '{pkgname}' is not installed", - "package_unexpected_error": "An unexpected error occurred processing the package '{pkgname}'", - "package_unknown": "Unknown package '{pkgname}'", "packages_no_upgrade": "لا يوجد هناك أية حزمة بحاجة إلى تحديث", - "packages_upgrade_critical_later": "Critical packages ({packages:s}) will be upgraded later", - "packages_upgrade_failed": "Unable to upgrade all of the packages", - "path_removal_failed": "Unable to remove path {:s}", - "pattern_backup_archive_name": "Must be a valid filename with max 30 characters, and alphanumeric and -_. characters only", "pattern_domain": "يتوجب أن يكون إسم نطاق صالح (مثل my-domain.org)", "pattern_email": "يتوجب أن يكون عنوان بريد إلكتروني صالح (مثل someone@domain.org)", - "pattern_firstname": "Must be a valid first name", - "pattern_lastname": "Must be a valid last name", - "pattern_listname": "Must be alphanumeric and underscore characters only", - "pattern_mailbox_quota": "Must be a size with b/k/M/G/T suffix or 0 to disable the quota", "pattern_password": "يتوجب أن تكون مكونة من 3 حروف على الأقل", "pattern_port": "يجب أن يكون رقم منفذ صالح (مثال 0-65535)", - "pattern_port_or_range": "Must be a valid port number (i.e. 0-65535) or range of ports (e.g. 100:200)", "pattern_positive_number": "يجب أن يكون عددا إيجابيا", - "pattern_username": "Must be lower-case alphanumeric and underscore characters only", - "port_already_closed": "Port {port:d} is already closed for {ip_version:s} connections", - "port_already_opened": "Port {port:d} is already opened for {ip_version:s} connections", "port_available": "المنفذ {port:d} متوفر", - "port_unavailable": "Port {port:d} is not available", - "restore_action_required": "You must specify something to restore", - "restore_already_installed_app": "An app is already installed with the id '{app:s}'", - "restore_app_failed": "Unable to restore the app '{app:s}'", - "restore_cleaning_failed": "Unable to clean-up the temporary restoration directory", - "restore_complete": "Restore complete", - "restore_confirm_yunohost_installed": "Do you really want to restore an already installed system? [{answers:s}]", "restore_extracting": "جارٍ فك الضغط عن الملفات التي نحتاجها من النسخة الاحتياطية…", - "restore_failed": "Unable to restore the system", - "restore_hook_unavailable": "Restoration script for '{part:s}' not available on your system and not in the archive either", - "restore_may_be_not_enough_disk_space": "Your system seems not to have enough disk space (freespace: {free_space:d} B, needed space: {needed_space:d} B, security margin: {margin:d} B)", "restore_mounting_archive": "تنصيب النسخة الإحتياطية على المسار '{path:s}'", - "restore_not_enough_disk_space": "Not enough disk space (freespace: {free_space:d} B, needed space: {needed_space:d} B, security margin: {margin:d} B)", - "restore_nothings_done": "Nothing has been restored", - "restore_removing_tmp_dir_failed": "Unable to remove an old temporary directory", - "restore_running_app_script": "Running restore script of app '{app:s}'…", - "restore_running_hooks": "Running restoration hooks…", - "restore_system_part_failed": "Unable to restore the '{part:s}' system part", "server_shutdown": "سوف ينطفئ الخادوم", "server_shutdown_confirm": "سوف ينطفئ الخادوم حالا. متأكد ؟ [{answers:s}]", "server_reboot": "سيعاد تشغيل الخادوم", "server_reboot_confirm": "سيعاد تشغيل الخادوم في الحين. هل أنت متأكد ؟ [{answers:s}]", "service_add_failed": "تعذرت إضافة خدمة '{service:s}'", - "service_added": "The service '{service:s}' has been added", - "service_already_started": "Service '{service:s}' has already been started", "service_already_stopped": "إنّ خدمة '{service:s}' متوقفة مِن قبلُ", - "service_cmd_exec_failed": "Unable to execute command '{command:s}'", - "service_conf_file_backed_up": "The configuration file '{conf}' has been backed up to '{backup}'", - "service_conf_file_copy_failed": "Unable to copy the new configuration file '{new}' to '{conf}'", - "service_conf_file_kept_back": "The configuration file '{conf}' is expected to be deleted by service {service} but has been kept back.", - "service_conf_file_manually_modified": "The configuration file '{conf}' has been manually modified and will not be updated", - "service_conf_file_manually_removed": "The configuration file '{conf}' has been manually removed and will not be created", - "service_conf_file_remove_failed": "Unable to remove the configuration file '{conf}'", - "service_conf_file_removed": "The configuration file '{conf}' has been removed", - "service_conf_file_updated": "The configuration file '{conf}' has been updated", - "service_conf_new_managed_file": "The configuration file '{conf}' is now managed by the service {service}.", - "service_conf_up_to_date": "The configuration is already up-to-date for service '{service}'", - "service_conf_updated": "The configuration has been updated for service '{service}'", - "service_conf_would_be_updated": "The configuration would have been updated for service '{service}'", - "service_disable_failed": "", "service_disabled": "لن يتم إطلاق خدمة '{service:s}' أثناء بداية تشغيل النظام.", - "service_enable_failed": "", "service_enabled": "تم تنشيط خدمة '{service:s}'", "service_no_log": "ليس لخدمة '{service:s}' أي سِجلّ للعرض", - "service_regenconf_dry_pending_applying": "Checking pending configuration which would have been applied for service '{service}'...", - "service_regenconf_failed": "Unable to regenerate the configuration for service(s): {services}", - "service_regenconf_pending_applying": "Applying pending configuration for service '{service}'...", - "service_remove_failed": "Unable to remove service '{service:s}'", "service_removed": "تمت إزالة خدمة '{service:s}'", - "service_start_failed": "", "service_started": "تم إطلاق تشغيل خدمة '{service:s}'", - "service_status_failed": "Unable to determine status of service '{service:s}'", - "service_stop_failed": "", "service_stopped": "تمّ إيقاف خدمة '{service:s}'", - "service_unknown": "Unknown service '{service:s}'", - "ssowat_conf_generated": "The SSOwat configuration has been generated", - "ssowat_conf_updated": "The SSOwat configuration has been updated", - "ssowat_persistent_conf_read_error": "Error while reading SSOwat persistent configuration: {error:s}. Edit /etc/ssowat/conf.json.persistent file to fix the JSON syntax", - "ssowat_persistent_conf_write_error": "Error while saving SSOwat persistent configuration: {error:s}. Edit /etc/ssowat/conf.json.persistent file to fix the JSON syntax", "system_upgraded": "تمت عملية ترقية النظام", - "system_username_exists": "Username already exists in the system users", - "unbackup_app": "App '{app:s}' will not be saved", - "unexpected_error": "An unexpected error occured", - "unit_unknown": "Unknown unit '{unit:s}'", "unlimit": "دون تحديد الحصة", - "unrestore_app": "App '{app:s}' will not be restored", - "update_cache_failed": "Unable to update APT cache", "updating_apt_cache": "جارٍ جلب قائمة حُزم النظام المحدّثة المتوفرة…", "upgrade_complete": "اكتملت عملية الترقية و التحديث", "upgrading_packages": "عملية ترقية الحُزم جارية …", - "upnp_dev_not_found": "No UPnP device found", "upnp_disabled": "تم تعطيل UPnP", - "upnp_enabled": "UPnP has been enabled", - "upnp_port_open_failed": "Unable to open UPnP ports", "user_created": "تم إنشاء المستخدم", - "user_creation_failed": "Unable to create user", "user_deleted": "تم حذف المستخدم", "user_deletion_failed": "لا يمكن حذف المستخدم", - "user_home_creation_failed": "Unable to create user home folder", - "user_info_failed": "Unable to retrieve user information", "user_unknown": "المستخدم {user:s} مجهول", "user_update_failed": "لا يمكن تحديث المستخدم", "user_updated": "تم تحديث المستخدم", - "yunohost_already_installed": "YunoHost is already installed", "yunohost_ca_creation_failed": "تعذرت عملية إنشاء هيئة الشهادات", "yunohost_ca_creation_success": "تم إنشاء هيئة الشهادات المحلية.", - "yunohost_configured": "YunoHost has been configured", "yunohost_installing": "عملية تنصيب يونوهوست جارية …", "yunohost_not_installed": "إنَّ واي يونوهوست ليس مُنَصَّب أو هو مثبت حاليا بشكل خاطئ. قم بتنفيذ الأمر 'yunohost tools postinstall'", "migration_description_0003_migrate_to_stretch": "تحديث النظام إلى ديبيان ستريتش و واي يونوهوست 3.0", From dc445a8aa879c45b7b67efd3458f1a433e8b5349 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 30 Mar 2020 07:01:25 +0200 Subject: [PATCH 0782/3170] Remove stale strings from translations --- locales/ar.json | 31 +--- locales/bn_BD.json | 2 +- locales/br.json | 2 +- locales/ca.json | 134 +------------- locales/de.json | 119 +----------- locales/el.json | 2 +- locales/eo.json | 74 +------- locales/es.json | 142 +-------------- locales/eu.json | 2 +- locales/fr.json | 172 +----------------- locales/hi.json | 28 +-- locales/hu.json | 2 +- locales/it.json | 103 +---------- locales/nb_NO.json | 34 +--- locales/ne.json | 2 +- locales/nl.json | 44 +---- locales/oc.json | 154 +--------------- locales/pl.json | 2 +- locales/pt.json | 58 +----- locales/ru.json | 16 +- locales/sv.json | 2 +- locales/tr.json | 2 +- locales/zh_Hans.json | 2 +- ....py => remove_stale_translated_strings.py} | 0 24 files changed, 23 insertions(+), 1106 deletions(-) rename tests/{remove_stale_string.py => remove_stale_translated_strings.py} (100%) diff --git a/locales/ar.json b/locales/ar.json index 1e089ec57..502cc2cf6 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -6,12 +6,9 @@ "app_already_installed": "{app:s} تم تنصيبه مِن قبل", "app_already_up_to_date": "{app:s} تم تحديثه مِن قَبل", "app_argument_required": "المُعامِل '{name:s}' مطلوب", - "app_change_no_change_url_script": "إنّ التطبيق {app_name:s} لا يدعم تغيير الرابط، مِن الممكن أنه يتوجب عليكم تحدثيه.", "app_change_url_failed_nginx_reload": "فشلت عملية إعادة تشغيل NGINX. ها هي نتيجة الأمر 'nginx -t':\n{nginx_errors:s}", "app_extraction_failed": "تعذر فك الضغط عن ملفات التنصيب", - "app_incompatible": "إن التطبيق {app} غير متوافق مع إصدار واي يونوهوست YunoHost الخاص بك", "app_install_files_invalid": "ملفات التنصيب خاطئة", - "app_no_upgrade": "ليس هناك أي تطبيق بحاجة إلى تحديث", "app_not_correctly_installed": "يبدو أن التطبيق {app:s} لم يتم تنصيبه بشكل صحيح", "app_not_installed": "إنّ التطبيق {app:s} غير مُنصَّب", "app_not_properly_removed": "لم يتم حذف تطبيق {app:s} بشكلٍ جيّد", @@ -23,22 +20,15 @@ "app_upgrade_failed": "تعذرت عملية ترقية {app:s}", "app_upgrade_some_app_failed": "تعذرت عملية ترقية بعض التطبيقات", "app_upgraded": "تم تحديث التطبيق {app:s}", - "appslist_fetched": "تم جلب قائمة تطبيقات {appslist:s}", - "appslist_removed": "تم حذف قائمة التطبيقات {appslist:s}", - "appslist_unknown": "قائمة التطبيقات {appslist:s} مجهولة.", - "ask_current_admin_password": "كلمة السر الإدارية الحالية", "ask_email": "عنوان البريد الإلكتروني", "ask_firstname": "الإسم", "ask_lastname": "اللقب", - "ask_list_to_remove": "القائمة المختارة للحذف", "ask_main_domain": "النطاق الرئيسي", "ask_new_admin_password": "كلمة السر الإدارية الجديدة", "ask_password": "كلمة السر", - "ask_path": "المسار", "backup_applying_method_copy": "جارٍ نسخ كافة الملفات إلى النسخة الإحتياطية …", "backup_applying_method_tar": "جارٍ إنشاء ملف TAR للنسخة الاحتياطية…", "backup_created": "تم إنشاء النسخة الإحتياطية", - "backup_creating_archive": "جارٍ إنشاء ملف النسخة الاحتياطية…", "backup_invalid_archive": "نسخة إحتياطية غير صالحة", "backup_method_copy_finished": "إنتهت عملية النسخ الإحتياطي", "backup_nothings_done": "ليس هناك أي شيء للحفظ", @@ -49,14 +39,11 @@ "certmanager_cert_signing_failed": "فشل إجراء توقيع الشهادة الجديدة", "certmanager_domain_unknown": "النطاق مجهول {domain:s}", "certmanager_no_cert_file": "تعذرت عملية قراءة شهادة نطاق {domain:s} (الملف : {file:s})", - "diagnosis_debian_version_error": "لم نتمكن من العثور على إصدار ديبيان : {error}", - "diagnosis_no_apps": "لم تقم بتنصيب أية تطبيقات بعد", "domain_created": "تم إنشاء النطاق", "domain_creation_failed": "تعذرت عملية إنشاء النطاق", "domain_deleted": "تم حذف النطاق", "domain_exists": "اسم النطاق موجود مِن قبل", "domain_unknown": "النطاق مجهول", - "domain_zone_exists": "ملف منطقة أسماء النطاقات موجود مِن قبل", "domains_available": "النطاقات المتوفرة :", "done": "تم", "downloading": "عملية التنزيل جارية …", @@ -72,15 +59,11 @@ "migrate_tsig_wait_3": "دقيقة واحدة …", "migrate_tsig_wait_4": "30 ثانية …", "migrations_skip_migration": "جارٍ تجاهل التهجير {id}…", - "packages_no_upgrade": "لا يوجد هناك أية حزمة بحاجة إلى تحديث", "pattern_domain": "يتوجب أن يكون إسم نطاق صالح (مثل my-domain.org)", "pattern_email": "يتوجب أن يكون عنوان بريد إلكتروني صالح (مثل someone@domain.org)", "pattern_password": "يتوجب أن تكون مكونة من 3 حروف على الأقل", - "pattern_port": "يجب أن يكون رقم منفذ صالح (مثال 0-65535)", "pattern_positive_number": "يجب أن يكون عددا إيجابيا", - "port_available": "المنفذ {port:d} متوفر", "restore_extracting": "جارٍ فك الضغط عن الملفات التي نحتاجها من النسخة الاحتياطية…", - "restore_mounting_archive": "تنصيب النسخة الإحتياطية على المسار '{path:s}'", "server_shutdown": "سوف ينطفئ الخادوم", "server_shutdown_confirm": "سوف ينطفئ الخادوم حالا. متأكد ؟ [{answers:s}]", "server_reboot": "سيعاد تشغيل الخادوم", @@ -89,7 +72,6 @@ "service_already_stopped": "إنّ خدمة '{service:s}' متوقفة مِن قبلُ", "service_disabled": "لن يتم إطلاق خدمة '{service:s}' أثناء بداية تشغيل النظام.", "service_enabled": "تم تنشيط خدمة '{service:s}'", - "service_no_log": "ليس لخدمة '{service:s}' أي سِجلّ للعرض", "service_removed": "تمت إزالة خدمة '{service:s}'", "service_started": "تم إطلاق تشغيل خدمة '{service:s}'", "service_stopped": "تمّ إيقاف خدمة '{service:s}'", @@ -115,19 +97,14 @@ "migration_0003_fail2ban_upgrade": "بداية عملية تحديث Fail2Ban…", "migration_0003_not_jessie": "إن توزيعة ديبيان الحالية تختلف عن جيسي !", "migration_description_0002_migrate_to_tsig_sha256": "يقوم بتحسين أمان TSIG لنظام أسماء النطاقات الديناميكة باستخدام SHA512 بدلًا مِن MD5", - "migration_0003_backward_impossible": "لا يُمكن إلغاء عملية الإنتقال إلى ستريتش.", "migration_0003_system_not_fully_up_to_date": "إنّ نظامك غير مُحدَّث بعدُ لذا يرجى القيام بتحديث عادي أولا قبل إطلاق إجراء الإنتقال إلى نظام ستريتش.", "migrations_list_conflict_pending_done": "لا يمكنك استخدام --previous و --done معًا على نفس سطر الأوامر.", "service_description_avahi-daemon": "يسمح لك بالنفاذ إلى خادومك عبر الشبكة المحلية باستخدام yunohost.local", - "service_description_glances": "يقوم بمراقبة معلومات النظام على خادومك", "service_description_metronome": "يُدير حسابات الدردشة الفورية XMPP", "service_description_nginx": "يقوم بتوفير النفاذ و السماح بالوصول إلى كافة مواقع الويب المستضافة على خادومك", - "service_description_php5-fpm": "يقوم بتشغيل تطبيقات الـ PHP مع خادوم الويب nginx", "service_description_postfix": "يقوم بإرسال و تلقي الرسائل البريدية الإلكترونية", "service_description_yunohost-api": "يقوم بإدارة التفاعلات ما بين واجهة الويب لواي يونوهوست و النظام", "log_category_404": "فئةالسجل '{category}' لا وجود لها", - "log_app_fetchlist": "إضافة قائمة للتطبيقات", - "log_app_removelist": "حذف قائمة للتطبيقات", "log_app_change_url": "تعديل رابط تطبيق '{}'", "log_app_install": "تنصيب تطبيق '{}'", "log_app_remove": "حذف تطبيق '{}'", @@ -144,7 +121,6 @@ "log_letsencrypt_cert_install": "تنصيب شهادة Let’s Encrypt على النطاق '{}'", "log_selfsigned_cert_install": "تنصيب شهادة موقَّعَة ذاتيا على اسم النطاق '{}'", "log_letsencrypt_cert_renew": "تجديد شهادة Let's Encrypt لـ '{}'", - "log_service_enable": "تنشيط خدمة '{}'", "log_user_create": "إضافة المستخدم '{}'", "log_user_delete": "حذف المستخدم '{}'", "log_user_update": "تحديث معلومات المستخدم '{}'", @@ -169,10 +145,8 @@ "ask_new_path": "مسار جديد", "global_settings_setting_security_password_admin_strength": "قوة الكلمة السرية الإدارية", "global_settings_setting_security_password_user_strength": "قوة الكلمة السرية للمستخدم", - "log_app_addaccess": "إضافة ترخيص بالنفاذ إلى '{}'", "password_too_simple_1": "يجب أن يكون طول الكلمة السرية على الأقل 8 حروف", "service_description_php7.0-fpm": "يُشغّل التطبيقات المكتوبة بلغة الـ PHP على NGINX", - "updating_app_lists": "جارٍ جلب التحديثات المتوفرة الخاصة بالتطبيقات…", "already_up_to_date": "كل شيء على ما يرام. ليس هناك ما يتطلّب تحديثًا.", "service_description_nslcd": "يدير اتصال متسخدمي واي يونوهوست عبر طرفية سطر الأوامر", "service_description_slapd": "يخزّن المستخدمين والنطاقات والمعلومات المتعلقة بها", @@ -182,12 +156,9 @@ "group_deletion_failed": "فشلت عملية حذف الفريق '{group}': {error}", "group_deleted": "تم حذف الفريق '{group}'", "group_created": "تم إنشاء الفريق '{group}'", - "group_name_already_exist": "الفريق {name:s} موجود بالفعل", - "error_when_removing_sftpuser_group": "حدث خطأ أثناء محاولة حذف فريق sftpusers", "dyndns_could_not_check_available": "لا يمكن التحقق مِن أنّ {domain:s} متوفر على {provider:s}.", "backup_mount_archive_for_restore": "جارٍ تهيئة النسخة الاحتياطية للاسترجاع…", "root_password_replaced_by_admin_password": "لقد تم استبدال كلمة سر الجذر root بالكلمة الإدارية لـ admin.", - "app_upgrade_stopped": "لقد تم إلغاء تحديث كافة التطبيقات لتجنب حادث بسبب فشل تحديث التطبيق السابق", "app_action_broke_system": "يبدو أنّ هذا الإجراء أدّى إلى تحطيم هذه الخدمات المهمة: {services}", "diagnosis_basesystem_host": "هذا الخادم يُشغّل ديبيان {debian_version}", "diagnosis_basesystem_kernel": "هذا الخادم يُشغّل نواة لينكس {kernel_version}", @@ -202,4 +173,4 @@ "app_remove_after_failed_install": "جارٍ حذف التطبيق بعدما فشل تنصيبها…", "apps_catalog_updating": "جارٍ تحديث فهرس التطبيقات…", "apps_catalog_update_success": "تم تحديث فهرس التطبيقات!" -} +} \ No newline at end of file diff --git a/locales/bn_BD.json b/locales/bn_BD.json index b5425128d..c912ef50a 100644 --- a/locales/bn_BD.json +++ b/locales/bn_BD.json @@ -1,3 +1,3 @@ { "password_too_simple_1": "পাসওয়ার্ডটি কমপক্ষে 8 টি অক্ষরের দীর্ঘ হওয়া দরকার" -} +} \ No newline at end of file diff --git a/locales/br.json b/locales/br.json index 0967ef424..9e26dfeeb 100644 --- a/locales/br.json +++ b/locales/br.json @@ -1 +1 @@ -{} +{} \ No newline at end of file diff --git a/locales/ca.json b/locales/ca.json index 2b482ca3a..175543a13 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -9,28 +9,21 @@ "app_argument_choice_invalid": "Utilitzeu una de les opcions «{choices:s}» per l'argument «{name:s}»", "app_argument_invalid": "Escolliu un valor vàlid per l'argument «{name:s}»: {error:s}", "app_argument_required": "Es necessita l'argument '{name:s}'", - "app_change_no_change_url_script": "L'aplicació {app_name:s} encara no permet canviar la seva URL, es possible que s'hagi d'actualitzar.", "app_change_url_failed_nginx_reload": "No s'ha pogut tornar a carregar NGINX. Aquí teniu el resultat de \"nginx -t\":\n{nginx_errors:s}", "app_change_url_identical_domains": "L'antic i el nou domini/camí són idèntics ('{domain:s}{path:s}'), no hi ha res per fer.", "app_change_url_no_script": "L'aplicació '{app_name:s}' encara no permet modificar la URL. Potser s'ha d'actualitzar.", "app_change_url_success": "La URL de {app:s} ara és {domain:s}{path:s}", "app_extraction_failed": "No s'han pogut extreure els fitxers d'instal·lació", "app_id_invalid": "ID de l'aplicació incorrecte", - "app_incompatible": "L'aplicació {app} no és compatible amb la teva versió de YunoHost", "app_install_files_invalid": "Aquests fitxers no es poden instal·lar", - "app_location_already_used": "L'aplicació «{app}» ja està instal·lada en ({path})", "app_make_default_location_already_used": "No es pot fer l'aplicació '{app}' per defecte en el domini «{domain}» ja que ja és utilitzat per una altra aplicació '{other_app}'", - "app_location_install_failed": "No s'ha pogut instal·lar l'aplicació aquí ja que entra en conflicte amb l'aplicació «{other_app}» ja instal·lada a «{other_path}»", "app_location_unavailable": "Aquesta URL no està disponible o entra en conflicte amb aplicacions ja instal·lades:\n{apps:s}", "app_manifest_invalid": "Hi ha algun error amb el manifest de l'aplicació: {error}", - "app_no_upgrade": "No hi ha cap aplicació per actualitzar", "app_not_correctly_installed": "{app:s} sembla estar mal instal·lada", "app_not_installed": "No s'ha trobat l'aplicació «{app:s}» en la llista d'aplicacions instal·lades: {all_apps}", "app_not_properly_removed": "{app:s} no s'ha pogut suprimir correctament", - "app_package_need_update": "El paquet de l'aplicació {app} ha de ser actualitzat per poder seguir els canvis de YunoHost", "app_removed": "{app:s} ha estat suprimida", "app_requirements_checking": "Verificació dels paquets requerits per {app}…", - "app_requirements_failed": "No es poden satisfer els requeriments per {app}: {error}", "app_requirements_unmeet": "No es compleixen els requeriments per {app}, el paquet {pkgname} ({version}) ha de ser {spec}", "app_sources_fetch_failed": "No s'han pogut carregar els fitxers font, l'URL és correcta?", "app_unknown": "Aplicació desconeguda", @@ -39,27 +32,13 @@ "app_upgrade_failed": "No s'ha pogut actualitzar {app:s}: {error}", "app_upgrade_some_app_failed": "No s'han pogut actualitzar algunes aplicacions", "app_upgraded": "S'ha actualitzat {app:s}", - "appslist_corrupted_json": "No s'han pogut carregar les llistes d'aplicacions. Sembla que {filename:s} està danyat.", - "appslist_could_not_migrate": "No s'ha pogut migrar la llista d'aplicacions «{appslist:s}»! No s'ha pogut analitzar la URL... L'antic cronjob s'ha guardat a {bkp_file:s}.", - "appslist_fetched": "S'ha actualitzat la llista d'aplicacions «{appslist:s}»", - "appslist_migrating": "Migrant la llista d'aplicacions «{appslist:s}»…", - "appslist_name_already_tracked": "Ja hi ha una llista d'aplicacions registrada amb el nom {name:s}.", - "appslist_removed": "S'ha eliminat la llista d'aplicacions «{appslist:s}»", - "appslist_retrieve_bad_format": "No s'ha pogut llegir la llista d'aplicacions obtinguda «{appslist:s}»", - "appslist_retrieve_error": "No s'ha pogut obtenir la llista d'aplicacions remota «{appslist:s}»: {error:s}", - "appslist_unknown": "La llista d'aplicacions «{appslist:s}» es desconeguda.", - "appslist_url_already_tracked": "Ja hi ha una llista d'aplicacions registrada amb al URL {url:s}.", - "ask_current_admin_password": "Contrasenya d'administrador actual", "ask_email": "Adreça de correu electrònic", "ask_firstname": "Nom", "ask_lastname": "Cognom", - "ask_list_to_remove": "Llista per a suprimir", "ask_main_domain": "Domini principal", "ask_new_admin_password": "Nova contrasenya d'administrador", "ask_password": "Contrasenya", - "ask_path": "Camí", "backup_abstract_method": "Encara està per implementar aquest mètode de còpia de seguretat", - "backup_action_required": "S'ha d'especificar què s'ha de guardar", "backup_app_failed": "No s'ha pogut fer la còpia de seguretat de l'aplicació \"{app:s}\"", "backup_applying_method_borg": "Enviant tots els fitxers de la còpia de seguretat al repositori borg-backup…", "backup_applying_method_copy": "Còpia de tots els fitxers a la còpia de seguretat…", @@ -67,7 +46,6 @@ "backup_applying_method_tar": "Creació de l'arxiu TAR de la còpia de seguretat…", "backup_archive_app_not_found": "No s'ha pogut trobar l'aplicació «{app:s}» dins l'arxiu de la còpia de seguretat", "backup_archive_broken_link": "No s'ha pogut accedir a l'arxiu de la còpia de seguretat (enllaç invàlid cap a {path:s})", - "backup_archive_mount_failed": "No s'ha pogut carregar l'arxiu de la còpia de seguretat", "backup_archive_name_exists": "Ja hi ha una còpia de seguretat amb aquest nom.", "backup_archive_name_unknown": "Còpia de seguretat local \"{name:s}\" desconeguda", "backup_archive_open_failed": "No s'ha pogut obrir l'arxiu de la còpia de seguretat", @@ -80,7 +58,6 @@ "backup_copying_to_organize_the_archive": "Copiant {size:s}MB per organitzar l'arxiu", "backup_couldnt_bind": "No es pot lligar {src:s} amb {dest:s}.", "backup_created": "S'ha creat la còpia de seguretat", - "backup_creating_archive": "Creant l'arxiu de la còpia de seguretat…", "aborting": "Avortant.", "app_not_upgraded": "L'aplicació «{failed_app}» no s'ha pogut actualitzar, i com a conseqüència l'actualització de les següents aplicacions ha estat cancel·lada: {apps}", "app_start_install": "instal·lant l'aplicació «{app}»…", @@ -96,10 +73,8 @@ "backup_csv_creation_failed": "No s'ha pogut crear el fitxer CSV necessari per a la restauració", "backup_custom_backup_error": "El mètode de còpia de seguretat personalitzat ha fallat a l'etapa «backup»", "backup_custom_mount_error": "El mètode de còpia de seguretat personalitzat ha fallat a l'etapa «mount»", - "backup_custom_need_mount_error": "El mètode de còpia de seguretat personalitzat ha fallat a l'etapa \"need_mount\"", "backup_delete_error": "No s'ha pogut suprimir «{path:s}»", "backup_deleted": "S'ha suprimit la còpia de seguretat", - "backup_extracting_archive": "Extraient l'arxiu de la còpia de seguretat…", "backup_hook_unknown": "Script de còpia de seguretat «{hook:s}» desconegut", "backup_invalid_archive": "Aquest no és un arxiu de còpia de seguretat", "backup_method_borg_finished": "La còpia de seguretat a Borg ha acabat", @@ -140,7 +115,6 @@ "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_not_resolved_locally": "El domini {domain:s} no es pot resoldre dins del vostre servidor YunoHost. Això pot passar si heu modificat recentment el registre DNS. Si és així, si us plau espereu unes hores per a que es propagui. Si el problema continua, considereu afegir {domain:s} a /etc/hosts. (Si sabeu el que esteu fent, podeu utilitzar --no-checks per desactivar aquestes comprovacions.)", "certmanager_domain_unknown": "Domini desconegut «{domain:s}»", "certmanager_error_no_A_record": "No s'ha trobat cap registre DNS «A» per «{domain:s}». Heu de fer que el vostre nom de domini apunti cap a la vostra màquina per tal de poder instal·lar un certificat Let's Encrypt. (Si sabeu el que esteu fent, podeu utilitzar «--no-checks» per desactivar aquestes comprovacions.)", "certmanager_hit_rate_limit": "S'han emès massa certificats recentment per aquest mateix conjunt de dominis {domain:s}. Si us plau torneu-ho a intentar més tard. Consulteu https://letsencrypt.org/docs/rate-limits/ per obtenir més detalls", @@ -152,16 +126,8 @@ "confirm_app_install_danger": "PERILL! Aquesta aplicació encara és experimental (si no és que no funciona directament)! No hauríeu d'instal·lar-la a no ser que sapigueu el que feu. No obtindreu CAP AJUDA si l'aplicació no funciona o trenca el sistema… Si accepteu el risc, escriviu «{answers:s}»", "confirm_app_install_thirdparty": "PERILL! Aquesta aplicació no es part del catàleg d'aplicacions de YunoHost. La instal·lació d'aplicacions de terceres parts pot comprometre la integritat i seguretat del seu sistema. No hauríeu d'instal·lar-ne a no ser que sapigueu el que feu. No obtindreu CAP AJUDA si l'aplicació no funciona o trenca el sistema… Si accepteu el risc, escriviu «{answers:s}»", "custom_app_url_required": "Heu de especificar una URL per actualitzar la vostra aplicació personalitzada {app:s}", - "custom_appslist_name_required": "Heu d'especificar un nom per la vostra llista d'aplicacions personalitzada", - "diagnosis_debian_version_error": "No s'ha pogut obtenir la versió Debian: {error}", - "diagnosis_kernel_version_error": "No s'ha pogut obtenir la versió del nucli: {error}", - "diagnosis_monitor_disk_error": "No es poden monitorar els discs: {error}", - "diagnosis_monitor_network_error": "No es pot monitorar la xarxa: {error}", - "diagnosis_monitor_system_error": "No es pot monitorar el sistema: {error}", - "diagnosis_no_apps": "Aquesta aplicació no està instal·lada", "admin_password_too_long": "Trieu una contrasenya de menys de 127 caràcters", "dpkg_is_broken": "No es pot fer això en aquest instant perquè dpkg/APT (els gestors de paquets del sistema) sembla estar mal configurat… Podeu intentar solucionar-ho connectant-vos per SSH i executant «sudo dpkg --configure -a».", - "dnsmasq_isnt_installed": "sembla que dnsmasq no està instal·lat, executeu \"apt-get remove bind9 && apt-get install dnsmasq\"", "domain_cannot_remove_main": "No es pot eliminar «{domain:s}» ja que és el domini principal, primer s'ha d'establir un nou domini principal utilitzant «yunohost domain main-domain -n »; aquí hi ha una llista dels possibles dominis: {other_domains:s}", "domain_cert_gen_failed": "No s'ha pogut generar el certificat", "domain_created": "S'ha creat el domini", @@ -172,14 +138,10 @@ "app_action_cannot_be_ran_because_required_services_down": "Aquests serveis necessaris haurien d'estar funcionant per poder executar aquesta acció: {services} Intenteu reiniciar-los per continuar (i possiblement investigar perquè estan aturats).", "domain_dns_conf_is_just_a_recommendation": "Aquesta ordre mostra la configuració *recomanada*. En cap cas fa la configuració del DNS. És la vostra responsabilitat configurar la zona DNS en el vostre registrar en acord amb aquesta recomanació.", "domain_dyndns_already_subscribed": "Ja us heu subscrit a un domini DynDNS", - "domain_dyndns_dynette_is_unreachable": "No s'ha pogut abastar la dynette YunoHost, o bé YunoHost no està connectat a internet correctament o bé el servidor dynette està caigut. Error: {error}", - "domain_dyndns_invalid": "Domini no vàlid per utilitzar amb 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_unknown": "Domini desconegut", - "domain_zone_exists": "El fitxer de zona DNS ja existeix", - "domain_zone_not_found": "No s'ha trobat el fitxer de zona DNS pel domini {:s}", "domains_available": "Dominis disponibles:", "done": "Fet", "downloading": "Descarregant…", @@ -206,7 +168,6 @@ "firewall_reload_failed": "No s'ha pogut tornar a carregar el tallafocs", "firewall_reloaded": "S'ha tornat a carregar el tallafocs", "firewall_rules_cmd_failed": "No s'han pogut aplicar algunes regles del tallafocs. Més informació en el registre.", - "format_datetime_short": "%d/%m/%Y %H:%M", "global_settings_bad_choice_for_enum": "Opció pel paràmetre {setting:s} incorrecta, s'ha rebut «{choice:s}», però les opcions disponibles són: {available_choices:s}", "global_settings_bad_type_for_setting": "El tipus del paràmetre {setting:s} és incorrecte. S'ha rebut {received_type:s}, però s'esperava {expected_type:s}", "global_settings_cant_open_settings": "No s'ha pogut obrir el fitxer de configuració, raó: {reason:s}", @@ -233,7 +194,6 @@ "hook_name_unknown": "Nom de script « {name:s} » desconegut", "installation_complete": "Instal·lació completada", "installation_failed": "Ha fallat alguna cosa amb la instal·lació", - "invalid_url_format": "Format d'URL invàlid", "ip6tables_unavailable": "No podeu modificar les ip6tables aquí. O bé sou en un contenidor o bé el vostre nucli no és compatible amb aquesta opció", "iptables_unavailable": "No podeu modificar les iptables aquí. O bé sou en un contenidor o bé el vostre nucli no és compatible amb aquesta opció", "log_corrupted_md_file": "El fitxer de metadades YAML associat amb els registres està malmès: « {md_file} »\nError: {error}", @@ -244,11 +204,6 @@ "log_help_to_get_failed_log": "No s'ha pogut completar l'operació « {desc} ». Per obtenir ajuda, compartiu el registre complete de l'operació utilitzant l'ordre « yunohost log display {name} --share »", "log_does_exists": "No hi ha cap registre per l'operació amb el nom« {log} », utilitzeu « yunohost log list » per veure tots els registre d'operació disponibles", "log_operation_unit_unclosed_properly": "L'operació no s'ha tancat de forma correcta", - "log_app_addaccess": "Afegir accés a « {} »", - "log_app_removeaccess": "Suprimeix accés a « {} »", - "log_app_clearaccess": "Suprimeix tots els accessos a « {} »", - "log_app_fetchlist": "Afegeix una llista d'aplicacions", - "log_app_removelist": "Elimina una llista d'aplicacions", "log_app_change_url": "Canvia l'URL de l'aplicació « {} »", "log_app_install": "Instal·la l'aplicació « {} »", "log_app_remove": "Elimina l'aplicació « {} »", @@ -266,14 +221,12 @@ "log_letsencrypt_cert_install": "Instal·la un certificat Let's Encrypt al domini « {} »", "log_selfsigned_cert_install": "Instal·la el certificat autosignat al domini « {} »", "log_letsencrypt_cert_renew": "Renova el certificat Let's Encrypt de « {} »", - "log_service_enable": "Activa el servei « {} »", "log_regen_conf": "Regenera la configuració del sistema « {} »", "log_user_create": "Afegeix l'usuari « {} »", "log_user_delete": "Elimina l'usuari « {} »", "log_user_update": "Actualitza la informació de l'usuari « {} »", "log_domain_main_domain": "Fes de « {} » el domini principal", "log_tools_migrations_migrate_forward": "Executa les migracions", - "log_tools_migrations_migrate_backward": "Migrar endarrera", "log_tools_postinstall": "Fer la post instal·lació del servidor YunoHost", "log_tools_upgrade": "Actualitza els paquets del sistema", "log_tools_shutdown": "Apaga el servidor", @@ -283,7 +236,6 @@ "global_settings_setting_security_postfix_compatibility": "Solució de compromís entre compatibilitat i seguretat pel servidor Postfix. Afecta els criptògrafs (i altres aspectes relacionats amb la seguretat)", "ldap_init_failed_to_create_admin": "La inicialització de LDAP no ha pogut crear l'usuari admin", "ldap_initialized": "S'ha iniciat LDAP", - "license_undefined": "indefinit", "mail_alias_remove_failed": "No s'han pogut eliminar els àlies del correu «{mail:s}»", "mail_domain_unknown": "El domini «{domain:s}» de l'adreça de correu no és vàlid. Utilitzeu un domini administrat per aquest servidor.", "mail_forward_remove_failed": "No s'han pogut eliminar el reenviament de correu «{mail:s}»", @@ -309,7 +261,6 @@ "migration_description_0008_ssh_conf_managed_by_yunohost_step2": "La configuració SSH serà gestionada per YunoHost (pas 2, manual)", "migration_description_0009_decouple_regenconf_from_services": "Desvincula el mecanisme regen-conf dels serveis", "migration_description_0010_migrate_to_apps_json": "Elimina els catàlegs d'aplicacions obsolets i utilitza la nova llista unificada «apps.json» en el seu lloc (obsolet, substituït per la migració 13)", - "migration_0003_backward_impossible": "La migració Stretch no és reversible.", "migration_0003_start": "Ha començat la migració a Stretch. Els registres estaran disponibles a {logfile}.", "migration_0003_patching_sources_list": "Modificant el fitxer sources.lists…", "migration_0003_main_upgrade": "Començant l'actualització principal…", @@ -335,67 +286,31 @@ "migration_0008_warning": "Si heu entès els avisos i voleu que YunoHost sobreescrigui la configuració actual, comenceu la migració. Sinó, podeu saltar-vos la migració, tot i que no està recomanat.", "migration_0008_no_warning": "Hauria de ser segur sobreescriure la configuració SSH, però no es pot estar del tot segur! Executetu la migració per sobreescriure-la. Sinó, podeu saltar-vos la migració, tot i que no està recomanat.", "migration_0009_not_needed": "Sembla que ja s'ha fet aquesta migració… (?) Ometent.", - "migrations_backward": "Migració cap enrere.", - "migrations_bad_value_for_target": "Nombre invàlid pel paràmetre target, els nombres de migració disponibles són 0 o {}", "migrations_cant_reach_migration_file": "No s'ha pogut accedir als fitxers de migració al camí «%s»", - "migrations_current_target": "La migració objectiu és {}", - "migrations_error_failed_to_load_migration": "ERROR: no s'ha pogut carregar la migració {number} {name}", - "migrations_forward": "Migració endavant", "migrations_list_conflict_pending_done": "No es pot utilitzar «--previous» i «--done» al mateix temps.", "migrations_loading_migration": "Carregant la migració {id}…", "migrations_migration_has_failed": "La migració {id} ha fallat, cancel·lant. Error: {exception}", "migrations_no_migrations_to_run": "No hi ha cap migració a fer", - "migrations_show_currently_running_migration": "Fent la migració {number} {name}…", - "migrations_show_last_migration": "L'última migració feta és {}", "migrations_skip_migration": "Saltant migració {id}…", - "migrations_success": "S'ha completat la migració {number} {name} amb èxit!", "migrations_to_be_ran_manually": "La migració {id} s'ha de fer manualment. Aneu a Eines → Migracions a la interfície admin, o executeu «yunohost tools migrations migrate».", "migrations_need_to_accept_disclaimer": "Per fer la migració {id}, heu d'acceptar aquesta clàusula de no responsabilitat:\n---\n{disclaimer}\n---\nSi accepteu fer la migració, torneu a executar l'ordre amb l'opció «--accept-disclaimer».", - "monitor_disabled": "S'ha desactivat el monitoratge del servidor", - "monitor_enabled": "S'ha activat el monitoratge del sistema", - "monitor_glances_con_failed": "No s'ha pogut connectar al servidor Glances", - "monitor_not_enabled": "El monitoratge del servidor no està activat", - "monitor_period_invalid": "Període de temps invàlid", - "monitor_stats_file_not_found": "No s'ha pogut trobar el fitxer d'estadístiques", - "monitor_stats_no_update": "No hi ha dades de monitoratge per actualitzar", - "monitor_stats_period_unavailable": "No s'han trobat estadístiques per aquest període", - "mountpoint_unknown": "Punt de muntatge desconegut", - "mysql_db_creation_failed": "No s'ha pogut crear la base de dades MySQL", - "mysql_db_init_failed": "No s'ha pogut inicialitzar la base de dades MySQL", - "mysql_db_initialized": "S'ha inicialitzat la base de dades MySQL", - "network_check_mx_ko": "El registre DNS MX no està configurat", - "network_check_smtp_ko": "El tràfic de correu sortint (SMTP port 25) sembla que està bloquejat per la xarxa", - "network_check_smtp_ok": "El tràfic de correu sortint (SMTP port 25) no està bloquejat", - "new_domain_required": "S'ha d'especificar un nou domini principal", - "no_appslist_found": "No s'ha trobat cap llista d'aplicacions", "no_internet_connection": "El servidor no està connectat a Internet", - "no_ipv6_connectivity": "La connectivitat IPv6 no està disponible", - "no_restore_script": "No hi ha cap script de restauració per l'aplicació «{app:s}»", "not_enough_disk_space": "No hi ha prou espai en «{path:s}»", - "package_not_installed": "El paquet «{pkgname}» no està instal·lat", - "package_unexpected_error": "Hi ha hagut un error inesperat processant el paquet «{pkgname}»", "package_unknown": "Paquet desconegut «{pkgname}»", - "packages_upgrade_critical_later": "Els paquets crítics ({packages:s}) seran actualitzats més tard", "packages_upgrade_failed": "No s'han pogut actualitzar tots els paquets", - "path_removal_failed": "No s'ha pogut eliminar el camí {:s}", "pattern_backup_archive_name": "Ha de ser un nom d'arxiu vàlid amb un màxim de 30 caràcters, compost per caràcters alfanumèrics i -_. exclusivament", "pattern_domain": "Ha de ser un nom de domini vàlid (ex.: el-meu-domini.cat)", "pattern_email": "Ha de ser una adreça de correu vàlida (ex.: algu@domini.cat)", "pattern_firstname": "Ha de ser un nom vàlid", "pattern_lastname": "Ha de ser un cognom vàlid", - "pattern_listname": "Ha d'estar compost per caràcters alfanumèrics i guió baix exclusivament", "pattern_mailbox_quota": "Ha de ser una mida amb el sufix b/k/M/G/T o 0 per no tenir quota", "pattern_password": "Ha de tenir un mínim de 3 caràcters", - "pattern_port": "Ha de ser un número de port vàlid (i.e. 0-65535)", "pattern_port_or_range": "Ha de ser un número de port vàlid (i.e. 0-65535) o un interval de ports (ex. 100:200)", "pattern_positive_number": "Ha de ser un nombre positiu", "pattern_username": "Ha d'estar compost per caràcters alfanumèrics en minúscula i guió baix exclusivament", "pattern_password_app": "Les contrasenyes no poden de tenir els següents caràcters: {forbidden_chars}", "port_already_closed": "El port {port:d} ja està tancat per les connexions {ip_version:s}", "port_already_opened": "El port {port:d} ja està obert per les connexions {ip_version:s}", - "port_available": "El port {port:d} està disponible", - "port_unavailable": "El port {port:d} no està disponible", - "recommend_to_add_first_user": "La post instal·lació s'ha acabat, però YunoHost necessita com a mínim un usuari per funcionar correctament, hauríeu d'afegir un usuari executant «yunohost user create »; o fer-ho des de la interfície d'administració.", "regenconf_file_backed_up": "S'ha guardat una còpia de seguretat del fitxer de configuració «{conf}» a «{backup}»", "regenconf_file_copy_failed": "No s'ha pogut copiar el nou fitxer de configuració «{new}» a «{conf}»", "regenconf_file_kept_back": "S'espera que el fitxer de configuració «{conf}» sigui suprimit per regen-conf (categoria {category}) però s'ha mantingut.", @@ -411,7 +326,6 @@ "regenconf_dry_pending_applying": "Verificació de la configuració pendent que s'hauria d'haver aplicat per la categoria «{category}»…", "regenconf_failed": "No s'ha pogut regenerar la configuració per la/les categoria/es : {categories}", "regenconf_pending_applying": "Aplicació de la configuració pendent per la categoria «{category}»…", - "restore_action_required": "S'ha d'especificar quelcom a restaurar", "restore_already_installed_app": "Una aplicació amb la ID «{app:s}» ja està instal·lada", "restore_app_failed": "No s'ha pogut restaurar l'aplicació «{app:s}»", "restore_cleaning_failed": "No s'ha pogut netejar el directori temporal de restauració", @@ -421,7 +335,6 @@ "restore_failed": "No s'ha pogut restaurar el sistema", "restore_hook_unavailable": "El script de restauració «{part:s}» no està disponible en el sistema i tampoc és en l'arxiu", "restore_may_be_not_enough_disk_space": "Sembla que no hi ha prou espai disponible en el sistema (lliure: {free_space:d} B, espai necessari: {needed_space:d} B, marge de seguretat: {margin:d} B)", - "restore_mounting_archive": "Muntatge de l'arxiu a «{path:s}»", "restore_not_enough_disk_space": "No hi ha prou espai disponible (espai: {free_space:d} B, espai necessari: {needed_space:d} B, marge de seguretat: {margin:d} B)", "restore_nothings_done": "No s'ha restaurat res", "restore_removing_tmp_dir_failed": "No s'ha pogut eliminar un directori temporal antic", @@ -443,7 +356,6 @@ "service_description_dnsmasq": "Gestiona la resolució del nom de domini (DNS)", "service_description_dovecot": "Permet als clients de correu accedir/recuperar correus (via IMAP i POP3)", "service_description_fail2ban": "Protegeix contra els atacs de força bruta i a altres atacs provinents d'Internet", - "service_description_glances": "Monitora la informació del sistema en el servidor", "service_description_metronome": "Gestiona els comptes de missatgeria instantània XMPP", "service_description_mysql": "Guarda les dades de les aplicacions (base de dades SQL)", "service_description_nginx": "Serveix o permet l'accés a totes les pàgines web allotjades en el servidor", @@ -451,7 +363,6 @@ "service_description_php7.0-fpm": "Executa les aplicacions escrites en PHP amb NGINX", "service_description_postfix": "Utilitzat per enviar i rebre correus", "service_description_redis-server": "Una base de dades especialitzada per l'accés ràpid a dades, files d'espera i comunicació entre programes", - "service_description_rmilter": "Verifica diferents paràmetres en els correus", "service_description_rspamd": "Filtra el correu brossa, i altres funcionalitats relacionades amb el correu", "service_description_slapd": "Guarda el usuaris, dominis i informació relacionada", "service_description_ssh": "Permet la connexió remota al servidor via terminal (protocol SSH)", @@ -461,7 +372,6 @@ "service_disabled": "El servei «{service:s}» ja no començarà al arrancar el sistema.", "service_enable_failed": "No s'ha pogut fer que el servei «{service:s}» comenci automàticament a l'arrancada.\n\nRegistres recents: {logs:s}", "service_enabled": "El servei «{service:s}» començarà automàticament durant l'arrancada del sistema.", - "service_no_log": "No hi ha cap registre pel servei «{service:s}»", "service_regen_conf_is_deprecated": "«yunohost service regen-conf» està desfasat! Utilitzeu «yunohost tools regen-conf» en el seu lloc.", "service_remove_failed": "No s'ha pogut eliminar el servei «{service:s}»", "service_removed": "S'ha eliminat el servei «{service:s}»", @@ -473,14 +383,11 @@ "service_reloaded_or_restarted": "S'ha tornat a carregar o s'ha reiniciat el servei «{service:s}»", "service_start_failed": "No s'ha pogut iniciar el servei «{service:s}»\n\nRegistres recents: {logs:s}", "service_started": "S'ha iniciat el servei «{service:s}»", - "service_status_failed": "No s'ha pogut determinar l'estat del servei «{service:s}»", "service_stop_failed": "No s'ha pogut aturar el servei «{service:s}»\n\nRegistres recents: {logs:s}", "service_stopped": "S'ha aturat el servei «{service:s}»", "service_unknown": "Servei «{service:s}» desconegut", "ssowat_conf_generated": "S'ha generat la configuració SSOwat", "ssowat_conf_updated": "S'ha actualitzat la configuració SSOwat", - "ssowat_persistent_conf_read_error": "No s'ha pogut llegir la configuració persistent de SSOwat: {error:s}. Modifiqueu el fitxer /etc/ssowat/conf.json.persistent per arreglar la sintaxi JSON", - "ssowat_persistent_conf_write_error": "No s'ha pogut guardar la configuració persistent de SSOwat: {error:s}. Modifiqueu el fitxer /etc/ssowat/conf.json.persistent per arreglar la sintaxi JSON", "system_upgraded": "S'ha actualitzat el sistema", "system_username_exists": "El nom d'usuari ja existeix en la llista d'usuaris de sistema", "this_action_broke_dpkg": "Aquesta acció a trencat dpkg/APT (els gestors de paquets del sistema)… Podeu intentar resoldre el problema connectant-vos amb SSH i executant «sudo dpkg --configure -a».", @@ -495,13 +402,11 @@ "tools_upgrade_special_packages_completed": "Actualització dels paquets YunoHost acabada.\nPremeu [Enter] per tornar a la línia d'ordres", "unbackup_app": "L'aplicació «{app:s}» no serà guardada", "unexpected_error": "Hi ha hagut un error inesperat: {error}", - "unit_unknown": "Unitat desconeguda «{unit:s}»", "unlimit": "Sense quota", "unrestore_app": "L'aplicació «{app:s} no serà restaurada", "update_apt_cache_failed": "No s'ha pogut actualitzar la memòria cau d'APT (el gestor de paquets de Debian). Aquí teniu les línies de sources.list, que poden ajudar-vos a identificar les línies problemàtiques:\n{sourceslist}", "update_apt_cache_warning": "Hi ha hagut errors al actualitzar la memòria cau d'APT (el gestor de paquets de Debian). Aquí teniu les línies de sources.list que poden ajudar-vos a identificar les línies problemàtiques:\n{sourceslist}", "updating_apt_cache": "Obtenció de les actualitzacions disponibles per als paquets del sistema…", - "updating_app_lists": "Obtenció de les actualitzacions disponibles per a les aplicacions…", "upgrade_complete": "Actualització acabada", "upgrading_packages": "Actualitzant els paquets…", "upnp_dev_not_found": "No s'ha trobat cap dispositiu UPnP", @@ -513,7 +418,6 @@ "user_deleted": "S'ha suprimit l'usuari", "user_deletion_failed": "No s'ha pogut suprimir l'usuari {user}: {error}", "user_home_creation_failed": "No s'ha pogut crear la carpeta personal «home» per l'usuari", - "user_info_failed": "No s'ha pogut obtenir la informació de l'usuari", "user_unknown": "Usuari desconegut: {user:s}", "user_update_failed": "No s'ha pogut actualitzar l'usuari {user}: {error}", "user_updated": "S'ha canviat la informació de l'usuari", @@ -524,69 +428,41 @@ "yunohost_configured": "YunoHost està configurat", "yunohost_installing": "Instal·lació de YunoHost…", "yunohost_not_installed": "YunoHost no està instal·lat correctament. Executeu «yunohost tools postinstall»", - "apps_permission_not_found": "No s'ha trobat cap permís per les aplicacions instal·lades", - "apps_permission_restoration_failed": "Ha fallat el permís «{permission:s}» per la restauració de l'aplicació {app:s}", "backup_permission": "Permís de còpia de seguretat per l'aplicació {app:s}", - "edit_group_not_allowed": "No teniu autorització per modificar el grup {group:s}", - "edit_permission_with_group_all_users_not_allowed": "No podeu modificar els permisos del grup «all_users», s'ha d'utlilitzar «yunohost user permission clear APP» o «yunohost user permission add APP -u USER».", - "error_when_removing_sftpuser_group": "Error intentant eliminar el gruo sftpusers", - "group_already_allowed": "El grup «{group:s}» ja té el permís «{permission:s}» activat per l'aplicació «{app:s}»", - "group_already_disallowed": "El grup «{group:s}» ja té els permisos «{permission:s}» desactivats per l'aplicació «{app:s}»", - "group_name_already_exist": "El grup {name:s} ja existeix", "group_created": "S'ha creat el grup «{group}»", "group_creation_failed": "No s'ha pogut crear el grup «{group}»: {error}", "group_deleted": "S'ha eliminat el grup «{group}»", "group_deletion_failed": "No s'ha pogut eliminar el grup «{group}»: {error}", - "group_deletion_not_allowed": "El grup {group:s} no es pot eliminar manualment.", - "group_info_failed": "Ha fallat la informació del grup", "group_unknown": "Grup {group:s} desconegut", "group_updated": "S'ha actualitzat el grup «{group}»", "group_update_failed": "No s'ha pogut actualitzat el grup «{group}»: {error}", - "log_permission_add": "Afegir el permís «{}» per l'aplicació «{}»", - "log_permission_remove": "Suprimir el permís «{}»", - "log_permission_update": "Actualitzar el permís «{}» per l'aplicació «{}»", - "log_user_group_add": "Afegir grup «{}»", "log_user_group_delete": "Eliminar grup «{}»", "log_user_group_update": "Actualitzar grup «{}»", - "log_user_permission_add": "Actualitzar el permís «{}»", - "log_user_permission_remove": "Actualitzar el permís «{}»", "mailbox_disabled": "La bústia de correu està desactivada per al usuari {user:s}", "migration_description_0011_setup_group_permission": "Configurar els grups d'usuaris i els permisos per les aplicacions i els serveis", "migration_0011_backup_before_migration": "Creant una còpia de seguretat de la base de dades LDAP i la configuració de les aplicacions abans d'efectuar la migració.", "migration_0011_can_not_backup_before_migration": "No s'ha pogut completar la còpia de seguretat abans de que la migració fallés. Error: {error:s}", "migration_0011_create_group": "Creant un grup per a cada usuari…", "migration_0011_done": "Migració completada. Ja podeu gestionar grups d'usuaris.", - "migration_0011_LDAP_config_dirty": "Sembla que heu modificat manualment la configuració LDAP. Per fer aquesta migració s'ha d'actualitzar la configuració LDAP.\nGuardeu la configuració actual, reinicieu la configuració original executant l'ordre «yunohost tools regen-conf -f» i torneu a intentar la migració", "migration_0011_LDAP_update_failed": "Ha fallat l'actualització de LDAP. Error: {error:s}", "migration_0011_migrate_permission": "Fent la migració dels permisos de la configuració de les aplicacions a LDAP…", "migration_0011_migration_failed_trying_to_rollback": "No s'ha pogut fer la migració… s'intenta tornar el sistema a l'estat anterior.", "migration_0011_rollback_success": "S'ha tornat el sistema a l'estat anterior.", "migration_0011_update_LDAP_database": "Actualitzant la base de dades LDAP…", "migration_0011_update_LDAP_schema": "Actualitzant l'esquema LDAP…", - "need_define_permission_before": "Heu de tornar a redefinir els permisos utilitzant «yunohost user permission add -u USER» abans d'eliminar un grup permès", - "permission_already_clear": "Ja s'ha donat el permís «{permission:s}» per l'aplicació {app:s}", "permission_already_exist": "El permís «{permission:s}» ja existeix", "permission_created": "S'ha creat el permís «{permission:s}»", "permission_creation_failed": "No s'ha pogut crear el permís «{permission}»: {error}", "permission_deleted": "S'ha eliminat el permís «{permission:s}»", "permission_deletion_failed": "No s'ha pogut eliminar el permís «{permission:s}»: {error}", "permission_not_found": "No s'ha trobat el permís «{permission:s}»", - "permission_name_not_valid": "El nom del permís «{permission:s}» no és vàlid", "permission_update_failed": "No s'ha pogut actualitzar el permís «{permission}»: {error}", - "permission_generated": "S'ha actualitzat la base de dades del permís", "permission_updated": "S'ha actualitzat el permís «{permission:s}»", "permission_update_nothing_to_do": "No hi ha cap permís per actualitzar", - "remove_main_permission_not_allowed": "No es pot eliminar el permís principal", - "remove_user_of_group_not_allowed": "No es pot eliminar l'usuari {user:s} del grup {group:s}", - "system_groupname_exists": "El nom de grup ja existeix en el sistema de grups", - "tools_update_failed_to_app_fetchlist": "No s'ha pogut actualitzar la llista d'aplicacions de YunoHost a causa de: {error}", - "user_already_in_group": "L'usuari {user:s} ja és en el grup {group:s}", - "user_not_in_group": "L'usuari {user:s} no és en el grup {group:s}", "migration_description_0012_postgresql_password_to_md5_authentication": "Força l'autenticació PostgreSQL a fer servir MD5 per a les connexions locals", "app_full_domain_unavailable": "Aquesta aplicació ha de ser instal·lada en el seu propi domini, però ja hi ha altres aplicacions instal·lades en el domini «{domain}». Podeu utilitzar un subdomini dedicat a aquesta aplicació.", "migrations_not_pending_cant_skip": "Aquestes migracions no estan pendents, així que no poden ser omeses: {ids}", "app_action_broke_system": "Aquesta acció sembla haver trencat els següents serveis importants: {services}", - "log_permission_urls": "Actualitzar les URLs relacionades amb el permís «{}»", "log_user_group_create": "Crear grup «{}»", "log_user_permission_update": "Actualitzar els accessos per al permís «{}»", "log_user_permission_reset": "Restablir el permís «{}»", @@ -605,7 +481,6 @@ "operation_interrupted": "S'ha interromput manualment l'operació?", "group_already_exist": "El grup {group} ja existeix", "group_already_exist_on_system": "El grup {group} ja existeix en els grups del sistema", - "group_cannot_be_edited": "El grup {group} no es pot editar manualment.", "group_cannot_be_deleted": "El grup {group} no es pot eliminar manualment.", "group_user_already_in_group": "L'usuari {user} ja està en el grup {group}", "group_user_not_in_group": "L'usuari {user} no està en el grup {group}", @@ -615,7 +490,6 @@ "permission_already_allowed": "El grup «{group}» ja té el permís «{permission}» activat", "permission_cannot_remove_main": "No es permet eliminar un permís principal", "user_already_exists": "L'usuari «{user}» ja existeix", - "app_upgrade_stopped": "S'ha aturat l'actualització de totes les aplicacions per prevenir possibles danys ja que no s'ha pogut actualitzar una aplicació", "app_install_failed": "No s'ha pogut instal·lar {app}: {error}", "app_install_script_failed": "Hi ha hagut un error en el script d'instal·lació de l'aplicació", "group_cannot_edit_all_users": "El grup «all_users» no es pot editar manualment. És un grup especial destinat a contenir els usuaris registrats a YunoHost", @@ -624,7 +498,6 @@ "log_permission_url": "Actualització de la URL associada al permís «{}»", "migration_0011_slapd_config_will_be_overwritten": "Sembla que heu modificat manualment la configuració de sldap. Per aquesta migració crítica, YunoHost ha de forçar l'actualització de la configuració sldap. Es farà una còpia de seguretat a {conf_backup_folder}.", "permission_already_up_to_date": "No s'ha actualitzat el permís perquè la petició d'afegir/eliminar ja corresponent a l'estat actual.", - "permission_currently_allowed_for_visitors": "El permís ja el tenen el grup de visitants a més d'altres grups. Segurament s'hauria de revocar el permís al grup dels visitants o eliminar els altres grups als que s'ha atribuït.", "permission_currently_allowed_for_all_users": "El permís ha el té el grup de tots els usuaris (all_users) a més d'altres grups. Segurament s'hauria de revocar el permís a «all_users» o eliminar els altres grups als que s'ha atribuït.", "permission_require_account": "El permís {permission} només té sentit per als usuaris que tenen un compte, i per tant no es pot activar per als visitants.", "app_remove_after_failed_install": "Eliminant l'aplicació després que hagi fallat la instal·lació…", @@ -632,7 +505,6 @@ "diagnosis_ram_low": "El sistema només té {available_abs_MB} MB ({available_percent}%) de memòria RAM disponibles d'un total de {total_abs_MB} MB. Aneu amb compte.", "diagnosis_swap_none": "El sistema no té swap. Hauríeu de considerar afegir un mínim de 256 MB 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_nginx_conf_broken": "Sembla que s'ha trencat la configuració NGINX!", "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. Error: {error}", "domain_cannot_remove_main_add_new_one": "No es pot eliminar «{domain:s}» ja que és el domini principal i únic domini, primer s'ha d'afegir un altre domini utilitzant «yunohost domain add », i després fer-lo el domini principal amb «yunohost domain main-domain -n » i després es pot eliminar el domini «{domain:s}» utilitzant «yunohost domain remove {domain:s}».", @@ -665,7 +537,6 @@ "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 de tipus {0}, nom {1} i valor {2}. Hi ha més informació a https://yunohost.org/dns_config.", "diagnosis_dns_discrepancy": "El registre DNS de tipus {0} i nom {1} no concorda amb la configuració recomanada. Valor actual: {2}. Valor esperat: {3}. Més informació a https://yunohost.org/dns_config.", - "diagnosis_services_good_status": "El servei {service} està {status} tal i com s'esperava!", "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_abs_GB} GB ({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_abs_GB} GB ({free_percent}%). Aneu amb compte.", @@ -686,7 +557,6 @@ "diagnosis_description_services": "Verificació de l'estat dels serveis", "diagnosis_description_systemresources": "Recursos del sistema", "diagnosis_description_ports": "Exposició dels ports", - "diagnosis_description_http": "Exposició HTTP", "diagnosis_description_regenconf": "Configuració del sistema", "diagnosis_description_security": "Verificacions de seguretat", "diagnosis_ports_could_not_diagnose": "No s'ha pogut diagnosticar si els ports són accessibles des de l'exterior. Error: {error}", @@ -717,8 +587,6 @@ "diagnosis_services_running": "El servei {service} s'està executant!", "diagnosis_services_conf_broken": "La configuració pel servei {service} està trencada!", "diagnosis_ports_needed_by": "És necessari exposar aquest port per a les funcions {1} (servei {0})", - "permission_all_users_implicitly_added": "El permís també s'ha donat implícitament a «all_users» ja que és necessari per atorgar-lo al grup «visitors»", - "permission_cannot_remove_all_users_while_visitors_allowed": "No podeu retirar el permís a «all_users» mentre encara el tingui el grup «visitors»", "global_settings_setting_pop3_enabled": "Activa el protocol POP3 per al servidor de correu", "log_app_action_run": "Executa l'acció de l'aplicació «{}»", "log_app_config_show_panel": "Mostra el taulell de configuració de l'aplicació «{}»", @@ -728,4 +596,4 @@ "diagnosis_basesystem_hardware_board": "El model de la targeta del servidor és {model}", "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à…" -} +} \ No newline at end of file diff --git a/locales/de.json b/locales/de.json index 955f6ad67..c53cb60d2 100644 --- a/locales/de.json +++ b/locales/de.json @@ -10,66 +10,46 @@ "app_extraction_failed": "Installationsdateien konnten nicht entpackt werden", "app_id_invalid": "Falsche App-ID", "app_install_files_invalid": "Diese Dateien können nicht installiert werden", - "app_location_already_used": "Die App ({app}) ist bereits hier ({path}) installiert", - "app_location_install_failed": "Die App kann dort nicht installiert werden, da ein Konflikt mit der App '{other_app}' besteht, die bereits in '{other_path}' installiert ist", "app_manifest_invalid": "Mit dem App-Manifest stimmt etwas nicht: {error}", - "app_no_upgrade": "Alle Apps sind bereits aktuell", "app_not_installed": "Die App {app:s} konnte nicht in der Liste installierter Apps gefunden werden: {all_apps}", - "app_recent_version_required": "Für {:s} benötigt eine aktuellere Version von moulinette", "app_removed": "{app:s} wurde entfernt", "app_sources_fetch_failed": "Quelldateien konnten nicht abgerufen werden, ist die URL korrekt?", "app_unknown": "Unbekannte App", "app_upgrade_failed": "{app:s} konnte nicht aktualisiert werden", "app_upgraded": "{app:s} aktualisiert", - "appslist_fetched": "Appliste {appslist:s} wurde erfolgreich gelanden", - "appslist_removed": "Appliste {appslist:s} wurde entfernt", - "appslist_retrieve_error": "Entfernte Appliste {appslist:s} kann nicht empfangen werden: {error:s}", - "appslist_unknown": "Appliste {appslist:s} ist unbekannt.", - "ask_current_admin_password": "Derzeitiges Administrator-Kennwort", "ask_email": "E-Mail-Adresse", "ask_firstname": "Vorname", "ask_lastname": "Nachname", - "ask_list_to_remove": "zu entfernende Liste", "ask_main_domain": "Hauptdomain", "ask_new_admin_password": "Neues Verwaltungskennwort", "ask_password": "Passwort", - "backup_action_required": "Du musst etwas zum Speichern auswählen", "backup_app_failed": "Konnte keine Sicherung für die App '{app:s}' erstellen", "backup_archive_app_not_found": "App '{app:s}' konnte in keiner Datensicherung gefunden werden", - "backup_archive_hook_not_exec": "Hook '{hook:s}' konnte für diese Datensicherung nicht ausgeführt werden", "backup_archive_name_exists": "Datensicherung mit dem selben Namen existiert bereits.", "backup_archive_name_unknown": "Unbekanntes lokale Datensicherung mit Namen '{name:s}' gefunden", "backup_archive_open_failed": "Kann Sicherungsarchiv nicht öfnen", "backup_cleaning_failed": "Temporäres Sicherungsverzeichnis konnte nicht geleert werden", "backup_created": "Datensicherung komplett", - "backup_creating_archive": "Datensicherung wird erstellt…", "backup_delete_error": "Pfad '{path:s}' konnte nicht gelöscht werden", "backup_deleted": "Backup wurde entfernt", - "backup_extracting_archive": "Entpacke Sicherungsarchiv...", "backup_hook_unknown": "Der Datensicherungshook '{hook:s}' unbekannt", "backup_invalid_archive": "Dies ist kein Backup-Archiv", "backup_nothings_done": "Keine Änderungen zur Speicherung", "backup_output_directory_forbidden": "Wähle ein anderes Ausgabeverzeichnis. Datensicherung können nicht in /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var oder in Unterordnern von /home/yunohost.backup/archives erstellt werden", "backup_output_directory_not_empty": "Der gewählte Ausgabeordner sollte leer sein", "backup_output_directory_required": "Für die Datensicherung muss ein Zielverzeichnis angegeben werden", - "backup_running_app_script": "Datensicherung für App '{app:s}' wurd durchgeführt...", "backup_running_hooks": "Datensicherunghook wird ausgeführt…", "custom_app_url_required": "Es muss eine URL angegeben werden, um deine benutzerdefinierte App {app:s} zu aktualisieren", - "custom_appslist_name_required": "Du musst einen Namen für deine benutzerdefinierte Appliste angeben", - "dnsmasq_isnt_installed": "dnsmasq scheint nicht installiert zu sein. Bitte führe 'apt-get remove bind9 && apt-get install dnsmasq' aus", "domain_cert_gen_failed": "Zertifikat konnte nicht erzeugt werden", "domain_created": "Die Domain wurde angelegt", "domain_creation_failed": "Konnte Domain nicht erzeugen", "domain_deleted": "Die Domain wurde gelöscht", "domain_deletion_failed": "Konnte Domain nicht löschen", "domain_dyndns_already_subscribed": "Du hast dich schon für eine DynDNS-Domain angemeldet", - "domain_dyndns_invalid": "Domain nicht mittels DynDNS nutzbar", "domain_dyndns_root_unknown": "Unbekannte DynDNS Hauptdomain", "domain_exists": "Die Domain existiert bereits", "domain_uninstall_app_first": "Mindestens eine App ist noch für diese Domain installiert. Bitte deinstalliere zuerst die App, bevor du die Domain löschst", "domain_unknown": "Unbekannte Domain", - "domain_zone_exists": "DNS Zonen Datei existiert bereits", - "domain_zone_not_found": "DNS Zonen Datei kann nicht für Domäne {:s} gefunden werden", "done": "Erledigt", "downloading": "Wird heruntergeladen…", "dyndns_cron_installed": "DynDNS Cronjob erfolgreich angelegt", @@ -88,9 +68,6 @@ "firewall_reload_failed": "Die Firewall konnte nicht neu geladen werden", "firewall_reloaded": "Die Firewall wurde neu geladen", "firewall_rules_cmd_failed": "Einzelne Firewallregeln konnten nicht übernommen werden. Mehr Informationen sind im Log zu finden.", - "format_datetime_short": "%d/%m/%Y %I:%M %p", - "hook_argument_missing": "Fehlend Argument '{:s}'", - "hook_choice_invalid": "ungültige Wahl '{:s}'", "hook_exec_failed": "Skriptausführung fehlgeschlagen: {path:s}", "hook_exec_not_terminated": "Skriptausführung noch nicht beendet: {path:s}", "hook_list_by_invalid": "Ungültiger Wert zur Anzeige von Hooks", @@ -100,53 +77,24 @@ "ip6tables_unavailable": "ip6tables kann nicht verwendet werden. Du befindest dich entweder in einem Container oder es wird nicht vom Kernel unterstützt", "iptables_unavailable": "iptables kann nicht verwendet werden. Du befindest dich entweder in einem Container oder es wird nicht vom Kernel unterstützt", "ldap_initialized": "LDAP wurde initialisiert", - "license_undefined": "Undeiniert", "mail_alias_remove_failed": "E-Mail Alias '{mail:s}' konnte nicht entfernt werden", "mail_domain_unknown": "Unbekannte Mail Domain '{domain:s}'", "mail_forward_remove_failed": "Mailweiterleitung '{mail:s}' konnte nicht entfernt werden", "main_domain_change_failed": "Die Hauptdomain konnte nicht geändert werden", "main_domain_changed": "Die Hauptdomain wurde geändert", - "monitor_disabled": "Das Servermonitoring wurde erfolgreich deaktiviert", - "monitor_enabled": "Das Servermonitoring wurde aktiviert", - "monitor_glances_con_failed": "Verbindung mit Glances nicht möglich", - "monitor_not_enabled": "Servermonitoring ist nicht aktiviert", - "monitor_period_invalid": "Falscher Zeitraum", - "monitor_stats_file_not_found": "Statistikdatei nicht gefunden", - "monitor_stats_no_update": "Keine Monitoringstatistik zur Aktualisierung", - "monitor_stats_period_unavailable": "Keine Statistiken für den gewählten Zeitraum verfügbar", - "mountpoint_unknown": "Unbekannten Einhängepunkt", - "mysql_db_creation_failed": "MySQL Datenbankerzeugung fehlgeschlagen", - "mysql_db_init_failed": "MySQL Datenbankinitialisierung fehlgeschlagen", - "mysql_db_initialized": "Die MySQL Datenbank wurde initialisiert", - "network_check_mx_ko": "Es ist kein DNS MX Eintrag vorhanden", - "network_check_smtp_ko": "Ausgehender Mailverkehr (SMTP Port 25) scheint in deinem Netzwerk blockiert zu sein", - "network_check_smtp_ok": "Ausgehender Mailverkehr (SMTP Port 25) ist blockiert", - "new_domain_required": "Du musst eine neue Hauptdomain angeben", - "no_appslist_found": "Keine Appliste gefunden", "no_internet_connection": "Der Server ist nicht mit dem Internet verbunden", - "no_ipv6_connectivity": "Eine IPv6 Verbindung steht nicht zur Verfügung", - "no_restore_script": "Es konnte kein Wiederherstellungsskript für '{app:s}' gefunden werden", - "no_such_conf_file": "Datei {file:s}: konnte nicht kopiert werden, da diese nicht existiert", - "packages_no_upgrade": "Es müssen keine Pakete aktualisiert werden", - "packages_upgrade_critical_later": "Ein wichtiges Paket ({packages:s}) wird später aktualisiert", "packages_upgrade_failed": "Es konnten nicht alle Pakete aktualisiert werden", - "path_removal_failed": "Pfad {:s} konnte nicht entfernt werden", "pattern_backup_archive_name": "Ein gültiger Dateiname kann nur aus maximal 30 alphanumerischen sowie -_. Zeichen bestehen", "pattern_domain": "Muss ein gültiger Domainname sein (z.B. meine-domain.org)", "pattern_email": "Muss eine gültige E-Mail Adresse sein (z.B. someone@domain.org)", "pattern_firstname": "Muss ein gültiger Vorname sein", "pattern_lastname": "Muss ein gültiger Nachname sein", - "pattern_listname": "Kann nur Alphanumerische Zeichen oder Unterstriche enthalten", "pattern_mailbox_quota": "Muss eine Größe inkl. b/k/M/G/T Suffix, oder 0 zum deaktivieren sein", "pattern_password": "Muss mindestens drei Zeichen lang sein", - "pattern_port": "Es muss ein valider Port (zwischen 0 und 65535) angegeben werden", "pattern_port_or_range": "Muss ein valider Port (z.B. 0-65535) oder ein Bereich (z.B. 100:200) sein", "pattern_username": "Darf nur aus klein geschriebenen alphanumerischen Zeichen und Unterstrichen bestehen", "port_already_closed": "Der Port {port:d} wurde bereits für {ip_version:s} Verbindungen geschlossen", "port_already_opened": "Der Port {port:d} wird bereits von {ip_version:s} benutzt", - "port_available": "Der Port {port:d} ist verfügbar", - "port_unavailable": "Der Port {port:d} ist nicht verfügbar", - "restore_action_required": "Du musst etwas zum Wiederherstellen auswählen", "restore_already_installed_app": "Es ist bereits eine App mit der ID '{app:s}' installiet", "restore_app_failed": "App '{app:s}' konnte nicht wiederhergestellt werden", "restore_cleaning_failed": "Das temporäre Wiederherstellungsverzeichnis konnte nicht geleert werden", @@ -157,38 +105,30 @@ "restore_nothings_done": "Es wurde nicht wiederhergestellt", "restore_running_app_script": "Wiederherstellung wird ausfeührt für App '{app:s}'...", "restore_running_hooks": "Wiederherstellung wird gestartet…", - "service_add_configuration": "Füge Konfigurationsdatei {file:s} hinzu", "service_add_failed": "Der Dienst '{service:s}' kann nicht hinzugefügt werden", "service_added": "Der Service '{service:s}' wurde erfolgreich hinzugefügt", "service_already_started": "Der Dienst '{service:s}' läuft bereits", "service_already_stopped": "Dienst '{service:s}' wurde bereits gestoppt", "service_cmd_exec_failed": "Der Befehl '{command:s}' konnte nicht ausgeführt werden", - "service_configuration_conflict": "Die Datei {file:s} wurde zwischenzeitlich verändert. Bitte übernehme die Änderungen manuell oder nutze die Option --force (diese wird alle Änderungen überschreiben).", "service_disable_failed": "Der Dienst '{service:s}' konnte nicht deaktiviert werden", "service_disabled": "Der Dienst '{service:s}' wurde erfolgreich deaktiviert", "service_enable_failed": "Der Dienst '{service:s}' konnte nicht aktiviert werden", "service_enabled": "Der Dienst '{service:s}' wurde erfolgreich aktiviert", - "service_no_log": "Für den Dienst '{service:s}' kann kein Log angezeigt werden", "service_remove_failed": "Der Dienst '{service:s}' konnte nicht entfernt werden", "service_removed": "Der Dienst '{service:s}' wurde erfolgreich entfernt", "service_start_failed": "Der Dienst '{service:s}' konnte nicht gestartet werden", "service_started": "Der Dienst '{service:s}' wurde erfolgreich gestartet", - "service_status_failed": "Der Status von '{service:s}' kann nicht festgestellt werden", "service_stop_failed": "Der Dienst '{service:s}' kann nicht gestoppt werden", "service_stopped": "Der Dienst '{service:s}' wurde erfolgreich beendet", "service_unknown": "Unbekannter Dienst '{service:s}'", - "services_configured": "Konfiguration erfolgreich erstellt", - "show_diff": "Es gibt folgende Änderungen:\n{diff:s}", "ssowat_conf_generated": "Die Konfiguration von SSOwat war erfolgreich", "ssowat_conf_updated": "Die persistente SSOwat Einstellung wurde aktualisiert", "system_upgraded": "Das System wurde aktualisiert", "system_username_exists": "Der Benutzername existiert bereits", "unbackup_app": "App '{app:s}' konnte nicht gespeichert werden", "unexpected_error": "Ein unerwarteter Fehler ist aufgetreten", - "unit_unknown": "Unbekannte Einheit '{unit:s}'", "unlimit": "Kein Kontingent", "unrestore_app": "App '{app:s}' kann nicht Wiederhergestellt werden", - "update_cache_failed": "Konnte APT cache nicht aktualisieren", "updating_apt_cache": "Die Liste der verfügbaren Pakete wird aktualisiert…", "upgrade_complete": "Upgrade vollständig", "upgrading_packages": "Pakete werden aktualisiert…", @@ -201,7 +141,6 @@ "user_deleted": "Der Benutzer wurde entfernt", "user_deletion_failed": "Nutzer konnte nicht gelöscht werden", "user_home_creation_failed": "Benutzer Home konnte nicht erstellt werden", - "user_info_failed": "Nutzerinformationen können nicht angezeigt werden", "user_unknown": "Unbekannter Benutzer: {user:s}", "user_update_failed": "Benutzer kann nicht aktualisiert werden", "user_updated": "Der Benutzer wurde aktualisiert", @@ -211,44 +150,20 @@ "yunohost_installing": "YunoHost wird installiert…", "yunohost_not_installed": "YunoHost ist nicht oder unvollständig installiert worden. Bitte 'yunohost tools postinstall' ausführen", "app_not_properly_removed": "{app:s} wurde nicht ordnungsgemäß entfernt", - "service_regenconf_failed": "Konnte die Konfiguration für folgende Dienste nicht neu erzeugen: {services}", "not_enough_disk_space": "Zu wenig freier Speicherplatz unter '{path:s}' verfügbar", "backup_creation_failed": "Konnte Backup-Archiv nicht erstellen", - "service_conf_up_to_date": "Die Konfiguration für den Dienst '{service}' ist bereits aktuell", - "package_not_installed": "Das Paket '{pkgname}' ist nicht installiert", "pattern_positive_number": "Muss eine positive Zahl sein", - "diagnosis_kernel_version_error": "Kann Kernelversion nicht abrufen: {error}", - "package_unexpected_error": "Ein unerwarteter Fehler trat bei der Verarbeitung des Pakets '{pkgname}' auf", - "app_incompatible": "Die Anwendung {app} ist nicht mit deiner YunoHost-Version kompatibel", "app_not_correctly_installed": "{app:s} scheint nicht korrekt installiert zu sein", "app_requirements_checking": "Überprüfe notwendige Pakete für {app}…", - "app_requirements_failed": "Anforderungen für {app} werden nicht erfüllt: {error}", "app_requirements_unmeet": "Anforderungen für {app} werden nicht erfüllt, das Paket {pkgname} ({version}) muss {spec} sein", "app_unsupported_remote_type": "Für die App wurde ein nicht unterstützer Steuerungstyp verwendet", "backup_archive_broken_link": "Auf das Backup-Archiv konnte nicht zugegriffen werden (ungültiger Link zu {path:s})", - "diagnosis_debian_version_error": "Debian Version konnte nicht abgerufen werden: {error}", - "diagnosis_monitor_disk_error": "Festplatten können nicht aufgelistet werden: {error}", - "diagnosis_monitor_network_error": "Netzwerk kann nicht angezeigt werden: {error}", - "diagnosis_monitor_system_error": "System kann nicht angezeigt werden: {error}", - "diagnosis_no_apps": "Keine Anwendung ist installiert", "domains_available": "Verfügbare Domains:", "dyndns_key_not_found": "DNS-Schlüssel für die Domain wurde nicht gefunden", "dyndns_no_domain_registered": "Es wurde keine Domain mit DynDNS registriert", "ldap_init_failed_to_create_admin": "Die LDAP Initialisierung konnte keinen admin Benutzer erstellen", "mailbox_used_space_dovecot_down": "Der Dovecot Mailbox Dienst muss gestartet sein, wenn du den von der Mailbox belegten Speicher angezeigen lassen willst", "package_unknown": "Unbekanntes Paket '{pkgname}'", - "service_conf_file_backed_up": "Von der Konfigurationsdatei {conf} wurde ein Backup in {backup} erstellt", - "service_conf_file_copy_failed": "Die neue Konfigurationsdatei konnte von {new} nach {conf} nicht kopiert werden", - "service_conf_file_manually_modified": "Die Konfigurationsdatei {conf} wurde manuell verändert und wird nicht aktualisiert", - "service_conf_file_manually_removed": "Die Konfigurationsdatei {conf} wurde manuell entfern und wird nicht erstellt", - "service_conf_file_not_managed": "Die Konfigurationsdatei {conf} wurde noch nicht verwaltet und wird nicht aktualisiert", - "service_conf_file_remove_failed": "Die Konfigurationsdatei {conf} konnte nicht entfernt werden", - "service_conf_file_removed": "Die Konfigurationsdatei {conf} wurde entfernt", - "service_conf_file_updated": "Die Konfigurationsdatei {conf} wurde aktualisiert", - "service_conf_updated": "Die Konfigurationsdatei wurde für den Service {service} aktualisiert", - "service_conf_would_be_updated": "Die Konfigurationsdatei sollte für den Service {service} aktualisiert werden", - "ssowat_persistent_conf_read_error": "Ein Fehler ist aufgetreten, als die persistente SSOwat Konfiguration eingelesen wurde {error:s} Bearbeite die persistente Datei /etc/ssowat/conf.json , um die JSON syntax zu korregieren", - "ssowat_persistent_conf_write_error": "Ein Fehler ist aufgetreten, als die persistente SSOwat Konfiguration gespeichert wurde {error:s} Bearbeite die persistente Datei /etc/ssowat/conf.json , um die JSON syntax zu korregieren", "certmanager_attempt_to_replace_valid_cert": "Du versuchst gerade eine richtiges und gültiges Zertifikat der Domain {domain:s} zu überschreiben! (Benutze --force , um diese Nachricht zu umgehen)", "certmanager_domain_unknown": "Unbekannte Domain {domain:s}", "certmanager_domain_cert_not_selfsigned": "Das Zertifikat der Domain {domain:s} is kein selbstsigniertes Zertifikat. Bist du dir sicher, dass du es ersetzen willst? (Benutze --force)", @@ -258,12 +173,10 @@ "certmanager_domain_http_not_working": "Es scheint so, dass die Domain {domain:s} nicht über HTTP erreicht werden kann. Bitte überprüfe, ob deine DNS und nginx Konfiguration in Ordnung ist", "certmanager_error_no_A_record": "Kein DNS 'A' Eintrag für die Domain {domain:s} gefunden. Dein Domainname muss auf diese Maschine weitergeleitet werden, um ein Let's Encrypt Zertifikat installieren zu können! (Wenn du weißt was du tust, kannst du --no-checks benutzen, um diese Überprüfung zu überspringen. )", "certmanager_domain_dns_ip_differs_from_public_ip": "Der DNS 'A' Eintrag der Domain {domain:s} unterscheidet sich von dieser Server-IP. Wenn du gerade deinen A Eintrag verändert hast, warte bitte etwas, damit die Änderungen wirksam werden (du kannst die DNS Propagation mittels Website überprüfen) (Wenn du weißt was du tust, kannst du --no-checks benutzen, um diese Überprüfung zu überspringen. )", - "certmanager_domain_not_resolved_locally": "Die Domain {domain:s} konnte von innerhalb des Yunohost-Servers nicht aufgelöst werden. Das kann passieren, wenn du den DNS Eintrag vor Kurzem verändert hast. Falls dies der Fall ist, warte bitte ein paar Stunden, damit die Änderungen wirksam werden. Wenn der Fehler bestehen bleibt, ziehe in Betracht die Domain {domain:s} in /etc/hosts einzutragen. (Wenn du weißt was du tust, benutze --no-checks , um diese Nachricht zu umgehen. )", "certmanager_cannot_read_cert": "Es ist ein Fehler aufgetreten, als es versucht wurde das aktuelle Zertifikat für die Domain {domain:s} zu öffnen (Datei: {file:s}), Grund: {reason:s}", "certmanager_cert_install_success_selfsigned": "Ein selbstsigniertes Zertifikat für die Domain {domain:s} wurde erfolgreich installiert!", "certmanager_cert_install_success": "Für die Domain {domain:s} wurde erfolgreich ein Let's Encrypt installiert!", "certmanager_cert_renew_success": "Das Let's Encrypt Zertifikat für die Domain {domain:s} wurde erfolgreich erneuert!", - "certmanager_old_letsencrypt_app_detected": "\nYunohost hat erkannt, dass eine Version von 'letsencrypt' installiert ist, die mit den neuen, integrierten Zertifikatsmanagement-Features in Yunohost kollidieren. Wenn du die neuen Features nutzen willst, führe die folgenden Befehle aus:\n\n yunohost app remove letsencrypt\n yunohost domain cert-install\n\nAnm.: Diese Befehle werden die selbstsignierten und Let's Encrypt Zertifikate aller Domains neu installieren", "certmanager_hit_rate_limit": "Es wurden innerhalb kurzer Zeit schon zu viele Zertifikate für die exakt gleiche Domain {domain:s} ausgestellt. Bitte versuche es später nochmal. Besuche https://letsencrypt.org/docs/rate-limits/ für mehr Informationen", "certmanager_cert_signing_failed": "Signieren des neuen Zertifikats ist fehlgeschlagen", "certmanager_no_cert_file": "Die Zertifikatsdatei für die Domain {domain:s} (Datei: {file:s}) konnte nicht gelesen werden", @@ -272,21 +185,11 @@ "certmanager_self_ca_conf_file_not_found": "Die Konfigurationsdatei der Zertifizierungsstelle für selbstsignierte Zertifikate wurde nicht gefunden (Datei {file:s})", "certmanager_acme_not_configured_for_domain": "Das Zertifikat für die Domain {domain:s} scheint nicht richtig installiert zu sein. Bitte führe den Befehl cert-install für diese Domain nochmals aus.", "certmanager_unable_to_parse_self_CA_name": "Der Name der Zertifizierungsstelle für selbstsignierte Zertifikate konnte nicht analysiert werden (Datei: {file:s})", - "app_package_need_update": "Es ist notwendig das Paket {app} zu aktualisieren, um Aktualisierungen für YunoHost zu erhalten", - "service_regenconf_dry_pending_applying": "Überprüfe ausstehende Konfigurationen, die für den Server {service} notwendig sind...", - "service_regenconf_pending_applying": "Überprüfe ausstehende Konfigurationen, die für den Server '{service}' notwendig sind...", "certmanager_http_check_timeout": "Eine Zeitüberschreitung ist aufgetreten als der Server versuchte sich selbst über HTTP mit der öffentlichen IP (Domain {domain:s} mit der IP {ip:s}) zu erreichen. Möglicherweise ist dafür hairpinning oder eine falsch konfigurierte Firewall/Router deines Servers dafür verantwortlich.", "certmanager_couldnt_fetch_intermediate_cert": "Eine Zeitüberschreitung ist aufgetreten als der Server versuchte die Teilzertifikate von Let's Encrypt zusammenzusetzen. Die Installation/Erneuerung des Zertifikats wurde abgebrochen - bitte versuche es später erneut.", - "appslist_retrieve_bad_format": "Die geladene Appliste {appslist:s} ist ungültig", "domain_hostname_failed": "Erstellen des neuen Hostnamens fehlgeschlagen", - "appslist_name_already_tracked": "Es gibt bereits eine registrierte App-Liste mit Namen {name:s}.", - "appslist_url_already_tracked": "Es gibt bereits eine registrierte Anwendungsliste mit der URL {url:s}.", - "appslist_migrating": "Migriere Anwendungsliste {appslist:s} …", - "appslist_could_not_migrate": "Konnte die Anwendungsliste {appslist:s} nicht migrieren. Konnte die URL nicht verarbeiten... Der alte Cron-Job wurde unter {bkp_file:s} beibehalten.", - "appslist_corrupted_json": "Anwendungslisten konnte nicht geladen werden. Es scheint, dass {filename:s} beschädigt ist.", "yunohost_ca_creation_success": "Die lokale Zertifizierungs-Authorität wurde angelegt.", "app_already_installed_cant_change_url": "Diese Application ist bereits installiert. Die URL kann durch diese Funktion nicht modifiziert werden. Überprüfe ob `app changeurl` verfügbar ist.", - "app_change_no_change_url_script": "Die Application {app_name:s} unterstützt das anpassen der URL noch nicht. Sie muss gegebenenfalls erweitert werden.", "app_change_url_failed_nginx_reload": "NGINX konnte nicht neu gestartet werden. Hier ist der Output von 'nginx -t':\n{nginx_errors:s}", "app_change_url_identical_domains": "Die alte und neue domain/url_path sind identisch: ('{domain:s} {path:s}'). Es gibt nichts zu tun.", "app_already_up_to_date": "{app:s} ist bereits aktuell", @@ -297,22 +200,16 @@ "app_location_unavailable": "Diese URL ist nicht verfügbar oder wird von einer installierten Anwendung genutzt:\n{apps:s}", "backup_applying_method_custom": "Rufe die benutzerdefinierte Backup-Methode '{method:s}' auf…", "backup_archive_system_part_not_available": "Der System-Teil '{part:s}' ist in diesem Backup nicht enthalten", - "backup_archive_mount_failed": "Das Einbinden des Backup-Archives ist fehlgeschlagen", "backup_archive_writing_error": "Die Dateien '{source:s} (im Ordner '{dest:s}') konnten nicht in das komprimierte Archiv-Backup '{archive:s}' hinzugefügt werden", "app_change_url_success": "{app:s} URL ist nun {domain:s}{path:s}", "backup_applying_method_borg": "Sende alle Dateien zur Sicherung ins borg-backup repository…", - "invalid_url_format": "ungültiges URL Format", "global_settings_bad_type_for_setting": "Falscher Typ für Einstellung {setting:s}. Empfangen: {received_type:s}, aber erwartet: {expected_type:s}", "global_settings_bad_choice_for_enum": "Falsche Wahl für die Einstellung {setting:s}. Habe '{choice:s}' erhalten, aber es stehen nur folgende Auswahlmöglichkeiten zur Verfügung: {available_choices:s}", "file_does_not_exist": "Die Datei {path:s} existiert nicht.", "experimental_feature": "Warnung: Diese Funktion ist experimentell und gilt nicht als stabil. Sie sollten sie nur verwenden, wenn Sie wissen, was Sie tun.", - "error_when_removing_sftpuser_group": "Fehler beim Versuch, die Gruppe sftpusers zu entfernen", - "edit_permission_with_group_all_users_not_allowed": "Sie dürfen die Berechtigung für die Gruppe \"all_users\" nicht bearbeiten. Verwenden Sie stattdessen \"yunohost user permission clear APP\" oder \"yunohost user permission add APP -u USER\".", - "edit_group_not_allowed": "Du bist nicht berechtigt zum Bearbeiten der Gruppe {group: s}", "dyndns_domain_not_provided": "Der DynDNS-Anbieter {provider:s} kann die Domain(s) {domain:s} nicht bereitstellen.", "dyndns_could_not_check_available": "Konnte nicht überprüfen, ob {domain:s} auf {provider:s} verfügbar ist.", "dyndns_could_not_check_provide": "Konnte nicht überprüft, ob {provider:s} die Domain(s) {domain:s} bereitstellen kann.", - "domain_dyndns_dynette_is_unreachable": "YunoHost dynette kann nicht erreicht werden, entweder ist Ihr YunoHost nicht korrekt mit dem Internet verbunden oder der dynette-Server ist inaktiv. Fehler: {error}", "domain_dns_conf_is_just_a_recommendation": "Dieser Befehl zeigt Ihnen, was die * empfohlene * Konfiguration ist. Die DNS-Konfiguration wird NICHT für Sie eingerichtet. Es liegt in Ihrer Verantwortung, Ihre DNS-Zone in Ihrem Registrar gemäß dieser Empfehlung zu konfigurieren.", "dpkg_lock_not_available": "Dieser Befehl kann momentan nicht ausgeführt werden, da anscheinend ein anderes Programm die Sperre von dpkg (dem Systempaket-Manager) verwendet", "confirm_app_install_thirdparty": "WARNUNG! Das Installieren von Anwendungen von Drittanbietern kann die Integrität und Sicherheit Deines Systems beeinträchtigen. Du solltest es wahrscheinlich NICHT installieren, es sei denn, Du weisst, was Du tust. Bist du bereit, dieses Risiko einzugehen? [{answers:s}]", @@ -329,7 +226,6 @@ "backup_method_custom_finished": "Benutzerdefinierte Sicherungsmethode '{method:s}' beendet", "backup_method_copy_finished": "Sicherungskopie beendet", "backup_method_borg_finished": "Backup in Borg beendet", - "backup_custom_need_mount_error": "Bei der benutzerdefinierten Sicherungsmethode ist beim Arbeitsschritt \"Braucht ein Einhängen/Verbinden\" (need_mount) ein Fehler aufgetreten", "backup_custom_mount_error": "Bei der benutzerdefinierten Sicherungsmethode ist beim Arbeitsschritt \"Einhängen/Verbinden\" ein Fehler aufgetreten", "backup_custom_backup_error": "Bei der benutzerdefinierten Sicherungsmethode ist beim Arbeitsschritt \"Sicherung\" ein Fehler aufgetreten", "backup_csv_creation_failed": "Die zur Wiederherstellung erforderliche CSV-Datei kann nicht erstellt werden", @@ -337,11 +233,8 @@ "backup_borg_not_implemented": "Die Borg-Sicherungsmethode ist noch nicht implementiert", "backup_ask_for_copying_if_needed": "Möchten Sie die Sicherung mit {size:s} MB temporär durchführen? (Dieser Weg wird verwendet, da einige Dateien nicht mit einer effizienteren Methode vorbereitet werden konnten).", "backup_actually_backuping": "Erstellt ein Backup-Archiv aus den gesammelten Dateien …", - "ask_path": "Pfad", "ask_new_path": "Neuer Pfad", "ask_new_domain": "Neue Domain", - "apps_permission_restoration_failed": "Erteilen der Berechtigung '{permission:s}' für die Wiederherstellung der App {app:s} erforderlich", - "apps_permission_not_found": "Keine Berechtigung für die installierten Apps gefunden", "app_upgrade_some_app_failed": "Einige Anwendungen können nicht aktualisiert werden", "app_upgrade_app_name": "{app} wird jetzt aktualisiert…", "app_upgrade_several_apps": "Die folgenden Apps werden aktualisiert: {apps}", @@ -358,24 +251,17 @@ "app_action_broke_system": "Diese Aktion scheint diese wichtigen Dienste unterbrochen zu haben: {services}", "apps_already_up_to_date": "Alle Apps sind bereits aktuell", "backup_copying_to_organize_the_archive": "Kopieren von {size:s} MB, um das Archiv zu organisieren", - "app_upgrade_stopped": "Das Upgrade aller Anwendungen wurde gestoppt, um mögliche Schäden zu vermeiden, da das Upgrade der vorherigen Anwendung fehlgeschlagen ist", - "group_already_disallowed": "Die Gruppe '{group:s}' hat bereits die Berechtigungen '{permission:s}' für die App '{app:s}' deaktiviert", "global_settings_setting_security_ssh_compatibility": "Kompatibilität vs. Sicherheitskompromiss für den SSH-Server. Beeinflusst die Chiffren (und andere sicherheitsrelevante Aspekte)", "group_deleted": "Gruppe '{group}' gelöscht", "group_deletion_failed": "Kann Gruppe '{group}' nicht löschen", - "group_deletion_not_allowed": "Die Gruppe {group:s} kann nicht manuell gelöscht werden.", "dyndns_provider_unreachable": "Dyndns-Anbieter {provider} kann nicht erreicht werden: Entweder ist dein YunoHost nicht korrekt mit dem Internet verbunden oder der Dynette-Server ist ausgefallen.", - "group_already_allowed": "Gruppe '{group:s}' hat bereits die Berechtigung '{permission:s}' für die App '{app:s}' eingeschaltet", - "group_name_already_exist": "Gruppe {name:s} existiert bereits", "group_created": "Gruppe '{group}' angelegt", "group_creation_failed": "Kann Gruppe '{group}' nicht anlegen", "group_unknown": "Die Gruppe '{group:s}' ist unbekannt", "group_updated": "Gruppe '{group:s}' erneuert", "group_update_failed": "Kann Gruppe '{group:s}' nicht anpassen", "log_does_exists": "Es gibt kein Operationsprotokoll mit dem Namen'{log}', verwende'yunohost log list', um alle verfügbaren Operationsprotokolle anzuzeigen", - "log_app_removelist": "Entferne eine Applikationsliste", "log_operation_unit_unclosed_properly": "Die Operationseinheit wurde nicht richtig geschlossen", - "log_app_removeaccess": "Entziehe Zugriff auf '{}'", "global_settings_setting_security_postfix_compatibility": "Kompatibilität vs. Sicherheitskompromiss für den Postfix-Server. Beeinflusst die Chiffren (und andere sicherheitsrelevante Aspekte)", "log_category_404": "Die Log-Kategorie '{category}' existiert nicht", "global_settings_unknown_type": "Unerwartete Situation, die Einstellung {setting:s} scheint den Typ {unknown_type:s} zu haben, ist aber kein vom System unterstützter Typ.", @@ -383,13 +269,11 @@ "global_settings_unknown_setting_from_settings_file": "Unbekannter Schlüssel in den Einstellungen: '{setting_key:s}', verwerfen und speichern in /etc/yunohost/settings-unknown.json", "log_link_to_log": "Vollständiges Log dieser Operation: '{desc}'", "global_settings_setting_example_bool": "Beispiel einer booleschen Option", - "log_app_fetchlist": "Füge eine Applikationsliste hinzu", "log_help_to_get_log": "Um das Protokoll der Operation '{desc}' anzuzeigen, verwende den Befehl 'yunohost log display {name}'", "global_settings_setting_security_nginx_compatibility": "Kompatibilität vs. Sicherheitskompromiss für den Webserver NGINX. Beeinflusst die Chiffren (und andere sicherheitsrelevante Aspekte)", "backup_php5_to_php7_migration_may_fail": "Dein Archiv konnte nicht für PHP 7 konvertiert werden, Du kannst deine PHP-Anwendungen möglicherweise nicht wiederherstellen (Grund: {error:s})", "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Erlaubt die Verwendung eines (veralteten) DSA-Hostkeys für die SSH-Daemon-Konfiguration", "global_settings_setting_example_string": "Beispiel einer string Option", - "log_app_addaccess": "Füge Zugriff auf '{}' hinzu", "log_app_remove": "Entferne die Anwendung '{}'", "global_settings_setting_example_int": "Beispiel einer int Option", "global_settings_cant_open_settings": "Einstellungsdatei konnte nicht geöffnet werden, Grund: {reason:s}", @@ -409,7 +293,6 @@ "log_link_to_failed_log": "Der Vorgang konnte nicht abgeschlossen werden '{desc}'. Bitte gib das vollständige Protokoll dieser Operation mit Klicken Sie hier an, um Hilfe zu erhalten", "backup_cant_mount_uncompress_archive": "Das unkomprimierte Archiv konnte nicht als schreibgeschützt gemountet werden", "backup_csv_addition_failed": "Es konnten keine Dateien zur Sicherung in die CSV-Datei hinzugefügt werden", - "log_app_clearaccess": "Entziehe alle Zugriffe auf '{}'", "global_settings_setting_security_password_admin_strength": "Stärke des Admin-Passworts", "global_settings_key_doesnt_exists": "Der Schlüssel'{settings_key:s}' existiert nicht in den globalen Einstellungen, du kannst alle verfügbaren Schlüssel sehen, indem du 'yunohost settings list' ausführst", "log_app_makedefault": "Mache '{}' zur Standard-Anwendung", @@ -432,4 +315,4 @@ "apps_catalog_update_success": "Der Apps-Katalog wurde aktualisiert!", "password_too_simple_1": "Das Passwort muss mindestens 8 Zeichen lang sein", "diagnosis_display_tip_cli": "Sie können 'yunohost diagnosis show --issues' ausführen, um die gefundenen Probleme anzuzeigen." -} +} \ No newline at end of file diff --git a/locales/el.json b/locales/el.json index 615dfdd48..efa5bf769 100644 --- a/locales/el.json +++ b/locales/el.json @@ -1,3 +1,3 @@ { "password_too_simple_1": "Ο κωδικός πρόσβασης πρέπει να έχει μήκος τουλάχιστον 8 χαρακτήρων" -} +} \ No newline at end of file diff --git a/locales/eo.json b/locales/eo.json index 8dc5c1d98..d825c84c7 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -8,16 +8,13 @@ "app_change_url_success": "{app:s} URL nun estas {domain:s} {path:s}", "app_extraction_failed": "Ne povis ĉerpi la instalajn dosierojn", "app_id_invalid": "Nevalida apo ID", - "app_incompatible": "Apo {app} ne estas kongrua kun via YunoHost versio", "app_install_files_invalid": "Ĉi tiuj dosieroj ne povas esti instalitaj", - "app_location_already_used": "La app '{app}' jam estas instalita en ({path})", "user_updated": "Uzantinformoj ŝanĝis", "users_available": "Uzantoj disponeblaj :", "yunohost_already_installed": "YunoHost estas jam instalita", "yunohost_ca_creation_failed": "Ne povis krei atestan aŭtoritaton", "yunohost_ca_creation_success": "Loka atestila aŭtoritato kreiĝis.", "yunohost_installing": "Instalante YunoHost…", - "service_description_glances": "Monitoras sistemajn informojn en via servilo", "service_description_metronome": "Mastrumas XMPP tujmesaĝilon kontojn", "service_description_mysql": "Butikigas datumojn de programoj (SQL datumbazo)", "service_description_nginx": "Servas aŭ permesas atingi ĉiujn retejojn gastigita sur via servilo", @@ -25,7 +22,6 @@ "service_description_php7.0-fpm": "Ekzekutas programojn skribitajn en PHP kun NGINX", "service_description_postfix": "Uzita por sendi kaj ricevi retpoŝtojn", "service_description_redis-server": "Specialita datumbazo uzita por rapida datumo atingo, atendovicoj kaj komunikadoj inter programoj", - "service_description_rmilter": "Kontrolas diversajn parametrojn en retpoŝtoj", "service_description_rspamd": "Filtras trudmesaĝojn, kaj aliaj funkcioj rilate al retpoŝto", "service_description_slapd": "Stokas uzantojn, domajnojn kaj rilatajn informojn", "service_description_ssh": "Permesas al vi konekti al via servilo kun fora terminalo (SSH protokolo)", @@ -40,22 +36,17 @@ "app_argument_choice_invalid": "Uzu unu el ĉi tiuj elektoj '{choices:s}' por la argumento '{name:s}'", "app_argument_invalid": "Elektu validan valoron por la argumento '{name:s}': {error:s}", "app_change_url_failed_nginx_reload": "Ne eblis reŝarĝi NGINX. Jen la eligo de 'nginx -t':\n{nginx_errors:s}", - "appslist_url_already_tracked": "Jam ekzistas registrita app-listo kun la URL {url:s}.", "ask_new_admin_password": "Nova administrada pasvorto", "app_action_broke_system": "Ĉi tiu ago ŝajne rompis ĉi tiujn gravajn servojn: {services}", "app_unsupported_remote_type": "Malkontrolita fora speco uzita por la apliko", "backup_archive_system_part_not_available": "Sistemo parto '{part:s}' ne haveblas en ĉi tiu rezervo", - "apps_permission_not_found": "Neniu permeso trovita por la instalitaj programoj", - "apps_permission_restoration_failed": "Donu la rajtigan permeson '{permission:s}' por restarigi {app:s}", "backup_abstract_method": "Ĉi tiu rezerva metodo ankoraŭ efektiviĝis", "apps_already_up_to_date": "Ĉiuj aplikoj estas jam ĝisdatigitaj", "backup_borg_not_implemented": "La kopia metodo de Borg ankoraŭ ne estas efektivigita", - "app_upgrade_stopped": "Ĝisdatigi ĉiujn aplikaĵojn estis ĉesigita por eviti eblajn damaĝojn ĉar unu app ne povis esti altgradigita", "app_location_unavailable": "Ĉi tiu URL aŭ ne haveblas, aŭ konfliktas kun la jam instalita (j) apliko (j):\n{apps:s}", "backup_archive_app_not_found": "Ne povis trovi la programon '{app:s}' en la rezerva ar archiveivo", "backup_actually_backuping": "Krei rezervan ar archiveivon el la kolektitaj dosieroj …", "backup_method_borg_finished": "Sekurkopio en Borg finiĝis", - "appslist_removed": "La listo de '{appslist:s}' estis forigita", "app_change_url_no_script": "La app '{app_name:s}' ankoraŭ ne subtenas URL-modifon. Eble vi devus altgradigi ĝin.", "app_start_install": "Instali la programon '{app}' …", "backup_created": "Sekurkopio kreita", @@ -65,29 +56,21 @@ "backup_archive_broken_link": "Ne povis aliri la rezervan ar archiveivon (rompita ligilo al {path:s})", "app_requirements_checking": "Kontrolante postulatajn pakaĵojn por {app} …", "app_not_installed": "Ne povis trovi la aplikon '{app:s}' en la listo de instalitaj programoj: {all_apps}", - "app_location_install_failed": "Ne eblas instali la aplikon tie ĉar ĝi konfliktas kun la '{other_app}' jam instalita en '{other_path}'", "ask_new_path": "Nova vojo", "backup_custom_mount_error": "Propra rezerva metodo ne povis preterpasi la paŝon 'monto'", "app_upgrade_app_name": "Nun ĝisdatiganta {app} …", "app_manifest_invalid": "Io misas pri la aplika manifesto: {error}", "backup_cleaning_failed": "Ne povis purigi la provizoran rezervan dosierujon", "backup_invalid_archive": "Ĉi tio ne estas rezerva ar archiveivo", - "ask_current_admin_password": "Pasvorto pri aktuala administrado", "backup_creation_failed": "Ne povis krei la rezervan ar archiveivon", "backup_hook_unknown": "La rezerva hoko '{hook:s}' estas nekonata", "backup_custom_backup_error": "Propra rezerva metodo ne povis preterpasi la paŝon \"sekurkopio\"", "ask_main_domain": "Ĉefa domajno", "backup_method_tar_finished": "TAR-rezerva ar archiveivo kreita", - "appslist_unknown": "La app-listo '{appslist:s}' estas nekonata.", - "ask_list_to_remove": "Listo por forigi", "backup_cant_mount_uncompress_archive": "Ne povis munti la nekompresitan ar archiveivon kiel protektita kontraŭ skribo", - "appslist_retrieve_bad_format": "Ne povis legi la elprenitan liston '{appslist:s}'", - "appslist_corrupted_json": "Ne povis ŝarĝi la aplikajn listojn. Ĝi aspektas kiel {filename:s} estas damaĝita.", "app_action_cannot_be_ran_because_required_services_down": "Ĉi tiuj postulataj servoj devas funkcii por funkciigi ĉi tiun agon: {services}. Provu rekomenci ilin por daŭrigi (kaj eble esploru, kial ili malsupreniras).", "backup_copying_to_organize_the_archive": "Kopiante {size:s} MB por organizi la ar archiveivon", "backup_output_directory_forbidden": "Elektu malsaman elirejan dosierujon. Sekurkopioj ne povas esti kreitaj en sub-dosierujoj / bin, / boot, / dev, / ktp, / lib, / root, / run, / sbin, / sys, / usr, / var aŭ /home/yunohost.backup/archives", - "appslist_could_not_migrate": "Ne povis migri la liston de aplikoj '{appslist:s}'! Ne eblis analizi la URL ... La malnova cron-laboro konserviĝis en {bkp_file:s}.", - "app_requirements_failed": "Certaines exigences ne sont pas remplies pour {app}: {error}", "backup_no_uncompress_archive_dir": "Ne ekzistas tia nekompremita arkiva dosierujo", "password_too_simple_1": "Pasvorto devas esti almenaŭ 8 signojn longa", "app_upgrade_failed": "Ne povis ĝisdatigi {app:s}: {error}", @@ -98,15 +81,12 @@ "backup_archive_name_exists": "Rezerva arkivo kun ĉi tiu nomo jam ekzistas.", "backup_applying_method_tar": "Krei la rezervan TAR-ar archiveivon …", "backup_method_custom_finished": "Propra rezerva metodo '{method:s}' finiĝis", - "appslist_retrieve_error": "Ne eblas akiri la forajn listojn '{appslist:s}': {error:s}", "app_already_installed_cant_change_url": "Ĉi tiu app estas jam instalita. La URL ne povas esti ŝanĝita nur per ĉi tiu funkcio. Rigardu \"app changeurl\" se ĝi haveblas.", "app_not_correctly_installed": "{app:s} ŝajnas esti malĝuste instalita", "app_removed": "{app:s} forigita", "backup_delete_error": "Ne povis forigi '{path:s}'", - "app_package_need_update": "La pakaĵo {app} devas esti ĝisdatigita por sekvi YunoHost-ŝanĝojn", "backup_nothings_done": "Nenio por ŝpari", "backup_applying_method_custom": "Nomante la kutiman rezervan metodon '{method:s}' …", - "appslist_fetched": "Ĝisdatigis la liston de aplikoj '{appslist:s}'", "backup_app_failed": "Ne eblis rezervi la programon '{app:s}'", "app_upgrade_some_app_failed": "Iuj aplikoj ne povis esti altgradigitaj", "app_start_remove": "Forigo de la apliko '{app}' …", @@ -121,17 +101,14 @@ "ask_firstname": "Antaŭnomo", "backup_ask_for_copying_if_needed": "Ĉu vi volas realigi la sekurkopion uzante {size:s} MB provizore? (Ĉi tiu maniero estas uzata ĉar iuj dosieroj ne povus esti pretigitaj per pli efika metodo.)", "backup_mount_archive_for_restore": "Preparante arkivon por restarigo …", - "appslist_migrating": "Migrado de la aplika listo '{appslist:s}' …", "backup_csv_creation_failed": "Ne povis krei la CSV-dosieron bezonatan por restarigo", "backup_archive_name_unknown": "Nekonata loka rezerva ar archiveivo nomata '{name:s}'", "backup_applying_method_borg": "Sendado de ĉiuj dosieroj al sekurkopio en borg-rezerva deponejo …", "app_sources_fetch_failed": "Ne povis akiri fontajn dosierojn, ĉu la URL estas ĝusta?", - "appslist_name_already_tracked": "Registrita aplika listo kun la nomo {name:s} jam ekzistas.", "ask_new_domain": "Nova domajno", "app_unknown": "Nekonata apliko", "app_not_upgraded": "La aplikaĵo '{failed_app}' ne ĝisdatigis, kaj pro tio la sekvaj ĝisdatigoj de aplikoj estis nuligitaj: {apps}", "aborting": "Aborti.", - "ask_path": "Pado", "app_upgraded": "{app:s} altgradigita", "backup_deleted": "Rezerva forigita", "backup_csv_addition_failed": "Ne povis aldoni dosierojn al sekurkopio en la CSV-dosiero", @@ -149,7 +126,6 @@ "mailbox_disabled": "Retpoŝto malŝaltita por uzanto {user:s}", "migration_description_0011_setup_group_permission": "Agordu uzantogrupon kaj starigu permeson por programoj kaj servoj", "migration_0011_backup_before_migration": "Krei sekurkopion de LDAP-datumbazo kaj agordojn antaŭ la efektiva migrado.", - "migration_0011_LDAP_config_dirty": "Similas ke vi agordis vian LDAP-agordon. Por ĉi tiu migrado la LDAP-agordo bezonas esti ĝisdatigita.\nVi devas konservi vian aktualan agordon, reintaligi la originalan agordon per funkciado de \"yunohost iloj regen-conf -f\" kaj reprovi la migradon", "migration_0011_migrate_permission": "Migrado de permesoj de agordoj al aplikoj al LDAP…", "migration_0011_migration_failed_trying_to_rollback": "Ne povis migri ... provante redakti la sistemon.", "migrations_dependencies_not_satisfied": "Rulu ĉi tiujn migradojn: '{dependencies_id}', antaŭ migrado {id}.", @@ -177,7 +153,6 @@ "group_updated": "Ĝisdatigita \"{group}\" grupo", "group_already_exist": "Grupo {group} jam ekzistas", "group_already_exist_on_system": "Grupo {group} jam ekzistas en la sistemaj grupoj", - "group_cannot_be_edited": "La grupo {group} ne povas esti redaktita permane.", "group_cannot_be_deleted": "La grupo {group} ne povas esti forigita permane.", "group_update_failed": "Ne povis ĝisdatigi la grupon '{group}': {error}", "group_user_already_in_group": "Uzanto {user} jam estas en grupo {group}", @@ -186,7 +161,6 @@ "log_category_404": "La loga kategorio '{category}' ne ekzistas", "log_permission_create": "Krei permeson '{}'", "log_permission_delete": "Forigi permeson '{}'", - "log_permission_urls": "Ĝisdatigu URLojn rilatajn al permeso '{}'", "log_user_group_create": "Krei grupon '{}'", "log_user_permission_update": "Mise à jour des accès pour la permission '{}'", "log_user_permission_reset": "Restarigi permeson '{}'", @@ -201,7 +175,6 @@ "permission_already_disallowed": "Grupo '{group}' jam havas permeson '{permission}' malebligita'", "permission_cannot_remove_main": "Forigo de ĉefa permeso ne rajtas", "permission_creation_failed": "Ne povis krei permeson '{permission}': {error}", - "tools_update_failed_to_app_fetchlist": "Ne povis ĝisdatigi la listojn de aplikoj de YunoHost ĉar: {error}", "user_already_exists": "La uzanto '{user}' jam ekzistas", "migrations_pending_cant_rerun": "Tiuj migradoj ankoraŭ estas pritraktataj, do ne plu rajtas esti ekzekutitaj: {ids}", "migrations_running_forward": "Kuranta migrado {id}…", @@ -236,7 +209,6 @@ "regenconf_file_backed_up": "Agordodosiero '{conf}' estis rezervita al '{backup}'", "migration_0007_cannot_restart": "SSH ne rekomencas post provi nuligi la migradan numeron 6.", "migration_description_0006_sync_admin_and_root_passwords": "Sinkronigu admin kaj radikajn pasvortojn", - "updating_app_lists": "Akirante haveblajn ĝisdatigojn por programoj…", "iptables_unavailable": "Vi ne povas ludi kun iptables ĉi tie. Vi estas en ujo aŭ via kerno ne subtenas ĝin", "global_settings_cant_write_settings": "Ne eblis konservi agordojn, tial: {reason:s}", "service_added": "La servo '{service:s}' aldonis", @@ -244,7 +216,6 @@ "service_started": "Servo '{service:s}' komenciĝis", "port_already_opened": "Haveno {port:d} estas jam malfermita por {ip_version:s} rilatoj", "installation_failed": "Io okazis malbone kun la instalado", - "network_check_mx_ko": "DNS MX-rekordo ne estas agordita", "migrate_tsig_wait_3": "1 minuto …", "certmanager_conflicting_nginx_file": "Ne povis prepari domajnon por ACME-defio: la agordo de NGINX {filepath:s} konfliktas kaj unue devas esti forigita", "upgrading_packages": "Ĝisdatigi pakojn…", @@ -254,7 +225,6 @@ "hook_json_return_error": "Ne povis legi revenon de hoko {path:s}. Eraro: {msg:s}. Kruda enhavo: {raw_content}", "dyndns_cron_removed": "DynDNS cron-laboro forigita", "dyndns_key_not_found": "DNS-ŝlosilo ne trovita por la domajno", - "custom_appslist_name_required": "Vi devas doni nomon por via kutima app-listo", "tools_upgrade_regular_packages_failed": "Ne povis ĝisdatigi pakojn: {packages_list}", "service_start_failed": "Ne povis komenci la servon '{service:s}'\n\nLastatempaj servaj protokoloj: {logs:s}", "service_reloaded": "Servo '{service:s}' reŝargita", @@ -266,12 +236,10 @@ "migration_description_0008_ssh_conf_managed_by_yunohost_step2": "Lasu la SSH-agordon estu administrata de YunoHost (paŝo 2, manlibro)", "restore_confirm_yunohost_installed": "Ĉu vi vere volas restarigi jam instalitan sistemon? [{answers:s}]", "pattern_positive_number": "Devas esti pozitiva nombro", - "monitor_stats_file_not_found": "Ne povis trovi la statistikan dosieron", "certmanager_error_no_A_record": "Neniu DNS 'A' rekordo trovita por '{domain:s}'. Vi bezonas atentigi vian domajnan nomon al via maŝino por povi instali atestilon Lasu-Ĉifri. (Se vi scias, kion vi faras, uzu '--no-checks' por malŝalti tiujn ĉekojn.)", "update_apt_cache_failed": "Ne eblis ĝisdatigi la kaŝmemoron de APT (paka administranto de Debian). Jen rubujo de la sources.list-linioj, kiuj povus helpi identigi problemajn liniojn:\n{sourceslist}", "migrations_no_migrations_to_run": "Neniuj migradoj por funkcii", "executing_command": "Plenumanta komandon '{command:s}' …", - "diagnosis_no_apps": "Neniu tia instalita app", "certmanager_attempt_to_renew_nonLE_cert": "La atestilo por la domajno '{domain:s}' ne estas elsendita de Let's Encrypt. Ne eblas renovigi ĝin aŭtomate!", "global_settings_setting_example_bool": "Ekzemplo bulea elekto", "domain_dyndns_already_subscribed": "Vi jam abonis DynDNS-domajnon", @@ -280,32 +248,25 @@ "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…", - "diagnosis_monitor_disk_error": "Ne povis monitori diskojn: {error}", "log_link_to_log": "Plena ŝtipo de ĉi tiu operacio: ' {desc} '", - "service_no_log": "Neniu registro por montri por servo '{service:s}'", "global_settings_cant_serialize_settings": "Ne eblis serialigi datumojn pri agordoj, motivo: {reason:s}", "backup_running_hooks": "Kurado de apogaj hokoj …", - "package_not_installed": "Pako '{pkgname}' ne estas instalita", "certmanager_domain_unknown": "Nekonata domajno '{domain:s}'", "unexpected_error": "Io neatendita iris malbone: {error}", "password_listed": "Ĉi tiu pasvorto estas inter la plej uzataj pasvortoj en la mondo. Bonvolu elekti ion pli unikan.", - "ssowat_persistent_conf_write_error": "Ne povis konservi konstantan SSOwat-agordon: {error:s}. Redakti /etc/ssowat/conf.json.persistent dosiero por ripari la Jaks-sintakson", "migration_description_0007_ssh_conf_managed_by_yunohost_step1": "Lasu la SSH-agordon estu administrata de YunoHost (paŝo 1, aŭtomata)", "migration_0009_not_needed": "Ĉi tiu migrado jam iel okazis ... (?) Saltado.", "ssowat_conf_generated": "SSOwat-agordo generita", "migrate_tsig_wait": "Atendante tri minutojn por ke la servilo DynDNS enkalkulu la novan ŝlosilon …", "log_remove_on_failed_restore": "Forigu '{}' post malsukcesa restarigo de rezerva ar archiveivo", "dpkg_is_broken": "Vi ne povas fari ĉi tion nun ĉar dpkg/APT (la administrantoj pri pakaĵaj sistemoj) ŝajnas esti rompita stato ... Vi povas provi solvi ĉi tiun problemon per konekto per SSH kaj funkcianta `sudo dpkg --configure -a`.", - "recommend_to_add_first_user": "La postinstalo finiĝis, sed YunoHost bezonas almenaŭ unu uzanton por funkcii ĝuste, vi devas aldoni unu uzante 'yunohost user create ' aŭ fari ĝin de la administra interfaco.", "certmanager_cert_signing_failed": "Ne povis subskribi la novan atestilon", "migration_description_0003_migrate_to_stretch": "Altgradigu la sistemon al Debian Stretch kaj YunoHost 3.0", "log_tools_upgrade": "Ĝisdatigu sistemajn pakaĵojn", - "network_check_smtp_ko": "Ekstera retpoŝto (SMTP-haveno 25) ŝajnas esti blokita de via reto", "log_available_on_yunopaste": "Ĉi tiu protokolo nun haveblas per {url}", "certmanager_http_check_timeout": "Ekdifinita kiam servilo provis kontakti sin per HTTP per publika IP-adreso (domajno '{domain:s}' kun IP '{ip:s}'). Vi eble spertas haŭtoproblemon, aŭ la fajroŝirmilo / enkursigilo antaŭ via servilo miskonfiguras.", "pattern_port_or_range": "Devas esti valida haveno-nombro (t.e. 0-65535) aŭ gamo da havenoj (t.e. 100:200)", "migrations_loading_migration": "Ŝarĝante migradon {id}…", - "port_available": "Haveno {port:d} estas havebla", "pattern_mailbox_quota": "Devas esti grandeco kun la sufikso b/k/M/G/T aŭ 0 por ne havi kvoton", "migration_0008_general_disclaimer": "Por plibonigi la sekurecon de via servilo, rekomendas lasi YunoHost administri la SSH-agordon. Via nuna SSH-aranĝo diferencas de la rekomendo. Se vi lasas YunoHost agordi ĝin, la maniero per kiu vi konektas al via servilo per SSH ŝanĝiĝos tiel:", "user_deletion_failed": "Ne povis forigi uzanton {user}: {error}", @@ -314,7 +275,6 @@ "global_settings_key_doesnt_exists": "La ŝlosilo '{settings_key:s}' ne ekzistas en la tutmondaj agordoj, vi povas vidi ĉiujn disponeblajn klavojn per uzado de 'yunohost settings list'", "dyndns_no_domain_registered": "Neniu domajno registrita ĉe DynDNS", "dyndns_could_not_check_available": "Ne povis kontroli ĉu {domain:s} haveblas sur {provider:s}.", - "log_app_removelist": "Forigu aplikan liston", "global_settings_setting_example_enum": "Ekzemplo enum elekto", "hook_exec_not_terminated": "Skripto ne finiĝis ĝuste: {path:s}", "service_stopped": "Servo '{service:s}' ĉesis", @@ -325,13 +285,11 @@ "upnp_enabled": "UPnP ŝaltis", "mailbox_used_space_dovecot_down": "La retpoŝta servo de Dovecot devas funkcii, se vi volas akcepti uzitan poŝtan spacon", "restore_system_part_failed": "Ne povis restarigi la sisteman parton '{part:s}'", - "diagnosis_monitor_system_error": "Ne povis monitori sistemon: {error}", "service_stop_failed": "Ne povis maldaŭrigi la servon '{service:s}'\n\nLastatempaj servaj protokoloj: {logs:s}", "unbackup_app": "App '{app:s}' ne konserviĝos", "updating_apt_cache": "Akirante haveblajn ĝisdatigojn por sistemaj pakoj…", "tools_upgrade_at_least_one": "Bonvolu specifi '--apps' aŭ '--system'", "service_already_stopped": "La servo '{service:s}' jam ĉesis", - "unit_unknown": "Nekonata unuo '{unit:s}'", "migration_0003_modified_files": "Bonvolu noti, ke la jenaj dosieroj estis trovitaj mane kaj modifitaj kaj povus esti anstataŭigitaj sekve de la ĝisdatigo: {manually_modified_files}", "tools_upgrade_cant_both": "Ne eblas ĝisdatigi ambaŭ sistemon kaj programojn samtempe", "restore_extracting": "Eltirante bezonatajn dosierojn el la ar theivo…", @@ -339,20 +297,16 @@ "log_app_upgrade": "Ĝisdatigu la aplikon '{}'", "log_help_to_get_failed_log": "La operacio '{desc}' ne povis finiĝi. Bonvolu dividi la plenan ŝtipon de ĉi tiu operacio per la komando 'yunohost log display {name} --share' por akiri helpon", "migration_description_0002_migrate_to_tsig_sha256": "Plibonigu sekurecon de DynDNS TSIG-ĝisdatigoj per SHA-512 anstataŭ MD5", - "monitor_disabled": "Servilo-monitorado nun malŝaltita", - "pattern_port": "Devas esti valida havena numero (t.e. 0-65535)", "port_already_closed": "Haveno {port:d} estas jam fermita por {ip_version:s} rilatoj", "hook_name_unknown": "Nekonata hoko-nomo '{name:s}'", "migration_0003_system_not_fully_up_to_date": "Via sistemo ne estas plene ĝisdata. Bonvolu plenumi regulan ĝisdatigon antaŭ ol ruli la migradon al Stretch.", "dyndns_could_not_check_provide": "Ne povis kontroli ĉu {provider:s} povas provizi {domain:s}.", "dyndns_cron_remove_failed": "Ne povis forigi la cron-laboron DynDNS ĉar: {error}", - "pattern_listname": "Devas esti nur alfanumeraj kaj substrekaj signoj", "restore_nothings_done": "Nenio estis restarigita", "log_tools_postinstall": "Afiŝu vian servilon YunoHost", "dyndns_unavailable": "La domajno '{domain:s}' ne haveblas.", "experimental_feature": "Averto: Ĉi tiu funkcio estas eksperimenta kaj ne konsiderata stabila, vi ne uzu ĝin krom se vi scias kion vi faras.", "root_password_replaced_by_admin_password": "Via radika pasvorto estis anstataŭigita per via administra pasvorto.", - "ssowat_persistent_conf_read_error": "Ne povis legi konstantan SSOwat-agordon: {error:s}. Redakti /etc/ssowat/conf.json.persistent dosiero por ripari la Jaks-sintakson", "migration_description_0005_postgresql_9p4_to_9p6": "Migru datumbazojn de PostgreSQL 9.4 al 9.6", "migration_0008_root": "• Vi ne povos konekti kiel radiko per SSH. Anstataŭe vi uzu la administran uzanton;", "package_unknown": "Nekonata pako '{pkgname}'", @@ -396,10 +350,8 @@ "migration_0006_disclaimer": "YunoHost nun atendas, ke la pasvortoj de admin kaj radiko estos sinkronigitaj. Ĉi tiu migrado anstataŭigas vian radikan pasvorton kun la administran pasvorton.", "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", - "monitor_glances_con_failed": "Ne povis konektiĝi al servilo de Glances", "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_app_fetchlist": "Aldonu liston de aplikoj", "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", @@ -411,16 +363,13 @@ "certmanager_cert_renew_success": "Ni Ĉifru atestilon renovigitan por la domajno '{domain:s}'", "global_settings_reset_success": "Antaŭaj agordoj nun estas rezervitaj al {path:s}", "pattern_domain": "Devas esti valida domajna nomo (t.e. mia-domino.org)", - "package_unexpected_error": "Neatendita eraro okazis prilaborante la pakon '{pkgname}'", "dyndns_key_generating": "Generi DNS-ŝlosilon ... Eble daŭros iom da tempo.", "restore_running_app_script": "Restarigi la programon '{app:s}'…", "migrations_skip_migration": "Salti migradon {id}…", - "mysql_db_init_failed": "MysQL-datumbazo init malsukcesis", "regenconf_file_removed": "Agordodosiero '{conf}' forigita", "log_tools_shutdown": "Enŝaltu vian servilon", "password_too_simple_3": "La pasvorto bezonas almenaŭ 8 signojn kaj enhavas ciferon, majusklon, pli malaltan kaj specialajn signojn", "migration_0003_general_warning": "Bonvolu noti, ke ĉi tiu migrado estas delikata operacio. La teamo de YunoHost faris sian plej bonan revizii kaj testi ĝin, sed la migrado eble ankoraŭ rompos partojn de la sistemo aŭ ĝiaj programoj.\n\nTial oni rekomendas al:\n - Elfari kopion de iuj kritikaj datumoj aŭ app. Pliaj informoj pri https://yunohost.org/backup;\n - Paciencu post lanĉo de la migrado: Depende de via interreta konekto kaj aparataro, eble daŭros kelkaj horoj ĝis ĉio ĝisdatigi.\n\nAldone, la haveno por SMTP, uzata de eksteraj retpoŝtaj klientoj (kiel Thunderbird aŭ K9-Mail) estis ŝanĝita de 465 (SSL / TLS) al 587 (STARTTLS). La malnova haveno (465) aŭtomate fermiĝos, kaj la nova haveno (587) malfermiĝos en la fajrejo. Vi kaj viaj uzantoj * devos adapti la agordon de viaj retpoŝtaj klientoj laŭe.", - "diagnosis_kernel_version_error": "Ne povis akiri la kernan version: {error}", "global_settings_setting_example_int": "Ekzemple int elekto", "backup_output_symlink_dir_broken": "Via arkiva dosierujo '{path:s}' estas rompita ligilo. Eble vi forgesis restarigi aŭ munti aŭ enŝovi la stokadon, al kiu ĝi notas.", "good_practices_about_admin_password": "Vi nun estas por difini novan administran pasvorton. La pasvorto devas esti almenaŭ 8 signoj - kvankam estas bone praktiki uzi pli longan pasvorton (t.e. pasfrazon) kaj / aŭ uzi variaĵon de signoj (majuskloj, minuskloj, ciferoj kaj specialaj signoj).", @@ -433,13 +382,10 @@ "log_backup_restore_system": "Restarigi sistemon de rezerva arkivo", "log_app_change_url": "Ŝanĝu la URL de la apliko '{}'", "service_already_started": "La servo '{service:s}' jam funkcias", - "license_undefined": "nedifinita", "global_settings_setting_security_password_admin_strength": "Admin pasvorta forto", "service_reload_or_restart_failed": "Ne povis reŝargi aŭ rekomenci la servon '{service:s}'\n\nLastatempaj servaj protokoloj: {logs:s}", "migrations_list_conflict_pending_done": "Vi ne povas uzi ambaŭ '--previous' kaj '--done' samtempe.", - "maindomain_changed": "La ĉefa domajno nun ŝanĝiĝis", "server_shutdown_confirm": "La servilo haltos tuj, ĉu vi certas? [{answers:s}]", - "monitor_period_invalid": "Nevalida tempoperiodo", "log_backup_restore_app": "Restarigu '{}' de rezerva ar archiveivo", "log_does_exists": "Ne estas operacio-registro kun la nomo '{log}', uzu 'yunohost loglist' por vidi ĉiujn disponeblajn operaciojn", "service_add_failed": "Ne povis aldoni la servon '{service:s}'", @@ -447,11 +393,9 @@ "this_action_broke_dpkg": "Ĉi tiu ago rompis dpkg / APT (la administrantoj pri la paka sistemo) ... Vi povas provi solvi ĉi tiun problemon per konekto per SSH kaj funkcianta `sudo dpkg --configure -a`.", "log_regen_conf": "Regeneri sistemajn agordojn '{}'", "restore_hook_unavailable": "La restariga skripto por '{part:s}' ne haveblas en via sistemo kaj ankaŭ ne en la ar theivo", - "network_check_smtp_ok": "Eksteren retpoŝto (SMTP-haveno 25) ne estas blokita", "log_dyndns_subscribe": "Aboni al YunoHost-subdominio '{}'", "password_too_simple_4": "La pasvorto bezonas almenaŭ 12 signojn kaj enhavas ciferon, majuskle, pli malaltan kaj specialajn signojn", "migration_0003_main_upgrade": "Komencanta ĉefa ĝisdatigo …", - "user_info_failed": "Ne povis akiri informojn pri uzanto", "regenconf_file_updated": "Agordodosiero '{conf}' ĝisdatigita", "log_help_to_get_log": "Por vidi la protokolon de la operacio '{desc}', uzu la komandon 'yunohost log display {name}'", "global_settings_setting_security_nginx_compatibility": "Kongruo vs sekureca kompromiso por la TTT-servilo NGINX. Afektas la ĉifradojn (kaj aliajn aspektojn pri sekureco)", @@ -466,25 +410,19 @@ "user_created": "Uzanto kreita", "service_description_avahi-daemon": "Permesas al vi atingi vian servilon uzante 'yunohost.local' en via loka reto", "certmanager_attempt_to_replace_valid_cert": "Vi provas anstataŭigi bonan kaj validan atestilon por domajno {domain:s}! (Uzu --forte pretervidi)", - "monitor_stats_period_unavailable": "Ne ekzistas disponeblaj statistikoj por la periodo", "regenconf_updated": "Agordo ĝisdatigita por '{category}'", "update_apt_cache_warning": "Io iris malbone dum la ĝisdatigo de la kaŝmemoro de APT (paka administranto de Debian). Jen rubujo de la sources.list-linioj, kiuj povus helpi identigi problemajn liniojn:\n{sourceslist}", "regenconf_dry_pending_applying": "Kontrolado de pritraktata agordo, kiu estus aplikita por kategorio '{category}'…", "regenconf_file_copy_failed": "Ne povis kopii la novan agordodosieron '{new}' al '{conf}'", "global_settings_setting_example_string": "Ekzemple korda elekto", "restore_already_installed_app": "App kun la ID '{app:s}' estas jam instalita", - "mountpoint_unknown": "Nekonata montpunkto", - "log_tools_maindomain": "Faru de '{}' la ĉefa domajno", - "maindomain_change_failed": "Ne povis ŝanĝi la ĉefan domajnon", "mail_domain_unknown": "Nevalida retadreso por domajno '{domain:s}'. Bonvolu uzi domajnon administritan de ĉi tiu servilo.", "migrations_cant_reach_migration_file": "Ne povis aliri migrajn dosierojn ĉe la vojo '% s'", "pattern_email": "Devas esti valida retpoŝta adreso (t.e. iu@ekzemple.com)", "mail_alias_remove_failed": "Ne povis forigi retpoŝton alias '{mail:s}'", "regenconf_file_manually_removed": "La dosiero de agordo '{conf}' estis forigita permane, kaj ne estos kreita", - "monitor_enabled": "Servilo-monitorado nun", "domain_exists": "La domajno jam ekzistas", "migration_description_0001_change_cert_group_to_sslcert": "Ŝanĝu grupajn permesojn de 'metronomo' al 'ssl-cert'", - "mysql_db_creation_failed": "Ne povis krei MySQL-datumbazon", "ldap_initialized": "LDAP inicializis", "migrate_tsig_not_needed": "Vi ne ŝajnas uzi DynDNS-domajnon, do neniu migrado necesas.", "certmanager_domain_cert_not_selfsigned": "La atestilo por domajno {domain:s} ne estas mem-subskribita. Ĉu vi certas, ke vi volas anstataŭigi ĝin? (Uzu '--force' por fari tion.)", @@ -506,18 +444,14 @@ "server_reboot": "La servilo rekomenciĝos", "regenconf_failed": "Ne povis regeneri la agordon por kategorio(j): {categories}", "domain_uninstall_app_first": "Unu aŭ pluraj programoj estas instalitaj en ĉi tiu domajno. Bonvolu malinstali ilin antaŭ ol daŭrigi la domajnan forigon", - "port_unavailable": "Haveno {port:d} ne haveblas", "service_unknown": "Nekonata servo '{service:s}'", "migration_0003_start": "Komencante migradon al Stretch. La protokoloj haveblos en {logfile}.", - "monitor_stats_no_update": "Neniuj monitoradaj statistikoj ĝisdatigi", "domain_deletion_failed": "Ne eblas forigi domajnon {domain}: {error}", "log_user_update": "Ĝisdatigu uzantinformojn de '{}'", "user_creation_failed": "Ne povis krei uzanton {user}: {error}", "migrations_migration_has_failed": "Migrado {id} ne kompletigis, abolis. Eraro: {exception}", "done": "Farita", "log_domain_remove": "Forigi domon '{}' de agordo de sistemo", - "monitor_not_enabled": "Servila monitorado estas malŝaltita", - "diagnosis_debian_version_error": "Ne povis retrovi la Debianan version: {error}", "hook_list_by_invalid": "Ĉi tiu posedaĵo ne povas esti uzata por listigi hokojn", "confirm_app_install_thirdparty": "Danĝero! Ĉi tiu apliko ne estas parto de la aplika katalogo de Yunohost. Instali triajn aplikojn povas kompromiti la integrecon kaj sekurecon de via sistemo. Vi probable ne devas instali ĝin krom se vi scias kion vi faras. NENIU SUBTENO estos provizita se ĉi tiu app ne funkcias aŭ rompas vian sistemon ... Se vi pretas riski ĉiuokaze, tajpu '{answers:s}'", "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Permesu uzon de (malaktuala) DSA-hostkey por la agordo de daemon SSH", @@ -536,7 +470,6 @@ "certmanager_domain_http_not_working": "Ŝajnas ke la domajno {domain:s} ne atingeblas per HTTP. Kontrolu, ke via DNS kaj NGINX-agordo ĝustas", "domain_cannot_remove_main": "Vi ne povas forigi '{domain:s}' ĉar ĝi estas la ĉefa domajno, vi bezonas unue agordi alian domajnon kiel la ĉefan domajnon per uzado de 'yunohost domain main-domain -n ', jen la listo de kandidataj domajnoj. : {other_domains:s}", "service_reloaded_or_restarted": "La servo '{service:s}' estis reŝarĝita aŭ rekomencita", - "mysql_db_initialized": "La datumbazo MySQL jam estas pravalorizita", "log_domain_add": "Aldonu '{}' domajnon en sisteman agordon", "global_settings_bad_type_for_setting": "Malbona tipo por agordo {setting:s}, ricevita {received_type:s}, atendata {expected_type:s}", "unlimit": "Neniu kvoto", @@ -560,7 +493,6 @@ "group_cannot_edit_primary_group": "La grupo '{group}' ne povas esti redaktita permane. Ĝi estas la primara grupo celita enhavi nur unu specifan uzanton.", "log_permission_url": "Ĝisdatigu url-rilataj al permeso '{}'", "permission_already_up_to_date": "La permeso ne estis ĝisdatigita ĉar la petoj pri aldono/forigo jam kongruas kun la aktuala stato.", - "permission_currently_allowed_for_visitors": "Ĉi tiu permeso estas nuntempe donita al vizitantoj aldone al aliaj grupoj. Vi probable volas aŭ forigi la permeson de \"vizitantoj\" aŭ forigi la aliajn grupojn al kiuj ĝi nun estas koncedita.", "permission_currently_allowed_for_all_users": "Ĉi tiu permeso estas nuntempe donita al ĉiuj uzantoj aldone al aliaj grupoj. Vi probable volas aŭ forigi la permeson \"all_users\" aŭ forigi la aliajn grupojn, kiujn ĝi nuntempe donas.", "app_install_failed": "Ne povis instali {app} : {error}", "app_install_script_failed": "Eraro okazis en la skripto de instalado de la app", @@ -586,9 +518,7 @@ "diagnosis_mail_ougoing_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": "Ne povis atingi vian servilon kiel atendite, ĝi redonis malbonan statuskodon. Povas esti, ke alia maŝino respondis anstataŭ via servilo. Vi devus kontroli, ke vi ĝuste redonas la havenon 80, ke via nginx-agordo ĝisdatigas kaj ke reverso-prokuro ne interbatalas.", "main_domain_changed": "La ĉefa domajno estis ŝanĝita", - "permission_all_users_implicitly_added": "La permeso ankaŭ estis implicite donita al 'all_users' ĉar ĝi bezonas por permesi al la 'visitors' de la speciala grupo", "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ŭ 'yunohost user create ' en komandlinio);\n - diagnozi problemojn atendantajn solvi por ke via servilo funkciu kiel eble plej glate tra la sekcio 'Diagnosis' de la retadministrado (aŭ 'yunohost diagnosis run' en komandlinio);\n - legante la partojn 'Finigi vian agordon' kaj 'Ekkoni Yunohost' en la administra dokumentado: https://yunohost.org/admindoc.", - "permission_cannot_remove_all_users_while_visitors_allowed": "Vi ne povas forigi ĉi tiun permeson por 'all_users' dum ĝi tamen estas permesita por 'visitors'", "migration_description_0014_remove_app_status_json": "Forigi heredajn dosierojn", "diagnosis_ip_connected_ipv4": "La servilo estas konektita al la interreto per IPv4 !", "diagnosis_ip_no_ipv4": "La servilo ne havas funkciantan IPv4.", @@ -606,7 +536,6 @@ "diagnosis_regenconf_manually_modified_details": "Ĉi tio probable estas bona tiel longe kiel vi scias kion vi faras;)!", "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_regenconf_nginx_conf_broken": "La agordo de nginx ŝajnas rompi !", "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}'", @@ -658,9 +587,8 @@ "diagnosis_everything_ok": "Ĉio aspektas bone por {category}!", "diagnosis_failed": "Malsukcesis preni la diagnozan rezulton por kategorio '{category}': {error}", "diagnosis_description_ports": "Ekspoziciaj havenoj", - "diagnosis_description_http": "HTTP-ekspozicio", "diagnosis_description_mail": "Retpoŝto", "log_app_action_run": "Funkciigu agon de la apliko '{}'", "log_app_config_show_panel": "Montri la agordan panelon de la apliko '{}'", "log_app_config_apply": "Apliki agordon al la apliko '{}'" -} +} \ No newline at end of file diff --git a/locales/es.json b/locales/es.json index 51daddf32..30c9980fa 100644 --- a/locales/es.json +++ b/locales/es.json @@ -9,82 +9,53 @@ "app_argument_required": "Se requiere el argumento '{name:s} 7'", "app_extraction_failed": "No se pudieron extraer los archivos de instalación", "app_id_invalid": "ID de la aplicación no válida", - "app_incompatible": "La aplicación {app} no es compatible con su versión de YunoHost", "app_install_files_invalid": "Estos archivos no se pueden instalar", - "app_location_already_used": "La aplicación «{app}» ya está instalada en ({path})", - "app_location_install_failed": "No se puede instalar la aplicación ahí porque entra en conflicto con la aplicación «{other_app}» ya instalada en «{other_path}»", "app_manifest_invalid": "Algo va mal con el manifiesto de la aplicación: {error}", - "app_no_upgrade": "Todas las aplicaciones están ya actualizadas", "app_not_correctly_installed": "La aplicación {app:s} 8 parece estar incorrectamente instalada", "app_not_installed": "No se pudo encontrar la aplicación «{app:s}» en la lista de aplicaciones instaladas: {all_apps}", "app_not_properly_removed": "La {app:s} 0 no ha sido desinstalada correctamente", - "app_package_need_update": "El paquete de la aplicación {app} necesita ser actualizada debido a los cambios en YunoHost", - "app_recent_version_required": "{:s} requiere una versión más reciente de moulinette ", "app_removed": "Eliminado {app:s}", "app_requirements_checking": "Comprobando los paquetes necesarios para {app}…", - "app_requirements_failed": "No se cumplen algunos requisitos para {app}: {error}", "app_requirements_unmeet": "No se cumplen los requisitos para {app}, el paquete {pkgname} ({version}) debe ser {spec}", "app_sources_fetch_failed": "No se pudieron obtener los archivos con el código fuente, ¿es el URL correcto?", "app_unknown": "Aplicación desconocida", "app_unsupported_remote_type": "Tipo remoto no soportado por la aplicación", "app_upgrade_failed": "No se pudo actualizar {app:s}: {error}", "app_upgraded": "Actualizado {app:s}", - "appslist_fetched": "Lista de aplicaciones «{appslist:s}» actualizada", - "appslist_removed": "Eliminada la lista de aplicaciones «{appslist:s}»", - "appslist_retrieve_error": "No se puede recuperar la lista remota de aplicaciones «{appslist:s}»: {error:s}", - "appslist_unknown": "Lista de aplicaciones «{appslist:s}» desconocida.", - "ask_current_admin_password": "Contraseña administrativa actual", "ask_email": "Dirección de correo electrónico", "ask_firstname": "Nombre", "ask_lastname": "Apellido", - "ask_list_to_remove": "Lista para desinstalar", "ask_main_domain": "Dominio principal", "ask_new_admin_password": "Nueva contraseña administrativa", "ask_password": "Contraseña", - "backup_action_required": "Debe especificar algo que guardar", "backup_app_failed": "No se pudo respaldar la aplicación «{app:s}»", "backup_archive_app_not_found": "No se pudo encontrar la aplicación «{app:s}» en el archivo de respaldo", - "backup_archive_hook_not_exec": "El hook {hook:s} no ha sido ejecutado en esta copia de seguridad", "backup_archive_name_exists": "Ya existe un archivo de respaldo con este nombre.", "backup_archive_name_unknown": "Copia de seguridad local desconocida '{name:s}'", "backup_archive_open_failed": "No se pudo abrir el archivo de respaldo", "backup_cleaning_failed": "No se pudo limpiar la carpeta de respaldo temporal", "backup_created": "Se ha creado la copia de seguridad", - "backup_creating_archive": "Creando el archivo de copia de seguridad…", "backup_creation_failed": "No se pudo crear el archivo de respaldo", "backup_delete_error": "No se pudo eliminar «{path:s}»", "backup_deleted": "Eliminada la copia de seguridad", - "backup_extracting_archive": "Extrayendo el archivo de respaldo…", "backup_hook_unknown": "El gancho «{hook:s}» de la copia de seguridad es desconocido", "backup_invalid_archive": "Esto no es un archivo de respaldo", "backup_nothings_done": "Nada que guardar", "backup_output_directory_forbidden": "Elija un directorio de salida diferente. No se pueden crear copias de seguridad en las subcarpetas de /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var o /home/yunohost.backup/archives", "backup_output_directory_not_empty": "Debe elegir un directorio de salida vacío", "backup_output_directory_required": "Debe proporcionar un directorio de salida para la copia de seguridad", - "backup_running_app_script": "Ejecutando la script de copia de seguridad de la aplicación '{app:s}'...", "backup_running_hooks": "Ejecutando los hooks de copia de seguridad...", "custom_app_url_required": "Debe proporcionar una URL para actualizar su aplicación personalizada {app:s}", - "custom_appslist_name_required": "Debe proporcionar un nombre para su lista de aplicaciones personalizadas", - "diagnosis_debian_version_error": "No se pudo obtener la versión de Debian: {error}", - "diagnosis_kernel_version_error": "No se pudo obtener la versión del núcleo: {error}", - "diagnosis_monitor_disk_error": "No se pudieron monitorizar los discos: {error}", - "diagnosis_monitor_network_error": "No se pudo monitorizar la red: {error}", - "diagnosis_monitor_system_error": "No se pudo monitorizar el sistema: {error}", - "diagnosis_no_apps": "No hay tal aplicación instalada", - "dnsmasq_isnt_installed": "Parece que dnsmasq no está instalado, ejecute «apt-get remove bind9 && apt-get install it»", "domain_cert_gen_failed": "No se pudo generar el certificado", "domain_created": "Dominio creado", "domain_creation_failed": "No se pudo crear el dominio {domain}: {error}", "domain_deleted": "Dominio eliminado", "domain_deletion_failed": "No se pudo eliminar el dominio {domain}: {error}", "domain_dyndns_already_subscribed": "Ya se ha suscrito a un dominio de DynDNS", - "domain_dyndns_invalid": "Este dominio no se puede usar con DynDNS", "domain_dyndns_root_unknown": "Dominio raíz de DynDNS desconocido", "domain_exists": "El dominio ya existe", "domain_uninstall_app_first": "Una o más aplicaciones están instaladas en este dominio. Debe desinstalarlas antes de eliminar el dominio", "domain_unknown": "Dominio desconocido", - "domain_zone_exists": "El archivo de zona del DNS ya existe", - "domain_zone_not_found": "No se ha encontrado el archivo de zona del DNS para el dominio [:s]", "done": "Hecho.", "downloading": "Descargando…", "dyndns_cron_installed": "Creado el trabajo de cron de DynDNS", @@ -105,9 +76,6 @@ "firewall_reload_failed": "No se pudo recargar el cortafuegos", "firewall_reloaded": "Cortafuegos recargado", "firewall_rules_cmd_failed": "Algunos comandos para aplicar reglas del cortafuegos han fallado. Más información en el registro.", - "format_datetime_short": "%d/%m/%Y %I:%M %p", - "hook_argument_missing": "Falta un parámetro '{:s}'", - "hook_choice_invalid": "Selección inválida '{:s}'", "hook_exec_failed": "No se pudo ejecutar el guión: {path:s}", "hook_exec_not_terminated": "El guión no terminó correctamente:{path:s}", "hook_list_by_invalid": "Esta propiedad no se puede usar para enumerar ganchos («hooks»)", @@ -117,57 +85,27 @@ "ip6tables_unavailable": "No puede modificar ip6tables aquí. O bien está en un 'container' o su kernel no soporta esta opción", "iptables_unavailable": "No puede modificar iptables aquí. O bien está en un 'container' o su kernel no soporta esta opción", "ldap_initialized": "Inicializado LDAP", - "license_undefined": "indefinido", "mail_alias_remove_failed": "No se pudo eliminar el alias de correo «{mail:s}»", "mail_domain_unknown": "Dirección de correo no válida para el dominio «{domain:s}». Use un dominio administrado por este servidor.", "mail_forward_remove_failed": "No se pudo eliminar el reenvío de correo «{mail:s}»", "main_domain_change_failed": "No se pudo cambiar el dominio principal", "main_domain_changed": "El dominio principal ha cambiado", - "monitor_disabled": "La monitorización del servidor está ahora desactivada", - "monitor_enabled": "La monitorización del servidor está ahora activada", - "monitor_glances_con_failed": "No se pudo conectar al servidor de Glances", - "monitor_not_enabled": "La monitorización del servidor está apagada", - "monitor_period_invalid": "Período de tiempo no válido", - "monitor_stats_file_not_found": "No se pudo encontrar el archivo de estadísticas", - "monitor_stats_no_update": "No hay estadísticas de monitorización para actualizar", - "monitor_stats_period_unavailable": "No hay estadísticas para el período", - "mountpoint_unknown": "Punto de montaje desconocido", - "mysql_db_creation_failed": "No se pudo crear la base de datos MySQL", - "mysql_db_init_failed": "No se pudo inicializar la base de datos MySQL", - "mysql_db_initialized": "La base de datos MySQL está ahora inicializada", - "network_check_mx_ko": "El registro DNS MX no está configurado", - "network_check_smtp_ko": "El correo saliente (SMTP puerto 25) parece estar bloqueado por su red", - "network_check_smtp_ok": "El correo saliente (SMTP puerto 25) no está bloqueado", - "new_domain_required": "Debe proporcionar el nuevo dominio principal", - "no_appslist_found": "No se ha encontrado ninguna lista de aplicaciones", "no_internet_connection": "El servidor no está conectado a Internet", - "no_ipv6_connectivity": "La conexión por IPv6 no está disponible", - "no_restore_script": "No se ha encontrado un script de restauración para la aplicación '{app:s}'", "not_enough_disk_space": "No hay espacio libre suficiente en «{path:s}»", - "package_not_installed": "El paquete «{pkgname}» no está instalado", - "package_unexpected_error": "Ha ocurrido un error inesperado procesando el paquete '{pkgname}'", "package_unknown": "Paquete desconocido '{pkgname}'", - "packages_no_upgrade": "No hay paquetes para actualizar", - "packages_upgrade_critical_later": "Los paquetes críticos ({packages:s}) serán actualizados más tarde", "packages_upgrade_failed": "No se pudieron actualizar todos los paquetes", - "path_removal_failed": "No se pudo eliminar la ruta {:s}", "pattern_backup_archive_name": "Debe ser un nombre de archivo válido con un máximo de 30 caracteres, solo se admiten caracteres alfanuméricos y los caracteres -_. (guiones y punto)", "pattern_domain": "El nombre de dominio debe ser válido (por ejemplo mi-dominio.org)", "pattern_email": "Debe ser una dirección de correo electrónico válida (p.ej. alguien@example.com)", "pattern_firstname": "Debe ser un nombre válido", "pattern_lastname": "Debe ser un apellido válido", - "pattern_listname": "Solo se pueden usar caracteres alfanuméricos y el guion bajo", "pattern_mailbox_quota": "Debe ser un tamaño con el sufijo «b/k/M/G/T» o «0» para no tener una cuota", "pattern_password": "Debe contener al menos 3 caracteres", - "pattern_port": "Debe ser un número de puerto válido (es decir, entre 0-65535)", "pattern_port_or_range": "Debe ser un número de puerto válido (es decir entre 0-65535) o un intervalo de puertos (por ejemplo 100:200)", "pattern_positive_number": "Deber ser un número positivo", "pattern_username": "Solo puede contener caracteres alfanuméricos o el guión bajo", "port_already_closed": "El puerto {port:d} ya está cerrado para las conexiones {ip_version:s}", "port_already_opened": "El puerto {port:d} ya está abierto para las conexiones {ip_version:s}", - "port_available": "El puerto {port:d} está disponible", - "port_unavailable": "El puerto {port:d} no está disponible", - "restore_action_required": "Debe escoger algo que restaurar", "restore_already_installed_app": "Una aplicación con el ID «{app:s}» ya está instalada", "restore_app_failed": "No se pudo restaurar la aplicación «{app:s}»", "restore_cleaning_failed": "No se pudo limpiar el directorio temporal de restauración", @@ -183,30 +121,14 @@ "service_already_started": "El servicio «{service:s}» ya está funcionando", "service_already_stopped": "El servicio «{service:s}» ya ha sido detenido", "service_cmd_exec_failed": "No se pudo ejecutar la orden «{command:s}»", - "service_conf_file_backed_up": "Se ha realizado una copia de seguridad del archivo de configuración '{conf}' en '{backup}'", - "service_conf_file_copy_failed": "No se puede copiar el nuevo archivo de configuración '{new}' a {conf}", - "service_conf_file_manually_modified": "El archivo de configuración '{conf}' ha sido modificado manualmente y no será actualizado", - "service_conf_file_manually_removed": "El archivo de configuración '{conf}' ha sido eliminado manualmente y no será creado", - "service_conf_file_not_managed": "El archivo de configuración '{conf}' no está gestionado y no será actualizado", - "service_conf_file_remove_failed": "No se puede eliminar el archivo de configuración '{conf}'", - "service_conf_file_removed": "El archivo de configuración '{conf}' ha sido eliminado", - "service_conf_file_updated": "El archivo de configuración '{conf}' ha sido actualizado", - "service_conf_up_to_date": "La configuración del servicio '{service}' ya está actualizada", - "service_conf_updated": "La configuración ha sido actualizada para el servicio '{service}'", - "service_conf_would_be_updated": "La configuración podría haber sido actualizada para el servicio '{service} 1'", "service_disable_failed": "No se pudo desactivar el servicio «{service:s}»\n\nRegistro de servicios recientes:{logs:s}", "service_disabled": "El servicio «{service:s}» ha sido desactivado", "service_enable_failed": "No se pudo activar el servicio «{service:s}»\n\nRegistro de servicios recientes:{logs:s}", "service_enabled": "El servicio «{service:s}» ha sido desactivado", - "service_no_log": "No hay ningún registro que mostrar para el servicio «{service:s}»", - "service_regenconf_dry_pending_applying": "Comprobando configuración pendiente que podría haber sido aplicada al servicio '{service}'...", - "service_regenconf_failed": "No se puede regenerar la configuración para el servicio(s): {services}", - "service_regenconf_pending_applying": "Aplicando la configuración pendiente para el servicio '{service}'...", "service_remove_failed": "No se pudo eliminar el servicio «{service:s}»", "service_removed": "Eliminado el servicio «{service:s}»", "service_start_failed": "No se pudo iniciar el servicio «{service:s}»\n\nRegistro de servicios recientes:{logs:s}", "service_started": "Iniciado el servicio «{service:s}»", - "service_status_failed": "No se pudo determinar el estado del servicio «{service:s}»", "service_stop_failed": "No se pudo detener el servicio «{service:s}»\n\nRegistro de servicios recientes:{logs:s}", "service_stopped": "El servicio «{service:s}» se detuvo", "service_unknown": "Servicio desconocido '{service:s}'", @@ -216,10 +138,8 @@ "system_username_exists": "El nombre de usuario ya existe en la lista de usuarios del sistema", "unbackup_app": "La aplicación '{app:s}' no se guardará", "unexpected_error": "Algo inesperado salió mal: {error}", - "unit_unknown": "Unidad desconocida '{unit:s}'", "unlimit": "Sin cuota", "unrestore_app": "La aplicación '{app:s}' no será restaurada", - "update_cache_failed": "No se pudo actualizar la caché de APT", "updating_apt_cache": "Obteniendo las actualizaciones disponibles para los paquetes del sistema…", "upgrade_complete": "Actualización finalizada", "upgrading_packages": "Actualizando paquetes…", @@ -232,7 +152,6 @@ "user_deleted": "Usuario eliminado", "user_deletion_failed": "No se pudo eliminar el usuario {user}: {error}", "user_home_creation_failed": "No se pudo crear la carpeta «home» para el usuario", - "user_info_failed": "No se pudo obtener la información del usuario", "user_unknown": "Usuario desconocido: {user:s}", "user_update_failed": "No se pudo actualizar el usuario {user}: {error}", "user_updated": "Cambiada la información de usuario", @@ -243,8 +162,6 @@ "yunohost_not_installed": "YunoHost no está correctamente instalado. Ejecute «yunohost tools postinstall»", "ldap_init_failed_to_create_admin": "La inicialización de LDAP no pudo crear el usuario «admin»", "mailbox_used_space_dovecot_down": "El servicio de correo Dovecot debe estar funcionando si desea obtener el espacio usado por el buzón de correo", - "ssowat_persistent_conf_read_error": "No se pudo leer la configuración persistente de SSOwat: {error:s}. Edite el archivo /etc/ssowat/conf.json.persistent para corregir la sintaxis de JSON", - "ssowat_persistent_conf_write_error": "No se pudo guardar la configuración persistente de SSOwat: {error:s}. Edite el archivo /etc/ssowat/conf.json.persistent para corregir la sintaxis de JSON", "certmanager_attempt_to_replace_valid_cert": "Está intentando sobrescribir un certificado correcto y válido para el dominio {domain:s}! (Use --force para omitir este mensaje)", "certmanager_domain_unknown": "Dominio desconocido «{domain:s}»", "certmanager_domain_cert_not_selfsigned": "El certificado para el dominio {domain:s} no es un certificado autofirmado. ¿Está seguro de que quiere reemplazarlo? (Use «--force» para hacerlo)", @@ -258,7 +175,6 @@ "certmanager_cert_install_success_selfsigned": "Instalado correctamente un certificado autofirmado para el dominio «{domain:s}»", "certmanager_cert_install_success": "Instalado correctamente un certificado de Let's Encrypt para el dominio «{domain:s}»", "certmanager_cert_renew_success": "Renovado correctamente el certificado de Let's Encrypt para el dominio «{domain:s}»", - "certmanager_old_letsencrypt_app_detected": "\nYunohost ha detectado que la aplicación 'letsencrypt' está instalada, esto produce conflictos con las nuevas funciones de administración de certificados integradas en Yunohost. Si desea utilizar las nuevas funciones integradas, ejecute los siguientes comandos para migrar su instalación:\n\n Yunohost app remove letsencrypt\n Yunohost domain cert-install\n\nP.D.: esto intentará reinstalar los certificados para todos los dominios con un certificado Let's Encrypt o con un certificado autofirmado", "certmanager_hit_rate_limit": "Se han emitido demasiados certificados recientemente para este conjunto exacto de dominios {domain:s}. Pruebe de nuevo más tarde. Vea para más detalles https://letsencrypt.org/docs/rate-limits/", "certmanager_cert_signing_failed": "No se pudo firmar el nuevo certificado", "certmanager_no_cert_file": "No se pudo leer el certificado para el dominio {domain:s} (archivo: {file:s})", @@ -268,37 +184,26 @@ "certmanager_unable_to_parse_self_CA_name": "No se pudo procesar el nombre de la autoridad de autofirma (archivo: {file:s})", "domains_available": "Dominios disponibles:", "backup_archive_broken_link": "No se pudo acceder al archivo de respaldo (enlace roto a {path:s})", - "certmanager_domain_not_resolved_locally": "El dominio {domain:s} no puede ser resuelto desde su servidor de YunoHost. Esto puede suceder si ha modificado su registro DNS recientemente. De ser así, espere unas horas para que se propague. Si el problema continúa, considere añadir {domain:s} a /etc/hosts. (Si sabe lo que está haciendo, use «--no-checks» para desactivar esas comprobaciones.)", "certmanager_acme_not_configured_for_domain": "El certificado para el dominio «{domain:s}» no parece que esté instalado correctamente. Ejecute primero «cert-install» para este dominio.", "certmanager_http_check_timeout": "Tiempo de espera agotado cuando el servidor intentaba conectarse consigo mismo a través de HTTP usando una dirección IP pública (dominio «{domain:s}» con IP «{ip:s}»). Puede que esté experimentando un problema de redirección («hairpinning»), o que el cortafuegos o el enrutador de su servidor esté mal configurado.", "certmanager_couldnt_fetch_intermediate_cert": "Tiempo de espera agotado intentando obtener el certificado intermedio de Let's Encrypt. Cancelada la instalación o renovación del certificado. Vuelva a intentarlo más tarde.", - "appslist_retrieve_bad_format": "No se pudo leer la lista de aplicaciones obtenida «{appslist:s}»", "domain_hostname_failed": "No se pudo establecer un nuevo nombre de anfitrión («hostname»). Esto podría causar problemas más tarde (no es seguro... podría ir bien).", "yunohost_ca_creation_success": "Creada la autoridad de certificación local.", "app_already_installed_cant_change_url": "Esta aplicación ya está instalada. No se puede cambiar el URL únicamente mediante esta función. Compruebe si está disponible la opción `app changeurl`.", - "app_change_no_change_url_script": "La aplicacion {app_name:s} aún no permite cambiar su URL, es posible que deba actualizarla.", "app_change_url_failed_nginx_reload": "No se pudo recargar NGINX. Esta es la salida de «nginx -t»:\n{nginx_errors:s}", "app_change_url_identical_domains": "El antiguo y nuevo dominio/url_path son idénticos ('{domain:s} {path:s}'), no se realizarán cambios.", "app_change_url_no_script": "La aplicación «{app_name:s}» aún no permite la modificación de URLs. Quizás debería actualizarla.", "app_change_url_success": "El URL de la aplicación {app:s} es ahora {domain:s} {path:s}", "app_location_unavailable": "Este URL o no está disponible o está en conflicto con otra(s) aplicación(es) instalada(s):\n{apps:s}", "app_already_up_to_date": "La aplicación {app:s} ya está actualizada", - "appslist_name_already_tracked": "Ya existe una lista de aplicaciones registradas con el nombre {name:s}.", - "appslist_url_already_tracked": "Ya existe una lista de aplicaciones registradas con el URL {url:s}.", - "appslist_migrating": "Migrando la lista de aplicaciones «{appslist:s}»…", - "appslist_could_not_migrate": "¡No se pudo migrar la lista de aplicaciones «{appslist:s}»! No se pudo analizar el URL… El antiguo trabajo de cron se mantuvo en {bkp_file:s}.", - "appslist_corrupted_json": "No se pudieron cargar las listas de aplicaciones. Parece que {filename:s} está dañado.", - "invalid_url_format": "Algo va mal con el URL", "app_upgrade_some_app_failed": "No se pudieron actualizar algunas aplicaciones", "app_make_default_location_already_used": "No puede hacer que la aplicación «{app}» sea la predeterminada en el dominio, «{domain}» ya está siendo usado por otra aplicación «{other_app}»", "app_upgrade_app_name": "Actualizando ahora {app}…", - "ask_path": "Camino", "backup_abstract_method": "Este método de respaldo aún no se ha implementado", "backup_applying_method_borg": "Enviando todos los archivos para la copia de seguridad al repositorio de borg-backup…", "backup_applying_method_copy": "Copiando todos los archivos a la copia de seguridad…", "backup_applying_method_custom": "Llamando al método de copia de seguridad personalizado «{method:s}»…", "backup_applying_method_tar": "Creando el archivo TAR de respaldo…", - "backup_archive_mount_failed": "No se pudo montar el archivo de respaldo", "backup_archive_system_part_not_available": "La parte del sistema «{part:s}» no está disponible en esta copia de seguridad", "backup_archive_writing_error": "No se pudieron añadir los archivos «{source:s}» (llamados en el archivo «{dest:s}») para ser respaldados en el archivo comprimido «{archive:s}»", "backup_ask_for_copying_if_needed": "¿Quiere realizar la copia de seguridad usando {size:s} MB temporalmente? (Se usa este modo ya que algunos archivos no se pudieron preparar usando un método más eficiente.)", @@ -309,7 +214,6 @@ "backup_csv_addition_failed": "No se pudo añadir archivos para respaldar en el archivo CSV", "backup_csv_creation_failed": "No se pudo crear el archivo CSV necesario para la restauración", "backup_custom_mount_error": "El método de respaldo personalizado no pudo superar el paso «mount»", - "backup_custom_need_mount_error": "El método de respaldo personalizado no pudo superar el paso «need_mount»", "backup_no_uncompress_archive_dir": "No existe tal directorio de archivos sin comprimir", "backup_php5_to_php7_migration_may_fail": "No se pudo convertir su archivo para que sea compatible con PHP 7, puede que no pueda restaurar sus aplicaciones de PHP (motivo: {error:s})", "backup_system_part_failed": "No se pudo respaldar la parte del sistema «{part:s}»", @@ -325,9 +229,6 @@ "password_too_simple_3": "La contraseña tiene que ser de al menos 8 caracteres de longitud e incluir un número, mayúsculas, minúsculas y caracteres especiales", "password_too_simple_4": "La contraseña tiene que ser de al menos 12 caracteres de longitud e incluir un número, mayúsculas, minúsculas y caracteres especiales", "users_available": "Usuarios disponibles:", - "user_not_in_group": "El usuario «{user:s}» no está en el grupo «{group:s}»", - "user_already_in_group": "El usuario «{user:}» ya está en el grupo «{group:s}»", - "updating_app_lists": "Obteniendo actualizaciones disponibles para las aplicaciones…", "update_apt_cache_warning": "Algo fue mal durante la actualización de la caché de APT (gestor de paquetes de Debian). Aquí tiene un volcado de las líneas de sources.list que podría ayudarle a identificar las líneas problemáticas:\n{sourceslist}", "update_apt_cache_failed": "No se pudo actualizar la caché de APT (gestor de paquetes de Debian). Aquí tiene un volcado de las líneas de sources.list que podría ayudarle a identificar las líneas problemáticas:\n{sourceslist}", "tools_upgrade_special_packages_completed": "Actualización de paquetes de YunoHost completada.\nPulse [Intro] para regresar a la línea de órdenes", @@ -339,9 +240,7 @@ "tools_upgrade_cant_hold_critical_packages": "No se pudieron retener los paquetes críticos…", "tools_upgrade_cant_both": "No se puede actualizar el sistema y las aplicaciones al mismo tiempo", "tools_upgrade_at_least_one": "Especifique «--apps», o «--system»", - "tools_update_failed_to_app_fetchlist": "No se pudo actualizar la lista de aplicaciones de YunoHost porque: {error}", "this_action_broke_dpkg": "Esta acción rompió dpkg/APT(los gestores de paquetes del sistema)… Puede tratar de solucionar este problema conectando mediante SSH y ejecutando `sudo dpkg --configure -a`.", - "system_groupname_exists": "El nombre de grupo ya existe en el grupo del sistema", "service_reloaded_or_restarted": "El servicio «{service:s}» ha sido recargado o reiniciado", "service_reload_or_restart_failed": "No se pudo recargar o reiniciar el servicio «{service:s}»\n\nRegistro de servicios recientes:{logs:s}", "service_restarted": "Reiniciado el servicio «{service:s}»", @@ -354,7 +253,6 @@ "service_description_ssh": "Permite conectar a su servidor remotamente mediante un terminal (protocolo SSH)", "service_description_slapd": "Almacena usuarios, dominios e información relacionada", "service_description_rspamd": "Filtra correo no deseado y otras características relacionadas con el correo", - "service_description_rmilter": "Comprueba varios parámetros en el correo", "service_description_redis-server": "Una base de datos especializada usada para el acceso rápido de datos, cola de tareas y comunicación entre programas", "service_description_postfix": "Usado para enviar y recibir correos", "service_description_php7.0-fpm": "Ejecuta aplicaciones escritas en PHP con NGINX", @@ -362,7 +260,6 @@ "service_description_nginx": "Sirve o proporciona acceso a todos los sitios web alojados en su servidor", "service_description_mysql": "Almacena los datos de la aplicación (base de datos SQL)", "service_description_metronome": "Gestionar las cuentas XMPP de mensajería instantánea", - "service_description_glances": "Supervisa la información del sistema en su servidor", "service_description_fail2ban": "Protege contra ataques de fuerza bruta y otras clases de ataques desde Internet", "service_description_dovecot": "Permite a los clientes de correo acceder/obtener correo (vía IMAP y POP3)", "service_description_dnsmasq": "Maneja la resolución de nombres de dominio (DNS)", @@ -376,7 +273,6 @@ "restore_system_part_failed": "No se pudo restaurar la parte del sistema «{part:s}»", "restore_removing_tmp_dir_failed": "No se pudo eliminar un directorio temporal antiguo", "restore_not_enough_disk_space": "Espacio insuficiente (espacio: {free_space:d} B, espacio necesario: {needed_space:d} B, margen de seguridad: {margin:d} B)", - "restore_mounting_archive": "Montando archivo en «{path:s}»", "restore_may_be_not_enough_disk_space": "Parece que su sistema no tiene suficiente espacio (libre: {free_space:d} B, espacio necesario: {needed_space:d} B, margen de seguridad: {margin:d} B)", "restore_extracting": "Extrayendo los archivos necesarios para el archivo…", "regenconf_pending_applying": "Aplicando la configuración pendiente para la categoría «{category}»…", @@ -394,23 +290,16 @@ "regenconf_file_kept_back": "Se espera que el archivo de configuración «{conf}» sea eliminado por regen-conf (categoría {category}) pero ha sido retenido.", "regenconf_file_copy_failed": "No se pudo copiar el nuevo archivo de configuración «{new}» a «{conf}»", "regenconf_file_backed_up": "Archivo de configuración «{conf}» respaldado en «{backup}»", - "remove_user_of_group_not_allowed": "No tiene permiso para eliminar al usuario «{user:s}» en el grupo «{group:s}»", - "remove_main_permission_not_allowed": "No se permite eliminar el permiso principal", - "recommend_to_add_first_user": "La posinstalación ha terminado pero YunoHost necesita al menos un usuario para funcionar correctamente, debe añadir uno ejecutando «yunohost user create » o usando la interfaz de administración.", "permission_update_nothing_to_do": "No hay permisos para actualizar", "permission_updated": "Actualizado el permiso «{permission:s}»", - "permission_generated": "Actualizada la base de datos de permisos", "permission_update_failed": "No se pudo actualizar el permiso «{permission}» : {error}", - "permission_name_not_valid": "Elija un nombre de permiso permitido para «{permission:s}", "permission_not_found": "No se encontró el permiso «{permission:s}»", "permission_deletion_failed": "No se pudo eliminar el permiso «{permission}»: {error}", "permission_deleted": "Eliminado el permiso «{permission:s}»", "permission_creation_failed": "No se pudo crear el permiso «{permission}»: {error}", "permission_created": "Creado el permiso «{permission:s}»", "permission_already_exist": "El permiso «{permission}» ya existe", - "permission_already_clear": "El permiso «{permission:s}» ya está definido para la aplicación {app:s}", "pattern_password_app": "Las contraseñas no pueden incluir los siguientes caracteres: {forbidden_chars}", - "need_define_permission_before": "Redefina los permisos ejecutando «yunohost user permission add -u USUARIO» antes de eliminar un grupo permitido", "migrations_to_be_ran_manually": "La migración {id} hay que ejecutarla manualmente. Vaya a Herramientas → Migraciones en la página web de administración o ejecute `yunohost tools migrations migrate`.", "migrations_success_forward": "Migración {id} completada", "migrations_skip_migration": "Omitiendo migración {id}…", @@ -435,7 +324,6 @@ "migration_0011_migration_failed_trying_to_rollback": "No se pudo migrar… intentando revertir el sistema.", "migration_0011_migrate_permission": "Migrando permisos desde la configuración de las aplicaciones a LDAP…", "migration_0011_LDAP_update_failed": "No se pudo actualizar LDAP. Error: {error:s}", - "migration_0011_LDAP_config_dirty": "Parece que ha personalizado la configuración de LDAP. Para esta migración se necesita actualizar la configuración de LDAP.\nNecesita guardar su configuración actual, reiniciar la configuración original ejecutando «yunohost tools regen-conf -f» y reintentar la migración", "migration_0011_done": "Migración finalizada. Ahora puede gestionar los grupos de usuarios.", "migration_0011_create_group": "Creando un grupo para cada usuario…", "migration_0011_can_not_backup_before_migration": "El respaldo del sistema no se pudo completar antes de que la migración fallase. Error: {error:s}", @@ -492,21 +380,14 @@ "log_tools_upgrade": "Actualizar paquetes del sistema", "log_tools_postinstall": "Posinstalación del servidor YunoHost", "log_tools_migrations_migrate_forward": "Inicializa la migración", - "log_tools_maindomain": "Convertir «{}» en el dominio principal", - "log_user_permission_remove": "Actualizar permiso «{}»", - "log_user_permission_add": "Actualizar permiso «{}»", "log_user_update": "Actualizar la información de usuario de «{}»", "log_user_group_update": "Actualizar grupo «{}»", "log_user_group_delete": "Eliminar grupo «{}»", - "log_user_group_add": "Añadir grupo «{}»", "log_user_delete": "Eliminar usuario «{}»", "log_user_create": "Añadir usuario «{}»", "log_regen_conf": "Regenerar la configuración del sistema «{}»", "log_letsencrypt_cert_renew": "Renovar el certificado «{}» de Let's Encrypt", "log_selfsigned_cert_install": "Instalar el certificado auto-firmado en el dominio '{}'", - "log_permission_update": "Actualizar permiso «{}» para la aplicación «{}»", - "log_permission_remove": "Eliminar permiso «{}»", - "log_permission_add": "Añadir el permiso «{}» para la aplicación «{}»", "log_letsencrypt_cert_install": "Instalar un certificado de Let's Encrypt en el dominio «{}»", "log_dyndns_update": "Actualizar la IP asociada con su subdominio de YunoHost «{}»", "log_dyndns_subscribe": "Subscribirse a un subdomino de YunoHost «{}»", @@ -522,11 +403,6 @@ "log_app_remove": "Eliminar la aplicación «{}»", "log_app_install": "Instalar la aplicación «{}»", "log_app_change_url": "Cambiar el URL de la aplicación «{}»", - "log_app_removelist": "Eliminar una lista de aplicaciones", - "log_app_fetchlist": "Añadir una lista de aplicaciones", - "log_app_clearaccess": "Eliminar todos los accesos a «{}»", - "log_app_removeaccess": "Eliminar acceso a «{}»", - "log_app_addaccess": "Añadir acceso a «{}»", "log_operation_unit_unclosed_properly": "La unidad de operación no se ha cerrado correctamente", "log_does_exists": "No existe ningún registro de actividades con el nombre '{log}', ejecute 'yunohost log list' para ver todos los registros de actividades disponibles", "log_help_to_get_failed_log": "No se pudo completar la operación «{desc}». Para obtener ayuda, comparta el registro completo de esta operación ejecutando la orden «yunohost log display {name} --share»", @@ -539,15 +415,10 @@ "group_update_failed": "No se pudo actualizar el grupo «{group}»: {error}", "group_updated": "Grupo «{group}» actualizado", "group_unknown": "El grupo «{group:s}» es desconocido", - "group_info_failed": "No se pudo mostrar la información del grupo", - "group_deletion_not_allowed": "No se puede eliminar el grupo {group:s} manualmente.", "group_deletion_failed": "No se pudo eliminar el grupo «{group}»: {error}", "group_deleted": "Eliminado el grupo «{group}»", "group_creation_failed": "No se pudo crear el grupo «{group}»: {error}", "group_created": "Creado el grupo «{group}»", - "group_name_already_exist": "El grupo {name:s} ya existe", - "group_already_disallowed": "El grupo «{group:s}» ya tiene desactivado el permiso «{permission:s}» para la aplicación «{app:s}»", - "group_already_allowed": "El grupo «{group:s}» ya tiene activado el permiso «{permission:s}» para la aplicación «{app:s}»", "good_practices_about_admin_password": "Va a establecer una nueva contraseña de administración. La contraseña debería tener al menos 8 caracteres, aunque es una buena práctica usar una contraseña más extensa (básicamente una frase) y/o usar caracteres de varias clases (mayúsculas, minúsculas, números y caracteres especiales).", "global_settings_unknown_type": "Situación imprevista, la configuración {setting:s} parece tener el tipo {unknown_type:s} pero no es un tipo compatible con el sistema.", "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Permitir el uso de la llave (obsoleta) DSA para la configuración del demonio SSH", @@ -569,11 +440,7 @@ "global_settings_bad_type_for_setting": "Tipo erróneo para la configuración {setting:s}, obtuvo {received_type:s}, esperado {expected_type:s}", "global_settings_bad_choice_for_enum": "Opción errónea para la configuración {setting:s}, obtuvo «{choice:s}» pero las opciones disponibles son: {available_choices:s}", "file_does_not_exist": "El archivo {path:s} no existe.", - "error_when_removing_sftpuser_group": "No se pudo eliminar el grupo sftpusers", - "edit_permission_with_group_all_users_not_allowed": "No puede editar el permiso para el grupo «all_users», utilice «yunohost user permission clear APLICACIÓN» o «yunohost user permission add APLICACIÓN -u USUARIO».", - "edit_group_not_allowed": "No tiene permiso para editar el grupo {group:s}", "dyndns_could_not_check_available": "No se pudo comprobar si {domain:s} está disponible en {provider:s}.", - "domain_dyndns_dynette_is_unreachable": "No se pudo conectar a dynette de YunoHost. O su YunoHost no está correctamente conectado a Internet o el servidor dynette está caído. Error: {error}", "domain_dns_conf_is_just_a_recommendation": "Esta orden muestra la configuración *recomendada*. No configura el DNS en realidad. Es su responsabilidad configurar la zona de DNS en su registrador según esta recomendación.", "dpkg_lock_not_available": "Esta orden no se puede ejecutar en este momento ,parece que programa está usando el bloqueo de dpkg (el gestor de paquetes del sistema)", "dpkg_is_broken": "No puede hacer esto en este momento porque dpkg/apt (los gestores de paquetes del sistema) parecen estar en un estado roto... Puede tratar de solucionar este problema conectando a través de SSH y ejecutando `sudo dpkg --configure -a`.", @@ -592,8 +459,6 @@ "backup_actually_backuping": "Creando un archivo de respaldo de los archivos obtenidos…", "ask_new_path": "Nueva ruta", "ask_new_domain": "Nuevo dominio", - "apps_permission_restoration_failed": "Otorgar el permiso «{permission:s}» para restaurar {app:s}", - "apps_permission_not_found": "No se han encontrado permisos para las aplicaciones instaladas", "app_upgrade_several_apps": "Las siguientes aplicaciones se actualizarán: {apps}", "app_start_restore": "Restaurando aplicación «{app}»…", "app_start_backup": "Obteniendo archivos para el respaldo de «{app}»…", @@ -604,20 +469,17 @@ "already_up_to_date": "Nada que hacer. Todo está actualizado.", "admin_password_too_long": "Elija una contraseña de menos de 127 caracteres", "aborting": "Cancelando.", - "app_upgrade_stopped": "Se ha detenido la actualización de todas las aplicaciones para prevenir un posible daño porque una aplicación no se pudo actualizar", "app_action_broke_system": "Esta acción parece que ha roto estos servicios importantes: {services}", "operation_interrupted": "¿Ha sido interrumpida la operación manualmente?", "apps_already_up_to_date": "Todas las aplicaciones están ya actualizadas", "dyndns_provider_unreachable": "No se puede conectar con el proveedor de Dyndns {provider}: o su YunoHost no está correctamente conectado a Internet o el servidor dynette está caído.", "group_already_exist": "El grupo {group} ya existe", "group_already_exist_on_system": "El grupo {group} ya existe en los grupos del sistema", - "group_cannot_be_edited": "El grupo {group} no se puede editar manualmente.", "group_cannot_be_deleted": "El grupo {group} no se puede eliminar manualmente.", "group_user_already_in_group": "El usuario {user} ya está en el grupo {group}", "group_user_not_in_group": "El usuario {user} no está en el grupo {group}", "log_permission_create": "Crear permiso «{}»", "log_permission_delete": "Eliminar permiso «{}»", - "log_permission_urls": "Actualizar URLs relacionadas con el permiso «{}»", "log_user_group_create": "Crear grupo «{}»", "log_user_permission_update": "Actualizar los accesos para el permiso «{}»", "log_user_permission_reset": "Restablecer permiso «{}»", @@ -635,7 +497,6 @@ "log_permission_url": "Actualizar la URL relacionada con el permiso «{}»", "migration_0011_slapd_config_will_be_overwritten": "Parece que ha editado manualmente la configuración de slapd. Para esta migración crítica, YunoHost necesita forzar la actualización de la configuración de slapd. Los archivos originales se respaldarán en {conf_backup_folder}.", "permission_already_up_to_date": "El permiso no se ha actualizado porque las peticiones de incorporación o eliminación ya coinciden con el estado actual.", - "permission_currently_allowed_for_visitors": "Este permiso se concede actualmente a los visitantes además de otros grupos. Probablemente quiere o eliminar el permiso de «visitors» o eliminar los otros grupos a los que está otorgado actualmente.", "permission_currently_allowed_for_all_users": "Este permiso se concede actualmente a todos los usuarios además de los otros grupos. Probablemente quiere o eliminar el permiso de «all_users» o eliminar los otros grupos a los que está otorgado actualmente.", "permission_require_account": "El permiso {permission} solo tiene sentido para usuarios con una cuenta y, por lo tanto, no se puede activar para visitantes.", "app_remove_after_failed_install": "Eliminando la aplicación tras el fallo de instalación…", @@ -696,7 +557,6 @@ "diagnosis_regenconf_manually_modified_details": "Esto este probablemente BIEN siempre y cuando sepas lo que estas haciendo ;) !", "diagnosis_regenconf_manually_modified_debian": "El archivos de configuración {file} fue modificado manualmente comparado con el valor predeterminado de Debian.", "diagnosis_regenconf_manually_modified_debian_details": "Esto este probablemente BIEN, pero igual no lo pierdas de vista...", - "diagnosis_regenconf_nginx_conf_broken": "La configuración nginx parece rota!", "diagnosis_security_all_good": "Ninguna vulnerabilidad critica de seguridad fue encontrada.", "diagnosis_security_vulnerable_to_meltdown": "Pareces vulnerable a el colapso de vulnerabilidad critica de seguridad.", "diagnosis_description_basesystem": "Sistema de base", @@ -735,4 +595,4 @@ "diagnosis_http_ok": "El Dominio {domain} es accesible desde internet a través de HTTP.", "diagnosis_http_could_not_diagnose": "No se pudo verificar si el dominio es accesible desde internet. Error: {error}", "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" -} +} \ No newline at end of file diff --git a/locales/eu.json b/locales/eu.json index 539fb9157..1891e00a3 100644 --- a/locales/eu.json +++ b/locales/eu.json @@ -1,3 +1,3 @@ { "password_too_simple_1": "Pasahitzak gutxienez 8 karaktere izan behar ditu" -} +} \ No newline at end of file diff --git a/locales/fr.json b/locales/fr.json index e44d592e0..125df66ad 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -6,86 +6,56 @@ "app_already_installed": "{app:s} est déjà installé", "app_argument_choice_invalid": "Choix invalide pour le paramètre '{name:s}', il doit être l’un de {choices:s}", "app_argument_invalid": "Valeur invalide pour le paramètre '{name:s}' : {error:s}", - "app_argument_missing": "Paramètre manquant « {:s} »", "app_argument_required": "Le paramètre '{name:s}' est requis", "app_extraction_failed": "Impossible d’extraire les fichiers d’installation", "app_id_invalid": "Identifiant d’application invalide", - "app_incompatible": "L’application {app} est incompatible avec votre version de YunoHost", "app_install_files_invalid": "Fichiers d’installation incorrects", - "app_location_already_used": "L’application '{app}' est déjà installée à cet emplacement ({path})", - "app_location_install_failed": "Impossible d’installer l’application à cet emplacement pour cause de conflit avec l’application '{other_app}' déjà installée sur '{other_path}'", "app_manifest_invalid": "Manifeste d’application incorrect : {error}", - "app_no_upgrade": "Aucune application à mettre à jour", "app_not_correctly_installed": "{app:s} semble être mal installé", "app_not_installed": "Nous n’avons pas trouvé l’application « {app:s} » dans la liste des applications installées: {all_apps}", "app_not_properly_removed": "{app:s} n’a pas été supprimé correctement", - "app_package_need_update": "Le paquet de l’application {app} doit être mis à jour pour être en adéquation avec les changements de YunoHost", - "app_recent_version_required": "{app:s} nécessite une version plus récente de YunoHost", "app_removed": "{app:s} supprimé", "app_requirements_checking": "Vérification des paquets requis pour {app} …", - "app_requirements_failed": "Impossible de satisfaire les pré-requis pour {app} : {error}", "app_requirements_unmeet": "Les pré-requis de {app} ne sont pas satisfaits, le paquet {pkgname} ({version}) doit être {spec}", "app_sources_fetch_failed": "Impossible de récupérer les fichiers sources, l'URL est-elle correcte ?", "app_unknown": "Application inconnue", "app_unsupported_remote_type": "Ce type de commande à distance utilisé pour cette application n'est pas supporté", "app_upgrade_failed": "Impossible de mettre à jour {app:s} : {error}", "app_upgraded": "{app:s} mis à jour", - "appslist_fetched": "La liste d’applications mise à jour '{appslist:s}'", - "appslist_removed": "La liste d'applications '{appslist:s}' a été supprimée", - "appslist_retrieve_error": "Impossible de récupérer la liste d’applications distante '{appslist:s}' : {error:s}", - "appslist_unknown": "La liste d’applications '{appslist:s}' est inconnue.", - "ask_current_admin_password": "Mot de passe d’administration actuel", "ask_email": "Adresse de courriel", "ask_firstname": "Prénom", "ask_lastname": "Nom", - "ask_list_to_remove": "Liste à supprimer", "ask_main_domain": "Domaine principal", "ask_new_admin_password": "Nouveau mot de passe d’administration", "ask_password": "Mot de passe", - "backup_action_required": "Vous devez préciser ce qui est à sauvegarder", "backup_app_failed": "Impossible de sauvegarder l’application '{app:s}'", "backup_archive_app_not_found": "L’application '{app:s}' n’a pas été trouvée dans l’archive de la sauvegarde", - "backup_archive_hook_not_exec": "Le script « {hook:s} » n'a pas été exécuté dans cette sauvegarde", "backup_archive_name_exists": "Une archive de sauvegarde avec ce nom existe déjà.", "backup_archive_name_unknown": "L’archive locale de sauvegarde nommée '{name:s}' est inconnue", "backup_archive_open_failed": "Impossible d’ouvrir l’archive de la sauvegarde", "backup_cleaning_failed": "Impossible de nettoyer le dossier temporaire de sauvegarde", "backup_created": "Sauvegarde terminée", - "backup_creating_archive": "Création de l’archive de sauvegarde …", "backup_creation_failed": "Impossible de créer l'archive de la sauvegarde", "backup_delete_error": "Impossible de supprimer '{path:s}'", "backup_deleted": "La sauvegarde a été supprimée", - "backup_extracting_archive": "Extraction de l’archive de sauvegarde …", "backup_hook_unknown": "Script de sauvegarde '{hook:s}' inconnu", "backup_invalid_archive": "Archive de sauvegarde invalide", "backup_nothings_done": "Il n’y a rien à sauvegarder", "backup_output_directory_forbidden": "Dossier de destination interdit. Les sauvegardes ne peuvent être créées dans les sous-dossiers /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var ou /home/yunohost.backup/archives", "backup_output_directory_not_empty": "Le répertoire de destination n'est pas vide", "backup_output_directory_required": "Vous devez spécifier un dossier de destination pour la sauvegarde", - "backup_running_app_script": "Lancement du script de sauvegarde de l’application « {app:s} »...", "backup_running_hooks": "Exécution des scripts de sauvegarde …", "custom_app_url_required": "Vous devez spécifier une URL pour mettre à jour votre application personnalisée {app:s}", - "custom_appslist_name_required": "Vous devez spécifier un nom pour votre liste d’applications personnalisées", - "diagnosis_debian_version_error": "Impossible de déterminer la version de Debian : {error}", - "diagnosis_kernel_version_error": "Impossible de récupérer la version du noyau : {error}", - "diagnosis_monitor_disk_error": "Impossible de superviser les disques : {error}", - "diagnosis_monitor_network_error": "Impossible de superviser le réseau : {error}", - "diagnosis_monitor_system_error": "Impossible de superviser le système : {error}", - "diagnosis_no_apps": "Aucune application installée", - "dnsmasq_isnt_installed": "dnsmasq ne semble pas être installé, veuillez lancer 'apt-get remove bind9 && apt-get install dnsmasq'", "domain_cert_gen_failed": "Impossible de générer le certificat", "domain_created": "Le domaine a été créé", "domain_creation_failed": "Impossible de créer le domaine {domain}: {error}", "domain_deleted": "Le domaine a été supprimé", "domain_deletion_failed": "Impossible de supprimer le domaine {domain}:{error}", "domain_dyndns_already_subscribed": "Vous avez déjà souscris à un domaine DynDNS", - "domain_dyndns_invalid": "Domaine incorrect pour un usage avec DynDNS", "domain_dyndns_root_unknown": "Domaine DynDNS principal inconnu", "domain_exists": "Le domaine existe déjà", "domain_uninstall_app_first": "Une ou plusieurs applications sont installées sur ce domaine. Veuillez d’abord les désinstaller avant de supprimer ce domaine", "domain_unknown": "Domaine inconnu", - "domain_zone_exists": "Le fichier de zone DNS existe déjà", - "domain_zone_not_found": "Fichier de zone DNS introuvable pour le domaine {:s}", "done": "Terminé", "downloading": "Téléchargement en cours …", "dyndns_cron_installed": "La tâche cron pour le domaine DynDNS a été créée", @@ -106,9 +76,6 @@ "firewall_reload_failed": "Impossible de recharger le pare-feu", "firewall_reloaded": "Pare-feu rechargé", "firewall_rules_cmd_failed": "Certaines règles du pare-feu n’ont pas pu être appliquées. Plus d'info dans le journal de log.", - "format_datetime_short": "%d/%m/%Y %H:%M", - "hook_argument_missing": "Argument manquant : '{:s}'", - "hook_choice_invalid": "Choix incorrect : '{:s}'", "hook_exec_failed": "Échec de l’exécution du script : {path:s}", "hook_exec_not_terminated": "L’exécution du script {path:s} ne s’est pas terminée correctement", "hook_list_by_invalid": "Propriété invalide pour lister les actions par celle-ci", @@ -118,58 +85,27 @@ "ip6tables_unavailable": "Vous ne pouvez pas jouer avec ip6tables ici. Vous êtes soit dans un conteneur, soit votre noyau ne le prend pas en charge", "iptables_unavailable": "Vous ne pouvez pas jouer avec iptables ici. Vous êtes soit dans un conteneur, soit votre noyau ne le prend pas en charge", "ldap_initialized": "L’annuaire LDAP initialisé", - "license_undefined": "indéfinie", "mail_alias_remove_failed": "Impossible de supprimer l’alias de courriel '{mail:s}'", "mail_domain_unknown": "Le domaine '{domain:s}' de cette adress de courriel n'est pas valide. Merci d'utiliser un domain administré par ce serveur.", "mail_forward_remove_failed": "Impossible de supprimer le courriel de transfert '{mail:s}'", "main_domain_change_failed": "Impossible de modifier le domaine principal", "main_domain_changed": "Le domaine principal modifié", - "monitor_disabled": "Surveillance du serveur est maintenant arrêté", - "monitor_enabled": "La supervision du serveur est maintenant allumée", - "monitor_glances_con_failed": "Impossible de se connecter au serveur Glances", - "monitor_not_enabled": "Le suivi de l’état du serveur n’est pas activé", - "monitor_period_invalid": "Période de temps incorrecte", - "monitor_stats_file_not_found": "Impossible de trouver le fichier de statistiques", - "monitor_stats_no_update": "Aucune donnée de l’état du serveur à mettre à jour", - "monitor_stats_period_unavailable": "Aucune statistique n’est disponible pour la période", - "mountpoint_unknown": "Point de montage inconnu", - "mysql_db_creation_failed": "Impossible de créer la base de données MySQL", - "mysql_db_init_failed": "Impossible d'initialiser la base de données MySQL", - "mysql_db_initialized": "La base de données MySQL est maintenant initialisée", - "network_check_mx_ko": "L’enregistrement DNS MX n’est pas défini", - "network_check_smtp_ko": "Le trafic courriel sortant (port 25 SMTP) semble bloqué par votre réseau", - "network_check_smtp_ok": "Le trafic courriel sortant (port 25 SMTP) n’est pas bloqué", - "new_domain_required": "Vous devez spécifier le nouveau domaine principal", - "no_appslist_found": "Aucune liste d’applications n’a été trouvée", "no_internet_connection": "Le serveur n’est pas connecté à Internet", - "no_ipv6_connectivity": "La connectivité IPv6 n’est pas disponible", - "no_restore_script": "Le script de sauvegarde n’a pas été trouvé pour l’application '{app:s}'", - "no_such_conf_file": "Le fichier {file:s} n’existe pas, il ne peut pas être copié", "not_enough_disk_space": "L’espace disque est insuffisant sur '{path:s}'", - "package_not_installed": "Le paquet '{pkgname}' n’est pas installé", - "package_unexpected_error": "Une erreur inattendue s'est produite lors du traitement du paquet '{pkgname}'", "package_unknown": "Le paquet '{pkgname}' est inconnu", - "packages_no_upgrade": "Il n’y a aucun paquet à mettre à jour", - "packages_upgrade_critical_later": "Les paquets critiques ({packages:s}) seront mis à jour ultérieurement", "packages_upgrade_failed": "Impossible de mettre à jour tous les paquets", - "path_removal_failed": "Impossible de supprimer le chemin {:s}", "pattern_backup_archive_name": "Doit être un nom de fichier valide avec un maximum de 30 caractères, et composé de caractères alphanumériques et -_. uniquement", "pattern_domain": "Doit être un nom de domaine valide (ex : mon-domaine.fr)", "pattern_email": "Doit être une adresse de courriel valide (ex. : pseudo@example.com)", "pattern_firstname": "Doit être un prénom valide", "pattern_lastname": "Doit être un nom valide", - "pattern_listname": "Doit être composé uniquement de caractères alphanumériques et de tirets bas (aussi appelé tiret du 8 ou underscore)", "pattern_mailbox_quota": "Doit avoir une taille suffixée avec b/k/M/G/T ou 0 pour désactiver le quota", "pattern_password": "Doit être composé d’au moins 3 caractères", - "pattern_port": "Doit être un numéro de port valide compris entre 0 et 65535", "pattern_port_or_range": "Doit être un numéro de port valide compris entre 0 et 65535, ou une gamme de ports (exemple : 100:200)", "pattern_positive_number": "Doit être un nombre positif", "pattern_username": "Doit être composé uniquement de caractères alphanumériques minuscules et de tirets bas (aussi appelé tiret du 8 ou underscore)", "port_already_closed": "Le port {port:d} est déjà fermé pour les connexions {ip_version:s}", "port_already_opened": "Le port {port:d} est déjà ouvert pour les connexions {ip_version:s}", - "port_available": "Le port {port:d} est disponible", - "port_unavailable": "Le port {port:d} n’est pas disponible", - "restore_action_required": "Vous devez préciser ce qui est à restaurer", "restore_already_installed_app": "Une application est déjà installée avec l’identifiant '{app:s}'", "restore_app_failed": "Impossible de restaurer l’application '{app:s}'", "restore_cleaning_failed": "Impossible de nettoyer le dossier temporaire de restauration", @@ -180,54 +116,30 @@ "restore_nothings_done": "Rien n’a été restauré", "restore_running_app_script": "Exécution du script de restauration de l'application '{app:s}' .…", "restore_running_hooks": "Exécution des scripts de restauration …", - "service_add_configuration": "Ajout du fichier de configuration {file:s}", "service_add_failed": "Impossible d’ajouter le service '{service:s}'", "service_added": "Le service '{service:s}' a été ajouté", "service_already_started": "Le service '{service:s}' est déjà en cours d'exécution", "service_already_stopped": "Le service '{service:s}' est déjà arrêté", "service_cmd_exec_failed": "Impossible d’exécuter la commande '{command:s}'", - "service_conf_file_backed_up": "Le fichier de configuration '{conf}' a été sauvegardé dans '{backup}'", - "service_conf_file_copy_failed": "Impossible de copier le nouveau fichier de configuration '{new}' vers '{conf}'", - "service_conf_file_manually_modified": "Le fichier de configuration '{conf}' a été modifié manuellement et ne sera pas mis à jour", - "service_conf_file_manually_removed": "Le fichier de configuration '{conf}' a été supprimé manuellement et ne sera pas créé", - "service_conf_file_not_managed": "Le fichier de configuration « {conf} » n'est pas géré pour l'instant et ne sera pas mis à jour", - "service_conf_file_remove_failed": "Impossible de supprimer le fichier de configuration '{conf}'", - "service_conf_file_removed": "Le fichier de configuration '{conf}' a été supprimé", - "service_conf_file_updated": "Le fichier de configuration '{conf}' a été mis à jour", - "service_conf_up_to_date": "La configuration du service '{service}' est déjà à jour", - "service_conf_updated": "La configuration a été mise à jour pour le service '{service}'", - "service_conf_would_be_updated": "La configuration du service '{service}' aurait été mise à jour", - "service_configuration_conflict": "Le fichier {file:s} a été modifié depuis sa dernière génération. Veuillez y appliquer les modifications manuellement ou utiliser l’option --force (ce qui écrasera toutes les modifications effectuées sur le fichier).", - "service_configured": "La configuration du service « {service:s} » a été générée avec succès", - "service_configured_all": "La configuration de tous les services a été générée avec succès", "service_disable_failed": "Impossible de ne pas lancer le service « {service:s} » au démarrage.\n\nJournaux récents du service : {logs:s}", "service_disabled": "Le service « {service:s} » ne sera plus lancé au démarrage du système.", "service_enable_failed": "Impossible de lancer automatiquement le service « {service:s} » au démarrage.\n\nJournaux récents du service : {logs:s}", "service_enabled": "Le service « {service:s} » sera désormais lancé automatiquement au démarrage du système.", - "service_no_log": "Aucun journal à afficher pour le service '{service:s}'", - "service_regenconf_dry_pending_applying": "Vérification des configurations en attentes qui pourraient être appliquées au le service '{service}' …", - "service_regenconf_failed": "Impossible de régénérer la configuration pour les services : {services}", - "service_regenconf_pending_applying": "Application des configurations en attentes pour le service '{service}' …", "service_remove_failed": "Impossible de supprimer le service '{service:s}'", "service_removed": "Le service « {service:s} » a été supprimé", "service_start_failed": "Impossible de démarrer le service '{service:s}'\n\nJournaux historisés récents : {logs:s}", "service_started": "Le service « {service:s} » a été démarré", - "service_status_failed": "Impossible de déterminer le statut du service '{service:s}'", "service_stop_failed": "Impossible d’arrêter le service '{service:s}'\n\nJournaux historisés récents : {logs:s}", "service_stopped": "Le service « {service:s} » a été arrêté", "service_unknown": "Le service '{service:s}' est inconnu", - "services_configured": "La configuration a été générée avec succès", - "show_diff": "Voici les différences :\n{diff:s}", "ssowat_conf_generated": "La configuration de SSOwat générée", "ssowat_conf_updated": "La configuration de SSOwat mise à jour", "system_upgraded": "Système mis à jour", "system_username_exists": "Ce nom d’utilisateur existe déjà dans les utilisateurs système", "unbackup_app": "L’application '{app:s}' ne sera pas sauvegardée", "unexpected_error": "Une erreur inattendue est survenue : {error}", - "unit_unknown": "L'unité '{unit:s}' est inconnue", "unlimit": "Pas de quota", "unrestore_app": "L’application '{app:s}' ne sera pas restaurée", - "update_cache_failed": "Impossible de mettre à jour le cache de l'outil de gestion avancée des paquets (APT)", "updating_apt_cache": "Récupération des mises à jour disponibles pour les paquets du système …", "upgrade_complete": "Mise à jour terminée", "upgrading_packages": "Mise à jour des paquets en cours …", @@ -240,7 +152,6 @@ "user_deleted": "L’utilisateur supprimé", "user_deletion_failed": "Impossible de supprimer l’utilisateur {user}: {error}", "user_home_creation_failed": "Impossible de créer le dossier personnel de l’utilisateur", - "user_info_failed": "Impossible de récupérer les informations de l’utilisateur", "user_unknown": "L'utilisateur {user:s} est inconnu", "user_update_failed": "Impossible de mettre à jour l'utilisateur {user}: {error}", "user_updated": "L’utilisateur a été modifié", @@ -262,14 +173,11 @@ "certmanager_cert_install_success_selfsigned": "Le certificat auto-signé est maintenant installé pour le domaine « {domain:s} »", "certmanager_cert_install_success": "Le certificat Let’s Encrypt est maintenant installé pour le domaine « {domain:s} »", "certmanager_cert_renew_success": "Certificat Let's Encrypt renouvelé pour le domaine '{domain:s}'", - "certmanager_old_letsencrypt_app_detected": "\nYunoHost a détecté que l’application « letsencrypt » est installé, ce qui est en conflit avec les nouvelles fonctionnalités de gestion intégrée de certificats dans YunoHost. Si vous souhaitez utiliser ces nouvelles fonctionnalités intégrées, veuillez lancer les commandes suivantes pour migrer votre installation :\n\n yunohost app remove letsencrypt\n yunohost domain cert-install\n\nN.B. : cela tentera de réinstaller les certificats de tous les domaines avec un certificat Let's Encrypt ou ceux auto-signés", "certmanager_cert_signing_failed": "Impossible de signer le nouveau certificat", "certmanager_no_cert_file": "Impossible de lire le fichier du certificat pour le domaine {domain:s} (fichier : {file:s})", "certmanager_conflicting_nginx_file": "Impossible de préparer le domaine pour le défi ACME : le fichier de configuration NGINX {filepath:s} est en conflit et doit être préalablement retiré", "certmanager_hit_rate_limit": "Trop de certificats ont déjà été émis récemment pour ce même ensemble de domaines {domain:s}. Veuillez réessayer plus tard. Lisez https://letsencrypt.org/docs/rate-limits/ pour obtenir plus de détails sur les ratios et limitations", "ldap_init_failed_to_create_admin": "L’initialisation de l'annuaire LDAP n’a pas réussi à créer l’utilisateur admin", - "ssowat_persistent_conf_read_error": "Impossible de lire la configuration persistante de SSOwat : {error:s}. Modifiez le fichier /etc/ssowat/conf.json.persistent pour réparer la syntaxe JSON", - "ssowat_persistent_conf_write_error": "Impossible de sauvegarder de la configuration persistante de SSOwat : {error:s}. Modifiez le fichier /etc/ssowat/conf.json.persistent pour réparer la syntaxe JSON", "domain_cannot_remove_main": "Vous ne pouvez pas supprimer '{domain:s}' car il s'agit du domaine principal. Vous devez d'abord définir un autre domaine comme domaine principal à l'aide de 'yunohost domain main-domain -n ', voici la liste des domaines candidats. : {other_domains:s}", "certmanager_self_ca_conf_file_not_found": "Le fichier de configuration pour l’autorité du certificat auto-signé est introuvable (fichier : {file:s})", "certmanager_unable_to_parse_self_CA_name": "Impossible d’analyser le nom de l’autorité du certificat auto-signé (fichier : {file:s})", @@ -277,30 +185,20 @@ "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_domain_not_resolved_locally": "Le domaine {domain:s} ne peut être résolu depuis votre serveur YunoHost. Cela peut se produire si vous avez récemment modifié votre enregistrement DNS. Si c'est le cas, merci d’attendre quelques heures qu’il se propage. Si le problème persiste, envisager d’ajouter {domain:s} au fichier /etc/hosts. (Si vous savez ce que vous faites, utilisez --no-checks pour désactiver ces vérifications.)", "certmanager_http_check_timeout": "Expiration du délai lorsque le serveur a essayé de se contacter lui-même via HTTP en utilisant l'adresse IP public {ip:s} du domaine {domain:s}. Vous rencontrez peut-être un problème d’hairpinning ou alors le pare-feu/routeur en amont de votre serveur est mal configuré.", "certmanager_couldnt_fetch_intermediate_cert": "Expiration du délai lors de la tentative de récupération du certificat intermédiaire depuis Let’s Encrypt. L’installation ou le renouvellement du certificat a été annulé. Veuillez réessayer plus tard.", - "appslist_retrieve_bad_format": "Impossible de lire la liste des applications extraites '{appslist: s}'", "domain_hostname_failed": "Échec de l’utilisation d’un nouveau nom d’hôte. Cela pourrait causer des soucis plus tard (peut-être que ça n’en causera pas).", "yunohost_ca_creation_success": "L’autorité de certification locale créée.", - "appslist_name_already_tracked": "Une liste d'applications enregistrées portant le nom {name:s} existe déjà.", - "appslist_url_already_tracked": "Il y a déjà une liste d’applications enregistrée avec l’URL {url:s}.", - "appslist_migrating": "Migration de la liste d’applications '{appslist:s}' …", - "appslist_could_not_migrate": "Impossible de migrer la liste '{appslist:s}' ! Impossible d’exploiter l’URL. L’ancienne tâche programmée a été conservée dans {bkp_file:s}.", - "appslist_corrupted_json": "Impossible de charger la liste d’applications. Il semble que {filename:s} soit endommager.", "app_already_installed_cant_change_url": "Cette application est déjà installée. L’URL ne peut pas être changé simplement par cette fonction. Vérifiez si cela est disponible avec `app changeurl`.", - "app_change_no_change_url_script": "L’application {app_name:s} ne prend pas encore en charge le changement d’URL, vous pourriez avoir besoin de la mettre à jour.", "app_change_url_failed_nginx_reload": "Le redémarrage de Nginx a échoué. Voici la sortie de 'nginx -t' :\n{nginx_errors:s}", "app_change_url_identical_domains": "L’ancien et le nouveau couple domaine/chemin_de_l'URL sont identiques pour ('{domain:s}{path:s}'), rien à faire.", "app_change_url_no_script": "L’application '{app_name:s}' ne prend pas encore en charge le changement d’URL. Vous devriez peut-être la mettre à jour.", "app_change_url_success": "L’URL de l’application {app:s} a été changée en {domain:s}{path:s}", "app_location_unavailable": "Cette URL n’est pas disponible ou est en conflit avec une application existante :\n{apps:s}", "app_already_up_to_date": "{app:s} est déjà à jour", - "invalid_url_format": "Format d’URL non valide", "global_settings_bad_choice_for_enum": "Valeur du paramètre {setting:s} incorrecte. Reçu : {choice:s}, mais les valeurs possibles sont : {available_choices:s}", "global_settings_bad_type_for_setting": "Le type du paramètre {setting:s} est incorrect. Reçu {received_type:s} alors que {expected_type:s} était attendu", "global_settings_cant_open_settings": "Échec de l’ouverture du ficher de configurations car : {reason:s}", - "global_settings_cant_serialize_setings": "Échec de sérialisation des données de configurations, cause : {reason:s}", "global_settings_cant_write_settings": "Échec d’écriture du fichier de configurations car : {reason:s}", "global_settings_key_doesnt_exists": "La clef '{settings_key:s}' n’existe pas dans les configurations générales, vous pouvez voir toutes les clefs disponibles en saisissant 'yunohost settings list'", "global_settings_reset_success": "Vos configurations précédentes ont été sauvegardées dans {path:s}", @@ -310,15 +208,12 @@ "global_settings_setting_example_enum": "Exemple d’option de type énumération", "global_settings_unknown_type": "Situation inattendue : la configuration {setting:s} semble avoir le type {unknown_type:s} mais celui-ci n'est pas pris en charge par le système.", "global_settings_unknown_setting_from_settings_file": "Clé inconnue dans les paramètres : '{setting_key:s}', rejet de cette clé et sauvegarde de celle-ci dans /etc/yunohost/unkown_settings.json", - "service_conf_new_managed_file": "Le fichier de configuration « {conf} » est désormais géré par le service {service}.", - "service_conf_file_kept_back": "Le fichier de configuration '{conf}' devait être supprimé par le service {service} mais a été conservé.", "backup_abstract_method": "Cette méthode de sauvegarde reste à implémenter", "backup_applying_method_tar": "Création de l’archive TAR de la sauvegarde…", "backup_applying_method_copy": "Copie de tous les fichiers à sauvegarder …", "backup_applying_method_borg": "Envoi de tous les fichiers à sauvegarder dans le répertoire borg-backup…", "backup_applying_method_custom": "Appel de la méthode de sauvegarde personnalisée '{method:s}' …", "backup_archive_system_part_not_available": "La partie '{part:s}' du système n’est pas disponible dans cette sauvegarde", - "backup_archive_mount_failed": "Le montage de l’archive de sauvegarde a échoué", "backup_archive_writing_error": "Impossible d'ajouter des fichiers '{source:s}' (nommés dans l'archive : '{dest:s}') à sauvegarder dans l'archive compressée '{archive:s}'", "backup_ask_for_copying_if_needed": "Voulez-vous effectuer la sauvegarde en utilisant {size:s} temporairement? (Cette méthode est utilisée car certains fichiers n'ont pas pu être préparés avec une méthode plus efficace.)", "backup_borg_not_implemented": "La méthode de sauvegarde Borg n’est pas encore implémentée", @@ -326,7 +221,6 @@ "backup_copying_to_organize_the_archive": "Copie de {size:s} Mo pour organiser l’archive", "backup_csv_creation_failed": "Impossible de créer le fichier CSV nécessaire à la restauration", "backup_csv_addition_failed": "Impossible d’ajouter des fichiers à sauvegarder dans le fichier CSV", - "backup_custom_need_mount_error": "Échec de la méthode de sauvegarde personnalisée à l’étape 'need_mount'", "backup_custom_backup_error": "Échec de la méthode de sauvegarde personnalisée à l’étape 'backup'", "backup_custom_mount_error": "Échec de la méthode de sauvegarde personnalisée à l’étape 'mount'", "backup_no_uncompress_archive_dir": "Ce dossier d’archive décompressée n’existe pas", @@ -341,31 +235,21 @@ "global_settings_cant_serialize_settings": "Échec de la sérialisation des données de paramétrage car : {reason:s}", "restore_removing_tmp_dir_failed": "Impossible de sauvegarder un ancien dossier temporaire", "restore_extracting": "Extraction des fichiers nécessaires depuis l’archive …", - "restore_mounting_archive": "Montage de l’archive dans '{path:s}'", "restore_may_be_not_enough_disk_space": "Votre système semble ne pas avoir suffisamment d’espace disponible (L'espace libre est de {free_space:d} octets. Le besoin d'espace nécessaire est de {needed_space:d} octets. En appliquant une marge de sécurité, la quantité d'espace nécessaire est de {margin:d} octets)", "restore_not_enough_disk_space": "Espace disponible insuffisant (L'espace libre est de {free_space:d} octets. Le besoin d'espace nécessaire est de {needed_space:d} octets. En appliquant une marge de sécurité, la quantité d'espace nécessaire est de {margin:d} octets)", "restore_system_part_failed": "Impossible de restaurer la partie '{part:s}' du système", "backup_couldnt_bind": "Impossible de lier {src:s} avec {dest:s}.", "domain_dns_conf_is_just_a_recommendation": "Cette page montre la configuration *recommandée*. Elle ne configure *pas* le DNS pour vous. Il est de votre responsabilité que de configurer votre zone DNS chez votre fournisseur/registrar DNS avec cette recommandation.", - "domain_dyndns_dynette_is_unreachable": "Impossible de contacter la dynette YunoHost. Soit YunoHost n’est pas correctement connecté à internet, soit le serveur de dynette est en panne. Erreur : {error}", - "migrations_backward": "Migration en arrière.", - "migrations_bad_value_for_target": "Nombre invalide pour le paramètre target, les numéros de migration sont 0 ou {}", "migrations_cant_reach_migration_file": "Impossible d'accéder aux fichiers de migration via le chemin '%s'", - "migrations_current_target": "La cible de migration est {}", - "migrations_error_failed_to_load_migration": "ERREUR : échec du chargement de migration {number} {name}", - "migrations_forward": "Migration en avant", "migrations_loading_migration": "Chargement de la migration {id} …", "migrations_migration_has_failed": "La migration {id} a échoué avec l’exception {exception} : annulation", "migrations_no_migrations_to_run": "Aucune migration à lancer", - "migrations_show_currently_running_migration": "Application de la migration {number} {name} …", - "migrations_show_last_migration": "La dernière migration appliquée est {}", "migrations_skip_migration": "Ignorer et passer la migration {id}…", "server_shutdown": "Le serveur va éteindre", "server_shutdown_confirm": "Le serveur va être éteint immédiatement, le voulez-vous vraiment ? [{answers:s}]", "server_reboot": "Le serveur va redémarrer", "server_reboot_confirm": "Le serveur va redémarrer immédiatement, le voulez-vous vraiment ? [{answers:s}]", "app_upgrade_some_app_failed": "Certaines applications n’ont pas été mises à jour", - "ask_path": "Chemin", "dyndns_could_not_check_provide": "Impossible de vérifier si {provider:s} peut fournir {domain:s}.", "dyndns_domain_not_provided": "Le fournisseur DynDNS {provider:s} ne peut pas fournir le domaine {domain:s}.", "app_make_default_location_already_used": "Impossible de configurer l’application '{app}' par défaut pour le domaine '{domain}' car il est déjà utilisé par l'application '{other_app}'", @@ -379,11 +263,9 @@ "migrate_tsig_wait_3": "1 minute …", "migrate_tsig_wait_4": "30 secondes …", "migrate_tsig_not_needed": "Il ne semble pas que vous utilisez un domaine DynDNS, donc aucune migration n’est nécessaire.", - "app_checkurl_is_deprecated": "Packagers /!\\ 'app checkurl' est obsolète ! Utilisez 'app register-url' en remplacement !", "migration_description_0001_change_cert_group_to_sslcert": "Changement des permissions de groupe des certificats de « metronome » à « ssl-cert »", "migration_description_0002_migrate_to_tsig_sha256": "Amélioration de la sécurité de DynDNS TSIG en utilisant SHA512 au lieu de MD5", "migration_description_0003_migrate_to_stretch": "Mise à niveau du système vers Debian Stretch et YunoHost 3.0", - "migration_0003_backward_impossible": "La migration Stretch n’est pas réversible.", "migration_0003_start": "Démarrage de la migration vers Stretch. Les journaux seront disponibles dans {logfile}.", "migration_0003_patching_sources_list": "Modification du fichier sources.lists …", "migration_0003_main_upgrade": "Démarrage de la mise à niveau principale …", @@ -403,15 +285,12 @@ "service_description_dnsmasq": "Gère la résolution des noms de domaine (DNS)", "service_description_dovecot": "Permet aux clients de messagerie d’accéder/récupérer les courriels (via IMAP et POP3)", "service_description_fail2ban": "Protège contre les attaques brute-force et autres types d’attaques venant d’Internet", - "service_description_glances": "Surveille les info système de votre serveur", "service_description_metronome": "Gère les comptes de messagerie instantanée XMPP", "service_description_mysql": "Stocke les données des applications (bases de données SQL)", "service_description_nginx": "Sert ou permet l’accès à tous les sites web hébergés sur votre serveur", "service_description_nslcd": "Gère la connexion en ligne de commande des utilisateurs YunoHost", - "service_description_php5-fpm": "exécute des applications écrites en PHP avec nginx", "service_description_postfix": "Utilisé pour envoyer et recevoir des courriels", "service_description_redis-server": "Une base de données spécialisée utilisée pour l’accès rapide aux données, les files d’attentes et la communication entre les programmes", - "service_description_rmilter": "Vérifie divers paramètres dans les courriels", "service_description_rspamd": "Filtre le pourriel, et d’autres fonctionnalités liées au courriel", "service_description_slapd": "Stocke les utilisateurs, domaines et leurs informations liées", "service_description_ssh": "Vous permet de vous connecter à distance à votre serveur via un terminal (protocole SSH)", @@ -427,11 +306,6 @@ "log_help_to_get_failed_log": "L’opération '{desc}' a échouée ! Pour obtenir de l’aide, merci de partager le journal de l'opération en utilisant la commande 'yunohost log display {name} --share'", "log_does_exists": "Il n’existe pas de journal de l’opération ayant pour nom '{log}', utiliser 'yunohost log list' pour voir tous les fichiers de journaux disponibles", "log_operation_unit_unclosed_properly": "L’opération ne s’est pas terminée correctement", - "log_app_addaccess": "Ajouter l’accès à '{}'", - "log_app_removeaccess": "Enlever l’accès à '{}'", - "log_app_clearaccess": "Retirer tous les accès à '{}'", - "log_app_fetchlist": "Ajouter une liste d’application", - "log_app_removelist": "Enlever une liste d’application", "log_app_change_url": "Changer l’URL de l’application '{}'", "log_app_install": "Installer l’application '{}'", "log_app_remove": "Enlever l’application '{}'", @@ -449,14 +323,11 @@ "log_letsencrypt_cert_install": "Installer le certificat Let’s Encrypt sur le domaine '{}'", "log_selfsigned_cert_install": "Installer le certificat auto-signé sur le domaine '{}'", "log_letsencrypt_cert_renew": "Renouveler le certificat Let’s Encrypt de '{}'", - "log_service_enable": "Activer le service '{}'", - "log_service_regen_conf": "Régénérer la configuration système de '{}'", "log_user_create": "Ajouter l’utilisateur '{}'", "log_user_delete": "Supprimer l’utilisateur '{}'", "log_user_update": "Mettre à jour les informations de l’utilisateur '{}'", "log_domain_main_domain": "Faire de '{}' le domaine principal", "log_tools_migrations_migrate_forward": "Éxecuter les migrations", - "log_tools_migrations_migrate_backward": "Revenir en arrière", "log_tools_postinstall": "Faire la post-installation de votre serveur YunoHost", "log_tools_upgrade": "Mettre à jour les paquets du système", "log_tools_shutdown": "Éteindre votre serveur", @@ -467,14 +338,12 @@ "migration_0005_postgresql_94_not_installed": "PostgreSQL n’a pas été installé sur votre système. Rien à faire !", "migration_0005_postgresql_96_not_installed": "PostgreSQL 9.4 a été trouvé et installé, mais pas PostgreSQL 9.6 !? Quelque chose d’étrange a dû arriver à votre système… :(", "migration_0005_not_enough_space": "Laissez suffisamment d'espace disponible dans {path} pour exécuter la migration.", - "recommend_to_add_first_user": "La post-installation est terminée mais YunoHost a besoin d’au moins un utilisateur pour fonctionner correctement. Vous devez en ajouter un en utilisant la commande 'yunohost user create $nomdutilisateur' ou bien via l’interface d’administration web.", "service_description_php7.0-fpm": "Exécute des applications écrites en PHP avec NGINX", "users_available": "Liste des utilisateurs disponibles :", "good_practices_about_admin_password": "Vous êtes maintenant sur le point de définir un nouveau mot de passe d’administration. Le mot de passe doit comporter au moins 8 caractères – bien qu’il soit recommandé d’utiliser un mot de passe plus long (c’est-à-dire une phrase secrète) et/ou d’utiliser différents types de caractères (majuscules, minuscules, chiffres et caractères spéciaux).", "good_practices_about_user_password": "Vous êtes maintenant sur le point de définir un nouveau mot de passe utilisateur. Le mot de passe doit comporter au moins 8 caractères - bien qu’il soit recommandé d’utiliser un mot de passe plus long (c’est-à-dire une phrase secrète) et/ou d’utiliser différents types de caractères tels que : majuscules, minuscules, chiffres et caractères spéciaux.", "migration_description_0006_sync_admin_and_root_passwords": "Synchroniser les mots de passe admin et root", "migration_0006_disclaimer": "YunoHost s'attend maintenant à ce que les mots de passe administrateur et racine soient synchronisés. Cette migration remplace votre mot de passe root par le mot de passe administrateur.", - "migration_0006_done": "Votre mot de passe root a été remplacé par celui de votre adminitrateur.", "password_listed": "Ce mot de passe est l'un des mots de passe les plus utilisés dans le monde. Veuillez choisir quelque chose d'un peu plus singulier.", "password_too_simple_1": "Le mot de passe doit comporter au moins 8 caractères", "password_too_simple_2": "Le mot de passe doit comporter au moins 8 caractères et contenir des chiffres, des majuscules et des minuscules", @@ -512,10 +381,8 @@ "migration_0008_dsa": "- La clé DSA sera désactivée. Par conséquent, il se peut que vous ayez besoin d'invalider un avertissement effrayant de votre client SSH afin de revérifier l'empreinte de votre serveur ;", "migration_0008_warning": "Si vous comprenez ces avertissements et souhaitez que YunoHost écrase votre configuration actuelle, exécutez la migration. Sinon, vous pouvez également ignorer la migration, bien que cela ne soit pas recommandé.", "migration_0008_no_warning": "Remplacer votre configuration SSH devrait être sûr, bien que cela ne puisse être promis! Exécutez la migration pour la remplacer. Sinon, vous pouvez également ignorer la migration, bien que cela ne soit pas recommandé.", - "migrations_success": "Migration {number} {name} réussie !", "pattern_password_app": "Désolé, les mots de passe ne doivent pas contenir les caractères suivants : {forbidden_chars}", "root_password_replaced_by_admin_password": "Votre mot de passe root a été remplacé par votre mot de passe administrateur.", - "service_conf_now_managed_by_yunohost": "Le fichier de configuration '{conf}' est maintenant géré par YunoHost.", "service_reload_failed": "Impossible de recharger le service '{service:s}'.\n\nJournaux historisés récents de ce service : {logs:s}", "service_reloaded": "Le service « {service:s} » a été rechargé", "service_restart_failed": "Impossible de redémarrer le service '{service:s}'\n\nJournaux historisés récents de ce service : {logs:s}", @@ -556,42 +423,24 @@ "tools_upgrade_regular_packages_failed": "Impossible de mettre à jour les paquets suivants : {packages_list}", "tools_upgrade_special_packages": "Mise à jour des paquets 'spécifiques' (liés a YunoHost) …", "tools_upgrade_special_packages_completed": "La mise à jour des paquets de YunoHost est finie!\nPressez [Entrée] pour revenir à la ligne de commande", - "updating_app_lists": "Récupération des mises à jour des applications disponibles…", "dpkg_lock_not_available": "Cette commande ne peut être exécutée actuellement car un autre programme semble utiliser le verrou de dpkg (gestionnaire de paquets)", "tools_upgrade_cant_unhold_critical_packages": "Impossible de conserver les paquets critiques…", "tools_upgrade_special_packages_explanation": "La mise à jour spéciale va continuer en arrière-plan. Veuillez ne pas lancer d’autres actions sur votre serveur pendant environ 10 minutes (en fonction de la vitesse du matériel). Après cela, il vous faudra peut-être vous reconnecter à la webadmin. Le journal de mise à niveau sera disponible dans Outils → Journal (dans la webadmin) ou via \"yunohost log list\" (en ligne de commande).", "update_apt_cache_failed": "Impossible de mettre à jour le cache APT (gestionnaire de paquets Debian). Voici un extrait du fichier sources.list qui pourrait vous aider à identifier les lignes problématiques :\n{sourceslist}", "update_apt_cache_warning": "Des erreurs se sont produites lors de la mise à jour du cache APT (gestionnaire de paquets Debian). Voici un extrait des lignes du fichier sources.list qui pourrait vous aider à identifier les lignes problématiques :\n{sourceslist}", - "apps_permission_not_found": "Aucune permission trouvée pour les applications installées", - "apps_permission_restoration_failed": "L'autorisation '{permission:s}' pour la restauration de l'application {app:s} a échoué", "backup_permission": "Permission de sauvegarde pour l'application {app:s}", - "edit_group_not_allowed": "Vous n'êtes pas autorisé à modifier le groupe {group:s}", - "error_when_removing_sftpuser_group": "Vous ne pouvez pas supprimer le groupe sftpusers", "group_created": "Le groupe '{group}' a été créé", "group_deleted": "Suppression du groupe '{group}'", - "group_deletion_not_allowed": "Le groupe {group:s} ne peut pas être supprimé manuellement.", - "group_info_failed": "L'information sur le groupe a échoué", "group_unknown": "Le groupe {group:s} est inconnu", "group_updated": "Le groupe '{group}' a été mis à jour", "group_update_failed": "La mise à jour du groupe '{group}' a échoué : {error}", - "group_already_allowed": "Le groupe '{group:s}' a déjà la permission '{permission:s}' activée pour l'application '{app:s}'", - "group_already_disallowed": "Le groupe '{group:s}' a déjà la permission '{permission:s}' désactivée pour l'application '{app:s}'", - "group_name_already_exist": "Le groupe {name:s} existe déjà", "group_creation_failed": "Échec de la création du groupe '{group}': {error}", "group_deletion_failed": "Échec de la suppression du groupe '{group}': {error}", - "edit_permission_with_group_all_users_not_allowed": "Vous n'êtes pas autorisé à modifier les permissions pour le groupe 'all_users', utilisez 'yunohost user permission clear APP' ou 'yunohost user permission add APP -u USER' à la place.", - "log_permission_add": "Ajouter l'autorisation '{}' pour l'application '{}'", - "log_permission_remove": "Supprimer l'autorisation '{}'", - "log_permission_update": "Mise à jour de l'autorisation '{}' pour l'application '{}'", - "log_user_group_add": "Ajouter '{}' au groupe", "log_user_group_delete": "Supprimer le groupe '{}'", "log_user_group_update": "Mettre à jour '{}' pour le groupe", - "log_user_permission_add": "Mettre à jour l'autorisation pour '{}'", - "log_user_permission_remove": "Mettre à jour l'autorisation pour '{}'", "mailbox_disabled": "La boîte aux lettres est désactivée pour l'utilisateur {user:s}", "app_action_broke_system": "Cette action semble avoir cassé des services importants : {services}", "apps_already_up_to_date": "Toutes les applications sont déjà à jour", - "app_upgrade_stopped": "La mise à niveau de toutes les applications s'est arrêtée pour éviter tout dommage, car une application n'a pas pu être mise à niveau.", "migration_0011_create_group": "Création d'un groupe pour chaque utilisateur…", "migration_0011_done": "Migration terminée. Vous êtes maintenant en mesure de gérer des groupes d'utilisateurs.", "migrations_must_provide_explicit_targets": "Vous devez fournir des cibles explicites lorsque vous utilisez '--skip' ou '--force-rerun'", @@ -605,18 +454,11 @@ "migration_0011_migration_failed_trying_to_rollback": "La migration a échouée… Tentative de restauration du système.", "migration_0011_rollback_success": "Système restauré.", "migration_0011_update_LDAP_database": "Mise à jour de la base de données LDAP…", - "system_groupname_exists": "Le nom de groupe existe déjà dans le groupe du systèmes", - "tools_update_failed_to_app_fetchlist": "Impossible de mettre à jour les listes d'applications de YunoHost car: {error}", - "user_already_in_group": "L'utilisateur '{user:}' est déjà dans le groupe '{group: s}'", - "user_not_in_group": "L'utilisateur '{user: s}' ne fait pas partie du groupe {group: s}", "migration_0011_backup_before_migration": "Création d'une sauvegarde des paramètres de la base de données LDAP et des applications avant la migration.", "permission_not_found": "Autorisation '{permission:s}' introuvable", - "permission_name_not_valid": "Choisissez un nom d'autorisation autorisé pour '{permission: s}'", "permission_update_failed": "Impossible de mettre à jour la permission '{permission}': {error}", - "permission_generated": "Base de données des autorisations mise à jour", "permission_updated": "Permission '{permission:s}' mise à jour", "permission_update_nothing_to_do": "Aucune autorisation pour mettre à jour", - "remove_main_permission_not_allowed": "Supprimer l'autorisation principale n'est pas autorisé", "dyndns_provider_unreachable": "Impossible d’atteindre le fournisseur DynDNS {provider}: votre YunoHost n’est pas correctement connecté à Internet ou le serveur Dynette est en panne.", "migration_0011_update_LDAP_schema": "Mise à jour du schéma LDAP…", "migrations_already_ran": "Ces migrations sont déjà effectuées: {ids}", @@ -624,27 +466,21 @@ "migrations_failed_to_load_migration": "Impossible de charger la migration {id}: {error}", "migrations_running_forward": "Exécution de la migration {id}…", "migrations_success_forward": "Migration {id} terminée", - "need_define_permission_before": "Redéfinissez l'autorisation à l'aide de 'yunohost user permission add -u USER' avant de supprimer un groupe autorisé", "operation_interrupted": "L'opération a été interrompue manuellement ?", - "permission_already_clear": "L'autorisation '{permission: s}' est déjà vide pour l'application {app: s}", "permission_already_exist": "L'autorisation '{permission}' existe déjà", "permission_created": "Permission '{permission:s}' créée", "permission_creation_failed": "Impossible de créer l'autorisation '{permission}': {error}", "permission_deleted": "Permission '{permission:s}' supprimée", "permission_deletion_failed": "Impossible de supprimer la permission '{permission}': {error}", - "remove_user_of_group_not_allowed": "Vous n'êtes pas autorisé à supprimer l'utilisateur '{user:s}' dans le groupe '{group:s}'", "migration_description_0011_setup_group_permission": "Initialiser les groupes d'utilisateurs et autorisations pour les applications et les services", - "migration_0011_LDAP_config_dirty": "Il semble que vous ayez personnalisé votre configuration LDAP. Pour cette migration, la configuration LDAP doit être mise à jour.\nVous devez enregistrer votre configuration actuelle, réintialiser la configuration d'origine en exécutant 'yunohost tools regen-conf -f', puis réessayer la migration", "migration_0011_LDAP_update_failed": "Impossible de mettre à jour LDAP. Erreur: {error:s}", "group_already_exist": "Le groupe {group} existe déjà", "group_already_exist_on_system": "Le groupe {group} existe déjà dans les groupes système", - "group_cannot_be_edited": "Le groupe {group} ne peut pas être édité manuellement.", "group_cannot_be_deleted": "Le groupe {group} ne peut pas être supprimé manuellement.", "group_user_already_in_group": "L'utilisateur {user} est déjà dans le groupe {group}", "group_user_not_in_group": "L'utilisateur {user} n'est pas dans le groupe {group}", "log_permission_create": "Créer permission '{}'", "log_permission_delete": "supprimer permission '{}'", - "log_permission_urls": "Mettre à jour les URL liées à la permission '{}'", "log_user_group_create": "Créer '{}' groupe", "log_user_permission_update": "Mise à jour des accès pour la permission '{}'", "log_user_permission_reset": "Réinitialiser la permission '{}'", @@ -660,7 +496,6 @@ "log_permission_url": "Mise à jour de l'URL associée à l'autorisation '{}'", "migration_0011_slapd_config_will_be_overwritten": "Il semble que vous ayez modifié manuellement la configuration de slapd. Pour cette migration critique, YunoHost doit forcer la mise à jour de la configuration de slapd. Les fichiers originaux seront sauvegardés dans {conf_backup_folder}.", "permission_already_up_to_date": "L'autorisation n'a pas été mise à jour car les demandes d'ajout/suppression correspondent déjà à l'état actuel.", - "permission_currently_allowed_for_visitors": "Cette autorisation est actuellement accordée aux visiteurs en plus d'autres groupes. Vous voudrez probablement supprimer l'autorisation \"visiteurs\" ou supprimer les autres groupes auxquels il est actuellement attribué.", "permission_currently_allowed_for_all_users": "Cette autorisation est actuellement accordée à tous les utilisateurs en plus des autres groupes. Vous voudrez probablement soit supprimer l'autorisation 'all_users', soit supprimer les autres groupes auxquels il est actuellement autorisé.", "app_install_failed": "Impossible d'installer {app}: {error}", "app_install_script_failed": "Une erreur est survenue dans le script d'installation de l'application", @@ -701,7 +536,6 @@ "diagnosis_dns_bad_conf": "Configuration DNS incorrecte ou manquante pour le domaine {domain} (catégorie {category})", "diagnosis_dns_discrepancy": "L'enregistrement DNS de type {0} et nom {1} ne correspond pas à la configuration recommandée. Valeur actuelle: {2}. Valeur exceptée: {3}. Vous pouvez consulter https://yunohost.org/dns_config pour plus d'informations.", "diagnosis_services_bad_status": "Le service {service} est {status} :-(", - "diagnosis_services_good_status": "Le service {service} est {status} comme prévu !", "diagnosis_diskusage_verylow": "Le stockage {mountpoint} (sur le périphérique {device}) ne dispose que de {free_abs_GB} Go ({free_percent}%). Vous devriez vraiment envisager de nettoyer un peu d'espace.", "diagnosis_diskusage_low": "Le stockage {mountpoint} (sur le périphérique {device}) ne dispose que de {free_abs_GB} Go ({free_percent}%). Faites attention.", "diagnosis_ram_verylow": "Le système ne dispose plus que de {available_abs_MB} MB ({available_percent}%)! (sur {total_abs_MB} Mo)", @@ -713,7 +547,6 @@ "diagnosis_regenconf_manually_modified_debian": "Le fichier de configuration {file} a été modifié manuellement par rapport à celui par défaut de Debian.", "diagnosis_regenconf_manually_modified_details": "C'est probablement OK tant que vous savez ce que vous faites;) !", "diagnosis_regenconf_manually_modified_debian_details": "Cela peut probablement être OK, mais il faut garder un œil dessus ...", - "diagnosis_regenconf_nginx_conf_broken": "La configuration de nginx semble être cassée !", "diagnosis_security_all_good": "Aucune vulnérabilité de sécurité critique n'a été trouvée.", "apps_catalog_init_success": "Système de catalogue d'applications initialisé !", "apps_catalog_failed_to_download": "Impossible de télécharger le catalogue des applications {apps_catalog}:{error}", @@ -726,7 +559,6 @@ "diagnosis_description_services": "État des services", "diagnosis_description_systemresources": "Ressources système", "diagnosis_description_ports": "Exposition des ports", - "diagnosis_description_http": "Exposition HTTP", "diagnosis_description_regenconf": "Configurations système", "diagnosis_description_security": "Contrôles de sécurité", "diagnosis_ports_could_not_diagnose": "Impossible de diagnostiquer si les ports sont accessibles de l'extérieur. Erreur: {error}", @@ -751,8 +583,6 @@ "diagnosis_http_connection_error": "Erreur de connexion : impossible de se connecter au domaine demandé, il est probablement injoignable.", "diagnosis_no_cache": "Pas encore de cache de diagnostique pour la catégorie « {category} »", "diagnosis_http_unknown_error": "Une erreur est survenue en essayant de joindre votre domaine, il est probablement injoignable.", - "permission_all_users_implicitly_added": "La permission a également été implicitement accordée à 'all_users' car il est nécessaire pour permettre au groupe spécial 'visiteurs'", - "permission_cannot_remove_all_users_while_visitors_allowed": "Vous ne pouvez pas supprimer cette autorisation pour 'all_users' alors qu'elle est toujours autorisée pour 'visiteurs'", "yunohost_postinstall_end_tip": "La post-installation terminée! Pour finaliser votre configuration, il est recommendé de :\n - ajouter un premier utilisateur depuis la section \"Utilisateurs\" de l'interface web (ou \"yunohost user create \" en ligne de commande);\n - diagnostiquer les potentiels problèmes dans la section \"Diagnostic\" de l'interface web (ou \"yunohost diagnosis run\" en ligne de commande);\n - lire les parties \"Finalisation de votre configuration\" et \"Découverte de Yunohost\" dans le guide de l'administrateur: https://yunohost.org/admindoc.", "diagnosis_services_bad_status_tip": "Vous pouvez essayer de redémarrer le service. Si cela ne fonctionne pas, consultez les journaux de service à l'aide de 'yunohost service log {0}' ou de la section 'Services' de l'administrateur Web.", "diagnosis_http_bad_status_code": "Le système de diagnostique n'a pas réussi à contacter votre serveur. Il se peut qu'une autre machine réponde à la place de votre serveur. Vérifiez que le port 80 est correctement redirigé, que votre configuration nginx est à jour et qu’un reverse-proxy n’interfère pas.", @@ -765,4 +595,4 @@ "diagnosis_description_web": "Web", "diagnosis_basesystem_hardware_board": "Le modèle de carte du serveur est {model}", "diagnosis_basesystem_hardware": "L'architecture du serveur est {virt} {arch}" -} +} \ No newline at end of file diff --git a/locales/hi.json b/locales/hi.json index 23d391c47..609464c8f 100644 --- a/locales/hi.json +++ b/locales/hi.json @@ -9,74 +9,48 @@ "app_argument_required": "तर्क '{name:s}' की आवश्यकता है", "app_extraction_failed": "इन्सटाल्ड फ़ाइलों को निकालने में असमर्थ", "app_id_invalid": "अवैध एप्लिकेशन id", - "app_incompatible": "यह एप्लिकेशन युनोहोस्ट की इस वर्जन के लिए नहीं है", "app_install_files_invalid": "फाइलों की अमान्य स्थापना", - "app_location_already_used": "इस लोकेशन पे पहले से ही कोई एप्लीकेशन इन्सटाल्ड है", - "app_location_install_failed": "इस लोकेशन पे एप्लीकेशन इंस्टाल करने में असमर्थ", "app_manifest_invalid": "एप्लीकेशन का मैनिफेस्ट अमान्य", - "app_no_upgrade": "कोई भी एप्लीकेशन को अपडेट की जरूरत नहीं", "app_not_correctly_installed": "{app:s} ठीक ढंग से इनस्टॉल नहीं हुई", "app_not_installed": "{app:s} इनस्टॉल नहीं हुई", "app_not_properly_removed": "{app:s} ठीक ढंग से नहीं अनइन्सटॉल की गई", - "app_package_need_update": "इस एप्लीकेशन पैकेज को युनोहोस्ट के नए बदलावों/गाइडलिनेज़ के कारण उपडटेशन की जरूरत", "app_removed": "{app:s} को अनइन्सटॉल कर दिया गया", "app_requirements_checking": "जरूरी पैकेजेज़ की जाँच हो रही है ....", - "app_requirements_failed": "आवश्यकताओं को पूरा करने में असमर्थ: {error}", "app_requirements_unmeet": "आवश्यकताए पूरी नहीं हो सकी, पैकेज {pkgname}({version})यह होना चाहिए {spec}", "app_sources_fetch_failed": "सोर्स फाइल्स प्राप्त करने में असमर्थ", "app_unknown": "अनजान एप्लीकेशन", "app_unsupported_remote_type": "एप्लीकेशन के लिए उन्सुपपोर्टेड रिमोट टाइप इस्तेमाल किया गया", "app_upgrade_failed": "{app:s} अपडेट करने में असमर्थ", "app_upgraded": "{app:s} अपडेट हो गयी हैं", - "appslist_fetched": "एप्लीकेशन की सूचि अपडेट हो गयी", - "appslist_removed": "एप्लीकेशन की सूचि निकल दी गयी है", - "appslist_retrieve_error": "दूरस्थ एप्लिकेशन सूची प्राप्त करने में असमर्थ", - "appslist_unknown": "अनजान एप्लिकेशन सूची", - "ask_current_admin_password": "वर्तमान व्यवस्थापक पासवर्ड", "ask_email": "ईमेल का पता", "ask_firstname": "नाम", "ask_lastname": "अंतिम नाम", - "ask_list_to_remove": "सूचि जिसको हटाना है", "ask_main_domain": "मुख्य डोमेन", "ask_new_admin_password": "नया व्यवस्थापक पासवर्ड", "ask_password": "पासवर्ड", - "backup_action_required": "आप को सेव करने के लिए कुछ लिखना होगा", "backup_app_failed": "एप्लीकेशन का बैकअप करने में असमर्थ '{app:s}'", "backup_archive_app_not_found": "'{app:s}' बैकअप आरचिव में नहीं मिला", - "backup_archive_hook_not_exec": "हुक '{hook:s}' इस बैकअप में एक्सेक्युट नहीं किया गया", "backup_archive_name_exists": "इस बैकअप आरचिव का नाम पहले से ही मौजूद है", "backup_archive_name_unknown": "'{name:s}' इस नाम की लोकल बैकअप आरचिव मौजूद नहीं", "backup_archive_open_failed": "बैकअप आरचिव को खोलने में असमर्थ", "backup_cleaning_failed": "टेम्पोरेरी बैकअप डायरेक्टरी को उड़ने में असमर्थ", "backup_created": "बैकअप सफलतापूर्वक किया गया", - "backup_creating_archive": "बैकअप आरचिव बनाई जा रही है ...", "backup_creation_failed": "बैकअप बनाने में विफल", "backup_delete_error": "'{path:s}' डिलीट करने में असमर्थ", "backup_deleted": "इस बैकअप को डिलीट दिया गया है", - "backup_extracting_archive": "बैकअप आरचिव को एक्सट्रेक्ट किया जा रहा है ...", "backup_hook_unknown": "'{hook:s}' यह बैकअप हुक नहीं मिला", "backup_invalid_archive": "अवैध बैकअप आरचिव", "backup_nothings_done": "सेव करने के लिए कुछ नहीं", "backup_output_directory_forbidden": "निषिद्ध आउटपुट डायरेक्टरी। निम्न दिए गए डायरेक्टरी में बैकअप नहीं बन सकता /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var और /home/yunohost.backup/archives के सब-फोल्डर।", "backup_output_directory_not_empty": "आउटपुट डायरेक्टरी खाली नहीं है", "backup_output_directory_required": "बैकअप करने के लिए आउट पुट डायरेक्टरी की आवश्यकता है", - "backup_running_app_script": "'{app:s}' एप्लीकेशन की बैकअप स्क्रिप्ट चल रही है...", "backup_running_hooks": "बैकअप हुक्स चल रहे है...", "custom_app_url_required": "आप को अपनी कस्टम एप्लिकेशन '{app:s}' को अपग्रेड करने के लिए यूआरएल(URL) देने की आवश्यकता है", - "custom_appslist_name_required": "आप को अपनी कस्टम एप्लीकेशन के लिए नाम देने की आवश्यकता है", - "diagnosis_debian_version_error": "डेबियन वर्जन प्राप्त करने में असफलता {error}", - "diagnosis_kernel_version_error": "कर्नेल वर्जन प्राप्त नहीं की जा पा रही : {error}", - "diagnosis_monitor_disk_error": "डिस्क की मॉनिटरिंग नहीं की जा पा रही: {error}", - "diagnosis_monitor_network_error": "नेटवर्क की मॉनिटरिंग नहीं की जा पा रही: {error}", - "diagnosis_monitor_system_error": "सिस्टम की मॉनिटरिंग नहीं की जा पा रही: {error}", - "diagnosis_no_apps": "कोई एप्लीकेशन इन्सटाल्ड नहीं है", - "dnsmasq_isnt_installed": "dnsmasq इन्सटाल्ड नहीं लगता,इनस्टॉल करने के लिए किप्या ये कमांड चलाये 'apt-get remove bind9 && apt-get install dnsmasq'", "domain_cert_gen_failed": "सर्टिफिकेट उत्पन करने में असमर्थ", "domain_created": "डोमेन बनाया गया", "domain_creation_failed": "डोमेन बनाने में असमर्थ", "domain_deleted": "डोमेन डिलीट कर दिया गया है", "domain_deletion_failed": "डोमेन डिलीट करने में असमर्थ", "domain_dyndns_already_subscribed": "DynDNS डोमेन पहले ही सब्स्क्राइड है", - "domain_dyndns_invalid": "DynDNS के साथ इनवैलिड डोमिन इस्तेमाल किया गया", "password_too_simple_1": "पासवर्ड को कम से कम 8 वर्ण लंबा होना चाहिए" -} +} \ No newline at end of file diff --git a/locales/hu.json b/locales/hu.json index b39882148..3ba14286f 100644 --- a/locales/hu.json +++ b/locales/hu.json @@ -11,4 +11,4 @@ "app_argument_invalid": "'{name:s}' hibás paraméter érték :{error:s}", "app_argument_required": "Parameter '{name:s}' kötelező", "password_too_simple_1": "A jelszónak legalább 8 karakter hosszúnak kell lennie" -} +} \ No newline at end of file diff --git a/locales/it.json b/locales/it.json index deaed8d8f..79204320b 100644 --- a/locales/it.json +++ b/locales/it.json @@ -9,15 +9,12 @@ "backup_created": "Backup completo", "backup_invalid_archive": "Archivio di backup non valido", "backup_output_directory_not_empty": "La directory di output non è vuota", - "backup_running_app_script": "Esecuzione del script di backup dell'applicazione '{app:s}'...", "domain_created": "Il dominio è stato creato", - "domain_dyndns_invalid": "Il dominio non è valido per essere usato con DynDNS", "domain_exists": "Il dominio è già esistente", "ldap_initialized": "LDAP è stato inizializzato", "pattern_email": "L'indirizzo email deve essere valido (es. someone@domain.org)", "pattern_mailbox_quota": "La dimensione deve avere un suffisso b/k/M/G/T o 0 per disattivare la quota", "port_already_opened": "La porta {port:d} è già aperta per {ip_version:s} connessioni", - "port_unavailable": "La porta {port:d} non è disponibile", "service_add_failed": "Impossibile aggiungere il servizio '{service:s}'", "service_cmd_exec_failed": "Impossibile eseguire il comando '{command:s}'", "service_disabled": "Il servizio '{service:s}' è stato disattivato", @@ -31,12 +28,8 @@ "admin_password": "Password dell'amministrazione", "admin_password_change_failed": "Impossibile cambiare la password", "admin_password_changed": "La password dell'amministrazione è stata cambiata", - "app_incompatible": "L'applicazione {app} è incompatibile con la tua versione YunoHost", "app_install_files_invalid": "Non sono validi i file di installazione", - "app_location_already_used": "L'applicazione '{app}' è già installata in questo percorso ({path})", - "app_location_install_failed": "Impossibile installare l'applicazione in questo percorso perchè andrebbe in conflitto con l'applicazione '{other_app}' già installata in '{other_path}'", "app_manifest_invalid": "Manifesto dell'applicazione non valido: {error}", - "app_no_upgrade": "Nessun applicazione da aggiornare", "app_not_correctly_installed": "{app:s} sembra di non essere installata correttamente", "app_not_properly_removed": "{app:s} non è stata correttamente rimossa", "action_invalid": "L'azione '{action:s}' non è valida", @@ -44,20 +37,12 @@ "app_sources_fetch_failed": "Impossibile riportare i file sorgenti", "app_upgrade_failed": "Impossibile aggiornare {app:s}", "app_upgraded": "{app:s} è stata aggiornata", - "appslist_fetched": "La lista delle applicazioni {appslist:s} è stata recuperata", - "appslist_removed": "La lista delle applicazioni {appslist:s} è stata rimossa", - "app_package_need_update": "Il pacchetto dell'applicazione {app} deve essere aggiornato per seguire i cambiamenti di YunoHost", "app_requirements_checking": "Controllo i pacchetti richiesti per {app}…", - "app_requirements_failed": "Impossibile soddisfare i requisiti per {app}: {error}", "app_requirements_unmeet": "Requisiti non soddisfatti per {app}, il pacchetto {pkgname} ({version}) deve essere {spec}", - "appslist_unknown": "Lista di applicazioni {appslist:s} sconosciuta.", - "ask_current_admin_password": "Password attuale dell'amministrazione", "ask_firstname": "Nome", "ask_lastname": "Cognome", - "ask_list_to_remove": "Lista da rimuovere", "ask_main_domain": "Dominio principale", "ask_new_admin_password": "Nuova password dell'amministrazione", - "backup_action_required": "Devi specificare qualcosa da salvare", "backup_app_failed": "Non è possibile fare il backup dell'applicazione '{app:s}'", "backup_archive_app_not_found": "L'applicazione '{app:s}' non è stata trovata nel archivio di backup", "app_argument_choice_invalid": "Scelta non valida per l'argomento '{name:s}', deve essere uno di {choices:s}", @@ -65,32 +50,19 @@ "app_argument_required": "L'argomento '{name:s}' è requisito", "app_id_invalid": "Identificativo dell'applicazione non valido", "app_unsupported_remote_type": "Il tipo remoto usato per l'applicazione non è supportato", - "appslist_retrieve_error": "Impossibile recuperare la lista di applicazioni remote {appslist:s}: {error:s}", - "appslist_retrieve_bad_format": "Il file recuperato per la lista di applicazioni {appslist:s} non è valido", "backup_archive_broken_link": "Non è possibile accedere al archivio di backup (link rotto verso {path:s})", - "backup_archive_hook_not_exec": "Il hook '{hook:s}' non è stato eseguito in questo backup", "backup_archive_name_unknown": "Archivio di backup locale chiamato '{name:s}' sconosciuto", "backup_archive_open_failed": "Non è possibile aprire l'archivio di backup", "backup_cleaning_failed": "Non è possibile pulire la directory temporanea di backup", - "backup_creating_archive": "Creazione del archivio di backup…", "backup_creation_failed": "La creazione del backup è fallita", "backup_delete_error": "Impossibile cancellare '{path:s}'", "backup_deleted": "Il backup è stato cancellato", - "backup_extracting_archive": "Estrazione del archivio di backup…", "backup_hook_unknown": "Hook di backup '{hook:s}' sconosciuto", "backup_nothings_done": "Non c'è niente da salvare", "backup_output_directory_forbidden": "Directory di output vietata. I backup non possono esser creati nelle sotto-cartelle /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var o /home/yunohost.backup/archives", "backup_output_directory_required": "Devi fornire una directory di output per il backup", "backup_running_hooks": "Esecuzione degli hook di backup…", "custom_app_url_required": "Devi fornire un URL per essere in grado di aggiornare l'applicazione personalizzata {app:s}", - "custom_appslist_name_required": "Devi fornire un nome per la lista di applicazioni personalizzata", - "diagnosis_debian_version_error": "Impossibile riportare la versione di Debian: {error}", - "diagnosis_kernel_version_error": "Impossibile riportare la versione del kernel: {error}", - "diagnosis_monitor_disk_error": "Impossibile controllare i dischi: {error}", - "diagnosis_monitor_network_error": "Impossibile controllare la rete: {error}", - "diagnosis_monitor_system_error": "Impossibile controllare il sistema: {error}", - "diagnosis_no_apps": "Nessuna applicazione installata", - "dnsmasq_isnt_installed": "dnsmasq non sembra installato, impartisci il comando 'apt-get remove bind9 && apt-get install dnsmasq'", "domain_creation_failed": "Impossibile creare un dominio", "domain_deleted": "Il dominio è stato cancellato", "domain_deletion_failed": "Impossibile cancellare il dominio", @@ -99,8 +71,6 @@ "domain_hostname_failed": "La definizione del nuovo hostname è fallita", "domain_uninstall_app_first": "Una o più applicazioni sono installate su questo dominio. Disinstalla loro prima di procedere alla cancellazione di un dominio", "domain_unknown": "Dominio sconosciuto", - "domain_zone_exists": "Il file di zona DNS è già esistente", - "domain_zone_not_found": "Il file di zona DNS non è stato trovato per il dominio {:s}", "done": "Terminato", "domains_available": "Domini disponibili:", "downloading": "Scaricamento…", @@ -122,7 +92,6 @@ "firewall_reload_failed": "Impossibile ricaricare il firewall", "firewall_reloaded": "Il firewall è stato ricaricato", "firewall_rules_cmd_failed": "Alcune regole del firewall sono fallite. Per ulteriori informazioni, vedi il registro.", - "format_datetime_short": "%m/%d/%Y %I:%M %p", "hook_exec_failed": "L'esecuzione dello script è fallita: {path:s}", "hook_exec_not_terminated": "L'esecuzione dello script non è stata terminata: {path:s}", "hook_name_unknown": "Nome di hook '{name:s}' sconosciuto", @@ -131,49 +100,25 @@ "ip6tables_unavailable": "Non puoi giocare con ip6tables qui. O sei in un container o il tuo kernel non lo supporta", "iptables_unavailable": "Non puoi giocare con iptables qui. O sei in un container o il tuo kernel non lo supporta", "ldap_init_failed_to_create_admin": "L'inizializzazione LDAP non è riuscita a creare un utente admin", - "license_undefined": "Indeterminato", "mail_alias_remove_failed": "Impossibile rimuovere l'alias mail '{mail:s}'", "mail_domain_unknown": "Dominio d'indirizzo mail '{domain:s}' sconosciuto", "mail_forward_remove_failed": "Impossibile rimuovere la mail inoltrata '{mail:s}'", "mailbox_used_space_dovecot_down": "Il servizio di posta elettronica Dovecot deve essere attivato se vuoi riportare lo spazio usato dalla posta elettronica", "main_domain_change_failed": "Impossibile cambiare il dominio principale", "main_domain_changed": "Il dominio principale è stato cambiato", - "monitor_disabled": "Il monitoraggio del sistema è stato disattivato", - "monitor_enabled": "Il monitoraggio del sistema è stato attivato", - "monitor_glances_con_failed": "Impossibile collegarsi al server Glances", - "monitor_not_enabled": "Il monitoraggio del server non è attivato", - "monitor_period_invalid": "Periodo di tempo non valido", - "monitor_stats_file_not_found": "I file statistici non sono stati trovati", - "monitor_stats_no_update": "Nessuna statistica di monitoraggio da aggiornare", - "monitor_stats_period_unavailable": "Nessuna statistica disponibile per il periodo", - "mountpoint_unknown": "Punto di mount sconosciuto", - "mysql_db_creation_failed": "La creazione del database MySQL è fallita", - "mysql_db_init_failed": "L'inizializzazione del database MySQL è fallita", - "mysql_db_initialized": "Il database MySQL è stato inizializzato", - "new_domain_required": "Devi fornire il nuovo dominio principale", - "no_appslist_found": "Nessuna lista di applicazioni trovata", "no_internet_connection": "Il server non è collegato a Internet", - "no_ipv6_connectivity": "La connessione IPv6 non è disponibile", "not_enough_disk_space": "Non c'è abbastanza spazio libero in '{path:s}'", - "package_not_installed": "Il pacchetto '{pkgname}' non è installato", "package_unknown": "Pacchetto '{pkgname}' sconosciuto", - "packages_no_upgrade": "Nessuno pacchetto da aggiornare", - "packages_upgrade_critical_later": "I pacchetti critici {packages:s} verranno aggiornati più tardi", "packages_upgrade_failed": "Impossibile aggiornare tutti i pacchetti", - "path_removal_failed": "Impossibile rimuovere il percorso {:s}", "pattern_backup_archive_name": "Deve essere un nome di file valido con caratteri alfanumerici e -_. soli", "pattern_domain": "Deve essere un nome di dominio valido (es. il-mio-dominio.org)", "pattern_firstname": "Deve essere un nome valido", "pattern_lastname": "Deve essere un cognome valido", - "pattern_listname": "Caratteri alfanumerici e trattini bassi soli", "pattern_password": "Deve contenere almeno 3 caratteri", - "pattern_port": "Deve essere un numero di porta valido (es. 0-65535)", "pattern_port_or_range": "Deve essere un numero di porta valido (es. 0-65535) o una fascia di porte valida (es. 100:200)", "pattern_positive_number": "Deve essere un numero positivo", "pattern_username": "Caratteri minuscoli alfanumerici o trattini bassi soli", "port_already_closed": "La porta {port:d} è già chiusa per le connessioni {ip_version:s}", - "port_available": "La porta {port:d} è disponibile", - "restore_action_required": "Devi specificare qualcosa da ripristinare", "restore_already_installed_app": "Un'applicazione è già installata con l'identificativo '{app:s}'", "restore_app_failed": "Impossibile ripristinare l'applicazione '{app:s}'", "restore_cleaning_failed": "Impossibile pulire la directory temporanea di ripristino", @@ -181,10 +126,6 @@ "restore_confirm_yunohost_installed": "Sei sicuro di volere ripristinare un sistema già installato? {answers:s}", "restore_failed": "Impossibile ripristinare il sistema", "user_update_failed": "Impossibile aggiornare l'utente", - "network_check_smtp_ko": "La posta in uscita (SMTP porta 25) sembra bloccata dalla tua rete", - "network_check_smtp_ok": "La posta in uscita (SMTP porta 25) non è bloccata", - "no_restore_script": "Nessuno script di ripristino trovato per l'applicazone '{app:s}'", - "package_unexpected_error": "Un'errore inaspettata si è verificata durante il trattamento del pacchetto '{pkgname}'", "restore_hook_unavailable": "Lo script di ripristino per '{part:s}' non è disponibile per il tuo sistema e non è nemmeno nell'archivio", "restore_nothings_done": "Non è stato ripristinato nulla", "restore_running_app_script": "Esecuzione dello script di ripristino dell'applicazione '{app:s}'…", @@ -192,39 +133,19 @@ "service_added": "Il servizio '{service:s}' è stato aggiunto", "service_already_started": "Il servizio '{service:s}' è già stato avviato", "service_already_stopped": "Il servizio '{service:s}' è già stato fermato", - "service_conf_file_backed_up": "Il file di configurazione '{conf}' è stato salvato in '{backup}'", - "service_conf_file_copy_failed": "Impossibile copiare il nuovo file di configurazione '{new}' in '{conf}'", - "service_conf_file_manually_modified": "Il file di configurazione '{conf}' è stato modificato manualmente e non verrà aggiornato", - "service_conf_file_manually_removed": "Il file di configurazione '{conf}' è stato rimosso manualmente e non verrà creato", - "service_conf_file_not_managed": "Il file di configurazione '{conf}' non è ancora amministrato e non verrà aggiornato", - "service_conf_file_remove_failed": "Impossibile rimuovere il file di configurazione '{conf}'", - "service_conf_file_removed": "Il file di configurazione '{conf}' è stato rimosso", - "service_conf_file_updated": "Il file di configurazione '{conf}' è stato aggiornato", - "service_conf_up_to_date": "La configurazione è già aggiornata per il servizio '{service}'", - "service_conf_updated": "La configurazione è stata aggiornata per il servizio '{service}'", - "service_conf_would_be_updated": "La configurazione sarebbe stata aggiornata per il servizio '{service}'", "service_disable_failed": "Impossibile disabilitare il servizio '{service:s}'\n\nRegistri di servizio recenti:{logs:s}", "service_enable_failed": "Impossibile abilitare il servizio '{service:s}'\n\nRegistri di servizio recenti:{logs:s}", "service_enabled": "Il servizio '{service:s}' è stato attivato", - "service_no_log": "Nessuno registro da visualizzare per il servizio '{service:s}'", - "service_regenconf_dry_pending_applying": "Verifica della configurazione in sospeso che sarebbe stata applicata per il servizio '{service}'…", - "service_regenconf_failed": "Impossibile rigenerare la configurazione per il/i servizio/i: {services}", - "service_regenconf_pending_applying": "Applicazione della configurazione in sospeso per il servizio '{service}'…", "service_start_failed": "Impossibile eseguire il servizio '{service:s}'\n\nRegistri di servizio recenti:{logs:s}", "service_started": "Il servizio '{service:s}' è stato avviato", - "service_status_failed": "Impossibile determinare lo stato del servizio '{service:s}'", "service_stopped": "Il servizio '{service:s}' è stato fermato", "service_unknown": "Servizio '{service:s}' sconosciuto", "ssowat_conf_generated": "La configurazione SSOwat è stata generata", "ssowat_conf_updated": "La configurazione SSOwat è stata aggiornata", - "ssowat_persistent_conf_read_error": "Un'errore si è verificata durante la lettura della configurazione persistente SSOwat: {error:s}. Modifica il file persistente /etc/ssowat/conf.json per correggere la sintassi JSON", - "ssowat_persistent_conf_write_error": "Un'errore si è verificata durante la registrazione della configurazione persistente SSOwat: {error:s}. Modifica il file persistente /etc/ssowat/conf.json per correggere la sintassi JSON", "system_upgraded": "Il sistema è stato aggiornato", "unbackup_app": "L'applicazione '{app:s}' non verrà salvata", "unexpected_error": "Un'errore inaspettata si è verificata", - "unit_unknown": "Unità '{unit:s}' sconosciuta", "unlimit": "Nessuna quota", - "update_cache_failed": "Impossibile aggiornare la cache APT", "updating_apt_cache": "Recupero degli aggiornamenti disponibili per i pacchetti di sistema…", "upgrade_complete": "Aggiornamento completo", "upnp_dev_not_found": "Nessuno supporto UPnP trovato", @@ -235,7 +156,6 @@ "user_creation_failed": "Impossibile creare l'utente", "user_deletion_failed": "Impossibile cancellare l'utente", "user_home_creation_failed": "Impossibile creare la home directory del utente", - "user_info_failed": "Impossibile riportare le informazioni del utente", "user_unknown": "Utente sconosciuto: {user:s}", "user_updated": "L'utente è stato aggiornato", "yunohost_already_installed": "YunoHost è già installato", @@ -253,7 +173,6 @@ "certmanager_domain_http_not_working": "Sembra che non sia possibile accedere al dominio {domain:s} attraverso HTTP. Verifica la configurazione del DNS e di nginx", "app_already_installed_cant_change_url": "Questa applicazione è già installata. L'URL non può essere cambiato solo da questa funzione. Guarda se `app changeurl` è disponibile.", "app_already_up_to_date": "{app:s} è già aggiornata", - "app_change_no_change_url_script": "L'applicazione {app_name:s} non supporta ancora il cambio del proprio URL, potrebbe essere necessario aggiornarla.", "app_change_url_failed_nginx_reload": "Riavvio di nginx fallito. Questo è il risultato di 'nginx -t':\n{nginx_errors:s}", "app_change_url_identical_domains": "Il vecchio ed il nuovo dominio/percorso_url sono identici ('{domain:s}{path:s}'), nessuna operazione necessaria.", "app_change_url_no_script": "L'applicazione '{app_name:s}' non supporta ancora la modifica dell'URL. Forse dovresti aggiornare l'applicazione.", @@ -262,18 +181,11 @@ "app_location_unavailable": "Questo URL non è disponibile o va in conflitto con la/le applicazione/i già installata/e:\n{apps:s}", "app_upgrade_app_name": "Aggiornando l'applicazione {app}…", "app_upgrade_some_app_failed": "Impossibile aggiornare alcune applicazioni", - "appslist_corrupted_json": "Caricamento della lista delle applicazioni non riuscita. Sembra che {filename:s} sia corrotto.", - "appslist_could_not_migrate": "Migrazione della lista delle applicazioni {appslist:s} non riuscita! Impossibile analizzare l'URL... La vecchia operazione pianificata è stata tenuta in {bkp_file:s}.", - "appslist_migrating": "Migrando la lista di applicazioni {appslist:s}…", - "appslist_name_already_tracked": "C'è già una lista di applicazioni registrata con il nome {name:s}.", - "appslist_url_already_tracked": "C'è già una lista di applicazioni registrata con URL {url:s}.", - "ask_path": "Percorso", "backup_abstract_method": "Questo metodo di backup non è ancora stato implementato", "backup_applying_method_borg": "Inviando tutti i file da salvare nel backup nel deposito borg-backup…", "backup_applying_method_copy": "Copiando tutti i files nel backup…", "backup_applying_method_custom": "Chiamando il metodo di backup personalizzato '{method:s}'…", "backup_applying_method_tar": "Creando l'archivio tar del backup…", - "backup_archive_mount_failed": "Montaggio dell'archivio del backup non riuscito", "backup_archive_system_part_not_available": "La parte di sistema '{part:s}' non è disponibile in questo backup", "backup_archive_writing_error": "Impossibile aggiungere i file al backup nell'archivio compresso", "backup_ask_for_copying_if_needed": "Alcuni files non possono essere preparati al backup utilizzando il metodo che consente di evitare il consumo temporaneo di spazio nel sistema. Per eseguire il backup, {size:s}MB dovranno essere utilizzati temporaneamente. Sei d'accordo?", @@ -285,7 +197,6 @@ "backup_csv_creation_failed": "Impossibile creare il file CVS richiesto per le future operazioni di ripristino", "backup_custom_backup_error": "Il metodo di backup personalizzato è fallito allo step 'backup'", "backup_custom_mount_error": "Il metodo di backup personalizzato è fallito allo step 'mount'", - "backup_custom_need_mount_error": "Il metodo di backup personalizzato è fallito allo step 'need_mount'", "backup_method_borg_finished": "Backup in borg terminato", "backup_method_copy_finished": "Copia di backup terminata", "backup_method_custom_finished": "Metodo di backup personalizzato '{method:s}' terminato", @@ -327,7 +238,6 @@ "certmanager_conflicting_nginx_file": "Impossibile preparare il dominio per il controllo ACME: il file di configurazione nginx {filepath:s} è in conflitto e dovrebbe essere prima rimosso", "certmanager_couldnt_fetch_intermediate_cert": "Tempo scaduto durante il tentativo di recupero di un certificato intermedio da Let's Encrypt. Installazione/rinnovo non riuscito - per favore riprova più tardi.", "certmanager_domain_dns_ip_differs_from_public_ip": "Il valore DNS 'A' per il dominio {domain:s} è diverso dall'IP di questo server. Se hai modificato recentemente il tuo valore A, attendi che si propaghi (esistono online alcuni siti per il controllo della propagazione DNS). (Se sai cosa stai facendo, usa --no-checks per disabilitare quei controlli.)", - "certmanager_domain_not_resolved_locally": "Il dominio {domain:s} non può essere risolto in locale dal server Yunohost. Questo può accadere se hai modificato recentemente il tuo valore DNS. Se così fosse, per favore aspetta qualche ora per far si che si propaghi. Se il problema persiste, prova ad aggiungere {domain:s} in /etc/hosts. (Se sai cosa stai facendo, usa --no-checks per disabilitare quei controlli.)", "certmanager_error_no_A_record": "Nessun valore DNS 'A' trovato per {domain:s}. Devi far puntare il tuo nome di dominio verso la tua macchina per essere in grado di installare un certificato Let's Encrypt! (Se sai cosa stai facendo, usa --no-checks per disabilitare quei controlli.)", "certmanager_hit_rate_limit": "Troppi certificati già rilasciati per l'esatta serie di dominii {domain:s} recentemente. Per favore riprova più tardi. Guarda https://letsencrypt.org/docs/rate-limits/ per maggiori dettagli", "certmanager_http_check_timeout": "Tempo scaduto durante il tentativo di contatto del tuo server a se stesso attraverso HTTP utilizzando l'indirizzo IP pubblico (dominio {domain:s} con ip {ip:s}). Potresti avere un problema di hairpinning o il firewall/router davanti al tuo server non è correttamente configurato.", @@ -340,7 +250,6 @@ "dpkg_is_broken": "Non puoi eseguire questo ora perchè dpkg/apt (i gestori di pacchetti del sistema) sembrano essere in stato danneggiato... Puoi provare a risolvere il problema connettendoti via SSH ed eseguire `sudo dpkg --configure -a`.", "domain_cannot_remove_main": "Non è possibile rimuovere il dominio principale ora. Prima imposta un nuovo dominio principale", "domain_dns_conf_is_just_a_recommendation": "Questo comando ti mostra qual è la configurazione *raccomandata*. Non ti imposta la configurazione DNS al tuo posto. È tua responsabilità configurare la tua zona DNS nel tuo registrar in accordo con queste raccomandazioni.", - "domain_dyndns_dynette_is_unreachable": "Impossibile raggiungere la dynette YunoHost, o il tuo YunHost non è correttamente connesso a internet o il server dynette non è attivo. Errore: {error}", "dyndns_could_not_check_provide": "Impossibile controllare se {provider:s} possano fornire {domain:s}.", "dyndns_could_not_check_available": "Impossibile controllare se {domain:s} è disponibile su {provider:s}.", "dyndns_domain_not_provided": "Il fornitore Dyndns {provider:s} non può fornire il dominio {domain:s}.", @@ -366,7 +275,6 @@ "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Consenti l'uso del (deprecato) hostkey DSA per la configurazione del demone SSH", "global_settings_unknown_type": "Situazione inaspettata, l'impostazione {setting:s} sembra essere di tipo {unknown_type:s} ma non è un tipo supportato dal sistema.", "good_practices_about_admin_password": "Stai per definire una nuova password di amministratore. La password deve essere almeno di 8 caratteri - anche se è buona pratica utilizzare password più lunghe (es. una frase, una serie di parole) e/o utilizzare vari tipi di caratteri (maiuscole, minuscole, numeri e simboli).", - "invalid_url_format": "Formato URL non valido", "log_corrupted_md_file": "Il file dei metadati yaml associato con i registri è corrotto: '{md_file}'", "log_category_404": "La categoria di registrazione '{category}' non esiste", "log_link_to_log": "Registro completo di questa operazione: '{desc}'", @@ -375,11 +283,6 @@ "log_link_to_failed_log": "L'operazione '{desc}' è fallita! Per ottenere aiuto, per favore fornisci il registro completo dell'operazione cliccando qui", "log_help_to_get_failed_log": "L'operazione '{desc}' è fallita! Per ottenere aiuto, per favore condividi il registro completo dell'operazione utilizzando il comando 'yunohost log display {name} --share'", "log_does_exists": "Non esiste nessun registro delle operazioni chiamato '{log}', usa 'yunohost log list' per vedere tutti i registri delle operazioni disponibili", - "log_app_addaccess": "Aggiungi accesso a '{}'", - "log_app_removeaccess": "Rimuovi accesso a '{}'", - "log_app_clearaccess": "Rimuovi tutti gli accessi a '{}'", - "log_app_fetchlist": "Aggiungi un elenco di applicazioni", - "log_app_removelist": "Rimuovi un elenco di applicazioni", "log_app_change_url": "Cambia l'url dell'applicazione '{}'", "log_app_install": "Installa l'applicazione '{}'", "log_app_remove": "Rimuovi l'applicazione '{}'", @@ -397,14 +300,12 @@ "log_letsencrypt_cert_install": "Installa un certificato Let's encrypt sul dominio '{}'", "log_selfsigned_cert_install": "Installa un certificato autofirmato sul dominio '{}'", "log_letsencrypt_cert_renew": "Rinnova il certificato Let's encrypt sul dominio '{}'", - "log_service_enable": "Abilita il servizio '{}'", "log_regen_conf": "Rigenera configurazioni di sistema '{}'", "log_user_create": "Aggiungi l'utente '{}'", "log_user_delete": "Elimina l'utente '{}'", "log_user_update": "Aggiornate le informazioni dell'utente '{}'", "log_domain_main_domain": "Rendi '{}' dominio principale", "log_tools_migrations_migrate_forward": "Migra avanti", - "log_tools_migrations_migrate_backward": "Migra indietro", "log_tools_postinstall": "Postinstallazione del tuo server YunoHost", "log_tools_upgrade": "Aggiornamento dei pacchetti di sistema", "log_tools_shutdown": "Spegni il tuo server", @@ -425,7 +326,6 @@ "migration_description_0005_postgresql_9p4_to_9p6": "Migra i database da postgresql 9.4 a 9.6", "migration_description_0006_sync_admin_and_root_passwords": "Sincronizza password di amministratore e root", "migration_description_0010_migrate_to_apps_json": "Rimuovi gli elenchi di app deprecati ed usa invece il nuovo elenco unificato 'apps.json'", - "migration_0003_backward_impossible": "La migrazione a Stretch non può essere annullata.", "migration_0003_start": "Migrazione a Stretch iniziata. I registri saranno disponibili in {logfile}.", "migration_0003_patching_sources_list": "Sistemando il file sources.lists…", "migration_0003_main_upgrade": "Iniziando l'aggiornamento principale…", @@ -435,6 +335,5 @@ "migration_0003_not_jessie": "La distribuzione attuale non è Jessie!", "migration_0003_system_not_fully_up_to_date": "Il tuo sistema non è completamente aggiornato. Per favore prima esegui un aggiornamento normale prima di migrare a stretch.", "this_action_broke_dpkg": "Questa azione ha danneggiato dpkg/apt (i gestori di pacchetti del sistema)… Puoi provare a risolvere questo problema connettendoti via SSH ed eseguendo `sudo dpkg --configure -a`.", - "updating_app_lists": "Recupero degli aggiornamenti disponibili per le applicazioni…", "app_action_broke_system": "Questa azione sembra avere roto servizi importanti: {services}" -} +} \ No newline at end of file diff --git a/locales/nb_NO.json b/locales/nb_NO.json index f15388941..07695ec3d 100644 --- a/locales/nb_NO.json +++ b/locales/nb_NO.json @@ -8,17 +8,14 @@ "app_already_up_to_date": "{app:s} er allerede oppdatert", "app_argument_invalid": "Velg en gydlig verdi for argumentet '{name:s}': {error:s}", "app_argument_required": "Argumentet '{name:s}' er påkrevd", - "diagnosis_no_apps": "Inget program installert", "app_id_invalid": "Ugyldig program-ID", "dyndns_cron_remove_failed": "Kunne ikke fjerne cron-jobb for DynDNS: {error}", "dyndns_key_not_found": "Fant ikke DNS-nøkkel for domenet", "app_not_correctly_installed": "{app:s} ser ikke ut til å ha blitt installert på riktig måte", "dyndns_provider_unreachable": "Kunne ikke nå DynDNS-tilbyder {provider}: Enten har du ikke satt opp din YunoHost rett, dynette-tjeneren er nede, eller du mangler nett.", "app_not_properly_removed": "{app:s} har ikke blitt fjernet på riktig måte", - "app_package_need_update": "Programmet {app}-pakken må oppdateres for å holde følge med YunoHost sine endringer", "app_removed": "{app:s} fjernet", "app_requirements_checking": "Sjekker påkrevde pakker for {app:s}…", - "app_requirements_failed": "Noen krav er ikke oppfylt for {app:s}: {error}", "app_start_install": "Installerer programmet '{app}'…", "action_invalid": "Ugyldig handling '{action:s}'", "app_start_restore": "Gjenoppretter programmet '{app}'…", @@ -30,8 +27,6 @@ "backup_method_tar_finished": "TAR-sikkerhetskopiarkiv opprettet", "app_action_cannot_be_ran_because_required_services_down": "Dette programmet krever noen tjenester som ikke kjører. Før du fortsetter, du bør prøve å starte følgende tjenester på ny (og antagelig undersøke hvorfor de er nede): {services}", "app_already_installed_cant_change_url": "Dette programmet er allerede installert. Nettadressen kan ikke endres kun med denne funksjonen. Ta en titt på `app changeurl` hvis den er tilgjengelig.", - "diagnosis_monitor_disk_error": "Kunne ikke holde oppsyn med disker: {error}", - "diagnosis_monitor_system_error": "Kunne ikke holde oppsyn med systemet: {error}", "domain_exists": "Domenet finnes allerede", "app_change_url_failed_nginx_reload": "Kunne ikke gjeninnlaste NGINX. Her har du utdataen for 'nginx -t'\n{nginx_errors:s}", "domains_available": "Tilgjengelige domener:", @@ -39,23 +34,17 @@ "downloading": "Laster ned…", "dyndns_could_not_check_provide": "Kunne ikke sjekke om {provider:s} kan tilby {domain:s}.", "dyndns_could_not_check_available": "Kunne ikke sjekke om {domain:s} er tilgjengelig på {provider:s}.", - "log_app_removeaccess": "Fjern tilgang til '{}'", - "license_undefined": "udefinert", "mail_domain_unknown": "Ukjent e-postadresse for domenet '{domain:s}'", "migrate_tsig_wait_2": "2 min…", "log_remove_on_failed_restore": "Fjern '{}' etter mislykket gjenoppretting fra sikkerhetskopiarkiv", "log_letsencrypt_cert_install": "Installer et Let's Encrypt-sertifikat på '{}'-domenet", - "log_permission_update": "Oppdater tilgang '{}' for programmet '{}'", "log_letsencrypt_cert_renew": "Forny '{}'-Let's Encrypt-sertifikat", "log_user_update": "Oppdater brukerinfo for '{}'", "mail_alias_remove_failed": "Kunne ikke fjerne e-postaliaset '{mail:s}'", "app_action_broke_system": "Denne handlingen ser ut til å ha knekt disse viktige tjenestene: {services}", "app_argument_choice_invalid": "Bruk én av disse valgene '{choices:s}' for argumentet '{name:s}'", "app_extraction_failed": "Kunne ikke pakke ut installasjonsfilene", - "app_incompatible": "Programmet {app} er ikke kompatibelt med din YunoHost-versjon", "app_install_files_invalid": "Disse filene kan ikke installeres", - "app_location_already_used": "Programmet '{app}' er allerede installert i ({path})", - "ask_path": "Sti", "backup_abstract_method": "Denne sikkerhetskopimetoden er ikke implementert enda", "backup_actually_backuping": "Oppretter sikkerhetskopiarkiv fra innsamlede filer…", "backup_app_failed": "Kunne ikke sikkerhetskopiere programmet '{app:s}'", @@ -74,34 +63,26 @@ "backup_delete_error": "Kunne ikke slette '{path:s}'", "certmanager_domain_unknown": "Ukjent domene '{domain:s}'", "certmanager_cert_signing_failed": "Kunne ikke signere det nye sertifikatet", - "diagnosis_debian_version_error": "Kunne ikke hente Debian-versjon: {error}", - "diagnosis_kernel_version_error": "Kunne ikke hente kjerneversjon: {error}", - "error_when_removing_sftpuser_group": "Kunne ikke fjerne sftpusers-gruppen", "executing_command": "Kjører kommendoen '{command:s}'…", "executing_script": "Kjører skriptet '{script:s}'…", "extracting": "Pakker ut…", - "edit_group_not_allowed": "Du tillates ikke å redigere gruppen {group:s}", "log_domain_add": "Legg til '{}'-domenet i systemoppsett", "log_domain_remove": "Fjern '{}'-domenet fra systemoppsett", "log_dyndns_subscribe": "Abonner på YunoHost-underdomenet '{}'", "log_dyndns_update": "Oppdater IP-adressen tilknyttet ditt YunoHost-underdomene '{}'", "migrate_tsig_wait_3": "1 min…", "migrate_tsig_wait_4": "30 sekunder…", - "apps_permission_restoration_failed": "Innvilg tilgangen '{permission:s}' for å gjenopprette {app:}", - "apps_permission_not_found": "Fant ingen tilgang for de installerte programmene", "backup_invalid_archive": "Dette er ikke et sikkerhetskopiarkiv", "backup_nothings_done": "Ingenting å lagre", "backup_method_borg_finished": "Sikkerhetskopi inn i Borg fullført", "field_invalid": "Ugyldig felt '{:s}'", "firewall_reloaded": "Brannmur gjeninnlastet", - "log_app_removelist": "Fjern en programliste", "log_app_change_url": "Endre nettadresse for '{}'-programmet", "log_app_install": "Installer '{}'-programmet", "log_app_remove": "Fjern '{}'-programmet", "log_app_upgrade": "Oppgrader '{}'-programmet", "log_app_makedefault": "Gjør '{}' til forvalgt program", "log_available_on_yunopaste": "Denne loggen er nå tilgjengelig via {url}", - "log_tools_maindomain": "Gjør '{}' til hoveddomene", "log_tools_shutdown": "Slå av tjeneren din", "log_tools_reboot": "Utfør omstart av tjeneren din", "apps_already_up_to_date": "Alle programmer allerede oppdatert", @@ -123,21 +104,14 @@ "global_settings_setting_security_password_admin_strength": "Admin-passordets styrke", "dyndns_registration_failed": "Kunne ikke registrere DynDNS-domene: {error:s}", "global_settings_setting_security_password_user_strength": "Brukerpassordets styrke", - "log_app_fetchlist": "Legg til en programliste", "log_backup_restore_app": "Gjenopprett '{}' fra sikkerhetskopiarkiv", "log_remove_on_failed_install": "Fjern '{}' etter mislykket installasjon", - "log_permission_add": "Legg til '{}'-tilgangen for programmet '{}'", - "log_permission_remove": "Fjern tilgangen '{}'", "log_selfsigned_cert_install": "Installer selvsignert sertifikat på '{}'-domenet", "log_user_delete": "Slett '{}' bruker", - "log_user_group_add": "Legg til '{}' gruppe", "log_user_group_delete": "Slett '{}' gruppe", "log_user_group_update": "Oppdater '{}' gruppe", - "log_user_permission_add": "Oppdater '{}' tilgang", - "log_user_permission_remove": "Oppdater '{}' tilgang", "ldap_init_failed_to_create_admin": "LDAP-igangsettelse kunne ikke opprette admin-bruker", "ldap_initialized": "LDAP-igangsatt", - "maindomain_changed": "Hoveddomenet er nå endret", "migration_description_0003_migrate_to_stretch": "Oppgrader systemet til Debian Stretch og YunoHost 3.0", "app_unknown": "Ukjent program", "app_upgrade_app_name": "Oppgraderer {app}…", @@ -147,14 +121,9 @@ "ask_email": "E-postadresse", "ask_firstname": "Fornavn", "ask_lastname": "Etternavn", - "ask_list_to_remove": "Liste å fjerne", "ask_main_domain": "Hoveddomene", "ask_new_admin_password": "Nytt administrasjonspassord", "app_upgrade_several_apps": "Følgende programmer vil oppgraderes: {apps}", - "appslist_removed": "{appslist:s}-programliste fjernet", - "appslist_url_already_tracked": "Dette er allerede en registrert programliste med nettadressen {url:s}.", - "ask_current_admin_password": "Nåværende administrasjonspassord", - "appslist_unknown": "Programlisten {appslist:s} er ukjent.", "ask_new_domain": "Nytt domene", "ask_new_path": "Ny sti", "ask_password": "Passord", @@ -164,8 +133,7 @@ "log_category_404": "Loggkategorien '{category}' finnes ikke", "log_link_to_log": "Full logg for denne operasjonen: '{desc}'", "log_help_to_get_log": "For å vise loggen for operasjonen '{desc}', bruk kommandoen 'yunohost log display {name}'", - "log_app_clearaccess": "Fjern all tilgang til '{}'", "log_user_create": "Legg til '{}' bruker", "app_change_url_success": "{app:s} nettadressen er nå {domain:s}{path:s}", "app_install_failed": "Kunne ikke installere {app}: {error}" -} +} \ No newline at end of file diff --git a/locales/ne.json b/locales/ne.json index 0967ef424..9e26dfeeb 100644 --- a/locales/ne.json +++ b/locales/ne.json @@ -1 +1 @@ -{} +{} \ No newline at end of file diff --git a/locales/nl.json b/locales/nl.json index 9406d9bea..dfee556b2 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -8,21 +8,13 @@ "app_extraction_failed": "Kan installatiebestanden niet uitpakken", "app_id_invalid": "Ongeldige app-id", "app_install_files_invalid": "Ongeldige installatiebestanden", - "app_location_already_used": "Er is al een app geïnstalleerd op deze locatie", - "app_location_install_failed": "Kan app niet installeren op deze locatie", "app_manifest_invalid": "Ongeldig app-manifest", - "app_no_upgrade": "Geen apps op te upgraden", "app_not_installed": "{app:s} is niet geïnstalleerd", - "app_recent_version_required": "{:s} vereist een nieuwere versie van moulinette", "app_removed": "{app:s} succesvol verwijderd", "app_sources_fetch_failed": "Kan bronbestanden niet ophalen", "app_unknown": "Onbekende app", "app_upgrade_failed": "Kan app {app:s} niet updaten", "app_upgraded": "{app:s} succesvol geüpgraded", - "appslist_fetched": "App-lijst {appslist:s} succesvol opgehaald", - "appslist_removed": "App-lijst {appslist:s} succesvol verwijderd", - "appslist_unknown": "App-lijst {appslist:s} is onbekend.", - "ask_current_admin_password": "Huidig administratorwachtwoord", "ask_email": "Email-adres", "ask_firstname": "Voornaam", "ask_lastname": "Achternaam", @@ -30,26 +22,19 @@ "ask_password": "Wachtwoord", "backup_archive_name_exists": "Een backuparchief met dezelfde naam bestaat al", "backup_cleaning_failed": "Kan tijdelijke backup map niet leeg maken", - "backup_creating_archive": "Backup wordt gestart...", "backup_invalid_archive": "Ongeldig backup archief", "backup_output_directory_not_empty": "Doelmap is niet leeg", - "backup_running_app_script": "Backup script voor app '{app:s}' is gestart...", "custom_app_url_required": "U moet een URL opgeven om uw aangepaste app {app:s} bij te werken", - "custom_appslist_name_required": "U moet een naam opgeven voor uw aangepaste app-lijst", - "dnsmasq_isnt_installed": "dnsmasq lijkt niet geïnstalleerd te zijn, voer alstublieft het volgende commando uit: 'apt-get remove bind9 && apt-get install dnsmasq'", "domain_cert_gen_failed": "Kan certificaat niet genereren", "domain_created": "Domein succesvol aangemaakt", "domain_creation_failed": "Kan domein niet aanmaken", "domain_deleted": "Domein succesvol verwijderd", "domain_deletion_failed": "Kan domein niet verwijderen", "domain_dyndns_already_subscribed": "U heeft reeds een domein bij DynDNS geregistreerd", - "domain_dyndns_invalid": "Het domein is ongeldig voor DynDNS", "domain_dyndns_root_unknown": "Onbekend DynDNS root domein", "domain_exists": "Domein bestaat al", "domain_uninstall_app_first": "Een of meerdere apps zijn geïnstalleerd op dit domein, verwijder deze voordat u het domein verwijdert", "domain_unknown": "Onbekend domein", - "domain_zone_exists": "DNS zone bestand bestaat al", - "domain_zone_not_found": "DNS zone bestand niet gevonden voor domein: {:s}", "done": "Voltooid", "downloading": "Downloaden...", "dyndns_cron_remove_failed": "De cron-job voor DynDNS kon niet worden verwijderd", @@ -62,25 +47,13 @@ "installation_complete": "Installatie voltooid", "installation_failed": "Installatie gefaald", "ldap_initialized": "LDAP is klaar voor gebruik", - "license_undefined": "Niet gedefinieerd", "mail_alias_remove_failed": "Kan mail-alias '{mail:s}' niet verwijderen", - "monitor_stats_no_update": "Er zijn geen recente monitoringstatistieken bij te werken", - "mysql_db_creation_failed": "Aanmaken MySQL database gefaald", - "mysql_db_init_failed": "Initialiseren MySQL database gefaald", - "mysql_db_initialized": "MySQL database is succesvol geïnitialiseerd", - "network_check_smtp_ko": "Uitgaande mail (SMPT port 25) wordt blijkbaar geblokkeerd door uw het netwerk", - "no_appslist_found": "Geen app-lijst gevonden", "no_internet_connection": "Server is niet verbonden met het internet", - "no_ipv6_connectivity": "IPv6-stack is onbeschikbaar", - "path_removal_failed": "Kan pad niet verwijderen {:s}", "pattern_email": "Moet een geldig emailadres bevatten (bv. abc@example.org)", - "pattern_listname": "Slechts cijfers, letters en '_' zijn toegelaten", "pattern_mailbox_quota": "Mailbox quota moet een waarde bevatten met b/k/M/G/T erachter of 0 om geen quota in te stellen", "pattern_password": "Wachtwoord moet tenminste 3 karakters lang zijn", "port_already_closed": "Poort {port:d} is al gesloten voor {ip_version:s} verbindingen", "port_already_opened": "Poort {port:d} is al open voor {ip_version:s} verbindingen", - "port_available": "Poort {port:d} is beschikbaar", - "port_unavailable": "Poort {port:d} is niet beschikbaar", "restore_app_failed": "De app '{app:s}' kon niet worden terug gezet", "restore_hook_unavailable": "De herstel-hook '{part:s}' is niet beschikbaar op dit systeem", "service_add_failed": "Kan service '{service:s}' niet toevoegen", @@ -91,7 +64,6 @@ "service_removed": "Service werd verwijderd", "service_stop_failed": "Kan service '{service:s}' niet stoppen", "service_unknown": "De service '{service:s}' bestaat niet", - "show_diff": "Let op de volgende verschillen zijn:\n{diff:s}", "unexpected_error": "Er is een onbekende fout opgetreden", "unrestore_app": "App '{app:s}' wordt niet teruggezet", "updating_apt_cache": "Lijst van beschikbare pakketten wordt bijgewerkt...", @@ -108,35 +80,21 @@ "yunohost_configured": "YunoHost configuratie is OK", "admin_password_change_failed": "Wachtwoord kan niet veranderd worden", "app_argument_choice_invalid": "Ongeldige keuze voor argument '{name:s}'. Het moet een van de volgende keuzes zijn {choices:s}", - "app_incompatible": "Deze applicatie is incompatibel met uw YunoHost versie", "app_not_correctly_installed": "{app:s} schijnt niet juist geïnstalleerd te zijn", "app_not_properly_removed": "{app:s} werd niet volledig verwijderd", - "app_package_need_update": "Het is noodzakelijk om het app pakket te updaten, in navolging van veranderingen aan YunoHost", "app_requirements_checking": "Controleer noodzakelijke pakketten...", - "app_requirements_failed": "Er wordt niet aan de aanvorderingen voldaan: {error}", "app_requirements_unmeet": "Er wordt niet aan de aanvorderingen voldaan, het pakket {pkgname} ({version}) moet {spec} zijn", "app_unsupported_remote_type": "Niet ondersteund besturings type voor de app", - "appslist_retrieve_error": "Niet mogelijk om de externe applicatie lijst op te halen {appslist:s}: {error:s}", - "appslist_retrieve_bad_format": "Opgehaald bestand voor applicatie lijst {appslist:s} is geen geldige applicatie lijst", - "appslist_name_already_tracked": "Er is reeds een geregistreerde applicatie lijst met de naam {name:s}.", - "appslist_url_already_tracked": "Er is reeds een geregistreerde applicatie lijst met de url {url:s}.", - "appslist_migrating": "Migreer applicatielijst {appslist:s} ...", - "appslist_could_not_migrate": "Kon applicatielijst {appslist:s} niet migreren! Niet in staat om de url te verwerken... De oude cron job is opgeslagen onder {bkp_file:s}.", - "appslist_corrupted_json": "Kon de applicatielijst niet laden. Het schijnt, dat {filename:s} beschadigd is.", - "ask_list_to_remove": "Te verwijderen lijst", "ask_main_domain": "Hoofd-domein", - "backup_action_required": "U moet iets om op te slaan uitkiezen", "backup_app_failed": "Kon geen backup voor app '{app:s}' aanmaken", "backup_archive_app_not_found": "App '{app:s}' kon niet in het backup archief gevonden worden", "backup_archive_broken_link": "Het backup archief kon niet geopend worden (Ongeldig verwijs naar {path:s})", - "backup_archive_hook_not_exec": "Hook '{hook:s}' kon voor deze backup niet uitgevoerd worden", "backup_archive_name_unknown": "Onbekend lokaal backup archief namens '{name:s}' gevonden", "backup_archive_open_failed": "Kan het backup archief niet openen", "backup_created": "Backup aangemaakt", "backup_creation_failed": "Aanmaken van backup mislukt", "backup_delete_error": "Kon pad '{path:s}' niet verwijderen", "backup_deleted": "Backup werd verwijderd", - "backup_extracting_archive": "Backup archief uitpakken...", "backup_hook_unknown": "backup hook '{hook:s}' onbekend", "backup_nothings_done": "Niets om op te slaan", "password_too_simple_1": "Het wachtwoord moet minimaal 8 tekens lang zijn", @@ -144,4 +102,4 @@ "admin_password_too_long": "Gelieve een wachtwoord te kiezen met minder dan 127 karakters", "app_action_cannot_be_ran_because_required_services_down": "De volgende diensten moeten actief zijn om deze actie uit te voeren: {services}. Probeer om deze te herstarten om verder te gaan (en om eventueel te onderzoeken waarom ze niet werken).", "aborting": "Annulatie." -} +} \ No newline at end of file diff --git a/locales/oc.json b/locales/oc.json index 64801e373..49e3ab02e 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -7,7 +7,6 @@ "installation_complete": "Installacion acabada", "app_id_invalid": "ID d’aplicacion incorrècte", "app_install_files_invalid": "Installacion impossibla d’aquestes fichièrs", - "app_no_upgrade": "Pas cap d’aplicacion d’actualizar", "app_not_correctly_installed": "{app:s} sembla pas ben installat", "app_not_installed": "Impossible de trobar l’aplicacion {app:s}. Vaquí la lista de las aplicacions installadas : {all_apps}", "app_not_properly_removed": "{app:s} es pas estat corrèctament suprimit", @@ -17,23 +16,12 @@ "app_upgrade_failed": "Impossible d’actualizar {app:s} : {error}", "app_upgrade_some_app_failed": "D’aplicacions se pòdon pas actualizar", "app_upgraded": "{app:s} es estada actualizada", - "appslist_fetched": "Recuperacion de la lista d’aplicacions {appslist:s} corrèctament realizada", - "appslist_migrating": "Migracion de la lista d’aplicacion{appslist:s}…", - "appslist_name_already_tracked": "I a ja una lista d’aplicacion enregistrada amb lo nom {name:s}.", - "appslist_removed": "Supression de la lista d’aplicacions {appslist:s} corrèctament realizada", - "appslist_retrieve_bad_format": "Lo fichièr recuperat per la lista d’aplicacions {appslist:s} es pas valid", - "appslist_unknown": "La lista d’aplicacions {appslist:s} es desconeguda.", - "appslist_url_already_tracked": "I a ja una lista d’aplicacions enregistrada amb l’URL {url:s}.", - "ask_current_admin_password": "Senhal administrator actual", "ask_email": "Adreça de corrièl", "ask_firstname": "Prenom", "ask_lastname": "Nom", - "ask_list_to_remove": "Lista de suprimir", "ask_main_domain": "Domeni màger", "ask_new_admin_password": "Nòu senhal administrator", "ask_password": "Senhal", - "ask_path": "Camin", - "backup_action_required": "Devètz precisar çò que cal salvagardar", "backup_app_failed": "Impossible de salvagardar l’aplicacion « {app:s} »", "backup_applying_method_copy": "Còpia de totes los fichièrs dins la salvagarda…", "backup_applying_method_tar": "Creacion de l’archiu TAR de la salvagarda…", @@ -46,33 +34,23 @@ "app_change_url_failed_nginx_reload": "Reaviada de NGINX impossibla. Vaquí la sortida de « nginx -t » :\n{nginx_errors:s}", "app_change_url_identical_domains": "L’ancian e lo novèl coble domeni/camin son identics per {domain:s}{path:s}, pas res a far.", "app_change_url_success": "L’URL de l’aplicacion {app:s} es ara {domain:s}{path:s}", - "app_checkurl_is_deprecated": "Packagers /!\\ ’app checkurl’ es obsolèt ! Utilizatz ’app register-url’ a la plaça !", "app_extraction_failed": "Extraccion dels fichièrs d’installacion impossibla", - "app_incompatible": "L’aplicacion {app} es pas compatibla amb vòstra version de YunoHost", - "app_location_already_used": "L’aplicacion « {app} » es ja installada dins ({path})", "app_manifest_invalid": "I a quicòm que truca amb lo manifest de l’aplicacion : {error}", - "app_package_need_update": "Lo paquet de l’aplicacion {app} deu èsser actualizat per poder seguir los cambiaments de YunoHost", "app_requirements_checking": "Verificacion dels paquets requesits per {app}…", "app_sources_fetch_failed": "Recuperacion dels fichièrs fonts impossibla, l’URL es corrècta ?", "app_unsupported_remote_type": "Lo tipe alonhat utilizat per l’aplicacion es pas suportat", - "appslist_retrieve_error": "Impossible de recuperar la lista d’aplicacions alonhadas {appslist:s} : {error:s}", "backup_archive_app_not_found": "L’aplicacion « {app:s} » es pas estada trobada dins l’archiu de la salvagarda", "backup_archive_broken_link": "Impossible d’accedir a l’archiu de salvagarda (ligam invalid cap a {path:s})", - "backup_archive_mount_failed": "Lo montatge de l’archiu de salvagarda a fracassat", "backup_archive_open_failed": "Impossible de dobrir l’archiu de salvagarda", "backup_archive_system_part_not_available": "La part « {part:s} » del sistèma es pas disponibla dins aquesta salvagarda", "backup_cleaning_failed": "Impossible de netejar lo repertòri temporari de salvagarda", "backup_copying_to_organize_the_archive": "Còpia de {size:s} Mio per organizar l’archiu", "backup_created": "Salvagarda acabada", - "backup_creating_archive": "Creacion de l’archiu de salvagarda…", "backup_creation_failed": "Creacion impossibla de l’archiu de salvagarda", "app_already_installed_cant_change_url": "Aquesta aplicacion es ja installada. Aquesta foncion pòt pas simplament cambiar l’URL. Agachatz « app changeurl » s’es disponible.", - "app_change_no_change_url_script": "L’aplicacion {app_name:s} pren pas en compte lo cambiament d’URL, poiretz aver de l’actualizar.", "app_change_url_no_script": "L’aplicacion {app_name:s} pren pas en compte lo cambiament d’URL, benlèu que vos cal l’actualizar.", "app_make_default_location_already_used": "Impossible de configurar l’aplicacion « {app} » per defaut pel domeni {domain} perque es ja utilizat per l’aplicacion {other_app}", - "app_location_install_failed": "Impossible d’installar l’aplicacion a aqueste emplaçament per causa de conflicte amb l’aplicacion {other_app} qu’es ja installada sus {other_path}", "app_location_unavailable": "Aquesta URL es pas disponibla o en conflicte amb una aplicacion existenta :\n{apps:s}", - "appslist_corrupted_json": "Cargament impossible de la lista d’aplicacion. Sembla que {filename:s} siá gastat.", "backup_delete_error": "Supression impossibla de « {path:s} »", "backup_deleted": "La salvagarda es estada suprimida", "backup_hook_unknown": "Script de salvagarda « {hook:s} » desconegut", @@ -82,12 +60,9 @@ "backup_method_tar_finished": "L’archiu TAR de la salvagarda es estat creat", "backup_output_directory_not_empty": "Devètz causir un dorsièr de sortida void", "backup_output_directory_required": "Vos cal especificar un dorsièr de sortida per la salvagarda", - "backup_running_app_script": "Lançament de l’escript de salvagarda de l’aplicacion « {app:s} »...", "backup_running_hooks": "Execucion dels scripts de salvagarda…", "backup_system_part_failed": "Impossible de salvagardar la part « {part:s} » del sistèma", - "app_requirements_failed": "Impossible de complir unas condicions requesidas per {app} : {error}", "app_requirements_unmeet": "Las condicions requesidas per {app} son pas complidas, lo paquet {pkgname} ({version}) deu èsser {spec}", - "appslist_could_not_migrate": "Migracion de la lista impossibla {appslist:s} ! Impossible d’analizar l’URL… L’anciana tasca cron es estada servada dins {bkp_file:s}.", "backup_abstract_method": "Aqueste metòde de salvagarda es pas encara implementat", "backup_applying_method_custom": "Crida lo metòde de salvagarda personalizat « {method:s} »…", "backup_borg_not_implemented": "Lo metòde de salvagarda Bord es pas encara implementat", @@ -95,15 +70,12 @@ "backup_csv_addition_failed": "Impossible d’ajustar de fichièrs a la salvagarda dins lo fichièr CSV", "backup_custom_backup_error": "Fracàs del metòde de salvagarda personalizat a l’etapa « backup »", "backup_custom_mount_error": "Fracàs del metòde de salvagarda personalizat a l’etapa « mount »", - "backup_custom_need_mount_error": "Fracàs del metòde de salvagarda personalizat a l’etapa « need_mount »", "backup_method_custom_finished": "Lo metòde de salvagarda personalizat « {method:s} » es acabat", "backup_nothings_done": "I a pas res de salvagardar", "backup_unable_to_organize_files": "Impossible d’organizar los fichièrs dins l’archiu amb lo metòde rapid", - "service_status_failed": "Impossible de determinar l’estat del servici « {service:s} »", "service_stopped": "Lo servici « {service:s} » es estat arrestat", "service_unknown": "Servici « {service:s} » desconegut", "unbackup_app": "L’aplicacion « {app:s} » serà pas salvagardada", - "unit_unknown": "Unitat « {unit:s} » desconeguda", "unlimit": "Cap de quòta", "unrestore_app": "L’aplicacion « {app:s} » serà pas restaurada", "upnp_dev_not_found": "Cap de periferic compatible UPnP pas trobat", @@ -115,7 +87,6 @@ "yunohost_installing": "Installacion de YunoHost…", "backup_applying_method_borg": "Mandadís de totes los fichièrs a la salvagarda dins lo repertòri borg-backup…", "backup_csv_creation_failed": "Creacion impossibla del fichièr CSV necessari a las operacions futuras de restauracion", - "backup_extracting_archive": "Extraccion de l’archiu de salvagarda…", "backup_output_symlink_dir_broken": "Vòstre repertòri d’archiu « {path:s} » es un ligam simbolic copat. Saique oblidèretz de re/montar o de connectar supòrt.", "backup_with_no_backup_script_for_app": "L’aplicacion {app:s} a pas cap de script de salvagarda. I fasèm pas cas.", "backup_with_no_restore_script_for_app": "L’aplicacion {app:s} a pas cap de script de restauracion, poiretz pas restaurar automaticament la salvagarda d’aquesta aplicacion.", @@ -134,24 +105,16 @@ "certmanager_self_ca_conf_file_not_found": "Impossible de trobar lo fichièr de configuracion per l’autoritat del certificat auto-signat (fichièr : {file:s})", "certmanager_unable_to_parse_self_CA_name": "Analisi impossibla del nom de l’autoritat del certificat auto-signat (fichièr : {file:s})", "custom_app_url_required": "Cal que donetz una URL per actualizar vòstra aplicacion personalizada {app:s}", - "custom_appslist_name_required": "Cal que nomenetz vòstra lista d’aplicacions personalizadas", - "diagnosis_debian_version_error": "Impossible de determinar la version de Debian : {error}", - "diagnosis_kernel_version_error": "Impossible de recuperar la version del nuclèu : {error}", - "diagnosis_no_apps": "Pas cap d’aplicacion installada", - "dnsmasq_isnt_installed": "dnsmasq sembla pas èsser installat, mercés de lançar « apt-get remove bind9 && apt-get install dnsmasq »", "domain_cannot_remove_main": "Impossible de levar lo domeni màger. Definissètz un novèl domeni màger d’en primièr", "domain_cert_gen_failed": "Generacion del certificat impossibla", "domain_created": "Domeni creat", "domain_creation_failed": "Creacion del domeni {domain}: impossibla", "domain_deleted": "Domeni suprimit", "domain_deletion_failed": "Supression impossibla del domeni {domain}: {error}", - "domain_dyndns_invalid": "Domeni incorrècte per una utilizacion amb DynDNS", "domain_dyndns_root_unknown": "Domeni DynDNS màger desconegut", "domain_exists": "Lo domeni existís ja", "domain_hostname_failed": "Fracàs de la creacion d’un nòu nom d’òst. Aquò poirà provocar de problèmas mai tard (mas es pas segur… benlèu que coparà pas res).", "domain_unknown": "Domeni desconegut", - "domain_zone_exists": "Lo fichièr zòna DNS existís ja", - "domain_zone_not_found": "Fichèr de zòna DNS introbable pel domeni {:s}", "domains_available": "Domenis disponibles :", "done": "Acabat", "downloading": "Telecargament…", @@ -170,16 +133,13 @@ "dyndns_unavailable": "Lo domeni {domain:s} es pas disponible.", "extracting": "Extraccion…", "field_invalid": "Camp incorrècte : « {:s} »", - "format_datetime_short": "%d/%m/%Y %H:%M", "global_settings_cant_open_settings": "Fracàs de la dobertura del fichièr de configuracion, rason : {reason:s}", "global_settings_key_doesnt_exists": "La clau « {settings_key:s} » existís pas dins las configuracions globalas, podètz veire totas las claus disponiblas en executant « yunohost settings list »", "global_settings_reset_success": "Configuracion precedenta ara salvagarda dins {path:s}", "global_settings_setting_example_bool": "Exemple d’opcion booleana", "global_settings_unknown_setting_from_settings_file": "Clau desconeguda dins los paramètres : {setting_key:s}, apartada e salvagardada dins /etc/yunohost/settings-unknown.json", "installation_failed": "Quicòm a trucat e l’installacion a pas reüssit", - "invalid_url_format": "Format d’URL pas valid", "ldap_initialized": "L’annuari LDAP es inicializat", - "license_undefined": "indefinida", "main_domain_change_failed": "Modificacion impossibla del domeni màger", "main_domain_changed": "Lo domeni màger es estat modificat", "migrate_tsig_end": "La migracion cap a HMAC-SHA512 es acabada", @@ -188,44 +148,27 @@ "migrate_tsig_wait_4": "30 segondas…", "migration_description_0002_migrate_to_tsig_sha256": "Melhora la seguretat de DynDNS TSIG en utilizar SHA512 allòc de MD5", "migration_description_0003_migrate_to_stretch": "Mesa a nivèl del sistèma cap a Debian Stretch e YunoHost 3.0", - "migration_0003_backward_impossible": "La migracion Stretch es pas reversibla.", "migration_0003_start": "Aviada de la migracion cap a Stretech. Los jornals seràn disponibles dins {logfile}.", "migration_0003_patching_sources_list": "Petaçatge de sources.lists…", "migration_0003_main_upgrade": "Aviada de la mesa a nivèl màger…", "migration_0003_fail2ban_upgrade": "Aviada de la mesa a nivèl de Fail2Ban…", "migration_0003_not_jessie": "La distribucion Debian actuala es pas Jessie !", "migrations_cant_reach_migration_file": "Impossible d’accedir als fichièrs de migracion amb lo camin %s", - "migrations_current_target": "La cibla de migracion est {}", - "migrations_error_failed_to_load_migration": "ERROR : fracàs del cargament de la migracion {number} {name}", "migrations_list_conflict_pending_done": "Podètz pas utilizar --previous e --done a l’encòp.", "migrations_loading_migration": "Cargament de la migracion {id}…", "migrations_no_migrations_to_run": "Cap de migracion de lançar", - "migrations_show_currently_running_migration": "Realizacion de la migracion {number} {name}…", - "migrations_show_last_migration": "La darrièra migracion realizada es {}", - "monitor_glances_con_failed": "Connexion impossibla al servidor Glances", - "monitor_not_enabled": "Lo seguiment de l’estat del servidor es pas activat", - "monitor_stats_no_update": "Cap de donadas d’estat del servidor d’actualizar", - "mountpoint_unknown": "Ponch de montatge desconegut", - "mysql_db_creation_failed": "Creacion de la basa de donadas MySQL impossibla", - "no_appslist_found": "Cap de lista d’aplicacions pas trobada", "no_internet_connection": "Lo servidor es pas connectat a Internet", - "package_not_installed": "Lo paquet « {pkgname} » es pas installat", "package_unknown": "Paquet « {pkgname} » desconegut", - "packages_no_upgrade": "I a pas cap de paquet d’actualizar", "packages_upgrade_failed": "Actualizacion de totes los paquets impossibla", - "path_removal_failed": "Impossible de suprimir lo camin {:s}", "pattern_domain": "Deu èsser un nom de domeni valid (ex : mon-domeni.org)", "pattern_email": "Deu èsser una adreça electronica valida (ex : escais@domeni.org)", "pattern_firstname": "Deu èsser un pichon nom valid", "pattern_lastname": "Deu èsser un nom valid", "pattern_password": "Deu conténer almens 3 caractèrs", - "pattern_port": "Deu èsser un numèro de pòrt valid (ex : 0-65535)", "pattern_port_or_range": "Deu èsser un numèro de pòrt valid (ex : 0-65535) o un interval de pòrt (ex : 100:200)", "pattern_positive_number": "Deu èsser un nombre positiu", "port_already_closed": "Lo pòrt {port:d} es ja tampat per las connexions {ip_version:s}", "port_already_opened": "Lo pòrt {port:d} es ja dubèrt per las connexions {ip_version:s}", - "port_available": "Lo pòrt {port:d} es disponible", - "port_unavailable": "Lo pòrt {port:d} es pas disponible", "restore_already_installed_app": "Una aplicacion es ja installada amb l’id « {app:s} »", "restore_app_failed": "Impossible de restaurar l’aplicacion « {app:s} »", "backup_ask_for_copying_if_needed": "Volètz far una salvagarda en utilizant {size:s} Mo temporàriament ? (Aqueste biais de far es emplegat perque unes fichièrs an pas pogut èsser preparats amb un metòde mai eficaç.)", @@ -236,13 +179,11 @@ "certmanager_certificate_fetching_or_enabling_failed": "Sembla qu’utilizar lo nòu certificat per {domain:s} fonciona pas…", "certmanager_conflicting_nginx_file": "Impossible de preparar lo domeni pel desfís ACME : lo fichièr de configuracion NGINX {filepath:s} es en conflicte e deu èsser levat d’en primièr", "certmanager_couldnt_fetch_intermediate_cert": "Expiracion del relambi pendent l’ensag de recuperacion del certificat intermediari dins de Let’s Encrypt. L’installacion / lo renovèlament es estat interromput - tornatz ensajar mai tard.", - "certmanager_domain_not_resolved_locally": "Lo domeni {domain:s} pòt pas èsser determinat dins de vòstre servidor YunoHost. Pòt arribar s’avètz recentament modificat vòstre enregistrament DNS. Dins aqueste cas, mercés d’esperar unas oras per l’espandiment. Se lo problèma dura, consideratz ajustar {domain:s} a /etc/hosts. (Se sabètz çò que fasètz, utilizatz --no-checks per desactivar las verificacions.)", "certmanager_error_no_A_record": "Cap d’enregistrament DNS « A » pas trobat per {domain:s}. Vos cal indicar que lo nom de domeni mene a vòstra maquina per poder installar un certificat Let’S Encrypt ! (Se sabètz çò que fasètz, utilizatz --no-checks per desactivar las verificacions.)", "certmanager_hit_rate_limit": "Tròp de certificats son ja estats demandats recentament per aqueste ensem de domeni {domain:s}. Mercés de tornar ensajar mai tard. Legissètz https://letsencrypt.org/docs/rate-limits/ per mai detalhs", "certmanager_http_check_timeout": "Expiracion del relambi d’ensag del servidor de se contactar via HTTP amb son adreça IP publica {domain:s} amb l’adreça {ip:s}. Coneissètz benlèu de problèmas d’hairpinning o lo parafuòc/router amont de vòstre servidor es mal configurat.", "domain_dns_conf_is_just_a_recommendation": "Aqueste pagina mòstra la configuracion *recomandada*. Non configura *pas* lo DNS per vos. Sètz responsable de la configuracion de vòstra zòna DNS en çò de vòstre registrar DNS amb aquesta recomandacion.", "domain_dyndns_already_subscribed": "Avètz ja soscrich a un domeni DynDNS", - "domain_dyndns_dynette_is_unreachable": "Impossible de contactar la dynette YunoHost, siá YunoHost pas es pas corrèctament connectat a Internet, siá lo servidor de la dynett es arrestat. Error : {error}", "domain_uninstall_app_first": "Una o mantuna aplicacions son installadas sus aqueste domeni. Mercés de las desinstallar d’en primièr abans de suprimir aqueste domeni", "firewall_reload_failed": "Impossible de recargar lo parafuòc", "firewall_reloaded": "Parafuòc recargat", @@ -267,18 +208,10 @@ "migration_0003_yunohost_upgrade": "Aviada de la mesa a nivèl del paquet YunoHost… La migracion acabarà, mas l’actualizacion reala se realizarà tot bèl aprèp. Un còp acabada, poiretz vos reconnectar a l’administracion web.", "migration_0003_system_not_fully_up_to_date": "Lo sistèma es pas complètament a jorn. Mercés de lançar una mesa a jorn classica abans de començar la migracion per Stretch.", "migration_0003_modified_files": "Mercés de notar que los fichièrs seguents son estats detectats coma modificats manualament e poiràn èsser escafats a la fin de la mesa a nivèl : {manually_modified_files}", - "monitor_period_invalid": "Lo periòde de temps es incorrècte", - "monitor_stats_file_not_found": "Lo fichièr d’estatisticas es introbable", - "monitor_stats_period_unavailable": "Cap d’estatisticas son pas disponiblas pel periòde", - "mysql_db_init_failed": "Impossible d’inicializar la basa de donadas MySQL", "service_disable_failed": "Impossible de desactivar lo servici « {service:s} »↵\n↵\nJornals recents : {logs:s}", "service_disabled": "Lo servici « {service:s} » es desactivat", "service_enable_failed": "Impossible d’activar lo servici « {service:s} »↵\n↵\nJornals recents : {logs:s}", "service_enabled": "Lo servici « {service:s} » es activat", - "service_no_log": "Cap de jornal de far veire pel servici « {service:s} »", - "service_regenconf_dry_pending_applying": "Verificacion de las configuracions en espèra que poirián èsser aplicadas pel servici « {service} »…", - "service_regenconf_failed": "Regeneracion impossibla de la configuracion pels servicis : {services}", - "service_regenconf_pending_applying": "Aplicacion de las configuracions en espèra pel servici « {service} »…", "service_remove_failed": "Impossible de levar lo servici « {service:s} »", "service_removed": "Lo servici « {service:s} » es estat levat", "service_start_failed": "Impossible d’aviar lo servici « {service:s} »↵\n↵\nJornals recents : {logs:s}", @@ -296,26 +229,14 @@ "user_deleted": "L’utilizaire es suprimit", "user_deletion_failed": "Supression impossibla de l’utilizaire", "user_home_creation_failed": "Creacion impossibla del repertòri personal a l’utilizaire", - "user_info_failed": "Recuperacion impossibla de las informacions tocant l’utilizaire", "user_unknown": "Utilizaire « {user:s} » desconegut", "user_update_failed": "Modificacion impossibla de l’utilizaire", "user_updated": "L’utilizaire es estat modificat", "yunohost_ca_creation_failed": "Creacion impossibla de l’autoritat de certificacion", "yunohost_ca_creation_success": "L’autoritat de certificacion locala es creada.", - "service_conf_file_kept_back": "Lo fichièr de configuracion « {conf} » deuriá èsser suprimit pel servici {service} mas es estat servat.", - "service_conf_file_manually_modified": "Lo fichièr de configuracion « {conf} » es estat modificat manualament e serà pas actualizat", - "service_conf_file_manually_removed": "Lo fichièr de configuracion « {conf} » es suprimit manualament e serà pas creat", - "service_conf_file_remove_failed": "Supression impossibla del fichièr de configuracion « {conf} »", - "service_conf_file_removed": "Lo fichièr de configuracion « {conf} » es suprimit", - "service_conf_file_updated": "Lo fichièr de configuracion « {conf} » es actualizat", - "service_conf_new_managed_file": "Lo servici {service} gerís ara lo fichièr de configuracion « {conf} ».", - "service_conf_up_to_date": "La configuracion del servici « {service} » es ja actualizada", - "service_conf_would_be_updated": "La configuracion del servici « {service} » seriá estada actualizada", "service_description_avahi-daemon": "permet d’aténher vòstre servidor via yunohost.local sus vòstre ret local", "service_description_dnsmasq": "gerís la resolucion dels noms de domeni (DNS)", "updating_apt_cache": "Actualizacion de la lista dels paquets disponibles…", - "service_conf_file_backed_up": "Lo fichièr de configuracion « {conf} » es salvagardat dins « {backup} »", - "service_conf_file_copy_failed": "Còpia impossibla del nòu fichièr de configuracion « {new} » cap a « {conf} »", "server_reboot_confirm": "Lo servidor es per reaviar sul pic, o volètz vertadièrament ? {answers:s}", "service_add_failed": "Apondon impossible del servici « {service:s} »", "service_added": "Lo servici « {service:s} » es ajustat", @@ -328,7 +249,6 @@ "restore_failed": "Impossible de restaurar lo sistèma", "restore_hook_unavailable": "Lo script de restauracion « {part:s} » es pas disponible sus vòstre sistèma e es pas tanpauc dins l’archiu", "restore_may_be_not_enough_disk_space": "Lo sistèma sembla d’aver pas pro d’espaci disponible (liure : {free_space:d} octets, necessari : {needed_space:d} octets, marge de seguretat : {margin:d} octets)", - "restore_mounting_archive": "Montatge de l’archiu dins « {path:s} »", "restore_not_enough_disk_space": "Espaci disponible insufisent (liure : {free_space:d} octets, necessari : {needed_space:d} octets, marge de seguretat : {margin:d} octets)", "restore_nothings_done": "Res es pas estat restaurat", "restore_removing_tmp_dir_failed": "Impossible de levar u ancian repertòri temporari", @@ -338,35 +258,19 @@ "server_shutdown": "Lo servidor serà atudat", "server_shutdown_confirm": "Lo servidor es per s’atudar sul pic, o volètz vertadièrament ? {answers:s}", "server_reboot": "Lo servidor es per reaviar", - "network_check_mx_ko": "L’enregistrament DNS MX es pas especificat", - "new_domain_required": "Vos cal especificar lo domeni màger", - "no_ipv6_connectivity": "La connectivitat IPv6 es pas disponibla", "not_enough_disk_space": "Espaci disc insufisent sus « {path:s} »", - "package_unexpected_error": "Una error inesperada es apareguda amb lo paquet « {pkgname} »", - "packages_upgrade_critical_later": "Los paquets critics {packages:s} seràn actualizats mai tard", - "restore_action_required": "Devètz precisar çò que cal restaurar", "service_cmd_exec_failed": "Impossible d’executar la comanda « {command:s} »", - "service_conf_updated": "La configuracion es estada actualizada pel servici « {service} »", "service_description_mysql": "garda las donadas de las aplicacions (base de donadas SQL)", - "service_description_php5-fpm": "executa d’aplicacions escrichas en PHP amb nginx", "service_description_postfix": "emplegat per enviar e recebre de corrièls", - "service_description_rmilter": "verifica mantun paramètres dels corrièls", "service_description_slapd": "garda los utilizaires, domenis e lors informacions ligadas", "service_description_ssh": "vos permet de vos connectar a distància a vòstre servidor via un teminal (protocòl SSH)", "service_description_yunohost-api": "permet las interaccions entre l’interfàcia web de YunoHost e le sistèma", "service_description_yunohost-firewall": "gerís los pòrts de connexion dobèrts e tampats als servicis", - "ssowat_persistent_conf_read_error": "Error en legir la configuracion duradissa de SSOwat : {error:s}. Modificatz lo fichièr /etc/ssowat/conf.json.persistent per reparar la sintaxi JSON", - "ssowat_persistent_conf_write_error": "Error en salvagardar la configuracion duradissa de SSOwat : {error:s}. Modificatz lo fichièr /etc/ssowat/conf.json.persistent per reparar la sintaxi JSON", - "certmanager_old_letsencrypt_app_detected": "\nYunohost a detectat que l’aplicacion ’letsencrypt’ es installada, aquò es en conflicte amb las novèlas foncionalitats integradas de gestion dels certificats de Yunohost. Se volètz utilizar aquelas foncionalitats integradas, mercés de lançar las comandas seguentas per migrar vòstra installacion :\n\n yunohost app remove letsencrypt\n yunohost domain cert-install\n\nN.B. : aquò provarà de tornar installar los certificats de totes los domenis amb un certificat Let’s Encrypt o las auto-signats", - "diagnosis_monitor_disk_error": "Impossible de supervisar los disques : {error}", - "diagnosis_monitor_network_error": "Impossible de supervisar la ret : {error}", - "diagnosis_monitor_system_error": "Impossible de supervisar lo sistèma : {error}", "executing_command": "Execucion de la comanda « {command:s} »…", "executing_script": "Execucion del script « {script:s} »…", "global_settings_cant_serialize_settings": "Fracàs de la serializacion de las donadas de parametratge, rason : {reason:s}", "ip6tables_unavailable": "Podètz pas jogar amb ip6tables aquí. Siá sèts dins un contenedor, siá vòstre nuclèu es pas compatible amb aquela opcion", "iptables_unavailable": "Podètz pas jogar amb iptables aquí. Siá sèts dins un contenedor, siá vòstre nuclèu es pas compatible amb aquela opcion", - "update_cache_failed": "Impossible d’actualizar lo cache de l’APT", "mail_alias_remove_failed": "Supression impossibla de l’alias de corrièl « {mail:s} »", "mail_forward_remove_failed": "Supression impossibla del corrièl de transferiment « {mail:s} »", "migrate_tsig_start": "L’algorisme de generacion de claus es pas pro securizat per la signatura TSIG del domeni « {domain} », lançament de la migracion cap a un mai segur HMAC-SHA-512", @@ -375,29 +279,18 @@ "migration_0003_still_on_jessie_after_main_upgrade": "Quicòm a trucat pendent la mesa a nivèl màger : lo sistèma es encara jos Jessie ?!? Per trobar lo problèma, agachatz {log}…", "migration_0003_general_warning": "Notatz qu’aquesta migracion es una operacion delicata. Encara que la còla YunoHost aguèsse fach çò melhor per la tornar legir e provar, la migracion poiriá copar de parts del sistèma o de las aplicacions.\n\nEn consequéncia, vos recomandam :\n· · · · - de lançar una salvagarda de vòstras donadas o aplicacions criticas. Mai d’informacions a https://yunohost.org/backup ;\n· · · · - d’èsser pacient aprèp aver lançat la migracion : segon vòstra connexion Internet e material, pòt trigar qualques oras per que tot siá mes al nivèl.\n\nEn mai, lo pòrt per SMTP, utilizat pels clients de corrièls extèrns (coma Thunderbird o K9-Mail per exemple) foguèt cambiat de 465 (SSL/TLS) per 587 (STARTTLS). L’ancian pòrt 465 serà automaticament tampat e lo nòu pòrt 587 serà dobèrt dins lo parafuòc. Vosautres e vòstres utilizaires *auretz* d’adaptar la configuracion de vòstre client de corrièl segon aqueles cambiaments !", "migration_0003_problematic_apps_warning": "Notatz que las aplicacions seguentas, saique problematicas, son estadas desactivadas. Semblan d’aver estadas installadas d’una lista d’aplicacions o que son pas marcadas coma «working ». En consequéncia, podèm pas assegurar que tendràn de foncionar aprèp la mesa a nivèl : {problematic_apps}", - "migrations_bad_value_for_target": "Nombre invalid pel paramètre « target », los numèros de migracion son 0 o {}", "migrations_migration_has_failed": "La migracion {id} a pas capitat, abandon. Error : {exception}", "migrations_skip_migration": "Passatge de la migracion {id}…", "migrations_to_be_ran_manually": "La migracion {id} deu èsser lançada manualament. Mercés d’anar a Aisinas > Migracion dins l’interfàcia admin, o lançar « yunohost tools migrations migrate ».", "migrations_need_to_accept_disclaimer": "Per lançar la migracion {id} , avètz d’acceptar aquesta clausa de non-responsabilitat :\n---\n{disclaimer}\n---\nS’acceptatz de lançar la migracion, mercés de tornar executar la comanda amb l’opcion accept-disclaimer.", - "monitor_disabled": "La supervision del servidor es desactivada", - "monitor_enabled": "La supervision del servidor es activada", - "mysql_db_initialized": "La basa de donadas MySQL es estada inicializada", - "no_restore_script": "Lo script de salvagarda es pas estat trobat per l’aplicacion « {app:s} »", "pattern_backup_archive_name": "Deu èsser un nom de fichièr valid compausat de 30 caractèrs alfanumerics al maximum e « -_. »", - "pattern_listname": "Deu èsser compausat solament de caractèrs alfanumerics e de tirets basses", "service_description_dovecot": "permet als clients de messatjariá d’accedir/recuperar los corrièls (via IMAP e POP3)", "service_description_fail2ban": "protegís contra los atacs brute-force e d’autres atacs venents d’Internet", - "service_description_glances": "susvelha las informacions sistèma de vòstre servidor", "service_description_metronome": "gerís los comptes de messatjariás instantanèas XMPP", "service_description_nginx": "fornís o permet l’accès a totes los sites web albergats sus vòstre servidor", "service_description_nslcd": "gerís la connexion en linha de comanda dels utilizaires YunoHost", "service_description_redis-server": "una basa de donadas especializada per un accès rapid a las donadas, las filas d’espèra e la comunicacion entre programas", "service_description_rspamd": "filtra lo corrièl pas desirat e mai foncionalitats ligadas al corrièl", - "migrations_backward": "Migracion en darrièr.", - "migrations_forward": "Migracion en avant", - "network_check_smtp_ko": "Lo trafic de corrièl sortent (pòrt 25 SMTP) sembla blocat per vòstra ret", - "network_check_smtp_ok": "Lo trafic de corrièl sortent (pòrt 25 SMTP) es pas blocat", "pattern_mailbox_quota": "Deu èsser una talha amb lo sufixe b/k/M/G/T o 0 per desactivar la quòta", "backup_archive_writing_error": "Impossible d’ajustar los fichièrs « {source:s} » a la salvagarda (nomenats dins l’archiu « {dest:s} »)dins l’archiu comprimit « {archive:s} »", "backup_cant_mount_uncompress_archive": "Impossible de montar en lectura sola lo repertòri de l’archiu descomprimit", @@ -413,11 +306,6 @@ "log_help_to_get_failed_log": "L’operacion « {desc} » a pas reüssit ! Per obténer d’ajuda, mercés de partejar lo jornal d’audit complèt d’aquesta operacion en utilizant la comanda « yunohost log display {name} --share »", "log_does_exists": "I a pas cap de jornal d’audit per l’operacion amb lo nom « {log} », utilizatz « yunohost log list » per veire totes los jornals d’operacion disponibles", "log_operation_unit_unclosed_properly": "L’operacion a pas acabat corrèctament", - "log_app_addaccess": "Ajustar l’accès a « {} »", - "log_app_removeaccess": "Tirar l’accès a « {} »", - "log_app_clearaccess": "Tirar totes los accèsses a « {} »", - "log_app_fetchlist": "Ajustar una lista d’aplicacions", - "log_app_removelist": "Levar una lista d’aplicacions", "log_app_change_url": "Cambiar l’URL de l’aplicacion « {} »", "log_app_install": "Installar l’aplicacion « {} »", "log_app_remove": "Levar l’aplicacion « {} »", @@ -435,14 +323,11 @@ "log_letsencrypt_cert_install": "Installar un certificat Let's Encrypt sul domeni « {} »", "log_selfsigned_cert_install": "Installar lo certificat auto-signat sul domeni « {} »", "log_letsencrypt_cert_renew": "Renovar lo certificat Let's Encrypt de « {} »", - "log_service_enable": "Activar lo servici « {} »", - "log_service_regen_conf": "Regenerar la configuracion sistèma de « {} »", "log_user_create": "Ajustar l’utilizaire « {} »", "log_user_delete": "Levar l’utilizaire « {} »", "log_user_update": "Actualizar las informacions de l’utilizaire « {} »", "log_domain_main_domain": "Far venir « {} » lo domeni màger", "log_tools_migrations_migrate_forward": "Migrar", - "log_tools_migrations_migrate_backward": "Tornar en arrièr", "log_tools_postinstall": "Realizar la post installacion del servidor YunoHost", "log_tools_upgrade": "Actualizacion dels paquets sistèma", "log_tools_shutdown": "Atudar lo servidor", @@ -453,14 +338,12 @@ "migration_0005_postgresql_94_not_installed": "PostgreSQL es pas installat sul sistèma. I a pas res per far.", "migration_0005_postgresql_96_not_installed": "Avèm trobat que Postgresql 9.4 es installat, mas cap de version de Postgresql 9.6 pas trobada !? Quicòm d’estranh a degut arribar a vòstre sistèma :( …", "migration_0005_not_enough_space": "I a pas pro d’espaci disponible sus {path} per lançar la migracion d’aquela passa :(.", - "recommend_to_add_first_user": "La post installacion es acabada, mas YunoHost fa besonh d’almens un utilizaire per foncionar coma cal. Vos cal n’ajustar un en utilizant la comanda « yunohost user create Jornals d’audit (dins l’interfàcia d’administracion) o amb « yunohost log list » (en linha de comanda).", "update_apt_cache_failed": "I a agut d’errors en actualizar la memòria cache d’APT (lo gestionari de paquets de Debian). Aquí avètz las linhas de sources.list que pòdon vos ajudar a identificar las linhas problematicas : \n{sourceslist}", "update_apt_cache_warning": "I a agut d’errors en actualizar la memòria cache d’APT (lo gestionari de paquets de Debian). Aquí avètz las linhas de sources.list que pòdon vos ajudar a identificar las linhas problematicas : \n{sourceslist}", - "apps_permission_not_found": "Cap d’autorizacion pas trobada per las aplicacions installadas", "backup_permission": "Autorizacion de salvagarda per l’aplicacion {app:s}", - "edit_group_not_allowed": "Sètz pas autorizat a cambiar lo grop {group:s}", - "error_when_removing_sftpuser_group": "Error en ensajar de suprimir lo grop sftpusers", - "group_name_already_exist": "Lo grop {name:s} existís ja", "group_created": "Grop « {group} » creat", "group_creation_failed": "Fracàs de la creacion del grop « {group} » : {error}", "group_deleted": "Lo grop « {group} » es estat suprimit", "group_deletion_failed": "Fracàs de la supression del grop « {group} » : {error}", - "group_deletion_not_allowed": "Lo grop « {group} » pòt pas èsser suprimir manualament.", - "group_info_failed": "Recuperacion de las informacions del grop « {group} » impossibla", "group_unknown": "Lo grop « {group} » es desconegut", - "log_user_group_add": "Ajustar lo grop « {} »", "log_user_group_delete": "Suprimir lo grop « {} »", "migration_0011_backup_before_migration": "Creacion d’una còpia de seguretat de la basa de donadas LDAP e de la configuracion de las aplicacions abans d’efectuar la migracion.", "migration_0011_create_group": "Creacion d’un grop per cada utilizaire…", @@ -568,17 +441,9 @@ "migration_0011_LDAP_update_failed": "Actualizacion impossibla de LDAP. Error : {error:s}", "migration_0011_migration_failed_trying_to_rollback": "La migracion a fracassat… ensag de tornar lo sistèma a l’estat anterio.", "migration_0011_rollback_success": "Restauracion del sistèma reüssida.", - "apps_permission_restoration_failed": "Fracàs de la permission « {permission:s} » per la restauracion de l’aplicacion {app:s}", - "group_already_allowed": "Lo grop « {group:s} » a ja la permission « {permission:s} » activada per l’aplicacion « {app:s} »", - "group_already_disallowed": "Lo grop « {group:s} »a ja las permissions « {permission:s} » desactivadas per l’aplicacion « {app:s} »", "group_updated": "Lo grop « {group} » es estat actualizat", "group_update_failed": "Actualizacion impossibla del grop « {group} » : {error}", - "log_permission_add": "Ajustar la permission « {} » per l’aplicacion « {} »", - "log_permission_remove": "Suprimir la permission « {} »", - "log_permission_update": "Actualizacion de la permission « {} » per l’aplicacion « {} »", "log_user_group_update": "Actualizar lo grop « {} »", - "log_user_permission_add": "Actualizar la permission « {} »", - "log_user_permission_remove": "Actualizar la permission « {} »", "migration_description_0011_setup_group_permission": "Configurar lo grop d’utilizaire e las permission de las aplicacions e dels servicis", "migration_0011_can_not_backup_before_migration": "La salvagarda del sistèma abans la migracion a pas capitat. La migracion a fracassat. Error : {error:s}", "migration_0011_migrate_permission": "Migracion de las permission dels paramètres d’aplicacion a LDAP…", @@ -590,22 +455,10 @@ "permission_deleted": "Permission « {permission:s} » suprimida", "permission_deletion_failed": "Fracàs de la supression de la permission « {permission:s} »", "permission_not_found": "Permission « {permission:s} » pas trobada", - "permission_name_not_valid": "Lo nom de la permission « {permission:s} » es pas valid", "permission_update_failed": "Fracàs de l’actualizacion de la permission", - "permission_generated": "La basa de donadas de las permission es estada actualizada", "permission_updated": "La permission « {permission:s} » es estada actualizada", "permission_update_nothing_to_do": "Cap de permission d’actualizar", - "remove_main_permission_not_allowed": "Se pòt pas suprimir la permission màger", - "remove_user_of_group_not_allowed": "Sètz pas autorizat a suprimir {user:s} del grop {group:s}", - "system_groupname_exists": "Lo nom del grop existís ja dins lo sistèma de grops", - "tools_update_failed_to_app_fetchlist": "Fracàs de l’actualizacion de la lista d’aplicacions de YunoHost a causa de : {error}", - "user_already_in_group": "L’utilizaire {user:} es ja dins lo grop {group:s}", - "user_not_in_group": "L’utilizaire {user:} es pas dins lo grop {group:s}", - "edit_permission_with_group_all_users_not_allowed": "Podètz pas modificar las permissions del grop « all_users », utilizatz « yunohost user permission clear APP » o « yunohost user permission add APP -u USER ».", "mailbox_disabled": "La bóstia de las letras es desactivada per l’utilizaire {user:s}", - "migration_0011_LDAP_config_dirty": "Sembla qu’avètz modificat manualament la configuracion LDAP. Per far aquesta migracion cal actualizar la configuracion LDAP.\nSalvagardatz la configuracion actuala, reïnicializatz la configuracion originala amb la comanda « yunohost tools regen-conf -f » e tornatz ensajar la migracion", - "need_define_permission_before": "Vos cal tornar definir las permission en utilizant « yunohost user permission add -u USER » abans de suprimir un grop permés", - "permission_already_clear": "La permission « {permission:s} » ja levada per l’aplicacion {app:s}", "migration_description_0012_postgresql_password_to_md5_authentication": "Forçar l’autentificacion PostgreSQL a utilizar MD5 per las connexions localas", "migrations_success_forward": "Migracion {id} corrèctament realizada !", "migrations_running_forward": "Execucion de la migracion {id}…", @@ -625,7 +478,6 @@ "diagnosis_description_regenconf": "Configuracion sistèma", "diagnosis_http_ok": "Lo domeni {domain} accessible de l’exterior.", "app_full_domain_unavailable": "Aquesta aplicacion a d’èsser installada sul seu pròpri domeni, mas i a d’autras aplicacions installadas sus aqueste domeni « {domain} ». Podètz utilizar allòc un josdomeni dedicat a aquesta aplicacion.", - "app_upgrade_stopped": "L’actualizacion de totas las aplicacions s‘es arrestada per evitar de possibles damatges pr’amor qu’èra pas possible d’actualizar una aplicacion", "diagnosis_dns_bad_conf": "Configuracion DNS incorrècta o inexistenta pel domeni {domain} (categoria {category})", "diagnosis_ram_verylow": "Lo sistèma a solament {available_abs_MB} Mo ({available_percent}%) de memòria RAM disponibla ! (d’un total de {total_abs_MB} MB)", "diagnosis_ram_ok": "Lo sistèma a encara {available_abs_MB} Mo ({available_percent}%) de memòria RAM disponibla d’un total de {total_abs_MB} MB).", @@ -658,13 +510,11 @@ "diagnosis_failed_for_category": "Lo diagnostic a reüssit per la categoria « {category} » : {error}", "diagnosis_cache_still_valid": "(Memòria cache totjorn valida pel diagnostic {category}. Cap d’autre diagnostic pel moment !)", "diagnosis_found_errors": "{errors} errors importantas trobadas ligadas a {category} !", - "diagnosis_services_good_status": "Lo servici {service} es {status} coma previst !", "diagnosis_services_bad_status": "Lo servici {service} es {status} :(", "diagnosis_swap_ok": "Lo sistèma a {total_MB} MB d’escambi !", "diagnosis_regenconf_allgood": "Totes los fichièrs de configuracion son confòrmes a la configuracion recomandada !", "diagnosis_regenconf_manually_modified": "Lo fichièr de configuracion {file} foguèt modificat manualament.", "diagnosis_regenconf_manually_modified_details": "Es probablament bon tan que sabètz çò que fasètz ;) !", - "diagnosis_regenconf_nginx_conf_broken": "La configuracion de nginx sembla èsser copada !", "diagnosis_security_vulnerable_to_meltdown": "Semblatz èsser vulnerable a la vulnerabilitat de seguretat critica de Meltdown", "diagnosis_description_basesystem": "Sistèma de basa", "diagnosis_description_ip": "Connectivitat Internet", @@ -672,7 +522,6 @@ "diagnosis_description_services": "Verificacion d’estat de servicis", "diagnosis_description_systemresources": "Resorgas sistèma", "diagnosis_description_ports": "Exposicion dels pòrts", - "diagnosis_description_http": "Exposicion HTTP", "diagnosis_description_security": "Verificacion de seguretat", "diagnosis_ports_unreachable": "Lo pòrt {port} es pas accessible de l’exterior.", "diagnosis_ports_ok": "Lo pòrt {port} es accessible de l’exterior.", @@ -685,7 +534,6 @@ "log_user_group_create": "Crear lo grop « {} »", "log_user_permission_update": "Actualizacion dels accèsses per la permission « {} »", "operation_interrupted": "L’operacion es estada interrompuda manualament ?", - "group_cannot_be_edited": "Lo grop « {group} » pòt pas èsser modificat manualament.", "group_cannot_be_deleted": "Lo grop « {group} » pòt pas èsser suprimit manualament.", "diagnosis_found_warnings": "Trobat {warnings} element(s) que se poirián melhorar per {category}.", "diagnosis_dns_missing_record": "Segon la configuracion DNS recomandada, vos calriá ajustar un enregistrament DNS de tipe {0}, nom {1} e valor {2}. Podètz consultar https://yunohost.org/dns_config per mai d’informacions.", @@ -723,4 +571,4 @@ "diagnosis_diskusage_ok": "Lo lòc d’emmagazinatge {mountpoint} (sul periferic {device}) a encara {free_abs_GB} Go ({free_percent}%) de liure !", "diagnosis_swap_none": "Lo sistèma a pas cap de memòria d’escambi. Auriatz de considerar d’ajustar almens 256 Mo d’escambi per evitar las situacions ont lo sistèma manca de memòria.", "diagnosis_swap_notsomuch": "Lo sistèma a solament {total_MB} de memòria d’escambi. Auriatz de considerar d’ajustar almens 256 Mo d’escambi per evitar las situacions ont lo sistèma manca de memòria." -} +} \ No newline at end of file diff --git a/locales/pl.json b/locales/pl.json index f1417a80c..7ff9fbcd2 100644 --- a/locales/pl.json +++ b/locales/pl.json @@ -1,3 +1,3 @@ { "password_too_simple_1": "Hasło musi mieć co najmniej 8 znaków" -} +} \ No newline at end of file diff --git a/locales/pt.json b/locales/pt.json index 417800fd5..bd5ba3bc6 100644 --- a/locales/pt.json +++ b/locales/pt.json @@ -7,48 +7,33 @@ "app_extraction_failed": "Não foi possível extrair os ficheiros para instalação", "app_id_invalid": "A ID da aplicação é inválida", "app_install_files_invalid": "Ficheiros para instalação corrompidos", - "app_location_already_used": "A aplicação {app} Já está instalada nesta localização ({path})", - "app_location_install_failed": "Não é possível instalar a aplicação neste diretório porque está em conflito com a aplicação '{other_app}', que já está instalada no diretório '{other_path}'", "app_manifest_invalid": "Manifesto da aplicação inválido: {error}", - "app_no_upgrade": "Não existem aplicações para atualizar", "app_not_installed": "{app:s} não está instalada", - "app_recent_version_required": "{:s} requer uma versão mais recente da moulinette", "app_removed": "{app:s} removida com êxito", "app_sources_fetch_failed": "Incapaz obter os ficheiros fonte", "app_unknown": "Aplicação desconhecida", "app_upgrade_failed": "Não foi possível atualizar {app:s}", "app_upgraded": "{app:s} atualizada com sucesso", - "appslist_fetched": "A lista de aplicações, {appslist:s}, foi trazida com sucesso", - "appslist_removed": "A Lista de aplicações {appslist:s} foi removida", - "appslist_retrieve_error": "Não foi possível obter a lista de aplicações remotas {appslist:s}: {error:s}", - "appslist_unknown": "Desconhece-se a lista de aplicaçoes {appslist:s}.", - "ask_current_admin_password": "Senha atual da administração", "ask_email": "Endereço de Email", "ask_firstname": "Primeiro nome", "ask_lastname": "Último nome", - "ask_list_to_remove": "Lista para remover", "ask_main_domain": "Domínio principal", "ask_new_admin_password": "Nova senha de administração", "ask_password": "Senha", "backup_created": "Backup completo", - "backup_creating_archive": "A criar ficheiro de backup...", "backup_invalid_archive": "Arquivo de backup inválido", "backup_output_directory_not_empty": "A pasta de destino não se encontra vazia", "custom_app_url_required": "Deve fornecer um link para atualizar a sua aplicação personalizada {app:s}", - "custom_appslist_name_required": "Deve fornecer um nome para a sua lista de aplicações personalizada", "domain_cert_gen_failed": "Não foi possível gerar o certificado", "domain_created": "Domínio criado com êxito", "domain_creation_failed": "Não foi possível criar o domínio", "domain_deleted": "Domínio removido com êxito", "domain_deletion_failed": "Não foi possível eliminar o domínio", "domain_dyndns_already_subscribed": "Já subscreveu um domínio DynDNS", - "domain_dyndns_invalid": "Domínio inválido para ser utilizado com DynDNS", "domain_dyndns_root_unknown": "Domínio root (administrador) DynDNS desconhecido", "domain_exists": "O domínio já existe", "domain_uninstall_app_first": "Existem uma ou mais aplicações instaladas neste domínio. Por favor desinstale-as antes de proceder com a remoção do domínio.", "domain_unknown": "Domínio desconhecido", - "domain_zone_exists": "Ficheiro para zona DMZ já existe", - "domain_zone_not_found": "Ficheiro para zona DMZ não encontrado no domínio {:s}", "done": "Concluído.", "downloading": "Transferência em curso...", "dyndns_cron_installed": "Gestor de tarefas cron DynDNS instalado com êxito", @@ -64,44 +49,22 @@ "extracting": "Extração em curso...", "field_invalid": "Campo inválido '{:s}'", "firewall_reloaded": "Firewall recarregada com êxito", - "hook_argument_missing": "Argumento em falta '{:s}'", - "hook_choice_invalid": "Escolha inválida '{:s}'", "installation_complete": "Instalação concluída", "installation_failed": "A instalação falhou", "iptables_unavailable": "Não pode alterar aqui a iptables. Ou o seu kernel não o suporta ou está num espaço reservado.", "ldap_initialized": "LDAP inicializada com êxito", - "license_undefined": "indefinido", "mail_alias_remove_failed": "Não foi possível remover a etiqueta de correio '{mail:s}'", "mail_domain_unknown": "Domínio de endereço de correio '{domain:s}' inválido. Por favor, usa um domínio administrado per esse servidor.", "mail_forward_remove_failed": "Não foi possível remover o reencaminhamento de correio '{mail:s}'", "main_domain_change_failed": "Incapaz alterar o domínio raiz", "main_domain_changed": "Domínio raiz alterado com êxito", - "monitor_disabled": "Monitorização do servidor parada com êxito", - "monitor_enabled": "Monitorização do servidor ativada com êxito", - "monitor_glances_con_failed": "Não foi possível ligar ao servidor Glances", - "monitor_not_enabled": "A monitorização do servidor não está ativa", - "monitor_period_invalid": "Período de tempo inválido", - "monitor_stats_file_not_found": "Ficheiro de estatísticas não encontrado", - "monitor_stats_no_update": "Não existem estatísticas de monitorização para atualizar", - "monitor_stats_period_unavailable": "Não existem estatísticas disponíveis para este período", - "mountpoint_unknown": "Ponto de montagem desconhecido", - "mysql_db_creation_failed": "Criação da base de dados MySQL falhou", - "mysql_db_init_failed": "Inicialização da base de dados MySQL falhou", - "mysql_db_initialized": "Base de dados MySQL iniciada com êxito", - "new_domain_required": "Deve escrever um novo domínio principal", - "no_appslist_found": "Não foi encontrada a lista de aplicações", "no_internet_connection": "O servidor não está ligado à Internet", - "packages_no_upgrade": "Não existem pacotes para atualizar", - "packages_upgrade_critical_later": "Os pacotes críticos ({packages:s}) serão atualizados depois", "packages_upgrade_failed": "Não foi possível atualizar todos os pacotes", - "path_removal_failed": "Incapaz remover o caminho {:s}", "pattern_domain": "Deve ser um nome de domínio válido (p.e. meu-dominio.org)", "pattern_email": "Deve ser um endereço de correio válido (p.e. alguem@dominio.org)", "pattern_firstname": "Deve ser um primeiro nome válido", "pattern_lastname": "Deve ser um último nome válido", - "pattern_listname": "Apenas são permitidos caracteres alfanuméricos e travessões", "pattern_password": "Deve ter no mínimo 3 caracteres", - "pattern_port": "Deve ser um número de porta válido (entre 0-65535)", "pattern_username": "Devem apenas ser carácteres minúsculos alfanuméricos e subtraços", "restore_confirm_yunohost_installed": "Quer mesmo restaurar um sistema já instalado? [{answers:s}]", "service_add_failed": "Incapaz adicionar serviço '{service:s}'", @@ -113,12 +76,10 @@ "service_disabled": "O serviço '{service:s}' foi desativado com êxito", "service_enable_failed": "Incapaz de ativar o serviço '{service:s}'", "service_enabled": "Serviço '{service:s}' ativado com êxito", - "service_no_log": "Não existem registos para mostrar do serviço '{service:s}'", "service_remove_failed": "Incapaz de remover o serviço '{service:s}'", "service_removed": "Serviço eliminado com êxito", "service_start_failed": "Não foi possível iniciar o serviço '{service:s}'", "service_started": "O serviço '{service:s}' foi iniciado com êxito", - "service_status_failed": "Incapaz determinar o estado do serviço '{service:s}'", "service_stop_failed": "Incapaz parar o serviço '{service:s}'", "service_stopped": "O serviço '{service:s}' foi parado com êxito", "service_unknown": "Serviço desconhecido '{service:s}'", @@ -127,8 +88,6 @@ "system_upgraded": "Sistema atualizado com êxito", "system_username_exists": "O utilizador já existe no registo do sistema", "unexpected_error": "Ocorreu um erro inesperado", - "unit_unknown": "Unidade desconhecida '{unit:s}'", - "update_cache_failed": "Não foi possível atualizar os cabeçalhos APT", "updating_apt_cache": "A atualizar a lista de pacotes disponíveis...", "upgrade_complete": "Atualização completa", "upgrading_packages": "Atualização de pacotes em curso...", @@ -136,7 +95,6 @@ "user_creation_failed": "Não foi possível criar o utilizador", "user_deleted": "Utilizador eliminado com êxito", "user_deletion_failed": "Incapaz eliminar o utilizador", - "user_info_failed": "Incapaz obter informações sobre o utilizador", "user_unknown": "Utilizador desconhecido", "user_update_failed": "Não foi possível atualizar o utilizador", "user_updated": "Utilizador atualizado com êxito", @@ -145,21 +103,18 @@ "yunohost_configured": "YunoHost configurada com êxito", "yunohost_installing": "A instalar a YunoHost...", "yunohost_not_installed": "YunoHost ainda não está corretamente configurado. Por favor execute as 'ferramentas pós-instalação yunohost'.", - "app_incompatible": "A aplicação {app} é incompatível com a sua versão de Yunohost", "app_not_correctly_installed": "{app:s} parece não estar corretamente instalada", "app_not_properly_removed": "{app:s} não foi corretamente removido", "app_requirements_checking": "Verificando os pacotes necessários para {app}...", "app_unsupported_remote_type": "A aplicação não possui suporte ao tipo remoto utilizado", "backup_archive_app_not_found": "A aplicação '{app:s}' não foi encontrada no arquivo de backup", "backup_archive_broken_link": "Impossível acessar o arquivo de backup (link quebrado ao {path:s})", - "backup_archive_hook_not_exec": "O gancho '{hook:s}' não foi executado neste backup", "backup_archive_name_exists": "O nome do arquivo de backup já existe", "backup_archive_open_failed": "Não é possível abrir o arquivo de backup", "backup_cleaning_failed": "Não é possível limpar a pasta temporária de backups", "backup_creation_failed": "A criação do backup falhou", "backup_delete_error": "Impossível apagar '{path:s}'", "backup_deleted": "O backup foi suprimido", - "backup_extracting_archive": "Extraindo arquivo de backup...", "backup_hook_unknown": "Gancho de backup '{hook:s}' desconhecido", "backup_nothings_done": "Não há nada para guardar", "backup_output_directory_forbidden": "Diretório de saída proibido. Os backups não podem ser criados em /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var ou /home/yunohost.backup/archives subpastas", @@ -169,24 +124,13 @@ "app_argument_invalid": "Valor inválido de argumento '{name:s}': {error:s}", "app_argument_required": "O argumento '{name:s}' é obrigatório", "app_change_url_failed_nginx_reload": "Falha ao reiniciar o nginx. Aqui está o retorno de 'nginx -t':\n{nginx_errors:s}", - "app_change_no_change_url_script": "A aplicação {app_name:s} ainda não permite mudança da URL, talvez seja necessário atualiza-la.", "app_location_unavailable": "Esta url não está disponível ou está em conflito com outra aplicação já instalada", - "app_package_need_update": "O pacote da aplicação {app} precisa ser atualizado para aderir as mudanças do YunoHost", - "app_requirements_failed": "Não foi possível atender aos requisitos da aplicação {app}: {error}", "app_upgrade_app_name": "Atualizando aplicação {app}…", "app_upgrade_some_app_failed": "Não foi possível atualizar algumas aplicações", - "appslist_corrupted_json": "Falha ao carregar a lista de aplicações. O arquivo {filename:s} aparenta estar corrompido.", - "appslist_migrating": "Migando lista de aplicações {appslist:s}…", - "appslist_name_already_tracked": "Já existe uma lista de aplicações registrada com o nome {name:s}.", - "appslist_retrieve_bad_format": "O arquivo recuperado para a lista de aplicações {appslist:s} é invalido", - "appslist_url_already_tracked": "Já existe uma lista de aplicações registrada com a url {url:s}.", - "ask_path": "Caminho", "backup_abstract_method": "Este metodo de backup ainda não foi implementado", - "backup_action_required": "Deve-se especificar algo a salvar", "backup_app_failed": "Não foi possível fazer o backup dos aplicativos '{app:s}'", "backup_applying_method_custom": "Chamando o metodo personalizado de backup '{method:s}'…", "backup_applying_method_tar": "Criando o arquivo tar de backup…", - "backup_archive_mount_failed": "Falha ao montar o arquivo de backup", "backup_archive_name_unknown": "Desconhece-se o arquivo local de backup de nome '{name:s}'", "backup_archive_system_part_not_available": "A seção do sistema '{part:s}' está indisponivel neste backup", "backup_ask_for_copying_if_needed": "Alguns arquivos não consiguiram ser preparados para backup utilizando o metodo que não gasta espaço de disco temporariamente. Para realizar o backup {size:s}MB precisam ser usados temporariamente. Você concorda?", @@ -196,4 +140,4 @@ "app_change_url_identical_domains": "O antigo e o novo domínio / url_path são idênticos ('{domain:s}{path:s}'), nada para fazer.", "password_too_simple_1": "A senha precisa ter pelo menos 8 caracteres", "admin_password_too_long": "Escolha uma senha que contenha menos de 127 caracteres" -} +} \ No newline at end of file diff --git a/locales/ru.json b/locales/ru.json index ed0a4c183..afe8e06f0 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -9,23 +9,17 @@ "app_argument_invalid": "Недопустимое значение аргумента '{name:s}': {error:s}'", "app_already_up_to_date": "{app:s} уже обновлено", "app_argument_required": "Аргумент '{name:s}' необходим", - "app_change_no_change_url_script": "Приложение {app_name:s} не поддерживает изменение URL, вы должны обновить его.", "app_change_url_identical_domains": "Старый и новый domain/url_path идентичны ('{domain:s}{path:s}'), ничего делать не надо.", "app_change_url_no_script": "Приложение '{app_name:s}' не поддерживает изменение url. Наверное, вам нужно обновить приложение.", "app_change_url_success": "Успешно изменён {app:s} url на {domain:s}{path:s}", "app_extraction_failed": "Невозможно извлечь файлы для инсталляции", "app_id_invalid": "Неправильный id приложения", - "app_incompatible": "Приложение {app} несовместимо с вашей версией YonoHost", "app_install_files_invalid": "Неправильные файлы инсталляции", - "app_location_already_used": "Приложение '{app}' уже установлено по этому адресу ({path})", - "app_location_install_failed": "Невозможно установить приложение в это место, потому что оно конфликтует с приложением, '{other_app}' установленном на '{other_path}'", "app_location_unavailable": "Этот url отсутствует или конфликтует с уже установленным приложением или приложениями: {apps:s}", "app_manifest_invalid": "Недопустимый манифест приложения: {error}", - "app_no_upgrade": "Нет приложений, требующих обновления", "app_not_correctly_installed": "{app:s} , кажется, установлены неправильно", "app_not_installed": "{app:s} не установлены", "app_not_properly_removed": "{app:s} удалены неправильно", - "app_package_need_update": "Пакет приложения {app} должен быть обновлён в соответствии с изменениями YonoHost", "app_removed": "{app:s} удалено", "app_requirements_checking": "Проверяю необходимые пакеты для {app}...", "app_sources_fetch_failed": "Невозможно получить исходные файлы", @@ -34,14 +28,6 @@ "app_upgrade_failed": "Невозможно обновить {app:s}", "app_upgrade_some_app_failed": "Невозможно обновить некоторые приложения", "app_upgraded": "{app:s} обновлено", - "appslist_corrupted_json": "Не могу загрузить список приложений. Кажется, {filename:s} поврежден.", - "appslist_fetched": "Был выбран список приложений {appslist:s}", - "appslist_name_already_tracked": "Уже есть зарегистрированный список приложений по имени {name:s}.", - "appslist_removed": "Список приложений {appslist:s} удалён", - "appslist_retrieve_bad_format": "Неверный файл списка приложений{appslist:s}", - "appslist_retrieve_error": "Невозможно получить список удаленных приложений {appslist:s}: {error:s}", - "appslist_unknown": "Список приложений {appslist:s} неизвестен.", - "appslist_url_already_tracked": "Это уже зарегистрированный список приложений с url{url:s}.", "installation_complete": "Установка завершена", "password_too_simple_1": "Пароль должен быть не менее 8 символов" -} +} \ No newline at end of file diff --git a/locales/sv.json b/locales/sv.json index 85572756d..26162419e 100644 --- a/locales/sv.json +++ b/locales/sv.json @@ -8,4 +8,4 @@ "action_invalid": "Ej tillåten åtgärd '{action:s}'", "admin_password_changed": "Administratörskontots lösenord ändrades", "aborting": "Avbryter." -} +} \ No newline at end of file diff --git a/locales/tr.json b/locales/tr.json index c6eb58ed1..6c881eec7 100644 --- a/locales/tr.json +++ b/locales/tr.json @@ -1,3 +1,3 @@ { "password_too_simple_1": "Şifre en az 8 karakter uzunluğunda olmalı" -} +} \ No newline at end of file diff --git a/locales/zh_Hans.json b/locales/zh_Hans.json index cb5d7002c..dee71a1d4 100644 --- a/locales/zh_Hans.json +++ b/locales/zh_Hans.json @@ -1,3 +1,3 @@ { "password_too_simple_1": "密码长度至少为8个字符" -} +} \ No newline at end of file diff --git a/tests/remove_stale_string.py b/tests/remove_stale_translated_strings.py similarity index 100% rename from tests/remove_stale_string.py rename to tests/remove_stale_translated_strings.py From c627580479ac4796646c6b6f1ef9543ed7211e59 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 30 Mar 2020 20:39:56 +0200 Subject: [PATCH 0783/3170] Convert previous check draft into a proper test for pytest/tox --- tests/check_locale_format_consistency.py | 40 ----------------- tests/test_translation_format_consistency.py | 45 ++++++++++++++++++++ 2 files changed, 45 insertions(+), 40 deletions(-) delete mode 100644 tests/check_locale_format_consistency.py create mode 100644 tests/test_translation_format_consistency.py diff --git a/tests/check_locale_format_consistency.py b/tests/check_locale_format_consistency.py deleted file mode 100644 index 99107ccc2..000000000 --- a/tests/check_locale_format_consistency.py +++ /dev/null @@ -1,40 +0,0 @@ -import re -import json -import glob - -# List all locale files (except en.json being the ref) -locale_folder = "../locales/" -locale_files = glob.glob(locale_folder + "*.json") -locale_files = [filename.split("/")[-1] for filename in locale_files] -locale_files.remove("en.json") - -reference = json.loads(open(locale_folder + "en.json").read()) - -found_inconsistencies = False - -# Let's iterate over each locale file -for locale_file in locale_files: - - this_locale = json.loads(open(locale_folder + locale_file).read()) - - # We iterate over all keys/string in en.json - for key, string in reference.items(): - # If there is a translation available for this key/string - if key in this_locale: - - # Then we check that every "{stuff}" (for python's .format()) - # should also be in the translated string, otherwise the .format - # will trigger an exception! - subkeys_in_ref = set(k[0] for k in re.findall(r"{(\w+)(:\w)?}", string)) - subkeys_in_this_locale = set(k[0] for k in re.findall(r"{(\w+)(:\w)?}", this_locale[key])) - - if any(key not in subkeys_in_ref for key in subkeys_in_this_locale): - found_inconsistencies = True - print("\n") - print("==========================") - print("Format inconsistency for string %s in %s:" % (key, locale_file)) - print("%s -> %s " % ("en.json", string)) - print("%s -> %s " % (locale_file, this_locale[key])) - -if found_inconsistencies: - sys.exit(1) diff --git a/tests/test_translation_format_consistency.py b/tests/test_translation_format_consistency.py new file mode 100644 index 000000000..81e98f3d5 --- /dev/null +++ b/tests/test_translation_format_consistency.py @@ -0,0 +1,45 @@ +import re +import json +import glob +import pytest + +# List all locale files (except en.json being the ref) +locale_folder = "locales/" +locale_files = glob.glob(locale_folder + "*.json") +locale_files = [filename.split("/")[-1] for filename in locale_files] +locale_files.remove("en.json") + +reference = json.loads(open(locale_folder + "en.json").read()) + + +def find_inconsistencies(locale_file): + + this_locale = json.loads(open(locale_folder + locale_file).read()) + + # We iterate over all keys/string in en.json + for key, string in reference.items(): + + # Ignore check if there's no translation yet for this key + if key not in this_locale: + continue + + # Then we check that every "{stuff}" (for python's .format()) + # should also be in the translated string, otherwise the .format + # will trigger an exception! + subkeys_in_ref = set(k[0] for k in re.findall(r"{(\w+)(:\w)?}", string)) + subkeys_in_this_locale = set(k[0] for k in re.findall(r"{(\w+)(:\w)?}", this_locale[key])) + + if any(k not in subkeys_in_ref for k in subkeys_in_this_locale): + yield """\n +========================== +Format inconsistency for string {key} in {locale_file}:" +en.json -> {string} +{locale_file} -> {translated_string} +""".format(key=key, string=string.encode("utf-8"), locale_file=locale_file, translated_string=this_locale[key].encode("utf-8")) + + +@pytest.mark.parametrize('locale_file', locale_files) +def test_translation_format_consistency(locale_file): + inconsistencies = list(find_inconsistencies(locale_file)) + if inconsistencies: + raise Exception(''.join(inconsistencies)) From 90459e7ae6a4af5d7a6c532e8d53ccef3a6e8c50 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Mon, 30 Mar 2020 21:32:29 +0200 Subject: [PATCH 0784/3170] Add legacy_args, fix the helper --- data/actionsmap/yunohost.yml | 2 -- data/helpers.d/setting | 18 ++++++++++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index c0eca3d03..b0bb7f9dc 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -303,8 +303,6 @@ user: arguments: permission: help: Name of the permission to fetch info about - extra: - pattern: *pattern_username ### user_permission_update() update: diff --git a/data/helpers.d/setting b/data/helpers.d/setting index 1c1139442..4782afd84 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -270,6 +270,8 @@ ynh_webpath_register () { # # Requires YunoHost version 3.7.0 or higher. ynh_permission_create() { + # Declare an array to define the options of this helper. + local legacy_args=pua declare -Ar args_array=( [p]=permission= [u]=url= [a]=allowed= ) local permission local url @@ -298,6 +300,8 @@ ynh_permission_create() { # # Requires YunoHost version 3.7.0 or higher. ynh_permission_delete() { + # Declare an array to define the options of this helper. + local legacy_args=p declare -Ar args_array=( [p]=permission= ) local permission ynh_handle_getopts_args "$@" @@ -312,6 +316,8 @@ ynh_permission_delete() { # # Requires YunoHost version 3.7.0 or higher. ynh_permission_exists() { + # Declare an array to define the options of this helper. + local legacy_args=p declare -Ar args_array=( [p]=permission= ) local permission ynh_handle_getopts_args "$@" @@ -327,6 +333,8 @@ ynh_permission_exists() { # # Requires YunoHost version 3.7.0 or higher. ynh_permission_url() { + # Declare an array to define the options of this helper. + local legacy_args=pu declare -Ar args_array=([p]=permission= [u]=url=) local permission local url @@ -352,6 +360,8 @@ ynh_permission_url() { # example: ynh_permission_update --permission admin --add samdoe --remove all_users # Requires YunoHost version 3.7.0 or higher. ynh_permission_update() { + # Declare an array to define the options of this helper. + local legacy_args=par declare -Ar args_array=( [p]=permission= [a]=add= [r]=remove= ) local permission local add @@ -376,13 +386,17 @@ ynh_permission_update() { # # Requires YunoHost version 3.7.1 or higher. ynh_permission_has_user() { + # Declare an array to define the options of this helper. + local legacy_args=pu declare -Ar args_array=( [p]=permission= [u]=user) local permission + local user ynh_handle_getopts_args "$@" - if ! ynh_permission_exists --permission $permission + if ! ynh_permission_exists --permission "$permission" + then return 1 fi - yunohost user permission info $permission | grep -w -q "$user" + yunohost user permission info "$app.$permission" | grep -w -q "$user" } \ No newline at end of file From 9dd6d799f4e241bf70a9efb737788795297d6068 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Mon, 30 Mar 2020 21:37:25 +0200 Subject: [PATCH 0785/3170] fix example --- data/helpers.d/setting | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index ec9404d5f..9466c5631 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -384,7 +384,7 @@ ynh_permission_update() { # | arg: -p, --permission - the permission to check # | arg: -u, --user - the user seek in the permission # -# example: ynh_permission_has_user --permission=nextcloud.main --user=visitors +# example: ynh_permission_has_user --permission=main --user=visitors # # Requires YunoHost version 3.7.1 or higher. ynh_permission_has_user() { From ec13f77852360b3f62656ee3ffd5ca7ebe99a6a0 Mon Sep 17 00:00:00 2001 From: romain raynaud Date: Mon, 30 Mar 2020 18:55:48 +0000 Subject: [PATCH 0786/3170] Translated using Weblate (Spanish) Currently translated at 94.6% (581 of 614 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/es/ --- locales/es.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/locales/es.json b/locales/es.json index 51daddf32..865254cf4 100644 --- a/locales/es.json +++ b/locales/es.json @@ -174,7 +174,7 @@ "restore_complete": "Restaurada", "restore_confirm_yunohost_installed": "¿Realmente desea restaurar un sistema ya instalado? [{answers:s}]", "restore_failed": "No se pudo restaurar el sistema", - "restore_hook_unavailable": "El guión de restauración para «{part:s}» no está disponible en su sistema y tampoco en el archivo", + "restore_hook_unavailable": "El script de restauración para «{part:s}» no está disponible en su sistema y tampoco en el archivo", "restore_nothings_done": "No se ha restaurado nada", "restore_running_app_script": "Restaurando la aplicación «{app:s}»…", "restore_running_hooks": "Ejecutando los ganchos de restauración…", @@ -377,13 +377,13 @@ "restore_removing_tmp_dir_failed": "No se pudo eliminar un directorio temporal antiguo", "restore_not_enough_disk_space": "Espacio insuficiente (espacio: {free_space:d} B, espacio necesario: {needed_space:d} B, margen de seguridad: {margin:d} B)", "restore_mounting_archive": "Montando archivo en «{path:s}»", - "restore_may_be_not_enough_disk_space": "Parece que su sistema no tiene suficiente espacio (libre: {free_space:d} B, espacio necesario: {needed_space:d} B, margen de seguridad: {margin:d} B)", + "restore_may_be_not_enough_disk_space": "Parece que su sistema no tiene suficiente espacio libre (libre: {free_space:d} B, espacio necesario: {needed_space:d} B, margen de seguridad: {margin:d} B)", "restore_extracting": "Extrayendo los archivos necesarios para el archivo…", "regenconf_pending_applying": "Aplicando la configuración pendiente para la categoría «{category}»…", "regenconf_failed": "No se pudo regenerar la configuración para la(s) categoría(s): {categories}", "regenconf_dry_pending_applying": "Comprobando la configuración pendiente que habría sido aplicada para la categoría «{category}»…", "regenconf_would_be_updated": "La configuración habría sido actualizada para la categoría «{category}»", - "regenconf_updated": "Actualizada la configuración para la categoría «{category}»", + "regenconf_updated": "Actualizada la configuración para la categoría '{category}'", "regenconf_up_to_date": "Ya está actualizada la configuración para la categoría «{category}»", "regenconf_now_managed_by_yunohost": "El archivo de configuración «{conf}» está gestionado ahora por YunoHost (categoría {category}).", "regenconf_file_updated": "Actualizado el archivo de configuración «{conf}»", @@ -400,7 +400,7 @@ "permission_update_nothing_to_do": "No hay permisos para actualizar", "permission_updated": "Actualizado el permiso «{permission:s}»", "permission_generated": "Actualizada la base de datos de permisos", - "permission_update_failed": "No se pudo actualizar el permiso «{permission}» : {error}", + "permission_update_failed": "No se pudo actualizar el permiso '{permission}': {error}", "permission_name_not_valid": "Elija un nombre de permiso permitido para «{permission:s}", "permission_not_found": "No se encontró el permiso «{permission:s}»", "permission_deletion_failed": "No se pudo eliminar el permiso «{permission}»: {error}", @@ -441,7 +441,7 @@ "migration_0011_can_not_backup_before_migration": "El respaldo del sistema no se pudo completar antes de que la migración fallase. Error: {error:s}", "migration_0011_backup_before_migration": "Creando un respaldo de la base de datos de LDAP y de la configuración de las aplicaciones antes de la migración real.", "migration_0009_not_needed": "La migración ya ocurrió de algún modo… (?) Omitiendo.", - "migration_0008_no_warning": "Ignorar su configuración SSH debería ser seguro ¡aunque esto no se puede prometer! Ejecute la migración para ignorarla. Por otra parte puede omitir la migración, aunque no se recomienda.", + "migration_0008_no_warning": "Sobre escribir su configuración SSH debería ser seguro ¡aunque esto no se puede prometer! Ejecute la migración para ignorarla. Por otra parte puede omitir la migración, aunque no se recomienda.", "migration_0008_warning": "Si entiende esos avisos y quiere que YunoHost ignore su configuración actual, ejecute la migración. Por otra parte puede omitir la migración, aunque no se recomienda.", "migration_0008_dsa": "• Se desactivará la clave DSA. Así que podría tener que anular un aviso espeluznante de su cliente SSH y volver a comprobar la huella de su servidor;", "migration_0008_root": "• No podrá conectarse como «root» a través de SSH. En su lugar debe usar el usuario «admin»;", @@ -451,7 +451,7 @@ "migration_0007_cancelled": "No se pudo mejorar el modo en el que se gestiona su configuración de SSH.", "migration_0006_disclaimer": "YunoHost espera ahora que las contraseñas de «admin» y «root» estén sincronizadas. Esta migración reemplaza su contraseña de «root» por la contraseña de «admin».", "migration_0005_not_enough_space": "Tenga suficiente espacio libre disponible en {path} para ejecutar la migración.", - "migration_0005_postgresql_96_not_installed": "⸘PostgreSQL 9.4 está instalado pero no PostgreSQL 9.6‽ Algo raro podría haber ocurrido en su sistema:(…", + "migration_0005_postgresql_96_not_installed": "PostgreSQL 9.4 está instalado pero no PostgreSQL 9.6. Algo raro podría haber ocurrido en su sistema:(…", "migration_0005_postgresql_94_not_installed": "PostgreSQL no estaba instalado en su sistema. Nada que hacer.", "migration_0003_modified_files": "Tenga en cuenta que se encontró que los siguientes archivos fueron modificados manualmente y podrían ser sobrescritos después de la actualización: {manually_modified_files}", "migration_0003_problematic_apps_warning": "Tenga en cuenta que las aplicaciones listadas mas abajo fueron detectadas como 'posiblemente problemáticas'. Parece que no fueron instaladas desde una lista de aplicaciones o no estaban etiquetadas como 'funcional'. Así que no hay garantía de que aún funcionen después de la actualización: {problematic_apps}", From 55e34aa5ae26b544ba78e3dc72fa2fa1a20aac10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Tue, 31 Mar 2020 22:59:00 +0200 Subject: [PATCH 0787/3170] Depreciate app_change_label --- locales/en.json | 1 + src/yunohost/app.py | 7 ++----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/locales/en.json b/locales/en.json index 885f378f1..713f1ee41 100644 --- a/locales/en.json +++ b/locales/en.json @@ -27,6 +27,7 @@ "app_install_failed": "Could not install {app}: {error}", "app_install_script_failed": "An error occurred inside the app installation script", "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_label_depreciated": "This command is depreciated !! Please use the new command 'yunohost user permission update' to manage the app label.", "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_not_upgraded": "The app '{failed_app}' failed to upgrade, and as a consequence the following apps' upgrades have been cancelled: {apps}", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index a0bc87ddb..8c7c1975d 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -724,7 +724,6 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu # Set initial app settings app_settings = { 'id': app_instance_name, - 'label': label, 'install_time': int(time.time()), 'current_revision': manifest.get('remote', {}).get('revision', "?") } @@ -1356,10 +1355,8 @@ def app_change_label(app, new_label): installed = _is_installed(app) if not installed: raise YunohostError('app_not_installed', app=app, all_apps=_get_all_installed_apps_id()) - - app_setting(app, "label", value=new_label) - - app_ssowatconf() + logger.warning(m18n.n('app_label_depreciated')) + user_permission_update(app + ".main", label=new_label) # actions todo list: From c6b79a55d214a449453a8d4302b2103efaaa88a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Tue, 31 Mar 2020 23:11:12 +0200 Subject: [PATCH 0788/3170] Remove code duplication --- src/yunohost/permission.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 6876b7fdf..07f926523 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -111,7 +111,6 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, existing_permission = user_permission_list(full=True)["permissions"].get(permission, None) # Refuse to add "visitors" to mail, xmpp ... they require an account to make sense. - existing_permission = user_permission_list(full=True)["permissions"].get(permission, None) if add and "visitors" in add and permission.split(".")[0] in SYSTEM_PERMS: raise YunohostError('permission_require_account', permission=permission) From 98f77e78c9851d8859ec19afa26dbe78d3ff22ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Tue, 31 Mar 2020 23:03:46 +0200 Subject: [PATCH 0789/3170] Add full_path args for permission_list and enable it by default --- src/yunohost/app.py | 6 ++--- src/yunohost/backup.py | 4 +-- src/yunohost/permission.py | 31 +++++++++++++++++------- src/yunohost/tests/test_backuprestore.py | 4 +-- src/yunohost/tests/test_permission.py | 6 ++--- src/yunohost/user.py | 2 +- 6 files changed, 33 insertions(+), 20 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 8c7c1975d..1d4ab4101 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -188,7 +188,7 @@ def app_map(app=None, raw=False, user=None): apps = [] result = {} - permissions = user_permission_list(full=True)["permissions"] + permissions = user_permission_list(full=True, full_path=False)["permissions"] if app is not None: if not _is_installed(app): @@ -483,7 +483,7 @@ def app_upgrade(app=[], url=None, file=None): 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_LABEL"] = user_permission_list(full=True, ignore_system_perms=True)['permissions'][app_id+".main"]['label'] + env_dict["YNH_APP_LABEL"] = user_permission_list(full=True, ignore_system_perms=True, full_path=False)['permissions'][app_id+".main"]['label'] # Start register change on system related_to = [('app', app_instance_name)] @@ -1221,7 +1221,7 @@ def app_ssowatconf(): main_domain = _get_maindomain() domains = domain_list()['domains'] - all_permissions = user_permission_list(full=True)['permissions'] + all_permissions = user_permission_list(full=True, full_path=False)['permissions'] skipped_urls = [] skipped_regex = [] diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 9a4e3b860..f8a2f54ba 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -705,7 +705,7 @@ class BackupManager(): # backup permissions logger.debug(m18n.n('backup_permission', app=app)) - permissions = user_permission_list(full=True)["permissions"] + permissions = user_permission_list(full=True, full_path=False)["permissions"] this_app_permissions = {name: infos for name, infos in permissions.items() if name.startswith(app + ".")} write_to_yaml("%s/permissions.yml" % settings_dir, this_app_permissions) @@ -1189,7 +1189,7 @@ class RestoreManager(): # Backup old permission for apps # We need to do that because in case of an app is installed we can't remove the permission for this app - old_apps_permission = user_permission_list(ignore_system_perms=True, full=True)["permissions"] + old_apps_permission = user_permission_list(ignore_system_perms=True, full=True, full_path=False)["permissions"] # Start register change on system operation_logger = OperationLogger('backup_restore_system') diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 07f926523..b57534079 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -45,13 +45,13 @@ SYSTEM_PERMS = ["mail", "xmpp", "stfp"] # -def user_permission_list(short=False, full=False, ignore_system_perms=False): +def user_permission_list(short=False, full=False, ignore_system_perms=False, full_path=True): """ List permissions and corresponding accesses """ # Fetch relevant informations - + from yunohost.app import app_setting, app_list from yunohost.utils.ldap import _get_ldap_interface, _ldap_path_extract ldap = _get_ldap_interface() permissions_infos = ldap.search('ou=permission,dc=yunohost,dc=org', @@ -60,6 +60,15 @@ def user_permission_list(short=False, full=False, ignore_system_perms=False): 'URL', 'additionalUrls', 'authHeader', 'label', 'showTile', 'isProtected']) # Parse / organize information to be outputed + app_settings = {app['id']: app_setting(app['id'], 'domain') + app_setting(app['id'], 'path') for app in app_list()['apps']} + + def complete_url(url, name): + if url is None: + return None + if url.startswith('/'): + return app_settings[name.split('.')[0]] + url + else: + return url permissions = {} for infos in permissions_infos: @@ -74,12 +83,16 @@ def user_permission_list(short=False, full=False, ignore_system_perms=False): if full: permissions[name]["corresponding_users"] = [_ldap_path_extract(p, "uid") for p in infos.get('inheritPermission', [])] - permissions[name]["url"] = infos.get("URL", [None])[0] - permissions[name]["additional_urls"] = infos.get("additionalUrls", [None]) permissions[name]["auth_header"] = False if infos.get("authHeader", [False])[0] == "FALSE" else True permissions[name]["label"] = infos.get("label", [None])[0] permissions[name]["show_tile"] = False if infos.get("showTile", [False])[0] == "FALSE" else True permissions[name]["protected"] = False if infos.get("isProtected", [False])[0] == "FALSE" else True + if full_path and name.split(".")[0] not in SYSTEM_PERMS: + permissions[name]["url"] = complete_url(infos.get("URL", [None])[0], name) + permissions[name]["additional_urls"] = [complete_url(url, name) for url in infos.get("additionalUrls", [None])] + else: + permissions[name]["url"] = infos.get("URL", [None])[0] + permissions[name]["additional_urls"] = infos.get("additionalUrls", [None]) if short: permissions = permissions.keys() @@ -108,7 +121,7 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, if "." not in permission: permission = permission + ".main" - existing_permission = user_permission_list(full=True)["permissions"].get(permission, None) + existing_permission = user_permission_list(full=True, full_path=False)["permissions"].get(permission, None) # Refuse to add "visitors" to mail, xmpp ... they require an account to make sense. if add and "visitors" in add and permission.split(".")[0] in SYSTEM_PERMS: @@ -189,7 +202,7 @@ def user_permission_reset(operation_logger, permission, sync_perm=True): # Fetch existing permission - existing_permission = user_permission_list(full=True)["permissions"].get(permission, None) + existing_permission = user_permission_list(full=True, full_path=False)["permissions"].get(permission, None) if existing_permission is None: raise YunohostError('permission_not_found', permission=permission) @@ -331,7 +344,7 @@ def permission_url(operation_logger, permission, # Fetch existing permission - existing_permission = user_permission_list(full=True)["permissions"].get(permission, None) + existing_permission = user_permission_list(full=True, full_path=False)["permissions"].get(permission, None) if not existing_permission: raise YunohostError('permission_not_found', permission=permission) @@ -438,7 +451,7 @@ def permission_sync_to_user(): ldap = _get_ldap_interface() groups = user_group_list(full=True)["groups"] - permissions = user_permission_list(full=True)["permissions"] + permissions = user_permission_list(full=True, full_path=False)["permissions"] for permission_name, permission_infos in permissions.items(): @@ -498,7 +511,7 @@ def _update_ldap_group_permission(permission, allowed, ldap = _get_ldap_interface() # Fetch currently allowed groups for this permission - existing_permission = user_permission_list(full=True)["permissions"][permission] + existing_permission = user_permission_list(full=True, full_path=False)["permissions"][permission] if allowed is None: allowed = existing_permission['allowed'] diff --git a/src/yunohost/tests/test_backuprestore.py b/src/yunohost/tests/test_backuprestore.py index bcba21bb6..c097e208f 100644 --- a/src/yunohost/tests/test_backuprestore.py +++ b/src/yunohost/tests/test_backuprestore.py @@ -502,7 +502,7 @@ def test_backup_and_restore_with_ynh_restore(mocker): @pytest.mark.with_permission_app_installed def test_backup_and_restore_permission_app(mocker): - res = user_permission_list(full=True)['permissions'] + res = user_permission_list(full=True, full_path=False)['permissions'] assert "permissions_app.main" in res assert "permissions_app.admin" in res assert "permissions_app.dev" in res @@ -517,7 +517,7 @@ def test_backup_and_restore_permission_app(mocker): _test_backup_and_restore_app(mocker, "permissions_app") - res = user_permission_list(full=True)['permissions'] + res = user_permission_list(full=True, full_path=False)['permissions'] assert "permissions_app.main" in res assert "permissions_app.admin" in res assert "permissions_app.dev" in res diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index 1a9eaee68..bf8ab61ab 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -442,7 +442,7 @@ def test_permission_app_install(): app_install("./tests/apps/permissions_app_ynh", args="domain=%s&path=%s&is_public=0&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True) - res = user_permission_list(full=True)['permissions'] + res = user_permission_list(full=True, full_path=False)['permissions'] assert "permissions_app.main" in res assert "permissions_app.admin" in res assert "permissions_app.dev" in res @@ -481,14 +481,14 @@ def test_permission_app_change_url(): args="domain=%s&path=%s&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True) # FIXME : should rework this test to look for differences in the generated app map / app tiles ... - res = user_permission_list(full=True)['permissions'] + res = user_permission_list(full=True, full_path=False)['permissions'] assert res['permissions_app.main']['url'] == "/" assert res['permissions_app.admin']['url'] == "/admin" assert res['permissions_app.dev']['url'] == "/dev" app_change_url("permissions_app", maindomain, "/newchangeurl") - res = user_permission_list(full=True)['permissions'] + res = user_permission_list(full=True, full_path=False)['permissions'] assert res['permissions_app.main']['url'] == "/" assert res['permissions_app.admin']['url'] == "/admin" assert res['permissions_app.dev']['url'] == "/dev" diff --git a/src/yunohost/user.py b/src/yunohost/user.py index ff31bbb62..739fbcb02 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -462,7 +462,7 @@ def user_info(username): if service_status("dovecot")["status"] != "running": logger.warning(m18n.n('mailbox_used_space_dovecot_down')) - elif username not in user_permission_list(full=True)["permissions"]["mail.main"]["corresponding_users"]: + elif username not in user_permission_list(full=True, full_path=False)["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] From 97e83337d4eec37c178a4254bcd02ca106c4f92f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 31 Mar 2020 03:10:30 +0200 Subject: [PATCH 0790/3170] Update network --- data/helpers.d/network | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/network b/data/helpers.d/network index 5d20b22ac..2dff82108 100644 --- a/data/helpers.d/network +++ b/data/helpers.d/network @@ -17,7 +17,7 @@ ynh_find_port () { ynh_handle_getopts_args "$@" test -n "$port" || ynh_die --message="The argument of ynh_find_port must be a valid port." - while ss -nltu | grep -q -w :$port # Check if the port is free + while ss -nltu | grep -q -w "*:$port" # Check if the port is free do port=$((port+1)) # Else, pass to next port done From be303f3e443d3352118f1e221fb65ce9efc2d062 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 1 Apr 2020 02:20:02 +0200 Subject: [PATCH 0791/3170] Fix ynh_find_port grep Co-Authored-By: Kayou --- data/helpers.d/network | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/network b/data/helpers.d/network index 2dff82108..330aa5383 100644 --- a/data/helpers.d/network +++ b/data/helpers.d/network @@ -17,7 +17,7 @@ ynh_find_port () { ynh_handle_getopts_args "$@" test -n "$port" || ynh_die --message="The argument of ynh_find_port must be a valid port." - while ss -nltu | grep -q -w "*:$port" # Check if the port is free + while ss -nltu | awk '{print$5}' | grep -q -E ":$port$" # Check if the port is free do port=$((port+1)) # Else, pass to next port done From eaaa6be6e80d4aa00b29d0d218b3530fa816babc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9lanie=20Chauvel?= Date: Mon, 30 Mar 2020 23:40:56 +0000 Subject: [PATCH 0792/3170] Translated using Weblate (French) Currently translated at 100.0% (614 of 614 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index adeeada3b..3b387876f 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -513,7 +513,7 @@ "migration_0008_warning": "Si vous comprenez ces avertissements et souhaitez que YunoHost écrase votre configuration actuelle, exécutez la migration. Sinon, vous pouvez également ignorer la migration, bien que cela ne soit pas recommandé.", "migration_0008_no_warning": "Remplacer votre configuration SSH devrait être sûr, bien que cela ne puisse être promis! Exécutez la migration pour la remplacer. Sinon, vous pouvez également ignorer la migration, bien que cela ne soit pas recommandé.", "migrations_success": "Migration {number} {name} réussie !", - "pattern_password_app": "Désolé, les mots de passe ne doivent pas contenir les caractères suivants : {forbidden_chars}", + "pattern_password_app": "Désolé, les mots de passe ne peuvent pas contenir les caractères suivants : {forbidden_chars}", "root_password_replaced_by_admin_password": "Votre mot de passe root a été remplacé par votre mot de passe administrateur.", "service_conf_now_managed_by_yunohost": "Le fichier de configuration '{conf}' est maintenant géré par YunoHost.", "service_reload_failed": "Impossible de recharger le service '{service:s}'.\n\nJournaux historisés récents de ce service : {logs:s}", @@ -764,5 +764,8 @@ "diagnosis_never_ran_yet": "Il apparaît que le serveur a été installé récemment et qu'il n'y a pas encore eu de diagnostic. Vous devriez en lancer un depuis le webmin ou en utilisant 'yunohost diagnosis run' depuis la ligne de commande.", "diagnosis_description_web": "Web", "diagnosis_basesystem_hardware_board": "Le modèle de carte du serveur est {model}", - "diagnosis_basesystem_hardware": "L'architecture du serveur est {virt} {arch}" + "diagnosis_basesystem_hardware": "L'architecture du serveur est {virt} {arch}", + "group_already_exist_on_system_but_removing_it": "Le groupe {group} est déjà présent dans les groupes du système, mais Yuhonost va le supprimer…", + "certmanager_warning_subdomain_dns_record": "Le sous-domaine '{subdomain:s}' ne résout pas vers la même adresse IP que '{domain:s}'. Certaines fonctionnalités seront indisponibles tant que vous n’aurez pas corrigé cela et regénéré le certificat.", + "domain_cannot_add_xmpp_upload": "Vous ne pouvez pas ajouter de domaine commençant par 'xmpp-upload.'. Ce type de nom est réservé à la fonctionnalité d’upload XMPP intégrée dans Yunohost." } From b0e67460dff0a40713a3c80d6eee91d4faa5ee7e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 1 Apr 2020 17:24:08 +0200 Subject: [PATCH 0793/3170] Add conflict rule against apache2 and bind9 --- debian/control | 1 + 1 file changed, 1 insertion(+) diff --git a/debian/control b/debian/control index 42bafc16c..aed123246 100644 --- a/debian/control +++ b/debian/control @@ -43,6 +43,7 @@ Conflicts: iptables-persistent , yunohost-config-dovecot, yunohost-config-slapd , yunohost-config-nginx, yunohost-config-amavis , yunohost-config-mysql, yunohost-predepends + , apache2, bind9 Replaces: moulinette-yunohost, yunohost-config , yunohost-config-others, yunohost-config-postfix , yunohost-config-dovecot, yunohost-config-slapd From 4303653d7752138a2fc948d206884ea07802f895 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 1 Apr 2020 20:42:19 +0200 Subject: [PATCH 0794/3170] Check url validity for permissions --- locales/en.json | 2 ++ src/yunohost/domain.py | 65 ++++++++++++++++++++++++++++++++++++++ src/yunohost/permission.py | 13 +++++--- 3 files changed, 75 insertions(+), 5 deletions(-) diff --git a/locales/en.json b/locales/en.json index 713f1ee41..605d7633e 100644 --- a/locales/en.json +++ b/locales/en.json @@ -309,6 +309,7 @@ "hook_name_unknown": "Unknown hook name '{name:s}'", "installation_complete": "Installation completed", "installation_failed": "Something went wrong with the installation", + "invalid_regex": "Invalid regex :'{regex:s}'", "ip6tables_unavailable": "You cannot play with ip6tables here. You are either in a container or your kernel does not support it", "iptables_unavailable": "You cannot play with iptables here. You are either in a container or your kernel does not support it", "log_corrupted_md_file": "The YAML metadata file associated with logs is damaged: '{md_file}\nError: {error}'", @@ -498,6 +499,7 @@ "regenconf_dry_pending_applying": "Checking pending configuration which would have been applied for category '{category}'…", "regenconf_failed": "Could not regenerate the configuration for category(s): {categories}", "regenconf_pending_applying": "Applying pending configuration for category '{category}'…", + "regex_with_only_domain": "You can't use a regex for domain, only for path", "restore_already_installed_app": "An app with the ID '{app:s}' is already installed", "restore_app_failed": "Could not restore the app '{app:s}'", "restore_cleaning_failed": "Could not clean up the temporary restoration directory", diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 456dfa4bf..d1a621615 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -396,6 +396,71 @@ def _normalize_domain_path(domain, path): return domain, path +def _check_and_normalize_permission_path(url): + """ + Check and normalize the urls passed for all permissions + Also check that the Regex is valid + + As documented in the 'ynh_permission_create' helper: + + If provided, 'url' is assumed to be relative to the app domain/path if they + start with '/'. For example: + / -> domain.tld/app + /admin -> domain.tld/app/admin + domain.tld/app/api -> domain.tld/app/api + domain.tld -> domain.tld + + 'url' can be later treated as a regex if it starts with "re:". + For example: + re:/api/[A-Z]*$ -> domain.tld/app/api/[A-Z]*$ + re:domain.tld/app/api/[A-Z]*$ -> domain.tld/app/api/[A-Z]*$ + """ + import re, sre_constants + + # Uri without domain + if url.startwith('re:/'): + regex = url[4:] + # check regex + try: + re.compile(regex) + except sre_constants.error: + raise YunohostError('invalid_regex', regex=regex) + return url + + if url.startswith('/'): + return "/" + url.strip("/") + + # Uri with domain + domains = domain_list()['domains'] + + if url.startwith('re:'): + if '/' not in url: + raise YunohostError('regex_with_only_domain') + domain = url[3:].split('/')[0] + path = url[3:].split('/', 1)[1] + + if domain not in domains: + raise YunohostError('domain_unknown') + + try: + re.compile(path) + except sre_constants.error: + raise YunohostError('invalid_regex', regex=path) + + return 're:' + domain + path + + else: + domain = url.split('/')[0] + if domain not in domains: + raise YunohostError('domain_unknown') + + if '/' in url: + path = '/' + url.split('/', 1)[1].strip('/') + return domain + path + else: + return domain + + def _build_dns_conf(domain, ttl=3600): """ Internal function that will returns a data structure containing the needed diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index b57534079..dace552f9 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -336,6 +336,7 @@ def permission_url(operation_logger, permission, clear_urls -- (optional) Clean all urls (url and additional_urls) """ from yunohost.utils.ldap import _get_ldap_interface + from yunohost.domain import _check_and_normalize_permission_path ldap = _get_ldap_interface() # By default, manipulate main permission @@ -352,20 +353,22 @@ def permission_url(operation_logger, permission, if url is None: url = existing_permission["url"] + else: + url = _check_and_normalize_permission_path(url) current_additional_urls = existing_permission["additional_urls"] new_additional_urls = copy.copy(current_additional_urls) if add_url: - for url in add_url: - if url in current_additional_urls: + for ur in add_url: + if ur in current_additional_urls: logger.warning(m18n.n('additional_urls_already_added', permission=permission, url=url)) else: - new_additional_urls += [url] + new_additional_urls += [_check_and_normalize_permission_path(url)] if remove_url: - for url in remove_url: - if url not in current_additional_urls: + for ur in remove_url: + if ur not in current_additional_urls: logger.warning(m18n.n('additional_urls_already_removed', permission=permission, url=url)) new_additional_urls = [u for u in new_additional_urls if u not in remove_url] From d95341840b68f26215b9b059726bd3adca814908 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 1 Apr 2020 20:58:50 +0200 Subject: [PATCH 0795/3170] Rename default label name --- data/helpers.d/setting | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index 5e5bce6c3..f37c674a3 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -260,7 +260,7 @@ ynh_webpath_register () { # | arg: additional_urls - (optional) List of additional URL for which access will be allowed/forbidden # | arg: auth_header - (optional) Define for the URL of this permission, if SSOwat pass the authentication header to the application. Default is true # | arg: allowed - (optional) A list of group/user to allow for the permission -# | arg: label - (optional) Define a name for the permission. This label will be shown on the SSO and in the admin. Default is "APP_LABEL permission name". +# | arg: label - (optional) Define a name for the permission. This label will be shown on the SSO and in the admin. Default is "APP_LABEL (permission name)". # | arg: show_tile - (optional) Define if a tile will be shown in the SSO # | arg: protected - (optional) Define if this permission is protected. If it is protected the administrator # | won't be able to add or remove the visitors group of this permission. @@ -313,7 +313,7 @@ ynh_permission_create() { if [[ -n ${label:-} ]]; then label=",label='$label'" else - label=",label='$YNH_APP_LABEL $permission'" + label=",label='$YNH_APP_LABEL ($permission)'" fi if [[ -n ${show_tile:-} ]]; then From 2a0866b583f7b41fadbd5a729d1a39407316be9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 1 Apr 2020 22:02:45 +0200 Subject: [PATCH 0796/3170] Improve check url validity for permissions --- src/yunohost/domain.py | 4 ++-- src/yunohost/permission.py | 10 ++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index d1a621615..dfa8f4919 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -428,7 +428,7 @@ def _check_and_normalize_permission_path(url): return url if url.startswith('/'): - return "/" + url.strip("/") + return url.rstrip("/") # Uri with domain domains = domain_list()['domains'] @@ -455,7 +455,7 @@ def _check_and_normalize_permission_path(url): raise YunohostError('domain_unknown') if '/' in url: - path = '/' + url.split('/', 1)[1].strip('/') + path = url.split('/', 1)[1].rstrip('/') return domain + path else: return domain diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index dace552f9..52f0dc22e 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -62,11 +62,13 @@ def user_permission_list(short=False, full=False, ignore_system_perms=False, ful # Parse / organize information to be outputed app_settings = {app['id']: app_setting(app['id'], 'domain') + app_setting(app['id'], 'path') for app in app_list()['apps']} - def complete_url(url, name): + def _complete_url(url, name): if url is None: return None if url.startswith('/'): - return app_settings[name.split('.')[0]] + url + return app_settings[name.split('.')[0]] + url.rstrip("/") + if url.startswith('re:/'): + return 're:' + app_settings[name.split('.')[0]] + url.lstrip('re:/') else: return url @@ -88,8 +90,8 @@ def user_permission_list(short=False, full=False, ignore_system_perms=False, ful permissions[name]["show_tile"] = False if infos.get("showTile", [False])[0] == "FALSE" else True permissions[name]["protected"] = False if infos.get("isProtected", [False])[0] == "FALSE" else True if full_path and name.split(".")[0] not in SYSTEM_PERMS: - permissions[name]["url"] = complete_url(infos.get("URL", [None])[0], name) - permissions[name]["additional_urls"] = [complete_url(url, name) for url in infos.get("additionalUrls", [None])] + permissions[name]["url"] = _complete_url(infos.get("URL", [None])[0], name) + permissions[name]["additional_urls"] = [_complete_url(url, name) for url in infos.get("additionalUrls", [None])] else: permissions[name]["url"] = infos.get("URL", [None])[0] permissions[name]["additional_urls"] = infos.get("additionalUrls", [None]) From e724dbccf06967454ba04237e602aa52dc50b64c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 1 Apr 2020 22:04:00 +0200 Subject: [PATCH 0797/3170] Rework app_map --- src/yunohost/app.py | 81 +++++++++++--------------------------- src/yunohost/permission.py | 1 + 2 files changed, 24 insertions(+), 58 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 1d4ab4101..d3ca2eb82 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -173,7 +173,7 @@ def _app_upgradable(app_infos): return "no" -def app_map(app=None, raw=False, user=None): +def app_map(app=None, raw=False, user=None, permission=None): """ List apps by domain @@ -188,7 +188,7 @@ def app_map(app=None, raw=False, user=None): apps = [] result = {} - permissions = user_permission_list(full=True, full_path=False)["permissions"] + permissions = user_permission_list(full=True, full_path=True)["permissions"] if app is not None: if not _is_installed(app): @@ -219,72 +219,37 @@ def app_map(app=None, raw=False, user=None): if user not in main_perm["corresponding_users"]: continue - domain = app_settings['domain'] - path = app_settings['path'].rstrip('/') - label = app_settings['label'] - - def _sanitized_absolute_url(perm_url): - # Nominal case : url is relative to the app's path - if perm_url.startswith("/"): - perm_domain = domain - perm_path = path + perm_url.rstrip("/") - # Otherwise, the urls starts with a domain name, like domain.tld/foo/bar - # We want perm_domain = domain.tld and perm_path = "/foo/bar" - else: - perm_domain, perm_path = perm_url.split("/", 1) - perm_path = "/" + perm_path.rstrip("/") - - return perm_domain, perm_path - this_app_perms = {p: i for p, i in permissions.items() if p.startswith(app_id + ".") and i["url"]} for perm_name, perm_info in this_app_perms.items(): # If we're building the map for a specific user, check the user # actually is allowed for this specific perm if user and user not in perm_info["corresponding_users"]: continue - if perm_info["url"].startswith("re:"): - # Here, we have an issue if the chosen url is a regex, because - # the url we want to add to the dict is going to be turned into - # a clickable link (or analyzed by other parts of yunohost - # code...). To put it otherwise : in the current code of ssowat, - # you can't give access a user to a regex. - # - # Instead, as drafted by Josue, we could rework the ssowat logic - # about how routes and their permissions are defined. So for example, - # have a dict of - # { "/route1": ["visitors", "user1", "user2", ...], # Public route - # "/route2_with_a_regex$": ["user1", "user2"], # Private route - # "/route3": None, # Skipped route idk - # } - # then each time a user try to request and url, we only keep the - # longest matching rule and check the user is allowed etc... - # - # The challenge with this is (beside actually implementing it) - # is that it creates a whole new mechanism that ultimately - # replace all the existing logic about - # protected/unprotected/skipped uris and regexes and we gotta - # handle / migrate all the legacy stuff somehow if we don't - # want to end up with a total mess in the future idk - logger.error("Permission %s can't be added to the SSOwat configuration because it doesn't support regexes so far..." % perm_name) + if permission == perm_name: continue - perm_domain, perm_path = _sanitized_absolute_url(perm_info["url"]) + # The challenge with this is (beside actually implementing it) + # to migrate all the legacy stuff like + # protected/unprotected/skipped uris and regexes - if perm_name.endswith(".main"): - perm_label = label - else: - # e.g. if perm_name is wordpress.admin, we want "Blog (Admin)" (where Blog is the label of this app) - perm_label = "%s (%s)" % (label, perm_name.rsplit(".")[-1].replace("_", " ").title()) + perm_label = perm_info['label'] - if raw: - if domain not in result: - result[perm_domain] = {} - result[perm_domain][perm_path] = { - 'label': perm_label, - 'id': app_id - } - else: - result[perm_domain + perm_path] = perm_label + for url in [perm_info["url"]] + perm_info['additional_urls']: + if url is None: + # Happend when 'additional_urls' is empty !! + continue + + perm_domain, perm_path = url.split("/", 1) + perm_path = '/' + perm_path + if raw: + if perm_domain not in result: + result[perm_domain] = {} + result[perm_domain][perm_path] = { + 'label': perm_label, + 'id': app_id + } + else: + result[perm_domain + perm_path] = perm_label return result diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 52f0dc22e..62ae4449f 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -526,6 +526,7 @@ def _update_ldap_group_permission(permission, allowed, if show_tile is None: show_tile = existing_permission["show_tile"] + # TODO set show_tile to False if url is regex if protected is None: protected = existing_permission["protected"] From a7432393c1d169f671a0361a39684b0276817beb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 1 Apr 2020 22:04:28 +0200 Subject: [PATCH 0798/3170] Rework app_ssowatconf --- src/yunohost/app.py | 122 ++++++++++++++++++++++++++++---------------- 1 file changed, 79 insertions(+), 43 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index d3ca2eb82..d12ae014f 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1186,14 +1186,25 @@ def app_ssowatconf(): main_domain = _get_maindomain() domains = domain_list()['domains'] - all_permissions = user_permission_list(full=True, full_path=False)['permissions'] + all_permissions = user_permission_list(full=True, full_path=True)['permissions'] - skipped_urls = [] - skipped_regex = [] - unprotected_urls = [] - unprotected_regex = [] - protected_urls = [] - protected_regex = [] + permissions = { + 'core_skipped': { + "users": [], + "label": "Core permissions - skipped", + "show_tile": False, + "auth_header": False, + "protected": False, + "uris": [ + [domain + '/yunohost/admin' for domain in domains] + \ + [domain + '/yunohost/api' for domain in domains] + [ + "re:^[^/]*/%.well%-known/ynh%-diagnosis/.*$", + "re:^[^/]*/%.well%-known/acme%-challenge/.*$", + "re:^[^/]*/%.well%-known/autoconfig/mail/config%-v1%.1%.xml.*$" + ] + ] + } + } redirected_regex = {main_domain + '/yunohost[\/]?$': 'https://' + main_domain + '/yunohost/sso/'} redirected_urls = {} @@ -1205,6 +1216,8 @@ def app_ssowatconf(): app_settings = read_yaml(APPS_SETTING_PATH + app + '/settings.yml') + ## BEGIN Legacy part ## + if 'domain' not in app_settings: continue if 'path' not in app_settings: @@ -1232,20 +1245,19 @@ def app_ssowatconf(): return perm_domain + perm_path # Skipped - skipped_urls += [_sanitized_absolute_url(uri) for uri in _get_setting(app_settings, 'skipped_uris')] - skipped_regex += _get_setting(app_settings, 'skipped_regex') - - # Redirected - redirected_urls.update(app_settings.get('redirected_urls', {})) - redirected_regex.update(app_settings.get('redirected_regex', {})) + skipped_urls = [_sanitized_absolute_url(uri) for uri in _get_setting(app_settings, 'skipped_uris')] + skipped_urls += ['re:' + regex for regex in _get_setting(app_settings, 'skipped_regex')] # Legacy permission system using (un)protected_uris and _regex managed in app settings... - unprotected_urls += [_sanitized_absolute_url(uri) for uri in _get_setting(app_settings, 'unprotected_uris')] - protected_urls += [_sanitized_absolute_url(uri) for uri in _get_setting(app_settings, 'protected_uris')] - unprotected_regex += _get_setting(app_settings, 'unprotected_regex') - protected_regex += _get_setting(app_settings, 'protected_regex') + unprotected_urls = [_sanitized_absolute_url(uri) for uri in _get_setting(app_settings, 'unprotected_uris')] + protected_urls = [_sanitized_absolute_url(uri) for uri in _get_setting(app_settings, 'protected_uris')] + unprotected_urls += ['re:' + regex for regex in _get_setting(app_settings, 'unprotected_regex')] + protected_urls += ['re:' + regex for regex in _get_setting(app_settings, 'protected_regex')] - # New permission system + if skipped_urls == [] and unprotected_urls == [] and protected_urls == []: + continue + + # Manage compatibility with old protected, unprotected, skipped urls !! this_app_perms = {name: info for name, info in all_permissions.items() if name.startswith(app + ".")} for perm_name, perm_info in this_app_perms.items(): @@ -1253,39 +1265,71 @@ def app_ssowatconf(): if not perm_info["url"]: continue - # FIXME : gotta handle regex-urls here... meh url = _sanitized_absolute_url(perm_info["url"]) perm_info["url"] = url if "visitors" in perm_info["allowed"]: - if url not in unprotected_urls: - unprotected_urls.append(url) - # Legacy stuff : we remove now protected-urls that might have been declared as unprotected earlier... protected_urls = [u for u in protected_urls if u != url] else: - if url not in protected_urls: - protected_urls.append(url) - # Legacy stuff : we remove now unprotected-urls / skipped-urls that might have been declared as protected earlier... unprotected_urls = [u for u in unprotected_urls if u != url] skipped_urls = [u for u in skipped_urls if u != url] - for domain in domains: - skipped_urls.extend([domain + '/yunohost/admin', domain + '/yunohost/api']) + # Create special permission for legacy apps + if skipped_urls != []: + permissions[app + ".legacy_skipped_urls"] = { + "users": [], + "label": "Legacy permission - skipped_urls for app :" + app, + "show_tile": False, + "auth_header": False, + "public": False, + "uris": skipped_urls + } + if unprotected_urls != []: + permissions[app + ".legacy_unprotected_urls"] = { + "users": all_permissions[app + '.main']['corresponding_users'], + "label": "Legacy permission - unprotected_urls for app :" + app, + "show_tile": False, + "auth_header": True, + "public": False, + "uris": unprotected_urls + } + if protected_urls != []: + permissions[app + ".legacy_protected_urls"] = { + "users": all_permissions[app + '.main']['corresponding_users'], + "label": "Legacy permission - protected_urls for app :" + app, + "show_tile": False, + "auth_header": True, + "public": True, + "uris": protected_urls + } - # Authorize ynh remote diagnosis, ACME challenge and mail autoconfig urls - skipped_regex.append("^[^/]*/%.well%-known/ynh%-diagnosis/.*$") - skipped_regex.append("^[^/]*/%.well%-known/acme%-challenge/.*$") - skipped_regex.append("^[^/]*/%.well%-known/autoconfig/mail/config%-v1%.1%.xml.*$") + ## END Legacy part ## + # Redirected + redirected_urls.update(app_settings.get('redirected_urls', {})) + redirected_regex.update(app_settings.get('redirected_regex', {})) - permissions_per_url = {} + # New permission system for perm_name, perm_info in all_permissions.items(): # Ignore permissions for which there's no url defined - if not perm_info["url"]: + if not perm_info["url"] and not perm_info['additional_urls']: continue - permissions_per_url[perm_info["url"]] = perm_info['corresponding_users'] + uris = [] + if perm_info['url'] is not None: + uris += [perm_info['url']] + if perm_info['additional_urls'] != [None]: + uris += perm_info['additional_urls'] + + permissions[perm_name] = { + "users": perm_info['corresponding_users'], + "label": perm_info['label'], + "show_tile": perm_info['show_tile'], + "auth_header": perm_info['auth_header'], + "public": "visitors" in perm_info["allowed"], + "uris": uris + } conf_dict = { 'portal_domain': main_domain, @@ -1297,17 +1341,9 @@ def app_ssowatconf(): 'Email': 'mail' }, 'domains': domains, - 'skipped_urls': skipped_urls, - 'unprotected_urls': unprotected_urls, - 'protected_urls': protected_urls, - 'skipped_regex': skipped_regex, - 'unprotected_regex': unprotected_regex, - 'protected_regex': protected_regex, 'redirected_urls': redirected_urls, 'redirected_regex': redirected_regex, - 'users': {username: app_map(user=username) - for username in user_list()['users'].keys()}, - 'permissions': permissions_per_url, + 'permissions': permissions, } with open('/etc/ssowat/conf.json', 'w+') as f: From ad04c33cbf9666151cd5f47db3d237aef4b072e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 1 Apr 2020 23:10:11 +0200 Subject: [PATCH 0799/3170] Simplify if --- src/yunohost/permission.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 62ae4449f..0ef6abf8c 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -85,10 +85,10 @@ def user_permission_list(short=False, full=False, ignore_system_perms=False, ful if full: permissions[name]["corresponding_users"] = [_ldap_path_extract(p, "uid") for p in infos.get('inheritPermission', [])] - permissions[name]["auth_header"] = False if infos.get("authHeader", [False])[0] == "FALSE" else True + permissions[name]["auth_header"] = infos.get("authHeader", [False])[0] == "TRUE" permissions[name]["label"] = infos.get("label", [None])[0] - permissions[name]["show_tile"] = False if infos.get("showTile", [False])[0] == "FALSE" else True - permissions[name]["protected"] = False if infos.get("isProtected", [False])[0] == "FALSE" else True + permissions[name]["show_tile"] = infos.get("showTile", [False])[0] == "TRUE" + permissions[name]["protected"] = infos.get("isProtected", [False])[0] == "TRUE" if full_path and name.split(".")[0] not in SYSTEM_PERMS: permissions[name]["url"] = _complete_url(infos.get("URL", [None])[0], name) permissions[name]["additional_urls"] = [_complete_url(url, name) for url in infos.get("additionalUrls", [None])] From 56886bb6a090749a1481b1dcec330314f28a62bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 1 Apr 2020 23:20:57 +0200 Subject: [PATCH 0800/3170] Fix ssowat config --- 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 d12ae014f..768e8549f 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1194,7 +1194,7 @@ def app_ssowatconf(): "label": "Core permissions - skipped", "show_tile": False, "auth_header": False, - "protected": False, + "public": False, "uris": [ [domain + '/yunohost/admin' for domain in domains] + \ [domain + '/yunohost/api' for domain in domains] + [ From 55b1cea91914844932fd2a57fade740b8b73c43d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 1 Apr 2020 23:31:44 +0200 Subject: [PATCH 0801/3170] Rename migration --- locales/en.json | 1 + ...sion_protection.py => 0015_extends_permissions_features_1.py} | 0 2 files changed, 1 insertion(+) rename src/yunohost/data_migrations/{0015_add_permission_protection.py => 0015_extends_permissions_features_1.py} (100%) diff --git a/locales/en.json b/locales/en.json index 605d7633e..749936d89 100644 --- a/locales/en.json +++ b/locales/en.json @@ -390,6 +390,7 @@ "migration_description_0012_postgresql_password_to_md5_authentication": "Force PostgreSQL authentication to use MD5 for local connections", "migration_description_0013_futureproof_apps_catalog_system": "Migrate to the new future-proof apps catalog system", "migration_description_0014_remove_app_status_json": "Remove legacy status.json app files", + "migration_description_0015_extends_permissions_features_1": "Extends permissions features, step 1", "migration_0003_start": "Starting migration to Stretch. The logs will be available in {logfile}.", "migration_0003_patching_sources_list": "Patching the sources.lists…", "migration_0003_main_upgrade": "Starting main upgrade…", diff --git a/src/yunohost/data_migrations/0015_add_permission_protection.py b/src/yunohost/data_migrations/0015_extends_permissions_features_1.py similarity index 100% rename from src/yunohost/data_migrations/0015_add_permission_protection.py rename to src/yunohost/data_migrations/0015_extends_permissions_features_1.py From 77b8c440fb9c9611bc098f3a954b90805575dd43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 1 Apr 2020 23:42:16 +0200 Subject: [PATCH 0802/3170] Clean locales --- locales/en.json | 1 - 1 file changed, 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 749936d89..418a7d7f2 100644 --- a/locales/en.json +++ b/locales/en.json @@ -480,7 +480,6 @@ "permission_not_found": "Permission '{permission:s}' not found", "permission_update_failed": "Could not update permission '{permission}': {error}", "permission_updated": "Permission '{permission:s}' updated", - "permission_update_nothing_to_do": "No permissions to update", "permission_protected": "Permission {permission} protected. You can't modify the visitors group to access to this permission.", "permission_require_account": "Permission {permission} only makes sense for users having an account, and therefore cannot be enabled for visitors.", "port_already_closed": "Port {port:d} is already closed for {ip_version:s} connections", From ff7bd0e860ec14eb3244472937d8f7c96f2a3aad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Thu, 2 Apr 2020 11:41:12 +0200 Subject: [PATCH 0803/3170] Check url conflict for permissions --- src/yunohost/permission.py | 66 ++++++++++++++++++++++++++++---------- 1 file changed, 49 insertions(+), 17 deletions(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 0ef6abf8c..1dcdc176d 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -60,17 +60,7 @@ def user_permission_list(short=False, full=False, ignore_system_perms=False, ful 'URL', 'additionalUrls', 'authHeader', 'label', 'showTile', 'isProtected']) # Parse / organize information to be outputed - app_settings = {app['id']: app_setting(app['id'], 'domain') + app_setting(app['id'], 'path') for app in app_list()['apps']} - - def _complete_url(url, name): - if url is None: - return None - if url.startswith('/'): - return app_settings[name.split('.')[0]] + url.rstrip("/") - if url.startswith('re:/'): - return 're:' + app_settings[name.split('.')[0]] + url.lstrip('re:/') - else: - return url + apps_main_path = {app['id']: app_setting(app['id'], 'domain') + app_setting(app['id'], 'path') for app in app_list()['apps']} permissions = {} for infos in permissions_infos: @@ -90,8 +80,8 @@ def user_permission_list(short=False, full=False, ignore_system_perms=False, ful permissions[name]["show_tile"] = infos.get("showTile", [False])[0] == "TRUE" permissions[name]["protected"] = infos.get("isProtected", [False])[0] == "TRUE" if full_path and name.split(".")[0] not in SYSTEM_PERMS: - permissions[name]["url"] = _complete_url(infos.get("URL", [None])[0], name) - permissions[name]["additional_urls"] = [_complete_url(url, name) for url in infos.get("additionalUrls", [None])] + permissions[name]["url"] = _get_full_url(infos.get("URL", [None])[0], apps_main_path[name.split('.')[0]]) + permissions[name]["additional_urls"] = [_get_full_url(url, apps_main_path[name.split('.')[0]]) for url in infos.get("additionalUrls", [None])] else: permissions[name]["url"] = infos.get("URL", [None])[0] permissions[name]["additional_urls"] = infos.get("additionalUrls", [None]) @@ -337,26 +327,42 @@ def permission_url(operation_logger, permission, auth_header -- (optional) Define for the URL of this permission, if SSOwat pass the authentication header to the application clear_urls -- (optional) Clean all urls (url and additional_urls) """ + from yunohost.app import app_setting from yunohost.utils.ldap import _get_ldap_interface - from yunohost.domain import _check_and_normalize_permission_path + from yunohost.domain import _check_and_normalize_permission_path, domain_url_available ldap = _get_ldap_interface() # By default, manipulate main permission if "." not in permission: permission = permission + ".main" + # App main path in setting to manage conflict + app_main_path = app_setting(permission.split('.')[0], 'domain') + app_setting(permission.split('.')[0], 'path') + # Fetch existing permission existing_permission = user_permission_list(full=True, full_path=False)["permissions"].get(permission, None) if not existing_permission: raise YunohostError('permission_not_found', permission=permission) - # TODO -> Check conflict with other app and other URL !! - if url is None: url = existing_permission["url"] else: url = _check_and_normalize_permission_path(url) + domain, path = _get_full_url(url, app_main_path).split('/', 1) + conflicts = _get_conflicting_apps(domain, path, ignore_app=permission.spit('.')[0]) + + if conflicts: + apps = [] + for path, app_id, app_label in conflicts: + apps.append(" * {domain:s}{path:s} → {app_label:s} ({app_id:s})".format( + domain=domain, + path=path, + app_id=app_id, + app_label=app_label, + )) + + raise YunohostError('app_location_unavailable', apps="\n".join(apps)) current_additional_urls = existing_permission["additional_urls"] new_additional_urls = copy.copy(current_additional_urls) @@ -366,7 +372,22 @@ def permission_url(operation_logger, permission, if ur in current_additional_urls: logger.warning(m18n.n('additional_urls_already_added', permission=permission, url=url)) else: - new_additional_urls += [_check_and_normalize_permission_path(url)] + new_url = _check_and_normalize_permission_path(new_url) + domain, path = _get_full_url(new_url, app_main_path).split('/', 1) + conflicts = _get_conflicting_apps(domain, path, ignore_app=permission.spit('.')[0]) + + if conflicts: + apps = [] + for path, app_id, app_label in conflicts: + apps.append(" * {domain:s}{path:s} → {app_label:s} ({app_id:s})".format( + domain=domain, + path=path, + app_id=app_id, + app_label=app_label, + )) + + raise YunohostError('app_location_unavailable', apps="\n".join(apps)) + new_additional_urls += [new_url] if remove_url: for ur in remove_url: @@ -574,3 +595,14 @@ def _update_ldap_group_permission(permission, allowed, hook_callback('post_app_removeaccess', args=[app, ','.join(effectively_removed_users), sub_permission, ','.join(effectively_removed_group)]) return new_permission + + +def _get_full_url(url, app_main_path): + if url is None: + return None + if url.startswith('/'): + return app_main_path + url.rstrip("/") + if url.startswith('re:/'): + return 're:' + app_main_path + url.lstrip('re:/') + else: + return url From 4e8a5d8e598d307ce43de360984ce13bebfaec49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Thu, 2 Apr 2020 11:56:53 +0200 Subject: [PATCH 0804/3170] Update migration --- .../0015_extends_permissions_features_1.py | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/yunohost/data_migrations/0015_extends_permissions_features_1.py b/src/yunohost/data_migrations/0015_extends_permissions_features_1.py index 75a31def4..992006840 100644 --- a/src/yunohost/data_migrations/0015_extends_permissions_features_1.py +++ b/src/yunohost/data_migrations/0015_extends_permissions_features_1.py @@ -6,6 +6,7 @@ from yunohost.utils.error import YunohostError from moulinette.utils.log import getActionLogger from yunohost.tools import Migration +from yunohost.app import app_setting from yunohost.permission import user_permission_list, SYSTEM_PERMS logger = getActionLogger('yunohost.migration') @@ -36,8 +37,27 @@ class MyMigration(Migration): for permission in permission_list: if permission.split('.')[0] in SYSTEM_PERMS: - ldap.update('cn=%s,ou=permission' % permission, {'isProtected': "TRUE"}) - elif permission.endswith(".main"): - ldap.update('cn=%s,ou=permission' % permission, {'isProtected': "FALSE"}) + ldap.update('cn=%s,ou=permission' % permission, { + 'authHeader': ["FALSE"], + 'label': [permission.split('.')[0]], + 'showTile': ["FALSE"], + 'isProtected': ["TRUE"], + }) else: - ldap.update('cn=%s,ou=permission' % permission, {'isProtected': "TRUE"}) + label = app_setting(permission.split('.')[0], 'label') + + if permission.endswith(".main"): + ldap.update('cn=%s,ou=permission' % permission, { + 'authHeader': ["TRUE"], + 'label': [label], + 'showTile': ["TRUE"], + 'isProtected': ["FALSE"] + }) + else: + ldap.update('cn=%s,ou=permission' % permission, { + 'authHeader': ["TRUE"], + 'label': ["%s (%s)" (label, permission.split('.')[1])], + 'showTile': ["FALSE"], + 'isProtected': ["TRUE"] + }) + app_setting(permission.split('.')[0], 'label', delete=True) From 678334eb2897b24cf3056f40ccb7d98979047bd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Thu, 2 Apr 2020 14:23:43 +0200 Subject: [PATCH 0805/3170] Fix ssowatconf --- src/yunohost/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 768e8549f..9cd8097d3 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1186,7 +1186,7 @@ def app_ssowatconf(): main_domain = _get_maindomain() domains = domain_list()['domains'] - all_permissions = user_permission_list(full=True, full_path=True)['permissions'] + all_permissions = user_permission_list(full=True, ignore_system_perms=True, full_path=True)['permissions'] permissions = { 'core_skipped': { @@ -1313,7 +1313,7 @@ def app_ssowatconf(): # New permission system for perm_name, perm_info in all_permissions.items(): # Ignore permissions for which there's no url defined - if not perm_info["url"] and not perm_info['additional_urls']: + if perm_info["url"] is None and perm_info['additional_urls'] == [None]: continue uris = [] From dc38d57baaef9c0b322651cee1fd07737c87b5c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Thu, 2 Apr 2020 14:39:20 +0200 Subject: [PATCH 0806/3170] Make more robust migration --- .../0015_extends_permissions_features_1.py | 57 ++++++++++++++++++- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/src/yunohost/data_migrations/0015_extends_permissions_features_1.py b/src/yunohost/data_migrations/0015_extends_permissions_features_1.py index 992006840..433bf94da 100644 --- a/src/yunohost/data_migrations/0015_extends_permissions_features_1.py +++ b/src/yunohost/data_migrations/0015_extends_permissions_features_1.py @@ -6,7 +6,7 @@ from yunohost.utils.error import YunohostError from moulinette.utils.log import getActionLogger from yunohost.tools import Migration -from yunohost.app import app_setting +from yunohost.app import app_setting, app_ssowatconf from yunohost.permission import user_permission_list, SYSTEM_PERMS logger = getActionLogger('yunohost.migration') @@ -18,7 +18,7 @@ class MyMigration(Migration): required = True - def run(self): + def add_new_ldap_attributes(self): from yunohost.utils.ldap import _get_ldap_interface from yunohost.regenconf import regen_conf, BACKUP_CONF_DIR @@ -61,3 +61,56 @@ class MyMigration(Migration): 'isProtected': ["TRUE"] }) app_setting(permission.split('.')[0], 'label', delete=True) + + def run(self): + + # FIXME : what do we really want to do here ... + # Imho we should just force-regen the conf in all case, and maybe + # just display a warning if we detect that the conf was manually modified + + # Check if the migration can be processed + ldap_regen_conf_status = regen_conf(names=['slapd'], dry_run=True) + # By this we check if the have been customized + if ldap_regen_conf_status and ldap_regen_conf_status['slapd']['pending']: + logger.warning(m18n.n("migration_0011_slapd_config_will_be_overwritten", conf_backup_folder=BACKUP_CONF_DIR)) + + # Backup LDAP and the apps settings before to do the migration + logger.info(m18n.n("migration_0011_backup_before_migration")) + try: + backup_folder = "/home/yunohost.backup/premigration/" + time.strftime('%Y%m%d-%H%M%S', time.gmtime()) + os.makedirs(backup_folder, 0o750) + os.system("systemctl stop slapd") + os.system("cp -r --preserve /etc/ldap %s/ldap_config" % backup_folder) + os.system("cp -r --preserve /var/lib/ldap %s/ldap_db" % backup_folder) + os.system("cp -r --preserve /etc/yunohost/apps %s/apps_settings" % backup_folder) + except Exception as e: + raise YunohostError("migration_0011_can_not_backup_before_migration", error=e) + finally: + os.system("systemctl start slapd") + + try: + # Update LDAP schema restart slapd + logger.info(m18n.n("migration_0011_update_LDAP_schema")) + regen_conf(names=['slapd'], force=True) + + # Update LDAP database + self.add_new_ldap_attributes() + + app_ssowatconf() + + except Exception as e: + logger.warn(m18n.n("migration_0011_migration_failed_trying_to_rollback")) + os.system("systemctl stop slapd") + os.system("rm -r /etc/ldap/slapd.d") # To be sure that we don't keep some part of the old config + os.system("cp -r --preserve %s/ldap_config/. /etc/ldap/" % backup_folder) + os.system("cp -r --preserve %s/ldap_db/. /var/lib/ldap/" % backup_folder) + os.system("cp -r --preserve %s/apps_settings/. /etc/yunohost/apps/" % backup_folder) + os.system("systemctl start slapd") + os.system("rm -r " + backup_folder) + logger.info(m18n.n("migration_0011_rollback_success")) + raise + else: + os.system("rm -r " + backup_folder) + + logger.info(m18n.n("migration_0011_done")) + From 6c9b5379415701adf1e3b35532ccadd6b39483f8 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 2 Apr 2020 19:30:40 +0200 Subject: [PATCH 0807/3170] Drop unused helpers ynh_add_skipped/(un)protected_uris --- data/helpers.d/setting | 92 ------------------------------------------ 1 file changed, 92 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index 384fdc399..f3692cf96 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -59,98 +59,6 @@ ynh_app_setting_delete() { ynh_app_setting "delete" "$app" "$key" } -# Add skipped_uris urls into the config -# -# usage: ynh_add_skipped_uris [--appid=app] --url=url1,url2 [--regex] -# | arg: -a, --appid - the application id -# | arg: -u, --url - the urls to add to the sso for this app -# | arg: -r, --regex - Use the key 'skipped_regex' instead of 'skipped_uris' -# -# An URL set with 'skipped_uris' key will be totally ignored by the SSO, -# which means that the access will be public and the logged-in user information will not be passed to the app. -# -# Requires YunoHost version 3.6.0 or higher. -ynh_add_skipped_uris() { - # Declare an array to define the options of this helper. - local legacy_args=aur - declare -Ar args_array=( [a]=appid= [u]=url= [r]=regex ) - local appid - local url - local regex - # Manage arguments with getopts - ynh_handle_getopts_args "$@" - appid={appid:-$app} - regex={regex:-0} - - local key=skipped_uris - if [ $regex -eq 1 ]; then - key=skipped_regex - fi - - ynh_app_setting_set --app=$appid --key=$key --value="$url" -} - -# Add unprotected_uris urls into the config -# -# usage: ynh_add_unprotected_uris [--appid=app] --url=url1,url2 [--regex] -# | arg: -a, --appid - the application id -# | arg: -u, --url - the urls to add to the sso for this app -# | arg: -r, --regex - Use the key 'unprotected_regex' instead of 'unprotected_uris' -# -# An URL set with unprotected_uris key will be accessible publicly, but if an user is logged in, -# his information will be accessible (through HTTP headers) to the app. -# -# Requires YunoHost version 3.6.0 or higher. -ynh_add_unprotected_uris() { - # Declare an array to define the options of this helper. - local legacy_args=aur - declare -Ar args_array=( [a]=appid= [u]=url= [r]=regex ) - local appid - local url - local regex - # Manage arguments with getopts - ynh_handle_getopts_args "$@" - appid={appid:-$app} - regex={regex:-0} - - local key=unprotected_uris - if [ $regex -eq 1 ]; then - key=unprotected_regex - fi - - ynh_app_setting_set --app=$appid --key=$key --value="$url" -} - -# Add protected_uris urls into the config -# -# usage: ynh_add_protected_uris [--appid=app] --url=url1,url2 [--regex] -# | arg: -a, --appid - the application id -# | arg: -u, --url - the urls to add to the sso for this app -# | arg: -r, --regex - Use the key 'protected_regex' instead of 'protected_uris' -# -# An URL set with protected_uris will be blocked by the SSO and accessible only to authenticated and authorized users. -# -# Requires YunoHost version 3.6.0 or higher. -ynh_add_protected_uris() { - # Declare an array to define the options of this helper. - local legacy_args=aur - declare -Ar args_array=( [a]=appid= [u]=url= [r]=regex ) - local appid - local url - local regex - # Manage arguments with getopts - ynh_handle_getopts_args "$@" - appid={appid:-$app} - regex={regex:-0} - - local key=protected_uris - if [ $regex -eq 1 ]; then - key=protected_regex - fi - - ynh_app_setting_set --app=$appid --key=$key --value="$url" -} - # Small "hard-coded" interface to avoid calling "yunohost app" directly each # time dealing with a setting is needed (which may be so slow on ARM boards) # From 2c9f1f89e052e6cc1d8adcdded8105417b2f1f53 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 2 Apr 2020 23:05:54 +0200 Subject: [PATCH 0808/3170] Simplify indentation in ynh_psql_test_if_first_run --- data/helpers.d/postgresql | 49 ++++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/data/helpers.d/postgresql b/data/helpers.d/postgresql index e5df73654..4f51f434d 100644 --- a/data/helpers.d/postgresql +++ b/data/helpers.d/postgresql @@ -265,34 +265,35 @@ ynh_psql_remove_db() { ynh_psql_test_if_first_run() { if [ -f "$PSQL_ROOT_PWD_FILE" ]; then echo "PostgreSQL is already installed, no need to create master password" - else - local psql_root_password="$(ynh_string_random)" - echo "$psql_root_password" >$PSQL_ROOT_PWD_FILE + return + fi - if [ -e /etc/postgresql/9.4/ ]; then - local pg_hba=/etc/postgresql/9.4/main/pg_hba.conf - local logfile=/var/log/postgresql/postgresql-9.4-main.log - elif [ -e /etc/postgresql/9.6/ ]; then - local pg_hba=/etc/postgresql/9.6/main/pg_hba.conf - local logfile=/var/log/postgresql/postgresql-9.6-main.log - else - ynh_die "postgresql shoud be 9.4 or 9.6" - fi + local psql_root_password="$(ynh_string_random)" + echo "$psql_root_password" >$PSQL_ROOT_PWD_FILE - ynh_systemd_action --service_name=postgresql --action=start + if [ -e /etc/postgresql/9.4/ ]; then + local pg_hba=/etc/postgresql/9.4/main/pg_hba.conf + local logfile=/var/log/postgresql/postgresql-9.4-main.log + elif [ -e /etc/postgresql/9.6/ ]; then + local pg_hba=/etc/postgresql/9.6/main/pg_hba.conf + local logfile=/var/log/postgresql/postgresql-9.6-main.log + else + ynh_die "postgresql shoud be 9.4 or 9.6" + fi - sudo --login --user=postgres psql -c"ALTER user postgres WITH PASSWORD '$psql_root_password'" postgres + ynh_systemd_action --service_name=postgresql --action=start - # 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" + sudo --login --user=postgres psql -c"ALTER user postgres WITH PASSWORD '$psql_root_password'" postgres - # Advertise service in admin panel - yunohost service add postgresql --log "$logfile" + # 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" - systemctl enable postgresql - ynh_systemd_action --service_name=postgresql --action=reload - fi + # Advertise service in admin panel + yunohost service add postgresql --log "$logfile" + + systemctl enable postgresql + ynh_systemd_action --service_name=postgresql --action=reload } From 83a1d6dec53b93a7fcc4e194c5b92b11256b956d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 2 Apr 2020 23:13:25 +0200 Subject: [PATCH 0809/3170] This is not needed --- data/helpers.d/postgresql | 1 - 1 file changed, 1 deletion(-) diff --git a/data/helpers.d/postgresql b/data/helpers.d/postgresql index 4f51f434d..03c713afd 100644 --- a/data/helpers.d/postgresql +++ b/data/helpers.d/postgresql @@ -243,7 +243,6 @@ ynh_psql_remove_db() { # Manage arguments with getopts ynh_handle_getopts_args "$@" - local psql_root_password=$(cat $PSQL_ROOT_PWD_FILE) if ynh_psql_database_exists --database=$db_name; then # Check if the database exists ynh_psql_drop_db $db_name # Remove the database else From fb5dac80d5316f8b8776fff1d57df31ef6a67ac1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 3 Apr 2020 00:12:58 +0200 Subject: [PATCH 0810/3170] Force locale to C/en to avoid perl whining and flooding logs about the damn missing locale --- data/helpers.d/apt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index b2c781faf..7859d44c5 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -94,7 +94,7 @@ ynh_package_version() { # Requires YunoHost version 2.4.0.3 or higher. ynh_apt() { ynh_wait_dpkg_free - DEBIAN_FRONTEND=noninteractive apt-get -y $@ + LC_ALL=C DEBIAN_FRONTEND=noninteractive apt-get -y $@ } # Update package index files @@ -184,7 +184,7 @@ ynh_package_install_from_equivs () { ynh_wait_dpkg_free cp "$controlfile" "${TMPDIR}/control" (cd "$TMPDIR" - equivs-build ./control 1> /dev/null + LC_ALL=C equivs-build ./control 1> /dev/null dpkg --force-depends -i "./${pkgname}_${pkgversion}_all.deb" 2>&1) # If install fails we use "apt-get check" to try to debug and diagnose possible unmet dependencies # Note the use of { } which allows to group commands without starting a subshell (otherwise the ynh_die wouldn't exit the current shell). From eb4586c244c1af7dc22763a55b67a062fcccdbc7 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 3 Apr 2020 01:32:05 +0200 Subject: [PATCH 0811/3170] Do not redact stuff corresponding to --manifest_key --- src/yunohost/log.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/yunohost/log.py b/src/yunohost/log.py index 72e497b5d..cd08bdfe0 100644 --- a/src/yunohost/log.py +++ b/src/yunohost/log.py @@ -315,9 +315,9 @@ class RedactingFormatter(Formatter): try: # This matches stuff like db_pwd=the_secret or admin_password=other_secret # (the secret part being at least 3 chars to avoid catching some lines like just "db_pwd=") - # For 'key', we require to at least have one word char [a-zA-Z0-9_] before it to avoid catching "--key" used in many helpers - match = re.search(r'(pwd|pass|password|secret|\wkey|token)=(\S{3,})$', record.strip()) - if match and match.group(2) not in self.data_to_redact: + # Some names like "key" or "manifest_key" are ignored, used in helpers like ynh_app_setting_set or ynh_read_manifest + match = re.search(r'(pwd|pass|password|secret|\w+key|token)=(\S{3,})$', record.strip()) + if match and match.group(2) not in self.data_to_redact and match.group(1) not in ["key", "manifest_key"]: self.data_to_redact.append(match.group(2)) except Exception as e: logger.warning("Failed to parse line to try to identify data to redact ... : %s" % e) From 128577686a5df3920ba8169c28a5ecf1b62a9987 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 3 Apr 2020 03:09:46 +0200 Subject: [PATCH 0812/3170] Forgot to make yunohost_admin.conf to also use the common securit.conf.inc --- data/templates/nginx/yunohost_admin.conf | 42 ++---------------------- 1 file changed, 2 insertions(+), 40 deletions(-) diff --git a/data/templates/nginx/yunohost_admin.conf b/data/templates/nginx/yunohost_admin.conf index e0d9f6bb1..63d466ecd 100644 --- a/data/templates/nginx/yunohost_admin.conf +++ b/data/templates/nginx/yunohost_admin.conf @@ -15,48 +15,10 @@ server { listen 443 ssl http2 default_server; listen [::]:443 ssl http2 default_server; + include /etc/nginx/conf.d/security.conf.inc; + ssl_certificate /etc/yunohost/certs/yunohost.org/crt.pem; ssl_certificate_key /etc/yunohost/certs/yunohost.org/key.pem; - ssl_session_timeout 5m; - ssl_session_cache shared:SSL:50m; - - {% if compatibility == "modern" %} - # Ciphers with modern compatibility - # https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=nginx-1.6.2&openssl=1.0.1t&hsts=yes&profile=modern - # Uncomment the following to use modern ciphers, but remove compatibility with some old clients (android < 5.0, Internet Explorer < 10, ...) - ssl_protocols TLSv1.2; - ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256'; - ssl_prefer_server_ciphers on; - {% else %} - # As suggested by Mozilla : https://wiki.mozilla.org/Security/Server_Side_TLS and https://en.wikipedia.org/wiki/Curve25519 - ssl_ecdh_curve secp521r1:secp384r1:prime256v1; - ssl_prefer_server_ciphers on; - - # Ciphers with intermediate compatibility - # https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=nginx-1.6.2&openssl=1.0.1t&hsts=yes&profile=intermediate - ssl_protocols TLSv1 TLSv1.1 TLSv1.2; - ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS'; - - # Uncomment the following directive after DH generation - # > openssl dhparam -out /etc/ssl/private/dh2048.pem -outform PEM -2 2048 - #ssl_dhparam /etc/ssl/private/dh2048.pem; - {% endif %} - - # Follows the Web Security Directives from the Mozilla Dev Lab and the Mozilla Obervatory + Partners - # https://wiki.mozilla.org/Security/Guidelines/Web_Security - # https://observatory.mozilla.org/ - more_set_headers "Strict-Transport-Security : max-age=63072000; includeSubDomains; preload"; - more_set_headers "Referrer-Policy : 'same-origin'"; - more_set_headers "Content-Security-Policy : upgrade-insecure-requests; object-src 'none'; script-src https: 'unsafe-eval'"; - more_set_headers "X-Content-Type-Options : nosniff"; - more_set_headers "X-XSS-Protection : 1; mode=block"; - more_set_headers "X-Download-Options : noopen"; - more_set_headers "X-Permitted-Cross-Domain-Policies : none"; - more_set_headers "X-Frame-Options : SAMEORIGIN"; - - # Disable gzip to protect against BREACH - # Read https://trac.nginx.org/nginx/ticket/1720 (text/html cannot be disabled!) - gzip off; location / { return 302 https://$http_host/yunohost/admin; From 23617a9386e2549f5288dcbcf1b0349bc0eb7ca7 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 3 Apr 2020 03:41:37 +0200 Subject: [PATCH 0813/3170] Update dovecot SSL conf according to Mozilla recommentation --- data/templates/dovecot/dovecot.conf | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/data/templates/dovecot/dovecot.conf b/data/templates/dovecot/dovecot.conf index 477ccbfb1..0a3c185ee 100644 --- a/data/templates/dovecot/dovecot.conf +++ b/data/templates/dovecot/dovecot.conf @@ -12,10 +12,25 @@ protocols = imap sieve {% if pop3_enabled == "True" %}pop3{% endif %} mail_plugins = $mail_plugins quota -ssl = yes +############################################################################### + +# generated 2020-04-03, Mozilla Guideline v5.4, Dovecot 2.2.27, OpenSSL 1.1.1l, intermediate configuration +# https://ssl-config.mozilla.org/#server=dovecot&version=2.2.27&config=intermediate&openssl=1.1.1l&guideline=5.4 + +ssl = required + ssl_cert = Date: Fri, 3 Apr 2020 03:41:52 +0200 Subject: [PATCH 0814/3170] Update postfix SSL conf according to Moz^Cla recommentation --- data/templates/postfix/main.cf | 44 ++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/data/templates/postfix/main.cf b/data/templates/postfix/main.cf index 045b8edd0..79a551a6c 100644 --- a/data/templates/postfix/main.cf +++ b/data/templates/postfix/main.cf @@ -18,35 +18,39 @@ append_dot_mydomain = no readme_directory = no # -- TLS for incoming connections -# By default, TLS is disabled in the Postfix SMTP server, so no difference to -# plain Postfix is visible. Explicitly switch it on with "smtpd_tls_security_level = may". -smtpd_tls_security_level=may +############################################################################### +# generated 2020-04-03, Mozilla Guideline v5.4, Postfix 3.1.14, OpenSSL 1.1.1l, intermediate configuration +# https://ssl-config.mozilla.org/#server=postfix&version=3.1.14&config=intermediate&openssl=1.1.1l&guideline=5.4 -# Sending AUTH data over an unencrypted channel poses a security risk. -# When TLS layer encryption is optional ("smtpd_tls_security_level = may"), it -# may however still be useful to only offer AUTH when TLS is active. To maintain -# compatibility with non-TLS clients, the default is to accept AUTH without -# encryption. In order to change this behavior, we set "smtpd_tls_auth_only = yes". -smtpd_tls_auth_only=yes +# (No modern conf support until we're on buster...) +# {% if compatibility == "intermediate" %} {% else %} {% endif %} + +smtpd_use_tls = yes + +smtpd_tls_security_level = may +smtpd_tls_auth_only = yes smtpd_tls_cert_file = /etc/yunohost/certs/{{ main_domain }}/crt.pem smtpd_tls_key_file = /etc/yunohost/certs/{{ main_domain }}/key.pem -smtpd_tls_exclude_ciphers = aNULL, MD5, DES, ADH, RC4, 3DES +smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1 +smtpd_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1 +smtpd_tls_mandatory_ciphers = medium + +# curl https://ssl-config.mozilla.org/ffdhe2048.txt > /path/to/dhparam.pem +# not actually 1024 bits, this applies to all DHE >= 1024 bits +# smtpd_tls_dh1024_param_file = /path/to/dhparam.pem + +tls_medium_cipherlist = ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384 +tls_preempt_cipherlist = no +############################################################################### smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache smtpd_tls_loglevel=1 -{% if compatibility == "intermediate" %} -smtpd_tls_mandatory_protocols=!SSLv2,!SSLv3 -{% else %} -smtpd_tls_mandatory_protocols=!SSLv2,!SSLv3,!TLSv1,!TLSv1.1 -{% endif %} -smtpd_tls_mandatory_ciphers=high -smtpd_tls_eecdh_grade = ultra # -- TLS for outgoing connections # Use TLS if this is supported by the remote SMTP server, otherwise use plaintext. smtp_tls_security_level=may smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache -smtp_tls_exclude_ciphers = $smtpd_tls_exclude_ciphers -smtp_tls_mandatory_ciphers= $smtpd_tls_mandatory_ciphers +smtp_tls_exclude_ciphers = aNULL, MD5, DES, ADH, RC4, 3DES +smtp_tls_mandatory_ciphers= high smtp_tls_loglevel=1 # Configure Root CA certificates @@ -167,4 +171,4 @@ default_destination_rate_delay = 5s # By default it's possible to detect if the email adress exist # So it's easly possible to scan a server to know which email adress is valid # and after to send spam -disable_vrfy_command = yes \ No newline at end of file +disable_vrfy_command = yes From 6813a64cf6e17c23515786de4618456c966c9eb4 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Fri, 3 Apr 2020 20:28:13 +0200 Subject: [PATCH 0815/3170] remove sync_perm argument --- src/yunohost/permission.py | 2 +- src/yunohost/user.py | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 05def2101..b5ef0884f 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -197,7 +197,7 @@ def user_permission_reset(operation_logger, permission, sync_perm=True): return new_permission -def user_permission_info(permission, sync_perm=True): +def user_permission_info(permission): """ Return informations about a specific permission diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 74ad9f977..4afcc4e72 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -780,10 +780,9 @@ def user_permission_reset(permission, sync_perm=True): sync_perm=sync_perm) -def user_permission_info(permission, sync_perm=True): +def user_permission_info(permission): import yunohost.permission - return yunohost.permission.user_permission_info(permission, - sync_perm=sync_perm) + return yunohost.permission.user_permission_info(permission) # From b8a1687f88f4abd159c0845559c6352739ef0ca0 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Fri, 3 Apr 2020 20:30:00 +0200 Subject: [PATCH 0816/3170] Use a dedicated php service for each app --- data/helpers.d/php | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/data/helpers.d/php b/data/helpers.d/php index 41af467c5..5167afa2a 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -18,7 +18,7 @@ ynh_add_fpm_config () { phpversion="${phpversion:-7.0}" local fpm_config_dir="/etc/php/$phpversion/fpm" - local fpm_service="php${phpversion}-fpm" + local fpm_service="php${phpversion}-fpm-$app" # Configure PHP-FPM 5 on Debian Jessie if [ "$(ynh_get_debian_release)" == "jessie" ]; then fpm_config_dir="/etc/php5/fpm" @@ -45,7 +45,27 @@ ynh_add_fpm_config () { chown root: "$finalphpini" ynh_store_file_checksum "$finalphpini" fi - ynh_systemd_action --service_name=$fpm_service --action=reload + + # Create a config for a dedicated php-fpm service for the app + echo " +[Unit] +Description=PHP $phpversion FastCGI Process Manager for $app +After=network.target + +[Service] +Type=notify +PIDFile=/run/php/php${phpversion}-fpm-$app.pid +ExecStart=/usr/sbin/php-fpm${phpversion} --nodaemonize --fpm-config $finalphpconf --pid /run/php/php${phpversion}-fpm-$app.pid +ExecReload=/bin/kill -USR2 \$MAINPID + +[Install] +WantedBy=multi-user.target +" > ../conf/$fpm_service + + # Create this dedicated php-fpm service + ynh_add_systemd_config --service=$fpm_service --template=$fpm_service + + ynh_systemd_action --service_name=$fpm_service --action=restart } # Remove the dedicated php-fpm config @@ -61,7 +81,10 @@ ynh_remove_fpm_config () { fpm_config_dir="/etc/php/7.0/fpm" fpm_service="php7.0-fpm" fi + + # Remove the dedicated service php-fpm service + ynh_remove_systemd_config --service=$fpm_service + ynh_secure_remove --file="$fpm_config_dir/pool.d/$app.conf" ynh_secure_remove --file="$fpm_config_dir/conf.d/20-$app.ini" 2>&1 - ynh_systemd_action --service_name=$fpm_service --action=reload } From 7afe07018edac5adc34f1d3a83bb607f13367b1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Fri, 3 Apr 2020 21:27:44 +0200 Subject: [PATCH 0817/3170] Fix app_map --- 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 9cd8097d3..d72e439bb 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -219,7 +219,7 @@ def app_map(app=None, raw=False, user=None, permission=None): if user not in main_perm["corresponding_users"]: continue - this_app_perms = {p: i for p, i in permissions.items() if p.startswith(app_id + ".") and i["url"]} + this_app_perms = {p: i for p, i in permissions.items() if p.startswith(app_id + ".") and (i["url"] or i['additional_urls'] != [None])} for perm_name, perm_info in this_app_perms.items(): # If we're building the map for a specific user, check the user # actually is allowed for this specific perm From 756237c051ffd042ce75036fe2ea55cb07c2eb32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Thu, 2 Apr 2020 16:58:24 +0200 Subject: [PATCH 0818/3170] Fix and improve migration --- locales/en.json | 2 ++ src/yunohost/app.py | 4 ---- .../0015_extends_permissions_features_1.py | 22 +++++-------------- 3 files changed, 8 insertions(+), 20 deletions(-) diff --git a/locales/en.json b/locales/en.json index 418a7d7f2..30ede23a8 100644 --- a/locales/en.json +++ b/locales/en.json @@ -428,6 +428,8 @@ "migration_0011_update_LDAP_database": "Updating LDAP database…", "migration_0011_update_LDAP_schema": "Updating LDAP schema…", "migration_0011_failed_to_remove_stale_object": "Could not remove stale object {dn}: {error}", + "migration_0015_add_new_attributes_in_ldap": "Add new attributes for permissions in LDAP database", + "migration_0015_migrate_old_app_settings": "Migrate old apps settings 'skipped_uris', 'unprotected_uris', 'protected_uris' in permissions system.", "migrations_already_ran": "Those migrations are already done: {ids}", "migrations_cant_reach_migration_file": "Could not access migrations files at the path '%s'", "migrations_dependencies_not_satisfied": "Run these migrations: '{dependencies_id}', before migration {id}.", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index d72e439bb..afb40d8ed 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1208,10 +1208,6 @@ def app_ssowatconf(): redirected_regex = {main_domain + '/yunohost[\/]?$': 'https://' + main_domain + '/yunohost/sso/'} redirected_urls = {} - def _get_setting(settings, name): - s = settings.get(name, None) - return s.split(',') if s else [] - for app in _installed_apps(): app_settings = read_yaml(APPS_SETTING_PATH + app + '/settings.yml') diff --git a/src/yunohost/data_migrations/0015_extends_permissions_features_1.py b/src/yunohost/data_migrations/0015_extends_permissions_features_1.py index 433bf94da..83222fd0a 100644 --- a/src/yunohost/data_migrations/0015_extends_permissions_features_1.py +++ b/src/yunohost/data_migrations/0015_extends_permissions_features_1.py @@ -6,8 +6,8 @@ from yunohost.utils.error import YunohostError from moulinette.utils.log import getActionLogger from yunohost.tools import Migration -from yunohost.app import app_setting, app_ssowatconf -from yunohost.permission import user_permission_list, SYSTEM_PERMS +from yunohost.app import app_setting, app_ssowatconf, _installed_apps +from yunohost.permission import user_permission_list, SYSTEM_PERMS, permission_sync_to_user logger = getActionLogger('yunohost.migration') @@ -29,10 +29,12 @@ class MyMigration(Migration): if ldap_regen_conf_status and ldap_regen_conf_status['slapd']['pending']: logger.warning(m18n.n("migration_0011_slapd_config_will_be_overwritten", conf_backup_folder=BACKUP_CONF_DIR)) + # Update LDAP schema restart slapd + logger.info(m18n.n("migration_0011_update_LDAP_schema")) regen_conf(names=['slapd'], force=True) + logger.info(m18n.n("migration_0015_add_new_attributes_in_ldap")) ldap = _get_ldap_interface() - permission_list = user_permission_list(short=True)["permissions"] for permission in permission_list: @@ -62,18 +64,13 @@ class MyMigration(Migration): }) app_setting(permission.split('.')[0], 'label', delete=True) + def run(self): # FIXME : what do we really want to do here ... # Imho we should just force-regen the conf in all case, and maybe # just display a warning if we detect that the conf was manually modified - # Check if the migration can be processed - ldap_regen_conf_status = regen_conf(names=['slapd'], dry_run=True) - # By this we check if the have been customized - if ldap_regen_conf_status and ldap_regen_conf_status['slapd']['pending']: - logger.warning(m18n.n("migration_0011_slapd_config_will_be_overwritten", conf_backup_folder=BACKUP_CONF_DIR)) - # Backup LDAP and the apps settings before to do the migration logger.info(m18n.n("migration_0011_backup_before_migration")) try: @@ -89,10 +86,6 @@ class MyMigration(Migration): os.system("systemctl start slapd") try: - # Update LDAP schema restart slapd - logger.info(m18n.n("migration_0011_update_LDAP_schema")) - regen_conf(names=['slapd'], force=True) - # Update LDAP database self.add_new_ldap_attributes() @@ -111,6 +104,3 @@ class MyMigration(Migration): raise else: os.system("rm -r " + backup_folder) - - logger.info(m18n.n("migration_0011_done")) - From 1ff4f578d7bb7b0e6805a081e500d9c74f47bd06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Fri, 3 Apr 2020 22:12:50 +0200 Subject: [PATCH 0819/3170] Fix app_ssowatconf --- src/yunohost/app.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index afb40d8ed..d72e439bb 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1208,6 +1208,10 @@ def app_ssowatconf(): redirected_regex = {main_domain + '/yunohost[\/]?$': 'https://' + main_domain + '/yunohost/sso/'} redirected_urls = {} + def _get_setting(settings, name): + s = settings.get(name, None) + return s.split(',') if s else [] + for app in _installed_apps(): app_settings = read_yaml(APPS_SETTING_PATH + app + '/settings.yml') From e6ccb01af18f9b174a92ee8a30fb143d0c9143fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Fri, 3 Apr 2020 23:23:05 +0200 Subject: [PATCH 0820/3170] Print warning if packager try to show tile for regex and disable show tile in this case --- locales/en.json | 1 + src/yunohost/app.py | 2 +- src/yunohost/permission.py | 6 +++++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/locales/en.json b/locales/en.json index 30ede23a8..cacf8282e 100644 --- a/locales/en.json +++ b/locales/en.json @@ -501,6 +501,7 @@ "regenconf_dry_pending_applying": "Checking pending configuration which would have been applied for category '{category}'…", "regenconf_failed": "Could not regenerate the configuration for category(s): {categories}", "regenconf_pending_applying": "Applying pending configuration for category '{category}'…", + "regex_incompatible_with_tile": "/!\\ Packagers! You for the permission '{permission}' can't set the regex {regex} as main url and set 'show_tile' to 'true'", "regex_with_only_domain": "You can't use a regex for domain, only for path", "restore_already_installed_app": "An app with the ID '{app:s}' is already installed", "restore_app_failed": "Could not restore the app '{app:s}'", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index d72e439bb..0263a9d98 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1325,7 +1325,7 @@ def app_ssowatconf(): permissions[perm_name] = { "users": perm_info['corresponding_users'], "label": perm_info['label'], - "show_tile": perm_info['show_tile'], + "show_tile": perm_info['show_tile'] if not perm_info["url"].startswith('re:') else False, "auth_header": perm_info['auth_header'], "public": "visitors" in perm_info["allowed"], "uris": uris diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 1dcdc176d..2ea12b508 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -167,6 +167,9 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, if "visitors" not in new_allowed_groups or len(new_allowed_groups) >= 3: logger.warning(m18n.n("permission_currently_allowed_for_all_users")) + if existing_permission['url'].startswith('re:') and show_tile: + logger.warning(m18n.n('regex_incompatible_with_tile', regex=existing_permission['url'], permission=permission)) + # Commit the new allowed group list operation_logger.start() @@ -351,6 +354,8 @@ def permission_url(operation_logger, permission, url = _check_and_normalize_permission_path(url) domain, path = _get_full_url(url, app_main_path).split('/', 1) conflicts = _get_conflicting_apps(domain, path, ignore_app=permission.spit('.')[0]) + if url.startswith('re:') and existing_permission['show_tile']: + logger.warning(m18n.n('regex_incompatible_with_tile', regex=url, permission=permission)) if conflicts: apps = [] @@ -547,7 +552,6 @@ def _update_ldap_group_permission(permission, allowed, if show_tile is None: show_tile = existing_permission["show_tile"] - # TODO set show_tile to False if url is regex if protected is None: protected = existing_permission["protected"] From b859953aa4304916c999c9ff0990e52aa1b3c4ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Fri, 3 Apr 2020 23:51:10 +0200 Subject: [PATCH 0821/3170] Fix user_permission_list --- src/yunohost/permission.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 2ea12b508..39da59bd4 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -60,7 +60,9 @@ def user_permission_list(short=False, full=False, ignore_system_perms=False, ful 'URL', 'additionalUrls', 'authHeader', 'label', 'showTile', 'isProtected']) # Parse / organize information to be outputed - apps_main_path = {app['id']: app_setting(app['id'], 'domain') + app_setting(app['id'], 'path') for app in app_list()['apps']} + apps_main_path = {app['id']: app_setting(app['id'], 'domain') + app_setting(app['id'], 'path') + for app in app_list()['apps'] + if app_setting(app['id'], 'domain') and app_setting(app['id'], 'path')} permissions = {} for infos in permissions_infos: @@ -79,7 +81,7 @@ def user_permission_list(short=False, full=False, ignore_system_perms=False, ful permissions[name]["label"] = infos.get("label", [None])[0] permissions[name]["show_tile"] = infos.get("showTile", [False])[0] == "TRUE" permissions[name]["protected"] = infos.get("isProtected", [False])[0] == "TRUE" - if full_path and name.split(".")[0] not in SYSTEM_PERMS: + if full_path and name.split(".")[0] in apps_main_path: permissions[name]["url"] = _get_full_url(infos.get("URL", [None])[0], apps_main_path[name.split('.')[0]]) permissions[name]["additional_urls"] = [_get_full_url(url, apps_main_path[name.split('.')[0]]) for url in infos.get("additionalUrls", [None])] else: From 45bb5f4b3541967f68f3b181a97dd3186021d971 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Fri, 3 Apr 2020 23:58:32 +0200 Subject: [PATCH 0822/3170] Fix tests --- locales/en.json | 1 - 1 file changed, 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index cacf8282e..42ed6d7ff 100644 --- a/locales/en.json +++ b/locales/en.json @@ -429,7 +429,6 @@ "migration_0011_update_LDAP_schema": "Updating LDAP schema…", "migration_0011_failed_to_remove_stale_object": "Could not remove stale object {dn}: {error}", "migration_0015_add_new_attributes_in_ldap": "Add new attributes for permissions in LDAP database", - "migration_0015_migrate_old_app_settings": "Migrate old apps settings 'skipped_uris', 'unprotected_uris', 'protected_uris' in permissions system.", "migrations_already_ran": "Those migrations are already done: {ids}", "migrations_cant_reach_migration_file": "Could not access migrations files at the path '%s'", "migrations_dependencies_not_satisfied": "Run these migrations: '{dependencies_id}', before migration {id}.", From 6e5e118bcfda01b606c8d60b6f6fe12bcc5b0560 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Sat, 4 Apr 2020 00:14:47 +0200 Subject: [PATCH 0823/3170] Set mandatory domain and path to add url in permission --- locales/en.json | 1 + src/yunohost/permission.py | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/locales/en.json b/locales/en.json index 42ed6d7ff..564c0b821 100644 --- a/locales/en.json +++ b/locales/en.json @@ -578,6 +578,7 @@ "tools_upgrade_special_packages_completed": "YunoHost package upgrade completed.\nPress [Enter] to get the command line back", "unbackup_app": "App '{app:s}' will not be saved", "unexpected_error": "Something unexpected went wrong: {error}", + "unknown_main_domain_path": "Unknown domain or path for app '{app}'. You need to specify a domain and a path to be able to specify a url for permission.", "unlimit": "No quota", "unrestore_app": "App '{app:s}' will not be restored", "update_apt_cache_failed": "Could not to update the cache of APT (Debian's package manager). Here is a dump of the sources.list lines, which might help identify problematic lines: \n{sourceslist}", diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 39da59bd4..29872d044 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -341,8 +341,12 @@ def permission_url(operation_logger, permission, if "." not in permission: permission = permission + ".main" - # App main path in setting to manage conflict - app_main_path = app_setting(permission.split('.')[0], 'domain') + app_setting(permission.split('.')[0], 'path') + if url or add_url: + if app_setting(permission.split('.')[0], 'domain') is None or app_setting(permission.split('.')[0], 'path') is None: + raise YunohostError('unknown_main_domain_path', app=permission.split('.')[0]) + else: + # App main path in setting to manage conflict + app_main_path = app_setting(permission.split('.')[0], 'domain') + app_setting(permission.split('.')[0], 'path') # Fetch existing permission From 8fabd8f1400de0a7c9d55cdeba4c8400d8a9c692 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Sat, 4 Apr 2020 00:36:01 +0200 Subject: [PATCH 0824/3170] Define url of permission at the end of the app install --- 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 0263a9d98..a19871699 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -717,7 +717,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu # Initialize the main permission for the app # After the install, if apps don't have a domain and path defined, the default url '/' is removed from the permission - permission_create(app_instance_name+".main", url="/", allowed=["all_users"], label=label, show_tile=True, protected=False) + permission_create(app_instance_name+".main", allowed=["all_users"], label=label, show_tile=True, protected=False) # Execute the app install script install_failed = True @@ -830,12 +830,12 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu os.system('chown -R root: %s' % app_setting_path) os.system('chown -R admin: %s/scripts' % app_setting_path) - # If an app doesn't have at least a domain and a path, assume it's not a webapp and remove the default "/" permission + # If the app haven't set the url of the main permission and domain and path is set set / as main url app_settings = _get_app_settings(app_instance_name) domain = app_settings.get('domain', None) path = app_settings.get('path', None) - if not (domain and path): - permission_url(app_instance_name + ".main", url=None, sync_perm=False) + if domain and path and user_permission_list(full=True, full_path=False)['permissions'][app_instance_name + '.main']['url'] is None: + permission_url(app_instance_name + ".main", url='/', sync_perm=False) _migrate_legacy_permissions(app_instance_name) From 147b2490074d4d306fd68e43a8502afd597d2737 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sat, 4 Apr 2020 01:19:49 +0200 Subject: [PATCH 0825/3170] Proper migration to new directory --- data/helpers.d/php | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/php b/data/helpers.d/php index 5167afa2a..3c5390f56 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -17,17 +17,36 @@ ynh_add_fpm_config () { # Configure PHP-FPM 7.0 by default phpversion="${phpversion:-7.0}" - local fpm_config_dir="/etc/php/$phpversion/fpm" + local fpm_config_dir="/etc/php/$phpversion/dedicated-fpm" + local old_fpm_config_dir="/etc/php/$phpversion/fpm" local fpm_service="php${phpversion}-fpm-$app" # Configure PHP-FPM 5 on Debian Jessie if [ "$(ynh_get_debian_release)" == "jessie" ]; then fpm_config_dir="/etc/php5/fpm" fpm_service="php5-fpm" fi + + # Create the directory for fpm pools + mkdir -p "$fpm_config_dir/pool.d" + ynh_app_setting_set --app=$app --key=fpm_config_dir --value="$fpm_config_dir" ynh_app_setting_set --app=$app --key=fpm_service --value="$fpm_service" finalphpconf="$fpm_config_dir/pool.d/$app.conf" - ynh_backup_if_checksum_is_different --file="$finalphpconf" + + # Migrate from mutual php service to dedicated one. + if [ -e "$old_fpm_config_dir/pool.d/$app.conf" ] + then + ynh_print_info --message="Migrate to a dedicated php-fpm service for $app." + # Create a backup of the old file before migration + ynh_backup_if_checksum_is_different --file="$old_fpm_config_dir/pool.d/$app.conf" + # Remove the old php config file + ynh_secure_remove --file="$old_fpm_config_dir/pool.d/$app.conf" + # Reload php to release the socket and allow the dedicated service to use it + systemctl reload php${phpversion}-fpm + else + ynh_backup_if_checksum_is_different --file="$finalphpconf" + fi + cp ../conf/php-fpm.conf "$finalphpconf" ynh_replace_string --match_string="__NAMETOCHANGE__" --replace_string="$app" --target_file="$finalphpconf" ynh_replace_string --match_string="__FINALPATH__" --replace_string="$final_path" --target_file="$finalphpconf" From f7ac93b0b74b370674ec9492047b679eb02a459b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 5 Apr 2020 18:31:16 +0200 Subject: [PATCH 0826/3170] We in fact only have ssl 1.1.0l, not 1.1.1l on Stretch. --- data/templates/dovecot/dovecot.conf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data/templates/dovecot/dovecot.conf b/data/templates/dovecot/dovecot.conf index 0a3c185ee..8fc0e75ae 100644 --- a/data/templates/dovecot/dovecot.conf +++ b/data/templates/dovecot/dovecot.conf @@ -14,8 +14,8 @@ mail_plugins = $mail_plugins quota ############################################################################### -# generated 2020-04-03, Mozilla Guideline v5.4, Dovecot 2.2.27, OpenSSL 1.1.1l, intermediate configuration -# https://ssl-config.mozilla.org/#server=dovecot&version=2.2.27&config=intermediate&openssl=1.1.1l&guideline=5.4 +# generated 2020-04-03, Mozilla Guideline v5.4, Dovecot 2.2.27, OpenSSL 1.1.0l, intermediate configuration +# https://ssl-config.mozilla.org/#server=dovecot&version=2.2.27&config=intermediate&openssl=1.1.0l&guideline=5.4 ssl = required @@ -25,7 +25,7 @@ ssl_key = Date: Sun, 5 Apr 2020 18:31:33 +0200 Subject: [PATCH 0827/3170] We in fact only have ssl 1.1.0l, not 1.1.1l on Stretch. --- data/templates/postfix/main.cf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/templates/postfix/main.cf b/data/templates/postfix/main.cf index 79a551a6c..2642fd8f0 100644 --- a/data/templates/postfix/main.cf +++ b/data/templates/postfix/main.cf @@ -19,8 +19,8 @@ readme_directory = no # -- TLS for incoming connections ############################################################################### -# generated 2020-04-03, Mozilla Guideline v5.4, Postfix 3.1.14, OpenSSL 1.1.1l, intermediate configuration -# https://ssl-config.mozilla.org/#server=postfix&version=3.1.14&config=intermediate&openssl=1.1.1l&guideline=5.4 +# generated 2020-04-03, Mozilla Guideline v5.4, Postfix 3.1.14, OpenSSL 1.1.0l, intermediate configuration +# https://ssl-config.mozilla.org/#server=postfix&version=3.1.14&config=intermediate&openssl=1.1.0l&guideline=5.4 # (No modern conf support until we're on buster...) # {% if compatibility == "intermediate" %} {% else %} {% endif %} From ecdb30aab234ebef95dd4e54e4847623327178e8 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Sun, 5 Apr 2020 19:44:39 +0200 Subject: [PATCH 0828/3170] [fix] config_appy return link --- src/yunohost/app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index de2a74c9c..39793ec1a 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1574,6 +1574,7 @@ def app_config_apply(operation_logger, app, args): logger.success("Config updated as expected") return { + "app": app, "logs": operation_logger.success(), } From ecce6f11cc467d4745aa7e94fa3cbb4184e30f32 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 5 Apr 2020 20:22:17 +0200 Subject: [PATCH 0829/3170] Move wildcard DNS record to 'extra' category --- src/yunohost/domain.py | 78 ++++++++++++++++++++++++++---------------- src/yunohost/dyndns.py | 12 ++++++- 2 files changed, 59 insertions(+), 31 deletions(-) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 456dfa4bf..23b5a4179 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -236,8 +236,7 @@ def domain_dns_conf(domain, ttl=None): for record in record_list: result += "\n{name} {ttl} IN {type} {value}".format(**record) - is_cli = True if msettings.get('interface') == 'cli' else False - if is_cli: + if msettings.get('interface') == 'cli': logger.info(m18n.n("domain_dns_conf_is_just_a_recommendation")) return result @@ -406,10 +405,8 @@ def _build_dns_conf(domain, ttl=3600): "basic": [ # if ipv4 available {"type": "A", "name": "@", "value": "123.123.123.123", "ttl": 3600}, - {"type": "A", "name": "*", "value": "123.123.123.123", "ttl": 3600}, # if ipv6 available {"type": "AAAA", "name": "@", "value": "valid-ipv6", "ttl": 3600}, - {"type": "AAAA", "name": "*", "value": "valid-ipv6", "ttl": 3600}, ], "xmpp": [ {"type": "SRV", "name": "_xmpp-client._tcp", "value": "0 5 5222 domain.tld.", "ttl": 3600}, @@ -426,6 +423,10 @@ def _build_dns_conf(domain, ttl=3600): {"type": "TXT", "name": "_dmarc", "value": "\"v=DMARC1; p=none\"", "ttl": 3600} ], "extra": [ + # if ipv4 available + {"type": "A", "name": "*", "value": "123.123.123.123", "ttl": 3600}, + # if ipv6 available + {"type": "AAAA", "name": "*", "value": "valid-ipv6", "ttl": 3600}, {"type": "CAA", "name": "@", "value": "128 issue \"letsencrypt.org\"", "ttl": 3600}, ], "example_of_a_custom_rule": [ @@ -437,32 +438,21 @@ def _build_dns_conf(domain, ttl=3600): ipv4 = get_public_ip() ipv6 = get_public_ip(6) - basic = [] + ########################### + # Basic ipv4/ipv6 records # + ########################### - # Basic ipv4/ipv6 records + basic = [] if ipv4: - basic += [ - ["@", ttl, "A", ipv4], - ["*", ttl, "A", ipv4], - ] + basic.append(["@", ttl, "A", ipv4]) if ipv6: - basic += [ - ["@", ttl, "AAAA", ipv6], - ["*", ttl, "AAAA", ipv6], - ] + basic.append(["@", ttl, "AAAA", ipv6]) - # XMPP - xmpp = [ - ["_xmpp-client._tcp", ttl, "SRV", "0 5 5222 %s." % domain], - ["_xmpp-server._tcp", ttl, "SRV", "0 5 5269 %s." % domain], - ["muc", ttl, "CNAME", "@"], - ["pubsub", ttl, "CNAME", "@"], - ["vjud", ttl, "CNAME", "@"], - ["xmpp-upload", ttl, "CNAME", "@"], - ] + ######### + # Email # + ######### - # SPF record spf_record = '"v=spf1 a mx' if ipv4: spf_record += ' ip4:{ip4}'.format(ip4=ipv4) @@ -470,7 +460,6 @@ def _build_dns_conf(domain, ttl=3600): spf_record += ' ip6:{ip6}'.format(ip6=ipv6) spf_record += ' -all"' - # Email mail = [ ["@", ttl, "MX", "10 %s." % domain], ["@", ttl, "TXT", spf_record], @@ -485,12 +474,36 @@ def _build_dns_conf(domain, ttl=3600): ["_dmarc", ttl, "TXT", '"v=DMARC1; p=none"'], ] - # Extra - extra = [ - ["@", ttl, "CAA", '128 issue "letsencrypt.org"'] + ######## + # XMPP # + ######## + + xmpp = [ + ["_xmpp-client._tcp", ttl, "SRV", "0 5 5222 %s." % domain], + ["_xmpp-server._tcp", ttl, "SRV", "0 5 5269 %s." % domain], + ["muc", ttl, "CNAME", "@"], + ["pubsub", ttl, "CNAME", "@"], + ["vjud", ttl, "CNAME", "@"], + ["xmpp-upload", ttl, "CNAME", "@"], ] - # Official record + ######### + # Extra # + ######### + + extra = [] + + if ipv4: + extra.append(["*", ttl, "A", ipv4]) + if ipv6: + extra.append(["*", ttl, "AAAA", ipv6]) + + extra.append(["@", ttl, "CAA", '128 issue "letsencrypt.org"']) + + #################### + # Standard records # + #################### + 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], @@ -498,7 +511,12 @@ def _build_dns_conf(domain, ttl=3600): "extra": [{"name": name, "ttl": ttl, "type": type_, "value": value} for name, ttl, type_, value in extra], } - # Custom records + ################## + # Custom records # + ################## + + # Defined by custom hooks ships in apps for example ... + hook_results = hook_callback('custom_dns_rules', args=[domain]) for hook_name, results in hook_results.items(): # diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index 70817b3fe..6e597fbbf 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -258,7 +258,17 @@ def dyndns_update(operation_logger, dyn_host="dyndns.yunohost.org", domain=None, logger.info("Updated needed, going on...") dns_conf = _build_dns_conf(domain) - del dns_conf["extra"] # Ignore records from the 'extra' category + + for i, record in enumerate(dns_conf["extra"]): + # Ignore CAA record ... not sure why, we could probably enforce it... + if record[3] == "CAA": + del dns_conf["extra"][i] + + # Delete custom DNS records, we don't support them (have to explicitly + # authorize them on dynette) + for category in dns_conf.keys(): + if category not in ["basic", "mail", "xmpp", "extra"]: + del dns_conf[category] # Delete the old records for all domain/subdomains From f032ba16cc5b6778e18a7042943f45e212fab6f0 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 5 Apr 2020 20:23:32 +0200 Subject: [PATCH 0830/3170] Only diagnose basic records for subdomains --- data/hooks/diagnosis/12-dnsrecords.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py index 96ac31d55..a889201b9 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -28,21 +28,24 @@ class DNSRecordsDiagnoser(Diagnoser): all_domains = domain_list()["domains"] for domain in all_domains: self.logger_debug("Diagnosing DNS conf for %s" % domain) - for report in self.check_domain(domain, domain == main_domain): + is_subdomain = domain.split(".",1)[1] in all_domains + for report in self.check_domain(domain, domain == main_domain, is_subdomain=is_subdomain): yield report # FIXME : somewhere, should implement a check for reverse DNS ... # FIXME / TODO : somewhere, could also implement a check for domain expiring soon - def check_domain(self, domain, is_main_domain): + def check_domain(self, domain, is_main_domain, is_subdomain): expected_configuration = _build_dns_conf(domain) - # Here if there are no AAAA record, we should add something to expect "no" AAAA record + # FIXME: Here if there are no AAAA record, we should add something to expect "no" AAAA record # to properly diagnose situations where people have a AAAA record but no IPv6 - categories = ["basic", "mail", "xmpp", "extra"] + if is_subdomain: + categories = ["basic"] + for category in categories: records = expected_configuration[category] From a4d28efa6c249e7585f48e222ff8510e287b7889 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 5 Apr 2020 22:37:24 +0200 Subject: [PATCH 0831/3170] less0 -> at_least_one --- data/helpers.d/php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/data/helpers.d/php b/data/helpers.d/php index 817be7f4d..4f5e63dfd 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -311,7 +311,7 @@ ynh_get_scalable_phpfpm () { # Get the total of RAM available, except swap. local max_ram=$(ynh_check_ram --no_swap) - less0() { + at_least_one() { # Do not allow value below 1 if [ $1 -le 0 ] then @@ -331,7 +331,7 @@ ynh_get_scalable_phpfpm () { then php_max_children=$(( $php_max_children / 2 )) fi - php_max_children=$(less0 $php_max_children) + php_max_children=$(at_least_one $php_max_children) # To not overload the proc, limit the number of children to 4 times the number of cores. local core_number=$(nproc) @@ -345,13 +345,13 @@ ynh_get_scalable_phpfpm () { then # Define pm.start_servers, pm.min_spare_servers and pm.max_spare_servers for a dynamic process manager php_min_spare_servers=$(( $php_max_children / 8 )) - php_min_spare_servers=$(less0 $php_min_spare_servers) + php_min_spare_servers=$(at_least_one $php_min_spare_servers) php_max_spare_servers=$(( $php_max_children / 2 )) - php_max_spare_servers=$(less0 $php_max_spare_servers) + php_max_spare_servers=$(at_least_one $php_max_spare_servers) php_start_servers=$(( $php_min_spare_servers + ( $php_max_spare_servers - $php_min_spare_servers ) /2 )) - php_start_servers=$(less0 $php_start_servers) + php_start_servers=$(at_least_one $php_start_servers) else php_min_spare_servers=0 php_max_spare_servers=0 From 810e5b0d0909da9393367694790dc645144897f8 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 5 Apr 2020 22:53:56 +0200 Subject: [PATCH 0832/3170] no_swap -> ignore_swap --- data/helpers.d/hardware | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/data/helpers.d/hardware b/data/helpers.d/hardware index 11012a3d1..11c7b27dc 100644 --- a/data/helpers.d/hardware +++ b/data/helpers.d/hardware @@ -2,22 +2,22 @@ # Check the amount of available RAM # -# usage: ynh_check_ram [--required=RAM required in Mb] [--no_swap|--only_swap] [--free_ram] +# usage: ynh_check_ram [--required=RAM required in Mb] [--ignore_swap|--only_swap] [--free_ram] # | arg: -r, --required= - Amount of RAM required in Mb. The helper will return 0 is there's enough RAM, or 1 otherwise. # If --required isn't set, the helper will print the amount of RAM, in Mb. -# | arg: -s, --no_swap - Ignore swap +# | arg: -s, --ignore_swap - Ignore swap # | arg: -o, --only_swap - Ignore real RAM, consider only swap. # | arg: -f, --free_ram - Count only free RAM, not the total amount of RAM available. -ynh_check_ram () { +ynh_available_ram () { # Declare an array to define the options of this helper. - declare -Ar args_array=( [r]=required= [s]=no_swap [o]=only_swap [f]=free_ram ) + declare -Ar args_array=( [r]=required= [s]=ignore_swap [o]=only_swap [f]=free_ram ) local required - local no_swap + local ignore_swap local only_swap # Manage arguments with getopts ynh_handle_getopts_args "$@" required=${required:-} - no_swap=${no_swap:-0} + ignore_swap=${ignore_swap:-0} only_swap=${only_swap:-0} local total_ram=$(vmstat --stats --unit M | grep "total memory" | awk '{print $1}') @@ -34,7 +34,7 @@ ynh_check_ram () { then # Use the total amount of free ram ram=$free_ram_swap - if [ $no_swap -eq 1 ] + if [ $ignore_swap -eq 1 ] then # Use only the amount of free ram ram=$free_ram @@ -44,7 +44,7 @@ ynh_check_ram () { ram=$free_swap fi else - if [ $no_swap -eq 1 ] + if [ $ignore_swap -eq 1 ] then # Use only the amount of free ram ram=$total_ram From 195214bdbfcab61979fe2bb83c05e6d2483765b8 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sun, 5 Apr 2020 23:18:58 +0200 Subject: [PATCH 0833/3170] Update data/helpers.d/php Co-Authored-By: Kayou --- data/helpers.d/php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/php b/data/helpers.d/php index 3c5390f56..13036684b 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -103,7 +103,7 @@ ynh_remove_fpm_config () { # Remove the dedicated service php-fpm service ynh_remove_systemd_config --service=$fpm_service - + yunohost service remove $fpm_service ynh_secure_remove --file="$fpm_config_dir/pool.d/$app.conf" ynh_secure_remove --file="$fpm_config_dir/conf.d/20-$app.ini" 2>&1 } From cbf573c34689502b815a6ea29fce3350ad0d2b29 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 5 Apr 2020 23:57:43 +0200 Subject: [PATCH 0834/3170] Try to improve the semantic of RAM helper --- data/helpers.d/hardware | 86 +++++++++++++++++++++++++++-------------- 1 file changed, 57 insertions(+), 29 deletions(-) diff --git a/data/helpers.d/hardware b/data/helpers.d/hardware index 11c7b27dc..be669568e 100644 --- a/data/helpers.d/hardware +++ b/data/helpers.d/hardware @@ -1,24 +1,25 @@ #!/bin/bash -# Check the amount of available RAM +# Get the total or free amount of RAM+swap on the system # -# usage: ynh_check_ram [--required=RAM required in Mb] [--ignore_swap|--only_swap] [--free_ram] -# | arg: -r, --required= - Amount of RAM required in Mb. The helper will return 0 is there's enough RAM, or 1 otherwise. -# If --required isn't set, the helper will print the amount of RAM, in Mb. -# | arg: -s, --ignore_swap - Ignore swap -# | arg: -o, --only_swap - Ignore real RAM, consider only swap. -# | arg: -f, --free_ram - Count only free RAM, not the total amount of RAM available. -ynh_available_ram () { +# usage: ynh_get_ram [--free|--total] [--ignore_swap|--only_swap] +# | arg: -f, --free - Count free RAM+swap +# | arg: -t, --total - Count total RAM+swap +# | arg: -s, --ignore_swap - Ignore swap, consider only real RAM +# | arg: -o, --only_swap - Ignore real RAM, consider only swap +ynh_get_ram () { # Declare an array to define the options of this helper. - declare -Ar args_array=( [r]=required= [s]=ignore_swap [o]=only_swap [f]=free_ram ) - local required + declare -Ar args_array=( [f]=free [t]=total [s]=ignore_swap [o]=only_swap ) + local free + local total local ignore_swap local only_swap # Manage arguments with getopts ynh_handle_getopts_args "$@" - required=${required:-} ignore_swap=${ignore_swap:-0} only_swap=${only_swap:-0} + free=${free:-0} + total=${total:-0} local total_ram=$(vmstat --stats --unit M | grep "total memory" | awk '{print $1}') local total_swap=$(vmstat --stats --unit M | grep "total swap" | awk '{print $1}') @@ -29,11 +30,10 @@ ynh_available_ram () { local free_ram_swap=$(( free_ram + free_swap )) # Use the total amount of ram - local ram=$total_ram_swap - if [ $free_ram -eq 1 ] + if [ $free -eq 1 ] then # Use the total amount of free ram - ram=$free_ram_swap + local ram=$free_ram_swap if [ $ignore_swap -eq 1 ] then # Use only the amount of free ram @@ -43,7 +43,9 @@ ynh_available_ram () { # Use only the amount of free swap ram=$free_swap fi - else + elif [ $total -eq 1 ] + then + local ram=$total_ram_swap if [ $ignore_swap -eq 1 ] then # Use only the amount of free ram @@ -53,20 +55,46 @@ ynh_available_ram () { # Use only the amount of free swap ram=$total_swap fi + else + echo "Uhoh, you should choose --free or --total when using ynh_get_ram" >&2 + ram=0 fi - if [ -n "$required" ] - then - # Return 1 if the amount of ram isn't enough. - if [ $ram -lt $required ] - then - return 1 - else - return 0 - fi - - # If no RAM is required, return the amount of available ram. - else - echo $ram - fi + echo $ram +} + +# Return 0 or 1 depending if the system has a given amount of RAM+swap free or total +# +# usage: ynh_require_ram [--amount=RAM required in Mb] [--free|--total] [--ignore_swap|--only_swap] +# | arg: -a, --amount - The amount to require, in Mb +# | arg: -f, --free - Count free RAM+swap +# | arg: -t, --total - Count total RAM+swap +# | arg: -s, --ignore_swap - Ignore swap, consider only real RAM +# | arg: -o, --only_swap - Ignore real RAM, consider only swap +ynh_require_ram () { + # Declare an array to define the options of this helper. + declare -Ar args_array=( [a]=amount= [f]=free [t]=total [s]=ignore_swap [o]=only_swap ) + local amount + local free + local total + local ignore_swap + local only_swap + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + amount=${amount:-0} + # Dunno if that's the right way to do, but that's some black magic to be able to + # forward the bool args to ynh_get_ram easily? + free=${free:+--free} + total=${total:+--total} + ignore_swap=${ignore_swap:+--ignore_swap} + only_swap=${only_swap:+--only_swap} + + local ram=$(ynh_get_ram $free $total $ignore_swap $only_swap) + + if [ $ram -lt $amount ] + then + return 1 + else + return 0 + fi } From fdc0ecf6e5346b629a48d0f50bd72916314b966c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 6 Apr 2020 00:20:16 +0200 Subject: [PATCH 0835/3170] Propagate change in RAM helper to php helper where it's used --- data/helpers.d/php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/php b/data/helpers.d/php index 4f5e63dfd..78c4f1bc0 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -309,7 +309,7 @@ ynh_get_scalable_phpfpm () { fi # Get the total of RAM available, except swap. - local max_ram=$(ynh_check_ram --no_swap) + local max_ram=$(ynh_get_ram --total --ignore_swap) at_least_one() { # Do not allow value below 1 From 85cab0edbe2c8463d060b689a9676622fddd9c94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Sat, 4 Apr 2020 00:36:21 +0200 Subject: [PATCH 0836/3170] Fix some typo --- src/yunohost/domain.py | 4 ++-- src/yunohost/permission.py | 14 +++++++------- src/yunohost/user.py | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index dfa8f4919..2082949f0 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -418,7 +418,7 @@ def _check_and_normalize_permission_path(url): import re, sre_constants # Uri without domain - if url.startwith('re:/'): + if url.startswith('re:/'): regex = url[4:] # check regex try: @@ -433,7 +433,7 @@ def _check_and_normalize_permission_path(url): # Uri with domain domains = domain_list()['domains'] - if url.startwith('re:'): + if url.startswith('re:'): if '/' not in url: raise YunohostError('regex_with_only_domain') domain = url[3:].split('/')[0] diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 29872d044..f2c6eb36d 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -169,7 +169,7 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, if "visitors" not in new_allowed_groups or len(new_allowed_groups) >= 3: logger.warning(m18n.n("permission_currently_allowed_for_all_users")) - if existing_permission['url'].startswith('re:') and show_tile: + if existing_permission['url'] and existing_permission['url'].startswith('re:') and show_tile: logger.warning(m18n.n('regex_incompatible_with_tile', regex=existing_permission['url'], permission=permission)) # Commit the new allowed group list @@ -334,7 +334,7 @@ def permission_url(operation_logger, permission, """ from yunohost.app import app_setting from yunohost.utils.ldap import _get_ldap_interface - from yunohost.domain import _check_and_normalize_permission_path, domain_url_available + from yunohost.domain import _check_and_normalize_permission_path, _get_conflicting_apps ldap = _get_ldap_interface() # By default, manipulate main permission @@ -359,7 +359,7 @@ def permission_url(operation_logger, permission, else: url = _check_and_normalize_permission_path(url) domain, path = _get_full_url(url, app_main_path).split('/', 1) - conflicts = _get_conflicting_apps(domain, path, ignore_app=permission.spit('.')[0]) + conflicts = _get_conflicting_apps(domain, path, ignore_app=permission.split('.')[0]) if url.startswith('re:') and existing_permission['show_tile']: logger.warning(m18n.n('regex_incompatible_with_tile', regex=url, permission=permission)) @@ -383,9 +383,9 @@ def permission_url(operation_logger, permission, if ur in current_additional_urls: logger.warning(m18n.n('additional_urls_already_added', permission=permission, url=url)) else: - new_url = _check_and_normalize_permission_path(new_url) - domain, path = _get_full_url(new_url, app_main_path).split('/', 1) - conflicts = _get_conflicting_apps(domain, path, ignore_app=permission.spit('.')[0]) + ur = _check_and_normalize_permission_path(ur) + domain, path = _get_full_url(ur, app_main_path).split('/', 1) + conflicts = _get_conflicting_apps(domain, path, ignore_app=permission.split('.')[0]) if conflicts: apps = [] @@ -398,7 +398,7 @@ def permission_url(operation_logger, permission, )) raise YunohostError('app_location_unavailable', apps="\n".join(apps)) - new_additional_urls += [new_url] + new_additional_urls += [ur] if remove_url: for ur in remove_url: diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 739fbcb02..ff31bbb62 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -462,7 +462,7 @@ def user_info(username): if service_status("dovecot")["status"] != "running": logger.warning(m18n.n('mailbox_used_space_dovecot_down')) - elif username not in user_permission_list(full=True, full_path=False)["permissions"]["mail.main"]["corresponding_users"]: + 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] From 353b29613d280ab2e2119fcc2f041c840d3374a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Mon, 6 Apr 2020 10:42:12 +0200 Subject: [PATCH 0837/3170] Impove _get_full_url management --- .../data_migrations/0015_extends_permissions_features_1.py | 2 +- src/yunohost/permission.py | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/yunohost/data_migrations/0015_extends_permissions_features_1.py b/src/yunohost/data_migrations/0015_extends_permissions_features_1.py index 83222fd0a..b470b7a6f 100644 --- a/src/yunohost/data_migrations/0015_extends_permissions_features_1.py +++ b/src/yunohost/data_migrations/0015_extends_permissions_features_1.py @@ -35,7 +35,7 @@ class MyMigration(Migration): logger.info(m18n.n("migration_0015_add_new_attributes_in_ldap")) ldap = _get_ldap_interface() - permission_list = user_permission_list(short=True)["permissions"] + permission_list = user_permission_list(short=True, full_path=False)["permissions"] for permission in permission_list: if permission.split('.')[0] in SYSTEM_PERMS: diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index f2c6eb36d..2128f114d 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -82,8 +82,8 @@ def user_permission_list(short=False, full=False, ignore_system_perms=False, ful permissions[name]["show_tile"] = infos.get("showTile", [False])[0] == "TRUE" permissions[name]["protected"] = infos.get("isProtected", [False])[0] == "TRUE" if full_path and name.split(".")[0] in apps_main_path: - permissions[name]["url"] = _get_full_url(infos.get("URL", [None])[0], apps_main_path[name.split('.')[0]]) - permissions[name]["additional_urls"] = [_get_full_url(url, apps_main_path[name.split('.')[0]]) for url in infos.get("additionalUrls", [None])] + permissions[name]["url"] = _get_full_url(infos["URL"][0], apps_main_path[name.split('.')[0]]) if "URL" in infos else None + permissions[name]["additional_urls"] = [_get_full_url(url, apps_main_path[name.split('.')[0]]) for url in infos.get("additionalUrls", [None]) if url] else: permissions[name]["url"] = infos.get("URL", [None])[0] permissions[name]["additional_urls"] = infos.get("additionalUrls", [None]) @@ -608,8 +608,6 @@ def _update_ldap_group_permission(permission, allowed, def _get_full_url(url, app_main_path): - if url is None: - return None if url.startswith('/'): return app_main_path + url.rstrip("/") if url.startswith('re:/'): From 69c0d33bb1f6a8f506a51321fabbd68f14dbf8ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Mon, 6 Apr 2020 11:20:01 +0200 Subject: [PATCH 0838/3170] Fix app_ssowatconf --- 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 a19871699..3d3d5ca34 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1325,7 +1325,7 @@ def app_ssowatconf(): permissions[perm_name] = { "users": perm_info['corresponding_users'], "label": perm_info['label'], - "show_tile": perm_info['show_tile'] if not perm_info["url"].startswith('re:') else False, + "show_tile": perm_info['show_tile'] if perm_info['url'] and not perm_info["url"].startswith('re:') else False, "auth_header": perm_info['auth_header'], "public": "visitors" in perm_info["allowed"], "uris": uris From 8443e9c4488539a6f06fd25fa5d484575210d1f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Mon, 6 Apr 2020 11:20:18 +0200 Subject: [PATCH 0839/3170] Fix url normalization --- src/yunohost/domain.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 2082949f0..06bfedf1a 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -428,7 +428,7 @@ def _check_and_normalize_permission_path(url): return url if url.startswith('/'): - return url.rstrip("/") + return '/' + url.strip("/") # Uri with domain domains = domain_list()['domains'] @@ -456,7 +456,7 @@ def _check_and_normalize_permission_path(url): if '/' in url: path = url.split('/', 1)[1].rstrip('/') - return domain + path + return domain + '/' + path else: return domain From 3234b14b78657ccf36fba2e38d5aa0209d1cd453 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Mon, 6 Apr 2020 12:54:05 +0200 Subject: [PATCH 0840/3170] Update data/helpers.d/php Co-Authored-By: Alexandre Aubin --- data/helpers.d/php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/php b/data/helpers.d/php index 817be7f4d..24314b52f 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -246,7 +246,7 @@ ynh_remove_php () { # medium - Low usage, few people or/and publicly accessible. # high - High usage, frequently visited website. # -# | arg: -p, --print - Print the result +# | arg: -p, --print - Print the result (intended for debug purpose only when packaging the app) # # # The footprint of the service will be used to defined the maximum footprint we can allow, which is half the maximum RAM. From 0828eec7298c405fba8a1e98de3b21450f69a628 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Mon, 6 Apr 2020 11:40:35 +0200 Subject: [PATCH 0841/3170] Fix postinstall and backup restore --- data/other/ldap_scheme.yml | 6 ++++++ src/yunohost/backup.py | 7 ++++++- .../data_migrations/0015_extends_permissions_features_1.py | 2 +- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/data/other/ldap_scheme.yml b/data/other/ldap_scheme.yml index 769de0b2e..8fdded8ac 100644 --- a/data/other/ldap_scheme.yml +++ b/data/other/ldap_scheme.yml @@ -73,6 +73,9 @@ depends_children: - permissionYnh groupPermission: - "cn=all_users,ou=groups,dc=yunohost,dc=org" + authHeader: "FALSE" + label: "Mail" + showTile: "FALSE" isProtected: "TRUE" cn=xmpp.main,ou=permission: cn: xmpp.main @@ -82,4 +85,7 @@ depends_children: - permissionYnh groupPermission: - "cn=all_users,ou=groups,dc=yunohost,dc=org" + authHeader: "FALSE" + label: "XMPP" + showTile: "FALSE" isProtected: "TRUE" diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index f8a2f54ba..6a287b631 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -1247,7 +1247,12 @@ class RestoreManager(): for permission_name, permission_infos in old_apps_permission.items(): app_name = permission_name.split(".")[0] if _is_installed(app_name): - permission_create(permission_name, url=permission_infos["url"], allowed=permission_infos["allowed"], + permission_create(permission_name, allowed=permission_infos["allowed"], + url=permission_infos["url"], + additional_urls=permission_infos['additional_urls'], + auth_header=permission_infos['auth_header'], + label=permission_infos['label'], + show_tile=permission_infos['show_tile'], protected=permission_infos["protected"], sync_perm=False) permission_sync_to_user() diff --git a/src/yunohost/data_migrations/0015_extends_permissions_features_1.py b/src/yunohost/data_migrations/0015_extends_permissions_features_1.py index b470b7a6f..a9abf3403 100644 --- a/src/yunohost/data_migrations/0015_extends_permissions_features_1.py +++ b/src/yunohost/data_migrations/0015_extends_permissions_features_1.py @@ -41,7 +41,7 @@ class MyMigration(Migration): if permission.split('.')[0] in SYSTEM_PERMS: ldap.update('cn=%s,ou=permission' % permission, { 'authHeader': ["FALSE"], - 'label': [permission.split('.')[0]], + 'label': [permission.split('.')[0].title()], 'showTile': ["FALSE"], 'isProtected': ["TRUE"], }) From 3bd6a7aa2983f19237939ade661ebee1780461ed Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 6 Apr 2020 15:39:40 +0200 Subject: [PATCH 0842/3170] Explicitly depends on lsb-release --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index aed123246..4b3837c1b 100644 --- a/debian/control +++ b/debian/control @@ -15,7 +15,7 @@ Depends: ${python:Depends}, ${misc:Depends} , python-psutil, python-requests, python-dnspython, python-openssl , python-apt, python-miniupnpc, python-dbus, python-jinja2 , python-toml - , apt-transport-https + , apt, apt-transport-https, lsb-release , dnsutils, bind9utils, unzip, git, curl, cron, wget, jq , ca-certificates, netcat-openbsd, iproute2 , mariadb-server, php-mysql | php-mysqlnd From 4d99cbe87075fc9180b49f12ef45d51db2d3892d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 6 Apr 2020 16:54:25 +0200 Subject: [PATCH 0843/3170] Add ref for security headers --- data/templates/nginx/security.conf.inc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/data/templates/nginx/security.conf.inc b/data/templates/nginx/security.conf.inc index 272a29e26..28d12055b 100644 --- a/data/templates/nginx/security.conf.inc +++ b/data/templates/nginx/security.conf.inc @@ -20,6 +20,9 @@ ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECD #ssl_dhparam /etc/ssl/private/dh2048.pem; {% endif %} +# Follows the Web Security Directives from the Mozilla Dev Lab and the Mozilla Obervatory + Partners +# https://wiki.mozilla.org/Security/Guidelines/Web_Security +# https://observatory.mozilla.org/ more_set_headers "Content-Security-Policy : upgrade-insecure-requests"; more_set_headers "Content-Security-Policy-Report-Only : default-src https: data: 'unsafe-inline' 'unsafe-eval'"; more_set_headers "X-Content-Type-Options : nosniff"; From 22b9565eb72161e1a66db5980aad8ad56d220a3c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 6 Apr 2020 16:56:53 +0200 Subject: [PATCH 0844/3170] Forgot to check that these headers are different from the default in security.conf ... maybe we want to keep them as is? Not clear why they have different values tan the domain configs... --- data/templates/nginx/yunohost_admin.conf | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/data/templates/nginx/yunohost_admin.conf b/data/templates/nginx/yunohost_admin.conf index 63d466ecd..3df838c4a 100644 --- a/data/templates/nginx/yunohost_admin.conf +++ b/data/templates/nginx/yunohost_admin.conf @@ -20,6 +20,10 @@ server { ssl_certificate /etc/yunohost/certs/yunohost.org/crt.pem; ssl_certificate_key /etc/yunohost/certs/yunohost.org/key.pem; + 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 12d7bad16bf6395918547718981bd80dc7297b1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Mon, 6 Apr 2020 17:01:38 +0200 Subject: [PATCH 0845/3170] Create dummy apps settings for permission with url and add possibility to manage multiple domain in permission tests --- src/yunohost/tests/test_permission.py | 87 +++++++++++++++++++++++---- 1 file changed, 76 insertions(+), 11 deletions(-) diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index bf8ab61ab..ab2b4fefe 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -1,17 +1,22 @@ import requests import pytest +import string +import os +import json +import shutil from conftest import message, raiseYunohostError -from yunohost.app import app_install, app_upgrade, app_remove, app_change_url, app_list, app_map, _installed_apps +from yunohost.app import app_install, app_upgrade, app_remove, app_change_url, app_list, app_map, _installed_apps, APPS_SETTING_PATH, _set_app_settings, _get_app_settings from yunohost.user import user_list, user_create, user_delete, \ user_group_list, user_group_delete from yunohost.permission import user_permission_update, user_permission_list, user_permission_reset, \ permission_create, permission_delete, permission_url -from yunohost.domain import _get_maindomain +from yunohost.domain import _get_maindomain, domain_add, domain_remove, domain_list # Get main domain maindomain = "" +other_domains = [] dummy_password = "test123Ynh" # Dirty patch of DNS resolution. Force the DNS to 127.0.0.1 address even if dnsmasq have the public address. @@ -20,6 +25,44 @@ import socket prv_getaddrinfo = socket.getaddrinfo +def _permission_create_with_dummy_app(permission, allowed=None, + url=None, additional_urls=None, auth_header=True, + label=None, show_tile=False, + protected=True, sync_perm=True, + domain=None, path=None): + app = permission.split('.')[0] + if app not in _installed_apps(): + app_setting_path = os.path.join(APPS_SETTING_PATH, app) + if not os.path.exists(app_setting_path): + os.makedirs(app_setting_path) + settings = {'id': app, 'dummy_permission_app': True} + if domain: + settings['domain'] = domain + if path: + settings['path'] = path + _set_app_settings(app, settings) + + with open(os.path.join(APPS_SETTING_PATH, app, 'manifest.json'), 'w') as f: + json.dump({ + "name": app, + "id": app, + "description": { + "en": "Dummy app to test permissions" + } + }, f) + permission_create(permission=permission, allowed=allowed, url=url, additional_urls=additional_urls, auth_header=auth_header, + label=label, show_tile=show_tile, protected=protected, sync_perm=sync_perm) + + +def _clear_dummy_app_settings(): + # Clean dummy app settings + for app in _installed_apps(): + if _get_app_settings(app).get('dummy_permission_app', False): + app_setting_path = os.path.join(APPS_SETTING_PATH, app) + if os.path.exists(app_setting_path): + shutil.rmtree(app_setting_path) + + def clean_user_groups_permission(): for u in user_list()['users']: user_delete(u) @@ -36,10 +79,18 @@ def clean_user_groups_permission(): def setup_function(function): clean_user_groups_permission() + markers = {m.name: {'args':m.args, 'kwargs':m.kwargs} for m in function.__dict__.get("pytestmark",[])} global maindomain + global other_domains maindomain = _get_maindomain() + if "other_domains" in markers: + other_domains = ["domain_%s.dev" % string.ascii_lowercase[number] for number in range(markers['other_domains']['kwargs']['number'])] + for domain in other_domains: + if domain not in domain_list()['domains']: + domain_add(domain) + # Dirty patch of DNS resolution. Force the DNS to 127.0.0.1 address even if dnsmasq have the public address. # Mainly used for 'can_access_webpage' function dns_cache = {(maindomain, 443, 0, 1): [(2, 1, 6, '', ('127.0.0.1', 443))]} @@ -54,14 +105,22 @@ def setup_function(function): user_create("alice", "Alice", "White", "alice@" + maindomain, dummy_password) user_create("bob", "Bob", "Snow", "bob@" + maindomain, dummy_password) - permission_create("wiki.main", url="/", allowed=["all_users"], protected=False, sync_perm=False) - permission_create("blog.main", allowed=["all_users"], protected=False, sync_perm=False) - permission_create("blog.api", allowed=["visitors"], protected=True, sync_perm=False) + _permission_create_with_dummy_app(permission="wiki.main", url="/", allowed=["all_users"], protected=False, sync_perm=False, + domain=maindomain, path='/wiki') + _permission_create_with_dummy_app(permission="blog.main", allowed=["all_users"], protected=False, sync_perm=False) + _permission_create_with_dummy_app(permission="blog.api", allowed=["visitors"], protected=True, sync_perm=False) user_permission_update("blog.main", remove="all_users", add="alice") def teardown_function(function): clean_user_groups_permission() + global other_domains + for domain in other_domains: + domain_remove(domain) + other_domains = [] + + _clear_dummy_app_settings() + try: app_remove("permissions_app") except: @@ -438,9 +497,10 @@ def test_permission_remove_url(): # +@pytest.mark.other_domains(number=1) def test_permission_app_install(): app_install("./tests/apps/permissions_app_ynh", - args="domain=%s&path=%s&is_public=0&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True) + args="domain=%s&domain_2=%s&path=%s&is_public=0&admin=%s" % (maindomain, other_domains[0], "/urlpermissionapp", "alice"), force=True) res = user_permission_list(full=True, full_path=False)['permissions'] assert "permissions_app.main" in res @@ -466,9 +526,10 @@ def test_permission_app_install(): assert maindomain + "/urlpermissionapp" in app_map(user="bob").keys() +@pytest.mark.other_domains(number=1) def test_permission_app_remove(): app_install("./tests/apps/permissions_app_ynh", - args="domain=%s&path=%s&is_public=0&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True) + args="domain=%s&domain_2=%s&path=%s&is_public=0&admin=%s" % (maindomain, other_domains[0], "/urlpermissionapp", "alice"), force=True) app_remove("permissions_app") # Check all permissions for this app got deleted @@ -476,9 +537,10 @@ def test_permission_app_remove(): assert not any(p.startswith("permissions_app.") for p in res.keys()) +@pytest.mark.other_domains(number=1) def test_permission_app_change_url(): app_install("./tests/apps/permissions_app_ynh", - args="domain=%s&path=%s&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True) + args="domain=%s&domain_2=%s&path=%s&admin=%s" % (maindomain, other_domains[0], "/urlpermissionapp", "alice"), force=True) # FIXME : should rework this test to look for differences in the generated app map / app tiles ... res = user_permission_list(full=True, full_path=False)['permissions'] @@ -494,9 +556,10 @@ def test_permission_app_change_url(): assert res['permissions_app.dev']['url'] == "/dev" +@pytest.mark.other_domains(number=1) def test_permission_protection_management_by_helper(): app_install("./tests/apps/permissions_app_ynh", - args="domain=%s&path=%s&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True) + args="domain=%s&domain_2=%s&path=%s&admin=%s" % (maindomain, other_domains[0], "/urlpermissionapp", "alice"), force=True) res = user_permission_list(full=True)['permissions'] assert res['permissions_app.main']['protected'] == False @@ -511,10 +574,11 @@ def test_permission_protection_management_by_helper(): assert res['permissions_app.dev']['protected'] == True +@pytest.mark.other_domains(number=1) def test_permission_app_propagation_on_ssowat(): app_install("./tests/apps/permissions_app_ynh", - args="domain=%s&path=%s&is_public=1&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True) + args="domain=%s&domain_2=%s&path=%s&is_public=1&admin=%s" % (maindomain, other_domains[0], "/urlpermissionapp", "alice"), force=True) res = user_permission_list(full=True)['permissions'] assert "visitors" in res['permissions_app.main']['allowed'] @@ -541,10 +605,11 @@ def test_permission_app_propagation_on_ssowat(): assert not can_access_webpage(app_webroot+"/admin", logged_as="bob") +@pytest.mark.other_domains(number=1) def test_permission_legacy_app_propagation_on_ssowat(): app_install("./tests/apps/legacy_app_ynh", - args="domain=%s&path=%s" % (maindomain, "/legacy"), force=True) + args="domain=%s&domain_2=%s&path=%s" % (maindomain, other_domains[0], "/legacy"), force=True) # App is configured as public by default using the legacy unprotected_uri mechanics # It should automatically be migrated during the install From 3a7b93d8aac481f41f3dcea3b4e0b6409b0fc0c9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 5 Apr 2020 18:12:24 +0200 Subject: [PATCH 0846/3170] Get rid of domain-specific acme-challenge snippet, use a single snippet including in every conf --- data/hooks/conf_regen/15-nginx | 15 ++++++ .../nginx/plain/acme-challenge.conf.inc | 5 ++ data/templates/nginx/server.tpl.conf | 2 + locales/en.json | 1 - src/yunohost/certificate.py | 47 ------------------- 5 files changed, 22 insertions(+), 48 deletions(-) create mode 100644 data/templates/nginx/plain/acme-challenge.conf.inc diff --git a/data/hooks/conf_regen/15-nginx b/data/hooks/conf_regen/15-nginx index 11e5f596c..90d99ff5e 100755 --- a/data/hooks/conf_regen/15-nginx +++ b/data/hooks/conf_regen/15-nginx @@ -110,6 +110,21 @@ do_post_regen() { mkdir -p "/etc/nginx/conf.d/${domain}.d" done + # Get rid of legacy lets encrypt snippets + for domain in $domain_list; do + # If the legacy letsencrypt / acme-challenge domain-specific snippet is still there + if [ -e /etc/nginx/conf.d/${domain}.d/000-acmechallenge.conf ] + then + # And if we're effectively including the new domain-independant snippet now + if grep -q "include /etc/nginx/conf.d/acme-challenge.conf.inc;" /etc/nginx/conf.d/${domain}.conf + then + # Delete the old domain-specific snippet + rm /etc/nginx/conf.d/${domain}.d/000-acmechallenge.conf + fi + fi + done + + # Reload nginx configuration pgrep nginx && service nginx reload } diff --git a/data/templates/nginx/plain/acme-challenge.conf.inc b/data/templates/nginx/plain/acme-challenge.conf.inc new file mode 100644 index 000000000..aae3e0eb3 --- /dev/null +++ b/data/templates/nginx/plain/acme-challenge.conf.inc @@ -0,0 +1,5 @@ +location ^~ '/.well-known/acme-challenge/' +{ + default_type "text/plain"; + alias /tmp/acme-challenge-public/; +} diff --git a/data/templates/nginx/server.tpl.conf b/data/templates/nginx/server.tpl.conf index 6316960c4..485079883 100644 --- a/data/templates/nginx/server.tpl.conf +++ b/data/templates/nginx/server.tpl.conf @@ -10,6 +10,8 @@ server { access_by_lua_file /usr/share/ssowat/access.lua; + include /etc/nginx/conf.d/acme-challenge.conf.inc; + include /etc/nginx/conf.d/{{ domain }}.d/*.conf; location /yunohost/admin { diff --git a/locales/en.json b/locales/en.json index 567b6a460..f6aa35f67 100644 --- a/locales/en.json +++ b/locales/en.json @@ -120,7 +120,6 @@ "certmanager_cert_renew_success": "Let's Encrypt certificate renewed for the domain '{domain:s}'", "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_conflicting_nginx_file": "Could not prepare domain for ACME challenge: the NGINX configuration file {filepath:s} is conflicting and should be removed first", "certmanager_couldnt_fetch_intermediate_cert": "Timed out when trying to fetch intermediate certificate from Let's Encrypt. Certificate installation/renewal aborted—please try again later.", "certmanager_domain_cert_not_selfsigned": "The certificate for domain {domain:s} is not self-signed. Are you sure you want to replace it? (Use '--force' 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.)", diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 5fae59060..fd792ccae 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -285,7 +285,6 @@ def _certificate_install_letsencrypt(domain_list, force=False, no_checks=False, operation_logger.start() - _configure_for_acme_challenge(domain) _fetch_and_enable_new_certificate(domain, staging, no_checks=no_checks) _install_cron(no_checks=no_checks) @@ -468,52 +467,6 @@ Subject: %s smtp.quit() -def _configure_for_acme_challenge(domain): - - nginx_conf_folder = "/etc/nginx/conf.d/%s.d" % domain - nginx_conf_file = "%s/000-acmechallenge.conf" % nginx_conf_folder - - nginx_configuration = ''' -location ^~ '/.well-known/acme-challenge/' -{ - default_type "text/plain"; - alias %s; -} - ''' % WEBROOT_FOLDER - - # Check there isn't a conflicting file for the acme-challenge well-known - # uri - for path in glob.glob('%s/*.conf' % nginx_conf_folder): - - if path == nginx_conf_file: - continue - - with open(path) as f: - contents = f.read() - - if '/.well-known/acme-challenge' in contents: - raise YunohostError('certmanager_conflicting_nginx_file', filepath=path) - - # Write the conf - if os.path.exists(nginx_conf_file): - logger.debug( - "Nginx configuration file for ACME challenge already exists for domain, skipping.") - return - - logger.debug( - "Adding Nginx configuration file for Acme challenge for domain %s.", domain) - - with open(nginx_conf_file, "w") as f: - f.write(nginx_configuration) - - # Assume nginx conf is okay, and reload it - # (FIXME : maybe add a check that it is, using nginx -t, haven't found - # any clean function already implemented in yunohost to do this though) - _run_service_command("reload", "nginx") - - app_ssowatconf() - - def _check_acme_challenge_configuration(domain): # Check nginx conf file exists nginx_conf_folder = "/etc/nginx/conf.d/%s.d" % domain From ae3badda9eb02c52dcc0440528c8e46ca8d889d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Mon, 6 Apr 2020 17:47:07 +0200 Subject: [PATCH 0847/3170] Fix _update_ldap_group_permission --- src/yunohost/permission.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 2128f114d..858bee614 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -562,6 +562,8 @@ def _update_ldap_group_permission(permission, allowed, if protected is None: protected = existing_permission["protected"] + allowed = [allowed] if not isinstance(allowed, list) else allowed + # Guarantee uniqueness of all values, which would otherwise make ldap.update angry. allowed = set(allowed) From a34eebf9cd6f02a49456246c88816c9952b45fdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Mon, 6 Apr 2020 17:32:27 +0200 Subject: [PATCH 0848/3170] Fix tests permissions --- src/yunohost/tests/test_permission.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index ab2b4fefe..269148292 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -107,9 +107,9 @@ def setup_function(function): user_create("bob", "Bob", "Snow", "bob@" + maindomain, dummy_password) _permission_create_with_dummy_app(permission="wiki.main", url="/", allowed=["all_users"], protected=False, sync_perm=False, domain=maindomain, path='/wiki') - _permission_create_with_dummy_app(permission="blog.main", allowed=["all_users"], protected=False, sync_perm=False) - _permission_create_with_dummy_app(permission="blog.api", allowed=["visitors"], protected=True, sync_perm=False) - user_permission_update("blog.main", remove="all_users", add="alice") + _permission_create_with_dummy_app(permission="blog.main", allowed=["all_users"], protected=False, sync_perm=False, + domain=maindomain, path='/blog') + _permission_create_with_dummy_app(permission="blog.api", allowed=["visitors"], protected=True, sync_perm=True) def teardown_function(function): @@ -287,8 +287,10 @@ def test_permission_list(): assert res['blog.main']['allowed'] == ["alice"] assert set(res['wiki.main']['corresponding_users']) == set(["alice", "bob"]) assert res['blog.main']['corresponding_users'] == ["alice"] - assert res['wiki.main']['url'] == "/" + assert res['wiki.main']['url'] == maindomain + "/wiki" + res = user_permission_list(full=True, full_path=False)['permissions'] + assert res['wiki.main']['url'] == "/" # # Create - Remove functions # @@ -460,30 +462,30 @@ def test_permission_update_permission_that_doesnt_exist(mocker): def test_permission_protected_update(mocker): res = user_permission_list(full=True)['permissions'] - assert res['blog.api']['allowed'] == ["visitors", "all_users"] + assert res['blog.api']['allowed'] == ["visitors"] with raiseYunohostError(mocker, "permission_protected"): user_permission_update("blog.api", remove="visitors") res = user_permission_list(full=True)['permissions'] - assert res['blog.api']['allowed'] == ["visitors", "all_users"] + assert res['blog.api']['allowed'] == ["visitors"] user_permission_update("blog.api", remove="visitors", force=True) res = user_permission_list(full=True)['permissions'] - assert res['blog.api']['allowed'] == ["all_users"] + assert res['blog.api']['allowed'] == [] with raiseYunohostError(mocker, "permission_protected"): user_permission_update("blog.api", add="visitors") res = user_permission_list(full=True)['permissions'] - assert res['blog.api']['allowed'] == ["all_users"] + assert res['blog.api']['allowed'] == [] # Permission url management def test_permission_redefine_url(): permission_url("blog.main", url="/pwet") - res = user_permission_list(full=True)['permissions'] + res = user_permission_list(full=True, full_path=False)['permissions'] assert res["blog.main"]["url"] == "/pwet" def test_permission_remove_url(): From 748dcfd8c5f869dd6d78fe471c9a1eb30d1a3334 Mon Sep 17 00:00:00 2001 From: pitchum Date: Sat, 4 Apr 2020 14:36:01 +0200 Subject: [PATCH 0849/3170] Setup XMPP components for each domain, not only the main domain. --- data/hooks/conf_regen/12-metronome | 8 +-- data/templates/metronome/domain.tpl.cfg.lua | 56 +++++++++++++++++++++ data/templates/metronome/metronome.cfg.lua | 47 ----------------- src/yunohost/certificate.py | 16 +++--- 4 files changed, 67 insertions(+), 60 deletions(-) diff --git a/data/hooks/conf_regen/12-metronome b/data/hooks/conf_regen/12-metronome index 5c9c67f11..903e9fb2e 100755 --- a/data/hooks/conf_regen/12-metronome +++ b/data/hooks/conf_regen/12-metronome @@ -48,11 +48,11 @@ do_post_regen() { # create metronome directories for domains for domain in $domain_list; do mkdir -p "/var/lib/metronome/${domain//./%2e}/pep" + # http_upload directory must be writable by metronome and readable by nginx + mkdir -p "/var/xmpp-upload/${domain}/upload" + chmod g+s "/var/xmpp-upload/${domain}/upload" + chown -R metronome:www-data "/var/xmpp-upload/${domain}" done - # http_upload directory must be writable by metronome and readable by nginx - mkdir -p "/var/xmpp-upload/${main_domain}/upload" - chmod g+s "/var/xmpp-upload/${main_domain}/upload" - chown -R metronome:www-data "/var/xmpp-upload/${main_domain}" # fix some permissions diff --git a/data/templates/metronome/domain.tpl.cfg.lua b/data/templates/metronome/domain.tpl.cfg.lua index e7f6bcef7..aa2f45e5a 100644 --- a/data/templates/metronome/domain.tpl.cfg.lua +++ b/data/templates/metronome/domain.tpl.cfg.lua @@ -1,4 +1,5 @@ VirtualHost "{{ domain }}" + enable = true ssl = { key = "/etc/yunohost/certs/{{ domain }}/key.pem"; certificate = "/etc/yunohost/certs/{{ domain }}/crt.pem"; @@ -13,3 +14,58 @@ VirtualHost "{{ domain }}" namefield = "cn", }, } + + -- Discovery items + disco_items = { + { "muc.{{ domain }}" }, + { "pubsub.{{ domain }}" }, + { "jabber.{{ domain }}" }, + { "vjud.{{ domain }}" }, + { "xmpp-upload.{{ domain }}" }, + }; + +-- contact_info = { +-- abuse = { "mailto:abuse@{{ domain }}", "xmpp:admin@{{ domain }}" }; +-- admin = { "mailto:root@{{ domain }}", "xmpp:admin@{{ domain }}" }; +-- }; + +------ Components ------ +-- You can specify components to add hosts that provide special services, +-- like multi-user conferences, and transports. + +---Set up a MUC (multi-user chat) room server +Component "muc.{{ domain }}" "muc" + name = "{{ domain }} Chatrooms" + + modules_enabled = { + "muc_limits"; + "muc_log"; + "muc_log_mam"; + "muc_log_http"; + "muc_vcard"; + } + + muc_event_rate = 0.5 + muc_burst_factor = 10 + +---Set up a PubSub server +Component "pubsub.{{ domain }}" "pubsub" + name = "{{ domain }} Publish/Subscribe" + + unrestricted_node_creation = true -- Anyone can create a PubSub node (from any server) + +---Set up a HTTP Upload service +Component "xmpp-upload.{{ domain }}" "http_upload" + name = "{{ domain }} Sharing Service" + + http_file_path = "/var/xmpp-upload/{{ domain }}/upload" + http_external_url = "https://xmpp-upload.{{ domain }}:443" + http_file_base_path = "/upload" + http_file_size_limit = 6*1024*1024 + http_file_quota = 60*1024*1024 + http_upload_file_size_limit = 100 * 1024 * 1024 -- bytes + http_upload_quota = 10 * 1024 * 1024 * 1024 -- bytes + +---Set up a VJUD service +Component "vjud.{{ domain }}" "vjud" + vjud_disco_name = "{{ domain }} User Directory" diff --git a/data/templates/metronome/metronome.cfg.lua b/data/templates/metronome/metronome.cfg.lua index b35684add..c1ea83281 100644 --- a/data/templates/metronome/metronome.cfg.lua +++ b/data/templates/metronome/metronome.cfg.lua @@ -81,14 +81,6 @@ http_interfaces = { "127.0.0.1", "::1" } -- Enable IPv6 use_ipv6 = true --- Discovery items -disco_items = { - { "muc.{{ main_domain }}" }, - { "pubsub.{{ main_domain }}" }, - { "xmpp-upload.{{ main_domain }}" }, - { "vjud.{{ main_domain }}" } -}; - -- BOSH configuration (mod_bosh) consider_bosh_secure = true cross_domain_bosh = true @@ -119,45 +111,6 @@ log = { Component "localhost" "http" modules_enabled = { "bosh" } ----Set up a MUC (multi-user chat) room server -Component "muc.{{ main_domain }}" "muc" - name = "{{ main_domain }} Chatrooms" - - modules_enabled = { - "muc_limits"; - "muc_log"; - "muc_log_mam"; - "muc_log_http"; - "muc_vcard"; - } - - muc_event_rate = 0.5 - muc_burst_factor = 10 - ----Set up a PubSub server -Component "pubsub.{{ main_domain }}" "pubsub" - name = "{{ main_domain }} Publish/Subscribe" - - unrestricted_node_creation = true -- Anyone can create a PubSub node (from any server) - ----Set up a HTTP Upload service -Component "xmpp-upload.{{ main_domain }}" "http_upload" - name = "{{ main_domain }} Sharing Service" - - http_file_path = "/var/xmpp-upload/{{ main_domain }}/upload" - http_external_url = "https://xmpp-upload.{{ main_domain }}:443" - http_file_base_path = "/upload" - http_file_size_limit = 6*1024*1024 - http_file_quota = 60*1024*1024 - http_upload_file_size_limit = 100 * 1024 * 1024 -- bytes - http_upload_quota = 10 * 1024 * 1024 * 1024 -- bytes - - ----Set up a VJUD service -Component "vjud.{{ main_domain }}" "vjud" - ud_disco_name = "{{ main_domain }} User Directory" - - ----------- Virtual hosts ----------- -- You need to add a VirtualHost entry for each domain you wish Metronome to serve. -- Settings under each VirtualHost entry apply *only* to that host. diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 5fae59060..c6f520b4e 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -639,15 +639,13 @@ def _prepare_certificate_signing_request(domain, key_file, output_folder): # Set the domain csr.get_subject().CN = domain - from yunohost.domain import _get_maindomain - if domain == _get_maindomain(): - # Include xmpp-upload subdomain in subject alternate names - subdomain="xmpp-upload." + domain - try: - _dns_ip_match_public_ip(get_public_ip(), subdomain) - csr.add_extensions([crypto.X509Extension("subjectAltName", False, "DNS:" + subdomain)]) - except YunohostError: - logger.warning(m18n.n('certmanager_warning_subdomain_dns_record', subdomain=subdomain, domain=domain)) + # Include xmpp-upload subdomain in subject alternate names + subdomain="xmpp-upload." + domain + try: + _dns_ip_match_public_ip(get_public_ip(), subdomain) + csr.add_extensions([crypto.X509Extension("subjectAltName", False, "DNS:" + subdomain)]) + except YunohostError: + logger.warning(m18n.n('certmanager_warning_subdomain_dns_record', subdomain=subdomain, domain=domain)) # Set the key with open(key_file, 'rt') as f: From cf3b98b5237db1f67a28af7d9fd4f5852dc0a593 Mon Sep 17 00:00:00 2001 From: pitchum Date: Sat, 4 Apr 2020 15:06:44 +0200 Subject: [PATCH 0850/3170] Fix nginx config for xmpp-upload. --- data/templates/nginx/server.tpl.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/templates/nginx/server.tpl.conf b/data/templates/nginx/server.tpl.conf index 6316960c4..5a5176688 100644 --- a/data/templates/nginx/server.tpl.conf +++ b/data/templates/nginx/server.tpl.conf @@ -71,7 +71,7 @@ server { root /dev/null; location /upload/ { - alias /var/xmpp-upload/{{ domain }}/upload; + alias /var/xmpp-upload/{{ domain }}/upload/; # Pass all requests to metronome, except for GET and HEAD requests. limit_except GET HEAD { proxy_pass http://localhost:5290; From 22c88dc47e57980058265ae1083a5a8ef4284310 Mon Sep 17 00:00:00 2001 From: pitchum Date: Mon, 6 Apr 2020 20:38:42 +0200 Subject: [PATCH 0851/3170] Enable XMPP features only on "parent domains". --- data/actionsmap/yunohost.yml | 4 ++++ data/hooks/conf_regen/12-metronome | 2 +- src/yunohost/domain.py | 11 ++++++++--- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 3a4c9db97..cd1c4916f 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -399,6 +399,10 @@ domain: list: action_help: List domains api: GET /domains + arguments: + --exclude-subdomains: + help: Filter out domains that are obviously subdomains of other declared domains + action: store_true ### domain_add() add: diff --git a/data/hooks/conf_regen/12-metronome b/data/hooks/conf_regen/12-metronome index 903e9fb2e..25ccd40ac 100755 --- a/data/hooks/conf_regen/12-metronome +++ b/data/hooks/conf_regen/12-metronome @@ -43,7 +43,7 @@ do_post_regen() { # retrieve variables main_domain=$(cat /etc/yunohost/current_host) - domain_list=$(yunohost domain list --output-as plain --quiet) + domain_list=$(yunohost domain list --exclude-subdomains --output-as plain --quiet) # create metronome directories for domains for domain in $domain_list; do diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 23b5a4179..a1ac65b81 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -41,7 +41,7 @@ from yunohost.hook import hook_callback logger = getActionLogger('yunohost.domain') -def domain_list(): +def domain_list(exclude_subdomains=False): """ List domains @@ -49,16 +49,21 @@ def domain_list(): filter -- LDAP filter used to search offset -- Starting number for domain fetching limit -- Maximum number of domain fetched + exclude_subdomains -- Filter out domains that are subdomains of other declared domains """ from yunohost.utils.ldap import _get_ldap_interface ldap = _get_ldap_interface() - result = ldap.search('ou=domains,dc=yunohost,dc=org', 'virtualdomain=*', ['virtualdomain']) + result = [entry['virtualdomain'][0] for entry in ldap.search('ou=domains,dc=yunohost,dc=org', 'virtualdomain=*', ['virtualdomain'])] result_list = [] for domain in result: - result_list.append(domain['virtualdomain'][0]) + if exclude_subdomains: + parent_domain = domain.split(".", 1)[1] + if parent_domain in result: + continue + result_list.append(domain) return {'domains': result_list} From d563b6a04d006415130db337e345d5834bee3376 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Mon, 6 Apr 2020 23:13:50 +0200 Subject: [PATCH 0852/3170] Be more explicit about domain unknown --- locales/en.json | 1 + src/yunohost/app.py | 2 +- src/yunohost/domain.py | 10 +++++----- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/locales/en.json b/locales/en.json index 564c0b821..47425d0c3 100644 --- a/locales/en.json +++ b/locales/en.json @@ -234,6 +234,7 @@ "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_named_unknown": "Domain '{domain}' unknown", "domain_unknown": "Unknown domain", "domains_available": "Available domains:", "done": "Done", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 3d3d5ca34..c99255eb6 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1065,7 +1065,7 @@ def app_makedefault(operation_logger, app, domain=None): domain = app_domain operation_logger.related_to.append(('domain', domain)) elif domain not in domain_list()['domains']: - raise YunohostError('domain_unknown') + raise YunohostError('domain_named_unknown', domain=domain) operation_logger.start() if '/' in app_map(raw=True)[domain]: diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 06bfedf1a..c3579c805 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -153,7 +153,7 @@ def domain_remove(operation_logger, domain, force=False): from yunohost.utils.ldap import _get_ldap_interface if not force and domain not in domain_list()['domains']: - raise YunohostError('domain_unknown') + raise YunohostError('domain_named_unknown', domain=domain) # Check domain is not the main domain if domain == _get_maindomain(): @@ -260,7 +260,7 @@ def domain_main_domain(operation_logger, new_main_domain=None): # Check domain exists if new_main_domain not in domain_list()['domains']: - raise YunohostError('domain_unknown') + raise YunohostError('domain_named_unknown', domain=new_main_domain) operation_logger.related_to.append(('domain', new_main_domain)) operation_logger.start() @@ -329,7 +329,7 @@ def _get_conflicting_apps(domain, path, ignore_app=None): # Abort if domain is unknown if domain not in domain_list()['domains']: - raise YunohostError('domain_unknown') + raise YunohostError('domain_named_unknown', domain=domain) # This import cannot be put on top of file because it would create a # recursive import... @@ -440,7 +440,7 @@ def _check_and_normalize_permission_path(url): path = url[3:].split('/', 1)[1] if domain not in domains: - raise YunohostError('domain_unknown') + raise YunohostError('domain_named_unknown', domain=domain) try: re.compile(path) @@ -452,7 +452,7 @@ def _check_and_normalize_permission_path(url): else: domain = url.split('/')[0] if domain not in domains: - raise YunohostError('domain_unknown') + raise YunohostError('domain_named_unknown', domain=domain) if '/' in url: path = url.split('/', 1)[1].rstrip('/') From 5b09dabedcc8351b43e79a90645a81be11a9acc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Mon, 6 Apr 2020 23:30:31 +0200 Subject: [PATCH 0853/3170] Create domain before to restore backup --- src/yunohost/tests/test_backuprestore.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/yunohost/tests/test_backuprestore.py b/src/yunohost/tests/test_backuprestore.py index c097e208f..b4d4118f7 100644 --- a/src/yunohost/tests/test_backuprestore.py +++ b/src/yunohost/tests/test_backuprestore.py @@ -8,7 +8,7 @@ from conftest import message, raiseYunohostError from yunohost.app import app_install, app_remove, app_ssowatconf from yunohost.app import _is_installed from yunohost.backup import backup_create, backup_restore, backup_list, backup_info, backup_delete, _recursive_umount -from yunohost.domain import _get_maindomain +from yunohost.domain import _get_maindomain, domain_list, domain_add, domain_remove 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 @@ -31,7 +31,7 @@ def setup_function(function): assert len(backup_list()["archives"]) == 0 - markers = [m.name for m in function.__dict__.get("pytestmark",[])] + markers = {m.name: {'args':m.args, 'kwargs':m.kwargs} for m in function.__dict__.get("pytestmark",[])} if "with_wordpress_archive_from_2p4" in markers: add_archive_wordpress_from_2p4() @@ -65,6 +65,11 @@ def setup_function(function): "&admin=alice") assert app_is_installed("permissions_app") + if "with_custom_domain" in markers: + domain = markers['with_custom_domain']['args'][0] + if domain not in domain_list()['domains']: + domain_add(domain) + def teardown_function(function): @@ -74,7 +79,7 @@ def teardown_function(function): delete_all_backups() uninstall_test_apps_if_needed() - markers = [m.name for m in function.__dict__.get("pytestmark",[])] + markers = {m.name: {'args':m.args, 'kwargs':m.kwargs} for m in function.__dict__.get("pytestmark",[])} if "clean_opt_dir" in markers: shutil.rmtree("/opt/test_backup_output_directory") @@ -82,6 +87,9 @@ def teardown_function(function): if "alice" in user_list()["users"]: user_delete("alice") + if "with_custom_domain" in markers: + domain = markers['with_custom_domain']['args'][0] + domain_remove(domain) @pytest.fixture(autouse=True) def check_LDAP_db_integrity_call(): @@ -403,6 +411,7 @@ def test_backup_with_no_compress(mocker): # @pytest.mark.with_wordpress_archive_from_2p4 +@pytest.mark.with_custom_domain("yolo.test") def test_restore_app_wordpress_from_Ynh2p4(mocker): with message(mocker, "restore_complete"): @@ -411,6 +420,7 @@ def test_restore_app_wordpress_from_Ynh2p4(mocker): @pytest.mark.with_wordpress_archive_from_2p4 +@pytest.mark.with_custom_domain("yolo.test") def test_restore_app_script_failure_handling(monkeypatch, mocker): def custom_hook_exec(name, *args, **kwargs): @@ -464,6 +474,7 @@ def test_restore_app_not_in_backup(mocker): @pytest.mark.with_wordpress_archive_from_2p4 +@pytest.mark.with_custom_domain("yolo.test") def test_restore_app_already_installed(mocker): assert not _is_installed("wordpress") From 130ffab6ffb25aeefdf6532744c5370c4a1895e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Mon, 6 Apr 2020 21:14:29 +0200 Subject: [PATCH 0854/3170] Fix tests --- src/yunohost/tests/test_backuprestore.py | 3 ++- src/yunohost/tests/test_permission.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/yunohost/tests/test_backuprestore.py b/src/yunohost/tests/test_backuprestore.py index b4d4118f7..a23785558 100644 --- a/src/yunohost/tests/test_backuprestore.py +++ b/src/yunohost/tests/test_backuprestore.py @@ -9,7 +9,8 @@ from yunohost.app import app_install, app_remove, app_ssowatconf from yunohost.app import _is_installed from yunohost.backup import backup_create, backup_restore, backup_list, backup_info, backup_delete, _recursive_umount from yunohost.domain import _get_maindomain, domain_list, domain_add, domain_remove -from yunohost.user import user_permission_list, user_create, user_list, user_delete +from yunohost.user import user_create, user_list, user_delete +from yunohost.permission import user_permission_list from yunohost.tests.test_permission import check_LDAP_db_integrity, check_permission_for_apps # Get main domain diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index 269148292..240aa3160 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -107,7 +107,7 @@ def setup_function(function): user_create("bob", "Bob", "Snow", "bob@" + maindomain, dummy_password) _permission_create_with_dummy_app(permission="wiki.main", url="/", allowed=["all_users"], protected=False, sync_perm=False, domain=maindomain, path='/wiki') - _permission_create_with_dummy_app(permission="blog.main", allowed=["all_users"], protected=False, sync_perm=False, + _permission_create_with_dummy_app(permission="blog.main", allowed=["alice"], protected=False, sync_perm=False, domain=maindomain, path='/blog') _permission_create_with_dummy_app(permission="blog.api", allowed=["visitors"], protected=True, sync_perm=True) From 7d69079e46575b6a88580a04434d33cf54495259 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Tue, 7 Apr 2020 10:55:04 +0200 Subject: [PATCH 0855/3170] Add test for permission path normalization --- src/yunohost/domain.py | 2 +- src/yunohost/tests/test_appurl.py | 43 ++++++++++++++++++++++++++++++- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index c3579c805..903f65c95 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -437,7 +437,7 @@ def _check_and_normalize_permission_path(url): if '/' not in url: raise YunohostError('regex_with_only_domain') domain = url[3:].split('/')[0] - path = url[3:].split('/', 1)[1] + path = '/' + url[3:].split('/', 1)[1] if domain not in domains: raise YunohostError('domain_named_unknown', domain=domain) diff --git a/src/yunohost/tests/test_appurl.py b/src/yunohost/tests/test_appurl.py index b0a47d89f..3b7af4d92 100644 --- a/src/yunohost/tests/test_appurl.py +++ b/src/yunohost/tests/test_appurl.py @@ -2,7 +2,7 @@ import pytest from yunohost.utils.error import YunohostError from yunohost.app import app_install, app_remove -from yunohost.domain import _get_maindomain, domain_url_available, _normalize_domain_path +from yunohost.domain import _get_maindomain, domain_url_available, _normalize_domain_path, _check_and_normalize_permission_path # Get main domain maindomain = _get_maindomain() @@ -59,3 +59,44 @@ def test_registerurl_baddomain(): with pytest.raises(YunohostError): app_install("./tests/apps/register_url_app_ynh", args="domain=%s&path=%s" % ("yolo.swag", "/urlregisterapp"), force=True) + + +def test_normalize_permission_path(): + # Relative path + assert _check_and_normalize_permission_path("/wiki/") == "/wiki" + assert _check_and_normalize_permission_path("/") == "/" + assert _check_and_normalize_permission_path("//salut/") == "/salut" + + # Full path + assert _check_and_normalize_permission_path(maindomain + "/hey/") == maindomain + "/hey" + assert _check_and_normalize_permission_path(maindomain + "//") == maindomain + "/" + assert _check_and_normalize_permission_path(maindomain + "/") == maindomain + "/" + + # Relative Regex + assert _check_and_normalize_permission_path("re:/yolo.*/") == "re:/yolo.*/" + assert _check_and_normalize_permission_path("re:/y.*o(o+)[a-z]*/bo\1y") == "re:/y.*o(o+)[a-z]*/bo\1y" + + # Full Regex + assert _check_and_normalize_permission_path("re:" + maindomain + "/yolo.*/") == "re:" + maindomain + "/yolo.*/" + assert _check_and_normalize_permission_path("re:" + maindomain + "/y.*o(o+)[a-z]*/bo\1y") == "re:" + maindomain + "/y.*o(o+)[a-z]*/bo\1y" + + +def test_normalize_permission_path_with_bad_regex(): + # Relative Regex + with pytest.raises(YunohostError): + _check_and_normalize_permission_path("re:/yolo.*[1-7]^?/") + with pytest.raises(YunohostError): + _check_and_normalize_permission_path("re:/yolo.*[1-7](]/") + + # Full Regex + with pytest.raises(YunohostError): + _check_and_normalize_permission_path("re:" + maindomain + "/yolo?+/") + with pytest.raises(YunohostError): + _check_and_normalize_permission_path("re:" + maindomain + "/yolo[1-9]**/") + + +def test_normalize_permission_path_with_unknown_domain(): + with pytest.raises(YunohostError): + _check_and_normalize_permission_path("shouldntexist.tld/hey") + with pytest.raises(YunohostError): + _check_and_normalize_permission_path("re:shouldntexist.tld/hey.*") From be8427d5a117fd34ade956d8b67f0ad42533e2e6 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 8 Apr 2020 12:15:01 +0200 Subject: [PATCH 0856/3170] Gotta generate security.conf.inc during .deb deployment because it's needed by yunohost_admin.conf --- data/hooks/conf_regen/15-nginx | 1 + 1 file changed, 1 insertion(+) diff --git a/data/hooks/conf_regen/15-nginx b/data/hooks/conf_regen/15-nginx index 11e5f596c..412320e0b 100755 --- a/data/hooks/conf_regen/15-nginx +++ b/data/hooks/conf_regen/15-nginx @@ -23,6 +23,7 @@ do_init_regen() { rm -f "${nginx_dir}/sites-enabled/default" export compatibility="intermediate" + ynh_render_template "security.conf.inc" "${nginx_conf_dir}/security.conf.inc" ynh_render_template "yunohost_admin.conf" "${nginx_conf_dir}/yunohost_admin.conf" # Restart nginx if conf looks good, otherwise display error and exit unhappy From 0a482fd879ce721c3e362e2b0ae876515051b75d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 8 Apr 2020 12:56:47 +0200 Subject: [PATCH 0857/3170] Move openssh-server to Depends, reorganize Depends list --- debian/control | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/debian/control b/debian/control index 4b3837c1b..5bcd78491 100644 --- a/debian/control +++ b/debian/control @@ -15,22 +15,23 @@ Depends: ${python:Depends}, ${misc:Depends} , python-psutil, python-requests, python-dnspython, python-openssl , python-apt, python-miniupnpc, python-dbus, python-jinja2 , python-toml - , apt, apt-transport-https, lsb-release - , dnsutils, bind9utils, unzip, git, curl, cron, wget, jq - , ca-certificates, netcat-openbsd, iproute2 + , apt, apt-transport-https + , nginx, nginx-extras (>=1.6.2) + , php-fpm, php-ldap, php-intl , mariadb-server, php-mysql | php-mysqlnd + , openssh-server, iptables, fail2ban, dnsutils, bind9utils + , openssl, ca-certificates, netcat-openbsd, iproute2 , slapd, ldap-utils, sudo-ldap, libnss-ldapd, unscd, libpam-ldapd - , postfix-ldap, postfix-policyd-spf-perl, postfix-pcre, procmail, mailutils, postsrsd - , dovecot-ldap, dovecot-lmtpd, dovecot-managesieved - , dovecot-antispam, fail2ban, iptables - , nginx-extras (>=1.6.2), php-fpm, php-ldap, php-intl - , dnsmasq, openssl, avahi-daemon, libnss-mdns, resolvconf, libnss-myhostname + , dnsmasq, avahi-daemon, libnss-mdns, resolvconf, libnss-myhostname + , postfix, postfix-ldap, postfix-policyd-spf-perl, postfix-pcre + , dovecot-core, dovecot-ldap, dovecot-lmtpd, dovecot-managesieved, dovecot-antispam + , rspamd (>= 1.6.0), opendkim-tools, postsrsd, procmail, mailutils + , redis-server , metronome - , rspamd (>= 1.6.0), redis-server, opendkim-tools - , haveged, fake-hwclock - , equivs, lsof + , git, curl, wget, cron, unzip, jq + , lsb-release, haveged, fake-hwclock, equivs, lsof Recommends: yunohost-admin - , openssh-server, ntp, inetutils-ping | iputils-ping + , ntp, inetutils-ping | iputils-ping , bash-completion, rsyslog , php-gd, php-curl, php-gettext, php-mcrypt , python-pip From f390f02077294cc1033977601071ba242da4bf85 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 3 Apr 2020 03:12:09 +0200 Subject: [PATCH 0858/3170] Update nginx security.conf.inc with new Mozilla recommendation --- data/templates/nginx/security.conf.inc | 28 ++++++++++++-------------- data/templates/nginx/server.tpl.conf | 12 ++++------- 2 files changed, 17 insertions(+), 23 deletions(-) diff --git a/data/templates/nginx/security.conf.inc b/data/templates/nginx/security.conf.inc index 28d12055b..79a891a21 100644 --- a/data/templates/nginx/security.conf.inc +++ b/data/templates/nginx/security.conf.inc @@ -1,24 +1,22 @@ -{% if compatibility == "modern" %} -# Ciphers with modern compatibility -# https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=nginx-1.6.2&openssl=1.0.1t&hsts=yes&profile=modern -# The following configuration use modern ciphers, but remove compatibility with some old clients (android < 5.0, Internet Explorer < 10, ...) -ssl_protocols TLSv1.2; -ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256'; -ssl_prefer_server_ciphers on; -{% else %} -# As suggested by Mozilla : https://wiki.mozilla.org/Security/Server_Side_TLS and https://en.wikipedia.org/wiki/Curve25519 -ssl_ecdh_curve secp521r1:secp384r1:prime256v1; -ssl_prefer_server_ciphers on; +ssl_session_timeout 1d; +ssl_session_cache shared:SSL:10m; # about 40000 sessions +ssl_session_tickets off; + +# nginx 1.10 in stretch doesn't support TLS1.3 and Mozilla doesn't have any +# "modern" config recommendation with it. +# So until buster the modern conf is same as intermediate +{% if compatibility == "modern" %} {% else %} {% endif %} # Ciphers with intermediate compatibility -# https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=nginx-1.6.2&openssl=1.0.1t&hsts=yes&profile=intermediate -ssl_protocols TLSv1 TLSv1.1 TLSv1.2; -ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS'; +# generated 2020-04-03, Mozilla Guideline v5.4, nginx 1.10.3, OpenSSL 1.1.1l, intermediate configuration +# https://ssl-config.mozilla.org/#server=nginx&version=1.10.3&config=intermediate&openssl=1.1.1l&guideline=5.4 +ssl_protocols TLSv1.2; +ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; +ssl_prefer_server_ciphers off; # Uncomment the following directive after DH generation # > openssl dhparam -out /etc/ssl/private/dh2048.pem -outform PEM -2 2048 #ssl_dhparam /etc/ssl/private/dh2048.pem; -{% endif %} # Follows the Web Security Directives from the Mozilla Dev Lab and the Mozilla Obervatory + Partners # https://wiki.mozilla.org/Security/Guidelines/Web_Security diff --git a/data/templates/nginx/server.tpl.conf b/data/templates/nginx/server.tpl.conf index 6316960c4..dcfd139ba 100644 --- a/data/templates/nginx/server.tpl.conf +++ b/data/templates/nginx/server.tpl.conf @@ -33,12 +33,10 @@ server { listen [::]:443 ssl http2; server_name {{ domain }}; + include /etc/nginx/conf.d/security.conf.inc; + ssl_certificate /etc/yunohost/certs/{{ domain }}/crt.pem; ssl_certificate_key /etc/yunohost/certs/{{ domain }}/key.pem; - ssl_session_timeout 5m; - ssl_session_cache shared:SSL:50m; - - include /etc/nginx/conf.d/security.conf.inc; {% if domain_cert_ca != "Self-signed" %} more_set_headers "Strict-Transport-Security : max-age=63072000; includeSubDomains; preload"; @@ -85,12 +83,10 @@ server { client_max_body_size 105M; # Choose a value a bit higher than the max upload configured in XMPP server } + include /etc/nginx/conf.d/security.conf.inc; + ssl_certificate /etc/yunohost/certs/{{ domain }}/crt.pem; ssl_certificate_key /etc/yunohost/certs/{{ domain }}/key.pem; - ssl_session_timeout 5m; - ssl_session_cache shared:SSL:50m; - - include /etc/nginx/conf.d/security.conf.inc; {% if domain_cert_ca != "Self-signed" %} more_set_headers "Strict-Transport-Security : max-age=63072000; includeSubDomains; preload"; From 71cc4fde97514b580705c6af517e6e2635e6bd5e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 5 Apr 2020 18:32:03 +0200 Subject: [PATCH 0859/3170] We in fact only have ssl 1.1.0l, not 1.1.1l on Stretch. --- data/templates/nginx/security.conf.inc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/templates/nginx/security.conf.inc b/data/templates/nginx/security.conf.inc index 79a891a21..a7e1ac718 100644 --- a/data/templates/nginx/security.conf.inc +++ b/data/templates/nginx/security.conf.inc @@ -8,8 +8,8 @@ ssl_session_tickets off; {% if compatibility == "modern" %} {% else %} {% endif %} # Ciphers with intermediate compatibility -# generated 2020-04-03, Mozilla Guideline v5.4, nginx 1.10.3, OpenSSL 1.1.1l, intermediate configuration -# https://ssl-config.mozilla.org/#server=nginx&version=1.10.3&config=intermediate&openssl=1.1.1l&guideline=5.4 +# generated 2020-04-03, Mozilla Guideline v5.4, nginx 1.10.3, OpenSSL 1.1.0l, intermediate configuration +# https://ssl-config.mozilla.org/#server=nginx&version=1.10.3&config=intermediate&openssl=1.1.0l&guideline=5.4 ssl_protocols TLSv1.2; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers off; From c06fe42078d13ccf6494ac23ee9cef99d1895c64 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 8 Apr 2020 21:33:34 +0200 Subject: [PATCH 0860/3170] Hmgn don't change the value for the session cache size otherwise that break test for restore from old version for stupid reasons -.- --- 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 a7e1ac718..ff3d2ee99 100644 --- a/data/templates/nginx/security.conf.inc +++ b/data/templates/nginx/security.conf.inc @@ -1,5 +1,5 @@ ssl_session_timeout 1d; -ssl_session_cache shared:SSL:10m; # about 40000 sessions +ssl_session_cache shared:SSL:50m; # about 200000 sessions ssl_session_tickets off; # nginx 1.10 in stretch doesn't support TLS1.3 and Mozilla doesn't have any From 562b41a47d8bee485c085f587a56a7cf60ec28bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 8 Apr 2020 22:46:03 +0200 Subject: [PATCH 0861/3170] Add more tests for permission extension --- src/yunohost/tests/test_permission.py | 257 +++++++++++++++++++++++++- 1 file changed, 247 insertions(+), 10 deletions(-) diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index 240aa3160..635e64665 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -72,7 +72,7 @@ def clean_user_groups_permission(): user_group_delete(g) for p in user_permission_list()['permissions']: - if any(p.startswith(name) for name in ["wiki", "blog", "site", "permissions_app"]): + if any(p.startswith(name) for name in ["wiki", "blog", "site", "web", "permissions_app"]): permission_delete(p, force=True, sync_perm=False) socket.getaddrinfo = prv_getaddrinfo @@ -105,10 +105,14 @@ def setup_function(function): user_create("alice", "Alice", "White", "alice@" + maindomain, dummy_password) user_create("bob", "Bob", "Snow", "bob@" + maindomain, dummy_password) - _permission_create_with_dummy_app(permission="wiki.main", url="/", allowed=["all_users"], protected=False, sync_perm=False, + _permission_create_with_dummy_app(permission="wiki.main", url="/", additional_urls=['/whatever','/idontnow'], auth_header=False, + label="Wiki", show_tile=True, + allowed=["all_users"], protected=False, sync_perm=False, domain=maindomain, path='/wiki') - _permission_create_with_dummy_app(permission="blog.main", allowed=["alice"], protected=False, sync_perm=False, - domain=maindomain, path='/blog') + _permission_create_with_dummy_app(permission="blog.main", url="/", auth_header=True, + show_tile=False, + protected=False, sync_perm=False, + allowed=["alice"], domain=maindomain, path='/blog') _permission_create_with_dummy_app(permission="blog.api", allowed=["visitors"], protected=True, sync_perm=True) @@ -285,12 +289,35 @@ def test_permission_list(): assert "xmpp.main" in res assert res['wiki.main']['allowed'] == ["all_users"] assert res['blog.main']['allowed'] == ["alice"] + assert res['blog.api']['allowed'] == ["visitors"] assert set(res['wiki.main']['corresponding_users']) == set(["alice", "bob"]) assert res['blog.main']['corresponding_users'] == ["alice"] + assert res['blog.api']['corresponding_users'] == [] assert res['wiki.main']['url'] == maindomain + "/wiki" + assert res['blog.main']['url'] == maindomain + "/blog" + assert res['blog.api']['url'] == None + assert set(res['wiki.main']['additional_urls']) == set([maindomain + '/wiki/whatever', maindomain + '/wiki/idontnow']) + assert res['blog.main']['additional_urls'] == [] + assert res['blog.api']['additional_urls'] == [] + assert res['wiki.main']['protected'] == False + assert res['blog.main']['protected'] == False + assert res['blog.api']['protected'] == True + assert res['wiki.main']['label'] == "Wiki" + assert res['blog.main']['label'] == "Blog" + assert res['blog.api']['label'] == "Blog (api)" + assert res['wiki.main']['show_tile'] == True + assert res['blog.main']['show_tile'] == False + assert res['blog.api']['show_tile'] == False + assert res['wiki.main']['auth_header'] == False + assert res['blog.main']['auth_header'] == True + assert res['blog.api']['auth_header'] == True res = user_permission_list(full=True, full_path=False)['permissions'] assert res['wiki.main']['url'] == "/" + assert res['blog.main']['url'] == "/" + assert set(res['wiki.main']['additional_urls']) == set(['/whatever', '/idontnow']) + + # # Create - Remove functions # @@ -304,6 +331,7 @@ def test_permission_create_main(mocker): assert "site.main" in res assert res['site.main']['allowed'] == ["all_users"] assert set(res['site.main']['corresponding_users']) == set(["alice", "bob"]) + assert res['site.main']['protected'] == False def test_permission_create_extra(mocker): @@ -315,6 +343,7 @@ def test_permission_create_extra(mocker): # all_users is only enabled by default on .main perms assert "all_users" not in res['site.test']['allowed'] assert res['site.test']['corresponding_users'] == [] + assert res['site.test']['protected'] == True def test_permission_create_with_specific_user(): @@ -325,6 +354,77 @@ def test_permission_create_with_specific_user(): assert res['site.test']['allowed'] == ["alice"] +def test_permission_create_with_tile_management(mocker): + with message(mocker, "permission_created", permission="site.main"): + permission_create("site.main", allowed=["all_users"], + label="The Site", show_tile=False) + + res = user_permission_list(full=True)['permissions'] + assert "site.main" in res + assert res['site.main']['label'] == "The Site" + assert res['site.main']['show_tile'] == False + +def test_permission_create_with_tile_management_with_main_default_value(mocker): + with message(mocker, "permission_created", permission="web.main"): + permission_create("web.main", allowed=["all_users"], show_tile=True) + + res = user_permission_list(full=True)['permissions'] + assert "web.main" in res + assert res['web.main']['label'] == "Web" + assert res['web.main']['show_tile'] == True + +def test_permission_create_with_tile_management_with_not_main_default_value(mocker): + with message(mocker, "permission_created", permission="site.api"): + permission_create("site.api", allowed=["all_users"], show_tile=True) + + res = user_permission_list(full=True)['permissions'] + assert "site.api" in res + assert res['site.api']['label'] == "Site (api)" + assert res['site.api']['show_tile'] == True + + +def test_permission_create_with_urls_management_without_url(mocker): + with message(mocker, "permission_created", permission="site.main"): + _permission_create_with_dummy_app("site.api", allowed=["all_users"], + domain=maindomain, path='/site') + + res = user_permission_list(full=True)['permissions'] + assert "site.main" in res + assert res['site.main']['url'] == None + assert res['site.main']['additional_urls'] == [] + assert res['site.main']['auth_header'] == False + + +def test_permission_create_with_urls_management_simple_domain(mocker): + with message(mocker, "permission_created", permission="site.main"): + _permission_create_with_dummy_app("site.main", allowed=["all_users"], + url="/", additional_urls=['/whatever','/idontnow'], auth_header=False, + domain=maindomain, path='/site') + + res = user_permission_list(full=True)['permissions'] + assert "site.main" in res + assert res['site.main']['url'] == maindomain + "/site" + assert set(res['site.main']['additional_urls']) == set([maindomain + "/site/whatever", maindomain + "/site/idontnow"]) + assert res['site.main']['auth_header'] == False + + +@pytest.mark.other_domains(number=2) +def test_permission_create_with_urls_management_multiple_domain(mocker): + with message(mocker, "permission_created", permission="site.main"): + _permission_create_with_dummy_app("site.main", allowed=["all_users"], + url=maindomain + "/site/something", + additional_urls=[other_domains[0] + "/blabla", + other_domains[1] + "/ahh"], + auth_header=True, + domain=maindomain, path='/site') + + res = user_permission_list(full=True)['permissions'] + assert "site.main" in res + assert res['site.main']['url'] == maindomain + "/site" + assert set(res['site.main']['additional_urls']) == set([other_domains[0] + "/blabla", other_domains[1] + "/ahh"]) + assert res['site.main']['auth_header'] == True + + def test_permission_delete(mocker): with message(mocker, "permission_deleted", permission="wiki.main"): permission_delete("wiki.main", force=True) @@ -332,6 +432,18 @@ def test_permission_delete(mocker): res = user_permission_list()['permissions'] assert "wiki.main" not in res + with message(mocker, "permission_deleted", permission="blog.main"): + permission_delete("blog.main", force=True) + + res = user_permission_list()['permissions'] + assert "blog.main" not in res + + with message(mocker, "permission_deleted", permission="blog.api"): + permission_delete("blog.api", force=False) + + res = user_permission_list()['permissions'] + assert "blog.api" not in res + # # Error on create - remove function # @@ -431,14 +543,46 @@ def test_permission_reset_idempotency(): assert set(res['blog.main']['corresponding_users']) == set(["alice", "bob"]) -def test_permission_reset_idempotency(): - # Reset permission - user_permission_reset("blog.main") - user_permission_reset("blog.main") +def test_permission_change_label(mocker): + with message(mocker, "permission_updated", permission="wiki.main"): + user_permission_update("wiki.main", label="New Wiki") res = user_permission_list(full=True)['permissions'] - assert res['blog.main']['allowed'] == ["all_users"] - assert set(res['blog.main']['corresponding_users']) == set(["alice", "bob"]) + assert res['wiki.main']['label'] == "New Wiki" + + +def test_permission_change_label_with_same_value(mocker): + with message(mocker, "permission_updated", permission="wiki.main"): + user_permission_update("wiki.main", label="Wiki") + + res = user_permission_list(full=True)['permissions'] + assert res['wiki.main']['label'] == "Wiki" + + +def test_permission_switch_show_tile(mocker): + # Note that from the actionmap the value is passed as string, not as bool + # Try with lowercase + with message(mocker, "permission_updated", permission="wiki.main"): + user_permission_update("wiki.main", show_tile="false") + + res = user_permission_list(full=True)['permissions'] + assert res['wiki.main']['show_tile'] == False + + # Try with uppercase + with message(mocker, "permission_updated", permission="wiki.main"): + user_permission_update("wiki.main", show_tile="TRUE") + + res = user_permission_list(full=True)['permissions'] + assert res['wiki.main']['show_tile'] == True + + +def test_permission_switch_show_tile_with_same_value(mocker): + # Note that from the actionmap the value is passed as string, not as bool + with message(mocker, "permission_updated", permission="wiki.main"): + user_permission_update("wiki.main", show_tile="True") + + res = user_permission_list(full=True)['permissions'] + assert res['wiki.main']['show_tile'] == True # @@ -480,20 +624,113 @@ def test_permission_protected_update(mocker): res = user_permission_list(full=True)['permissions'] assert res['blog.api']['allowed'] == [] + # Permission url management + def test_permission_redefine_url(): permission_url("blog.main", url="/pwet") res = user_permission_list(full=True, full_path=False)['permissions'] assert res["blog.main"]["url"] == "/pwet" + def test_permission_remove_url(): permission_url("blog.main", url=None) res = user_permission_list(full=True)['permissions'] assert res["blog.main"]["url"] is None + +@pytest.mark.other_domains(number=1) +def test_permission_add_additional_url(): + permission_url("wiki.main", add_url=[other_domains[0] + "/heyby", "/myhouse"]) + + res = user_permission_list(full=True)['permissions'] + assert res['wiki.main']['url'] == maindomain + "/wiki" + assert set(res['wiki.main']['additional_urls']) == set([maindomain + '/wiki/whatever', + maindomain + '/wiki/idontnow', + other_domains[0] + "/heyby", + maindomain + '/wiki/myhouse']) + + +def test_permission_remove_additional_url(): + permission_url("wiki.main", remove_url=['/whatever']) + + res = user_permission_list(full=True)['permissions'] + assert res['wiki.main']['url'] == maindomain + "/wiki" + assert res['wiki.main']['additional_urls'] == [maindomain + '/wiki/idontnow'] + + +def test_permssion_add_additional_url_already_exist(): + permission_url("wiki.main", add_url=['/whatever', "/myhouse"]) + permission_url("wiki.main", add_url=['/whatever']) + + res = user_permission_list(full=True)['permissions'] + assert res['wiki.main']['url'] == maindomain + "/wiki" + assert set(res['wiki.main']['additional_urls']) == set([maindomain + '/wiki/whatever', + maindomain + '/wiki/idontnow', + maindomain + '/wiki/myhouse']) + + +def test_permission_remove_additional_url_dont_exist(): + permission_url("wiki.main", remove_url=['/shouldntexist', '/whatever']) + permission_url("wiki.main", remove_url=['/shouldntexist']) + + res = user_permission_list(full=True)['permissions'] + assert res['wiki.main']['url'] == maindomain + "/wiki" + assert res['wiki.main']['additional_urls'] == [maindomain + '/wiki/idontnow'] + + +def test_permission_clear_additional_url(): + permission_url("wiki.main", clear_url=True) + + res = user_permission_list(full=True)['permissions'] + assert res['wiki.main']['url'] == None + assert res['wiki.main']['additional_urls'] == [] + + +def test_permission_switch_auth_header(): + permission_url("wiki.main", auth_header=True) + + res = user_permission_list(full=True)['permissions'] + assert res['wiki.main']['auth_header'] == True + + permission_url("wiki.main", auth_header=False) + + res = user_permission_list(full=True)['permissions'] + assert res['wiki.main']['auth_header'] == False + + +def test_permission_switch_auth_header_with_same_value(): + permission_url("wiki.main", auth_header=False) + + res = user_permission_list(full=True)['permissions'] + assert res['wiki.main']['auth_header'] == False + + +# Permission protected + + +def test_permission_switch_protected(): + permission_update("wiki.main", protected=True) + + res = user_permission_list(full=True)['permissions'] + assert res['wiki.main']['protected'] == True + + permission_update("wiki.main", protected=False) + + res = user_permission_list(full=True)['permissions'] + assert res['wiki.main']['protected'] == False + + +def test_permission_switch_protected_with_same_value(): + permission_update("wiki.main", protected=False) + + res = user_permission_list(full=True)['permissions'] + assert res['wiki.main']['protected'] == False + + # # Application interaction # From 4ebf716d2145f53dde12fb9e4ae5b156fefff2ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 8 Apr 2020 22:46:53 +0200 Subject: [PATCH 0862/3170] Fix typo --- locales/en.json | 4 ++-- src/yunohost/permission.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/locales/en.json b/locales/en.json index 47425d0c3..922ad2c4e 100644 --- a/locales/en.json +++ b/locales/en.json @@ -1,8 +1,8 @@ { "aborting": "Aborting.", "action_invalid": "Invalid action '{action:s}'", - "additional_urls_already_added": "Additionnal url '{url:s} already added in the additional URL for permission {permission:s}'", - "additional_urls_already_removed": "Additionnal url '{url:s} already removed in the additional URL for permission {permission:s}'", + "additional_urls_already_added": "Additionnal url '{url:s}' already added in the additional URL for permission '{permission:s}'", + "additional_urls_already_removed": "Additionnal url '{url:s}' already removed in the additional URL for permission '{permission:s}'", "admin_password": "Administration password", "admin_password_change_failed": "Cannot change password", "admin_password_changed": "The administration password was changed", diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 858bee614..52d331f44 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -381,7 +381,7 @@ def permission_url(operation_logger, permission, if add_url: for ur in add_url: if ur in current_additional_urls: - logger.warning(m18n.n('additional_urls_already_added', permission=permission, url=url)) + logger.warning(m18n.n('additional_urls_already_added', permission=permission, url=ur)) else: ur = _check_and_normalize_permission_path(ur) domain, path = _get_full_url(ur, app_main_path).split('/', 1) @@ -403,7 +403,7 @@ def permission_url(operation_logger, permission, if remove_url: for ur in remove_url: if ur not in current_additional_urls: - logger.warning(m18n.n('additional_urls_already_removed', permission=permission, url=url)) + logger.warning(m18n.n('additional_urls_already_removed', permission=permission, url=ur)) new_additional_urls = [u for u in new_additional_urls if u not in remove_url] From feb41889df615425abc840539c9321cb3e5452b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 8 Apr 2020 22:47:32 +0200 Subject: [PATCH 0863/3170] Improve label management for permission --- src/yunohost/permission.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 52d331f44..badd37ef3 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -283,7 +283,8 @@ def permission_create(operation_logger, permission, allowed=None, 'cn': str(permission), 'gidNumber': gid, 'authHeader': ['TRUE'], - 'label': [str(permission)], + 'label': [permission.split('.')[0].title() if permission.endswith('.main') + else "%s (%s)" % (permission.split('.')[0].title(), permission.split('.')[1])], 'showTile': ['FALSE'], # Dummy value, it will be fixed when we call '_update_ldap_group_permission' 'isProtected': ['FALSE'] # Dummy value, it will be fixed when we call '_update_ldap_group_permission' } From 64c950c1dcc8f227a0f26ca985ad90e86f6eac8c Mon Sep 17 00:00:00 2001 From: Josue-T Date: Wed, 8 Apr 2020 23:06:45 +0200 Subject: [PATCH 0864/3170] Fix typo Co-Authored-By: Kayou --- .../data_migrations/0015_extends_permissions_features_1.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/data_migrations/0015_extends_permissions_features_1.py b/src/yunohost/data_migrations/0015_extends_permissions_features_1.py index a9abf3403..d929157b2 100644 --- a/src/yunohost/data_migrations/0015_extends_permissions_features_1.py +++ b/src/yunohost/data_migrations/0015_extends_permissions_features_1.py @@ -58,7 +58,7 @@ class MyMigration(Migration): else: ldap.update('cn=%s,ou=permission' % permission, { 'authHeader': ["TRUE"], - 'label': ["%s (%s)" (label, permission.split('.')[1])], + 'label': ["%s (%s)" % (label, permission.split('.')[1])], 'showTile': ["FALSE"], 'isProtected': ["TRUE"] }) From cfc782f015502e259036fb2c7042d6a41788a7fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 8 Apr 2020 23:14:25 +0200 Subject: [PATCH 0865/3170] Fix label management in migration --- .../0015_extends_permissions_features_1.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/yunohost/data_migrations/0015_extends_permissions_features_1.py b/src/yunohost/data_migrations/0015_extends_permissions_features_1.py index d929157b2..d11d4f136 100644 --- a/src/yunohost/data_migrations/0015_extends_permissions_features_1.py +++ b/src/yunohost/data_migrations/0015_extends_permissions_features_1.py @@ -37,6 +37,11 @@ class MyMigration(Migration): ldap = _get_ldap_interface() permission_list = user_permission_list(short=True, full_path=False)["permissions"] + labels = {} + for app in _installed_apps(): + labels[app] = app_setting(app, 'label') + app_setting(app, 'label', delete=True) + for permission in permission_list: if permission.split('.')[0] in SYSTEM_PERMS: ldap.update('cn=%s,ou=permission' % permission, { @@ -46,7 +51,7 @@ class MyMigration(Migration): 'isProtected': ["TRUE"], }) else: - label = app_setting(permission.split('.')[0], 'label') + label = labels[permission.split('.')[0]] if permission.endswith(".main"): ldap.update('cn=%s,ou=permission' % permission, { @@ -62,7 +67,6 @@ class MyMigration(Migration): 'showTile': ["FALSE"], 'isProtected': ["TRUE"] }) - app_setting(permission.split('.')[0], 'label', delete=True) def run(self): From 2f2ef9b95d5676e8f66f6b5482b9317067758e20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Thu, 9 Apr 2020 09:49:22 +0200 Subject: [PATCH 0866/3170] Fix ssowatconf --- src/yunohost/app.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index c99255eb6..9feb5bec9 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1195,14 +1195,13 @@ def app_ssowatconf(): "show_tile": False, "auth_header": False, "public": False, - "uris": [ + "uris": \ [domain + '/yunohost/admin' for domain in domains] + \ [domain + '/yunohost/api' for domain in domains] + [ "re:^[^/]*/%.well%-known/ynh%-diagnosis/.*$", "re:^[^/]*/%.well%-known/acme%-challenge/.*$", "re:^[^/]*/%.well%-known/autoconfig/mail/config%-v1%.1%.xml.*$" ] - ] } } redirected_regex = {main_domain + '/yunohost[\/]?$': 'https://' + main_domain + '/yunohost/sso/'} @@ -1282,7 +1281,7 @@ def app_ssowatconf(): "label": "Legacy permission - skipped_urls for app :" + app, "show_tile": False, "auth_header": False, - "public": False, + "public": True, "uris": skipped_urls } if unprotected_urls != []: @@ -1291,7 +1290,7 @@ def app_ssowatconf(): "label": "Legacy permission - unprotected_urls for app :" + app, "show_tile": False, "auth_header": True, - "public": False, + "public": True, "uris": unprotected_urls } if protected_urls != []: @@ -1300,7 +1299,7 @@ def app_ssowatconf(): "label": "Legacy permission - protected_urls for app :" + app, "show_tile": False, "auth_header": True, - "public": True, + "public": False, "uris": protected_urls } From c0f94ba98ae3b8e64a5b7254144e3f4a65ef1bb9 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Thu, 9 Apr 2020 12:29:44 +0200 Subject: [PATCH 0867/3170] [fix] uid will be tested as a string --- src/yunohost/user.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 39a2d8f15..fd67314d8 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -165,8 +165,8 @@ def user_create(operation_logger, username, firstname, lastname, mail, password, operation_logger.start() # Get random UID/GID - all_uid = {x.pw_uid for x in pwd.getpwall()} - all_gid = {x.gr_gid for x in grp.getgrall()} + all_uid = {str(x.pw_uid) for x in pwd.getpwall()} + all_gid = {str(x.gr_gid) for x in grp.getgrall()} uid_guid_found = False while not uid_guid_found: From 3c8442925852a27a73c21a51cc84738c51a37861 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 20 Nov 2019 15:31:55 +0100 Subject: [PATCH 0868/3170] Improve messages wording ? More consistent service 'X' vs. 'X' service --- locales/en.json | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/locales/en.json b/locales/en.json index 4bde03919..6a2af5e41 100644 --- a/locales/en.json +++ b/locales/en.json @@ -446,7 +446,7 @@ "regenconf_file_updated": "Configuration file '{conf}' updated", "regenconf_now_managed_by_yunohost": "The configuration file '{conf}' is now managed by YunoHost (category {category}).", "regenconf_up_to_date": "The configuration is already up-to-date for category '{category}'", - "regenconf_updated": "Configuration for category '{category}' updated", + "regenconf_updated": "Configuration updated for '{category}'", "regenconf_would_be_updated": "The configuration would have been updated for category '{category}'", "regenconf_dry_pending_applying": "Checking pending configuration which would have been applied for category '{category}'…", "regenconf_failed": "Could not regenerate the configuration for category(s): {categories}", @@ -495,24 +495,23 @@ "service_description_ssh": "Allows you to connect remotely to your server via a terminal (SSH protocol)", "service_description_yunohost-api": "Manages interactions between the YunoHost web interface and the system", "service_description_yunohost-firewall": "Manages open and close connection ports to services", - "service_disable_failed": "Could not turn off the service '{service:s}'\n\nRecent service logs:{logs:s}", - "service_disabled": "The '{service:s}' service was turned off", - "service_enable_failed": "Could not turn on the service '{service:s}'\n\nRecent service logs:{logs:s}", - "service_enabled": "The '{service:s}' service was turned off", - "service_no_log": "No logs to display for the service '{service:s}'", + "service_disable_failed": "Could not make the service '{service:s}' not start at boot.\n\nRecent service logs:{logs:s}", + "service_disabled": "The service '{service:s}' will not be started anymore when system boots.", + "service_enable_failed": "Could not make the service '{service:s}' automatically start at boot.\n\nRecent service logs:{logs:s}", + "service_enabled": "The service '{service:s}' will now be automatically started during system boots.", "service_regen_conf_is_deprecated": "'yunohost service regen-conf' is deprecated! Please use 'yunohost tools regen-conf' instead.", "service_remove_failed": "Could not remove the service '{service:s}'", - "service_removed": "'{service:s}' service removed", + "service_removed": "Service '{service:s}' removed", "service_reload_failed": "Could not reload the service '{service:s}'\n\nRecent service logs:{logs:s}", - "service_reloaded": "The '{service:s}' service was reloaded", + "service_reloaded": "Service '{service:s}' reloaded", "service_restart_failed": "Could not restart the service '{service:s}'\n\nRecent service logs:{logs:s}", - "service_restarted": "'{service:s}' service restarted", + "service_restarted": "Service '{service:s}' restarted", "service_reload_or_restart_failed": "Could not reload or restart the service '{service:s}'\n\nRecent service logs:{logs:s}", - "service_reloaded_or_restarted": "The '{service:s}' service was reloaded or restarted", + "service_reloaded_or_restarted": "The service '{service:s}' was reloaded or restarted", "service_start_failed": "Could not start the service '{service:s}'\n\nRecent service logs:{logs:s}", - "service_started": "'{service:s}' service started", + "service_started": "Service '{service:s}' started", "service_stop_failed": "Could not stop the service '{service:s}'\n\nRecent service logs:{logs:s}", - "service_stopped": "The '{service:s}' service stopped", + "service_stopped": "Service '{service:s}' stopped", "service_unknown": "Unknown service '{service:s}'", "ssowat_conf_generated": "SSOwat configuration generated", "ssowat_conf_updated": "SSOwat configuration updated", From 031f8a6e3814dd9c387814e1c1c61b284df95174 Mon Sep 17 00:00:00 2001 From: Matthew DeAbreu Date: Wed, 20 Nov 2019 09:52:01 -0800 Subject: [PATCH 0869/3170] ensure metronome owns domain dir When adding new domains to Yunohost a directory for each newly added domain is created in `/var/lib/metronome` unfortunately since the directory is created with `sudo mkdir` that means `root:root` owns the directory. Metronome will now fail to write to the directory. --- data/hooks/conf_regen/12-metronome | 1 + 1 file changed, 1 insertion(+) diff --git a/data/hooks/conf_regen/12-metronome b/data/hooks/conf_regen/12-metronome index 4214722fc..f3df22317 100755 --- a/data/hooks/conf_regen/12-metronome +++ b/data/hooks/conf_regen/12-metronome @@ -51,6 +51,7 @@ do_post_regen() { # create metronome directories for domains for domain in $domain_list; do sudo mkdir -p "/var/lib/metronome/${domain//./%2e}/pep" + sudo chown -R metronome: /var/lib/metronome/${domain//./%2e}/ done [[ -z "$regen_conf_files" ]] \ From 1f623830b3b54e49bf776d47295de98eced004d5 Mon Sep 17 00:00:00 2001 From: Matthew DeAbreu Date: Fri, 22 Nov 2019 09:02:01 -0800 Subject: [PATCH 0870/3170] Update 12-metronome simplify change by reordering operations --- data/hooks/conf_regen/12-metronome | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/data/hooks/conf_regen/12-metronome b/data/hooks/conf_regen/12-metronome index f3df22317..7047af660 100755 --- a/data/hooks/conf_regen/12-metronome +++ b/data/hooks/conf_regen/12-metronome @@ -41,19 +41,18 @@ do_pre_regen() { do_post_regen() { regen_conf_files=$1 - # fix some permissions - sudo chown -R metronome: /var/lib/metronome/ - sudo chown -R metronome: /etc/metronome/conf.d/ - # retrieve variables domain_list=$(sudo yunohost domain list --output-as plain --quiet) # create metronome directories for domains for domain in $domain_list; do sudo mkdir -p "/var/lib/metronome/${domain//./%2e}/pep" - sudo chown -R metronome: /var/lib/metronome/${domain//./%2e}/ done + # fix some permissions + sudo chown -R metronome: /var/lib/metronome/ + sudo chown -R metronome: /etc/metronome/conf.d/ + [[ -z "$regen_conf_files" ]] \ || sudo service metronome restart } From be88a2835a5663c64d31917581772c5d754ef51c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 27 Nov 2019 23:58:36 +0100 Subject: [PATCH 0871/3170] Remove those random sudo which are useless yet triggers LDAP warning when LDAP is in bad state --- data/helpers.d/apt | 2 +- data/helpers.d/backup | 24 +++++++++++----------- data/helpers.d/logging | 4 ++-- data/helpers.d/logrotate | 6 +++--- data/helpers.d/mysql | 8 ++++---- data/helpers.d/nginx | 2 +- data/helpers.d/php | 8 ++++---- data/helpers.d/postgresql | 10 ++++----- data/helpers.d/setting | 4 ++-- data/helpers.d/string | 2 +- data/helpers.d/systemd | 8 ++++---- data/helpers.d/user | 6 +++--- data/hooks/backup/05-conf_ldap | 4 ++-- data/hooks/conf_regen/01-yunohost | 14 ++++++------- data/hooks/conf_regen/02-ssl | 6 +++--- data/hooks/conf_regen/06-slapd | 2 +- data/hooks/conf_regen/09-nslcd | 2 +- data/hooks/conf_regen/12-metronome | 12 +++++------ data/hooks/conf_regen/15-nginx | 8 ++++---- data/hooks/conf_regen/19-postfix | 4 ++-- data/hooks/conf_regen/25-dovecot | 20 +++++++++--------- data/hooks/conf_regen/31-rspamd | 24 +++++++++++----------- data/hooks/conf_regen/34-mysql | 16 +++++++-------- data/hooks/conf_regen/37-avahi-daemon | 2 +- data/hooks/conf_regen/40-glances | 2 +- data/hooks/conf_regen/43-dnsmasq | 4 ++-- data/hooks/conf_regen/46-nsswitch | 2 +- data/hooks/conf_regen/52-fail2ban | 2 +- data/hooks/restore/05-conf_ldap | 2 +- data/hooks/restore/08-conf_ssh | 4 ++-- data/hooks/restore/11-conf_ynh_mysql | 16 +++++++-------- data/hooks/restore/14-conf_ssowat | 2 +- data/hooks/restore/17-data_home | 2 +- data/hooks/restore/20-conf_ynh_firewall | 4 ++-- data/hooks/restore/21-conf_ynh_certs | 8 ++++---- data/hooks/restore/23-data_mail | 8 ++++---- data/hooks/restore/26-conf_xmpp | 6 +++--- data/hooks/restore/29-conf_nginx | 4 ++-- data/hooks/restore/32-conf_cron | 4 ++-- data/hooks/restore/40-conf_ynh_currenthost | 2 +- src/yunohost/tools.py | 6 +++--- 41 files changed, 138 insertions(+), 138 deletions(-) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index da2740d01..55c85c90b 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -13,7 +13,7 @@ ynh_wait_dpkg_free() { for try in `seq 1 17` do # Check if /var/lib/dpkg/lock is used by another process - if sudo lsof /var/lib/dpkg/lock > /dev/null + if lsof /var/lib/dpkg/lock > /dev/null then echo "apt is already in use..." # Sleep an exponential time at each round diff --git a/data/helpers.d/backup b/data/helpers.d/backup index d3ffffcd3..590e951a5 100644 --- a/data/helpers.d/backup +++ b/data/helpers.d/backup @@ -179,7 +179,7 @@ ynh_restore () { # usage: _get_archive_path ORIGIN_PATH _get_archive_path () { # For security reasons we use csv python library to read the CSV - sudo python -c " + python -c " import sys import csv with open(sys.argv[1], 'r') as backup_file: @@ -302,7 +302,7 @@ ynh_store_file_checksum () { ynh_handle_getopts_args "$@" local checksum_setting_name=checksum_${file//[\/ ]/_} # Replace all '/' and ' ' by '_' - ynh_app_setting_set --app=$app --key=$checksum_setting_name --value=$(sudo md5sum "$file" | cut -d' ' -f1) + ynh_app_setting_set --app=$app --key=$checksum_setting_name --value=$(md5sum "$file" | cut -d' ' -f1) # If backup_file_checksum isn't empty, ynh_backup_if_checksum_is_different has made a backup if [ -n "${backup_file_checksum-}" ] @@ -339,11 +339,11 @@ ynh_backup_if_checksum_is_different () { backup_file_checksum="" if [ -n "$checksum_value" ] then # Proceed only if a value was stored into the app settings - if [ -e $file ] && ! echo "$checksum_value $file" | sudo md5sum -c --status + if [ -e $file ] && ! echo "$checksum_value $file" | md5sum -c --status then # If the checksum is now different backup_file_checksum="/home/yunohost.conf/backup/$file.backup.$(date '+%Y%m%d.%H%M%S')" - sudo mkdir -p "$(dirname "$backup_file_checksum")" - sudo cp -a "$file" "$backup_file_checksum" # Backup the current file + mkdir -p "$(dirname "$backup_file_checksum")" + cp -a "$file" "$backup_file_checksum" # Backup the current file ynh_print_warn "File $file has been manually modified since the installation or last upgrade. So it has been duplicated in $backup_file_checksum" echo "$backup_file_checksum" # Return the name of the backup file fi @@ -394,7 +394,7 @@ ynh_backup_before_upgrade () { if [ "$NO_BACKUP_UPGRADE" -eq 0 ] then # Check if a backup already exists with the prefix 1 - if sudo yunohost backup list | grep -q $app_bck-pre-upgrade1 + if yunohost backup list | grep -q $app_bck-pre-upgrade1 then # Prefix becomes 2 to preserve the previous backup backup_number=2 @@ -402,14 +402,14 @@ ynh_backup_before_upgrade () { fi # Create backup - sudo BACKUP_CORE_ONLY=1 yunohost backup create --apps $app --name $app_bck-pre-upgrade$backup_number --debug + BACKUP_CORE_ONLY=1 yunohost backup create --apps $app --name $app_bck-pre-upgrade$backup_number --debug if [ "$?" -eq 0 ] then # If the backup succeeded, remove the previous backup - if sudo yunohost backup list | grep -q $app_bck-pre-upgrade$old_backup_number + if yunohost backup list | grep -q $app_bck-pre-upgrade$old_backup_number then # Remove the previous backup only if it exists - sudo yunohost backup delete $app_bck-pre-upgrade$old_backup_number > /dev/null + yunohost backup delete $app_bck-pre-upgrade$old_backup_number > /dev/null fi else ynh_die --message="Backup failed, the upgrade process was aborted." @@ -438,12 +438,12 @@ ynh_restore_upgradebackup () { if [ "$NO_BACKUP_UPGRADE" -eq 0 ] then # Check if an existing backup can be found before removing and restoring the application. - if sudo yunohost backup list | grep -q $app_bck-pre-upgrade$backup_number + if yunohost backup list | grep -q $app_bck-pre-upgrade$backup_number then # Remove the application then restore it - sudo yunohost app remove $app + yunohost app remove $app # Restore the backup - sudo yunohost backup restore $app_bck-pre-upgrade$backup_number --apps $app --force --debug + yunohost backup restore $app_bck-pre-upgrade$backup_number --apps $app --force --debug ynh_die --message="The app was restored to the way it was before the failed upgrade." fi else diff --git a/data/helpers.d/logging b/data/helpers.d/logging index be33b75a5..89fb89c6e 100644 --- a/data/helpers.d/logging +++ b/data/helpers.d/logging @@ -46,10 +46,10 @@ ynh_print_info() { # Requires YunoHost version 2.6.4 or higher. ynh_no_log() { local ynh_cli_log=/var/log/yunohost/yunohost-cli.log - sudo cp -a ${ynh_cli_log} ${ynh_cli_log}-move + cp -a ${ynh_cli_log} ${ynh_cli_log}-move eval $@ local exit_code=$? - sudo mv ${ynh_cli_log}-move ${ynh_cli_log} + mv ${ynh_cli_log}-move ${ynh_cli_log} return $? } diff --git a/data/helpers.d/logrotate b/data/helpers.d/logrotate index 82cdee6a5..9e2429218 100644 --- a/data/helpers.d/logrotate +++ b/data/helpers.d/logrotate @@ -90,8 +90,8 @@ $logfile { $su_directive } EOF - sudo mkdir -p $(dirname "$logfile") # Create the log directory, if not exist - cat ${app}-logrotate | sudo $customtee /etc/logrotate.d/$app > /dev/null # Append this config to the existing config file, or replace the whole config file (depending on $customtee) + mkdir -p $(dirname "$logfile") # Create the log directory, if not exist + cat ${app}-logrotate | $customtee /etc/logrotate.d/$app > /dev/null # Append this config to the existing config file, or replace the whole config file (depending on $customtee) } # Remove the app's logrotate config. @@ -101,6 +101,6 @@ EOF # Requires YunoHost version 2.6.4 or higher. ynh_remove_logrotate () { if [ -e "/etc/logrotate.d/$app" ]; then - sudo rm "/etc/logrotate.d/$app" + rm "/etc/logrotate.d/$app" fi } diff --git a/data/helpers.d/mysql b/data/helpers.d/mysql index e9cf59b3c..91d4abcd2 100644 --- a/data/helpers.d/mysql +++ b/data/helpers.d/mysql @@ -44,7 +44,7 @@ ynh_mysql_execute_as_root() { ynh_handle_getopts_args "$@" database="${database:-}" - ynh_mysql_connect_as --user="root" --password="$(sudo cat $MYSQL_ROOT_PWD_FILE)" \ + ynh_mysql_connect_as --user="root" --password="$(cat $MYSQL_ROOT_PWD_FILE)" \ --database="$database" <<< "$sql" } @@ -65,7 +65,7 @@ ynh_mysql_execute_file_as_root() { ynh_handle_getopts_args "$@" database="${database:-}" - ynh_mysql_connect_as --user="root" --password="$(sudo cat $MYSQL_ROOT_PWD_FILE)" \ + ynh_mysql_connect_as --user="root" --password="$(cat $MYSQL_ROOT_PWD_FILE)" \ --database="$database" < "$file" } @@ -126,7 +126,7 @@ ynh_mysql_dump_db() { # Manage arguments with getopts ynh_handle_getopts_args "$@" - mysqldump -u "root" -p"$(sudo cat $MYSQL_ROOT_PWD_FILE)" --single-transaction --skip-dump-date "$database" + mysqldump -u "root" -p"$(cat $MYSQL_ROOT_PWD_FILE)" --single-transaction --skip-dump-date "$database" } # Create a user @@ -223,7 +223,7 @@ ynh_mysql_remove_db () { # Manage arguments with getopts ynh_handle_getopts_args "$@" - local mysql_root_password=$(sudo cat $MYSQL_ROOT_PWD_FILE) + local mysql_root_password=$(cat $MYSQL_ROOT_PWD_FILE) if mysqlshow -u root -p$mysql_root_password | grep -q "^| $db_name"; then # Check if the database exists ynh_mysql_drop_db $db_name # Remove the database else diff --git a/data/helpers.d/nginx b/data/helpers.d/nginx index ce6b61d3c..e3e45d2d4 100644 --- a/data/helpers.d/nginx +++ b/data/helpers.d/nginx @@ -22,7 +22,7 @@ ynh_add_nginx_config () { finalnginxconf="/etc/nginx/conf.d/$domain.d/$app.conf" local others_var=${1:-} ynh_backup_if_checksum_is_different --file="$finalnginxconf" - sudo cp ../conf/nginx.conf "$finalnginxconf" + cp ../conf/nginx.conf "$finalnginxconf" # To avoid a break by set -u, use a void substitution ${var:-}. If the variable is not set, it's simply set with an empty variable. # Substitute in a nginx config file only if the variable is not empty diff --git a/data/helpers.d/php b/data/helpers.d/php index c9e3ba9ed..41af467c5 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -28,12 +28,12 @@ ynh_add_fpm_config () { ynh_app_setting_set --app=$app --key=fpm_service --value="$fpm_service" finalphpconf="$fpm_config_dir/pool.d/$app.conf" ynh_backup_if_checksum_is_different --file="$finalphpconf" - sudo cp ../conf/php-fpm.conf "$finalphpconf" + cp ../conf/php-fpm.conf "$finalphpconf" ynh_replace_string --match_string="__NAMETOCHANGE__" --replace_string="$app" --target_file="$finalphpconf" ynh_replace_string --match_string="__FINALPATH__" --replace_string="$final_path" --target_file="$finalphpconf" ynh_replace_string --match_string="__USER__" --replace_string="$app" --target_file="$finalphpconf" ynh_replace_string --match_string="__PHPVERSION__" --replace_string="$phpversion" --target_file="$finalphpconf" - sudo chown root: "$finalphpconf" + chown root: "$finalphpconf" ynh_store_file_checksum --file="$finalphpconf" if [ -e "../conf/php-fpm.ini" ] @@ -41,8 +41,8 @@ ynh_add_fpm_config () { echo "Packagers ! Please do not use a separate php ini file, merge your directives in the pool file instead." >&2 finalphpini="$fpm_config_dir/conf.d/20-$app.ini" ynh_backup_if_checksum_is_different "$finalphpini" - sudo cp ../conf/php-fpm.ini "$finalphpini" - sudo chown root: "$finalphpini" + cp ../conf/php-fpm.ini "$finalphpini" + chown root: "$finalphpini" ynh_store_file_checksum "$finalphpini" fi ynh_systemd_action --service_name=$fpm_service --action=reload diff --git a/data/helpers.d/postgresql b/data/helpers.d/postgresql index d252ae2dc..6d8524e54 100644 --- a/data/helpers.d/postgresql +++ b/data/helpers.d/postgresql @@ -45,7 +45,7 @@ ynh_psql_execute_as_root() { ynh_handle_getopts_args "$@" database="${database:-}" - ynh_psql_connect_as --user="postgres" --password="$(sudo cat $PSQL_ROOT_PWD_FILE)" \ + ynh_psql_connect_as --user="postgres" --password="$(cat $PSQL_ROOT_PWD_FILE)" \ --database="$database" <<<"$sql" } @@ -66,7 +66,7 @@ ynh_psql_execute_file_as_root() { ynh_handle_getopts_args "$@" database="${database:-}" - ynh_psql_connect_as --user="postgres" --password="$(sudo cat $PSQL_ROOT_PWD_FILE)" \ + ynh_psql_connect_as --user="postgres" --password="$(cat $PSQL_ROOT_PWD_FILE)" \ --database="$database" <"$file" } @@ -160,7 +160,7 @@ ynh_psql_user_exists() { # Manage arguments with getopts ynh_handle_getopts_args "$@" - if ! sudo --login --user=postgres PGUSER="postgres" PGPASSWORD="$(sudo cat $PSQL_ROOT_PWD_FILE)" psql -tAc "SELECT rolname FROM pg_roles WHERE rolname='$user';" | grep --quiet "$user" ; then + if ! sudo --login --user=postgres PGUSER="postgres" PGPASSWORD="$(cat $PSQL_ROOT_PWD_FILE)" psql -tAc "SELECT rolname FROM pg_roles WHERE rolname='$user';" | grep --quiet "$user" ; then return 1 else return 0 @@ -179,7 +179,7 @@ ynh_psql_database_exists() { # Manage arguments with getopts ynh_handle_getopts_args "$@" - if ! sudo --login --user=postgres PGUSER="postgres" PGPASSWORD="$(sudo cat $PSQL_ROOT_PWD_FILE)" psql -tAc "SELECT datname FROM pg_database WHERE datname='$database';" | grep --quiet "$database"; then + if ! sudo --login --user=postgres PGUSER="postgres" PGPASSWORD="$(cat $PSQL_ROOT_PWD_FILE)" psql -tAc "SELECT datname FROM pg_database WHERE datname='$database';" | grep --quiet "$database"; then return 1 else return 0 @@ -243,7 +243,7 @@ ynh_psql_remove_db() { # Manage arguments with getopts ynh_handle_getopts_args "$@" - local psql_root_password=$(sudo cat $PSQL_ROOT_PWD_FILE) + local psql_root_password=$(cat $PSQL_ROOT_PWD_FILE) if ynh_psql_database_exists --database=$db_name; then # Check if the database exists ynh_psql_drop_db $db_name # Remove the database else diff --git a/data/helpers.d/setting b/data/helpers.d/setting index 9f68cb5d9..384fdc399 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -222,7 +222,7 @@ ynh_webpath_available () { # Manage arguments with getopts ynh_handle_getopts_args "$@" - sudo yunohost domain url-available $domain $path_url + yunohost domain url-available $domain $path_url } # Register/book a web path for an app @@ -245,7 +245,7 @@ ynh_webpath_register () { # Manage arguments with getopts ynh_handle_getopts_args "$@" - sudo yunohost app register-url $app $domain $path_url + yunohost app register-url $app $domain $path_url } # Create a new permission for the app diff --git a/data/helpers.d/string b/data/helpers.d/string index fcbc5190d..e50f781fe 100644 --- a/data/helpers.d/string +++ b/data/helpers.d/string @@ -49,7 +49,7 @@ ynh_replace_string () { match_string=${match_string//${delimit}/"\\${delimit}"} replace_string=${replace_string//${delimit}/"\\${delimit}"} - sudo sed --in-place "s${delimit}${match_string}${delimit}${replace_string}${delimit}g" "$target_file" + sed --in-place "s${delimit}${match_string}${delimit}${replace_string}${delimit}g" "$target_file" } # Substitute/replace a special string by another in a file diff --git a/data/helpers.d/systemd b/data/helpers.d/systemd index 105678b88..960382f8f 100644 --- a/data/helpers.d/systemd +++ b/data/helpers.d/systemd @@ -28,7 +28,7 @@ ynh_add_systemd_config () { finalsystemdconf="/etc/systemd/system/$service.service" ynh_backup_if_checksum_is_different --file="$finalsystemdconf" - sudo cp ../conf/$template "$finalsystemdconf" + cp ../conf/$template "$finalsystemdconf" # To avoid a break by set -u, use a void substitution ${var:-}. If the variable is not set, it's simply set with an empty variable. # Substitute in a nginx config file only if the variable is not empty @@ -40,9 +40,9 @@ ynh_add_systemd_config () { fi ynh_store_file_checksum --file="$finalsystemdconf" - sudo chown root: "$finalsystemdconf" - sudo systemctl enable $service - sudo systemctl daemon-reload + chown root: "$finalsystemdconf" + systemctl enable $service + systemctl daemon-reload } # Remove the dedicated systemd config diff --git a/data/helpers.d/user b/data/helpers.d/user index e7890ccb2..7051ed4c0 100644 --- a/data/helpers.d/user +++ b/data/helpers.d/user @@ -16,7 +16,7 @@ ynh_user_exists() { # Manage arguments with getopts ynh_handle_getopts_args "$@" - sudo yunohost user list --output-as json | grep -q "\"username\": \"${username}\"" + yunohost user list --output-as json | grep -q "\"username\": \"${username}\"" } # Retrieve a YunoHost user information @@ -38,7 +38,7 @@ ynh_user_get_info() { # Manage arguments with getopts ynh_handle_getopts_args "$@" - sudo yunohost user info "$username" --output-as plain | ynh_get_plain_key "$key" + yunohost user info "$username" --output-as plain | ynh_get_plain_key "$key" } # Get the list of YunoHost users @@ -50,7 +50,7 @@ ynh_user_get_info() { # # Requires YunoHost version 2.4.0 or higher. ynh_user_list() { - sudo yunohost user list --output-as plain --quiet \ + yunohost user list --output-as plain --quiet \ | awk '/^##username$/{getline; print}' } diff --git a/data/hooks/backup/05-conf_ldap b/data/hooks/backup/05-conf_ldap index 9ae22095e..75b4c2075 100755 --- a/data/hooks/backup/05-conf_ldap +++ b/data/hooks/backup/05-conf_ldap @@ -11,7 +11,7 @@ backup_dir="${1}/conf/ldap" # Backup the configuration ynh_backup "/etc/ldap/slapd.conf" "${backup_dir}/slapd.conf" -sudo slapcat -b cn=config -l "${backup_dir}/cn=config.master.ldif" +slapcat -b cn=config -l "${backup_dir}/cn=config.master.ldif" # Backup the database -sudo slapcat -b dc=yunohost,dc=org -l "${backup_dir}/dc=yunohost-dc=org.ldif" +slapcat -b dc=yunohost,dc=org -l "${backup_dir}/dc=yunohost-dc=org.ldif" diff --git a/data/hooks/conf_regen/01-yunohost b/data/hooks/conf_regen/01-yunohost index f22de7a53..1abfca35e 100755 --- a/data/hooks/conf_regen/01-yunohost +++ b/data/hooks/conf_regen/01-yunohost @@ -38,25 +38,25 @@ do_pre_regen() { if [[ -f $services_path ]]; then tmp_services_path="${services_path}-tmp" new_services_path="${services_path}-new" - sudo cp "$services_path" "$tmp_services_path" + cp "$services_path" "$tmp_services_path" _update_services "$new_services_path" || { - sudo mv "$tmp_services_path" "$services_path" + mv "$tmp_services_path" "$services_path" exit 1 } if [[ -f $new_services_path ]]; then # replace services.yml with new one - sudo mv "$new_services_path" "$services_path" - sudo mv "$tmp_services_path" "${services_path}-old" + mv "$new_services_path" "$services_path" + mv "$tmp_services_path" "${services_path}-old" else - sudo rm -f "$tmp_services_path" + rm -f "$tmp_services_path" fi else - sudo cp services.yml /etc/yunohost/services.yml + cp services.yml /etc/yunohost/services.yml fi } _update_services() { - sudo python2 - << EOF + python2 - << EOF import yaml diff --git a/data/hooks/conf_regen/02-ssl b/data/hooks/conf_regen/02-ssl index 1df3a3260..a893b21e1 100755 --- a/data/hooks/conf_regen/02-ssl +++ b/data/hooks/conf_regen/02-ssl @@ -99,13 +99,13 @@ do_post_regen() { [[ -f "${index_txt}" ]] || { if [[ -f "${index_txt}.saved" ]]; then # use saved database from 2.2 - sudo cp "${index_txt}.saved" "${index_txt}" + cp "${index_txt}.saved" "${index_txt}" elif [[ -f "${index_txt}.old" ]]; then # ... or use the state-1 database - sudo cp "${index_txt}.old" "${index_txt}" + cp "${index_txt}.old" "${index_txt}" else # ... or create an empty one - sudo touch "${index_txt}" + touch "${index_txt}" fi } diff --git a/data/hooks/conf_regen/06-slapd b/data/hooks/conf_regen/06-slapd index 50149392b..2fa108baa 100755 --- a/data/hooks/conf_regen/06-slapd +++ b/data/hooks/conf_regen/06-slapd @@ -127,7 +127,7 @@ do_post_regen() { # wait a maximum time of 5 minutes # yes, force-reload behave like a restart number_of_wait=0 - while ! sudo su admin -c '' && ((number_of_wait < 60)) + while ! su admin -c '' && ((number_of_wait < 60)) do sleep 5 ((number_of_wait += 1)) diff --git a/data/hooks/conf_regen/09-nslcd b/data/hooks/conf_regen/09-nslcd index 5071ac1fd..7090fc758 100755 --- a/data/hooks/conf_regen/09-nslcd +++ b/data/hooks/conf_regen/09-nslcd @@ -14,7 +14,7 @@ do_post_regen() { regen_conf_files=$1 [[ -z "$regen_conf_files" ]] \ - || sudo service nslcd restart + || service nslcd restart } FORCE=${2:-0} diff --git a/data/hooks/conf_regen/12-metronome b/data/hooks/conf_regen/12-metronome index 7047af660..fbd956e7c 100755 --- a/data/hooks/conf_regen/12-metronome +++ b/data/hooks/conf_regen/12-metronome @@ -14,7 +14,7 @@ do_pre_regen() { # retrieve variables main_domain=$(cat /etc/yunohost/current_host) - domain_list=$(sudo yunohost domain list --output-as plain --quiet) + domain_list=$(yunohost domain list --output-as plain --quiet) # install main conf file cat metronome.cfg.lua \ @@ -42,19 +42,19 @@ do_post_regen() { regen_conf_files=$1 # retrieve variables - domain_list=$(sudo yunohost domain list --output-as plain --quiet) + domain_list=$(yunohost domain list --output-as plain --quiet) # create metronome directories for domains for domain in $domain_list; do - sudo mkdir -p "/var/lib/metronome/${domain//./%2e}/pep" + mkdir -p "/var/lib/metronome/${domain//./%2e}/pep" done # fix some permissions - sudo chown -R metronome: /var/lib/metronome/ - sudo chown -R metronome: /etc/metronome/conf.d/ + chown -R metronome: /var/lib/metronome/ + chown -R metronome: /etc/metronome/conf.d/ [[ -z "$regen_conf_files" ]] \ - || sudo service metronome restart + || service metronome restart } FORCE=${2:-0} diff --git a/data/hooks/conf_regen/15-nginx b/data/hooks/conf_regen/15-nginx index 59654a771..55a5494b2 100755 --- a/data/hooks/conf_regen/15-nginx +++ b/data/hooks/conf_regen/15-nginx @@ -45,7 +45,7 @@ do_pre_regen() { # retrieve variables main_domain=$(cat /etc/yunohost/current_host) - domain_list=$(sudo yunohost domain list --output-as plain --quiet) + domain_list=$(yunohost domain list --output-as plain --quiet) # Support different strategy for security configurations export compatibility="$(yunohost settings get 'security.nginx.compatibility')" @@ -102,15 +102,15 @@ do_post_regen() { [ -z "$regen_conf_files" ] && exit 0 # retrieve variables - domain_list=$(sudo yunohost domain list --output-as plain --quiet) + domain_list=$(yunohost domain list --output-as plain --quiet) # create NGINX conf directories for domains for domain in $domain_list; do - sudo mkdir -p "/etc/nginx/conf.d/${domain}.d" + mkdir -p "/etc/nginx/conf.d/${domain}.d" done # Reload nginx configuration - pgrep nginx && sudo service nginx reload + pgrep nginx && service nginx reload } FORCE=${2:-0} diff --git a/data/hooks/conf_regen/19-postfix b/data/hooks/conf_regen/19-postfix index b37425984..0f09f0299 100755 --- a/data/hooks/conf_regen/19-postfix +++ b/data/hooks/conf_regen/19-postfix @@ -20,7 +20,7 @@ do_pre_regen() { # prepare main.cf conf file main_domain=$(cat /etc/yunohost/current_host) - domain_list=$(sudo yunohost domain list --output-as plain --quiet | tr '\n' ' ') + domain_list=$(yunohost domain list --output-as plain --quiet | tr '\n' ' ') # Support different strategy for security configurations export compatibility="$(yunohost settings get 'security.postfix.compatibility')" @@ -49,7 +49,7 @@ do_post_regen() { regen_conf_files=$1 [[ -z "$regen_conf_files" ]] \ - || { sudo service postfix restart && sudo service postsrsd restart; } + || { service postfix restart && service postsrsd restart; } } diff --git a/data/hooks/conf_regen/25-dovecot b/data/hooks/conf_regen/25-dovecot index 4c5ae24c1..2638c7f6f 100755 --- a/data/hooks/conf_regen/25-dovecot +++ b/data/hooks/conf_regen/25-dovecot @@ -35,28 +35,28 @@ do_pre_regen() { do_post_regen() { regen_conf_files=$1 - sudo mkdir -p "/etc/dovecot/yunohost.d/pre-ext.d" - sudo mkdir -p "/etc/dovecot/yunohost.d/post-ext.d" + mkdir -p "/etc/dovecot/yunohost.d/pre-ext.d" + mkdir -p "/etc/dovecot/yunohost.d/post-ext.d" # create vmail user id vmail > /dev/null 2>&1 \ - || sudo adduser --system --ingroup mail --uid 500 vmail + || adduser --system --ingroup mail --uid 500 vmail # fix permissions - sudo chown -R vmail:mail /etc/dovecot/global_script - sudo chmod 770 /etc/dovecot/global_script - sudo chown root:mail /var/mail - sudo chmod 1775 /var/mail + chown -R vmail:mail /etc/dovecot/global_script + chmod 770 /etc/dovecot/global_script + chown root:mail /var/mail + chmod 1775 /var/mail [ -z "$regen_conf_files" ] && exit 0 # compile sieve script [[ "$regen_conf_files" =~ dovecot\.sieve ]] && { - sudo sievec /etc/dovecot/global_script/dovecot.sieve - sudo chown -R vmail:mail /etc/dovecot/global_script + sievec /etc/dovecot/global_script/dovecot.sieve + chown -R vmail:mail /etc/dovecot/global_script } - sudo service dovecot restart + service dovecot restart } FORCE=${2:-0} diff --git a/data/hooks/conf_regen/31-rspamd b/data/hooks/conf_regen/31-rspamd index d263d9cc9..26fea4336 100755 --- a/data/hooks/conf_regen/31-rspamd +++ b/data/hooks/conf_regen/31-rspamd @@ -22,11 +22,11 @@ do_post_regen() { ## # create DKIM directory with proper permission - sudo mkdir -p /etc/dkim - sudo chown _rspamd /etc/dkim + mkdir -p /etc/dkim + chown _rspamd /etc/dkim # retrieve domain list - domain_list=$(sudo yunohost domain list --output-as plain --quiet) + domain_list=$(yunohost domain list --output-as plain --quiet) # create DKIM key for domains for domain in $domain_list; do @@ -34,30 +34,30 @@ do_post_regen() { [ ! -f "$domain_key" ] && { # We use a 1024 bit size because nsupdate doesn't seem to be able to # handle 2048... - sudo opendkim-genkey --domain="$domain" \ + opendkim-genkey --domain="$domain" \ --selector=mail --directory=/etc/dkim -b 1024 - sudo mv /etc/dkim/mail.private "$domain_key" - sudo mv /etc/dkim/mail.txt "/etc/dkim/${domain}.mail.txt" + mv /etc/dkim/mail.private "$domain_key" + mv /etc/dkim/mail.txt "/etc/dkim/${domain}.mail.txt" } done # fix DKIM keys permissions - sudo chown _rspamd /etc/dkim/*.mail.key - sudo chmod 400 /etc/dkim/*.mail.key + chown _rspamd /etc/dkim/*.mail.key + chmod 400 /etc/dkim/*.mail.key regen_conf_files=$1 [ -z "$regen_conf_files" ] && exit 0 # compile sieve script [[ "$regen_conf_files" =~ rspamd\.sieve ]] && { - sudo sievec /etc/dovecot/global_script/rspamd.sieve - sudo chown -R vmail:mail /etc/dovecot/global_script - sudo systemctl restart dovecot + sievec /etc/dovecot/global_script/rspamd.sieve + chown -R vmail:mail /etc/dovecot/global_script + systemctl restart dovecot } # Restart rspamd due to the upgrade # https://rspamd.com/announce/2016/08/01/rspamd-1.3.1.html - sudo systemctl -q restart rspamd.service + systemctl -q restart rspamd.service } FORCE=${2:-0} diff --git a/data/hooks/conf_regen/34-mysql b/data/hooks/conf_regen/34-mysql index 8f7b5455e..43f9fdde1 100755 --- a/data/hooks/conf_regen/34-mysql +++ b/data/hooks/conf_regen/34-mysql @@ -18,12 +18,12 @@ do_post_regen() { if [ ! -f /etc/yunohost/mysql ]; then # ensure that mysql is running - sudo systemctl -q is-active mysql.service \ - || sudo service mysql start + systemctl -q is-active mysql.service \ + || service mysql start # generate and set new root password mysql_password=$(ynh_string_random 10) - sudo mysqladmin -s -u root -pyunohost password "$mysql_password" || { + mysqladmin -s -u root -pyunohost password "$mysql_password" || { if [ $FORCE -eq 1 ]; then echo "It seems that you have already configured MySQL." \ "YunoHost needs to have a root access to MySQL to runs its" \ @@ -31,13 +31,13 @@ do_post_regen() { "You can find this new password in /etc/yunohost/mysql." >&2 # set new password with debconf - sudo debconf-set-selections << EOF + debconf-set-selections << EOF $MYSQL_PKG mysql-server/root_password password $mysql_password $MYSQL_PKG mysql-server/root_password_again password $mysql_password EOF # reconfigure Debian package - sudo dpkg-reconfigure -freadline -u "$MYSQL_PKG" 2>&1 + dpkg-reconfigure -freadline -u "$MYSQL_PKG" 2>&1 else echo "It seems that you have already configured MySQL." \ "YunoHost needs to have a root access to MySQL to runs its" \ @@ -49,12 +49,12 @@ EOF } # store new root password - echo "$mysql_password" | sudo tee /etc/yunohost/mysql - sudo chmod 400 /etc/yunohost/mysql + echo "$mysql_password" | tee /etc/yunohost/mysql + chmod 400 /etc/yunohost/mysql fi [[ -z "$regen_conf_files" ]] \ - || sudo service mysql restart + || service mysql restart } FORCE=${2:-0} diff --git a/data/hooks/conf_regen/37-avahi-daemon b/data/hooks/conf_regen/37-avahi-daemon index 655a2e054..239c3ad0c 100755 --- a/data/hooks/conf_regen/37-avahi-daemon +++ b/data/hooks/conf_regen/37-avahi-daemon @@ -15,7 +15,7 @@ do_post_regen() { regen_conf_files=$1 [[ -z "$regen_conf_files" ]] \ - || sudo service avahi-daemon restart + || service avahi-daemon restart } FORCE=${2:-0} diff --git a/data/hooks/conf_regen/40-glances b/data/hooks/conf_regen/40-glances index a19d35d56..70b8f4b5a 100755 --- a/data/hooks/conf_regen/40-glances +++ b/data/hooks/conf_regen/40-glances @@ -14,7 +14,7 @@ do_post_regen() { regen_conf_files=$1 [[ -z "$regen_conf_files" ]] \ - || sudo service glances restart + || service glances restart } FORCE=${2:-0} diff --git a/data/hooks/conf_regen/43-dnsmasq b/data/hooks/conf_regen/43-dnsmasq index ed795c058..90e96a04c 100755 --- a/data/hooks/conf_regen/43-dnsmasq +++ b/data/hooks/conf_regen/43-dnsmasq @@ -26,7 +26,7 @@ do_pre_regen() { ynh_validate_ip4 "$ipv4" || ipv4='127.0.0.1' ipv6=$(curl -s -6 https://ip6.yunohost.org 2>/dev/null || true) ynh_validate_ip6 "$ipv6" || ipv6='' - domain_list=$(sudo yunohost domain list --output-as plain --quiet) + domain_list=$(yunohost domain list --output-as plain --quiet) # add domain conf files for domain in $domain_list; do @@ -51,7 +51,7 @@ do_post_regen() { regen_conf_files=$1 [[ -z "$regen_conf_files" ]] \ - || sudo service dnsmasq restart + || service dnsmasq restart } FORCE=${2:-0} diff --git a/data/hooks/conf_regen/46-nsswitch b/data/hooks/conf_regen/46-nsswitch index 06a596e44..fa9b07511 100755 --- a/data/hooks/conf_regen/46-nsswitch +++ b/data/hooks/conf_regen/46-nsswitch @@ -14,7 +14,7 @@ do_post_regen() { regen_conf_files=$1 [[ -z "$regen_conf_files" ]] \ - || sudo service unscd restart + || service unscd restart } FORCE=${2:-0} diff --git a/data/hooks/conf_regen/52-fail2ban b/data/hooks/conf_regen/52-fail2ban index 950f27b5b..3cb499db7 100755 --- a/data/hooks/conf_regen/52-fail2ban +++ b/data/hooks/conf_regen/52-fail2ban @@ -20,7 +20,7 @@ do_post_regen() { regen_conf_files=$1 [[ -z "$regen_conf_files" ]] \ - || sudo service fail2ban restart + || service fail2ban restart } FORCE=${2:-0} diff --git a/data/hooks/restore/05-conf_ldap b/data/hooks/restore/05-conf_ldap index eb6824993..74093136d 100644 --- a/data/hooks/restore/05-conf_ldap +++ b/data/hooks/restore/05-conf_ldap @@ -5,7 +5,7 @@ if [[ $EUID -ne 0 ]]; then # We need to execute this script as root, since the ldap # service will be shut down during the operation (and sudo # won't be available) - sudo /bin/bash $(readlink -f $0) $1 + /bin/bash $(readlink -f $0) $1 else diff --git a/data/hooks/restore/08-conf_ssh b/data/hooks/restore/08-conf_ssh index 0c0f9bf9b..4b69d1696 100644 --- a/data/hooks/restore/08-conf_ssh +++ b/data/hooks/restore/08-conf_ssh @@ -1,8 +1,8 @@ backup_dir="$1/conf/ssh" if [ -d /etc/ssh/ ]; then - sudo cp -a $backup_dir/. /etc/ssh - sudo service ssh restart + cp -a $backup_dir/. /etc/ssh + service ssh restart else echo "SSH is not installed" fi diff --git a/data/hooks/restore/11-conf_ynh_mysql b/data/hooks/restore/11-conf_ynh_mysql index 24cdb1e79..f54641d6f 100644 --- a/data/hooks/restore/11-conf_ynh_mysql +++ b/data/hooks/restore/11-conf_ynh_mysql @@ -9,15 +9,15 @@ service mysql status >/dev/null 2>&1 \ # retrieve current and new password [ -f /etc/yunohost/mysql ] \ - && curr_pwd=$(sudo cat /etc/yunohost/mysql) -new_pwd=$(sudo cat "${backup_dir}/root_pwd" || sudo cat "${backup_dir}/mysql") + && curr_pwd=$(cat /etc/yunohost/mysql) +new_pwd=$(cat "${backup_dir}/root_pwd" || cat "${backup_dir}/mysql") [ -z "$curr_pwd" ] && curr_pwd="yunohost" [ -z "$new_pwd" ] && { new_pwd=$(ynh_string_random 10) } # attempt to change it -sudo mysqladmin -s -u root -p"$curr_pwd" password "$new_pwd" || { +mysqladmin -s -u root -p"$curr_pwd" password "$new_pwd" || { echo "It seems that you have already configured MySQL." \ "YunoHost needs to have a root access to MySQL to runs its" \ @@ -25,18 +25,18 @@ sudo mysqladmin -s -u root -p"$curr_pwd" password "$new_pwd" || { "You can find this new password in /etc/yunohost/mysql." >&2 # set new password with debconf - sudo debconf-set-selections << EOF + debconf-set-selections << EOF $MYSQL_PKG mysql-server/root_password password $new_pwd $MYSQL_PKG mysql-server/root_password_again password $new_pwd EOF # reconfigure Debian package - sudo dpkg-reconfigure -freadline -u "$MYSQL_PKG" 2>&1 + dpkg-reconfigure -freadline -u "$MYSQL_PKG" 2>&1 } # store new root password -echo "$new_pwd" | sudo tee /etc/yunohost/mysql -sudo chmod 400 /etc/yunohost/mysql +echo "$new_pwd" | tee /etc/yunohost/mysql +chmod 400 /etc/yunohost/mysql # reload the grant tables -sudo mysqladmin -s -u root -p"$new_pwd" reload +mysqladmin -s -u root -p"$new_pwd" reload diff --git a/data/hooks/restore/14-conf_ssowat b/data/hooks/restore/14-conf_ssowat index 01ac787ee..71a011488 100644 --- a/data/hooks/restore/14-conf_ssowat +++ b/data/hooks/restore/14-conf_ssowat @@ -1,3 +1,3 @@ backup_dir="$1/conf/ssowat" -sudo cp -a $backup_dir/. /etc/ssowat +cp -a $backup_dir/. /etc/ssowat diff --git a/data/hooks/restore/17-data_home b/data/hooks/restore/17-data_home index a7ba2733c..6226eab6d 100644 --- a/data/hooks/restore/17-data_home +++ b/data/hooks/restore/17-data_home @@ -1,3 +1,3 @@ backup_dir="$1/data/home" -sudo cp -a $backup_dir/. /home +cp -a $backup_dir/. /home diff --git a/data/hooks/restore/20-conf_ynh_firewall b/data/hooks/restore/20-conf_ynh_firewall index c0ee18818..1789aed1e 100644 --- a/data/hooks/restore/20-conf_ynh_firewall +++ b/data/hooks/restore/20-conf_ynh_firewall @@ -1,4 +1,4 @@ backup_dir="$1/conf/ynh/firewall" -sudo cp -a $backup_dir/. /etc/yunohost -sudo yunohost firewall reload +cp -a $backup_dir/. /etc/yunohost +yunohost firewall reload diff --git a/data/hooks/restore/21-conf_ynh_certs b/data/hooks/restore/21-conf_ynh_certs index 34e651319..983bfb5a1 100644 --- a/data/hooks/restore/21-conf_ynh_certs +++ b/data/hooks/restore/21-conf_ynh_certs @@ -1,7 +1,7 @@ backup_dir="$1/conf/ynh/certs" -sudo mkdir -p /etc/yunohost/certs/ +mkdir -p /etc/yunohost/certs/ -sudo cp -a $backup_dir/. /etc/yunohost/certs/ -sudo service nginx reload -sudo service metronome reload +cp -a $backup_dir/. /etc/yunohost/certs/ +service nginx reload +service metronome reload diff --git a/data/hooks/restore/23-data_mail b/data/hooks/restore/23-data_mail index 81b9b923f..f9fd6e699 100644 --- a/data/hooks/restore/23-data_mail +++ b/data/hooks/restore/23-data_mail @@ -1,8 +1,8 @@ backup_dir="$1/data/mail" -sudo cp -a $backup_dir/. /var/mail/ || echo 'No mail found' -sudo chown -R vmail:mail /var/mail/ +cp -a $backup_dir/. /var/mail/ || echo 'No mail found' +chown -R vmail:mail /var/mail/ # Restart services to use migrated certs -sudo service postfix restart -sudo service dovecot restart +service postfix restart +service dovecot restart diff --git a/data/hooks/restore/26-conf_xmpp b/data/hooks/restore/26-conf_xmpp index 61692b316..a300a7268 100644 --- a/data/hooks/restore/26-conf_xmpp +++ b/data/hooks/restore/26-conf_xmpp @@ -1,7 +1,7 @@ backup_dir="$1/conf/xmpp" -sudo cp -a $backup_dir/etc/. /etc/metronome -sudo cp -a $backup_dir/var/. /var/lib/metronome +cp -a $backup_dir/etc/. /etc/metronome +cp -a $backup_dir/var/. /var/lib/metronome # Restart to apply new conf and certs -sudo service metronome restart +service metronome restart diff --git a/data/hooks/restore/29-conf_nginx b/data/hooks/restore/29-conf_nginx index 0795f53df..7288f52f3 100644 --- a/data/hooks/restore/29-conf_nginx +++ b/data/hooks/restore/29-conf_nginx @@ -1,7 +1,7 @@ backup_dir="$1/conf/nginx" # Copy all conf except apps specific conf located in DOMAIN.d -sudo find $backup_dir/ -mindepth 1 -maxdepth 1 -name '*.d' -or -exec sudo cp -a {} /etc/nginx/conf.d/ \; +find $backup_dir/ -mindepth 1 -maxdepth 1 -name '*.d' -or -exec cp -a {} /etc/nginx/conf.d/ \; # Restart to use new conf and certs -sudo service nginx restart +service nginx restart diff --git a/data/hooks/restore/32-conf_cron b/data/hooks/restore/32-conf_cron index 68657963e..59a2bde61 100644 --- a/data/hooks/restore/32-conf_cron +++ b/data/hooks/restore/32-conf_cron @@ -1,6 +1,6 @@ backup_dir="$1/conf/cron" -sudo cp -a $backup_dir/. /etc/cron.d +cp -a $backup_dir/. /etc/cron.d # Restart just in case -sudo service cron restart +service cron restart diff --git a/data/hooks/restore/40-conf_ynh_currenthost b/data/hooks/restore/40-conf_ynh_currenthost index a0bdf94d3..700e806b4 100644 --- a/data/hooks/restore/40-conf_ynh_currenthost +++ b/data/hooks/restore/40-conf_ynh_currenthost @@ -1,3 +1,3 @@ backup_dir="$1/conf/ynh" -sudo cp -a "${backup_dir}/current_host" /etc/yunohost/current_host +cp -a "${backup_dir}/current_host" /etc/yunohost/current_host diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index f4bb83c15..a3aa26fc5 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -233,9 +233,9 @@ def _set_hostname(hostname, pretty_hostname=None): # Then call hostnamectl commands = [ - "sudo hostnamectl --static set-hostname".split() + [hostname], - "sudo hostnamectl --transient set-hostname".split() + [hostname], - "sudo hostnamectl --pretty set-hostname".split() + [pretty_hostname] + "hostnamectl --static set-hostname".split() + [hostname], + "hostnamectl --transient set-hostname".split() + [hostname], + "hostnamectl --pretty set-hostname".split() + [pretty_hostname] ] for command in commands: From f56f4724c36a5261d53c8c78f30d62c12f85fe0e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 22 Mar 2020 01:23:55 +0100 Subject: [PATCH 0872/3170] Attempt to anonymize data pasted to paste.yunohost.org (in particular domain names) --- src/yunohost/utils/yunopaste.py | 45 +++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/yunohost/utils/yunopaste.py b/src/yunohost/utils/yunopaste.py index 89c62d761..530295735 100644 --- a/src/yunohost/utils/yunopaste.py +++ b/src/yunohost/utils/yunopaste.py @@ -2,14 +2,23 @@ import requests import json +import logging +from yunohost.domain import _get_maindomain, domain_list +from yunohost.utils.network import get_public_ip from yunohost.utils.error import YunohostError +logger = logging.getLogger('yunohost.utils.yunopaste') def yunopaste(data): paste_server = "https://paste.yunohost.org" + try: + data = anonymize(data) + except Exception as e: + logger.warning("For some reason, YunoHost was not able to anonymize the pasted data. Sorry about that. Be careful about sharing the link, as it may contain somewhat private infos like domain names or IP addresses. Error: %s" % e) + try: r = requests.post("%s/documents" % paste_server, data=data, timeout=30) except Exception as e: @@ -24,3 +33,39 @@ def yunopaste(data): raise YunohostError("Uhoh, couldn't parse the answer from paste.yunohost.org : %s" % r.text, raw_msg=True) return "%s/raw/%s" % (paste_server, url) + + +def anonymize(data): + + # First, let's replace every occurence of the main domain by "domain.tld" + # This should cover a good fraction of the info leaked + main_domain = _get_maindomain() + data = data.replace(main_domain, "maindomain.tld") + + # Next, let's replace other domains. We do this in increasing lengths, + # because e.g. knowing that the domain is a sub-domain of another domain may + # still be informative. + # So e.g. if there's jitsi.foobar.com as a subdomain of foobar.com, it may + # be interesting to know that the log is about a supposedly dedicated domain + # for jisti (hopefully this explanation make sense). + domains = domain_list()["domains"] + domains = sorted(domains, key=lambda d: len(d)) + + count = 2 + for domain in domains: + if domain not in data: + continue + data = data.replace(domain, "domain%s.tld" % count) + count += 1 + + # We also want to anonymize the ips + ipv4 = get_public_ip() + ipv6 = get_public_ip(6) + + if ipv4: + data = data.replace(str(ipv4), "xx.xx.xx.xx") + + if ipv6: + data = data.replace(str(ipv6), "xx:xx:xx:xx:xx:xx") + + return data From 210d5f3fc4b5ce5630ad81b795828377fbf4575e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 22 Mar 2020 01:28:37 +0100 Subject: [PATCH 0873/3170] [enh] Tell apt to explain what's wrong when there are unmet dependencies (#889) * Ask apt to explain what's wrong when dependencies fail to install * Add comment explaining the syntax Co-Authored-By: Maniack Crudelis Co-authored-by: Maniack Crudelis --- data/helpers.d/apt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index 55c85c90b..b2c781faf 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -186,7 +186,10 @@ ynh_package_install_from_equivs () { (cd "$TMPDIR" equivs-build ./control 1> /dev/null dpkg --force-depends -i "./${pkgname}_${pkgversion}_all.deb" 2>&1) - ynh_package_install -f || ynh_die --message="Unable to install dependencies" + # If install fails we use "apt-get check" to try to debug and diagnose possible unmet dependencies + # Note the use of { } which allows to group commands without starting a subshell (otherwise the ynh_die wouldn't exit the current shell). + # Be careful with the syntax : the semicolon + space at the end is important! + ynh_package_install -f || { apt-get check 2>&1; ynh_die --message="Unable to install dependencies"; } [[ -n "$TMPDIR" ]] && rm -rf $TMPDIR # Remove the temp dir. # check if the package is actually installed From d17fcaf94f9bb2f9f601033ccd700ce4917f98e3 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 23 Mar 2020 19:35:41 +0100 Subject: [PATCH 0874/3170] When dumping debug info after app script failure, be slightly smarter and stop at ynh_die to have more meaningul lines being shown --- 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 3feca796e..21e31d34d 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1139,7 +1139,7 @@ def dump_app_log_extract_for_debugging(operation_logger): line = line.strip().split(": ", 1)[1] lines_to_display.append(line) - if line.endswith("+ ynh_exit_properly"): + if line.endswith("+ ynh_exit_properly") or " + ynh_die " in line: break elif len(lines_to_display) > 20: lines_to_display.pop(0) From af8981e4e033d7426700333020fbbfe27455222c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 30 Mar 2020 20:54:57 +0200 Subject: [PATCH 0875/3170] Lazy loading might improve performances a bit --- src/yunohost/domain.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 3f906748b..18c4bd8e2 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -32,8 +32,7 @@ from moulinette.core import MoulinetteError from yunohost.utils.error import YunohostError from moulinette.utils.log import getActionLogger -import yunohost.certificate - +from yunohost.app import app_ssowatconf from yunohost.regenconf import regen_conf from yunohost.utils.network import get_public_ip from yunohost.log import is_unit_operation @@ -105,6 +104,7 @@ def domain_add(operation_logger, domain, dyndns=False): dyndns_subscribe(domain=domain) try: + import yunohost.certificate yunohost.certificate._certificate_install_selfsigned([domain], False) attr_dict = { @@ -234,14 +234,17 @@ def domain_dns_conf(domain, ttl=None): def domain_cert_status(domain_list, full=False): + import yunohost.certificate return yunohost.certificate.certificate_status(domain_list, full) def domain_cert_install(domain_list, force=False, no_checks=False, self_signed=False, staging=False): + import yunohost.certificate return yunohost.certificate.certificate_install(domain_list, force, no_checks, self_signed, staging) def domain_cert_renew(domain_list, force=False, no_checks=False, email=False, staging=False): + import yunohost.certificate return yunohost.certificate.certificate_renew(domain_list, force, no_checks, email, staging) From 7d3238140c0913641cd2b5405c7b759659b50567 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 3 Apr 2020 00:12:58 +0200 Subject: [PATCH 0876/3170] Force locale to C/en to avoid perl whining and flooding logs about the damn missing locale --- data/helpers.d/apt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index b2c781faf..7859d44c5 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -94,7 +94,7 @@ ynh_package_version() { # Requires YunoHost version 2.4.0.3 or higher. ynh_apt() { ynh_wait_dpkg_free - DEBIAN_FRONTEND=noninteractive apt-get -y $@ + LC_ALL=C DEBIAN_FRONTEND=noninteractive apt-get -y $@ } # Update package index files @@ -184,7 +184,7 @@ ynh_package_install_from_equivs () { ynh_wait_dpkg_free cp "$controlfile" "${TMPDIR}/control" (cd "$TMPDIR" - equivs-build ./control 1> /dev/null + LC_ALL=C equivs-build ./control 1> /dev/null dpkg --force-depends -i "./${pkgname}_${pkgversion}_all.deb" 2>&1) # If install fails we use "apt-get check" to try to debug and diagnose possible unmet dependencies # Note the use of { } which allows to group commands without starting a subshell (otherwise the ynh_die wouldn't exit the current shell). From 1eef9b6760f70d86ea58edad17f0ef76abd36085 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 3 Apr 2020 01:32:05 +0200 Subject: [PATCH 0877/3170] Do not redact stuff corresponding to --manifest_key --- src/yunohost/log.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/yunohost/log.py b/src/yunohost/log.py index 72e497b5d..cd08bdfe0 100644 --- a/src/yunohost/log.py +++ b/src/yunohost/log.py @@ -315,9 +315,9 @@ class RedactingFormatter(Formatter): try: # This matches stuff like db_pwd=the_secret or admin_password=other_secret # (the secret part being at least 3 chars to avoid catching some lines like just "db_pwd=") - # For 'key', we require to at least have one word char [a-zA-Z0-9_] before it to avoid catching "--key" used in many helpers - match = re.search(r'(pwd|pass|password|secret|\wkey|token)=(\S{3,})$', record.strip()) - if match and match.group(2) not in self.data_to_redact: + # Some names like "key" or "manifest_key" are ignored, used in helpers like ynh_app_setting_set or ynh_read_manifest + match = re.search(r'(pwd|pass|password|secret|\w+key|token)=(\S{3,})$', record.strip()) + if match and match.group(2) not in self.data_to_redact and match.group(1) not in ["key", "manifest_key"]: self.data_to_redact.append(match.group(2)) except Exception as e: logger.warning("Failed to parse line to try to identify data to redact ... : %s" % e) From a886053de76927d6186bad1c5a05bd33ff31bd4f Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Thu, 9 Apr 2020 12:29:44 +0200 Subject: [PATCH 0878/3170] [fix] uid will be tested as a string --- src/yunohost/user.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 4a047b58f..bc19bc5ea 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -165,8 +165,8 @@ def user_create(operation_logger, username, firstname, lastname, mail, password, operation_logger.start() # Get random UID/GID - all_uid = {x.pw_uid for x in pwd.getpwall()} - all_gid = {x.gr_gid for x in grp.getgrall()} + all_uid = {str(x.pw_uid) for x in pwd.getpwall()} + all_gid = {str(x.gr_gid) for x in grp.getgrall()} uid_guid_found = False while not uid_guid_found: From 5aa25563062c972d542fd2800b3c8aa863111400 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Sun, 5 Apr 2020 19:44:39 +0200 Subject: [PATCH 0879/3170] [fix] config_appy return link --- src/yunohost/app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 21e31d34d..4e4878f9e 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1947,6 +1947,7 @@ def app_config_apply(operation_logger, app, args): logger.success("Config updated as expected") return { + "app": app, "logs": operation_logger.success(), } From 5b0269622a90936b3b194ca2f3d0541df49fa85c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 30 Mar 2020 20:09:26 +0200 Subject: [PATCH 0880/3170] Attempt to simplify permission migration --- data/helpers.d/setting | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index 384fdc399..557afb332 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -197,7 +197,7 @@ EOF if [[ "$1" == "set" ]] && [[ "${4:-}" == "/" ]] then ynh_permission_update --permission "main" --add "visitors" - elif [[ "$1" == "delete" ]] && [[ "${current_value:-}" == "/" ]] + elif [[ "$1" == "delete" ]] && [[ "${current_value:-}" == "/" ]] && [[ -n "$(ynh_app_setting_get --app=$2 --key='is_public' )" ]] then ynh_permission_update --permission "main" --remove "visitors" fi From 729aeb2425985182950d3a967361c351b290fc8b Mon Sep 17 00:00:00 2001 From: Kay0u Date: Mon, 30 Mar 2020 19:36:41 +0200 Subject: [PATCH 0881/3170] add ynh_permission_has_user --- data/actionsmap/yunohost.yml | 9 +++++++++ data/helpers.d/setting | 19 +++++++++++++++++++ src/yunohost/permission.py | 22 ++++++++++++++++++++++ src/yunohost/user.py | 6 ++++++ 4 files changed, 56 insertions(+) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 245b3615d..af697efc0 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -296,6 +296,15 @@ user: help: Display all info known about each permission, including the full user list of each group it is granted to. action: store_true + ### user_permission_info() + info: + action_help: Get information about a specific permission + api: GET /users/permissions/ + arguments: + permission: + help: Name of the permission to fetch info about + extra: + pattern: *pattern_username ### user_permission_update() update: diff --git a/data/helpers.d/setting b/data/helpers.d/setting index 557afb332..917d4def7 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -367,3 +367,22 @@ ynh_permission_update() { yunohost user permission update "$app.$permission" ${add:-} ${remove:-} } + +# Check if a permission exists +# +# usage: ynh_permission_has_user --permission=permission --user=user +# | arg: -p, --permission - the permission to check +# | arg: -u, --user - the user seek in the permission +# +# Requires YunoHost version 3.7.1 or higher. +ynh_permission_has_user() { + declare -Ar args_array=( [p]=permission= [u]=user) + local permission + ynh_handle_getopts_args "$@" + + if ! ynh_permission_exists --permission $permission + return 1 + fi + + yunohost user permission info $permission | grep -w -q "$user" +} \ No newline at end of file diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 71472eeaf..79b346a1f 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -196,6 +196,28 @@ def user_permission_reset(operation_logger, permission, sync_perm=True): return new_permission + +def user_permission_info(permission, sync_perm=True): + """ + Return informations about a specific permission + + Keyword argument: + permission -- Name of the permission (e.g. mail or nextcloud or wordpress.editors) + """ + + # By default, manipulate main permission + if "." not in permission: + permission = permission + ".main" + + # Fetch existing permission + + existing_permission = user_permission_list(full=True)["permissions"].get(permission, None) + if existing_permission is None: + raise YunohostError('permission_not_found', permission=permission) + + return existing_permission + + # # # The followings methods are *not* directly exposed. diff --git a/src/yunohost/user.py b/src/yunohost/user.py index bc19bc5ea..69baf4435 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -792,6 +792,12 @@ def user_permission_reset(permission, sync_perm=True): sync_perm=sync_perm) +def user_permission_info(permission, sync_perm=True): + import yunohost.permission + return yunohost.permission.user_permission_info(permission, + sync_perm=sync_perm) + + # # SSH subcategory # From 9e1cc92ce823c3679fecee05faa5eab506222aa7 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Mon, 30 Mar 2020 19:58:06 +0200 Subject: [PATCH 0882/3170] Let's have a working helper --- data/helpers.d/setting | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index 917d4def7..1ab2b6efe 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -374,15 +374,22 @@ ynh_permission_update() { # | arg: -p, --permission - the permission to check # | arg: -u, --user - the user seek in the permission # +# example: ynh_permission_has_user --permission=nextcloud.main --user=visitors +# # Requires YunoHost version 3.7.1 or higher. ynh_permission_has_user() { - declare -Ar args_array=( [p]=permission= [u]=user) + local legacy_args=pu + # Declare an array to define the options of this helper. + declare -Ar args_array=( [p]=permission= [u]=user= ) local permission + local user + # Manage arguments with getopts ynh_handle_getopts_args "$@" - if ! ynh_permission_exists --permission $permission + if ! ynh_permission_exists --permission=$permission + then return 1 fi yunohost user permission info $permission | grep -w -q "$user" -} \ No newline at end of file +} From 3e6cbe4e845d4355c937bd17510fd858f89a5b3a Mon Sep 17 00:00:00 2001 From: Kay0u Date: Mon, 30 Mar 2020 21:32:29 +0200 Subject: [PATCH 0883/3170] Add legacy_args, fix the helper --- data/actionsmap/yunohost.yml | 2 -- data/helpers.d/setting | 12 +++++++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index af697efc0..efded2450 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -303,8 +303,6 @@ user: arguments: permission: help: Name of the permission to fetch info about - extra: - pattern: *pattern_username ### user_permission_update() update: diff --git a/data/helpers.d/setting b/data/helpers.d/setting index 1ab2b6efe..c859fc398 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -270,6 +270,8 @@ ynh_webpath_register () { # # Requires YunoHost version 3.7.0 or higher. ynh_permission_create() { + # Declare an array to define the options of this helper. + local legacy_args=pua declare -Ar args_array=( [p]=permission= [u]=url= [a]=allowed= ) local permission local url @@ -298,6 +300,8 @@ ynh_permission_create() { # # Requires YunoHost version 3.7.0 or higher. ynh_permission_delete() { + # Declare an array to define the options of this helper. + local legacy_args=p declare -Ar args_array=( [p]=permission= ) local permission ynh_handle_getopts_args "$@" @@ -312,6 +316,8 @@ ynh_permission_delete() { # # Requires YunoHost version 3.7.0 or higher. ynh_permission_exists() { + # Declare an array to define the options of this helper. + local legacy_args=p declare -Ar args_array=( [p]=permission= ) local permission ynh_handle_getopts_args "$@" @@ -327,6 +333,8 @@ ynh_permission_exists() { # # Requires YunoHost version 3.7.0 or higher. ynh_permission_url() { + # Declare an array to define the options of this helper. + local legacy_args=pu declare -Ar args_array=([p]=permission= [u]=url=) local permission local url @@ -352,6 +360,8 @@ ynh_permission_url() { # example: ynh_permission_update --permission admin --add samdoe --remove all_users # Requires YunoHost version 3.7.0 or higher. ynh_permission_update() { + # Declare an array to define the options of this helper. + local legacy_args=par declare -Ar args_array=( [p]=permission= [a]=add= [r]=remove= ) local permission local add @@ -391,5 +401,5 @@ ynh_permission_has_user() { return 1 fi - yunohost user permission info $permission | grep -w -q "$user" + yunohost user permission info "$app.$permission" | grep -w -q "$user" } From a221b7b9f0bc9b00d97bb6aba69d5e7c5166125e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 9 Apr 2020 14:53:34 +0200 Subject: [PATCH 0884/3170] Update changelog for 3.7.1 --- debian/changelog | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/debian/changelog b/debian/changelog index 9bcaea043..018807b16 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,20 @@ +yunohost (3.7.1) stable; urgency=low + + - [enh] Add ynh_permission_has_user helper (#905) + - [mod] Change behavior of ynh_setting_delete to try to make migrating away from legacy permissions easier (#906) + - [fix] app_config_apply should also return 'app' info (#918) + - [fix] uid/gid conflicts in user_create because of inconsistent comparison (#924) + - [fix] Ensure metronome owns its directories (1f623830, 031f8a6e) + - [mod] Remove useless sudos in helpers (be88a283) + - [enh] Improve message wording for services (3c844292) + - [enh] Attempt to anonymize data pasted to paste.yunohost.org (f56f4724) + - [enh] Lazy load yunohost.certificate to possibly improve perfs (af8981e4) + - [fix] Improve logging / debugging (1eef9b67, 7d323814, d17fcaf9, 210d5f3f) + + Thanks to all contributors <3 ! (Bram, Kay0u, Maniack, Matthew D.) + + -- Alexandre Aubin Thu, 9 April 2020 14:52:00 +0000 + yunohost (3.7.0.12) stable; urgency=low - Fix previous buggy hotfix about deleting existing primary groups ... From 4933b23b5b2779cd2b8a7e456d83ea8473feba06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Thu, 9 Apr 2020 15:40:20 +0200 Subject: [PATCH 0885/3170] Improve management of show_tile settings --- locales/en.json | 4 +++- src/yunohost/app.py | 3 ++- src/yunohost/permission.py | 26 +++++++++++++++++++++----- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/locales/en.json b/locales/en.json index 922ad2c4e..3e776f1a2 100644 --- a/locales/en.json +++ b/locales/en.json @@ -501,7 +501,7 @@ "regenconf_dry_pending_applying": "Checking pending configuration which would have been applied for category '{category}'…", "regenconf_failed": "Could not regenerate the configuration for category(s): {categories}", "regenconf_pending_applying": "Applying pending configuration for category '{category}'…", - "regex_incompatible_with_tile": "/!\\ Packagers! You for the permission '{permission}' can't set the regex {regex} as main url and set 'show_tile' to 'true'", + "regex_incompatible_with_tile": "/!\\ Packagers! For the permission '{permission}' can't set the regex {regex} as main url and set 'show_tile' to 'true'", "regex_with_only_domain": "You can't use a regex for domain, only for path", "restore_already_installed_app": "An app with the ID '{app:s}' is already installed", "restore_app_failed": "Could not restore the app '{app:s}'", @@ -563,6 +563,8 @@ "service_stop_failed": "Could not stop the service '{service:s}'\n\nRecent service logs:{logs:s}", "service_stopped": "Service '{service:s}' stopped", "service_unknown": "Unknown service '{service:s}'", + "show_tile_cant_be_enabled_for_url_not_defined": "The url for the permission '{permission}' is not defined. So you can't enable the settings show_tile", + "show_tile_cant_be_enabled_for_regex": "The url for the permission '{permission}' is a regex. So you can't enable the settings show_tile", "ssowat_conf_generated": "SSOwat configuration generated", "ssowat_conf_updated": "SSOwat configuration updated", "system_upgraded": "System upgraded", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 9feb5bec9..33d63ba81 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -717,7 +717,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu # Initialize the main permission for the app # After the install, if apps don't have a domain and path defined, the default url '/' is removed from the permission - permission_create(app_instance_name+".main", allowed=["all_users"], label=label, show_tile=True, protected=False) + permission_create(app_instance_name+".main", allowed=["all_users"], label=label, show_tile=False, protected=False) # Execute the app install script install_failed = True @@ -834,6 +834,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu app_settings = _get_app_settings(app_instance_name) domain = app_settings.get('domain', None) path = app_settings.get('path', None) + user_permission_update(app_instance_name + ".main", show_tile=True, sync_perm=False) if domain and path and user_permission_list(full=True, full_path=False)['permissions'][app_instance_name + '.main']['url'] is None: permission_url(app_instance_name + ".main", url='/', sync_perm=False) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index badd37ef3..972a863c5 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -169,6 +169,13 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, if "visitors" not in new_allowed_groups or len(new_allowed_groups) >= 3: logger.warning(m18n.n("permission_currently_allowed_for_all_users")) + # Note that we can get is argument as string we it come from the CLI + if isinstance(show_tile, str): + if show_tile.lower() == "true": + show_tile = True + else: + show_tile = False + if existing_permission['url'] and existing_permission['url'].startswith('re:') and show_tile: logger.warning(m18n.n('regex_incompatible_with_tile', regex=existing_permission['url'], permission=permission)) @@ -307,12 +314,12 @@ def permission_create(operation_logger, permission, allowed=None, except Exception as e: raise YunohostError('permission_creation_failed', permission=permission, error=e) + permission_url(permission, url=url, add_url=additional_urls, auth_header=auth_header, + sync_perm=False) + new_permission = _update_ldap_group_permission(permission=permission, allowed=allowed, label=label, show_tile=show_tile, - protected=protected, sync_perm=False) - - permission_url(permission, url=url, add_url=additional_urls, auth_header=auth_header, - sync_perm=sync_perm) + protected=protected, sync_perm=sync_perm) logger.debug(m18n.n('permission_created', permission=permission)) return new_permission @@ -414,6 +421,7 @@ def permission_url(operation_logger, permission, if clear_urls: url = None new_additional_urls = [] + show_tile = False # Guarantee uniqueness of all values, which would otherwise make ldap.update angry. new_additional_urls = set(new_additional_urls) @@ -426,7 +434,8 @@ def permission_url(operation_logger, permission, try: ldap.update('cn=%s,ou=permission' % permission, {'URL': [url] if url is not None else [], 'additionalUrls': new_additional_urls, - 'authHeader': [str(auth_header).upper()]}) + 'authHeader': [str(auth_header).upper()], + 'showTile': [str(show_tile).upper()],}) except Exception as e: raise YunohostError('permission_update_failed', permission=permission, error=e) @@ -559,6 +568,13 @@ def _update_ldap_group_permission(permission, allowed, if show_tile is None: show_tile = existing_permission["show_tile"] + elif show_tile is True: + if not existing_permission['url']: + logger.warning(m18n.n('show_tile_cant_be_enabled_for_url_not_defined', permission=permission)) + show_tile = False + elif existing_permission['url'].startswith('re:'): + logger.warning(m18n.n('show_tile_cant_be_enabled_for_regex', permission=permission)) + show_tile = False if protected is None: protected = existing_permission["protected"] From 8f61e493e4310545f4d2b97b5aec611dcd77b825 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Thu, 9 Apr 2020 15:41:45 +0200 Subject: [PATCH 0886/3170] Fix label management --- src/yunohost/permission.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 972a863c5..33efab1a9 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -290,8 +290,8 @@ def permission_create(operation_logger, permission, allowed=None, 'cn': str(permission), 'gidNumber': gid, 'authHeader': ['TRUE'], - 'label': [permission.split('.')[0].title() if permission.endswith('.main') - else "%s (%s)" % (permission.split('.')[0].title(), permission.split('.')[1])], + 'label': [str(permission.split('.')[0].title() if permission.endswith('.main') + else "%s (%s)" % (permission.split('.')[0].title(), permission.split('.')[1]))], 'showTile': ['FALSE'], # Dummy value, it will be fixed when we call '_update_ldap_group_permission' 'isProtected': ['FALSE'] # Dummy value, it will be fixed when we call '_update_ldap_group_permission' } From 1c1b88e45547c876bbd6df28b7a481431c81d96c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Thu, 9 Apr 2020 15:42:22 +0200 Subject: [PATCH 0887/3170] Fix regex management --- src/yunohost/permission.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 33efab1a9..cbf1285e8 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -362,14 +362,17 @@ def permission_url(operation_logger, permission, if not existing_permission: raise YunohostError('permission_not_found', permission=permission) + show_tile = existing_permission['show_tile'] + if url is None: url = existing_permission["url"] else: url = _check_and_normalize_permission_path(url) domain, path = _get_full_url(url, app_main_path).split('/', 1) - conflicts = _get_conflicting_apps(domain, path, ignore_app=permission.split('.')[0]) + conflicts = _get_conflicting_apps(domain.lstrip("re:"), path, ignore_app=permission.split('.')[0]) if url.startswith('re:') and existing_permission['show_tile']: logger.warning(m18n.n('regex_incompatible_with_tile', regex=url, permission=permission)) + show_tile = False if conflicts: apps = [] @@ -393,7 +396,7 @@ def permission_url(operation_logger, permission, else: ur = _check_and_normalize_permission_path(ur) domain, path = _get_full_url(ur, app_main_path).split('/', 1) - conflicts = _get_conflicting_apps(domain, path, ignore_app=permission.split('.')[0]) + conflicts = _get_conflicting_apps(domain.lstrip("re:"), path, ignore_app=permission.split('.')[0]) if conflicts: apps = [] @@ -630,6 +633,6 @@ def _get_full_url(url, app_main_path): if url.startswith('/'): return app_main_path + url.rstrip("/") if url.startswith('re:/'): - return 're:' + app_main_path + url.lstrip('re:/') + return 're:' + app_main_path + url.lstrip('re:') else: return url From 83a97798a0332a91add9c183d45cd92deb8bad42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Thu, 9 Apr 2020 15:46:38 +0200 Subject: [PATCH 0888/3170] Don't create a new domain at each test --- src/yunohost/tests/test_permission.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index 635e64665..1defbfb75 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -79,12 +79,13 @@ def clean_user_groups_permission(): def setup_function(function): clean_user_groups_permission() - markers = {m.name: {'args':m.args, 'kwargs':m.kwargs} for m in function.__dict__.get("pytestmark",[])} global maindomain global other_domains maindomain = _get_maindomain() + markers = {m.name: {'args':m.args, 'kwargs':m.kwargs} for m in function.__dict__.get("pytestmark",[])} + if "other_domains" in markers: other_domains = ["domain_%s.dev" % string.ascii_lowercase[number] for number in range(markers['other_domains']['kwargs']['number'])] for domain in other_domains: @@ -94,6 +95,8 @@ def setup_function(function): # Dirty patch of DNS resolution. Force the DNS to 127.0.0.1 address even if dnsmasq have the public address. # Mainly used for 'can_access_webpage' function dns_cache = {(maindomain, 443, 0, 1): [(2, 1, 6, '', ('127.0.0.1', 443))]} + for domain in other_domains: + dns_cache[(domain, 443, 0, 1)] = [(2, 1, 6, '', ('127.0.0.1', 443))] def new_getaddrinfo(*args): try: return dns_cache[args] @@ -135,6 +138,12 @@ def teardown_function(function): pass +def teardown_module(module): + global other_domains + for domain in other_domains: + domain_remove(domain) + + @pytest.fixture(autouse=True) def check_LDAP_db_integrity_call(): check_LDAP_db_integrity() From fe6deb628d46d47a84f76d80693238264ec99d24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Thu, 9 Apr 2020 15:47:58 +0200 Subject: [PATCH 0889/3170] Cleanup code --- src/yunohost/tests/test_permission.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index 1defbfb75..f05ee832a 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -41,7 +41,7 @@ def _permission_create_with_dummy_app(permission, allowed=None, if path: settings['path'] = path _set_app_settings(app, settings) - + with open(os.path.join(APPS_SETTING_PATH, app, 'manifest.json'), 'w') as f: json.dump({ "name": app, @@ -114,7 +114,7 @@ def setup_function(function): domain=maindomain, path='/wiki') _permission_create_with_dummy_app(permission="blog.main", url="/", auth_header=True, show_tile=False, - protected=False, sync_perm=False, + protected=False, sync_perm=False, allowed=["alice"], domain=maindomain, path='/blog') _permission_create_with_dummy_app(permission="blog.api", allowed=["visitors"], protected=True, sync_perm=True) @@ -558,8 +558,8 @@ def test_permission_change_label(mocker): res = user_permission_list(full=True)['permissions'] assert res['wiki.main']['label'] == "New Wiki" - - + + def test_permission_change_label_with_same_value(mocker): with message(mocker, "permission_updated", permission="wiki.main"): user_permission_update("wiki.main", label="Wiki") From e815a3389fad8dc5c51955098efe4ff91cadf507 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Thu, 9 Apr 2020 15:50:06 +0200 Subject: [PATCH 0890/3170] Simplify the way to declare set --- src/yunohost/tests/test_permission.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index f05ee832a..a59c90ab6 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -305,7 +305,7 @@ def test_permission_list(): assert res['wiki.main']['url'] == maindomain + "/wiki" assert res['blog.main']['url'] == maindomain + "/blog" assert res['blog.api']['url'] == None - assert set(res['wiki.main']['additional_urls']) == set([maindomain + '/wiki/whatever', maindomain + '/wiki/idontnow']) + assert set(res['wiki.main']['additional_urls']) == {maindomain + '/wiki/whatever', maindomain + '/wiki/idontnow'} assert res['blog.main']['additional_urls'] == [] assert res['blog.api']['additional_urls'] == [] assert res['wiki.main']['protected'] == False @@ -324,7 +324,7 @@ def test_permission_list(): res = user_permission_list(full=True, full_path=False)['permissions'] assert res['wiki.main']['url'] == "/" assert res['blog.main']['url'] == "/" - assert set(res['wiki.main']['additional_urls']) == set(['/whatever', '/idontnow']) + assert set(res['wiki.main']['additional_urls']) == {'/whatever', '/idontnow'} # @@ -413,7 +413,7 @@ def test_permission_create_with_urls_management_simple_domain(mocker): res = user_permission_list(full=True)['permissions'] assert "site.main" in res assert res['site.main']['url'] == maindomain + "/site" - assert set(res['site.main']['additional_urls']) == set([maindomain + "/site/whatever", maindomain + "/site/idontnow"]) + assert set(res['site.main']['additional_urls']) == {maindomain + "/site/whatever", maindomain + "/site/idontnow"} assert res['site.main']['auth_header'] == False @@ -429,8 +429,8 @@ def test_permission_create_with_urls_management_multiple_domain(mocker): res = user_permission_list(full=True)['permissions'] assert "site.main" in res - assert res['site.main']['url'] == maindomain + "/site" - assert set(res['site.main']['additional_urls']) == set([other_domains[0] + "/blabla", other_domains[1] + "/ahh"]) + assert res['site.main']['url'] == maindomain + "/site/something" + assert set(res['site.main']['additional_urls']) == {other_domains[0] + "/blabla", other_domains[1] + "/ahh"} assert res['site.main']['auth_header'] == True @@ -677,9 +677,9 @@ def test_permssion_add_additional_url_already_exist(): res = user_permission_list(full=True)['permissions'] assert res['wiki.main']['url'] == maindomain + "/wiki" - assert set(res['wiki.main']['additional_urls']) == set([maindomain + '/wiki/whatever', - maindomain + '/wiki/idontnow', - maindomain + '/wiki/myhouse']) + assert set(res['wiki.main']['additional_urls']) == {maindomain + '/wiki/whatever', + maindomain + '/wiki/idontnow', + maindomain + '/wiki/myhouse'} def test_permission_remove_additional_url_dont_exist(): From 892d1f752adc038e8d23bbfc7cd90c4a608ab451 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Thu, 9 Apr 2020 15:51:19 +0200 Subject: [PATCH 0891/3170] Fix tests --- src/yunohost/tests/test_permission.py | 41 +++++++++++++-------------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index a59c90ab6..9b14933ae 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -334,7 +334,7 @@ def test_permission_list(): def test_permission_create_main(mocker): with message(mocker, "permission_created", permission="site.main"): - permission_create("site.main", allowed=["all_users"]) + permission_create("site.main", allowed=["all_users"], protected=False) res = user_permission_list(full=True)['permissions'] assert "site.main" in res @@ -365,8 +365,9 @@ def test_permission_create_with_specific_user(): def test_permission_create_with_tile_management(mocker): with message(mocker, "permission_created", permission="site.main"): - permission_create("site.main", allowed=["all_users"], - label="The Site", show_tile=False) + _permission_create_with_dummy_app("site.main", allowed=["all_users"], + label="The Site", show_tile=False, + domain=maindomain, path='/site') res = user_permission_list(full=True)['permissions'] assert "site.main" in res @@ -374,17 +375,19 @@ def test_permission_create_with_tile_management(mocker): assert res['site.main']['show_tile'] == False def test_permission_create_with_tile_management_with_main_default_value(mocker): - with message(mocker, "permission_created", permission="web.main"): - permission_create("web.main", allowed=["all_users"], show_tile=True) + with message(mocker, "permission_created", permission="site.main"): + _permission_create_with_dummy_app("site.main", allowed=["all_users"], show_tile=True, url="/", + domain=maindomain, path='/site') res = user_permission_list(full=True)['permissions'] - assert "web.main" in res - assert res['web.main']['label'] == "Web" - assert res['web.main']['show_tile'] == True + assert "site.main" in res + assert res['site.main']['label'] == "Site" + assert res['site.main']['show_tile'] == True def test_permission_create_with_tile_management_with_not_main_default_value(mocker): with message(mocker, "permission_created", permission="site.api"): - permission_create("site.api", allowed=["all_users"], show_tile=True) + _permission_create_with_dummy_app("site.api", allowed=["all_users"], show_tile=True, url="/", + domain=maindomain, path='/site') res = user_permission_list(full=True)['permissions'] assert "site.api" in res @@ -393,15 +396,15 @@ def test_permission_create_with_tile_management_with_not_main_default_value(mock def test_permission_create_with_urls_management_without_url(mocker): - with message(mocker, "permission_created", permission="site.main"): + with message(mocker, "permission_created", permission="site.api"): _permission_create_with_dummy_app("site.api", allowed=["all_users"], domain=maindomain, path='/site') res = user_permission_list(full=True)['permissions'] - assert "site.main" in res - assert res['site.main']['url'] == None - assert res['site.main']['additional_urls'] == [] - assert res['site.main']['auth_header'] == False + assert "site.api" in res + assert res['site.api']['url'] == None + assert res['site.api']['additional_urls'] == [] + assert res['site.api']['auth_header'] == True def test_permission_create_with_urls_management_simple_domain(mocker): @@ -441,12 +444,6 @@ def test_permission_delete(mocker): res = user_permission_list()['permissions'] assert "wiki.main" not in res - with message(mocker, "permission_deleted", permission="blog.main"): - permission_delete("blog.main", force=True) - - res = user_permission_list()['permissions'] - assert "blog.main" not in res - with message(mocker, "permission_deleted", permission="blog.api"): permission_delete("blog.api", force=False) @@ -692,8 +689,8 @@ def test_permission_remove_additional_url_dont_exist(): def test_permission_clear_additional_url(): - permission_url("wiki.main", clear_url=True) - + permission_url("wiki.main", clear_urls=True) + res = user_permission_list(full=True)['permissions'] assert res['wiki.main']['url'] == None assert res['wiki.main']['additional_urls'] == [] From baaf4bb7580a8b870bdee82fb087a21edec8e163 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Thu, 9 Apr 2020 15:52:51 +0200 Subject: [PATCH 0892/3170] Add test for regex management --- src/yunohost/tests/test_permission.py | 40 +++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index 9b14933ae..379e29ad8 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -642,22 +642,52 @@ def test_permission_redefine_url(): def test_permission_remove_url(): - permission_url("blog.main", url=None) + permission_url("blog.main", clear_urls=True) res = user_permission_list(full=True)['permissions'] assert res["blog.main"]["url"] is None +def test_permission_main_url_regex(): + permission_url("blog.main", url="re:/[a-z]+reboy/.*") + + res = user_permission_list(full=True, full_path=False)['permissions'] + assert res["blog.main"]["url"] == "re:/[a-z]+reboy/.*" + + res = user_permission_list(full=True, full_path=True)['permissions'] + assert res["blog.main"]["url"] == "re:%s/blog/[a-z]+reboy/.*" % maindomain + + +def test_permission_main_url_bad_regex(mocker): + with raiseYunohostError(mocker, "invalid_regex"): + permission_url("blog.main", url="re:/[a-z]++reboy/.*") + + @pytest.mark.other_domains(number=1) def test_permission_add_additional_url(): permission_url("wiki.main", add_url=[other_domains[0] + "/heyby", "/myhouse"]) res = user_permission_list(full=True)['permissions'] assert res['wiki.main']['url'] == maindomain + "/wiki" - assert set(res['wiki.main']['additional_urls']) == set([maindomain + '/wiki/whatever', - maindomain + '/wiki/idontnow', - other_domains[0] + "/heyby", - maindomain + '/wiki/myhouse']) + assert set(res['wiki.main']['additional_urls']) == {maindomain + '/wiki/whatever', + maindomain + '/wiki/idontnow', + other_domains[0] + "/heyby", + maindomain + '/wiki/myhouse'} + + +def test_permission_add_additional_regex(): + permission_url("blog.main", add_url=["re:/[a-z]+reboy/.*"]) + + res = user_permission_list(full=True, full_path=False)['permissions'] + assert res["blog.main"]["additional_urls"] == ["re:/[a-z]+reboy/.*"] + + res = user_permission_list(full=True, full_path=True)['permissions'] + assert res["blog.main"]["additional_urls"] == ["re:%s/blog/[a-z]+reboy/.*" % maindomain] + + +def test_permission_add_additional_bad_regex(mocker): + with raiseYunohostError(mocker, "invalid_regex"): + permission_url("blog.main", add_url=["re:/[a-z]++reboy/.*"]) def test_permission_remove_additional_url(): From 5b76a42126bb913a308b07240d8a879bd6037a9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Thu, 9 Apr 2020 15:53:20 +0200 Subject: [PATCH 0893/3170] Add new tests for ssowat conf --- src/yunohost/tests/test_permission.py | 79 +++++++++++++++++++++++---- 1 file changed, 69 insertions(+), 10 deletions(-) diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index 379e29ad8..b5b57857d 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -728,45 +728,104 @@ def test_permission_clear_additional_url(): def test_permission_switch_auth_header(): permission_url("wiki.main", auth_header=True) - + res = user_permission_list(full=True)['permissions'] assert res['wiki.main']['auth_header'] == True permission_url("wiki.main", auth_header=False) - + res = user_permission_list(full=True)['permissions'] assert res['wiki.main']['auth_header'] == False def test_permission_switch_auth_header_with_same_value(): permission_url("wiki.main", auth_header=False) - + res = user_permission_list(full=True)['permissions'] assert res['wiki.main']['auth_header'] == False # Permission protected - def test_permission_switch_protected(): - permission_update("wiki.main", protected=True) - + user_permission_update("wiki.main", protected=True) + res = user_permission_list(full=True)['permissions'] assert res['wiki.main']['protected'] == True - permission_update("wiki.main", protected=False) - + user_permission_update("wiki.main", protected=False) + res = user_permission_list(full=True)['permissions'] assert res['wiki.main']['protected'] == False def test_permission_switch_protected_with_same_value(): - permission_update("wiki.main", protected=False) - + user_permission_update("wiki.main", protected=False) + res = user_permission_list(full=True)['permissions'] assert res['wiki.main']['protected'] == False +# Test SSOWAT conf generation + +def test_ssowat_conf(): + with open("/etc/ssowat/conf.json") as f: + res = json.load(f) + + permissions = res['permissions'] + assert "wiki.main" in permissions + assert "blog.main" in permissions + assert "blog.api" in permissions + + assert set(permissions['wiki.main']['users']) == {'alice', 'bob'} + assert permissions['blog.main']['users'] == ['alice'] + assert permissions['blog.api']['users'] == [] + + assert permissions['wiki.main']['uris'][0] == maindomain + "/wiki" + + assert set(permissions['wiki.main']['uris']) == {maindomain + "/wiki", + maindomain + "/wiki/whatever", + maindomain + "/wiki/idontnow"} + assert permissions['blog.main']['uris'] == [maindomain + "/blog"] + assert permissions['blog.api']['uris'] == [] + + assert permissions['wiki.main']['public'] == False + assert permissions['blog.main']['public'] == False + assert permissions['blog.api']['public'] == True + + assert permissions['wiki.main']['auth_header'] == False + assert permissions['blog.main']['auth_header'] == True + assert permissions['blog.api']['auth_header'] == True + + assert permissions['wiki.main']['label'] == "Wiki" + assert permissions['blog.main']['label'] == "Blog" + assert permissions['blog.api']['label'] == "Blog (api)" + + assert permissions['wiki.main']['show_tile'] == True + assert permissions['blog.main']['show_tile'] == False + assert permissions['blog.api']['show_tile'] == False + + +def test_ssowat_conf_show_tile_impossible(): + _permission_create_with_dummy_app(permission="site.main", auth_header=False, + label="Site", show_tile=True, + allowed=["all_users"], protected=False, sync_perm=False, + domain=maindomain, path="/site") + + _permission_create_with_dummy_app(permission="web.main", url="re:/[a-z]{3}/bla", auth_header=False, + label="Web", show_tile=True, + allowed=["all_users"], protected=False, sync_perm=True, + domain=maindomain, path="/web") + + with open("/etc/ssowat/conf.json") as f: + res = json.load(f) + + permissions = res['permissions'] + + assert permissions['site.main']['show_tile'] == False + assert permissions['web.main']['show_tile'] == False + + # # Application interaction # From 3ba3055f43b595d31ff70cad6bad4d8e7efdbb12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Thu, 9 Apr 2020 16:14:17 +0200 Subject: [PATCH 0894/3170] Fix ssowatconf --- 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 33d63ba81..631c1574f 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1195,7 +1195,7 @@ def app_ssowatconf(): "label": "Core permissions - skipped", "show_tile": False, "auth_header": False, - "public": False, + "public": True, "uris": \ [domain + '/yunohost/admin' for domain in domains] + \ [domain + '/yunohost/api' for domain in domains] + [ From 530d94b2cae5d5efec7bf79115e56b61755f8ac1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Thu, 9 Apr 2020 16:53:28 +0200 Subject: [PATCH 0895/3170] Fix regex manamgement --- src/yunohost/permission.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index dc933da33..fddb7539e 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -391,7 +391,8 @@ def permission_url(operation_logger, permission, else: url = _check_and_normalize_permission_path(url) domain, path = _get_full_url(url, app_main_path).split('/', 1) - conflicts = _get_conflicting_apps(domain.lstrip("re:"), path, ignore_app=permission.split('.')[0]) + domain = domain[3:] if domain.startswith("re:") else domain + conflicts = _get_conflicting_apps(domain, path, ignore_app=permission.split('.')[0]) if url.startswith('re:') and existing_permission['show_tile']: logger.warning(m18n.n('regex_incompatible_with_tile', regex=url, permission=permission)) show_tile = False @@ -418,7 +419,8 @@ def permission_url(operation_logger, permission, else: ur = _check_and_normalize_permission_path(ur) domain, path = _get_full_url(ur, app_main_path).split('/', 1) - conflicts = _get_conflicting_apps(domain.lstrip("re:"), path, ignore_app=permission.split('.')[0]) + domain = domain[3:] if domain.startswith("re:") else domain + conflicts = _get_conflicting_apps(domain, path, ignore_app=permission.split('.')[0]) if conflicts: apps = [] @@ -655,6 +657,6 @@ def _get_full_url(url, app_main_path): if url.startswith('/'): return app_main_path + url.rstrip("/") if url.startswith('re:/'): - return 're:' + app_main_path + url.lstrip('re:') + return 're:' + app_main_path + url[3:] else: return url From 68d6ed911e97d2274638facc0082773bb9a476d7 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Thu, 9 Apr 2020 17:37:04 +0200 Subject: [PATCH 0896/3170] [fix] also invalidate group cache --- src/yunohost/user.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index fd67314d8..af5ff77fb 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -201,8 +201,9 @@ def user_create(operation_logger, username, firstname, lastname, mail, password, except Exception as e: raise YunohostError('user_creation_failed', user=username, error=e) - # Invalidate passwd to take user creation into account + # Invalidate passwd and group to take user and group creation into account subprocess.call(['nscd', '-i', 'passwd']) + subprocess.call(['nscd', '-i', 'group']) try: # Attempt to create user home folder From 4968f1aa7df184c9c731a2fb3c2d4f4aa04a719e Mon Sep 17 00:00:00 2001 From: Kayou Date: Thu, 9 Apr 2020 18:08:51 +0200 Subject: [PATCH 0897/3170] [fix] custom_portal and custom_overlay redirect --- data/templates/nginx/plain/yunohost_panel.conf.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/templates/nginx/plain/yunohost_panel.conf.inc b/data/templates/nginx/plain/yunohost_panel.conf.inc index 1c5a2d656..53a69d705 100644 --- a/data/templates/nginx/plain/yunohost_panel.conf.inc +++ b/data/templates/nginx/plain/yunohost_panel.conf.inc @@ -4,5 +4,5 @@ sub_filter_once on; # Apply to other mime types than text/html sub_filter_types application/xhtml+xml; # Prevent YunoHost panel files from being blocked by specific app rules -location ~ (ynh_portal.js|ynh_overlay.css|ynh_userinfo.json) { +location ~ (ynh_portal.js|ynh_overlay.css|ynh_userinfo.json|ynhtheme/custom_portal.js|ynhtheme/custom_overlay.css) { } From 3d44560e26f15d23dfdf474908001f1a651ee2cb Mon Sep 17 00:00:00 2001 From: kay0u Date: Thu, 9 Apr 2020 19:51:18 +0000 Subject: [PATCH 0898/3170] remove the placeholder --- debian/changelog | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/debian/changelog b/debian/changelog index d64900b25..364757b92 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,10 +1,3 @@ -yunohost (3.8.0~alpha) testing; urgency=low - - Placeholder for upcoming 3.8 to avoid funky stuff with version numbers in - builds etc. - - -- Alexandre Aubin Mon, 16 Mar 2020 01:00:00 +0000 - yunohost (3.7.1) stable; urgency=low - [enh] Add ynh_permission_has_user helper (#905) @@ -20,7 +13,7 @@ yunohost (3.7.1) stable; urgency=low Thanks to all contributors <3 ! (Bram, Kay0u, Maniack, Matthew D.) - -- Alexandre Aubin Thu, 9 April 2020 14:52:00 +0000 + -- Alexandre Aubin Thu, 9 Apr 2020 14:52:00 +0000 yunohost (3.7.0.12) stable; urgency=low From d8dbf81f77bb9615559cd4875b5ce759f8b0d969 Mon Sep 17 00:00:00 2001 From: kay0u Date: Thu, 9 Apr 2020 20:10:49 +0000 Subject: [PATCH 0899/3170] Update changelog for 3.8.0 release --- debian/changelog | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/debian/changelog b/debian/changelog index 364757b92..29f086b09 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,50 @@ +yunohost (3.8.0) testing; urgency=low + + # Major stuff + + - [enh] New diagnosis system (#534, #872, #919, a416044, a354425, 4ab3653, decb372, e686dc6, b5d18d6, 69bc124, 937d339, cc2288c, aaa9805, 526a3a2) + - [enh] App categories (#778, #853) + - [enh] Support XMPP http upload (#831) + - [enh] Many small improvements in the way we manage services (#838, fa5c0e9, dd92a34, c97a839) + - [enh] Add subcategories management in bash completion (#839) + - [mod] Add conflict with apache2 and bind9, other minor changes in Depends (#909, 3bd6a7a, 0a482fd) + - [enh] Setting to enable POP3 in email stack (#791) + - [enh] Better UX for CLI/API to change maindomain (#796) + + # Misc technical + + - Update ciphers for nginx, postfix and dovecot according to new Mozilla recommendation (#913, #914) + - Get rid of domain-specific acme-challenge snippet, use a single snippet included in every conf (#917) + - [enh] Persist cookies between multiple ynh_local_curl calls for the same app (#884, #903) + - [fix] ynh_find_port didn't detect port already used on UDP (#827, #907) + - [fix] prevent firefox to mix CA and server certificate (#857) + - [enh] add operation logger for config panel (#869) + - [fix] psql helpers: Revoke sessions before dropping tables (#895) + - [fix] moulinette logs were never displayed #lol (#758) + + # Tests, cleaning, refactoring + + - Add core CI, improve/fix tests (#856, #863, 6eb8efb, c4590ab, 711cc35, 6c24755) + - Refactoring (#805, 101d3be, #784) + - Drop some very-old deprecated app helpers (though still somewhat supporting them through hacky patching) (#780) + - Drop glances and the old monitoring system (#821) + - Drop app_debug (#824) + - Drop app's status.json (#834) + - Drop ynh_add_skipped/(un)protected_uris helpers (#910) + - Use a common security.conf.inc instead of having cipher setting in each nginx's domain file (1285776, 4d99cbe, be8427d, 22b9565) + - Don't add weird tmp redirected_urls after postinstall (#902) + - Don't do weird stuff with yunohost-firewall during debian's postinst (978d9d5) + + # i18n, messaging + + - Unit tests / lint / cleaning for translation files (#901) + - Improve message wording, spelling (8b0c9e5, 9fe43b1, f69ab4c, 0decb64, 986f38f, 8d40c73, 8fe343a, 1d84f17) + - Improve translations for French, Catalan, Bengali (Bangladesh), Italian, Dutch, Norwegian Bokmål, Chinese, Occitan, Spanish, Esperanto, German, Nepali, Portuguese, Arabic, Russian, Hungarian, Hindi, Polish, Greek + + Thanks to all contributors <3 ! (Aeris One, Aleks, Allan N., Alvaro, Armando F., Arthur L., Augustin T., Bram, ButterflyOfFire, Damien P., Gustavo M., Jeroen F., Jimmy M., Josué, Kay0u, Maniack Crudelis, Mario, Matthew D., Mélanie C., Patrick B., Quentí, Yasss Gurl, amirale qt, Elie G., ljf, pitchum, Romain R., tituspijean, xaloc33, yalh76) + + -- Kay0u Thu, 09 Apr 2020 19:59:18 +0000 + yunohost (3.7.1) stable; urgency=low - [enh] Add ynh_permission_has_user helper (#905) From b06e8c0f7a77fab4ad09720053a726caf36b50d7 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Thu, 9 Apr 2020 23:47:16 +0200 Subject: [PATCH 0900/3170] Minor fix to avoid the key to be used if not asked --- data/helpers.d/apt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index def430055..286985026 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -337,7 +337,7 @@ ynh_install_extra_app_dependencies () { # Manage arguments with getopts ynh_handle_getopts_args "$@" name="${name:-$app}" - key=${key:-0} + key=${key:-} # Set a key only if asked if [ -n "$key" ] @@ -377,7 +377,7 @@ ynh_install_extra_repo () { ynh_handle_getopts_args "$@" name="${name:-$app}" append=${append:-0} - key=${key:-0} + key=${key:-} priority=${priority:-} if [ $append -eq 1 ] From 0b17aece2ea72e87708e64e806d2c356c44bce52 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Fri, 10 Apr 2020 00:05:56 +0200 Subject: [PATCH 0901/3170] Various insignificant corrections --- data/helpers.d/hardware | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/data/helpers.d/hardware b/data/helpers.d/hardware index be669568e..f98006aae 100644 --- a/data/helpers.d/hardware +++ b/data/helpers.d/hardware @@ -10,16 +10,16 @@ ynh_get_ram () { # Declare an array to define the options of this helper. declare -Ar args_array=( [f]=free [t]=total [s]=ignore_swap [o]=only_swap ) - local free - local total + local free + local total local ignore_swap local only_swap # Manage arguments with getopts ynh_handle_getopts_args "$@" ignore_swap=${ignore_swap:-0} only_swap=${only_swap:-0} - free=${free:-0} - total=${total:-0} + free=${free:-0} + total=${total:-0} local total_ram=$(vmstat --stats --unit M | grep "total memory" | awk '{print $1}') local total_swap=$(vmstat --stats --unit M | grep "total swap" | awk '{print $1}') @@ -43,9 +43,9 @@ ynh_get_ram () { # Use only the amount of free swap ram=$free_swap fi - elif [ $total -eq 1 ] - then - local ram=$total_ram_swap + elif [ $total -eq 1 ] + then + local ram=$total_ram_swap if [ $ignore_swap -eq 1 ] then # Use only the amount of free ram @@ -55,9 +55,9 @@ ynh_get_ram () { # Use only the amount of free swap ram=$total_swap fi - else - echo "Uhoh, you should choose --free or --total when using ynh_get_ram" >&2 - ram=0 + else + ynh_print_warn --message="You have to choose --free or --total when using ynh_get_ram" + ram=0 fi echo $ram @@ -65,25 +65,25 @@ ynh_get_ram () { # Return 0 or 1 depending if the system has a given amount of RAM+swap free or total # -# usage: ynh_require_ram [--amount=RAM required in Mb] [--free|--total] [--ignore_swap|--only_swap] -# | arg: -a, --amount - The amount to require, in Mb +# usage: ynh_require_ram --required=RAM required in Mb [--free|--total] [--ignore_swap|--only_swap] +# | arg: -r, --required - The amount to require, in Mb # | arg: -f, --free - Count free RAM+swap # | arg: -t, --total - Count total RAM+swap # | arg: -s, --ignore_swap - Ignore swap, consider only real RAM # | arg: -o, --only_swap - Ignore real RAM, consider only swap ynh_require_ram () { # Declare an array to define the options of this helper. - declare -Ar args_array=( [a]=amount= [f]=free [t]=total [s]=ignore_swap [o]=only_swap ) - local amount + declare -Ar args_array=( [r]=required= [f]=free [t]=total [s]=ignore_swap [o]=only_swap ) + local required local free local total - local ignore_swap - local only_swap - # Manage arguments with getopts - ynh_handle_getopts_args "$@" - amount=${amount:-0} + local ignore_swap + local only_swap + # Manage arguments with getopts + ynh_handle_getopts_args "$@" # Dunno if that's the right way to do, but that's some black magic to be able to # forward the bool args to ynh_get_ram easily? + # If the variable $free is not empty, set it to '--free' free=${free:+--free} total=${total:+--total} ignore_swap=${ignore_swap:+--ignore_swap} @@ -91,7 +91,7 @@ ynh_require_ram () { local ram=$(ynh_get_ram $free $total $ignore_swap $only_swap) - if [ $ram -lt $amount ] + if [ $ram -lt $required ] then return 1 else From bdeac5a92575ffb22cdd8c5073929bcce5c5a4df Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Fri, 10 Apr 2020 00:17:50 +0200 Subject: [PATCH 0902/3170] Move the comments about php where we can read it --- data/helpers.d/php | 46 ++++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/data/helpers.d/php b/data/helpers.d/php index 24314b52f..92fab46f6 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -23,6 +23,28 @@ # medium - Low usage, few people or/and publicly accessible. # high - High usage, frequently visited website. # +# +# The footprint of the service will be used to defined the maximum footprint we can allow, which is half the maximum RAM. +# So it will be used to defined 'pm.max_children' +# A lower value for the footprint will allow more children for 'pm.max_children'. And so for +# 'pm.start_servers', 'pm.min_spare_servers' and 'pm.max_spare_servers' which are defined from the +# value of 'pm.max_children' +# NOTE: 'pm.max_children' can't exceed 4 times the number of processor's cores. +# +# The usage value will defined the way php will handle the children for the pool. +# A value set as 'low' will set the process manager to 'ondemand'. Children will start only if the +# service is used, otherwise no child will stay alive. This config gives the lower footprint when the +# service is idle. But will use more proc since it has to start a child as soon it's used. +# Set as 'medium', the process manager will be at dynamic. If the service is idle, a number of children +# equal to pm.min_spare_servers will stay alive. So the service can be quick to answer to any request. +# The number of children can grow if needed. The footprint can stay low if the service is idle, but +# not null. The impact on the proc is a little bit less than 'ondemand' as there's always a few +# children already available. +# Set as 'high', the process manager will be set at 'static'. There will be always as many children as +# 'pm.max_children', the footprint is important (but will be set as maximum a quarter of the maximum +# RAM) but the impact on the proc is lower. The service will be quick to answer as there's always many +# children ready to answer. +# # Requires YunoHost version 2.7.2 or higher. ynh_add_fpm_config () { # Declare an array to define the options of this helper. @@ -232,6 +254,8 @@ ynh_remove_php () { # Define the values to configure php-fpm # +# [internal] +# # usage: ynh_get_scalable_phpfpm --usage=usage --footprint=footprint [--print] # | arg: -f, --footprint - Memory footprint of the service (low/medium/high). # low - Less than 20Mb of ram by pool. @@ -247,28 +271,6 @@ ynh_remove_php () { # high - High usage, frequently visited website. # # | arg: -p, --print - Print the result (intended for debug purpose only when packaging the app) -# -# -# The footprint of the service will be used to defined the maximum footprint we can allow, which is half the maximum RAM. -# So it will be used to defined 'pm.max_children' -# A lower value for the footprint will allow more children for 'pm.max_children'. And so for -# 'pm.start_servers', 'pm.min_spare_servers' and 'pm.max_spare_servers' which are defined from the -# value of 'pm.max_children' -# NOTE: 'pm.max_children' can't exceed 4 times the number of processor's cores. -# -# The usage value will defined the way php will handle the children for the pool. -# A value set as 'low' will set the process manager to 'ondemand'. Children will start only if the -# service is used, otherwise no child will stay alive. This config gives the lower footprint when the -# service is idle. But will use more proc since it has to start a child as soon it's used. -# Set as 'medium', the process manager will be at dynamic. If the service is idle, a number of children -# equal to pm.min_spare_servers will stay alive. So the service can be quick to answer to any request. -# The number of children can grow if needed. The footprint can stay low if the service is idle, but -# not null. The impact on the proc is a little bit less than 'ondemand' as there's always a few -# children already available. -# Set as 'high', the process manager will be set at 'static'. There will be always as many children as -# 'pm.max_children', the footprint is important (but will be set as maximum a quarter of the maximum -# RAM) but the impact on the proc is lower. The service will be quick to answer as there's always many -# children ready to answer. ynh_get_scalable_phpfpm () { local legacy_args=ufp # Declare an array to define the options of this helper. From 017b0e929c7f6a07f7828013316da7fcc3fe80f5 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Fri, 10 Apr 2020 00:31:06 +0200 Subject: [PATCH 0903/3170] Use YNH_DEFAULT_PHP_VERSION instead of 7.0 --- data/helpers.d/php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/data/helpers.d/php b/data/helpers.d/php index 07bf5ab7c..29b9995d4 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -195,9 +195,9 @@ ynh_install_php () { # Store phpversion into the config of this app ynh_app_setting_set $app phpversion $phpversion - if [ "$phpversion" == "7.0" ] + if [ "$phpversion" == "$YNH_DEFAULT_PHP_VERSION" ] then - ynh_die "Do not use ynh_install_php to install php7.0" + ynh_die "Do not use ynh_install_php to install php$YNH_DEFAULT_PHP_VERSION" fi # Store the ID of this app and the version of php requested for it @@ -211,12 +211,12 @@ ynh_install_php () { ynh_add_app_dependencies --package="php${phpversion}-fpm" ynh_add_app_dependencies --package="php$phpversion php${phpversion}-common $package" - # Set php7.0 back as the default version for php-cli. - update-alternatives --set php /usr/bin/php7.0 + # Set the default php version back as the default version for php-cli. + update-alternatives --set php /usr/bin/php$YNH_DEFAULT_PHP_VERSION # Pin this extra repository after packages are installed to prevent sury of doing shit ynh_pin_repo --package="*" --pin="origin \"packages.sury.org\"" --priority=200 --name=extra_php_version - ynh_pin_repo --package="php7.0*" --pin="origin \"packages.sury.org\"" --priority=600 --name=extra_php_version --append + ynh_pin_repo --package="php${$YNH_DEFAULT_PHP_VERSION}*" --pin="origin \"packages.sury.org\"" --priority=600 --name=extra_php_version --append # Advertise service in admin panel yunohost service add php${phpversion}-fpm --log "/var/log/php${phpversion}-fpm.log" @@ -229,11 +229,11 @@ ynh_remove_php () { # Get the version of php used by this app local phpversion=$(ynh_app_setting_get $app phpversion) - if [ "$phpversion" == "7.0" ] || [ -z "$phpversion" ] + if [ "$phpversion" == "$YNH_DEFAULT_PHP_VERSION" ] || [ -z "$phpversion" ] then - if [ "$phpversion" == "7.0" ] + if [ "$phpversion" == "$YNH_DEFAULT_PHP_VERSION" ] then - ynh_print_err "Do not use ynh_remove_php to install php7.0" + ynh_print_err "Do not use ynh_remove_php to install php$YNH_DEFAULT_PHP_VERSION" fi return 0 fi From 475754de1ed2f6c11a249f36c81a6b8233591286 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Fri, 10 Apr 2020 00:35:28 +0200 Subject: [PATCH 0904/3170] Add legacy_args --- data/helpers.d/hardware | 2 ++ 1 file changed, 2 insertions(+) diff --git a/data/helpers.d/hardware b/data/helpers.d/hardware index f98006aae..46e27caf4 100644 --- a/data/helpers.d/hardware +++ b/data/helpers.d/hardware @@ -9,6 +9,7 @@ # | arg: -o, --only_swap - Ignore real RAM, consider only swap ynh_get_ram () { # Declare an array to define the options of this helper. + local legacy_args=ftso declare -Ar args_array=( [f]=free [t]=total [s]=ignore_swap [o]=only_swap ) local free local total @@ -73,6 +74,7 @@ ynh_get_ram () { # | arg: -o, --only_swap - Ignore real RAM, consider only swap ynh_require_ram () { # Declare an array to define the options of this helper. + local legacy_args=rftso declare -Ar args_array=( [r]=required= [f]=free [t]=total [s]=ignore_swap [o]=only_swap ) local required local free From 1e6da91c783ce565087d1be96815b2b85864c0e6 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 11 Apr 2020 00:29:49 +0200 Subject: [PATCH 0905/3170] Add automail conf for https, + increase priority for automail conf and diagnosis --- data/templates/nginx/server.tpl.conf | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/data/templates/nginx/server.tpl.conf b/data/templates/nginx/server.tpl.conf index 093e96b0e..f2e9de2de 100644 --- a/data/templates/nginx/server.tpl.conf +++ b/data/templates/nginx/server.tpl.conf @@ -18,11 +18,11 @@ server { return 301 https://$http_host$request_uri; } - location /.well-known/ynh-diagnosis/ { + location ^~ '/.well-known/ynh-diagnosis/' { alias /tmp/.well-known/ynh-diagnosis/; } - location /.well-known/autoconfig/mail/ { + location ^~ '/.well-known/autoconfig/mail/' { alias /var/www/.well-known/{{ domain }}/autoconfig/mail/; } @@ -52,6 +52,10 @@ server { resolver_timeout 5s; {% endif %} + location ^~ '/.well-known/autoconfig/mail/' { + alias /var/www/.well-known/{{ domain }}/autoconfig/mail/; + } + access_by_lua_file /usr/share/ssowat/access.lua; include /etc/nginx/conf.d/{{ domain }}.d/*.conf; From 7b38b064d71d129cc11be2fa72bede9c81a579ef Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sat, 11 Apr 2020 01:54:32 +0200 Subject: [PATCH 0906/3170] Fixes and enhancements --- data/helpers.d/apt | 2 +- data/helpers.d/php | 76 +++++++++++++++++++++++++++++++++------------- 2 files changed, 56 insertions(+), 22 deletions(-) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index 09b881bdc..9a038ac4d 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -255,7 +255,7 @@ ynh_install_app_dependencies () { # Pin this sury repository to prevent sury of doing shit ynh_pin_repo --package="*" --pin="origin \"packages.sury.org\"" --priority=200 --name=extra_php_version - ynh_pin_repo --package="php7.0*" --pin="origin \"packages.sury.org\"" --priority=600 --name=extra_php_version --append + ynh_pin_repo --package="php${$YNH_DEFAULT_PHP_VERSION}*" --pin="origin \"packages.sury.org\"" --priority=600 --name=extra_php_version --append fi fi fi diff --git a/data/helpers.d/php b/data/helpers.d/php index 680f37245..bdd68e4bb 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -13,7 +13,7 @@ YNH_PHP_VERSION=${YNH_PHP_VERSION:-$YNH_DEFAULT_PHP_VERSION} # ----------------------------------------------------------------------------- # # usage 2: ynh_add_fpm_config [--phpversion=7.X] --usage=usage --footprint=footprint -# | arg: -v, --phpversion - Version of php to use.# +# | arg: -v, --phpversion - Version of php to use. # | arg: -f, --footprint - Memory footprint of the service (low/medium/high). # low - Less than 20Mb of ram by pool. # medium - Between 20Mb and 40Mb of ram by pool. @@ -61,7 +61,7 @@ ynh_add_fpm_config () { # Manage arguments with getopts ynh_handle_getopts_args "$@" - # The default behaviour is to use the template. + # The default behaviour is to use the template. use_template="${use_template:-1}" usage="${usage:-}" footprint="${footprint:-}" @@ -72,6 +72,13 @@ ynh_add_fpm_config () { # Set the default PHP-FPM version by default phpversion="${phpversion:-$YNH_PHP_VERSION}" + # If the requested php version is not the default version for YunoHost + if [ "$phpversion" != "$YNH_DEFAULT_PHP_VERSION" ] + then + # Install this specific version of php. + ynh_install_php --phpversion=$phpversion + fi + local fpm_config_dir="/etc/php/$phpversion/fpm" local fpm_service="php${phpversion}-fpm" # Configure PHP-FPM 5 on Debian Jessie @@ -87,7 +94,7 @@ ynh_add_fpm_config () { if [ $use_template -eq 1 ] then - # Usage 1, use the template in ../conf/php-fpm.conf + # Usage 1, use the template in ../conf/php-fpm.conf cp ../conf/php-fpm.conf "$finalphpconf" ynh_replace_string --match_string="__NAMETOCHANGE__" --replace_string="$app" --target_file="$finalphpconf" ynh_replace_string --match_string="__FINALPATH__" --replace_string="$final_path" --target_file="$finalphpconf" @@ -95,7 +102,9 @@ ynh_add_fpm_config () { ynh_replace_string --match_string="__PHPVERSION__" --replace_string="$phpversion" --target_file="$finalphpconf" else - # Usage 2, generate a php-fpm config file with ynh_get_scalable_phpfpm + # Usage 2, generate a php-fpm config file with ynh_get_scalable_phpfpm + + # Define the values to use for the configuration of php. ynh_get_scalable_phpfpm --usage=$usage --footprint=$footprint # Copy the default file @@ -141,14 +150,12 @@ ynh_add_fpm_config () { fi fi - - chown root: "$finalphpconf" ynh_store_file_checksum --file="$finalphpconf" if [ -e "../conf/php-fpm.ini" ] then - echo "Packagers ! Please do not use a separate php ini file, merge your directives in the pool file instead." >&2 + ynh_print_warn -message="Packagers ! Please do not use a separate php ini file, merge your directives in the pool file instead." finalphpini="$fpm_config_dir/conf.d/20-$app.ini" ynh_backup_if_checksum_is_different "$finalphpini" cp ../conf/php-fpm.ini "$finalphpini" @@ -167,18 +174,36 @@ ynh_add_fpm_config () { ynh_remove_fpm_config () { local fpm_config_dir=$(ynh_app_setting_get --app=$app --key=fpm_config_dir) local fpm_service=$(ynh_app_setting_get --app=$app --key=fpm_service) - # Assume default php version if not set + # Get the version of php used by this app + local phpversion=$(ynh_app_setting_get $app phpversion) + + # Assume default PHP-FPM version by default + phpversion="${phpversion:-$YNH_DEFAULT_PHP_VERSION}" + + # Assume default php files if not set if [ -z "$fpm_config_dir" ]; then fpm_config_dir="/etc/php/$YNH_DEFAULT_PHP_VERSION/fpm" fpm_service="php$YNH_DEFAULT_PHP_VERSION-fpm" fi ynh_secure_remove --file="$fpm_config_dir/pool.d/$app.conf" ynh_secure_remove --file="$fpm_config_dir/conf.d/20-$app.ini" 2>&1 - ynh_systemd_action --service_name=$fpm_service --action=reload + + if ynh_package_is_installed --package="php${phpversion}-fpm"; then + ynh_systemd_action --service_name=$fpm_service --action=reload + fi + + # If the php version used is not the default version for YunoHost + if [ "$phpversion" != "$YNH_DEFAULT_PHP_VERSION" ] + then + # Remove this specific version of php + ynh_remove_php + fi } # Install another version of php. # +# [internal] +# # usage: ynh_install_php --phpversion=phpversion [--package=packages] # | arg: -v, --phpversion - Version of php to install. # | arg: -p, --package - Additionnal php packages to install @@ -200,8 +225,15 @@ ynh_install_php () { ynh_die "Do not use ynh_install_php to install php$YNH_DEFAULT_PHP_VERSION" fi - # Store the ID of this app and the version of php requested for it - echo "$YNH_APP_INSTANCE_NAME:$phpversion" | tee --append "/etc/php/ynh_app_version" + # Create the file if doesn't exist already + touch /etc/php/ynh_app_version + + # Do not add twice the same line + if ! grep --quiet "$YNH_APP_INSTANCE_NAME:" "/etc/php/ynh_app_version" + then + # Store the ID of this app and the version of php requested for it + echo "$YNH_APP_INSTANCE_NAME:$phpversion" | tee --append "/etc/php/ynh_app_version" + fi # Add an extra repository for those packages ynh_install_extra_repo --repo="https://packages.sury.org/php/ $(lsb_release -sc) main" --key="https://packages.sury.org/php/apt.gpg" --priority=995 --name=extra_php_version @@ -216,7 +248,7 @@ ynh_install_php () { # Pin this extra repository after packages are installed to prevent sury of doing shit ynh_pin_repo --package="*" --pin="origin \"packages.sury.org\"" --priority=200 --name=extra_php_version - ynh_pin_repo --package="php${$YNH_DEFAULT_PHP_VERSION}*" --pin="origin \"packages.sury.org\"" --priority=600 --name=extra_php_version --append + ynh_pin_repo --package="php${YNH_DEFAULT_PHP_VERSION}*" --pin="origin \"packages.sury.org\"" --priority=600 --name=extra_php_version --append # Advertise service in admin panel yunohost service add php${phpversion}-fpm --log "/var/log/php${phpversion}-fpm.log" @@ -224,6 +256,8 @@ ynh_install_php () { # Remove the specific version of php used by the app. # +# [internal] +# # usage: ynh_install_php ynh_remove_php () { # Get the version of php used by this app @@ -233,27 +267,27 @@ ynh_remove_php () { then if [ "$phpversion" == "$YNH_DEFAULT_PHP_VERSION" ] then - ynh_print_err "Do not use ynh_remove_php to install php$YNH_DEFAULT_PHP_VERSION" + ynh_print_err "Do not use ynh_remove_php to remove php$YNH_DEFAULT_PHP_VERSION !" fi return 0 fi + # Create the file if doesn't exist already + touch /etc/php/ynh_app_version + # Remove the line for this app sed --in-place "/$YNH_APP_INSTANCE_NAME:$phpversion/d" "/etc/php/ynh_app_version" # If no other app uses this version of php, remove it. if ! grep --quiet "$phpversion" "/etc/php/ynh_app_version" then - # Purge php dependences for this version. - ynh_package_autopurge "php$phpversion php${phpversion}-fpm php${phpversion}-common" # Remove the service from the admin panel - yunohost service remove php${phpversion}-fpm - fi + if ynh_package_is_installed --package="php${phpversion}-fpm"; then + yunohost service remove php${phpversion}-fpm + fi - # If no other app uses alternate php versions, remove the extra repo for php - if [ ! -s "/etc/php/ynh_app_version" ] - then - ynh_secure_remove /etc/php/ynh_app_version + # Purge php dependencies for this version. + ynh_package_autopurge "php$phpversion php${phpversion}-fpm php${phpversion}-common" fi } From 7154bca33c9de5377c9fb76b0429ddbe2035608e Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sat, 11 Apr 2020 20:52:52 +0200 Subject: [PATCH 0907/3170] Fix php migration, integrate --package= to ynh_add_fpm_config --- data/helpers.d/php | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/data/helpers.d/php b/data/helpers.d/php index bdd68e4bb..a72cae3b3 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -6,13 +6,14 @@ YNH_PHP_VERSION=${YNH_PHP_VERSION:-$YNH_DEFAULT_PHP_VERSION} # Create a dedicated php-fpm config # -# usage 1: ynh_add_fpm_config [--phpversion=7.X] [--use_template] +# usage 1: ynh_add_fpm_config [--phpversion=7.X] [--use_template] [--package=packages] # | arg: -v, --phpversion - Version of php to use. # | arg: -t, --use_template - Use this helper in template mode. +# | arg: -p, --package - Additionnal php packages to install # # ----------------------------------------------------------------------------- # -# usage 2: ynh_add_fpm_config [--phpversion=7.X] --usage=usage --footprint=footprint +# usage 2: ynh_add_fpm_config [--phpversion=7.X] --usage=usage --footprint=footprint [--package=packages] # | arg: -v, --phpversion - Version of php to use. # | arg: -f, --footprint - Memory footprint of the service (low/medium/high). # low - Less than 20Mb of ram by pool. @@ -27,6 +28,8 @@ YNH_PHP_VERSION=${YNH_PHP_VERSION:-$YNH_DEFAULT_PHP_VERSION} # medium - Low usage, few people or/and publicly accessible. # high - High usage, frequently visited website. # +# | arg: -p, --package - Additionnal php packages to install for a specific version of php +# # # The footprint of the service will be used to defined the maximum footprint we can allow, which is half the maximum RAM. # So it will be used to defined 'pm.max_children' @@ -52,14 +55,16 @@ YNH_PHP_VERSION=${YNH_PHP_VERSION:-$YNH_DEFAULT_PHP_VERSION} # Requires YunoHost version 2.7.2 or higher. ynh_add_fpm_config () { # Declare an array to define the options of this helper. - local legacy_args=vtuf - declare -Ar args_array=( [v]=phpversion= [t]=use_template [u]=usage= [f]=footprint= ) + local legacy_args=vtufp + declare -Ar args_array=( [v]=phpversion= [t]=use_template [u]=usage= [f]=footprint= [p]=package= ) local phpversion local use_template local usage local footprint + local package # Manage arguments with getopts ynh_handle_getopts_args "$@" + package=${package:-} # The default behaviour is to use the template. use_template="${use_template:-1}" @@ -75,8 +80,18 @@ ynh_add_fpm_config () { # If the requested php version is not the default version for YunoHost if [ "$phpversion" != "$YNH_DEFAULT_PHP_VERSION" ] then + # If the argument --package is used, add the packages to ynh_install_php to install them from sury + if [ -n "$package" ]; then + local additionnal_packages="--package=$package" + else + local additionnal_packages="" + fi # Install this specific version of php. - ynh_install_php --phpversion=$phpversion + ynh_install_php --phpversion=$phpversion "$additionnal_packages" + elif [ -n "$package" ] + then + # Install the additionnal packages from the default repository + ynh_add_app_dependencies --package="$package" fi local fpm_config_dir="/etc/php/$phpversion/fpm" From 49d9832f0bc1ca4f2e27810a6439f8a921ac3b17 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sat, 11 Apr 2020 20:53:16 +0200 Subject: [PATCH 0908/3170] Better apt logging --- data/helpers.d/apt | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index 9a038ac4d..bcce02dcb 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -189,7 +189,16 @@ ynh_package_install_from_equivs () { # If install fails we use "apt-get check" to try to debug and diagnose possible unmet dependencies # Note the use of { } which allows to group commands without starting a subshell (otherwise the ynh_die wouldn't exit the current shell). # Be careful with the syntax : the semicolon + space at the end is important! - ynh_package_install -f || { apt-get check 2>&1; ynh_die --message="Unable to install dependencies"; } + + ynh_package_install -f || \ + { # If the installation failed + # 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 -n '/--fix-broken/,$p' >&2 + ynh_die --message="Unable to install dependencies"; } [[ -n "$TMPDIR" ]] && rm -rf $TMPDIR # Remove the temp dir. # check if the package is actually installed @@ -507,7 +516,7 @@ ynh_add_repo () { # | arg: -n, --name - Name for the files for this repo, $app as default value. # | arg: -a, --append - Do not overwrite existing files. # -# See https://manpages.debian.org/stretch/apt/apt_preferences.5.en.html for information about pinning. +# See https://manpages.debian.org/stretch/apt/apt_preferences.5.en.html#How_APT_Interprets_Priorities for information about pinning. # ynh_pin_repo () { # Declare an array to define the options of this helper. 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 0909/3170] 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 bf291a0c506f076a951116a123ed7cb791db3147 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 11 Apr 2020 23:25:51 +0200 Subject: [PATCH 0910/3170] Add 'yunohost tools versions' to have a simple way to fetch version from the webadmin --- data/actionsmap/yunohost.yml | 5 +++++ src/yunohost/tools.py | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index b0bb7f9dc..44419a342 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1459,6 +1459,11 @@ tools: help: List pending configuration files and exit action: store_true + ### tools_versions() + versions: + action_help: Display YunoHost's packages versions + api: GET /versions + subcategories: migrations: diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index e6d013894..3208bda60 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -43,7 +43,7 @@ from yunohost.dyndns import _dyndns_available, _dyndns_provides from yunohost.firewall import firewall_upnp from yunohost.service import service_start, service_enable from yunohost.regenconf import regen_conf -from yunohost.utils.packages import _dump_sources_list, _list_upgradable_apt_packages +from yunohost.utils.packages import _dump_sources_list, _list_upgradable_apt_packages, ynh_packages_version from yunohost.utils.error import YunohostError from yunohost.log import is_unit_operation, OperationLogger @@ -53,6 +53,8 @@ MIGRATIONS_STATE_PATH = "/etc/yunohost/migrations.yaml" logger = getActionLogger('yunohost.tools') +def tools_versions(): + return ynh_packages_version() def tools_ldapinit(): """ From 21c3cc4a5398dc435886e62e939a39ac3e8057e7 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sun, 12 Apr 2020 00:29:47 +0200 Subject: [PATCH 0911/3170] Store fpm_footprint and fpm_usage --- data/helpers.d/php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/data/helpers.d/php b/data/helpers.d/php index a72cae3b3..dbb5f5930 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -119,6 +119,10 @@ ynh_add_fpm_config () { else # Usage 2, generate a php-fpm config file with ynh_get_scalable_phpfpm + # Store settings + ynh_app_setting_set --app=$app --key=fpm_footprint --value=$footprint + ynh_app_setting_set --app=$app --key=fpm_usage --value=$usage + # Define the values to use for the configuration of php. ynh_get_scalable_phpfpm --usage=$usage --footprint=$footprint From b0cd37aecad25bacd74c765101473d8ca8150d7d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 12 Apr 2020 01:57:56 +0200 Subject: [PATCH 0912/3170] Make sure we have at least the standard stuff in /usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games:/snap/bin:/snap/bin:/var/lib/snapd/snap/bin:/snap/bin:/var/lib/snapd/snap/bin ~.~ --- bin/yunohost | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bin/yunohost b/bin/yunohost index 10a21a9da..b640c8c52 100755 --- a/bin/yunohost +++ b/bin/yunohost @@ -179,6 +179,10 @@ def _retrieve_namespaces(): ret.append(n) return ret +# 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: + os.environ["PATH"] = default_path + ":" + os.environ["PATH"] # Main action ---------------------------------------------------------- From 240a7d76d8b36942cd9a5360f14ebb6b044928bd Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Fri, 10 Apr 2020 23:44:13 +0200 Subject: [PATCH 0913/3170] [fix] lxc uid number is limited to 65536 by default --- src/yunohost/user.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index af5ff77fb..3696272d0 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -170,7 +170,8 @@ def user_create(operation_logger, username, firstname, lastname, mail, password, uid_guid_found = False while not uid_guid_found: - uid = str(random.randint(200, 99999)) + # LXC uid number is limited to 65536 by default + uid = str(random.randint(200, 65000)) uid_guid_found = uid not in all_uid and uid not in all_gid # Adapt values for LDAP From 2fcc93fcc80a7e8571b194ed9602c4198a9363a6 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Sun, 12 Apr 2020 16:37:55 +0200 Subject: [PATCH 0914/3170] add YNH_DEFAULT_PHP_VERSION in backup.py --- src/yunohost/backup.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 8408e7fa3..7ae6069e3 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -51,6 +51,7 @@ from yunohost.hook import ( from yunohost.tools import tools_postinstall from yunohost.regenconf import regen_conf from yunohost.log import OperationLogger +from yunohost.app import APPS_DEFAULT_PHP_VERSION from functools import reduce BACKUP_PATH = '/home/yunohost.backup' @@ -561,6 +562,7 @@ class BackupManager(): env_var["YNH_APP_ID"] = app_id env_var["YNH_APP_INSTANCE_NAME"] = app env_var["YNH_APP_INSTANCE_NUMBER"] = str(app_instance_nb) + env_var["YNH_DEFAULT_PHP_VERSION"] = APPS_DEFAULT_PHP_VERSION tmp_app_dir = os.path.join('apps/', app) tmp_app_bkp_dir = os.path.join(self.work_dir, tmp_app_dir, 'backup') env_var["YNH_APP_BACKUP_DIR"] = tmp_app_bkp_dir @@ -1411,6 +1413,7 @@ class RestoreManager(): 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(app_instance_nb) + env_dict_remove["YNH_DEFAULT_PHP_VERSION"] = APPS_DEFAULT_PHP_VERSION operation_logger = OperationLogger('remove_on_failed_restore', [('app', app_instance_name)], @@ -1458,6 +1461,7 @@ class RestoreManager(): env_var["YNH_APP_ID"] = app_id env_var["YNH_APP_INSTANCE_NAME"] = app env_var["YNH_APP_INSTANCE_NUMBER"] = str(app_instance_nb) + env_var["YNH_DEFAULT_PHP_VERSION"] = APPS_DEFAULT_PHP_VERSION env_var["YNH_APP_BACKUP_DIR"] = app_backup_in_archive return env_var From ef2f4b2a6ecb68557671710c1ef50d7b842d15f2 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Sun, 12 Apr 2020 16:52:23 +0200 Subject: [PATCH 0915/3170] some hooks use helpers without php --- data/helpers.d/php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/php b/data/helpers.d/php index 56d35cee8..c099fd7a2 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -2,7 +2,7 @@ # Declare the actual php version to use. # A packager willing to use another version of php can override the variable into its _common.sh. -YNH_PHP_VERSION=${YNH_PHP_VERSION:-$YNH_DEFAULT_PHP_VERSION} +YNH_PHP_VERSION=${YNH_PHP_VERSION:-${YNH_DEFAULT_PHP_VERSION:-7.0}} # Create a dedicated php-fpm config # From 509190532933f27e72b8519db2be19361fbc096e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 12 Apr 2020 17:22:57 +0200 Subject: [PATCH 0916/3170] Update data/helpers.d/php Co-Authored-By: Kayou --- data/helpers.d/php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/data/helpers.d/php b/data/helpers.d/php index c099fd7a2..4c711056d 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -2,7 +2,9 @@ # Declare the actual php version to use. # A packager willing to use another version of php can override the variable into its _common.sh. -YNH_PHP_VERSION=${YNH_PHP_VERSION:-${YNH_DEFAULT_PHP_VERSION:-7.0}} +if [ -n "$YNH_DEFAULT_PHP_VERSION" ]; then + YNH_PHP_VERSION=${YNH_PHP_VERSION:-YNH_DEFAULT_PHP_VERSION} +fi # Create a dedicated php-fpm config # From 6c9187e7e4d0458b9310ee1fed931e9e28385c56 Mon Sep 17 00:00:00 2001 From: Kayou Date: Sun, 12 Apr 2020 17:43:33 +0200 Subject: [PATCH 0917/3170] Update php --- data/helpers.d/php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/php b/data/helpers.d/php index 4c711056d..55c24ac57 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -2,7 +2,7 @@ # Declare the actual php version to use. # A packager willing to use another version of php can override the variable into its _common.sh. -if [ -n "$YNH_DEFAULT_PHP_VERSION" ]; then +if [ -n "${YNH_DEFAULT_PHP_VERSION:-}" ]; then YNH_PHP_VERSION=${YNH_PHP_VERSION:-YNH_DEFAULT_PHP_VERSION} fi From b20b7f3a852ed40ea20d74136c1ba0d010a01720 Mon Sep 17 00:00:00 2001 From: Kayou Date: Sun, 12 Apr 2020 20:03:09 +0200 Subject: [PATCH 0918/3170] Update php --- data/helpers.d/php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/data/helpers.d/php b/data/helpers.d/php index 55c24ac57..eaeee23ed 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -2,9 +2,8 @@ # Declare the actual php version to use. # A packager willing to use another version of php can override the variable into its _common.sh. -if [ -n "${YNH_DEFAULT_PHP_VERSION:-}" ]; then - YNH_PHP_VERSION=${YNH_PHP_VERSION:-YNH_DEFAULT_PHP_VERSION} -fi +YNH_DEFAULT_PHP_VERSION=${YNH_DEFAULT_PHP_VERSION:-7.0} +YNH_PHP_VERSION=${YNH_PHP_VERSION:-YNH_DEFAULT_PHP_VERSION} # Create a dedicated php-fpm config # From aaabf8c75c993030ef3056f2aba7e87d55278a4b Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Thu, 9 Apr 2020 17:37:04 +0200 Subject: [PATCH 0919/3170] [fix] also invalidate group cache --- src/yunohost/user.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 69baf4435..ee3504135 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -213,8 +213,9 @@ def user_create(operation_logger, username, firstname, lastname, mail, password, except Exception as e: raise YunohostError('user_creation_failed', user=username, error=e) - # Invalidate passwd to take user creation into account + # Invalidate passwd and group to take user and group creation into account subprocess.call(['nscd', '-i', 'passwd']) + subprocess.call(['nscd', '-i', 'group']) try: # Attempt to create user home folder From f03bb82aadd1d16226cafdad9581390c0a866799 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 12 Apr 2020 01:57:56 +0200 Subject: [PATCH 0920/3170] Make sure we have at least the standard stuff in /usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games:/snap/bin:/snap/bin:/var/lib/snapd/snap/bin:/snap/bin:/var/lib/snapd/snap/bin ~.~ --- bin/yunohost | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bin/yunohost b/bin/yunohost index 10a21a9da..b640c8c52 100755 --- a/bin/yunohost +++ b/bin/yunohost @@ -179,6 +179,10 @@ def _retrieve_namespaces(): ret.append(n) return ret +# 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: + os.environ["PATH"] = default_path + ":" + os.environ["PATH"] # Main action ---------------------------------------------------------- From 0c9a4509f765a60cc6f2840c243b8abb1c09a676 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Fri, 10 Apr 2020 23:44:13 +0200 Subject: [PATCH 0921/3170] [fix] lxc uid number is limited to 65536 by default --- src/yunohost/user.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index ee3504135..df0527655 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -170,7 +170,8 @@ def user_create(operation_logger, username, firstname, lastname, mail, password, uid_guid_found = False while not uid_guid_found: - uid = str(random.randint(200, 99999)) + # LXC uid number is limited to 65536 by default + uid = str(random.randint(200, 65000)) uid_guid_found = uid not in all_uid and uid not in all_gid # Adapt values for LDAP From 37fd69653a13e7cd90c61df8fbb52580d143f776 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 12 Apr 2020 23:14:07 +0200 Subject: [PATCH 0922/3170] Update changelog for 3.7.1.1 --- debian/changelog | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/debian/changelog b/debian/changelog index 018807b16..6245bb4b0 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,11 @@ +yunohost (3.7.1.1) stable; urgency=low + + - [fix] lxc uid number is limited to 65536 by default (0c9a4509) + - [fix] also invalidate group cache when creating users (aaabf8c7) + - [fix] Make sure to have a path that include sbin for stupid cron jobs (f03bb82a) + + -- Alexandre Aubin Sun, 12 Apr 2020 23:15:00 +0000 + yunohost (3.7.1) stable; urgency=low - [enh] Add ynh_permission_has_user helper (#905) @@ -13,7 +21,7 @@ yunohost (3.7.1) stable; urgency=low Thanks to all contributors <3 ! (Bram, Kay0u, Maniack, Matthew D.) - -- Alexandre Aubin Thu, 9 April 2020 14:52:00 +0000 + -- Alexandre Aubin Thu, 9 Apr 2020 14:52:00 +0000 yunohost (3.7.0.12) stable; urgency=low From 23c6ca52364b549afda78a42f21cbf9a0e1405c2 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Sun, 12 Apr 2020 23:43:39 +0200 Subject: [PATCH 0923/3170] Remove APPS_DEFAULT_PHP_VERSION from the core --- src/yunohost/app.py | 9 --------- src/yunohost/backup.py | 4 ---- 2 files changed, 13 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 5a0403af2..39793ec1a 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -59,7 +59,6 @@ APPS_CATALOG_CONF = '/etc/yunohost/apps_catalog.yml' APPS_CATALOG_CRON_PATH = "/etc/cron.daily/yunohost-fetch-apps-catalog" APPS_CATALOG_API_VERSION = 2 APPS_CATALOG_DEFAULT_URL = "https://app.yunohost.org/default" -APPS_DEFAULT_PHP_VERSION = "7.0" re_github_repo = re.compile( r'^(http[s]?://|git@)github.com[/:]' @@ -348,7 +347,6 @@ 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_DEFAULT_PHP_VERSION"] = APPS_DEFAULT_PHP_VERSION env_dict["YNH_APP_OLD_DOMAIN"] = old_domain env_dict["YNH_APP_OLD_PATH"] = old_path @@ -485,7 +483,6 @@ def app_upgrade(app=[], url=None, file=None): 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_DEFAULT_PHP_VERSION"] = APPS_DEFAULT_PHP_VERSION # Start register change on system related_to = [('app', app_instance_name)] @@ -698,7 +695,6 @@ 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_DEFAULT_PHP_VERSION"] = APPS_DEFAULT_PHP_VERSION # Start register change on system operation_logger.extra.update({'env': env_dict}) @@ -807,7 +803,6 @@ 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_remove["YNH_DEFAULT_PHP_VERSION"] = APPS_DEFAULT_PHP_VERSION # Execute remove script operation_logger_remove = OperationLogger('remove_on_failed_install', @@ -985,7 +980,6 @@ 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_DEFAULT_PHP_VERSION"] = APPS_DEFAULT_PHP_VERSION operation_logger.extra.update({'env': env_dict}) operation_logger.flush() @@ -1410,7 +1404,6 @@ def app_action_run(operation_logger, app, action, args=None): 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_DEFAULT_PHP_VERSION"] = APPS_DEFAULT_PHP_VERSION env_dict["YNH_ACTION"] = action _, path = tempfile.mkstemp() @@ -1474,7 +1467,6 @@ def app_config_show_panel(operation_logger, app): "YNH_APP_ID": app_id, "YNH_APP_INSTANCE_NAME": app, "YNH_APP_INSTANCE_NUMBER": str(app_instance_nb), - "YNH_DEFAULT_PHP_VERSION": APPS_DEFAULT_PHP_VERSION, } return_code, parsed_values = hook_exec(config_script, @@ -1548,7 +1540,6 @@ def app_config_apply(operation_logger, app, args): "YNH_APP_ID": app_id, "YNH_APP_INSTANCE_NAME": app, "YNH_APP_INSTANCE_NUMBER": str(app_instance_nb), - "YNH_DEFAULT_PHP_VERSION": APPS_DEFAULT_PHP_VERSION, } args = dict(urlparse.parse_qsl(args, keep_blank_values=True)) if args else {} diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 7ae6069e3..8408e7fa3 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -51,7 +51,6 @@ from yunohost.hook import ( from yunohost.tools import tools_postinstall from yunohost.regenconf import regen_conf from yunohost.log import OperationLogger -from yunohost.app import APPS_DEFAULT_PHP_VERSION from functools import reduce BACKUP_PATH = '/home/yunohost.backup' @@ -562,7 +561,6 @@ class BackupManager(): env_var["YNH_APP_ID"] = app_id env_var["YNH_APP_INSTANCE_NAME"] = app env_var["YNH_APP_INSTANCE_NUMBER"] = str(app_instance_nb) - env_var["YNH_DEFAULT_PHP_VERSION"] = APPS_DEFAULT_PHP_VERSION tmp_app_dir = os.path.join('apps/', app) tmp_app_bkp_dir = os.path.join(self.work_dir, tmp_app_dir, 'backup') env_var["YNH_APP_BACKUP_DIR"] = tmp_app_bkp_dir @@ -1413,7 +1411,6 @@ class RestoreManager(): 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(app_instance_nb) - env_dict_remove["YNH_DEFAULT_PHP_VERSION"] = APPS_DEFAULT_PHP_VERSION operation_logger = OperationLogger('remove_on_failed_restore', [('app', app_instance_name)], @@ -1461,7 +1458,6 @@ class RestoreManager(): env_var["YNH_APP_ID"] = app_id env_var["YNH_APP_INSTANCE_NAME"] = app env_var["YNH_APP_INSTANCE_NUMBER"] = str(app_instance_nb) - env_var["YNH_DEFAULT_PHP_VERSION"] = APPS_DEFAULT_PHP_VERSION env_var["YNH_APP_BACKUP_DIR"] = app_backup_in_archive return env_var From 71743d211bc9e95f4bdaca77199aa9c891892495 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Mon, 13 Apr 2020 10:44:56 +0200 Subject: [PATCH 0924/3170] Update data/helpers.d/php --- data/helpers.d/php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/php b/data/helpers.d/php index eaeee23ed..beaa01f14 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -2,7 +2,7 @@ # Declare the actual php version to use. # A packager willing to use another version of php can override the variable into its _common.sh. -YNH_DEFAULT_PHP_VERSION=${YNH_DEFAULT_PHP_VERSION:-7.0} +YNH_DEFAULT_PHP_VERSION=7.0 YNH_PHP_VERSION=${YNH_PHP_VERSION:-YNH_DEFAULT_PHP_VERSION} # Create a dedicated php-fpm config From 4b3f7a1ddd13f0ce7a5f0c807d5069a692ed6024 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Mon, 13 Apr 2020 10:45:42 +0200 Subject: [PATCH 0925/3170] Move YNH_DEFAULT_PHP_VERSION before the comment for YNH_DEFAULT_PHP_VERSION --- data/helpers.d/php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/php b/data/helpers.d/php index beaa01f14..0bef2ad13 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -1,8 +1,8 @@ #!/bin/bash +YNH_DEFAULT_PHP_VERSION=7.0 # Declare the actual php version to use. # A packager willing to use another version of php can override the variable into its _common.sh. -YNH_DEFAULT_PHP_VERSION=7.0 YNH_PHP_VERSION=${YNH_PHP_VERSION:-YNH_DEFAULT_PHP_VERSION} # Create a dedicated php-fpm config From ab2f918a8c5d0eee66fefd852ca43b05b5c1ec6f Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Mon, 13 Apr 2020 10:46:37 +0200 Subject: [PATCH 0926/3170] Missing $ --- data/helpers.d/php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/php b/data/helpers.d/php index 0bef2ad13..e70302912 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -3,7 +3,7 @@ YNH_DEFAULT_PHP_VERSION=7.0 # Declare the actual php version to use. # A packager willing to use another version of php can override the variable into its _common.sh. -YNH_PHP_VERSION=${YNH_PHP_VERSION:-YNH_DEFAULT_PHP_VERSION} +YNH_PHP_VERSION=${YNH_PHP_VERSION:-$YNH_DEFAULT_PHP_VERSION} # Create a dedicated php-fpm config # From 7f48631c3f4831f7f293f345865bee6e33d5dc11 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Mon, 13 Apr 2020 13:36:33 +0200 Subject: [PATCH 0927/3170] Optionnal dedicated service --- data/helpers.d/php | 80 ++++++++++++++++++++++++++++++---------------- 1 file changed, 53 insertions(+), 27 deletions(-) diff --git a/data/helpers.d/php b/data/helpers.d/php index 13036684b..14b107582 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -2,24 +2,35 @@ # Create a dedicated php-fpm config # -# usage: ynh_add_fpm_config [--phpversion=7.X] +# usage: ynh_add_fpm_config [--phpversion=7.X] [--dedicated_service] # | arg: -v, --phpversion - Version of php to use. +# | arg: -d, --dedicated_service - Use a dedicated php-fpm service instead of the common one. # # Requires YunoHost version 2.7.2 or higher. ynh_add_fpm_config () { # Declare an array to define the options of this helper. - local legacy_args=v - declare -Ar args_array=( [v]=phpversion= ) + local legacy_args=vd + declare -Ar args_array=( [v]=phpversion= [d]=dedicated_service ) local phpversion + local dedicated_service # Manage arguments with getopts ynh_handle_getopts_args "$@" # Configure PHP-FPM 7.0 by default phpversion="${phpversion:-7.0}" - local fpm_config_dir="/etc/php/$phpversion/dedicated-fpm" - local old_fpm_config_dir="/etc/php/$phpversion/fpm" - local fpm_service="php${phpversion}-fpm-$app" + # Do not use a dedicated service by default + dedicated_service=${dedicated_service:-0} + + if [ $dedicated_service -eq 1 ] + then + local fpm_service="php${phpversion}-fpm-$app" + local fpm_config_dir="/etc/php/$phpversion/dedicated-fpm" + local old_fpm_config_dir="/etc/php/$phpversion/fpm" + else + local fpm_service="php${phpversion}-fpm" + local fpm_config_dir="/etc/php/$phpversion/fpm" + fi # Configure PHP-FPM 5 on Debian Jessie if [ "$(ynh_get_debian_release)" == "jessie" ]; then fpm_config_dir="/etc/php5/fpm" @@ -31,22 +42,27 @@ ynh_add_fpm_config () { ynh_app_setting_set --app=$app --key=fpm_config_dir --value="$fpm_config_dir" ynh_app_setting_set --app=$app --key=fpm_service --value="$fpm_service" + ynh_app_setting_set --app=$app --key=fpm_dedicated_service --value="$dedicated_service" finalphpconf="$fpm_config_dir/pool.d/$app.conf" # Migrate from mutual php service to dedicated one. - if [ -e "$old_fpm_config_dir/pool.d/$app.conf" ] + if [ $dedicated_service -eq 1 ] then - ynh_print_info --message="Migrate to a dedicated php-fpm service for $app." - # Create a backup of the old file before migration - ynh_backup_if_checksum_is_different --file="$old_fpm_config_dir/pool.d/$app.conf" - # Remove the old php config file - ynh_secure_remove --file="$old_fpm_config_dir/pool.d/$app.conf" - # Reload php to release the socket and allow the dedicated service to use it - systemctl reload php${phpversion}-fpm - else - ynh_backup_if_checksum_is_different --file="$finalphpconf" + # If a config file exist in the common pool, move it. + if [ -e "$old_fpm_config_dir/pool.d/$app.conf" ] + then + ynh_print_info --message="Migrate to a dedicated php-fpm service for $app." + # Create a backup of the old file before migration + ynh_backup_if_checksum_is_different --file="$old_fpm_config_dir/pool.d/$app.conf" + # Remove the old php config file + ynh_secure_remove --file="$old_fpm_config_dir/pool.d/$app.conf" + # Reload php to release the socket and allow the dedicated service to use it + ynh_systemd_action --service_name=php${phpversion}-fpm --action=reload + fi fi + ynh_backup_if_checksum_is_different --file="$finalphpconf" + cp ../conf/php-fpm.conf "$finalphpconf" ynh_replace_string --match_string="__NAMETOCHANGE__" --replace_string="$app" --target_file="$finalphpconf" ynh_replace_string --match_string="__FINALPATH__" --replace_string="$final_path" --target_file="$finalphpconf" @@ -65,9 +81,10 @@ ynh_add_fpm_config () { ynh_store_file_checksum "$finalphpini" fi - # Create a config for a dedicated php-fpm service for the app - echo " -[Unit] + if [ $dedicated_service -eq 1 ] + then + # Create a config for a dedicated php-fpm service for the app + echo "[Unit] Description=PHP $phpversion FastCGI Process Manager for $app After=network.target @@ -81,10 +98,14 @@ ExecReload=/bin/kill -USR2 \$MAINPID WantedBy=multi-user.target " > ../conf/$fpm_service - # Create this dedicated php-fpm service - ynh_add_systemd_config --service=$fpm_service --template=$fpm_service - - ynh_systemd_action --service_name=$fpm_service --action=restart + # Create this dedicated php-fpm service + ynh_add_systemd_config --service=$fpm_service --template=$fpm_service + # Restart the service, as this service is either stopped or only for this app + ynh_systemd_action --service_name=$fpm_service --action=restart + else + # Reload php, to not impact other parts of the system using php + ynh_systemd_action --service_name=$fpm_service --action=reload + fi } # Remove the dedicated php-fpm config @@ -95,15 +116,20 @@ WantedBy=multi-user.target ynh_remove_fpm_config () { local fpm_config_dir=$(ynh_app_setting_get --app=$app --key=fpm_config_dir) local fpm_service=$(ynh_app_setting_get --app=$app --key=fpm_service) - # Assume php version 7 if not set + local dedicated_service=$(ynh_app_setting_get --app=$app --key=fpm_dedicated_service) + dedicated_service=${dedicated_service:-0} + # Assume php version 7.0 if not set if [ -z "$fpm_config_dir" ]; then fpm_config_dir="/etc/php/7.0/fpm" fpm_service="php7.0-fpm" fi - # Remove the dedicated service php-fpm service - ynh_remove_systemd_config --service=$fpm_service - yunohost service remove $fpm_service + if [ $dedicated_service -eq 1 ] + then + # Remove the dedicated service php-fpm service for the app + ynh_remove_systemd_config --service=$fpm_service + fi + ynh_secure_remove --file="$fpm_config_dir/pool.d/$app.conf" ynh_secure_remove --file="$fpm_config_dir/conf.d/20-$app.ini" 2>&1 } From 613142c34290b422e8d130432bc251e1a258503b Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Mon, 13 Apr 2020 13:37:00 +0200 Subject: [PATCH 0928/3170] Dedicate log for each php service --- data/helpers.d/php | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/data/helpers.d/php b/data/helpers.d/php index 14b107582..7aefc697e 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -83,6 +83,15 @@ ynh_add_fpm_config () { if [ $dedicated_service -eq 1 ] then + # Create a dedicated php-fpm.conf for the service + local globalphpconf=$fpm_config_dir/php-fpm-$app.conf + cp /etc/php/${phpversion}/fpm/php-fpm.conf $globalphpconf + + ynh_replace_string --match_string="^[; ]*pid *=.*" --replace_string="pid = /run/php/php${phpversion}-fpm-$app.pid" --target_file="$globalphpconf" + ynh_replace_string --match_string="^[; ]*error_log *=.*" --replace_string="error_log = /var/log/php/fpm-php.$app.log" --target_file="$globalphpconf" + ynh_replace_string --match_string="^[; ]*syslog.ident *=.*" --replace_string="syslog.ident = php-fpm-$app" --target_file="$globalphpconf" + ynh_replace_string --match_string="^[; ]*include *=.*" --replace_string="include = $finalphpconf" --target_file="$globalphpconf" + # Create a config for a dedicated php-fpm service for the app echo "[Unit] Description=PHP $phpversion FastCGI Process Manager for $app @@ -91,7 +100,7 @@ After=network.target [Service] Type=notify PIDFile=/run/php/php${phpversion}-fpm-$app.pid -ExecStart=/usr/sbin/php-fpm${phpversion} --nodaemonize --fpm-config $finalphpconf --pid /run/php/php${phpversion}-fpm-$app.pid +ExecStart=/usr/sbin/php-fpm$phpversion --nodaemonize --fpm-config $globalphpconf ExecReload=/bin/kill -USR2 \$MAINPID [Install] @@ -100,6 +109,10 @@ WantedBy=multi-user.target # Create this dedicated php-fpm service ynh_add_systemd_config --service=$fpm_service --template=$fpm_service + # Integrate the service in YunoHost admin panel + yunohost service add $fpm_service --log /var/log/php/fpm-php.$app.log --log_type file --description "Php-fpm dedicated to $app" + # Configure log rotate + ynh_use_logrotate --logfile=/var/log/php # Restart the service, as this service is either stopped or only for this app ynh_systemd_action --service_name=$fpm_service --action=restart else @@ -128,6 +141,10 @@ ynh_remove_fpm_config () { then # Remove the dedicated service php-fpm service for the app ynh_remove_systemd_config --service=$fpm_service + # Remove the global php-fpm conf + ynh_secure_remove --file="$fpm_config_dir/php-fpm-$app.conf" + # Remove the service from the list of services known by Yunohost + yunohost service remove $fpm_service fi ynh_secure_remove --file="$fpm_config_dir/pool.d/$app.conf" From a62513b0b0bb24d83031a614f34d687b42a835f1 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Mon, 13 Apr 2020 16:21:45 +0200 Subject: [PATCH 0929/3170] Clean after conflict --- data/helpers.d/php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/data/helpers.d/php b/data/helpers.d/php index b479747c6..7a26824a4 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -77,6 +77,8 @@ ynh_add_fpm_config () { if [ -n "$usage" ] || [ -n "$footprint" ]; then use_template=0 fi + # Do not use a dedicated service by default + dedicated_service=${dedicated_service:-0} # Set the default PHP-FPM version by default phpversion="${phpversion:-$YNH_PHP_VERSION}" @@ -98,14 +100,10 @@ ynh_add_fpm_config () { ynh_add_app_dependencies --package="$package" fi - # Do not use a dedicated service by default - dedicated_service=${dedicated_service:-0} - if [ $dedicated_service -eq 1 ] then local fpm_service="php${phpversion}-fpm-$app" local fpm_config_dir="/etc/php/$phpversion/dedicated-fpm" - local old_fpm_config_dir="/etc/php/$phpversion/fpm" else local fpm_service="php${phpversion}-fpm" local fpm_config_dir="/etc/php/$phpversion/fpm" @@ -128,6 +126,7 @@ ynh_add_fpm_config () { # Migrate from mutual php service to dedicated one. if [ $dedicated_service -eq 1 ] then + local old_fpm_config_dir="/etc/php/$phpversion/fpm" # If a config file exist in the common pool, move it. if [ -e "$old_fpm_config_dir/pool.d/$app.conf" ] then @@ -163,7 +162,7 @@ ynh_add_fpm_config () { ynh_get_scalable_phpfpm --usage=$usage --footprint=$footprint # Copy the default file - cp "$fpm_config_dir/pool.d/www.conf" "$finalphpconf" + cp "/etc/php/$phpversion/fpm/pool.d/www.conf" "$finalphpconf" # Replace standard variables into the default file ynh_replace_string --match_string="^\[www\]" --replace_string="[$app]" --target_file="$finalphpconf" 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 0930/3170] 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 8005429dc489f856f579b3406629b43ffc45af86 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Mon, 13 Apr 2020 20:50:58 +0200 Subject: [PATCH 0931/3170] Update php --- data/helpers.d/php | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/data/helpers.d/php b/data/helpers.d/php index 118477a0b..19e586b70 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -355,6 +355,18 @@ ynh_get_scalable_phpfpm () { footprint=50 fi + # Define the factor to determine min_spare_servers + # To not have not enough children ready to start for heavy apps. + if [ $footprint -le 20 ] + then + min_spare_servers_factor=8 + elif [ $footprint -le 35 ] + then + min_spare_servers_factor=5 + else + min_spare_servers_factor=3 + fi + # Define the way the process manager handle child processes. if [ "$usage" = "low" ] then @@ -405,7 +417,7 @@ ynh_get_scalable_phpfpm () { if [ "$php_pm" = "dynamic" ] then # Define pm.start_servers, pm.min_spare_servers and pm.max_spare_servers for a dynamic process manager - php_min_spare_servers=$(( $php_max_children / 8 )) + php_min_spare_servers=$(( $php_max_children / $min_spare_servers_factor )) php_min_spare_servers=$(at_least_one $php_min_spare_servers) php_max_spare_servers=$(( $php_max_children / 2 )) From 3484f73506e75cb4d44beb2bdb40f6b5509b8cb3 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Tue, 14 Apr 2020 12:35:35 +0200 Subject: [PATCH 0932/3170] 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 df47040462983b65b0978f17960a6920279a712b Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Tue, 14 Apr 2020 12:47:29 +0200 Subject: [PATCH 0933/3170] Allow to overwrite pm.max_children --- data/helpers.d/php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/data/helpers.d/php b/data/helpers.d/php index 118477a0b..401c262b3 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -402,6 +402,12 @@ ynh_get_scalable_phpfpm () { php_max_children=$max_proc fi + # Get an potential forced value for php_max_children + local php_forced_max_children=$(ynh_app_setting_get --app=$app --key=php_forced_max_children) + if [ -n "$php_forced_max_children" ]; then + php_max_children=$php_forced_max_children + fi + if [ "$php_pm" = "dynamic" ] then # Define pm.start_servers, pm.min_spare_servers and pm.max_spare_servers for a dynamic process manager From 14ef523585834aff3cfd280020ce5f06f4aeda20 Mon Sep 17 00:00:00 2001 From: Josue-T Date: Tue, 14 Apr 2020 13:45:05 +0200 Subject: [PATCH 0934/3170] 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 0935/3170] 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 0936/3170] 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 0937/3170] 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 0938/3170] 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 f9429ea91a52c884a1ba496e525b5a1664f1f55f Mon Sep 17 00:00:00 2001 From: ericgaspar Date: Wed, 15 Apr 2020 11:41:24 +0200 Subject: [PATCH 0939/3170] Spelling and typo corrections --- locales/fr.json | 320 ++++++++++++++++++++++++------------------------ 1 file changed, 160 insertions(+), 160 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 770d59dde..1a55fbed9 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -17,9 +17,9 @@ "app_removed": "{app:s} supprimé", "app_requirements_checking": "Vérification des paquets requis pour {app} …", "app_requirements_unmeet": "Les pré-requis de {app} ne sont pas satisfaits, le paquet {pkgname} ({version}) doit être {spec}", - "app_sources_fetch_failed": "Impossible de récupérer les fichiers sources, l'URL est-elle correcte ?", + "app_sources_fetch_failed": "Impossible de récupérer les fichiers sources, l’URL est-elle correcte ?", "app_unknown": "Application inconnue", - "app_unsupported_remote_type": "Ce type de commande à distance utilisé pour cette application n'est pas supporté", + "app_unsupported_remote_type": "Ce type de commande à distance utilisé pour cette application n’est pas supporté", "app_upgrade_failed": "Impossible de mettre à jour {app:s} : {error}", "app_upgraded": "{app:s} mis à jour", "ask_email": "Adresse de courriel", @@ -35,14 +35,14 @@ "backup_archive_open_failed": "Impossible d’ouvrir l’archive de la sauvegarde", "backup_cleaning_failed": "Impossible de nettoyer le dossier temporaire de sauvegarde", "backup_created": "Sauvegarde terminée", - "backup_creation_failed": "Impossible de créer l'archive de la sauvegarde", + "backup_creation_failed": "Impossible de créer l’archive de la sauvegarde", "backup_delete_error": "Impossible de supprimer '{path:s}'", "backup_deleted": "La sauvegarde a été supprimée", "backup_hook_unknown": "Script de sauvegarde '{hook:s}' inconnu", "backup_invalid_archive": "Archive de sauvegarde invalide", "backup_nothings_done": "Il n’y a rien à sauvegarder", "backup_output_directory_forbidden": "Dossier de destination interdit. Les sauvegardes ne peuvent être créées dans les sous-dossiers /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var ou /home/yunohost.backup/archives", - "backup_output_directory_not_empty": "Le répertoire de destination n'est pas vide", + "backup_output_directory_not_empty": "Le répertoire de destination n’est pas vide", "backup_output_directory_required": "Vous devez spécifier un dossier de destination pour la sauvegarde", "backup_running_hooks": "Exécution des scripts de sauvegarde …", "custom_app_url_required": "Vous devez spécifier une URL pour mettre à jour votre application personnalisée {app:s}", @@ -59,11 +59,11 @@ "done": "Terminé", "downloading": "Téléchargement en cours …", "dyndns_cron_installed": "La tâche cron pour le domaine DynDNS a été créée", - "dyndns_cron_remove_failed": "Impossible de supprimer la tâche cron DynDNS parce que: {error}", + "dyndns_cron_remove_failed": "Impossible de supprimer la tâche cron DynDNS parce que : {error}", "dyndns_cron_removed": "La tâche cron pour le domaine DynDNS enlevée", "dyndns_ip_update_failed": "Impossible de mettre à jour l’adresse IP sur le domaine DynDNS", "dyndns_ip_updated": "Mise à jour de votre IP pour le domaine DynDNS", - "dyndns_key_generating": "Génération de la clé DNS ... , cela peut prendre un certain temps.", + "dyndns_key_generating": "Génération de la clé DNS …, cela peut prendre un certain temps.", "dyndns_key_not_found": "Clé DNS introuvable pour le domaine", "dyndns_no_domain_registered": "Aucun domaine enregistré avec DynDNS", "dyndns_registered": "Domaine DynDNS enregistré", @@ -75,18 +75,18 @@ "field_invalid": "Champ incorrect : '{:s}'", "firewall_reload_failed": "Impossible de recharger le pare-feu", "firewall_reloaded": "Pare-feu rechargé", - "firewall_rules_cmd_failed": "Certaines règles du pare-feu n’ont pas pu être appliquées. Plus d'info dans le journal de log.", + "firewall_rules_cmd_failed": "Certaines règles du pare-feu n’ont pas pu être appliquées. Plus d’info dans le journal de log.", "hook_exec_failed": "Échec de l’exécution du script : {path:s}", "hook_exec_not_terminated": "L’exécution du script {path:s} ne s’est pas terminée correctement", "hook_list_by_invalid": "Propriété invalide pour lister les actions par celle-ci", - "hook_name_unknown": "Nom de l'action '{name:s}' inconnu", + "hook_name_unknown": "Nom de l’action '{name:s}' inconnu", "installation_complete": "Installation terminée", - "installation_failed": "Quelque chose s'est mal passé lors de l'installation", + "installation_failed": "Quelque chose s’est mal passé lors de l’installation", "ip6tables_unavailable": "Vous ne pouvez pas jouer avec ip6tables ici. Vous êtes soit dans un conteneur, soit votre noyau ne le prend pas en charge", "iptables_unavailable": "Vous ne pouvez pas jouer avec iptables ici. Vous êtes soit dans un conteneur, soit votre noyau ne le prend pas en charge", "ldap_initialized": "L’annuaire LDAP initialisé", "mail_alias_remove_failed": "Impossible de supprimer l’alias de courriel '{mail:s}'", - "mail_domain_unknown": "Le domaine '{domain:s}' de cette adress de courriel n'est pas valide. Merci d'utiliser un domain administré par ce serveur.", + "mail_domain_unknown": "Le domaine '{domain:s}' de cette adresse de courriel n’est pas valide. Merci d’utiliser un domaine administré par ce serveur.", "mail_forward_remove_failed": "Impossible de supprimer le courriel de transfert '{mail:s}'", "main_domain_change_failed": "Impossible de modifier le domaine principal", "main_domain_changed": "Le domaine principal modifié", @@ -112,13 +112,13 @@ "restore_complete": "Restauré", "restore_confirm_yunohost_installed": "Voulez-vous vraiment restaurer un système déjà installé ? [{answers:s}]", "restore_failed": "Impossible de restaurer le système", - "restore_hook_unavailable": "Le script de restauration '{part:s}' n’est pas disponible sur votre système, et ne l'est pas non plus dans l’archive", + "restore_hook_unavailable": "Le script de restauration '{part:s}' n’est pas disponible sur votre système, et ne l’est pas non plus dans l’archive", "restore_nothings_done": "Rien n’a été restauré", - "restore_running_app_script": "Exécution du script de restauration de l'application '{app:s}' .…", + "restore_running_app_script": "Exécution du script de restauration de l’application '{app:s}' …", "restore_running_hooks": "Exécution des scripts de restauration …", "service_add_failed": "Impossible d’ajouter le service '{service:s}'", "service_added": "Le service '{service:s}' a été ajouté", - "service_already_started": "Le service '{service:s}' est déjà en cours d'exécution", + "service_already_started": "Le service '{service:s}' est déjà en cours d’exécution", "service_already_stopped": "Le service '{service:s}' est déjà arrêté", "service_cmd_exec_failed": "Impossible d’exécuter la commande '{command:s}'", "service_disable_failed": "Impossible de ne pas lancer le service « {service:s} » au démarrage.\n\nJournaux récents du service : {logs:s}", @@ -152,46 +152,46 @@ "user_deleted": "L’utilisateur supprimé", "user_deletion_failed": "Impossible de supprimer l’utilisateur {user}: {error}", "user_home_creation_failed": "Impossible de créer le dossier personnel de l’utilisateur", - "user_unknown": "L'utilisateur {user:s} est inconnu", - "user_update_failed": "Impossible de mettre à jour l'utilisateur {user}: {error}", + "user_unknown": "L’utilisateur {user:s} est inconnu", + "user_update_failed": "Impossible de mettre à jour l’utilisateur {user}: {error}", "user_updated": "L’utilisateur a été modifié", "yunohost_already_installed": "YunoHost est déjà installé", "yunohost_ca_creation_failed": "Impossible de créer l’autorité de certification", "yunohost_configured": "YunoHost est maintenant configuré", - "yunohost_installing": "L'installation de YunoHost est en cours …", - "yunohost_not_installed": "YunoHost n'est pas correctement installé. Veuillez exécuter 'yunohost tools postinstall'", + "yunohost_installing": "L’installation de YunoHost est en cours …", + "yunohost_not_installed": "YunoHost n’est pas correctement installé. Veuillez exécuter 'yunohost tools postinstall'", "certmanager_attempt_to_replace_valid_cert": "Vous êtes en train de vouloir remplacer un certificat correct et valide pour le domaine {domain:s} ! (Utilisez --force pour contourner cela)", "certmanager_domain_unknown": "Domaine {domain:s} inconnu", "certmanager_domain_cert_not_selfsigned": "Le certificat du domaine {domain:s} n’est pas auto-signé. Voulez-vous vraiment le remplacer ? (Utilisez --force pour cela)", "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_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_error_no_A_record": "Aucun enregistrement DNS 'A' n’a été trouvé pour {domain:s}. Vous devez faire pointer votre nom de domaine vers votre machine pour être en mesure d’installer un certificat Let’s Encrypt ! (Si vous savez ce que vous faites, utilisez --no-checks pour désactiver ces contrôles)", - "certmanager_domain_dns_ip_differs_from_public_ip": "L’enregistrement DNS 'A' du domaine {domain:s} est différent de l’adresse IP de ce serveur. Si vous avez récemment modifié votre enregistrement 'A', veuillez attendre sa propagation (quelques vérificateur de propagation DNS sont disponibles en ligne). (Si vous savez ce que vous faites, utilisez --no-checks pour désactiver ces contrôles)", + "certmanager_domain_dns_ip_differs_from_public_ip": "L’enregistrement DNS 'A' du domaine {domain:s} est différent de l’adresse IP de ce serveur. Si vous avez récemment modifié votre enregistrement 'A', veuillez attendre sa propagation (quelques vé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}", "certmanager_cert_install_success_selfsigned": "Le certificat auto-signé est maintenant installé pour le domaine « {domain:s} »", "certmanager_cert_install_success": "Le certificat Let’s Encrypt est maintenant installé pour le domaine « {domain:s} »", - "certmanager_cert_renew_success": "Certificat Let's Encrypt renouvelé pour le domaine '{domain:s}'", + "certmanager_cert_renew_success": "Certificat Let’s Encrypt renouvelé pour le domaine '{domain:s}'", "certmanager_cert_signing_failed": "Impossible de signer le nouveau certificat", "certmanager_no_cert_file": "Impossible de lire le fichier du certificat pour le domaine {domain:s} (fichier : {file:s})", - "certmanager_conflicting_nginx_file": "Impossible de préparer le domaine pour le défi ACME : le fichier de configuration NGINX {filepath:s} est en conflit et doit être préalablement retiré", + "certmanager_conflicting_nginx_file": "Impossible de préparer le domaine pour le défi ACME : le fichier de configuration NGINX {filepath:s} est en conflit et doit être préalablement retiré", "certmanager_hit_rate_limit": "Trop de certificats ont déjà été émis récemment pour ce même ensemble de domaines {domain:s}. Veuillez réessayer plus tard. Lisez https://letsencrypt.org/docs/rate-limits/ pour obtenir plus de détails sur les ratios et limitations", - "ldap_init_failed_to_create_admin": "L’initialisation de l'annuaire LDAP n’a pas réussi à créer l’utilisateur admin", - "domain_cannot_remove_main": "Vous ne pouvez pas supprimer '{domain:s}' car il s'agit du domaine principal. Vous devez d'abord définir un autre domaine comme domaine principal à l'aide de 'yunohost domain main-domain -n ', voici la liste des domaines candidats. : {other_domains:s}", + "ldap_init_failed_to_create_admin": "L’initialisation de l’annuaire LDAP n’a pas réussi à créer l’utilisateur admin", + "domain_cannot_remove_main": "Vous ne pouvez pas supprimer '{domain:s}' car il s’agit du domaine principal. Vous devez d’abord définir un autre domaine comme domaine principal à l’aide de 'yunohost domain main-domain -n ', voici la liste des domaines candidats. : {other_domains:s}", "certmanager_self_ca_conf_file_not_found": "Le fichier de configuration pour l’autorité du certificat auto-signé est introuvable (fichier : {file:s})", "certmanager_unable_to_parse_self_CA_name": "Impossible d’analyser le nom de l’autorité du certificat auto-signé (fichier : {file:s})", "mailbox_used_space_dovecot_down": "Le service de 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_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_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_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 (peut-être que ça n’en causera pas).", "yunohost_ca_creation_success": "L’autorité de certification locale créée.", "app_already_installed_cant_change_url": "Cette application est déjà installée. L’URL ne peut pas être changé simplement par cette fonction. Vérifiez si cela est disponible avec `app changeurl`.", "app_change_url_failed_nginx_reload": "Le redémarrage de Nginx a échoué. Voici la sortie de 'nginx -t' :\n{nginx_errors:s}", - "app_change_url_identical_domains": "L’ancien et le nouveau couple domaine/chemin_de_l'URL sont identiques pour ('{domain:s}{path:s}'), rien à faire.", + "app_change_url_identical_domains": "L’ancien et le nouveau couple domaine/chemin_de_l’URL sont identiques pour ('{domain:s}{path:s}'), rien à faire.", "app_change_url_no_script": "L’application '{app_name:s}' ne prend pas encore en charge le changement d’URL. Vous devriez peut-être la mettre à jour.", "app_change_url_success": "L’URL de l’application {app:s} a été changée en {domain:s}{path:s}", "app_location_unavailable": "Cette URL n’est pas disponible ou est en conflit avec une application existante :\n{apps:s}", @@ -206,16 +206,16 @@ "global_settings_setting_example_int": "Exemple d’option de type entier", "global_settings_setting_example_string": "Exemple d’option de type chaîne", "global_settings_setting_example_enum": "Exemple d’option de type énumération", - "global_settings_unknown_type": "Situation inattendue : la configuration {setting:s} semble avoir le type {unknown_type:s} mais celui-ci n'est pas pris en charge par le système.", + "global_settings_unknown_type": "Situation inattendue : la configuration {setting:s} semble avoir le type {unknown_type:s} mais celui-ci n’est pas pris en charge par le système.", "global_settings_unknown_setting_from_settings_file": "Clé inconnue dans les paramètres : '{setting_key:s}', rejet de cette clé et sauvegarde de celle-ci dans /etc/yunohost/unkown_settings.json", "backup_abstract_method": "Cette méthode de sauvegarde reste à implémenter", - "backup_applying_method_tar": "Création de l’archive TAR de la sauvegarde…", + "backup_applying_method_tar": "Création de l’archive TAR de la sauvegarde …", "backup_applying_method_copy": "Copie de tous les fichiers à sauvegarder …", - "backup_applying_method_borg": "Envoi de tous les fichiers à sauvegarder dans le répertoire borg-backup…", + "backup_applying_method_borg": "Envoi de tous les fichiers à sauvegarder dans le répertoire borg-backup …", "backup_applying_method_custom": "Appel de la méthode de sauvegarde personnalisée '{method:s}' …", "backup_archive_system_part_not_available": "La partie '{part:s}' du système n’est pas disponible dans cette sauvegarde", - "backup_archive_writing_error": "Impossible d'ajouter des fichiers '{source:s}' (nommés dans l'archive : '{dest:s}') à sauvegarder dans l'archive compressée '{archive:s}'", - "backup_ask_for_copying_if_needed": "Voulez-vous effectuer la sauvegarde en utilisant {size:s} temporairement? (Cette méthode est utilisée car certains fichiers n'ont pas pu être préparés avec une méthode plus efficace.)", + "backup_archive_writing_error": "Impossible d’ajouter des fichiers '{source:s}' (nommés dans l’archive : '{dest:s}') à sauvegarder dans l’archive compressée '{archive:s}'", + "backup_ask_for_copying_if_needed": "Voulez-vous effectuer la sauvegarde en utilisant {size:s} temporairement ? (Cette méthode est utilisée car certains fichiers n’ont pas pu être préparés avec une méthode plus efficace.)", "backup_borg_not_implemented": "La méthode de sauvegarde Borg n’est pas encore implémentée", "backup_cant_mount_uncompress_archive": "Impossible de monter en lecture seule le dossier de l’archive décompressée", "backup_copying_to_organize_the_archive": "Copie de {size:s} Mo pour organiser l’archive", @@ -235,33 +235,33 @@ "global_settings_cant_serialize_settings": "Échec de la sérialisation des données de paramétrage car : {reason:s}", "restore_removing_tmp_dir_failed": "Impossible de sauvegarder un ancien dossier temporaire", "restore_extracting": "Extraction des fichiers nécessaires depuis l’archive …", - "restore_may_be_not_enough_disk_space": "Votre système semble ne pas avoir suffisamment d’espace disponible (L'espace libre est de {free_space:d} octets. Le besoin d'espace nécessaire est de {needed_space:d} octets. En appliquant une marge de sécurité, la quantité d'espace nécessaire est de {margin:d} octets)", - "restore_not_enough_disk_space": "Espace disponible insuffisant (L'espace libre est de {free_space:d} octets. Le besoin d'espace nécessaire est de {needed_space:d} octets. En appliquant une marge de sécurité, la quantité d'espace nécessaire est de {margin:d} octets)", + "restore_may_be_not_enough_disk_space": "Votre système semble ne pas avoir suffisamment d’espace disponible (L’espace libre est de {free_space:d} octets. Le besoin d’espace nécessaire est de {needed_space:d} octets. En appliquant une marge de sécurité, la quantité d’espace nécessaire est de {margin:d} octets)", + "restore_not_enough_disk_space": "Espace disponible insuffisant (L’espace libre est de {free_space:d} octets. Le besoin d’espace nécessaire est de {needed_space:d} octets. En appliquant une marge de sécurité, la quantité d’espace nécessaire est de {margin:d} octets)", "restore_system_part_failed": "Impossible de restaurer la partie '{part:s}' du système", "backup_couldnt_bind": "Impossible de lier {src:s} avec {dest:s}.", "domain_dns_conf_is_just_a_recommendation": "Cette page montre la configuration *recommandée*. Elle ne configure *pas* le DNS pour vous. Il est de votre responsabilité que de configurer votre zone DNS chez votre fournisseur/registrar DNS avec cette recommandation.", - "migrations_cant_reach_migration_file": "Impossible d'accéder aux fichiers de migration via le chemin '%s'", + "migrations_cant_reach_migration_file": "Impossible d’accéder aux fichiers de migration via le chemin '%s'", "migrations_loading_migration": "Chargement de la migration {id} …", "migrations_migration_has_failed": "La migration {id} a échoué avec l’exception {exception} : annulation", "migrations_no_migrations_to_run": "Aucune migration à lancer", - "migrations_skip_migration": "Ignorer et passer la migration {id}…", - "server_shutdown": "Le serveur va éteindre", + "migrations_skip_migration": "Ignorer et passer la migration {id} …", + "server_shutdown": "Le serveur va s’éteindre", "server_shutdown_confirm": "Le serveur va être éteint immédiatement, le voulez-vous vraiment ? [{answers:s}]", "server_reboot": "Le serveur va redémarrer", "server_reboot_confirm": "Le serveur va redémarrer immédiatement, le voulez-vous vraiment ? [{answers:s}]", "app_upgrade_some_app_failed": "Certaines applications n’ont pas été mises à jour", "dyndns_could_not_check_provide": "Impossible de vérifier si {provider:s} peut fournir {domain:s}.", "dyndns_domain_not_provided": "Le fournisseur DynDNS {provider:s} ne peut pas fournir le domaine {domain:s}.", - "app_make_default_location_already_used": "Impossible de configurer l’application '{app}' par défaut pour le domaine '{domain}' car il est déjà utilisé par l'application '{other_app}'", + "app_make_default_location_already_used": "Impossible de configurer l’application '{app}' par défaut pour le domaine '{domain}' car il est déjà utilisé par l’application '{other_app}'", "app_upgrade_app_name": "Mise à jour de l’application {app} …", - "backup_output_symlink_dir_broken": "Votre répertoire d'archivage '{path:s}' est un lien symbolique brisé. Peut-être avez-vous oublié de re/monter ou de brancher le support de stockage sur lequel il pointe.", + "backup_output_symlink_dir_broken": "Votre répertoire d’archivage '{path:s}' est un lien symbolique brisé. Peut-être avez-vous oublié de re/monter ou de brancher le support de stockage sur lequel il pointe.", "migrate_tsig_end": "La migration à HMAC-SHA-512 est terminée", "migrate_tsig_failed": "La migration du domaine DynDNS {domain} à hmac-sha512 a échoué. Annulation des modifications. Erreur : {error_code} - {error}", "migrate_tsig_start": "L’algorithme de génération des clefs n’est pas suffisamment sécurisé pour la signature TSIG du domaine '{domain}', lancement de la migration vers HMAC-SHA-512 qui est plus sécurisé", "migrate_tsig_wait": "Attendre trois minutes pour que le serveur DynDNS prenne en compte la nouvelle clef …", "migrate_tsig_wait_2": "2 minutes …", "migrate_tsig_wait_3": "1 minute …", - "migrate_tsig_wait_4": "30 secondes …", + "migrate_tsig_wait_4": "30 secondes …", "migrate_tsig_not_needed": "Il ne semble pas que vous utilisez un domaine DynDNS, donc aucune migration n’est nécessaire.", "migration_description_0001_change_cert_group_to_sslcert": "Changement des permissions de groupe des certificats de « metronome » à « ssl-cert »", "migration_description_0002_migrate_to_tsig_sha256": "Amélioration de la sécurité de DynDNS TSIG en utilisant SHA512 au lieu de MD5", @@ -270,13 +270,13 @@ "migration_0003_patching_sources_list": "Modification du fichier sources.lists …", "migration_0003_main_upgrade": "Démarrage de la mise à niveau principale …", "migration_0003_fail2ban_upgrade": "Démarrage de la mise à niveau de fail2ban …", - "migration_0003_restoring_origin_nginx_conf": "Votre fichier /etc/nginx/nginx.conf a été modifié d’une manière ou d’une autre. La migration va d’abords le réinitialiser à son état initial. Le fichier précédent sera disponible en tant que {backup_dest}.", + "migration_0003_restoring_origin_nginx_conf": "Votre fichier /etc/nginx/nginx.conf a été modifié d’une manière ou d’une autre. La migration va d’abord le réinitialiser à son état initial. Le fichier précédent sera disponible en tant que {backup_dest}.", "migration_0003_yunohost_upgrade": "Démarrage de la mise à niveau du paquet YunoHost. La migration se terminera, mais la mise à jour réelle aura lieu immédiatement après. Une fois cette opération terminée, vous pourriez avoir à vous reconnecter à l’administration via le panel web.", "migration_0003_not_jessie": "La distribution Debian actuelle n’est pas Jessie !", - "migration_0003_system_not_fully_up_to_date": "Votre système n’est pas complètement à jour. Veuillez mener une mise à jour classique avant de lancer à migration à Stretch.", + "migration_0003_system_not_fully_up_to_date": "Votre système n’est pas complètement à jour. Veuillez mener une mise à jour classique avant de lancer la migration à Stretch.", "migration_0003_still_on_jessie_after_main_upgrade": "Quelque chose s’est mal passé pendant la mise à niveau principale : le système est toujours sur Debian Jessie !? Pour investiguer sur le problème, veuillez regarder les journaux {log}:s …", "migration_0003_general_warning": "Veuillez noter que cette migration est une opération délicate. Si l’équipe YunoHost a fait de son mieux pour la relire et la tester, la migration pourrait tout de même casser des parties de votre système ou de vos applications.\n\nEn conséquence, nous vous recommandons :\n - de lancer une sauvegarde de vos données ou applications critiques. Plus d’informations sur https://yunohost.org/backup ;\n - d’être patient après avoir lancé la migration : selon votre connexion internet et matériel, cela pourrait prendre jusqu’à quelques heures pour que tout soit à niveau.\n\nEn outre, le port SMTP utilisé par les clients de messagerie externes comme (Thunderbird ou K9-Mail) a été changé de 465 (SSL/TLS) à 587 (STARTTLS). L’ancien port 465 sera automatiquement fermé et le nouveau port 587 sera ouvert dans le pare-feu. Vous et vos utilisateurs *devront* adapter la configuration de vos clients de messagerie en conséquence.", - "migration_0003_problematic_apps_warning": "Veuillez noter que les applications installées potentiellement problématiques suivantes ont été détectées. Il semble que celles-ci n'ont pas été installées à partir d'un catalogue d'applications, ou ne sont pas marquées comme \"fonctionnelle\". Par conséquent, il ne peut pas être garanti qu'ils fonctionneront toujours après la mise à niveau: {problematic_apps}", + "migration_0003_problematic_apps_warning": "Veuillez noter que les applications installées potentiellement problématiques suivantes ont été détectées. Il semble que celles-ci n’ont pas été installées à partir d’un catalogue d’applications, ou ne sont pas marquées comme \"fonctionnelle\". Par conséquent, il ne peut pas être garanti qu’ils fonctionneront toujours après la mise à niveau: {problematic_apps}", "migration_0003_modified_files": "Veuillez noter que les fichiers suivants ont été détectés comme modifiés manuellement et pourraient être écrasés à la fin de la mise à niveau : {manually_modified_files}", "migrations_list_conflict_pending_done": "Vous ne pouvez pas utiliser --previous et --done simultanément.", "migrations_to_be_ran_manually": "La migration {id} doit être lancée manuellement. Veuillez aller dans Outils > Migrations dans l’interface admin, ou lancer `yunohost tools migrations migrate`.", @@ -295,15 +295,15 @@ "service_description_slapd": "Stocke les utilisateurs, domaines et leurs informations liées", "service_description_ssh": "Vous permet de vous connecter à distance à votre serveur via un terminal (protocole SSH)", "service_description_yunohost-api": "Permet les interactions entre l’interface web de YunoHost et le système", - "service_description_yunohost-firewall": "Gère l'ouverture et la fermeture des ports de connexion aux services", + "service_description_yunohost-firewall": "Gère l’ouverture et la fermeture des ports de connexion aux services", "experimental_feature": "Attention : cette fonctionnalité est expérimentale et ne doit pas être considérée comme stable, vous ne devriez pas l’utiliser à moins que vous ne sachiez ce que vous faites.", "log_corrupted_md_file": "Le fichier YAML de métadonnées associé aux logs est corrompu : '{md_file}'\nErreur : {error}", "log_category_404": "Le journal de la catégorie '{category}' n’existe pas", "log_link_to_log": "Journal complet de cette opération : ' {desc} '", "log_help_to_get_log": "Pour voir le journal de cette opération '{desc}', utilisez la commande 'yunohost log display {name}'", - "log_link_to_failed_log": "L’opération '{desc}' a échouée ! Pour obtenir de l’aide, merci de partager le journal de l'opération en cliquant ici", + "log_link_to_failed_log": "L’opération '{desc}' a échoué ! Pour obtenir de l’aide, merci de partager le journal de l’opération en cliquant ici", "backup_php5_to_php7_migration_may_fail": "Impossible de convertir votre archive pour prendre en charge PHP 7, vous pourriez ne plus pouvoir restaurer vos applications PHP (cause : {error:s})", - "log_help_to_get_failed_log": "L’opération '{desc}' a échouée ! Pour obtenir de l’aide, merci de partager le journal de l'opération en utilisant la commande 'yunohost log display {name} --share'", + "log_help_to_get_failed_log": "L’opération '{desc}' a échoué ! Pour obtenir de l’aide, merci de partager le journal de l’opération en utilisant la commande 'yunohost log display {name} --share'", "log_does_exists": "Il n’existe pas de journal de l’opération ayant pour nom '{log}', utiliser 'yunohost log list' pour voir tous les fichiers de journaux disponibles", "log_operation_unit_unclosed_properly": "L’opération ne s’est pas terminée correctement", "log_app_change_url": "Changer l’URL de l’application '{}'", @@ -337,14 +337,14 @@ "migration_description_0005_postgresql_9p4_to_9p6": "Migration des bases de données de PostgreSQL 9.4 vers PostgreSQL 9.6", "migration_0005_postgresql_94_not_installed": "PostgreSQL n’a pas été installé sur votre système. Rien à faire !", "migration_0005_postgresql_96_not_installed": "PostgreSQL 9.4 a été trouvé et installé, mais pas PostgreSQL 9.6 !? Quelque chose d’étrange a dû arriver à votre système… :(", - "migration_0005_not_enough_space": "Laissez suffisamment d'espace disponible dans {path} pour exécuter la migration.", + "migration_0005_not_enough_space": "Laissez suffisamment d’espace disponible dans {path} pour exécuter la migration.", "service_description_php7.0-fpm": "Exécute des applications écrites en PHP avec NGINX", "users_available": "Liste des utilisateurs disponibles :", "good_practices_about_admin_password": "Vous êtes maintenant sur le point de définir un nouveau mot de passe d’administration. Le mot de passe doit comporter au moins 8 caractères – bien qu’il soit recommandé d’utiliser un mot de passe plus long (c’est-à-dire une phrase secrète) et/ou d’utiliser différents types de caractères (majuscules, minuscules, chiffres et caractères spéciaux).", "good_practices_about_user_password": "Vous êtes maintenant sur le point de définir un nouveau mot de passe utilisateur. Le mot de passe doit comporter au moins 8 caractères - bien qu’il soit recommandé d’utiliser un mot de passe plus long (c’est-à-dire une phrase secrète) et/ou d’utiliser différents types de caractères tels que : majuscules, minuscules, chiffres et caractères spéciaux.", "migration_description_0006_sync_admin_and_root_passwords": "Synchroniser les mots de passe admin et root", - "migration_0006_disclaimer": "YunoHost s'attend maintenant à ce que les mots de passe administrateur et racine soient synchronisés. Cette migration remplace votre mot de passe root par le mot de passe administrateur.", - "password_listed": "Ce mot de passe est l'un des mots de passe les plus utilisés dans le monde. Veuillez choisir quelque chose d'un peu plus singulier.", + "migration_0006_disclaimer": "YunoHost s’attend maintenant à ce que les mots de passe administrateur et racine soient synchronisés. Cette migration remplace votre mot de passe root par le mot de passe administrateur.", + "password_listed": "Ce mot de passe est l’un des mots de passe les plus utilisés dans le monde. Veuillez choisir quelque chose d’un peu plus singulier.", "password_too_simple_1": "Le mot de passe doit comporter au moins 8 caractères", "password_too_simple_2": "Le mot de passe doit comporter au moins 8 caractères et contenir des chiffres, des majuscules et des minuscules", "password_too_simple_3": "Le mot de passe doit comporter au moins 8 caractères et contenir des chiffres, des majuscules, des minuscules et des caractères spéciaux", @@ -352,35 +352,35 @@ "root_password_desynchronized": "Le mot de passe administrateur a été changé, mais YunoHost n’a pas pu le propager au mot de passe root !", "aborting": "Annulation.", "app_not_upgraded": "L’application {failed_app} n’a pas été mise à jour et par conséquence les applications suivantes n’ont pas été mises à jour : {apps}", - "app_start_install": "Installation de l'application {app} …", - "app_start_remove": "Suppression de l'application {app} …", - "app_start_backup": "Collecte des fichiers devant être sauvegardés pour l'application {app} …", - "app_start_restore": "Restauration de l'application {app} …", + "app_start_install": "Installation de l’application {app} …", + "app_start_remove": "Suppression de l’application {app} …", + "app_start_backup": "Collecte des fichiers devant être sauvegardés pour l’application {app} …", + "app_start_restore": "Restauration de l’application {app} …", "app_upgrade_several_apps": "Les applications suivantes seront mises à jour : {apps}", "ask_new_domain": "Nouveau domaine", "ask_new_path": "Nouveau chemin", - "backup_actually_backuping": "Création d'une archive de sauvegarde à partir des fichiers collectés …", - "backup_mount_archive_for_restore": "Préparation de l'archive pour restauration …", - "confirm_app_install_warning": "Avertissement : cette application peut fonctionner mais n'est pas bien intégrée dans YunoHost. Certaines fonctionnalités telles que l'authentification unique et la sauvegarde/restauration peuvent ne pas être disponibles. L'installer quand même ? [{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}'", + "backup_actually_backuping": "Création d’une archive de sauvegarde à partir des fichiers collectés …", + "backup_mount_archive_for_restore": "Préparation de l’archive pour restauration …", + "confirm_app_install_warning": "Avertissement : cette application peut fonctionner mais n’est pas bien intégrée dans YunoHost. Certaines fonctionnalités telles que l’authentification unique et la sauvegarde/restauration peuvent ne pas être disponibles. L’installer quand même ? [{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'.", "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.", + "file_does_not_exist": "Le fichier dont le chemin est {path:s} n’existe pas.", "global_settings_setting_security_password_admin_strength": "Qualité du mot de passe administrateur", - "global_settings_setting_security_password_user_strength": "Qualité du mot de passe de l'utilisateur", - "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Autoriser l'utilisation de la clé hôte DSA (obsolète) pour la configuration du service SSH", + "global_settings_setting_security_password_user_strength": "Qualité du mot de passe de l’utilisateur", + "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Autoriser l’utilisation de la clé hôte DSA (obsolète) pour la configuration du service SSH", "hook_json_return_error": "Échec de la lecture au retour du script {path:s}. Erreur : {msg:s}. Contenu brut : {raw_content}", "migration_description_0007_ssh_conf_managed_by_yunohost_step1": "La configuration SSH sera gérée par YunoHost (étape 1, automatique)", "migration_description_0008_ssh_conf_managed_by_yunohost_step2": "La configuration SSH sera gérée par YunoHost (étape 2, manuelle)", - "migration_0007_cancelled": "Impossible d'améliorer la gestion de votre configuration SSH.", - "migration_0007_cannot_restart": "SSH ne peut pas être redémarré après avoir essayé d'annuler la migration numéro 6.", + "migration_0007_cancelled": "Impossible d’améliorer la gestion de votre configuration SSH.", + "migration_0007_cannot_restart": "SSH ne peut pas être redémarré après avoir essayé d’annuler la migration numéro 6.", "migration_0008_general_disclaimer": "Pour améliorer la sécurité de votre serveur, il est recommandé de laisser YunoHost gérer la configuration SSH. Votre configuration SSH actuelle diffère de la configuration recommandée. Si vous laissez YunoHost la reconfigurer, la façon dont vous vous connectez à votre serveur via SSH changera comme suit :", - "migration_0008_port": "- Vous devrez vous connecter en utilisant le port 22 au lieu de votre actuel port SSH personnalisé. N'hésitez pas à le reconfigurer ;", - "migration_0008_root": "- Vous ne pourrez pas vous connecter en tant que root via SSH. Au lieu de cela, vous devrez utiliser l'utilisateur admin ;", - "migration_0008_dsa": "- La clé DSA sera désactivée. Par conséquent, il se peut que vous ayez besoin d'invalider un avertissement effrayant de votre client SSH afin de revérifier l'empreinte de votre serveur ;", + "migration_0008_port": "- Vous devrez vous connecter en utilisant le port 22 au lieu de votre actuel port SSH personnalisé. N’hésitez pas à le reconfigurer ;", + "migration_0008_root": "- Vous ne pourrez pas vous connecter en tant que root via SSH. Au lieu de cela, vous devrez utiliser l’utilisateur admin ;", + "migration_0008_dsa": "- La clé DSA sera désactivée. Par conséquent, il se peut que vous ayez besoin d’invalider un avertissement effrayant de votre client SSH afin de revérifier l’empreinte de votre serveur ;", "migration_0008_warning": "Si vous comprenez ces avertissements et souhaitez que YunoHost écrase votre configuration actuelle, exécutez la migration. Sinon, vous pouvez également ignorer la migration, bien que cela ne soit pas recommandé.", - "migration_0008_no_warning": "Remplacer votre configuration SSH devrait être sûr, bien que cela ne puisse être promis! Exécutez la migration pour la remplacer. Sinon, vous pouvez également ignorer la migration, bien que cela ne soit pas recommandé.", + "migration_0008_no_warning": "Remplacer votre configuration SSH devrait être sûr, bien que cela ne puisse être promis ! Exécutez la migration pour la remplacer. Sinon, vous pouvez également ignorer la migration, bien que cela ne soit pas recommandé.", "migrations_success": "Migration {number} {name} réussie !", "pattern_password_app": "Désolé, les mots de passe ne peuvent pas contenir les caractères suivants : {forbidden_chars}", "root_password_replaced_by_admin_password": "Votre mot de passe root a été remplacé par votre mot de passe administrateur.", @@ -391,10 +391,10 @@ "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`.", - "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).", + "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 '{}'", - "migration_0009_not_needed": "Cette migration semble avoir déjà été jouée ? On l'ignore.", + "migration_0009_not_needed": "Cette migration semble avoir déjà été jouée ? On l’ignore.", "regenconf_file_backed_up": "Le fichier de configuration '{conf}' a été sauvegardé sous '{backup}'", "regenconf_file_copy_failed": "Impossible de copier le nouveau fichier de configuration '{new}' vers '{conf}'", "regenconf_file_manually_modified": "Le fichier de configuration '{conf}' a été modifié manuellement et ne sera pas mis à jour", @@ -404,12 +404,12 @@ "regenconf_file_updated": "Le fichier de configuration '{conf}' a été mis à jour", "regenconf_now_managed_by_yunohost": "Le fichier de configuration '{conf}' est maintenant géré par YunoHost (catégorie {category}).", "regenconf_up_to_date": "La configuration est déjà à jour pour la catégorie '{category}'", - "already_up_to_date": "Il n'y a rien à faire ! Tout est déjà à jour !", - "global_settings_setting_security_nginx_compatibility": "Compatibilité versus compromis sécuritaire pour le serveur web nginx. Affecte les cryptogrammes (et d'autres aspects liés à la sécurité)", - "global_settings_setting_security_ssh_compatibility": "Compatibilité versus compromis sécuritaire pour le serveur SSH. Affecte les cryptogrammes (et d'autres aspects liés à la sécurité)", - "global_settings_setting_security_postfix_compatibility": "Compatibilité versus compromis sécuritaire pour le serveur Postfix. Affecte les cryptogrammes (et d'autres aspects liés à la sécurité)", + "already_up_to_date": "Il n’y a rien à faire ! Tout est déjà à jour !", + "global_settings_setting_security_nginx_compatibility": "Compatibilité versus compromis sécuritaire pour le serveur web nginx. Affecte les cryptogrammes (et d’autres aspects liés à la sécurité)", + "global_settings_setting_security_ssh_compatibility": "Compatibilité versus compromis sécuritaire pour le serveur SSH. Affecte les cryptogrammes (et d’autres aspects liés à la sécurité)", + "global_settings_setting_security_postfix_compatibility": "Compatibilité versus compromis sécuritaire pour le serveur Postfix. Affecte les cryptogrammes (et d’autres aspects liés à la sécurité)", "migration_description_0009_decouple_regenconf_from_services": "Dissocier le mécanisme « regen-conf » des services", - "migration_description_0010_migrate_to_apps_json": "Supprimer les catalogues d'applications obsolètes afin d'utiliser la nouvelle liste unifiée 'apps.json' à la place (les anciens catalogues seront remplacés durant la migration 13)", + "migration_description_0010_migrate_to_apps_json": "Supprimer les catalogues d’applications obsolètes afin d’utiliser la nouvelle liste unifiée 'apps.json' à la place (les anciens catalogues seront remplacés durant la migration 13)", "regenconf_file_kept_back": "Le fichier de configuration '{conf}' devait être supprimé par « regen-conf » (catégorie {category}) mais a été conservé.", "regenconf_updated": "La configuration a été mise à jour pour '{category}'", "regenconf_would_be_updated": "La configuration aurait dû être mise à jour pour la catégorie '{category}'", @@ -423,13 +423,13 @@ "tools_upgrade_regular_packages": "Mise à jour des paquets du système (non liés a YunoHost) …", "tools_upgrade_regular_packages_failed": "Impossible de mettre à jour les paquets suivants : {packages_list}", "tools_upgrade_special_packages": "Mise à jour des paquets 'spécifiques' (liés a YunoHost) …", - "tools_upgrade_special_packages_completed": "La mise à jour des paquets de YunoHost est finie!\nPressez [Entrée] pour revenir à la ligne de commande", + "tools_upgrade_special_packages_completed": "La mise à jour des paquets de YunoHost est finie !\nPressez [Entrée] pour revenir à la ligne de commande", "dpkg_lock_not_available": "Cette commande ne peut être exécutée actuellement car un autre programme semble utiliser le verrou de dpkg (gestionnaire de paquets)", "tools_upgrade_cant_unhold_critical_packages": "Impossible de conserver les paquets critiques…", "tools_upgrade_special_packages_explanation": "La mise à jour spéciale va continuer en arrière-plan. Veuillez ne pas lancer d’autres actions sur votre serveur pendant environ 10 minutes (en fonction de la vitesse du matériel). Après cela, il vous faudra peut-être vous reconnecter à la webadmin. Le journal de mise à niveau sera disponible dans Outils → Journal (dans la webadmin) ou via \"yunohost log list\" (en ligne de commande).", "update_apt_cache_failed": "Impossible de mettre à jour le cache APT (gestionnaire de paquets Debian). Voici un extrait du fichier sources.list qui pourrait vous aider à identifier les lignes problématiques :\n{sourceslist}", "update_apt_cache_warning": "Des erreurs se sont produites lors de la mise à jour du cache APT (gestionnaire de paquets Debian). Voici un extrait des lignes du fichier sources.list qui pourrait vous aider à identifier les lignes problématiques :\n{sourceslist}", - "backup_permission": "Permission de sauvegarde pour l'application {app:s}", + "backup_permission": "Permission de sauvegarde pour l’application {app:s}", "group_created": "Le groupe '{group}' a été créé", "group_deleted": "Suppression du groupe '{group}'", "group_unknown": "Le groupe {group:s} est inconnu", @@ -439,121 +439,121 @@ "group_deletion_failed": "Échec de la suppression du groupe '{group}': {error}", "log_user_group_delete": "Supprimer le groupe '{}'", "log_user_group_update": "Mettre à jour '{}' pour le groupe", - "mailbox_disabled": "La boîte aux lettres est désactivée pour l'utilisateur {user:s}", + "mailbox_disabled": "La boîte aux lettres est désactivée pour l’utilisateur {user:s}", "app_action_broke_system": "Cette action semble avoir cassé des services importants : {services}", "apps_already_up_to_date": "Toutes les applications sont déjà à jour", - "migration_0011_create_group": "Création d'un groupe pour chaque utilisateur…", - "migration_0011_done": "Migration terminée. Vous êtes maintenant en mesure de gérer des groupes d'utilisateurs.", + "migration_0011_create_group": "Création d’un groupe pour chaque utilisateur…", + "migration_0011_done": "Migration terminée. Vous êtes maintenant en mesure de gérer des groupes d’utilisateurs.", "migrations_must_provide_explicit_targets": "Vous devez fournir des cibles explicites lorsque vous utilisez '--skip' ou '--force-rerun'", - "migrations_no_such_migration": "Il n'y a pas de migration appelée '{id}'", - "migrations_pending_cant_rerun": "Ces migrations étant toujours en attente, vous ne pouvez pas les exécuter à nouveau: {ids}", - "migration_description_0012_postgresql_password_to_md5_authentication": "Forcer l'authentification PostgreSQL à utiliser MD5 pour les connexions locales", + "migrations_no_such_migration": "Il n’y a pas de migration appelée '{id}'", + "migrations_pending_cant_rerun": "Ces migrations étant toujours en attente, vous ne pouvez pas les exécuter à nouveau : {ids}", + "migration_description_0012_postgresql_password_to_md5_authentication": "Forcer l’authentification PostgreSQL à utiliser MD5 pour les connexions locales", "migrations_exclusive_options": "'auto', '--skip' et '--force-rerun' sont des options mutuellement exclusives.", "migrations_not_pending_cant_skip": "Ces migrations ne sont pas en attente et ne peuvent donc pas être ignorées: {ids}", - "migration_0011_can_not_backup_before_migration": "La sauvegarde du système n'a pas pu être terminée avant l'échec de la migration. Erreur: {error:s}", - "migration_0011_migrate_permission": "Migration des autorisations des paramètres des applications vers LDAP…", - "migration_0011_migration_failed_trying_to_rollback": "La migration a échouée… Tentative de restauration du système.", + "migration_0011_can_not_backup_before_migration": "La sauvegarde du système n’a pas pu être terminée avant l’échec de la migration. Erreur: {error:s}", + "migration_0011_migrate_permission": "Migration des autorisations des paramètres des applications vers LDAP …", + "migration_0011_migration_failed_trying_to_rollback": "La migration a échoué… Tentative de restauration du système.", "migration_0011_rollback_success": "Système restauré.", "migration_0011_update_LDAP_database": "Mise à jour de la base de données LDAP…", - "migration_0011_backup_before_migration": "Création d'une sauvegarde des paramètres de la base de données LDAP et des applications avant la migration.", + "migration_0011_backup_before_migration": "Création d’une sauvegarde des paramètres de la base de données LDAP et des applications avant la migration.", "permission_not_found": "Autorisation '{permission:s}' introuvable", - "permission_update_failed": "Impossible de mettre à jour la permission '{permission}': {error}", + "permission_update_failed": "Impossible de mettre à jour la permission '{permission}' : {error}", "permission_updated": "Permission '{permission:s}' mise à jour", "permission_update_nothing_to_do": "Aucune autorisation pour mettre à jour", "dyndns_provider_unreachable": "Impossible d’atteindre le fournisseur DynDNS {provider}: votre YunoHost n’est pas correctement connecté à Internet ou le serveur Dynette est en panne.", - "migration_0011_update_LDAP_schema": "Mise à jour du schéma LDAP…", - "migrations_already_ran": "Ces migrations sont déjà effectuées: {ids}", - "migrations_dependencies_not_satisfied": "Exécutez ces migrations: '{dependencies_id}', avant migration {id}.", + "migration_0011_update_LDAP_schema": "Mise à jour du schéma LDAP …", + "migrations_already_ran": "Ces migrations sont déjà effectuées : {ids}", + "migrations_dependencies_not_satisfied": "Exécutez ces migrations : '{dependencies_id}', avant migration {id}.", "migrations_failed_to_load_migration": "Impossible de charger la migration {id}: {error}", - "migrations_running_forward": "Exécution de la migration {id}…", + "migrations_running_forward": "Exécution de la migration {id} …", "migrations_success_forward": "Migration {id} terminée", - "operation_interrupted": "L'opération a été interrompue manuellement ?", - "permission_already_exist": "L'autorisation '{permission}' existe déjà", + "operation_interrupted": "L’opération a été interrompue manuellement ?", + "permission_already_exist": "L’autorisation '{permission}' existe déjà", "permission_created": "Permission '{permission:s}' créée", - "permission_creation_failed": "Impossible de créer l'autorisation '{permission}': {error}", + "permission_creation_failed": "Impossible de créer l’autorisation '{permission}' : {error}", "permission_deleted": "Permission '{permission:s}' supprimée", - "permission_deletion_failed": "Impossible de supprimer la permission '{permission}': {error}", - "migration_description_0011_setup_group_permission": "Initialiser les groupes d'utilisateurs et autorisations pour les applications et les services", + "permission_deletion_failed": "Impossible de supprimer la permission '{permission}' : {error}", + "migration_description_0011_setup_group_permission": "Initialiser les groupes d’utilisateurs et autorisations pour les applications et les services", "migration_0011_LDAP_update_failed": "Impossible de mettre à jour LDAP. Erreur: {error:s}", "group_already_exist": "Le groupe {group} existe déjà", "group_already_exist_on_system": "Le groupe {group} existe déjà dans les groupes système", "group_cannot_be_deleted": "Le groupe {group} ne peut pas être supprimé manuellement.", - "group_user_already_in_group": "L'utilisateur {user} est déjà dans le groupe {group}", - "group_user_not_in_group": "L'utilisateur {user} n'est pas dans le groupe {group}", - "log_permission_create": "Créer permission '{}'", - "log_permission_delete": "supprimer permission '{}'", + "group_user_already_in_group": "L’utilisateur {user} est déjà dans le groupe {group}", + "group_user_not_in_group": "L’utilisateur {user} n’est pas dans le groupe {group}", + "log_permission_create": "Créer permission '{}'", + "log_permission_delete": "Supprimer permission '{}'", "log_user_group_create": "Créer '{}' groupe", "log_user_permission_update": "Mise à jour des accès pour la permission '{}'", "log_user_permission_reset": "Réinitialiser la permission '{}'", "migration_0011_failed_to_remove_stale_object": "Impossible de supprimer un objet périmé {dn}: {error}", - "permission_already_allowed": "Le groupe '{group}' a déjà l'autorisation '{permission}' activée", - "permission_already_disallowed": "Le groupe '{group}' a déjà l'autorisation '{permission}' désactivé '", - "permission_cannot_remove_main": "Supprimer une autorisation principale n'est pas autorisé", - "user_already_exists": "L'utilisateur '{user}' existe déjà", - "app_full_domain_unavailable": "Désolé, cette application doit être installée sur un domaine qui lui est propre, mais d'autres applications sont déjà installées sur le domaine '{domain}'. Vous pouvez utiliser un sous-domaine dédié à cette application à la place.", - "group_cannot_edit_all_users": "Le groupe 'all_users' ne peut pas être édité manuellement. C'est un groupe spécial destiné à contenir tous les utilisateurs enregistrés dans YunoHost", - "group_cannot_edit_visitors": "Le groupe 'visiteurs' ne peut pas être édité manuellement. C'est un groupe spécial représentant les visiteurs anonymes", - "group_cannot_edit_primary_group": "Le groupe '{group}' ne peut pas être édité manuellement. C'est le groupe principal destiné à ne contenir qu'un utilisateur spécifique.", - "log_permission_url": "Mise à jour de l'URL associée à l'autorisation '{}'", + "permission_already_allowed": "Le groupe '{group}' a déjà l’autorisation '{permission}' activée", + "permission_already_disallowed": "Le groupe '{group}' a déjà l’autorisation '{permission}' désactivé '", + "permission_cannot_remove_main": "Supprimer une autorisation principale n’est pas autorisé", + "user_already_exists": "L’utilisateur '{user}' existe déjà", + "app_full_domain_unavailable": "Désolé, cette application doit être installée sur un domaine qui lui est propre, mais d’autres applications sont déjà installées sur le domaine '{domain}'. Vous pouvez utiliser un sous-domaine dédié à cette application à la place.", + "group_cannot_edit_all_users": "Le groupe 'all_users' ne peut pas être édité manuellement. C’est un groupe spécial destiné à contenir tous les utilisateurs enregistrés dans YunoHost", + "group_cannot_edit_visitors": "Le groupe 'visiteurs' ne peut pas être édité manuellement. C’est un groupe spécial représentant les visiteurs anonymes", + "group_cannot_edit_primary_group": "Le groupe '{group}' ne peut pas être édité manuellement. C’est le groupe principal destiné à ne contenir qu’un utilisateur spécifique.", + "log_permission_url": "Mise à jour de l’URL associée à l’autorisation '{}'", "migration_0011_slapd_config_will_be_overwritten": "Il semble que vous ayez modifié manuellement la configuration de slapd. Pour cette migration critique, YunoHost doit forcer la mise à jour de la configuration de slapd. Les fichiers originaux seront sauvegardés dans {conf_backup_folder}.", - "permission_already_up_to_date": "L'autorisation n'a pas été mise à jour car les demandes d'ajout/suppression correspondent déjà à l'état actuel.", - "permission_currently_allowed_for_all_users": "Cette autorisation est actuellement accordée à tous les utilisateurs en plus des autres groupes. Vous voudrez probablement soit supprimer l'autorisation 'all_users', soit supprimer les autres groupes auxquels il est actuellement autorisé.", - "app_install_failed": "Impossible d'installer {app}: {error}", - "app_install_script_failed": "Une erreur est survenue dans le script d'installation de l'application", - "permission_require_account": "Permission {permission} n'a de sens que pour les utilisateurs ayant un compte et ne peut donc pas être activé pour les visiteurs.", - "app_remove_after_failed_install": "Supprimer l'application après l'échec de l'installation…", - "diagnosis_display_tip_web": "Vous pouvez aller à la section Diagnostic (dans l'écran d'accueil) pour voir les problèmes rencontrés.", - "diagnosis_cant_run_because_of_dep": "Impossible d'exécuter le diagnostic pour {category} alors qu'il existe des problèmes importants liés à {dep}.", + "permission_already_up_to_date": "L’autorisation n’a pas été mise à jour car les demandes d’ajout/suppression correspondent déjà à l’état actuel.", + "permission_currently_allowed_for_all_users": "Cette autorisation est actuellement accordée à tous les utilisateurs en plus des autres groupes. Vous voudrez probablement soit supprimer l’autorisation 'all_users', soit supprimer les autres groupes auxquels il est actuellement autorisé.", + "app_install_failed": "Impossible d’installer {app}: {error}", + "app_install_script_failed": "Une erreur est survenue dans le script d’installation de l’application", + "permission_require_account": "Permission {permission} n’a de sens que pour les utilisateurs ayant un compte et ne peut donc pas être activé pour les visiteurs.", + "app_remove_after_failed_install": "Supprimer l’application après l’échec de l’installation …", + "diagnosis_display_tip_web": "Vous pouvez aller à la section Diagnostic (dans l’écran d’accueil) pour voir les problèmes rencontrés.", + "diagnosis_cant_run_because_of_dep": "Impossible d’exécuter le diagnostic pour {category} alors qu’il existe des problèmes importants liés à {dep}.", "diagnosis_found_errors": "Trouvé {errors} problème(s) significatif(s) lié(s) à {category} !", "diagnosis_found_errors_and_warnings": "Trouvé {errors} problème(s) significatif(s) (et {warnings} (avertissement(s)) en relation avec {category} !", "diagnosis_ip_not_connected_at_all": "Le serveur ne semble pas du tout connecté à Internet !?", "diagnosis_ip_weird_resolvconf": "La résolution DNS semble fonctionner, mais soyez prudent en utilisant un fichier /etc/resolv.conf personnalisé.", "diagnosis_ip_weird_resolvconf_details": "Au lieu de cela, ce fichier devrait être un lien symbolique vers /etc/resolvconf/run/resolv.conf lui-même pointant vers 127.0.0.1 (dnsmasq). Les résolveurs réels doivent être configurés dans /etc/resolv.dnsmasq.conf.", - "diagnosis_dns_missing_record": "Selon la configuration DNS recommandée, vous devez ajouter un enregistrement DNS de type {0}, nom {1} et valeur {2}. Vous pouvez consulter https://yunohost.org/dns_config pour plus d'informations.", - "diagnosis_diskusage_ok": "Le stockage {mountpoint} (sur le périphérique {device}) a encore {free_abs_GB} Go ({free_percent}%) d'espace libre !", + "diagnosis_dns_missing_record": "Selon la configuration DNS recommandée, vous devez ajouter un enregistrement DNS de type {0}, nom {1} et valeur {2}. Vous pouvez consulter https://yunohost.org/dns_config pour plus d’informations.", + "diagnosis_diskusage_ok": "Le stockage {mountpoint} (sur le périphérique {device}) a encore {free_abs_GB} Go ({free_percent}%) d’espace libre !", "diagnosis_ram_ok": "Le système dispose encore de {available_abs_MB} MB ({available_percent}%) de RAM sur {total_abs_MB} MB.", "diagnosis_regenconf_allgood": "Tous les fichiers de configuration sont conformes à la configuration recommandée !", "diagnosis_security_vulnerable_to_meltdown": "Vous semblez vulnérable à la vulnérabilité de sécurité critique de Meltdown", "diagnosis_basesystem_host": "Le serveur utilise Debian {debian_version}", "diagnosis_basesystem_kernel": "Le serveur utilise le noyau Linux {kernel_version}", - "diagnosis_basesystem_ynh_single_version": "{0} version: {1} ({2})", + "diagnosis_basesystem_ynh_single_version": "{0} version : {1} ({2})", "diagnosis_basesystem_ynh_main_version": "Le serveur utilise YunoHost {main_version} ({repo})", - "diagnosis_basesystem_ynh_inconsistent_versions": "Vous exécutez des versions incohérentes des packages YunoHost ... probablement à cause d'une mise à niveau partielle ou échouée.", + "diagnosis_basesystem_ynh_inconsistent_versions": "Vous exécutez des versions incohérentes des packages YunoHost … probablement à cause d’une mise à niveau partielle ou échouée.", "diagnosis_display_tip_cli": "Vous pouvez exécuter 'yunohost diagnosis show --issues' pour afficher les problèmes détectés.", - "diagnosis_failed_for_category": "Échec du diagnostic pour la catégorie '{category}': {error}", - "diagnosis_cache_still_valid": "(Le cache est toujours valide pour le diagnostic {category}. Pas re-diagnostiquer pour le moment!)", + "diagnosis_failed_for_category": "Échec du diagnostic pour la catégorie '{category}' : {error}", + "diagnosis_cache_still_valid": "(Le cache est toujours valide pour le diagnostic {category}. Pas re-diagnostiquer pour le moment !)", "diagnosis_ignored_issues": "(+ {nb_ignored} questions ignorée(s))", "diagnosis_found_warnings": "Trouvé {warnings} objet(s) pouvant être amélioré(s) pour {category}.", "diagnosis_everything_ok": "Tout semble bien pour {category} !", - "diagnosis_failed": "Impossible d'extraire le résultat du diagnostic pour la catégorie '{category}': {error}", + "diagnosis_failed": "Impossible d’extraire le résultat du diagnostic pour la catégorie '{category}': {error}", "diagnosis_ip_connected_ipv4": "Le serveur est connecté à Internet en IPv4 !", "diagnosis_ip_no_ipv4": "Le serveur ne dispose pas d’une adresse IPv4.", "diagnosis_ip_connected_ipv6": "Le serveur est connecté à Internet en IPv6 !", - "diagnosis_ip_no_ipv6": "Le serveur ne dispose pas d'une adresse IPv6.", + "diagnosis_ip_no_ipv6": "Le serveur ne dispose pas d’une adresse IPv6.", "diagnosis_ip_dnsresolution_working": "La résolution de nom de domaine fonctionne !", - "diagnosis_ip_broken_dnsresolution": "La résolution du nom de domaine semble interrompue pour une raison quelconque... Un pare-feu bloque-t-il les requêtes DNS ?", + "diagnosis_ip_broken_dnsresolution": "La résolution du nom de domaine semble interrompue pour une raison quelconque … Un pare-feu bloque-t-il les requêtes DNS ?", "diagnosis_ip_broken_resolvconf": "La résolution du nom de domaine semble cassée sur votre serveur, ce qui semble lié au fait que /etc/resolv.conf ne pointe pas vers 127.0.0.1.", "diagnosis_dns_good_conf": "Bonne configuration DNS pour le domaine {domain} (catégorie {category})", "diagnosis_dns_bad_conf": "Configuration DNS incorrecte ou manquante pour le domaine {domain} (catégorie {category})", - "diagnosis_dns_discrepancy": "L'enregistrement DNS de type {0} et nom {1} ne correspond pas à la configuration recommandée. Valeur actuelle: {2}. Valeur exceptée: {3}. Vous pouvez consulter https://yunohost.org/dns_config pour plus d'informations.", + "diagnosis_dns_discrepancy": "L’enregistrement DNS de type {0} et nom {1} ne correspond pas à la configuration recommandée. Valeur actuelle: {2}. Valeur exceptée: {3}. Vous pouvez consulter https://yunohost.org/dns_config pour plus d’informations.", "diagnosis_services_bad_status": "Le service {service} est {status} :-(", - "diagnosis_diskusage_verylow": "Le stockage {mountpoint} (sur le périphérique {device}) ne dispose que de {free_abs_GB} Go ({free_percent}%). Vous devriez vraiment envisager de nettoyer un peu d'espace.", + "diagnosis_diskusage_verylow": "Le stockage {mountpoint} (sur le périphérique {device}) ne dispose que de {free_abs_GB} Go ({free_percent}%). Vous devriez vraiment envisager de nettoyer un peu d’espace.", "diagnosis_diskusage_low": "Le stockage {mountpoint} (sur le périphérique {device}) ne dispose que de {free_abs_GB} Go ({free_percent}%). Faites attention.", - "diagnosis_ram_verylow": "Le système ne dispose plus que de {available_abs_MB} MB ({available_percent}%)! (sur {total_abs_MB} Mo)", - "diagnosis_ram_low": "Le système n'a plus de {available_abs_MB} MB ({available_percent}%) RAM sur {total_abs_MB} MB. Faites attention.", - "diagnosis_swap_none": "Le système n'a aucun échange. Vous devez envisager d’ajouter au moins 256 Mo de swap pour éviter les situations où le système manque de mémoire.", - "diagnosis_swap_notsomuch": "Le système ne dispose que de {total_MB} Mo de swap. Vous devez envisager d'avoir au moins 256 Mo pour éviter les situations où le système manque de mémoire.", + "diagnosis_ram_verylow": "Le système ne dispose plus que de {available_abs_MB} MB ({available_percent}%) ! (sur {total_abs_MB} Mo)", + "diagnosis_ram_low": "Le système n’a plus de {available_abs_MB} MB ({available_percent}%) RAM sur {total_abs_MB} MB. Faites attention.", + "diagnosis_swap_none": "Le système n’a aucun échange. Vous devez envisager d’ajouter au moins 256 Mo de swap pour éviter les situations où le système manque de mémoire.", + "diagnosis_swap_notsomuch": "Le système ne dispose que de {total_MB} Mo de swap. Vous devez envisager d’avoir au moins 256 Mo pour éviter les situations où le système manque de mémoire.", "diagnosis_swap_ok": "Le système dispose de {total_MB} Mo de swap !", "diagnosis_regenconf_manually_modified": "Le fichier de configuration {file} a été modifié manuellement.", "diagnosis_regenconf_manually_modified_debian": "Le fichier de configuration {file} a été modifié manuellement par rapport à celui par défaut de Debian.", - "diagnosis_regenconf_manually_modified_details": "C'est probablement OK tant que vous savez ce que vous faites;) !", - "diagnosis_regenconf_manually_modified_debian_details": "Cela peut probablement être OK, mais il faut garder un œil dessus ...", - "diagnosis_security_all_good": "Aucune vulnérabilité de sécurité critique n'a été trouvée.", - "apps_catalog_init_success": "Système de catalogue d'applications initialisé !", + "diagnosis_regenconf_manually_modified_details": "C’est probablement OK tant que vous savez ce que vous faites ;) !", + "diagnosis_regenconf_manually_modified_debian_details": "Cela peut probablement être OK, mais il faut garder un œil dessus …", + "diagnosis_security_all_good": "Aucune vulnérabilité de sécurité critique n’a été trouvée.", + "apps_catalog_init_success": "Système de catalogue d’applications initialisé !", "apps_catalog_failed_to_download": "Impossible de télécharger le catalogue des applications {apps_catalog}:{error}", - "diagnosis_mail_ougoing_port_25_blocked": "Le port sortant 25 semble être bloqué. Vous devriez essayer de le débloquer dans le panneau de configuration de votre fournisseur de services Internet (ou hébergeur). En attendant, le serveur ne pourra pas envoyer de courrier électronique à d'autres serveurs.", - "domain_cannot_remove_main_add_new_one": "Vous ne pouvez pas supprimer '{domain:s}' car il s'agit du domaine principal et de votre seul domaine. Vous devez d'abord ajouter un autre domaine à l'aide de 'yunohost domain add ', puis définir comme domaine principal à l'aide de ' yunohost domain main-domain -n ' et vous pouvez ensuite supprimer le domaine '{domain:s}' à l'aide de 'yunohost domain remove {domain:s}'.'", - "diagnosis_security_vulnerable_to_meltdown_details": "Pour résoudre ce problème, vous devez mettre à niveau votre système et redémarrer pour charger le nouveau noyau Linux (ou contacter votre fournisseur de serveur si cela ne fonctionne pas). Voir https://meltdownattack.com/ pour plus d'informations.", + "diagnosis_mail_ougoing_port_25_blocked": "Le port sortant 25 semble être bloqué. Vous devriez essayer de le débloquer dans le panneau de configuration de votre fournisseur de services Internet (ou hébergeur). En attendant, le serveur ne pourra pas envoyer de courrier électronique à d’autres serveurs.", + "domain_cannot_remove_main_add_new_one": "Vous ne pouvez pas supprimer '{domain:s}' car il s’agit du domaine principal et de votre seul domaine. Vous devez d’abord ajouter un autre domaine à l’aide de 'yunohost domain add ', puis définir comme domaine principal à l’aide de 'yunohost domain main-domain -n ' et vous pouvez ensuite supprimer le domaine '{domain:s}' à l’aide de 'yunohost domain remove {domain:s}'.'", + "diagnosis_security_vulnerable_to_meltdown_details": "Pour résoudre ce problème, vous devez mettre à niveau votre système et redémarrer pour charger le nouveau noyau Linux (ou contacter votre fournisseur de serveur si cela ne fonctionne pas). Voir https://meltdownattack.com/ pour plus d’informations.", "diagnosis_description_basesystem": "Système de base", "diagnosis_description_ip": "Connectivité Internet", "diagnosis_description_dnsrecords": "Enregistrements DNS", @@ -562,41 +562,41 @@ "diagnosis_description_ports": "Exposition des ports", "diagnosis_description_regenconf": "Configurations système", "diagnosis_description_security": "Contrôles de sécurité", - "diagnosis_ports_could_not_diagnose": "Impossible de diagnostiquer si les ports sont accessibles de l'extérieur. Erreur: {error}", - "apps_catalog_updating": "Mise à jour du catalogue d'applications...", - "apps_catalog_obsolete_cache": "Le cache du catalogue d'applications est vide ou obsolète.", + "diagnosis_ports_could_not_diagnose": "Impossible de diagnostiquer si les ports sont accessibles de l’extérieur. Erreur : {error}", + "apps_catalog_updating": "Mise à jour du catalogue d’applications …", + "apps_catalog_obsolete_cache": "Le cache du catalogue d’applications est vide ou obsolète.", "apps_catalog_update_success": "Le catalogue des applications a été mis à jour !", - "diagnosis_mail_ougoing_port_25_ok": "Le port sortant 25 n'est pas bloqué et le courrier électronique peut être envoyé à d'autres serveurs.", + "diagnosis_mail_ougoing_port_25_ok": "Le port sortant 25 n’est pas bloqué et le courrier électronique peut être envoyé à d’autres serveurs.", "diagnosis_description_mail": "Email", - "diagnosis_ports_unreachable": "Le port {port} n'est pas accessible de l'extérieur.", - "diagnosis_ports_ok": "Le port {port} est accessible de l'extérieur.", - "diagnosis_http_could_not_diagnose": "Impossible de diagnostiquer si le domaine est accessible de l'extérieur. Erreur: {error}", - "diagnosis_http_ok": "Le domaine {domain} est accessible au travers de HTTP depuis l'extérieur.", - "diagnosis_http_unreachable": "Le domaine {domain} est inaccessible au travers de HTTP depuis l'extérieur.", - "diagnosis_unknown_categories": "Les catégories suivantes sont inconnues: {categories}", - "migration_description_0013_futureproof_apps_catalog_system": "Migrer vers le nouveau système de catalogue d'applications à l'épreuve du temps", - "app_upgrade_script_failed": "Une erreur s'est produite durant l’exécution du script de mise à niveau de l'application", - "migration_description_0014_remove_app_status_json": "Supprimer les fichiers d'application status.json hérités", - "diagnosis_services_running": "Le service {service} s'exécute correctement !", + "diagnosis_ports_unreachable": "Le port {port} n’est pas accessible de l’extérieur.", + "diagnosis_ports_ok": "Le port {port} est accessible de l’extérieur.", + "diagnosis_http_could_not_diagnose": "Impossible de diagnostiquer si le domaine est accessible de l’extérieur. Erreur : {error}", + "diagnosis_http_ok": "Le domaine {domain} est accessible au travers de HTTP depuis l’extérieur.", + "diagnosis_http_unreachable": "Le domaine {domain} est inaccessible au travers de HTTP depuis l’extérieur.", + "diagnosis_unknown_categories": "Les catégories suivantes sont inconnues : {categories}", + "migration_description_0013_futureproof_apps_catalog_system": "Migrer vers le nouveau système de catalogue d’applications à l’épreuve du temps", + "app_upgrade_script_failed": "Une erreur s’est produite durant l’exécution du script de mise à niveau de l’application", + "migration_description_0014_remove_app_status_json": "Supprimer les fichiers d’application status.json hérités", + "diagnosis_services_running": "Le service {service} s’exécute correctement !", "diagnosis_services_conf_broken": "La configuration est cassée pour le service {service} !", "diagnosis_ports_needed_by": "Rendre ce port accessible est nécessaire pour les fonctionnalités de type {1} (service {0})", "diagnosis_ports_forwarding_tip": "Pour résoudre ce problème, vous devez probablement configurer la redirection de port sur votre routeur Internet comme décrit sur https://yunohost.org/isp_box_config", "diagnosis_http_connection_error": "Erreur de connexion : impossible de se connecter au domaine demandé, il est probablement injoignable.", "diagnosis_no_cache": "Pas encore de cache de diagnostique pour la catégorie « {category} »", "diagnosis_http_unknown_error": "Une erreur est survenue en essayant de joindre votre domaine, il est probablement injoignable.", - "yunohost_postinstall_end_tip": "La post-installation terminée! Pour finaliser votre configuration, il est recommendé de :\n - ajouter un premier utilisateur depuis la section \"Utilisateurs\" de l'interface web (ou \"yunohost user create \" en ligne de commande);\n - diagnostiquer les potentiels problèmes dans la section \"Diagnostic\" de l'interface web (ou \"yunohost diagnosis run\" en ligne de commande);\n - lire les parties \"Finalisation de votre configuration\" et \"Découverte de Yunohost\" dans le guide de l'administrateur: https://yunohost.org/admindoc.", - "diagnosis_services_bad_status_tip": "Vous pouvez essayer de redémarrer le service. Si cela ne fonctionne pas, consultez les journaux de service à l'aide de 'yunohost service log {0}' ou de la section 'Services' de l'administrateur Web.", - "diagnosis_http_bad_status_code": "Le système de diagnostique n'a pas réussi à contacter votre serveur. Il se peut qu'une autre machine réponde à la place de votre serveur. Vérifiez que le port 80 est correctement redirigé, que votre configuration nginx est à jour et qu’un reverse-proxy n’interfère pas.", - "diagnosis_http_timeout": "Expiration du délai en essayant de contacter votre serveur de l'extérieur. Il semble être inaccessible. Vérifiez que vous transférez correctement le port 80, que nginx est en cours d’exécution et qu’un pare-feu n’interfère pas.", + "yunohost_postinstall_end_tip": "La post-installation terminée ! Pour finaliser votre configuration, il est recommendé de :\n - ajouter un premier utilisateur depuis la section \"Utilisateurs\" de l’interface web (ou \"yunohost user create \" en ligne de commande);\n - diagnostiquer les potentiels problèmes dans la section \"Diagnostic\" de l’interface web (ou \"yunohost diagnosis run\" en ligne de commande);\n - lire les parties \"Finalisation de votre configuration\" et \"Découverte de YunoHost\" dans le guide de l’administrateur: https://yunohost.org/admindoc.", + "diagnosis_services_bad_status_tip": "Vous pouvez essayer de redémarrer le service. Si cela ne fonctionne pas, consultez les journaux de service à l’aide de 'yunohost service log {0}' ou de la section 'Services' de l’administrateur Web.", + "diagnosis_http_bad_status_code": "Le système de diagnostique n’a pas réussi à contacter votre serveur. Il se peut qu’une autre machine réponde à la place de votre serveur. Vérifiez que le port 80 est correctement redirigé, que votre configuration nginx est à jour et qu’un reverse-proxy n’interfère pas.", + "diagnosis_http_timeout": "Expiration du délai en essayant de contacter votre serveur de l’extérieur. Il semble être inaccessible. Vérifiez que vous transférez correctement le port 80, que nginx est en cours d’exécution et qu’un pare-feu n’interfère pas.", "global_settings_setting_pop3_enabled": "Activer le protocole POP3 pour le serveur de messagerie", "log_app_action_run": "Lancer l’action de l’application '{}'", "log_app_config_show_panel": "Montrer le panneau de configuration de l’application '{}'", "log_app_config_apply": "Appliquer la configuration à l’application '{}'", - "diagnosis_never_ran_yet": "Il apparaît que le serveur a été installé récemment et qu'il n'y a pas encore eu de diagnostic. Vous devriez en lancer un depuis le webmin ou en utilisant 'yunohost diagnosis run' depuis la ligne de commande.", + "diagnosis_never_ran_yet": "Il apparaît que le serveur a été installé récemment et qu’il n’y a pas encore eu de diagnostic. Vous devriez en lancer un depuis le webmin ou en utilisant 'yunohost diagnosis run' depuis la ligne de commande.", "diagnosis_description_web": "Web", "diagnosis_basesystem_hardware_board": "Le modèle de carte du serveur est {model}", - "diagnosis_basesystem_hardware": "L'architecture du serveur est {virt} {arch}", - "group_already_exist_on_system_but_removing_it": "Le groupe {group} est déjà présent dans les groupes du système, mais Yuhonost va le supprimer…", + "diagnosis_basesystem_hardware": "L’architecture du serveur est {virt} {arch}", + "group_already_exist_on_system_but_removing_it": "Le groupe {group} est déjà présent dans les groupes du système, mais YunoHost va le supprimer…", "certmanager_warning_subdomain_dns_record": "Le sous-domaine '{subdomain:s}' ne résout pas vers la même adresse IP que '{domain:s}'. Certaines fonctionnalités seront indisponibles tant que vous n’aurez pas corrigé cela et regénéré le certificat.", - "domain_cannot_add_xmpp_upload": "Vous ne pouvez pas ajouter de domaine commençant par 'xmpp-upload.'. Ce type de nom est réservé à la fonctionnalité d’upload XMPP intégrée dans Yunohost." + "domain_cannot_add_xmpp_upload": "Vous ne pouvez pas ajouter de domaine commençant par 'xmpp-upload.'. Ce type de nom est réservé à la fonctionnalité d’upload XMPP intégrée dans YunoHost." } From 35785888608c370f8dc00c0b664f290df0e9bf6d Mon Sep 17 00:00:00 2001 From: Josue-T Date: Wed, 15 Apr 2020 11:53:39 +0200 Subject: [PATCH 0940/3170] 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 0941/3170] 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 0942/3170] 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 720e1daf8b4e31d5b79f4dbc0776951f05d29169 Mon Sep 17 00:00:00 2001 From: Kayou Date: Wed, 15 Apr 2020 15:21:10 +0200 Subject: [PATCH 0943/3170] Update en.json --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index f6aa35f67..64cca8713 100644 --- a/locales/en.json +++ b/locales/en.json @@ -462,7 +462,7 @@ "pattern_username": "Must be lower-case alphanumeric and underscore characters only", "pattern_password_app": "Sorry, passwords can not contain the following characters: {forbidden_chars}", "permission_already_allowed": "Group '{group}' already has permission '{permission}' enabled", - "permission_already_disallowed": "Group '{group}' already has permission '{permission}' disabled'", + "permission_already_disallowed": "Group '{group}' already has permission '{permission}' disabled", "permission_already_exist": "Permission '{permission}' already exists", "permission_already_up_to_date": "The permission was not updated because the addition/removal requests already match the current state.", "permission_cannot_remove_main": "Removing a main permission is not allowed", From 369f945c779aa9d6990edc99cdaeed4c6c52f28d Mon Sep 17 00:00:00 2001 From: Weblate Admin Date: Wed, 15 Apr 2020 13:04:24 +0000 Subject: [PATCH 0944/3170] Translated using Weblate (French) Currently translated at 94.6% (566 of 598 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 770d59dde..738dc0807 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -563,7 +563,7 @@ "diagnosis_description_regenconf": "Configurations système", "diagnosis_description_security": "Contrôles de sécurité", "diagnosis_ports_could_not_diagnose": "Impossible de diagnostiquer si les ports sont accessibles de l'extérieur. Erreur: {error}", - "apps_catalog_updating": "Mise à jour du catalogue d'applications...", + "apps_catalog_updating": "Mise à jour du catalogue d'applications…", "apps_catalog_obsolete_cache": "Le cache du catalogue d'applications est vide ou obsolète.", "apps_catalog_update_success": "Le catalogue des applications a été mis à jour !", "diagnosis_mail_ougoing_port_25_ok": "Le port sortant 25 n'est pas bloqué et le courrier électronique peut être envoyé à d'autres serveurs.", From 78167da5ca5537e37b48ca22213fc687b3f37ee7 Mon Sep 17 00:00:00 2001 From: Kayou Date: Wed, 15 Apr 2020 13:20:08 +0000 Subject: [PATCH 0945/3170] Translated using Weblate (French) Currently translated at 95.0% (568 of 598 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 738dc0807..688f884ab 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -487,7 +487,7 @@ "log_user_permission_reset": "Réinitialiser la permission '{}'", "migration_0011_failed_to_remove_stale_object": "Impossible de supprimer un objet périmé {dn}: {error}", "permission_already_allowed": "Le groupe '{group}' a déjà l'autorisation '{permission}' activée", - "permission_already_disallowed": "Le groupe '{group}' a déjà l'autorisation '{permission}' désactivé '", + "permission_already_disallowed": "Le groupe '{group}' a déjà l'autorisation '{permission}' désactivé'", "permission_cannot_remove_main": "Supprimer une autorisation principale n'est pas autorisé", "user_already_exists": "L'utilisateur '{user}' existe déjà", "app_full_domain_unavailable": "Désolé, cette application doit être installée sur un domaine qui lui est propre, mais d'autres applications sont déjà installées sur le domaine '{domain}'. Vous pouvez utiliser un sous-domaine dédié à cette application à la place.", From 7cfd553c3f6b96e6862bc95f5eec93f6d058f613 Mon Sep 17 00:00:00 2001 From: Kayou Date: Wed, 15 Apr 2020 13:21:27 +0000 Subject: [PATCH 0946/3170] Translated using Weblate (French) Currently translated at 95.0% (568 of 598 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 688f884ab..9f7dd445b 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -487,7 +487,7 @@ "log_user_permission_reset": "Réinitialiser la permission '{}'", "migration_0011_failed_to_remove_stale_object": "Impossible de supprimer un objet périmé {dn}: {error}", "permission_already_allowed": "Le groupe '{group}' a déjà l'autorisation '{permission}' activée", - "permission_already_disallowed": "Le groupe '{group}' a déjà l'autorisation '{permission}' désactivé'", + "permission_already_disallowed": "Le groupe '{group}' a déjà l'autorisation '{permission}' désactivé", "permission_cannot_remove_main": "Supprimer une autorisation principale n'est pas autorisé", "user_already_exists": "L'utilisateur '{user}' existe déjà", "app_full_domain_unavailable": "Désolé, cette application doit être installée sur un domaine qui lui est propre, mais d'autres applications sont déjà installées sur le domaine '{domain}'. Vous pouvez utiliser un sous-domaine dédié à cette application à la place.", 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 0947/3170] 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 0948/3170] 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 0949/3170] 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 0950/3170] 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 0951/3170] 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 f57b302299f9ea4b90d09db248329ce374de6980 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Fri, 17 Apr 2020 00:40:00 +0200 Subject: [PATCH 0952/3170] Update data/helpers.d/php Co-Authored-By: Kayou --- data/helpers.d/php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/php b/data/helpers.d/php index 7a26824a4..3509bdc3d 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -292,7 +292,7 @@ ynh_remove_fpm_config () { fi ynh_secure_remove --file="$fpm_config_dir/pool.d/$app.conf" - ynh_secure_remove --file="$fpm_config_dir/conf.d/20-$app.ini" 2>&1 + ynh_exec_warn_less ynh_secure_remove --file="$fpm_config_dir/conf.d/20-$app.ini" # If the php version used is not the default version for YunoHost if [ "$phpversion" != "$YNH_DEFAULT_PHP_VERSION" ] From 05503d2f8ea5623831b809dd16a8ad6189ecdc21 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Fri, 17 Apr 2020 00:48:32 +0200 Subject: [PATCH 0953/3170] fpm_service name --- data/helpers.d/php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/php b/data/helpers.d/php index 3509bdc3d..4d3cdc480 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -102,7 +102,7 @@ ynh_add_fpm_config () { if [ $dedicated_service -eq 1 ] then - local fpm_service="php${phpversion}-fpm-$app" + local fpm_service="${app}-phpfpm" local fpm_config_dir="/etc/php/$phpversion/dedicated-fpm" else local fpm_service="php${phpversion}-fpm" From 8e83f8aa2904bae4a253b40d572b64ede5a326af Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 7 Apr 2020 00:16:18 +0200 Subject: [PATCH 0954/3170] Add a 'yunohost diagnosis get' to get one specific raw info --- data/actionsmap/yunohost.yml | 13 +++++- data/hooks/diagnosis/10-ip.py | 4 +- data/hooks/diagnosis/14-ports.py | 4 +- src/yunohost/diagnosis.py | 76 +++++++++++++++++++++++--------- 4 files changed, 70 insertions(+), 27 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 44419a342..48b1687d4 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1676,7 +1676,7 @@ diagnosis: action: store_true run: - action_help: Show most recents diagnosis results + action_help: Run diagnosis api: POST /diagnosis/run arguments: categories: @@ -1701,3 +1701,14 @@ diagnosis: --list: help: List active ignore filters action: store_true + + get: + action_help: Low-level command to fetch raw data and status about a specific diagnosis test + api: GET /diagnosis/item/ + arguments: + category: + help: Diagnosis category to fetch results from + item: + help: "List of criteria describing the test. Must correspond exactly to the 'meta' infos in 'yunohost diagnosis show'" + metavar: CRITERIA + nargs: "*" diff --git a/data/hooks/diagnosis/10-ip.py b/data/hooks/diagnosis/10-ip.py index 552092fe3..32232457e 100644 --- a/data/hooks/diagnosis/10-ip.py +++ b/data/hooks/diagnosis/10-ip.py @@ -72,13 +72,13 @@ class IPDiagnoser(Diagnoser): ipv4 = self.get_public_ip(4) if can_ping_ipv4 else None ipv6 = self.get_public_ip(6) if can_ping_ipv6 else None - yield dict(meta={"test": "ip", "version": 4}, + yield dict(meta={"test": "ip", "version": '4'}, data=ipv4, status="SUCCESS" if ipv4 else "ERROR", summary=("diagnosis_ip_connected_ipv4", {}) if ipv4 else ("diagnosis_ip_no_ipv4", {})) - yield dict(meta={"test": "ip", "version": 6}, + yield dict(meta={"test": "ip", "version": '6'}, data=ipv6, status="SUCCESS" if ipv6 else "WARNING", summary=("diagnosis_ip_connected_ipv6", {}) if ipv6 diff --git a/data/hooks/diagnosis/14-ports.py b/data/hooks/diagnosis/14-ports.py index 7730ddb57..712d0007b 100644 --- a/data/hooks/diagnosis/14-ports.py +++ b/data/hooks/diagnosis/14-ports.py @@ -46,12 +46,12 @@ class PortsDiagnoser(Diagnoser): for port, service in sorted(ports.items()): category = services[service].get("category", "[?]") if r["ports"].get(str(port), None) is not True: - yield dict(meta={"port": port, "needed_by": service}, + yield dict(meta={"port": str(port)}, status="ERROR", summary=("diagnosis_ports_unreachable", {"port": port}), details=[("diagnosis_ports_needed_by", (service, category)), ("diagnosis_ports_forwarding_tip", ())]) else: - yield dict(meta={"port": port, "needed_by": service}, + yield dict(meta={"port": str(port)}, status="SUCCESS", summary=("diagnosis_ports_ok", {"port": port}), details=[("diagnosis_ports_needed_by", (service, category))]) diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index db791fcdf..7f488b6aa 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -44,6 +44,25 @@ def diagnosis_list(): return {"categories": all_categories_names} +def diagnosis_get(category, item): + + # Get all the categories + all_categories = _list_diagnosis_categories() + all_categories_names = [c for c, _ in all_categories] + + if category not in all_categories_names: + raise YunohostError('diagnosis_unknown_categories', categories=category) + + if isinstance(item, list): + if any("=" not in criteria for criteria in item): + raise YunohostError("Criterias should be of the form key=value (e.g. domain=yolo.test)") + + # Convert the provided criteria into a nice dict + item = {c.split("=")[0]: c.split("=")[1] for c in item} + + return Diagnoser.get_cached_report(category, item=item) + + def diagnosis_show(categories=[], issues=False, full=False, share=False): # Get all the categories @@ -56,7 +75,7 @@ def diagnosis_show(categories=[], issues=False, full=False, share=False): else: unknown_categories = [c for c in categories if c not in all_categories_names] if unknown_categories: - raise YunohostError('diagnosis_unknown_categories', categories=", ".join(categories)) + raise YunohostError('diagnosis_unknown_categories', categories=", ".join(unknown_categories)) if not os.path.exists(DIAGNOSIS_CACHE): logger.warning(m18n.n("diagnosis_never_ran_yet")) @@ -65,19 +84,14 @@ def diagnosis_show(categories=[], issues=False, full=False, share=False): # Fetch all reports all_reports = [] for category in categories: - if not os.path.exists(Diagnoser.cache_file(category)): - logger.warning(m18n.n("diagnosis_no_cache", category=category)) - report = {"id": category, - "cached_for": -1, - "timestamp": -1, - "items": []} - Diagnoser.i18n(report) - else: - try: - report = Diagnoser.get_cached_report(category) - except Exception as e: - logger.error(m18n.n("diagnosis_failed", category=category, error=str(e))) - continue + + try: + report = Diagnoser.get_cached_report(category) + except Exception as e: + logger.error(m18n.n("diagnosis_failed", category=category, error=str(e))) + continue + + Diagnoser.i18n(report) add_ignore_flag_to_issues(report) if not full: @@ -221,7 +235,7 @@ def diagnosis_ignore(add_filter=None, remove_filter=None, list=False): if category not in all_categories_names: raise YunohostError("%s is not a diagnosis category" % category) if any("=" not in criteria for criteria in filter_[1:]): - raise YunohostError("Extra criterias should be of the form key=value (e.g. domain=yolo.test)") + raise YunohostError("Criterias should be of the form key=value (e.g. domain=yolo.test)") # Convert the provided criteria into a nice dict criterias = {c.split("=")[0]: c.split("=")[1] for c in filter_[1:]} @@ -356,7 +370,12 @@ class Diagnoser(): for dependency in self.dependencies: dep_report = Diagnoser.get_cached_report(dependency) - dep_errors = [item for item in dep_report["items"] if item["status"] == "ERROR"] + + if dep_report["timestamp"] == -1: # No cache yet for this dep + dep_errors = True + else: + dep_errors = [item for item in dep_report["items"] if item["status"] == "ERROR"] + if dep_errors: logger.error(m18n.n("diagnosis_cant_run_because_of_dep", category=self.description, dep=Diagnoser.get_description(dependency))) return 1, {} @@ -396,12 +415,25 @@ class Diagnoser(): return os.path.join(DIAGNOSIS_CACHE, "%s.json" % id_) @staticmethod - def get_cached_report(id_): - filename = Diagnoser.cache_file(id_) - report = read_json(filename) - report["timestamp"] = int(os.path.getmtime(filename)) - Diagnoser.i18n(report) - return report + def get_cached_report(id_, item=None): + cache_file = Diagnoser.cache_file(id_) + if not os.path.exists(cache_file): + logger.warning(m18n.n("diagnosis_no_cache", category=id_)) + report = {"id": category, + "cached_for": -1, + "timestamp": -1, + "items": []} + else: + report = read_json(cache_file) + report["timestamp"] = int(os.path.getmtime(cache_file)) + + if item: + for report_item in report["items"]: + if report_item.get("meta") == item: + return report_item + return {} + else: + return report @staticmethod def get_description(id_): From f0c0f63bb4da66e5e052dbe6efea433586fa8525 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 7 Apr 2020 02:21:33 +0200 Subject: [PATCH 0955/3170] Let's use dict for details data, much better for semantic when defining strings etc... --- data/hooks/diagnosis/00-basesystem.py | 10 +++++++--- data/hooks/diagnosis/10-ip.py | 2 +- data/hooks/diagnosis/12-dnsrecords.py | 14 ++++++++------ data/hooks/diagnosis/14-ports.py | 5 +++-- data/hooks/diagnosis/21-web.py | 2 +- data/hooks/diagnosis/30-services.py | 4 ++-- data/hooks/diagnosis/90-security.py | 2 +- locales/en.json | 11 ++++++----- src/yunohost/diagnosis.py | 2 +- 9 files changed, 30 insertions(+), 22 deletions(-) diff --git a/data/hooks/diagnosis/00-basesystem.py b/data/hooks/diagnosis/00-basesystem.py index bf7a27047..3c932b488 100644 --- a/data/hooks/diagnosis/00-basesystem.py +++ b/data/hooks/diagnosis/00-basesystem.py @@ -27,7 +27,7 @@ class BaseSystemDiagnoser(Diagnoser): if os.path.exists("/proc/device-tree/model"): model = read_file('/proc/device-tree/model').strip() hardware["data"]["board"] = model - hardware["details"] = [("diagnosis_basesystem_hardware_board", (model,))] + hardware["details"] = [("diagnosis_basesystem_hardware_board", {"model": model})] yield hardware @@ -51,8 +51,12 @@ class BaseSystemDiagnoser(Diagnoser): # Here, ynh_core_version is for example "3.5.4.12", so [:3] is "3.5" and we check it's the same for all packages ynh_core_version = ynh_packages["yunohost"]["version"] consistent_versions = all(infos["version"][:3] == ynh_core_version[:3] for infos in ynh_packages.values()) - ynh_version_details = [("diagnosis_basesystem_ynh_single_version", (package, infos["version"], infos["repo"])) - for package, infos in ynh_packages.items()] + ynh_version_details = [("diagnosis_basesystem_ynh_single_version", + {"package":package, + "version": infos["version"], + "repo": infos["repo"]} + ) + for package, infos in ynh_packages.items()] if consistent_versions: yield dict(meta={"test": "ynh_versions"}, diff --git a/data/hooks/diagnosis/10-ip.py b/data/hooks/diagnosis/10-ip.py index 32232457e..7e96a7b56 100644 --- a/data/hooks/diagnosis/10-ip.py +++ b/data/hooks/diagnosis/10-ip.py @@ -58,7 +58,7 @@ class IPDiagnoser(Diagnoser): yield dict(meta={"test": "dnsresolv"}, status="WARNING", summary=("diagnosis_ip_weird_resolvconf", {}), - details=[("diagnosis_ip_weird_resolvconf_details", ())]) + details=[("diagnosis_ip_weird_resolvconf_details", {})]) else: yield dict(meta={"test": "dnsresolv"}, status="SUCCESS", diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py index a889201b9..5d8a12ebb 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -52,15 +52,17 @@ class DNSRecordsDiagnoser(Diagnoser): discrepancies = [] for r in records: - current_value = self.get_current_record(domain, r["name"], r["type"]) or "None" - expected_value = r["value"] if r["value"] != "@" else domain + "." + r["current"] = self.get_current_record(domain, r["name"], r["type"]) or "None" + if r["value"] == "@": + r["value"] = domain + "." - if current_value == "None": - discrepancies.append(("diagnosis_dns_missing_record", (r["type"], r["name"], expected_value))) - elif current_value != expected_value: - discrepancies.append(("diagnosis_dns_discrepancy", (r["type"], r["name"], expected_value, current_value))) + if r["current"] == "None": + discrepancies.append(("diagnosis_dns_missing_record", r)) + elif r["current"] != r["value"]: + discrepancies.append(("diagnosis_dns_discrepancy", r)) if discrepancies: + discrepancies = [("diagnosis_dns_point_to_doc", {})] + discrepancies status = "ERROR" if (category == "basic" or (is_main_domain and category != "extra")) else "WARNING" summary = ("diagnosis_dns_bad_conf", {"domain": domain, "category": category}) else: diff --git a/data/hooks/diagnosis/14-ports.py b/data/hooks/diagnosis/14-ports.py index 712d0007b..fe7c9003d 100644 --- a/data/hooks/diagnosis/14-ports.py +++ b/data/hooks/diagnosis/14-ports.py @@ -49,12 +49,13 @@ class PortsDiagnoser(Diagnoser): yield dict(meta={"port": str(port)}, status="ERROR", summary=("diagnosis_ports_unreachable", {"port": port}), - details=[("diagnosis_ports_needed_by", (service, category)), ("diagnosis_ports_forwarding_tip", ())]) + details=[("diagnosis_ports_needed_by", {"service": service, "category": category}), + ("diagnosis_ports_forwarding_tip", {})]) else: yield dict(meta={"port": str(port)}, status="SUCCESS", summary=("diagnosis_ports_ok", {"port": port}), - details=[("diagnosis_ports_needed_by", (service, category))]) + details=[("diagnosis_ports_needed_by", {"service": service, "category": category})]) def main(args, env, loggers): diff --git a/data/hooks/diagnosis/21-web.py b/data/hooks/diagnosis/21-web.py index 2a3afba88..6b65b8da3 100644 --- a/data/hooks/diagnosis/21-web.py +++ b/data/hooks/diagnosis/21-web.py @@ -51,7 +51,7 @@ class WebDiagnoser(Diagnoser): yield dict(meta={"domain": domain}, status="ERROR", summary=("diagnosis_http_unreachable", {"domain": domain}), - details=[(detail,())]) + details=[(detail,{})]) # In there or idk where else ... # try to diagnose hairpinning situation by crafting a request for the diff --git a/data/hooks/diagnosis/30-services.py b/data/hooks/diagnosis/30-services.py index a46fa735d..9d6879933 100644 --- a/data/hooks/diagnosis/30-services.py +++ b/data/hooks/diagnosis/30-services.py @@ -22,12 +22,12 @@ class ServicesDiagnoser(Diagnoser): if result["status"] != "running": item["status"] = "ERROR" item["summary"] = ("diagnosis_services_bad_status", {"service": service, "status": result["status"]}) - item["details"] = [("diagnosis_services_bad_status_tip", (service,))] + item["details"] = [("diagnosis_services_bad_status_tip", {"service":service})] elif result["configuration"] == "broken": item["status"] = "WARNING" item["summary"] = ("diagnosis_services_conf_broken", {"service": service}) - item["details"] = [(d, tuple()) for d in result["configuration-details"]] + item["details"] = [(d, {}) for d in result["configuration-details"]] else: item["status"] = "SUCCESS" diff --git a/data/hooks/diagnosis/90-security.py b/data/hooks/diagnosis/90-security.py index 0b1b61226..1eedcc8ca 100644 --- a/data/hooks/diagnosis/90-security.py +++ b/data/hooks/diagnosis/90-security.py @@ -22,7 +22,7 @@ class SecurityDiagnoser(Diagnoser): yield dict(meta={"test": "meltdown"}, status="ERROR", summary=("diagnosis_security_vulnerable_to_meltdown", {}), - details=[("diagnosis_security_vulnerable_to_meltdown_details", ())] + details=[("diagnosis_security_vulnerable_to_meltdown_details", {})] ) else: yield dict(meta={}, diff --git a/locales/en.json b/locales/en.json index 64cca8713..3318e762a 100644 --- a/locales/en.json +++ b/locales/en.json @@ -140,7 +140,7 @@ "diagnosis_basesystem_hardware_board": "Server board model is {model}", "diagnosis_basesystem_host": "Server is running Debian {debian_version}", "diagnosis_basesystem_kernel": "Server is running Linux kernel {kernel_version}", - "diagnosis_basesystem_ynh_single_version": "{0} version: {1} ({2})", + "diagnosis_basesystem_ynh_single_version": "{package} version: {version} ({repo})", "diagnosis_basesystem_ynh_main_version": "Server is running YunoHost {main_version} ({repo})", "diagnosis_basesystem_ynh_inconsistent_versions": "You are running inconsistent versions of the YunoHost packages... most probably because of a failed or partial upgrade.", "diagnosis_display_tip_web": "You can go to the Diagnosis section (in the home screen) to see the issues found.", @@ -167,12 +167,13 @@ "diagnosis_ip_weird_resolvconf_details": "Instead, this file should be a symlink to /etc/resolvconf/run/resolv.conf itself pointing to 127.0.0.1 (dnsmasq). The actual resolvers should be configured in /etc/resolv.dnsmasq.conf.", "diagnosis_dns_good_conf": "Good DNS configuration for domain {domain} (category {category})", "diagnosis_dns_bad_conf": "Bad or missing DNS configuration for domain {domain} (category {category})", - "diagnosis_dns_missing_record": "According to the recommended DNS configuration, you should add a DNS record with type {0}, name {1} and value {2}. You can check https://yunohost.org/dns_config for more info.", - "diagnosis_dns_discrepancy": "The DNS record with type {0} and name {1} does not match the recommended configuration. Current value: {2}. Excepted value: {3}. You can check https://yunohost.org/dns_config for more info.", + "diagnosis_dns_missing_record": "According to the recommended DNS configuration, you should add a DNS record with type: {type}, name: {name}, and value: {value}", + "diagnosis_dns_discrepancy": "The DNS record with type {type} and name {name} does not match the recommended configuration. Current value: {current}. Excepted value: {value}", + "diagnosis_dns_point_to_doc": "Please check the documentation at https://yunohost.org/dns_config if you need help about configuring DNS records", "diagnosis_services_running": "Service {service} is running!", "diagnosis_services_conf_broken": "Configuration is broken for service {service}!", "diagnosis_services_bad_status": "Service {service} is {status} :(", - "diagnosis_services_bad_status_tip": "You can try to restart the service, and if it doesn't work, have a look at the service logs using 'yunohost service log {0}' or through the 'Services' section of the webadmin.", + "diagnosis_services_bad_status_tip": "You can try to restart the service, and if it doesn't work, have a look at the service logs using 'yunohost service log {service}' or through the 'Services' section of the webadmin.", "diagnosis_diskusage_verylow": "Storage {mountpoint} (on device {device}) has only {free_abs_GB} GB ({free_percent}%) space remaining. You should really consider cleaning up some space.", "diagnosis_diskusage_low": "Storage {mountpoint} (on device {device}) has only {free_abs_GB} GB ({free_percent}%) space remaining. Be careful.", "diagnosis_diskusage_ok": "Storage {mountpoint} (on device {device}) still has {free_abs_GB} GB ({free_percent}%) space left!", @@ -205,7 +206,7 @@ "diagnosis_ports_could_not_diagnose": "Could not diagnose if ports are reachable from outside. Error: {error}", "diagnosis_ports_unreachable": "Port {port} is not reachable from outside.", "diagnosis_ports_ok": "Port {port} is reachable from outside.", - "diagnosis_ports_needed_by": "Exposing this port is needed for {1} features (service {0})", + "diagnosis_ports_needed_by": "Exposing this port is needed for {category} features (service {service})", "diagnosis_ports_forwarding_tip": "To fix this issue, you most probably need to configure port forwarding on your internet router as described in https://yunohost.org/isp_box_config", "diagnosis_http_could_not_diagnose": "Could not diagnose if domain is reachable from outside. Error: {error}", "diagnosis_http_ok": "Domain {domain} is reachable through HTTP from outside the local network.", diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index 7f488b6aa..7f93f7c0d 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -458,7 +458,7 @@ class Diagnoser(): item["summary"] = m18n.n(summary_key, **summary_args) if "details" in item: - item["details"] = [m18n.n(key, *values) for key, values in item["details"]] + item["details"] = [m18n.n(key, **values) for key, values in item["details"]] def _list_diagnosis_categories(): From 587a07a6e6da1ee12c0f8cf013126c74cd1a5272 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 9 Apr 2020 03:00:10 +0200 Subject: [PATCH 0956/3170] Propagate change in string format to other locales --- locales/ar.json | 2 +- locales/ca.json | 12 ++++++------ locales/de.json | 2 +- locales/en.json | 4 ++-- locales/eo.json | 10 +++++----- locales/es.json | 10 +++++----- locales/fr.json | 10 +++++----- locales/oc.json | 10 +++++----- 8 files changed, 30 insertions(+), 30 deletions(-) diff --git a/locales/ar.json b/locales/ar.json index a1349fde7..9c1e67fe0 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -162,7 +162,7 @@ "app_action_broke_system": "يبدو أنّ هذا الإجراء أدّى إلى تحطيم هذه الخدمات المهمة: {services}", "diagnosis_basesystem_host": "هذا الخادم يُشغّل ديبيان {debian_version}", "diagnosis_basesystem_kernel": "هذا الخادم يُشغّل نواة لينكس {kernel_version}", - "diagnosis_basesystem_ynh_single_version": "{0} الإصدار: {1} ({2})", + "diagnosis_basesystem_ynh_single_version": "{package} الإصدار: {version} ({repo})", "diagnosis_basesystem_ynh_main_version": "هذا الخادم يُشغّل YunoHost {main_version} ({repo})", "diagnosis_everything_ok": "كل شيء على ما يرام في {category}!", "diagnosis_ip_connected_ipv4": "الخادم مُتّصل بالإنترنت عبر IPv4!", diff --git a/locales/ca.json b/locales/ca.json index 175543a13..4c31e4a6c 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -510,7 +510,7 @@ "domain_cannot_remove_main_add_new_one": "No es pot eliminar «{domain:s}» ja que és el domini principal i únic domini, primer s'ha d'afegir un altre domini utilitzant «yunohost domain add », i després fer-lo el domini principal amb «yunohost domain main-domain -n » i després es pot eliminar el domini «{domain:s}» utilitzant «yunohost domain remove {domain:s}».", "diagnosis_basesystem_host": "El servidor funciona amb Debian {debian_version}", "diagnosis_basesystem_kernel": "El servidor funciona amb el nucli de Linux {kernel_version}", - "diagnosis_basesystem_ynh_single_version": "{0} versió: {1}({2})", + "diagnosis_basesystem_ynh_single_version": "{package} versió: {version}({repo})", "diagnosis_basesystem_ynh_inconsistent_versions": "Esteu utilitzant versions inconsistents dels paquets de YunoHost… probablement a causa d'una actualització fallida o parcial.", "diagnosis_display_tip_web": "Podeu anar a la secció de Diagnòstics (en la pantalla principal) per veure els errors que s'han trobat.", "diagnosis_failed_for_category": "Ha fallat el diagnòstic per la categoria «{category}»: {error}", @@ -535,8 +535,8 @@ "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 de tipus {0}, nom {1} i valor {2}. Hi ha més informació a https://yunohost.org/dns_config.", - "diagnosis_dns_discrepancy": "El registre DNS de tipus {0} i nom {1} no concorda amb la configuració recomanada. Valor actual: {2}. Valor esperat: {3}. Més informació a https://yunohost.org/dns_config.", + "diagnosis_dns_missing_record": "Segons la configuració DNS recomanada, hauríeu d'afegir un registre DNS\ntipus: {type}\nnom: {name}\nvalor: {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_abs_GB} GB ({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_abs_GB} GB ({free_percent}%). Aneu amb compte.", @@ -575,7 +575,7 @@ "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 {0}» o a través de «Serveis» a la secció de la pàgina web d'administració.", + "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_no_cache": "Encara no hi ha memòria cau pel diagnòstic de la categoria «{category}»", @@ -586,7 +586,7 @@ "migration_description_0014_remove_app_status_json": "Eliminar els fitxers d'aplicació status.json heretats", "diagnosis_services_running": "El servei {service} s'està executant!", "diagnosis_services_conf_broken": "La configuració pel servei {service} està trencada!", - "diagnosis_ports_needed_by": "És necessari exposar aquest port per a les funcions {1} (servei {0})", + "diagnosis_ports_needed_by": "És necessari exposar aquest port per a les funcions {category} (servei {service})", "global_settings_setting_pop3_enabled": "Activa el protocol POP3 per al servidor de correu", "log_app_action_run": "Executa l'acció de l'aplicació «{}»", "log_app_config_show_panel": "Mostra el taulell de configuració de l'aplicació «{}»", @@ -596,4 +596,4 @@ "diagnosis_basesystem_hardware_board": "El model de la targeta del servidor és {model}", "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à…" -} \ No newline at end of file +} diff --git a/locales/de.json b/locales/de.json index d250a22fd..2369e3bdc 100644 --- a/locales/de.json +++ b/locales/de.json @@ -304,7 +304,7 @@ "app_upgrade_script_failed": "Es ist ein Fehler im App-Upgrade-Skript aufgetreten", "diagnosis_basesystem_host": "Server läuft unter Debian {debian_version}.", "diagnosis_basesystem_kernel": "Server läuft unter Linux-Kernel {kernel_version}", - "diagnosis_basesystem_ynh_single_version": "{0} Version: {1} ({2})", + "diagnosis_basesystem_ynh_single_version": "{package} Version: {version} ({repo})", "diagnosis_basesystem_ynh_main_version": "Server läuft YunoHost {main_version} ({repo})", "diagnosis_basesystem_ynh_inconsistent_versions": "Sie verwenden inkonsistente Versionen der YunoHost-Pakete... wahrscheinlich wegen eines fehlgeschlagenen oder teilweisen Upgrades.", "diagnosis_display_tip_web": "Sie können den Abschnitt Diagnose (im Startbildschirm) aufrufen, um die gefundenen Probleme anzuzeigen.", diff --git a/locales/en.json b/locales/en.json index 3318e762a..cec219ee6 100644 --- a/locales/en.json +++ b/locales/en.json @@ -167,8 +167,8 @@ "diagnosis_ip_weird_resolvconf_details": "Instead, this file should be a symlink to /etc/resolvconf/run/resolv.conf itself pointing to 127.0.0.1 (dnsmasq). The actual resolvers should be configured in /etc/resolv.dnsmasq.conf.", "diagnosis_dns_good_conf": "Good DNS configuration for domain {domain} (category {category})", "diagnosis_dns_bad_conf": "Bad or missing DNS configuration for domain {domain} (category {category})", - "diagnosis_dns_missing_record": "According to the recommended DNS configuration, you should add a DNS record with type: {type}, name: {name}, and value: {value}", - "diagnosis_dns_discrepancy": "The DNS record with type {type} and name {name} does not match the recommended configuration. Current value: {current}. Excepted value: {value}", + "diagnosis_dns_missing_record": "According to the recommended DNS configuration, you should add a DNS record with the following info.\nType: {type}\nName: {name}\nValue: {value}", + "diagnosis_dns_discrepancy": "The DNS record with type {type} and name {name} does not match the recommended configuration.\nCurrent value: {current}\nExcepted value: {value}", "diagnosis_dns_point_to_doc": "Please check the documentation at https://yunohost.org/dns_config if you need help about configuring DNS records", "diagnosis_services_running": "Service {service} is running!", "diagnosis_services_conf_broken": "Configuration is broken for service {service}!", diff --git a/locales/eo.json b/locales/eo.json index 7142d9f72..127e7df39 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -504,7 +504,7 @@ "apps_catalog_obsolete_cache": "La kaŝmemoro de la katalogo de programoj estas malplena aŭ malaktuala.", "apps_catalog_update_success": "La aplika katalogo estis ĝisdatigita!", "diagnosis_basesystem_kernel": "Servilo funkcias Linuksan kernon {kernel_version}", - "diagnosis_basesystem_ynh_single_version": "{0} versio: {1} ({2})", + "diagnosis_basesystem_ynh_single_version": "{package} versio: {version} ({repo})", "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.", @@ -541,8 +541,8 @@ "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_dns_missing_record": "Laŭ la rekomendita DNS-agordo, vi devas aldoni DNS-registron kun tipo {0}, nomo {1} kaj valoro {2}. Vi povas kontroli https://yunohost.org/dns_config por pliaj informoj.", - "diagnosis_dns_discrepancy": "La DNS-registro kun tipo {0} kaj nomo {1} ne kongruas kun la rekomendita agordo. Nuna valoro: {2}. Esceptita valoro: {3}. Vi povas kontroli https://yunohost.org/dns_config por pliaj informoj.", + "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} !", "diagnosis_services_bad_status": "Servo {service} estas {status} :(", "diagnosis_ram_low": "La sistemo havas {available_abs_MB} MB ({available_percent}%) RAM forlasita de {total_abs_MB} MB. Estu zorgema.", @@ -556,7 +556,7 @@ "diagnosis_description_systemresources": "Rimedaj sistemoj", "diagnosis_description_security": "Sekurecaj kontroloj", "diagnosis_ports_could_not_diagnose": "Ne povis diagnozi, ĉu haveblaj havenoj de ekstere. 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 {0}' aŭ tra la sekcio 'Servoj' de la retadreso.", + "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_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", @@ -576,7 +576,7 @@ "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 servo {0}", + "diagnosis_ports_needed_by": "Eksponi ĉi tiun havenon necesas por servo {service}", "diagnosis_ports_forwarding_tip": "Por solvi ĉi tiun problemon, plej probable vi 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. Eraro: {error}", "diagnosis_http_ok": "Domajno {domain} atingeblas de ekstere.", diff --git a/locales/es.json b/locales/es.json index 5a00ab6dc..b72665066 100644 --- a/locales/es.json +++ b/locales/es.json @@ -505,7 +505,7 @@ "app_remove_after_failed_install": "Eliminando la aplicación tras el fallo de instalación…", "diagnosis_basesystem_host": "El servidor está ejecutando Debian {debian_version}.", "diagnosis_basesystem_kernel": "El servidor está ejecutando el núcleo de Linux {kernel_version}", - "diagnosis_basesystem_ynh_single_version": "{0} versión: {1} ({2})", + "diagnosis_basesystem_ynh_single_version": "{package} versión: {version} ({repo})", "diagnosis_basesystem_ynh_main_version": "El servidor está ejecutando YunoHost {main_version} ({repo})", "diagnosis_basesystem_ynh_inconsistent_versions": "Está ejecutando versiones incoherentes de los paquetes de YunoHost... probablemente por una actualización errónea o parcial.", "diagnosis_failed_for_category": "Diagnóstico fallido para la categoría «{category}» : {error}", @@ -528,9 +528,9 @@ "diagnosis_ip_no_ipv4": "El servidor no cuenta con ipv4 funcional.", "diagnosis_ip_not_connected_at_all": "¿¡Está conectado el servidor a internet!?", "diagnosis_ip_broken_resolvconf": "DNS parece no funcionar en tu servidor, lo que parece estar relacionado con /etc/resolv.conf no apuntando a 127.0.0.1.", - "diagnosis_dns_missing_record": "Según la configuración DNS recomendada, deberías añadir un registro DNS de tipo {0}, nombre {1} y valor {2}. Puedes consultar https://yunohost.org/dns_config para más información.", + "diagnosis_dns_missing_record": "Según la configuración DNS recomendada, deberías añadir un registro DNS\ntipo: {type}\nnombre: {name}\nvalor: {value}", "diagnosis_diskusage_low": "El almacenamiento {mountpoint} (en dispositivo {device}) solo tiene {free_abs_GB} GB ({free_percent}%) de espacio disponible. Ten cuidado.", - "diagnosis_services_bad_status_tip": "Puedes intentar reiniciar el servicio, y si no funciona, echar un vistazo a los logs del servicio usando 'yunohost service log {0}' o a través de la sección 'Servicios' en webadmin.", + "diagnosis_services_bad_status_tip": "Puedes intentar reiniciar el servicio, y si no funciona, echar un vistazo a los logs del servicio usando 'yunohost service log {service}' o a través de la sección 'Servicios' en webadmin.", "diagnosis_ip_connected_ipv6": "¡El servidor está conectado a internet a través de IPv6!", "diagnosis_ip_no_ipv6": "El servidor no cuenta con IPv6 funcional.", "diagnosis_ip_dnsresolution_working": "¡DNS no está funcionando!", @@ -539,7 +539,7 @@ "diagnosis_ip_weird_resolvconf_details": "En su lugar, este fichero debería ser un enlace simbólico a /etc/resolvconf/run/resolv.conf apuntando a 127.0.0.1 (dnsmasq). Los servidores de nombre de domino deben configurarse a través de /etc/resolv.dnsmasq.conf.", "diagnosis_dns_good_conf": "Buena configuración DNS para el dominio {domain} (categoría {category})", "diagnosis_dns_bad_conf": "Configuración mala o faltante de los DNS para el dominio {domain} (categoría {category})", - "diagnosis_dns_discrepancy": "El registro DNS con tipo {0} y nombre {1} no se corresponde a la configuración recomendada. Valor actual: {2}. Valor esperado: {3}. Puedes consultar https://yunohost.org/dns_config para más información.", + "diagnosis_dns_discrepancy": "El registro DNS con tipo {type} y nombre {name} no se corresponde a la configuración recomendada.\nValor actual: {current}\nValor esperado: {value}", "diagnosis_services_bad_status": "El servicio {service} está {status} :(", "diagnosis_diskusage_verylow": "El almacenamiento {mountpoint} (en el dispositivo {device}) sólo tiene {free_abs_GB} GB ({free_percent}%) de espacio disponible. Deberías considerar la posibilidad de limpiar algo de espacio.", "diagnosis_diskusage_ok": "¡El almacenamiento {mountpoint} (en el dispositivo {device}) todavía tiene {free_abs_GB} GB ({free_percent}%) de espacio libre!", @@ -569,7 +569,7 @@ "diagnosis_description_ports": "Exposición de puertos", "diagnosis_description_systemresources": "Recursos del sistema", "diagnosis_swap_ok": "El sistema tiene {total_MB} MB de espacio de intercambio!", - "diagnosis_ports_needed_by": "La apertura de este puerto es requerida para la funcionalidad {1} (service {0})", + "diagnosis_ports_needed_by": "La apertura de este puerto es requerida para la funcionalidad {category} (service {service})", "diagnosis_ports_ok": "El puerto {port} es accesible desde internet.", "diagnosis_ports_unreachable": "El puerto {port} no es accesible desde internet.", "diagnosis_ports_could_not_diagnose": "No se puede comprobar si los puertos están accesibles desde el exterior. Error: {error}", diff --git a/locales/fr.json b/locales/fr.json index 9f7dd445b..c8dfd12a9 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -509,14 +509,14 @@ "diagnosis_ip_not_connected_at_all": "Le serveur ne semble pas du tout connecté à Internet !?", "diagnosis_ip_weird_resolvconf": "La résolution DNS semble fonctionner, mais soyez prudent en utilisant un fichier /etc/resolv.conf personnalisé.", "diagnosis_ip_weird_resolvconf_details": "Au lieu de cela, ce fichier devrait être un lien symbolique vers /etc/resolvconf/run/resolv.conf lui-même pointant vers 127.0.0.1 (dnsmasq). Les résolveurs réels doivent être configurés dans /etc/resolv.dnsmasq.conf.", - "diagnosis_dns_missing_record": "Selon la configuration DNS recommandée, vous devez ajouter un enregistrement DNS de type {0}, nom {1} et valeur {2}. Vous pouvez consulter https://yunohost.org/dns_config pour plus d'informations.", + "diagnosis_dns_missing_record": "Selon la configuration DNS recommandée, vous devez ajouter un enregistrement DNS\nType: {type}\nNom: {name}\nValeur {value}", "diagnosis_diskusage_ok": "Le stockage {mountpoint} (sur le périphérique {device}) a encore {free_abs_GB} Go ({free_percent}%) d'espace libre !", "diagnosis_ram_ok": "Le système dispose encore de {available_abs_MB} MB ({available_percent}%) de RAM sur {total_abs_MB} MB.", "diagnosis_regenconf_allgood": "Tous les fichiers de configuration sont conformes à la configuration recommandée !", "diagnosis_security_vulnerable_to_meltdown": "Vous semblez vulnérable à la vulnérabilité de sécurité critique de Meltdown", "diagnosis_basesystem_host": "Le serveur utilise Debian {debian_version}", "diagnosis_basesystem_kernel": "Le serveur utilise le noyau Linux {kernel_version}", - "diagnosis_basesystem_ynh_single_version": "{0} version: {1} ({2})", + "diagnosis_basesystem_ynh_single_version": "{package} version: {version} ({repo})", "diagnosis_basesystem_ynh_main_version": "Le serveur utilise YunoHost {main_version} ({repo})", "diagnosis_basesystem_ynh_inconsistent_versions": "Vous exécutez des versions incohérentes des packages YunoHost ... probablement à cause d'une mise à niveau partielle ou échouée.", "diagnosis_display_tip_cli": "Vous pouvez exécuter 'yunohost diagnosis show --issues' pour afficher les problèmes détectés.", @@ -535,7 +535,7 @@ "diagnosis_ip_broken_resolvconf": "La résolution du nom de domaine semble cassée sur votre serveur, ce qui semble lié au fait que /etc/resolv.conf ne pointe pas vers 127.0.0.1.", "diagnosis_dns_good_conf": "Bonne configuration DNS pour le domaine {domain} (catégorie {category})", "diagnosis_dns_bad_conf": "Configuration DNS incorrecte ou manquante pour le domaine {domain} (catégorie {category})", - "diagnosis_dns_discrepancy": "L'enregistrement DNS de type {0} et nom {1} ne correspond pas à la configuration recommandée. Valeur actuelle: {2}. Valeur exceptée: {3}. Vous pouvez consulter https://yunohost.org/dns_config pour plus d'informations.", + "diagnosis_dns_discrepancy": "L'enregistrement DNS de type {type} et nom {name} ne correspond pas à la configuration recommandée.\nValeur actuelle: {current}\nValeur attendue: {value}", "diagnosis_services_bad_status": "Le service {service} est {status} :-(", "diagnosis_diskusage_verylow": "Le stockage {mountpoint} (sur le périphérique {device}) ne dispose que de {free_abs_GB} Go ({free_percent}%). Vous devriez vraiment envisager de nettoyer un peu d'espace.", "diagnosis_diskusage_low": "Le stockage {mountpoint} (sur le périphérique {device}) ne dispose que de {free_abs_GB} Go ({free_percent}%). Faites attention.", @@ -579,13 +579,13 @@ "migration_description_0014_remove_app_status_json": "Supprimer les fichiers d'application status.json hérités", "diagnosis_services_running": "Le service {service} s'exécute correctement !", "diagnosis_services_conf_broken": "La configuration est cassée pour le service {service} !", - "diagnosis_ports_needed_by": "Rendre ce port accessible est nécessaire pour les fonctionnalités de type {1} (service {0})", + "diagnosis_ports_needed_by": "Rendre ce port accessible est nécessaire pour les fonctionnalités de type {category} (service {service})", "diagnosis_ports_forwarding_tip": "Pour résoudre ce problème, vous devez probablement configurer la redirection de port sur votre routeur Internet comme décrit sur https://yunohost.org/isp_box_config", "diagnosis_http_connection_error": "Erreur de connexion : impossible de se connecter au domaine demandé, il est probablement injoignable.", "diagnosis_no_cache": "Pas encore de cache de diagnostique pour la catégorie « {category} »", "diagnosis_http_unknown_error": "Une erreur est survenue en essayant de joindre votre domaine, il est probablement injoignable.", "yunohost_postinstall_end_tip": "La post-installation terminée! Pour finaliser votre configuration, il est recommendé de :\n - ajouter un premier utilisateur depuis la section \"Utilisateurs\" de l'interface web (ou \"yunohost user create \" en ligne de commande);\n - diagnostiquer les potentiels problèmes dans la section \"Diagnostic\" de l'interface web (ou \"yunohost diagnosis run\" en ligne de commande);\n - lire les parties \"Finalisation de votre configuration\" et \"Découverte de Yunohost\" dans le guide de l'administrateur: https://yunohost.org/admindoc.", - "diagnosis_services_bad_status_tip": "Vous pouvez essayer de redémarrer le service. Si cela ne fonctionne pas, consultez les journaux de service à l'aide de 'yunohost service log {0}' ou de la section 'Services' de l'administrateur Web.", + "diagnosis_services_bad_status_tip": "Vous pouvez essayer de redémarrer le service. Si cela ne fonctionne pas, consultez les journaux de service à l'aide de 'yunohost service log {service}' ou de la section 'Services' de l'administrateur Web.", "diagnosis_http_bad_status_code": "Le système de diagnostique n'a pas réussi à contacter votre serveur. Il se peut qu'une autre machine réponde à la place de votre serveur. Vérifiez que le port 80 est correctement redirigé, que votre configuration nginx est à jour et qu’un reverse-proxy n’interfère pas.", "diagnosis_http_timeout": "Expiration du délai en essayant de contacter votre serveur de l'extérieur. Il semble être inaccessible. Vérifiez que vous transférez correctement le port 80, que nginx est en cours d’exécution et qu’un pare-feu n’interfère pas.", "global_settings_setting_pop3_enabled": "Activer le protocole POP3 pour le serveur de messagerie", diff --git a/locales/oc.json b/locales/oc.json index 5472c97e8..a452b72bb 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -497,7 +497,7 @@ "user_already_exists": "L’utilizaire {user} existís ja", "diagnosis_basesystem_host": "Lo servidor fonciona amb Debian {debian_version}.", "diagnosis_basesystem_kernel": "Lo servidor fonciona amb lo nuclèu Linuxl {kernel_version}", - "diagnosis_basesystem_ynh_single_version": "{0} version : {1} ({2})", + "diagnosis_basesystem_ynh_single_version": "{package} version : {version} ({repo})", "diagnosis_basesystem_ynh_inconsistent_versions": "Utilizatz de versions inconsistentas dels paquets de YunoHost… probablament a causa d'una actualizacion fracassada o parciala.", "diagnosis_display_tip_cli": "Podètz executar « yunohost diagnosis show --issues » per mostrar las errors trobadas.", "diagnosis_ignored_issues": "(+ {nb_ignored} problèma(es) ignorat(s))", @@ -536,8 +536,8 @@ "operation_interrupted": "L’operacion es estada interrompuda manualament ?", "group_cannot_be_deleted": "Lo grop « {group} » pòt pas èsser suprimit manualament.", "diagnosis_found_warnings": "Trobat {warnings} element(s) que se poirián melhorar per {category}.", - "diagnosis_dns_missing_record": "Segon la configuracion DNS recomandada, vos calriá ajustar un enregistrament DNS de tipe {0}, nom {1} e valor {2}. Podètz consultar https://yunohost.org/dns_config per mai d’informacions.", - "diagnosis_dns_discrepancy": "Segon la configuracion DNS recomandada, la valor per l’enregistrament DNS de tipe {0} e nom {1} deuriá èsser {2} allòc de {3}.", + "diagnosis_dns_missing_record": "Segon la configuracion DNS recomandada, vos calriá ajustar un enregistrament DNS\ntipe: {type}\nnom: {name}\nvalor: {value}", + "diagnosis_dns_discrepancy": "Segon la configuracion DNS recomandada, la valor per l’enregistrament DNS\ntipe: {type}\nnom: {name}\ndeuriá èsser: {current}\nallòc de: {value}", "diagnosis_regenconf_manually_modified_debian_details": "Es pas problematic, mas car téner d’agacher...", "diagnosis_ports_could_not_diagnose": "Impossible de diagnosticar se los pòrts son accessibles de l’exterior. Error : {error}", "diagnosis_http_could_not_diagnose": "Impossible de diagnosticar se lo domeni es accessible de l’exterior. Error : {error}", @@ -556,11 +556,11 @@ "apps_catalog_init_success": "Sistèma de catalòg d’aplicacion iniciat !", "diagnosis_services_running": "Lo servici {service} es lançat !", "diagnosis_services_conf_broken": "La configuracion es copada pel servici {service} !", - "diagnosis_ports_needed_by": "Es necessari qu’aqueste pòrt siá accessible pel servici {0}", + "diagnosis_ports_needed_by": "Es necessari qu’aqueste pòrt siá accessible pel servici {service}", "diagnosis_diskusage_low": "Lo lòc d’emmagazinatge {mountpoint} (sul periferic {device}) a solament {free_abs_GB} Go ({free_percent}%). Siatz prudent.", "migration_description_0014_remove_app_status_json": "Suprimir los fichièrs d’aplicacion status.json eretats", "dyndns_provider_unreachable": "Impossible d’atenher lo provesidor Dyndns : siá vòstre YunoHost es pas corrèctament connectat a Internet siá lo servidor dynette es copat.", - "diagnosis_services_bad_status_tip": "Podètz ensajar de reaviar lo servici, e se non fonciona pas, podètz agachar los jornals en utilizant « yunohost service log {0} » o via la seccion « Servicis » de pas la pagina web d’administracion.", + "diagnosis_services_bad_status_tip": "Podètz ensajar de reaviar lo servici, e se non fonciona pas, podètz agachar los jornals en utilizant « yunohost service log {service} » o via la seccion « Servicis » de pas la pagina web d’administracion.", "diagnosis_http_connection_error": "Error de connexion : connexion impossibla al domeni demandat, benlèu qu’es pas accessible.", "diagnosis_http_unknown_error": "Una error s’es producha en ensajar de se connectar a vòstre domeni, es benlèu pas accessible.", "group_user_already_in_group": "L’utilizaire {user} es ja dins lo grop « {group} »", From 3cff370c62f2150b0a306871b2258c42f01b29d1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 9 Apr 2020 01:55:25 +0200 Subject: [PATCH 0957/3170] Add some bits of magic to simplify the way we yield test items --- data/hooks/diagnosis/00-basesystem.py | 37 +++++++++------------- data/hooks/diagnosis/10-ip.py | 26 +++++++-------- data/hooks/diagnosis/12-dnsrecords.py | 7 ++-- data/hooks/diagnosis/14-ports.py | 11 ++++--- data/hooks/diagnosis/21-web.py | 6 ++-- data/hooks/diagnosis/24-mail.py | 4 +-- data/hooks/diagnosis/30-services.py | 13 ++++---- data/hooks/diagnosis/50-systemresources.py | 34 +++++++++++--------- data/hooks/diagnosis/70-regenconf.py | 6 ++-- data/hooks/diagnosis/90-security.py | 6 ++-- src/yunohost/diagnosis.py | 21 ++++++++++++ 11 files changed, 94 insertions(+), 77 deletions(-) diff --git a/data/hooks/diagnosis/00-basesystem.py b/data/hooks/diagnosis/00-basesystem.py index 3c932b488..97f77cc1d 100644 --- a/data/hooks/diagnosis/00-basesystem.py +++ b/data/hooks/diagnosis/00-basesystem.py @@ -23,55 +23,48 @@ class BaseSystemDiagnoser(Diagnoser): hardware = dict(meta={"test": "hardware"}, status="INFO", data={"virt": virt, "arch": arch}, - summary=("diagnosis_basesystem_hardware", {"virt": virt, "arch": arch})) + summary="diagnosis_basesystem_hardware") if os.path.exists("/proc/device-tree/model"): model = read_file('/proc/device-tree/model').strip() - hardware["data"]["board"] = model - hardware["details"] = [("diagnosis_basesystem_hardware_board", {"model": model})] + hardware["data"]["model"] = model + hardware["details"] = ["diagnosis_basesystem_hardware_board"] yield hardware # Kernel version kernel_version = read_file('/proc/sys/kernel/osrelease').strip() yield dict(meta={"test": "kernel"}, + data={"kernel_version": kernel_version}, status="INFO", - summary=("diagnosis_basesystem_kernel", {"kernel_version": kernel_version})) + summary="diagnosis_basesystem_kernel") # Debian release debian_version = read_file("/etc/debian_version").strip() yield dict(meta={"test": "host"}, + data={"debian_version": debian_version}, status="INFO", - summary=("diagnosis_basesystem_host", {"debian_version": debian_version})) + summary="diagnosis_basesystem_host") # Yunohost packages versions - ynh_packages = ynh_packages_version() # We check if versions are consistent (e.g. all 3.6 and not 3 packages with 3.6 and the other with 3.5) # This is a classical issue for upgrades that failed in the middle # (or people upgrading half of the package because they did 'apt upgrade' instead of 'dist-upgrade') # Here, ynh_core_version is for example "3.5.4.12", so [:3] is "3.5" and we check it's the same for all packages + ynh_packages = ynh_packages_version() ynh_core_version = ynh_packages["yunohost"]["version"] consistent_versions = all(infos["version"][:3] == ynh_core_version[:3] for infos in ynh_packages.values()) ynh_version_details = [("diagnosis_basesystem_ynh_single_version", {"package":package, "version": infos["version"], "repo": infos["repo"]} - ) - for package, infos in ynh_packages.items()] + ) + for package, infos in ynh_packages.items()] - if consistent_versions: - yield dict(meta={"test": "ynh_versions"}, - data={"main_version": ynh_core_version, "repo": ynh_packages["yunohost"]["repo"]}, - status="INFO", - summary=("diagnosis_basesystem_ynh_main_version", - {"main_version": ynh_core_version, - "repo": ynh_packages["yunohost"]["repo"]}), - details=ynh_version_details) - else: - yield dict(meta={"test": "ynh_versions"}, - data={"main_version": ynh_core_version, "repo": ynh_packages["yunohost"]["repo"]}, - status="ERROR", - summary=("diagnosis_basesystem_ynh_inconsistent_versions", {}), - details=ynh_version_details) + yield dict(meta={"test": "ynh_versions"}, + data={"main_version": ynh_core_version, "repo": ynh_packages["yunohost"]["repo"]}, + status="INFO" if consistent_versions else "ERROR", + summary="diagnosis_basesystem_ynh_main_version" if consistent_versions else "diagnosis_basesystem_ynh_inconsistent_versions", + details=ynh_version_details) def main(args, env, loggers): diff --git a/data/hooks/diagnosis/10-ip.py b/data/hooks/diagnosis/10-ip.py index 7e96a7b56..3f197a7bc 100644 --- a/data/hooks/diagnosis/10-ip.py +++ b/data/hooks/diagnosis/10-ip.py @@ -28,7 +28,7 @@ class IPDiagnoser(Diagnoser): if not can_ping_ipv4 and not can_ping_ipv6: yield dict(meta={"test": "ping"}, status="ERROR", - summary=("diagnosis_ip_not_connected_at_all", {})) + summary="diagnosis_ip_not_connected_at_all") # Not much else we can do if there's no internet at all return @@ -49,20 +49,19 @@ class IPDiagnoser(Diagnoser): if not can_resolve_dns: yield dict(meta={"test": "dnsresolv"}, status="ERROR", - summary=("diagnosis_ip_broken_dnsresolution", {}) if good_resolvconf - else ("diagnosis_ip_broken_resolvconf", {})) + summary="diagnosis_ip_broken_dnsresolution" if good_resolvconf else "diagnosis_ip_broken_resolvconf") return # Otherwise, if the resolv conf is bad but we were able to resolve domain name, # still warn that we're using a weird resolv conf ... elif not good_resolvconf: yield dict(meta={"test": "dnsresolv"}, status="WARNING", - summary=("diagnosis_ip_weird_resolvconf", {}), - details=[("diagnosis_ip_weird_resolvconf_details", {})]) + summary="diagnosis_ip_weird_resolvconf", + details=["diagnosis_ip_weird_resolvconf_details"]) else: yield dict(meta={"test": "dnsresolv"}, status="SUCCESS", - summary=("diagnosis_ip_dnsresolution_working", {})) + summary="diagnosis_ip_dnsresolution_working") # ##################################################### # # IP DIAGNOSIS : Check that we're actually able to talk # @@ -72,17 +71,16 @@ class IPDiagnoser(Diagnoser): ipv4 = self.get_public_ip(4) if can_ping_ipv4 else None ipv6 = self.get_public_ip(6) if can_ping_ipv6 else None - yield dict(meta={"test": "ip", "version": '4'}, - data=ipv4, + yield dict(meta={"test": "ipv4"}, + data={"global": ipv4}, status="SUCCESS" if ipv4 else "ERROR", - summary=("diagnosis_ip_connected_ipv4", {}) if ipv4 - else ("diagnosis_ip_no_ipv4", {})) + summary="diagnosis_ip_connected_ipv4" if ipv4 else "diagnosis_ip_no_ipv4") - yield dict(meta={"test": "ip", "version": '6'}, - data=ipv6, + yield dict(meta={"test": "ipv6"}, + data={"global": ipv6}, status="SUCCESS" if ipv6 else "WARNING", - summary=("diagnosis_ip_connected_ipv6", {}) if ipv6 - else ("diagnosis_ip_no_ipv6", {})) + summary="diagnosis_ip_connected_ipv6" if ipv6 else "diagnosis_ip_no_ipv6") + # TODO / FIXME : add some attempt to detect ISP (using whois ?) ? diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py index 5d8a12ebb..d653b044c 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -62,19 +62,18 @@ class DNSRecordsDiagnoser(Diagnoser): discrepancies.append(("diagnosis_dns_discrepancy", r)) if discrepancies: - discrepancies = [("diagnosis_dns_point_to_doc", {})] + discrepancies status = "ERROR" if (category == "basic" or (is_main_domain and category != "extra")) else "WARNING" - summary = ("diagnosis_dns_bad_conf", {"domain": domain, "category": category}) + summary = "diagnosis_dns_bad_conf" else: status = "SUCCESS" - summary = ("diagnosis_dns_good_conf", {"domain": domain, "category": category}) + summary = "diagnosis_dns_good_conf" output = dict(meta={"domain": domain, "category": category}, status=status, summary=summary) if discrepancies: - output["details"] = discrepancies + output["details"] = ["diagnosis_dns_point_to_doc"] + discrepancies yield output diff --git a/data/hooks/diagnosis/14-ports.py b/data/hooks/diagnosis/14-ports.py index fe7c9003d..f973a3275 100644 --- a/data/hooks/diagnosis/14-ports.py +++ b/data/hooks/diagnosis/14-ports.py @@ -47,15 +47,16 @@ class PortsDiagnoser(Diagnoser): category = services[service].get("category", "[?]") if r["ports"].get(str(port), None) is not True: yield dict(meta={"port": str(port)}, + data={"service": service, "category": category}, status="ERROR", - summary=("diagnosis_ports_unreachable", {"port": port}), - details=[("diagnosis_ports_needed_by", {"service": service, "category": category}), - ("diagnosis_ports_forwarding_tip", {})]) + summary="diagnosis_ports_unreachable", + details=["diagnosis_ports_needed_by", "diagnosis_ports_forwarding_tip"]) else: yield dict(meta={"port": str(port)}, + data={"service": service, "category": category}, status="SUCCESS", - summary=("diagnosis_ports_ok", {"port": port}), - details=[("diagnosis_ports_needed_by", {"service": service, "category": category})]) + summary="diagnosis_ports_ok", + details=["diagnosis_ports_needed_by"]) def main(args, env, loggers): diff --git a/data/hooks/diagnosis/21-web.py b/data/hooks/diagnosis/21-web.py index 6b65b8da3..5008f0360 100644 --- a/data/hooks/diagnosis/21-web.py +++ b/data/hooks/diagnosis/21-web.py @@ -45,13 +45,13 @@ class WebDiagnoser(Diagnoser): if r["status"] == "ok": yield dict(meta={"domain": domain}, status="SUCCESS", - summary=("diagnosis_http_ok", {"domain": domain})) + summary="diagnosis_http_ok") else: detail = r["code"].replace("error_http_check", "diagnosis_http") if "code" in r else "diagnosis_http_unknown_error" yield dict(meta={"domain": domain}, status="ERROR", - summary=("diagnosis_http_unreachable", {"domain": domain}), - details=[(detail,{})]) + summary="diagnosis_http_unreachable", + details=[detail]) # In there or idk where else ... # try to diagnose hairpinning situation by crafting a request for the diff --git a/data/hooks/diagnosis/24-mail.py b/data/hooks/diagnosis/24-mail.py index f0060df52..0a3a97102 100644 --- a/data/hooks/diagnosis/24-mail.py +++ b/data/hooks/diagnosis/24-mail.py @@ -17,11 +17,11 @@ class MailDiagnoser(Diagnoser): if os.system('/bin/nc -z -w2 yunohost.org 25') == 0: yield dict(meta={"test": "ougoing_port_25"}, status="SUCCESS", - summary=("diagnosis_mail_ougoing_port_25_ok",{})) + summary="diagnosis_mail_ougoing_port_25_ok") else: yield dict(meta={"test": "outgoing_port_25"}, status="ERROR", - summary=("diagnosis_mail_ougoing_port_25_blocked",{})) + summary="diagnosis_mail_ougoing_port_25_blocked") diff --git a/data/hooks/diagnosis/30-services.py b/data/hooks/diagnosis/30-services.py index 9d6879933..6217d89d3 100644 --- a/data/hooks/diagnosis/30-services.py +++ b/data/hooks/diagnosis/30-services.py @@ -17,21 +17,22 @@ class ServicesDiagnoser(Diagnoser): for service, result in sorted(all_result.items()): - item = dict(meta={"service": service}) + item = dict(meta={"service": service}, + data={"status": result["status"], "configuration": result["configuration"]}) if result["status"] != "running": item["status"] = "ERROR" - item["summary"] = ("diagnosis_services_bad_status", {"service": service, "status": result["status"]}) - item["details"] = [("diagnosis_services_bad_status_tip", {"service":service})] + item["summary"] = "diagnosis_services_bad_status" + item["details"] = ["diagnosis_services_bad_status_tip"] elif result["configuration"] == "broken": item["status"] = "WARNING" - item["summary"] = ("diagnosis_services_conf_broken", {"service": service}) - item["details"] = [(d, {}) for d in result["configuration-details"]] + item["summary"] = "diagnosis_services_conf_broken" + item["details"] = result["configuration-details"] else: item["status"] = "SUCCESS" - item["summary"] = ("diagnosis_services_running", {"service": service, "status": result["status"]}) + item["summary"] = "diagnosis_services_running" yield item diff --git a/data/hooks/diagnosis/50-systemresources.py b/data/hooks/diagnosis/50-systemresources.py index 95f58ddb7..1f0c07f47 100644 --- a/data/hooks/diagnosis/50-systemresources.py +++ b/data/hooks/diagnosis/50-systemresources.py @@ -20,17 +20,19 @@ class SystemResourcesDiagnoser(Diagnoser): ram_total_abs_MB = ram.total / (1024**2) ram_available_abs_MB = ram.available / (1024**2) ram_available_percent = round(100 * ram.available / ram.total) - item = dict(meta={"test": "ram"}) - infos = {"total_abs_MB": ram_total_abs_MB, "available_abs_MB": ram_available_abs_MB, "available_percent": ram_available_percent} + item = dict(meta={"test": "ram"}, + data={"total_abs_MB": ram_total_abs_MB, + "available_abs_MB": ram_available_abs_MB, + "available_percent": ram_available_percent}) if ram_available_abs_MB < 100 or ram_available_percent < 5: item["status"] = "ERROR" - item["summary"] = ("diagnosis_ram_verylow", infos) + item["summary"] = "diagnosis_ram_verylow" elif ram_available_abs_MB < 200 or ram_available_percent < 10: item["status"] = "WARNING" - item["summary"] = ("diagnosis_ram_low", infos) + item["summary"] = "diagnosis_ram_low" else: item["status"] = "SUCCESS" - item["summary"] = ("diagnosis_ram_ok", infos) + item["summary"] = "diagnosis_ram_ok" yield item # @@ -39,19 +41,21 @@ class SystemResourcesDiagnoser(Diagnoser): swap = psutil.swap_memory() swap_total_abs_MB = swap.total / (1024*1024) - item = dict(meta={"test": "swap"}) - infos = {"total_MB": swap_total_abs_MB} + item = dict(meta={"test": "swap"}, + data={"total_MB": swap_total_abs_MB}) if swap_total_abs_MB <= 0: item["status"] = "ERROR" - item["summary"] = ("diagnosis_swap_none", infos) + item["summary"] = "diagnosis_swap_none" elif swap_total_abs_MB <= 256: item["status"] = "WARNING" - item["summary"] = ("diagnosis_swap_notsomuch", infos) + item["summary"] = "diagnosis_swap_notsomuch" else: item["status"] = "SUCCESS" - item["summary"] = ("diagnosis_swap_ok", infos) + item["summary"] = "diagnosis_swap_ok" yield item + # FIXME : add a check that swapiness is low if swap is on a sdcard... + # # Disks usage # @@ -66,17 +70,17 @@ class SystemResourcesDiagnoser(Diagnoser): free_abs_GB = usage.free / (1024 ** 3) free_percent = 100 - usage.percent - item = dict(meta={"test": "diskusage", "mountpoint": mountpoint}) - infos = {"mountpoint": mountpoint, "device": device, "free_abs_GB": free_abs_GB, "free_percent": free_percent} + item = dict(meta={"test": "diskusage", "mountpoint": mountpoint}, + data={"device": device, "free_abs_GB": free_abs_GB, "free_percent": free_percent}) if free_abs_GB < 1 or free_percent < 5: item["status"] = "ERROR" - item["summary"] = ("diagnosis_diskusage_verylow", infos) + item["summary"] = "diagnosis_diskusage_verylow" elif free_abs_GB < 2 or free_percent < 10: item["status"] = "WARNING" - item["summary"] = ("diagnosis_diskusage_low", infos) + item["summary"] = "diagnosis_diskusage_low" else: item["status"] = "SUCCESS" - item["summary"] = ("diagnosis_diskusage_ok", infos) + item["summary"] = "diagnosis_diskusage_ok" yield item diff --git a/data/hooks/diagnosis/70-regenconf.py b/data/hooks/diagnosis/70-regenconf.py index a3e284f90..75db146ab 100644 --- a/data/hooks/diagnosis/70-regenconf.py +++ b/data/hooks/diagnosis/70-regenconf.py @@ -22,14 +22,14 @@ class RegenconfDiagnoser(Diagnoser): if regenconf_modified_files == []: yield dict(meta={"test": "regenconf"}, status="SUCCESS", - summary=("diagnosis_regenconf_allgood", {}) + summary="diagnosis_regenconf_allgood" ) else: for f in regenconf_modified_files: yield dict(meta={"test": "regenconf", "file": f}, status="WARNING", - summary=("diagnosis_regenconf_manually_modified", {"file": f}), - details=[("diagnosis_regenconf_manually_modified_details", {})] + summary="diagnosis_regenconf_manually_modified", + details=["diagnosis_regenconf_manually_modified_details"] ) #for f in debian_modified_files: diff --git a/data/hooks/diagnosis/90-security.py b/data/hooks/diagnosis/90-security.py index 1eedcc8ca..d281042b0 100644 --- a/data/hooks/diagnosis/90-security.py +++ b/data/hooks/diagnosis/90-security.py @@ -21,13 +21,13 @@ class SecurityDiagnoser(Diagnoser): 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", {})] + summary="diagnosis_security_vulnerable_to_meltdown", + details=["diagnosis_security_vulnerable_to_meltdown_details"] ) else: yield dict(meta={}, status="SUCCESS", - summary=("diagnosis_security_all_good", {}) + summary="diagnosis_security_all_good" ) diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index 7f93f7c0d..effd610cc 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -453,11 +453,32 @@ class Diagnoser(): report["description"] = Diagnoser.get_description(report["id"]) + def is_tuple_or_list(stuff): + return isinstance(stuff, tuple) or isinstance(stuff, list) + for item in report["items"]: + + # For the summary and each details, we want to call + # m18n() on the string, with the appropriate data for string + # formatting which can come from : + # - infos super-specific to the summary/details (if it's a tuple(key,dict_with_info) and not just a string) + # - 'meta' info = parameters of the test (e.g. which domain/category for DNS conf record) + # - actual 'data' retrieved from the test (e.g. actual global IP, ...) + + meta_data = item.get("meta", {}).copy() + meta_data.update(item.get("data", {})) + + if not is_tuple_or_list(item["summary"]): + item["summary"] = (item["summary"], {}) summary_key, summary_args = item["summary"] + summary_args.update(meta_data) + item["summary"] = m18n.n(summary_key, **summary_args) if "details" in item: + item["details"] = [(d[0], d[1]) if is_tuple_or_list(d) else (d, {}) for d in item["details"]] + for d in item["details"]: + d[1].update(meta_data) item["details"] = [m18n.n(key, **values) for key, values in item["details"]] From 7c3cce6bf97937aeb09282c229fcf3e10d63d120 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 9 Apr 2020 02:26:47 +0200 Subject: [PATCH 0958/3170] Try to diagnose and add details about global and local IPs --- data/hooks/diagnosis/10-ip.py | 24 ++++++++++++++++++------ locales/en.json | 2 ++ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/data/hooks/diagnosis/10-ip.py b/data/hooks/diagnosis/10-ip.py index 3f197a7bc..70a5c9594 100644 --- a/data/hooks/diagnosis/10-ip.py +++ b/data/hooks/diagnosis/10-ip.py @@ -8,7 +8,7 @@ from moulinette.utils.process import check_output from moulinette.utils.filesystem import read_file from yunohost.diagnosis import Diagnoser - +from yunohost.utils.network import get_network_interfaces class IPDiagnoser(Diagnoser): @@ -71,16 +71,28 @@ class IPDiagnoser(Diagnoser): ipv4 = self.get_public_ip(4) if can_ping_ipv4 else None ipv6 = self.get_public_ip(6) if can_ping_ipv6 else None + network_interfaces = get_network_interfaces() + def get_local_ip(version): + local_ip = {iface:addr[version].split("/")[0] + for iface, addr in network_interfaces.items() if version in addr} + if not local_ip: + return None + elif len(local_ip): + return next(iter(local_ip.values())) + else: + return local_ip + yield dict(meta={"test": "ipv4"}, - data={"global": ipv4}, + data={"global": ipv4, "local": get_local_ip("ipv4")}, status="SUCCESS" if ipv4 else "ERROR", - summary="diagnosis_ip_connected_ipv4" if ipv4 else "diagnosis_ip_no_ipv4") + summary="diagnosis_ip_connected_ipv4" if ipv4 else "diagnosis_ip_no_ipv4", + details=["diagnosis_ip_global", "diagnosis_ip_local"] if ipv4 else None) yield dict(meta={"test": "ipv6"}, - data={"global": ipv6}, + data={"global": ipv6, "local": get_local_ip("ipv6")}, status="SUCCESS" if ipv6 else "WARNING", - summary="diagnosis_ip_connected_ipv6" if ipv6 else "diagnosis_ip_no_ipv6") - + summary="diagnosis_ip_connected_ipv6" if ipv6 else "diagnosis_ip_no_ipv6", + details=["diagnosis_ip_global", "diagnosis_ip_local"] if ipv6 else None) # TODO / FIXME : add some attempt to detect ISP (using whois ?) ? diff --git a/locales/en.json b/locales/en.json index cec219ee6..c02c6890e 100644 --- a/locales/en.json +++ b/locales/en.json @@ -159,6 +159,8 @@ "diagnosis_ip_no_ipv4": "The server does not have working IPv4.", "diagnosis_ip_connected_ipv6": "The server is connected to the Internet through IPv6 !", "diagnosis_ip_no_ipv6": "The server does not have working IPv6.", + "diagnosis_ip_global": "Global IP: {global}", + "diagnosis_ip_local": "Local IP: {local}", "diagnosis_ip_not_connected_at_all": "The server does not seem to be connected to the Internet at all!?", "diagnosis_ip_dnsresolution_working": "Domain name resolution is working!", "diagnosis_ip_broken_dnsresolution": "Domain name resolution seems to be broken for some reason... Is a firewall blocking DNS requests ?", From 9ebb3102cdc0a1d4d2f259f7495663a7025acfbf Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 9 Apr 2020 02:27:09 +0200 Subject: [PATCH 0959/3170] Remove details key if it's empty --- src/yunohost/diagnosis.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index effd610cc..31518c257 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -384,6 +384,10 @@ class Diagnoser(): items = list(self.run()) + for item in items: + if "details" in item and not item["details"]: + del item["details"] + new_report = {"id": self.id_, "cached_for": self.cache_duration, "items": items} From 8cb2640872a49d780d01029758e601caaaa03338 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 10 Apr 2020 20:43:11 +0200 Subject: [PATCH 0960/3170] Fix usage of systemd-detect-virt on baremetal --- data/hooks/diagnosis/00-basesystem.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/data/hooks/diagnosis/00-basesystem.py b/data/hooks/diagnosis/00-basesystem.py index 97f77cc1d..68a9570ce 100644 --- a/data/hooks/diagnosis/00-basesystem.py +++ b/data/hooks/diagnosis/00-basesystem.py @@ -17,13 +17,20 @@ class BaseSystemDiagnoser(Diagnoser): def run(self): # Detect virt technology (if not bare metal) and arch - # Also possibly the board name - virt = check_output("systemd-detect-virt").strip() or "bare-metal" + # Gotta have this "|| true" because it systemd-detect-virt return 'none' + # with an error code on bare metal ~.~ + virt = check_output("systemd-detect-virt || true", shell=True).strip() + if virt.lower() == "none": + virt = "bare-metal" + + # Detect arch arch = check_output("dpkg --print-architecture").strip() hardware = dict(meta={"test": "hardware"}, status="INFO", data={"virt": virt, "arch": arch}, summary="diagnosis_basesystem_hardware") + + # Also possibly the board name if os.path.exists("/proc/device-tree/model"): model = read_file('/proc/device-tree/model').strip() hardware["data"]["model"] = model From f9dd634ebeace1983d3dce9ce9a9048269369391 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 11 Apr 2020 00:18:45 +0200 Subject: [PATCH 0961/3170] Detect if nginx conf does not include well-known diagnosis location --- data/hooks/diagnosis/21-web.py | 9 +++++++++ locales/en.json | 2 ++ 2 files changed, 11 insertions(+) diff --git a/data/hooks/diagnosis/21-web.py b/data/hooks/diagnosis/21-web.py index 5008f0360..add192685 100644 --- a/data/hooks/diagnosis/21-web.py +++ b/data/hooks/diagnosis/21-web.py @@ -22,6 +22,15 @@ class WebDiagnoser(Diagnoser): all_domains = domain_list()["domains"] for domain in all_domains: + # If the diagnosis location ain't defined, can't do diagnosis, + # probably because nginx conf manually modified... + nginx_conf = "/etc/nginx/conf.d/%s.conf" % domain + if os.system("grep -q '^.*location .*/.well-known/ynh-diagnosis/' %s" % nginx_conf) != 0: + yield dict(meta={"domain": domain}, + status="WARNING", + summary="diagnosis_http_nginx_conf_not_up_to_date", + details=["diagnosis_http_nginx_conf_not_up_to_date_details"]) + nonce = ''.join(random.choice(nonce_digits) for i in range(16)) os.system("rm -rf /tmp/.well-known/ynh-diagnosis/") os.system("mkdir -p /tmp/.well-known/ynh-diagnosis/") diff --git a/locales/en.json b/locales/en.json index c02c6890e..5032bb4f3 100644 --- a/locales/en.json +++ b/locales/en.json @@ -217,6 +217,8 @@ "diagnosis_http_unknown_error": "An error happened while trying to reach your domain, it's very likely unreachable.", "diagnosis_http_bad_status_code": "The diagnosis system could not reach your server. It might be that another machine answered instead of your server. You should check that you're correctly forwarding port 80, that your nginx configuration is up to date, and that a reverse-proxy is not interfering.", "diagnosis_http_unreachable": "Domain {domain} appears unreachable through HTTP from outside the local network.", + "diagnosis_http_nginx_conf_not_up_to_date": "This domain's nginx configuration appears to have been modified manually, and prevents YunoHost from diagnosing if it's reachable on HTTP.", + "diagnosis_http_nginx_conf_not_up_to_date_details": "To fix the situation, inspect the different with the command line using 'yunohost tools regen-conf nginx --dry-run --with-diff' and if you're ok, apply the changes with 'yunohost tools regen-conf nginx --force'.", "diagnosis_unknown_categories": "The following categories are unknown: {categories}", "diagnosis_never_ran_yet": "It looks like this server was setup recently and there's no diagnosis report to show yet. You should start by running a full diagnosis, either from the webadmin or using 'yunohost diagnosis run' from the command line.", "domain_cannot_remove_main": "You cannot remove '{domain:s}' since it's the main domain, you first need to set another domain as the main domain using 'yunohost domain main-domain -n '; here is the list of candidate domains: {other_domains:s}", From 3869c2f68e02f1fe170484213a46c312575764b9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 11 Apr 2020 02:28:40 +0200 Subject: [PATCH 0962/3170] Add html tags to improve readability of some results (in particular DNS records stuff) on webadmin --- locales/en.json | 20 +++++++++++--------- src/yunohost/diagnosis.py | 25 +++++++++++++------------ 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/locales/en.json b/locales/en.json index 5032bb4f3..d3261a2cf 100644 --- a/locales/en.json +++ b/locales/en.json @@ -159,19 +159,19 @@ "diagnosis_ip_no_ipv4": "The server does not have working IPv4.", "diagnosis_ip_connected_ipv6": "The server is connected to the Internet through IPv6 !", "diagnosis_ip_no_ipv6": "The server does not have working IPv6.", - "diagnosis_ip_global": "Global IP: {global}", - "diagnosis_ip_local": "Local IP: {local}", + "diagnosis_ip_global": "Global IP: {global}", + "diagnosis_ip_local": "Local IP: {local}", "diagnosis_ip_not_connected_at_all": "The server does not seem to be connected to the Internet at all!?", "diagnosis_ip_dnsresolution_working": "Domain name resolution is working!", "diagnosis_ip_broken_dnsresolution": "Domain name resolution seems to be broken for some reason... Is a firewall blocking DNS requests ?", - "diagnosis_ip_broken_resolvconf": "Domain name resolution seems to be broken on your server, which seems related to /etc/resolv.conf not pointing to 127.0.0.1.", - "diagnosis_ip_weird_resolvconf": "DNS resolution seems to be working, but be careful that you seem to be using a custom /etc/resolv.conf.", - "diagnosis_ip_weird_resolvconf_details": "Instead, this file should be a symlink to /etc/resolvconf/run/resolv.conf itself pointing to 127.0.0.1 (dnsmasq). The actual resolvers should be configured in /etc/resolv.dnsmasq.conf.", + "diagnosis_ip_broken_resolvconf": "Domain name resolution seems to be broken on your server, which seems related to /etc/resolv.conf not pointing to 127.0.0.1.", + "diagnosis_ip_weird_resolvconf": "DNS resolution seems to be working, but it looks like you're using a custom /etc/resolv.conf.", + "diagnosis_ip_weird_resolvconf_details": "The file /etc/resolv.conf should be a symlink to /etc/resolvconf/run/resolv.conf itself pointing to 127.0.0.1 (dnsmasq). If you want to manually configure DNS resolvers, please edit /etc/resolv.dnsmasq.conf.", "diagnosis_dns_good_conf": "Good DNS configuration for domain {domain} (category {category})", "diagnosis_dns_bad_conf": "Bad or missing DNS configuration for domain {domain} (category {category})", - "diagnosis_dns_missing_record": "According to the recommended DNS configuration, you should add a DNS record with the following info.\nType: {type}\nName: {name}\nValue: {value}", - "diagnosis_dns_discrepancy": "The DNS record with type {type} and name {name} does not match the recommended configuration.\nCurrent value: {current}\nExcepted value: {value}", - "diagnosis_dns_point_to_doc": "Please check the documentation at https://yunohost.org/dns_config if you need help about configuring DNS records", + "diagnosis_dns_missing_record": "According to the recommended DNS configuration, you should add a DNS record with the following info.
Type: {type}
Name: {name}
Value: {value}", + "diagnosis_dns_discrepancy": "The following DNS record does not seem to follow the recommended configuration:
Type: {type}
Name: {name}
Current value: {current}
Excepted value: {value}", + "diagnosis_dns_point_to_doc": "Please check the documentation at https://yunohost.org/dns_config if you need help about configuring DNS records.", "diagnosis_services_running": "Service {service} is running!", "diagnosis_services_conf_broken": "Configuration is broken for service {service}!", "diagnosis_services_bad_status": "Service {service} is {status} :(", @@ -209,7 +209,9 @@ "diagnosis_ports_unreachable": "Port {port} is not reachable from outside.", "diagnosis_ports_ok": "Port {port} is reachable from outside.", "diagnosis_ports_needed_by": "Exposing this port is needed for {category} features (service {service})", - "diagnosis_ports_forwarding_tip": "To fix this issue, you most probably need to configure port forwarding on your internet router as described in https://yunohost.org/isp_box_config", + "diagnosis_ports_forwarding_tip": "To fix this issue, you most probably need to configure port forwarding on your internet router as described in https://yunohost.org/isp_box_config", + "diagnosis_http_hairpinning_issue": "Your local network does not seem to have hairpinning enabled.", + "diagnosis_http_hairpinning_issue_details": "This is probably because of your ISP box / router. As a result, people from outside your local network will be able to access your server as expected, but not people from inside the local network (like you, probably?). You may be able to improve the situation by having a look at https://yunohost.org/dns_local_network", "diagnosis_http_could_not_diagnose": "Could not diagnose if domain is reachable from outside. Error: {error}", "diagnosis_http_ok": "Domain {domain} is reachable through HTTP from outside the local network.", "diagnosis_http_timeout": "Timed-out while trying to contact your server from outside. It appears to be unreachable. You should check that you're correctly forwarding port 80, that nginx is running, and that a firewall is not interfering.", diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index 31518c257..369554bd4 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -24,6 +24,7 @@ Look for possible issues on the server """ +import re import os import time @@ -457,9 +458,6 @@ class Diagnoser(): report["description"] = Diagnoser.get_description(report["id"]) - def is_tuple_or_list(stuff): - return isinstance(stuff, tuple) or isinstance(stuff, list) - for item in report["items"]: # For the summary and each details, we want to call @@ -472,18 +470,21 @@ class Diagnoser(): meta_data = item.get("meta", {}).copy() meta_data.update(item.get("data", {})) - if not is_tuple_or_list(item["summary"]): - item["summary"] = (item["summary"], {}) - summary_key, summary_args = item["summary"] - summary_args.update(meta_data) + html_tags = re.compile(r'<[^>]+>') + def m18n_(info): + if not isinstance(info, tuple) and not isinstance(info, list): + info = (info, {}) + info[1].update(meta_data) + s = m18n.n(info[0], **(info[1])) + # In cli, we remove the html tags + if msettings.get("interface") != "api": + s = html_tags.sub('', s.replace("
","\n")) + return s - item["summary"] = m18n.n(summary_key, **summary_args) + item["summary"] = m18n_(item["summary"]) if "details" in item: - item["details"] = [(d[0], d[1]) if is_tuple_or_list(d) else (d, {}) for d in item["details"]] - for d in item["details"]: - d[1].update(meta_data) - item["details"] = [m18n.n(key, **values) for key, values in item["details"]] + item["details"] = [m18n_(info) for info in item["details"]] def _list_diagnosis_categories(): From 2f0a95645ae58f273e55667cb266d16e4d329f11 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 11 Apr 2020 03:25:03 +0200 Subject: [PATCH 0963/3170] Hmpf boring resolvconf shit --- data/hooks/conf_regen/43-dnsmasq | 15 +++++++++++++++ data/hooks/diagnosis/10-ip.py | 15 +++++++-------- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/data/hooks/conf_regen/43-dnsmasq b/data/hooks/conf_regen/43-dnsmasq index 90e96a04c..d6ab8648c 100755 --- a/data/hooks/conf_regen/43-dnsmasq +++ b/data/hooks/conf_regen/43-dnsmasq @@ -50,6 +50,21 @@ do_pre_regen() { do_post_regen() { regen_conf_files=$1 + # Fuck it, those domain/search entries from dhclient are usually annoying + # lying shit from the ISP trying to MiTM + if grep -q -E "^ *(domain|search)" /run/resolvconf/resolv.conf + then + if grep -q -E "^ *(domain|search)" /run/resolvconf/interface/*.dhclient 2>/dev/null + then + sed -E "s/^(domain|search)/#\1/g" -i /run/resolvconf/interface/*.dhclient + fi + + grep -q '^supersede domain-name "";' /etc/dhcp/dhclient.conf 2>/dev/null || echo '^supersede domain-name "";' >> /etc/dhcp/dhclient.conf + grep -q '^supersede domain-search "";' /etc/dhcp/dhclient.conf 2>/dev/null || echo '^supersede domain-search "";' >> /etc/dhcp/dhclient.conf + grep -q '^supersede name "";' /etc/dhcp/dhclient.conf 2>/dev/null || echo '^supersede name "";' >> /etc/dhcp/dhclient.conf + systemctl restart resolvconf + fi + [[ -z "$regen_conf_files" ]] \ || service dnsmasq restart } diff --git a/data/hooks/diagnosis/10-ip.py b/data/hooks/diagnosis/10-ip.py index 70a5c9594..7d0aa8da2 100644 --- a/data/hooks/diagnosis/10-ip.py +++ b/data/hooks/diagnosis/10-ip.py @@ -41,7 +41,7 @@ class IPDiagnoser(Diagnoser): # In every case, we can check that resolvconf seems to be okay # (symlink managed by resolvconf service + pointing to dnsmasq) - good_resolvconf = self.resolvconf_is_symlink() and self.resolvconf_points_to_localhost() + good_resolvconf = self.good_resolvconf() # If we can't resolve domain names at all, that's a pretty big issue ... # If it turns out that at the same time, resolvconf is bad, that's probably @@ -131,13 +131,12 @@ class IPDiagnoser(Diagnoser): def can_resolve_dns(self): return os.system("dig +short ip.yunohost.org >/dev/null 2>/dev/null") == 0 - def resolvconf_is_symlink(self): - return os.path.realpath("/etc/resolv.conf") == "/run/resolvconf/resolv.conf" - - def resolvconf_points_to_localhost(self): - file_ = "/etc/resolv.conf" - resolvers = [r.split(" ")[1] for r in read_file(file_).split("\n") if r.startswith("nameserver")] - return resolvers == ["127.0.0.1"] + def good_resolvconf(self): + content = read_file(file_).strip().split("\n") + # Ignore comments and empty lines + content = [l.strip() for l in content if l.strip() and not l.strip().startswith("#")] + # We should only find a "nameserver 127.0.0.1" + return len(content) == 1 and content.split() == ["nameserver", "127.0.0.1"] def get_public_ip(self, protocol=4): From 42293fcce38792e10623777542011dd10224cde0 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 9 Apr 2020 13:53:31 +0200 Subject: [PATCH 0964/3170] Attempt to detect hairpinning --- data/hooks/diagnosis/21-web.py | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/data/hooks/diagnosis/21-web.py b/data/hooks/diagnosis/21-web.py index add192685..56b054e53 100644 --- a/data/hooks/diagnosis/21-web.py +++ b/data/hooks/diagnosis/21-web.py @@ -19,6 +19,7 @@ class WebDiagnoser(Diagnoser): nonce_digits = "0123456789abcedf" + at_least_one_domain_ok = False all_domains = domain_list()["domains"] for domain in all_domains: @@ -52,6 +53,7 @@ class WebDiagnoser(Diagnoser): raise YunohostError("diagnosis_http_could_not_diagnose", error=e) if r["status"] == "ok": + at_least_one_domain_ok = True yield dict(meta={"domain": domain}, status="SUCCESS", summary="diagnosis_http_ok") @@ -62,9 +64,28 @@ class WebDiagnoser(Diagnoser): summary="diagnosis_http_unreachable", details=[detail]) - # In there or idk where else ... - # try to diagnose hairpinning situation by crafting a request for the - # global ip (from within local network) and seeing if we're getting the right page ? + # If at least one domain is correctly exposed to the outside, + # attempt to diagnose hairpinning situations. On network with + # hairpinning issues, the server may be correctly exposed on the + # outside, but from the outside, it will be as if the port forwarding + # was not configured... Hence, calling for example + # "curl --head the.global.ip" will simply timeout... + if at_least_one_domain_ok: + ipv4 = Diagnoser.get_cached_report_item("ip", {"test": "ipv4"}) + global_ipv4 = ipv4.get("data", {}).get("global", {}) + if global_ipv4: + try: + requests.head("http://" + ipv4, timeout=5) + except requests.exceptions.Timeout as e: + yield dict(meta={"test": "hairpinning"}, + status="WARNING", + summary="diagnosis_http_hairpinning_issue", + details=["diagnosis_http_hairpinning_issue_details"]) + except: + # Well I dunno what to do if that's another exception + # type... That'll most probably *not* be an hairpinning + # issue but something else super weird ... + pass def main(args, env, loggers): From ad4c13887862fbaec774619f34d41cf6de5ef4d0 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 11 Apr 2020 17:20:24 +0200 Subject: [PATCH 0965/3170] Better debugging info when miserably failing to run diagnosis --- src/yunohost/diagnosis.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index 369554bd4..d8c6b5f57 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -167,7 +167,8 @@ def diagnosis_run(categories=[], force=False): try: code, report = hook_exec(path, args={"force": force}, env=None) except Exception as e: - logger.error(m18n.n("diagnosis_failed_for_category", category=category, error=str(e)), exc_info=True) + import traceback + logger.error(m18n.n("diagnosis_failed_for_category", category=category, error='\n'+traceback.format_exc())) else: diagnosed_categories.append(category) if report != {}: @@ -424,7 +425,7 @@ class Diagnoser(): cache_file = Diagnoser.cache_file(id_) if not os.path.exists(cache_file): logger.warning(m18n.n("diagnosis_no_cache", category=id_)) - report = {"id": category, + report = {"id": id_, "cached_for": -1, "timestamp": -1, "items": []} From f47352df8896ec39a26338048eb552491cd32528 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 11 Apr 2020 18:10:21 +0200 Subject: [PATCH 0966/3170] Improve message about server unreachable on http --- locales/en.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/en.json b/locales/en.json index d3261a2cf..be479efca 100644 --- a/locales/en.json +++ b/locales/en.json @@ -214,10 +214,10 @@ "diagnosis_http_hairpinning_issue_details": "This is probably because of your ISP box / router. As a result, people from outside your local network will be able to access your server as expected, but not people from inside the local network (like you, probably?). You may be able to improve the situation by having a look at https://yunohost.org/dns_local_network", "diagnosis_http_could_not_diagnose": "Could not diagnose if domain is reachable from outside. Error: {error}", "diagnosis_http_ok": "Domain {domain} is reachable through HTTP from outside the local network.", - "diagnosis_http_timeout": "Timed-out while trying to contact your server from outside. It appears to be unreachable. You should check that you're correctly forwarding port 80, that nginx is running, and that a firewall is not interfering.", + "diagnosis_http_timeout": "Timed-out while trying to contact your server from outside. It appears to be unreachable.
1. The most common cause for this issue is that you did not correctly configure port forwarding for port 80.
2. Also make sure that the web server nginx is running
3. On more complex setups: make sure that a firewall or reverse-proxy is not interfering.", "diagnosis_http_connection_error": "Connection error: could not connect to the requested domain, it's very likely unreachable.", "diagnosis_http_unknown_error": "An error happened while trying to reach your domain, it's very likely unreachable.", - "diagnosis_http_bad_status_code": "The diagnosis system could not reach your server. It might be that another machine answered instead of your server. You should check that you're correctly forwarding port 80, that your nginx configuration is up to date, and that a reverse-proxy is not interfering.", + "diagnosis_http_bad_status_code": "Timed-out while trying to contact your server from outside. It might be that another machine answered instead of your server.
1. The most common cause for this issue is that you did not correctly configure port forwarding for port 80.
2. On more complex setups: make sure that a firewall or reverse-proxy is not interfering.", "diagnosis_http_unreachable": "Domain {domain} appears unreachable through HTTP from outside the local network.", "diagnosis_http_nginx_conf_not_up_to_date": "This domain's nginx configuration appears to have been modified manually, and prevents YunoHost from diagnosing if it's reachable on HTTP.", "diagnosis_http_nginx_conf_not_up_to_date_details": "To fix the situation, inspect the different with the command line using 'yunohost tools regen-conf nginx --dry-run --with-diff' and if you're ok, apply the changes with 'yunohost tools regen-conf nginx --force'.", From b443caf63a0877d63fca26f34010e47cd4a58452 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 11 Apr 2020 18:10:46 +0200 Subject: [PATCH 0967/3170] Open links in new tab in the webadmin --- src/yunohost/diagnosis.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index d8c6b5f57..9a8962ac4 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -480,6 +480,9 @@ class Diagnoser(): # In cli, we remove the html tags if msettings.get("interface") != "api": s = html_tags.sub('', s.replace("
","\n")) + else: + # Make it so that links open in new tabs + s = s.replace(" Date: Sat, 11 Apr 2020 19:21:29 +0200 Subject: [PATCH 0968/3170] Uhoh typo --- data/hooks/diagnosis/10-ip.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/hooks/diagnosis/10-ip.py b/data/hooks/diagnosis/10-ip.py index 7d0aa8da2..42b52eb07 100644 --- a/data/hooks/diagnosis/10-ip.py +++ b/data/hooks/diagnosis/10-ip.py @@ -132,7 +132,7 @@ class IPDiagnoser(Diagnoser): return os.system("dig +short ip.yunohost.org >/dev/null 2>/dev/null") == 0 def good_resolvconf(self): - content = read_file(file_).strip().split("\n") + content = read_file("/etc/resolv.conf").strip().split("\n") # Ignore comments and empty lines content = [l.strip() for l in content if l.strip() and not l.strip().startswith("#")] # We should only find a "nameserver 127.0.0.1" From ae82fe3693cb51d0c19b0817a7093ed8980e5129 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 11 Apr 2020 19:52:57 +0200 Subject: [PATCH 0969/3170] Improve the way we check DNS records to avoid false negative on TXT or MX --- data/hooks/diagnosis/12-dnsrecords.py | 45 +++++++++++++++++++++------ 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py index d653b044c..f5d779118 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -52,14 +52,15 @@ class DNSRecordsDiagnoser(Diagnoser): discrepancies = [] for r in records: - r["current"] = self.get_current_record(domain, r["name"], r["type"]) or "None" + r["current"] = self.get_current_record(domain, r["name"], r["type"]) if r["value"] == "@": r["value"] = domain + "." - if r["current"] == "None": - discrepancies.append(("diagnosis_dns_missing_record", r)) - elif r["current"] != r["value"]: - discrepancies.append(("diagnosis_dns_discrepancy", r)) + if not self.current_record_match_expected(r): + if r["current"] is None: + discrepancies.append(("diagnosis_dns_missing_record", r)) + else: + discrepancies.append(("diagnosis_dns_discrepancy", r)) if discrepancies: status = "ERROR" if (category == "basic" or (is_main_domain and category != "extra")) else "WARNING" @@ -85,10 +86,36 @@ class DNSRecordsDiagnoser(Diagnoser): # FIXME : gotta handle case where this command fails ... # e.g. no internet connectivity (dependency mechanism to good result from 'ip' diagosis ?) # or the resolver is unavailable for some reason - output = check_output(command).strip() - if output.startswith('"') and output.endswith('"'): - output = '"' + ' '.join(output.replace('"', ' ').split()) + '"' - return output + output = check_output(command).strip().split("\n") + if len(output) == 0 or not output[0]: + return None + elif len(output) == 1: + return output[0] + else: + return output + + def current_record_match_expected(self, r): + if r["value"] is not None and r["current"] is None: + return False + if r["value"] is None and r["current"] is not None: + return False + elif isinstance(r["current"], list): + return False + + if r["type"] == "TXT": + # Split expected/current + # from "v=DKIM1; k=rsa; p=hugekey;" + # to a set like {'v=DKIM1', 'k=rsa', 'p=...'} + expected = set(r["value"].strip(' "').strip(";").replace(" ", "").split()) + current = set(r["current"].strip(' "').strip(";").replace(" ", "").split()) + return expected == current + elif r["type"] == "MX": + # For MX, we want to ignore the priority + expected = r["value"].split()[-1] + current = r["current"].split()[-1] + return expected == current + else: + return r["current"] == r["value"] def main(args, env, loggers): From 093ccd8020f509845a81df31c43f9843914defd7 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 11 Apr 2020 20:02:47 +0200 Subject: [PATCH 0970/3170] Make sure that there's no AAAA records when no ipv6 --- data/hooks/diagnosis/12-dnsrecords.py | 2 +- src/yunohost/domain.py | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py index f5d779118..7ea92e3f7 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -38,7 +38,7 @@ class DNSRecordsDiagnoser(Diagnoser): def check_domain(self, domain, is_main_domain, is_subdomain): - expected_configuration = _build_dns_conf(domain) + expected_configuration = _build_dns_conf(domain, include_empty_AAAA_if_no_ipv6=True) # FIXME: Here if there are no AAAA record, we should add something to expect "no" AAAA record # to properly diagnose situations where people have a AAAA record but no IPv6 diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 23b5a4179..7910147a3 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -395,7 +395,7 @@ def _normalize_domain_path(domain, path): return domain, path -def _build_dns_conf(domain, ttl=3600): +def _build_dns_conf(domain, ttl=3600, include_empty_AAAA_if_no_ipv6=False): """ Internal function that will returns a data structure containing the needed information to generate/adapt the dns configuration @@ -448,6 +448,8 @@ def _build_dns_conf(domain, ttl=3600): if ipv6: basic.append(["@", ttl, "AAAA", ipv6]) + elif include_empty_AAAA_if_no_ipv6: + basic.append(["@", ttl, "AAAA", None]) ######### # Email # @@ -495,8 +497,11 @@ def _build_dns_conf(domain, ttl=3600): if ipv4: extra.append(["*", ttl, "A", ipv4]) + if ipv6: extra.append(["*", ttl, "AAAA", ipv6]) + elif include_empty_AAAA_if_no_ipv6: + extra.append(["*", ttl, "AAAA", None]) extra.append(["@", ttl, "CAA", '128 issue "letsencrypt.org"']) From 16b234044137bbf24a757482a8e8f0e2820beed0 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 11 Apr 2020 20:04:03 +0200 Subject: [PATCH 0971/3170] Uhoh typo again --- data/hooks/diagnosis/21-web.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/hooks/diagnosis/21-web.py b/data/hooks/diagnosis/21-web.py index 56b054e53..2d0344abe 100644 --- a/data/hooks/diagnosis/21-web.py +++ b/data/hooks/diagnosis/21-web.py @@ -75,7 +75,7 @@ class WebDiagnoser(Diagnoser): global_ipv4 = ipv4.get("data", {}).get("global", {}) if global_ipv4: try: - requests.head("http://" + ipv4, timeout=5) + requests.head("http://" + global_ipv4, timeout=5) except requests.exceptions.Timeout as e: yield dict(meta={"test": "hairpinning"}, status="WARNING", From bfe3f415cacbea9fff7746c456128911d4d6a98a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 11 Apr 2020 20:06:14 +0200 Subject: [PATCH 0972/3170] Report bad XMPP DNS records as warning for now --- data/hooks/diagnosis/12-dnsrecords.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py index 7ea92e3f7..3132cf45f 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -63,7 +63,7 @@ class DNSRecordsDiagnoser(Diagnoser): discrepancies.append(("diagnosis_dns_discrepancy", r)) if discrepancies: - status = "ERROR" if (category == "basic" or (is_main_domain and category != "extra")) else "WARNING" + status = "ERROR" if (category == "basic" or (is_main_domain and category == "mail")) else "WARNING" summary = "diagnosis_dns_bad_conf" else: status = "SUCCESS" From 92d9d49a05aa2d68cf78d4e7d4bfd8f4a55087d3 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 13 Apr 2020 18:50:37 +0200 Subject: [PATCH 0973/3170] Fix resolvconf check --- data/hooks/diagnosis/10-ip.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/hooks/diagnosis/10-ip.py b/data/hooks/diagnosis/10-ip.py index 42b52eb07..36e04b5c1 100644 --- a/data/hooks/diagnosis/10-ip.py +++ b/data/hooks/diagnosis/10-ip.py @@ -136,7 +136,7 @@ class IPDiagnoser(Diagnoser): # Ignore comments and empty lines content = [l.strip() for l in content if l.strip() and not l.strip().startswith("#")] # We should only find a "nameserver 127.0.0.1" - return len(content) == 1 and content.split() == ["nameserver", "127.0.0.1"] + return len(content) == 1 and content[0].split() == ["nameserver", "127.0.0.1"] def get_public_ip(self, protocol=4): From efb45d4ece1c870736882eebf09470198e0fa4af Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 14 Apr 2020 16:01:59 +0200 Subject: [PATCH 0974/3170] Add special behavior for tags for diagnosis messages... --- src/yunohost/diagnosis.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index 9a8962ac4..c11cde566 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -479,8 +479,10 @@ class Diagnoser(): s = m18n.n(info[0], **(info[1])) # In cli, we remove the html tags if msettings.get("interface") != "api": + s = s.replace("", "'").replace("", "'") s = html_tags.sub('', s.replace("
","\n")) else: + s = s.replace("", "").replace("", "") # Make it so that links open in new tabs s = s.replace("
yunohost service log {service} or through the 'Services' section of the webadmin.", "diagnosis_diskusage_verylow": "Storage {mountpoint} (on device {device}) has only {free_abs_GB} GB ({free_percent}%) space remaining. You should really consider cleaning up some space.", "diagnosis_diskusage_low": "Storage {mountpoint} (on device {device}) has only {free_abs_GB} GB ({free_percent}%) space remaining. Be careful.", "diagnosis_diskusage_ok": "Storage {mountpoint} (on device {device}) still has {free_abs_GB} GB ({free_percent}%) space left!", @@ -188,10 +188,8 @@ "diagnosis_mail_ougoing_port_25_ok": "Outgoing port 25 is not blocked and email can be sent to other servers.", "diagnosis_mail_ougoing_port_25_blocked": "Outgoing port 25 appears to be blocked. You should try to unblock it in your internet service provider (or hosting provider) configuration panel. Meanwhile, the server won't be able to send emails to other servers.", "diagnosis_regenconf_allgood": "All configurations files are in line with the recommended configuration!", - "diagnosis_regenconf_manually_modified": "Configuration file {file} was manually modified.", - "diagnosis_regenconf_manually_modified_details": "This is probably OK as long as you know what you're doing ;) !", - "diagnosis_regenconf_manually_modified_debian": "Configuration file {file} was manually modified compared to Debian's default.", - "diagnosis_regenconf_manually_modified_debian_details": "This may probably be OK, but gotta keep an eye on it...", + "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! Though YunoHost will stop updating this file automatically, beware that YunoHost upgrades may contain important recommended changes. You can inspect the difference 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.", @@ -220,7 +218,7 @@ "diagnosis_http_bad_status_code": "Timed-out while trying to contact your server from outside. It might be that another machine answered instead of your server.
1. The most common cause for this issue is that you did not correctly configure port forwarding for port 80.
2. On more complex setups: make sure that a firewall or reverse-proxy is not interfering.", "diagnosis_http_unreachable": "Domain {domain} appears unreachable through HTTP from outside the local network.", "diagnosis_http_nginx_conf_not_up_to_date": "This domain's nginx configuration appears to have been modified manually, and prevents YunoHost from diagnosing if it's reachable on HTTP.", - "diagnosis_http_nginx_conf_not_up_to_date_details": "To fix the situation, inspect the different with the command line using 'yunohost tools regen-conf nginx --dry-run --with-diff' and if you're ok, apply the changes with 'yunohost tools regen-conf nginx --force'.", + "diagnosis_http_nginx_conf_not_up_to_date_details": "To fix the situation, inspect the difference with the command line using yunohost tools regen-conf nginx --dry-run --with-diff and if you're ok, apply the changes with yunohost tools regen-conf nginx --force.", "diagnosis_unknown_categories": "The following categories are unknown: {categories}", "diagnosis_never_ran_yet": "It looks like this server was setup recently and there's no diagnosis report to show yet. You should start by running a full diagnosis, either from the webadmin or using 'yunohost diagnosis run' from the command line.", "domain_cannot_remove_main": "You cannot remove '{domain:s}' since it's the main domain, you first need to set another domain as the main domain using 'yunohost domain main-domain -n '; here is the list of candidate domains: {other_domains:s}", From a03ee5b912dd6aaf3e6c514ee266e01cee8d3402 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 14 Apr 2020 18:47:58 +0200 Subject: [PATCH 0976/3170] Be able to restart services from the webadmin --- data/actionsmap/yunohost.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 48b1687d4..ded56a7c1 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1041,6 +1041,7 @@ service: ### service_restart() restart: action_help: Restart one or more services. If the services are not running yet, they will be started. + api: PUT /services//restart arguments: names: help: Service name to restart From 4787f0ce042f8eaace1440c95e1d3cf9dabe73dc Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 14 Apr 2020 23:48:59 +0200 Subject: [PATCH 0977/3170] Rework diagnosis of system resources --- data/hooks/diagnosis/50-systemresources.py | 78 +++++++++++++++------- locales/en.json | 20 +++--- 2 files changed, 65 insertions(+), 33 deletions(-) diff --git a/data/hooks/diagnosis/50-systemresources.py b/data/hooks/diagnosis/50-systemresources.py index 1f0c07f47..491c5b665 100644 --- a/data/hooks/diagnosis/50-systemresources.py +++ b/data/hooks/diagnosis/50-systemresources.py @@ -12,22 +12,24 @@ class SystemResourcesDiagnoser(Diagnoser): def run(self): + MB = 1024**2 + GB = 1024**2 + # # RAM # ram = psutil.virtual_memory() - ram_total_abs_MB = ram.total / (1024**2) - ram_available_abs_MB = ram.available / (1024**2) - ram_available_percent = round(100 * ram.available / ram.total) + ram_available_percent = 100 * ram.available / ram.total item = dict(meta={"test": "ram"}, - data={"total_abs_MB": ram_total_abs_MB, - "available_abs_MB": ram_available_abs_MB, - "available_percent": ram_available_percent}) - if ram_available_abs_MB < 100 or ram_available_percent < 5: + data={"total": human_size(ram.total), + "available": human_size(ram.available), + "available_percent": round_(ram_available_percent)}) + + if ram.available < 100 * MB or ram_available_percent < 5: item["status"] = "ERROR" item["summary"] = "diagnosis_ram_verylow" - elif ram_available_abs_MB < 200 or ram_available_percent < 10: + elif ram.available < 200 * MB or ram_available_percent < 10: item["status"] = "WARNING" item["summary"] = "diagnosis_ram_low" else: @@ -40,13 +42,12 @@ class SystemResourcesDiagnoser(Diagnoser): # swap = psutil.swap_memory() - swap_total_abs_MB = swap.total / (1024*1024) item = dict(meta={"test": "swap"}, - data={"total_MB": swap_total_abs_MB}) - if swap_total_abs_MB <= 0: + data={"total": human_size(swap.total)}) + if swap.total <= 1 * MB: item["status"] = "ERROR" item["summary"] = "diagnosis_swap_none" - elif swap_total_abs_MB <= 256: + elif swap.total <= 256 * MB: item["status"] = "WARNING" item["summary"] = "diagnosis_swap_notsomuch" else: @@ -67,23 +68,54 @@ class SystemResourcesDiagnoser(Diagnoser): mountpoint = disk_partition.mountpoint usage = psutil.disk_usage(mountpoint) - free_abs_GB = usage.free / (1024 ** 3) - free_percent = 100 - usage.percent + free_percent = round_(100 - usage.percent) item = dict(meta={"test": "diskusage", "mountpoint": mountpoint}, - data={"device": device, "free_abs_GB": free_abs_GB, "free_percent": free_percent}) - if free_abs_GB < 1 or free_percent < 5: - item["status"] = "ERROR" - item["summary"] = "diagnosis_diskusage_verylow" - elif free_abs_GB < 2 or free_percent < 10: - item["status"] = "WARNING" - item["summary"] = "diagnosis_diskusage_low" + data={"device": device, "total": human_size(usage.total), "free": human_size(usage.free), "free_percent": free_percent}) + + # Special checks for /boot partition because they sometimes are + # pretty small and that's kind of okay... (for example on RPi) + if mountpoint.startswith("/boot"): + if usage.free < 10 * MB or free_percent < 10: + item["status"] = "ERROR" + item["summary"] = "diagnosis_diskusage_verylow" + elif usage.free < 20 * MB or free_percent < 20: + item["status"] = "WARNING" + item["summary"] = "diagnosis_diskusage_low" + else: + item["status"] = "SUCCESS" + item["summary"] = "diagnosis_diskusage_ok" else: - item["status"] = "SUCCESS" - item["summary"] = "diagnosis_diskusage_ok" + if usage.free < 1 * GB or free_percent < 5: + item["status"] = "ERROR" + item["summary"] = "diagnosis_diskusage_verylow" + elif usage.free < 2 * GB or free_percent < 10: + item["status"] = "WARNING" + item["summary"] = "diagnosis_diskusage_low" + else: + item["status"] = "SUCCESS" + item["summary"] = "diagnosis_diskusage_ok" + yield item +def human_size(bytes_): + # Adapted from https://stackoverflow.com/a/1094933 + for unit in ['','ki','Mi','Gi','Ti','Pi','Ei','Zi']: + if abs(bytes_) < 1024.0: + return "%s %sB" % (round_(bytes_), unit) + bytes_ /= 1024.0 + return "%s %sB" % (round_(bytes_), 'Yi') + + +def round_(n): + # round_(22.124) -> 22 + # round_(9.45) -> 9.4 + n = round(n, 1) + if n > 10: + n = int(round(n)) + return n + def main(args, env, loggers): return SystemResourcesDiagnoser(args, env, loggers).diagnose() diff --git a/locales/en.json b/locales/en.json index 4b1fdaa05..71804af97 100644 --- a/locales/en.json +++ b/locales/en.json @@ -175,21 +175,21 @@ "diagnosis_services_running": "Service {service} is running!", "diagnosis_services_conf_broken": "Configuration is broken for service {service}!", "diagnosis_services_bad_status": "Service {service} is {status} :(", - "diagnosis_services_bad_status_tip": "You can try to restart the service, and if it doesn't work, have a look at the service logs using yunohost service log {service} or through the 'Services' section of the webadmin.", - "diagnosis_diskusage_verylow": "Storage {mountpoint} (on device {device}) has only {free_abs_GB} GB ({free_percent}%) space remaining. You should really consider cleaning up some space.", - "diagnosis_diskusage_low": "Storage {mountpoint} (on device {device}) has only {free_abs_GB} GB ({free_percent}%) space remaining. Be careful.", - "diagnosis_diskusage_ok": "Storage {mountpoint} (on device {device}) still has {free_abs_GB} GB ({free_percent}%) space left!", - "diagnosis_ram_verylow": "The system has only {available_abs_MB} MB ({available_percent}%) RAM left! (out of {total_abs_MB} MB)", - "diagnosis_ram_low": "The system has {available_abs_MB} MB ({available_percent}%) RAM left out of {total_abs_MB} MB. Be careful.", - "diagnosis_ram_ok": "The system still has {available_abs_MB} MB ({available_percent}%) RAM left out of {total_abs_MB} MB.", + "diagnosis_services_bad_status_tip": "You can try to restart the service, and if it doesn't work, have a look at the service logs in the webadmin (from the command line, you can do this with yunohost service restart {service} and yunohost service log {service}).", + "diagnosis_diskusage_verylow": "Storage {mountpoint} (on device {device}) has only {free} ({free_percent}%) space remaining (out of {total}). You should really consider cleaning up some space!", + "diagnosis_diskusage_low": "Storage {mountpoint} (on device {device}) has only {free} ({free_percent}%) space remaining (out of {total}). Be careful.", + "diagnosis_diskusage_ok": "Storage {mountpoint} (on device {device}) still has {free} ({free_percent}%) space left (out of {total})!", + "diagnosis_ram_verylow": "The system has only {available} ({available_percent}%) RAM available! (out of {total})", + "diagnosis_ram_low": "The system has {available} ({available_percent}%) RAM available (out of {total}). Be careful.", + "diagnosis_ram_ok": "The system still has {available} ({available_percent}%) RAM available out of {total}.", "diagnosis_swap_none": "The system has no swap at all. You should consider adding at least 256 MB of swap to avoid situations where the system runs out of memory.", - "diagnosis_swap_notsomuch": "The system has only {total_MB} MB swap. You should consider having at least 256 MB to avoid situations where the system runs out of memory.", - "diagnosis_swap_ok": "The system has {total_MB} MB of swap!", + "diagnosis_swap_notsomuch": "The system has only {total} swap. You should consider having at least 256 MB to avoid situations where the system runs out of memory.", + "diagnosis_swap_ok": "The system has {total} of swap!", "diagnosis_mail_ougoing_port_25_ok": "Outgoing port 25 is not blocked and email can be sent to other servers.", "diagnosis_mail_ougoing_port_25_blocked": "Outgoing port 25 appears to be blocked. You should try to unblock it in your internet service provider (or hosting provider) configuration panel. Meanwhile, the server won't be able to send emails to other servers.", "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! Though YunoHost will stop updating this file automatically, beware that YunoHost upgrades may contain important recommended changes. You can inspect the difference 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_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.", From a85c15dd0bf58d8bc1a75fb2adac61db7a6a9cca Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 15 Apr 2020 01:07:40 +0200 Subject: [PATCH 0978/3170] Update data/hooks/diagnosis/50-systemresources.py Co-Authored-By: Kayou --- 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 491c5b665..b4e50ccf1 100644 --- a/data/hooks/diagnosis/50-systemresources.py +++ b/data/hooks/diagnosis/50-systemresources.py @@ -13,7 +13,7 @@ class SystemResourcesDiagnoser(Diagnoser): def run(self): MB = 1024**2 - GB = 1024**2 + GB = MB*1024 # # RAM From 8e46b536dc9089cd2db934354dacca497036c926 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 15 Apr 2020 03:48:14 +0200 Subject: [PATCH 0979/3170] Somewhat cleaner hack to check the status of those damn services that aren't the real services... --- data/templates/yunohost/services.yml | 4 +- src/yunohost/service.py | 55 +++++++++++++++------------- 2 files changed, 32 insertions(+), 27 deletions(-) diff --git a/data/templates/yunohost/services.yml b/data/templates/yunohost/services.yml index fdf278fcf..e1dd57e55 100644 --- a/data/templates/yunohost/services.yml +++ b/data/templates/yunohost/services.yml @@ -13,7 +13,7 @@ metronome: category: xmpp mysql: log: [/var/log/mysql.log,/var/log/mysql.err,/var/log/mysql/error.log] - alternates: ['mariadb'] + actual_systemd_service: mariadb category: database nginx: log: /var/log/nginx @@ -27,7 +27,7 @@ php7.0-fpm: category: web postfix: log: [/var/log/mail.log,/var/log/mail.err] - test_status: systemctl show postfix@- | grep -q "^SubState=running" + actual_systemd_service: postfix@- needs_exposed_ports: [25, 587] category: email redis-server: diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 748037df6..b6c93b5ae 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -80,7 +80,7 @@ def service_add(name, description=None, log=None, log_type="file", test_status=N services[name]['description'] = description else: # Try to get the description from systemd service - out = subprocess.check_output("systemctl show %s | grep '^Description='" % name, shell=True) + out = subprocess.check_output("systemctl show %s | grep '^Description='" % name, shell=True).strip() out = out.replace("Description=", "") # 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 @@ -295,16 +295,11 @@ def service_status(names=[]): if services[name].get("status", "") is None: continue - status = _get_service_information_from_systemd(name) - - # try to get status using alternative version if they exists - # this is for mariadb/mysql but is generic in case of - alternates = services[name].get("alternates", []) - while status is None and alternates: - status = _get_service_information_from_systemd(alternates.pop()) + systemd_service = services[name].get("actual_systemd_service", name) + status = _get_service_information_from_systemd(systemd_service) if status is None: - logger.error("Failed to get status information via dbus for service %s, systemctl didn't recognize this service ('NoSuchUnit')." % name) + 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", @@ -338,6 +333,8 @@ def service_status(names=[]): # 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 status: result[name]['last_state_change'] = datetime.utcfromtimestamp(status["StateChangeTimestamp"] / 1000000) @@ -408,6 +405,7 @@ def service_log(name, number=50): """ services = _get_services() + number = int(number) if name not in services.keys(): raise YunohostError('service_unknown', service=name) @@ -423,11 +421,7 @@ def service_log(name, number=50): result = {} # First we always add the logs from journalctl / systemd - result["journalctl"] = _get_journalctl_logs(name, int(number)).splitlines() - - # Mysql and journalctl are fucking annoying, we gotta explictly fetch mariadb ... - if name == "mysql": - result["journalctl"] = _get_journalctl_logs("mariadb", int(number)).splitlines() + result["journalctl"] = _get_journalctl_logs(name, number).splitlines() for index, log_path in enumerate(log_list): log_type = log_type_list[index] @@ -435,7 +429,7 @@ def service_log(name, number=50): if log_type == "file": # log is a file, read it if not os.path.isdir(log_path): - result[log_path] = _tail(log_path, int(number)) if os.path.exists(log_path) else [] + result[log_path] = _tail(log_path, number) if os.path.exists(log_path) else [] continue for log_file in os.listdir(log_path): @@ -447,10 +441,11 @@ def service_log(name, number=50): if not log_file.endswith(".log"): continue - result[log_file_path] = _tail(log_file_path, int(number)) if os.path.exists(log_file_path) else [] + 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, int(number)).splitlines() + result[log_path] = _get_journalctl_logs(log_path, number).splitlines() return result @@ -572,14 +567,22 @@ def _get_services(): services = yaml.load(f) except: return {} - else: - # some services are marked as None to remove them from YunoHost - # filter this - for key, value in services.items(): - if value is None: - del services[key] - return services + # some services are marked as None to remove them from YunoHost + # filter this + for key, value in services.items(): + if value is None: + del services[key] + + # Stupid hack for postgresql which ain't an official service ... Can't + # really inject that info otherwise. Real service we want to check for + # status and log is in fact postgresql@x.y-main (x.y being the version) + if "postgresql" in services: + if "description" in services["postgresql"]: + del services["postgresql"]["description"] + services["postgresql"]["actual_systemd_service"] = "postgresql@9.6-main" + + return services def _save_services(services): @@ -674,8 +677,10 @@ def _find_previous_log_file(file): 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(service, number), shell=True) + return subprocess.check_output("journalctl -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 7f3cc334873d693e13667de7b6ae3d34eca0217f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 16 Apr 2020 02:51:29 +0200 Subject: [PATCH 0980/3170] Add a static method to call remote diagnosis and supports ipv4-only or ipv6-only check --- data/hooks/diagnosis/14-ports.py | 20 ++----- data/hooks/diagnosis/21-web.py | 98 +++++++++++++++++++------------- src/yunohost/diagnosis.py | 46 +++++++++++++++ 3 files changed, 110 insertions(+), 54 deletions(-) diff --git a/data/hooks/diagnosis/14-ports.py b/data/hooks/diagnosis/14-ports.py index f973a3275..05c28e8dc 100644 --- a/data/hooks/diagnosis/14-ports.py +++ b/data/hooks/diagnosis/14-ports.py @@ -1,7 +1,6 @@ #!/usr/bin/env python import os -import requests from yunohost.diagnosis import Diagnoser from yunohost.utils.error import YunohostError @@ -27,25 +26,16 @@ class PortsDiagnoser(Diagnoser): ports[port] = service try: - r = requests.post('https://diagnosis.yunohost.org/check-ports', json={'ports': ports.keys()}, timeout=30) - if r.status_code not in [200, 400, 418]: - raise Exception("Bad response from the server https://diagnosis.yunohost.org/check-ports : %s - %s" % (str(r.status_code), r.content)) - r = r.json() - if "status" not in r.keys(): - raise Exception("Bad syntax for response ? Raw json: %s" % str(r)) - elif r["status"] == "error": - if "content" in r.keys(): - raise Exception(r["content"]) - else: - raise Exception("Bad syntax for response ? Raw json: %s" % str(r)) - elif r["status"] != "ok" or "ports" not in r.keys() or not isinstance(r["ports"], dict): - raise Exception("Bad syntax for response ? Raw json: %s" % str(r)) + r = Diagnoser.remote_diagnosis('check-ports', + data={'ports': ports.keys()}, + ipversion=4) + results = r["ports"] except Exception as e: raise YunohostError("diagnosis_ports_could_not_diagnose", error=e) for port, service in sorted(ports.items()): category = services[service].get("category", "[?]") - if r["ports"].get(str(port), None) is not True: + if results.get(str(port), None) is not True: yield dict(meta={"port": str(port)}, data={"service": service, "category": category}, status="ERROR", diff --git a/data/hooks/diagnosis/21-web.py b/data/hooks/diagnosis/21-web.py index 2d0344abe..270c566cc 100644 --- a/data/hooks/diagnosis/21-web.py +++ b/data/hooks/diagnosis/21-web.py @@ -4,10 +4,14 @@ import os import random import requests +from moulinette.utils.filesystem import read_file + from yunohost.diagnosis import Diagnoser from yunohost.domain import domain_list from yunohost.utils.error import YunohostError +DIAGNOSIS_SERVER = "diagnosis.yunohost.org" + class WebDiagnoser(Diagnoser): @@ -17,52 +21,42 @@ class WebDiagnoser(Diagnoser): def run(self): - nonce_digits = "0123456789abcedf" - - at_least_one_domain_ok = False all_domains = domain_list()["domains"] + domains_to_check = [] for domain in all_domains: # If the diagnosis location ain't defined, can't do diagnosis, # probably because nginx conf manually modified... nginx_conf = "/etc/nginx/conf.d/%s.conf" % domain - if os.system("grep -q '^.*location .*/.well-known/ynh-diagnosis/' %s" % nginx_conf) != 0: + if ".well-known/ynh-diagnosis/" not in read_file(nginx_conf): yield dict(meta={"domain": domain}, status="WARNING", summary="diagnosis_http_nginx_conf_not_up_to_date", details=["diagnosis_http_nginx_conf_not_up_to_date_details"]) - - nonce = ''.join(random.choice(nonce_digits) for i in range(16)) - os.system("rm -rf /tmp/.well-known/ynh-diagnosis/") - os.system("mkdir -p /tmp/.well-known/ynh-diagnosis/") - os.system("touch /tmp/.well-known/ynh-diagnosis/%s" % nonce) - - try: - r = requests.post('https://diagnosis.yunohost.org/check-http', json={'domain': domain, "nonce": nonce}, timeout=30) - if r.status_code not in [200, 400, 418]: - raise Exception("Bad response from the server https://diagnosis.yunohost.org/check-http : %s - %s" % (str(r.status_code), r.content)) - r = r.json() - if "status" not in r.keys(): - raise Exception("Bad syntax for response ? Raw json: %s" % str(r)) - elif r["status"] == "error" and ("code" not in r.keys() or not r["code"].startswith("error_http_check_")): - if "content" in r.keys(): - raise Exception(r["content"]) - else: - raise Exception("Bad syntax for response ? Raw json: %s" % str(r)) - except Exception as e: - raise YunohostError("diagnosis_http_could_not_diagnose", error=e) - - if r["status"] == "ok": - at_least_one_domain_ok = True - yield dict(meta={"domain": domain}, - status="SUCCESS", - summary="diagnosis_http_ok") else: - detail = r["code"].replace("error_http_check", "diagnosis_http") if "code" in r else "diagnosis_http_unknown_error" - yield dict(meta={"domain": domain}, - status="ERROR", - summary="diagnosis_http_unreachable", - details=[detail]) + domains_to_check.append(domain) + + self.nonce = ''.join(random.choice("0123456789abcedf") for i in range(16)) + os.system("rm -rf /tmp/.well-known/ynh-diagnosis/") + os.system("mkdir -p /tmp/.well-known/ynh-diagnosis/") + os.system("touch /tmp/.well-known/ynh-diagnosis/%s" % self.nonce) + + if not domains_to_check: + return + + # To perform hairpinning test, we gotta make sure that port forwarding + # is working and therefore we'll do it only if at least one ipv4 domain + # works. + self.do_hairpinning_test = False + ipv4 = Diagnoser.get_cached_report("ip", item={"test": "ipv4"}) or {} + if ipv4.get("status") == "SUCCESS": + for item in self.test_http(domains_to_check, ipversion=4): + yield item + + ipv6 = Diagnoser.get_cached_report("ip", item={"test": "ipv6"}) or {} + if ipv6.get("status") == "SUCCESS": + for item in self.test_http(domains_to_check, ipversion=6): + yield item # If at least one domain is correctly exposed to the outside, # attempt to diagnose hairpinning situations. On network with @@ -70,13 +64,12 @@ class WebDiagnoser(Diagnoser): # outside, but from the outside, it will be as if the port forwarding # was not configured... Hence, calling for example # "curl --head the.global.ip" will simply timeout... - if at_least_one_domain_ok: - ipv4 = Diagnoser.get_cached_report_item("ip", {"test": "ipv4"}) - global_ipv4 = ipv4.get("data", {}).get("global", {}) + if self.do_hairpinning_test: + global_ipv4 = ipv4.get("data", {}).get("global", None) if global_ipv4: try: requests.head("http://" + global_ipv4, timeout=5) - except requests.exceptions.Timeout as e: + except requests.exceptions.Timeout: yield dict(meta={"test": "hairpinning"}, status="WARNING", summary="diagnosis_http_hairpinning_issue", @@ -87,6 +80,33 @@ class WebDiagnoser(Diagnoser): # issue but something else super weird ... pass + def test_http(self, domains, ipversion): + + try: + r = Diagnoser.remote_diagnosis('check-http', + data={'domains': domains, + "nonce": self.nonce}, + ipversion=ipversion) + results = r["http"] + except Exception as e: + raise YunohostError("diagnosis_http_could_not_diagnose", error=e) + + assert set(results.keys()) == set(domains) + + for domain, result in results.items(): + + if result["status"] == "ok": + if ipversion == 4: + self.do_hairpinning_test = True + yield dict(meta={"domain": domain}, + status="SUCCESS", + summary="diagnosis_http_ok") + else: + yield dict(meta={"domain": domain}, + status="ERROR", + summary="diagnosis_http_unreachable", + details=[result["status"].replace("error_http_check", "diagnosis_http")]) + def main(args, env, loggers): return WebDiagnoser(args, env, loggers).diagnose() diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index c11cde566..fd7a37480 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -27,6 +27,8 @@ import re import os import time +import requests +import socket from moulinette import m18n, msettings from moulinette.utils import log @@ -39,6 +41,7 @@ logger = log.getActionLogger('yunohost.diagnosis') 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()] @@ -492,6 +495,49 @@ class Diagnoser(): if "details" in item: item["details"] = [m18n_(info) for info in item["details"]] + @staticmethod + def remote_diagnosis(uri, data, ipversion, timeout=30): + + # Monkey patch socket.getaddrinfo to force request() to happen in ipv4 + # or 6 ... + # Inspired by https://stackoverflow.com/a/50044152 + old_getaddrinfo = socket.getaddrinfo + + def getaddrinfo_ipv4_only(*args, **kwargs): + responses = old_getaddrinfo(*args, **kwargs) + return [response + for response in responses + if response[0] == socket.AF_INET] + + def getaddrinfo_ipv6_only(*args, **kwargs): + responses = old_getaddrinfo(*args, **kwargs) + return [response + for response in responses + if response[0] == socket.AF_INET6] + + if ipversion == 4: + socket.getaddrinfo = getaddrinfo_ipv4_only + elif ipversion == 6: + socket.getaddrinfo = getaddrinfo_ipv6_only + + url = 'https://%s/%s' % (DIAGNOSIS_SERVER, uri) + try: + r = requests.post(url, json=data, timeout=timeout) + finally: + socket.getaddrinfo = old_getaddrinfo + + if r.status_code not in [200, 400]: + raise Exception("Bad response from diagnosis server.\nURL: %s\nStatus code: %s\nMessage: %s" % (url, r.status_code, r.content)) + if r.status_code == 400: + raise Exception("Diagnosis request was refused: %s" % r.content) + + try: + r = r.json() + except Exception as e: + raise Exception("Failed to parse json from diagnosis server response.\nError: %s\nOriginal content: %s" % (e, r.content)) + + return r + def _list_diagnosis_categories(): hooks_raw = hook_list("diagnosis", list_by="priority", show_info=True)["hooks"] From e8730ad92b4abe2f27e22b090b1693773d3d1eec Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 16 Apr 2020 03:21:01 +0200 Subject: [PATCH 0981/3170] Correctly handle cases where domain works in IPv4 but not IPv6 or viceversa --- data/hooks/diagnosis/21-web.py | 57 ++++++++++++++++++++++------------ locales/en.json | 1 + 2 files changed, 38 insertions(+), 20 deletions(-) diff --git a/data/hooks/diagnosis/21-web.py b/data/hooks/diagnosis/21-web.py index 270c566cc..c54544aa0 100644 --- a/data/hooks/diagnosis/21-web.py +++ b/data/hooks/diagnosis/21-web.py @@ -48,15 +48,20 @@ class WebDiagnoser(Diagnoser): # is working and therefore we'll do it only if at least one ipv4 domain # works. self.do_hairpinning_test = False + + ipversions = [] ipv4 = Diagnoser.get_cached_report("ip", item={"test": "ipv4"}) or {} if ipv4.get("status") == "SUCCESS": - for item in self.test_http(domains_to_check, ipversion=4): - yield item + ipversions.append(4) + # To be discussed: we could also make this check dependent on the + # existence of an AAAA record... ipv6 = Diagnoser.get_cached_report("ip", item={"test": "ipv6"}) or {} if ipv6.get("status") == "SUCCESS": - for item in self.test_http(domains_to_check, ipversion=6): - yield item + ipversions.append(6) + + for item in self.test_http(domains_to_check, ipversions): + yield item # If at least one domain is correctly exposed to the outside, # attempt to diagnose hairpinning situations. On network with @@ -80,32 +85,44 @@ class WebDiagnoser(Diagnoser): # issue but something else super weird ... pass - def test_http(self, domains, ipversion): + def test_http(self, domains, ipversions): - try: - r = Diagnoser.remote_diagnosis('check-http', - data={'domains': domains, - "nonce": self.nonce}, - ipversion=ipversion) - results = r["http"] - except Exception as e: - raise YunohostError("diagnosis_http_could_not_diagnose", error=e) + results = {} + for ipversion in ipversions: + try: + r = Diagnoser.remote_diagnosis('check-http', + data={'domains': domains, + "nonce": self.nonce}, + ipversion=ipversion) + results[ipversion] = r["http"] + except Exception as e: + raise YunohostError("diagnosis_http_could_not_diagnose", error=e) - assert set(results.keys()) == set(domains) + for domain in domains: - for domain, result in results.items(): - - if result["status"] == "ok": - if ipversion == 4: + # If both IPv4 and IPv6 (if applicable) are good + if all(results[ipversion][domain]["status"] == "ok" for ipversion in ipversions): + if 4 in ipversions: self.do_hairpinning_test = True yield dict(meta={"domain": domain}, status="SUCCESS", summary="diagnosis_http_ok") - else: + # If both IPv4 and IPv6 (if applicable) are failed + elif all(results[ipversion][domain]["status"] != "ok" for ipversion in ipversions): + detail = results[4 if 4 in ipversions else 6][domain]["status"] yield dict(meta={"domain": domain}, status="ERROR", summary="diagnosis_http_unreachable", - details=[result["status"].replace("error_http_check", "diagnosis_http")]) + details=[detail.replace("error_http_check", "diagnosis_http")]) + # If only IPv4 is failed or only IPv6 is failed (if applicable) + else: + passed, failed = (4, 6) if results[4][domain]["status"] == "ok" else (6, 4) + detail = results[failed][domain]["status"] + yield dict(meta={"domain": domain}, + data={"passed": passed, "failed": failed}, + status="ERROR", + summary="diagnosis_http_partially_unreachable", + details=[detail.replace("error_http_check", "diagnosis_http")]) def main(args, env, loggers): diff --git a/locales/en.json b/locales/en.json index 71804af97..96a1f4658 100644 --- a/locales/en.json +++ b/locales/en.json @@ -217,6 +217,7 @@ "diagnosis_http_unknown_error": "An error happened while trying to reach your domain, it's very likely unreachable.", "diagnosis_http_bad_status_code": "Timed-out while trying to contact your server from outside. It might be that another machine answered instead of your server.
1. The most common cause for this issue is that you did not correctly configure port forwarding for port 80.
2. On more complex setups: make sure that a firewall or reverse-proxy is not interfering.", "diagnosis_http_unreachable": "Domain {domain} appears unreachable through HTTP from outside the local network.", + "diagnosis_http_partiallu_unreachable": "Domain {domain} appears unreachable through HTTP from outside the local network in IPv{failed}, though it works in IPv{passed}.", "diagnosis_http_nginx_conf_not_up_to_date": "This domain's nginx configuration appears to have been modified manually, and prevents YunoHost from diagnosing if it's reachable on HTTP.", "diagnosis_http_nginx_conf_not_up_to_date_details": "To fix the situation, inspect the difference with the command line using yunohost tools regen-conf nginx --dry-run --with-diff and if you're ok, apply the changes with yunohost tools regen-conf nginx --force.", "diagnosis_unknown_categories": "The following categories are unknown: {categories}", From 1552c6472bbd375071edb146c4be91ecd2b0fd06 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 16 Apr 2020 18:57:52 +0200 Subject: [PATCH 0982/3170] Try to improve wording of DNS reports --- locales/en.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/en.json b/locales/en.json index 96a1f4658..cf1b8d552 100644 --- a/locales/en.json +++ b/locales/en.json @@ -167,8 +167,8 @@ "diagnosis_ip_broken_resolvconf": "Domain name resolution seems to be broken on your server, which seems related to /etc/resolv.conf not pointing to 127.0.0.1.", "diagnosis_ip_weird_resolvconf": "DNS resolution seems to be working, but it looks like you're using a custom /etc/resolv.conf.", "diagnosis_ip_weird_resolvconf_details": "The file /etc/resolv.conf should be a symlink to /etc/resolvconf/run/resolv.conf itself pointing to 127.0.0.1 (dnsmasq). If you want to manually configure DNS resolvers, please edit /etc/resolv.dnsmasq.conf.", - "diagnosis_dns_good_conf": "Good DNS configuration for domain {domain} (category {category})", - "diagnosis_dns_bad_conf": "Bad or missing DNS configuration for domain {domain} (category {category})", + "diagnosis_dns_good_conf": "DNS records are correctly configured for domain {domain} (category {category})", + "diagnosis_dns_bad_conf": "Some DNS records are missing or incorrect for domain {domain} (category {category})", "diagnosis_dns_missing_record": "According to the recommended DNS configuration, you should add a DNS record with the following info.
Type: {type}
Name: {name}
Value: {value}", "diagnosis_dns_discrepancy": "The following DNS record does not seem to follow the recommended configuration:
Type: {type}
Name: {name}
Current value: {current}
Excepted value: {value}", "diagnosis_dns_point_to_doc": "Please check the documentation at https://yunohost.org/dns_config if you need help about configuring DNS records.", From 4e64e2ccfda6374b6c8e3eb1412afa7855f30c2f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 16 Apr 2020 19:03:44 +0200 Subject: [PATCH 0983/3170] Save a per-record result for DNS diagnosis and report missing AAAA as warning only --- data/hooks/diagnosis/12-dnsrecords.py | 29 +++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py index 3132cf45f..c4996de38 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -40,9 +40,8 @@ class DNSRecordsDiagnoser(Diagnoser): expected_configuration = _build_dns_conf(domain, include_empty_AAAA_if_no_ipv6=True) - # FIXME: Here if there are no AAAA record, we should add something to expect "no" AAAA record - # to properly diagnose situations where people have a AAAA record but no IPv6 categories = ["basic", "mail", "xmpp", "extra"] + # For subdomains, we only diagnosis A and AAAA records if is_subdomain: categories = ["basic"] @@ -50,26 +49,48 @@ class DNSRecordsDiagnoser(Diagnoser): records = expected_configuration[category] discrepancies = [] + results = {} for r in records: + id_ = r["type"] + ":" + r["name"] r["current"] = self.get_current_record(domain, r["name"], r["type"]) if r["value"] == "@": r["value"] = domain + "." - if not self.current_record_match_expected(r): + if self.current_record_match_expected(r): + results[id_] = "OK" + else: if r["current"] is None: + results[id_] = "MISSING" discrepancies.append(("diagnosis_dns_missing_record", r)) else: + results[id_] = "WRONG" discrepancies.append(("diagnosis_dns_discrepancy", r)) + + def its_important(): + # Every mail DNS records are important for main domain + # For other domain, we only report it as a warning for now... + if is_main_domain and category == "mail": + return True + elif category == "basic": + # A bad or missing A record is critical ... + # And so is a wrong AAAA record + # (However, a missing AAAA record is acceptable) + if results["A:@"] != "OK" or results["AAAA:@"] == "WRONG": + return True + + return False + if discrepancies: - status = "ERROR" if (category == "basic" or (is_main_domain and category == "mail")) else "WARNING" + status = "ERROR" if its_important() else "WARNING" summary = "diagnosis_dns_bad_conf" else: status = "SUCCESS" summary = "diagnosis_dns_good_conf" output = dict(meta={"domain": domain, "category": category}, + data=results, status=status, summary=summary) From be0da3b9dcd10bbb528ff674d8536a787c571168 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 16 Apr 2020 20:48:25 +0200 Subject: [PATCH 0984/3170] Only report an INFO is domain ain't accessible in IPv6 and there's in fact no AAAA record set yet --- data/hooks/diagnosis/21-web.py | 40 +++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/data/hooks/diagnosis/21-web.py b/data/hooks/diagnosis/21-web.py index c54544aa0..10deea28d 100644 --- a/data/hooks/diagnosis/21-web.py +++ b/data/hooks/diagnosis/21-web.py @@ -17,7 +17,7 @@ class WebDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] cache_duration = 3600 - dependencies = ["ip"] + dependencies = ["ip", "dnsrecords"] def run(self): @@ -118,11 +118,39 @@ class WebDiagnoser(Diagnoser): else: passed, failed = (4, 6) if results[4][domain]["status"] == "ok" else (6, 4) detail = results[failed][domain]["status"] - yield dict(meta={"domain": domain}, - data={"passed": passed, "failed": failed}, - status="ERROR", - summary="diagnosis_http_partially_unreachable", - details=[detail.replace("error_http_check", "diagnosis_http")]) + + # Failing in ipv4 is critical. + # If we failed in IPv6 but there's in fact no AAAA record + # It's an acceptable situation and we shall not report an + # error + def ipv6_is_important_for_this_domain(): + dnsrecords = Diagnoser.get_cached_report("dnsrecords", item={"domain": domain, "category": "basic"}) or {} + AAAA_status = dnsrecords.get("data", {}).get("AAAA:@") + + return AAAA_status in ["OK", "WRONG"] + + if failed == 4 or ipv6_is_important_for_this_domain(): + yield dict(meta={"domain": domain}, + data={"passed": passed, "failed": failed}, + status="ERROR", + summary="diagnosis_http_partially_unreachable", + details=[detail.replace("error_http_check", "diagnosis_http")]) + # So otherwise we report a success (note that this info is + # later used to know that ACME challenge is doable) + # + # And in addition we report an info about the failure in IPv6 + # *with a different meta* (important to avoid conflicts when + # fetching the other info...) + else: + self.do_hairpinning_test = True + yield dict(meta={"domain": domain}, + status="SUCCESS", + summary="diagnosis_http_ok") + yield dict(meta={"test": "ipv6", "domain": domain}, + data={"passed": passed, "failed": failed}, + status="INFO", + summary="diagnosis_http_partially_unreachable", + details=[detail.replace("error_http_check", "diagnosis_http")]) def main(args, env, loggers): From dd7b42d3e8c2878da2bb67e6a7ba442f8d8149ff Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 16 Apr 2020 23:38:05 +0200 Subject: [PATCH 0985/3170] Add ipv6 check for ports --- data/hooks/diagnosis/14-ports.py | 87 ++++++++++++++++++++++++++------ 1 file changed, 71 insertions(+), 16 deletions(-) diff --git a/data/hooks/diagnosis/14-ports.py b/data/hooks/diagnosis/14-ports.py index 05c28e8dc..809407be3 100644 --- a/data/hooks/diagnosis/14-ports.py +++ b/data/hooks/diagnosis/14-ports.py @@ -10,10 +10,12 @@ class PortsDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] cache_duration = 3600 - dependencies = ["ip"] + dependencies = ["ip", "dnsrecords"] def run(self): + # TODO: report a warning if port 53 or 5353 is exposed to the outside world... + # This dict is something like : # { 80: "nginx", # 25: "postfix", @@ -25,28 +27,81 @@ class PortsDiagnoser(Diagnoser): for port in infos.get("needs_exposed_ports", []): ports[port] = service - try: - r = Diagnoser.remote_diagnosis('check-ports', - data={'ports': ports.keys()}, - ipversion=4) - results = r["ports"] - except Exception as e: - raise YunohostError("diagnosis_ports_could_not_diagnose", error=e) + ipversions = [] + ipv4 = Diagnoser.get_cached_report("ip", item={"test": "ipv4"}) or {} + if ipv4.get("status") == "SUCCESS": + ipversions.append(4) + + # To be discussed: we could also make this check dependent on the + # existence of an AAAA record... + ipv6 = Diagnoser.get_cached_report("ip", item={"test": "ipv6"}) or {} + if ipv6.get("status") == "SUCCESS": + ipversions.append(6) + + # Fetch test result for each relevant IP version + results = {} + for ipversion in ipversions: + try: + r = Diagnoser.remote_diagnosis('check-ports', + data={'ports': ports.keys()}, + ipversion=ipversion) + results[ipversion] = r["ports"] + except Exception as e: + raise YunohostError("diagnosis_http_could_not_diagnose", error=e) + for port, service in sorted(ports.items()): + port = str(port) category = services[service].get("category", "[?]") - if results.get(str(port), None) is not True: - yield dict(meta={"port": str(port)}, - data={"service": service, "category": category}, - status="ERROR", - summary="diagnosis_ports_unreachable", - details=["diagnosis_ports_needed_by", "diagnosis_ports_forwarding_tip"]) - else: - yield dict(meta={"port": str(port)}, + + # If both IPv4 and IPv6 (if applicable) are good + if all(results[ipversion].get(port) is True for ipversion in ipversions): + yield dict(meta={"port": port}, data={"service": service, "category": category}, status="SUCCESS", summary="diagnosis_ports_ok", details=["diagnosis_ports_needed_by"]) + # If both IPv4 and IPv6 (if applicable) are failed + elif all(results[ipversion].get(port) is not True for ipversion in ipversions): + yield dict(meta={"port": port}, + data={"service": service, "category": category}, + status="ERROR", + summary="diagnosis_ports_unreachable", + details=["diagnosis_ports_needed_by", "diagnosis_ports_forwarding_tip"]) + # If only IPv4 is failed or only IPv6 is failed (if applicable) + else: + passed, failed = (4, 6) if results[4].get(port) is True else (6, 4) + + # Failing in ipv4 is critical. + # If we failed in IPv6 but there's in fact no AAAA record + # It's an acceptable situation and we shall not report an + # error + # If any AAAA record is set, IPv6 is important... + def ipv6_is_important(): + dnsrecords = Diagnoser.get_cached_report("dnsrecords") or {} + return any(record["data"]["AAAA:@"] in ["OK", "WRONG"] for record in dnsrecords.get("items", [])) + + if failed == 4 or ipv6_is_important(): + yield dict(meta={"port": port}, + data={"service": service, "category": category, "passed": passed, "failed": failed}, + status="ERROR", + summary="diagnosis_ports_partially_unreachable", + details=["diagnosis_ports_needed_by", "diagnosis_ports_forwarding_tip"]) + # So otherwise we report a success + # And in addition we report an info about the failure in IPv6 + # *with a different meta* (important to avoid conflicts when + # fetching the other info...) + else: + yield dict(meta={"port": port}, + data={"service": service, "category": category}, + status="SUCCESS", + summary="diagnosis_ports_ok", + details=["diagnosis_ports_needed_by"]) + yield dict(meta={"test": "ipv6", "port": port}, + data={"service": service, "category": category, "passed": passed, "failed": failed}, + status="INFO", + summary="diagnosis_ports_partially_unreachable", + details=["diagnosis_ports_needed_by", "diagnosis_ports_forwarding_tip"]) def main(args, env, loggers): From 4306db7cf1a8ee89ee7a3090f53ea4cb81d213c4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 16 Apr 2020 23:59:42 +0200 Subject: [PATCH 0986/3170] Be more flexible about SPF record: just 'a mx -all' is enough, no need to specify IP. (also fix TXT record validation in diagnosis) --- data/hooks/diagnosis/12-dnsrecords.py | 8 ++++++-- src/yunohost/domain.py | 9 +-------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py index c4996de38..3853350bd 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -127,8 +127,12 @@ class DNSRecordsDiagnoser(Diagnoser): # Split expected/current # from "v=DKIM1; k=rsa; p=hugekey;" # to a set like {'v=DKIM1', 'k=rsa', 'p=...'} - expected = set(r["value"].strip(' "').strip(";").replace(" ", "").split()) - current = set(r["current"].strip(' "').strip(";").replace(" ", "").split()) + expected = set(r["value"].strip(';" ').replace(";", " ").split()) + current = set(r["current"].strip(';" ').replace(";", " ").split()) + + # For SPF, ignore parts starting by ip4: or ip6: + if r["name"] == "@": + current = {part for part in current if not part.startswith("ip4:") and not part.startswith("ip6:")} return expected == current elif r["type"] == "MX": # For MX, we want to ignore the priority diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 7910147a3..1d1e10da1 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -455,16 +455,9 @@ def _build_dns_conf(domain, ttl=3600, include_empty_AAAA_if_no_ipv6=False): # Email # ######### - spf_record = '"v=spf1 a mx' - if ipv4: - spf_record += ' ip4:{ip4}'.format(ip4=ipv4) - if ipv6: - spf_record += ' ip6:{ip6}'.format(ip6=ipv6) - spf_record += ' -all"' - mail = [ ["@", ttl, "MX", "10 %s." % domain], - ["@", ttl, "TXT", spf_record], + ["@", ttl, "TXT", "v=spf1 a mx -all"], ] # DKIM/DMARC record From 2c269613142032cbca8bb0a2376c5614876b8da6 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 17 Apr 2020 02:17:15 +0200 Subject: [PATCH 0987/3170] Fix bad copy pasta for string key --- data/hooks/diagnosis/14-ports.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/hooks/diagnosis/14-ports.py b/data/hooks/diagnosis/14-ports.py index 809407be3..b63971b71 100644 --- a/data/hooks/diagnosis/14-ports.py +++ b/data/hooks/diagnosis/14-ports.py @@ -47,7 +47,7 @@ class PortsDiagnoser(Diagnoser): ipversion=ipversion) results[ipversion] = r["ports"] except Exception as e: - raise YunohostError("diagnosis_http_could_not_diagnose", error=e) + raise YunohostError("diagnosis_ports_could_not_diagnose", error=e) for port, service in sorted(ports.items()): From 040bc1d09fd5c6ba89ab87ccfaeb99d954b32265 Mon Sep 17 00:00:00 2001 From: Kayou Date: Fri, 17 Apr 2020 03:20:02 +0200 Subject: [PATCH 0988/3170] Yolo fix locales (#936) * Yolo fix locales * Fix bad copy pasta for string key * Yolo fix locales * Add diagnosis_ports_could_not_diagnose back Co-authored-by: Alexandre Aubin --- locales/ca.json | 17 ++++++++--------- locales/en.json | 4 ++-- locales/eo.json | 17 ++++++++--------- locales/es.json | 17 ++++++++--------- locales/fr.json | 17 ++++++++--------- locales/oc.json | 17 ++++++++--------- 6 files changed, 42 insertions(+), 47 deletions(-) diff --git a/locales/ca.json b/locales/ca.json index 4c31e4a6c..0ea0d91f6 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -502,7 +502,7 @@ "permission_require_account": "El permís {permission} només té sentit per als usuaris que tenen un compte, i per tant no es pot activar per als visitants.", "app_remove_after_failed_install": "Eliminant l'aplicació després que hagi fallat la instal·lació…", "diagnosis_basesystem_ynh_main_version": "El servidor funciona amb YunoHost {main_version} ({repo})", - "diagnosis_ram_low": "El sistema només té {available_abs_MB} MB ({available_percent}%) de memòria RAM disponibles d'un total de {total_abs_MB} MB. Aneu amb compte.", + "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 256 MB 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_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ó.", @@ -538,13 +538,13 @@ "diagnosis_dns_missing_record": "Segons la configuració DNS recomanada, hauríeu d'afegir un registre DNS\ntipus: {type}\nnom: {name}\nvalor: {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_abs_GB} GB ({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_abs_GB} GB ({free_percent}%). Aneu amb compte.", - "diagnosis_diskusage_ok": "El lloc d'emmagatzematge {mountpoint} (en l'aparell {device}) encara té {free_abs_GB} GB ({free_percent}%) lliures!", - "diagnosis_ram_verylow": "El sistema només té {available_abs_MB} MB ({available_percent}%) de memòria RAM disponibles! (d'un total de {total_abs_MB} MB)", - "diagnosis_ram_ok": "El sistema encara té {available_abs_MB} MB ({available_percent}%) de memòria RAM disponibles d'un total de {total_abs_MB} MB.", - "diagnosis_swap_notsomuch": "El sistema només té {total_MB} MB de swap. Hauríeu de considerar tenir un mínim de 256 MB per evitar situacions en les que el sistema es queda sense memòria.", - "diagnosis_swap_ok": "El sistema té {total_MB} MB de swap!", + "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 256 MB 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_debian": "El fitxer de configuració {file} ha estat modificat manualment respecte al fitxer per defecte de Debian.", @@ -581,7 +581,6 @@ "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_connection_error": "Error de connexió: no s'ha pogut connectar amb el domini demanat, segurament és inaccessible.", - "diagnosis_http_unknown_error": "Hi ha hagut un error intentant accedir al domini, 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", "diagnosis_services_running": "El servei {service} s'està executant!", diff --git a/locales/en.json b/locales/en.json index cf1b8d552..27fb19444 100644 --- a/locales/en.json +++ b/locales/en.json @@ -205,6 +205,7 @@ "diagnosis_description_security": "Security checks", "diagnosis_ports_could_not_diagnose": "Could not diagnose if ports are reachable from outside. Error: {error}", "diagnosis_ports_unreachable": "Port {port} is not reachable from outside.", + "diagnosis_ports_partially_unreachable": "Port {port} is not reachable from outside in IPv{failed}.", "diagnosis_ports_ok": "Port {port} is reachable from outside.", "diagnosis_ports_needed_by": "Exposing this port is needed for {category} features (service {service})", "diagnosis_ports_forwarding_tip": "To fix this issue, you most probably need to configure port forwarding on your internet router as described in https://yunohost.org/isp_box_config", @@ -214,10 +215,9 @@ "diagnosis_http_ok": "Domain {domain} is reachable through HTTP from outside the local network.", "diagnosis_http_timeout": "Timed-out while trying to contact your server from outside. It appears to be unreachable.
1. The most common cause for this issue is that you did not correctly configure port forwarding for port 80.
2. Also make sure that the web server nginx is running
3. On more complex setups: make sure that a firewall or reverse-proxy is not interfering.", "diagnosis_http_connection_error": "Connection error: could not connect to the requested domain, it's very likely unreachable.", - "diagnosis_http_unknown_error": "An error happened while trying to reach your domain, it's very likely unreachable.", "diagnosis_http_bad_status_code": "Timed-out while trying to contact your server from outside. It might be that another machine answered instead of your server.
1. The most common cause for this issue is that you did not correctly configure port forwarding for port 80.
2. On more complex setups: make sure that a firewall or reverse-proxy is not interfering.", "diagnosis_http_unreachable": "Domain {domain} appears unreachable through HTTP from outside the local network.", - "diagnosis_http_partiallu_unreachable": "Domain {domain} appears unreachable through HTTP from outside the local network in IPv{failed}, though it works in IPv{passed}.", + "diagnosis_http_partially_unreachable": "Domain {domain} appears unreachable through HTTP from outside the local network in IPv{failed}, though it works in IPv{passed}.", "diagnosis_http_nginx_conf_not_up_to_date": "This domain's nginx configuration appears to have been modified manually, and prevents YunoHost from diagnosing if it's reachable on HTTP.", "diagnosis_http_nginx_conf_not_up_to_date_details": "To fix the situation, inspect the difference with the command line using yunohost tools regen-conf nginx --dry-run --with-diff and if you're ok, apply the changes with yunohost tools regen-conf nginx --force.", "diagnosis_unknown_categories": "The following categories are unknown: {categories}", diff --git a/locales/eo.json b/locales/eo.json index 127e7df39..87e062ea2 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -513,8 +513,8 @@ "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_abs_GB} GB ({free_percent}%) spaco. Vi vere konsideru purigi iom da spaco.", - "diagnosis_ram_verylow": "La sistemo nur restas {available_abs_MB} MB ({available_percent}%) RAM! (el {total_abs_MB} MB)", + "diagnosis_diskusage_verylow": "Stokado {mountpoint} (sur aparato {device)) restas nur {free} ({free_percent}%) spaco. Vi vere konsideru purigi iom da spaco.", + "diagnosis_ram_verylow": "La sistemo nur restas {available} ({available_percent}%) RAM! (el {total})", "diagnosis_mail_ougoing_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": "Ne povis atingi vian servilon kiel atendite, ĝi redonis malbonan statuskodon. Povas esti, ke alia maŝino respondis anstataŭ via servilo. Vi devus kontroli, ke vi ĝuste redonas la havenon 80, ke via nginx-agordo ĝisdatigas kaj ke reverso-prokuro ne interbatalas.", "main_domain_changed": "La ĉefa domajno estis ŝanĝita", @@ -530,9 +530,9 @@ "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 per /etc/resolv.dnsmasq.conf.", "diagnosis_dns_good_conf": "Bona DNS-agordo por domajno {domain} (kategorio {category})", "diagnosis_dns_bad_conf": "Malbona / mankas DNS-agordo por domajno {domain} (kategorio {category})", - "diagnosis_ram_ok": "La sistemo ankoraŭ havas {available_abs_MB} MB ({available_percent}%) RAM forlasita de {total_abs_MB} MB.", + "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ŭ 256 MB da interŝanĝo por eviti situaciojn en kiuj la sistemo restas sen memoro.", - "diagnosis_swap_notsomuch": "La sistemo havas nur {total_MB} MB-interŝanĝon. Vi konsideru havi almenaŭ 256 MB 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ŭ 256 MB 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_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...", @@ -545,8 +545,8 @@ "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} !", "diagnosis_services_bad_status": "Servo {service} estas {status} :(", - "diagnosis_ram_low": "La sistemo havas {available_abs_MB} MB ({available_percent}%) RAM forlasita de {total_abs_MB} MB. Estu zorgema.", - "diagnosis_swap_ok": "La sistemo havas {total_MB} MB da interŝanĝoj!", + "diagnosis_ram_low": "La sistemo havas {available} ({available_percent}%) RAM forlasita de {total}. Estu zorgema.", + "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.", @@ -564,13 +564,12 @@ "log_domain_main_domain": "Faru '{}' kiel ĉ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_connection_error": "Rilata eraro: ne povis konektiĝi al la petita domajno, tre probable ĝi estas neatingebla.", - "diagnosis_http_unknown_error": "Eraro okazis dum provado atingi vian domajnon, 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_abs_GB} GB ({free_percent}%) spaco. Estu zorgema.", - "diagnosis_diskusage_ok": "Stokado {mountpoint} (sur aparato {device) ankoraŭ restas {free_abs_GB} GB ({free_percent}%) spaco!", + "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!", "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!", diff --git a/locales/es.json b/locales/es.json index b72665066..6a55378da 100644 --- a/locales/es.json +++ b/locales/es.json @@ -529,7 +529,7 @@ "diagnosis_ip_not_connected_at_all": "¿¡Está conectado el servidor a internet!?", "diagnosis_ip_broken_resolvconf": "DNS parece no funcionar en tu servidor, lo que parece estar relacionado con /etc/resolv.conf no apuntando a 127.0.0.1.", "diagnosis_dns_missing_record": "Según la configuración DNS recomendada, deberías añadir un registro DNS\ntipo: {type}\nnombre: {name}\nvalor: {value}", - "diagnosis_diskusage_low": "El almacenamiento {mountpoint} (en dispositivo {device}) solo tiene {free_abs_GB} GB ({free_percent}%) de espacio disponible. Ten cuidado.", + "diagnosis_diskusage_low": "El almacenamiento {mountpoint} (en dispositivo {device}) solo tiene {free} ({free_percent}%) de espacio disponible. Ten cuidado.", "diagnosis_services_bad_status_tip": "Puedes intentar reiniciar el servicio, y si no funciona, echar un vistazo a los logs del servicio usando 'yunohost service log {service}' o a través de la sección 'Servicios' en webadmin.", "diagnosis_ip_connected_ipv6": "¡El servidor está conectado a internet a través de IPv6!", "diagnosis_ip_no_ipv6": "El servidor no cuenta con IPv6 funcional.", @@ -541,18 +541,18 @@ "diagnosis_dns_bad_conf": "Configuración mala o faltante de los DNS para el dominio {domain} (categoría {category})", "diagnosis_dns_discrepancy": "El registro DNS con tipo {type} y nombre {name} no se corresponde a la configuración recomendada.\nValor actual: {current}\nValor esperado: {value}", "diagnosis_services_bad_status": "El servicio {service} está {status} :(", - "diagnosis_diskusage_verylow": "El almacenamiento {mountpoint} (en el dispositivo {device}) sólo tiene {free_abs_GB} GB ({free_percent}%) de espacio disponible. Deberías considerar la posibilidad de limpiar algo de espacio.", - "diagnosis_diskusage_ok": "¡El almacenamiento {mountpoint} (en el dispositivo {device}) todavía tiene {free_abs_GB} GB ({free_percent}%) de espacio libre!", + "diagnosis_diskusage_verylow": "El almacenamiento {mountpoint} (en el dispositivo {device}) sólo tiene {free} ({free_percent}%) de espacio disponible. Deberías considerar la posibilidad de limpiar algo de espacio.", + "diagnosis_diskusage_ok": "¡El almacenamiento {mountpoint} (en el dispositivo {device}) todavía tiene {free} ({free_percent}%) de espacio libre!", "diagnosis_services_conf_broken": "¡Mala configuración para el servicio {service}!", "diagnosis_services_running": "¡El servicio {service} está en ejecución!", "diagnosis_failed": "No se ha podido obtener el resultado del diagnóstico para la categoría '{category}': {error}", "diagnosis_ip_connected_ipv4": "¡El servidor está conectado a internet a través de IPv4!", "diagnosis_security_vulnerable_to_meltdown_details": "Para corregir esto, debieras actualizar y reiniciar tu sistema para cargar el nuevo kernel de Linux (o contacta tu proveedor si esto no funciona). Mas información en https://meltdownattack.com/", - "diagnosis_ram_verylow": "Al sistema le queda solamente {available_abs_MB} MB ({available_percent}%) de RAM! (De un total de {total_abs_MB} MB)", - "diagnosis_ram_low": "Al sistema le queda {available_abs_MB} MB ({available_percent}%) de RAM de un total de {total_abs_MB} MB. Cuidado.", - "diagnosis_ram_ok": "El sistema aun tiene {available_abs_MB} MB ({available_percent}%) de RAM de un total de {total_abs_MB} MB.", + "diagnosis_ram_verylow": "Al sistema le queda solamente {available} ({available_percent}%) de RAM! (De un total de {total})", + "diagnosis_ram_low": "Al sistema le queda {available} ({available_percent}%) de RAM de un total de {total}. Cuidado.", + "diagnosis_ram_ok": "El sistema aun tiene {available} ({available_percent}%) de RAM de un total de {total}.", "diagnosis_swap_none": "El sistema no tiene mas espacio de intercambio. Considera agregar por lo menos 256 MB de espacio de intercambio para evitar que el sistema se quede sin memoria.", - "diagnosis_swap_notsomuch": "Al sistema le queda solamente {total_MB} MB de espacio de intercambio. Considera agregar al menos 256 MB para evitar que el sistema se quede sin memoria.", + "diagnosis_swap_notsomuch": "Al sistema le queda solamente {total} de espacio de intercambio. Considera agregar al menos 256 MB para evitar que el sistema se quede sin memoria.", "diagnosis_mail_ougoing_port_25_ok": "El puerto de salida 25 no esta bloqueado y los correos electrónicos pueden ser enviados a otros servidores.", "diagnosis_mail_ougoing_port_25_blocked": "El puerto de salida 25 parece estar bloqueado. Intenta desbloquearlo con el panel de configuración de tu proveedor de servicios de Internet (o proveedor de halbergue). Mientras tanto, el servidor no podrá enviar correos electrónicos a otros servidores.", "diagnosis_regenconf_allgood": "Todos los archivos de configuración están en linea con la configuración recomendada!", @@ -568,7 +568,7 @@ "diagnosis_description_services": "Comprobación del estado de los servicios", "diagnosis_description_ports": "Exposición de puertos", "diagnosis_description_systemresources": "Recursos del sistema", - "diagnosis_swap_ok": "El sistema tiene {total_MB} MB de espacio de intercambio!", + "diagnosis_swap_ok": "El sistema tiene {total} de espacio de intercambio!", "diagnosis_ports_needed_by": "La apertura de este puerto es requerida para la funcionalidad {category} (service {service})", "diagnosis_ports_ok": "El puerto {port} es accesible desde internet.", "diagnosis_ports_unreachable": "El puerto {port} no es accesible desde internet.", @@ -592,7 +592,6 @@ "diagnosis_unknown_categories": "Las siguientes categorías están desconocidas: {categories}", "diagnosis_http_unreachable": "El dominio {domain} esta fuera de alcance desde internet y a través de HTTP.", "diagnosis_http_bad_status_code": "El sistema de diagnostico no pudo comunicarse con su servidor. Puede ser otra maquina que contesto en lugar del servidor. Debería verificar en su firewall que el re-direccionamiento del puerto 80 esta correcto.", - "diagnosis_http_unknown_error": "Hubo un error durante la búsqueda de su dominio, parece inalcanzable.", "diagnosis_http_connection_error": "Error de conexión: Ne se pudo conectar al dominio solicitado,", "diagnosis_http_timeout": "El intento de contactar a su servidor desde internet corrió fuera de tiempo. Al parece esta incomunicado. Debería verificar que nginx corre en el puerto 80, y que la redireción del puerto 80 no interfiere con en el firewall.", "diagnosis_http_ok": "El Dominio {domain} es accesible desde internet a través de HTTP.", diff --git a/locales/fr.json b/locales/fr.json index c8dfd12a9..f029a1d13 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -510,8 +510,8 @@ "diagnosis_ip_weird_resolvconf": "La résolution DNS semble fonctionner, mais soyez prudent en utilisant un fichier /etc/resolv.conf personnalisé.", "diagnosis_ip_weird_resolvconf_details": "Au lieu de cela, ce fichier devrait être un lien symbolique vers /etc/resolvconf/run/resolv.conf lui-même pointant vers 127.0.0.1 (dnsmasq). Les résolveurs réels doivent être configurés dans /etc/resolv.dnsmasq.conf.", "diagnosis_dns_missing_record": "Selon la configuration DNS recommandée, vous devez ajouter un enregistrement DNS\nType: {type}\nNom: {name}\nValeur {value}", - "diagnosis_diskusage_ok": "Le stockage {mountpoint} (sur le périphérique {device}) a encore {free_abs_GB} Go ({free_percent}%) d'espace libre !", - "diagnosis_ram_ok": "Le système dispose encore de {available_abs_MB} MB ({available_percent}%) de RAM sur {total_abs_MB} MB.", + "diagnosis_diskusage_ok": "Le stockage {mountpoint} (sur le périphérique {device}) a encore {free} ({free_percent}%) d'espace libre !", + "diagnosis_ram_ok": "Le système dispose encore de {available} ({available_percent}%) de RAM sur {total}.", "diagnosis_regenconf_allgood": "Tous les fichiers de configuration sont conformes à la configuration recommandée !", "diagnosis_security_vulnerable_to_meltdown": "Vous semblez vulnérable à la vulnérabilité de sécurité critique de Meltdown", "diagnosis_basesystem_host": "Le serveur utilise Debian {debian_version}", @@ -537,13 +537,13 @@ "diagnosis_dns_bad_conf": "Configuration DNS incorrecte ou manquante pour le domaine {domain} (catégorie {category})", "diagnosis_dns_discrepancy": "L'enregistrement DNS de type {type} et nom {name} ne correspond pas à la configuration recommandée.\nValeur actuelle: {current}\nValeur attendue: {value}", "diagnosis_services_bad_status": "Le service {service} est {status} :-(", - "diagnosis_diskusage_verylow": "Le stockage {mountpoint} (sur le périphérique {device}) ne dispose que de {free_abs_GB} Go ({free_percent}%). Vous devriez vraiment envisager de nettoyer un peu d'espace.", - "diagnosis_diskusage_low": "Le stockage {mountpoint} (sur le périphérique {device}) ne dispose que de {free_abs_GB} Go ({free_percent}%). Faites attention.", - "diagnosis_ram_verylow": "Le système ne dispose plus que de {available_abs_MB} MB ({available_percent}%)! (sur {total_abs_MB} Mo)", - "diagnosis_ram_low": "Le système n'a plus de {available_abs_MB} MB ({available_percent}%) RAM sur {total_abs_MB} MB. Faites attention.", + "diagnosis_diskusage_verylow": "Le stockage {mountpoint} (sur le périphérique {device}) ne dispose que de {free} ({free_percent}%). Vous devriez vraiment envisager de nettoyer un peu d'espace.", + "diagnosis_diskusage_low": "Le stockage {mountpoint} (sur le périphérique {device}) ne dispose que de {free} ({free_percent}%). Faites attention.", + "diagnosis_ram_verylow": "Le système ne dispose plus que de {available} ({available_percent}%)! (sur {total})", + "diagnosis_ram_low": "Le système n'a plus de {available} ({available_percent}%) RAM sur {total}. Faites attention.", "diagnosis_swap_none": "Le système n'a aucun échange. Vous devez envisager d’ajouter au moins 256 Mo de swap pour éviter les situations où le système manque de mémoire.", - "diagnosis_swap_notsomuch": "Le système ne dispose que de {total_MB} Mo de swap. Vous devez envisager d'avoir au moins 256 Mo pour éviter les situations où le système manque de mémoire.", - "diagnosis_swap_ok": "Le système dispose de {total_MB} Mo de swap !", + "diagnosis_swap_notsomuch": "Le système ne dispose que de {total} de swap. Vous devez envisager d'avoir au moins 256 Mo pour éviter les situations où le système manque de mémoire.", + "diagnosis_swap_ok": "Le système dispose de {total} de swap !", "diagnosis_regenconf_manually_modified": "Le fichier de configuration {file} a été modifié manuellement.", "diagnosis_regenconf_manually_modified_debian": "Le fichier de configuration {file} a été modifié manuellement par rapport à celui par défaut de Debian.", "diagnosis_regenconf_manually_modified_details": "C'est probablement OK tant que vous savez ce que vous faites;) !", @@ -583,7 +583,6 @@ "diagnosis_ports_forwarding_tip": "Pour résoudre ce problème, vous devez probablement configurer la redirection de port sur votre routeur Internet comme décrit sur https://yunohost.org/isp_box_config", "diagnosis_http_connection_error": "Erreur de connexion : impossible de se connecter au domaine demandé, il est probablement injoignable.", "diagnosis_no_cache": "Pas encore de cache de diagnostique pour la catégorie « {category} »", - "diagnosis_http_unknown_error": "Une erreur est survenue en essayant de joindre votre domaine, il est probablement injoignable.", "yunohost_postinstall_end_tip": "La post-installation terminée! Pour finaliser votre configuration, il est recommendé de :\n - ajouter un premier utilisateur depuis la section \"Utilisateurs\" de l'interface web (ou \"yunohost user create \" en ligne de commande);\n - diagnostiquer les potentiels problèmes dans la section \"Diagnostic\" de l'interface web (ou \"yunohost diagnosis run\" en ligne de commande);\n - lire les parties \"Finalisation de votre configuration\" et \"Découverte de Yunohost\" dans le guide de l'administrateur: https://yunohost.org/admindoc.", "diagnosis_services_bad_status_tip": "Vous pouvez essayer de redémarrer le service. Si cela ne fonctionne pas, consultez les journaux de service à l'aide de 'yunohost service log {service}' ou de la section 'Services' de l'administrateur Web.", "diagnosis_http_bad_status_code": "Le système de diagnostique n'a pas réussi à contacter votre serveur. Il se peut qu'une autre machine réponde à la place de votre serveur. Vérifiez que le port 80 est correctement redirigé, que votre configuration nginx est à jour et qu’un reverse-proxy n’interfère pas.", diff --git a/locales/oc.json b/locales/oc.json index a452b72bb..eebfaac64 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -479,8 +479,8 @@ "diagnosis_http_ok": "Lo domeni {domain} accessible de l’exterior.", "app_full_domain_unavailable": "Aquesta aplicacion a d’èsser installada sul seu pròpri domeni, mas i a d’autras aplicacions installadas sus aqueste domeni « {domain} ». Podètz utilizar allòc un josdomeni dedicat a aquesta aplicacion.", "diagnosis_dns_bad_conf": "Configuracion DNS incorrècta o inexistenta pel domeni {domain} (categoria {category})", - "diagnosis_ram_verylow": "Lo sistèma a solament {available_abs_MB} Mo ({available_percent}%) de memòria RAM disponibla ! (d’un total de {total_abs_MB} MB)", - "diagnosis_ram_ok": "Lo sistèma a encara {available_abs_MB} Mo ({available_percent}%) de memòria RAM disponibla d’un total de {total_abs_MB} MB).", + "diagnosis_ram_verylow": "Lo sistèma a solament {available} ({available_percent}%) de memòria RAM disponibla ! (d’un total de {total})", + "diagnosis_ram_ok": "Lo sistèma a encara {available} ({available_percent}%) de memòria RAM disponibla d’un total de {total}).", "permission_already_allowed": "Lo grop « {group} » a ja la permission « {permission} » activada", "permission_already_disallowed": "Lo grop « {group} » a ja la permission « {permission} » desactivada", "permission_cannot_remove_main": "La supression d’una permission màger es pas autorizada", @@ -511,7 +511,7 @@ "diagnosis_cache_still_valid": "(Memòria cache totjorn valida pel diagnostic {category}. Cap d’autre diagnostic pel moment !)", "diagnosis_found_errors": "{errors} errors importantas trobadas ligadas a {category} !", "diagnosis_services_bad_status": "Lo servici {service} es {status} :(", - "diagnosis_swap_ok": "Lo sistèma a {total_MB} MB d’escambi !", + "diagnosis_swap_ok": "Lo sistèma a {total} d’escambi !", "diagnosis_regenconf_allgood": "Totes los fichièrs de configuracion son confòrmes a la configuracion recomandada !", "diagnosis_regenconf_manually_modified": "Lo fichièr de configuracion {file} foguèt modificat manualament.", "diagnosis_regenconf_manually_modified_details": "Es probablament bon tan que sabètz çò que fasètz ;) !", @@ -527,7 +527,7 @@ "diagnosis_ports_ok": "Lo pòrt {port} es accessible de l’exterior.", "diagnosis_http_unreachable": "Lo domeni {domain} es pas accessible via HTTP de l’exterior.", "diagnosis_unknown_categories": "La categorias seguentas son desconegudas : {categories}", - "diagnosis_ram_low": "Lo sistèma a {available_abs_MB} Mo ({available_percent}%) de memòria RAM disponibla d’un total de {total_abs_MB} MB). Atencion.", + "diagnosis_ram_low": "Lo sistèma a {available} ({available_percent}%) de memòria RAM disponibla d’un total de {total}). Atencion.", "diagnosis_regenconf_manually_modified_debian": "Lo fichier de configuracion {file} foguèt modificat manualament respècte al fichièr per defaut de Debian.", "log_permission_create": "Crear la permission « {} »", "log_permission_delete": "Suprimir la permission « {} »", @@ -557,18 +557,17 @@ "diagnosis_services_running": "Lo servici {service} es lançat !", "diagnosis_services_conf_broken": "La configuracion es copada pel servici {service} !", "diagnosis_ports_needed_by": "Es necessari qu’aqueste pòrt siá accessible pel servici {service}", - "diagnosis_diskusage_low": "Lo lòc d’emmagazinatge {mountpoint} (sul periferic {device}) a solament {free_abs_GB} Go ({free_percent}%). Siatz prudent.", + "diagnosis_diskusage_low": "Lo lòc d’emmagazinatge {mountpoint} (sul periferic {device}) a solament {free} ({free_percent}%). Siatz prudent.", "migration_description_0014_remove_app_status_json": "Suprimir los fichièrs d’aplicacion status.json eretats", "dyndns_provider_unreachable": "Impossible d’atenher lo provesidor Dyndns : siá vòstre YunoHost es pas corrèctament connectat a Internet siá lo servidor dynette es copat.", "diagnosis_services_bad_status_tip": "Podètz ensajar de reaviar lo servici, e se non fonciona pas, podètz agachar los jornals en utilizant « yunohost service log {service} » o via la seccion « Servicis » de pas la pagina web d’administracion.", "diagnosis_http_connection_error": "Error de connexion : connexion impossibla al domeni demandat, benlèu qu’es pas accessible.", - "diagnosis_http_unknown_error": "Una error s’es producha en ensajar de se connectar a vòstre domeni, es benlèu pas accessible.", "group_user_already_in_group": "L’utilizaire {user} es ja dins lo grop « {group} »", "diagnosis_ip_broken_resolvconf": "La resolucion del nom de domeni sembla copada sul servidor, poiriá èsser ligada al fait que /etc/resolv.conf manda pas a 127.0.0.1.", "diagnosis_ip_weird_resolvconf": "La resolucion del nom de domeni sembla foncionar, mas siatz prudent en utilizant un fichièr /etc/resolv.con personalizat.", - "diagnosis_diskusage_verylow": "Lo lòc d’emmagazinatge {mountpoint} (sul periferic {device}) a solament {free_abs_GB} Go ({free_percent}%). Deuriatz considerar de liberar un pauc d’espaci.", + "diagnosis_diskusage_verylow": "Lo lòc d’emmagazinatge {mountpoint} (sul periferic {device}) a solament {free} ({free_percent}%). Deuriatz considerar de liberar un pauc d’espaci.", "global_settings_setting_pop3_enabled": "Activar lo protocòl POP3 pel servidor de corrièr", - "diagnosis_diskusage_ok": "Lo lòc d’emmagazinatge {mountpoint} (sul periferic {device}) a encara {free_abs_GB} Go ({free_percent}%) de liure !", + "diagnosis_diskusage_ok": "Lo lòc d’emmagazinatge {mountpoint} (sul periferic {device}) a encara {free} ({free_percent}%) de liure !", "diagnosis_swap_none": "Lo sistèma a pas cap de memòria d’escambi. Auriatz de considerar d’ajustar almens 256 Mo d’escambi per evitar las situacions ont lo sistèma manca de memòria.", - "diagnosis_swap_notsomuch": "Lo sistèma a solament {total_MB} de memòria d’escambi. Auriatz de considerar d’ajustar almens 256 Mo d’escambi per evitar las situacions ont lo sistèma manca de memòria." + "diagnosis_swap_notsomuch": "Lo sistèma a solament {total} de memòria d’escambi. Auriatz de considerar d’ajustar almens 256 Mo d’escambi per evitar las situacions ont lo sistèma manca de memòria." } From 0a3e7aa88b04c58c1795b52443c9eb8953768d85 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Fri, 17 Apr 2020 12:01:50 +0200 Subject: [PATCH 0989/3170] Update data/helpers.d/php Co-Authored-By: JimboJoe --- data/helpers.d/php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/php b/data/helpers.d/php index 19e586b70..532e3ba1a 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -356,7 +356,7 @@ ynh_get_scalable_phpfpm () { fi # Define the factor to determine min_spare_servers - # To not have not enough children ready to start for heavy apps. + # to avoid having too few children ready to start for heavy apps if [ $footprint -le 20 ] then min_spare_servers_factor=8 From f90a238973dc4c9687492ec696f3922977076a74 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Fri, 17 Apr 2020 12:02:11 +0200 Subject: [PATCH 0990/3170] Update data/helpers.d/php Co-Authored-By: JimboJoe --- data/helpers.d/php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/php b/data/helpers.d/php index 401c262b3..0bfbd2c29 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -402,7 +402,7 @@ ynh_get_scalable_phpfpm () { php_max_children=$max_proc fi - # Get an potential forced value for php_max_children + # Get a potential forced value for php_max_children local php_forced_max_children=$(ynh_app_setting_get --app=$app --key=php_forced_max_children) if [ -n "$php_forced_max_children" ]; then php_max_children=$php_forced_max_children From 7061c4c3fe489ffb4e5d5b4b230bef887e911ca1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 17 Apr 2020 14:40:53 +0200 Subject: [PATCH 0991/3170] Improve systemd settings for slapd (#933) --- data/hooks/conf_regen/06-slapd | 10 ++++++++++ data/templates/slapd/systemd-override.conf | 9 +++++++++ 2 files changed, 19 insertions(+) create mode 100644 data/templates/slapd/systemd-override.conf diff --git a/data/hooks/conf_regen/06-slapd b/data/hooks/conf_regen/06-slapd index 2fa108baa..9b2c20138 100755 --- a/data/hooks/conf_regen/06-slapd +++ b/data/hooks/conf_regen/06-slapd @@ -63,6 +63,9 @@ do_pre_regen() { cp -a ldap.conf slapd.conf "$ldap_dir" cp -a sudo.schema mailserver.schema yunohost.schema "$schema_dir" + mkdir -p ${pending_dir}/etc/systemd/system/slapd.service.d/ + cp systemd-override.conf ${pending_dir}/etc/systemd/system/slapd.service.d/ynh-override.conf + install -D -m 644 slapd.default "${pending_dir}/etc/default/slapd" } @@ -83,6 +86,13 @@ do_post_regen() { 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$" + then + systemctl daemon-reload + systemctl restart slapd + fi + [ -z "$regen_conf_files" ] && exit 0 # check the slapd config file at first diff --git a/data/templates/slapd/systemd-override.conf b/data/templates/slapd/systemd-override.conf new file mode 100644 index 000000000..afa821bd4 --- /dev/null +++ b/data/templates/slapd/systemd-override.conf @@ -0,0 +1,9 @@ +[Service] +# Prevent slapd from getting killed by oom reaper as much as possible +OOMScoreAdjust=-1000 +# If slapd exited (for instance if got killed) the service should not be +# considered as active anymore... +RemainAfterExit=no +# Automatically restart the service if the service gets down +Restart=always +RestartSec=3 From 61ef67252e37c436b2efa346d9c8312a03c1b077 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 17 Apr 2020 19:38:46 +0200 Subject: [PATCH 0992/3170] Don't contact ip6.yunohost if we can know right away that there's no IPv6 at all on the system --- data/hooks/diagnosis/10-ip.py | 2 +- src/yunohost/utils/network.py | 21 +++++++++++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/data/hooks/diagnosis/10-ip.py b/data/hooks/diagnosis/10-ip.py index 36e04b5c1..0cb608b48 100644 --- a/data/hooks/diagnosis/10-ip.py +++ b/data/hooks/diagnosis/10-ip.py @@ -106,7 +106,7 @@ class IPDiagnoser(Diagnoser): # If we are indeed connected in ipv4 or ipv6, we should find a default route routes = check_output("ip -%s route" % protocol).split("\n") - if not [r for r in routes if r.startswith("default")]: + if not any(r.startswith("default") for r in routes): return False # We use the resolver file as a list of well-known, trustable (ie not google ;)) IPs that we can ping diff --git a/src/yunohost/utils/network.py b/src/yunohost/utils/network.py index 4e23516c3..3ae1ba910 100644 --- a/src/yunohost/utils/network.py +++ b/src/yunohost/utils/network.py @@ -18,10 +18,12 @@ along with this program; if not, see http://www.gnu.org/licenses """ -import logging +import os import re -import subprocess +import logging + from moulinette.utils.network import download_text +from moulinette.utils.process import check_output logger = logging.getLogger('yunohost.utils.network') @@ -36,6 +38,17 @@ def get_public_ip(protocol=4): else: raise ValueError("invalid protocol version") + # We can know that ipv6 is not available directly if this file does not exists + if protocol == 6 and not os.path.exists("/proc/net/if_inet6"): + logger.debug("IPv6 appears not at all available on the system, so assuming there's no IP address for that version") + return None + + # If we are indeed connected in ipv4 or ipv6, we should find a default route + routes = check_output("ip -%s route" % protocol).split("\n") + if not any(r.startswith("default") for r in routes): + logger.debug("No default route for IPv%s, so assuming there's no IP address for that version" % protocol) + return None + try: return download_text(url, timeout=30).strip() except Exception as e: @@ -47,7 +60,7 @@ def get_network_interfaces(): # Get network devices and their addresses (raw infos from 'ip addr') devices_raw = {} - output = subprocess.check_output('ip addr show'.split()) + output = check_output('ip addr show') for d in re.split(r'^(?:[0-9]+: )', output, flags=re.MULTILINE): # Extract device name (1) and its addresses (2) m = re.match(r'([^\s@]+)(?:@[\S]+)?: (.*)', d, flags=re.DOTALL) @@ -62,7 +75,7 @@ def get_network_interfaces(): def get_gateway(): - output = subprocess.check_output('ip route show'.split()) + output = check_output('ip route show') m = re.search(r'default via (.*) dev ([a-z]+[0-9]?)', output) if not m: return None From 485de92b50c8f826beb764225083cdc0ade5fc51 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 17 Apr 2020 21:22:02 +0200 Subject: [PATCH 0993/3170] Redundant message --- src/yunohost/diagnosis.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index fd7a37480..f8e3f36cc 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -385,8 +385,6 @@ class Diagnoser(): logger.error(m18n.n("diagnosis_cant_run_because_of_dep", category=self.description, dep=Diagnoser.get_description(dependency))) return 1, {} - self.logger_debug("Running diagnostic for %s" % self.id_) - items = list(self.run()) for item in items: From 08f9091257feb7423ffb9e06f72845dcd4533131 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 18 Apr 2020 02:40:40 +0200 Subject: [PATCH 0994/3170] Handle stupid weird case where the manifest file disappeared yet the app folder is still there ... --- src/yunohost/app.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 39793ec1a..ed7747b29 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -116,7 +116,11 @@ def app_list(full=False): """ out = [] for app_id in sorted(_installed_apps()): - app_info_dict = app_info(app_id, full=full) + try: + app_info_dict = app_info(app_id, full=full) + except Exception as e: + logger.error("Failed to read info for %s : %s" % (app_id, e)) + continue app_info_dict["id"] = app_id out.append(app_info_dict) @@ -131,6 +135,7 @@ def app_info(app, full=False): raise YunohostError('app_not_installed', app=app, all_apps=_get_all_installed_apps_id()) local_manifest = _get_manifest_of_app(os.path.join(APPS_SETTING_PATH, app)) + settings = _get_app_settings(app) ret = { @@ -2026,7 +2031,7 @@ def _get_manifest_of_app(path): elif os.path.exists(os.path.join(path, "manifest.json")): return read_json(os.path.join(path, "manifest.json")) else: - return None + 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) def _get_git_last_commit_hash(repository, reference='HEAD'): From 749ca54a34f05a9751f0bbf17d84c9b067c5a592 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 18 Apr 2020 02:53:15 +0200 Subject: [PATCH 0995/3170] More sensible cache_duration for diagnosis categories --- data/hooks/diagnosis/00-basesystem.py | 2 +- data/hooks/diagnosis/10-ip.py | 2 +- data/hooks/diagnosis/12-dnsrecords.py | 2 +- data/hooks/diagnosis/14-ports.py | 2 +- data/hooks/diagnosis/21-web.py | 2 +- data/hooks/diagnosis/24-mail.py | 2 +- data/hooks/diagnosis/50-systemresources.py | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/data/hooks/diagnosis/00-basesystem.py b/data/hooks/diagnosis/00-basesystem.py index 68a9570ce..51926924a 100644 --- a/data/hooks/diagnosis/00-basesystem.py +++ b/data/hooks/diagnosis/00-basesystem.py @@ -11,7 +11,7 @@ from yunohost.utils.packages import ynh_packages_version class BaseSystemDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] - cache_duration = 3600 * 24 + cache_duration = 600 dependencies = [] def run(self): diff --git a/data/hooks/diagnosis/10-ip.py b/data/hooks/diagnosis/10-ip.py index 0cb608b48..6571ca556 100644 --- a/data/hooks/diagnosis/10-ip.py +++ b/data/hooks/diagnosis/10-ip.py @@ -13,7 +13,7 @@ from yunohost.utils.network import get_network_interfaces class IPDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] - cache_duration = 60 + cache_duration = 600 dependencies = [] def run(self): diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py index 3853350bd..5ed7fc737 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -12,7 +12,7 @@ from yunohost.domain import domain_list, _build_dns_conf, _get_maindomain class DNSRecordsDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] - cache_duration = 3600 * 24 + cache_duration = 600 dependencies = ["ip"] def run(self): diff --git a/data/hooks/diagnosis/14-ports.py b/data/hooks/diagnosis/14-ports.py index b63971b71..6f4c808bd 100644 --- a/data/hooks/diagnosis/14-ports.py +++ b/data/hooks/diagnosis/14-ports.py @@ -9,7 +9,7 @@ from yunohost.service import _get_services class PortsDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] - cache_duration = 3600 + cache_duration = 600 dependencies = ["ip", "dnsrecords"] def run(self): diff --git a/data/hooks/diagnosis/21-web.py b/data/hooks/diagnosis/21-web.py index 10deea28d..6e9dd6b79 100644 --- a/data/hooks/diagnosis/21-web.py +++ b/data/hooks/diagnosis/21-web.py @@ -16,7 +16,7 @@ DIAGNOSIS_SERVER = "diagnosis.yunohost.org" class WebDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] - cache_duration = 3600 + cache_duration = 600 dependencies = ["ip", "dnsrecords"] def run(self): diff --git a/data/hooks/diagnosis/24-mail.py b/data/hooks/diagnosis/24-mail.py index 0a3a97102..3f9517bb0 100644 --- a/data/hooks/diagnosis/24-mail.py +++ b/data/hooks/diagnosis/24-mail.py @@ -8,7 +8,7 @@ from yunohost.diagnosis import Diagnoser class MailDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] - cache_duration = 3600 + cache_duration = 600 dependencies = ["ip"] def run(self): diff --git a/data/hooks/diagnosis/50-systemresources.py b/data/hooks/diagnosis/50-systemresources.py index b4e50ccf1..ab9ead7bb 100644 --- a/data/hooks/diagnosis/50-systemresources.py +++ b/data/hooks/diagnosis/50-systemresources.py @@ -7,7 +7,7 @@ from yunohost.diagnosis import Diagnoser class SystemResourcesDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] - cache_duration = 3600 * 24 + cache_duration = 300 dependencies = [] def run(self): From 301ced9d6ebbd350a734da40572c5312068fbb35 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 18 Apr 2020 02:54:16 +0200 Subject: [PATCH 0996/3170] Hmf I dunno let's have this weird option if we really want to have a special case for first-run ... --- data/actionsmap/yunohost.yml | 3 +++ src/yunohost/diagnosis.py | 13 ++++++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index ded56a7c1..e1229352c 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1686,6 +1686,9 @@ diagnosis: --force: help: Ignore the cached report even if it is still 'fresh' action: store_true + --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 ignore: action_help: Configure some diagnosis results to be ignored and therefore not considered as actual issues diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index f8e3f36cc..f40687989 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -69,6 +69,10 @@ def diagnosis_get(category, item): def diagnosis_show(categories=[], issues=False, full=False, share=False): + if not os.path.exists(DIAGNOSIS_CACHE): + logger.warning(m18n.n("diagnosis_never_ran_yet")) + return + # Get all the categories all_categories = _list_diagnosis_categories() all_categories_names = [category for category, _ in all_categories] @@ -81,10 +85,6 @@ def diagnosis_show(categories=[], issues=False, full=False, share=False): if unknown_categories: raise YunohostError('diagnosis_unknown_categories', categories=", ".join(unknown_categories)) - if not os.path.exists(DIAGNOSIS_CACHE): - logger.warning(m18n.n("diagnosis_never_ran_yet")) - return - # Fetch all reports all_reports = [] for category in categories: @@ -146,7 +146,10 @@ def _dump_human_readable_reports(reports): return(output) -def diagnosis_run(categories=[], force=False): +def diagnosis_run(categories=[], force=False, except_if_never_ran_yet=False): + + if except_if_never_ran_yet and not os.path.exists(DIAGNOSIS_CACHE): + return # Get all the categories all_categories = _list_diagnosis_categories() From e880e775c15f730d7a9a23ecd88a425cf02bbef6 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 18 Apr 2020 03:09:43 +0200 Subject: [PATCH 0997/3170] No need to display this message in webadmin (we're already on the diagnosis script when this happens ...) + in CLI we want to have it as a warning so that it's displayed in cron email --- locales/en.json | 3 +-- src/yunohost/diagnosis.py | 7 ++----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/locales/en.json b/locales/en.json index 27fb19444..f0189f8fe 100644 --- a/locales/en.json +++ b/locales/en.json @@ -143,8 +143,7 @@ "diagnosis_basesystem_ynh_single_version": "{package} version: {version} ({repo})", "diagnosis_basesystem_ynh_main_version": "Server is running YunoHost {main_version} ({repo})", "diagnosis_basesystem_ynh_inconsistent_versions": "You are running inconsistent versions of the YunoHost packages... most probably because of a failed or partial upgrade.", - "diagnosis_display_tip_web": "You can go to the Diagnosis section (in the home screen) to see the issues found.", - "diagnosis_display_tip_cli": "You can run 'yunohost diagnosis show --issues' to display the issues found.", + "diagnosis_display_tip": "To see the issues found, you can go to the Diagnosis section of the webadmin, or run 'yunohost diagnosis show --issues' from the command-line.", "diagnosis_failed_for_category": "Diagnosis failed for category '{category}': {error}", "diagnosis_cache_still_valid": "(Cache still valid for {category} diagnosis. Not re-diagnosing yet!)", "diagnosis_cant_run_because_of_dep": "Can't run diagnosis for {category} while there are important issues related to {dep}.", diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index f40687989..aba65a619 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -180,11 +180,8 @@ 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: - if msettings.get("interface") == "api": - logger.info(m18n.n("diagnosis_display_tip_web")) - else: - logger.info(m18n.n("diagnosis_display_tip_cli")) + if issues and msettings.get("interface") == "cli": + logger.warning(m18n.n("diagnosis_display_tip")) return From db347115fcc69c45705e505b150eed36bdab9e1e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 18 Apr 2020 03:24:31 +0200 Subject: [PATCH 0998/3170] In fact we can't have a hard dependency on dnsrecords here, otherwise no check are performed until the dnsrecords have no important issue ... --- data/hooks/diagnosis/14-ports.py | 2 +- data/hooks/diagnosis/21-web.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/hooks/diagnosis/14-ports.py b/data/hooks/diagnosis/14-ports.py index 6f4c808bd..a4459d92f 100644 --- a/data/hooks/diagnosis/14-ports.py +++ b/data/hooks/diagnosis/14-ports.py @@ -10,7 +10,7 @@ class PortsDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] cache_duration = 600 - dependencies = ["ip", "dnsrecords"] + dependencies = ["ip"] def run(self): diff --git a/data/hooks/diagnosis/21-web.py b/data/hooks/diagnosis/21-web.py index 6e9dd6b79..09f5b2b73 100644 --- a/data/hooks/diagnosis/21-web.py +++ b/data/hooks/diagnosis/21-web.py @@ -17,7 +17,7 @@ class WebDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] cache_duration = 600 - dependencies = ["ip", "dnsrecords"] + dependencies = ["ip"] def run(self): From fc5047838213f67db2bb3cdf76d83e15d2440f82 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 18 Apr 2020 03:28:52 +0200 Subject: [PATCH 0999/3170] Another attempt to improve messages... --- locales/en.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/en.json b/locales/en.json index f0189f8fe..800a1d696 100644 --- a/locales/en.json +++ b/locales/en.json @@ -212,9 +212,9 @@ "diagnosis_http_hairpinning_issue_details": "This is probably because of your ISP box / router. As a result, people from outside your local network will be able to access your server as expected, but not people from inside the local network (like you, probably?). You may be able to improve the situation by having a look at https://yunohost.org/dns_local_network", "diagnosis_http_could_not_diagnose": "Could not diagnose if domain is reachable from outside. Error: {error}", "diagnosis_http_ok": "Domain {domain} is reachable through HTTP from outside the local network.", - "diagnosis_http_timeout": "Timed-out while trying to contact your server from outside. It appears to be unreachable.
1. The most common cause for this issue is that you did not correctly configure port forwarding for port 80.
2. Also make sure that the web server nginx is running
3. On more complex setups: make sure that a firewall or reverse-proxy is not interfering.", + "diagnosis_http_timeout": "Timed-out while trying to contact your server from outside. It appears to be unreachable.
1. The most common cause for this issue is that you did not correctly configure port forwarding for port 80.
2. Also make sure that the web server nginx is running
3. On more complex setups: make sure that a firewall or reverse-proxy is not interfering.", "diagnosis_http_connection_error": "Connection error: could not connect to the requested domain, it's very likely unreachable.", - "diagnosis_http_bad_status_code": "Timed-out while trying to contact your server from outside. It might be that another machine answered instead of your server.
1. The most common cause for this issue is that you did not correctly configure port forwarding for port 80.
2. On more complex setups: make sure that a firewall or reverse-proxy is not interfering.", + "diagnosis_http_bad_status_code": "It looks like another machine (maybe your internet router) answered instead of your server.
1. The most common cause for this issue is that you did not correctly configure port forwarding for port 80.
2. On more complex setups: make sure that a firewall or reverse-proxy is not interfering.", "diagnosis_http_unreachable": "Domain {domain} appears unreachable through HTTP from outside the local network.", "diagnosis_http_partially_unreachable": "Domain {domain} appears unreachable through HTTP from outside the local network in IPv{failed}, though it works in IPv{passed}.", "diagnosis_http_nginx_conf_not_up_to_date": "This domain's nginx configuration appears to have been modified manually, and prevents YunoHost from diagnosing if it's reachable on HTTP.", From 9bd6d39a79ced84d0cb6b3f939aa86ef568754b6 Mon Sep 17 00:00:00 2001 From: tituspijean Date: Sat, 18 Apr 2020 10:53:22 +0200 Subject: [PATCH 1000/3170] [enh] add dynamic variables to systemd helper --- data/helpers.d/systemd | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/data/helpers.d/systemd b/data/helpers.d/systemd index 47e905f0f..5b1e3eaa8 100644 --- a/data/helpers.d/systemd +++ b/data/helpers.d/systemd @@ -2,9 +2,10 @@ # Create a dedicated systemd config # -# usage: ynh_add_systemd_config [--service=service] [--template=template] -# | arg: -s, --service - Service name (optionnal, $app by default) -# | arg: -t, --template - Name of template file (optionnal, this is 'systemd' by default, meaning ./conf/systemd.service will be used as template) +# usage: ynh_add_systemd_config [--service=service] [--template=template] "list of others variables to replace" +# | arg: -s, --service - Service name (optional, $app by default) +# | arg: -t, --template - Name of template file (optional, this is 'systemd' by default, meaning ./conf/systemd.service will be used as template) +# | arg: -v, --others_var= - List of others variables to replace separeted by a space. For example : 'var_1 var_2 ...' # # This will use the template ../conf/.service # to generate a systemd config, by replacing the following keywords @@ -14,17 +15,22 @@ # __APP__ by $app # __FINALPATH__ by $final_path # +# And dynamic variables (from the last example) : +# __PATH_2__ by $path_2 +# __PORT_2__ by $port_2 +# # Requires YunoHost version 2.7.2 or higher. ynh_add_systemd_config () { # Declare an array to define the options of this helper. local legacy_args=st - declare -Ar args_array=( [s]=service= [t]=template= ) + declare -Ar args_array=( [s]=service= [t]=template= [v]=others_var=) local service local template # Manage arguments with getopts ynh_handle_getopts_args "$@" local service="${service:-$app}" local template="${template:-systemd.service}" + local others_var="${others_var:-}" finalsystemdconf="/etc/systemd/system/$service.service" ynh_backup_if_checksum_is_different --file="$finalsystemdconf" @@ -38,6 +44,15 @@ ynh_add_systemd_config () { if test -n "${app:-}"; then ynh_replace_string --match_string="__APP__" --replace_string="$app" --target_file="$finalsystemdconf" fi + + # Replace all other variable given as arguments + for var_to_replace in $others_var + do + # ${var_to_replace^^} make the content of the variable on upper-cases + # ${!var_to_replace} get the content of the variable named $var_to_replace + ynh_replace_string --match_string="__${var_to_replace^^}__" --replace_string="${!var_to_replace}" --target_file="$finalnginxconf" + done + ynh_store_file_checksum --file="$finalsystemdconf" chown root: "$finalsystemdconf" From 34a12c142776a6b558a87ae1793ed34f63ba5798 Mon Sep 17 00:00:00 2001 From: tituspijean Date: Sat, 18 Apr 2020 11:07:20 +0200 Subject: [PATCH 1001/3170] [enh] ynh_add_systemd_config comments Some typos fixes should be propagated to the fail2ban helper too. --- data/helpers.d/systemd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/systemd b/data/helpers.d/systemd index 5b1e3eaa8..6b46dc875 100644 --- a/data/helpers.d/systemd +++ b/data/helpers.d/systemd @@ -2,10 +2,10 @@ # Create a dedicated systemd config # -# usage: ynh_add_systemd_config [--service=service] [--template=template] "list of others variables to replace" +# usage: ynh_add_systemd_config [--service=service] [--template=template] [--others_var="list of others variables to replace"] # | arg: -s, --service - Service name (optional, $app by default) # | arg: -t, --template - Name of template file (optional, this is 'systemd' by default, meaning ./conf/systemd.service will be used as template) -# | arg: -v, --others_var= - List of others variables to replace separeted by a space. For example : 'var_1 var_2 ...' +# | arg: -v, --others_var - List of others variables to replace separated by a space. For example: 'var_1 var_2 ...' # # This will use the template ../conf/.service # to generate a systemd config, by replacing the following keywords From 5777a266a2150fa4490f73fedc25b5e4c4ed38ed Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sat, 18 Apr 2020 12:22:32 +0200 Subject: [PATCH 1002/3170] Update systemd --- data/helpers.d/systemd | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/data/helpers.d/systemd b/data/helpers.d/systemd index 6b46dc875..4f731f5db 100644 --- a/data/helpers.d/systemd +++ b/data/helpers.d/systemd @@ -16,21 +16,22 @@ # __FINALPATH__ by $final_path # # And dynamic variables (from the last example) : -# __PATH_2__ by $path_2 -# __PORT_2__ by $port_2 +# __VAR_1__ by $var_1 +# __VAR_2__ by $var_2 # # Requires YunoHost version 2.7.2 or higher. ynh_add_systemd_config () { # Declare an array to define the options of this helper. - local legacy_args=st - declare -Ar args_array=( [s]=service= [t]=template= [v]=others_var=) + local legacy_args=stv + declare -Ar args_array=( [s]=service= [t]=template= [v]=others_var= ) local service local template + local others_var # Manage arguments with getopts ynh_handle_getopts_args "$@" - local service="${service:-$app}" - local template="${template:-systemd.service}" - local others_var="${others_var:-}" + service="${service:-$app}" + template="${template:-systemd.service}" + others_var="${others_var:-}" finalsystemdconf="/etc/systemd/system/$service.service" ynh_backup_if_checksum_is_different --file="$finalsystemdconf" @@ -44,15 +45,15 @@ ynh_add_systemd_config () { if test -n "${app:-}"; then ynh_replace_string --match_string="__APP__" --replace_string="$app" --target_file="$finalsystemdconf" fi - - # Replace all other variable given as arguments + + # Replace all other variables given as arguments for var_to_replace in $others_var do # ${var_to_replace^^} make the content of the variable on upper-cases # ${!var_to_replace} get the content of the variable named $var_to_replace - ynh_replace_string --match_string="__${var_to_replace^^}__" --replace_string="${!var_to_replace}" --target_file="$finalnginxconf" + ynh_replace_string --match_string="__${var_to_replace^^}__" --replace_string="${!var_to_replace}" --target_file="$finalsystemdconf" done - + ynh_store_file_checksum --file="$finalsystemdconf" chown root: "$finalsystemdconf" From d8feb1b72ae605100e8656f39e874209fa43172f Mon Sep 17 00:00:00 2001 From: ljf Date: Tue, 7 Apr 2020 01:53:05 +0200 Subject: [PATCH 1003/3170] [enh] Add RBL check --- data/hooks/diagnosis/24-mail.py | 89 ++++++++++++++++++++++++++++++++- locales/en.json | 3 ++ 2 files changed, 91 insertions(+), 1 deletion(-) diff --git a/data/hooks/diagnosis/24-mail.py b/data/hooks/diagnosis/24-mail.py index 3f9517bb0..731267593 100644 --- a/data/hooks/diagnosis/24-mail.py +++ b/data/hooks/diagnosis/24-mail.py @@ -1,9 +1,42 @@ #!/usr/bin/env python import os +import dns.resolver + +from moulinette.utils.network import download_text from yunohost.diagnosis import Diagnoser +DEFAULT_BLACKLIST = [ + ('zen.spamhaus.org' , 'Spamhaus SBL, XBL and PBL' ), + ('dnsbl.sorbs.net' , 'SORBS aggregated' ), + ('safe.dnsbl.sorbs.net' , "'safe' subset of SORBS aggregated"), + ('ix.dnsbl.manitu.net' , 'Heise iX NiX Spam' ), + ('babl.rbl.webiron.net' , 'Bad Abuse' ), + ('cabl.rbl.webiron.net' , 'Chronicly Bad Abuse' ), + ('truncate.gbudb.net' , 'Exclusively Spam/Malware' ), + ('dnsbl-1.uceprotect.net' , 'Trapserver Cluster' ), + ('cbl.abuseat.org' , 'Net of traps' ), + ('dnsbl.cobion.com' , 'used in IBM products' ), + ('psbl.surriel.com' , 'passive list, easy to unlist' ), + ('dnsrbl.org' , 'Real-time black list' ), + ('db.wpbl.info' , 'Weighted private' ), + ('bl.spamcop.net' , 'Based on spamcop users' ), + ('dyna.spamrats.com' , 'Dynamic IP addresses' ), + ('spam.spamrats.com' , 'Manual submissions' ), + ('auth.spamrats.com' , 'Suspicious authentications' ), + ('dnsbl.inps.de' , 'automated and reported' ), + ('bl.blocklist.de' , 'fail2ban reports etc.' ), + ('srnblack.surgate.net' , 'feeders' ), + ('all.s5h.net' , 'traps' ), + ('rbl.realtimeblacklist.com' , 'lists ip ranges' ), + ('b.barracudacentral.org' , 'traps' ), + ('hostkarma.junkemailfilter.com', 'Autotected Virus Senders' ), + ('rbl.megarbl.net' , 'Curated Spamtraps' ), + ('ubl.unsubscore.com' , 'Collected Opt-Out Addresses' ), + ('0spam.fusionzero.com' , 'Spam Trap' ), +] + class MailDiagnoser(Diagnoser): @@ -14,6 +47,7 @@ class MailDiagnoser(Diagnoser): def run(self): # Is outgoing port 25 filtered somehow ? + self.logger_debug("Running outgoing 25 port check") if os.system('/bin/nc -z -w2 yunohost.org 25') == 0: yield dict(meta={"test": "ougoing_port_25"}, status="SUCCESS", @@ -23,9 +57,22 @@ class MailDiagnoser(Diagnoser): status="ERROR", summary="diagnosis_mail_ougoing_port_25_blocked") + # Is Reverse DNS well configured ? - # Mail blacklist using dig requests (c.f. ljf's code) + # Are IPs blacklisted ? + self.logger_debug("Running RBL detection") + blacklisted_details = tuple(self.check_blacklisted(self.get_public_ip(4))) + blacklisted_details += tuple(self.check_blacklisted(self.get_public_ip(6))) + if blacklisted_details: + yield dict(meta={}, + status="ERROR", + summary=("diagnosis_mail_blacklist_nok", {}), + details=blacklisted_details) + else: + yield dict(meta={}, + status="SUCCESS", + summary=("diagnosis_mail_blacklist_ok", {})) # SMTP reachability (c.f. check-smtp to be implemented on yunohost's remote diagnoser) @@ -37,6 +84,46 @@ class MailDiagnoser(Diagnoser): # check for unusual failed sending attempt being refused in the logs ? + def check_blacklisted(self, ip): + """ Check with dig onto blacklist DNS server + """ + if ip is None: + return + + for blacklist, description in DEFAULT_BLACKLIST: + + # Determine if we are listed on this RBL + try: + rev = dns.reversename.from_address(ip) + query = str(rev.split(3)[0]) + '.' + blacklist + # TODO add timeout lifetime + dns.resolver.query(query, "A") + except (dns.resolver.NXDOMAIN, dns.resolver.NoNameservers, dns.resolver.NoAnswer, + dns.exception.Timeout): + continue + + # Try to get the reason + reason = "not explained" + try: + reason = str(dns.resolver.query(query, "TXT")[0]) + except Exception: + pass + + yield ('diagnosis_mail_blacklisted_by', + (ip, blacklist, reason)) + + def get_public_ip(self, protocol=4): + # TODO we might call this function from another side + assert protocol in [4, 6], "Invalid protocol version, it should be either 4 or 6 and was '%s'" % repr(protocol) + + url = 'https://ip%s.yunohost.org' % ('6' if protocol == 6 else '') + + try: + return download_text(url, timeout=30).strip() + except Exception as e: + self.logger_debug("Could not get public IPv%s : %s" % (str(protocol), str(e))) + return None + def main(args, env, loggers): return MailDiagnoser(args, env, loggers).diagnose() diff --git a/locales/en.json b/locales/en.json index 800a1d696..dbce8f367 100644 --- a/locales/en.json +++ b/locales/en.json @@ -186,6 +186,9 @@ "diagnosis_swap_ok": "The system has {total} of swap!", "diagnosis_mail_ougoing_port_25_ok": "Outgoing port 25 is not blocked and email can be sent to other servers.", "diagnosis_mail_ougoing_port_25_blocked": "Outgoing port 25 appears to be blocked. You should try to unblock it in your internet service provider (or hosting provider) configuration panel. Meanwhile, the server won't be able to send emails to other servers.", + "diagnosis_mail_blacklist_ok": "Your server public IP are not listed on email blacklist.", + "diagnosis_mail_blacklist_nok": "Your server public IPs are listed on email blacklist.", + "diagnosis_mail_blacklisted_by": "{0} is listed on {1}. Reason: {2}", "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", From bb162662c6007d729c6105c7e40352fba8500015 Mon Sep 17 00:00:00 2001 From: ljf Date: Sat, 11 Apr 2020 19:34:34 +0200 Subject: [PATCH 1004/3170] [enh] Use named var in i18n --- data/hooks/diagnosis/24-mail.py | 20 ++++++++++++-------- locales/en.json | 2 +- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/data/hooks/diagnosis/24-mail.py b/data/hooks/diagnosis/24-mail.py index 731267593..25d0ff984 100644 --- a/data/hooks/diagnosis/24-mail.py +++ b/data/hooks/diagnosis/24-mail.py @@ -62,17 +62,21 @@ class MailDiagnoser(Diagnoser): # Are IPs blacklisted ? self.logger_debug("Running RBL detection") - blacklisted_details = tuple(self.check_blacklisted(self.get_public_ip(4))) - blacklisted_details += tuple(self.check_blacklisted(self.get_public_ip(6))) + ipv4 = Diagnoser.get_cached_report_item("ip", {"test": "ipv4"}) + global_ipv4 = ipv4.get("data", {}).get("global", {}) + ipv6 = Diagnoser.get_cached_report_item("ip", {"test": "ipv6"}) + global_ipv6 = ipv6.get("data", {}).get("global", {}) + blacklisted_details = tuple(self.check_blacklisted(global_ipv4)) + blacklisted_details += tuple(self.check_blacklisted(global_ipv6)) if blacklisted_details: - yield dict(meta={}, + yield dict(meta={"test": "mail_blacklist"}, status="ERROR", - summary=("diagnosis_mail_blacklist_nok", {}), - details=blacklisted_details) + summary="diagnosis_mail_blacklist_nok", + details=list(blacklisted_details)) else: - yield dict(meta={}, + yield dict(meta={"test": "mail_blacklist"}, status="SUCCESS", - summary=("diagnosis_mail_blacklist_ok", {})) + summary="diagnosis_mail_blacklist_ok") # SMTP reachability (c.f. check-smtp to be implemented on yunohost's remote diagnoser) @@ -110,7 +114,7 @@ class MailDiagnoser(Diagnoser): pass yield ('diagnosis_mail_blacklisted_by', - (ip, blacklist, reason)) + {'ip': ip, 'blacklist': blacklist, 'reason': reason}) def get_public_ip(self, protocol=4): # TODO we might call this function from another side diff --git a/locales/en.json b/locales/en.json index dbce8f367..26c51d253 100644 --- a/locales/en.json +++ b/locales/en.json @@ -188,7 +188,7 @@ "diagnosis_mail_ougoing_port_25_blocked": "Outgoing port 25 appears to be blocked. You should try to unblock it in your internet service provider (or hosting provider) configuration panel. Meanwhile, the server won't be able to send emails to other servers.", "diagnosis_mail_blacklist_ok": "Your server public IP are not listed on email blacklist.", "diagnosis_mail_blacklist_nok": "Your server public IPs are listed on email blacklist.", - "diagnosis_mail_blacklisted_by": "{0} is listed on {1}. Reason: {2}", + "diagnosis_mail_blacklisted_by": "{ip} is listed on {blacklist}. Reason: {reason}", "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", From 0b7984adf117a413b63d8604d6b54cea22bc3c87 Mon Sep 17 00:00:00 2001 From: ljf Date: Sun, 12 Apr 2020 04:14:49 +0200 Subject: [PATCH 1005/3170] [enh] Improve DNSBL check --- data/hooks/diagnosis/24-mail.py | 120 +++++++++------------ data/other/dnsbl_list.yml | 184 ++++++++++++++++++++++++++++++++ debian/install | 1 + locales/en.json | 6 +- 4 files changed, 237 insertions(+), 74 deletions(-) create mode 100644 data/other/dnsbl_list.yml diff --git a/data/hooks/diagnosis/24-mail.py b/data/hooks/diagnosis/24-mail.py index 25d0ff984..333d98c8a 100644 --- a/data/hooks/diagnosis/24-mail.py +++ b/data/hooks/diagnosis/24-mail.py @@ -4,38 +4,11 @@ import os import dns.resolver from moulinette.utils.network import download_text +from moulinette.utils.filesystem import read_yaml from yunohost.diagnosis import Diagnoser -DEFAULT_BLACKLIST = [ - ('zen.spamhaus.org' , 'Spamhaus SBL, XBL and PBL' ), - ('dnsbl.sorbs.net' , 'SORBS aggregated' ), - ('safe.dnsbl.sorbs.net' , "'safe' subset of SORBS aggregated"), - ('ix.dnsbl.manitu.net' , 'Heise iX NiX Spam' ), - ('babl.rbl.webiron.net' , 'Bad Abuse' ), - ('cabl.rbl.webiron.net' , 'Chronicly Bad Abuse' ), - ('truncate.gbudb.net' , 'Exclusively Spam/Malware' ), - ('dnsbl-1.uceprotect.net' , 'Trapserver Cluster' ), - ('cbl.abuseat.org' , 'Net of traps' ), - ('dnsbl.cobion.com' , 'used in IBM products' ), - ('psbl.surriel.com' , 'passive list, easy to unlist' ), - ('dnsrbl.org' , 'Real-time black list' ), - ('db.wpbl.info' , 'Weighted private' ), - ('bl.spamcop.net' , 'Based on spamcop users' ), - ('dyna.spamrats.com' , 'Dynamic IP addresses' ), - ('spam.spamrats.com' , 'Manual submissions' ), - ('auth.spamrats.com' , 'Suspicious authentications' ), - ('dnsbl.inps.de' , 'automated and reported' ), - ('bl.blocklist.de' , 'fail2ban reports etc.' ), - ('srnblack.surgate.net' , 'feeders' ), - ('all.s5h.net' , 'traps' ), - ('rbl.realtimeblacklist.com' , 'lists ip ranges' ), - ('b.barracudacentral.org' , 'traps' ), - ('hostkarma.junkemailfilter.com', 'Autotected Virus Senders' ), - ('rbl.megarbl.net' , 'Curated Spamtraps' ), - ('ubl.unsubscore.com' , 'Collected Opt-Out Addresses' ), - ('0spam.fusionzero.com' , 'Spam Trap' ), -] +DEFAULT_DNS_BLACKLIST = "/usr/share/yunohost/other/dnsbl_list.yml" class MailDiagnoser(Diagnoser): @@ -57,17 +30,13 @@ class MailDiagnoser(Diagnoser): status="ERROR", summary="diagnosis_mail_ougoing_port_25_blocked") - # Is Reverse DNS well configured ? + # Forward-confirmed reverse DNS (FCrDNS) verification - # Are IPs blacklisted ? - self.logger_debug("Running RBL detection") - ipv4 = Diagnoser.get_cached_report_item("ip", {"test": "ipv4"}) - global_ipv4 = ipv4.get("data", {}).get("global", {}) - ipv6 = Diagnoser.get_cached_report_item("ip", {"test": "ipv6"}) - global_ipv6 = ipv6.get("data", {}).get("global", {}) - blacklisted_details = tuple(self.check_blacklisted(global_ipv4)) - blacklisted_details += tuple(self.check_blacklisted(global_ipv6)) + # Are IPs listed on a DNSBL ? + self.logger_debug("Running DNSBL detection") + + blacklisted_details = self.check_ip_dnsbl() if blacklisted_details: yield dict(meta={"test": "mail_blacklist"}, status="ERROR", @@ -88,45 +57,54 @@ class MailDiagnoser(Diagnoser): # check for unusual failed sending attempt being refused in the logs ? - def check_blacklisted(self, ip): + def check_blacklisted(self): """ Check with dig onto blacklist DNS server """ - if ip is None: - return + dns_blacklists = read_yaml(DEFAULT_DNS_BLACKLIST) + for ip in self.get_public_ips(): + for blacklist in dns_blacklists: + + if "." in ip and not blacklist.ipv4: + continue - for blacklist, description in DEFAULT_BLACKLIST: + if ":" in ip and not blacklist.ipv6: + continue + + # Determine if we are listed on this RBL + try: + rev = dns.reversename.from_address(ip) + query = str(rev.split(3)[0]) + '.' + blacklist.dns_server + # TODO add timeout lifetime + dns.resolver.query(query, "A") + except (dns.resolver.NXDOMAIN, dns.resolver.NoNameservers, dns.resolver.NoAnswer, + dns.exception.Timeout): + continue - # Determine if we are listed on this RBL - try: - rev = dns.reversename.from_address(ip) - query = str(rev.split(3)[0]) + '.' + blacklist - # TODO add timeout lifetime - dns.resolver.query(query, "A") - except (dns.resolver.NXDOMAIN, dns.resolver.NoNameservers, dns.resolver.NoAnswer, - dns.exception.Timeout): - continue + # Try to get the reason + reason = "not explained" + try: + reason = str(dns.resolver.query(query, "TXT")[0]) + except Exception: + pass - # Try to get the reason - reason = "not explained" - try: - reason = str(dns.resolver.query(query, "TXT")[0]) - except Exception: - pass + yield ('diagnosis_mail_blacklisted_by', { + 'ip': ip, + 'blacklist': blacklist, + 'reason': reason}) - yield ('diagnosis_mail_blacklisted_by', - {'ip': ip, 'blacklist': blacklist, 'reason': reason}) - - def get_public_ip(self, protocol=4): - # TODO we might call this function from another side - assert protocol in [4, 6], "Invalid protocol version, it should be either 4 or 6 and was '%s'" % repr(protocol) - - url = 'https://ip%s.yunohost.org' % ('6' if protocol == 6 else '') - - try: - return download_text(url, timeout=30).strip() - except Exception as e: - self.logger_debug("Could not get public IPv%s : %s" % (str(protocol), str(e))) - return None + def get_public_ips(self): + # Todo code a better way to access a data + ipv4 = Diagnoser.get_cached_report("ip", {"test": "ipv4"}) + if ipv4: + global_ipv4 = ipv4.get("data", {}).get("global", {}) + if global_ipv4: + yield global_ipv4 + + ipv6 = Diagnoser.get_cached_report("ip", {"test": "ipv6"}) + if ipv6: + global_ipv6 = ipv6.get("data", {}).get("global", {}) + if global_ipv6: + yield global_ipv6 def main(args, env, loggers): diff --git a/data/other/dnsbl_list.yml b/data/other/dnsbl_list.yml new file mode 100644 index 000000000..839aeaab6 --- /dev/null +++ b/data/other/dnsbl_list.yml @@ -0,0 +1,184 @@ +# Used by GAFAM +- name: Spamhaus ZEN + dns_server: zen.spamhaus.org + website: https://www.spamhaus.org/zen/ + ipv4: true + ipv6: true + domain: false +- name: Barracuda Reputation Block List + dns_server: b.barracudacentral.org + website: https://barracudacentral.org/rbl/ + ipv4: true + ipv6: false + domain: false +- name: Hostkarma + dns_server: hostkarma.junkemailfilter.com + website: https://ipadmin.junkemailfilter.com/remove.php + ipv4: true + ipv6: false + domain: false +- name: ImproWare IP based spamlist + dns_server: spamrbl.imp.ch + website: https://antispam.imp.ch/ + ipv4: true + ipv6: false + domain: false +- name: ImproWare IP based wormlist + dns_server: wormrbl.imp.ch + website: https://antispam.imp.ch/ + ipv4: true + ipv6: false + domain: false +- name: Backscatterer.org + dns_server: ips.backscatterer.org + website: http://www.backscatterer.org/ + ipv4: true + ipv6: false + domain: false +- name: inps.de + dns_server: dnsbl.inps.de + website: http://dnsbl.inps.de/ + ipv4: true + ipv6: false + domain: false +- name: LASHBACK + dns_server: ubl.unsubscore.com + website: https://blacklist.lashback.com/ + ipv4: true + ipv6: false + domain: false +- name: Mailspike.org + dns_server: bl.mailspike.net + website: http://www.mailspike.net/ + ipv4: true + ipv6: false + domain: false +- name: NiX Spam + dns_server: ix.dnsbl.manitu.net + website: http://www.dnsbl.manitu.net/ + ipv4: true + ipv6: false + domain: false +- name: REDHAWK + dns_server: access.redhawk.org + website: https://www.redhawk.org/SpamHawk/query.php + ipv4: true + ipv6: false + domain: false +- name: SORBS Open SMTP relays + dns_server: smtp.dnsbl.sorbs.net + website: http://www.sorbs.net/ + ipv4: true + ipv6: false + domain: false +- name: SORBS Spamhost (last 28 days) + dns_server: recent.spam.dnsbl.sorbs.net + website: http://www.sorbs.net/ + ipv4: true + ipv6: false + domain: false +- name: SORBS Spamhost (last 48 hours) + dns_server: new.spam.dnsbl.sorbs.net + website: http://www.sorbs.net/ + ipv4: true + ipv6: false + domain: false +- name: SpamCop Blocking List + dns_server: bl.spamcop.net + website: https://www.spamcop.net/bl.shtml + ipv4: true + ipv6: false + domain: false +- name: Spam Eating Monkey SEM-BACKSCATTER + dns_server: backscatter.spameatingmonkey.net + website: https://spameatingmonkey.com/services + ipv4: true + ipv6: false + domain: false +- name: Spam Eating Monkey SEM-BLACK + dns_server: bl.spameatingmonkey.net + website: https://spameatingmonkey.com/services + ipv4: true + ipv6: false + domain: false +- name: Spam Eating Monkey SEM-IPV6BL + dns_server: bl.ipv6.spameatingmonkey.net + website: https://spameatingmonkey.com/services + ipv4: false + ipv6: true + domain: false +- name: SpamRATS! all + dns_server: all.spamrats.com + website: http://www.spamrats.com/ + ipv4: true + ipv6: false + domain: false +- name: PSBL (Passive Spam Block List) + dns_server: psbl.surriel.com + website: http://psbl.surriel.com/ + ipv4: true + ipv6: false + domain: false +- name: SWINOG + dns_server: dnsrbl.swinog.ch + website: https://antispam.imp.ch/ + ipv4: true + ipv6: false + domain: false +- name: GBUdb Truncate + dns_server: truncate.gbudb.net + website: http://www.gbudb.com/truncate/index.jsp + ipv4: true + ipv6: false + domain: false +- name: Weighted Private Block List + dns_server: db.wpbl.info + website: http://www.wpbl.info/ + ipv4: true + ipv6: false + domain: false +# Used by GAFAM +- name: Composite Blocking List + dns_server: cbl.abuseat.org + website: cbl.abuseat.org + ipv4: true + ipv6: false + domain: false +# Used by GAFAM +- name: SenderScore Blacklist + dns_server: bl.score.senderscore.com + website: https://senderscore.com + ipv4: true + ipv6: false + domain: false +- name: Invaluement + dns_server: sip.invaluement.com + website: https://www.invaluement.com/ + ipv4: true + ipv6: false + domain: false +# Added cause it supports IPv6 +- name: AntiCaptcha.NET IPv6 + dns_server: dnsbl6.anticaptcha.net + website: http://anticaptcha.net/ + ipv4: false + ipv6: true + domain: false +- name: SPFBL.net RBL + dns_server: dnsbl.spfbl.net + website: https://spfbl.net/en/dnsbl/ + ipv4: true + ipv6: true + domain: true +- name: Suomispam Blacklist + dns_server: bl.suomispam.net + website: http://suomispam.net/ + ipv4: true + ipv6: true + domain: false +- name: NordSpam + dns_server: bl.nordspam.com + website: https://www.nordspam.com/ + ipv4: true + ipv6: true + domain: false diff --git a/debian/install b/debian/install index e0743cdd1..cf682d958 100644 --- a/debian/install +++ b/debian/install @@ -7,6 +7,7 @@ data/hooks/* /usr/share/yunohost/hooks/ data/other/yunoprompt.service /etc/systemd/system/ data/other/password/* /usr/share/yunohost/other/password/ data/other/dpkg-origins/yunohost /etc/dpkg/origins +data/other/dnsbl_list.yml /usr/share/yunohost/other/dnsbl_list.yml data/other/* /usr/share/yunohost/yunohost-config/moulinette/ data/templates/* /usr/share/yunohost/templates/ data/helpers /usr/share/yunohost/ diff --git a/locales/en.json b/locales/en.json index 26c51d253..37ae2a34f 100644 --- a/locales/en.json +++ b/locales/en.json @@ -186,9 +186,9 @@ "diagnosis_swap_ok": "The system has {total} of swap!", "diagnosis_mail_ougoing_port_25_ok": "Outgoing port 25 is not blocked and email can be sent to other servers.", "diagnosis_mail_ougoing_port_25_blocked": "Outgoing port 25 appears to be blocked. You should try to unblock it in your internet service provider (or hosting provider) configuration panel. Meanwhile, the server won't be able to send emails to other servers.", - "diagnosis_mail_blacklist_ok": "Your server public IP are not listed on email blacklist.", - "diagnosis_mail_blacklist_nok": "Your server public IPs are listed on email blacklist.", - "diagnosis_mail_blacklisted_by": "{ip} is listed on {blacklist}. Reason: {reason}", + "diagnosis_mail_blacklist_ok": "Your server public IP are not listed on email blacklists.", + "diagnosis_mail_blacklist_nok": "Your server public IPs are listed on email blacklists.", + "diagnosis_mail_blacklisted_by": "{ip} is listed on {blacklist.name}. Reason: {reason}. See {blacklist.website}", "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", From 5b0698e798421c1a3d71147c279b326b4b2726a6 Mon Sep 17 00:00:00 2001 From: ljf Date: Mon, 13 Apr 2020 16:41:27 +0200 Subject: [PATCH 1006/3170] [fix] Bad call to dict --- data/hooks/diagnosis/24-mail.py | 19 ++++++++++--------- locales/en.json | 2 +- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/data/hooks/diagnosis/24-mail.py b/data/hooks/diagnosis/24-mail.py index 333d98c8a..f4f897e28 100644 --- a/data/hooks/diagnosis/24-mail.py +++ b/data/hooks/diagnosis/24-mail.py @@ -36,12 +36,13 @@ class MailDiagnoser(Diagnoser): # Are IPs listed on a DNSBL ? self.logger_debug("Running DNSBL detection") - blacklisted_details = self.check_ip_dnsbl() + blacklisted_details = list(self.check_dnsbl(self.get_public_ips())) + print(blacklisted_details) if blacklisted_details: yield dict(meta={"test": "mail_blacklist"}, status="ERROR", summary="diagnosis_mail_blacklist_nok", - details=list(blacklisted_details)) + details=blacklisted_details) else: yield dict(meta={"test": "mail_blacklist"}, status="SUCCESS", @@ -57,23 +58,22 @@ class MailDiagnoser(Diagnoser): # check for unusual failed sending attempt being refused in the logs ? - def check_blacklisted(self): + def check_dnsbl(self, ips): """ Check with dig onto blacklist DNS server """ dns_blacklists = read_yaml(DEFAULT_DNS_BLACKLIST) - for ip in self.get_public_ips(): + for ip in ips: for blacklist in dns_blacklists: - - if "." in ip and not blacklist.ipv4: + if "." in ip and not blacklist['ipv4']: continue - if ":" in ip and not blacklist.ipv6: + if ":" in ip and not blacklist['ipv6']: continue # Determine if we are listed on this RBL try: rev = dns.reversename.from_address(ip) - query = str(rev.split(3)[0]) + '.' + blacklist.dns_server + query = str(rev.split(3)[0]) + '.' + blacklist['dns_server'] # TODO add timeout lifetime dns.resolver.query(query, "A") except (dns.resolver.NXDOMAIN, dns.resolver.NoNameservers, dns.resolver.NoAnswer, @@ -89,7 +89,8 @@ class MailDiagnoser(Diagnoser): yield ('diagnosis_mail_blacklisted_by', { 'ip': ip, - 'blacklist': blacklist, + 'blacklist_name': blacklist['name'], + 'blacklist_website': blacklist['website'], 'reason': reason}) def get_public_ips(self): diff --git a/locales/en.json b/locales/en.json index 37ae2a34f..93f7680bf 100644 --- a/locales/en.json +++ b/locales/en.json @@ -188,7 +188,7 @@ "diagnosis_mail_ougoing_port_25_blocked": "Outgoing port 25 appears to be blocked. You should try to unblock it in your internet service provider (or hosting provider) configuration panel. Meanwhile, the server won't be able to send emails to other servers.", "diagnosis_mail_blacklist_ok": "Your server public IP are not listed on email blacklists.", "diagnosis_mail_blacklist_nok": "Your server public IPs are listed on email blacklists.", - "diagnosis_mail_blacklisted_by": "{ip} is listed on {blacklist.name}. Reason: {reason}. See {blacklist.website}", + "diagnosis_mail_blacklisted_by": "{ip} is listed on {blacklist_name}. Reason: {reason}. See {blacklist_website}", "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", From 027a0ed73c9281fd35582d9e683348483f12f7bd Mon Sep 17 00:00:00 2001 From: ljf Date: Tue, 14 Apr 2020 03:56:35 +0200 Subject: [PATCH 1007/3170] [wip] Add rDNS and mailqueue check --- data/hooks/diagnosis/24-mail.py | 78 ++++++++++++++++++++++++++++++--- locales/en.json | 12 ++++- 2 files changed, 82 insertions(+), 8 deletions(-) diff --git a/data/hooks/diagnosis/24-mail.py b/data/hooks/diagnosis/24-mail.py index f4f897e28..b91bfec85 100644 --- a/data/hooks/diagnosis/24-mail.py +++ b/data/hooks/diagnosis/24-mail.py @@ -2,11 +2,15 @@ import os import dns.resolver +import smtplib +import socket +from moulinette.utils.process import check_output from moulinette.utils.network import download_text from moulinette.utils.filesystem import read_yaml from yunohost.diagnosis import Diagnoser +from yunohost.domain import _get_maindomain DEFAULT_DNS_BLACKLIST = "/usr/share/yunohost/other/dnsbl_list.yml" @@ -18,6 +22,8 @@ class MailDiagnoser(Diagnoser): dependencies = ["ip"] def run(self): + + ips = self.get_public_ips() # Is outgoing port 25 filtered somehow ? self.logger_debug("Running outgoing 25 port check") @@ -30,14 +36,56 @@ class MailDiagnoser(Diagnoser): status="ERROR", summary="diagnosis_mail_ougoing_port_25_blocked") - # Forward-confirmed reverse DNS (FCrDNS) verification + # Get HELO and be sure postfix is running + # TODO SMTP reachability (c.f. check-smtp to be implemented on yunohost's remote diagnoser) + server = None + result = dict(meta={"test": "mail_ehlo"}, + status="SUCCESS", + summary="diagnosis_mail_service_working") + try: + server = smtplib.SMTP("127.0.0.1", 25, timeout=10) + ehlo = server.ehlo() + ehlo_domain = ehlo[1].decode("utf-8").split("\n")[0] + except OSError: + result = dict(meta={"test": "mail_ehlo"}, + status="ERROR", + summary="diagnosis_mail_service_not_working") + ehlo_domain = _get_maindomain() + if server: + server.quit() + yield result + # Forward-confirmed reverse DNS (FCrDNS) verification + self.logger_debug("Running Forward-confirmed reverse DNS check") + for ip in ips: + try: + rdns_domain, _, _ = socket.gethostbyaddr(ip) + except socket.herror as e: + yield dict(meta={"test": "mail_fcrdns"}, + data={"ip": ip, "ehlo_domain": ehlo_domain}, + status="ERROR", + summary="diagnosis_mail_reverse_dns_missing") + continue + else: + if rdns_domain != ehlo_domain: + yield dict(meta={"test": "mail_fcrdns"}, + data={"ip": ip, "ehlo_domain": ehlo_domain, + "rdns_domain": rdns_domain}, + status="ERROR", + summary="diagnosis_mail_rdns_different_from_ehlo_domain") + else: + yield dict(meta={"test": "mail_fcrdns"}, + data={"ip": ip, "ehlo_domain": ehlo_domain}, + status="SUCCESS", + summary="diagnosis_mail_rdns_equal_to_ehlo_domain") + + # TODO Is a A/AAAA and MX Record ? # Are IPs listed on a DNSBL ? - self.logger_debug("Running DNSBL detection") + self.logger_debug("Running DNS Blacklist detection") + # TODO Test if domain are blacklisted too blacklisted_details = list(self.check_dnsbl(self.get_public_ips())) - print(blacklisted_details) if blacklisted_details: yield dict(meta={"test": "mail_blacklist"}, status="ERROR", @@ -48,11 +96,29 @@ class MailDiagnoser(Diagnoser): status="SUCCESS", summary="diagnosis_mail_blacklist_ok") - # SMTP reachability (c.f. check-smtp to be implemented on yunohost's remote diagnoser) + # TODO Are outgoing public IPs authorized to send mail by SPF ? + + # TODO Validate DKIM and dmarc ? - # ideally, SPF / DMARC / DKIM validation ... (c.f. https://github.com/alexAubin/yunoScripts/blob/master/yunoDKIM.py possibly though that looks horrible) - # check that the mail queue is not filled with hundreds of email pending + # Is mail queue filled with hundreds of email pending ? + command = 'postqueue -p | grep -c "^[A-Z0-9]"' + output = check_output(command).strip() + try: + pending_emails = int(output) + except ValueError: + yield dict(meta={"test": "mail_queue"}, + status="ERROR", + summary="diagnosis_mail_cannot_get_queue") + else: + if pending_emails > 300: + yield dict(meta={"test": "mail_queue"}, + status="WARNING", + summary="diagnosis_mail_queue_too_many_pending_emails") + else: + yield dict(meta={"test": "mail_queue"}, + status="INFO", + summary="diagnosis_mail_queue_ok") # check that the recent mail logs are not filled with thousand of email sending (unusual number of mail sent) diff --git a/locales/en.json b/locales/en.json index 93f7680bf..978ceb831 100644 --- a/locales/en.json +++ b/locales/en.json @@ -186,9 +186,17 @@ "diagnosis_swap_ok": "The system has {total} of swap!", "diagnosis_mail_ougoing_port_25_ok": "Outgoing port 25 is not blocked and email can be sent to other servers.", "diagnosis_mail_ougoing_port_25_blocked": "Outgoing port 25 appears to be blocked. You should try to unblock it in your internet service provider (or hosting provider) configuration panel. Meanwhile, the server won't be able to send emails to other servers.", - "diagnosis_mail_blacklist_ok": "Your server public IP are not listed on email blacklists.", - "diagnosis_mail_blacklist_nok": "Your server public IPs are listed on email blacklists.", + "diagnosis_mail_blacklist_ok": "The public IPs of this instance are not listed on email blacklists.", + "diagnosis_mail_blacklist_nok": "Some of the public IPs of this instance are listed on email blacklists.", "diagnosis_mail_blacklisted_by": "{ip} is listed on {blacklist_name}. Reason: {reason}. See {blacklist_website}", + "diagnosis_mail_service_working": "Postfix mail service answer correctly.", + "diagnosis_mail_service_not_working": "Postfix mail service don't answer to EHLO request.", + "diagnosis_mail_reverse_dns_missing": "No reverse DNS defined for the ip {ip}.", + "diagnosis_mail_rdns_different_from_ehlo_domain": "Your reverse DNS {rdns_domain} is different from your EHLO domain {ehlo_domain} on {ip}.", + "diagnosis_mail_rdns_equal_to_ehlo_domain": "Your reverse DNS is equal to your EHLO domain {ehlo_domain} on {ip}.", + "diagnosis_mail_queue_unavailable": "Can not consult number of pending emails in queue", + "diagnosis_mail_queue_too_big": "The mail queue has {nb_pending} pending emails in the mail queue. It seems abnormal.", + "diagnosis_mail_queue_unavailable": "The mail queue has {nb_pending} pending emails in the mail queue.", "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", From da6ae405dd426fabb72d9673bfc2b5ac02accbe2 Mon Sep 17 00:00:00 2001 From: ljf Date: Tue, 14 Apr 2020 03:59:33 +0200 Subject: [PATCH 1008/3170] [fix] Missing pending number args --- data/hooks/diagnosis/24-mail.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/data/hooks/diagnosis/24-mail.py b/data/hooks/diagnosis/24-mail.py index b91bfec85..f1a267641 100644 --- a/data/hooks/diagnosis/24-mail.py +++ b/data/hooks/diagnosis/24-mail.py @@ -113,10 +113,12 @@ class MailDiagnoser(Diagnoser): else: if pending_emails > 300: yield dict(meta={"test": "mail_queue"}, + data={'nb_pending': pending_emails}, status="WARNING", summary="diagnosis_mail_queue_too_many_pending_emails") else: yield dict(meta={"test": "mail_queue"}, + data={'nb_pending': pending_emails}, status="INFO", summary="diagnosis_mail_queue_ok") From a17adc274c90517b42bdcdf31d9a12b58f43d7d9 Mon Sep 17 00:00:00 2001 From: ljf Date: Sat, 18 Apr 2020 17:08:09 +0200 Subject: [PATCH 1009/3170] [wip] Small refactoring for mail diagnoser --- data/hooks/diagnosis/24-mail.py | 279 ++++++++++++++++++-------------- locales/en.json | 15 +- 2 files changed, 165 insertions(+), 129 deletions(-) diff --git a/data/hooks/diagnosis/24-mail.py b/data/hooks/diagnosis/24-mail.py index f1a267641..1336e8c2b 100644 --- a/data/hooks/diagnosis/24-mail.py +++ b/data/hooks/diagnosis/24-mail.py @@ -2,15 +2,21 @@ import os import dns.resolver -import smtplib import socket +import re + +from subprocess import CalledProcessError +from types import FunctionType from moulinette.utils.process import check_output from moulinette.utils.network import download_text from moulinette.utils.filesystem import read_yaml from yunohost.diagnosis import Diagnoser -from yunohost.domain import _get_maindomain +from yunohost.domain import _get_maindomain, domain_list +from yunohost.utils.error import YunohostError + +DIAGNOSIS_SERVER = "diagnosis.yunohost.org" DEFAULT_DNS_BLACKLIST = "/usr/share/yunohost/other/dnsbl_list.yml" @@ -22,126 +28,124 @@ class MailDiagnoser(Diagnoser): dependencies = ["ip"] def run(self): - - ips = self.get_public_ips() - # Is outgoing port 25 filtered somehow ? - self.logger_debug("Running outgoing 25 port check") - if os.system('/bin/nc -z -w2 yunohost.org 25') == 0: - yield dict(meta={"test": "ougoing_port_25"}, - status="SUCCESS", - summary="diagnosis_mail_ougoing_port_25_ok") - else: - yield dict(meta={"test": "outgoing_port_25"}, - status="ERROR", - summary="diagnosis_mail_ougoing_port_25_blocked") + self.ehlo_domain = _get_maindomain() + self.mail_domains = domain_list()["domains"] + self.ipversions, self.ips = self.get_ips_checked() - # Get HELO and be sure postfix is running - # TODO SMTP reachability (c.f. check-smtp to be implemented on yunohost's remote diagnoser) - server = None - result = dict(meta={"test": "mail_ehlo"}, - status="SUCCESS", - summary="diagnosis_mail_service_working") - try: - server = smtplib.SMTP("127.0.0.1", 25, timeout=10) - ehlo = server.ehlo() - ehlo_domain = ehlo[1].decode("utf-8").split("\n")[0] - except OSError: - result = dict(meta={"test": "mail_ehlo"}, - status="ERROR", - summary="diagnosis_mail_service_not_working") - ehlo_domain = _get_maindomain() - if server: - server.quit() - yield result + # TODO Is a A/AAAA and MX Record ? + # TODO Are outgoing public IPs authorized to send mail by SPF ? + # TODO Validate DKIM and dmarc ? + # TODO check that the recent mail logs are not filled with thousand of email sending (unusual number of mail sent) + # TODO check for unusual failed sending attempt being refused in the logs ? + checks = [name for name, value in MailDiagnoser.__dict__.items() + if type(value) == FunctionType and name.startswith("check_")] + for check in checks: + self.logger_debug("Running " + check) + for report in getattr(self, check): + yield report + else: + name = checks[6:] + yield dict(meta={"test": "mail_" + name}, + status="SUCCESS", + summary="diagnosis_mail_" + name + "_ok") - # Forward-confirmed reverse DNS (FCrDNS) verification - self.logger_debug("Running Forward-confirmed reverse DNS check") - for ip in ips: + + def check_outgoing_port_25(self): + """ + Check outgoing port 25 is open and not blocked by router + This check is ran on IPs we could used to send mail. + """ + + for ipversion in self.ipversions: + cmd = '/bin/nc -{ipversion} -z -w2 yunohost.org 25'.format({ + 'ipversion': ipversion}) + if os.system(cmd) != 0: + yield dict(meta={"test": "outgoing_port_25", "ipversion": ipversion}, + data={}, + status="ERROR", + summary="diagnosis_mail_ougoing_port_25_blocked") + + + def check_ehlo(self): + """ + Check the server is reachable from outside and it's the good one + This check is ran on IPs we could used to send mail. + """ + + for ipversion in self.ipversions: + try: + r = Diagnoser.remote_diagnosis('check-smtp', + data={}, + ipversion=ipversion) + except Exception as e: + yield dict(meta={"test": "mail_ehlo", "ipversion": ipversion}, + data={"error": e}, + status="WARNING", + summary="diagnosis_mail_ehlo_could_not_diagnose") + continue + + if r["status"] == "error_smtp_unreachable": + yield dict(meta={"test": "mail_ehlo", "ipversion": ipversion}, + data={}, + status="ERROR", + summary="diagnosis_mail_ehlo_unavailable") + elif r["helo"] != self.ehlo_domain: + yield dict(meta={"test": "mail_ehlo", "ipversion": ipversion}, + data={"wrong_ehlo": r["helo"], "right_ehlo": self.ehlo_domain}, + status="ERROR", + summary="diagnosis_mail_ehlo_wrong") + + + def check_fcrdns(self): + """ + Check the reverse DNS is well defined by doing a Forward-confirmed + reverse DNS check + This check is ran on IPs we could used to send mail. + """ + + for ip in self.ips: try: rdns_domain, _, _ = socket.gethostbyaddr(ip) - except socket.herror as e: - yield dict(meta={"test": "mail_fcrdns"}, - data={"ip": ip, "ehlo_domain": ehlo_domain}, + except socket.herror: + yield dict(meta={"test": "mail_fcrdns", "ip": ip}, + data={"ehlo_domain": self.ehlo_domain}, status="ERROR", summary="diagnosis_mail_reverse_dns_missing") continue - else: - if rdns_domain != ehlo_domain: - yield dict(meta={"test": "mail_fcrdns"}, - data={"ip": ip, "ehlo_domain": ehlo_domain, - "rdns_domain": rdns_domain}, - status="ERROR", - summary="diagnosis_mail_rdns_different_from_ehlo_domain") - else: - yield dict(meta={"test": "mail_fcrdns"}, - data={"ip": ip, "ehlo_domain": ehlo_domain}, - status="SUCCESS", - summary="diagnosis_mail_rdns_equal_to_ehlo_domain") - - # TODO Is a A/AAAA and MX Record ? - - # Are IPs listed on a DNSBL ? - self.logger_debug("Running DNS Blacklist detection") - # TODO Test if domain are blacklisted too - - blacklisted_details = list(self.check_dnsbl(self.get_public_ips())) - if blacklisted_details: - yield dict(meta={"test": "mail_blacklist"}, - status="ERROR", - summary="diagnosis_mail_blacklist_nok", - details=blacklisted_details) - else: - yield dict(meta={"test": "mail_blacklist"}, - status="SUCCESS", - summary="diagnosis_mail_blacklist_ok") - - # TODO Are outgoing public IPs authorized to send mail by SPF ? - - # TODO Validate DKIM and dmarc ? + if rdns_domain != self.ehlo_domain: + yield dict(meta={"test": "mail_fcrdns", "ip": ip}, + data={"ehlo_domain": self.ehlo_domain, + "rdns_domain": rdns_domain}, + status="ERROR", + summary="diagnosis_mail_rdns_different_from_ehlo_domain") - # Is mail queue filled with hundreds of email pending ? - command = 'postqueue -p | grep -c "^[A-Z0-9]"' - output = check_output(command).strip() - try: - pending_emails = int(output) - except ValueError: - yield dict(meta={"test": "mail_queue"}, - status="ERROR", - summary="diagnosis_mail_cannot_get_queue") - else: - if pending_emails > 300: - yield dict(meta={"test": "mail_queue"}, - data={'nb_pending': pending_emails}, - status="WARNING", - summary="diagnosis_mail_queue_too_many_pending_emails") - else: - yield dict(meta={"test": "mail_queue"}, - data={'nb_pending': pending_emails}, - status="INFO", - summary="diagnosis_mail_queue_ok") - - # check that the recent mail logs are not filled with thousand of email sending (unusual number of mail sent) - - # check for unusual failed sending attempt being refused in the logs ? - - def check_dnsbl(self, ips): - """ Check with dig onto blacklist DNS server + def check_blacklist(self): """ + Check with dig onto blacklist DNS server + This check is ran on IPs and domains we could used to send mail. + """ + dns_blacklists = read_yaml(DEFAULT_DNS_BLACKLIST) - for ip in ips: + for item in self.ips + self.mail_domains: for blacklist in dns_blacklists: - if "." in ip and not blacklist['ipv4']: + item_type = "domain" + if ":" in item: + item_type = 'ipv6' + elif re.match(r'^\d+\.\d+\.\d+\.\d+$', item): + item_type = 'ipv4' + + if not blacklist[item_type]: continue - if ":" in ip and not blacklist['ipv6']: - continue - # Determine if we are listed on this RBL try: - rev = dns.reversename.from_address(ip) - query = str(rev.split(3)[0]) + '.' + blacklist['dns_server'] + subdomain = item + if item_type != "domain": + rev = dns.reversename.from_address(item) + subdomain = str(rev.split(3)[0]) + query = subdomain + '.' + blacklist['dns_server'] # TODO add timeout lifetime dns.resolver.query(query, "A") except (dns.resolver.NXDOMAIN, dns.resolver.NoNameservers, dns.resolver.NoAnswer, @@ -149,32 +153,63 @@ class MailDiagnoser(Diagnoser): continue # Try to get the reason - reason = "not explained" try: reason = str(dns.resolver.query(query, "TXT")[0]) except Exception: - pass + reason = "-" - yield ('diagnosis_mail_blacklisted_by', { - 'ip': ip, - 'blacklist_name': blacklist['name'], - 'blacklist_website': blacklist['website'], - 'reason': reason}) + yield dict(meta={"test": "mail_blacklist", "item": item, + "blacklist": blacklist["dns_server"]}, + data={'blacklist_name': blacklist['name'], + 'blacklist_website': blacklist['website'], + 'reason': reason}, + status="ERROR", + summary='diagnosis_mail_blacklist_listed_by') - def get_public_ips(self): - # Todo code a better way to access a data - ipv4 = Diagnoser.get_cached_report("ip", {"test": "ipv4"}) - if ipv4: + def check_queue(self): + """ + Check mail queue is not filled with hundreds of email pending + """ + + command = 'postqueue -p | grep -v "Mail queue is empty" | grep -c "^[A-Z0-9]"' + try: + output = check_output(command).strip() + pending_emails = int(output) + except (ValueError, CalledProcessError) as e: + yield dict(meta={"test": "mail_queue"}, + data={"error": e}, + status="ERROR", + summary="diagnosis_mail_cannot_get_queue") + else: + if pending_emails > 100: + yield dict(meta={"test": "mail_queue"}, + data={'nb_pending': pending_emails}, + status="WARNING", + summary="diagnosis_mail_queue_too_many_pending_emails") + else: + yield dict(meta={"test": "mail_queue"}, + data={'nb_pending': pending_emails}, + status="SUCCESS", + summary="diagnosis_mail_queue_ok") + + + def get_ips_checked(self): + outgoing_ipversions = [] + outgoing_ips = [] + ipv4 = Diagnoser.get_cached_report("ip", {"test": "ipv4"}) or {} + if ipv4.get("status") == "SUCCESS": + outgoing_ipversions.append(4) global_ipv4 = ipv4.get("data", {}).get("global", {}) if global_ipv4: - yield global_ipv4 - - ipv6 = Diagnoser.get_cached_report("ip", {"test": "ipv6"}) - if ipv6: + outgoing_ips.append(global_ipv4) + + ipv6 = Diagnoser.get_cached_report("ip", {"test": "ipv6"}) or {} + if ipv6.get("status") == "SUCCESS": + outgoing_ipversions.append(6) global_ipv6 = ipv6.get("data", {}).get("global", {}) if global_ipv6: - yield global_ipv6 - + outgoing_ips.append(global_ipv6) + return (outgoing_ipversions, outgoing_ips) def main(args, env, loggers): return MailDiagnoser(args, env, loggers).diagnose() diff --git a/locales/en.json b/locales/en.json index 978ceb831..1a17c484f 100644 --- a/locales/en.json +++ b/locales/en.json @@ -185,18 +185,19 @@ "diagnosis_swap_notsomuch": "The system has only {total} swap. You should consider having at least 256 MB to avoid situations where the system runs out of memory.", "diagnosis_swap_ok": "The system has {total} of swap!", "diagnosis_mail_ougoing_port_25_ok": "Outgoing port 25 is not blocked and email can be sent to other servers.", - "diagnosis_mail_ougoing_port_25_blocked": "Outgoing port 25 appears to be blocked. You should try to unblock it in your internet service provider (or hosting provider) configuration panel. Meanwhile, the server won't be able to send emails to other servers.", - "diagnosis_mail_blacklist_ok": "The public IPs of this instance are not listed on email blacklists.", - "diagnosis_mail_blacklist_nok": "Some of the public IPs of this instance are listed on email blacklists.", - "diagnosis_mail_blacklisted_by": "{ip} is listed on {blacklist_name}. Reason: {reason}. See {blacklist_website}", - "diagnosis_mail_service_working": "Postfix mail service answer correctly.", - "diagnosis_mail_service_not_working": "Postfix mail service don't answer to EHLO request.", + "diagnosis_mail_ougoing_port_25_blocked": "Outgoing port 25 appears to be blocked in IPv{ipversion}. You should try to unblock it in your internet service provider (or hosting provider) configuration panel. Meanwhile, the server won't be able to send emails to other servers.", + "diagnosis_mail_ehlo_ok": "Postfix mail service answer correctly from outside", + "diagnosis_mail_ehlo_unavailable": "Postfix mail service don't answer to EHLO request on IPv{ipversion}.", + "diagnosis_mail_ehlo_wrong": "A mail server answer {wrong_ehlo} instead {right_ehlo} on IPv{ipversion}.", + "diagnosis_mail_ehlo_could_not_diagnose": "Could not diagnose if postfix mail server is reachable from outside. Error: {error}", "diagnosis_mail_reverse_dns_missing": "No reverse DNS defined for the ip {ip}.", "diagnosis_mail_rdns_different_from_ehlo_domain": "Your reverse DNS {rdns_domain} is different from your EHLO domain {ehlo_domain} on {ip}.", "diagnosis_mail_rdns_equal_to_ehlo_domain": "Your reverse DNS is equal to your EHLO domain {ehlo_domain} on {ip}.", + "diagnosis_mail_blacklist_ok": "The public IPs of this instance are not listed on email blacklists.", + "diagnosis_mail_blacklist_listed_by": "{item} is blacklisted on {blacklist_name}. Reason: {reason}. See {blacklist_website}", "diagnosis_mail_queue_unavailable": "Can not consult number of pending emails in queue", "diagnosis_mail_queue_too_big": "The mail queue has {nb_pending} pending emails in the mail queue. It seems abnormal.", - "diagnosis_mail_queue_unavailable": "The mail queue has {nb_pending} pending emails in the mail queue.", + "diagnosis_mail_queue_ok": "The mail queue has {nb_pending} pending emails in the mail queue.", "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", From b1124b7080aae3c1750503b430cfc4c067184f7c Mon Sep 17 00:00:00 2001 From: ljf Date: Sat, 18 Apr 2020 19:06:45 +0200 Subject: [PATCH 1010/3170] [fix] Maildiagnoser typo --- data/hooks/diagnosis/24-mail.py | 22 +++++++++++----------- locales/en.json | 10 +++++----- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/data/hooks/diagnosis/24-mail.py b/data/hooks/diagnosis/24-mail.py index 1336e8c2b..4c36d7ca0 100644 --- a/data/hooks/diagnosis/24-mail.py +++ b/data/hooks/diagnosis/24-mail.py @@ -24,7 +24,7 @@ DEFAULT_DNS_BLACKLIST = "/usr/share/yunohost/other/dnsbl_list.yml" class MailDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] - cache_duration = 600 + cache_duration = 0 dependencies = ["ip"] def run(self): @@ -42,10 +42,11 @@ class MailDiagnoser(Diagnoser): if type(value) == FunctionType and name.startswith("check_")] for check in checks: self.logger_debug("Running " + check) - for report in getattr(self, check): + reports = list(getattr(self, check)()) + for report in reports: yield report - else: - name = checks[6:] + if not reports: + name = check[6:] yield dict(meta={"test": "mail_" + name}, status="SUCCESS", summary="diagnosis_mail_" + name + "_ok") @@ -58,8 +59,7 @@ class MailDiagnoser(Diagnoser): """ for ipversion in self.ipversions: - cmd = '/bin/nc -{ipversion} -z -w2 yunohost.org 25'.format({ - 'ipversion': ipversion}) + cmd = '/bin/nc -{ipversion} -z -w2 yunohost.org 25'.format(ipversion=ipversion) if os.system(cmd) != 0: yield dict(meta={"test": "outgoing_port_25", "ipversion": ipversion}, data={}, @@ -80,7 +80,7 @@ class MailDiagnoser(Diagnoser): ipversion=ipversion) except Exception as e: yield dict(meta={"test": "mail_ehlo", "ipversion": ipversion}, - data={"error": e}, + data={"error": str(e)}, status="WARNING", summary="diagnosis_mail_ehlo_could_not_diagnose") continue @@ -111,14 +111,14 @@ class MailDiagnoser(Diagnoser): yield dict(meta={"test": "mail_fcrdns", "ip": ip}, data={"ehlo_domain": self.ehlo_domain}, status="ERROR", - summary="diagnosis_mail_reverse_dns_missing") + summary="diagnosis_mail_fcrdns_dns_missing") continue if rdns_domain != self.ehlo_domain: yield dict(meta={"test": "mail_fcrdns", "ip": ip}, data={"ehlo_domain": self.ehlo_domain, "rdns_domain": rdns_domain}, status="ERROR", - summary="diagnosis_mail_rdns_different_from_ehlo_domain") + summary="diagnosis_mail_fcrdns_different_from_ehlo_domain") def check_blacklist(self): @@ -177,9 +177,9 @@ class MailDiagnoser(Diagnoser): pending_emails = int(output) except (ValueError, CalledProcessError) as e: yield dict(meta={"test": "mail_queue"}, - data={"error": e}, + data={"error": str(e)}, status="ERROR", - summary="diagnosis_mail_cannot_get_queue") + summary="diagnosis_mail_queue_unavailable") else: if pending_emails > 100: yield dict(meta={"test": "mail_queue"}, diff --git a/locales/en.json b/locales/en.json index 1a17c484f..327dba2a9 100644 --- a/locales/en.json +++ b/locales/en.json @@ -184,15 +184,15 @@ "diagnosis_swap_none": "The system has no swap at all. You should consider adding at least 256 MB 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 256 MB to avoid situations where the system runs out of memory.", "diagnosis_swap_ok": "The system has {total} of swap!", - "diagnosis_mail_ougoing_port_25_ok": "Outgoing port 25 is not blocked and email can be sent to other servers.", - "diagnosis_mail_ougoing_port_25_blocked": "Outgoing port 25 appears to be blocked in IPv{ipversion}. You should try to unblock it in your internet service provider (or hosting provider) configuration panel. Meanwhile, the server won't be able to send emails to other servers.", + "diagnosis_mail_outgoing_port_25_ok": "Outgoing port 25 is not blocked and email can be sent to other servers.", + "diagnosis_mail_outgoing_port_25_blocked": "Outgoing port 25 appears to be blocked in IPv{ipversion}. You should try to unblock it in your internet service provider (or hosting provider) configuration panel. Meanwhile, the server won't be able to send emails to other servers.", "diagnosis_mail_ehlo_ok": "Postfix mail service answer correctly from outside", "diagnosis_mail_ehlo_unavailable": "Postfix mail service don't answer to EHLO request on IPv{ipversion}.", "diagnosis_mail_ehlo_wrong": "A mail server answer {wrong_ehlo} instead {right_ehlo} on IPv{ipversion}.", "diagnosis_mail_ehlo_could_not_diagnose": "Could not diagnose if postfix mail server is reachable from outside. Error: {error}", - "diagnosis_mail_reverse_dns_missing": "No reverse DNS defined for the ip {ip}.", - "diagnosis_mail_rdns_different_from_ehlo_domain": "Your reverse DNS {rdns_domain} is different from your EHLO domain {ehlo_domain} on {ip}.", - "diagnosis_mail_rdns_equal_to_ehlo_domain": "Your reverse DNS is equal to your EHLO domain {ehlo_domain} on {ip}.", + "diagnosis_mail_fcrdns_dns_missing": "No reverse DNS defined for the ip {ip}.", + "diagnosis_mail_fcrdns_different_from_ehlo_domain": "Your reverse DNS {rdns_domain} is different from your EHLO domain {ehlo_domain} on {ip}.", + "diagnosis_mail_fcrdns_ok": "Your reverse DNS is well configured.", "diagnosis_mail_blacklist_ok": "The public IPs of this instance are not listed on email blacklists.", "diagnosis_mail_blacklist_listed_by": "{item} is blacklisted on {blacklist_name}. Reason: {reason}. See {blacklist_website}", "diagnosis_mail_queue_unavailable": "Can not consult number of pending emails in queue", From a30ed783da379f4085ec24309002aaa23b8b60e8 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 18 Apr 2020 20:33:30 +0200 Subject: [PATCH 1011/3170] Improve message about error 500 --- src/yunohost/diagnosis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index aba65a619..f7d2830b6 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -525,7 +525,7 @@ class Diagnoser(): socket.getaddrinfo = old_getaddrinfo if r.status_code not in [200, 400]: - raise Exception("Bad response from diagnosis server.\nURL: %s\nStatus code: %s\nMessage: %s" % (url, r.status_code, r.content)) + raise Exception("The remote diagnosis server failed miserably while trying to diagnose your server. This is most likely an error on Yunohost's infrastructure and not on your side. Please contact the YunoHost team an provide them with the following information.\nURL:
%s
\nStatus code:
%s
" % (url, r.status_code)) if r.status_code == 400: raise Exception("Diagnosis request was refused: %s" % r.content) From 0014fe29033c6eeb2e4238b7283ea342ff72fc34 Mon Sep 17 00:00:00 2001 From: ljf Date: Sat, 18 Apr 2020 20:40:18 +0200 Subject: [PATCH 1012/3170] [fix] Order of mail checks and mail queue --- data/hooks/diagnosis/24-mail.py | 26 ++++++++++++++++---------- locales/en.json | 30 ++++++++++++++++++------------ 2 files changed, 34 insertions(+), 22 deletions(-) diff --git a/data/hooks/diagnosis/24-mail.py b/data/hooks/diagnosis/24-mail.py index 4c36d7ca0..b122e876a 100644 --- a/data/hooks/diagnosis/24-mail.py +++ b/data/hooks/diagnosis/24-mail.py @@ -6,15 +6,12 @@ import socket import re from subprocess import CalledProcessError -from types import FunctionType from moulinette.utils.process import check_output -from moulinette.utils.network import download_text from moulinette.utils.filesystem import read_yaml from yunohost.diagnosis import Diagnoser from yunohost.domain import _get_maindomain, domain_list -from yunohost.utils.error import YunohostError DIAGNOSIS_SERVER = "diagnosis.yunohost.org" @@ -38,8 +35,8 @@ class MailDiagnoser(Diagnoser): # TODO Validate DKIM and dmarc ? # TODO check that the recent mail logs are not filled with thousand of email sending (unusual number of mail sent) # TODO check for unusual failed sending attempt being refused in the logs ? - checks = [name for name, value in MailDiagnoser.__dict__.items() - if type(value) == FunctionType and name.startswith("check_")] + checks = ["check_outgoing_port_25", "check_ehlo", "check_fcrdns", + "check_blacklist", "check_queue"] for check in checks: self.logger_debug("Running " + check) reports = list(getattr(self, check)()) @@ -64,7 +61,9 @@ class MailDiagnoser(Diagnoser): yield dict(meta={"test": "outgoing_port_25", "ipversion": ipversion}, data={}, status="ERROR", - summary="diagnosis_mail_ougoing_port_25_blocked") + summary="diagnosis_mail_ougoing_port_25_blocked", + details=["diagnosis_mail_ougoing_port_25_blocked_details", + "diagnosis_mail_outgoing_port_25_blocked_relay_vpn"]) def check_ehlo(self): @@ -82,7 +81,8 @@ class MailDiagnoser(Diagnoser): yield dict(meta={"test": "mail_ehlo", "ipversion": ipversion}, data={"error": str(e)}, status="WARNING", - summary="diagnosis_mail_ehlo_could_not_diagnose") + summary="diagnosis_mail_ehlo_could_not_diagnose", + details=["diagnosis_mail_ehlo_could_not_diagnose_details"]) continue if r["status"] == "error_smtp_unreachable": @@ -153,25 +153,30 @@ class MailDiagnoser(Diagnoser): continue # Try to get the reason + details = [] try: reason = str(dns.resolver.query(query, "TXT")[0]) + details.append("diagnosis_mail_blacklist_reason") except Exception: reason = "-" + details.append("diagnosis_mail_blacklist_website") + yield dict(meta={"test": "mail_blacklist", "item": item, "blacklist": blacklist["dns_server"]}, data={'blacklist_name': blacklist['name'], 'blacklist_website': blacklist['website'], 'reason': reason}, status="ERROR", - summary='diagnosis_mail_blacklist_listed_by') + summary='diagnosis_mail_blacklist_listed_by', + details=details) def check_queue(self): """ Check mail queue is not filled with hundreds of email pending """ - command = 'postqueue -p | grep -v "Mail queue is empty" | grep -c "^[A-Z0-9]"' + command = 'postqueue -p | grep -v "Mail queue is empty" | grep -c "^[A-Z0-9]" || true' try: output = check_output(command).strip() pending_emails = int(output) @@ -179,7 +184,8 @@ class MailDiagnoser(Diagnoser): yield dict(meta={"test": "mail_queue"}, data={"error": str(e)}, status="ERROR", - summary="diagnosis_mail_queue_unavailable") + summary="diagnosis_mail_queue_unavailable", + details="diagnosis_mail_queue_unavailable_details") else: if pending_emails > 100: yield dict(meta={"test": "mail_queue"}, diff --git a/locales/en.json b/locales/en.json index 327dba2a9..d2f4a925b 100644 --- a/locales/en.json +++ b/locales/en.json @@ -184,20 +184,26 @@ "diagnosis_swap_none": "The system has no swap at all. You should consider adding at least 256 MB 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 256 MB to avoid situations where the system runs out of memory.", "diagnosis_swap_ok": "The system has {total} of swap!", - "diagnosis_mail_outgoing_port_25_ok": "Outgoing port 25 is not blocked and email can be sent to other servers.", - "diagnosis_mail_outgoing_port_25_blocked": "Outgoing port 25 appears to be blocked in IPv{ipversion}. You should try to unblock it in your internet service provider (or hosting provider) configuration panel. Meanwhile, the server won't be able to send emails to other servers.", + "diagnosis_mail_outgoing_port_25_ok": "Outgoing port 25 is open, emails can be sent", + "diagnosis_mail_outgoing_port_25_blocked": "Outgoing port 25 appears to be bloecked in IPv{ipversion}", + "diagnosis_mail_outgoing_port_25_blocked_details": "You should first try to unblock it in your internet service provider (or hosting provider) configuration panel or by sending a ticket to your hosting provider. Meanwhile, the server won't be able to send emails to other servers.", + "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Some providers won't let you unblock outgoing port 25 because they don't care about Net Neutrality.
- Some of them provide the alternative of using a mail server relay though it implies that the relay will be able to spy on your email traffic.
- A privacy-friendly alternative is to use a VPN *with a dedicated public IP* to bypass this kind of limits. See https://yunohost.org/#/vpn_advantage
- Finally, it's also possible to change of provider", "diagnosis_mail_ehlo_ok": "Postfix mail service answer correctly from outside", - "diagnosis_mail_ehlo_unavailable": "Postfix mail service don't answer to EHLO request on IPv{ipversion}.", - "diagnosis_mail_ehlo_wrong": "A mail server answer {wrong_ehlo} instead {right_ehlo} on IPv{ipversion}.", - "diagnosis_mail_ehlo_could_not_diagnose": "Could not diagnose if postfix mail server is reachable from outside. Error: {error}", - "diagnosis_mail_fcrdns_dns_missing": "No reverse DNS defined for the ip {ip}.", - "diagnosis_mail_fcrdns_different_from_ehlo_domain": "Your reverse DNS {rdns_domain} is different from your EHLO domain {ehlo_domain} on {ip}.", - "diagnosis_mail_fcrdns_ok": "Your reverse DNS is well configured.", - "diagnosis_mail_blacklist_ok": "The public IPs of this instance are not listed on email blacklists.", - "diagnosis_mail_blacklist_listed_by": "{item} is blacklisted on {blacklist_name}. Reason: {reason}. See {blacklist_website}", + "diagnosis_mail_ehlo_unavailable": "Postfix mail service don't answer to EHLO request on IPv{ipversion}", + "diagnosis_mail_ehlo_wrong": "A mail server answers {wrong_ehlo} instead {right_ehlo} on IPv{ipversion}", + "diagnosis_mail_ehlo_could_not_diagnose": "Could not diagnose if postfix mail server is reachable from outside", + "diagnosis_mail_ehlo_could_not_diagnose_details": "Error: {error}", + "diagnosis_mail_fcrdns_ok": "Your reverse DNS is well configured", + "diagnosis_mail_fcrdns_dns_missing": "No reverse DNS defined for the ip {ip}", + "diagnosis_mail_fcrdns_different_from_ehlo_domain": "Your reverse DNS {rdns_domain} is different from your EHLO domain {ehlo_domain} on {ip}", + "diagnosis_mail_blacklist_ok": "IPs and domains used by this server to send mail are not on most used email blacklists", + "diagnosis_mail_blacklist_listed_by": "{item} is blacklisted on {blacklist_name}", + "diagnosis_mail_blacklist_reason": "The blacklist explains: {reason}", + "diagnosis_mail_blacklist_website": "After identifying why you are listed and fixed it, feel free to ask for delisting on {blacklist_website}", + "diagnosis_mail_queue_ok": "{nb_pending} pending emails in the mail queues", "diagnosis_mail_queue_unavailable": "Can not consult number of pending emails in queue", - "diagnosis_mail_queue_too_big": "The mail queue has {nb_pending} pending emails in the mail queue. It seems abnormal.", - "diagnosis_mail_queue_ok": "The mail queue has {nb_pending} pending emails in the mail queue.", + "diagnosis_mail_queue_unavailable_details": "Error: {error}", + "diagnosis_mail_queue_too_big": "Too many pending emails in mail queue ({nb_pending} emails)", "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", From 4c5f280aef38a62fbd1a768467691a6097ad3a21 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sun, 19 Apr 2020 00:01:54 +0200 Subject: [PATCH 1013/3170] Make nodejs helpers easier to use --- data/helpers.d/nodejs | 52 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/data/helpers.d/nodejs b/data/helpers.d/nodejs index 288240b1b..3e7ac5da2 100644 --- a/data/helpers.d/nodejs +++ b/data/helpers.d/nodejs @@ -28,14 +28,37 @@ SOURCE_SUM=3983fa3f00d4bf85ba8e21f1a590f6e28938093abe0bb950aeea52b1717471fc" > " # Load the version of node for an app, and set variables. # # ynh_use_nodejs has to be used in any app scripts before using node for the first time. +# This helper will provide alias and variables to use in your scripts. # -# 2 variables are available: -# - $nodejs_path: The absolute path of node for the chosen version. +# To use npm or node, use the alias `ynh_npm` and `ynh_node` +# Those alias will use the correct version installed for the app +# For example: use `ynh_npm install` instead of `npm install` +# +# With `sudo` or `ynh_exec_as`, use instead the fallback variables `$ynh_npm` and `$ynh_node` +# Exemple: `ynh_exec_as $app $ynh_npm install` +# +# $PATH contains the path of the requested version of node. +# However, $PATH is duplicated into $node_PATH to outlast any manipulation of $PATH +# You can use the variable `$ynh_node_load_PATH` to quickly load your node version +# in $PATH for an usage into a separate script. +# Exemple: $ynh_node_load_PATH $final_path/script_that_use_npm.sh` +# +# +# Finally, to start a nodejs service with the correct version, 2 solutions +# Either the app is dependent of node or npm, but does not called it directly. +# In such situation, you need to load PATH +# `Environment="__NODE_ENV_PATH__"` +# `ExecStart=__FINALPATH__/my_app` +# You will replace __NODE_ENV_PATH__ with $ynh_node_load_PATH +# +# Or node start the app directly, then you don't need to load the PATH variable +# `ExecStart=__YNH_NODE__ my_app run` +# You will replace __YNH_NODE__ with $ynh_node +# +# +# 2 other variables are also available +# - $nodejs_path: The absolute path to node binaries for the chosen version. # - $nodejs_version: Just the version number of node for this app. Stored as 'nodejs_version' in settings.yml. -# And 2 alias stored in variables: -# - $nodejs_use_version: An old variable, not used anymore. Keep here to not break old apps -# NB: $PATH will contain the path to node, it has to be propagated to any other shell which needs to use it. -# That's means it has to be added to any systemd script. # # usage: ynh_use_nodejs # @@ -43,13 +66,24 @@ SOURCE_SUM=3983fa3f00d4bf85ba8e21f1a590f6e28938093abe0bb950aeea52b1717471fc" > " ynh_use_nodejs () { nodejs_version=$(ynh_app_setting_get --app=$app --key=nodejs_version) - nodejs_use_version="echo \"Deprecated command, should be removed\"" - # Get the absolute path of this version of node nodejs_path="$node_version_path/$nodejs_version/bin" + # Allow alias to be used into bash script + shopt -s expand_aliases + + # Create an alias for the specific version of node and a variable as fallback + ynh_node="$nodejs_path/node" + alias ynh_node="$ynh_node" + # And npm + ynh_npm="$nodejs_path/npm" + alias ynh_npm="$ynh_npm" + # Load the path of this version of node in $PATH [[ :$PATH: == *":$nodejs_path"* ]] || PATH="$nodejs_path:$PATH" + node_PATH="$PATH" + # Create an alias to easily load the PATH + ynh_node_load_PATH="PATH=$node_PATH" } # Install a specific version of nodejs @@ -62,6 +96,8 @@ ynh_use_nodejs () { # usage: ynh_install_nodejs --nodejs_version=nodejs_version # | arg: -n, --nodejs_version - Version of node to install. When possible, your should prefer to use major version number (e.g. 8 instead of 8.10.0). The crontab will then handle the update of minor versions when needed. # +# Refer to ynh_use_nodejs for more information about available commands and variables +# # Requires YunoHost version 2.7.12 or higher. ynh_install_nodejs () { # Use n, https://github.com/tj/n to manage the nodejs versions From 7818eb39464846bbfb1c65aea7d6326520b48c86 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 19 Apr 2020 00:26:33 +0200 Subject: [PATCH 1014/3170] Better handling of failure to use the remote-diagnosis --- data/hooks/diagnosis/14-ports.py | 10 +++++++++- data/hooks/diagnosis/21-web.py | 11 ++++++++++- data/hooks/diagnosis/24-mail.py | 2 -- locales/en.json | 10 ++++++---- src/yunohost/diagnosis.py | 2 +- 5 files changed, 26 insertions(+), 9 deletions(-) diff --git a/data/hooks/diagnosis/14-ports.py b/data/hooks/diagnosis/14-ports.py index a4459d92f..bd68c60d6 100644 --- a/data/hooks/diagnosis/14-ports.py +++ b/data/hooks/diagnosis/14-ports.py @@ -47,8 +47,16 @@ class PortsDiagnoser(Diagnoser): ipversion=ipversion) results[ipversion] = r["ports"] except Exception as e: - raise YunohostError("diagnosis_ports_could_not_diagnose", error=e) + yield dict(meta={"reason": "remote_diagnosis_failed", "ipversion": ipversion}, + data={"error": str(e)}, + status="WARNING", + summary="diagnosis_ports_could_not_diagnose", + details=["diagnosis_ports_could_not_diagnose_details"]) + continue + ipversions = results.keys() + if not ipversions: + return for port, service in sorted(ports.items()): port = str(port) diff --git a/data/hooks/diagnosis/21-web.py b/data/hooks/diagnosis/21-web.py index 09f5b2b73..c1f6d912a 100644 --- a/data/hooks/diagnosis/21-web.py +++ b/data/hooks/diagnosis/21-web.py @@ -96,7 +96,16 @@ class WebDiagnoser(Diagnoser): ipversion=ipversion) results[ipversion] = r["http"] except Exception as e: - raise YunohostError("diagnosis_http_could_not_diagnose", error=e) + yield dict(meta={"reason": "remote_diagnosis_failed", "ipversion": ipversion}, + data={"error": str(e)}, + status="WARNING", + summary="diagnosis_http_could_not_diagnose", + details=["diagnosis_http_could_not_diagnose_details"]) + continue + + ipversions = results.keys() + if not ipversions: + return for domain in domains: diff --git a/data/hooks/diagnosis/24-mail.py b/data/hooks/diagnosis/24-mail.py index b122e876a..0ce1f3f25 100644 --- a/data/hooks/diagnosis/24-mail.py +++ b/data/hooks/diagnosis/24-mail.py @@ -13,8 +13,6 @@ from moulinette.utils.filesystem import read_yaml from yunohost.diagnosis import Diagnoser from yunohost.domain import _get_maindomain, domain_list -DIAGNOSIS_SERVER = "diagnosis.yunohost.org" - DEFAULT_DNS_BLACKLIST = "/usr/share/yunohost/other/dnsbl_list.yml" diff --git a/locales/en.json b/locales/en.json index d2f4a925b..4a0aefca8 100644 --- a/locales/en.json +++ b/locales/en.json @@ -191,8 +191,8 @@ "diagnosis_mail_ehlo_ok": "Postfix mail service answer correctly from outside", "diagnosis_mail_ehlo_unavailable": "Postfix mail service don't answer to EHLO request on IPv{ipversion}", "diagnosis_mail_ehlo_wrong": "A mail server answers {wrong_ehlo} instead {right_ehlo} on IPv{ipversion}", - "diagnosis_mail_ehlo_could_not_diagnose": "Could not diagnose if postfix mail server is reachable from outside", - "diagnosis_mail_ehlo_could_not_diagnose_details": "Error: {error}", + "diagnosis_mail_ehlo_could_not_diagnose": "Could not diagnose if postfix mail server is reachable from outside in IPv{ipversion}.", + "diagnosis_mail_ehlo_could_not_diagnose_details": "{error}", "diagnosis_mail_fcrdns_ok": "Your reverse DNS is well configured", "diagnosis_mail_fcrdns_dns_missing": "No reverse DNS defined for the ip {ip}", "diagnosis_mail_fcrdns_different_from_ehlo_domain": "Your reverse DNS {rdns_domain} is different from your EHLO domain {ehlo_domain} on {ip}", @@ -220,7 +220,8 @@ "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. Error: {error}", + "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.", "diagnosis_ports_partially_unreachable": "Port {port} is not reachable from outside in IPv{failed}.", "diagnosis_ports_ok": "Port {port} is reachable from outside.", @@ -228,7 +229,8 @@ "diagnosis_ports_forwarding_tip": "To fix this issue, you most probably need to configure port forwarding on your internet router as described in https://yunohost.org/isp_box_config", "diagnosis_http_hairpinning_issue": "Your local network does not seem to have hairpinning enabled.", "diagnosis_http_hairpinning_issue_details": "This is probably because of your ISP box / router. As a result, people from outside your local network will be able to access your server as expected, but not people from inside the local network (like you, probably?). You may be able to improve the situation by having a look at https://yunohost.org/dns_local_network", - "diagnosis_http_could_not_diagnose": "Could not diagnose if domain is reachable from outside. Error: {error}", + "diagnosis_http_could_not_diagnose": "Could not diagnose if domains are reachable from outside in IPv{ipversion}.", + "diagnosis_http_could_not_diagnose_details": "Error: {error}", "diagnosis_http_ok": "Domain {domain} is reachable through HTTP from outside the local network.", "diagnosis_http_timeout": "Timed-out while trying to contact your server from outside. It appears to be unreachable.
1. The most common cause for this issue is that you did not correctly configure port forwarding for port 80.
2. Also make sure that the web server nginx is running
3. On more complex setups: make sure that a firewall or reverse-proxy is not interfering.", "diagnosis_http_connection_error": "Connection error: could not connect to the requested domain, it's very likely unreachable.", diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index f7d2830b6..bd52f57f8 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -525,7 +525,7 @@ class Diagnoser(): socket.getaddrinfo = old_getaddrinfo if r.status_code not in [200, 400]: - raise Exception("The remote diagnosis server failed miserably while trying to diagnose your server. This is most likely an error on Yunohost's infrastructure and not on your side. Please contact the YunoHost team an provide them with the following information.\nURL:
%s
\nStatus code:
%s
" % (url, r.status_code)) + raise Exception("The remote diagnosis server failed miserably while trying to diagnose your server. This is most likely an error on Yunohost's infrastructure and not on your side. Please contact the YunoHost team an provide them with the following information.
URL: %s
Status code: %s" % (url, r.status_code)) if r.status_code == 400: raise Exception("Diagnosis request was refused: %s" % r.content) From 3c174389b64581dd91581c424f7299f637e1f00c Mon Sep 17 00:00:00 2001 From: ljf Date: Sun, 19 Apr 2020 00:48:54 +0200 Subject: [PATCH 1015/3170] [enh] Add some details --- data/hooks/diagnosis/24-mail.py | 13 ++++++++----- locales/ca.json | 2 +- locales/en.json | 19 +++++++++++-------- locales/eo.json | 2 +- locales/es.json | 2 +- locales/fr.json | 2 +- tests/test_i18n_keys.py | 7 +++++++ 7 files changed, 30 insertions(+), 17 deletions(-) diff --git a/data/hooks/diagnosis/24-mail.py b/data/hooks/diagnosis/24-mail.py index 0ce1f3f25..27903c9e9 100644 --- a/data/hooks/diagnosis/24-mail.py +++ b/data/hooks/diagnosis/24-mail.py @@ -59,8 +59,8 @@ class MailDiagnoser(Diagnoser): yield dict(meta={"test": "outgoing_port_25", "ipversion": ipversion}, data={}, status="ERROR", - summary="diagnosis_mail_ougoing_port_25_blocked", - details=["diagnosis_mail_ougoing_port_25_blocked_details", + summary="diagnosis_mail_outgoing_port_25_blocked", + details=["diagnosis_mail_outgoing_port_25_blocked_details", "diagnosis_mail_outgoing_port_25_blocked_relay_vpn"]) @@ -76,18 +76,21 @@ class MailDiagnoser(Diagnoser): data={}, ipversion=ipversion) except Exception as e: - yield dict(meta={"test": "mail_ehlo", "ipversion": ipversion}, + yield dict(meta={"test": "mail_ehlo", "reason": "remote_server_failed", + "ipversion": ipversion}, data={"error": str(e)}, status="WARNING", summary="diagnosis_mail_ehlo_could_not_diagnose", details=["diagnosis_mail_ehlo_could_not_diagnose_details"]) continue - if r["status"] == "error_smtp_unreachable": + if r["status"] != "ok": + summary = r["status"].replace("error_smtp_", "diagnosis_mail_ehlo_") yield dict(meta={"test": "mail_ehlo", "ipversion": ipversion}, data={}, status="ERROR", - summary="diagnosis_mail_ehlo_unavailable") + summary=summary, + details=[summary + "_details"]) elif r["helo"] != self.ehlo_domain: yield dict(meta={"test": "mail_ehlo", "ipversion": ipversion}, data={"wrong_ehlo": r["helo"], "right_ehlo": self.ehlo_domain}, diff --git a/locales/ca.json b/locales/ca.json index 0ea0d91f6..c20b94d6e 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -571,7 +571,7 @@ "apps_catalog_obsolete_cache": "La memòria cau del catàleg d'aplicacions és buida o obsoleta.", "apps_catalog_update_success": "S'ha actualitzat el catàleg d'aplicacions!", "diagnosis_mail_ougoing_port_25_ok": "El port de sortida 25 no està bloquejat i els correus es poden enviar a altres servidors.", - "diagnosis_mail_ougoing_port_25_blocked": "Sembla que el port de sortida 25 està bloquejat. Hauríeu d'intentar desbloquejar-lo al panell de configuració del proveïdor d'accés a internet (o allotjador). Mentrestant, el servidor no podrà enviar correus a altres servidors.", + "diagnosis_mail_outgoing_port_25_blocked": "Sembla que el port de sortida 25 està bloquejat. Hauríeu d'intentar desbloquejar-lo al panell de configuració del proveïdor d'accés a internet (o allotjador). Mentrestant, el servidor no podrà enviar correus a altres servidors.", "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ó", diff --git a/locales/en.json b/locales/en.json index 4a0aefca8..63cef236b 100644 --- a/locales/en.json +++ b/locales/en.json @@ -189,12 +189,17 @@ "diagnosis_mail_outgoing_port_25_blocked_details": "You should first try to unblock it in your internet service provider (or hosting provider) configuration panel or by sending a ticket to your hosting provider. Meanwhile, the server won't be able to send emails to other servers.", "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Some providers won't let you unblock outgoing port 25 because they don't care about Net Neutrality.
- Some of them provide the alternative of using a mail server relay though it implies that the relay will be able to spy on your email traffic.
- A privacy-friendly alternative is to use a VPN *with a dedicated public IP* to bypass this kind of limits. See https://yunohost.org/#/vpn_advantage
- Finally, it's also possible to change of provider", "diagnosis_mail_ehlo_ok": "Postfix mail service answer correctly from outside", - "diagnosis_mail_ehlo_unavailable": "Postfix mail service don't answer to EHLO request on IPv{ipversion}", - "diagnosis_mail_ehlo_wrong": "A mail server answers {wrong_ehlo} instead {right_ehlo} on IPv{ipversion}", - "diagnosis_mail_ehlo_could_not_diagnose": "Could not diagnose if postfix mail server is reachable from outside in IPv{ipversion}.", - "diagnosis_mail_ehlo_could_not_diagnose_details": "{error}", + "diagnosis_mail_ehlo_unreachable": "SMTP server unreachable on IPv{ipversion}", + "diagnosis_mail_ehlo_unreachable_details": "Could not open a connection on port 25 through IPv{ipversion}, probably because of a firewall, port forwarding issue or postfix service down", + "diagnosis_mail_ehlo_bad_answer": "A non-SMTP service answered on port 25 on IPv{ipversion}", + "diagnosis_mail_ehlo_bad_answer_details": "It could be due to an other machine answering instead of your server.", + "diagnosis_mail_ehlo_wrong": "An other SMTP server answers on IPv{ipversion}", + "diagnosis_mail_ehlo_wrong_details": "The remote diagnoser return a wrong EHLO answer from your IPv{ipversion}.
Received: {wrong_ehlo}
Expected: {right_ehlo}
You probably have a port forwarding issue or a reverse proxy server unconfigured for mail.", + "diagnosis_mail_ehlo_could_not_diagnose": "Could not diagnose if postfix mail server is reachable from outside", + "diagnosis_mail_ehlo_could_not_diagnose_details": "Error: {error}", "diagnosis_mail_fcrdns_ok": "Your reverse DNS is well configured", "diagnosis_mail_fcrdns_dns_missing": "No reverse DNS defined for the ip {ip}", + "diagnosis_mail_fcrdns_dns_missing_details": "You can configure it on ", "diagnosis_mail_fcrdns_different_from_ehlo_domain": "Your reverse DNS {rdns_domain} is different from your EHLO domain {ehlo_domain} on {ip}", "diagnosis_mail_blacklist_ok": "IPs and domains used by this server to send mail are not on most used email blacklists", "diagnosis_mail_blacklist_listed_by": "{item} is blacklisted on {blacklist_name}", @@ -220,8 +225,7 @@ "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_could_not_diagnose": "Could not diagnose if ports are reachable from outside. Error: {error}", "diagnosis_ports_unreachable": "Port {port} is not reachable from outside.", "diagnosis_ports_partially_unreachable": "Port {port} is not reachable from outside in IPv{failed}.", "diagnosis_ports_ok": "Port {port} is reachable from outside.", @@ -229,8 +233,7 @@ "diagnosis_ports_forwarding_tip": "To fix this issue, you most probably need to configure port forwarding on your internet router as described in https://yunohost.org/isp_box_config", "diagnosis_http_hairpinning_issue": "Your local network does not seem to have hairpinning enabled.", "diagnosis_http_hairpinning_issue_details": "This is probably because of your ISP box / router. As a result, people from outside your local network will be able to access your server as expected, but not people from inside the local network (like you, probably?). You may be able to improve the situation by having a look at https://yunohost.org/dns_local_network", - "diagnosis_http_could_not_diagnose": "Could not diagnose if domains are reachable from outside in IPv{ipversion}.", - "diagnosis_http_could_not_diagnose_details": "Error: {error}", + "diagnosis_http_could_not_diagnose": "Could not diagnose if domain is reachable from outside. Error: {error}", "diagnosis_http_ok": "Domain {domain} is reachable through HTTP from outside the local network.", "diagnosis_http_timeout": "Timed-out while trying to contact your server from outside. It appears to be unreachable.
1. The most common cause for this issue is that you did not correctly configure port forwarding for port 80.
2. Also make sure that the web server nginx is running
3. On more complex setups: make sure that a firewall or reverse-proxy is not interfering.", "diagnosis_http_connection_error": "Connection error: could not connect to the requested domain, it's very likely unreachable.", diff --git a/locales/eo.json b/locales/eo.json index 87e062ea2..9c1aed008 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -515,7 +515,7 @@ "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_ram_verylow": "La sistemo nur restas {available} ({available_percent}%) RAM! (el {total})", - "diagnosis_mail_ougoing_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_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": "Ne povis atingi vian servilon kiel atendite, ĝi redonis malbonan statuskodon. Povas esti, ke alia maŝino respondis anstataŭ via servilo. Vi devus kontroli, ke vi ĝuste redonas la havenon 80, ke via nginx-agordo ĝisdatigas kaj ke 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ŭ 'yunohost user create ' en komandlinio);\n - diagnozi problemojn atendantajn solvi por ke via servilo funkciu kiel eble plej glate tra la sekcio 'Diagnosis' de la retadministrado (aŭ 'yunohost diagnosis run' en komandlinio);\n - legante la partojn 'Finigi vian agordon' kaj 'Ekkoni Yunohost' en la administra dokumentado: https://yunohost.org/admindoc.", diff --git a/locales/es.json b/locales/es.json index 6a55378da..de9eb91c6 100644 --- a/locales/es.json +++ b/locales/es.json @@ -554,7 +554,7 @@ "diagnosis_swap_none": "El sistema no tiene mas espacio de intercambio. Considera agregar por lo menos 256 MB de espacio de intercambio para evitar que el sistema se quede sin memoria.", "diagnosis_swap_notsomuch": "Al sistema le queda solamente {total} de espacio de intercambio. Considera agregar al menos 256 MB para evitar que el sistema se quede sin memoria.", "diagnosis_mail_ougoing_port_25_ok": "El puerto de salida 25 no esta bloqueado y los correos electrónicos pueden ser enviados a otros servidores.", - "diagnosis_mail_ougoing_port_25_blocked": "El puerto de salida 25 parece estar bloqueado. Intenta desbloquearlo con el panel de configuración de tu proveedor de servicios de Internet (o proveedor de halbergue). Mientras tanto, el servidor no podrá enviar correos electrónicos a otros servidores.", + "diagnosis_mail_outgoing_port_25_blocked": "El puerto de salida 25 parece estar bloqueado. Intenta desbloquearlo con el panel de configuración de tu proveedor de servicios de Internet (o proveedor de halbergue). Mientras tanto, el servidor no podrá enviar correos electrónicos a otros servidores.", "diagnosis_regenconf_allgood": "Todos los archivos de configuración están en linea con la configuración recomendada!", "diagnosis_regenconf_manually_modified": "El archivo de configuración {file} fue modificado manualmente.", "diagnosis_regenconf_manually_modified_details": "Esto este probablemente BIEN siempre y cuando sepas lo que estas haciendo ;) !", diff --git a/locales/fr.json b/locales/fr.json index f029a1d13..faf2837a3 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -551,7 +551,7 @@ "diagnosis_security_all_good": "Aucune vulnérabilité de sécurité critique n'a été trouvée.", "apps_catalog_init_success": "Système de catalogue d'applications initialisé !", "apps_catalog_failed_to_download": "Impossible de télécharger le catalogue des applications {apps_catalog}:{error}", - "diagnosis_mail_ougoing_port_25_blocked": "Le port sortant 25 semble être bloqué. Vous devriez essayer de le débloquer dans le panneau de configuration de votre fournisseur de services Internet (ou hébergeur). En attendant, le serveur ne pourra pas envoyer de courrier électronique à d'autres serveurs.", + "diagnosis_mail_outgoing_port_25_blocked": "Le port sortant 25 semble être bloqué. Vous devriez essayer de le débloquer dans le panneau de configuration de votre fournisseur de services Internet (ou hébergeur). En attendant, le serveur ne pourra pas envoyer de courrier électronique à d'autres serveurs.", "domain_cannot_remove_main_add_new_one": "Vous ne pouvez pas supprimer '{domain:s}' car il s'agit du domaine principal et de votre seul domaine. Vous devez d'abord ajouter un autre domaine à l'aide de 'yunohost domain add ', puis définir comme domaine principal à l'aide de ' yunohost domain main-domain -n ' et vous pouvez ensuite supprimer le domaine '{domain:s}' à l'aide de 'yunohost domain remove {domain:s}'.'", "diagnosis_security_vulnerable_to_meltdown_details": "Pour résoudre ce problème, vous devez mettre à niveau votre système et redémarrer pour charger le nouveau noyau Linux (ou contacter votre fournisseur de serveur si cela ne fonctionne pas). Voir https://meltdownattack.com/ pour plus d'informations.", "diagnosis_description_basesystem": "Système de base", diff --git a/tests/test_i18n_keys.py b/tests/test_i18n_keys.py index 0d5af33f6..20e9dd8a0 100644 --- a/tests/test_i18n_keys.py +++ b/tests/test_i18n_keys.py @@ -122,6 +122,13 @@ def find_expected_string_keys(): yield "password_listed" for i in [1, 2, 3, 4]: yield "password_too_simple_%s" % i + + checks = ["outgoing_port_25_ok", "ehlo_ok", "fcrdns_ok", + "blacklist_ok", "queue_ok", "ehlo_bad_answer", + "ehlo_unreachable", "ehlo_bad_answer_details", + "ehlo_unreachable_details", ] + for check in checks: + yield "diagnosis_mail_%" ############################################################################### # Load en locale json keys # From 55957d77b09386c48362590702a306029983fe9d Mon Sep 17 00:00:00 2001 From: ljf Date: Sun, 19 Apr 2020 00:52:28 +0200 Subject: [PATCH 1016/3170] [fix] Key queue_to_big --- data/hooks/diagnosis/24-mail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/hooks/diagnosis/24-mail.py b/data/hooks/diagnosis/24-mail.py index 27903c9e9..608bfd931 100644 --- a/data/hooks/diagnosis/24-mail.py +++ b/data/hooks/diagnosis/24-mail.py @@ -192,7 +192,7 @@ class MailDiagnoser(Diagnoser): yield dict(meta={"test": "mail_queue"}, data={'nb_pending': pending_emails}, status="WARNING", - summary="diagnosis_mail_queue_too_many_pending_emails") + summary="diagnosis_mail_queue_too_big") else: yield dict(meta={"test": "mail_queue"}, data={'nb_pending': pending_emails}, From dae8adff4b21aaa74657ae317c9caf2b717ae42f Mon Sep 17 00:00:00 2001 From: ljf Date: Sun, 19 Apr 2020 01:02:30 +0200 Subject: [PATCH 1017/3170] [fix] rebase issue --- locales/en.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/locales/en.json b/locales/en.json index 63cef236b..3be85fd35 100644 --- a/locales/en.json +++ b/locales/en.json @@ -225,7 +225,8 @@ "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. Error: {error}", + "diagnosis_ports_could_not_diagnose": "Could not diagnose if ports are reachable from outside.", + "diagnosis_ports_could_not_diagnose_details": "Error: {error}", "diagnosis_ports_unreachable": "Port {port} is not reachable from outside.", "diagnosis_ports_partially_unreachable": "Port {port} is not reachable from outside in IPv{failed}.", "diagnosis_ports_ok": "Port {port} is reachable from outside.", @@ -233,7 +234,8 @@ "diagnosis_ports_forwarding_tip": "To fix this issue, you most probably need to configure port forwarding on your internet router as described in https://yunohost.org/isp_box_config", "diagnosis_http_hairpinning_issue": "Your local network does not seem to have hairpinning enabled.", "diagnosis_http_hairpinning_issue_details": "This is probably because of your ISP box / router. As a result, people from outside your local network will be able to access your server as expected, but not people from inside the local network (like you, probably?). You may be able to improve the situation by having a look at https://yunohost.org/dns_local_network", - "diagnosis_http_could_not_diagnose": "Could not diagnose if domain is reachable from outside. Error: {error}", + "diagnosis_http_could_not_diagnose": "Could not diagnose if domain is reachable from outside.", + "diagnosis_http_could_not_diagnose_details": "Error: {error}", "diagnosis_http_ok": "Domain {domain} is reachable through HTTP from outside the local network.", "diagnosis_http_timeout": "Timed-out while trying to contact your server from outside. It appears to be unreachable.
1. The most common cause for this issue is that you did not correctly configure port forwarding for port 80.
2. Also make sure that the web server nginx is running
3. On more complex setups: make sure that a firewall or reverse-proxy is not interfering.", "diagnosis_http_connection_error": "Connection error: could not connect to the requested domain, it's very likely unreachable.", From 0ac1cfb31aea189c44671f3b889057c2e6c4c1cc Mon Sep 17 00:00:00 2001 From: ljf Date: Sun, 19 Apr 2020 01:04:02 +0200 Subject: [PATCH 1018/3170] [fix] rebase issue --- locales/en.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/en.json b/locales/en.json index 3be85fd35..92067229f 100644 --- a/locales/en.json +++ b/locales/en.json @@ -225,7 +225,7 @@ "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.", + "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.", "diagnosis_ports_partially_unreachable": "Port {port} is not reachable from outside in IPv{failed}.", @@ -234,7 +234,7 @@ "diagnosis_ports_forwarding_tip": "To fix this issue, you most probably need to configure port forwarding on your internet router as described in https://yunohost.org/isp_box_config", "diagnosis_http_hairpinning_issue": "Your local network does not seem to have hairpinning enabled.", "diagnosis_http_hairpinning_issue_details": "This is probably because of your ISP box / router. As a result, people from outside your local network will be able to access your server as expected, but not people from inside the local network (like you, probably?). You may be able to improve the situation by having a look at https://yunohost.org/dns_local_network", - "diagnosis_http_could_not_diagnose": "Could not diagnose if domain is reachable from outside.", + "diagnosis_http_could_not_diagnose": "Could not diagnose if domain is reachable from outside in IPv{ipversion}.", "diagnosis_http_could_not_diagnose_details": "Error: {error}", "diagnosis_http_ok": "Domain {domain} is reachable through HTTP from outside the local network.", "diagnosis_http_timeout": "Timed-out while trying to contact your server from outside. It appears to be unreachable.
1. The most common cause for this issue is that you did not correctly configure port forwarding for port 80.
2. Also make sure that the web server nginx is running
3. On more complex setups: make sure that a firewall or reverse-proxy is not interfering.", From a7a0f93102b617f7a344498a496c3fbc5d84b09a Mon Sep 17 00:00:00 2001 From: ljf Date: Sun, 19 Apr 2020 01:05:36 +0200 Subject: [PATCH 1019/3170] [fix] rebase issue --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 92067229f..8272fc86c 100644 --- a/locales/en.json +++ b/locales/en.json @@ -234,7 +234,7 @@ "diagnosis_ports_forwarding_tip": "To fix this issue, you most probably need to configure port forwarding on your internet router as described in https://yunohost.org/isp_box_config", "diagnosis_http_hairpinning_issue": "Your local network does not seem to have hairpinning enabled.", "diagnosis_http_hairpinning_issue_details": "This is probably because of your ISP box / router. As a result, people from outside your local network will be able to access your server as expected, but not people from inside the local network (like you, probably?). You may be able to improve the situation by having a look at https://yunohost.org/dns_local_network", - "diagnosis_http_could_not_diagnose": "Could not diagnose if domain is reachable from outside in IPv{ipversion}.", + "diagnosis_http_could_not_diagnose": "Could not diagnose if domains are reachable from outside in IPv{ipversion}.", "diagnosis_http_could_not_diagnose_details": "Error: {error}", "diagnosis_http_ok": "Domain {domain} is reachable through HTTP from outside the local network.", "diagnosis_http_timeout": "Timed-out while trying to contact your server from outside. It appears to be unreachable.
1. The most common cause for this issue is that you did not correctly configure port forwarding for port 80.
2. Also make sure that the web server nginx is running
3. On more complex setups: make sure that a firewall or reverse-proxy is not interfering.", From 91a07bdf08ee4eecdcb1734493fed31fccb7ecd3 Mon Sep 17 00:00:00 2001 From: ljf Date: Sun, 19 Apr 2020 01:07:06 +0200 Subject: [PATCH 1020/3170] [fix] tests i18n key --- tests/test_i18n_keys.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_i18n_keys.py b/tests/test_i18n_keys.py index 20e9dd8a0..c845a2e3e 100644 --- a/tests/test_i18n_keys.py +++ b/tests/test_i18n_keys.py @@ -128,7 +128,7 @@ def find_expected_string_keys(): "ehlo_unreachable", "ehlo_bad_answer_details", "ehlo_unreachable_details", ] for check in checks: - yield "diagnosis_mail_%" + yield "diagnosis_mail_%" % check ############################################################################### # Load en locale json keys # From 9d0074d71bb53f9a0ac6a6d28acf1a74aef7b521 Mon Sep 17 00:00:00 2001 From: ljf Date: Sun, 19 Apr 2020 01:15:21 +0200 Subject: [PATCH 1021/3170] [fix] tests i18n key --- tests/test_i18n_keys.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_i18n_keys.py b/tests/test_i18n_keys.py index c845a2e3e..7546f51aa 100644 --- a/tests/test_i18n_keys.py +++ b/tests/test_i18n_keys.py @@ -128,7 +128,7 @@ def find_expected_string_keys(): "ehlo_unreachable", "ehlo_bad_answer_details", "ehlo_unreachable_details", ] for check in checks: - yield "diagnosis_mail_%" % check + yield "diagnosis_mail_%s" % check ############################################################################### # Load en locale json keys # From 8aced5b4ce2e7b55da7681d602085eec3d78a861 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 19 Apr 2020 01:30:22 +0200 Subject: [PATCH 1022/3170] Let's cache mail diagnosis with a duration similar to other checks... --- data/hooks/diagnosis/24-mail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/hooks/diagnosis/24-mail.py b/data/hooks/diagnosis/24-mail.py index 0ce1f3f25..c2f898e65 100644 --- a/data/hooks/diagnosis/24-mail.py +++ b/data/hooks/diagnosis/24-mail.py @@ -19,7 +19,7 @@ DEFAULT_DNS_BLACKLIST = "/usr/share/yunohost/other/dnsbl_list.yml" class MailDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] - cache_duration = 0 + cache_duration = 600 dependencies = ["ip"] def run(self): From 3cb47a226f948af74d8625b7e9197cf5bd9bf67c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 19 Apr 2020 01:43:27 +0200 Subject: [PATCH 1023/3170] More flexible warning about swap size... Move it to 512 MiB 'cause 256 MiB really aint much I think --- data/hooks/diagnosis/50-systemresources.py | 4 ++-- locales/ca.json | 4 ++-- locales/en.json | 4 ++-- locales/eo.json | 4 ++-- locales/es.json | 4 ++-- locales/fr.json | 4 ++-- locales/oc.json | 4 ++-- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/data/hooks/diagnosis/50-systemresources.py b/data/hooks/diagnosis/50-systemresources.py index ab9ead7bb..417b88ae7 100644 --- a/data/hooks/diagnosis/50-systemresources.py +++ b/data/hooks/diagnosis/50-systemresources.py @@ -43,11 +43,11 @@ class SystemResourcesDiagnoser(Diagnoser): swap = psutil.swap_memory() item = dict(meta={"test": "swap"}, - data={"total": human_size(swap.total)}) + data={"total": human_size(swap.total), "recommended": "512 MiB"}) if swap.total <= 1 * MB: item["status"] = "ERROR" item["summary"] = "diagnosis_swap_none" - elif swap.total <= 256 * MB: + elif swap.total <= 512 * MB: item["status"] = "WARNING" item["summary"] = "diagnosis_swap_notsomuch" else: diff --git a/locales/ca.json b/locales/ca.json index 0ea0d91f6..07e2c6f27 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -503,7 +503,7 @@ "app_remove_after_failed_install": "Eliminant l'aplicació després que hagi fallat la instal·lació…", "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 256 MB de swap per evitar situacions en les que el sistema es queda sense memòria.", + "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_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. Error: {error}", @@ -543,7 +543,7 @@ "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 256 MB per evitar situacions en les que el sistema es queda sense memòria.", + "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 ;) !", diff --git a/locales/en.json b/locales/en.json index 4a0aefca8..a5048b8c9 100644 --- a/locales/en.json +++ b/locales/en.json @@ -181,8 +181,8 @@ "diagnosis_ram_verylow": "The system has only {available} ({available_percent}%) RAM available! (out of {total})", "diagnosis_ram_low": "The system has {available} ({available_percent}%) RAM available (out of {total}). Be careful.", "diagnosis_ram_ok": "The system still has {available} ({available_percent}%) RAM available out of {total}.", - "diagnosis_swap_none": "The system has no swap at all. You should consider adding at least 256 MB 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 256 MB to avoid situations where the system runs out of memory.", + "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_mail_outgoing_port_25_ok": "Outgoing port 25 is open, emails can be sent", "diagnosis_mail_outgoing_port_25_blocked": "Outgoing port 25 appears to be bloecked in IPv{ipversion}", diff --git a/locales/eo.json b/locales/eo.json index 87e062ea2..36396d6f1 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -531,8 +531,8 @@ "diagnosis_dns_good_conf": "Bona DNS-agordo por domajno {domain} (kategorio {category})", "diagnosis_dns_bad_conf": "Malbona / mankas DNS-agordo 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ŭ 256 MB 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ŭ 256 MB por eviti situaciojn en kiuj la sistemo restas sen memoro.", + "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_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...", diff --git a/locales/es.json b/locales/es.json index 6a55378da..c21585e7b 100644 --- a/locales/es.json +++ b/locales/es.json @@ -551,8 +551,8 @@ "diagnosis_ram_verylow": "Al sistema le queda solamente {available} ({available_percent}%) de RAM! (De un total de {total})", "diagnosis_ram_low": "Al sistema le queda {available} ({available_percent}%) de RAM de un total de {total}. Cuidado.", "diagnosis_ram_ok": "El sistema aun tiene {available} ({available_percent}%) de RAM de un total de {total}.", - "diagnosis_swap_none": "El sistema no tiene mas espacio de intercambio. Considera agregar por lo menos 256 MB de espacio de intercambio para evitar que el sistema se quede sin memoria.", - "diagnosis_swap_notsomuch": "Al sistema le queda solamente {total} de espacio de intercambio. Considera agregar al menos 256 MB para evitar que el sistema se quede sin memoria.", + "diagnosis_swap_none": "El sistema no tiene mas espacio de intercambio. Considera agregar por lo menos {recommended} de espacio de intercambio para evitar que el sistema se quede sin memoria.", + "diagnosis_swap_notsomuch": "Al sistema le queda solamente {total} de espacio de intercambio. Considera agregar al menos {recommended} para evitar que el sistema se quede sin memoria.", "diagnosis_mail_ougoing_port_25_ok": "El puerto de salida 25 no esta bloqueado y los correos electrónicos pueden ser enviados a otros servidores.", "diagnosis_mail_ougoing_port_25_blocked": "El puerto de salida 25 parece estar bloqueado. Intenta desbloquearlo con el panel de configuración de tu proveedor de servicios de Internet (o proveedor de halbergue). Mientras tanto, el servidor no podrá enviar correos electrónicos a otros servidores.", "diagnosis_regenconf_allgood": "Todos los archivos de configuración están en linea con la configuración recomendada!", diff --git a/locales/fr.json b/locales/fr.json index f029a1d13..7e77cdc7a 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -541,8 +541,8 @@ "diagnosis_diskusage_low": "Le stockage {mountpoint} (sur le périphérique {device}) ne dispose que de {free} ({free_percent}%). Faites attention.", "diagnosis_ram_verylow": "Le système ne dispose plus que de {available} ({available_percent}%)! (sur {total})", "diagnosis_ram_low": "Le système n'a plus de {available} ({available_percent}%) RAM sur {total}. Faites attention.", - "diagnosis_swap_none": "Le système n'a aucun échange. Vous devez envisager d’ajouter au moins 256 Mo de swap pour éviter les situations où le système manque de mémoire.", - "diagnosis_swap_notsomuch": "Le système ne dispose que de {total} de swap. Vous devez envisager d'avoir au moins 256 Mo pour éviter les situations où le système manque de mémoire.", + "diagnosis_swap_none": "Le système n'a aucun échange. Vous devez envisager d’ajouter au moins {recommended} de swap pour éviter les situations où le système manque de mémoire.", + "diagnosis_swap_notsomuch": "Le système ne dispose que de {total} de swap. Vous devez envisager d'avoir au moins {recommended} pour éviter les situations où le système manque de mémoire.", "diagnosis_swap_ok": "Le système dispose de {total} de swap !", "diagnosis_regenconf_manually_modified": "Le fichier de configuration {file} a été modifié manuellement.", "diagnosis_regenconf_manually_modified_debian": "Le fichier de configuration {file} a été modifié manuellement par rapport à celui par défaut de Debian.", diff --git a/locales/oc.json b/locales/oc.json index eebfaac64..97978bb18 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -568,6 +568,6 @@ "diagnosis_diskusage_verylow": "Lo lòc d’emmagazinatge {mountpoint} (sul periferic {device}) a solament {free} ({free_percent}%). Deuriatz considerar de liberar un pauc d’espaci.", "global_settings_setting_pop3_enabled": "Activar lo protocòl POP3 pel servidor de corrièr", "diagnosis_diskusage_ok": "Lo lòc d’emmagazinatge {mountpoint} (sul periferic {device}) a encara {free} ({free_percent}%) de liure !", - "diagnosis_swap_none": "Lo sistèma a pas cap de memòria d’escambi. Auriatz de considerar d’ajustar almens 256 Mo d’escambi per evitar las situacions ont lo sistèma manca de memòria.", - "diagnosis_swap_notsomuch": "Lo sistèma a solament {total} de memòria d’escambi. Auriatz de considerar d’ajustar almens 256 Mo d’escambi per evitar las situacions ont lo sistèma manca de memòria." + "diagnosis_swap_none": "Lo sistèma a pas cap de memòria d’escambi. Auriatz de considerar d’ajustar almens {recommended} d’escambi per evitar las situacions ont lo sistèma manca de memòria.", + "diagnosis_swap_notsomuch": "Lo sistèma a solament {total} de memòria d’escambi. Auriatz de considerar d’ajustar almens {recommended} d’escambi per evitar las situacions ont lo sistèma manca de memòria." } From 97ab8c91f86c43dcec728253a2e3bd6a8304208c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 19 Apr 2020 02:03:35 +0200 Subject: [PATCH 1024/3170] Fix the fix for stupid 'search' stuff in resolvconf ... + let's in fact ignore it if it does exists in /etc/resolv.conf >.> --- data/hooks/conf_regen/43-dnsmasq | 6 +++--- data/hooks/diagnosis/10-ip.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/hooks/conf_regen/43-dnsmasq b/data/hooks/conf_regen/43-dnsmasq index d6ab8648c..59a1f8a06 100755 --- a/data/hooks/conf_regen/43-dnsmasq +++ b/data/hooks/conf_regen/43-dnsmasq @@ -59,9 +59,9 @@ do_post_regen() { sed -E "s/^(domain|search)/#\1/g" -i /run/resolvconf/interface/*.dhclient fi - grep -q '^supersede domain-name "";' /etc/dhcp/dhclient.conf 2>/dev/null || echo '^supersede domain-name "";' >> /etc/dhcp/dhclient.conf - grep -q '^supersede domain-search "";' /etc/dhcp/dhclient.conf 2>/dev/null || echo '^supersede domain-search "";' >> /etc/dhcp/dhclient.conf - grep -q '^supersede name "";' /etc/dhcp/dhclient.conf 2>/dev/null || echo '^supersede name "";' >> /etc/dhcp/dhclient.conf + grep -q '^supersede domain-name "";' /etc/dhcp/dhclient.conf 2>/dev/null || echo 'supersede domain-name "";' >> /etc/dhcp/dhclient.conf + grep -q '^supersede domain-search "";' /etc/dhcp/dhclient.conf 2>/dev/null || echo 'supersede domain-search "";' >> /etc/dhcp/dhclient.conf + grep -q '^supersede name "";' /etc/dhcp/dhclient.conf 2>/dev/null || echo 'supersede name "";' >> /etc/dhcp/dhclient.conf systemctl restart resolvconf fi diff --git a/data/hooks/diagnosis/10-ip.py b/data/hooks/diagnosis/10-ip.py index 6571ca556..c0d35278c 100644 --- a/data/hooks/diagnosis/10-ip.py +++ b/data/hooks/diagnosis/10-ip.py @@ -134,7 +134,7 @@ class IPDiagnoser(Diagnoser): def good_resolvconf(self): content = read_file("/etc/resolv.conf").strip().split("\n") # Ignore comments and empty lines - content = [l.strip() for l in content if l.strip() and not l.strip().startswith("#")] + content = [l.strip() for l in content if l.strip() and not l.strip().startswith("#") and not l.strip().startswith("search")] # We should only find a "nameserver 127.0.0.1" return len(content) == 1 and content[0].split() == ["nameserver", "127.0.0.1"] From 4686673bb52c6181cbed60105917cb32d9c43a3d Mon Sep 17 00:00:00 2001 From: ljf Date: Sun, 19 Apr 2020 02:30:23 +0200 Subject: [PATCH 1025/3170] [enh] Be able to disable ipv6 for smtp --- data/hooks/conf_regen/19-postfix | 3 ++- data/hooks/diagnosis/24-mail.py | 31 ++++++++++++++++++++++--------- locales/en.json | 9 ++++++--- src/yunohost/settings.py | 1 + 4 files changed, 31 insertions(+), 13 deletions(-) diff --git a/data/hooks/conf_regen/19-postfix b/data/hooks/conf_regen/19-postfix index 0f09f0299..172438f37 100755 --- a/data/hooks/conf_regen/19-postfix +++ b/data/hooks/conf_regen/19-postfix @@ -35,7 +35,8 @@ do_pre_regen() { > "${default_dir}/postsrsd" # adapt it for IPv4-only hosts - if [ ! -f /proc/net/if_inet6 ]; then + ipv6="$(yunohost settings get 'smtp.ipv6')" + if [ "$ipv6" == "False" ] || [ ! -f /proc/net/if_inet6 ]; then sed -i \ 's/ \[::ffff:127.0.0.0\]\/104 \[::1\]\/128//g' \ "${postfix_dir}/main.cf" diff --git a/data/hooks/diagnosis/24-mail.py b/data/hooks/diagnosis/24-mail.py index 608bfd931..022b24114 100644 --- a/data/hooks/diagnosis/24-mail.py +++ b/data/hooks/diagnosis/24-mail.py @@ -12,6 +12,7 @@ from moulinette.utils.filesystem import read_yaml from yunohost.diagnosis import Diagnoser from yunohost.domain import _get_maindomain, domain_list +from yunohost.settings import settings_get DEFAULT_DNS_BLACKLIST = "/usr/share/yunohost/other/dnsbl_list.yml" @@ -95,7 +96,8 @@ class MailDiagnoser(Diagnoser): yield dict(meta={"test": "mail_ehlo", "ipversion": ipversion}, data={"wrong_ehlo": r["helo"], "right_ehlo": self.ehlo_domain}, status="ERROR", - summary="diagnosis_mail_ehlo_wrong") + summary="diagnosis_mail_ehlo_wrong", + details=["diagnosis_mail_ehlo_wrong_details"]) def check_fcrdns(self): @@ -106,20 +108,30 @@ class MailDiagnoser(Diagnoser): """ for ip in self.ips: + if ":" in ip: + details = ["diagnosis_mail_fcrdns_nok_details", + "diagnosis_mail_fcrdns_nok_alternatives_6"] + else: + details = ["diagnosis_mail_fcrdns_nok_details", + "diagnosis_mail_fcrdns_nok_alternatives_4"] + try: rdns_domain, _, _ = socket.gethostbyaddr(ip) except socket.herror: yield dict(meta={"test": "mail_fcrdns", "ip": ip}, data={"ehlo_domain": self.ehlo_domain}, status="ERROR", - summary="diagnosis_mail_fcrdns_dns_missing") + summary="diagnosis_mail_fcrdns_dns_missing", + details=details) continue if rdns_domain != self.ehlo_domain: + details = ["diagnosis_mail_fcrdns_different_from_ehlo_domain_details"] + details yield dict(meta={"test": "mail_fcrdns", "ip": ip}, data={"ehlo_domain": self.ehlo_domain, "rdns_domain": rdns_domain}, status="ERROR", - summary="diagnosis_mail_fcrdns_different_from_ehlo_domain") + summary="diagnosis_mail_fcrdns_different_from_ehlo_domain", + details=details) def check_blacklist(self): @@ -210,12 +222,13 @@ class MailDiagnoser(Diagnoser): if global_ipv4: outgoing_ips.append(global_ipv4) - ipv6 = Diagnoser.get_cached_report("ip", {"test": "ipv6"}) or {} - if ipv6.get("status") == "SUCCESS": - outgoing_ipversions.append(6) - global_ipv6 = ipv6.get("data", {}).get("global", {}) - if global_ipv6: - outgoing_ips.append(global_ipv6) + if settings_get("smtp.ipv6"): + ipv6 = Diagnoser.get_cached_report("ip", {"test": "ipv6"}) or {} + if ipv6.get("status") == "SUCCESS": + outgoing_ipversions.append(6) + global_ipv6 = ipv6.get("data", {}).get("global", {}) + if global_ipv6: + outgoing_ips.append(global_ipv6) return (outgoing_ipversions, outgoing_ips) def main(args, env, loggers): diff --git a/locales/en.json b/locales/en.json index 8272fc86c..0fc9ca777 100644 --- a/locales/en.json +++ b/locales/en.json @@ -185,7 +185,7 @@ "diagnosis_swap_notsomuch": "The system has only {total} swap. You should consider having at least 256 MB to avoid situations where the system runs out of memory.", "diagnosis_swap_ok": "The system has {total} of swap!", "diagnosis_mail_outgoing_port_25_ok": "Outgoing port 25 is open, emails can be sent", - "diagnosis_mail_outgoing_port_25_blocked": "Outgoing port 25 appears to be bloecked in IPv{ipversion}", + "diagnosis_mail_outgoing_port_25_blocked": "Outgoing port 25 appears to be blocked in IPv{ipversion}", "diagnosis_mail_outgoing_port_25_blocked_details": "You should first try to unblock it in your internet service provider (or hosting provider) configuration panel or by sending a ticket to your hosting provider. Meanwhile, the server won't be able to send emails to other servers.", "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Some providers won't let you unblock outgoing port 25 because they don't care about Net Neutrality.
- Some of them provide the alternative of using a mail server relay though it implies that the relay will be able to spy on your email traffic.
- A privacy-friendly alternative is to use a VPN *with a dedicated public IP* to bypass this kind of limits. See https://yunohost.org/#/vpn_advantage
- Finally, it's also possible to change of provider", "diagnosis_mail_ehlo_ok": "Postfix mail service answer correctly from outside", @@ -199,8 +199,11 @@ "diagnosis_mail_ehlo_could_not_diagnose_details": "Error: {error}", "diagnosis_mail_fcrdns_ok": "Your reverse DNS is well configured", "diagnosis_mail_fcrdns_dns_missing": "No reverse DNS defined for the ip {ip}", - "diagnosis_mail_fcrdns_dns_missing_details": "You can configure it on ", - "diagnosis_mail_fcrdns_different_from_ehlo_domain": "Your reverse DNS {rdns_domain} is different from your EHLO domain {ehlo_domain} on {ip}", + "diagnosis_mail_fcrdns_nok_details": "You should first try to configure the reverse DNS with {ehlo_domain} on your internet service provider (or hosting provider) config panel or by sending a ticket to your hosting provider. Meanwhile, some outgoing mails won't be delivered.", + "diagnosis_mail_fcrdns_nok_alternatives_4": "Some providers won't let you configure it or the feature is broken on their config panel. If you are experiencing some server refusing your email for this reason, you could try those solutions:
- Some ISP provide the alternative of using a mail server relay though it implies that the relay will be able to spy on your email traffic.
- A privacy-friendly alternative is to use a VPN *with a dedicated public IP* to bypass this kind of limits. See https://yunohost.org/#/vpn_advantage
- Finally, it's also possible to change of provider", + "diagnosis_mail_fcrdns_nok_alternatives_6": "Some providers won't let you configure it or the feature is broken on their config panel in IPv6. If your reverse DNS is ok in IPv4, you can try to disable the use of IPv6 to send mail by running yunohost settings set smtp.ipv6 -v off ; yunohost tools regen-conf postfix. Note: with this last solution you won't be able to send or received emails from the rare ipv6 only servers.", + "diagnosis_mail_fcrdns_different_from_ehlo_domain": "The reverse DNS is different from your EHLO domain on {ip}", + "diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "Current reverse DNS: {rdns_domain}
Expected value: {ehlo_domain}", "diagnosis_mail_blacklist_ok": "IPs and domains used by this server to send mail are not on most used email blacklists", "diagnosis_mail_blacklist_listed_by": "{item} is blacklisted on {blacklist_name}", "diagnosis_mail_blacklist_reason": "The blacklist explains: {reason}", diff --git a/src/yunohost/settings.py b/src/yunohost/settings.py index 72477e4de..c016e0809 100644 --- a/src/yunohost/settings.py +++ b/src/yunohost/settings.py @@ -70,6 +70,7 @@ DEFAULTS = OrderedDict([ ("security.postfix.compatibility", {"type": "enum", "default": "intermediate", "choices": ["intermediate", "modern"]}), ("pop3.enabled", {"type": "bool", "default": False}), + ("smtp.ipv6", {"type": "bool", "default": True}), ]) From ed75108142840090b7dd6f249ad4e39ffac6000c Mon Sep 17 00:00:00 2001 From: ljf Date: Sun, 19 Apr 2020 02:32:15 +0200 Subject: [PATCH 1026/3170] [fix] Cache duration --- data/hooks/diagnosis/24-mail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/hooks/diagnosis/24-mail.py b/data/hooks/diagnosis/24-mail.py index 022b24114..0c89fd7e0 100644 --- a/data/hooks/diagnosis/24-mail.py +++ b/data/hooks/diagnosis/24-mail.py @@ -20,7 +20,7 @@ DEFAULT_DNS_BLACKLIST = "/usr/share/yunohost/other/dnsbl_list.yml" class MailDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] - cache_duration = 0 + cache_duration = 12 * 3600 dependencies = ["ip"] def run(self): From a33ae634c3d78245791585959c9326b0c492e3ad Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 19 Apr 2020 02:39:36 +0200 Subject: [PATCH 1027/3170] We need those quotes around spf --- 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 1d1e10da1..c725b58c9 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -457,7 +457,7 @@ def _build_dns_conf(domain, ttl=3600, include_empty_AAAA_if_no_ipv6=False): mail = [ ["@", ttl, "MX", "10 %s." % domain], - ["@", ttl, "TXT", "v=spf1 a mx -all"], + ["@", ttl, "TXT", '"v=spf1 a mx -all"'], ] # DKIM/DMARC record From da112a3668df850d007fba9cadc8f4d284c02fe1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 19 Apr 2020 02:44:39 +0200 Subject: [PATCH 1028/3170] Let's push also CAA for nohost.me ... Assuming dynette will eventually allow it .. --- src/yunohost/dyndns.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index 6e597fbbf..efa25f23f 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -259,11 +259,6 @@ def dyndns_update(operation_logger, dyn_host="dyndns.yunohost.org", domain=None, dns_conf = _build_dns_conf(domain) - for i, record in enumerate(dns_conf["extra"]): - # Ignore CAA record ... not sure why, we could probably enforce it... - if record[3] == "CAA": - del dns_conf["extra"][i] - # Delete custom DNS records, we don't support them (have to explicitly # authorize them on dynette) for category in dns_conf.keys(): From f78af06a355ad4590e7b27937bec897a01d75720 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 19 Apr 2020 02:59:16 +0200 Subject: [PATCH 1029/3170] Lazy loading for performance, possibly --- src/yunohost/diagnosis.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index bd52f57f8..bfb2619eb 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -27,8 +27,6 @@ import re import os import time -import requests -import socket from moulinette import m18n, msettings from moulinette.utils import log @@ -496,6 +494,10 @@ class Diagnoser(): @staticmethod def remote_diagnosis(uri, data, ipversion, timeout=30): + # Lazy loading for performance + import requests + import socket + # Monkey patch socket.getaddrinfo to force request() to happen in ipv4 # or 6 ... # Inspired by https://stackoverflow.com/a/50044152 From b53695af2743a300fe3dc3dc4ff6ef478dabe200 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 19 Apr 2020 03:08:34 +0200 Subject: [PATCH 1030/3170] Fix _could_not_diagnose string consistency --- locales/ca.json | 6 ++++-- locales/en.json | 2 +- locales/eo.json | 6 ++++-- locales/es.json | 6 ++++-- locales/fr.json | 6 ++++-- locales/oc.json | 6 ++++-- 6 files changed, 21 insertions(+), 11 deletions(-) diff --git a/locales/ca.json b/locales/ca.json index 07e2c6f27..6416307f6 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -506,7 +506,8 @@ "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_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. Error: {error}", + "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}", "domain_cannot_remove_main_add_new_one": "No es pot eliminar «{domain:s}» ja que és el domini principal i únic domini, primer s'ha d'afegir un altre domini utilitzant «yunohost domain add », i després fer-lo el domini principal amb «yunohost domain main-domain -n » i després es pot eliminar el domini «{domain:s}» utilitzant «yunohost domain remove {domain:s}».", "diagnosis_basesystem_host": "El servidor funciona amb Debian {debian_version}", "diagnosis_basesystem_kernel": "El servidor funciona amb el nucli de Linux {kernel_version}", @@ -559,7 +560,8 @@ "diagnosis_description_ports": "Exposició dels ports", "diagnosis_description_regenconf": "Configuració del sistema", "diagnosis_description_security": "Verificacions de seguretat", - "diagnosis_ports_could_not_diagnose": "No s'ha pogut diagnosticar si els ports són accessibles des de l'exterior. Error: {error}", + "diagnosis_ports_could_not_diagnose": "No s'ha pogut diagnosticar si els ports són accessibles des de l'exterior.", + "diagnosis_ports_could_not_diagnose_details": "Error: {error}", "diagnosis_ports_unreachable": "El port {port} no és accessible des de l'exterior.", "diagnosis_ports_ok": "El port {port} és accessible des de l'exterior.", "diagnosis_http_ok": "El domini {domain} és accessible per mitjà de HTTP des de fora de la xarxa local.", diff --git a/locales/en.json b/locales/en.json index a5048b8c9..b850b5b41 100644 --- a/locales/en.json +++ b/locales/en.json @@ -192,7 +192,7 @@ "diagnosis_mail_ehlo_unavailable": "Postfix mail service don't answer to EHLO request on IPv{ipversion}", "diagnosis_mail_ehlo_wrong": "A mail server answers {wrong_ehlo} instead {right_ehlo} on IPv{ipversion}", "diagnosis_mail_ehlo_could_not_diagnose": "Could not diagnose if postfix mail server is reachable from outside in IPv{ipversion}.", - "diagnosis_mail_ehlo_could_not_diagnose_details": "{error}", + "diagnosis_mail_ehlo_could_not_diagnose_details": "Error: {error}", "diagnosis_mail_fcrdns_ok": "Your reverse DNS is well configured", "diagnosis_mail_fcrdns_dns_missing": "No reverse DNS defined for the ip {ip}", "diagnosis_mail_fcrdns_different_from_ehlo_domain": "Your reverse DNS {rdns_domain} is different from your EHLO domain {ehlo_domain} on {ip}", diff --git a/locales/eo.json b/locales/eo.json index 36396d6f1..64571e7e7 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -555,7 +555,8 @@ "diagnosis_description_services": "Servo kontrolas staton", "diagnosis_description_systemresources": "Rimedaj sistemoj", "diagnosis_description_security": "Sekurecaj kontroloj", - "diagnosis_ports_could_not_diagnose": "Ne povis diagnozi, ĉu haveblaj havenoj de ekstere. Eraro: {error}", + "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_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", @@ -577,7 +578,8 @@ "diagnosis_ports_ok": "Haveno {port} atingeblas de ekstere.", "diagnosis_ports_needed_by": "Eksponi ĉi tiun havenon necesas por servo {service}", "diagnosis_ports_forwarding_tip": "Por solvi ĉi tiun problemon, plej probable vi 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. Eraro: {error}", + "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} atingeblas de ekstere.", "diagnosis_http_unreachable": "Domajno {domain} estas atingebla per HTTP de ekstere.", "domain_cannot_remove_main_add_new_one": "Vi ne povas forigi '{domain:s}' ĉar ĝi estas la ĉefa domajno kaj via sola domajno, vi devas unue aldoni alian domajnon uzante ''yunohost domain add ', tiam agordi kiel ĉefan domajnon uzante 'yunohost domain main-domain -n ' kaj tiam vi povas forigi la domajnon' {domain:s} 'uzante' yunohost domain remove {domain:s} '.'", diff --git a/locales/es.json b/locales/es.json index c21585e7b..beefb838c 100644 --- a/locales/es.json +++ b/locales/es.json @@ -572,7 +572,8 @@ "diagnosis_ports_needed_by": "La apertura de este puerto es requerida para la funcionalidad {category} (service {service})", "diagnosis_ports_ok": "El puerto {port} es accesible desde internet.", "diagnosis_ports_unreachable": "El puerto {port} no es accesible desde internet.", - "diagnosis_ports_could_not_diagnose": "No se puede comprobar si los puertos están accesibles desde el exterior. Error: {error}", + "diagnosis_ports_could_not_diagnose": "No se puede comprobar si los puertos están accesibles desde el exterior.", + "diagnosis_ports_could_not_diagnose_details": "Error: {error}", "diagnosis_description_security": "Validación de seguridad", "diagnosis_description_regenconf": "Configuraciones de sistema", "diagnosis_description_mail": "Correo electrónico", @@ -595,6 +596,7 @@ "diagnosis_http_connection_error": "Error de conexión: Ne se pudo conectar al dominio solicitado,", "diagnosis_http_timeout": "El intento de contactar a su servidor desde internet corrió fuera de tiempo. Al parece esta incomunicado. Debería verificar que nginx corre en el puerto 80, y que la redireción del puerto 80 no interfiere con en el firewall.", "diagnosis_http_ok": "El Dominio {domain} es accesible desde internet a través de HTTP.", - "diagnosis_http_could_not_diagnose": "No se pudo verificar si el dominio es accesible desde internet. Error: {error}", + "diagnosis_http_could_not_diagnose": "No se pudo verificar si el dominio es accesible desde internet.", + "diagnosis_http_could_not_diagnose_details": "Error: {error}", "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" } diff --git a/locales/fr.json b/locales/fr.json index 7e77cdc7a..94cb76a5a 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -562,7 +562,8 @@ "diagnosis_description_ports": "Exposition des ports", "diagnosis_description_regenconf": "Configurations système", "diagnosis_description_security": "Contrôles de sécurité", - "diagnosis_ports_could_not_diagnose": "Impossible de diagnostiquer si les ports sont accessibles de l'extérieur. Erreur: {error}", + "diagnosis_ports_could_not_diagnose": "Impossible de diagnostiquer si les ports sont accessibles de l'extérieur.", + "diagnosis_ports_could_not_diagnose_details": "Erreur: {error}", "apps_catalog_updating": "Mise à jour du catalogue d'applications…", "apps_catalog_obsolete_cache": "Le cache du catalogue d'applications est vide ou obsolète.", "apps_catalog_update_success": "Le catalogue des applications a été mis à jour !", @@ -570,7 +571,8 @@ "diagnosis_description_mail": "Email", "diagnosis_ports_unreachable": "Le port {port} n'est pas accessible de l'extérieur.", "diagnosis_ports_ok": "Le port {port} est accessible de l'extérieur.", - "diagnosis_http_could_not_diagnose": "Impossible de diagnostiquer si le domaine est accessible de l'extérieur. Erreur: {error}", + "diagnosis_http_could_not_diagnose": "Impossible de diagnostiquer si le domaine est accessible de l'extérieur.", + "diagnosis_http_could_not_diagnose_details": "Erreur: {error}", "diagnosis_http_ok": "Le domaine {domain} est accessible au travers de HTTP depuis l'extérieur.", "diagnosis_http_unreachable": "Le domaine {domain} est inaccessible au travers de HTTP depuis l'extérieur.", "diagnosis_unknown_categories": "Les catégories suivantes sont inconnues: {categories}", diff --git a/locales/oc.json b/locales/oc.json index 97978bb18..95f581851 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -539,8 +539,10 @@ "diagnosis_dns_missing_record": "Segon la configuracion DNS recomandada, vos calriá ajustar un enregistrament DNS\ntipe: {type}\nnom: {name}\nvalor: {value}", "diagnosis_dns_discrepancy": "Segon la configuracion DNS recomandada, la valor per l’enregistrament DNS\ntipe: {type}\nnom: {name}\ndeuriá èsser: {current}\nallòc de: {value}", "diagnosis_regenconf_manually_modified_debian_details": "Es pas problematic, mas car téner d’agacher...", - "diagnosis_ports_could_not_diagnose": "Impossible de diagnosticar se los pòrts son accessibles de l’exterior. Error : {error}", - "diagnosis_http_could_not_diagnose": "Impossible de diagnosticar se lo domeni es accessible de l’exterior. Error : {error}", + "diagnosis_ports_could_not_diagnose": "Impossible de diagnosticar se los pòrts son accessibles de l’exterior.", + "diagnosis_ports_could_not_diagnose_details": "Error : {error}", + "diagnosis_http_could_not_diagnose": "Impossible de diagnosticar se lo domeni es accessible de l’exterior.", + "diagnosis_http_could_not_diagnose_details": "Error : {error}", "apps_catalog_updating": "Actualizacion del catalòg d’aplicacion…", "apps_catalog_failed_to_download": "Telecargament impossible del catalòg d’aplicacions {apps_catalog} : {error}", "apps_catalog_obsolete_cache": "La memòria cache del catalòg d’aplicacion es voida o obsolèta.", From 70566b70220d427ec0215c6da310db19f9eaef22 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 19 Apr 2020 03:19:34 +0200 Subject: [PATCH 1031/3170] Ignore some string keys which are only fragments concatenated with other stuff --- tests/test_i18n_keys.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_i18n_keys.py b/tests/test_i18n_keys.py index 0d5af33f6..d6df56452 100644 --- a/tests/test_i18n_keys.py +++ b/tests/test_i18n_keys.py @@ -49,6 +49,9 @@ def find_expected_string_keys(): for python_file in glob.glob("data/hooks/diagnosis/*.py"): content = open(python_file).read() for m in p3.findall(content): + if m.endswith("_"): + # Ignore some name fragments which are actually concatenated with other stuff.. + continue yield m yield "diagnosis_description_" + os.path.basename(python_file)[:-3].split("-")[-1] From e6f0091f59b37be9bc6c365da98b26e5c50d59f5 Mon Sep 17 00:00:00 2001 From: ljf Date: Sun, 19 Apr 2020 03:45:59 +0200 Subject: [PATCH 1032/3170] [fix] Rename ipv6 mail settings + desc --- data/hooks/conf_regen/19-postfix | 2 +- locales/en.json | 1 + src/yunohost/settings.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/data/hooks/conf_regen/19-postfix b/data/hooks/conf_regen/19-postfix index 172438f37..10076b680 100755 --- a/data/hooks/conf_regen/19-postfix +++ b/data/hooks/conf_regen/19-postfix @@ -35,7 +35,7 @@ do_pre_regen() { > "${default_dir}/postsrsd" # adapt it for IPv4-only hosts - ipv6="$(yunohost settings get 'smtp.ipv6')" + ipv6="$(yunohost settings get 'smtp.allow_ipv6')" if [ "$ipv6" == "False" ] || [ ! -f /proc/net/if_inet6 ]; then sed -i \ 's/ \[::ffff:127.0.0.0\]\/104 \[::1\]\/128//g' \ diff --git a/locales/en.json b/locales/en.json index 0fc9ca777..fc4726aed 100644 --- a/locales/en.json +++ b/locales/en.json @@ -312,6 +312,7 @@ "global_settings_setting_security_postfix_compatibility": "Compatibility vs. security tradeoff for the Postfix server. Affects the ciphers (and other security-related aspects)", "global_settings_unknown_setting_from_settings_file": "Unknown key in settings: '{setting_key:s}', discard it and save it in /etc/yunohost/settings-unknown.json", "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Allow the use of (deprecated) DSA hostkey for the SSH daemon configuration", + "global_settings_setting_smtp_allow_ipv6": "Allow the use of IPv6 to receive and send mail", "global_settings_unknown_type": "Unexpected situation, the setting {setting:s} appears to have the type {unknown_type:s} but it is not a type supported by the system.", "good_practices_about_admin_password": "You are now about to define a new administration password. The password should be at least 8 characters long—though it is good practice to use a longer password (i.e. a passphrase) and/or to use a variation of characters (uppercase, lowercase, digits and special characters).", "good_practices_about_user_password": "You are now about to define a new user password. The password should be at least 8 characters long—though it is good practice to use a longer password (i.e. a passphrase) and/or to a variation of characters (uppercase, lowercase, digits and special characters).", diff --git a/src/yunohost/settings.py b/src/yunohost/settings.py index c016e0809..db94e7429 100644 --- a/src/yunohost/settings.py +++ b/src/yunohost/settings.py @@ -70,7 +70,7 @@ DEFAULTS = OrderedDict([ ("security.postfix.compatibility", {"type": "enum", "default": "intermediate", "choices": ["intermediate", "modern"]}), ("pop3.enabled", {"type": "bool", "default": False}), - ("smtp.ipv6", {"type": "bool", "default": True}), + ("smtp.allow_ipv6", {"type": "bool", "default": True}), ]) From 40141c84f39b1f17a387ca55aa4505046a729e3c Mon Sep 17 00:00:00 2001 From: ljf Date: Sun, 19 Apr 2020 03:55:50 +0200 Subject: [PATCH 1033/3170] [enh] Auto update postfix on smtp.allow_ipv6 change --- src/yunohost/settings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/yunohost/settings.py b/src/yunohost/settings.py index db94e7429..c1edadb93 100644 --- a/src/yunohost/settings.py +++ b/src/yunohost/settings.py @@ -321,6 +321,7 @@ def reconfigure_ssh(setting_name, old_value, new_value): if old_value != new_value: service_regen_conf(names=['ssh']) +@post_change_hook("smtp.allow_ipv6") @post_change_hook("security.postfix.compatibility") def reconfigure_postfix(setting_name, old_value, new_value): if old_value != new_value: From 6e334eba955439f30a1511ef0ceeb02f170fd93d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 19 Apr 2020 04:14:48 +0200 Subject: [PATCH 1034/3170] Wording / weird translation.. --- locales/en.json | 2 +- locales/fr.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/en.json b/locales/en.json index b59b53325..5c97c01fc 100644 --- a/locales/en.json +++ b/locales/en.json @@ -145,7 +145,7 @@ "diagnosis_basesystem_ynh_inconsistent_versions": "You are running inconsistent versions of the YunoHost packages... most probably because of a failed or partial upgrade.", "diagnosis_display_tip": "To see the issues found, you can go to the Diagnosis section of the webadmin, or run 'yunohost diagnosis show --issues' from the command-line.", "diagnosis_failed_for_category": "Diagnosis failed for category '{category}': {error}", - "diagnosis_cache_still_valid": "(Cache still valid for {category} diagnosis. Not re-diagnosing yet!)", + "diagnosis_cache_still_valid": "(Cache still valid for {category} diagnosis. Won't re-diagnose it yet!)", "diagnosis_cant_run_because_of_dep": "Can't run diagnosis for {category} while there are important issues related to {dep}.", "diagnosis_ignored_issues": "(+ {nb_ignored} ignored issue(s))", "diagnosis_found_errors": "Found {errors} significant issue(s) related to {category}!", diff --git a/locales/fr.json b/locales/fr.json index 2431af8da..6b8ddcabe 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -521,7 +521,7 @@ "diagnosis_basesystem_ynh_inconsistent_versions": "Vous exécutez des versions incohérentes des packages YunoHost ... probablement à cause d'une mise à niveau partielle ou échouée.", "diagnosis_display_tip_cli": "Vous pouvez exécuter 'yunohost diagnosis show --issues' pour afficher les problèmes détectés.", "diagnosis_failed_for_category": "Échec du diagnostic pour la catégorie '{category}': {error}", - "diagnosis_cache_still_valid": "(Le cache est toujours valide pour le diagnostic {category}. Pas re-diagnostiquer pour le moment!)", + "diagnosis_cache_still_valid": "(Le cache est encore valide pour le diagnostic {category}. Il ne sera pas re-diagnostiqué pour le moment!)", "diagnosis_ignored_issues": "(+ {nb_ignored} questions ignorée(s))", "diagnosis_found_warnings": "Trouvé {warnings} objet(s) pouvant être amélioré(s) pour {category}.", "diagnosis_everything_ok": "Tout semble bien pour {category} !", From 39f0aa3ef32d54c5c45a4621d052e51cb4586061 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 19 Apr 2020 04:44:09 +0200 Subject: [PATCH 1035/3170] Improve wording --- data/hooks/diagnosis/24-mail.py | 13 ++++++----- locales/en.json | 40 ++++++++++++++++----------------- 2 files changed, 28 insertions(+), 25 deletions(-) diff --git a/data/hooks/diagnosis/24-mail.py b/data/hooks/diagnosis/24-mail.py index 5457c5890..4ced72959 100644 --- a/data/hooks/diagnosis/24-mail.py +++ b/data/hooks/diagnosis/24-mail.py @@ -109,25 +109,28 @@ class MailDiagnoser(Diagnoser): for ip in self.ips: if ":" in ip: + ipversion = 6 details = ["diagnosis_mail_fcrdns_nok_details", "diagnosis_mail_fcrdns_nok_alternatives_6"] else: + ipversion = 4 details = ["diagnosis_mail_fcrdns_nok_details", "diagnosis_mail_fcrdns_nok_alternatives_4"] try: rdns_domain, _, _ = socket.gethostbyaddr(ip) except socket.herror: - yield dict(meta={"test": "mail_fcrdns", "ip": ip}, - data={"ehlo_domain": self.ehlo_domain}, + yield dict(meta={"test": "mail_fcrdns", "ipversion": ipversion}, + data={"ip": ip, "ehlo_domain": self.ehlo_domain}, status="ERROR", summary="diagnosis_mail_fcrdns_dns_missing", details=details) continue if rdns_domain != self.ehlo_domain: details = ["diagnosis_mail_fcrdns_different_from_ehlo_domain_details"] + details - yield dict(meta={"test": "mail_fcrdns", "ip": ip}, - data={"ehlo_domain": self.ehlo_domain, + yield dict(meta={"test": "mail_fcrdns", "ipversion": ipversion}, + data={"ip": ip, + "ehlo_domain": self.ehlo_domain, "rdns_domain": rdns_domain}, status="ERROR", summary="diagnosis_mail_fcrdns_different_from_ehlo_domain", @@ -222,7 +225,7 @@ class MailDiagnoser(Diagnoser): if global_ipv4: outgoing_ips.append(global_ipv4) - if settings_get("smtp.ipv6"): + if settings_get("smtp.allow_ipv6"): ipv6 = Diagnoser.get_cached_report("ip", {"test": "ipv6"}) or {} if ipv6.get("status") == "SUCCESS": outgoing_ipversions.append(6) diff --git a/locales/en.json b/locales/en.json index 5c97c01fc..400413e3d 100644 --- a/locales/en.json +++ b/locales/en.json @@ -184,29 +184,29 @@ "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_mail_outgoing_port_25_ok": "Outgoing port 25 is open, emails can be sent", - "diagnosis_mail_outgoing_port_25_blocked": "Outgoing port 25 appears to be blocked in IPv{ipversion}", - "diagnosis_mail_outgoing_port_25_blocked_details": "You should first try to unblock it in your internet service provider (or hosting provider) configuration panel or by sending a ticket to your hosting provider. Meanwhile, the server won't be able to send emails to other servers.", - "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Some providers won't let you unblock outgoing port 25 because they don't care about Net Neutrality.
- Some of them provide the alternative of using a mail server relay though it implies that the relay will be able to spy on your email traffic.
- A privacy-friendly alternative is to use a VPN *with a dedicated public IP* to bypass this kind of limits. See https://yunohost.org/#/vpn_advantage
- Finally, it's also possible to change of provider", - "diagnosis_mail_ehlo_ok": "Postfix mail service answer correctly from outside", - "diagnosis_mail_ehlo_unreachable": "SMTP server unreachable on IPv{ipversion}", - "diagnosis_mail_ehlo_unreachable_details": "Could not open a connection on port 25 through IPv{ipversion}, probably because of a firewall, port forwarding issue or postfix service down", + "diagnosis_mail_outgoing_port_25_ok": "Outgoing port 25 is open, emails can be sent!", + "diagnosis_mail_outgoing_port_25_blocked": "Outgoing port 25 appears to be blocked in IPv{ipversion}. This prevent emails from being sent to other servers.", + "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).", + "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Some providers won't let you unblock outgoing port 25 because they don't care about Net Neutrality.
- Some of them provide the alternative of using a mail server relay though it implies that the relay will be able to spy on your email traffic.
- A privacy-friendly alternative is to use a VPN *with a dedicated public IP* to bypass this kind of limits. See https://yunohost.org/#/vpn_advantage
- You can also consider switching to a more net neutrality-friendly provider", + "diagnosis_mail_ehlo_ok": "The SMTP mail server is reachable from the outside, which allows to receive email.", + "diagnosis_mail_ehlo_unreachable": "The SMTP mail server is unreachable from the outside on IPv{ipversion}. It won't be able to receive emails.", + "diagnosis_mail_ehlo_unreachable_details": "Could not open a connection on port 25 to your server in IPv{ipversion}. It appears to be unreachable.
1. The most common cause for this issue is that port 25 is not correctly forwarded to your server.
2. You should also make sure that service postfix is running.
3. On more complex setups: make sure that no firewall or reverse-proxy is interfering.", "diagnosis_mail_ehlo_bad_answer": "A non-SMTP service answered on port 25 on IPv{ipversion}", "diagnosis_mail_ehlo_bad_answer_details": "It could be due to an other machine answering instead of your server.", - "diagnosis_mail_ehlo_wrong": "An other SMTP server answers on IPv{ipversion}", - "diagnosis_mail_ehlo_wrong_details": "The remote diagnoser return a wrong EHLO answer from your IPv{ipversion}.
Received: {wrong_ehlo}
Expected: {right_ehlo}
You probably have a port forwarding issue or a reverse proxy server unconfigured for mail.", + "diagnosis_mail_ehlo_wrong": "A different SMTP server answers on IPv{ipversion}. It will probably not be able to receive emails.", + "diagnosis_mail_ehlo_wrong_details": "The EHLO received by the remote diagnoser in IPv{ipversion} is different from your server's domain.
Received EHLO: {wrong_ehlo}
Expected: {right_ehlo}
The most common cause for this issue is that port 25 is not correctly forwarded to your server. Alternatively, make sure that no firewall or reverse-proxy is interfering.", "diagnosis_mail_ehlo_could_not_diagnose": "Could not diagnose if postfix mail server is reachable from outside in IPv{ipversion}.", "diagnosis_mail_ehlo_could_not_diagnose_details": "Error: {error}", - "diagnosis_mail_fcrdns_ok": "Your reverse DNS is well configured", - "diagnosis_mail_fcrdns_dns_missing": "No reverse DNS defined for the ip {ip}", - "diagnosis_mail_fcrdns_nok_details": "You should first try to configure the reverse DNS with {ehlo_domain} on your internet service provider (or hosting provider) config panel or by sending a ticket to your hosting provider. Meanwhile, some outgoing mails won't be delivered.", - "diagnosis_mail_fcrdns_nok_alternatives_4": "Some providers won't let you configure it or the feature is broken on their config panel. If you are experiencing some server refusing your email for this reason, you could try those solutions:
- Some ISP provide the alternative of using a mail server relay though it implies that the relay will be able to spy on your email traffic.
- A privacy-friendly alternative is to use a VPN *with a dedicated public IP* to bypass this kind of limits. See https://yunohost.org/#/vpn_advantage
- Finally, it's also possible to change of provider", - "diagnosis_mail_fcrdns_nok_alternatives_6": "Some providers won't let you configure it or the feature is broken on their config panel in IPv6. If your reverse DNS is ok in IPv4, you can try to disable the use of IPv6 to send mail by running yunohost settings set smtp.ipv6 -v off ; yunohost tools regen-conf postfix. Note: with this last solution you won't be able to send or received emails from the rare ipv6 only servers.", - "diagnosis_mail_fcrdns_different_from_ehlo_domain": "The reverse DNS is different from your EHLO domain on {ip}", + "diagnosis_mail_fcrdns_ok": "Your reverse DNS is correctly configured!", + "diagnosis_mail_fcrdns_dns_missing": "No reverse DNS is defined in IPv{ipversion}. Some emails may fail to get delivered or may get flagged as spam.", + "diagnosis_mail_fcrdns_nok_details": "You should first try to configure the reverse DNS with {ehlo_domain} in your internet router interface or your hosting provider interface. (Some hosting provider may require you to send them a support ticket for this).", + "diagnosis_mail_fcrdns_nok_alternatives_4": "Some providers won't let you configure your reverse DNS (or their feature might be broken...). If you are experiencing issues because of this, consider the following solutions:
- Some ISP provide the alternative of using a mail server relay though it implies that the relay will be able to spy on your email traffic.
- A privacy-friendly alternative is to use a VPN *with a dedicated public IP* to bypass this kind of limits. See https://yunohost.org/#/vpn_advantage
- Finally, it's also possible to change of provider", + "diagnosis_mail_fcrdns_nok_alternatives_6": "Some providers won't let you configure your reverse DNS (or their feature might be broken...). If your reverse DNS is correctly configured for IPv4, you can try disabling the use of IPv6 when sending emails by running yunohost settings set smtp.allow_ipv6 -v off. Note: this last solution means that you won't be able to send or receive emails from the few IPv6-only servers out there.", + "diagnosis_mail_fcrdns_different_from_ehlo_domain": "The reverse DNS is not correctly configured in IPv{ipversion}. Some emails may fail to get delivered or may get flagged as spam.", "diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "Current reverse DNS: {rdns_domain}
Expected value: {ehlo_domain}", - "diagnosis_mail_blacklist_ok": "IPs and domains used by this server to send mail are not on most used email blacklists", - "diagnosis_mail_blacklist_listed_by": "{item} is blacklisted on {blacklist_name}", - "diagnosis_mail_blacklist_reason": "The blacklist explains: {reason}", + "diagnosis_mail_blacklist_ok": "The IPs and domains used by this server do not appear to be blacklisted", + "diagnosis_mail_blacklist_listed_by": "Your IP or domain {item} is blacklisted on {blacklist_name}", + "diagnosis_mail_blacklist_reason": "The blacklist reason is: {reason}", "diagnosis_mail_blacklist_website": "After identifying why you are listed and fixed it, feel free to ask for delisting on {blacklist_website}", "diagnosis_mail_queue_ok": "{nb_pending} pending emails in the mail queues", "diagnosis_mail_queue_unavailable": "Can not consult number of pending emails in queue", @@ -240,9 +240,9 @@ "diagnosis_http_could_not_diagnose": "Could not diagnose if domains are reachable from outside in IPv{ipversion}.", "diagnosis_http_could_not_diagnose_details": "Error: {error}", "diagnosis_http_ok": "Domain {domain} is reachable through HTTP from outside the local network.", - "diagnosis_http_timeout": "Timed-out while trying to contact your server from outside. It appears to be unreachable.
1. The most common cause for this issue is that you did not correctly configure port forwarding for port 80.
2. Also make sure that the web server nginx is running
3. On more complex setups: make sure that a firewall or reverse-proxy is not interfering.", + "diagnosis_http_timeout": "Timed-out while trying to contact your server from outside. It appears to be unreachable.
1. The most common cause for this issue is that port 80 (and 443) are not correctly forwarded to your server.
2. You should also make sure that the service nginx is running
3. On more complex setups: make sure that no firewall or reverse-proxy is interfering.", "diagnosis_http_connection_error": "Connection error: could not connect to the requested domain, it's very likely unreachable.", - "diagnosis_http_bad_status_code": "It looks like another machine (maybe your internet router) answered instead of your server.
1. The most common cause for this issue is that you did not correctly configure port forwarding for port 80.
2. On more complex setups: make sure that a firewall or reverse-proxy is not interfering.", + "diagnosis_http_bad_status_code": "It looks like another machine (maybe your internet router) answered instead of your server.
1. The most common cause for this issue is that port 80 (and 443) are not correctly forwarded to your server.
2. On more complex setups: make sure that no firewall or reverse-proxy is interfering.", "diagnosis_http_unreachable": "Domain {domain} appears unreachable through HTTP from outside the local network.", "diagnosis_http_partially_unreachable": "Domain {domain} appears unreachable through HTTP from outside the local network in IPv{failed}, though it works in IPv{passed}.", "diagnosis_http_nginx_conf_not_up_to_date": "This domain's nginx configuration appears to have been modified manually, and prevents YunoHost from diagnosing if it's reachable on HTTP.", From 878bb82d9df14cb14fc40dec3b775a9a6e9fd5c0 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 19 Apr 2020 05:10:32 +0200 Subject: [PATCH 1036/3170] Hmgn bad fr translation --- locales/fr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/fr.json b/locales/fr.json index 6b8ddcabe..3f7776009 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -541,7 +541,7 @@ "diagnosis_diskusage_low": "Le stockage {mountpoint} (sur le périphérique {device}) ne dispose que de {free} ({free_percent}%). Faites attention.", "diagnosis_ram_verylow": "Le système ne dispose plus que de {available} ({available_percent}%)! (sur {total})", "diagnosis_ram_low": "Le système n'a plus de {available} ({available_percent}%) RAM sur {total}. Faites attention.", - "diagnosis_swap_none": "Le système n'a aucun échange. Vous devez envisager d’ajouter au moins {recommended} de swap pour éviter les situations où le système manque de mémoire.", + "diagnosis_swap_none": "Le système n'a aucun espace de swap. Vous devriez envisager d’ajouter au moins {recommended} de swap pour éviter les situations où le système manque de mémoire.", "diagnosis_swap_notsomuch": "Le système ne dispose que de {total} de swap. Vous devez envisager d'avoir au moins {recommended} pour éviter les situations où le système manque de mémoire.", "diagnosis_swap_ok": "Le système dispose de {total} de swap !", "diagnosis_regenconf_manually_modified": "Le fichier de configuration {file} a été modifié manuellement.", From 4c95d52c37864fc2ff5d5c632e3e8fd390af4d77 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 19 Apr 2020 05:29:32 +0200 Subject: [PATCH 1037/3170] More small wording/translation improvement.. --- locales/en.json | 8 ++++---- locales/fr.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/locales/en.json b/locales/en.json index 400413e3d..c2c087031 100644 --- a/locales/en.json +++ b/locales/en.json @@ -184,16 +184,16 @@ "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_mail_outgoing_port_25_ok": "Outgoing port 25 is open, emails can be sent!", - "diagnosis_mail_outgoing_port_25_blocked": "Outgoing port 25 appears to be blocked in IPv{ipversion}. This prevent emails from being sent to other servers.", + "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).", "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Some providers won't let you unblock outgoing port 25 because they don't care about Net Neutrality.
- Some of them provide the alternative of using a mail server relay though it implies that the relay will be able to spy on your email traffic.
- A privacy-friendly alternative is to use a VPN *with a dedicated public IP* to bypass this kind of limits. See https://yunohost.org/#/vpn_advantage
- You can also consider switching to a more net neutrality-friendly provider", - "diagnosis_mail_ehlo_ok": "The SMTP mail server is reachable from the outside, which allows to receive email.", + "diagnosis_mail_ehlo_ok": "The SMTP mail server is reachable from the outside and therefore is able to receive emails!", "diagnosis_mail_ehlo_unreachable": "The SMTP mail server is unreachable from the outside on IPv{ipversion}. It won't be able to receive emails.", "diagnosis_mail_ehlo_unreachable_details": "Could not open a connection on port 25 to your server in IPv{ipversion}. It appears to be unreachable.
1. The most common cause for this issue is that port 25 is not correctly forwarded to your server.
2. You should also make sure that service postfix is running.
3. On more complex setups: make sure that no firewall or reverse-proxy is interfering.", "diagnosis_mail_ehlo_bad_answer": "A non-SMTP service answered on port 25 on IPv{ipversion}", "diagnosis_mail_ehlo_bad_answer_details": "It could be due to an other machine answering instead of your server.", - "diagnosis_mail_ehlo_wrong": "A different SMTP server answers on IPv{ipversion}. It will probably not be able to receive emails.", + "diagnosis_mail_ehlo_wrong": "A different SMTP mail server answers on IPv{ipversion}. It will probably not be able to receive emails.", "diagnosis_mail_ehlo_wrong_details": "The EHLO received by the remote diagnoser in IPv{ipversion} is different from your server's domain.
Received EHLO: {wrong_ehlo}
Expected: {right_ehlo}
The most common cause for this issue is that port 25 is not correctly forwarded to your server. Alternatively, make sure that no firewall or reverse-proxy is interfering.", "diagnosis_mail_ehlo_could_not_diagnose": "Could not diagnose if postfix mail server is reachable from outside in IPv{ipversion}.", "diagnosis_mail_ehlo_could_not_diagnose_details": "Error: {error}", diff --git a/locales/fr.json b/locales/fr.json index 3f7776009..c86ed244c 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -579,7 +579,7 @@ "migration_description_0013_futureproof_apps_catalog_system": "Migrer vers le nouveau système de catalogue d'applications à l'épreuve du temps", "app_upgrade_script_failed": "Une erreur s'est produite durant l’exécution du script de mise à niveau de l'application", "migration_description_0014_remove_app_status_json": "Supprimer les fichiers d'application status.json hérités", - "diagnosis_services_running": "Le service {service} s'exécute correctement !", + "diagnosis_services_running": "Le service {service} est en cours de fonctionnement !", "diagnosis_services_conf_broken": "La configuration est cassée pour le service {service} !", "diagnosis_ports_needed_by": "Rendre ce port accessible est nécessaire pour les fonctionnalités de type {category} (service {service})", "diagnosis_ports_forwarding_tip": "Pour résoudre ce problème, vous devez probablement configurer la redirection de port sur votre routeur Internet comme décrit sur https://yunohost.org/isp_box_config", From 8bd4ada50a455cf1264bf83968e64e914952fbe5 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 19 Apr 2020 06:19:08 +0200 Subject: [PATCH 1038/3170] Update changelog for 3.8.1 --- debian/changelog | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/debian/changelog b/debian/changelog index 83c310d67..eb925ab31 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,26 @@ +yunohost (3.8.1) testing; urgency=low + + ## Helpers (PHP, apt) + + - New helpers for extra apt repo, PHP version install, and PHP fpm (#881, #928, #929) + - Pave the way to migration to php7.3 and future ones (#880, #926) + - Option in PHP helper to use a dedicated php service (#915) + + ## Diagnosis + + - Many improvements in diagnosis mechanism (#923, #921, #940) + + ## Misc fixes, improvements + - custom_portal and custom_overlay redirect (#925) + - Improve systemd settings for slapd (#933) + - Spelling and typo corrections (#931) + - Improve translations for French, German, Catalan + + Thanks to all contributors <3 ! (Kay0u, Maniack Crudelis, ljf, E.Gaspar, + xaloc33) + + -- Alexandre Aubin Sun, 19 Apr 2020 06:20:00 +0000 + yunohost (3.8.0) testing; urgency=low # Major stuff @@ -50,7 +73,7 @@ yunohost (3.7.1.1) stable; urgency=low - [fix] lxc uid number is limited to 65536 by default (0c9a4509) - [fix] also invalidate group cache when creating users (aaabf8c7) - [fix] Make sure to have a path that include sbin for stupid cron jobs (f03bb82a) - + -- Alexandre Aubin Sun, 12 Apr 2020 23:15:00 +0000 yunohost (3.7.1) stable; urgency=low @@ -73,7 +96,7 @@ yunohost (3.7.1) stable; urgency=low yunohost (3.7.0.12) stable; urgency=low - Fix previous buggy hotfix about deleting existing primary groups ... - + -- Alexandre Aubin Sat, 28 Mar 2020 14:52:00 +0000 yunohost (3.7.0.11) stable; urgency=low @@ -85,7 +108,7 @@ yunohost (3.7.0.11) stable; urgency=low yunohost (3.7.0.10) stable; urgency=low - [fix] On some weird setup, this folder and content ain't readable by group ... gotta make sure to make rx for group other slapd will explode - + -- Alexandre Aubin Fri, 27 Mar 2020 21:45:00 +0000 yunohost (3.7.0.9) stable; urgency=low From f6837b17906fcc6f9dd0d5a9af22246ac31ed87e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 19 Apr 2020 07:03:21 +0200 Subject: [PATCH 1039/3170] Right side gotta be a folder path ... -_- --- debian/install | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/install b/debian/install index cf682d958..a814d1617 100644 --- a/debian/install +++ b/debian/install @@ -7,7 +7,7 @@ data/hooks/* /usr/share/yunohost/hooks/ data/other/yunoprompt.service /etc/systemd/system/ data/other/password/* /usr/share/yunohost/other/password/ data/other/dpkg-origins/yunohost /etc/dpkg/origins -data/other/dnsbl_list.yml /usr/share/yunohost/other/dnsbl_list.yml +data/other/dnsbl_list.yml /usr/share/yunohost/other/ data/other/* /usr/share/yunohost/yunohost-config/moulinette/ data/templates/* /usr/share/yunohost/templates/ data/helpers /usr/share/yunohost/ From 7cc04f51715ccfaea387a4ebb88ad58d2413b474 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 19 Apr 2020 07:04:32 +0200 Subject: [PATCH 1040/3170] Update changelog for 3.8.1.1 --- debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian/changelog b/debian/changelog index eb925ab31..fbeba2dcc 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +yunohost (3.8.1.1) testing; urgency=low + + - [fix] Stupid issue about path in debian/install ... + + -- Alexandre Aubin Sun, 19 Apr 2020 07:04:00 +0000 + yunohost (3.8.1) testing; urgency=low ## Helpers (PHP, apt) From 23664c5036e5eb59699cb1ceb06f50bec792762b Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sun, 19 Apr 2020 18:15:58 +0200 Subject: [PATCH 1041/3170] Wait for fail2ban to reload --- data/helpers.d/fail2ban | 2 +- data/helpers.d/systemd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/fail2ban b/data/helpers.d/fail2ban index 58af9ec0b..40f435ecd 100644 --- a/data/helpers.d/fail2ban +++ b/data/helpers.d/fail2ban @@ -130,7 +130,7 @@ EOF ynh_store_file_checksum "$finalfail2banjailconf" ynh_store_file_checksum "$finalfail2banfilterconf" - ynh_systemd_action --service_name=fail2ban --action=reload + ynh_systemd_action --service_name=fail2ban --action=reload --line_match="(Started|Reloaded) Fail2Ban Service" --log_path=systemd local fail2ban_error="$(journalctl -u fail2ban | tail -n50 | grep "WARNING.*$app.*")" if [[ -n "$fail2ban_error" ]]; then diff --git a/data/helpers.d/systemd b/data/helpers.d/systemd index 47e905f0f..4a9eac7e7 100644 --- a/data/helpers.d/systemd +++ b/data/helpers.d/systemd @@ -133,7 +133,7 @@ ynh_systemd_action() { for i in $(seq 1 $timeout) do # Read the log until the sentence is found, that means the app finished to start. Or run until the timeout - if grep --quiet "$line_match" "$templog" + if grep --extended-regexp --quiet "$line_match" "$templog" then ynh_print_info --message="The service $service_name has correctly executed the action ${action}." break From 1dd4a73e1720bbc1bc1e43443ee7804c0d908e71 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sun, 19 Apr 2020 19:10:39 +0200 Subject: [PATCH 1042/3170] Replace declare -Ar by local A for args_array --- data/helpers.d/apt | 16 ++++++++-------- data/helpers.d/backup | 10 +++++----- data/helpers.d/fail2ban | 2 +- data/helpers.d/getopts | 6 +++--- data/helpers.d/hardware | 4 ++-- data/helpers.d/logging | 12 ++++++------ data/helpers.d/logrotate | 2 +- data/helpers.d/mysql | 14 +++++++------- data/helpers.d/network | 10 +++++----- data/helpers.d/nodejs | 2 +- data/helpers.d/php | 6 +++--- data/helpers.d/postgresql | 16 ++++++++-------- data/helpers.d/setting | 22 +++++++++++----------- data/helpers.d/string | 10 +++++----- data/helpers.d/systemd | 6 +++--- data/helpers.d/user | 12 ++++++------ data/helpers.d/utils | 10 +++++----- 17 files changed, 80 insertions(+), 80 deletions(-) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index bcce02dcb..44d5c9c38 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -51,7 +51,7 @@ ynh_wait_dpkg_free() { ynh_package_is_installed() { # Declare an array to define the options of this helper. local legacy_args=p - declare -Ar args_array=( [p]=package= ) + local -A args_array=( [p]=package= ) local package # Manage arguments with getopts ynh_handle_getopts_args "$@" @@ -73,7 +73,7 @@ ynh_package_is_installed() { ynh_package_version() { # Declare an array to define the options of this helper. local legacy_args=p - declare -Ar args_array=( [p]=package= ) + local -A args_array=( [p]=package= ) local package # Manage arguments with getopts ynh_handle_getopts_args "$@" @@ -295,7 +295,7 @@ EOF ynh_add_app_dependencies () { # Declare an array to define the options of this helper. local legacy_args=pr - declare -Ar args_array=( [p]=package= [r]=replace) + local -A args_array=( [p]=package= [r]=replace) local package local replace # Manage arguments with getopts @@ -341,7 +341,7 @@ ynh_remove_app_dependencies () { ynh_install_extra_app_dependencies () { # Declare an array to define the options of this helper. local legacy_args=rpkn - declare -Ar args_array=( [r]=repo= [p]=package= [k]=key= [n]=name= ) + local -A args_array=( [r]=repo= [p]=package= [k]=key= [n]=name= ) local repo local package local key @@ -379,7 +379,7 @@ ynh_install_extra_app_dependencies () { ynh_install_extra_repo () { # Declare an array to define the options of this helper. local legacy_args=rkpna - declare -Ar args_array=( [r]=repo= [k]=key= [p]=priority= [n]=name= [a]=append ) + local -A args_array=( [r]=repo= [k]=key= [p]=priority= [n]=name= [a]=append ) local repo local key local priority @@ -448,7 +448,7 @@ ynh_install_extra_repo () { ynh_remove_extra_repo () { # Declare an array to define the options of this helper. local legacy_args=n - declare -Ar args_array=( [n]=name= ) + local -A args_array=( [n]=name= ) local name # Manage arguments with getopts ynh_handle_getopts_args "$@" @@ -481,7 +481,7 @@ ynh_remove_extra_repo () { ynh_add_repo () { # Declare an array to define the options of this helper. local legacy_args=uscna - declare -Ar args_array=( [u]=uri= [s]=suite= [c]=component= [n]=name= [a]=append ) + local -A args_array=( [u]=uri= [s]=suite= [c]=component= [n]=name= [a]=append ) local uri local suite local component @@ -521,7 +521,7 @@ ynh_add_repo () { ynh_pin_repo () { # Declare an array to define the options of this helper. local legacy_args=pirna - declare -Ar args_array=( [p]=package= [i]=pin= [r]=priority= [n]=name= [a]=append ) + local -A args_array=( [p]=package= [i]=pin= [r]=priority= [n]=name= [a]=append ) local package local pin local priority diff --git a/data/helpers.d/backup b/data/helpers.d/backup index 590e951a5..9ffb13bbb 100644 --- a/data/helpers.d/backup +++ b/data/helpers.d/backup @@ -46,7 +46,7 @@ ynh_backup() { # Declare an array to define the options of this helper. local legacy_args=sdbm - declare -Ar args_array=( [s]=src_path= [d]=dest_path= [b]=is_big [m]=not_mandatory ) + local -A args_array=( [s]=src_path= [d]=dest_path= [b]=is_big [m]=not_mandatory ) local src_path local dest_path local is_big @@ -220,7 +220,7 @@ with open(sys.argv[1], 'r') as backup_file: ynh_restore_file () { # Declare an array to define the options of this helper. local legacy_args=odm - declare -Ar args_array=( [o]=origin_path= [d]=dest_path= [m]=not_mandatory ) + local -A args_array=( [o]=origin_path= [d]=dest_path= [m]=not_mandatory ) local origin_path local archive_path local dest_path @@ -296,7 +296,7 @@ ynh_bind_or_cp() { ynh_store_file_checksum () { # Declare an array to define the options of this helper. local legacy_args=f - declare -Ar args_array=( [f]=file= ) + local -A args_array=( [f]=file= ) local file # Manage arguments with getopts ynh_handle_getopts_args "$@" @@ -328,7 +328,7 @@ ynh_store_file_checksum () { ynh_backup_if_checksum_is_different () { # Declare an array to define the options of this helper. local legacy_args=f - declare -Ar args_array=( [f]=file= ) + local -A args_array=( [f]=file= ) local file # Manage arguments with getopts ynh_handle_getopts_args "$@" @@ -361,7 +361,7 @@ ynh_backup_if_checksum_is_different () { ynh_delete_file_checksum () { # Declare an array to define the options of this helper. local legacy_args=f - declare -Ar args_array=( [f]=file= ) + local -A args_array=( [f]=file= ) local file # Manage arguments with getopts ynh_handle_getopts_args "$@" diff --git a/data/helpers.d/fail2ban b/data/helpers.d/fail2ban index 58af9ec0b..5c4cb89a9 100644 --- a/data/helpers.d/fail2ban +++ b/data/helpers.d/fail2ban @@ -65,7 +65,7 @@ ynh_add_fail2ban_config () { # Declare an array to define the options of this helper. local legacy_args=lrmptv - declare -Ar args_array=( [l]=logpath= [r]=failregex= [m]=max_retry= [p]=ports= [t]=use_template [v]=others_var=) + local -A args_array=( [l]=logpath= [r]=failregex= [m]=max_retry= [p]=ports= [t]=use_template [v]=others_var=) local logpath local failregex local max_retry diff --git a/data/helpers.d/getopts b/data/helpers.d/getopts index c8045fa25..285375915 100644 --- a/data/helpers.d/getopts +++ b/data/helpers.d/getopts @@ -6,7 +6,7 @@ # # example: function my_helper() # { -# declare -Ar args_array=( [a]=arg1= [b]=arg2= [c]=arg3 ) +# local -A args_array=( [a]=arg1= [b]=arg2= [c]=arg3 ) # local arg1 # local arg2 # local arg3 @@ -22,13 +22,13 @@ # This helper need an array, named "args_array" with all the arguments used by the helper # that want to use ynh_handle_getopts_args # Be carreful, this array has to be an associative array, as the following example: -# declare -Ar args_array=( [a]=arg1 [b]=arg2= [c]=arg3 ) +# local -A args_array=( [a]=arg1 [b]=arg2= [c]=arg3 ) # Let's explain this array: # a, b and c are short options, -a, -b and -c # arg1, arg2 and arg3 are the long options associated to the previous short ones. --arg1, --arg2 and --arg3 # For each option, a short and long version has to be defined. # Let's see something more significant -# declare -Ar args_array=( [u]=user [f]=finalpath= [d]=database ) +# local -A args_array=( [u]=user [f]=finalpath= [d]=database ) # # NB: Because we're using 'declare' without -g, the array will be declared as a local variable. # diff --git a/data/helpers.d/hardware b/data/helpers.d/hardware index 46e27caf4..1bfc648fe 100644 --- a/data/helpers.d/hardware +++ b/data/helpers.d/hardware @@ -10,7 +10,7 @@ ynh_get_ram () { # Declare an array to define the options of this helper. local legacy_args=ftso - declare -Ar args_array=( [f]=free [t]=total [s]=ignore_swap [o]=only_swap ) + local -A args_array=( [f]=free [t]=total [s]=ignore_swap [o]=only_swap ) local free local total local ignore_swap @@ -75,7 +75,7 @@ ynh_get_ram () { ynh_require_ram () { # Declare an array to define the options of this helper. local legacy_args=rftso - declare -Ar args_array=( [r]=required= [f]=free [t]=total [s]=ignore_swap [o]=only_swap ) + local -A args_array=( [r]=required= [f]=free [t]=total [s]=ignore_swap [o]=only_swap ) local required local free local total diff --git a/data/helpers.d/logging b/data/helpers.d/logging index 89fb89c6e..9f4a89df8 100644 --- a/data/helpers.d/logging +++ b/data/helpers.d/logging @@ -8,7 +8,7 @@ ynh_die() { # Declare an array to define the options of this helper. local legacy_args=mc - declare -Ar args_array=( [m]=message= [c]=ret_code= ) + local -A args_array=( [m]=message= [c]=ret_code= ) local message local ret_code # Manage arguments with getopts @@ -26,7 +26,7 @@ ynh_die() { ynh_print_info() { # Declare an array to define the options of this helper. local legacy_args=m - declare -Ar args_array=( [m]=message= ) + local -A args_array=( [m]=message= ) local message # Manage arguments with getopts ynh_handle_getopts_args "$@" @@ -71,7 +71,7 @@ ynh_print_log () { ynh_print_warn () { # Declare an array to define the options of this helper. local legacy_args=m - declare -Ar args_array=( [m]=message= ) + local -A args_array=( [m]=message= ) local message # Manage arguments with getopts ynh_handle_getopts_args "$@" @@ -88,7 +88,7 @@ ynh_print_warn () { ynh_print_err () { # Declare an array to define the options of this helper. local legacy_args=m - declare -Ar args_array=( [m]=message= ) + local -A args_array=( [m]=message= ) local message # Manage arguments with getopts ynh_handle_getopts_args "$@" @@ -224,7 +224,7 @@ ynh_script_progression () { set +x # Declare an array to define the options of this helper. local legacy_args=mwtl - declare -Ar args_array=( [m]=message= [w]=weight= [t]=time [l]=last ) + local -A args_array=( [m]=message= [w]=weight= [t]=time [l]=last ) local message local weight local time @@ -320,7 +320,7 @@ ynh_debug () { set +x # Declare an array to define the options of this helper. local legacy_args=mt - declare -Ar args_array=( [m]=message= [t]=trace= ) + local -A args_array=( [m]=message= [t]=trace= ) local message local trace # Manage arguments with getopts diff --git a/data/helpers.d/logrotate b/data/helpers.d/logrotate index 9e2429218..f77e25342 100644 --- a/data/helpers.d/logrotate +++ b/data/helpers.d/logrotate @@ -19,7 +19,7 @@ ynh_use_logrotate () { # Declare an array to define the options of this helper. local legacy_args=lnuya - declare -Ar args_array=( [l]=logfile= [n]=nonappend [u]=specific_user= [y]=non [a]=append ) + local -A args_array=( [l]=logfile= [n]=nonappend [u]=specific_user= [y]=non [a]=append ) # [y]=non [a]=append are only for legacy purpose, to not fail on the old option '--non-append' local logfile local nonappend diff --git a/data/helpers.d/mysql b/data/helpers.d/mysql index 91d4abcd2..658a79c17 100644 --- a/data/helpers.d/mysql +++ b/data/helpers.d/mysql @@ -16,7 +16,7 @@ MYSQL_ROOT_PWD_FILE=/etc/yunohost/mysql ynh_mysql_connect_as() { # Declare an array to define the options of this helper. local legacy_args=upd - declare -Ar args_array=( [u]=user= [p]=password= [d]=database= ) + local -A args_array=( [u]=user= [p]=password= [d]=database= ) local user local password local database @@ -37,7 +37,7 @@ ynh_mysql_connect_as() { ynh_mysql_execute_as_root() { # Declare an array to define the options of this helper. local legacy_args=sd - declare -Ar args_array=( [s]=sql= [d]=database= ) + local -A args_array=( [s]=sql= [d]=database= ) local sql local database # Manage arguments with getopts @@ -58,7 +58,7 @@ ynh_mysql_execute_as_root() { ynh_mysql_execute_file_as_root() { # Declare an array to define the options of this helper. local legacy_args=fd - declare -Ar args_array=( [f]=file= [d]=database= ) + local -A args_array=( [f]=file= [d]=database= ) local file local database # Manage arguments with getopts @@ -121,7 +121,7 @@ ynh_mysql_drop_db() { ynh_mysql_dump_db() { # Declare an array to define the options of this helper. local legacy_args=d - declare -Ar args_array=( [d]=database= ) + local -A args_array=( [d]=database= ) local database # Manage arguments with getopts ynh_handle_getopts_args "$@" @@ -153,7 +153,7 @@ ynh_mysql_user_exists() { # Declare an array to define the options of this helper. local legacy_args=u - declare -Ar args_array=( [u]=user= ) + local -A args_array=( [u]=user= ) local user # Manage arguments with getopts ynh_handle_getopts_args "$@" @@ -192,7 +192,7 @@ ynh_mysql_drop_user() { ynh_mysql_setup_db () { # Declare an array to define the options of this helper. local legacy_args=unp - declare -Ar args_array=( [u]=db_user= [n]=db_name= [p]=db_pwd= ) + local -A args_array=( [u]=db_user= [n]=db_name= [p]=db_pwd= ) local db_user local db_name db_pwd="" @@ -217,7 +217,7 @@ ynh_mysql_setup_db () { ynh_mysql_remove_db () { # Declare an array to define the options of this helper. local legacy_args=un - declare -Ar args_array=( [u]=db_user= [n]=db_name= ) + local -A args_array=( [u]=db_user= [n]=db_name= ) local db_user local db_name # Manage arguments with getopts diff --git a/data/helpers.d/network b/data/helpers.d/network index 330aa5383..0f6e9c442 100644 --- a/data/helpers.d/network +++ b/data/helpers.d/network @@ -11,7 +11,7 @@ ynh_find_port () { # Declare an array to define the options of this helper. local legacy_args=p - declare -Ar args_array=( [p]=port= ) + local -A args_array=( [p]=port= ) local port # Manage arguments with getopts ynh_handle_getopts_args "$@" @@ -35,7 +35,7 @@ ynh_find_port () { ynh_port_available () { # Declare an array to define the options of this helper. local legacy_args=p - declare -Ar args_array=( [p]=port= ) + local -A args_array=( [p]=port= ) local port # Manage arguments with getopts ynh_handle_getopts_args "$@" @@ -63,7 +63,7 @@ ynh_validate_ip() # Declare an array to define the options of this helper. local legacy_args=fi - declare -Ar args_array=( [f]=family= [i]=ip_address= ) + local -A args_array=( [f]=family= [i]=ip_address= ) local family local ip_address # Manage arguments with getopts @@ -95,7 +95,7 @@ ynh_validate_ip4() { # Declare an array to define the options of this helper. local legacy_args=i - declare -Ar args_array=( [i]=ip_address= ) + local -A args_array=( [i]=ip_address= ) local ip_address # Manage arguments with getopts ynh_handle_getopts_args "$@" @@ -116,7 +116,7 @@ ynh_validate_ip6() { # Declare an array to define the options of this helper. local legacy_args=i - declare -Ar args_array=( [i]=ip_address= ) + local -A args_array=( [i]=ip_address= ) local ip_address # Manage arguments with getopts ynh_handle_getopts_args "$@" diff --git a/data/helpers.d/nodejs b/data/helpers.d/nodejs index 288240b1b..03cb5dffb 100644 --- a/data/helpers.d/nodejs +++ b/data/helpers.d/nodejs @@ -68,7 +68,7 @@ ynh_install_nodejs () { # Declare an array to define the options of this helper. local legacy_args=n - declare -Ar args_array=( [n]=nodejs_version= ) + local -A args_array=( [n]=nodejs_version= ) local nodejs_version # Manage arguments with getopts ynh_handle_getopts_args "$@" diff --git a/data/helpers.d/php b/data/helpers.d/php index d5b17c58f..c1ae91c2e 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -59,7 +59,7 @@ YNH_PHP_VERSION=${YNH_PHP_VERSION:-$YNH_DEFAULT_PHP_VERSION} ynh_add_fpm_config () { # Declare an array to define the options of this helper. local legacy_args=vtufpd - declare -Ar args_array=( [v]=phpversion= [t]=use_template [u]=usage= [f]=footprint= [p]=package= [d]=dedicated_service ) + local -A args_array=( [v]=phpversion= [t]=use_template [u]=usage= [f]=footprint= [p]=package= [d]=dedicated_service ) local phpversion local use_template local usage @@ -312,7 +312,7 @@ ynh_remove_fpm_config () { ynh_install_php () { # Declare an array to define the options of this helper. local legacy_args=vp - declare -Ar args_array=( [v]=phpversion= [p]=package= ) + local -A args_array=( [v]=phpversion= [p]=package= ) local phpversion local package # Manage arguments with getopts @@ -415,7 +415,7 @@ ynh_remove_php () { ynh_get_scalable_phpfpm () { local legacy_args=ufp # Declare an array to define the options of this helper. - declare -Ar args_array=( [u]=usage= [f]=footprint= [p]=print ) + local -A args_array=( [u]=usage= [f]=footprint= [p]=print ) local usage local footprint local print diff --git a/data/helpers.d/postgresql b/data/helpers.d/postgresql index 03c713afd..ff6ef0f57 100644 --- a/data/helpers.d/postgresql +++ b/data/helpers.d/postgresql @@ -17,7 +17,7 @@ PSQL_ROOT_PWD_FILE=/etc/yunohost/psql ynh_psql_connect_as() { # Declare an array to define the options of this helper. local legacy_args=upd - declare -Ar args_array=([u]=user= [p]=password= [d]=database=) + local -A args_array=([u]=user= [p]=password= [d]=database=) local user local password local database @@ -38,7 +38,7 @@ ynh_psql_connect_as() { ynh_psql_execute_as_root() { # Declare an array to define the options of this helper. local legacy_args=sd - declare -Ar args_array=([s]=sql= [d]=database=) + local -A args_array=([s]=sql= [d]=database=) local sql local database # Manage arguments with getopts @@ -59,7 +59,7 @@ ynh_psql_execute_as_root() { ynh_psql_execute_file_as_root() { # Declare an array to define the options of this helper. local legacy_args=fd - declare -Ar args_array=([f]=file= [d]=database=) + local -A args_array=([f]=file= [d]=database=) local file local database # Manage arguments with getopts @@ -125,7 +125,7 @@ ynh_psql_drop_db() { ynh_psql_dump_db() { # Declare an array to define the options of this helper. local legacy_args=d - declare -Ar args_array=([d]=database=) + local -A args_array=([d]=database=) local database # Manage arguments with getopts ynh_handle_getopts_args "$@" @@ -155,7 +155,7 @@ ynh_psql_create_user() { ynh_psql_user_exists() { # Declare an array to define the options of this helper. local legacy_args=u - declare -Ar args_array=([u]=user=) + local -A args_array=([u]=user=) local user # Manage arguments with getopts ynh_handle_getopts_args "$@" @@ -174,7 +174,7 @@ ynh_psql_user_exists() { ynh_psql_database_exists() { # Declare an array to define the options of this helper. local legacy_args=d - declare -Ar args_array=([d]=database=) + local -A args_array=([d]=database=) local database # Manage arguments with getopts ynh_handle_getopts_args "$@" @@ -210,7 +210,7 @@ ynh_psql_drop_user() { ynh_psql_setup_db() { # Declare an array to define the options of this helper. local legacy_args=unp - declare -Ar args_array=([u]=db_user= [n]=db_name= [p]=db_pwd=) + local -A args_array=([u]=db_user= [n]=db_name= [p]=db_pwd=) local db_user local db_name db_pwd="" @@ -237,7 +237,7 @@ ynh_psql_setup_db() { ynh_psql_remove_db() { # Declare an array to define the options of this helper. local legacy_args=un - declare -Ar args_array=([u]=db_user= [n]=db_name=) + local -A args_array=([u]=db_user= [n]=db_name=) local db_user local db_name # Manage arguments with getopts diff --git a/data/helpers.d/setting b/data/helpers.d/setting index 350ed3ea0..5cc5d19dd 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -10,7 +10,7 @@ ynh_app_setting_get() { # Declare an array to define the options of this helper. local legacy_args=ak - declare -Ar args_array=( [a]=app= [k]=key= ) + local -A args_array=( [a]=app= [k]=key= ) local app local key # Manage arguments with getopts @@ -30,7 +30,7 @@ ynh_app_setting_get() { ynh_app_setting_set() { # Declare an array to define the options of this helper. local legacy_args=akv - declare -Ar args_array=( [a]=app= [k]=key= [v]=value= ) + local -A args_array=( [a]=app= [k]=key= [v]=value= ) local app local key local value @@ -50,7 +50,7 @@ ynh_app_setting_set() { ynh_app_setting_delete() { # Declare an array to define the options of this helper. local legacy_args=ak - declare -Ar args_array=( [a]=app= [k]=key= ) + local -A args_array=( [a]=app= [k]=key= ) local app local key # Manage arguments with getopts @@ -124,7 +124,7 @@ EOF ynh_webpath_available () { # Declare an array to define the options of this helper. local legacy_args=dp - declare -Ar args_array=( [d]=domain= [p]=path_url= ) + local -A args_array=( [d]=domain= [p]=path_url= ) local domain local path_url # Manage arguments with getopts @@ -146,7 +146,7 @@ ynh_webpath_available () { ynh_webpath_register () { # Declare an array to define the options of this helper. local legacy_args=adp - declare -Ar args_array=( [a]=app= [d]=domain= [p]=path_url= ) + local -A args_array=( [a]=app= [d]=domain= [p]=path_url= ) local app local domain local path_url @@ -180,7 +180,7 @@ ynh_webpath_register () { ynh_permission_create() { # Declare an array to define the options of this helper. local legacy_args=pua - declare -Ar args_array=( [p]=permission= [u]=url= [a]=allowed= ) + local -A args_array=( [p]=permission= [u]=url= [a]=allowed= ) local permission local url local allowed @@ -210,7 +210,7 @@ ynh_permission_create() { ynh_permission_delete() { # Declare an array to define the options of this helper. local legacy_args=p - declare -Ar args_array=( [p]=permission= ) + local -A args_array=( [p]=permission= ) local permission ynh_handle_getopts_args "$@" @@ -226,7 +226,7 @@ ynh_permission_delete() { ynh_permission_exists() { # Declare an array to define the options of this helper. local legacy_args=p - declare -Ar args_array=( [p]=permission= ) + local -A args_array=( [p]=permission= ) local permission ynh_handle_getopts_args "$@" @@ -243,7 +243,7 @@ ynh_permission_exists() { ynh_permission_url() { # Declare an array to define the options of this helper. local legacy_args=pu - declare -Ar args_array=([p]=permission= [u]=url=) + local -A args_array=([p]=permission= [u]=url=) local permission local url ynh_handle_getopts_args "$@" @@ -270,7 +270,7 @@ ynh_permission_url() { ynh_permission_update() { # Declare an array to define the options of this helper. local legacy_args=par - declare -Ar args_array=( [p]=permission= [a]=add= [r]=remove= ) + local -A args_array=( [p]=permission= [a]=add= [r]=remove= ) local permission local add local remove @@ -298,7 +298,7 @@ ynh_permission_update() { ynh_permission_has_user() { local legacy_args=pu # Declare an array to define the options of this helper. - declare -Ar args_array=( [p]=permission= [u]=user= ) + local -A args_array=( [p]=permission= [u]=user= ) local permission local user # Manage arguments with getopts diff --git a/data/helpers.d/string b/data/helpers.d/string index e50f781fe..9b8437953 100644 --- a/data/helpers.d/string +++ b/data/helpers.d/string @@ -11,7 +11,7 @@ ynh_string_random() { # Declare an array to define the options of this helper. local legacy_args=l - declare -Ar args_array=( [l]=length= ) + local -A args_array=( [l]=length= ) local length # Manage arguments with getopts ynh_handle_getopts_args "$@" @@ -37,7 +37,7 @@ ynh_string_random() { ynh_replace_string () { # Declare an array to define the options of this helper. local legacy_args=mrf - declare -Ar args_array=( [m]=match_string= [r]=replace_string= [f]=target_file= ) + local -A args_array=( [m]=match_string= [r]=replace_string= [f]=target_file= ) local match_string local replace_string local target_file @@ -66,7 +66,7 @@ ynh_replace_string () { ynh_replace_special_string () { # Declare an array to define the options of this helper. local legacy_args=mrf - declare -Ar args_array=( [m]=match_string= [r]=replace_string= [f]=target_file= ) + local -A args_array=( [m]=match_string= [r]=replace_string= [f]=target_file= ) local match_string local replace_string local target_file @@ -97,7 +97,7 @@ ynh_replace_special_string () { ynh_sanitize_dbid () { # Declare an array to define the options of this helper. local legacy_args=n - declare -Ar args_array=( [n]=db_name= ) + local -A args_array=( [n]=db_name= ) local db_name # Manage arguments with getopts ynh_handle_getopts_args "$@" @@ -125,7 +125,7 @@ ynh_sanitize_dbid () { ynh_normalize_url_path () { # Declare an array to define the options of this helper. local legacy_args=p - declare -Ar args_array=( [p]=path_url= ) + local -A args_array=( [p]=path_url= ) local path_url # Manage arguments with getopts ynh_handle_getopts_args "$@" diff --git a/data/helpers.d/systemd b/data/helpers.d/systemd index 47e905f0f..276674e70 100644 --- a/data/helpers.d/systemd +++ b/data/helpers.d/systemd @@ -18,7 +18,7 @@ ynh_add_systemd_config () { # Declare an array to define the options of this helper. local legacy_args=st - declare -Ar args_array=( [s]=service= [t]=template= ) + local -A args_array=( [s]=service= [t]=template= ) local service local template # Manage arguments with getopts @@ -54,7 +54,7 @@ ynh_add_systemd_config () { ynh_remove_systemd_config () { # Declare an array to define the options of this helper. local legacy_args=s - declare -Ar args_array=( [s]=service= ) + local -A args_array=( [s]=service= ) local service # Manage arguments with getopts ynh_handle_getopts_args "$@" @@ -81,7 +81,7 @@ ynh_remove_systemd_config () { ynh_systemd_action() { # Declare an array to define the options of this helper. local legacy_args=nalpte - declare -Ar args_array=( [n]=service_name= [a]=action= [l]=line_match= [p]=log_path= [t]=timeout= [e]=length= ) + local -A args_array=( [n]=service_name= [a]=action= [l]=line_match= [p]=log_path= [t]=timeout= [e]=length= ) local service_name local action local line_match diff --git a/data/helpers.d/user b/data/helpers.d/user index 7051ed4c0..72cb9bece 100644 --- a/data/helpers.d/user +++ b/data/helpers.d/user @@ -11,7 +11,7 @@ ynh_user_exists() { # Declare an array to define the options of this helper. local legacy_args=u - declare -Ar args_array=( [u]=username= ) + local -A args_array=( [u]=username= ) local username # Manage arguments with getopts ynh_handle_getopts_args "$@" @@ -32,7 +32,7 @@ ynh_user_exists() { ynh_user_get_info() { # Declare an array to define the options of this helper. local legacy_args=uk - declare -Ar args_array=( [u]=username= [k]=key= ) + local -A args_array=( [u]=username= [k]=key= ) local username local key # Manage arguments with getopts @@ -63,7 +63,7 @@ ynh_user_list() { ynh_system_user_exists() { # Declare an array to define the options of this helper. local legacy_args=u - declare -Ar args_array=( [u]=username= ) + local -A args_array=( [u]=username= ) local username # Manage arguments with getopts ynh_handle_getopts_args "$@" @@ -78,7 +78,7 @@ ynh_system_user_exists() { ynh_system_group_exists() { # Declare an array to define the options of this helper. local legacy_args=g - declare -Ar args_array=( [g]=group= ) + local -A args_array=( [g]=group= ) local group # Manage arguments with getopts ynh_handle_getopts_args "$@" @@ -103,7 +103,7 @@ ynh_system_group_exists() { ynh_system_user_create () { # Declare an array to define the options of this helper. local legacy_args=uhs - declare -Ar args_array=( [u]=username= [h]=home_dir= [s]=use_shell ) + local -A args_array=( [u]=username= [h]=home_dir= [s]=use_shell ) local username local home_dir local use_shell @@ -137,7 +137,7 @@ ynh_system_user_create () { ynh_system_user_delete () { # Declare an array to define the options of this helper. local legacy_args=u - declare -Ar args_array=( [u]=username= ) + local -A args_array=( [u]=username= ) local username # Manage arguments with getopts ynh_handle_getopts_args "$@" diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 133a47247..5f352ab96 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -99,7 +99,7 @@ ynh_abort_if_errors () { ynh_setup_source () { # Declare an array to define the options of this helper. local legacy_args=ds - declare -Ar args_array=( [d]=dest_dir= [s]=source_id= ) + local -A args_array=( [d]=dest_dir= [s]=source_id= ) local dest_dir local source_id # Manage arguments with getopts @@ -304,7 +304,7 @@ properly with chmod/chown." ynh_secure_remove () { # Declare an array to define the options of this helper. local legacy_args=f - declare -Ar args_array=( [f]=file= ) + local -A args_array=( [f]=file= ) local file # Manage arguments with getopts ynh_handle_getopts_args "$@" @@ -378,7 +378,7 @@ ynh_get_plain_key() { ynh_read_manifest () { # Declare an array to define the options of this helper. local legacy_args=mk - declare -Ar args_array=( [m]=manifest= [k]=manifest_key= ) + local -A args_array=( [m]=manifest= [k]=manifest_key= ) local manifest local manifest_key # Manage arguments with getopts @@ -406,7 +406,7 @@ ynh_read_manifest () { ynh_app_upstream_version () { # Declare an array to define the options of this helper. local legacy_args=m - declare -Ar args_array=( [m]=manifest= ) + local -A args_array=( [m]=manifest= ) local manifest # Manage arguments with getopts ynh_handle_getopts_args "$@" @@ -430,7 +430,7 @@ ynh_app_upstream_version () { ynh_app_package_version () { # Declare an array to define the options of this helper. local legacy_args=m - declare -Ar args_array=( [m]=manifest= ) + local -A args_array=( [m]=manifest= ) local manifest # Manage arguments with getopts ynh_handle_getopts_args "$@" From 80964a13121e5b2a5f802ede2bf411c356a66244 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sun, 19 Apr 2020 20:03:55 +0200 Subject: [PATCH 1043/3170] Standardize tabulations --- data/helpers.d/apt | 306 +++++++++++------------ data/helpers.d/backup | 18 +- data/helpers.d/fail2ban | 98 ++++---- data/helpers.d/getopts | 316 ++++++++++++------------ data/helpers.d/hardware | 98 ++++---- data/helpers.d/logging | 294 +++++++++++----------- data/helpers.d/logrotate | 142 +++++------ data/helpers.d/mysql | 85 ++++--- data/helpers.d/network | 82 +++--- data/helpers.d/nginx | 86 +++---- data/helpers.d/nodejs | 190 +++++++------- data/helpers.d/php | 508 +++++++++++++++++++------------------- data/helpers.d/postgresql | 226 ++++++++--------- data/helpers.d/setting | 54 ++-- data/helpers.d/string | 104 ++++---- data/helpers.d/systemd | 78 +++--- data/helpers.d/user | 58 ++--- data/helpers.d/utils | 116 ++++----- 18 files changed, 1429 insertions(+), 1430 deletions(-) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index 44d5c9c38..4093e593f 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -114,7 +114,7 @@ ynh_package_update() { # Requires YunoHost version 2.2.4 or higher. ynh_package_install() { ynh_apt --no-remove -o Dpkg::Options::=--force-confdef \ - -o Dpkg::Options::=--force-confold install $@ + -o Dpkg::Options::=--force-confold install $@ } # Remove package(s) @@ -194,7 +194,7 @@ ynh_package_install_from_equivs () { { # If the installation failed # 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')" + 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 -n '/--fix-broken/,$p' >&2 @@ -222,7 +222,7 @@ ynh_install_app_dependencies () { local dependencies=${dependencies//|/ | } local manifest_path="../manifest.json" if [ ! -e "$manifest_path" ]; then - manifest_path="../settings/manifest.json" # Into the restore script, the manifest is not at the same place + manifest_path="../settings/manifest.json" # Into the restore script, the manifest is not at the same place fi local version=$(grep '\"version\": ' "$manifest_path" | cut -d '"' -f 4) # Retrieve the version number in the manifest file. @@ -293,28 +293,28 @@ EOF # | arg: -p, --package - Packages to add as dependencies for the app. # | arg: -r, --replace - Replace dependencies instead of adding to existing ones. ynh_add_app_dependencies () { - # Declare an array to define the options of this helper. - local legacy_args=pr - local -A args_array=( [p]=package= [r]=replace) - local package - local replace - # Manage arguments with getopts - ynh_handle_getopts_args "$@" - replace=${replace:-0} + # Declare an array to define the options of this helper. + local legacy_args=pr + local -A args_array=( [p]=package= [r]=replace) + local package + local replace + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + replace=${replace:-0} - local current_dependencies="" - if [ $replace -eq 0 ] - then - local dep_app=${app//_/-} # Replace all '_' by '-' - if ynh_package_is_installed --package="${dep_app}-ynh-deps" - then - current_dependencies="$(dpkg-query --show --showformat='${Depends}' ${dep_app}-ynh-deps) " - fi + local current_dependencies="" + if [ $replace -eq 0 ] + then + local dep_app=${app//_/-} # Replace all '_' by '-' + if ynh_package_is_installed --package="${dep_app}-ynh-deps" + then + current_dependencies="$(dpkg-query --show --showformat='${Depends}' ${dep_app}-ynh-deps) " + fi - current_dependencies=${current_dependencies// | /|} - fi + current_dependencies=${current_dependencies// | /|} + fi - ynh_install_app_dependencies "${current_dependencies}${package}" + ynh_install_app_dependencies "${current_dependencies}${package}" } # Remove fake package and its dependencies @@ -339,31 +339,31 @@ ynh_remove_app_dependencies () { # | arg: -k, --key - url to get the public key. # | arg: -n, --name - Name for the files for this repo, $app as default value. ynh_install_extra_app_dependencies () { - # Declare an array to define the options of this helper. - local legacy_args=rpkn - local -A args_array=( [r]=repo= [p]=package= [k]=key= [n]=name= ) - local repo - local package - local key - local name - # Manage arguments with getopts - ynh_handle_getopts_args "$@" - name="${name:-$app}" - key=${key:-} + # Declare an array to define the options of this helper. + local legacy_args=rpkn + local -A args_array=( [r]=repo= [p]=package= [k]=key= [n]=name= ) + local repo + local package + local key + local name + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + name="${name:-$app}" + key=${key:-} - # Set a key only if asked - if [ -n "$key" ] - then - key="--key=$key" - fi - # Add an extra repository for those packages - ynh_install_extra_repo --repo="$repo" $key --priority=995 --name=$name + # Set a key only if asked + if [ -n "$key" ] + then + key="--key=$key" + fi + # Add an extra repository for those packages + ynh_install_extra_repo --repo="$repo" $key --priority=995 --name=$name - # Install requested dependencies from this extra repository. - ynh_add_app_dependencies --package="$package" + # Install requested dependencies from this extra repository. + ynh_add_app_dependencies --package="$package" - # Remove this extra repository after packages are installed - ynh_remove_extra_repo --name=$app + # Remove this extra repository after packages are installed + ynh_remove_extra_repo --name=$app } # Add an extra repository correctly, pin it and get the key. @@ -377,66 +377,66 @@ ynh_install_extra_app_dependencies () { # | arg: -n, --name - Name for the files for this repo, $app as default value. # | arg: -a, --append - Do not overwrite existing files. ynh_install_extra_repo () { - # Declare an array to define the options of this helper. - local legacy_args=rkpna - local -A args_array=( [r]=repo= [k]=key= [p]=priority= [n]=name= [a]=append ) - local repo - local key - local priority - local name - local append - # Manage arguments with getopts - ynh_handle_getopts_args "$@" - name="${name:-$app}" - append=${append:-0} - key=${key:-} - priority=${priority:-} + # Declare an array to define the options of this helper. + local legacy_args=rkpna + local -A args_array=( [r]=repo= [k]=key= [p]=priority= [n]=name= [a]=append ) + local repo + local key + local priority + local name + local append + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + name="${name:-$app}" + append=${append:-0} + key=${key:-} + priority=${priority:-} - if [ $append -eq 1 ] - then - append="--append" - wget_append="tee -a" - else - append="" - wget_append="tee" - fi + if [ $append -eq 1 ] + then + append="--append" + wget_append="tee -a" + else + append="" + wget_append="tee" + fi - # Split the repository into uri, suite and components. - # Remove "deb " at the beginning of the repo. - repo="${repo#deb }" + # Split the repository into uri, suite and components. + # Remove "deb " at the beginning of the repo. + repo="${repo#deb }" - # Get the uri - local uri="$(echo "$repo" | awk '{ print $1 }')" + # Get the uri + local uri="$(echo "$repo" | awk '{ print $1 }')" - # Get the suite - local suite="$(echo "$repo" | awk '{ print $2 }')" + # Get the suite + local suite="$(echo "$repo" | awk '{ print $2 }')" - # Get the components - local component="${repo##$uri $suite }" + # Get the components + local component="${repo##$uri $suite }" - # Add the repository into sources.list.d - ynh_add_repo --uri="$uri" --suite="$suite" --component="$component" --name="$name" $append + # Add the repository into sources.list.d + ynh_add_repo --uri="$uri" --suite="$suite" --component="$component" --name="$name" $append - # Pin the new repo with the default priority, so it won't be used for upgrades. - # Build $pin from the uri without http and any sub path - local pin="${uri#*://}" - pin="${pin%%/*}" - # Set a priority only if asked - if [ -n "$priority" ] - then - priority="--priority=$priority" - fi - ynh_pin_repo --package="*" --pin="origin \"$pin\"" $priority --name="$name" $append + # Pin the new repo with the default priority, so it won't be used for upgrades. + # Build $pin from the uri without http and any sub path + local pin="${uri#*://}" + pin="${pin%%/*}" + # Set a priority only if asked + if [ -n "$priority" ] + then + priority="--priority=$priority" + fi + ynh_pin_repo --package="*" --pin="origin \"$pin\"" $priority --name="$name" $append - # Get the public key for the repo - if [ -n "$key" ] - then - mkdir -p "/etc/apt/trusted.gpg.d" - wget -q "$key" -O - | gpg --dearmor | $wget_append /etc/apt/trusted.gpg.d/$name.gpg > /dev/null - fi + # Get the public key for the repo + if [ -n "$key" ] + then + mkdir -p "/etc/apt/trusted.gpg.d" + wget -q "$key" -O - | gpg --dearmor | $wget_append /etc/apt/trusted.gpg.d/$name.gpg > /dev/null + fi - # Update the list of package with the new repo - ynh_package_update + # Update the list of package with the new repo + ynh_package_update } # Remove an extra repository and the assiociated configuration. @@ -446,21 +446,21 @@ ynh_install_extra_repo () { # usage: ynh_remove_extra_repo [--name=name] # | arg: -n, --name - Name for the files for this repo, $app as default value. ynh_remove_extra_repo () { - # Declare an array to define the options of this helper. - local legacy_args=n - local -A args_array=( [n]=name= ) - local name - # Manage arguments with getopts - ynh_handle_getopts_args "$@" - name="${name:-$app}" + # Declare an array to define the options of this helper. + local legacy_args=n + local -A args_array=( [n]=name= ) + local name + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + name="${name:-$app}" - ynh_secure_remove "/etc/apt/sources.list.d/$name.list" - ynh_secure_remove "/etc/apt/preferences.d/$name" - ynh_secure_remove "/etc/apt/trusted.gpg.d/$name.gpg" - ynh_secure_remove "/etc/apt/trusted.gpg.d/$name.asc" + ynh_secure_remove "/etc/apt/sources.list.d/$name.list" + ynh_secure_remove "/etc/apt/preferences.d/$name" + ynh_secure_remove "/etc/apt/trusted.gpg.d/$name.gpg" + ynh_secure_remove "/etc/apt/trusted.gpg.d/$name.asc" - # Update the list of package to exclude the old repo - ynh_package_update + # Update the list of package to exclude the old repo + ynh_package_update } # Add a repository. @@ -479,30 +479,30 @@ ynh_remove_extra_repo () { # ynh_add_repo --uri=http://forge.yunohost.org/debian/ --suite=stretch --component=stable # ynh_add_repo () { - # Declare an array to define the options of this helper. - local legacy_args=uscna - local -A args_array=( [u]=uri= [s]=suite= [c]=component= [n]=name= [a]=append ) - local uri - local suite - local component - local name - local append - # Manage arguments with getopts - ynh_handle_getopts_args "$@" - name="${name:-$app}" - append=${append:-0} + # Declare an array to define the options of this helper. + local legacy_args=uscna + local -A args_array=( [u]=uri= [s]=suite= [c]=component= [n]=name= [a]=append ) + local uri + local suite + local component + local name + local append + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + name="${name:-$app}" + append=${append:-0} - if [ $append -eq 1 ] - then - append="tee -a" - else - append="tee" - fi + if [ $append -eq 1 ] + then + append="tee -a" + else + append="tee" + fi - mkdir -p "/etc/apt/sources.list.d" - # Add the new repo in sources.list.d - echo "deb $uri $suite $component" \ - | $append "/etc/apt/sources.list.d/$name.list" + mkdir -p "/etc/apt/sources.list.d" + # Add the new repo in sources.list.d + echo "deb $uri $suite $component" \ + | $append "/etc/apt/sources.list.d/$name.list" } # Pin a repository. @@ -519,32 +519,32 @@ ynh_add_repo () { # See https://manpages.debian.org/stretch/apt/apt_preferences.5.en.html#How_APT_Interprets_Priorities for information about pinning. # ynh_pin_repo () { - # Declare an array to define the options of this helper. - local legacy_args=pirna - local -A args_array=( [p]=package= [i]=pin= [r]=priority= [n]=name= [a]=append ) - local package - local pin - local priority - local name - local append - # Manage arguments with getopts - ynh_handle_getopts_args "$@" - package="${package:-*}" - priority=${priority:-50} - name="${name:-$app}" - append=${append:-0} + # Declare an array to define the options of this helper. + local legacy_args=pirna + local -A args_array=( [p]=package= [i]=pin= [r]=priority= [n]=name= [a]=append ) + local package + local pin + local priority + local name + local append + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + package="${package:-*}" + priority=${priority:-50} + name="${name:-$app}" + append=${append:-0} - if [ $append -eq 1 ] - then - append="tee -a" - else - append="tee" - fi + if [ $append -eq 1 ] + then + append="tee -a" + else + append="tee" + fi - mkdir -p "/etc/apt/preferences.d" - echo "Package: $package + mkdir -p "/etc/apt/preferences.d" + echo "Package: $package Pin: $pin Pin-Priority: $priority " \ - | $append "/etc/apt/preferences.d/$name" + | $append "/etc/apt/preferences.d/$name" } diff --git a/data/helpers.d/backup b/data/helpers.d/backup index 9ffb13bbb..bb676a0e0 100644 --- a/data/helpers.d/backup +++ b/data/helpers.d/backup @@ -80,16 +80,16 @@ ynh_backup() { ynh_print_warn --message="Source path '${src_path}' does not exist" if [ "$not_mandatory" == "0" ] then - # This is a temporary fix for fail2ban config files missing after the migration to stretch. - if echo "${src_path}" | grep --quiet "/etc/fail2ban" - then - touch "${src_path}" - ynh_print_info --message="The missing file will be replaced by a dummy one for the backup !!!" - else - return 1 - fi + # This is a temporary fix for fail2ban config files missing after the migration to stretch. + if echo "${src_path}" | grep --quiet "/etc/fail2ban" + then + touch "${src_path}" + ynh_print_info --message="The missing file will be replaced by a dummy one for the backup !!!" + else + return 1 + fi else - return 0 + return 0 fi } diff --git a/data/helpers.d/fail2ban b/data/helpers.d/fail2ban index 5c4cb89a9..2c17e1300 100644 --- a/data/helpers.d/fail2ban +++ b/data/helpers.d/fail2ban @@ -63,47 +63,47 @@ # # Requires YunoHost version 3.5.0 or higher. ynh_add_fail2ban_config () { - # Declare an array to define the options of this helper. - local legacy_args=lrmptv - local -A args_array=( [l]=logpath= [r]=failregex= [m]=max_retry= [p]=ports= [t]=use_template [v]=others_var=) - local logpath - local failregex - local max_retry - local ports - local others_var - local use_template - # Manage arguments with getopts - ynh_handle_getopts_args "$@" - use_template="${use_template:-0}" - max_retry=${max_retry:-3} - ports=${ports:-http,https} + # Declare an array to define the options of this helper. + local legacy_args=lrmptv + local -A args_array=( [l]=logpath= [r]=failregex= [m]=max_retry= [p]=ports= [t]=use_template [v]=others_var=) + local logpath + local failregex + local max_retry + local ports + local others_var + local use_template + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + use_template="${use_template:-0}" + max_retry=${max_retry:-3} + ports=${ports:-http,https} - finalfail2banjailconf="/etc/fail2ban/jail.d/$app.conf" - finalfail2banfilterconf="/etc/fail2ban/filter.d/$app.conf" - ynh_backup_if_checksum_is_different "$finalfail2banjailconf" - ynh_backup_if_checksum_is_different "$finalfail2banfilterconf" + finalfail2banjailconf="/etc/fail2ban/jail.d/$app.conf" + finalfail2banfilterconf="/etc/fail2ban/filter.d/$app.conf" + ynh_backup_if_checksum_is_different "$finalfail2banjailconf" + ynh_backup_if_checksum_is_different "$finalfail2banfilterconf" - if [ $use_template -eq 1 ] - then - # Usage 2, templates - cp ../conf/f2b_jail.conf $finalfail2banjailconf - cp ../conf/f2b_filter.conf $finalfail2banfilterconf - - if [ -n "${app:-}" ] + if [ $use_template -eq 1 ] then - ynh_replace_string "__APP__" "$app" "$finalfail2banjailconf" - ynh_replace_string "__APP__" "$app" "$finalfail2banfilterconf" - fi + # Usage 2, templates + cp ../conf/f2b_jail.conf $finalfail2banjailconf + cp ../conf/f2b_filter.conf $finalfail2banfilterconf - # Replace all other variable given as arguments - for var_to_replace in ${others_var:-}; do - # ${var_to_replace^^} make the content of the variable on upper-cases - # ${!var_to_replace} get the content of the variable named $var_to_replace - ynh_replace_string --match_string="__${var_to_replace^^}__" --replace_string="${!var_to_replace}" --target_file="$finalfail2banjailconf" - ynh_replace_string --match_string="__${var_to_replace^^}__" --replace_string="${!var_to_replace}" --target_file="$finalfail2banfilterconf" - done + if [ -n "${app:-}" ] + then + ynh_replace_string "__APP__" "$app" "$finalfail2banjailconf" + ynh_replace_string "__APP__" "$app" "$finalfail2banfilterconf" + fi - else + # Replace all other variable given as arguments + for var_to_replace in ${others_var:-}; do + # ${var_to_replace^^} make the content of the variable on upper-cases + # ${!var_to_replace} get the content of the variable named $var_to_replace + ynh_replace_string --match_string="__${var_to_replace^^}__" --replace_string="${!var_to_replace}" --target_file="$finalfail2banjailconf" + ynh_replace_string --match_string="__${var_to_replace^^}__" --replace_string="${!var_to_replace}" --target_file="$finalfail2banfilterconf" + done + + else # Usage 1, no template. Build a config file from scratch. test -n "$logpath" || ynh_die "ynh_add_fail2ban_config expects a logfile path as first argument and received nothing." test -n "$failregex" || ynh_die "ynh_add_fail2ban_config expects a failure regex as second argument and received nothing." @@ -124,19 +124,19 @@ before = common.conf failregex = $failregex ignoreregex = EOF - fi + fi - # Common to usage 1 and 2. - ynh_store_file_checksum "$finalfail2banjailconf" - ynh_store_file_checksum "$finalfail2banfilterconf" + # Common to usage 1 and 2. + ynh_store_file_checksum "$finalfail2banjailconf" + ynh_store_file_checksum "$finalfail2banfilterconf" - ynh_systemd_action --service_name=fail2ban --action=reload + ynh_systemd_action --service_name=fail2ban --action=reload - local fail2ban_error="$(journalctl -u fail2ban | tail -n50 | grep "WARNING.*$app.*")" - if [[ -n "$fail2ban_error" ]]; then - ynh_print_err --message="Fail2ban failed to load the jail for $app" - ynh_print_warn --message="${fail2ban_error#*WARNING}" - fi + local fail2ban_error="$(journalctl -u fail2ban | tail -n50 | grep "WARNING.*$app.*")" + if [[ -n "$fail2ban_error" ]]; then + ynh_print_err --message="Fail2ban failed to load the jail for $app" + ynh_print_warn --message="${fail2ban_error#*WARNING}" + fi } # Remove the dedicated fail2ban config (jail and filter conf files) @@ -145,7 +145,7 @@ EOF # # Requires YunoHost version 3.5.0 or higher. ynh_remove_fail2ban_config () { - ynh_secure_remove "/etc/fail2ban/jail.d/$app.conf" - ynh_secure_remove "/etc/fail2ban/filter.d/$app.conf" - ynh_systemd_action --service_name=fail2ban --action=reload + ynh_secure_remove "/etc/fail2ban/jail.d/$app.conf" + ynh_secure_remove "/etc/fail2ban/filter.d/$app.conf" + ynh_systemd_action --service_name=fail2ban --action=reload } diff --git a/data/helpers.d/getopts b/data/helpers.d/getopts index 285375915..3bdfc80eb 100644 --- a/data/helpers.d/getopts +++ b/data/helpers.d/getopts @@ -46,173 +46,173 @@ # # Requires YunoHost version 3.2.2 or higher. ynh_handle_getopts_args () { - # Manage arguments only if there's some provided - set +x - if [ $# -ne 0 ] - then - # Store arguments in an array to keep each argument separated - local arguments=("$@") + # Manage arguments only if there's some provided + set +x + if [ $# -ne 0 ] + then + # Store arguments in an array to keep each argument separated + local arguments=("$@") - # For each option in the array, reduce to short options for getopts (e.g. for [u]=user, --user will be -u) - # And built parameters string for getopts - # ${!args_array[@]} is the list of all option_flags in the array (An option_flag is 'u' in [u]=user, user is a value) - local getopts_parameters="" - local option_flag="" - for option_flag in "${!args_array[@]}" - do - # Concatenate each option_flags of the array to build the string of arguments for getopts - # Will looks like 'abcd' for -a -b -c -d - # If the value of an option_flag finish by =, it's an option with additionnal values. (e.g. --user bob or -u bob) - # Check the last character of the value associate to the option_flag - if [ "${args_array[$option_flag]: -1}" = "=" ] - then - # For an option with additionnal values, add a ':' after the letter for getopts. - getopts_parameters="${getopts_parameters}${option_flag}:" - else - getopts_parameters="${getopts_parameters}${option_flag}" - fi - # Check each argument given to the function - local arg="" - # ${#arguments[@]} is the size of the array - for arg in `seq 0 $(( ${#arguments[@]} - 1 ))` - do - # Escape options' values starting with -. Otherwise the - will be considered as another option. - arguments[arg]="${arguments[arg]//--${args_array[$option_flag]}-/--${args_array[$option_flag]}\\TOBEREMOVED\\-}" - # And replace long option (value of the option_flag) by the short option, the option_flag itself - # (e.g. for [u]=user, --user will be -u) - # Replace long option with = - arguments[arg]="${arguments[arg]//--${args_array[$option_flag]}/-${option_flag} }" - # And long option without = - arguments[arg]="${arguments[arg]//--${args_array[$option_flag]%=}/-${option_flag}}" - done - done + # For each option in the array, reduce to short options for getopts (e.g. for [u]=user, --user will be -u) + # And built parameters string for getopts + # ${!args_array[@]} is the list of all option_flags in the array (An option_flag is 'u' in [u]=user, user is a value) + local getopts_parameters="" + local option_flag="" + for option_flag in "${!args_array[@]}" + do + # Concatenate each option_flags of the array to build the string of arguments for getopts + # Will looks like 'abcd' for -a -b -c -d + # If the value of an option_flag finish by =, it's an option with additionnal values. (e.g. --user bob or -u bob) + # Check the last character of the value associate to the option_flag + if [ "${args_array[$option_flag]: -1}" = "=" ] + then + # For an option with additionnal values, add a ':' after the letter for getopts. + getopts_parameters="${getopts_parameters}${option_flag}:" + else + getopts_parameters="${getopts_parameters}${option_flag}" + fi + # Check each argument given to the function + local arg="" + # ${#arguments[@]} is the size of the array + for arg in `seq 0 $(( ${#arguments[@]} - 1 ))` + do + # Escape options' values starting with -. Otherwise the - will be considered as another option. + arguments[arg]="${arguments[arg]//--${args_array[$option_flag]}-/--${args_array[$option_flag]}\\TOBEREMOVED\\-}" + # And replace long option (value of the option_flag) by the short option, the option_flag itself + # (e.g. for [u]=user, --user will be -u) + # Replace long option with = + arguments[arg]="${arguments[arg]//--${args_array[$option_flag]}/-${option_flag} }" + # And long option without = + arguments[arg]="${arguments[arg]//--${args_array[$option_flag]%=}/-${option_flag}}" + done + done - # Read and parse all the arguments - # Use a function here, to use standart arguments $@ and be able to use shift. - parse_arg () { - # Read all arguments, until no arguments are left - while [ $# -ne 0 ] - do - # Initialize the index of getopts - OPTIND=1 - # Parse with getopts only if the argument begin by -, that means the argument is an option - # getopts will fill $parameter with the letter of the option it has read. - local parameter="" - getopts ":$getopts_parameters" parameter || true + # Read and parse all the arguments + # Use a function here, to use standart arguments $@ and be able to use shift. + parse_arg () { + # Read all arguments, until no arguments are left + while [ $# -ne 0 ] + do + # Initialize the index of getopts + OPTIND=1 + # Parse with getopts only if the argument begin by -, that means the argument is an option + # getopts will fill $parameter with the letter of the option it has read. + local parameter="" + getopts ":$getopts_parameters" parameter || true - if [ "$parameter" = "?" ] - then - ynh_die --message="Invalid argument: -${OPTARG:-}" - elif [ "$parameter" = ":" ] - then - ynh_die --message="-$OPTARG parameter requires an argument." - else - local shift_value=1 - # Use the long option, corresponding to the short option read by getopts, as a variable - # (e.g. for [u]=user, 'user' will be used as a variable) - # Also, remove '=' at the end of the long option - # The variable name will be stored in 'option_var' - local option_var="${args_array[$parameter]%=}" - # If this option doesn't take values - # if there's a '=' at the end of the long option name, this option takes values - if [ "${args_array[$parameter]: -1}" != "=" ] - then - # 'eval ${option_var}' will use the content of 'option_var' - eval ${option_var}=1 - else - # Read all other arguments to find multiple value for this option. - # Load args in a array - local all_args=("$@") + if [ "$parameter" = "?" ] + then + ynh_die --message="Invalid argument: -${OPTARG:-}" + elif [ "$parameter" = ":" ] + then + ynh_die --message="-$OPTARG parameter requires an argument." + else + local shift_value=1 + # Use the long option, corresponding to the short option read by getopts, as a variable + # (e.g. for [u]=user, 'user' will be used as a variable) + # Also, remove '=' at the end of the long option + # The variable name will be stored in 'option_var' + local option_var="${args_array[$parameter]%=}" + # If this option doesn't take values + # if there's a '=' at the end of the long option name, this option takes values + if [ "${args_array[$parameter]: -1}" != "=" ] + then + # 'eval ${option_var}' will use the content of 'option_var' + eval ${option_var}=1 + else + # Read all other arguments to find multiple value for this option. + # Load args in a array + local all_args=("$@") - # If the first argument is longer than 2 characters, - # There's a value attached to the option, in the same array cell - if [ ${#all_args[0]} -gt 2 ]; then - # Remove the option and the space, so keep only the value itself. - all_args[0]="${all_args[0]#-${parameter} }" - # Reduce the value of shift, because the option has been removed manually - shift_value=$(( shift_value - 1 )) - fi + # If the first argument is longer than 2 characters, + # There's a value attached to the option, in the same array cell + if [ ${#all_args[0]} -gt 2 ]; then + # Remove the option and the space, so keep only the value itself. + all_args[0]="${all_args[0]#-${parameter} }" + # Reduce the value of shift, because the option has been removed manually + shift_value=$(( shift_value - 1 )) + fi - # Declare the content of option_var as a variable. - eval ${option_var}="" - # Then read the array value per value - local i - for i in `seq 0 $(( ${#all_args[@]} - 1 ))` - do - # If this argument is an option, end here. - if [ "${all_args[$i]:0:1}" == "-" ] - then - # Ignore the first value of the array, which is the option itself - if [ "$i" -ne 0 ]; then - break - fi - else - # Else, add this value to this option - # Each value will be separated by ';' - if [ -n "${!option_var}" ] - then - # If there's already another value for this option, add a ; before adding the new value - eval ${option_var}+="\;" - fi + # Declare the content of option_var as a variable. + eval ${option_var}="" + # Then read the array value per value + local i + for i in `seq 0 $(( ${#all_args[@]} - 1 ))` + do + # If this argument is an option, end here. + if [ "${all_args[$i]:0:1}" == "-" ] + then + # Ignore the first value of the array, which is the option itself + if [ "$i" -ne 0 ]; then + break + fi + else + # Else, add this value to this option + # Each value will be separated by ';' + if [ -n "${!option_var}" ] + then + # If there's already another value for this option, add a ; before adding the new value + eval ${option_var}+="\;" + fi - # Remove the \ that escape - at beginning of values. - all_args[i]="${all_args[i]//\\TOBEREMOVED\\/}" + # Remove the \ that escape - at beginning of values. + all_args[i]="${all_args[i]//\\TOBEREMOVED\\/}" - # For the record. - # We're using eval here to get the content of the variable stored itself as simple text in $option_var... - # Other ways to get that content would be to use either ${!option_var} or declare -g ${option_var} - # But... ${!option_var} can't be used as left part of an assignation. - # declare -g ${option_var} will create a local variable (despite -g !) and will not be available for the helper itself. - # So... Stop fucking arguing each time that eval is evil... Go find an other working solution if you can find one! + # For the record. + # We're using eval here to get the content of the variable stored itself as simple text in $option_var... + # Other ways to get that content would be to use either ${!option_var} or declare -g ${option_var} + # But... ${!option_var} can't be used as left part of an assignation. + # declare -g ${option_var} will create a local variable (despite -g !) and will not be available for the helper itself. + # So... Stop fucking arguing each time that eval is evil... Go find an other working solution if you can find one! - eval ${option_var}+='"${all_args[$i]}"' - shift_value=$(( shift_value + 1 )) - fi - done - fi - fi + eval ${option_var}+='"${all_args[$i]}"' + shift_value=$(( shift_value + 1 )) + fi + done + fi + fi - # Shift the parameter and its argument(s) - shift $shift_value - done - } + # Shift the parameter and its argument(s) + shift $shift_value + done + } - # LEGACY MODE - # Check if there's getopts arguments - if [ "${arguments[0]:0:1}" != "-" ] - then - # If not, enter in legacy mode and manage the arguments as positionnal ones.. - # Dot not echo, to prevent to go through a helper output. But print only in the log. - set -x; echo "! Helper used in legacy mode !" > /dev/null; set +x - local i - for i in `seq 0 $(( ${#arguments[@]} -1 ))` - do - # Try to use legacy_args as a list of option_flag of the array args_array - # Otherwise, fallback to getopts_parameters to get the option_flag. But an associative arrays isn't always sorted in the correct order... - # Remove all ':' in getopts_parameters - getopts_parameters=${legacy_args:-${getopts_parameters//:}} - # Get the option_flag from getopts_parameters, by using the option_flag according to the position of the argument. - option_flag=${getopts_parameters:$i:1} - if [ -z "$option_flag" ]; then - ynh_print_warn --message="Too many arguments ! \"${arguments[$i]}\" will be ignored." - continue - fi - # Use the long option, corresponding to the option_flag, as a variable - # (e.g. for [u]=user, 'user' will be used as a variable) - # Also, remove '=' at the end of the long option - # The variable name will be stored in 'option_var' - local option_var="${args_array[$option_flag]%=}" + # LEGACY MODE + # Check if there's getopts arguments + if [ "${arguments[0]:0:1}" != "-" ] + then + # If not, enter in legacy mode and manage the arguments as positionnal ones.. + # Dot not echo, to prevent to go through a helper output. But print only in the log. + set -x; echo "! Helper used in legacy mode !" > /dev/null; set +x + local i + for i in `seq 0 $(( ${#arguments[@]} -1 ))` + do + # Try to use legacy_args as a list of option_flag of the array args_array + # Otherwise, fallback to getopts_parameters to get the option_flag. But an associative arrays isn't always sorted in the correct order... + # Remove all ':' in getopts_parameters + getopts_parameters=${legacy_args:-${getopts_parameters//:}} + # Get the option_flag from getopts_parameters, by using the option_flag according to the position of the argument. + option_flag=${getopts_parameters:$i:1} + if [ -z "$option_flag" ]; then + ynh_print_warn --message="Too many arguments ! \"${arguments[$i]}\" will be ignored." + continue + fi + # Use the long option, corresponding to the option_flag, as a variable + # (e.g. for [u]=user, 'user' will be used as a variable) + # Also, remove '=' at the end of the long option + # The variable name will be stored in 'option_var' + local option_var="${args_array[$option_flag]%=}" - # Store each value given as argument in the corresponding variable - # The values will be stored in the same order than $args_array - eval ${option_var}+='"${arguments[$i]}"' - done - unset legacy_args - else - # END LEGACY MODE - # Call parse_arg and pass the modified list of args as an array of arguments. - parse_arg "${arguments[@]}" - fi - fi - set -x + # Store each value given as argument in the corresponding variable + # The values will be stored in the same order than $args_array + eval ${option_var}+='"${arguments[$i]}"' + done + unset legacy_args + else + # END LEGACY MODE + # Call parse_arg and pass the modified list of args as an array of arguments. + parse_arg "${arguments[@]}" + fi + fi + set -x } diff --git a/data/helpers.d/hardware b/data/helpers.d/hardware index 1bfc648fe..d7e14ccc5 100644 --- a/data/helpers.d/hardware +++ b/data/helpers.d/hardware @@ -8,58 +8,58 @@ # | arg: -s, --ignore_swap - Ignore swap, consider only real RAM # | arg: -o, --only_swap - Ignore real RAM, consider only swap ynh_get_ram () { - # Declare an array to define the options of this helper. - local legacy_args=ftso - local -A args_array=( [f]=free [t]=total [s]=ignore_swap [o]=only_swap ) - local free - local total - local ignore_swap - local only_swap - # Manage arguments with getopts - ynh_handle_getopts_args "$@" - ignore_swap=${ignore_swap:-0} - only_swap=${only_swap:-0} - free=${free:-0} - total=${total:-0} + # Declare an array to define the options of this helper. + local legacy_args=ftso + local -A args_array=( [f]=free [t]=total [s]=ignore_swap [o]=only_swap ) + local free + local total + local ignore_swap + local only_swap + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + ignore_swap=${ignore_swap:-0} + only_swap=${only_swap:-0} + free=${free:-0} + total=${total:-0} - local total_ram=$(vmstat --stats --unit M | grep "total memory" | awk '{print $1}') - local total_swap=$(vmstat --stats --unit M | grep "total swap" | awk '{print $1}') - local total_ram_swap=$(( total_ram + total_swap )) + local total_ram=$(vmstat --stats --unit M | grep "total memory" | awk '{print $1}') + local total_swap=$(vmstat --stats --unit M | grep "total swap" | awk '{print $1}') + local total_ram_swap=$(( total_ram + total_swap )) - local free_ram=$(vmstat --stats --unit M | grep "free memory" | awk '{print $1}') - local free_swap=$(vmstat --stats --unit M | grep "free swap" | awk '{print $1}') - local free_ram_swap=$(( free_ram + free_swap )) + local free_ram=$(vmstat --stats --unit M | grep "free memory" | awk '{print $1}') + local free_swap=$(vmstat --stats --unit M | grep "free swap" | awk '{print $1}') + local free_ram_swap=$(( free_ram + free_swap )) - # Use the total amount of ram - if [ $free -eq 1 ] - then - # Use the total amount of free ram - local ram=$free_ram_swap - if [ $ignore_swap -eq 1 ] - then - # Use only the amount of free ram - ram=$free_ram - elif [ $only_swap -eq 1 ] - then - # Use only the amount of free swap - ram=$free_swap - fi - elif [ $total -eq 1 ] - then - local ram=$total_ram_swap - if [ $ignore_swap -eq 1 ] - then - # Use only the amount of free ram - ram=$total_ram - elif [ $only_swap -eq 1 ] - then - # Use only the amount of free swap - ram=$total_swap - fi - else - ynh_print_warn --message="You have to choose --free or --total when using ynh_get_ram" - ram=0 - fi + # Use the total amount of ram + if [ $free -eq 1 ] + then + # Use the total amount of free ram + local ram=$free_ram_swap + if [ $ignore_swap -eq 1 ] + then + # Use only the amount of free ram + ram=$free_ram + elif [ $only_swap -eq 1 ] + then + # Use only the amount of free swap + ram=$free_swap + fi + elif [ $total -eq 1 ] + then + local ram=$total_ram_swap + if [ $ignore_swap -eq 1 ] + then + # Use only the amount of free ram + ram=$total_ram + elif [ $only_swap -eq 1 ] + then + # Use only the amount of free swap + ram=$total_swap + fi + else + ynh_print_warn --message="You have to choose --free or --total when using ynh_get_ram" + ram=0 + fi echo $ram } diff --git a/data/helpers.d/logging b/data/helpers.d/logging index 9f4a89df8..0cd25fb57 100644 --- a/data/helpers.d/logging +++ b/data/helpers.d/logging @@ -6,16 +6,16 @@ # # Requires YunoHost version 2.4.0 or higher. ynh_die() { - # Declare an array to define the options of this helper. - local legacy_args=mc - local -A args_array=( [m]=message= [c]=ret_code= ) - local message - local ret_code - # Manage arguments with getopts - ynh_handle_getopts_args "$@" + # Declare an array to define the options of this helper. + local legacy_args=mc + local -A args_array=( [m]=message= [c]=ret_code= ) + local message + local ret_code + # Manage arguments with getopts + ynh_handle_getopts_args "$@" - echo "$message" 1>&2 - exit "${ret_code:-1}" + echo "$message" 1>&2 + exit "${ret_code:-1}" } # Display a message in the 'INFO' logging category @@ -45,12 +45,12 @@ ynh_print_info() { # # Requires YunoHost version 2.6.4 or higher. ynh_no_log() { - local ynh_cli_log=/var/log/yunohost/yunohost-cli.log - cp -a ${ynh_cli_log} ${ynh_cli_log}-move - eval $@ - local exit_code=$? - mv ${ynh_cli_log}-move ${ynh_cli_log} - return $? + local ynh_cli_log=/var/log/yunohost/yunohost-cli.log + cp -a ${ynh_cli_log} ${ynh_cli_log}-move + eval $@ + local exit_code=$? + mv ${ynh_cli_log}-move ${ynh_cli_log} + return $? } # Main printer, just in case in the future we have to change anything about that. @@ -59,7 +59,7 @@ ynh_no_log() { # # Requires YunoHost version 3.2.0 or higher. ynh_print_log () { - echo -e "${1}" + echo -e "${1}" } # Print a warning on stderr @@ -69,14 +69,14 @@ ynh_print_log () { # # Requires YunoHost version 3.2.0 or higher. ynh_print_warn () { - # Declare an array to define the options of this helper. - local legacy_args=m - local -A args_array=( [m]=message= ) - local message - # Manage arguments with getopts - ynh_handle_getopts_args "$@" + # Declare an array to define the options of this helper. + local legacy_args=m + local -A args_array=( [m]=message= ) + local message + # Manage arguments with getopts + ynh_handle_getopts_args "$@" - ynh_print_log "\e[93m\e[1m[WARN]\e[0m ${message}" >&2 + ynh_print_log "\e[93m\e[1m[WARN]\e[0m ${message}" >&2 } # Print an error on stderr @@ -86,14 +86,14 @@ ynh_print_warn () { # # Requires YunoHost version 3.2.0 or higher. ynh_print_err () { - # Declare an array to define the options of this helper. - local legacy_args=m - local -A args_array=( [m]=message= ) - local message - # Manage arguments with getopts - ynh_handle_getopts_args "$@" + # Declare an array to define the options of this helper. + local legacy_args=m + local -A args_array=( [m]=message= ) + local message + # Manage arguments with getopts + ynh_handle_getopts_args "$@" - ynh_print_log "\e[91m\e[1m[ERR]\e[0m ${message}" >&2 + ynh_print_log "\e[91m\e[1m[ERR]\e[0m ${message}" >&2 } # Execute a command and print the result as an error @@ -109,7 +109,7 @@ ynh_print_err () { # # Requires YunoHost version 3.2.0 or higher. ynh_exec_err () { - ynh_print_err "$(eval $@)" + ynh_print_err "$(eval $@)" } # Execute a command and print the result as a warning @@ -125,7 +125,7 @@ ynh_exec_err () { # # Requires YunoHost version 3.2.0 or higher. ynh_exec_warn () { - ynh_print_warn "$(eval $@)" + ynh_print_warn "$(eval $@)" } # Execute a command and force the result to be printed on stdout @@ -141,7 +141,7 @@ ynh_exec_warn () { # # Requires YunoHost version 3.2.0 or higher. ynh_exec_warn_less () { - eval $@ 2>&1 + eval $@ 2>&1 } # Execute a command and redirect stdout in /dev/null @@ -157,7 +157,7 @@ ynh_exec_warn_less () { # # Requires YunoHost version 3.2.0 or higher. ynh_exec_quiet () { - eval $@ > /dev/null + eval $@ > /dev/null } # Execute a command and redirect stdout and stderr in /dev/null @@ -173,7 +173,7 @@ ynh_exec_quiet () { # # Requires YunoHost version 3.2.0 or higher. ynh_exec_fully_quiet () { - eval $@ > /dev/null 2>&1 + eval $@ > /dev/null 2>&1 } # Remove any logs for all the following commands. @@ -184,7 +184,7 @@ ynh_exec_fully_quiet () { # # Requires YunoHost version 3.2.0 or higher. ynh_print_OFF () { - exec {BASH_XTRACEFD}>/dev/null + exec {BASH_XTRACEFD}>/dev/null } # Restore the logging after ynh_print_OFF @@ -193,9 +193,9 @@ ynh_print_OFF () { # # Requires YunoHost version 3.2.0 or higher. ynh_print_ON () { - exec {BASH_XTRACEFD}>&1 - # Print an echo only for the log, to be able to know that ynh_print_ON has been called. - echo ynh_print_ON > /dev/null + exec {BASH_XTRACEFD}>&1 + # Print an echo only for the log, to be able to know that ynh_print_ON has been called. + echo ynh_print_ON > /dev/null } # Initial definitions for ynh_script_progression @@ -221,81 +221,81 @@ base_time=$(date +%s) # # Requires YunoHost version 3.5.0 or higher. ynh_script_progression () { - set +x - # Declare an array to define the options of this helper. - local legacy_args=mwtl - local -A args_array=( [m]=message= [w]=weight= [t]=time [l]=last ) - local message - local weight - local time - local last - # Manage arguments with getopts - ynh_handle_getopts_args "$@" - set +x - weight=${weight:-1} - time=${time:-0} - last=${last:-0} + set +x + # Declare an array to define the options of this helper. + local legacy_args=mwtl + local -A args_array=( [m]=message= [w]=weight= [t]=time [l]=last ) + local message + local weight + local time + local last + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + set +x + weight=${weight:-1} + time=${time:-0} + last=${last:-0} - # Get execution time since the last $base_time - local exec_time=$(( $(date +%s) - $base_time )) - base_time=$(date +%s) + # Get execution time since the last $base_time + local exec_time=$(( $(date +%s) - $base_time )) + base_time=$(date +%s) - # Compute $max_progression (if we didn't already) - if [ "$max_progression" = -1 ] - then - # Get the number of occurrences of 'ynh_script_progression' in the script. Except those are commented. - local helper_calls="$(grep --count "^[^#]*ynh_script_progression" $0)" - # Get the number of call with a weight value - local weight_calls=$(grep --perl-regexp --count "^[^#]*ynh_script_progression.*(--weight|-w )" $0) + # Compute $max_progression (if we didn't already) + if [ "$max_progression" = -1 ] + then + # Get the number of occurrences of 'ynh_script_progression' in the script. Except those are commented. + local helper_calls="$(grep --count "^[^#]*ynh_script_progression" $0)" + # Get the number of call with a weight value + local weight_calls=$(grep --perl-regexp --count "^[^#]*ynh_script_progression.*(--weight|-w )" $0) - # Get the weight of each occurrences of 'ynh_script_progression' in the script using --weight - local weight_valuesA="$(grep --perl-regexp "^[^#]*ynh_script_progression.*--weight" $0 | sed 's/.*--weight[= ]\([[:digit:]]*\).*/\1/g')" - # Get the weight of each occurrences of 'ynh_script_progression' in the script using -w - local weight_valuesB="$(grep --perl-regexp "^[^#]*ynh_script_progression.*-w " $0 | sed 's/.*-w[= ]\([[:digit:]]*\).*/\1/g')" - # Each value will be on a different line. - # Remove each 'end of line' and replace it by a '+' to sum the values. - local weight_values=$(( $(echo "$weight_valuesA" | tr '\n' '+') + $(echo "$weight_valuesB" | tr '\n' '+') 0 )) + # Get the weight of each occurrences of 'ynh_script_progression' in the script using --weight + local weight_valuesA="$(grep --perl-regexp "^[^#]*ynh_script_progression.*--weight" $0 | sed 's/.*--weight[= ]\([[:digit:]]*\).*/\1/g')" + # Get the weight of each occurrences of 'ynh_script_progression' in the script using -w + local weight_valuesB="$(grep --perl-regexp "^[^#]*ynh_script_progression.*-w " $0 | sed 's/.*-w[= ]\([[:digit:]]*\).*/\1/g')" + # Each value will be on a different line. + # Remove each 'end of line' and replace it by a '+' to sum the values. + local weight_values=$(( $(echo "$weight_valuesA" | tr '\n' '+') + $(echo "$weight_valuesB" | tr '\n' '+') 0 )) - # max_progression is a total number of calls to this helper. - # Less the number of calls with a weight value. - # Plus the total of weight values - max_progression=$(( $helper_calls - $weight_calls + $weight_values )) - fi + # max_progression is a total number of calls to this helper. + # Less the number of calls with a weight value. + # Plus the total of weight values + max_progression=$(( $helper_calls - $weight_calls + $weight_values )) + fi - # Increment each execution of ynh_script_progression in this script by the weight of the previous call. - increment_progression=$(( $increment_progression + $previous_weight )) - # Store the weight of the current call in $previous_weight for next call - previous_weight=$weight + # Increment each execution of ynh_script_progression in this script by the weight of the previous call. + increment_progression=$(( $increment_progression + $previous_weight )) + # Store the weight of the current call in $previous_weight for next call + previous_weight=$weight - # Reduce $increment_progression to the size of the scale - if [ $last -eq 0 ] - then - local effective_progression=$(( $increment_progression * $progress_scale / $max_progression )) - # If last is specified, fill immediately the progression_bar - else - local effective_progression=$progress_scale - fi + # Reduce $increment_progression to the size of the scale + if [ $last -eq 0 ] + then + local effective_progression=$(( $increment_progression * $progress_scale / $max_progression )) + # If last is specified, fill immediately the progression_bar + else + local effective_progression=$progress_scale + fi - # Build $progression_bar from progress_string(0,1,2) according to $effective_progression and the weight of the current task - # expected_progression is the progression expected after the current task - local expected_progression="$(( ( $increment_progression + $weight ) * $progress_scale / $max_progression - $effective_progression ))" - if [ $last -eq 1 ] - then - expected_progression=0 - fi - # left_progression is the progression not yet done - local left_progression="$(( $progress_scale - $effective_progression - $expected_progression ))" - # Build the progression bar with $effective_progression, work done, $expected_progression, current work and $left_progression, work to be done. - local progression_bar="${progress_string2:0:$effective_progression}${progress_string1:0:$expected_progression}${progress_string0:0:$left_progression}" + # Build $progression_bar from progress_string(0,1,2) according to $effective_progression and the weight of the current task + # expected_progression is the progression expected after the current task + local expected_progression="$(( ( $increment_progression + $weight ) * $progress_scale / $max_progression - $effective_progression ))" + if [ $last -eq 1 ] + then + expected_progression=0 + fi + # left_progression is the progression not yet done + local left_progression="$(( $progress_scale - $effective_progression - $expected_progression ))" + # Build the progression bar with $effective_progression, work done, $expected_progression, current work and $left_progression, work to be done. + local progression_bar="${progress_string2:0:$effective_progression}${progress_string1:0:$expected_progression}${progress_string0:0:$left_progression}" - local print_exec_time="" - if [ $time -eq 1 ] - then - print_exec_time=" [$(date +%Hh%Mm,%Ss --date="0 + $exec_time sec")]" - fi + local print_exec_time="" + if [ $time -eq 1 ] + then + print_exec_time=" [$(date +%Hh%Mm,%Ss --date="0 + $exec_time sec")]" + fi - ynh_print_info "[$progression_bar] > ${message}${print_exec_time}" - set -x + ynh_print_info "[$progression_bar] > ${message}${print_exec_time}" + set -x } # Return data to the Yunohost core for later processing @@ -316,49 +316,49 @@ ynh_return () { # # Requires YunoHost version 3.5.0 or higher. ynh_debug () { - # Disable set xtrace for the helper itself, to not pollute the debug log - set +x - # Declare an array to define the options of this helper. - local legacy_args=mt - local -A args_array=( [m]=message= [t]=trace= ) - local message - local trace - # Manage arguments with getopts - ynh_handle_getopts_args "$@" - # Redisable xtrace, ynh_handle_getopts_args set it back - set +x - message=${message:-} - trace=${trace:-} + # Disable set xtrace for the helper itself, to not pollute the debug log + set +x + # Declare an array to define the options of this helper. + local legacy_args=mt + local -A args_array=( [m]=message= [t]=trace= ) + local message + local trace + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + # Redisable xtrace, ynh_handle_getopts_args set it back + set +x + message=${message:-} + trace=${trace:-} - if [ -n "$message" ] - then - ynh_print_log "\e[34m\e[1m[DEBUG]\e[0m ${message}" >&2 - fi + if [ -n "$message" ] + then + ynh_print_log "\e[34m\e[1m[DEBUG]\e[0m ${message}" >&2 + fi - if [ "$trace" == "1" ] - then - ynh_debug --message="Enable debugging" - set +x - # Get the current file descriptor of xtrace - old_bash_xtracefd=$BASH_XTRACEFD - # Add the current file name and the line number of any command currently running while tracing. - PS4='$(basename ${BASH_SOURCE[0]})-L${LINENO}: ' - # Force xtrace to stderr - BASH_XTRACEFD=2 - # Force stdout to stderr - exec 1>&2 - fi - if [ "$trace" == "0" ] - then - ynh_debug --message="Disable debugging" - set +x - # Put xtrace back to its original fild descriptor - BASH_XTRACEFD=$old_bash_xtracefd - # Restore stdout - exec 1>&1 - fi - # Renable set xtrace - set -x + if [ "$trace" == "1" ] + then + ynh_debug --message="Enable debugging" + set +x + # Get the current file descriptor of xtrace + old_bash_xtracefd=$BASH_XTRACEFD + # Add the current file name and the line number of any command currently running while tracing. + PS4='$(basename ${BASH_SOURCE[0]})-L${LINENO}: ' + # Force xtrace to stderr + BASH_XTRACEFD=2 + # Force stdout to stderr + exec 1>&2 + fi + if [ "$trace" == "0" ] + then + ynh_debug --message="Disable debugging" + set +x + # Put xtrace back to its original fild descriptor + BASH_XTRACEFD=$old_bash_xtracefd + # Restore stdout + exec 1>&1 + fi + # Renable set xtrace + set -x } # Execute a command and print the result as debug @@ -374,5 +374,5 @@ ynh_debug () { # # Requires YunoHost version 3.5.0 or higher. ynh_debug_exec () { - ynh_debug --message="$(eval $@)" + ynh_debug --message="$(eval $@)" } diff --git a/data/helpers.d/logrotate b/data/helpers.d/logrotate index f77e25342..b0a64f553 100644 --- a/data/helpers.d/logrotate +++ b/data/helpers.d/logrotate @@ -17,81 +17,81 @@ # # Requires YunoHost version 2.6.4 or higher. ynh_use_logrotate () { - # Declare an array to define the options of this helper. - local legacy_args=lnuya - local -A args_array=( [l]=logfile= [n]=nonappend [u]=specific_user= [y]=non [a]=append ) - # [y]=non [a]=append are only for legacy purpose, to not fail on the old option '--non-append' - local logfile - local nonappend - local specific_user - # Manage arguments with getopts - ynh_handle_getopts_args "$@" - local logfile="${logfile:-}" - local nonappend="${nonappend:-0}" - local specific_user="${specific_user:-}" + # Declare an array to define the options of this helper. + local legacy_args=lnuya + local -A args_array=( [l]=logfile= [n]=nonappend [u]=specific_user= [y]=non [a]=append ) + # [y]=non [a]=append are only for legacy purpose, to not fail on the old option '--non-append' + local logfile + local nonappend + local specific_user + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + local logfile="${logfile:-}" + local nonappend="${nonappend:-0}" + local specific_user="${specific_user:-}" - # LEGACY CODE - PRE GETOPTS - if [ $# -gt 0 ] && [ "$1" == "--non-append" ]; then - nonappend=1 - # Destroy this argument for the next command. - shift - elif [ $# -gt 1 ] && [ "$2" == "--non-append" ]; then - nonappend=1 - fi + # LEGACY CODE - PRE GETOPTS + if [ $# -gt 0 ] && [ "$1" == "--non-append" ]; then + nonappend=1 + # Destroy this argument for the next command. + shift + elif [ $# -gt 1 ] && [ "$2" == "--non-append" ]; then + nonappend=1 + fi - if [ $# -gt 0 ] && [ "$(echo ${1:0:1})" != "-" ]; then - # If the given logfile parameter already exists as a file, or if it ends up with ".log", - # we just want to manage a single file - if [ -f "$1" ] || [ "$(echo ${1##*.})" == "log" ]; then - local logfile=$1 - # Otherwise we assume we want to manage a directory and all its .log file inside - else - local logfile=$1/*.log - fi - fi - # LEGACY CODE + if [ $# -gt 0 ] && [ "$(echo ${1:0:1})" != "-" ]; then + # If the given logfile parameter already exists as a file, or if it ends up with ".log", + # we just want to manage a single file + if [ -f "$1" ] || [ "$(echo ${1##*.})" == "log" ]; then + local logfile=$1 + # Otherwise we assume we want to manage a directory and all its .log file inside + else + local logfile=$1/*.log + fi + fi + # LEGACY CODE - local customtee="tee -a" - if [ "$nonappend" -eq 1 ]; then - customtee="tee" - fi - if [ -n "$logfile" ] - then - if [ ! -f "$1" ] && [ "$(echo ${logfile##*.})" != "log" ]; then # Keep only the extension to check if it's a logfile - local logfile="$logfile/*.log" # Else, uses the directory and all logfile into it. - fi - else - logfile="/var/log/${app}/*.log" # Without argument, use a defaut directory in /var/log - fi - local su_directive="" - if [[ -n $specific_user ]]; then - su_directive=" # Run logorotate as specific user - group - su ${specific_user%/*} ${specific_user#*/}" - fi + local customtee="tee -a" + if [ "$nonappend" -eq 1 ]; then + customtee="tee" + fi + if [ -n "$logfile" ] + then + if [ ! -f "$1" ] && [ "$(echo ${logfile##*.})" != "log" ]; then # Keep only the extension to check if it's a logfile + local logfile="$logfile/*.log" # Else, uses the directory and all logfile into it. + fi + else + logfile="/var/log/${app}/*.log" # Without argument, use a defaut directory in /var/log + fi + local su_directive="" + if [[ -n $specific_user ]]; then + su_directive=" # Run logorotate as specific user - group + su ${specific_user%/*} ${specific_user#*/}" + fi - cat > ./${app}-logrotate << EOF # Build a config file for logrotate + cat > ./${app}-logrotate << EOF # Build a config file for logrotate $logfile { - # Rotate if the logfile exceeds 100Mo - size 100M - # Keep 12 old log maximum - rotate 12 - # Compress the logs with gzip - compress - # Compress the log at the next cycle. So keep always 2 non compressed logs - delaycompress - # Copy and truncate the log to allow to continue write on it. Instead of move the log. - copytruncate - # Do not do an error if the log is missing - missingok - # Not rotate if the log is empty - notifempty - # Keep old logs in the same dir - noolddir - $su_directive + # Rotate if the logfile exceeds 100Mo + size 100M + # Keep 12 old log maximum + rotate 12 + # Compress the logs with gzip + compress + # Compress the log at the next cycle. So keep always 2 non compressed logs + delaycompress + # Copy and truncate the log to allow to continue write on it. Instead of move the log. + copytruncate + # Do not do an error if the log is missing + missingok + # Not rotate if the log is empty + notifempty + # Keep old logs in the same dir + noolddir + $su_directive } EOF - mkdir -p $(dirname "$logfile") # Create the log directory, if not exist - cat ${app}-logrotate | $customtee /etc/logrotate.d/$app > /dev/null # Append this config to the existing config file, or replace the whole config file (depending on $customtee) + mkdir -p $(dirname "$logfile") # Create the log directory, if not exist + cat ${app}-logrotate | $customtee /etc/logrotate.d/$app > /dev/null # Append this config to the existing config file, or replace the whole config file (depending on $customtee) } # Remove the app's logrotate config. @@ -100,7 +100,7 @@ EOF # # Requires YunoHost version 2.6.4 or higher. ynh_remove_logrotate () { - if [ -e "/etc/logrotate.d/$app" ]; then - rm "/etc/logrotate.d/$app" - fi + if [ -e "/etc/logrotate.d/$app" ]; then + rm "/etc/logrotate.d/$app" + fi } diff --git a/data/helpers.d/mysql b/data/helpers.d/mysql index 658a79c17..8e7518d8f 100644 --- a/data/helpers.d/mysql +++ b/data/helpers.d/mysql @@ -151,19 +151,19 @@ ynh_mysql_create_user() { # Requires YunoHost version 2.2.4 or higher. ynh_mysql_user_exists() { - # Declare an array to define the options of this helper. - local legacy_args=u - local -A args_array=( [u]=user= ) - local user - # Manage arguments with getopts - ynh_handle_getopts_args "$@" + # Declare an array to define the options of this helper. + local legacy_args=u + local -A args_array=( [u]=user= ) + local user + # Manage arguments with getopts + ynh_handle_getopts_args "$@" - if [[ -z $(ynh_mysql_execute_as_root --sql="SELECT User from mysql.user WHERE User = '$user';") ]] - then - return 1 - else - return 0 - fi + if [[ -z $(ynh_mysql_execute_as_root --sql="SELECT User from mysql.user WHERE User = '$user';") ]] + then + return 1 + else + return 0 + fi } # Drop a user @@ -190,21 +190,21 @@ ynh_mysql_drop_user() { # # Requires YunoHost version 2.6.4 or higher. ynh_mysql_setup_db () { - # Declare an array to define the options of this helper. - local legacy_args=unp - local -A args_array=( [u]=db_user= [n]=db_name= [p]=db_pwd= ) - local db_user - local db_name - db_pwd="" - # Manage arguments with getopts - ynh_handle_getopts_args "$@" + # Declare an array to define the options of this helper. + local legacy_args=unp + local -A args_array=( [u]=db_user= [n]=db_name= [p]=db_pwd= ) + local db_user + local db_name + db_pwd="" + # Manage arguments with getopts + ynh_handle_getopts_args "$@" - local new_db_pwd=$(ynh_string_random) # Generate a random password - # If $db_pwd is not provided, use new_db_pwd instead for db_pwd - db_pwd="${db_pwd:-$new_db_pwd}" + local new_db_pwd=$(ynh_string_random) # Generate a random password + # If $db_pwd is not provided, use new_db_pwd instead for db_pwd + db_pwd="${db_pwd:-$new_db_pwd}" - ynh_mysql_create_db "$db_name" "$db_user" "$db_pwd" # Create the database - ynh_app_setting_set --app=$app --key=mysqlpwd --value=$db_pwd # Store the password in the app's config + ynh_mysql_create_db "$db_name" "$db_user" "$db_pwd" # Create the database + ynh_app_setting_set --app=$app --key=mysqlpwd --value=$db_pwd # Store the password in the app's config } # Remove a database if it exists, and the associated user @@ -215,24 +215,23 @@ ynh_mysql_setup_db () { # # Requires YunoHost version 2.6.4 or higher. ynh_mysql_remove_db () { - # Declare an array to define the options of this helper. - local legacy_args=un - local -A args_array=( [u]=db_user= [n]=db_name= ) - local db_user - local db_name - # Manage arguments with getopts - ynh_handle_getopts_args "$@" + # Declare an array to define the options of this helper. + local legacy_args=un + local -A args_array=( [u]=db_user= [n]=db_name= ) + local db_user + local db_name + # Manage arguments with getopts + ynh_handle_getopts_args "$@" - local mysql_root_password=$(cat $MYSQL_ROOT_PWD_FILE) - if mysqlshow -u root -p$mysql_root_password | grep -q "^| $db_name"; then # Check if the database exists - ynh_mysql_drop_db $db_name # Remove the database - else - ynh_print_warn --message="Database $db_name not found" - fi + local mysql_root_password=$(cat $MYSQL_ROOT_PWD_FILE) + if mysqlshow -u root -p$mysql_root_password | grep -q "^| $db_name"; then # Check if the database exists + ynh_mysql_drop_db $db_name # Remove the database + else + ynh_print_warn --message="Database $db_name not found" + fi - # Remove mysql user if it exists - if ynh_mysql_user_exists --user=$db_user; then - ynh_mysql_drop_user $db_user - fi + # Remove mysql user if it exists + if ynh_mysql_user_exists --user=$db_user; then + ynh_mysql_drop_user $db_user + fi } - diff --git a/data/helpers.d/network b/data/helpers.d/network index 0f6e9c442..ca15e6919 100644 --- a/data/helpers.d/network +++ b/data/helpers.d/network @@ -9,19 +9,19 @@ # # Requires YunoHost version 2.6.4 or higher. ynh_find_port () { - # Declare an array to define the options of this helper. - local legacy_args=p - local -A args_array=( [p]=port= ) - local port - # Manage arguments with getopts - ynh_handle_getopts_args "$@" + # Declare an array to define the options of this helper. + local legacy_args=p + local -A args_array=( [p]=port= ) + local port + # Manage arguments with getopts + ynh_handle_getopts_args "$@" - test -n "$port" || ynh_die --message="The argument of ynh_find_port must be a valid port." - while ss -nltu | awk '{print$5}' | grep -q -E ":$port$" # Check if the port is free - do - port=$((port+1)) # Else, pass to next port - done - echo $port + test -n "$port" || ynh_die --message="The argument of ynh_find_port must be a valid port." + while ss -nltu | awk '{print$5}' | grep -q -E ":$port$" # Check if the port is free + do + port=$((port+1)) # Else, pass to next port + done + echo $port } # Test if a port is available @@ -33,12 +33,12 @@ ynh_find_port () { # # Requires YunoHost version 3.7.x or higher. ynh_port_available () { - # Declare an array to define the options of this helper. - local legacy_args=p - local -A args_array=( [p]=port= ) - local port - # Manage arguments with getopts - ynh_handle_getopts_args "$@" + # Declare an array to define the options of this helper. + local legacy_args=p + local -A args_array=( [p]=port= ) + local port + # Manage arguments with getopts + ynh_handle_getopts_args "$@" if ss -nltu | grep -q -w :$port then @@ -59,17 +59,17 @@ ynh_port_available () { # Requires YunoHost version 2.2.4 or higher. ynh_validate_ip() { - # http://stackoverflow.com/questions/319279/how-to-validate-ip-address-in-python#319298 + # http://stackoverflow.com/questions/319279/how-to-validate-ip-address-in-python#319298 - # Declare an array to define the options of this helper. - local legacy_args=fi - local -A args_array=( [f]=family= [i]=ip_address= ) - local family - local ip_address - # Manage arguments with getopts - ynh_handle_getopts_args "$@" + # Declare an array to define the options of this helper. + local legacy_args=fi + local -A args_array=( [f]=family= [i]=ip_address= ) + local family + local ip_address + # Manage arguments with getopts + ynh_handle_getopts_args "$@" - [ "$family" == "4" ] || [ "$family" == "6" ] || return 1 + [ "$family" == "4" ] || [ "$family" == "6" ] || return 1 python /dev/stdin << EOF import socket @@ -93,14 +93,14 @@ EOF # Requires YunoHost version 2.2.4 or higher. ynh_validate_ip4() { - # Declare an array to define the options of this helper. - local legacy_args=i - local -A args_array=( [i]=ip_address= ) - local ip_address - # Manage arguments with getopts - ynh_handle_getopts_args "$@" + # Declare an array to define the options of this helper. + local legacy_args=i + local -A args_array=( [i]=ip_address= ) + local ip_address + # Manage arguments with getopts + ynh_handle_getopts_args "$@" - ynh_validate_ip 4 $ip_address + ynh_validate_ip 4 $ip_address } @@ -114,12 +114,12 @@ ynh_validate_ip4() # Requires YunoHost version 2.2.4 or higher. ynh_validate_ip6() { - # Declare an array to define the options of this helper. - local legacy_args=i - local -A args_array=( [i]=ip_address= ) - local ip_address - # Manage arguments with getopts - ynh_handle_getopts_args "$@" + # Declare an array to define the options of this helper. + local legacy_args=i + local -A args_array=( [i]=ip_address= ) + local ip_address + # Manage arguments with getopts + ynh_handle_getopts_args "$@" - ynh_validate_ip 6 $ip_address + ynh_validate_ip 6 $ip_address } diff --git a/data/helpers.d/nginx b/data/helpers.d/nginx index b34ebb4e1..161a1b413 100644 --- a/data/helpers.d/nginx +++ b/data/helpers.d/nginx @@ -20,51 +20,51 @@ # # Requires YunoHost version 2.7.2 or higher. ynh_add_nginx_config () { - finalnginxconf="/etc/nginx/conf.d/$domain.d/$app.conf" - local others_var=${1:-} - ynh_backup_if_checksum_is_different --file="$finalnginxconf" - cp ../conf/nginx.conf "$finalnginxconf" + finalnginxconf="/etc/nginx/conf.d/$domain.d/$app.conf" + local others_var=${1:-} + ynh_backup_if_checksum_is_different --file="$finalnginxconf" + cp ../conf/nginx.conf "$finalnginxconf" - # To avoid a break by set -u, use a void substitution ${var:-}. If the variable is not set, it's simply set with an empty variable. - # Substitute in a nginx config file only if the variable is not empty - if test -n "${path_url:-}"; then - # path_url_slash_less is path_url, or a blank value if path_url is only '/' - local path_url_slash_less=${path_url%/} - ynh_replace_string --match_string="__PATH__/" --replace_string="$path_url_slash_less/" --target_file="$finalnginxconf" - ynh_replace_string --match_string="__PATH__" --replace_string="$path_url" --target_file="$finalnginxconf" - fi - if test -n "${domain:-}"; then - ynh_replace_string --match_string="__DOMAIN__" --replace_string="$domain" --target_file="$finalnginxconf" - fi - if test -n "${port:-}"; then - ynh_replace_string --match_string="__PORT__" --replace_string="$port" --target_file="$finalnginxconf" - fi - if test -n "${app:-}"; then - ynh_replace_string --match_string="__NAME__" --replace_string="$app" --target_file="$finalnginxconf" - fi - if test -n "${final_path:-}"; then - ynh_replace_string --match_string="__FINALPATH__" --replace_string="$final_path" --target_file="$finalnginxconf" - fi - ynh_replace_string --match_string="__PHPVERSION__" --replace_string="$YNH_PHP_VERSION" --target_file="$finalnginxconf" + # To avoid a break by set -u, use a void substitution ${var:-}. If the variable is not set, it's simply set with an empty variable. + # Substitute in a nginx config file only if the variable is not empty + if test -n "${path_url:-}"; then + # path_url_slash_less is path_url, or a blank value if path_url is only '/' + local path_url_slash_less=${path_url%/} + ynh_replace_string --match_string="__PATH__/" --replace_string="$path_url_slash_less/" --target_file="$finalnginxconf" + ynh_replace_string --match_string="__PATH__" --replace_string="$path_url" --target_file="$finalnginxconf" + fi + if test -n "${domain:-}"; then + ynh_replace_string --match_string="__DOMAIN__" --replace_string="$domain" --target_file="$finalnginxconf" + fi + if test -n "${port:-}"; then + ynh_replace_string --match_string="__PORT__" --replace_string="$port" --target_file="$finalnginxconf" + fi + if test -n "${app:-}"; then + ynh_replace_string --match_string="__NAME__" --replace_string="$app" --target_file="$finalnginxconf" + fi + if test -n "${final_path:-}"; then + ynh_replace_string --match_string="__FINALPATH__" --replace_string="$final_path" --target_file="$finalnginxconf" + fi + ynh_replace_string --match_string="__PHPVERSION__" --replace_string="$YNH_PHP_VERSION" --target_file="$finalnginxconf" - # Replace all other variable given as arguments - for var_to_replace in $others_var - do - # ${var_to_replace^^} make the content of the variable on upper-cases - # ${!var_to_replace} get the content of the variable named $var_to_replace - ynh_replace_string --match_string="__${var_to_replace^^}__" --replace_string="${!var_to_replace}" --target_file="$finalnginxconf" - done - - if [ "${path_url:-}" != "/" ] - then - ynh_replace_string --match_string="^#sub_path_only" --replace_string="" --target_file="$finalnginxconf" - else - ynh_replace_string --match_string="^#root_path_only" --replace_string="" --target_file="$finalnginxconf" - fi + # Replace all other variable given as arguments + for var_to_replace in $others_var + do + # ${var_to_replace^^} make the content of the variable on upper-cases + # ${!var_to_replace} get the content of the variable named $var_to_replace + ynh_replace_string --match_string="__${var_to_replace^^}__" --replace_string="${!var_to_replace}" --target_file="$finalnginxconf" + done - ynh_store_file_checksum --file="$finalnginxconf" + if [ "${path_url:-}" != "/" ] + then + ynh_replace_string --match_string="^#sub_path_only" --replace_string="" --target_file="$finalnginxconf" + else + ynh_replace_string --match_string="^#root_path_only" --replace_string="" --target_file="$finalnginxconf" + fi - ynh_systemd_action --service_name=nginx --action=reload + ynh_store_file_checksum --file="$finalnginxconf" + + ynh_systemd_action --service_name=nginx --action=reload } # Remove the dedicated nginx config @@ -73,6 +73,6 @@ ynh_add_nginx_config () { # # Requires YunoHost version 2.7.2 or higher. ynh_remove_nginx_config () { - ynh_secure_remove --file="/etc/nginx/conf.d/$domain.d/$app.conf" - ynh_systemd_action --service_name=nginx --action=reload + ynh_secure_remove --file="/etc/nginx/conf.d/$domain.d/$app.conf" + ynh_systemd_action --service_name=nginx --action=reload } diff --git a/data/helpers.d/nodejs b/data/helpers.d/nodejs index 03cb5dffb..2d4ea66dc 100644 --- a/data/helpers.d/nodejs +++ b/data/helpers.d/nodejs @@ -13,16 +13,16 @@ export N_PREFIX="$n_install_dir" # # Requires YunoHost version 2.7.12 or higher. ynh_install_n () { - ynh_print_info --message="Installation of N - Node.js version management" - # Build an app.src for n - mkdir -p "../conf" - echo "SOURCE_URL=https://github.com/tj/n/archive/v4.1.0.tar.gz + ynh_print_info --message="Installation of N - Node.js version management" + # Build an app.src for n + mkdir -p "../conf" + echo "SOURCE_URL=https://github.com/tj/n/archive/v4.1.0.tar.gz SOURCE_SUM=3983fa3f00d4bf85ba8e21f1a590f6e28938093abe0bb950aeea52b1717471fc" > "../conf/n.src" - # Download and extract n - ynh_setup_source --dest_dir="$n_install_dir/git" --source_id=n - # Install n - (cd "$n_install_dir/git" - PREFIX=$N_PREFIX make install 2>&1) + # Download and extract n + ynh_setup_source --dest_dir="$n_install_dir/git" --source_id=n + # Install n + (cd "$n_install_dir/git" + PREFIX=$N_PREFIX make install 2>&1) } # Load the version of node for an app, and set variables. @@ -41,15 +41,15 @@ SOURCE_SUM=3983fa3f00d4bf85ba8e21f1a590f6e28938093abe0bb950aeea52b1717471fc" > " # # Requires YunoHost version 2.7.12 or higher. ynh_use_nodejs () { - nodejs_version=$(ynh_app_setting_get --app=$app --key=nodejs_version) + nodejs_version=$(ynh_app_setting_get --app=$app --key=nodejs_version) - nodejs_use_version="echo \"Deprecated command, should be removed\"" + nodejs_use_version="echo \"Deprecated command, should be removed\"" - # Get the absolute path of this version of node - nodejs_path="$node_version_path/$nodejs_version/bin" + # Get the absolute path of this version of node + nodejs_path="$node_version_path/$nodejs_version/bin" - # Load the path of this version of node in $PATH - [[ :$PATH: == *":$nodejs_path"* ]] || PATH="$nodejs_path:$PATH" + # Load the path of this version of node in $PATH + [[ :$PATH: == *":$nodejs_path"* ]] || PATH="$nodejs_path:$PATH" } # Install a specific version of nodejs @@ -64,72 +64,72 @@ ynh_use_nodejs () { # # Requires YunoHost version 2.7.12 or higher. ynh_install_nodejs () { - # Use n, https://github.com/tj/n to manage the nodejs versions + # Use n, https://github.com/tj/n to manage the nodejs versions - # Declare an array to define the options of this helper. - local legacy_args=n - local -A args_array=( [n]=nodejs_version= ) - local nodejs_version - # Manage arguments with getopts - ynh_handle_getopts_args "$@" + # Declare an array to define the options of this helper. + local legacy_args=n + local -A args_array=( [n]=nodejs_version= ) + local nodejs_version + # Manage arguments with getopts + ynh_handle_getopts_args "$@" - # Create $n_install_dir - mkdir -p "$n_install_dir" + # Create $n_install_dir + mkdir -p "$n_install_dir" - # Load n path in PATH - CLEAR_PATH="$n_install_dir/bin:$PATH" - # Remove /usr/local/bin in PATH in case of node prior installation - PATH=$(echo $CLEAR_PATH | sed 's@/usr/local/bin:@@') + # Load n path in PATH + CLEAR_PATH="$n_install_dir/bin:$PATH" + # Remove /usr/local/bin in PATH in case of node prior installation + PATH=$(echo $CLEAR_PATH | sed 's@/usr/local/bin:@@') - # Move an existing node binary, to avoid to block n. - test -x /usr/bin/node && mv /usr/bin/node /usr/bin/node_n - test -x /usr/bin/npm && mv /usr/bin/npm /usr/bin/npm_n + # Move an existing node binary, to avoid to block n. + test -x /usr/bin/node && mv /usr/bin/node /usr/bin/node_n + test -x /usr/bin/npm && mv /usr/bin/npm /usr/bin/npm_n - # If n is not previously setup, install it - if ! test $(n --version > /dev/null 2>&1) - then - ynh_install_n - fi + # If n is not previously setup, install it + if ! test $(n --version > /dev/null 2>&1) + then + ynh_install_n + fi - # Modify the default N_PREFIX in n script - ynh_replace_string --match_string="^N_PREFIX=\${N_PREFIX-.*}$" --replace_string="N_PREFIX=\${N_PREFIX-$N_PREFIX}" --target_file="$n_install_dir/bin/n" + # Modify the default N_PREFIX in n script + ynh_replace_string --match_string="^N_PREFIX=\${N_PREFIX-.*}$" --replace_string="N_PREFIX=\${N_PREFIX-$N_PREFIX}" --target_file="$n_install_dir/bin/n" - # Restore /usr/local/bin in PATH - PATH=$CLEAR_PATH + # Restore /usr/local/bin in PATH + PATH=$CLEAR_PATH - # And replace the old node binary. - test -x /usr/bin/node_n && mv /usr/bin/node_n /usr/bin/node - test -x /usr/bin/npm_n && mv /usr/bin/npm_n /usr/bin/npm + # And replace the old node binary. + test -x /usr/bin/node_n && mv /usr/bin/node_n /usr/bin/node + test -x /usr/bin/npm_n && mv /usr/bin/npm_n /usr/bin/npm - # Install the requested version of nodejs - uname=$(uname -m) - if [[ $uname =~ aarch64 || $uname =~ arm64 ]] - then - n $nodejs_version --arch=arm64 - else - n $nodejs_version - fi + # Install the requested version of nodejs + uname=$(uname -m) + if [[ $uname =~ aarch64 || $uname =~ arm64 ]] + then + n $nodejs_version --arch=arm64 + else + n $nodejs_version + fi - # Find the last "real" version for this major version of node. - real_nodejs_version=$(find $node_version_path/$nodejs_version* -maxdepth 0 | sort --version-sort | tail --lines=1) - real_nodejs_version=$(basename $real_nodejs_version) + # Find the last "real" version for this major version of node. + real_nodejs_version=$(find $node_version_path/$nodejs_version* -maxdepth 0 | sort --version-sort | tail --lines=1) + real_nodejs_version=$(basename $real_nodejs_version) - # Create a symbolic link for this major version if the file doesn't already exist - if [ ! -e "$node_version_path/$nodejs_version" ] - then - ln --symbolic --force --no-target-directory $node_version_path/$real_nodejs_version $node_version_path/$nodejs_version - fi + # Create a symbolic link for this major version if the file doesn't already exist + if [ ! -e "$node_version_path/$nodejs_version" ] + then + ln --symbolic --force --no-target-directory $node_version_path/$real_nodejs_version $node_version_path/$nodejs_version + fi - # Store the ID of this app and the version of node requested for it - echo "$YNH_APP_INSTANCE_NAME:$nodejs_version" | tee --append "$n_install_dir/ynh_app_version" + # Store the ID of this app and the version of node requested for it + echo "$YNH_APP_INSTANCE_NAME:$nodejs_version" | tee --append "$n_install_dir/ynh_app_version" - # Store nodejs_version into the config of this app - ynh_app_setting_set --app=$app --key=nodejs_version --value=$nodejs_version + # Store nodejs_version into the config of this app + ynh_app_setting_set --app=$app --key=nodejs_version --value=$nodejs_version - # Build the update script and set the cronjob - ynh_cron_upgrade_node + # Build the update script and set the cronjob + ynh_cron_upgrade_node - ynh_use_nodejs + ynh_use_nodejs } # Remove the version of node used by the app. @@ -142,25 +142,25 @@ ynh_install_nodejs () { # # Requires YunoHost version 2.7.12 or higher. ynh_remove_nodejs () { - nodejs_version=$(ynh_app_setting_get --app=$app --key=nodejs_version) + nodejs_version=$(ynh_app_setting_get --app=$app --key=nodejs_version) - # Remove the line for this app - sed --in-place "/$YNH_APP_INSTANCE_NAME:$nodejs_version/d" "$n_install_dir/ynh_app_version" + # Remove the line for this app + sed --in-place "/$YNH_APP_INSTANCE_NAME:$nodejs_version/d" "$n_install_dir/ynh_app_version" - # If no other app uses this version of nodejs, remove it. - if ! grep --quiet "$nodejs_version" "$n_install_dir/ynh_app_version" - then - $n_install_dir/bin/n rm $nodejs_version - fi + # If no other app uses this version of nodejs, remove it. + if ! grep --quiet "$nodejs_version" "$n_install_dir/ynh_app_version" + then + $n_install_dir/bin/n rm $nodejs_version + fi - # If no other app uses n, remove n - if [ ! -s "$n_install_dir/ynh_app_version" ] - then - ynh_secure_remove --file="$n_install_dir" - ynh_secure_remove --file="/usr/local/n" - sed --in-place "/N_PREFIX/d" /root/.bashrc - rm -f /etc/cron.daily/node_update - fi + # If no other app uses n, remove n + if [ ! -s "$n_install_dir/ynh_app_version" ] + then + ynh_secure_remove --file="$n_install_dir" + ynh_secure_remove --file="/usr/local/n" + sed --in-place "/N_PREFIX/d" /root/.bashrc + rm -f /etc/cron.daily/node_update + fi } # Set a cron design to update your node versions @@ -173,8 +173,8 @@ ynh_remove_nodejs () { # # Requires YunoHost version 2.7.12 or higher. ynh_cron_upgrade_node () { - # Build the update script - cat > "$n_install_dir/node_update.sh" << EOF + # Build the update script + cat > "$n_install_dir/node_update.sh" << EOF #!/bin/bash version_path="$node_version_path" @@ -195,26 +195,26 @@ all_real_version=\$(echo "\$all_real_version" | sort --unique) # Read each major version while read version do - echo "Update of the version \$version" - sudo \$n_install_dir/bin/n \$version + echo "Update of the version \$version" + sudo \$n_install_dir/bin/n \$version - # Find the last "real" version for this major version of node. - real_nodejs_version=\$(find \$version_path/\$version* -maxdepth 0 | sort --version-sort | tail --lines=1) - real_nodejs_version=\$(basename \$real_nodejs_version) + # Find the last "real" version for this major version of node. + real_nodejs_version=\$(find \$version_path/\$version* -maxdepth 0 | sort --version-sort | tail --lines=1) + real_nodejs_version=\$(basename \$real_nodejs_version) - # Update the symbolic link for this version - sudo ln --symbolic --force --no-target-directory \$version_path/\$real_nodejs_version \$version_path/\$version + # Update the symbolic link for this version + sudo ln --symbolic --force --no-target-directory \$version_path/\$real_nodejs_version \$version_path/\$version done <<< "\$(echo "\$all_real_version")" EOF - chmod +x "$n_install_dir/node_update.sh" + chmod +x "$n_install_dir/node_update.sh" - # Build the cronjob - cat > "/etc/cron.daily/node_update" << EOF + # Build the cronjob + cat > "/etc/cron.daily/node_update" << EOF #!/bin/bash $n_install_dir/node_update.sh >> $n_install_dir/node_update.log EOF - chmod +x "/etc/cron.daily/node_update" + chmod +x "/etc/cron.daily/node_update" } diff --git a/data/helpers.d/php b/data/helpers.d/php index c1ae91c2e..8bd96e42b 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -57,179 +57,179 @@ YNH_PHP_VERSION=${YNH_PHP_VERSION:-$YNH_DEFAULT_PHP_VERSION} # # Requires YunoHost version 2.7.2 or higher. ynh_add_fpm_config () { - # Declare an array to define the options of this helper. - local legacy_args=vtufpd - local -A args_array=( [v]=phpversion= [t]=use_template [u]=usage= [f]=footprint= [p]=package= [d]=dedicated_service ) - local phpversion - local use_template - local usage - local footprint - local package - local dedicated_service - # Manage arguments with getopts - ynh_handle_getopts_args "$@" - package=${package:-} + # Declare an array to define the options of this helper. + local legacy_args=vtufpd + local -A args_array=( [v]=phpversion= [t]=use_template [u]=usage= [f]=footprint= [p]=package= [d]=dedicated_service ) + local phpversion + local use_template + local usage + local footprint + local package + local dedicated_service + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + package=${package:-} - # The default behaviour is to use the template. - use_template="${use_template:-1}" - usage="${usage:-}" - footprint="${footprint:-}" - if [ -n "$usage" ] || [ -n "$footprint" ]; then - use_template=0 - fi - # Do not use a dedicated service by default - dedicated_service=${dedicated_service:-0} + # The default behaviour is to use the template. + use_template="${use_template:-1}" + usage="${usage:-}" + footprint="${footprint:-}" + if [ -n "$usage" ] || [ -n "$footprint" ]; then + use_template=0 + fi + # Do not use a dedicated service by default + dedicated_service=${dedicated_service:-0} - # Set the default PHP-FPM version by default - phpversion="${phpversion:-$YNH_PHP_VERSION}" + # Set the default PHP-FPM version by default + phpversion="${phpversion:-$YNH_PHP_VERSION}" - # If the requested php version is not the default version for YunoHost - if [ "$phpversion" != "$YNH_DEFAULT_PHP_VERSION" ] - then - # If the argument --package is used, add the packages to ynh_install_php to install them from sury - if [ -n "$package" ]; then - local additionnal_packages="--package=$package" - else - local additionnal_packages="" - fi - # Install this specific version of php. - ynh_install_php --phpversion=$phpversion "$additionnal_packages" - elif [ -n "$package" ] - then - # Install the additionnal packages from the default repository - ynh_add_app_dependencies --package="$package" - fi + # If the requested php version is not the default version for YunoHost + if [ "$phpversion" != "$YNH_DEFAULT_PHP_VERSION" ] + then + # If the argument --package is used, add the packages to ynh_install_php to install them from sury + if [ -n "$package" ]; then + local additionnal_packages="--package=$package" + else + local additionnal_packages="" + fi + # Install this specific version of php. + ynh_install_php --phpversion=$phpversion "$additionnal_packages" + elif [ -n "$package" ] + then + # Install the additionnal packages from the default repository + ynh_add_app_dependencies --package="$package" + fi - if [ $dedicated_service -eq 1 ] - then - local fpm_service="${app}-phpfpm" - local fpm_config_dir="/etc/php/$phpversion/dedicated-fpm" - else - local fpm_service="php${phpversion}-fpm" - local fpm_config_dir="/etc/php/$phpversion/fpm" - fi - # Configure PHP-FPM 5 on Debian Jessie - if [ "$(ynh_get_debian_release)" == "jessie" ]; then - fpm_config_dir="/etc/php5/fpm" - fpm_service="php5-fpm" - fi + if [ $dedicated_service -eq 1 ] + then + local fpm_service="${app}-phpfpm" + local fpm_config_dir="/etc/php/$phpversion/dedicated-fpm" + else + local fpm_service="php${phpversion}-fpm" + local fpm_config_dir="/etc/php/$phpversion/fpm" + fi + # Configure PHP-FPM 5 on Debian Jessie + if [ "$(ynh_get_debian_release)" == "jessie" ]; then + fpm_config_dir="/etc/php5/fpm" + fpm_service="php5-fpm" + fi - # Create the directory for fpm pools - mkdir -p "$fpm_config_dir/pool.d" + # Create the directory for fpm pools + mkdir -p "$fpm_config_dir/pool.d" - ynh_app_setting_set --app=$app --key=fpm_config_dir --value="$fpm_config_dir" - ynh_app_setting_set --app=$app --key=fpm_service --value="$fpm_service" - ynh_app_setting_set --app=$app --key=fpm_dedicated_service --value="$dedicated_service" - ynh_app_setting_set --app=$app --key=phpversion --value=$phpversion - finalphpconf="$fpm_config_dir/pool.d/$app.conf" + ynh_app_setting_set --app=$app --key=fpm_config_dir --value="$fpm_config_dir" + ynh_app_setting_set --app=$app --key=fpm_service --value="$fpm_service" + ynh_app_setting_set --app=$app --key=fpm_dedicated_service --value="$dedicated_service" + ynh_app_setting_set --app=$app --key=phpversion --value=$phpversion + finalphpconf="$fpm_config_dir/pool.d/$app.conf" - # Migrate from mutual php service to dedicated one. - if [ $dedicated_service -eq 1 ] - then - local old_fpm_config_dir="/etc/php/$phpversion/fpm" - # If a config file exist in the common pool, move it. - if [ -e "$old_fpm_config_dir/pool.d/$app.conf" ] - then - ynh_print_info --message="Migrate to a dedicated php-fpm service for $app." - # Create a backup of the old file before migration - ynh_backup_if_checksum_is_different --file="$old_fpm_config_dir/pool.d/$app.conf" - # Remove the old php config file - ynh_secure_remove --file="$old_fpm_config_dir/pool.d/$app.conf" - # Reload php to release the socket and allow the dedicated service to use it - ynh_systemd_action --service_name=php${phpversion}-fpm --action=reload - fi - fi + # Migrate from mutual php service to dedicated one. + if [ $dedicated_service -eq 1 ] + then + local old_fpm_config_dir="/etc/php/$phpversion/fpm" + # If a config file exist in the common pool, move it. + if [ -e "$old_fpm_config_dir/pool.d/$app.conf" ] + then + ynh_print_info --message="Migrate to a dedicated php-fpm service for $app." + # Create a backup of the old file before migration + ynh_backup_if_checksum_is_different --file="$old_fpm_config_dir/pool.d/$app.conf" + # Remove the old php config file + ynh_secure_remove --file="$old_fpm_config_dir/pool.d/$app.conf" + # Reload php to release the socket and allow the dedicated service to use it + ynh_systemd_action --service_name=php${phpversion}-fpm --action=reload + fi + fi - ynh_backup_if_checksum_is_different --file="$finalphpconf" + ynh_backup_if_checksum_is_different --file="$finalphpconf" - if [ $use_template -eq 1 ] - then - # Usage 1, use the template in ../conf/php-fpm.conf - cp ../conf/php-fpm.conf "$finalphpconf" - ynh_replace_string --match_string="__NAMETOCHANGE__" --replace_string="$app" --target_file="$finalphpconf" - ynh_replace_string --match_string="__FINALPATH__" --replace_string="$final_path" --target_file="$finalphpconf" - ynh_replace_string --match_string="__USER__" --replace_string="$app" --target_file="$finalphpconf" - ynh_replace_string --match_string="__PHPVERSION__" --replace_string="$phpversion" --target_file="$finalphpconf" + if [ $use_template -eq 1 ] + then + # Usage 1, use the template in ../conf/php-fpm.conf + cp ../conf/php-fpm.conf "$finalphpconf" + ynh_replace_string --match_string="__NAMETOCHANGE__" --replace_string="$app" --target_file="$finalphpconf" + ynh_replace_string --match_string="__FINALPATH__" --replace_string="$final_path" --target_file="$finalphpconf" + ynh_replace_string --match_string="__USER__" --replace_string="$app" --target_file="$finalphpconf" + ynh_replace_string --match_string="__PHPVERSION__" --replace_string="$phpversion" --target_file="$finalphpconf" - else - # Usage 2, generate a php-fpm config file with ynh_get_scalable_phpfpm + else + # Usage 2, generate a php-fpm config file with ynh_get_scalable_phpfpm - # Store settings - ynh_app_setting_set --app=$app --key=fpm_footprint --value=$footprint - ynh_app_setting_set --app=$app --key=fpm_usage --value=$usage + # Store settings + ynh_app_setting_set --app=$app --key=fpm_footprint --value=$footprint + ynh_app_setting_set --app=$app --key=fpm_usage --value=$usage - # Define the values to use for the configuration of php. - ynh_get_scalable_phpfpm --usage=$usage --footprint=$footprint + # Define the values to use for the configuration of php. + ynh_get_scalable_phpfpm --usage=$usage --footprint=$footprint - # Copy the default file - cp "/etc/php/$phpversion/fpm/pool.d/www.conf" "$finalphpconf" + # Copy the default file + cp "/etc/php/$phpversion/fpm/pool.d/www.conf" "$finalphpconf" - # Replace standard variables into the default file - ynh_replace_string --match_string="^\[www\]" --replace_string="[$app]" --target_file="$finalphpconf" - ynh_replace_string --match_string=".*listen = .*" --replace_string="listen = /var/run/php/php$phpversion-fpm-$app.sock" --target_file="$finalphpconf" - ynh_replace_string --match_string="^user = .*" --replace_string="user = $app" --target_file="$finalphpconf" - ynh_replace_string --match_string="^group = .*" --replace_string="group = $app" --target_file="$finalphpconf" - ynh_replace_string --match_string=".*chdir = .*" --replace_string="chdir = $final_path" --target_file="$finalphpconf" + # Replace standard variables into the default file + ynh_replace_string --match_string="^\[www\]" --replace_string="[$app]" --target_file="$finalphpconf" + ynh_replace_string --match_string=".*listen = .*" --replace_string="listen = /var/run/php/php$phpversion-fpm-$app.sock" --target_file="$finalphpconf" + ynh_replace_string --match_string="^user = .*" --replace_string="user = $app" --target_file="$finalphpconf" + ynh_replace_string --match_string="^group = .*" --replace_string="group = $app" --target_file="$finalphpconf" + ynh_replace_string --match_string=".*chdir = .*" --replace_string="chdir = $final_path" --target_file="$finalphpconf" - # Configure fpm children - ynh_replace_string --match_string=".*pm = .*" --replace_string="pm = $php_pm" --target_file="$finalphpconf" - ynh_replace_string --match_string=".*pm.max_children = .*" --replace_string="pm.max_children = $php_max_children" --target_file="$finalphpconf" - ynh_replace_string --match_string=".*pm.max_requests = .*" --replace_string="pm.max_requests = 500" --target_file="$finalphpconf" - ynh_replace_string --match_string=".*request_terminate_timeout = .*" --replace_string="request_terminate_timeout = 1d" --target_file="$finalphpconf" - if [ "$php_pm" = "dynamic" ] - then - ynh_replace_string --match_string=".*pm.start_servers = .*" --replace_string="pm.start_servers = $php_start_servers" --target_file="$finalphpconf" - ynh_replace_string --match_string=".*pm.min_spare_servers = .*" --replace_string="pm.min_spare_servers = $php_min_spare_servers" --target_file="$finalphpconf" - ynh_replace_string --match_string=".*pm.max_spare_servers = .*" --replace_string="pm.max_spare_servers = $php_max_spare_servers" --target_file="$finalphpconf" - elif [ "$php_pm" = "ondemand" ] - then - ynh_replace_string --match_string=".*pm.process_idle_timeout = .*" --replace_string="pm.process_idle_timeout = 10s" --target_file="$finalphpconf" - fi + # Configure fpm children + ynh_replace_string --match_string=".*pm = .*" --replace_string="pm = $php_pm" --target_file="$finalphpconf" + ynh_replace_string --match_string=".*pm.max_children = .*" --replace_string="pm.max_children = $php_max_children" --target_file="$finalphpconf" + ynh_replace_string --match_string=".*pm.max_requests = .*" --replace_string="pm.max_requests = 500" --target_file="$finalphpconf" + ynh_replace_string --match_string=".*request_terminate_timeout = .*" --replace_string="request_terminate_timeout = 1d" --target_file="$finalphpconf" + if [ "$php_pm" = "dynamic" ] + then + ynh_replace_string --match_string=".*pm.start_servers = .*" --replace_string="pm.start_servers = $php_start_servers" --target_file="$finalphpconf" + ynh_replace_string --match_string=".*pm.min_spare_servers = .*" --replace_string="pm.min_spare_servers = $php_min_spare_servers" --target_file="$finalphpconf" + ynh_replace_string --match_string=".*pm.max_spare_servers = .*" --replace_string="pm.max_spare_servers = $php_max_spare_servers" --target_file="$finalphpconf" + elif [ "$php_pm" = "ondemand" ] + then + ynh_replace_string --match_string=".*pm.process_idle_timeout = .*" --replace_string="pm.process_idle_timeout = 10s" --target_file="$finalphpconf" + fi - # Comment unused parameters - if [ "$php_pm" != "dynamic" ] - then - ynh_replace_string --match_string=".*\(pm.start_servers = .*\)" --replace_string=";\1" --target_file="$finalphpconf" - ynh_replace_string --match_string=".*\(pm.min_spare_servers = .*\)" --replace_string=";\1" --target_file="$finalphpconf" - ynh_replace_string --match_string=".*\(pm.max_spare_servers = .*\)" --replace_string=";\1" --target_file="$finalphpconf" - fi - if [ "$php_pm" != "ondemand" ] - then - ynh_replace_string --match_string=".*\(pm.process_idle_timeout = .*\)" --replace_string=";\1" --target_file="$finalphpconf" - fi + # Comment unused parameters + if [ "$php_pm" != "dynamic" ] + then + ynh_replace_string --match_string=".*\(pm.start_servers = .*\)" --replace_string=";\1" --target_file="$finalphpconf" + ynh_replace_string --match_string=".*\(pm.min_spare_servers = .*\)" --replace_string=";\1" --target_file="$finalphpconf" + ynh_replace_string --match_string=".*\(pm.max_spare_servers = .*\)" --replace_string=";\1" --target_file="$finalphpconf" + fi + if [ "$php_pm" != "ondemand" ] + then + ynh_replace_string --match_string=".*\(pm.process_idle_timeout = .*\)" --replace_string=";\1" --target_file="$finalphpconf" + fi - # Concatene the extra config. - if [ -e ../conf/extra_php-fpm.conf ]; then - cat ../conf/extra_php-fpm.conf >> "$finalphpconf" - fi - fi + # Concatene the extra config. + if [ -e ../conf/extra_php-fpm.conf ]; then + cat ../conf/extra_php-fpm.conf >> "$finalphpconf" + fi + fi - chown root: "$finalphpconf" - ynh_store_file_checksum --file="$finalphpconf" + chown root: "$finalphpconf" + ynh_store_file_checksum --file="$finalphpconf" - if [ -e "../conf/php-fpm.ini" ] - then - ynh_print_warn -message="Packagers ! Please do not use a separate php ini file, merge your directives in the pool file instead." - finalphpini="$fpm_config_dir/conf.d/20-$app.ini" - ynh_backup_if_checksum_is_different "$finalphpini" - cp ../conf/php-fpm.ini "$finalphpini" - chown root: "$finalphpini" - ynh_store_file_checksum "$finalphpini" - fi + if [ -e "../conf/php-fpm.ini" ] + then + ynh_print_warn -message="Packagers ! Please do not use a separate php ini file, merge your directives in the pool file instead." + finalphpini="$fpm_config_dir/conf.d/20-$app.ini" + ynh_backup_if_checksum_is_different "$finalphpini" + cp ../conf/php-fpm.ini "$finalphpini" + chown root: "$finalphpini" + ynh_store_file_checksum "$finalphpini" + fi - if [ $dedicated_service -eq 1 ] - then - # Create a dedicated php-fpm.conf for the service - local globalphpconf=$fpm_config_dir/php-fpm-$app.conf - cp /etc/php/${phpversion}/fpm/php-fpm.conf $globalphpconf + if [ $dedicated_service -eq 1 ] + then + # Create a dedicated php-fpm.conf for the service + local globalphpconf=$fpm_config_dir/php-fpm-$app.conf + cp /etc/php/${phpversion}/fpm/php-fpm.conf $globalphpconf - ynh_replace_string --match_string="^[; ]*pid *=.*" --replace_string="pid = /run/php/php${phpversion}-fpm-$app.pid" --target_file="$globalphpconf" - ynh_replace_string --match_string="^[; ]*error_log *=.*" --replace_string="error_log = /var/log/php/fpm-php.$app.log" --target_file="$globalphpconf" - ynh_replace_string --match_string="^[; ]*syslog.ident *=.*" --replace_string="syslog.ident = php-fpm-$app" --target_file="$globalphpconf" - ynh_replace_string --match_string="^[; ]*include *=.*" --replace_string="include = $finalphpconf" --target_file="$globalphpconf" + ynh_replace_string --match_string="^[; ]*pid *=.*" --replace_string="pid = /run/php/php${phpversion}-fpm-$app.pid" --target_file="$globalphpconf" + ynh_replace_string --match_string="^[; ]*error_log *=.*" --replace_string="error_log = /var/log/php/fpm-php.$app.log" --target_file="$globalphpconf" + ynh_replace_string --match_string="^[; ]*syslog.ident *=.*" --replace_string="syslog.ident = php-fpm-$app" --target_file="$globalphpconf" + ynh_replace_string --match_string="^[; ]*include *=.*" --replace_string="include = $finalphpconf" --target_file="$globalphpconf" - # Create a config for a dedicated php-fpm service for the app - echo "[Unit] + # Create a config for a dedicated php-fpm service for the app + echo "[Unit] Description=PHP $phpversion FastCGI Process Manager for $app After=network.target @@ -243,18 +243,18 @@ ExecReload=/bin/kill -USR2 \$MAINPID WantedBy=multi-user.target " > ../conf/$fpm_service - # Create this dedicated php-fpm service - ynh_add_systemd_config --service=$fpm_service --template=$fpm_service - # Integrate the service in YunoHost admin panel - yunohost service add $fpm_service --log /var/log/php/fpm-php.$app.log --log_type file --description "Php-fpm dedicated to $app" - # Configure log rotate - ynh_use_logrotate --logfile=/var/log/php - # Restart the service, as this service is either stopped or only for this app - ynh_systemd_action --service_name=$fpm_service --action=restart - else - # Reload php, to not impact other parts of the system using php - ynh_systemd_action --service_name=$fpm_service --action=reload - fi + # Create this dedicated php-fpm service + ynh_add_systemd_config --service=$fpm_service --template=$fpm_service + # Integrate the service in YunoHost admin panel + yunohost service add $fpm_service --log /var/log/php/fpm-php.$app.log --log_type file --description "Php-fpm dedicated to $app" + # Configure log rotate + ynh_use_logrotate --logfile=/var/log/php + # Restart the service, as this service is either stopped or only for this app + ynh_systemd_action --service_name=$fpm_service --action=restart + else + # Reload php, to not impact other parts of the system using php + ynh_systemd_action --service_name=$fpm_service --action=reload + fi } # Remove the dedicated php-fpm config @@ -263,43 +263,43 @@ WantedBy=multi-user.target # # Requires YunoHost version 2.7.2 or higher. ynh_remove_fpm_config () { - local fpm_config_dir=$(ynh_app_setting_get --app=$app --key=fpm_config_dir) - local fpm_service=$(ynh_app_setting_get --app=$app --key=fpm_service) - local dedicated_service=$(ynh_app_setting_get --app=$app --key=fpm_dedicated_service) - dedicated_service=${dedicated_service:-0} - # Get the version of php used by this app - local phpversion=$(ynh_app_setting_get $app phpversion) + local fpm_config_dir=$(ynh_app_setting_get --app=$app --key=fpm_config_dir) + local fpm_service=$(ynh_app_setting_get --app=$app --key=fpm_service) + local dedicated_service=$(ynh_app_setting_get --app=$app --key=fpm_dedicated_service) + dedicated_service=${dedicated_service:-0} + # Get the version of php used by this app + local phpversion=$(ynh_app_setting_get $app phpversion) - # Assume default PHP-FPM version by default - phpversion="${phpversion:-$YNH_DEFAULT_PHP_VERSION}" + # Assume default PHP-FPM version by default + phpversion="${phpversion:-$YNH_DEFAULT_PHP_VERSION}" - # Assume default php files if not set - if [ -z "$fpm_config_dir" ]; then - fpm_config_dir="/etc/php/$YNH_DEFAULT_PHP_VERSION/fpm" - fpm_service="php$YNH_DEFAULT_PHP_VERSION-fpm" - fi + # Assume default php files if not set + if [ -z "$fpm_config_dir" ]; then + fpm_config_dir="/etc/php/$YNH_DEFAULT_PHP_VERSION/fpm" + fpm_service="php$YNH_DEFAULT_PHP_VERSION-fpm" + fi - if [ $dedicated_service -eq 1 ] - then - # Remove the dedicated service php-fpm service for the app - ynh_remove_systemd_config --service=$fpm_service - # Remove the global php-fpm conf - ynh_secure_remove --file="$fpm_config_dir/php-fpm-$app.conf" - # Remove the service from the list of services known by Yunohost - yunohost service remove $fpm_service - elif ynh_package_is_installed --package="php${phpversion}-fpm"; then - ynh_systemd_action --service_name=$fpm_service --action=reload - fi + if [ $dedicated_service -eq 1 ] + then + # Remove the dedicated service php-fpm service for the app + ynh_remove_systemd_config --service=$fpm_service + # Remove the global php-fpm conf + ynh_secure_remove --file="$fpm_config_dir/php-fpm-$app.conf" + # Remove the service from the list of services known by Yunohost + yunohost service remove $fpm_service + elif ynh_package_is_installed --package="php${phpversion}-fpm"; then + ynh_systemd_action --service_name=$fpm_service --action=reload + 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" + 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 the php version used is not the default version for YunoHost - if [ "$phpversion" != "$YNH_DEFAULT_PHP_VERSION" ] - then - # Remove this specific version of php - ynh_remove_php - fi + # If the php version used is not the default version for YunoHost + if [ "$phpversion" != "$YNH_DEFAULT_PHP_VERSION" ] + then + # Remove this specific version of php + ynh_remove_php + fi } # Install another version of php. @@ -310,50 +310,50 @@ ynh_remove_fpm_config () { # | arg: -v, --phpversion - Version of php to install. # | arg: -p, --package - Additionnal php packages to install ynh_install_php () { - # Declare an array to define the options of this helper. - local legacy_args=vp - local -A args_array=( [v]=phpversion= [p]=package= ) - local phpversion - local package - # Manage arguments with getopts - ynh_handle_getopts_args "$@" - package=${package:-} + # Declare an array to define the options of this helper. + local legacy_args=vp + local -A args_array=( [v]=phpversion= [p]=package= ) + local phpversion + local package + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + package=${package:-} - # Store phpversion into the config of this app - ynh_app_setting_set $app phpversion $phpversion + # Store phpversion into the config of this app + ynh_app_setting_set $app phpversion $phpversion - if [ "$phpversion" == "$YNH_DEFAULT_PHP_VERSION" ] - then - ynh_die "Do not use ynh_install_php to install php$YNH_DEFAULT_PHP_VERSION" - fi + if [ "$phpversion" == "$YNH_DEFAULT_PHP_VERSION" ] + then + ynh_die "Do not use ynh_install_php to install php$YNH_DEFAULT_PHP_VERSION" + fi - # Create the file if doesn't exist already - touch /etc/php/ynh_app_version + # Create the file if doesn't exist already + touch /etc/php/ynh_app_version - # Do not add twice the same line - if ! grep --quiet "$YNH_APP_INSTANCE_NAME:" "/etc/php/ynh_app_version" - then - # Store the ID of this app and the version of php requested for it - echo "$YNH_APP_INSTANCE_NAME:$phpversion" | tee --append "/etc/php/ynh_app_version" - fi + # Do not add twice the same line + if ! grep --quiet "$YNH_APP_INSTANCE_NAME:" "/etc/php/ynh_app_version" + then + # Store the ID of this app and the version of php requested for it + echo "$YNH_APP_INSTANCE_NAME:$phpversion" | tee --append "/etc/php/ynh_app_version" + fi - # Add an extra repository for those packages - ynh_install_extra_repo --repo="https://packages.sury.org/php/ $(lsb_release -sc) main" --key="https://packages.sury.org/php/apt.gpg" --priority=995 --name=extra_php_version + # Add an extra repository for those packages + ynh_install_extra_repo --repo="https://packages.sury.org/php/ $(lsb_release -sc) main" --key="https://packages.sury.org/php/apt.gpg" --priority=995 --name=extra_php_version - # Install requested dependencies from this extra repository. - # Install php-fpm first, otherwise php will install apache as a dependency. - ynh_add_app_dependencies --package="php${phpversion}-fpm" - ynh_add_app_dependencies --package="php$phpversion php${phpversion}-common $package" + # Install requested dependencies from this extra repository. + # Install php-fpm first, otherwise php will install apache as a dependency. + ynh_add_app_dependencies --package="php${phpversion}-fpm" + ynh_add_app_dependencies --package="php$phpversion php${phpversion}-common $package" - # Set the default php version back as the default version for php-cli. - update-alternatives --set php /usr/bin/php$YNH_DEFAULT_PHP_VERSION + # Set the default php version back as the default version for php-cli. + update-alternatives --set php /usr/bin/php$YNH_DEFAULT_PHP_VERSION - # Pin this extra repository after packages are installed to prevent sury of doing shit - ynh_pin_repo --package="*" --pin="origin \"packages.sury.org\"" --priority=200 --name=extra_php_version - ynh_pin_repo --package="php${YNH_DEFAULT_PHP_VERSION}*" --pin="origin \"packages.sury.org\"" --priority=600 --name=extra_php_version --append + # Pin this extra repository after packages are installed to prevent sury of doing shit + ynh_pin_repo --package="*" --pin="origin \"packages.sury.org\"" --priority=200 --name=extra_php_version + ynh_pin_repo --package="php${YNH_DEFAULT_PHP_VERSION}*" --pin="origin \"packages.sury.org\"" --priority=600 --name=extra_php_version --append - # Advertise service in admin panel - yunohost service add php${phpversion}-fpm --log "/var/log/php${phpversion}-fpm.log" + # Advertise service in admin panel + yunohost service add php${phpversion}-fpm --log "/var/log/php${phpversion}-fpm.log" } # Remove the specific version of php used by the app. @@ -362,35 +362,35 @@ ynh_install_php () { # # usage: ynh_install_php ynh_remove_php () { - # Get the version of php used by this app - local phpversion=$(ynh_app_setting_get $app phpversion) + # Get the version of php used by this app + local phpversion=$(ynh_app_setting_get $app phpversion) - if [ "$phpversion" == "$YNH_DEFAULT_PHP_VERSION" ] || [ -z "$phpversion" ] - then - if [ "$phpversion" == "$YNH_DEFAULT_PHP_VERSION" ] - then - ynh_print_err "Do not use ynh_remove_php to remove php$YNH_DEFAULT_PHP_VERSION !" - fi - return 0 - fi + if [ "$phpversion" == "$YNH_DEFAULT_PHP_VERSION" ] || [ -z "$phpversion" ] + then + if [ "$phpversion" == "$YNH_DEFAULT_PHP_VERSION" ] + then + ynh_print_err "Do not use ynh_remove_php to remove php$YNH_DEFAULT_PHP_VERSION !" + fi + return 0 + fi - # Create the file if doesn't exist already - touch /etc/php/ynh_app_version + # Create the file if doesn't exist already + touch /etc/php/ynh_app_version - # Remove the line for this app - sed --in-place "/$YNH_APP_INSTANCE_NAME:$phpversion/d" "/etc/php/ynh_app_version" + # Remove the line for this app + sed --in-place "/$YNH_APP_INSTANCE_NAME:$phpversion/d" "/etc/php/ynh_app_version" - # If no other app uses this version of php, remove it. - if ! grep --quiet "$phpversion" "/etc/php/ynh_app_version" - then - # Remove the service from the admin panel - if ynh_package_is_installed --package="php${phpversion}-fpm"; then - yunohost service remove php${phpversion}-fpm - fi + # If no other app uses this version of php, remove it. + if ! grep --quiet "$phpversion" "/etc/php/ynh_app_version" + then + # Remove the service from the admin panel + if ynh_package_is_installed --package="php${phpversion}-fpm"; then + yunohost service remove php${phpversion}-fpm + fi - # Purge php dependencies for this version. - ynh_package_autopurge "php$phpversion php${phpversion}-fpm php${phpversion}-common" - fi + # Purge php dependencies for this version. + ynh_package_autopurge "php$phpversion php${phpversion}-fpm php${phpversion}-common" + fi } # Define the values to configure php-fpm diff --git a/data/helpers.d/postgresql b/data/helpers.d/postgresql index ff6ef0f57..e6984c8db 100644 --- a/data/helpers.d/postgresql +++ b/data/helpers.d/postgresql @@ -15,17 +15,17 @@ PSQL_ROOT_PWD_FILE=/etc/yunohost/psql # # Requires YunoHost version 3.5.0 or higher. ynh_psql_connect_as() { - # Declare an array to define the options of this helper. - local legacy_args=upd - local -A args_array=([u]=user= [p]=password= [d]=database=) - local user - local password - local database - # Manage arguments with getopts - ynh_handle_getopts_args "$@" - database="${database:-}" + # Declare an array to define the options of this helper. + local legacy_args=upd + local -A args_array=([u]=user= [p]=password= [d]=database=) + local user + local password + local database + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + database="${database:-}" - sudo --login --user=postgres PGUSER="$user" PGPASSWORD="$password" psql "$database" + sudo --login --user=postgres PGUSER="$user" PGPASSWORD="$password" psql "$database" } # Execute a command as root user @@ -36,17 +36,17 @@ ynh_psql_connect_as() { # # Requires YunoHost version 3.5.0 or higher. ynh_psql_execute_as_root() { - # Declare an array to define the options of this helper. - local legacy_args=sd - local -A args_array=([s]=sql= [d]=database=) - local sql - local database - # Manage arguments with getopts - ynh_handle_getopts_args "$@" - database="${database:-}" + # Declare an array to define the options of this helper. + local legacy_args=sd + local -A args_array=([s]=sql= [d]=database=) + local sql + local database + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + database="${database:-}" - ynh_psql_connect_as --user="postgres" --password="$(cat $PSQL_ROOT_PWD_FILE)" \ - --database="$database" <<<"$sql" + ynh_psql_connect_as --user="postgres" --password="$(cat $PSQL_ROOT_PWD_FILE)" \ + --database="$database" <<<"$sql" } # Execute a command from a file as root user @@ -57,17 +57,17 @@ ynh_psql_execute_as_root() { # # Requires YunoHost version 3.5.0 or higher. ynh_psql_execute_file_as_root() { - # Declare an array to define the options of this helper. - local legacy_args=fd - local -A args_array=([f]=file= [d]=database=) - local file - local database - # Manage arguments with getopts - ynh_handle_getopts_args "$@" - database="${database:-}" + # Declare an array to define the options of this helper. + local legacy_args=fd + local -A args_array=([f]=file= [d]=database=) + local file + local database + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + database="${database:-}" - ynh_psql_connect_as --user="postgres" --password="$(cat $PSQL_ROOT_PWD_FILE)" \ - --database="$database" <"$file" + ynh_psql_connect_as --user="postgres" --password="$(cat $PSQL_ROOT_PWD_FILE)" \ + --database="$database" <"$file" } # Create a database and grant optionnaly privilegies to a user @@ -80,17 +80,17 @@ ynh_psql_execute_file_as_root() { # # Requires YunoHost version 3.5.0 or higher. ynh_psql_create_db() { - local db=$1 - local user=${2:-} + local db=$1 + local user=${2:-} - local sql="CREATE DATABASE ${db};" + local sql="CREATE DATABASE ${db};" - # grant all privilegies to user - if [ -n "$user" ]; then - sql+="GRANT ALL PRIVILEGES ON DATABASE ${db} TO ${user} WITH GRANT OPTION;" - fi + # grant all privilegies to user + if [ -n "$user" ]; then + sql+="GRANT ALL PRIVILEGES ON DATABASE ${db} TO ${user} WITH GRANT OPTION;" + fi - ynh_psql_execute_as_root --sql="$sql" + ynh_psql_execute_as_root --sql="$sql" } # Drop a database @@ -105,12 +105,12 @@ ynh_psql_create_db() { # # Requires YunoHost version 3.5.0 or higher. ynh_psql_drop_db() { - local db=$1 - # First, force disconnection of all clients connected to the database - # https://stackoverflow.com/questions/17449420/postgresql-unable-to-drop-database-because-of-some-auto-connections-to-db - ynh_psql_execute_as_root --sql="REVOKE CONNECT ON DATABASE $db FROM public;" --database="$db" - ynh_psql_execute_as_root --sql="SELECT pg_terminate_backend (pg_stat_activity.pid) FROM pg_stat_activity WHERE pg_stat_activity.datname = '$db' AND pid <> pg_backend_pid();" --database="$db" - sudo --login --user=postgres dropdb $db + local db=$1 + # First, force disconnection of all clients connected to the database + # https://stackoverflow.com/questions/17449420/postgresql-unable-to-drop-database-because-of-some-auto-connections-to-db + ynh_psql_execute_as_root --sql="REVOKE CONNECT ON DATABASE $db FROM public;" --database="$db" + ynh_psql_execute_as_root --sql="SELECT pg_terminate_backend (pg_stat_activity.pid) FROM pg_stat_activity WHERE pg_stat_activity.datname = '$db' AND pid <> pg_backend_pid();" --database="$db" + sudo --login --user=postgres dropdb $db } # Dump a database @@ -123,14 +123,14 @@ ynh_psql_drop_db() { # # Requires YunoHost version 3.5.0 or higher. ynh_psql_dump_db() { - # Declare an array to define the options of this helper. - local legacy_args=d - local -A args_array=([d]=database=) - local database - # Manage arguments with getopts - ynh_handle_getopts_args "$@" + # Declare an array to define the options of this helper. + local legacy_args=d + local -A args_array=([d]=database=) + local database + # Manage arguments with getopts + ynh_handle_getopts_args "$@" - sudo --login --user=postgres pg_dump "$database" + sudo --login --user=postgres pg_dump "$database" } # Create a user @@ -143,9 +143,9 @@ ynh_psql_dump_db() { # # Requires YunoHost version 3.5.0 or higher. ynh_psql_create_user() { - local user=$1 - local pwd=$2 - ynh_psql_execute_as_root --sql="CREATE USER $user WITH ENCRYPTED PASSWORD '$pwd'" + local user=$1 + local pwd=$2 + ynh_psql_execute_as_root --sql="CREATE USER $user WITH ENCRYPTED PASSWORD '$pwd'" } # Check if a psql user exists @@ -153,18 +153,18 @@ ynh_psql_create_user() { # usage: ynh_psql_user_exists --user=user # | arg: -u, --user - the user for which to check existence ynh_psql_user_exists() { - # Declare an array to define the options of this helper. - local legacy_args=u - local -A args_array=([u]=user=) - local user - # Manage arguments with getopts - ynh_handle_getopts_args "$@" + # Declare an array to define the options of this helper. + local legacy_args=u + local -A args_array=([u]=user=) + local user + # Manage arguments with getopts + ynh_handle_getopts_args "$@" - if ! sudo --login --user=postgres PGUSER="postgres" PGPASSWORD="$(cat $PSQL_ROOT_PWD_FILE)" psql -tAc "SELECT rolname FROM pg_roles WHERE rolname='$user';" | grep --quiet "$user" ; then - return 1 - else - return 0 - fi + if ! sudo --login --user=postgres PGUSER="postgres" PGPASSWORD="$(cat $PSQL_ROOT_PWD_FILE)" psql -tAc "SELECT rolname FROM pg_roles WHERE rolname='$user';" | grep --quiet "$user" ; then + return 1 + else + return 0 + fi } # Check if a psql database exists @@ -172,18 +172,18 @@ ynh_psql_user_exists() { # usage: ynh_psql_database_exists --database=database # | arg: -d, --database - the database for which to check existence ynh_psql_database_exists() { - # Declare an array to define the options of this helper. - local legacy_args=d - local -A args_array=([d]=database=) - local database - # Manage arguments with getopts - ynh_handle_getopts_args "$@" + # Declare an array to define the options of this helper. + local legacy_args=d + local -A args_array=([d]=database=) + local database + # Manage arguments with getopts + ynh_handle_getopts_args "$@" - if ! sudo --login --user=postgres PGUSER="postgres" PGPASSWORD="$(cat $PSQL_ROOT_PWD_FILE)" psql -tAc "SELECT datname FROM pg_database WHERE datname='$database';" | grep --quiet "$database"; then - return 1 - else - return 0 - fi + if ! sudo --login --user=postgres PGUSER="postgres" PGPASSWORD="$(cat $PSQL_ROOT_PWD_FILE)" psql -tAc "SELECT datname FROM pg_database WHERE datname='$database';" | grep --quiet "$database"; then + return 1 + else + return 0 + fi } # Drop a user @@ -195,7 +195,7 @@ ynh_psql_database_exists() { # # Requires YunoHost version 3.5.0 or higher. ynh_psql_drop_user() { - ynh_psql_execute_as_root --sql="DROP USER ${1};" + ynh_psql_execute_as_root --sql="DROP USER ${1};" } # Create a database, an user and its password. Then store the password in the app's config @@ -208,25 +208,25 @@ ynh_psql_drop_user() { # | arg: -n, --db_name - Name of the database # | arg: -p, --db_pwd - Password of the database. If not given, a password will be generated ynh_psql_setup_db() { - # Declare an array to define the options of this helper. - local legacy_args=unp - local -A args_array=([u]=db_user= [n]=db_name= [p]=db_pwd=) - local db_user - local db_name - db_pwd="" - # Manage arguments with getopts - ynh_handle_getopts_args "$@" + # Declare an array to define the options of this helper. + local legacy_args=unp + local -A args_array=([u]=db_user= [n]=db_name= [p]=db_pwd=) + local db_user + local db_name + db_pwd="" + # Manage arguments with getopts + ynh_handle_getopts_args "$@" - local new_db_pwd=$(ynh_string_random) # Generate a random password - # If $db_pwd is not given, use new_db_pwd instead for db_pwd - db_pwd="${db_pwd:-$new_db_pwd}" + local new_db_pwd=$(ynh_string_random) # Generate a random password + # If $db_pwd is not given, use new_db_pwd instead for db_pwd + db_pwd="${db_pwd:-$new_db_pwd}" - if ! ynh_psql_user_exists --user=$db_user; then - ynh_psql_create_user "$db_user" "$db_pwd" - fi + if ! ynh_psql_user_exists --user=$db_user; then + ynh_psql_create_user "$db_user" "$db_pwd" + fi - ynh_psql_create_db "$db_name" "$db_user" # Create the database - ynh_app_setting_set --app=$app --key=psqlpwd --value=$db_pwd # Store the password in the app's config + ynh_psql_create_db "$db_name" "$db_user" # Create the database + ynh_app_setting_set --app=$app --key=psqlpwd --value=$db_pwd # Store the password in the app's config } # Remove a database if it exists, and the associated user @@ -235,26 +235,26 @@ ynh_psql_setup_db() { # | arg: -u, --db_user - Owner of the database # | arg: -n, --db_name - Name of the database ynh_psql_remove_db() { - # Declare an array to define the options of this helper. - local legacy_args=un - local -A args_array=([u]=db_user= [n]=db_name=) - local db_user - local db_name - # Manage arguments with getopts - ynh_handle_getopts_args "$@" + # Declare an array to define the options of this helper. + local legacy_args=un + local -A args_array=([u]=db_user= [n]=db_name=) + local db_user + local db_name + # Manage arguments with getopts + ynh_handle_getopts_args "$@" - if ynh_psql_database_exists --database=$db_name; then # Check if the database exists - ynh_psql_drop_db $db_name # Remove the database - else - ynh_print_warn --message="Database $db_name not found" - fi + if ynh_psql_database_exists --database=$db_name; then # Check if the database exists + ynh_psql_drop_db $db_name # Remove the database + else + ynh_print_warn --message="Database $db_name not found" + fi - # Remove psql user if it exists - if ynh_psql_user_exists --user=$db_user; then - ynh_psql_drop_user $db_user - else - ynh_print_warn --message="User $db_user not found" - fi + # Remove psql user if it exists + if ynh_psql_user_exists --user=$db_user; then + ynh_psql_drop_user $db_user + else + ynh_print_warn --message="User $db_user not found" + fi } # Create a master password and set up global settings @@ -262,8 +262,8 @@ ynh_psql_remove_db() { # # usage: ynh_psql_test_if_first_run ynh_psql_test_if_first_run() { - if [ -f "$PSQL_ROOT_PWD_FILE" ]; then - echo "PostgreSQL is already installed, no need to create master password" + if [ -f "$PSQL_ROOT_PWD_FILE" ]; then + echo "PostgreSQL is already installed, no need to create master password" return fi diff --git a/data/helpers.d/setting b/data/helpers.d/setting index 5cc5d19dd..874ae44c1 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -122,15 +122,15 @@ EOF # # Requires YunoHost version 2.6.4 or higher. ynh_webpath_available () { - # Declare an array to define the options of this helper. - local legacy_args=dp - local -A args_array=( [d]=domain= [p]=path_url= ) - local domain - local path_url - # Manage arguments with getopts - ynh_handle_getopts_args "$@" + # Declare an array to define the options of this helper. + local legacy_args=dp + local -A args_array=( [d]=domain= [p]=path_url= ) + local domain + local path_url + # Manage arguments with getopts + ynh_handle_getopts_args "$@" - yunohost domain url-available $domain $path_url + yunohost domain url-available $domain $path_url } # Register/book a web path for an app @@ -144,16 +144,16 @@ ynh_webpath_available () { # # Requires YunoHost version 2.6.4 or higher. ynh_webpath_register () { - # Declare an array to define the options of this helper. - local legacy_args=adp - local -A args_array=( [a]=app= [d]=domain= [p]=path_url= ) - local app - local domain - local path_url - # Manage arguments with getopts - ynh_handle_getopts_args "$@" + # Declare an array to define the options of this helper. + local legacy_args=adp + local -A args_array=( [a]=app= [d]=domain= [p]=path_url= ) + local app + local domain + local path_url + # Manage arguments with getopts + ynh_handle_getopts_args "$@" - yunohost app register-url $app $domain $path_url + yunohost app register-url $app $domain $path_url } # Create a new permission for the app @@ -178,8 +178,8 @@ ynh_webpath_register () { # # Requires YunoHost version 3.7.0 or higher. ynh_permission_create() { - # Declare an array to define the options of this helper. - local legacy_args=pua + # Declare an array to define the options of this helper. + local legacy_args=pua local -A args_array=( [p]=permission= [u]=url= [a]=allowed= ) local permission local url @@ -208,8 +208,8 @@ ynh_permission_create() { # # Requires YunoHost version 3.7.0 or higher. ynh_permission_delete() { - # Declare an array to define the options of this helper. - local legacy_args=p + # Declare an array to define the options of this helper. + local legacy_args=p local -A args_array=( [p]=permission= ) local permission ynh_handle_getopts_args "$@" @@ -224,8 +224,8 @@ ynh_permission_delete() { # # Requires YunoHost version 3.7.0 or higher. ynh_permission_exists() { - # Declare an array to define the options of this helper. - local legacy_args=p + # Declare an array to define the options of this helper. + local legacy_args=p local -A args_array=( [p]=permission= ) local permission ynh_handle_getopts_args "$@" @@ -241,8 +241,8 @@ ynh_permission_exists() { # # Requires YunoHost version 3.7.0 or higher. ynh_permission_url() { - # Declare an array to define the options of this helper. - local legacy_args=pu + # Declare an array to define the options of this helper. + local legacy_args=pu local -A args_array=([p]=permission= [u]=url=) local permission local url @@ -268,8 +268,8 @@ ynh_permission_url() { # example: ynh_permission_update --permission admin --add samdoe --remove all_users # Requires YunoHost version 3.7.0 or higher. ynh_permission_update() { - # Declare an array to define the options of this helper. - local legacy_args=par + # Declare an array to define the options of this helper. + local legacy_args=par local -A args_array=( [p]=permission= [a]=add= [r]=remove= ) local permission local add diff --git a/data/helpers.d/string b/data/helpers.d/string index 9b8437953..76c3b37e4 100644 --- a/data/helpers.d/string +++ b/data/helpers.d/string @@ -18,8 +18,8 @@ ynh_string_random() { length=${length:-24} dd if=/dev/urandom bs=1 count=1000 2> /dev/null \ - | tr -c -d 'A-Za-z0-9' \ - | sed -n 's/\(.\{'"$length"'\}\).*/\1/p' + | tr -c -d 'A-Za-z0-9' \ + | sed -n 's/\(.\{'"$length"'\}\).*/\1/p' } # Substitute/replace a string (or expression) by another in a file @@ -35,21 +35,21 @@ ynh_string_random() { # # Requires YunoHost version 2.6.4 or higher. ynh_replace_string () { - # Declare an array to define the options of this helper. - local legacy_args=mrf - local -A args_array=( [m]=match_string= [r]=replace_string= [f]=target_file= ) - local match_string - local replace_string - local target_file - # Manage arguments with getopts - ynh_handle_getopts_args "$@" + # Declare an array to define the options of this helper. + local legacy_args=mrf + local -A args_array=( [m]=match_string= [r]=replace_string= [f]=target_file= ) + local match_string + local replace_string + local target_file + # Manage arguments with getopts + ynh_handle_getopts_args "$@" - local delimit=@ - # Escape the delimiter if it's in the string. - match_string=${match_string//${delimit}/"\\${delimit}"} - replace_string=${replace_string//${delimit}/"\\${delimit}"} + local delimit=@ + # Escape the delimiter if it's in the string. + match_string=${match_string//${delimit}/"\\${delimit}"} + replace_string=${replace_string//${delimit}/"\\${delimit}"} - sed --in-place "s${delimit}${match_string}${delimit}${replace_string}${delimit}g" "$target_file" + sed --in-place "s${delimit}${match_string}${delimit}${replace_string}${delimit}g" "$target_file" } # Substitute/replace a special string by another in a file @@ -64,24 +64,24 @@ ynh_replace_string () { # # Requires YunoHost version 2.7.7 or higher. ynh_replace_special_string () { - # Declare an array to define the options of this helper. - local legacy_args=mrf - local -A args_array=( [m]=match_string= [r]=replace_string= [f]=target_file= ) - local match_string - local replace_string - local target_file - # Manage arguments with getopts - ynh_handle_getopts_args "$@" + # Declare an array to define the options of this helper. + local legacy_args=mrf + local -A args_array=( [m]=match_string= [r]=replace_string= [f]=target_file= ) + local match_string + local replace_string + local target_file + # Manage arguments with getopts + ynh_handle_getopts_args "$@" - # Escape any backslash to preserve them as simple backslash. - match_string=${match_string//\\/"\\\\"} - replace_string=${replace_string//\\/"\\\\"} + # Escape any backslash to preserve them as simple backslash. + match_string=${match_string//\\/"\\\\"} + replace_string=${replace_string//\\/"\\\\"} - # Escape the & character, who has a special function in sed. - match_string=${match_string//&/"\&"} - replace_string=${replace_string//&/"\&"} + # Escape the & character, who has a special function in sed. + match_string=${match_string//&/"\&"} + replace_string=${replace_string//&/"\&"} - ynh_replace_string --match_string="$match_string" --replace_string="$replace_string" --target_file="$target_file" + ynh_replace_string --match_string="$match_string" --replace_string="$replace_string" --target_file="$target_file" } # Sanitize a string intended to be the name of a database @@ -95,15 +95,15 @@ ynh_replace_special_string () { # # Requires YunoHost version 2.2.4 or higher. ynh_sanitize_dbid () { - # Declare an array to define the options of this helper. - local legacy_args=n - local -A args_array=( [n]=db_name= ) - local db_name - # Manage arguments with getopts - ynh_handle_getopts_args "$@" + # Declare an array to define the options of this helper. + local legacy_args=n + local -A args_array=( [n]=db_name= ) + local db_name + # Manage arguments with getopts + ynh_handle_getopts_args "$@" - # We should avoid having - and . in the name of databases. They are replaced by _ - echo ${db_name//[-.]/_} + # We should avoid having - and . in the name of databases. They are replaced by _ + echo ${db_name//[-.]/_} } # Normalize the url path syntax @@ -123,19 +123,19 @@ ynh_sanitize_dbid () { # # Requires YunoHost version 2.6.4 or higher. ynh_normalize_url_path () { - # Declare an array to define the options of this helper. - local legacy_args=p - local -A args_array=( [p]=path_url= ) - local path_url - # Manage arguments with getopts - ynh_handle_getopts_args "$@" + # Declare an array to define the options of this helper. + local legacy_args=p + local -A args_array=( [p]=path_url= ) + local path_url + # Manage arguments with getopts + ynh_handle_getopts_args "$@" - test -n "$path_url" || ynh_die --message="ynh_normalize_url_path expect a URL path as first argument and received nothing." - if [ "${path_url:0:1}" != "/" ]; then # If the first character is not a / - path_url="/$path_url" # Add / at begin of path variable - fi - if [ "${path_url:${#path_url}-1}" == "/" ] && [ ${#path_url} -gt 1 ]; then # If the last character is a / and that not the only character. - path_url="${path_url:0:${#path_url}-1}" # Delete the last character - fi - echo $path_url + test -n "$path_url" || ynh_die --message="ynh_normalize_url_path expect a URL path as first argument and received nothing." + if [ "${path_url:0:1}" != "/" ]; then # If the first character is not a / + path_url="/$path_url" # Add / at begin of path variable + fi + if [ "${path_url:${#path_url}-1}" == "/" ] && [ ${#path_url} -gt 1 ]; then # If the last character is a / and that not the only character. + path_url="${path_url:0:${#path_url}-1}" # Delete the last character + fi + echo $path_url } diff --git a/data/helpers.d/systemd b/data/helpers.d/systemd index 276674e70..9ab3ff150 100644 --- a/data/helpers.d/systemd +++ b/data/helpers.d/systemd @@ -16,33 +16,33 @@ # # Requires YunoHost version 2.7.2 or higher. ynh_add_systemd_config () { - # Declare an array to define the options of this helper. - local legacy_args=st - local -A args_array=( [s]=service= [t]=template= ) - local service - local template - # Manage arguments with getopts - ynh_handle_getopts_args "$@" - local service="${service:-$app}" - local template="${template:-systemd.service}" + # Declare an array to define the options of this helper. + local legacy_args=st + local -A args_array=( [s]=service= [t]=template= ) + local service + local template + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + local service="${service:-$app}" + local template="${template:-systemd.service}" - finalsystemdconf="/etc/systemd/system/$service.service" - ynh_backup_if_checksum_is_different --file="$finalsystemdconf" - cp ../conf/$template "$finalsystemdconf" + finalsystemdconf="/etc/systemd/system/$service.service" + ynh_backup_if_checksum_is_different --file="$finalsystemdconf" + cp ../conf/$template "$finalsystemdconf" - # To avoid a break by set -u, use a void substitution ${var:-}. If the variable is not set, it's simply set with an empty variable. - # Substitute in a nginx config file only if the variable is not empty - if test -n "${final_path:-}"; then - ynh_replace_string --match_string="__FINALPATH__" --replace_string="$final_path" --target_file="$finalsystemdconf" - fi - if test -n "${app:-}"; then - ynh_replace_string --match_string="__APP__" --replace_string="$app" --target_file="$finalsystemdconf" - fi - ynh_store_file_checksum --file="$finalsystemdconf" + # To avoid a break by set -u, use a void substitution ${var:-}. If the variable is not set, it's simply set with an empty variable. + # Substitute in a nginx config file only if the variable is not empty + if test -n "${final_path:-}"; then + ynh_replace_string --match_string="__FINALPATH__" --replace_string="$final_path" --target_file="$finalsystemdconf" + fi + if test -n "${app:-}"; then + ynh_replace_string --match_string="__APP__" --replace_string="$app" --target_file="$finalsystemdconf" + fi + ynh_store_file_checksum --file="$finalsystemdconf" - chown root: "$finalsystemdconf" - systemctl enable $service - systemctl daemon-reload + chown root: "$finalsystemdconf" + systemctl enable $service + systemctl daemon-reload } # Remove the dedicated systemd config @@ -52,21 +52,21 @@ ynh_add_systemd_config () { # # Requires YunoHost version 2.7.2 or higher. ynh_remove_systemd_config () { - # Declare an array to define the options of this helper. - local legacy_args=s - local -A args_array=( [s]=service= ) - local service - # Manage arguments with getopts - ynh_handle_getopts_args "$@" - local service="${service:-$app}" + # Declare an array to define the options of this helper. + local legacy_args=s + local -A args_array=( [s]=service= ) + local service + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + local service="${service:-$app}" - local finalsystemdconf="/etc/systemd/system/$service.service" - if [ -e "$finalsystemdconf" ]; then - ynh_systemd_action --service_name=$service --action=stop - systemctl disable $service - ynh_secure_remove --file="$finalsystemdconf" - systemctl daemon-reload - fi + local finalsystemdconf="/etc/systemd/system/$service.service" + if [ -e "$finalsystemdconf" ]; then + ynh_systemd_action --service_name=$service --action=stop + systemctl disable $service + ynh_secure_remove --file="$finalsystemdconf" + systemctl daemon-reload + fi } # Start (or other actions) a service, print a log in case of failure and optionnaly wait until the service is completely started @@ -172,7 +172,7 @@ ynh_clean_check_starting () { fi if [ -n "$templog" ] then - ynh_secure_remove "$templog" 2>&1 + ynh_secure_remove "$templog" 2>&1 fi } diff --git a/data/helpers.d/user b/data/helpers.d/user index 72cb9bece..9d9bc9089 100644 --- a/data/helpers.d/user +++ b/data/helpers.d/user @@ -51,7 +51,7 @@ ynh_user_get_info() { # Requires YunoHost version 2.4.0 or higher. ynh_user_list() { yunohost user list --output-as plain --quiet \ - | awk '/^##username$/{getline; print}' + | awk '/^##username$/{getline; print}' } # Check if a user exists on the system @@ -101,31 +101,31 @@ ynh_system_group_exists() { # # Requires YunoHost version 2.6.4 or higher. ynh_system_user_create () { - # Declare an array to define the options of this helper. - local legacy_args=uhs - local -A args_array=( [u]=username= [h]=home_dir= [s]=use_shell ) - local username - local home_dir - local use_shell - # Manage arguments with getopts - ynh_handle_getopts_args "$@" - use_shell="${use_shell:-0}" - home_dir="${home_dir:-}" + # Declare an array to define the options of this helper. + local legacy_args=uhs + local -A args_array=( [u]=username= [h]=home_dir= [s]=use_shell ) + local username + local home_dir + local use_shell + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + use_shell="${use_shell:-0}" + home_dir="${home_dir:-}" - if ! ynh_system_user_exists "$username" # Check if the user exists on the system - then # If the user doesn't exist - if [ -n "$home_dir" ]; then # If a home dir is mentioned - local user_home_dir="-d $home_dir" - else - local user_home_dir="--no-create-home" - fi - if [ $use_shell -eq 1 ]; then # If we want a shell for the user - local shell="" # Use default shell - else - local shell="--shell /usr/sbin/nologin" - fi - useradd $user_home_dir --system --user-group $username $shell || ynh_die "Unable to create $username system account" - fi + if ! ynh_system_user_exists "$username" # Check if the user exists on the system + then # If the user doesn't exist + if [ -n "$home_dir" ]; then # If a home dir is mentioned + local user_home_dir="-d $home_dir" + else + local user_home_dir="--no-create-home" + fi + if [ $use_shell -eq 1 ]; then # If we want a shell for the user + local shell="" # Use default shell + else + local shell="--shell /usr/sbin/nologin" + fi + useradd $user_home_dir --system --user-group $username $shell || ynh_die "Unable to create $username system account" + fi } # Delete a system user @@ -145,14 +145,14 @@ ynh_system_user_delete () { # Check if the user exists on the system if ynh_system_user_exists "$username" then - deluser $username - else - ynh_print_warn --message="The user $username was not found" + deluser $username + else + ynh_print_warn --message="The user $username was not found" fi # Check if the group exists on the system if ynh_system_group_exists "$username" then - delgroup $username + delgroup $username fi } diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 5f352ab96..bdc8ee849 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -17,22 +17,22 @@ # It prints a warning to inform that the script was failed, and execute the ynh_clean_setup function if used in the app script # ynh_exit_properly () { - local exit_code=$? - if [ "$exit_code" -eq 0 ]; then - exit 0 # Exit without error if the script ended correctly - fi + local exit_code=$? + if [ "$exit_code" -eq 0 ]; then + exit 0 # Exit without error if the script ended correctly + fi - trap '' EXIT # Ignore new exit signals - set +eu # Do not exit anymore if a command fail or if a variable is empty + trap '' EXIT # Ignore new exit signals + set +eu # Do not exit anymore if a command fail or if a variable is empty - # Small tempo to avoid the next message being mixed up with other DEBUG messages - sleep 0.5 + # Small tempo to avoid the next message being mixed up with other DEBUG messages + sleep 0.5 - if type -t ynh_clean_setup > /dev/null; then # Check if the function exist in the app script. - ynh_clean_setup # Call the function to do specific cleaning for the app. - fi + if type -t ynh_clean_setup > /dev/null; then # Check if the function exist in the app script. + ynh_clean_setup # Call the function to do specific cleaning for the app. + fi - ynh_die # Exit with error status + ynh_die # Exit with error status } # Exits if an error occurs during the execution of the script. @@ -46,8 +46,8 @@ ynh_exit_properly () { # # Requires YunoHost version 2.6.4 or higher. ynh_abort_if_errors () { - set -eu # Exit if a command fail, and if a variable is used unset. - trap ynh_exit_properly EXIT # Capturing exit signals on shell script + set -eu # Exit if a command fail, and if a variable is used unset. + trap ynh_exit_properly EXIT # Capturing exit signals on shell script } # Download, check integrity, uncompress and patch the source from app.src @@ -256,13 +256,13 @@ ynh_local_curl () { # | arg: some_template - Template file to be rendered # | arg: output_path - The path where the output will be redirected to ynh_render_template() { - local template_path=$1 - local output_path=$2 - mkdir -p "$(dirname $output_path)" - # Taken from https://stackoverflow.com/a/35009576 - python2.7 -c 'import os, sys, jinja2; sys.stdout.write( + local template_path=$1 + local output_path=$2 + mkdir -p "$(dirname $output_path)" + # Taken from https://stackoverflow.com/a/35009576 + python2.7 -c 'import os, sys, jinja2; sys.stdout.write( jinja2.Template(sys.stdin.read() - ).render(os.environ));' < $template_path > $output_path + ).render(os.environ));' < $template_path > $output_path } # Fetch the Debian release codename @@ -272,7 +272,7 @@ ynh_render_template() { # # Requires YunoHost version 2.7.12 or higher. ynh_get_debian_release () { - echo $(lsb_release --codename --short) + echo $(lsb_release --codename --short) } # Create a directory under /tmp @@ -376,20 +376,20 @@ ynh_get_plain_key() { # # Requires YunoHost version 3.5.0 or higher. ynh_read_manifest () { - # Declare an array to define the options of this helper. - local legacy_args=mk - local -A args_array=( [m]=manifest= [k]=manifest_key= ) - local manifest - local manifest_key - # Manage arguments with getopts - ynh_handle_getopts_args "$@" + # Declare an array to define the options of this helper. + local legacy_args=mk + local -A args_array=( [m]=manifest= [k]=manifest_key= ) + local manifest + local manifest_key + # Manage arguments with getopts + ynh_handle_getopts_args "$@" - if [ ! -e "$manifest" ]; then - # If the manifest isn't found, try the common place for backup and restore script. - manifest="../settings/manifest.json" - fi + if [ ! -e "$manifest" ]; then + # If the manifest isn't found, try the common place for backup and restore script. + manifest="../settings/manifest.json" + fi - jq ".$manifest_key" "$manifest" --raw-output + jq ".$manifest_key" "$manifest" --raw-output } # Read the upstream version from the manifest @@ -458,32 +458,32 @@ ynh_app_package_version () { # # Requires YunoHost version 3.5.0 or higher. ynh_check_app_version_changed () { - local force_upgrade=${YNH_FORCE_UPGRADE:-0} - local package_check=${PACKAGE_CHECK_EXEC:-0} + local force_upgrade=${YNH_FORCE_UPGRADE:-0} + local package_check=${PACKAGE_CHECK_EXEC:-0} - # By default, upstream app version has changed - local return_value="UPGRADE_APP" + # By default, upstream app version has changed + local return_value="UPGRADE_APP" - local current_version=$(ynh_read_manifest --manifest="/etc/yunohost/apps/$YNH_APP_INSTANCE_NAME/manifest.json" --manifest_key="version" || echo 1.0) - local current_upstream_version="$(ynh_app_upstream_version --manifest="/etc/yunohost/apps/$YNH_APP_INSTANCE_NAME/manifest.json")" - local update_version=$(ynh_read_manifest --manifest="../manifest.json" --manifest_key="version" || echo 1.0) - local update_upstream_version="$(ynh_app_upstream_version)" + local current_version=$(ynh_read_manifest --manifest="/etc/yunohost/apps/$YNH_APP_INSTANCE_NAME/manifest.json" --manifest_key="version" || echo 1.0) + local current_upstream_version="$(ynh_app_upstream_version --manifest="/etc/yunohost/apps/$YNH_APP_INSTANCE_NAME/manifest.json")" + local update_version=$(ynh_read_manifest --manifest="../manifest.json" --manifest_key="version" || echo 1.0) + local update_upstream_version="$(ynh_app_upstream_version)" - if [ "$current_version" == "$update_version" ] ; then - # Complete versions are the same - if [ "$force_upgrade" != "0" ] - then - ynh_print_info --message="Upgrade forced by YNH_FORCE_UPGRADE." - unset YNH_FORCE_UPGRADE - elif [ "$package_check" != "0" ] - then - ynh_print_info --message="Upgrade forced for package check." - else - ynh_die "Up-to-date, nothing to do" 0 - fi - elif [ "$current_upstream_version" == "$update_upstream_version" ] ; then - # Upstream versions are the same, only YunoHost package versions differ - return_value="UPGRADE_PACKAGE" - fi - echo $return_value + if [ "$current_version" == "$update_version" ] ; then + # Complete versions are the same + if [ "$force_upgrade" != "0" ] + then + ynh_print_info --message="Upgrade forced by YNH_FORCE_UPGRADE." + unset YNH_FORCE_UPGRADE + elif [ "$package_check" != "0" ] + then + ynh_print_info --message="Upgrade forced for package check." + else + ynh_die "Up-to-date, nothing to do" 0 + fi + elif [ "$current_upstream_version" == "$update_upstream_version" ] ; then + # Upstream versions are the same, only YunoHost package versions differ + return_value="UPGRADE_PACKAGE" + fi + echo $return_value } From 57061b8e1db1171ddebf4f8a7ffdad25a1bb11f7 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sun, 19 Apr 2020 20:31:06 +0200 Subject: [PATCH 1044/3170] Unfold if-then when more than one line --- data/helpers.d/apt | 5 +++-- data/helpers.d/backup | 22 ++++++++++++++-------- data/helpers.d/fail2ban | 14 ++++++++------ data/helpers.d/getopts | 6 ++++-- data/helpers.d/logrotate | 15 ++++++++++----- data/helpers.d/mysql | 6 ++++-- data/helpers.d/nginx | 3 ++- data/helpers.d/php | 21 ++++++++++++++------- data/helpers.d/postgresql | 21 ++++++++++++++------- data/helpers.d/setting | 6 ++++-- data/helpers.d/systemd | 6 ++++-- data/helpers.d/user | 6 ++++-- data/helpers.d/utils | 35 ++++++++++++++++++++++------------- 13 files changed, 107 insertions(+), 59 deletions(-) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index 4093e593f..a1eb6f470 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -78,7 +78,8 @@ ynh_package_version() { # Manage arguments with getopts ynh_handle_getopts_args "$@" - if ynh_package_is_installed "$package"; then + if ynh_package_is_installed "$package" + then dpkg-query -W -f '${Version}' "$package" 2>/dev/null else echo '' @@ -251,7 +252,7 @@ ynh_install_app_dependencies () { # https://github.com/YunoHost/issues/issues/1407 # # If we require to install php dependency - if echo $dependencies | grep -q 'php'; + if echo $dependencies | grep -q 'php' then # And we have packages from sury installed (7.0.33-10+weirdshiftafter instead of 7.0.33-0 on debian) if dpkg --list | grep "php7.0" | grep -q -v "7.0.33-0+deb9" diff --git a/data/helpers.d/backup b/data/helpers.d/backup index bb676a0e0..096804380 100644 --- a/data/helpers.d/backup +++ b/data/helpers.d/backup @@ -64,7 +64,8 @@ ynh_backup() { # don't backup big data items if [ $is_big -eq 1 ] && ( [ ${do_not_backup_data:-0} -eq 1 ] || [ $BACKUP_CORE_ONLY -eq 1 ] ) then - if [ $BACKUP_CORE_ONLY -eq 1 ]; then + if [ $BACKUP_CORE_ONLY -eq 1 ] + then ynh_print_warn --message="$src_path will not be saved, because 'BACKUP_CORE_ONLY' is set." else ynh_print_warn --message="$src_path will not be saved, because 'do_not_backup_data' is set." @@ -100,12 +101,13 @@ ynh_backup() { # If there is no destination path, initialize it with the source path # relative to "/". # eg: src_path=/etc/yunohost -> dest_path=etc/yunohost - if [[ -z "$dest_path" ]]; then - + if [[ -z "$dest_path" ]] + then dest_path="${src_path#/}" else - if [[ "${dest_path:0:1}" == "/" ]]; then + if [[ "${dest_path:0:1}" == "/" ]] + then # If the destination path is an absolute path, transform it as a path # relative to the current working directory ($YNH_CWD) @@ -165,7 +167,8 @@ ynh_restore () { # For each destination path begining by $REL_DIR cat ${YNH_BACKUP_CSV} | tr -d $'\r' | grep -ohP "^\".*\",\"$REL_DIR.*\"$" | \ - while read line; do + while read line + do local ORIGIN_PATH=$(echo "$line" | grep -ohP "^\"\K.*(?=\",\".*\"$)") local ARCHIVE_PATH=$(echo "$line" | grep -ohP "^\".*\",\"$REL_DIR\K.*(?=\"$)") ynh_restore_file --origin_path="$ARCHIVE_PATH" --dest_path="$ORIGIN_PATH" @@ -234,7 +237,8 @@ ynh_restore_file () { local not_mandatory="${not_mandatory:-0}" # If archive_path doesn't exist, search for a corresponding path in CSV - if [ ! -d "$archive_path" ] && [ ! -f "$archive_path" ] && [ ! -L "$archive_path" ]; then + if [ ! -d "$archive_path" ] && [ ! -f "$archive_path" ] && [ ! -L "$archive_path" ] + then if [ "$not_mandatory" == "0" ] then archive_path="$YNH_BACKUP_DIR/$(_get_archive_path \"$origin_path\")" @@ -261,8 +265,10 @@ ynh_restore_file () { mkdir -p $(dirname "$dest_path") # Do a copy if it's just a mounting point - if mountpoint -q $YNH_BACKUP_DIR; then - if [[ -d "${archive_path}" ]]; then + if mountpoint -q $YNH_BACKUP_DIR + then + if [[ -d "${archive_path}" ]] + then archive_path="${archive_path}/." mkdir -p "$dest_path" fi diff --git a/data/helpers.d/fail2ban b/data/helpers.d/fail2ban index 2c17e1300..438b3b355 100644 --- a/data/helpers.d/fail2ban +++ b/data/helpers.d/fail2ban @@ -96,7 +96,8 @@ ynh_add_fail2ban_config () { fi # Replace all other variable given as arguments - for var_to_replace in ${others_var:-}; do + for var_to_replace in ${others_var:-} + do # ${var_to_replace^^} make the content of the variable on upper-cases # ${!var_to_replace} get the content of the variable named $var_to_replace ynh_replace_string --match_string="__${var_to_replace^^}__" --replace_string="${!var_to_replace}" --target_file="$finalfail2banjailconf" @@ -104,11 +105,11 @@ ynh_add_fail2ban_config () { done else - # Usage 1, no template. Build a config file from scratch. - test -n "$logpath" || ynh_die "ynh_add_fail2ban_config expects a logfile path as first argument and received nothing." - test -n "$failregex" || ynh_die "ynh_add_fail2ban_config expects a failure regex as second argument and received nothing." + # Usage 1, no template. Build a config file from scratch. + test -n "$logpath" || ynh_die "ynh_add_fail2ban_config expects a logfile path as first argument and received nothing." + test -n "$failregex" || ynh_die "ynh_add_fail2ban_config expects a failure regex as second argument and received nothing." - tee $finalfail2banjailconf <$PSQL_ROOT_PWD_FILE - if [ -e /etc/postgresql/9.4/ ]; then + if [ -e /etc/postgresql/9.4/ ] + then local pg_hba=/etc/postgresql/9.4/main/pg_hba.conf local logfile=/var/log/postgresql/postgresql-9.4-main.log - elif [ -e /etc/postgresql/9.6/ ]; then + elif [ -e /etc/postgresql/9.6/ ] + then local pg_hba=/etc/postgresql/9.6/main/pg_hba.conf local logfile=/var/log/postgresql/postgresql-9.6-main.log else diff --git a/data/helpers.d/setting b/data/helpers.d/setting index 874ae44c1..eca529069 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -186,7 +186,8 @@ ynh_permission_create() { local allowed ynh_handle_getopts_args "$@" - if [[ -n ${url:-} ]]; then + if [[ -n ${url:-} ]] + then url="'$url'" else url="None" @@ -248,7 +249,8 @@ ynh_permission_url() { local url ynh_handle_getopts_args "$@" - if [[ -n ${url:-} ]]; then + if [[ -n ${url:-} ]] + then url="'$url'" else url="None" diff --git a/data/helpers.d/systemd b/data/helpers.d/systemd index 9ab3ff150..6257b9138 100644 --- a/data/helpers.d/systemd +++ b/data/helpers.d/systemd @@ -61,7 +61,8 @@ ynh_remove_systemd_config () { local service="${service:-$app}" local finalsystemdconf="/etc/systemd/system/$service.service" - if [ -e "$finalsystemdconf" ]; then + if [ -e "$finalsystemdconf" ] + then ynh_systemd_action --service_name=$service --action=stop systemctl disable $service ynh_secure_remove --file="$finalsystemdconf" @@ -103,7 +104,8 @@ ynh_systemd_action() { then local templog="$(mktemp)" # Following the starting of the app in its log - if [ "$log_path" == "systemd" ] ; then + if [ "$log_path" == "systemd" ] + then # Read the systemd journal journalctl --unit=$service_name --follow --since=-0 --quiet > "$templog" & # Get the PID of the journalctl command diff --git a/data/helpers.d/user b/data/helpers.d/user index 9d9bc9089..0b964c7c0 100644 --- a/data/helpers.d/user +++ b/data/helpers.d/user @@ -114,12 +114,14 @@ ynh_system_user_create () { if ! ynh_system_user_exists "$username" # Check if the user exists on the system then # If the user doesn't exist - if [ -n "$home_dir" ]; then # If a home dir is mentioned + if [ -n "$home_dir" ] + then # If a home dir is mentioned local user_home_dir="-d $home_dir" else local user_home_dir="--no-create-home" fi - if [ $use_shell -eq 1 ]; then # If we want a shell for the user + if [ $use_shell -eq 1 ] + then # If we want a shell for the user local shell="" # Use default shell else local shell="--shell /usr/sbin/nologin" diff --git a/data/helpers.d/utils b/data/helpers.d/utils index bdc8ee849..2ad05f93c 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -129,7 +129,7 @@ ynh_setup_source () { src_format=${src_format:-tar.gz} src_format=$(echo "$src_format" | tr '[:upper:]' '[:lower:]') src_extract=${src_extract:-true} - if [ "$src_filename" = "" ] ; then + if [ "$src_filename" = "" ]; then src_filename="${source_id}.${src_format}" fi local local_src="/opt/yunohost-apps-src/${YNH_APP_ID}/${src_filename}" @@ -155,7 +155,8 @@ ynh_setup_source () { then # Zip format # Using of a temp directory, because unzip doesn't manage --strip-components - if $src_in_subdir ; then + if $src_in_subdir + then local tmp_dir=$(mktemp -d) unzip -quo $src_filename -d "$tmp_dir" cp -a $tmp_dir/*/. "$dest_dir" @@ -167,14 +168,16 @@ ynh_setup_source () { local strip="" if [ "$src_in_subdir" != "false" ] then - if [ "$src_in_subdir" == "true" ]; then + if [ "$src_in_subdir" == "true" ] + then local sub_dirs=1 else local sub_dirs="$src_in_subdir" fi strip="--strip-components $sub_dirs" fi - if [[ "$src_format" =~ ^tar.gz|tar.bz2|tar.xz$ ]] ; then + if [[ "$src_format" =~ ^tar.gz|tar.bz2|tar.xz$ ]] + then tar -xf $src_filename -C "$dest_dir" $strip else ynh_die --message="Archive format unrecognized." @@ -321,7 +324,7 @@ ynh_secure_remove () { if [[ -z "$file" ]] then ynh_print_warn --message="ynh_secure_remove called with empty argument, ignoring." - else if [[ "$forbidden_path" =~ "$file" \ + elif [[ "$forbidden_path" =~ "$file" \ # Match all paths or subpaths in $forbidden_path || "$file" =~ ^/[[:alnum:]]+$ \ # Match all first level paths from / (Like /var, /root, etc...) @@ -329,12 +332,12 @@ ynh_secure_remove () { # Match if the path finishes by /. Because it seems there is an empty variable then ynh_print_warn --message="Not deleting '$file' because it is not an acceptable path to delete." - else if [ -e "$file" ] + elif [ -e "$file" ] then rm -R "$file" else ynh_print_info --message="'$file' wasn't deleted because it doesn't exist." - fi fi fi + fi } # Extract a key from a plain command output @@ -352,12 +355,16 @@ ynh_get_plain_key() { # an info to be redacted by the core local key_=$1 shift - while read line; do - if [[ "$founded" == "1" ]] ; then + while read line + do + if [[ "$founded" == "1" ]] + then [[ "$line" =~ ^${prefix}[^#] ]] && return echo $line - elif [[ "$line" =~ ^${prefix}${key_}$ ]]; then - if [[ -n "${1:-}" ]]; then + elif [[ "$line" =~ ^${prefix}${key_}$ ]] + then + if [[ -n "${1:-}" ]] + then prefix+="#" key_=$1 shift @@ -469,7 +476,8 @@ ynh_check_app_version_changed () { local update_version=$(ynh_read_manifest --manifest="../manifest.json" --manifest_key="version" || echo 1.0) local update_upstream_version="$(ynh_app_upstream_version)" - if [ "$current_version" == "$update_version" ] ; then + if [ "$current_version" == "$update_version" ] + then # Complete versions are the same if [ "$force_upgrade" != "0" ] then @@ -481,7 +489,8 @@ ynh_check_app_version_changed () { else ynh_die "Up-to-date, nothing to do" 0 fi - elif [ "$current_upstream_version" == "$update_upstream_version" ] ; then + elif [ "$current_upstream_version" == "$update_upstream_version" ] + then # Upstream versions are the same, only YunoHost package versions differ return_value="UPGRADE_PACKAGE" fi From 4f56f03e3258202deeb0fc8f784c611e1722f937 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sun, 19 Apr 2020 23:19:57 +0200 Subject: [PATCH 1045/3170] Update nodejs --- data/helpers.d/nodejs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data/helpers.d/nodejs b/data/helpers.d/nodejs index 3e7ac5da2..32cfb5680 100644 --- a/data/helpers.d/nodejs +++ b/data/helpers.d/nodejs @@ -35,7 +35,8 @@ SOURCE_SUM=3983fa3f00d4bf85ba8e21f1a590f6e28938093abe0bb950aeea52b1717471fc" > " # For example: use `ynh_npm install` instead of `npm install` # # With `sudo` or `ynh_exec_as`, use instead the fallback variables `$ynh_npm` and `$ynh_node` -# Exemple: `ynh_exec_as $app $ynh_npm install` +# And propagate $PATH to sudo with $ynh_node_load_PATH +# Exemple: `ynh_exec_as $app $ynh_node_load_PATH $ynh_npm install` # # $PATH contains the path of the requested version of node. # However, $PATH is duplicated into $node_PATH to outlast any manipulation of $PATH From b0398ae6dce76fa13d762bc544db7662c477a332 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Mon, 20 Apr 2020 15:20:31 +0200 Subject: [PATCH 1046/3170] Use long arguments instead of short ones --- data/helpers.d/apt | 52 ++++++++++++++++++++-------------------- data/helpers.d/backup | 38 ++++++++++++++--------------- data/helpers.d/fail2ban | 2 +- data/helpers.d/getopts | 4 ++-- data/helpers.d/logging | 18 +++++++------- data/helpers.d/logrotate | 4 ++-- data/helpers.d/mysql | 6 ++--- data/helpers.d/network | 4 ++-- data/helpers.d/nodejs | 8 +++---- data/helpers.d/php | 4 ++-- data/helpers.d/setting | 4 ++-- data/helpers.d/string | 4 ++-- data/helpers.d/systemd | 4 ++-- data/helpers.d/user | 4 ++-- data/helpers.d/utils | 41 ++++++++++++++++--------------- 15 files changed, 100 insertions(+), 97 deletions(-) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index a1eb6f470..9d2c2b64c 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -27,7 +27,7 @@ ynh_wait_dpkg_free() { while read dpkg_file <&9 do # Check if the name of this file contains only numbers. - if echo "$dpkg_file" | grep -Pq "^[[:digit:]]+$" + if echo "$dpkg_file" | grep --perl-regexp --quiet "^[[:digit:]]+$" then # If so, that a remaining of dpkg. ynh_print_err "E: dpkg was interrupted, you must manually run 'sudo dpkg --configure -a' to correct the problem." @@ -57,8 +57,8 @@ ynh_package_is_installed() { ynh_handle_getopts_args "$@" ynh_wait_dpkg_free - dpkg-query -W -f '${Status}' "$package" 2>/dev/null \ - | grep -c "ok installed" &>/dev/null + dpkg-query --show --showformat='${Status}' "$package" 2>/dev/null \ + | grep --count "ok installed" &>/dev/null } # Get the version of an installed package @@ -80,7 +80,7 @@ ynh_package_version() { if ynh_package_is_installed "$package" then - dpkg-query -W -f '${Version}' "$package" 2>/dev/null + dpkg-query --show --showformat='${Version}' "$package" 2>/dev/null else echo '' fi @@ -95,7 +95,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 -y $@ + LC_ALL=C DEBIAN_FRONTEND=noninteractive apt-get --assume-yes $@ } # Update package index files @@ -114,8 +114,8 @@ ynh_package_update() { # # Requires YunoHost version 2.2.4 or higher. ynh_package_install() { - ynh_apt --no-remove -o Dpkg::Options::=--force-confdef \ - -o Dpkg::Options::=--force-confold install $@ + ynh_apt --no-remove --option Dpkg::Options::=--force-confdef \ + --option Dpkg::Options::=--force-confold install $@ } # Remove package(s) @@ -164,8 +164,8 @@ ynh_package_install_from_equivs () { local controlfile=$1 # retrieve package information - local pkgname=$(grep '^Package: ' $controlfile | cut -d' ' -f 2) # Retrieve the name of the debian package - local pkgversion=$(grep '^Version: ' $controlfile | cut -d' ' -f 2) # And its version number + local pkgname=$(grep '^Package: ' $controlfile | cut --delimiter=' ' --fields=2) # Retrieve the name of the debian package + local pkgversion=$(grep '^Version: ' $controlfile | cut --delimiter=' ' --fields=2) # And its version number [[ -z "$pkgname" || -z "$pkgversion" ]] \ && ynh_die --message="Invalid control file" # Check if this 2 variables aren't empty. @@ -173,7 +173,7 @@ ynh_package_install_from_equivs () { ynh_package_update # Build and install the package - local TMPDIR=$(mktemp -d) + local TMPDIR=$(mktemp --directory) # Force the compatibility level at 10, levels below are deprecated echo 10 > /usr/share/equivs/template/debian/compat @@ -186,21 +186,21 @@ ynh_package_install_from_equivs () { cp "$controlfile" "${TMPDIR}/control" (cd "$TMPDIR" LC_ALL=C equivs-build ./control 1> /dev/null - dpkg --force-depends -i "./${pkgname}_${pkgversion}_all.deb" 2>&1) + dpkg --force-depends --install "./${pkgname}_${pkgversion}_all.deb" 2>&1) # If install fails we use "apt-get check" to try to debug and diagnose possible unmet dependencies # Note the use of { } which allows to group commands without starting a subshell (otherwise the ynh_die wouldn't exit the current shell). # Be careful with the syntax : the semicolon + space at the end is important! - ynh_package_install -f || \ + ynh_package_install --fix-broken || \ { # If the installation failed # 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 -n '/--fix-broken/,$p' >&2 + ynh_package_install $dependencies --dry-run | sed --quiet '/--fix-broken/,$p' >&2 ynh_die --message="Unable to install dependencies"; } - [[ -n "$TMPDIR" ]] && rm -rf $TMPDIR # Remove the temp dir. + [[ -n "$TMPDIR" ]] && rm --recursive --force $TMPDIR # Remove the temp dir. # check if the package is actually installed ynh_package_is_installed "$pkgname" @@ -226,7 +226,7 @@ ynh_install_app_dependencies () { manifest_path="../settings/manifest.json" # Into the restore script, the manifest is not at the same place fi - local version=$(grep '\"version\": ' "$manifest_path" | cut -d '"' -f 4) # Retrieve the version number in the manifest file. + local version=$(grep '\"version\": ' "$manifest_path" | cut --delimiter='"' --fields=4) # Retrieve the version number in the manifest file. if [ ${#version} -eq 0 ]; then version="1.0" fi @@ -252,16 +252,16 @@ ynh_install_app_dependencies () { # https://github.com/YunoHost/issues/issues/1407 # # If we require to install php dependency - if echo $dependencies | grep -q 'php' + if echo $dependencies | grep --quiet 'php' then # And we have packages from sury installed (7.0.33-10+weirdshiftafter instead of 7.0.33-0 on debian) - if dpkg --list | grep "php7.0" | grep -q -v "7.0.33-0+deb9" + if dpkg --list | grep "php7.0" | grep --quiet --invert-match "7.0.33-0+deb9" then # And sury ain't already installed - if ! grep -nrq "sury" /etc/apt/sources.list* + if ! grep --line-number --recursive --quiet "sury" /etc/apt/sources.list* then # Re-add sury - ynh_install_extra_repo --repo="https://packages.sury.org/php/ $(lsb_release -sc) main" --key="https://packages.sury.org/php/apt.gpg" --name=extra_php_version + ynh_install_extra_repo --repo="https://packages.sury.org/php/ $(ynh_get_debian_release) main" --key="https://packages.sury.org/php/apt.gpg" --name=extra_php_version # Pin this sury repository to prevent sury of doing shit ynh_pin_repo --package="*" --pin="origin \"packages.sury.org\"" --priority=200 --name=extra_php_version @@ -396,7 +396,7 @@ ynh_install_extra_repo () { if [ $append -eq 1 ] then append="--append" - wget_append="tee -a" + wget_append="tee --append" else append="" wget_append="tee" @@ -432,8 +432,8 @@ ynh_install_extra_repo () { # Get the public key for the repo if [ -n "$key" ] then - mkdir -p "/etc/apt/trusted.gpg.d" - wget -q "$key" -O - | gpg --dearmor | $wget_append /etc/apt/trusted.gpg.d/$name.gpg > /dev/null + mkdir --parents "/etc/apt/trusted.gpg.d" + wget --quiet "$key" --output-document=- | gpg --dearmor | $wget_append /etc/apt/trusted.gpg.d/$name.gpg > /dev/null fi # Update the list of package with the new repo @@ -495,12 +495,12 @@ ynh_add_repo () { if [ $append -eq 1 ] then - append="tee -a" + append="tee --append" else append="tee" fi - mkdir -p "/etc/apt/sources.list.d" + mkdir --parents "/etc/apt/sources.list.d" # Add the new repo in sources.list.d echo "deb $uri $suite $component" \ | $append "/etc/apt/sources.list.d/$name.list" @@ -537,12 +537,12 @@ ynh_pin_repo () { if [ $append -eq 1 ] then - append="tee -a" + append="tee --append" else append="tee" fi - mkdir -p "/etc/apt/preferences.d" + mkdir --parents "/etc/apt/preferences.d" echo "Package: $package Pin: $pin Pin-Priority: $priority diff --git a/data/helpers.d/backup b/data/helpers.d/backup index 096804380..60e9fca94 100644 --- a/data/helpers.d/backup +++ b/data/helpers.d/backup @@ -144,15 +144,15 @@ ynh_backup() { # ============================================================================== # Write file to backup into backup_list # ============================================================================== - local src=$(echo "${src_path}" | sed -r 's/"/\"\"/g') - local dest=$(echo "${dest_path}" | sed -r 's/"/\"\"/g') + local src=$(echo "${src_path}" | sed --regexp-extended 's/"/\"\"/g') + local dest=$(echo "${dest_path}" | sed --regexp-extended 's/"/\"\"/g') echo "\"${src}\",\"${dest}\"" >> "${YNH_BACKUP_CSV}" # ============================================================================== # Create the parent dir of the destination path # It's for retro compatibility, some script consider ynh_backup creates this dir - mkdir -p $(dirname "$YNH_BACKUP_DIR/${dest_path}") + mkdir --parents $(dirname "$YNH_BACKUP_DIR/${dest_path}") } # Restore all files that were previously backuped in a core backup script or app backup script @@ -166,11 +166,11 @@ ynh_restore () { REL_DIR="${REL_DIR%/}/" # For each destination path begining by $REL_DIR - cat ${YNH_BACKUP_CSV} | tr -d $'\r' | grep -ohP "^\".*\",\"$REL_DIR.*\"$" | \ + cat ${YNH_BACKUP_CSV} | tr --delete $'\r' | grep --only-matching --no-filename --perl-regexp "^\".*\",\"$REL_DIR.*\"$" | \ while read line do - local ORIGIN_PATH=$(echo "$line" | grep -ohP "^\"\K.*(?=\",\".*\"$)") - local ARCHIVE_PATH=$(echo "$line" | grep -ohP "^\".*\",\"$REL_DIR\K.*(?=\"$)") + local ORIGIN_PATH=$(echo "$line" | grep --only-matching --no-filename --perl-regexp "^\"\K.*(?=\",\".*\"$)") + local ARCHIVE_PATH=$(echo "$line" | grep --only-matching --no-filename --perl-regexp "^\".*\",\"$REL_DIR\K.*(?=\"$)") ynh_restore_file --origin_path="$ARCHIVE_PATH" --dest_path="$ORIGIN_PATH" done } @@ -251,10 +251,10 @@ ynh_restore_file () { if [[ -e "${dest_path}" ]] then # Check if the file/dir size is less than 500 Mo - if [[ $(du -sb ${dest_path} | cut -d"/" -f1) -le "500000000" ]] + if [[ $(du --summarize --bytes ${dest_path} | cut --delimiter="/" --fields=1) -le "500000000" ]] then local backup_file="/home/yunohost.conf/backup/${dest_path}.backup.$(date '+%Y%m%d.%H%M%S')" - mkdir -p "$(dirname "$backup_file")" + mkdir --parents "$(dirname "$backup_file")" mv "${dest_path}" "$backup_file" # Move the current file or directory else ynh_secure_remove --file=${dest_path} @@ -262,17 +262,17 @@ ynh_restore_file () { fi # Restore origin_path into dest_path - mkdir -p $(dirname "$dest_path") + mkdir --parents $(dirname "$dest_path") # Do a copy if it's just a mounting point - if mountpoint -q $YNH_BACKUP_DIR + if mountpoint --quiet $YNH_BACKUP_DIR then if [[ -d "${archive_path}" ]] then archive_path="${archive_path}/." - mkdir -p "$dest_path" + mkdir --parents "$dest_path" fi - cp -a "$archive_path" "${dest_path}" + cp --archive "$archive_path" "${dest_path}" # Do a move if YNH_BACKUP_DIR is already a copy else mv "$archive_path" "${dest_path}" @@ -308,7 +308,7 @@ ynh_store_file_checksum () { ynh_handle_getopts_args "$@" local checksum_setting_name=checksum_${file//[\/ ]/_} # Replace all '/' and ' ' by '_' - ynh_app_setting_set --app=$app --key=$checksum_setting_name --value=$(md5sum "$file" | cut -d' ' -f1) + ynh_app_setting_set --app=$app --key=$checksum_setting_name --value=$(md5sum "$file" | cut --delimiter=' ' --fields=1) # If backup_file_checksum isn't empty, ynh_backup_if_checksum_is_different has made a backup if [ -n "${backup_file_checksum-}" ] @@ -345,11 +345,11 @@ ynh_backup_if_checksum_is_different () { backup_file_checksum="" if [ -n "$checksum_value" ] then # Proceed only if a value was stored into the app settings - if [ -e $file ] && ! echo "$checksum_value $file" | md5sum -c --status + if [ -e $file ] && ! echo "$checksum_value $file" | md5sum --check --status then # If the checksum is now different backup_file_checksum="/home/yunohost.conf/backup/$file.backup.$(date '+%Y%m%d.%H%M%S')" - mkdir -p "$(dirname "$backup_file_checksum")" - cp -a "$file" "$backup_file_checksum" # Backup the current file + mkdir --parents "$(dirname "$backup_file_checksum")" + cp --archive "$file" "$backup_file_checksum" # Backup the current file ynh_print_warn "File $file has been manually modified since the installation or last upgrade. So it has been duplicated in $backup_file_checksum" echo "$backup_file_checksum" # Return the name of the backup file fi @@ -400,7 +400,7 @@ ynh_backup_before_upgrade () { if [ "$NO_BACKUP_UPGRADE" -eq 0 ] then # Check if a backup already exists with the prefix 1 - if yunohost backup list | grep -q $app_bck-pre-upgrade1 + if yunohost backup list | grep --quiet $app_bck-pre-upgrade1 then # Prefix becomes 2 to preserve the previous backup backup_number=2 @@ -412,7 +412,7 @@ ynh_backup_before_upgrade () { if [ "$?" -eq 0 ] then # If the backup succeeded, remove the previous backup - if yunohost backup list | grep -q $app_bck-pre-upgrade$old_backup_number + if yunohost backup list | grep --quiet $app_bck-pre-upgrade$old_backup_number then # Remove the previous backup only if it exists yunohost backup delete $app_bck-pre-upgrade$old_backup_number > /dev/null @@ -444,7 +444,7 @@ ynh_restore_upgradebackup () { if [ "$NO_BACKUP_UPGRADE" -eq 0 ] then # Check if an existing backup can be found before removing and restoring the application. - if yunohost backup list | grep -q $app_bck-pre-upgrade$backup_number + if yunohost backup list | grep --quiet $app_bck-pre-upgrade$backup_number then # Remove the application then restore it yunohost app remove $app diff --git a/data/helpers.d/fail2ban b/data/helpers.d/fail2ban index 438b3b355..1eef67f5c 100644 --- a/data/helpers.d/fail2ban +++ b/data/helpers.d/fail2ban @@ -133,7 +133,7 @@ EOF ynh_systemd_action --service_name=fail2ban --action=reload - local fail2ban_error="$(journalctl -u fail2ban | tail -n50 | grep "WARNING.*$app.*")" + local fail2ban_error="$(journalctl --unit=fail2ban | tail --lines=50 | grep "WARNING.*$app.*")" if [[ -n "$fail2ban_error" ]] then ynh_print_err --message="Fail2ban failed to load the jail for $app" diff --git a/data/helpers.d/getopts b/data/helpers.d/getopts index 1717ea8fc..4dfd08d9e 100644 --- a/data/helpers.d/getopts +++ b/data/helpers.d/getopts @@ -47,7 +47,7 @@ # Requires YunoHost version 3.2.2 or higher. ynh_handle_getopts_args () { # Manage arguments only if there's some provided - set +x + set +o xtrace # set +x if [ $# -ne 0 ] then # Store arguments in an array to keep each argument separated @@ -216,5 +216,5 @@ ynh_handle_getopts_args () { parse_arg "${arguments[@]}" fi fi - set -x + set -o xtrace # set -x } diff --git a/data/helpers.d/logging b/data/helpers.d/logging index 0cd25fb57..812709921 100644 --- a/data/helpers.d/logging +++ b/data/helpers.d/logging @@ -46,7 +46,7 @@ ynh_print_info() { # Requires YunoHost version 2.6.4 or higher. ynh_no_log() { local ynh_cli_log=/var/log/yunohost/yunohost-cli.log - cp -a ${ynh_cli_log} ${ynh_cli_log}-move + cp --archive ${ynh_cli_log} ${ynh_cli_log}-move eval $@ local exit_code=$? mv ${ynh_cli_log}-move ${ynh_cli_log} @@ -221,7 +221,7 @@ base_time=$(date +%s) # # Requires YunoHost version 3.5.0 or higher. ynh_script_progression () { - set +x + set +o xtrace # set +x # Declare an array to define the options of this helper. local legacy_args=mwtl local -A args_array=( [m]=message= [w]=weight= [t]=time [l]=last ) @@ -231,7 +231,7 @@ ynh_script_progression () { local last # Manage arguments with getopts ynh_handle_getopts_args "$@" - set +x + set +o xtrace # set +x weight=${weight:-1} time=${time:-0} last=${last:-0} @@ -295,7 +295,7 @@ ynh_script_progression () { fi ynh_print_info "[$progression_bar] > ${message}${print_exec_time}" - set -x + set -o xtrace # set -x } # Return data to the Yunohost core for later processing @@ -317,7 +317,7 @@ ynh_return () { # Requires YunoHost version 3.5.0 or higher. ynh_debug () { # Disable set xtrace for the helper itself, to not pollute the debug log - set +x + set +o xtrace # set +x # Declare an array to define the options of this helper. local legacy_args=mt local -A args_array=( [m]=message= [t]=trace= ) @@ -326,7 +326,7 @@ ynh_debug () { # Manage arguments with getopts ynh_handle_getopts_args "$@" # Redisable xtrace, ynh_handle_getopts_args set it back - set +x + set +o xtrace # set +x message=${message:-} trace=${trace:-} @@ -338,7 +338,7 @@ ynh_debug () { if [ "$trace" == "1" ] then ynh_debug --message="Enable debugging" - set +x + set +o xtrace # set +x # Get the current file descriptor of xtrace old_bash_xtracefd=$BASH_XTRACEFD # Add the current file name and the line number of any command currently running while tracing. @@ -351,14 +351,14 @@ ynh_debug () { if [ "$trace" == "0" ] then ynh_debug --message="Disable debugging" - set +x + set +o xtrace # set +x # Put xtrace back to its original fild descriptor BASH_XTRACEFD=$old_bash_xtracefd # Restore stdout exec 1>&1 fi # Renable set xtrace - set -x + set -o xtrace # set -x } # Execute a command and print the result as debug diff --git a/data/helpers.d/logrotate b/data/helpers.d/logrotate index 3cd835eee..b9af082a6 100644 --- a/data/helpers.d/logrotate +++ b/data/helpers.d/logrotate @@ -55,7 +55,7 @@ ynh_use_logrotate () { fi # LEGACY CODE - local customtee="tee -a" + local customtee="tee --append" if [ "$nonappend" -eq 1 ]; then customtee="tee" fi @@ -95,7 +95,7 @@ $logfile { $su_directive } EOF - mkdir -p $(dirname "$logfile") # Create the log directory, if not exist + mkdir --parents $(dirname "$logfile") # Create the log directory, if not exist cat ${app}-logrotate | $customtee /etc/logrotate.d/$app > /dev/null # Append this config to the existing config file, or replace the whole config file (depending on $customtee) } diff --git a/data/helpers.d/mysql b/data/helpers.d/mysql index 52c65cc63..62edd8822 100644 --- a/data/helpers.d/mysql +++ b/data/helpers.d/mysql @@ -24,7 +24,7 @@ ynh_mysql_connect_as() { ynh_handle_getopts_args "$@" database="${database:-}" - mysql -u "$user" --password="$password" -B "$database" + mysql --user="$user" --password="$password" --batch "$database" } # Execute a command as root user @@ -127,7 +127,7 @@ ynh_mysql_dump_db() { # Manage arguments with getopts ynh_handle_getopts_args "$@" - mysqldump -u "root" -p"$(cat $MYSQL_ROOT_PWD_FILE)" --single-transaction --skip-dump-date "$database" + mysqldump --user="root" --password="$(cat $MYSQL_ROOT_PWD_FILE)" --single-transaction --skip-dump-date "$database" } # Create a user @@ -225,7 +225,7 @@ ynh_mysql_remove_db () { ynh_handle_getopts_args "$@" local mysql_root_password=$(cat $MYSQL_ROOT_PWD_FILE) - if mysqlshow -u root -p$mysql_root_password | grep -q "^| $db_name" + if mysqlshow --user=root --password=$mysql_root_password | grep --quiet "^| $db_name" then # Check if the database exists ynh_mysql_drop_db $db_name # Remove the database else diff --git a/data/helpers.d/network b/data/helpers.d/network index ca15e6919..2e301090c 100644 --- a/data/helpers.d/network +++ b/data/helpers.d/network @@ -17,7 +17,7 @@ ynh_find_port () { ynh_handle_getopts_args "$@" test -n "$port" || ynh_die --message="The argument of ynh_find_port must be a valid port." - while ss -nltu | awk '{print$5}' | grep -q -E ":$port$" # Check if the port is free + while ss --numeric --listening --tcp --udp | awk '{print$5}' | grep --quiet --extended-regexp ":$port$" # Check if the port is free do port=$((port+1)) # Else, pass to next port done @@ -40,7 +40,7 @@ ynh_port_available () { # Manage arguments with getopts ynh_handle_getopts_args "$@" - if ss -nltu | grep -q -w :$port + if ss --numeric --listening --tcp --udp | grep --quiet --word-regexp :$port then return 1 else diff --git a/data/helpers.d/nodejs b/data/helpers.d/nodejs index 2d4ea66dc..cb83e3136 100644 --- a/data/helpers.d/nodejs +++ b/data/helpers.d/nodejs @@ -15,7 +15,7 @@ export N_PREFIX="$n_install_dir" ynh_install_n () { ynh_print_info --message="Installation of N - Node.js version management" # Build an app.src for n - mkdir -p "../conf" + mkdir --parents "../conf" echo "SOURCE_URL=https://github.com/tj/n/archive/v4.1.0.tar.gz SOURCE_SUM=3983fa3f00d4bf85ba8e21f1a590f6e28938093abe0bb950aeea52b1717471fc" > "../conf/n.src" # Download and extract n @@ -74,7 +74,7 @@ ynh_install_nodejs () { ynh_handle_getopts_args "$@" # Create $n_install_dir - mkdir -p "$n_install_dir" + mkdir --parents "$n_install_dir" # Load n path in PATH CLEAR_PATH="$n_install_dir/bin:$PATH" @@ -102,7 +102,7 @@ ynh_install_nodejs () { test -x /usr/bin/npm_n && mv /usr/bin/npm_n /usr/bin/npm # Install the requested version of nodejs - uname=$(uname -m) + uname=$(uname --machine) if [[ $uname =~ aarch64 || $uname =~ arm64 ]] then n $nodejs_version --arch=arm64 @@ -159,7 +159,7 @@ ynh_remove_nodejs () { ynh_secure_remove --file="$n_install_dir" ynh_secure_remove --file="/usr/local/n" sed --in-place "/N_PREFIX/d" /root/.bashrc - rm -f /etc/cron.daily/node_update + rm --force /etc/cron.daily/node_update fi } diff --git a/data/helpers.d/php b/data/helpers.d/php index c904f8f1b..1bbb6c84b 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -117,7 +117,7 @@ ynh_add_fpm_config () { fi # Create the directory for fpm pools - mkdir -p "$fpm_config_dir/pool.d" + mkdir --parents "$fpm_config_dir/pool.d" ynh_app_setting_set --app=$app --key=fpm_config_dir --value="$fpm_config_dir" ynh_app_setting_set --app=$app --key=fpm_service --value="$fpm_service" @@ -341,7 +341,7 @@ ynh_install_php () { fi # Add an extra repository for those packages - ynh_install_extra_repo --repo="https://packages.sury.org/php/ $(lsb_release -sc) main" --key="https://packages.sury.org/php/apt.gpg" --priority=995 --name=extra_php_version + ynh_install_extra_repo --repo="https://packages.sury.org/php/ $(ynh_get_debian_release) main" --key="https://packages.sury.org/php/apt.gpg" --priority=995 --name=extra_php_version # Install requested dependencies from this extra repository. # Install php-fpm first, otherwise php will install apache as a dependency. diff --git a/data/helpers.d/setting b/data/helpers.d/setting index eca529069..86634dcc3 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -231,7 +231,7 @@ ynh_permission_exists() { local permission ynh_handle_getopts_args "$@" - yunohost user permission list -s | grep -w -q "$app.$permission" + yunohost user permission list --short | grep --word-regexp --quiet "$app.$permission" } # Redefine the url associated to a permission @@ -311,5 +311,5 @@ ynh_permission_has_user() { return 1 fi - yunohost user permission info "$app.$permission" | grep -w -q "$user" + yunohost user permission info "$app.$permission" | grep --word-regexp --quiet "$user" } diff --git a/data/helpers.d/string b/data/helpers.d/string index 76c3b37e4..dd318b4de 100644 --- a/data/helpers.d/string +++ b/data/helpers.d/string @@ -18,8 +18,8 @@ ynh_string_random() { length=${length:-24} dd if=/dev/urandom bs=1 count=1000 2> /dev/null \ - | tr -c -d 'A-Za-z0-9' \ - | sed -n 's/\(.\{'"$length"'\}\).*/\1/p' + | tr --complement --delete 'A-Za-z0-9' \ + | sed --quiet 's/\(.\{'"$length"'\}\).*/\1/p' } # Substitute/replace a string (or expression) by another in a file diff --git a/data/helpers.d/systemd b/data/helpers.d/systemd index 6257b9138..b6c4722af 100644 --- a/data/helpers.d/systemd +++ b/data/helpers.d/systemd @@ -112,7 +112,7 @@ ynh_systemd_action() { local pid_tail=$! else # Read the specified log file - tail -F -n0 "$log_path" > "$templog" 2>&1 & + tail --follow --retry --lines=0 "$log_path" > "$templog" 2>&1 & # Get the PID of the tail command local pid_tail=$! fi @@ -170,7 +170,7 @@ ynh_clean_check_starting () { if [ -n "$pid_tail" ] then # Stop the execution of tail. - kill -s 15 $pid_tail 2>&1 + kill --signal 15 $pid_tail 2>&1 fi if [ -n "$templog" ] then diff --git a/data/helpers.d/user b/data/helpers.d/user index 0b964c7c0..ff6c4e6ea 100644 --- a/data/helpers.d/user +++ b/data/helpers.d/user @@ -16,7 +16,7 @@ ynh_user_exists() { # Manage arguments with getopts ynh_handle_getopts_args "$@" - yunohost user list --output-as json | grep -q "\"username\": \"${username}\"" + yunohost user list --output-as json | grep --quiet "\"username\": \"${username}\"" } # Retrieve a YunoHost user information @@ -116,7 +116,7 @@ ynh_system_user_create () { then # If the user doesn't exist if [ -n "$home_dir" ] then # If a home dir is mentioned - local user_home_dir="-d $home_dir" + local user_home_dir="--home-dir $home_dir" else local user_home_dir="--no-create-home" fi diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 2ad05f93c..41cef98c2 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -23,7 +23,9 @@ ynh_exit_properly () { fi trap '' EXIT # Ignore new exit signals - set +eu # Do not exit anymore if a command fail or if a variable is empty + # Do not exit anymore if a command fail or if a variable is empty + set +o errexit # set +e + set +o nounset # set +u # Small tempo to avoid the next message being mixed up with other DEBUG messages sleep 0.5 @@ -46,7 +48,8 @@ ynh_exit_properly () { # # Requires YunoHost version 2.6.4 or higher. ynh_abort_if_errors () { - set -eu # Exit if a command fail, and if a variable is used unset. + set -o errexit # set -e; Exit if a command fail + set -o nounset # set -u; And if a variable is used unset trap ynh_exit_properly EXIT # Capturing exit signals on shell script } @@ -115,13 +118,13 @@ ynh_setup_source () { # Load value from configuration file (see above for a small doc about this file # format) - local src_url=$(grep 'SOURCE_URL=' "$src_file_path" | cut -d= -f2-) - local src_sum=$(grep 'SOURCE_SUM=' "$src_file_path" | cut -d= -f2-) - local src_sumprg=$(grep 'SOURCE_SUM_PRG=' "$src_file_path" | cut -d= -f2-) - local src_format=$(grep 'SOURCE_FORMAT=' "$src_file_path" | cut -d= -f2-) - local src_extract=$(grep 'SOURCE_EXTRACT=' "$src_file_path" | cut -d= -f2-) - local src_in_subdir=$(grep 'SOURCE_IN_SUBDIR=' "$src_file_path" | cut -d= -f2-) - local src_filename=$(grep 'SOURCE_FILENAME=' "$src_file_path" | cut -d= -f2-) + local src_url=$(grep 'SOURCE_URL=' "$src_file_path" | cut --delimiter='=' --fields=2-) + local src_sum=$(grep 'SOURCE_SUM=' "$src_file_path" | cut --delimiter='=' --fields=2-) + local src_sumprg=$(grep 'SOURCE_SUM_PRG=' "$src_file_path" | cut --delimiter='=' --fields=2-) + local src_format=$(grep 'SOURCE_FORMAT=' "$src_file_path" | cut --delimiter='=' --fields=2-) + local src_extract=$(grep 'SOURCE_EXTRACT=' "$src_file_path" | cut --delimiter='=' --fields=2-) + local src_in_subdir=$(grep 'SOURCE_IN_SUBDIR=' "$src_file_path" | cut --delimiter='=' --fields=2-) + local src_filename=$(grep 'SOURCE_FILENAME=' "$src_file_path" | cut --delimiter='=' --fields=2-) # Default value src_sumprg=${src_sumprg:-sha256sum} @@ -138,15 +141,15 @@ ynh_setup_source () { then # Use the local source file if it is present cp $local_src $src_filename else # If not, download the source - local out=`wget -nv -O $src_filename $src_url 2>&1` || ynh_print_err --message="$out" + local out=`wget --no-verbose --output-document=$src_filename $src_url 2>&1` || ynh_print_err --message="$out" fi # Check the control sum - echo "${src_sum} ${src_filename}" | ${src_sumprg} -c --status \ + echo "${src_sum} ${src_filename}" | ${src_sumprg} --check --status \ || ynh_die --message="Corrupt source" # Extract source into the app dir - mkdir -p "$dest_dir" + mkdir --parents "$dest_dir" if ! "$src_extract" then @@ -157,9 +160,9 @@ ynh_setup_source () { # Using of a temp directory, because unzip doesn't manage --strip-components if $src_in_subdir then - local tmp_dir=$(mktemp -d) + local tmp_dir=$(mktemp --directory) unzip -quo $src_filename -d "$tmp_dir" - cp -a $tmp_dir/*/. "$dest_dir" + cp --archive $tmp_dir/*/. "$dest_dir" ynh_secure_remove --file="$tmp_dir" else unzip -quo $src_filename -d "$dest_dir" @@ -178,7 +181,7 @@ ynh_setup_source () { fi if [[ "$src_format" =~ ^tar.gz|tar.bz2|tar.xz$ ]] then - tar -xf $src_filename -C "$dest_dir" $strip + tar --extract --file=$src_filename --directory="$dest_dir" $strip else ynh_die --message="Archive format unrecognized." fi @@ -196,7 +199,7 @@ ynh_setup_source () { # Add supplementary files if test -e "$YNH_CWD/../sources/extra_files/${source_id}"; then - cp -a $YNH_CWD/../sources/extra_files/$source_id/. "$dest_dir" + cp --archive $YNH_CWD/../sources/extra_files/$source_id/. "$dest_dir" fi } @@ -247,7 +250,7 @@ ynh_local_curl () { chmod 700 $cookiefile # Curl the URL - curl --silent --show-error -kL -H "Host: $domain" --resolve $domain:443:127.0.0.1 $POST_data "$full_page_url" --cookie-jar $cookiefile --cookie $cookiefile + curl --silent --show-error --insecure --location --header "Host: $domain" --resolve $domain:443:127.0.0.1 $POST_data "$full_page_url" --cookie-jar $cookiefile --cookie $cookiefile } # Render templates with Jinja2 @@ -290,7 +293,7 @@ ynh_mkdir_tmp() { ynh_print_warn --message="The helper ynh_mkdir_tmp is deprecated." ynh_print_warn --message="You should use 'mktemp -d' instead and manage permissions \ properly with chmod/chown." - local TMP_DIR=$(mktemp -d) + local TMP_DIR=$(mktemp --directory) # Give rights to other users could be a security risk. # But for retrocompatibility we need it. (This helpers is deprecated) @@ -334,7 +337,7 @@ ynh_secure_remove () { ynh_print_warn --message="Not deleting '$file' because it is not an acceptable path to delete." elif [ -e "$file" ] then - rm -R "$file" + rm --recursive "$file" else ynh_print_info --message="'$file' wasn't deleted because it doesn't exist." fi From 3442ab5b806fb09281cc156e23fb2c9b3d1ba6b1 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Mon, 20 Apr 2020 15:21:11 +0200 Subject: [PATCH 1047/3170] Add internal flags --- data/helpers.d/apt | 2 -- data/helpers.d/network | 2 ++ data/helpers.d/string | 2 ++ data/helpers.d/utils | 4 ++++ 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index 9d2c2b64c..1c032f26c 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -288,8 +288,6 @@ EOF # Add dependencies to install with ynh_install_app_dependencies # -# [internal] -# # usage: ynh_add_app_dependencies --package=phpversion [--replace] # | arg: -p, --package - Packages to add as dependencies for the app. # | arg: -r, --replace - Replace dependencies instead of adding to existing ones. diff --git a/data/helpers.d/network b/data/helpers.d/network index 2e301090c..c8493d7ac 100644 --- a/data/helpers.d/network +++ b/data/helpers.d/network @@ -51,6 +51,8 @@ ynh_port_available () { # Validate an IP address # +# [internal] +# # usage: ynh_validate_ip --family=family --ip_address=ip_address # | ret: 0 for valid ip addresses, 1 otherwise # diff --git a/data/helpers.d/string b/data/helpers.d/string index dd318b4de..7a37f29c3 100644 --- a/data/helpers.d/string +++ b/data/helpers.d/string @@ -108,6 +108,8 @@ ynh_sanitize_dbid () { # Normalize the url path syntax # +# [internal] +# # Handle the slash at the beginning of path and its absence at ending # Return a normalized url path # diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 41cef98c2..a991853e3 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -255,6 +255,8 @@ ynh_local_curl () { # Render templates with Jinja2 # +# [internal] +# # Attention : Variables should be exported before calling this helper to be # accessible inside templates. # @@ -345,6 +347,8 @@ ynh_secure_remove () { # Extract a key from a plain command output # +# [internal] +# # example: yunohost user info tata --output-as plain | ynh_get_plain_key mail # # usage: ynh_get_plain_key key [subkey [subsubkey ...]] From 464149eb7664b91be9a6d2ee78ad293093ee75e9 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Mon, 20 Apr 2020 15:21:37 +0200 Subject: [PATCH 1048/3170] Use ynh_print_info --- data/helpers.d/postgresql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/postgresql b/data/helpers.d/postgresql index 4fc349169..f0aa6d0f0 100644 --- a/data/helpers.d/postgresql +++ b/data/helpers.d/postgresql @@ -268,7 +268,7 @@ ynh_psql_remove_db() { ynh_psql_test_if_first_run() { if [ -f "$PSQL_ROOT_PWD_FILE" ] then - echo "PostgreSQL is already installed, no need to create master password" + ynh_print_info --message="PostgreSQL is already installed, no need to create master password" return fi From e64eb3478e0ba9d8b734610e88aba18bc54efaab Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Mon, 20 Apr 2020 15:24:07 +0200 Subject: [PATCH 1049/3170] Unfold AND OR --- data/helpers.d/backup | 20 ++++++++++++-------- data/helpers.d/mysql | 4 +++- data/helpers.d/systemd | 30 ++++++++++++++++++++++-------- data/helpers.d/utils | 14 +++++++------- 4 files changed, 44 insertions(+), 24 deletions(-) diff --git a/data/helpers.d/backup b/data/helpers.d/backup index 60e9fca94..e5186cb59 100644 --- a/data/helpers.d/backup +++ b/data/helpers.d/backup @@ -77,7 +77,8 @@ ynh_backup() { # Format correctly source and destination paths # ============================================================================== # Be sure the source path is not empty - [[ -e "${src_path}" ]] || { + if [ ! -e "$src_path" ] + then ynh_print_warn --message="Source path '${src_path}' does not exist" if [ "$not_mandatory" == "0" ] then @@ -92,7 +93,7 @@ ynh_backup() { else return 0 fi - } + fi # Transform the source path as an absolute path # If it's a dir remove the ending / @@ -119,20 +120,23 @@ ynh_backup() { dest_path="${dest_path#$YNH_CWD/}" # Case where $2 is an absolute dir but doesn't begin with $YNH_CWD - [[ "${dest_path:0:1}" == "/" ]] \ - && dest_path="${dest_path#/}" + if [[ "${dest_path:0:1}" == "/" ]]; then + dest_path="${dest_path#/}" + fi fi # Complete dest_path if ended by a / - [[ "${dest_path: -1}" == "/" ]] \ - && dest_path="${dest_path}/$(basename $src_path)" + if [[ "${dest_path: -1}" == "/" ]]; then + dest_path="${dest_path}/$(basename $src_path)" + fi fi # Check if dest_path already exists in tmp archive - [[ ! -e "${dest_path}" ]] || { + if [[ -e "${dest_path}" ]] + then ynh_print_err --message="Destination path '${dest_path}' already exist" return 1 - } + fi # Add the relative current working directory to the destination path local rel_dir="${YNH_CWD#$YNH_BACKUP_DIR}" diff --git a/data/helpers.d/mysql b/data/helpers.d/mysql index 62edd8822..7edc633b4 100644 --- a/data/helpers.d/mysql +++ b/data/helpers.d/mysql @@ -88,7 +88,9 @@ ynh_mysql_create_db() { if [[ $# -gt 1 ]] then sql+=" GRANT ALL PRIVILEGES ON ${db}.* TO '${2}'@'localhost'" - [[ -n ${3:-} ]] && sql+=" IDENTIFIED BY '${3}'" + if [[ -n ${3:-} ]]; then + sql+=" IDENTIFIED BY '${3}'" + fi sql+=" WITH GRANT OPTION;" fi diff --git a/data/helpers.d/systemd b/data/helpers.d/systemd index b6c4722af..871d6459d 100644 --- a/data/helpers.d/systemd +++ b/data/helpers.d/systemd @@ -32,10 +32,10 @@ ynh_add_systemd_config () { # To avoid a break by set -u, use a void substitution ${var:-}. If the variable is not set, it's simply set with an empty variable. # Substitute in a nginx config file only if the variable is not empty - if test -n "${final_path:-}"; then + if [ -n "${final_path:-}" ]; then ynh_replace_string --match_string="__FINALPATH__" --replace_string="$final_path" --target_file="$finalsystemdconf" fi - if test -n "${app:-}"; then + if [ -n "${app:-}" ]; then ynh_replace_string --match_string="__APP__" --replace_string="$app" --target_file="$finalsystemdconf" fi ynh_store_file_checksum --file="$finalsystemdconf" @@ -123,10 +123,20 @@ ynh_systemd_action() { action="reload-or-restart" fi - systemctl $action $service_name \ - || ( journalctl --no-pager --lines=$length -u $service_name >&2 \ - ; test -e "$log_path" && echo "--" >&2 && tail --lines=$length "$log_path" >&2 \ - ; false ) + # If the service fails to perform the action + if ! systemctl $action $service_name + then + # Show syslog for this service + ynh_exec_err journalctl --no-pager --lines=$length --unit=$service_name + # If a log is specified for this service, show also the content of this log + if [ -e "$log_path" ] + then + ynh_print_err --message="--" + ynh_exec_err tail --lines=$length "$log_path" + fi + # Fail the app script, since the service failed. + false + fi # Start the timeout and try to find line_match if [[ -n "${line_match:-}" ]] @@ -155,8 +165,12 @@ ynh_systemd_action() { then ynh_print_warn --message="The service $service_name didn't fully executed the action ${action} before the timeout." ynh_print_warn --message="Please find here an extract of the end of the log of the service $service_name:" - journalctl --no-pager --lines=$length -u $service_name >&2 - test -e "$log_path" && echo "--" >&2 && tail --lines=$length "$log_path" >&2 + ynh_exec_warn journalctl --no-pager --lines=$length --unit=$service_name + if [ -e "$log_path" ] + then + ynh_print_warn --message="--" + ynh_exec_warn tail --lines=$length "$log_path" + fi fi ynh_clean_check_starting fi diff --git a/data/helpers.d/utils b/data/helpers.d/utils index a991853e3..6b75426fc 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -188,13 +188,13 @@ ynh_setup_source () { fi # Apply patches - if (( $(find $YNH_CWD/../sources/patches/ -type f -name "${source_id}-*.patch" 2> /dev/null | wc -l) > "0" )); then - local old_dir=$(pwd) - (cd "$dest_dir" \ - && for p in $YNH_CWD/../sources/patches/${source_id}-*.patch; do \ - patch -p1 < $p; done) \ - || ynh_die --message="Unable to apply patches" - cd $old_dir + if (( $(find $YNH_CWD/../sources/patches/ -type f -name "${source_id}-*.patch" 2> /dev/null | wc --lines) > "0" )) + then + (cd "$dest_dir" + for p in $YNH_CWD/../sources/patches/${source_id}-*.patch + do + patch --strip=1 < $p + done) || ynh_die --message="Unable to apply patches" fi # Add supplementary files From 3b653994c7665410a2a925ba29a31d86b360b15d Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Mon, 20 Apr 2020 20:58:17 +0200 Subject: [PATCH 1050/3170] Standardize helper comments --- data/helpers.d/apt | 49 ++++++++++++++------------- data/helpers.d/backup | 54 ++++++++++++++--------------- data/helpers.d/hardware | 20 ++++++----- data/helpers.d/logging | 29 +++++++--------- data/helpers.d/logrotate | 6 ++-- data/helpers.d/mysql | 39 ++++++++++----------- data/helpers.d/network | 8 +++-- data/helpers.d/nodejs | 8 ++--- data/helpers.d/php | 28 +++++++-------- data/helpers.d/postgresql | 35 ++++++++++--------- data/helpers.d/setting | 71 ++++++++++++++++++++------------------- data/helpers.d/string | 19 ++++++----- data/helpers.d/systemd | 8 ++--- data/helpers.d/user | 21 +++++++----- data/helpers.d/utils | 39 +++++++++++---------- 15 files changed, 226 insertions(+), 208 deletions(-) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index 1c032f26c..50db7613f 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -5,6 +5,7 @@ # [internal] # # usage: ynh_wait_dpkg_free +# | exit: Return 1 if dpkg is broken # # Requires YunoHost version 3.3.1 or higher. ynh_wait_dpkg_free() { @@ -45,7 +46,7 @@ ynh_wait_dpkg_free() { # example: ynh_package_is_installed --package=yunohost && echo "ok" # # usage: ynh_package_is_installed --package=name -# | arg: -p, --package - the package name to check +# | arg: -p, --package= - the package name to check # # Requires YunoHost version 2.2.4 or higher. ynh_package_is_installed() { @@ -66,7 +67,7 @@ ynh_package_is_installed() { # example: version=$(ynh_package_version --package=yunohost) # # usage: ynh_package_version --package=name -# | arg: -p, --package - the package name to get version +# | arg: -p, --package= - the package name to get version # | ret: the version or an empty string # # Requires YunoHost version 2.2.4 or higher. @@ -289,8 +290,8 @@ EOF # Add dependencies to install with ynh_install_app_dependencies # # usage: ynh_add_app_dependencies --package=phpversion [--replace] -# | arg: -p, --package - Packages to add as dependencies for the app. -# | arg: -r, --replace - Replace dependencies instead of adding to existing ones. +# | arg: -p, --package= - Packages to add as dependencies for the app. +# | arg: -r, --replace - Replace dependencies instead of adding to existing ones. ynh_add_app_dependencies () { # Declare an array to define the options of this helper. local legacy_args=pr @@ -333,10 +334,10 @@ ynh_remove_app_dependencies () { # Install packages from an extra repository properly. # # usage: ynh_install_extra_app_dependencies --repo="repo" --package="dep1 dep2" [--key=key_url] [--name=name] -# | arg: -r, --repo - Complete url of the extra repository. -# | arg: -p, --package - The packages to install from this extra repository -# | arg: -k, --key - url to get the public key. -# | arg: -n, --name - Name for the files for this repo, $app as default value. +# | arg: -r, --repo= - Complete url of the extra repository. +# | arg: -p, --package= - The packages to install from this extra repository +# | arg: -k, --key= - url to get the public key. +# | arg: -n, --name= - Name for the files for this repo, $app as default value. ynh_install_extra_app_dependencies () { # Declare an array to define the options of this helper. local legacy_args=rpkn @@ -370,11 +371,11 @@ ynh_install_extra_app_dependencies () { # [internal] # # usage: ynh_install_extra_repo --repo="repo" [--key=key_url] [--priority=priority_value] [--name=name] [--append] -# | arg: -r, --repo - Complete url of the extra repository. -# | arg: -k, --key - url to get the public key. -# | arg: -p, --priority - Priority for the pin -# | arg: -n, --name - Name for the files for this repo, $app as default value. -# | arg: -a, --append - Do not overwrite existing files. +# | arg: -r, --repo= - Complete url of the extra repository. +# | arg: -k, --key= - url to get the public key. +# | arg: -p, --priority= - Priority for the pin +# | arg: -n, --name= - Name for the files for this repo, $app as default value. +# | arg: -a, --append - Do not overwrite existing files. ynh_install_extra_repo () { # Declare an array to define the options of this helper. local legacy_args=rkpna @@ -443,7 +444,7 @@ ynh_install_extra_repo () { # [internal] # # usage: ynh_remove_extra_repo [--name=name] -# | arg: -n, --name - Name for the files for this repo, $app as default value. +# | arg: -n, --name= - Name for the files for this repo, $app as default value. ynh_remove_extra_repo () { # Declare an array to define the options of this helper. local legacy_args=n @@ -467,11 +468,11 @@ ynh_remove_extra_repo () { # [internal] # # usage: ynh_add_repo --uri=uri --suite=suite --component=component [--name=name] [--append] -# | arg: -u, --uri - Uri of the repository. -# | arg: -s, --suite - Suite of the repository. -# | arg: -c, --component - Component of the repository. -# | arg: -n, --name - Name for the files for this repo, $app as default value. -# | arg: -a, --append - Do not overwrite existing files. +# | arg: -u, --uri= - Uri of the repository. +# | arg: -s, --suite= - Suite of the repository. +# | arg: -c, --component= - Component of the repository. +# | arg: -n, --name= - Name for the files for this repo, $app as default value. +# | arg: -a, --append - Do not overwrite existing files. # # Example for a repo like deb http://forge.yunohost.org/debian/ stretch stable # uri suite component @@ -509,11 +510,11 @@ ynh_add_repo () { # [internal] # # usage: ynh_pin_repo --package=packages --pin=pin_filter [--priority=priority_value] [--name=name] [--append] -# | arg: -p, --package - Packages concerned by the pin. Or all, *. -# | arg: -i, --pin - Filter for the pin. -# | arg: -p, --priority - Priority for the pin -# | arg: -n, --name - Name for the files for this repo, $app as default value. -# | arg: -a, --append - Do not overwrite existing files. +# | arg: -p, --package= - Packages concerned by the pin. Or all, *. +# | arg: -i, --pin= - Filter for the pin. +# | arg: -p, --priority= - Priority for the pin +# | arg: -n, --name= - Name for the files for this repo, $app as default value. +# | arg: -a, --append - Do not overwrite existing files. # # See https://manpages.debian.org/stretch/apt/apt_preferences.5.en.html#How_APT_Interprets_Priorities for information about pinning. # diff --git a/data/helpers.d/backup b/data/helpers.d/backup index e5186cb59..2fae73ba0 100644 --- a/data/helpers.d/backup +++ b/data/helpers.d/backup @@ -4,6 +4,13 @@ CAN_BIND=${CAN_BIND:-1} # Add a file or a directory to the list of paths to backup # +# usage: ynh_backup --src_path=src_path [--dest_path=dest_path] [--is_big] [--not_mandatory] +# | arg: -s, --src_path= - file or directory to bind or symlink or copy. it shouldn't be in the backup dir. +# | arg: -d, --dest_path= - destination file or directory inside the backup dir +# | arg: -b, --is_big - Indicate data are big (mail, video, image ...) +# | arg: -m, --not_mandatory - Indicate that if the file is missing, the backup can ignore it. +# | arg: arg - Deprecated arg +# # This helper can be used both in a system backup hook, and in an app backup script # # Details: ynh_backup writes SRC and the relative DEST into a CSV file. And it @@ -11,13 +18,6 @@ CAN_BIND=${CAN_BIND:-1} # # If DEST is ended by a slash it complete this path with the basename of SRC. # -# usage: ynh_backup --src_path=src_path [--dest_path=dest_path] [--is_big] [--not_mandatory] -# | arg: -s, --src_path - file or directory to bind or symlink or copy. it shouldn't be in the backup dir. -# | arg: -d, --dest_path - destination file or directory inside the backup dir -# | arg: -b, --is_big - Indicate data are big (mail, video, image ...) -# | arg: -m, --not_mandatory - Indicate that if the file is missing, the backup can ignore it. -# | arg: arg - Deprecated arg -# # Example in the context of a wordpress app # # ynh_backup "/etc/nginx/conf.d/$domain.d/$app.conf" @@ -206,9 +206,9 @@ with open(sys.argv[1], 'r') as backup_file: # the right place. # # usage: ynh_restore_file --origin_path=origin_path [--dest_path=dest_path] [--not_mandatory] -# | arg: -o, --origin_path - Path where was located the file or the directory before to be backuped or relative path to $YNH_CWD where it is located in the backup archive -# | arg: -d, --dest_path - Path where restore the file or the dir, if unspecified, the destination will be ORIGIN_PATH or if the ORIGIN_PATH doesn't exist in the archive, the destination will be searched into backup.csv -# | arg: -m, --not_mandatory - Indicate that if the file is missing, the restore process can ignore it. +# | arg: -o, --origin_path= - Path where was located the file or the directory before to be backuped or relative path to $YNH_CWD where it is located in the backup archive +# | arg: -d, --dest_path= - Path where restore the file or the dir, if unspecified, the destination will be ORIGIN_PATH or if the ORIGIN_PATH doesn't exist in the archive, the destination will be searched into backup.csv +# | arg: -m, --not_mandatory - Indicate that if the file is missing, the restore process can ignore it. # # examples: # ynh_restore_file "/etc/nginx/conf.d/$domain.d/$app.conf" @@ -297,10 +297,10 @@ ynh_bind_or_cp() { # Calculate and store a file checksum into the app settings # -# $app should be defined when calling this helper -# # usage: ynh_store_file_checksum --file=file -# | arg: -f, --file - The file on which the checksum will performed, then stored. +# | arg: -f, --file= - The file on which the checksum will performed, then stored. +# +# $app should be defined when calling this helper # # Requires YunoHost version 2.6.4 or higher. ynh_store_file_checksum () { @@ -331,7 +331,7 @@ ynh_store_file_checksum () { # modified config files. # # usage: ynh_backup_if_checksum_is_different --file=file -# | arg: -f, --file - The file on which the checksum test will be perfomed. +# | arg: -f, --file= - The file on which the checksum test will be perfomed. # | ret: the name of a backup file, or nothing # # Requires YunoHost version 2.6.4 or higher. @@ -362,10 +362,10 @@ ynh_backup_if_checksum_is_different () { # Delete a file checksum from the app settings # -# $app should be defined when calling this helper +# usage: ynh_delete_file_checksum --file=file +# | arg: -f, --file= - The file for which the checksum will be deleted # -# usage: ynh_remove_file_checksum file -# | arg: -f, --file= - The file for which the checksum will be deleted +# $app should be defined when calling this helper # # Requires YunoHost version 3.3.1 or higher. ynh_delete_file_checksum () { @@ -383,11 +383,11 @@ ynh_delete_file_checksum () { # Make a backup in case of failed upgrade # # usage: -# ynh_backup_before_upgrade -# ynh_clean_setup () { -# ynh_restore_upgradebackup -# } -# ynh_abort_if_errors +# ynh_backup_before_upgrade +# ynh_clean_setup () { +# ynh_restore_upgradebackup +# } +# ynh_abort_if_errors # # Requires YunoHost version 2.7.2 or higher. ynh_backup_before_upgrade () { @@ -432,11 +432,11 @@ ynh_backup_before_upgrade () { # Restore a previous backup if the upgrade process failed # # usage: -# ynh_backup_before_upgrade -# ynh_clean_setup () { -# ynh_restore_upgradebackup -# } -# ynh_abort_if_errors +# ynh_backup_before_upgrade +# ynh_clean_setup () { +# ynh_restore_upgradebackup +# } +# ynh_abort_if_errors # # Requires YunoHost version 2.7.2 or higher. ynh_restore_upgradebackup () { diff --git a/data/helpers.d/hardware b/data/helpers.d/hardware index d7e14ccc5..b46edcdd3 100644 --- a/data/helpers.d/hardware +++ b/data/helpers.d/hardware @@ -3,10 +3,11 @@ # Get the total or free amount of RAM+swap on the system # # usage: ynh_get_ram [--free|--total] [--ignore_swap|--only_swap] -# | arg: -f, --free - Count free RAM+swap -# | arg: -t, --total - Count total RAM+swap -# | arg: -s, --ignore_swap - Ignore swap, consider only real RAM -# | arg: -o, --only_swap - Ignore real RAM, consider only swap +# | arg: -f, --free - Count free RAM+swap +# | arg: -t, --total - Count total RAM+swap +# | arg: -s, --ignore_swap - Ignore swap, consider only real RAM +# | arg: -o, --only_swap - Ignore real RAM, consider only swap +# | ret: the amount of free ram ynh_get_ram () { # Declare an array to define the options of this helper. local legacy_args=ftso @@ -67,11 +68,12 @@ ynh_get_ram () { # Return 0 or 1 depending if the system has a given amount of RAM+swap free or total # # usage: ynh_require_ram --required=RAM required in Mb [--free|--total] [--ignore_swap|--only_swap] -# | arg: -r, --required - The amount to require, in Mb -# | arg: -f, --free - Count free RAM+swap -# | arg: -t, --total - Count total RAM+swap -# | arg: -s, --ignore_swap - Ignore swap, consider only real RAM -# | arg: -o, --only_swap - Ignore real RAM, consider only swap +# | arg: -r, --required= - The amount to require, in Mb +# | arg: -f, --free - Count free RAM+swap +# | arg: -t, --total - Count total RAM+swap +# | arg: -s, --ignore_swap - Ignore swap, consider only real RAM +# | arg: -o, --only_swap - Ignore real RAM, consider only swap +# | exit: Return 1 if the ram is under the requirement, 0 otherwise. ynh_require_ram () { # Declare an array to define the options of this helper. local legacy_args=rftso diff --git a/data/helpers.d/logging b/data/helpers.d/logging index 812709921..49374ec1e 100644 --- a/data/helpers.d/logging +++ b/data/helpers.d/logging @@ -3,6 +3,8 @@ # Print a message to stderr and exit # # usage: ynh_die --message=MSG [--ret_code=RETCODE] +# | arg: -m, --message= - Message to display +# | arg: -c, --ret_code= - Exit code to exit with # # Requires YunoHost version 2.4.0 or higher. ynh_die() { @@ -21,6 +23,7 @@ ynh_die() { # Display a message in the 'INFO' logging category # # usage: ynh_print_info --message="Some message" +# | arg: -m, --message= - Message to display # # Requires YunoHost version 3.2.0 or higher. ynh_print_info() { @@ -65,7 +68,7 @@ ynh_print_log () { # Print a warning on stderr # # usage: ynh_print_warn --message="Text to print" -# | arg: -m, --message - The text to print +# | arg: -m, --message= - The text to print # # Requires YunoHost version 3.2.0 or higher. ynh_print_warn () { @@ -82,7 +85,7 @@ ynh_print_warn () { # Print an error on stderr # # usage: ynh_print_err --message="Text to print" -# | arg: -m, --message - The text to print +# | arg: -m, --message= - The text to print # # Requires YunoHost version 3.2.0 or higher. ynh_print_err () { @@ -100,13 +103,12 @@ ynh_print_err () { # # usage: ynh_exec_err your_command # usage: ynh_exec_err "your_command | other_command" +# | arg: command - command to execute # # When using pipes, double quotes are required - otherwise, this helper will run the first command, and the whole output will be sent through the next pipe. # # If the command to execute uses double quotes, they have to be escaped or they will be interpreted and removed. # -# | arg: command - command to execute -# # Requires YunoHost version 3.2.0 or higher. ynh_exec_err () { ynh_print_err "$(eval $@)" @@ -116,13 +118,12 @@ ynh_exec_err () { # # usage: ynh_exec_warn your_command # usage: ynh_exec_warn "your_command | other_command" +# | arg: command - command to execute # # When using pipes, double quotes are required - otherwise, this helper will run the first command, and the whole output will be sent through the next pipe. # # If the command to execute uses double quotes, they have to be escaped or they will be interpreted and removed. # -# | arg: command - command to execute -# # Requires YunoHost version 3.2.0 or higher. ynh_exec_warn () { ynh_print_warn "$(eval $@)" @@ -132,13 +133,12 @@ ynh_exec_warn () { # # usage: ynh_exec_warn_less your_command # usage: ynh_exec_warn_less "your_command | other_command" +# | arg: command - command to execute # # When using pipes, double quotes are required - otherwise, this helper will run the first command, and the whole output will be sent through the next pipe. # # If the command to execute uses double quotes, they have to be escaped or they will be interpreted and removed. # -# | arg: command - command to execute -# # Requires YunoHost version 3.2.0 or higher. ynh_exec_warn_less () { eval $@ 2>&1 @@ -148,13 +148,12 @@ ynh_exec_warn_less () { # # usage: ynh_exec_quiet your_command # usage: ynh_exec_quiet "your_command | other_command" +# | arg: command - command to execute # # When using pipes, double quotes are required - otherwise, this helper will run the first command, and the whole output will be sent through the next pipe. # # If the command to execute uses double quotes, they have to be escaped or they will be interpreted and removed. # -# | arg: command - command to execute -# # Requires YunoHost version 3.2.0 or higher. ynh_exec_quiet () { eval $@ > /dev/null @@ -164,13 +163,12 @@ ynh_exec_quiet () { # # usage: ynh_exec_fully_quiet your_command # usage: ynh_exec_fully_quiet "your_command | other_command" +# | arg: command - command to execute # # When using pipes, double quotes are required - otherwise, this helper will run the first command, and the whole output will be sent through the next pipe. # # If the command to execute uses double quotes, they have to be escaped or they will be interpreted and removed. # -# | arg: command - command to execute -# # Requires YunoHost version 3.2.0 or higher. ynh_exec_fully_quiet () { eval $@ > /dev/null 2>&1 @@ -216,8 +214,8 @@ base_time=$(date +%s) # usage: ynh_script_progression --message=message [--weight=weight] [--time] # | arg: -m, --message= - The text to print # | arg: -w, --weight= - The weight for this progression. This value is 1 by default. Use a bigger value for a longer part of the script. -# | arg: -t, --time= - Print the execution time since the last call to this helper. Especially usefull to define weights. The execution time is given for the duration since the previous call. So the weight should be applied to this previous call. -# | arg: -l, --last= - Use for the last call of the helper, to fill te progression bar. +# | arg: -t, --time - Print the execution time since the last call to this helper. Especially usefull to define weights. The execution time is given for the duration since the previous call. So the weight should be applied to this previous call. +# | arg: -l, --last - Use for the last call of the helper, to fill te progression bar. # # Requires YunoHost version 3.5.0 or higher. ynh_script_progression () { @@ -365,13 +363,12 @@ ynh_debug () { # # usage: ynh_debug_exec your_command # usage: ynh_debug_exec "your_command | other_command" +# | arg: command - command to execute # # When using pipes, double quotes are required - otherwise, this helper will run the first command, and the whole output will be sent through the next pipe. # # If the command to execute uses double quotes, they have to be escaped or they will be interpreted and removed. # -# | arg: command - command to execute -# # Requires YunoHost version 3.5.0 or higher. ynh_debug_exec () { ynh_debug --message="$(eval $@)" diff --git a/data/helpers.d/logrotate b/data/helpers.d/logrotate index b9af082a6..0fcc63009 100644 --- a/data/helpers.d/logrotate +++ b/data/helpers.d/logrotate @@ -3,9 +3,9 @@ # Use logrotate to manage the logfile # # usage: ynh_use_logrotate [--logfile=/log/file] [--nonappend] [--specific_user=user/group] -# | arg: -l, --logfile - absolute path of logfile -# | arg: -n, --nonappend - (optional) Replace the config file instead of appending this new config. -# | arg: -u, --specific_user : run logrotate as the specified user and group. If not specified logrotate is runned as root. +# | arg: -l, --logfile= - absolute path of logfile +# | arg: -n, --nonappend - (optional) Replace the config file instead of appending this new config. +# | arg: -u, --specific_user= - run logrotate as the specified user and group. If not specified logrotate is runned as root. # # If no --logfile is provided, /var/log/${app} will be used as default. # logfile can be just a directory, or a full path to a logfile : diff --git a/data/helpers.d/mysql b/data/helpers.d/mysql index 7edc633b4..84acc1029 100644 --- a/data/helpers.d/mysql +++ b/data/helpers.d/mysql @@ -4,13 +4,13 @@ MYSQL_ROOT_PWD_FILE=/etc/yunohost/mysql # Open a connection as a user # -# example: ynh_mysql_connect_as 'user' 'pass' <<< "UPDATE ...;" -# example: ynh_mysql_connect_as 'user' 'pass' < /path/to/file.sql +# example: ynh_mysql_connect_as --user="user" --password="pass" <<< "UPDATE ...;" +# example: ynh_mysql_connect_as --user="user" --password="pass" < /path/to/file.sql # # usage: ynh_mysql_connect_as --user=user --password=password [--database=database] -# | arg: -u, --user - the user name to connect as -# | arg: -p, --password - the user password -# | arg: -d, --database - the database to connect to +# | arg: -u, --user= - the user name to connect as +# | arg: -p, --password= - the user password +# | arg: -d, --database= - the database to connect to # # Requires YunoHost version 2.2.4 or higher. ynh_mysql_connect_as() { @@ -30,8 +30,8 @@ ynh_mysql_connect_as() { # Execute a command as root user # # usage: ynh_mysql_execute_as_root --sql=sql [--database=database] -# | arg: -s, --sql - the SQL command to execute -# | arg: -d, --database - the database to connect to +# | arg: -s, --sql= - the SQL command to execute +# | arg: -d, --database= - the database to connect to # # Requires YunoHost version 2.2.4 or higher. ynh_mysql_execute_as_root() { @@ -51,8 +51,8 @@ ynh_mysql_execute_as_root() { # Execute a command from a file as root user # # usage: ynh_mysql_execute_file_as_root --file=file [--database=database] -# | arg: -f, --file - the file containing SQL commands -# | arg: -d, --database - the database to connect to +# | arg: -f, --file= - the file containing SQL commands +# | arg: -d, --database= - the database to connect to # # Requires YunoHost version 2.2.4 or higher. ynh_mysql_execute_file_as_root() { @@ -114,10 +114,10 @@ ynh_mysql_drop_db() { # Dump a database # -# example: ynh_mysql_dump_db 'roundcube' > ./dump.sql +# example: ynh_mysql_dump_db --database=roundcube > ./dump.sql # # usage: ynh_mysql_dump_db --database=database -# | arg: -d, --database - the database name to dump +# | arg: -d, --database= - the database name to dump # | ret: the mysqldump output # # Requires YunoHost version 2.2.4 or higher. @@ -149,7 +149,8 @@ ynh_mysql_create_user() { # Check if a mysql user exists # # usage: ynh_mysql_user_exists --user=user -# | arg: -u, --user - the user for which to check existence +# | arg: -u, --user= - the user for which to check existence +# | exit: Return 1 if the user doesn't exist, 0 otherwise. # # Requires YunoHost version 2.2.4 or higher. ynh_mysql_user_exists() @@ -183,14 +184,14 @@ ynh_mysql_drop_user() { # Create a database, an user and its password. Then store the password in the app's config # +# usage: ynh_mysql_setup_db --db_user=user --db_name=name [--db_pwd=pwd] +# | arg: -u, --db_user= - Owner of the database +# | arg: -n, --db_name= - Name of the database +# | arg: -p, --db_pwd= - Password of the database. If not provided, a password will be generated +# # After executing this helper, the password of the created database will be available in $db_pwd # It will also be stored as "mysqlpwd" into the app settings. # -# usage: ynh_mysql_setup_db --db_user=user --db_name=name [--db_pwd=pwd] -# | arg: -u, --db_user - Owner of the database -# | arg: -n, --db_name - Name of the database -# | arg: -p, --db_pwd - Password of the database. If not provided, a password will be generated -# # Requires YunoHost version 2.6.4 or higher. ynh_mysql_setup_db () { # Declare an array to define the options of this helper. @@ -213,8 +214,8 @@ ynh_mysql_setup_db () { # Remove a database if it exists, and the associated user # # usage: ynh_mysql_remove_db --db_user=user --db_name=name -# | arg: -u, --db_user - Owner of the database -# | arg: -n, --db_name - Name of the database +# | arg: -u, --db_user= - Owner of the database +# | arg: -n, --db_name= - Name of the database # # Requires YunoHost version 2.6.4 or higher. ynh_mysql_remove_db () { diff --git a/data/helpers.d/network b/data/helpers.d/network index c8493d7ac..03df04c1e 100644 --- a/data/helpers.d/network +++ b/data/helpers.d/network @@ -5,7 +5,8 @@ # example: port=$(ynh_find_port --port=8080) # # usage: ynh_find_port --port=begin_port -# | arg: -p, --port - port to start to search +# | arg: -p, --port= - port to start to search +# | ret: the port number # # Requires YunoHost version 2.6.4 or higher. ynh_find_port () { @@ -29,7 +30,8 @@ ynh_find_port () { # example: ynh_port_available --port=1234 || ynh_die "Port 1234 is needs to be available for this app" # # usage: ynh_find_port --port=XYZ -# | arg: -p, --port - port to check +# | arg: -p, --port= - port to check +# | exit: Return 1 if the port is already used by another process. # # Requires YunoHost version 3.7.x or higher. ynh_port_available () { @@ -90,6 +92,7 @@ EOF # example: ynh_validate_ip4 111.222.333.444 # # usage: ynh_validate_ip4 --ip_address=ip_address +# | arg: -i, --ip_address= - the ipv4 address to check # | ret: 0 for valid ipv4 addresses, 1 otherwise # # Requires YunoHost version 2.2.4 or higher. @@ -111,6 +114,7 @@ ynh_validate_ip4() # example: ynh_validate_ip6 2000:dead:beef::1 # # usage: ynh_validate_ip6 --ip_address=ip_address +# | arg: -i, --ip_address= - the ipv6 address to check # | ret: 0 for valid ipv6 addresses, 1 otherwise # # Requires YunoHost version 2.2.4 or higher. diff --git a/data/helpers.d/nodejs b/data/helpers.d/nodejs index cb83e3136..3ede3c8c9 100644 --- a/data/helpers.d/nodejs +++ b/data/helpers.d/nodejs @@ -54,13 +54,13 @@ ynh_use_nodejs () { # Install a specific version of nodejs # -# n (Node version management) uses the PATH variable to store the path of the version of node it is going to use. -# That's how it changes the version -# # ynh_install_nodejs will install the version of node provided as argument by using n. # # usage: ynh_install_nodejs --nodejs_version=nodejs_version -# | arg: -n, --nodejs_version - Version of node to install. When possible, your should prefer to use major version number (e.g. 8 instead of 8.10.0). The crontab will then handle the update of minor versions when needed. +# | arg: -n, --nodejs_version= - Version of node to install. When possible, your should prefer to use major version number (e.g. 8 instead of 8.10.0). The crontab will then handle the update of minor versions when needed. +# +# n (Node version management) uses the PATH variable to store the path of the version of node it is going to use. +# That's how it changes the version # # Requires YunoHost version 2.7.12 or higher. ynh_install_nodejs () { diff --git a/data/helpers.d/php b/data/helpers.d/php index 1bbb6c84b..588bf7177 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -8,16 +8,16 @@ YNH_PHP_VERSION=${YNH_PHP_VERSION:-$YNH_DEFAULT_PHP_VERSION} # Create a dedicated php-fpm config # # usage 1: ynh_add_fpm_config [--phpversion=7.X] [--use_template] [--package=packages] [--dedicated_service] -# | arg: -v, --phpversion - Version of php to use. -# | arg: -t, --use_template - Use this helper in template mode. -# | arg: -p, --package - Additionnal php packages to install -# | arg: -d, --dedicated_service - Use a dedicated php-fpm service instead of the common one. +# | arg: -v, --phpversion= - Version of php to use. +# | arg: -t, --use_template - Use this helper in template mode. +# | arg: -p, --package= - Additionnal php packages to install +# | arg: -d, --dedicated_service - Use a dedicated php-fpm service instead of the common one. # # ----------------------------------------------------------------------------- # # usage 2: ynh_add_fpm_config [--phpversion=7.X] --usage=usage --footprint=footprint [--package=packages] [--dedicated_service] -# | arg: -v, --phpversion - Version of php to use. -# | arg: -f, --footprint - Memory footprint of the service (low/medium/high). +# | arg: -v, --phpversion= - Version of php to use. +# | arg: -f, --footprint= - Memory footprint of the service (low/medium/high). # low - Less than 20Mb of ram by pool. # medium - Between 20Mb and 40Mb of ram by pool. # high - More than 40Mb of ram by pool. @@ -25,13 +25,13 @@ YNH_PHP_VERSION=${YNH_PHP_VERSION:-$YNH_DEFAULT_PHP_VERSION} # To have this value, use the following command and stress the service. # watch -n0.5 ps -o user,cmd,%cpu,rss -u APP # -# | arg: -u, --usage - Expected usage of the service (low/medium/high). +# | arg: -u, --usage= - Expected usage of the service (low/medium/high). # low - Personal usage, behind the sso. # medium - Low usage, few people or/and publicly accessible. # high - High usage, frequently visited website. # -# | arg: -p, --package - Additionnal php packages to install for a specific version of php -# | arg: -d, --dedicated_service - Use a dedicated php-fpm service instead of the common one. +# | arg: -p, --package= - Additionnal php packages to install for a specific version of php +# | arg: -d, --dedicated_service - Use a dedicated php-fpm service instead of the common one. # # # The footprint of the service will be used to defined the maximum footprint we can allow, which is half the maximum RAM. @@ -310,8 +310,8 @@ ynh_remove_fpm_config () { # [internal] # # usage: ynh_install_php --phpversion=phpversion [--package=packages] -# | arg: -v, --phpversion - Version of php to install. -# | arg: -p, --package - Additionnal php packages to install +# | arg: -v, --phpversion= - Version of php to install. +# | arg: -p, --package= - Additionnal php packages to install ynh_install_php () { # Declare an array to define the options of this helper. local legacy_args=vp @@ -401,7 +401,7 @@ ynh_remove_php () { # [internal] # # usage: ynh_get_scalable_phpfpm --usage=usage --footprint=footprint [--print] -# | arg: -f, --footprint - Memory footprint of the service (low/medium/high). +# | arg: -f, --footprint= - Memory footprint of the service (low/medium/high). # low - Less than 20Mb of ram by pool. # medium - Between 20Mb and 40Mb of ram by pool. # high - More than 40Mb of ram by pool. @@ -409,12 +409,12 @@ ynh_remove_php () { # To have this value, use the following command and stress the service. # watch -n0.5 ps -o user,cmd,%cpu,rss -u APP # -# | arg: -u, --usage - Expected usage of the service (low/medium/high). +# | arg: -u, --usage= - Expected usage of the service (low/medium/high). # low - Personal usage, behind the sso. # medium - Low usage, few people or/and publicly accessible. # high - High usage, frequently visited website. # -# | arg: -p, --print - Print the result (intended for debug purpose only when packaging the app) +# | arg: -p, --print - Print the result (intended for debug purpose only when packaging the app) ynh_get_scalable_phpfpm () { local legacy_args=ufp # Declare an array to define the options of this helper. diff --git a/data/helpers.d/postgresql b/data/helpers.d/postgresql index f0aa6d0f0..4122deec6 100644 --- a/data/helpers.d/postgresql +++ b/data/helpers.d/postgresql @@ -9,9 +9,9 @@ PSQL_ROOT_PWD_FILE=/etc/yunohost/psql # ynh_psql_connect_as 'user' 'pass' < /path/to/file.sql # # usage: ynh_psql_connect_as --user=user --password=password [--database=database] -# | arg: -u, --user - the user name to connect as -# | arg: -p, --password - the user password -# | arg: -d, --database - the database to connect to +# | arg: -u, --user= - the user name to connect as +# | arg: -p, --password= - the user password +# | arg: -d, --database= - the database to connect to # # Requires YunoHost version 3.5.0 or higher. ynh_psql_connect_as() { @@ -31,8 +31,8 @@ ynh_psql_connect_as() { # Execute a command as root user # # usage: ynh_psql_execute_as_root --sql=sql [--database=database] -# | arg: -s, --sql - the SQL command to execute -# | arg: -d, --database - the database to connect to +# | arg: -s, --sql= - the SQL command to execute +# | arg: -d, --database= - the database to connect to # # Requires YunoHost version 3.5.0 or higher. ynh_psql_execute_as_root() { @@ -52,8 +52,8 @@ ynh_psql_execute_as_root() { # Execute a command from a file as root user # # usage: ynh_psql_execute_file_as_root --file=file [--database=database] -# | arg: -f, --file - the file containing SQL commands -# | arg: -d, --database - the database to connect to +# | arg: -f, --file= - the file containing SQL commands +# | arg: -d, --database= - the database to connect to # # Requires YunoHost version 3.5.0 or higher. ynh_psql_execute_file_as_root() { @@ -118,7 +118,7 @@ ynh_psql_drop_db() { # example: ynh_psql_dump_db 'roundcube' > ./dump.sql # # usage: ynh_psql_dump_db --database=database -# | arg: -d, --database - the database name to dump +# | arg: -d, --database= - the database name to dump # | ret: the psqldump output # # Requires YunoHost version 3.5.0 or higher. @@ -151,7 +151,8 @@ ynh_psql_create_user() { # Check if a psql user exists # # usage: ynh_psql_user_exists --user=user -# | arg: -u, --user - the user for which to check existence +# | arg: -u, --user= - the user for which to check existence +# | exit: Return 1 if the user doesn't exist, 0 otherwise ynh_psql_user_exists() { # Declare an array to define the options of this helper. local legacy_args=u @@ -171,7 +172,8 @@ ynh_psql_user_exists() { # Check if a psql database exists # # usage: ynh_psql_database_exists --database=database -# | arg: -d, --database - the database for which to check existence +# | arg: -d, --database= - the database for which to check existence +# | exit: Return 1 if the database doesn't exist, 0 otherwise ynh_psql_database_exists() { # Declare an array to define the options of this helper. local legacy_args=d @@ -202,13 +204,14 @@ ynh_psql_drop_user() { # Create a database, an user and its password. Then store the password in the app's config # +# usage: ynh_psql_setup_db --db_user=user --db_name=name [--db_pwd=pwd] +# | arg: -u, --db_user= - Owner of the database +# | arg: -n, --db_name= - Name of the database +# | arg: -p, --db_pwd= - Password of the database. If not given, a password will be generated +# # After executing this helper, the password of the created database will be available in $db_pwd # It will also be stored as "psqlpwd" into the app settings. # -# usage: ynh_psql_setup_db --db_user=user --db_name=name [--db_pwd=pwd] -# | arg: -u, --db_user - Owner of the database -# | arg: -n, --db_name - Name of the database -# | arg: -p, --db_pwd - Password of the database. If not given, a password will be generated ynh_psql_setup_db() { # Declare an array to define the options of this helper. local legacy_args=unp @@ -234,8 +237,8 @@ ynh_psql_setup_db() { # Remove a database if it exists, and the associated user # # usage: ynh_psql_remove_db --db_user=user --db_name=name -# | arg: -u, --db_user - Owner of the database -# | arg: -n, --db_name - Name of the database +# | arg: -u, --db_user= - Owner of the database +# | arg: -n, --db_name= - Name of the database ynh_psql_remove_db() { # Declare an array to define the options of this helper. local legacy_args=un diff --git a/data/helpers.d/setting b/data/helpers.d/setting index 86634dcc3..00a2a5188 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -3,8 +3,8 @@ # Get an application setting # # usage: ynh_app_setting_get --app=app --key=key -# | arg: -a, --app - the application id -# | arg: -k, --key - the setting to get +# | arg: -a, --app= - the application id +# | arg: -k, --key= - the setting to get # # Requires YunoHost version 2.2.4 or higher. ynh_app_setting_get() { @@ -22,9 +22,9 @@ ynh_app_setting_get() { # Set an application setting # # usage: ynh_app_setting_set --app=app --key=key --value=value -# | arg: -a, --app - the application id -# | arg: -k, --key - the setting name to set -# | arg: -v, --value - the setting value to set +# | arg: -a, --app= - the application id +# | arg: -k, --key= - the setting name to set +# | arg: -v, --value= - the setting value to set # # Requires YunoHost version 2.2.4 or higher. ynh_app_setting_set() { @@ -43,8 +43,8 @@ ynh_app_setting_set() { # Delete an application setting # # usage: ynh_app_setting_delete --app=app --key=key -# | arg: -a, --app - the application id -# | arg: -k, --key - the setting to delete +# | arg: -a, --app= - the application id +# | arg: -k, --key= - the setting to delete # # Requires YunoHost version 2.2.4 or higher. ynh_app_setting_delete() { @@ -117,8 +117,8 @@ EOF # example: ynh_webpath_available --domain=some.domain.tld --path_url=/coffee # # usage: ynh_webpath_available --domain=domain --path_url=path -# | arg: -d, --domain - the domain/host of the url -# | arg: -p, --path_url - the web path to check the availability of +# | arg: -d, --domain= - the domain/host of the url +# | arg: -p, --path_url= - the web path to check the availability of # # Requires YunoHost version 2.6.4 or higher. ynh_webpath_available () { @@ -138,9 +138,9 @@ ynh_webpath_available () { # example: ynh_webpath_register --app=wordpress --domain=some.domain.tld --path_url=/coffee # # usage: ynh_webpath_register --app=app --domain=domain --path_url=path -# | arg: -a, --app - the app for which the domain should be registered -# | arg: -d, --domain - the domain/host of the web path -# | arg: -p, --path_url - the web path to be registered +# | arg: -a, --app= - the app for which the domain should be registered +# | arg: -d, --domain= - the domain/host of the web path +# | arg: -p, --path_url= - the web path to be registered # # Requires YunoHost version 2.6.4 or higher. ynh_webpath_register () { @@ -158,12 +158,12 @@ ynh_webpath_register () { # Create a new permission for the app # -# example: ynh_permission_create --permission admin --url /admin --allowed alice bob +# example: ynh_permission_create --permission=admin --url=/admin --allowed="alice bob" # -# usage: ynh_permission_create --permission "permission" [--url "url"] [--allowed group1 group2] -# | arg: permission - the name for the permission (by default a permission named "main" already exist) -# | arg: url - (optional) URL for which access will be allowed/forbidden -# | arg: allowed - (optional) A list of group/user to allow for the permission +# usage: ynh_permission_create --permission "permission" [--url=url] [--allowed="group1 group2"] +# | arg: -p, --permission= - the name for the permission (by default a permission named "main" already exist) +# | arg: -u, --url= - (optional) URL for which access will be allowed/forbidden +# | arg: -a, --allowed= - (optional) A list of group/user to allow for the permission # # If provided, 'url' is assumed to be relative to the app domain/path if they # start with '/'. For example: @@ -202,10 +202,10 @@ ynh_permission_create() { # Remove a permission for the app (note that when the app is removed all permission is automatically removed) # -# example: ynh_permission_delete --permission editors +# example: ynh_permission_delete --permission=editors # -# usage: ynh_permission_delete --permission "permission" -# | arg: permission - the name for the permission (by default a permission named "main" is removed automatically when the app is removed) +# usage: ynh_permission_delete --permission="permission" +# | arg: -p, --permission= - the name for the permission (by default a permission named "main" is removed automatically when the app is removed) # # Requires YunoHost version 3.7.0 or higher. ynh_permission_delete() { @@ -221,7 +221,8 @@ ynh_permission_delete() { # Check if a permission exists # # usage: ynh_permission_exists --permission=permission -# | arg: -p, --permission - the permission to check +# | arg: -p, --permission= - the permission to check +# | exit: Return 1 if the permission doesn't exist, 0 otherwise # # Requires YunoHost version 3.7.0 or higher. ynh_permission_exists() { @@ -236,9 +237,9 @@ ynh_permission_exists() { # Redefine the url associated to a permission # -# usage: ynh_permission_url --permission "permission" --url "url" -# | arg: permission - the name for the permission (by default a permission named "main" is removed automatically when the app is removed) -# | arg: url - (optional) URL for which access will be allowed/forbidden +# usage: ynh_permission_url --permission="permission" [--url="url"] +# | arg: -p, --permission= - the name for the permission (by default a permission named "main" is removed automatically when the app is removed) +# | arg: -u, --url= - (optional) URL for which access will be allowed/forbidden # # Requires YunoHost version 3.7.0 or higher. ynh_permission_url() { @@ -262,12 +263,13 @@ ynh_permission_url() { # Update a permission for the app # -# usage: ynh_permission_update --permission "permission" --add "group" ["group" ...] --remove "group" ["group" ...] -# | arg: permission - the name for the permission (by default a permission named "main" already exist) -# | arg: add - the list of group or users to enable add to the permission -# | arg: remove - the list of group or users to remove from the permission +# example: ynh_permission_update --permission admin --add=samdoe --remove=all_users +# +# usage: ynh_permission_update --permission="permission" [--add="group1 group2"] [--remove="group1 group2"] +# | arg: -p, --permission= - the name for the permission (by default a permission named "main" already exist) +# | arg: -a, --add= - the list of group or users to enable add to the permission +# | arg: -r, --remove= - the list of group or users to remove from the permission # -# example: ynh_permission_update --permission admin --add samdoe --remove all_users # Requires YunoHost version 3.7.0 or higher. ynh_permission_update() { # Declare an array to define the options of this helper. @@ -288,14 +290,15 @@ ynh_permission_update() { yunohost user permission update "$app.$permission" ${add:-} ${remove:-} } -# Check if a permission exists -# -# usage: ynh_permission_has_user --permission=permission --user=user -# | arg: -p, --permission - the permission to check -# | arg: -u, --user - the user seek in the permission +# Check if a permission has an user # # example: ynh_permission_has_user --permission=main --user=visitors # +# usage: ynh_permission_has_user --permission=permission --user=user +# | arg: -p, --permission= - the permission to check +# | arg: -u, --user= - the user seek in the permission +# | exit: Return 1 if the permission doesn't have that user or doesn't exist, 0 otherwise +# # Requires YunoHost version 3.7.1 or higher. ynh_permission_has_user() { local legacy_args=pu diff --git a/data/helpers.d/string b/data/helpers.d/string index 7a37f29c3..a0bcdbfaf 100644 --- a/data/helpers.d/string +++ b/data/helpers.d/string @@ -5,7 +5,8 @@ # example: pwd=$(ynh_string_random --length=8) # # usage: ynh_string_random [--length=string_length] -# | arg: -l, --length - the string length to generate (default: 24) +# | arg: -l, --length= - the string length to generate (default: 24) +# | ret: the generated string # # Requires YunoHost version 2.2.4 or higher. ynh_string_random() { @@ -25,9 +26,9 @@ ynh_string_random() { # Substitute/replace a string (or expression) by another in a file # # usage: ynh_replace_string --match_string=match_string --replace_string=replace_string --target_file=target_file -# | arg: -m, --match_string - String to be searched and replaced in the file -# | arg: -r, --replace_string - String that will replace matches -# | arg: -f, --target_file - File in which the string will be replaced. +# | arg: -m, --match_string= - String to be searched and replaced in the file +# | arg: -r, --replace_string= - String that will replace matches +# | arg: -f, --target_file= - File in which the string will be replaced. # # As this helper is based on sed command, regular expressions and # references to sub-expressions can be used @@ -55,9 +56,9 @@ ynh_replace_string () { # Substitute/replace a special string by another in a file # # usage: ynh_replace_special_string --match_string=match_string --replace_string=replace_string --target_file=target_file -# | arg: -m, --match_string - String to be searched and replaced in the file -# | arg: -r, --replace_string - String that will replace matches -# | arg: -t, --target_file - File in which the string will be replaced. +# | arg: -m, --match_string= - String to be searched and replaced in the file +# | arg: -r, --replace_string= - String that will replace matches +# | arg: -t, --target_file= - File in which the string will be replaced. # # This helper will use ynh_replace_string, but as you can use special # characters, you can't use some regular expressions and sub-expressions. @@ -90,7 +91,7 @@ ynh_replace_special_string () { # example: dbname=$(ynh_sanitize_dbid $app) # # usage: ynh_sanitize_dbid --db_name=name -# | arg: -n, --db_name - name to correct/sanitize +# | arg: -n, --db_name= - name to correct/sanitize # | ret: the corrected name # # Requires YunoHost version 2.2.4 or higher. @@ -121,7 +122,7 @@ ynh_sanitize_dbid () { # ynh_normalize_url_path / # -> / # # usage: ynh_normalize_url_path --path_url=path_to_normalize -# | arg: -p, --path_url - URL path to normalize before using it +# | arg: -p, --path_url= - URL path to normalize before using it # # Requires YunoHost version 2.6.4 or higher. ynh_normalize_url_path () { diff --git a/data/helpers.d/systemd b/data/helpers.d/systemd index 871d6459d..5117aeb99 100644 --- a/data/helpers.d/systemd +++ b/data/helpers.d/systemd @@ -3,8 +3,8 @@ # Create a dedicated systemd config # # usage: ynh_add_systemd_config [--service=service] [--template=template] -# | arg: -s, --service - Service name (optionnal, $app by default) -# | arg: -t, --template - Name of template file (optionnal, this is 'systemd' by default, meaning ./conf/systemd.service will be used as template) +# | arg: -s, --service= - Service name (optionnal, $app by default) +# | arg: -t, --template= - Name of template file (optionnal, this is 'systemd' by default, meaning ./conf/systemd.service will be used as template) # # This will use the template ../conf/.service # to generate a systemd config, by replacing the following keywords @@ -48,7 +48,7 @@ ynh_add_systemd_config () { # Remove the dedicated systemd config # # usage: ynh_remove_systemd_config [--service=service] -# | arg: -s, --service - Service name (optionnal, $app by default) +# | arg: -s, --service= - Service name (optionnal, $app by default) # # Requires YunoHost version 2.7.2 or higher. ynh_remove_systemd_config () { @@ -72,7 +72,7 @@ ynh_remove_systemd_config () { # Start (or other actions) a service, print a log in case of failure and optionnaly wait until the service is completely started # -# usage: ynh_systemd_action [-n service_name] [-a action] [ [-l "line to match"] [-p log_path] [-t timeout] [-e length] ] +# usage: ynh_systemd_action [--service_name=service_name] [--action=action] [ [--line_match="line to match"] [--log_path=log_path] [--timeout=300] [--length=20] ] # | arg: -n, --service_name= - Name of the service to start. Default : $app # | arg: -a, --action= - Action to perform with systemctl. Default: start # | arg: -l, --line_match= - Line to match - The line to find in the log to attest the service have finished to boot. If not defined it don't wait until the service is completely started. WARNING: When using --line_match, you should always add `ynh_clean_check_starting` into your `ynh_clean_setup` at the beginning of the script. Otherwise, tail will not stop in case of failure of the script. The script will then hang forever. diff --git a/data/helpers.d/user b/data/helpers.d/user index ff6c4e6ea..304658ff8 100644 --- a/data/helpers.d/user +++ b/data/helpers.d/user @@ -5,7 +5,8 @@ # example: ynh_user_exists 'toto' || exit 1 # # usage: ynh_user_exists --username=username -# | arg: -u, --username - the username to check +# | arg: -u, --username= - the username to check +# | exit: Return 1 if the user doesn't exist, 0 otherwise # # Requires YunoHost version 2.2.4 or higher. ynh_user_exists() { @@ -24,8 +25,8 @@ ynh_user_exists() { # example: mail=$(ynh_user_get_info 'toto' 'mail') # # usage: ynh_user_get_info --username=username --key=key -# | arg: -u, --username - the username to retrieve info from -# | arg: -k, --key - the key to retrieve +# | arg: -u, --username= - the username to retrieve info from +# | arg: -k, --key= - the key to retrieve # | ret: string - the key's value # # Requires YunoHost version 2.2.4 or higher. @@ -57,7 +58,8 @@ ynh_user_list() { # Check if a user exists on the system # # usage: ynh_system_user_exists --username=username -# | arg: -u, --username - the username to check +# | arg: -u, --username= - the username to check +# | exit: Return 1 if the user doesn't exist, 0 otherwise # # Requires YunoHost version 2.2.4 or higher. ynh_system_user_exists() { @@ -74,7 +76,8 @@ ynh_system_user_exists() { # Check if a group exists on the system # # usage: ynh_system_group_exists --group=group -# | arg: -g, --group - the group to check +# | arg: -g, --group= - the group to check +# | exit: Return 1 if the group doesn't exist, 0 otherwise ynh_system_group_exists() { # Declare an array to define the options of this helper. local legacy_args=g @@ -95,9 +98,9 @@ ynh_system_group_exists() { # ynh_system_user_create --username=discourse --home_dir=/var/www/discourse --use_shell # # usage: ynh_system_user_create --username=user_name [--home_dir=home_dir] [--use_shell] -# | arg: -u, --username - Name of the system user that will be create -# | arg: -h, --home_dir - Path of the home dir for the user. Usually the final path of the app. If this argument is omitted, the user will be created without home -# | arg: -s, --use_shell - Create a user using the default login shell if present. If this argument is omitted, the user will be created with /usr/sbin/nologin shell +# | arg: -u, --username= - Name of the system user that will be create +# | arg: -h, --home_dir= - Path of the home dir for the user. Usually the final path of the app. If this argument is omitted, the user will be created without home +# | arg: -s, --use_shell - Create a user using the default login shell if present. If this argument is omitted, the user will be created with /usr/sbin/nologin shell # # Requires YunoHost version 2.6.4 or higher. ynh_system_user_create () { @@ -133,7 +136,7 @@ ynh_system_user_create () { # Delete a system user # # usage: ynh_system_user_delete --username=user_name -# | arg: -u, --username - Name of the system user that will be create +# | arg: -u, --username= - Name of the system user that will be create # # Requires YunoHost version 2.6.4 or higher. ynh_system_user_delete () { diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 6b75426fc..46242e634 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -55,6 +55,10 @@ ynh_abort_if_errors () { # Download, check integrity, uncompress and patch the source from app.src # +# usage: ynh_setup_source --dest_dir=dest_dir [--source_id=source_id] +# | arg: -d, --dest_dir= - Directory where to setup sources +# | arg: -s, --source_id= - Name of the app, if the package contains more than one app +# # The file conf/app.src need to contains: # # SOURCE_URL=Address to download the app archive @@ -93,11 +97,6 @@ ynh_abort_if_errors () { # Finally, patches named sources/patches/${src_id}-*.patch and extra files in # sources/extra_files/$src_id will be applied to dest_dir # -# -# usage: ynh_setup_source --dest_dir=dest_dir [--source_id=source_id] -# | arg: -d, --dest_dir - Directory where to setup sources -# | arg: -s, --source_id - Name of the app, if the package contains more than one app -# # Requires YunoHost version 2.6.4 or higher. ynh_setup_source () { # Declare an array to define the options of this helper. @@ -204,9 +203,6 @@ ynh_setup_source () { } # Curl abstraction to help with POST requests to local pages (such as installation forms) -# For multiple calls, cookies are persisted between each call for the same app -# -# $domain and $path_url should be defined externally (and correspond to the domain.tld and the /path (of the app?)) # # example: ynh_local_curl "/install.php?installButton" "foo=$var1" "bar=$var2" # @@ -216,6 +212,10 @@ ynh_setup_source () { # | arg: key2=value2 - (Optionnal) Another POST key and corresponding value # | arg: ... - (Optionnal) More POST keys and values # +# For multiple calls, cookies are persisted between each call for the same app +# +# $domain and $path_url should be defined externally (and correspond to the domain.tld and the /path (of the app?)) +# # Requires YunoHost version 2.6.4 or higher. ynh_local_curl () { # Define url of page to curl @@ -306,7 +306,7 @@ properly with chmod/chown." # Remove a file or a directory securely # # usage: ynh_secure_remove --file=path_to_remove -# | arg: -f, --file - File or directory to remove +# | arg: -f, --file= - File or directory to remove # # Requires YunoHost version 2.6.4 or higher. ynh_secure_remove () { @@ -384,9 +384,10 @@ ynh_get_plain_key() { # Read the value of a key in a ynh manifest file # -# usage: ynh_read_manifest manifest key -# | arg: -m, --manifest= - Path of the manifest to read -# | arg: -k, --key= - Name of the key to find +# usage: ynh_read_manifest --manifest="manifest.json" --key="key" +# | arg: -m, --manifest= - Path of the manifest to read +# | arg: -k, --key= - Name of the key to find +# | ret: the value associate to that key # # Requires YunoHost version 3.5.0 or higher. ynh_read_manifest () { @@ -408,14 +409,15 @@ ynh_read_manifest () { # Read the upstream version from the manifest # +# usage: ynh_app_upstream_version [--manifest="manifest.json"] +# | arg: -m, --manifest= - Path of the manifest to read +# | ret: the version number of the upstream app +# # The version number in the manifest is defined by ~ynh # For example : 4.3-2~ynh3 # This include the number before ~ynh # In the last example it return 4.3-2 # -# usage: ynh_app_upstream_version [-m manifest] -# | arg: -m, --manifest= - Path of the manifest to read -# # Requires YunoHost version 3.5.0 or higher. ynh_app_upstream_version () { # Declare an array to define the options of this helper. @@ -432,14 +434,15 @@ ynh_app_upstream_version () { # Read package version from the manifest # +# usage: ynh_app_package_version [--manifest="manifest.json"] +# | arg: -m, --manifest= - Path of the manifest to read +# | ret: the version number of the package +# # The version number in the manifest is defined by ~ynh # For example : 4.3-2~ynh3 # This include the number after ~ynh # In the last example it return 3 # -# usage: ynh_app_package_version [-m manifest] -# | arg: -m, --manifest= - Path of the manifest to read -# # Requires YunoHost version 3.5.0 or higher. ynh_app_package_version () { # Declare an array to define the options of this helper. From 6fb1e62a4c79035c7a0a7d67b985386bad543373 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Mon, 20 Apr 2020 21:00:45 +0200 Subject: [PATCH 1051/3170] Clean getopts arguments --- data/helpers.d/backup | 15 +++++++-------- data/helpers.d/fail2ban | 5 +++-- data/helpers.d/logging | 3 ++- data/helpers.d/logrotate | 6 +++--- data/helpers.d/setting | 19 ++++++++++++------- data/helpers.d/systemd | 15 +++++++-------- data/helpers.d/utils | 4 ++-- 7 files changed, 36 insertions(+), 31 deletions(-) diff --git a/data/helpers.d/backup b/data/helpers.d/backup index 2fae73ba0..9603ba525 100644 --- a/data/helpers.d/backup +++ b/data/helpers.d/backup @@ -53,9 +53,9 @@ ynh_backup() { local not_mandatory # Manage arguments with getopts ynh_handle_getopts_args "$@" - local dest_path="${dest_path:-}" - local is_big="${is_big:-0}" - local not_mandatory="${not_mandatory:-0}" + dest_path="${dest_path:-}" + is_big="${is_big:-0}" + not_mandatory="${not_mandatory:-0}" BACKUP_CORE_ONLY=${BACKUP_CORE_ONLY:-0} test -n "${app:-}" && do_not_backup_data=$(ynh_app_setting_get --app=$app --key=do_not_backup_data) @@ -229,17 +229,16 @@ ynh_restore_file () { local legacy_args=odm local -A args_array=( [o]=origin_path= [d]=dest_path= [m]=not_mandatory ) local origin_path - local archive_path local dest_path local not_mandatory # Manage arguments with getopts ynh_handle_getopts_args "$@" - local origin_path="/${origin_path#/}" - local archive_path="$YNH_CWD${origin_path}" + origin_path="/${origin_path#/}" # Default value for dest_path = /$origin_path - local dest_path="${dest_path:-$origin_path}" - local not_mandatory="${not_mandatory:-0}" + dest_path="${dest_path:-$origin_path}" + not_mandatory="${not_mandatory:-0}" + local archive_path="$YNH_CWD${origin_path}" # If archive_path doesn't exist, search for a corresponding path in CSV if [ ! -d "$archive_path" ] && [ ! -f "$archive_path" ] && [ ! -L "$archive_path" ] then diff --git a/data/helpers.d/fail2ban b/data/helpers.d/fail2ban index 1eef67f5c..54581483d 100644 --- a/data/helpers.d/fail2ban +++ b/data/helpers.d/fail2ban @@ -74,9 +74,10 @@ ynh_add_fail2ban_config () { local use_template # Manage arguments with getopts ynh_handle_getopts_args "$@" - use_template="${use_template:-0}" max_retry=${max_retry:-3} ports=${ports:-http,https} + others_var=${others_var:-} + use_template="${use_template:-0}" finalfail2banjailconf="/etc/fail2ban/jail.d/$app.conf" finalfail2banfilterconf="/etc/fail2ban/filter.d/$app.conf" @@ -96,7 +97,7 @@ ynh_add_fail2ban_config () { fi # Replace all other variable given as arguments - for var_to_replace in ${others_var:-} + for var_to_replace in $others_var do # ${var_to_replace^^} make the content of the variable on upper-cases # ${!var_to_replace} get the content of the variable named $var_to_replace diff --git a/data/helpers.d/logging b/data/helpers.d/logging index 49374ec1e..37dfd286c 100644 --- a/data/helpers.d/logging +++ b/data/helpers.d/logging @@ -15,9 +15,10 @@ ynh_die() { local ret_code # Manage arguments with getopts ynh_handle_getopts_args "$@" + ret_code=${ret_code:-1} echo "$message" 1>&2 - exit "${ret_code:-1}" + exit "$ret_code" } # Display a message in the 'INFO' logging category diff --git a/data/helpers.d/logrotate b/data/helpers.d/logrotate index 0fcc63009..7df954c15 100644 --- a/data/helpers.d/logrotate +++ b/data/helpers.d/logrotate @@ -26,9 +26,9 @@ ynh_use_logrotate () { local specific_user # Manage arguments with getopts ynh_handle_getopts_args "$@" - local logfile="${logfile:-}" - local nonappend="${nonappend:-0}" - local specific_user="${specific_user:-}" + logfile="${logfile:-}" + nonappend="${nonappend:-0}" + specific_user="${specific_user:-}" # LEGACY CODE - PRE GETOPTS if [ $# -gt 0 ] && [ "$1" == "--non-append" ] diff --git a/data/helpers.d/setting b/data/helpers.d/setting index 00a2a5188..61397151b 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -185,19 +185,21 @@ ynh_permission_create() { local url local allowed ynh_handle_getopts_args "$@" + url=${url:-} + allowed=${allowed:-} - if [[ -n ${url:-} ]] + if [[ -n $url ]] then url="'$url'" else url="None" fi - if [[ -n ${allowed:-} ]]; then + if [[ -n $allowed ]]; then allowed=",allowed=['${allowed//';'/"','"}']" fi - yunohost tools shell -c "from yunohost.permission import permission_create; permission_create('$app.$permission', url=$url ${allowed:-} , sync_perm=False)" + yunohost tools shell -c "from yunohost.permission import permission_create; permission_create('$app.$permission', url=$url $allowed , sync_perm=False)" } # Remove a permission for the app (note that when the app is removed all permission is automatically removed) @@ -249,8 +251,9 @@ ynh_permission_url() { local permission local url ynh_handle_getopts_args "$@" + url=${url:-} - if [[ -n ${url:-} ]] + if [[ -n $url ]] then url="'$url'" else @@ -279,15 +282,17 @@ ynh_permission_update() { local add local remove ynh_handle_getopts_args "$@" + add=${add:-} + remove=${remove:-} - if [[ -n ${add:-} ]]; then + if [[ -n $add ]]; then add="--add ${add//';'/" "}" fi - if [[ -n ${remove:-} ]]; then + if [[ -n $remove ]]; then remove="--remove ${remove//';'/" "} " fi - yunohost user permission update "$app.$permission" ${add:-} ${remove:-} + yunohost user permission update "$app.$permission" $add $remove } # Check if a permission has an user diff --git a/data/helpers.d/systemd b/data/helpers.d/systemd index 5117aeb99..c718e50c2 100644 --- a/data/helpers.d/systemd +++ b/data/helpers.d/systemd @@ -89,18 +89,17 @@ ynh_systemd_action() { local length local log_path local timeout - # Manage arguments with getopts ynh_handle_getopts_args "$@" - - local service_name="${service_name:-$app}" - local action=${action:-start} - local log_path="${log_path:-/var/log/$service_name/$service_name.log}" - local length=${length:-20} - local timeout=${timeout:-300} + service_name="${service_name:-$app}" + action=${action:-start} + line_match=${line_match:-} + length=${length:-20} + log_path="${log_path:-/var/log/$service_name/$service_name.log}" + timeout=${timeout:-300} # Start to read the log - if [[ -n "${line_match:-}" ]] + if [[ -n "$line_match" ]] then local templog="$(mktemp)" # Following the starting of the app in its log diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 46242e634..13d3a5dcd 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -426,8 +426,8 @@ ynh_app_upstream_version () { local manifest # Manage arguments with getopts ynh_handle_getopts_args "$@" - manifest="${manifest:-../manifest.json}" + version_key=$(ynh_read_manifest --manifest="$manifest" --manifest_key="version") echo "${version_key/~ynh*/}" } @@ -451,8 +451,8 @@ ynh_app_package_version () { local manifest # Manage arguments with getopts ynh_handle_getopts_args "$@" - manifest="${manifest:-../manifest.json}" + version_key=$(ynh_read_manifest --manifest="$manifest" --manifest_key="version") echo "${version_key/*~ynh/}" } From 23a083b08770b97adf34903f04b2f5d06008950a Mon Sep 17 00:00:00 2001 From: Kay0u Date: Mon, 20 Apr 2020 23:50:42 +0200 Subject: [PATCH 1052/3170] YNH_DEFAULT_PHP_VERSION is now readonly --- data/helpers.d/php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/php b/data/helpers.d/php index d5b17c58f..747d40321 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -1,6 +1,6 @@ #!/bin/bash -YNH_DEFAULT_PHP_VERSION=7.0 +readonly YNH_DEFAULT_PHP_VERSION=7.0 # Declare the actual php version to use. # A packager willing to use another version of php can override the variable into its _common.sh. YNH_PHP_VERSION=${YNH_PHP_VERSION:-$YNH_DEFAULT_PHP_VERSION} From b392efdf85f3f9528772ff76dd40ad3239613bef Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 21 Apr 2020 02:42:46 +0200 Subject: [PATCH 1053/3170] Also anonymize folder name containing %2e instead of dot --- src/yunohost/utils/yunopaste.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/yunohost/utils/yunopaste.py b/src/yunohost/utils/yunopaste.py index 530295735..dc8b6fb8d 100644 --- a/src/yunohost/utils/yunopaste.py +++ b/src/yunohost/utils/yunopaste.py @@ -37,10 +37,18 @@ def yunopaste(data): def anonymize(data): + def anonymize_domain(data, domain, redact): + data = data.replace(domain, redact) + # This stuff appears sometimes because some folder in + # /var/lib/metronome/ have some folders named this way + data = data.replace(domain.replace(".", "%2e"), redact.replace(".", "%2e")) + return data + + # First, let's replace every occurence of the main domain by "domain.tld" # This should cover a good fraction of the info leaked main_domain = _get_maindomain() - data = data.replace(main_domain, "maindomain.tld") + data = anonymize_domain(data, main_domain, "maindomain.tld") # Next, let's replace other domains. We do this in increasing lengths, # because e.g. knowing that the domain is a sub-domain of another domain may @@ -55,7 +63,7 @@ def anonymize(data): for domain in domains: if domain not in data: continue - data = data.replace(domain, "domain%s.tld" % count) + data = anonymize_domain(data, domain, "domain%s.tld" % count) count += 1 # We also want to anonymize the ips From 99ad8cc492de863d86b55a1128635eae8892babd Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 21 Apr 2020 04:45:16 +0200 Subject: [PATCH 1054/3170] Force-flush the regen-conf for nginx domain conf when adding/removing a domain... --- src/yunohost/domain.py | 32 +++++++++++++++++++++++++++++++- src/yunohost/regenconf.py | 12 ++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index c725b58c9..99f6605f2 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -33,7 +33,7 @@ from yunohost.utils.error import YunohostError from moulinette.utils.log import getActionLogger from yunohost.app import app_ssowatconf -from yunohost.regenconf import regen_conf +from yunohost.regenconf import regen_conf, _force_clear_hashes, _process_regen_conf from yunohost.utils.network import get_public_ip from yunohost.log import is_unit_operation from yunohost.hook import hook_callback @@ -122,6 +122,17 @@ def domain_add(operation_logger, domain, dyndns=False): # Don't regen these conf if we're still in postinstall if os.path.exists('/etc/yunohost/installed'): + # Sometime we have weird issues with the regenconf where some files + # appears as manually modified even though they weren't touched ... + # There are a few ideas why this happens (like backup/restore nginx + # conf ... which we shouldnt do ...). This in turns creates funky + # situation where the regenconf may refuse to re-create the conf + # (when re-creating a domain..) + # So here we force-clear the has out of the regenconf if it exists. + # This is a pretty ad hoc solution and only applied to nginx + # because it's one of the major service, but in the long term we + # should identify the root of this bug... + _force_clear_hashes(["/etc/nginx/conf.d/%s.conf" % domain]) regen_conf(names=['nginx', 'metronome', 'dnsmasq', 'postfix', 'rspamd']) app_ssowatconf() @@ -186,6 +197,25 @@ def domain_remove(operation_logger, domain, force=False): os.system('rm -rf /etc/yunohost/certs/%s' % domain) + # Sometime we have weird issues with the regenconf where some files + # appears as manually modified even though they weren't touched ... + # There are a few ideas why this happens (like backup/restore nginx + # conf ... which we shouldnt do ...). This in turns creates funky + # situation where the regenconf may refuse to re-create the conf + # (when re-creating a domain..) + # + # So here we force-clear the has out of the regenconf if it exists. + # This is a pretty ad hoc solution and only applied to nginx + # because it's one of the major service, but in the long term we + # should identify the root of this bug... + _force_clear_hashes(["/etc/nginx/conf.d/%s.conf" % domain]) + # And in addition we even force-delete the file Otherwise, if the file was + # manually modified, it may not get removed by the regenconf which leads to + # catastrophic consequences of nginx breaking because it can't load the + # cert file which disappeared etc.. + if os.path.exists("/etc/nginx/conf.d/%s.conf" % domain): + _process_regen_conf("/etc/nginx/conf.d/%s.conf" % domain, new_conf=None, save=True) + regen_conf(names=['nginx', 'metronome', 'dnsmasq', 'postfix']) app_ssowatconf() diff --git a/src/yunohost/regenconf.py b/src/yunohost/regenconf.py index ad84c8164..4062628aa 100644 --- a/src/yunohost/regenconf.py +++ b/src/yunohost/regenconf.py @@ -473,6 +473,18 @@ def _update_conf_hashes(category, hashes): _save_regenconf_infos(categories) +def _force_clear_hashes(paths): + + categories = _get_regenconf_infos() + for path in paths: + for category in categories.keys(): + if path in categories[category]['conffiles']: + logger.debug("force-clearing old conf hash for %s in category %s" % (path, category)) + del categories[category]['conffiles'][path] + + _save_regenconf_infos(categories) + + def _process_regen_conf(system_conf, new_conf=None, save=True): """Regenerate a given system configuration file From 6d42baff38f1876473a88f9a59a9192a81f158ff Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 21 Apr 2020 04:48:13 +0200 Subject: [PATCH 1055/3170] Be more robust against broken config or service failing to start, show info to help debugging --- data/hooks/conf_regen/15-nginx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/data/hooks/conf_regen/15-nginx b/data/hooks/conf_regen/15-nginx index f8b7d8062..86c7c2438 100755 --- a/data/hooks/conf_regen/15-nginx +++ b/data/hooks/conf_regen/15-nginx @@ -27,7 +27,8 @@ do_init_regen() { ynh_render_template "yunohost_admin.conf" "${nginx_conf_dir}/yunohost_admin.conf" # Restart nginx if conf looks good, otherwise display error and exit unhappy - nginx -t 2>/dev/null && service nginx restart || (nginx -t && exit 1) + nginx -t 2>/dev/null || { nginx -t; exit 1; } + systemctl restart nginx || { journalctl --no-pager --lines=10 -u nginx >&2; exit 1; } exit 0 } @@ -125,9 +126,9 @@ do_post_regen() { fi done - - # Reload nginx configuration - pgrep nginx && service nginx reload + # Reload nginx if conf looks good, otherwise display error and exit unhappy + nginx -t 2>/dev/null || { nginx -t; exit 1; } + pgrep nginx && systemctl reload nginx || { journalctl --no-pager --lines=10 -u nginx >&2; exit 1; } } FORCE=${2:-0} From 56a1fba297e98d09297d0d2a22faafceace34250 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 21 Apr 2020 04:48:26 +0200 Subject: [PATCH 1056/3170] Add regenconf tests for previous commits --- src/yunohost/tests/test_regenconf.py | 80 ++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 src/yunohost/tests/test_regenconf.py diff --git a/src/yunohost/tests/test_regenconf.py b/src/yunohost/tests/test_regenconf.py new file mode 100644 index 000000000..357f96c88 --- /dev/null +++ b/src/yunohost/tests/test_regenconf.py @@ -0,0 +1,80 @@ +import glob +import os +import pytest +import shutil +import requests + +from conftest import message, raiseYunohostError + +from moulinette import m18n +from moulinette.utils.filesystem import mkdir + +from yunohost.domain import _get_maindomain, domain_add, domain_remove, domain_list +from yunohost.utils.error import YunohostError +from yunohost.regenconf import manually_modified_files, _get_conf_hashes, _force_clear_hashes + +TEST_DOMAIN = "secondarydomain.test" +TEST_DOMAIN_NGINX_CONFIG = "/etc/nginx/conf.d/secondarydomain.test.conf" + +def setup_function(function): + + _force_clear_hashes([TEST_DOMAIN_NGINX_CONFIG]) + clean() + +def teardown_function(function): + + clean() + _force_clear_hashes([TEST_DOMAIN_NGINX_CONFIG]) + +def clean(): + + assert os.system("pgrep slapd >/dev/null") == 0 + assert os.system("pgrep nginx >/dev/null") == 0 + + if TEST_DOMAIN in domain_list()["domains"]: + domain_remove(TEST_DOMAIN) + assert not os.path.exists(TEST_DOMAIN_NGINX_CONFIG) + + os.system("rm -f %s" % TEST_DOMAIN_NGINX_CONFIG) + + assert os.system("nginx -t 2>/dev/null") == 0 + + assert not os.path.exists(TEST_DOMAIN_NGINX_CONFIG) + assert TEST_DOMAIN_NGINX_CONFIG not in _get_conf_hashes("nginx") + assert TEST_DOMAIN_NGINX_CONFIG not in manually_modified_files() + + +def test_add_domain(): + + domain_add(TEST_DOMAIN) + + assert TEST_DOMAIN in domain_list()["domains"] + + assert os.path.exists(TEST_DOMAIN_NGINX_CONFIG) + + assert TEST_DOMAIN_NGINX_CONFIG in _get_conf_hashes("nginx") + assert TEST_DOMAIN_NGINX_CONFIG not in manually_modified_files() + + +def test_add_and_edit_domain_conf(): + + domain_add(TEST_DOMAIN) + + assert os.path.exists(TEST_DOMAIN_NGINX_CONFIG) + assert TEST_DOMAIN_NGINX_CONFIG in _get_conf_hashes("nginx") + assert TEST_DOMAIN_NGINX_CONFIG not in manually_modified_files() + + os.system("echo ' ' >> %s" % TEST_DOMAIN_NGINX_CONFIG) + + assert TEST_DOMAIN_NGINX_CONFIG in manually_modified_files() + + +def test_add_domain_conf_already_exists(): + + os.system("echo ' ' >> %s" % TEST_DOMAIN_NGINX_CONFIG) + + domain_add(TEST_DOMAIN) + + assert os.path.exists(TEST_DOMAIN_NGINX_CONFIG) + assert TEST_DOMAIN_NGINX_CONFIG in _get_conf_hashes("nginx") + assert TEST_DOMAIN_NGINX_CONFIG not in manually_modified_files() From 6eab62ceb153c56f99a07ad57010f19ba78ef07f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Tue, 21 Apr 2020 11:15:24 +0200 Subject: [PATCH 1057/3170] Fix show_tile settings --- 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 f113a208d..f0c392daa 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -834,9 +834,9 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu app_settings = _get_app_settings(app_instance_name) domain = app_settings.get('domain', None) path = app_settings.get('path', None) - user_permission_update(app_instance_name + ".main", show_tile=True, sync_perm=False) if domain and path and user_permission_list(full=True, full_path=False)['permissions'][app_instance_name + '.main']['url'] is None: permission_url(app_instance_name + ".main", url='/', sync_perm=False) + user_permission_update(app_instance_name + ".main", show_tile=True, sync_perm=False) _migrate_legacy_permissions(app_instance_name) From 03379007b42bd313325430aa94a89c47b4e9b5b2 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Tue, 21 Apr 2020 14:48:52 +0200 Subject: [PATCH 1058/3170] Update YunoHost version requirements --- data/helpers.d/apt | 10 ++++++++++ data/helpers.d/backup | 2 ++ data/helpers.d/hardware | 4 ++++ data/helpers.d/logrotate | 1 + data/helpers.d/network | 2 +- data/helpers.d/nginx | 1 + data/helpers.d/php | 6 ++++++ data/helpers.d/postgresql | 9 +++++++++ data/helpers.d/systemd | 6 +++++- data/helpers.d/user | 2 ++ data/helpers.d/utils | 1 + 11 files changed, 42 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index 50db7613f..9e3f26b90 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -292,6 +292,8 @@ EOF # usage: ynh_add_app_dependencies --package=phpversion [--replace] # | arg: -p, --package= - Packages to add as dependencies for the app. # | arg: -r, --replace - Replace dependencies instead of adding to existing ones. +# +# Requires YunoHost version 3.8.1 or higher. ynh_add_app_dependencies () { # Declare an array to define the options of this helper. local legacy_args=pr @@ -338,6 +340,8 @@ ynh_remove_app_dependencies () { # | arg: -p, --package= - The packages to install from this extra repository # | arg: -k, --key= - url to get the public key. # | arg: -n, --name= - Name for the files for this repo, $app as default value. +# +# Requires YunoHost version 3.8.1 or higher. ynh_install_extra_app_dependencies () { # Declare an array to define the options of this helper. local legacy_args=rpkn @@ -376,6 +380,8 @@ ynh_install_extra_app_dependencies () { # | arg: -p, --priority= - Priority for the pin # | arg: -n, --name= - Name for the files for this repo, $app as default value. # | arg: -a, --append - Do not overwrite existing files. +# +# Requires YunoHost version 3.8.1 or higher. ynh_install_extra_repo () { # Declare an array to define the options of this helper. local legacy_args=rkpna @@ -445,6 +451,8 @@ ynh_install_extra_repo () { # # usage: ynh_remove_extra_repo [--name=name] # | arg: -n, --name= - Name for the files for this repo, $app as default value. +# +# Requires YunoHost version 3.8.1 or higher. ynh_remove_extra_repo () { # Declare an array to define the options of this helper. local legacy_args=n @@ -478,6 +486,7 @@ ynh_remove_extra_repo () { # uri suite component # ynh_add_repo --uri=http://forge.yunohost.org/debian/ --suite=stretch --component=stable # +# Requires YunoHost version 3.8.1 or higher. ynh_add_repo () { # Declare an array to define the options of this helper. local legacy_args=uscna @@ -518,6 +527,7 @@ ynh_add_repo () { # # See https://manpages.debian.org/stretch/apt/apt_preferences.5.en.html#How_APT_Interprets_Priorities for information about pinning. # +# Requires YunoHost version 3.8.1 or higher. ynh_pin_repo () { # Declare an array to define the options of this helper. local legacy_args=pirna diff --git a/data/helpers.d/backup b/data/helpers.d/backup index 9603ba525..a62f6c104 100644 --- a/data/helpers.d/backup +++ b/data/helpers.d/backup @@ -41,6 +41,7 @@ CAN_BIND=${CAN_BIND:-1} # # => "/etc/nginx/conf.d/$domain.d/$app.conf","apps/wordpress/conf/$app.conf" # # Requires YunoHost version 2.4.0 or higher. +# Requires YunoHost version 3.5.0 or higher for the argument --not_mandatory ynh_backup() { # TODO find a way to avoid injection by file strange naming ! @@ -224,6 +225,7 @@ with open(sys.argv[1], 'r') as backup_file: # /etc/nginx/conf.d/$domain.d/$app.conf # # Requires YunoHost version 2.6.4 or higher. +# Requires YunoHost version 3.5.0 or higher for the argument --not_mandatory ynh_restore_file () { # Declare an array to define the options of this helper. local legacy_args=odm diff --git a/data/helpers.d/hardware b/data/helpers.d/hardware index b46edcdd3..6702a8548 100644 --- a/data/helpers.d/hardware +++ b/data/helpers.d/hardware @@ -8,6 +8,8 @@ # | arg: -s, --ignore_swap - Ignore swap, consider only real RAM # | arg: -o, --only_swap - Ignore real RAM, consider only swap # | ret: the amount of free ram +# +# Requires YunoHost version 3.8.1 or higher. ynh_get_ram () { # Declare an array to define the options of this helper. local legacy_args=ftso @@ -74,6 +76,8 @@ ynh_get_ram () { # | arg: -s, --ignore_swap - Ignore swap, consider only real RAM # | arg: -o, --only_swap - Ignore real RAM, consider only swap # | exit: Return 1 if the ram is under the requirement, 0 otherwise. +# +# Requires YunoHost version 3.8.1 or higher. ynh_require_ram () { # Declare an array to define the options of this helper. local legacy_args=rftso diff --git a/data/helpers.d/logrotate b/data/helpers.d/logrotate index 7df954c15..d5384264c 100644 --- a/data/helpers.d/logrotate +++ b/data/helpers.d/logrotate @@ -16,6 +16,7 @@ # the same logrotate config file. Unless you use the option --non-append # # Requires YunoHost version 2.6.4 or higher. +# Requires YunoHost version 3.2.0 or higher for the argument --specific_user ynh_use_logrotate () { # Declare an array to define the options of this helper. local legacy_args=lnuya diff --git a/data/helpers.d/network b/data/helpers.d/network index 03df04c1e..cb5a9e540 100644 --- a/data/helpers.d/network +++ b/data/helpers.d/network @@ -33,7 +33,7 @@ ynh_find_port () { # | arg: -p, --port= - port to check # | exit: Return 1 if the port is already used by another process. # -# Requires YunoHost version 3.7.x or higher. +# Requires YunoHost version 3.8.0 or higher. ynh_port_available () { # Declare an array to define the options of this helper. local legacy_args=p diff --git a/data/helpers.d/nginx b/data/helpers.d/nginx index 6b60a3ef7..cd4380f16 100644 --- a/data/helpers.d/nginx +++ b/data/helpers.d/nginx @@ -19,6 +19,7 @@ # __PORT_2__ by $port_2 # # Requires YunoHost version 2.7.2 or higher. +# Requires YunoHost version 2.7.13 or higher for dynamic variables ynh_add_nginx_config () { finalnginxconf="/etc/nginx/conf.d/$domain.d/$app.conf" local others_var=${1:-} diff --git a/data/helpers.d/php b/data/helpers.d/php index 588bf7177..4ec011217 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -56,6 +56,8 @@ YNH_PHP_VERSION=${YNH_PHP_VERSION:-$YNH_DEFAULT_PHP_VERSION} # children ready to answer. # # Requires YunoHost version 2.7.2 or higher. +# Requires YunoHost version 3.5.1 or higher for the argument --phpversion +# Requires YunoHost version 3.8.1 or higher for the arguments --use_template, --usage, --footprint, --package and --dedicated_service ynh_add_fpm_config () { # Declare an array to define the options of this helper. local legacy_args=vtufpd @@ -312,6 +314,8 @@ ynh_remove_fpm_config () { # usage: ynh_install_php --phpversion=phpversion [--package=packages] # | arg: -v, --phpversion= - Version of php to install. # | arg: -p, --package= - Additionnal php packages to install +# +# Requires YunoHost version 3.8.1 or higher. ynh_install_php () { # Declare an array to define the options of this helper. local legacy_args=vp @@ -364,6 +368,8 @@ ynh_install_php () { # [internal] # # usage: ynh_install_php +# +# Requires YunoHost version 3.8.1 or higher. ynh_remove_php () { # Get the version of php used by this app local phpversion=$(ynh_app_setting_get $app phpversion) diff --git a/data/helpers.d/postgresql b/data/helpers.d/postgresql index 4122deec6..4ac9fcbec 100644 --- a/data/helpers.d/postgresql +++ b/data/helpers.d/postgresql @@ -153,6 +153,8 @@ ynh_psql_create_user() { # usage: ynh_psql_user_exists --user=user # | arg: -u, --user= - the user for which to check existence # | exit: Return 1 if the user doesn't exist, 0 otherwise +# +# Requires YunoHost version 3.5.0 or higher. ynh_psql_user_exists() { # Declare an array to define the options of this helper. local legacy_args=u @@ -174,6 +176,8 @@ ynh_psql_user_exists() { # usage: ynh_psql_database_exists --database=database # | arg: -d, --database= - the database for which to check existence # | exit: Return 1 if the database doesn't exist, 0 otherwise +# +# Requires YunoHost version 3.5.0 or higher. ynh_psql_database_exists() { # Declare an array to define the options of this helper. local legacy_args=d @@ -212,6 +216,7 @@ ynh_psql_drop_user() { # After executing this helper, the password of the created database will be available in $db_pwd # It will also be stored as "psqlpwd" into the app settings. # +# Requires YunoHost version 2.7.13 or higher. ynh_psql_setup_db() { # Declare an array to define the options of this helper. local legacy_args=unp @@ -239,6 +244,8 @@ ynh_psql_setup_db() { # usage: ynh_psql_remove_db --db_user=user --db_name=name # | arg: -u, --db_user= - Owner of the database # | arg: -n, --db_name= - Name of the database +# +# Requires YunoHost version 2.7.13 or higher. ynh_psql_remove_db() { # Declare an array to define the options of this helper. local legacy_args=un @@ -268,6 +275,8 @@ ynh_psql_remove_db() { # Please always call this script in install and restore scripts # # usage: ynh_psql_test_if_first_run +# +# Requires YunoHost version 2.7.13 or higher. ynh_psql_test_if_first_run() { if [ -f "$PSQL_ROOT_PWD_FILE" ] then diff --git a/data/helpers.d/systemd b/data/helpers.d/systemd index c718e50c2..5e67baf4d 100644 --- a/data/helpers.d/systemd +++ b/data/helpers.d/systemd @@ -14,7 +14,7 @@ # __APP__ by $app # __FINALPATH__ by $final_path # -# Requires YunoHost version 2.7.2 or higher. +# Requires YunoHost version 2.7.11 or higher. ynh_add_systemd_config () { # Declare an array to define the options of this helper. local legacy_args=st @@ -79,6 +79,8 @@ ynh_remove_systemd_config () { # | arg: -p, --log_path= - Log file - Path to the log file. Default : /var/log/$app/$app.log # | arg: -t, --timeout= - Timeout - The maximum time to wait before ending the watching. Default : 300 seconds. # | arg: -e, --length= - Length of the error log : Default : 20 +# +# Requires YunoHost version 3.5.0 or higher. ynh_systemd_action() { # Declare an array to define the options of this helper. local legacy_args=nalpte @@ -179,6 +181,8 @@ ynh_systemd_action() { # (usually used in ynh_clean_setup scripts) # # usage: ynh_clean_check_starting +# +# Requires YunoHost version 3.5.0 or higher. ynh_clean_check_starting () { if [ -n "$pid_tail" ] then diff --git a/data/helpers.d/user b/data/helpers.d/user index 304658ff8..08b1b1d42 100644 --- a/data/helpers.d/user +++ b/data/helpers.d/user @@ -78,6 +78,8 @@ ynh_system_user_exists() { # usage: ynh_system_group_exists --group=group # | arg: -g, --group= - the group to check # | exit: Return 1 if the group doesn't exist, 0 otherwise +# +# Requires YunoHost version 3.5.0.2 or higher. ynh_system_group_exists() { # Declare an array to define the options of this helper. local legacy_args=g diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 13d3a5dcd..fb50305ce 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -16,6 +16,7 @@ # # It prints a warning to inform that the script was failed, and execute the ynh_clean_setup function if used in the app script # +# Requires YunoHost version 2.6.4 or higher. ynh_exit_properly () { local exit_code=$? if [ "$exit_code" -eq 0 ]; then From 194a0bb187902e90572fcde1d51afc15f542d750 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 21 Apr 2020 15:46:03 +0200 Subject: [PATCH 1059/3170] We need that trailing / for the download of files to actually work, c.f. feedback in 3.8 post on the forum --- data/templates/nginx/server.tpl.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/templates/nginx/server.tpl.conf b/data/templates/nginx/server.tpl.conf index f2e9de2de..29af9f532 100644 --- a/data/templates/nginx/server.tpl.conf +++ b/data/templates/nginx/server.tpl.conf @@ -75,7 +75,7 @@ server { root /dev/null; location /upload/ { - alias /var/xmpp-upload/{{ domain }}/upload; + alias /var/xmpp-upload/{{ domain }}/upload/; # Pass all requests to metronome, except for GET and HEAD requests. limit_except GET HEAD { proxy_pass http://localhost:5290; From ba8514fb670367cadc9731d731c02ac27d51b960 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Tue, 21 Apr 2020 16:18:04 +0200 Subject: [PATCH 1060/3170] Fix regressions --- data/helpers.d/php | 2 +- data/helpers.d/systemd | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/data/helpers.d/php b/data/helpers.d/php index 4ec011217..9b23baf25 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -96,7 +96,7 @@ ynh_add_fpm_config () { local additionnal_packages="" fi # Install this specific version of php. - ynh_install_php --phpversion=$phpversion "$additionnal_packages" + ynh_install_php --phpversion="$phpversion" "$additionnal_packages" elif [ -n "$package" ] then # Install the additionnal packages from the default repository diff --git a/data/helpers.d/systemd b/data/helpers.d/systemd index 5e67baf4d..d72744aa0 100644 --- a/data/helpers.d/systemd +++ b/data/helpers.d/systemd @@ -169,7 +169,7 @@ ynh_systemd_action() { ynh_exec_warn journalctl --no-pager --lines=$length --unit=$service_name if [ -e "$log_path" ] then - ynh_print_warn --message="--" + ynh_print_warn --message="\-\-\-" ynh_exec_warn tail --lines=$length "$log_path" fi fi @@ -187,7 +187,7 @@ ynh_clean_check_starting () { if [ -n "$pid_tail" ] then # Stop the execution of tail. - kill --signal 15 $pid_tail 2>&1 + kill -SIGTERM $pid_tail 2>&1 fi if [ -n "$templog" ] then From f72be82429b11787ac2d521ed84d80de9dee9917 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Tue, 21 Apr 2020 16:24:49 +0200 Subject: [PATCH 1061/3170] Fix getopts with empty parameters --- data/helpers.d/getopts | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/data/helpers.d/getopts b/data/helpers.d/getopts index c8045fa25..5d2bbe896 100644 --- a/data/helpers.d/getopts +++ b/data/helpers.d/getopts @@ -147,26 +147,30 @@ ynh_handle_getopts_args () { break fi else - # Else, add this value to this option - # Each value will be separated by ';' - if [ -n "${!option_var}" ] - then - # If there's already another value for this option, add a ; before adding the new value - eval ${option_var}+="\;" - fi + # Ignore empty parameters + if [ -n "${all_args[$i]}" ] + then + # Else, add this value to this option + # Each value will be separated by ';' + if [ -n "${!option_var}" ] + then + # If there's already another value for this option, add a ; before adding the new value + eval ${option_var}+="\;" + fi - # Remove the \ that escape - at beginning of values. - all_args[i]="${all_args[i]//\\TOBEREMOVED\\/}" + # Remove the \ that escape - at beginning of values. + all_args[i]="${all_args[i]//\\TOBEREMOVED\\/}" - # For the record. - # We're using eval here to get the content of the variable stored itself as simple text in $option_var... - # Other ways to get that content would be to use either ${!option_var} or declare -g ${option_var} - # But... ${!option_var} can't be used as left part of an assignation. - # declare -g ${option_var} will create a local variable (despite -g !) and will not be available for the helper itself. - # So... Stop fucking arguing each time that eval is evil... Go find an other working solution if you can find one! + # For the record. + # We're using eval here to get the content of the variable stored itself as simple text in $option_var... + # Other ways to get that content would be to use either ${!option_var} or declare -g ${option_var} + # But... ${!option_var} can't be used as left part of an assignation. + # declare -g ${option_var} will create a local variable (despite -g !) and will not be available for the helper itself. + # So... Stop fucking arguing each time that eval is evil... Go find an other working solution if you can find one! - eval ${option_var}+='"${all_args[$i]}"' - shift_value=$(( shift_value + 1 )) + eval ${option_var}+='"${all_args[$i]}"' + fi + shift_value=$(( shift_value + 1 )) fi done fi From 1a828c725fd43c2a4cc4ae327e05c7eabae7f1e1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 21 Apr 2020 20:20:53 +0200 Subject: [PATCH 1062/3170] Fix postfix ciphers --- data/templates/postfix/main.cf | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/data/templates/postfix/main.cf b/data/templates/postfix/main.cf index 2642fd8f0..61cbfa2e6 100644 --- a/data/templates/postfix/main.cf +++ b/data/templates/postfix/main.cf @@ -33,14 +33,20 @@ smtpd_tls_cert_file = /etc/yunohost/certs/{{ main_domain }}/crt.pem smtpd_tls_key_file = /etc/yunohost/certs/{{ main_domain }}/key.pem smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1 smtpd_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1 -smtpd_tls_mandatory_ciphers = medium +# smtpd_tls_mandatory_ciphers = medium # (c.f. below) # curl https://ssl-config.mozilla.org/ffdhe2048.txt > /path/to/dhparam.pem # not actually 1024 bits, this applies to all DHE >= 1024 bits # smtpd_tls_dh1024_param_file = /path/to/dhparam.pem -tls_medium_cipherlist = ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384 +# This custom medium cipherlist recommendation only works if we have a DH ... which we don't, c.f. https://github.com/YunoHost/issues/issues/93 +# On the other hand, the postfix doc strongly discourage tweaking this list ... So whatever, let's keep the mandatory_ciphers to high like we did before applying the Mozilla recommendation ... +#tls_medium_cipherlist = ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384 tls_preempt_cipherlist = no + +# Custom Yunohost stuff ... because we can't use the recommendation about medium cipher list ... +smtpd_tls_mandatory_ciphers=high +smtpd_tls_eecdh_grade = ultra ############################################################################### smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache smtpd_tls_loglevel=1 From cc5dc0e7a7e909348cd1c9174ca2d2d92aeef126 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 22 Apr 2020 02:21:28 +0200 Subject: [PATCH 1063/3170] How did we not find out about this huge typo earlier :| --- src/yunohost/backup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 8408e7fa3..10a232f38 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -604,7 +604,7 @@ class BackupManager(): ret_succeed = {hook: [path for path, result in infos.items() if result["state"] == "succeed"] for hook, infos in ret.items() if any(result["state"] == "succeed" for result in infos.values())} - ret_failed = {hook: [path for path, result in infos.items.items() if result["state"] == "failed"] + ret_failed = {hook: [path for path, result in infos.items() if result["state"] == "failed"] for hook, infos in ret.items() if any(result["state"] == "failed" for result in infos.values())} From c42f7172f7a4ada26209cac392c844a2d57c6d01 Mon Sep 17 00:00:00 2001 From: pitchum Date: Wed, 22 Apr 2020 10:34:40 +0200 Subject: [PATCH 1064/3170] Do not include xmpp-upload in certificates of "child" domains Co-Authored-By: Alexandre Aubin --- src/yunohost/certificate.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index c6f520b4e..aa137c784 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -639,13 +639,15 @@ def _prepare_certificate_signing_request(domain, key_file, output_folder): # Set the domain csr.get_subject().CN = domain - # Include xmpp-upload subdomain in subject alternate names - subdomain="xmpp-upload." + domain - try: - _dns_ip_match_public_ip(get_public_ip(), subdomain) - csr.add_extensions([crypto.X509Extension("subjectAltName", False, "DNS:" + subdomain)]) - except YunohostError: - logger.warning(m18n.n('certmanager_warning_subdomain_dns_record', subdomain=subdomain, domain=domain)) + from yunohost.domain import domain_list + # 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) + csr.add_extensions([crypto.X509Extension("subjectAltName", False, "DNS:" + subdomain)]) + except YunohostError: + logger.warning(m18n.n('certmanager_warning_subdomain_dns_record', subdomain=subdomain, domain=domain)) # Set the key with open(key_file, 'rt') as f: From 73a6eef6e3a688dcce99afae9fe6ba6aa89594a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 22 Apr 2020 14:44:43 +0200 Subject: [PATCH 1065/3170] Fix permission_list --- src/yunohost/permission.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index fddb7539e..387b04858 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -86,7 +86,7 @@ def user_permission_list(short=False, full=False, ignore_system_perms=False, ful permissions[name]["additional_urls"] = [_get_full_url(url, apps_main_path[name.split('.')[0]]) for url in infos.get("additionalUrls", [None]) if url] else: permissions[name]["url"] = infos.get("URL", [None])[0] - permissions[name]["additional_urls"] = infos.get("additionalUrls", [None]) + permissions[name]["additional_urls"] = infos.get("additionalUrls", []) if short: permissions = permissions.keys() From 0ab3192b81c98f1ba75cc6e29bcad6f6bb7f699d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 22 Apr 2020 15:49:16 +0200 Subject: [PATCH 1066/3170] Improve regex management and simplify urls check --- src/yunohost/domain.py | 73 +++++++++++++++++++++---------- src/yunohost/permission.py | 39 +++-------------- src/yunohost/tests/test_appurl.py | 44 ++++++++++++------- 3 files changed, 83 insertions(+), 73 deletions(-) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 3848213d2..8ef7bb390 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -395,7 +395,7 @@ def _normalize_domain_path(domain, path): return domain, path -def _check_and_normalize_permission_path(url): +def _check_and_sanitize_permission_path(url, app_main_path, permission): """ Check and normalize the urls passed for all permissions Also check that the Regex is valid @@ -416,48 +416,77 @@ def _check_and_normalize_permission_path(url): """ import re, sre_constants - # Uri without domain - if url.startswith('re:/'): - regex = url[4:] - # check regex - try: - re.compile(regex) - except sre_constants.error: - raise YunohostError('invalid_regex', regex=regex) - return url - - if url.startswith('/'): - return '/' + url.strip("/") - # Uri with domain domains = domain_list()['domains'] + # regex without domain + if url.startswith('re:/'): + regex = url[4:] + # check regex if it's a PCRE regex, if it's a lua regex just print a warning + # I don't how how to validate a regex + if '%' in regex: + logger.warning("/!\\ Packagers! You are probably using a lua regex. You should use a PCRE regex instead.") + else: + try: + re.compile(regex) + except sre_constants.error: + raise YunohostError('invalid_regex', regex=regex) + return url + + # regex with domain if url.startswith('re:'): if '/' not in url: raise YunohostError('regex_with_only_domain') domain = url[3:].split('/')[0] path = '/' + url[3:].split('/', 1)[1] - if domain not in domains: + if domain.replace('%', '').replace('\\', '') not in domains: raise YunohostError('domain_named_unknown', domain=domain) - try: - re.compile(path) - except sre_constants.error: - raise YunohostError('invalid_regex', regex=path) + if '%' in path: + logger.warning("/!\\ Packagers! You are probably using a lua regex. You should use a PCRE regex instead.") + else: + try: + re.compile(path) + except sre_constants.error: + raise YunohostError('invalid_regex', regex=path) return 're:' + domain + path + # uris without domain + if url.startswith('/'): + sanitized_url = '/' + url.strip("/") + domain = app_main_path.split('/')[0] + path = ('/' + app_main_path.split('/')[1]) if '/' in app_main_path else '/' + + # uris with domain else: domain = url.split('/')[0] if domain not in domains: raise YunohostError('domain_named_unknown', domain=domain) if '/' in url: - path = url.split('/', 1)[1].rstrip('/') - return domain + '/' + path + path = '/' + url.split('/', 1)[1].rstrip('/') + sanitized_url = domain + path else: - return domain + sanitized_url = domain + path = '/' + + conflicts = _get_conflicting_apps(domain, path, ignore_app=permission.split('.')[0]) + + if conflicts: + apps = [] + for path, app_id, app_label in conflicts: + apps.append(" * {domain:s}{path:s} → {app_label:s} ({app_id:s})".format( + domain=domain, + path=path, + app_id=app_id, + app_label=app_label, + )) + + raise YunohostError('app_location_unavailable', apps="\n".join(apps)) + + return sanitized_url def _build_dns_conf(domain, ttl=3600): diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 387b04858..e3d912047 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -364,7 +364,7 @@ def permission_url(operation_logger, permission, """ from yunohost.app import app_setting from yunohost.utils.ldap import _get_ldap_interface - from yunohost.domain import _check_and_normalize_permission_path, _get_conflicting_apps + from yunohost.domain import _check_and_sanitize_permission_path, _get_conflicting_apps ldap = _get_ldap_interface() # By default, manipulate main permission @@ -389,26 +389,12 @@ def permission_url(operation_logger, permission, if url is None: url = existing_permission["url"] else: - url = _check_and_normalize_permission_path(url) - domain, path = _get_full_url(url, app_main_path).split('/', 1) - domain = domain[3:] if domain.startswith("re:") else domain - conflicts = _get_conflicting_apps(domain, path, ignore_app=permission.split('.')[0]) + url = _check_and_sanitize_permission_path(url, app_main_path, permission) + if url.startswith('re:') and existing_permission['show_tile']: logger.warning(m18n.n('regex_incompatible_with_tile', regex=url, permission=permission)) show_tile = False - if conflicts: - apps = [] - for path, app_id, app_label in conflicts: - apps.append(" * {domain:s}{path:s} → {app_label:s} ({app_id:s})".format( - domain=domain, - path=path, - app_id=app_id, - app_label=app_label, - )) - - raise YunohostError('app_location_unavailable', apps="\n".join(apps)) - current_additional_urls = existing_permission["additional_urls"] new_additional_urls = copy.copy(current_additional_urls) @@ -417,22 +403,7 @@ def permission_url(operation_logger, permission, if ur in current_additional_urls: logger.warning(m18n.n('additional_urls_already_added', permission=permission, url=ur)) else: - ur = _check_and_normalize_permission_path(ur) - domain, path = _get_full_url(ur, app_main_path).split('/', 1) - domain = domain[3:] if domain.startswith("re:") else domain - conflicts = _get_conflicting_apps(domain, path, ignore_app=permission.split('.')[0]) - - if conflicts: - apps = [] - for path, app_id, app_label in conflicts: - apps.append(" * {domain:s}{path:s} → {app_label:s} ({app_id:s})".format( - domain=domain, - path=path, - app_id=app_id, - app_label=app_label, - )) - - raise YunohostError('app_location_unavailable', apps="\n".join(apps)) + ur = _check_and_sanitize_permission_path(ur, app_main_path, permission) new_additional_urls += [ur] if remove_url: @@ -657,6 +628,6 @@ def _get_full_url(url, app_main_path): if url.startswith('/'): return app_main_path + url.rstrip("/") if url.startswith('re:/'): - return 're:' + app_main_path + url[3:] + return 're:' + app_main_path.replace('.', '\\.') + url[3:] else: return url diff --git a/src/yunohost/tests/test_appurl.py b/src/yunohost/tests/test_appurl.py index 3b7af4d92..e7df12a12 100644 --- a/src/yunohost/tests/test_appurl.py +++ b/src/yunohost/tests/test_appurl.py @@ -2,7 +2,7 @@ import pytest from yunohost.utils.error import YunohostError from yunohost.app import app_install, app_remove -from yunohost.domain import _get_maindomain, domain_url_available, _normalize_domain_path, _check_and_normalize_permission_path +from yunohost.domain import _get_maindomain, domain_url_available, _normalize_domain_path, _check_and_sanitize_permission_path # Get main domain maindomain = _get_maindomain() @@ -63,40 +63,50 @@ def test_registerurl_baddomain(): def test_normalize_permission_path(): # Relative path - assert _check_and_normalize_permission_path("/wiki/") == "/wiki" - assert _check_and_normalize_permission_path("/") == "/" - assert _check_and_normalize_permission_path("//salut/") == "/salut" + assert _check_and_sanitize_permission_path("/wiki/", maindomain + '/path', 'test_permission.main') == "/wiki" + assert _check_and_sanitize_permission_path("/", maindomain + '/path', 'test_permission.main') == "/" + assert _check_and_sanitize_permission_path("//salut/", maindomain + '/path', 'test_permission.main') == "/salut" # Full path - assert _check_and_normalize_permission_path(maindomain + "/hey/") == maindomain + "/hey" - assert _check_and_normalize_permission_path(maindomain + "//") == maindomain + "/" - assert _check_and_normalize_permission_path(maindomain + "/") == maindomain + "/" + assert _check_and_sanitize_permission_path(maindomain + "/hey/", maindomain + '/path', 'test_permission.main') == maindomain + "/hey" + assert _check_and_sanitize_permission_path(maindomain + "//", maindomain + '/path', 'test_permission.main') == maindomain + "/" + assert _check_and_sanitize_permission_path(maindomain + "/", maindomain + '/path', 'test_permission.main') == maindomain + "/" # Relative Regex - assert _check_and_normalize_permission_path("re:/yolo.*/") == "re:/yolo.*/" - assert _check_and_normalize_permission_path("re:/y.*o(o+)[a-z]*/bo\1y") == "re:/y.*o(o+)[a-z]*/bo\1y" + assert _check_and_sanitize_permission_path("re:/yolo.*/", maindomain + '/path', 'test_permission.main') == "re:/yolo.*/" + assert _check_and_sanitize_permission_path("re:/y.*o(o+)[a-z]*/bo\1y", maindomain + '/path', 'test_permission.main') == "re:/y.*o(o+)[a-z]*/bo\1y" # Full Regex - assert _check_and_normalize_permission_path("re:" + maindomain + "/yolo.*/") == "re:" + maindomain + "/yolo.*/" - assert _check_and_normalize_permission_path("re:" + maindomain + "/y.*o(o+)[a-z]*/bo\1y") == "re:" + maindomain + "/y.*o(o+)[a-z]*/bo\1y" + assert _check_and_sanitize_permission_path("re:" + maindomain + "/yolo.*/", maindomain + '/path', 'test_permission.main') == "re:" + maindomain + "/yolo.*/" + assert _check_and_sanitize_permission_path("re:" + maindomain + "/y.*o(o+)[a-z]*/bo\1y", maindomain + '/path', 'test_permission.main') == "re:" + maindomain + "/y.*o(o+)[a-z]*/bo\1y" def test_normalize_permission_path_with_bad_regex(): # Relative Regex with pytest.raises(YunohostError): - _check_and_normalize_permission_path("re:/yolo.*[1-7]^?/") + _check_and_sanitize_permission_path("re:/yolo.*[1-7]^?/", maindomain + '/path', 'test_permission.main') with pytest.raises(YunohostError): - _check_and_normalize_permission_path("re:/yolo.*[1-7](]/") + _check_and_sanitize_permission_path("re:/yolo.*[1-7](]/", maindomain + '/path', 'test_permission.main') # Full Regex with pytest.raises(YunohostError): - _check_and_normalize_permission_path("re:" + maindomain + "/yolo?+/") + _check_and_sanitize_permission_path("re:" + maindomain + "/yolo?+/", maindomain + '/path', 'test_permission.main') with pytest.raises(YunohostError): - _check_and_normalize_permission_path("re:" + maindomain + "/yolo[1-9]**/") + _check_and_sanitize_permission_path("re:" + maindomain + "/yolo[1-9]**/", maindomain + '/path', 'test_permission.main') def test_normalize_permission_path_with_unknown_domain(): with pytest.raises(YunohostError): - _check_and_normalize_permission_path("shouldntexist.tld/hey") + _check_and_sanitize_permission_path("shouldntexist.tld/hey", maindomain + '/path', 'test_permission.main') with pytest.raises(YunohostError): - _check_and_normalize_permission_path("re:shouldntexist.tld/hey.*") + _check_and_sanitize_permission_path("re:shouldntexist.tld/hey.*", maindomain + '/path', 'test_permission.main') + + +def test_normalize_permission_path_conflicting_path(): + app_install("./tests/apps/register_url_app_ynh", + args="domain=%s&path=%s" % (maindomain, "/url/registerapp"), force=True) + + with pytest.raises(YunohostError): + _check_and_sanitize_permission_path("/registerapp", maindomain + '/url', 'test_permission.main') + with pytest.raises(YunohostError): + _check_and_sanitize_permission_path(maindomain + "/url/registerapp", maindomain + '/path', 'test_permission.main') From 1ed21beb8727d32ceb13dbf032e0eff9383fb0bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 22 Apr 2020 16:09:06 +0200 Subject: [PATCH 1067/3170] Fix restore permission --- src/yunohost/backup.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 6a287b631..c0c231e84 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -1375,7 +1375,9 @@ class RestoreManager(): else: should_be_allowed = [g for g in permission_infos["allowed"] if g in existing_groups] - permission_create(permission_name, url=permission_infos.get("url", None), allowed=should_be_allowed, + permission_create(permission_name, allowed=should_be_allowed, + url=permission_infos.get("url", None), additional_urls=permission_infos.get("additional_urls", None), auth_header=permission_infos.get("auth_header", None), + label=permission_infos.get("label", None), show_tile=permission_infos.get("show_tile", None), protected=permission_infos.get("protected", True), sync_perm=False) permission_sync_to_user() From 98223a7db31a8c342634653c648f7a684d601141 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 22 Apr 2020 22:29:06 +0200 Subject: [PATCH 1068/3170] Fix tests for regex --- src/yunohost/tests/test_permission.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index b5b57857d..659e28667 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -655,7 +655,7 @@ def test_permission_main_url_regex(): assert res["blog.main"]["url"] == "re:/[a-z]+reboy/.*" res = user_permission_list(full=True, full_path=True)['permissions'] - assert res["blog.main"]["url"] == "re:%s/blog/[a-z]+reboy/.*" % maindomain + assert res["blog.main"]["url"] == "re:%s/blog/[a-z]+reboy/.*" % maindomain.replace('.', '\.') def test_permission_main_url_bad_regex(mocker): @@ -682,7 +682,7 @@ def test_permission_add_additional_regex(): assert res["blog.main"]["additional_urls"] == ["re:/[a-z]+reboy/.*"] res = user_permission_list(full=True, full_path=True)['permissions'] - assert res["blog.main"]["additional_urls"] == ["re:%s/blog/[a-z]+reboy/.*" % maindomain] + assert res["blog.main"]["additional_urls"] == ["re:%s/blog/[a-z]+reboy/.*" % maindomain.replace('.', '\.')] def test_permission_add_additional_bad_regex(mocker): From 817e45108631f1979e1443584713ea36a7d26643 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 23 Apr 2020 01:27:11 +0200 Subject: [PATCH 1069/3170] Add regenconf tests to gitlab-ci --- .gitlab-ci.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8e3938ad6..05aafe43b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -92,3 +92,10 @@ test-user-group: script: - cd src/yunohost - py.test tests/test_user-group.py + +test-regenconf: + extends: .tests + stage: tests + script: + - cd src/yunohost + - py.test tests/test_regenconf.py From 9eef8af53d858467ef5b8ef2fd4deb1f21735478 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 23 Apr 2020 04:18:23 +0200 Subject: [PATCH 1070/3170] Fix improper use of logger.exception in app.py --- src/yunohost/app.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index ed7747b29..b94f57502 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -512,7 +512,7 @@ def app_upgrade(app=[], url=None, file=None): upgrade_failed = True if upgrade_retcode != 0 else False if upgrade_failed: error = m18n.n('app_upgrade_script_failed') - logger.exception(m18n.n("app_upgrade_failed", app=app_instance_name, error=error)) + logger.error(m18n.n("app_upgrade_failed", app=app_instance_name, error=error)) failure_message_with_debug_instructions = operation_logger.error(error) if msettings.get('interface') != 'api': dump_app_log_extract_for_debugging(operation_logger) @@ -520,13 +520,13 @@ def app_upgrade(app=[], url=None, file=None): except (KeyboardInterrupt, EOFError): upgrade_retcode = -1 error = m18n.n('operation_interrupted') - logger.exception(m18n.n("app_upgrade_failed", app=app_instance_name, error=error)) + logger.error(m18n.n("app_upgrade_failed", app=app_instance_name, error=error)) failure_message_with_debug_instructions = operation_logger.error(error) # 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()) - logger.exception(m18n.n("app_install_failed", app=app_instance_name, error=error)) + logger.error(m18n.n("app_install_failed", app=app_instance_name, 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 @@ -536,7 +536,7 @@ def app_upgrade(app=[], url=None, file=None): _assert_system_is_sane_for_app(manifest, "post") except Exception as e: broke_the_system = True - logger.exception(m18n.n("app_upgrade_failed", app=app_instance_name, error=str(e))) + logger.error(m18n.n("app_upgrade_failed", app=app_instance_name, error=str(e))) failure_message_with_debug_instructions = operation_logger.error(str(e)) # If upgrade failed or broke the system, @@ -768,20 +768,20 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu install_failed = True if install_retcode != 0 else False if install_failed: error = m18n.n('app_install_script_failed') - logger.exception(m18n.n("app_install_failed", app=app_id, error=error)) + logger.error(m18n.n("app_install_failed", app=app_id, error=error)) failure_message_with_debug_instructions = operation_logger.error(error) if msettings.get('interface') != 'api': dump_app_log_extract_for_debugging(operation_logger) # Script got manually interrupted ... N.B. : KeyboardInterrupt does not inherit from Exception except (KeyboardInterrupt, EOFError): error = m18n.n('operation_interrupted') - logger.exception(m18n.n("app_install_failed", app=app_id, error=error)) + logger.error(m18n.n("app_install_failed", app=app_id, error=error)) failure_message_with_debug_instructions = operation_logger.error(error) # 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()) - logger.exception(m18n.n("app_install_failed", app=app_id, error=error)) + 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 @@ -791,7 +791,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu _assert_system_is_sane_for_app(manifest, "post") except Exception as e: broke_the_system = True - logger.exception(m18n.n("app_install_failed", app=app_id, error=str(e))) + 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 @@ -828,7 +828,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.exception(m18n.n('unexpected_error', error=u"\n" + traceback.format_exc())) + logger.error(m18n.n('unexpected_error', error=u"\n" + traceback.format_exc())) # Remove all permission in LDAP for permission_name in user_permission_list()["permissions"].keys(): @@ -999,7 +999,7 @@ def app_remove(operation_logger, app): except (KeyboardInterrupt, EOFError, Exception): ret = -1 import traceback - logger.exception(m18n.n('unexpected_error', error=u"\n" + traceback.format_exc())) + logger.error(m18n.n('unexpected_error', error=u"\n" + traceback.format_exc())) if ret == 0: logger.success(m18n.n('app_removed', app=app)) @@ -1825,7 +1825,7 @@ def _get_app_settings(app_id): if app_id == settings['id']: return settings except (IOError, TypeError, KeyError): - logger.exception(m18n.n('app_not_correctly_installed', + logger.error(m18n.n('app_not_correctly_installed', app=app_id)) return {} 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 1071/3170] 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 1072/3170] 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 af8773f76776578bf95ead7f8840a20e2aa4ee5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Thu, 23 Apr 2020 15:12:54 +0200 Subject: [PATCH 1073/3170] Fix typo --- data/helpers.d/setting | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index b16d087ea..1995da2e6 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -158,7 +158,7 @@ ynh_webpath_register () { # Create a new permission for the app # -# example: ynh_permission_create --permission admin --url /admin --additional_urls 'domain.tld/otherurl /superadmin' --allowed alice bob --tile_name 'My app admin' +# example: ynh_permission_create --permission admin --url /admin --additional_urls 'domain.tld/otherurl /superadmin' --allowed alice bob --label 'My app admin' # # usage: ynh_permission_create --permission "permission" [--url "url"] [--additional_urls "second-url" [ "other-url" ]] [--auth_header true|false] # [--allowed group1 [ group2 ]] [--label "label"] [--show_tile true|false] From e79f73d4638c0ed100751835789b057c946287a7 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 23 Apr 2020 17:02:58 +0200 Subject: [PATCH 1074/3170] Make sure to return / and not empty string for stuff on domain root --- src/yunohost/app.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index b94f57502..37da3a957 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -239,6 +239,8 @@ def app_map(app=None, raw=False, user=None): perm_domain, perm_path = perm_url.split("/", 1) perm_path = "/" + perm_path.rstrip("/") + perm_path = perm_path if perm_path != "" else "/" + return perm_domain, perm_path this_app_perms = {p: i for p, i in permissions.items() if p.startswith(app_id + ".") and i["url"]} @@ -274,7 +276,6 @@ def app_map(app=None, raw=False, user=None): continue perm_domain, perm_path = _sanitized_absolute_url(perm_info["url"]) - if perm_name.endswith(".main"): perm_label = label else: @@ -1105,11 +1106,12 @@ def app_makedefault(operation_logger, app, domain=None): elif domain not in domain_list()['domains']: raise YunohostError('domain_unknown') - operation_logger.start() if '/' in app_map(raw=True)[domain]: raise YunohostError('app_make_default_location_already_used', app=app, domain=app_domain, other_app=app_map(raw=True)[domain]["/"]["id"]) + operation_logger.start() + # TODO / FIXME : current trick is to add this to conf.json.persisten # This is really not robust and should be improved # e.g. have a flag in /etc/yunohost/apps/$app/ to say that this is the @@ -1267,6 +1269,8 @@ def app_ssowatconf(): perm_domain, perm_path = perm_url.split("/", 1) perm_path = "/" + perm_path.rstrip("/") + perm_path = perm_path if perm_path != "" else "/" + return perm_domain + perm_path # Skipped From 755ba61b326c0eb035e8a8090d66b7b9a3b6b607 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 23 Apr 2020 17:44:01 +0200 Subject: [PATCH 1075/3170] Moar tests to check the content of app_map --- src/yunohost/tests/test_apps.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/yunohost/tests/test_apps.py b/src/yunohost/tests/test_apps.py index 6bc625a91..7c0861aa1 100644 --- a/src/yunohost/tests/test_apps.py +++ b/src/yunohost/tests/test_apps.py @@ -9,7 +9,7 @@ from conftest import message, raiseYunohostError from moulinette import m18n from moulinette.utils.filesystem import mkdir -from yunohost.app import app_install, app_remove, app_ssowatconf, _is_installed, app_upgrade +from yunohost.app import app_install, app_remove, app_ssowatconf, _is_installed, app_upgrade, app_map from yunohost.domain import _get_maindomain, domain_add, domain_remove, domain_list from yunohost.utils.error import YunohostError from yunohost.tests.test_permission import check_LDAP_db_integrity, check_permission_for_apps @@ -142,6 +142,12 @@ def test_legacy_app_install_main_domain(): install_legacy_app(main_domain, "/legacy") + app_map_ = app_map(raw=True) + assert main_domain in app_map_ + assert '/legacy' in app_map_[main_domain] + assert 'id' in app_map_[main_domain]['/legacy'] + assert app_map_[main_domain]['/legacy']['id'] == 'legacy_app' + assert app_is_installed(main_domain, "legacy_app") assert app_is_exposed_on_http(main_domain, "/legacy", "This is a dummy app") @@ -166,6 +172,12 @@ def test_legacy_app_install_secondary_domain_on_root(secondary_domain): install_legacy_app(secondary_domain, "/") + app_map_ = app_map(raw=True) + assert secondary_domain in app_map_ + assert '/' in app_map_[secondary_domain] + assert 'id' in app_map_[secondary_domain]['/'] + assert app_map_[secondary_domain]['/']['id'] == 'legacy_app' + assert app_is_installed(secondary_domain, "legacy_app") assert app_is_exposed_on_http(secondary_domain, "/", "This is a dummy app") From f68ae4561f23daa2fa1d25e0efea7663facc0e6c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 23 Apr 2020 18:00:46 +0200 Subject: [PATCH 1076/3170] Patch files earlier to avoid raising an exception is setting folder already exists --- src/yunohost/app.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index b94f57502..8f16198bc 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -489,17 +489,17 @@ def app_upgrade(app=[], url=None, file=None): env_dict["YNH_APP_INSTANCE_NAME"] = app_instance_name env_dict["YNH_APP_INSTANCE_NUMBER"] = str(app_instance_nb) - # Start register change on system - related_to = [('app', app_instance_name)] - operation_logger = OperationLogger('app_upgrade', related_to, env=env_dict) - operation_logger.start() - # Attempt to patch legacy helpers ... _patch_legacy_helpers(extracted_app_folder) # Apply dirty patch to make php5 apps compatible with php7 _patch_php5(extracted_app_folder) + # Start register change on system + related_to = [('app', app_instance_name)] + operation_logger = OperationLogger('app_upgrade', related_to, env=env_dict) + operation_logger.start() + # Execute App upgrade script os.system('chown -hR admin: %s' % INSTALL_TMP) @@ -695,6 +695,12 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu # Validate domain / path availability for webapps _validate_and_normalize_webpath(manifest, args_odict, extracted_app_folder) + # Attempt to patch legacy helpers ... + _patch_legacy_helpers(extracted_app_folder) + + # Apply dirty patch to make php5 apps compatible with php7 + _patch_php5(extracted_app_folder) + # Prepare env. var. to pass to script env_dict = _make_environment_dict(args_odict) env_dict["YNH_APP_ID"] = app_id @@ -732,12 +738,6 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu } _set_app_settings(app_instance_name, app_settings) - # Attempt to patch legacy helpers ... - _patch_legacy_helpers(extracted_app_folder) - - # Apply dirty patch to make php5 apps compatible with php7 - _patch_php5(extracted_app_folder) - os.system('chown -R admin: ' + extracted_app_folder) # Execute App install script From cb0a87de256ad54d83725d9c395e940c3f441597 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 23 Apr 2020 18:59:12 +0200 Subject: [PATCH 1077/3170] Patch usage of old in apps 'yunohost tools diagnosis' --- src/yunohost/app.py | 53 +++++++++++++++++++++++++++++++-------------- 1 file changed, 37 insertions(+), 16 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 8f16198bc..ec4e05664 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -2843,29 +2843,46 @@ def _patch_legacy_helpers(app_folder): # sudo yunohost app initdb $db_user -p $db_pwd # by # ynh_mysql_setup_db --db_user=$db_user --db_name=$db_user --db_pwd=$db_pwd - "yunohost app initdb": ( - r"(sudo )?yunohost app initdb \"?(\$\{?\w+\}?)\"?\s+-p\s\"?(\$\{?\w+\}?)\"?", - r"ynh_mysql_setup_db --db_user=\2 --db_name=\2 --db_pwd=\3"), + "yunohost app initdb": { + "pattern": r"(sudo )?yunohost app initdb \"?(\$\{?\w+\}?)\"?\s+-p\s\"?(\$\{?\w+\}?)\"?", + "replace": r"ynh_mysql_setup_db --db_user=\2 --db_name=\2 --db_pwd=\3", + "important": True + }, # Replace # sudo yunohost app checkport whaterver # by # ynh_port_available whatever - "yunohost app checkport": ( - r"(sudo )?yunohost app checkport", - r"ynh_port_available"), + "yunohost app checkport": { + "pattern": r"(sudo )?yunohost app checkport", + "replace": r"ynh_port_available", + "important": True + }, # We can't migrate easily port-available # .. but at the time of writing this code, only two non-working apps are using it. - "yunohost tools port-available": (None, None), + "yunohost tools port-available": {"important":True}, # Replace # yunohost app checkurl "${domain}${path_url}" -a "${app}" # by # ynh_webpath_register --app=${app} --domain=${domain} --path_url=${path_url} - "yunohost app checkurl": ( - r"(sudo )?yunohost app checkurl \"?(\$\{?\w+\}?)\/?(\$\{?\w+\}?)\"?\s+-a\s\"?(\$\{?\w+\}?)\"?", - r"ynh_webpath_register --app=\4 --domain=\2 --path_url=\3"), + "yunohost app checkurl": { + "pattern": r"(sudo )?yunohost app checkurl \"?(\$\{?\w+\}?)\/?(\$\{?\w+\}?)\"?\s+-a\s\"?(\$\{?\w+\}?)\"?", + "replace": r"ynh_webpath_register --app=\4 --domain=\2 --path_url=\3", + "important": True + }, + # Remove + # Automatic diagnosis data from YunoHost + # __PRE_TAG1__$(yunohost tools diagnosis | ...)__PRE_TAG2__" + # + "yunohost tools diagnosis": { + "pattern": r"(Automatic diagnosis data from YunoHost( *\n)*)? *(__\w+__)? *\$\(yunohost tools diagnosis.*\)(__\w+__)?", + "replace": r"", + "important": False + } } - stuff_to_replace_compiled = {h: (re.compile(r[0]), r[1]) if r[0] else (None,None) for h, r in stuff_to_replace.items()} + for helper, infos in stuff_to_replace.items(): + infos["pattern"] = re.compile(infos["pattern"]) if infos.get("pattern") else None + infos["replace"] = infos.get("replace") for filename in files_to_patch: @@ -2875,18 +2892,20 @@ def _patch_legacy_helpers(app_folder): content = read_file(filename) replaced_stuff = False + show_warning = False - for helper, regexes in stuff_to_replace_compiled.items(): - pattern, replace = regexes + for helper, infos in stuff_to_replace.items(): # If helper is used, attempt to patch the file - if helper in content and pattern != "": - content = pattern.sub(replace, content) + if helper in content and infos["pattern"]: + content = infos["pattern"].sub(infos["replace"], content) replaced_stuff = True + if infos["important"]: + show_warning = True # If the helpert is *still* in the content, it means that we # couldn't patch the deprecated helper in the previous lines. In # that case, abort the install or whichever step is performed - if helper in content: + if helper in content and infos["important"]: raise YunohostError("This app is likely pretty old and uses deprecated / outdated helpers that can't be migrated easily. It can't be installed anymore.") if replaced_stuff: @@ -2902,5 +2921,7 @@ def _patch_legacy_helpers(app_folder): # Actually write the new content in the file write_to_file(filename, content) + + if show_warning: # And complain about those damn deprecated helpers logger.error("/!\ Packagers ! This app uses a very old deprecated helpers ... Yunohost automatically patched the helpers to use the new recommended practice, but please do consider fixing the upstream code right now ...") From e36e5ea89c4da9a9498d387c40dc195eb283c7f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Thu, 23 Apr 2020 22:00:08 +0200 Subject: [PATCH 1078/3170] Improve label --- data/other/ldap_scheme.yml | 2 +- .../0015_extends_permissions_features_1.py | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/data/other/ldap_scheme.yml b/data/other/ldap_scheme.yml index 8fdded8ac..aa2b46ad3 100644 --- a/data/other/ldap_scheme.yml +++ b/data/other/ldap_scheme.yml @@ -74,7 +74,7 @@ depends_children: groupPermission: - "cn=all_users,ou=groups,dc=yunohost,dc=org" authHeader: "FALSE" - label: "Mail" + label: "E-mail" showTile: "FALSE" isProtected: "TRUE" cn=xmpp.main,ou=permission: diff --git a/src/yunohost/data_migrations/0015_extends_permissions_features_1.py b/src/yunohost/data_migrations/0015_extends_permissions_features_1.py index d11d4f136..9bbe8baeb 100644 --- a/src/yunohost/data_migrations/0015_extends_permissions_features_1.py +++ b/src/yunohost/data_migrations/0015_extends_permissions_features_1.py @@ -43,15 +43,22 @@ class MyMigration(Migration): app_setting(app, 'label', delete=True) for permission in permission_list: - if permission.split('.')[0] in SYSTEM_PERMS: + if permission.split('.')[0] == 'mail': ldap.update('cn=%s,ou=permission' % permission, { 'authHeader': ["FALSE"], - 'label': [permission.split('.')[0].title()], + 'label': 'E-mail', + 'showTile': ["FALSE"], + 'isProtected': ["TRUE"], + }) + elif permission.split('.')[0] == 'xmpp': + ldap.update('cn=%s,ou=permission' % permission, { + 'authHeader': ["FALSE"], + 'label': 'XMPP', 'showTile': ["FALSE"], 'isProtected': ["TRUE"], }) else: - label = labels[permission.split('.')[0]] + label = labels[permission.split('.')[0]].title() if permission.endswith(".main"): ldap.update('cn=%s,ou=permission' % permission, { From 5baadd1fa18cc45534a1a66a67af7c61d442af3e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 24 Apr 2020 03:08:31 +0200 Subject: [PATCH 1079/3170] Be more robust against some situation where archive is corrupted --- locales/en.json | 3 ++- src/yunohost/backup.py | 42 +++++++++++++++++++++++++----------------- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/locales/en.json b/locales/en.json index c2c087031..9207b304b 100644 --- a/locales/en.json +++ b/locales/en.json @@ -74,6 +74,8 @@ "backup_archive_name_exists": "A backup archive with this name already exists.", "backup_archive_name_unknown": "Unknown local backup archive named '{name:s}'", "backup_archive_open_failed": "Could not open the backup archive", + "backup_archive_cant_retrieve_info_json": "Could not load infos for archive '{archive}' ... The info.json cannot be retrieved (or is not a valid json).", + "backup_archive_corrupted": "It looks like the backup archive '{archive}' is corrupted : {error}", "backup_archive_system_part_not_available": "System part '{part:s}' unavailable in this backup", "backup_archive_writing_error": "Could not add the files '{source:s}' (named in the archive '{dest:s}') to be backed up into the compressed archive '{archive:s}'", "backup_ask_for_copying_if_needed": "Do you want to perform the backup using {size:s} MB temporarily? (This way is used since some files could not be prepared using a more efficient method.)", @@ -91,7 +93,6 @@ "backup_delete_error": "Could not delete '{path:s}'", "backup_deleted": "Backup deleted", "backup_hook_unknown": "The backup hook '{hook:s}' is unknown", - "backup_invalid_archive": "This is not a backup archive", "backup_method_borg_finished": "Backup into Borg finished", "backup_method_copy_finished": "Backup copy finalized", "backup_method_custom_finished": "Custom backup method '{method:s}' finished", diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 10a232f38..5f24f444f 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -870,7 +870,7 @@ class RestoreManager(): Read the info file from inside an archive Exceptions: - backup_invalid_archive -- Raised if we can't read the info + backup_archive_cant_retrieve_info_json -- Raised if we can't read the info """ # Retrieve backup info info_file = os.path.join(self.work_dir, "info.json") @@ -883,7 +883,7 @@ class RestoreManager(): self.info["system"] = self.info["hooks"] except IOError: logger.debug("unable to load '%s'", info_file, exc_info=1) - raise YunohostError('backup_invalid_archive') + raise YunohostError('backup_archive_cant_retrieve_info_json', archive=self.archive_path) else: logger.debug("restoring from backup '%s' created on %s", self.name, datetime.utcfromtimestamp(self.info['created_at'])) @@ -891,10 +891,6 @@ class RestoreManager(): def _postinstall_if_needed(self): """ Post install yunohost if needed - - Exceptions: - backup_invalid_archive -- Raised if the current_host isn't in the - archive """ # Check if YunoHost is installed if not os.path.isfile('/etc/yunohost/installed'): @@ -906,7 +902,7 @@ class RestoreManager(): logger.debug("unable to retrieve current_host from the backup", exc_info=1) # FIXME include the current_host by default ? - raise YunohostError('backup_invalid_archive') + raise YunohostError("The main domain name cannot be retrieved from inside the archive, and is needed to perform the postinstall", raw_msg=True) logger.debug("executing the post-install...") tools_postinstall(domain, 'Yunohost', True) @@ -1924,6 +1920,12 @@ class TarBackupMethod(BackupMethod): self._archive_file, exc_info=1) raise YunohostError('backup_archive_open_failed') + try: + files_in_archive = tar.getnames() + print(files_in_archive) + 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() @@ -1932,21 +1934,21 @@ class TarBackupMethod(BackupMethod): logger.debug(m18n.n("restore_extracting")) tar = tarfile.open(self._archive_file, "r:gz") - if "info.json" in tar.getnames(): + if "info.json" in files_in_archive: leading_dot = "" tar.extract('info.json', path=self.work_dir) - elif "./info.json" in tar.getnames(): + elif "./info.json" in files_in_archive: leading_dot = "./" tar.extract('./info.json', path=self.work_dir) else: logger.debug("unable to retrieve 'info.json' inside the archive", exc_info=1) tar.close() - raise YunohostError('backup_invalid_archive') + raise YunohostError('backup_archive_cant_retrieve_info_json', archive=self._archive_file) - if "backup.csv" in tar.getnames(): + if "backup.csv" in files_in_archive: tar.extract('backup.csv', path=self.work_dir) - elif "./backup.csv" in tar.getnames(): + elif "./backup.csv" in files_in_archive: tar.extract('./backup.csv', path=self.work_dir) else: # Old backup archive have no backup.csv file @@ -2288,7 +2290,7 @@ def backup_list(with_info=False, human_readable=False): try: d[a] = backup_info(a, human_readable=human_readable) except YunohostError as e: - logger.warning('%s: %s' % (a, e.strerror)) + logger.warning(str(e)) result = d @@ -2325,17 +2327,23 @@ def backup_info(name, with_details=False, human_readable=False): if not os.path.exists(info_file): tar = tarfile.open(archive_file, "r:gz") info_dir = info_file + '.d' + try: - if "info.json" in tar.getnames(): + files_in_archive = tar.getnames() + except IOError as e: + raise YunohostError("backup_archive_corrupted", archive=archive_file, error=str(e)) + + try: + if "info.json" in files_in_archive: tar.extract('info.json', path=info_dir) - elif "./info.json" in tar.getnames(): + elif "./info.json" in files_in_archive: tar.extract('./info.json', path=info_dir) else: raise KeyError except KeyError: logger.debug("unable to retrieve '%s' inside the archive", info_file, exc_info=1) - raise YunohostError('backup_invalid_archive') + raise YunohostError('backup_archive_cant_retrieve_info_json', archive=archive_file) else: shutil.move(os.path.join(info_dir, 'info.json'), info_file) finally: @@ -2348,7 +2356,7 @@ def backup_info(name, with_details=False, human_readable=False): info = json.load(f) except: logger.debug("unable to load '%s'", info_file, exc_info=1) - raise YunohostError('backup_invalid_archive') + raise YunohostError('backup_archive_cant_retrieve_info_json', archive=archive_file) # Retrieve backup size size = info.get('size', 0) From 54cc684a356e7e92cf34c6514c3da48ee2d37b14 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 24 Apr 2020 03:33:59 +0200 Subject: [PATCH 1080/3170] Keep track of yunohost version a backup was made from, for possible future uses --- src/yunohost/backup.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 10a232f38..3e2f467d1 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -35,9 +35,9 @@ import tempfile from datetime import datetime from glob import glob from collections import OrderedDict +from functools import reduce from moulinette import msignals, m18n, msettings -from yunohost.utils.error import YunohostError from moulinette.utils import filesystem from moulinette.utils.log import getActionLogger from moulinette.utils.filesystem import read_file, mkdir, write_to_yaml, read_yaml @@ -51,7 +51,8 @@ from yunohost.hook import ( from yunohost.tools import tools_postinstall from yunohost.regenconf import regen_conf from yunohost.log import OperationLogger -from functools import reduce +from yunohost.utils.error import YunohostError +from yunohost.utils.packages import ynh_packages_version BACKUP_PATH = '/home/yunohost.backup' ARCHIVES_PATH = '%s/archives' % BACKUP_PATH @@ -282,7 +283,8 @@ class BackupManager(): 'size': self.size, 'size_details': self.size_details, 'apps': self.apps_return, - 'system': self.system_return + 'system': self.system_return, + 'from_yunohost_version': ynh_packages_version()["yunohost"]["version"] } @property 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 1081/3170] 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 b9e226caed6d6fc9f775a9d3121a30ad258c0a70 Mon Sep 17 00:00:00 2001 From: pitchum Date: Fri, 24 Apr 2020 19:07:05 +0200 Subject: [PATCH 1082/3170] Remove deprecated docstrings. --- src/yunohost/domain.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index a1ac65b81..85d804584 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -46,9 +46,6 @@ def domain_list(exclude_subdomains=False): List domains Keyword argument: - filter -- LDAP filter used to search - offset -- Starting number for domain fetching - limit -- Maximum number of domain fetched exclude_subdomains -- Filter out domains that are subdomains of other declared domains """ From 67785edb1c5e2bf41ea8c8af6e3facb8b185f893 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Fri, 24 Apr 2020 23:45:22 +0200 Subject: [PATCH 1083/3170] Long arguments for ynh_validate_ip4 Co-Authored-By: Kayou --- data/helpers.d/network | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/network b/data/helpers.d/network index cb5a9e540..e45fe1a5e 100644 --- a/data/helpers.d/network +++ b/data/helpers.d/network @@ -105,7 +105,7 @@ ynh_validate_ip4() # Manage arguments with getopts ynh_handle_getopts_args "$@" - ynh_validate_ip 4 $ip_address + ynh_validate_ip --family=4 --ip_address=$ip_address } From b3b0aef0477591fcf09ed612a37a442c7046bd81 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Fri, 24 Apr 2020 23:45:43 +0200 Subject: [PATCH 1084/3170] Long arguments for ynh_validate_ip6 Co-Authored-By: Kayou --- data/helpers.d/network | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/network b/data/helpers.d/network index e45fe1a5e..5618ff377 100644 --- a/data/helpers.d/network +++ b/data/helpers.d/network @@ -127,5 +127,5 @@ ynh_validate_ip6() # Manage arguments with getopts ynh_handle_getopts_args "$@" - ynh_validate_ip 6 $ip_address + ynh_validate_ip --family=6 --ip_address=$ip_address } From 1decbd242364166134de57952fb65191d3e30e21 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Fri, 24 Apr 2020 23:47:53 +0200 Subject: [PATCH 1085/3170] Fix ynh_no_log Co-Authored-By: Kayou --- data/helpers.d/logging | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/logging b/data/helpers.d/logging index 37dfd286c..fe0ad70b0 100644 --- a/data/helpers.d/logging +++ b/data/helpers.d/logging @@ -54,7 +54,7 @@ ynh_no_log() { eval $@ local exit_code=$? mv ${ynh_cli_log}-move ${ynh_cli_log} - return $? + return $exit_code } # Main printer, just in case in the future we have to change anything about that. From d9aa345ee866c47e7f6792fccd26fc79f280df96 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Fri, 24 Apr 2020 23:51:18 +0200 Subject: [PATCH 1086/3170] Unfold OR Co-Authored-By: Kayou --- data/helpers.d/nodejs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/data/helpers.d/nodejs b/data/helpers.d/nodejs index 3ede3c8c9..efb50ae37 100644 --- a/data/helpers.d/nodejs +++ b/data/helpers.d/nodejs @@ -49,7 +49,9 @@ ynh_use_nodejs () { nodejs_path="$node_version_path/$nodejs_version/bin" # Load the path of this version of node in $PATH - [[ :$PATH: == *":$nodejs_path"* ]] || PATH="$nodejs_path:$PATH" + if [[ :$PATH: != *":$nodejs_path"* ]]; then + PATH="$nodejs_path:$PATH" + fi } # Install a specific version of nodejs From defabdbecb3a4ad29702db3f82afce132beecc43 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Fri, 24 Apr 2020 23:56:49 +0200 Subject: [PATCH 1087/3170] Missing argument Co-Authored-By: Kayou --- data/helpers.d/user | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/user b/data/helpers.d/user index 08b1b1d42..aeac3a9c5 100644 --- a/data/helpers.d/user +++ b/data/helpers.d/user @@ -131,7 +131,7 @@ ynh_system_user_create () { else local shell="--shell /usr/sbin/nologin" fi - useradd $user_home_dir --system --user-group $username $shell || ynh_die "Unable to create $username system account" + useradd $user_home_dir --system --user-group $username $shell || ynh_die --message="Unable to create $username system account" fi } From 1af4d20e1efcd82e60c66c83efd82e030d976435 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Fri, 24 Apr 2020 23:59:59 +0200 Subject: [PATCH 1088/3170] Typo Co-Authored-By: Kayou --- data/helpers.d/logging | 1 + 1 file changed, 1 insertion(+) diff --git a/data/helpers.d/logging b/data/helpers.d/logging index fe0ad70b0..ec996a7bc 100644 --- a/data/helpers.d/logging +++ b/data/helpers.d/logging @@ -230,6 +230,7 @@ ynh_script_progression () { local last # Manage arguments with getopts ynh_handle_getopts_args "$@" + # Re-disable xtrace, ynh_handle_getopts_args set it back set +o xtrace # set +x weight=${weight:-1} time=${time:-0} From 54aa6f891290fff1e709200ebbc93bc08e0d5eb6 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sat, 25 Apr 2020 00:00:57 +0200 Subject: [PATCH 1089/3170] Typo Co-Authored-By: Kayou --- data/helpers.d/logging | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/logging b/data/helpers.d/logging index ec996a7bc..c79090e25 100644 --- a/data/helpers.d/logging +++ b/data/helpers.d/logging @@ -325,7 +325,7 @@ ynh_debug () { local trace # Manage arguments with getopts ynh_handle_getopts_args "$@" - # Redisable xtrace, ynh_handle_getopts_args set it back + # Re-disable xtrace, ynh_handle_getopts_args set it back set +o xtrace # set +x message=${message:-} trace=${trace:-} From a75af4896c980e0f3e4c5e8bc52b86f8e998dee0 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sat, 25 Apr 2020 00:35:39 +0200 Subject: [PATCH 1090/3170] follow=name Co-Authored-By: Kayou --- data/helpers.d/systemd | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/data/helpers.d/systemd b/data/helpers.d/systemd index d72744aa0..798dfd02a 100644 --- a/data/helpers.d/systemd +++ b/data/helpers.d/systemd @@ -113,7 +113,7 @@ ynh_systemd_action() { local pid_tail=$! else # Read the specified log file - tail --follow --retry --lines=0 "$log_path" > "$templog" 2>&1 & + tail --follow=name --retry --lines=0 "$log_path" > "$templog" 2>&1 & # Get the PID of the tail command local pid_tail=$! fi @@ -195,4 +195,3 @@ ynh_clean_check_starting () { fi } - From b6daf0c448c93cfb21906d15cc938ef3cd8da6db Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sat, 25 Apr 2020 00:38:59 +0200 Subject: [PATCH 1091/3170] ynh_die instead of false --- data/helpers.d/systemd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/systemd b/data/helpers.d/systemd index 798dfd02a..238f65d93 100644 --- a/data/helpers.d/systemd +++ b/data/helpers.d/systemd @@ -136,7 +136,7 @@ ynh_systemd_action() { ynh_exec_err tail --lines=$length "$log_path" fi # Fail the app script, since the service failed. - false + ynh_die fi # Start the timeout and try to find line_match From dd5699ee404081e95bcf0ec1e60f0e019cfa0a3a Mon Sep 17 00:00:00 2001 From: Kay0u Date: Sat, 25 Apr 2020 01:03:33 +0200 Subject: [PATCH 1092/3170] use ynh_port_available in ynh_find_port --- data/helpers.d/network | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/network b/data/helpers.d/network index 5618ff377..4f108422b 100644 --- a/data/helpers.d/network +++ b/data/helpers.d/network @@ -18,7 +18,7 @@ ynh_find_port () { ynh_handle_getopts_args "$@" test -n "$port" || ynh_die --message="The argument of ynh_find_port must be a valid port." - while ss --numeric --listening --tcp --udp | awk '{print$5}' | grep --quiet --extended-regexp ":$port$" # Check if the port is free + while ! ynh_port_available --port=$port do port=$((port+1)) # Else, pass to next port done @@ -42,7 +42,7 @@ ynh_port_available () { # Manage arguments with getopts ynh_handle_getopts_args "$@" - if ss --numeric --listening --tcp --udp | grep --quiet --word-regexp :$port + if ss --numeric --listening --tcp --udp | awk '{print$5}' | grep --quiet --extended-regexp ":$port$" # Check if the port is free then return 1 else From a20fd04955581ce5a261de58bf461ed00b7beb2f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 25 Apr 2020 01:27:20 +0200 Subject: [PATCH 1093/3170] Remove tmp debug print() Co-Authored-By: Kayou --- src/yunohost/backup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 5f24f444f..5d64ae5d6 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -1922,7 +1922,6 @@ class TarBackupMethod(BackupMethod): try: files_in_archive = tar.getnames() - print(files_in_archive) except IOError as e: raise YunohostError("backup_archive_corrupted", archive=self._archive_file, error=str(e)) From 77e124519f14b2fc50d29f1c12a15f993b736121 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Sat, 25 Apr 2020 01:54:12 +0200 Subject: [PATCH 1094/3170] add bad archive test --- src/yunohost/tests/test_backuprestore.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/yunohost/tests/test_backuprestore.py b/src/yunohost/tests/test_backuprestore.py index bcba21bb6..b715aad04 100644 --- a/src/yunohost/tests/test_backuprestore.py +++ b/src/yunohost/tests/test_backuprestore.py @@ -574,9 +574,20 @@ def test_restore_archive_with_no_json(mocker): assert "badbackup" in backup_list()["archives"] - with raiseYunohostError(mocker, 'backup_invalid_archive'): + with raiseYunohostError(mocker, 'backup_archive_cant_retrieve_info_json'): backup_restore(name="badbackup", force=True) +@pytest.mark.with_wordpress_archive_from_2p4 +def test_restore_archive_with_bad_archive(mocker): + + # Break the archive + os.system("head -n 1000 /home/yunohost.backup/archives/backup_wordpress_from_2p4.tar.gz > /home/yunohost.backup/archives/backup_wordpress_from_2p4.tar.gz") + + assert "backup_wordpress_from_2p4" in backup_list()["archives"] + + with raiseYunohostError(mocker, 'backup_archive_open_failed'): + backup_restore(name="backup_wordpress_from_2p4", force=True) + def test_backup_binds_are_readonly(mocker, monkeypatch): From 05734dfd7cb221197b065e0cde747721f84bafbe Mon Sep 17 00:00:00 2001 From: Kay0u Date: Sat, 25 Apr 2020 02:28:45 +0200 Subject: [PATCH 1095/3170] clean tmp backuo dir --- src/yunohost/tests/test_backuprestore.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/yunohost/tests/test_backuprestore.py b/src/yunohost/tests/test_backuprestore.py index b715aad04..c7a4f9016 100644 --- a/src/yunohost/tests/test_backuprestore.py +++ b/src/yunohost/tests/test_backuprestore.py @@ -588,6 +588,8 @@ def test_restore_archive_with_bad_archive(mocker): with raiseYunohostError(mocker, 'backup_archive_open_failed'): backup_restore(name="backup_wordpress_from_2p4", force=True) + clean_tmp_backup_directory() + def test_backup_binds_are_readonly(mocker, monkeypatch): From cf32853f810adb4d4a0f674ab887bfbb117703de Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 25 Apr 2020 03:44:26 +0200 Subject: [PATCH 1096/3170] Fetch all cert-status at once because running a yunohost command takes ~3ish seconds per call --- data/hooks/conf_regen/15-nginx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/data/hooks/conf_regen/15-nginx b/data/hooks/conf_regen/15-nginx index f8b7d8062..f1a278440 100755 --- a/data/hooks/conf_regen/15-nginx +++ b/data/hooks/conf_regen/15-nginx @@ -52,6 +52,8 @@ do_pre_regen() { export compatibility="$(yunohost settings get 'security.nginx.compatibility')" ynh_render_template "security.conf.inc" "${nginx_conf_dir}/security.conf.inc" + cert_status=$(yunohost domain cert-status --json) + # add domain conf files for domain in $domain_list; do domain_conf_dir="${nginx_conf_dir}/${domain}.d" @@ -61,7 +63,7 @@ do_pre_regen() { # NGINX server configuration export domain - export domain_cert_ca=$(yunohost domain cert-status $domain --json \ + export domain_cert_ca=$(echo $cert_status \ | jq ".certificates.\"$domain\".CA_type" \ | tr -d '"') From 319898baf7892582e90b5b9b24fccb8234939fe3 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 25 Apr 2020 03:49:30 +0200 Subject: [PATCH 1097/3170] Feed domain list to regen-conf hooks directly through env to avoid having to call 'yunohost domain list' --- data/hooks/conf_regen/12-metronome | 8 +++----- data/hooks/conf_regen/15-nginx | 14 +++++--------- data/hooks/conf_regen/19-postfix | 5 ++--- data/hooks/conf_regen/31-rspamd | 5 +---- data/hooks/conf_regen/43-dnsmasq | 5 ++--- src/yunohost/regenconf.py | 11 +++++++++-- 6 files changed, 22 insertions(+), 26 deletions(-) diff --git a/data/hooks/conf_regen/12-metronome b/data/hooks/conf_regen/12-metronome index 5a50b2b6e..8aee90212 100755 --- a/data/hooks/conf_regen/12-metronome +++ b/data/hooks/conf_regen/12-metronome @@ -14,7 +14,6 @@ do_pre_regen() { # retrieve variables main_domain=$(cat /etc/yunohost/current_host) - domain_list=$(yunohost domain list --output-as plain --quiet) # install main conf file cat metronome.cfg.lua \ @@ -22,7 +21,7 @@ do_pre_regen() { > "${metronome_dir}/metronome.cfg.lua" # add domain conf files - for domain in $domain_list; do + for domain in $YNH_DOMAINS; do cat domain.tpl.cfg.lua \ | sed "s/{{ domain }}/${domain}/g" \ > "${metronome_conf_dir}/${domain}.cfg.lua" @@ -33,7 +32,7 @@ do_pre_regen() { | awk '/^[^\.]+\.[^\.]+.*\.cfg\.lua$/ { print $1 }') for file in $conf_files; do domain=${file%.cfg.lua} - [[ $domain_list =~ $domain ]] \ + [[ $YNH_DOMAINS =~ $domain ]] \ || touch "${metronome_conf_dir}/${file}" done } @@ -43,10 +42,9 @@ do_post_regen() { # retrieve variables main_domain=$(cat /etc/yunohost/current_host) - domain_list=$(yunohost domain list --output-as plain --quiet) # create metronome directories for domains - for domain in $domain_list; do + for domain in $YNH_DOMAINS; do mkdir -p "/var/lib/metronome/${domain//./%2e}/pep" done # http_upload directory must be writable by metronome and readable by nginx diff --git a/data/hooks/conf_regen/15-nginx b/data/hooks/conf_regen/15-nginx index f1a278440..a43107599 100755 --- a/data/hooks/conf_regen/15-nginx +++ b/data/hooks/conf_regen/15-nginx @@ -46,7 +46,6 @@ do_pre_regen() { # retrieve variables main_domain=$(cat /etc/yunohost/current_host) - domain_list=$(yunohost domain list --output-as plain --quiet) # Support different strategy for security configurations export compatibility="$(yunohost settings get 'security.nginx.compatibility')" @@ -55,7 +54,7 @@ do_pre_regen() { cert_status=$(yunohost domain cert-status --json) # add domain conf files - for domain in $domain_list; do + for domain in $YNH_DOMAINS; do domain_conf_dir="${nginx_conf_dir}/${domain}.d" mkdir -p "$domain_conf_dir" mail_autoconfig_dir="${pending_dir}/var/www/.well-known/${domain}/autoconfig/mail/" @@ -83,7 +82,7 @@ do_pre_regen() { | awk '/^[^\.]+\.[^\.]+.*\.conf$/ { print $1 }') for file in $conf_files; do domain=${file%.conf} - [[ $domain_list =~ $domain ]] \ + [[ $YNH_DOMAINS =~ $domain ]] \ || touch "${nginx_conf_dir}/${file}" done @@ -91,7 +90,7 @@ do_pre_regen() { autoconfig_files=$(ls -1 /var/www/.well-known/*/autoconfig/mail/config-v1.1.xml 2>/dev/null || true) for file in $autoconfig_files; do domain=$(basename $(readlink -f $(dirname $file)/../..)) - [[ $domain_list =~ $domain ]] \ + [[ $YNH_DOMAINS =~ $domain ]] \ || (mkdir -p "$(dirname ${pending_dir}/${file})" && touch "${pending_dir}/${file}") done @@ -105,16 +104,13 @@ do_post_regen() { [ -z "$regen_conf_files" ] && exit 0 - # retrieve variables - domain_list=$(yunohost domain list --output-as plain --quiet) - # create NGINX conf directories for domains - for domain in $domain_list; do + for domain in $YNH_DOMAINS; do mkdir -p "/etc/nginx/conf.d/${domain}.d" done # Get rid of legacy lets encrypt snippets - for domain in $domain_list; do + for domain in $YNH_DOMAINS; do # If the legacy letsencrypt / acme-challenge domain-specific snippet is still there if [ -e /etc/nginx/conf.d/${domain}.d/000-acmechallenge.conf ] then diff --git a/data/hooks/conf_regen/19-postfix b/data/hooks/conf_regen/19-postfix index 10076b680..68afe4bc9 100755 --- a/data/hooks/conf_regen/19-postfix +++ b/data/hooks/conf_regen/19-postfix @@ -20,18 +20,17 @@ do_pre_regen() { # prepare main.cf conf file main_domain=$(cat /etc/yunohost/current_host) - domain_list=$(yunohost domain list --output-as plain --quiet | tr '\n' ' ') # Support different strategy for security configurations export compatibility="$(yunohost settings get 'security.postfix.compatibility')" export main_domain - export domain_list + export domain_list="$YNH_DOMAINS" ynh_render_template "main.cf" "${postfix_dir}/main.cf" cat postsrsd \ | sed "s/{{ main_domain }}/${main_domain}/g" \ - | sed "s/{{ domain_list }}/${domain_list}/g" \ + | sed "s/{{ domain_list }}/${YNH_DOMAINS}/g" \ > "${default_dir}/postsrsd" # adapt it for IPv4-only hosts diff --git a/data/hooks/conf_regen/31-rspamd b/data/hooks/conf_regen/31-rspamd index 26fea4336..861549e27 100755 --- a/data/hooks/conf_regen/31-rspamd +++ b/data/hooks/conf_regen/31-rspamd @@ -25,11 +25,8 @@ do_post_regen() { mkdir -p /etc/dkim chown _rspamd /etc/dkim - # retrieve domain list - domain_list=$(yunohost domain list --output-as plain --quiet) - # create DKIM key for domains - for domain in $domain_list; do + for domain in $YNH_DOMAINS; do domain_key="/etc/dkim/${domain}.mail.key" [ ! -f "$domain_key" ] && { # We use a 1024 bit size because nsupdate doesn't seem to be able to diff --git a/data/hooks/conf_regen/43-dnsmasq b/data/hooks/conf_regen/43-dnsmasq index 59a1f8a06..8a2985f34 100755 --- a/data/hooks/conf_regen/43-dnsmasq +++ b/data/hooks/conf_regen/43-dnsmasq @@ -26,10 +26,9 @@ do_pre_regen() { ynh_validate_ip4 "$ipv4" || ipv4='127.0.0.1' ipv6=$(curl -s -6 https://ip6.yunohost.org 2>/dev/null || true) ynh_validate_ip6 "$ipv6" || ipv6='' - domain_list=$(yunohost domain list --output-as plain --quiet) # add domain conf files - for domain in $domain_list; do + for domain in $YNH_DOMAINS; do cat domain.tpl \ | sed "s/{{ domain }}/${domain}/g" \ | sed "s/{{ ip }}/${ipv4}/g" \ @@ -42,7 +41,7 @@ do_pre_regen() { conf_files=$(ls -1 /etc/dnsmasq.d \ | awk '/^[^\.]+\.[^\.]+.*$/ { print $1 }') for domain in $conf_files; do - [[ $domain_list =~ $domain ]] \ + [[ $YNH_DOMAINS =~ $domain ]] \ || touch "${dnsmasq_dir}/${domain}" done } diff --git a/src/yunohost/regenconf.py b/src/yunohost/regenconf.py index ad84c8164..b81c043ce 100644 --- a/src/yunohost/regenconf.py +++ b/src/yunohost/regenconf.py @@ -141,7 +141,14 @@ def regen_conf(operation_logger, names=[], with_diff=False, force=False, dry_run if "glances" in names: names.remove("glances") - pre_result = hook_callback('conf_regen', names, pre_callback=_pre_call) + # [Optimization] We compute and feed the domain list to the conf regen + # hooks to avoid having to call "yunohost domain list" so many times which + # ends up in wasted time (about 3~5 seconds per call on a RPi2) + from yunohost.domain import domain_list + env = {} + env["YNH_DOMAINS"] = " ".join(domain_list()["domains"]) + + pre_result = hook_callback('conf_regen', names, pre_callback=_pre_call, env=env) # Keep only the hook names with at least one success names = [hook for hook, infos in pre_result.items() @@ -310,7 +317,7 @@ def regen_conf(operation_logger, names=[], with_diff=False, force=False, dry_run regen_conf_files = '' return post_args + [regen_conf_files, ] - hook_callback('conf_regen', names, pre_callback=_pre_call) + hook_callback('conf_regen', names, pre_callback=_pre_call, env=env) operation_logger.success() From b90155423d73614340c9aa9c85d902afd7b0f1e9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 25 Apr 2020 04:33:08 +0200 Subject: [PATCH 1098/3170] Add a caching mechanism to get_public_ip to avoid loading requests and calling ip.yunohost.org dozens of time per minute... --- src/yunohost/utils/network.py | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/yunohost/utils/network.py b/src/yunohost/utils/network.py index 3ae1ba910..92ca2a266 100644 --- a/src/yunohost/utils/network.py +++ b/src/yunohost/utils/network.py @@ -21,7 +21,9 @@ import os import re import logging +import time +from moulinette.utils.filesystem import read_file, write_to_file from moulinette.utils.network import download_text from moulinette.utils.process import check_output @@ -29,14 +31,24 @@ logger = logging.getLogger('yunohost.utils.network') def get_public_ip(protocol=4): - """Retrieve the public IP address from ip.yunohost.org""" - if protocol == 4: - url = 'https://ip.yunohost.org' - elif protocol == 6: - url = 'https://ip6.yunohost.org' + assert protocol in [4, 6], "Invalid protocol version for get_public_ip: %s, expected 4 or 6" % protocol + + cache_file = "/var/cache/yunohost/ipv%s" % protocol + cache_duration = 120 # 2 min + if os.path.exists(cache_file) and abs(os.path.getctime(cache_file) - time.time()) < cache_duration: + ip = read_file(cache_file).strip() + ip = ip if ip else None # Empty file (empty string) means there's no IP + logger.debug("Reusing IPv%s from cache: %s" % (protocol, ip)) else: - raise ValueError("invalid protocol version") + ip = get_public_ip_from_remote_server(protocol) + logger.debug("IP fetched: %s" % ip) + write_to_file(cache_file, ip or "") + return ip + + +def get_public_ip_from_remote_server(protocol=4): + """Retrieve the public IP address from ip.yunohost.org""" # We can know that ipv6 is not available directly if this file does not exists if protocol == 6 and not os.path.exists("/proc/net/if_inet6"): @@ -49,6 +61,9 @@ def get_public_ip(protocol=4): logger.debug("No default route for IPv%s, so assuming there's no IP address for that version" % protocol) return None + url = 'https://ip%s.yunohost.org' % (protocol if protocol != 4 else '') + logger.debug("Fetching IP from %s " % url) + try: return download_text(url, timeout=30).strip() except Exception as e: From 9ef2c60a90e828bd991f6d06e728b4537b9af358 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 25 Apr 2020 05:24:27 +0200 Subject: [PATCH 1099/3170] Uhoh we can't call domain_list if postinstall ain't done yet --- src/yunohost/regenconf.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/yunohost/regenconf.py b/src/yunohost/regenconf.py index b81c043ce..94883367b 100644 --- a/src/yunohost/regenconf.py +++ b/src/yunohost/regenconf.py @@ -146,7 +146,12 @@ def regen_conf(operation_logger, names=[], with_diff=False, force=False, dry_run # ends up in wasted time (about 3~5 seconds per call on a RPi2) from yunohost.domain import domain_list env = {} - env["YNH_DOMAINS"] = " ".join(domain_list()["domains"]) + # Well we can only do domain_list() if postinstall is done ... + # ... but hooks that effectively need the domain list are only + # called only after the 'installed' flag is set so that's all good, + # though kinda tight-coupled to the postinstall logic :s + if os.path.exists("/etc/yunohost/installed"): + env["YNH_DOMAINS"] = " ".join(domain_list()["domains"]) pre_result = hook_callback('conf_regen', names, pre_callback=_pre_call, env=env) From cde68cd7ccebc5ae0ebd8c60a35e5389d9164ef4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 25 Apr 2020 23:52:55 +0200 Subject: [PATCH 1100/3170] Make sure to strip() the path just in case Co-Authored-By: Bram --- src/yunohost/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 37da3a957..a9bcf02fc 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -239,7 +239,7 @@ def app_map(app=None, raw=False, user=None): perm_domain, perm_path = perm_url.split("/", 1) perm_path = "/" + perm_path.rstrip("/") - perm_path = perm_path if perm_path != "" else "/" + perm_path = perm_path if perm_path.strip() != "" else "/" return perm_domain, perm_path @@ -1269,7 +1269,7 @@ def app_ssowatconf(): perm_domain, perm_path = perm_url.split("/", 1) perm_path = "/" + perm_path.rstrip("/") - perm_path = perm_path if perm_path != "" else "/" + perm_path = perm_path if perm_path.strip() != "" else "/" return perm_domain + perm_path From 69938c3feb50c2c72d9e7208b8b88c27d6f70174 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 26 Apr 2020 03:43:05 +0200 Subject: [PATCH 1101/3170] Re-add 'app fetchlist', 'app list -i', 'app list' filter for backward compatibility... --- data/actionsmap/yunohost.yml | 9 +++++++++ src/yunohost/app.py | 24 +++++++++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index e1229352c..d55303d08 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -563,6 +563,9 @@ app: help: Also return a list of app categories action: store_true + fetchlist: + deprecated: true + ### app_list() list: action_help: List installed apps @@ -572,6 +575,12 @@ app: full: --full help: Display all details, including the app manifest and various other infos action: store_true + -i: + full: --installed + help: Dummy argument, does nothing anymore (still there only for backward compatibility) + action: store_true + filter: + nargs: '?' ### app_info() info: diff --git a/src/yunohost/app.py b/src/yunohost/app.py index b94f57502..8dce2ff38 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -110,12 +110,34 @@ def app_catalog(full=False, with_categories=False): return {"apps": catalog["apps"], "categories": catalog["categories"]} -def app_list(full=False): + +# Old legacy function... +def app_fetchlist(): + logger.warning("'yunohost app fetchlist' is deprecated. Please use 'yunohost tools update --apps' instead") + from yunohost.tools import tools_update + tools_update(apps=True) + + +def app_list(full=False, installed=False, filter=None): """ List installed apps """ + + # Old legacy argument ... app_list was a combination of app_list and + # app_catalog before 3.8 ... + if installed: + logger.warning("Argument --installed ain't needed anymore when using 'yunohost app list'. It directly returns the list of installed apps..") + + # Filter is a deprecated option... + if filter: + logger.warning("Using -f $appname in 'yunohost app list' is deprecated. Just use 'yunohost app list | grep -q 'id: $appname' to check a specific app is installed") + out = [] for app_id in sorted(_installed_apps()): + + if filter and not app_id.startswith(filter): + continue + try: app_info_dict = app_info(app_id, full=full) except Exception as e: From 0226ff2fd1f71d82e1e9a6f969f21c09dc464f8c Mon Sep 17 00:00:00 2001 From: xaloc33 Date: Thu, 16 Apr 2020 12:58:13 +0000 Subject: [PATCH 1102/3170] Translated using Weblate (Catalan) Currently translated at 100.0% (598 of 598 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ca/ --- locales/ca.json | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/locales/ca.json b/locales/ca.json index ff7045d0a..bd071e354 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -4,7 +4,7 @@ "admin_password_change_failed": "No es pot canviar la contrasenya", "admin_password_changed": "S'ha canviat la contrasenya d'administració", "app_already_installed": "{app:s} ja està instal·lada", - "app_already_installed_cant_change_url": "Aquesta aplicació ja està instal·lada. La URL no és pot canviar únicament amb aquesta funció. Mireu a \"app changeurl\" si està disponible.", + "app_already_installed_cant_change_url": "Aquesta aplicació ja està instal·lada. La URL no és pot canviar únicament amb aquesta funció. Mireu a `app changeurl` si està disponible.", "app_already_up_to_date": "{app:s} ja està actualitzada", "app_argument_choice_invalid": "Utilitzeu una de les opcions «{choices:s}» per l'argument «{name:s}»", "app_argument_invalid": "Escolliu un valor vàlid per l'argument «{name:s}»: {error:s}", @@ -59,7 +59,7 @@ "backup_couldnt_bind": "No es pot lligar {src:s} amb {dest:s}.", "backup_created": "S'ha creat la còpia de seguretat", "aborting": "Avortant.", - "app_not_upgraded": "L'aplicació «{failed_app}» no s'ha pogut actualitzar, i com a conseqüència l'actualització de les següents aplicacions ha estat cancel·lada: {apps}", + "app_not_upgraded": "L'aplicació «{failed_app}» no s'ha pogut actualitzar, i com a conseqüència s'ha cancel·lat l'actualització de les següents aplicacions: {apps}", "app_start_install": "instal·lant l'aplicació «{app}»…", "app_start_remove": "Eliminant l'aplicació «{app}»…", "app_start_backup": "Recuperant els fitxers pels que s'ha de fer una còpia de seguretat per «{app}»…", @@ -167,7 +167,7 @@ "file_does_not_exist": "El camí {path:s} no existeix.", "firewall_reload_failed": "No s'ha pogut tornar a carregar el tallafocs", "firewall_reloaded": "S'ha tornat a carregar el tallafocs", - "firewall_rules_cmd_failed": "No s'han pogut aplicar algunes regles del tallafocs. Més informació en el registre.", + "firewall_rules_cmd_failed": "Han fallat algunes comandes per aplicar regles del tallafocs. Més informació en el registre.", "global_settings_bad_choice_for_enum": "Opció pel paràmetre {setting:s} incorrecta, s'ha rebut «{choice:s}», però les opcions disponibles són: {available_choices:s}", "global_settings_bad_type_for_setting": "El tipus del paràmetre {setting:s} és incorrecte. S'ha rebut {received_type:s}, però s'esperava {expected_type:s}", "global_settings_cant_open_settings": "No s'ha pogut obrir el fitxer de configuració, raó: {reason:s}", @@ -284,7 +284,7 @@ "migration_0008_root": "• No es podrà connectar com a root a través de SSH. S'haurà d'utilitzar l'usuari admin per fer-ho;", "migration_0008_dsa": "• Es desactivarà la clau DSA. Per tant, es podria haver d'invalidar un missatge esgarrifós del client SSH, i tornar a verificar l'empremta digital del servidor;", "migration_0008_warning": "Si heu entès els avisos i voleu que YunoHost sobreescrigui la configuració actual, comenceu la migració. Sinó, podeu saltar-vos la migració, tot i que no està recomanat.", - "migration_0008_no_warning": "Hauria de ser segur sobreescriure la configuració SSH, però no es pot estar del tot segur! Executetu la migració per sobreescriure-la. Sinó, podeu saltar-vos la migració, tot i que no està recomanat.", + "migration_0008_no_warning": "Hauria de ser segur sobreescriure la configuració SSH, però no es pot estar del tot segur! Executeu la migració per sobreescriure-la. Sinó, podeu saltar-vos la migració, tot i que no està recomanat.", "migration_0009_not_needed": "Sembla que ja s'ha fet aquesta migració… (?) Ometent.", "migrations_cant_reach_migration_file": "No s'ha pogut accedir als fitxers de migració al camí «%s»", "migrations_list_conflict_pending_done": "No es pot utilitzar «--previous» i «--done» al mateix temps.", @@ -596,5 +596,7 @@ "diagnosis_description_web": "Web", "diagnosis_basesystem_hardware_board": "El model de la targeta del servidor és {model}", "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à…" + "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." } From 8fd7456a9be2f069bbf9ea0702023dc7bac16119 Mon Sep 17 00:00:00 2001 From: Zeik0s Date: Wed, 15 Apr 2020 15:38:08 +0000 Subject: [PATCH 1103/3170] Translated using Weblate (German) Currently translated at 36.1% (216 of 598 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 43 +++++++++++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/locales/de.json b/locales/de.json index 2369e3bdc..b354f60c5 100644 --- a/locales/de.json +++ b/locales/de.json @@ -15,7 +15,7 @@ "app_removed": "{app:s} wurde entfernt", "app_sources_fetch_failed": "Quelldateien konnten nicht abgerufen werden, ist die URL korrekt?", "app_unknown": "Unbekannte App", - "app_upgrade_failed": "{app:s} konnte nicht aktualisiert werden", + "app_upgrade_failed": "{app:s} konnte nicht aktualisiert werden: {error}", "app_upgraded": "{app:s} aktualisiert", "ask_email": "E-Mail-Adresse", "ask_firstname": "Vorname", @@ -35,7 +35,7 @@ "backup_hook_unknown": "Der Datensicherungshook '{hook:s}' unbekannt", "backup_invalid_archive": "Dies ist kein Backup-Archiv", "backup_nothings_done": "Keine Änderungen zur Speicherung", - "backup_output_directory_forbidden": "Wähle ein anderes Ausgabeverzeichnis. Datensicherung können nicht in /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var oder in Unterordnern von /home/yunohost.backup/archives erstellt werden", + "backup_output_directory_forbidden": "Wähle ein anderes Ausgabeverzeichnis. Datensicherungen können nicht in /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var oder in Unterordnern von /home/yunohost.backup/archives erstellt werden", "backup_output_directory_not_empty": "Der gewählte Ausgabeordner sollte leer sein", "backup_output_directory_required": "Für die Datensicherung muss ein Zielverzeichnis angegeben werden", "backup_running_hooks": "Datensicherunghook wird ausgeführt…", @@ -213,7 +213,7 @@ "domain_dns_conf_is_just_a_recommendation": "Dieser Befehl zeigt Ihnen, was die * empfohlene * Konfiguration ist. Die DNS-Konfiguration wird NICHT für Sie eingerichtet. Es liegt in Ihrer Verantwortung, Ihre DNS-Zone in Ihrem Registrar gemäß dieser Empfehlung zu konfigurieren.", "dpkg_lock_not_available": "Dieser Befehl kann momentan nicht ausgeführt werden, da anscheinend ein anderes Programm die Sperre von dpkg (dem Systempaket-Manager) verwendet", "confirm_app_install_thirdparty": "WARNUNG! Das Installieren von Anwendungen von Drittanbietern kann die Integrität und Sicherheit Deines Systems beeinträchtigen. Du solltest es wahrscheinlich NICHT installieren, es sei denn, Du weisst, was Du tust. Bist du bereit, dieses Risiko einzugehen? [{answers:s}]", - "confirm_app_install_danger": "WARNUNG! Diese Anwendung ist noch experimentell (wenn nicht ausdrücklich \"not working\"/\"funktioniert nicht\") und es ist wahrscheinlich, dass Dein System Schaden nimmt! Du solltest es wahrscheinlich NICHT installieren, es sei denn, Du weisst, was Du tust. Bist du bereit, dieses Risiko einzugehen? [{answers:s}]", + "confirm_app_install_danger": "WARNUNG! Diese Anwendung ist noch experimentell (wenn nicht ausdrücklich \"not working\"/\"nicht funktionsfähig\")! Du solltest es wahrscheinlich NICHT installieren, es sei denn, du weißt, was du tust. Es wird keine Unterstützung geleistet, falls diese Anwendung nicht funktioniert oder dein System zerstört... Falls du bereit bist, dieses Risiko einzugehen, tippe '{answers:s}'", "confirm_app_install_warning": "Warnung: Diese Anwendung funktioniert möglicherweise, ist jedoch nicht gut in YunoHost integriert. Einige Funktionen wie Single Sign-On und Backup / Restore sind möglicherweise nicht verfügbar. Trotzdem installieren? [{answers:s}] ", "backup_with_no_restore_script_for_app": "Die App {app:s} hat kein Wiederherstellungsskript. Das Backup dieser App kann nicht automatisch wiederhergestellt werden.", "backup_with_no_backup_script_for_app": "Die App {app:s} hat kein Sicherungsskript. Ignoriere es.", @@ -231,7 +231,7 @@ "backup_csv_creation_failed": "Die zur Wiederherstellung erforderliche CSV-Datei kann nicht erstellt werden", "backup_couldnt_bind": "{src:s} konnte nicht an {dest:s} angebunden werden.", "backup_borg_not_implemented": "Die Borg-Sicherungsmethode ist noch nicht implementiert", - "backup_ask_for_copying_if_needed": "Möchten Sie die Sicherung mit {size:s} MB temporär durchführen? (Dieser Weg wird verwendet, da einige Dateien nicht mit einer effizienteren Methode vorbereitet werden konnten).", + "backup_ask_for_copying_if_needed": "Möchten Sie die Sicherung mit {size:s} MB temporär durchführen? (Dieser Weg wird verwendet, da einige Dateien nicht mit einer effizienteren Methode vorbereitet werden konnten.)", "backup_actually_backuping": "Erstellt ein Backup-Archiv aus den gesammelten Dateien …", "ask_new_path": "Neuer Pfad", "ask_new_domain": "Neue Domain", @@ -302,17 +302,40 @@ "app_install_script_failed": "Im Installationsscript ist ein Fehler aufgetreten", "app_remove_after_failed_install": "Entfernen der App nach fehlgeschlagener Installation…", "app_upgrade_script_failed": "Es ist ein Fehler im App-Upgrade-Skript aufgetreten", - "diagnosis_basesystem_host": "Server läuft unter Debian {debian_version}.", + "diagnosis_basesystem_host": "Server läuft unter Debian {debian_version}", "diagnosis_basesystem_kernel": "Server läuft unter Linux-Kernel {kernel_version}", "diagnosis_basesystem_ynh_single_version": "{package} Version: {version} ({repo})", "diagnosis_basesystem_ynh_main_version": "Server läuft YunoHost {main_version} ({repo})", "diagnosis_basesystem_ynh_inconsistent_versions": "Sie verwenden inkonsistente Versionen der YunoHost-Pakete... wahrscheinlich wegen eines fehlgeschlagenen oder teilweisen Upgrades.", "diagnosis_display_tip_web": "Sie können den Abschnitt Diagnose (im Startbildschirm) aufrufen, um die gefundenen Probleme anzuzeigen.", - "apps_catalog_init_success": "Apps-Katalogsystem initialisiert!", - "apps_catalog_updating": "Aktualisierung des Applikationskatalogs...", - "apps_catalog_failed_to_download": "Der {apps_catalog} Apps-Katalog kann nicht heruntergeladen werden: {error}", - "apps_catalog_obsolete_cache": "Der Cache des Apps-Katalogs ist leer oder veraltet.", + "apps_catalog_init_success": "App-Katalogsystem initialisiert!", + "apps_catalog_updating": "Aktualisierung des Applikationskatalogs…", + "apps_catalog_failed_to_download": "Der {apps_catalog} App-Katalog kann nicht heruntergeladen werden: {error}", + "apps_catalog_obsolete_cache": "Der Cache des App-Katalogs ist leer oder veraltet.", "apps_catalog_update_success": "Der Apps-Katalog wurde aktualisiert!", "password_too_simple_1": "Das Passwort muss mindestens 8 Zeichen lang sein", - "diagnosis_display_tip_cli": "Sie können 'yunohost diagnosis show --issues' ausführen, um die gefundenen Probleme anzuzeigen." + "diagnosis_display_tip_cli": "Sie können 'yunohost diagnosis show --issues' ausführen, um die gefundenen Probleme anzuzeigen.", + "diagnosis_everything_ok": "Alles schaut gut aus für {category}!", + "diagnosis_failed": "Kann Diagnose-Ergebnis für die Kategorie '{category}' nicht abrufen: {error}", + "diagnosis_ip_connected_ipv4": "Der Server ist mit dem Internet über IPv4 verbunden!", + "diagnosis_no_cache": "Kein Diagnose Cache aktuell für die Kategorie '{category}'", + "diagnosis_ip_no_ipv4": "Der Server hat kein funktionierendes IPv4.", + "diagnosis_ip_connected_ipv6": "Der Server ist mit dem Internet über IPv6 verbunden!", + "diagnosis_ip_no_ipv6": "Der Server hat kein funktionierendes IPv6.", + "diagnosis_ip_not_connected_at_all": "Der Server scheint überhaupt nicht mit dem Internet verbunden zu sein!?", + "diagnosis_failed_for_category": "Diagnose fehlgeschlagen für die Kategorie '{category}': {error}", + "diagnosis_cache_still_valid": "(Cache noch gültig für {category} Diagnose. Werde keine neue Diagnose durchführen!)", + "diagnosis_cant_run_because_of_dep": "Kann Diagnose für {category} nicht ausführen während wichtige Probleme zu {dep} noch nicht behoben sind.", + "diagnosis_found_errors_and_warnings": "Habe {errors} erhebliche(s) Problem(e) (und {warnings} Warnung(en)) in Verbindung mit {category} gefunden!", + "diagnosis_ip_broken_dnsresolution": "Domänen-Namens-Auflösung scheint aus einem bestimmten Grund nicht zu funktionieren... Blockiert eine Firewall die DNS Anfragen?", + "diagnosis_ip_broken_resolvconf": "Domänen-Namens-Auflösung scheint nicht zu funktionieren, was daran liegen könnte, dass in /etc/resolv.conf kein Eintrag auf 127.0.0.1 zeigt.", + "diagnosis_ip_weird_resolvconf_details": "Stattdessen sollte diese Datei ein Softlink auf /etc/resolvconf/run/resolv.conf sein, die auf sich selbst zu 127.0.0.1 zeigt (dnsmasq). Der eigentlich Auflösende sollte in /etc/resolv.dnsmasq.conf konfiguriert werden.", + "diagnosis_dns_good_conf": "Gute DNS Konfiguration für Domäne {domain} (Kategorie {category})", + "diagnosis_ignored_issues": "(+ {nb_ignored} ignorierte(s) Problem(e))", + "diagnosis_basesystem_hardware": "Server Hardware Architektur ist {virt} {arch}", + "diagnosis_basesystem_hardware_board": "Server Platinen Modell ist {model}", + "diagnosis_found_errors": "Habe {errors} erhebliche(s) Problem(e) in Verbindung mit {category} gefunden!", + "diagnosis_found_warnings": "Habe {warnings} Ding(e) gefunden, die verbessert werden könnten für {category}.", + "diagnosis_ip_dnsresolution_working": "Domänen-Namens-Auflösung funktioniert!", + "diagnosis_ip_weird_resolvconf": "DNS Auflösung scheint zu funktionieren, aber sei vorsichtig wenn du eine eigene /etc/resolv.conf verwendest." } From ced18062562370f9347f930ef2be8876ff8e06e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= Date: Wed, 15 Apr 2020 16:45:40 +0000 Subject: [PATCH 1104/3170] Translated using Weblate (French) Currently translated at 96.2% (575 of 598 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 80d9f07dd..ee6aca0a8 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -555,9 +555,8 @@ "diagnosis_swap_ok": "Le système dispose de {total} de swap !", "diagnosis_regenconf_manually_modified": "Le fichier de configuration {file} a été modifié manuellement.", "diagnosis_regenconf_manually_modified_debian": "Le fichier de configuration {file} a été modifié manuellement par rapport à celui par défaut de Debian.", - "diagnosis_regenconf_manually_modified_details": "C’est probablement OK tant que vous savez ce que vous faites ;) !", + "diagnosis_regenconf_manually_modified_details": "C'est probablement OK tant que vous savez ce que vous faites;) !", "diagnosis_regenconf_manually_modified_debian_details": "Cela peut probablement être OK, mais il faut garder un œil dessus …", - "diagnosis_security_all_good": "Aucune vulnérabilité de sécurité critique n’a été trouvée.", "apps_catalog_init_success": "Système de catalogue d’applications initialisé !", "apps_catalog_failed_to_download": "Impossible de télécharger le catalogue des applications {apps_catalog}:{error}", "diagnosis_mail_outgoing_port_25_blocked": "Le port sortant 25 semble être bloqué. Vous devriez essayer de le débloquer dans le panneau de configuration de votre fournisseur de services Internet (ou hébergeur). En attendant, le serveur ne pourra pas envoyer de courrier électronique à d’autres serveurs.", From 661735c9d9b59d884469c1e062e4a7e8cfce87a2 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Mon, 20 Apr 2020 10:48:16 +0000 Subject: [PATCH 1105/3170] Translated using Weblate (Esperanto) Currently translated at 90.8% (543 of 598 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/eo/ --- locales/eo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/eo.json b/locales/eo.json index f81ea8da6..eb701494d 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -1,6 +1,6 @@ { "admin_password_change_failed": "Ne eblas ŝanĝi pasvorton", - "admin_password_changed": "La pasvorto de administrado ŝanĝiĝis", + "admin_password_changed": "La pasvorto de administrado estis ŝanĝita", "app_already_installed": "{app:s} estas jam instalita", "app_already_up_to_date": "{app:s} estas jam ĝisdata", "app_argument_required": "Parametro {name:s} estas bezonata", @@ -81,7 +81,7 @@ "backup_archive_name_exists": "Rezerva arkivo kun ĉi tiu nomo jam ekzistas.", "backup_applying_method_tar": "Krei la rezervan TAR-ar archiveivon …", "backup_method_custom_finished": "Propra rezerva metodo '{method:s}' finiĝis", - "app_already_installed_cant_change_url": "Ĉi tiu app estas jam instalita. La URL ne povas esti ŝanĝita nur per ĉi tiu funkcio. Rigardu \"app changeurl\" se ĝi haveblas.", + "app_already_installed_cant_change_url": "Ĉi tiu app estas jam instalita. La URL ne povas esti ŝanĝita nur per ĉi tiu funkcio. Kontrolu en `app changeurl` se ĝi haveblas.", "app_not_correctly_installed": "{app:s} ŝajnas esti malĝuste instalita", "app_removed": "{app:s} forigita", "backup_delete_error": "Ne povis forigi '{path:s}'", From 220c62ab4c18e1a66546a9d3af8aebf5ea1c0da8 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Mon, 20 Apr 2020 09:54:48 +0000 Subject: [PATCH 1106/3170] Translated using Weblate (Spanish) Currently translated at 100.0% (598 of 598 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/es/ --- locales/es.json | 89 +++++++++++++++++++++++++------------------------ 1 file changed, 46 insertions(+), 43 deletions(-) diff --git a/locales/es.json b/locales/es.json index 15b4b5316..f76d722e6 100644 --- a/locales/es.json +++ b/locales/es.json @@ -2,7 +2,7 @@ "action_invalid": "Acción no válida '{action:s} 1'", "admin_password": "Contraseña administrativa", "admin_password_change_failed": "No se puede cambiar la contraseña", - "admin_password_changed": "La contraseña de administración ha sido cambiada", + "admin_password_changed": "La contraseña de administración fue cambiada", "app_already_installed": "{app:s} ya está instalada", "app_argument_choice_invalid": "Use una de estas opciones «{choices:s}» para el argumento «{name:s}»", "app_argument_invalid": "Elija un valor válido para el argumento «{name:s}»: {error:s}", @@ -41,16 +41,16 @@ "backup_hook_unknown": "El gancho «{hook:s}» de la copia de seguridad es desconocido", "backup_invalid_archive": "Esto no es un archivo de respaldo", "backup_nothings_done": "Nada que guardar", - "backup_output_directory_forbidden": "Elija un directorio de salida diferente. No se pueden crear copias de seguridad en las subcarpetas de /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var o /home/yunohost.backup/archives", + "backup_output_directory_forbidden": "Elija un directorio de salida diferente. Las copias de seguridad no se pueden crear en /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var o /home/yunohost.backup/archives subcarpetas", "backup_output_directory_not_empty": "Debe elegir un directorio de salida vacío", "backup_output_directory_required": "Debe proporcionar un directorio de salida para la copia de seguridad", "backup_running_hooks": "Ejecutando los hooks de copia de seguridad...", "custom_app_url_required": "Debe proporcionar una URL para actualizar su aplicación personalizada {app:s}", "domain_cert_gen_failed": "No se pudo generar el certificado", "domain_created": "Dominio creado", - "domain_creation_failed": "No se pudo crear el dominio {domain}: {error}", + "domain_creation_failed": "No se puede crear el dominio {domain}: {error}", "domain_deleted": "Dominio eliminado", - "domain_deletion_failed": "No se pudo eliminar el dominio {domain}: {error}", + "domain_deletion_failed": "No se puede eliminar el dominio {domain}: {error}", "domain_dyndns_already_subscribed": "Ya se ha suscrito a un dominio de DynDNS", "domain_dyndns_root_unknown": "Dominio raíz de DynDNS desconocido", "domain_exists": "El dominio ya existe", @@ -117,20 +117,20 @@ "restore_running_app_script": "Restaurando la aplicación «{app:s}»…", "restore_running_hooks": "Ejecutando los ganchos de restauración…", "service_add_failed": "No se pudo añadir el servicio «{service:s}»", - "service_added": "Añadido el servicio «{service:s}»", + "service_added": "Se agregó el servicio '{service:s}'", "service_already_started": "El servicio «{service:s}» ya está funcionando", "service_already_stopped": "El servicio «{service:s}» ya ha sido detenido", "service_cmd_exec_failed": "No se pudo ejecutar la orden «{command:s}»", - "service_disable_failed": "No se pudo desactivar el servicio «{service:s}»\n\nRegistro de servicios recientes:{logs:s}", - "service_disabled": "El servicio «{service:s}» ha sido desactivado", - "service_enable_failed": "No se pudo activar el servicio «{service:s}»\n\nRegistro de servicios recientes:{logs:s}", - "service_enabled": "El servicio «{service:s}» ha sido desactivado", + "service_disable_failed": "No se pudo hacer que el servicio '{service:s}' no se iniciara en el arranque.\n\nRegistros de servicio recientes: {logs:s}", + "service_disabled": "El servicio '{service:s}' ya no se iniciará cuando se inicie el sistema.", + "service_enable_failed": "No se pudo hacer que el servicio '{service:s}' se inicie automáticamente en el arranque.\n\nRegistros de servicio recientes: {logs s}", + "service_enabled": "El servicio '{service:s}' ahora se iniciará automáticamente durante el arranque del sistema.", "service_remove_failed": "No se pudo eliminar el servicio «{service:s}»", - "service_removed": "Eliminado el servicio «{service:s}»", + "service_removed": "Servicio '{service:s}' eliminado", "service_start_failed": "No se pudo iniciar el servicio «{service:s}»\n\nRegistro de servicios recientes:{logs:s}", - "service_started": "Iniciado el servicio «{service:s}»", + "service_started": "El servicio '{service:s}' comenzó", "service_stop_failed": "No se pudo detener el servicio «{service:s}»\n\nRegistro de servicios recientes:{logs:s}", - "service_stopped": "El servicio «{service:s}» se detuvo", + "service_stopped": "Servicio '{service:s}' detenido", "service_unknown": "Servicio desconocido '{service:s}'", "ssowat_conf_generated": "Generada la configuración de SSOwat", "ssowat_conf_updated": "Actualizada la configuración de SSOwat", @@ -161,7 +161,7 @@ "yunohost_installing": "Instalando YunoHost…", "yunohost_not_installed": "YunoHost no está correctamente instalado. Ejecute «yunohost tools postinstall»", "ldap_init_failed_to_create_admin": "La inicialización de LDAP no pudo crear el usuario «admin»", - "mailbox_used_space_dovecot_down": "El servicio de correo Dovecot debe estar funcionando si desea obtener el espacio usado por el buzón de correo", + "mailbox_used_space_dovecot_down": "El servicio de buzón Dovecot debe estar activo si desea recuperar el espacio usado del buzón", "certmanager_attempt_to_replace_valid_cert": "Está intentando sobrescribir un certificado correcto y válido para el dominio {domain:s}! (Use --force para omitir este mensaje)", "certmanager_domain_unknown": "Dominio desconocido «{domain:s}»", "certmanager_domain_cert_not_selfsigned": "El certificado para el dominio {domain:s} no es un certificado autofirmado. ¿Está seguro de que quiere reemplazarlo? (Use «--force» para hacerlo)", @@ -170,7 +170,7 @@ "certmanager_attempt_to_renew_valid_cert": "¡El certificado para el dominio «{domain:s}» no está a punto de expirar! (Puede usar --force si sabe lo que está haciendo)", "certmanager_domain_http_not_working": "Parece que no se puede acceder al dominio {domain:s} a través de HTTP. Compruebe que la configuración del DNS y de NGINX es correcta", "certmanager_error_no_A_record": "No se ha encontrado un registro DNS «A» para el dominio {domain:s}. Debe hacer que su nombre de dominio apunte a su máquina para poder instalar un certificado de Let's Encrypt. (Si sabe lo que está haciendo, use «--no-checks» para desactivar esas comprobaciones.)", - "certmanager_domain_dns_ip_differs_from_public_ip": "El registro «A» del DNS para el dominio «{domain:s}» es diferente de la IP de este servidor. Si recientemente modificó su registro A, espere a que se propague (existen algunos verificadores de propagación de DNS disponibles en línea). (Si sabe lo que está haciendo, use «--no-checks» para desactivar esas comprobaciones.)", + "certmanager_domain_dns_ip_differs_from_public_ip": "El registro DNS 'A' para el dominio '{dominio:s}' es diferente de la IP de este servidor. Si recientemente modificó su registro A, espere a que se propague (algunos verificadores de propagación de DNS están disponibles en línea). (Si sabe lo que está haciendo, use '--no-checks' para desactivar esos cheques)", "certmanager_cannot_read_cert": "Se ha producido un error al intentar abrir el certificado actual para el dominio {domain:s} (archivo: {file:s}), razón: {reason:s}", "certmanager_cert_install_success_selfsigned": "Instalado correctamente un certificado autofirmado para el dominio «{domain:s}»", "certmanager_cert_install_success": "Instalado correctamente un certificado de Let's Encrypt para el dominio «{domain:s}»", @@ -179,7 +179,7 @@ "certmanager_cert_signing_failed": "No se pudo firmar el nuevo certificado", "certmanager_no_cert_file": "No se pudo leer el certificado para el dominio {domain:s} (archivo: {file:s})", "certmanager_conflicting_nginx_file": "No se pudo preparar el dominio para el desafío ACME: el archivo de configuración de NGINX {filepath:s} está en conflicto y debe ser eliminado primero", - "domain_cannot_remove_main": "No se puede eliminar el dominio principal. Primero debes configurar otro utilizando la linea de comando 'yunohost domain main-domain -n ' donde es parte de esta lista: {other_domains:s}", + "domain_cannot_remove_main": "No puede eliminar '{domain:s}' ya que es el dominio principal, primero debe configurar otro dominio como el dominio principal usando 'yunohost domain main-domain -n '; Aquí está la lista de dominios candidatos: {other_domains:s}", "certmanager_self_ca_conf_file_not_found": "No se pudo encontrar el archivo de configuración para la autoridad de autofirma (archivo: {file:s})", "certmanager_unable_to_parse_self_CA_name": "No se pudo procesar el nombre de la autoridad de autofirma (archivo: {file:s})", "domains_available": "Dominios disponibles:", @@ -189,7 +189,7 @@ "certmanager_couldnt_fetch_intermediate_cert": "Tiempo de espera agotado intentando obtener el certificado intermedio de Let's Encrypt. Cancelada la instalación o renovación del certificado. Vuelva a intentarlo más tarde.", "domain_hostname_failed": "No se pudo establecer un nuevo nombre de anfitrión («hostname»). Esto podría causar problemas más tarde (no es seguro... podría ir bien).", "yunohost_ca_creation_success": "Creada la autoridad de certificación local.", - "app_already_installed_cant_change_url": "Esta aplicación ya está instalada. No se puede cambiar el URL únicamente mediante esta función. Compruebe si está disponible la opción `app changeurl`.", + "app_already_installed_cant_change_url": "Esta aplicación ya está instalada. La URL no se puede cambiar solo con esta función. Marque `app changeurl` si está disponible.", "app_change_url_failed_nginx_reload": "No se pudo recargar NGINX. Esta es la salida de «nginx -t»:\n{nginx_errors:s}", "app_change_url_identical_domains": "El antiguo y nuevo dominio/url_path son idénticos ('{domain:s} {path:s}'), no se realizarán cambios.", "app_change_url_no_script": "La aplicación «{app_name:s}» aún no permite la modificación de URLs. Quizás debería actualizarla.", @@ -222,8 +222,8 @@ "dyndns_could_not_check_provide": "No se pudo verificar si {provider:s} puede ofrecer {domain:s}.", "dyndns_domain_not_provided": "El proveedor de DynDNS {provider:s} no puede proporcionar el dominio {domain:s}.", "experimental_feature": "Aviso : esta funcionalidad es experimental y no se considera estable, no debería usarla a menos que sepa lo que está haciendo.", - "good_practices_about_user_password": "Está a punto de establecer una nueva contraseña de usuario. La contraseña debería de ser de al menos 8 caracteres, aunque es una buena práctica usar una contraseña más extensa (básicamente una frase) y/o usar caracteres de varias clases (mayúsculas, minúsculas, números y caracteres especiales).", - "password_listed": "Esta contraseña es una de las más usadas en el mundo. Elija algo más único.", + "good_practices_about_user_password": "Ahora está a punto de definir una nueva contraseña de usuario. La contraseña debe tener al menos 8 caracteres, aunque es una buena práctica usar una contraseña más larga (es decir, una frase de contraseña) y / o una variación de caracteres (mayúsculas, minúsculas, dígitos y caracteres especiales).", + "password_listed": "Esta contraseña se encuentra entre las contraseñas más utilizadas en el mundo. Por favor, elija algo más único.", "password_too_simple_1": "La contraseña debe tener al menos 8 caracteres de longitud", "password_too_simple_2": "La contraseña tiene que ser de al menos 8 caracteres de longitud e incluir un número y caracteres en mayúsculas y minúsculas", "password_too_simple_3": "La contraseña tiene que ser de al menos 8 caracteres de longitud e incluir un número, mayúsculas, minúsculas y caracteres especiales", @@ -232,7 +232,7 @@ "update_apt_cache_warning": "Algo fue mal durante la actualización de la caché de APT (gestor de paquetes de Debian). Aquí tiene un volcado de las líneas de sources.list que podría ayudarle a identificar las líneas problemáticas:\n{sourceslist}", "update_apt_cache_failed": "No se pudo actualizar la caché de APT (gestor de paquetes de Debian). Aquí tiene un volcado de las líneas de sources.list que podría ayudarle a identificar las líneas problemáticas:\n{sourceslist}", "tools_upgrade_special_packages_completed": "Actualización de paquetes de YunoHost completada.\nPulse [Intro] para regresar a la línea de órdenes", - "tools_upgrade_special_packages_explanation": "Esta acción terminará pero la actualización especial real continuará en segundo plano. No inicie ninguna otra acción en su servidor en aproximadamente 10 minutos (dependiendo de la velocidad de su hardware). Una vez hecho, podría tener que volver a iniciar sesión en la administración web. El registro de actualización estará disponible en Herramientas → Registro (en la página de administración web) o mediante «yunohost log list» (desde la línea de órdenes).", + "tools_upgrade_special_packages_explanation": "La actualización especial continuará en segundo plano. No inicie ninguna otra acción en su servidor durante los próximos 10 minutos (dependiendo de la velocidad del hardware). Después de esto, es posible que deba volver a iniciar sesión en el administrador web. El registro de actualización estará disponible en Herramientas → Registro (en el webadmin) o usando 'yunohost log list' (desde la línea de comandos).", "tools_upgrade_special_packages": "Actualizando ahora paquetes «especiales» (relacionados con YunoHost)…", "tools_upgrade_regular_packages_failed": "No se pudieron actualizar los paquetes: {packages_list}", "tools_upgrade_regular_packages": "Actualizando ahora paquetes «normales» (no relacionados con YunoHost)…", @@ -241,11 +241,11 @@ "tools_upgrade_cant_both": "No se puede actualizar el sistema y las aplicaciones al mismo tiempo", "tools_upgrade_at_least_one": "Especifique «--apps», o «--system»", "this_action_broke_dpkg": "Esta acción rompió dpkg/APT(los gestores de paquetes del sistema)… Puede tratar de solucionar este problema conectando mediante SSH y ejecutando `sudo dpkg --configure -a`.", - "service_reloaded_or_restarted": "El servicio «{service:s}» ha sido recargado o reiniciado", + "service_reloaded_or_restarted": "El servicio '{service:s}' fue recargado o reiniciado", "service_reload_or_restart_failed": "No se pudo recargar o reiniciar el servicio «{service:s}»\n\nRegistro de servicios recientes:{logs:s}", - "service_restarted": "Reiniciado el servicio «{service:s}»", + "service_restarted": "Servicio '{service:s}' reiniciado", "service_restart_failed": "No se pudo reiniciar el servicio «{service:s}»\n\nRegistro de servicios recientes:{logs:s}", - "service_reloaded": "El servicio «{service:s}» ha sido recargado", + "service_reloaded": "Servicio '{service:s}' recargado", "service_reload_failed": "No se pudo recargar el servicio «{service:s}»\n\nRegistro de servicios recientes:{logs:s}", "service_regen_conf_is_deprecated": "¡«yunohost service regen-conf» está obsoleto! Use «yunohost tools regen-conf» en su lugar.", "service_description_yunohost-firewall": "Gestiona los puertos de conexiones abiertos y cerrados a los servicios", @@ -280,7 +280,7 @@ "regenconf_failed": "No se pudo regenerar la configuración para la(s) categoría(s): {categories}", "regenconf_dry_pending_applying": "Comprobando la configuración pendiente que habría sido aplicada para la categoría «{category}»…", "regenconf_would_be_updated": "La configuración habría sido actualizada para la categoría «{category}»", - "regenconf_updated": "Actualizada la configuración para la categoría '{category}'", + "regenconf_updated": "Configuración actualizada para '{category}'", "regenconf_up_to_date": "Ya está actualizada la configuración para la categoría «{category}»", "regenconf_now_managed_by_yunohost": "El archivo de configuración «{conf}» está gestionado ahora por YunoHost (categoría {category}).", "regenconf_file_updated": "Actualizado el archivo de configuración «{conf}»", @@ -345,7 +345,7 @@ "migration_0005_postgresql_96_not_installed": "PostgreSQL 9.4 está instalado pero no PostgreSQL 9.6. Algo raro podría haber ocurrido en su sistema:(…", "migration_0005_postgresql_94_not_installed": "PostgreSQL no estaba instalado en su sistema. Nada que hacer.", "migration_0003_modified_files": "Tenga en cuenta que se encontró que los siguientes archivos fueron modificados manualmente y podrían ser sobrescritos después de la actualización: {manually_modified_files}", - "migration_0003_problematic_apps_warning": "Tenga en cuenta que las aplicaciones listadas mas abajo fueron detectadas como 'posiblemente problemáticas'. Parece que no fueron instaladas desde una lista de aplicaciones o no estaban etiquetadas como 'funcional'. Así que no hay garantía de que aún funcionen después de la actualización: {problematic_apps}", + "migration_0003_problematic_apps_warning": "Tenga en cuenta que se detectaron las siguientes aplicaciones instaladas posiblemente problemáticas. Parece que no se instalaron desde un catálogo de aplicaciones, o no se marcan como \"en funcionamiento\". En consecuencia, no se puede garantizar que seguirán funcionando después de la actualización: {problematic_apps}", "migration_0003_general_warning": "Tenga en cuenta que esta migración es una operación delicada. El equipo de YunoHost ha hecho todo lo posible para revisarla y probarla, pero la migración aún podría romper parte del sistema o de sus aplicaciones.\n\nPor lo tanto, se recomienda que:\n - Realice una copia de seguridad de cualquier dato crítico o aplicación. Más información en https://yunohost.org/backup;\n - Tenga paciencia tras iniciar la migración: dependiendo de su conexión a Internet y de su hardware, podría tardar unas cuantas horas hasta que todo se actualice.\n\nAdemás, el puerto para SMTP usado por los clientes de correo externos (como Thunderbird o K9-Mail) cambió de 465 (SSL/TLS) a 587 (STARTTLS). El antiguo puerto (465) se cerrará automáticamente y el nuevo puerto (587) se abrirá en el cortafuegos. Todos los usuarios *tendrán* que adaptar la configuración de sus clientes de correo por lo tanto.", "migration_0003_still_on_jessie_after_main_upgrade": "Algo fue mal durante la actualización principal: ⸘el sistema está aún en Jessie‽ Para investigar el problema, vea {log}:s…", "migration_0003_system_not_fully_up_to_date": "Su sistema no está totalmente actualizado. Realice una actualización normal antes de ejecutar la migración a Stretch.", @@ -358,7 +358,7 @@ "migration_0003_start": "Iniciando migración a Stretch. El registro estará disponible en {logfile}.", "migration_description_0012_postgresql_password_to_md5_authentication": "Forzar a la autentificación de PostgreSQL a usar MD5 para las conexiones locales", "migration_description_0011_setup_group_permission": "Configurar grupo de usuario y permisos para aplicaciones y servicios", - "migration_description_0010_migrate_to_apps_json": "Eliminar las listas de aplicaciones («appslists») obsoletas y usar en su lugar la nueva lista unificada «apps.json»", + "migration_description_0010_migrate_to_apps_json": "Elimine los catálogos de aplicaciones obsoletas y use la nueva lista unificada de 'apps.json' en su lugar (desactualizada, reemplazada por la migración 13)", "migration_description_0009_decouple_regenconf_from_services": "Separar el mecanismo «regen-conf» de los servicios", "migration_description_0008_ssh_conf_managed_by_yunohost_step2": "Permitir que la configuración de SSH la gestione YunoHost (paso 2, manual)", "migration_description_0007_ssh_conf_managed_by_yunohost_step1": "Permitir que la configuración de SSH la gestione YunoHost (paso 1, automático)", @@ -422,7 +422,7 @@ "group_deleted": "Eliminado el grupo «{group}»", "group_creation_failed": "No se pudo crear el grupo «{group}»: {error}", "group_created": "Creado el grupo «{group}»", - "good_practices_about_admin_password": "Va a establecer una nueva contraseña de administración. La contraseña debería tener al menos 8 caracteres, aunque es una buena práctica usar una contraseña más extensa (básicamente una frase) y/o usar caracteres de varias clases (mayúsculas, minúsculas, números y caracteres especiales).", + "good_practices_about_admin_password": "Ahora está a punto de definir una nueva contraseña de administración. La contraseña debe tener al menos 8 caracteres, aunque es una buena práctica usar una contraseña más larga (es decir, una frase de contraseña) y / o usar una variación de caracteres (mayúsculas, minúsculas, dígitos y caracteres especiales).", "global_settings_unknown_type": "Situación imprevista, la configuración {setting:s} parece tener el tipo {unknown_type:s} pero no es un tipo compatible con el sistema.", "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Permitir el uso de la llave (obsoleta) DSA para la configuración del demonio SSH", "global_settings_unknown_setting_from_settings_file": "Clave desconocida en la configuración: «{setting_key:s}», desechada y guardada en /etc/yunohost/settings-unknown.json", @@ -447,8 +447,8 @@ "domain_dns_conf_is_just_a_recommendation": "Esta orden muestra la configuración *recomendada*. No configura el DNS en realidad. Es su responsabilidad configurar la zona de DNS en su registrador según esta recomendación.", "dpkg_lock_not_available": "Esta orden no se puede ejecutar en este momento ,parece que programa está usando el bloqueo de dpkg (el gestor de paquetes del sistema)", "dpkg_is_broken": "No puede hacer esto en este momento porque dpkg/apt (los gestores de paquetes del sistema) parecen estar en un estado roto... Puede tratar de solucionar este problema conectando a través de SSH y ejecutando `sudo dpkg --configure -a`.", - "confirm_app_install_thirdparty": "¡PELIGRO! Esta aplicación no forma parte del catálogo de aplicaciones de YunoHost. Instalar aplicaciones de terceros podría comprometer la integridad y seguridad de su sistema. Probablemente NO debería instalarla salvo que sepa lo que está haciendo. No tendrá NINGUNA AYUDA si esta aplicación no funciona o rompe su sistema… Si está dispuesto a aceptar ese riesgo de todas formas, escriba «{answers:s}»", - "confirm_app_install_danger": "¡PELIGRO! ¡Esta aplicación es conocida por ser aún experimental (o no funciona explícitamente)! Probablemente NO debería instalarla salvo que sepa lo que está haciendo. No tendrá NINGUNA AYUDA si esta aplicación no funciona o rompe su sistema… Si está dispuesto a aceptar ese riesgo de todas formas, escriba «{answers:s}»", + "confirm_app_install_thirdparty": "¡PELIGRO! Esta aplicación no forma parte del catálogo de aplicaciones de Yunohost. La instalación de aplicaciones de terceros puede comprometer la integridad y la seguridad de su sistema. Probablemente NO debería instalarlo a menos que sepa lo que está haciendo. NO se proporcionará SOPORTE si esta aplicación no funciona o rompe su sistema ... Si de todos modos está dispuesto a correr ese riesgo, escriba '{answers:s}'", + "confirm_app_install_danger": "¡PELIGRO! ¡Se sabe que esta aplicación sigue siendo experimental (si no explícitamente no funciona)! Probablemente NO debería instalarlo a menos que sepa lo que está haciendo. NO se proporcionará SOPORTE si esta aplicación no funciona o rompe su sistema ... Si de todos modos está dispuesto a correr ese riesgo, escriba '{answers:s}'", "confirm_app_install_warning": "Aviso: esta aplicación puede funcionar pero no está bien integrada en YunoHost. Algunas herramientas como la autentificación única y respaldo/restauración podrían no estar disponibles. ¿Instalar de todos modos? [{answers:s}] ", "backup_unable_to_organize_files": "No se pudo usar el método rápido de organización de los archivos en el archivo", "backup_permission": "Permiso de respaldo para la aplicación {app:s}", @@ -467,13 +467,13 @@ "app_start_backup": "Obteniendo archivos para el respaldo de «{app}»…", "app_start_remove": "Eliminando aplicación «{app}»…", "app_start_install": "Instalando aplicación «{app}»…", - "app_not_upgraded": "Error al actualizar la aplicación «{failed_app}» y como consecuencia se han cancelado las actualizaciones de las siguientes aplicaciones: {apps}", + "app_not_upgraded": "La aplicación '{failed_app}' no se pudo actualizar y, como consecuencia, se cancelaron las actualizaciones de las siguientes aplicaciones: {apps}", "app_action_cannot_be_ran_because_required_services_down": "Estos servicios necesarios deberían estar funcionando para ejecutar esta acción: {services}. Pruebe a reiniciarlos para continuar (y posiblemente investigar por qué están caídos).", "already_up_to_date": "Nada que hacer. Todo está actualizado.", "admin_password_too_long": "Elija una contraseña de menos de 127 caracteres", "aborting": "Cancelando.", "app_action_broke_system": "Esta acción parece que ha roto estos servicios importantes: {services}", - "operation_interrupted": "¿Ha sido interrumpida la operación manualmente?", + "operation_interrupted": "¿La operación fue interrumpida manualmente?", "apps_already_up_to_date": "Todas las aplicaciones están ya actualizadas", "dyndns_provider_unreachable": "No se puede conectar con el proveedor de Dyndns {provider}: o su YunoHost no está correctamente conectado a Internet o el servidor dynette está caído.", "group_already_exist": "El grupo {group} ya existe", @@ -488,7 +488,7 @@ "log_user_permission_reset": "Restablecer permiso «{}»", "migration_0011_failed_to_remove_stale_object": "No se pudo eliminar el objeto obsoleto {dn}: {error}", "permission_already_allowed": "El grupo «{group}» ya tiene el permiso «{permission}» activado", - "permission_already_disallowed": "El grupo «{group}» ya tiene el permiso «{permission}» desactivado", + "permission_already_disallowed": "El grupo '{group}' ya tiene el permiso '{permission}' deshabilitado", "permission_cannot_remove_main": "No está permitido eliminar un permiso principal", "user_already_exists": "El usuario «{user}» ya existe", "app_full_domain_unavailable": "Lamentablemente esta aplicación tiene que instalarse en un dominio propio pero ya hay otras aplicaciones instaladas en el dominio «{domain}». Podría usar un subdomino dedicado a esta aplicación en su lugar.", @@ -503,20 +503,20 @@ "permission_currently_allowed_for_all_users": "Este permiso se concede actualmente a todos los usuarios además de los otros grupos. Probablemente quiere o eliminar el permiso de «all_users» o eliminar los otros grupos a los que está otorgado actualmente.", "permission_require_account": "El permiso {permission} solo tiene sentido para usuarios con una cuenta y, por lo tanto, no se puede activar para visitantes.", "app_remove_after_failed_install": "Eliminando la aplicación tras el fallo de instalación…", - "diagnosis_basesystem_host": "El servidor está ejecutando Debian {debian_version}.", + "diagnosis_basesystem_host": "El servidor está ejecutando Debian {debian_version}", "diagnosis_basesystem_kernel": "El servidor está ejecutando el núcleo de Linux {kernel_version}", "diagnosis_basesystem_ynh_single_version": "{package} versión: {version} ({repo})", "diagnosis_basesystem_ynh_main_version": "El servidor está ejecutando YunoHost {main_version} ({repo})", - "diagnosis_basesystem_ynh_inconsistent_versions": "Está ejecutando versiones incoherentes de los paquetes de YunoHost... probablemente por una actualización errónea o parcial.", - "diagnosis_failed_for_category": "Diagnóstico fallido para la categoría «{category}» : {error}", + "diagnosis_basesystem_ynh_inconsistent_versions": "Está ejecutando versiones inconsistentes de los paquetes de YunoHost ... probablemente debido a una actualización parcial o fallida.", + "diagnosis_failed_for_category": "Error de diagnóstico para la categoría '{category}': {error}", "diagnosis_cache_still_valid": "(Caché aún válida para el diagnóstico de {category}. ¡Aún no se ha rediagnosticado!)", "diagnosis_found_errors_and_warnings": "¡Encontrado(s) error(es) significativo(s) {errors} (y aviso(s) {warnings}) relacionado(s) con {category}!", "diagnosis_display_tip_web": "Puede ir a la sección de diagnóstico (en la pantalla principal) para ver los problemas encontrados.", "diagnosis_display_tip_cli": "Puede ejecutar «yunohost diagnosis show --issues» para mostrar los problemas encontrados.", "apps_catalog_init_success": "¡Sistema de catálogo de aplicaciones inicializado!", - "apps_catalog_updating": "Actualizando catálogo de aplicaciones...", - "apps_catalog_failed_to_download": "No se pudo descargar el catálogo de aplicaciones {apps_catalog}: {error}", - "apps_catalog_obsolete_cache": "La caché del catálogo de aplicaciones está vacía u obsoleta.", + "apps_catalog_updating": "Actualizando el catálogo de aplicaciones…", + "apps_catalog_failed_to_download": "No se puede descargar el catálogo de aplicaciones {apps_catalog}: {error}", + "apps_catalog_obsolete_cache": "El caché del catálogo de aplicaciones está vacío u obsoleto.", "apps_catalog_update_success": "¡El catálogo de aplicaciones ha sido actualizado!", "diagnosis_cant_run_because_of_dep": "No se puede ejecutar el diagnóstico para {category} mientras haya problemas importantes relacionados con {dep}.", "diagnosis_ignored_issues": "(+ {nb_ignored} problema(s) ignorado(s))", @@ -545,9 +545,9 @@ "diagnosis_diskusage_ok": "¡El almacenamiento {mountpoint} (en el dispositivo {device}) todavía tiene {free} ({free_percent}%) de espacio libre!", "diagnosis_services_conf_broken": "¡Mala configuración para el servicio {service}!", "diagnosis_services_running": "¡El servicio {service} está en ejecución!", - "diagnosis_failed": "No se ha podido obtener el resultado del diagnóstico para la categoría '{category}': {error}", + "diagnosis_failed": "Error al obtener el resultado del diagnóstico para la categoría '{category}': {error}", "diagnosis_ip_connected_ipv4": "¡El servidor está conectado a internet a través de IPv4!", - "diagnosis_security_vulnerable_to_meltdown_details": "Para corregir esto, debieras actualizar y reiniciar tu sistema para cargar el nuevo kernel de Linux (o contacta tu proveedor si esto no funciona). Mas información en https://meltdownattack.com/", + "diagnosis_security_vulnerable_to_meltdown_details": "Para corregir esto, debieras actualizar y reiniciar tu sistema para cargar el nuevo kernel de Linux (o contacta tu proveedor si esto no funciona). Mas información en https://meltdownattack.com/ .", "diagnosis_ram_verylow": "Al sistema le queda solamente {available} ({available_percent}%) de RAM! (De un total de {total})", "diagnosis_ram_low": "Al sistema le queda {available} ({available_percent}%) de RAM de un total de {total}. Cuidado.", "diagnosis_ram_ok": "El sistema aun tiene {available} ({available_percent}%) de RAM de un total de {total}.", @@ -561,7 +561,7 @@ "diagnosis_regenconf_manually_modified_debian": "El archivos de configuración {file} fue modificado manualmente comparado con el valor predeterminado de Debian.", "diagnosis_regenconf_manually_modified_debian_details": "Esto este probablemente BIEN, pero igual no lo pierdas de vista...", "diagnosis_security_all_good": "Ninguna vulnerabilidad critica de seguridad fue encontrada.", - "diagnosis_security_vulnerable_to_meltdown": "Pareces vulnerable a el colapso de vulnerabilidad critica de seguridad.", + "diagnosis_security_vulnerable_to_meltdown": "Pareces vulnerable a el colapso de vulnerabilidad critica de seguridad", "diagnosis_description_basesystem": "Sistema de base", "diagnosis_description_ip": "Conectividad a Internet", "diagnosis_description_dnsrecords": "Registro DNS", @@ -588,15 +588,18 @@ "log_app_action_run": "Inicializa la acción de la aplicación '{}'", "group_already_exist_on_system_but_removing_it": "El grupo {group} ya existe en el grupo de sistema, pero YunoHost lo suprimirá …", "global_settings_setting_pop3_enabled": "Habilita el protocolo POP3 para el servidor de correo electrónico", - "domain_cannot_remove_main_add_new_one": "No se puede remover '{domain:s}' porque es su principal y único dominio. Primero debe agregar un nuevo dominio con la linea de comando 'yunohost domain add ', entonces configurarlo como dominio principal con 'yunohost domain main-domain -n ' y finalmente borrar el dominio '{domain:s}' con 'yunohost domain remove {domain:s}'.", + "domain_cannot_remove_main_add_new_one": "No se puede remover '{domain:s}' porque es su principal y único dominio. Primero debe agregar un nuevo dominio con la linea de comando 'yunohost domain add ', entonces configurarlo como dominio principal con 'yunohost domain main-domain -n ' y finalmente borrar el dominio '{domain:s}' con 'yunohost domain remove {domain:s}'.'", "diagnosis_never_ran_yet": "Este servidor todavía no tiene reportes de diagnostico. Puede iniciar un diagnostico completo desde la interface administrador web o con la linea de comando 'yunohost diagnosis run'.", "diagnosis_unknown_categories": "Las siguientes categorías están desconocidas: {categories}", "diagnosis_http_unreachable": "El dominio {domain} esta fuera de alcance desde internet y a través de HTTP.", "diagnosis_http_bad_status_code": "El sistema de diagnostico no pudo comunicarse con su servidor. Puede ser otra maquina que contesto en lugar del servidor. Debería verificar en su firewall que el re-direccionamiento del puerto 80 esta correcto.", - "diagnosis_http_connection_error": "Error de conexión: Ne se pudo conectar al dominio solicitado,", + "diagnosis_http_connection_error": "Error de conexión: Ne se pudo conectar al dominio solicitado.", "diagnosis_http_timeout": "El intento de contactar a su servidor desde internet corrió fuera de tiempo. Al parece esta incomunicado. Debería verificar que nginx corre en el puerto 80, y que la redireción del puerto 80 no interfiere con en el firewall.", "diagnosis_http_ok": "El Dominio {domain} es accesible desde internet a través de HTTP.", "diagnosis_http_could_not_diagnose": "No se pudo verificar si el dominio es accesible desde internet.", "diagnosis_http_could_not_diagnose_details": "Error: {error}", - "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" + "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." } From 48329bee03211ef58ceed115e49bec0fe0b646dd Mon Sep 17 00:00:00 2001 From: amirale qt Date: Mon, 20 Apr 2020 07:59:36 +0000 Subject: [PATCH 1107/3170] Translated using Weblate (French) Currently translated at 100.0% (598 of 598 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 53 ++++++++++++++++++++----------------------------- 1 file changed, 22 insertions(+), 31 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index ee6aca0a8..7b31f9237 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -41,7 +41,7 @@ "backup_hook_unknown": "Script de sauvegarde '{hook:s}' inconnu", "backup_invalid_archive": "Archive de sauvegarde invalide", "backup_nothings_done": "Il n’y a rien à sauvegarder", - "backup_output_directory_forbidden": "Dossier de destination interdit. Les sauvegardes ne peuvent être créées dans les sous-dossiers /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var ou /home/yunohost.backup/archives", + "backup_output_directory_forbidden": "Choisissez un répertoire de sortie différent. Les sauvegardes ne peuvent pas être créées dans les sous-dossiers /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var ou /home/yunohost.backup/archives", "backup_output_directory_not_empty": "Le répertoire de destination n’est pas vide", "backup_output_directory_required": "Vous devez spécifier un dossier de destination pour la sauvegarde", "backup_running_hooks": "Exécution des scripts de sauvegarde …", @@ -75,7 +75,7 @@ "field_invalid": "Champ incorrect : '{:s}'", "firewall_reload_failed": "Impossible de recharger le pare-feu", "firewall_reloaded": "Pare-feu rechargé", - "firewall_rules_cmd_failed": "Certaines règles du pare-feu n’ont pas pu être appliquées. Plus d’info dans le journal de log.", + "firewall_rules_cmd_failed": "Certaines commandes de règles de pare-feu ont échoué. Plus d'informations dans le journal.", "hook_exec_failed": "Échec de l’exécution du script : {path:s}", "hook_exec_not_terminated": "L’exécution du script {path:s} ne s’est pas terminée correctement", "hook_list_by_invalid": "Propriété invalide pour lister les actions par celle-ci", @@ -112,7 +112,7 @@ "restore_complete": "Restauré", "restore_confirm_yunohost_installed": "Voulez-vous vraiment restaurer un système déjà installé ? [{answers:s}]", "restore_failed": "Impossible de restaurer le système", - "restore_hook_unavailable": "Le script de restauration '{part:s}' n’est pas disponible sur votre système, et ne l’est pas non plus dans l’archive", + "restore_hook_unavailable": "Script de restauration pour '{part:s}' non disponible sur votre système et non plus dans l'archive", "restore_nothings_done": "Rien n’a été restauré", "restore_running_app_script": "Exécution du script de restauration de l’application '{app:s}' …", "restore_running_hooks": "Exécution des scripts de restauration …", @@ -168,7 +168,7 @@ "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_error_no_A_record": "Aucun enregistrement DNS 'A' n’a été trouvé pour {domain:s}. Vous devez faire pointer votre nom de domaine vers votre machine pour être en mesure d’installer un certificat Let’s Encrypt ! (Si vous savez ce que vous faites, utilisez --no-checks pour désactiver ces contrôles)", - "certmanager_domain_dns_ip_differs_from_public_ip": "L’enregistrement DNS 'A' du domaine {domain:s} est différent de l’adresse IP de ce serveur. Si vous avez récemment modifié votre enregistrement 'A', veuillez attendre sa propagation (quelques vé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_domain_dns_ip_differs_from_public_ip": "L’enregistrement DNS 'A' du domaine {domain:s} est différent de l’adresse IP de ce serveur. Si vous avez récemment modifié votre enregistrement 'A', veuillez attendre sa propagation (quelques vérificateur de propagation DNS sont disponibles en ligne). (Si vous savez ce que vous faites, utilisez --no-checks pour désactiver ces contrôles)", "certmanager_cannot_read_cert": "Quelque chose s’est mal passé lors de la tentative d’ouverture du certificat actuel pour le domaine {domain:s} (fichier : {file:s}), la cause est : {reason:s}", "certmanager_cert_install_success_selfsigned": "Le certificat auto-signé est maintenant installé pour le domaine « {domain:s} »", "certmanager_cert_install_success": "Le certificat Let’s Encrypt est maintenant installé pour le domaine « {domain:s} »", @@ -235,7 +235,7 @@ "global_settings_cant_serialize_settings": "Échec de la sérialisation des données de paramétrage car : {reason:s}", "restore_removing_tmp_dir_failed": "Impossible de sauvegarder un ancien dossier temporaire", "restore_extracting": "Extraction des fichiers nécessaires depuis l’archive …", - "restore_may_be_not_enough_disk_space": "Votre système semble ne pas avoir suffisamment d’espace disponible (L’espace libre est de {free_space:d} octets. Le besoin d’espace nécessaire est de {needed_space:d} octets. En appliquant une marge de sécurité, la quantité d’espace nécessaire est de {margin:d} octets)", + "restore_may_be_not_enough_disk_space": "Votre système ne semble pas avoir suffisamment d'espace (libre: {free_space:d} B, espace nécessaire: {needed_space:d} B, marge de sécurité: {margin:d} B)", "restore_not_enough_disk_space": "Espace disponible insuffisant (L’espace libre est de {free_space:d} octets. Le besoin d’espace nécessaire est de {needed_space:d} octets. En appliquant une marge de sécurité, la quantité d’espace nécessaire est de {margin:d} octets)", "restore_system_part_failed": "Impossible de restaurer la partie '{part:s}' du système", "backup_couldnt_bind": "Impossible de lier {src:s} avec {dest:s}.", @@ -270,8 +270,8 @@ "migration_0003_patching_sources_list": "Modification du fichier sources.lists …", "migration_0003_main_upgrade": "Démarrage de la mise à niveau principale …", "migration_0003_fail2ban_upgrade": "Démarrage de la mise à niveau de fail2ban …", - "migration_0003_restoring_origin_nginx_conf": "Votre fichier /etc/nginx/nginx.conf a été modifié d’une manière ou d’une autre. La migration va d’abord le réinitialiser à son état initial. Le fichier précédent sera disponible en tant que {backup_dest}.", - "migration_0003_yunohost_upgrade": "Démarrage de la mise à niveau du paquet YunoHost. La migration se terminera, mais la mise à jour réelle aura lieu immédiatement après. Une fois cette opération terminée, vous pourriez avoir à vous reconnecter à l’administration via le panel web.", + "migration_0003_restoring_origin_nginx_conf": "Votre fichier /etc/nginx/nginx.conf a été modifié d'une manière ou d'une autre. La migration va d'abord le réinitialiser à son état d'origine… Le fichier précédent sera disponible en tant que {backup_dest}.", + "migration_0003_yunohost_upgrade": "Démarrage de la mise à niveau du package YunoHost… La migration se terminera, mais la mise à niveau réelle aura lieu immédiatement après. Une fois l'opération terminée, vous devrez peut-être vous reconnecter à la page webadmin.", "migration_0003_not_jessie": "La distribution Debian actuelle n’est pas Jessie !", "migration_0003_system_not_fully_up_to_date": "Votre système n’est pas complètement à jour. Veuillez mener une mise à jour classique avant de lancer la migration à Stretch.", "migration_0003_still_on_jessie_after_main_upgrade": "Quelque chose s’est mal passé pendant la mise à niveau principale : le système est toujours sur Debian Jessie !? Pour investiguer sur le problème, veuillez regarder les journaux {log}:s …", @@ -304,7 +304,7 @@ "log_link_to_failed_log": "L’opération '{desc}' a échoué ! Pour obtenir de l’aide, merci de partager le journal de l’opération en cliquant ici", "backup_php5_to_php7_migration_may_fail": "Impossible de convertir votre archive pour prendre en charge PHP 7, vous pourriez ne plus pouvoir restaurer vos applications PHP (cause : {error:s})", "log_help_to_get_failed_log": "L’opération '{desc}' a échoué ! Pour obtenir de l’aide, merci de partager le journal de l’opération en utilisant la commande 'yunohost log display {name} --share'", - "log_does_exists": "Il n’existe pas de journal de l’opération ayant pour nom '{log}', utiliser 'yunohost log list' pour voir tous les fichiers de journaux disponibles", + "log_does_exists": "Il n'y a pas de journal des opérations avec le nom '{log}', utilisez 'yunohost log list' pour voir tous les journaux d'opérations disponibles", "log_operation_unit_unclosed_properly": "L’opération ne s’est pas terminée correctement", "log_app_change_url": "Changer l’URL de l’application '{}'", "log_app_install": "Installer l’application '{}'", @@ -321,12 +321,12 @@ "log_dyndns_subscribe": "Souscrire au sous-domaine YunoHost '{}'", "log_dyndns_update": "Mettre à jour l’adresse IP associée à votre sous-domaine YunoHost '{}'", "log_letsencrypt_cert_install": "Installer le certificat Let’s Encrypt sur le domaine '{}'", - "log_selfsigned_cert_install": "Installer le certificat auto-signé sur le domaine '{}'", + "log_selfsigned_cert_install": "Installer un certificat auto-signé sur le domaine '{}'", "log_letsencrypt_cert_renew": "Renouveler le certificat Let’s Encrypt de '{}'", "log_user_create": "Ajouter l’utilisateur '{}'", "log_user_delete": "Supprimer l’utilisateur '{}'", "log_user_update": "Mettre à jour les informations de l’utilisateur '{}'", - "log_domain_main_domain": "Faire de '{}' le domaine principal", + "log_domain_main_domain": "Faites de '{}' le domaine principal", "log_tools_migrations_migrate_forward": "Éxecuter les migrations", "log_tools_postinstall": "Faire la post-installation de votre serveur YunoHost", "log_tools_upgrade": "Mettre à jour les paquets du système", @@ -336,15 +336,15 @@ "migration_description_0004_php5_to_php7_pools": "Reconfigurer les espaces utilisateurs PHP pour utiliser PHP 7 au lieu de PHP 5", "migration_description_0005_postgresql_9p4_to_9p6": "Migration des bases de données de PostgreSQL 9.4 vers PostgreSQL 9.6", "migration_0005_postgresql_94_not_installed": "PostgreSQL n’a pas été installé sur votre système. Rien à faire !", - "migration_0005_postgresql_96_not_installed": "PostgreSQL 9.4 a été trouvé et installé, mais pas PostgreSQL 9.6 !? Quelque chose d’étrange a dû arriver à votre système… :(", + "migration_0005_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: (…", "migration_0005_not_enough_space": "Laissez suffisamment d’espace disponible dans {path} pour exécuter la migration.", "service_description_php7.0-fpm": "Exécute des applications écrites en PHP avec NGINX", "users_available": "Liste des utilisateurs disponibles :", - "good_practices_about_admin_password": "Vous êtes maintenant sur le point de définir un nouveau mot de passe d’administration. Le mot de passe doit comporter au moins 8 caractères – bien qu’il soit recommandé d’utiliser un mot de passe plus long (c’est-à-dire une phrase secrète) et/ou d’utiliser différents types de caractères (majuscules, minuscules, chiffres et caractères spéciaux).", - "good_practices_about_user_password": "Vous êtes maintenant sur le point de définir un nouveau mot de passe utilisateur. Le mot de passe doit comporter au moins 8 caractères - bien qu’il soit recommandé d’utiliser un mot de passe plus long (c’est-à-dire une phrase secrète) et/ou d’utiliser différents types de caractères tels que : majuscules, minuscules, chiffres et caractères spéciaux.", + "good_practices_about_admin_password": "Vous êtes maintenant sur le point de définir un nouveau mot de passe d'administration. Le mot de passe doit comporter au moins 8 caractères, bien qu'il soit recommandé d'utiliser un mot de passe plus long (c'est-à-dire une phrase de passe) et / ou d'utiliser une variation de caractères (majuscule, minuscule, chiffres et caractères spéciaux).", + "good_practices_about_user_password": "Vous êtes maintenant sur le point de définir un nouveau mot de passe utilisateur. Le mot de passe doit comporter au moins 8 caractères, bien qu'il soit recommandé d'utiliser un mot de passe plus long (c'est-à-dire une phrase secrète) et / ou une variation de caractères (majuscule, minuscule, chiffres et caractères spéciaux).", "migration_description_0006_sync_admin_and_root_passwords": "Synchroniser les mots de passe admin et root", "migration_0006_disclaimer": "YunoHost s’attend maintenant à ce que les mots de passe administrateur et racine soient synchronisés. Cette migration remplace votre mot de passe root par le mot de passe administrateur.", - "password_listed": "Ce mot de passe est l’un des mots de passe les plus utilisés dans le monde. Veuillez choisir quelque chose d’un peu plus singulier.", + "password_listed": "Ce mot de passe fait partie des mots de passe les plus utilisés au monde. Veuillez choisir quelque chose de plus unique.", "password_too_simple_1": "Le mot de passe doit comporter au moins 8 caractères", "password_too_simple_2": "Le mot de passe doit comporter au moins 8 caractères et contenir des chiffres, des majuscules et des minuscules", "password_too_simple_3": "Le mot de passe doit comporter au moins 8 caractères et contenir des chiffres, des majuscules, des minuscules et des caractères spéciaux", @@ -363,7 +363,7 @@ "backup_mount_archive_for_restore": "Préparation de l’archive pour restauration …", "confirm_app_install_warning": "Avertissement : cette application peut fonctionner mais n’est pas bien intégrée dans YunoHost. Certaines fonctionnalités telles que l’authentification unique et la sauvegarde/restauration peuvent ne pas être disponibles. L’installer quand même ? [{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}'", + "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'.", "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.", @@ -380,7 +380,7 @@ "migration_0008_root": "- Vous ne pourrez pas vous connecter en tant que root via SSH. Au lieu de cela, vous devrez utiliser l’utilisateur admin ;", "migration_0008_dsa": "- La clé DSA sera désactivée. Par conséquent, il se peut que vous ayez besoin d’invalider un avertissement effrayant de votre client SSH afin de revérifier l’empreinte de votre serveur ;", "migration_0008_warning": "Si vous comprenez ces avertissements et souhaitez que YunoHost écrase votre configuration actuelle, exécutez la migration. Sinon, vous pouvez également ignorer la migration, bien que cela ne soit pas recommandé.", - "migration_0008_no_warning": "Remplacer votre configuration SSH devrait être sûr, bien que cela ne puisse être promis ! Exécutez la migration pour la remplacer. Sinon, vous pouvez également ignorer la migration, bien que cela ne soit pas recommandé.", + "migration_0008_no_warning": "Remplacer votre configuration SSH devrait être sûr, bien que cela ne puisse pas être promis! Exécutez la migration pour la remplacer. Sinon, vous pouvez également ignorer la migration, bien que cela ne soit pas recommandé.", "migrations_success": "Migration {number} {name} réussie !", "pattern_password_app": "Désolé, les mots de passe ne peuvent pas contenir les caractères suivants : {forbidden_chars}", "root_password_replaced_by_admin_password": "Votre mot de passe root a été remplacé par votre mot de passe administrateur.", @@ -424,9 +424,9 @@ "tools_upgrade_regular_packages_failed": "Impossible de mettre à jour les paquets suivants : {packages_list}", "tools_upgrade_special_packages": "Mise à jour des paquets 'spécifiques' (liés a YunoHost) …", "tools_upgrade_special_packages_completed": "La mise à jour des paquets de YunoHost est finie !\nPressez [Entrée] pour revenir à la ligne de commande", - "dpkg_lock_not_available": "Cette commande ne peut être exécutée actuellement car un autre programme semble utiliser le verrou de dpkg (gestionnaire de paquets)", + "dpkg_lock_not_available": "Cette commande ne peut pas être exécutée pour le moment car un autre programme semble utiliser le verrou de dpkg (le gestionnaire de package système)", "tools_upgrade_cant_unhold_critical_packages": "Impossible de conserver les paquets critiques…", - "tools_upgrade_special_packages_explanation": "La mise à jour spéciale va continuer en arrière-plan. Veuillez ne pas lancer d’autres actions sur votre serveur pendant environ 10 minutes (en fonction de la vitesse du matériel). Après cela, il vous faudra peut-être vous reconnecter à la webadmin. Le journal de mise à niveau sera disponible dans Outils → Journal (dans la webadmin) ou via \"yunohost log list\" (en ligne de commande).", + "tools_upgrade_special_packages_explanation": "La mise à niveau spéciale se poursuivra en arrière-plan. Veuillez ne pas lancer d'autres actions sur votre serveur pendant les 10 prochaines minutes (selon la vitesse du matériel). Après cela, vous devrez peut-être vous reconnecter à l'administrateur Web. Le journal de mise à niveau sera disponible dans Outils → Journal (dans le webadmin) ou en utilisant la «liste des journaux yunohost» (à partir de la ligne de commande).", "update_apt_cache_failed": "Impossible de mettre à jour le cache APT (gestionnaire de paquets Debian). Voici un extrait du fichier sources.list qui pourrait vous aider à identifier les lignes problématiques :\n{sourceslist}", "update_apt_cache_warning": "Des erreurs se sont produites lors de la mise à jour du cache APT (gestionnaire de paquets Debian). Voici un extrait des lignes du fichier sources.list qui pourrait vous aider à identifier les lignes problématiques :\n{sourceslist}", "backup_permission": "Permission de sauvegarde pour l’application {app:s}", @@ -457,7 +457,7 @@ "migration_0011_update_LDAP_database": "Mise à jour de la base de données LDAP…", "migration_0011_backup_before_migration": "Création d’une sauvegarde des paramètres de la base de données LDAP et des applications avant la migration.", "permission_not_found": "Autorisation '{permission:s}' introuvable", - "permission_update_failed": "Impossible de mettre à jour la permission '{permission}' : {error}", + "permission_update_failed": "Impossible de mettre à jour l'autorisation '{permission}': {error}", "permission_updated": "Permission '{permission:s}' mise à jour", "permission_update_nothing_to_do": "Aucune autorisation pour mettre à jour", "dyndns_provider_unreachable": "Impossible d’atteindre le fournisseur DynDNS {provider}: votre YunoHost n’est pas correctement connecté à Internet ou le serveur Dynette est en panne.", @@ -518,14 +518,14 @@ "diagnosis_basesystem_kernel": "Le serveur utilise le noyau Linux {kernel_version}", "diagnosis_basesystem_ynh_single_version": "{package} version: {version} ({repo})", "diagnosis_basesystem_ynh_main_version": "Le serveur utilise YunoHost {main_version} ({repo})", - "diagnosis_basesystem_ynh_inconsistent_versions": "Vous exécutez des versions incohérentes des packages YunoHost … probablement à cause d’une mise à niveau partielle ou échouée.", + "diagnosis_basesystem_ynh_inconsistent_versions": "Vous exécutez des versions incohérentes des packages YunoHost ... très probablement en raison d'une mise à niveau échouée ou partielle.", "diagnosis_display_tip_cli": "Vous pouvez exécuter 'yunohost diagnosis show --issues' pour afficher les problèmes détectés.", "diagnosis_failed_for_category": "Échec du diagnostic pour la catégorie '{category}': {error}", "diagnosis_cache_still_valid": "(Le cache est encore valide pour le diagnostic {category}. Il ne sera pas re-diagnostiqué pour le moment!)", "diagnosis_ignored_issues": "(+ {nb_ignored} questions ignorée(s))", "diagnosis_found_warnings": "Trouvé {warnings} objet(s) pouvant être amélioré(s) pour {category}.", "diagnosis_everything_ok": "Tout semble bien pour {category} !", - "diagnosis_failed": "Impossible d’extraire le résultat du diagnostic pour la catégorie '{category}': {error}", + "diagnosis_failed": "Échec de la récupération du résultat du diagnostic pour la catégorie '{category}': {error}", "diagnosis_ip_connected_ipv4": "Le serveur est connecté à Internet en IPv4 !", "diagnosis_ip_no_ipv4": "Le serveur ne dispose pas d’une adresse IPv4.", "diagnosis_ip_connected_ipv6": "Le serveur est connecté à Internet en IPv6 !", @@ -535,15 +535,6 @@ "diagnosis_ip_broken_resolvconf": "La résolution du nom de domaine semble cassée sur votre serveur, ce qui semble lié au fait que /etc/resolv.conf ne pointe pas vers 127.0.0.1.", "diagnosis_dns_good_conf": "Bonne configuration DNS pour le domaine {domain} (catégorie {category})", "diagnosis_dns_bad_conf": "Configuration DNS incorrecte ou manquante pour le domaine {domain} (catégorie {category})", - "diagnosis_dns_discrepancy": "L’enregistrement DNS de type {0} et nom {1} ne correspond pas à la configuration recommandée. Valeur actuelle: {2}. Valeur exceptée: {3}. Vous pouvez consulter https://yunohost.org/dns_config pour plus d’informations.", - "diagnosis_services_bad_status": "Le service {service} est {status} :-(", - "diagnosis_diskusage_verylow": "Le stockage {mountpoint} (sur le périphérique {device}) ne dispose que de {free_abs_GB} Go ({free_percent}%). Vous devriez vraiment envisager de nettoyer un peu d’espace.", - "diagnosis_diskusage_low": "Le stockage {mountpoint} (sur le périphérique {device}) ne dispose que de {free_abs_GB} Go ({free_percent}%). Faites attention.", - "diagnosis_ram_verylow": "Le système ne dispose plus que de {available_abs_MB} MB ({available_percent}%) ! (sur {total_abs_MB} Mo)", - "diagnosis_ram_low": "Le système n’a plus de {available_abs_MB} MB ({available_percent}%) RAM sur {total_abs_MB} MB. Faites attention.", - "diagnosis_swap_none": "Le système n’a aucun échange. Vous devez envisager d’ajouter au moins 256 Mo de swap pour éviter les situations où le système manque de mémoire.", - "diagnosis_swap_notsomuch": "Le système ne dispose que de {total_MB} Mo de swap. Vous devez envisager d’avoir au moins 256 Mo pour éviter les situations où le système manque de mémoire.", - "diagnosis_swap_ok": "Le système dispose de {total_MB} Mo de swap !", "diagnosis_dns_discrepancy": "L’enregistrement DNS de type {type} et nom {name} ne correspond pas à la configuration recommandée.\nValeur actuelle: {current}\nValeur attendue: {value}", "diagnosis_services_bad_status": "Le service {service} est {status} :-(", "diagnosis_diskusage_verylow": "Le stockage {mountpoint} (sur le périphérique {device}) ne dispose que de {free} ({free_percent}%). Vous devriez vraiment envisager de nettoyer un peu d’espace.", @@ -590,7 +581,7 @@ "diagnosis_services_running": "Le service {service} est en cours de fonctionnement !", "diagnosis_services_conf_broken": "La configuration est cassée pour le service {service} !", "diagnosis_ports_needed_by": "Rendre ce port accessible est nécessaire pour les fonctionnalités de type {category} (service {service})", - "diagnosis_ports_forwarding_tip": "Pour résoudre ce problème, vous devez probablement configurer la redirection de port sur votre routeur Internet comme décrit sur https://yunohost.org/isp_box_config", + "diagnosis_ports_forwarding_tip": "Pour résoudre ce problème, vous devez probablement configurer la redirection de port sur votre routeur Internet comme décrit dans https://yunohost.org/isp_box_config", "diagnosis_http_connection_error": "Erreur de connexion : impossible de se connecter au domaine demandé, il est probablement injoignable.", "diagnosis_no_cache": "Pas encore de cache de diagnostique pour la catégorie « {category} »", "yunohost_postinstall_end_tip": "La post-installation terminée! Pour finaliser votre configuration, il est recommendé de :\n - ajouter un premier utilisateur depuis la section \"Utilisateurs\" de l’interface web (ou \"yunohost user create \" en ligne de commande);\n - diagnostiquer les potentiels problèmes dans la section \"Diagnostic\" de l'interface web (ou \"yunohost diagnosis run\" en ligne de commande);\n - lire les parties \"Finalisation de votre configuration\" et \"Découverte de Yunohost\" dans le guide de l’administrateur: https://yunohost.org/admindoc.", From b1dda63385d0bb826adf80b268be91c61a89e1eb Mon Sep 17 00:00:00 2001 From: amirale qt Date: Mon, 20 Apr 2020 09:44:28 +0000 Subject: [PATCH 1108/3170] Translated using Weblate (Greek) Currently translated at 0.2% (1 of 598 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/el/ --- locales/el.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/el.json b/locales/el.json index efa5bf769..b43f11d5d 100644 --- a/locales/el.json +++ b/locales/el.json @@ -1,3 +1,3 @@ { - "password_too_simple_1": "Ο κωδικός πρόσβασης πρέπει να έχει μήκος τουλάχιστον 8 χαρακτήρων" -} \ No newline at end of file + "password_too_simple_1": "Ο κωδικός πρόσβασης πρέπει να έχει τουλάχιστον 8 χαρακτήρες" +} From e35d87f7ed4fe58cfaae0e66f01581deac849749 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Mon, 20 Apr 2020 09:11:59 +0000 Subject: [PATCH 1109/3170] Translated using Weblate (Nepali) Currently translated at 0.2% (1 of 598 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ne/ --- locales/ne.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/locales/ne.json b/locales/ne.json index 9e26dfeeb..72c4c8537 100644 --- a/locales/ne.json +++ b/locales/ne.json @@ -1 +1,3 @@ -{} \ No newline at end of file +{ + "password_too_simple_1": "पासवर्ड कम्तिमा characters अक्षर लामो हुनु आवश्यक छ" +} From c95c08ef8f792175a02fc226cd38ad1b9a649401 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Tue, 21 Apr 2020 06:16:56 +0000 Subject: [PATCH 1110/3170] Translated using Weblate (Esperanto) Currently translated at 100.0% (598 of 598 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/eo/ --- locales/eo.json | 75 +++++++++++++++++++++++++++---------------------- 1 file changed, 41 insertions(+), 34 deletions(-) diff --git a/locales/eo.json b/locales/eo.json index eb701494d..22656188d 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -70,7 +70,7 @@ "backup_cant_mount_uncompress_archive": "Ne povis munti la nekompresitan ar archiveivon kiel protektita kontraŭ skribo", "app_action_cannot_be_ran_because_required_services_down": "Ĉi tiuj postulataj servoj devas funkcii por funkciigi ĉi tiun agon: {services}. Provu rekomenci ilin por daŭrigi (kaj eble esploru, kial ili malsupreniras).", "backup_copying_to_organize_the_archive": "Kopiante {size:s} MB por organizi la ar archiveivon", - "backup_output_directory_forbidden": "Elektu malsaman elirejan dosierujon. Sekurkopioj ne povas esti kreitaj en sub-dosierujoj / bin, / boot, / dev, / ktp, / lib, / root, / run, / sbin, / sys, / usr, / var aŭ /home/yunohost.backup/archives", + "backup_output_directory_forbidden": "Elektu malsaman elirejan dosierujon. Sekurkopioj ne povas esti kreitaj en sub-dosierujoj /bin, /boot, /dev, /ktp, /lib, /root, /run, /sbin, /sys, /usr, /var aŭ /home/yunohost.backup/archives", "backup_no_uncompress_archive_dir": "Ne ekzistas tia nekompremita arkiva dosierujo", "password_too_simple_1": "Pasvorto devas esti almenaŭ 8 signojn longa", "app_upgrade_failed": "Ne povis ĝisdatigi {app:s}: {error}", @@ -107,13 +107,13 @@ "app_sources_fetch_failed": "Ne povis akiri fontajn dosierojn, ĉu la URL estas ĝusta?", "ask_new_domain": "Nova domajno", "app_unknown": "Nekonata apliko", - "app_not_upgraded": "La aplikaĵo '{failed_app}' ne ĝisdatigis, kaj pro tio la sekvaj ĝisdatigoj de aplikoj estis nuligitaj: {apps}", + "app_not_upgraded": "La '{failed_app}' de la programo ne sukcesis ĝisdatigi, kaj sekve la nuntempaj plibonigoj de la sekvaj programoj estis nuligitaj: {apps}", "aborting": "Aborti.", "app_upgraded": "{app:s} altgradigita", "backup_deleted": "Rezerva forigita", "backup_csv_addition_failed": "Ne povis aldoni dosierojn al sekurkopio en la CSV-dosiero", "dpkg_lock_not_available": "Ĉi tiu komando ne povas funkcii nun ĉar alia programo uzas la seruron de dpkg (la administrilo de paka sistemo)", - "migration_0003_yunohost_upgrade": "Komenci la ĝisdatigon de YunoHost-pako ... La migrado finiĝos, sed la efektiva ĝisdatigo okazos tuj poste. Post kiam la operacio finiĝos, vi eble devos ensaluti denove sur la retpaĝo.", + "migration_0003_yunohost_upgrade": "Komencante la ĝisdatigon de la pakaĵo YunoHost ... La migrado finiĝos, sed la efektiva ĝisdatigo okazos tuj poste. Post kiam la operacio finiĝos, vi eble devos ensaluti al la retpaĝo.", "domain_dyndns_root_unknown": "Nekonata radika domajno DynDNS", "field_invalid": "Nevalida kampo '{:s}'", "log_app_makedefault": "Faru '{}' la defaŭlta apliko", @@ -124,7 +124,7 @@ "global_settings_setting_security_postfix_compatibility": "Kongruo vs sekureca kompromiso por la Postfix-servilo. Afektas la ĉifradojn (kaj aliajn aspektojn pri sekureco)", "group_unknown": "La grupo '{group:s}' estas nekonata", "mailbox_disabled": "Retpoŝto malŝaltita por uzanto {user:s}", - "migration_description_0011_setup_group_permission": "Agordu uzantogrupon kaj starigu permeson por programoj kaj servoj", + "migration_description_0011_setup_group_permission": "Agordu uzantajn grupojn kaj permesojn por programoj kaj servoj", "migration_0011_backup_before_migration": "Krei sekurkopion de LDAP-datumbazo kaj agordojn antaŭ la efektiva migrado.", "migration_0011_migrate_permission": "Migrado de permesoj de agordoj al aplikoj al LDAP…", "migration_0011_migration_failed_trying_to_rollback": "Ne povis migri ... provante redakti la sistemon.", @@ -148,8 +148,8 @@ "log_user_group_delete": "Forigi grupon '{}'", "log_user_group_update": "Ĝisdatigi grupon '{}'", "migration_0005_postgresql_94_not_installed": "PostgreSQL ne estis instalita en via sistemo. Nenio por fari.", - "dyndns_provider_unreachable": "Ne povas atingi Dyndns-provizanton {provider}: ĉu via YunoHost ne estas ĝuste konektita al la interreto aŭ la dynette-servilo malŝaltiĝas.", - "good_practices_about_user_password": "Vi nun estas por difini novan uzantan pasvorton. La pasvorto devas esti almenaŭ 8 signoj - kvankam estas bone praktiki uzi pli longan pasvorton (t.e. pasfrazon) kaj / aŭ variaĵon de signoj (majuskloj, minuskloj, ciferoj kaj specialaj signoj).", + "dyndns_provider_unreachable": "Ne povas atingi la provizanton DynDNS {provider}: ĉu via YunoHost ne estas ĝuste konektita al la interreto aŭ la dyneta servilo malŝaltiĝas.", + "good_practices_about_user_password": "Vi nun estas por difini novan uzantan pasvorton. La pasvorto devas esti almenaŭ 8 signojn - kvankam estas bone praktiki uzi pli longan pasvorton (t.e. pasfrazon) kaj/aŭ variaĵon de signoj (majuskloj, minuskloj, ciferoj kaj specialaj signoj).", "group_updated": "Ĝisdatigita \"{group}\" grupo", "group_already_exist": "Grupo {group} jam ekzistas", "group_already_exist_on_system": "Grupo {group} jam ekzistas en la sistemaj grupoj", @@ -172,7 +172,7 @@ "migrations_already_ran": "Tiuj migradoj estas jam faritaj: {ids}", "migrations_no_such_migration": "Estas neniu migrado nomata '{id}'", "permission_already_allowed": "Grupo '{group}' jam havas rajtigitan permeson '{permission}'", - "permission_already_disallowed": "Grupo '{group}' jam havas permeson '{permission}' malebligita'", + "permission_already_disallowed": "Grupo '{group}' jam havas permeson '{permission}' malebligita", "permission_cannot_remove_main": "Forigo de ĉefa permeso ne rajtas", "permission_creation_failed": "Ne povis krei permeson '{permission}': {error}", "user_already_exists": "La uzanto '{user}' jam ekzistas", @@ -186,7 +186,7 @@ "permission_not_found": "Permesita \"{permission:s}\" ne trovita", "restore_not_enough_disk_space": "Ne sufiĉa spaco (spaco: {free_space:d} B, necesa spaco: {needed_space:d} B, sekureca marĝeno: {margin:d} B)", "tools_upgrade_regular_packages": "Nun ĝisdatigi 'regulajn' (ne-yunohost-rilatajn) pakojn …", - "tools_upgrade_special_packages_explanation": "La speciala ĝisdatigo daŭros en fono. Bonvolu ne komenci aliajn agojn en via servilo la sekvajn ~ 10 minutojn (depende de la aparata rapideco). Post tio, vi eble devos re-ensaluti sur la retadreso. La ĝisdatiga registro estos havebla en Iloj → Ensaluto (en la retadreso) aŭ uzante 'yunohost-logliston' (el la komandlinio).", + "tools_upgrade_special_packages_explanation": "La speciala ĝisdatigo daŭros en la fono. Bonvolu ne komenci aliajn agojn en via servilo dum la sekvaj ~ 10 minutoj (depende de la aparata rapideco). Post tio, vi eble devos re-ensaluti al la retadreso. La ĝisdatiga registro estos havebla en Iloj → Ensaluto (en la retadreso) aŭ uzante 'yunohost logliston' (el la komandlinio).", "unrestore_app": "App '{app:s}' ne restarigos", "group_created": "Grupo '{group}' kreita", "group_creation_failed": "Ne povis krei la grupon '{group}': {error}", @@ -199,7 +199,7 @@ "log_user_create": "Aldonu uzanton '{}'", "ip6tables_unavailable": "Vi ne povas ludi kun ip6tabloj ĉi tie. Vi estas en ujo aŭ via kerno ne subtenas ĝin", "mail_unavailable": "Ĉi tiu retpoŝta adreso estas rezervita kaj aŭtomate estos atribuita al la unua uzanto", - "certmanager_domain_dns_ip_differs_from_public_ip": "La DNS 'A' rekordo por la domajno '{domain:s}' diferencas de ĉi tiu IP-servilo. Se vi lastatempe modifis vian A-registron, bonvolu atendi ĝin propagandi (iuj DNS-disvastigaj kontroliloj estas disponeblaj interrete). (Se vi scias, kion vi faras, uzu '--no-checks' por malŝalti tiujn ĉekojn.)", + "certmanager_domain_dns_ip_differs_from_public_ip": "La DNS 'A' rekordo por la domajno '{domain:s}' diferencas de la IP de ĉi tiu servilo. Se vi lastatempe modifis vian A-registron, bonvolu atendi ĝin propagandi (iuj DNS-disvastigaj kontroliloj estas disponeblaj interrete). (Se vi scias, kion vi faras, uzu '--no-checks' por malŝalti tiujn ĉekojn.)", "tools_upgrade_special_packages_completed": "Plenumis la ĝisdatigon de pakaĵoj de YunoHost.\nPremu [Enter] por retrovi la komandlinion", "log_remove_on_failed_install": "Forigu '{}' post malsukcesa instalado", "regenconf_file_manually_modified": "La agorddosiero '{conf}' estis modifita permane kaj ne estos ĝisdatigita", @@ -211,7 +211,7 @@ "migration_description_0006_sync_admin_and_root_passwords": "Sinkronigu admin kaj radikajn pasvortojn", "iptables_unavailable": "Vi ne povas ludi kun iptables ĉi tie. Vi estas en ujo aŭ via kerno ne subtenas ĝin", "global_settings_cant_write_settings": "Ne eblis konservi agordojn, tial: {reason:s}", - "service_added": "La servo '{service:s}' aldonis", + "service_added": "La servo '{service:s}' estis aldonita", "upnp_disabled": "UPnP malŝaltis", "service_started": "Servo '{service:s}' komenciĝis", "port_already_opened": "Haveno {port:d} estas jam malfermita por {ip_version:s} rilatoj", @@ -283,7 +283,7 @@ "log_operation_unit_unclosed_properly": "Operaciumo ne estis fermita ĝuste", "upgrade_complete": "Ĝisdatigo kompleta", "upnp_enabled": "UPnP ŝaltis", - "mailbox_used_space_dovecot_down": "La retpoŝta servo de Dovecot devas funkcii, se vi volas akcepti uzitan poŝtan spacon", + "mailbox_used_space_dovecot_down": "La poŝta servo de Dovecot devas funkcii, se vi volas akcepti uzitan poŝtan keston", "restore_system_part_failed": "Ne povis restarigi la sisteman parton '{part:s}'", "service_stop_failed": "Ne povis maldaŭrigi la servon '{service:s}'\n\nLastatempaj servaj protokoloj: {logs:s}", "unbackup_app": "App '{app:s}' ne konserviĝos", @@ -312,7 +312,7 @@ "package_unknown": "Nekonata pako '{pkgname}'", "domain_unknown": "Nekonata domajno", "global_settings_setting_security_password_user_strength": "Uzanto pasvorta forto", - "restore_may_be_not_enough_disk_space": "Via sistemo ŝajnas ne havi sufiĉe da spaco (free:{free_space:d} B, necesa spaco: {needed_space:d} B, sekureca marĝeno: {margin:d} B)", + "restore_may_be_not_enough_disk_space": "Via sistemo ne ŝajnas havi sufiĉe da spaco (libera: {libera_spaco:d} B, necesa spaco: {necesa_spaco:d} B, sekureca marĝeno: {rando:d} B)", "log_corrupted_md_file": "La YAD-metadata dosiero asociita kun protokoloj estas damaĝita: '{md_file}\nEraro: {error} '", "downloading": "Elŝutante …", "user_deleted": "Uzanto forigita", @@ -323,7 +323,7 @@ "service_description_fail2ban": "Protektas kontraŭ bruta forto kaj aliaj specoj de atakoj de la interreto", "file_does_not_exist": "La dosiero {path:s} ne ekzistas.", "yunohost_not_installed": "YunoHost ne estas ĝuste instalita. Bonvolu prilabori 'yunohost tools postinstall'", - "migration_0005_postgresql_96_not_installed": "PostgreSQL 9.4 estas instalita, sed ne postgresql 9.6‽ Io stranga eble okazis en via sistemo: (…", + "migration_0005_postgresql_96_not_installed": "PostgreSQL 9.4 estas instalita, sed ne postgresql 9.6‽ Io stranga eble okazis en via sistemo :(…", "restore_removing_tmp_dir_failed": "Ne povis forigi malnovan provizoran dosierujon", "certmanager_cannot_read_cert": "Io malbona okazis, kiam mi provis malfermi aktualan atestilon por domajno {domain:s} (dosiero: {file:s}), kialo: {reason:s}", "service_removed": "Servo '{service:s}' forigita", @@ -372,7 +372,7 @@ "migration_0003_general_warning": "Bonvolu noti, ke ĉi tiu migrado estas delikata operacio. La teamo de YunoHost faris sian plej bonan revizii kaj testi ĝin, sed la migrado eble ankoraŭ rompos partojn de la sistemo aŭ ĝiaj programoj.\n\nTial oni rekomendas al:\n - Elfari kopion de iuj kritikaj datumoj aŭ app. Pliaj informoj pri https://yunohost.org/backup;\n - Paciencu post lanĉo de la migrado: Depende de via interreta konekto kaj aparataro, eble daŭros kelkaj horoj ĝis ĉio ĝisdatigi.\n\nAldone, la haveno por SMTP, uzata de eksteraj retpoŝtaj klientoj (kiel Thunderbird aŭ K9-Mail) estis ŝanĝita de 465 (SSL / TLS) al 587 (STARTTLS). La malnova haveno (465) aŭtomate fermiĝos, kaj la nova haveno (587) malfermiĝos en la fajrejo. Vi kaj viaj uzantoj * devos adapti la agordon de viaj retpoŝtaj klientoj laŭe.", "global_settings_setting_example_int": "Ekzemple int elekto", "backup_output_symlink_dir_broken": "Via arkiva dosierujo '{path:s}' estas rompita ligilo. Eble vi forgesis restarigi aŭ munti aŭ enŝovi la stokadon, al kiu ĝi notas.", - "good_practices_about_admin_password": "Vi nun estas por difini novan administran pasvorton. La pasvorto devas esti almenaŭ 8 signoj - kvankam estas bone praktiki uzi pli longan pasvorton (t.e. pasfrazon) kaj / aŭ uzi variaĵon de signoj (majuskloj, minuskloj, ciferoj kaj specialaj signoj).", + "good_practices_about_admin_password": "Vi nun estas por difini novan administran pasvorton. La pasvorto devas esti almenaŭ 8 signojn - kvankam estas bone praktiki uzi pli longan pasvorton (t.e. pasfrazon) kaj/aŭ uzi variaĵon de signoj (majuskloj, minuskloj, ciferoj kaj specialaj signoj).", "certmanager_attempt_to_renew_valid_cert": "La atestilo por la domajno '{domain:s}' ne finiĝos! (Vi eble uzos --force se vi scias kion vi faras)", "restore_running_hooks": "Kurantaj restarigaj hokoj…", "regenconf_pending_applying": "Aplikante pritraktata agordo por kategorio '{category}'…", @@ -387,12 +387,12 @@ "migrations_list_conflict_pending_done": "Vi ne povas uzi ambaŭ '--previous' kaj '--done' samtempe.", "server_shutdown_confirm": "La servilo haltos tuj, ĉu vi certas? [{answers:s}]", "log_backup_restore_app": "Restarigu '{}' de rezerva ar archiveivo", - "log_does_exists": "Ne estas operacio-registro kun la nomo '{log}', uzu 'yunohost loglist' por vidi ĉiujn disponeblajn operaciojn", + "log_does_exists": "Ne estas operacio kun la nomo '{log}', uzu 'yunohost log list' por vidi ĉiujn disponeblajn operaciojn", "service_add_failed": "Ne povis aldoni la servon '{service:s}'", "pattern_password_app": "Bedaŭrinde, pasvortoj ne povas enhavi jenajn signojn: {forbidden_chars}", "this_action_broke_dpkg": "Ĉi tiu ago rompis dpkg / APT (la administrantoj pri la paka sistemo) ... Vi povas provi solvi ĉi tiun problemon per konekto per SSH kaj funkcianta `sudo dpkg --configure -a`.", "log_regen_conf": "Regeneri sistemajn agordojn '{}'", - "restore_hook_unavailable": "La restariga skripto por '{part:s}' ne haveblas en via sistemo kaj ankaŭ ne en la ar theivo", + "restore_hook_unavailable": "Restariga skripto por '{part:s}' ne haveblas en via sistemo kaj ankaŭ ne en la ar theivo", "log_dyndns_subscribe": "Aboni al YunoHost-subdominio '{}'", "password_too_simple_4": "La pasvorto bezonas almenaŭ 12 signojn kaj enhavas ciferon, majuskle, pli malaltan kaj specialajn signojn", "migration_0003_main_upgrade": "Komencanta ĉefa ĝisdatigo …", @@ -401,7 +401,7 @@ "global_settings_setting_security_nginx_compatibility": "Kongruo vs sekureca kompromiso por la TTT-servilo NGINX. Afektas la ĉifradojn (kaj aliajn aspektojn pri sekureco)", "no_internet_connection": "La servilo ne estas konektita al la interreto", "migration_0008_dsa": "• La DSA-ŝlosilo estos malŝaltita. Tial vi eble bezonos nuligi spuran averton de via SSH-kliento kaj revizii la fingrospuron de via servilo;", - "migration_0003_restoring_origin_nginx_conf": "Fileia dosiero /etc/nginx/nginx.conf estis iel redaktita. La migrado reaperos unue al sia originala stato ... La antaŭa dosiero estos havebla kiel {backup_dest}.", + "migration_0003_restoring_origin_nginx_conf": "Fileia dosiero /etc/nginx/nginx.conf estis iel redaktita. La migrado unue restarigos ĝin al sia originala stato ... La antaŭa dosiero estos havebla kiel {backup_dest}.", "migrate_tsig_end": "Migrado al HMAC-SHA-512 finiĝis", "restore_complete": "Restarigita", "certmanager_couldnt_fetch_intermediate_cert": "Ekvilibrigita kiam vi provis ricevi interajn atestilojn de Let's Encrypt. Atestita instalado / renovigo nuligita - bonvolu reprovi poste.", @@ -432,14 +432,14 @@ "certmanager_cert_install_success": "Ni Ĉifru atestilon nun instalitan por la domajno '{domain:s}'", "global_settings_bad_choice_for_enum": "Malbona elekto por agordo {setting:s}, ricevita '{choice:s}', sed disponeblaj elektoj estas: {available_choices:s}", "server_shutdown": "La servilo haltos", - "log_tools_migrations_migrate_forward": "Migri antaŭen", - "migration_0008_no_warning": "Supersalti vian SSH-agordon estu sekura, kvankam ĉi tio ne povas esti promesita! Ekfunkciu la migradon por superregi ĝin. Alie, vi ankaŭ povas salti la migradon, kvankam ĝi ne rekomendas.", + "log_tools_migrations_migrate_forward": "Kuru migradoj", + "migration_0008_no_warning": "Supersalti vian SSH-agordon estu sekura, kvankam tio ne povas esti promesita! Ekfunkciu la migradon por superregi ĝin. Alie, vi ankaŭ povas salti la migradon, kvankam ĝi ne rekomendas.", "regenconf_now_managed_by_yunohost": "La agorda dosiero '{conf}' nun estas administrata de YunoHost (kategorio {category}).", "server_reboot_confirm": "Ĉu la servilo rekomencos tuj, ĉu vi certas? [{answers:s}]", "log_app_install": "Instalu la aplikon '{}'", "service_description_dnsmasq": "Traktas rezolucion de domajna nomo (DNS)", "global_settings_unknown_type": "Neatendita situacio, la agordo {setting:s} ŝajnas havi la tipon {unknown_type:s} sed ĝi ne estas tipo subtenata de la sistemo.", - "migration_0003_problematic_apps_warning": "Bonvolu noti, ke la sekvaj eventuale problemaj instalitaj programoj estis detektitaj. Ŝajnas, ke tiuj ne estis instalitaj el app_katalogo aŭ ne estas markitaj kiel \"funkciantaj\". Tial ne eblas garantii, ke ili ankoraŭ funkcios post la ĝisdatigo: {problematic_apps}", + "migration_0003_problematic_apps_warning": "Bonvolu noti, ke la sekvaj eventuale problemaj instalitaj programoj estis detektitaj. Ŝajnas, ke tiuj ne estis instalitaj el aplika katalogo aŭ ne estas markitaj kiel \"funkciantaj\". Tial ne eblas garantii, ke ili ankoraŭ funkcios post la ĝisdatigo: {problematic_apps}", "domain_hostname_failed": "Ne povis agordi novan gastigilon. Ĉi tio eble kaŭzos problemon poste (eble bone).", "server_reboot": "La servilo rekomenciĝos", "regenconf_failed": "Ne povis regeneri la agordon por kategorio(j): {categories}", @@ -497,11 +497,11 @@ "app_install_failed": "Ne povis instali {app} : {error}", "app_install_script_failed": "Eraro okazis en la skripto de instalado de la app", "app_remove_after_failed_install": "Forigado de la app post la instala fiasko …", - "diagnosis_basesystem_host": "Servilo funkcias Debian {debian_version}.", + "diagnosis_basesystem_host": "Servilo funkcias Debian {debian_version}", "apps_catalog_init_success": "Aplikoj katalogsistemo inicializita !", - "apps_catalog_updating": "Ĝisdatigante katalogo de aplikoj ...", + "apps_catalog_updating": "Ĝisdatigante katalogo de aplikoj …", "apps_catalog_failed_to_download": "Ne eblas elŝuti la katalogon de {apps_catalog}: {error}", - "apps_catalog_obsolete_cache": "La kaŝmemoro de la katalogo de programoj estas malplena aŭ malaktuala.", + "apps_catalog_obsolete_cache": "La kaŝmemoro de la aplika katalogo estas malplena aŭ malaktuala.", "apps_catalog_update_success": "La aplika katalogo estis ĝisdatigita!", "diagnosis_basesystem_kernel": "Servilo funkcias Linuksan kernon {kernel_version}", "diagnosis_basesystem_ynh_single_version": "{package} versio: {version} ({repo})", @@ -516,9 +516,9 @@ "diagnosis_diskusage_verylow": "Stokado {mountpoint} (sur aparato {device)) restas nur {free} ({free_percent}%) spaco. 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": "Ne povis atingi vian servilon kiel atendite, ĝi redonis malbonan statuskodon. Povas esti, ke alia maŝino respondis anstataŭ via servilo. Vi devus kontroli, ke vi ĝuste redonas la havenon 80, ke via nginx-agordo ĝisdatigas kaj ke reverso-prokuro ne interbatalas.", + "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.", "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ŭ 'yunohost user create ' en komandlinio);\n - diagnozi problemojn atendantajn solvi por ke via servilo funkciu kiel eble plej glate tra la sekcio 'Diagnosis' de la retadministrado (aŭ 'yunohost diagnosis run' en komandlinio);\n - legante la partojn 'Finigi vian agordon' kaj 'Ekkoni Yunohost' en la administra dokumentado: https://yunohost.org/admindoc.", + "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", "diagnosis_ip_connected_ipv4": "La servilo estas konektita al la interreto per IPv4 !", "diagnosis_ip_no_ipv4": "La servilo ne havas funkciantan IPv4.", @@ -527,9 +527,9 @@ "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 per /etc/resolv.dnsmasq.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 / mankas DNS-agordo por domajno {domain} (kategorio {category})", + "diagnosis_dns_bad_conf": "Malbona aŭ mankas DNS-agordo 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.", @@ -539,7 +539,7 @@ "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_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_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}", @@ -562,7 +562,7 @@ "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 '{}' kiel ĉefa domajno", + "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_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", @@ -576,12 +576,12 @@ "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 servo {service}", - "diagnosis_ports_forwarding_tip": "Por solvi ĉi tiun problemon, plej probable vi devas agordi la plusendon de haveno en via interreta enkursigilo kiel priskribite en https://yunohost.org/isp_box_config", + "diagnosis_ports_needed_by": "Eksponi ĉi tiun havenon necesas por {1} funkcioj (servo {0})", + "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_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} atingeblas de ekstere.", - "diagnosis_http_unreachable": "Domajno {domain} estas atingebla per HTTP de ekstere.", + "diagnosis_http_ok": "Domajno {domain} atingebla per HTTP de ekster la loka reto.", + "diagnosis_http_unreachable": "Domajno {domain} ŝajnas neatingebla per HTTP de ekster la loka reto.", "domain_cannot_remove_main_add_new_one": "Vi ne povas forigi '{domain:s}' ĉar ĝi estas la ĉefa domajno kaj via sola domajno, vi devas unue aldoni alian domajnon uzante ''yunohost domain add ', tiam agordi kiel ĉefan domajnon uzante 'yunohost domain main-domain -n ' kaj tiam vi povas forigi la domajnon' {domain:s} 'uzante' yunohost domain remove {domain:s} '.'", "permission_require_account": "Permesilo {permission} nur havas sencon por uzantoj, kiuj havas konton, kaj tial ne rajtas esti ebligitaj por vizitantoj.", "diagnosis_found_warnings": "Trovitaj {warnings} ero (j) kiuj povus esti plibonigitaj por {category}.", @@ -591,5 +591,12 @@ "diagnosis_description_mail": "Retpoŝto", "log_app_action_run": "Funkciigu agon de la apliko '{}'", "log_app_config_show_panel": "Montri la agordan panelon de la apliko '{}'", - "log_app_config_apply": "Apliki agordon al la apliko '{}'" + "log_app_config_apply": "Apliki agordon al la apliko '{}'", + "diagnosis_never_ran_yet": "Ŝajnas, ke ĉi tiu servilo estis instalita antaŭ nelonge kaj estas neniu diagnoza raporto por montri. Vi devas komenci kurante plenan diagnozon, ĉu de la retadministro aŭ uzante 'yunohost diagnosis run' el la komandlinio.", + "certmanager_warning_subdomain_dns_record": "Subdominio '{subdomain:s}' ne solvas al la sama IP-adreso kiel '{domain:s}'. Iuj funkcioj ne estos haveblaj ĝis vi riparos ĉi tion kaj regeneros la atestilon.", + "diagnosis_basesystem_hardware": "Arkitekturo de servila aparataro estas {virt} {arch}", + "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 …" } From caf41928cc17f665acab3c0fc3decf7d5280fb67 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Tue, 21 Apr 2020 06:58:00 +0000 Subject: [PATCH 1111/3170] Translated using Weblate (French) Currently translated at 100.0% (632 of 632 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 67 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 53 insertions(+), 14 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 7b31f9237..ef60a6956 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -507,10 +507,10 @@ "diagnosis_found_errors": "Trouvé {errors} problème(s) significatif(s) lié(s) à {category} !", "diagnosis_found_errors_and_warnings": "Trouvé {errors} problème(s) significatif(s) (et {warnings} (avertissement(s)) en relation avec {category} !", "diagnosis_ip_not_connected_at_all": "Le serveur ne semble pas du tout connecté à Internet !?", - "diagnosis_ip_weird_resolvconf": "La résolution DNS semble fonctionner, mais soyez prudent en utilisant un fichier /etc/resolv.conf personnalisé.", - "diagnosis_ip_weird_resolvconf_details": "Au lieu de cela, ce fichier devrait être un lien symbolique vers /etc/resolvconf/run/resolv.conf lui-même pointant vers 127.0.0.1 (dnsmasq). Les résolveurs réels doivent être configurés dans /etc/resolv.dnsmasq.conf.", + "diagnosis_ip_weird_resolvconf": "La résolution DNS semble fonctionner, mais il semble que vous utilisez un /etc/resolv.conf personnalisé.", + "diagnosis_ip_weird_resolvconf_details": "Le fichier /etc/resolv.conf doit être un lien symbolique vers /etc/resolvconf/run/resolv.conf lui-même pointant vers 127.0.0.1 (dnsmasq). Si vous souhaitez configurer manuellement les résolveurs DNS, veuillez modifier /etc/resolv.dnsmasq.conf.", "diagnosis_dns_missing_record": "Selon la configuration DNS recommandée, vous devez ajouter un enregistrement DNS\nType: {type}\nNom: {name}\nValeur {value}", - "diagnosis_diskusage_ok": "Le stockage {mountpoint} (sur le périphérique {device}) a encore {free} ({free_percent}%) d’espace libre !", + "diagnosis_diskusage_ok": "L'espace de stockage {mountpoint} (sur l'appareil {device}) a encore {libre} ({free_percent}%) espace restant (sur {total}) !", "diagnosis_ram_ok": "Le système dispose encore de {available} ({available_percent}%) de RAM sur {total}.", "diagnosis_regenconf_allgood": "Tous les fichiers de configuration sont conformes à la configuration recommandée !", "diagnosis_security_vulnerable_to_meltdown": "Vous semblez vulnérable à la vulnérabilité de sécurité critique de Meltdown", @@ -532,26 +532,26 @@ "diagnosis_ip_no_ipv6": "Le serveur ne dispose pas d’une adresse IPv6.", "diagnosis_ip_dnsresolution_working": "La résolution de nom de domaine fonctionne !", "diagnosis_ip_broken_dnsresolution": "La résolution du nom de domaine semble interrompue pour une raison quelconque … Un pare-feu bloque-t-il les requêtes DNS ?", - "diagnosis_ip_broken_resolvconf": "La résolution du nom de domaine semble cassée sur votre serveur, ce qui semble lié au fait que /etc/resolv.conf ne pointe pas vers 127.0.0.1.", - "diagnosis_dns_good_conf": "Bonne configuration DNS pour le domaine {domain} (catégorie {category})", - "diagnosis_dns_bad_conf": "Configuration DNS incorrecte ou manquante pour le domaine {domain} (catégorie {category})", + "diagnosis_ip_broken_resolvconf": "La résolution du nom de domaine semble être rompue sur votre serveur, ce qui semble lié au fait que /etc/resolv.conf ne pointe pas sur 127.0.0.1.", + "diagnosis_dns_good_conf": "Les enregistrements DNS sont correctement configurés pour le domaine {domain} (catégorie {category})", + "diagnosis_dns_bad_conf": "Certains enregistrements DNS sont manquants ou incorrects pour le domaine {domain} (catégorie {category})", "diagnosis_dns_discrepancy": "L’enregistrement DNS de type {type} et nom {name} ne correspond pas à la configuration recommandée.\nValeur actuelle: {current}\nValeur attendue: {value}", "diagnosis_services_bad_status": "Le service {service} est {status} :-(", - "diagnosis_diskusage_verylow": "Le stockage {mountpoint} (sur le périphérique {device}) ne dispose que de {free} ({free_percent}%). Vous devriez vraiment envisager de nettoyer un peu d’espace.", - "diagnosis_diskusage_low": "Le stockage {mountpoint} (sur le périphérique {device}) ne dispose que de {free} ({free_percent}%). Faites attention.", + "diagnosis_diskusage_verylow": "L'espace de stockage {mountpoint} (sur l'appareil {device} ) ne dispose que de {free} ({free_percent}%) espace restant (sur {total}). Vous devriez vraiment envisager de nettoyer de l'espace !", + "diagnosis_diskusage_low": "L'espace de stockage {mountpoint} (sur l'appareil {device}) ne dispose que de {free} ({free_percent}%) espace restant (sur {total}). Faites attention.", "diagnosis_ram_verylow": "Le système ne dispose plus que de {available} ({available_percent}%)! (sur {total})", "diagnosis_ram_low": "Le système n’a plus de {available} ({available_percent}%) RAM sur {total}. Faites attention.", "diagnosis_swap_none": "Le système n’a aucun espace de swap. Vous devriez envisager d’ajouter au moins {recommended} de swap pour éviter les situations où le système manque de mémoire.", "diagnosis_swap_notsomuch": "Le système ne dispose que de {total} de swap. Vous devez envisager d’avoir au moins {recommended} pour éviter les situations où le système manque de mémoire.", "diagnosis_swap_ok": "Le système dispose de {total} de swap !", - "diagnosis_regenconf_manually_modified": "Le fichier de configuration {file} a été modifié manuellement.", + "diagnosis_regenconf_manually_modified": "Le fichier de configuration {file} semble avoir été modifié manuellement.", "diagnosis_regenconf_manually_modified_debian": "Le fichier de configuration {file} a été modifié manuellement par rapport à celui par défaut de Debian.", - "diagnosis_regenconf_manually_modified_details": "C'est probablement OK tant que vous savez ce que vous faites;) !", + "diagnosis_regenconf_manually_modified_details": "C'est probablement OK si vous savez ce que vous faites! YunoHost cessera de mettre à jour ce fichier automatiquement ... Mais attention, les mises à jour de YunoHost pourraient contenir d'importantes modifications recommandées. Si vous le souhaitez, vous pouvez inspecter les différences avec les outils yunohost regen-conf {category} --dry-run --with-diff et forcer la réinitialisation à la configuration recommandée avec les outils yunohost regen-conf {category} --force ", "diagnosis_regenconf_manually_modified_debian_details": "Cela peut probablement être OK, mais il faut garder un œil dessus …", "apps_catalog_init_success": "Système de catalogue d’applications initialisé !", "apps_catalog_failed_to_download": "Impossible de télécharger le catalogue des applications {apps_catalog}:{error}", "diagnosis_mail_outgoing_port_25_blocked": "Le port sortant 25 semble être bloqué. Vous devriez essayer de le débloquer dans le panneau de configuration de votre fournisseur de services Internet (ou hébergeur). En attendant, le serveur ne pourra pas envoyer de courrier électronique à d’autres serveurs.", - "domain_cannot_remove_main_add_new_one": "Vous ne pouvez pas supprimer '{domain:s}' car il s’agit du domaine principal et de votre seul domaine. Vous devez d’abord ajouter un autre domaine à l’aide de 'yunohost domain add ', puis définir comme domaine principal à l’aide de 'yunohost domain main-domain -n ' et vous pouvez ensuite supprimer le domaine '{domain:s}' à l’aide de 'yunohost domain remove {domain:s}'.", + "domain_cannot_remove_main_add_new_one": "Vous ne pouvez pas supprimer '{domain:s}' car il s’agit du domaine principal et de votre seul domaine. Vous devez d’abord ajouter un autre domaine à l’aide de 'yunohost domain add ', puis définir comme domaine principal à l’aide de 'yunohost domain main-domain -n ' et vous pouvez ensuite supprimer le domaine '{domain:s}' à l’aide de 'yunohost domain remove {domain:s}'.'", "diagnosis_security_vulnerable_to_meltdown_details": "Pour résoudre ce problème, vous devez mettre à niveau votre système et redémarrer pour charger le nouveau noyau Linux (ou contacter votre fournisseur de serveur si cela ne fonctionne pas). Voir https://meltdownattack.com/ pour plus d’informations.", "diagnosis_description_basesystem": "Système de base", "diagnosis_description_ip": "Connectivité Internet", @@ -581,11 +581,11 @@ "diagnosis_services_running": "Le service {service} est en cours de fonctionnement !", "diagnosis_services_conf_broken": "La configuration est cassée pour le service {service} !", "diagnosis_ports_needed_by": "Rendre ce port accessible est nécessaire pour les fonctionnalités de type {category} (service {service})", - "diagnosis_ports_forwarding_tip": "Pour résoudre ce problème, vous devez probablement configurer la redirection de port sur votre routeur Internet comme décrit dans https://yunohost.org/isp_box_config", + "diagnosis_ports_forwarding_tip": "Pour résoudre ce problème, vous devez probablement configurer la redirection de port sur votre routeur Internet comme décrit dans https://yunohost.org/isp_box_config ", "diagnosis_http_connection_error": "Erreur de connexion : impossible de se connecter au domaine demandé, il est probablement injoignable.", "diagnosis_no_cache": "Pas encore de cache de diagnostique pour la catégorie « {category} »", "yunohost_postinstall_end_tip": "La post-installation terminée! Pour finaliser votre configuration, il est recommendé de :\n - ajouter un premier utilisateur depuis la section \"Utilisateurs\" de l’interface web (ou \"yunohost user create \" en ligne de commande);\n - diagnostiquer les potentiels problèmes dans la section \"Diagnostic\" de l'interface web (ou \"yunohost diagnosis run\" en ligne de commande);\n - lire les parties \"Finalisation de votre configuration\" et \"Découverte de Yunohost\" dans le guide de l’administrateur: https://yunohost.org/admindoc.", - "diagnosis_services_bad_status_tip": "Vous pouvez essayer de redémarrer le service. Si cela ne fonctionne pas, consultez les journaux de service à l’aide de 'yunohost service log {service}' ou de la section 'Services' dans la webadmin.", + "diagnosis_services_bad_status_tip": "Vous pouvez essayer de redémarrer le service, et si cela ne fonctionne pas, consultez les journaux de service dans le webadmin (à partir de la ligne de commande, vous pouvez le faire avec yunohost service restart {service} et yunohost service log {service} ).", "diagnosis_http_bad_status_code": "Le système de diagnostique n’a pas réussi à contacter votre serveur. Il se peut qu’une autre machine réponde à la place de votre serveur. Vérifiez que le port 80 est correctement redirigé, que votre configuration nginx est à jour et qu’un reverse-proxy n’interfère pas.", "diagnosis_http_timeout": "Expiration du délai en essayant de contacter votre serveur de l’extérieur. Il semble être inaccessible. Vérifiez que vous transférez correctement le port 80, que nginx est en cours d’exécution et qu’un pare-feu n’interfère pas.", "global_settings_setting_pop3_enabled": "Activer le protocole POP3 pour le serveur de messagerie", @@ -598,5 +598,44 @@ "diagnosis_basesystem_hardware": "L’architecture du serveur est {virt} {arch}", "group_already_exist_on_system_but_removing_it": "Le groupe {group} est déjà présent dans les groupes du système, mais YunoHost va le supprimer…", "certmanager_warning_subdomain_dns_record": "Le sous-domaine '{subdomain:s}' ne résout pas vers la même adresse IP que '{domain:s}'. Certaines fonctionnalités seront indisponibles tant que vous n’aurez pas corrigé cela et regénéré le certificat.", - "domain_cannot_add_xmpp_upload": "Vous ne pouvez pas ajouter de domaine commençant par 'xmpp-upload.'. Ce type de nom est réservé à la fonctionnalité d’upload XMPP intégrée dans YunoHost." + "domain_cannot_add_xmpp_upload": "Vous ne pouvez pas ajouter de domaine commençant par 'xmpp-upload.'. Ce type de nom est réservé à la fonctionnalité d’upload XMPP intégrée dans YunoHost.", + "diagnosis_mail_outgoing_port_25_ok": "Le serveur de messagerie SMTP peut envoyer des e-mails (le port sortant 25 n'est pas bloqué).", + "diagnosis_mail_outgoing_port_25_blocked_details": "Vous devez d'abord essayer de débloquer le port sortant 25 dans votre interface de routeur Internet ou votre interface d'hébergement. (Certains hébergeurs peuvent vous demander de leur envoyer un ticket d'assistance pour cela).", + "diagnosis_mail_ehlo_bad_answer": "Un service non SMTP a répondu sur le port 25 sur IPv {ipversion}", + "diagnosis_mail_ehlo_bad_answer_details": "Cela peut être dû à un autre répondeur au lieu de votre serveur.", + "diagnosis_mail_ehlo_wrong": "Un autre serveur de messagerie SMTP répond sur IPv{ipversion}. Il ne sera probablement pas en mesure de recevoir des e-mails.", + "diagnosis_mail_ehlo_could_not_diagnose": "Impossible de diagnostiquer si le serveur de messagerie postfix est accessible de l'extérieur pour IPv {ipversion}.", + "diagnosis_mail_ehlo_could_not_diagnose_details": "Erreur: {error}", + "diagnosis_mail_fcrdns_dns_missing": "Aucun DNS inverse n'est défini dans IPv {ipversion}. Certains e-mails peuvent ne pas être livrés ou être signalés comme spam.", + "diagnosis_mail_fcrdns_ok": "Votre DNS inversé est correctement configuré !", + "diagnosis_mail_fcrdns_nok_details": "Vous devez d'abord essayer de configurer le DNS inverse avec {ehlo_domain} dans votre interface de routeur Internet ou votre interface d'hébergement. (Certains hébergeurs peuvent vous demander de leur envoyer un ticket d'assistance pour cela).", + "diagnosis_mail_fcrdns_different_from_ehlo_domain": "Le DNS inverse n'est pas correctement configuré dans IPv {ipversion}. Certains e-mails peuvent ne pas être livrés ou être signalés comme spam.", + "diagnosis_mail_blacklist_ok": "Les adresses IP et les domaines utilisés par ce serveur ne semblent pas être sur liste noire", + "diagnosis_mail_blacklist_reason": "La raison de la liste noire est: {reason}", + "diagnosis_mail_blacklist_website": "Après avoir identifié pourquoi vous êtes répertorié et corrigé, n'hésitez pas à demander la radiation sur {blacklist_website}", + "diagnosis_mail_queue_ok": "{nb_pending} e-mails en attente dans les files d'attente de messagerie", + "diagnosis_mail_queue_unavailable_details": "Erreur: {error}", + "diagnosis_mail_queue_too_big": "Trop d'e-mails en attente dans la file d'attente ({nb_pending} e-mails)", + "global_settings_setting_smtp_allow_ipv6": "Autoriser l'utilisation d'IPv6 pour recevoir et envoyer du courrier", + "diagnosis_security_all_good": "Aucune vulnérabilité de sécurité critique n'a été trouvée.", + "diagnosis_display_tip": "Pour voir les problèmes détectés, vous pouvez accéder à la section Diagnostic du webadmin ou exécuter «yunohost diagnostic show --issues» à partir de la ligne de commande.", + "diagnosis_ip_global": "IP globale: {global} ", + "diagnosis_ip_local": "IP locale: {local} ", + "diagnosis_dns_point_to_doc": "Veuillez consulter la documentation à https://yunohost.org/dns_config si vous avez besoin d'aide pour configurer les enregistrements DNS.", + "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Certains fournisseurs ne vous laisseront pas débloquer le port sortant 25 parce qu'ils ne se soucient pas de la neutralité du Net.
- Certains d'entre eux offrent l'alternative de en utilisant un relais de serveur de messagerie bien que cela implique que le relais sera en mesure d'espionner votre trafic de messagerie.
- Une alternative respectueuse de la vie privée consiste à utiliser un VPN * avec une IP publique dédiée * pour contourner ce type des limites. Voir https://yunohost.org/#/vpn_advantage
- Vous pouvez également envisager de passer à un fournisseur plus respectueux de la neutralité du net ", + "diagnosis_mail_ehlo_ok": "Le serveur de messagerie SMTP est accessible de l'extérieur et peut donc recevoir des e-mails !", + "diagnosis_mail_ehlo_unreachable": "Le serveur de messagerie SMTP est inaccessible de l'extérieur sur IPv {ipversion}. Il ne pourra pas recevoir d'e-mails.", + "diagnosis_mail_ehlo_unreachable_details": "Impossible d'ouvrir une connexion sur le port 25 à votre serveur dans IPv {ipversion}. Il semble inaccessible.
1. La cause la plus courante de ce problème est que le port 25 n'est pas correctement transmis à votre serveur .
2. Vous devez également vous assurer que le suffixe de service est en cours d'exécution.
3. Sur les configurations plus complexes: assurez-vous qu'aucun pare-feu ou proxy inverse n'interfère.", + "diagnosis_mail_ehlo_wrong_details": "L'EHLO reçu par le diagnostiqueur distant dans IPv {ipversion} est différent du domaine de votre serveur.
EHLO reçu: {bad_ehlo}
Attendu: {right_ehlo}
La cause la plus courante ce problème est que le port 25 n'est pas correctement transmis à votre serveur . Vous pouvez également vous assurer qu'aucun pare-feu ou proxy inverse n'interfère.", + "diagnosis_mail_fcrdns_nok_alternatives_4": "Certains fournisseurs ne vous laisseront pas configurer votre DNS inversé (ou leur fonctionnalité pourrait être cassée …). Si vous rencontrez des problèmes à cause de cela, envisagez les solutions suivantes:
- Certains FAI fournissent l’alternative de à l’aide d’un relais de serveur de messagerie bien que cela implique que le relais pourra espionner votre trafic de messagerie.
- Une alternative respectueuse de la vie privée consiste à utiliser un VPN * avec une IP publique dédiée * pour contourner ce type de limites. Voir
https://yunohost.org/#/vpn_advantage
- Enfin, il est également possible de changement de fournisseur ", + "diagnosis_mail_fcrdns_nok_alternatives_6": "Certains fournisseurs ne vous laisseront pas configurer votre DNS inversé (ou leur fonctionnalité pourrait être cassée ...). Si votre DNS inversé est correctement configuré pour IPv4, vous pouvez essayer de désactiver l'utilisation d'IPv6 lors de l'envoi d'e-mails en exécutant yunohost settings set smtp.allow_ipv6 -v off . Remarque: cette dernière solution signifie que vous ne pourrez pas envoyer ou recevoir de courriels des quelques serveurs IPv6 uniquement disponibles.", + "diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "DNS inverse actuel: {rdns_domain}
Valeur attendue: {ehlo_domain} ", + "diagnosis_mail_blacklist_listed_by": "Votre IP ou domaine {item} est sur liste noire sur {blacklist_name}", + "diagnosis_mail_queue_unavailable": "Impossible de consulter le nombre d'e-mails en attente dans la file d'attente", + "diagnosis_ports_partially_unreachable": "Le port {port} n'est pas accessible de l'extérieur dans IPv {failed}.", + "diagnosis_http_hairpinning_issue": "Votre réseau local ne semble pas avoir activé l'épingle à cheveux.", + "diagnosis_http_hairpinning_issue_details": "C'est probablement à cause de votre box/routeur ISP. 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 ?). 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 via 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 sur 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." } From 91355274f8c4cee03c63faf9dea3fec278e8e698 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= Date: Wed, 22 Apr 2020 10:09:38 +0000 Subject: [PATCH 1112/3170] Translated using Weblate (French) Currently translated at 100.0% (632 of 632 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 192 ++++++++++++++++++++++++------------------------ 1 file changed, 96 insertions(+), 96 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index ef60a6956..7bc6b1687 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -12,7 +12,7 @@ "app_install_files_invalid": "Fichiers d’installation incorrects", "app_manifest_invalid": "Manifeste d’application incorrect : {error}", "app_not_correctly_installed": "{app:s} semble être mal installé", - "app_not_installed": "Nous n’avons pas trouvé l’application « {app:s} » dans la liste des applications installées: {all_apps}", + "app_not_installed": "Nous n’avons pas trouvé l’application « {app:s} » dans la liste des applications installées : {all_apps}", "app_not_properly_removed": "{app:s} n’a pas été supprimé correctement", "app_removed": "{app:s} supprimé", "app_requirements_checking": "Vérification des paquets requis pour {app} …", @@ -48,9 +48,9 @@ "custom_app_url_required": "Vous devez spécifier une URL pour mettre à jour votre application personnalisée {app:s}", "domain_cert_gen_failed": "Impossible de générer le certificat", "domain_created": "Le domaine a été créé", - "domain_creation_failed": "Impossible de créer le domaine {domain}: {error}", + "domain_creation_failed": "Impossible de créer le domaine {domain} : {error}", "domain_deleted": "Le domaine a été supprimé", - "domain_deletion_failed": "Impossible de supprimer le domaine {domain}:{error}", + "domain_deletion_failed": "Impossible de supprimer le domaine {domain} : {error}", "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à", @@ -114,8 +114,8 @@ "restore_failed": "Impossible de restaurer le système", "restore_hook_unavailable": "Script de restauration pour '{part:s}' non disponible sur votre système et non plus dans l'archive", "restore_nothings_done": "Rien n’a été restauré", - "restore_running_app_script": "Exécution du script de restauration de l’application '{app:s}' …", - "restore_running_hooks": "Exécution des scripts de restauration …", + "restore_running_app_script": "Exécution du script de restauration de l’application '{app:s}'…", + "restore_running_hooks": "Exécution des scripts de restauration…", "service_add_failed": "Impossible d’ajouter le service '{service:s}'", "service_added": "Le service '{service:s}' a été ajouté", "service_already_started": "Le service '{service:s}' est déjà en cours d’exécution", @@ -140,25 +140,25 @@ "unexpected_error": "Une erreur inattendue est survenue : {error}", "unlimit": "Pas de quota", "unrestore_app": "L’application '{app:s}' ne sera pas restaurée", - "updating_apt_cache": "Récupération des mises à jour disponibles pour les paquets du système …", + "updating_apt_cache": "Récupération des mises à jour disponibles pour les paquets du système…", "upgrade_complete": "Mise à jour terminée", - "upgrading_packages": "Mise à jour des paquets en cours …", + "upgrading_packages": "Mise à jour des paquets en cours…", "upnp_dev_not_found": "Aucun périphérique compatible UPnP n’a été trouvé", "upnp_disabled": "UPnP désactivé", "upnp_enabled": "UPnP activé", "upnp_port_open_failed": "Impossible d’ouvrir les ports UPnP", "user_created": "L’utilisateur créé", - "user_creation_failed": "Impossible de créer l’utilisateur {user}: {error}", + "user_creation_failed": "Impossible de créer l’utilisateur {user} : {error}", "user_deleted": "L’utilisateur supprimé", - "user_deletion_failed": "Impossible de supprimer l’utilisateur {user}: {error}", + "user_deletion_failed": "Impossible de supprimer l’utilisateur {user} : {error}", "user_home_creation_failed": "Impossible de créer le dossier personnel de l’utilisateur", "user_unknown": "L’utilisateur {user:s} est inconnu", - "user_update_failed": "Impossible de mettre à jour l’utilisateur {user}: {error}", + "user_update_failed": "Impossible de mettre à jour l’utilisateur {user} : {error}", "user_updated": "L’utilisateur a été modifié", "yunohost_already_installed": "YunoHost est déjà installé", "yunohost_ca_creation_failed": "Impossible de créer l’autorité de certification", "yunohost_configured": "YunoHost est maintenant configuré", - "yunohost_installing": "L’installation de YunoHost est en cours …", + "yunohost_installing": "L’installation de YunoHost est en cours…", "yunohost_not_installed": "YunoHost n’est pas correctement installé. Veuillez exécuter 'yunohost tools postinstall'", "certmanager_attempt_to_replace_valid_cert": "Vous êtes en train de vouloir remplacer un certificat correct et valide pour le domaine {domain:s} ! (Utilisez --force pour contourner cela)", "certmanager_domain_unknown": "Domaine {domain:s} inconnu", @@ -178,7 +178,7 @@ "certmanager_conflicting_nginx_file": "Impossible de préparer le domaine pour le défi ACME : le fichier de configuration NGINX {filepath:s} est en conflit et doit être préalablement retiré", "certmanager_hit_rate_limit": "Trop de certificats ont déjà été émis récemment pour ce même ensemble de domaines {domain:s}. Veuillez réessayer plus tard. Lisez https://letsencrypt.org/docs/rate-limits/ pour obtenir plus de détails sur les ratios et limitations", "ldap_init_failed_to_create_admin": "L’initialisation de l’annuaire LDAP n’a pas réussi à créer l’utilisateur admin", - "domain_cannot_remove_main": "Vous ne pouvez pas supprimer '{domain:s}' car il s’agit du domaine principal. Vous devez d’abord définir un autre domaine comme domaine principal à l’aide de 'yunohost domain main-domain -n ', voici la liste des domaines candidats. : {other_domains:s}", + "domain_cannot_remove_main": "Vous ne pouvez pas supprimer '{domain:s}' car il s’agit du domaine principal. Vous devez d’abord définir un autre domaine comme domaine principal à l’aide de 'yunohost domain main-domain -n ', voici la liste des domaines candidats : {other_domains:s}", "certmanager_self_ca_conf_file_not_found": "Le fichier de configuration pour l’autorité du certificat auto-signé est introuvable (fichier : {file:s})", "certmanager_unable_to_parse_self_CA_name": "Impossible d’analyser le nom de l’autorité du certificat auto-signé (fichier : {file:s})", "mailbox_used_space_dovecot_down": "Le service de courriel Dovecot doit être démarré si vous souhaitez voir l’espace disque occupé par la messagerie", @@ -187,7 +187,7 @@ "certmanager_acme_not_configured_for_domain": "Le certificat du domaine {domain:s} ne semble pas être correctement installé. Veuillez d’abord exécuter cert-install.", "certmanager_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 (peut-être que ça n’en causera pas).", + "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).", "yunohost_ca_creation_success": "L’autorité de certification locale créée.", "app_already_installed_cant_change_url": "Cette application est déjà installée. L’URL ne peut pas être changé simplement par cette fonction. Vérifiez si cela est disponible avec `app changeurl`.", "app_change_url_failed_nginx_reload": "Le redémarrage de Nginx a échoué. Voici la sortie de 'nginx -t' :\n{nginx_errors:s}", @@ -234,8 +234,8 @@ "backup_with_no_restore_script_for_app": "L’application « {app:s} » n’a pas de script de restauration, vous ne pourrez pas restaurer automatiquement la sauvegarde de cette application.", "global_settings_cant_serialize_settings": "Échec de la sérialisation des données de paramétrage car : {reason:s}", "restore_removing_tmp_dir_failed": "Impossible de sauvegarder un ancien dossier temporaire", - "restore_extracting": "Extraction des fichiers nécessaires depuis l’archive …", - "restore_may_be_not_enough_disk_space": "Votre système ne semble pas avoir suffisamment d'espace (libre: {free_space:d} B, espace nécessaire: {needed_space:d} B, marge de sécurité: {margin:d} B)", + "restore_extracting": "Extraction des fichiers nécessaires depuis l’archive…", + "restore_may_be_not_enough_disk_space": "Votre système ne semble pas avoir suffisamment d’espace (libre : {free_space:d} B, espace nécessaire : {needed_space:d} B, marge de sécurité : {margin:d} B)", "restore_not_enough_disk_space": "Espace disponible insuffisant (L’espace libre est de {free_space:d} octets. Le besoin d’espace nécessaire est de {needed_space:d} octets. En appliquant une marge de sécurité, la quantité d’espace nécessaire est de {margin:d} octets)", "restore_system_part_failed": "Impossible de restaurer la partie '{part:s}' du système", "backup_couldnt_bind": "Impossible de lier {src:s} avec {dest:s}.", @@ -244,7 +244,7 @@ "migrations_loading_migration": "Chargement de la migration {id} …", "migrations_migration_has_failed": "La migration {id} a échoué avec l’exception {exception} : annulation", "migrations_no_migrations_to_run": "Aucune migration à lancer", - "migrations_skip_migration": "Ignorer et passer la migration {id} …", + "migrations_skip_migration": "Ignorer et passer la migration {id}…", "server_shutdown": "Le serveur va s’éteindre", "server_shutdown_confirm": "Le serveur va être éteint immédiatement, le voulez-vous vraiment ? [{answers:s}]", "server_reboot": "Le serveur va redémarrer", @@ -256,7 +256,7 @@ "app_upgrade_app_name": "Mise à jour de l’application {app} …", "backup_output_symlink_dir_broken": "Votre répertoire d’archivage '{path:s}' est un lien symbolique brisé. Peut-être avez-vous oublié de re/monter ou de brancher le support de stockage sur lequel il pointe.", "migrate_tsig_end": "La migration à HMAC-SHA-512 est terminée", - "migrate_tsig_failed": "La migration du domaine DynDNS {domain} à hmac-sha512 a échoué. Annulation des modifications. Erreur : {error_code} - {error}", + "migrate_tsig_failed": "La migration du domaine DynDNS {domain} à HMAC-SHA-512 a échoué. Annulation des modifications. Erreur : {error_code} - {error}", "migrate_tsig_start": "L’algorithme de génération des clefs n’est pas suffisamment sécurisé pour la signature TSIG du domaine '{domain}', lancement de la migration vers HMAC-SHA-512 qui est plus sécurisé", "migrate_tsig_wait": "Attendre trois minutes pour que le serveur DynDNS prenne en compte la nouvelle clef …", "migrate_tsig_wait_2": "2 minutes …", @@ -269,19 +269,19 @@ "migration_0003_start": "Démarrage de la migration vers Stretch. Les journaux seront disponibles dans {logfile}.", "migration_0003_patching_sources_list": "Modification du fichier sources.lists …", "migration_0003_main_upgrade": "Démarrage de la mise à niveau principale …", - "migration_0003_fail2ban_upgrade": "Démarrage de la mise à niveau de fail2ban …", - "migration_0003_restoring_origin_nginx_conf": "Votre fichier /etc/nginx/nginx.conf a été modifié d'une manière ou d'une autre. La migration va d'abord le réinitialiser à son état d'origine… Le fichier précédent sera disponible en tant que {backup_dest}.", + "migration_0003_fail2ban_upgrade": "Démarrage de la mise à niveau de Fail2Ban …", + "migration_0003_restoring_origin_nginx_conf": "Votre fichier /etc/nginx/nginx.conf a été modifié d'une manière ou d’une autre. La migration va d’abord le réinitialiser à son état d'origine… Le fichier précédent sera disponible en tant que {backup_dest}.", "migration_0003_yunohost_upgrade": "Démarrage de la mise à niveau du package YunoHost… La migration se terminera, mais la mise à niveau réelle aura lieu immédiatement après. Une fois l'opération terminée, vous devrez peut-être vous reconnecter à la page webadmin.", "migration_0003_not_jessie": "La distribution Debian actuelle n’est pas Jessie !", "migration_0003_system_not_fully_up_to_date": "Votre système n’est pas complètement à jour. Veuillez mener une mise à jour classique avant de lancer la migration à Stretch.", "migration_0003_still_on_jessie_after_main_upgrade": "Quelque chose s’est mal passé pendant la mise à niveau principale : le système est toujours sur Debian Jessie !? Pour investiguer sur le problème, veuillez regarder les journaux {log}:s …", "migration_0003_general_warning": "Veuillez noter que cette migration est une opération délicate. Si l’équipe YunoHost a fait de son mieux pour la relire et la tester, la migration pourrait tout de même casser des parties de votre système ou de vos applications.\n\nEn conséquence, nous vous recommandons :\n - de lancer une sauvegarde de vos données ou applications critiques. Plus d’informations sur https://yunohost.org/backup ;\n - d’être patient après avoir lancé la migration : selon votre connexion internet et matériel, cela pourrait prendre jusqu’à quelques heures pour que tout soit à niveau.\n\nEn outre, le port SMTP utilisé par les clients de messagerie externes comme (Thunderbird ou K9-Mail) a été changé de 465 (SSL/TLS) à 587 (STARTTLS). L’ancien port 465 sera automatiquement fermé et le nouveau port 587 sera ouvert dans le pare-feu. Vous et vos utilisateurs *devront* adapter la configuration de vos clients de messagerie en conséquence.", - "migration_0003_problematic_apps_warning": "Veuillez noter que les applications installées potentiellement problématiques suivantes ont été détectées. Il semble que celles-ci n’ont pas été installées à partir d’un catalogue d’applications, ou ne sont pas marquées comme \"fonctionnelle\". Par conséquent, il ne peut pas être garanti qu’ils fonctionneront toujours après la mise à niveau: {problematic_apps}", + "migration_0003_problematic_apps_warning": "Veuillez noter que les applications installées potentiellement problématiques suivantes ont été détectées. Il semble que celles-ci n’ont pas été installées à partir d’un catalogue d’applications, ou ne sont pas marquées comme \"fonctionnelle\". Par conséquent, il ne peut pas être garanti qu’ils fonctionneront toujours après la mise à niveau : {problematic_apps}", "migration_0003_modified_files": "Veuillez noter que les fichiers suivants ont été détectés comme modifiés manuellement et pourraient être écrasés à la fin de la mise à niveau : {manually_modified_files}", "migrations_list_conflict_pending_done": "Vous ne pouvez pas utiliser --previous et --done simultanément.", "migrations_to_be_ran_manually": "La migration {id} doit être lancée manuellement. Veuillez aller dans Outils > Migrations dans l’interface admin, ou lancer `yunohost tools migrations migrate`.", "migrations_need_to_accept_disclaimer": "Pour lancer la migration {id}, vous devez accepter cette clause de non-responsabilité :\n---\n{disclaimer}\n---\nSi vous acceptez de lancer la migration, veuillez relancer la commande avec l’option --accept-disclaimer.", - "service_description_avahi-daemon": "Vous permet d’atteindre votre serveur en utilisant «yunohost.local» sur votre réseau local", + "service_description_avahi-daemon": "Vous permet d’atteindre votre serveur en utilisant « yunohost.local » sur votre réseau local", "service_description_dnsmasq": "Gère la résolution des noms de domaine (DNS)", "service_description_dovecot": "Permet aux clients de messagerie d’accéder/récupérer les courriels (via IMAP et POP3)", "service_description_fail2ban": "Protège contre les attaques brute-force et autres types d’attaques venant d’Internet", @@ -304,7 +304,7 @@ "log_link_to_failed_log": "L’opération '{desc}' a échoué ! Pour obtenir de l’aide, merci de partager le journal de l’opération en cliquant ici", "backup_php5_to_php7_migration_may_fail": "Impossible de convertir votre archive pour prendre en charge PHP 7, vous pourriez ne plus pouvoir restaurer vos applications PHP (cause : {error:s})", "log_help_to_get_failed_log": "L’opération '{desc}' a échoué ! Pour obtenir de l’aide, merci de partager le journal de l’opération en utilisant la commande 'yunohost log display {name} --share'", - "log_does_exists": "Il n'y a pas de journal des opérations avec le nom '{log}', utilisez 'yunohost log list' pour voir tous les journaux d'opérations disponibles", + "log_does_exists": "Il n’y a pas de journal des opérations avec le nom '{log}', utilisez 'yunohost log list' pour voir tous les journaux d’opérations disponibles", "log_operation_unit_unclosed_properly": "L’opération ne s’est pas terminée correctement", "log_app_change_url": "Changer l’URL de l’application '{}'", "log_app_install": "Installer l’application '{}'", @@ -327,7 +327,7 @@ "log_user_delete": "Supprimer l’utilisateur '{}'", "log_user_update": "Mettre à jour les informations de l’utilisateur '{}'", "log_domain_main_domain": "Faites de '{}' le domaine principal", - "log_tools_migrations_migrate_forward": "Éxecuter les migrations", + "log_tools_migrations_migrate_forward": "Exécuter les migrations", "log_tools_postinstall": "Faire la post-installation de votre serveur YunoHost", "log_tools_upgrade": "Mettre à jour les paquets du système", "log_tools_shutdown": "Éteindre votre serveur", @@ -336,7 +336,7 @@ "migration_description_0004_php5_to_php7_pools": "Reconfigurer les espaces utilisateurs 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: (…", + "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 :(…", "migration_0005_not_enough_space": "Laissez suffisamment d’espace disponible dans {path} pour exécuter la migration.", "service_description_php7.0-fpm": "Exécute des applications écrites en PHP avec NGINX", "users_available": "Liste des utilisateurs disponibles :", @@ -380,7 +380,7 @@ "migration_0008_root": "- Vous ne pourrez pas vous connecter en tant que root via SSH. Au lieu de cela, vous devrez utiliser l’utilisateur admin ;", "migration_0008_dsa": "- La clé DSA sera désactivée. Par conséquent, il se peut que vous ayez besoin d’invalider un avertissement effrayant de votre client SSH afin de revérifier l’empreinte de votre serveur ;", "migration_0008_warning": "Si vous comprenez ces avertissements et souhaitez que YunoHost écrase votre configuration actuelle, exécutez la migration. Sinon, vous pouvez également ignorer la migration, bien que cela ne soit pas recommandé.", - "migration_0008_no_warning": "Remplacer votre configuration SSH devrait être sûr, bien que cela ne puisse pas être promis! Exécutez la migration pour la remplacer. Sinon, vous pouvez également ignorer la migration, bien que cela ne soit pas recommandé.", + "migration_0008_no_warning": "Remplacer votre configuration SSH devrait être sûr, bien que cela ne puisse pas être promis ! Exécutez la migration pour la remplacer. Sinon, vous pouvez également ignorer la migration, bien que cela ne soit pas recommandé.", "migrations_success": "Migration {number} {name} réussie !", "pattern_password_app": "Désolé, les mots de passe ne peuvent pas contenir les caractères suivants : {forbidden_chars}", "root_password_replaced_by_admin_password": "Votre mot de passe root a été remplacé par votre mot de passe administrateur.", @@ -391,7 +391,7 @@ "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`.", - "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).", + "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 '{}'", "migration_0009_not_needed": "Cette migration semble avoir déjà été jouée ? On l’ignore.", @@ -405,7 +405,7 @@ "regenconf_now_managed_by_yunohost": "Le fichier de configuration '{conf}' est maintenant géré par YunoHost (catégorie {category}).", "regenconf_up_to_date": "La configuration est déjà à jour pour la catégorie '{category}'", "already_up_to_date": "Il n’y a rien à faire ! Tout est déjà à jour !", - "global_settings_setting_security_nginx_compatibility": "Compatibilité versus compromis sécuritaire pour le serveur web nginx. Affecte les cryptogrammes (et d’autres aspects liés à la sécurité)", + "global_settings_setting_security_nginx_compatibility": "Compatibilité versus compromis sécuritaire pour le serveur web Nginx. Affecte les cryptogrammes (et d’autres aspects liés à la sécurité)", "global_settings_setting_security_ssh_compatibility": "Compatibilité versus compromis sécuritaire pour le serveur SSH. Affecte les cryptogrammes (et d’autres aspects liés à la sécurité)", "global_settings_setting_security_postfix_compatibility": "Compatibilité versus compromis sécuritaire pour le serveur Postfix. Affecte les cryptogrammes (et d’autres aspects liés à la sécurité)", "migration_description_0009_decouple_regenconf_from_services": "Dissocier le mécanisme « regen-conf » des services", @@ -413,20 +413,20 @@ "regenconf_file_kept_back": "Le fichier de configuration '{conf}' devait être supprimé par « regen-conf » (catégorie {category}) mais a été conservé.", "regenconf_updated": "La configuration a été mise à jour pour '{category}'", "regenconf_would_be_updated": "La configuration aurait dû être mise à jour pour la catégorie '{category}'", - "regenconf_dry_pending_applying": "Vérification de la configuration en attente qui aurait été appliquée pour la catégorie '{category}' …", + "regenconf_dry_pending_applying": "Vérification de la configuration en attente qui aurait été appliquée pour la catégorie '{category}'…", "regenconf_failed": "Impossible de régénérer la configuration pour la ou les catégorie(s) : '{categories}'", "regenconf_pending_applying": "Applique la configuration en attente pour la catégorie '{category}' …", "service_regen_conf_is_deprecated": "'yunohost service regen-conf' est obsolète ! Veuillez plutôt utiliser 'yunohost tools regen-conf' à la place.", - "tools_upgrade_at_least_one": "Veuillez spécifier '--apps' OU '--system'", + "tools_upgrade_at_least_one": "Veuillez spécifier '--apps' ou '--system'", "tools_upgrade_cant_both": "Impossible de mettre à niveau le système et les applications en même temps", "tools_upgrade_cant_hold_critical_packages": "Impossibilité de maintenir les paquets critiques…", - "tools_upgrade_regular_packages": "Mise à jour des paquets du système (non liés a YunoHost) …", + "tools_upgrade_regular_packages": "Mise à jour des paquets du système (non liés a YunoHost)…", "tools_upgrade_regular_packages_failed": "Impossible de mettre à jour les paquets suivants : {packages_list}", - "tools_upgrade_special_packages": "Mise à jour des paquets 'spécifiques' (liés a YunoHost) …", + "tools_upgrade_special_packages": "Mise à jour des paquets 'spécifiques' (liés a YunoHost)…", "tools_upgrade_special_packages_completed": "La mise à jour des paquets de YunoHost est finie !\nPressez [Entrée] pour revenir à la ligne de commande", "dpkg_lock_not_available": "Cette commande ne peut pas être exécutée pour le moment car un autre programme semble utiliser le verrou de dpkg (le gestionnaire de package système)", "tools_upgrade_cant_unhold_critical_packages": "Impossible de conserver les paquets critiques…", - "tools_upgrade_special_packages_explanation": "La mise à niveau spéciale se poursuivra en arrière-plan. Veuillez ne pas lancer d'autres actions sur votre serveur pendant les 10 prochaines minutes (selon la vitesse du matériel). Après cela, vous devrez peut-être vous reconnecter à l'administrateur Web. Le journal de mise à niveau sera disponible dans Outils → Journal (dans le webadmin) ou en utilisant la «liste des journaux yunohost» (à partir de la ligne de commande).", + "tools_upgrade_special_packages_explanation": "La mise à niveau spéciale se poursuivra en arrière-plan. Veuillez ne pas lancer d'autres actions sur votre serveur pendant les 10 prochaines minutes (selon la vitesse du matériel). Après cela, vous devrez peut-être vous reconnecter à l'administrateur Web. Le journal de mise à niveau sera disponible dans Outils → Journal (dans le webadmin) ou en utilisant la « liste des journaux yunohost » (à partir de la ligne de commande).", "update_apt_cache_failed": "Impossible de mettre à jour le cache APT (gestionnaire de paquets Debian). Voici un extrait du fichier sources.list qui pourrait vous aider à identifier les lignes problématiques :\n{sourceslist}", "update_apt_cache_warning": "Des erreurs se sont produites lors de la mise à jour du cache APT (gestionnaire de paquets Debian). Voici un extrait des lignes du fichier sources.list qui pourrait vous aider à identifier les lignes problématiques :\n{sourceslist}", "backup_permission": "Permission de sauvegarde pour l’application {app:s}", @@ -435,8 +435,8 @@ "group_unknown": "Le groupe {group:s} est inconnu", "group_updated": "Le groupe '{group}' a été mis à jour", "group_update_failed": "La mise à jour du groupe '{group}' a échoué : {error}", - "group_creation_failed": "Échec de la création du groupe '{group}': {error}", - "group_deletion_failed": "Échec de la suppression du groupe '{group}': {error}", + "group_creation_failed": "Échec de la création du groupe '{group}' : {error}", + "group_deletion_failed": "Échec de la suppression du groupe '{group}' : {error}", "log_user_group_delete": "Supprimer le groupe '{}'", "log_user_group_update": "Mettre à jour '{}' pour le groupe", "mailbox_disabled": "La boîte aux lettres est désactivée pour l’utilisateur {user:s}", @@ -449,23 +449,23 @@ "migrations_pending_cant_rerun": "Ces migrations étant toujours en attente, vous ne pouvez pas les exécuter à nouveau : {ids}", "migration_description_0012_postgresql_password_to_md5_authentication": "Forcer l’authentification PostgreSQL à utiliser MD5 pour les connexions locales", "migrations_exclusive_options": "'auto', '--skip' et '--force-rerun' sont des options mutuellement exclusives.", - "migrations_not_pending_cant_skip": "Ces migrations ne sont pas en attente et ne peuvent donc pas être ignorées: {ids}", - "migration_0011_can_not_backup_before_migration": "La sauvegarde du système n’a pas pu être terminée avant l’échec de la migration. Erreur: {error:s}", - "migration_0011_migrate_permission": "Migration des autorisations des paramètres des applications vers LDAP …", + "migrations_not_pending_cant_skip": "Ces migrations ne sont pas en attente et ne peuvent donc pas être ignorées : {ids}", + "migration_0011_can_not_backup_before_migration": "La sauvegarde du système n’a pas pu être terminée avant l’échec de la migration. Erreur : {error:s}", + "migration_0011_migrate_permission": "Migration des autorisations des paramètres des applications vers LDAP…", "migration_0011_migration_failed_trying_to_rollback": "La migration a échoué… Tentative de restauration du système.", "migration_0011_rollback_success": "Système restauré.", "migration_0011_update_LDAP_database": "Mise à jour de la base de données LDAP…", "migration_0011_backup_before_migration": "Création d’une sauvegarde des paramètres de la base de données LDAP et des applications avant la migration.", "permission_not_found": "Autorisation '{permission:s}' introuvable", - "permission_update_failed": "Impossible de mettre à jour l'autorisation '{permission}': {error}", + "permission_update_failed": "Impossible de mettre à jour l’autorisation '{permission}' : {error}", "permission_updated": "Permission '{permission:s}' mise à jour", "permission_update_nothing_to_do": "Aucune autorisation pour mettre à jour", - "dyndns_provider_unreachable": "Impossible d’atteindre le fournisseur DynDNS {provider}: votre YunoHost n’est pas correctement connecté à Internet ou le serveur Dynette est en panne.", - "migration_0011_update_LDAP_schema": "Mise à jour du schéma LDAP …", + "dyndns_provider_unreachable": "Impossible d’atteindre le fournisseur DynDNS {provider} : votre YunoHost n’est pas correctement connecté à Internet ou le serveur Dynette est en panne.", + "migration_0011_update_LDAP_schema": "Mise à jour du schéma LDAP…", "migrations_already_ran": "Ces migrations sont déjà effectuées : {ids}", "migrations_dependencies_not_satisfied": "Exécutez ces migrations : '{dependencies_id}', avant migration {id}.", - "migrations_failed_to_load_migration": "Impossible de charger la migration {id}: {error}", - "migrations_running_forward": "Exécution de la migration {id} …", + "migrations_failed_to_load_migration": "Impossible de charger la migration {id} : {error}", + "migrations_running_forward": "Exécution de la migration {id}…", "migrations_success_forward": "Migration {id} terminée", "operation_interrupted": "L’opération a été interrompue manuellement ?", "permission_already_exist": "L’autorisation '{permission}' existe déjà", @@ -474,7 +474,7 @@ "permission_deleted": "Permission '{permission:s}' supprimée", "permission_deletion_failed": "Impossible de supprimer la permission '{permission}' : {error}", "migration_description_0011_setup_group_permission": "Initialiser les groupes d’utilisateurs et autorisations pour les applications et les services", - "migration_0011_LDAP_update_failed": "Impossible de mettre à jour LDAP. Erreur: {error:s}", + "migration_0011_LDAP_update_failed": "Impossible de mettre à jour LDAP. Erreur : {error:s}", "group_already_exist": "Le groupe {group} existe déjà", "group_already_exist_on_system": "Le groupe {group} existe déjà dans les groupes système", "group_cannot_be_deleted": "Le groupe {group} ne peut pas être supprimé manuellement.", @@ -485,20 +485,20 @@ "log_user_group_create": "Créer '{}' groupe", "log_user_permission_update": "Mise à jour des accès pour la permission '{}'", "log_user_permission_reset": "Réinitialiser la permission '{}'", - "migration_0011_failed_to_remove_stale_object": "Impossible de supprimer un objet périmé {dn}: {error}", - "permission_already_allowed": "Le groupe '{group}' a déjà l'autorisation '{permission}' activée", - "permission_already_disallowed": "Le groupe '{group}' a déjà l'autorisation '{permission}' désactivé", - "permission_cannot_remove_main": "Supprimer une autorisation principale n'est pas autorisé", - "user_already_exists": "L'utilisateur '{user}' existe déjà", - "app_full_domain_unavailable": "Désolé, cette application doit être installée sur un domaine qui lui est propre, mais d'autres applications sont déjà installées sur le domaine '{domain}'. Vous pouvez utiliser un sous-domaine dédié à cette application à la place.", - "group_cannot_edit_all_users": "Le groupe 'all_users' ne peut pas être édité manuellement. C'est un groupe spécial destiné à contenir tous les utilisateurs enregistrés dans YunoHost", - "group_cannot_edit_visitors": "Le groupe 'visiteurs' ne peut pas être édité manuellement. C'est un groupe spécial représentant les visiteurs anonymes", - "group_cannot_edit_primary_group": "Le groupe '{group}' ne peut pas être édité manuellement. C'est le groupe principal destiné à ne contenir qu'un utilisateur spécifique.", - "log_permission_url": "Mise à jour de l'URL associée à l'autorisation '{}'", + "migration_0011_failed_to_remove_stale_object": "Impossible de supprimer un objet périmé {dn} : {error}", + "permission_already_allowed": "Le groupe '{group}' a déjà l’autorisation '{permission}' activée", + "permission_already_disallowed": "Le groupe '{group}' a déjà l’autorisation '{permission}' désactivé", + "permission_cannot_remove_main": "Supprimer une autorisation principale n’est pas autorisé", + "user_already_exists": "L’utilisateur '{user}' existe déjà", + "app_full_domain_unavailable": "Désolé, cette application doit être installée sur un domaine qui lui est propre, mais d’autres applications sont déjà installées sur le domaine '{domain}'. Vous pouvez utiliser un sous-domaine dédié à cette application à la place.", + "group_cannot_edit_all_users": "Le groupe 'all_users' ne peut pas être édité manuellement. C’est un groupe spécial destiné à contenir tous les utilisateurs enregistrés dans YunoHost", + "group_cannot_edit_visitors": "Le groupe 'visiteurs' ne peut pas être édité manuellement. C’est un groupe spécial représentant les visiteurs anonymes", + "group_cannot_edit_primary_group": "Le groupe '{group}' ne peut pas être édité manuellement. C’est le groupe principal destiné à ne contenir qu’un utilisateur spécifique.", + "log_permission_url": "Mise à jour de l’URL associée à l’autorisation '{}'", "migration_0011_slapd_config_will_be_overwritten": "Il semble que vous ayez modifié manuellement la configuration de slapd. Pour cette migration critique, YunoHost doit forcer la mise à jour de la configuration de slapd. Les fichiers originaux seront sauvegardés dans {conf_backup_folder}.", "permission_already_up_to_date": "L’autorisation n’a pas été mise à jour car les demandes d’ajout/suppression correspondent déjà à l’état actuel.", "permission_currently_allowed_for_all_users": "Cette autorisation est actuellement accordée à tous les utilisateurs en plus des autres groupes. Vous voudrez probablement soit supprimer l’autorisation 'all_users', soit supprimer les autres groupes auxquels il est actuellement autorisé.", - "app_install_failed": "Impossible d’installer {app}: {error}", + "app_install_failed": "Impossible d’installer {app} : {error}", "app_install_script_failed": "Une erreur est survenue dans le script d’installation de l’application", "permission_require_account": "Permission {permission} n’a de sens que pour les utilisateurs ayant un compte et ne peut donc pas être activé pour les visiteurs.", "app_remove_after_failed_install": "Supprimer l’application après l’échec de l’installation …", @@ -509,23 +509,23 @@ "diagnosis_ip_not_connected_at_all": "Le serveur ne semble pas du tout connecté à Internet !?", "diagnosis_ip_weird_resolvconf": "La résolution DNS semble fonctionner, mais il semble que vous utilisez un /etc/resolv.conf personnalisé.", "diagnosis_ip_weird_resolvconf_details": "Le fichier /etc/resolv.conf doit être un lien symbolique vers /etc/resolvconf/run/resolv.conf lui-même pointant vers 127.0.0.1 (dnsmasq). Si vous souhaitez configurer manuellement les résolveurs DNS, veuillez modifier /etc/resolv.dnsmasq.conf.", - "diagnosis_dns_missing_record": "Selon la configuration DNS recommandée, vous devez ajouter un enregistrement DNS\nType: {type}\nNom: {name}\nValeur {value}", - "diagnosis_diskusage_ok": "L'espace de stockage {mountpoint} (sur l'appareil {device}) a encore {libre} ({free_percent}%) espace restant (sur {total}) !", + "diagnosis_dns_missing_record": "Selon la configuration DNS recommandée, vous devez ajouter un enregistrement DNS\nType : {type}\nNom : {name}\nValeur : {value}", + "diagnosis_diskusage_ok": "L’espace de stockage {mountpoint} (sur l’appareil {device}) a encore {libre} ({free_percent}%) espace restant (sur {total}) !", "diagnosis_ram_ok": "Le système dispose encore de {available} ({available_percent}%) de RAM sur {total}.", "diagnosis_regenconf_allgood": "Tous les fichiers de configuration sont conformes à la configuration recommandée !", "diagnosis_security_vulnerable_to_meltdown": "Vous semblez vulnérable à la vulnérabilité de sécurité critique de Meltdown", "diagnosis_basesystem_host": "Le serveur utilise Debian {debian_version}", "diagnosis_basesystem_kernel": "Le serveur utilise le noyau Linux {kernel_version}", - "diagnosis_basesystem_ynh_single_version": "{package} version: {version} ({repo})", + "diagnosis_basesystem_ynh_single_version": "{package} version : {version} ({repo})", "diagnosis_basesystem_ynh_main_version": "Le serveur utilise YunoHost {main_version} ({repo})", - "diagnosis_basesystem_ynh_inconsistent_versions": "Vous exécutez des versions incohérentes des packages YunoHost ... très probablement en raison d'une mise à niveau échouée ou partielle.", + "diagnosis_basesystem_ynh_inconsistent_versions": "Vous exécutez des versions incohérentes des packages YunoHost ... très probablement en raison d’une mise à niveau échouée ou partielle.", "diagnosis_display_tip_cli": "Vous pouvez exécuter 'yunohost diagnosis show --issues' pour afficher les problèmes détectés.", "diagnosis_failed_for_category": "Échec du diagnostic pour la catégorie '{category}': {error}", "diagnosis_cache_still_valid": "(Le cache est encore valide pour le diagnostic {category}. Il ne sera pas re-diagnostiqué pour le moment!)", "diagnosis_ignored_issues": "(+ {nb_ignored} questions ignorée(s))", "diagnosis_found_warnings": "Trouvé {warnings} objet(s) pouvant être amélioré(s) pour {category}.", "diagnosis_everything_ok": "Tout semble bien pour {category} !", - "diagnosis_failed": "Échec de la récupération du résultat du diagnostic pour la catégorie '{category}': {error}", + "diagnosis_failed": "Échec de la récupération du résultat du diagnostic pour la catégorie '{category}' : {error}", "diagnosis_ip_connected_ipv4": "Le serveur est connecté à Internet en IPv4 !", "diagnosis_ip_no_ipv4": "Le serveur ne dispose pas d’une adresse IPv4.", "diagnosis_ip_connected_ipv6": "Le serveur est connecté à Internet en IPv6 !", @@ -535,9 +535,9 @@ "diagnosis_ip_broken_resolvconf": "La résolution du nom de domaine semble être rompue sur votre serveur, ce qui semble lié au fait que /etc/resolv.conf ne pointe pas sur 127.0.0.1.", "diagnosis_dns_good_conf": "Les enregistrements DNS sont correctement configurés pour le domaine {domain} (catégorie {category})", "diagnosis_dns_bad_conf": "Certains enregistrements DNS sont manquants ou incorrects pour le domaine {domain} (catégorie {category})", - "diagnosis_dns_discrepancy": "L’enregistrement DNS de type {type} et nom {name} ne correspond pas à la configuration recommandée.\nValeur actuelle: {current}\nValeur attendue: {value}", + "diagnosis_dns_discrepancy": "L’enregistrement DNS de type {type} et nom {name} ne correspond pas à la configuration recommandée.\nValeur actuelle : {current}\nValeur attendue : {value}", "diagnosis_services_bad_status": "Le service {service} est {status} :-(", - "diagnosis_diskusage_verylow": "L'espace de stockage {mountpoint} (sur l'appareil {device} ) ne dispose que de {free} ({free_percent}%) espace restant (sur {total}). Vous devriez vraiment envisager de nettoyer de l'espace !", + "diagnosis_diskusage_verylow": "L'espace de stockage {mountpoint} (sur l’appareil {device} ) ne dispose que de {free} ({free_percent}%) espace restant (sur {total}). Vous devriez vraiment envisager de nettoyer de l’espace !", "diagnosis_diskusage_low": "L'espace de stockage {mountpoint} (sur l'appareil {device}) ne dispose que de {free} ({free_percent}%) espace restant (sur {total}). Faites attention.", "diagnosis_ram_verylow": "Le système ne dispose plus que de {available} ({available_percent}%)! (sur {total})", "diagnosis_ram_low": "Le système n’a plus de {available} ({available_percent}%) RAM sur {total}. Faites attention.", @@ -546,10 +546,10 @@ "diagnosis_swap_ok": "Le système dispose de {total} de swap !", "diagnosis_regenconf_manually_modified": "Le fichier de configuration {file} semble avoir été modifié manuellement.", "diagnosis_regenconf_manually_modified_debian": "Le fichier de configuration {file} a été modifié manuellement par rapport à celui par défaut de Debian.", - "diagnosis_regenconf_manually_modified_details": "C'est probablement OK si vous savez ce que vous faites! YunoHost cessera de mettre à jour ce fichier automatiquement ... Mais attention, les mises à jour de YunoHost pourraient contenir d'importantes modifications recommandées. Si vous le souhaitez, vous pouvez inspecter les différences avec les outils yunohost regen-conf {category} --dry-run --with-diff et forcer la réinitialisation à la configuration recommandée avec les outils yunohost regen-conf {category} --force ", + "diagnosis_regenconf_manually_modified_details": "C’est probablement OK si vous savez ce que vous faites ! YunoHost cessera de mettre à jour ce fichier automatiquement ... Mais attention, les mises à jour de YunoHost pourraient contenir d’importantes modifications recommandées. Si vous le souhaitez, vous pouvez inspecter les différences avec les outils yunohost regen-conf {category} --dry-run --with-diff et forcer la réinitialisation à la configuration recommandée avec les outils yunohost regen-conf {category} --force ", "diagnosis_regenconf_manually_modified_debian_details": "Cela peut probablement être OK, mais il faut garder un œil dessus …", "apps_catalog_init_success": "Système de catalogue d’applications initialisé !", - "apps_catalog_failed_to_download": "Impossible de télécharger le catalogue des applications {apps_catalog}:{error}", + "apps_catalog_failed_to_download": "Impossible de télécharger le catalogue des applications {apps_catalog} : {error}", "diagnosis_mail_outgoing_port_25_blocked": "Le port sortant 25 semble être bloqué. Vous devriez essayer de le débloquer dans le panneau de configuration de votre fournisseur de services Internet (ou hébergeur). En attendant, le serveur ne pourra pas envoyer de courrier électronique à d’autres serveurs.", "domain_cannot_remove_main_add_new_one": "Vous ne pouvez pas supprimer '{domain:s}' car il s’agit du domaine principal et de votre seul domaine. Vous devez d’abord ajouter un autre domaine à l’aide de 'yunohost domain add ', puis définir comme domaine principal à l’aide de 'yunohost domain main-domain -n ' et vous pouvez ensuite supprimer le domaine '{domain:s}' à l’aide de 'yunohost domain remove {domain:s}'.'", "diagnosis_security_vulnerable_to_meltdown_details": "Pour résoudre ce problème, vous devez mettre à niveau votre système et redémarrer pour charger le nouveau noyau Linux (ou contacter votre fournisseur de serveur si cela ne fonctionne pas). Voir https://meltdownattack.com/ pour plus d’informations.", @@ -562,19 +562,19 @@ "diagnosis_description_regenconf": "Configurations système", "diagnosis_description_security": "Contrôles de sécurité", "diagnosis_ports_could_not_diagnose": "Impossible de diagnostiquer si les ports sont accessibles de l'extérieur.", - "diagnosis_ports_could_not_diagnose_details": "Erreur: {error}", - "apps_catalog_updating": "Mise à jour du catalogue d'applications…", + "diagnosis_ports_could_not_diagnose_details": "Erreur : {error}", + "apps_catalog_updating": "Mise à jour du catalogue d’applications…", "apps_catalog_obsolete_cache": "Le cache du catalogue d'applications est vide ou obsolète.", "apps_catalog_update_success": "Le catalogue des applications a été mis à jour !", "diagnosis_mail_ougoing_port_25_ok": "Le port sortant 25 n’est pas bloqué et le courrier électronique peut être envoyé à d’autres serveurs.", - "diagnosis_description_mail": "Email", + "diagnosis_description_mail": "E-mail", "diagnosis_ports_unreachable": "Le port {port} n’est pas accessible de l’extérieur.", "diagnosis_ports_ok": "Le port {port} est accessible de l’extérieur.", "diagnosis_http_could_not_diagnose": "Impossible de diagnostiquer si le domaine est accessible de l’extérieur.", - "diagnosis_http_could_not_diagnose_details": "Erreur: {error}", + "diagnosis_http_could_not_diagnose_details": "Erreur : {error}", "diagnosis_http_ok": "Le domaine {domain} est accessible en HTTP depuis l’extérieur.", "diagnosis_http_unreachable": "Le domaine {domain} est inaccessible en HTTP depuis l’extérieur.", - "diagnosis_unknown_categories": "Les catégories suivantes sont inconnues: {categories}", + "diagnosis_unknown_categories": "Les catégories suivantes sont inconnues : {categories}", "migration_description_0013_futureproof_apps_catalog_system": "Migrer vers le nouveau système de catalogue d’applications à l’épreuve du temps", "app_upgrade_script_failed": "Une erreur s’est produite durant l’exécution du script de mise à niveau de l’application", "migration_description_0014_remove_app_status_json": "Supprimer les anciens fichiers d’application status.json", @@ -584,10 +584,10 @@ "diagnosis_ports_forwarding_tip": "Pour résoudre ce problème, vous devez probablement configurer la redirection de port sur votre routeur Internet comme décrit dans https://yunohost.org/isp_box_config ", "diagnosis_http_connection_error": "Erreur de connexion : impossible de se connecter au domaine demandé, il est probablement injoignable.", "diagnosis_no_cache": "Pas encore de cache de diagnostique pour la catégorie « {category} »", - "yunohost_postinstall_end_tip": "La post-installation terminée! Pour finaliser votre configuration, il est recommendé de :\n - ajouter un premier utilisateur depuis la section \"Utilisateurs\" de l’interface web (ou \"yunohost user create \" en ligne de commande);\n - diagnostiquer les potentiels problèmes dans la section \"Diagnostic\" de l'interface web (ou \"yunohost diagnosis run\" en ligne de commande);\n - lire les parties \"Finalisation de votre configuration\" et \"Découverte de Yunohost\" dans le guide de l’administrateur: https://yunohost.org/admindoc.", + "yunohost_postinstall_end_tip": "La post-installation terminée! Pour finaliser votre configuration, il est recommandé de :\n - ajouter un premier utilisateur depuis la section \"Utilisateurs\" de l’interface web (ou \"yunohost user create \" en ligne de commande) ;\n - diagnostiquer les potentiels problèmes dans la section \"Diagnostic\" de l'interface web (ou \"yunohost diagnosis run\" en ligne de commande) ;\n - lire les parties \"Finalisation de votre configuration\" et \"Découverte de YunoHost\" dans le guide de l’administrateur: https://yunohost.org/admindoc.", "diagnosis_services_bad_status_tip": "Vous pouvez essayer de redémarrer le service, et si cela ne fonctionne pas, consultez les journaux de service dans le webadmin (à partir de la ligne de commande, vous pouvez le faire avec yunohost service restart {service} et yunohost service log {service} ).", - "diagnosis_http_bad_status_code": "Le système de diagnostique n’a pas réussi à contacter votre serveur. Il se peut qu’une autre machine réponde à la place de votre serveur. Vérifiez que le port 80 est correctement redirigé, que votre configuration nginx est à jour et qu’un reverse-proxy n’interfère pas.", - "diagnosis_http_timeout": "Expiration du délai en essayant de contacter votre serveur de l’extérieur. Il semble être inaccessible. Vérifiez que vous transférez correctement le port 80, que nginx est en cours d’exécution et qu’un pare-feu n’interfère pas.", + "diagnosis_http_bad_status_code": "Le système de diagnostique n’a pas réussi à contacter votre serveur. Il se peut qu’une autre machine réponde à la place de votre serveur. Vérifiez que le port 80 est correctement redirigé, que votre configuration Nginx est à jour et qu’un reverse-proxy n’interfère pas.", + "diagnosis_http_timeout": "Expiration du délai en essayant de contacter votre serveur de l’extérieur. Il semble être inaccessible. Vérifiez que vous transférez correctement le port 80, que Nginx est en cours d’exécution et qu’un pare-feu n’interfère pas.", "global_settings_setting_pop3_enabled": "Activer le protocole POP3 pour le serveur de messagerie", "log_app_action_run": "Lancer l’action de l’application '{}'", "log_app_config_show_panel": "Montrer le panneau de configuration de l’application '{}'", @@ -600,42 +600,42 @@ "certmanager_warning_subdomain_dns_record": "Le sous-domaine '{subdomain:s}' ne résout pas vers la même adresse IP que '{domain:s}'. Certaines fonctionnalités seront indisponibles tant que vous n’aurez pas corrigé cela et regénéré le certificat.", "domain_cannot_add_xmpp_upload": "Vous ne pouvez pas ajouter de domaine commençant par 'xmpp-upload.'. Ce type de nom est réservé à la fonctionnalité d’upload XMPP intégrée dans YunoHost.", "diagnosis_mail_outgoing_port_25_ok": "Le serveur de messagerie SMTP peut envoyer des e-mails (le port sortant 25 n'est pas bloqué).", - "diagnosis_mail_outgoing_port_25_blocked_details": "Vous devez d'abord essayer de débloquer le port sortant 25 dans votre interface de routeur Internet ou votre interface d'hébergement. (Certains hébergeurs peuvent vous demander de leur envoyer un ticket d'assistance pour cela).", + "diagnosis_mail_outgoing_port_25_blocked_details": "Vous devez d’abord essayer de débloquer le port sortant 25 dans votre interface de routeur Internet ou votre interface d’hébergement. (Certains hébergeurs peuvent vous demander de leur envoyer un ticket d'assistance pour cela).", "diagnosis_mail_ehlo_bad_answer": "Un service non SMTP a répondu sur le port 25 sur IPv {ipversion}", "diagnosis_mail_ehlo_bad_answer_details": "Cela peut être dû à un autre répondeur au lieu de votre serveur.", "diagnosis_mail_ehlo_wrong": "Un autre serveur de messagerie SMTP répond sur IPv{ipversion}. Il ne sera probablement pas en mesure de recevoir des e-mails.", - "diagnosis_mail_ehlo_could_not_diagnose": "Impossible de diagnostiquer si le serveur de messagerie postfix est accessible de l'extérieur pour IPv {ipversion}.", - "diagnosis_mail_ehlo_could_not_diagnose_details": "Erreur: {error}", - "diagnosis_mail_fcrdns_dns_missing": "Aucun DNS inverse n'est défini dans IPv {ipversion}. Certains e-mails peuvent ne pas être livrés ou être signalés comme spam.", + "diagnosis_mail_ehlo_could_not_diagnose": "Impossible de diagnostiquer si le serveur de messagerie postfix est accessible de l’extérieur pour IPv {ipversion}.", + "diagnosis_mail_ehlo_could_not_diagnose_details": "Erreur : {error}", + "diagnosis_mail_fcrdns_dns_missing": "Aucun DNS inverse n’est défini dans IPv {ipversion}. Certains e-mails peuvent ne pas être livrés ou être signalés comme spam.", "diagnosis_mail_fcrdns_ok": "Votre DNS inversé est correctement configuré !", - "diagnosis_mail_fcrdns_nok_details": "Vous devez d'abord essayer de configurer le DNS inverse avec {ehlo_domain} dans votre interface de routeur Internet ou votre interface d'hébergement. (Certains hébergeurs peuvent vous demander de leur envoyer un ticket d'assistance pour cela).", + "diagnosis_mail_fcrdns_nok_details": "Vous devez d’abord essayer de configurer le DNS inverse avec {ehlo_domain} dans votre interface de routeur Internet ou votre interface d’hébergement. (Certains hébergeurs peuvent vous demander de leur envoyer un ticket d'assistance pour cela).", "diagnosis_mail_fcrdns_different_from_ehlo_domain": "Le DNS inverse n'est pas correctement configuré dans IPv {ipversion}. Certains e-mails peuvent ne pas être livrés ou être signalés comme spam.", "diagnosis_mail_blacklist_ok": "Les adresses IP et les domaines utilisés par ce serveur ne semblent pas être sur liste noire", - "diagnosis_mail_blacklist_reason": "La raison de la liste noire est: {reason}", - "diagnosis_mail_blacklist_website": "Après avoir identifié pourquoi vous êtes répertorié et corrigé, n'hésitez pas à demander la radiation sur {blacklist_website}", + "diagnosis_mail_blacklist_reason": "La raison de la liste noire est : {reason}", + "diagnosis_mail_blacklist_website": "Après avoir identifié pourquoi vous êtes répertorié et corrigé, n’hésitez pas à demander la radiation sur {blacklist_website}", "diagnosis_mail_queue_ok": "{nb_pending} e-mails en attente dans les files d'attente de messagerie", - "diagnosis_mail_queue_unavailable_details": "Erreur: {error}", - "diagnosis_mail_queue_too_big": "Trop d'e-mails en attente dans la file d'attente ({nb_pending} e-mails)", - "global_settings_setting_smtp_allow_ipv6": "Autoriser l'utilisation d'IPv6 pour recevoir et envoyer du courrier", - "diagnosis_security_all_good": "Aucune vulnérabilité de sécurité critique n'a été trouvée.", - "diagnosis_display_tip": "Pour voir les problèmes détectés, vous pouvez accéder à la section Diagnostic du webadmin ou exécuter «yunohost diagnostic show --issues» à partir de la ligne de commande.", - "diagnosis_ip_global": "IP globale: {global} ", - "diagnosis_ip_local": "IP locale: {local} ", - "diagnosis_dns_point_to_doc": "Veuillez consulter la documentation à https://yunohost.org/dns_config si vous avez besoin d'aide pour configurer les enregistrements DNS.", - "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Certains fournisseurs ne vous laisseront pas débloquer le port sortant 25 parce qu'ils ne se soucient pas de la neutralité du Net.
- Certains d'entre eux offrent l'alternative de en utilisant un relais de serveur de messagerie bien que cela implique que le relais sera en mesure d'espionner votre trafic de messagerie.
- Une alternative respectueuse de la vie privée consiste à utiliser un VPN * avec une IP publique dédiée * pour contourner ce type des limites. Voir https://yunohost.org/#/vpn_advantage
- Vous pouvez également envisager de passer à un fournisseur plus respectueux de la neutralité du net ", + "diagnosis_mail_queue_unavailable_details": "Erreur : {error}", + "diagnosis_mail_queue_too_big": "Trop d’e-mails en attente dans la file d'attente ({nb_pending} e-mails)", + "global_settings_setting_smtp_allow_ipv6": "Autoriser l'utilisation d’IPv6 pour recevoir et envoyer du courrier", + "diagnosis_security_all_good": "Aucune vulnérabilité de sécurité critique n’a été trouvée.", + "diagnosis_display_tip": "Pour voir les problèmes détectés, vous pouvez accéder à la section Diagnostic du webadmin ou exécuter « yunohost diagnostic show --issues » à partir de la ligne de commande.", + "diagnosis_ip_global": "IP globale : {global} ", + "diagnosis_ip_local": "IP locale : {local} ", + "diagnosis_dns_point_to_doc": "Veuillez consulter la documentation à https://yunohost.org/dns_config si vous avez besoin d’aide pour configurer les enregistrements DNS.", + "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Certains fournisseurs ne vous laisseront pas débloquer le port sortant 25 parce qu’ils ne se soucient pas de la neutralité du Net.
- Certains d’entre eux offrent l’alternative de en utilisant un relais de serveur de messagerie bien que cela implique que le relais sera en mesure d’espionner votre trafic de messagerie.
- Une alternative respectueuse de la vie privée consiste à utiliser un VPN * avec une IP publique dédiée * pour contourner ce type des limites. Voir https://yunohost.org/#/vpn_advantage
- Vous pouvez également envisager de passer à un fournisseur plus respectueux de la neutralité du net ", "diagnosis_mail_ehlo_ok": "Le serveur de messagerie SMTP est accessible de l'extérieur et peut donc recevoir des e-mails !", - "diagnosis_mail_ehlo_unreachable": "Le serveur de messagerie SMTP est inaccessible de l'extérieur sur IPv {ipversion}. Il ne pourra pas recevoir d'e-mails.", + "diagnosis_mail_ehlo_unreachable": "Le serveur de messagerie SMTP est inaccessible de l’extérieur sur IPv {ipversion}. Il ne pourra pas recevoir d’e-mails.", "diagnosis_mail_ehlo_unreachable_details": "Impossible d'ouvrir une connexion sur le port 25 à votre serveur dans IPv {ipversion}. Il semble inaccessible.
1. La cause la plus courante de ce problème est que le port 25 n'est pas correctement transmis à votre serveur .
2. Vous devez également vous assurer que le suffixe de service est en cours d'exécution.
3. Sur les configurations plus complexes: assurez-vous qu'aucun pare-feu ou proxy inverse n'interfère.", - "diagnosis_mail_ehlo_wrong_details": "L'EHLO reçu par le diagnostiqueur distant dans IPv {ipversion} est différent du domaine de votre serveur.
EHLO reçu: {bad_ehlo}
Attendu: {right_ehlo}
La cause la plus courante ce problème est que le port 25 n'est pas correctement transmis à votre serveur . Vous pouvez également vous assurer qu'aucun pare-feu ou proxy inverse n'interfère.", - "diagnosis_mail_fcrdns_nok_alternatives_4": "Certains fournisseurs ne vous laisseront pas configurer votre DNS inversé (ou leur fonctionnalité pourrait être cassée …). Si vous rencontrez des problèmes à cause de cela, envisagez les solutions suivantes:
- Certains FAI fournissent l’alternative de à l’aide d’un relais de serveur de messagerie bien que cela implique que le relais pourra espionner votre trafic de messagerie.
- Une alternative respectueuse de la vie privée consiste à utiliser un VPN * avec une IP publique dédiée * pour contourner ce type de limites. Voir
https://yunohost.org/#/vpn_advantage
- Enfin, il est également possible de changement de fournisseur ", + "diagnosis_mail_ehlo_wrong_details": "L’EHLO reçu par le diagnostiqueur distant dans IPv {ipversion} est différent du domaine de votre serveur.
EHLO reçu: {bad_ehlo}
Attendu : {right_ehlo}
La cause la plus courante ce problème est que le port 25 n’est pas correctement transmis à votre serveur . Vous pouvez également vous assurer qu’aucun pare-feu ou proxy inverse n’interfère.", + "diagnosis_mail_fcrdns_nok_alternatives_4": "Certains fournisseurs ne vous laisseront pas configurer votre DNS inversé (ou leur fonctionnalité pourrait être cassée …). Si vous rencontrez des problèmes à cause de cela, envisagez les solutions suivantes :
- Certains FAI fournissent l’alternative de à l’aide d’un relais de serveur de messagerie bien que cela implique que le relais pourra espionner votre trafic de messagerie.
- Une alternative respectueuse de la vie privée consiste à utiliser un VPN * avec une IP publique dédiée * pour contourner ce type de limites. Voir
https://yunohost.org/#/vpn_advantage
- Enfin, il est également possible de changement de fournisseur ", "diagnosis_mail_fcrdns_nok_alternatives_6": "Certains fournisseurs ne vous laisseront pas configurer votre DNS inversé (ou leur fonctionnalité pourrait être cassée ...). Si votre DNS inversé est correctement configuré pour IPv4, vous pouvez essayer de désactiver l'utilisation d'IPv6 lors de l'envoi d'e-mails en exécutant yunohost settings set smtp.allow_ipv6 -v off . Remarque: cette dernière solution signifie que vous ne pourrez pas envoyer ou recevoir de courriels des quelques serveurs IPv6 uniquement disponibles.", - "diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "DNS inverse actuel: {rdns_domain}
Valeur attendue: {ehlo_domain} ", + "diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "DNS inverse actuel : {rdns_domain}
Valeur attendue : {ehlo_domain} ", "diagnosis_mail_blacklist_listed_by": "Votre IP ou domaine {item} est sur liste noire sur {blacklist_name}", - "diagnosis_mail_queue_unavailable": "Impossible de consulter le nombre d'e-mails en attente dans la file d'attente", + "diagnosis_mail_queue_unavailable": "Impossible de consulter le nombre d’e-mails en attente dans la file d'attente", "diagnosis_ports_partially_unreachable": "Le port {port} n'est pas accessible de l'extérieur dans IPv {failed}.", - "diagnosis_http_hairpinning_issue": "Votre réseau local ne semble pas avoir activé l'épingle à cheveux.", + "diagnosis_http_hairpinning_issue": "Votre réseau local ne semble pas avoir activé l’épingle à cheveux.", "diagnosis_http_hairpinning_issue_details": "C'est probablement à cause de votre box/routeur ISP. 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 ?). 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 via 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 sur 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_partially_unreachable": "Le domaine {domain} semble inaccessible via 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 sur 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." } From 45bbd061904ba2d0900b14e7cfef042e0787cc50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quent=C3=AD?= Date: Thu, 23 Apr 2020 08:39:49 +0000 Subject: [PATCH 1113/3170] Translated using Weblate (Occitan) Currently translated at 60.0% (379 of 632 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/oc/ --- locales/oc.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/locales/oc.json b/locales/oc.json index 95f581851..cdefd0931 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -571,5 +571,12 @@ "global_settings_setting_pop3_enabled": "Activar lo protocòl POP3 pel servidor de corrièr", "diagnosis_diskusage_ok": "Lo lòc d’emmagazinatge {mountpoint} (sul periferic {device}) a encara {free} ({free_percent}%) de liure !", "diagnosis_swap_none": "Lo sistèma a pas cap de memòria d’escambi. Auriatz de considerar d’ajustar almens {recommended} d’escambi per evitar las situacions ont lo sistèma manca de memòria.", - "diagnosis_swap_notsomuch": "Lo sistèma a solament {total} de memòria d’escambi. Auriatz de considerar d’ajustar almens {recommended} d’escambi per evitar las situacions ont lo sistèma manca de memòria." + "diagnosis_swap_notsomuch": "Lo sistèma a solament {total} de memòria d’escambi. Auriatz de considerar d’ajustar almens {recommended} d’escambi per evitar las situacions ont lo sistèma manca de memòria.", + "diagnosis_description_web": "Web", + "diagnosis_ip_global": "IP Global  : {global}", + "diagnosis_ip_local": "IP locala : {local}", + "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}" } From dbcb08a522ba476147fb71e7036e1916132707e3 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 24 Apr 2020 01:40:00 +0000 Subject: [PATCH 1114/3170] Improve wording, fix some weird translation... --- locales/en.json | 8 +++--- locales/fr.json | 72 ++++++++++++++++++++++++------------------------- 2 files changed, 40 insertions(+), 40 deletions(-) diff --git a/locales/en.json b/locales/en.json index c2c087031..f1906e7c6 100644 --- a/locales/en.json +++ b/locales/en.json @@ -193,8 +193,8 @@ "diagnosis_mail_ehlo_unreachable_details": "Could not open a connection on port 25 to your server in IPv{ipversion}. It appears to be unreachable.
1. The most common cause for this issue is that port 25 is not correctly forwarded to your server.
2. You should also make sure that service postfix is running.
3. On more complex setups: make sure that no firewall or reverse-proxy is interfering.", "diagnosis_mail_ehlo_bad_answer": "A non-SMTP service answered on port 25 on IPv{ipversion}", "diagnosis_mail_ehlo_bad_answer_details": "It could be due to an other machine answering instead of your server.", - "diagnosis_mail_ehlo_wrong": "A different SMTP mail server answers on IPv{ipversion}. It will probably not be able to receive emails.", - "diagnosis_mail_ehlo_wrong_details": "The EHLO received by the remote diagnoser in IPv{ipversion} is different from your server's domain.
Received EHLO: {wrong_ehlo}
Expected: {right_ehlo}
The most common cause for this issue is that port 25 is not correctly forwarded to your server. Alternatively, make sure that no firewall or reverse-proxy is interfering.", + "diagnosis_mail_ehlo_wrong": "A different SMTP mail server answers on IPv{ipversion}. Your server will probably not be able to receive emails.", + "diagnosis_mail_ehlo_wrong_details": "The EHLO received by the remote diagnoser in IPv{ipversion} is different from your server's domain.
Received EHLO: {wrong_ehlo}
Expected: {right_ehlo}
The most common cause for this issue is that port 25 is not correctly forwarded to your server. Alternatively, make sure that no firewall or reverse-proxy is interfering.", "diagnosis_mail_ehlo_could_not_diagnose": "Could not diagnose if postfix mail server is reachable from outside in IPv{ipversion}.", "diagnosis_mail_ehlo_could_not_diagnose_details": "Error: {error}", "diagnosis_mail_fcrdns_ok": "Your reverse DNS is correctly configured!", @@ -207,7 +207,7 @@ "diagnosis_mail_blacklist_ok": "The IPs and domains used by this server do not appear to be blacklisted", "diagnosis_mail_blacklist_listed_by": "Your IP or domain {item} is blacklisted on {blacklist_name}", "diagnosis_mail_blacklist_reason": "The blacklist reason is: {reason}", - "diagnosis_mail_blacklist_website": "After identifying why you are listed and fixed it, feel free to ask for delisting on {blacklist_website}", + "diagnosis_mail_blacklist_website": "After identifying why you are listed and fixed it, feel free to ask for your IP or domaine to be removed on {blacklist_website}", "diagnosis_mail_queue_ok": "{nb_pending} pending emails in the mail queues", "diagnosis_mail_queue_unavailable": "Can not consult number of pending emails in queue", "diagnosis_mail_queue_unavailable_details": "Error: {error}", @@ -236,7 +236,7 @@ "diagnosis_ports_needed_by": "Exposing this port is needed for {category} features (service {service})", "diagnosis_ports_forwarding_tip": "To fix this issue, you most probably need to configure port forwarding on your internet router as described in https://yunohost.org/isp_box_config", "diagnosis_http_hairpinning_issue": "Your local network does not seem to have hairpinning enabled.", - "diagnosis_http_hairpinning_issue_details": "This is probably because of your ISP box / router. As a result, people from outside your local network will be able to access your server as expected, but not people from inside the local network (like you, probably?). You may be able to improve the situation by having a look at https://yunohost.org/dns_local_network", + "diagnosis_http_hairpinning_issue_details": "This is probably because of your ISP box / router. As a result, people from outside your local network will be able to access your server as expected, but not people from inside the local network (like you, probably?) when using the domain name or global IP. You may be able to improve the situation by having a look at https://yunohost.org/dns_local_network", "diagnosis_http_could_not_diagnose": "Could not diagnose if domains are reachable from outside in IPv{ipversion}.", "diagnosis_http_could_not_diagnose_details": "Error: {error}", "diagnosis_http_ok": "Domain {domain} is reachable through HTTP from outside the local network.", diff --git a/locales/fr.json b/locales/fr.json index 7bc6b1687..614733056 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -41,7 +41,7 @@ "backup_hook_unknown": "Script de sauvegarde '{hook:s}' inconnu", "backup_invalid_archive": "Archive de sauvegarde invalide", "backup_nothings_done": "Il n’y a rien à sauvegarder", - "backup_output_directory_forbidden": "Choisissez un répertoire de sortie différent. Les sauvegardes ne peuvent pas être créées dans les sous-dossiers /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var ou /home/yunohost.backup/archives", + "backup_output_directory_forbidden": "Choisissez un répertoire de destination différent. Les sauvegardes ne peuvent pas être créées dans les sous-dossiers /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var ou /home/yunohost.backup/archives", "backup_output_directory_not_empty": "Le répertoire de destination n’est pas vide", "backup_output_directory_required": "Vous devez spécifier un dossier de destination pour la sauvegarde", "backup_running_hooks": "Exécution des scripts de sauvegarde …", @@ -112,7 +112,7 @@ "restore_complete": "Restauré", "restore_confirm_yunohost_installed": "Voulez-vous vraiment restaurer un système déjà installé ? [{answers:s}]", "restore_failed": "Impossible de restaurer le système", - "restore_hook_unavailable": "Script de restauration pour '{part:s}' non disponible sur votre système et non plus dans l'archive", + "restore_hook_unavailable": "Le script de restauration '{part:s}' n’est pas disponible sur votre système, et ne l’est pas non plus dans l’archive", "restore_nothings_done": "Rien n’a été restauré", "restore_running_app_script": "Exécution du script de restauration de l’application '{app:s}'…", "restore_running_hooks": "Exécution des scripts de restauration…", @@ -168,7 +168,7 @@ "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_error_no_A_record": "Aucun enregistrement DNS 'A' n’a été trouvé pour {domain:s}. Vous devez faire pointer votre nom de domaine vers votre machine pour être en mesure d’installer un certificat Let’s Encrypt ! (Si vous savez ce que vous faites, utilisez --no-checks pour désactiver ces contrôles)", - "certmanager_domain_dns_ip_differs_from_public_ip": "L’enregistrement DNS 'A' du domaine {domain:s} est différent de l’adresse IP de ce serveur. Si vous avez récemment modifié votre enregistrement 'A', veuillez attendre sa propagation (quelques vérificateur de propagation DNS sont disponibles en ligne). (Si vous savez ce que vous faites, utilisez --no-checks pour désactiver ces contrôles)", + "certmanager_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}", "certmanager_cert_install_success_selfsigned": "Le certificat auto-signé est maintenant installé pour le domaine « {domain:s} »", "certmanager_cert_install_success": "Le certificat Let’s Encrypt est maintenant installé pour le domaine « {domain:s} »", @@ -326,7 +326,7 @@ "log_user_create": "Ajouter l’utilisateur '{}'", "log_user_delete": "Supprimer l’utilisateur '{}'", "log_user_update": "Mettre à jour les informations de l’utilisateur '{}'", - "log_domain_main_domain": "Faites de '{}' le domaine principal", + "log_domain_main_domain": "Faire de '{}' le domaine principal", "log_tools_migrations_migrate_forward": "Exécuter les migrations", "log_tools_postinstall": "Faire la post-installation de votre serveur YunoHost", "log_tools_upgrade": "Mettre à jour les paquets du système", @@ -380,7 +380,7 @@ "migration_0008_root": "- Vous ne pourrez pas vous connecter en tant que root via SSH. Au lieu de cela, vous devrez utiliser l’utilisateur admin ;", "migration_0008_dsa": "- La clé DSA sera désactivée. Par conséquent, il se peut que vous ayez besoin d’invalider un avertissement effrayant de votre client SSH afin de revérifier l’empreinte de votre serveur ;", "migration_0008_warning": "Si vous comprenez ces avertissements et souhaitez que YunoHost écrase votre configuration actuelle, exécutez la migration. Sinon, vous pouvez également ignorer la migration, bien que cela ne soit pas recommandé.", - "migration_0008_no_warning": "Remplacer votre configuration SSH devrait être sûr, bien que cela ne puisse pas être promis ! Exécutez la migration pour la remplacer. Sinon, vous pouvez également ignorer la migration, bien que cela ne soit pas recommandé.", + "migration_0008_no_warning": "Remplacer votre configuration SSH ne devrait pas poser de problème, bien qu'il soit difficile de le promettre ! Exécutez la migration pour la remplacer. Sinon, vous pouvez également ignorer la migration, bien que cela ne soit pas recommandé.", "migrations_success": "Migration {number} {name} réussie !", "pattern_password_app": "Désolé, les mots de passe ne peuvent pas contenir les caractères suivants : {forbidden_chars}", "root_password_replaced_by_admin_password": "Votre mot de passe root a été remplacé par votre mot de passe administrateur.", @@ -510,7 +510,7 @@ "diagnosis_ip_weird_resolvconf": "La résolution DNS semble fonctionner, mais il semble que vous utilisez un /etc/resolv.conf personnalisé.", "diagnosis_ip_weird_resolvconf_details": "Le fichier /etc/resolv.conf doit être un lien symbolique vers /etc/resolvconf/run/resolv.conf lui-même pointant vers 127.0.0.1 (dnsmasq). Si vous souhaitez configurer manuellement les résolveurs DNS, veuillez modifier /etc/resolv.dnsmasq.conf.", "diagnosis_dns_missing_record": "Selon la configuration DNS recommandée, vous devez ajouter un enregistrement DNS\nType : {type}\nNom : {name}\nValeur : {value}", - "diagnosis_diskusage_ok": "L’espace de stockage {mountpoint} (sur l’appareil {device}) a encore {libre} ({free_percent}%) espace restant (sur {total}) !", + "diagnosis_diskusage_ok": "L’espace de stockage {mountpoint} (sur le périphérique {device}) a encore {libre} ({free_percent}%) espace restant (sur {total}) !", "diagnosis_ram_ok": "Le système dispose encore de {available} ({available_percent}%) de RAM sur {total}.", "diagnosis_regenconf_allgood": "Tous les fichiers de configuration sont conformes à la configuration recommandée !", "diagnosis_security_vulnerable_to_meltdown": "Vous semblez vulnérable à la vulnérabilité de sécurité critique de Meltdown", @@ -532,12 +532,12 @@ "diagnosis_ip_no_ipv6": "Le serveur ne dispose pas d’une adresse IPv6.", "diagnosis_ip_dnsresolution_working": "La résolution de nom de domaine fonctionne !", "diagnosis_ip_broken_dnsresolution": "La résolution du nom de domaine semble interrompue pour une raison quelconque … Un pare-feu bloque-t-il les requêtes DNS ?", - "diagnosis_ip_broken_resolvconf": "La résolution du nom de domaine semble être rompue sur votre serveur, ce qui semble lié au fait que /etc/resolv.conf ne pointe pas sur 127.0.0.1.", + "diagnosis_ip_broken_resolvconf": "La résolution du nom de domaine semble être cassée sur votre serveur, ce qui semble lié au fait que /etc/resolv.conf ne pointe pas vers 127.0.0.1.", "diagnosis_dns_good_conf": "Les enregistrements DNS sont correctement configurés pour le domaine {domain} (catégorie {category})", "diagnosis_dns_bad_conf": "Certains enregistrements DNS sont manquants ou incorrects pour le domaine {domain} (catégorie {category})", "diagnosis_dns_discrepancy": "L’enregistrement DNS de type {type} et nom {name} ne correspond pas à la configuration recommandée.\nValeur actuelle : {current}\nValeur attendue : {value}", "diagnosis_services_bad_status": "Le service {service} est {status} :-(", - "diagnosis_diskusage_verylow": "L'espace de stockage {mountpoint} (sur l’appareil {device} ) ne dispose que de {free} ({free_percent}%) espace restant (sur {total}). Vous devriez vraiment envisager de nettoyer de l’espace !", + "diagnosis_diskusage_verylow": "L'espace de stockage {mountpoint} (sur l’appareil {device}) ne dispose que de {free} ({free_percent}%) espace restant (sur {total}). Vous devriez vraiment envisager de nettoyer de l’espace !", "diagnosis_diskusage_low": "L'espace de stockage {mountpoint} (sur l'appareil {device}) ne dispose que de {free} ({free_percent}%) espace restant (sur {total}). Faites attention.", "diagnosis_ram_verylow": "Le système ne dispose plus que de {available} ({available_percent}%)! (sur {total})", "diagnosis_ram_low": "Le système n’a plus de {available} ({available_percent}%) RAM sur {total}. Faites attention.", @@ -546,7 +546,7 @@ "diagnosis_swap_ok": "Le système dispose de {total} de swap !", "diagnosis_regenconf_manually_modified": "Le fichier de configuration {file} semble avoir été modifié manuellement.", "diagnosis_regenconf_manually_modified_debian": "Le fichier de configuration {file} a été modifié manuellement par rapport à celui par défaut de Debian.", - "diagnosis_regenconf_manually_modified_details": "C’est probablement OK si vous savez ce que vous faites ! YunoHost cessera de mettre à jour ce fichier automatiquement ... Mais attention, les mises à jour de YunoHost pourraient contenir d’importantes modifications recommandées. Si vous le souhaitez, vous pouvez inspecter les différences avec les outils yunohost regen-conf {category} --dry-run --with-diff et forcer la réinitialisation à la configuration recommandée avec les outils yunohost regen-conf {category} --force ", + "diagnosis_regenconf_manually_modified_details": "C’est probablement OK si vous savez ce que vous faites ! YunoHost cessera de mettre à jour ce fichier automatiquement ... Mais attention, les mises à jour de YunoHost pourraient contenir d’importantes modifications recommandées. Si vous le souhaitez, vous pouvez inspecter les différences avec yunohost tools regen-conf {category} --dry-run --with-diff et forcer la réinitialisation à la configuration recommandée avec yunohost tools regen-conf {category} --force", "diagnosis_regenconf_manually_modified_debian_details": "Cela peut probablement être OK, mais il faut garder un œil dessus …", "apps_catalog_init_success": "Système de catalogue d’applications initialisé !", "apps_catalog_failed_to_download": "Impossible de télécharger le catalogue des applications {apps_catalog} : {error}", @@ -581,11 +581,11 @@ "diagnosis_services_running": "Le service {service} est en cours de fonctionnement !", "diagnosis_services_conf_broken": "La configuration est cassée pour le service {service} !", "diagnosis_ports_needed_by": "Rendre ce port accessible est nécessaire pour les fonctionnalités de type {category} (service {service})", - "diagnosis_ports_forwarding_tip": "Pour résoudre ce problème, vous devez probablement configurer la redirection de port sur votre routeur Internet comme décrit dans https://yunohost.org/isp_box_config ", + "diagnosis_ports_forwarding_tip": "Pour résoudre ce problème, vous devez probablement configurer la redirection de port sur votre routeur Internet comme décrit dans https://yunohost.org/isp_box_config", "diagnosis_http_connection_error": "Erreur de connexion : impossible de se connecter au domaine demandé, il est probablement injoignable.", "diagnosis_no_cache": "Pas encore de cache de diagnostique pour la catégorie « {category} »", "yunohost_postinstall_end_tip": "La post-installation terminée! Pour finaliser votre configuration, il est recommandé de :\n - ajouter un premier utilisateur depuis la section \"Utilisateurs\" de l’interface web (ou \"yunohost user create \" en ligne de commande) ;\n - diagnostiquer les potentiels problèmes dans la section \"Diagnostic\" de l'interface web (ou \"yunohost diagnosis run\" en ligne de commande) ;\n - lire les parties \"Finalisation de votre configuration\" et \"Découverte de YunoHost\" dans le guide de l’administrateur: https://yunohost.org/admindoc.", - "diagnosis_services_bad_status_tip": "Vous pouvez essayer de redémarrer le service, et si cela ne fonctionne pas, consultez les journaux de service dans le webadmin (à partir de la ligne de commande, vous pouvez le faire avec yunohost service restart {service} et yunohost service log {service} ).", + "diagnosis_services_bad_status_tip": "Vous pouvez essayer de redémarrer le service, et si cela ne fonctionne pas, consultez les journaux de service dans le webadmin (à partir de la ligne de commande, vous pouvez le faire avec yunohost service restart {service} et yunohost service log {service} ).", "diagnosis_http_bad_status_code": "Le système de diagnostique n’a pas réussi à contacter votre serveur. Il se peut qu’une autre machine réponde à la place de votre serveur. Vérifiez que le port 80 est correctement redirigé, que votre configuration Nginx est à jour et qu’un reverse-proxy n’interfère pas.", "diagnosis_http_timeout": "Expiration du délai en essayant de contacter votre serveur de l’extérieur. Il semble être inaccessible. Vérifiez que vous transférez correctement le port 80, que Nginx est en cours d’exécution et qu’un pare-feu n’interfère pas.", "global_settings_setting_pop3_enabled": "Activer le protocole POP3 pour le serveur de messagerie", @@ -600,42 +600,42 @@ "certmanager_warning_subdomain_dns_record": "Le sous-domaine '{subdomain:s}' ne résout pas vers la même adresse IP que '{domain:s}'. Certaines fonctionnalités seront indisponibles tant que vous n’aurez pas corrigé cela et regénéré le certificat.", "domain_cannot_add_xmpp_upload": "Vous ne pouvez pas ajouter de domaine commençant par 'xmpp-upload.'. Ce type de nom est réservé à la fonctionnalité d’upload XMPP intégrée dans YunoHost.", "diagnosis_mail_outgoing_port_25_ok": "Le serveur de messagerie SMTP peut envoyer des e-mails (le port sortant 25 n'est pas bloqué).", - "diagnosis_mail_outgoing_port_25_blocked_details": "Vous devez d’abord essayer de débloquer le port sortant 25 dans votre interface de routeur Internet ou votre interface d’hébergement. (Certains hébergeurs peuvent vous demander de leur envoyer un ticket d'assistance pour cela).", - "diagnosis_mail_ehlo_bad_answer": "Un service non SMTP a répondu sur le port 25 sur IPv {ipversion}", - "diagnosis_mail_ehlo_bad_answer_details": "Cela peut être dû à un autre répondeur au lieu de votre serveur.", - "diagnosis_mail_ehlo_wrong": "Un autre serveur de messagerie SMTP répond sur IPv{ipversion}. Il ne sera probablement pas en mesure de recevoir des e-mails.", - "diagnosis_mail_ehlo_could_not_diagnose": "Impossible de diagnostiquer si le serveur de messagerie postfix est accessible de l’extérieur pour IPv {ipversion}.", + "diagnosis_mail_outgoing_port_25_blocked_details": "Vous devez d’abord essayer de débloquer le port sortant 25 dans votre interface de routeur Internet ou votre interface d’hébergement. (Certains hébergeurs peuvent vous demander de leur envoyer un ticket de support pour cela).", + "diagnosis_mail_ehlo_bad_answer": "Un service non SMTP a répondu sur le port 25 en IPv{ipversion}", + "diagnosis_mail_ehlo_bad_answer_details": "Cela peut être dû à une autre machine qui répond au lieu de votre serveur.", + "diagnosis_mail_ehlo_wrong": "Un autre serveur de messagerie SMTP répond sur IPv{ipversion}. Votre serveur ne sera probablement pas en mesure de recevoir des e-mails.", + "diagnosis_mail_ehlo_could_not_diagnose": "Impossible de diagnostiquer si le serveur de messagerie postfix est accessible de l’extérieur en IPv{ipversion}.", "diagnosis_mail_ehlo_could_not_diagnose_details": "Erreur : {error}", - "diagnosis_mail_fcrdns_dns_missing": "Aucun DNS inverse n’est défini dans IPv {ipversion}. Certains e-mails peuvent ne pas être livrés ou être signalés comme spam.", - "diagnosis_mail_fcrdns_ok": "Votre DNS inversé est correctement configuré !", - "diagnosis_mail_fcrdns_nok_details": "Vous devez d’abord essayer de configurer le DNS inverse avec {ehlo_domain} dans votre interface de routeur Internet ou votre interface d’hébergement. (Certains hébergeurs peuvent vous demander de leur envoyer un ticket d'assistance pour cela).", - "diagnosis_mail_fcrdns_different_from_ehlo_domain": "Le DNS inverse n'est pas correctement configuré dans IPv {ipversion}. Certains e-mails peuvent ne pas être livrés ou être signalés comme spam.", + "diagnosis_mail_fcrdns_dns_missing": "Aucun DNS inverse n’est défini pour IPv{ipversion}. Certains e-mails seront peut-être refusés ou considérés comme des spam.", + "diagnosis_mail_fcrdns_ok": "Votre DNS inverse est correctement configuré !", + "diagnosis_mail_fcrdns_nok_details": "Vous devez d’abord essayer de configurer le DNS inverse avec {ehlo_domain} dans votre interface de routeur Internet ou votre interface d’hébergement. (Certains hébergeurs peuvent vous demander de leur envoyer un ticket de support pour cela).", + "diagnosis_mail_fcrdns_different_from_ehlo_domain": "Le DNS inverse n'est pas correctement configuré en IPv{ipversion}. Certains e-mails seront peut-être refusés ou considérés comme des spam.", "diagnosis_mail_blacklist_ok": "Les adresses IP et les domaines utilisés par ce serveur ne semblent pas être sur liste noire", "diagnosis_mail_blacklist_reason": "La raison de la liste noire est : {reason}", - "diagnosis_mail_blacklist_website": "Après avoir identifié pourquoi vous êtes répertorié et corrigé, n’hésitez pas à demander la radiation sur {blacklist_website}", + "diagnosis_mail_blacklist_website": "Après avoir identifié la raison pour laquelle vous êtes répertorié et l'avoir corrigé, n’hésitez pas à demander le retrait de votre IP ou domaine sur {blacklist_website}", "diagnosis_mail_queue_ok": "{nb_pending} e-mails en attente dans les files d'attente de messagerie", "diagnosis_mail_queue_unavailable_details": "Erreur : {error}", "diagnosis_mail_queue_too_big": "Trop d’e-mails en attente dans la file d'attente ({nb_pending} e-mails)", "global_settings_setting_smtp_allow_ipv6": "Autoriser l'utilisation d’IPv6 pour recevoir et envoyer du courrier", "diagnosis_security_all_good": "Aucune vulnérabilité de sécurité critique n’a été trouvée.", "diagnosis_display_tip": "Pour voir les problèmes détectés, vous pouvez accéder à la section Diagnostic du webadmin ou exécuter « yunohost diagnostic show --issues » à partir de la ligne de commande.", - "diagnosis_ip_global": "IP globale : {global} ", - "diagnosis_ip_local": "IP locale : {local} ", - "diagnosis_dns_point_to_doc": "Veuillez consulter la documentation à https://yunohost.org/dns_config si vous avez besoin d’aide pour configurer les enregistrements DNS.", - "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Certains fournisseurs ne vous laisseront pas débloquer le port sortant 25 parce qu’ils ne se soucient pas de la neutralité du Net.
- Certains d’entre eux offrent l’alternative de en utilisant un relais de serveur de messagerie bien que cela implique que le relais sera en mesure d’espionner votre trafic de messagerie.
- Une alternative respectueuse de la vie privée consiste à utiliser un VPN * avec une IP publique dédiée * pour contourner ce type des limites. Voir https://yunohost.org/#/vpn_advantage
- Vous pouvez également envisager de passer à un fournisseur plus respectueux de la neutralité du net ", + "diagnosis_ip_global": "IP globale : {global}", + "diagnosis_ip_local": "IP locale : {local}", + "diagnosis_dns_point_to_doc": "Veuillez consulter la documentation sur https://yunohost.org/dns_config si vous avez besoin d’aide pour configurer les enregistrements DNS.", + "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Certains fournisseurs ne vous laisseront pas débloquer le port sortant 25 parce qu’ils ne se soucient pas de la neutralité du Net.
- Certains d’entre eux offrent l’alternative d'utiliser un serveur de messagerie relai bien que cela implique que le relai sera en mesure d’espionner votre trafic de messagerie.
- Une alternative respectueuse de la vie privée consiste à utiliser un VPN *avec une IP publique dédiée* pour contourner ce type de limites. Voir https://yunohost.org/#/vpn_advantage
- Vous pouvez également envisager de passer à un fournisseur plus respectueux de la neutralité du net", "diagnosis_mail_ehlo_ok": "Le serveur de messagerie SMTP est accessible de l'extérieur et peut donc recevoir des e-mails !", - "diagnosis_mail_ehlo_unreachable": "Le serveur de messagerie SMTP est inaccessible de l’extérieur sur IPv {ipversion}. Il ne pourra pas recevoir d’e-mails.", - "diagnosis_mail_ehlo_unreachable_details": "Impossible d'ouvrir une connexion sur le port 25 à votre serveur dans IPv {ipversion}. Il semble inaccessible.
1. La cause la plus courante de ce problème est que le port 25 n'est pas correctement transmis à votre serveur .
2. Vous devez également vous assurer que le suffixe de service est en cours d'exécution.
3. Sur les configurations plus complexes: assurez-vous qu'aucun pare-feu ou proxy inverse n'interfère.", - "diagnosis_mail_ehlo_wrong_details": "L’EHLO reçu par le diagnostiqueur distant dans IPv {ipversion} est différent du domaine de votre serveur.
EHLO reçu: {bad_ehlo}
Attendu : {right_ehlo}
La cause la plus courante ce problème est que le port 25 n’est pas correctement transmis à votre serveur . Vous pouvez également vous assurer qu’aucun pare-feu ou proxy inverse n’interfère.", - "diagnosis_mail_fcrdns_nok_alternatives_4": "Certains fournisseurs ne vous laisseront pas configurer votre DNS inversé (ou leur fonctionnalité pourrait être cassée …). Si vous rencontrez des problèmes à cause de cela, envisagez les solutions suivantes :
- Certains FAI fournissent l’alternative de à l’aide d’un relais de serveur de messagerie bien que cela implique que le relais pourra espionner votre trafic de messagerie.
- Une alternative respectueuse de la vie privée consiste à utiliser un VPN * avec une IP publique dédiée * pour contourner ce type de limites. Voir
https://yunohost.org/#/vpn_advantage
- Enfin, il est également possible de changement de fournisseur ", - "diagnosis_mail_fcrdns_nok_alternatives_6": "Certains fournisseurs ne vous laisseront pas configurer votre DNS inversé (ou leur fonctionnalité pourrait être cassée ...). Si votre DNS inversé est correctement configuré pour IPv4, vous pouvez essayer de désactiver l'utilisation d'IPv6 lors de l'envoi d'e-mails en exécutant yunohost settings set smtp.allow_ipv6 -v off . Remarque: cette dernière solution signifie que vous ne pourrez pas envoyer ou recevoir de courriels des quelques serveurs IPv6 uniquement disponibles.", - "diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "DNS inverse actuel : {rdns_domain}
Valeur attendue : {ehlo_domain} ", + "diagnosis_mail_ehlo_unreachable": "Le serveur de messagerie SMTP est inaccessible de l’extérieur en IPv{ipversion}. Il ne pourra pas recevoir d’e-mails.", + "diagnosis_mail_ehlo_unreachable_details": "Impossible d'ouvrir une connexion sur le port 25 à votre serveur en IPv{ipversion}. Il semble inaccessible.
1. La cause la plus courante de ce problème est que le port 25 n'est pas correctement redirigé vers votre serveur.
2. Vous devez également vous assurer que le service postfix est en cours d'exécution.
3. Sur les configurations plus complexes: assurez-vous qu'aucun pare-feu ou proxy inversé n'interfère.", + "diagnosis_mail_ehlo_wrong_details": "Le EHLO reçu par le serveur de diagnostique distant en IPv{ipversion} est différent du domaine de votre serveur.
EHLO reçu: {bad_ehlo}
Attendu : {right_ehlo}
La cause la plus courante ce problème est que le port 25 n’est pas correctement redirigé vers votre serveur . Vous pouvez également vous assurer qu’aucun pare-feu ou proxy inversé n’interfère.", + "diagnosis_mail_fcrdns_nok_alternatives_4": "Certains fournisseurs ne vous laisseront pas configurer votre DNS inversé (ou leur fonctionnalité pourrait être cassée …). Si vous rencontrez des problèmes à cause de cela, envisagez les solutions suivantes :
- Certains FAI fournissent l’alternative de à l’aide d’un relais de serveur de messagerie bien que cela implique que le relais pourra espionner votre trafic de messagerie.
- Une alternative respectueuse de la vie privée consiste à utiliser un VPN *avec une IP publique dédiée* pour contourner ce type de limites. Voir https://yunohost.org/#/vpn_advantage
- Enfin, il est également possible de changer de fournisseur", + "diagnosis_mail_fcrdns_nok_alternatives_6": "Certains fournisseurs ne vous laisseront pas configurer votre DNS inversé (ou leur fonctionnalité pourrait être cassée ...). Si votre DNS inversé est correctement configuré en IPv4, vous pouvez essayer de désactiver l'utilisation d'IPv6 lors de l'envoi d'e-mails en exécutant yunohost settings set smtp.allow_ipv6 -v off. Remarque: cette dernière solution signifie que vous ne pourrez pas envoyer ou recevoir de courriels avec les quelques serveurs qui ont uniquement de l'IPv6.", + "diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "DNS inverse actuel : {rdns_domain}
Valeur attendue : {ehlo_domain}", "diagnosis_mail_blacklist_listed_by": "Votre IP ou domaine {item} est sur liste noire sur {blacklist_name}", "diagnosis_mail_queue_unavailable": "Impossible de consulter le nombre d’e-mails en attente dans la file d'attente", - "diagnosis_ports_partially_unreachable": "Le port {port} n'est pas accessible de l'extérieur dans IPv {failed}.", - "diagnosis_http_hairpinning_issue": "Votre réseau local ne semble pas avoir activé l’épingle à cheveux.", - "diagnosis_http_hairpinning_issue_details": "C'est probablement à cause de votre box/routeur ISP. 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 ?). 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 via 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 sur HTTP.", + "diagnosis_ports_partially_unreachable": "Le port {port} n'est pas accessible de l'extérieur en IPv{failed}.", + "diagnosis_http_hairpinning_issue": "Votre réseau local ne semble pas supporter l'hairpinning.", + "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." } From 87cf61dd3e49ed86d2cc5443c6344c9501abf54c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 24 Apr 2020 01:40:59 +0000 Subject: [PATCH 1115/3170] Fix bad placeholder names... --- locales/eo.json | 4 ++-- locales/es.json | 2 +- locales/fr.json | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/locales/eo.json b/locales/eo.json index 22656188d..d778938e9 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -312,7 +312,7 @@ "package_unknown": "Nekonata pako '{pkgname}'", "domain_unknown": "Nekonata domajno", "global_settings_setting_security_password_user_strength": "Uzanto pasvorta forto", - "restore_may_be_not_enough_disk_space": "Via sistemo ne ŝajnas havi sufiĉe da spaco (libera: {libera_spaco:d} B, necesa spaco: {necesa_spaco:d} B, sekureca marĝeno: {rando:d} B)", + "restore_may_be_not_enough_disk_space": "Via sistemo ne ŝajnas havi sufiĉe da spaco (libera: {free_space:d} B, necesa spaco: {needed_space:d} B, sekureca marĝeno: {margin:d} B)", "log_corrupted_md_file": "La YAD-metadata dosiero asociita kun protokoloj estas damaĝita: '{md_file}\nEraro: {error} '", "downloading": "Elŝutante …", "user_deleted": "Uzanto forigita", @@ -576,7 +576,7 @@ "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 {1} funkcioj (servo {0})", + "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_http_could_not_diagnose": "Ne povis diagnozi, ĉu atingeblas domajno de ekstere.", "diagnosis_http_could_not_diagnose_details": "Eraro: {error}", diff --git a/locales/es.json b/locales/es.json index f76d722e6..6d77dd2ef 100644 --- a/locales/es.json +++ b/locales/es.json @@ -170,7 +170,7 @@ "certmanager_attempt_to_renew_valid_cert": "¡El certificado para el dominio «{domain:s}» no está a punto de expirar! (Puede usar --force si sabe lo que está haciendo)", "certmanager_domain_http_not_working": "Parece que no se puede acceder al dominio {domain:s} a través de HTTP. Compruebe que la configuración del DNS y de NGINX es correcta", "certmanager_error_no_A_record": "No se ha encontrado un registro DNS «A» para el dominio {domain:s}. Debe hacer que su nombre de dominio apunte a su máquina para poder instalar un certificado de Let's Encrypt. (Si sabe lo que está haciendo, use «--no-checks» para desactivar esas comprobaciones.)", - "certmanager_domain_dns_ip_differs_from_public_ip": "El registro DNS 'A' para el dominio '{dominio:s}' es diferente de la IP de este servidor. Si recientemente modificó su registro A, espere a que se propague (algunos verificadores de propagación de DNS están disponibles en línea). (Si sabe lo que está haciendo, use '--no-checks' para desactivar esos cheques)", + "certmanager_domain_dns_ip_differs_from_public_ip": "El registro DNS 'A' para el dominio '{domain:s}' es diferente de la IP de este servidor. Si recientemente modificó su registro A, espere a que se propague (algunos verificadores de propagación de DNS están disponibles en línea). (Si sabe lo que está haciendo, use '--no-checks' para desactivar esos cheques)", "certmanager_cannot_read_cert": "Se ha producido un error al intentar abrir el certificado actual para el dominio {domain:s} (archivo: {file:s}), razón: {reason:s}", "certmanager_cert_install_success_selfsigned": "Instalado correctamente un certificado autofirmado para el dominio «{domain:s}»", "certmanager_cert_install_success": "Instalado correctamente un certificado de Let's Encrypt para el dominio «{domain:s}»", diff --git a/locales/fr.json b/locales/fr.json index 614733056..764b6bb10 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -510,7 +510,7 @@ "diagnosis_ip_weird_resolvconf": "La résolution DNS semble fonctionner, mais il semble que vous utilisez un /etc/resolv.conf personnalisé.", "diagnosis_ip_weird_resolvconf_details": "Le fichier /etc/resolv.conf doit être un lien symbolique vers /etc/resolvconf/run/resolv.conf lui-même pointant vers 127.0.0.1 (dnsmasq). Si vous souhaitez configurer manuellement les résolveurs DNS, veuillez modifier /etc/resolv.dnsmasq.conf.", "diagnosis_dns_missing_record": "Selon la configuration DNS recommandée, vous devez ajouter un enregistrement DNS\nType : {type}\nNom : {name}\nValeur : {value}", - "diagnosis_diskusage_ok": "L’espace de stockage {mountpoint} (sur le périphérique {device}) a encore {libre} ({free_percent}%) espace restant (sur {total}) !", + "diagnosis_diskusage_ok": "L’espace de stockage {mountpoint} (sur le périphérique {device}) a encore {free} ({free_percent}%) espace restant (sur {total}) !", "diagnosis_ram_ok": "Le système dispose encore de {available} ({available_percent}%) de RAM sur {total}.", "diagnosis_regenconf_allgood": "Tous les fichiers de configuration sont conformes à la configuration recommandée !", "diagnosis_security_vulnerable_to_meltdown": "Vous semblez vulnérable à la vulnérabilité de sécurité critique de Meltdown", @@ -626,7 +626,7 @@ "diagnosis_mail_ehlo_ok": "Le serveur de messagerie SMTP est accessible de l'extérieur et peut donc recevoir des e-mails !", "diagnosis_mail_ehlo_unreachable": "Le serveur de messagerie SMTP est inaccessible de l’extérieur en IPv{ipversion}. Il ne pourra pas recevoir d’e-mails.", "diagnosis_mail_ehlo_unreachable_details": "Impossible d'ouvrir une connexion sur le port 25 à votre serveur en IPv{ipversion}. Il semble inaccessible.
1. La cause la plus courante de ce problème est que le port 25 n'est pas correctement redirigé vers votre serveur.
2. Vous devez également vous assurer que le service postfix est en cours d'exécution.
3. Sur les configurations plus complexes: assurez-vous qu'aucun pare-feu ou proxy inversé n'interfère.", - "diagnosis_mail_ehlo_wrong_details": "Le EHLO reçu par le serveur de diagnostique distant en IPv{ipversion} est différent du domaine de votre serveur.
EHLO reçu: {bad_ehlo}
Attendu : {right_ehlo}
La cause la plus courante ce problème est que le port 25 n’est pas correctement redirigé vers votre serveur . Vous pouvez également vous assurer qu’aucun pare-feu ou proxy inversé n’interfère.", + "diagnosis_mail_ehlo_wrong_details": "Le EHLO reçu par le serveur de diagnostique distant en IPv{ipversion} est différent du domaine de votre serveur.
EHLO reçu: {wrong_ehlo}
Attendu : {right_ehlo}
La cause la plus courante ce problème est que le port 25 n’est pas correctement redirigé vers votre serveur . Vous pouvez également vous assurer qu’aucun pare-feu ou proxy inversé n’interfère.", "diagnosis_mail_fcrdns_nok_alternatives_4": "Certains fournisseurs ne vous laisseront pas configurer votre DNS inversé (ou leur fonctionnalité pourrait être cassée …). Si vous rencontrez des problèmes à cause de cela, envisagez les solutions suivantes :
- Certains FAI fournissent l’alternative de à l’aide d’un relais de serveur de messagerie bien que cela implique que le relais pourra espionner votre trafic de messagerie.
- Une alternative respectueuse de la vie privée consiste à utiliser un VPN *avec une IP publique dédiée* pour contourner ce type de limites. Voir https://yunohost.org/#/vpn_advantage
- Enfin, il est également possible de changer de fournisseur", "diagnosis_mail_fcrdns_nok_alternatives_6": "Certains fournisseurs ne vous laisseront pas configurer votre DNS inversé (ou leur fonctionnalité pourrait être cassée ...). Si votre DNS inversé est correctement configuré en IPv4, vous pouvez essayer de désactiver l'utilisation d'IPv6 lors de l'envoi d'e-mails en exécutant yunohost settings set smtp.allow_ipv6 -v off. Remarque: cette dernière solution signifie que vous ne pourrez pas envoyer ou recevoir de courriels avec les quelques serveurs qui ont uniquement de l'IPv6.", "diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "DNS inverse actuel : {rdns_domain}
Valeur attendue : {ehlo_domain}", From e044d20802312323489eea7b4ba7868f97702411 Mon Sep 17 00:00:00 2001 From: Christian Wehrli Date: Fri, 24 Apr 2020 20:50:28 +0000 Subject: [PATCH 1116/3170] Translated using Weblate (German) Currently translated at 34.8% (220 of 632 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/locales/de.json b/locales/de.json index b354f60c5..5b372edfc 100644 --- a/locales/de.json +++ b/locales/de.json @@ -165,10 +165,10 @@ "mailbox_used_space_dovecot_down": "Der Dovecot Mailbox Dienst muss gestartet sein, wenn du den von der Mailbox belegten Speicher angezeigen lassen willst", "package_unknown": "Unbekanntes Paket '{pkgname}'", "certmanager_attempt_to_replace_valid_cert": "Du versuchst gerade eine richtiges und gültiges Zertifikat der Domain {domain:s} zu überschreiben! (Benutze --force , um diese Nachricht zu umgehen)", - "certmanager_domain_unknown": "Unbekannte Domain {domain:s}", - "certmanager_domain_cert_not_selfsigned": "Das Zertifikat der Domain {domain:s} is kein selbstsigniertes Zertifikat. Bist du dir sicher, dass du es ersetzen willst? (Benutze --force)", + "certmanager_domain_unknown": "Unbekannte Domain '{domain:s}'", + "certmanager_domain_cert_not_selfsigned": "Das Zertifikat der Domain {domain:s} ist kein selbstsigniertes Zertifikat. Bist du dir sicher, dass du es ersetzen willst? (Benutze dafür '--force')", "certmanager_certificate_fetching_or_enabling_failed": "Es scheint so als wäre die Aktivierung des Zertifikats für die Domain {domain:s} fehlgeschlagen...", - "certmanager_attempt_to_renew_nonLE_cert": "Das Zertifikat der Domain {domain:s} wurde nicht von Let's Encrypt ausgestellt. Es kann nicht automatisch erneuert werden!", + "certmanager_attempt_to_renew_nonLE_cert": "Das Zertifikat der Domain '{domain:s}' wurde nicht von Let's Encrypt ausgestellt. Es kann nicht automatisch erneuert werden!", "certmanager_attempt_to_renew_valid_cert": "Das Zertifikat der Domain {domain:s} läuft nicht in Kürze ab! (Benutze --force um diese Nachricht zu umgehen)", "certmanager_domain_http_not_working": "Es scheint so, dass die Domain {domain:s} nicht über HTTP erreicht werden kann. Bitte überprüfe, ob deine DNS und nginx Konfiguration in Ordnung ist", "certmanager_error_no_A_record": "Kein DNS 'A' Eintrag für die Domain {domain:s} gefunden. Dein Domainname muss auf diese Maschine weitergeleitet werden, um ein Let's Encrypt Zertifikat installieren zu können! (Wenn du weißt was du tust, kannst du --no-checks benutzen, um diese Überprüfung zu überspringen. )", @@ -178,15 +178,15 @@ "certmanager_cert_install_success": "Für die Domain {domain:s} wurde erfolgreich ein Let's Encrypt installiert!", "certmanager_cert_renew_success": "Das Let's Encrypt Zertifikat für die Domain {domain:s} wurde erfolgreich erneuert!", "certmanager_hit_rate_limit": "Es wurden innerhalb kurzer Zeit schon zu viele Zertifikate für die exakt gleiche Domain {domain:s} ausgestellt. Bitte versuche es später nochmal. Besuche https://letsencrypt.org/docs/rate-limits/ für mehr Informationen", - "certmanager_cert_signing_failed": "Signieren des neuen Zertifikats ist fehlgeschlagen", + "certmanager_cert_signing_failed": "Das neue Zertifikat konnte nicht signiert werden", "certmanager_no_cert_file": "Die Zertifikatsdatei für die Domain {domain:s} (Datei: {file:s}) konnte nicht gelesen werden", "certmanager_conflicting_nginx_file": "Die Domain konnte nicht für die ACME challenge vorbereitet werden: Die nginx Konfigurationsdatei {filepath:s} verursacht Probleme und sollte vorher entfernt werden", "domain_cannot_remove_main": "Die primäre Domain konnten nicht entfernt werden. Lege zuerst einen neue primäre Domain fest", "certmanager_self_ca_conf_file_not_found": "Die Konfigurationsdatei der Zertifizierungsstelle für selbstsignierte Zertifikate wurde nicht gefunden (Datei {file:s})", - "certmanager_acme_not_configured_for_domain": "Das Zertifikat für die Domain {domain:s} scheint nicht richtig installiert zu sein. Bitte führe den Befehl cert-install für diese Domain nochmals aus.", + "certmanager_acme_not_configured_for_domain": "Das Zertifikat für die Domain '{domain:s}' scheint nicht richtig installiert zu sein. Bitte führe den Befehl cert-install für diese Domain nochmals aus.", "certmanager_unable_to_parse_self_CA_name": "Der Name der Zertifizierungsstelle für selbstsignierte Zertifikate konnte nicht analysiert werden (Datei: {file:s})", - "certmanager_http_check_timeout": "Eine Zeitüberschreitung ist aufgetreten als der Server versuchte sich selbst über HTTP mit der öffentlichen IP (Domain {domain:s} mit der IP {ip:s}) zu erreichen. Möglicherweise ist dafür hairpinning oder eine falsch konfigurierte Firewall/Router deines Servers dafür verantwortlich.", - "certmanager_couldnt_fetch_intermediate_cert": "Eine Zeitüberschreitung ist aufgetreten als der Server versuchte die Teilzertifikate von Let's Encrypt zusammenzusetzen. Die Installation/Erneuerung des Zertifikats wurde abgebrochen - bitte versuche es später erneut.", + "certmanager_http_check_timeout": "Eine Zeitüberschreitung ist aufgetreten, als der Server versuchte sich selbst über HTTP mit der öffentlichen IP (Domain '{domain:s}' mit der IP '{ip:s}') zu erreichen. Möglicherweise ist dafür hairpinning oder eine falsch konfigurierte Firewall/Router deines Servers dafür verantwortlich.", + "certmanager_couldnt_fetch_intermediate_cert": "Eine Zeitüberschreitung ist aufgetreten als der Server versuchte die Teilzertifikate von Let's Encrypt zusammenzusetzen. Die Installation/Erneuerung des Zertifikats wurde abgebrochen — bitte versuche es später erneut.", "domain_hostname_failed": "Erstellen des neuen Hostnamens fehlgeschlagen", "yunohost_ca_creation_success": "Die lokale Zertifizierungs-Authorität wurde angelegt.", "app_already_installed_cant_change_url": "Diese Application ist bereits installiert. Die URL kann durch diese Funktion nicht modifiziert werden. Überprüfe ob `app changeurl` verfügbar ist.", @@ -328,7 +328,7 @@ "diagnosis_cant_run_because_of_dep": "Kann Diagnose für {category} nicht ausführen während wichtige Probleme zu {dep} noch nicht behoben sind.", "diagnosis_found_errors_and_warnings": "Habe {errors} erhebliche(s) Problem(e) (und {warnings} Warnung(en)) in Verbindung mit {category} gefunden!", "diagnosis_ip_broken_dnsresolution": "Domänen-Namens-Auflösung scheint aus einem bestimmten Grund nicht zu funktionieren... Blockiert eine Firewall die DNS Anfragen?", - "diagnosis_ip_broken_resolvconf": "Domänen-Namens-Auflösung scheint nicht zu funktionieren, was daran liegen könnte, dass in /etc/resolv.conf kein Eintrag auf 127.0.0.1 zeigt.", + "diagnosis_ip_broken_resolvconf": "Domänen-Namens-Auflösung scheint nicht zu funktionieren, was daran liegen könnte, dass in /etc/resolv.conf kein Eintrag auf 127.0.0.1 zeigt.", "diagnosis_ip_weird_resolvconf_details": "Stattdessen sollte diese Datei ein Softlink auf /etc/resolvconf/run/resolv.conf sein, die auf sich selbst zu 127.0.0.1 zeigt (dnsmasq). Der eigentlich Auflösende sollte in /etc/resolv.dnsmasq.conf konfiguriert werden.", "diagnosis_dns_good_conf": "Gute DNS Konfiguration für Domäne {domain} (Kategorie {category})", "diagnosis_ignored_issues": "(+ {nb_ignored} ignorierte(s) Problem(e))", @@ -337,5 +337,6 @@ "diagnosis_found_errors": "Habe {errors} erhebliche(s) Problem(e) in Verbindung mit {category} gefunden!", "diagnosis_found_warnings": "Habe {warnings} Ding(e) gefunden, die verbessert werden könnten für {category}.", "diagnosis_ip_dnsresolution_working": "Domänen-Namens-Auflösung funktioniert!", - "diagnosis_ip_weird_resolvconf": "DNS Auflösung scheint zu funktionieren, aber sei vorsichtig wenn du eine eigene /etc/resolv.conf verwendest." + "diagnosis_ip_weird_resolvconf": "DNS Auflösung scheint zu funktionieren, aber sei vorsichtig wenn du eine eigene /etc/resolv.conf verwendest.", + "diagnosis_display_tip": "Um die gefundenen Probleme zu sehen, kannst Du zum Diagnose-Bereich des webadmin gehen, oder 'yunohost diagnosis show --issues' in der Kommandozeile ausführen." } From c0c026613f18de3741d091581ac700e83d026ef1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 27 Apr 2020 02:15:14 +0200 Subject: [PATCH 1117/3170] 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 1118/3170] 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 1119/3170] 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 1120/3170] 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 1121/3170] 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 c6c85556ace4e720715ae38f46cc5b2f4f00de35 Mon Sep 17 00:00:00 2001 From: ljf Date: Sun, 19 Apr 2020 21:45:46 +0200 Subject: [PATCH 1122/3170] [fix] False positive on blacklist due to search in resovconf --- data/hooks/diagnosis/24-mail.py | 30 +++++++++++++++--------------- src/yunohost/utils/network.py | 19 +++++++++++++++++++ 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/data/hooks/diagnosis/24-mail.py b/data/hooks/diagnosis/24-mail.py index 4ced72959..afb88f7cf 100644 --- a/data/hooks/diagnosis/24-mail.py +++ b/data/hooks/diagnosis/24-mail.py @@ -13,6 +13,7 @@ from moulinette.utils.filesystem import read_yaml from yunohost.diagnosis import Diagnoser from yunohost.domain import _get_maindomain, domain_list from yunohost.settings import settings_get +from yunohost.utils.network import dig DEFAULT_DNS_BLACKLIST = "/usr/share/yunohost/other/dnsbl_list.yml" @@ -155,26 +156,25 @@ class MailDiagnoser(Diagnoser): if not blacklist[item_type]: continue - # Determine if we are listed on this RBL - try: - subdomain = item - if item_type != "domain": - rev = dns.reversename.from_address(item) - subdomain = str(rev.split(3)[0]) - query = subdomain + '.' + blacklist['dns_server'] - # TODO add timeout lifetime - dns.resolver.query(query, "A") - except (dns.resolver.NXDOMAIN, dns.resolver.NoNameservers, dns.resolver.NoAnswer, - dns.exception.Timeout): + # Build the query for DNSBL + subdomain = item + if item_type != "domain": + rev = dns.reversename.from_address(item) + subdomain = str(rev.split(3)[0]) + query = subdomain + '.' + blacklist['dns_server'] + + # Do the DNS Query + status, answers = dig(query, 'A') + if status != 'ok': continue # Try to get the reason details = [] - try: - reason = str(dns.resolver.query(query, "TXT")[0]) + status, answers = dig(query, 'TXT') + reason = "-" + if status == 'ok': + reason = ', '.join(answers) details.append("diagnosis_mail_blacklist_reason") - except Exception: - reason = "-" details.append("diagnosis_mail_blacklist_website") diff --git a/src/yunohost/utils/network.py b/src/yunohost/utils/network.py index 3ae1ba910..6dc4c22a0 100644 --- a/src/yunohost/utils/network.py +++ b/src/yunohost/utils/network.py @@ -21,6 +21,7 @@ import os import re import logging +import dns.resolver from moulinette.utils.network import download_text from moulinette.utils.process import check_output @@ -84,6 +85,24 @@ def get_gateway(): return addr.popitem()[1] if len(addr) == 1 else None +def dig(qname, rdtype="A", timeout=5, resolvers=["127.0.0.1"], edns_size=1500): + """ + Do a quick DNS request and avoid the "search" trap inside /etc/resolv.conf + """ + + resolver = dns.resolver.Resolver(configure=False) + resolver.use_edns(0, 0, edns_size) + resolver.nameservers = resolvers + resolver.timeout = timeout + try: + answers = resolver.query(qname, rdtype) + except (dns.resolver.NXDOMAIN, dns.resolver.NoNameservers, dns.resolver.NoAnswer, + dns.exception.Timeout) as e: + return ("nok", e.__class__.__name__, e) + + return ("ok", [(answer.to_text(), answer) for answer in answers]) + + def _extract_inet(string, skip_netmask=False, skip_loopback=True): """ Extract IP addresses (v4 and/or v6) from a string limited to one From 17d3ec5ad3e083df4920d3550151caee2c1ae7ca Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 27 Apr 2020 17:24:57 +0200 Subject: [PATCH 1123/3170] Improve new dig() helper, and use it in dnsrecords diagnosis as well --- data/hooks/diagnosis/12-dnsrecords.py | 20 +++++--------- data/hooks/diagnosis/24-mail.py | 2 +- src/yunohost/utils/network.py | 39 +++++++++++++++++++++++---- 3 files changed, 42 insertions(+), 19 deletions(-) diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py index 5ed7fc737..53afb2c2d 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -2,9 +2,9 @@ import os -from moulinette.utils.process import check_output from moulinette.utils.filesystem import read_file +from yunohost.utils.network import dig from yunohost.diagnosis import Diagnoser from yunohost.domain import domain_list, _build_dns_conf, _get_maindomain @@ -100,20 +100,14 @@ class DNSRecordsDiagnoser(Diagnoser): yield output def get_current_record(self, domain, name, type_): - if name == "@": - command = "dig +short @%s %s %s" % (self.resolver, type_, domain) - else: - command = "dig +short @%s %s %s.%s" % (self.resolver, type_, name, domain) - # FIXME : gotta handle case where this command fails ... - # e.g. no internet connectivity (dependency mechanism to good result from 'ip' diagosis ?) - # or the resolver is unavailable for some reason - output = check_output(command).strip().split("\n") - if len(output) == 0 or not output[0]: + + query = "%s.%s" % (name, domain) if name != "@" else domain + success, answers = dig(query, type_, resolvers="force_external") + + if success != "ok": return None - elif len(output) == 1: - return output[0] else: - return output + return answers[0] if len(answers) == 1 else answers def current_record_match_expected(self, r): if r["value"] is not None and r["current"] is None: diff --git a/data/hooks/diagnosis/24-mail.py b/data/hooks/diagnosis/24-mail.py index afb88f7cf..a60b4f0d4 100644 --- a/data/hooks/diagnosis/24-mail.py +++ b/data/hooks/diagnosis/24-mail.py @@ -164,7 +164,7 @@ class MailDiagnoser(Diagnoser): query = subdomain + '.' + blacklist['dns_server'] # Do the DNS Query - status, answers = dig(query, 'A') + status, _ = dig(query, 'A') if status != 'ok': continue diff --git a/src/yunohost/utils/network.py b/src/yunohost/utils/network.py index 6dc4c22a0..23b2310f8 100644 --- a/src/yunohost/utils/network.py +++ b/src/yunohost/utils/network.py @@ -25,6 +25,7 @@ import dns.resolver from moulinette.utils.network import download_text from moulinette.utils.process import check_output +from moulinette.utils.filesystem import read_file logger = logging.getLogger('yunohost.utils.network') @@ -85,22 +86,50 @@ def get_gateway(): return addr.popitem()[1] if len(addr) == 1 else None -def dig(qname, rdtype="A", timeout=5, resolvers=["127.0.0.1"], edns_size=1500): +# Lazy dev caching to avoid re-reading the file multiple time when calling +# dig() often during same yunohost operation +external_resolvers_ = [] + + +def external_resolvers(): + + global external_resolvers_ + + if not external_resolvers_: + resolv_dnsmasq_conf = read_file("/etc/resolv.dnsmasq.conf").split("\n") + external_resolvers_ = [r.split(" ")[1] for r in resolv_dnsmasq_conf if r.startswith("nameserver")] + + return external_resolvers_ + + +def dig(qname, rdtype="A", timeout=5, resolvers="local", edns_size=1500, full_answers=False): """ Do a quick DNS request and avoid the "search" trap inside /etc/resolv.conf """ + if resolvers == "local": + resolvers = ["127.0.0.1"] + elif resolvers == "force_external": + resolvers = external_resolvers() + else: + assert isinstance(resolvers, list) + resolver = dns.resolver.Resolver(configure=False) resolver.use_edns(0, 0, edns_size) resolver.nameservers = resolvers resolver.timeout = timeout try: answers = resolver.query(qname, rdtype) - except (dns.resolver.NXDOMAIN, dns.resolver.NoNameservers, dns.resolver.NoAnswer, - dns.exception.Timeout) as e: - return ("nok", e.__class__.__name__, e) + except (dns.resolver.NXDOMAIN, + dns.resolver.NoNameservers, + dns.resolver.NoAnswer, + dns.exception.Timeout) as e: + return ("nok", (e.__class__.__name__, e)) - return ("ok", [(answer.to_text(), answer) for answer in answers]) + if not full_answers: + answers = [answer.to_text() for answer in answers] + + return ("ok", answers) def _extract_inet(string, skip_netmask=False, skip_loopback=True): From c1262ab9a93855e241d2c25197de05858547ab36 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 22 Apr 2020 03:09:28 +0200 Subject: [PATCH 1124/3170] Fix acme challenge code snippet detection for this domain --- locales/en.json | 2 +- src/yunohost/certificate.py | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/locales/en.json b/locales/en.json index c2c087031..b23d3b5c3 100644 --- a/locales/en.json +++ b/locales/en.json @@ -110,7 +110,7 @@ "backup_unable_to_organize_files": "Could not use the quick method to organize files in the archive", "backup_with_no_backup_script_for_app": "The app '{app:s}' has no backup script. Ignoring.", "backup_with_no_restore_script_for_app": "The '{app:s}' has no restoration script, you will not be able to automatically restore the backup of this app.", - "certmanager_acme_not_configured_for_domain": "Certificate for the domain '{domain:s}' does not appear to be correctly installed. Please run 'cert-install' for this domain first.", + "certmanager_acme_not_configured_for_domain": "The ACME challenge cannot be ran for this domain right now because you are missing a code snippet in nginx conf... Please make sure that your nginx configuration is up to date using `yunohost tools regen-conf nginx --dry-run --with-diff`.", "certmanager_attempt_to_renew_nonLE_cert": "The certificate for the domain '{domain:s}' is not issued by Let's Encrypt. Cannot renew it automatically!", "certmanager_attempt_to_renew_valid_cert": "The certificate for the domain '{domain:s}' is not about to expire! (You may use --force if you know what you're doing)", "certmanager_attempt_to_replace_valid_cert": "You are attempting to overwrite a good and valid certificate for domain {domain:s}! (Use --force to bypass)", diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index fd792ccae..89aadce99 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -38,6 +38,7 @@ from yunohost.vendor.acme_tiny.acme_tiny import get_crt as sign_certificate from yunohost.utils.error import YunohostError from moulinette.utils.log import getActionLogger +from moulinette.utils.filesystem import read_file from yunohost.utils.network import get_public_ip @@ -468,14 +469,15 @@ Subject: %s def _check_acme_challenge_configuration(domain): - # Check nginx conf file exists - nginx_conf_folder = "/etc/nginx/conf.d/%s.d" % domain - nginx_conf_file = "%s/000-acmechallenge.conf" % nginx_conf_folder - if not os.path.exists(nginx_conf_file): - return False - else: + domain_conf = "/etc/nginx/conf.d/%s.conf" % domain + if "include /etc/nginx/conf.d/acme-challenge.conf.inc" in read_file(domain_conf): return True + else: + # This is for legacy setups which haven't updated their domain conf to + # the new conf that include the acme snippet... + legacy_acme_conf = "/etc/nginx/conf.d/%s.d/000-acmechallenge.conf" % domain + return os.path.exists(legacy_acme_conf) def _fetch_and_enable_new_certificate(domain, staging=False, no_checks=False): From 32c300e62742da4645e15797da4eb317074a4da5 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 22 Apr 2020 03:09:50 +0200 Subject: [PATCH 1125/3170] Reorganize import, make linter happier --- src/yunohost/certificate.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 89aadce99..5558caad5 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -34,16 +34,14 @@ import glob from datetime import datetime -from yunohost.vendor.acme_tiny.acme_tiny import get_crt as sign_certificate - -from yunohost.utils.error import YunohostError +from moulinette import m18n from moulinette.utils.log import getActionLogger 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 moulinette import m18n -from yunohost.app import app_ssowatconf from yunohost.service import _run_service_command from yunohost.regenconf import regen_conf from yunohost.log import OperationLogger @@ -597,7 +595,7 @@ def _prepare_certificate_signing_request(domain, key_file, output_folder): from yunohost.domain import _get_maindomain if domain == _get_maindomain(): # Include xmpp-upload subdomain in subject alternate names - subdomain="xmpp-upload." + domain + subdomain = "xmpp-upload." + domain try: _dns_ip_match_public_ip(get_public_ip(), subdomain) csr.add_extensions([crypto.X509Extension("subjectAltName", False, "DNS:" + subdomain)]) From f91eeff9dd3c09f8d8bfcf509541f484c536d340 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 22 Apr 2020 03:49:53 +0200 Subject: [PATCH 1126/3170] Uhoh we should use {domain}, fix wording.. --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index b23d3b5c3..aa1c4e4f2 100644 --- a/locales/en.json +++ b/locales/en.json @@ -110,7 +110,7 @@ "backup_unable_to_organize_files": "Could not use the quick method to organize files in the archive", "backup_with_no_backup_script_for_app": "The app '{app:s}' has no backup script. Ignoring.", "backup_with_no_restore_script_for_app": "The '{app:s}' has no restoration script, you will not be able to automatically restore the backup of this app.", - "certmanager_acme_not_configured_for_domain": "The ACME challenge cannot be ran for this domain right now because you are missing a code snippet in nginx conf... Please make sure that your nginx configuration is up to date using `yunohost tools regen-conf nginx --dry-run --with-diff`.", + "certmanager_acme_not_configured_for_domain": "The ACME challenge cannot be ran for {domain} right now because its nginx conf lacks the corresponding code snippet... Please make sure that your nginx configuration is up to date using `yunohost tools regen-conf nginx --dry-run --with-diff`.", "certmanager_attempt_to_renew_nonLE_cert": "The certificate for the domain '{domain:s}' is not issued by Let's Encrypt. Cannot renew it automatically!", "certmanager_attempt_to_renew_valid_cert": "The certificate for the domain '{domain:s}' is not about to expire! (You may use --force if you know what you're doing)", "certmanager_attempt_to_replace_valid_cert": "You are attempting to overwrite a good and valid certificate for domain {domain:s}! (Use --force to bypass)", From d6b2275b33a52f978c87770405f7acad44ab0471 Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Mon, 27 Apr 2020 18:30:33 +0200 Subject: [PATCH 1127/3170] [enh] On 2 lines it's better --- data/helpers.d/postgresql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data/helpers.d/postgresql b/data/helpers.d/postgresql index 1dac6715d..aac223214 100644 --- a/data/helpers.d/postgresql +++ b/data/helpers.d/postgresql @@ -87,7 +87,8 @@ ynh_psql_create_db() { # grant all privilegies to user if [ -n "$user" ]; then - sql+="ALTER DATABASE ${db} OWNER TO ${user}; GRANT ALL PRIVILEGES ON DATABASE ${db} TO ${user} WITH GRANT OPTION;" + sql+="ALTER DATABASE ${db} OWNER TO ${user};" + sql+="GRANT ALL PRIVILEGES ON DATABASE ${db} TO ${user} WITH GRANT OPTION;" fi ynh_psql_execute_as_root --sql="$sql" From e01859ffc4b8aa6759adc75ff8920eff59d23026 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 27 Apr 2020 20:57:38 +0200 Subject: [PATCH 1128/3170] 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 311835b1b5ccb8a64f8baa3390153524fe402b80 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 27 Apr 2020 23:23:31 +0200 Subject: [PATCH 1129/3170] Add name of the exceptions that can be raised to docstring.. --- src/yunohost/backup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 5d64ae5d6..c2d2e276a 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -1909,6 +1909,8 @@ class TarBackupMethod(BackupMethod): Exceptions: backup_archive_open_failed -- Raised if the archive can't be open + 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) From a62b127aca961d79188474aa416aff63e0139584 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 23 Apr 2020 04:18:23 +0200 Subject: [PATCH 1130/3170] Fix improper use of logger.exception in app.py --- src/yunohost/app.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 4e4878f9e..41c2f97a3 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -736,7 +736,7 @@ def app_upgrade(app=[], url=None, file=None): upgrade_failed = True if upgrade_retcode != 0 else False if upgrade_failed: error = m18n.n('app_upgrade_script_failed') - logger.exception(m18n.n("app_upgrade_failed", app=app_instance_name, error=error)) + logger.error(m18n.n("app_upgrade_failed", app=app_instance_name, error=error)) failure_message_with_debug_instructions = operation_logger.error(error) if msettings.get('interface') != 'api': dump_app_log_extract_for_debugging(operation_logger) @@ -744,13 +744,13 @@ def app_upgrade(app=[], url=None, file=None): except (KeyboardInterrupt, EOFError): upgrade_retcode = -1 error = m18n.n('operation_interrupted') - logger.exception(m18n.n("app_upgrade_failed", app=app_instance_name, error=error)) + logger.error(m18n.n("app_upgrade_failed", app=app_instance_name, error=error)) failure_message_with_debug_instructions = operation_logger.error(error) # 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()) - logger.exception(m18n.n("app_install_failed", app=app_instance_name, error=error)) + logger.error(m18n.n("app_install_failed", app=app_instance_name, 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 @@ -760,7 +760,7 @@ def app_upgrade(app=[], url=None, file=None): _assert_system_is_sane_for_app(manifest, "post") except Exception as e: broke_the_system = True - logger.exception(m18n.n("app_upgrade_failed", app=app_instance_name, error=str(e))) + logger.error(m18n.n("app_upgrade_failed", app=app_instance_name, error=str(e))) failure_message_with_debug_instructions = operation_logger.error(str(e)) # If upgrade failed or broke the system, @@ -1002,20 +1002,20 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu install_failed = True if install_retcode != 0 else False if install_failed: error = m18n.n('app_install_script_failed') - logger.exception(m18n.n("app_install_failed", app=app_id, error=error)) + logger.error(m18n.n("app_install_failed", app=app_id, error=error)) failure_message_with_debug_instructions = operation_logger.error(error) if msettings.get('interface') != 'api': dump_app_log_extract_for_debugging(operation_logger) # Script got manually interrupted ... N.B. : KeyboardInterrupt does not inherit from Exception except (KeyboardInterrupt, EOFError): error = m18n.n('operation_interrupted') - logger.exception(m18n.n("app_install_failed", app=app_id, error=error)) + logger.error(m18n.n("app_install_failed", app=app_id, error=error)) failure_message_with_debug_instructions = operation_logger.error(error) # 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()) - logger.exception(m18n.n("app_install_failed", app=app_id, error=error)) + 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 @@ -1025,7 +1025,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu _assert_system_is_sane_for_app(manifest, "post") except Exception as e: broke_the_system = True - logger.exception(m18n.n("app_install_failed", app=app_id, error=str(e))) + 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 @@ -1062,7 +1062,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.exception(m18n.n('unexpected_error', error=u"\n" + traceback.format_exc())) + logger.error(m18n.n('unexpected_error', error=u"\n" + traceback.format_exc())) # Remove all permission in LDAP for permission_name in user_permission_list()["permissions"].keys(): @@ -1234,7 +1234,7 @@ def app_remove(operation_logger, app): except (KeyboardInterrupt, EOFError, Exception): ret = -1 import traceback - logger.exception(m18n.n('unexpected_error', error=u"\n" + traceback.format_exc())) + logger.error(m18n.n('unexpected_error', error=u"\n" + traceback.format_exc())) if ret == 0: logger.success(m18n.n('app_removed', app=app)) @@ -2197,7 +2197,7 @@ def _get_app_settings(app_id): if app_id == settings['id']: return settings except (IOError, TypeError, KeyError): - logger.exception(m18n.n('app_not_correctly_installed', + logger.error(m18n.n('app_not_correctly_installed', app=app_id)) return {} From 428f0a61fc074a996c982c239d4c6a457076437d Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sun, 19 Apr 2020 18:15:58 +0200 Subject: [PATCH 1131/3170] Wait for fail2ban to reload --- data/helpers.d/fail2ban | 2 +- data/helpers.d/systemd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/fail2ban b/data/helpers.d/fail2ban index 58af9ec0b..40f435ecd 100644 --- a/data/helpers.d/fail2ban +++ b/data/helpers.d/fail2ban @@ -130,7 +130,7 @@ EOF ynh_store_file_checksum "$finalfail2banjailconf" ynh_store_file_checksum "$finalfail2banfilterconf" - ynh_systemd_action --service_name=fail2ban --action=reload + ynh_systemd_action --service_name=fail2ban --action=reload --line_match="(Started|Reloaded) Fail2Ban Service" --log_path=systemd local fail2ban_error="$(journalctl -u fail2ban | tail -n50 | grep "WARNING.*$app.*")" if [[ -n "$fail2ban_error" ]]; then diff --git a/data/helpers.d/systemd b/data/helpers.d/systemd index 960382f8f..2c290ad64 100644 --- a/data/helpers.d/systemd +++ b/data/helpers.d/systemd @@ -133,7 +133,7 @@ ynh_systemd_action() { for i in $(seq 1 $timeout) do # Read the log until the sentence is found, that means the app finished to start. Or run until the timeout - if grep --quiet "$line_match" "$templog" + if grep --extended-regexp --quiet "$line_match" "$templog" then ynh_print_info --message="The service $service_name has correctly started." break From 1ba08be8fb3cc9d177c74bc8620b14678d97eae1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 23 Apr 2020 17:02:58 +0200 Subject: [PATCH 1132/3170] Make sure to return / and not empty string for stuff on domain root --- src/yunohost/app.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 41c2f97a3..69ea10928 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -455,6 +455,8 @@ def app_map(app=None, raw=False, user=None): perm_domain, perm_path = perm_url.split("/", 1) perm_path = "/" + perm_path.rstrip("/") + perm_path = perm_path if perm_path != "" else "/" + return perm_domain, perm_path this_app_perms = {p: i for p, i in permissions.items() if p.startswith(app_id + ".") and i["url"]} @@ -490,7 +492,6 @@ def app_map(app=None, raw=False, user=None): continue perm_domain, perm_path = _sanitized_absolute_url(perm_info["url"]) - if perm_name.endswith(".main"): perm_label = label else: @@ -1362,11 +1363,12 @@ def app_makedefault(operation_logger, app, domain=None): elif domain not in domain_list()['domains']: raise YunohostError('domain_unknown') - operation_logger.start() if '/' in app_map(raw=True)[domain]: raise YunohostError('app_make_default_location_already_used', app=app, domain=app_domain, other_app=app_map(raw=True)[domain]["/"]["id"]) + operation_logger.start() + # TODO / FIXME : current trick is to add this to conf.json.persisten # This is really not robust and should be improved # e.g. have a flag in /etc/yunohost/apps/$app/ to say that this is the @@ -1636,6 +1638,8 @@ def app_ssowatconf(): perm_domain, perm_path = perm_url.split("/", 1) perm_path = "/" + perm_path.rstrip("/") + perm_path = perm_path if perm_path != "" else "/" + return perm_domain + perm_path # Skipped From 794640a6739981560a6221988b4dbf2b5cecd847 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 25 Apr 2020 23:52:55 +0200 Subject: [PATCH 1133/3170] Make sure to strip() the path just in case Co-Authored-By: Bram --- src/yunohost/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 69ea10928..8c52f4928 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -455,7 +455,7 @@ def app_map(app=None, raw=False, user=None): perm_domain, perm_path = perm_url.split("/", 1) perm_path = "/" + perm_path.rstrip("/") - perm_path = perm_path if perm_path != "" else "/" + perm_path = perm_path if perm_path.strip() != "" else "/" return perm_domain, perm_path @@ -1638,7 +1638,7 @@ def app_ssowatconf(): perm_domain, perm_path = perm_url.split("/", 1) perm_path = "/" + perm_path.rstrip("/") - perm_path = perm_path if perm_path != "" else "/" + perm_path = perm_path if perm_path.strip() != "" else "/" return perm_domain + perm_path From d72156b91f95b5c0ee5283bf3c3ee2c9507406ea Mon Sep 17 00:00:00 2001 From: ljf Date: Mon, 20 Apr 2020 01:40:37 +0200 Subject: [PATCH 1134/3170] [enh] Check domain expiration date --- data/hooks/diagnosis/12-dnsrecords.py | 79 +++++++++++++++++++++++++-- debian/control | 2 +- locales/en.json | 5 ++ 3 files changed, 81 insertions(+), 5 deletions(-) diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py index 53afb2c2d..402f5f994 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -1,6 +1,10 @@ #!/usr/bin/env python import os +import re + +from datetime import datetime, timedelta +from subprocess import Popen, PIPE from moulinette.utils.filesystem import read_file @@ -8,6 +12,7 @@ from yunohost.utils.network import dig from yunohost.diagnosis import Diagnoser from yunohost.domain import domain_list, _build_dns_conf, _get_maindomain +SMALL_SUFFIX_LIST = ['noho.st', 'nohost.me', 'ynh.fr', 'netlib.re'] class DNSRecordsDiagnoser(Diagnoser): @@ -31,10 +36,12 @@ class DNSRecordsDiagnoser(Diagnoser): is_subdomain = domain.split(".",1)[1] in all_domains for report in self.check_domain(domain, domain == main_domain, is_subdomain=is_subdomain): yield report - - # FIXME : somewhere, should implement a check for reverse DNS ... - - # FIXME / TODO : somewhere, could also implement a check for domain expiring soon + + # Check if a domain buy by the user will expire soon + domains_from_registrar = ['.'.join(domain.split('.')[-2:]) for domain in all_domains] + domains_from_registrar = set(domains_from_registrar) - set(SMALL_SUFFIX_LIST) + for report in self.check_expiration_date(domains_from_registrar): + yield report def check_domain(self, domain, is_main_domain, is_subdomain): @@ -137,5 +144,69 @@ class DNSRecordsDiagnoser(Diagnoser): return r["current"] == r["value"] + def check_expiration_date(self, domains): + """ + Alert if expiration date of a domain is soon + """ + + # FIXME find a way to ignore a specific domain without + # create a report by domain each time. We need something small + details = { + "not_found": [], + "error": [], + "warning": [], + "info": [] + } + + for domain in domains: + expire_date = self.get_domain_expiration(domain) + + if not expire_date: + details["not_found"].append(( + "diagnosis_domain_expiration_date_not_found", + {"domain": domain})) + continue + + expire_in = expire_date - datetime.now() + + alert_type = "info" + if expire_in <= timedelta(7): + alert_type = "error" + elif expire_in <= timedelta(30): + alert_type = "warning" + + args = { + "domain": domain, + "days": expire_in.days - 1, + "expire_date": str(expire_date) + } + details[alert_type].append(("diagnosis_domain_expires_in", args)) + + for alert_type in ["error", "warning", "not_found", "info"]: + if details[alert_type]: + yield dict(meta={"category": "expiration"}, + data={}, + status=alert_type.upper() if alert_type != "not_found" else "INFO", + summary="diagnosis_domain_expiration_" + alert_type, + details=details[alert_type]) + + def get_domain_expiration(self, domain): + """ + Return the expiration datetime of a domain or None + """ + + p1 = Popen(['whois', domain], stdout=PIPE) + p2 = Popen(['grep', 'Expir'], stdin=p1.stdout, stdout=PIPE) + out, err = p2.communicate() + out = out.decode("utf-8").split('\n') + p1.terminate() + #p2.terminate() + + for line in out: + match = re.search(r'\d{4}-\d{2}-\d{2}', line) + if match is not None: + return datetime.strptime(match.group(), '%Y-%m-%d') + return None + def main(args, env, loggers): return DNSRecordsDiagnoser(args, env, loggers).diagnose() diff --git a/debian/control b/debian/control index 5bcd78491..fcffa87f6 100644 --- a/debian/control +++ b/debian/control @@ -29,7 +29,7 @@ Depends: ${python:Depends}, ${misc:Depends} , redis-server , metronome , git, curl, wget, cron, unzip, jq - , lsb-release, haveged, fake-hwclock, equivs, lsof + , lsb-release, haveged, fake-hwclock, equivs, lsof, whois Recommends: yunohost-admin , ntp, inetutils-ping | iputils-ping , bash-completion, rsyslog diff --git a/locales/en.json b/locales/en.json index 3607052e3..7f6d9ce34 100644 --- a/locales/en.json +++ b/locales/en.json @@ -172,6 +172,11 @@ "diagnosis_dns_missing_record": "According to the recommended DNS configuration, you should add a DNS record with the following info.
Type: {type}
Name: {name}
Value: {value}", "diagnosis_dns_discrepancy": "The following DNS record does not seem to follow the recommended configuration:
Type: {type}
Name: {name}
Current value: {current}
Excepted value: {value}", "diagnosis_dns_point_to_doc": "Please check the documentation at https://yunohost.org/dns_config if you need help about configuring DNS records.", + "diagnosis_domain_expiration_not_found": "Unable to check the expiration date of some domains", + "diagnosis_domain_expiration_info": "Domains expiration dates", + "diagnosis_domain_expiration_warning": "Some domains expire in less than a month", + "diagnosis_domain_expiration_error": "Some domains expire in less than a week", + "diagnosis_domain_expires_in": "{domain} expires in {days} days.", "diagnosis_services_running": "Service {service} is running!", "diagnosis_services_conf_broken": "Configuration is broken for service {service}!", "diagnosis_services_bad_status": "Service {service} is {status} :(", From c0f27c02353d7007f0ae32823213866c6321fa35 Mon Sep 17 00:00:00 2001 From: ljf Date: Mon, 20 Apr 2020 01:47:16 +0200 Subject: [PATCH 1135/3170] [fix] i18n checks --- tests/test_i18n_keys.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_i18n_keys.py b/tests/test_i18n_keys.py index 9125c5d52..e3edc4d48 100644 --- a/tests/test_i18n_keys.py +++ b/tests/test_i18n_keys.py @@ -119,13 +119,16 @@ def find_expected_string_keys(): for level in ["danger", "thirdparty", "warning"]: yield "confirm_app_install_%s" % level + for errortype in ["not_found", "error", "warning", "info"]: + yield "diagnosis_domain_expiration_" % errortype + for errortype in ["bad_status_code", "connection_error", "timeout"]: yield "diagnosis_http_%s" % errortype yield "password_listed" for i in [1, 2, 3, 4]: yield "password_too_simple_%s" % i - + checks = ["outgoing_port_25_ok", "ehlo_ok", "fcrdns_ok", "blacklist_ok", "queue_ok", "ehlo_bad_answer", "ehlo_unreachable", "ehlo_bad_answer_details", From 6954ca9002c808dab7ad3d3b2b3c27f31661850b Mon Sep 17 00:00:00 2001 From: ljf Date: Mon, 20 Apr 2020 01:50:04 +0200 Subject: [PATCH 1136/3170] [fix] i18n checks --- tests/test_i18n_keys.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_i18n_keys.py b/tests/test_i18n_keys.py index e3edc4d48..6dcfd5c70 100644 --- a/tests/test_i18n_keys.py +++ b/tests/test_i18n_keys.py @@ -120,7 +120,7 @@ def find_expected_string_keys(): yield "confirm_app_install_%s" % level for errortype in ["not_found", "error", "warning", "info"]: - yield "diagnosis_domain_expiration_" % errortype + yield "diagnosis_domain_expiration_%s" % errortype for errortype in ["bad_status_code", "connection_error", "timeout"]: yield "diagnosis_http_%s" % errortype From d98d753f521def498bd8300294a3ff7b21b0f16a Mon Sep 17 00:00:00 2001 From: ljf Date: Mon, 27 Apr 2020 17:07:38 +0200 Subject: [PATCH 1137/3170] [fix] Bad i18n key --- data/hooks/diagnosis/12-dnsrecords.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py index 402f5f994..b6d87aed0 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -163,7 +163,7 @@ class DNSRecordsDiagnoser(Diagnoser): if not expire_date: details["not_found"].append(( - "diagnosis_domain_expiration_date_not_found", + "diagnosis_domain_expiration_not_found", {"domain": domain})) continue From cdb917e5652bc3782ef861272be9ae3acf32d0db Mon Sep 17 00:00:00 2001 From: ljf Date: Mon, 27 Apr 2020 18:09:07 +0200 Subject: [PATCH 1138/3170] [enh] Explain why domain expiration not found --- data/hooks/diagnosis/12-dnsrecords.py | 47 +++++++++++++++------------ locales/en.json | 2 ++ tests/test_i18n_keys.py | 3 +- 3 files changed, 31 insertions(+), 21 deletions(-) diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py index b6d87aed0..f03e92df3 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -14,6 +14,7 @@ from yunohost.domain import domain_list, _build_dns_conf, _get_maindomain SMALL_SUFFIX_LIST = ['noho.st', 'nohost.me', 'ynh.fr', 'netlib.re'] + class DNSRecordsDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] @@ -33,12 +34,13 @@ class DNSRecordsDiagnoser(Diagnoser): all_domains = domain_list()["domains"] for domain in all_domains: self.logger_debug("Diagnosing DNS conf for %s" % domain) - is_subdomain = domain.split(".",1)[1] in all_domains + is_subdomain = domain.split(".", 1)[1] in all_domains for report in self.check_domain(domain, domain == main_domain, is_subdomain=is_subdomain): yield report - + # Check if a domain buy by the user will expire soon domains_from_registrar = ['.'.join(domain.split('.')[-2:]) for domain in all_domains] + domains_from_registrar = ['ynh.local', 'grimaud.me', 'netlib.re', 'arn-fai.net'] domains_from_registrar = set(domains_from_registrar) - set(SMALL_SUFFIX_LIST) for report in self.check_expiration_date(domains_from_registrar): yield report @@ -74,7 +76,6 @@ class DNSRecordsDiagnoser(Diagnoser): results[id_] = "WRONG" discrepancies.append(("diagnosis_dns_discrepancy", r)) - def its_important(): # Every mail DNS records are important for main domain # For other domain, we only report it as a warning for now... @@ -135,7 +136,7 @@ class DNSRecordsDiagnoser(Diagnoser): if r["name"] == "@": current = {part for part in current if not part.startswith("ip4:") and not part.startswith("ip6:")} return expected == current - elif r["type"] == "MX": + elif r["type"] == "MX": # For MX, we want to ignore the priority expected = r["value"].split()[-1] current = r["current"].split()[-1] @@ -143,14 +144,11 @@ class DNSRecordsDiagnoser(Diagnoser): else: return r["current"] == r["value"] - def check_expiration_date(self, domains): """ Alert if expiration date of a domain is soon """ - # FIXME find a way to ignore a specific domain without - # create a report by domain each time. We need something small details = { "not_found": [], "error": [], @@ -161,9 +159,9 @@ class DNSRecordsDiagnoser(Diagnoser): for domain in domains: expire_date = self.get_domain_expiration(domain) - if not expire_date: + if isinstance(expire_date, str): details["not_found"].append(( - "diagnosis_domain_expiration_not_found", + "diagnosis_%s_details" % (expire_date), {"domain": domain})) continue @@ -184,9 +182,17 @@ class DNSRecordsDiagnoser(Diagnoser): for alert_type in ["error", "warning", "not_found", "info"]: if details[alert_type]: - yield dict(meta={"category": "expiration"}, + if alert_type == "not_found": + meta = {"test": "domain_not_found"} + else: + meta = {"test": "domain_expiration"} + # Allow to ignor specifically a single domain + if len(details[alert_type]) == 1: + meta["domain"] = details[alert_type][0][1]["domain"] + meta["domain"] = details[alert_type][0][1]["domain"] + yield dict(meta=meta, data={}, - status=alert_type.upper() if alert_type != "not_found" else "INFO", + status=alert_type.upper() if alert_type != "not_found" else "WARNING", summary="diagnosis_domain_expiration_" + alert_type, details=details[alert_type]) @@ -194,19 +200,20 @@ class DNSRecordsDiagnoser(Diagnoser): """ Return the expiration datetime of a domain or None """ + # "echo failed" avoid to trigger CalledProcessError + command = "whois -H %s || echo failed" % (domain) + out = check_output(command).strip().split("\n") - p1 = Popen(['whois', domain], stdout=PIPE) - p2 = Popen(['grep', 'Expir'], stdin=p1.stdout, stdout=PIPE) - out, err = p2.communicate() - out = out.decode("utf-8").split('\n') - p1.terminate() - #p2.terminate() + # If there is less 5 lines, it's NOT FOUND response + if len(out) <= 4: + return "domain_not_found" for line in out: - match = re.search(r'\d{4}-\d{2}-\d{2}', line) + match = re.search(r'Expir.+(\d{4}-\d{2}-\d{2})', line) if match is not None: - return datetime.strptime(match.group(), '%Y-%m-%d') - return None + return datetime.strptime(match.group(1), '%Y-%m-%d') + return "domain_expiration_not_found" + def main(args, env, loggers): return DNSRecordsDiagnoser(args, env, loggers).diagnose() diff --git a/locales/en.json b/locales/en.json index 7f6d9ce34..a0e7454f1 100644 --- a/locales/en.json +++ b/locales/en.json @@ -173,6 +173,8 @@ "diagnosis_dns_discrepancy": "The following DNS record does not seem to follow the recommended configuration:
Type: {type}
Name: {name}
Current value: {current}
Excepted value: {value}", "diagnosis_dns_point_to_doc": "Please check the documentation at https://yunohost.org/dns_config if you need help about configuring DNS records.", "diagnosis_domain_expiration_not_found": "Unable to check the expiration date of some domains", + "diagnosis_domain_not_found_details": "The domain {domain} doesn't exist in WHOIS database !", + "diagnosis_domain_expiration_not_found_details": "The WHOIS returns some info about the domain {domain} but we are not able to found the expiration date inside those info.", "diagnosis_domain_expiration_info": "Domains expiration dates", "diagnosis_domain_expiration_warning": "Some domains expire in less than a month", "diagnosis_domain_expiration_error": "Some domains expire in less than a week", diff --git a/tests/test_i18n_keys.py b/tests/test_i18n_keys.py index 6dcfd5c70..6ddfa9c4a 100644 --- a/tests/test_i18n_keys.py +++ b/tests/test_i18n_keys.py @@ -119,8 +119,9 @@ def find_expected_string_keys(): for level in ["danger", "thirdparty", "warning"]: yield "confirm_app_install_%s" % level - for errortype in ["not_found", "error", "warning", "info"]: + for errortype in ["not_found", "error", "warning", "info", "not_found_details"]: yield "diagnosis_domain_expiration_%s" % errortype + yield "diagnosis_domain_not_found_details" for errortype in ["bad_status_code", "connection_error", "timeout"]: yield "diagnosis_http_%s" % errortype From c347e368fc8913e2042ae62589a6775746c6e3e9 Mon Sep 17 00:00:00 2001 From: ljf Date: Mon, 27 Apr 2020 18:22:22 +0200 Subject: [PATCH 1139/3170] [fix] Remove this damn test --- data/hooks/diagnosis/12-dnsrecords.py | 1 - 1 file changed, 1 deletion(-) diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py index f03e92df3..003c19cfb 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -40,7 +40,6 @@ class DNSRecordsDiagnoser(Diagnoser): # Check if a domain buy by the user will expire soon domains_from_registrar = ['.'.join(domain.split('.')[-2:]) for domain in all_domains] - domains_from_registrar = ['ynh.local', 'grimaud.me', 'netlib.re', 'arn-fai.net'] domains_from_registrar = set(domains_from_registrar) - set(SMALL_SUFFIX_LIST) for report in self.check_expiration_date(domains_from_registrar): yield report From d1b694447a15e799cd514eb106623e33e86db87b Mon Sep 17 00:00:00 2001 From: ljf Date: Mon, 27 Apr 2020 23:37:45 +0200 Subject: [PATCH 1140/3170] [enh] Use publicsuffix list to avoid alert on dyndns domain --- data/hooks/diagnosis/12-dnsrecords.py | 40 ++++++++++++++++++--------- debian/control | 2 +- locales/en.json | 2 +- 3 files changed, 29 insertions(+), 15 deletions(-) diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py index 003c19cfb..c92c2648e 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -4,15 +4,16 @@ import os import re from datetime import datetime, timedelta -from subprocess import Popen, PIPE +from publicsuffix import PublicSuffixList from moulinette.utils.filesystem import read_file from yunohost.utils.network import dig from yunohost.diagnosis import Diagnoser from yunohost.domain import domain_list, _build_dns_conf, _get_maindomain +from yunohost.utils.network import dig -SMALL_SUFFIX_LIST = ['noho.st', 'nohost.me', 'ynh.fr', 'netlib.re'] +PENDING_SUFFIX_LIST = ['ynh.fr', 'netlib.re'] class DNSRecordsDiagnoser(Diagnoser): @@ -39,8 +40,11 @@ class DNSRecordsDiagnoser(Diagnoser): yield report # Check if a domain buy by the user will expire soon - domains_from_registrar = ['.'.join(domain.split('.')[-2:]) for domain in all_domains] - domains_from_registrar = set(domains_from_registrar) - set(SMALL_SUFFIX_LIST) + psl = PublicSuffixList() + all_domains = ["grimaud.me", "reflexlibre.net", "netlib.re", "noho.st", "nohost.me", "ynh.fr", "test.noho.st", "hub.netlib.re", "sans-nuage.fr", "yunohost.org", "yunohost.local", "free.fr"] + domains_from_registrar = [psl.get_public_suffix(domain) for domain in all_domains] + domains_from_registrar = [domain for domain in domains_from_registrar if "." in domain] + domains_from_registrar = set(domains_from_registrar) - set(PENDING_SUFFIX_LIST) for report in self.check_expiration_date(domains_from_registrar): yield report @@ -159,9 +163,12 @@ class DNSRecordsDiagnoser(Diagnoser): expire_date = self.get_domain_expiration(domain) if isinstance(expire_date, str): - details["not_found"].append(( - "diagnosis_%s_details" % (expire_date), - {"domain": domain})) + status_ns, _ = dig(domain, "NS", resolvers="force_external") + status_a, _ = dig(domain, "A", resolvers="force_external") + if "ok" not in [status_ns, status_a]: + details["not_found"].append(( + "diagnosis_domain_%s_details" % (expire_date), + {"domain": domain})) continue expire_in = expire_date - datetime.now() @@ -199,19 +206,26 @@ class DNSRecordsDiagnoser(Diagnoser): """ Return the expiration datetime of a domain or None """ - # "echo failed" avoid to trigger CalledProcessError - command = "whois -H %s || echo failed" % (domain) + command = "whois -H %s" % (domain) + + # Reduce output to determine if whois answer is equivalent to NOT FOUND out = check_output(command).strip().split("\n") + filtered_out = [line for line in out + if re.search(r'^\w{4,25}:', line, re.IGNORECASE) and + not re.match(r'>>> Last update of whois', line, re.IGNORECASE) and + not re.match(r'^NOTICE:', line, re.IGNORECASE) and + not re.match(r'^%%', line, re.IGNORECASE) and + not re.match(r'"https?:"', line, re.IGNORECASE)] # If there is less 5 lines, it's NOT FOUND response - if len(out) <= 4: - return "domain_not_found" + if len(filtered_out) <= 6: + return "not_found" for line in out: - match = re.search(r'Expir.+(\d{4}-\d{2}-\d{2})', line) + match = re.search(r'Expir.+(\d{4}-\d{2}-\d{2})', line, re.IGNORECASE) if match is not None: return datetime.strptime(match.group(1), '%Y-%m-%d') - return "domain_expiration_not_found" + return "expiration_not_found" def main(args, env, loggers): diff --git a/debian/control b/debian/control index fcffa87f6..5061ad4f2 100644 --- a/debian/control +++ b/debian/control @@ -29,7 +29,7 @@ Depends: ${python:Depends}, ${misc:Depends} , redis-server , metronome , git, curl, wget, cron, unzip, jq - , lsb-release, haveged, fake-hwclock, equivs, lsof, whois + , lsb-release, haveged, fake-hwclock, equivs, lsof, whois, python-publicsuffix Recommends: yunohost-admin , ntp, inetutils-ping | iputils-ping , bash-completion, rsyslog diff --git a/locales/en.json b/locales/en.json index a0e7454f1..3f957c702 100644 --- a/locales/en.json +++ b/locales/en.json @@ -173,7 +173,7 @@ "diagnosis_dns_discrepancy": "The following DNS record does not seem to follow the recommended configuration:
Type: {type}
Name: {name}
Current value: {current}
Excepted value: {value}", "diagnosis_dns_point_to_doc": "Please check the documentation at https://yunohost.org/dns_config if you need help about configuring DNS records.", "diagnosis_domain_expiration_not_found": "Unable to check the expiration date of some domains", - "diagnosis_domain_not_found_details": "The domain {domain} doesn't exist in WHOIS database !", + "diagnosis_domain_not_found_details": "The domain {domain} doesn't exist in WHOIS database or is expired !", "diagnosis_domain_expiration_not_found_details": "The WHOIS returns some info about the domain {domain} but we are not able to found the expiration date inside those info.", "diagnosis_domain_expiration_info": "Domains expiration dates", "diagnosis_domain_expiration_warning": "Some domains expire in less than a month", From 01a6aa13719bf99ed7cf6210b6fa669ee15725ce Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 21 Apr 2020 04:45:16 +0200 Subject: [PATCH 1141/3170] Force-flush the regen-conf for nginx domain conf when adding/removing a domain... --- src/yunohost/domain.py | 32 +++++++++++++++++++++++++++++++- src/yunohost/regenconf.py | 12 ++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 18c4bd8e2..5ef6ef650 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -33,7 +33,7 @@ from yunohost.utils.error import YunohostError from moulinette.utils.log import getActionLogger from yunohost.app import app_ssowatconf -from yunohost.regenconf import regen_conf +from yunohost.regenconf import regen_conf, _force_clear_hashes, _process_regen_conf from yunohost.utils.network import get_public_ip from yunohost.log import is_unit_operation from yunohost.hook import hook_callback @@ -119,6 +119,17 @@ def domain_add(operation_logger, domain, dyndns=False): # Don't regen these conf if we're still in postinstall if os.path.exists('/etc/yunohost/installed'): + # Sometime we have weird issues with the regenconf where some files + # appears as manually modified even though they weren't touched ... + # There are a few ideas why this happens (like backup/restore nginx + # conf ... which we shouldnt do ...). This in turns creates funky + # situation where the regenconf may refuse to re-create the conf + # (when re-creating a domain..) + # So here we force-clear the has out of the regenconf if it exists. + # This is a pretty ad hoc solution and only applied to nginx + # because it's one of the major service, but in the long term we + # should identify the root of this bug... + _force_clear_hashes(["/etc/nginx/conf.d/%s.conf" % domain]) regen_conf(names=['nginx', 'metronome', 'dnsmasq', 'postfix', 'rspamd']) app_ssowatconf() @@ -176,6 +187,25 @@ def domain_remove(operation_logger, domain, force=False): os.system('rm -rf /etc/yunohost/certs/%s' % domain) + # Sometime we have weird issues with the regenconf where some files + # appears as manually modified even though they weren't touched ... + # There are a few ideas why this happens (like backup/restore nginx + # conf ... which we shouldnt do ...). This in turns creates funky + # situation where the regenconf may refuse to re-create the conf + # (when re-creating a domain..) + # + # So here we force-clear the has out of the regenconf if it exists. + # This is a pretty ad hoc solution and only applied to nginx + # because it's one of the major service, but in the long term we + # should identify the root of this bug... + _force_clear_hashes(["/etc/nginx/conf.d/%s.conf" % domain]) + # And in addition we even force-delete the file Otherwise, if the file was + # manually modified, it may not get removed by the regenconf which leads to + # catastrophic consequences of nginx breaking because it can't load the + # cert file which disappeared etc.. + if os.path.exists("/etc/nginx/conf.d/%s.conf" % domain): + _process_regen_conf("/etc/nginx/conf.d/%s.conf" % domain, new_conf=None, save=True) + regen_conf(names=['nginx', 'metronome', 'dnsmasq', 'postfix']) app_ssowatconf() diff --git a/src/yunohost/regenconf.py b/src/yunohost/regenconf.py index b7a42dd9d..fea6dbea7 100644 --- a/src/yunohost/regenconf.py +++ b/src/yunohost/regenconf.py @@ -463,6 +463,18 @@ def _update_conf_hashes(category, hashes): _save_regenconf_infos(categories) +def _force_clear_hashes(paths): + + categories = _get_regenconf_infos() + for path in paths: + for category in categories.keys(): + if path in categories[category]['conffiles']: + logger.debug("force-clearing old conf hash for %s in category %s" % (path, category)) + del categories[category]['conffiles'][path] + + _save_regenconf_infos(categories) + + def _process_regen_conf(system_conf, new_conf=None, save=True): """Regenerate a given system configuration file From 34fd4e90bd3ffcdf5ac067159c16251896dcfc7c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 21 Apr 2020 04:48:13 +0200 Subject: [PATCH 1142/3170] Be more robust against broken config or service failing to start, show info to help debugging --- data/hooks/conf_regen/15-nginx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/data/hooks/conf_regen/15-nginx b/data/hooks/conf_regen/15-nginx index 55a5494b2..87cc9b5b9 100755 --- a/data/hooks/conf_regen/15-nginx +++ b/data/hooks/conf_regen/15-nginx @@ -26,7 +26,8 @@ do_init_regen() { ynh_render_template "yunohost_admin.conf" "${nginx_conf_dir}/yunohost_admin.conf" # Restart nginx if conf looks good, otherwise display error and exit unhappy - nginx -t 2>/dev/null && service nginx restart || (nginx -t && exit 1) + nginx -t 2>/dev/null || { nginx -t; exit 1; } + systemctl restart nginx || { journalctl --no-pager --lines=10 -u nginx >&2; exit 1; } exit 0 } @@ -109,8 +110,9 @@ do_post_regen() { mkdir -p "/etc/nginx/conf.d/${domain}.d" done - # Reload nginx configuration - pgrep nginx && service nginx reload + # Reload nginx if conf looks good, otherwise display error and exit unhappy + nginx -t 2>/dev/null || { nginx -t; exit 1; } + pgrep nginx && systemctl reload nginx || { journalctl --no-pager --lines=10 -u nginx >&2; exit 1; } } FORCE=${2:-0} From 176d0176db3b3b90a1e86eaa9e0ed370fc1f1187 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 24 Apr 2020 03:08:31 +0200 Subject: [PATCH 1143/3170] Be more robust against some situation where archive is corrupted --- locales/en.json | 3 ++- src/yunohost/backup.py | 42 +++++++++++++++++++++++++----------------- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/locales/en.json b/locales/en.json index 6a2af5e41..9ffa35ab6 100644 --- a/locales/en.json +++ b/locales/en.json @@ -88,6 +88,8 @@ "backup_archive_name_exists": "A backup archive with this name already exists.", "backup_archive_name_unknown": "Unknown local backup archive named '{name:s}'", "backup_archive_open_failed": "Could not open the backup archive", + "backup_archive_cant_retrieve_info_json": "Could not load infos for archive '{archive}' ... The info.json cannot be retrieved (or is not a valid json).", + "backup_archive_corrupted": "It looks like the backup archive '{archive}' is corrupted : {error}", "backup_archive_system_part_not_available": "System part '{part:s}' unavailable in this backup", "backup_archive_writing_error": "Could not add the files '{source:s}' (named in the archive '{dest:s}') to be backed up into the compressed archive '{archive:s}'", "backup_ask_for_copying_if_needed": "Do you want to perform the backup using {size:s} MB temporarily? (This way is used since some files could not be prepared using a more efficient method.)", @@ -105,7 +107,6 @@ "backup_delete_error": "Could not delete '{path:s}'", "backup_deleted": "Backup deleted", "backup_hook_unknown": "The backup hook '{hook:s}' is unknown", - "backup_invalid_archive": "This is not a backup archive", "backup_method_borg_finished": "Backup into Borg finished", "backup_method_copy_finished": "Backup copy finalized", "backup_method_custom_finished": "Custom backup method '{method:s}' finished", diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 3344d2807..5e90bce6c 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -871,7 +871,7 @@ class RestoreManager(): Read the info file from inside an archive Exceptions: - backup_invalid_archive -- Raised if we can't read the info + backup_archive_cant_retrieve_info_json -- Raised if we can't read the info """ # Retrieve backup info info_file = os.path.join(self.work_dir, "info.json") @@ -884,7 +884,7 @@ class RestoreManager(): self.info["system"] = self.info["hooks"] except IOError: logger.debug("unable to load '%s'", info_file, exc_info=1) - raise YunohostError('backup_invalid_archive') + raise YunohostError('backup_archive_cant_retrieve_info_json', archive=self.archive_path) else: logger.debug("restoring from backup '%s' created on %s", self.name, datetime.utcfromtimestamp(self.info['created_at'])) @@ -892,10 +892,6 @@ class RestoreManager(): def _postinstall_if_needed(self): """ Post install yunohost if needed - - Exceptions: - backup_invalid_archive -- Raised if the current_host isn't in the - archive """ # Check if YunoHost is installed if not os.path.isfile('/etc/yunohost/installed'): @@ -907,7 +903,7 @@ class RestoreManager(): logger.debug("unable to retrieve current_host from the backup", exc_info=1) # FIXME include the current_host by default ? - raise YunohostError('backup_invalid_archive') + raise YunohostError("The main domain name cannot be retrieved from inside the archive, and is needed to perform the postinstall", raw_msg=True) logger.debug("executing the post-install...") tools_postinstall(domain, 'Yunohost', True) @@ -1924,6 +1920,12 @@ class TarBackupMethod(BackupMethod): self._archive_file, exc_info=1) raise YunohostError('backup_archive_open_failed') + try: + files_in_archive = tar.getnames() + print(files_in_archive) + 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() @@ -1932,21 +1934,21 @@ class TarBackupMethod(BackupMethod): logger.debug(m18n.n("restore_extracting")) tar = tarfile.open(self._archive_file, "r:gz") - if "info.json" in tar.getnames(): + if "info.json" in files_in_archive: leading_dot = "" tar.extract('info.json', path=self.work_dir) - elif "./info.json" in tar.getnames(): + elif "./info.json" in files_in_archive: leading_dot = "./" tar.extract('./info.json', path=self.work_dir) else: logger.debug("unable to retrieve 'info.json' inside the archive", exc_info=1) tar.close() - raise YunohostError('backup_invalid_archive') + raise YunohostError('backup_archive_cant_retrieve_info_json', archive=self._archive_file) - if "backup.csv" in tar.getnames(): + if "backup.csv" in files_in_archive: tar.extract('backup.csv', path=self.work_dir) - elif "./backup.csv" in tar.getnames(): + elif "./backup.csv" in files_in_archive: tar.extract('./backup.csv', path=self.work_dir) else: # Old backup archive have no backup.csv file @@ -2288,7 +2290,7 @@ def backup_list(with_info=False, human_readable=False): try: d[a] = backup_info(a, human_readable=human_readable) except YunohostError as e: - logger.warning('%s: %s' % (a, e.strerror)) + logger.warning(str(e)) result = d @@ -2325,17 +2327,23 @@ def backup_info(name, with_details=False, human_readable=False): if not os.path.exists(info_file): tar = tarfile.open(archive_file, "r:gz") info_dir = info_file + '.d' + try: - if "info.json" in tar.getnames(): + files_in_archive = tar.getnames() + except IOError as e: + raise YunohostError("backup_archive_corrupted", archive=archive_file, error=str(e)) + + try: + if "info.json" in files_in_archive: tar.extract('info.json', path=info_dir) - elif "./info.json" in tar.getnames(): + elif "./info.json" in files_in_archive: tar.extract('./info.json', path=info_dir) else: raise KeyError except KeyError: logger.debug("unable to retrieve '%s' inside the archive", info_file, exc_info=1) - raise YunohostError('backup_invalid_archive') + raise YunohostError('backup_archive_cant_retrieve_info_json', archive=archive_file) else: shutil.move(os.path.join(info_dir, 'info.json'), info_file) finally: @@ -2348,7 +2356,7 @@ def backup_info(name, with_details=False, human_readable=False): info = json.load(f) except: logger.debug("unable to load '%s'", info_file, exc_info=1) - raise YunohostError('backup_invalid_archive') + raise YunohostError('backup_archive_cant_retrieve_info_json', archive=archive_file) # Retrieve backup size size = info.get('size', 0) From aea8b97993084b61fb63f9e8d2a56772bd473de7 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 25 Apr 2020 01:27:20 +0200 Subject: [PATCH 1144/3170] Remove tmp debug print() Co-Authored-By: Kayou --- src/yunohost/backup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 5e90bce6c..452d87361 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -1922,7 +1922,6 @@ class TarBackupMethod(BackupMethod): try: files_in_archive = tar.getnames() - print(files_in_archive) except IOError as e: raise YunohostError("backup_archive_corrupted", archive=self._archive_file, error=str(e)) From d91966ca98a2b4829695560cb4cb99dd46ea61ca Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 27 Apr 2020 23:48:46 +0200 Subject: [PATCH 1145/3170] Update changelog for 3.7.1.2 --- debian/changelog | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/debian/changelog b/debian/changelog index 6245bb4b0..fcef69c4f 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,14 @@ +yunohost (3.7.1.2) stable; urgency=low + + - [fix] Be more robust against some situation where some archives are corrupted + - [fix] Make nginx regen-conf more robust against broken config or service failing to start, show info to help debugging + - [fix] Force-flush the regen-conf for nginx domain conf when adding/removing a domain... + - [fix] app_map : Make sure to return / and not empty string for stuff on domain root + - [fix] Improve ynh_systemd_action to wait for fail2ban to reload + - [fix] Improper use of logger.exception in app.py leading to infamous weird "KeyError: label" + + -- Alexandre Aubin Mon, 27 Apr 2020 23:50:00 +0000 + yunohost (3.7.1.1) stable; urgency=low - [fix] lxc uid number is limited to 65536 by default (0c9a4509) From 575aa674015d3a5c343a102d31d807fb4f04a5dd Mon Sep 17 00:00:00 2001 From: ljf Date: Tue, 28 Apr 2020 00:30:38 +0200 Subject: [PATCH 1146/3170] [fix] whois on co.uk --- data/hooks/diagnosis/12-dnsrecords.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py index c92c2648e..d47e33d33 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -7,11 +7,11 @@ from datetime import datetime, timedelta from publicsuffix import PublicSuffixList from moulinette.utils.filesystem import read_file +from moulinette.utils.process import check_output from yunohost.utils.network import dig from yunohost.diagnosis import Diagnoser from yunohost.domain import domain_list, _build_dns_conf, _get_maindomain -from yunohost.utils.network import dig PENDING_SUFFIX_LIST = ['ynh.fr', 'netlib.re'] @@ -41,7 +41,6 @@ class DNSRecordsDiagnoser(Diagnoser): # Check if a domain buy by the user will expire soon psl = PublicSuffixList() - all_domains = ["grimaud.me", "reflexlibre.net", "netlib.re", "noho.st", "nohost.me", "ynh.fr", "test.noho.st", "hub.netlib.re", "sans-nuage.fr", "yunohost.org", "yunohost.local", "free.fr"] domains_from_registrar = [psl.get_public_suffix(domain) for domain in all_domains] domains_from_registrar = [domain for domain in domains_from_registrar if "." in domain] domains_from_registrar = set(domains_from_registrar) - set(PENDING_SUFFIX_LIST) @@ -211,7 +210,7 @@ class DNSRecordsDiagnoser(Diagnoser): # Reduce output to determine if whois answer is equivalent to NOT FOUND out = check_output(command).strip().split("\n") filtered_out = [line for line in out - if re.search(r'^\w{4,25}:', line, re.IGNORECASE) and + if re.search(r'^[a-zA-Z0-9 ]{4,25}:', line, re.IGNORECASE) and not re.match(r'>>> Last update of whois', line, re.IGNORECASE) and not re.match(r'^NOTICE:', line, re.IGNORECASE) and not re.match(r'^%%', line, re.IGNORECASE) and @@ -225,6 +224,11 @@ class DNSRecordsDiagnoser(Diagnoser): match = re.search(r'Expir.+(\d{4}-\d{2}-\d{2})', line, re.IGNORECASE) if match is not None: return datetime.strptime(match.group(1), '%Y-%m-%d') + + match = re.search(r'Expir.+(\d{2}-\w{3}-\d{4})', line, re.IGNORECASE) + if match is not None: + return datetime.strptime(match.group(1), '%d-%b-%Y') + return "expiration_not_found" From b241c2fa1d552c719e83181940893092bef53316 Mon Sep 17 00:00:00 2001 From: ljf Date: Tue, 28 Apr 2020 00:53:23 +0200 Subject: [PATCH 1147/3170] [enh] Whois not working --- data/hooks/diagnosis/12-dnsrecords.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py index d47e33d33..7ff791823 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -3,6 +3,7 @@ import os import re +from subprocess import CalledProcessError from datetime import datetime, timedelta from publicsuffix import PublicSuffixList @@ -161,13 +162,15 @@ class DNSRecordsDiagnoser(Diagnoser): for domain in domains: expire_date = self.get_domain_expiration(domain) - if isinstance(expire_date, str): + if isinstance(expire_date, str) and expire_date != "not_working": status_ns, _ = dig(domain, "NS", resolvers="force_external") status_a, _ = dig(domain, "A", resolvers="force_external") if "ok" not in [status_ns, status_a]: details["not_found"].append(( "diagnosis_domain_%s_details" % (expire_date), {"domain": domain})) + else: + self.logger_debug("Dyndns domain: %s" % (domain)) continue expire_in = expire_date - datetime.now() @@ -207,8 +210,13 @@ class DNSRecordsDiagnoser(Diagnoser): """ command = "whois -H %s" % (domain) + try: + out = check_output(command).strip().split("\n") + except CalledProcessError as e: + self.logger_warning("Unable to get whois data for %s . Could be due to a rate limit on whois. Error: %s" % (domain, str(e))) + return "not_working" + # Reduce output to determine if whois answer is equivalent to NOT FOUND - out = check_output(command).strip().split("\n") filtered_out = [line for line in out if re.search(r'^[a-zA-Z0-9 ]{4,25}:', line, re.IGNORECASE) and not re.match(r'>>> Last update of whois', line, re.IGNORECASE) and @@ -216,7 +224,7 @@ class DNSRecordsDiagnoser(Diagnoser): not re.match(r'^%%', line, re.IGNORECASE) and not re.match(r'"https?:"', line, re.IGNORECASE)] - # If there is less 5 lines, it's NOT FOUND response + # If there is less than 7 lines, it's NOT FOUND response if len(filtered_out) <= 6: return "not_found" From 196a3d4d477f7cb168e2748715e06ddec74737d3 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 28 Apr 2020 05:36:11 +0200 Subject: [PATCH 1148/3170] Refactor check about apps being installed on domain when trying to remove a domain --- src/yunohost/domain.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 82cbbc322..f1dcefba9 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -25,14 +25,13 @@ """ import os import re -import yaml from moulinette import m18n, msettings from moulinette.core import MoulinetteError from yunohost.utils.error import YunohostError from moulinette.utils.log import getActionLogger -from yunohost.app import app_ssowatconf +from yunohost.app import app_ssowatconf, _installed_apps, _get_app_settings from yunohost.regenconf import regen_conf, _force_clear_hashes, _process_regen_conf from yunohost.utils.network import get_public_ip from yunohost.log import is_unit_operation @@ -180,15 +179,9 @@ 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 - for app in os.listdir('/etc/yunohost/apps/'): - with open('/etc/yunohost/apps/' + app + '/settings.yml') as f: - try: - app_domain = yaml.load(f)['domain'] - except: - continue - else: - if app_domain == domain: - raise YunohostError('domain_uninstall_app_first') + app_settings = [_get_app_settings(app) for app in _installed_apps()] + if any(s["domain"] == domain for s in app_settings): + raise YunohostError('domain_uninstall_app_first') operation_logger.start() ldap = _get_ldap_interface() From 03bc568276d6c8cd84941009269f98ac36431a62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Tue, 28 Apr 2020 15:00:21 +0200 Subject: [PATCH 1149/3170] Fix regression due to merge --- data/helpers.d/setting | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index 814afd95e..abf6ab3d4 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -201,7 +201,12 @@ ynh_permission_create() { local protected ynh_handle_getopts_args "$@" url=${url:-} + additional_urls=${additional_urls:-} + auth_header=${auth_header:-} allowed=${allowed:-} + label=${label:-} + show_tile=${show_tile:-} + protected=${protected:-} if [[ -n $url ]] then @@ -312,6 +317,10 @@ ynh_permission_url() { local clear_urls ynh_handle_getopts_args "$@" url=${url:-} + add_url=${add_url:-} + remove_url=${remove_url:-} + auth_header=${auth_header:-} + clear_urls=${clear_urls:-} if [[ -n $url ]] then @@ -370,6 +379,9 @@ ynh_permission_update() { ynh_handle_getopts_args "$@" add=${add:-} remove=${remove:-} + label=${label:-} + show_tile=${show_tile:-} + protected=${protected:-} if [[ -n $add ]] then From fe5ca2422208c6c8653ba33310ab8ca4260d546a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Thu, 2 Apr 2020 16:17:48 +0200 Subject: [PATCH 1150/3170] Migrate old skipped,unprotected,protected_uris and create permission instead --- data/helpers.d/setting | 31 +++++-- src/yunohost/app.py | 92 ------------------- src/yunohost/backup.py | 9 ++ .../0015_extends_permissions_features_1.py | 47 +++++++++- 4 files changed, 76 insertions(+), 103 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index abf6ab3d4..7edeed588 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -1,5 +1,7 @@ #!/bin/bash +migrate_to_permission_deprecitated_warning="/!\\ Packagers! This app is still using the skipped/protected/unprotected_uris/regex settings which are now obsolete and deprecated... Instead, you should use the new helpers 'ynh_permission_{create,urls,update,delete}' and the 'visitors' group to initialize the public/private access. Check out the documentation at the bottom of yunohost.org/groups_and_permissions to learn how to use the new permission mechanism.\n" + # Get an application setting # # usage: ynh_app_setting_get --app=app --key=key @@ -66,7 +68,7 @@ ynh_app_setting_delete() { # ynh_app_setting() { - if [[ "$1" == "delete" ]] && [[ "$3" =~ ^(unprotected|skipped)_ ]] + if [[ "$1" == "delete" ]] && [[ "$3" =~ ^(unprotected|skipped)_ ]] then current_value=$(ynh_app_setting_get --app=$app --key=$3) fi @@ -89,8 +91,6 @@ else: elif action == "set": if key in ['redirected_urls', 'redirected_regex']: value = yaml.load(value) - if any(key.startswith(word+"_") for word in ["unprotected", "protected", "skipped"]): - sys.stderr.write("/!\\ Packagers! This app is still using the skipped/protected/unprotected_uris/regex settings which are now obsolete and deprecated... Instead, you should use the new helpers 'ynh_permission_{create,urls,update,delete}' and the 'visitors' group to initialize the public/private access. Check out the documentation at the bottom of yunohost.org/groups_and_permissions to learn how to use the new permission mechanism.\n") settings[key] = value else: raise ValueError("action should either be get, set or delete") @@ -102,12 +102,23 @@ EOF # We need this because app temporarily set the app as unprotected to configure it with curl... if [[ "$3" =~ ^(unprotected|skipped)_ ]] then - if [[ "$1" == "set" ]] && [[ "${4:-}" == "/" ]] + if [[ "$1" == "delete" ]] then - ynh_permission_update --permission "main" --add "visitors" - elif [[ "$1" == "delete" ]] && [[ "${current_value:-}" == "/" ]] && [[ -n "$(ynh_app_setting_get --app=$2 --key='is_public' )" ]] - then - ynh_permission_update --permission "main" --remove "visitors" + if [[ "${current_value:-}" == "/" ]] && [[ -n "$(ynh_app_setting_get --app=$2 --key='is_public' )" ]] + then + ynh_permission_update --permission "main" --remove "visitors" + else + if [ "$3" == "skipped_uris" ] && ynh_permission_exists --permission legacy_skipped_uris + then + ynh_permission_delete --permission legacy_skipped_uris + elif [ "$3" == "unprotected_uris" ] && ynh_permission_exists --permission legacy_unprotected_uris + then + ynh_permission_delete --permission legacy_unprotected_uris + elif [ "$3" == "protected_uris" ] && ynh_permission_exists --permission legacy_protected_uris + then + ynh_permission_delete --permission legacy_protected_uris + fi + fi fi fi } @@ -253,7 +264,7 @@ ynh_permission_create() { protected=",protected=False" fi fi - + yunohost tools shell -c "from yunohost.permission import permission_create; permission_create('$app.$permission' $url $additional_urls $auth_header $allowed $label $show_tile $protected , sync_perm=False)" } @@ -294,7 +305,7 @@ ynh_permission_exists() { # Redefine the url associated to a permission # -# usage: ynh_permission_url --permission "permission" [--url "url"] [--add_url "new-url" [ "other-new-url" ]] [--remove_url "old-url" [ "other-old-url"]] +# usage: ynh_permission_url --permission "permission" [--url "url"] [--add_url "new-url" [ "other-new-url" ]] [--remove_url "old-url" [ "other-old-url"]] # [--auth_header true|false][--clear_urls] # | arg: permission - the name for the permission (by default a permission named "main" is removed automatically when the app is removed) # | arg: url - (optional) URL for which access will be allowed/forbidden. diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 038040524..9db9c9918 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1245,98 +1245,6 @@ def app_ssowatconf(): app_settings = read_yaml(APPS_SETTING_PATH + app + '/settings.yml') - ## BEGIN Legacy part ## - - if 'domain' not in app_settings: - continue - if 'path' not in app_settings: - continue - - # This 'no_sso' settings sound redundant to not having $path defined .... - # At least from what I can see, all apps using it don't have a path defined ... - if 'no_sso' in app_settings: - continue - - domain = app_settings['domain'] - path = app_settings['path'].rstrip('/') - - def _sanitized_absolute_url(perm_url): - # Nominal case : url is relative to the app's path - if perm_url.startswith("/"): - perm_domain = domain - perm_path = path + perm_url.rstrip("/") - # Otherwise, the urls starts with a domain name, like domain.tld/foo/bar - # We want perm_domain = domain.tld and perm_path = "/foo/bar" - else: - perm_domain, perm_path = perm_url.split("/", 1) - perm_path = "/" + perm_path.rstrip("/") - - perm_path = perm_path if perm_path.strip() != "" else "/" - - return perm_domain + perm_path - - # Skipped - skipped_urls = [_sanitized_absolute_url(uri) for uri in _get_setting(app_settings, 'skipped_uris')] - skipped_urls += ['re:' + regex for regex in _get_setting(app_settings, 'skipped_regex')] - - # Legacy permission system using (un)protected_uris and _regex managed in app settings... - unprotected_urls = [_sanitized_absolute_url(uri) for uri in _get_setting(app_settings, 'unprotected_uris')] - protected_urls = [_sanitized_absolute_url(uri) for uri in _get_setting(app_settings, 'protected_uris')] - unprotected_urls += ['re:' + regex for regex in _get_setting(app_settings, 'unprotected_regex')] - protected_urls += ['re:' + regex for regex in _get_setting(app_settings, 'protected_regex')] - - if skipped_urls == [] and unprotected_urls == [] and protected_urls == []: - continue - - # Manage compatibility with old protected, unprotected, skipped urls !! - this_app_perms = {name: info for name, info in all_permissions.items() if name.startswith(app + ".")} - for perm_name, perm_info in this_app_perms.items(): - - # Ignore permissions for which there's no url defined - if not perm_info["url"]: - continue - - url = _sanitized_absolute_url(perm_info["url"]) - perm_info["url"] = url - if "visitors" in perm_info["allowed"]: - # Legacy stuff : we remove now protected-urls that might have been declared as unprotected earlier... - protected_urls = [u for u in protected_urls if u != url] - else: - # Legacy stuff : we remove now unprotected-urls / skipped-urls that might have been declared as protected earlier... - unprotected_urls = [u for u in unprotected_urls if u != url] - skipped_urls = [u for u in skipped_urls if u != url] - - # Create special permission for legacy apps - if skipped_urls != []: - permissions[app + ".legacy_skipped_urls"] = { - "users": [], - "label": "Legacy permission - skipped_urls for app :" + app, - "show_tile": False, - "auth_header": False, - "public": True, - "uris": skipped_urls - } - if unprotected_urls != []: - permissions[app + ".legacy_unprotected_urls"] = { - "users": all_permissions[app + '.main']['corresponding_users'], - "label": "Legacy permission - unprotected_urls for app :" + app, - "show_tile": False, - "auth_header": True, - "public": True, - "uris": unprotected_urls - } - if protected_urls != []: - permissions[app + ".legacy_protected_urls"] = { - "users": all_permissions[app + '.main']['corresponding_users'], - "label": "Legacy permission - protected_urls for app :" + app, - "show_tile": False, - "auth_header": True, - "public": False, - "uris": protected_urls - } - - ## END Legacy part ## - # Redirected redirected_urls.update(app_settings.get('redirected_urls', {})) redirected_regex.update(app_settings.get('redirected_regex', {})) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 3530718d2..5018d627e 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -1292,6 +1292,7 @@ class RestoreManager(): restore_app_failed -- Raised if the restore bash script failed """ from yunohost.user import user_group_list + from yunohost.app import app_setting from yunohost.permission import permission_create, permission_delete, user_permission_list, permission_sync_to_user def copytree(src, dst, symlinks=False, ignore=None): @@ -1388,6 +1389,14 @@ class RestoreManager(): setup_group_permission = _get_migration_by_name("setup_group_permission") setup_group_permission.migrate_app_permission(app=app_instance_name) + # Migrate old settings + if app_setting(app, 'skipped_uris') is not None or \ + app_setting(app, 'unprotected_uris') is not None or \ + app_setting(app, 'protected_uris') is not None: + from yunohost.tools import _get_migration_by_name + extends_permissions_features_1 = _get_migration_by_name("extends_permissions_features_1") + extends_permissions_features_1.migrate_skipped_unprotected_protected_uris(app=app_instance_name) + # Prepare env. var. to pass to script env_dict = self._get_env_var(app_instance_name) diff --git a/src/yunohost/data_migrations/0015_extends_permissions_features_1.py b/src/yunohost/data_migrations/0015_extends_permissions_features_1.py index 9bbe8baeb..69511761d 100644 --- a/src/yunohost/data_migrations/0015_extends_permissions_features_1.py +++ b/src/yunohost/data_migrations/0015_extends_permissions_features_1.py @@ -76,6 +76,50 @@ class MyMigration(Migration): }) + def migrate_skipped_unprotected_protected_uris(self, app=None): + logger.info(m18n.n("migration_0015_migrate_old_app_settings")) + apps = _installed_apps() + + if app: + if app not in apps: + logger.error("Can't migrate permission for app %s because it ain't installed..." % app) + apps = [] + else: + apps = [app] + + def _get_setting(app, name): + s = app_setting(app, name) + return s.split(',') if s else [] + + for app in apps: + skipped_urls = [_sanitized_absolute_url(uri) for uri in app_setting(app, 'skipped_uris')] + skipped_urls += ['re:' + regex for regex in app_setting(app, 'skipped_regex')] + unprotected_urls = [_sanitized_absolute_url(uri) for uri in app_setting(app, 'unprotected_uris')] + unprotected_urls += ['re:' + regex for regex in app_setting(app, 'unprotected_regex')] + protected_urls = [_sanitized_absolute_url(uri) for uri in app_setting(app, 'protected_uris')] + protected_urls += ['re:' + regex for regex in app_setting(app, 'protected_regex')] + + if skipped_urls != []: + permission_create(app+".legacy_skipped_uris", additional_urls=skipped_urls, + auth_header=False, label='Legacy permission - skipped_urls for app : ' + app, + show_tile=False, allowed='visitors', protected=True, sync_perm=False) + if unprotected_urls != []: + permission_create(app+".legacy_unprotected_uris", additional_urls=unprotected_urls, + auth_header=True, label='Legacy permission - unprotected_uris for app : ' + app, + show_tile=False, allowed='visitors', protected=True, sync_perm=False) + if protected_urls != []: + permission_create(app+".legacy_protected_uris", additional_urls=protected_urls, + auth_header=True, label='Legacy permission - protected_uris for app : ' + app, + show_tile=False, allowed=permission_list()['permissions']['allowed'], + protected=True, sync_perm=False) + + app_setting(app, 'skipped_uris', delete=True) + app_setting(app, 'unprotected_uris', delete=True) + app_setting(app, 'protected_uris', delete=True) + + permission_sync_to_user() + + def run(self): # FIXME : what do we really want to do here ... @@ -100,7 +144,8 @@ class MyMigration(Migration): # Update LDAP database self.add_new_ldap_attributes() - app_ssowatconf() + # Migrate old settings + self.migrate_skipped_unprotected_protected_uris() except Exception as e: logger.warn(m18n.n("migration_0011_migration_failed_trying_to_rollback")) From d5c61f2d27253bf79e69c39a9034c56d61a50ce3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Thu, 2 Apr 2020 17:11:01 +0200 Subject: [PATCH 1151/3170] Manage skipped, unprotected uris in root path case --- locales/en.json | 1 + .../0015_extends_permissions_features_1.py | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/locales/en.json b/locales/en.json index 40a5eb52b..6df38e3e5 100644 --- a/locales/en.json +++ b/locales/en.json @@ -464,6 +464,7 @@ "migration_0011_update_LDAP_schema": "Updating LDAP schema…", "migration_0011_failed_to_remove_stale_object": "Could not remove stale object {dn}: {error}", "migration_0015_add_new_attributes_in_ldap": "Add new attributes for permissions in LDAP database", + "migration_0015_migrate_old_app_settings": "Migrate old apps settings 'skipped_uris', 'unprotected_uris', 'protected_uris' in permissions system.", "migrations_already_ran": "Those migrations are already done: {ids}", "migrations_cant_reach_migration_file": "Could not access migrations files at the path '%s'", "migrations_dependencies_not_satisfied": "Run these migrations: '{dependencies_id}', before migration {id}.", diff --git a/src/yunohost/data_migrations/0015_extends_permissions_features_1.py b/src/yunohost/data_migrations/0015_extends_permissions_features_1.py index 69511761d..18077e1bf 100644 --- a/src/yunohost/data_migrations/0015_extends_permissions_features_1.py +++ b/src/yunohost/data_migrations/0015_extends_permissions_features_1.py @@ -92,12 +92,12 @@ class MyMigration(Migration): return s.split(',') if s else [] for app in apps: - skipped_urls = [_sanitized_absolute_url(uri) for uri in app_setting(app, 'skipped_uris')] - skipped_urls += ['re:' + regex for regex in app_setting(app, 'skipped_regex')] - unprotected_urls = [_sanitized_absolute_url(uri) for uri in app_setting(app, 'unprotected_uris')] - unprotected_urls += ['re:' + regex for regex in app_setting(app, 'unprotected_regex')] - protected_urls = [_sanitized_absolute_url(uri) for uri in app_setting(app, 'protected_uris')] - protected_urls += ['re:' + regex for regex in app_setting(app, 'protected_regex')] + skipped_urls = [_sanitized_absolute_url(uri) for uri in _get_setting(app, 'skipped_uris') if uri != '/'] + skipped_urls += ['re:' + regex for regex in _get_setting(app, 'skipped_regex')] + unprotected_urls = [_sanitized_absolute_url(uri) for uri in _get_setting(app, 'unprotected_uris') if uri != '/'] + unprotected_urls += ['re:' + regex for regex in _get_setting(app, 'unprotected_regex')] + protected_urls = [_sanitized_absolute_url(uri) for uri in _get_setting(app, 'protected_uris') if uri != '/'] + protected_urls += ['re:' + regex for regex in _get_setting(app, 'protected_regex')] if skipped_urls != []: permission_create(app+".legacy_skipped_uris", additional_urls=skipped_urls, From 79fb034321242c4aa4cbe38d6d4af9cad6179390 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Fri, 3 Apr 2020 21:27:28 +0200 Subject: [PATCH 1152/3170] Improve support of settings migrations --- data/helpers.d/setting | 52 ++++++++--------------- src/yunohost/app.py | 95 ++++++++++++++++++++++++++++++++++++------ 2 files changed, 100 insertions(+), 47 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index 7edeed588..94084e534 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -1,7 +1,5 @@ #!/bin/bash -migrate_to_permission_deprecitated_warning="/!\\ Packagers! This app is still using the skipped/protected/unprotected_uris/regex settings which are now obsolete and deprecated... Instead, you should use the new helpers 'ynh_permission_{create,urls,update,delete}' and the 'visitors' group to initialize the public/private access. Check out the documentation at the bottom of yunohost.org/groups_and_permissions to learn how to use the new permission mechanism.\n" - # Get an application setting # # usage: ynh_app_setting_get --app=app --key=key @@ -18,7 +16,11 @@ ynh_app_setting_get() { # Manage arguments with getopts ynh_handle_getopts_args "$@" - ynh_app_setting "get" "$app" "$key" + if [[ $key =~ '^(unprotected|protected|skipped)_' ]]; then + yunohost app setting $app $key + else + ynh_app_setting "get" "$app" "$key" + fi } # Set an application setting @@ -39,7 +41,12 @@ ynh_app_setting_set() { # Manage arguments with getopts ynh_handle_getopts_args "$@" - ynh_app_setting "set" "$app" "$key" "$value" + # Manage old legacy unprotected,protectedskipped + if [[ $key =~ '^(unprotected|protected|skipped)_' ]]; then + yunohost app setting $app $key $value + else + ynh_app_setting "set" "$app" "$key" "$value" + fi } # Delete an application setting @@ -58,7 +65,13 @@ ynh_app_setting_delete() { # Manage arguments with getopts ynh_handle_getopts_args "$@" - ynh_app_setting "delete" "$app" "$key" + # Fucking legacy permission management. + # We need this because app temporarily set the app as unprotected to configure it with curl... + if [[ "$3" =~ ^(unprotected|skipped|protected)_ ]]; then + yunohost app setting $app $key -d + else + ynh_app_setting "delete" "$app" "$key" + fi } # Small "hard-coded" interface to avoid calling "yunohost app" directly each @@ -68,11 +81,6 @@ ynh_app_setting_delete() { # ynh_app_setting() { - if [[ "$1" == "delete" ]] && [[ "$3" =~ ^(unprotected|skipped)_ ]] - then - current_value=$(ynh_app_setting_get --app=$app --key=$3) - fi - ACTION="$1" APP="$2" KEY="$3" VALUE="${4:-}" python2.7 - < Date: Fri, 3 Apr 2020 22:16:28 +0200 Subject: [PATCH 1153/3170] Fix app_ssowatconf --- src/yunohost/app.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index b3971a989..0ec6dedae 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1306,10 +1306,6 @@ def app_ssowatconf(): redirected_regex = {main_domain + '/yunohost[\/]?$': 'https://' + main_domain + '/yunohost/sso/'} redirected_urls = {} - def _get_setting(settings, name): - s = settings.get(name, None) - return s.split(',') if s else [] - for app in _installed_apps(): app_settings = read_yaml(APPS_SETTING_PATH + app + '/settings.yml') From 60af7e0fe999f9948612f74bb491c08c533ecccf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Fri, 3 Apr 2020 22:17:50 +0200 Subject: [PATCH 1154/3170] Fix typo --- src/yunohost/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 0ec6dedae..0b8c679c0 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1139,7 +1139,7 @@ def app_setting(app, key, value=None, delete=False): if value is None and not delete: try: if any(key.startswith(word+"_") for word in ["unprotected", "protected", "skipped"]): - logger.warning(legacy_settings_warning + logger.warning(legacy_settings_warning) # Well, here there are no solution to manage the root case # so just ignore this case, I don't think that get this setting # The only time that I see this is when we try to migrate to group-permission @@ -1159,7 +1159,7 @@ def app_setting(app, key, value=None, delete=False): if delete: if key in app_settings: if any(key.startswith(word+"_") for word in ["unprotected", "protected", "skipped"]): - logger.warning(legacy_settings_warning + logger.warning(legacy_settings_warning) from permission import user_permission_list, user_permission_update, permission_delete permissions = user_permission_list(full=True, full_path=False)['permissions'] From e27035f8165451b77159ba4c7261ad21744768e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Thu, 16 Apr 2020 16:56:57 +0200 Subject: [PATCH 1155/3170] Fix restore --- src/yunohost/backup.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 5018d627e..9069f265a 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -1390,9 +1390,9 @@ class RestoreManager(): setup_group_permission.migrate_app_permission(app=app_instance_name) # Migrate old settings - if app_setting(app, 'skipped_uris') is not None or \ - app_setting(app, 'unprotected_uris') is not None or \ - app_setting(app, 'protected_uris') is not None: + if app_setting(app_instance_name, 'skipped_uris') is not None or \ + app_setting(app_instance_name, 'unprotected_uris') is not None or \ + app_setting(app_instance_name, 'protected_uris') is not None: from yunohost.tools import _get_migration_by_name extends_permissions_features_1 = _get_migration_by_name("extends_permissions_features_1") extends_permissions_features_1.migrate_skipped_unprotected_protected_uris(app=app_instance_name) From c4f7fc2baca452bd8561bfda072c845daa352c26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Tue, 21 Apr 2020 11:24:25 +0200 Subject: [PATCH 1156/3170] Remove migration of legacy settings in install --- src/yunohost/app.py | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 0b8c679c0..73a2a34d2 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -866,8 +866,6 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu permission_url(app_instance_name + ".main", url='/', sync_perm=False) user_permission_update(app_instance_name + ".main", show_tile=True, sync_perm=False) - _migrate_legacy_permissions(app_instance_name) - permission_sync_to_user() logger.success(m18n.n('installation_complete')) @@ -902,34 +900,6 @@ def dump_app_log_extract_for_debugging(operation_logger): logger.info(line) -def _migrate_legacy_permissions(app): - - from yunohost.permission import user_permission_list, user_permission_update - - # Check if app is apparently using the legacy permission management, defined by the presence of something like - # ynh_app_setting_set on unprotected_uris (or yunohost app setting) - install_script_path = os.path.join(APPS_SETTING_PATH, app, 'scripts/install') - install_script_content = open(install_script_path, "r").read() - if not re.search(r"(yunohost app setting|ynh_app_setting_set) .*(unprotected|skipped)_uris", install_script_content): - return - - app_settings = _get_app_settings(app) - app_perm_currently_allowed = user_permission_list()["permissions"][app + ".main"]["allowed"] - - settings_say_it_should_be_public = (app_settings.get("unprotected_uris", None) == "/" - or app_settings.get("skipped_uris", None) == "/") - - # If the current permission says app is protected, but there are legacy rules saying it should be public... - if app_perm_currently_allowed == ["all_users"] and settings_say_it_should_be_public: - # Make it public - user_permission_update(app + ".main", add="visitors", sync_perm=False) - - # If the current permission says app is public, but there are no setting saying it should be public... - if app_perm_currently_allowed == ["visitors"] and not settings_say_it_should_be_public: - # Make is private - user_permission_update(app + ".main", remove="visitors", sync_perm=False) - - @is_unit_operation() def app_remove(operation_logger, app): """ From e068133b54162703ea3ea9d27a76107cbc9da1f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Tue, 21 Apr 2020 17:14:52 +0200 Subject: [PATCH 1157/3170] Fix apps settings management --- data/helpers.d/setting | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index 94084e534..5152e844a 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -16,7 +16,7 @@ ynh_app_setting_get() { # Manage arguments with getopts ynh_handle_getopts_args "$@" - if [[ $key =~ '^(unprotected|protected|skipped)_' ]]; then + if [[ $key =~ (unprotected|protected|skipped)_ ]]; then yunohost app setting $app $key else ynh_app_setting "get" "$app" "$key" @@ -42,8 +42,8 @@ ynh_app_setting_set() { ynh_handle_getopts_args "$@" # Manage old legacy unprotected,protectedskipped - if [[ $key =~ '^(unprotected|protected|skipped)_' ]]; then - yunohost app setting $app $key $value + if [[ $key =~ (unprotected|protected|skipped)_ ]]; then + yunohost app setting $app $key -v $value else ynh_app_setting "set" "$app" "$key" "$value" fi @@ -67,7 +67,7 @@ ynh_app_setting_delete() { # Fucking legacy permission management. # We need this because app temporarily set the app as unprotected to configure it with curl... - if [[ "$3" =~ ^(unprotected|skipped|protected)_ ]]; then + if [[ "$key" =~ (unprotected|skipped|protected)_ ]]; then yunohost app setting $app $key -d else ynh_app_setting "delete" "$app" "$key" From 76c7bcc69b2ab90fcc927f8b91f1e87d248bbd06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 22 Apr 2020 14:46:26 +0200 Subject: [PATCH 1158/3170] Fix some typos --- src/yunohost/app.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 73a2a34d2..914220a81 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1155,16 +1155,14 @@ def app_setting(app, key, value=None, delete=False): if urls == '/': if key.startswith("unprotected_") or key.startswith("skipped_"): + permission_url(app + ".main", url='/', sync_perm=False) user_permission_update(app + ".main", add="visitors") else: user_permission_update(app + ".main", remove="visitors") else: - # Add re: in case of regex, as we distingish regex by this since the permission + # Add re: in case of regex, as we distingish regex by this since the new permission system if key.endswith('_regex'): - if urls.startswith('/'): - urls = 're:' + urls - else: - urls = 're:/' + urls + urls = 're:' + urls permissions = user_permission_list(full=True, full_path=False)['permissions'] if permission_name in permissions: @@ -1179,15 +1177,15 @@ def app_setting(app, key, value=None, delete=False): new_urls = urls.split(',') + actuals_urls_or_regex # We need to clear urls because in the old setting the new setting override the old one and dont just add some urls - permission_url(clear_url=True, sync_perm=False) - permission_url(add_url=new_urls) + permission_url(permission_name, clear_urls=True, sync_perm=False) + permission_url(permission_name, add_url=new_urls) else: # Let's create a "special" permission for the legacy settings - permission_create(permission=permission, + permission_create(permission=permission_name, # FIXME find a way to limit to only the user allowed to the main permission allowed=['all_users'] if key.startswith('protected_') else ['all_users', 'visitors'], url=None, - additional_urls=url.split(','), + additional_urls=urls.split(','), auth_header=not key.startswith('skipped_'), label="Legacy permission - %s_uris/regex for app : %s" % (key.split('_')[0], app), show_tile=False, From 06bc5ba16f61a8ee60abc101699e6adc3f0f46aa Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Tue, 28 Apr 2020 16:15:52 +0200 Subject: [PATCH 1159/3170] ... --- data/helpers.d/setting | 66 +++++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index abf6ab3d4..7d388de8b 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -158,20 +158,20 @@ ynh_webpath_register () { # Create a new permission for the app # -# example: ynh_permission_create --permission admin --url /admin --additional_urls 'domain.tld/otherurl /superadmin' --allowed alice bob --label 'My app admin' +# example: ynh_permission_create --permission=admin --url=/admin --additional_urls=domain.tld/otherurl /superadmin --allowed=alice bob --label="My app admin" # -# usage: ynh_permission_create --permission "permission" [--url "url"] [--additional_urls "second-url" [ "other-url" ]] [--auth_header true|false] -# [--allowed group1 [ group2 ]] [--label "label"] [--show_tile true|false] -# [--protected true|false] -# | arg: permission - the name for the permission (by default a permission named "main" already exist) -# | arg: url - (optional) URL for which access will be allowed/forbidden -# | arg: additional_urls - (optional) List of additional URL for which access will be allowed/forbidden -# | arg: auth_header - (optional) Define for the URL of this permission, if SSOwat pass the authentication header to the application. Default is true -# | arg: allowed - (optional) A list of group/user to allow for the permission -# | arg: label - (optional) Define a name for the permission. This label will be shown on the SSO and in the admin. -# | Default is "APP_LABEL (permission name)". -# | arg: show_tile - (optional) Define if a tile will be shown in the SSO -# | arg: protected - (optional) Define if this permission is protected. If it is protected the administrator +# usage: ynh_permission_create --permission="permission" [--url="url"] [--additional_urls="second-url" [ "third-url" ]] [--auth_header=true|false] +# [--allowed=group1 [ group2 ]] [--label="label"] [--show_tile=true|false] +# [--protected=true|false] +# | arg: -p, permission= - the name for the permission (by default a permission named "main" already exist) +# | arg: -u, url= - (optional) URL for which access will be allowed/forbidden +# | arg: -A, additional_urls= - (optional) List of additional URL for which access will be allowed/forbidden +# | arg: -h, auth_header= - (optional) Define for the URL of this permission, if SSOwat pass the authentication header to the application. Default is true +# | arg: -a, allowed= - (optional) A list of group/user to allow for the permission +# | arg: -l, label= - (optional) Define a name for the permission. This label will be shown on the SSO and in the admin. +# | Default is "APP_LABEL (permission name)". +# | arg: -t, show_tile= - (optional) Define if a tile will be shown in the SSO +# | arg: -P, protected= - (optional) Define if this permission is protected. If it is protected the administrator # | won't be able to add or remove the visitors group of this permission. # | By default it's 'true' (for the permission different than 'main'). # @@ -190,7 +190,7 @@ ynh_webpath_register () { ynh_permission_create() { # Declare an array to define the options of this helper. local legacy_args=puAhaltP - declare -A args_array=( [p]=permission= [u]=url= [A]=additional_urls= [h]=auth_header= [a]=allowed= [l]=label= [t]=show_tile= [P]=protected= ) + local -A args_array=( [p]=permission= [u]=url= [A]=additional_urls= [h]=auth_header= [a]=allowed= [l]=label= [t]=show_tile= [P]=protected= ) local permission local url local additional_urls @@ -294,21 +294,21 @@ ynh_permission_exists() { # Redefine the url associated to a permission # -# usage: ynh_permission_url --permission "permission" [--url "url"] [--add_url "new-url" [ "other-new-url" ]] [--remove_url "old-url" [ "other-old-url"]] -# [--auth_header true|false][--clear_urls] -# | arg: permission - the name for the permission (by default a permission named "main" is removed automatically when the app is removed) -# | arg: url - (optional) URL for which access will be allowed/forbidden. +# usage: ynh_permission_url --permission "permission" [--url="url"] [--add_url="new-url" [ "other-new-url" ]] [--remove_url="old-url" [ "other-old-url" ]] +# [--auth_header=true|false] [--clear_urls] +# | arg: -p, permission= - the name for the permission (by default a permission named "main" is removed automatically when the app is removed) +# | arg: -u, url= - (optional) URL for which access will be allowed/forbidden. # | Note that if you want to remove url you can pass an empty sting as arguments (""). -# | arg: add_url - (optional) List of additional url to add for which access will be allowed/forbidden. -# | arg: remove_url - (optional) List of additional url to remove for which access will be allowed/forbidden -# | arg: auth_header - (optional) Define for the URL of this permission, if SSOwat pass the authentication header to the application -# | arg: clear_urls - (optional) Clean all urls (url and additional_urls) +# | arg: -a, add_url= - (optional) List of additional url to add for which access will be allowed/forbidden. +# | arg: -r, remove_url= - (optional) List of additional url to remove for which access will be allowed/forbidden +# | arg: -h, auth_header= - (optional) Define for the URL of this permission, if SSOwat pass the authentication header to the application +# | arg: -c, clear_urls - (optional) Clean all urls (url and additional_urls) # # Requires YunoHost version 3.7.0 or higher. ynh_permission_url() { # Declare an array to define the options of this helper. local legacy_args=puarhc - declare -A args_array=([p]=permission= [u]=url= [a]=add_url= [r]=remove_url= [h]=auth_header= [c]=clear_urls) + local -A args_array=( [p]=permission= [u]=url= [a]=add_url= [r]=remove_url= [h]=auth_header= [c]=clear_urls ) local permission local url local add_url @@ -355,21 +355,21 @@ ynh_permission_url() { # Update a permission for the app # -# usage: ynh_permission_update --permission "permission" [--add "group" ["group" ...]] [--remove "group" ["group" ...]] -# [--label "label"] [--show_tile true|false] [--protected true|false] -# | arg: permission - the name for the permission (by default a permission named "main" already exist) -# | arg: add - the list of group or users to enable add to the permission -# | arg: remove - the list of group or users to remove from the permission -# | arg: label - (optional) Define a name for the permission. This label will be shown on the SSO and in the admin. -# | arg: show_tile - (optional) Define if a tile will be shown in the SSO -# | arg: protected - (optional) Define if this permission is protected. If it is protected the administrator +# usage: ynh_permission_update --permission "permission" [--add="group" ["group" ...]] [--remove="group" ["group" ...]] +# [--label="label"] [--show_tile=true|false] [--protected=true|false] +# | arg: -p, permission= - the name for the permission (by default a permission named "main" already exist) +# | arg: -a, add= - the list of group or users to enable add to the permission +# | arg: -r, remove= - the list of group or users to remove from the permission +# | arg: -l, label= - (optional) Define a name for the permission. This label will be shown on the SSO and in the admin. +# | arg: -t, show_tile= - (optional) Define if a tile will be shown in the SSO +# | arg: -P, protected= - (optional) Define if this permission is protected. If it is protected the administrator # | won't be able to add or remove the visitors group of this permission. # # Requires YunoHost version 3.7.0 or higher. ynh_permission_update() { # Declare an array to define the options of this helper. - local legacy_args=parlsp - declare -A args_array=( [p]=permission= [a]=add= [r]=remove= [l]=label= [t]=show_tile= [P]=protected= ) + local legacy_args=parltP + local -A args_array=( [p]=permission= [a]=add= [r]=remove= [l]=label= [t]=show_tile= [P]=protected= ) local permission local add local remove From 2c7a059f1903aeb5cf623af2c83883bc9c12c010 Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Tue, 28 Apr 2020 17:01:11 +0200 Subject: [PATCH 1160/3170] [enh] Add a small comments to explain the pending suffix list --- data/hooks/diagnosis/12-dnsrecords.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py index 7ff791823..8d59538dc 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -14,6 +14,8 @@ from yunohost.utils.network import dig from yunohost.diagnosis import Diagnoser from yunohost.domain import domain_list, _build_dns_conf, _get_maindomain +# We put here domains we know has dyndns provider, but that are not yet +# registered in the public suffix list PENDING_SUFFIX_LIST = ['ynh.fr', 'netlib.re'] From ba73bd03b4b563c9a0ea4b7992db070bac0bb167 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 28 Apr 2020 17:18:25 +0200 Subject: [PATCH 1161/3170] Update postgresql --- data/helpers.d/postgresql | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/data/helpers.d/postgresql b/data/helpers.d/postgresql index a4cb50393..954f44d0b 100644 --- a/data/helpers.d/postgresql +++ b/data/helpers.d/postgresql @@ -276,7 +276,13 @@ ynh_psql_test_if_first_run() { local pg_hba=/etc/postgresql/9.6/main/pg_hba.conf local logfile=/var/log/postgresql/postgresql-9.6-main.log else - ynh_die "postgresql shoud be 9.4 or 9.6 or it could be a problem of locale see https://serverfault.com/questions/426989/postgresql-etc-postgresql-doesnt-exist" + 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 or " + fi + fi ynh_systemd_action --service_name=postgresql --action=start From ae98ec1aa7548f13cf99318bdcfdf89853bb2b52 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 28 Apr 2020 18:55:39 +0200 Subject: [PATCH 1162/3170] Trailing slash in ssowat uris cause issues to access app installed on root, we only need it for app_map ... --- src/yunohost/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 25f856c10..f7e4fd435 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -261,6 +261,8 @@ def app_map(app=None, raw=False, user=None): perm_domain, perm_path = perm_url.split("/", 1) perm_path = "/" + perm_path.rstrip("/") + # N.B. : having '/' instead of empty string is needed in app_map + # but should *not* be done in app_ssowatconf (yeah :[) perm_path = perm_path if perm_path.strip() != "" else "/" return perm_domain, perm_path @@ -1291,8 +1293,6 @@ def app_ssowatconf(): perm_domain, perm_path = perm_url.split("/", 1) perm_path = "/" + perm_path.rstrip("/") - perm_path = perm_path if perm_path.strip() != "" else "/" - return perm_domain + perm_path # Skipped From 3582a5a389d1ffff31d5feb28901364ceb857a56 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 28 Apr 2020 18:55:39 +0200 Subject: [PATCH 1163/3170] Trailing slash in ssowat uris cause issues to access app installed on root, we only need it for app_map ... --- src/yunohost/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 8c52f4928..94e453b1d 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -455,6 +455,8 @@ def app_map(app=None, raw=False, user=None): perm_domain, perm_path = perm_url.split("/", 1) perm_path = "/" + perm_path.rstrip("/") + # N.B. : having '/' instead of empty string is needed in app_map + # but should *not* be done in app_ssowatconf (yeah :[) perm_path = perm_path if perm_path.strip() != "" else "/" return perm_domain, perm_path @@ -1638,8 +1640,6 @@ def app_ssowatconf(): perm_domain, perm_path = perm_url.split("/", 1) perm_path = "/" + perm_path.rstrip("/") - perm_path = perm_path if perm_path.strip() != "" else "/" - return perm_domain + perm_path # Skipped From 3c234c7895e8ed82dddff204e6d54db9fdcccd9a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 28 Apr 2020 18:59:19 +0200 Subject: [PATCH 1164/3170] Update changelog for 3.7.1.3 --- debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian/changelog b/debian/changelog index fcef69c4f..eb7af8f4d 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +yunohost (3.7.1.3) stable; urgency=low + + - [fix] Fix the hotfix about trailing slash, it was breaking access to app installed on domain root.. + + -- Alexandre Aubin Thu, 28 Apr 2020 19:00:00 +0000 + yunohost (3.7.1.2) stable; urgency=low - [fix] Be more robust against some situation where some archives are corrupted From fd967e08795ae82bd0879a7f31f7da02d2bf1f60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Tue, 28 Apr 2020 22:30:35 +0200 Subject: [PATCH 1165/3170] Add more comment about list conversion --- data/helpers.d/setting | 79 +++++++++++++++++++++++++++++++++--------- 1 file changed, 63 insertions(+), 16 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index 7d388de8b..768f1e005 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -170,7 +170,7 @@ ynh_webpath_register () { # | arg: -a, allowed= - (optional) A list of group/user to allow for the permission # | arg: -l, label= - (optional) Define a name for the permission. This label will be shown on the SSO and in the admin. # | Default is "APP_LABEL (permission name)". -# | arg: -t, show_tile= - (optional) Define if a tile will be shown in the SSO +# | arg: -t, show_tile= - (optional) Define if a tile will be shown in the SSO. Default is false (for the permission different than 'main'). # | arg: -P, protected= - (optional) Define if this permission is protected. If it is protected the administrator # | won't be able to add or remove the visitors group of this permission. # | By default it's 'true' (for the permission different than 'main'). @@ -215,7 +215,13 @@ ynh_permission_create() { if [[ -n $additional_urls ]] then - additional_urls=",additional_urls=['${additional_urls//';'/"','"}']" + # Convert a list from getopts to python list + # Note that getopts separate the args with ';' + # By example: + # --additional_urls /urlA /urlB + # will be: + # additional_urls=['/urlA', '/urlB'] + additional_urls=",additional_urls=['${additional_urls//;/\',\'}']" fi if [[ -n $auth_header ]] @@ -228,8 +234,15 @@ ynh_permission_create() { fi fi - if [[ -n $allowed ]]; then - allowed=",allowed=['${allowed//';'/"','"}']" + if [[ -n $allowed ]] + then + # Convert a list from getopts to python list + # Note that getopts separate the args with ';' + # By example: + # --additional_urls /urlA /urlB + # will be: + # additional_urls=['/urlA', '/urlB'] + allowed=",allowed=['${allowed//;/\',\'}']" fi if [[ -n ${label:-} ]]; then @@ -238,16 +251,20 @@ ynh_permission_create() { label=",label='$YNH_APP_LABEL ($permission)'" fi - if [[ -n ${show_tile:-} ]]; then - if [ $show_tile == "true" ]; then + if [[ -n ${show_tile:-} ]] + then + if [ $show_tile == "true" ] + then show_tile=",show_tile=True" else show_tile=",show_tile=False" fi fi - if [[ -n ${protected:-} ]]; then - if [ $protected == "true" ]; then + if [[ -n ${protected:-} ]] + then + if [ $protected == "true" ] + then protected=",protected=True" else protected=",protected=False" @@ -329,15 +346,30 @@ ynh_permission_url() { if [[ -n $add_url ]] then - add_url=",add_url=['${add_url//';'/"','"}']" + # Convert a list from getopts to python list + # Note that getopts separate the args with ';' + # By example: + # --additional_urls /urlA /urlB + # will be: + # additional_urls=['/urlA', '/urlB'] + add_url=",add_url=['${add_url//;/\',\'}']" fi - if [[ -n $remove_url ]]; then - remove_url=",remove_url=['${remove_url//';'/"','"}']" + if [[ -n $remove_url ]] + then + # Convert a list from getopts to python list + # Note that getopts separate the args with ';' + # By example: + # --additional_urls /urlA /urlB + # will be: + # additional_urls=['/urlA', '/urlB'] + remove_url=",remove_url=['${remove_url//;/\',\'}']" fi - if [[ -n $auth_header ]]; then - if [ $auth_header == "true" ]; then + if [[ -n $auth_header ]] + then + if [ $auth_header == "true" ] + then auth_header=",auth_header=True" else auth_header=",auth_header=False" @@ -385,10 +417,22 @@ ynh_permission_update() { if [[ -n $add ]] then + # Convert a list from getopts to python list + # Note that getopts separate the args with ';' + # By example: + # --additional_urls /urlA /urlB + # will be: + # additional_urls=['/urlA', '/urlB'] add=",add=['${add//';'/"','"}']" fi if [[ -n $remove ]] then + # Convert a list from getopts to python list + # Note that getopts separate the args with ';' + # By example: + # --additional_urls /urlA /urlB + # will be: + # additional_urls=['/urlA', '/urlB'] remove=",remove=['${remove//';'/"','"}']" fi @@ -397,8 +441,10 @@ ynh_permission_update() { label=",label='$label'" fi - if [[ -n $show_tile ]]; then - if [ $show_tile == "true" ]; then + if [[ -n $show_tile ]] + then + if [ $show_tile == "true" ] + then show_tile=",show_tile=True" else show_tile=",show_tile=False" @@ -406,7 +452,8 @@ ynh_permission_update() { fi if [[ -n $protected ]]; then - if [ $protected == "true" ]; then + if [ $protected == "true" ] + then protected=",protected=True" else protected=",protected=False" From 4e84b6368851e0bb03632378a05abec78ca56792 Mon Sep 17 00:00:00 2001 From: ljf Date: Wed, 29 Apr 2020 00:23:33 +0200 Subject: [PATCH 1166/3170] [fix] Who is the creator of whois ? #consistency --- data/hooks/diagnosis/12-dnsrecords.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py index 7ff791823..90dc2e04b 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -162,7 +162,7 @@ class DNSRecordsDiagnoser(Diagnoser): for domain in domains: expire_date = self.get_domain_expiration(domain) - if isinstance(expire_date, str) and expire_date != "not_working": + if isinstance(expire_date, str): status_ns, _ = dig(domain, "NS", resolvers="force_external") status_a, _ = dig(domain, "A", resolvers="force_external") if "ok" not in [status_ns, status_a]: @@ -208,13 +208,8 @@ class DNSRecordsDiagnoser(Diagnoser): """ Return the expiration datetime of a domain or None """ - command = "whois -H %s" % (domain) - - try: - out = check_output(command).strip().split("\n") - except CalledProcessError as e: - self.logger_warning("Unable to get whois data for %s . Could be due to a rate limit on whois. Error: %s" % (domain, str(e))) - return "not_working" + command = "whois -H %s || echo failed" % (domain) + out = check_output(command).strip().split("\n") # Reduce output to determine if whois answer is equivalent to NOT FOUND filtered_out = [line for line in out From 0fba21f92495668e84591522fd49e7813e38bab9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 29 Apr 2020 01:07:07 +0200 Subject: [PATCH 1167/3170] 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 76de0bb2e9bfd8b0a26ba0123eb6ce754fac0439 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 29 Apr 2020 02:42:23 +0200 Subject: [PATCH 1168/3170] Remove stale code --- data/hooks/diagnosis/12-dnsrecords.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py index 9db6f88bc..ef720928b 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -3,18 +3,16 @@ import os import re -from subprocess import CalledProcessError from datetime import datetime, timedelta from publicsuffix import PublicSuffixList -from moulinette.utils.filesystem import read_file from moulinette.utils.process import check_output from yunohost.utils.network import dig from yunohost.diagnosis import Diagnoser from yunohost.domain import domain_list, _build_dns_conf, _get_maindomain -# We put here domains we know has dyndns provider, but that are not yet +# We put here domains we know has dyndns provider, but that are not yet # registered in the public suffix list PENDING_SUFFIX_LIST = ['ynh.fr', 'netlib.re'] @@ -27,12 +25,6 @@ class DNSRecordsDiagnoser(Diagnoser): def run(self): - resolvers = read_file("/etc/resolv.dnsmasq.conf").split("\n") - ipv4_resolvers = [r.split(" ")[1] for r in resolvers if r.startswith("nameserver") and ":" not in r] - # FIXME some day ... handle ipv4-only and ipv6-only servers. For now we assume we have at least ipv4 - assert ipv4_resolvers != [], "Uhoh, need at least one IPv4 DNS resolver ..." - - self.resolver = ipv4_resolvers[0] main_domain = _get_maindomain() all_domains = domain_list()["domains"] From f22ac67468ea35a29c42328e16b4298610370d6a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 29 Apr 2020 02:43:37 +0200 Subject: [PATCH 1169/3170] Success for domains not about to expire --- data/hooks/diagnosis/12-dnsrecords.py | 9 ++++----- tests/test_i18n_keys.py | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py index ef720928b..60efcdf9f 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -150,7 +150,7 @@ class DNSRecordsDiagnoser(Diagnoser): "not_found": [], "error": [], "warning": [], - "info": [] + "success": [] } for domain in domains: @@ -169,7 +169,7 @@ class DNSRecordsDiagnoser(Diagnoser): expire_in = expire_date - datetime.now() - alert_type = "info" + alert_type = "success" if expire_in <= timedelta(7): alert_type = "error" elif expire_in <= timedelta(30): @@ -182,16 +182,15 @@ class DNSRecordsDiagnoser(Diagnoser): } details[alert_type].append(("diagnosis_domain_expires_in", args)) - for alert_type in ["error", "warning", "not_found", "info"]: + for alert_type in ["success", "error", "warning", "not_found"]: if details[alert_type]: if alert_type == "not_found": meta = {"test": "domain_not_found"} else: meta = {"test": "domain_expiration"} - # Allow to ignor specifically a single domain + # Allow to ignore specifically a single domain if len(details[alert_type]) == 1: meta["domain"] = details[alert_type][0][1]["domain"] - meta["domain"] = details[alert_type][0][1]["domain"] yield dict(meta=meta, data={}, status=alert_type.upper() if alert_type != "not_found" else "WARNING", diff --git a/tests/test_i18n_keys.py b/tests/test_i18n_keys.py index 6ddfa9c4a..874794e11 100644 --- a/tests/test_i18n_keys.py +++ b/tests/test_i18n_keys.py @@ -119,7 +119,7 @@ def find_expected_string_keys(): for level in ["danger", "thirdparty", "warning"]: yield "confirm_app_install_%s" % level - for errortype in ["not_found", "error", "warning", "info", "not_found_details"]: + for errortype in ["not_found", "error", "warning", "success", "not_found_details"]: yield "diagnosis_domain_expiration_%s" % errortype yield "diagnosis_domain_not_found_details" From b528336fd05f0490a0409940afc186d2ddc68c4a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 29 Apr 2020 02:43:58 +0200 Subject: [PATCH 1170/3170] Update / improve strings --- locales/en.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/locales/en.json b/locales/en.json index 3f957c702..da37f144c 100644 --- a/locales/en.json +++ b/locales/en.json @@ -172,12 +172,12 @@ "diagnosis_dns_missing_record": "According to the recommended DNS configuration, you should add a DNS record with the following info.
Type: {type}
Name: {name}
Value: {value}", "diagnosis_dns_discrepancy": "The following DNS record does not seem to follow the recommended configuration:
Type: {type}
Name: {name}
Current value: {current}
Excepted value: {value}", "diagnosis_dns_point_to_doc": "Please check the documentation at https://yunohost.org/dns_config if you need help about configuring DNS records.", - "diagnosis_domain_expiration_not_found": "Unable to check the expiration date of some domains", - "diagnosis_domain_not_found_details": "The domain {domain} doesn't exist in WHOIS database or is expired !", - "diagnosis_domain_expiration_not_found_details": "The WHOIS returns some info about the domain {domain} but we are not able to found the expiration date inside those info.", - "diagnosis_domain_expiration_info": "Domains expiration dates", - "diagnosis_domain_expiration_warning": "Some domains expire in less than a month", - "diagnosis_domain_expiration_error": "Some domains expire in less than a week", + "diagnosis_domain_expiration_not_found": "Unable to check the expiration date for some domains", + "diagnosis_domain_not_found_details": "The domain {domain} doesn't exist in WHOIS database or is expired!", + "diagnosis_domain_expiration_not_found_details": "The WHOIS information for domain {domain} doesn't seem to contain the information about the expiration date?", + "diagnosis_domain_expiration_success": "Your domains are registered and not going to expire anytime soon.", + "diagnosis_domain_expiration_warning": "Some domains will expire soon!", + "diagnosis_domain_expiration_error": "Some domains will expire VERY SOON!", "diagnosis_domain_expires_in": "{domain} expires in {days} days.", "diagnosis_services_running": "Service {service} is running!", "diagnosis_services_conf_broken": "Configuration is broken for service {service}!", From 415e805f74cc312ca9752c6075f858a64fe00831 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 29 Apr 2020 02:44:39 +0200 Subject: [PATCH 1171/3170] Change threshold to warn earlier about soon-to-expire domain --- data/hooks/diagnosis/12-dnsrecords.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py index 60efcdf9f..560127bb0 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -170,9 +170,9 @@ class DNSRecordsDiagnoser(Diagnoser): expire_in = expire_date - datetime.now() alert_type = "success" - if expire_in <= timedelta(7): + if expire_in <= timedelta(15): alert_type = "error" - elif expire_in <= timedelta(30): + elif expire_in <= timedelta(45): alert_type = "warning" args = { From 31e868e82d3cc328705e5278a05c94537708409a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 29 Apr 2020 03:49:37 +0200 Subject: [PATCH 1172/3170] 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 a3fb329f21e41d9ce5b7d2c9138ef7d9cb7ab6af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 29 Apr 2020 13:37:38 +0200 Subject: [PATCH 1173/3170] Improve comments --- data/helpers.d/setting | 45 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index 768f1e005..3c48bf4cc 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -158,34 +158,69 @@ ynh_webpath_register () { # Create a new permission for the app # -# example: ynh_permission_create --permission=admin --url=/admin --additional_urls=domain.tld/otherurl /superadmin --allowed=alice bob --label="My app admin" +# example 1: ynh_permission_create --permission=admin --url=/admin --additional_urls=domain.tld/admin /superadmin --allowed=alice bob \ +# --label="My app admin" --show_tile=true +# +# This example will create a new permission permission with this following effect: +# - A tile named "My app admin" in the SSO will be available for the users alice and bob. This tile will point to the relative url '/admin'. +# - Only the user alice and bob will have the access to theses following url: /admin, domain.tld/admin, /superadmin +# +# +# example 2: ynh_permission_create --permission=api --url=domain.tld/api --auth_header=false --allowed=visitors \ +# --label="MyApp API" --protected=true +# +# This example will create a new protected permission. So the admin won't be able to add/remove the visitors group of this permission. +# In case of an API with need to be always public it avoid that the admin break anything. +# With this permission all client will be allowed to access to the url 'domain.tld/api'. +# Note that in this case no tile will be show on the SSO. +# Note that the auth_header parameter is to 'false'. So no authentication header will be passed to the application. +# Generally the API is requested by an application and enabling the auth_header has no advantage and could bring some issues in some case. +# So in this case it's better to disable this option for all API. +# # # usage: ynh_permission_create --permission="permission" [--url="url"] [--additional_urls="second-url" [ "third-url" ]] [--auth_header=true|false] # [--allowed=group1 [ group2 ]] [--label="label"] [--show_tile=true|false] # [--protected=true|false] # | arg: -p, permission= - the name for the permission (by default a permission named "main" already exist) -# | arg: -u, url= - (optional) URL for which access will be allowed/forbidden +# | arg: -u, url= - (optional) URL for which access will be allowed/forbidden. +# | Not that if 'show_tile' is enabled, this URL will be the URL of the tile. # | arg: -A, additional_urls= - (optional) List of additional URL for which access will be allowed/forbidden # | arg: -h, auth_header= - (optional) Define for the URL of this permission, if SSOwat pass the authentication header to the application. Default is true # | arg: -a, allowed= - (optional) A list of group/user to allow for the permission # | arg: -l, label= - (optional) Define a name for the permission. This label will be shown on the SSO and in the admin. # | Default is "APP_LABEL (permission name)". -# | arg: -t, show_tile= - (optional) Define if a tile will be shown in the SSO. Default is false (for the permission different than 'main'). +# | arg: -t, show_tile= - (optional) Define if a tile will be shown in the SSO. If yes the name of the tile will be the 'label' parameter. +# | Default is false (for the permission different than 'main'). # | arg: -P, protected= - (optional) Define if this permission is protected. If it is protected the administrator # | won't be able to add or remove the visitors group of this permission. # | By default it's 'true' (for the permission different than 'main'). # -# If provided, 'url' is assumed to be relative to the app domain/path if they +# If provided, 'url' or 'additional_urls' is assumed to be relative to the app domain/path if they # start with '/'. For example: # / -> domain.tld/app # /admin -> domain.tld/app/admin # domain.tld/app/api -> domain.tld/app/api # -# 'url' can be later treated as a regex if it starts with "re:". +# 'url' or 'additional_urls' can be later treated as a regex if it starts with "re:". # For example: # re:/api/[A-Z]*$ -> domain.tld/app/api/[A-Z]*$ # re:domain.tld/app/api/[A-Z]*$ -> domain.tld/app/api/[A-Z]*$ # +# Note that globally the parameter 'url' and 'additional_urls' are same. The only difference is: +# - 'url' is only one url, 'additional_urls' can be a list of urls. There are no limitation of 'additional_urls' +# - 'url' is used for the url of tile in the SSO (if enabled with the 'show_tile' parameter) +# +# +# About the authentication header (auth_header parameter). +# The SSO pass (by default) to the application theses following HTTP header (linked to the authenticated user) to the application: +# - "Auth-User": username +# - "Remote-User": username +# - "Email": user email +# +# Generally this feature is usefull to authenticate automatically the user in the application but in some case the application don't work with theses header and theses header need to be disabled to have the application to work correctly. +# See https://github.com/YunoHost/issues/issues/1420 for more informations +# +# # Requires YunoHost version 3.7.0 or higher. ynh_permission_create() { # Declare an array to define the options of this helper. From 9757ef2dddf635b9e1a91eafcbf543e351c438d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 29 Apr 2020 13:38:11 +0200 Subject: [PATCH 1174/3170] Fix typo --- data/helpers.d/setting | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index 3c48bf4cc..5d2db657c 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -274,9 +274,9 @@ ynh_permission_create() { # Convert a list from getopts to python list # Note that getopts separate the args with ';' # By example: - # --additional_urls /urlA /urlB + # --allowed alice bob # will be: - # additional_urls=['/urlA', '/urlB'] + # allowed=['alice', 'bob'] allowed=",allowed=['${allowed//;/\',\'}']" fi @@ -384,9 +384,9 @@ ynh_permission_url() { # Convert a list from getopts to python list # Note that getopts separate the args with ';' # By example: - # --additional_urls /urlA /urlB + # --add_url /urlA /urlB # will be: - # additional_urls=['/urlA', '/urlB'] + # add_url=['/urlA', '/urlB'] add_url=",add_url=['${add_url//;/\',\'}']" fi @@ -395,9 +395,9 @@ ynh_permission_url() { # Convert a list from getopts to python list # Note that getopts separate the args with ';' # By example: - # --additional_urls /urlA /urlB + # --remove_url /urlA /urlB # will be: - # additional_urls=['/urlA', '/urlB'] + # remove_url=['/urlA', '/urlB'] remove_url=",remove_url=['${remove_url//;/\',\'}']" fi @@ -455,9 +455,9 @@ ynh_permission_update() { # Convert a list from getopts to python list # Note that getopts separate the args with ';' # By example: - # --additional_urls /urlA /urlB + # --add alice bob # will be: - # additional_urls=['/urlA', '/urlB'] + # add=['alice', 'bob'] add=",add=['${add//';'/"','"}']" fi if [[ -n $remove ]] @@ -465,9 +465,9 @@ ynh_permission_update() { # Convert a list from getopts to python list # Note that getopts separate the args with ';' # By example: - # --additional_urls /urlA /urlB + # --remove alice bob # will be: - # additional_urls=['/urlA', '/urlB'] + # remove=['alice', 'bob'] remove=",remove=['${remove//';'/"','"}']" fi From abe421caa87a7d7f99565c51192805f17b1ca344 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 29 Apr 2020 13:42:14 +0200 Subject: [PATCH 1175/3170] Change default value for protected permission to 'false' --- data/helpers.d/setting | 2 +- src/yunohost/permission.py | 2 +- src/yunohost/tests/test_permission.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index 5d2db657c..c3da11de3 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -193,7 +193,7 @@ ynh_webpath_register () { # | Default is false (for the permission different than 'main'). # | arg: -P, protected= - (optional) Define if this permission is protected. If it is protected the administrator # | won't be able to add or remove the visitors group of this permission. -# | By default it's 'true' (for the permission different than 'main'). +# | By default it's 'false' # # If provided, 'url' or 'additional_urls' is assumed to be relative to the app domain/path if they # start with '/'. For example: diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 98a3ffd2b..610d18752 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -260,7 +260,7 @@ def user_permission_info(permission): def permission_create(operation_logger, permission, allowed=None, url=None, additional_urls=None, auth_header=True, label=None, show_tile=False, - protected=True, sync_perm=True): + protected=False, sync_perm=True): """ Create a new permission for a specific application diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index 659e28667..fc86c8dcc 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -352,7 +352,7 @@ def test_permission_create_extra(mocker): # all_users is only enabled by default on .main perms assert "all_users" not in res['site.test']['allowed'] assert res['site.test']['corresponding_users'] == [] - assert res['site.test']['protected'] == True + assert res['site.test']['protected'] == False def test_permission_create_with_specific_user(): From 8deb0838305e7b02123ac2892e79ccc7ca242881 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 29 Apr 2020 14:09:49 +0200 Subject: [PATCH 1176/3170] Regex should be now available --- data/helpers.d/setting | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index c3da11de3..0276ae351 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -201,7 +201,7 @@ ynh_webpath_register () { # /admin -> domain.tld/app/admin # domain.tld/app/api -> domain.tld/app/api # -# 'url' or 'additional_urls' can be later treated as a regex if it starts with "re:". +# 'url' or 'additional_urls' can be treated as a regex if it starts with "re:". # For example: # re:/api/[A-Z]*$ -> domain.tld/app/api/[A-Z]*$ # re:domain.tld/app/api/[A-Z]*$ -> domain.tld/app/api/[A-Z]*$ From c04d3c38069d79abbec5ee060291624fabfcbd13 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 29 Apr 2020 17:13:47 +0200 Subject: [PATCH 1177/3170] Remove comment about old lines that got replaced --- data/helpers.d/apt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index 9e3f26b90..82e3ab40c 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -188,12 +188,10 @@ ynh_package_install_from_equivs () { (cd "$TMPDIR" LC_ALL=C equivs-build ./control 1> /dev/null dpkg --force-depends --install "./${pkgname}_${pkgversion}_all.deb" 2>&1) - # If install fails we use "apt-get check" to try to debug and diagnose possible unmet dependencies - # Note the use of { } which allows to group commands without starting a subshell (otherwise the ynh_die wouldn't exit the current shell). - # Be careful with the syntax : the semicolon + space at the end is important! ynh_package_install --fix-broken || \ - { # If the installation failed + { # If the installation failed + # (the following is ran inside { } to not start a subshell otherwise ynh_die wouldnt exit the original process) # 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')" From b78d72278501641c6f93b1d1d6b55103ccc4624c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 29 Apr 2020 18:01:40 +0200 Subject: [PATCH 1178/3170] Dirty hack to automatically find custom SSH port --- src/yunohost/service.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index b6c93b5ae..a593aac4f 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -24,6 +24,7 @@ Manage services """ import os +import re import time import yaml import subprocess @@ -33,11 +34,12 @@ from datetime import datetime from moulinette import m18n from yunohost.utils.error import YunohostError -from moulinette.utils import log, filesystem +from moulinette.utils.log import getActionLogger +from moulinette.utils.filesystem import read_file MOULINETTE_LOCK = "/var/run/moulinette_yunohost.lock" -logger = log.getActionLogger('yunohost.service') +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): @@ -552,7 +554,7 @@ def _give_lock(action, service, p): def _remove_lock(PID_to_remove): # FIXME ironically not concurrency safe because it's not atomic... - PIDs = filesystem.read_file(MOULINETTE_LOCK).split("\n") + PIDs = read_file(MOULINETTE_LOCK).split("\n") PIDs_to_keep = [PID for PID in PIDs if int(PID) != PID_to_remove] filesystem.write_to_file(MOULINETTE_LOCK, '\n'.join(PIDs_to_keep)) @@ -574,6 +576,11 @@ def _get_services(): if value is None: del services[key] + # Dirty hack to automatically find custom SSH port ... + ssh_port_line = re.findall(r"\bPort *([0-9]{2,5})\b", read_file("/etc/ssh/sshd_config")) + if len(ssh_port_line) == 1: + services["ssh"]["needs_exposed_ports"] = [int(ssh_port_line[0])] + # Stupid hack for postgresql which ain't an official service ... Can't # really inject that info otherwise. Real service we want to check for # status and log is in fact postgresql@x.y-main (x.y being the version) @@ -654,8 +661,6 @@ def _find_previous_log_file(file): """ Find the previous log file """ - import re - splitext = os.path.splitext(file) if splitext[1] == '.gz': file = splitext[0] From e236a2872a0ef54e7cac365ed7e1ba090ec6aa7b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 29 Apr 2020 18:22:38 +0200 Subject: [PATCH 1179/3170] Lul I fuckedup import refactoring --- src/yunohost/service.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index a593aac4f..7c6b28b10 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -35,7 +35,7 @@ from datetime import datetime from moulinette import m18n from yunohost.utils.error import YunohostError from moulinette.utils.log import getActionLogger -from moulinette.utils.filesystem import read_file +from moulinette.utils.filesystem import read_file, append_to_file, write_to_file MOULINETTE_LOCK = "/var/run/moulinette_yunohost.lock" @@ -546,7 +546,7 @@ def _give_lock(action, service, p): # Append the PID to the lock file logger.debug("Giving a lock to PID %s for service %s !" % (str(son_PID), service)) - filesystem.append_to_file(MOULINETTE_LOCK, "\n%s" % str(son_PID)) + append_to_file(MOULINETTE_LOCK, "\n%s" % str(son_PID)) return son_PID @@ -556,7 +556,7 @@ def _remove_lock(PID_to_remove): PIDs = read_file(MOULINETTE_LOCK).split("\n") PIDs_to_keep = [PID for PID in PIDs if int(PID) != PID_to_remove] - filesystem.write_to_file(MOULINETTE_LOCK, '\n'.join(PIDs_to_keep)) + write_to_file(MOULINETTE_LOCK, '\n'.join(PIDs_to_keep)) def _get_services(): From 0b75f5d437af69c9777f47489ef2f6dfabd277c3 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 29 Apr 2020 19:20:29 +0200 Subject: [PATCH 1180/3170] IPv6 resolvers make everything super slow on IPv4-only servers --- data/hooks/diagnosis/10-ip.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/hooks/diagnosis/10-ip.py b/data/hooks/diagnosis/10-ip.py index c0d35278c..ac867efb5 100644 --- a/data/hooks/diagnosis/10-ip.py +++ b/data/hooks/diagnosis/10-ip.py @@ -73,7 +73,7 @@ class IPDiagnoser(Diagnoser): network_interfaces = get_network_interfaces() def get_local_ip(version): - local_ip = {iface:addr[version].split("/")[0] + local_ip = {iface: addr[version].split("/")[0] for iface, addr in network_interfaces.items() if version in addr} if not local_ip: return None @@ -92,7 +92,7 @@ class IPDiagnoser(Diagnoser): data={"global": ipv6, "local": get_local_ip("ipv6")}, status="SUCCESS" if ipv6 else "WARNING", summary="diagnosis_ip_connected_ipv6" if ipv6 else "diagnosis_ip_no_ipv6", - details=["diagnosis_ip_global", "diagnosis_ip_local"] if ipv6 else None) + details=["diagnosis_ip_global", "diagnosis_ip_local"] if ipv6 else ["diagnosis_ip_no_ipv6_tip"]) # TODO / FIXME : add some attempt to detect ISP (using whois ?) ? From 426d93825d5e0afaf5bec24e9e056cbe6e1c11e6 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 29 Apr 2020 19:20:54 +0200 Subject: [PATCH 1181/3170] Add a tip about not having IPv6 --- locales/en.json | 1 + src/yunohost/utils/network.py | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index fcc603f41..1bf83469e 100644 --- a/locales/en.json +++ b/locales/en.json @@ -159,6 +159,7 @@ "diagnosis_ip_no_ipv4": "The server does not have working IPv4.", "diagnosis_ip_connected_ipv6": "The server is connected to the Internet through IPv6 !", "diagnosis_ip_no_ipv6": "The server does not have working IPv6.", + "diagnosis_ip_no_ipv6_tip": "Having a working IPv6 is not mandatory for your server to work, but it is better for the health of the Internet as a whole. IPv6 should usually be automatically configured by the system or your provider if it's available. Otherwise, you might need to configure a few things manually as explained in the documentation here: https://yunohost.org/#/ipv6. If you cannot enable IPv6 or if it seems too technical for you, you can also safely ignore this warning.", "diagnosis_ip_global": "Global IP: {global}", "diagnosis_ip_local": "Local IP: {local}", "diagnosis_ip_not_connected_at_all": "The server does not seem to be connected to the Internet at all!?", diff --git a/src/yunohost/utils/network.py b/src/yunohost/utils/network.py index ef6378692..2da758886 100644 --- a/src/yunohost/utils/network.py +++ b/src/yunohost/utils/network.py @@ -27,7 +27,6 @@ import dns.resolver from moulinette.utils.filesystem import read_file, write_to_file from moulinette.utils.network import download_text from moulinette.utils.process import check_output -from moulinette.utils.filesystem import read_file logger = logging.getLogger('yunohost.utils.network') @@ -113,6 +112,10 @@ def external_resolvers(): if not external_resolvers_: resolv_dnsmasq_conf = read_file("/etc/resolv.dnsmasq.conf").split("\n") external_resolvers_ = [r.split(" ")[1] for r in resolv_dnsmasq_conf if r.startswith("nameserver")] + # We keep only ipv4 resolvers, otherwise on IPv4-only instances, IPv6 + # will be tried anyway resulting in super-slow dig requests that'll wait + # until timeout... + external_resolvers_ = [r for r in external_resolvers_ if ":" not in r] return external_resolvers_ From fd47e45df3589cfbdf5fd9165f29ce4493793c6a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 29 Apr 2020 19:39:46 +0200 Subject: [PATCH 1182/3170] Wording --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 1bf83469e..25712e8cd 100644 --- a/locales/en.json +++ b/locales/en.json @@ -209,7 +209,7 @@ "diagnosis_mail_fcrdns_ok": "Your reverse DNS is correctly configured!", "diagnosis_mail_fcrdns_dns_missing": "No reverse DNS is defined in IPv{ipversion}. Some emails may fail to get delivered or may get flagged as spam.", "diagnosis_mail_fcrdns_nok_details": "You should first try to configure the reverse DNS with {ehlo_domain} in your internet router interface or your hosting provider interface. (Some hosting provider may require you to send them a support ticket for this).", - "diagnosis_mail_fcrdns_nok_alternatives_4": "Some providers won't let you configure your reverse DNS (or their feature might be broken...). If you are experiencing issues because of this, consider the following solutions:
- Some ISP provide the alternative of using a mail server relay though it implies that the relay will be able to spy on your email traffic.
- A privacy-friendly alternative is to use a VPN *with a dedicated public IP* to bypass this kind of limits. See https://yunohost.org/#/vpn_advantage
- Finally, it's also possible to change of provider", + "diagnosis_mail_fcrdns_nok_alternatives_4": "Some providers won't let you configure your reverse DNS (or their feature might be broken...). If you are experiencing issues because of this, consider the following solutions:
- Some ISP provide the alternative of using a mail server relay though it implies that the relay will be able to spy on your email traffic.
- A privacy-friendly alternative is to use a VPN *with a dedicated public IP* to bypass this kind of limits. See https://yunohost.org/#/vpn_advantage
- Or it's possible to switch to a different provider", "diagnosis_mail_fcrdns_nok_alternatives_6": "Some providers won't let you configure your reverse DNS (or their feature might be broken...). If your reverse DNS is correctly configured for IPv4, you can try disabling the use of IPv6 when sending emails by running yunohost settings set smtp.allow_ipv6 -v off. Note: this last solution means that you won't be able to send or receive emails from the few IPv6-only servers out there.", "diagnosis_mail_fcrdns_different_from_ehlo_domain": "The reverse DNS is not correctly configured in IPv{ipversion}. Some emails may fail to get delivered or may get flagged as spam.", "diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "Current reverse DNS: {rdns_domain}
Expected value: {ehlo_domain}", From e8ca600bdc77d12b0f5eca58e151b5d5b7cc2644 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 29 Apr 2020 20:32:07 +0200 Subject: [PATCH 1183/3170] Permission regex is a PCRE regex --- data/helpers.d/setting | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index 0276ae351..031b92610 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -201,7 +201,7 @@ ynh_webpath_register () { # /admin -> domain.tld/app/admin # domain.tld/app/api -> domain.tld/app/api # -# 'url' or 'additional_urls' can be treated as a regex if it starts with "re:". +# 'url' or 'additional_urls' can be treated as a PCRE (lua) regex if it starts with "re:". # For example: # re:/api/[A-Z]*$ -> domain.tld/app/api/[A-Z]*$ # re:domain.tld/app/api/[A-Z]*$ -> domain.tld/app/api/[A-Z]*$ From 554291827fa8f57e0c69f163cebd21ee7cd7c51f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 29 Apr 2020 21:02:59 +0200 Subject: [PATCH 1184/3170] Fix typo --- data/helpers.d/setting | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index 031b92610..004171e05 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -201,7 +201,7 @@ ynh_webpath_register () { # /admin -> domain.tld/app/admin # domain.tld/app/api -> domain.tld/app/api # -# 'url' or 'additional_urls' can be treated as a PCRE (lua) regex if it starts with "re:". +# 'url' or 'additional_urls' can be treated as a PCRE (not lua) regex if it starts with "re:". # For example: # re:/api/[A-Z]*$ -> domain.tld/app/api/[A-Z]*$ # re:domain.tld/app/api/[A-Z]*$ -> domain.tld/app/api/[A-Z]*$ From 822c731086ec18a57f192295cb89ebdf5b11ba07 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 29 Apr 2020 21:48:22 +0200 Subject: [PATCH 1185/3170] Improve default IPv6 route check to cover stuff happening on internet cube --- src/yunohost/utils/network.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/yunohost/utils/network.py b/src/yunohost/utils/network.py index 2da758886..ce2356fcf 100644 --- a/src/yunohost/utils/network.py +++ b/src/yunohost/utils/network.py @@ -58,7 +58,13 @@ def get_public_ip_from_remote_server(protocol=4): # If we are indeed connected in ipv4 or ipv6, we should find a default route routes = check_output("ip -%s route" % protocol).split("\n") - if not any(r.startswith("default") for r in routes): + def is_default_route(r): + # Typically the default route starts with "default" + # But of course IPv6 is more complex ... e.g. on internet cube there's + # no default route but a /3 which acts as a default-like route... + # e.g. 2000:/3 dev tun0 ... + return r.startswith("default") or (":" in r and re.match(r".*/[0-3]$", r.split()[0])) + if not any(is_default_route(r) for r in routes): logger.debug("No default route for IPv%s, so assuming there's no IP address for that version" % protocol) return None From 3d4ef03ad27f8f2c81b6dc305821ca01816373e3 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 29 Apr 2020 22:03:58 +0200 Subject: [PATCH 1186/3170] Dirty hack to check status of ynh-vpnclient for real --- src/yunohost/service.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 7c6b28b10..2f45e28c3 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -581,6 +581,14 @@ def _get_services(): if len(ssh_port_line) == 1: services["ssh"]["needs_exposed_ports"] = [int(ssh_port_line[0])] + # Dirty hack to check the status of ynh-vpnclient + if "ynh-vpnclient" in services: + status_check = "systemctl is-active openvpn@client.service" + if "test_status" not in services["ynh-vpnclient"]: + services["ynh-vpnclient"]["test_status"] = status_check + if "log" not in services["ynh-vpnclient"]: + services["ynh-vpnclient"]["log"] = ["/var/log/ynh-vpnclient.log"] + # Stupid hack for postgresql which ain't an official service ... Can't # really inject that info otherwise. Real service we want to check for # status and log is in fact postgresql@x.y-main (x.y being the version) From 645ed9c211ae026705db4dd1c00be365ea30955a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 29 Apr 2020 23:16:39 +0200 Subject: [PATCH 1187/3170] Update changelog for 3.8.2 --- debian/changelog | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/debian/changelog b/debian/changelog index abf42446e..37a945919 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,38 @@ +yunohost (3.8.2) testing; urgency=low + + ### Diagnosis + + - [fix] Some DNS queries triggered false negatives about CNAME/A record and email blacklisting (#943) + - [enh] Add a check about domain expiration (#944) + - [enh] Dirty hack to automatically find custom SSH port and diagnose it instead of 22 (b78d722) + - [enh] Add a tip / explanation when IPv6 ain't working / available (426d938) + - [fix] Small false-negative about not having IPv6 when it's actually working (822c731) + + ### Helpers + + - [fix] When setting up a new db, corresponding user should be declared as owner (#813) + - [enh] Add dynamic variables to systemd helper (#937) + - [enh] Clean helpers (#947) + - [fix] getopts behaved in weird way when fed empty parameters (#948) + - [fix] Use ynh_port_available in ynh_find_port (#957) + + ### Others + + - [enh] Setup all XMPP components for each "parent" domains (#916) + - [fix] Previous change in Postfix ciphers broke TLS (#949) + - [fix] Update ACME snippet detection following previous change (#950) + - [fix] Trying to install apps with unpatchable legacy helpers was breaking stuff (#954) + - [fix] Patch usage of old 'yunohost tools diagnosis' (#954) + - [enh] Misc optimizations to speed up regen-conf and other things (#958) + - [enh] When sharing logs, also anonymize folder name containing %2e instead of dot (b392efd) + - [enh] Keep track of yunohost version a backup was made from (54cc684) + - [fix] Re-add 'app fetchlist', 'app list -i', 'app list' filter for backward compatibility... (69938c3) + - [i18n] Improve translations for Catalan, German, French, Esperanto, Spanish, Greek, Nepali, Occitan + + Thanks to all contributors <3 ! (Bram, C. Wehrli, Kay0u, Maniack C., Quentí, Zeik0s, amirale qt, ljf, pitchum, tituspijean, xaloc33, Éric G.) + + -- Alexandre Aubin Wed, 29 Apr 2020 23:15:00 +0000 + yunohost (3.8.1.1) testing; urgency=low - [fix] Stupid issue about path in debian/install ... From 7ce56cd867b0939aabf95fcbab344c3f01083a5e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 30 Apr 2020 00:18:12 +0200 Subject: [PATCH 1188/3170] Bad french translation --- locales/fr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/fr.json b/locales/fr.json index 764b6bb10..e9402730d 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -522,7 +522,7 @@ "diagnosis_display_tip_cli": "Vous pouvez exécuter 'yunohost diagnosis show --issues' pour afficher les problèmes détectés.", "diagnosis_failed_for_category": "Échec du diagnostic pour la catégorie '{category}': {error}", "diagnosis_cache_still_valid": "(Le cache est encore valide pour le diagnostic {category}. Il ne sera pas re-diagnostiqué pour le moment!)", - "diagnosis_ignored_issues": "(+ {nb_ignored} questions ignorée(s))", + "diagnosis_ignored_issues": "(+ {nb_ignored} problèmes ignorée(s))", "diagnosis_found_warnings": "Trouvé {warnings} objet(s) pouvant être amélioré(s) pour {category}.", "diagnosis_everything_ok": "Tout semble bien pour {category} !", "diagnosis_failed": "Échec de la récupération du résultat du diagnostic pour la catégorie '{category}' : {error}", From 4f8aa5e338537f211aa459d142b97cfe3585b628 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 30 Apr 2020 02:38:27 +0200 Subject: [PATCH 1189/3170] Propagate route check to ip diagnoser as well :/ --- data/hooks/diagnosis/10-ip.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/data/hooks/diagnosis/10-ip.py b/data/hooks/diagnosis/10-ip.py index ac867efb5..d4c203e7e 100644 --- a/data/hooks/diagnosis/10-ip.py +++ b/data/hooks/diagnosis/10-ip.py @@ -106,8 +106,15 @@ class IPDiagnoser(Diagnoser): # If we are indeed connected in ipv4 or ipv6, we should find a default route routes = check_output("ip -%s route" % protocol).split("\n") - if not any(r.startswith("default") for r in routes): - return False + def is_default_route(r): + # Typically the default route starts with "default" + # But of course IPv6 is more complex ... e.g. on internet cube there's + # no default route but a /3 which acts as a default-like route... + # e.g. 2000:/3 dev tun0 ... + return r.startswith("default") or (":" in r and re.match(r".*/[0-3]$", r.split()[0])) + if not any(is_default_route(r) for r in routes): + logger.debug("No default route for IPv%s, so assuming there's no IP address for that version" % protocol) + return None # We use the resolver file as a list of well-known, trustable (ie not google ;)) IPs that we can ping resolver_file = "/usr/share/yunohost/templates/dnsmasq/plain/resolv.dnsmasq.conf" From aaccb547750076694af33656126eb04bdf0a3ff5 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 30 Apr 2020 02:40:22 +0200 Subject: [PATCH 1190/3170] Hmf, comparison return a warning if swap is exactly 512.. --- 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 417b88ae7..5007f8ede 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"] = "ERROR" item["summary"] = "diagnosis_swap_none" - elif swap.total <= 512 * MB: + elif swap.total < 500 * MB: item["status"] = "WARNING" item["summary"] = "diagnosis_swap_notsomuch" else: From 8de8d0ad6fdefafdb641b5aad83396476a7af3ff Mon Sep 17 00:00:00 2001 From: ljf Date: Thu, 30 Apr 2020 02:44:51 +0200 Subject: [PATCH 1191/3170] [fix] Reverse DNS check --- data/hooks/diagnosis/24-mail.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/data/hooks/diagnosis/24-mail.py b/data/hooks/diagnosis/24-mail.py index a60b4f0d4..f325c72da 100644 --- a/data/hooks/diagnosis/24-mail.py +++ b/data/hooks/diagnosis/24-mail.py @@ -2,7 +2,6 @@ import os import dns.resolver -import socket import re from subprocess import CalledProcessError @@ -118,15 +117,25 @@ class MailDiagnoser(Diagnoser): details = ["diagnosis_mail_fcrdns_nok_details", "diagnosis_mail_fcrdns_nok_alternatives_4"] - try: - rdns_domain, _, _ = socket.gethostbyaddr(ip) - except socket.herror: + rev = dns.reversename.from_address(ip) + subdomain = str(rev.split(3)[0]) + query = subdomain + if ipversion == 4: + query += '.in-addr.arpa' + else: + query += '.ip6.arpa' + + # Do the DNS Query + status, value = dig(query, 'PTR') + if status == "nok": yield dict(meta={"test": "mail_fcrdns", "ipversion": ipversion}, data={"ip": ip, "ehlo_domain": self.ehlo_domain}, status="ERROR", summary="diagnosis_mail_fcrdns_dns_missing", details=details) continue + + rdns_domain = value[0] if len(value) > 0 else '' if rdns_domain != self.ehlo_domain: details = ["diagnosis_mail_fcrdns_different_from_ehlo_domain_details"] + details yield dict(meta={"test": "mail_fcrdns", "ipversion": ipversion}, From 1cb330823d3e1348fc1e350105729d6604890648 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 26 Apr 2020 20:49:09 +0200 Subject: [PATCH 1192/3170] Try to show smarter / more useful logs by filtering irrelevant lines like 'set +x' etc --- data/actionsmap/yunohost.yml | 4 ++++ src/yunohost/app.py | 17 +++++++++++++++++ src/yunohost/log.py | 19 +++++++++++++++++-- src/yunohost/service.py | 10 +++++++++- 4 files changed, 47 insertions(+), 3 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 0ad1268f2..6ccd5ebfe 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1659,6 +1659,10 @@ log: --share: help: Share the full log using yunopaste action: store_true + -f: + full: --filter-irrelevant + help: Do not show some lines deemed not relevant (like set +x or helper argument parsing) + action: store_true ############################# diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 8a29f9dbb..ba3ac4c01 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -914,6 +914,19 @@ def dump_app_log_extract_for_debugging(operation_logger): with open(operation_logger.log_path, "r") as f: lines = f.readlines() + filters = [ + r"set [+-]x$", + r"local \w+$", + r"local legacy_args=.*$", + r".*Helper used in legacy mode.*", + r"args_array=.*$", + r"declare -Ar args_array$", + r"ynh_handle_getopts_args", + r"ynh_script_progression" + ] + + filters = [re.compile(f) for f in filters] + lines_to_display = [] for line in lines: @@ -924,6 +937,10 @@ def dump_app_log_extract_for_debugging(operation_logger): # 2019-10-19 16:10:27,611: DEBUG - + mysql -u piwigo --password=********** -B piwigo # And we just want the part starting by "DEBUG - " line = line.strip().split(": ", 1)[1] + + if any(filter_.search(line) for filter_ in filters): + continue + lines_to_display.append(line) if line.endswith("+ ynh_exit_properly") or " + ynh_die " in line: diff --git a/src/yunohost/log.py b/src/yunohost/log.py index cd08bdfe0..523a10f76 100644 --- a/src/yunohost/log.py +++ b/src/yunohost/log.py @@ -122,7 +122,7 @@ def log_list(category=[], limit=None, with_details=False): return result -def log_display(path, number=None, share=False): +def log_display(path, number=None, share=False, filter_irrelevant=False): """ Display a log file enriched with metadata if any. @@ -202,9 +202,24 @@ def log_display(path, number=None, share=False): # Display logs if exist if os.path.exists(log_path): + + if filter_irrelevant: + filters = [ + r"set [+-]x$", + r"local \w+$", + r"local legacy_args=.*$", + r".*Helper used in legacy mode.*", + r"args_array=.*$", + r"declare -Ar args_array$", + r"ynh_handle_getopts_args", + r"ynh_script_progression" + ] + else: + filters = [] + from yunohost.service import _tail if number: - logs = _tail(log_path, int(number)) + logs = _tail(log_path, int(number), filters=filters) else: logs = read_file(log_path) infos['log_path'] = log_path diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 2f45e28c3..c17eb04c2 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -23,6 +23,8 @@ Manage services """ + +import re import os import re import time @@ -616,7 +618,7 @@ def _save_services(services): raise -def _tail(file, n): +def _tail(file, n, filters=[]): """ Reads a n lines from f with an offset of offset lines. The return value is a tuple in the form ``(lines, has_more)`` where `has_more` is @@ -627,6 +629,9 @@ def _tail(file, n): avg_line_length = 74 to_read = n + if filters: + filters = [re.compile(f) for f in filters] + try: if file.endswith(".gz"): import gzip @@ -647,6 +652,9 @@ def _tail(file, n): pos = f.tell() lines = f.read().splitlines() + for filter_ in filters: + lines = [l for l in lines if not filter_.search(l)] + if len(lines) >= to_read: return lines[-to_read:] From 9a7b9b0b321bb99babc033cc9a3c5b1e8c10a0e4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 27 Apr 2020 02:24:50 +0200 Subject: [PATCH 1193/3170] declare -Ar -> local -A --- 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 ba3ac4c01..7d5eccd30 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -920,7 +920,7 @@ def dump_app_log_extract_for_debugging(operation_logger): r"local legacy_args=.*$", r".*Helper used in legacy mode.*", r"args_array=.*$", - r"declare -Ar args_array$", + r"local -A args_array$", r"ynh_handle_getopts_args", r"ynh_script_progression" ] From a51478dc40f3b17ffc73692029a3003970b89d8e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 27 Apr 2020 02:25:14 +0200 Subject: [PATCH 1194/3170] declare -Ar -> local -A --- src/yunohost/log.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/log.py b/src/yunohost/log.py index 523a10f76..b15a65943 100644 --- a/src/yunohost/log.py +++ b/src/yunohost/log.py @@ -210,7 +210,7 @@ def log_display(path, number=None, share=False, filter_irrelevant=False): r"local legacy_args=.*$", r".*Helper used in legacy mode.*", r"args_array=.*$", - r"declare -Ar args_array$", + r"local -A args_array$", r"ynh_handle_getopts_args", r"ynh_script_progression" ] From e9f359e5f0bb3682d9a286aa2e39c7ac9296e8d1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 30 Apr 2020 05:14:29 +0200 Subject: [PATCH 1195/3170] Call exit 1 directly instead of ynh_die to avoid a full arg parse just to exit.. --- 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 fb50305ce..0a2967363 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -35,7 +35,7 @@ ynh_exit_properly () { ynh_clean_setup # Call the function to do specific cleaning for the app. fi - ynh_die # Exit with error status + exit 1 # Exit with error status } # Exits if an error occurs during the execution of the script. From bb82c41db64e7a341e4cbcceb52bfb972f59128d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 30 Apr 2020 05:21:42 +0200 Subject: [PATCH 1196/3170] Apparently set +x is set +o xtrace now ;P --- src/yunohost/app.py | 1 + src/yunohost/log.py | 1 + 2 files changed, 2 insertions(+) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 7d5eccd30..e2df6ba78 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -916,6 +916,7 @@ def dump_app_log_extract_for_debugging(operation_logger): filters = [ r"set [+-]x$", + r"set [+-]o xtrace$", r"local \w+$", r"local legacy_args=.*$", r".*Helper used in legacy mode.*", diff --git a/src/yunohost/log.py b/src/yunohost/log.py index b15a65943..de84280f0 100644 --- a/src/yunohost/log.py +++ b/src/yunohost/log.py @@ -206,6 +206,7 @@ def log_display(path, number=None, share=False, filter_irrelevant=False): if filter_irrelevant: filters = [ r"set [+-]x$", + r"set [+-]o xtrace$", r"local \w+$", r"local legacy_args=.*$", r".*Helper used in legacy mode.*", From be883c3aef92a207277a1221516b4fea9ffa55bf Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 30 Apr 2020 05:23:23 +0200 Subject: [PATCH 1197/3170] Let's have the short option be -i instead of -f --- data/actionsmap/yunohost.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 6ccd5ebfe..a748e4533 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1659,7 +1659,7 @@ log: --share: help: Share the full log using yunopaste action: store_true - -f: + -i: full: --filter-irrelevant help: Do not show some lines deemed not relevant (like set +x or helper argument parsing) action: store_true From 65d54ba6b90d95d15eb7f01f671248f0f840e7d5 Mon Sep 17 00:00:00 2001 From: ljf Date: Thu, 30 Apr 2020 17:15:48 +0200 Subject: [PATCH 1198/3170] [fix] Blacklist false positive --- src/yunohost/utils/network.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/yunohost/utils/network.py b/src/yunohost/utils/network.py index ce2356fcf..ccd938fd4 100644 --- a/src/yunohost/utils/network.py +++ b/src/yunohost/utils/network.py @@ -131,6 +131,12 @@ def dig(qname, rdtype="A", timeout=5, resolvers="local", edns_size=1500, full_an Do a quick DNS request and avoid the "search" trap inside /etc/resolv.conf """ + # It's very important to do the request with a qname ended by . + # If we don't and the domain fail, dns resolver try a second request + # by concatenate the qname with the end of the "hostname" + if not qname.endswith("."): + qname += "." + if resolvers == "local": resolvers = ["127.0.0.1"] elif resolvers == "force_external": From c33e61ab2ede8a467019d347edae8a99d19ec36a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 30 Apr 2020 17:32:29 +0200 Subject: [PATCH 1199/3170] Update changelog for 3.8.2.1 --- debian/changelog | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/debian/changelog b/debian/changelog index 37a945919..d79900fae 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,13 @@ +yunohost (3.8.2.1) testing; urgency=low + + - [fix] Make sure DNS queries are dong using absolute names to avoid stupid issues + - [fix] More reliable way to fetch PTR record / reverse DNS + - [fix] Propagate IPv6 default route check to ip diagnoser code as well + + Thanks to ljf for the tests and fixes ! + + -- Alexandre Aubin Thu, 30 Apr 2020 17:30:00 +0000 + yunohost (3.8.2) testing; urgency=low ### Diagnosis From e63679684a31dda638b98396e9dd59640d9622d9 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Sun, 12 Apr 2020 23:28:49 +0200 Subject: [PATCH 1200/3170] 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 1201/3170] 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 1202/3170] 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 86810fb68a1729a069b1ac3175790ca2727fcf6d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 30 Apr 2020 18:03:44 +0200 Subject: [PATCH 1203/3170] Goddamit Aleks, check your damn code before release yo --- data/hooks/diagnosis/10-ip.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/data/hooks/diagnosis/10-ip.py b/data/hooks/diagnosis/10-ip.py index d4c203e7e..fe4993935 100644 --- a/data/hooks/diagnosis/10-ip.py +++ b/data/hooks/diagnosis/10-ip.py @@ -1,5 +1,6 @@ #!/usr/bin/env python +import re import os import random @@ -10,6 +11,7 @@ from moulinette.utils.filesystem import read_file from yunohost.diagnosis import Diagnoser from yunohost.utils.network import get_network_interfaces + class IPDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] @@ -72,6 +74,7 @@ class IPDiagnoser(Diagnoser): ipv6 = self.get_public_ip(6) if can_ping_ipv6 else None network_interfaces = get_network_interfaces() + def get_local_ip(version): local_ip = {iface: addr[version].split("/")[0] for iface, addr in network_interfaces.items() if version in addr} @@ -106,6 +109,7 @@ class IPDiagnoser(Diagnoser): # If we are indeed connected in ipv4 or ipv6, we should find a default route routes = check_output("ip -%s route" % protocol).split("\n") + def is_default_route(r): # Typically the default route starts with "default" # But of course IPv6 is more complex ... e.g. on internet cube there's @@ -113,7 +117,7 @@ class IPDiagnoser(Diagnoser): # e.g. 2000:/3 dev tun0 ... return r.startswith("default") or (":" in r and re.match(r".*/[0-3]$", r.split()[0])) if not any(is_default_route(r) for r in routes): - logger.debug("No default route for IPv%s, so assuming there's no IP address for that version" % protocol) + self.logger_debug("No default route for IPv%s, so assuming there's no IP address for that version" % protocol) return None # We use the resolver file as a list of well-known, trustable (ie not google ;)) IPs that we can ping From 15807c411c3cea86743ac3177de694ad4c182ccc Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 30 Apr 2020 17:45:39 +0200 Subject: [PATCH 1204/3170] Bad parenthesis positioning --- src/yunohost/tools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 3208bda60..abfc3b7af 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -598,8 +598,8 @@ def tools_upgrade(operation_logger, apps=None, system=False): ) returncode = call_async_output(dist_upgrade, callbacks, shell=True) if returncode != 0: - logger.warning(m18n.n('tools_upgrade_regular_packages_failed'), - packages_list=', '.join(noncritical_packages_upgradable)) + logger.warning(m18n.n('tools_upgrade_regular_packages_failed', + packages_list=', '.join(noncritical_packages_upgradable))) operation_logger.error(m18n.n('packages_upgrade_failed')) raise YunohostError(m18n.n('packages_upgrade_failed')) From f358bdde19ab0a0f9678a1d43bb8fcd49caaa51a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 30 Apr 2020 18:04:35 +0200 Subject: [PATCH 1205/3170] Update changelog for 3.8.2.2 --- debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian/changelog b/debian/changelog index d79900fae..c119d57e7 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +yunohost (3.8.2.2) testing; urgency=low + + Aleks broke everything /again/ *.* + + -- Alexandre Aubin Thu, 30 Apr 2020 18:05:00 +0000 + yunohost (3.8.2.1) testing; urgency=low - [fix] Make sure DNS queries are dong using absolute names to avoid stupid issues From fd5ba7b1e50b47b45adec14a0913399063c33dec Mon Sep 17 00:00:00 2001 From: Kay0u Date: Wed, 29 Apr 2020 11:18:01 +0200 Subject: [PATCH 1206/3170] 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 572feafc805165419065b1687366a32b3cd4a620 Mon Sep 17 00:00:00 2001 From: ljf Date: Thu, 30 Apr 2020 20:06:43 +0200 Subject: [PATCH 1207/3170] [fix] Remove point in reverse DNS --- data/hooks/diagnosis/24-mail.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/data/hooks/diagnosis/24-mail.py b/data/hooks/diagnosis/24-mail.py index f325c72da..bc159c3b7 100644 --- a/data/hooks/diagnosis/24-mail.py +++ b/data/hooks/diagnosis/24-mail.py @@ -135,7 +135,9 @@ class MailDiagnoser(Diagnoser): details=details) continue - rdns_domain = value[0] if len(value) > 0 else '' + rdns_domain = '' + if len(value) > 0: + rdns_domain = value[0][:-1] if value[0].endswith('.') else value[0] if rdns_domain != self.ehlo_domain: details = ["diagnosis_mail_fcrdns_different_from_ehlo_domain_details"] + details yield dict(meta={"test": "mail_fcrdns", "ipversion": ipversion}, From d6095a3c0f79c44826539ededdbedf2a3e4db8a3 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Thu, 30 Apr 2020 20:10:20 +0200 Subject: [PATCH 1208/3170] Add linter --- .gitlab-ci.yml | 83 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 56 insertions(+), 27 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 05aafe43b..6aac589c1 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,9 +1,25 @@ stages: - postinstall - tests + - lint -.tests: +######################################## +# POSTINSTALL +######################################## + +postinstall: + image: before-postinstall + stage: postinstall + script: + - yunohost tools postinstall -d domain.tld -p the_password --ignore-dyndns + +######################################## +# TESTS +######################################## + +.test-stage: image: after-postinstall + stage: tests before_script: - apt-get install python-pip -y - mkdir -p .pip @@ -25,77 +41,90 @@ stages: - src/yunohost/tests/apps key: "$CI_JOB_STAGE-$CI_COMMIT_REF_SLUG" -postinstall: - image: before-postinstall - stage: postinstall - script: - - yunohost tools postinstall -d domain.tld -p the_password --ignore-dyndns - root-tests: - extends: .tests - stage: tests + extends: .test-stage script: - py.test tests test-apps: - extends: .tests - stage: tests + extends: .test-stage script: - cd src/yunohost - py.test tests/test_apps.py test-appscatalog: - extends: .tests - stage: tests + extends: .test-stage script: - cd src/yunohost - py.test tests/test_appscatalog.py test-appurl: - extends: .tests - stage: tests + extends: .test-stage script: - cd src/yunohost - py.test tests/test_appurl.py test-backuprestore: - extends: .tests - stage: tests + extends: .test-stage script: - cd src/yunohost - py.test tests/test_backuprestore.py test-changeurl: - extends: .tests - stage: tests + extends: .test-stage script: - cd src/yunohost - py.test tests/test_changeurl.py test-permission: - extends: .tests - stage: tests + extends: .test-stage script: - cd src/yunohost - py.test tests/test_permission.py test-settings: - extends: .tests - stage: tests + extends: .test-stage script: - cd src/yunohost - py.test tests/test_settings.py test-user-group: - extends: .tests - stage: tests + extends: .test-stage script: - cd src/yunohost - py.test tests/test_user-group.py test-regenconf: - extends: .tests - stage: tests + extends: .test-stage script: - cd src/yunohost - py.test tests/test_regenconf.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 flake8 blake + cache: + paths: + - .pip + - src/yunohost/tests/apps + key: "$CI_JOB_STAGE-$CI_COMMIT_REF_SLUG" + +lint: + extends: .lint-stage + script: + - flake8 src tests data + +format-check: + extends: .lint-stage + script: + - black {posargs:--check --diff} src tests data \ No newline at end of file From 18580803e61b09ace3ba9b8279538a7b5cb63241 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Thu, 30 Apr 2020 23:40:58 +0200 Subject: [PATCH 1209/3170] use python3 for linter --- .gitlab-ci.yml | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6aac589c1..bd5a320e7 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -108,15 +108,14 @@ test-regenconf: 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 flake8 blake + - apt-get install python3-pip -y + - mkdir -p .pip3 + - pip3 install -U pip + - hash -d pip3 + - pip3 --cache-dir=.pip3 install flake8 black cache: paths: - - .pip - - src/yunohost/tests/apps + - .pip3 key: "$CI_JOB_STAGE-$CI_COMMIT_REF_SLUG" lint: From 81bdb3382465e30c1d44b9bbbcdf5aabe112d453 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Fri, 1 May 2020 00:41:50 +0200 Subject: [PATCH 1210/3170] disable black until buster --- .gitlab-ci.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index bd5a320e7..8248d6caf 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -112,7 +112,7 @@ test-regenconf: - mkdir -p .pip3 - pip3 install -U pip - hash -d pip3 - - pip3 --cache-dir=.pip3 install flake8 black + - pip3 --cache-dir=.pip3 install flake8 # black cache: paths: - .pip3 @@ -123,7 +123,8 @@ lint: script: - flake8 src tests data -format-check: - extends: .lint-stage - script: - - black {posargs:--check --diff} src tests data \ No newline at end of file +# Disabled, waiting for buster +#format-check: +# extends: .lint-stage +# script: +# - black {posargs:--check --diff} src tests data \ No newline at end of file From 4dccab981971052b16bc8776e57527cf3f09c7f5 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Fri, 1 May 2020 00:45:13 +0200 Subject: [PATCH 1211/3170] linter on all the repo --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8248d6caf..d55b02d3a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -121,10 +121,10 @@ test-regenconf: lint: extends: .lint-stage script: - - flake8 src tests data + - flake8 # Disabled, waiting for buster #format-check: # extends: .lint-stage # script: -# - black {posargs:--check --diff} src tests data \ No newline at end of file +# - black --check --diff \ No newline at end of file From 3b93ba47721d0a3234e88b06f6c320ac98405ae7 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Fri, 1 May 2020 01:27:31 +0200 Subject: [PATCH 1212/3170] use tox --- .gitlab-ci.yml | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d55b02d3a..7459ae982 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -108,20 +108,22 @@ test-regenconf: image: before-postinstall stage: lint before_script: - - apt-get install python3-pip -y - - mkdir -p .pip3 - - pip3 install -U pip - - hash -d pip3 - - pip3 --cache-dir=.pip3 install flake8 # black + - apt-get install python-pip -y + - mkdir -p .pip + - pip install -U pip + - hash -d pip + - pip --cache-dir=.pip install tox cache: paths: - - .pip3 + - .pip + - .tox key: "$CI_JOB_STAGE-$CI_COMMIT_REF_SLUG" lint: extends: .lint-stage + allow_failure: true script: - - flake8 + - tox -e lint # Disabled, waiting for buster #format-check: From 6bd7eb64bd3aa4d57e3656afa6d519174138c455 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 1 May 2020 17:53:07 +0200 Subject: [PATCH 1213/3170] Assert slapd is running to avoid miserably crashing with weird ldap errors --- src/yunohost/utils/ldap.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/yunohost/utils/ldap.py b/src/yunohost/utils/ldap.py index 22e95ad07..b7111a6cd 100644 --- a/src/yunohost/utils/ldap.py +++ b/src/yunohost/utils/ldap.py @@ -19,8 +19,10 @@ """ +import os import atexit from moulinette.authenticators import ldap +from yunohost.utils.error import YunohostError # We use a global variable to do some caching # to avoid re-authenticating in case we call _get_ldap_authenticator multiple times @@ -32,6 +34,8 @@ 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', @@ -45,6 +49,13 @@ def _get_ldap_interface(): return _ldap_interface +def assert_slapd_is_running(): + + # Assert slapd is running... + if not os.system("pgrep slapd >dev/null") == 0: + 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'") + + # We regularly want to extract stuff like 'bar' in ldap path like # foo=bar,dn=users.example.org,ou=example.org,dc=org so this small helper allow # to do this without relying of dozens of mysterious string.split()[0] From acba1c4bc82203b9f6fa1b5b168f36e6ce45bcfb Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 2 May 2020 02:13:09 +0200 Subject: [PATCH 1214/3170] Comment about why not using ynh_die --- data/helpers.d/utils | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 0a2967363..bad157fe1 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -35,7 +35,10 @@ ynh_exit_properly () { ynh_clean_setup # Call the function to do specific cleaning for the app. fi - exit 1 # Exit with error status + # Exit with error status + # We don't call ynh_die basically to avoid unecessary 10-ish + # debug lines about parsing args and stuff just to exit 1.. + exit 1 } # Exits if an error occurs during the execution of the script. From e85a29fbf3ee64ce3018a4eb77aa956e5dfd2b28 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 2 May 2020 02:34:28 +0200 Subject: [PATCH 1215/3170] Typo zblerg ~.~ --- src/yunohost/utils/ldap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/utils/ldap.py b/src/yunohost/utils/ldap.py index b7111a6cd..fd984ce56 100644 --- a/src/yunohost/utils/ldap.py +++ b/src/yunohost/utils/ldap.py @@ -52,7 +52,7 @@ def _get_ldap_interface(): def assert_slapd_is_running(): # Assert slapd is running... - if not os.system("pgrep slapd >dev/null") == 0: + if not os.system("pgrep slapd >/dev/null") == 0: 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'") From fc07468051b831286661905b4882f7ac36af356e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 2 May 2020 06:08:54 +0200 Subject: [PATCH 1216/3170] 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 1217/3170] 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 8559fb646559fa9099f6e28c8e1bf36532c2c569 Mon Sep 17 00:00:00 2001 From: Kayou Date: Sat, 2 May 2020 14:21:29 +0200 Subject: [PATCH 1218/3170] [enh] ynh_get_ram --- data/helpers.d/hardware | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/data/helpers.d/hardware b/data/helpers.d/hardware index 6702a8548..0771c81d8 100644 --- a/data/helpers.d/hardware +++ b/data/helpers.d/hardware @@ -25,17 +25,17 @@ ynh_get_ram () { free=${free:-0} total=${total:-0} - local total_ram=$(vmstat --stats --unit M | grep "total memory" | awk '{print $1}') - local total_swap=$(vmstat --stats --unit M | grep "total swap" | awk '{print $1}') - local total_ram_swap=$(( total_ram + total_swap )) - - local free_ram=$(vmstat --stats --unit M | grep "free memory" | awk '{print $1}') - local free_swap=$(vmstat --stats --unit M | grep "free swap" | awk '{print $1}') - local free_ram_swap=$(( free_ram + free_swap )) - - # Use the total amount of ram - if [ $free -eq 1 ] + if [ $free -eq $total ] then + ynh_print_warn --message="You have to choose --free or --total when using ynh_get_ram" + ram=0 + # Use the total amount of ram + elif [ $free -eq 1 ] + then + local free_ram=$(vmstat --stats --unit M | grep "free memory" | awk '{print $1}') + local free_swap=$(vmstat --stats --unit M | grep "free swap" | awk '{print $1}') + local free_ram_swap=$(( free_ram + free_swap )) + # Use the total amount of free ram local ram=$free_ram_swap if [ $ignore_swap -eq 1 ] @@ -49,6 +49,10 @@ ynh_get_ram () { fi elif [ $total -eq 1 ] then + local total_ram=$(vmstat --stats --unit M | grep "total memory" | awk '{print $1}') + local total_swap=$(vmstat --stats --unit M | grep "total swap" | awk '{print $1}') + local total_ram_swap=$(( total_ram + total_swap )) + local ram=$total_ram_swap if [ $ignore_swap -eq 1 ] then @@ -59,9 +63,6 @@ ynh_get_ram () { # Use only the amount of free swap ram=$total_swap fi - else - ynh_print_warn --message="You have to choose --free or --total when using ynh_get_ram" - ram=0 fi echo $ram From 843e88c67d1daad426489154a64790691db5f907 Mon Sep 17 00:00:00 2001 From: Kayou Date: Sat, 2 May 2020 14:50:00 +0200 Subject: [PATCH 1219/3170] [Epic Fix] ynh_install_app_dependencies --- data/helpers.d/apt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index 82e3ab40c..45e03b3cb 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -264,7 +264,7 @@ ynh_install_app_dependencies () { # Pin this sury repository to prevent sury of doing shit ynh_pin_repo --package="*" --pin="origin \"packages.sury.org\"" --priority=200 --name=extra_php_version - ynh_pin_repo --package="php${$YNH_DEFAULT_PHP_VERSION}*" --pin="origin \"packages.sury.org\"" --priority=600 --name=extra_php_version --append + ynh_pin_repo --package="php${YNH_DEFAULT_PHP_VERSION}*" --pin="origin \"packages.sury.org\"" --priority=600 --name=extra_php_version --append fi fi fi From 25a1e569213b429e55ba6eb9fb2ad0aab7782f22 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 3 May 2020 00:17:01 +0200 Subject: [PATCH 1220/3170] Misc tweak for disk usage diagnosis, some values were inconsistent / bad UX / ... --- data/hooks/diagnosis/50-systemresources.py | 44 ++++++++++------------ 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/data/hooks/diagnosis/50-systemresources.py b/data/hooks/diagnosis/50-systemresources.py index 5007f8ede..66d27866a 100644 --- a/data/hooks/diagnosis/50-systemresources.py +++ b/data/hooks/diagnosis/50-systemresources.py @@ -61,40 +61,36 @@ class SystemResourcesDiagnoser(Diagnoser): # Disks usage # - disk_partitions = psutil.disk_partitions() + disk_partitions = sorted(psutil.disk_partitions(), key=lambda k: k.mountpoint) for disk_partition in disk_partitions: device = disk_partition.device mountpoint = disk_partition.mountpoint usage = psutil.disk_usage(mountpoint) - free_percent = round_(100 - usage.percent) + free_percent = 100 - round_(usage.percent) item = dict(meta={"test": "diskusage", "mountpoint": mountpoint}, - data={"device": device, "total": human_size(usage.total), "free": human_size(usage.free), "free_percent": free_percent}) + data={"device": device, + # N.B.: we do not use usage.total because we want + # to take into account the 5% security margin + # correctly (c.f. the doc of psutil ...) + "total": human_size(usage.used+usage.free), + "free": human_size(usage.free), + "free_percent": free_percent}) - # Special checks for /boot partition because they sometimes are - # pretty small and that's kind of okay... (for example on RPi) - if mountpoint.startswith("/boot"): - if usage.free < 10 * MB or free_percent < 10: - item["status"] = "ERROR" - item["summary"] = "diagnosis_diskusage_verylow" - elif usage.free < 20 * MB or free_percent < 20: - item["status"] = "WARNING" - item["summary"] = "diagnosis_diskusage_low" - else: - item["status"] = "SUCCESS" - item["summary"] = "diagnosis_diskusage_ok" + # We have an additional absolute constrain on / and /var because + # system partitions are critical, having them full may prevent + # upgrades etc... + if free_percent < 2.5 or (mountpoint in ["/", "/var"] and usage.free < 1 * GB): + item["status"] = "ERROR" + item["summary"] = "diagnosis_diskusage_verylow" + elif free_percent < 5 or (mountpoint in ["/", "/var"] and usage.free < 2 * GB): + item["status"] = "WARNING" + item["summary"] = "diagnosis_diskusage_low" else: - if usage.free < 1 * GB or free_percent < 5: - item["status"] = "ERROR" - item["summary"] = "diagnosis_diskusage_verylow" - elif usage.free < 2 * GB or free_percent < 10: - item["status"] = "WARNING" - item["summary"] = "diagnosis_diskusage_low" - else: - item["status"] = "SUCCESS" - item["summary"] = "diagnosis_diskusage_ok" + item["status"] = "SUCCESS" + item["summary"] = "diagnosis_diskusage_ok" yield item From 71e30f5b1be5411846e7f12692d71fe7b0a05cf8 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 3 May 2020 00:55:53 +0200 Subject: [PATCH 1221/3170] Simplify / improve robustness of backup list, follow-up of issue reported on the forum... --- src/yunohost/backup.py | 33 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 51aa7d6cd..d059170e9 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -2270,34 +2270,25 @@ def backup_list(with_info=False, human_readable=False): human_readable -- Print sizes in human readable format """ - result = [] + # Get local archives sorted according to last modification time + archives = sorted(glob("%s/*.tar.gz" % ARCHIVES_PATH), key=lambda x: os.path.getctime(x)) + # Extract only filename without the extension + archives = [os.path.basename(f)[:-len(".tar.gz")] for f in archives] - try: - # Retrieve local archives - archives = os.listdir(ARCHIVES_PATH) - except OSError: - logger.debug("unable to iterate over local archives", exc_info=1) - else: - # Iterate over local archives - for f in archives: - try: - name = f[:f.rindex('.tar.gz')] - except ValueError: - continue - result.append(name) - result.sort(key=lambda x: os.path.getctime(os.path.join(ARCHIVES_PATH, x + ".tar.gz"))) - - if result and with_info: + if with_info: d = OrderedDict() - for a in result: + for archive in archives: try: - d[a] = backup_info(a, human_readable=human_readable) + d[archive] = backup_info(archive, human_readable=human_readable) except YunohostError as e: logger.warning(str(e)) + except Exception as e: + import traceback + logger.warning("Could not check infos for archive %s: %s" % (archive, '\n'+traceback.format_exc())) - result = d + archives = d - return {'archives': result} + return {'archives': archives} def backup_info(name, with_details=False, human_readable=False): From cab6fb8b782be15e0f84b758cfb8d53bbf2cc0e5 Mon Sep 17 00:00:00 2001 From: Kayou Date: Sun, 3 May 2020 12:11:47 +0200 Subject: [PATCH 1222/3170] Update logging --- data/helpers.d/logging | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/logging b/data/helpers.d/logging index c79090e25..d5f4f5eec 100644 --- a/data/helpers.d/logging +++ b/data/helpers.d/logging @@ -216,7 +216,7 @@ base_time=$(date +%s) # | arg: -m, --message= - The text to print # | arg: -w, --weight= - The weight for this progression. This value is 1 by default. Use a bigger value for a longer part of the script. # | arg: -t, --time - Print the execution time since the last call to this helper. Especially usefull to define weights. The execution time is given for the duration since the previous call. So the weight should be applied to this previous call. -# | arg: -l, --last - Use for the last call of the helper, to fill te progression bar. +# | arg: -l, --last - Use for the last call of the helper, to fill the progression bar. # # Requires YunoHost version 3.5.0 or higher. ynh_script_progression () { From 0b1103faf85019696d0e4bf5b660c5303f7fac2f Mon Sep 17 00:00:00 2001 From: Marco Cirillo Date: Sun, 3 May 2020 16:40:24 +0200 Subject: [PATCH 1223/3170] 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 d29bf04e7c4840e35b993a280dad1d81aeb7c534 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 3 May 2020 17:37:33 +0200 Subject: [PATCH 1224/3170] Add a timeout to wget in helpers --- data/helpers.d/apt | 3 ++- data/helpers.d/utils | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index 82e3ab40c..5c7c5454b 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -436,7 +436,8 @@ ynh_install_extra_repo () { if [ -n "$key" ] then mkdir --parents "/etc/apt/trusted.gpg.d" - wget --quiet "$key" --output-document=- | gpg --dearmor | $wget_append /etc/apt/trusted.gpg.d/$name.gpg > /dev/null + # Timeout option is here to enforce the timeout on dns query and tcp connect (c.f. man wget) + wget --timeout 900 --quiet "$key" --output-document=- | gpg --dearmor | $wget_append /etc/apt/trusted.gpg.d/$name.gpg > /dev/null fi # Update the list of package with the new repo diff --git a/data/helpers.d/utils b/data/helpers.d/utils index bad157fe1..9c2f40618 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -144,7 +144,8 @@ ynh_setup_source () { then # Use the local source file if it is present cp $local_src $src_filename else # If not, download the source - local out=`wget --no-verbose --output-document=$src_filename $src_url 2>&1` || ynh_print_err --message="$out" + # Timeout option is here to enforce the timeout on dns query and tcp connect (c.f. man wget) + local out=`wget --timeout 900 --no-verbose --output-document=$src_filename $src_url 2>&1` || ynh_print_err --message="$out" fi # Check the control sum From 9c0ccd0b4f8a6f0820165faa96bbcad3dc0c7630 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 3 May 2020 19:46:21 +0200 Subject: [PATCH 1225/3170] 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 1226/3170] 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 1227/3170] 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 b4447bf2b790ce1eeddfc08bf8c65588bd7e7b80 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 3 May 2020 20:14:42 +0200 Subject: [PATCH 1228/3170] [pep8] black test_apps.py --- src/yunohost/tests/test_apps.py | 113 ++++++++++++++++++++++---------- 1 file changed, 79 insertions(+), 34 deletions(-) diff --git a/src/yunohost/tests/test_apps.py b/src/yunohost/tests/test_apps.py index 7c0861aa1..2b41d5ef5 100644 --- a/src/yunohost/tests/test_apps.py +++ b/src/yunohost/tests/test_apps.py @@ -9,10 +9,20 @@ from conftest import message, raiseYunohostError from moulinette import m18n from moulinette.utils.filesystem import mkdir -from yunohost.app import app_install, app_remove, app_ssowatconf, _is_installed, app_upgrade, app_map +from yunohost.app import ( + app_install, + app_remove, + app_ssowatconf, + _is_installed, + app_upgrade, + app_map, +) from yunohost.domain import _get_maindomain, domain_add, domain_remove, domain_list from yunohost.utils.error import YunohostError -from yunohost.tests.test_permission import check_LDAP_db_integrity, check_permission_for_apps +from yunohost.tests.test_permission import ( + check_LDAP_db_integrity, + check_permission_for_apps, +) from yunohost.permission import user_permission_list, permission_delete @@ -20,10 +30,12 @@ def setup_function(function): clean() + def teardown_function(function): clean() + def clean(): # Make sure we have a ssowat @@ -44,10 +56,18 @@ def clean(): for folderpath in glob.glob("/var/www/*%s*" % test_app): shutil.rmtree(folderpath, ignore_errors=True) - os.system("bash -c \"mysql -u root --password=$(cat /etc/yunohost/mysql) 2>/dev/null <<< 'DROP DATABASE %s' \"" % test_app) - os.system("bash -c \"mysql -u root --password=$(cat /etc/yunohost/mysql) 2>/dev/null <<< 'DROP USER %s@localhost'\"" % test_app) + os.system( + "bash -c \"mysql -u root --password=$(cat /etc/yunohost/mysql) 2>/dev/null <<< 'DROP DATABASE %s' \"" + % test_app + ) + os.system( + "bash -c \"mysql -u root --password=$(cat /etc/yunohost/mysql) 2>/dev/null <<< 'DROP USER %s@localhost'\"" + % test_app + ) - os.system("systemctl reset-failed nginx") # Reset failed quota for service to avoid running into start-limit rate ? + os.system( + "systemctl reset-failed nginx" + ) # Reset failed quota for service to avoid running into start-limit rate ? os.system("systemctl start nginx") # Clean permissions @@ -55,6 +75,7 @@ def clean(): if any(test_app in permission_name for test_app in test_apps): permission_delete(permission_name, force=True) + @pytest.fixture(autouse=True) def check_LDAP_db_integrity_call(): check_LDAP_db_integrity() @@ -68,6 +89,7 @@ def check_permission_for_apps_call(): yield check_permission_for_apps() + @pytest.fixture(scope="module") def secondary_domain(request): @@ -76,6 +98,7 @@ def secondary_domain(request): def remove_example_domain(): domain_remove("example.test") + request.addfinalizer(remove_example_domain) return "example.test" @@ -85,6 +108,7 @@ def secondary_domain(request): # Helpers # # + def app_expected_files(domain, app): yield "/etc/nginx/conf.d/%s.d/%s.conf" % (domain, app) @@ -98,18 +122,27 @@ def app_expected_files(domain, app): def app_is_installed(domain, app): - return _is_installed(app) and all(os.path.exists(f) for f in app_expected_files(domain, app)) + return _is_installed(app) and all( + os.path.exists(f) for f in app_expected_files(domain, app) + ) def app_is_not_installed(domain, app): - return not _is_installed(app) and not all(os.path.exists(f) for f in app_expected_files(domain, app)) + return not _is_installed(app) and not all( + os.path.exists(f) for f in app_expected_files(domain, app) + ) def app_is_exposed_on_http(domain, path, message_in_page): try: - r = requests.get("http://127.0.0.1" + path + "/", headers={"Host": domain}, timeout=10, verify=False) + r = requests.get( + "http://127.0.0.1" + path + "/", + headers={"Host": domain}, + timeout=10, + verify=False, + ) return r.status_code == 200 and message_in_page in r.text except Exception as e: return False @@ -117,23 +150,27 @@ def app_is_exposed_on_http(domain, path, message_in_page): def install_legacy_app(domain, path, public=True): - app_install("./tests/apps/legacy_app_ynh", - args="domain=%s&path=%s&is_public=%s" % (domain, path, 1 if public else 0), - force=True) + app_install( + "./tests/apps/legacy_app_ynh", + args="domain=%s&path=%s&is_public=%s" % (domain, path, 1 if public else 0), + force=True, + ) def install_full_domain_app(domain): - app_install("./tests/apps/full_domain_app_ynh", - args="domain=%s" % domain, - force=True) + app_install( + "./tests/apps/full_domain_app_ynh", args="domain=%s" % domain, force=True + ) def install_break_yo_system(domain, breakwhat): - app_install("./tests/apps/break_yo_system_ynh", - args="domain=%s&breakwhat=%s" % (domain, breakwhat), - force=True) + app_install( + "./tests/apps/break_yo_system_ynh", + args="domain=%s&breakwhat=%s" % (domain, breakwhat), + force=True, + ) def test_legacy_app_install_main_domain(): @@ -144,9 +181,9 @@ def test_legacy_app_install_main_domain(): app_map_ = app_map(raw=True) assert main_domain in app_map_ - assert '/legacy' in app_map_[main_domain] - assert 'id' in app_map_[main_domain]['/legacy'] - assert app_map_[main_domain]['/legacy']['id'] == 'legacy_app' + assert "/legacy" in app_map_[main_domain] + assert "id" in app_map_[main_domain]["/legacy"] + assert app_map_[main_domain]["/legacy"]["id"] == "legacy_app" assert app_is_installed(main_domain, "legacy_app") assert app_is_exposed_on_http(main_domain, "/legacy", "This is a dummy app") @@ -174,9 +211,9 @@ def test_legacy_app_install_secondary_domain_on_root(secondary_domain): app_map_ = app_map(raw=True) assert secondary_domain in app_map_ - assert '/' in app_map_[secondary_domain] - assert 'id' in app_map_[secondary_domain]['/'] - assert app_map_[secondary_domain]['/']['id'] == 'legacy_app' + assert "/" in app_map_[secondary_domain] + assert "id" in app_map_[secondary_domain]["/"] + assert app_map_[secondary_domain]["/"]["id"] == "legacy_app" assert app_is_installed(secondary_domain, "legacy_app") assert app_is_exposed_on_http(secondary_domain, "/", "This is a dummy app") @@ -191,7 +228,9 @@ def test_legacy_app_install_private(secondary_domain): install_legacy_app(secondary_domain, "/legacy", public=False) assert app_is_installed(secondary_domain, "legacy_app") - assert not app_is_exposed_on_http(secondary_domain, "/legacy", "This is a dummy app") + assert not app_is_exposed_on_http( + secondary_domain, "/legacy", "This is a dummy app" + ) app_remove("legacy_app") @@ -246,7 +285,9 @@ def test_legacy_app_install_with_nginx_down(mocker, secondary_domain): os.system("systemctl stop nginx") - with raiseYunohostError(mocker, "app_action_cannot_be_ran_because_required_services_down"): + with raiseYunohostError( + mocker, "app_action_cannot_be_ran_because_required_services_down" + ): install_legacy_app(secondary_domain, "/legacy") @@ -257,7 +298,7 @@ def test_legacy_app_failed_install(mocker, secondary_domain): mkdir("/var/www/legacy_app/", 0o750) with pytest.raises(YunohostError): - with message(mocker, 'app_install_script_failed'): + with message(mocker, "app_install_script_failed"): install_legacy_app(secondary_domain, "/legacy") assert app_is_not_installed(secondary_domain, "legacy_app") @@ -302,7 +343,7 @@ def test_systemfuckedup_during_app_install(mocker, secondary_domain): with pytest.raises(YunohostError): with message(mocker, "app_install_failed"): - with message(mocker, 'app_action_broke_system'): + with message(mocker, "app_action_broke_system"): install_break_yo_system(secondary_domain, breakwhat="install") assert app_is_not_installed(secondary_domain, "break_yo_system") @@ -313,8 +354,8 @@ def test_systemfuckedup_during_app_remove(mocker, secondary_domain): install_break_yo_system(secondary_domain, breakwhat="remove") with pytest.raises(YunohostError): - with message(mocker, 'app_action_broke_system'): - with message(mocker, 'app_removed'): + with message(mocker, "app_action_broke_system"): + with message(mocker, "app_removed"): app_remove("break_yo_system") assert app_is_not_installed(secondary_domain, "break_yo_system") @@ -324,7 +365,7 @@ def test_systemfuckedup_during_app_install_and_remove(mocker, secondary_domain): with pytest.raises(YunohostError): with message(mocker, "app_install_failed"): - with message(mocker, 'app_action_broke_system'): + with message(mocker, "app_action_broke_system"): install_break_yo_system(secondary_domain, breakwhat="everything") assert app_is_not_installed(secondary_domain, "break_yo_system") @@ -335,7 +376,7 @@ def test_systemfuckedup_during_app_upgrade(mocker, secondary_domain): install_break_yo_system(secondary_domain, breakwhat="upgrade") with pytest.raises(YunohostError): - with message(mocker, 'app_action_broke_system'): + with message(mocker, "app_action_broke_system"): app_upgrade("break_yo_system", file="./tests/apps/break_yo_system_ynh") @@ -346,6 +387,10 @@ def test_failed_multiple_app_upgrade(mocker, secondary_domain): with pytest.raises(YunohostError): with message(mocker, "app_not_upgraded"): - app_upgrade(["break_yo_system", "legacy_app"], - file={"break_yo_system": "./tests/apps/break_yo_system_ynh", - "legacy": "./tests/apps/legacy_app_ynh"}) + app_upgrade( + ["break_yo_system", "legacy_app"], + file={ + "break_yo_system": "./tests/apps/break_yo_system_ynh", + "legacy": "./tests/apps/legacy_app_ynh", + }, + ) From 67fe8545dc1bf53c6af88021caf6cddb6b23a1ae Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 3 May 2020 20:14:54 +0200 Subject: [PATCH 1229/3170] [mod] remove useless import --- src/yunohost/tests/test_apps.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/yunohost/tests/test_apps.py b/src/yunohost/tests/test_apps.py index 2b41d5ef5..4af29912c 100644 --- a/src/yunohost/tests/test_apps.py +++ b/src/yunohost/tests/test_apps.py @@ -6,7 +6,6 @@ import requests from conftest import message, raiseYunohostError -from moulinette import m18n from moulinette.utils.filesystem import mkdir from yunohost.app import ( From f760d6aa0f88f9a9bc340884389bc04b4174c8ed Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 3 May 2020 20:15:33 +0200 Subject: [PATCH 1230/3170] [mod] remove unused import --- src/yunohost/tests/test_apps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/tests/test_apps.py b/src/yunohost/tests/test_apps.py index 4af29912c..c2c7b8415 100644 --- a/src/yunohost/tests/test_apps.py +++ b/src/yunohost/tests/test_apps.py @@ -143,7 +143,7 @@ def app_is_exposed_on_http(domain, path, message_in_page): verify=False, ) return r.status_code == 200 and message_in_page in r.text - except Exception as e: + except Exception: return False From e80fe075e640c595be409fa0ac1d1f7ea5a89fcc Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 3 May 2020 22:17:15 +0200 Subject: [PATCH 1231/3170] [tests/mod] auto clone/pull the test app when running tests --- .gitlab-ci.yml | 10 +--------- src/yunohost/tests/conftest.py | 10 ++++++++++ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7459ae982..ac3584630 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -26,14 +26,6 @@ postinstall: - pip install -U pip - hash -d pip - pip --cache-dir=.pip install pytest pytest-sugar pytest-mock requests-mock mock - - pushd src/yunohost/tests - - > - if [ ! -d "./apps" ]; then - git clone https://github.com/YunoHost/test_apps ./apps - fi - - cd apps - - git pull > /dev/null 2>&1 - - popd - export PYTEST_ADDOPTS="--color=yes" cache: paths: @@ -129,4 +121,4 @@ lint: #format-check: # extends: .lint-stage # script: -# - black --check --diff \ No newline at end of file +# - black --check --diff diff --git a/src/yunohost/tests/conftest.py b/src/yunohost/tests/conftest.py index bd1702571..073c880f8 100644 --- a/src/yunohost/tests/conftest.py +++ b/src/yunohost/tests/conftest.py @@ -1,3 +1,4 @@ +import os import pytest import sys import moulinette @@ -9,6 +10,15 @@ from contextlib import contextmanager sys.path.append("..") +@pytest.fixture(scope="session", autouse=True) +def clone_test_app(request): + cwd = os.path.split(os.path.realpath(__file__))[0] + + if not os.path.exists(cwd + "/apps"): + os.system("git clone https://github.com/YunoHost/test_apps %s/apps" % cwd) + else: + os.system("cd %s/apps && git pull > /dev/null 2>&1" % cwd) + @contextmanager def message(mocker, key, **kwargs): From 121b40879fc3d84a1eed07af392f0cefd76ccee7 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Mon, 4 May 2020 00:21:02 +0200 Subject: [PATCH 1232/3170] [tests/fix] add markers to pytest.ini to please pytest --- pytest.ini | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pytest.ini b/pytest.ini index f9200ab9c..fb4d5b265 100644 --- a/pytest.ini +++ b/pytest.ini @@ -2,3 +2,11 @@ addopts = -s -v norecursedirs = dist doc build .tox .eggs testpaths = tests/ +markers = + with_system_archive_from_2p4 + with_backup_recommended_app_installed + clean_opt_dir + with_wordpress_archive_from_2p4 + with_legacy_app_installed + with_backup_recommended_app_installed_with_ynh_restore + with_permission_app_installed From 82c4357421de8a422fc3ba7df5eaa639f2ee5990 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Mon, 4 May 2020 14:00:22 +0200 Subject: [PATCH 1233/3170] [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 64066f85b0ba6da48945a6c842a1c49f84fdd6d3 Mon Sep 17 00:00:00 2001 From: ljf Date: Tue, 13 Aug 2019 22:49:01 +0200 Subject: [PATCH 1234/3170] [enh] Allow admin to specify an smtp relay --- data/hooks/conf_regen/19-postfix | 12 +++++++++++- data/templates/postfix/main.cf | 20 ++++++++++++++++++++ src/yunohost/settings.py | 4 ++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/data/hooks/conf_regen/19-postfix b/data/hooks/conf_regen/19-postfix index 68afe4bc9..235923b3d 100755 --- a/data/hooks/conf_regen/19-postfix +++ b/data/hooks/conf_regen/19-postfix @@ -23,7 +23,17 @@ do_pre_regen() { # Support different strategy for security configurations export compatibility="$(yunohost settings get 'security.postfix.compatibility')" - + + # Add possibility to specify a relay + # Could be useful with some isp with no 25 port open or more complex setup + export relay_host="$(yunohost settings get 'smtp.relay.host')" + if [ ! -z "${relay_host}" ]; then + export relay_port="$(yunohost settings get 'smtp.relay.port')" + export relay_user="$(yunohost settings get 'smtp.relay.user')" + relay_password="$(yunohost settings get 'smtp.relay.password')" + echo "[${relay_host}]:${relay_port} ${relay_user}:${relay_password}" > /etc/postfix/sasl_passwd + postmap /etc/postfix/sasl_passwd + fi export main_domain export domain_list="$YNH_DOMAINS" ynh_render_template "main.cf" "${postfix_dir}/main.cf" diff --git a/data/templates/postfix/main.cf b/data/templates/postfix/main.cf index 61cbfa2e6..8121ad3d9 100644 --- a/data/templates/postfix/main.cf +++ b/data/templates/postfix/main.cf @@ -72,7 +72,11 @@ alias_maps = hash:/etc/aliases alias_database = hash:/etc/aliases mydomain = {{ main_domain }} mydestination = localhost +{% if relay_host == "" %} relayhost = +{% else %} +relayhost = [{{ relay_host }}]:{{ relay_port }} +{% endif %} mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 mailbox_command = procmail -a "$EXTENSION" mailbox_size_limit = 0 @@ -178,3 +182,19 @@ default_destination_rate_delay = 5s # So it's easly possible to scan a server to know which email adress is valid # and after to send spam disable_vrfy_command = yes + +{% if relay_user != "" %} +# Relay email through an other smtp account +# enable SASL authentication +smtp_sasl_auth_enable = yes +# disallow methods that allow anonymous authentication. +smtp_sasl_security_options = noanonymous +# where to find sasl_passwd +smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd +{% if relay_port == "587" %} +# Enable STARTTLS encryption +smtp_use_tls = yes +{% endif %} +# where to find CA certificates +smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt +{% endif %} diff --git a/src/yunohost/settings.py b/src/yunohost/settings.py index c1edadb93..f40bb61af 100644 --- a/src/yunohost/settings.py +++ b/src/yunohost/settings.py @@ -71,6 +71,10 @@ DEFAULTS = OrderedDict([ "choices": ["intermediate", "modern"]}), ("pop3.enabled", {"type": "bool", "default": False}), ("smtp.allow_ipv6", {"type": "bool", "default": True}), + ("smtp.relay.host", {"type": "string", "default": ""}), + ("smtp.relay.port", {"type": "int", "default": 587}), + ("smtp.relay.user", {"type": "string", "default": ""}), + ("smtp.relay.password", {"type": "string", "default": ""}), ]) From 3a0104861ed04c554abfc57c4e52c9b7f020fe51 Mon Sep 17 00:00:00 2001 From: ljf Date: Wed, 29 Apr 2020 00:42:57 +0200 Subject: [PATCH 1235/3170] [fix] Don't modify directly files in regen conf --- data/hooks/conf_regen/19-postfix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/hooks/conf_regen/19-postfix b/data/hooks/conf_regen/19-postfix index 235923b3d..69790fd39 100755 --- a/data/hooks/conf_regen/19-postfix +++ b/data/hooks/conf_regen/19-postfix @@ -31,8 +31,8 @@ do_pre_regen() { export relay_port="$(yunohost settings get 'smtp.relay.port')" export relay_user="$(yunohost settings get 'smtp.relay.user')" relay_password="$(yunohost settings get 'smtp.relay.password')" - echo "[${relay_host}]:${relay_port} ${relay_user}:${relay_password}" > /etc/postfix/sasl_passwd - postmap /etc/postfix/sasl_passwd + echo "[${relay_host}]:${relay_port} ${relay_user}:${relay_password}" > ${postfix_dir}/sasl_passwd + postmap ${postfix_dir}/sasl_passwd fi export main_domain export domain_list="$YNH_DOMAINS" From fae6b3f3f474c7ba13d9e6f38ea8bb7270ec6ee6 Mon Sep 17 00:00:00 2001 From: ljf Date: Wed, 29 Apr 2020 00:53:52 +0200 Subject: [PATCH 1236/3170] [fix] Unrelevant obsolete config params --- data/templates/postfix/main.cf | 6 ------ 1 file changed, 6 deletions(-) diff --git a/data/templates/postfix/main.cf b/data/templates/postfix/main.cf index 8121ad3d9..b15964241 100644 --- a/data/templates/postfix/main.cf +++ b/data/templates/postfix/main.cf @@ -191,10 +191,4 @@ smtp_sasl_auth_enable = yes smtp_sasl_security_options = noanonymous # where to find sasl_passwd smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd -{% if relay_port == "587" %} -# Enable STARTTLS encryption -smtp_use_tls = yes -{% endif %} -# where to find CA certificates -smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt {% endif %} From c1fddb312dec74c1d471279819a5bdc297bc8ea0 Mon Sep 17 00:00:00 2001 From: ljf Date: Wed, 29 Apr 2020 01:11:25 +0200 Subject: [PATCH 1237/3170] [enh] Add settings description --- locales/en.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/locales/en.json b/locales/en.json index 25712e8cd..e81505efd 100644 --- a/locales/en.json +++ b/locales/en.json @@ -322,6 +322,10 @@ "global_settings_unknown_setting_from_settings_file": "Unknown key in settings: '{setting_key:s}', discard it and save it in /etc/yunohost/settings-unknown.json", "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Allow the use of (deprecated) DSA hostkey for the SSH daemon configuration", "global_settings_setting_smtp_allow_ipv6": "Allow the use of IPv6 to receive and send mail", + "global_settings_setting_smtp_relay_host": "SMTP relay host to use in order to send mail instead of this yunohost instance. Useful if you are in one of this situation: your 25 port is blocked by your ISP or VPS provider, you have a residential IP listed on DUHL, you are not able to configure reverse DNS or this server is not directly exposed on the internet and you want use an other one to send mails.", + "global_settings_setting_smtp_relay_port": "SMTP relay port", + "global_settings_setting_smtp_relay_user": "SMTP relay user account", + "global_settings_setting_smtp_relay_password": "SMTP relay host password", "global_settings_unknown_type": "Unexpected situation, the setting {setting:s} appears to have the type {unknown_type:s} but it is not a type supported by the system.", "good_practices_about_admin_password": "You are now about to define a new administration password. The password should be at least 8 characters long—though it is good practice to use a longer password (i.e. a passphrase) and/or to use a variation of characters (uppercase, lowercase, digits and special characters).", "good_practices_about_user_password": "You are now about to define a new user password. The password should be at least 8 characters long—though it is good practice to use a longer password (i.e. a passphrase) and/or to a variation of characters (uppercase, lowercase, digits and special characters).", From 94eb9246bbed517d003c410aa4253da8b1e8ce64 Mon Sep 17 00:00:00 2001 From: ljf Date: Wed, 29 Apr 2020 03:12:52 +0200 Subject: [PATCH 1238/3170] [fix] Avoid sasl account reachable from other users --- data/hooks/conf_regen/19-postfix | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/data/hooks/conf_regen/19-postfix b/data/hooks/conf_regen/19-postfix index 69790fd39..3a8117a61 100755 --- a/data/hooks/conf_regen/19-postfix +++ b/data/hooks/conf_regen/19-postfix @@ -31,8 +31,21 @@ do_pre_regen() { export relay_port="$(yunohost settings get 'smtp.relay.port')" export relay_user="$(yunohost settings get 'smtp.relay.user')" relay_password="$(yunohost settings get 'smtp.relay.password')" - echo "[${relay_host}]:${relay_port} ${relay_user}:${relay_password}" > ${postfix_dir}/sasl_passwd + + # Avoid to display "Relay account paswword" to other users + touch ${postfix_dir}/sasl_passwd + chmod o=--- ${postfix_dir}/sasl_passwd + touch ${postfix_dir}/sasl_passwd.db + chmod o=--- ${postfix_dir}/sasl_passwd.db + # Avoid "postmap: warning: removing zero-length database file" + chown postfix ${pending_dir}/etc/postfix + chown postfix ${pending_dir}/etc/postfix/sasl_passwd + chown postfix ${pending_dir}/etc/postfix/sasl_passwd.db + + cat <<< "[${relay_host}]:${relay_port} ${relay_user}:${relay_password}" > ${postfix_dir}/sasl_passwd postmap ${postfix_dir}/sasl_passwd + + fi export main_domain export domain_list="$YNH_DOMAINS" @@ -57,6 +70,8 @@ do_pre_regen() { do_post_regen() { regen_conf_files=$1 + chmod o=--- /etc/postfix/sasl_passwd + chmod o=--- /etc/postfix/sasl_passwd.db [[ -z "$regen_conf_files" ]] \ || { service postfix restart && service postsrsd restart; } From ff0a2192b9d29b072e00582dcf31062af3a0da70 Mon Sep 17 00:00:00 2001 From: ljf Date: Wed, 29 Apr 2020 03:13:30 +0200 Subject: [PATCH 1239/3170] [enh] Automatic regenconf after editing smtp settings --- src/yunohost/settings.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/yunohost/settings.py b/src/yunohost/settings.py index f40bb61af..3dea458f1 100644 --- a/src/yunohost/settings.py +++ b/src/yunohost/settings.py @@ -326,6 +326,10 @@ def reconfigure_ssh(setting_name, old_value, new_value): service_regen_conf(names=['ssh']) @post_change_hook("smtp.allow_ipv6") +@post_change_hook("smtp.relay.host") +@post_change_hook("smtp.relay.port") +@post_change_hook("smtp.relay.user") +@post_change_hook("smtp.relay.password") @post_change_hook("security.postfix.compatibility") def reconfigure_postfix(setting_name, old_value, new_value): if old_value != new_value: From d51b126df85e7a74884a5dc4ebd9a4d7f9ca8001 Mon Sep 17 00:00:00 2001 From: ljf Date: Wed, 29 Apr 2020 03:23:30 +0200 Subject: [PATCH 1240/3170] [fix] postmap: warning: removing zero-length database file --- data/hooks/conf_regen/19-postfix | 3 --- 1 file changed, 3 deletions(-) diff --git a/data/hooks/conf_regen/19-postfix b/data/hooks/conf_regen/19-postfix index 3a8117a61..1a1b88a25 100755 --- a/data/hooks/conf_regen/19-postfix +++ b/data/hooks/conf_regen/19-postfix @@ -35,12 +35,9 @@ do_pre_regen() { # Avoid to display "Relay account paswword" to other users touch ${postfix_dir}/sasl_passwd chmod o=--- ${postfix_dir}/sasl_passwd - touch ${postfix_dir}/sasl_passwd.db - chmod o=--- ${postfix_dir}/sasl_passwd.db # Avoid "postmap: warning: removing zero-length database file" chown postfix ${pending_dir}/etc/postfix chown postfix ${pending_dir}/etc/postfix/sasl_passwd - chown postfix ${pending_dir}/etc/postfix/sasl_passwd.db cat <<< "[${relay_host}]:${relay_port} ${relay_user}:${relay_password}" > ${postfix_dir}/sasl_passwd postmap ${postfix_dir}/sasl_passwd From a5ecf52c30955ab3c1f8092a1d2f1952eec10131 Mon Sep 17 00:00:00 2001 From: ljf Date: Wed, 29 Apr 2020 03:38:10 +0200 Subject: [PATCH 1241/3170] [fix] chown postfix to avoid warning --- data/hooks/conf_regen/19-postfix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/hooks/conf_regen/19-postfix b/data/hooks/conf_regen/19-postfix index 1a1b88a25..67ca22991 100755 --- a/data/hooks/conf_regen/19-postfix +++ b/data/hooks/conf_regen/19-postfix @@ -67,8 +67,8 @@ do_pre_regen() { do_post_regen() { regen_conf_files=$1 - chmod o=--- /etc/postfix/sasl_passwd - chmod o=--- /etc/postfix/sasl_passwd.db + chmod o=--- /etc/postfix/sasl_passwd* + chown postfix /etc/postfix/sasl_passwd* [[ -z "$regen_conf_files" ]] \ || { service postfix restart && service postsrsd restart; } From cf57b77d6a03f48a5f6be461c76c8570806368bf Mon Sep 17 00:00:00 2001 From: Kay0u Date: Mon, 4 May 2020 18:28:05 +0200 Subject: [PATCH 1242/3170] [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 1243/3170] [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 1244/3170] 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 1245/3170] 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 1246/3170] 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 1247/3170] 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 1248/3170] 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 1249/3170] 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 1250/3170] 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 1251/3170] 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 1252/3170] 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 1253/3170] [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 1254/3170] 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 1255/3170] 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 1256/3170] 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 1257/3170] 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 1258/3170] 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 1259/3170] 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 1260/3170] 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 1261/3170] 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 1262/3170] 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 1263/3170] 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 1264/3170] 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 1265/3170] 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 1266/3170] 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 1267/3170] 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 1268/3170] 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 1269/3170] 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 1270/3170] 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 1271/3170] 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 1272/3170] 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 1273/3170] 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 1274/3170] 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 1275/3170] 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 1276/3170] 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 1277/3170] 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 1278/3170] 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 1279/3170] 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 1280/3170] 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 1281/3170] 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 1282/3170] 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 1283/3170] 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 1284/3170] 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 1285/3170] 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 1286/3170] 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 1287/3170] 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 1288/3170] 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 1289/3170] 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 1290/3170] 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 1291/3170] 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 1292/3170] 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 1293/3170] 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 1294/3170] 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 1295/3170] 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 1296/3170] 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 1297/3170] 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 1298/3170] 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 1299/3170] 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 1300/3170] 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 1301/3170] 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 1302/3170] 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 1303/3170] 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 1304/3170] 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 1305/3170] 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 1306/3170] 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 1307/3170] 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 1308/3170] 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 1309/3170] 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 1310/3170] 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 1311/3170] 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 1312/3170] 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 1313/3170] [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 1314/3170] [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 1315/3170] 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 1316/3170] 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 1317/3170] [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 1318/3170] 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 1319/3170] 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 1320/3170] 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 1321/3170] 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 1322/3170] 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 1323/3170] 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 1324/3170] 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 1325/3170] 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 1326/3170] 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 1327/3170] 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 1328/3170] 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 1329/3170] [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 1330/3170] 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 1331/3170] 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 1332/3170] 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 1333/3170] 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 1334/3170] 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 1335/3170] 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 1336/3170] 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 1337/3170] 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 1338/3170] 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 1339/3170] 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 1340/3170] 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 1341/3170] 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 1342/3170] 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 1343/3170] 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 1344/3170] 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 1345/3170] 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 1346/3170] 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 1347/3170] 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 1348/3170] 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 1349/3170] 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 1350/3170] 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 1351/3170] [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}})

- - diff --git a/doc/helper_doc_template.md b/doc/helper_doc_template.md new file mode 100644 index 000000000..1ae6095a3 --- /dev/null +++ b/doc/helper_doc_template.md @@ -0,0 +1,59 @@ +--- +title: App helpers +template: docs +taxonomy: + category: docs +routes: + default: '/packaging_apps_helpers' +--- + +Doc auto-generated by [this script](https://github.com/YunoHost/yunohost/blob/{{ current_commit }}/doc/generate_helper_doc.py) on {{data.date}} (Yunohost version {{data.version}}) + +{% for category, helpers in data.helpers %} +### {{ category.upper() }} +{% for h in helpers %} +**{{ h.name }}** +[details summary="{{ h.brief }}" class="helper-card-subtitle text-muted"] +

+ +**Usage**: `{{ h.usage }}` + {% if h.args %} + +**Arguments**: + {% for infos in h.args %} + {% if infos|length == 2 %} +- `{{ infos[0] }}`: {{ infos[1] }} + {% else %} +- `{{ infos[0] }}`, `{{ infos[1] }}`: {{ infos[2] }} + {% endif %} + {% endfor %} + {% endif %} + {% if h.ret %} + +**Returns**: {{ h.ret }} + {% endif %} + {% if "example" in h.keys() %} +**Example**: `{{ h.example }}` + {% endif %} + {% if "examples" in h.keys() %} + +**Examples**: + {% for example in h.examples %} + {% if not example.strip().startswith("# ") %} +- `{{ example }}` + {% else %} +- `{{ example.strip("# ") }}` + {% endif %} + {% endfor %} + {% endif %} + {% if h.details %} + +**Details**: +{{ h.details.replace('\n', '
').replace('_', '\_') }} + {% endif %} + +[Dude, show me the code!](https://github.com/YunoHost/yunohost/blob/{{ current_commit }}/data/helpers.d/{{ category }}#L{{ h.line + 1 }}) +[/details] +---------------- +{% endfor %} +{% endfor %} From 933b73400bd76e5befd9f056c56f1b61f0f5c8ab Mon Sep 17 00:00:00 2001 From: Kay0u Date: Sun, 7 Feb 2021 15:54:01 +0100 Subject: [PATCH 2130/3170] change artifacts filename --- .gitlab/ci/doc.gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab/ci/doc.gitlab-ci.yml b/.gitlab/ci/doc.gitlab-ci.yml index c2ad255ba..3b161dc08 100644 --- a/.gitlab/ci/doc.gitlab-ci.yml +++ b/.gitlab/ci/doc.gitlab-ci.yml @@ -22,6 +22,6 @@ generate-helpers-doc: - hub pull-request -m "[CI] Helper for ${CI_COMMIT_REF_NAME}" -p # GITHUB_USER and GITHUB_TOKEN registered here https://gitlab.com/yunohost/yunohost/-/settings/ci_cd artifacts: paths: - - doc/helpers.html + - doc/helpers.md only: - tags From df70a75007a75a71906cd53f2bc10ad49b544f67 Mon Sep 17 00:00:00 2001 From: Gabriel Date: Tue, 9 Feb 2021 20:29:08 +0100 Subject: [PATCH 2131/3170] [metronome] deactivate stanza mention optimization --- data/templates/metronome/metronome.cfg.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/data/templates/metronome/metronome.cfg.lua b/data/templates/metronome/metronome.cfg.lua index c1ea83281..2f6e48691 100644 --- a/data/templates/metronome/metronome.cfg.lua +++ b/data/templates/metronome/metronome.cfg.lua @@ -95,6 +95,10 @@ allow_registration = false -- Use LDAP storage backend for all stores storage = "ldap" +-- stanza optimization +csi_config_queue_all_muc_messages_but_mentions = false; + + -- Logging configuration log = { info = "/var/log/metronome/metronome.log"; -- Change 'info' to 'debug' for verbose logging From 958c052f1ec72a4445dc342a50f0741380278b25 Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Thu, 11 Feb 2021 18:35:35 +0100 Subject: [PATCH 2132/3170] [fix] Avoid admin part of apps to be reachable from visitors --- src/yunohost/utils/legacy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/utils/legacy.py b/src/yunohost/utils/legacy.py index c84817f98..7479d12c4 100644 --- a/src/yunohost/utils/legacy.py +++ b/src/yunohost/utils/legacy.py @@ -280,7 +280,7 @@ def migrate_legacy_permission_settings(app=None): auth_header=True, label=legacy_permission_label(app, "protected"), show_tile=False, - allowed=user_permission_list()["permissions"][app + ".main"]["allowed"], + allowed=[], protected=True, sync_perm=False, ) From 6d625d457d4beeb2ac983353b58ae917b261c38e Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Thu, 11 Feb 2021 19:42:55 +0100 Subject: [PATCH 2133/3170] [fix] Unused import --- src/yunohost/utils/legacy.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/yunohost/utils/legacy.py b/src/yunohost/utils/legacy.py index 7479d12c4..728fcaefc 100644 --- a/src/yunohost/utils/legacy.py +++ b/src/yunohost/utils/legacy.py @@ -13,7 +13,6 @@ from yunohost.app import ( ) from yunohost.permission import ( permission_create, - user_permission_list, user_permission_update, permission_sync_to_user, ) From 8003288a67536212e5ff9c54461e8cc4cc036b5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Pi=C3=A9dallu?= Date: Tue, 16 Feb 2021 14:14:40 +0100 Subject: [PATCH 2134/3170] Enhance the jinja template for bash helpers --- doc/helper_doc_template.md | 51 +++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/doc/helper_doc_template.md b/doc/helper_doc_template.md index 1ae6095a3..1b9fa873d 100644 --- a/doc/helper_doc_template.md +++ b/doc/helper_doc_template.md @@ -12,45 +12,46 @@ Doc auto-generated by [this script](https://github.com/YunoHost/yunohost/blob/{{ {% for category, helpers in data.helpers %} ### {{ category.upper() }} {% for h in helpers %} -**{{ h.name }}** +**{{ h.name }}**
[details summary="{{ h.brief }}" class="helper-card-subtitle text-muted"]

**Usage**: `{{ h.usage }}` - {% if h.args %} +{%- if h.args %} -**Arguments**: - {% for infos in h.args %} - {% if infos|length == 2 %} +**Arguments**: + {%- for infos in h.args %} + {%- if infos|length == 2 %} - `{{ infos[0] }}`: {{ infos[1] }} - {% else %} + {%- else %} - `{{ infos[0] }}`, `{{ infos[1] }}`: {{ infos[2] }} - {% endif %} - {% endfor %} - {% endif %} - {% if h.ret %} + {%- endif %} + {%- endfor %} +{%- endif %} +{%- if h.ret %} **Returns**: {{ h.ret }} - {% endif %} - {% if "example" in h.keys() %} +{%- endif %} +{%- if "example" in h.keys() %} + **Example**: `{{ h.example }}` - {% endif %} - {% if "examples" in h.keys() %} +{%- endif %} +{%- if "examples" in h.keys() %} -**Examples**: - {% for example in h.examples %} - {% if not example.strip().startswith("# ") %} +**Examples**: + {% for example in h.examples %} + {% if not example.strip().startswith("# ") %} - `{{ example }}` - {% else %} + {% else %} - `{{ example.strip("# ") }}` - {% endif %} - {% endfor %} - {% endif %} - {% if h.details %} + {% endif %} + {% endfor %} +{%- endif %} +{%- if h.details %} -**Details**: -{{ h.details.replace('\n', '
').replace('_', '\_') }} - {% endif %} +**Details**:
+{{ h.details }} +{%- endif %} [Dude, show me the code!](https://github.com/YunoHost/yunohost/blob/{{ current_commit }}/data/helpers.d/{{ category }}#L{{ h.line + 1 }}) [/details] From aa92954f1b07a23c38abf277638cddf171b44e1b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 20 Feb 2021 17:07:38 +0100 Subject: [PATCH 2135/3170] Translation typo.. --- locales/it.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/it.json b/locales/it.json index 29f1db1e9..9f8f6a167 100644 --- a/locales/it.json +++ b/locales/it.json @@ -591,7 +591,7 @@ "log_user_permission_update": "Aggiorna gli accessi del permesso '{}'", "log_user_group_update": "Aggiorna il gruppo '{}'", "log_user_group_delete": "Cancella il gruppo '{}'", - "log_user_group_create": "Crea il gruppo '[}'", + "log_user_group_create": "Crea il gruppo '{}'", "log_permission_url": "Aggiorna l'URL collegato al permesso '{}'", "log_permission_delete": "Cancella permesso '{}'", "log_permission_create": "Crea permesso '{}'", From c7c1eaca4ed8086439bb2d528318ff2223790611 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 21 Feb 2021 05:03:07 +0100 Subject: [PATCH 2136/3170] Mysql is a fucking joke (... trying to fix the mysql issue on RPi ...) --- data/hooks/conf_regen/34-mysql | 63 ++++++++++++++-------------------- 1 file changed, 25 insertions(+), 38 deletions(-) diff --git a/data/hooks/conf_regen/34-mysql b/data/hooks/conf_regen/34-mysql index ac2395f34..d9374bbf5 100755 --- a/data/hooks/conf_regen/34-mysql +++ b/data/hooks/conf_regen/34-mysql @@ -15,6 +15,31 @@ do_pre_regen() { do_post_regen() { regen_conf_files=$1 + if [[ ! -d /var/lib/mysql/mysql ]] + then + # dpkg-reconfigure will initialize mysql (if it ain't already) + # It enabled auth_socket for root, so no need to define any root password... + # c.f. : cat /var/lib/dpkg/info/mariadb-server-10.3.postinst | grep install_db -C3 + dpkg-reconfigure -freadline -u "$MYSQL_PKG" 2>&1 + + systemctl -q is-active mariadb.service \ + || systemctl start mariadb + + sleep 5 + + echo "" | mysql && echo "Can't connect to mysql using unix_socket auth ... something went wrong during initial configuration of mysql !?" + fi + + if [ ! -e /etc/yunohost/mysql ] + then + # Dummy password that's not actually used nor meaningful ... + # (because mysql is supposed to be configured to use unix_socket on new setups) + # but keeping it for legacy + # until we merge https://github.com/YunoHost/yunohost/pull/912 ... + ynh_string_random 10 > /etc/yunohost/mysql + chmod 400 /etc/yunohost/mysql + fi + # mysql is supposed to be an alias to mariadb... but in some weird case is not # c.f. https://forum.yunohost.org/t/mysql-ne-fonctionne-pas/11661 # Playing with enable/disable allows to recreate the proper symlinks. @@ -27,44 +52,6 @@ do_post_regen() { systemctl is-active mariadb -q || systemctl start mariadb fi - if [ ! -f /etc/yunohost/mysql ]; then - - # ensure that mysql is running - systemctl -q is-active mysql.service \ - || service mysql start - - # generate and set new root password - mysql_password=$(ynh_string_random 10) - mysqladmin -s -u root -pyunohost password "$mysql_password" || { - if [ $FORCE -eq 1 ]; then - echo "It seems that you have already configured MySQL." \ - "YunoHost needs to have a root access to MySQL to runs its" \ - "applications, and is going to reset the MySQL root password." \ - "You can find this new password in /etc/yunohost/mysql." >&2 - - # set new password with debconf - debconf-set-selections << EOF -$MYSQL_PKG mysql-server/root_password password $mysql_password -$MYSQL_PKG mysql-server/root_password_again password $mysql_password -EOF - - # reconfigure Debian package - dpkg-reconfigure -freadline -u "$MYSQL_PKG" 2>&1 - else - echo "It seems that you have already configured MySQL." \ - "YunoHost needs to have a root access to MySQL to runs its" \ - "applications, but the MySQL root password is unknown." \ - "You must either pass --force to reset the password or" \ - "put the current one into the file /etc/yunohost/mysql." >&2 - exit 1 - fi - } - - # store new root password - echo "$mysql_password" | tee /etc/yunohost/mysql - chmod 400 /etc/yunohost/mysql - fi - [[ -z "$regen_conf_files" ]] \ || service mysql restart } From 0f8a44028c4283248267991759eef59c20864724 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 21 Feb 2021 05:12:38 +0100 Subject: [PATCH 2137/3170] Replace \t in conf.json.persistent... --- src/yunohost/utils/legacy.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/yunohost/utils/legacy.py b/src/yunohost/utils/legacy.py index c84817f98..0067fe05e 100644 --- a/src/yunohost/utils/legacy.py +++ b/src/yunohost/utils/legacy.py @@ -308,6 +308,9 @@ def translate_legacy_rules_in_ssowant_conf_json_persistent(): if not os.path.exists(persistent_file_name): return + # Ugly hack because for some reason so many people have tabs in their conf.json.persistent ... + os.system(r"sed -i 's/\t/ /g' /etc/ssowat/conf.json.persistent") + # Ugly hack to try not to misarably fail migration persistent = read_yaml(persistent_file_name) From 29bd3c4a26c3d77c2a09f126c9720867678b0300 Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Thu, 11 Feb 2021 18:35:35 +0100 Subject: [PATCH 2138/3170] [fix] Avoid admin part of apps to be reachable from visitors --- src/yunohost/utils/legacy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/utils/legacy.py b/src/yunohost/utils/legacy.py index f3269cce1..ebc7b65de 100644 --- a/src/yunohost/utils/legacy.py +++ b/src/yunohost/utils/legacy.py @@ -189,7 +189,7 @@ def migrate_legacy_permission_settings(app=None): if protected_urls != []: permission_create(app + ".legacy_protected_uris", additional_urls=protected_urls, auth_header=True, label=legacy_permission_label(app, "protected"), - show_tile=False, allowed=user_permission_list()['permissions'][app + ".main"]['allowed'], + show_tile=False, allowed=[], protected=True, sync_perm=False) legacy_permission_settings = [ From cd4fdb2b61a64d99c270a370bb33cdebd1cd07c9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 21 Feb 2021 05:03:07 +0100 Subject: [PATCH 2139/3170] Mysql is a fucking joke (... trying to fix the mysql issue on RPi ...) --- data/hooks/conf_regen/34-mysql | 63 ++++++++++++++-------------------- 1 file changed, 25 insertions(+), 38 deletions(-) diff --git a/data/hooks/conf_regen/34-mysql b/data/hooks/conf_regen/34-mysql index ac2395f34..d9374bbf5 100755 --- a/data/hooks/conf_regen/34-mysql +++ b/data/hooks/conf_regen/34-mysql @@ -15,6 +15,31 @@ do_pre_regen() { do_post_regen() { regen_conf_files=$1 + if [[ ! -d /var/lib/mysql/mysql ]] + then + # dpkg-reconfigure will initialize mysql (if it ain't already) + # It enabled auth_socket for root, so no need to define any root password... + # c.f. : cat /var/lib/dpkg/info/mariadb-server-10.3.postinst | grep install_db -C3 + dpkg-reconfigure -freadline -u "$MYSQL_PKG" 2>&1 + + systemctl -q is-active mariadb.service \ + || systemctl start mariadb + + sleep 5 + + echo "" | mysql && echo "Can't connect to mysql using unix_socket auth ... something went wrong during initial configuration of mysql !?" + fi + + if [ ! -e /etc/yunohost/mysql ] + then + # Dummy password that's not actually used nor meaningful ... + # (because mysql is supposed to be configured to use unix_socket on new setups) + # but keeping it for legacy + # until we merge https://github.com/YunoHost/yunohost/pull/912 ... + ynh_string_random 10 > /etc/yunohost/mysql + chmod 400 /etc/yunohost/mysql + fi + # mysql is supposed to be an alias to mariadb... but in some weird case is not # c.f. https://forum.yunohost.org/t/mysql-ne-fonctionne-pas/11661 # Playing with enable/disable allows to recreate the proper symlinks. @@ -27,44 +52,6 @@ do_post_regen() { systemctl is-active mariadb -q || systemctl start mariadb fi - if [ ! -f /etc/yunohost/mysql ]; then - - # ensure that mysql is running - systemctl -q is-active mysql.service \ - || service mysql start - - # generate and set new root password - mysql_password=$(ynh_string_random 10) - mysqladmin -s -u root -pyunohost password "$mysql_password" || { - if [ $FORCE -eq 1 ]; then - echo "It seems that you have already configured MySQL." \ - "YunoHost needs to have a root access to MySQL to runs its" \ - "applications, and is going to reset the MySQL root password." \ - "You can find this new password in /etc/yunohost/mysql." >&2 - - # set new password with debconf - debconf-set-selections << EOF -$MYSQL_PKG mysql-server/root_password password $mysql_password -$MYSQL_PKG mysql-server/root_password_again password $mysql_password -EOF - - # reconfigure Debian package - dpkg-reconfigure -freadline -u "$MYSQL_PKG" 2>&1 - else - echo "It seems that you have already configured MySQL." \ - "YunoHost needs to have a root access to MySQL to runs its" \ - "applications, but the MySQL root password is unknown." \ - "You must either pass --force to reset the password or" \ - "put the current one into the file /etc/yunohost/mysql." >&2 - exit 1 - fi - } - - # store new root password - echo "$mysql_password" | tee /etc/yunohost/mysql - chmod 400 /etc/yunohost/mysql - fi - [[ -z "$regen_conf_files" ]] \ || service mysql restart } From f398f463f4ef2e72a9f7fddac91a3c9118f4ff43 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 21 Feb 2021 05:12:38 +0100 Subject: [PATCH 2140/3170] Replace \t in conf.json.persistent... --- src/yunohost/utils/legacy.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/yunohost/utils/legacy.py b/src/yunohost/utils/legacy.py index ebc7b65de..c3f7ab5a9 100644 --- a/src/yunohost/utils/legacy.py +++ b/src/yunohost/utils/legacy.py @@ -215,6 +215,9 @@ def translate_legacy_rules_in_ssowant_conf_json_persistent(): if not os.path.exists(persistent_file_name): return + # Ugly hack because for some reason so many people have tabs in their conf.json.persistent ... + os.system(r"sed -i 's/\t/ /g' /etc/ssowat/conf.json.persistent") + # Ugly hack to try not to misarably fail migration persistent = read_yaml(persistent_file_name) From 1846c3a07b314d25f7a58e1fa79a92a1c8c12d99 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 21 Feb 2021 05:28:30 +0100 Subject: [PATCH 2141/3170] Update changelog for 4.1.7.2 --- debian/changelog | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/debian/changelog b/debian/changelog index 4e893dee0..95cca2eb8 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,13 @@ +yunohost (4.1.7.2) testing; urgency=low + + - [fix] When migration legacy protected permissions, all users were allowed on the new perm (29bd3c4a) + - [fix] Mysql is a fucking joke (... trying to fix the mysql issue on RPi ...) (cd4fdb2b) + - [fix] Replace \t when converting legacy conf.json.persistent... (f398f463) + + Thanks to all contributors <3 ! (ljf) + + -- Alexandre Aubin Sun, 21 Feb 2021 05:25:49 +0100 + yunohost (4.1.7.1) stable; urgency=low - [enh] helpers: Fix ynh_exec_as regression (ac38e53a7) From 1adff77e3ae241b18bd383ce27d9e04a1c12d099 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 3 Jan 2021 18:19:30 +0100 Subject: [PATCH 2142/3170] Add multimedia helpers and hooks --- data/helpers.d/multimedia | 87 ++++++++++++++++++++++ data/hooks/post_user_create/ynh_multimedia | 28 +++++++ data/hooks/post_user_delete/ynh_multimedia | 8 ++ debian/control | 1 + 4 files changed, 124 insertions(+) create mode 100644 data/helpers.d/multimedia create mode 100644 data/hooks/post_user_create/ynh_multimedia create mode 100644 data/hooks/post_user_delete/ynh_multimedia diff --git a/data/helpers.d/multimedia b/data/helpers.d/multimedia new file mode 100644 index 000000000..5517917b5 --- /dev/null +++ b/data/helpers.d/multimedia @@ -0,0 +1,87 @@ +readonly MEDIA_GROUP=multimedia +readonly MEDIA_DIRECTORY=/home/yunohost.multimedia + +# Initialize the multimedia directory system +# +# usage: ynh_multimedia_build_main_dir +ynh_multimedia_build_main_dir() { + + ## Création du groupe multimedia + groupadd -f $MEDIA_GROUP + + ## Création des dossiers génériques + mkdir -p "$MEDIA_DIRECTORY" + mkdir -p "$MEDIA_DIRECTORY/share" + mkdir -p "$MEDIA_DIRECTORY/share/Music" + mkdir -p "$MEDIA_DIRECTORY/share/Picture" + mkdir -p "$MEDIA_DIRECTORY/share/Video" + mkdir -p "$MEDIA_DIRECTORY/share/eBook" + + ## Création des dossiers utilisateurs + for user in $(yunohost user list --output-as json | jq -r '.users | keys[]') + do + mkdir -p "$MEDIA_DIRECTORY/$user" + mkdir -p "$MEDIA_DIRECTORY/$user/Music" + mkdir -p "$MEDIA_DIRECTORY/$user/Picture" + mkdir -p "$MEDIA_DIRECTORY/$user/Video" + mkdir -p "$MEDIA_DIRECTORY/$user/eBook" + ln -sfn "$MEDIA_DIRECTORY/share" "$MEDIA_DIRECTORY/$user/Share" + # Création du lien symbolique dans le home de l'utilisateur. + ln -sfn "$MEDIA_DIRECTORY/$user" "/home/$user/Multimedia" + # Propriétaires des dossiers utilisateurs. + chown -R $user "$MEDIA_DIRECTORY/$user" + done + # Default yunohost hooks for post_user_create,delete will take care + # of creating/deleting corresponding multimedia folders when users + # are created/deleted in the future... + + ## Application des droits étendus sur le dossier multimedia. + # Droit d'écriture pour le groupe et le groupe multimedia en acl et droit de lecture pour other: + setfacl -RnL -m g:$MEDIA_GROUP:rwX,g::rwX,o:r-X "$MEDIA_DIRECTORY" + # Application de la même règle que précédemment, mais par défaut pour les nouveaux fichiers. + setfacl -RnL -m d:g:$MEDIA_GROUP:rwX,g::rwX,o:r-X "$MEDIA_DIRECTORY" + # Réglage du masque par défaut. Qui garantie (en principe...) un droit maximal à rwx. Donc pas de restriction de droits par l'acl. + setfacl -RL -m m::rwx "$MEDIA_DIRECTORY" +} + +# Add a directory in yunohost.multimedia +# This "directory" will be a symbolic link to a existing directory. +# +# usage: ynh_multimedia_addfolder "Source directory" "Destination directory" +# +# | arg: -s, --source_dir= - Source directory - The real directory which contains your medias. +# | arg: -d, --dest_dir= - Destination directory - The name and the place of the symbolic link, relative to "/home/yunohost.multimedia" +ynh_multimedia_addfolder() { + + # Declare an array to define the options of this helper. + declare -Ar args_array=( [s]=source_dir= [d]=dest_dir= ) + local source_dir + local dest_dir + + # Ajout d'un lien symbolique vers le dossier à partager + ln -sfn "$source_dir" "$MEDIA_DIRECTORY/$dest_dir" + + ## Application des droits étendus sur le dossier ajouté + # Droit d'écriture pour le groupe et le groupe multimedia en acl et droit de lecture pour other: + setfacl -RnL -m g:$MEDIA_GROUP:rwX,g::rwX,o:r-X "$source_dir" + # Application de la même règle que précédemment, mais par défaut pour les nouveaux fichiers. + setfacl -RnL -m d:g:$MEDIA_GROUP:rwX,g::rwX,o:r-X "$source_dir" + # Réglage du masque par défaut. Qui garantie (en principe...) un droit maximal à rwx. Donc pas de restriction de droits par l'acl. + setfacl -RL -m m::rwx "$source_dir" +} + +# Allow an user to have an write authorisation in multimedia directories +# +# usage: ynh_multimedia_addaccess user_name +# +# | arg: -u, --user_name= - The name of the user which gain this access. +ynh_multimedia_addaccess () { + # Declare an array to define the options of this helper. + declare -Ar args_array=( [u]=user_name=) + local user_name + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + groupadd -f multimedia + usermod -a -G multimedia $user_name +} diff --git a/data/hooks/post_user_create/ynh_multimedia b/data/hooks/post_user_create/ynh_multimedia new file mode 100644 index 000000000..560e6a293 --- /dev/null +++ b/data/hooks/post_user_create/ynh_multimedia @@ -0,0 +1,28 @@ +#!/bin/bash + +user=$1 + +readonly MEDIA_GROUP=multimedia +readonly MEDIA_DIRECTORY=/home/yunohost.multimedia + +# We only do this if multimedia directory is enabled (= the folder exists) +[ -e "$MEDIA_DIRECTORY" ] || exit + +mkdir -p "$MEDIA_DIRECTORY/$user" +mkdir -p "$MEDIA_DIRECTORY/$user/Music" +mkdir -p "$MEDIA_DIRECTORY/$user/Picture" +mkdir -p "$MEDIA_DIRECTORY/$user/Video" +mkdir -p "$MEDIA_DIRECTORY/$user/eBook" +ln -sfn "$MEDIA_DIRECTORY/share" "$MEDIA_DIRECTORY/$user/Share" +# Création du lien symbolique dans le home de l'utilisateur. +ln -sfn "$MEDIA_DIRECTORY/$user" "/home/$user/Multimedia" +# Propriétaires des dossiers utilisateurs. +chown -R $user "$MEDIA_DIRECTORY/$user" + +## Application des droits étendus sur le dossier multimedia. +# Droit d'écriture pour le groupe et le groupe multimedia en acl et droit de lecture pour other: +setfacl -RnL -m g:$MEDIA_GROUP:rwX,g::rwX,o:r-X "$MEDIA_DIRECTORY/$user" +# Application de la même règle que précédemment, mais par défaut pour les nouveaux fichiers. +setfacl -RnL -m d:g:$MEDIA_GROUP:rwX,g::rwX,o:r-X "$MEDIA_DIRECTORY/$user" +# Réglage du masque par défaut. Qui garantie (en principe...) un droit maximal à rwx. Donc pas de restriction de droits par l'acl. +setfacl -RL -m m::rwx "$MEDIA_DIRECTORY/$user" diff --git a/data/hooks/post_user_delete/ynh_multimedia b/data/hooks/post_user_delete/ynh_multimedia new file mode 100644 index 000000000..af06e1637 --- /dev/null +++ b/data/hooks/post_user_delete/ynh_multimedia @@ -0,0 +1,8 @@ +#!/bin/bash + +user=$1 +MEDIA_DIRECTORY=/home/yunohost.multimedia + +if [ -n "$user" ] && [ -e "$MEDIA_DIRECTORY/$user" ]; then + sudo rm -r "$MEDIA_DIRECTORY/$user" +fi diff --git a/debian/control b/debian/control index d95b17f4e..7275cb7b1 100644 --- a/debian/control +++ b/debian/control @@ -26,6 +26,7 @@ Depends: ${python3:Depends}, ${misc:Depends} , rspamd, opendkim-tools, postsrsd, procmail, mailutils , redis-server , metronome (>=3.14.0) + , acl , git, curl, wget, cron, unzip, jq, bc , lsb-release, haveged, fake-hwclock, equivs, lsof, whois Recommends: yunohost-admin From 822c05da2bb4354db7dbc3a7508863137e02b849 Mon Sep 17 00:00:00 2001 From: Gabriel Date: Mon, 22 Feb 2021 22:01:29 +0100 Subject: [PATCH 2143/3170] [metronome] activate module pubsub --- data/templates/metronome/metronome.cfg.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/data/templates/metronome/metronome.cfg.lua b/data/templates/metronome/metronome.cfg.lua index c1ea83281..3755277e2 100644 --- a/data/templates/metronome/metronome.cfg.lua +++ b/data/templates/metronome/metronome.cfg.lua @@ -32,6 +32,7 @@ modules_enabled = { "private"; -- Private XML storage (for room bookmarks, etc.) "vcard"; -- Allow users to set vCards "pep"; -- Allows setting of mood, tune, etc. + "pubsub"; -- Publish-subscribe XEP-0060 "posix"; -- POSIX functionality, sends server to background, enables syslog, etc. "bidi"; -- Enables Bidirectional Server-to-Server Streams. From 29b511f5e375beb313aa9cf3d37e1ff840cdca2f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 22 Feb 2021 23:42:52 +0100 Subject: [PATCH 2144/3170] Fix multimedia hook if not media directory yet --- data/hooks/post_user_create/ynh_multimedia | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/hooks/post_user_create/ynh_multimedia b/data/hooks/post_user_create/ynh_multimedia index 560e6a293..441212bbc 100644 --- a/data/hooks/post_user_create/ynh_multimedia +++ b/data/hooks/post_user_create/ynh_multimedia @@ -6,7 +6,7 @@ readonly MEDIA_GROUP=multimedia readonly MEDIA_DIRECTORY=/home/yunohost.multimedia # We only do this if multimedia directory is enabled (= the folder exists) -[ -e "$MEDIA_DIRECTORY" ] || exit +[ -e "$MEDIA_DIRECTORY" ] || exit 0 mkdir -p "$MEDIA_DIRECTORY/$user" mkdir -p "$MEDIA_DIRECTORY/$user/Music" From 59da04e92b172de23f19f1c8546aa20c8be63e26 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 22 Feb 2021 23:46:12 +0100 Subject: [PATCH 2145/3170] Gotta escape \ during ynh_replace_vars --- data/helpers.d/utils | 1 + 1 file changed, 1 insertion(+) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 13f84424e..a23d06d2c 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -401,6 +401,7 @@ ynh_replace_vars () { match_string="__${one_var^^}__" match_string=${match_string//${delimit}/"\\${delimit}"} replace_string="${!one_var}" + replace_string=${replace_string//\\/\\\\} replace_string=${replace_string//${delimit}/"\\${delimit}"} # Actually replace (sed is used instead of ynh_replace_string to avoid triggering an epic amount of debug logs) From acfea3d76d0a675815e8bf5019e3c69013af58a1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 23 Feb 2021 02:17:04 +0100 Subject: [PATCH 2146/3170] Define YNH_APP_BASEDIR to be able to properly point to conf folder depending on the app script we're running --- data/helpers.d/fail2ban | 8 ++++---- data/helpers.d/nginx | 6 +++--- data/helpers.d/php | 19 ++++++++----------- data/helpers.d/systemd | 2 +- data/helpers.d/utils | 15 +++++---------- src/yunohost/backup.py | 10 ++++------ 6 files changed, 25 insertions(+), 35 deletions(-) diff --git a/data/helpers.d/fail2ban b/data/helpers.d/fail2ban index da090d2f9..c41226e14 100644 --- a/data/helpers.d/fail2ban +++ b/data/helpers.d/fail2ban @@ -87,7 +87,7 @@ port = __PORTS__ filter = __APP__ logpath = __LOGPATH__ maxretry = __MAX_RETRY__ -" > ../conf/f2b_jail.conf +" > $YNH_APP_BASEDIR/conf/f2b_jail.conf echo " [INCLUDES] @@ -95,11 +95,11 @@ before = common.conf [Definition] failregex = __FAILREGEX__ ignoreregex = -" > ../conf/f2b_filter.conf +" > $YNH_APP_BASEDIR/conf/f2b_filter.conf fi - ynh_add_config --template="../conf/f2b_jail.conf" --destination="/etc/fail2ban/jail.d/$app.conf" - ynh_add_config --template="../conf/f2b_filter.conf" --destination="/etc/fail2ban/filter.d/$app.conf" + ynh_add_config --template="$YNH_APP_BASEDIR/conf/f2b_jail.conf" --destination="/etc/fail2ban/jail.d/$app.conf" + ynh_add_config --template="$YNH_APP_BASEDIR/conf/f2b_filter.conf" --destination="/etc/fail2ban/filter.d/$app.conf" ynh_systemd_action --service_name=fail2ban --action=reload --line_match="(Started|Reloaded) Fail2Ban Service" --log_path=systemd diff --git a/data/helpers.d/nginx b/data/helpers.d/nginx index f7157cd8d..3c6254953 100644 --- a/data/helpers.d/nginx +++ b/data/helpers.d/nginx @@ -22,12 +22,12 @@ ynh_add_nginx_config () { if [ "${path_url:-}" != "/" ] then - ynh_replace_string --match_string="^#sub_path_only" --replace_string="" --target_file="../conf/nginx.conf" + ynh_replace_string --match_string="^#sub_path_only" --replace_string="" --target_file="$YNH_APP_BASEDIR/conf/nginx.conf" else - ynh_replace_string --match_string="^#root_path_only" --replace_string="" --target_file="../conf/nginx.conf" + ynh_replace_string --match_string="^#root_path_only" --replace_string="" --target_file="$YNH_APP_BASEDIR/conf/nginx.conf" fi - ynh_add_config --template="../conf/nginx.conf" --destination="$finalnginxconf" + ynh_add_config --template="$YNH_APP_BASEDIR/conf/nginx.conf" --destination="$finalnginxconf" ynh_systemd_action --service_name=nginx --action=reload diff --git a/data/helpers.d/php b/data/helpers.d/php index 0e1ac48b0..683f252be 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -153,10 +153,7 @@ ynh_add_fpm_config () { if [ $use_template -eq 1 ] then # Usage 1, use the template in conf/php-fpm.conf - local phpfpm_path="../conf/php-fpm.conf" - if [ ! -e "$phpfpm_path" ]; then - phpfpm_path="../settings/conf/php-fpm.conf" # Into the restore script, the PHP-FPM template is not at the same place - fi + local phpfpm_path="$YNH_APP_BASEDIR/conf/php-fpm.conf" # Make sure now that the template indeed exists [ -e "$phpfpm_path" ] || ynh_die --message="Unable to find template to configure PHP-FPM." else @@ -169,7 +166,7 @@ ynh_add_fpm_config () { # Define the values to use for the configuration of PHP. ynh_get_scalable_phpfpm --usage=$usage --footprint=$footprint - local phpfpm_path="../conf/php-fpm.conf" + local phpfpm_path="$YNH_APP_BASEDIR/conf/php-fpm.conf" echo " [__APP__] @@ -204,18 +201,18 @@ pm.process_idle_timeout = 10s fi # Concatene the extra config. - if [ -e ../conf/extra_php-fpm.conf ]; then - cat ../conf/extra_php-fpm.conf >> "$phpfpm_path" + if [ -e $YNH_APP_BASEDIR/conf/extra_php-fpm.conf ]; then + cat $YNH_APP_BASEDIR/conf/extra_php-fpm.conf >> "$phpfpm_path" fi fi local finalphpconf="$fpm_config_dir/pool.d/$app.conf" ynh_add_config --template="$phpfpm_path" --destination="$finalphpconf" - if [ -e "../conf/php-fpm.ini" ] + if [ -e "$YNH_APP_BASEDIR/conf/php-fpm.ini" ] then ynh_print_warn --message="Packagers ! Please do not use a separate php ini file, merge your directives in the pool file instead." - ynh_add_config --template="../conf/php-fpm.ini" --destination="$fpm_config_dir/conf.d/20-$app.ini" + ynh_add_config --template="$YNH_APP_BASEDIR/conf/php-fpm.ini" --destination="$fpm_config_dir/conf.d/20-$app.ini" fi if [ $dedicated_service -eq 1 ] @@ -228,7 +225,7 @@ pid = /run/php/php__PHPVERSION__-fpm-__APP__.pid error_log = /var/log/php/fpm-php.__APP__.log syslog.ident = php-fpm-__APP__ include = __FINALPHPCONF__ -" > ../conf/php-fpm-$app.conf +" > $YNH_APP_BASEDIR/conf/php-fpm-$app.conf ynh_add_config --template="../config/php-fpm-$app.conf" --destination="$globalphpconf" @@ -245,7 +242,7 @@ ExecReload=/bin/kill -USR2 \$MAINPID [Install] WantedBy=multi-user.target -" > ../conf/$fpm_service +" > $YNH_APP_BASEDIR/conf/$fpm_service # Create this dedicated PHP-FPM service ynh_add_systemd_config --service=$fpm_service --template=$fpm_service diff --git a/data/helpers.d/systemd b/data/helpers.d/systemd index 493a724a9..b416e5745 100644 --- a/data/helpers.d/systemd +++ b/data/helpers.d/systemd @@ -22,7 +22,7 @@ ynh_add_systemd_config () { local service="${service:-$app}" local template="${template:-systemd.service}" - ynh_add_config --template="../conf/$template" --destination="/etc/systemd/system/$service.service" + ynh_add_config --template="$YNH_APP_BASEDIR/conf/$template" --destination="/etc/systemd/system/$service.service" systemctl enable $service --quiet systemctl daemon-reload diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 13f84424e..1bdbc98cd 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -1,5 +1,7 @@ #!/bin/bash +YNH_APP_BASEDIR=$([[ "$(basename $0)" =~ ^backup|restore$ ]] && echo '../settings' || echo '..') + # Handle script crashes / failures # # [internal] @@ -112,12 +114,7 @@ ynh_setup_source () { ynh_handle_getopts_args "$@" source_id="${source_id:-app}" # If the argument is not given, source_id equals "app" - local src_file_path="$YNH_CWD/../conf/${source_id}.src" - # In case of restore script the src file is in an other path. - # So try to use the restore path if the general path point to no file. - if [ ! -e "$src_file_path" ]; then - src_file_path="$YNH_CWD/../settings/conf/${source_id}.src" - fi + local src_file_path="$YNH_APP_BASEDIR/conf/${source_id}.src" # Load value from configuration file (see above for a small doc about this file # format) @@ -309,10 +306,8 @@ ynh_add_config () { ynh_handle_getopts_args "$@" local template_path - if [ -f "../conf/$template" ]; then - template_path="../conf/$template" - elif [ -f "../settings/conf/$template" ]; then - template_path="../settings/conf/$template" + if [ -f "$YNH_APP_BASEDIR/conf/$template" ]; then + template_path="$YNH_APP_BASEDIR/conf/$template" elif [ -f "$template" ]; then template_path=$template else diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 50765ba5f..408cd6f15 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -704,9 +704,7 @@ class BackupManager: settings_dir = os.path.join(self.work_dir, "apps", app, "settings") logger.info(m18n.n("app_start_backup", app=app)) - tmp_script = ( - None # This is to make sure the var exists later in the 'finally' ... - ) + tmp_folder = tempfile.mkdtemp() try: # Prepare backup directory for the app filesystem.mkdir(tmp_app_bkp_dir, 0o750, True, uid="admin") @@ -715,8 +713,8 @@ class BackupManager: shutil.copytree(app_setting_path, settings_dir) # Copy app backup script in a temporary folder and execute it - _, tmp_script = tempfile.mkstemp(prefix="backup_") app_script = os.path.join(app_setting_path, "scripts/backup") + tmp_script = os.path.join(tmp_folder, "backup") subprocess.call(["install", "-Dm555", app_script, tmp_script]) hook_exec( @@ -752,8 +750,8 @@ class BackupManager: # Remove tmp files in all situations finally: - if tmp_script: - filesystem.rm(tmp_script, force=True) + if tmp_folder and os.path.exists(tmp_folder): + shutil.rmtree(tmp_folder) filesystem.rm(env_dict["YNH_BACKUP_CSV"], force=True) # From 4168a9d19348232beacf03b05a5f1e1705b7e3c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20Kroul=C3=ADk?= Date: Sat, 23 Jan 2021 20:58:00 +0000 Subject: [PATCH 2147/3170] Translated using Weblate (Czech) Currently translated at 0.1% (1 of 631 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/cs/ --- locales/cs.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/locales/cs.json b/locales/cs.json index 0967ef424..eafada5e6 100644 --- a/locales/cs.json +++ b/locales/cs.json @@ -1 +1,3 @@ -{} +{ + "password_too_simple_1": "Heslo musí být aspoň 8 znaků dlouhé" +} From 7f8a4c16aef87cb7ccf0a9e70704cbc1a2d65787 Mon Sep 17 00:00:00 2001 From: Christian Wehrli Date: Mon, 25 Jan 2021 19:26:28 +0000 Subject: [PATCH 2148/3170] Translated using Weblate (German) Currently translated at 63.3% (400 of 631 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/locales/de.json b/locales/de.json index efc25f7c5..982de116b 100644 --- a/locales/de.json +++ b/locales/de.json @@ -60,14 +60,14 @@ "dyndns_key_generating": "Generierung des DNS-Schlüssels..., das könnte eine Weile dauern.", "dyndns_registered": "DynDNS Domain registriert", "dyndns_registration_failed": "DynDNS Domain konnte nicht registriert werden: {error:s}", - "dyndns_unavailable": "DynDNS Subdomain ist nicht verfügbar", + "dyndns_unavailable": "Die Domäne {domain:s} ist nicht verfügbar.", "executing_command": "Führe den Behfehl '{command:s}' aus…", "executing_script": "Skript '{script:s}' wird ausgeührt…", - "extracting": "Wird entpackt…", + "extracting": "Wird entpackt...", "field_invalid": "Feld '{:s}' ist unbekannt", "firewall_reload_failed": "Die Firewall konnte nicht neu geladen werden", "firewall_reloaded": "Die Firewall wurde neu geladen", - "firewall_rules_cmd_failed": "Einzelne Firewallregeln konnten nicht übernommen werden. Mehr Informationen sind im Log zu finden.", + "firewall_rules_cmd_failed": "Einige Befehle für die Firewallregeln konnten nicht ausgeführt werden. Mehr Informationen sind im Log zu finden.", "hook_exec_failed": "Skriptausführung fehlgeschlagen: {path:s}", "hook_exec_not_terminated": "Skriptausführung noch nicht beendet: {path:s}", "hook_list_by_invalid": "Ungültiger Wert zur Anzeige von Hooks", @@ -204,10 +204,10 @@ "app_change_url_success": "{app:s} URL ist nun {domain:s}{path:s}", "backup_applying_method_borg": "Sende alle Dateien zur Sicherung ins borg-backup repository...", "global_settings_bad_type_for_setting": "Falscher Typ für Einstellung {setting:s}. Empfangen: {received_type:s}, aber erwartet: {expected_type:s}", - "global_settings_bad_choice_for_enum": "Falsche Wahl für die Einstellung {setting:s}. Habe '{choice:s}' erhalten, aber es stehen nur folgende Auswahlmöglichkeiten zur Verfügung: {available_choices:s}", + "global_settings_bad_choice_for_enum": "Der Wert dieses Einstellungsparameters {setting:s} ist ungültig. Der Wert den Sie eingegeben haben: '{choice:s}', die gültigen Werte für diese Einstellung: {available_choices:s}", "file_does_not_exist": "Die Datei {path:s} existiert nicht.", "experimental_feature": "Warnung: Diese Funktion ist experimentell und gilt nicht als stabil. Sie sollten sie nur verwenden, wenn Sie wissen, was Sie tun.", - "dyndns_domain_not_provided": "Der DynDNS-Anbieter {provider:s} kann die Domain(s) {domain:s} nicht bereitstellen.", + "dyndns_domain_not_provided": "Der DynDNS-Anbieter {provider:s} kann die Domäne(n) {domain:s} nicht bereitstellen.", "dyndns_could_not_check_available": "Konnte nicht überprüfen, ob {domain:s} auf {provider:s} verfügbar ist.", "dyndns_could_not_check_provide": "Konnte nicht überprüft, ob {provider:s} die Domain(s) {domain:s} bereitstellen kann.", "domain_dns_conf_is_just_a_recommendation": "Dieser Befehl zeigt Ihnen die * empfohlene * Konfiguration. Die DNS-Konfiguration wird NICHT für Sie eingerichtet. Es liegt in Ihrer Verantwortung, Ihre DNS-Zone in Ihrem Registrar gemäß dieser Empfehlung zu konfigurieren.", From 7d4ef2b14224328b763fa0bdf6e6ebd9745997b1 Mon Sep 17 00:00:00 2001 From: Mathieu Massaviol Date: Thu, 28 Jan 2021 09:44:51 +0000 Subject: [PATCH 2149/3170] Translated using Weblate (French) Currently translated at 100.0% (631 of 631 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index b65268fb7..dfe2e372e 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": "Ces applications sont toujours installées sur votre domaine :\n{apps}\n\nAfin de pouvoir procéder à la suppression du domaine, vous devez préalablement :\n- soit désinstaller toutes ces applications avec la commande 'yunohost app remove nom-de-l-application' ;\n- soit déplacer toutes ces applications vers un autre domaine avec la commande 'yunohost app change-url nom-de-l-application'", + "domain_uninstall_app_first": "Ces applications sont toujours installées sur votre domaine :\n{apps}\n\nVeuillez les désinstaller avec la commande 'yunohost app remove nom-de-l-application' ou les déplacer vers un autre domaine avec la commande 'yunohost app change-url nom-de-l-application' avant de procéder à la suppression du domaine", "domain_unknown": "Domaine inconnu", "done": "Terminé", "downloading": "Téléchargement en cours …", @@ -327,7 +327,7 @@ "password_too_simple_3": "Le mot de passe doit comporter au moins 8 caractères et contenir des chiffres, des majuscules, des minuscules et des caractères spéciaux", "password_too_simple_4": "Le mot de passe doit comporter au moins 12 caractères et contenir des chiffres, des majuscules, des minuscules et des caractères spéciaux", "root_password_desynchronized": "Le mot de passe administrateur a été changé, mais YunoHost n’a pas pu le propager au mot de passe root !", - "aborting": "Annulation.", + "aborting": "Annulation en cours.", "app_not_upgraded": "L’application {failed_app} n’a pas été mise à jour et par conséquence les applications suivantes n’ont pas été mises à jour : {apps}", "app_start_install": "Installation de {app}...", "app_start_remove": "Suppression de {app}...", From c6974932b44bc07ff3cf0a1f874d47ec6175db9b Mon Sep 17 00:00:00 2001 From: ppr Date: Thu, 28 Jan 2021 09:41:44 +0000 Subject: [PATCH 2150/3170] Translated using Weblate (French) Currently translated at 100.0% (631 of 631 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 dfe2e372e..7d016a70e 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -650,7 +650,7 @@ "migration_description_0018_xtable_to_nftable": "Migrer les anciennes règles de trafic réseau vers le nouveau système basé sur nftables", "service_description_php7.3-fpm": "Exécute les applications écrites en PHP avec NGINX", "migration_0018_failed_to_reset_legacy_rules": "La réinitialisation des règles iptable par défaut a échoué : {error}", - "migration_0018_failed_to_migrate_iptables_rules": "La migration des règles iptables héritées vers nftables a échoué: {error}", + "migration_0018_failed_to_migrate_iptables_rules": "Échec de la migration des anciennes règles iptables vers nftables : {error}", "migration_0017_not_enough_space": "Laissez suffisamment d'espace disponible dans {path} avant de lancer la migration.", "migration_0017_postgresql_11_not_installed": "PostgreSQL 9.6 est installé mais pas posgreSQL 11 ? Il s'est sans doute passé quelque chose d'étrange sur votre système :(...", "migration_0017_postgresql_96_not_installed": "PostgreSQL n'a pas été installé sur votre système. Aucune opération à effectuer.", From 6880a6f01741f3f88a7aeff0bf42db0002d3b66d Mon Sep 17 00:00:00 2001 From: Yifei Ding Date: Sun, 31 Jan 2021 09:36:34 +0000 Subject: [PATCH 2151/3170] Translated using Weblate (Chinese (Simplified)) Currently translated at 2.2% (14 of 631 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/zh_Hans/ --- locales/zh_Hans.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/zh_Hans.json b/locales/zh_Hans.json index d11e570d0..e72cd52da 100644 --- a/locales/zh_Hans.json +++ b/locales/zh_Hans.json @@ -2,7 +2,7 @@ "password_too_simple_1": "密码长度至少为8个字符", "backup_created": "备份已创建", "app_start_remove": "正在删除{app}……", - "admin_password_change_failed": "不能修改密码", + "admin_password_change_failed": "无法修改密码", "admin_password_too_long": "请选择一个小于127个字符的密码", "app_upgrade_failed": "不能升级{app:s}:{error}", "app_id_invalid": "无效 app ID", From a7152e2c69ac07aaaed15b1f78303164d3d1f6c7 Mon Sep 17 00:00:00 2001 From: Christian Wehrli Date: Mon, 1 Feb 2021 19:22:02 +0000 Subject: [PATCH 2152/3170] Translated using Weblate (German) Currently translated at 63.6% (402 of 632 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/locales/de.json b/locales/de.json index 982de116b..23f9bc217 100644 --- a/locales/de.json +++ b/locales/de.json @@ -489,5 +489,7 @@ "global_settings_setting_smtp_relay_port": "SMTP Relay Port", "global_settings_setting_smtp_allow_ipv6": "Erlaube die Nutzung von IPv6 um Mails zu empfangen und zu versenden", "global_settings_setting_pop3_enabled": "Aktiviere das POP3 Protokoll für den Mailserver", - "domain_cannot_remove_main_add_new_one": "Du kannst \"{domain:s}\" nicht entfernen da es die Hauptdomain und deine einzige Domain ist, erst musst erst eine andere Domain hinzufügen indem du eingibst \"yunohost domain add \", setze es dann als deine Hauptdomain indem du eingibst \"yunohost domain main-domain -n \", erst jetzt kannst du die domain \"{domain:s}\" entfernen." + "domain_cannot_remove_main_add_new_one": "Du kannst \"{domain:s}\" nicht entfernen da es die Hauptdomain und deine einzige Domain ist, erst musst erst eine andere Domain hinzufügen indem du eingibst \"yunohost domain add \", setze es dann als deine Hauptdomain indem du eingibst \"yunohost domain main-domain -n \", erst jetzt kannst du die domain \"{domain:s}\" entfernen.", + "diagnosis_rootfstotalspace_critical": "Das Root-Filesystem hat noch freien Speicher von {space}. Das ist besorngiserregend! Der Speicher wird schnell aufgebraucht sein. 16 GB für das Root-Filesystem werden empfohlen.", + "diagnosis_rootfstotalspace_warning": "Das Root-Filesystem hat noch freien Speicher von {space}. Möglich, dass das in Ordnung ist. Vielleicht ist er aber auch schneller aufgebraucht. 16 GB für das Root-Filesystem werden empfohlen." } From d5f82856c6c0db3665fbdffcb76ff21b08f9eae8 Mon Sep 17 00:00:00 2001 From: Christian Wehrli Date: Tue, 2 Feb 2021 11:15:12 +0000 Subject: [PATCH 2153/3170] Translated using Weblate (German) Currently translated at 63.6% (402 of 632 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/locales/de.json b/locales/de.json index 23f9bc217..13534f5f3 100644 --- a/locales/de.json +++ b/locales/de.json @@ -170,9 +170,9 @@ "certmanager_certificate_fetching_or_enabling_failed": "Die Aktivierung des neuen Zertifikats für die {domain:s} ist fehlgeschlagen...", "certmanager_attempt_to_renew_nonLE_cert": "Das Zertifikat der Domain '{domain:s}' wurde nicht von Let's Encrypt ausgestellt. Es kann nicht automatisch erneuert werden!", "certmanager_attempt_to_renew_valid_cert": "Das Zertifikat der Domain {domain:s} läuft nicht in Kürze ab! (Benutze --force um diese Nachricht zu umgehen)", - "certmanager_domain_http_not_working": "Die Domäne {domain:s} scheint über HTTP nicht erreichbar zu sein. Für weitere Informationen überprüfen Sie bitte die Kategorie 'Web' im Diagnose-Bereich. (Wenn Sie wißen was Sie tun, nutzen Sie '--no-checks' um die Überprüfung zu überspringen.)", + "certmanager_domain_http_not_working": "Es scheint so, dass die Domain {domain:s} nicht über HTTP erreicht werden kann. Bitte überprüfe, ob deine DNS und nginx Konfiguration in Ordnung ist. (Wenn du weißt was du tust, nutze \"--no-checks\" um die überprüfung zu überspringen.)", "certmanager_error_no_A_record": "Kein DNS 'A' Eintrag für die Domain {domain:s} gefunden. Dein Domainname muss auf diese Maschine weitergeleitet werden, um ein Let's Encrypt Zertifikat installieren zu können! (Wenn du weißt was du tust, kannst du --no-checks benutzen, um diese Überprüfung zu überspringen. )", - "certmanager_domain_dns_ip_differs_from_public_ip": "Die DNS-Einträge der Domäne {domain:s} unterscheiden sich von der IP dieses Servers. Wenn Sie gerade Ihren A-Eintrag verändert haben, warten Sie bitte etwas, damit die Änderungen wirksam werden (Sie können die DNS Propagation mittels Website überprüfen) (Wenn Sie wißen was Sie tun, können Sie --no-checks benutzen, um diese Überprüfung zu überspringen. )", + "certmanager_domain_dns_ip_differs_from_public_ip": "Der DNS 'A' Eintrag der Domain {domain:s} unterscheidet sich von dieser Server-IP. Für weitere Informationen überprüfe bitte die 'DNS records' (basic) Kategorie in der Diagnose. Wenn du gerade deinen A Eintrag verändert hast, warte bitte etwas, damit die Änderungen wirksam werden (du kannst die DNS Propagation mittels Website überprüfen) (Wenn du weißt was du tust, kannst du --no-checks benutzen, um diese Überprüfung zu überspringen. )", "certmanager_cannot_read_cert": "Es ist ein Fehler aufgetreten, als es versucht wurde das aktuelle Zertifikat für die Domain {domain:s} zu öffnen (Datei: {file:s}), Grund: {reason:s}", "certmanager_cert_install_success_selfsigned": "Ein selbstsigniertes Zertifikat für die Domain {domain:s} wurde erfolgreich installiert", "certmanager_cert_install_success": "Für die Domain {domain:s} wurde erfolgreich ein Let's Encrypt Zertifikat installiert.", @@ -377,7 +377,7 @@ "service_reloaded_or_restarted": "Der Dienst '{service:s}' wurde erfolgreich neu geladen oder gestartet", "service_restarted": "Der Dienst '{service:s}' wurde neu gestartet", "service_regen_conf_is_deprecated": "'yunohost service regen-conf' ist veraltet! Bitte verwenden Sie stattdessen 'yunohost tools regen-conf'.", - "certmanager_warning_subdomain_dns_record": "Die Subdomain '{subdomain:s}' löst nicht dieselbe IP wie '{domain:s} auf. Einige Funktionen werden nicht verfügbar sein, solange Sie dies nicht beheben und das Zertifikat erneuern.", + "certmanager_warning_subdomain_dns_record": "Die Subdomäne \"{subdomain:s}\" löst nicht zur gleichen IP Adresse auf wie \"{domain:s}\". Einige Funktionen sind nicht verfügbar bis du dies behebst und die Zertifikate neu erzeugst.", "diagnosis_ports_ok": "Port {port} ist von außen erreichbar.", "diagnosis_ram_verylow": "Das System hat nur {available} ({available_percent}%) RAM zur Verfügung! (von insgesamt {total})", "diagnosis_mail_outgoing_port_25_blocked_details": "Sie sollten zuerst versuchen den ausgehenden Port 25 auf Ihrer Router-Konfigurationsoberfläche oder Ihrer Hosting-Anbieter-Konfigurationsoberfläche zu öffnen. (Bei einigen Hosting-Anbieter kann es sein, daß Sie verlangen, daß man dafür ein Support-Ticket sendet).", From 0ff75e1e2274747fe7ee027ed811c078629a89cc Mon Sep 17 00:00:00 2001 From: Christian Wehrli Date: Wed, 3 Feb 2021 06:40:20 +0000 Subject: [PATCH 2154/3170] Translated using Weblate (German) Currently translated at 64.5% (408 of 632 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/locales/de.json b/locales/de.json index 13534f5f3..a979031b3 100644 --- a/locales/de.json +++ b/locales/de.json @@ -65,9 +65,9 @@ "executing_script": "Skript '{script:s}' wird ausgeührt…", "extracting": "Wird entpackt...", "field_invalid": "Feld '{:s}' ist unbekannt", - "firewall_reload_failed": "Die Firewall konnte nicht neu geladen werden", - "firewall_reloaded": "Die Firewall wurde neu geladen", - "firewall_rules_cmd_failed": "Einige Befehle für die Firewallregeln konnten nicht ausgeführt werden. Mehr Informationen sind im Log zu finden.", + "firewall_reload_failed": "Firewall konnte nicht neu geladen werden", + "firewall_reloaded": "Firewall neu geladen", + "firewall_rules_cmd_failed": "Einige Befehle für die Firewallregeln sind gescheitert. Mehr Informationen im Log.", "hook_exec_failed": "Skriptausführung fehlgeschlagen: {path:s}", "hook_exec_not_terminated": "Skriptausführung noch nicht beendet: {path:s}", "hook_list_by_invalid": "Ungültiger Wert zur Anzeige von Hooks", @@ -203,10 +203,10 @@ "backup_archive_writing_error": "Die Dateien '{source:s} (im Ordner '{dest:s}') konnten nicht in das komprimierte Archiv-Backup '{archive:s}' hinzugefügt werden", "app_change_url_success": "{app:s} URL ist nun {domain:s}{path:s}", "backup_applying_method_borg": "Sende alle Dateien zur Sicherung ins borg-backup repository...", - "global_settings_bad_type_for_setting": "Falscher Typ für Einstellung {setting:s}. Empfangen: {received_type:s}, aber erwartet: {expected_type:s}", - "global_settings_bad_choice_for_enum": "Der Wert dieses Einstellungsparameters {setting:s} ist ungültig. Der Wert den Sie eingegeben haben: '{choice:s}', die gültigen Werte für diese Einstellung: {available_choices:s}", - "file_does_not_exist": "Die Datei {path:s} existiert nicht.", - "experimental_feature": "Warnung: Diese Funktion ist experimentell und gilt nicht als stabil. Sie sollten sie nur verwenden, wenn Sie wissen, was Sie tun.", + "global_settings_bad_type_for_setting": "Falscher Typ der Einstellung {setting:s}. Empfangen: {received_type:s}, aber erwarteter Typ: {expected_type:s}", + "global_settings_bad_choice_for_enum": "Wert des Einstellungsparameters {setting:s} ungültig. Der Wert den Sie eingegeben haben: '{choice:s}', die gültigen Werte für diese Einstellung: {available_choices:s}", + "file_does_not_exist": "Die Datei {path: s} existiert nicht.", + "experimental_feature": "Warnung: Der Maintainer hat diese Funktion als experimentell gekennzeichnet. Sie ist nicht stabil. Sie sollten sie nur verwenden, wenn Sie wissen, was Sie tun.", "dyndns_domain_not_provided": "Der DynDNS-Anbieter {provider:s} kann die Domäne(n) {domain:s} nicht bereitstellen.", "dyndns_could_not_check_available": "Konnte nicht überprüfen, ob {domain:s} auf {provider:s} verfügbar ist.", "dyndns_could_not_check_provide": "Konnte nicht überprüft, ob {provider:s} die Domain(s) {domain:s} bereitstellen kann.", @@ -491,5 +491,6 @@ "global_settings_setting_pop3_enabled": "Aktiviere das POP3 Protokoll für den Mailserver", "domain_cannot_remove_main_add_new_one": "Du kannst \"{domain:s}\" nicht entfernen da es die Hauptdomain und deine einzige Domain ist, erst musst erst eine andere Domain hinzufügen indem du eingibst \"yunohost domain add \", setze es dann als deine Hauptdomain indem du eingibst \"yunohost domain main-domain -n \", erst jetzt kannst du die domain \"{domain:s}\" entfernen.", "diagnosis_rootfstotalspace_critical": "Das Root-Filesystem hat noch freien Speicher von {space}. Das ist besorngiserregend! Der Speicher wird schnell aufgebraucht sein. 16 GB für das Root-Filesystem werden empfohlen.", - "diagnosis_rootfstotalspace_warning": "Das Root-Filesystem hat noch freien Speicher von {space}. Möglich, dass das in Ordnung ist. Vielleicht ist er aber auch schneller aufgebraucht. 16 GB für das Root-Filesystem werden empfohlen." + "diagnosis_rootfstotalspace_warning": "Das Root-Filesystem hat noch freien Speicher von {space}. Möglich, dass das in Ordnung ist. Vielleicht ist er aber auch schneller aufgebraucht. 16 GB für das Root-Filesystem werden empfohlen.", + "global_settings_setting_smtp_relay_host": "Zu verwendender SMTP-Relay-Host um E-Mails zu versenden. Er wird anstelle dieser YunoHost-Instanz verwendet. Nützlich, wenn Sie in einer der folgenden Situationen sind: Ihr ISP- oder VPS-Provider hat Ihren Port 25 geblockt, eine Ihrer residentiellen IPs ist auf DUHL gelistet, Sie können keinen Reverse-DNS konfigurieren oder dieser Server ist nicht direkt mit dem Internet verbunden und Sie möchten einen anderen verwenden, um E-Mails zu versenden." } From def4115fd7b46b64fdab5ca426348b88cdbabbd7 Mon Sep 17 00:00:00 2001 From: Christian Wehrli Date: Wed, 3 Feb 2021 19:24:11 +0000 Subject: [PATCH 2155/3170] Translated using Weblate (German) Currently translated at 64.7% (409 of 632 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/de.json b/locales/de.json index a979031b3..0bacbc39a 100644 --- a/locales/de.json +++ b/locales/de.json @@ -492,5 +492,6 @@ "domain_cannot_remove_main_add_new_one": "Du kannst \"{domain:s}\" nicht entfernen da es die Hauptdomain und deine einzige Domain ist, erst musst erst eine andere Domain hinzufügen indem du eingibst \"yunohost domain add \", setze es dann als deine Hauptdomain indem du eingibst \"yunohost domain main-domain -n \", erst jetzt kannst du die domain \"{domain:s}\" entfernen.", "diagnosis_rootfstotalspace_critical": "Das Root-Filesystem hat noch freien Speicher von {space}. Das ist besorngiserregend! Der Speicher wird schnell aufgebraucht sein. 16 GB für das Root-Filesystem werden empfohlen.", "diagnosis_rootfstotalspace_warning": "Das Root-Filesystem hat noch freien Speicher von {space}. Möglich, dass das in Ordnung ist. Vielleicht ist er aber auch schneller aufgebraucht. 16 GB für das Root-Filesystem werden empfohlen.", - "global_settings_setting_smtp_relay_host": "Zu verwendender SMTP-Relay-Host um E-Mails zu versenden. Er wird anstelle dieser YunoHost-Instanz verwendet. Nützlich, wenn Sie in einer der folgenden Situationen sind: Ihr ISP- oder VPS-Provider hat Ihren Port 25 geblockt, eine Ihrer residentiellen IPs ist auf DUHL gelistet, Sie können keinen Reverse-DNS konfigurieren oder dieser Server ist nicht direkt mit dem Internet verbunden und Sie möchten einen anderen verwenden, um E-Mails zu versenden." + "global_settings_setting_smtp_relay_host": "Zu verwendender SMTP-Relay-Host um E-Mails zu versenden. Er wird anstelle dieser YunoHost-Instanz verwendet. Nützlich, wenn Sie in einer der folgenden Situationen sind: Ihr ISP- oder VPS-Provider hat Ihren Port 25 geblockt, eine Ihrer residentiellen IPs ist auf DUHL gelistet, Sie können keinen Reverse-DNS konfigurieren oder dieser Server ist nicht direkt mit dem Internet verbunden und Sie möchten einen anderen verwenden, um E-Mails zu versenden.", + "global_settings_setting_backup_compress_tar_archives": "Beim Erstellen von Backups die Archive komprimieren (.tar.gz) anstelle von unkomprimierten Archiven (.tar). N.B. : Diese Option ergibt leichtere Backup-Archive, aber das initiale Backupprozedere wird länger dauern und mehr CPU brauchen." } From eacc571cc3b4bef24d4a614c02457e831ec73d4e Mon Sep 17 00:00:00 2001 From: Christian Wehrli Date: Fri, 5 Feb 2021 07:17:42 +0000 Subject: [PATCH 2156/3170] Translated using Weblate (German) Currently translated at 68.3% (432 of 632 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 38 +++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/locales/de.json b/locales/de.json index 0bacbc39a..48de3df5f 100644 --- a/locales/de.json +++ b/locales/de.json @@ -68,12 +68,12 @@ "firewall_reload_failed": "Firewall konnte nicht neu geladen werden", "firewall_reloaded": "Firewall neu geladen", "firewall_rules_cmd_failed": "Einige Befehle für die Firewallregeln sind gescheitert. Mehr Informationen im Log.", - "hook_exec_failed": "Skriptausführung fehlgeschlagen: {path:s}", - "hook_exec_not_terminated": "Skriptausführung noch nicht beendet: {path:s}", - "hook_list_by_invalid": "Ungültiger Wert zur Anzeige von Hooks", + "hook_exec_failed": "Konnte Skript nicht ausführen: {path:s}", + "hook_exec_not_terminated": "Skript ist nicht normal beendet worden: {path:s}", + "hook_list_by_invalid": "Dieser Wert kann nicht verwendet werden, um Hooks anzuzeigen", "hook_name_unknown": "Hook '{name:s}' ist nicht bekannt", "installation_complete": "Installation vollständig", - "installation_failed": "Installation fehlgeschlagen", + "installation_failed": "Etwas ist mit der Installation falsch gelaufen", "ip6tables_unavailable": "ip6tables kann nicht verwendet werden. Du befindest dich entweder in einem Container oder es wird nicht vom Kernel unterstützt", "iptables_unavailable": "iptables kann nicht verwendet werden. Du befindest dich entweder in einem Container oder es wird nicht vom Kernel unterstützt", "ldap_initialized": "LDAP wurde initialisiert", @@ -253,14 +253,14 @@ "backup_copying_to_organize_the_archive": "Kopieren von {size:s} MB, um das Archiv zu organisieren", "global_settings_setting_security_ssh_compatibility": "Kompatibilität vs. Sicherheitskompromiss für den SSH-Server. Beeinflusst die Chiffren (und andere sicherheitsrelevante Aspekte)", "group_deleted": "Gruppe '{group}' gelöscht", - "group_deletion_failed": "Kann Gruppe '{group}' nicht löschen", + "group_deletion_failed": "Konnte Gruppe '{group}' nicht löschen", "dyndns_provider_unreachable": "DynDNS-Anbieter {provider} kann nicht erreicht werden: Entweder ist dein YunoHost nicht korrekt mit dem Internet verbunden oder der Dynette-Server ist ausgefallen.", "group_created": "Gruppe '{group}' angelegt", - "group_creation_failed": "Kann Gruppe '{group}' nicht anlegen", + "group_creation_failed": "Konnte Gruppe '{group}' nicht anlegen", "group_unknown": "Die Gruppe '{group:s}' ist unbekannt", "group_updated": "Gruppe '{group:s}' erneuert", "group_update_failed": "Kann Gruppe '{group:s}' nicht aktualisieren: {error}", - "log_does_exists": "Es gibt kein Operationsprotokoll mit dem Namen'{log}', verwende'yunohost log list', um alle verfügbaren Operationsprotokolle anzuzeigen", + "log_does_exists": "Es gibt kein Operationsprotokoll mit dem Namen'{log}', verwende 'yunohost log list', um alle verfügbaren Operationsprotokolle anzuzeigen", "log_operation_unit_unclosed_properly": "Die Operationseinheit wurde nicht richtig geschlossen", "global_settings_setting_security_postfix_compatibility": "Kompatibilität vs. Sicherheitskompromiss für den Postfix-Server. Beeinflusst die Chiffren (und andere sicherheitsrelevante Aspekte)", "log_category_404": "Die Log-Kategorie '{category}' existiert nicht", @@ -274,28 +274,28 @@ "backup_php5_to_php7_migration_may_fail": "Dein Archiv konnte nicht für PHP 7 konvertiert werden, Du kannst deine PHP-Anwendungen möglicherweise nicht wiederherstellen (Grund: {error:s})", "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Erlaubt die Verwendung eines (veralteten) DSA-Hostkeys für die SSH-Daemon-Konfiguration", "global_settings_setting_example_string": "Beispiel einer string Option", - "log_app_remove": "Entferne die Anwendung '{}'", + "log_app_remove": "Entferne die Applikation '{}'", "global_settings_setting_example_int": "Beispiel einer int Option", "global_settings_cant_open_settings": "Einstellungsdatei konnte nicht geöffnet werden, Grund: {reason:s}", "global_settings_cant_write_settings": "Einstellungsdatei konnte nicht gespeichert werden, Grund: {reason:s}", - "log_app_install": "Installiere die Anwendung '{}'", + "log_app_install": "Installiere die Applikation '{}'", "global_settings_reset_success": "Frühere Einstellungen werden nun auf {path:s} gesichert", - "log_app_upgrade": "Upgrade der Anwendung '{}'", - "good_practices_about_admin_password": "Sie sind nun dabei, ein neues Administrationspasswort zu definieren. Das Passwort sollte mindestens 8 Zeichen lang sein - obwohl es sinnvoll ist, ein längeres Passwort (z.B. eine Passphrase) und/oder eine Variation von Zeichen (Groß- und Kleinschreibung, Ziffern und Sonderzeichen) zu verwenden.", + "log_app_upgrade": "Upgrade der Applikation '{}'", + "good_practices_about_admin_password": "Sie sind nun dabei, ein neues Administrationspasswort zu definieren. Das Passwort sollte mindestens 8 Zeichen lang sein, obwohl es sinnvoll ist, ein längeres Passwort (z.B. eine Passphrase) und/oder eine Variation von Zeichen (Groß- und Kleinschreibung, Ziffern und Sonderzeichen) zu verwenden.", "log_corrupted_md_file": "Die mit Protokollen verknüpfte YAML-Metadatendatei ist beschädigt: '{md_file}\nFehler: {error}''", "global_settings_cant_serialize_settings": "Einstellungsdaten konnten nicht serialisiert werden, Grund: {reason:s}", "log_help_to_get_failed_log": "Der Vorgang'{desc}' konnte nicht abgeschlossen werden. Bitte teile das vollständige Protokoll dieser Operation mit dem Befehl 'yunohost log share {name}', um Hilfe zu erhalten", "backup_no_uncompress_archive_dir": "Dieses unkomprimierte Archivverzeichnis gibt es nicht", - "log_app_change_url": "Ändere die URL der Anwendung '{}'", + "log_app_change_url": "Ändere die URL der Applikation '{}'", "global_settings_setting_security_password_user_strength": "Stärke des Benutzerpassworts", - "good_practices_about_user_password": "Du bist nun dabei, ein neues Benutzerpasswort zu definieren. Das Passwort sollte mindestens 8 Zeichen lang sein - obwohl es ratsam ist, ein längeres Passwort (z.B. eine Passphrase) und/oder eine Variation von Zeichen (Groß- und Kleinschreibung, Ziffern und Sonderzeichen) zu verwenden.", + "good_practices_about_user_password": "Sie sind dabei, ein neues Benutzerpasswort zu definieren. Das Passwort sollte mindestens 8 Zeichen lang sein, obwohl es ratsam ist, ein längeres Passwort (z.B. eine Passphrase) und/oder eine Variation von Zeichen (Groß- und Kleinschreibung, Ziffern und Sonderzeichen) zu verwenden.", "global_settings_setting_example_enum": "Beispiel einer enum Option", "log_link_to_failed_log": "Der Vorgang konnte nicht abgeschlossen werden '{desc}'. Bitte gib das vollständige Protokoll dieser Operation mit Klicken Sie hier an, um Hilfe zu erhalten", "backup_cant_mount_uncompress_archive": "Das unkomprimierte Archiv konnte nicht als schreibgeschützt gemountet werden", "backup_csv_addition_failed": "Es konnten keine Dateien zur Sicherung in die CSV-Datei hinzugefügt werden", "global_settings_setting_security_password_admin_strength": "Stärke des Admin-Passworts", "global_settings_key_doesnt_exists": "Der Schlüssel'{settings_key:s}' existiert nicht in den globalen Einstellungen, du kannst alle verfügbaren Schlüssel sehen, indem du 'yunohost settings list' ausführst", - "log_app_makedefault": "Mache '{}' zur Standard-Anwendung", + "log_app_makedefault": "Mache '{}' zur Standard-Applikation", "hook_json_return_error": "Konnte die Rückkehr vom Einsprungpunkt {path:s} nicht lesen. Fehler: {msg:s}. Unformatierter Inhalt: {raw_content}", "app_full_domain_unavailable": "Es tut uns leid, aber diese Anwendung erfordert die Installation auf einer eigenen Domain, aber einige andere Anwendungen sind bereits auf der Domäne'{domain}' installiert. Eine mögliche Lösung ist das Hinzufügen und Verwenden einer Subdomain, die dieser Anwendung zugeordnet ist.", "app_install_failed": "{app} kann nicht installiert werden: {error}", @@ -493,5 +493,13 @@ "diagnosis_rootfstotalspace_critical": "Das Root-Filesystem hat noch freien Speicher von {space}. Das ist besorngiserregend! Der Speicher wird schnell aufgebraucht sein. 16 GB für das Root-Filesystem werden empfohlen.", "diagnosis_rootfstotalspace_warning": "Das Root-Filesystem hat noch freien Speicher von {space}. Möglich, dass das in Ordnung ist. Vielleicht ist er aber auch schneller aufgebraucht. 16 GB für das Root-Filesystem werden empfohlen.", "global_settings_setting_smtp_relay_host": "Zu verwendender SMTP-Relay-Host um E-Mails zu versenden. Er wird anstelle dieser YunoHost-Instanz verwendet. Nützlich, wenn Sie in einer der folgenden Situationen sind: Ihr ISP- oder VPS-Provider hat Ihren Port 25 geblockt, eine Ihrer residentiellen IPs ist auf DUHL gelistet, Sie können keinen Reverse-DNS konfigurieren oder dieser Server ist nicht direkt mit dem Internet verbunden und Sie möchten einen anderen verwenden, um E-Mails zu versenden.", - "global_settings_setting_backup_compress_tar_archives": "Beim Erstellen von Backups die Archive komprimieren (.tar.gz) anstelle von unkomprimierten Archiven (.tar). N.B. : Diese Option ergibt leichtere Backup-Archive, aber das initiale Backupprozedere wird länger dauern und mehr CPU brauchen." + "global_settings_setting_backup_compress_tar_archives": "Beim Erstellen von Backups die Archive komprimieren (.tar.gz) anstelle von unkomprimierten Archiven (.tar). N.B. : Diese Option ergibt leichtere Backup-Archive, aber das initiale Backupprozedere wird länger dauern und mehr CPU brauchen.", + "log_remove_on_failed_restore": "'{}' entfernen nach einer fehlerhaften Wiederherstellung aus einem Backup-Archiv", + "log_backup_restore_app": "'{}' aus einem Backup-Archiv wiederherstellen", + "log_backup_restore_system": "System aus einem Backup-Archiv wiederherstellen", + "log_available_on_yunopaste": "Das Protokoll ist nun via {url} verfügbar", + "log_app_config_apply": "Wende die Konfiguration auf die Applikation '{}' an", + "log_app_config_show_panel": "Zeige das Konfigurations-Panel der Applikation '{}'", + "log_app_action_run": "Führe Aktion der Applikation '{}' aus", + "invalid_regex": "Ungültige Regex:'{regex:s}'" } From 46ceef146770310ee91f55c60f4d96aba01e2a89 Mon Sep 17 00:00:00 2001 From: xaloc33 Date: Mon, 8 Feb 2021 19:09:26 +0000 Subject: [PATCH 2157/3170] 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 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/locales/ca.json b/locales/ca.json index 7924193d0..b6888d391 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -714,5 +714,8 @@ "additional_urls_already_removed": "URL addicional «{url:s}» ja ha estat eliminada per al permís «{permission:s}»", "additional_urls_already_added": "URL addicional «{url:s}» ja ha estat afegida per al permís «{permission:s}»", "diagnosis_backports_in_sources_list": "Sembla que apt (el gestor de paquets) està configurat per utilitzar el repositori backports. A menys de saber el que esteu fent, recomanem fortament no instal·lar paquets de backports, ja que poder causar inestabilitats o conflictes en el sistema.", - "diagnosis_basesystem_hardware_model": "El model del servidor és {model}" + "diagnosis_basesystem_hardware_model": "El model del servidor és {model}", + "postinstall_low_rootfsspace": "El sistema de fitxers arrel té un total de menys de 10 GB d'espai, el que es preocupant! És molt probable que us quedeu sense espai ràpidament! Es recomana tenir un mínim de 16 GB per al sistema de fitxers arrel. Si voleu instal·lar YunoHost tot i aquest avís, torneu a executar la postinstal·lació amb --force-diskspace", + "diagnosis_rootfstotalspace_critical": "El sistema de fitxers arrel només té {space} en total i és preocupant! És molt probable que us quedeu sense espai ràpidament! Es recomanar tenir un mínim de 16 GB per al sistema de fitxers arrel.", + "diagnosis_rootfstotalspace_warning": "El sistema de fitxers arrel només té {space} en total. Això no hauria de causar cap problema, però haureu de parar atenció ja que us podrieu quedar sense espai ràpidament… Es recomanar tenir un mínim de 16 GB per al sistema de fitxers arrel." } From 351928f566d06ca4aa9add30563a8cfe65bbf8ec Mon Sep 17 00:00:00 2001 From: Christian Wehrli Date: Wed, 10 Feb 2021 19:25:35 +0000 Subject: [PATCH 2158/3170] Translated using Weblate (German) Currently translated at 72.1% (456 of 632 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/locales/de.json b/locales/de.json index 48de3df5f..40d7ec5e7 100644 --- a/locales/de.json +++ b/locales/de.json @@ -172,7 +172,7 @@ "certmanager_attempt_to_renew_valid_cert": "Das Zertifikat der Domain {domain:s} läuft nicht in Kürze ab! (Benutze --force um diese Nachricht zu umgehen)", "certmanager_domain_http_not_working": "Es scheint so, dass die Domain {domain:s} nicht über HTTP erreicht werden kann. Bitte überprüfe, ob deine DNS und nginx Konfiguration in Ordnung ist. (Wenn du weißt was du tust, nutze \"--no-checks\" um die überprüfung zu überspringen.)", "certmanager_error_no_A_record": "Kein DNS 'A' Eintrag für die Domain {domain:s} gefunden. Dein Domainname muss auf diese Maschine weitergeleitet werden, um ein Let's Encrypt Zertifikat installieren zu können! (Wenn du weißt was du tust, kannst du --no-checks benutzen, um diese Überprüfung zu überspringen. )", - "certmanager_domain_dns_ip_differs_from_public_ip": "Der DNS 'A' Eintrag der Domain {domain:s} unterscheidet sich von dieser Server-IP. Für weitere Informationen überprüfe bitte die 'DNS records' (basic) Kategorie in der Diagnose. Wenn du gerade deinen A Eintrag verändert hast, warte bitte etwas, damit die Änderungen wirksam werden (du kannst die DNS Propagation mittels Website überprüfen) (Wenn du weißt was du tust, kannst du --no-checks benutzen, um diese Überprüfung zu überspringen. )", + "certmanager_domain_dns_ip_differs_from_public_ip": "Der DNS-A-Eintrag der Domain {domain:s} unterscheidet sich von dieser Server-IP. Für weitere Informationen überprüfen Sie bitte die 'DNS records' (basic) Kategorie in der Diagnose. Wenn Sie gerade Ihren A-Eintrag verändert haben, warten Sie bitte etwas, damit die Änderungen wirksam werden (Sie können die DNS-Propagation mittels Website überprüfen) (Wenn Sie wissen was Sie tun, können Sie --no-checks benutzen, um diese Überprüfung zu überspringen.)", "certmanager_cannot_read_cert": "Es ist ein Fehler aufgetreten, als es versucht wurde das aktuelle Zertifikat für die Domain {domain:s} zu öffnen (Datei: {file:s}), Grund: {reason:s}", "certmanager_cert_install_success_selfsigned": "Ein selbstsigniertes Zertifikat für die Domain {domain:s} wurde erfolgreich installiert", "certmanager_cert_install_success": "Für die Domain {domain:s} wurde erfolgreich ein Let's Encrypt Zertifikat installiert.", @@ -501,5 +501,29 @@ "log_app_config_apply": "Wende die Konfiguration auf die Applikation '{}' an", "log_app_config_show_panel": "Zeige das Konfigurations-Panel der Applikation '{}'", "log_app_action_run": "Führe Aktion der Applikation '{}' aus", - "invalid_regex": "Ungültige Regex:'{regex:s}'" + "invalid_regex": "Ungültige Regex:'{regex:s}'", + "migration_description_0016_php70_to_php73_pools": "Migrieren der php7.0-fpm-Konfigurationsdateien zu php7.3", + "mailbox_disabled": "E-Mail für Benutzer {user:s} deaktiviert", + "log_tools_reboot": "Server neustarten", + "log_tools_shutdown": "Server ausschalten", + "log_tools_upgrade": "Systempakete aktualisieren", + "log_tools_postinstall": "Post-Installation des YunoHost-Servers durchführen", + "log_tools_migrations_migrate_forward": "Migrationen durchführen", + "log_domain_main_domain": "Mache '{}' zur Hauptdomäne", + "log_user_permission_reset": "Zurücksetzen der Berechtigung '{}'", + "log_user_permission_update": "Aktualisiere Zugriffe für Berechtigung '{}'", + "log_user_update": "Aktualisiere Information für Benutzer '{}'", + "log_user_group_update": "Aktualisiere Gruppe '{}'", + "log_user_group_delete": "Lösche Gruppe '{}'", + "log_user_group_create": "Erstelle Gruppe '{}'", + "log_user_delete": "Lösche Benutzer '{}'", + "log_user_create": "Füge Benutzer '{}' hinzu", + "log_permission_url": "Aktualisiere URL, die mit der Berechtigung '{}' verknüpft ist", + "log_permission_delete": "Lösche Berechtigung '{}'", + "log_permission_create": "Erstelle Berechtigung '{}'", + "log_dyndns_update": "Die IP, die mit der YunoHost-Subdomain '{}' verbunden ist, aktualisieren", + "log_dyndns_subscribe": "Für eine YunoHost-Subdomain registrieren '{}'", + "log_domain_remove": "Entfernen der Domäne '{}' aus der Systemkonfiguration", + "log_domain_add": "Hinzufügen der Domäne '{}' zur Systemkonfiguration", + "log_remove_on_failed_install": "Entfernen von '{}' nach einer fehlgeschlagenen Installation" } From 5e6f77c8f724ae8b0fdfe2db7f2d165868ad04da Mon Sep 17 00:00:00 2001 From: Christian Wehrli Date: Mon, 15 Feb 2021 06:31:00 +0000 Subject: [PATCH 2159/3170] Translated using Weblate (German) Currently translated at 73.4% (465 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/locales/de.json b/locales/de.json index 40d7ec5e7..2699ebe74 100644 --- a/locales/de.json +++ b/locales/de.json @@ -525,5 +525,14 @@ "log_dyndns_subscribe": "Für eine YunoHost-Subdomain registrieren '{}'", "log_domain_remove": "Entfernen der Domäne '{}' aus der Systemkonfiguration", "log_domain_add": "Hinzufügen der Domäne '{}' zur Systemkonfiguration", - "log_remove_on_failed_install": "Entfernen von '{}' nach einer fehlgeschlagenen Installation" + "log_remove_on_failed_install": "Entfernen von '{}' nach einer fehlgeschlagenen Installation", + "migration_0015_still_on_stretch_after_main_upgrade": "Etwas ist schiefgelaufen während dem Haupt-Upgrade. Das System scheint immer noch auf Debian Stretch zu laufen", + "migration_0015_yunohost_upgrade": "Beginne YunoHost-Core-Upgrade...", + "migration_description_0019_extend_permissions_features": "Erweitern und überarbeiten des Applikationsberechtigungs-Managementsystems", + "migrating_legacy_permission_settings": "Migrieren der Legacy-Berechtigungseinstellungen...", + "migration_description_0017_postgresql_9p6_to_11": "Migrieren der Datenbanken von PostgreSQL 9.6 nach 11", + "migration_0015_main_upgrade": "Beginne Haupt-Upgrade...", + "migration_0015_not_stretch": "Die aktuelle Debian-Distribution ist nicht Stretch!", + "migration_0015_not_enough_free_space": "Der freie Speicher in /var/ ist sehr gering! Sie sollten minimal 1GB frei haben, um diese Migration durchzuführen.", + "domain_remove_confirm_apps_removal": "Wenn Sie diese Domäne löschen, werden folgende Applikationen entfernt:\n{apps}\n\nSind Sie sicher? [{answers}]" } From b050a35b8a27750afcb2f45cb6812ead44965ffc Mon Sep 17 00:00:00 2001 From: MrMorals Date: Fri, 19 Feb 2021 16:46:19 +0000 Subject: [PATCH 2160/3170] Translated using Weblate (Dutch) Currently translated at 8.3% (53 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/nl/ --- locales/nl.json | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/locales/nl.json b/locales/nl.json index dfee556b2..63ec7bd6d 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -3,15 +3,15 @@ "admin_password": "Administrator wachtwoord", "admin_password_changed": "Het administratie wachtwoord werd gewijzigd", "app_already_installed": "{app:s} is al geïnstalleerd", - "app_argument_invalid": "'{name:s}' bevat ongeldige waarde: {error:s}", + "app_argument_invalid": "Kies een geldige waarde voor '{name:s}': {error:s}", "app_argument_required": "Het '{name:s}' moet ingevuld worden", "app_extraction_failed": "Kan installatiebestanden niet uitpakken", "app_id_invalid": "Ongeldige app-id", - "app_install_files_invalid": "Ongeldige installatiebestanden", + "app_install_files_invalid": "Deze bestanden kunnen niet worden geïnstalleerd", "app_manifest_invalid": "Ongeldig app-manifest", "app_not_installed": "{app:s} is niet geïnstalleerd", "app_removed": "{app:s} succesvol verwijderd", - "app_sources_fetch_failed": "Kan bronbestanden niet ophalen", + "app_sources_fetch_failed": "Kan bronbestanden niet ophalen, klopt de URL?", "app_unknown": "Onbekende app", "app_upgrade_failed": "Kan app {app:s} niet updaten", "app_upgraded": "{app:s} succesvol geüpgraded", @@ -82,7 +82,7 @@ "app_argument_choice_invalid": "Ongeldige keuze voor argument '{name:s}'. Het moet een van de volgende keuzes zijn {choices:s}", "app_not_correctly_installed": "{app:s} schijnt niet juist geïnstalleerd te zijn", "app_not_properly_removed": "{app:s} werd niet volledig verwijderd", - "app_requirements_checking": "Controleer noodzakelijke pakketten...", + "app_requirements_checking": "Noodzakelijke pakketten voor {app} aan het controleren...", "app_requirements_unmeet": "Er wordt niet aan de aanvorderingen voldaan, het pakket {pkgname} ({version}) moet {spec} zijn", "app_unsupported_remote_type": "Niet ondersteund besturings type voor de app", "ask_main_domain": "Hoofd-domein", @@ -101,5 +101,25 @@ "already_up_to_date": "Er is niets te doen, alles is al up-to-date.", "admin_password_too_long": "Gelieve een wachtwoord te kiezen met minder dan 127 karakters", "app_action_cannot_be_ran_because_required_services_down": "De volgende diensten moeten actief zijn om deze actie uit te voeren: {services}. Probeer om deze te herstarten om verder te gaan (en om eventueel te onderzoeken waarom ze niet werken).", - "aborting": "Annulatie." -} \ No newline at end of file + "aborting": "Annulatie.", + "app_upgrade_app_name": "Bezig {app} te upgraden...", + "app_make_default_location_already_used": "Kan '{app}' niet de standaardapp maken op het domein, '{domein}' wordt al gebruikt door '{other_app}'", + "app_install_failed": "Kan {app} niet installeren: {error}", + "app_remove_after_failed_install": "Bezig de app te verwijderen na gefaalde installatie...", + "app_manifest_install_ask_domain": "Kies het domein waar deze app op geïnstalleerd moet worden", + "app_manifest_install_ask_path": "Kies het pad waar deze app geïnstalleerd moet worden", + "app_manifest_install_ask_admin": "Kies een administrator voor deze app", + "app_change_url_failed_nginx_reload": "Kon NGINX niet opnieuw laden. Hier is de output van 'nginx -t':\n{nginx_errors:s}", + "app_change_url_success": "{app:s} URL is nu {domain:s}{path:s}", + "app_full_domain_unavailable": "Sorry, deze app moet op haar eigen domein geïnstalleerd worden, maar andere apps zijn al geïnstalleerd op het domein '{domain}'. U kunt wel een subdomein aan deze app toewijden.", + "app_install_script_failed": "Er is een fout opgetreden in het installatiescript van de app", + "app_location_unavailable": "Deze URL is niet beschikbaar of is in conflict met de al geïnstalleerde app(s):\n{apps:s}", + "app_manifest_install_ask_password": "Kies een administratiewachtwoord voor deze app", + "app_manifest_install_ask_is_public": "Moet deze app zichtbaar zijn voor anomieme bezoekers?", + "app_not_upgraded": "De app '{failed_app}' kon niet upgraden en daardoor zijn de upgrades van de volgende apps geannuleerd: {apps}", + "app_start_install": "{app} installeren...", + "app_start_remove": "{app} verwijderen...", + "app_start_backup": "Bestanden aan het verzamelen voor de backup van {app}...", + "app_start_restore": "{app} herstellen...", + "app_upgrade_several_apps": "De volgende apps zullen worden geüpgraded: {apps}" +} From dc844b5d73167ea923960e6594a08d487039e75b Mon Sep 17 00:00:00 2001 From: Christian Wehrli Date: Mon, 22 Feb 2021 06:45:23 +0000 Subject: [PATCH 2161/3170] Translated using Weblate (German) Currently translated at 74.7% (473 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/locales/de.json b/locales/de.json index 2699ebe74..36f710f07 100644 --- a/locales/de.json +++ b/locales/de.json @@ -534,5 +534,13 @@ "migration_0015_main_upgrade": "Beginne Haupt-Upgrade...", "migration_0015_not_stretch": "Die aktuelle Debian-Distribution ist nicht Stretch!", "migration_0015_not_enough_free_space": "Der freie Speicher in /var/ ist sehr gering! Sie sollten minimal 1GB frei haben, um diese Migration durchzuführen.", - "domain_remove_confirm_apps_removal": "Wenn Sie diese Domäne löschen, werden folgende Applikationen entfernt:\n{apps}\n\nSind Sie sicher? [{answers}]" + "domain_remove_confirm_apps_removal": "Wenn Sie diese Domäne löschen, werden folgende Applikationen entfernt:\n{apps}\n\nSind Sie sicher? [{answers}]", + "migration_0015_cleaning_up": "Bereinigung des Cache und der Pakete, welche nicht mehr benötigt werden...", + "migration_0017_postgresql_96_not_installed": "PostgreSQL wurde auf ihrem System nicht installiert. Nichts zu tun.", + "migration_0015_system_not_fully_up_to_date": "Ihr System ist nicht vollständig auf dem neuesten Stand. Bitte führen Sie ein reguläres Upgrade durch, bevor Sie die Migration auf Buster durchführen.", + "migration_0015_modified_files": "Bitte beachten Sie, dass die folgenden Dateien als manuell bearbeitet erkannt wurden und beim nächsten Upgrade überschrieben werden könnten: {manually_modified_files}", + "migration_0015_general_warning": "Bitte beachten Sie, dass diese Migration eine heikle Angelegenheit darstellt. Das YunoHost-Team hat alles unternommen, um sie zu testen und zu überarbeiten. Dennoch ist es möglich, dass diese Migration Teile des Systems oder Applikationen beschädigen könnte.\n\nDeshalb ist folgendes zu empfehlen:\n…- Führen Sie ein Backup aller kritischen Daten und Applikationen durch. Mehr unter https://yunohost.org/backup;\n…- Seien Sie geduldig nachdem Sie die Migration gestartet haben: Abhängig von Ihrer Internetverbindung und Ihrer Hardware kann es einige Stunden dauern, bis das Upgrade fertig ist.", + "migration_0015_problematic_apps_warning": "Bitte beachten Sie, dass folgende möglicherweise problematischen Applikationen auf Ihrer Installation erkannt wurden. Es scheint, als ob sie nicht aus dem YunoHost-Applikationskatalog installiert oder nicht als 'working' gekennzeichnet worden sind. Folglich kann nicht garantiert werden, dass sie nach dem Upgrade immer noch funktionieren: {problematic_apps}", + "migration_0015_specific_upgrade": "Start des Upgrades der Systempakete, deren Upgrade separat durchgeführt werden muss...", + "migration_0015_weak_certs": "Die folgenden Zertifikate verwenden immer noch schwache Signierungsalgorithmen und müssen aktualisiert werden um mit der nächsten Version von nginx kompatibel zu sein: {certs}" } From 2ea7b026533055f94848787fc84b950b1ba6d740 Mon Sep 17 00:00:00 2001 From: Christian Wehrli Date: Mon, 22 Feb 2021 19:23:24 +0000 Subject: [PATCH 2162/3170] Translated using Weblate (German) Currently translated at 78.9% (500 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/locales/de.json b/locales/de.json index 36f710f07..77b4fad49 100644 --- a/locales/de.json +++ b/locales/de.json @@ -542,5 +542,32 @@ "migration_0015_general_warning": "Bitte beachten Sie, dass diese Migration eine heikle Angelegenheit darstellt. Das YunoHost-Team hat alles unternommen, um sie zu testen und zu überarbeiten. Dennoch ist es möglich, dass diese Migration Teile des Systems oder Applikationen beschädigen könnte.\n\nDeshalb ist folgendes zu empfehlen:\n…- Führen Sie ein Backup aller kritischen Daten und Applikationen durch. Mehr unter https://yunohost.org/backup;\n…- Seien Sie geduldig nachdem Sie die Migration gestartet haben: Abhängig von Ihrer Internetverbindung und Ihrer Hardware kann es einige Stunden dauern, bis das Upgrade fertig ist.", "migration_0015_problematic_apps_warning": "Bitte beachten Sie, dass folgende möglicherweise problematischen Applikationen auf Ihrer Installation erkannt wurden. Es scheint, als ob sie nicht aus dem YunoHost-Applikationskatalog installiert oder nicht als 'working' gekennzeichnet worden sind. Folglich kann nicht garantiert werden, dass sie nach dem Upgrade immer noch funktionieren: {problematic_apps}", "migration_0015_specific_upgrade": "Start des Upgrades der Systempakete, deren Upgrade separat durchgeführt werden muss...", - "migration_0015_weak_certs": "Die folgenden Zertifikate verwenden immer noch schwache Signierungsalgorithmen und müssen aktualisiert werden um mit der nächsten Version von nginx kompatibel zu sein: {certs}" + "migration_0015_weak_certs": "Die folgenden Zertifikate verwenden immer noch schwache Signierungsalgorithmen und müssen aktualisiert werden um mit der nächsten Version von nginx kompatibel zu sein: {certs}", + "migrations_pending_cant_rerun": "Diese Migrationen sind immer noch anstehend und können deshalb nicht erneut durchgeführt werden: {ids}", + "migration_0019_add_new_attributes_in_ldap": "Hinzufügen neuer Attribute für die Berechtigungen in der LDAP-Datenbank", + "migration_0019_can_not_backup_before_migration": "Das Backup des Systems konnte nicht abgeschlossen werden bevor die Migration fehlschlug. Fehlermeldung: {error}", + "migration_0019_migration_failed_trying_to_rollback": "Konnte nicht migrieren... versuche ein Rollback des Systems.", + "migrations_not_pending_cant_skip": "Diese Migrationen sind nicht anstehend und können deshalb nicht übersprungen werden: {ids}", + "migration_0018_failed_to_reset_legacy_rules": "Zurücksetzen der veralteten iptables-Regeln fehlgeschlagen: {error}", + "migration_0019_rollback_success": "Rollback des Systems durchgeführt.", + "migration_0019_slapd_config_will_be_overwritten": "Es schaut aus, als ob Sie die slapd-Konfigurationsdatei manuell bearbeitet haben. Für diese kritische Migration muss das Update der slapd-Konfiguration erzwungen werden. Von der Originaldatei wird ein Backup gemacht in {conf_backup_folder}.", + "migrations_success_forward": "Migration {id} abgeschlossen", + "migrations_cant_reach_migration_file": "Die Migrationsdateien konnten nicht aufgerufen werden im Verzeichnis '%s'", + "migrations_dependencies_not_satisfied": "Führen Sie diese Migrationen aus: '{dependencies_id}', vor der Migration {id}.", + "migrations_failed_to_load_migration": "Konnte Migration nicht laden {id}: {error}", + "migrations_list_conflict_pending_done": "Sie können nicht '--previous' und '--done' gleichzeitig benützen.", + "migrations_already_ran": "Diese Migrationen wurden bereits durchgeführt: {ids}", + "migrations_loading_migration": "Lade Migrationen {id}...", + "migrations_migration_has_failed": "Migration {id} gescheitert mit der Ausnahme {exception}: Abbruch", + "migrations_must_provide_explicit_targets": "Sie müssen konkrete Ziele angeben, wenn Sie '--skip' oder '--force-rerun' verwenden", + "migrations_need_to_accept_disclaimer": "Um die Migration {id} durchzuführen, müssen Sie den Disclaimer akzeptieren.\n---\n{disclaimer}\n---\n Wenn Sie bestätigen, dass Sie die Migration durchführen wollen, wiederholen Sie bitte den Befehl mit der Option '--accept-disclaimer'.", + "migrations_no_migrations_to_run": "Keine Migrationen durchzuführen", + "migration_0017_postgresql_11_not_installed": "PostgreSQL 9.6 ist installiert aber nicht postgreSQL 11? Etwas komisches ist Ihrem System zugestossen :(...", + "migration_0017_not_enough_space": "Stellen Siea ausreichend Speicherplatz im Verzeichnis {path} zur Verfügung um die Migration durchzuführen.", + "migration_0018_failed_to_migrate_iptables_rules": "Migration der veralteten iptables-Regeln zu nftables fehlgeschlagen: {error}", + "migration_0019_backup_before_migration": "Ein Backup der LDAP-Datenbank und der Applikationseinstellungen erstellen vor der Migration.", + "migrations_exclusive_options": "'--auto', '--skip' und '--force-rerun' sind Optionen, die sich gegenseitig ausschliessen.", + "migrations_no_such_migration": "Es existiert keine Migration genannt '{id}'", + "migrations_running_forward": "Durchführen der Migrationen {id}...", + "migrations_skip_migration": "Überspringe Migrationen {id}..." } From a0cd4d3ca50b2fdcbeda5b5884d78d547434395f Mon Sep 17 00:00:00 2001 From: Christian Wehrli Date: Wed, 24 Feb 2021 06:52:40 +0000 Subject: [PATCH 2163/3170] Translated using Weblate (German) Currently translated at 79.7% (505 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/locales/de.json b/locales/de.json index 77b4fad49..536bdd842 100644 --- a/locales/de.json +++ b/locales/de.json @@ -569,5 +569,10 @@ "migrations_exclusive_options": "'--auto', '--skip' und '--force-rerun' sind Optionen, die sich gegenseitig ausschliessen.", "migrations_no_such_migration": "Es existiert keine Migration genannt '{id}'", "migrations_running_forward": "Durchführen der Migrationen {id}...", - "migrations_skip_migration": "Überspringe Migrationen {id}..." + "migrations_skip_migration": "Überspringe Migrationen {id}...", + "password_too_simple_2": "Dieses Passwort gehört zu den meistverwendeten der Welt. Bitte nehmen Sie etwas einzigartigeres.", + "password_listed": "Dieses Passwort gehört zu den meistverwendeten der Welt. Bitte nehmen Sie etwas einzigartigeres.", + "operation_interrupted": "Wurde die Operation manuell unterbrochen?", + "invalid_number": "Muss eine Zahl sein", + "migrations_to_be_ran_manually": "Die Migration {id} muss manuell durchgeführt werden. Bitte gehen Sie zu Werkzeuge → Migrationen auf der Webadmin-Seite oder führen Sie 'yunohost tools migrations run' aus." } From a5038b5cc0a4cedffa0bc39ad001b75ced11bd6a Mon Sep 17 00:00:00 2001 From: Nils Van Zuijlen Date: Tue, 23 Feb 2021 14:20:24 +0000 Subject: [PATCH 2164/3170] Translated using Weblate (French) Currently translated at 99.5% (630 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/locales/fr.json b/locales/fr.json index 7d016a70e..47bae6407 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -692,5 +692,9 @@ "invalid_number": "Doit être un nombre", "migration_description_0019_extend_permissions_features": "Étendre et retravailler le système de gestion des permissions applicatives", "diagnosis_basesystem_hardware_model": "Le modèle du serveur est {model}", - "diagnosis_backports_in_sources_list": "Il semble qu'apt (le gestionnaire de paquets) soit configuré pour utiliser le dépôt des rétroportages (backports). A moins que vous ne sachiez vraiment ce que vous faites, nous vous déconseillons fortement d'installer des paquets provenant des rétroportages, car cela risque de créer des instabilités ou des conflits sur votre système." + "diagnosis_backports_in_sources_list": "Il semble qu'apt (le gestionnaire de paquets) soit configuré pour utiliser le dépôt des rétroportages (backports). A moins que vous ne sachiez vraiment ce que vous faites, nous vous déconseillons fortement d'installer des paquets provenant des rétroportages, car cela risque de créer des instabilités ou des conflits sur votre système.", + "postinstall_low_rootfsspace": "Le système de fichiers racine ne fait que {space} ! Vous allez certainement les remplir très rapidement ! Il est recommandé d'avoir au moins 16 GB pour ce système de fichiers. Si vous voulez installer YunoHost malgré cet avertissement, relancez le postinstall avec --force-diskspace", + "domain_remove_confirm_apps_removal": "Le retrait de ce domaine retirera aussi ces applications :\n{apps}\n\nÊtes vous sûr de vouloir cela ? [{answers}]", + "diagnosis_rootfstotalspace_critical": "Le système de fichiers racine ne fait que {space} ! Vous allez certainement les remplir très rapidement ! Il est recommandé d'avoir au moins 16 GB pour ce système de fichiers.", + "diagnosis_rootfstotalspace_warning": "Le système de fichiers racine n'est que de {space}. Ça peut suffire, mais faites attention car vous risquez de les remplire rapidement... Il est recommandé d'avoir au moins 16 GB pour ce système de fichiers." } From fdba94e2e5e0d18686f86efb1d8249accb995042 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 25 Feb 2021 05:17:36 +0100 Subject: [PATCH 2165/3170] Fix fr translation --- locales/fr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/fr.json b/locales/fr.json index 47bae6407..8982d7ccc 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -693,7 +693,7 @@ "migration_description_0019_extend_permissions_features": "Étendre et retravailler le système de gestion des permissions applicatives", "diagnosis_basesystem_hardware_model": "Le modèle du serveur est {model}", "diagnosis_backports_in_sources_list": "Il semble qu'apt (le gestionnaire de paquets) soit configuré pour utiliser le dépôt des rétroportages (backports). A moins que vous ne sachiez vraiment ce que vous faites, nous vous déconseillons fortement d'installer des paquets provenant des rétroportages, car cela risque de créer des instabilités ou des conflits sur votre système.", - "postinstall_low_rootfsspace": "Le système de fichiers racine ne fait que {space} ! Vous allez certainement les remplir très rapidement ! Il est recommandé d'avoir au moins 16 GB pour ce système de fichiers. Si vous voulez installer YunoHost malgré cet avertissement, relancez le postinstall avec --force-diskspace", + "postinstall_low_rootfsspace": "Le système de fichiers racine a une taille totale inférieure à 10 GB, ce qui est inquiétant ! Vous allez certainement arriver à court d'espace disque rapidement ! Il est recommandé d'avoir au moins 16 GB pour ce système de fichiers. Si vous voulez installer YunoHost malgré cet avertissement, relancez le postinstall avec --force-diskspace", "domain_remove_confirm_apps_removal": "Le retrait de ce domaine retirera aussi ces applications :\n{apps}\n\nÊtes vous sûr de vouloir cela ? [{answers}]", "diagnosis_rootfstotalspace_critical": "Le système de fichiers racine ne fait que {space} ! Vous allez certainement les remplir très rapidement ! Il est recommandé d'avoir au moins 16 GB pour ce système de fichiers.", "diagnosis_rootfstotalspace_warning": "Le système de fichiers racine n'est que de {space}. Ça peut suffire, mais faites attention car vous risquez de les remplire rapidement... Il est recommandé d'avoir au moins 16 GB pour ce système de fichiers." From a8b315b873e745dc8a5d039b92456f499e9513d8 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 25 Feb 2021 05:18:21 +0100 Subject: [PATCH 2166/3170] Fix nl translation --- locales/nl.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/nl.json b/locales/nl.json index 63ec7bd6d..59de95f58 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -103,7 +103,7 @@ "app_action_cannot_be_ran_because_required_services_down": "De volgende diensten moeten actief zijn om deze actie uit te voeren: {services}. Probeer om deze te herstarten om verder te gaan (en om eventueel te onderzoeken waarom ze niet werken).", "aborting": "Annulatie.", "app_upgrade_app_name": "Bezig {app} te upgraden...", - "app_make_default_location_already_used": "Kan '{app}' niet de standaardapp maken op het domein, '{domein}' wordt al gebruikt door '{other_app}'", + "app_make_default_location_already_used": "Kan '{app}' niet de standaardapp maken op het domein, '{domain}' wordt al gebruikt door '{other_app}'", "app_install_failed": "Kan {app} niet installeren: {error}", "app_remove_after_failed_install": "Bezig de app te verwijderen na gefaalde installatie...", "app_manifest_install_ask_domain": "Kies het domein waar deze app op geïnstalleerd moet worden", From 8f7ced35a5ee7f1493dcbbbcb42256b8b36a78df Mon Sep 17 00:00:00 2001 From: Kay0u Date: Thu, 25 Feb 2021 11:16:34 +0100 Subject: [PATCH 2167/3170] fix ynh_systemd_action in case of service fails --- data/helpers.d/systemd | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/data/helpers.d/systemd b/data/helpers.d/systemd index b416e5745..f8e21bfbd 100644 --- a/data/helpers.d/systemd +++ b/data/helpers.d/systemd @@ -58,7 +58,7 @@ ynh_remove_systemd_config () { # usage: ynh_systemd_action [--service_name=service_name] [--action=action] [ [--line_match="line to match"] [--log_path=log_path] [--timeout=300] [--length=20] ] # | arg: -n, --service_name= - Name of the service to start. Default : $app # | arg: -a, --action= - Action to perform with systemctl. Default: start -# | arg: -l, --line_match= - Line to match - The line to find in the log to attest the service have finished to boot. If not defined it don't wait until the service is completely started. WARNING: When using --line_match, you should always add `ynh_clean_check_starting` into your `ynh_clean_setup` at the beginning of the script. Otherwise, tail will not stop in case of failure of the script. The script will then hang forever. +# | arg: -l, --line_match= - Line to match - The line to find in the log to attest the service have finished to boot. If not defined it don't wait until the service is completely started. # | arg: -p, --log_path= - Log file - Path to the log file. Default : /var/log/$app/$app.log # | arg: -t, --timeout= - Timeout - The maximum time to wait before ending the watching. Default : 300 seconds. # | arg: -e, --length= - Length of the error log : Default : 20 @@ -117,6 +117,7 @@ ynh_systemd_action() { then ynh_exec_err tail --lines=$length "$log_path" fi + ynh_clean_check_starting return 1 fi @@ -161,9 +162,8 @@ ynh_systemd_action() { } # Clean temporary process and file used by ynh_check_starting -# (usually used in ynh_clean_setup scripts) # -# usage: ynh_clean_check_starting +# [internal] # # Requires YunoHost version 3.5.0 or higher. ynh_clean_check_starting () { @@ -174,7 +174,7 @@ ynh_clean_check_starting () { fi if [ -n "$templog" ] then - ynh_secure_remove "$templog" 2>&1 + ynh_secure_remove --file="$templog" 2>&1 fi } From 1d3380415eca14ea9291d3b29fbf326111077e68 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Thu, 25 Feb 2021 11:17:46 +0100 Subject: [PATCH 2168/3170] add missing getops --- data/helpers.d/apt | 6 +++--- data/helpers.d/fail2ban | 8 ++++---- data/helpers.d/network | 2 +- data/helpers.d/php | 6 +++--- data/helpers.d/postgresql | 4 ++-- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index 6abaf20a2..bfdeffe7b 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -459,11 +459,11 @@ ynh_remove_extra_repo () { ynh_handle_getopts_args "$@" name="${name:-$app}" - ynh_secure_remove "/etc/apt/sources.list.d/$name.list" + ynh_secure_remove --file="/etc/apt/sources.list.d/$name.list" # Sury pinning is managed by the regenconf in the core... [[ "$name" == "extra_php_version" ]] || ynh_secure_remove "/etc/apt/preferences.d/$name" - ynh_secure_remove "/etc/apt/trusted.gpg.d/$name.gpg" > /dev/null - ynh_secure_remove "/etc/apt/trusted.gpg.d/$name.asc" > /dev/null + ynh_secure_remove --file="/etc/apt/trusted.gpg.d/$name.gpg" > /dev/null + ynh_secure_remove --file="/etc/apt/trusted.gpg.d/$name.asc" > /dev/null # Update the list of package to exclude the old repo ynh_package_update diff --git a/data/helpers.d/fail2ban b/data/helpers.d/fail2ban index c41226e14..c9322d067 100644 --- a/data/helpers.d/fail2ban +++ b/data/helpers.d/fail2ban @@ -77,8 +77,8 @@ ynh_add_fail2ban_config () { if [ $use_template -ne 1 ] then # Usage 1, no template. Build a config file from scratch. - test -n "$logpath" || ynh_die "ynh_add_fail2ban_config expects a logfile path as first argument and received nothing." - test -n "$failregex" || ynh_die "ynh_add_fail2ban_config expects a failure regex as second argument and received nothing." + test -n "$logpath" || ynh_die --message="ynh_add_fail2ban_config expects a logfile path as first argument and received nothing." + test -n "$failregex" || ynh_die --message="ynh_add_fail2ban_config expects a failure regex as second argument and received nothing." echo " [__APP__] @@ -117,7 +117,7 @@ ignoreregex = # # Requires YunoHost version 3.5.0 or higher. ynh_remove_fail2ban_config () { - ynh_secure_remove "/etc/fail2ban/jail.d/$app.conf" - ynh_secure_remove "/etc/fail2ban/filter.d/$app.conf" + ynh_secure_remove --file="/etc/fail2ban/jail.d/$app.conf" + ynh_secure_remove --file="/etc/fail2ban/filter.d/$app.conf" ynh_systemd_action --service_name=fail2ban --action=reload } diff --git a/data/helpers.d/network b/data/helpers.d/network index 4f108422b..0760909c6 100644 --- a/data/helpers.d/network +++ b/data/helpers.d/network @@ -27,7 +27,7 @@ ynh_find_port () { # Test if a port is available # -# example: ynh_port_available --port=1234 || ynh_die "Port 1234 is needs to be available for this app" +# example: ynh_port_available --port=1234 || ynh_die --message="Port 1234 is needs to be available for this app" # # usage: ynh_find_port --port=XYZ # | arg: -p, --port= - port to check diff --git a/data/helpers.d/php b/data/helpers.d/php index 683f252be..5c050cc88 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -337,7 +337,7 @@ ynh_install_php () { if [ "$phpversion" == "$YNH_DEFAULT_PHP_VERSION" ] then - ynh_die "Do not use ynh_install_php to install php$YNH_DEFAULT_PHP_VERSION" + ynh_die --message="Do not use ynh_install_php to install php$YNH_DEFAULT_PHP_VERSION" fi # Create the file if doesn't exist already @@ -611,9 +611,9 @@ ynh_install_composer () { curl -sS https://getcomposer.org/installer \ | COMPOSER_HOME="$workdir/.composer" \ php${phpversion} -- --quiet --install-dir="$workdir" --version=$composerversion \ - || ynh_die "Unable to install Composer." + || ynh_die --message="Unable to install Composer." # install dependencies ynh_composer_exec --phpversion="${phpversion}" --workdir="$workdir" --commands="install --no-dev $install_args" \ - || ynh_die "Unable to install core dependencies with Composer." + || ynh_die --message="Unable to install core dependencies with Composer." } diff --git a/data/helpers.d/postgresql b/data/helpers.d/postgresql index 11b9c0fed..f2f427842 100644 --- a/data/helpers.d/postgresql +++ b/data/helpers.d/postgresql @@ -295,10 +295,10 @@ ynh_psql_remove_db() { ynh_psql_test_if_first_run() { # Make sure postgresql is indeed installed - dpkg --list | grep -q "ii postgresql-$PSQL_VERSION" || ynh_die "postgresql-$PSQL_VERSION is not installed !?" + dpkg --list | grep -q "ii postgresql-$PSQL_VERSION" || ynh_die --message="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" + [ -e "/etc/postgresql/$PSQL_VERSION" ] || ynh_die --message="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 From 47420c623252c98f2efc65498abde32ba90a3dec Mon Sep 17 00:00:00 2001 From: Kay0u Date: Thu, 25 Feb 2021 12:40:49 +0100 Subject: [PATCH 2169/3170] fix multimedia helper --- data/helpers.d/multimedia | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/multimedia b/data/helpers.d/multimedia index 5517917b5..f7c9d5974 100644 --- a/data/helpers.d/multimedia +++ b/data/helpers.d/multimedia @@ -1,3 +1,5 @@ +#!/bin/bash + readonly MEDIA_GROUP=multimedia readonly MEDIA_DIRECTORY=/home/yunohost.multimedia @@ -53,10 +55,13 @@ ynh_multimedia_build_main_dir() { # | arg: -d, --dest_dir= - Destination directory - The name and the place of the symbolic link, relative to "/home/yunohost.multimedia" ynh_multimedia_addfolder() { - # Declare an array to define the options of this helper. - declare -Ar args_array=( [s]=source_dir= [d]=dest_dir= ) + # Declare an array to define the options of this helper. + local legacy_args=sd + local -A args_array=( [s]=source_dir= [d]=dest_dir= ) local source_dir local dest_dir + # Manage arguments with getopts + ynh_handle_getopts_args "$@" # Ajout d'un lien symbolique vers le dossier à partager ln -sfn "$source_dir" "$MEDIA_DIRECTORY/$dest_dir" @@ -77,6 +82,7 @@ ynh_multimedia_addfolder() { # | arg: -u, --user_name= - The name of the user which gain this access. ynh_multimedia_addaccess () { # Declare an array to define the options of this helper. + local legacy_args=u declare -Ar args_array=( [u]=user_name=) local user_name # Manage arguments with getopts From 33f291be962333b2a455ac6e4a7162cf5aee8a8b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 25 Feb 2021 16:36:28 +0100 Subject: [PATCH 2170/3170] Fix ynh_replace_vars again, mystical bash is mystic... --- data/helpers.d/utils | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index bd9e6a87b..96609c7dc 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -389,8 +389,11 @@ ynh_replace_vars () { for one_var in "${uniques_vars[@]}" do # Validate that one_var is indeed defined - # Explanation for the weird '+x' syntax: https://stackoverflow.com/a/13864829 - test -n "${one_var+x}" || ynh_die --message="Variable \$$one_var wasn't initialized when trying to replace __${one_var^^}__ in $file" + # -v checks if the variable is defined, for example: + # -v FOO tests if $FOO is defined + # -v $FOO tests if ${!FOO} is defined + # More info: https://stackoverflow.com/questions/3601515/how-to-check-if-a-variable-is-set-in-bash/17538964#comment96392525_17538964 + [[ -v "${one_var:-}" ]] || ynh_die --message="Variable \$$one_var wasn't initialized when trying to replace __${one_var^^}__ in $file" # Escape delimiter in match/replace string match_string="__${one_var^^}__" From 0dd033745089316ad6d03be1985942ec76bb81bf Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 25 Feb 2021 16:44:55 +0100 Subject: [PATCH 2171/3170] Don't redact empty string... --- src/yunohost/log.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/yunohost/log.py b/src/yunohost/log.py index 24ecc6713..763967b38 100644 --- a/src/yunohost/log.py +++ b/src/yunohost/log.py @@ -399,7 +399,11 @@ class RedactingFormatter(Formatter): msg = super(RedactingFormatter, self).format(record) self.identify_data_to_redact(msg) for data in self.data_to_redact: - msg = msg.replace(data, "**********") + # we check that data is not empty string, + # otherwise this may lead to super epic stuff + # (try to run "foo".replace("", "bar")) + if data: + msg = msg.replace(data, "**********") return msg def identify_data_to_redact(self, record): From 675c4d0eeaea772703b951c78e12bbefae855913 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 25 Feb 2021 16:57:57 +0100 Subject: [PATCH 2172/3170] Fix permission helper doc format --- data/helpers.d/permission | 47 +++++++++++++++++---------------------- 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/data/helpers.d/permission b/data/helpers.d/permission index f4e200019..ba2b22a53 100644 --- a/data/helpers.d/permission +++ b/data/helpers.d/permission @@ -25,19 +25,14 @@ # usage: ynh_permission_create --permission="permission" [--url="url"] [--additional_urls="second-url" [ "third-url" ]] [--auth_header=true|false] # [--allowed=group1 [ group2 ]] [--label="label"] [--show_tile=true|false] # [--protected=true|false] -# | arg: -p, permission= - the name for the permission (by default a permission named "main" already exist) -# | arg: -u, url= - (optional) URL for which access will be allowed/forbidden. -# | Not that if 'show_tile' is enabled, this URL will be the URL of the tile. -# | arg: -A, additional_urls= - (optional) List of additional URL for which access will be allowed/forbidden -# | arg: -h, auth_header= - (optional) Define for the URL of this permission, if SSOwat pass the authentication header to the application. Default is true -# | arg: -a, allowed= - (optional) A list of group/user to allow for the permission -# | arg: -l, label= - (optional) Define a name for the permission. This label will be shown on the SSO and in the admin. -# | Default is "APP_LABEL (permission name)". -# | arg: -t, show_tile= - (optional) Define if a tile will be shown in the SSO. If yes the name of the tile will be the 'label' parameter. -# | Default is false (for the permission different than 'main'). -# | arg: -P, protected= - (optional) Define if this permission is protected. If it is protected the administrator -# | won't be able to add or remove the visitors group of this permission. -# | By default it's 'false' +# | arg: -p, --permission= - the name for the permission (by default a permission named "main" already exist) +# | arg: -u, --url= - (optional) URL for which access will be allowed/forbidden. Note that if 'show_tile' is enabled, this URL will be the URL of the tile. +# | arg: -A, --additional_urls= - (optional) List of additional URL for which access will be allowed/forbidden +# | arg: -h, --auth_header= - (optional) Define for the URL of this permission, if SSOwat pass the authentication header to the application. Default is true +# | arg: -a, --allowed= - (optional) A list of group/user to allow for the permission +# | arg: -l, --label= - (optional) Define a name for the permission. This label will be shown on the SSO and in the admin. Default is "APP_LABEL (permission name)". +# | arg: -t, --show_tile= - (optional) Define if a tile will be shown in the SSO. If yes the name of the tile will be the 'label' parameter. Defaults to false for the permission different than 'main'. +# | arg: -P, --protected= - (optional) Define if this permission is protected. If it is protected the administrator won't be able to add or remove the visitors group of this permission. Defaults to 'false'. # # If provided, 'url' or 'additional_urls' is assumed to be relative to the app domain/path if they # start with '/'. For example: @@ -193,13 +188,12 @@ ynh_permission_exists() { # # usage: ynh_permission_url --permission "permission" [--url="url"] [--add_url="new-url" [ "other-new-url" ]] [--remove_url="old-url" [ "other-old-url" ]] # [--auth_header=true|false] [--clear_urls] -# | arg: -p, permission= - the name for the permission (by default a permission named "main" is removed automatically when the app is removed) -# | arg: -u, url= - (optional) URL for which access will be allowed/forbidden. -# | Note that if you want to remove url you can pass an empty sting as arguments (""). -# | arg: -a, add_url= - (optional) List of additional url to add for which access will be allowed/forbidden. -# | arg: -r, remove_url= - (optional) List of additional url to remove for which access will be allowed/forbidden -# | arg: -h, auth_header= - (optional) Define for the URL of this permission, if SSOwat pass the authentication header to the application -# | arg: -c, clear_urls - (optional) Clean all urls (url and additional_urls) +# | arg: -p, --permission= - the name for the permission (by default a permission named "main" is removed automatically when the app is removed) +# | arg: -u, --url= - (optional) URL for which access will be allowed/forbidden. Note that if you want to remove url you can pass an empty sting as arguments (""). +# | arg: -a, --add_url= - (optional) List of additional url to add for which access will be allowed/forbidden. +# | arg: -r, --remove_url= - (optional) List of additional url to remove for which access will be allowed/forbidden +# | arg: -h, --auth_header= - (optional) Define for the URL of this permission, if SSOwat pass the authentication header to the application +# | arg: -c, --clear_urls - (optional) Clean all urls (url and additional_urls) # # Requires YunoHost version 3.7.0 or higher. ynh_permission_url() { @@ -269,13 +263,12 @@ ynh_permission_url() { # # usage: ynh_permission_update --permission "permission" [--add="group" ["group" ...]] [--remove="group" ["group" ...]] # [--label="label"] [--show_tile=true|false] [--protected=true|false] -# | arg: -p, permission= - the name for the permission (by default a permission named "main" already exist) -# | arg: -a, add= - the list of group or users to enable add to the permission -# | arg: -r, remove= - the list of group or users to remove from the permission -# | arg: -l, label= - (optional) Define a name for the permission. This label will be shown on the SSO and in the admin. -# | arg: -t, show_tile= - (optional) Define if a tile will be shown in the SSO -# | arg: -P, protected= - (optional) Define if this permission is protected. If it is protected the administrator -# | won't be able to add or remove the visitors group of this permission. +# | arg: -p, --permission= - the name for the permission (by default a permission named "main" already exist) +# | arg: -a, --add= - the list of group or users to enable add to the permission +# | arg: -r, --remove= - the list of group or users to remove from the permission +# | arg: -l, --label= - (optional) Define a name for the permission. This label will be shown on the SSO and in the admin. +# | arg: -t, --show_tile= - (optional) Define if a tile will be shown in the SSO +# | arg: -P, --protected= - (optional) Define if this permission is protected. If it is protected the administrator won't be able to add or remove the visitors group of this permission. # # Requires YunoHost version 3.7.0 or higher. ynh_permission_update() { From 7efc6dcd07d08b8c3923165d1378d8d7490c1754 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Jan 2021 02:56:09 +0100 Subject: [PATCH 2173/3170] Handle the dyndns cron from the regenconf --- data/hooks/conf_regen/01-yunohost | 11 +++++++++++ src/yunohost/dyndns.py | 26 +++++++++++++++++++------- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/data/hooks/conf_regen/01-yunohost b/data/hooks/conf_regen/01-yunohost index 9da2d91ca..8b7a7c6fc 100755 --- a/data/hooks/conf_regen/01-yunohost +++ b/data/hooks/conf_regen/01-yunohost @@ -86,6 +86,17 @@ SHELL=/bin/bash 0 7,19 * * * root : YunoHost Automatic Diagnosis; sleep \$((RANDOM\\%1200)); yunohost diagnosis run --email > /dev/null 2>/dev/null || echo "Running the automatic diagnosis failed miserably" EOF + # If we subscribed to a dyndns domain, add the corresponding cron + # - delay between 0 and 60 secs to spread the check over a 1 min window + # - do not run the command if some process already has the lock, to avoid queuing hundreds of commands... + if ls -l /etc/yunohost/dyndns/K*.private 2>/dev/null + then + cat > $pending_dir/etc/cron.d/yunohost-dyndns << EOF +SHELL=/bin/bash +*/10 * * * * root : YunoHost DynDNS update; sleep \$((RANDOM\\%60)); test -e /var/run/moulinette_yunohost.lock || yunohost dyndns update >> /dev/null +EOF + fi + # legacy stuff to avoid yunohost reporting etckeeper as manually modified # (this make sure that the hash is null / file is flagged as to-delete) mkdir -p $pending_dir/etc/etckeeper diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index d94748881..ce5ebfc5e 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -40,6 +40,7 @@ from yunohost.utils.error import YunohostError from yunohost.domain import _get_maindomain, _build_dns_conf from yunohost.utils.network import get_public_ip, dig from yunohost.log import is_unit_operation +from yunohost.regenconf import regen_conf logger = getActionLogger("yunohost.dyndns") @@ -121,10 +122,9 @@ def dyndns_subscribe( subscribe_host -- Dynette HTTP API to subscribe to """ - if len(glob.glob("/etc/yunohost/dyndns/*.key")) != 0 or os.path.exists( - "/etc/cron.d/yunohost-dyndns" - ): - raise YunohostError("domain_dyndns_already_subscribed") + + if _guess_current_dyndns_domain(dyn_host) != (None, None): + raise YunohostError('domain_dyndns_already_subscribed') if domain is None: domain = _get_maindomain() @@ -186,9 +186,17 @@ def dyndns_subscribe( error = 'Server error, code: %s. (Message: "%s")' % (r.status_code, r.text) raise YunohostError("dyndns_registration_failed", error=error) - logger.success(m18n.n("dyndns_registered")) + # Yunohost regen conf will add the dyndns cron job if a private key exists + # in /etc/yunohost/dyndns + regen_conf("yunohost") - dyndns_installcron() + # Add some dyndns update in 2 and 4 minutes from now such that user should + # not have to wait 10ish minutes for the conf to propagate + cmd = "at -M now + {t} >/dev/null 2>&1 <<< \"/bin/bash -c 'yunohost dyndns update'\"" + subprocess.check_call(cmd.format(t="2 min"), shell=True) + subprocess.check_call(cmd.format(t="4 min"), shell=True) + + logger.success(m18n.n('dyndns_registered')) @is_unit_operation() @@ -220,6 +228,10 @@ def dyndns_update( # If domain is not given, try to guess it from keys available... if domain is None: (domain, key) = _guess_current_dyndns_domain(dyn_host) + + if domain is None: + raise YunohostError('dyndns_no_domain_registered') + # If key is not given, pick the first file we find with the domain given else: if key is None: @@ -414,4 +426,4 @@ def _guess_current_dyndns_domain(dyn_host): else: return (_domain, path) - raise YunohostError("dyndns_no_domain_registered") + return (None, None) From 2752a8e485dfcffed761f93309829ba31cd74188 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Jan 2021 02:56:36 +0100 Subject: [PATCH 2174/3170] Deprecate yunohost dyndns installcron/removecron --- data/actionsmap/yunohost.yml | 6 ++---- locales/en.json | 3 --- src/yunohost/dyndns.py | 22 ++-------------------- 3 files changed, 4 insertions(+), 27 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 33b8b5cfe..290952aa3 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1363,13 +1363,11 @@ dyndns: ### dyndns_installcron() installcron: - action_help: Install IP update cron - api: POST /dyndns/cron + deprecated: true ### dyndns_removecron() removecron: - action_help: Remove IP update cron - api: DELETE /dyndns/cron + deprecated: true ############################# diff --git a/locales/en.json b/locales/en.json index 7e3de2341..199c21b66 100644 --- a/locales/en.json +++ b/locales/en.json @@ -291,9 +291,6 @@ "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}.", - "dyndns_cron_installed": "DynDNS cron job created", - "dyndns_cron_remove_failed": "Could not remove the DynDNS cron job because: {error}", - "dyndns_cron_removed": "DynDNS cron job removed", "dyndns_ip_update_failed": "Could not update IP address to DynDNS", "dyndns_ip_updated": "Updated your IP on DynDNS", "dyndns_key_generating": "Generating DNS key... It may take a while.", diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index ce5ebfc5e..6545d33c7 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -373,29 +373,11 @@ def dyndns_update( def dyndns_installcron(): - """ - Install IP update cron - - - """ - with open("/etc/cron.d/yunohost-dyndns", "w+") as f: - f.write("*/2 * * * * root yunohost dyndns update >> /dev/null\n") - - logger.success(m18n.n("dyndns_cron_installed")) + logger.warning("This command is deprecated. The dyndns cron job should automatically be added/removed by the regenconf depending if there's a private key in /etc/yunohost/dyndns. You can run the regenconf yourself with 'yunohost tools regen-conf yunohost'.") def dyndns_removecron(): - """ - Remove IP update cron - - - """ - try: - os.remove("/etc/cron.d/yunohost-dyndns") - except Exception as e: - raise YunohostError("dyndns_cron_remove_failed", error=e) - - logger.success(m18n.n("dyndns_cron_removed")) + logger.warning("This command is deprecated. The dyndns cron job should automatically be added/removed by the regenconf depending if there's a private key in /etc/yunohost/dyndns. You can run the regenconf yourself with 'yunohost tools regen-conf yunohost'.") def _guess_current_dyndns_domain(dyn_host): From a8e11c19db6d8b21f5d6d90512cc4d5471ce3bd6 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Jan 2021 03:01:05 +0100 Subject: [PATCH 2175/3170] Delete dyndns key during domain removal if any --- src/yunohost/domain.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index cc9980549..95d9ee7b0 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -245,6 +245,9 @@ def domain_remove(operation_logger, domain, remove_apps=False, force=False): os.system("rm -rf /etc/yunohost/certs/%s" % domain) + # Delete dyndns keys for this domain (if any) + os.system('rm -rf /etc/yunohost/dyndns/K%s.+*' % domain) + # Sometime we have weird issues with the regenconf where some files # appears as manually modified even though they weren't touched ... # There are a few ideas why this happens (like backup/restore nginx From cc6cc1860add2c44d9af2a47329acef2c3a48dd7 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Jan 2021 03:02:04 +0100 Subject: [PATCH 2176/3170] Do not backup damn cron jobs ... they will automatically be regenerated by the regenconf --- data/hooks/backup/42-conf_ynh_dyndns | 1 - data/hooks/restore/42-conf_ynh_dyndns | 1 - 2 files changed, 2 deletions(-) diff --git a/data/hooks/backup/42-conf_ynh_dyndns b/data/hooks/backup/42-conf_ynh_dyndns index 3dbcc2780..6343f9086 100644 --- a/data/hooks/backup/42-conf_ynh_dyndns +++ b/data/hooks/backup/42-conf_ynh_dyndns @@ -8,4 +8,3 @@ cd "$YNH_CWD" # Backup the configuration ynh_exec_warn_less ynh_backup --src_path="/etc/yunohost/dyndns" --not_mandatory -ynh_exec_warn_less ynh_backup --src_path="/etc/cron.d/yunohost-dyndns" --not_mandatory diff --git a/data/hooks/restore/42-conf_ynh_dyndns b/data/hooks/restore/42-conf_ynh_dyndns index d16d7a67c..8ed4941ef 100644 --- a/data/hooks/restore/42-conf_ynh_dyndns +++ b/data/hooks/restore/42-conf_ynh_dyndns @@ -7,4 +7,3 @@ cd "$YNH_CWD" # Restore file if exists ynh_restore_file --origin_path="/etc/yunohost/dyndns" --not_mandatory -ynh_restore_file --origin_path="/etc/cron.d/yunohost-dyndns" --not_mandatory From 071732dd7f78f96d4d4f8f2973082f308f841335 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Jan 2021 03:04:56 +0100 Subject: [PATCH 2177/3170] Improve check that a dyndns domain already exists --- src/yunohost/domain.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 95d9ee7b0..1198ef473 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -119,11 +119,11 @@ def domain_add(operation_logger, domain, dyndns=False): # DynDNS domain if dyndns: - # Do not allow to subscribe to multiple dyndns domains... - if os.path.exists("/etc/cron.d/yunohost-dyndns"): - raise YunohostError("domain_dyndns_already_subscribed") + from yunohost.dyndns import dyndns_subscribe, _dyndns_provides, _guess_current_dyndns_domain - from yunohost.dyndns import dyndns_subscribe, _dyndns_provides + # Do not allow to subscribe to multiple dyndns domains... + if _guess_current_dyndns_domain("dyndns.yunohost.org") != (None, None): + raise YunohostError('domain_dyndns_already_subscribed') # Check that this domain can effectively be provided by # dyndns.yunohost.org. (i.e. is it a nohost.me / noho.st) From 64a1b4cad2ad8b871db88f820e30ddfe59e21edd Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 26 Feb 2021 00:20:39 +0100 Subject: [PATCH 2178/3170] Misc fixes after testing --- debian/control | 2 +- src/yunohost/dyndns.py | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/debian/control b/debian/control index 7275cb7b1..ef5061fe7 100644 --- a/debian/control +++ b/debian/control @@ -27,7 +27,7 @@ Depends: ${python3:Depends}, ${misc:Depends} , redis-server , metronome (>=3.14.0) , acl - , git, curl, wget, cron, unzip, jq, bc + , git, curl, wget, cron, unzip, jq, bc, at , lsb-release, haveged, fake-hwclock, equivs, lsof, whois Recommends: yunohost-admin , ntp, inetutils-ping | iputils-ping diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index 6545d33c7..a921cfb5c 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -123,7 +123,7 @@ def dyndns_subscribe( """ - if _guess_current_dyndns_domain(dyn_host) != (None, None): + if _guess_current_dyndns_domain(subscribe_host) != (None, None): raise YunohostError('domain_dyndns_already_subscribed') if domain is None: @@ -169,7 +169,7 @@ def dyndns_subscribe( try: r = requests.post( "https://%s/key/%s?key_algo=hmac-sha512" - % (subscribe_host, base64.b64encode(key)), + % (subscribe_host, base64.b64encode(key.encode()).decode()), data={"subdomain": domain}, timeout=30, ) @@ -188,13 +188,14 @@ def dyndns_subscribe( # Yunohost regen conf will add the dyndns cron job if a private key exists # in /etc/yunohost/dyndns - regen_conf("yunohost") + regen_conf(["yunohost"]) # Add some dyndns update in 2 and 4 minutes from now such that user should # not have to wait 10ish minutes for the conf to propagate cmd = "at -M now + {t} >/dev/null 2>&1 <<< \"/bin/bash -c 'yunohost dyndns update'\"" - subprocess.check_call(cmd.format(t="2 min"), shell=True) - subprocess.check_call(cmd.format(t="4 min"), shell=True) + # For some reason subprocess doesn't like the redirections so we have to use bash -c explicity... + subprocess.check_call(["bash", "-c", cmd.format(t="2 min")]) + subprocess.check_call(["bash", "-c", cmd.format(t="4 min")]) logger.success(m18n.n('dyndns_registered')) From c6d14219cc5b5cc47ca9e367bdec085d71404967 Mon Sep 17 00:00:00 2001 From: yalh76 Date: Sat, 27 Feb 2021 20:39:55 +0100 Subject: [PATCH 2179/3170] Force destination to be replaced --- 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 96609c7dc..d4797c8a0 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -316,7 +316,7 @@ ynh_add_config () { ynh_backup_if_checksum_is_different --file="$destination" - cp "$template_path" "$destination" + cp -f "$template_path" "$destination" chown root: "$destination" ynh_replace_vars --file="$destination" From fcea5a6af0d0d29d8d57c030c7c0836b8fdbb462 Mon Sep 17 00:00:00 2001 From: yalh76 Date: Sun, 28 Feb 2021 13:38:10 +0100 Subject: [PATCH 2180/3170] Make grep lazy --- 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 d4797c8a0..af8464279 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -382,7 +382,7 @@ ynh_replace_vars () { # Replace others variables # List other unique (__ __) variables in $file - local uniques_vars=( $(grep -o '__[A-Z0-9_]*__' $file | sort --unique | sed "s@__\([^.]*\)__@\L\1@g" )) + local uniques_vars=( $(grep -oP '__[A-Z0-9_]+?__' $file | sort --unique | sed "s@__\([^.]*\)__@\L\1@g" )) # Do the replacement local delimit=@ From 4131ddb0706ccd6121e0d542cc2b6d84c665f0ab Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 28 Feb 2021 17:00:48 +0100 Subject: [PATCH 2181/3170] Fix cases where we want to test if translation exists for a key --- src/yunohost/diagnosis.py | 3 +-- src/yunohost/service.py | 11 ++++------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index 93ece21fc..5666afb07 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -449,9 +449,8 @@ class Diagnoser(): @staticmethod def get_description(id_): key = "diagnosis_description_" + id_ - descr = m18n.n(key) # If no description available, fallback to id - return descr if descr != key else id_ + return m18n.n(key) if m18n.key_exists(key) else id_ @staticmethod def i18n(report, force_remove_html_tags=False): diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 347932add..211d7bf56 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -352,14 +352,11 @@ def _get_and_format_service_status(service, infos): # If no description was there, try to get it from the .json locales if not description: - translation_key = "service_description_%s" % service - description = m18n.n(translation_key) - # If descrption is still equal to the 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: + translation_key = "service_description_%s" % service + if m18n.key_exists(translation_key): + description = m18n.key_exists(translation_key) + else: description = str(raw_status.get("Description", "")) output = { From c86d4327832dc85a559a71d28e50405875bbed0a Mon Sep 17 00:00:00 2001 From: yalh76 Date: Sun, 28 Feb 2021 18:56:30 +0100 Subject: [PATCH 2182/3170] Fixing ___APP__ --- 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 af8464279..487ec41db 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -382,7 +382,7 @@ ynh_replace_vars () { # Replace others variables # List other unique (__ __) variables in $file - local uniques_vars=( $(grep -oP '__[A-Z0-9_]+?__' $file | sort --unique | sed "s@__\([^.]*\)__@\L\1@g" )) + local uniques_vars=( $(grep -oP '__[A-Z0-9]+?[A-Z0-9_]*?[A-Z0-9]*?__' $file | sort --unique | sed "s@__\([^.]*\)__@\L\1@g" )) # Do the replacement local delimit=@ From 0884a0c162af28ac71f5d1cdf6878349b0f6ffc1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 1 Mar 2021 18:06:43 +0100 Subject: [PATCH 2183/3170] Further simplify logging configuration by using the 'cli' handler when running yunohost-api in debug mode instead of creating a 'console' handler which is pretty much the same --- src/yunohost/__init__.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/yunohost/__init__.py b/src/yunohost/__init__.py index 04b2115b2..04caefc7a 100644 --- a/src/yunohost/__init__.py +++ b/src/yunohost/__init__.py @@ -154,14 +154,8 @@ def init_logging(interface="cli", debug=False, quiet=False, logdir="/var/log/yun # This is for when launching yunohost-api in debug mode, we want to display stuff in the console if debug: - logging_configuration["handlers"]["console"] = { - 'class': 'logging.StreamHandler', - 'formatter': 'console', - 'stream': 'ext://sys.stdout', - 'filters': ['action'], - }, - logging_configuration["loggers"]["yunohost"]["handlers"].append("console") - logging_configuration["loggers"]["moulinette"]["handlers"].append("console") - logging_configuration["root"]["handlers"].append("console") + logging_configuration["loggers"]["yunohost"]["handlers"].append("cli") + logging_configuration["loggers"]["moulinette"]["handlers"].append("cli") + logging_configuration["root"]["handlers"].append("cli") configure_logging(logging_configuration) From d763247df445a0e485746f49453a024e5285e660 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 1 Mar 2021 19:11:41 +0100 Subject: [PATCH 2184/3170] No need for mysql root password (#912) * Get rid of /etc/yunohost/mysql * Get rid of restore hook for mysql password * Tab -> spaces * declare->local lost while merging conflicts etc * Gotta keep that var --- data/helpers.d/mysql | 26 +++++++++------------- data/hooks/conf_regen/34-mysql | 33 +++++++++++++++++++--------- data/hooks/restore/11-conf_ynh_mysql | 5 ----- src/yunohost/tests/test_apps.py | 15 ++++--------- 4 files changed, 37 insertions(+), 42 deletions(-) delete mode 100644 data/hooks/restore/11-conf_ynh_mysql diff --git a/data/helpers.d/mysql b/data/helpers.d/mysql index 05f75e0a2..6808441b7 100644 --- a/data/helpers.d/mysql +++ b/data/helpers.d/mysql @@ -1,7 +1,5 @@ #!/bin/bash -MYSQL_ROOT_PWD_FILE=/etc/yunohost/mysql - # Open a connection as a user # # example: ynh_mysql_connect_as --user="user" --password="pass" <<< "UPDATE ...;" @@ -49,8 +47,7 @@ ynh_mysql_execute_as_root() { database="--database=$database" fi - ynh_mysql_connect_as --user="root" --password="$(cat $MYSQL_ROOT_PWD_FILE)" \ - $database <<< "$sql" + mysql -B "$database" <<< "$sql" } # Execute a command from a file as root user @@ -75,9 +72,7 @@ ynh_mysql_execute_file_as_root() { database="--database=$database" fi - - ynh_mysql_connect_as --user="root" --password="$(cat $MYSQL_ROOT_PWD_FILE)" \ - $database < "$file" + mysql -B "$database" < "$file" } # Create a database and grant optionnaly privilegies to a user @@ -140,7 +135,7 @@ ynh_mysql_dump_db() { # Manage arguments with getopts ynh_handle_getopts_args "$@" - mysqldump --user="root" --password="$(cat $MYSQL_ROOT_PWD_FILE)" --single-transaction --skip-dump-date "$database" + mysqldump --single-transaction --skip-dump-date "$database" } # Create a user @@ -214,12 +209,13 @@ ynh_mysql_setup_db () { # Manage arguments with getopts ynh_handle_getopts_args "$@" - local new_db_pwd=$(ynh_string_random) # Generate a random password + # Generate a random password + local new_db_pwd=$(ynh_string_random) # If $db_pwd is not provided, use new_db_pwd instead for db_pwd db_pwd="${db_pwd:-$new_db_pwd}" - ynh_mysql_create_db "$db_name" "$db_user" "$db_pwd" # Create the database - ynh_app_setting_set --app=$app --key=mysqlpwd --value=$db_pwd # Store the password in the app's config + ynh_mysql_create_db "$db_name" "$db_user" "$db_pwd" + ynh_app_setting_set --app=$app --key=mysqlpwd --value=$db_pwd } # Remove a database if it exists, and the associated user @@ -232,16 +228,14 @@ ynh_mysql_setup_db () { ynh_mysql_remove_db () { # Declare an array to define the options of this helper. local legacy_args=un - local -A args_array=( [u]=db_user= [n]=db_name= ) + local -Ar args_array=( [u]=db_user= [n]=db_name= ) local db_user local db_name # Manage arguments with getopts ynh_handle_getopts_args "$@" - local mysql_root_password=$(cat $MYSQL_ROOT_PWD_FILE) - if mysqlshow --user=root --password=$mysql_root_password | grep --quiet "^| $db_name" - then # Check if the database exists - ynh_mysql_drop_db $db_name # Remove the database + if mysqlshow | grep -q "^| $db_name "; then + ynh_mysql_drop_db $db_name else ynh_print_warn --message="Database $db_name not found" fi diff --git a/data/hooks/conf_regen/34-mysql b/data/hooks/conf_regen/34-mysql index d9374bbf5..6c9694796 100755 --- a/data/hooks/conf_regen/34-mysql +++ b/data/hooks/conf_regen/34-mysql @@ -1,7 +1,6 @@ #!/bin/bash set -e -MYSQL_PKG="$(dpkg --list | sed -ne 's/^ii \(mariadb-server-[[:digit:].]\+\) .*$/\1/p')" . /usr/share/yunohost/helpers do_pre_regen() { @@ -20,6 +19,7 @@ do_post_regen() { # dpkg-reconfigure will initialize mysql (if it ain't already) # It enabled auth_socket for root, so no need to define any root password... # c.f. : cat /var/lib/dpkg/info/mariadb-server-10.3.postinst | grep install_db -C3 + MYSQL_PKG="$(dpkg --list | sed -ne 's/^ii \(mariadb-server-[[:digit:].]\+\) .*$/\1/p')" dpkg-reconfigure -freadline -u "$MYSQL_PKG" 2>&1 systemctl -q is-active mariadb.service \ @@ -27,17 +27,30 @@ do_post_regen() { sleep 5 - echo "" | mysql && echo "Can't connect to mysql using unix_socket auth ... something went wrong during initial configuration of mysql !?" + echo "" | mysql && echo "Can't connect to mysql using unix_socket auth ... something went wrong during initial configuration of mysql !?" >&2 fi - if [ ! -e /etc/yunohost/mysql ] - then - # Dummy password that's not actually used nor meaningful ... - # (because mysql is supposed to be configured to use unix_socket on new setups) - # but keeping it for legacy - # until we merge https://github.com/YunoHost/yunohost/pull/912 ... - ynh_string_random 10 > /etc/yunohost/mysql - chmod 400 /etc/yunohost/mysql + # Legacy code to get rid of /etc/yunohost/mysql ... + # Nowadays, we can simply run mysql while being run as root of unix_socket/auth_socket is enabled... + if [ -f /etc/yunohost/mysql ]; then + + # This is a trick to check if we're able to use mysql without password + # Expect instances installed in stretch to already have unix_socket + #configured, but not old instances from the jessie/wheezy era + if ! echo "" | mysql + then + password="$(cat /etc/yunohost/mysql)" + # Enable plugin unix_socket for root on localhost + mysql -u root -p"$password" <<< "GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' IDENTIFIED WITH unix_socket WITH GRANT OPTION;" + fi + + # If now we're able to login without password, drop the mysql password + if echo "" | mysql + then + rm /etc/yunohost/mysql + else + echo "Can't connect to mysql using unix_socket auth ... something went wrong while trying to get rid of mysql password !?" >&2 + fi fi # mysql is supposed to be an alias to mariadb... but in some weird case is not diff --git a/data/hooks/restore/11-conf_ynh_mysql b/data/hooks/restore/11-conf_ynh_mysql deleted file mode 100644 index 11353425a..000000000 --- a/data/hooks/restore/11-conf_ynh_mysql +++ /dev/null @@ -1,5 +0,0 @@ -# We don't backup/restore mysql password anymore -# c.f. https://github.com/YunoHost/yunohost/pull/912 - -# This is a dummy empty file as a workaround for -# https://github.com/YunoHost/issues/issues/1553 until it is fixed diff --git a/src/yunohost/tests/test_apps.py b/src/yunohost/tests/test_apps.py index ae8a4829b..b9e9e7530 100644 --- a/src/yunohost/tests/test_apps.py +++ b/src/yunohost/tests/test_apps.py @@ -55,18 +55,11 @@ def clean(): for folderpath in glob.glob("/var/www/*%s*" % test_app): shutil.rmtree(folderpath, ignore_errors=True) - os.system( - "bash -c \"mysql -u root --password=$(cat /etc/yunohost/mysql) 2>/dev/null <<< 'DROP DATABASE %s' \"" - % test_app - ) - os.system( - "bash -c \"mysql -u root --password=$(cat /etc/yunohost/mysql) 2>/dev/null <<< 'DROP USER %s@localhost'\"" - % test_app - ) + os.system("bash -c \"mysql -B 2>/dev/null <<< 'DROP DATABASE %s' \"" % test_app) + os.system("bash -c \"mysql -B 2>/dev/null <<< 'DROP USER %s@localhost'\"" % test_app) - os.system( - "systemctl reset-failed nginx" - ) # Reset failed quota for service to avoid running into start-limit rate ? + # Reset failed quota for service to avoid running into start-limit rate ? + os.system("systemctl reset-failed nginx") os.system("systemctl start nginx") # Clean permissions From 01058aca281a2bcde3092d9fb35a548505370867 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 2 Mar 2021 01:49:23 +0100 Subject: [PATCH 2185/3170] Catch more secrets not redacted --- src/yunohost/log.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/log.py b/src/yunohost/log.py index 763967b38..1b7d66c44 100644 --- a/src/yunohost/log.py +++ b/src/yunohost/log.py @@ -415,7 +415,7 @@ class RedactingFormatter(Formatter): # (the secret part being at least 3 chars to avoid catching some lines like just "db_pwd=") # Some names like "key" or "manifest_key" are ignored, used in helpers like ynh_app_setting_set or ynh_read_manifest match = re.search( - r"(pwd|pass|password|secret|\w+key|token)=(\S{3,})$", record.strip() + r"(pwd|pass|password|secret\w*|\w+key|token)=(\S{3,})$", record.strip() ) if ( match From 0c172cd3f95799f20d3ab55faa9d3d02437a35d4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 2 Mar 2021 01:49:23 +0100 Subject: [PATCH 2186/3170] Catch more secrets not redacted --- src/yunohost/log.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/log.py b/src/yunohost/log.py index 7e9ae18e6..c51628c50 100644 --- a/src/yunohost/log.py +++ b/src/yunohost/log.py @@ -387,7 +387,7 @@ class RedactingFormatter(Formatter): # This matches stuff like db_pwd=the_secret or admin_password=other_secret # (the secret part being at least 3 chars to avoid catching some lines like just "db_pwd=") # Some names like "key" or "manifest_key" are ignored, used in helpers like ynh_app_setting_set or ynh_read_manifest - match = re.search(r'(pwd|pass|password|secret|\w+key|token)=(\S{3,})$', record.strip()) + match = re.search(r'(pwd|pass|password|secret\w*|\w+key|token)=(\S{3,})$', record.strip()) if match and match.group(2) not in self.data_to_redact and match.group(1) not in ["key", "manifest_key"]: self.data_to_redact.append(match.group(2)) except Exception as e: From a43cd72c72524b2f9eb22fa86a2edac0465ab432 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 25 Feb 2021 16:36:28 +0100 Subject: [PATCH 2187/3170] Fix ynh_replace_vars again, mystical bash is mystic... --- data/helpers.d/utils | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 5e02ca762..c67e1fae1 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -393,8 +393,11 @@ ynh_replace_vars () { for one_var in "${uniques_vars[@]}" do # Validate that one_var is indeed defined - # Explanation for the weird '+x' syntax: https://stackoverflow.com/a/13864829 - test -n "${one_var+x}" || ynh_die --message="Variable \$$one_var wasn't initialized when trying to replace __${one_var^^}__ in $file" + # -v checks if the variable is defined, for example: + # -v FOO tests if $FOO is defined + # -v $FOO tests if ${!FOO} is defined + # More info: https://stackoverflow.com/questions/3601515/how-to-check-if-a-variable-is-set-in-bash/17538964#comment96392525_17538964 + [[ -v "${one_var:-}" ]] || ynh_die --message="Variable \$$one_var wasn't initialized when trying to replace __${one_var^^}__ in $file" # Escape delimiter in match/replace string match_string="__${one_var^^}__" From 2728801d1798be9eff25294dc85dbce2c4bb02ad Mon Sep 17 00:00:00 2001 From: yalh76 Date: Sat, 27 Feb 2021 20:39:55 +0100 Subject: [PATCH 2188/3170] Force destination to be replaced --- 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 c67e1fae1..fee7e009b 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -321,7 +321,7 @@ ynh_add_config () { ynh_backup_if_checksum_is_different --file="$destination" - cp "$template_path" "$destination" + cp -f "$template_path" "$destination" ynh_replace_vars --file="$destination" From 9bbc3b72ae30092b717af7095359b16975700ca6 Mon Sep 17 00:00:00 2001 From: yalh76 Date: Sun, 28 Feb 2021 13:38:10 +0100 Subject: [PATCH 2189/3170] Make grep lazy --- 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 fee7e009b..b1b265b8b 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -386,7 +386,7 @@ ynh_replace_vars () { # Replace others variables # List other unique (__ __) variables in $file - local uniques_vars=( $(grep -o '__[A-Z0-9_]*__' $file | sort --unique | sed "s@__\([^.]*\)__@\L\1@g" )) + local uniques_vars=( $(grep -oP '__[A-Z0-9_]+?__' $file | sort --unique | sed "s@__\([^.]*\)__@\L\1@g" )) # Do the replacement local delimit=@ From 2402a1db39bf3a294459e1c0123b254e36c9f19c Mon Sep 17 00:00:00 2001 From: yalh76 Date: Sun, 28 Feb 2021 18:56:30 +0100 Subject: [PATCH 2190/3170] Fixing ___APP__ --- 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 b1b265b8b..53a7ea3b7 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -386,7 +386,7 @@ ynh_replace_vars () { # Replace others variables # List other unique (__ __) variables in $file - local uniques_vars=( $(grep -oP '__[A-Z0-9_]+?__' $file | sort --unique | sed "s@__\([^.]*\)__@\L\1@g" )) + local uniques_vars=( $(grep -oP '__[A-Z0-9]+?[A-Z0-9_]*?[A-Z0-9]*?__' $file | sort --unique | sed "s@__\([^.]*\)__@\L\1@g" )) # Do the replacement local delimit=@ From 88b414c8f3e0d3192a057f18260b65b9e3b1b23e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 25 Feb 2021 16:44:55 +0100 Subject: [PATCH 2191/3170] Don't redact empty string... --- src/yunohost/log.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/yunohost/log.py b/src/yunohost/log.py index c51628c50..a5501557d 100644 --- a/src/yunohost/log.py +++ b/src/yunohost/log.py @@ -376,7 +376,11 @@ class RedactingFormatter(Formatter): msg = super(RedactingFormatter, self).format(record) self.identify_data_to_redact(msg) for data in self.data_to_redact: - msg = msg.replace(data, "**********") + # we check that data is not empty string, + # otherwise this may lead to super epic stuff + # (try to run "foo".replace("", "bar")) + if data: + msg = msg.replace(data, "**********") return msg def identify_data_to_redact(self, record): From d12f403fe3235364f80f33c0522fb56e1998cb26 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 25 Feb 2021 16:57:57 +0100 Subject: [PATCH 2192/3170] Fix permission helper doc format --- data/helpers.d/permission | 47 +++++++++++++++++---------------------- 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/data/helpers.d/permission b/data/helpers.d/permission index 1791425b5..4b51902eb 100644 --- a/data/helpers.d/permission +++ b/data/helpers.d/permission @@ -25,19 +25,14 @@ # usage: ynh_permission_create --permission="permission" [--url="url"] [--additional_urls="second-url" [ "third-url" ]] [--auth_header=true|false] # [--allowed=group1 [ group2 ]] [--label="label"] [--show_tile=true|false] # [--protected=true|false] -# | arg: -p, permission= - the name for the permission (by default a permission named "main" already exist) -# | arg: -u, url= - (optional) URL for which access will be allowed/forbidden. -# | Not that if 'show_tile' is enabled, this URL will be the URL of the tile. -# | arg: -A, additional_urls= - (optional) List of additional URL for which access will be allowed/forbidden -# | arg: -h, auth_header= - (optional) Define for the URL of this permission, if SSOwat pass the authentication header to the application. Default is true -# | arg: -a, allowed= - (optional) A list of group/user to allow for the permission -# | arg: -l, label= - (optional) Define a name for the permission. This label will be shown on the SSO and in the admin. -# | Default is "APP_LABEL (permission name)". -# | arg: -t, show_tile= - (optional) Define if a tile will be shown in the SSO. If yes the name of the tile will be the 'label' parameter. -# | Default is false (for the permission different than 'main'). -# | arg: -P, protected= - (optional) Define if this permission is protected. If it is protected the administrator -# | won't be able to add or remove the visitors group of this permission. -# | By default it's 'false' +# | arg: -p, --permission= - the name for the permission (by default a permission named "main" already exist) +# | arg: -u, --url= - (optional) URL for which access will be allowed/forbidden. Note that if 'show_tile' is enabled, this URL will be the URL of the tile. +# | arg: -A, --additional_urls= - (optional) List of additional URL for which access will be allowed/forbidden +# | arg: -h, --auth_header= - (optional) Define for the URL of this permission, if SSOwat pass the authentication header to the application. Default is true +# | arg: -a, --allowed= - (optional) A list of group/user to allow for the permission +# | arg: -l, --label= - (optional) Define a name for the permission. This label will be shown on the SSO and in the admin. Default is "APP_LABEL (permission name)". +# | arg: -t, --show_tile= - (optional) Define if a tile will be shown in the SSO. If yes the name of the tile will be the 'label' parameter. Defaults to false for the permission different than 'main'. +# | arg: -P, --protected= - (optional) Define if this permission is protected. If it is protected the administrator won't be able to add or remove the visitors group of this permission. Defaults to 'false'. # # If provided, 'url' or 'additional_urls' is assumed to be relative to the app domain/path if they # start with '/'. For example: @@ -192,13 +187,12 @@ ynh_permission_exists() { # # usage: ynh_permission_url --permission "permission" [--url="url"] [--add_url="new-url" [ "other-new-url" ]] [--remove_url="old-url" [ "other-old-url" ]] # [--auth_header=true|false] [--clear_urls] -# | arg: -p, permission= - the name for the permission (by default a permission named "main" is removed automatically when the app is removed) -# | arg: -u, url= - (optional) URL for which access will be allowed/forbidden. -# | Note that if you want to remove url you can pass an empty sting as arguments (""). -# | arg: -a, add_url= - (optional) List of additional url to add for which access will be allowed/forbidden. -# | arg: -r, remove_url= - (optional) List of additional url to remove for which access will be allowed/forbidden -# | arg: -h, auth_header= - (optional) Define for the URL of this permission, if SSOwat pass the authentication header to the application -# | arg: -c, clear_urls - (optional) Clean all urls (url and additional_urls) +# | arg: -p, --permission= - the name for the permission (by default a permission named "main" is removed automatically when the app is removed) +# | arg: -u, --url= - (optional) URL for which access will be allowed/forbidden. Note that if you want to remove url you can pass an empty sting as arguments (""). +# | arg: -a, --add_url= - (optional) List of additional url to add for which access will be allowed/forbidden. +# | arg: -r, --remove_url= - (optional) List of additional url to remove for which access will be allowed/forbidden +# | arg: -h, --auth_header= - (optional) Define for the URL of this permission, if SSOwat pass the authentication header to the application +# | arg: -c, --clear_urls - (optional) Clean all urls (url and additional_urls) # # Requires YunoHost version 3.7.0 or higher. ynh_permission_url() { @@ -268,13 +262,12 @@ ynh_permission_url() { # # usage: ynh_permission_update --permission "permission" [--add="group" ["group" ...]] [--remove="group" ["group" ...]] # [--label="label"] [--show_tile=true|false] [--protected=true|false] -# | arg: -p, permission= - the name for the permission (by default a permission named "main" already exist) -# | arg: -a, add= - the list of group or users to enable add to the permission -# | arg: -r, remove= - the list of group or users to remove from the permission -# | arg: -l, label= - (optional) Define a name for the permission. This label will be shown on the SSO and in the admin. -# | arg: -t, show_tile= - (optional) Define if a tile will be shown in the SSO -# | arg: -P, protected= - (optional) Define if this permission is protected. If it is protected the administrator -# | won't be able to add or remove the visitors group of this permission. +# | arg: -p, --permission= - the name for the permission (by default a permission named "main" already exist) +# | arg: -a, --add= - the list of group or users to enable add to the permission +# | arg: -r, --remove= - the list of group or users to remove from the permission +# | arg: -l, --label= - (optional) Define a name for the permission. This label will be shown on the SSO and in the admin. +# | arg: -t, --show_tile= - (optional) Define if a tile will be shown in the SSO +# | arg: -P, --protected= - (optional) Define if this permission is protected. If it is protected the administrator won't be able to add or remove the visitors group of this permission. # # Requires YunoHost version 3.7.0 or higher. ynh_permission_update() { From 05969184feda1cb6c554ff0be2857229d140fde9 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Thu, 25 Feb 2021 11:16:34 +0100 Subject: [PATCH 2193/3170] fix ynh_systemd_action in case of service fails --- data/helpers.d/systemd | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/data/helpers.d/systemd b/data/helpers.d/systemd index b0e175d4d..7f316cf2b 100644 --- a/data/helpers.d/systemd +++ b/data/helpers.d/systemd @@ -92,7 +92,7 @@ ynh_remove_systemd_config () { # usage: ynh_systemd_action [--service_name=service_name] [--action=action] [ [--line_match="line to match"] [--log_path=log_path] [--timeout=300] [--length=20] ] # | arg: -n, --service_name= - Name of the service to start. Default : $app # | arg: -a, --action= - Action to perform with systemctl. Default: start -# | arg: -l, --line_match= - Line to match - The line to find in the log to attest the service have finished to boot. If not defined it don't wait until the service is completely started. WARNING: When using --line_match, you should always add `ynh_clean_check_starting` into your `ynh_clean_setup` at the beginning of the script. Otherwise, tail will not stop in case of failure of the script. The script will then hang forever. +# | arg: -l, --line_match= - Line to match - The line to find in the log to attest the service have finished to boot. If not defined it don't wait until the service is completely started. # | arg: -p, --log_path= - Log file - Path to the log file. Default : /var/log/$app/$app.log # | arg: -t, --timeout= - Timeout - The maximum time to wait before ending the watching. Default : 300 seconds. # | arg: -e, --length= - Length of the error log : Default : 20 @@ -151,6 +151,7 @@ ynh_systemd_action() { then ynh_exec_err tail --lines=$length "$log_path" fi + ynh_clean_check_starting return 1 fi @@ -195,9 +196,8 @@ ynh_systemd_action() { } # Clean temporary process and file used by ynh_check_starting -# (usually used in ynh_clean_setup scripts) # -# usage: ynh_clean_check_starting +# [internal] # # Requires YunoHost version 3.5.0 or higher. ynh_clean_check_starting () { @@ -208,7 +208,7 @@ ynh_clean_check_starting () { fi if [ -n "$templog" ] then - ynh_secure_remove "$templog" 2>&1 + ynh_secure_remove --file="$templog" 2>&1 fi } From 6ce02270ae9b6f5ad0c14b790769795b80aa0afa Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 22 Feb 2021 23:46:12 +0100 Subject: [PATCH 2194/3170] Gotta escape \ during ynh_replace_vars --- data/helpers.d/utils | 1 + 1 file changed, 1 insertion(+) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 53a7ea3b7..e26cac5ba 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -403,6 +403,7 @@ ynh_replace_vars () { match_string="__${one_var^^}__" match_string=${match_string//${delimit}/"\\${delimit}"} replace_string="${!one_var}" + replace_string=${replace_string//\\/\\\\} replace_string=${replace_string//${delimit}/"\\${delimit}"} # Actually replace (sed is used instead of ynh_replace_string to avoid triggering an epic amount of debug logs) From bd8644a651a77b16713aec2d83ca04468c31b5b5 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 20 Feb 2021 17:07:38 +0100 Subject: [PATCH 2195/3170] Translation typo.. --- locales/it.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/it.json b/locales/it.json index 9b5d18f1a..0a5095b9e 100644 --- a/locales/it.json +++ b/locales/it.json @@ -591,7 +591,7 @@ "log_user_permission_update": "Aggiorna gli accessi del permesso '{}'", "log_user_group_update": "Aggiorna il gruppo '{}'", "log_user_group_delete": "Cancella il gruppo '{}'", - "log_user_group_create": "Crea il gruppo '[}'", + "log_user_group_create": "Crea il gruppo '{}'", "log_permission_url": "Aggiorna l'URL collegato al permesso '{}'", "log_permission_delete": "Cancella permesso '{}'", "log_permission_create": "Crea permesso '{}'", From 234ae15d771b620b1239236c789cd5be66f7062f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 2 Mar 2021 02:07:58 +0100 Subject: [PATCH 2196/3170] Update changelog for 4.1.7.3 --- debian/changelog | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/debian/changelog b/debian/changelog index 95cca2eb8..5fb0e563d 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,4 +1,17 @@ -yunohost (4.1.7.2) testing; urgency=low +yunohost (4.1.7.3) stable; urgency=low + + - [fix] log: Some secrets were not redacted (0c172cd3) + - [fix] log: For some reason sometimes we were redacting 'empty string' which made everything explode (88b414c8) + - [fix] helpers: Various fixes for ynh_add_config / ynh_replace_vars (a43cd72c, 2728801d, 9bbc3b72, 2402a1db, 6ce02270) + - [fix] helpers: Fix permission helpers doc format (d12f403f) + - [fix] helpers: ynh_systemd_action did not properly clean the 'tail' process when service action failed (05969184) + - [fix] i18n: Translation typo in italian translation ... (bd8644a6) + + Thanks to all contributors <3 ! (Kay0u, yalh76) + + -- Alexandre Aubin Tue, 02 Mar 2021 02:03:35 +0100 + +yunohost (4.1.7.2) stable; urgency=low - [fix] When migration legacy protected permissions, all users were allowed on the new perm (29bd3c4a) - [fix] Mysql is a fucking joke (... trying to fix the mysql issue on RPi ...) (cd4fdb2b) From 9b13c95f77f1d2929841ec889d5c46173bc9033b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 2 Mar 2021 19:54:24 +0100 Subject: [PATCH 2197/3170] Re-add --others_var in fail2ban and systemd helpers for backward compatibility... --- data/helpers.d/fail2ban | 7 +++++-- data/helpers.d/systemd | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/data/helpers.d/fail2ban b/data/helpers.d/fail2ban index c9322d067..f8b2b1761 100644 --- a/data/helpers.d/fail2ban +++ b/data/helpers.d/fail2ban @@ -61,12 +61,13 @@ # Requires YunoHost version 3.5.0 or higher. ynh_add_fail2ban_config () { # Declare an array to define the options of this helper. - local legacy_args=lrmpt - local -A args_array=( [l]=logpath= [r]=failregex= [m]=max_retry= [p]=ports= [t]=use_template) + local legacy_args=lrmptv + local -A args_array=( [l]=logpath= [r]=failregex= [m]=max_retry= [p]=ports= [t]=use_template [v]=others_var=) local logpath local failregex local max_retry local ports + local others_var local use_template # Manage arguments with getopts ynh_handle_getopts_args "$@" @@ -74,6 +75,8 @@ ynh_add_fail2ban_config () { ports=${ports:-http,https} use_template="${use_template:-0}" + [[ -z "$others_var" ]] || ynh_print_warn --message="Packagers: using --others_var is unecessary since Yunohost 4.2" + if [ $use_template -ne 1 ] then # Usage 1, no template. Build a config file from scratch. diff --git a/data/helpers.d/systemd b/data/helpers.d/systemd index f8e21bfbd..b43b593fa 100644 --- a/data/helpers.d/systemd +++ b/data/helpers.d/systemd @@ -13,15 +13,18 @@ # Requires YunoHost version 2.7.11 or higher. ynh_add_systemd_config () { # Declare an array to define the options of this helper. - local legacy_args=st - local -A args_array=( [s]=service= [t]=template=) + local legacy_args=stv + local -A args_array=( [s]=service= [t]=template= [v]=others_var=) local service local template + local others_var # Manage arguments with getopts ynh_handle_getopts_args "$@" local service="${service:-$app}" local template="${template:-systemd.service}" + [[ -z "$others_var" ]] || ynh_print_warn --message="Packagers: using --others_var is unecessary since Yunohost 4.2" + ynh_add_config --template="$YNH_APP_BASEDIR/conf/$template" --destination="/etc/systemd/system/$service.service" systemctl enable $service --quiet From 4603697ac5e37efc6cd0c734dec260019301d96c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Pi=C3=A9dallu?= Date: Tue, 16 Feb 2021 14:39:24 +0100 Subject: [PATCH 2198/3170] helper doc fixes : permission --- data/helpers.d/permission | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/data/helpers.d/permission b/data/helpers.d/permission index ba2b22a53..995bff0eb 100644 --- a/data/helpers.d/permission +++ b/data/helpers.d/permission @@ -2,15 +2,17 @@ # Create a new permission for the app # -# example 1: ynh_permission_create --permission=admin --url=/admin --additional_urls=domain.tld/admin /superadmin --allowed=alice bob \ -# --label="My app admin" --show_tile=true +# Example 1: `ynh_permission_create --permission=admin --url=/admin --additional_urls=domain.tld/admin /superadmin --allowed=alice bob \ +# --label="My app admin" --show_tile=true` # # This example will create a new permission permission with this following effect: # - A tile named "My app admin" in the SSO will be available for the users alice and bob. This tile will point to the relative url '/admin'. # - Only the user alice and bob will have the access to theses following url: /admin, domain.tld/admin, /superadmin # # -# example 2: ynh_permission_create --permission=api --url=domain.tld/api --auth_header=false --allowed=visitors \ +# Example 2: +# +# ynh_permission_create --permission=api --url=domain.tld/api --auth_header=false --allowed=visitors \ # --label="MyApp API" --protected=true # # This example will create a new protected permission. So the admin won't be able to add/remove the visitors group of this permission. @@ -18,7 +20,7 @@ # With this permission all client will be allowed to access to the url 'domain.tld/api'. # Note that in this case no tile will be show on the SSO. # Note that the auth_header parameter is to 'false'. So no authentication header will be passed to the application. -# Generally the API is requested by an application and enabling the auth_header has no advantage and could bring some issues in some case. +# Generally the API is requested by an application and enabling the auth_header has no advantage and could bring some issues in some case. # So in this case it's better to disable this option for all API. # # @@ -36,14 +38,14 @@ # # If provided, 'url' or 'additional_urls' is assumed to be relative to the app domain/path if they # start with '/'. For example: -# / -> domain.tld/app -# /admin -> domain.tld/app/admin -# domain.tld/app/api -> domain.tld/app/api +# / -> domain.tld/app +# /admin -> domain.tld/app/admin +# domain.tld/app/api -> domain.tld/app/api # # 'url' or 'additional_urls' can be treated as a PCRE (not lua) regex if it starts with "re:". # For example: -# re:/api/[A-Z]*$ -> domain.tld/app/api/[A-Z]*$ -# re:domain.tld/app/api/[A-Z]*$ -> domain.tld/app/api/[A-Z]*$ +# re:/api/[A-Z]*$ -> domain.tld/app/api/[A-Z]*$ +# re:domain.tld/app/api/[A-Z]*$ -> domain.tld/app/api/[A-Z]*$ # # Note that globally the parameter 'url' and 'additional_urls' are same. The only difference is: # - 'url' is only one url, 'additional_urls' can be a list of urls. There are no limitation of 'additional_urls' @@ -56,7 +58,7 @@ # - "Remote-User": username # - "Email": user email # -# Generally this feature is usefull to authenticate automatically the user in the application but in some case the application don't work with theses header and theses header need to be disabled to have the application to work correctly. +# Generally this feature is usefull to authenticate automatically the user in the application but in some case the application don't work with theses header and theses header need to be disabled to have the application to work correctly. # See https://github.com/YunoHost/issues/issues/1420 for more informations # # @@ -186,7 +188,7 @@ ynh_permission_exists() { # Redefine the url associated to a permission # -# usage: ynh_permission_url --permission "permission" [--url="url"] [--add_url="new-url" [ "other-new-url" ]] [--remove_url="old-url" [ "other-old-url" ]] +# usage: ynh_permission_url --permission "permission" [--url="url"] [--add_url="new-url" [ "other-new-url" ]] [--remove_url="old-url" [ "other-old-url" ]] # [--auth_header=true|false] [--clear_urls] # | arg: -p, --permission= - the name for the permission (by default a permission named "main" is removed automatically when the app is removed) # | arg: -u, --url= - (optional) URL for which access will be allowed/forbidden. Note that if you want to remove url you can pass an empty sting as arguments (""). From e6f3adfa08a08373e5ab1b4e75a8fc88e7f00dde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Pi=C3=A9dallu?= Date: Wed, 3 Mar 2021 10:58:06 +0100 Subject: [PATCH 2199/3170] helper doc fixes : apt --- data/helpers.d/apt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index bfdeffe7b..1b5ab982f 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -47,10 +47,11 @@ ynh_wait_dpkg_free() { # Check either a package is installed or not # -# example: ynh_package_is_installed --package=yunohost && echo "ok" +# example: ynh_package_is_installed --package=yunohost && echo "installed" # # usage: ynh_package_is_installed --package=name # | arg: -p, --package= - the package name to check +# | ret: 0 if the package is installed, 1 else. # # Requires YunoHost version 2.2.4 or higher. ynh_package_is_installed() { @@ -180,7 +181,7 @@ ynh_package_install_from_equivs () { # Build and install the package local TMPDIR=$(mktemp --directory) - # Force the compatibility level at 10, levels below are deprecated + # Force the compatibility level at 10, levels below are deprecated echo 10 > /usr/share/equivs/template/debian/compat # Note that the cd executes into a sub shell @@ -194,7 +195,7 @@ ynh_package_install_from_equivs () { LC_ALL=C dpkg --force-depends --install "./${pkgname}_${pkgversion}_all.deb" 2>&1 | tee ./dpkg_log) ynh_package_install --fix-broken || \ - { # If the installation failed + { # If the installation failed # (the following is ran inside { } to not start a subshell otherwise ynh_die wouldnt exit the original process) # Parse the list of problematic dependencies from dpkg's log ... # (relevant lines look like: "foo-ynh-deps depends on bar; however:") @@ -216,7 +217,8 @@ ynh_package_install_from_equivs () { # example : ynh_install_app_dependencies dep1 dep2 "dep3|dep4|dep5" # # usage: ynh_install_app_dependencies dep [dep [...]] -# | arg: dep - the package name to install in dependence. Writing "dep3|dep4|dep5" can be used to specify alternatives. For example : dep1 dep2 "dep3|dep4|dep5" will require to install dep1 and dep 2 and (dep3 or dep4 or dep5). +# | arg: dep - the package name to install in dependence. +# | arg: "dep1|dep2|…" - You can specify alternatives. It will require to install (dep1 or dep2, etc). # # Requires YunoHost version 2.6.4 or higher. ynh_install_app_dependencies () { @@ -253,7 +255,7 @@ ynh_install_app_dependencies () { # Epic ugly hack to fix the goddamn dependency nightmare of sury # Sponsored by the "Djeezusse Fokin Kraiste Why Do Adminsys Has To Be So Fucking Complicated I Should Go Grow Potatoes Instead Of This Shit" collective # https://github.com/YunoHost/issues/issues/1407 - # + # # If we require to install php dependency if echo $dependencies | grep --quiet 'php' then From d69f031d8fffedbfe916f91f493c32ea5d627ef1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Pi=C3=A9dallu?= Date: Wed, 3 Mar 2021 10:58:34 +0100 Subject: [PATCH 2200/3170] helper doc fixes : backup --- data/helpers.d/backup | 79 ++++++++++++++++++++++++------------------- 1 file changed, 44 insertions(+), 35 deletions(-) diff --git a/data/helpers.d/backup b/data/helpers.d/backup index 33a6db4e2..17da0fb2e 100644 --- a/data/helpers.d/backup +++ b/data/helpers.d/backup @@ -13,13 +13,13 @@ CAN_BIND=${CAN_BIND:-1} # # This helper can be used both in a system backup hook, and in an app backup script # -# Details: ynh_backup writes SRC and the relative DEST into a CSV file. And it +# `ynh_backup` writes `src_path` and the relative `dest_path` into a CSV file, and it # creates the parent destination directory # -# If DEST is ended by a slash it complete this path with the basename of SRC. -# -# Example in the context of a wordpress app +# If `dest_path` is ended by a slash it complete this path with the basename of `src_path`. # +# Example in the context of a wordpress app : +# ``` # ynh_backup "/etc/nginx/conf.d/$domain.d/$app.conf" # # => This line will be added into CSV file # # "/etc/nginx/conf.d/$domain.d/$app.conf","apps/wordpress/etc/nginx/conf.d/$domain.d/$app.conf" @@ -40,26 +40,28 @@ CAN_BIND=${CAN_BIND:-1} # ynh_backup "/etc/nginx/conf.d/$domain.d/$app.conf" "/conf/" # # => "/etc/nginx/conf.d/$domain.d/$app.conf","apps/wordpress/conf/$app.conf" # +# ``` # -# How to use --is_big: -# --is_big is used to specify that this part of the backup can be quite huge. +# How to use `--is_big`: +# +# `--is_big` is used to specify that this part of the backup can be quite huge. # So, you don't want that your package does backup that part during ynh_backup_before_upgrade. # In the same way, an user may doesn't want to backup this big part of the app for -# each of his backup. And so handle that part differently. -# -# As this part of your backup may not be done, your restore script has to handle it. -# In your restore script, use --not_mandatory with ynh_restore_file -# As well in your remove script, you should not remove those data ! Or an user may end up with -# a failed upgrade restoring an app without data anymore ! +# each of his backup. And so handle that part differently. # -# To have the benefit of --is_big while doing a backup, you can whether set the environement -# variable BACKUP_CORE_ONLY to 1 (BACKUP_CORE_ONLY=1) before the backup command. It will affect -# only that backup command. -# Or set the config do_not_backup_data to 1 into the settings.yml of the app. This will affect -# all backups for this app until the setting is removed. +# As this part of your backup may not be done, your restore script has to handle it. +# In your restore script, use `--not_mandatory` with `ynh_restore_file` +# As well in your remove script, you should not remove those data ! Or an user may end up with +# a failed upgrade restoring an app without data anymore ! +# +# To have the benefit of `--is_big` while doing a backup, you can whether set the environement +# variable `BACKUP_CORE_ONLY` to 1 (`BACKUP_CORE_ONLY=1`) before the backup command. It will affect +# only that backup command. +# Or set the config `do_not_backup_data` to 1 into the `settings.yml` of the app. This will affect +# all backups for this app until the setting is removed. # # Requires YunoHost version 2.4.0 or higher. -# Requires YunoHost version 3.5.0 or higher for the argument --not_mandatory +# Requires YunoHost version 3.5.0 or higher for the argument `--not_mandatory` ynh_backup() { # TODO find a way to avoid injection by file strange naming ! @@ -81,7 +83,7 @@ ynh_backup() { # If backing up core only (used by ynh_backup_before_upgrade), # don't backup big data items - if [ $is_big -eq 1 ] && ( [ ${do_not_backup_data:-0} -eq 1 ] || [ $BACKUP_CORE_ONLY -eq 1 ] ) + if [ $is_big -eq 1 ] && ( [ ${do_not_backup_data:-0} -eq 1 ] || [ $BACKUP_CORE_ONLY -eq 1 ] ) then if [ $BACKUP_CORE_ONLY -eq 1 ] then @@ -221,26 +223,25 @@ with open(sys.argv[1], 'r') as backup_file: # Restore a file or a directory # -# Use the registered path in backup_list by ynh_backup to restore the file at -# the right place. -# # usage: ynh_restore_file --origin_path=origin_path [--dest_path=dest_path] [--not_mandatory] # | arg: -o, --origin_path= - Path where was located the file or the directory before to be backuped or relative path to $YNH_CWD where it is located in the backup archive -# | arg: -d, --dest_path= - Path where restore the file or the dir, if unspecified, the destination will be ORIGIN_PATH or if the ORIGIN_PATH doesn't exist in the archive, the destination will be searched into backup.csv +# | arg: -d, --dest_path= - Path where restore the file or the dir. If unspecified, the destination will be `ORIGIN_PATH` or if the `ORIGIN_PATH` doesn't exist in the archive, the destination will be searched into `backup.csv` # | arg: -m, --not_mandatory - Indicate that if the file is missing, the restore process can ignore it. # +# Use the registered path in backup_list by ynh_backup to restore the file at the right place. +# # examples: -# ynh_restore_file "/etc/nginx/conf.d/$domain.d/$app.conf" +# ynh_restore_file -o "/etc/nginx/conf.d/$domain.d/$app.conf" # # You can also use relative paths: -# ynh_restore_file "conf/nginx.conf" +# ynh_restore_file -o "conf/nginx.conf" # -# If DEST_PATH already exists and is lighter than 500 Mo, a backup will be made in -# /home/yunohost.conf/backup/. Otherwise, the existing file is removed. +# If `DEST_PATH` already exists and is lighter than 500 Mo, a backup will be made in +# `/home/yunohost.conf/backup/`. Otherwise, the existing file is removed. # -# if apps/wordpress/etc/nginx/conf.d/$domain.d/$app.conf exists, restore it into -# /etc/nginx/conf.d/$domain.d/$app.conf +# if `apps/$app/etc/nginx/conf.d/$domain.d/$app.conf` exists, restore it into +# `/etc/nginx/conf.d/$domain.d/$app.conf` # if no, search for a match in the csv (eg: conf/nginx.conf) and restore it into -# /etc/nginx/conf.d/$domain.d/$app.conf +# `/etc/nginx/conf.d/$domain.d/$app.conf` # # Requires YunoHost version 2.6.4 or higher. # Requires YunoHost version 3.5.0 or higher for the argument --not_mandatory @@ -345,14 +346,14 @@ ynh_store_file_checksum () { } # Verify the checksum and backup the file if it's different -# -# This helper is primarily meant to allow to easily backup personalised/manually -# modified config files. # # usage: ynh_backup_if_checksum_is_different --file=file # | arg: -f, --file= - The file on which the checksum test will be perfomed. # | ret: the name of a backup file, or nothing # +# This helper is primarily meant to allow to easily backup personalised/manually +# modified config files. +# # Requires YunoHost version 2.6.4 or higher. ynh_backup_if_checksum_is_different () { # Declare an array to define the options of this helper. @@ -410,12 +411,16 @@ ynh_backup_archive_exists () { # Make a backup in case of failed upgrade # -# usage: +# usage: ynh_backup_before_upgrade +# +# Usage in a package script: +# ``` # ynh_backup_before_upgrade # ynh_clean_setup () { # ynh_restore_upgradebackup # } # ynh_abort_if_errors +# ``` # # Requires YunoHost version 2.7.2 or higher. ynh_backup_before_upgrade () { @@ -459,12 +464,16 @@ ynh_backup_before_upgrade () { # Restore a previous backup if the upgrade process failed # -# usage: +# usage: ynh_restore_upgradebackup +# +# Usage in a package script: +# ``` # ynh_backup_before_upgrade # ynh_clean_setup () { # ynh_restore_upgradebackup # } # ynh_abort_if_errors +# ``` # # Requires YunoHost version 2.7.2 or higher. ynh_restore_upgradebackup () { From 8f294916d9ea0af8318635c0588cb5ac4155cf84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Pi=C3=A9dallu?= Date: Wed, 3 Mar 2021 10:58:44 +0100 Subject: [PATCH 2201/3170] helper doc fixes : fail2ban --- data/helpers.d/fail2ban | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/data/helpers.d/fail2ban b/data/helpers.d/fail2ban index f8b2b1761..756f4b84a 100644 --- a/data/helpers.d/fail2ban +++ b/data/helpers.d/fail2ban @@ -12,15 +12,14 @@ # # usage 2: ynh_add_fail2ban_config --use_template [--others_var="list of others variables to replace"] # | arg: -t, --use_template - Use this helper in template mode -# | arg: -v, --others_var= - List of others variables to replace separeted by a space -# | for example : 'var_1 var_2 ...' +# | arg: -v, --others_var= - List of others variables to replace separeted by a space for example : 'var_1 var_2 ...' # -# This will use a template in ../conf/f2b_jail.conf and ../conf/f2b_filter.conf -# See the documentation of ynh_add_config for a description of the template +# This will use a template in `../conf/f2b_jail.conf` and `../conf/f2b_filter.conf` +# See the documentation of `ynh_add_config` for a description of the template # format and how placeholders are replaced with actual variables. # # Generally your template will look like that by example (for synapse): -# +# ``` # f2b_jail.conf: # [__APP__] # enabled = true @@ -28,7 +27,8 @@ # filter = __APP__ # logpath = /var/log/__APP__/logfile.log # maxretry = 3 -# +# ``` +# ``` # f2b_filter.conf: # [INCLUDES] # before = common.conf @@ -41,22 +41,25 @@ # failregex = ^%(__synapse_start_line)s INFO \- POST\-(\d+)\- \- \d+ \- Received request\: POST /_matrix/client/r0/login\??%(__synapse_start_line)s INFO \- POST\-\1\- Got login request with identifier: \{u'type': u'm.id.user', u'user'\: u'(.+?)'\}, medium\: None, address: None, user\: u'\5'%(__synapse_start_line)s WARNING \- \- (Attempted to login as @\5\:.+ but they do not exist|Failed password login for user @\5\:.+)$ # # ignoreregex = +# ``` # # ----------------------------------------------------------------------------- # # Note about the "failregex" option: -# regex to match the password failure messages in the logfile. The -# host must be matched by a group named "host". The tag "" can -# be used for standard IP/hostname matching and is only an alias for -# (?:::f{4,6}:)?(?P[\w\-.^_]+) # -# You can find some more explainations about how to make a regex here : -# https://www.fail2ban.org/wiki/index.php/MANUAL_0_8#Filters +# regex to match the password failure messages in the logfile. The host must be +# matched by a group named "`host`". The tag "``" can be used for standard +# IP/hostname matching and is only an alias for `(?:::f{4,6}:)?(?P[\w\-.^_]+)` +# +# You can find some more explainations about how to make a regex here : +# https://www.fail2ban.org/wiki/index.php/MANUAL_0_8#Filters # # Note that the logfile need to exist before to call this helper !! # # To validate your regex you can test with this command: +# ``` # fail2ban-regex /var/log/YOUR_LOG_FILE_PATH /etc/fail2ban/filter.d/YOUR_APP.conf +# ``` # # Requires YunoHost version 3.5.0 or higher. ynh_add_fail2ban_config () { From 966cd2ff165c1d2de5d999eb5b5a4e42657c4983 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Pi=C3=A9dallu?= Date: Wed, 3 Mar 2021 10:58:53 +0100 Subject: [PATCH 2202/3170] helper doc fixes : hardware --- data/helpers.d/hardware | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/data/helpers.d/hardware b/data/helpers.d/hardware index 0771c81d8..6d1c314fa 100644 --- a/data/helpers.d/hardware +++ b/data/helpers.d/hardware @@ -7,7 +7,7 @@ # | arg: -t, --total - Count total RAM+swap # | arg: -s, --ignore_swap - Ignore swap, consider only real RAM # | arg: -o, --only_swap - Ignore real RAM, consider only swap -# | ret: the amount of free ram +# | ret: the amount of free ram, in MB (MegaBytes) # # Requires YunoHost version 3.8.1 or higher. ynh_get_ram () { @@ -35,7 +35,7 @@ ynh_get_ram () { local free_ram=$(vmstat --stats --unit M | grep "free memory" | awk '{print $1}') local free_swap=$(vmstat --stats --unit M | grep "free swap" | awk '{print $1}') local free_ram_swap=$(( free_ram + free_swap )) - + # Use the total amount of free ram local ram=$free_ram_swap if [ $ignore_swap -eq 1 ] @@ -52,7 +52,7 @@ ynh_get_ram () { local total_ram=$(vmstat --stats --unit M | grep "total memory" | awk '{print $1}') local total_swap=$(vmstat --stats --unit M | grep "total swap" | awk '{print $1}') local total_ram_swap=$(( total_ram + total_swap )) - + local ram=$total_ram_swap if [ $ignore_swap -eq 1 ] then @@ -70,13 +70,13 @@ ynh_get_ram () { # Return 0 or 1 depending if the system has a given amount of RAM+swap free or total # -# usage: ynh_require_ram --required=RAM required in Mb [--free|--total] [--ignore_swap|--only_swap] -# | arg: -r, --required= - The amount to require, in Mb +# usage: ynh_require_ram --required=RAM [--free|--total] [--ignore_swap|--only_swap] +# | arg: -r, --required= - The amount to require, in MB # | arg: -f, --free - Count free RAM+swap # | arg: -t, --total - Count total RAM+swap # | arg: -s, --ignore_swap - Ignore swap, consider only real RAM # | arg: -o, --only_swap - Ignore real RAM, consider only swap -# | exit: Return 1 if the ram is under the requirement, 0 otherwise. +# | ret: 1 if the ram is under the requirement, 0 otherwise. # # Requires YunoHost version 3.8.1 or higher. ynh_require_ram () { From 33f8337f11af0a3d9809483cdae5e7e3d78ad1aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Pi=C3=A9dallu?= Date: Wed, 3 Mar 2021 10:59:28 +0100 Subject: [PATCH 2203/3170] helper doc fixes : logging --- data/helpers.d/logging | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/data/helpers.d/logging b/data/helpers.d/logging index dc32ecba9..0505117b7 100644 --- a/data/helpers.d/logging +++ b/data/helpers.d/logging @@ -102,8 +102,7 @@ ynh_print_err () { # Execute a command and print the result as an error # -# usage: ynh_exec_err your_command -# usage: ynh_exec_err "your_command | other_command" +# usage: ynh_exec_err "your_command [ | other_command ]" # | arg: command - command to execute # # When using pipes, double quotes are required - otherwise, this helper will run the first command, and the whole output will be sent through the next pipe. @@ -117,8 +116,7 @@ ynh_exec_err () { # Execute a command and print the result as a warning # -# usage: ynh_exec_warn your_command -# usage: ynh_exec_warn "your_command | other_command" +# usage: ynh_exec_warn "your_command [ | other_command ]" # | arg: command - command to execute # # When using pipes, double quotes are required - otherwise, this helper will run the first command, and the whole output will be sent through the next pipe. @@ -132,8 +130,7 @@ ynh_exec_warn () { # Execute a command and force the result to be printed on stdout # -# usage: ynh_exec_warn_less your_command -# usage: ynh_exec_warn_less "your_command | other_command" +# usage: ynh_exec_warn_less "your_command [ | other_command ]" # | arg: command - command to execute # # When using pipes, double quotes are required - otherwise, this helper will run the first command, and the whole output will be sent through the next pipe. @@ -147,8 +144,7 @@ ynh_exec_warn_less () { # Execute a command and redirect stdout in /dev/null # -# usage: ynh_exec_quiet your_command -# usage: ynh_exec_quiet "your_command | other_command" +# usage: ynh_exec_quiet "your_command [ | other_command ]" # | arg: command - command to execute # # When using pipes, double quotes are required - otherwise, this helper will run the first command, and the whole output will be sent through the next pipe. @@ -162,8 +158,7 @@ ynh_exec_quiet () { # Execute a command and redirect stdout and stderr in /dev/null # -# usage: ynh_exec_fully_quiet your_command -# usage: ynh_exec_fully_quiet "your_command | other_command" +# usage: ynh_exec_fully_quiet "your_command [ | other_command ]" # | arg: command - command to execute # # When using pipes, double quotes are required - otherwise, this helper will run the first command, and the whole output will be sent through the next pipe. @@ -363,8 +358,7 @@ ynh_debug () { # Execute a command and print the result as debug # -# usage: ynh_debug_exec your_command -# usage: ynh_debug_exec "your_command | other_command" +# usage: ynh_debug_exec "your_command [ | other_command ]" # | arg: command - command to execute # # When using pipes, double quotes are required - otherwise, this helper will run the first command, and the whole output will be sent through the next pipe. From 0dde41fe4aaac5bd56c1d091df9a0a00c4532856 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Pi=C3=A9dallu?= Date: Wed, 3 Mar 2021 10:59:28 +0100 Subject: [PATCH 2204/3170] helper doc fixes : logrotate --- data/helpers.d/logrotate | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/data/helpers.d/logrotate b/data/helpers.d/logrotate index d5384264c..2d9ab6b72 100644 --- a/data/helpers.d/logrotate +++ b/data/helpers.d/logrotate @@ -7,16 +7,14 @@ # | arg: -n, --nonappend - (optional) Replace the config file instead of appending this new config. # | arg: -u, --specific_user= - run logrotate as the specified user and group. If not specified logrotate is runned as root. # -# If no --logfile is provided, /var/log/${app} will be used as default. -# logfile can be just a directory, or a full path to a logfile : -# /parentdir/logdir -# /parentdir/logdir/logfile.log +# If no `--logfile` is provided, `/var/log/$app` will be used as default. +# `logfile` can point to a directory or a file. # # It's possible to use this helper multiple times, each config will be added to -# the same logrotate config file. Unless you use the option --non-append +# the same logrotate config file. Unless you use the option `--non-append` # # Requires YunoHost version 2.6.4 or higher. -# Requires YunoHost version 3.2.0 or higher for the argument --specific_user +# Requires YunoHost version 3.2.0 or higher for the argument `--specific_user` ynh_use_logrotate () { # Declare an array to define the options of this helper. local legacy_args=lnuya From 27caab72345bd9f27fa304ca23b4b48ff3b5380c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Pi=C3=A9dallu?= Date: Wed, 3 Mar 2021 10:59:29 +0100 Subject: [PATCH 2205/3170] helper doc fixes : multimedia --- data/helpers.d/multimedia | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/multimedia b/data/helpers.d/multimedia index f7c9d5974..7b379ad0f 100644 --- a/data/helpers.d/multimedia +++ b/data/helpers.d/multimedia @@ -47,12 +47,14 @@ ynh_multimedia_build_main_dir() { } # Add a directory in yunohost.multimedia -# This "directory" will be a symbolic link to a existing directory. # -# usage: ynh_multimedia_addfolder "Source directory" "Destination directory" +# usage: ynh_multimedia_addfolder --source_dir="source_dir" --dest_dir="dest_dir" # # | arg: -s, --source_dir= - Source directory - The real directory which contains your medias. # | arg: -d, --dest_dir= - Destination directory - The name and the place of the symbolic link, relative to "/home/yunohost.multimedia" +# +# This "directory" will be a symbolic link to a existing directory. +# ynh_multimedia_addfolder() { # Declare an array to define the options of this helper. @@ -80,6 +82,7 @@ ynh_multimedia_addfolder() { # usage: ynh_multimedia_addaccess user_name # # | arg: -u, --user_name= - The name of the user which gain this access. +# ynh_multimedia_addaccess () { # Declare an array to define the options of this helper. local legacy_args=u From 75539472ea9d28ce76d1a298fb85aa25a946b1d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Pi=C3=A9dallu?= Date: Wed, 3 Mar 2021 10:59:29 +0100 Subject: [PATCH 2206/3170] helper doc fixes : mysql --- data/helpers.d/mysql | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/data/helpers.d/mysql b/data/helpers.d/mysql index 6808441b7..091dfaf40 100644 --- a/data/helpers.d/mysql +++ b/data/helpers.d/mysql @@ -2,14 +2,15 @@ # Open a connection as a user # -# example: ynh_mysql_connect_as --user="user" --password="pass" <<< "UPDATE ...;" -# example: ynh_mysql_connect_as --user="user" --password="pass" < /path/to/file.sql -# # usage: ynh_mysql_connect_as --user=user --password=password [--database=database] # | arg: -u, --user= - the user name to connect as # | arg: -p, --password= - the user password # | arg: -d, --database= - the database to connect to # +# examples: +# ynh_mysql_connect_as --user="user" --password="pass" <<< "UPDATE ...;" +# ynh_mysql_connect_as --user="user" --password="pass" < /path/to/file.sql +# # Requires YunoHost version 2.2.4 or higher. ynh_mysql_connect_as() { # Declare an array to define the options of this helper. @@ -120,11 +121,11 @@ ynh_mysql_drop_db() { # Dump a database # -# example: ynh_mysql_dump_db --database=roundcube > ./dump.sql -# # usage: ynh_mysql_dump_db --database=database # | arg: -d, --database= - the database name to dump -# | ret: the mysqldump output +# | ret: The mysqldump output +# +# example: ynh_mysql_dump_db --database=roundcube > ./dump.sql # # Requires YunoHost version 2.2.4 or higher. ynh_mysql_dump_db() { @@ -156,7 +157,7 @@ ynh_mysql_create_user() { # # usage: ynh_mysql_user_exists --user=user # | arg: -u, --user= - the user for which to check existence -# | exit: Return 1 if the user doesn't exist, 0 otherwise. +# | ret: 0 if the user exists, 1 otherwise. # # Requires YunoHost version 2.2.4 or higher. ynh_mysql_user_exists() @@ -195,8 +196,8 @@ ynh_mysql_drop_user() { # | arg: -n, --db_name= - Name of the database # | arg: -p, --db_pwd= - Password of the database. If not provided, a password will be generated # -# After executing this helper, the password of the created database will be available in $db_pwd -# It will also be stored as "mysqlpwd" into the app settings. +# After executing this helper, the password of the created database will be available in `$db_pwd` +# It will also be stored as "`mysqlpwd`" into the app settings. # # Requires YunoHost version 2.6.4 or higher. ynh_mysql_setup_db () { From 94097d1153a40261a6d7b9df654e8f9a9cd66716 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Pi=C3=A9dallu?= Date: Wed, 3 Mar 2021 10:59:29 +0100 Subject: [PATCH 2207/3170] helper doc fixes : network --- data/helpers.d/network | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/data/helpers.d/network b/data/helpers.d/network index 0760909c6..702757534 100644 --- a/data/helpers.d/network +++ b/data/helpers.d/network @@ -2,12 +2,12 @@ # Find a free port and return it # -# example: port=$(ynh_find_port --port=8080) -# # usage: ynh_find_port --port=begin_port # | arg: -p, --port= - port to start to search # | ret: the port number # +# example: port=$(ynh_find_port --port=8080) +# # Requires YunoHost version 2.6.4 or higher. ynh_find_port () { # Declare an array to define the options of this helper. @@ -27,11 +27,11 @@ ynh_find_port () { # Test if a port is available # -# example: ynh_port_available --port=1234 || ynh_die --message="Port 1234 is needs to be available for this app" -# # usage: ynh_find_port --port=XYZ # | arg: -p, --port= - port to check -# | exit: Return 1 if the port is already used by another process. +# | ret: 0 if the port is available, 1 if it is already used by another process. +# +# example: ynh_port_available --port=1234 || ynh_die --message="Port 1234 is needs to be available for this app" # # Requires YunoHost version 3.8.0 or higher. ynh_port_available () { @@ -89,12 +89,12 @@ EOF # Validate an IPv4 address # -# example: ynh_validate_ip4 111.222.333.444 -# # usage: ynh_validate_ip4 --ip_address=ip_address # | arg: -i, --ip_address= - the ipv4 address to check # | ret: 0 for valid ipv4 addresses, 1 otherwise # +# example: ynh_validate_ip4 111.222.333.444 +# # Requires YunoHost version 2.2.4 or higher. ynh_validate_ip4() { @@ -111,12 +111,12 @@ ynh_validate_ip4() # Validate an IPv6 address # -# example: ynh_validate_ip6 2000:dead:beef::1 -# # usage: ynh_validate_ip6 --ip_address=ip_address -# | arg: -i, --ip_address= - the ipv6 address to check +# | arg: -i, --ip_address= - the ipv6 address to check # | ret: 0 for valid ipv6 addresses, 1 otherwise # +# example: ynh_validate_ip6 2000:dead:beef::1 +# # Requires YunoHost version 2.2.4 or higher. ynh_validate_ip6() { From 52cc5949da252c9c3ec807c303aac6365c86a41a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Pi=C3=A9dallu?= Date: Wed, 3 Mar 2021 10:59:29 +0100 Subject: [PATCH 2208/3170] helper doc fixes : nginx --- data/helpers.d/nginx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/data/helpers.d/nginx b/data/helpers.d/nginx index 3c6254953..7214b1e26 100644 --- a/data/helpers.d/nginx +++ b/data/helpers.d/nginx @@ -4,13 +4,13 @@ # # usage: ynh_add_nginx_config # -# This will use a template in ../conf/nginx.conf -# See the documentation of ynh_add_config for a description of the template +# This will use a template in `../conf/nginx.conf` +# See the documentation of `ynh_add_config` for a description of the template # format and how placeholders are replaced with actual variables. # # Additionally, ynh_add_nginx_config will replace: -# - #sub_path_only by empty string if path_url is not '/' -# - #root_path_only by empty string if path_url *is* '/' +# - `#sub_path_only` by empty string if `path_url` is not `'/'` +# - `#root_path_only` by empty string if `path_url` *is* `'/'` # # This allows to enable/disable specific behaviors dependenging on the install # location From 3844b48962d58116d89ccb8d58c343ab3b391c58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Pi=C3=A9dallu?= Date: Wed, 3 Mar 2021 10:59:29 +0100 Subject: [PATCH 2209/3170] helper doc fixes : nodejs --- data/helpers.d/nodejs | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/data/helpers.d/nodejs b/data/helpers.d/nodejs index 2e1c787cf..f78a53fd5 100644 --- a/data/helpers.d/nodejs +++ b/data/helpers.d/nodejs @@ -28,11 +28,14 @@ SOURCE_SUM=2933855140f980fc6d1d6103ea07cd4d915b17dea5e17e43921330ea89978b5b" > " # Load the version of node for an app, and set variables. # -# ynh_use_nodejs has to be used in any app scripts before using node for the first time. +# usage: ynh_use_nodejs +# +# `ynh_use_nodejs` has to be used in any app scripts before using node for the first time. # This helper will provide alias and variables to use in your scripts. # -# To use npm or node, use the alias `ynh_npm` and `ynh_node` -# Those alias will use the correct version installed for the app +# To use npm or node, use the alias `ynh_npm` and `ynh_node`. +# +# Those alias will use the correct version installed for the app. # For example: use `ynh_npm install` instead of `npm install` # # With `sudo` or `ynh_exec_as`, use instead the fallback variables `$ynh_npm` and `$ynh_node` @@ -40,30 +43,32 @@ SOURCE_SUM=2933855140f980fc6d1d6103ea07cd4d915b17dea5e17e43921330ea89978b5b" > " # Exemple: `ynh_exec_as $app $ynh_node_load_PATH $ynh_npm install` # # $PATH contains the path of the requested version of node. -# However, $PATH is duplicated into $node_PATH to outlast any manipulation of $PATH +# However, $PATH is duplicated into $node_PATH to outlast any manipulation of `$PATH` # You can use the variable `$ynh_node_load_PATH` to quickly load your node version -# in $PATH for an usage into a separate script. +# in $PATH for an usage into a separate script. # Exemple: $ynh_node_load_PATH $final_path/script_that_use_npm.sh` # # # Finally, to start a nodejs service with the correct version, 2 solutions # Either the app is dependent of node or npm, but does not called it directly. -# In such situation, you need to load PATH -# `Environment="__NODE_ENV_PATH__"` -# `ExecStart=__FINALPATH__/my_app` -# You will replace __NODE_ENV_PATH__ with $ynh_node_load_PATH +# In such situation, you need to load PATH : +# ``` +# Environment="__NODE_ENV_PATH__" +# ExecStart=__FINALPATH__/my_app +# ``` +# You will replace __NODE_ENV_PATH__ with $ynh_node_load_PATH. # # Or node start the app directly, then you don't need to load the PATH variable -# `ExecStart=__YNH_NODE__ my_app run` -# You will replace __YNH_NODE__ with $ynh_node +# ``` +# ExecStart=__YNH_NODE__ my_app run +# ``` +# You will replace __YNH_NODE__ with $ynh_node # # # 2 other variables are also available # - $nodejs_path: The absolute path to node binaries for the chosen version. # - $nodejs_version: Just the version number of node for this app. Stored as 'nodejs_version' in settings.yml. # -# usage: ynh_use_nodejs -# # Requires YunoHost version 2.7.12 or higher. ynh_use_nodejs () { nodejs_version=$(ynh_app_setting_get --app=$app --key=nodejs_version) @@ -97,10 +102,10 @@ ynh_use_nodejs () { # usage: ynh_install_nodejs --nodejs_version=nodejs_version # | arg: -n, --nodejs_version= - Version of node to install. When possible, your should prefer to use major version number (e.g. 8 instead of 8.10.0). The crontab will then handle the update of minor versions when needed. # -# n (Node version management) uses the PATH variable to store the path of the version of node it is going to use. +# `n` (Node version management) uses the `PATH` variable to store the path of the version of node it is going to use. # That's how it changes the version # -# Refer to ynh_use_nodejs for more information about available commands and variables +# Refer to `ynh_use_nodejs` for more information about available commands and variables # # Requires YunoHost version 2.7.12 or higher. ynh_install_nodejs () { @@ -177,12 +182,12 @@ ynh_install_nodejs () { # Remove the version of node used by the app. # -# This helper will check if another app uses the same version of node, -# if not, this version of node will be removed. -# If no other app uses node, n will be also removed. -# # usage: ynh_remove_nodejs # +# This helper will check if another app uses the same version of node. +# - If not, this version of node will be removed. +# - If no other app uses node, n will be also removed. +# # Requires YunoHost version 2.7.12 or higher. ynh_remove_nodejs () { nodejs_version=$(ynh_app_setting_get --app=$app --key=nodejs_version) From 706dbe723e9852943596de38c26202ac4367d36f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Pi=C3=A9dallu?= Date: Wed, 3 Mar 2021 10:59:29 +0100 Subject: [PATCH 2210/3170] helper doc fixes : php --- data/helpers.d/php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/data/helpers.d/php b/data/helpers.d/php index 5c050cc88..a590da3dd 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -569,6 +569,7 @@ YNH_COMPOSER_VERSION=${YNH_COMPOSER_VERSION:-$YNH_DEFAULT_COMPOSER_VERSION} # | arg: -v, --phpversion - PHP version to use with composer # | arg: -w, --workdir - The directory from where the command will be executed. Default $final_path. # | arg: -c, --commands - Commands to execute. +# ynh_composer_exec () { # Declare an array to define the options of this helper. local legacy_args=vwc @@ -593,6 +594,7 @@ ynh_composer_exec () { # | arg: -w, --workdir - The directory from where the command will be executed. Default $final_path. # | arg: -a, --install_args - Additional arguments provided to the composer install. Argument --no-dev already include # | arg: -c, --composerversion - Composer version to install +# ynh_install_composer () { # Declare an array to define the options of this helper. local legacy_args=vwac From dc27fd3ec3ab2a1c67102f023c4d1413723b4ea4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Pi=C3=A9dallu?= Date: Wed, 3 Mar 2021 10:59:29 +0100 Subject: [PATCH 2211/3170] helper doc fixes : postgresql --- data/helpers.d/postgresql | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/data/helpers.d/postgresql b/data/helpers.d/postgresql index f2f427842..12738a922 100644 --- a/data/helpers.d/postgresql +++ b/data/helpers.d/postgresql @@ -5,15 +5,15 @@ PSQL_VERSION=11 # Open a connection as a user # -# examples: -# ynh_psql_connect_as 'user' 'pass' <<< "UPDATE ...;" -# ynh_psql_connect_as 'user' 'pass' < /path/to/file.sql -# # usage: ynh_psql_connect_as --user=user --password=password [--database=database] # | arg: -u, --user= - the user name to connect as # | arg: -p, --password= - the user password # | arg: -d, --database= - the database to connect to # +# examples: +# ynh_psql_connect_as 'user' 'pass' <<< "UPDATE ...;" +# ynh_psql_connect_as 'user' 'pass' < /path/to/file.sql +# # Requires YunoHost version 3.5.0 or higher. ynh_psql_connect_as() { # Declare an array to define the options of this helper. @@ -127,12 +127,12 @@ ynh_psql_drop_db() { # Dump a database # -# example: ynh_psql_dump_db 'roundcube' > ./dump.sql -# # usage: ynh_psql_dump_db --database=database # | arg: -d, --database= - the database name to dump # | ret: the psqldump output # +# example: ynh_psql_dump_db 'roundcube' > ./dump.sql +# # Requires YunoHost version 3.5.0 or higher. ynh_psql_dump_db() { # Declare an array to define the options of this helper. @@ -243,7 +243,7 @@ ynh_psql_setup_db() { local new_db_pwd=$(ynh_string_random) # Generate a random password # If $db_pwd is not provided, use new_db_pwd instead for db_pwd db_pwd="${db_pwd:-$new_db_pwd}" - + ynh_psql_create_user "$db_user" "$db_pwd" elif [ -z $db_pwd ]; then ynh_die --message="The user $db_user exists, please provide his password" @@ -286,11 +286,12 @@ 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 # +# It also make sure that postgresql is installed and running +# Please always call this script in install and restore scripts +# # Requires YunoHost version 2.7.13 or higher. ynh_psql_test_if_first_run() { From 305a70a8d5a60fb10aad807a0af71c82e059afc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Pi=C3=A9dallu?= Date: Wed, 3 Mar 2021 10:59:29 +0100 Subject: [PATCH 2212/3170] helper doc fixes : setting --- data/helpers.d/setting | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index a66e0d1ea..2950b3829 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -77,7 +77,7 @@ ynh_app_setting_delete() { # [internal] # ynh_app_setting() -{ +{ set +o xtrace # set +x ACTION="$1" APP="$2" KEY="$3" VALUE="${4:-}" python3 - < Date: Wed, 3 Mar 2021 10:59:29 +0100 Subject: [PATCH 2213/3170] helper doc fixes : string --- data/helpers.d/string | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/data/helpers.d/string b/data/helpers.d/string index a0bcdbfaf..7036b3b3c 100644 --- a/data/helpers.d/string +++ b/data/helpers.d/string @@ -2,12 +2,12 @@ # Generate a random string # -# example: pwd=$(ynh_string_random --length=8) -# # usage: ynh_string_random [--length=string_length] # | arg: -l, --length= - the string length to generate (default: 24) # | ret: the generated string # +# example: pwd=$(ynh_string_random --length=8) +# # Requires YunoHost version 2.2.4 or higher. ynh_string_random() { # Declare an array to define the options of this helper. @@ -30,9 +30,8 @@ ynh_string_random() { # | arg: -r, --replace_string= - String that will replace matches # | arg: -f, --target_file= - File in which the string will be replaced. # -# As this helper is based on sed command, regular expressions and -# references to sub-expressions can be used -# (see sed manual page for more information) +# As this helper is based on sed command, regular expressions and references to +# sub-expressions can be used (see sed manual page for more information) # # Requires YunoHost version 2.6.4 or higher. ynh_replace_string () { @@ -86,14 +85,15 @@ ynh_replace_special_string () { } # Sanitize a string intended to be the name of a database -# (More specifically : replace - and . by _) -# -# example: dbname=$(ynh_sanitize_dbid $app) # # usage: ynh_sanitize_dbid --db_name=name # | arg: -n, --db_name= - name to correct/sanitize # | ret: the corrected name # +# example: dbname=$(ynh_sanitize_dbid $app) +# +# Underscorify the string (replace - and . by _) +# # Requires YunoHost version 2.2.4 or higher. ynh_sanitize_dbid () { # Declare an array to define the options of this helper. From f0d7eba64a9d087d5d44c3472014301937c408ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Pi=C3=A9dallu?= Date: Wed, 3 Mar 2021 10:59:29 +0100 Subject: [PATCH 2214/3170] helper doc fixes : systemd --- data/helpers.d/systemd | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/data/helpers.d/systemd b/data/helpers.d/systemd index b43b593fa..16dee928a 100644 --- a/data/helpers.d/systemd +++ b/data/helpers.d/systemd @@ -3,11 +3,12 @@ # Create a dedicated systemd config # # usage: ynh_add_systemd_config [--service=service] [--template=template] -# | arg: -s, --service= - Service name (optionnal, $app by default) -# | arg: -t, --template= - Name of template file (optionnal, this is 'systemd' by default, meaning ./conf/systemd.service will be used as template) +# | arg: -s, --service= - Service name (optionnal, `$app` by default) +# | arg: -t, --template= - Name of template file (optionnal, this is 'systemd' by default, meaning `../conf/systemd.service` will be used as template) # -# This will use the template ../conf/.service -# See the documentation of ynh_add_config for a description of the template +# This will use the template `../conf/.service`. +# +# See the documentation of `ynh_add_config` for a description of the template # format and how placeholders are replaced with actual variables. # # Requires YunoHost version 2.7.11 or higher. @@ -59,10 +60,10 @@ ynh_remove_systemd_config () { # Start (or other actions) a service, print a log in case of failure and optionnaly wait until the service is completely started # # usage: ynh_systemd_action [--service_name=service_name] [--action=action] [ [--line_match="line to match"] [--log_path=log_path] [--timeout=300] [--length=20] ] -# | arg: -n, --service_name= - Name of the service to start. Default : $app +# | arg: -n, --service_name= - Name of the service to start. Default : `$app` # | arg: -a, --action= - Action to perform with systemctl. Default: start # | arg: -l, --line_match= - Line to match - The line to find in the log to attest the service have finished to boot. If not defined it don't wait until the service is completely started. -# | arg: -p, --log_path= - Log file - Path to the log file. Default : /var/log/$app/$app.log +# | arg: -p, --log_path= - Log file - Path to the log file. Default : `/var/log/$app/$app.log` # | arg: -t, --timeout= - Timeout - The maximum time to wait before ending the watching. Default : 300 seconds. # | arg: -e, --length= - Length of the error log : Default : 20 # @@ -180,4 +181,3 @@ ynh_clean_check_starting () { ynh_secure_remove --file="$templog" 2>&1 fi } - From 36996482659bfd711a715a5403a0871ed7d7266e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Pi=C3=A9dallu?= Date: Wed, 3 Mar 2021 10:59:29 +0100 Subject: [PATCH 2215/3170] helper doc fixes : user --- data/helpers.d/user | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/data/helpers.d/user b/data/helpers.d/user index 4d2d9ad65..c12b4656e 100644 --- a/data/helpers.d/user +++ b/data/helpers.d/user @@ -2,11 +2,11 @@ # Check if a YunoHost user exists # -# example: ynh_user_exists 'toto' || exit 1 -# # usage: ynh_user_exists --username=username # | arg: -u, --username= - the username to check -# | exit: Return 1 if the user doesn't exist, 0 otherwise +# | ret: 0 if the user exists, 1 otherwise. +# +# example: ynh_user_exists 'toto' || echo "User does not exist" # # Requires YunoHost version 2.2.4 or higher. ynh_user_exists() { @@ -22,12 +22,12 @@ ynh_user_exists() { # Retrieve a YunoHost user information # -# example: mail=$(ynh_user_get_info 'toto' 'mail') -# # usage: ynh_user_get_info --username=username --key=key # | arg: -u, --username= - the username to retrieve info from # | arg: -k, --key= - the key to retrieve -# | ret: string - the key's value +# | ret: the value associate to that key +# +# example: mail=$(ynh_user_get_info 'toto' 'mail') # # Requires YunoHost version 2.2.4 or higher. ynh_user_get_info() { @@ -44,10 +44,10 @@ ynh_user_get_info() { # Get the list of YunoHost users # -# example: for u in $(ynh_user_list); do ... -# # usage: ynh_user_list -# | ret: string - one username per line +# | ret: one username per line as strings +# +# example: for u in $(ynh_user_list); do ... ; done # # Requires YunoHost version 2.4.0 or higher. ynh_user_list() { @@ -58,7 +58,7 @@ ynh_user_list() { # # usage: ynh_system_user_exists --username=username # | arg: -u, --username= - the username to check -# | exit: Return 1 if the user doesn't exist, 0 otherwise +# | ret: 0 if the user exists, 1 otherwise. # # Requires YunoHost version 2.2.4 or higher. ynh_system_user_exists() { @@ -76,7 +76,7 @@ ynh_system_user_exists() { # # usage: ynh_system_group_exists --group=group # | arg: -g, --group= - the group to check -# | exit: Return 1 if the group doesn't exist, 0 otherwise +# | ret: 0 if the group exists, 1 otherwise. # # Requires YunoHost version 3.5.0.2 or higher. ynh_system_group_exists() { @@ -92,17 +92,20 @@ ynh_system_group_exists() { # Create a system user # -# examples: -# # Create a nextcloud user with no home directory and /usr/sbin/nologin login shell (hence no login capability) -# ynh_system_user_create --username=nextcloud -# # Create a discourse user using /var/www/discourse as home directory and the default login shell -# ynh_system_user_create --username=discourse --home_dir=/var/www/discourse --use_shell -# # usage: ynh_system_user_create --username=user_name [--home_dir=home_dir] [--use_shell] # | arg: -u, --username= - Name of the system user that will be create # | arg: -h, --home_dir= - Path of the home dir for the user. Usually the final path of the app. If this argument is omitted, the user will be created without home # | arg: -s, --use_shell - Create a user using the default login shell if present. If this argument is omitted, the user will be created with /usr/sbin/nologin shell # +# Create a nextcloud user with no home directory and /usr/sbin/nologin login shell (hence no login capability) : +# ``` +# ynh_system_user_create --username=nextcloud +# ``` +# Create a discourse user using /var/www/discourse as home directory and the default login shell : +# ``` +# ynh_system_user_create --username=discourse --home_dir=/var/www/discourse --use_shell +# ``` +# # Requires YunoHost version 2.6.4 or higher. ynh_system_user_create () { # Declare an array to define the options of this helper. From 72da2ad8e3a40f2237f37af911eb069b14b356aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Pi=C3=A9dallu?= Date: Wed, 3 Mar 2021 10:59:29 +0100 Subject: [PATCH 2216/3170] helper doc fixes : utils --- data/helpers.d/utils | 135 ++++++++++++++++++++----------------------- 1 file changed, 64 insertions(+), 71 deletions(-) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 487ec41db..28b1e5a1d 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -48,9 +48,8 @@ ynh_exit_properly () { # usage: ynh_abort_if_errors # # This configure the rest of the script execution such that, if an error occurs -# or if an empty variable is used, the execution of the script stops -# immediately and a call to `ynh_clean_setup` is triggered if it has been -# defined by your script. +# or if an empty variable is used, the execution of the script stops immediately +# and a call to `ynh_clean_setup` is triggered if it has been defined by your script. # # Requires YunoHost version 2.6.4 or higher. ynh_abort_if_errors () { @@ -63,45 +62,37 @@ ynh_abort_if_errors () { # # usage: ynh_setup_source --dest_dir=dest_dir [--source_id=source_id] # | arg: -d, --dest_dir= - Directory where to setup sources -# | arg: -s, --source_id= - Name of the app, if the package contains more than one app +# | arg: -s, --source_id= - Name of the source, defaults to `app` # -# The file conf/app.src need to contains: +# This helper will read `conf/${source_id}.src`, download and install the sources. # +# The src file need to contains: +# ``` # SOURCE_URL=Address to download the app archive # SOURCE_SUM=Control sum -# # (Optional) Program to check the integrity (sha256sum, md5sum...) -# # default: sha256 +# # (Optional) Program to check the integrity (sha256sum, md5sum...). Default: sha256 # SOURCE_SUM_PRG=sha256 -# # (Optional) Archive format -# # default: tar.gz +# # (Optional) Archive format. Default: tar.gz # SOURCE_FORMAT=tar.gz -# # (Optional) Put false if sources are directly in the archive root -# # default: true -# # Instead of true, SOURCE_IN_SUBDIR could be the number of sub directories -# # to remove. +# # (Optional) Put false if sources are directly in the archive root. Default: true +# # Instead of true, SOURCE_IN_SUBDIR could be the number of sub directories to remove. # SOURCE_IN_SUBDIR=false -# # (Optionnal) Name of the local archive (offline setup support) -# # default: ${src_id}.${src_format} +# # (Optionnal) Name of the local archive (offline setup support). Default: ${src_id}.${src_format} # SOURCE_FILENAME=example.tar.gz -# # (Optional) If it set as false don't extract the source. +# # (Optional) If it set as false don't extract the source. Default: true # # (Useful to get a debian package or a python wheel.) -# # default: true # SOURCE_EXTRACT=(true|false) +# ``` # -# Details: -# This helper downloads sources from SOURCE_URL if there is no local source -# archive in /opt/yunohost-apps-src/APP_ID/SOURCE_FILENAME -# -# Next, it checks the integrity with "SOURCE_SUM_PRG -c --status" command. -# -# If it's ok, the source archive will be uncompressed in $dest_dir. If the -# SOURCE_IN_SUBDIR is true, the first level directory of the archive will be -# removed. -# If SOURCE_IN_SUBDIR is a numeric value, 2 for example, the 2 first level -# directories will be removed -# -# Finally, patches named sources/patches/${src_id}-*.patch and extra files in -# sources/extra_files/$src_id will be applied to dest_dir +# The helper will: +# - Check if there is a local source archive in `/opt/yunohost-apps-src/$APP_ID/$SOURCE_FILENAME` +# - Download `$SOURCE_URL` if there is no local archive +# - Check the integrity with `$SOURCE_SUM_PRG -c --status` +# - Uncompress the archive to `$dest_dir`. +# - If `$SOURCE_IN_SUBDIR` is true, the first level directory of the archive will be removed. +# - If `$SOURCE_IN_SUBDIR` is a numeric value, the N first level directories will be removed. +# - Patches named `sources/patches/${src_id}-*.patch` will be applied to `$dest_dir` +# - Extra files in `sources/extra_files/$src_id` will be copied to dest_dir # # Requires YunoHost version 2.6.4 or higher. ynh_setup_source () { @@ -211,17 +202,17 @@ ynh_setup_source () { # Curl abstraction to help with POST requests to local pages (such as installation forms) # -# example: ynh_local_curl "/install.php?installButton" "foo=$var1" "bar=$var2" -# # usage: ynh_local_curl "page_uri" "key1=value1" "key2=value2" ... -# | arg: page_uri - Path (relative to $path_url) of the page where POST data will be sent +# | arg: page_uri - Path (relative to `$path_url`) of the page where POST data will be sent # | arg: key1=value1 - (Optionnal) POST key and corresponding value # | arg: key2=value2 - (Optionnal) Another POST key and corresponding value # | arg: ... - (Optionnal) More POST keys and values # +# example: ynh_local_curl "/install.php?installButton" "foo=$var1" "bar=$var2" +# # For multiple calls, cookies are persisted between each call for the same app # -# $domain and $path_url should be defined externally (and correspond to the domain.tld and the /path (of the app?)) +# `$domain` and `$path_url` should be defined externally (and correspond to the domain.tld and the /path (of the app?)) # # Requires YunoHost version 2.6.4 or higher. ynh_local_curl () { @@ -250,7 +241,7 @@ ynh_local_curl () { # Wait untils nginx has fully reloaded (avoid curl fail with http2) sleep 2 - + local cookiefile=/tmp/ynh-$app-cookie.txt touch $cookiefile chown root $cookiefile @@ -262,20 +253,22 @@ ynh_local_curl () { # Create a dedicated config file from a template # +# usage: ynh_add_config --template="template" --destination="destination" +# | arg: -t, --template= - Template config file to use +# | arg: -d, --destination= - Destination of the config file +# # examples: # ynh_add_config --template=".env" --destination="$final_path/.env" # ynh_add_config --template="../conf/.env" --destination="$final_path/.env" # ynh_add_config --template="/etc/nginx/sites-available/default" --destination="etc/nginx/sites-available/mydomain.conf" # -# usage: ynh_add_config --template="template" --destination="destination" -# | arg: -t, --template= - Template config file to use -# | arg: -d, --destination= - Destination of the config file -# # The template can be by default the name of a file in the conf directory -# of a YunoHost Package, a relative path or an absolute path -# The helper will use the template $template to generate a config file -# $destination by replacing the following keywords with global variables +# of a YunoHost Package, a relative path or an absolute path. +# +# The helper will use the template `template` to generate a config file +# `destination` by replacing the following keywords with global variables # that should be defined before calling this helper : +# ``` # __PATH__ by $path_url # __NAME__ by $app # __NAMETOCHANGE__ by $app @@ -283,15 +276,18 @@ ynh_local_curl () { # __FINALPATH__ by $final_path # __PHPVERSION__ by $YNH_PHP_VERSION # __YNH_NODE_LOAD_PATH__ by $ynh_node_load_PATH -# +# ``` # And any dynamic variables that should be defined before calling this helper like: +# ``` # __DOMAIN__ by $domain # __APP__ by $app # __VAR_1__ by $var_1 # __VAR_2__ by $var_2 +# ``` # # The helper will verify the checksum and backup the destination file # if it's different before applying the new template. +# # And it will calculate and store the destination file checksum # into the app settings when configuration is done. # @@ -556,16 +552,17 @@ ynh_read_manifest () { jq ".$manifest_key" "$manifest" --raw-output } -# Read the upstream version from the manifest, or from the env variable $YNH_APP_MANIFEST_VERSION if not given +# Read the upstream version from the manifest or `$YNH_APP_MANIFEST_VERSION` # # usage: ynh_app_upstream_version [--manifest="manifest.json"] # | arg: -m, --manifest= - Path of the manifest to read # | ret: the version number of the upstream app # -# The version number in the manifest is defined by ~ynh -# For example : 4.3-2~ynh3 -# This include the number before ~ynh -# In the last example it return 4.3-2 +# If the `manifest` is not specified, the envvar `$YNH_APP_MANIFEST_VERSION` will be used. +# +# The version number in the manifest is defined by `~ynh`. +# +# For example, if the manifest contains `4.3-2~ynh3` the function will return `4.3-2` # # Requires YunoHost version 3.5.0 or higher. ynh_app_upstream_version () { @@ -593,10 +590,9 @@ ynh_app_upstream_version () { # | arg: -m, --manifest= - Path of the manifest to read # | ret: the version number of the package # -# The version number in the manifest is defined by ~ynh -# For example : 4.3-2~ynh3 -# This include the number after ~ynh -# In the last example it return 3 +# The version number in the manifest is defined by `~ynh`. +# +# For example, if the manifest contains `4.3-2~ynh3` the function will return `3` # # Requires YunoHost version 3.5.0 or higher. ynh_app_package_version () { @@ -613,18 +609,16 @@ ynh_app_package_version () { # Checks the app version to upgrade with the existing app version and returns: # -# - UPGRADE_PACKAGE if only the YunoHost package has changed -# - UPGRADE_APP otherwise +# usage: ynh_check_app_version_changed +# | ret: `UPGRADE_APP` if the upstream version changed, `UPGRADE_PACKAGE` otherwise. # # This helper should be used to avoid an upgrade of an app, or the upstream part # of it, when it's not needed # -# To force an upgrade, even if the package is up to date, -# you have to use the parameter --force (or -F). -# example: sudo yunohost app upgrade MyApp --force -# -# usage: ynh_check_app_version_changed -# +# You can force an upgrade, even if the package is up to date, with the `--force` (or `-F`) argument : +# ``` +# sudo yunohost app upgrade --force +# ``` # Requires YunoHost version 3.5.0 or higher. ynh_check_app_version_changed () { local return_value=${YNH_APP_UPGRADE_TYPE} @@ -638,24 +632,23 @@ 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. +# +# usage: ynh_compare_current_package_version --comparison (lt|le|eq|ne|ge|gt) --version +# | 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`) +# | ret: 0 if the evaluation is true, 1 if false. # # 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 +# This helper is usually used when we need to do some actions only for some old package versions. # +# Generally you might probably use it as follow in the upgrade script : +# ``` # if ynh_compare_current_package_version --comparison lt --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_current_package_version --comparison lt|le|eq|ne|ge|gt -# | 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) -# -# Return 0 if the evaluation is true. 1 if false. +# ``` # # Requires YunoHost version 3.8.0 or higher. ynh_compare_current_package_version() { From 5539751ca4bfadb2dfc3a1864a81736d28017c54 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Mon, 8 Mar 2021 17:38:10 +0100 Subject: [PATCH 2217/3170] fix others_var --- data/helpers.d/fail2ban | 1 + data/helpers.d/systemd | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/fail2ban b/data/helpers.d/fail2ban index f8b2b1761..aa2c8d3c0 100644 --- a/data/helpers.d/fail2ban +++ b/data/helpers.d/fail2ban @@ -73,6 +73,7 @@ ynh_add_fail2ban_config () { ynh_handle_getopts_args "$@" max_retry=${max_retry:-3} ports=${ports:-http,https} + others_var="${others_var:-}" use_template="${use_template:-0}" [[ -z "$others_var" ]] || ynh_print_warn --message="Packagers: using --others_var is unecessary since Yunohost 4.2" diff --git a/data/helpers.d/systemd b/data/helpers.d/systemd index b43b593fa..4c7bd31d1 100644 --- a/data/helpers.d/systemd +++ b/data/helpers.d/systemd @@ -20,8 +20,9 @@ ynh_add_systemd_config () { local others_var # Manage arguments with getopts ynh_handle_getopts_args "$@" - local service="${service:-$app}" - local template="${template:-systemd.service}" + service="${service:-$app}" + template="${template:-systemd.service}" + others_var="${others_var:-}" [[ -z "$others_var" ]] || ynh_print_warn --message="Packagers: using --others_var is unecessary since Yunohost 4.2" From 9e7eda681bcf82fac70a0b6cb481c5fbda3b689c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 8 Mar 2021 18:37:53 +0100 Subject: [PATCH 2218/3170] ynh_install_n: put the source descriptor file in the appropriate dir during backup/restore --- data/helpers.d/nodejs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/nodejs b/data/helpers.d/nodejs index 2e1c787cf..200e5ecbd 100644 --- a/data/helpers.d/nodejs +++ b/data/helpers.d/nodejs @@ -18,7 +18,7 @@ ynh_install_n () { # Build an app.src for n mkdir --parents "../conf" echo "SOURCE_URL=https://github.com/tj/n/archive/v${n_version}.tar.gz -SOURCE_SUM=2933855140f980fc6d1d6103ea07cd4d915b17dea5e17e43921330ea89978b5b" > "../conf/n.src" +SOURCE_SUM=2933855140f980fc6d1d6103ea07cd4d915b17dea5e17e43921330ea89978b5b" > "$YNH_APP_BASEDIR/conf/n.src" # Download and extract n ynh_setup_source --dest_dir="$n_install_dir/git" --source_id=n # Install n From db93b82b23d0470a8dcde3393e67002e2d975fd8 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 8 Mar 2021 18:41:54 +0100 Subject: [PATCH 2219/3170] ynh_setup_source: add a check that we could actually parse SOURCE_URL --- data/helpers.d/utils | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 487ec41db..8246b9986 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -135,12 +135,15 @@ ynh_setup_source () { if [ "$src_filename" = "" ]; then src_filename="${source_id}.${src_format}" fi - local local_src="/opt/yunohost-apps-src/${YNH_APP_ID}/${src_filename}" + # (Unused?) mecanism where one can have the file in a special local cache to not have to download it... + local local_src="/opt/yunohost-apps-src/${YNH_APP_ID}/${src_filename}" if test -e "$local_src" - then # Use the local source file if it is present + then cp $local_src $src_filename - else # If not, download the source + else + [ -n "$src_url" ] || ynh_die "Couldn't parse SOURCE_URL from $src_file_path ?" + # NB. we have to declare the var as local first, # otherwise 'local foo=$(false) || echo 'pwet'" does'nt work # because local always return 0 ... From ad602ee0a13d59bb3c79c289e34ab294e4bb26f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= <46165813+ericgaspar@users.noreply.github.com> Date: Mon, 8 Mar 2021 20:55:45 +0100 Subject: [PATCH 2220/3170] Upgrade n (#1178) Co-authored-by: Alexandre Aubin --- data/helpers.d/nodejs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/nodejs b/data/helpers.d/nodejs index 200e5ecbd..d4122c1c6 100644 --- a/data/helpers.d/nodejs +++ b/data/helpers.d/nodejs @@ -1,6 +1,6 @@ #!/bin/bash -n_version=7.0.0 +n_version=7.0.2 n_install_dir="/opt/node_n" node_version_path="$n_install_dir/n/versions/node" # N_PREFIX is the directory of n, it needs to be loaded as a environment variable. @@ -18,7 +18,7 @@ ynh_install_n () { # Build an app.src for n mkdir --parents "../conf" echo "SOURCE_URL=https://github.com/tj/n/archive/v${n_version}.tar.gz -SOURCE_SUM=2933855140f980fc6d1d6103ea07cd4d915b17dea5e17e43921330ea89978b5b" > "$YNH_APP_BASEDIR/conf/n.src" +SOURCE_SUM=fa80a8685f0fb1b4187fc0a1228b44f0ea2f244e063fe8f443b8913ea595af89" > "$YNH_APP_BASEDIR/conf/n.src" # Download and extract n ynh_setup_source --dest_dir="$n_install_dir/git" --source_id=n # Install n From b85d959d7e0dd6f287d9121dba5adc9bd2caa490 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 8 Mar 2021 20:56:32 +0100 Subject: [PATCH 2221/3170] ynh_install_n: No need to mkdir ../conf --- data/helpers.d/nodejs | 1 - 1 file changed, 1 deletion(-) diff --git a/data/helpers.d/nodejs b/data/helpers.d/nodejs index d4122c1c6..c2f374a56 100644 --- a/data/helpers.d/nodejs +++ b/data/helpers.d/nodejs @@ -16,7 +16,6 @@ export N_PREFIX="$n_install_dir" ynh_install_n () { ynh_print_info --message="Installation of N - Node.js version management" # Build an app.src for n - mkdir --parents "../conf" echo "SOURCE_URL=https://github.com/tj/n/archive/v${n_version}.tar.gz SOURCE_SUM=fa80a8685f0fb1b4187fc0a1228b44f0ea2f244e063fe8f443b8913ea595af89" > "$YNH_APP_BASEDIR/conf/n.src" # Download and extract n From 50a42e1d87959e25ee2cc97a01ae2a6e3ee6bad3 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 9 Mar 2021 05:45:24 +0100 Subject: [PATCH 2222/3170] Rework the authenticator system, split the LDAP stuff into auth part and utils part --- data/actionsmap/yunohost.yml | 19 +- src/yunohost/authenticators/ldap_admin.py | 75 ++++++ src/yunohost/utils/ldap.py | 291 +++++++++++++++++++--- 3 files changed, 336 insertions(+), 49 deletions(-) create mode 100644 src/yunohost/authenticators/ldap_admin.py diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 290952aa3..741d75a22 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -33,18 +33,9 @@ # Global parameters # ############################# _global: - configuration: - authenticate: - - api - authenticator: - default: - vendor: ldap - help: admin_password - parameters: - uri: ldap://localhost:389 - base_dn: dc=yunohost,dc=org - user_rdn: cn=admin,dc=yunohost,dc=org - argument_auth: false + authentication: + api: ldap_admin + cli: null arguments: -v: full: --version @@ -1404,9 +1395,9 @@ tools: postinstall: action_help: YunoHost post-install api: POST /postinstall - configuration: + authentication: # We need to be able to run the postinstall without being authenticated, otherwise we can't run the postinstall - authenticate: false + api: null arguments: -d: full: --domain diff --git a/src/yunohost/authenticators/ldap_admin.py b/src/yunohost/authenticators/ldap_admin.py new file mode 100644 index 000000000..d7a1dadda --- /dev/null +++ b/src/yunohost/authenticators/ldap_admin.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- + +import os +import logging +import ldap +import ldap.sasl +import time +import ldap.modlist as modlist + +from moulinette import m18n +from moulinette.core import MoulinetteError +from moulinette.authentication import BaseAuthenticator +from yunohost.utils.error import YunohostError + +logger = logging.getLogger("yunohost.authenticators.lpda_admin") + +class Authenticator(BaseAuthenticator): + + """LDAP Authenticator + + Initialize a LDAP connexion for the given arguments. It attempts to + authenticate a user if 'user_rdn' is given - by associating user_rdn + and base_dn - and provides extra methods to manage opened connexion. + + Keyword arguments: + - uri -- The LDAP server URI + - base_dn -- The base dn + - user_rdn -- The user rdn to authenticate + + """ + + name = "ldap_admin" + + def __init__(self, *args, **kwargs): + self.uri = "ldap://localhost:389" + self.basedn = "dc=yunohost,dc=org" + self.admindn = "cn=admin,dc=yunohost,dc=org" + + def authenticate(self, password=None): + def _reconnect(): + con = ldap.ldapobject.ReconnectLDAPObject( + self.uri, retry_max=10, retry_delay=0.5 + ) + con.simple_bind_s(self.admindn, password) + return con + + try: + con = _reconnect() + except ldap.INVALID_CREDENTIALS: + raise MoulinetteError("invalid_password") + except ldap.SERVER_DOWN: + # ldap is down, attempt to restart it before really failing + logger.warning(m18n.g("ldap_server_is_down_restart_it")) + os.system("systemctl restart slapd") + time.sleep(10) # waits 10 secondes so we are sure that slapd has restarted + + try: + con = _reconnect() + except ldap.SERVER_DOWN: + raise YunohostError("ldap_server_down") + + # Check that we are indeed logged in with the expected identity + try: + # whoami_s return dn:..., then delete these 3 characters + who = con.whoami_s()[3:] + except Exception as e: + logger.warning("Error during ldap authentication process: %s", e) + raise + else: + if who != self.admindn: + raise MoulinetteError(f"Not logged with the appropriate identity ? Found {who}, expected {self.admindn} !?") + finally: + # Free the connection, we don't really need it to keep it open as the point is only to check authentication... + if con: + con.unbind_s() diff --git a/src/yunohost/utils/ldap.py b/src/yunohost/utils/ldap.py index 85bca34d7..28d7a17ce 100644 --- a/src/yunohost/utils/ldap.py +++ b/src/yunohost/utils/ldap.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- - """ License Copyright (C) 2019 YunoHost @@ -21,10 +20,18 @@ import os import atexit -from moulinette.core import MoulinetteLdapIsDownError -from moulinette.authenticators import ldap +import logging +import ldap +import ldap.sasl +import time +import ldap.modlist as modlist + +from moulinette import m18n +from moulinette.core import MoulinetteError from yunohost.utils.error import YunohostError +logger = logging.getLogger("yunohost.utils.ldap") + # We use a global variable to do some caching # to avoid re-authenticating in case we call _get_ldap_authenticator multiple times _ldap_interface = None @@ -35,51 +42,21 @@ def _get_ldap_interface(): global _ldap_interface if _ldap_interface is None: - - conf = { - "vendor": "ldap", - "name": "as-root", - "parameters": { - "uri": "ldapi://%2Fvar%2Frun%2Fslapd%2Fldapi", - "base_dn": "dc=yunohost,dc=org", - "user_rdn": "gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth", - }, - "extra": {}, - } - - 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() + _ldap_interface = LDAPInterface() return _ldap_interface -def assert_slapd_is_running(): - - # Assert slapd is running... - if not os.system("pgrep slapd >/dev/null") == 0: - 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'" - ) - - # We regularly want to extract stuff like 'bar' in ldap path like # foo=bar,dn=users.example.org,ou=example.org,dc=org so this small helper allow # to do this without relying of dozens of mysterious string.split()[0] # # e.g. using _ldap_path_extract(path, "foo") on the previous example will # return bar - - def _ldap_path_extract(path, info): for element in path.split(","): if element.startswith(info + "="): - return element[len(info + "=") :] + return element[len(info + "="):] # Add this to properly close / delete the ldap interface / authenticator @@ -93,3 +70,247 @@ def _destroy_ldap_interface(): atexit.register(_destroy_ldap_interface) + + +class LDAPInterface(): + + def __init__(self): + logger.debug("initializing ldap interface") + + self.uri = "ldapi://%2Fvar%2Frun%2Fslapd%2Fldapi" + self.basedn = "dc=yunohost,dc=org" + self.rootdn = "gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" + self.connect() + + def connect(self): + def _reconnect(): + con = ldap.ldapobject.ReconnectLDAPObject( + self.uri, retry_max=10, retry_delay=0.5 + ) + con.sasl_non_interactive_bind_s("EXTERNAL") + return con + + try: + con = _reconnect() + except ldap.SERVER_DOWN: + # ldap is down, attempt to restart it before really failing + logger.warning(m18n.g("ldap_server_is_down_restart_it")) + os.system("systemctl restart slapd") + time.sleep(10) # waits 10 secondes so we are sure that slapd has restarted + try: + con = _reconnect() + except ldap.SERVER_DOWN: + 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'" + ) + + # Check that we are indeed logged in with the right identity + try: + # whoami_s return dn:..., then delete these 3 characters + who = con.whoami_s()[3:] + except Exception as e: + logger.warning("Error during ldap authentication process: %s", e) + raise + else: + if who != self.rootdn: + raise MoulinetteError("Not logged in with the expected userdn ?!") + else: + self.con = con + + def __del__(self): + """Disconnect and free ressources""" + if hasattr(self, "con") and self.con: + self.con.unbind_s() + + def search(self, base=None, filter="(objectClass=*)", attrs=["dn"]): + """Search in LDAP base + + Perform an LDAP search operation with given arguments and return + results as a list. + + Keyword arguments: + - base -- The dn to search into + - filter -- A string representation of the filter to apply + - attrs -- A list of attributes to fetch + + Returns: + A list of all results + + """ + if not base: + base = self.basedn + + try: + result = self.con.search_s(base, ldap.SCOPE_SUBTREE, filter, attrs) + except Exception as e: + raise MoulinetteError( + "error during LDAP search operation with: base='%s', " + "filter='%s', attrs=%s and exception %s" % (base, filter, attrs, e), + raw_msg=True, + ) + + result_list = [] + if not attrs or "dn" not in attrs: + result_list = [entry for dn, entry in result] + else: + for dn, entry in result: + entry["dn"] = [dn] + result_list.append(entry) + + def decode(value): + if isinstance(value, bytes): + value = value.decode("utf-8") + return value + + # result_list is for example : + # [{'virtualdomain': [b'test.com']}, {'virtualdomain': [b'yolo.test']}, + for stuff in result_list: + if isinstance(stuff, dict): + for key, values in stuff.items(): + stuff[key] = [decode(v) for v in values] + + return result_list + + def add(self, rdn, attr_dict): + """ + Add LDAP entry + + Keyword arguments: + rdn -- DN without domain + attr_dict -- Dictionnary of attributes/values to add + + Returns: + Boolean | MoulinetteError + + """ + dn = rdn + "," + self.basedn + ldif = modlist.addModlist(attr_dict) + for i, (k, v) in enumerate(ldif): + if isinstance(v, list): + v = [a.encode("utf-8") for a in v] + elif isinstance(v, str): + v = [v.encode("utf-8")] + ldif[i] = (k, v) + + try: + self.con.add_s(dn, ldif) + except Exception as e: + raise MoulinetteError( + "error during LDAP add operation with: rdn='%s', " + "attr_dict=%s and exception %s" % (rdn, attr_dict, e), + raw_msg=True, + ) + else: + return True + + def remove(self, rdn): + """ + Remove LDAP entry + + Keyword arguments: + rdn -- DN without domain + + Returns: + Boolean | MoulinetteError + + """ + dn = rdn + "," + self.basedn + try: + self.con.delete_s(dn) + except Exception as e: + raise MoulinetteError( + "error during LDAP delete operation with: rdn='%s' and exception %s" + % (rdn, e), + raw_msg=True, + ) + else: + return True + + def update(self, rdn, attr_dict, new_rdn=False): + """ + Modify LDAP entry + + Keyword arguments: + rdn -- DN without domain + attr_dict -- Dictionnary of attributes/values to add + new_rdn -- New RDN for modification + + Returns: + Boolean | MoulinetteError + + """ + dn = rdn + "," + self.basedn + actual_entry = self.search(base=dn, attrs=None) + ldif = modlist.modifyModlist(actual_entry[0], attr_dict, ignore_oldexistent=1) + + if ldif == []: + logger.debug("Nothing to update in LDAP") + return True + + try: + if new_rdn: + self.con.rename_s(dn, new_rdn) + new_base = dn.split(",", 1)[1] + dn = new_rdn + "," + new_base + + for i, (a, k, vs) in enumerate(ldif): + if isinstance(vs, list): + vs = [v.encode("utf-8") for v in vs] + elif isinstance(vs, str): + vs = [vs.encode("utf-8")] + ldif[i] = (a, k, vs) + + self.con.modify_ext_s(dn, ldif) + except Exception as e: + raise MoulinetteError( + "error during LDAP update operation with: rdn='%s', " + "attr_dict=%s, new_rdn=%s and exception: %s" + % (rdn, attr_dict, new_rdn, e), + raw_msg=True, + ) + else: + return True + + def validate_uniqueness(self, value_dict): + """ + Check uniqueness of values + + Keyword arguments: + value_dict -- Dictionnary of attributes/values to check + + Returns: + Boolean | MoulinetteError + + """ + attr_found = self.get_conflict(value_dict) + if attr_found: + logger.info( + "attribute '%s' with value '%s' is not unique", + attr_found[0], + attr_found[1], + ) + raise MoulinetteError( + "ldap_attribute_already_exists", + attribute=attr_found[0], + value=attr_found[1], + ) + return True + + def get_conflict(self, value_dict, base_dn=None): + """ + Check uniqueness of values + + Keyword arguments: + value_dict -- Dictionnary of attributes/values to check + + Returns: + None | tuple with Fist conflict attribute name and value + + """ + for attr, value in value_dict.items(): + if not self.search(base=base_dn, filter=attr + "=" + value): + continue + else: + return (attr, value) + return None From 78cc445bd28bda590029270ba062da7360f49c10 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 9 Mar 2021 06:00:04 +0100 Subject: [PATCH 2223/3170] Typo --- src/yunohost/authenticators/ldap_admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/authenticators/ldap_admin.py b/src/yunohost/authenticators/ldap_admin.py index d7a1dadda..dcecae88f 100644 --- a/src/yunohost/authenticators/ldap_admin.py +++ b/src/yunohost/authenticators/ldap_admin.py @@ -12,7 +12,7 @@ from moulinette.core import MoulinetteError from moulinette.authentication import BaseAuthenticator from yunohost.utils.error import YunohostError -logger = logging.getLogger("yunohost.authenticators.lpda_admin") +logger = logging.getLogger("yunohost.authenticators.ldap_admin") class Authenticator(BaseAuthenticator): From 9a8cbbd88369b959fed26d7de6ebca5858a863c0 Mon Sep 17 00:00:00 2001 From: Paco Date: Tue, 9 Mar 2021 21:34:30 +0100 Subject: [PATCH 2224/3170] sample _get_domain_and_subdomains_settings() --- src/yunohost/domain.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index cc9980549..1b1136a1b 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -45,6 +45,7 @@ from yunohost.hook import hook_callback logger = getActionLogger("yunohost.domain") +DOMAIN_SETTINGS_PATH = "/etc/yunohost/domains/" def domain_list(exclude_subdomains=False): """ @@ -657,3 +658,25 @@ def _get_DKIM(domain): p=dkim.group("p"), ), ) + + +def _get_domain_and_subdomains_settings(domain): + """ + Give data about a domain and its subdomains + """ + return { + "cmercier.fr" : { + "main": true, + "xmpp": true, + "mail": true, + "owned_dns_zone": true, + "ttl": 3600, + }, + "node.cmercier.fr" : { + "main": false, + "xmpp": false, + "mail": false, + "ttl": 3600, + }, + } + From c111b9c6c2b682856d23cfec1463fc82a5105d14 Mon Sep 17 00:00:00 2001 From: MercierCorentin Date: Tue, 9 Mar 2021 22:57:46 +0100 Subject: [PATCH 2225/3170] First implementation of configurable dns conf generation --- data/actionsmap/yunohost.yml | 7 -- src/yunohost/domain.py | 146 ++++++++++++++++++++--------------- 2 files changed, 83 insertions(+), 70 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 33b8b5cfe..ced353b75 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -467,13 +467,6 @@ domain: arguments: domain: help: Target domain - -t: - full: --ttl - help: Time To Live (TTL) in second before DNS servers update. Default is 3600 seconds (i.e. 1 hour). - extra: - pattern: - - !!str ^[0-9]+$ - - "pattern_positive_number" ### domain_maindomain() main-domain: diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 1b1136a1b..c62118a5a 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -25,6 +25,7 @@ """ import os import re +import sys from moulinette import m18n, msettings, msignals from moulinette.core import MoulinetteError @@ -275,22 +276,21 @@ def domain_remove(operation_logger, domain, remove_apps=False, force=False): logger.success(m18n.n("domain_deleted")) -def domain_dns_conf(domain, ttl=None): +def domain_dns_conf(domain): """ Generate DNS configuration for a domain Keyword argument: domain -- Domain name - ttl -- Time to live """ if domain not in domain_list()["domains"]: raise YunohostError("domain_name_unknown", domain=domain) - ttl = 3600 if ttl is None else ttl + domains_settings = _get_domain_and_subdomains_settings(domain) - dns_conf = _build_dns_conf(domain, ttl) + dns_conf = _build_dns_conf(domains_settings) result = "" @@ -411,7 +411,7 @@ def _get_maindomain(): return maindomain -def _build_dns_conf(domain, ttl=3600, include_empty_AAAA_if_no_ipv6=False): +def _build_dns_conf(domains): """ Internal function that will returns a data structure containing the needed information to generate/adapt the dns configuration @@ -451,72 +451,92 @@ def _build_dns_conf(domain, ttl=3600, include_empty_AAAA_if_no_ipv6=False): } """ + root = min(domains.keys(), key=(lambda k: len(k))) + + basic = [] + mail = [] + xmpp = [] + extra = [] ipv4 = get_public_ip() ipv6 = get_public_ip(6) - ########################### - # Basic ipv4/ipv6 records # - ########################### + name_prefix = root.partition(".")[0] - basic = [] - if ipv4: - basic.append(["@", ttl, "A", ipv4]) - if ipv6: - basic.append(["@", ttl, "AAAA", ipv6]) - elif include_empty_AAAA_if_no_ipv6: - basic.append(["@", ttl, "AAAA", None]) + for domain_name, domain in domains.items(): + print(domain_name) + ttl = domain["ttl"] - ######### - # Email # - ######### + owned_dns_zone = "owned_dns_zone" in domains[root] and domains[root]["owned_dns_zone"] == True + if domain_name == root: + name = name_prefix if not owned_dns_zone else "@" + else: + name = domain_name[0:-(1 + len(root))] + if not owned_dns_zone: + name += "." + name_prefix - mail = [ - ["@", ttl, "MX", "10 %s." % domain], - ["@", ttl, "TXT", '"v=spf1 a mx -all"'], - ] + ########################### + # Basic ipv4/ipv6 records # + ########################### + if ipv4: + basic.append([name, ttl, "A", ipv4]) - # DKIM/DMARC record - dkim_host, dkim_publickey = _get_DKIM(domain) + if ipv6: + basic.append([name, ttl, "AAAA", ipv6]) + # TODO + # elif include_empty_AAAA_if_no_ipv6: + # basic.append(["@", ttl, "AAAA", None]) - if dkim_host: - mail += [ - [dkim_host, ttl, "TXT", dkim_publickey], - ["_dmarc", ttl, "TXT", '"v=DMARC1; p=none"'], - ] + ######### + # Email # + ######### + if domain["mail"] == True: - ######## - # XMPP # - ######## + mail += [ + [name, ttl, "MX", "10 %s." % domain], + [name, ttl, "TXT", '"v=spf1 a mx -all"'], + ] - xmpp = [ - ["_xmpp-client._tcp", ttl, "SRV", "0 5 5222 %s." % domain], - ["_xmpp-server._tcp", ttl, "SRV", "0 5 5269 %s." % domain], - ["muc", ttl, "CNAME", "@"], - ["pubsub", ttl, "CNAME", "@"], - ["vjud", ttl, "CNAME", "@"], - ["xmpp-upload", ttl, "CNAME", "@"], - ] + # DKIM/DMARC record + dkim_host, dkim_publickey = _get_DKIM(domain) - ######### - # Extra # - ######### + if dkim_host: + mail += [ + [dkim_host, ttl, "TXT", dkim_publickey], + ["_dmarc", ttl, "TXT", '"v=DMARC1; p=none"'], + ] - extra = [] + ######## + # XMPP # + ######## + if domain["xmpp"] == True: + xmpp += [ + ["_xmpp-client._tcp", ttl, "SRV", "0 5 5222 %s." % domain_name], + ["_xmpp-server._tcp", ttl, "SRV", "0 5 5269 %s." % domain_name], + ["muc", ttl, "CNAME", name], + ["pubsub", ttl, "CNAME", name], + ["vjud", ttl, "CNAME", name], + ["xmpp-upload", ttl, "CNAME", name], + ] - if ipv4: - extra.append(["*", ttl, "A", ipv4]) + ######### + # Extra # + ######### - if ipv6: - extra.append(["*", ttl, "AAAA", ipv6]) - elif include_empty_AAAA_if_no_ipv6: - extra.append(["*", ttl, "AAAA", None]) - extra.append(["@", ttl, "CAA", '128 issue "letsencrypt.org"']) + if ipv4: + extra.append(["*", ttl, "A", ipv4]) - #################### - # Standard records # - #################### + if ipv6: + extra.append(["*", ttl, "AAAA", ipv6]) + elif include_empty_AAAA_if_no_ipv6: + extra.append(["*", ttl, "AAAA", None]) + + extra.append([name, ttl, "CAA", '128 issue "letsencrypt.org"']) + + #################### + # Standard records # + #################### records = { "basic": [ @@ -665,17 +685,17 @@ def _get_domain_and_subdomains_settings(domain): Give data about a domain and its subdomains """ return { - "cmercier.fr" : { - "main": true, - "xmpp": true, - "mail": true, - "owned_dns_zone": true, + "node.cmercier.fr" : { + "main": True, + "xmpp": True, + "mail": True, + "owned_dns_zone": True, "ttl": 3600, }, - "node.cmercier.fr" : { - "main": false, - "xmpp": false, - "mail": false, + "sub.node.cmercier.fr" : { + "main": False, + "xmpp": True, + "mail": False, "ttl": 3600, }, } From 1a4d02c9bf14c397b013890ff45f7648ccef8a97 Mon Sep 17 00:00:00 2001 From: Paco Date: Tue, 9 Mar 2021 23:53:50 +0100 Subject: [PATCH 2226/3170] Add loading of domain settings. Generate defaults for missing entries (and backward-compability) --- data/actionsmap/yunohost.yml | 6 +++ src/yunohost/domain.py | 73 +++++++++++++++++++++++++++++++++++- 2 files changed, 77 insertions(+), 2 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 33b8b5cfe..b2579be29 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -460,6 +460,12 @@ domain: help: Do not ask confirmation to remove apps action: store_true + settings: + action_help: Get settings for a domain + api: GET /domains//settings + arguments: + domain: + help: Target domain ### domain_dns_conf() dns-conf: action_help: Generate sample DNS configuration for a domain diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 1b1136a1b..d32b5954c 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -45,7 +45,7 @@ from yunohost.hook import hook_callback logger = getActionLogger("yunohost.domain") -DOMAIN_SETTINGS_PATH = "/etc/yunohost/domains/" +DOMAIN_SETTINGS_PATH = "/etc/yunohost/domains.yml" def domain_list(exclude_subdomains=False): """ @@ -661,7 +661,7 @@ def _get_DKIM(domain): def _get_domain_and_subdomains_settings(domain): - """ + """ Give data about a domain and its subdomains """ return { @@ -680,3 +680,72 @@ def _get_domain_and_subdomains_settings(domain): }, } +def _load_domain_settings(): + """ + Retrieve entries in domains.yml + And fill the holes if any + """ + # Retrieve entries in the YAML + if os.path.exists(DOMAIN_SETTINGS_PATH) and os.path.isfile(DOMAIN_SETTINGS_PATH): + old_domains = yaml.load(open(DOMAIN_SETTINGS_PATH, "r+")) + else: + old_domains = dict() + + # Create sanitized data + new_domains = dict() + + get_domain_list = domain_list() + + # Load main domain + maindomain = get_domain_list["main"] + + for domain in get_domain_list["domains"]: + # Update each setting if not present + new_domains[domain] = { + # Set "main" value + "main": True if domain == maindomain else False + } + # Set other values (default value if missing) + for setting, default in [ ("xmpp", True), ("mail", True), ("owned_dns_zone", True), ("ttl", 3600) ]: + if domain in old_domains.keys() and setting in old_domains[domain].keys(): + new_domains[domain][setting] = old_domains[domain][setting] + else: + new_domains[domain][setting] = default + + return new_domains + + +def domain_settings(domain): + """ + Get settings of a domain + + Keyword arguments: + domain -- The domain name + """ + return _get_domain_settings(domain, False) + +def _get_domain_settings(domain, subdomains): + """ + Get settings of a domain + + Keyword arguments: + domain -- The domain name + subdomains -- Do we include the subdomains? Default is False + + """ + domains = _load_domain_settings() + if not domain in domains.keys(): + return {} + + only_wanted_domains = dict() + for entry in domains.keys(): + if subdomains: + if domain in entry: + only_wanted_domains[entry] = domains[entry] + else: + if domain == entry: + only_wanted_domains[entry] = domains[entry] + + + return only_wanted_domains + From 8dd5859a46cba3e202a4c627f80edf85b348aa7c Mon Sep 17 00:00:00 2001 From: Paco Date: Tue, 9 Mar 2021 23:59:42 +0100 Subject: [PATCH 2227/3170] Integration of domain settings loading/generation with domains DNS entries generation --- src/yunohost/domain.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 986bcb826..b1052fdbb 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -288,7 +288,7 @@ def domain_dns_conf(domain): if domain not in domain_list()["domains"]: raise YunohostError("domain_name_unknown", domain=domain) - domains_settings = _get_domain_and_subdomains_settings(domain) + domains_settings = _get_domain_settings(domain, True) dns_conf = _build_dns_conf(domains_settings) @@ -464,7 +464,6 @@ def _build_dns_conf(domains): for domain_name, domain in domains.items(): - print(domain_name) ttl = domain["ttl"] owned_dns_zone = "owned_dns_zone" in domains[root] and domains[root]["owned_dns_zone"] == True @@ -493,7 +492,7 @@ def _build_dns_conf(domains): if domain["mail"] == True: mail += [ - [name, ttl, "MX", "10 %s." % domain], + [name, ttl, "MX", "10 %s." % domain_name], [name, ttl, "TXT", '"v=spf1 a mx -all"'], ] From e639c8cd5ab8a26b853bae9df9c0c210135d682f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 10 Mar 2021 19:00:24 +0100 Subject: [PATCH 2228/3170] Move applist refresh cron + lets encrypt renewal cron to regen conf --- data/hooks/conf_regen/01-yunohost | 20 +++++++++++++++++- src/yunohost/app.py | 14 ------------- src/yunohost/certificate.py | 28 -------------------------- src/yunohost/tests/test_appscatalog.py | 16 --------------- 4 files changed, 19 insertions(+), 59 deletions(-) diff --git a/data/hooks/conf_regen/01-yunohost b/data/hooks/conf_regen/01-yunohost index 8b7a7c6fc..2d162e738 100755 --- a/data/hooks/conf_regen/01-yunohost +++ b/data/hooks/conf_regen/01-yunohost @@ -77,13 +77,27 @@ do_pre_regen() { cp services.yml /etc/yunohost/services.yml fi + mkdir -p $pending_dir/etc/cron.d/ + mkdir -p $pending_dir/etc/cron.daily/ + # add cron job for diagnosis to be ran at 7h and 19h + a random delay between # 0 and 20min, meant to avoid every instances running their diagnosis at # exactly the same time, which may overload the diagnosis server. - mkdir -p $pending_dir/etc/cron.d/ cat > $pending_dir/etc/cron.d/yunohost-diagnosis << EOF SHELL=/bin/bash 0 7,19 * * * root : YunoHost Automatic Diagnosis; sleep \$((RANDOM\\%1200)); yunohost diagnosis run --email > /dev/null 2>/dev/null || echo "Running the automatic diagnosis failed miserably" +EOF + + # Cron job that upgrade the app list everyday + cat > $pending_dir/etc/cron.daily/yunohost-fetch-apps-catalog << EOF +#!/bin/bash +(sleep \$((RANDOM%3600)); yunohost tools update --apps > /dev/null) & +EOF + + # Cron job that renew lets encrypt certificates if there's any that needs renewal + cat > $pending_dir/etc/cron.daily/yunohost-certificate-renew << EOF +#!/bin/bash +yunohost domain cert-renew --email EOF # If we subscribed to a dyndns domain, add the corresponding cron @@ -137,6 +151,10 @@ do_post_regen() { find /etc/yunohost/certs/ -type f -exec chmod 640 {} \; find /etc/yunohost/certs/ -type d -exec chmod 750 {} \; + find /etc/cron.*/yunohost-* -type f -exec chmod 755 {} \; + find /etc/cron.d/yunohost-* -type f -exec chmod 644 {} \; + find /etc/cron.*/yunohost-* -type f -exec chmod root:root {} \; + # 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) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 3d1d16f3c..28d8681e6 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -66,7 +66,6 @@ APP_TMP_FOLDER = INSTALL_TMP + "/from_file" APPS_CATALOG_CACHE = "/var/cache/yunohost/repo" APPS_CATALOG_CONF = "/etc/yunohost/apps_catalog.yml" -APPS_CATALOG_CRON_PATH = "/etc/cron.daily/yunohost-fetch-apps-catalog" APPS_CATALOG_API_VERSION = 2 APPS_CATALOG_DEFAULT_URL = "https://app.yunohost.org/default" @@ -3232,28 +3231,15 @@ def _parse_app_instance_name(app_instance_name): def _initialize_apps_catalog_system(): """ This function is meant to intialize the apps_catalog system with YunoHost's default app catalog. - - It also creates the cron job that will update the list every day """ default_apps_catalog_list = [{"id": "default", "url": APPS_CATALOG_DEFAULT_URL}] - cron_job = [] - cron_job.append("#!/bin/bash") - # We add a random delay between 0 and 60 min to avoid every instance fetching - # the apps catalog at the same time every night - cron_job.append("(sleep $((RANDOM%3600));") - cron_job.append("yunohost tools update --apps > /dev/null) &") try: logger.debug( "Initializing apps catalog system with YunoHost's default app list" ) write_to_yaml(APPS_CATALOG_CONF, default_apps_catalog_list) - - logger.debug("Installing apps catalog fetch daily cron job") - write_to_file(APPS_CATALOG_CRON_PATH, "\n".join(cron_job)) - chown(APPS_CATALOG_CRON_PATH, uid="root", gid="root") - chmod(APPS_CATALOG_CRON_PATH, 0o755) except Exception as e: raise YunohostError( "Could not initialize the apps catalog system... : %s" % str(e) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index c48af2c07..43fe2af14 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -315,8 +315,6 @@ def _certificate_install_letsencrypt( % domain ) else: - _install_cron(no_checks=no_checks) - logger.success(m18n.n("certmanager_cert_install_success", domain=domain)) operation_logger.success() @@ -455,32 +453,6 @@ def certificate_renew( # Back-end stuff # # - -def _install_cron(no_checks=False): - cron_job_file = "/etc/cron.daily/yunohost-certificate-renew" - - # we need to check if "--no-checks" isn't already put inside the existing - # crontab, if it's the case it's probably because another domain needed it - # at some point so we keep it - if not no_checks and os.path.exists(cron_job_file): - with open(cron_job_file, "r") as f: - # no the best test in the world but except if we uses a shell - # script parser I'm not expected a much more better way to do that - no_checks = "--no-checks" in f.read() - - command = "yunohost domain cert-renew --email\n" - - if no_checks: - # handle trailing "\n with ":-1" - command = command[:-1] + " --no-checks\n" - - with open(cron_job_file, "w") as f: - f.write("#!/bin/bash\n") - f.write(command) - - _set_permissions(cron_job_file, "root", "root", 0o755) - - def _email_renewing_failed(domain, exception_message, stack=""): from_ = "certmanager@%s (Certificate Manager)" % domain to_ = "root" diff --git a/src/yunohost/tests/test_appscatalog.py b/src/yunohost/tests/test_appscatalog.py index e3bd5d49d..a2619a660 100644 --- a/src/yunohost/tests/test_appscatalog.py +++ b/src/yunohost/tests/test_appscatalog.py @@ -19,13 +19,11 @@ from yunohost.app import ( logger, APPS_CATALOG_CACHE, APPS_CATALOG_CONF, - APPS_CATALOG_CRON_PATH, APPS_CATALOG_API_VERSION, APPS_CATALOG_DEFAULT_URL, ) APPS_CATALOG_DEFAULT_URL_FULL = _actual_apps_catalog_api_url(APPS_CATALOG_DEFAULT_URL) -CRON_FOLDER, CRON_NAME = APPS_CATALOG_CRON_PATH.rsplit("/", 1) DUMMY_APP_CATALOG = """{ "apps": { @@ -50,10 +48,6 @@ def setup_function(function): # Clear apps catalog cache shutil.rmtree(APPS_CATALOG_CACHE, ignore_errors=True) - # Clear apps_catalog cron - if os.path.exists(APPS_CATALOG_CRON_PATH): - os.remove(APPS_CATALOG_CRON_PATH) - # Clear apps_catalog conf if os.path.exists(APPS_CATALOG_CONF): os.remove(APPS_CATALOG_CONF) @@ -67,11 +61,6 @@ def teardown_function(function): shutil.rmtree(APPS_CATALOG_CACHE, ignore_errors=True) -def cron_job_is_there(): - r = os.system("run-parts -v --test %s | grep %s" % (CRON_FOLDER, CRON_NAME)) - return r == 0 - - # # ################################################ # @@ -83,17 +72,12 @@ def test_apps_catalog_init(mocker): assert not glob.glob(APPS_CATALOG_CACHE + "/*") # Conf doesn't exist yet assert not os.path.exists(APPS_CATALOG_CONF) - # Conf doesn't exist yet - assert not os.path.exists(APPS_CATALOG_CRON_PATH) # Initialize ... mocker.spy(m18n, "n") _initialize_apps_catalog_system() m18n.n.assert_any_call("apps_catalog_init_success") - # Then there's a cron enabled - assert cron_job_is_there() - # And a conf with at least one list assert os.path.exists(APPS_CATALOG_CONF) apps_catalog_list = _read_apps_catalog_list() From 6f3df383d2c8269b2eddc866f5b1b6cd005e5836 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 10 Mar 2021 19:06:26 +0100 Subject: [PATCH 2229/3170] Don't backup/restore crons >_> --- data/hooks/backup/32-conf_cron | 15 --------------- data/hooks/restore/32-conf_cron | 6 ------ 2 files changed, 21 deletions(-) delete mode 100755 data/hooks/backup/32-conf_cron delete mode 100644 data/hooks/restore/32-conf_cron diff --git a/data/hooks/backup/32-conf_cron b/data/hooks/backup/32-conf_cron deleted file mode 100755 index acbd009ab..000000000 --- a/data/hooks/backup/32-conf_cron +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash - -# Exit hook on subcommand error or unset variable -set -eu - -# Source YNH helpers -source /usr/share/yunohost/helpers - -# Backup destination -backup_dir="${1}/conf/cron" - -# Backup the configuration -for f in $(ls -1B /etc/cron.d/yunohost* 2> /dev/null); do - ynh_backup "$f" "${backup_dir}/${f##*/}" -done diff --git a/data/hooks/restore/32-conf_cron b/data/hooks/restore/32-conf_cron deleted file mode 100644 index 59a2bde61..000000000 --- a/data/hooks/restore/32-conf_cron +++ /dev/null @@ -1,6 +0,0 @@ -backup_dir="$1/conf/cron" - -cp -a $backup_dir/. /etc/cron.d - -# Restart just in case -service cron restart From 727e135c728666a6e64b010d8051d050346faaa6 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 10 Mar 2021 19:39:45 +0100 Subject: [PATCH 2230/3170] Unused import --- src/yunohost/authenticators/ldap_admin.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/yunohost/authenticators/ldap_admin.py b/src/yunohost/authenticators/ldap_admin.py index dcecae88f..734148536 100644 --- a/src/yunohost/authenticators/ldap_admin.py +++ b/src/yunohost/authenticators/ldap_admin.py @@ -5,7 +5,6 @@ import logging import ldap import ldap.sasl import time -import ldap.modlist as modlist from moulinette import m18n from moulinette.core import MoulinetteError From 01ccab52529a55e8d40fe3676f2d7023c112aef1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 11 Mar 2021 01:39:52 +0100 Subject: [PATCH 2231/3170] Add semantic of YunohostValidationError for all exceptions which are related to validating stuff --- src/yunohost/app.py | 74 +++++++++---------- src/yunohost/backup.py | 28 +++---- src/yunohost/certificate.py | 24 +++--- .../0017_postgresql_9p6_to_11.py | 4 +- src/yunohost/diagnosis.py | 18 ++--- src/yunohost/domain.py | 29 ++++---- src/yunohost/dyndns.py | 12 +-- src/yunohost/firewall.py | 4 +- src/yunohost/hook.py | 8 +- src/yunohost/log.py | 2 +- src/yunohost/permission.py | 24 +++--- src/yunohost/service.py | 12 +-- src/yunohost/settings.py | 22 +++--- src/yunohost/ssh.py | 6 +- src/yunohost/tools.py | 36 ++++----- src/yunohost/user.py | 52 ++++++------- src/yunohost/utils/error.py | 4 + src/yunohost/utils/password.py | 4 +- tests/test_i18n_keys.py | 2 + 19 files changed, 187 insertions(+), 178 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 3d1d16f3c..613ca21df 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -54,7 +54,7 @@ from moulinette.utils.filesystem import ( from yunohost.service import service_status, _run_service_command from yunohost.utils import packages -from yunohost.utils.error import YunohostError +from yunohost.utils.error import YunohostError, YunohostValidationError from yunohost.log import is_unit_operation, OperationLogger logger = getActionLogger("yunohost.app") @@ -192,7 +192,7 @@ def app_info(app, full=False): from yunohost.permission import user_permission_list if not _is_installed(app): - raise YunohostError( + raise YunohostValidationError( "app_not_installed", app=app, all_apps=_get_all_installed_apps_id() ) @@ -321,7 +321,7 @@ def app_map(app=None, raw=False, user=None): if app is not None: if not _is_installed(app): - raise YunohostError( + raise YunohostValidationError( "app_not_installed", app=app, all_apps=_get_all_installed_apps_id() ) apps = [ @@ -421,14 +421,14 @@ def app_change_url(operation_logger, app, domain, path): installed = _is_installed(app) if not installed: - raise YunohostError( + raise YunohostValidationError( "app_not_installed", app=app, all_apps=_get_all_installed_apps_id() ) if not os.path.exists( os.path.join(APPS_SETTING_PATH, app, "scripts", "change_url") ): - raise YunohostError("app_change_url_no_script", app_name=app) + raise YunohostValidationError("app_change_url_no_script", app_name=app) old_domain = app_setting(app, "domain") old_path = app_setting(app, "path") @@ -438,7 +438,7 @@ def app_change_url(operation_logger, app, domain, path): domain, path = _normalize_domain_path(domain, path) if (domain, path) == (old_domain, old_path): - raise YunohostError( + raise YunohostValidationError( "app_change_url_identical_domains", domain=domain, path=path ) @@ -551,12 +551,12 @@ def app_upgrade(app=[], url=None, file=None, force=False): # Abort if any of those app is in fact not installed.. for app in [app_ for app_ in apps if not _is_installed(app_)]: - raise YunohostError( + raise YunohostValidationError( "app_not_installed", app=app, all_apps=_get_all_installed_apps_id() ) if len(apps) == 0: - raise YunohostError("apps_already_up_to_date") + raise YunohostValidationError("apps_already_up_to_date") if len(apps) > 1: logger.info(m18n.n("app_upgrade_several_apps", apps=", ".join(apps))) @@ -880,11 +880,11 @@ def app_install( confirm_install("thirdparty") manifest, extracted_app_folder = _extract_app_from_file(app) else: - raise YunohostError("app_unknown") + raise YunohostValidationError("app_unknown") # Check ID if "id" not in manifest or "__" in manifest["id"]: - raise YunohostError("app_id_invalid") + raise YunohostValidationError("app_id_invalid") app_id = manifest["id"] label = label if label else manifest["name"] @@ -897,7 +897,7 @@ def app_install( instance_number = _installed_instance_number(app_id, last=True) + 1 if instance_number > 1: if "multi_instance" not in manifest or not is_true(manifest["multi_instance"]): - raise YunohostError("app_already_installed", app=app_id) + raise YunohostValidationError("app_already_installed", app=app_id) # Change app_id to the forked app id app_instance_name = app_id + "__" + str(instance_number) @@ -1209,7 +1209,7 @@ def app_remove(operation_logger, app): ) if not _is_installed(app): - raise YunohostError( + raise YunohostValidationError( "app_not_installed", app=app, all_apps=_get_all_installed_apps_id() ) @@ -1372,10 +1372,10 @@ def app_makedefault(operation_logger, app, domain=None): domain = app_domain operation_logger.related_to.append(("domain", domain)) elif domain not in domain_list()["domains"]: - raise YunohostError("domain_name_unknown", domain=domain) + raise YunohostValidationError("domain_name_unknown", domain=domain) if "/" in app_map(raw=True)[domain]: - raise YunohostError( + raise YunohostValidationError( "app_make_default_location_already_used", app=app, domain=app_domain, @@ -1578,7 +1578,7 @@ def app_register_url(app, domain, path): if _is_installed(app): settings = _get_app_settings(app) if "path" in settings.keys() and "domain" in settings.keys(): - raise YunohostError("app_already_installed_cant_change_url") + raise YunohostValidationError("app_already_installed_cant_change_url") # Check the url is available _assert_no_conflicting_apps(domain, path) @@ -1694,7 +1694,7 @@ def app_change_label(app, new_label): installed = _is_installed(app) if not installed: - raise YunohostError( + raise YunohostValidationError( "app_not_installed", app=app, all_apps=_get_all_installed_apps_id() ) logger.warning(m18n.n("app_label_deprecated")) @@ -1730,7 +1730,7 @@ def app_action_run(operation_logger, app, action, args=None): actions = {x["id"]: x for x in actions} if action not in actions: - raise YunohostError( + raise YunohostValidationError( "action '%s' not available for app '%s', available actions are: %s" % (action, app, ", ".join(actions.keys())), raw_msg=True, @@ -1884,7 +1884,7 @@ def app_config_apply(operation_logger, app, args): installed = _is_installed(app) if not installed: - raise YunohostError( + raise YunohostValidationError( "app_not_installed", app=app, all_apps=_get_all_installed_apps_id() ) @@ -2199,7 +2199,7 @@ def _get_app_settings(app_id): """ if not _is_installed(app_id): - raise YunohostError( + raise YunohostValidationError( "app_not_installed", app=app_id, all_apps=_get_all_installed_apps_id() ) try: @@ -2546,9 +2546,9 @@ def _fetch_app_from_git(app): app_id, _ = _parse_app_instance_name(app) if app_id not in app_dict: - raise YunohostError("app_unknown") + raise YunohostValidationError("app_unknown") elif "git" not in app_dict[app_id]: - raise YunohostError("app_unsupported_remote_type") + raise YunohostValidationError("app_unsupported_remote_type") app_info = app_dict[app_id] url = app_info["git"]["url"] @@ -2684,7 +2684,7 @@ def _check_manifest_requirements(manifest, app_instance_name): packaging_format = int(manifest.get("packaging_format", 0)) if packaging_format not in [0, 1]: - raise YunohostError("app_packaging_format_not_supported") + raise YunohostValidationError("app_packaging_format_not_supported") requirements = manifest.get("requirements", dict()) @@ -2697,7 +2697,7 @@ def _check_manifest_requirements(manifest, app_instance_name): for pkgname, spec in requirements.items(): if not packages.meets_version_specifier(pkgname, spec): version = packages.ynh_packages_version()[pkgname]["version"] - raise YunohostError( + raise YunohostValidationError( "app_requirements_unmeet", pkgname=pkgname, version=version, @@ -2796,7 +2796,7 @@ class YunoHostArgumentFormatParser(object): # we don't have an answer, check optional and default_value if question.value is None or question.value == "": if not question.optional and question.default is None: - raise YunohostError("app_argument_required", name=question.name) + raise YunohostValidationError("app_argument_required", name=question.name) else: question.value = ( getattr(self, "default_value", None) @@ -2816,7 +2816,7 @@ class YunoHostArgumentFormatParser(object): return (question.value, self.argument_type) def _raise_invalid_answer(self, question): - raise YunohostError( + raise YunohostValidationError( "app_argument_choice_invalid", name=question.name, choices=", ".join(question.choices), @@ -2854,13 +2854,13 @@ class PasswordArgumentParser(YunoHostArgumentFormatParser): ) if question.default is not None: - raise YunohostError("app_argument_password_no_default", name=question.name) + raise YunohostValidationError("app_argument_password_no_default", name=question.name) return question def _post_parse_value(self, question): if any(char in question.value for char in self.forbidden_chars): - raise YunohostError( + raise YunohostValidationError( "pattern_password_app", forbidden_chars=self.forbidden_chars ) @@ -2913,7 +2913,7 @@ class BooleanArgumentParser(YunoHostArgumentFormatParser): if str(question.value).lower() in ["0", "no", "n", "false"]: return 0 - raise YunohostError( + raise YunohostValidationError( "app_argument_choice_invalid", name=question.name, choices="yes, no, y, n, 1, 0", @@ -2938,7 +2938,7 @@ class DomainArgumentParser(YunoHostArgumentFormatParser): return question def _raise_invalid_answer(self, question): - raise YunohostError( + raise YunohostValidationError( "app_argument_invalid", name=question.name, error=m18n.n("domain_unknown") ) @@ -2964,7 +2964,7 @@ class UserArgumentParser(YunoHostArgumentFormatParser): return question def _raise_invalid_answer(self, question): - raise YunohostError( + raise YunohostValidationError( "app_argument_invalid", name=question.name, error=m18n.n("user_unknown", user=question.value), @@ -2992,7 +2992,7 @@ class NumberArgumentParser(YunoHostArgumentFormatParser): if isinstance(question.value, str) and question.value.isdigit(): return int(question.value) - raise YunohostError( + raise YunohostValidationError( "app_argument_invalid", name=question.name, error=m18n.n("invalid_number") ) @@ -3123,7 +3123,7 @@ def _get_conflicting_apps(domain, path, ignore_app=None): # Abort if domain is unknown if domain not in domain_list()["domains"]: - raise YunohostError("domain_name_unknown", domain=domain) + raise YunohostValidationError("domain_name_unknown", domain=domain) # Fetch apps map apps_map = app_map(raw=True) @@ -3162,9 +3162,9 @@ def _assert_no_conflicting_apps(domain, path, ignore_app=None, full_domain=False ) if full_domain: - raise YunohostError("app_full_domain_unavailable", domain=domain) + raise YunohostValidationError("app_full_domain_unavailable", domain=domain) else: - raise YunohostError("app_location_unavailable", apps="\n".join(apps)) + raise YunohostValidationError("app_location_unavailable", apps="\n".join(apps)) def _make_environment_for_app_script(app, args={}, args_prefix="APP_ARG_"): @@ -3469,7 +3469,7 @@ def _assert_system_is_sane_for_app(manifest, when): faulty_services = [s for s in services if service_status(s)["status"] != "running"] if faulty_services: if when == "pre": - raise YunohostError( + raise YunohostValidationError( "app_action_cannot_be_ran_because_required_services_down", services=", ".join(faulty_services), ) @@ -3480,7 +3480,7 @@ def _assert_system_is_sane_for_app(manifest, when): if packages.dpkg_is_broken(): if when == "pre": - raise YunohostError("dpkg_is_broken") + raise YunohostValidationError("dpkg_is_broken") elif when == "post": raise YunohostError("this_action_broke_dpkg") @@ -3659,7 +3659,7 @@ def _patch_legacy_helpers(app_folder): # couldn't patch the deprecated helper in the previous lines. In # that case, abort the install or whichever step is performed if helper in content and infos["important"]: - raise YunohostError( + raise YunohostValidationError( "This app is likely pretty old and uses deprecated / outdated helpers that can't be migrated easily. It can't be installed anymore.", raw_msg=True, ) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 408cd6f15..b020c0f34 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -348,7 +348,7 @@ class BackupManager: # Try to recursively unmount stuff (from a previously failed backup ?) if not _recursive_umount(self.work_dir): - raise YunohostError("backup_output_directory_not_empty") + raise YunohostValidationError("backup_output_directory_not_empty") else: # If umount succeeded, remove the directory (we checked that # we're in /home/yunohost.backup/tmp so that should be okay... @@ -1027,7 +1027,7 @@ class RestoreManager: already_installed = [app for app in to_be_restored if _is_installed(app)] if already_installed != []: if already_installed == to_be_restored: - raise YunohostError( + raise YunohostValidationError( "restore_already_installed_apps", apps=", ".join(already_installed) ) else: @@ -1133,14 +1133,14 @@ class RestoreManager: return True elif free_space > needed_space: # TODO Add --force options to avoid the error raising - raise YunohostError( + raise YunohostValidationError( "restore_may_be_not_enough_disk_space", free_space=free_space, needed_space=needed_space, margin=margin, ) else: - raise YunohostError( + raise YunohostValidationError( "restore_not_enough_disk_space", free_space=free_space, needed_space=needed_space, @@ -1729,7 +1729,7 @@ class BackupMethod(object): free_space, backup_size, ) - raise YunohostError("not_enough_disk_space", path=self.repo) + raise YunohostValidationError("not_enough_disk_space", path=self.repo) def _organize_files(self): """ @@ -2186,7 +2186,7 @@ def backup_create( # Validate there is no archive with the same name if name and name in backup_list()["archives"]: - raise YunohostError("backup_archive_name_exists") + raise YunohostValidationError("backup_archive_name_exists") # By default we backup using the tar method if not methods: @@ -2201,14 +2201,14 @@ def backup_create( r"^/(|(bin|boot|dev|etc|lib|root|run|sbin|sys|usr|var)(|/.*))$", output_directory, ): - raise YunohostError("backup_output_directory_forbidden") + raise YunohostValidationError("backup_output_directory_forbidden") if "copy" in methods: if not output_directory: - raise YunohostError("backup_output_directory_required") + raise YunohostValidationError("backup_output_directory_required") # Check that output directory is empty elif os.path.isdir(output_directory) and os.listdir(output_directory): - raise YunohostError("backup_output_directory_not_empty") + raise YunohostValidationError("backup_output_directory_not_empty") # If no --system or --apps given, backup everything if system is None and apps is None: @@ -2381,7 +2381,7 @@ def backup_download(name): if not os.path.lexists(archive_file): archive_file += ".gz" if not os.path.lexists(archive_file): - raise YunohostError("backup_archive_name_unknown", name=name) + raise YunohostValidationError("backup_archive_name_unknown", name=name) # If symlink, retrieve the real path if os.path.islink(archive_file): @@ -2389,7 +2389,7 @@ def backup_download(name): # Raise exception if link is broken (e.g. on unmounted external storage) if not os.path.exists(archive_file): - raise YunohostError("backup_archive_broken_link", path=archive_file) + raise YunohostValidationError("backup_archive_broken_link", path=archive_file) # We return a raw bottle HTTPresponse (instead of serializable data like # list/dict, ...), which is gonna be picked and used directly by moulinette @@ -2415,7 +2415,7 @@ def backup_info(name, with_details=False, human_readable=False): if not os.path.lexists(archive_file): archive_file += ".gz" if not os.path.lexists(archive_file): - raise YunohostError("backup_archive_name_unknown", name=name) + raise YunohostValidationError("backup_archive_name_unknown", name=name) # If symlink, retrieve the real path if os.path.islink(archive_file): @@ -2423,7 +2423,7 @@ def backup_info(name, with_details=False, human_readable=False): # Raise exception if link is broken (e.g. on unmounted external storage) if not os.path.exists(archive_file): - raise YunohostError("backup_archive_broken_link", path=archive_file) + raise YunohostValidationError("backup_archive_broken_link", path=archive_file) info_file = "%s/%s.info.json" % (ARCHIVES_PATH, name) @@ -2531,7 +2531,7 @@ def backup_delete(name): """ if name not in backup_list()["archives"]: - raise YunohostError("backup_archive_name_unknown", name=name) + raise YunohostValidationError("backup_archive_name_unknown", name=name) hook_callback("pre_backup_delete", args=[name]) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index c48af2c07..56ea70a04 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -37,7 +37,7 @@ from moulinette.utils.log import getActionLogger 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.error import YunohostError, YunohostValidationError from yunohost.utils.network import get_public_ip from yunohost.diagnosis import Diagnoser @@ -90,7 +90,7 @@ def certificate_status(domain_list, full=False): for domain in domain_list: # Is it in Yunohost domain list? if domain not in yunohost_domains_list: - raise YunohostError("domain_name_unknown", domain=domain) + raise YunohostValidationError("domain_name_unknown", domain=domain) certificates = {} @@ -166,7 +166,7 @@ def _certificate_install_selfsigned(domain_list, force=False): status = _get_status(domain) if status["summary"]["code"] in ("good", "great"): - raise YunohostError( + raise YunohostValidationError( "certmanager_attempt_to_replace_valid_cert", domain=domain ) @@ -267,12 +267,12 @@ def _certificate_install_letsencrypt( for domain in domain_list: yunohost_domains_list = yunohost.domain.domain_list()["domains"] if domain not in yunohost_domains_list: - raise YunohostError("domain_name_unknown", domain=domain) + raise YunohostValidationError("domain_name_unknown", domain=domain) # Is it self-signed? status = _get_status(domain) if not force and status["CA_type"]["code"] != "self-signed": - raise YunohostError( + raise YunohostValidationError( "certmanager_domain_cert_not_selfsigned", domain=domain ) @@ -370,25 +370,25 @@ def certificate_renew( # Is it in Yunohost dmomain list? if domain not in yunohost.domain.domain_list()["domains"]: - raise YunohostError("domain_name_unknown", domain=domain) + raise YunohostValidationError("domain_name_unknown", domain=domain) status = _get_status(domain) # Does it expire soon? if status["validity"] > VALIDITY_LIMIT and not force: - raise YunohostError( + raise YunohostValidationError( "certmanager_attempt_to_renew_valid_cert", domain=domain ) # Does it have a Let's Encrypt cert? if status["CA_type"]["code"] != "lets-encrypt": - raise YunohostError( + raise YunohostValidationError( "certmanager_attempt_to_renew_nonLE_cert", domain=domain ) # Check ACME challenge configured for given domain if not _check_acme_challenge_configuration(domain): - raise YunohostError( + raise YunohostValidationError( "certmanager_acme_not_configured_for_domain", domain=domain ) @@ -898,20 +898,20 @@ def _check_domain_is_ready_for_ACME(domain): ) if not dnsrecords or not httpreachable: - raise YunohostError("certmanager_domain_not_diagnosed_yet", domain=domain) + raise YunohostValidationError("certmanager_domain_not_diagnosed_yet", domain=domain) # Check if IP from DNS matches public IP if not dnsrecords.get("status") in [ "SUCCESS", "WARNING", ]: # Warning is for missing IPv6 record which ain't critical for ACME - raise YunohostError( + raise YunohostValidationError( "certmanager_domain_dns_ip_differs_from_public_ip", domain=domain ) # Check if domain seems to be accessible through HTTP? if not httpreachable.get("status") == "SUCCESS": - raise YunohostError("certmanager_domain_http_not_working", domain=domain) + raise YunohostValidationError("certmanager_domain_http_not_working", domain=domain) # FIXME / TODO : ideally this should not be needed. There should be a proper diff --git a/src/yunohost/data_migrations/0017_postgresql_9p6_to_11.py b/src/yunohost/data_migrations/0017_postgresql_9p6_to_11.py index 728ae443f..0526c025d 100644 --- a/src/yunohost/data_migrations/0017_postgresql_9p6_to_11.py +++ b/src/yunohost/data_migrations/0017_postgresql_9p6_to_11.py @@ -23,7 +23,7 @@ class MyMigration(Migration): return if not self.package_is_installed("postgresql-11"): - raise YunohostError("migration_0017_postgresql_11_not_installed") + raise YunohostValidationError("migration_0017_postgresql_11_not_installed") # Make sure there's a 9.6 cluster try: @@ -37,7 +37,7 @@ class MyMigration(Migration): if not space_used_by_directory( "/var/lib/postgresql/9.6" ) > free_space_in_directory("/var/lib/postgresql"): - raise YunohostError( + raise YunohostValidationError( "migration_0017_not_enough_space", path="/var/lib/postgresql/" ) diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index d01d56613..cc0035755 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -37,7 +37,7 @@ from moulinette.utils.filesystem import ( write_to_yaml, ) -from yunohost.utils.error import YunohostError +from yunohost.utils.error import YunohostError, YunohostValidationError from yunohost.hook import hook_list, hook_exec logger = log.getActionLogger("yunohost.diagnosis") @@ -59,11 +59,11 @@ def diagnosis_get(category, item): all_categories_names = [c for c, _ in all_categories] if category not in all_categories_names: - raise YunohostError("diagnosis_unknown_categories", categories=category) + raise YunohostValidationError("diagnosis_unknown_categories", categories=category) if isinstance(item, list): if any("=" not in criteria for criteria in item): - raise YunohostError( + raise YunohostValidationError( "Criterias should be of the form key=value (e.g. domain=yolo.test)" ) @@ -91,7 +91,7 @@ def diagnosis_show( else: unknown_categories = [c for c in categories if c not in all_categories_names] if unknown_categories: - raise YunohostError( + raise YunohostValidationError( "diagnosis_unknown_categories", categories=", ".join(unknown_categories) ) @@ -181,7 +181,7 @@ def diagnosis_run( else: unknown_categories = [c for c in categories if c not in all_categories_names] if unknown_categories: - raise YunohostError( + raise YunohostValidationError( "diagnosis_unknown_categories", categories=", ".join(unknown_categories) ) @@ -270,14 +270,14 @@ def diagnosis_ignore(add_filter=None, remove_filter=None, list=False): # Sanity checks for the provided arguments if len(filter_) == 0: - raise YunohostError( + raise YunohostValidationError( "You should provide at least one criteria being the diagnosis category to ignore" ) category = filter_[0] if category not in all_categories_names: - raise YunohostError("%s is not a diagnosis category" % category) + raise YunohostValidationError("%s is not a diagnosis category" % category) if any("=" not in criteria for criteria in filter_[1:]): - raise YunohostError( + raise YunohostValidationError( "Criterias should be of the form key=value (e.g. domain=yolo.test)" ) @@ -331,7 +331,7 @@ def diagnosis_ignore(add_filter=None, remove_filter=None, list=False): configuration["ignore_filters"][category] = [] if criterias not in configuration["ignore_filters"][category]: - raise YunohostError("This filter does not exists.") + raise YunohostValidationError("This filter does not exists.") configuration["ignore_filters"][category].remove(criterias) _diagnosis_write_configuration(configuration) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 1198ef473..fdf247f89 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -101,16 +101,14 @@ def domain_add(operation_logger, domain, dyndns=False): from yunohost.utils.ldap import _get_ldap_interface if domain.startswith("xmpp-upload."): - raise YunohostError("domain_cannot_add_xmpp_upload") + raise YunohostValidationError("domain_cannot_add_xmpp_upload") ldap = _get_ldap_interface() try: ldap.validate_uniqueness({"virtualdomain": domain}) except MoulinetteError: - raise YunohostError("domain_exists") - - operation_logger.start() + raise YunohostValidationError("domain_exists") # Lower domain to avoid some edge cases issues # See: https://forum.yunohost.org/t/invalid-domain-causes-diagnosis-web-to-fail-fr-on-demand/11765 @@ -119,17 +117,21 @@ def domain_add(operation_logger, domain, dyndns=False): # DynDNS domain if dyndns: - from yunohost.dyndns import dyndns_subscribe, _dyndns_provides, _guess_current_dyndns_domain + from yunohost.dyndns import _dyndns_provides, _guess_current_dyndns_domain # Do not allow to subscribe to multiple dyndns domains... if _guess_current_dyndns_domain("dyndns.yunohost.org") != (None, None): - raise YunohostError('domain_dyndns_already_subscribed') + raise YunohostValidationError('domain_dyndns_already_subscribed') # Check that this domain can effectively be provided by # dyndns.yunohost.org. (i.e. is it a nohost.me / noho.st) if not _dyndns_provides("dyndns.yunohost.org", domain): - raise YunohostError("domain_dyndns_root_unknown") + raise YunohostValidationError("domain_dyndns_root_unknown") + operation_logger.start() + + if dyndns: + from yunohost.dyndns import dndns_subscribe # Actually subscribe dyndns_subscribe(domain=domain) @@ -197,7 +199,7 @@ def domain_remove(operation_logger, domain, remove_apps=False, force=False): # we don't want to check the domain exists because the ldap add may have # failed if not force and domain not in domain_list()['domains']: - raise YunohostError('domain_name_unknown', domain=domain) + raise YunohostValidationError('domain_name_unknown', domain=domain) # Check domain is not the main domain if domain == _get_maindomain(): @@ -205,13 +207,13 @@ def domain_remove(operation_logger, domain, remove_apps=False, force=False): other_domains.remove(domain) if other_domains: - raise YunohostError( + raise YunohostValidationError( "domain_cannot_remove_main", domain=domain, other_domains="\n * " + ("\n * ".join(other_domains)), ) else: - raise YunohostError("domain_cannot_remove_main_add_new_one", domain=domain) + raise YunohostValidationError("domain_cannot_remove_main_add_new_one", domain=domain) # Check if apps are installed on the domain apps_on_that_domain = [] @@ -234,9 +236,10 @@ def domain_remove(operation_logger, domain, remove_apps=False, force=False): for app, _ in apps_on_that_domain: app_remove(app) else: - raise YunohostError('domain_uninstall_app_first', apps="\n".join([x[1] for x in apps_on_that_domain])) + raise YunohostValidationError('domain_uninstall_app_first', apps="\n".join([x[1] for x in apps_on_that_domain])) operation_logger.start() + ldap = _get_ldap_interface() try: ldap.remove("virtualdomain=" + domain + ",ou=domains") @@ -288,7 +291,7 @@ def domain_dns_conf(domain, ttl=None): """ if domain not in domain_list()["domains"]: - raise YunohostError("domain_name_unknown", domain=domain) + raise YunohostValidationError("domain_name_unknown", domain=domain) ttl = 3600 if ttl is None else ttl @@ -345,7 +348,7 @@ def domain_main_domain(operation_logger, new_main_domain=None): # Check domain exists if new_main_domain not in domain_list()["domains"]: - raise YunohostError("domain_name_unknown", domain=new_main_domain) + raise YunohostValidationError("domain_name_unknown", domain=new_main_domain) operation_logger.related_to.append(("domain", new_main_domain)) operation_logger.start() diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index a921cfb5c..b2ac3de6d 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -36,7 +36,7 @@ from moulinette.utils.log import getActionLogger from moulinette.utils.filesystem import write_to_file, read_file from moulinette.utils.network import download_json -from yunohost.utils.error import YunohostError +from yunohost.utils.error import YunohostError, YunohostValidationError from yunohost.domain import _get_maindomain, _build_dns_conf from yunohost.utils.network import get_public_ip, dig from yunohost.log import is_unit_operation @@ -124,7 +124,7 @@ def dyndns_subscribe( """ if _guess_current_dyndns_domain(subscribe_host) != (None, None): - raise YunohostError('domain_dyndns_already_subscribed') + raise YunohostValidationError('domain_dyndns_already_subscribed') if domain is None: domain = _get_maindomain() @@ -132,13 +132,13 @@ def dyndns_subscribe( # Verify if domain is provided by subscribe_host if not _dyndns_provides(subscribe_host, domain): - raise YunohostError( + raise YunohostValidationError( "dyndns_domain_not_provided", domain=domain, provider=subscribe_host ) # Verify if domain is available if not _dyndns_available(subscribe_host, domain): - raise YunohostError("dyndns_unavailable", domain=domain) + raise YunohostValidationError("dyndns_unavailable", domain=domain) operation_logger.start() @@ -231,7 +231,7 @@ def dyndns_update( (domain, key) = _guess_current_dyndns_domain(dyn_host) if domain is None: - raise YunohostError('dyndns_no_domain_registered') + raise YunohostValidationError('dyndns_no_domain_registered') # If key is not given, pick the first file we find with the domain given else: @@ -239,7 +239,7 @@ def dyndns_update( keys = glob.glob("/etc/yunohost/dyndns/K{0}.+*.private".format(domain)) if not keys: - raise YunohostError("dyndns_key_not_found") + raise YunohostValidationError("dyndns_key_not_found") key = keys[0] diff --git a/src/yunohost/firewall.py b/src/yunohost/firewall.py index 1b708a626..bc21f1948 100644 --- a/src/yunohost/firewall.py +++ b/src/yunohost/firewall.py @@ -28,7 +28,7 @@ import yaml import miniupnpc from moulinette import m18n -from yunohost.utils.error import YunohostError +from yunohost.utils.error import YunohostError, YunohostValidationError from moulinette.utils import process from moulinette.utils.log import getActionLogger from moulinette.utils.text import prependlines @@ -366,7 +366,7 @@ def firewall_upnp(action="status", no_refresh=False): if action == "status": no_refresh = True else: - raise YunohostError("action_invalid", action=action) + raise YunohostValidationError("action_invalid", action=action) # Refresh port mapping using UPnP if not no_refresh: diff --git a/src/yunohost/hook.py b/src/yunohost/hook.py index e9857e4f9..493ad2c35 100644 --- a/src/yunohost/hook.py +++ b/src/yunohost/hook.py @@ -32,7 +32,7 @@ from glob import iglob from importlib import import_module from moulinette import m18n, msettings -from yunohost.utils.error import YunohostError +from yunohost.utils.error import YunohostError, YunohostValidationError from moulinette.utils import log from moulinette.utils.filesystem import read_json @@ -117,7 +117,7 @@ def hook_info(action, name): ) if not hooks: - raise YunohostError("hook_name_unknown", name=name) + raise YunohostValidationError("hook_name_unknown", name=name) return { "action": action, "name": name, @@ -186,7 +186,7 @@ def hook_list(action, list_by="name", show_info=False): d.add(name) else: - raise YunohostError("hook_list_by_invalid") + raise YunohostValidationError("hook_list_by_invalid") def _append_folder(d, folder): # Iterate over and add hook from a folder @@ -273,7 +273,7 @@ def hook_callback( try: hl = hooks_names[n] except KeyError: - raise YunohostError("hook_name_unknown", n) + raise YunohostValidationError("hook_name_unknown", n) # Iterate over hooks with this name for h in hl: # Update hooks dict diff --git a/src/yunohost/log.py b/src/yunohost/log.py index e5a53d466..1260cd98d 100644 --- a/src/yunohost/log.py +++ b/src/yunohost/log.py @@ -191,7 +191,7 @@ def log_show( log_path = base_path + LOG_FILE_EXT if not os.path.exists(md_path) and not os.path.exists(log_path): - raise YunohostError("log_does_exists", log=path) + raise YunohostValidationError("log_does_exists", log=path) infos = {} diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 3cd67b148..e0a3c6be8 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -31,7 +31,7 @@ import random from moulinette import m18n from moulinette.utils.log import getActionLogger -from yunohost.utils.error import YunohostError +from yunohost.utils.error import YunohostError, YunohostValidationError from yunohost.log import is_unit_operation logger = getActionLogger("yunohost.user") @@ -175,14 +175,14 @@ def user_permission_update( # Refuse to add "visitors" to mail, xmpp ... they require an account to make sense. if add and "visitors" in add and permission.split(".")[0] in SYSTEM_PERMS: - raise YunohostError("permission_require_account", permission=permission) + raise YunohostValidationError("permission_require_account", permission=permission) # Refuse to add "visitors" to protected permission if ( (add and "visitors" in add and existing_permission["protected"]) or (remove and "visitors" in remove and existing_permission["protected"]) ) and not force: - raise YunohostError("permission_protected", permission=permission) + raise YunohostValidationError("permission_protected", permission=permission) # Fetch currently allowed groups for this permission @@ -198,7 +198,7 @@ def user_permission_update( groups_to_add = [add] if not isinstance(add, list) else add for group in groups_to_add: if group not in all_existing_groups: - raise YunohostError("group_unknown", group=group) + raise YunohostValidationError("group_unknown", group=group) if group in current_allowed_groups: logger.warning( m18n.n( @@ -326,7 +326,7 @@ def user_permission_info(permission): permission, None ) if existing_permission is None: - raise YunohostError("permission_not_found", permission=permission) + raise YunohostValidationError("permission_not_found", permission=permission) return existing_permission @@ -391,7 +391,7 @@ def permission_create( if ldap.get_conflict( {"cn": permission}, base_dn="ou=permission,dc=yunohost,dc=org" ): - raise YunohostError("permission_already_exist", permission=permission) + raise YunohostValidationError("permission_already_exist", permission=permission) # Get random GID all_gid = {x.gr_gid for x in grp.getgrall()} @@ -427,7 +427,7 @@ def permission_create( all_existing_groups = user_group_list()["groups"].keys() for group in allowed or []: if group not in all_existing_groups: - raise YunohostError("group_unknown", group=group) + raise YunohostValidationError("group_unknown", group=group) operation_logger.related_to.append(("app", permission.split(".")[0])) operation_logger.start() @@ -594,7 +594,7 @@ def permission_delete(operation_logger, permission, force=False, sync_perm=True) permission = permission + ".main" if permission.endswith(".main") and not force: - raise YunohostError("permission_cannot_remove_main") + raise YunohostValidationError("permission_cannot_remove_main") from yunohost.utils.ldap import _get_ldap_interface @@ -861,7 +861,7 @@ def _validate_and_sanitize_permission_url(url, app_base_path, app): try: re.compile(regex) except Exception: - raise YunohostError("invalid_regex", regex=regex) + raise YunohostValidationError("invalid_regex", regex=regex) if url.startswith("re:"): @@ -874,12 +874,12 @@ def _validate_and_sanitize_permission_url(url, app_base_path, app): # regex with domain if "/" not in url: - raise YunohostError("regex_with_only_domain") + raise YunohostValidationError("regex_with_only_domain") domain, path = url[3:].split("/", 1) path = "/" + path if domain.replace("%", "").replace("\\", "") not in domains: - raise YunohostError("domain_name_unknown", domain=domain) + raise YunohostValidationError("domain_name_unknown", domain=domain) validate_regex(path) @@ -914,7 +914,7 @@ def _validate_and_sanitize_permission_url(url, app_base_path, app): sanitized_url = domain + path if domain not in domains: - raise YunohostError("domain_name_unknown", domain=domain) + raise YunohostValidationError("domain_name_unknown", domain=domain) _assert_no_conflicting_apps(domain, path, ignore_app=app) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 2de395131..3a0450bce 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -34,7 +34,7 @@ from glob import glob from datetime import datetime from moulinette import m18n -from yunohost.utils.error import YunohostError +from yunohost.utils.error import YunohostError, YunohostValidationError from moulinette.utils.process import check_output from moulinette.utils.log import getActionLogger from moulinette.utils.filesystem import read_file, append_to_file, write_to_file @@ -145,7 +145,7 @@ def service_remove(name): services = _get_services() if name not in services: - raise YunohostError("service_unknown", service=name) + raise YunohostValidationError("service_unknown", service=name) del services[name] try: @@ -325,7 +325,7 @@ def service_status(names=[]): # Validate service names requested for name in names: if name not in services.keys(): - raise YunohostError("service_unknown", service=name) + raise YunohostValidationError("service_unknown", service=name) # Filter only requested servivces services = {k: v for k, v in services.items() if k in names} @@ -484,7 +484,7 @@ def service_log(name, number=50): number = int(number) if name not in services.keys(): - raise YunohostError("service_unknown", service=name) + raise YunohostValidationError("service_unknown", service=name) log_list = services[name].get("log", []) @@ -545,7 +545,7 @@ def service_regen_conf( for name in names: if name not in services.keys(): - raise YunohostError("service_unknown", service=name) + raise YunohostValidationError("service_unknown", service=name) if names is []: names = list(services.keys()) @@ -568,7 +568,7 @@ def _run_service_command(action, service): """ services = _get_services() if service not in services.keys(): - raise YunohostError("service_unknown", service=service) + raise YunohostValidationError("service_unknown", service=service) possible_actions = [ "start", diff --git a/src/yunohost/settings.py b/src/yunohost/settings.py index 9bf75ff1d..9d1a6d11f 100644 --- a/src/yunohost/settings.py +++ b/src/yunohost/settings.py @@ -6,7 +6,7 @@ from datetime import datetime from collections import OrderedDict from moulinette import m18n -from yunohost.utils.error import YunohostError +from yunohost.utils.error import YunohostError, YunohostValidationError from moulinette.utils.log import getActionLogger from yunohost.regenconf import regen_conf @@ -109,7 +109,7 @@ def settings_get(key, full=False): settings = _get_settings() if key not in settings: - raise YunohostError("global_settings_key_doesnt_exists", settings_key=key) + raise YunohostValidationError("global_settings_key_doesnt_exists", settings_key=key) if full: return settings[key] @@ -137,7 +137,7 @@ def settings_set(key, value): settings = _get_settings() if key not in settings: - raise YunohostError("global_settings_key_doesnt_exists", settings_key=key) + raise YunohostValidationError("global_settings_key_doesnt_exists", settings_key=key) key_type = settings[key]["type"] @@ -146,7 +146,7 @@ def settings_set(key, value): if boolean_value[0]: value = boolean_value[1] else: - raise YunohostError( + raise YunohostValidationError( "global_settings_bad_type_for_setting", setting=key, received_type=type(value).__name__, @@ -158,14 +158,14 @@ def settings_set(key, value): try: value = int(value) except Exception: - raise YunohostError( + raise YunohostValidationError( "global_settings_bad_type_for_setting", setting=key, received_type=type(value).__name__, expected_type=key_type, ) else: - raise YunohostError( + raise YunohostValidationError( "global_settings_bad_type_for_setting", setting=key, received_type=type(value).__name__, @@ -173,7 +173,7 @@ def settings_set(key, value): ) elif key_type == "string": if not isinstance(value, str): - raise YunohostError( + raise YunohostValidationError( "global_settings_bad_type_for_setting", setting=key, received_type=type(value).__name__, @@ -181,14 +181,14 @@ def settings_set(key, value): ) elif key_type == "enum": if value not in settings[key]["choices"]: - raise YunohostError( + raise YunohostValidationError( "global_settings_bad_choice_for_enum", setting=key, choice=str(value), available_choices=", ".join(settings[key]["choices"]), ) else: - raise YunohostError( + raise YunohostValidationError( "global_settings_unknown_type", setting=key, unknown_type=key_type ) @@ -214,7 +214,7 @@ def settings_reset(key): settings = _get_settings() if key not in settings: - raise YunohostError("global_settings_key_doesnt_exists", settings_key=key) + raise YunohostValidationError("global_settings_key_doesnt_exists", settings_key=key) settings[key]["value"] = settings[key]["default"] _save_settings(settings) @@ -304,7 +304,7 @@ def _get_settings(): ) unknown_settings[key] = value except Exception as e: - raise YunohostError("global_settings_cant_open_settings", reason=e) + raise YunohostValidationError("global_settings_cant_open_settings", reason=e) if unknown_settings: try: diff --git a/src/yunohost/ssh.py b/src/yunohost/ssh.py index f7c6fcbb1..e9e7e1831 100644 --- a/src/yunohost/ssh.py +++ b/src/yunohost/ssh.py @@ -5,7 +5,7 @@ import os import pwd import subprocess -from yunohost.utils.error import YunohostError +from yunohost.utils.error import YunohostError, YunohostValidationError from moulinette.utils.filesystem import read_file, write_to_file, chown, chmod, mkdir SSHD_CONFIG_PATH = "/etc/ssh/sshd_config" @@ -21,7 +21,7 @@ def user_ssh_allow(username): # TODO it would be good to support different kind of shells if not _get_user_for_ssh(username): - raise YunohostError("user_unknown", user=username) + raise YunohostValidationError("user_unknown", user=username) from yunohost.utils.ldap import _get_ldap_interface @@ -43,7 +43,7 @@ def user_ssh_disallow(username): # TODO it would be good to support different kind of shells if not _get_user_for_ssh(username): - raise YunohostError("user_unknown", user=username) + raise YunohostValidationError("user_unknown", user=username) from yunohost.utils.ldap import _get_ldap_interface diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index e1ebe1307..e5699dede 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -51,7 +51,7 @@ from yunohost.utils.packages import ( _list_upgradable_apt_packages, ynh_packages_version, ) -from yunohost.utils.error import YunohostError +from yunohost.utils.error import YunohostError, YunohostValidationError from yunohost.log import is_unit_operation, OperationLogger # FIXME this is a duplicate from apps.py @@ -156,7 +156,7 @@ def tools_adminpw(new_password, check_strength=True): # UNIX seems to not like password longer than 127 chars ... # e.g. SSH login gets broken (or even 'su admin' when entering the password) if len(new_password) >= 127: - raise YunohostError("admin_password_too_long") + raise YunohostValidationError("admin_password_too_long") new_hash = _hash_user_password(new_password) @@ -285,10 +285,10 @@ def tools_postinstall( # Do some checks at first if os.path.isfile("/etc/yunohost/installed"): - raise YunohostError("yunohost_already_installed") + raise YunohostValidationError("yunohost_already_installed") if os.path.isdir("/etc/yunohost/apps") and os.listdir("/etc/yunohost/apps") != []: - raise YunohostError( + raise YunohostValidationError( "It looks like you're trying to re-postinstall a system that was already working previously ... If you recently had some bug or issues with your installation, please first discuss with the team on how to fix the situation instead of savagely re-running the postinstall ...", raw_msg=True, ) @@ -301,7 +301,7 @@ def tools_postinstall( ) GB = 1024 ** 3 if not force_diskspace and main_space < 10 * GB: - raise YunohostError("postinstall_low_rootfsspace") + raise YunohostValidationError("postinstall_low_rootfsspace") # Check password if not force_password: @@ -331,14 +331,14 @@ def tools_postinstall( dyndns = True # If not, abort the postinstall else: - raise YunohostError("dyndns_unavailable", domain=domain) + raise YunohostValidationError("dyndns_unavailable", domain=domain) else: dyndns = False else: dyndns = False if os.system("iptables -V >/dev/null 2>/dev/null") != 0: - raise YunohostError( + raise YunohostValidationError( "iptables/nftables does not seems to be working on your setup. You may be in a container or your kernel does have the proper modules loaded. Sometimes, rebooting the machine may solve the issue.", raw_msg=True, ) @@ -530,17 +530,17 @@ def tools_upgrade( from yunohost.utils import packages if packages.dpkg_is_broken(): - raise YunohostError("dpkg_is_broken") + raise YunohostValidationError("dpkg_is_broken") # Check for obvious conflict with other dpkg/apt commands already running in parallel if not packages.dpkg_lock_available(): - raise YunohostError("dpkg_lock_not_available") + raise YunohostValidationError("dpkg_lock_not_available") if system is not False and apps is not None: - raise YunohostError("tools_upgrade_cant_both") + raise YunohostValidationError("tools_upgrade_cant_both") if system is False and apps is None: - raise YunohostError("tools_upgrade_at_least_one") + raise YunohostValidationError("tools_upgrade_at_least_one") # # Apps @@ -825,7 +825,7 @@ def tools_migrations_list(pending=False, done=False): # Check for option conflict if pending and done: - raise YunohostError("migrations_list_conflict_pending_done") + raise YunohostValidationError("migrations_list_conflict_pending_done") # Get all migrations migrations = _get_migrations_list() @@ -875,17 +875,17 @@ def tools_migrations_run( if m.id == target or m.name == target or m.id.split("_")[0] == target: return m - raise YunohostError("migrations_no_such_migration", id=target) + raise YunohostValidationError("migrations_no_such_migration", id=target) # auto, skip and force are exclusive options if auto + skip + force_rerun > 1: - raise YunohostError("migrations_exclusive_options") + raise YunohostValidationError("migrations_exclusive_options") # If no target specified if not targets: # skip, revert or force require explicit targets if skip or force_rerun: - raise YunohostError("migrations_must_provide_explicit_targets") + raise YunohostValidationError("migrations_must_provide_explicit_targets") # Otherwise, targets are all pending migrations targets = [m for m in all_migrations if m.state == "pending"] @@ -897,11 +897,11 @@ def tools_migrations_run( pending = [t.id for t in targets if t.state == "pending"] if skip and done: - raise YunohostError("migrations_not_pending_cant_skip", ids=", ".join(done)) + raise YunohostValidationError("migrations_not_pending_cant_skip", ids=", ".join(done)) if force_rerun and pending: - raise YunohostError("migrations_pending_cant_rerun", ids=", ".join(pending)) + raise YunohostValidationError("migrations_pending_cant_rerun", ids=", ".join(pending)) if not (skip or force_rerun) and done: - raise YunohostError("migrations_already_ran", ids=", ".join(done)) + raise YunohostValidationError("migrations_already_ran", ids=", ".join(done)) # So, is there actually something to do ? if not targets: diff --git a/src/yunohost/user.py b/src/yunohost/user.py index f1fab786a..089f2ba0e 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -37,7 +37,7 @@ from moulinette import msignals, msettings, m18n from moulinette.utils.log import getActionLogger from moulinette.utils.process import check_output -from yunohost.utils.error import YunohostError +from yunohost.utils.error import YunohostError, YunohostValidationError from yunohost.service import service_status from yunohost.log import is_unit_operation @@ -125,7 +125,7 @@ def user_create( # Validate domain used for email address/xmpp account if domain is None: if msettings.get("interface") == "api": - raise YunohostError("Invalide usage, specify domain argument") + raise YunohostValidationError("Invalid usage, you should specify a domain argument") else: # On affiche les differents domaines possibles msignals.display(m18n.n("domains_available")) @@ -141,24 +141,24 @@ def user_create( # Check that the domain exists if domain not in domain_list()["domains"]: - raise YunohostError("domain_name_unknown", domain=domain) + raise YunohostValidationError("domain_name_unknown", domain=domain) mail = username + "@" + domain ldap = _get_ldap_interface() if username in user_list()["users"]: - raise YunohostError("user_already_exists", user=username) + raise YunohostValidationError("user_already_exists", user=username) # Validate uniqueness of username and mail in LDAP try: ldap.validate_uniqueness({"uid": username, "mail": mail, "cn": username}) except Exception as e: - raise YunohostError("user_creation_failed", user=username, error=e) + raise YunohostValidationError("user_creation_failed", user=username, error=e) # Validate uniqueness of username in system users all_existing_usernames = {x.pw_name for x in pwd.getpwall()} if username in all_existing_usernames: - raise YunohostError("system_username_exists") + raise YunohostValidationError("system_username_exists") main_domain = _get_maindomain() aliases = [ @@ -170,7 +170,7 @@ def user_create( ] if mail in aliases: - raise YunohostError("mail_unavailable") + raise YunohostValidationError("mail_unavailable") operation_logger.start() @@ -264,7 +264,7 @@ def user_delete(operation_logger, username, purge=False): from yunohost.utils.ldap import _get_ldap_interface if username not in user_list()["users"]: - raise YunohostError("user_unknown", user=username) + raise YunohostValidationError("user_unknown", user=username) operation_logger.start() @@ -347,7 +347,7 @@ def user_update( attrs=attrs_to_fetch, ) if not result: - raise YunohostError("user_unknown", user=username) + raise YunohostValidationError("user_unknown", user=username) user = result[0] env_dict = {"YNH_USER_USERNAME": username} @@ -396,13 +396,13 @@ def user_update( try: ldap.validate_uniqueness({"mail": mail}) except Exception as e: - raise YunohostError("user_update_failed", user=username, error=e) + raise YunohostValidationError("user_update_failed", user=username, error=e) if mail[mail.find("@") + 1 :] not in domains: - raise YunohostError( + raise YunohostValidationError( "mail_domain_unknown", domain=mail[mail.find("@") + 1 :] ) if mail in aliases: - raise YunohostError("mail_unavailable") + raise YunohostValidationError("mail_unavailable") del user["mail"][0] new_attr_dict["mail"] = [mail] + user["mail"] @@ -414,9 +414,9 @@ def user_update( try: ldap.validate_uniqueness({"mail": mail}) except Exception as e: - raise YunohostError("user_update_failed", user=username, error=e) + raise YunohostValidationError("user_update_failed", user=username, error=e) if mail[mail.find("@") + 1 :] not in domains: - raise YunohostError( + raise YunohostValidationError( "mail_domain_unknown", domain=mail[mail.find("@") + 1 :] ) user["mail"].append(mail) @@ -429,7 +429,7 @@ def user_update( if len(user["mail"]) > 1 and mail in user["mail"][1:]: user["mail"].remove(mail) else: - raise YunohostError("mail_alias_remove_failed", mail=mail) + raise YunohostValidationError("mail_alias_remove_failed", mail=mail) new_attr_dict["mail"] = user["mail"] if "mail" in new_attr_dict: @@ -451,7 +451,7 @@ def user_update( if len(user["maildrop"]) > 1 and mail in user["maildrop"][1:]: user["maildrop"].remove(mail) else: - raise YunohostError("mail_forward_remove_failed", mail=mail) + raise YunohostValidationError("mail_forward_remove_failed", mail=mail) new_attr_dict["maildrop"] = user["maildrop"] if "maildrop" in new_attr_dict: @@ -500,7 +500,7 @@ def user_info(username): if result: user = result[0] else: - raise YunohostError("user_unknown", user=username) + raise YunohostValidationError("user_unknown", user=username) result_dict = { "username": user["uid"][0], @@ -638,7 +638,7 @@ def user_group_create( {"cn": groupname}, base_dn="ou=groups,dc=yunohost,dc=org" ) if conflict: - raise YunohostError("group_already_exist", group=groupname) + raise YunohostValidationError("group_already_exist", group=groupname) # Validate uniqueness of groupname in system group all_existing_groupnames = {x.gr_name for x in grp.getgrall()} @@ -651,7 +651,7 @@ def user_group_create( "sed --in-place '/^%s:/d' /etc/group" % groupname, shell=True ) else: - raise YunohostError("group_already_exist_on_system", group=groupname) + raise YunohostValidationError("group_already_exist_on_system", group=groupname) if not gid: # Get random GID @@ -705,7 +705,7 @@ def user_group_delete(operation_logger, groupname, force=False, sync_perm=True): existing_groups = list(user_group_list()["groups"].keys()) if groupname not in existing_groups: - raise YunohostError("group_unknown", group=groupname) + raise YunohostValidationError("group_unknown", group=groupname) # Refuse to delete primary groups of a user (e.g. group 'sam' related to user 'sam') # without the force option... @@ -714,7 +714,7 @@ def user_group_delete(operation_logger, groupname, force=False, sync_perm=True): existing_users = list(user_list()["users"].keys()) undeletable_groups = existing_users + ["all_users", "visitors"] if groupname in undeletable_groups and not force: - raise YunohostError("group_cannot_be_deleted", group=groupname) + raise YunohostValidationError("group_cannot_be_deleted", group=groupname) operation_logger.start() ldap = _get_ldap_interface() @@ -756,11 +756,11 @@ def user_group_update( # We also can't edit "all_users" without the force option because that's a special group... if not force: if groupname == "all_users": - raise YunohostError("group_cannot_edit_all_users") + raise YunohostValidationError("group_cannot_edit_all_users") elif groupname == "visitors": - raise YunohostError("group_cannot_edit_visitors") + raise YunohostValidationError("group_cannot_edit_visitors") elif groupname in existing_users: - raise YunohostError("group_cannot_edit_primary_group", group=groupname) + raise YunohostValidationError("group_cannot_edit_primary_group", group=groupname) # We extract the uid for each member of the group to keep a simple flat list of members current_group = user_group_info(groupname)["members"] @@ -771,7 +771,7 @@ def user_group_update( for user in users_to_add: if user not in existing_users: - raise YunohostError("user_unknown", user=user) + raise YunohostValidationError("user_unknown", user=user) if user in current_group: logger.warning( @@ -843,7 +843,7 @@ def user_group_info(groupname): ) if not result: - raise YunohostError("group_unknown", group=groupname) + raise YunohostValidationError("group_unknown", group=groupname) infos = result[0] diff --git a/src/yunohost/utils/error.py b/src/yunohost/utils/error.py index 3000a52f8..e78beb2c9 100644 --- a/src/yunohost/utils/error.py +++ b/src/yunohost/utils/error.py @@ -49,3 +49,7 @@ class YunohostError(MoulinetteError): return super(YunohostError, self).content() else: return {"error": self.strerror, "log_ref": self.log_ref} + + +class YunohostValidationError(YunohostError): + pass diff --git a/src/yunohost/utils/password.py b/src/yunohost/utils/password.py index dce337f84..9e693d8cd 100644 --- a/src/yunohost/utils/password.py +++ b/src/yunohost/utils/password.py @@ -90,11 +90,11 @@ class PasswordValidator(object): # on top (at least not the moulinette ones) # because the moulinette needs to be correctly initialized # as well as modules available in python's path. - from yunohost.utils.error import YunohostError + from yunohost.utils.error import YunohostValidationError status, msg = self.validation_summary(password) if status == "error": - raise YunohostError(msg) + raise YunohostValidationError(msg) def validation_summary(self, password): """ diff --git a/tests/test_i18n_keys.py b/tests/test_i18n_keys.py index 6876cbcd8..799dc0d0c 100644 --- a/tests/test_i18n_keys.py +++ b/tests/test_i18n_keys.py @@ -25,9 +25,11 @@ def find_expected_string_keys(): # Try to find : # m18n.n( "foo" # YunohostError("foo" + # YunohostValidationError("foo" # # i18n: foo p1 = re.compile(r"m18n\.n\(\n*\s*[\"\'](\w+)[\"\']") p2 = re.compile(r"YunohostError\(\n*\s*[\'\"](\w+)[\'\"]") + p2 = re.compile(r"YunohostValidationError\(\n*\s*[\'\"](\w+)[\'\"]") p3 = re.compile(r"# i18n: [\'\"]?(\w+)[\'\"]?") python_files = glob.glob("src/yunohost/*.py") From 41b5a1239336ff51868a3ec7cff7385e1d8ab88b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 11 Mar 2021 03:07:16 +0100 Subject: [PATCH 2232/3170] Enforce permissions for /home/yunohost.backup and .conf --- data/hooks/conf_regen/01-yunohost | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/data/hooks/conf_regen/01-yunohost b/data/hooks/conf_regen/01-yunohost index c4120d487..1dd2705e1 100755 --- a/data/hooks/conf_regen/01-yunohost +++ b/data/hooks/conf_regen/01-yunohost @@ -94,6 +94,22 @@ do_post_regen() { # Enfore permissions # ###################### + if [ -d /home/yunohost.backup ] + then + chmod 750 /home/yunohost.backup + chown admin:root /home/yunohost.backup + fi + if [ -d /home/yunohost.backup/archives ] + then + chmod 750 /home/yunohost.backup/archives + chown admin:root /home/yunohost.backup/archives + fi + if [ -d /home/yunohost.conf ] + then + chmod 750 /home/yunohost.conf + chown root:root /home/yunohost.conf + fi + # Certs # We do this with find because there could be a lot of them... chown -R root:ssl-cert /etc/yunohost/certs From 4a7129e69b4e4a0d2096f3f8029cb425488a8bbe Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 11 Mar 2021 03:08:41 +0100 Subject: [PATCH 2233/3170] Update changelog for 4.1.7.4 --- debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian/changelog b/debian/changelog index 5fb0e563d..51eed275b 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +yunohost (4.1.7.4) stable; urgency=low + + - [fix] sec: Enforce permissions for /home/yunohost.backup and .conf (41b5a123) + + -- Alexandre Aubin Thu, 11 Mar 2021 03:08:10 +0100 + yunohost (4.1.7.3) stable; urgency=low - [fix] log: Some secrets were not redacted (0c172cd3) From 37f0c30ddc8cf50a0fc7839026d6e604bff32810 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 11 Mar 2021 03:57:15 +0100 Subject: [PATCH 2234/3170] Inject log_ref into all is_unit_operation failures --- src/yunohost/app.py | 3 +-- src/yunohost/log.py | 10 +++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 613ca21df..f214824aa 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1124,8 +1124,7 @@ def app_install( raise YunohostError( failure_message_with_debug_instructions, - raw_msg=True, - log_ref=operation_logger.name, + raw_msg=True ) # Clean hooks and add new ones diff --git a/src/yunohost/log.py b/src/yunohost/log.py index 1260cd98d..3f5fa8e71 100644 --- a/src/yunohost/log.py +++ b/src/yunohost/log.py @@ -35,7 +35,7 @@ from logging import FileHandler, getLogger, Formatter from moulinette import m18n, msettings from moulinette.core import MoulinetteError -from yunohost.utils.error import YunohostError +from yunohost.utils.error import YunohostError, YunohostValidationError from yunohost.utils.packages import get_ynh_package_version from moulinette.utils.log import getActionLogger from moulinette.utils.filesystem import read_file, read_yaml @@ -635,6 +635,14 @@ class OperationLogger(object): return if error is not None and not isinstance(error, str): error = str(error) + + # When the error happen's in the is_unit_operation try/except, + # we want to inject the log ref in the exception, such that it may be + # transmitted to the webadmin which can then redirect to the appropriate + # log page + if isinstance(error, Exception) and not isinstance(error, YunohostValidationError): + error.log_ref = operation_logger.name + self.ended_at = datetime.utcnow() self._error = error self._success = error is None From 1a0ef941099ad6d9d2befa24b41fd5dfdc1bc2c8 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 12 Mar 2021 04:24:27 +0100 Subject: [PATCH 2235/3170] Define HTTP codes for Yunohost Errors --- src/yunohost/utils/error.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/yunohost/utils/error.py b/src/yunohost/utils/error.py index e78beb2c9..c0ff2690c 100644 --- a/src/yunohost/utils/error.py +++ b/src/yunohost/utils/error.py @@ -25,6 +25,8 @@ from moulinette import m18n class YunohostError(MoulinetteError): + http_code = 500 + """ Yunohost base exception @@ -52,4 +54,5 @@ class YunohostError(MoulinetteError): class YunohostValidationError(YunohostError): - pass + + http_code = 400 From ce04570bfda75291a06727d1607641c13fcdfbfa Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 12 Mar 2021 23:18:16 +0100 Subject: [PATCH 2236/3170] helpers: Simplify manifest path / parsing --- data/helpers.d/apt | 9 +++------ data/helpers.d/utils | 2 +- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index bfdeffe7b..f0c650ace 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -224,13 +224,10 @@ ynh_install_app_dependencies () { # Add a comma for each space between packages. But not add a comma if the space separate a version specification. (See below) dependencies="$(echo "$dependencies" | sed 's/\([^\<=\>]\)\ \([^(]\)/\1, \2/g')" local dependencies=${dependencies//|/ | } - local manifest_path="../manifest.json" - if [ ! -e "$manifest_path" ]; then - manifest_path="../settings/manifest.json" # Into the restore script, the manifest is not at the same place - fi + local manifest_path="$YNH_APP_BASEDIR/manifest.json" - local version=$(grep '\"version\": ' "$manifest_path" | cut --delimiter='"' --fields=4) # Retrieve the version number in the manifest file. - if [ ${#version} -eq 0 ]; then + local version=$(jq -r '.version' "$manifest_path") + if [ -z "${version}" ] || [ "$version" == "null" ]; then version="1.0" fi local dep_app=${app//_/-} # Replace all '_' by '-' diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 8246b9986..c5ebdcb96 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -553,7 +553,7 @@ ynh_read_manifest () { if [ ! -e "$manifest" ]; then # If the manifest isn't found, try the common place for backup and restore script. - manifest="../settings/manifest.json" + manifest="$YNH_APP_BASEDIR/manifest.json" fi jq ".$manifest_key" "$manifest" --raw-output From ac26925b916001f6dd5ec4b5924e9690fe3a280e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 13 Mar 2021 00:18:11 +0100 Subject: [PATCH 2237/3170] Sane default permissions for files added using ynh_add_config and ynh_setup_source --- data/helpers.d/utils | 50 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index c5ebdcb96..f0d0c7005 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -160,6 +160,11 @@ ynh_setup_source () { # Extract source into the app dir mkdir --parents "$dest_dir" + if [ -n "${final_path:-}" ] && [ "$dest_dir" == "$final_path" ] + then + _ynh_apply_default_permissions $dest_dir + fi + if ! "$src_extract" then mv $src_filename $dest_dir @@ -319,8 +324,17 @@ ynh_add_config () { ynh_backup_if_checksum_is_different --file="$destination" + # Make sure to set the permissions before we copy the file + # This is to cover a case where an attacker could have + # created a file beforehand to have control over it + # (cp won't overwrite ownership / modes by default...) + touch $destination + chown root:root $destination + chmod 750 $destination + cp -f "$template_path" "$destination" - chown root: "$destination" + + _ynh_apply_default_permissions $destination ynh_replace_vars --file="$destination" @@ -685,3 +699,37 @@ ynh_compare_current_package_version() { # Return the return value of dpkg --compare-versions dpkg --compare-versions $current_version $comparison $version } + +# Check if we should enforce sane default permissions (= disable rwx for 'others') +# on file/folders handled with ynh_setup_source and ynh_add_config +# +# [internal] +# +# Having a file others-readable or a folder others-executable(=enterable) +# is a security risk comparable to "chmod 777" +# +# Configuration files may contain secrets. Or even just being able to enter a +# folder may allow an attacker to do nasty stuff (maybe a file or subfolder has +# some write permission enabled for 'other' and the attacker may edit the +# content or create files as leverage for priviledge escalation ...) +# +# The sane default should be to set ownership to $app:$app. +# In specific case, you may want to set the ownership to $app:www-data +# for example if nginx needs access to static files. +# +_ynh_apply_default_permissions() { + local target=$1 + + local ynh_requirement=$(jq -r '.requirements.yunohost' $YNH_APP_BASEDIR/manifest.json) + + if [ -z "$ynh_requirements" ] || [ "$ynh_requirements" == "null" ] || dpkg --compare-versions $ynh_requirements ge 4.2 + then + chmod o-rwx $target + if ynh_system_user_exists $app + then + chown $app:$app $target + else + chown root:root $target + fi + fi +} From 721f6f265e1c69d3fcadf0eb27f61918c65cb8f8 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 14 Mar 2021 18:45:36 +0100 Subject: [PATCH 2238/3170] Typo ... --- 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 3d1d16f3c..fadc16b6e 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1069,7 +1069,7 @@ def app_install( 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", "?") + env_dict_remove["YNH_APP_MANIFEST_VERSION"] = manifest.get("version", "?") # Execute remove script operation_logger_remove = OperationLogger( From 07f8d6d7af437af38a23899aa1abb731dfff11db Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 14 Mar 2021 18:47:33 +0100 Subject: [PATCH 2239/3170] ynh_clean_check_starting: Let's not trigger an error when vars aren't set --- data/helpers.d/systemd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/systemd b/data/helpers.d/systemd index 4c7bd31d1..1b620e991 100644 --- a/data/helpers.d/systemd +++ b/data/helpers.d/systemd @@ -171,12 +171,12 @@ ynh_systemd_action() { # # Requires YunoHost version 3.5.0 or higher. ynh_clean_check_starting () { - if [ -n "$pid_tail" ] + if [ -n "${pid_tail:-}" ] then # Stop the execution of tail. kill -SIGTERM $pid_tail 2>&1 fi - if [ -n "$templog" ] + if [ -n "${templog:-}" ] then ynh_secure_remove --file="$templog" 2>&1 fi From 7a947dbce1684bbade4aeb22c33500543a6aa8d8 Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Mon, 15 Mar 2021 00:03:31 +0100 Subject: [PATCH 2240/3170] [fix] True instead of description (#1189) --- 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 2de395131..6ba271ea6 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -400,7 +400,7 @@ def _get_and_format_service_status(service, infos): translation_key = "service_description_%s" % service if m18n.key_exists(translation_key): - description = m18n.key_exists(translation_key) + description = m18n.n(translation_key) else: description = str(raw_status.get("Description", "")) From dc6033c3993023162869d770029b5ee8baab1c03 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Mon, 15 Mar 2021 14:10:13 +0100 Subject: [PATCH 2241/3170] python3 way to get a list of dict keys --- data/hooks/diagnosis/14-ports.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/hooks/diagnosis/14-ports.py b/data/hooks/diagnosis/14-ports.py index 6faf29053..7581a1ac6 100644 --- a/data/hooks/diagnosis/14-ports.py +++ b/data/hooks/diagnosis/14-ports.py @@ -43,7 +43,7 @@ class PortsDiagnoser(Diagnoser): for ipversion in ipversions: try: r = Diagnoser.remote_diagnosis( - "check-ports", data={"ports": ports.keys()}, ipversion=ipversion + "check-ports", data={"ports": list(ports)}, ipversion=ipversion ) results[ipversion] = r["ports"] except Exception as e: From 2b947830aadcd9b1513449aaabbf6a7fbe95c085 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Tue, 16 Mar 2021 16:27:51 +0100 Subject: [PATCH 2242/3170] remove src_filename --- data/helpers.d/utils | 1 + 1 file changed, 1 insertion(+) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index c5ebdcb96..89821f7c2 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -194,6 +194,7 @@ ynh_setup_source () { else ynh_die --message="Archive format unrecognized." fi + ynh_secure_remove --file="$src_filename" fi # Apply patches From 3c3e8711ed25236a2019410e7f2ccc5d6dd33b4b Mon Sep 17 00:00:00 2001 From: Kay0u Date: Tue, 16 Mar 2021 16:28:12 +0100 Subject: [PATCH 2243/3170] fix _patch_legacy_helpers --- src/yunohost/app.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index fadc16b6e..5ad2e4021 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1761,7 +1761,7 @@ def app_action_run(operation_logger, app, action, args=None): if action_declaration.get("cwd"): cwd = action_declaration["cwd"].replace("$app", app) else: - cwd = "/etc/yunohost/apps/" + app + cwd = os.path.join(APPS_SETTING_PATH, app) retcode = hook_exec( path, @@ -3636,7 +3636,11 @@ def _patch_legacy_helpers(app_folder): if not os.path.isfile(filename): continue - content = read_file(filename) + try: + content = read_file(filename) + except Exception: + continue + replaced_stuff = False show_warning = False From 4a19a60b44a81ab5f80e4547a05b64694b169154 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Wed, 10 Mar 2021 11:24:11 +0100 Subject: [PATCH 2244/3170] dirty patch to wait for services to finish reloading --- src/yunohost/app.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index fadc16b6e..9d53df815 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -3465,6 +3465,14 @@ def _assert_system_is_sane_for_app(manifest, when): if "fail2ban" not in services: services.append("fail2ban") + # Wait if a service is reloading + test_nb = 0 + while test_nb < 10: + if not any(s for s in services if service_status(s)["status"] == "reloading"): + break + time.sleep(0.5) + test_nb+=1 + # List services currently down and raise an exception if any are found faulty_services = [s for s in services if service_status(s)["status"] != "running"] if faulty_services: From c2506f362c476558e1dc4410c365e2bf79e88726 Mon Sep 17 00:00:00 2001 From: Christian Wehrli Date: Fri, 26 Feb 2021 18:49:26 +0000 Subject: [PATCH 2245/3170] Translated using Weblate (German) Currently translated at 80.9% (510 of 630 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/locales/de.json b/locales/de.json index 536bdd842..019781e91 100644 --- a/locales/de.json +++ b/locales/de.json @@ -574,5 +574,13 @@ "password_listed": "Dieses Passwort gehört zu den meistverwendeten der Welt. Bitte nehmen Sie etwas einzigartigeres.", "operation_interrupted": "Wurde die Operation manuell unterbrochen?", "invalid_number": "Muss eine Zahl sein", - "migrations_to_be_ran_manually": "Die Migration {id} muss manuell durchgeführt werden. Bitte gehen Sie zu Werkzeuge → Migrationen auf der Webadmin-Seite oder führen Sie 'yunohost tools migrations run' aus." + "migrations_to_be_ran_manually": "Die Migration {id} muss manuell durchgeführt werden. Bitte gehen Sie zu Werkzeuge → Migrationen auf der Webadmin-Seite oder führen Sie 'yunohost tools migrations run' aus.", + "permission_already_up_to_date": "Die Berechtigung wurde nicht aktualisiert, weil die Anfragen für Hinzufügen/Entfernen stimmen mit dem aktuellen Status bereits überein", + "permission_already_exist": "Berechtigung '{permission}' existiert bereits", + "permission_already_disallowed": "Für die Gruppe '{group}' wurde die Berechtigung '{permission}' deaktiviert", + "permission_already_allowed": "Die Gruppe '{group}' hat die Berechtigung '{permission}' bereits erhalten", + "pattern_password_app": "Entschuldigen Sie bitte! Passwörter dürfen folgende Zeichen nicht enthalten: {forbidden_chars}", + "pattern_email_forward": "Es muss sich um eine gültige E-Mail-Adresse handeln. Das Symbol '+' wird akzeptiert (zum Beispiel : maxmuster@beispiel.com oder maxmuster+yunohost@beispiel.com)", + "password_too_simple_4": "Dass Passwort muss mindestens 12 Zeichen lang sein und Zahlen, Klein- und Grossbuchstaben und Sonderzeichen enthalten", + "password_too_simple_3": "Das Passwort muss mindestens 8 Zeichen lang sein und Zahlen, Klein- und Grossbuchstaben und Sonderzeichen enthalten" } From 394fd90383f826bc918b611e466ca1b30be66839 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quent=C3=AD?= Date: Sat, 27 Feb 2021 20:20:48 +0000 Subject: [PATCH 2246/3170] Translated using Weblate (Occitan) Currently translated at 53.9% (340 of 630 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/oc/ --- locales/oc.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/oc.json b/locales/oc.json index 07d841579..1849b3f3b 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -340,8 +340,8 @@ "migration_0005_not_enough_space": "I a pas pro d’espaci disponible sus {path} per lançar la migracion d’aquela passa :(.", "service_description_php7.0-fpm": "executa d’aplicacions escrichas en PHP amb nginx", "users_available": "Lista dels utilizaires disponibles :", - "good_practices_about_admin_password": "Sètz per definir un nòu senhal per l’administracion. Lo senhal deu almens conténer 8 caractèrs - encara que siá de bon far d’utilizar un senhal mai long qu’aquò (ex. una passafrasa) e/o d’utilizar mantun tipes de caractèrs (majuscula, minuscula, nombre e caractèrs especials).", - "good_practices_about_user_password": "Sètz a mand de definir un nòu senhal d’utilizaire. Lo nòu senhal deu conténer almens 8 caractèrs, es de bon far d’utilizar un senhal mai long (es a dire una frasa de senhal) e/o utilizar mantuns tipes de caractèrs (majusculas, minusculas, nombres e caractèrs especials).", + "good_practices_about_admin_password": "Sètz per definir un nòu senhal per l’administracion. Lo senhal deu almens conténer 8 caractèrs - encara que siá de bon far d’utilizar un senhal mai long qu’aquò (ex. una passafrasa) e/o d’utilizar mantun tipe de caractèrs (majuscula, minuscula, nombre e caractèrs especials).", + "good_practices_about_user_password": "Sètz a mand de definir un nòu senhal d’utilizaire. Lo nòu senhal deu conténer almens 8 caractèrs, es de bon far d’utilizar un senhal mai long (es a dire una frasa de senhal) e/o utilizar mantun tipe de caractèrs (majusculas, minusculas, nombres e caractèrs especials).", "migration_description_0006_sync_admin_and_root_passwords": "Sincronizar los senhals admin e root", "migration_0006_disclaimer": "Ara YunoHost s’espèra que los senhals admin e root sián sincronizats. En lançant aquesta migracion, vòstre senhal root serà remplaçat pel senhal admin.", "password_listed": "Aqueste senhal es un dels mai utilizats al monde. Se vos plai utilizatz-ne un mai unic.", From 56321d41c4c666ff1fde30970d4507bfcb493597 Mon Sep 17 00:00:00 2001 From: Christian Wehrli Date: Sun, 28 Feb 2021 22:31:09 +0000 Subject: [PATCH 2247/3170] Translated using Weblate (German) Currently translated at 81.1% (511 of 630 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/de.json b/locales/de.json index 019781e91..58b791d06 100644 --- a/locales/de.json +++ b/locales/de.json @@ -79,7 +79,7 @@ "ldap_initialized": "LDAP wurde initialisiert", "mail_alias_remove_failed": "E-Mail Alias '{mail:s}' konnte nicht entfernt werden", "mail_domain_unknown": "Die Domäne '{domain:s}' dieser E-Mail-Adresse ist ungültig. Wähle bitte eine Domäne, welche durch diesen Server verwaltet wird.", - "mail_forward_remove_failed": "Mailweiterleitung '{mail:s}' konnte nicht entfernt werden", + "mail_forward_remove_failed": "Mailweiterleitung für '{mail:s}' konnte nicht deaktiviert werden", "main_domain_change_failed": "Die Hauptdomain konnte nicht geändert werden", "main_domain_changed": "Die Hauptdomain wurde geändert", "no_internet_connection": "Der Server ist nicht mit dem Internet verbunden", From 74a9512d8ea8e6de9f70271e2b33549686e5ea4f Mon Sep 17 00:00:00 2001 From: Scapharnaum Date: Sun, 28 Feb 2021 21:48:41 +0000 Subject: [PATCH 2248/3170] Translated using Weblate (German) Currently translated at 81.1% (511 of 630 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/locales/de.json b/locales/de.json index 58b791d06..a86ba681a 100644 --- a/locales/de.json +++ b/locales/de.json @@ -176,7 +176,7 @@ "certmanager_cannot_read_cert": "Es ist ein Fehler aufgetreten, als es versucht wurde das aktuelle Zertifikat für die Domain {domain:s} zu öffnen (Datei: {file:s}), Grund: {reason:s}", "certmanager_cert_install_success_selfsigned": "Ein selbstsigniertes Zertifikat für die Domain {domain:s} wurde erfolgreich installiert", "certmanager_cert_install_success": "Für die Domain {domain:s} wurde erfolgreich ein Let's Encrypt Zertifikat installiert.", - "certmanager_cert_renew_success": "Das Let's Encrypt Zertifikat für die Domain {domain:s} wurde erfolgreich erneuert.", + "certmanager_cert_renew_success": "Das Let's Encrypt Zertifikat für die Domain {domain:s} wurde erfolgreich erneuert", "certmanager_hit_rate_limit": "Es wurden innerhalb kurzer Zeit zu viele Zertifikate für dieselbe Domain {domain:s} ausgestellt. Bitte versuchen Sie es später nochmal. Besuchen Sie https://letsencrypt.org/docs/rate-limits/ für mehr Informationen", "certmanager_cert_signing_failed": "Das neue Zertifikat konnte nicht signiert werden", "certmanager_no_cert_file": "Die Zertifikatsdatei für die Domain {domain:s} (Datei: {file:s}) konnte nicht gelesen werden", @@ -337,7 +337,7 @@ "diagnosis_found_errors": "Habe {errors} erhebliche(s) Problem(e) in Verbindung mit {category} gefunden!", "diagnosis_found_warnings": "Habe {warnings} Ding(e) gefunden, die verbessert werden könnten für {category}.", "diagnosis_ip_dnsresolution_working": "Domänen-Namens-Auflösung funktioniert!", - "diagnosis_ip_weird_resolvconf": "DNS Auflösung scheint zu funktionieren, aber seien Sie vorsichtig wenn Sie eine eigene /etc/resolv.conf verwendest.", + "diagnosis_ip_weird_resolvconf": "DNS Auflösung scheint zu funktionieren, aber seien Sie vorsichtig wenn Sie Ihren eigenen /etc/resolv.conf verwenden.", "diagnosis_display_tip": "Um die gefundenen Probleme zu sehen, können Sie zum Diagnose-Bereich des webadmin gehen, oder 'yunohost diagnosis show --issues' in der Kommandozeile ausführen.", "backup_archive_corrupted": "Das Backup-Archiv '{archive}' scheint beschädigt: {error}", "backup_archive_cant_retrieve_info_json": "Die Informationen für das Archiv '{archive}' konnten nicht geladen werden... Die Datei info.json wurde nicht gefunden (oder ist kein gültiges json).", @@ -359,7 +359,7 @@ "diagnosis_domain_expiration_error": "Einige Domänen werden SEHR BALD ablaufen!", "diagnosis_domain_expiration_success": "Deine Domänen sind registriert und werden in nächster Zeit nicht ablaufen.", "diagnosis_domain_not_found_details": "Die Domäne {domain} existiert nicht in der WHOIS-Datenbank oder sie ist abgelaufen!", - "diagnosis_domain_expiration_not_found": "Konnte die Ablaufdaten für einige Domänen nicht überprüfen.", + "diagnosis_domain_expiration_not_found": "Das Ablaufdatum einiger Domains kann nicht überprüft werden", "diagnosis_dns_try_dyndns_update_force": "Die DNS-Konfiguration dieser Domäne sollte automatisch von Yunohost verwaltet werden. Andernfalls können Sie mittels yunohost dyndns update --force ein Update erzwingen.", "diagnosis_dns_point_to_doc": "Bitte schauen Sie in die Dokumentation unter https://yunohost.org/dns_config wenn Sie Hilfe bei der Konfiguration der DNS-Einträge brauchen.", "diagnosis_dns_discrepancy": "Der folgende DNS-Eintrag scheint nicht den empfohlenen Einstellungen zu entsprechen:
Typ: {type}
Name: {name}
Aktueller Wert: {current}
Erwarteter Wert: {value}", From acd4d223cbcfd54b5db8ca02420898bc2c71277b Mon Sep 17 00:00:00 2001 From: Scapharnaum Date: Mon, 1 Mar 2021 00:00:42 +0000 Subject: [PATCH 2249/3170] Translated using Weblate (German) Currently translated at 83.6% (527 of 630 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/locales/de.json b/locales/de.json index a86ba681a..18bb9068a 100644 --- a/locales/de.json +++ b/locales/de.json @@ -79,7 +79,7 @@ "ldap_initialized": "LDAP wurde initialisiert", "mail_alias_remove_failed": "E-Mail Alias '{mail:s}' konnte nicht entfernt werden", "mail_domain_unknown": "Die Domäne '{domain:s}' dieser E-Mail-Adresse ist ungültig. Wähle bitte eine Domäne, welche durch diesen Server verwaltet wird.", - "mail_forward_remove_failed": "Mailweiterleitung für '{mail:s}' konnte nicht deaktiviert werden", + "mail_forward_remove_failed": "Die Weiterleitungs-E-Mail '{mail:s}' konnte nicht gelöscht werden", "main_domain_change_failed": "Die Hauptdomain konnte nicht geändert werden", "main_domain_changed": "Die Hauptdomain wurde geändert", "no_internet_connection": "Der Server ist nicht mit dem Internet verbunden", @@ -582,5 +582,21 @@ "pattern_password_app": "Entschuldigen Sie bitte! Passwörter dürfen folgende Zeichen nicht enthalten: {forbidden_chars}", "pattern_email_forward": "Es muss sich um eine gültige E-Mail-Adresse handeln. Das Symbol '+' wird akzeptiert (zum Beispiel : maxmuster@beispiel.com oder maxmuster+yunohost@beispiel.com)", "password_too_simple_4": "Dass Passwort muss mindestens 12 Zeichen lang sein und Zahlen, Klein- und Grossbuchstaben und Sonderzeichen enthalten", - "password_too_simple_3": "Das Passwort muss mindestens 8 Zeichen lang sein und Zahlen, Klein- und Grossbuchstaben und Sonderzeichen enthalten" + "password_too_simple_3": "Das Passwort muss mindestens 8 Zeichen lang sein und Zahlen, Klein- und Grossbuchstaben und Sonderzeichen enthalten", + "regenconf_file_manually_removed": "Die Konfigurationsdatei '{conf}' wurde manuell gelöscht und wird nicht erstellt", + "regenconf_file_manually_modified": "Die Konfigurationsdatei '{conf}' wurde manuell bearbeitet und wird nicht aktualisiert", + "regenconf_file_kept_back": "Die Konfigurationsdatei '{conf}' sollte von \"regen-conf\" (Kategorie {Kategorie}) gelöscht werden, wurde aber beibehalten.", + "regenconf_file_copy_failed": "Die neue Konfigurationsdatei '{new}' kann nicht nach '{conf}' kopiert werden", + "regenconf_file_backed_up": "Die Konfigurationsdatei '{conf}' wurde unter '{backup}' gespeichert", + "permission_require_account": "Berechtigung {permission} ist nur für Benutzer mit einem Konto sinnvoll und kann daher nicht für Besucher aktiviert werden.", + "permission_protected": "Die Berechtigung ist geschützt. Sie können die Besuchergruppe nicht zu dieser Berechtigung hinzufügen oder daraus entfernen.", + "permission_updated": "Berechtigung '{permission:s}' aktualisiert", + "permission_update_failed": "Die Berechtigung '{permission}' kann nicht aktualisiert werden : {error}", + "permission_not_found": "Berechtigung nicht gefunden", + "permission_deletion_failed": "Entfernung der Berechtigung nicht möglich '{permission}': {error}", + "permission_deleted": "Berechtigung gelöscht", + "permission_currently_allowed_for_all_users": "Diese Berechtigung wird derzeit allen Benutzern zusätzlich zu anderen Gruppen erteilt. Möglicherweise möchten Sie entweder die Berechtigung 'all_users' entfernen oder die anderen Gruppen entfernen, für die sie derzeit zulässig sind.", + "permission_creation_failed": "Berechtigungserstellung nicht möglich '{permission}' : {error}", + "permission_created": "Berechtigung '{permission: s}' erstellt", + "permission_cannot_remove_main": "Entfernung einer Hauptberechtigung nicht genehmigt" } From 4fadd4b68f4b9fe32847d152133de47e9229d30a Mon Sep 17 00:00:00 2001 From: Christian Wehrli Date: Wed, 3 Mar 2021 21:56:17 +0000 Subject: [PATCH 2250/3170] Translated using Weblate (German) Currently translated at 84.2% (531 of 630 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/locales/de.json b/locales/de.json index 18bb9068a..2dc287db5 100644 --- a/locales/de.json +++ b/locales/de.json @@ -598,5 +598,9 @@ "permission_currently_allowed_for_all_users": "Diese Berechtigung wird derzeit allen Benutzern zusätzlich zu anderen Gruppen erteilt. Möglicherweise möchten Sie entweder die Berechtigung 'all_users' entfernen oder die anderen Gruppen entfernen, für die sie derzeit zulässig sind.", "permission_creation_failed": "Berechtigungserstellung nicht möglich '{permission}' : {error}", "permission_created": "Berechtigung '{permission: s}' erstellt", - "permission_cannot_remove_main": "Entfernung einer Hauptberechtigung nicht genehmigt" + "permission_cannot_remove_main": "Entfernung einer Hauptberechtigung nicht genehmigt", + "regenconf_file_updated": "Konfigurationsdatei '{conf}' aktualisiert", + "regenconf_file_removed": "Konfigurationsdatei '{conf}' entfernt", + "regenconf_file_remove_failed": "Konnte die Konfigurationsdatei '{conf}' nicht entfernen", + "postinstall_low_rootfsspace": "Das Root-Filesystem hat insgesamt weniger als 10GB freien Speicherplatz zur Verfügung, was ziemlich besorgniserregend ist! Sie werden sehr bald keinen freien Speicherplatz mehr haben! Für das Root-Filesystem werden mindestens 16GB empfohlen. Wenn Sie YunoHost trotz dieser Warnung installieren wollen, wiederholen Sie den Befehl mit --force-diskspace" } From 0b7e040ee2b378cbd411729b088795c32c9ee821 Mon Sep 17 00:00:00 2001 From: Christian Wehrli Date: Fri, 5 Mar 2021 06:50:41 +0000 Subject: [PATCH 2251/3170] Translated using Weblate (German) Currently translated at 84.6% (533 of 630 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/locales/de.json b/locales/de.json index 2dc287db5..4fcc7a7e3 100644 --- a/locales/de.json +++ b/locales/de.json @@ -602,5 +602,7 @@ "regenconf_file_updated": "Konfigurationsdatei '{conf}' aktualisiert", "regenconf_file_removed": "Konfigurationsdatei '{conf}' entfernt", "regenconf_file_remove_failed": "Konnte die Konfigurationsdatei '{conf}' nicht entfernen", - "postinstall_low_rootfsspace": "Das Root-Filesystem hat insgesamt weniger als 10GB freien Speicherplatz zur Verfügung, was ziemlich besorgniserregend ist! Sie werden sehr bald keinen freien Speicherplatz mehr haben! Für das Root-Filesystem werden mindestens 16GB empfohlen. Wenn Sie YunoHost trotz dieser Warnung installieren wollen, wiederholen Sie den Befehl mit --force-diskspace" + "postinstall_low_rootfsspace": "Das Root-Filesystem hat insgesamt weniger als 10GB freien Speicherplatz zur Verfügung, was ziemlich besorgniserregend ist! Sie werden sehr bald keinen freien Speicherplatz mehr haben! Für das Root-Filesystem werden mindestens 16GB empfohlen. Wenn Sie YunoHost trotz dieser Warnung installieren wollen, wiederholen Sie den Befehl mit --force-diskspace", + "regenconf_up_to_date": "Die Konfiguration ist bereits aktuell für die Kategorie '{category}'", + "regenconf_now_managed_by_yunohost": "Die Konfigurationsdatei '{conf}' wird jetzt von YunoHost (Kategorie {category}) verwaltet." } From 18cc2ecbc8a9e2c1494f8f1968b96bf5ef9e118b Mon Sep 17 00:00:00 2001 From: Radek S Date: Sat, 6 Mar 2021 13:22:33 +0000 Subject: [PATCH 2252/3170] Translated using Weblate (Czech) Currently translated at 1.7% (11 of 630 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/cs/ --- locales/cs.json | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/locales/cs.json b/locales/cs.json index eafada5e6..3df59e0fd 100644 --- a/locales/cs.json +++ b/locales/cs.json @@ -1,3 +1,13 @@ { - "password_too_simple_1": "Heslo musí být aspoň 8 znaků dlouhé" + "password_too_simple_1": "Heslo musí být aspoň 8 znaků dlouhé", + "app_already_installed": "{app:s} je již nainstalován/a", + "already_up_to_date": "Neprovedena žádná akce. Vše je již aktuální.", + "admin_password_too_long": "Zvolte prosím heslo kratší než 127 znaků", + "admin_password_changed": "Heslo správce bylo změněno", + "admin_password_change_failed": "Nebylo možné změnit heslo", + "admin_password": "Heslo správce", + "additional_urls_already_removed": "Dotatečný odkaz '{url:s}' byl již odebrán u oprávnění '{permission:s}'", + "additional_urls_already_added": "Dotatečný odkaz '{url:s}' byl již přidán v dodatečných odkazech pro oprávnění '{permission:s}'", + "action_invalid": "Nesprávné akce '{action:s}'", + "aborting": "Přerušení." } From ad4ca718f7131ab41e24c366393eba804d1b8e32 Mon Sep 17 00:00:00 2001 From: Christian Wehrli Date: Wed, 10 Mar 2021 19:07:07 +0000 Subject: [PATCH 2253/3170] Translated using Weblate (German) Currently translated at 84.7% (534 of 630 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/de.json b/locales/de.json index 4fcc7a7e3..cdaa714eb 100644 --- a/locales/de.json +++ b/locales/de.json @@ -604,5 +604,6 @@ "regenconf_file_remove_failed": "Konnte die Konfigurationsdatei '{conf}' nicht entfernen", "postinstall_low_rootfsspace": "Das Root-Filesystem hat insgesamt weniger als 10GB freien Speicherplatz zur Verfügung, was ziemlich besorgniserregend ist! Sie werden sehr bald keinen freien Speicherplatz mehr haben! Für das Root-Filesystem werden mindestens 16GB empfohlen. Wenn Sie YunoHost trotz dieser Warnung installieren wollen, wiederholen Sie den Befehl mit --force-diskspace", "regenconf_up_to_date": "Die Konfiguration ist bereits aktuell für die Kategorie '{category}'", - "regenconf_now_managed_by_yunohost": "Die Konfigurationsdatei '{conf}' wird jetzt von YunoHost (Kategorie {category}) verwaltet." + "regenconf_now_managed_by_yunohost": "Die Konfigurationsdatei '{conf}' wird jetzt von YunoHost (Kategorie {category}) verwaltet.", + "regenconf_updated": "Konfiguration aktualisiert für '{category}'" } From f0827451caeba1d29de86bd1f9dbbaa84a837fa3 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 17 Mar 2021 16:37:07 +0100 Subject: [PATCH 2254/3170] Fix translation inconsistency --- locales/de.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/de.json b/locales/de.json index cdaa714eb..67a1d1a2e 100644 --- a/locales/de.json +++ b/locales/de.json @@ -585,7 +585,7 @@ "password_too_simple_3": "Das Passwort muss mindestens 8 Zeichen lang sein und Zahlen, Klein- und Grossbuchstaben und Sonderzeichen enthalten", "regenconf_file_manually_removed": "Die Konfigurationsdatei '{conf}' wurde manuell gelöscht und wird nicht erstellt", "regenconf_file_manually_modified": "Die Konfigurationsdatei '{conf}' wurde manuell bearbeitet und wird nicht aktualisiert", - "regenconf_file_kept_back": "Die Konfigurationsdatei '{conf}' sollte von \"regen-conf\" (Kategorie {Kategorie}) gelöscht werden, wurde aber beibehalten.", + "regenconf_file_kept_back": "Die Konfigurationsdatei '{conf}' sollte von \"regen-conf\" (Kategorie {category}) gelöscht werden, wurde aber beibehalten.", "regenconf_file_copy_failed": "Die neue Konfigurationsdatei '{new}' kann nicht nach '{conf}' kopiert werden", "regenconf_file_backed_up": "Die Konfigurationsdatei '{conf}' wurde unter '{backup}' gespeichert", "permission_require_account": "Berechtigung {permission} ist nur für Benutzer mit einem Konto sinnvoll und kann daher nicht für Besucher aktiviert werden.", From cc3c073dc5b6402b387d848868cbbe7c4395bc36 Mon Sep 17 00:00:00 2001 From: Paco Date: Wed, 17 Mar 2021 21:24:13 +0100 Subject: [PATCH 2255/3170] Saving domain settings in /etc/yunohost/domains.yml --- data/actionsmap/yunohost.yml | 20 +++++++ src/yunohost/domain.py | 108 +++++++++++++++++++++++++---------- 2 files changed, 99 insertions(+), 29 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 052ace386..cb02ff781 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -466,6 +466,26 @@ domain: arguments: domain: help: Target domain + + set-settings: + action_help: Set settings of a domain + api: POST /domains//settings + arguments: + domain: + help: Target domain + -t: + full: --ttl + help: Time To Live of this domain's DNS records + -x: + full: --xmpp + help: Configure XMPP in this domain's DNS records? True or False + -m: + full: --mail + help: Configure mail in this domain's DNS records? True or False + -o: + full: --owned-dns-zone + help: Is this domain owned as a DNS zone? Is it a full domain, i.e not a subdomain? True or False + ### domain_dns_conf() dns-conf: action_help: Generate sample DNS configuration for a domain diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index b1052fdbb..0c8bade28 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -26,6 +26,7 @@ import os import re import sys +import yaml from moulinette import m18n, msettings, msignals from moulinette.core import MoulinetteError @@ -466,11 +467,11 @@ def _build_dns_conf(domains): for domain_name, domain in domains.items(): ttl = domain["ttl"] - owned_dns_zone = "owned_dns_zone" in domains[root] and domains[root]["owned_dns_zone"] == True + owned_dns_zone = "owned_dns_zone" in domains[root] and domains[root]["owned_dns_zone"] if domain_name == root: name = name_prefix if not owned_dns_zone else "@" else: - name = domain_name[0:-(1 + len(root))] + name = domain_name[0:-(1 + len(root))] if not owned_dns_zone: name += "." + name_prefix @@ -489,7 +490,7 @@ def _build_dns_conf(domains): ######### # Email # ######### - if domain["mail"] == True: + if domain["mail"]: mail += [ [name, ttl, "MX", "10 %s." % domain_name], @@ -508,7 +509,7 @@ def _build_dns_conf(domains): ######## # XMPP # ######## - if domain["xmpp"] == True: + if domain["xmpp"]: xmpp += [ ["_xmpp-client._tcp", ttl, "SRV", "0 5 5222 %s." % domain_name], ["_xmpp-server._tcp", ttl, "SRV", "0 5 5269 %s." % domain_name], @@ -679,26 +680,6 @@ def _get_DKIM(domain): ) -def _get_domain_and_subdomains_settings(domain): - """ - Give data about a domain and its subdomains - """ - return { - "node.cmercier.fr" : { - "main": True, - "xmpp": True, - "mail": True, - "owned_dns_zone": True, - "ttl": 3600, - }, - "sub.node.cmercier.fr" : { - "main": False, - "xmpp": True, - "mail": False, - "ttl": 3600, - }, - } - def _load_domain_settings(): """ Retrieve entries in domains.yml @@ -707,7 +688,8 @@ def _load_domain_settings(): # Retrieve entries in the YAML if os.path.exists(DOMAIN_SETTINGS_PATH) and os.path.isfile(DOMAIN_SETTINGS_PATH): old_domains = yaml.load(open(DOMAIN_SETTINGS_PATH, "r+")) - else: + + if old_domains is None: old_domains = dict() # Create sanitized data @@ -719,14 +701,16 @@ def _load_domain_settings(): maindomain = get_domain_list["main"] for domain in get_domain_list["domains"]: + is_maindomain = domain == maindomain + domain_in_old_domains = domain in old_domains.keys() # Update each setting if not present new_domains[domain] = { # Set "main" value - "main": True if domain == maindomain else False + "main": is_maindomain } # Set other values (default value if missing) - for setting, default in [ ("xmpp", True), ("mail", True), ("owned_dns_zone", True), ("ttl", 3600) ]: - if domain in old_domains.keys() and setting in old_domains[domain].keys(): + for setting, default in [ ("xmpp", is_maindomain), ("mail", is_maindomain), ("owned_dns_zone", True), ("ttl", 3600) ]: + if domain_in_old_domains and setting in old_domains[domain].keys(): new_domains[domain][setting] = old_domains[domain][setting] else: new_domains[domain][setting] = default @@ -743,6 +727,7 @@ def domain_settings(domain): """ return _get_domain_settings(domain, False) + def _get_domain_settings(domain, subdomains): """ Get settings of a domain @@ -765,6 +750,71 @@ def _get_domain_settings(domain, subdomains): if domain == entry: only_wanted_domains[entry] = domains[entry] - return only_wanted_domains + +def domain_set_settings(domain, ttl=None, xmpp=None, mail=None, owned_dns_zone=None): + """ + Set some settings of a domain, for DNS generation. + + Keyword arguments: + domain -- The domain name + --ttl -- the Time To Live for this domains DNS record + --xmpp -- configure XMPP DNS records for this domain + --mail -- configure mail DNS records for this domain + --owned_dns_zone -- is this domain DNS zone owned? (is it a full domain or a subdomain?) + """ + domains = _load_domain_settings() + + if not domain in domains.keys(): + raise YunohostError("domain_name_unknown", domain=domain) + + setting_set = False + + if ttl is not None: + try: + ttl = int(ttl) + except: + raise YunohostError("bad_value_type", value_type=type(ttl)) + + if ttl < 0: + raise YunohostError("must_be_positive", value_type=type(ttl)) + + domains[domain]["ttl"] = ttl + setting_set = True + + if xmpp is not None: + try: + xmpp = xmpp in ["True", "true", "1"] + except: + raise YunohostError("bad_value_type", value_type=type(xmpp)) + domains[domain]["xmpp"] = xmpp + setting_set = True + + if mail is not None: + try: + mail = mail in ["True", "true", "1"] + except: + raise YunohostError("bad_value_type", value_type=type(mail)) + + domains[domain]["mail"] = mail + setting_set = True + + if owned_dns_zone is not None: + try: + owned_dns_zone = owned_dns_zone in ["True", "true", "1"] + except: + raise YunohostError("bad_value_type", value_type=type(owned_dns_zone)) + + domains[domain]["owned_dns_zone"] = owned_dns_zone + setting_set = True + + if not setting_set: + raise YunohostError("no_setting_given") + + # Save the settings to the .yaml file + with open(DOMAIN_SETTINGS_PATH, 'w') as file: + yaml.dump(domains, file) + + return domains[domain] + From fa5b3198ccdaeeefa186f9638d92ef1407ebf845 Mon Sep 17 00:00:00 2001 From: Paco Date: Wed, 17 Mar 2021 21:26:45 +0100 Subject: [PATCH 2256/3170] Add TODOs for locales --- src/yunohost/domain.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 0c8bade28..04aa6b560 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -767,6 +767,7 @@ def domain_set_settings(domain, ttl=None, xmpp=None, mail=None, owned_dns_zone=N domains = _load_domain_settings() if not domain in domains.keys(): + # TODO add locales raise YunohostError("domain_name_unknown", domain=domain) setting_set = False @@ -775,9 +776,11 @@ def domain_set_settings(domain, ttl=None, xmpp=None, mail=None, owned_dns_zone=N try: ttl = int(ttl) except: + # TODO add locales raise YunohostError("bad_value_type", value_type=type(ttl)) if ttl < 0: + # TODO add locales raise YunohostError("must_be_positive", value_type=type(ttl)) domains[domain]["ttl"] = ttl @@ -787,6 +790,7 @@ def domain_set_settings(domain, ttl=None, xmpp=None, mail=None, owned_dns_zone=N try: xmpp = xmpp in ["True", "true", "1"] except: + # TODO add locales raise YunohostError("bad_value_type", value_type=type(xmpp)) domains[domain]["xmpp"] = xmpp setting_set = True @@ -795,6 +799,7 @@ def domain_set_settings(domain, ttl=None, xmpp=None, mail=None, owned_dns_zone=N try: mail = mail in ["True", "true", "1"] except: + # TODO add locales raise YunohostError("bad_value_type", value_type=type(mail)) domains[domain]["mail"] = mail @@ -804,12 +809,14 @@ def domain_set_settings(domain, ttl=None, xmpp=None, mail=None, owned_dns_zone=N try: owned_dns_zone = owned_dns_zone in ["True", "true", "1"] except: + # TODO add locales raise YunohostError("bad_value_type", value_type=type(owned_dns_zone)) domains[domain]["owned_dns_zone"] = owned_dns_zone setting_set = True if not setting_set: + # TODO add locales raise YunohostError("no_setting_given") # Save the settings to the .yaml file From 82d5be68023289c3f1017b5682f2fef57fc02662 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 19 Mar 2021 16:15:16 +0100 Subject: [PATCH 2257/3170] Uniformize API routes --- data/actionsmap/yunohost.yml | 123 ++++++++++++++--------------------- 1 file changed, 48 insertions(+), 75 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 290952aa3..6e9461057 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -482,7 +482,7 @@ domain: - maindomain api: - GET /domains/main - - PUT /domains/main + - PUT /domains//main arguments: -n: full: --new-main-domain @@ -493,7 +493,7 @@ domain: ### certificate_status() cert-status: action_help: List status of current certificates (all by default). - api: GET /domains/cert-status/ + api: GET /domains//cert arguments: domain_list: help: Domains to check @@ -505,7 +505,7 @@ domain: ### certificate_install() cert-install: action_help: Install Let's Encrypt certificates for given domains (all by default). - api: POST /domains/cert-install/ + api: POST /domains//cert arguments: domain_list: help: Domains for which to install the certificates @@ -526,7 +526,7 @@ domain: ### certificate_renew() cert-renew: action_help: Renew the Let's Encrypt certificates for given domains (all by default). - api: POST /domains/cert-renew/ + api: PUT /domains//cert/renew arguments: domain_list: help: Domains for which to renew the certificates @@ -547,7 +547,7 @@ domain: ### domain_url_available() url-available: action_help: Check availability of a web path - api: GET /domain/urlavailable + api: GET /domain//urlavailable arguments: domain: help: The domain for the web path (e.g. your.domain.tld) @@ -556,20 +556,6 @@ domain: path: help: The path to check (e.g. /coffee) - - ### domain_info() -# info: -# action_help: Get domain informations -# api: GET /domains/ -# arguments: -# domain: -# help: "" -# extra: -# pattern: -# - '^([a-zA-Z0-9]{1}([a-zA-Z0-9\-]*[a-zA-Z0-9])*)(\.[a-zA-Z0-9]{1}([a-zA-Z0-9\-]*[a-zA-Z0-9])*)*(\.[a-zA-Z]{1}([a-zA-Z0-9\-]*[a-zA-Z0-9])*)$' -# - "Must be a valid domain name (e.g. my-domain.org)" - - ############################# # App # ############################# @@ -631,7 +617,7 @@ app: ### app_map() map: action_help: Show the mapping between urls and apps - api: GET /appsmap + api: GET /apps/map arguments: -a: full: --app @@ -679,7 +665,7 @@ app: ### app_upgrade() upgrade: action_help: Upgrade app - api: PUT /upgrade/apps + api: PUT /apps//upgrade arguments: app: help: App(s) to upgrade (default all) @@ -737,7 +723,6 @@ app: ### app_register_url() register-url: action_help: Book/register a web path for a given app - api: PUT /tools/registerurl arguments: app: help: App which will use the web path @@ -761,7 +746,6 @@ app: ### app_ssowatconf() ssowatconf: action_help: Regenerate SSOwat configuration file - api: PUT /ssowatconf ### app_change_label() change-label: @@ -776,7 +760,6 @@ app: ### app_addaccess() TODO: Write help addaccess: action_help: Grant access right to users (everyone by default) - api: PUT /access deprecated: true arguments: apps: @@ -788,7 +771,6 @@ app: ### app_removeaccess() TODO: Write help removeaccess: action_help: Revoke access right to users (everyone by default) - api: DELETE /access deprecated: true arguments: apps: @@ -800,7 +782,6 @@ app: ### app_clearaccess() clearaccess: action_help: Reset access rights for the app - api: POST /access deprecated: true arguments: apps: @@ -866,7 +847,7 @@ backup: ### backup_create() create: action_help: Create a backup local archive. If neither --apps or --system are given, this will backup all apps and all system parts. If only --apps if given, this will only backup apps and no system parts. Similarly, if only --system is given, this will only backup system parts and no apps. - api: POST /backup + api: POST /backups arguments: -n: full: --name @@ -894,7 +875,7 @@ backup: ### backup_restore() restore: action_help: Restore from a local backup archive. If neither --apps or --system are given, this will restore all apps and all system parts in the archive. If only --apps if given, this will only restore apps and no system parts. Similarly, if only --system is given, this will only restore system parts and no apps. - api: POST /backup/restore/ + api: PUT /backups//restore arguments: name: help: Name of the local backup archive @@ -911,7 +892,7 @@ backup: ### backup_list() list: action_help: List available local backup archives - api: GET /backup/archives + api: GET /backups arguments: -i: full: --with-info @@ -925,7 +906,7 @@ backup: ### backup_info() info: action_help: Show info about a local backup archive - api: GET /backup/archives/ + api: GET /backups/ arguments: name: help: Name of the local backup archive @@ -941,7 +922,7 @@ backup: ### backup_download() download: action_help: (API only) Request to download the file - api: GET /backup/download/ + api: GET /backups//download arguments: name: help: Name of the local backup archive @@ -949,7 +930,7 @@ backup: ### backup_delete() delete: action_help: Delete a backup archive - api: DELETE /backup/archives/ + api: DELETE /backups/ arguments: name: help: Name of the archive to delete @@ -1016,7 +997,6 @@ service: ### service_add() add: action_help: Add a service - # api: POST /services arguments: name: help: Service name to add @@ -1054,7 +1034,6 @@ service: ### service_remove() remove: action_help: Remove a service - # api: DELETE /services arguments: name: help: Service name to remove @@ -1062,7 +1041,7 @@ service: ### service_start() start: action_help: Start one or more services - api: PUT /services/ + api: PUT /services//start arguments: names: help: Service name to start @@ -1072,7 +1051,7 @@ service: ### service_stop() stop: action_help: Stop one or more services - api: DELETE /services/ + api: PUT /services//stop arguments: names: help: Service name to stop @@ -1120,7 +1099,7 @@ service: ### service_disable() disable: action_help: Disable one or more services - api: DELETE /services//enable + api: PUT /services//disable arguments: names: help: Service name to disable @@ -1155,7 +1134,6 @@ service: ### service_regen_conf() regen-conf: action_help: Regenerate the configuration file(s) for a service - api: PUT /services/regenconf deprecated_alias: - regenconf arguments: @@ -1207,19 +1185,10 @@ firewall: help: List forwarded ports with UPnP action: store_true - ### firewall_reload() - reload: - action_help: Reload all firewall rules - api: PUT /firewall - arguments: - --skip-upnp: - help: Do not refresh port forwarding using UPnP - action: store_true - ### firewall_allow() allow: action_help: Allow connections on a port - api: POST /firewall/port + api: POST /firewall/ arguments: protocol: help: "Protocol type to allow (TCP/UDP/Both)" @@ -1249,11 +1218,10 @@ firewall: help: Do not reload firewall rules action: store_true - ### firewall_disallow() disallow: action_help: Disallow connections on a port - api: DELETE /firewall/port + api: DELETE /firewall/ arguments: protocol: help: "Protocol type to allow (TCP/UDP/Both)" @@ -1281,11 +1249,10 @@ firewall: help: Do not reload firewall rules action: store_true - ### firewall_upnp() upnp: action_help: Manage port forwarding using UPnP - api: GET /firewall/upnp + api: PUT /firewall/upnp/ arguments: action: choices: @@ -1299,10 +1266,19 @@ firewall: help: Do not refresh port forwarding action: store_true + + ### firewall_reload() + reload: + action_help: Reload all firewall rules + arguments: + --skip-upnp: + help: Do not refresh port forwarding using UPnP + action: store_true + ### firewall_stop() stop: action_help: Stop iptables and ip6tables - api: DELETE /firewall + @@ -1316,7 +1292,6 @@ dyndns: ### dyndns_subscribe() subscribe: action_help: Subscribe to a DynDNS service - api: POST /dyndns arguments: --subscribe-host: help: Dynette HTTP API to subscribe to @@ -1333,7 +1308,6 @@ dyndns: ### dyndns_update() update: action_help: Update IP on DynDNS platform - api: PUT /dyndns arguments: --dyn-host: help: Dynette DNS server to inform @@ -1489,7 +1463,9 @@ tools: ### tools_regen_conf() regen-conf: action_help: Regenerate the configuration file(s) - api: PUT /tools/regenconf + api: + - PUT /regenconf + - PUT /regenconf/ arguments: names: help: Categories to regenerate configuration of (all by default) @@ -1538,7 +1514,7 @@ tools: ### tools_migrations_run() run: action_help: Run migrations - api: POST /migrations/run + api: PUT /migrations/ deprecated_alias: - migrate arguments: @@ -1561,7 +1537,6 @@ tools: ### tools_migrations_state() state: action_help: Show current migrations state - api: GET /migrations/state ############################# @@ -1590,7 +1565,6 @@ hook: ### hook_info() info: action_help: Get information about a given hook - api: GET /hooks// arguments: action: help: Action name @@ -1600,7 +1574,6 @@ hook: ### hook_list() list: action_help: List available hooks for an action - api: GET /hooks/ arguments: action: help: Action name @@ -1620,7 +1593,6 @@ hook: ### hook_callback() callback: action_help: Execute all scripts binded to an action - api: POST /hooks/ arguments: action: help: Action name @@ -1716,7 +1688,7 @@ log: ### log_share() share: action_help: Share the full log on yunopaste (alias to show --share) - api: GET /logs/share + api: GET /logs//share arguments: path: help: Log file to share @@ -1731,11 +1703,11 @@ diagnosis: list: action_help: List diagnosis categories - api: GET /diagnosis/list + api: GET /diagnosis/categories show: action_help: Show most recents diagnosis results - api: GET /diagnosis/show + api: GET /diagnosis arguments: categories: help: Diagnosis categories to display (all by default) @@ -1753,9 +1725,20 @@ diagnosis: help: Show a human-readable output action: store_true + get: + action_help: Low-level command to fetch raw data and status about a specific diagnosis test + api: GET /diagnosis/ + arguments: + category: + help: Diagnosis category to fetch results from + item: + help: "List of criteria describing the test. Must correspond exactly to the 'meta' infos in 'yunohost diagnosis show'" + metavar: CRITERIA + nargs: "*" + run: action_help: Run diagnosis - api: POST /diagnosis/run + api: PUT /diagnosis/run arguments: categories: help: Diagnosis categories to run (all by default) @@ -1786,13 +1769,3 @@ diagnosis: help: List active ignore filters action: store_true - get: - action_help: Low-level command to fetch raw data and status about a specific diagnosis test - api: GET /diagnosis/item/ - arguments: - category: - help: Diagnosis category to fetch results from - item: - help: "List of criteria describing the test. Must correspond exactly to the 'meta' infos in 'yunohost diagnosis show'" - metavar: CRITERIA - nargs: "*" From 1fb9ddd42a1c277ee354067e974e1b3899524ac6 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 19 Mar 2021 17:45:37 +0100 Subject: [PATCH 2258/3170] Tweak tools update/upgrade to have a single 'target' arg for simpler routing --- data/actionsmap/yunohost.yml | 29 +++++++--- src/yunohost/app.py | 2 +- .../data_migrations/0015_migrate_to_buster.py | 8 +-- src/yunohost/tools.py | 57 ++++++++++++------- 4 files changed, 63 insertions(+), 33 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 6e9461057..3e9237b1e 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1411,25 +1411,40 @@ tools: ### tools_update() update: action_help: YunoHost update - api: PUT /update + api: PUT /update/ arguments: + target: + help: What to update, "apps" (application catalog) or "system" (fetch available package upgrades, equivalent to apt update), "all" for both + choices: + - apps + - system + - all + nargs: "?" + metavar: TARGET + default: all --apps: - help: Fetch the application list to check which apps can be upgraded + help: (Deprecated, see first positional arg) Fetch the application list to check which apps can be upgraded action: store_true --system: - help: Fetch available system packages upgrades (equivalent to apt update) + help: (Deprecated, see first positional arg) Fetch available system packages upgrades (equivalent to apt update) action: store_true ### tools_upgrade() upgrade: action_help: YunoHost upgrade - api: PUT /upgrade + api: PUT /upgrade/ arguments: + target: + help: What to upgrade, either "apps" (all apps) or "system" (all system packages) + choices: + - apps + - system + nargs: "?" --apps: - help: List of apps to upgrade (all by default) - nargs: "*" + help: (Deprecated, see first positional arg) Upgrade all applications + action: store_true --system: - help: Upgrade only the system packages + help: (Deprecated, see first positional arg) Upgrade only the system packages action: store_true ### tools_shell() diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 9d53df815..3173641b9 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -147,7 +147,7 @@ def app_fetchlist(): ) from yunohost.tools import tools_update - tools_update(apps=True) + tools_update(target="apps") def app_list(full=False, installed=False, filter=None): diff --git a/src/yunohost/data_migrations/0015_migrate_to_buster.py b/src/yunohost/data_migrations/0015_migrate_to_buster.py index e87c83087..4f2d4caf8 100644 --- a/src/yunohost/data_migrations/0015_migrate_to_buster.py +++ b/src/yunohost/data_migrations/0015_migrate_to_buster.py @@ -43,7 +43,7 @@ class MyMigration(Migration): # logger.info(m18n.n("migration_0015_patching_sources_list")) self.patch_apt_sources_list() - tools_update(system=True) + tools_update(target="system") # Tell libc6 it's okay to restart system stuff during the upgrade os.system( @@ -88,7 +88,7 @@ class MyMigration(Migration): apps_packages = self.get_apps_equivs_packages() self.hold(apps_packages) - tools_upgrade(system=True, allow_yunohost_upgrade=False) + tools_upgrade(target="system", allow_yunohost_upgrade=False) if self.debian_major_version() == 9: raise YunohostError("migration_0015_still_on_stretch_after_main_upgrade") @@ -103,7 +103,7 @@ class MyMigration(Migration): # logger.info(m18n.n("migration_0015_yunohost_upgrade")) self.unhold(apps_packages) - tools_upgrade(system=True) + tools_upgrade(target="system") def debian_major_version(self): # The python module "platform" and lsb_release are not reliable because @@ -141,7 +141,7 @@ class MyMigration(Migration): # (but we don't if 'stretch' is already in the sources.list ... # which means maybe a previous upgrade crashed and we're re-running it) if " buster " not in read_file("/etc/apt/sources.list"): - tools_update(system=True) + tools_update(target="system") upgradable_system_packages = list(_list_upgradable_apt_packages()) if upgradable_system_packages: raise YunohostError("migration_0015_system_not_fully_up_to_date") diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index e1ebe1307..42e7a01c3 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -404,22 +404,29 @@ def tools_regen_conf( return regen_conf(names, with_diff, force, dry_run, list_pending) -def tools_update(apps=False, system=False): +def tools_update(target=None, apps=False, system=False): """ Update apps & system package cache - - Keyword arguments: - system -- Fetch available system packages upgrades (equivalent to apt update) - apps -- Fetch the application list to check which apps can be upgraded """ - # If neither --apps nor --system specified, do both - if not apps and not system: - apps = True - system = True + # Legacy options (--system, --apps) + if apps or system: + logger.warning("Using 'yunohost tools update' with --apps / --system is deprecated, just write 'yunohost tools update apps system' (no -- prefix anymore)") + if apps and system: + target = "all" + elif apps: + target = "apps" + else: + target = "system" + + elif not target: + target = "all" + + if target not in ["system", "apps", "all"]: + raise YunohostError("Unknown target %s, should be 'system', 'apps' or 'all'" % target, raw_msg=True) upgradable_system_packages = [] - if system: + if target in ["system", "all"]: # Update APT cache # LC_ALL=C is here to make sure the results are in english @@ -467,7 +474,7 @@ def tools_update(apps=False, system=False): logger.debug(m18n.n("done")) upgradable_apps = [] - if apps: + if target in ["apps", "all"]: try: _update_apps_catalog() except YunohostError as e: @@ -518,7 +525,7 @@ def _list_upgradable_apps(): @is_unit_operation() def tools_upgrade( - operation_logger, apps=None, system=False, allow_yunohost_upgrade=True + operation_logger, target=None, apps=False, system=False, allow_yunohost_upgrade=True ): """ Update apps & package cache, then display changelog @@ -536,26 +543,34 @@ def tools_upgrade( if not packages.dpkg_lock_available(): raise YunohostError("dpkg_lock_not_available") - if system is not False and apps is not None: - raise YunohostError("tools_upgrade_cant_both") + # Legacy options management (--system, --apps) + if target is None: - if system is False and apps is None: - raise YunohostError("tools_upgrade_at_least_one") + logger.warning("Using 'yunohost tools upgrade' with --apps / --system is deprecated, just write 'yunohost tools upgrade apps' or 'system' (no -- prefix anymore)") + + if (system, apps) == (True, True): + raise YunohostError("tools_upgrade_cant_both") + + if (system, apps) == (False, False): + raise YunohostError("tools_upgrade_at_least_one") + + target = "apps" if apps else "system" + + if target not in ["apps", "system"]: + raise Exception("Uhoh ?! tools_upgrade should have 'apps' or 'system' value for argument target") # # Apps # This is basically just an alias to yunohost app upgrade ... # - if apps is not None: + if target == "apps": # Make sure there's actually something to upgrade upgradable_apps = [app["id"] for app in _list_upgradable_apps()] - if not upgradable_apps or ( - len(apps) and all(app not in upgradable_apps for app in apps) - ): + if not upgradable_apps: logger.info(m18n.n("apps_already_up_to_date")) return @@ -573,7 +588,7 @@ def tools_upgrade( # System # - if system is True: + if target == "system": # Check that there's indeed some packages to upgrade upgradables = list(_list_upgradable_apt_packages()) From f295dffd005e8af3e5f893191cdaaa9964b531dd Mon Sep 17 00:00:00 2001 From: Paco Date: Sun, 21 Mar 2021 22:27:57 +0100 Subject: [PATCH 2259/3170] Fix old_domains not assigned --- src/yunohost/domain.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 04aa6b560..6ed4eb281 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -686,6 +686,7 @@ def _load_domain_settings(): And fill the holes if any """ # Retrieve entries in the YAML + old_domains = None if os.path.exists(DOMAIN_SETTINGS_PATH) and os.path.isfile(DOMAIN_SETTINGS_PATH): old_domains = yaml.load(open(DOMAIN_SETTINGS_PATH, "r+")) From 3b6599ff0d26c2d744bf569bc8952cb141029221 Mon Sep 17 00:00:00 2001 From: MercierCorentin Date: Mon, 22 Mar 2021 00:00:03 +0100 Subject: [PATCH 2260/3170] Check if DNS zone is owned by user --- src/yunohost/domain.py | 4 +++- src/yunohost/utils/dns.py | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 src/yunohost/utils/dns.py diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 6ed4eb281..1909a0353 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -42,6 +42,7 @@ from yunohost.app import ( ) from yunohost.regenconf import regen_conf, _force_clear_hashes, _process_regen_conf from yunohost.utils.network import get_public_ip +from yunohost.utils.dns import get_public_suffix from yunohost.log import is_unit_operation from yunohost.hook import hook_callback @@ -703,6 +704,7 @@ def _load_domain_settings(): for domain in get_domain_list["domains"]: is_maindomain = domain == maindomain + default_owned_dns_zone = True if domain == get_public_suffix(domain) else False domain_in_old_domains = domain in old_domains.keys() # Update each setting if not present new_domains[domain] = { @@ -710,7 +712,7 @@ def _load_domain_settings(): "main": is_maindomain } # Set other values (default value if missing) - for setting, default in [ ("xmpp", is_maindomain), ("mail", is_maindomain), ("owned_dns_zone", True), ("ttl", 3600) ]: + for setting, default in [ ("xmpp", is_maindomain), ("mail", is_maindomain), ("owned_dns_zone", default_owned_dns_zone), ("ttl", 3600) ]: if domain_in_old_domains and setting in old_domains[domain].keys(): new_domains[domain][setting] = old_domains[domain][setting] else: diff --git a/src/yunohost/utils/dns.py b/src/yunohost/utils/dns.py new file mode 100644 index 000000000..3033743d1 --- /dev/null +++ b/src/yunohost/utils/dns.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- + +""" License + + Copyright (C) 2018 YUNOHOST.ORG + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program; if not, see http://www.gnu.org/licenses + +""" +from publicsuffix import PublicSuffixList + +YNH_DYNDNS_DOMAINS = ["nohost.me", "noho.st", "ynh.fr"] + +def get_public_suffix(domain): + """get_public_suffix("www.example.com") -> "example.com" + + Return the public suffix of a domain name based + """ + # Load domain public suffixes + psl = PublicSuffixList() + + public_suffix = psl.get_public_suffix(domain) + if public_suffix in YNH_DYNDNS_DOMAINS: + domain_prefix = domain_name[0:-(1 + len(public_suffix))] + public_suffix = domain_prefix.plit(".")[-1] + "." + public_suffix + + return public_suffix \ No newline at end of file From fa818a476a152a8b36c598049e947fa90d981ef6 Mon Sep 17 00:00:00 2001 From: Paco Date: Mon, 22 Mar 2021 01:57:20 +0100 Subject: [PATCH 2261/3170] Change API naming etc. Expect a new change to come! --- data/actionsmap/yunohost.yml | 83 +++++++++++++++++++++++++----------- src/yunohost/domain.py | 78 +++++++++++++++------------------ 2 files changed, 92 insertions(+), 69 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index cb02ff781..138f7c6cd 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -460,31 +460,6 @@ domain: help: Do not ask confirmation to remove apps action: store_true - settings: - action_help: Get settings for a domain - api: GET /domains//settings - arguments: - domain: - help: Target domain - - set-settings: - action_help: Set settings of a domain - api: POST /domains//settings - arguments: - domain: - help: Target domain - -t: - full: --ttl - help: Time To Live of this domain's DNS records - -x: - full: --xmpp - help: Configure XMPP in this domain's DNS records? True or False - -m: - full: --mail - help: Configure mail in this domain's DNS records? True or False - -o: - full: --owned-dns-zone - help: Is this domain owned as a DNS zone? Is it a full domain, i.e not a subdomain? True or False ### domain_dns_conf() dns-conf: @@ -493,6 +468,8 @@ domain: arguments: domain: help: Target domain + extra: + pattern: *pattern_domain ### domain_maindomain() main-domain: @@ -575,6 +552,62 @@ domain: path: help: The path to check (e.g. /coffee) + subcategories: + config: + subcategory_help: Domains DNS settings + actions: + # domain_config_list + list: + action_help: Get settings for all domains + api: GET /domains/list + arguments: + domain: + help: Target domain + extra: + pattern: *pattern_domain + + # domain_config_show + show: + action_help: Get settings for all domains + api: GET /domains//show + arguments: + domain: + help: Target domain + extra: + pattern: *pattern_domain + + # domain_config_get + get: + action_help: Get specific setting of a domain + api: GET /domains// + arguments: + domain: + help: Target domain + extra: + pattern: *pattern_domain + key: + help: Setting requested. One of ttl, xmpp, mail, owned_dns_zone + extra: + pattern: &pattern_domain_key + - !!str ^(ttl)|(xmpp)|(mail)|(owned_dns_zone)|$ + - "pattern_domain_key" + + # domain_config_set + set: + action_help: Set a setting of a domain + api: POST /domains// + arguments: + domain: + help: Target domain + extra: + pattern: *pattern_domain + key: + help: Setting requested. One of ttl (Time To Live of this domain's DNS records), xmpp (Configure XMPP in this domain's DNS records?), mail (Configure mail in this domain's DNS records?), owned_dns_zone (Is it a full domain, i.e not a subdomain?) + extra: + pattern: *pattern_domain_key + value: + help: Value of the setting. Must be a positive integer number for "ttl", or one of ("True", "False", "true", "false", "1", "0") for other settings + ### domain_info() # info: diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 6ed4eb281..41daeaa03 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -705,10 +705,8 @@ def _load_domain_settings(): is_maindomain = domain == maindomain domain_in_old_domains = domain in old_domains.keys() # Update each setting if not present - new_domains[domain] = { - # Set "main" value - "main": is_maindomain - } + new_domains[domain] = {} + # new_domains[domain] = { "main": is_maindomain } # Set other values (default value if missing) for setting, default in [ ("xmpp", is_maindomain), ("mail", is_maindomain), ("owned_dns_zone", True), ("ttl", 3600) ]: if domain_in_old_domains and setting in old_domains[domain].keys(): @@ -719,9 +717,9 @@ def _load_domain_settings(): return new_domains -def domain_settings(domain): +def domain_config_show(domain): """ - Get settings of a domain + Show settings of a domain Keyword arguments: domain -- The domain name @@ -729,6 +727,18 @@ def domain_settings(domain): return _get_domain_settings(domain, False) +def domain_config_get(domain, key): + """ + Show a setting of a domain + + Keyword arguments: + domain -- The domain name + key -- ttl, xmpp, mail, owned_dns_zone + """ + settings = _get_domain_settings(domain, False) + return settings[domain][key] + + def _get_domain_settings(domain, subdomains): """ Get settings of a domain @@ -740,7 +750,7 @@ def _get_domain_settings(domain, subdomains): """ domains = _load_domain_settings() if not domain in domains.keys(): - return {} + raise YunohostError("domain_name_unknown", domain=domain) only_wanted_domains = dict() for entry in domains.keys(): @@ -754,16 +764,19 @@ def _get_domain_settings(domain, subdomains): return only_wanted_domains -def domain_set_settings(domain, ttl=None, xmpp=None, mail=None, owned_dns_zone=None): +def domain_config_set(domain, key, value): + #(domain, ttl=None, xmpp=None, mail=None, owned_dns_zone=None): """ Set some settings of a domain, for DNS generation. Keyword arguments: domain -- The domain name - --ttl -- the Time To Live for this domains DNS record - --xmpp -- configure XMPP DNS records for this domain - --mail -- configure mail DNS records for this domain - --owned_dns_zone -- is this domain DNS zone owned? (is it a full domain or a subdomain?) + key must be one of this strings: + ttl -- the Time To Live for this domains DNS record + xmpp -- configure XMPP DNS records for this domain + mail -- configure mail DNS records for this domain + owned_dns_zone -- is this domain DNS zone owned? (is it a full domain or a subdomain?) + value must be set according to the key """ domains = _load_domain_settings() @@ -771,11 +784,9 @@ def domain_set_settings(domain, ttl=None, xmpp=None, mail=None, owned_dns_zone=N # TODO add locales raise YunohostError("domain_name_unknown", domain=domain) - setting_set = False - - if ttl is not None: + if "ttl" == key: try: - ttl = int(ttl) + ttl = int(value) except: # TODO add locales raise YunohostError("bad_value_type", value_type=type(ttl)) @@ -785,38 +796,17 @@ def domain_set_settings(domain, ttl=None, xmpp=None, mail=None, owned_dns_zone=N raise YunohostError("must_be_positive", value_type=type(ttl)) domains[domain]["ttl"] = ttl - setting_set = True - if xmpp is not None: - try: - xmpp = xmpp in ["True", "true", "1"] - except: - # TODO add locales - raise YunohostError("bad_value_type", value_type=type(xmpp)) - domains[domain]["xmpp"] = xmpp - setting_set = True + elif "xmpp" == key: + domains[domain]["xmpp"] = value in ["True", "true", "1"] - if mail is not None: - try: - mail = mail in ["True", "true", "1"] - except: - # TODO add locales - raise YunohostError("bad_value_type", value_type=type(mail)) + elif "mail" == key: + domains[domain]["mail"] = value in ["True", "true", "1"] - domains[domain]["mail"] = mail - setting_set = True + elif "owned_dns_zone" == key: + domains[domain]["owned_dns_zone"] = value in ["True", "true", "1"] - if owned_dns_zone is not None: - try: - owned_dns_zone = owned_dns_zone in ["True", "true", "1"] - except: - # TODO add locales - raise YunohostError("bad_value_type", value_type=type(owned_dns_zone)) - - domains[domain]["owned_dns_zone"] = owned_dns_zone - setting_set = True - - if not setting_set: + else: # TODO add locales raise YunohostError("no_setting_given") From 5f994bba98c965023b89ac8263db2aefeff31809 Mon Sep 17 00:00:00 2001 From: axolotle Date: Mon, 22 Mar 2021 16:24:23 +0100 Subject: [PATCH 2262/3170] separate update, add and remove permission on exposed API --- data/actionsmap/yunohost.yml | 44 ++++++++++++++++++++++++------------ src/yunohost/user.py | 29 +++++++++++++++++------- 2 files changed, 50 insertions(+), 23 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 3e9237b1e..4a46e8dee 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -319,20 +319,6 @@ user: arguments: permission: help: Permission to manage (e.g. mail or nextcloud or wordpress.editors) (use "yunohost user permission list" and "yunohost user permission -f" to see all the current permissions) - -a: - full: --add - help: Group or usernames to grant this permission to - nargs: "*" - metavar: GROUP_OR_USER - extra: - pattern: *pattern_username - -r: - full: --remove - help: Group or usernames revoke this permission from - nargs: "*" - metavar: GROUP_OR_USER - extra: - pattern: *pattern_username -l: full: --label help: Label for this permission. This label will be shown on the SSO and in the admin @@ -343,10 +329,38 @@ user: - 'True' - 'False' + ## user_permission_add() + add: + action_help: Grant permission to group or user + api: PUT /users/permissions//add/ + arguments: + permission: + help: Permission to manage (e.g. mail or nextcloud or wordpress.editors) (use "yunohost user permission list" and "yunohost user permission -f" to see all the current permissions) + names: + help: Group or usernames to grant this permission to + nargs: "*" + metavar: GROUP_OR_USER + extra: + pattern: *pattern_username + + ## user_permission_remove() + remove: + action_help: Revoke permission to group or user + api: PUT /users/permissions//remove/ + arguments: + permission: + help: Permission to manage (e.g. mail or nextcloud or wordpress.editors) (use "yunohost user permission list" and "yunohost user permission -f" to see all the current permissions) + names: + help: Group or usernames to revoke this permission to + nargs: "*" + metavar: GROUP_OR_USER + extra: + pattern: *pattern_username + ## user_permission_reset() reset: action_help: Reset allowed groups to the default (all_users) for a given permission - api: DELETE /users/permissions/ + api: DELETE /users/permissions/ arguments: permission: help: Permission to manage (e.g. mail or nextcloud or wordpress.editors) (use "yunohost user permission list" and "yunohost user permission -f" to see all the current permissions) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index f1fab786a..e585f5c69 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -868,18 +868,31 @@ def user_permission_list(short=False, full=False): return yunohost.permission.user_permission_list(short, full, absolute_urls=True) -def user_permission_update( - permission, add=None, remove=None, label=None, show_tile=None, sync_perm=True +def user_permission_update(permission, label=None, show_tile=None, sync_perm=True): + import yunohost.permission + + return yunohost.permission.user_permission_update( + permission, label=label, show_tile=show_tile, sync_perm=sync_perm + ) + + +def user_permission_add( + permission, names, protected=None, force=False, sync_perm=True ): import yunohost.permission return yunohost.permission.user_permission_update( - permission, - add=add, - remove=remove, - label=label, - show_tile=show_tile, - sync_perm=sync_perm, + permission, add=names, protected=protected, force=force, sync_perm=sync_perm + ) + + +def user_permission_remove( + permission, names, protected=None, force=False, sync_perm=True +): + import yunohost.permission + + return yunohost.permission.user_permission_update( + permission, remove=names, protected=protected, force=force, sync_perm=sync_perm ) From da3d7db3e66ce3d0f78c5bff14eeb4629bbd54ee Mon Sep 17 00:00:00 2001 From: axolotle Date: Mon, 22 Mar 2021 16:25:00 +0100 Subject: [PATCH 2263/3170] use PUT for domain cert modifications --- data/actionsmap/yunohost.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 4a46e8dee..38c7b4918 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -519,7 +519,7 @@ domain: ### certificate_install() cert-install: action_help: Install Let's Encrypt certificates for given domains (all by default). - api: POST /domains//cert + api: PUT /domains//cert arguments: domain_list: help: Domains for which to install the certificates From 9e032b04bc33dc8a3825adf5e7b5efd3460fd89c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 22 Mar 2021 16:49:41 +0100 Subject: [PATCH 2264/3170] Fix i18n key tests --- tests/test_i18n_keys.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/test_i18n_keys.py b/tests/test_i18n_keys.py index 799dc0d0c..2ad56a34e 100644 --- a/tests/test_i18n_keys.py +++ b/tests/test_i18n_keys.py @@ -29,8 +29,8 @@ def find_expected_string_keys(): # # i18n: foo p1 = re.compile(r"m18n\.n\(\n*\s*[\"\'](\w+)[\"\']") p2 = re.compile(r"YunohostError\(\n*\s*[\'\"](\w+)[\'\"]") - p2 = re.compile(r"YunohostValidationError\(\n*\s*[\'\"](\w+)[\'\"]") - p3 = re.compile(r"# i18n: [\'\"]?(\w+)[\'\"]?") + p3 = re.compile(r"YunohostValidationError\(\n*\s*[\'\"](\w+)[\'\"]") + p4 = re.compile(r"# i18n: [\'\"]?(\w+)[\'\"]?") python_files = glob.glob("src/yunohost/*.py") python_files.extend(glob.glob("src/yunohost/utils/*.py")) @@ -49,6 +49,10 @@ def find_expected_string_keys(): continue yield m for m in p3.findall(content): + if m.endswith("_"): + continue + yield m + for m in p4.findall(content): yield m # For each diagnosis, try to find strings like "diagnosis_stuff_foo" (c.f. diagnosis summaries) From 09d306924ac3139999aad96b9fe874e97636ca74 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 22 Mar 2021 17:46:32 +0100 Subject: [PATCH 2265/3170] Fix minor issue with main_space diagnosis: duplicated error/warning, missing / partition when it's on a /dev/loop --- data/hooks/diagnosis/50-systemresources.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/hooks/diagnosis/50-systemresources.py b/data/hooks/diagnosis/50-systemresources.py index 1e8e2201a..a8f3cb6df 100644 --- a/data/hooks/diagnosis/50-systemresources.py +++ b/data/hooks/diagnosis/50-systemresources.py @@ -77,7 +77,7 @@ class SystemResourcesDiagnoser(Diagnoser): # Ignore /dev/loop stuff which are ~virtual partitions ? (e.g. mounted to /snap/) disk_partitions = [ - d for d in disk_partitions if not d.device.startswith("/dev/loop") + d for d in disk_partitions if d.mountpoint in ["/", "/var"] or not d.device.startswith("/dev/loop") ] for disk_partition in disk_partitions: @@ -139,7 +139,7 @@ class SystemResourcesDiagnoser(Diagnoser): status="ERROR", summary="diagnosis_rootfstotalspace_critical", ) - if main_space < 14 * GB: + elif main_space < 14 * GB: yield dict( meta={"test": "rootfstotalspace"}, data={"space": human_size(main_space)}, From 473c5762c6665003e26df28daeea11bd528a5435 Mon Sep 17 00:00:00 2001 From: axolotle Date: Mon, 22 Mar 2021 18:41:18 +0100 Subject: [PATCH 2266/3170] replace api action group.update with group.add and group.remove --- data/actionsmap/yunohost.yml | 56 ++++++++++++++++++++---------------- src/yunohost/user.py | 28 ++++++++++++++++++ 2 files changed, 60 insertions(+), 24 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 38c7b4918..7fcf9e51b 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -252,30 +252,6 @@ user: extra: pattern: *pattern_groupname - ### user_group_update() - update: - action_help: Update group - api: PUT /users/groups/ - arguments: - groupname: - help: Name of the group to be updated - extra: - pattern: *pattern_groupname - -a: - full: --add - help: User(s) to add in the group - nargs: "*" - metavar: USERNAME - extra: - pattern: *pattern_username - -r: - full: --remove - help: User(s) to remove in the group - nargs: "*" - metavar: USERNAME - extra: - pattern: *pattern_username - ### user_group_info() info: action_help: Get information about a specific group @@ -286,6 +262,38 @@ user: extra: pattern: *pattern_username + ### user_group_add() + add: + action_help: Update group + api: PUT /users/groups//add/ + arguments: + groupname: + help: Name of the group to add user(s) to + extra: + pattern: *pattern_groupname + usernames: + help: User(s) to add in the group + nargs: "*" + metavar: USERNAME + extra: + pattern: *pattern_username + + ### user_group_remove() + remove: + action_help: Update group + api: PUT /users/groups//remove/ + arguments: + groupname: + help: Name of the group to remove user(s) from + extra: + pattern: *pattern_groupname + usernames: + help: User(s) to remove from the group + nargs: "*" + metavar: USERNAME + extra: + pattern: *pattern_username + permission: subcategory_help: Manage permissions actions: diff --git a/src/yunohost/user.py b/src/yunohost/user.py index e585f5c69..b551318fc 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -857,6 +857,34 @@ def user_group_info(groupname): } +def user_group_add(groupname, usernames, force=False, sync_perm=True): + """ + Add user(s) to a group + + Keyword argument: + groupname -- Groupname to update + usernames -- User(s) to add in the group + + """ + return user_group_update( + groupname, add=usernames, force=force, sync_perm=sync_perm + ) + + +def user_group_remove(groupname, usernames, force=False, sync_perm=True): + """ + Remove user(s) from a group + + Keyword argument: + groupname -- Groupname to update + usernames -- User(s) to remove from the group + + """ + return user_group_update( + groupname, remove=usernames, force=force, sync_perm=sync_perm + ) + + # # Permission subcategory # From 27eefcb286765a604d3fd9c6821438dc37d12084 Mon Sep 17 00:00:00 2001 From: axolotle Date: Mon, 22 Mar 2021 18:43:21 +0100 Subject: [PATCH 2267/3170] reexpose '/hooks/' since the web-admin needs it for backups --- data/actionsmap/yunohost.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 7fcf9e51b..de8c6a475 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1611,6 +1611,7 @@ hook: ### hook_list() list: action_help: List available hooks for an action + api: GET /hooks/ arguments: action: help: Action name From 11c50c018742647237315f5b7222e19a466125a4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 22 Mar 2021 18:49:25 +0100 Subject: [PATCH 2268/3170] Fix log_ref injection --- src/yunohost/log.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/yunohost/log.py b/src/yunohost/log.py index 3f5fa8e71..7a45565f8 100644 --- a/src/yunohost/log.py +++ b/src/yunohost/log.py @@ -631,17 +631,18 @@ class OperationLogger(object): """ Close properly the unit operation """ - if self.ended_at is not None or self.started_at is None: - return - if error is not None and not isinstance(error, str): - error = str(error) # When the error happen's in the is_unit_operation try/except, # we want to inject the log ref in the exception, such that it may be # transmitted to the webadmin which can then redirect to the appropriate # log page if isinstance(error, Exception) and not isinstance(error, YunohostValidationError): - error.log_ref = operation_logger.name + error.log_ref = self.name + + if self.ended_at is not None or self.started_at is None: + return + if error is not None and not isinstance(error, str): + error = str(error) self.ended_at = datetime.utcnow() self._error = error From 20553de539d655ba049d5f3fcf7dd522f6595914 Mon Sep 17 00:00:00 2001 From: axolotle Date: Mon, 22 Mar 2021 20:29:17 +0100 Subject: [PATCH 2269/3170] update firewall port update routes --- data/actionsmap/yunohost.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index de8c6a475..1f710202d 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1210,7 +1210,7 @@ firewall: ### firewall_allow() allow: action_help: Allow connections on a port - api: POST /firewall/ + api: PUT /firewall//allow/ arguments: protocol: help: "Protocol type to allow (TCP/UDP/Both)" @@ -1243,7 +1243,7 @@ firewall: ### firewall_disallow() disallow: action_help: Disallow connections on a port - api: DELETE /firewall/ + api: PUT /firewall//disallow/ arguments: protocol: help: "Protocol type to allow (TCP/UDP/Both)" From 33fab1c99f1775da0a32dcf7072be13a5cbd3c59 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 22 Mar 2021 20:45:03 +0100 Subject: [PATCH 2270/3170] Be more robust against non-int values for level in app catalog (e.g. for apps 'inprogress' for which level is '?') --- 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 9d53df815..62e4ffd59 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -257,8 +257,9 @@ def _app_upgradable(app_infos): return "url_required" # Do not advertise upgrades for bad-quality apps + level = app_in_catalog.get("level", -1) if ( - not app_in_catalog.get("level", -1) >= 5 + not (isinstance(level, int) and level >= 5) or app_in_catalog.get("state") != "working" ): return "bad_quality" From ab454ff62f56e83d978f0a77995b1f578cb0fb4a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 22 Mar 2021 21:40:33 +0100 Subject: [PATCH 2271/3170] Missing decode() for Popen output in certificate.py --- src/yunohost/certificate.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index c48af2c07..dade85285 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -197,6 +197,8 @@ def _certificate_install_selfsigned(domain_list, force=False): out, _ = p.communicate() + out = out.decode("utf-8") + if p.returncode != 0: logger.warning(out) raise YunohostError("domain_cert_gen_failed") From e63ca06d371e9e4eb5a724f11e6580404fec90a0 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 22 Mar 2021 21:58:15 +0100 Subject: [PATCH 2272/3170] Missing import --- src/yunohost/backup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index b020c0f34..ba940058b 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -63,7 +63,7 @@ from yunohost.hook import ( from yunohost.tools import tools_postinstall from yunohost.regenconf import regen_conf from yunohost.log import OperationLogger -from yunohost.utils.error import YunohostError +from yunohost.utils.error import YunohostError, YunohostValidationError from yunohost.utils.packages import ynh_packages_version from yunohost.settings import settings_get From 63f904850428d889ce0993cc6e850d945c420c63 Mon Sep 17 00:00:00 2001 From: axolotle Date: Mon, 22 Mar 2021 22:31:18 +0100 Subject: [PATCH 2273/3170] add PUT '/migration' to avoid specifying the list of the migrations to run --- data/actionsmap/yunohost.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 1f710202d..034034199 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1551,7 +1551,9 @@ tools: ### tools_migrations_run() run: action_help: Run migrations - api: PUT /migrations/ + api: + - PUT /migrations + - PUT /migrations/ deprecated_alias: - migrate arguments: From 113d9f5a8bea4dd8e4619b9379852b187ba57743 Mon Sep 17 00:00:00 2001 From: axolotle Date: Mon, 22 Mar 2021 23:22:56 +0100 Subject: [PATCH 2274/3170] PUT for '/apps//config' & 'appscatalog' to 'apps/catalog' --- data/actionsmap/yunohost.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 034034199..9dc3312f4 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -587,7 +587,7 @@ app: catalog: action_help: Show the catalog of installable application - api: GET /appscatalog + api: GET /apps/catalog arguments: -f: full: --full @@ -597,7 +597,7 @@ app: full: --with-categories help: Also return a list of app categories action: store_true - + ### app_search() search: action_help: Search installable apps @@ -851,7 +851,7 @@ app: ### app_config_apply() apply: action_help: apply the new configuration - api: POST /apps//config + api: PUT /apps//config arguments: app: help: App name @@ -1500,7 +1500,7 @@ tools: ### tools_regen_conf() regen-conf: action_help: Regenerate the configuration file(s) - api: + api: - PUT /regenconf - PUT /regenconf/ arguments: From ff772cd2949c91995260462d69b8c0f59c141f96 Mon Sep 17 00:00:00 2001 From: axolotle Date: Mon, 22 Mar 2021 23:41:06 +0100 Subject: [PATCH 2275/3170] split '/diagnosis/ignore' and '/diagnosis/ungignore' --- data/actionsmap/yunohost.yml | 16 ++++++++++------ src/yunohost/diagnosis.py | 10 +++++++++- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 9dc3312f4..3c6b863db 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1795,17 +1795,21 @@ diagnosis: ignore: action_help: Configure some diagnosis results to be ignored and therefore not considered as actual issues - api: POST /diagnosis/ignore + api: PUT /diagnosis/ignore arguments: - --add-filter: + --filter: help: "Add a filter. The first element should be a diagnosis category, and other criterias can be provided using the infos from the 'meta' sections in 'yunohost diagnosis show'. For example: 'dnsrecords domain=yolo.test category=xmpp'" nargs: "*" metavar: CRITERIA - --remove-filter: - help: Remove a filter (it should be an existing filter as listed with --list) - nargs: "*" - metavar: CRITERIA --list: help: List active ignore filters action: store_true + unignore: + action_help: Configure some diagnosis results to be unignored and therefore considered as actual issues + api: PUT /diagnosis/unignore + arguments: + --filter: + help: Remove a filter (it should be an existing filter as listed with --list) + nargs: "*" + metavar: CRITERIA diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index d01d56613..4a231d82c 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -221,7 +221,15 @@ def diagnosis_run( logger.warning(m18n.n("diagnosis_display_tip")) -def diagnosis_ignore(add_filter=None, remove_filter=None, list=False): +def diagnosis_ignore(filter, list=False): + return _diagnosis_ignore(add_filter=filter, list=list) + + +def diagnosis_unignore(filter): + return _diagnosis_ignore(remove_filter=filter) + + +def _diagnosis_ignore(add_filter=None, remove_filter=None, list=False): """ This action is meant for the admin to ignore issues reported by the diagnosis system if they are known and understood by the admin. For From a5fe21fd3864c818615f9c86e94f2e09c8844e86 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 23 Mar 2021 00:32:21 +0100 Subject: [PATCH 2276/3170] Zblerg multiple forgotten import / typo >_> --- src/yunohost/data_migrations/0017_postgresql_9p6_to_11.py | 2 +- src/yunohost/domain.py | 4 ++-- src/yunohost/ssh.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/yunohost/data_migrations/0017_postgresql_9p6_to_11.py b/src/yunohost/data_migrations/0017_postgresql_9p6_to_11.py index 0526c025d..cbdfabb1f 100644 --- a/src/yunohost/data_migrations/0017_postgresql_9p6_to_11.py +++ b/src/yunohost/data_migrations/0017_postgresql_9p6_to_11.py @@ -1,7 +1,7 @@ import subprocess from moulinette import m18n -from yunohost.utils.error import YunohostError +from yunohost.utils.error import YunohostError, YunohostValidationError from moulinette.utils.log import getActionLogger from yunohost.tools import Migration diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index fdf247f89..8d8be57a0 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -28,7 +28,7 @@ import re from moulinette import m18n, msettings, msignals from moulinette.core import MoulinetteError -from yunohost.utils.error import YunohostError +from yunohost.utils.error import YunohostError, YunohostValidationError from moulinette.utils.log import getActionLogger from moulinette.utils.filesystem import write_to_file @@ -131,7 +131,7 @@ def domain_add(operation_logger, domain, dyndns=False): operation_logger.start() if dyndns: - from yunohost.dyndns import dndns_subscribe + from yunohost.dyndns import dyndns_subscribe # Actually subscribe dyndns_subscribe(domain=domain) diff --git a/src/yunohost/ssh.py b/src/yunohost/ssh.py index e9e7e1831..39d4b4287 100644 --- a/src/yunohost/ssh.py +++ b/src/yunohost/ssh.py @@ -5,7 +5,7 @@ import os import pwd import subprocess -from yunohost.utils.error import YunohostError, YunohostValidationError +from yunohost.utils.error import YunohostValidationError from moulinette.utils.filesystem import read_file, write_to_file, chown, chmod, mkdir SSHD_CONFIG_PATH = "/etc/ssh/sshd_config" From c45d1010d6ee0b3a0fd0da697234b10387f62c3f Mon Sep 17 00:00:00 2001 From: Christian Wehrli Date: Fri, 19 Mar 2021 19:58:19 +0000 Subject: [PATCH 2277/3170] Translated using Weblate (German) Currently translated at 85.3% (538 of 630 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/locales/de.json b/locales/de.json index 67a1d1a2e..832fc1b91 100644 --- a/locales/de.json +++ b/locales/de.json @@ -605,5 +605,9 @@ "postinstall_low_rootfsspace": "Das Root-Filesystem hat insgesamt weniger als 10GB freien Speicherplatz zur Verfügung, was ziemlich besorgniserregend ist! Sie werden sehr bald keinen freien Speicherplatz mehr haben! Für das Root-Filesystem werden mindestens 16GB empfohlen. Wenn Sie YunoHost trotz dieser Warnung installieren wollen, wiederholen Sie den Befehl mit --force-diskspace", "regenconf_up_to_date": "Die Konfiguration ist bereits aktuell für die Kategorie '{category}'", "regenconf_now_managed_by_yunohost": "Die Konfigurationsdatei '{conf}' wird jetzt von YunoHost (Kategorie {category}) verwaltet.", - "regenconf_updated": "Konfiguration aktualisiert für '{category}'" + "regenconf_updated": "Konfiguration aktualisiert für '{category}'", + "regenconf_pending_applying": "Wende die anstehende Konfiguration für die Kategorie {category} an...", + "regenconf_failed": "Konnte die Konfiguration für die Kategorie(n) {categories} nicht neu erstellen", + "regenconf_dry_pending_applying": "Überprüfe die anstehende Konfiguration, welche für die Kategorie {category}' aktualisiert worden wäre…", + "regenconf_would_be_updated": "Die Konfiguration wäre für die Kategorie '{category}' aktualisiert worden" } From cf884b1149f231eea590e90a91a561a8b5561f82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quent=C3=AD?= Date: Wed, 17 Mar 2021 20:36:55 +0000 Subject: [PATCH 2278/3170] Translated using Weblate (Occitan) Currently translated at 57.6% (363 of 630 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/oc/ --- locales/oc.json | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/locales/oc.json b/locales/oc.json index 1849b3f3b..7fd423617 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -327,7 +327,7 @@ "log_user_delete": "Levar l’utilizaire « {} »", "log_user_update": "Actualizar las informacions de l’utilizaire « {} »", "log_domain_main_domain": "Far venir « {} » lo domeni màger", - "log_tools_migrations_migrate_forward": "Migrar", + "log_tools_migrations_migrate_forward": "Executar las migracions", "log_tools_postinstall": "Realizar la post installacion del servidor YunoHost", "log_tools_upgrade": "Actualizacion dels paquets sistèma", "log_tools_shutdown": "Atudar lo servidor", @@ -593,5 +593,19 @@ "app_argument_password_no_default": "Error pendent l’analisi de l’argument del senhal « {name} » : l’argument de senhal pòt pas aver de valor per defaut per de rason de seguretat", "app_label_deprecated": "Aquesta comanda es estada renduda obsolèta. Mercés d'utilizar lo nòva \"yunohost user permission update\" per gerir letiquetada de l'aplication", "additional_urls_already_removed": "URL addicionala {url:s} es ja estada elimida per la permission «#permission:s»", - "additional_urls_already_added": "URL addicionadal «{url:s}'» es ja estada aponduda per la permission «{permission:s}»" + "additional_urls_already_added": "URL addicionadal «{url:s}'» es ja estada aponduda per la permission «{permission:s}»", + "migration_0015_yunohost_upgrade": "Aviada de la mesa a jorn de YunoHost...", + "migration_0015_main_upgrade": "Aviada de la mesa a nivèl generala...", + "migration_0015_patching_sources_list": "Mesa a jorn del fichièr sources.lists...", + "migration_0015_start": "Aviar la migracion cap a Buster", + "migration_description_0017_postgresql_9p6_to_11": "Migrar las basas de donadas de PostgreSQL 9.6 cap a 11", + "migration_description_0016_php70_to_php73_pools": "Migrar los fichièrs de configuracion php7.0 cap a php7.3", + "migration_description_0015_migrate_to_buster": "Mesa a nivèl dels sistèmas Debian Buster e YunoHost 4.x", + "migrating_legacy_permission_settings": "Migracion dels paramètres de permission ancians...", + "log_app_config_apply": "Aplicar la configuracion a l’aplicacion « {} »", + "log_app_config_show_panel": "Mostrar lo panèl de configuracion de l’aplicacion « {} »", + "log_app_action_run": "Executar l’accion de l’aplicacion « {} »", + "diagnosis_basesystem_hardware_model": "Lo modèl del servidor es {model}", + "backup_archive_cant_retrieve_info_json": "Obtencion impossibla de las informacions de l’archiu « {archive} »... Se pòt pas recuperar lo fichièr info.json (o es pas un fichièr json valid).", + "app_packaging_format_not_supported": "Se pòt pas installar aquesta aplicacion pr’amor que son format es pas pres en carga per vòstra version de YunoHost. Deuriatz considerar actualizar lo sistèma." } From ea5971e355edca8d133431c00e2f1c538714a85e Mon Sep 17 00:00:00 2001 From: Krzysztof Nowakowski Date: Mon, 22 Mar 2021 21:50:28 +0000 Subject: [PATCH 2279/3170] Translated using Weblate (Polish) Currently translated at 1.5% (10 of 630 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/pl/ --- locales/pl.json | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/locales/pl.json b/locales/pl.json index 7ff9fbcd2..76ce2f408 100644 --- a/locales/pl.json +++ b/locales/pl.json @@ -1,3 +1,12 @@ { - "password_too_simple_1": "Hasło musi mieć co najmniej 8 znaków" -} \ No newline at end of file + "password_too_simple_1": "Hasło musi mieć co najmniej 8 znaków", + "app_already_up_to_date": "{app:s} jest obecnie aktualna", + "app_already_installed": "{app:s} jest już zainstalowane", + "already_up_to_date": "Nic do zrobienia. Wszystko jest obecnie aktualne.", + "admin_password_too_long": "Proszę wybrać hasło krótsze niż 127 znaków", + "admin_password_changed": "Hasło administratora zostało zmienione", + "admin_password_change_failed": "Nie można zmienić hasła", + "admin_password": "Hasło administratora", + "action_invalid": "Nieprawidłowa operacja '{action:s}'", + "aborting": "Przerywanie." +} From 3a7cf0a1b3676a5e9a10e8a2a66ce3659e6ed3b3 Mon Sep 17 00:00:00 2001 From: ericgaspar Date: Wed, 24 Mar 2021 18:25:00 +0100 Subject: [PATCH 2280/3170] Update nodejs --- data/helpers.d/nodejs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/nodejs b/data/helpers.d/nodejs index c7903ddcc..6f38a3e62 100644 --- a/data/helpers.d/nodejs +++ b/data/helpers.d/nodejs @@ -1,6 +1,6 @@ #!/bin/bash -n_version=7.0.2 +n_version=7.1.0 n_install_dir="/opt/node_n" node_version_path="$n_install_dir/n/versions/node" # N_PREFIX is the directory of n, it needs to be loaded as a environment variable. @@ -17,7 +17,7 @@ ynh_install_n () { ynh_print_info --message="Installation of N - Node.js version management" # Build an app.src for n echo "SOURCE_URL=https://github.com/tj/n/archive/v${n_version}.tar.gz -SOURCE_SUM=fa80a8685f0fb1b4187fc0a1228b44f0ea2f244e063fe8f443b8913ea595af89" > "$YNH_APP_BASEDIR/conf/n.src" +SOURCE_SUM=20100f3bc56648cc414717fb7367fcf0e8229dc59a10b0530ccac90042ee0a74" > "$YNH_APP_BASEDIR/conf/n.src" # Download and extract n ynh_setup_source --dest_dir="$n_install_dir/git" --source_id=n # Install n From 87b0b10e0e6853c79cb786ada4a28ddf1a747548 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 24 Mar 2021 20:24:31 +0100 Subject: [PATCH 2281/3170] Enforce permissions on /var/cache/yunohost --- data/hooks/conf_regen/01-yunohost | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/data/hooks/conf_regen/01-yunohost b/data/hooks/conf_regen/01-yunohost index a6d672f57..30828c462 100755 --- a/data/hooks/conf_regen/01-yunohost +++ b/data/hooks/conf_regen/01-yunohost @@ -50,6 +50,8 @@ do_init_regen() { chown root:root /etc/ssowat/conf.json.persistent mkdir -p /var/cache/yunohost/repo + chown root:root /var/cache/yunohost + chmod 700 /var/cache/yunohost } do_pre_regen() { @@ -142,6 +144,9 @@ do_post_regen() { find /etc/yunohost/certs/ -type f -exec chmod 640 {} \; find /etc/yunohost/certs/ -type d -exec chmod 750 {} \; + chown root:root /var/cache/yunohost + chmod 700 /var/cache/yunohost + # 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) From d98ec6ce35489af8b2de53c1e01ffd5f2b7c0825 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 24 Mar 2021 20:25:52 +0100 Subject: [PATCH 2282/3170] Download ynh_setup_source stuff to /var/cache/yunohost/download --- data/helpers.d/utils | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 28d2352fa..0f02630a4 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -21,6 +21,9 @@ YNH_APP_BASEDIR=$([[ "$(basename $0)" =~ ^backup|restore$ ]] && echo '../setting # Requires YunoHost version 2.6.4 or higher. ynh_exit_properly () { local exit_code=$? + + rm -rf "/var/cache/yunohost/download/" + if [ "$exit_code" -eq 0 ]; then exit 0 # Exit without error if the script ended correctly fi @@ -127,8 +130,13 @@ ynh_setup_source () { src_filename="${source_id}.${src_format}" fi + # (Unused?) mecanism where one can have the file in a special local cache to not have to download it... local local_src="/opt/yunohost-apps-src/${YNH_APP_ID}/${src_filename}" + + mkdir -p /var/cache/yunohost/download/${YNH_APP_ID}/ + src_filename="/var/cache/yunohost/download/${YNH_APP_ID}/${src_filename}" + if test -e "$local_src" then cp $local_src $src_filename @@ -672,7 +680,7 @@ ynh_compare_current_package_version() { # Check validity of the comparator if [[ ! $comparison =~ (lt|le|eq|ne|ge|gt) ]]; then - ynh_die --message="Invialid comparator must be : lt, le, eq, ne, ge, gt" + ynh_die --message="Invalid comparator must be : lt, le, eq, ne, ge, gt" fi # Return the return value of dpkg --compare-versions From e27ac6ff2c07117d7f9c0ab7f4830f185e7b0d83 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 24 Mar 2021 20:28:11 +0100 Subject: [PATCH 2283/3170] Rely on YNH_APP_BASEDIR to check the sources/ dir --- data/helpers.d/utils | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 0f02630a4..905b7b2ac 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -196,18 +196,18 @@ ynh_setup_source () { fi # Apply patches - if (( $(find $YNH_CWD/../sources/patches/ -type f -name "${source_id}-*.patch" 2> /dev/null | wc --lines) > "0" )) + if (( $(find $YNH_APP_BASEDIR/sources/patches/ -type f -name "${source_id}-*.patch" 2> /dev/null | wc --lines) > "0" )) then (cd "$dest_dir" - for p in $YNH_CWD/../sources/patches/${source_id}-*.patch + for p in $YNH_APP_BASEDIR/sources/patches/${source_id}-*.patch do patch --strip=1 < $p done) || ynh_die --message="Unable to apply patches" fi # Add supplementary files - if test -e "$YNH_CWD/../sources/extra_files/${source_id}"; then - cp --archive $YNH_CWD/../sources/extra_files/$source_id/. "$dest_dir" + if test -e "$YNH_APP_BASEDIR/sources/extra_files/${source_id}"; then + cp --archive $YNH_APP_BASEDIR/sources/extra_files/$source_id/. "$dest_dir" fi } From 44637237ce97cfaac21256384030fad04716c59e Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 24 Mar 2021 19:31:59 +0000 Subject: [PATCH 2284/3170] Translated using Weblate (German) Currently translated at 86.5% (545 of 630 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/locales/de.json b/locales/de.json index 832fc1b91..bfc9c36a4 100644 --- a/locales/de.json +++ b/locales/de.json @@ -609,5 +609,12 @@ "regenconf_pending_applying": "Wende die anstehende Konfiguration für die Kategorie {category} an...", "regenconf_failed": "Konnte die Konfiguration für die Kategorie(n) {categories} nicht neu erstellen", "regenconf_dry_pending_applying": "Überprüfe die anstehende Konfiguration, welche für die Kategorie {category}' aktualisiert worden wäre…", - "regenconf_would_be_updated": "Die Konfiguration wäre für die Kategorie '{category}' aktualisiert worden" + "regenconf_would_be_updated": "Die Konfiguration wäre für die Kategorie '{category}' aktualisiert worden", + "restore_system_part_failed": "Die Systemteile '{part:s}' konnten nicht wiederhergestellt werden", + "restore_removing_tmp_dir_failed": "Ein altes, temporäres Directory konnte nicht entfernt werden", + "restore_not_enough_disk_space": "Nicht genug Speicher (Speicher: {free_space:d} B, benötigter Speicher: {needed_space:d} B, Sicherheitspuffer: {margin:d} B)", + "restore_may_be_not_enough_disk_space": "Dein System scheint nicht genug Speicherplatz zu haben (frei: {free_space:d} B, benötigter Platz: {needed_space:d} B, Sicherheitspuffer: {margin:d} B)", + "restore_extracting": "Packe die benötigten Dateien aus dem Archiv aus…", + "restore_already_installed_apps": "Folgende Apps können nicht wiederhergestellt werden, weil sie schon installiert sind: {apps}", + "regex_with_only_domain": "Du kannst regex nicht als Domain verwenden, sondern nur als Pfad" } From c2af36a6529de465da9b0c6060644a286f5de1ce Mon Sep 17 00:00:00 2001 From: Flavio Cristoforetti Date: Wed, 24 Mar 2021 14:03:54 +0000 Subject: [PATCH 2285/3170] Translated using Weblate (Italian) Currently translated at 100.0% (630 of 630 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/it/ --- locales/it.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/locales/it.json b/locales/it.json index 9f8f6a167..45b85d548 100644 --- a/locales/it.json +++ b/locales/it.json @@ -674,5 +674,9 @@ "diagnosis_mail_blacklist_reason": "Il motivo della blacklist è: {reason}", "diagnosis_mail_blacklist_listed_by": "Il tuo IP o dominio {item} è nella blacklist {blacklist_name}", "diagnosis_backports_in_sources_list": "Sembra che apt (il package manager) sia configurato per utilizzare le backport del repository. A meno che tu non sappia quello che stai facendo, scoraggiamo fortemente di installare pacchetti tramite esse, perché ci sono alte probabilità di creare conflitti con il tuo sistema.", - "diagnosis_basesystem_hardware_model": "Modello server: {model}" + "diagnosis_basesystem_hardware_model": "Modello server: {model}", + "postinstall_low_rootfsspace": "La radice del filesystem ha uno spazio totale inferiore ai 10 GB, ed è piuttosto preoccupante! Consumerai tutta la memoria molto velocemente! Raccomandiamo di avere almeno 16 GB per la radice del filesystem. Se vuoi installare YunoHost ignorando questo avviso, esegui nuovamente il postinstall con l'argomento --force-diskspace", + "domain_remove_confirm_apps_removal": "Rimuovere questo dominio rimuoverà anche le seguenti applicazioni:\n{apps}\n\nSei sicuro di voler continuare? [{answers}]", + "diagnosis_rootfstotalspace_critical": "La radice del filesystem ha un totale di solo {space}, ed è piuttosto preoccupante! Probabilmente consumerai tutta la memoria molto velocemente! Raccomandiamo di avere almeno 16 GB per la radice del filesystem.", + "diagnosis_rootfstotalspace_warning": "La radice del filesystem ha un totale di solo {space}. Potrebbe non essere un problema, ma stai attento perché potresti consumare tutta la memoria velocemente... Raccomandiamo di avere almeno 16 GB per la radice del filesystem." } From 8751dcdd1b2c9c5133dc1997419ef84861147097 Mon Sep 17 00:00:00 2001 From: Kayou Date: Thu, 25 Mar 2021 01:05:41 +0100 Subject: [PATCH 2286/3170] Update changelog for 4.2 (#1195) * Update changelog for 4.2 * changelog: Improving error semantic * Update changelog Co-authored-by: Alexandre Aubin --- debian/changelog | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/debian/changelog b/debian/changelog index 0454553ae..f8211b82e 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,8 +1,39 @@ -yunohost (4.2) unstable; urgency=low +yunohost (4.2.0) testing; urgency=low - - Placeholder for 4.2 to satisfy CI / debian build during dev + - [mod] Python2 -> Python3 ([#1116](https://github.com/yunohost/yunohost/pull/1116), a97a9df3, 1387dff4, b53859db, f5ab4443, f9478b93, dc6033c3) + - [mod] refactoring: Drop legacy-way of passing arguments in hook_exec, prevent exposing secrets in command line args ([#1096](https://github.com/yunohost/yunohost/pull/1096)) + - [mod] refactoring: use regen_conf instead of service_regen_conf in settings.py (9c11fd58) + - [mod] refactoring: More consistent local CA management for simpler postinstall ([#1062](https://github.com/yunohost/yunohost/pull/1062)) + - [mod] refactoring: init folders during .deb install instead of regen conf ([#1063](https://github.com/yunohost/yunohost/pull/1063)) + - [mod] refactoring: init ldap before the postinstall ([#1064](https://github.com/yunohost/yunohost/pull/1064)) + - [mod] refactoring: simpler and more consistent logging initialization ([#1119](https://github.com/yunohost/yunohost/pull/1119), 0884a0c1) + - [mod] code-quality: add CI job to auto-format code, fix linter errors ([#1142](https://github.com/yunohost/yunohost/pull/1142), [#1161](https://github.com/yunohost/yunohost/pull/1161), 97f26015, [#1162](https://github.com/yunohost/yunohost/pull/1162)) + - [mod] misc: Prevent the installation of apache2 ... ([#1148](https://github.com/yunohost/yunohost/pull/1148)) + - [mod] misc: Drop old cache rules for .ms files, not relevant anymore ([#1150](https://github.com/yunohost/yunohost/pull/1150)) + - [fix] misc: Abort postinstall if /etc/yunohost/apps ain't empty ([#1147](https://github.com/yunohost/yunohost/pull/1147)) + - [mod] misc: No need for mysql root password anymore ([#912](https://github.com/YunoHost/yunohost/pull/912)) + - [fix] app operations: wait for services to finish reloading (4a19a60b) + - [enh] ux: Improve error semantic such that the webadmin can autoredirect to the proper log view ([#1077](https://github.com/yunohost/yunohost/pull/1077), [#1187](https://github.com/YunoHost/yunohost/pull/1187)) + - [mod] cli/api: Misc command and routes renaming / aliasing ([#1146](https://github.com/yunohost/yunohost/pull/1146)) + - [enh] cli: Add a new "yunohost app search" command ([#1070](https://github.com/yunohost/yunohost/pull/1070)) + - [enh] cli: Add '--remove-apps' (and '--force') options to "yunohost domain remove" ([#1125](https://github.com/yunohost/yunohost/pull/1125)) + - [enh] diagnosis: Report low total space for rootfs ([#1145](https://github.com/yunohost/yunohost/pull/1145)) + - [fix] upnp: Handle port closing ([#1154](https://github.com/yunohost/yunohost/pull/1154)) + - [fix] dyndns: clean old madness, improve update strategy, improve cron management, delete dyndns key upon domain removal ([#1149](https://github.com/yunohost/yunohost/pull/1149)) + - [enh] helpers: Adding composer helper ([#1090](https://github.com/yunohost/yunohost/pull/1090)) + - [enh] helpers: Upgrade n to v7.0.2 ([#1178](https://github.com/yunohost/yunohost/pull/1178)) + - [enh] helpers: Add multimedia helpers and hooks ([#1129](https://github.com/yunohost/yunohost/pull/1129), 47420c62) + - [enh] helpers: Normalize conf template handling for nginx, php-fpm, systemd and fail2ban using ynh_add_config ([#1118](https://github.com/yunohost/yunohost/pull/1118)) + - [fix] helpers, doc: Update template for the new doc (grav) ([#1167](https://github.com/yunohost/yunohost/pull/1167), [#1168](https://github.com/yunohost/yunohost/pull/1168), 59d3e387) + - [enh] helpers: Define YNH_APP_BASEDIR to be able to properly point to conf folder depending on the app script we're running ([#1172](https://github.com/yunohost/yunohost/pull/1172)) + - [enh] helpers: Use jq / output-as json to get info from yunohost commands instead of scraping with grep ([#1160](https://github.com/yunohost/yunohost/pull/1160)) + - [fix] helpers: Misc fixes/enh (b85d959d, db93b82b, ce04570b, 07f8d6d7) + - [fix] helpers: download ynh_setup_source stuff in /var/cache/yunohost to prevent situations where it ends up in /etc/yunohost/apps/ (d98ec6ce) + - [i18n] Translations updated for Catalan, Chinese (Simplified), Czech, Dutch, French, German, Italian, Occitan, Polish - -- Alexandre Aubin Wed, 20 Jan 2021 05:19:58 +0100 + Thanks to all contributors <3 ! (Bram, Christian W., Daniel, Dave, Éric G., Félix P., Flavio C., Kay0u, Krzysztof N., ljf, Mathieu M., Miloš K., MrMorals, Nils V.Z., penguin321, ppr, Quentí, Radek S, Scapharnaum, Sébastien M., xaloc33, yalh76, Yifei D.) + + -- Alexandre Aubin Thu, 25 Mar 2021 01:00:00 +0100 yunohost (4.1.7.4) stable; urgency=low From fb1fddd07ec97240aef9619a79bbe261a730435a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 25 Mar 2021 14:59:47 +0100 Subject: [PATCH 2287/3170] To be consistent with migration 0020, all new users should have /bin/bash as terminal, also we probably don't care about fetching loginShell anymore --- src/yunohost/ssh.py | 4 ---- src/yunohost/tests/test_apps_arguments_parsing.py | 10 ---------- src/yunohost/user.py | 11 ++--------- 3 files changed, 2 insertions(+), 23 deletions(-) diff --git a/src/yunohost/ssh.py b/src/yunohost/ssh.py index 428b70ea3..0e20737d0 100644 --- a/src/yunohost/ssh.py +++ b/src/yunohost/ssh.py @@ -151,8 +151,6 @@ def _get_user_for_ssh(username, attrs=None): "username": "root", "fullname": "", "mail": "", - "ssh_allowed": ssh_root_login_status()["PermitRootLogin"], - "shell": root_unix.pw_shell, "home_path": root_unix.pw_dir, } @@ -162,8 +160,6 @@ def _get_user_for_ssh(username, attrs=None): "username": "admin", "fullname": "", "mail": "", - "ssh_allowed": admin_unix.pw_shell.strip() != "/bin/false", - "shell": admin_unix.pw_shell, "home_path": admin_unix.pw_dir, } diff --git a/src/yunohost/tests/test_apps_arguments_parsing.py b/src/yunohost/tests/test_apps_arguments_parsing.py index 98dd280ff..95d1548ae 100644 --- a/src/yunohost/tests/test_apps_arguments_parsing.py +++ b/src/yunohost/tests/test_apps_arguments_parsing.py @@ -1206,7 +1206,6 @@ def test_parse_args_in_yunohost_format_user_empty(): "some_user": { "ssh_allowed": False, "username": "some_user", - "shell": "/bin/false", "mailbox-quota": "0", "mail": "p@ynh.local", "fullname": "the first name the last name", @@ -1232,7 +1231,6 @@ def test_parse_args_in_yunohost_format_user(): username: { "ssh_allowed": False, "username": "some_user", - "shell": "/bin/false", "mailbox-quota": "0", "mail": "p@ynh.local", "fullname": "the first name the last name", @@ -1261,7 +1259,6 @@ def test_parse_args_in_yunohost_format_user_two_users(): username: { "ssh_allowed": False, "username": "some_user", - "shell": "/bin/false", "mailbox-quota": "0", "mail": "p@ynh.local", "fullname": "the first name the last name", @@ -1269,7 +1266,6 @@ def test_parse_args_in_yunohost_format_user_two_users(): other_user: { "ssh_allowed": False, "username": "some_user", - "shell": "/bin/false", "mailbox-quota": "0", "mail": "z@ynh.local", "fullname": "john doe", @@ -1304,7 +1300,6 @@ def test_parse_args_in_yunohost_format_user_two_users_wrong_answer(): username: { "ssh_allowed": False, "username": "some_user", - "shell": "/bin/false", "mailbox-quota": "0", "mail": "p@ynh.local", "fullname": "the first name the last name", @@ -1312,7 +1307,6 @@ def test_parse_args_in_yunohost_format_user_two_users_wrong_answer(): other_user: { "ssh_allowed": False, "username": "some_user", - "shell": "/bin/false", "mailbox-quota": "0", "mail": "z@ynh.local", "fullname": "john doe", @@ -1339,7 +1333,6 @@ def test_parse_args_in_yunohost_format_user_two_users_no_default(): username: { "ssh_allowed": False, "username": "some_user", - "shell": "/bin/false", "mailbox-quota": "0", "mail": "p@ynh.local", "fullname": "the first name the last name", @@ -1347,7 +1340,6 @@ def test_parse_args_in_yunohost_format_user_two_users_no_default(): other_user: { "ssh_allowed": False, "username": "some_user", - "shell": "/bin/false", "mailbox-quota": "0", "mail": "z@ynh.local", "fullname": "john doe", @@ -1369,7 +1361,6 @@ def test_parse_args_in_yunohost_format_user_two_users_default_input(): username: { "ssh_allowed": False, "username": "some_user", - "shell": "/bin/false", "mailbox-quota": "0", "mail": "p@ynh.local", "fullname": "the first name the last name", @@ -1377,7 +1368,6 @@ def test_parse_args_in_yunohost_format_user_two_users_default_input(): other_user: { "ssh_allowed": False, "username": "some_user", - "shell": "/bin/false", "mailbox-quota": "0", "mail": "z@ynh.local", "fullname": "john doe", diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 4098092e5..455c71139 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -53,7 +53,6 @@ def user_list(fields=None): "cn": "fullname", "mail": "mail", "maildrop": "mail-forward", - "loginShell": "shell", "homeDirectory": "home_path", "mailuserquota": "mailbox-quota", } @@ -69,7 +68,7 @@ def user_list(fields=None): else: raise YunohostError("field_invalid", attr) else: - attrs = ["uid", "cn", "mail", "mailuserquota", "loginShell"] + attrs = ["uid", "cn", "mail", "mailuserquota"] ldap = _get_ldap_interface() result = ldap.search( @@ -82,12 +81,6 @@ def user_list(fields=None): entry = {} for attr, values in user.items(): if values: - if attr == "loginShell": - if values[0].strip() == "/bin/false": - entry["ssh_allowed"] = False - else: - entry["ssh_allowed"] = True - entry[user_attrs[attr]] = values[0] uid = entry[user_attrs["uid"]] @@ -206,7 +199,7 @@ def user_create( "gidNumber": [uid], "uidNumber": [uid], "homeDirectory": ["/home/" + username], - "loginShell": ["/bin/false"], + "loginShell": ["/bin/bash"], } # If it is the first user, add some aliases From 514112a5381387de6c10322ae5675064cd4d4776 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 25 Mar 2021 16:03:18 +0100 Subject: [PATCH 2288/3170] Misc permission tweak, set everything as owned by root by default --- 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 f0d0c7005..8a118726d 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -330,7 +330,7 @@ ynh_add_config () { # (cp won't overwrite ownership / modes by default...) touch $destination chown root:root $destination - chmod 750 $destination + chmod 640 $destination cp -f "$template_path" "$destination" @@ -725,11 +725,11 @@ _ynh_apply_default_permissions() { if [ -z "$ynh_requirements" ] || [ "$ynh_requirements" == "null" ] || dpkg --compare-versions $ynh_requirements ge 4.2 then chmod o-rwx $target + chmod g-w $target + chown -R root:root $target if ynh_system_user_exists $app then chown $app:$app $target - else - chown root:root $target fi fi } From f158a4da9e78050820903ecda0b6f5ac42d801bd Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 25 Mar 2021 16:10:00 +0100 Subject: [PATCH 2289/3170] Use YunohostValidationError instead of raw Exceptions --- src/yunohost/ssh.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/yunohost/ssh.py b/src/yunohost/ssh.py index 0e20737d0..e2ecaeef3 100644 --- a/src/yunohost/ssh.py +++ b/src/yunohost/ssh.py @@ -13,7 +13,7 @@ SSHD_CONFIG_PATH = "/etc/ssh/sshd_config" def user_ssh_list_keys(username): user = _get_user_for_ssh(username, ["homeDirectory"]) if not user: - raise Exception("User with username '%s' doesn't exists" % username) + raise YunohostValidationError("user_unknown", user=username) authorized_keys_file = os.path.join( user["homeDirectory"][0], ".ssh", "authorized_keys" @@ -50,7 +50,7 @@ def user_ssh_list_keys(username): def user_ssh_add_key(username, key, comment): user = _get_user_for_ssh(username, ["homeDirectory", "uid"]) if not user: - raise Exception("User with username '%s' doesn't exists" % username) + raise YunohostValidationError("user_unknown", user=username) authorized_keys_file = os.path.join( user["homeDirectory"][0], ".ssh", "authorized_keys" @@ -90,21 +90,26 @@ def user_ssh_add_key(username, key, comment): def user_ssh_remove_key(username, key): user = _get_user_for_ssh(username, ["homeDirectory", "uid"]) if not user: - raise Exception("User with username '%s' doesn't exists" % username) + raise YunohostValidationError("user_unknown", user=username) authorized_keys_file = os.path.join( user["homeDirectory"][0], ".ssh", "authorized_keys" ) if not os.path.exists(authorized_keys_file): - raise Exception( - "this key doesn't exists ({} dosesn't exists)".format(authorized_keys_file) + raise YunohostValidationError( + "this key doesn't exists ({} dosesn't exists)".format(authorized_keys_file), + raw_msg=True ) authorized_keys_content = read_file(authorized_keys_file) if key not in authorized_keys_content: - raise Exception("Key '{}' is not present in authorized_keys".format(key)) + raise YunohostValidationError( + "Key '{}' is not present in authorized_keys".format(key), + raw_msg=True + ) + # don't delete the previous comment because we can't verify if it's legit From b40f21458f82c3c9fc3619ac9593f6cca7f6ba73 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 25 Mar 2021 16:19:40 +0100 Subject: [PATCH 2290/3170] ssh config: indent, misc readabilty improvements --- data/templates/ssh/sshd_config | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/data/templates/ssh/sshd_config b/data/templates/ssh/sshd_config index bb6520e64..28e424aa8 100644 --- a/data/templates/ssh/sshd_config +++ b/data/templates/ssh/sshd_config @@ -78,18 +78,17 @@ Subsystem sftp internal-sftp # Apply following instructions to user with sftp perm only Match Group sftp.main,!ssh.main -ForceCommand internal-sftp -# We currently are not able to restrict /home/USER -# So we chroot only on /home -# See https://serverfault.com/questions/584986/bad-ownership-or-modes-for-chroot-directory-component -#ChrootDirectory /home/%u -ChrootDirectory /home -# Forbid SFTP users from using their account SSH as a VPN (even if SSH login is disabled) -AllowTcpForwarding no -AllowStreamLocalForwarding no -PermitTunnel no -# Disable .ssh/rc, which could be edited (e.g. from Nextcloud or whatever) by users to execute arbitrary commands even if SSH login is disabled -PermitUserRC no + ForceCommand internal-sftp + # We can't restrict to /home/%u because the chroot base must be owned by root + # So we chroot only on /home + # See https://serverfault.com/questions/584986/bad-ownership-or-modes-for-chroot-directory-component + ChrootDirectory /home + # Forbid SFTP users from using their account SSH as a VPN (even if SSH login is disabled) + AllowTcpForwarding no + AllowStreamLocalForwarding no + PermitTunnel no + # Disable .ssh/rc, which could be edited (e.g. from Nextcloud or whatever) by users to execute arbitrary commands even if SSH login is disabled + PermitUserRC no # root login is allowed on local networks @@ -98,4 +97,4 @@ PermitUserRC no # If the server is a VPS, it's expected that the owner of the # server has access to a web console through which to log in. Match Address 192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,169.254.0.0/16,fe80::/10,fd00::/8 - PermitRootLogin yes + PermitRootLogin yes From b3675051570507643f218d14b35dcb0ab034f5cd Mon Sep 17 00:00:00 2001 From: MercierCorentin Date: Thu, 25 Mar 2021 20:52:14 +0100 Subject: [PATCH 2291/3170] XMPP configuration for subdomains (i.e. not owned zone dns) --- src/yunohost/domain.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 1909a0353..56b04e95b 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -461,20 +461,24 @@ def _build_dns_conf(domains): extra = [] ipv4 = get_public_ip() ipv6 = get_public_ip(6) + owned_dns_zone = "owned_dns_zone" in domains[root] and domains[root]["owned_dns_zone"] - name_prefix = root.partition(".")[0] - + root_prefix = root.partition(".")[0] + child_domain_suffix = "" for domain_name, domain in domains.items(): ttl = domain["ttl"] - owned_dns_zone = "owned_dns_zone" in domains[root] and domains[root]["owned_dns_zone"] if domain_name == root: - name = name_prefix if not owned_dns_zone else "@" + name = root_prefix if not owned_dns_zone else "@" else: name = domain_name[0:-(1 + len(root))] if not owned_dns_zone: - name += "." + name_prefix + name += "." + root_prefix + + if name != "@": + child_domain_suffix = "." + name + ########################### # Basic ipv4/ipv6 records # @@ -514,10 +518,10 @@ def _build_dns_conf(domains): xmpp += [ ["_xmpp-client._tcp", ttl, "SRV", "0 5 5222 %s." % domain_name], ["_xmpp-server._tcp", ttl, "SRV", "0 5 5269 %s." % domain_name], - ["muc", ttl, "CNAME", name], - ["pubsub", ttl, "CNAME", name], - ["vjud", ttl, "CNAME", name], - ["xmpp-upload", ttl, "CNAME", name], + ["muc" + child_domain_suffix, ttl, "CNAME", name], + ["pubsub" + child_domain_suffix, ttl, "CNAME", name], + ["vjud" + child_domain_suffix, ttl, "CNAME", name], + ["xmpp-upload" + child_domain_suffix, ttl, "CNAME", name], ] ######### From 503a5ed6d2562b9f7b9b39f9a4e290dffd790018 Mon Sep 17 00:00:00 2001 From: Paco Date: Thu, 25 Mar 2021 21:08:08 +0100 Subject: [PATCH 2292/3170] Add `yunohost domain config list` command --- data/actionsmap/yunohost.yml | 5 ----- src/yunohost/domain.py | 10 ++++++++++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 138f7c6cd..c84e2c4b0 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -560,11 +560,6 @@ domain: list: action_help: Get settings for all domains api: GET /domains/list - arguments: - domain: - help: Target domain - extra: - pattern: *pattern_domain # domain_config_show show: diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index eec362bcb..e050ae6d6 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -719,6 +719,16 @@ def _load_domain_settings(): return new_domains +def domain_config_list(): + """ + Show settings of all domains + + Keyword arguments: + domain -- The domain name + """ + return _load_domain_settings() + + def domain_config_show(domain): """ Show settings of a domain From afe62877d3a87b8a93e65463002b140052aa1e45 Mon Sep 17 00:00:00 2001 From: MercierCorentin Date: Mon, 29 Mar 2021 14:46:03 +0200 Subject: [PATCH 2293/3170] Domain settings: transform cli to be like app settings --- data/actionsmap/yunohost.yml | 67 ++++++----------------- src/yunohost/domain.py | 103 ++++++++++++++--------------------- 2 files changed, 56 insertions(+), 114 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index c84e2c4b0..b363a218f 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -551,57 +551,22 @@ domain: pattern: *pattern_domain path: help: The path to check (e.g. /coffee) - - subcategories: - config: - subcategory_help: Domains DNS settings - actions: - # domain_config_list - list: - action_help: Get settings for all domains - api: GET /domains/list - - # domain_config_show - show: - action_help: Get settings for all domains - api: GET /domains//show - arguments: - domain: - help: Target domain - extra: - pattern: *pattern_domain - - # domain_config_get - get: - action_help: Get specific setting of a domain - api: GET /domains// - arguments: - domain: - help: Target domain - extra: - pattern: *pattern_domain - key: - help: Setting requested. One of ttl, xmpp, mail, owned_dns_zone - extra: - pattern: &pattern_domain_key - - !!str ^(ttl)|(xmpp)|(mail)|(owned_dns_zone)|$ - - "pattern_domain_key" - - # domain_config_set - set: - action_help: Set a setting of a domain - api: POST /domains// - arguments: - domain: - help: Target domain - extra: - pattern: *pattern_domain - key: - help: Setting requested. One of ttl (Time To Live of this domain's DNS records), xmpp (Configure XMPP in this domain's DNS records?), mail (Configure mail in this domain's DNS records?), owned_dns_zone (Is it a full domain, i.e not a subdomain?) - extra: - pattern: *pattern_domain_key - value: - help: Value of the setting. Must be a positive integer number for "ttl", or one of ("True", "False", "true", "false", "1", "0") for other settings + ### domain_setting() + setting: + action_help: Set or get an app setting value + api: GET /domains//settings + arguments: + domain: + help: Domaine name + key: + help: Key to get/set + -v: + full: --value + help: Value to set + -d: + full: --delete + help: Delete the key + action: store_true ### domain_info() diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index d88eb6b60..e799f52b2 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -722,38 +722,48 @@ def _load_domain_settings(): return new_domains - -def domain_config_list(): +def domain_setting(domain, key, value=None, delete=False): """ - Show settings of all domains + Set or get an app setting value + + Keyword argument: + value -- Value to set + app -- App ID + key -- Key to get/set + delete -- Delete the key - Keyword arguments: - domain -- The domain name """ - return _load_domain_settings() + domains = _load_domain_settings() + if not domain in domains.keys(): + # TODO add locales + raise YunohostError("domain_name_unknown", domain=domain) + + domain_settings = _get_domain_settings(domain, False) or {} -def domain_config_show(domain): - """ - Show settings of a domain + # GET + if value is None and not delete: + return domain_settings.get(key, None) - Keyword arguments: - domain -- The domain name - """ - return _get_domain_settings(domain, False) + # DELETE + if delete: + if key in domain_settings: + del domain_settings[key] + # SET + else: + + if "ttl" == key: + try: + ttl = int(value) + except: + # TODO add locales + raise YunohostError("bad_value_type", value_type=type(ttl)) -def domain_config_get(domain, key): - """ - Show a setting of a domain - - Keyword arguments: - domain -- The domain name - key -- ttl, xmpp, mail, owned_dns_zone - """ - settings = _get_domain_settings(domain, False) - return settings[domain][key] - + if ttl < 0: + # TODO add locales + raise YunohostError("must_be_positive", value_type=type(ttl)) + domain_settings[key] = value def _get_domain_settings(domain, subdomains): """ @@ -780,55 +790,22 @@ def _get_domain_settings(domain, subdomains): return only_wanted_domains -def domain_config_set(domain, key, value): - #(domain, ttl=None, xmpp=None, mail=None, owned_dns_zone=None): +def _set_domain_settings(domain, domain_settings): """ - Set some settings of a domain, for DNS generation. + Set settings of a domain Keyword arguments: domain -- The domain name - key must be one of this strings: - ttl -- the Time To Live for this domains DNS record - xmpp -- configure XMPP DNS records for this domain - mail -- configure mail DNS records for this domain - owned_dns_zone -- is this domain DNS zone owned? (is it a full domain or a subdomain?) - value must be set according to the key + settings -- Dict with doamin settings + """ domains = _load_domain_settings() - if not domain in domains.keys(): - # TODO add locales raise YunohostError("domain_name_unknown", domain=domain) - if "ttl" == key: - try: - ttl = int(value) - except: - # TODO add locales - raise YunohostError("bad_value_type", value_type=type(ttl)) - - if ttl < 0: - # TODO add locales - raise YunohostError("must_be_positive", value_type=type(ttl)) - - domains[domain]["ttl"] = ttl - - elif "xmpp" == key: - domains[domain]["xmpp"] = value in ["True", "true", "1"] - - elif "mail" == key: - domains[domain]["mail"] = value in ["True", "true", "1"] - - elif "owned_dns_zone" == key: - domains[domain]["owned_dns_zone"] = value in ["True", "true", "1"] - - else: - # TODO add locales - raise YunohostError("no_setting_given") + domains[domain] = domain_settings # Save the settings to the .yaml file with open(DOMAIN_SETTINGS_PATH, 'w') as file: - yaml.dump(domains, file) - - return domains[domain] + yaml.dump(domains, file, default_flow_style=False) From 45b55d1050383feb355d2d76108c31b2b2ae456c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 31 Mar 2021 17:41:20 +0200 Subject: [PATCH 2294/3170] Add --keep to ynh_setup_source to keep files that may be overwritten during upgrade (#1200) --- data/helpers.d/utils | 46 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 4 deletions(-) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 905b7b2ac..942f6b34d 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -63,9 +63,10 @@ ynh_abort_if_errors () { # Download, check integrity, uncompress and patch the source from app.src # -# usage: ynh_setup_source --dest_dir=dest_dir [--source_id=source_id] +# usage: ynh_setup_source --dest_dir=dest_dir [--source_id=source_id] [--keep="file1 file2"] # | arg: -d, --dest_dir= - Directory where to setup sources # | arg: -s, --source_id= - Name of the source, defaults to `app` +# | arg: -k, --keep= - Space-separated list of files/folders that will be backup/restored in $dest_dir, such as a config file you don't want to overwrite. For example 'conf.json secrets.json logs/' # # This helper will read `conf/${source_id}.src`, download and install the sources. # @@ -100,13 +101,15 @@ ynh_abort_if_errors () { # Requires YunoHost version 2.6.4 or higher. ynh_setup_source () { # Declare an array to define the options of this helper. - local legacy_args=ds - local -A args_array=( [d]=dest_dir= [s]=source_id= ) + local legacy_args=dsk + local -A args_array=( [d]=dest_dir= [s]=source_id= [k]=keep= ) local dest_dir local source_id + local keep # Manage arguments with getopts ynh_handle_getopts_args "$@" - source_id="${source_id:-app}" # If the argument is not given, source_id equals "app" + source_id="${source_id:-app}" + keep="${keep:-}" local src_file_path="$YNH_APP_BASEDIR/conf/${source_id}.src" @@ -156,6 +159,24 @@ ynh_setup_source () { echo "${src_sum} ${src_filename}" | ${src_sumprg} --check --status \ || ynh_die --message="Corrupt source" + # Keep files to be backup/restored at the end of the helper + # Assuming $dest_dir already exists + rm -rf /var/cache/yunohost/files_to_keep_during_setup_source/ + if [ -n "$keep" ] && [ -e "$dest_dir" ] + then + local keep_dir=/var/cache/yunohost/files_to_keep_during_setup_source/${YNH_APP_ID} + mkdir -p $keep_dir + local stuff_to_keep + for stuff_to_keep in $keep + do + if [ -e "$dest_dir/$stuff_to_keep" ] + then + mkdir --parents "$(dirname "$keep_dir/$stuff_to_keep")" + cp --archive "$dest_dir/$stuff_to_keep" "$keep_dir/$stuff_to_keep" + fi + done + fi + # Extract source into the app dir mkdir --parents "$dest_dir" @@ -209,6 +230,23 @@ ynh_setup_source () { if test -e "$YNH_APP_BASEDIR/sources/extra_files/${source_id}"; then cp --archive $YNH_APP_BASEDIR/sources/extra_files/$source_id/. "$dest_dir" fi + + # Keep files to be backup/restored at the end of the helper + # Assuming $dest_dir already exists + if [ -n "$keep" ] + then + local keep_dir=/var/cache/yunohost/files_to_keep_during_setup_source/${YNH_APP_ID} + local stuff_to_keep + for stuff_to_keep in $keep + do + if [ -e "$keep_dir/$stuff_to_keep" ] + then + mkdir --parents "$(dirname "$dest_dir/$stuff_to_keep")" + cp --archive "$keep_dir/$stuff_to_keep" "$dest_dir/$stuff_to_keep" + fi + done + fi + rm -rf /var/cache/yunohost/files_to_keep_during_setup_source/ } # Curl abstraction to help with POST requests to local pages (such as installation forms) From 4f44df388e54198ea2e27013a17b964ee6d7295f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 1 Apr 2021 15:51:49 +0200 Subject: [PATCH 2295/3170] services.py, python3: missing decode() in subprocess output fetch --- src/yunohost/service.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index d7c3e1db0..e6e960a57 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -465,6 +465,7 @@ def _get_and_format_service_status(service, infos): if p.returncode == 0: output["configuration"] = "valid" else: + out = out.decode() output["configuration"] = "broken" output["configuration-details"] = out.strip().split("\n") From cd1f64383bdac3994fec2ca7a39e02e63a7a1372 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 1 Apr 2021 15:55:46 +0200 Subject: [PATCH 2296/3170] log.py: don't inject log_ref if the operation didnt start yet --- src/yunohost/log.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/log.py b/src/yunohost/log.py index 7a45565f8..592e76bb4 100644 --- a/src/yunohost/log.py +++ b/src/yunohost/log.py @@ -636,7 +636,7 @@ class OperationLogger(object): # we want to inject the log ref in the exception, such that it may be # transmitted to the webadmin which can then redirect to the appropriate # log page - if isinstance(error, Exception) and not isinstance(error, YunohostValidationError): + if self.started_at and isinstance(error, Exception) and not isinstance(error, YunohostValidationError): error.log_ref = self.name if self.ended_at is not None or self.started_at is None: From 0ac57e5305e825a294b8ef04fe69222f42fe1cac Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 25 Mar 2021 18:26:46 +0100 Subject: [PATCH 2297/3170] Add drafty test system for helpers --- tests/helpers.tests/ynhtest_setup_source | 57 ++++++++++++++++++++++++ tests/test_helpers.sh | 43 ++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 tests/helpers.tests/ynhtest_setup_source create mode 100644 tests/test_helpers.sh diff --git a/tests/helpers.tests/ynhtest_setup_source b/tests/helpers.tests/ynhtest_setup_source new file mode 100644 index 000000000..33d685844 --- /dev/null +++ b/tests/helpers.tests/ynhtest_setup_source @@ -0,0 +1,57 @@ +EXAMPLE_SRC=" +SOURCE_URL=https://github.com/Hextris/hextris/archive/8872ec47d694628e2fe668ebaa90b13d5626d95f.tar.gz +SOURCE_SUM=67f3fbd54c405717a25fb1e6f71d2b46e94c7ac6971861dd99ae5e58f6609892 +" + +ynhtest_setup_source_nominal() { + mkdir -p /tmp/var/www/ + final_path="$(mktemp -d -p /tmp/var/www)" + mkdir ../conf + echo "$EXAMPLE_SRC" > ../conf/test.src + + ynh_setup_source --dest_dir="$final_path" --source_id="test" + + test -e "$final_path" + test -e "$final_path/index.html" +} + + +ynhtest_setup_source_nominal_upgrade() { + mkdir -p /tmp/var/www/ + final_path="$(mktemp -d -p /tmp/var/www)" + mkdir ../conf + echo "$EXAMPLE_SRC" > ../conf/test.src + + ynh_setup_source --dest_dir="$final_path" --source_id="test" + + test -e "$final_path" + test -e "$final_path/index.html" + + # Except index.html to get overwritten during next ynh_setup_source + echo "HELLOWORLD" > $final_path/index.html + test "$(cat $final_path/index.html)" == "HELLOWORLD" + + ynh_setup_source --dest_dir="$final_path" --source_id="test" + + test "$(cat $final_path/index.html)" != "HELLOWORLD" +} + + +ynhtest_setup_source_with_keep() { + mkdir -p /tmp/var/www/ + final_path="$(mktemp -d -p /tmp/var/www)" + mkdir ../conf + echo "$EXAMPLE_SRC" > ../conf/test.src + + echo "HELLOWORLD" > $final_path/index.html + echo "HELLOWORLD" > $final_path/test.conf.txt + + ynh_setup_source --dest_dir="$final_path" --source_id="test" --keep="index.html test.conf.txt" + + test -e "$final_path" + test -e "$final_path/index.html" + test -e "$final_path/test.conf.txt" + test "$(cat $final_path/index.html)" == "HELLOWORLD" + test "$(cat $final_path/test.conf.txt)" == "HELLOWORLD" +} + diff --git a/tests/test_helpers.sh b/tests/test_helpers.sh new file mode 100644 index 000000000..1a33a2433 --- /dev/null +++ b/tests/test_helpers.sh @@ -0,0 +1,43 @@ +readonly NORMAL=$(printf '\033[0m') +readonly BOLD=$(printf '\033[1m') +readonly RED=$(printf '\033[31m') +readonly GREEN=$(printf '\033[32m') +readonly ORANGE=$(printf '\033[33m') + +function log_test() +{ + echo -n "${BOLD}$1${NORMAL} ... " +} + +function log_passed() +{ + echo "${BOLD}${GREEN}✔ Passed${NORMAL}" +} + +function log_failed() +{ + echo "${BOLD}${RED}✘ Failed${NORMAL}" +} + +source /usr/share/yunohost/helpers +for TEST_SUITE in $(ls helpers.tests/*) +do + source $TEST_SUITE +done + +TESTS=$(declare -F | grep ' ynhtest_' | awk '{print $3}') + +for TEST in $TESTS +do + log_test $TEST + cd $(mktemp -d) + (app=ynhtest + YNH_APP_ID=$app + mkdir scripts + cd scripts + set -eu + $TEST + ) > ./test.log 2>&1 \ + && log_passed \ + || { echo -e "\n----------"; cat ./test.log; echo -e "----------"; log_failed; } +done From 94beb52b0863724b15393d3e3a47d3d3d074d525 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 25 Mar 2021 18:30:20 +0100 Subject: [PATCH 2298/3170] Rename to .sh for syntax highlighting, add /bin/bash shebang --- .../{ynhtest_setup_source => ynhtest_setup_source.sh} | 0 tests/test_helpers.sh | 2 ++ 2 files changed, 2 insertions(+) rename tests/helpers.tests/{ynhtest_setup_source => ynhtest_setup_source.sh} (100%) diff --git a/tests/helpers.tests/ynhtest_setup_source b/tests/helpers.tests/ynhtest_setup_source.sh similarity index 100% rename from tests/helpers.tests/ynhtest_setup_source rename to tests/helpers.tests/ynhtest_setup_source.sh diff --git a/tests/test_helpers.sh b/tests/test_helpers.sh index 1a33a2433..399345796 100644 --- a/tests/test_helpers.sh +++ b/tests/test_helpers.sh @@ -1,3 +1,5 @@ +#!/bin/bash + readonly NORMAL=$(printf '\033[0m') readonly BOLD=$(printf '\033[1m') readonly RED=$(printf '\033[31m') From 875fead9b57c4545b99cd15c58f10fec4cc659f4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 1 Apr 2021 15:47:56 +0200 Subject: [PATCH 2299/3170] Add test-helpers in gitlab CI pipeline --- .gitlab/ci/test.gitlab-ci.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.gitlab/ci/test.gitlab-ci.yml b/.gitlab/ci/test.gitlab-ci.yml index a4ec77ee8..308701475 100644 --- a/.gitlab/ci/test.gitlab-ci.yml +++ b/.gitlab/ci/test.gitlab-ci.yml @@ -53,6 +53,12 @@ root-tests: script: - python3 -m pytest tests +test-helpers: + extends: .test-stage + script: + - cd tests + - bash test_helpers.sh + test-apps: extends: .test-stage script: From 4d6fcb1b8f8203836fdd1f62576d9dc99e5c3b8f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 1 Apr 2021 16:06:17 +0200 Subject: [PATCH 2300/3170] mv helpers.tests test_helpers.d, better naming consistency --- tests/{helpers.tests => test_helpers.d}/ynhtest_setup_source.sh | 0 tests/test_helpers.sh | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename tests/{helpers.tests => test_helpers.d}/ynhtest_setup_source.sh (100%) diff --git a/tests/helpers.tests/ynhtest_setup_source.sh b/tests/test_helpers.d/ynhtest_setup_source.sh similarity index 100% rename from tests/helpers.tests/ynhtest_setup_source.sh rename to tests/test_helpers.d/ynhtest_setup_source.sh diff --git a/tests/test_helpers.sh b/tests/test_helpers.sh index 399345796..830f6d14b 100644 --- a/tests/test_helpers.sh +++ b/tests/test_helpers.sh @@ -22,7 +22,7 @@ function log_failed() } source /usr/share/yunohost/helpers -for TEST_SUITE in $(ls helpers.tests/*) +for TEST_SUITE in $(ls test_helpers.d/*) do source $TEST_SUITE done From d782fe331dfbbf1c2cce88bd1dca1fd20131a374 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 1 Apr 2021 16:06:33 +0200 Subject: [PATCH 2301/3170] Return exit code 1 when at least one test failed --- tests/test_helpers.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/test_helpers.sh b/tests/test_helpers.sh index 830f6d14b..38bf010cf 100644 --- a/tests/test_helpers.sh +++ b/tests/test_helpers.sh @@ -29,6 +29,8 @@ done TESTS=$(declare -F | grep ' ynhtest_' | awk '{print $3}') +global_result=0 + for TEST in $TESTS do log_test $TEST @@ -41,5 +43,7 @@ do $TEST ) > ./test.log 2>&1 \ && log_passed \ - || { echo -e "\n----------"; cat ./test.log; echo -e "----------"; log_failed; } + || { echo -e "\n----------"; cat ./test.log; echo -e "----------"; log_failed; global_result=1; } done + +exit $global_result From d76f8d7969cd479f0ad33c17bab0dff056c09cc3 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 1 Apr 2021 16:08:14 +0200 Subject: [PATCH 2302/3170] explaaaaain --- tests/test_helpers.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_helpers.sh b/tests/test_helpers.sh index 38bf010cf..4c62d6275 100644 --- a/tests/test_helpers.sh +++ b/tests/test_helpers.sh @@ -21,12 +21,15 @@ function log_failed() echo "${BOLD}${RED}✘ Failed${NORMAL}" } +# ========================================================= + source /usr/share/yunohost/helpers for TEST_SUITE in $(ls test_helpers.d/*) do source $TEST_SUITE done +# Hack to list all known function, keep only those starting by ynhtest_ TESTS=$(declare -F | grep ' ynhtest_' | awk '{print $3}') global_result=0 From d11d26bdb4c96373da2e7e992d53b9a43080bc2a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 1 Apr 2021 18:02:08 +0200 Subject: [PATCH 2303/3170] Generate/server a dummy.tar.gz locally instead of hardcoding an external url --- tests/test_helpers.d/ynhtest_setup_source.sh | 72 +++++++++++--------- tests/test_helpers.sh | 36 ++++++++-- 2 files changed, 72 insertions(+), 36 deletions(-) diff --git a/tests/test_helpers.d/ynhtest_setup_source.sh b/tests/test_helpers.d/ynhtest_setup_source.sh index 33d685844..cd28bc933 100644 --- a/tests/test_helpers.d/ynhtest_setup_source.sh +++ b/tests/test_helpers.d/ynhtest_setup_source.sh @@ -1,15 +1,28 @@ -EXAMPLE_SRC=" -SOURCE_URL=https://github.com/Hextris/hextris/archive/8872ec47d694628e2fe668ebaa90b13d5626d95f.tar.gz -SOURCE_SUM=67f3fbd54c405717a25fb1e6f71d2b46e94c7ac6971861dd99ae5e58f6609892 -" +_make_dummy_src() { +echo "test coucou" + if [ ! -e $HTTPSERVER_DIR/dummy.tar.gz ] + then + pushd "$HTTPSERVER_DIR" + mkdir dummy + pushd dummy + echo "Lorem Ipsum" > index.html + echo '{"foo": "bar"}' > conf.json + mkdir assets + echo '.some.css { }' > assets/main.css + echo 'var some="js";' > assets/main.js + popd + tar -czf dummy.tar.gz dummy + popd + fi + echo "SOURCE_URL=http://127.0.0.1:$HTTPSERVER_PORT/dummy.tar.gz" + echo "SOURCE_SUM=$(sha256sum $HTTPSERVER_DIR/dummy.tar.gz | awk '{print $1}')" +} ynhtest_setup_source_nominal() { - mkdir -p /tmp/var/www/ - final_path="$(mktemp -d -p /tmp/var/www)" - mkdir ../conf - echo "$EXAMPLE_SRC" > ../conf/test.src + final_path="$(mktemp -d -p $VAR_WWW)" + _make_dummy_src > ../conf/dummy.src - ynh_setup_source --dest_dir="$final_path" --source_id="test" + ynh_setup_source --dest_dir="$final_path" --source_id="dummy" test -e "$final_path" test -e "$final_path/index.html" @@ -17,41 +30,36 @@ ynhtest_setup_source_nominal() { ynhtest_setup_source_nominal_upgrade() { - mkdir -p /tmp/var/www/ - final_path="$(mktemp -d -p /tmp/var/www)" - mkdir ../conf - echo "$EXAMPLE_SRC" > ../conf/test.src - - ynh_setup_source --dest_dir="$final_path" --source_id="test" + final_path="$(mktemp -d -p $VAR_WWW)" + _make_dummy_src > ../conf/dummy.src - test -e "$final_path" - test -e "$final_path/index.html" + ynh_setup_source --dest_dir="$final_path" --source_id="dummy" + + test "$(cat $final_path/index.html)" == "Lorem Ipsum" + echo $? # Except index.html to get overwritten during next ynh_setup_source - echo "HELLOWORLD" > $final_path/index.html - test "$(cat $final_path/index.html)" == "HELLOWORLD" + echo "IEditedYou!" > $final_path/index.html + test "$(cat $final_path/index.html)" == "IEditedYou!" - ynh_setup_source --dest_dir="$final_path" --source_id="test" + ynh_setup_source --dest_dir="$final_path" --source_id="dummy" - test "$(cat $final_path/index.html)" != "HELLOWORLD" + test "$(cat $final_path/index.html)" == "Lorem Ipsum" } ynhtest_setup_source_with_keep() { - mkdir -p /tmp/var/www/ - final_path="$(mktemp -d -p /tmp/var/www)" - mkdir ../conf - echo "$EXAMPLE_SRC" > ../conf/test.src + final_path="$(mktemp -d -p $VAR_WWW)" + _make_dummy_src > ../conf/dummy.src - echo "HELLOWORLD" > $final_path/index.html - echo "HELLOWORLD" > $final_path/test.conf.txt + echo "IEditedYou!" > $final_path/index.html + echo "IEditedYou!" > $final_path/test.txt - ynh_setup_source --dest_dir="$final_path" --source_id="test" --keep="index.html test.conf.txt" + ynh_setup_source --dest_dir="$final_path" --source_id="dummy" --keep="index.html test.txt" test -e "$final_path" test -e "$final_path/index.html" - test -e "$final_path/test.conf.txt" - test "$(cat $final_path/index.html)" == "HELLOWORLD" - test "$(cat $final_path/test.conf.txt)" == "HELLOWORLD" + test -e "$final_path/test.txt" + test "$(cat $final_path/index.html)" == "IEditedYou!" + test "$(cat $final_path/test.txt)" == "IEditedYou!" } - diff --git a/tests/test_helpers.sh b/tests/test_helpers.sh index 4c62d6275..15879a18a 100644 --- a/tests/test_helpers.sh +++ b/tests/test_helpers.sh @@ -21,6 +21,26 @@ function log_failed() echo "${BOLD}${RED}✘ Failed${NORMAL}" } +function cleanup() +{ + [ -n "$HTTPSERVER" ] && kill "$HTTPSERVER" + [ -d "$HTTPSERVER_DIR" ] && rm -rf "$HTTPSERVER_DIR" + [ -d "$VAR_WWW" ] && rm -rf "$VAR_WWW" +} +trap cleanup EXIT SIGINT + +# ========================================================= + +# Dummy http server, to serve archives for ynh_setup_source +HTTPSERVER_DIR=$(mktemp -d) +HTTPSERVER_PORT=1312 +pushd "$HTTPSERVER_DIR" +python -m SimpleHTTPServer $HTTPSERVER_PORT &>/dev/null & +HTTPSERVER="$!" +popd + +VAR_WWW=$(mktemp -d)/var/www +mkdir -p $VAR_WWW # ========================================================= source /usr/share/yunohost/helpers @@ -34,19 +54,27 @@ TESTS=$(declare -F | grep ' ynhtest_' | awk '{print $3}') global_result=0 +echo $TESTS + for TEST in $TESTS do log_test $TEST cd $(mktemp -d) (app=ynhtest YNH_APP_ID=$app + mkdir conf mkdir scripts cd scripts - set -eu + set -eux $TEST - ) > ./test.log 2>&1 \ - && log_passed \ - || { echo -e "\n----------"; cat ./test.log; echo -e "----------"; log_failed; global_result=1; } + ) > ./test.log 2>&1 + + if [[ $? == 0 ]] + then + set +x; log_passed; + else + set +x; echo -e "\n----------"; cat ./test.log; echo -e "----------"; log_failed; global_result=1; + fi done exit $global_result From 4acfbdeade98124dffcff8f6d75a681aa98395fe Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 1 Apr 2021 18:26:18 +0200 Subject: [PATCH 2304/3170] Fix oopsies, fix and add test for ynh_setup_source with patch --- data/helpers.d/utils | 6 ++++-- tests/test_helpers.d/ynhtest_setup_source.sh | 20 ++++++++++++++++++-- tests/test_helpers.sh | 6 ++---- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 942f6b34d..e651a5f77 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -217,11 +217,13 @@ ynh_setup_source () { fi # Apply patches - if (( $(find $YNH_APP_BASEDIR/sources/patches/ -type f -name "${source_id}-*.patch" 2> /dev/null | wc --lines) > "0" )) + local patches_folder=$(realpath $YNH_APP_BASEDIR/sources/patches/) + if (( $(find $patches_folder -type f -name "${source_id}-*.patch" 2> /dev/null | wc --lines) > "0" )) then (cd "$dest_dir" - for p in $YNH_APP_BASEDIR/sources/patches/${source_id}-*.patch + for p in $patches_folder/${source_id}-*.patch do + echo $p patch --strip=1 < $p done) || ynh_die --message="Unable to apply patches" fi diff --git a/tests/test_helpers.d/ynhtest_setup_source.sh b/tests/test_helpers.d/ynhtest_setup_source.sh index cd28bc933..69edf8ac9 100644 --- a/tests/test_helpers.d/ynhtest_setup_source.sh +++ b/tests/test_helpers.d/ynhtest_setup_source.sh @@ -28,7 +28,6 @@ ynhtest_setup_source_nominal() { test -e "$final_path/index.html" } - ynhtest_setup_source_nominal_upgrade() { final_path="$(mktemp -d -p $VAR_WWW)" _make_dummy_src > ../conf/dummy.src @@ -36,7 +35,6 @@ ynhtest_setup_source_nominal_upgrade() { ynh_setup_source --dest_dir="$final_path" --source_id="dummy" test "$(cat $final_path/index.html)" == "Lorem Ipsum" - echo $? # Except index.html to get overwritten during next ynh_setup_source echo "IEditedYou!" > $final_path/index.html @@ -63,3 +61,21 @@ ynhtest_setup_source_with_keep() { test "$(cat $final_path/index.html)" == "IEditedYou!" test "$(cat $final_path/test.txt)" == "IEditedYou!" } + +ynhtest_setup_source_with_patch() { + final_path="$(mktemp -d -p $VAR_WWW)" + _make_dummy_src > ../conf/dummy.src + + mkdir -p ../sources/patches + cat > ../sources/patches/dummy-index.html.patch << EOF +--- a/index.html ++++ b/index.html +@@ -1 +1,1 @@ +-Lorem Ipsum ++Lorem Ipsum dolor sit amet +EOF + + ynh_setup_source --dest_dir="$final_path" --source_id="dummy" + + test "$(cat $final_path/index.html)" == "Lorem Ipsum dolor sit amet" +} diff --git a/tests/test_helpers.sh b/tests/test_helpers.sh index 15879a18a..6a5f29bf1 100644 --- a/tests/test_helpers.sh +++ b/tests/test_helpers.sh @@ -34,10 +34,10 @@ trap cleanup EXIT SIGINT # Dummy http server, to serve archives for ynh_setup_source HTTPSERVER_DIR=$(mktemp -d) HTTPSERVER_PORT=1312 -pushd "$HTTPSERVER_DIR" +pushd "$HTTPSERVER_DIR" >/dev/null python -m SimpleHTTPServer $HTTPSERVER_PORT &>/dev/null & HTTPSERVER="$!" -popd +popd >/dev/null VAR_WWW=$(mktemp -d)/var/www mkdir -p $VAR_WWW @@ -54,8 +54,6 @@ TESTS=$(declare -F | grep ' ynhtest_' | awk '{print $3}') global_result=0 -echo $TESTS - for TEST in $TESTS do log_test $TEST From ce946cc0b0c989204070b5bafa5ecb53912b2317 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 1 Apr 2021 19:12:19 +0200 Subject: [PATCH 2305/3170] Introduce a decorator to automatically backup/rollback ldap db during ldap-related migrations --- locales/en.json | 8 +-- .../0019_extend_permissions_features.py | 57 ++----------------- .../0020_ssh_sftp_permissions.py | 5 +- src/yunohost/tools.py | 40 +++++++++++++ 4 files changed, 52 insertions(+), 58 deletions(-) diff --git a/locales/en.json b/locales/en.json index d17ff1f91..4c731863d 100644 --- a/locales/en.json +++ b/locales/en.json @@ -420,6 +420,10 @@ "migration_description_0018_xtable_to_nftable": "Migrate old network traffic rules to the new nftable system", "migration_description_0019_extend_permissions_features": "Extend/rework the app permission management system", "migration_description_0020_ssh_sftp_permissions": "Add SSH and SFTP permissions support", + "migration_ldap_backup_before_migration": "Creating a backup of LDAP database and apps settings prior to the actual migration.", + "migration_ldap_can_not_backup_before_migration": "The backup of the system could not be completed before the migration failed. Error: {error:s}", + "migration_ldap_migration_failed_trying_to_rollback": "Could not migrate... trying to roll back the system.", + "migration_ldap_rollback_success": "System rolled back.", "migration_0011_create_group": "Creating a group for each user...", "migration_0011_LDAP_update_failed": "Unable to update LDAP. Error: {error:s}", "migration_0011_migrate_permission": "Migrating permissions from apps settings to LDAP...", @@ -446,10 +450,6 @@ "migration_0018_failed_to_migrate_iptables_rules": "Failed to migrate legacy iptables rules to nftables: {error}", "migration_0018_failed_to_reset_legacy_rules": "Failed to reset legacy iptables rules: {error}", "migration_0019_add_new_attributes_in_ldap": "Add new attributes for permissions in LDAP database", - "migration_0019_backup_before_migration": "Creating a backup of LDAP database and apps settings prior to the actual migration.", - "migration_0019_can_not_backup_before_migration": "The backup of the system could not be completed before the migration failed. Error: {error:s}", - "migration_0019_migration_failed_trying_to_rollback": "Could not migrate... trying to roll back the system.", - "migration_0019_rollback_success": "System rolled back.", "migration_0019_slapd_config_will_be_overwritten": "It looks like you manually edited the slapd configuration. For this critical migration, YunoHost needs to force the update of the slapd configuration. The original files will be backuped in {conf_backup_folder}.", "migration_0020_ssh_sftp_permissions": "SSH/SFTP permissions", "migrations_already_ran": "Those migrations are already done: {ids}", diff --git a/src/yunohost/data_migrations/0019_extend_permissions_features.py b/src/yunohost/data_migrations/0019_extend_permissions_features.py index 07f740a2b..30ae01ae4 100644 --- a/src/yunohost/data_migrations/0019_extend_permissions_features.py +++ b/src/yunohost/data_migrations/0019_extend_permissions_features.py @@ -17,8 +17,6 @@ class MyMigration(Migration): Add protected attribute in LDAP permission """ - required = True - def add_new_ldap_attributes(self): from yunohost.utils.ldap import _get_ldap_interface @@ -78,54 +76,11 @@ class MyMigration(Migration): ldap.update("cn=%s,ou=permission" % permission, update) - def run(self): + @ldap_migration + def run(self, backup_folder): - # FIXME : what do we really want to do here ... - # Imho we should just force-regen the conf in all case, and maybe - # just display a warning if we detect that the conf was manually modified + # Update LDAP database + self.add_new_ldap_attributes() - # Backup LDAP and the apps settings before to do the migration - logger.info(m18n.n("migration_0019_backup_before_migration")) - try: - backup_folder = "/home/yunohost.backup/premigration/" + time.strftime( - "%Y%m%d-%H%M%S", time.gmtime() - ) - os.makedirs(backup_folder, 0o750) - os.system("systemctl stop slapd") - os.system("cp -r --preserve /etc/ldap %s/ldap_config" % backup_folder) - os.system("cp -r --preserve /var/lib/ldap %s/ldap_db" % backup_folder) - os.system( - "cp -r --preserve /etc/yunohost/apps %s/apps_settings" % backup_folder - ) - except Exception as e: - raise YunohostError( - "migration_0019_can_not_backup_before_migration", error=e - ) - finally: - os.system("systemctl start slapd") - - try: - # Update LDAP database - self.add_new_ldap_attributes() - - # Migrate old settings - migrate_legacy_permission_settings() - - except Exception: - logger.warn(m18n.n("migration_0019_migration_failed_trying_to_rollback")) - os.system("systemctl stop slapd") - os.system( - "rm -r /etc/ldap/slapd.d" - ) # To be sure that we don't keep some part of the old config - os.system("cp -r --preserve %s/ldap_config/. /etc/ldap/" % backup_folder) - os.system("cp -r --preserve %s/ldap_db/. /var/lib/ldap/" % backup_folder) - os.system( - "cp -r --preserve %s/apps_settings/. /etc/yunohost/apps/" - % backup_folder - ) - os.system("systemctl start slapd") - os.system("rm -r " + backup_folder) - logger.info(m18n.n("migration_0019_rollback_success")) - raise - else: - os.system("rm -r " + backup_folder) + # Migrate old settings + migrate_legacy_permission_settings() diff --git a/src/yunohost/data_migrations/0020_ssh_sftp_permissions.py b/src/yunohost/data_migrations/0020_ssh_sftp_permissions.py index cd4f4c765..9796ca10d 100644 --- a/src/yunohost/data_migrations/0020_ssh_sftp_permissions.py +++ b/src/yunohost/data_migrations/0020_ssh_sftp_permissions.py @@ -19,9 +19,8 @@ class MyMigration(Migration): Add new permissions around SSH/SFTP features """ - required = True - - def run(self): + @ldap_migration + def run(self, *args): logger.info(m18n.n("migration_0020_ssh_sftp_permissions")) from yunohost.utils.ldap import _get_ldap_interface diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index e5699dede..a33460c39 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -1101,6 +1101,7 @@ def _skip_all_migrations(): write_to_yaml(MIGRATIONS_STATE_PATH, new_states) + class Migration(object): # Those are to be implemented by daughter classes @@ -1125,3 +1126,42 @@ class Migration(object): @property def description(self): return m18n.n("migration_description_%s" % self.id) + + def ldap_migration(run): + + def func(self): + + # Backup LDAP before the migration + logger.info(m18n.n("migration_ldap_backup_before_migration")) + try: + backup_folder = "/home/yunohost.backup/premigration/" + time.strftime( + "%Y%m%d-%H%M%S", time.gmtime() + ) + os.makedirs(backup_folder, 0o750) + os.system("systemctl stop slapd") + os.system(f"cp -r --preserve /etc/ldap {backup_folder}/ldap_config") + os.system(f"cp -r --preserve /var/lib/ldap {backup_folder}/ldap_db") + os.system(f"cp -r --preserve /etc/yunohost/apps {backup_folder}/apps_settings") + except Exception as e: + raise YunohostError( + "migration_ldap_can_not_backup_before_migration", error=e + ) + finally: + os.system("systemctl start slapd") + + try: + run(self, backup_folder) + except Exception: + logger.warn(m18n.n("migration_ldap_migration_failed_trying_to_rollback")) + os.system("systemctl stop slapd") + # To be sure that we don't keep some part of the old config + os.system("rm -r /etc/ldap/slapd.d") + os.system(f"cp -r --preserve {backup_folder}/ldap_config/. /etc/ldap/") + os.system(f"cp -r --preserve {backup_folder}/ldap_db/. /var/lib/ldap/") + os.system(f"cp -r --preserve {backup_folder}/apps_settings/. /etc/yunohost/apps/") + os.system("systemctl start slapd") + os.system(f"rm -r {backup_folder}") + logger.info(m18n.n("migration_ldap_rollback_success")) + raise + else: + os.system(f"rm -r {backup_folder}") From 83d03dc07446c0051a9404da6b487d6c0213e40d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 1 Apr 2021 19:37:39 +0200 Subject: [PATCH 2306/3170] Simplify migration / be more explicit about what new rdn to inject --- locales/en.json | 1 - .../0020_ssh_sftp_permissions.py | 21 ++++++++----------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/locales/en.json b/locales/en.json index 4c731863d..20a0c7703 100644 --- a/locales/en.json +++ b/locales/en.json @@ -451,7 +451,6 @@ "migration_0018_failed_to_reset_legacy_rules": "Failed to reset legacy iptables rules: {error}", "migration_0019_add_new_attributes_in_ldap": "Add new attributes for permissions in LDAP database", "migration_0019_slapd_config_will_be_overwritten": "It looks like you manually edited the slapd configuration. For this critical migration, YunoHost needs to force the update of the slapd configuration. The original files will be backuped in {conf_backup_folder}.", - "migration_0020_ssh_sftp_permissions": "SSH/SFTP permissions", "migrations_already_ran": "Those migrations are already done: {ids}", "migrations_cant_reach_migration_file": "Could not access migrations files at the path '%s'", "migrations_dependencies_not_satisfied": "Run these migrations: '{dependencies_id}', before migration {id}.", diff --git a/src/yunohost/data_migrations/0020_ssh_sftp_permissions.py b/src/yunohost/data_migrations/0020_ssh_sftp_permissions.py index 9796ca10d..18c00d25e 100644 --- a/src/yunohost/data_migrations/0020_ssh_sftp_permissions.py +++ b/src/yunohost/data_migrations/0020_ssh_sftp_permissions.py @@ -19,25 +19,19 @@ class MyMigration(Migration): Add new permissions around SSH/SFTP features """ + dependencies = ["extend_permissions_features"] + @ldap_migration def run(self, *args): - logger.info(m18n.n("migration_0020_ssh_sftp_permissions")) from yunohost.utils.ldap import _get_ldap_interface ldap = _get_ldap_interface() - add_perm_to_users = False - # Add SSH and SFTP permissions ldap_map = read_yaml('/usr/share/yunohost/yunohost-config/moulinette/ldap_scheme.yml') - for rdn, attr_dict in ldap_map['depends_children'].items(): - try: - ldap.search(rdn + ",dc=yunohost,dc=org") - # ldap search will raise an exception if no corresponding object is found >.> ... - except Exception: - if rdn == "cn=ssh.main,ou=permission": - add_perm_to_users = True - ldap.add(rdn, attr_dict) + + ldap.add("cn=ssh.main,ou=permission", ldap_map['depends_children']["cn=ssh.main,ou=permission"]) + ldap.add("cn=sftp.main,ou=permission", ldap_map['depends_children']["cn=sftp.main,ou=permission"]) # Add a bash terminal to each users users = ldap.search('ou=users,dc=yunohost,dc=org', filter="(loginShell=*)", attrs=["dn", "uid", "loginShell"]) @@ -45,9 +39,12 @@ class MyMigration(Migration): if user['loginShell'][0] == '/bin/false': dn=user['dn'][0].replace(',dc=yunohost,dc=org', '') ldap.update(dn, {'loginShell': ['/bin/bash']}) - elif add_perm_to_users: + else: user_permission_update("ssh.main", add=user["uid"][0], sync_perm=False) + permission_sync_to_user() + + # Somehow this is needed otherwise the PAM thing doesn't forget about the # old loginShell value ? subprocess.call(['nscd', '-i', 'passwd']) From 22e397f71c248d73a913d867cc04c0ef54409190 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 1 Apr 2021 20:12:17 +0200 Subject: [PATCH 2307/3170] Fix oopsies --- .../0019_extend_permissions_features.py | 18 +++++++++--------- .../0020_ssh_sftp_permissions.py | 4 ++-- src/yunohost/tools.py | 5 ++++- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/yunohost/data_migrations/0019_extend_permissions_features.py b/src/yunohost/data_migrations/0019_extend_permissions_features.py index 30ae01ae4..d5e852701 100644 --- a/src/yunohost/data_migrations/0019_extend_permissions_features.py +++ b/src/yunohost/data_migrations/0019_extend_permissions_features.py @@ -17,6 +17,15 @@ class MyMigration(Migration): Add protected attribute in LDAP permission """ + @Migration.ldap_migration + def run(self, backup_folder): + + # Update LDAP database + self.add_new_ldap_attributes() + + # Migrate old settings + migrate_legacy_permission_settings() + def add_new_ldap_attributes(self): from yunohost.utils.ldap import _get_ldap_interface @@ -75,12 +84,3 @@ class MyMigration(Migration): } ldap.update("cn=%s,ou=permission" % permission, update) - - @ldap_migration - def run(self, backup_folder): - - # Update LDAP database - self.add_new_ldap_attributes() - - # Migrate old settings - migrate_legacy_permission_settings() diff --git a/src/yunohost/data_migrations/0020_ssh_sftp_permissions.py b/src/yunohost/data_migrations/0020_ssh_sftp_permissions.py index 18c00d25e..d3368e4a0 100644 --- a/src/yunohost/data_migrations/0020_ssh_sftp_permissions.py +++ b/src/yunohost/data_migrations/0020_ssh_sftp_permissions.py @@ -5,7 +5,7 @@ from moulinette.utils.log import getActionLogger from moulinette.utils.filesystem import read_yaml from yunohost.tools import Migration -from yunohost.permission import user_permission_update +from yunohost.permission import user_permission_update, permission_sync_to_user logger = getActionLogger('yunohost.migration') @@ -21,7 +21,7 @@ class MyMigration(Migration): dependencies = ["extend_permissions_features"] - @ldap_migration + @Migration.ldap_migration def run(self, *args): from yunohost.utils.ldap import _get_ldap_interface diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index a33460c39..92c5982fd 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -28,6 +28,7 @@ import os import yaml import subprocess import pwd +import time from importlib import import_module from moulinette import msignals, m18n @@ -1144,7 +1145,7 @@ class Migration(object): os.system(f"cp -r --preserve /etc/yunohost/apps {backup_folder}/apps_settings") except Exception as e: raise YunohostError( - "migration_ldap_can_not_backup_before_migration", error=e + "migration_ldap_can_not_backup_before_migration", error=str(e) ) finally: os.system("systemctl start slapd") @@ -1165,3 +1166,5 @@ class Migration(object): raise else: os.system(f"rm -r {backup_folder}") + + return func From 5c2329c5b67a4b387315c078e9b360660ad96bf4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 1 Apr 2021 20:12:31 +0200 Subject: [PATCH 2308/3170] Refuse to add ssh/sftp permissions to all users --- locales/en.json | 1 + src/yunohost/permission.py | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/locales/en.json b/locales/en.json index 20a0c7703..0d80565a7 100644 --- a/locales/en.json +++ b/locales/en.json @@ -498,6 +498,7 @@ "permission_created": "Permission '{permission:s}' created", "permission_creation_failed": "Could not create permission '{permission}': {error}", "permission_currently_allowed_for_all_users": "This permission is currently granted to all users in addition to other groups. You probably want to either remove the 'all_users' permission or remove the other groups it is currently granted to.", + "permission_cant_add_to_all_users": "The permission {permission} can not be added to all users.", "permission_deleted": "Permission '{permission:s}' deleted", "permission_deletion_failed": "Could not delete permission '{permission}': {error}", "permission_not_found": "Permission '{permission:s}' not found", diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index e0a3c6be8..df30b40c4 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -184,6 +184,10 @@ def user_permission_update( ) and not force: raise YunohostValidationError("permission_protected", permission=permission) + # Refuse to add "all_users" to ssh/sftp permissions + if permission.split(".")[0] in ["ssh", "sftp"] and (add and "all_users" in add) and not force: + raise YunohostValidationError("permission_cant_add_to_all_users", permission=permission) + # Fetch currently allowed groups for this permission current_allowed_groups = existing_permission["allowed"] From 00ec7b2fc432139cd5b8924b19b8d90e9e6dae7a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 1 Apr 2021 22:57:42 +0200 Subject: [PATCH 2309/3170] Support having .tar / .tar.gz in the archive name arg of backup_info/restore --- src/yunohost/backup.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index ba940058b..93f532525 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -2277,6 +2277,11 @@ def backup_restore(name, system=[], apps=[], force=False): # Initialize # # + if name.endswith(".tar.gz"): + name = name[:-len(".tar.gz")] + elif name.endswith(".tar"): + name = name[:-len(".tar")] + restore_manager = RestoreManager(name) restore_manager.set_system_targets(system) @@ -2409,6 +2414,12 @@ def backup_info(name, with_details=False, human_readable=False): human_readable -- Print sizes in human readable format """ + + if name.endswith(".tar.gz"): + name = name[:-len(".tar.gz")] + elif name.endswith(".tar"): + name = name[:-len(".tar")] + archive_file = "%s/%s.tar" % (ARCHIVES_PATH, name) # Check file exist (even if it's a broken symlink) From 40b22617d3c476726ebec238485a86c0c4d3f36e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 1 Apr 2021 23:14:43 +0200 Subject: [PATCH 2310/3170] Unused mports --- src/yunohost/app.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 28d8681e6..a60df5822 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -47,8 +47,6 @@ from moulinette.utils.filesystem import ( write_to_file, write_to_json, write_to_yaml, - chmod, - chown, mkdir, ) From 37db93b49ae7e3e2f7a27e68d946adfeb0c8c6c6 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 1 Apr 2021 23:27:56 +0200 Subject: [PATCH 2311/3170] Typo --- data/hooks/conf_regen/01-yunohost | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/hooks/conf_regen/01-yunohost b/data/hooks/conf_regen/01-yunohost index 2d162e738..a774c6d67 100755 --- a/data/hooks/conf_regen/01-yunohost +++ b/data/hooks/conf_regen/01-yunohost @@ -153,7 +153,7 @@ do_post_regen() { find /etc/cron.*/yunohost-* -type f -exec chmod 755 {} \; find /etc/cron.d/yunohost-* -type f -exec chmod 644 {} \; - find /etc/cron.*/yunohost-* -type f -exec chmod root:root {} \; + find /etc/cron.*/yunohost-* -type f -exec chown root:root {} \; # Misc configuration / state files chown root:root $(ls /etc/yunohost/{*.yml,*.yaml,*.json,mysql,psql} 2>/dev/null) From 30421954a4117f078e306bd10f4d2501c5ba85e6 Mon Sep 17 00:00:00 2001 From: cyxae Date: Fri, 2 Apr 2021 02:21:29 +0200 Subject: [PATCH 2312/3170] Add an option to disable the 'YunoHost' panel overlay in web apps (#1071) * Add an option to disable the 'YunoHost' panel overlay in apps * set default value for overlay as true * Add a hook to auto-update nginx conf + fix deprecated 'service regen-conf' * Change name of setting to ssowat.panel_overlay.enabled * [fix] Duplicate function * Quote var, just in case the var is empty for some reason Co-authored-by: ljf (zamentur) Co-authored-by: Alexandre Aubin --- data/hooks/conf_regen/15-nginx | 6 ++++++ src/yunohost/settings.py | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/data/hooks/conf_regen/15-nginx b/data/hooks/conf_regen/15-nginx index 1d68bbdf8..8875693c6 100755 --- a/data/hooks/conf_regen/15-nginx +++ b/data/hooks/conf_regen/15-nginx @@ -47,6 +47,12 @@ do_pre_regen() { # install / update plain conf files cp plain/* "$nginx_conf_dir" + # remove the panel overlay if this is specified in settings + panel_overlay=$(yunohost settings get 'ssowat.panel_overlay.enabled') + if [ "$panel_overlay" == "false" ] || [ "$panel_overlay" == "False" ] + then + echo "#" > "${nginx_conf_dir}/yunohost_panel.conf.inc" + fi # retrieve variables main_domain=$(cat /etc/yunohost/current_host) diff --git a/src/yunohost/settings.py b/src/yunohost/settings.py index 9d1a6d11f..e252316bd 100644 --- a/src/yunohost/settings.py +++ b/src/yunohost/settings.py @@ -94,10 +94,10 @@ DEFAULTS = OrderedDict( ("smtp.relay.user", {"type": "string", "default": ""}), ("smtp.relay.password", {"type": "string", "default": ""}), ("backup.compress_tar_archives", {"type": "bool", "default": False}), + ("ssowat.panel_overlay.enabled", {"type": "bool", "default": True}), ] ) - def settings_get(key, full=False): """ Get an entry value in the settings @@ -376,7 +376,7 @@ def trigger_post_change_hook(setting_name, old_value, new_value): # # =========================================== - +@post_change_hook("ssowat.panel_overlay.enabled") @post_change_hook("security.nginx.compatibility") def reconfigure_nginx(setting_name, old_value, new_value): if old_value != new_value: From 8b8a8fb3c74f1632f315181d95cbdc9ef2a3512e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 2 Apr 2021 03:13:53 +0200 Subject: [PATCH 2313/3170] Drop support for restoring backup archives from prior to 3.8 --- locales/en.json | 1 + src/yunohost/backup.py | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/locales/en.json b/locales/en.json index 199c21b66..51fba7024 100644 --- a/locales/en.json +++ b/locales/en.json @@ -528,6 +528,7 @@ "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 {app:s}", + "restore_backup_too_old": "This backup archive can not be restored because it comes from a too-old YunoHost version.", "restore_cleaning_failed": "Could not clean up the temporary restoration directory", "restore_complete": "Restoration completed", "restore_confirm_yunohost_installed": "Do you really want to restore an already installed system? [{answers:s}]", diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 93f532525..3ffb3a875 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -36,6 +36,7 @@ from datetime import datetime from glob import glob from collections import OrderedDict from functools import reduce +from packaging import version from moulinette import msignals, m18n, msettings from moulinette.utils import filesystem @@ -858,6 +859,9 @@ class RestoreManager: # FIXME this way to get the info is not compatible with copy or custom # backup methods self.info = backup_info(name, with_details=True) + if not self.info["from_yunohost_version"] or version.parse(self.info["from_yunohost_version"]) < version.parse("3.8.0"): + raise YunohostValidationError("restore_backup_too_old") + self.archive_path = self.info["path"] self.name = name self.method = BackupMethod.create(method, self) @@ -2530,6 +2534,7 @@ def backup_info(name, with_details=False, human_readable=False): result["apps"] = info["apps"] result["system"] = info[system_key] + result["from_yunohost_version"] = info.get("from_yunohost_version") return result From a8656c835ce1c8b9c57e2c5d8ab8d8152baa179c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 2 Apr 2021 03:48:25 +0200 Subject: [PATCH 2314/3170] Use backups from 3.8 instead of old 2.4 archives for system/apps restore tests --- src/yunohost/tests/test_backuprestore.py | 58 ++++++++++++------------ 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/src/yunohost/tests/test_backuprestore.py b/src/yunohost/tests/test_backuprestore.py index 021566544..4e598a708 100644 --- a/src/yunohost/tests/test_backuprestore.py +++ b/src/yunohost/tests/test_backuprestore.py @@ -47,8 +47,8 @@ def setup_function(function): for m in function.__dict__.get("pytestmark", []) } - if "with_wordpress_archive_from_2p4" in markers: - add_archive_wordpress_from_2p4() + if "with_wordpress_archive_from_3p8" in markers: + add_archive_wordpress_from_3p8() assert len(backup_list()["archives"]) == 1 if "with_legacy_app_installed" in markers: @@ -70,8 +70,8 @@ def setup_function(function): ) assert app_is_installed("backup_recommended_app") - if "with_system_archive_from_2p4" in markers: - add_archive_system_from_2p4() + if "with_system_archive_from_3p8" in markers: + add_archive_system_from_3p8() assert len(backup_list()["archives"]) == 1 if "with_permission_app_installed" in markers: @@ -147,7 +147,7 @@ def backup_test_dependencies_are_met(): # Dummy test apps (or backup archives) assert os.path.exists( - os.path.join(get_test_apps_dir(), "backup_wordpress_from_2p4") + os.path.join(get_test_apps_dir(), "backup_wordpress_from_3p8") ) assert os.path.exists(os.path.join(get_test_apps_dir(), "legacy_app_ynh")) assert os.path.exists( @@ -216,39 +216,39 @@ def install_app(app, path, additionnal_args=""): ) -def add_archive_wordpress_from_2p4(): +def add_archive_wordpress_from_3p8(): os.system("mkdir -p /home/yunohost.backup/archives") os.system( "cp " + os.path.join( - get_test_apps_dir(), "backup_wordpress_from_2p4/backup.info.json" + get_test_apps_dir(), "backup_wordpress_from_3p8/backup.info.json" ) - + " /home/yunohost.backup/archives/backup_wordpress_from_2p4.info.json" + + " /home/yunohost.backup/archives/backup_wordpress_from_3p8.info.json" ) os.system( "cp " - + os.path.join(get_test_apps_dir(), "backup_wordpress_from_2p4/backup.tar.gz") - + " /home/yunohost.backup/archives/backup_wordpress_from_2p4.tar.gz" + + os.path.join(get_test_apps_dir(), "backup_wordpress_from_3p8/backup.tar.gz") + + " /home/yunohost.backup/archives/backup_wordpress_from_3p8.tar.gz" ) -def add_archive_system_from_2p4(): +def add_archive_system_from_3p8(): os.system("mkdir -p /home/yunohost.backup/archives") os.system( "cp " - + os.path.join(get_test_apps_dir(), "backup_system_from_2p4/backup.info.json") - + " /home/yunohost.backup/archives/backup_system_from_2p4.info.json" + + os.path.join(get_test_apps_dir(), "backup_system_from_3p8/backup.info.json") + + " /home/yunohost.backup/archives/backup_system_from_3p8.info.json" ) os.system( "cp " - + os.path.join(get_test_apps_dir(), "backup_system_from_2p4/backup.tar.gz") - + " /home/yunohost.backup/archives/backup_system_from_2p4.tar.gz" + + os.path.join(get_test_apps_dir(), "backup_system_from_3p8/backup.tar.gz") + + " /home/yunohost.backup/archives/backup_system_from_3p8.tar.gz" ) @@ -314,12 +314,12 @@ def test_backup_and_restore_all_sys(mocker): # -# System restore from 2.4 # +# System restore from 3.8 # # -@pytest.mark.with_system_archive_from_2p4 -def test_restore_system_from_Ynh2p4(monkeypatch, mocker): +@pytest.mark.with_system_archive_from_3p8 +def test_restore_system_from_Ynh3p8(monkeypatch, mocker): # Backup current system with message(mocker, "backup_created"): @@ -327,7 +327,7 @@ def test_restore_system_from_Ynh2p4(monkeypatch, mocker): archives = backup_list()["archives"] assert len(archives) == 2 - # Restore system archive from 2.4 + # Restore system archive from 3.8 try: with message(mocker, "restore_complete"): backup_restore( @@ -464,9 +464,9 @@ def test_backup_using_copy_method(mocker): # -@pytest.mark.with_wordpress_archive_from_2p4 +@pytest.mark.with_wordpress_archive_from_3p8 @pytest.mark.with_custom_domain("yolo.test") -def test_restore_app_wordpress_from_Ynh2p4(mocker): +def test_restore_app_wordpress_from_Ynh3p8(mocker): with message(mocker, "restore_complete"): backup_restore( @@ -474,7 +474,7 @@ def test_restore_app_wordpress_from_Ynh2p4(mocker): ) -@pytest.mark.with_wordpress_archive_from_2p4 +@pytest.mark.with_wordpress_archive_from_3p8 @pytest.mark.with_custom_domain("yolo.test") def test_restore_app_script_failure_handling(monkeypatch, mocker): def custom_hook_exec(name, *args, **kwargs): @@ -495,7 +495,7 @@ def test_restore_app_script_failure_handling(monkeypatch, mocker): assert not _is_installed("wordpress") -@pytest.mark.with_wordpress_archive_from_2p4 +@pytest.mark.with_wordpress_archive_from_3p8 def test_restore_app_not_enough_free_space(monkeypatch, mocker): def custom_free_space_in_directory(dirpath): return 0 @@ -514,7 +514,7 @@ def test_restore_app_not_enough_free_space(monkeypatch, mocker): assert not _is_installed("wordpress") -@pytest.mark.with_wordpress_archive_from_2p4 +@pytest.mark.with_wordpress_archive_from_3p8 def test_restore_app_not_in_backup(mocker): assert not _is_installed("wordpress") @@ -530,7 +530,7 @@ def test_restore_app_not_in_backup(mocker): assert not _is_installed("yoloswag") -@pytest.mark.with_wordpress_archive_from_2p4 +@pytest.mark.with_wordpress_archive_from_3p8 @pytest.mark.with_custom_domain("yolo.test") def test_restore_app_already_installed(mocker): @@ -648,18 +648,18 @@ def test_restore_archive_with_no_json(mocker): backup_restore(name="badbackup", force=True) -@pytest.mark.with_wordpress_archive_from_2p4 +@pytest.mark.with_wordpress_archive_from_3p8 def test_restore_archive_with_bad_archive(mocker): # Break the archive os.system( - "head -n 1000 /home/yunohost.backup/archives/backup_wordpress_from_2p4.tar.gz > /home/yunohost.backup/archives/backup_wordpress_from_2p4.tar.gz" + "head -n 1000 /home/yunohost.backup/archives/backup_wordpress_from_3p8.tar.gz > /home/yunohost.backup/archives/backup_wordpress_from_3p8.tar.gz" ) - assert "backup_wordpress_from_2p4" in backup_list()["archives"] + assert "backup_wordpress_from_3p8" in backup_list()["archives"] with raiseYunohostError(mocker, "backup_archive_open_failed"): - backup_restore(name="backup_wordpress_from_2p4", force=True) + backup_restore(name="backup_wordpress_from_3p8", force=True) clean_tmp_backup_directory() From df49cc83d561e473235c880049f699b97bc020e1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 2 Apr 2021 03:55:20 +0200 Subject: [PATCH 2315/3170] Drop legacy stuff for backups from before the 3.7 era --- src/yunohost/backup.py | 82 +++++++++----------- src/yunohost/utils/legacy.py | 143 ----------------------------------- 2 files changed, 34 insertions(+), 191 deletions(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 3ffb3a875..b20c68412 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -1282,17 +1282,8 @@ class RestoreManager: regen_conf() - # Check that at least a group exists (all_users) to know if we need to - # do the migration 0011 : setup group and permission - # - # Legacy code - if "all_users" not in user_group_list()["groups"].keys(): - from yunohost.utils.legacy import SetupGroupPermissions - - # Update LDAP schema restart slapd - logger.info(m18n.n("migration_0011_update_LDAP_schema")) - regen_conf(names=["slapd"], force=True) - SetupGroupPermissions.migrate_LDAP_db() + # TODO : here, we should have a way to go through all migrations + # and apply stuff if needed # Remove all permission for all app which is still in the LDAP for permission_name in user_permission_list(ignore_system_perms=True)[ @@ -1425,50 +1416,45 @@ class RestoreManager: restore_script = os.path.join(tmp_folder_for_app_restore, "restore") # Restore permissions - if os.path.isfile("%s/permissions.yml" % app_settings_new_path): + if not os.path.isfile("%s/permissions.yml" % app_settings_new_path): + raise YunohostError("Didnt find a permssions.yml for the app !?", raw_msg=True) - permissions = read_yaml("%s/permissions.yml" % app_settings_new_path) - existing_groups = user_group_list()["groups"] + permissions = read_yaml("%s/permissions.yml" % app_settings_new_path) + existing_groups = user_group_list()["groups"] - for permission_name, permission_infos in permissions.items(): + for permission_name, permission_infos in permissions.items(): - if "allowed" not in permission_infos: - logger.warning( - "'allowed' key corresponding to allowed groups for permission %s not found when restoring app %s … You might have to reconfigure permissions yourself." - % (permission_name, app_instance_name) - ) - should_be_allowed = ["all_users"] - else: - should_be_allowed = [ - g - for g in permission_infos["allowed"] - if g in existing_groups - ] - - perm_name = permission_name.split(".")[1] - permission_create( - permission_name, - allowed=should_be_allowed, - url=permission_infos.get("url"), - additional_urls=permission_infos.get("additional_urls"), - auth_header=permission_infos.get("auth_header"), - label=permission_infos.get("label") - if perm_name == "main" - else permission_infos.get("sublabel"), - show_tile=permission_infos.get("show_tile", True), - protected=permission_infos.get("protected", False), - sync_perm=False, + if "allowed" not in permission_infos: + logger.warning( + "'allowed' key corresponding to allowed groups for permission %s not found when restoring app %s … You might have to reconfigure permissions yourself." + % (permission_name, app_instance_name) ) + should_be_allowed = ["all_users"] + else: + should_be_allowed = [ + g + for g in permission_infos["allowed"] + if g in existing_groups + ] - permission_sync_to_user() + perm_name = permission_name.split(".")[1] + permission_create( + permission_name, + allowed=should_be_allowed, + url=permission_infos.get("url"), + additional_urls=permission_infos.get("additional_urls"), + auth_header=permission_infos.get("auth_header"), + label=permission_infos.get("label") + if perm_name == "main" + else permission_infos.get("sublabel"), + show_tile=permission_infos.get("show_tile", True), + protected=permission_infos.get("protected", False), + sync_perm=False, + ) - os.remove("%s/permissions.yml" % app_settings_new_path) - else: - # Otherwise, we need to migrate the legacy permissions of this - # app (included in its settings.yml) - from yunohost.utils.legacy import SetupGroupPermissions + permission_sync_to_user() - SetupGroupPermissions.migrate_app_permission(app=app_instance_name) + os.remove("%s/permissions.yml" % app_settings_new_path) # Migrate old settings legacy_permission_settings = [ diff --git a/src/yunohost/utils/legacy.py b/src/yunohost/utils/legacy.py index b83a69154..825cf132c 100644 --- a/src/yunohost/utils/legacy.py +++ b/src/yunohost/utils/legacy.py @@ -19,149 +19,6 @@ from yunohost.permission import ( logger = getActionLogger("yunohost.legacy") - -class SetupGroupPermissions: - @staticmethod - def remove_if_exists(target): - - from yunohost.utils.ldap import _get_ldap_interface - - ldap = _get_ldap_interface() - - try: - objects = ldap.search(target + ",dc=yunohost,dc=org") - # ldap search will raise an exception if no corresponding object is found >.> ... - except Exception: - logger.debug("%s does not exist, no need to delete it" % target) - return - - objects.reverse() - for o in objects: - for dn in o["dn"]: - dn = dn.replace(",dc=yunohost,dc=org", "") - logger.debug("Deleting old object %s ..." % dn) - try: - ldap.remove(dn) - except Exception as e: - raise YunohostError( - "migration_0011_failed_to_remove_stale_object", dn=dn, error=e - ) - - @staticmethod - def migrate_LDAP_db(): - - logger.info(m18n.n("migration_0011_update_LDAP_database")) - - from yunohost.utils.ldap import _get_ldap_interface - - ldap = _get_ldap_interface() - - ldap_map = read_yaml( - "/usr/share/yunohost/yunohost-config/moulinette/ldap_scheme.yml" - ) - - try: - SetupGroupPermissions.remove_if_exists("ou=permission") - SetupGroupPermissions.remove_if_exists("ou=groups") - - attr_dict = ldap_map["parents"]["ou=permission"] - ldap.add("ou=permission", attr_dict) - - attr_dict = ldap_map["parents"]["ou=groups"] - ldap.add("ou=groups", attr_dict) - - attr_dict = ldap_map["children"]["cn=all_users,ou=groups"] - ldap.add("cn=all_users,ou=groups", attr_dict) - - attr_dict = ldap_map["children"]["cn=visitors,ou=groups"] - ldap.add("cn=visitors,ou=groups", attr_dict) - - for rdn, attr_dict in ldap_map["depends_children"].items(): - ldap.add(rdn, attr_dict) - except Exception as e: - raise YunohostError("migration_0011_LDAP_update_failed", error=e) - - logger.info(m18n.n("migration_0011_create_group")) - - # Create a group for each yunohost user - user_list = ldap.search( - "ou=users,dc=yunohost,dc=org", - "(&(objectclass=person)(!(uid=root))(!(uid=nobody)))", - ["uid", "uidNumber"], - ) - for user_info in user_list: - username = user_info["uid"][0] - ldap.update( - "uid=%s,ou=users" % username, - { - "objectClass": [ - "mailAccount", - "inetOrgPerson", - "posixAccount", - "userPermissionYnh", - ] - }, - ) - user_group_create( - username, - gid=user_info["uidNumber"][0], - primary_group=True, - sync_perm=False, - ) - user_group_update( - groupname="all_users", add=username, force=True, sync_perm=False - ) - - @staticmethod - def migrate_app_permission(app=None): - logger.info(m18n.n("migration_0011_migrate_permission")) - - apps = _installed_apps() - - if app: - if app not in apps: - logger.error( - "Can't migrate permission for app %s because it ain't installed..." - % app - ) - apps = [] - else: - apps = [app] - - for app in apps: - permission = app_setting(app, "allowed_users") - path = app_setting(app, "path") - domain = app_setting(app, "domain") - - url = "/" if domain and path else None - if permission: - known_users = list(user_list()["users"].keys()) - allowed = [ - user for user in permission.split(",") if user in known_users - ] - else: - allowed = ["all_users"] - permission_create( - app + ".main", - url=url, - allowed=allowed, - show_tile=True, - protected=False, - sync_perm=False, - ) - - app_setting(app, "allowed_users", delete=True) - - # Migrate classic public app still using the legacy unprotected_uris - if ( - app_setting(app, "unprotected_uris") == "/" - or app_setting(app, "skipped_uris") == "/" - ): - user_permission_update(app + ".main", add="visitors", sync_perm=False) - - permission_sync_to_user() - - LEGACY_PERMISSION_LABEL = { ("nextcloud", "skipped"): "api", # .well-known ("libreto", "skipped"): "pad access", # /[^/]+ From c552b4f0063a6a57c82b5e5bc721493c9f6d6e8e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 2 Apr 2021 04:28:52 +0200 Subject: [PATCH 2316/3170] Be able to define directly in migrations hooks that are called when restoring system/apps prior to the introduction of the migration --- src/yunohost/backup.py | 26 +++---------- .../0019_extend_permissions_features.py | 26 +++++++++++++ src/yunohost/tools.py | 39 +++++++++++++++++++ 3 files changed, 70 insertions(+), 21 deletions(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index b20c68412..d270de189 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -61,7 +61,7 @@ from yunohost.hook import ( hook_exec, CUSTOM_HOOK_FOLDER, ) -from yunohost.tools import tools_postinstall +from yunohost.tools import tools_postinstall, _tools_migrations_run_after_system_restore, _tools_migrations_run_before_app_restore from yunohost.regenconf import regen_conf from yunohost.log import OperationLogger from yunohost.utils.error import YunohostError, YunohostValidationError @@ -1282,16 +1282,15 @@ class RestoreManager: regen_conf() - # TODO : here, we should have a way to go through all migrations - # and apply stuff if needed + _tools_migrations_run_after_system_restore(backup_version=self.info["from_yunohost_version"]) - # Remove all permission for all app which is still in the LDAP + # Remove all permission for all app still in the LDAP for permission_name in user_permission_list(ignore_system_perms=True)[ "permissions" ].keys(): permission_delete(permission_name, force=True, sync_perm=False) - # Restore permission for the app which is installed + # Restore permission for apps installed for permission_name, permission_infos in old_apps_permission.items(): app_name, perm_name = permission_name.split(".") if _is_installed(app_name): @@ -1456,22 +1455,7 @@ class RestoreManager: os.remove("%s/permissions.yml" % app_settings_new_path) - # Migrate old settings - legacy_permission_settings = [ - "skipped_uris", - "unprotected_uris", - "protected_uris", - "skipped_regex", - "unprotected_regex", - "protected_regex", - ] - if any( - app_setting(app_instance_name, setting) is not None - for setting in legacy_permission_settings - ): - from yunohost.utils.legacy import migrate_legacy_permission_settings - - migrate_legacy_permission_settings(app=app_instance_name) + _tools_migrations_run_before_app_restore(backup_version=self.info["from_yunohost_version"], app_id=app_instance_name) # Prepare env. var. to pass to script env_dict = _make_environment_for_app_script(app_instance_name) diff --git a/src/yunohost/data_migrations/0019_extend_permissions_features.py b/src/yunohost/data_migrations/0019_extend_permissions_features.py index 07f740a2b..fe1fb20b7 100644 --- a/src/yunohost/data_migrations/0019_extend_permissions_features.py +++ b/src/yunohost/data_migrations/0019_extend_permissions_features.py @@ -78,6 +78,32 @@ class MyMigration(Migration): ldap.update("cn=%s,ou=permission" % permission, update) + introduced_in_version = "4.1" + + def run_after_system_restore(self): + # Update LDAP database + self.add_new_ldap_attributes() + + def run_before_system_restore(self, app_id): + from yunohost.app import app_setting + from yunohost.utils.legacy import migrate_legacy_permission_settings + + # Migrate old settings + legacy_permission_settings = [ + "skipped_uris", + "unprotected_uris", + "protected_uris", + "skipped_regex", + "unprotected_regex", + "protected_regex", + ] + if any( + app_setting(app_id, setting) is not None + for setting in legacy_permission_settings + ): + migrate_legacy_permission_settings(app=app_id) + + def run(self): # FIXME : what do we really want to do here ... diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index e5699dede..bf33984c3 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -29,6 +29,7 @@ import yaml import subprocess import pwd from importlib import import_module +from packaging import version from moulinette import msignals, m18n from moulinette.utils.log import getActionLogger @@ -1101,6 +1102,44 @@ def _skip_all_migrations(): write_to_yaml(MIGRATIONS_STATE_PATH, new_states) +def _tools_migrations_run_after_system_restore(backup_version): + + all_migrations = _get_migrations_list() + + for migration in all_migrations: + if hasattr(migration, "introduced_in_version") \ + and version.parse(migration.introduced_in_version) > version.parse(backup_version) \ + and hasattr(migration, "run_after_system_restore"): + try: + logger.info(m18n.n("migrations_running_forward", id=migration.id)) + migration.run_after_system_restore() + except Exception as e: + msg = m18n.n( + "migrations_migration_has_failed", exception=e, id=migration.id + ) + logger.error(msg, exc_info=1) + raise + + +def _tools_migrations_run_after_system_restore(backup_version, app_id): + + all_migrations = _get_migrations_list() + + for migration in all_migrations: + if hasattr(migration, "introduced_in_version") \ + and version.parse(migration.introduced_in_version) > version.parse(backup_version) \ + and hasattr(migration, "run_before_app_restore"): + try: + logger.info(m18n.n("migrations_running_forward", id=migration.id)) + migration.run_before_app_restore(app_id) + except Exception as e: + msg = m18n.n( + "migrations_migration_has_failed", exception=e, id=migration.id + ) + logger.error(msg, exc_info=1) + raise + + class Migration(object): # Those are to be implemented by daughter classes From 93813e773f99155b27b0764b665dc40dbe3b3988 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 2 Apr 2021 04:53:37 +0200 Subject: [PATCH 2317/3170] Typo --- src/yunohost/tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index bf33984c3..52762932d 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -1121,7 +1121,7 @@ def _tools_migrations_run_after_system_restore(backup_version): raise -def _tools_migrations_run_after_system_restore(backup_version, app_id): +def _tools_migrations_run_before_app_restore(backup_version, app_id): all_migrations = _get_migrations_list() From 8e70484c6325c8f3bd71152e3e07283d6c29ba05 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 2 Apr 2021 04:56:03 +0200 Subject: [PATCH 2318/3170] Stale strings --- locales/en.json | 7 +------ .../data_migrations/0019_extend_permissions_features.py | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/locales/en.json b/locales/en.json index 51fba7024..e013dd01b 100644 --- a/locales/en.json +++ b/locales/en.json @@ -419,12 +419,7 @@ "migration_description_0017_postgresql_9p6_to_11": "Migrate databases from PostgreSQL 9.6 to 11", "migration_description_0018_xtable_to_nftable": "Migrate old network traffic rules to the new nftable system", "migration_description_0019_extend_permissions_features": "Extend/rework the app permission management system", - "migration_0011_create_group": "Creating a group for each user...", - "migration_0011_LDAP_update_failed": "Unable to update LDAP. Error: {error:s}", - "migration_0011_migrate_permission": "Migrating permissions from apps settings to LDAP...", - "migration_0011_update_LDAP_database": "Updating LDAP database...", - "migration_0011_update_LDAP_schema": "Updating LDAP schema...", - "migration_0011_failed_to_remove_stale_object": "Unable to remove stale object {dn}: {error}", + "migration_update_LDAP_schema": "Updating LDAP schema...", "migration_0015_start" : "Starting migration to Buster", "migration_0015_patching_sources_list": "Patching the sources.lists...", "migration_0015_main_upgrade": "Starting main upgrade...", diff --git a/src/yunohost/data_migrations/0019_extend_permissions_features.py b/src/yunohost/data_migrations/0019_extend_permissions_features.py index fe1fb20b7..734c11920 100644 --- a/src/yunohost/data_migrations/0019_extend_permissions_features.py +++ b/src/yunohost/data_migrations/0019_extend_permissions_features.py @@ -36,7 +36,7 @@ class MyMigration(Migration): ) # Update LDAP schema restart slapd - logger.info(m18n.n("migration_0011_update_LDAP_schema")) + logger.info(m18n.n("migration_update_LDAP_schema")) regen_conf(names=["slapd"], force=True) logger.info(m18n.n("migration_0019_add_new_attributes_in_ldap")) From 08fbfa2e39d5b0c2bfc61f5df3d2c6ab1b38b08e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 2 Apr 2021 17:18:39 +0200 Subject: [PATCH 2319/3170] Add new ssowat panel overlay setting description --- locales/en.json | 1 + 1 file changed, 1 insertion(+) diff --git a/locales/en.json b/locales/en.json index 199c21b66..45fdbfec2 100644 --- a/locales/en.json +++ b/locales/en.json @@ -323,6 +323,7 @@ "global_settings_setting_security_postfix_compatibility": "Compatibility vs. security tradeoff for the Postfix server. Affects the ciphers (and other security-related aspects)", "global_settings_unknown_setting_from_settings_file": "Unknown key in settings: '{setting_key:s}', discard it and save it in /etc/yunohost/settings-unknown.json", "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Allow the use of (deprecated) DSA hostkey for the SSH daemon configuration", + "global_settings_setting_ssowat_panel_overlay_enabled": "Enable SSOwat panel overlay", "global_settings_setting_smtp_allow_ipv6": "Allow the use of IPv6 to receive and send mail", "global_settings_setting_smtp_relay_host": "SMTP relay host to use in order to send mail instead of this yunohost instance. Useful if you are in one of this situation: your 25 port is blocked by your ISP or VPS provider, you have a residential IP listed on DUHL, you are not able to configure reverse DNS or this server is not directly exposed on the internet and you want use an other one to send mails.", "global_settings_setting_smtp_relay_port": "SMTP relay port", From dc10e88b1e5ee9fa782916dbf740a3fc1699c48d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 2 Apr 2021 22:36:34 +0200 Subject: [PATCH 2320/3170] Unused imports --- src/yunohost/backup.py | 2 -- src/yunohost/utils/legacy.py | 4 +--- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index d270de189..e29da9bab 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -1219,7 +1219,6 @@ class RestoreManager: if system_targets == []: return - from yunohost.user import user_group_list from yunohost.permission import ( permission_create, permission_delete, @@ -1341,7 +1340,6 @@ class RestoreManager: name should be already install) """ from yunohost.user import user_group_list - from yunohost.app import app_setting from yunohost.permission import ( permission_create, permission_delete, diff --git a/src/yunohost/utils/legacy.py b/src/yunohost/utils/legacy.py index 825cf132c..fc00ab586 100644 --- a/src/yunohost/utils/legacy.py +++ b/src/yunohost/utils/legacy.py @@ -1,12 +1,10 @@ import os from moulinette import m18n -from yunohost.utils.error import YunohostError from moulinette.utils.log import getActionLogger from moulinette.utils.filesystem import write_to_json, read_yaml -from yunohost.user import user_list, user_group_create, user_group_update +from yunohost.user import user_list from yunohost.app import ( - app_setting, _installed_apps, _get_app_settings, _set_app_settings, From 72eb0b2e496cc6af08582b7f81a67f81e553d868 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 2 Apr 2021 23:02:08 +0200 Subject: [PATCH 2321/3170] We don't use the info.json during restore from 3.8 anymore --- src/yunohost/tests/test_backuprestore.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/yunohost/tests/test_backuprestore.py b/src/yunohost/tests/test_backuprestore.py index 4e598a708..077a3285b 100644 --- a/src/yunohost/tests/test_backuprestore.py +++ b/src/yunohost/tests/test_backuprestore.py @@ -220,14 +220,6 @@ def add_archive_wordpress_from_3p8(): os.system("mkdir -p /home/yunohost.backup/archives") - os.system( - "cp " - + os.path.join( - get_test_apps_dir(), "backup_wordpress_from_3p8/backup.info.json" - ) - + " /home/yunohost.backup/archives/backup_wordpress_from_3p8.info.json" - ) - os.system( "cp " + os.path.join(get_test_apps_dir(), "backup_wordpress_from_3p8/backup.tar.gz") @@ -239,12 +231,6 @@ def add_archive_system_from_3p8(): os.system("mkdir -p /home/yunohost.backup/archives") - os.system( - "cp " - + os.path.join(get_test_apps_dir(), "backup_system_from_3p8/backup.info.json") - + " /home/yunohost.backup/archives/backup_system_from_3p8.info.json" - ) - os.system( "cp " + os.path.join(get_test_apps_dir(), "backup_system_from_3p8/backup.tar.gz") From 037a2a66a4ae30a8e7caa00d13880424ae1e6477 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 3 Apr 2021 00:30:44 +0200 Subject: [PATCH 2322/3170] Not sure what I'm doing but for some reason can't remove yolo.test anymore because it's the main domain during tests idk --- src/yunohost/tests/test_backuprestore.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/yunohost/tests/test_backuprestore.py b/src/yunohost/tests/test_backuprestore.py index 077a3285b..e92ce7080 100644 --- a/src/yunohost/tests/test_backuprestore.py +++ b/src/yunohost/tests/test_backuprestore.py @@ -107,7 +107,8 @@ def teardown_function(function): if "with_custom_domain" in markers: domain = markers["with_custom_domain"]["args"][0] - domain_remove(domain) + if domain != maindomain: + domain_remove(domain) @pytest.fixture(autouse=True) From 80e2e0da7197c4e5f69309d8ac20c9f7296d2b03 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 3 Apr 2021 01:28:52 +0200 Subject: [PATCH 2323/3170] Misc test fixes for corrupted archive test though not sure what doing .. --- src/yunohost/backup.py | 4 +++- src/yunohost/tests/test_backuprestore.py | 8 ++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index e29da9bab..ed96dac16 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -2418,7 +2418,7 @@ def backup_info(name, with_details=False, human_readable=False): try: files_in_archive = tar.getnames() - except IOError as e: + except (IOError, EOFError) as e: raise YunohostError( "backup_archive_corrupted", archive=archive_file, error=str(e) ) @@ -2532,6 +2532,8 @@ def backup_delete(name): files_to_delete.append(actual_archive) for backup_file in files_to_delete: + if not os.path.exists(backup_file): + continue try: os.remove(backup_file) except Exception: diff --git a/src/yunohost/tests/test_backuprestore.py b/src/yunohost/tests/test_backuprestore.py index e92ce7080..8af9f7149 100644 --- a/src/yunohost/tests/test_backuprestore.py +++ b/src/yunohost/tests/test_backuprestore.py @@ -640,13 +640,13 @@ def test_restore_archive_with_bad_archive(mocker): # Break the archive os.system( - "head -n 1000 /home/yunohost.backup/archives/backup_wordpress_from_3p8.tar.gz > /home/yunohost.backup/archives/backup_wordpress_from_3p8.tar.gz" + "head -n 1000 /home/yunohost.backup/archives/backup_wordpress_from_3p8.tar.gz > /home/yunohost.backup/archives/backup_wordpress_from_3p8_bad.tar.gz" ) - assert "backup_wordpress_from_3p8" in backup_list()["archives"] + assert "backup_wordpress_from_3p8_bad" in backup_list()["archives"] - with raiseYunohostError(mocker, "backup_archive_open_failed"): - backup_restore(name="backup_wordpress_from_3p8", force=True) + with raiseYunohostError(mocker, "backup_archive_corrupted"): + backup_restore(name="backup_wordpress_from_3p8_bad", force=True) clean_tmp_backup_directory() From 6d3fcd6cc3804752c088fe0076b36f99132bd4a6 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 17 Mar 2021 20:24:01 +0100 Subject: [PATCH 2324/3170] Improve error management for app restore, similar to what's done in app install --- locales/ca.json | 2 +- locales/de.json | 2 +- locales/en.json | 2 +- locales/eo.json | 2 +- locales/es.json | 2 +- locales/eu.json | 2 +- locales/fr.json | 2 +- locales/it.json | 2 +- locales/nl.json | 2 +- locales/oc.json | 2 +- src/yunohost/backup.py | 151 ++++++++++++++--------- src/yunohost/tests/test_backuprestore.py | 2 +- 12 files changed, 101 insertions(+), 72 deletions(-) diff --git a/locales/ca.json b/locales/ca.json index b6888d391..ceaa6791e 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -327,7 +327,7 @@ "regenconf_failed": "No s'ha pogut regenerar la configuració per la/les categoria/es : {categories}", "regenconf_pending_applying": "Aplicació de la configuració pendent per la categoria «{category}»...", "restore_already_installed_app": "Una aplicació amb la ID «{app:s}» ja està instal·lada", - "restore_app_failed": "No s'ha pogut restaurar {app:s}", + "app_restore_failed": "No s'ha pogut restaurar {app:s}: {error:s}", "restore_cleaning_failed": "No s'ha pogut netejar el directori temporal de restauració", "restore_complete": "Restauració completada", "restore_confirm_yunohost_installed": "Esteu segur de voler restaurar un sistema ja instal·lat? [{answers:s}]", diff --git a/locales/de.json b/locales/de.json index bfc9c36a4..b3617e476 100644 --- a/locales/de.json +++ b/locales/de.json @@ -96,7 +96,7 @@ "port_already_closed": "Der Port {port:d} wurde bereits für {ip_version:s} Verbindungen geschlossen", "port_already_opened": "Der Port {port:d} wird bereits von {ip_version:s} benutzt", "restore_already_installed_app": "Es ist bereits eine App mit der ID '{app:s}' installiet", - "restore_app_failed": "App '{app:s}' konnte nicht wiederhergestellt werden", + "app_restore_failed": "App '{app:s}' konnte nicht wiederhergestellt werden: {error:s}", "restore_cleaning_failed": "Das temporäre Wiederherstellungsverzeichnis konnte nicht geleert werden", "restore_complete": "Wiederherstellung abgeschlossen", "restore_confirm_yunohost_installed": "Möchtest du die Wiederherstellung wirklich starten? [{answers:s}]", diff --git a/locales/en.json b/locales/en.json index be31c7599..cbab5e382 100644 --- a/locales/en.json +++ b/locales/en.json @@ -523,7 +523,7 @@ "regex_with_only_domain": "You can't use a regex for domain, only for path", "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 {app:s}", + "restore_app_failed": "Could not restore {app:s}: {error: s}", "restore_backup_too_old": "This backup archive can not be restored because it comes from a too-old YunoHost version.", "restore_cleaning_failed": "Could not clean up the temporary restoration directory", "restore_complete": "Restoration completed", diff --git a/locales/eo.json b/locales/eo.json index 1a27831f2..95030d8fa 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -479,7 +479,7 @@ "service_restarted": "Servo '{service:s}' rekomencis", "pattern_username": "Devas esti minuskulaj literoj kaj minuskloj nur", "extracting": "Eltirante…", - "restore_app_failed": "Ne povis restarigi la programon '{app:s}'", + "app_restore_failed": "Ne povis restarigi la programon '{app:s}': {error:s}", "yunohost_configured": "YunoHost nun estas agordita", "certmanager_self_ca_conf_file_not_found": "Ne povis trovi agorddosieron por mem-subskriba aŭtoritato (dosiero: {file:s})", "log_app_remove": "Forigu la aplikon '{}'", diff --git a/locales/es.json b/locales/es.json index cfcca071f..a93c3f244 100644 --- a/locales/es.json +++ b/locales/es.json @@ -107,7 +107,7 @@ "port_already_closed": "El puerto {port:d} ya está cerrado para las conexiones {ip_version:s}", "port_already_opened": "El puerto {port:d} ya está abierto para las conexiones {ip_version:s}", "restore_already_installed_app": "Una aplicación con el ID «{app:s}» ya está instalada", - "restore_app_failed": "No se pudo restaurar la aplicación «{app:s}»", + "app_restore_failed": "No se pudo restaurar la aplicación «{app:s}»: {error:s}", "restore_cleaning_failed": "No se pudo limpiar el directorio temporal de restauración", "restore_complete": "Restaurada", "restore_confirm_yunohost_installed": "¿Realmente desea restaurar un sistema ya instalado? [{answers:s}]", diff --git a/locales/eu.json b/locales/eu.json index 1891e00a3..539fb9157 100644 --- a/locales/eu.json +++ b/locales/eu.json @@ -1,3 +1,3 @@ { "password_too_simple_1": "Pasahitzak gutxienez 8 karaktere izan behar ditu" -} \ No newline at end of file +} diff --git a/locales/fr.json b/locales/fr.json index 8982d7ccc..e60e17d0e 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -107,7 +107,7 @@ "port_already_closed": "Le port {port:d} est déjà fermé pour les connexions {ip_version:s}", "port_already_opened": "Le port {port:d} est déjà ouvert pour les connexions {ip_version:s}", "restore_already_installed_app": "Une application est déjà installée avec l’identifiant '{app:s}'", - "restore_app_failed": "Impossible de restaurer '{app:s}'", + "app_restore_failed": "Impossible de restaurer '{app:s}': {error:s}", "restore_cleaning_failed": "Impossible de nettoyer le dossier temporaire de restauration", "restore_complete": "Restauration terminée", "restore_confirm_yunohost_installed": "Voulez-vous vraiment restaurer un système déjà installé ? [{answers:s}]", diff --git a/locales/it.json b/locales/it.json index 45b85d548..22248367a 100644 --- a/locales/it.json +++ b/locales/it.json @@ -120,7 +120,7 @@ "pattern_username": "Caratteri minuscoli alfanumerici o trattini bassi soli", "port_already_closed": "La porta {port:d} è già chiusa per le connessioni {ip_version:s}", "restore_already_installed_app": "Un'applicazione con l'ID '{app:s}' è già installata", - "restore_app_failed": "Impossibile ripristinare l'applicazione '{app:s}'", + "app_restore_failed": "Impossibile ripristinare l'applicazione '{app:s}': {error:s}", "restore_cleaning_failed": "Impossibile pulire la directory temporanea di ripristino", "restore_complete": "Ripristino completo", "restore_confirm_yunohost_installed": "Sei sicuro di volere ripristinare un sistema già installato? {answers:s}", diff --git a/locales/nl.json b/locales/nl.json index 59de95f58..23ad8d0cb 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -54,7 +54,7 @@ "pattern_password": "Wachtwoord moet tenminste 3 karakters lang zijn", "port_already_closed": "Poort {port:d} is al gesloten voor {ip_version:s} verbindingen", "port_already_opened": "Poort {port:d} is al open voor {ip_version:s} verbindingen", - "restore_app_failed": "De app '{app:s}' kon niet worden terug gezet", + "app_restore_failed": "De app '{app:s}' kon niet worden terug gezet: {error:s}", "restore_hook_unavailable": "De herstel-hook '{part:s}' is niet beschikbaar op dit systeem", "service_add_failed": "Kan service '{service:s}' niet toevoegen", "service_already_started": "Service '{service:s}' draait al", diff --git a/locales/oc.json b/locales/oc.json index 7fd423617..3bed9cd5a 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -170,7 +170,7 @@ "port_already_closed": "Lo pòrt {port:d} es ja tampat per las connexions {ip_version:s}", "port_already_opened": "Lo pòrt {port:d} es ja dubèrt per las connexions {ip_version:s}", "restore_already_installed_app": "Una aplicacion es ja installada amb l’id « {app:s} »", - "restore_app_failed": "Impossible de restaurar l’aplicacion « {app:s} »", + "app_restore_failed": "Impossible de restaurar l’aplicacion « {app:s} »: {error:s}", "backup_ask_for_copying_if_needed": "Volètz far una salvagarda en utilizant {size:s} Mo temporàriament ? (Aqueste biais de far es emplegat perque unes fichièrs an pas pogut èsser preparats amb un metòde mai eficaç.)", "yunohost_not_installed": "YunoHost es pas installat o corrèctament installat. Mercés d’executar « yunohost tools postinstall »", "backup_output_directory_forbidden": "Causissètz un repertòri de destinacion deferent. Las salvagardas pòdon pas se realizar dins los repertòris bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var ou /home/yunohost.backup/archives", diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index ed96dac16..b88c6e124 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -1392,7 +1392,6 @@ class RestoreManager: self.targets.set_result("apps", app_instance_name, "Warning") return - logger.debug(m18n.n("restore_running_app_script", app=app_instance_name)) try: # Restore app settings app_settings_new_path = os.path.join( @@ -1401,7 +1400,7 @@ class RestoreManager: app_scripts_new_path = os.path.join(app_settings_new_path, "scripts") shutil.copytree(app_settings_in_archive, app_settings_new_path) filesystem.chmod(app_settings_new_path, 0o400, 0o400, True) - filesystem.chown(app_scripts_new_path, "admin", None, True) + filesystem.chown(app_scripts_new_path, "root", None, True) # Copy the app scripts to a writable temporary folder # FIXME : use 'install -Dm555' or something similar to what's done @@ -1409,7 +1408,7 @@ class RestoreManager: tmp_folder_for_app_restore = tempfile.mkdtemp(prefix="restore") copytree(app_scripts_in_archive, tmp_folder_for_app_restore) filesystem.chmod(tmp_folder_for_app_restore, 0o550, 0o550, True) - filesystem.chown(tmp_folder_for_app_restore, "admin", None, True) + filesystem.chown(tmp_folder_for_app_restore, "root", None, True) restore_script = os.path.join(tmp_folder_for_app_restore, "restore") # Restore permissions @@ -1454,81 +1453,111 @@ class RestoreManager: os.remove("%s/permissions.yml" % app_settings_new_path) _tools_migrations_run_before_app_restore(backup_version=self.info["from_yunohost_version"], app_id=app_instance_name) - - # Prepare env. var. to pass to script - env_dict = _make_environment_for_app_script(app_instance_name) - env_dict.update( - { - "YNH_BACKUP_DIR": self.work_dir, - "YNH_BACKUP_CSV": os.path.join(self.work_dir, "backup.csv"), - "YNH_APP_BACKUP_DIR": os.path.join( - self.work_dir, "apps", app_instance_name, "backup" - ), - } - ) - - operation_logger.extra["env"] = env_dict - operation_logger.flush() - - # Execute app restore script - hook_exec( - restore_script, - chdir=app_backup_in_archive, - raise_on_error=True, - env=env_dict, - )[0] except Exception: - msg = m18n.n("restore_app_failed", app=app_instance_name) + import traceback + error = m18n.n("unexpected_error", error="\n" + traceback.format_exc()) + msg = m18n.n("app_restore_failed", app=app_instance_name, error=error) logger.error(msg) operation_logger.error(msg) - if msettings.get("interface") != "api": - dump_app_log_extract_for_debugging(operation_logger) - self.targets.set_result("apps", app_instance_name, "Error") - remove_script = os.path.join(app_scripts_in_archive, "remove") - - # Setup environment for remove script - env_dict_remove = _make_environment_for_app_script(app_instance_name) - - operation_logger = OperationLogger( - "remove_on_failed_restore", - [("app", app_instance_name)], - env=env_dict_remove, - ) - operation_logger.start() - - # Execute remove script - if hook_exec(remove_script, env=env_dict_remove)[0] != 0: - msg = m18n.n("app_not_properly_removed", app=app_instance_name) - logger.warning(msg) - operation_logger.error(msg) - else: - operation_logger.success() - - # Cleaning app directory + # Cleanup shutil.rmtree(app_settings_new_path, ignore_errors=True) + shutil.rmtree(tmp_folder_for_app_restore, ignore_errors=True) - # Remove all permission in LDAP for this app - for permission_name in user_permission_list()["permissions"].keys(): - if permission_name.startswith(app_instance_name + "."): - permission_delete(permission_name, force=True) + return - # TODO Cleaning app hooks - else: - self.targets.set_result("apps", app_instance_name, "Success") - operation_logger.success() + logger.debug(m18n.n("restore_running_app_script", app=app_instance_name)) + + # Prepare env. var. to pass to script + env_dict = _make_environment_for_app_script(app_instance_name) + env_dict.update( + { + "YNH_BACKUP_DIR": self.work_dir, + "YNH_BACKUP_CSV": os.path.join(self.work_dir, "backup.csv"), + "YNH_APP_BACKUP_DIR": os.path.join( + self.work_dir, "apps", app_instance_name, "backup" + ), + } + ) + + operation_logger.extra["env"] = env_dict + operation_logger.flush() + + # Execute the app install script + restore_failed = True + try: + restore_retcode = hook_exec( + restore_script, + chdir=app_backup_in_archive, + env=env_dict, + )[0] + # "Common" app restore failure : the script failed and returned exit code != 0 + restore_failed = True if restore_retcode != 0 else False + if restore_failed: + error = m18n.n("app_restore_script_failed") + logger.error(m18n.n("app_restore_failed", app=app_instance_name, error=error)) + failure_message_with_debug_instructions = operation_logger.error(error) + if msettings.get("interface") != "api": + dump_app_log_extract_for_debugging(operation_logger) + # Script got manually interrupted ... N.B. : KeyboardInterrupt does not inherit from Exception + except (KeyboardInterrupt, EOFError): + error = m18n.n("operation_interrupted") + logger.error(m18n.n("app_restore_failed", app=app_instance_name, error=error)) + failure_message_with_debug_instructions = operation_logger.error(error) + # Something wrong happened in Yunohost's code (most probably hook_exec) + except Exception: + import traceback + error = m18n.n("unexpected_error", error="\n" + traceback.format_exc()) + logger.error(m18n.n("app_restore_failed", app=app_instance_name, error=error)) + failure_message_with_debug_instructions = operation_logger.error(error) finally: # Cleaning temporary scripts directory shutil.rmtree(tmp_folder_for_app_restore, ignore_errors=True) + if not restore_failed: + self.targets.set_result("apps", app_instance_name, "Success") + operation_logger.success() + else: + + self.targets.set_result("apps", app_instance_name, "Error") + + remove_script = os.path.join(app_scripts_in_archive, "remove") + + # Setup environment for remove script + env_dict_remove = _make_environment_for_app_script(app_instance_name) + + remove_operation_logger = OperationLogger( + "remove_on_failed_restore", + [("app", app_instance_name)], + env=env_dict_remove, + ) + remove_operation_logger.start() + + # Execute remove script + if hook_exec(remove_script, env=env_dict_remove)[0] != 0: + msg = m18n.n("app_not_properly_removed", app=app_instance_name) + logger.warning(msg) + remove_operation_logger.error(msg) + else: + remove_operation_logger.success() + + # Cleaning app directory + shutil.rmtree(app_settings_new_path, ignore_errors=True) + + # Remove all permission in LDAP for this app + for permission_name in user_permission_list()["permissions"].keys(): + if permission_name.startswith(app_instance_name + "."): + permission_delete(permission_name, force=True) + + # TODO Cleaning app hooks + + logger.error(failure_message_with_debug_instructions) # # Backup methods # # - - class BackupMethod(object): """ diff --git a/src/yunohost/tests/test_backuprestore.py b/src/yunohost/tests/test_backuprestore.py index 8af9f7149..fa3e180bd 100644 --- a/src/yunohost/tests/test_backuprestore.py +++ b/src/yunohost/tests/test_backuprestore.py @@ -473,7 +473,7 @@ def test_restore_app_script_failure_handling(monkeypatch, mocker): assert not _is_installed("wordpress") - with message(mocker, "restore_app_failed", app="wordpress"): + with message(mocker, "app_restore_failed", app="wordpress"): with raiseYunohostError(mocker, "restore_nothings_done"): backup_restore( system=None, name=backup_list()["archives"][0], apps=["wordpress"] From 0c4e4c67c5dc3a269fc168fb75b36242545d9dab Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 5 Apr 2021 16:33:21 +0200 Subject: [PATCH 2325/3170] Fix locale strings --- locales/en.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index cbab5e382..fbf7305a8 100644 --- a/locales/en.json +++ b/locales/en.json @@ -44,6 +44,8 @@ "app_requirements_checking": "Checking required packages for {app}...", "app_requirements_unmeet": "Requirements are not met for {app}, the package {pkgname} ({version}) must be {spec}", "app_remove_after_failed_install": "Removing the app following the installation failure...", + "app_restore_failed": "Could not restore {app:s}: {error:s}", + "app_restore_script_failed": "An error occured inside the app restore script", "app_sources_fetch_failed": "Could not fetch sources files, is the URL correct?", "app_start_install": "Installing {app}...", "app_start_remove": "Removing {app}...", @@ -523,7 +525,6 @@ "regex_with_only_domain": "You can't use a regex for domain, only for path", "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 {app:s}: {error: s}", "restore_backup_too_old": "This backup archive can not be restored because it comes from a too-old YunoHost version.", "restore_cleaning_failed": "Could not clean up the temporary restoration directory", "restore_complete": "Restoration completed", From 2b2527fb72708ab663b531952095250e98e47307 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 5 Apr 2021 17:15:34 +0200 Subject: [PATCH 2326/3170] Fix test for failed restore --- src/yunohost/tests/test_backuprestore.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/tests/test_backuprestore.py b/src/yunohost/tests/test_backuprestore.py index fa3e180bd..7a2a64756 100644 --- a/src/yunohost/tests/test_backuprestore.py +++ b/src/yunohost/tests/test_backuprestore.py @@ -467,13 +467,13 @@ def test_restore_app_script_failure_handling(monkeypatch, mocker): def custom_hook_exec(name, *args, **kwargs): if os.path.basename(name).startswith("restore"): monkeypatch.undo() - raise Exception + return (1, None) monkeypatch.setattr("yunohost.backup.hook_exec", custom_hook_exec) assert not _is_installed("wordpress") - with message(mocker, "app_restore_failed", app="wordpress"): + with message(mocker, "app_restore_script_failed"): with raiseYunohostError(mocker, "restore_nothings_done"): backup_restore( system=None, name=backup_list()["archives"][0], apps=["wordpress"] From 42f8c9dcc1ed03c3b1fc46d6a9c90562618d9dd9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 5 Apr 2021 18:35:42 +0200 Subject: [PATCH 2327/3170] Fix ynh_requirement parsing --- data/helpers.d/utils | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index da414bd3e..8db83ef78 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -761,9 +761,9 @@ ynh_compare_current_package_version() { _ynh_apply_default_permissions() { local target=$1 - local ynh_requirement=$(jq -r '.requirements.yunohost' $YNH_APP_BASEDIR/manifest.json) + local ynh_requirement=$(jq -r '.requirements.yunohost' $YNH_APP_BASEDIR/manifest.json | tr -d '>= ') - if [ -z "$ynh_requirements" ] || [ "$ynh_requirements" == "null" ] || dpkg --compare-versions $ynh_requirements ge 4.2 + if [ -z "$ynh_requirement" ] || [ "$ynh_requirement" == "null" ] || dpkg --compare-versions $ynh_requirement ge 4.2 then chmod o-rwx $target chmod g-w $target From 86f22d1b46ee701f258b7bd6366593105c1895d9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 5 Apr 2021 18:39:08 +0200 Subject: [PATCH 2328/3170] Fix warning when there's no patches folder --- data/helpers.d/utils | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 8db83ef78..f5dd76e92 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -222,15 +222,18 @@ ynh_setup_source () { fi # Apply patches - local patches_folder=$(realpath $YNH_APP_BASEDIR/sources/patches/) - if (( $(find $patches_folder -type f -name "${source_id}-*.patch" 2> /dev/null | wc --lines) > "0" )) + if [ -d "$YNH_APP_BASEDIR/sources/patches/" ] then - (cd "$dest_dir" - for p in $patches_folder/${source_id}-*.patch - do - echo $p - patch --strip=1 < $p - done) || ynh_die --message="Unable to apply patches" + local patches_folder=$(realpath $YNH_APP_BASEDIR/sources/patches/) + if (( $(find $patches_folder -type f -name "${source_id}-*.patch" 2> /dev/null | wc --lines) > "0" )) + then + (cd "$dest_dir" + for p in $patches_folder/${source_id}-*.patch + do + echo $p + patch --strip=1 < $p + done) || ynh_die --message="Unable to apply patches" + fi fi # Add supplementary files From fe9f0731e81af43a2bffa0ea619d128b2a328b99 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 5 Apr 2021 18:39:26 +0200 Subject: [PATCH 2329/3170] Add logging to backup_create --- locales/en.json | 1 + src/yunohost/backup.py | 11 ++++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index be31c7599..0058880c3 100644 --- a/locales/en.json +++ b/locales/en.json @@ -375,6 +375,7 @@ "log_app_config_show_panel": "Show the config panel of the '{}' app", "log_app_config_apply": "Apply config to the '{}' app", "log_available_on_yunopaste": "This log is now available via {url}", + "log_backup_create": "Create a backup archive", "log_backup_restore_system": "Restore system from a backup archive", "log_backup_restore_app": "Restore '{}' from a backup archive", "log_remove_on_failed_restore": "Remove '{}' after a failed restore from a backup archive", diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index ed96dac16..50bd617b7 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -63,7 +63,7 @@ from yunohost.hook import ( ) from yunohost.tools import tools_postinstall, _tools_migrations_run_after_system_restore, _tools_migrations_run_before_app_restore from yunohost.regenconf import regen_conf -from yunohost.log import OperationLogger +from yunohost.log import OperationLogger, is_unit_operation from yunohost.utils.error import YunohostError, YunohostValidationError from yunohost.utils.packages import ynh_packages_version from yunohost.settings import settings_get @@ -2135,7 +2135,9 @@ class CustomBackupMethod(BackupMethod): # +@is_unit_operation() def backup_create( + operation_logger, name=None, description=None, methods=[], output_directory=None, system=[], apps=[] ): """ @@ -2191,6 +2193,8 @@ def backup_create( # Intialize # # + operation_logger.start() + # Create yunohost archives directory if it does not exists _create_archive_dir() @@ -2205,6 +2209,10 @@ def backup_create( backup_manager.set_system_targets(system) backup_manager.set_apps_targets(apps) + for app in backup_manager.targets.list("apps", exclude=["Skipped"]): + operation_logger.related_to.append(("app", app)) + operation_logger.flush() + # # Collect files and put them in the archive # # @@ -2217,6 +2225,7 @@ def backup_create( backup_manager.backup() logger.success(m18n.n("backup_created")) + operation_logger.success() return { "name": backup_manager.name, From 81c43747a0a59ce01a23ea072a054410b25cab63 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 5 Apr 2021 18:48:05 +0200 Subject: [PATCH 2330/3170] Increase delay for reloading services + display actual status in error message (seeing some weird php7.3-fpm stuff during some specific test...) --- src/yunohost/app.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 529d6bd08..b4f162e17 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -3451,14 +3451,15 @@ def _assert_system_is_sane_for_app(manifest, when): # Wait if a service is reloading test_nb = 0 - while test_nb < 10: + while test_nb < 16: if not any(s for s in services if service_status(s)["status"] == "reloading"): break time.sleep(0.5) test_nb+=1 # List services currently down and raise an exception if any are found - faulty_services = [s for s in services if service_status(s)["status"] != "running"] + services_status = {s:service_status(s) for s in services} + faulty_services = [f"{s} ({status['status']})" for s, status in services_status.items() if status['status'] != "running"] if faulty_services: if when == "pre": raise YunohostValidationError( From 5ce4694f9b30f06b6f47c0da4a357afdb40edd77 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 24 Mar 2021 19:34:39 +0000 Subject: [PATCH 2331/3170] Translated using Weblate (German) Currently translated at 86.6% (546 of 630 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/de.json b/locales/de.json index bfc9c36a4..fa3991f4f 100644 --- a/locales/de.json +++ b/locales/de.json @@ -616,5 +616,6 @@ "restore_may_be_not_enough_disk_space": "Dein System scheint nicht genug Speicherplatz zu haben (frei: {free_space:d} B, benötigter Platz: {needed_space:d} B, Sicherheitspuffer: {margin:d} B)", "restore_extracting": "Packe die benötigten Dateien aus dem Archiv aus…", "restore_already_installed_apps": "Folgende Apps können nicht wiederhergestellt werden, weil sie schon installiert sind: {apps}", - "regex_with_only_domain": "Du kannst regex nicht als Domain verwenden, sondern nur als Pfad" + "regex_with_only_domain": "Du kannst regex nicht als Domain verwenden, sondern nur als Pfad", + "root_password_desynchronized": "Das Admin-Passwort wurde verändert, aber das root Passwort ist immer noch das alte." } From f027d9129a7fba7962e454d9deffa163f4152ad5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= Date: Thu, 25 Mar 2021 12:38:25 +0000 Subject: [PATCH 2332/3170] Translated using Weblate (French) Currently translated at 99.8% (629 of 630 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 8982d7ccc..fa8c1ba5f 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -695,6 +695,6 @@ "diagnosis_backports_in_sources_list": "Il semble qu'apt (le gestionnaire de paquets) soit configuré pour utiliser le dépôt des rétroportages (backports). A moins que vous ne sachiez vraiment ce que vous faites, nous vous déconseillons fortement d'installer des paquets provenant des rétroportages, car cela risque de créer des instabilités ou des conflits sur votre système.", "postinstall_low_rootfsspace": "Le système de fichiers racine a une taille totale inférieure à 10 GB, ce qui est inquiétant ! Vous allez certainement arriver à court d'espace disque rapidement ! Il est recommandé d'avoir au moins 16 GB pour ce système de fichiers. Si vous voulez installer YunoHost malgré cet avertissement, relancez le postinstall avec --force-diskspace", "domain_remove_confirm_apps_removal": "Le retrait de ce domaine retirera aussi ces applications :\n{apps}\n\nÊtes vous sûr de vouloir cela ? [{answers}]", - "diagnosis_rootfstotalspace_critical": "Le système de fichiers racine ne fait que {space} ! Vous allez certainement les remplir très rapidement ! Il est recommandé d'avoir au moins 16 GB pour ce système de fichiers.", - "diagnosis_rootfstotalspace_warning": "Le système de fichiers racine n'est que de {space}. Ça peut suffire, mais faites attention car vous risquez de les remplire rapidement... Il est recommandé d'avoir au moins 16 GB pour ce système de fichiers." + "diagnosis_rootfstotalspace_critical": "Le système de fichiers racine ne fait que {space} ! Vous allez certainement le remplir très rapidement ! Il est recommandé d'avoir au moins 16 GB pour ce système de fichiers.", + "diagnosis_rootfstotalspace_warning": "Le système de fichiers racine n'est que de {space}. Cela peut suffire, mais faites attention car vous risquez de les remplir rapidement... Il est recommandé d'avoir au moins 16 GB pour ce système de fichiers." } From 7837b3d108d2fe1aa82c80a534720501ab0d2dea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= Date: Sat, 27 Mar 2021 09:45:15 +0000 Subject: [PATCH 2333/3170] Translated using Weblate (French) Currently translated at 99.8% (629 of 630 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 fa8c1ba5f..56a811bcc 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -190,7 +190,7 @@ "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).", "yunohost_ca_creation_success": "L'autorité de certification locale a été créée.", "app_already_installed_cant_change_url": "Cette application est déjà installée. L’URL ne peut pas être changé simplement par cette fonction. Vérifiez si cela est disponible avec `app changeurl`.", - "app_change_url_failed_nginx_reload": "Le redémarrage de Nginx a échoué. Voici la sortie de 'nginx -t' :\n{nginx_errors:s}", + "app_change_url_failed_nginx_reload": "Le redémarrage de NGINX a échoué. Voici la sortie de 'nginx -t' :\n{nginx_errors:s}", "app_change_url_identical_domains": "L’ancien et le nouveau couple domaine/chemin_de_l’URL sont identiques pour ('{domain:s}{path:s}'), rien à faire.", "app_change_url_no_script": "L’application '{app_name:s}' ne prend pas encore en charge le changement d’URL. Vous devriez peut-être la mettre à jour.", "app_change_url_success": "L’URL de l’application {app:s} a été changée en {domain:s}{path:s}", From 3f3b2f8a579c722cbca09142b3a5b6bbfb241cd8 Mon Sep 17 00:00:00 2001 From: Scapharnaum Date: Sun, 4 Apr 2021 18:05:27 +0000 Subject: [PATCH 2334/3170] Translated using Weblate (German) Currently translated at 87.3% (551 of 631 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/locales/de.json b/locales/de.json index fa3991f4f..968b8bae3 100644 --- a/locales/de.json +++ b/locales/de.json @@ -96,13 +96,13 @@ "port_already_closed": "Der Port {port:d} wurde bereits für {ip_version:s} Verbindungen geschlossen", "port_already_opened": "Der Port {port:d} wird bereits von {ip_version:s} benutzt", "restore_already_installed_app": "Es ist bereits eine App mit der ID '{app:s}' installiet", - "restore_app_failed": "App '{app:s}' konnte nicht wiederhergestellt werden", - "restore_cleaning_failed": "Das temporäre Wiederherstellungsverzeichnis konnte nicht geleert werden", - "restore_complete": "Wiederherstellung abgeschlossen", + "restore_app_failed": "'{app:s}' konnte nicht wiederhergestellt werden", + "restore_cleaning_failed": "Das temporäre Dateiverzeichnis für Systemrestaurierung konnte nicht gelöscht werden", + "restore_complete": "Vollständig wiederhergestellt", "restore_confirm_yunohost_installed": "Möchtest du die Wiederherstellung wirklich starten? [{answers:s}]", "restore_failed": "System kann nicht Wiederhergestellt werden", "restore_hook_unavailable": "Das Wiederherstellungsskript für '{part:s}' steht weder in deinem System noch im Archiv zur Verfügung", - "restore_nothings_done": "Es wurde nicht wiederhergestellt", + "restore_nothings_done": "Nichts wurde wiederhergestellt", "restore_running_app_script": "Wiederherstellung wird ausfeührt für App '{app:s}'...", "restore_running_hooks": "Wiederherstellung wird gestartet…", "service_add_failed": "Der Dienst '{service:s}' konnte nicht hinzugefügt werden", @@ -161,7 +161,7 @@ "domains_available": "Verfügbare Domains:", "dyndns_key_not_found": "DNS-Schlüssel für die Domain wurde nicht gefunden", "dyndns_no_domain_registered": "Keine Domain mit DynDNS registriert", - "ldap_init_failed_to_create_admin": "Die LDAP Initialisierung konnte keinen admin Benutzer erstellen", + "ldap_init_failed_to_create_admin": "Die LDAP-Initialisierung konnte keinen admin-Benutzer erstellen", "mailbox_used_space_dovecot_down": "Der Dovecot Mailbox Dienst muss gestartet sein, wenn du den von der Mailbox belegten Speicher angezeigen lassen willst", "package_unknown": "Unbekanntes Paket '{pkgname}'", "certmanager_attempt_to_replace_valid_cert": "Du versuchst gerade eine richtiges und gültiges Zertifikat der Domain {domain:s} zu überschreiben! (Benutze --force , um diese Nachricht zu umgehen)", @@ -175,7 +175,7 @@ "certmanager_domain_dns_ip_differs_from_public_ip": "Der DNS-A-Eintrag der Domain {domain:s} unterscheidet sich von dieser Server-IP. Für weitere Informationen überprüfen Sie bitte die 'DNS records' (basic) Kategorie in der Diagnose. Wenn Sie gerade Ihren A-Eintrag verändert haben, warten Sie bitte etwas, damit die Änderungen wirksam werden (Sie können die DNS-Propagation mittels Website überprüfen) (Wenn Sie wissen was Sie tun, können Sie --no-checks benutzen, um diese Überprüfung zu überspringen.)", "certmanager_cannot_read_cert": "Es ist ein Fehler aufgetreten, als es versucht wurde das aktuelle Zertifikat für die Domain {domain:s} zu öffnen (Datei: {file:s}), Grund: {reason:s}", "certmanager_cert_install_success_selfsigned": "Ein selbstsigniertes Zertifikat für die Domain {domain:s} wurde erfolgreich installiert", - "certmanager_cert_install_success": "Für die Domain {domain:s} wurde erfolgreich ein Let's Encrypt Zertifikat installiert.", + "certmanager_cert_install_success": "Für die Domain {domain:s} wurde erfolgreich ein Let's Encrypt Zertifikat installiert!", "certmanager_cert_renew_success": "Das Let's Encrypt Zertifikat für die Domain {domain:s} wurde erfolgreich erneuert", "certmanager_hit_rate_limit": "Es wurden innerhalb kurzer Zeit zu viele Zertifikate für dieselbe Domain {domain:s} ausgestellt. Bitte versuchen Sie es später nochmal. Besuchen Sie https://letsencrypt.org/docs/rate-limits/ für mehr Informationen", "certmanager_cert_signing_failed": "Das neue Zertifikat konnte nicht signiert werden", From 28268c58ebd3b22ec019af95c0c2fbe6a0b2f163 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 5 Apr 2021 20:45:28 +0200 Subject: [PATCH 2335/3170] Add a --dry-run option to backup_create that returns the size of items that will be backed up --- data/actionsmap/yunohost.yml | 3 +++ locales/en.json | 1 + src/yunohost/backup.py | 9 ++++++++- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 290952aa3..fad9b57d3 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -890,6 +890,9 @@ backup: --apps: help: List of application names to backup (or all if none given) nargs: "*" + --dry-run: + help: "'Simulate' the backup and return the size details per item to backup" + action: store_true ### backup_restore() restore: diff --git a/locales/en.json b/locales/en.json index 0058880c3..c9c440d5c 100644 --- a/locales/en.json +++ b/locales/en.json @@ -93,6 +93,7 @@ "backup_copying_to_organize_the_archive": "Copying {size:s}MB to organize the archive", "backup_couldnt_bind": "Could not bind {src:s} to {dest:s}.", "backup_created": "Backup created", + "backup_create_size_estimation": "The archive will contain about {size} of data.", "backup_creation_failed": "Could not create the backup archive", "backup_csv_addition_failed": "Could not add files to backup into the CSV file", "backup_csv_creation_failed": "Could not create the CSV file needed for restoration", diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 50bd617b7..4987d5871 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -2138,7 +2138,7 @@ class CustomBackupMethod(BackupMethod): @is_unit_operation() def backup_create( operation_logger, - name=None, description=None, methods=[], output_directory=None, system=[], apps=[] + name=None, description=None, methods=[], output_directory=None, system=[], apps=[], dry_run=False ): """ Create a backup local archive @@ -2220,8 +2220,15 @@ def backup_create( # Collect files to be backup (by calling app backup script / system hooks) backup_manager.collect_files() + if dry_run: + return { + "size": backup_manager.size, + "size_details": backup_manager.size_details + } + # Apply backup methods on prepared files logger.info(m18n.n("backup_actually_backuping")) + logger.info(m18n.n("backup_create_size_estimation", size=binary_to_human(backup_manager.size) + "B")) backup_manager.backup() logger.success(m18n.n("backup_created")) From 93166741ee476850fe72ccc179bf7b21ae5954d0 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 5 Apr 2021 20:45:51 +0200 Subject: [PATCH 2336/3170] Simplify indentation --- src/yunohost/backup.py | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 4987d5871..247b99325 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -793,25 +793,28 @@ class BackupManager: self.size_details["apps"][app_key] = 0 for row in self.paths_to_backup: - if row["dest"] != "info.json": - size = disk_usage(row["source"]) + if row["dest"] == "info.json": + continue - # Add size to apps details - splitted_dest = row["dest"].split("/") - category = splitted_dest[0] - if category == "apps": - for app_key in self.apps_return: - if row["dest"].startswith("apps/" + app_key): - self.size_details["apps"][app_key] += size - break - # OR Add size to the correct system element - elif category == "data" or category == "conf": - for system_key in self.system_return: - if row["dest"].startswith(system_key.replace("_", "/")): - self.size_details["system"][system_key] += size - break + size = disk_usage(row["source"]) - self.size += size + # Add size to apps details + splitted_dest = row["dest"].split("/") + category = splitted_dest[0] + if category == "apps": + for app_key in self.apps_return: + if row["dest"].startswith("apps/" + app_key): + self.size_details["apps"][app_key] += size + break + + # OR Add size to the correct system element + elif category == "data" or category == "conf": + for system_key in self.system_return: + if row["dest"].startswith(system_key.replace("_", "/")): + self.size_details["system"][system_key] += size + break + + self.size += size return self.size From e5b4d2aa733648ce936ef06137305eedb2838d1e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 5 Apr 2021 20:46:33 +0200 Subject: [PATCH 2337/3170] Trick to add all the apps/ folder such that they are correctly attributed to the corresponding app when we compute the size_details later --- src/yunohost/backup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 247b99325..9a95cd55a 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -538,8 +538,8 @@ class BackupManager: # Add unlisted files from backup tmp dir self._add_to_list_to_backup("backup.csv") self._add_to_list_to_backup("info.json") - if len(self.apps_return) > 0: - self._add_to_list_to_backup("apps") + for app in self.apps_return.keys(): + self._add_to_list_to_backup(f"apps/{app}") if os.path.isdir(os.path.join(self.work_dir, "conf")): self._add_to_list_to_backup("conf") if os.path.isdir(os.path.join(self.work_dir, "data")): From d750b77e4686667e7dfe20f9a779fa52e29c5b0f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 10 Mar 2021 19:16:16 +0100 Subject: [PATCH 2338/3170] Add backup/restore hooks for manually modified files --- .../hooks/backup/50-conf_manually_modified_files | 16 ++++++++++++++++ .../restore/50-conf_manually_modified_files | 11 +++++++++++ 2 files changed, 27 insertions(+) create mode 100644 data/hooks/backup/50-conf_manually_modified_files create mode 100644 data/hooks/restore/50-conf_manually_modified_files diff --git a/data/hooks/backup/50-conf_manually_modified_files b/data/hooks/backup/50-conf_manually_modified_files new file mode 100644 index 000000000..e7217bda6 --- /dev/null +++ b/data/hooks/backup/50-conf_manually_modified_files @@ -0,0 +1,16 @@ +#!/bin/bash + +source /usr/share/yunohost/helpers +ynh_abort_if_errors +YNH_CWD="${YNH_BACKUP_DIR%/}/conf/manually_modified_files" +mkdir -p "$YNH_CWD" +cd "$YNH_CWD" + +yunohost tools shell -c "from yunohost.regenconf import manually_modified_files; print('\n'.join(manually_modified_files()))" > ./manually_modified_files_list + +ynh_backup --src_path="./manually_modified_files_list" + +for file in $(cat ./manually_modified_files_list) +do + ynh_backup --src_path="$file" +done diff --git a/data/hooks/restore/50-conf_manually_modified_files b/data/hooks/restore/50-conf_manually_modified_files new file mode 100644 index 000000000..a6c5bc26a --- /dev/null +++ b/data/hooks/restore/50-conf_manually_modified_files @@ -0,0 +1,11 @@ +#!/bin/bash + +source /usr/share/yunohost/helpers +ynh_abort_if_errors +YNH_CWD="${YNH_BACKUP_DIR%/}/conf/manually_modified_files" +cd "$YNH_CWD" + +for file in $(cat ./manually_modified_files_list) +do + ynh_restore_file --origin_path="$file" --not_mandatory +done From ce2c608253f521c756be53f26856ed085a076233 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 10 Mar 2021 19:25:30 +0100 Subject: [PATCH 2339/3170] Do not backup ssh, ssowat, certs, nginx, xmpp confs --- data/hooks/backup/08-conf_ssh | 17 ----------------- data/hooks/backup/14-conf_ssowat | 13 ------------- data/hooks/backup/21-conf_ynh_certs | 13 ------------- data/hooks/backup/26-conf_xmpp | 14 -------------- data/hooks/backup/29-conf_nginx | 13 ------------- data/hooks/restore/08-conf_ssh | 9 --------- data/hooks/restore/14-conf_ssowat | 3 --- data/hooks/restore/21-conf_ynh_certs | 7 ------- data/hooks/restore/26-conf_xmpp | 7 ------- data/hooks/restore/29-conf_nginx | 7 ------- 10 files changed, 103 deletions(-) delete mode 100755 data/hooks/backup/08-conf_ssh delete mode 100755 data/hooks/backup/14-conf_ssowat delete mode 100755 data/hooks/backup/21-conf_ynh_certs delete mode 100755 data/hooks/backup/26-conf_xmpp delete mode 100755 data/hooks/backup/29-conf_nginx delete mode 100644 data/hooks/restore/08-conf_ssh delete mode 100644 data/hooks/restore/14-conf_ssowat delete mode 100644 data/hooks/restore/21-conf_ynh_certs delete mode 100644 data/hooks/restore/26-conf_xmpp delete mode 100644 data/hooks/restore/29-conf_nginx diff --git a/data/hooks/backup/08-conf_ssh b/data/hooks/backup/08-conf_ssh deleted file mode 100755 index ee976080c..000000000 --- a/data/hooks/backup/08-conf_ssh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash - -# Exit hook on subcommand error or unset variable -set -eu - -# Source YNH helpers -source /usr/share/yunohost/helpers - -# Backup destination -backup_dir="${1}/conf/ssh" - -# Backup the configuration -if [ -d /etc/ssh/ ]; then - ynh_backup "/etc/ssh" "$backup_dir" -else - echo "SSH is not installed" -fi diff --git a/data/hooks/backup/14-conf_ssowat b/data/hooks/backup/14-conf_ssowat deleted file mode 100755 index d4db72493..000000000 --- a/data/hooks/backup/14-conf_ssowat +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -# Exit hook on subcommand error or unset variable -set -eu - -# Source YNH helpers -source /usr/share/yunohost/helpers - -# Backup destination -backup_dir="${1}/conf/ssowat" - -# Backup the configuration -ynh_backup "/etc/ssowat" "$backup_dir" diff --git a/data/hooks/backup/21-conf_ynh_certs b/data/hooks/backup/21-conf_ynh_certs deleted file mode 100755 index a3912a995..000000000 --- a/data/hooks/backup/21-conf_ynh_certs +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -# Exit hook on subcommand error or unset variable -set -eu - -# Source YNH helpers -source /usr/share/yunohost/helpers - -# Backup destination -backup_dir="${1}/conf/ynh/certs" - -# Backup certificates -ynh_backup "/etc/yunohost/certs" "$backup_dir" diff --git a/data/hooks/backup/26-conf_xmpp b/data/hooks/backup/26-conf_xmpp deleted file mode 100755 index b55ad2bfc..000000000 --- a/data/hooks/backup/26-conf_xmpp +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash - -# Exit hook on subcommand error or unset variable -set -eu - -# Source YNH helpers -source /usr/share/yunohost/helpers - -# Backup destination -backup_dir="${1}/conf/xmpp" - -# Backup the configuration -ynh_backup /etc/metronome "${backup_dir}/etc" -ynh_backup /var/lib/metronome "${backup_dir}/var" diff --git a/data/hooks/backup/29-conf_nginx b/data/hooks/backup/29-conf_nginx deleted file mode 100755 index 81e145e24..000000000 --- a/data/hooks/backup/29-conf_nginx +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -# Exit hook on subcommand error or unset variable -set -eu - -# Source YNH helpers -source /usr/share/yunohost/helpers - -# Backup destination -backup_dir="${1}/conf/nginx" - -# Backup the configuration -ynh_backup "/etc/nginx/conf.d" "$backup_dir" diff --git a/data/hooks/restore/08-conf_ssh b/data/hooks/restore/08-conf_ssh deleted file mode 100644 index 4b69d1696..000000000 --- a/data/hooks/restore/08-conf_ssh +++ /dev/null @@ -1,9 +0,0 @@ -backup_dir="$1/conf/ssh" - -if [ -d /etc/ssh/ ]; then - cp -a $backup_dir/. /etc/ssh - service ssh restart -else - echo "SSH is not installed" -fi - diff --git a/data/hooks/restore/14-conf_ssowat b/data/hooks/restore/14-conf_ssowat deleted file mode 100644 index 71a011488..000000000 --- a/data/hooks/restore/14-conf_ssowat +++ /dev/null @@ -1,3 +0,0 @@ -backup_dir="$1/conf/ssowat" - -cp -a $backup_dir/. /etc/ssowat diff --git a/data/hooks/restore/21-conf_ynh_certs b/data/hooks/restore/21-conf_ynh_certs deleted file mode 100644 index 983bfb5a1..000000000 --- a/data/hooks/restore/21-conf_ynh_certs +++ /dev/null @@ -1,7 +0,0 @@ -backup_dir="$1/conf/ynh/certs" - -mkdir -p /etc/yunohost/certs/ - -cp -a $backup_dir/. /etc/yunohost/certs/ -service nginx reload -service metronome reload diff --git a/data/hooks/restore/26-conf_xmpp b/data/hooks/restore/26-conf_xmpp deleted file mode 100644 index a300a7268..000000000 --- a/data/hooks/restore/26-conf_xmpp +++ /dev/null @@ -1,7 +0,0 @@ -backup_dir="$1/conf/xmpp" - -cp -a $backup_dir/etc/. /etc/metronome -cp -a $backup_dir/var/. /var/lib/metronome - -# Restart to apply new conf and certs -service metronome restart diff --git a/data/hooks/restore/29-conf_nginx b/data/hooks/restore/29-conf_nginx deleted file mode 100644 index 7288f52f3..000000000 --- a/data/hooks/restore/29-conf_nginx +++ /dev/null @@ -1,7 +0,0 @@ -backup_dir="$1/conf/nginx" - -# Copy all conf except apps specific conf located in DOMAIN.d -find $backup_dir/ -mindepth 1 -maxdepth 1 -name '*.d' -or -exec cp -a {} /etc/nginx/conf.d/ \; - -# Restart to use new conf and certs -service nginx restart From 7e4536752e71ebc55418dced66ec61a9b3b5e6fa Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 10 Mar 2021 19:25:45 +0100 Subject: [PATCH 2340/3170] Backup xmpp data --- data/hooks/backup/27-data_xmpp | 13 +++++++++++++ data/hooks/restore/27-data_xmpp | 4 ++++ 2 files changed, 17 insertions(+) create mode 100755 data/hooks/backup/27-data_xmpp create mode 100644 data/hooks/restore/27-data_xmpp diff --git a/data/hooks/backup/27-data_xmpp b/data/hooks/backup/27-data_xmpp new file mode 100755 index 000000000..37c6fe2e3 --- /dev/null +++ b/data/hooks/backup/27-data_xmpp @@ -0,0 +1,13 @@ +#!/bin/bash + +# Exit hook on subcommand error or unset variable +set -eu + +# Source YNH helpers +source /usr/share/yunohost/helpers + +# Backup destination +backup_dir="${1}/data/xmpp" + +ynh_backup /var/lib/metronome "${backup_dir}/var" +ynh_backup /var/xmpp-upload/ "${backup_dir}/xmpp-upload" diff --git a/data/hooks/restore/27-data_xmpp b/data/hooks/restore/27-data_xmpp new file mode 100644 index 000000000..f0fe9b6bc --- /dev/null +++ b/data/hooks/restore/27-data_xmpp @@ -0,0 +1,4 @@ +backup_dir="$1/data/xmpp" + +cp -a $backup_dir/var/lib/metronome/. /var/lib/metronome +cp -a $backup_dir/var/xmpp-upload. /var/xmpp-upload From 2be90e35eb80b465facd04dc433f7da3875c0bdc Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 10 Mar 2021 19:25:56 +0100 Subject: [PATCH 2341/3170] Backup ssowat/conf.json.persistent --- data/hooks/backup/50-conf_manually_modified_files | 2 ++ data/hooks/restore/50-conf_manually_modified_files | 2 ++ 2 files changed, 4 insertions(+) diff --git a/data/hooks/backup/50-conf_manually_modified_files b/data/hooks/backup/50-conf_manually_modified_files index e7217bda6..685fb56a8 100644 --- a/data/hooks/backup/50-conf_manually_modified_files +++ b/data/hooks/backup/50-conf_manually_modified_files @@ -14,3 +14,5 @@ for file in $(cat ./manually_modified_files_list) do ynh_backup --src_path="$file" done + +ynh_backup --src_path="/etc/ssowat/conf.json.persistent" diff --git a/data/hooks/restore/50-conf_manually_modified_files b/data/hooks/restore/50-conf_manually_modified_files index a6c5bc26a..2d0943043 100644 --- a/data/hooks/restore/50-conf_manually_modified_files +++ b/data/hooks/restore/50-conf_manually_modified_files @@ -9,3 +9,5 @@ for file in $(cat ./manually_modified_files_list) do ynh_restore_file --origin_path="$file" --not_mandatory done + +ynh_restore_file --origin_path="/etc/ssowat/conf.json.persistent" --not_mandatory From 5884d51b69d03738a829d1f11502170c8c5d2fe5 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 10 Mar 2021 20:32:39 +0100 Subject: [PATCH 2342/3170] Typo --- data/hooks/restore/27-data_xmpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/hooks/restore/27-data_xmpp b/data/hooks/restore/27-data_xmpp index f0fe9b6bc..8ad8229d3 100644 --- a/data/hooks/restore/27-data_xmpp +++ b/data/hooks/restore/27-data_xmpp @@ -1,4 +1,4 @@ backup_dir="$1/data/xmpp" cp -a $backup_dir/var/lib/metronome/. /var/lib/metronome -cp -a $backup_dir/var/xmpp-upload. /var/xmpp-upload +cp -a $backup_dir/var/xmpp-upload/. /var/xmpp-upload From 8c351ad1767cd0e7b77cce32564db30120b43060 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 2 Apr 2021 00:18:05 +0200 Subject: [PATCH 2343/3170] Fix data_xmpp backup/restore hook --- data/hooks/backup/27-data_xmpp | 4 ++-- data/hooks/restore/27-data_xmpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/hooks/backup/27-data_xmpp b/data/hooks/backup/27-data_xmpp index 37c6fe2e3..2cd93e02b 100755 --- a/data/hooks/backup/27-data_xmpp +++ b/data/hooks/backup/27-data_xmpp @@ -9,5 +9,5 @@ source /usr/share/yunohost/helpers # Backup destination backup_dir="${1}/data/xmpp" -ynh_backup /var/lib/metronome "${backup_dir}/var" -ynh_backup /var/xmpp-upload/ "${backup_dir}/xmpp-upload" +ynh_backup /var/lib/metronome "${backup_dir}/var_lib_metronome" +ynh_backup /var/xmpp-upload/ "${backup_dir}/var_xmpp-upload" diff --git a/data/hooks/restore/27-data_xmpp b/data/hooks/restore/27-data_xmpp index 8ad8229d3..02a4c6703 100644 --- a/data/hooks/restore/27-data_xmpp +++ b/data/hooks/restore/27-data_xmpp @@ -1,4 +1,4 @@ backup_dir="$1/data/xmpp" -cp -a $backup_dir/var/lib/metronome/. /var/lib/metronome -cp -a $backup_dir/var/xmpp-upload/. /var/xmpp-upload +cp -a $backup_dir/var_lib_metronome/. /var/lib/metronome +cp -a $backup_dir/var_xmpp-upload/. /var/xmpp-upload From 956e860ff73a8e722637707097a049ef4926ee98 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 2 Apr 2021 00:18:17 +0200 Subject: [PATCH 2344/3170] Simplify ldap restore hook --- data/hooks/restore/05-conf_ldap | 101 +++++++++++++++----------------- 1 file changed, 47 insertions(+), 54 deletions(-) diff --git a/data/hooks/restore/05-conf_ldap b/data/hooks/restore/05-conf_ldap index bdc1ebcdf..743048e67 100644 --- a/data/hooks/restore/05-conf_ldap +++ b/data/hooks/restore/05-conf_ldap @@ -1,61 +1,54 @@ +#!/bin/bash + backup_dir="${1}/conf/ldap" -if [[ $EUID -ne 0 ]]; then +systemctl stop slapd - # We need to execute this script as root, since the ldap - # service will be shut down during the operation (and sudo - # won't be available) - /bin/bash $(readlink -f $0) $1 +# Create a directory for backup +TMPDIR="/tmp/$(date +%s)" +mkdir -p "$TMPDIR" -else +die() { + state=$1 + error=$2 - service slapd stop || true + # Restore saved configuration and database + [[ $state -ge 1 ]] \ + && (rm -rf /etc/ldap/slapd.d && + mv "${TMPDIR}/slapd.d" /etc/ldap/slapd.d) + [[ $state -ge 2 ]] \ + && (rm -rf /var/lib/ldap && + mv "${TMPDIR}/ldap" /var/lib/ldap) + chown -R openldap: /etc/ldap/slapd.d /var/lib/ldap - # Create a directory for backup - TMPDIR="/tmp/$(date +%s)" - mkdir -p "$TMPDIR" - - die() { - state=$1 - error=$2 - - # Restore saved configuration and database - [[ $state -ge 1 ]] \ - && (rm -rf /etc/ldap/slapd.d && - mv "${TMPDIR}/slapd.d" /etc/ldap/slapd.d) - [[ $state -ge 2 ]] \ - && (rm -rf /var/lib/ldap && - mv "${TMPDIR}/ldap" /var/lib/ldap) - chown -R openldap: /etc/ldap/slapd.d /var/lib/ldap - - service slapd start - rm -rf "$TMPDIR" - - # Print an error message and exit - printf "%s" "$error" 1>&2 - exit 1 - } - - # Restore the configuration - mv /etc/ldap/slapd.d "$TMPDIR" - mkdir -p /etc/ldap/slapd.d - cp -a "${backup_dir}/ldap.conf" /etc/ldap/ldap.conf - cp -a "${backup_dir}/slapd.ldif" /etc/ldap/slapd.ldif - # Legacy thing but we need it to force the regen-conf in case of it exist - cp -a "${backup_dir}/slapd.conf" /etc/ldap/slapd.conf - slapadd -F /etc/ldap/slapd.d -b cn=config \ - -l "${backup_dir}/cn=config.master.ldif" \ - || die 1 "Unable to restore LDAP configuration" - chown -R openldap: /etc/ldap/slapd.d - - # Restore the database - mv /var/lib/ldap "$TMPDIR" - mkdir -p /var/lib/ldap - slapadd -F /etc/ldap/slapd.d -b dc=yunohost,dc=org \ - -l "${backup_dir}/dc=yunohost-dc=org.ldif" \ - || die 2 "Unable to restore LDAP database" - chown -R openldap: /var/lib/ldap - - service slapd start + systemctl start slapd rm -rf "$TMPDIR" -fi + + # Print an error message and exit + printf "%s" "$error" 1>&2 + exit 1 +} + +# Restore the configuration +mv /etc/ldap/slapd.d "$TMPDIR" +mkdir -p /etc/ldap/slapd.d +cp -a "${backup_dir}/ldap.conf" /etc/ldap/ldap.conf +cp -a "${backup_dir}/slapd.ldif" /etc/ldap/slapd.ldif +# Legacy thing but we need it to force the regen-conf in case of it exist +[ ! -e "${backup_dir}/slapd.conf" ] \ + || cp -a "${backup_dir}/slapd.conf" /etc/ldap/slapd.conf +slapadd -F /etc/ldap/slapd.d -b cn=config \ + -l "${backup_dir}/cn=config.master.ldif" \ + || die 1 "Unable to restore LDAP configuration" +chown -R openldap: /etc/ldap/slapd.d + +# Restore the database +mv /var/lib/ldap "$TMPDIR" +mkdir -p /var/lib/ldap +slapadd -F /etc/ldap/slapd.d -b dc=yunohost,dc=org \ + -l "${backup_dir}/dc=yunohost-dc=org.ldif" \ + || die 2 "Unable to restore LDAP database" +chown -R openldap: /var/lib/ldap + +service slapd start +rm -rf "$TMPDIR" From 9f599fee4c5959d76fcfda47380f93cb5752e80c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 5 Apr 2021 17:47:56 +0200 Subject: [PATCH 2345/3170] Re-add the certificate backup/restore hook ... --- data/hooks/backup/21-conf_ynh_certs | 13 +++++++++++++ data/hooks/restore/21-conf_ynh_certs | 7 +++++++ 2 files changed, 20 insertions(+) create mode 100644 data/hooks/backup/21-conf_ynh_certs create mode 100644 data/hooks/restore/21-conf_ynh_certs diff --git a/data/hooks/backup/21-conf_ynh_certs b/data/hooks/backup/21-conf_ynh_certs new file mode 100644 index 000000000..a3912a995 --- /dev/null +++ b/data/hooks/backup/21-conf_ynh_certs @@ -0,0 +1,13 @@ +#!/bin/bash + +# Exit hook on subcommand error or unset variable +set -eu + +# Source YNH helpers +source /usr/share/yunohost/helpers + +# Backup destination +backup_dir="${1}/conf/ynh/certs" + +# Backup certificates +ynh_backup "/etc/yunohost/certs" "$backup_dir" diff --git a/data/hooks/restore/21-conf_ynh_certs b/data/hooks/restore/21-conf_ynh_certs new file mode 100644 index 000000000..983bfb5a1 --- /dev/null +++ b/data/hooks/restore/21-conf_ynh_certs @@ -0,0 +1,7 @@ +backup_dir="$1/conf/ynh/certs" + +mkdir -p /etc/yunohost/certs/ + +cp -a $backup_dir/. /etc/yunohost/certs/ +service nginx reload +service metronome reload From 95ed4d67a0ece519f9f76d5c10c69f85ca46219b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 5 Apr 2021 17:52:08 +0200 Subject: [PATCH 2346/3170] Also backup Yunohost settings --- .../backup/{40-conf_ynh_currenthost => 40-conf_ynh_settings} | 1 + data/hooks/restore/40-conf_ynh_currenthost | 3 --- data/hooks/restore/40-conf_ynh_settings | 4 ++++ 3 files changed, 5 insertions(+), 3 deletions(-) rename data/hooks/backup/{40-conf_ynh_currenthost => 40-conf_ynh_settings} (70%) delete mode 100644 data/hooks/restore/40-conf_ynh_currenthost create mode 100644 data/hooks/restore/40-conf_ynh_settings diff --git a/data/hooks/backup/40-conf_ynh_currenthost b/data/hooks/backup/40-conf_ynh_settings similarity index 70% rename from data/hooks/backup/40-conf_ynh_currenthost rename to data/hooks/backup/40-conf_ynh_settings index 6a98fd0d2..5b66da722 100755 --- a/data/hooks/backup/40-conf_ynh_currenthost +++ b/data/hooks/backup/40-conf_ynh_settings @@ -11,3 +11,4 @@ backup_dir="${1}/conf/ynh" # Backup the configuration ynh_backup "/etc/yunohost/current_host" "${backup_dir}/current_host" +[ ! -e "/etc/yunohost/settings.json" ] || ynh_backup "/etc/yunohost/settings.json" "${backup_dir}/settings.json" diff --git a/data/hooks/restore/40-conf_ynh_currenthost b/data/hooks/restore/40-conf_ynh_currenthost deleted file mode 100644 index 700e806b4..000000000 --- a/data/hooks/restore/40-conf_ynh_currenthost +++ /dev/null @@ -1,3 +0,0 @@ -backup_dir="$1/conf/ynh" - -cp -a "${backup_dir}/current_host" /etc/yunohost/current_host diff --git a/data/hooks/restore/40-conf_ynh_settings b/data/hooks/restore/40-conf_ynh_settings new file mode 100644 index 000000000..3bfa63162 --- /dev/null +++ b/data/hooks/restore/40-conf_ynh_settings @@ -0,0 +1,4 @@ +backup_dir="$1/conf/ynh" + +cp -a "${backup_dir}/current_host" /etc/yunohost/current_host +[ ! -e "${backup_dir}/settings.json" ] || cp -a "${backup_dir}/settings.json" "/etc/yunohost/settings.json" From 93ce917e5e092102f18cad155c27789bd0bcd176 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 5 Apr 2021 17:53:09 +0200 Subject: [PATCH 2347/3170] Backup hooks don't need to be executable --- data/hooks/backup/05-conf_ldap | 0 data/hooks/backup/17-data_home | 0 data/hooks/backup/20-conf_ynh_firewall | 0 data/hooks/backup/23-data_mail | 0 data/hooks/backup/27-data_xmpp | 0 data/hooks/backup/40-conf_ynh_settings | 0 6 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 data/hooks/backup/05-conf_ldap mode change 100755 => 100644 data/hooks/backup/17-data_home mode change 100755 => 100644 data/hooks/backup/20-conf_ynh_firewall mode change 100755 => 100644 data/hooks/backup/23-data_mail mode change 100755 => 100644 data/hooks/backup/27-data_xmpp mode change 100755 => 100644 data/hooks/backup/40-conf_ynh_settings diff --git a/data/hooks/backup/05-conf_ldap b/data/hooks/backup/05-conf_ldap old mode 100755 new mode 100644 diff --git a/data/hooks/backup/17-data_home b/data/hooks/backup/17-data_home old mode 100755 new mode 100644 diff --git a/data/hooks/backup/20-conf_ynh_firewall b/data/hooks/backup/20-conf_ynh_firewall old mode 100755 new mode 100644 diff --git a/data/hooks/backup/23-data_mail b/data/hooks/backup/23-data_mail old mode 100755 new mode 100644 diff --git a/data/hooks/backup/27-data_xmpp b/data/hooks/backup/27-data_xmpp old mode 100755 new mode 100644 diff --git a/data/hooks/backup/40-conf_ynh_settings b/data/hooks/backup/40-conf_ynh_settings old mode 100755 new mode 100644 From c089d1bdcd993dd8ed27742ed355e5e06ad59863 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 5 Apr 2021 17:58:39 +0200 Subject: [PATCH 2348/3170] No need for random service reload/restart, that should be handled by the auto-regenconf of the core after the restore --- data/hooks/restore/05-conf_ldap | 2 +- data/hooks/restore/21-conf_ynh_certs | 2 -- data/hooks/restore/23-data_mail | 4 ---- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/data/hooks/restore/05-conf_ldap b/data/hooks/restore/05-conf_ldap index 743048e67..8dc511695 100644 --- a/data/hooks/restore/05-conf_ldap +++ b/data/hooks/restore/05-conf_ldap @@ -50,5 +50,5 @@ slapadd -F /etc/ldap/slapd.d -b dc=yunohost,dc=org \ || die 2 "Unable to restore LDAP database" chown -R openldap: /var/lib/ldap -service slapd start +systemctl start slapd rm -rf "$TMPDIR" diff --git a/data/hooks/restore/21-conf_ynh_certs b/data/hooks/restore/21-conf_ynh_certs index 983bfb5a1..a6b45efeb 100644 --- a/data/hooks/restore/21-conf_ynh_certs +++ b/data/hooks/restore/21-conf_ynh_certs @@ -3,5 +3,3 @@ backup_dir="$1/conf/ynh/certs" mkdir -p /etc/yunohost/certs/ cp -a $backup_dir/. /etc/yunohost/certs/ -service nginx reload -service metronome reload diff --git a/data/hooks/restore/23-data_mail b/data/hooks/restore/23-data_mail index f9fd6e699..b3946f341 100644 --- a/data/hooks/restore/23-data_mail +++ b/data/hooks/restore/23-data_mail @@ -2,7 +2,3 @@ backup_dir="$1/data/mail" cp -a $backup_dir/. /var/mail/ || echo 'No mail found' chown -R vmail:mail /var/mail/ - -# Restart services to use migrated certs -service postfix restart -service dovecot restart From 82784dc45d2d69ac185afa67388aeabd0c1594ea Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 5 Apr 2021 21:39:49 +0200 Subject: [PATCH 2349/3170] Merge firewall, dkim and dyndns backup hook into ynh_settings --- data/hooks/backup/20-conf_ynh_firewall | 13 ------------- data/hooks/backup/22-conf_mail | 9 --------- data/hooks/backup/40-conf_ynh_settings | 3 +++ data/hooks/backup/42-conf_ynh_dyndns | 10 ---------- data/hooks/restore/20-conf_ynh_firewall | 4 ---- data/hooks/restore/22-conf_mail | 9 --------- data/hooks/restore/40-conf_ynh_settings | 3 +++ data/hooks/restore/42-conf_ynh_dyndns | 9 --------- 8 files changed, 6 insertions(+), 54 deletions(-) delete mode 100644 data/hooks/backup/20-conf_ynh_firewall delete mode 100644 data/hooks/backup/22-conf_mail delete mode 100644 data/hooks/backup/42-conf_ynh_dyndns delete mode 100644 data/hooks/restore/20-conf_ynh_firewall delete mode 100644 data/hooks/restore/22-conf_mail delete mode 100644 data/hooks/restore/42-conf_ynh_dyndns diff --git a/data/hooks/backup/20-conf_ynh_firewall b/data/hooks/backup/20-conf_ynh_firewall deleted file mode 100644 index 98be3eb09..000000000 --- a/data/hooks/backup/20-conf_ynh_firewall +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -# Exit hook on subcommand error or unset variable -set -eu - -# Source YNH helpers -source /usr/share/yunohost/helpers - -# Backup destination -backup_dir="${1}/conf/ynh/firewall" - -# Backup the configuration -ynh_backup "/etc/yunohost/firewall.yml" "${backup_dir}/firewall.yml" diff --git a/data/hooks/backup/22-conf_mail b/data/hooks/backup/22-conf_mail deleted file mode 100644 index b604d8aa8..000000000 --- a/data/hooks/backup/22-conf_mail +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - -source /usr/share/yunohost/helpers -ynh_abort_if_errors -YNH_CWD="${YNH_BACKUP_DIR%/}/conf/dkim" -mkdir -p "$YNH_CWD" -cd "$YNH_CWD" - -ynh_backup --src_path="/etc/dkim" diff --git a/data/hooks/backup/40-conf_ynh_settings b/data/hooks/backup/40-conf_ynh_settings index 5b66da722..77148c4d9 100644 --- a/data/hooks/backup/40-conf_ynh_settings +++ b/data/hooks/backup/40-conf_ynh_settings @@ -10,5 +10,8 @@ source /usr/share/yunohost/helpers backup_dir="${1}/conf/ynh" # Backup the configuration +ynh_backup "/etc/yunohost/firewall.yml" "${backup_dir}/firewall.yml" ynh_backup "/etc/yunohost/current_host" "${backup_dir}/current_host" [ ! -e "/etc/yunohost/settings.json" ] || ynh_backup "/etc/yunohost/settings.json" "${backup_dir}/settings.json" +[ ! -d "/etc/yunohost/dyndns" ] || ynh_backup "/etc/yunohost/dyndns" "${backup_dir}/dyndns" +[ ! -d "/etc/dkim" ] || ynh_backup "/etc/dkim" "${backup_dir}/dkim" diff --git a/data/hooks/backup/42-conf_ynh_dyndns b/data/hooks/backup/42-conf_ynh_dyndns deleted file mode 100644 index 6343f9086..000000000 --- a/data/hooks/backup/42-conf_ynh_dyndns +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -source /usr/share/yunohost/helpers -ynh_abort_if_errors -YNH_CWD="${YNH_BACKUP_DIR%/}/conf/ynh/dyndns" -mkdir -p $YNH_CWD -cd "$YNH_CWD" - -# Backup the configuration -ynh_exec_warn_less ynh_backup --src_path="/etc/yunohost/dyndns" --not_mandatory diff --git a/data/hooks/restore/20-conf_ynh_firewall b/data/hooks/restore/20-conf_ynh_firewall deleted file mode 100644 index 1789aed1e..000000000 --- a/data/hooks/restore/20-conf_ynh_firewall +++ /dev/null @@ -1,4 +0,0 @@ -backup_dir="$1/conf/ynh/firewall" - -cp -a $backup_dir/. /etc/yunohost -yunohost firewall reload diff --git a/data/hooks/restore/22-conf_mail b/data/hooks/restore/22-conf_mail deleted file mode 100644 index 77e0a4d42..000000000 --- a/data/hooks/restore/22-conf_mail +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - -backup_dir="$1/conf/dkim" - -cp -a $backup_dir/etc/dkim/. /etc/dkim - -chown -R root:root /etc/dkim -chown _rspamd:root /etc/dkim -chown _rspamd:root /etc/dkim/*.mail.key diff --git a/data/hooks/restore/40-conf_ynh_settings b/data/hooks/restore/40-conf_ynh_settings index 3bfa63162..4de29a4aa 100644 --- a/data/hooks/restore/40-conf_ynh_settings +++ b/data/hooks/restore/40-conf_ynh_settings @@ -1,4 +1,7 @@ backup_dir="$1/conf/ynh" cp -a "${backup_dir}/current_host" /etc/yunohost/current_host +cp -a "${backup_dir}/firewall.yml" /etc/yunohost/firewall.yml [ ! -e "${backup_dir}/settings.json" ] || cp -a "${backup_dir}/settings.json" "/etc/yunohost/settings.json" +[ ! -d "${backup_dir}/dyndns" ] || cp -raT "${backup_dir}/dyndns" "/etc/yunohost/dyndns" +[ ! -d "${backup_dir}/dkim" ] || cp -raT "${backup_dir}/dkim" "/etc/dkim" diff --git a/data/hooks/restore/42-conf_ynh_dyndns b/data/hooks/restore/42-conf_ynh_dyndns deleted file mode 100644 index 8ed4941ef..000000000 --- a/data/hooks/restore/42-conf_ynh_dyndns +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - -source /usr/share/yunohost/helpers -ynh_abort_if_errors -YNH_CWD="${YNH_BACKUP_DIR%/}/conf/ynh/dyndns" -cd "$YNH_CWD" - -# Restore file if exists -ynh_restore_file --origin_path="/etc/yunohost/dyndns" --not_mandatory From 2388a16fd23f7645af43a6ebdad0063ec3b63f65 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 6 Apr 2021 02:37:07 +0200 Subject: [PATCH 2350/3170] Fix tests --- src/yunohost/tests/test_backuprestore.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/yunohost/tests/test_backuprestore.py b/src/yunohost/tests/test_backuprestore.py index 7a2a64756..e5c4724d9 100644 --- a/src/yunohost/tests/test_backuprestore.py +++ b/src/yunohost/tests/test_backuprestore.py @@ -413,7 +413,7 @@ def test_backup_with_different_output_directory(mocker): # Create the backup with message(mocker, "backup_created"): backup_create( - system=["conf_ssh"], + system=["conf_ynh_settings"], apps=None, output_directory="/opt/test_backup_output_directory", name="backup", @@ -436,7 +436,7 @@ def test_backup_using_copy_method(mocker): # Create the backup with message(mocker, "backup_created"): backup_create( - system=["conf_nginx"], + system=["conf_ynh_settings"], apps=None, output_directory="/opt/test_backup_output_directory", methods=["copy"], @@ -675,9 +675,9 @@ def test_backup_binds_are_readonly(mocker, monkeypatch): def custom_mount_and_backup(self): self._organize_files() - confssh = os.path.join(self.work_dir, "conf/ssh") + conf = os.path.join(self.work_dir, "conf/") output = subprocess.check_output( - "touch %s/test 2>&1 || true" % confssh, + "touch %s/test 2>&1 || true" % conf, shell=True, env={"LANG": "en_US.UTF-8"}, ) From 5b754aecc17ccdf6ece9fdbae69b18f31c19e78a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 6 Apr 2021 03:01:32 +0200 Subject: [PATCH 2351/3170] Fix moar tests --- src/yunohost/tests/test_backuprestore.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/tests/test_backuprestore.py b/src/yunohost/tests/test_backuprestore.py index e5c4724d9..76254ae07 100644 --- a/src/yunohost/tests/test_backuprestore.py +++ b/src/yunohost/tests/test_backuprestore.py @@ -427,7 +427,7 @@ def test_backup_with_different_output_directory(mocker): archives_info = backup_info(archives[0], with_details=True) assert archives_info["apps"] == {} assert len(archives_info["system"].keys()) == 1 - assert "conf_ssh" in archives_info["system"].keys() + assert "conf_ynh_settings" in archives_info["system"].keys() @pytest.mark.clean_opt_dir @@ -675,7 +675,7 @@ def test_backup_binds_are_readonly(mocker, monkeypatch): def custom_mount_and_backup(self): self._organize_files() - conf = os.path.join(self.work_dir, "conf/") + conf = os.path.join(self.work_dir, "conf/ynh") output = subprocess.check_output( "touch %s/test 2>&1 || true" % conf, shell=True, From 6779cc272dcc87b9a955e3e0e6223e3bb249d646 Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Wed, 7 Apr 2021 15:21:10 +0200 Subject: [PATCH 2352/3170] [fix] Remove passphrase keyword from logs --- src/yunohost/log.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/log.py b/src/yunohost/log.py index 592e76bb4..9a3eb9fa6 100644 --- a/src/yunohost/log.py +++ b/src/yunohost/log.py @@ -414,7 +414,7 @@ class RedactingFormatter(Formatter): # This matches stuff like db_pwd=the_secret or admin_password=other_secret # (the secret part being at least 3 chars to avoid catching some lines like just "db_pwd=") # Some names like "key" or "manifest_key" are ignored, used in helpers like ynh_app_setting_set or ynh_read_manifest - match = re.search(r'(pwd|pass|password|secret\w*|\w+key|token)=(\S{3,})$', record.strip()) + match = re.search(r'(pwd|pass|password|passphrase|secret\w*|\w+key|token)=(\S{3,})$', record.strip()) if match and match.group(2) not in self.data_to_redact and match.group(1) not in ["key", "manifest_key"]: self.data_to_redact.append(match.group(2)) except Exception as e: From 0af05ea3128b8144a0677598bbf7c6189c8c3014 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 8 Apr 2021 15:35:09 +0200 Subject: [PATCH 2353/3170] Missing raw_msg=True --- src/yunohost/dyndns.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index b2ac3de6d..c7a501b9c 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -260,7 +260,7 @@ def dyndns_update( ok, result = dig(dyn_host, "A") dyn_host_ip = result[0] if ok == "ok" and len(result) else None if not dyn_host_ip: - raise YunohostError("Failed to resolve %s" % dyn_host) + raise YunohostError("Failed to resolve %s" % dyn_host, raw_msg=True) ok, result = dig(domain, rdtype, resolvers=[dyn_host_ip]) if ok == "ok": From 278a95ab0e060fc9f061a0632f9e56049bcac42c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 8 Apr 2021 23:56:16 +0200 Subject: [PATCH 2354/3170] firewall_list: Don't miserably crash when trying to sort port range ("12300:12400", ain't an int) --- src/yunohost/firewall.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/yunohost/firewall.py b/src/yunohost/firewall.py index bc21f1948..af1cea2e3 100644 --- a/src/yunohost/firewall.py +++ b/src/yunohost/firewall.py @@ -188,18 +188,19 @@ def firewall_list(raw=False, by_ip_version=False, list_forwarded=False): for i in ["ipv4", "ipv6"]: f = firewall[i] # Combine TCP and UDP ports - ports[i] = sorted(set(f["TCP"]) | set(f["UDP"])) + ports[i] = sorted(set(f["TCP"]) | set(f["UDP"]), key=lambda p: int(p.split(':')[0]) if isinstance(p, str) else p) if not by_ip_version: # Combine IPv4 and IPv6 ports - ports = sorted(set(ports["ipv4"]) | set(ports["ipv6"])) + ports = sorted(set(ports["ipv4"]) | set(ports["ipv6"]), key=lambda p: int(p.split(':')[0]) if isinstance(p, str) else p) # Format returned dict ret = {"opened_ports": ports} if list_forwarded: # Combine TCP and UDP forwarded ports ret["forwarded_ports"] = sorted( - set(firewall["uPnP"]["TCP"]) | set(firewall["uPnP"]["UDP"]) + set(firewall["uPnP"]["TCP"]) | set(firewall["uPnP"]["UDP"]), + key=lambda p: int(p.split(':')[0]) if isinstance(p, str) else p ) return ret From f94a5f95a3f5ee3cac7b7b8e4a3c3135460dac8a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 9 Apr 2021 00:00:40 +0200 Subject: [PATCH 2355/3170] nginx conf: CSP rules for admin was blocking small images used for checkboxes, radio, pacman in the new webadmin --- data/templates/nginx/plain/yunohost_admin.conf.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/templates/nginx/plain/yunohost_admin.conf.inc b/data/templates/nginx/plain/yunohost_admin.conf.inc index 26f348dea..326e003ee 100644 --- a/data/templates/nginx/plain/yunohost_admin.conf.inc +++ b/data/templates/nginx/plain/yunohost_admin.conf.inc @@ -6,6 +6,6 @@ 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: 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'; img-src 'self' data:;"; more_set_headers "Content-Security-Policy-Report-Only:"; } From 81ba89483d171fca91cac57f35255a1d8bb15d78 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 9 Apr 2021 19:39:52 +0200 Subject: [PATCH 2356/3170] backuprestore: Fix binds readonly test --- 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 76254ae07..30204fa86 100644 --- a/src/yunohost/tests/test_backuprestore.py +++ b/src/yunohost/tests/test_backuprestore.py @@ -675,7 +675,7 @@ def test_backup_binds_are_readonly(mocker, monkeypatch): def custom_mount_and_backup(self): self._organize_files() - conf = os.path.join(self.work_dir, "conf/ynh") + conf = os.path.join(self.work_dir, "conf/ynh/dkim") output = subprocess.check_output( "touch %s/test 2>&1 || true" % conf, shell=True, From e3b90e6bdc6c7e83033814bb8d7b0cffd25e84e3 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 9 Apr 2021 20:38:49 +0200 Subject: [PATCH 2357/3170] backup: Reorder hook for better readability in webadmin --- data/hooks/backup/{40-conf_ynh_settings => 20-conf_ynh_settings} | 0 data/hooks/restore/{40-conf_ynh_settings => 20-conf_ynh_settings} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename data/hooks/backup/{40-conf_ynh_settings => 20-conf_ynh_settings} (100%) rename data/hooks/restore/{40-conf_ynh_settings => 20-conf_ynh_settings} (100%) diff --git a/data/hooks/backup/40-conf_ynh_settings b/data/hooks/backup/20-conf_ynh_settings similarity index 100% rename from data/hooks/backup/40-conf_ynh_settings rename to data/hooks/backup/20-conf_ynh_settings diff --git a/data/hooks/restore/40-conf_ynh_settings b/data/hooks/restore/20-conf_ynh_settings similarity index 100% rename from data/hooks/restore/40-conf_ynh_settings rename to data/hooks/restore/20-conf_ynh_settings From e6312db3c052fba84d8c7733d56f568bbb77a100 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 9 Apr 2021 21:07:35 +0200 Subject: [PATCH 2358/3170] perf: add optional 'apps' argument to user_permission_list to speed up user_info / user_list --- data/actionsmap/yunohost.yml | 3 +++ data/helpers.d/permission | 2 +- src/yunohost/app.py | 8 +++----- src/yunohost/permission.py | 15 ++++++++++----- src/yunohost/user.py | 4 ++-- 5 files changed, 19 insertions(+), 13 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 290952aa3..b7afe2703 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -295,6 +295,9 @@ user: action_help: List permissions and corresponding accesses api: GET /users/permissions arguments: + apps: + help: Apps to list permission for (all by default) + nargs: "*" -s: full: --short help: Only list permission names diff --git a/data/helpers.d/permission b/data/helpers.d/permission index 995bff0eb..a5c09cded 100644 --- a/data/helpers.d/permission +++ b/data/helpers.d/permission @@ -182,7 +182,7 @@ ynh_permission_exists() { local permission ynh_handle_getopts_args "$@" - yunohost user permission list --output-as json --quiet \ + yunohost user permission list "$app" --output-as json --quiet \ | jq -e --arg perm "$app.$permission" '.permissions[$perm]' >/dev/null } diff --git a/src/yunohost/app.py b/src/yunohost/app.py index b4f162e17..394def0ba 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -194,7 +194,7 @@ def app_info(app, full=False): ) local_manifest = _get_manifest_of_app(os.path.join(APPS_SETTING_PATH, app)) - permissions = user_permission_list(full=True, absolute_urls=True)["permissions"] + permissions = user_permission_list(full=True, absolute_urls=True, apps=[app])["permissions"] settings = _get_app_settings(app) @@ -229,9 +229,7 @@ def app_info(app, full=False): local_manifest.get("multi_instance", False) ) - ret["permissions"] = { - p: i for p, i in permissions.items() if p.startswith(app + ".") - } + ret["permissions"] = permissions ret["label"] = permissions.get(app + ".main", {}).get("label") if not ret["label"]: @@ -1435,7 +1433,7 @@ def app_setting(app, key, value=None, delete=False): permission_url, ) - permissions = user_permission_list(full=True)["permissions"] + permissions = user_permission_list(full=True, apps=[app])["permissions"] permission_name = "%s.legacy_%s_uris" % (app, key.split("_")[0]) permission = permissions.get(permission_name) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index e0a3c6be8..3ed9590d4 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -46,7 +46,7 @@ SYSTEM_PERMS = ["mail", "xmpp", "sftp", "ssh"] def user_permission_list( - short=False, full=False, ignore_system_perms=False, absolute_urls=False + short=False, full=False, ignore_system_perms=False, absolute_urls=False, apps=[] ): """ List permissions and corresponding accesses @@ -74,7 +74,9 @@ def user_permission_list( ) # Parse / organize information to be outputed - apps = sorted(_installed_apps()) + if apps: + ignore_system_perms = True + apps = apps if apps else sorted(_installed_apps()) apps_base_path = { app: app_setting(app, "domain") + app_setting(app, "path") for app in apps @@ -85,11 +87,14 @@ def user_permission_list( for infos in permissions_infos: name = infos["cn"][0] - if ignore_system_perms and name.split(".")[0] in SYSTEM_PERMS: - continue - app = name.split(".")[0] + if app in SYSTEM_PERMS: + if ignore_system_perms: + continue + elif app not in apps: + continue + perm = {} perm["allowed"] = [ _ldap_path_extract(p, "cn") for p in infos.get("groupPermission", []) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 089f2ba0e..aee5bf473 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -862,10 +862,10 @@ def user_group_info(groupname): # -def user_permission_list(short=False, full=False): +def user_permission_list(short=False, full=False, apps=[]): import yunohost.permission - return yunohost.permission.user_permission_list(short, full, absolute_urls=True) + return yunohost.permission.user_permission_list(short, full, absolute_urls=True, apps=apps) def user_permission_update( From aefc100ab4142a6d2e3deefa6393a2187a2a63f9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 9 Apr 2021 22:49:02 +0200 Subject: [PATCH 2359/3170] security: Enforce some permission for regular yunohost users --- data/hooks/conf_regen/01-yunohost | 8 ++++++++ data/hooks/conf_regen/12-metronome | 5 ++++- data/hooks/conf_regen/25-dovecot | 5 ++++- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/data/hooks/conf_regen/01-yunohost b/data/hooks/conf_regen/01-yunohost index 30828c462..0a92a6a32 100755 --- a/data/hooks/conf_regen/01-yunohost +++ b/data/hooks/conf_regen/01-yunohost @@ -130,6 +130,7 @@ do_post_regen() { # Enfore permissions # ###################### + chmod 750 /home/admin chmod 750 /home/yunohost.conf chmod 750 /home/yunohost.backup chmod 750 /home/yunohost.backup/archives @@ -146,6 +147,13 @@ do_post_regen() { chown root:root /var/cache/yunohost chmod 700 /var/cache/yunohost + chown root:root /var/cache/moulinette + chmod 700 /var/cache/moulinette + + setfacl -m g:all_users:--- /var/www + setfacl -m g:all_users:--- /var/log/nginx + setfacl -m g:all_users:--- /etc/yunohost + setfacl -m g:all_users:--- /etc/ssowat # Misc configuration / state files chown root:root $(ls /etc/yunohost/{*.yml,*.yaml,*.json,mysql,psql} 2>/dev/null) diff --git a/data/hooks/conf_regen/12-metronome b/data/hooks/conf_regen/12-metronome index 31d11555a..ca5d5dc82 100755 --- a/data/hooks/conf_regen/12-metronome +++ b/data/hooks/conf_regen/12-metronome @@ -52,11 +52,14 @@ do_post_regen() { mkdir -p "/var/lib/metronome/${domain//./%2e}/pep" # http_upload directory must be writable by metronome and readable by nginx mkdir -p "/var/xmpp-upload/${domain}/upload" + # sgid bit allows that file created in that dir will be owned by www-data + # despite the fact that metronome ain't in the www-data group chmod g+s "/var/xmpp-upload/${domain}/upload" - chown -R metronome:www-data "/var/xmpp-upload/${domain}" done # fix some permissions + [ ! -e '/var/xmpp-upload' ] || chown -R metronome:www-data "/var/xmpp-upload/" + [ ! -e '/var/xmpp-upload' ] || chmod 750 "/var/xmpp-upload/" # metronome should be in ssl-cert group to let it access SSL certificates usermod -aG ssl-cert metronome diff --git a/data/hooks/conf_regen/25-dovecot b/data/hooks/conf_regen/25-dovecot index 46c9bdf3e..ce2722bf4 100755 --- a/data/hooks/conf_regen/25-dovecot +++ b/data/hooks/conf_regen/25-dovecot @@ -41,7 +41,10 @@ do_post_regen() { # create vmail user id vmail > /dev/null 2>&1 \ - || adduser --system --ingroup mail --uid 500 vmail + || adduser --system --ingroup mail --uid 500 vmail --home /var/vmail --no-create-home + + # Delete legacy home for vmail that existed in the past but was empty, poluting /home/ + [ ! -e /home/vmail ] || rmdir --ignore-fail-on-non-empty /home/vmail # fix permissions chown -R vmail:mail /etc/dovecot/global_script From fc26837aa789fd228fe863e354de9caa249a83e9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 10 Apr 2021 01:04:59 +0200 Subject: [PATCH 2360/3170] security: Enforce permissions on /home/ so that they can't sneak in each other home --- data/hooks/conf_regen/01-yunohost | 5 +++++ src/yunohost/user.py | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/data/hooks/conf_regen/01-yunohost b/data/hooks/conf_regen/01-yunohost index 0a92a6a32..204b33b7d 100755 --- a/data/hooks/conf_regen/01-yunohost +++ b/data/hooks/conf_regen/01-yunohost @@ -155,6 +155,11 @@ do_post_regen() { setfacl -m g:all_users:--- /etc/yunohost setfacl -m g:all_users:--- /etc/ssowat + for USER in $(yunohost user list --quiet --output-as json | jq -r '.users | .[] | .username') + do + [ ! -e "/home/$USER" ] || setfacl -m g:all_users:--- /home/$USER + done + # 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) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 089f2ba0e..755bbd6ee 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -229,6 +229,11 @@ def user_create( if not os.path.isdir("/home/{0}".format(username)): logger.warning(m18n.n("user_home_creation_failed"), exc_info=1) + try: + subprocess.check_call(["setfacl", "-m", "g:all_users:---", "/home/%s" % username]) + except subprocess.CalledProcessError: + logger.warning("Failed to protect /home/%s" % username, exc_info=1) + # Create group for user and add to group 'all_users' user_group_create(groupname=username, gid=uid, primary_group=True, sync_perm=False) user_group_update(groupname="all_users", add=username, force=True, sync_perm=True) From 50bd61fe5144cc67311258d7db98531612806fcf Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 10 Apr 2021 01:09:20 +0200 Subject: [PATCH 2361/3170] Update changelog for 4.2.1 --- debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian/changelog b/debian/changelog index f8211b82e..fe1f42a23 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +yunohost (4.2.1) testing; urgency=low + + - security: Various permissions tweaks to protect from malicious yunohost users (aefc100a, fc26837a) + + -- Alexandre Aubin Sat, 10 Apr 2021 01:08:04 +0200 + yunohost (4.2.0) testing; urgency=low - [mod] Python2 -> Python3 ([#1116](https://github.com/yunohost/yunohost/pull/1116), a97a9df3, 1387dff4, b53859db, f5ab4443, f9478b93, dc6033c3) From 442a1cf96de0fe776ab25bef9e0144ddb7146368 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 10 Apr 2021 15:24:22 +0200 Subject: [PATCH 2362/3170] Return a dict structure to the API for ValidationErrors, for easier error identification/interface --- src/yunohost/utils/error.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/yunohost/utils/error.py b/src/yunohost/utils/error.py index c0ff2690c..f9b4ac61a 100644 --- a/src/yunohost/utils/error.py +++ b/src/yunohost/utils/error.py @@ -48,7 +48,7 @@ class YunohostError(MoulinetteError): def content(self): if not self.log_ref: - return super(YunohostError, self).content() + return super().content() else: return {"error": self.strerror, "log_ref": self.log_ref} @@ -56,3 +56,7 @@ class YunohostError(MoulinetteError): class YunohostValidationError(YunohostError): http_code = 400 + + def content(self): + + return {"error": self.strerror, "error_key": self.key} From 48c8dc7dea30b531de0b74748bbf12b4635f5120 Mon Sep 17 00:00:00 2001 From: lapineige Date: Sun, 11 Apr 2021 18:25:45 +0200 Subject: [PATCH 2363/3170] Diagnosis command : use "--human-readable" (#1207) * Diagnosis command : use "--human-readable" As recommended in the documentation https://yunohost.org/fr/install/hardware:vps_debian * Diagnosis command : use "--human-readable" * Propagate changes to all locales Co-authored-by: Alexandre Aubin --- locales/ca.json | 2 +- locales/de.json | 4 ++-- locales/en.json | 2 +- locales/eo.json | 4 ++-- locales/es.json | 4 ++-- locales/fr.json | 4 ++-- locales/it.json | 2 +- locales/oc.json | 2 +- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/locales/ca.json b/locales/ca.json index ceaa6791e..43d88f867 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -515,7 +515,7 @@ "diagnosis_basesystem_ynh_inconsistent_versions": "Esteu utilitzant versions inconsistents dels paquets de YunoHost… probablement a causa d'una actualització fallida o parcial.", "diagnosis_display_tip_web": "Podeu anar a la secció de Diagnòstics (en la pantalla principal) per veure els errors que s'han trobat.", "diagnosis_failed_for_category": "Ha fallat el diagnòstic per la categoria «{category}»: {error}", - "diagnosis_display_tip_cli": "Podeu executar «yunohost diagnosis show --issues» per mostrar els errors que s'han trobat.", + "diagnosis_display_tip_cli": "Podeu executar «yunohost diagnosis show --issues --human-readable» per mostrar els errors que s'han trobat.", "diagnosis_cache_still_valid": "(La memòria cau encara és vàlida pel diagnòstic de {category}. No es tornar a diagnosticar de moment!)", "diagnosis_cant_run_because_of_dep": "No es pot fer el diagnòstic per {category} mentre hi ha problemes importants relacionats amb {dep}.", "diagnosis_ignored_issues": "(+ {nb_ignored} problema(es) ignorat(s))", diff --git a/locales/de.json b/locales/de.json index b3617e476..6da229291 100644 --- a/locales/de.json +++ b/locales/de.json @@ -314,7 +314,7 @@ "apps_catalog_obsolete_cache": "Der Cache des App-Katalogs ist leer oder veraltet.", "apps_catalog_update_success": "Der Apps-Katalog wurde aktualisiert!", "password_too_simple_1": "Das Passwort muss mindestens 8 Zeichen lang sein", - "diagnosis_display_tip_cli": "Sie können 'yunohost diagnosis show --issues' ausführen, um die gefundenen Probleme anzuzeigen.", + "diagnosis_display_tip_cli": "Sie können 'yunohost diagnosis show --issues --human-readable' ausführen, um die gefundenen Probleme anzuzeigen.", "diagnosis_everything_ok": "Alles schaut gut aus für {category}!", "diagnosis_failed": "Kann Diagnose-Ergebnis für die Kategorie '{category}' nicht abrufen: {error}", "diagnosis_ip_connected_ipv4": "Der Server ist mit dem Internet über IPv4 verbunden!", @@ -338,7 +338,7 @@ "diagnosis_found_warnings": "Habe {warnings} Ding(e) gefunden, die verbessert werden könnten für {category}.", "diagnosis_ip_dnsresolution_working": "Domänen-Namens-Auflösung funktioniert!", "diagnosis_ip_weird_resolvconf": "DNS Auflösung scheint zu funktionieren, aber seien Sie vorsichtig wenn Sie Ihren eigenen /etc/resolv.conf verwenden.", - "diagnosis_display_tip": "Um die gefundenen Probleme zu sehen, können Sie zum Diagnose-Bereich des webadmin gehen, oder 'yunohost diagnosis show --issues' in der Kommandozeile ausführen.", + "diagnosis_display_tip": "Um die gefundenen Probleme zu sehen, können Sie zum Diagnose-Bereich des webadmin gehen, oder 'yunohost diagnosis show --issues --human-readable' in der Kommandozeile ausführen.", "backup_archive_corrupted": "Das Backup-Archiv '{archive}' scheint beschädigt: {error}", "backup_archive_cant_retrieve_info_json": "Die Informationen für das Archiv '{archive}' konnten nicht geladen werden... Die Datei info.json wurde nicht gefunden (oder ist kein gültiges json).", "app_packaging_format_not_supported": "Diese App kann nicht installiert werden da das Paketformat nicht von der YunoHost-Version unterstützt wird. Denken Sie darüber nach das System zu aktualisieren.", diff --git a/locales/en.json b/locales/en.json index aef38c693..f60f2f2e7 100644 --- a/locales/en.json +++ b/locales/en.json @@ -152,7 +152,7 @@ "diagnosis_backports_in_sources_list": "It looks like apt (the package manager) is configured to use the backports repository. Unless you really know what you are doing, we strongly discourage from installing packages from backports, because it's likely to create unstabilities or conflicts on your system.", "diagnosis_package_installed_from_sury": "Some system packages should be downgraded", "diagnosis_package_installed_from_sury_details": "Some packages were inadvertendly installed from a third-party repository called Sury. The Yunohost team improved the strategy that handle these packages, but it's expected that some setups that installed PHP7.3 apps while still on Stretch have some remaining inconsistencies. To fix this situation, you should try running the following command: {cmd_to_fix}", - "diagnosis_display_tip": "To see the issues found, you can go to the Diagnosis section of the webadmin, or run 'yunohost diagnosis show --issues' from the command-line.", + "diagnosis_display_tip": "To see the issues found, you can go to the Diagnosis section of the webadmin, or run 'yunohost diagnosis show --issues --human-readable' from the command-line.", "diagnosis_failed_for_category": "Diagnosis failed for category '{category}': {error}", "diagnosis_cache_still_valid": "(Cache still valid for {category} diagnosis. Won't re-diagnose it yet!)", "diagnosis_cant_run_because_of_dep": "Can't run diagnosis for {category} while there are important issues related to {dep}.", diff --git a/locales/eo.json b/locales/eo.json index 95030d8fa..fdcb7845c 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -510,7 +510,7 @@ "diagnosis_display_tip_web": "Vi povas iri al la sekcio Diagnozo (en la hejmekrano) por vidi la trovitajn problemojn.", "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_display_tip_cli": "Vi povas aranĝi 'yunohost diagnosis show --issues --human-readable' 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 !", @@ -602,7 +602,7 @@ "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_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 --human-readable\" 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.", diff --git a/locales/es.json b/locales/es.json index a93c3f244..59d3dc162 100644 --- a/locales/es.json +++ b/locales/es.json @@ -512,7 +512,7 @@ "diagnosis_cache_still_valid": "(Caché aún válida para el diagnóstico de {category}. ¡No se volvera a comprobar de momento!)", "diagnosis_found_errors_and_warnings": "¡Encontrado(s) error(es) significativo(s) {errors} (y aviso(s) {warnings}) relacionado(s) con {category}!", "diagnosis_display_tip_web": "Puede ir a la sección de diagnóstico (en la pantalla principal) para ver los problemas encontrados.", - "diagnosis_display_tip_cli": "Puede ejecutar «yunohost diagnosis show --issues» para mostrar los problemas encontrados.", + "diagnosis_display_tip_cli": "Puede ejecutar «yunohost diagnosis show --issues --human-readable» para mostrar los problemas encontrados.", "apps_catalog_init_success": "¡Sistema de catálogo de aplicaciones inicializado!", "apps_catalog_updating": "Actualizando el catálogo de aplicaciones…", "apps_catalog_failed_to_download": "No se puede descargar el catálogo de aplicaciones {apps_catalog}: {error}", @@ -617,7 +617,7 @@ "diagnosis_dns_try_dyndns_update_force": "La configuración DNS de este dominio debería ser administrada automáticamente por Yunohost. Si no es el caso, puede intentar forzar una actualización ejecutando yunohost dyndns update --force.", "diagnosis_ip_local": "IP Local: {local}", "diagnosis_ip_no_ipv6_tip": "Tener IPv6 funcionando no es obligatorio para que su servidor funcione, pero es mejor para la salud del Internet en general. IPv6 debería ser configurado automáticamente por el sistema o su proveedor si está disponible. De otra manera, es posible que tenga que configurar varias cosas manualmente, tal y como se explica en esta documentación https://yunohost.org/#/ipv6. Si no puede habilitar IPv6 o si parece demasiado técnico, puede ignorar esta advertencia con toda seguridad.", - "diagnosis_display_tip": "Para ver los problemas encontrados, puede ir a la sección de diagnóstico del webadmin, o ejecutar 'yunohost diagnosis show --issues' en la línea de comandos.", + "diagnosis_display_tip": "Para ver los problemas encontrados, puede ir a la sección de diagnóstico del webadmin, o ejecutar 'yunohost diagnosis show --issues --human-readable' en la línea de comandos.", "diagnosis_package_installed_from_sury_details": "Algunos paquetes fueron accidentalmente instalados de un repositorio de terceros llamado Sury. El equipo Yunohost ha mejorado la estrategia para manejar estos pquetes, pero es posible que algunas instalaciones con aplicaciones de PHP7.3 en Stretch puedan tener algunas inconsistencias. Para solucionar esta situación, debería intentar ejecutar el siguiente comando: {cmd_to_fix}", "diagnosis_package_installed_from_sury": "Algunos paquetes del sistema deberían ser devueltos a una versión anterior", "certmanager_domain_not_diagnosed_yet": "Aún no hay resultado del diagnóstico para el dominio {domain}. Por favor ejecute el diagnóstico para las categorías 'Registros DNS' y 'Web' en la sección de diagnóstico para verificar si el dominio está listo para Let's Encrypt. (O si sabe lo que está haciendo, utilice '--no-checks' para deshabilitar esos chequeos.)", diff --git a/locales/fr.json b/locales/fr.json index e60e17d0e..ab5411436 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -496,7 +496,7 @@ "diagnosis_basesystem_ynh_single_version": "{package} version : {version} ({repo})", "diagnosis_basesystem_ynh_main_version": "Le serveur utilise YunoHost {main_version} ({repo})", "diagnosis_basesystem_ynh_inconsistent_versions": "Vous exécutez des versions incohérentes des packages YunoHost ... très probablement en raison d’une mise à niveau échouée ou partielle.", - "diagnosis_display_tip_cli": "Vous pouvez exécuter 'yunohost diagnosis show --issues' pour afficher les problèmes détectés.", + "diagnosis_display_tip_cli": "Vous pouvez exécuter 'yunohost diagnosis show --issues --human-readable' pour afficher les problèmes détectés.", "diagnosis_failed_for_category": "Échec du diagnostic pour la catégorie '{category}': {error}", "diagnosis_cache_still_valid": "(Le cache est encore valide pour le diagnostic {category}. Il ne sera pas re-diagnostiqué pour le moment!)", "diagnosis_ignored_issues": "(+ {nb_ignored} problème(s) ignoré(s))", @@ -595,7 +595,7 @@ "diagnosis_mail_queue_too_big": "Trop d’e-mails en attente dans la file d'attente ({nb_pending} e-mails)", "global_settings_setting_smtp_allow_ipv6": "Autoriser l'utilisation d’IPv6 pour recevoir et envoyer du courrier", "diagnosis_security_all_good": "Aucune vulnérabilité de sécurité critique n’a été trouvée.", - "diagnosis_display_tip": "Pour voir les problèmes détectés, vous pouvez accéder à la section Diagnostic du webadmin ou exécuter « yunohost diagnosis show --issues » à partir de la ligne de commande.", + "diagnosis_display_tip": "Pour voir les problèmes détectés, vous pouvez accéder à la section Diagnostic du webadmin ou exécuter « yunohost diagnosis show --issues --human-readable» à partir de la ligne de commande.", "diagnosis_ip_global": "IP globale : {global}", "diagnosis_ip_local": "IP locale : {local}", "diagnosis_dns_point_to_doc": "Veuillez consulter la documentation sur https://yunohost.org/dns_config si vous avez besoin d’aide pour configurer les enregistrements DNS.", diff --git a/locales/it.json b/locales/it.json index 22248367a..1684963a7 100644 --- a/locales/it.json +++ b/locales/it.json @@ -410,7 +410,7 @@ "diagnosis_cant_run_because_of_dep": "Impossibile lanciare la diagnosi per {category} mentre ci sono problemi importanti collegati a {dep}.", "diagnosis_cache_still_valid": "(La cache della diagnosi di {category} è ancora valida. Non la ricontrollo di nuovo per ora!)", "diagnosis_failed_for_category": "Diagnosi fallita per la categoria '{category}:{error}", - "diagnosis_display_tip": "Per vedere i problemi rilevati, puoi andare alla sezione Diagnosi del amministratore, o eseguire 'yunohost diagnosis show --issues' dalla riga di comando.", + "diagnosis_display_tip": "Per vedere i problemi rilevati, puoi andare alla sezione Diagnosi del amministratore, o eseguire 'yunohost diagnosis show --issues --human-readable' dalla riga di comando.", "diagnosis_package_installed_from_sury_details": "Alcuni pacchetti sono stati inavvertitamente installati da un repository di terze parti chiamato Sury. Il team di Yunohost ha migliorato la gestione di tali pacchetti, ma ci si aspetta che alcuni setup di app PHP7.3 abbiano delle incompatibilità anche se sono ancora in Stretch. Per sistemare questa situazione, dovresti provare a lanciare il seguente comando: {cmd_to_fix}", "diagnosis_package_installed_from_sury": "Alcuni pacchetti di sistema dovrebbero fare il downgrade", "diagnosis_mail_ehlo_bad_answer": "Un servizio diverso da SMTP ha risposto sulla porta 25 su IPv{ipversion}", diff --git a/locales/oc.json b/locales/oc.json index 3bed9cd5a..4a4466101 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -499,7 +499,7 @@ "diagnosis_basesystem_kernel": "Lo servidor fonciona amb lo nuclèu Linuxl {kernel_version}", "diagnosis_basesystem_ynh_single_version": "{package} version : {version} ({repo})", "diagnosis_basesystem_ynh_inconsistent_versions": "Utilizatz de versions inconsistentas dels paquets de YunoHost… probablament a causa d'una actualizacion fracassada o parciala.", - "diagnosis_display_tip_cli": "Podètz executar « yunohost diagnosis show --issues » per mostrar las errors trobadas.", + "diagnosis_display_tip_cli": "Podètz executar « yunohost diagnosis show --issues --human-readable » per mostrar las errors trobadas.", "diagnosis_ignored_issues": "(+ {nb_ignored} problèma(es) ignorat(s))", "diagnosis_everything_ok": "Tot sembla corrècte per {category} !", "diagnosis_ip_connected_ipv4": "Lo servidor es connectat a Internet via IPv4 !", From e1d7a7b3f905b0f206895f03bbecd4d2b69be8e4 Mon Sep 17 00:00:00 2001 From: Christian Wehrli Date: Fri, 9 Apr 2021 20:42:09 +0000 Subject: [PATCH 2364/3170] Translated using Weblate (German) Currently translated at 87.7% (551 of 628 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/locales/de.json b/locales/de.json index 4db27e68a..fa5e86764 100644 --- a/locales/de.json +++ b/locales/de.json @@ -617,5 +617,8 @@ "restore_extracting": "Packe die benötigten Dateien aus dem Archiv aus…", "restore_already_installed_apps": "Folgende Apps können nicht wiederhergestellt werden, weil sie schon installiert sind: {apps}", "regex_with_only_domain": "Du kannst regex nicht als Domain verwenden, sondern nur als Pfad", - "root_password_desynchronized": "Das Admin-Passwort wurde verändert, aber das root Passwort ist immer noch das alte." + "root_password_desynchronized": "Das Admin-Passwort wurde verändert, aber das root Passwort ist immer noch das alte.", + "regenconf_need_to_explicitly_specify_ssh": "Die SSH-Konfiguration wurde manuell modifiziert, aber Sie müssen explizit die Kategorie 'SSH' mit --force spezifizieren, um die Änderungen tatsächlich anzuwenden.", + "migration_update_LDAP_schema": "Aktualisiere das LDAP-Schema...", + "log_backup_create": "Erstelle ein Backup-Archiv" } From aabe5f19a9cc6de1fad3f4d6ab98d59daf1d720c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 11 Apr 2021 18:40:53 +0200 Subject: [PATCH 2365/3170] Remove stale strings (using the util available in tests/) --- locales/ar.json | 23 +--------- locales/ca.json | 98 +---------------------------------------- locales/cs.json | 2 +- locales/de.json | 37 +--------------- locales/el.json | 2 +- locales/eo.json | 98 +---------------------------------------- locales/es.json | 101 +------------------------------------------ locales/eu.json | 2 +- locales/fr.json | 76 +------------------------------- locales/hi.json | 2 - locales/it.json | 58 +------------------------ locales/nb_NO.json | 15 ------- locales/ne.json | 2 +- locales/nl.json | 7 +-- locales/oc.json | 96 +--------------------------------------- locales/pl.json | 2 +- locales/pt.json | 11 +---- locales/zh_Hans.json | 2 +- 18 files changed, 16 insertions(+), 618 deletions(-) diff --git a/locales/ar.json b/locales/ar.json index cbfb7232c..06e444f4a 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -20,7 +20,6 @@ "app_upgrade_failed": "تعذرت عملية ترقية {app:s}", "app_upgrade_some_app_failed": "تعذرت عملية ترقية بعض التطبيقات", "app_upgraded": "تم تحديث التطبيق {app:s}", - "ask_email": "عنوان البريد الإلكتروني", "ask_firstname": "الإسم", "ask_lastname": "اللقب", "ask_main_domain": "النطاق الرئيسي", @@ -29,7 +28,6 @@ "backup_applying_method_copy": "جارٍ نسخ كافة الملفات إلى النسخة الإحتياطية …", "backup_applying_method_tar": "جارٍ إنشاء ملف TAR للنسخة الاحتياطية…", "backup_created": "تم إنشاء النسخة الإحتياطية", - "backup_invalid_archive": "نسخة إحتياطية غير صالحة", "backup_method_copy_finished": "إنتهت عملية النسخ الإحتياطي", "backup_nothings_done": "ليس هناك أي شيء للحفظ", "backup_output_directory_required": "يتوجب عليك تحديد مجلد لتلقي النسخ الإحتياطية", @@ -37,7 +35,6 @@ "certmanager_cert_install_success_selfsigned": "نجحت عملية تثبيت الشهادة الموقعة ذاتيا الخاصة بالنطاق {domain:s}", "certmanager_cert_renew_success": "نجحت عملية تجديد شهادة Let's Encrypt الخاصة باسم النطاق {domain:s} !", "certmanager_cert_signing_failed": "فشل إجراء توقيع الشهادة الجديدة", - "certmanager_domain_unknown": "النطاق مجهول {domain:s}", "certmanager_no_cert_file": "تعذرت عملية قراءة شهادة نطاق {domain:s} (الملف : {file:s})", "domain_created": "تم إنشاء النطاق", "domain_creation_failed": "تعذرت عملية إنشاء النطاق", @@ -54,10 +51,6 @@ "installation_complete": "إكتملت عملية التنصيب", "main_domain_change_failed": "تعذّر تغيير النطاق الأساسي", "main_domain_changed": "تم تغيير النطاق الأساسي", - "migrate_tsig_wait": "لننتظر الآن ثلاثة دقائق ريثما يأخذ خادم أسماء النطاقات الديناميكية بعين الاعتبار المفتاح الجديد…", - "migrate_tsig_wait_2": "دقيقتين …", - "migrate_tsig_wait_3": "دقيقة واحدة …", - "migrate_tsig_wait_4": "30 ثانية …", "migrations_skip_migration": "جارٍ تجاهل التهجير {id}…", "pattern_domain": "يتوجب أن يكون إسم نطاق صالح (مثل my-domain.org)", "pattern_email": "يتوجب أن يكون عنوان بريد إلكتروني صالح (مثل someone@domain.org)", @@ -87,24 +80,14 @@ "user_unknown": "المستخدم {user:s} مجهول", "user_update_failed": "لا يمكن تحديث المستخدم", "user_updated": "تم تحديث المستخدم", - "yunohost_ca_creation_failed": "تعذرت عملية إنشاء هيئة الشهادات", - "yunohost_ca_creation_success": "تم إنشاء هيئة الشهادات المحلية.", "yunohost_installing": "عملية تنصيب يونوهوست جارية …", "yunohost_not_installed": "إنَّ واي يونوهوست ليس مُنَصَّب أو هو مثبت حاليا بشكل خاطئ. قم بتنفيذ الأمر 'yunohost tools postinstall'", - "migration_description_0003_migrate_to_stretch": "تحديث النظام إلى ديبيان ستريتش و واي يونوهوست 3.0", - "migration_0003_patching_sources_list": "عملية تصحيح ملف المصادر sources.lists جارية…", - "migration_0003_main_upgrade": "بداية عملية التحديث الأساسية…", - "migration_0003_fail2ban_upgrade": "بداية عملية تحديث Fail2Ban…", - "migration_0003_not_jessie": "إن توزيعة ديبيان الحالية تختلف عن جيسي !", - "migration_description_0002_migrate_to_tsig_sha256": "يقوم بتحسين أمان TSIG لنظام أسماء النطاقات الديناميكة باستخدام SHA512 بدلًا مِن MD5", - "migration_0003_system_not_fully_up_to_date": "إنّ نظامك غير مُحدَّث بعدُ لذا يرجى القيام بتحديث عادي أولا قبل إطلاق إجراء الإنتقال إلى نظام ستريتش.", "migrations_list_conflict_pending_done": "لا يمكنك استخدام --previous و --done معًا على نفس سطر الأوامر.", "service_description_avahi-daemon": "يسمح لك بالنفاذ إلى خادومك عبر الشبكة المحلية باستخدام yunohost.local", "service_description_metronome": "يُدير حسابات الدردشة الفورية XMPP", "service_description_nginx": "يقوم بتوفير النفاذ و السماح بالوصول إلى كافة مواقع الويب المستضافة على خادومك", "service_description_postfix": "يقوم بإرسال و تلقي الرسائل البريدية الإلكترونية", "service_description_yunohost-api": "يقوم بإدارة التفاعلات ما بين واجهة الويب لواي يونوهوست و النظام", - "log_category_404": "فئةالسجل '{category}' لا وجود لها", "log_app_change_url": "تعديل رابط تطبيق '{}'", "log_app_install": "تنصيب تطبيق '{}'", "log_app_remove": "حذف تطبيق '{}'", @@ -128,12 +111,10 @@ "log_tools_upgrade": "تحديث حُزم ديبيان", "log_tools_shutdown": "إطفاء الخادم", "log_tools_reboot": "إعادة تشغيل الخادم", - "migration_description_0005_postgresql_9p4_to_9p6": "تهجير قواعد البيانات مِن postgresql 9.4 إلى 9.6", "service_description_dnsmasq": "مُكلَّف بتحليل أسماء النطاقات (DNS)", "service_description_mysql": "يقوم بتخزين بيانات التطبيقات (قواعد بيانات SQL)", "service_description_rspamd": "يقوم بتصفية البريد المزعج و إدارة ميزات أخرى للبريد", "service_description_yunohost-firewall": "يُدير فتح وإغلاق منافذ الاتصال إلى الخدمات", - "users_available": "المستخدمون المتوفرون:", "aborting": "إلغاء.", "admin_password_too_long": "يرجى اختيار كلمة سرية أقصر مِن 127 حرف", "app_not_upgraded": "", @@ -146,9 +127,7 @@ "global_settings_setting_security_password_admin_strength": "قوة الكلمة السرية الإدارية", "global_settings_setting_security_password_user_strength": "قوة الكلمة السرية للمستخدم", "password_too_simple_1": "يجب أن يكون طول الكلمة السرية على الأقل 8 حروف", - "service_description_php7.0-fpm": "يُشغّل التطبيقات المكتوبة بلغة الـ PHP على NGINX", "already_up_to_date": "كل شيء على ما يرام. ليس هناك ما يتطلّب تحديثًا.", - "service_description_nslcd": "يدير اتصال متسخدمي واي يونوهوست عبر طرفية سطر الأوامر", "service_description_slapd": "يخزّن المستخدمين والنطاقات والمعلومات المتعلقة بها", "service_reloaded": "تم إعادة تشغيل خدمة '{service:s}'", "service_restarted": "تم إعادة تشغيل خدمة '{service:s}'", @@ -184,4 +163,4 @@ "diagnosis_description_dnsrecords": "تسجيلات خدمة DNS", "diagnosis_description_ip": "الإتصال بالإنترنت", "diagnosis_description_basesystem": "النظام الأساسي" -} +} \ No newline at end of file diff --git a/locales/ca.json b/locales/ca.json index 43d88f867..7823c8c02 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -32,7 +32,6 @@ "app_upgrade_failed": "No s'ha pogut actualitzar {app:s}: {error}", "app_upgrade_some_app_failed": "No s'han pogut actualitzar algunes aplicacions", "app_upgraded": "S'ha actualitzat {app:s}", - "ask_email": "Adreça de correu electrònic", "ask_firstname": "Nom", "ask_lastname": "Cognom", "ask_main_domain": "Domini principal", @@ -40,7 +39,6 @@ "ask_password": "Contrasenya", "backup_abstract_method": "Encara està per implementar aquest mètode de còpia de seguretat", "backup_app_failed": "No s'ha pogut fer la còpia de seguretat de {app:s}", - "backup_applying_method_borg": "Enviant tots els fitxers de la còpia de seguretat al repositori borg-backup...", "backup_applying_method_copy": "Còpia de tots els fitxers a la còpia de seguretat...", "backup_applying_method_custom": "Crida del mètode de còpia de seguretat personalitzat \"{method:s}\"...", "backup_applying_method_tar": "Creació de l'arxiu TAR de la còpia de seguretat...", @@ -52,7 +50,6 @@ "backup_archive_system_part_not_available": "La part «{part:s}» del sistema no està disponible en aquesta copia de seguretat", "backup_archive_writing_error": "No es poden afegir els arxius «{source:s}» (anomenats en l'arxiu «{dest:s}») a l'arxiu comprimit de la còpia de seguretat «{archive:s}»", "backup_ask_for_copying_if_needed": "Voleu fer la còpia de seguretat utilitzant {size:s}MB temporalment? (S'utilitza aquest mètode ja que alguns dels fitxers no s'han pogut preparar utilitzar un mètode més eficient.)", - "backup_borg_not_implemented": "El mètode de còpia de seguretat Borg encara no està implementat", "backup_cant_mount_uncompress_archive": "No es pot carregar l'arxiu descomprimit com a protegit contra escriptura", "backup_cleaning_failed": "No s'ha pogut netejar el directori temporal de la còpia de seguretat", "backup_copying_to_organize_the_archive": "Copiant {size:s}MB per organitzar l'arxiu", @@ -76,8 +73,6 @@ "backup_delete_error": "No s'ha pogut suprimir «{path:s}»", "backup_deleted": "S'ha suprimit la còpia de seguretat", "backup_hook_unknown": "Script de còpia de seguretat «{hook:s}» desconegut", - "backup_invalid_archive": "Aquest no és un arxiu de còpia de seguretat", - "backup_method_borg_finished": "La còpia de seguretat a Borg ha acabat", "backup_method_copy_finished": "La còpia de la còpia de seguretat ha acabat", "backup_method_custom_finished": "El mètode de còpia de seguretat personalitzat \"{method:s}\" ha acabat", "backup_method_tar_finished": "S'ha creat l'arxiu de còpia de seguretat TAR", @@ -94,7 +89,6 @@ "backup_output_directory_not_empty": "Heu d'escollir un directori de sortida buit", "backup_output_directory_required": "Heu d'especificar un directori de sortida per la còpia de seguretat", "backup_output_symlink_dir_broken": "El directori del arxiu «{path:s}» es un enllaç simbòlic trencat. Pot ser heu oblidat muntar, tornar a muntar o connectar el mitja d'emmagatzematge al que apunta.", - "backup_php5_to_php7_migration_may_fail": "No s'ha pogut convertir l'arxiu per suportar PHP 7, pot ser que no es puguin restaurar les vostres aplicacions PHP (raó: {error:s})", "backup_running_hooks": "Executant els scripts de la còpia de seguretat...", "backup_system_part_failed": "No s'ha pogut fer la còpia de seguretat de la part \"{part:s}\" del sistema", "backup_unable_to_organize_files": "No s'ha pogut utilitzar el mètode ràpid per organitzar els fitxers dins de l'arxiu", @@ -110,15 +104,10 @@ "certmanager_cert_renew_success": "S'ha renovat correctament el certificat Let's Encrypt pel domini «{domain:s}»", "certmanager_cert_signing_failed": "No s'ha pogut firmar el nou certificat", "certmanager_certificate_fetching_or_enabling_failed": "Sembla que utilitzar el nou certificat per {domain:s} ha fallat...", - "certmanager_conflicting_nginx_file": "No s'ha pogut preparar el domini per al desafiament ACME: l'arxiu de configuració NGINX {filepath:s} entra en conflicte i s'ha d'eliminar primer", - "certmanager_couldnt_fetch_intermediate_cert": "S'ha exhaurit el temps d'esperar al intentar recollir el certificat intermedi des de Let's Encrypt. La instal·lació/renovació del certificat s'ha cancel·lat - torneu a intentar-ho més tard.", "certmanager_domain_cert_not_selfsigned": "El certificat pel domini {domain:s} no és auto-signat Esteu segur de voler canviar-lo? (Utilitzeu «--force» per fer-ho)", "certmanager_domain_dns_ip_differs_from_public_ip": "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", - "certmanager_http_check_timeout": "S'ha exhaurit el temps d'espera quan el servidor ha intentat contactar amb ell mateix via HTTP utilitzant la seva adreça IP pública (domini «{domain:s}» amb IP «{ip:s}»). Pot ser degut a hairpinning o a que el talla focs/router al que està connectat el servidor estan mal configurats.", "certmanager_no_cert_file": "No s'ha pogut llegir l'arxiu del certificat pel domini {domain:s} (fitxer: {file:s})", "certmanager_self_ca_conf_file_not_found": "No s'ha trobat el fitxer de configuració per l'autoritat del certificat auto-signat (fitxer: {file:s})", "certmanager_unable_to_parse_self_CA_name": "No s'ha pogut analitzar el nom de l'autoritat del certificat auto-signat (fitxer: {file:s})", @@ -156,12 +145,7 @@ "dyndns_registration_failed": "No s'ha pogut registrar el domini DynDNS: {error:s}", "dyndns_domain_not_provided": "El proveïdor de DynDNS {provider:s} no pot oferir el domini {domain:s}.", "dyndns_unavailable": "El domini {domain:s} no està disponible.", - "executing_command": "Execució de l'ordre « {command:s} »...", - "executing_script": "Execució de l'script « {script:s} »...", "extracting": "Extracció en curs...", - "dyndns_cron_installed": "S'ha creat la tasca cron pel DynDNS", - "dyndns_cron_remove_failed": "No s'ha pogut eliminar la tasca cron per a DynDNS: {error}", - "dyndns_cron_removed": "S'ha eliminat la tasca cron pel DynDNS", "experimental_feature": "Atenció: Aquesta funcionalitat és experimental i no es considera estable, no s'ha d'utilitzar a excepció de saber el que esteu fent.", "field_invalid": "Camp incorrecte « {:s} »", "file_does_not_exist": "El camí {path:s} no existeix.", @@ -175,10 +159,6 @@ "global_settings_cant_write_settings": "No s'ha pogut escriure el fitxer de configuració, raó: {reason:s}", "global_settings_key_doesnt_exists": "La clau « {settings_key:s} » no existeix en la configuració global, podeu veure totes les claus disponibles executant « yunohost settings list »", "global_settings_reset_success": "S'ha fet una còpia de seguretat de la configuració anterior a {path:s}", - "global_settings_setting_example_bool": "Exemple d'opció booleana", - "global_settings_setting_example_enum": "Exemple d'opció de tipus enumeració", - "global_settings_setting_example_int": "Exemple d'opció de tipus enter", - "global_settings_setting_example_string": "Exemple d'opció de tipus cadena", "global_settings_setting_security_nginx_compatibility": "Solució de compromís entre compatibilitat i seguretat pel servidor web NGINX. Afecta els criptògrafs (i altres aspectes relacionats amb la seguretat)", "global_settings_setting_security_password_admin_strength": "Robustesa de la contrasenya d'administrador", "global_settings_setting_security_password_user_strength": "Robustesa de la contrasenya de l'usuari", @@ -197,7 +177,6 @@ "ip6tables_unavailable": "No podeu modificar les ip6tables aquí. O bé sou en un contenidor o bé el vostre nucli no és compatible amb aquesta opció", "iptables_unavailable": "No podeu modificar les iptables aquí. O bé sou en un contenidor o bé el vostre nucli no és compatible amb aquesta opció", "log_corrupted_md_file": "El fitxer de metadades YAML associat amb els registres està malmès: « {md_file} »\nError: {error}", - "log_category_404": "La categoria de registres « {category} » no existeix", "log_link_to_log": "El registre complet d'aquesta operació: «{desc}»", "log_help_to_get_log": "Per veure el registre de l'operació « {desc} », utilitzeu l'ordre « yunohost log show {name}{name} »", "log_link_to_failed_log": "No s'ha pogut completar l'operació « {desc} ». Per obtenir ajuda, proveïu el registre complete de l'operació clicant aquí", @@ -243,49 +222,6 @@ "mail_unavailable": "Aquesta adreça de correu està reservada i ha de ser atribuïda automàticament el primer usuari", "main_domain_change_failed": "No s'ha pogut canviar el domini principal", "main_domain_changed": "S'ha canviat el domini principal", - "migrate_tsig_end": "La migració cap a HMAC-SHA-512 s'ha acabat", - "migrate_tsig_failed": "Ha fallat la migració del domini DynDNS «{domain}» cap a HMAC-SHA-512, anul·lant les modificacions. Error: {error_code}, {error}", - "migrate_tsig_start": "L'algoritme de generació de claus no es prou segur per a la signatura TSIG del domini «{domain}», començant la migració cap a un de més segur HMAC-SHA-512", - "migrate_tsig_wait": "Esperant tres minuts per a que el servidor DynDNS tingui en compte la nova clau…", - "migrate_tsig_wait_2": "2 minuts…", - "migrate_tsig_wait_3": "1 minut…", - "migrate_tsig_wait_4": "30 segons…", - "migrate_tsig_not_needed": "Sembla que no s'utilitza cap domini DynDNS, no és necessari fer cap migració.", - "migration_description_0001_change_cert_group_to_sslcert": "Canvia els permisos del grup dels certificats de «metronome» a «ssl-cert»", - "migration_description_0002_migrate_to_tsig_sha256": "Millora la seguretat de DynDNS TSIG utilitzant SHA-512 en lloc de MD5", - "migration_description_0003_migrate_to_stretch": "Actualització del sistema a Debian Stretch i YunoHost 3.0", - "migration_description_0004_php5_to_php7_pools": "Tornar a configurar els pools PHP per utilitzar PHP 7 en lloc de PHP 5", - "migration_description_0005_postgresql_9p4_to_9p6": "Migració de les bases de dades de PostgreSQL 9.4 a 9.6", - "migration_description_0006_sync_admin_and_root_passwords": "Sincronitzar les contrasenyes admin i root", - "migration_description_0007_ssh_conf_managed_by_yunohost_step1": "La configuració SSH serà gestionada per YunoHost (pas 1, automàtic)", - "migration_description_0008_ssh_conf_managed_by_yunohost_step2": "La configuració SSH serà gestionada per YunoHost (pas 2, manual)", - "migration_description_0009_decouple_regenconf_from_services": "Desvincula el mecanisme regen-conf dels serveis", - "migration_description_0010_migrate_to_apps_json": "Elimina els catàlegs d'aplicacions obsolets i utilitza la nova llista unificada «apps.json» en el seu lloc (obsolet, substituït per la migració 13)", - "migration_0003_start": "Ha començat la migració a Stretch. Els registres estaran disponibles a {logfile}.", - "migration_0003_patching_sources_list": "Modificant el fitxer sources.lists…", - "migration_0003_main_upgrade": "Començant l'actualització principal…", - "migration_0003_fail2ban_upgrade": "Començant l'actualització de Fail2Ban…", - "migration_0003_restoring_origin_nginx_conf": "El fitxer /etc/nginx/nginx.conf ha estat editat. La migració el tornarà al seu estat original... El fitxer anterior estarà disponible com a {backup_dest}.", - "migration_0003_yunohost_upgrade": "Començant l'actualització del paquet YunoHost... La migració acabarà, però l'actualització actual es farà just després. Després de completar aquesta operació, pot ser que us hagueu de tornar a connectar a la web d'administració.", - "migration_0003_not_jessie": "La distribució Debian actual no és Jessie!", - "migration_0003_system_not_fully_up_to_date": "El vostre sistema no està completament actualitzat. S'ha de fer una actualització normal abans de fer la migració a Stretch.", - "migration_0003_still_on_jessie_after_main_upgrade": "Hi ha hagut un problema durant l'actualització principal: El sistema encara està amb Jessie? Per investigar el problema, mireu el registres a {log}:s…", - "migration_0003_general_warning": "Tingueu en compte que la migració és una operació delicada. L'equip de YunoHost a fet els possibles per revisar-la i provar-la, però la migració pot provocar errors en parts del sistema o aplicacions.\n\nPer tant, es recomana:\n - Fer una còpia de seguretat de les dades o aplicacions importants. Més informació a https://yunohost.org/backup;\n - Sigueu pacient un cop llençada la migració: en funció de la connexió a internet i el maquinari, pot trigar fins a unes hores per actualitzar-ho tot.\n\nD'altra banda, el port per SMTP, utilitzat per clients de correu externs (com Thunderbird o K9-Mail) ha canviat de 465 (SSL/TLS) a 587 (STARTTLS). L'antic port (465) serà tancat automàticament, i el nou port (587) serà obert en el tallafocs. Tots els usuaris *hauran* d'adaptar la configuració dels clients de correu en acord amb aquests canvis.", - "migration_0003_problematic_apps_warning": "Tingueu en compte que s'han detectat les aplicacions, possiblement, problemàtiques següents. Sembla que aquestes no s'han instal·lat des d'un catàleg d'aplicacions, o que no estan marcades com a «working». Per conseqüent, no podem garantir que segueixin funcionant després de l'actualització: {problematic_apps}", - "migration_0003_modified_files": "Tingueu en compte que s'han detectat els següents fitxers que han estat modificats manualment i podrien sobreescriure's al final de l'actualització: {manually_modified_files}", - "migration_0005_postgresql_94_not_installed": "PostgreSQL no està instal·lat en el sistema. No hi ha res per fer.", - "migration_0005_postgresql_96_not_installed": "PostgreSQL 9.4 està instal·lat, però no PostgreSQL 9.6? Alguna cosa estranya a passat en el sistema :( …", - "migration_0005_not_enough_space": "Creu espai disponible en {path} per executar la migració.", - "migration_0006_disclaimer": "YunoHost esperar que les contrasenyes de admin i root estiguin sincronitzades. Aquesta migració canvia la contrasenya root per la contrasenya admin.", - "migration_0007_cancelled": "No s'ha pogut millorar la gestió de la configuració SSH.", - "migration_0007_cannot_restart": "No es pot reiniciar SSH després d'haver intentat cancel·lar la migració numero 6.", - "migration_0008_general_disclaimer": "Per millorar la seguretat del servidor, es recomana que sigui YunoHost qui gestioni la configuració SSH. La configuració SSH actual és diferent a la configuració recomanada. Si deixeu que YunoHost ho reconfiguri, la manera de connectar-se al servidor mitjançant SSH canviarà de la següent manera:", - "migration_0008_port": "• La connexió es farà utilitzant el port 22 en lloc del port SSH personalitzat actual. Es pot reconfigurar;", - "migration_0008_root": "• No es podrà connectar com a root a través de SSH. S'haurà d'utilitzar l'usuari admin per fer-ho;", - "migration_0008_dsa": "• Es desactivarà la clau DSA. Per tant, es podria haver d'invalidar un missatge esgarrifós del client SSH, i tornar a verificar l'empremta digital del servidor;", - "migration_0008_warning": "Si heu entès els avisos i voleu que YunoHost sobreescrigui la configuració actual, comenceu la migració. Sinó, podeu saltar-vos la migració, tot i que no està recomanat.", - "migration_0008_no_warning": "Hauria de ser segur sobreescriure la configuració SSH, però no es pot estar del tot segur! Executeu la migració per sobreescriure-la. Sinó, podeu saltar-vos la migració, tot i que no està recomanat.", - "migration_0009_not_needed": "Sembla que ja s'ha fet aquesta migració… (?) Ometent.", "migrations_cant_reach_migration_file": "No s'ha pogut accedir als fitxers de migració al camí «%s»", "migrations_list_conflict_pending_done": "No es pot utilitzar «--previous» i «--done» al mateix temps.", "migrations_loading_migration": "Carregant la migració {id}...", @@ -294,9 +230,7 @@ "migrations_skip_migration": "Saltant migració {id}...", "migrations_to_be_ran_manually": "La migració {id} s'ha de fer manualment. Aneu a Eines → Migracions a la interfície admin, o executeu «yunohost tools migrations run».", "migrations_need_to_accept_disclaimer": "Per fer la migració {id}, heu d'acceptar aquesta clàusula de no responsabilitat:\n---\n{disclaimer}\n---\nSi accepteu fer la migració, torneu a executar l'ordre amb l'opció «--accept-disclaimer».", - "no_internet_connection": "El servidor no està connectat a Internet", "not_enough_disk_space": "No hi ha prou espai en «{path:s}»", - "package_unknown": "Paquet desconegut «{pkgname}»", "packages_upgrade_failed": "No s'han pogut actualitzar tots els paquets", "pattern_backup_archive_name": "Ha de ser un nom d'arxiu vàlid amb un màxim de 30 caràcters, compost per caràcters alfanumèrics i -_. exclusivament", "pattern_domain": "Ha de ser un nom de domini vàlid (ex.: el-meu-domini.cat)", @@ -359,8 +293,6 @@ "service_description_metronome": "Gestiona els comptes de missatgeria instantània XMPP", "service_description_mysql": "Guarda les dades de les aplicacions (base de dades SQL)", "service_description_nginx": "Serveix o permet l'accés a totes les pàgines web allotjades en el servidor", - "service_description_nslcd": "Gestiona les connexions shell dels usuaris YunoHost", - "service_description_php7.0-fpm": "Executa les aplicacions escrites en PHP amb NGINX", "service_description_postfix": "Utilitzat per enviar i rebre correus", "service_description_redis-server": "Una base de dades especialitzada per l'accés ràpid a dades, files d'espera i comunicació entre programes", "service_description_rspamd": "Filtra el correu brossa, i altres funcionalitats relacionades amb el correu", @@ -421,10 +353,7 @@ "user_unknown": "Usuari desconegut: {user:s}", "user_update_failed": "No s'ha pogut actualitzar l'usuari {user}: {error}", "user_updated": "S'ha canviat la informació de l'usuari", - "users_available": "Usuaris disponibles:", "yunohost_already_installed": "YunoHost ja està instal·lat", - "yunohost_ca_creation_failed": "No s'ha pogut crear l'autoritat de certificació", - "yunohost_ca_creation_success": "S'ha creat l'autoritat de certificació local.", "yunohost_configured": "YunoHost està configurat", "yunohost_installing": "Instal·lació de YunoHost...", "yunohost_not_installed": "YunoHost no està instal·lat correctament. Executeu «yunohost tools postinstall»", @@ -439,17 +368,6 @@ "log_user_group_delete": "Eliminar grup «{}»", "log_user_group_update": "Actualitzar grup «{}»", "mailbox_disabled": "La bústia de correu està desactivada per al usuari {user:s}", - "migration_description_0011_setup_group_permission": "Configurar els grups d'usuaris i els permisos per les aplicacions i els serveis", - "migration_0011_backup_before_migration": "Creant una còpia de seguretat de la base de dades LDAP i la configuració de les aplicacions abans d'efectuar la migració.", - "migration_0011_can_not_backup_before_migration": "No s'ha pogut completar la còpia de seguretat abans de que la migració fallés. Error: {error:s}", - "migration_0011_create_group": "Creant un grup per a cada usuari...", - "migration_0011_done": "Migració completada. Ja podeu gestionar grups d'usuaris.", - "migration_0011_LDAP_update_failed": "No s'ha pogut actualitzar LDAP. Error: {error:s}", - "migration_0011_migrate_permission": "Fent la migració dels permisos de la configuració de les aplicacions a LDAP...", - "migration_0011_migration_failed_trying_to_rollback": "No s'ha pogut fer la migració… s'intenta tornar el sistema a l'estat anterior.", - "migration_0011_rollback_success": "S'ha tornat el sistema a l'estat anterior.", - "migration_0011_update_LDAP_database": "Actualitzant la base de dades LDAP...", - "migration_0011_update_LDAP_schema": "Actualitzant l'esquema LDAP...", "permission_already_exist": "El permís «{permission:s}» ja existeix", "permission_created": "S'ha creat el permís «{permission:s}»", "permission_creation_failed": "No s'ha pogut crear el permís «{permission}»: {error}", @@ -458,8 +376,6 @@ "permission_not_found": "No s'ha trobat el permís «{permission:s}»", "permission_update_failed": "No s'ha pogut actualitzar el permís «{permission}»: {error}", "permission_updated": "S'ha actualitzat el permís «{permission:s}»", - "permission_update_nothing_to_do": "No hi ha cap permís per actualitzar", - "migration_description_0012_postgresql_password_to_md5_authentication": "Força l'autenticació PostgreSQL a fer servir MD5 per a les connexions locals", "app_full_domain_unavailable": "Aquesta aplicació ha de ser instal·lada en el seu propi domini, però ja hi ha altres aplicacions instal·lades en el domini «{domain}». Podeu utilitzar un subdomini dedicat a aquesta aplicació.", "migrations_not_pending_cant_skip": "Aquestes migracions no estan pendents, així que no poden ser omeses: {ids}", "app_action_broke_system": "Aquesta acció sembla haver trencat els següents serveis importants: {services}", @@ -486,7 +402,6 @@ "group_user_not_in_group": "L'usuari {user} no està en el grup {group}", "log_permission_create": "Crear el permís «{}»", "log_permission_delete": "Eliminar el permís «{}»", - "migration_0011_failed_to_remove_stale_object": "No s'ha pogut eliminar l'objecte obsolet {dn}: {error}", "permission_already_allowed": "El grup «{group}» ja té el permís «{permission}» activat", "permission_cannot_remove_main": "No es permet eliminar un permís principal", "user_already_exists": "L'usuari «{user}» ja existeix", @@ -496,7 +411,6 @@ "group_cannot_edit_visitors": "El grup «visitors» no es pot editar manualment. És un grup especial que representa els visitants anònims", "group_cannot_edit_primary_group": "El grup «{group}» no es pot editar manualment. És el grup principal destinat a contenir un usuari específic.", "log_permission_url": "Actualització de la URL associada al permís «{}»", - "migration_0011_slapd_config_will_be_overwritten": "Sembla que heu modificat manualment la configuració de sldap. Per aquesta migració crítica, YunoHost ha de forçar l'actualització de la configuració sldap. Es farà una còpia de seguretat a {conf_backup_folder}.", "permission_already_up_to_date": "No s'ha actualitzat el permís perquè la petició d'afegir/eliminar ja corresponent a l'estat actual.", "permission_currently_allowed_for_all_users": "El permís ha el té el grup de tots els usuaris (all_users) a més d'altres grups. Segurament s'hauria de revocar el permís a «all_users» o eliminar els altres grups als que s'ha atribuït.", "permission_require_account": "El permís {permission} només té sentit per als usuaris que tenen un compte, i per tant no es pot activar per als visitants.", @@ -513,9 +427,7 @@ "diagnosis_basesystem_kernel": "El servidor funciona amb el nucli de Linux {kernel_version}", "diagnosis_basesystem_ynh_single_version": "{package} versió: {version}({repo})", "diagnosis_basesystem_ynh_inconsistent_versions": "Esteu utilitzant versions inconsistents dels paquets de YunoHost… probablement a causa d'una actualització fallida o parcial.", - "diagnosis_display_tip_web": "Podeu anar a la secció de Diagnòstics (en la pantalla principal) per veure els errors que s'han trobat.", "diagnosis_failed_for_category": "Ha fallat el diagnòstic per la categoria «{category}»: {error}", - "diagnosis_display_tip_cli": "Podeu executar «yunohost diagnosis show --issues --human-readable» per mostrar els errors que s'han trobat.", "diagnosis_cache_still_valid": "(La memòria cau encara és vàlida pel diagnòstic de {category}. No es tornar a diagnosticar de moment!)", "diagnosis_cant_run_because_of_dep": "No es pot fer el diagnòstic per {category} mentre hi ha problemes importants relacionats amb {dep}.", "diagnosis_ignored_issues": "(+ {nb_ignored} problema(es) ignorat(s))", @@ -548,9 +460,6 @@ "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! 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.", "diagnosis_security_vulnerable_to_meltdown": "Sembla que el sistema és vulnerable a la vulnerabilitat de seguretat crítica Meltdown", "diagnosis_description_basesystem": "Sistema de base", "diagnosis_description_ip": "Connectivitat a Internet", @@ -559,7 +468,6 @@ "diagnosis_description_systemresources": "Recursos del sistema", "diagnosis_description_ports": "Exposició dels ports", "diagnosis_description_regenconf": "Configuració del sistema", - "diagnosis_description_security": "Verificacions de seguretat", "diagnosis_ports_could_not_diagnose": "No s'ha pogut diagnosticar si els ports són accessibles des de l'exterior.", "diagnosis_ports_could_not_diagnose_details": "Error: {error}", "diagnosis_ports_unreachable": "El port {port} no és accessible des de l'exterior.", @@ -572,10 +480,8 @@ "apps_catalog_failed_to_download": "No s'ha pogut descarregar el catàleg d'aplicacions {apps_catalog}: {error}", "apps_catalog_obsolete_cache": "La memòria cau del catàleg d'aplicacions és buida o obsoleta.", "apps_catalog_update_success": "S'ha actualitzat el catàleg d'aplicacions!", - "diagnosis_mail_ougoing_port_25_ok": "El port de sortida 25 no està bloquejat i els correus es poden enviar a altres servidors.", "diagnosis_mail_outgoing_port_25_blocked": "Sembla que el port de sortida 25 està bloquejat. Hauríeu d'intentar desbloquejar-lo al panell de configuració del proveïdor d'accés a internet (o allotjador). Mentrestant, el servidor no podrà enviar correus a altres servidors.", "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 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", @@ -584,7 +490,6 @@ "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", "diagnosis_services_running": "El servei {service} s'està executant!", "diagnosis_services_conf_broken": "La configuració pel servei {service} està trencada!", "diagnosis_ports_needed_by": "És necessari exposar aquest port per a les funcions {category} (servei {service})", @@ -594,7 +499,6 @@ "log_app_config_apply": "Afegeix la configuració a l'aplicació «{}»", "diagnosis_never_ran_yet": "Sembla que el servidor s'ha configurat recentment i encara no hi cap informe de diagnòstic per mostrar. S'ha d'executar un diagnòstic complet primer, ja sigui des de la pàgina web d'administració o utilitzant la comanda «yunohost diagnosis run» al terminal.", "diagnosis_description_web": "Web", - "diagnosis_basesystem_hardware_board": "El model de la targeta del servidor és {model}", "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.", @@ -718,4 +622,4 @@ "postinstall_low_rootfsspace": "El sistema de fitxers arrel té un total de menys de 10 GB d'espai, el que es preocupant! És molt probable que us quedeu sense espai ràpidament! Es recomana tenir un mínim de 16 GB per al sistema de fitxers arrel. Si voleu instal·lar YunoHost tot i aquest avís, torneu a executar la postinstal·lació amb --force-diskspace", "diagnosis_rootfstotalspace_critical": "El sistema de fitxers arrel només té {space} en total i és preocupant! És molt probable que us quedeu sense espai ràpidament! Es recomanar tenir un mínim de 16 GB per al sistema de fitxers arrel.", "diagnosis_rootfstotalspace_warning": "El sistema de fitxers arrel només té {space} en total. Això no hauria de causar cap problema, però haureu de parar atenció ja que us podrieu quedar sense espai ràpidament… Es recomanar tenir un mínim de 16 GB per al sistema de fitxers arrel." -} +} \ No newline at end of file diff --git a/locales/cs.json b/locales/cs.json index 3df59e0fd..76b464260 100644 --- a/locales/cs.json +++ b/locales/cs.json @@ -10,4 +10,4 @@ "additional_urls_already_added": "Dotatečný odkaz '{url:s}' byl již přidán v dodatečných odkazech pro oprávnění '{permission:s}'", "action_invalid": "Nesprávné akce '{action:s}'", "aborting": "Přerušení." -} +} \ No newline at end of file diff --git a/locales/de.json b/locales/de.json index 4db27e68a..14e6e708c 100644 --- a/locales/de.json +++ b/locales/de.json @@ -17,7 +17,6 @@ "app_unknown": "Unbekannte App", "app_upgrade_failed": "{app:s} konnte nicht aktualisiert werden: {error}", "app_upgraded": "{app:s} aktualisiert", - "ask_email": "E-Mail-Adresse", "ask_firstname": "Vorname", "ask_lastname": "Nachname", "ask_main_domain": "Hauptdomain", @@ -33,7 +32,6 @@ "backup_delete_error": "Pfad '{path:s}' konnte nicht gelöscht werden", "backup_deleted": "Backup wurde entfernt", "backup_hook_unknown": "Der Datensicherungshook '{hook:s}' unbekannt", - "backup_invalid_archive": "Dies ist kein Backup-Archiv", "backup_nothings_done": "Keine Änderungen zur Speicherung", "backup_output_directory_forbidden": "Wähle ein anderes Ausgabeverzeichnis. Datensicherungen können nicht in /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var oder in Unterordnern von /home/yunohost.backup/archives erstellt werden", "backup_output_directory_not_empty": "Der gewählte Ausgabeordner sollte leer sein", @@ -52,17 +50,12 @@ "domain_unknown": "Unbekannte Domain", "done": "Erledigt", "downloading": "Wird heruntergeladen…", - "dyndns_cron_installed": "DynDNS Cronjob erfolgreich erstellt", - "dyndns_cron_remove_failed": "Der DynDNS Cronjob konnte aufgrund dieses Fehlers nicht entfernt werden: {error}", - "dyndns_cron_removed": "DynDNS-Cronjob gelöscht", "dyndns_ip_update_failed": "Konnte die IP-Adresse für DynDNS nicht aktualisieren", "dyndns_ip_updated": "Aktualisierung Ihrer IP-Adresse bei DynDNS", "dyndns_key_generating": "Generierung des DNS-Schlüssels..., das könnte eine Weile dauern.", "dyndns_registered": "DynDNS Domain registriert", "dyndns_registration_failed": "DynDNS Domain konnte nicht registriert werden: {error:s}", "dyndns_unavailable": "Die Domäne {domain:s} ist nicht verfügbar.", - "executing_command": "Führe den Behfehl '{command:s}' aus…", - "executing_script": "Skript '{script:s}' wird ausgeührt…", "extracting": "Wird entpackt...", "field_invalid": "Feld '{:s}' ist unbekannt", "firewall_reload_failed": "Firewall konnte nicht neu geladen werden", @@ -82,7 +75,6 @@ "mail_forward_remove_failed": "Die Weiterleitungs-E-Mail '{mail:s}' konnte nicht gelöscht werden", "main_domain_change_failed": "Die Hauptdomain konnte nicht geändert werden", "main_domain_changed": "Die Hauptdomain wurde geändert", - "no_internet_connection": "Der Server ist nicht mit dem Internet verbunden", "packages_upgrade_failed": "Es konnten nicht alle Pakete aktualisiert werden", "pattern_backup_archive_name": "Ein gültiger Dateiname kann nur aus maximal 30 alphanumerischen sowie -_. Zeichen bestehen", "pattern_domain": "Muss ein gültiger Domainname sein (z.B. meine-domain.org)", @@ -96,7 +88,6 @@ "port_already_closed": "Der Port {port:d} wurde bereits für {ip_version:s} Verbindungen geschlossen", "port_already_opened": "Der Port {port:d} wird bereits von {ip_version:s} benutzt", "restore_already_installed_app": "Es ist bereits eine App mit der ID '{app:s}' installiet", - "restore_app_failed": "'{app:s}' konnte nicht wiederhergestellt werden: {error:s}", "restore_cleaning_failed": "Das temporäre Dateiverzeichnis für Systemrestaurierung konnte nicht gelöscht werden", "restore_complete": "Vollständig wiederhergestellt", "restore_confirm_yunohost_installed": "Möchtest du die Wiederherstellung wirklich starten? [{answers:s}]", @@ -145,7 +136,6 @@ "user_update_failed": "Benutzer kann nicht aktualisiert werden", "user_updated": "Der Benutzer wurde aktualisiert", "yunohost_already_installed": "YunoHost ist bereits installiert", - "yunohost_ca_creation_failed": "Zertifikatsstelle konnte nicht erstellt werden", "yunohost_configured": "YunoHost wurde konfiguriert", "yunohost_installing": "YunoHost wird installiert…", "yunohost_not_installed": "YunoHost ist nicht oder unvollständig installiert worden. Bitte 'yunohost tools postinstall' ausführen", @@ -163,15 +153,12 @@ "dyndns_no_domain_registered": "Keine Domain mit DynDNS registriert", "ldap_init_failed_to_create_admin": "Die LDAP-Initialisierung konnte keinen admin-Benutzer erstellen", "mailbox_used_space_dovecot_down": "Der Dovecot Mailbox Dienst muss gestartet sein, wenn du den von der Mailbox belegten Speicher angezeigen lassen willst", - "package_unknown": "Unbekanntes Paket '{pkgname}'", "certmanager_attempt_to_replace_valid_cert": "Du versuchst gerade eine richtiges und gültiges Zertifikat der Domain {domain:s} zu überschreiben! (Benutze --force , um diese Nachricht zu umgehen)", - "certmanager_domain_unknown": "Unbekannte Domain '{domain:s}'", "certmanager_domain_cert_not_selfsigned": "Das Zertifikat der Domain {domain:s} ist kein selbstsigniertes Zertifikat. Sind Sie sich sicher, dass Sie es ersetzen wollen? (Benutzen Sie dafür '--force')", "certmanager_certificate_fetching_or_enabling_failed": "Die Aktivierung des neuen Zertifikats für die {domain:s} ist fehlgeschlagen...", "certmanager_attempt_to_renew_nonLE_cert": "Das Zertifikat der Domain '{domain:s}' wurde nicht von Let's Encrypt ausgestellt. Es kann nicht automatisch erneuert werden!", "certmanager_attempt_to_renew_valid_cert": "Das Zertifikat der Domain {domain:s} läuft nicht in Kürze ab! (Benutze --force um diese Nachricht zu umgehen)", "certmanager_domain_http_not_working": "Es scheint so, dass die Domain {domain:s} nicht über HTTP erreicht werden kann. Bitte überprüfe, ob deine DNS und nginx Konfiguration in Ordnung ist. (Wenn du weißt was du tust, nutze \"--no-checks\" um die überprüfung zu überspringen.)", - "certmanager_error_no_A_record": "Kein DNS 'A' Eintrag für die Domain {domain:s} gefunden. Dein Domainname muss auf diese Maschine weitergeleitet werden, um ein Let's Encrypt Zertifikat installieren zu können! (Wenn du weißt was du tust, kannst du --no-checks benutzen, um diese Überprüfung zu überspringen. )", "certmanager_domain_dns_ip_differs_from_public_ip": "Der DNS-A-Eintrag der Domain {domain:s} unterscheidet sich von dieser Server-IP. Für weitere Informationen überprüfen Sie bitte die 'DNS records' (basic) Kategorie in der Diagnose. Wenn Sie gerade Ihren A-Eintrag verändert haben, warten Sie bitte etwas, damit die Änderungen wirksam werden (Sie können die DNS-Propagation mittels Website überprüfen) (Wenn Sie wissen was Sie tun, können Sie --no-checks benutzen, um diese Überprüfung zu überspringen.)", "certmanager_cannot_read_cert": "Es ist ein Fehler aufgetreten, als es versucht wurde das aktuelle Zertifikat für die Domain {domain:s} zu öffnen (Datei: {file:s}), Grund: {reason:s}", "certmanager_cert_install_success_selfsigned": "Ein selbstsigniertes Zertifikat für die Domain {domain:s} wurde erfolgreich installiert", @@ -180,15 +167,11 @@ "certmanager_hit_rate_limit": "Es wurden innerhalb kurzer Zeit zu viele Zertifikate für dieselbe Domain {domain:s} ausgestellt. Bitte versuchen Sie es später nochmal. Besuchen Sie https://letsencrypt.org/docs/rate-limits/ für mehr Informationen", "certmanager_cert_signing_failed": "Das neue Zertifikat konnte nicht signiert werden", "certmanager_no_cert_file": "Die Zertifikatsdatei für die Domain {domain:s} (Datei: {file:s}) konnte nicht gelesen werden", - "certmanager_conflicting_nginx_file": "Die Domain konnte nicht für die ACME challenge vorbereitet werden: Die nginx Konfigurationsdatei {filepath:s} verursacht Probleme und sollte vorher entfernt werden", "domain_cannot_remove_main": "Die primäre Domain konnten nicht entfernt werden. Lege zuerst einen neue primäre Domain Sie können die Domäne '{domain:s}' nicht entfernen, weil Sie die Hauptdomäne ist. Sie müssen zuerst eine andere Domäne als Hauptdomäne festlegen. Sie können das mit dem Befehl 'yunohost domain main-domain -n tun. Hier ist eine Liste der möglichen Domänen: {other_domains:s}", "certmanager_self_ca_conf_file_not_found": "Die Konfigurationsdatei der Zertifizierungsstelle für selbstsignierte Zertifikate wurde nicht gefunden (Datei {file:s})", "certmanager_acme_not_configured_for_domain": "Die ACME Challenge kann im Moment nicht für {domain} ausgeführt werden, weil in ihrer nginx conf das entsprechende Code-Snippet fehlt... Bitte stellen Sie sicher, dass Ihre nginx-Konfiguration mit 'yunohost tools regen-conf nginx --dry-run --with-diff' auf dem neuesten Stand ist.", "certmanager_unable_to_parse_self_CA_name": "Der Name der Zertifizierungsstelle für selbstsignierte Zertifikate konnte nicht aufgelöst werden (Datei: {file:s})", - "certmanager_http_check_timeout": "Eine Zeitüberschreitung ist aufgetreten, als der Server versuchte sich selbst über HTTP mit der öffentlichen IP (Domain '{domain:s}' mit der IP '{ip:s}') zu erreichen. Möglicherweise ist dafür hairpinning oder eine falsch konfigurierte Firewall/Router deines Servers dafür verantwortlich.", - "certmanager_couldnt_fetch_intermediate_cert": "Eine Zeitüberschreitung ist aufgetreten als der Server versuchte die Teilzertifikate von Let's Encrypt zusammenzusetzen. Die Installation/Erneuerung des Zertifikats wurde abgebrochen — bitte versuche es später erneut.", "domain_hostname_failed": "Sie können keinen neuen Hostnamen verwenden. Das kann zukünftige Probleme verursachen (es kann auch sein, dass es funktioniert).", - "yunohost_ca_creation_success": "Die lokale Zertifizierungs-Authorität wurde angelegt.", "app_already_installed_cant_change_url": "Diese Application ist bereits installiert. Die URL kann durch diese Funktion nicht modifiziert werden. Überprüfe ob `app changeurl` verfügbar ist.", "app_change_url_failed_nginx_reload": "NGINX konnte nicht neu gestartet werden. Hier ist der Output von 'nginx -t':\n{nginx_errors:s}", "app_change_url_identical_domains": "Die alte und neue domain/url_path sind identisch: ('{domain:s} {path:s}'). Es gibt nichts zu tun.", @@ -202,7 +185,6 @@ "backup_archive_system_part_not_available": "Der System-Teil '{part:s}' ist in diesem Backup nicht enthalten", "backup_archive_writing_error": "Die Dateien '{source:s} (im Ordner '{dest:s}') konnten nicht in das komprimierte Archiv-Backup '{archive:s}' hinzugefügt werden", "app_change_url_success": "{app:s} URL ist nun {domain:s}{path:s}", - "backup_applying_method_borg": "Sende alle Dateien zur Sicherung ins borg-backup repository...", "global_settings_bad_type_for_setting": "Falscher Typ der Einstellung {setting:s}. Empfangen: {received_type:s}, aber erwarteter Typ: {expected_type:s}", "global_settings_bad_choice_for_enum": "Wert des Einstellungsparameters {setting:s} ungültig. Der Wert den Sie eingegeben haben: '{choice:s}', die gültigen Werte für diese Einstellung: {available_choices:s}", "file_does_not_exist": "Die Datei {path: s} existiert nicht.", @@ -225,12 +207,10 @@ "backup_method_tar_finished": "Tar-Backup-Archiv erstellt", "backup_method_custom_finished": "Benutzerdefinierte Sicherungsmethode '{method:s}' beendet", "backup_method_copy_finished": "Sicherungskopie beendet", - "backup_method_borg_finished": "Backup in Borg beendet", "backup_custom_mount_error": "Bei der benutzerdefinierten Sicherungsmethode ist beim Arbeitsschritt \"Einhängen/Verbinden\" ein Fehler aufgetreten", "backup_custom_backup_error": "Bei der benutzerdefinierten Sicherungsmethode ist beim Arbeitsschritt \"Sicherung\" ein Fehler aufgetreten", "backup_csv_creation_failed": "Die zur Wiederherstellung erforderliche CSV-Datei kann nicht erstellt werden", "backup_couldnt_bind": "{src:s} konnte nicht an {dest:s} angebunden werden.", - "backup_borg_not_implemented": "Die Borg-Sicherungsmethode ist noch nicht implementiert", "backup_ask_for_copying_if_needed": "Möchten Sie die Sicherung mit {size:s}MB temporär durchführen? (Dieser Weg wird verwendet, da einige Dateien nicht mit einer effizienteren Methode vorbereitet werden konnten.)", "backup_actually_backuping": "Erstellt ein Backup-Archiv aus den gesammelten Dateien...", "ask_new_path": "Neuer Pfad", @@ -263,19 +243,14 @@ "log_does_exists": "Es gibt kein Operationsprotokoll mit dem Namen'{log}', verwende 'yunohost log list', um alle verfügbaren Operationsprotokolle anzuzeigen", "log_operation_unit_unclosed_properly": "Die Operationseinheit wurde nicht richtig geschlossen", "global_settings_setting_security_postfix_compatibility": "Kompatibilität vs. Sicherheitskompromiss für den Postfix-Server. Beeinflusst die Chiffren (und andere sicherheitsrelevante Aspekte)", - "log_category_404": "Die Log-Kategorie '{category}' existiert nicht", "global_settings_unknown_type": "Unerwartete Situation, die Einstellung {setting:s} scheint den Typ {unknown_type:s} zu haben, ist aber kein vom System unterstützter Typ.", "dpkg_is_broken": "Du kannst das gerade nicht tun, weil dpkg/APT (der Systempaketmanager) in einem defekten Zustand zu sein scheint.... Du kannst versuchen, dieses Problem zu lösen, indem du dich über SSH verbindest und `sudo apt install --fix-broken` sowie/oder `sudo dpkg --configure -a` ausführst.", "global_settings_unknown_setting_from_settings_file": "Unbekannter Schlüssel in den Einstellungen: '{setting_key:s}', verwerfen und speichern in /etc/yunohost/settings-unknown.json", "log_link_to_log": "Vollständiges Log dieser Operation: '{desc}'", - "global_settings_setting_example_bool": "Beispiel einer booleschen Option", "log_help_to_get_log": "Um das Protokoll der Operation '{desc}' anzuzeigen, verwende den Befehl 'yunohost log show {name}{name}'", "global_settings_setting_security_nginx_compatibility": "Kompatibilität vs. Sicherheitskompromiss für den Webserver NGINX. Beeinflusst die Chiffren (und andere sicherheitsrelevante Aspekte)", - "backup_php5_to_php7_migration_may_fail": "Dein Archiv konnte nicht für PHP 7 konvertiert werden, Du kannst deine PHP-Anwendungen möglicherweise nicht wiederherstellen (Grund: {error:s})", "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Erlaubt die Verwendung eines (veralteten) DSA-Hostkeys für die SSH-Daemon-Konfiguration", - "global_settings_setting_example_string": "Beispiel einer string Option", "log_app_remove": "Entferne die Applikation '{}'", - "global_settings_setting_example_int": "Beispiel einer int Option", "global_settings_cant_open_settings": "Einstellungsdatei konnte nicht geöffnet werden, Grund: {reason:s}", "global_settings_cant_write_settings": "Einstellungsdatei konnte nicht gespeichert werden, Grund: {reason:s}", "log_app_install": "Installiere die Applikation '{}'", @@ -289,7 +264,6 @@ "log_app_change_url": "Ändere die URL der Applikation '{}'", "global_settings_setting_security_password_user_strength": "Stärke des Benutzerpassworts", "good_practices_about_user_password": "Sie sind dabei, ein neues Benutzerpasswort zu definieren. Das Passwort sollte mindestens 8 Zeichen lang sein, obwohl es ratsam ist, ein längeres Passwort (z.B. eine Passphrase) und/oder eine Variation von Zeichen (Groß- und Kleinschreibung, Ziffern und Sonderzeichen) zu verwenden.", - "global_settings_setting_example_enum": "Beispiel einer enum Option", "log_link_to_failed_log": "Der Vorgang konnte nicht abgeschlossen werden '{desc}'. Bitte gib das vollständige Protokoll dieser Operation mit Klicken Sie hier an, um Hilfe zu erhalten", "backup_cant_mount_uncompress_archive": "Das unkomprimierte Archiv konnte nicht als schreibgeschützt gemountet werden", "backup_csv_addition_failed": "Es konnten keine Dateien zur Sicherung in die CSV-Datei hinzugefügt werden", @@ -307,14 +281,12 @@ "diagnosis_basesystem_ynh_single_version": "{package} Version: {version} ({repo})", "diagnosis_basesystem_ynh_main_version": "Server läuft YunoHost {main_version} ({repo})", "diagnosis_basesystem_ynh_inconsistent_versions": "Sie verwenden inkonsistente Versionen der YunoHost-Pakete... wahrscheinlich wegen eines fehlgeschlagenen oder teilweisen Upgrades.", - "diagnosis_display_tip_web": "Sie können den Abschnitt Diagnose (im Startbildschirm) aufrufen, um die gefundenen Probleme anzuzeigen.", "apps_catalog_init_success": "App-Katalogsystem initialisiert!", "apps_catalog_updating": "Aktualisierung des Applikationskatalogs…", "apps_catalog_failed_to_download": "Der {apps_catalog} App-Katalog kann nicht heruntergeladen werden: {error}", "apps_catalog_obsolete_cache": "Der Cache des App-Katalogs ist leer oder veraltet.", "apps_catalog_update_success": "Der Apps-Katalog wurde aktualisiert!", "password_too_simple_1": "Das Passwort muss mindestens 8 Zeichen lang sein", - "diagnosis_display_tip_cli": "Sie können 'yunohost diagnosis show --issues --human-readable' ausführen, um die gefundenen Probleme anzuzeigen.", "diagnosis_everything_ok": "Alles schaut gut aus für {category}!", "diagnosis_failed": "Kann Diagnose-Ergebnis für die Kategorie '{category}' nicht abrufen: {error}", "diagnosis_ip_connected_ipv4": "Der Server ist mit dem Internet über IPv4 verbunden!", @@ -333,7 +305,6 @@ "diagnosis_dns_good_conf": "Die DNS-Einträge für die Domäne {domain} (Kategorie {category}) sind korrekt konfiguriert", "diagnosis_ignored_issues": "(+ {nb_ignored} ignorierte(s) Problem(e))", "diagnosis_basesystem_hardware": "Server Hardware Architektur ist {virt} {arch}", - "diagnosis_basesystem_hardware_board": "Server Platinen Modell ist {model}", "diagnosis_found_errors": "Habe {errors} erhebliche(s) Problem(e) in Verbindung mit {category} gefunden!", "diagnosis_found_warnings": "Habe {warnings} Ding(e) gefunden, die verbessert werden könnten für {category}.", "diagnosis_ip_dnsresolution_working": "Domänen-Namens-Auflösung funktioniert!", @@ -345,12 +316,6 @@ "certmanager_domain_not_diagnosed_yet": "Für {domain} gibt es noch keine Diagnose-Resultate. Bitte wiederholen Sie die Diagnose für die Kategorien 'DNS records' und 'Web' im Diagnose-Bereich um zu überprüfen ob die Domain für Let's Encrypt bereit ist. (Oder wenn Sie wissen was Sie tun, verwenden Sie '--no-checks' um diese Überprüfungen abzuschalten.", "migration_0015_patching_sources_list": "sources.lists wird repariert...", "migration_0015_start": "Start der Migration auf Buster", - "migration_0011_failed_to_remove_stale_object": "Abgelaufenes Objekt konne nicht entfernt werden. {dn}: {error}", - "migration_0011_update_LDAP_schema": "Das LDAP-Schema aktualisieren...", - "migration_0011_update_LDAP_database": "Die LDAP-Datenbank aktualisieren...", - "migration_0011_migrate_permission": "Berechtigungen der Applikationen von den Einstellungen zu LDAP migrieren...", - "migration_0011_LDAP_update_failed": "LDAP konnte nicht aktualisiert werden. Fehler:n{error:s}", - "migration_0011_create_group": "Eine Gruppe für jeden Benutzer erstellen…", "migration_description_0015_migrate_to_buster": "Auf Debian Buster und YunoHost 4.x upgraden", "mail_unavailable": "Diese E-Mail Adresse ist reserviert und wird dem ersten Benutzer automatisch zugewiesen", "diagnosis_services_conf_broken": "Die Konfiguration für den Dienst {service} ist fehlerhaft!", @@ -618,4 +583,4 @@ "restore_already_installed_apps": "Folgende Apps können nicht wiederhergestellt werden, weil sie schon installiert sind: {apps}", "regex_with_only_domain": "Du kannst regex nicht als Domain verwenden, sondern nur als Pfad", "root_password_desynchronized": "Das Admin-Passwort wurde verändert, aber das root Passwort ist immer noch das alte." -} +} \ No newline at end of file diff --git a/locales/el.json b/locales/el.json index b43f11d5d..db0189666 100644 --- a/locales/el.json +++ b/locales/el.json @@ -1,3 +1,3 @@ { "password_too_simple_1": "Ο κωδικός πρόσβασης πρέπει να έχει τουλάχιστον 8 χαρακτήρες" -} +} \ No newline at end of file diff --git a/locales/eo.json b/locales/eo.json index fdcb7845c..ba1f96675 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -10,16 +10,11 @@ "app_id_invalid": "Nevalida apo ID", "app_install_files_invalid": "Ĉi tiuj dosieroj ne povas esti instalitaj", "user_updated": "Uzantinformoj ŝanĝis", - "users_available": "Uzantoj disponeblaj :", "yunohost_already_installed": "YunoHost estas jam instalita", - "yunohost_ca_creation_failed": "Ne povis krei atestan aŭtoritaton", - "yunohost_ca_creation_success": "Loka atestila aŭtoritato kreiĝis.", "yunohost_installing": "Instalante YunoHost…", "service_description_metronome": "Mastrumas XMPP tujmesaĝilon kontojn", "service_description_mysql": "Butikigas datumojn de programoj (SQL datumbazo)", "service_description_nginx": "Servas aŭ permesas atingi ĉiujn retejojn gastigita sur via servilo", - "service_description_nslcd": "Mastrumas Yunohost uzantojn konektojn per komanda linio", - "service_description_php7.0-fpm": "Ekzekutas programojn skribitajn en PHP kun NGINX", "service_description_postfix": "Uzita por sendi kaj ricevi retpoŝtojn", "service_description_redis-server": "Specialita datumbazo uzita por rapida datumo atingo, atendovicoj kaj komunikadoj inter programoj", "service_description_rspamd": "Filtras trudmesaĝojn, kaj aliaj funkcioj rilate al retpoŝto", @@ -42,11 +37,9 @@ "backup_archive_system_part_not_available": "Sistemo parto '{part:s}' ne haveblas en ĉi tiu rezervo", "backup_abstract_method": "Ĉi tiu rezerva metodo ankoraŭ efektiviĝis", "apps_already_up_to_date": "Ĉiuj aplikoj estas jam ĝisdatigitaj", - "backup_borg_not_implemented": "La kopia metodo de Borg ankoraŭ ne estas efektivigita", "app_location_unavailable": "Ĉi tiu URL aŭ ne haveblas, aŭ konfliktas kun la jam instalita (j) apliko (j):\n{apps:s}", "backup_archive_app_not_found": "Ne povis trovi la programon '{app:s}' en la rezerva ar archiveivo", "backup_actually_backuping": "Krei rezervan ar archiveivon el la kolektitaj dosieroj …", - "backup_method_borg_finished": "Sekurkopio en Borg finiĝis", "app_change_url_no_script": "La app '{app_name:s}' ankoraŭ ne subtenas URL-modifon. Eble vi devus altgradigi ĝin.", "app_start_install": "Instali la programon '{app}' …", "backup_created": "Sekurkopio kreita", @@ -61,7 +54,6 @@ "app_upgrade_app_name": "Nun ĝisdatiganta {app} …", "app_manifest_invalid": "Io misas pri la aplika manifesto: {error}", "backup_cleaning_failed": "Ne povis purigi la provizoran rezervan dosierujon", - "backup_invalid_archive": "Ĉi tio ne estas rezerva ar archiveivo", "backup_creation_failed": "Ne povis krei la rezervan ar archiveivon", "backup_hook_unknown": "La rezerva hoko '{hook:s}' estas nekonata", "backup_custom_backup_error": "Propra rezerva metodo ne povis preterpasi la paŝon \"sekurkopio\"", @@ -92,7 +84,6 @@ "app_start_remove": "Forigo de la apliko '{app}' …", "backup_output_directory_not_empty": "Vi devas elekti malplenan eligitan dosierujon", "backup_archive_writing_error": "Ne povis aldoni la dosierojn '{source:s}' (nomitaj en la ar theivo '{dest:s}') por esti rezervitaj en la kunpremita arkivo '{archive:s}'", - "ask_email": "Retpoŝta adreso", "app_start_restore": "Restarigi la programon '{app}' …", "backup_applying_method_copy": "Kopiante ĉiujn dosierojn al sekurkopio …", "backup_couldnt_bind": "Ne povis ligi {src:s} al {dest:s}.", @@ -103,7 +94,6 @@ "backup_mount_archive_for_restore": "Preparante arkivon por restarigo …", "backup_csv_creation_failed": "Ne povis krei la CSV-dosieron bezonatan por restarigo", "backup_archive_name_unknown": "Nekonata loka rezerva ar archiveivo nomata '{name:s}'", - "backup_applying_method_borg": "Sendado de ĉiuj dosieroj al sekurkopio en borg-rezerva deponejo …", "app_sources_fetch_failed": "Ne povis akiri fontajn dosierojn, ĉu la URL estas ĝusta?", "ask_new_domain": "Nova domajno", "app_unknown": "Nekonata apliko", @@ -113,41 +103,27 @@ "backup_deleted": "Rezerva forigita", "backup_csv_addition_failed": "Ne povis aldoni dosierojn al sekurkopio en la CSV-dosiero", "dpkg_lock_not_available": "Ĉi tiu komando ne povas funkcii nun ĉar alia programo uzas la seruron de dpkg (la administrilo de paka sistemo)", - "migration_0003_yunohost_upgrade": "Komencante la ĝisdatigon de la pakaĵo YunoHost ... La migrado finiĝos, sed la efektiva ĝisdatigo okazos tuj poste. Post kiam la operacio finiĝos, vi eble devos ensaluti al la retpaĝo.", "domain_dyndns_root_unknown": "Nekonata radika domajno DynDNS", "field_invalid": "Nevalida kampo '{:s}'", "log_app_makedefault": "Faru '{}' la defaŭlta apliko", - "migration_0003_still_on_jessie_after_main_upgrade": "Io okazis malbone dum la ĉefa ĝisdatigo: Ĉu la sistemo ankoraŭ estas en Jessie‽ Por esplori la aferon, bonvolu rigardi {log}:s …", - "migration_0011_can_not_backup_before_migration": "La sekurkopio de la sistemo ne povis finiĝi antaŭ ol la migrado malsukcesis. Eraro: {error:s}", - "migration_0011_create_group": "Krei grupon por ĉiu uzanto…", "backup_system_part_failed": "Ne eblis sekurkopi la sistemon de '{part:s}'", "global_settings_setting_security_postfix_compatibility": "Kongruo vs sekureca kompromiso por la Postfix-servilo. Afektas la ĉifradojn (kaj aliajn aspektojn pri sekureco)", "group_unknown": "La grupo '{group:s}' estas nekonata", "mailbox_disabled": "Retpoŝto malŝaltita por uzanto {user:s}", - "migration_description_0011_setup_group_permission": "Agordu uzantajn grupojn kaj permesojn por programoj kaj servoj", - "migration_0011_backup_before_migration": "Krei sekurkopion de LDAP-datumbazo kaj agordojn antaŭ la efektiva migrado.", - "migration_0011_migrate_permission": "Migrado de permesoj de agordoj al aplikoj al LDAP…", - "migration_0011_migration_failed_trying_to_rollback": "Ne povis migri ... provante redakti la sistemon.", "migrations_dependencies_not_satisfied": "Rulu ĉi tiujn migradojn: '{dependencies_id}', antaŭ migrado {id}.", "migrations_failed_to_load_migration": "Ne povis ŝarĝi migradon {id}: {error}", "migrations_exclusive_options": "'--auto', '--skip' kaj '--force-rerun' estas reciproke ekskluzivaj ebloj.", "migrations_must_provide_explicit_targets": "Vi devas provizi eksplicitajn celojn kiam vi uzas '--skip' aŭ '--force-rerun'", "permission_update_failed": "Ne povis ĝisdatigi permeson '{permission}': {error}", "permission_updated": "Ĝisdatigita \"{permission:s}\" rajtigita", - "permission_update_nothing_to_do": "Neniuj permesoj ĝisdatigi", "tools_upgrade_cant_hold_critical_packages": "Ne povis teni kritikajn pakojn…", "upnp_dev_not_found": "Neniu UPnP-aparato trovita", - "migration_description_0012_postgresql_password_to_md5_authentication": "Devigu PostgreSQL-aŭtentigon uzi MD5 por lokaj ligoj", - "migration_0011_done": "Migrado finiĝis. Vi nun kapablas administri uzantajn grupojn.", - "migration_0011_LDAP_update_failed": "Ne povis ĝisdatigi LDAP. Eraro: {error:s}", "pattern_password": "Devas esti almenaŭ 3 signoj longaj", "root_password_desynchronized": "La pasvorta administranto estis ŝanĝita, sed YunoHost ne povis propagandi ĉi tion al la radika pasvorto!", "service_remove_failed": "Ne povis forigi la servon '{service:s}'", - "migration_0003_fail2ban_upgrade": "Komenci la ĝisdatigon Fail2Ban…", "backup_permission": "Rezerva permeso por app {app:s}", "log_user_group_delete": "Forigi grupon '{}'", "log_user_group_update": "Ĝisdatigi grupon '{}'", - "migration_0005_postgresql_94_not_installed": "PostgreSQL ne estis instalita en via sistemo. Nenio por fari.", "dyndns_provider_unreachable": "Ne povas atingi la provizanton DynDNS {provider}: ĉu via YunoHost ne estas ĝuste konektita al la interreto aŭ la dyneta servilo malŝaltiĝas.", "good_practices_about_user_password": "Vi nun estas por difini novan uzantan pasvorton. La pasvorto devas esti almenaŭ 8 signojn - kvankam estas bone praktiki uzi pli longan pasvorton (t.e. pasfrazon) kaj/aŭ variaĵon de signoj (majuskloj, minuskloj, ciferoj kaj specialaj signoj).", "group_updated": "Ĝisdatigita \"{group}\" grupo", @@ -158,17 +134,12 @@ "group_user_already_in_group": "Uzanto {user} jam estas en grupo {group}", "group_user_not_in_group": "Uzanto {user} ne estas en grupo {group}", "installation_complete": "Kompleta instalado", - "log_category_404": "La loga kategorio '{category}' ne ekzistas", "log_permission_create": "Krei permeson '{}'", "log_permission_delete": "Forigi permeson '{}'", "log_user_group_create": "Krei grupon '{}'", "log_user_permission_update": "Mise à jour des accès pour la permission '{}'", "log_user_permission_reset": "Restarigi permeson '{}'", "mail_forward_remove_failed": "Ne povis forigi retpoŝton plusendante '{mail:s}'", - "migration_0011_rollback_success": "Sistemo ruliĝis reen.", - "migration_0011_update_LDAP_database": "Ĝisdatigante LDAP-datumbazon…", - "migration_0011_update_LDAP_schema": "Ĝisdatigante LDAP-skemon…", - "migration_0011_failed_to_remove_stale_object": "Ne povis forigi neuzatan objekton {dn}: {error}", "migrations_already_ran": "Tiuj migradoj estas jam faritaj: {ids}", "migrations_no_such_migration": "Estas neniu migrado nomata '{id}'", "permission_already_allowed": "Grupo '{group}' jam havas rajtigitan permeson '{permission}'", @@ -195,7 +166,6 @@ "migrations_not_pending_cant_skip": "Tiuj migradoj ankoraŭ ne estas pritraktataj, do ne eblas preterlasi: {ids}", "permission_already_exist": "Permesita '{permission}' jam ekzistas", "domain_created": "Domajno kreita", - "migrate_tsig_wait_2": "2 minutoj …", "log_user_create": "Aldonu uzanton '{}'", "ip6tables_unavailable": "Vi ne povas ludi kun ip6tabloj ĉi tie. Vi estas en ujo aŭ via kerno ne subtenas ĝin", "mail_unavailable": "Ĉi tiu retpoŝta adreso estas rezervita kaj aŭtomate estos atribuita al la unua uzanto", @@ -207,8 +177,6 @@ "certmanager_cert_install_success_selfsigned": "Mem-subskribita atestilo nun instalita por la domajno '{domain:s}'", "global_settings_unknown_setting_from_settings_file": "Nekonata ŝlosilo en agordoj: '{setting_key:s}', forĵetu ĝin kaj konservu ĝin en /etc/yunohost/settings-unknown.json", "regenconf_file_backed_up": "Agordodosiero '{conf}' estis rezervita al '{backup}'", - "migration_0007_cannot_restart": "SSH ne rekomencas post provi nuligi la migradan numeron 6.", - "migration_description_0006_sync_admin_and_root_passwords": "Sinkronigu admin kaj radikajn pasvortojn", "iptables_unavailable": "Vi ne povas ludi kun iptables ĉi tie. Vi estas en ujo aŭ via kerno ne subtenas ĝin", "global_settings_cant_write_settings": "Ne eblis konservi agordojn, tial: {reason:s}", "service_added": "La servo '{service:s}' estis aldonita", @@ -216,14 +184,11 @@ "service_started": "Servo '{service:s}' komenciĝis", "port_already_opened": "Haveno {port:d} estas jam malfermita por {ip_version:s} rilatoj", "installation_failed": "Io okazis malbone kun la instalado", - "migrate_tsig_wait_3": "1 minuto …", - "certmanager_conflicting_nginx_file": "Ne povis prepari domajnon por ACME-defio: la agordo de NGINX {filepath:s} konfliktas kaj unue devas esti forigita", "upgrading_packages": "Ĝisdatigi pakojn…", "custom_app_url_required": "Vi devas provizi URL por altgradigi vian kutimon app {app:s}", "service_reload_failed": "Ne povis reŝargi la servon '{service:s}'\n\nLastatempaj servaj protokoloj: {logs:s}", "packages_upgrade_failed": "Ne povis ĝisdatigi ĉiujn pakojn", "hook_json_return_error": "Ne povis legi revenon de hoko {path:s}. Eraro: {msg:s}. Kruda enhavo: {raw_content}", - "dyndns_cron_removed": "DynDNS cron-laboro forigita", "dyndns_key_not_found": "DNS-ŝlosilo ne trovita por la domajno", "tools_upgrade_regular_packages_failed": "Ne povis ĝisdatigi pakojn: {packages_list}", "service_start_failed": "Ne povis komenci la servon '{service:s}'\n\nLastatempaj servaj protokoloj: {logs:s}", @@ -231,51 +196,37 @@ "system_upgraded": "Sistemo ĝisdatigita", "domain_deleted": "Domajno forigita", "certmanager_acme_not_configured_for_domain": "Atestilo por la domajno '{domain:s}' ne ŝajnas esti ĝuste instalita. Bonvolu ekzekuti 'cert-instali' por ĉi tiu regado unue.", - "migration_description_0009_decouple_regenconf_from_services": "Malkonstruu la regen-konf-mekanismon de servoj", "user_update_failed": "Ne povis ĝisdatigi uzanton {user}: {error}", - "migration_description_0008_ssh_conf_managed_by_yunohost_step2": "Lasu la SSH-agordon estu administrata de YunoHost (paŝo 2, manlibro)", "restore_confirm_yunohost_installed": "Ĉu vi vere volas restarigi jam instalitan sistemon? [{answers:s}]", "pattern_positive_number": "Devas esti pozitiva nombro", - "certmanager_error_no_A_record": "Neniu DNS 'A' rekordo trovita por '{domain:s}'. Vi bezonas atentigi vian domajnan nomon al via maŝino por povi instali atestilon Lasu-Ĉifri. (Se vi scias, kion vi faras, uzu '--no-checks' por malŝalti tiujn ĉekojn.)", "update_apt_cache_failed": "Ne eblis ĝisdatigi la kaŝmemoron de APT (paka administranto de Debian). Jen rubujo de la sources.list-linioj, kiuj povus helpi identigi problemajn liniojn:\n{sourceslist}", "migrations_no_migrations_to_run": "Neniuj migradoj por funkcii", - "executing_command": "Plenumanta komandon '{command:s}' …", "certmanager_attempt_to_renew_nonLE_cert": "La atestilo por la domajno '{domain:s}' ne estas elsendita de Let's Encrypt. Ne eblas renovigi ĝin aŭtomate!", - "global_settings_setting_example_bool": "Ekzemplo bulea elekto", "domain_dyndns_already_subscribed": "Vi jam abonis DynDNS-domajnon", "log_letsencrypt_cert_renew": "Renovigu '{}' Let's Encrypt atestilon", - "migrate_tsig_start": "Detektita ŝlosila algoritmo nesufiĉa por TSIG-subskribo de la domajno '{domain}', komencanta migradon al la pli sekura HMAC-SHA-512", "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} '", "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}'", "unexpected_error": "Io neatendita iris malbone: {error}", "password_listed": "Ĉi tiu pasvorto estas inter la plej uzataj pasvortoj en la mondo. Bonvolu elekti ion pli unikan.", - "migration_description_0007_ssh_conf_managed_by_yunohost_step1": "Lasu la SSH-agordon estu administrata de YunoHost (paŝo 1, aŭtomata)", - "migration_0009_not_needed": "Ĉi tiu migrado jam iel okazis ... (?) Saltado.", "ssowat_conf_generated": "SSOwat-agordo generita", - "migrate_tsig_wait": "Atendante tri minutojn por ke la servilo DynDNS enkalkulu la novan ŝlosilon …", "log_remove_on_failed_restore": "Forigu '{}' post malsukcesa restarigo de rezerva ar archiveivo", "dpkg_is_broken": "Vi ne povas fari ĉi tion nun ĉar dpkg/APT (la administrantoj pri pakaĵaj sistemoj) ŝajnas esti rompita stato ... Vi povas provi solvi ĉi tiun problemon per konekto per SSH kaj funkcianta `sudo dpkg --configure -a`.", "certmanager_cert_signing_failed": "Ne povis subskribi la novan atestilon", - "migration_description_0003_migrate_to_stretch": "Altgradigu la sistemon al Debian Stretch kaj YunoHost 3.0", "log_tools_upgrade": "Ĝisdatigu sistemajn pakaĵojn", "log_available_on_yunopaste": "Ĉi tiu protokolo nun haveblas per {url}", - "certmanager_http_check_timeout": "Ekdifinita kiam servilo provis kontakti sin per HTTP per publika IP-adreso (domajno '{domain:s}' kun IP '{ip:s}'). Vi eble spertas haŭtoproblemon, aŭ la fajroŝirmilo / enkursigilo antaŭ via servilo miskonfiguras.", "pattern_port_or_range": "Devas esti valida haveno-nombro (t.e. 0-65535) aŭ gamo da havenoj (t.e. 100:200)", "migrations_loading_migration": "Ŝarĝante migradon {id}…", "pattern_mailbox_quota": "Devas esti grandeco kun la sufikso b/k/M/G/T aŭ 0 por ne havi kvoton", - "migration_0008_general_disclaimer": "Por plibonigi la sekurecon de via servilo, rekomendas lasi YunoHost administri la SSH-agordon. Via nuna SSH-aranĝo diferencas de la rekomendo. Se vi lasas YunoHost agordi ĝin, la maniero per kiu vi konektas al via servilo per SSH ŝanĝiĝos tiel:", "user_deletion_failed": "Ne povis forigi uzanton {user}: {error}", "backup_with_no_backup_script_for_app": "La app '{app:s}' ne havas sekretan skripton. Ignorante.", "service_regen_conf_is_deprecated": "'yunohost service regen-conf' malakceptas! Bonvolu uzi anstataŭe 'yunohost tools regen-conf'.", "global_settings_key_doesnt_exists": "La ŝlosilo '{settings_key:s}' ne ekzistas en la tutmondaj agordoj, vi povas vidi ĉiujn disponeblajn klavojn per uzado de 'yunohost settings list'", "dyndns_no_domain_registered": "Neniu domajno registrita ĉe DynDNS", "dyndns_could_not_check_available": "Ne povis kontroli ĉu {domain:s} haveblas sur {provider:s}.", - "global_settings_setting_example_enum": "Ekzemplo enum elekto", "hook_exec_not_terminated": "Skripto ne finiĝis ĝuste: {path:s}", "service_stopped": "Servo '{service:s}' ĉesis", "restore_failed": "Ne povis restarigi sistemon", @@ -290,26 +241,19 @@ "updating_apt_cache": "Akirante haveblajn ĝisdatigojn por sistemaj pakoj…", "tools_upgrade_at_least_one": "Bonvolu specifi '--apps' aŭ '--system'", "service_already_stopped": "La servo '{service:s}' jam ĉesis", - "migration_0003_modified_files": "Bonvolu noti, ke la jenaj dosieroj estis trovitaj mane kaj modifitaj kaj povus esti anstataŭigitaj sekve de la ĝisdatigo: {manually_modified_files}", "tools_upgrade_cant_both": "Ne eblas ĝisdatigi ambaŭ sistemon kaj programojn samtempe", "restore_extracting": "Eltirante bezonatajn dosierojn el la ar theivo…", "upnp_port_open_failed": "Ne povis malfermi havenon per UPnP", "log_app_upgrade": "Ĝisdatigu la aplikon '{}'", "log_help_to_get_failed_log": "La operacio '{desc}' ne povis finiĝi. Bonvolu dividi la plenan ŝtipon de ĉi tiu operacio per la komando 'yunohost log share {name}' por akiri helpon", - "migration_description_0002_migrate_to_tsig_sha256": "Plibonigu sekurecon de DynDNS TSIG-ĝisdatigoj per SHA-512 anstataŭ MD5", "port_already_closed": "Haveno {port:d} estas jam fermita por {ip_version:s} rilatoj", "hook_name_unknown": "Nekonata hoko-nomo '{name:s}'", - "migration_0003_system_not_fully_up_to_date": "Via sistemo ne estas plene ĝisdata. Bonvolu plenumi regulan ĝisdatigon antaŭ ol ruli la migradon al Stretch.", "dyndns_could_not_check_provide": "Ne povis kontroli ĉu {provider:s} povas provizi {domain:s}.", - "dyndns_cron_remove_failed": "Ne povis forigi la cron-laboron DynDNS ĉar: {error}", "restore_nothings_done": "Nenio estis restarigita", "log_tools_postinstall": "Afiŝu vian servilon YunoHost", "dyndns_unavailable": "La domajno '{domain:s}' ne haveblas.", "experimental_feature": "Averto: Ĉi tiu funkcio estas eksperimenta kaj ne konsiderata stabila, vi ne uzu ĝin krom se vi scias kion vi faras.", "root_password_replaced_by_admin_password": "Via radika pasvorto estis anstataŭigita per via administra pasvorto.", - "migration_description_0005_postgresql_9p4_to_9p6": "Migru datumbazojn de PostgreSQL 9.4 al 9.6", - "migration_0008_root": "• Vi ne povos konekti kiel radiko per SSH. Anstataŭe vi uzu la administran uzanton;", - "package_unknown": "Nekonata pako '{pkgname}'", "domain_unknown": "Nekonata domajno", "global_settings_setting_security_password_user_strength": "Uzanto pasvorta forto", "restore_may_be_not_enough_disk_space": "Via sistemo ne ŝajnas havi sufiĉe da spaco (libera: {free_space:d} B, necesa spaco: {needed_space:d} B, sekureca marĝeno: {margin:d} B)", @@ -323,17 +267,13 @@ "service_description_fail2ban": "Protektas kontraŭ bruta forto kaj aliaj specoj de atakoj de la interreto", "file_does_not_exist": "La dosiero {path:s} ne ekzistas.", "yunohost_not_installed": "YunoHost ne estas ĝuste instalita. Bonvolu prilabori 'yunohost tools postinstall'", - "migration_0005_postgresql_96_not_installed": "PostgreSQL 9.4 estas instalita, sed ne postgresql 9.6‽ Io stranga eble okazis en via sistemo :(…", "restore_removing_tmp_dir_failed": "Ne povis forigi malnovan provizoran dosierujon", "certmanager_cannot_read_cert": "Io malbona okazis, kiam mi provis malfermi aktualan atestilon por domajno {domain:s} (dosiero: {file:s}), kialo: {reason:s}", "service_removed": "Servo '{service:s}' forigita", "certmanager_hit_rate_limit": "Tro multaj atestiloj jam eldonitaj por ĉi tiu ĝusta aro de domajnoj {domain:s} antaŭ nelonge. Bonvolu reprovi poste. Vidu https://letsencrypt.org/docs/rate-limits/ por pliaj detaloj", - "migration_0005_not_enough_space": "Disponigu sufiĉan spacon en {path} por ruli la migradon.", "pattern_firstname": "Devas esti valida antaŭnomo", - "migration_description_0010_migrate_to_apps_json": "Forigu malvalorigitajn katalogajn programojn kaj uzu anstataŭe la novan unuigitan liston de \"apps.json\" (malaktuale anstataŭita per migrado 13)", "domain_cert_gen_failed": "Ne povis generi atestilon", "regenconf_file_kept_back": "La agorda dosiero '{conf}' estas atendita forigi per regen-conf (kategorio {category}), sed ĝi estis konservita.", - "migrate_tsig_wait_4": "30 sekundoj …", "backup_with_no_restore_script_for_app": "La apliko \"{app:s}\" ne havas restarigan skripton, vi ne povos aŭtomate restarigi la sekurkopion de ĉi tiu apliko.", "log_letsencrypt_cert_install": "Instalu atestilon Let's Encrypt sur '{}' regado", "log_dyndns_update": "Ĝisdatigu la IP asociita kun via subdominio YunoHost '{}'", @@ -342,24 +282,19 @@ "log_user_delete": "Forigi uzanton '{}'", "dyndns_ip_updated": "Ĝisdatigis vian IP sur DynDNS", "regenconf_up_to_date": "La agordo jam estas ĝisdatigita por kategorio '{category}'", - "migration_0003_patching_sources_list": "Patching the sources.lists …", "global_settings_setting_security_ssh_compatibility": "Kongruo vs sekureca kompromiso por la SSH-servilo. Afektas la ĉifradojn (kaj aliajn aspektojn pri sekureco)", "migrations_need_to_accept_disclaimer": "Por funkciigi la migradon {id}, via devas akcepti la sekvan malakcepton:\n---\n{disclaimer}\n---\nSe vi akceptas funkcii la migradon, bonvolu rekonduki la komandon kun la opcio '--accept-disclaimer'.", "regenconf_file_remove_failed": "Ne povis forigi la agordodosieron '{conf}'", "not_enough_disk_space": "Ne sufiĉe libera spaco sur '{path:s}'", - "migration_0006_disclaimer": "YunoHost nun atendas, ke la pasvortoj de admin kaj radiko estos sinkronigitaj. Ĉi tiu migrado anstataŭigas vian radikan pasvorton kun la administran pasvorton.", "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", "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", "dyndns_registration_failed": "Ne povis registri DynDNS-domajnon: {error:s}", - "migration_0003_not_jessie": "La nuna Debian-distribuo ne estas Jessie!", "user_unknown": "Nekonata uzanto: {user:s}", "migrations_to_be_ran_manually": "Migrado {id} devas funkcii permane. Bonvolu iri al Iloj → Migradoj en la retpaĝa paĝo, aŭ kuri `yunohost tools migrations run`.", - "migration_0008_warning": "Se vi komprenas tiujn avertojn kaj volas ke YunoHost preterlasu vian nunan agordon, faru la migradon. Alie, vi ankaŭ povas salti la migradon, kvankam ĝi ne rekomendas.", "certmanager_cert_renew_success": "Ni Ĉifru atestilon renovigitan por la domajno '{domain:s}'", "global_settings_reset_success": "Antaŭaj agordoj nun estas rezervitaj al {path:s}", "pattern_domain": "Devas esti valida domajna nomo (t.e. mia-domino.org)", @@ -369,8 +304,6 @@ "regenconf_file_removed": "Agordodosiero '{conf}' forigita", "log_tools_shutdown": "Enŝaltu vian servilon", "password_too_simple_3": "La pasvorto bezonas almenaŭ 8 signojn kaj enhavas ciferon, majusklon, pli malaltan kaj specialajn signojn", - "migration_0003_general_warning": "Bonvolu noti, ke ĉi tiu migrado estas delikata operacio. La teamo de YunoHost faris sian plej bonan revizii kaj testi ĝin, sed la migrado eble ankoraŭ rompos partojn de la sistemo aŭ ĝiaj programoj.\n\nTial oni rekomendas al:\n - Elfari kopion de iuj kritikaj datumoj aŭ app. Pliaj informoj pri https://yunohost.org/backup;\n - Paciencu post lanĉo de la migrado: Depende de via interreta konekto kaj aparataro, eble daŭros kelkaj horoj ĝis ĉio ĝisdatigi.\n\nAldone, la haveno por SMTP, uzata de eksteraj retpoŝtaj klientoj (kiel Thunderbird aŭ K9-Mail) estis ŝanĝita de 465 (SSL / TLS) al 587 (STARTTLS). La malnova haveno (465) aŭtomate fermiĝos, kaj la nova haveno (587) malfermiĝos en la fajrejo. Vi kaj viaj uzantoj * devos adapti la agordon de viaj retpoŝtaj klientoj laŭe.", - "global_settings_setting_example_int": "Ekzemple int elekto", "backup_output_symlink_dir_broken": "Via arkiva dosierujo '{path:s}' estas rompita ligilo. Eble vi forgesis restarigi aŭ munti aŭ enŝovi la stokadon, al kiu ĝi notas.", "good_practices_about_admin_password": "Vi nun estas por difini novan administran pasvorton. La pasvorto devas esti almenaŭ 8 signojn - kvankam estas bone praktiki uzi pli longan pasvorton (t.e. pasfrazon) kaj/aŭ uzi variaĵon de signoj (majuskloj, minuskloj, ciferoj kaj specialaj signoj).", "certmanager_attempt_to_renew_valid_cert": "La atestilo por la domajno '{domain:s}' ne finiĝos! (Vi eble uzos --force se vi scias kion vi faras)", @@ -378,7 +311,6 @@ "regenconf_pending_applying": "Aplikante pritraktata agordo por kategorio '{category}'…", "service_description_dovecot": "Permesas al retpoŝtaj klientoj aliri / serĉi retpoŝton (per IMAP kaj POP3)", "domain_dns_conf_is_just_a_recommendation": "Ĉi tiu komando montras al vi la *rekomenditan* agordon. Ĝi efektive ne agordas la DNS-agordon por vi. Via respondeco agordi vian DNS-zonon en via registristo laŭ ĉi tiu rekomendo.", - "backup_php5_to_php7_migration_may_fail": "Ne povis konverti vian ar archiveivon por subteni PHP 7, vi eble ne povas restarigi viajn PHP-programojn (kialo: {error:s})", "log_backup_restore_system": "Restarigi sistemon de rezerva arkivo", "log_app_change_url": "Ŝanĝu la URL de la apliko '{}'", "service_already_started": "La servo '{service:s}' jam funkcias", @@ -395,16 +327,10 @@ "restore_hook_unavailable": "Restariga skripto por '{part:s}' ne haveblas en via sistemo kaj ankaŭ ne en la ar theivo", "log_dyndns_subscribe": "Aboni al YunoHost-subdominio '{}'", "password_too_simple_4": "La pasvorto bezonas almenaŭ 12 signojn kaj enhavas ciferon, majuskle, pli malaltan kaj specialajn signojn", - "migration_0003_main_upgrade": "Komencanta ĉefa ĝisdatigo …", "regenconf_file_updated": "Agordodosiero '{conf}' ĝisdatigita", "log_help_to_get_log": "Por vidi la protokolon de la operacio '{desc}', uzu la komandon 'yunohost log show {name}{name}'", "global_settings_setting_security_nginx_compatibility": "Kongruo vs sekureca kompromiso por la TTT-servilo NGINX. Afektas la ĉifradojn (kaj aliajn aspektojn pri sekureco)", - "no_internet_connection": "La servilo ne estas konektita al la interreto", - "migration_0008_dsa": "• La DSA-ŝlosilo estos malŝaltita. Tial vi eble bezonos nuligi spuran averton de via SSH-kliento kaj revizii la fingrospuron de via servilo;", - "migration_0003_restoring_origin_nginx_conf": "Fileia dosiero /etc/nginx/nginx.conf estis iel redaktita. La migrado unue restarigos ĝin al sia originala stato ... La antaŭa dosiero estos havebla kiel {backup_dest}.", - "migrate_tsig_end": "Migrado al HMAC-SHA-512 finiĝis", "restore_complete": "Restarigita", - "certmanager_couldnt_fetch_intermediate_cert": "Ekvilibrigita kiam vi provis ricevi interajn atestilojn de Let's Encrypt. Atestita instalado / renovigo nuligita - bonvolu reprovi poste.", "hook_exec_failed": "Ne povis funkcii skripto: {path:s}", "global_settings_cant_open_settings": "Ne eblis malfermi agordojn, tial: {reason:s}", "user_created": "Uzanto kreita", @@ -414,7 +340,6 @@ "update_apt_cache_warning": "Io iris malbone dum la ĝisdatigo de la kaŝmemoro de APT (paka administranto de Debian). Jen rubujo de la sources.list-linioj, kiuj povus helpi identigi problemajn liniojn:\n{sourceslist}", "regenconf_dry_pending_applying": "Kontrolado de pritraktata agordo, kiu estus aplikita por kategorio '{category}'…", "regenconf_file_copy_failed": "Ne povis kopii la novan agordodosieron '{new}' al '{conf}'", - "global_settings_setting_example_string": "Ekzemple korda elekto", "restore_already_installed_app": "App kun la ID '{app:s}' estas jam instalita", "mail_domain_unknown": "Nevalida retadreso por domajno '{domain:s}'. Bonvolu uzi domajnon administritan de ĉi tiu servilo.", "migrations_cant_reach_migration_file": "Ne povis aliri migrajn dosierojn ĉe la vojo '% s'", @@ -422,9 +347,7 @@ "mail_alias_remove_failed": "Ne povis forigi retpoŝton alias '{mail:s}'", "regenconf_file_manually_removed": "La dosiero de agordo '{conf}' estis forigita permane, kaj ne estos kreita", "domain_exists": "La domajno jam ekzistas", - "migration_description_0001_change_cert_group_to_sslcert": "Ŝanĝu grupajn permesojn de 'metronomo' al 'ssl-cert'", "ldap_initialized": "LDAP inicializis", - "migrate_tsig_not_needed": "Vi ne ŝajnas uzi DynDNS-domajnon, do neniu migrado necesas.", "certmanager_domain_cert_not_selfsigned": "La atestilo por domajno {domain:s} ne estas mem-subskribita. Ĉu vi certas, ke vi volas anstataŭigi ĝin? (Uzu '--force' por fari tion.)", "certmanager_unable_to_parse_self_CA_name": "Ne povis trapasi nomon de mem-subskribinta aŭtoritato (dosiero: {file:s})", "log_selfsigned_cert_install": "Instalu mem-subskribitan atestilon sur '{}' domajno", @@ -433,19 +356,16 @@ "global_settings_bad_choice_for_enum": "Malbona elekto por agordo {setting:s}, ricevita '{choice:s}', sed disponeblaj elektoj estas: {available_choices:s}", "server_shutdown": "La servilo haltos", "log_tools_migrations_migrate_forward": "Kuru migradoj", - "migration_0008_no_warning": "Supersalti vian SSH-agordon estu sekura, kvankam tio ne povas esti promesita! Ekfunkciu la migradon por superregi ĝin. Alie, vi ankaŭ povas salti la migradon, kvankam ĝi ne rekomendas.", "regenconf_now_managed_by_yunohost": "La agorda dosiero '{conf}' nun estas administrata de YunoHost (kategorio {category}).", "server_reboot_confirm": "Ĉu la servilo rekomencos tuj, ĉu vi certas? [{answers:s}]", "log_app_install": "Instalu la aplikon '{}'", "service_description_dnsmasq": "Traktas rezolucion de domajna nomo (DNS)", "global_settings_unknown_type": "Neatendita situacio, la agordo {setting:s} ŝajnas havi la tipon {unknown_type:s} sed ĝi ne estas tipo subtenata de la sistemo.", - "migration_0003_problematic_apps_warning": "Bonvolu noti, ke la sekvaj eventuale problemaj instalitaj programoj estis detektitaj. Ŝajnas, ke tiuj ne estis instalitaj el aplika katalogo aŭ ne estas markitaj kiel \"funkciantaj\". Tial ne eblas garantii, ke ili ankoraŭ funkcios post la ĝisdatigo: {problematic_apps}", "domain_hostname_failed": "Ne povis agordi novan gastigilon. Ĉi tio eble kaŭzos problemon poste (eble bone).", "server_reboot": "La servilo rekomenciĝos", "regenconf_failed": "Ne povis regeneri la agordon por kategorio(j): {categories}", "domain_uninstall_app_first": "Unu aŭ pluraj programoj estas instalitaj en ĉi tiu domajno. Bonvolu malinstali ilin antaŭ ol daŭrigi la domajnan forigon", "service_unknown": "Nekonata servo '{service:s}'", - "migration_0003_start": "Komencante migradon al Stretch. La protokoloj haveblos en {logfile}.", "domain_deletion_failed": "Ne eblas forigi domajnon {domain}: {error}", "log_user_update": "Ĝisdatigu uzantinformojn de '{}'", "user_creation_failed": "Ne povis krei uzanton {user}: {error}", @@ -458,14 +378,10 @@ "dyndns_domain_not_provided": "Provizanto DynDNS {provider:s} ne povas provizi domajnon {domain:s}.", "backup_unable_to_organize_files": "Ne povis uzi la rapidan metodon por organizi dosierojn en la ar archiveivo", "password_too_simple_2": "La pasvorto bezonas almenaŭ 8 signojn kaj enhavas ciferon, majusklojn kaj minusklojn", - "executing_script": "Plenumanta skripto '{script:s}' …", "service_cmd_exec_failed": "Ne povis plenumi la komandon '{command:s}'", - "migration_0007_cancelled": "Ne povis plibonigi la manieron kiel via SSH-agordo estas administrita.", - "migrate_tsig_failed": "Ne povis migri la DynDNS-domajnon '{domain}' al HMAC-SHA-512, ruliĝante. Eraro: {error_code}, {error}", "pattern_lastname": "Devas esti valida familinomo", "service_enabled": "La servo '{service:s}' nun aŭtomate komenciĝos dum sistemaj botoj.", "certmanager_no_cert_file": "Ne povis legi la atestan dosieron por la domajno {domain:s} (dosiero: {file:s})", - "migration_0008_port": "• Vi devos konekti uzante la havenon 22 anstataŭ via nuna kutimo SSH-haveno. Sentu vin libera reconfiguri ĝin;", "domain_creation_failed": "Ne eblas krei domajnon {domain}: {error}", "certmanager_domain_http_not_working": "Ŝajnas ke la domajno {domain:s} ne atingeblas per HTTP. Kontrolu, ke via DNS kaj NGINX-agordo ĝustas", "domain_cannot_remove_main": "Vi ne povas forigi '{domain:s}' ĉar ĝi estas la ĉefa domajno, vi bezonas unue agordi alian domajnon kiel la ĉefan domajnon per uzado de 'yunohost domain main-domain -n ', jen la listo de kandidataj domajnoj. : {other_domains:s}", @@ -473,7 +389,6 @@ "log_domain_add": "Aldonu '{}' domajnon en sisteman agordon", "global_settings_bad_type_for_setting": "Malbona tipo por agordo {setting:s}, ricevita {received_type:s}, atendata {expected_type:s}", "unlimit": "Neniu kvoto", - "dyndns_cron_installed": "Kreita laboro DynDNS cron", "system_username_exists": "Uzantnomo jam ekzistas en la listo de uzantoj de sistemo", "firewall_reloaded": "Fajroŝirmilo reŝarĝis", "service_restarted": "Servo '{service:s}' rekomencis", @@ -487,7 +402,6 @@ "firewall_rules_cmd_failed": "Iuj komandoj pri fajroŝirmilo malsukcesis. Pliaj informoj en ensaluto.", "certmanager_certificate_fetching_or_enabling_failed": "Provante uzi la novan atestilon por {domain:s} ne funkciis …", "app_full_domain_unavailable": "Bedaŭrinde, ĉi tiu app devas esti instalita sur propra domajno, sed aliaj programoj jam estas instalitaj sur la domajno '{domain}'. Vi povus uzi subdominon dediĉitan al ĉi tiu app anstataŭe.", - "migration_0011_slapd_config_will_be_overwritten": "Ŝajnas ke vi permane redaktis la slapd-agordon. Por ĉi tiu kritika migrado, YunoHost bezonas devigi la ĝisdatigon de la slapd-agordo. La originalaj dosieroj estos rezervitaj en {conf_backup_folder}.", "group_cannot_edit_all_users": "La grupo 'all_users' ne povas esti redaktita permane. Ĝi estas speciala grupo celita enhavi ĉiujn uzantojn registritajn en YunoHost", "group_cannot_edit_visitors": "La grupo 'vizitantoj' ne povas esti redaktita permane. Ĝi estas speciala grupo reprezentanta anonimajn vizitantojn", "group_cannot_edit_primary_group": "La grupo '{group}' ne povas esti redaktita permane. Ĝi estas la primara grupo celita enhavi nur unu specifan uzanton.", @@ -507,10 +421,8 @@ "diagnosis_basesystem_ynh_single_version": "{package} versio: {version} ({repo})", "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": "(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 --human-readable' 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 !", @@ -519,7 +431,6 @@ "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", "diagnosis_ip_connected_ipv4": "La servilo estas konektita al la interreto per IPv4 !", "diagnosis_ip_no_ipv4": "La servilo ne havas funkciantan IPv4.", "diagnosis_ip_connected_ipv6": "La servilo estas konektita al la interreto per IPv6 !", @@ -534,9 +445,6 @@ "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_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 ?", @@ -547,14 +455,12 @@ "diagnosis_services_bad_status": "Servo {service} estas {status} :(", "diagnosis_ram_low": "La sistemo havas {available} ({available_percent}%) RAM forlasita de {total}. Estu zorgema.", "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} ŝajnas esti permane modifita.", "diagnosis_description_ip": "Interreta konektebleco", "diagnosis_description_dnsrecords": "Registroj DNS", "diagnosis_description_services": "Servo kontrolas staton", "diagnosis_description_systemresources": "Rimedaj sistemoj", - "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} kajyunohost service log {service}).", @@ -565,7 +471,6 @@ "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_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}!", @@ -595,7 +500,6 @@ "diagnosis_never_ran_yet": "Ŝajnas, ke ĉi tiu servilo estis instalita antaŭ nelonge kaj estas neniu diagnoza raporto por montri. Vi devas komenci kurante plenan diagnozon, ĉu de la retadministro aŭ uzante 'yunohost diagnosis run' el la komandlinio.", "certmanager_warning_subdomain_dns_record": "Subdominio '{subdomain:s}' ne solvas al la sama IP-adreso kiel '{domain:s}'. Iuj funkcioj ne estos haveblaj ĝis vi riparos ĉi tion kaj regeneros la atestilon.", "diagnosis_basesystem_hardware": "Arkitekturo de servila aparataro estas {virt} {arch}", - "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 …", @@ -637,4 +541,4 @@ "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" -} +} \ No newline at end of file diff --git a/locales/es.json b/locales/es.json index 59d3dc162..f08803fb6 100644 --- a/locales/es.json +++ b/locales/es.json @@ -22,7 +22,6 @@ "app_unsupported_remote_type": "Tipo remoto no soportado por la aplicación", "app_upgrade_failed": "No se pudo actualizar {app:s}: {error}", "app_upgraded": "Actualizado {app:s}", - "ask_email": "Dirección de correo electrónico", "ask_firstname": "Nombre", "ask_lastname": "Apellido", "ask_main_domain": "Dominio principal", @@ -39,7 +38,6 @@ "backup_delete_error": "No se pudo eliminar «{path:s}»", "backup_deleted": "Eliminada la copia de seguridad", "backup_hook_unknown": "El gancho «{hook:s}» de la copia de seguridad es desconocido", - "backup_invalid_archive": "Esto no es un archivo de respaldo", "backup_nothings_done": "Nada que guardar", "backup_output_directory_forbidden": "Elija un directorio de salida diferente. Las copias de seguridad no se pueden crear en /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var o /home/yunohost.backup/archives subcarpetas", "backup_output_directory_not_empty": "Debe elegir un directorio de salida vacío", @@ -58,9 +56,6 @@ "domain_unknown": "Dominio desconocido", "done": "Hecho.", "downloading": "Descargando…", - "dyndns_cron_installed": "Creado el trabajo de cron de DynDNS", - "dyndns_cron_remove_failed": "No se pudo eliminar el trabajo de cron de DynDNS por: {error}", - "dyndns_cron_removed": "Eliminado el trabajo de cron de DynDNS", "dyndns_ip_update_failed": "No se pudo actualizar la dirección IP en DynDNS", "dyndns_ip_updated": "Actualizada su IP en DynDNS", "dyndns_key_generating": "Generando la clave del DNS. Esto podría tardar un rato.", @@ -69,8 +64,6 @@ "dyndns_registered": "Registrado dominio de DynDNS", "dyndns_registration_failed": "No se pudo registrar el dominio de DynDNS: {error:s}", "dyndns_unavailable": "El dominio «{domain:s}» no está disponible.", - "executing_command": "Ejecutando la orden «{command:s}»…", - "executing_script": "Ejecutando el guión «{script:s}»…", "extracting": "Extrayendo…", "field_invalid": "Campo no válido '{:s}'", "firewall_reload_failed": "No se pudo recargar el cortafuegos", @@ -90,9 +83,7 @@ "mail_forward_remove_failed": "No se pudo eliminar el reenvío de correo «{mail:s}»", "main_domain_change_failed": "No se pudo cambiar el dominio principal", "main_domain_changed": "El dominio principal ha cambiado", - "no_internet_connection": "El servidor no está conectado a Internet", "not_enough_disk_space": "No hay espacio libre suficiente en «{path:s}»", - "package_unknown": "Paquete desconocido '{pkgname}'", "packages_upgrade_failed": "No se pudieron actualizar todos los paquetes", "pattern_backup_archive_name": "Debe ser un nombre de archivo válido con un máximo de 30 caracteres, solo se admiten caracteres alfanuméricos y los caracteres -_. (guiones y punto)", "pattern_domain": "El nombre de dominio debe ser válido (por ejemplo mi-dominio.org)", @@ -156,20 +147,17 @@ "user_update_failed": "No se pudo actualizar el usuario {user}: {error}", "user_updated": "Cambiada la información de usuario", "yunohost_already_installed": "YunoHost ya está instalado", - "yunohost_ca_creation_failed": "No se pudo crear la autoridad de certificación", "yunohost_configured": "YunoHost está ahora configurado", "yunohost_installing": "Instalando YunoHost…", "yunohost_not_installed": "YunoHost no está correctamente instalado. Ejecute «yunohost tools postinstall»", "ldap_init_failed_to_create_admin": "La inicialización de LDAP no pudo crear el usuario «admin»", "mailbox_used_space_dovecot_down": "El servicio de buzón Dovecot debe estar activo si desea recuperar el espacio usado del buzón", "certmanager_attempt_to_replace_valid_cert": "Está intentando sobrescribir un certificado correcto y válido para el dominio {domain:s}! (Use --force para omitir este mensaje)", - "certmanager_domain_unknown": "Dominio desconocido «{domain:s}»", "certmanager_domain_cert_not_selfsigned": "El certificado para el dominio {domain:s} no es un certificado autofirmado. ¿Está seguro de que quiere reemplazarlo? (Use «--force» para hacerlo)", "certmanager_certificate_fetching_or_enabling_failed": "El intento de usar el nuevo certificado para {domain:s} no ha funcionado…", "certmanager_attempt_to_renew_nonLE_cert": "El certificado para el dominio «{domain:s}» no ha sido emitido por Let's Encrypt. ¡No se puede renovar automáticamente!", "certmanager_attempt_to_renew_valid_cert": "¡El certificado para el dominio «{domain:s}» no está a punto de expirar! (Puede usar --force si sabe lo que está haciendo)", "certmanager_domain_http_not_working": "Parece que no se puede acceder al dominio {domain:s} a través de HTTP. Por favor compruebe en los diagnósticos la categoría 'Web'para más información. (Si sabe lo que está haciendo, utilice '--no-checks' para no realizar estas comprobaciones.)", - "certmanager_error_no_A_record": "No se ha encontrado un registro DNS «A» para el dominio {domain:s}. Debe hacer que su nombre de dominio apunte a su máquina para poder instalar un certificado de Let's Encrypt. (Si sabe lo que está haciendo, use «--no-checks» para desactivar esas comprobaciones.)", "certmanager_domain_dns_ip_differs_from_public_ip": "El registro DNS 'A' para el dominio '{domain:s}' es diferente de la IP de este servidor. Por favor comprueba los 'registros DNS' (básicos) la categoría de diagnósticos para mayor información. Si recientemente modificó su registro 'A', espere a que se propague (algunos verificadores de propagación de DNS están disponibles en línea). (Si sabe lo que está haciendo, use '--no-checks' para desactivar esos cheques)", "certmanager_cannot_read_cert": "Se ha producido un error al intentar abrir el certificado actual para el dominio {domain:s} (archivo: {file:s}), razón: {reason:s}", "certmanager_cert_install_success_selfsigned": "Instalado correctamente un certificado autofirmado para el dominio «{domain:s}»", @@ -178,17 +166,13 @@ "certmanager_hit_rate_limit": "Se han emitido demasiados certificados recientemente para este conjunto exacto de dominios {domain:s}. Pruebe de nuevo más tarde. Vea para más detalles https://letsencrypt.org/docs/rate-limits/", "certmanager_cert_signing_failed": "No se pudo firmar el nuevo certificado", "certmanager_no_cert_file": "No se pudo leer el certificado para el dominio {domain:s} (archivo: {file:s})", - "certmanager_conflicting_nginx_file": "No se pudo preparar el dominio para el desafío ACME: el archivo de configuración de NGINX {filepath:s} está en conflicto y debe ser eliminado primero", "domain_cannot_remove_main": "No puede eliminar '{domain:s}' ya que es el dominio principal, primero debe configurar otro dominio como el dominio principal usando 'yunohost domain main-domain -n '; Aquí está la lista de dominios candidatos: {other_domains:s}", "certmanager_self_ca_conf_file_not_found": "No se pudo encontrar el archivo de configuración para la autoridad de autofirma (archivo: {file:s})", "certmanager_unable_to_parse_self_CA_name": "No se pudo procesar el nombre de la autoridad de autofirma (archivo: {file:s})", "domains_available": "Dominios disponibles:", "backup_archive_broken_link": "No se pudo acceder al archivo de respaldo (enlace roto a {path:s})", "certmanager_acme_not_configured_for_domain": "El reto ACME no ha podido ser realizado para {domain} porque su configuración de nginx no tiene el el código correcto... Por favor, asegurate que la configuración de nginx es correcta ejecutando en el terminal `yunohost tools regen-conf nginx --dry-run --with-diff`.", - "certmanager_http_check_timeout": "Tiempo de espera agotado cuando el servidor intentaba conectarse consigo mismo a través de HTTP usando una dirección IP pública (dominio «{domain:s}» con IP «{ip:s}»). Puede que esté experimentando un problema de redirección («hairpinning»), o que el cortafuegos o el enrutador de su servidor esté mal configurado.", - "certmanager_couldnt_fetch_intermediate_cert": "Tiempo de espera agotado intentando obtener el certificado intermedio de Let's Encrypt. Cancelada la instalación o renovación del certificado. Vuelva a intentarlo más tarde.", "domain_hostname_failed": "No se pudo establecer un nuevo nombre de anfitrión («hostname»). Esto podría causar problemas más tarde (no es seguro... podría ir bien).", - "yunohost_ca_creation_success": "Creada la autoridad de certificación local.", "app_already_installed_cant_change_url": "Esta aplicación ya está instalada. La URL no se puede cambiar solo con esta función. Marque `app changeurl` si está disponible.", "app_change_url_failed_nginx_reload": "No se pudo recargar NGINX. Esta es la salida de «nginx -t»:\n{nginx_errors:s}", "app_change_url_identical_domains": "El antiguo y nuevo dominio/url_path son idénticos ('{domain:s} {path:s}'), no se realizarán cambios.", @@ -200,14 +184,12 @@ "app_make_default_location_already_used": "No pudo hacer que la aplicación «{app}» sea la predeterminada en el dominio, «{domain}» ya está siendo usado por la aplicación «{other_app}»", "app_upgrade_app_name": "Ahora actualizando {app}…", "backup_abstract_method": "Este método de respaldo aún no se ha implementado", - "backup_applying_method_borg": "Enviando todos los archivos para la copia de seguridad al repositorio de borg-backup…", "backup_applying_method_copy": "Copiando todos los archivos en la copia de respaldo…", "backup_applying_method_custom": "Llamando al método de copia de seguridad personalizado «{method:s}»…", "backup_applying_method_tar": "Creando el archivo TAR de respaldo…", "backup_archive_system_part_not_available": "La parte del sistema «{part:s}» no está disponible en esta copia de seguridad", "backup_archive_writing_error": "No se pudieron añadir los archivos «{source:s}» (llamados en el archivo «{dest:s}») para ser respaldados en el archivo comprimido «{archive:s}»", "backup_ask_for_copying_if_needed": "¿Quiere realizar la copia de seguridad usando {size:s}MB temporalmente? (Se usa este modo ya que algunos archivos no se pudieron preparar usando un método más eficiente.)", - "backup_borg_not_implemented": "El método de respaldo de Borg aún no ha sido implementado", "backup_cant_mount_uncompress_archive": "No se pudo montar el archivo descomprimido como protegido contra escritura", "backup_copying_to_organize_the_archive": "Copiando {size:s}MB para organizar el archivo", "backup_couldnt_bind": "No se pudo enlazar {src:s} con {dest:s}.", @@ -215,7 +197,6 @@ "backup_csv_creation_failed": "No se pudo crear el archivo CSV necesario para la restauración", "backup_custom_mount_error": "El método de respaldo personalizado no pudo superar el paso «mount»", "backup_no_uncompress_archive_dir": "No existe tal directorio de archivos sin comprimir", - "backup_php5_to_php7_migration_may_fail": "No se pudo convertir su archivo para que sea compatible con PHP 7, puede que no pueda restaurar sus aplicaciones de PHP (motivo: {error:s})", "backup_system_part_failed": "No se pudo respaldar la parte del sistema «{part:s}»", "backup_with_no_backup_script_for_app": "La aplicación «{app:s}» no tiene un guión de respaldo. Omitiendo.", "backup_with_no_restore_script_for_app": "«{app:s}» no tiene un script de restauración, no podá restaurar automáticamente la copia de seguridad de esta aplicación.", @@ -228,7 +209,6 @@ "password_too_simple_2": "La contraseña tiene que ser de al menos 8 caracteres de longitud e incluir un número y caracteres en mayúsculas y minúsculas", "password_too_simple_3": "La contraseña tiene que ser de al menos 8 caracteres de longitud e incluir un número, mayúsculas, minúsculas y caracteres especiales", "password_too_simple_4": "La contraseña tiene que ser de al menos 12 caracteres de longitud e incluir un número, mayúsculas, minúsculas y caracteres especiales", - "users_available": "Usuarios disponibles:", "update_apt_cache_warning": "Algo fue mal durante la actualización de la caché de APT (gestor de paquetes de Debian). Aquí tiene un volcado de las líneas de sources.list que podría ayudarle a identificar las líneas problemáticas:\n{sourceslist}", "update_apt_cache_failed": "No se pudo actualizar la caché de APT (gestor de paquetes de Debian). Aquí tiene un volcado de las líneas de sources.list que podría ayudarle a identificar las líneas problemáticas:\n{sourceslist}", "tools_upgrade_special_packages_completed": "Actualización de paquetes de YunoHost completada.\nPulse [Intro] para regresar a la línea de órdenes", @@ -255,8 +235,6 @@ "service_description_rspamd": "Filtra correo no deseado y otras características relacionadas con el correo", "service_description_redis-server": "Una base de datos especializada usada para el acceso rápido de datos, cola de tareas y comunicación entre programas", "service_description_postfix": "Usado para enviar y recibir correos", - "service_description_php7.0-fpm": "Ejecuta aplicaciones escritas en PHP con NGINX", - "service_description_nslcd": "Maneja la conexión del intérprete de órdenes («shell») de usuario de YunoHost", "service_description_nginx": "Sirve o proporciona acceso a todos los sitios web alojados en su servidor", "service_description_mysql": "Almacena los datos de la aplicación (base de datos SQL)", "service_description_metronome": "Gestionar las cuentas XMPP de mensajería instantánea", @@ -273,7 +251,6 @@ "restore_system_part_failed": "No se pudo restaurar la parte del sistema «{part:s}»", "restore_removing_tmp_dir_failed": "No se pudo eliminar un directorio temporal antiguo", "restore_not_enough_disk_space": "Espacio insuficiente (espacio: {free_space:d} B, espacio necesario: {needed_space:d} B, margen de seguridad: {margin:d} B)", - "restore_mounting_archive": "Montando archivo en «{path:s}»", "restore_may_be_not_enough_disk_space": "Parece que su sistema no tiene suficiente espacio libre (libre: {free_space:d} B, espacio necesario: {needed_space:d} B, margen de seguridad: {margin:d} B)", "restore_extracting": "Extrayendo los archivos necesarios para el archivo…", "regenconf_pending_applying": "Aplicando la configuración pendiente para la categoría «{category}»…", @@ -291,11 +268,8 @@ "regenconf_file_kept_back": "Se espera que el archivo de configuración «{conf}» sea eliminado por regen-conf (categoría {category}) pero ha sido retenido.", "regenconf_file_copy_failed": "No se pudo copiar el nuevo archivo de configuración «{new}» a «{conf}»", "regenconf_file_backed_up": "Archivo de configuración «{conf}» respaldado en «{backup}»", - "permission_update_nothing_to_do": "No hay permisos para actualizar", "permission_updated": "Actualizado el permiso «{permission:s}»", - "permission_generated": "Actualizada la base de datos de permisos", "permission_update_failed": "No se pudo actualizar el permiso '{permission}': {error}", - "permission_name_not_valid": "Elija un nombre de permiso permitido para «{permission:s}", "permission_not_found": "No se encontró el permiso «{permission:s}»", "permission_deletion_failed": "No se pudo eliminar el permiso «{permission}»: {error}", "permission_deleted": "Eliminado el permiso «{permission:s}»", @@ -321,61 +295,6 @@ "migrations_dependencies_not_satisfied": "Ejecutar estas migraciones: «{dependencies_id}» antes de migrar {id}.", "migrations_cant_reach_migration_file": "No se pudo acceder a los archivos de migración en la ruta «%s»", "migrations_already_ran": "Esas migraciones ya se han realizado: {ids}", - "migration_0011_update_LDAP_schema": "Actualizando el esquema de LDAP…", - "migration_0011_update_LDAP_database": "Actualizando la base de datos de LDAP…", - "migration_0011_rollback_success": "Sistema revertido.", - "migration_0011_migration_failed_trying_to_rollback": "No se pudo migrar… intentando revertir el sistema.", - "migration_0011_migrate_permission": "Migrando permisos desde la configuración de las aplicaciones a LDAP…", - "migration_0011_LDAP_update_failed": "No se pudo actualizar LDAP. Error: {error:s}", - "migration_0011_done": "Migración finalizada. Ahora puede gestionar los grupos de usuarios.", - "migration_0011_create_group": "Creando un grupo para cada usuario…", - "migration_0011_can_not_backup_before_migration": "El respaldo del sistema no se pudo completar antes de que la migración fallase. Error: {error:s}", - "migration_0011_backup_before_migration": "Creando un respaldo de la base de datos de LDAP y de la configuración de las aplicaciones antes de la migración real.", - "migration_0009_not_needed": "La migración ya ocurrió de algún modo… (?) Omitiendo.", - "migration_0008_no_warning": "Sobre escribir su configuración SSH debería ser seguro ¡aunque esto no se puede prometer! Ejecute la migración para ignorarla. Por otra parte puede omitir la migración, aunque no se recomienda.", - "migration_0008_warning": "Si entiende esos avisos y quiere que YunoHost ignore su configuración actual, ejecute la migración. Por otra parte puede omitir la migración, aunque no se recomienda.", - "migration_0008_dsa": "• Se desactivará la clave DSA. Así que podría tener que anular un aviso espeluznante de su cliente SSH y volver a comprobar la huella de su servidor;", - "migration_0008_root": "• No podrá conectarse como «root» a través de SSH. En su lugar debe usar el usuario «admin»;", - "migration_0008_port": "• Tendrá que conectarse usando el puerto 22 en vez de su actual puerto SSH personalizado. No dude en reconfigurarlo;", - "migration_0008_general_disclaimer": "Para mejorar la seguridad de su servidor, es recomendable permitir a YunoHost gestionar la configuración de SSH. Su actual configuración de SSH difiere de la recomendación. Si permite a YunoHost reconfigurarla, la manera en la que conecta con su servidor a través de SSH cambiará así:", - "migration_0007_cannot_restart": "No se puede reiniciar SSH después de intentar cancelar la migración número 6.", - "migration_0007_cancelled": "No se pudo mejorar el modo en el que se gestiona su configuración de SSH.", - "migration_0006_disclaimer": "YunoHost espera ahora que las contraseñas de «admin» y «root» estén sincronizadas. Esta migración reemplaza su contraseña de «root» por la contraseña de «admin».", - "migration_0005_not_enough_space": "Tenga suficiente espacio libre disponible en {path} para ejecutar la migración.", - "migration_0005_postgresql_96_not_installed": "PostgreSQL 9.4 está instalado pero no PostgreSQL 9.6. Algo raro podría haber ocurrido en su sistema:(…", - "migration_0005_postgresql_94_not_installed": "PostgreSQL no estaba instalado en su sistema. Nada que hacer.", - "migration_0003_modified_files": "Tenga en cuenta que se encontró que los siguientes archivos fueron modificados manualmente y podrían ser sobrescritos después de la actualización: {manually_modified_files}", - "migration_0003_problematic_apps_warning": "Tenga en cuenta que se detectaron las siguientes aplicaciones instaladas posiblemente problemáticas. Parece que no se instalaron desde un catálogo de aplicaciones, o no se marcan como \"en funcionamiento\". En consecuencia, no se puede garantizar que seguirán funcionando después de la actualización: {problematic_apps}", - "migration_0003_general_warning": "Tenga en cuenta que esta migración es una operación delicada. El equipo de YunoHost ha hecho todo lo posible para revisarla y probarla, pero la migración aún podría romper parte del sistema o de sus aplicaciones.\n\nPor lo tanto, se recomienda que:\n - Realice una copia de seguridad de cualquier dato crítico o aplicación. Más información en https://yunohost.org/backup;\n - Tenga paciencia tras iniciar la migración: dependiendo de su conexión a Internet y de su hardware, podría tardar unas cuantas horas hasta que todo se actualice.\n\nAdemás, el puerto para SMTP usado por los clientes de correo externos (como Thunderbird o K9-Mail) cambió de 465 (SSL/TLS) a 587 (STARTTLS). El antiguo puerto (465) se cerrará automáticamente y el nuevo puerto (587) se abrirá en el cortafuegos. Todos los usuarios *tendrán* que adaptar la configuración de sus clientes de correo por lo tanto.", - "migration_0003_still_on_jessie_after_main_upgrade": "Algo fue mal durante la actualización principal: ⸘el sistema está aún en Jessie‽ Para investigar el problema, vea {log}:s…", - "migration_0003_system_not_fully_up_to_date": "Su sistema no está totalmente actualizado. Realice una actualización normal antes de ejecutar la migración a Stretch.", - "migration_0003_not_jessie": "¡La distribución de Debian actual no es Jessie!", - "migration_0003_yunohost_upgrade": "Iniciando la actualización del paquete YunoHost… la actualización ocurrirá inmediatamente después de que la migración finalizará. Después de que la operación esté completada, podría tener que iniciar sesión en la página de administración de nuevo.", - "migration_0003_restoring_origin_nginx_conf": "Su archivo /etc/nginx/nginx.conf ha sido editado. La migración lo devolverá a su estado original… El archivo anterior estará disponible como {backup_dest}.", - "migration_0003_fail2ban_upgrade": "Iniciando la actualización de Fail2Ban…", - "migration_0003_main_upgrade": "Iniciando la actualización principal…", - "migration_0003_patching_sources_list": "Corrigiendo «sources.lists»…", - "migration_0003_start": "Iniciando migración a Stretch. El registro estará disponible en {logfile}.", - "migration_description_0012_postgresql_password_to_md5_authentication": "Forzar a la autentificación de PostgreSQL a usar MD5 para las conexiones locales", - "migration_description_0011_setup_group_permission": "Configurar grupo de usuario y permisos para aplicaciones y servicios", - "migration_description_0010_migrate_to_apps_json": "Elimine los catálogos de aplicaciones obsoletas y use la nueva lista unificada de 'apps.json' en su lugar (desactualizada, reemplazada por la migración 13)", - "migration_description_0009_decouple_regenconf_from_services": "Separar el mecanismo «regen-conf» de los servicios", - "migration_description_0008_ssh_conf_managed_by_yunohost_step2": "Permitir que la configuración de SSH la gestione YunoHost (paso 2, manual)", - "migration_description_0007_ssh_conf_managed_by_yunohost_step1": "Permitir que la configuración de SSH la gestione YunoHost (paso 1, automático)", - "migration_description_0006_sync_admin_and_root_passwords": "Sincronizar las contraseñas de «admin» y «root»", - "migration_description_0005_postgresql_9p4_to_9p6": "Migrar las bases de datos de PostgreSQL 9.4 a 9.6", - "migration_description_0004_php5_to_php7_pools": "Reconfigurar los «pools» de PHP para usar PHP 7 en vez de 5", - "migration_description_0003_migrate_to_stretch": "Actualizar el sistema a Debian Stretch y YunoHost 3.0", - "migration_description_0002_migrate_to_tsig_sha256": "Mejore la seguridad de las actualizaciones de TSIG de DynDNS usando SHA-512 en vez de MD5", - "migration_description_0001_change_cert_group_to_sslcert": "Cambiar los permisos de grupo de certificados de «metronome» a «ssl-cert»", - "migrate_tsig_not_needed": "Parece que no usa un dominio de DynDNS, así que no es necesario migrar.", - "migrate_tsig_wait_4": "30 segundos…", - "migrate_tsig_wait_3": "1 min. …", - "migrate_tsig_wait_2": "2 min. …", - "migrate_tsig_wait": "Esperando tres minutos para que el servidor de DynDNS tenga en cuenta la nueva clave…", - "migrate_tsig_start": "Detectado algoritmo de clave insuficientemente seguro para la firma TSIG del dominio «{domain}», iniciando migración al más seguro HMAC-SHA-512", - "migrate_tsig_failed": "No se pudo migrar el dominio de DynDNS «{domain}» a HMAC-SHA-512, revertiendo. Error: {error_code}, {error}", - "migrate_tsig_end": "Terminada la migración a HMAC-SHA-512", "mail_unavailable": "Esta dirección de correo está reservada y será asignada automáticamente al primer usuario", "mailbox_disabled": "Correo desactivado para usuario {user:s}", "log_tools_reboot": "Reiniciar el servidor", @@ -412,7 +331,6 @@ "log_link_to_failed_log": "No se pudo completar la operación «{desc}». Para obtener ayuda, proporcione el registro completo de esta operación pulsando aquí", "log_help_to_get_log": "Para ver el registro de la operación «{desc}», ejecute la orden «yunohost log show {name}{name}»", "log_link_to_log": "Registro completo de esta operación: «{desc}»", - "log_category_404": "La categoría de registro «{category}» no existe", "log_corrupted_md_file": "El archivo de metadatos YAML asociado con el registro está dañado: «{md_file}\nError: {error}»", "hook_json_return_error": "No se pudo leer la respuesta del gancho {path:s}. Error: {msg:s}. Contenido sin procesar: {raw_content}", "group_update_failed": "No se pudo actualizar el grupo «{group}»: {error}", @@ -431,10 +349,6 @@ "global_settings_setting_security_password_user_strength": "Seguridad de la contraseña de usuario", "global_settings_setting_security_password_admin_strength": "Seguridad de la contraseña del administrador", "global_settings_setting_security_nginx_compatibility": "Compromiso entre compatibilidad y seguridad para el servidor web NGINX. Afecta al cifrado (y otros aspectos relacionados con la seguridad)", - "global_settings_setting_example_string": "Ejemplo de opción de cadena", - "global_settings_setting_example_int": "Ejemplo de opción «int»", - "global_settings_setting_example_enum": "Ejemplo de opción «enum»", - "global_settings_setting_example_bool": "Ejemplo de opción booleana", "global_settings_reset_success": "Respaldada la configuración previa en {path:s}", "global_settings_key_doesnt_exists": "La clave «{settings_key:s}» no existe en la configuración global, puede ver todas las claves disponibles ejecutando «yunohost settings list»", "global_settings_cant_write_settings": "No se pudo guardar el archivo de configuración, motivo: {reason:s}", @@ -457,7 +371,6 @@ "backup_method_tar_finished": "Creado el archivo TAR de respaldo", "backup_method_custom_finished": "Terminado el método «{method:s}» de respaldo personalizado", "backup_method_copy_finished": "Terminada la copia de seguridad", - "backup_method_borg_finished": "Terminado el respaldo en Borg", "backup_custom_backup_error": "El método de respaldo personalizado no pudo superar el paso de «copia de seguridad»", "backup_actually_backuping": "Creando un archivo de respaldo de los archivos obtenidos…", "ask_new_path": "Nueva ruta", @@ -486,7 +399,6 @@ "log_user_group_create": "Crear grupo «{}»", "log_user_permission_update": "Actualizar los accesos para el permiso «{}»", "log_user_permission_reset": "Restablecer permiso «{}»", - "migration_0011_failed_to_remove_stale_object": "No se pudo eliminar el objeto obsoleto {dn}: {error}", "permission_already_allowed": "El grupo «{group}» ya tiene el permiso «{permission}» activado", "permission_already_disallowed": "El grupo '{group}' ya tiene el permiso '{permission}' deshabilitado", "permission_cannot_remove_main": "No está permitido eliminar un permiso principal", @@ -498,7 +410,6 @@ "group_cannot_edit_visitors": "El grupo «visitors» no se puede editar manualmente. Es un grupo especial que representa a los visitantes anónimos", "group_cannot_edit_primary_group": "El grupo «{group}» no se puede editar manualmente. Es el grupo primario destinado a contener solo un usuario específico.", "log_permission_url": "Actualizar la URL relacionada con el permiso «{}»", - "migration_0011_slapd_config_will_be_overwritten": "Parece que ha editado manualmente la configuración de slapd. Para esta migración crítica, YunoHost necesita forzar la actualización de la configuración de slapd. Los archivos originales se respaldarán en {conf_backup_folder}.", "permission_already_up_to_date": "El permiso no se ha actualizado porque las peticiones de incorporación o eliminación ya coinciden con el estado actual.", "permission_currently_allowed_for_all_users": "Este permiso se concede actualmente a todos los usuarios además de los otros grupos. Probablemente quiere o eliminar el permiso de «all_users» o eliminar los otros grupos a los que está otorgado actualmente.", "permission_require_account": "El permiso {permission} solo tiene sentido para usuarios con una cuenta y, por lo tanto, no se puede activar para visitantes.", @@ -511,8 +422,6 @@ "diagnosis_failed_for_category": "Error de diagnóstico para la categoría '{category}': {error}", "diagnosis_cache_still_valid": "(Caché aún válida para el diagnóstico de {category}. ¡No se volvera a comprobar de momento!)", "diagnosis_found_errors_and_warnings": "¡Encontrado(s) error(es) significativo(s) {errors} (y aviso(s) {warnings}) relacionado(s) con {category}!", - "diagnosis_display_tip_web": "Puede ir a la sección de diagnóstico (en la pantalla principal) para ver los problemas encontrados.", - "diagnosis_display_tip_cli": "Puede ejecutar «yunohost diagnosis show --issues --human-readable» para mostrar los problemas encontrados.", "apps_catalog_init_success": "¡Sistema de catálogo de aplicaciones inicializado!", "apps_catalog_updating": "Actualizando el catálogo de aplicaciones…", "apps_catalog_failed_to_download": "No se puede descargar el catálogo de aplicaciones {apps_catalog}: {error}", @@ -553,14 +462,10 @@ "diagnosis_ram_ok": "El sistema aun tiene {available} ({available_percent}%) de RAM de un total de {total}.", "diagnosis_swap_none": "El sistema no tiene mas espacio de intercambio. Considera agregar por lo menos {recommended} de espacio de intercambio para evitar que el sistema se quede sin memoria.", "diagnosis_swap_notsomuch": "Al sistema le queda solamente {total} de espacio de intercambio. Considera agregar al menos {recommended} para evitar que el sistema se quede sin memoria.", - "diagnosis_mail_ougoing_port_25_ok": "El puerto de salida 25 no esta bloqueado y los correos electrónicos pueden ser enviados a otros servidores.", "diagnosis_mail_outgoing_port_25_blocked": "El puerto de salida 25 parece estar bloqueado. Intenta desbloquearlo con el panel de configuración de tu proveedor de servicios de Internet (o proveedor de halbergue). Mientras tanto, el servidor no podrá enviar correos electrónicos a otros servidores.", "diagnosis_regenconf_allgood": "Todos los archivos de configuración están en linea con la configuración recomendada!", "diagnosis_regenconf_manually_modified": "El archivo de configuración {file} parece que ha sido modificado manualmente.", "diagnosis_regenconf_manually_modified_details": "¡Esto probablemente esta BIEN si sabes lo que estás haciendo! YunoHost dejará de actualizar este fichero automáticamente... Pero ten en cuenta que las actualizaciones de YunoHost pueden contener importantes cambios que están recomendados. Si quieres puedes comprobar las diferencias mediante yunohost tools regen-conf {category} --dry-run --with-diff o puedes forzar el volver a las opciones recomendadas mediante el comando yunohost tools regen-conf {category} --force", - "diagnosis_regenconf_manually_modified_debian": "El archivos de configuración {file} fue modificado manualmente comparado con el valor predeterminado de Debian.", - "diagnosis_regenconf_manually_modified_debian_details": "Esto este probablemente BIEN, pero igual no lo pierdas de vista...", - "diagnosis_security_all_good": "Ninguna vulnerabilidad critica de seguridad fue encontrada.", "diagnosis_security_vulnerable_to_meltdown": "Pareces vulnerable a el colapso de vulnerabilidad critica de seguridad", "diagnosis_description_basesystem": "Sistema de base", "diagnosis_description_ip": "Conectividad a Internet", @@ -574,14 +479,10 @@ "diagnosis_ports_unreachable": "El puerto {port} no es accesible desde internet.", "diagnosis_ports_could_not_diagnose": "No se puede comprobar si los puertos están accesibles desde el exterior.", "diagnosis_ports_could_not_diagnose_details": "Error: {error}", - "diagnosis_description_security": "Validación de seguridad", "diagnosis_description_regenconf": "Configuraciones de sistema", "diagnosis_description_mail": "Correo electrónico", "diagnosis_description_web": "Web", - "diagnosis_basesystem_hardware_board": "El modelo de placa del servidor es {model}", "diagnosis_basesystem_hardware": "La arquitectura material del servidor es {virt} {arch}", - "migration_description_0014_remove_app_status_json": "Supresión del archivo de aplicaciones heredado status.json", - "migration_description_0013_futureproof_apps_catalog_system": "Migración hacia el nuevo sistema de catalogo de aplicación a prueba del futuro", "log_domain_main_domain": "Hacer de '{}' el dominio principal", "log_app_config_apply": "Aplica la configuración de la aplicación '{}'", "log_app_config_show_panel": "Muestra el panel de configuración de la aplicación '{}'", @@ -689,4 +590,4 @@ "diagnosis_basesystem_hardware_model": "El modelo de servidor es {model}", "additional_urls_already_removed": "La URL adicional «{url:s}» ya se ha eliminado para el permiso «{permission:s}»", "additional_urls_already_added": "La URL adicional «{url:s}» ya se ha añadido para el permiso «{permission:s}»" -} +} \ No newline at end of file diff --git a/locales/eu.json b/locales/eu.json index 539fb9157..1891e00a3 100644 --- a/locales/eu.json +++ b/locales/eu.json @@ -1,3 +1,3 @@ { "password_too_simple_1": "Pasahitzak gutxienez 8 karaktere izan behar ditu" -} +} \ No newline at end of file diff --git a/locales/fr.json b/locales/fr.json index 0ca00ef41..179b83722 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -22,7 +22,6 @@ "app_unsupported_remote_type": "Ce type de commande à distance utilisé pour cette application n’est pas supporté", "app_upgrade_failed": "Impossible de mettre à jour {app:s} : {error}", "app_upgraded": "{app:s} mis à jour", - "ask_email": "Adresse de courriel", "ask_firstname": "Prénom", "ask_lastname": "Nom", "ask_main_domain": "Domaine principal", @@ -39,7 +38,6 @@ "backup_delete_error": "Impossible de supprimer '{path:s}'", "backup_deleted": "La sauvegarde a été supprimée", "backup_hook_unknown": "Script de sauvegarde '{hook:s}' inconnu", - "backup_invalid_archive": "Archive de sauvegarde invalide", "backup_nothings_done": "Il n’y a rien à sauvegarder", "backup_output_directory_forbidden": "Choisissez un répertoire de destination différent. Les sauvegardes ne peuvent pas être créées dans les sous-dossiers /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var ou /home/yunohost.backup/archives", "backup_output_directory_not_empty": "Le répertoire de destination n’est pas vide", @@ -58,9 +56,6 @@ "domain_unknown": "Domaine inconnu", "done": "Terminé", "downloading": "Téléchargement en cours …", - "dyndns_cron_installed": "La tâche cron pour le domaine DynDNS a été créée", - "dyndns_cron_remove_failed": "Impossible de supprimer la tâche cron DynDNS parce que : {error}", - "dyndns_cron_removed": "La tâche cron pour le domaine DynDNS a été enlevée", "dyndns_ip_update_failed": "Impossible de mettre à jour l’adresse IP sur le domaine DynDNS", "dyndns_ip_updated": "Mise à jour de votre IP pour le domaine DynDNS", "dyndns_key_generating": "Génération de la clé DNS..., cela peut prendre un certain temps.", @@ -69,8 +64,6 @@ "dyndns_registered": "Domaine DynDNS enregistré", "dyndns_registration_failed": "Impossible d’enregistrer le domaine DynDNS : {error:s}", "dyndns_unavailable": "Le domaine {domain:s} est indisponible.", - "executing_command": "Exécution de la commande '{command:s}'...", - "executing_script": "Exécution du script '{script:s}'...", "extracting": "Extraction en cours...", "field_invalid": "Champ incorrect : '{:s}'", "firewall_reload_failed": "Impossible de recharger le pare-feu", @@ -90,9 +83,7 @@ "mail_forward_remove_failed": "Impossible de supprimer le courriel de transfert '{mail:s}'", "main_domain_change_failed": "Impossible de modifier le domaine principal", "main_domain_changed": "Le domaine principal a été modifié", - "no_internet_connection": "Le serveur n’est pas connecté à Internet", "not_enough_disk_space": "L’espace disque est insuffisant sur '{path:s}'", - "package_unknown": "Le paquet '{pkgname}' est inconnu", "packages_upgrade_failed": "Impossible de mettre à jour tous les paquets", "pattern_backup_archive_name": "Doit être un nom de fichier valide avec un maximum de 30 caractères, et composé de caractères alphanumériques et -_. uniquement", "pattern_domain": "Doit être un nom de domaine valide (ex : mon-domaine.fr)", @@ -156,18 +147,15 @@ "user_update_failed": "Impossible de mettre à jour l’utilisateur {user} : {error}", "user_updated": "L’utilisateur a été modifié", "yunohost_already_installed": "YunoHost est déjà installé", - "yunohost_ca_creation_failed": "Impossible de créer l’autorité de certification", "yunohost_configured": "YunoHost est maintenant configuré", "yunohost_installing": "L’installation de YunoHost est en cours...", "yunohost_not_installed": "YunoHost n’est pas correctement installé. Veuillez exécuter 'yunohost tools postinstall'", "certmanager_attempt_to_replace_valid_cert": "Vous êtes en train de vouloir remplacer un certificat correct et valide pour le domaine {domain:s} ! (Utilisez --force pour contourner cela)", - "certmanager_domain_unknown": "Domaine {domain:s} inconnu", "certmanager_domain_cert_not_selfsigned": "Le certificat du domaine {domain:s} n’est pas auto-signé. Voulez-vous vraiment le remplacer ? (Utilisez --force pour cela)", "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, 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 du domaine {domain:s} est différent de l’adresse IP de ce serveur. Pour plus d'informations, veuillez consulter la catégorie \"Enregistrements DNS\" dans la section diagnostic. 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}", "certmanager_cert_install_success_selfsigned": "Le certificat auto-signé est maintenant installé pour le domaine « {domain:s} »", @@ -175,7 +163,6 @@ "certmanager_cert_renew_success": "Certificat Let’s Encrypt renouvelé pour le domaine '{domain:s}'", "certmanager_cert_signing_failed": "Impossible de signer le nouveau certificat", "certmanager_no_cert_file": "Impossible de lire le fichier du certificat pour le domaine {domain:s} (fichier : {file:s})", - "certmanager_conflicting_nginx_file": "Impossible de préparer le domaine pour le défi ACME : le fichier de configuration NGINX {filepath:s} est en conflit et doit être préalablement retiré", "certmanager_hit_rate_limit": "Trop de certificats ont déjà été émis récemment pour ce même ensemble de domaines {domain:s}. Veuillez réessayer plus tard. Lisez https://letsencrypt.org/docs/rate-limits/ pour obtenir plus de détails sur les ratios et limitations", "ldap_init_failed_to_create_admin": "L’initialisation de l’annuaire LDAP n’a pas réussi à créer l’utilisateur admin", "domain_cannot_remove_main": "Vous ne pouvez pas supprimer '{domain:s}' car il s’agit du domaine principal. Vous devez d’abord définir un autre domaine comme domaine principal à l’aide de 'yunohost domain main-domain -n ', voici la liste des domaines candidats : {other_domains:s}", @@ -185,10 +172,7 @@ "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 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).", - "yunohost_ca_creation_success": "L'autorité de certification locale a été créée.", "app_already_installed_cant_change_url": "Cette application est déjà installée. L’URL ne peut pas être changé simplement par cette fonction. Vérifiez si cela est disponible avec `app changeurl`.", "app_change_url_failed_nginx_reload": "Le redémarrage de NGINX a échoué. Voici la sortie de 'nginx -t' :\n{nginx_errors:s}", "app_change_url_identical_domains": "L’ancien et le nouveau couple domaine/chemin_de_l’URL sont identiques pour ('{domain:s}{path:s}'), rien à faire.", @@ -202,21 +186,15 @@ "global_settings_cant_write_settings": "Échec d’écriture du fichier de configurations car : {reason:s}", "global_settings_key_doesnt_exists": "La clef '{settings_key:s}' n’existe pas dans les configurations générales, vous pouvez voir toutes les clefs disponibles en saisissant 'yunohost settings list'", "global_settings_reset_success": "Vos configurations précédentes ont été sauvegardées dans {path:s}", - "global_settings_setting_example_bool": "Exemple d’option booléenne", - "global_settings_setting_example_int": "Exemple d’option de type entier", - "global_settings_setting_example_string": "Exemple d’option de type chaîne", - "global_settings_setting_example_enum": "Exemple d’option de type énumération", "global_settings_unknown_type": "Situation inattendue : la configuration {setting:s} semble avoir le type {unknown_type:s} mais celui-ci n’est pas pris en charge par le système.", "global_settings_unknown_setting_from_settings_file": "Clé inconnue dans les paramètres : '{setting_key:s}', rejet de cette clé et sauvegarde de celle-ci dans /etc/yunohost/unkown_settings.json", "backup_abstract_method": "Cette méthode de sauvegarde reste à implémenter", "backup_applying_method_tar": "Création de l’archive TAR de la sauvegarde...", "backup_applying_method_copy": "Copie de tous les fichiers à sauvegarder...", - "backup_applying_method_borg": "Envoi de tous les fichiers à sauvegarder dans le répertoire borg-backup...", "backup_applying_method_custom": "Appel de la méthode de sauvegarde personnalisée '{method:s}'...", "backup_archive_system_part_not_available": "La partie '{part:s}' du système n’est pas disponible dans cette sauvegarde", "backup_archive_writing_error": "Impossible d’ajouter des fichiers '{source:s}' (nommés dans l’archive : '{dest:s}') à sauvegarder dans l’archive compressée '{archive:s}'", "backup_ask_for_copying_if_needed": "Voulez-vous effectuer la sauvegarde en utilisant {size:s}Mo temporairement ? (Cette méthode est utilisée car certains fichiers n’ont pas pu être préparés avec une méthode plus efficace.)", - "backup_borg_not_implemented": "La méthode de sauvegarde Borg n’est pas encore implémentée", "backup_cant_mount_uncompress_archive": "Impossible de monter en lecture seule le dossier de l’archive décompressée", "backup_copying_to_organize_the_archive": "Copie de {size:s} Mo pour organiser l’archive", "backup_csv_creation_failed": "Impossible de créer le fichier CSV nécessaire à la restauration", @@ -226,7 +204,6 @@ "backup_no_uncompress_archive_dir": "Ce dossier d’archive décompressée n’existe pas", "backup_method_tar_finished": "L’archive TAR de la sauvegarde a été créée", "backup_method_copy_finished": "La copie de la sauvegarde est terminée", - "backup_method_borg_finished": "La sauvegarde dans Borg est terminée", "backup_method_custom_finished": "La méthode de sauvegarde personnalisée '{method:s}' est terminée", "backup_system_part_failed": "Impossible de sauvegarder la partie '{part:s}' du système", "backup_unable_to_organize_files": "Impossible d’utiliser la méthode rapide pour organiser les fichiers dans l’archive", @@ -265,7 +242,6 @@ "service_description_metronome": "Gère les comptes de messagerie instantanée XMPP", "service_description_mysql": "Stocke les données des applications (bases de données SQL)", "service_description_nginx": "Sert ou permet l’accès à tous les sites web hébergés sur votre serveur", - "service_description_nslcd": "Gère la connexion en ligne de commande des utilisateurs YunoHost", "service_description_postfix": "Utilisé pour envoyer et recevoir des courriels", "service_description_redis-server": "Une base de données spécialisée utilisée pour l’accès rapide aux données, les files d’attentes et la communication entre les programmes", "service_description_rspamd": "Filtre le pourriel, et d’autres fonctionnalités liées au courriel", @@ -275,11 +251,9 @@ "service_description_yunohost-firewall": "Gère l’ouverture et la fermeture des ports de connexion aux services", "experimental_feature": "Attention : cette fonctionnalité est expérimentale et ne doit pas être considérée comme stable, vous ne devriez pas l’utiliser à moins que vous ne sachiez ce que vous faites.", "log_corrupted_md_file": "Le fichier YAML de métadonnées associé aux logs est corrompu : '{md_file}'\nErreur : {error}", - "log_category_404": "Le journal de la catégorie '{category}' n’existe pas", "log_link_to_log": "Journal complet de cette opération : ' {desc} '", "log_help_to_get_log": "Pour voir le journal de cette opération '{desc}', utilisez la commande 'yunohost log show {name}{name}'", "log_link_to_failed_log": "L’opération '{desc}' a échoué ! Pour obtenir de l’aide, merci de partager le journal de l’opération en cliquant ici", - "backup_php5_to_php7_migration_may_fail": "Impossible de convertir votre archive pour prendre en charge PHP 7, vous pourriez ne plus pouvoir restaurer vos applications PHP (cause : {error:s})", "log_help_to_get_failed_log": "L’opération '{desc}' a échoué ! Pour obtenir de l’aide, merci de partager le journal de l’opération en utilisant la commande 'yunohost log share {name}'", "log_does_exists": "Il n’y a pas de journal des opérations avec le nom '{log}', utilisez 'yunohost log list' pour voir tous les journaux d’opérations disponibles", "log_operation_unit_unclosed_properly": "L’opération ne s’est pas terminée correctement", @@ -310,17 +284,8 @@ "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 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 :(…", - "migration_0005_not_enough_space": "Laissez suffisamment d’espace disponible dans {path} pour exécuter la migration.", - "service_description_php7.0-fpm": "Exécute des applications écrites en PHP avec NGINX", - "users_available": "Liste des utilisateurs disponibles :", "good_practices_about_admin_password": "Vous êtes sur le point de définir un nouveau mot de passe d'administration. Le mot de passe doit comporter au moins 8 caractères, bien qu'il soit recommandé d'utiliser un mot de passe plus long (c'est-à-dire une phrase de passe) et / ou d'utiliser une variation de caractères (majuscule, minuscule, chiffres et caractères spéciaux).", "good_practices_about_user_password": "Vous êtes sur le point de définir un nouveau mot de passe utilisateur. Le mot de passe doit comporter au moins 8 caractères, bien qu'il soit recommandé d'utiliser un mot de passe plus long (c'est-à-dire une phrase secrète) et / ou une variation de caractères (majuscule, minuscule, chiffres et caractères spéciaux).", - "migration_description_0006_sync_admin_and_root_passwords": "Synchroniser les mots de passe admin et root", - "migration_0006_disclaimer": "YunoHost s’attend maintenant à ce que les mots de passe administrateur et racine soient synchronisés. Cette migration remplace votre mot de passe root par le mot de passe administrateur.", "password_listed": "Ce mot de passe fait partie des mots de passe les plus utilisés au monde. Veuillez choisir quelque chose de plus unique.", "password_too_simple_1": "Le mot de passe doit comporter au moins 8 caractères", "password_too_simple_2": "Le mot de passe doit comporter au moins 8 caractères et contenir des chiffres, des majuscules et des minuscules", @@ -348,17 +313,6 @@ "global_settings_setting_security_password_user_strength": "Qualité du mot de passe de l’utilisateur", "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Autoriser l’utilisation de la clé hôte DSA (obsolète) pour la configuration du service SSH", "hook_json_return_error": "Échec de la lecture au retour du script {path:s}. Erreur : {msg:s}. Contenu brut : {raw_content}", - "migration_description_0007_ssh_conf_managed_by_yunohost_step1": "La configuration SSH sera gérée par YunoHost (étape 1, automatique)", - "migration_description_0008_ssh_conf_managed_by_yunohost_step2": "La configuration SSH sera gérée par YunoHost (étape 2, manuelle)", - "migration_0007_cancelled": "Impossible d’améliorer la gestion de votre configuration SSH.", - "migration_0007_cannot_restart": "SSH ne peut pas être redémarré après avoir essayé d’annuler la migration numéro 6.", - "migration_0008_general_disclaimer": "Pour améliorer la sécurité de votre serveur, il est recommandé de laisser YunoHost gérer la configuration SSH. Votre configuration SSH actuelle diffère de la configuration recommandée. Si vous laissez YunoHost la reconfigurer, la façon dont vous vous connectez à votre serveur via SSH changera comme suit :", - "migration_0008_port": "- Vous devrez vous connecter en utilisant le port 22 au lieu de votre actuel port SSH personnalisé. N’hésitez pas à le reconfigurer ;", - "migration_0008_root": "- Vous ne pourrez pas vous connecter en tant que root via SSH. Au lieu de cela, vous devrez utiliser l’utilisateur admin ;", - "migration_0008_dsa": "- La clé DSA sera désactivée. Par conséquent, il se peut que vous ayez besoin d’invalider un avertissement effrayant de votre client SSH afin de revérifier l’empreinte de votre serveur ;", - "migration_0008_warning": "Si vous comprenez ces avertissements et souhaitez que YunoHost écrase votre configuration actuelle, exécutez la migration. Sinon, vous pouvez également ignorer la migration, bien que cela ne soit pas recommandé.", - "migration_0008_no_warning": "Remplacer votre configuration SSH ne devrait pas poser de problème, bien qu'il soit difficile de le promettre ! Exécutez la migration pour la remplacer. Sinon, vous pouvez également ignorer la migration, bien que cela ne soit pas recommandé.", - "migrations_success": "Migration {number} {name} réussie !", "pattern_password_app": "Désolé, les mots de passe ne peuvent pas contenir les caractères suivants : {forbidden_chars}", "root_password_replaced_by_admin_password": "Votre mot de passe root a été remplacé par votre mot de passe administrateur.", "service_reload_failed": "Impossible de recharger le service '{service:s}'.\n\nJournaux historisés récents de ce service : {logs:s}", @@ -371,7 +325,6 @@ "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 comportant moins de 127 caractères", "log_regen_conf": "Régénérer les configurations du système '{}'", - "migration_0009_not_needed": "Cette migration semble avoir déjà été jouée ? On l’ignore.", "regenconf_file_backed_up": "Le fichier de configuration '{conf}' a été sauvegardé sous '{backup}'", "regenconf_file_copy_failed": "Impossible de copier le nouveau fichier de configuration '{new}' vers '{conf}'", "regenconf_file_manually_modified": "Le fichier de configuration '{conf}' a été modifié manuellement et ne sera pas mis à jour", @@ -385,8 +338,6 @@ "global_settings_setting_security_nginx_compatibility": "Compatibilité versus compromis sécuritaire pour le serveur web Nginx. Affecte les cryptogrammes (et d’autres aspects liés à la sécurité)", "global_settings_setting_security_ssh_compatibility": "Compatibilité versus compromis sécuritaire pour le serveur SSH. Affecte les cryptogrammes (et d’autres aspects liés à la sécurité)", "global_settings_setting_security_postfix_compatibility": "Compatibilité versus compromis sécuritaire pour le serveur Postfix. Affecte les cryptogrammes (et d’autres aspects liés à la sécurité)", - "migration_description_0009_decouple_regenconf_from_services": "Dissocier le mécanisme « regen-conf » des services", - "migration_description_0010_migrate_to_apps_json": "Supprimer les catalogues d’applications obsolètes afin d’utiliser la nouvelle liste unifiée 'apps.json' à la place (les anciens catalogues seront remplacés durant la migration 13)", "regenconf_file_kept_back": "Le fichier de configuration '{conf}' devait être supprimé par « regen-conf » (catégorie {category}) mais a été conservé.", "regenconf_updated": "La configuration a été mise à jour pour '{category}'", "regenconf_would_be_updated": "La configuration aurait dû être mise à jour pour la catégorie '{category}'", @@ -419,26 +370,15 @@ "mailbox_disabled": "La boîte aux lettres est désactivée pour l’utilisateur {user:s}", "app_action_broke_system": "Cette action semble avoir cassé des services importants : {services}", "apps_already_up_to_date": "Toutes les applications sont déjà à jour", - "migration_0011_create_group": "Création d'un groupe pour chaque utilisateur…", - "migration_0011_done": "Migration terminée. Vous êtes maintenant en mesure de gérer des groupes d’utilisateurs.", "migrations_must_provide_explicit_targets": "Vous devez fournir des cibles explicites lorsque vous utilisez '--skip' ou '--force-rerun'", "migrations_no_such_migration": "Il n’y a pas de migration appelée '{id}'", "migrations_pending_cant_rerun": "Ces migrations étant toujours en attente, vous ne pouvez pas les exécuter à nouveau : {ids}", - "migration_description_0012_postgresql_password_to_md5_authentication": "Forcer l’authentification PostgreSQL à utiliser MD5 pour les connexions locales", "migrations_exclusive_options": "'auto', '--skip' et '--force-rerun' sont des options mutuellement exclusives.", "migrations_not_pending_cant_skip": "Ces migrations ne sont pas en attente et ne peuvent donc pas être ignorées : {ids}", - "migration_0011_can_not_backup_before_migration": "La sauvegarde du système n’a pas pu être terminée avant l’échec de la migration. Erreur : {error:s}", - "migration_0011_migrate_permission": "Migration des autorisations des paramètres des applications vers LDAP...", - "migration_0011_migration_failed_trying_to_rollback": "La migration a échoué… Tentative de restauration du système.", - "migration_0011_rollback_success": "Système restauré.", - "migration_0011_update_LDAP_database": "Mise à jour de la base de données LDAP...", - "migration_0011_backup_before_migration": "Création d’une sauvegarde des paramètres de la base de données LDAP et des applications avant la migration.", "permission_not_found": "Permission '{permission:s}' introuvable", "permission_update_failed": "Impossible de mettre à jour la permission '{permission}' : {error}", "permission_updated": "Permission '{permission:s}' mise à jour", - "permission_update_nothing_to_do": "Aucune permission à mettre à jour", "dyndns_provider_unreachable": "Impossible d’atteindre le fournisseur DynDNS {provider} : votre YunoHost n’est pas correctement connecté à Internet ou le serveur Dynette est en panne.", - "migration_0011_update_LDAP_schema": "Mise à jour du schéma LDAP...", "migrations_already_ran": "Ces migrations sont déjà effectuées : {ids}", "migrations_dependencies_not_satisfied": "Exécutez ces migrations : '{dependencies_id}', avant migration {id}.", "migrations_failed_to_load_migration": "Impossible de charger la migration {id} : {error}", @@ -450,8 +390,6 @@ "permission_creation_failed": "Impossible de créer l’autorisation '{permission}' : {error}", "permission_deleted": "Permission '{permission:s}' supprimée", "permission_deletion_failed": "Impossible de supprimer la permission '{permission}' : {error}", - "migration_description_0011_setup_group_permission": "Initialiser les groupes d’utilisateurs et autorisations pour les applications et les services", - "migration_0011_LDAP_update_failed": "Impossible de mettre à jour LDAP. Erreur : {error:s}", "group_already_exist": "Le groupe {group} existe déjà", "group_already_exist_on_system": "Le groupe {group} existe déjà dans les groupes système", "group_cannot_be_deleted": "Le groupe {group} ne peut pas être supprimé manuellement.", @@ -462,7 +400,6 @@ "log_user_group_create": "Créer le groupe '{}'", "log_user_permission_update": "Mise à jour des accès pour la permission '{}'", "log_user_permission_reset": "Réinitialiser la permission '{}'", - "migration_0011_failed_to_remove_stale_object": "Impossible de supprimer un objet périmé {dn} : {error}", "permission_already_allowed": "Le groupe '{group}' a déjà l’autorisation '{permission}' activée", "permission_already_disallowed": "Le groupe '{group}' a déjà l’autorisation '{permission}' désactivé", "permission_cannot_remove_main": "Supprimer une autorisation principale n’est pas autorisé", @@ -472,14 +409,12 @@ "group_cannot_edit_visitors": "Le groupe 'visiteurs' ne peut pas être édité manuellement. C’est un groupe spécial représentant les visiteurs anonymes", "group_cannot_edit_primary_group": "Le groupe '{group}' ne peut pas être édité manuellement. C’est le groupe principal destiné à ne contenir qu’un utilisateur spécifique.", "log_permission_url": "Mise à jour de l’URL associée à l’autorisation '{}'", - "migration_0011_slapd_config_will_be_overwritten": "Il semble que vous ayez modifié manuellement la configuration de slapd. Pour cette migration critique, YunoHost doit forcer la mise à jour de la configuration de slapd. Les fichiers originaux seront sauvegardés dans {conf_backup_folder}.", "permission_already_up_to_date": "L’autorisation n’a pas été mise à jour car les demandes d’ajout/suppression correspondent déjà à l’état actuel.", "permission_currently_allowed_for_all_users": "Cette autorisation est actuellement accordée à tous les utilisateurs en plus des autres groupes. Vous voudrez probablement soit supprimer l’autorisation 'all_users', soit supprimer les autres groupes auxquels il est actuellement autorisé.", "app_install_failed": "Impossible d’installer {app} : {error}", "app_install_script_failed": "Une erreur est survenue dans le script d’installation de l’application", "permission_require_account": "Permission {permission} n’a de sens que pour les utilisateurs ayant un compte et ne peut donc pas être activé pour les visiteurs.", "app_remove_after_failed_install": "Supprimer l’application après l’échec de l’installation...", - "diagnosis_display_tip_web": "Vous pouvez aller à la section Diagnostic (dans l’écran d’accueil) pour voir les problèmes rencontrés.", "diagnosis_cant_run_because_of_dep": "Impossible d’exécuter le diagnostic pour {category} alors qu’il existe des problèmes importants liés à {dep}.", "diagnosis_found_errors": "Trouvé {errors} problème(s) significatif(s) lié(s) à {category} !", "diagnosis_found_errors_and_warnings": "Trouvé {errors} problème(s) significatif(s) (et {warnings} (avertissement(s)) en relation avec {category} !", @@ -496,7 +431,6 @@ "diagnosis_basesystem_ynh_single_version": "{package} version : {version} ({repo})", "diagnosis_basesystem_ynh_main_version": "Le serveur utilise YunoHost {main_version} ({repo})", "diagnosis_basesystem_ynh_inconsistent_versions": "Vous exécutez des versions incohérentes des packages YunoHost ... très probablement en raison d’une mise à niveau échouée ou partielle.", - "diagnosis_display_tip_cli": "Vous pouvez exécuter 'yunohost diagnosis show --issues --human-readable' pour afficher les problèmes détectés.", "diagnosis_failed_for_category": "Échec du diagnostic pour la catégorie '{category}': {error}", "diagnosis_cache_still_valid": "(Le cache est encore valide pour le diagnostic {category}. Il ne sera pas re-diagnostiqué pour le moment!)", "diagnosis_ignored_issues": "(+ {nb_ignored} problème(s) ignoré(s))", @@ -522,9 +456,7 @@ "diagnosis_swap_notsomuch": "Le système ne dispose que de {total} de swap. Vous devez envisager d’avoir au moins {recommended} pour éviter les situations où le système manque de mémoire.", "diagnosis_swap_ok": "Le système dispose de {total} de swap !", "diagnosis_regenconf_manually_modified": "Le fichier de configuration {file} semble avoir été modifié manuellement.", - "diagnosis_regenconf_manually_modified_debian": "Le fichier de configuration {file} a été modifié manuellement par rapport à celui par défaut de Debian.", "diagnosis_regenconf_manually_modified_details": "C’est probablement OK si vous savez ce que vous faites ! YunoHost cessera de mettre à jour ce fichier automatiquement ... Mais attention, les mises à jour de YunoHost pourraient contenir d’importantes modifications recommandées. Si vous le souhaitez, vous pouvez inspecter les différences avec yunohost tools regen-conf {category} --dry-run --with-diff et forcer la réinitialisation à la configuration recommandée avec yunohost tools regen-conf {category} --force", - "diagnosis_regenconf_manually_modified_debian_details": "Cela peut probablement être OK, mais il faut garder un œil dessus …", "apps_catalog_init_success": "Système de catalogue d’applications initialisé !", "apps_catalog_failed_to_download": "Impossible de télécharger le catalogue des applications {apps_catalog} : {error}", "diagnosis_mail_outgoing_port_25_blocked": "Le port sortant 25 semble être bloqué. Vous devriez essayer de le débloquer dans le panneau de configuration de votre fournisseur de services Internet (ou hébergeur). En attendant, le serveur ne pourra pas envoyer des courriels à d’autres serveurs.", @@ -537,13 +469,11 @@ "diagnosis_description_systemresources": "Ressources système", "diagnosis_description_ports": "Exposition des ports", "diagnosis_description_regenconf": "Configurations système", - "diagnosis_description_security": "Contrôles de sécurité", "diagnosis_ports_could_not_diagnose": "Impossible de diagnostiquer si les ports sont accessibles de l'extérieur.", "diagnosis_ports_could_not_diagnose_details": "Erreur : {error}", "apps_catalog_updating": "Mise à jour du catalogue d’applications…", "apps_catalog_obsolete_cache": "Le cache du catalogue d'applications est vide ou obsolète.", "apps_catalog_update_success": "Le catalogue des applications a été mis à jour !", - "diagnosis_mail_ougoing_port_25_ok": "Le port sortant 25 n’est pas bloqué et le courrier électronique peut être envoyé à d’autres serveurs.", "diagnosis_description_mail": "E-mail", "diagnosis_ports_unreachable": "Le port {port} n’est pas accessible de l’extérieur.", "diagnosis_ports_ok": "Le port {port} est accessible de l’extérieur.", @@ -552,9 +482,7 @@ "diagnosis_http_ok": "Le domaine {domain} est accessible en HTTP depuis l’extérieur.", "diagnosis_http_unreachable": "Le domaine {domain} est inaccessible en HTTP depuis l’extérieur.", "diagnosis_unknown_categories": "Les catégories suivantes sont inconnues : {categories}", - "migration_description_0013_futureproof_apps_catalog_system": "Migrer vers le nouveau système de catalogue d’applications à l’épreuve du temps", "app_upgrade_script_failed": "Une erreur s’est produite durant l’exécution du script de mise à niveau de l’application", - "migration_description_0014_remove_app_status_json": "Supprimer les anciens fichiers d’application status.json", "diagnosis_services_running": "Le service {service} est en cours de fonctionnement !", "diagnosis_services_conf_broken": "La configuration est cassée pour le service {service} !", "diagnosis_ports_needed_by": "Rendre ce port accessible est nécessaire pour les fonctionnalités de type {category} (service {service})", @@ -571,7 +499,6 @@ "log_app_config_apply": "Appliquer la configuration à l’application '{}'", "diagnosis_never_ran_yet": "Il apparaît que le serveur a été installé récemment et qu’il n’y a pas encore eu de diagnostic. Vous devriez en lancer un depuis le webmin ou en utilisant 'yunohost diagnosis run' depuis la ligne de commande.", "diagnosis_description_web": "Web", - "diagnosis_basesystem_hardware_board": "Le modèle de carte du serveur est {model}", "diagnosis_basesystem_hardware": "L’architecture du serveur est {virt} {arch}", "group_already_exist_on_system_but_removing_it": "Le groupe {group} est déjà présent dans les groupes du système, mais YunoHost va le supprimer...", "certmanager_warning_subdomain_dns_record": "Le sous-domaine '{subdomain:s}' ne résout pas vers la même adresse IP que '{domain:s}'. Certaines fonctionnalités seront indisponibles tant que vous n’aurez pas corrigé cela et regénéré le certificat.", @@ -594,7 +521,6 @@ "diagnosis_mail_queue_unavailable_details": "Erreur : {error}", "diagnosis_mail_queue_too_big": "Trop d’e-mails en attente dans la file d'attente ({nb_pending} e-mails)", "global_settings_setting_smtp_allow_ipv6": "Autoriser l'utilisation d’IPv6 pour recevoir et envoyer du courrier", - "diagnosis_security_all_good": "Aucune vulnérabilité de sécurité critique n’a été trouvée.", "diagnosis_display_tip": "Pour voir les problèmes détectés, vous pouvez accéder à la section Diagnostic du webadmin ou exécuter « yunohost diagnosis show --issues --human-readable» à partir de la ligne de commande.", "diagnosis_ip_global": "IP globale : {global}", "diagnosis_ip_local": "IP locale : {local}", @@ -697,4 +623,4 @@ "domain_remove_confirm_apps_removal": "Le retrait de ce domaine retirera aussi ces applications :\n{apps}\n\nÊtes vous sûr de vouloir cela ? [{answers}]", "diagnosis_rootfstotalspace_critical": "Le système de fichiers racine ne fait que {space} ! Vous allez certainement le remplir très rapidement ! Il est recommandé d'avoir au moins 16 GB pour ce système de fichiers.", "diagnosis_rootfstotalspace_warning": "Le système de fichiers racine n'est que de {space}. Cela peut suffire, mais faites attention car vous risquez de les remplir rapidement... Il est recommandé d'avoir au moins 16 GB pour ce système de fichiers." -} +} \ No newline at end of file diff --git a/locales/hi.json b/locales/hi.json index 609464c8f..f84745264 100644 --- a/locales/hi.json +++ b/locales/hi.json @@ -22,7 +22,6 @@ "app_unsupported_remote_type": "एप्लीकेशन के लिए उन्सुपपोर्टेड रिमोट टाइप इस्तेमाल किया गया", "app_upgrade_failed": "{app:s} अपडेट करने में असमर्थ", "app_upgraded": "{app:s} अपडेट हो गयी हैं", - "ask_email": "ईमेल का पता", "ask_firstname": "नाम", "ask_lastname": "अंतिम नाम", "ask_main_domain": "मुख्य डोमेन", @@ -39,7 +38,6 @@ "backup_delete_error": "'{path:s}' डिलीट करने में असमर्थ", "backup_deleted": "इस बैकअप को डिलीट दिया गया है", "backup_hook_unknown": "'{hook:s}' यह बैकअप हुक नहीं मिला", - "backup_invalid_archive": "अवैध बैकअप आरचिव", "backup_nothings_done": "सेव करने के लिए कुछ नहीं", "backup_output_directory_forbidden": "निषिद्ध आउटपुट डायरेक्टरी। निम्न दिए गए डायरेक्टरी में बैकअप नहीं बन सकता /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var और /home/yunohost.backup/archives के सब-फोल्डर।", "backup_output_directory_not_empty": "आउटपुट डायरेक्टरी खाली नहीं है", diff --git a/locales/it.json b/locales/it.json index 1684963a7..8b557b077 100644 --- a/locales/it.json +++ b/locales/it.json @@ -3,11 +3,9 @@ "app_extraction_failed": "Impossibile estrarre i file di installazione", "app_not_installed": "Impossibile trovare l'applicazione {app:s} nell'elenco delle applicazioni installate: {all_apps}", "app_unknown": "Applicazione sconosciuta", - "ask_email": "Indirizzo email", "ask_password": "Password", "backup_archive_name_exists": "Il nome dell'archivio del backup è già esistente.", "backup_created": "Backup completo", - "backup_invalid_archive": "Archivio di backup non valido", "backup_output_directory_not_empty": "Dovresti scegliere una cartella di output vuota", "domain_created": "Dominio creato", "domain_exists": "Il dominio esiste già", @@ -74,9 +72,6 @@ "done": "Terminato", "domains_available": "Domini disponibili:", "downloading": "Scaricamento…", - "dyndns_cron_installed": "Cronjob DynDNS creato", - "dyndns_cron_remove_failed": "Impossibile rimuovere il cronjob DynDNS perchè: {error}", - "dyndns_cron_removed": "Cronjob DynDNS rimosso", "dyndns_ip_update_failed": "Impossibile aggiornare l'indirizzo IP in DynDNS", "dyndns_ip_updated": "Il tuo indirizzo IP è stato aggiornato su DynDNS", "dyndns_key_generating": "Generando la chiave DNS... Potrebbe richiedere del tempo.", @@ -85,8 +80,6 @@ "dyndns_registered": "Dominio DynDNS registrato", "dyndns_registration_failed": "Non è possibile registrare il dominio DynDNS: {error:s}", "dyndns_unavailable": "Il dominio {domain:s} non disponibile.", - "executing_command": "Esecuzione del comando '{command:s}'…", - "executing_script": "Esecuzione dello script '{script:s}'…", "extracting": "Estrazione...", "field_invalid": "Campo '{:s}' non valido", "firewall_reload_failed": "Impossibile ricaricare il firewall", @@ -106,9 +99,7 @@ "mailbox_used_space_dovecot_down": "La casella di posta elettronica Dovecot deve essere attivato se vuoi recuperare lo spazio usato dalla posta elettronica", "main_domain_change_failed": "Impossibile cambiare il dominio principale", "main_domain_changed": "Il dominio principale è stato cambiato", - "no_internet_connection": "Il server non è collegato a Internet", "not_enough_disk_space": "Non c'è abbastanza spazio libero in '{path:s}'", - "package_unknown": "Pacchetto '{pkgname}' sconosciuto", "packages_upgrade_failed": "Impossibile aggiornare tutti i pacchetti", "pattern_backup_archive_name": "Deve essere un nome di file valido di massimo 30 caratteri di lunghezza, con caratteri alfanumerici e \"-_.\" come unica punteggiatura", "pattern_domain": "Deve essere un nome di dominio valido (es. il-mio-dominio.org)", @@ -159,13 +150,11 @@ "user_unknown": "Utente sconosciuto: {user:s}", "user_updated": "Info dell'utente cambiate", "yunohost_already_installed": "YunoHost è già installato", - "yunohost_ca_creation_failed": "Impossibile creare una certificate authority", "yunohost_configured": "YunoHost ora è configurato", "yunohost_installing": "Installazione di YunoHost...", "yunohost_not_installed": "YunoHost non è correttamente installato. Esegui 'yunohost tools postinstall'", "domain_cert_gen_failed": "Impossibile generare il certificato", "certmanager_attempt_to_replace_valid_cert": "Stai provando a sovrascrivere un certificato buono e valido per il dominio {domain:s}! (Usa --force per ignorare)", - "certmanager_domain_unknown": "Dominio {domain:s} sconosciuto", "certmanager_domain_cert_not_selfsigned": "Il certificato per il dominio {domain:s} non è auto-firmato. Sei sicuro di volere sostituirlo? (Usa '--force')", "certmanager_certificate_fetching_or_enabling_failed": "Il tentativo di usare il nuovo certificato per {domain:s} non funziona...", "certmanager_attempt_to_renew_nonLE_cert": "Il certificato per il dominio {domain:s} non è emesso da Let's Encrypt. Impossibile rinnovarlo automaticamente!", @@ -182,14 +171,12 @@ "app_upgrade_app_name": "Aggiornamento di {app}...", "app_upgrade_some_app_failed": "Alcune applicazioni non possono essere aggiornate", "backup_abstract_method": "Questo metodo di backup deve essere ancora implementato", - "backup_applying_method_borg": "Invio di tutti i file del backup nel deposito borg-backup...", "backup_applying_method_copy": "Copiando tutti i files nel backup...", "backup_applying_method_custom": "Chiamando il metodo di backup personalizzato '{method:s}'...", "backup_applying_method_tar": "Creando l'archivio TAR del backup...", "backup_archive_system_part_not_available": "La parte di sistema '{part:s}' non è disponibile in questo backup", "backup_archive_writing_error": "Impossibile aggiungere i file '{source:s}' (indicati nell'archivio '{dest:s}') al backup nell'archivio compresso '{archive:s}'", "backup_ask_for_copying_if_needed": "Vuoi effettuare il backup usando {size:s}MB temporaneamente? (È necessario usare questo sistema poiché alcuni file non possono essere preparati in un modo più efficiente)", - "backup_borg_not_implemented": "Il metodo di backup Borg non è ancora stato implementato", "backup_cant_mount_uncompress_archive": "Impossibile montare in modalità sola lettura la cartella di archivio non compressa", "backup_copying_to_organize_the_archive": "Copiando {size:s}MB per organizzare l'archivio", "backup_couldnt_bind": "Impossibile legare {src:s} a {dest:s}.", @@ -197,12 +184,10 @@ "backup_csv_creation_failed": "Impossibile creare il file CVS richiesto per le operazioni di ripristino", "backup_custom_backup_error": "Il metodo di backup personalizzato è fallito allo step 'backup'", "backup_custom_mount_error": "Il metodo di backup personalizzato è fallito allo step 'mount'", - "backup_method_borg_finished": "Backup in borg terminato", "backup_method_copy_finished": "Copia di backup terminata", "backup_method_custom_finished": "Metodo di backup personalizzato '{method:s}' terminato", "backup_method_tar_finished": "Archivio TAR di backup creato", "backup_no_uncompress_archive_dir": "La cartella di archivio non compressa non esiste", - "backup_php5_to_php7_migration_may_fail": "Conversione del tuo archivio per supportare php7 non riuscita, le tue app php potrebbero fallire in fase di ripristino (motivo: {error:s})", "backup_system_part_failed": "Impossibile creare il backup della parte di sistema '{part:s}'", "backup_unable_to_organize_files": "Impossibile organizzare i file nell'archivio con il metodo veloce", "backup_with_no_backup_script_for_app": "L'app {app:s} non ha script di backup. Ignorata.", @@ -231,16 +216,10 @@ "password_too_simple_2": "La password deve essere lunga almeno 8 caratteri e contenere numeri, maiuscole e minuscole", "password_too_simple_3": "La password deve essere lunga almeno 8 caratteri e contenere numeri, maiuscole e minuscole e simboli", "password_too_simple_4": "La password deve essere lunga almeno 12 caratteri e contenere numeri, maiuscole e minuscole", - "users_available": "Utenti disponibili:", - "yunohost_ca_creation_success": "Autorità di certificazione locale creata.", "app_action_cannot_be_ran_because_required_services_down": "I seguenti servizi dovrebbero essere in funzione per completare questa azione: {services}. Prova a riavviarli per proseguire (e possibilmente cercare di capire come ma non funzionano più).", "backup_output_symlink_dir_broken": "La tua cartella d'archivio '{path:s}' è un link simbolico interrotto. Probabilmente hai dimenticato di montare o montare nuovamente il supporto al quale punta il link.", - "certmanager_conflicting_nginx_file": "Impossibile preparare il dominio per il controllo ACME: il file di configurazione nginx {filepath:s} è in conflitto e dovrebbe essere prima rimosso", - "certmanager_couldnt_fetch_intermediate_cert": "Tempo scaduto durante il tentativo di recupero di un certificato intermedio da Let's Encrypt. Installazione/rinnovo non riuscito - per favore riprova più tardi.", "certmanager_domain_dns_ip_differs_from_public_ip": "I record DNS per il dominio '{domain:s}' è diverso dall'IP di questo server. Controlla la sezione (basic) 'Record DNS' nella diagnosi per maggiori informazioni. Se hai modificato recentemente il tuo valore A, attendi che si propaghi (esistono online alcuni siti per il controllo della propagazione DNS). (Se sai cosa stai facendo, usa '--no-checks' per disattivare i controlli.)", - "certmanager_error_no_A_record": "Nessun valore DNS 'A' trovato per {domain:s}. Devi far puntare il tuo nome di dominio verso la tua macchina per essere in grado di installare un certificato Let's Encrypt! (Se sai cosa stai facendo, usa --no-checks per disabilitare quei controlli.)", "certmanager_hit_rate_limit": "Troppi certificati già rilasciati per questa esatta serie di domini {domain:s} recentemente. Per favore riprova più tardi. Guarda https://letsencrypt.org/docs/rate-limits/ per maggiori dettagli", - "certmanager_http_check_timeout": "Tempo scaduto durante il tentativo di contatto del tuo server a se stesso attraverso HTTP utilizzando l'indirizzo IP pubblico (dominio {domain:s} con ip {ip:s}). Potresti avere un problema di hairpinning o il firewall/router davanti al tuo server non è correttamente configurato.", "certmanager_no_cert_file": "Impossibile leggere il file di certificato per il dominio {domain:s} (file: {file:s})", "certmanager_self_ca_conf_file_not_found": "File di configurazione non trovato per l'autorità di auto-firma (file: {file:s})", "certmanager_unable_to_parse_self_CA_name": "Impossibile analizzare il nome dell'autorità di auto-firma (file: {file:s})", @@ -262,11 +241,7 @@ "global_settings_cant_write_settings": "Scrittura del file delle impostazioni non riuscita, motivo: {reason:s}", "global_settings_key_doesnt_exists": "La chiave '{settings_key:s}' non esiste nelle impostazioni globali, puoi vedere tutte le chiavi disponibili eseguendo 'yunohost settings list'", "global_settings_reset_success": "Le impostazioni precedenti sono state salvate in {path:s}", - "global_settings_setting_example_bool": "Esempio di opzione booleana", - "global_settings_setting_example_enum": "Esempio di opzione enum", "already_up_to_date": "Niente da fare. Tutto è già aggiornato.", - "global_settings_setting_example_int": "Esempio di opzione int", - "global_settings_setting_example_string": "Esempio di opzione string", "global_settings_setting_security_nginx_compatibility": "Bilanciamento tra compatibilità e sicurezza per il server web NGIX. Riguarda gli algoritmi di cifratura (e altri aspetti legati alla sicurezza)", "global_settings_setting_security_password_admin_strength": "Complessità della password di amministratore", "global_settings_setting_security_password_user_strength": "Complessità della password utente", @@ -276,7 +251,6 @@ "global_settings_unknown_type": "Situazione inaspettata, l'impostazione {setting:s} sembra essere di tipo {unknown_type:s} ma non è un tipo supportato dal sistema.", "good_practices_about_admin_password": "Stai per definire una nuova password di amministratore. La password deve essere almeno di 8 caratteri - anche se è buona pratica utilizzare password più lunghe (es. una frase, una serie di parole) e/o utilizzare vari tipi di caratteri (maiuscole, minuscole, numeri e simboli).", "log_corrupted_md_file": "Il file dei metadati YAML associato con i registri è danneggiato: '{md_file}'\nErrore: {error}", - "log_category_404": "La categoria di registrazione '{category}' non esiste", "log_link_to_log": "Registro completo di questa operazione: '{desc}'", "log_help_to_get_log": "Per vedere il registro dell'operazione '{desc}', usa il comando 'yunohost log show {name}{name}'", "global_settings_setting_security_postfix_compatibility": "Bilanciamento tra compatibilità e sicurezza per il server Postfix. Riguarda gli algoritmi di cifratura (e altri aspetti legati alla sicurezza)", @@ -311,29 +285,6 @@ "log_tools_shutdown": "Spegni il tuo server", "log_tools_reboot": "Riavvia il tuo server", "mail_unavailable": "Questo indirizzo email è riservato e dovrebbe essere automaticamente assegnato al primo utente", - "migrate_tsig_end": "Migrazione a hmac-sha512 terminata", - "migrate_tsig_failed": "Migrazione del dominio dyndns {domain} verso hmac-sha512 fallita, torno indetro. Errore: {error_code} - {error}", - "migrate_tsig_start": "Trovato un algoritmo di chiave non abbastanza sicuro per la firma TSIG del dominio '{domain}', inizio della migrazione verso la più sicura hmac-sha512", - "migrate_tsig_wait": "Aspetta 3 minuti che il server dyndns prenda la nuova chiave in gestione…", - "migrate_tsig_wait_2": "2 minuti…", - "migrate_tsig_wait_3": "1 minuto…", - "migrate_tsig_wait_4": "30 secondi…", - "migrate_tsig_not_needed": "Non sembra tu stia utilizzando un dominio dyndns, quindi non è necessaria nessuna migrazione!", - "migration_description_0001_change_cert_group_to_sslcert": "Cambia permessi del gruppo di certificati da 'metronome' a 'ssl-cert'", - "migration_description_0002_migrate_to_tsig_sha256": "Migliora la sicurezza del TSIG dyndns utilizzando SHA512 invece di MD5", - "migration_description_0003_migrate_to_stretch": "Aggiorna il sistema a Debian Stretch e YunoHost 3.0", - "migration_description_0004_php5_to_php7_pools": "Riconfigura le PHP pools ad utilizzare PHP 7 invece di 5", - "migration_description_0005_postgresql_9p4_to_9p6": "Migra i database da postgresql 9.4 a 9.6", - "migration_description_0006_sync_admin_and_root_passwords": "Sincronizza password di amministratore e root", - "migration_description_0010_migrate_to_apps_json": "Rimuovi gli elenchi di app deprecati ed usa invece il nuovo elenco unificato 'apps.json'", - "migration_0003_start": "Migrazione a Stretch iniziata. I registri saranno disponibili in {logfile}.", - "migration_0003_patching_sources_list": "Sistemando il file sources.lists…", - "migration_0003_main_upgrade": "Iniziando l'aggiornamento principale…", - "migration_0003_fail2ban_upgrade": "Iniziando l'aggiornamento di fail2ban…", - "migration_0003_restoring_origin_nginx_conf": "Il tuo file /etc/nginx/nginx.conf è stato modificato in qualche modo. La migrazione lo riporterà al suo stato originale… Il file precedente sarà disponibile come {backup_dest}.", - "migration_0003_yunohost_upgrade": "Iniziando l'aggiornamento dei pacchetti yunohost… La migrazione terminerà, ma l'aggiornamento attuale avverrà subito dopo. Dopo che l'operazione sarà completata, probabilmente dovrai riaccedere all'interfaccia di amministrazione.", - "migration_0003_not_jessie": "La distribuzione attuale non è Jessie!", - "migration_0003_system_not_fully_up_to_date": "Il tuo sistema non è completamente aggiornato. Per favore prima esegui un aggiornamento normale prima di migrare a stretch.", "this_action_broke_dpkg": "Questa azione ha danneggiato dpkg/APT (i gestori di pacchetti del sistema)... Puoi provare a risolvere questo problema connettendoti via SSH ed eseguendo `sudo apt install --fix-broken` e/o `sudo dpkg --configure -a`.", "app_action_broke_system": "Questa azione sembra avere rotto questi servizi importanti: {services}", "app_remove_after_failed_install": "Rimozione dell'applicazione a causa del fallimento dell'installazione...", @@ -365,7 +316,6 @@ "diagnosis_basesystem_ynh_single_version": "Versione {package}: {version} ({repo})", "diagnosis_basesystem_kernel": "Il server sta eseguendo Linux kernel {kernel_version}", "diagnosis_basesystem_host": "Il server sta eseguendo Debian {debian_version}", - "diagnosis_basesystem_hardware_board": "Il modello della scheda server è {model}", "diagnosis_basesystem_hardware": "L'architettura hardware del server è {virt} {arch}", "certmanager_warning_subdomain_dns_record": "Il sottodominio '{subdomain:s}' non si risolve nello stesso indirizzo IP di '{domain:s}'. Alcune funzioni non saranno disponibili finchè questa cosa non verrà sistemata e rigenerato il certificato.", "app_label_deprecated": "Questo comando è deprecato! Utilizza il nuovo comando 'yunohost user permission update' per gestire la label dell'app.", @@ -574,12 +524,6 @@ "migration_0015_main_upgrade": "Inizio l'aggiornamento principale...", "migration_0015_patching_sources_list": "Applico le patch a sources.lists...", "migration_0015_start": "Inizio migrazione a Buster", - "migration_0011_failed_to_remove_stale_object": "Impossibile rimuovere l'oggetto {dn}: {error}", - "migration_0011_update_LDAP_schema": "Aggiornado lo schema LDAP...", - "migration_0011_update_LDAP_database": "Aggiornando il database LDAP...", - "migration_0011_migrate_permission": "Migrando permessi dalle impostazioni delle app a LDAP...", - "migration_0011_LDAP_update_failed": "Impossibile aggiornare LDAP. Errore: {error:s}", - "migration_0011_create_group": "Sto creando un gruppo per ogni utente...", "migration_description_0019_extend_permissions_features": "Estendi il sistema di gestione dei permessi app", "migration_description_0018_xtable_to_nftable": "Migra le vecchie regole di traffico network sul nuovo sistema nftable", "migration_description_0017_postgresql_9p6_to_11": "Migra i database da PostgreSQL 9.6 a 11", @@ -679,4 +623,4 @@ "domain_remove_confirm_apps_removal": "Rimuovere questo dominio rimuoverà anche le seguenti applicazioni:\n{apps}\n\nSei sicuro di voler continuare? [{answers}]", "diagnosis_rootfstotalspace_critical": "La radice del filesystem ha un totale di solo {space}, ed è piuttosto preoccupante! Probabilmente consumerai tutta la memoria molto velocemente! Raccomandiamo di avere almeno 16 GB per la radice del filesystem.", "diagnosis_rootfstotalspace_warning": "La radice del filesystem ha un totale di solo {space}. Potrebbe non essere un problema, ma stai attento perché potresti consumare tutta la memoria velocemente... Raccomandiamo di avere almeno 16 GB per la radice del filesystem." -} +} \ No newline at end of file diff --git a/locales/nb_NO.json b/locales/nb_NO.json index 66cefad04..295ec5070 100644 --- a/locales/nb_NO.json +++ b/locales/nb_NO.json @@ -9,7 +9,6 @@ "app_argument_invalid": "Velg en gydlig verdi for argumentet '{name:s}': {error:s}", "app_argument_required": "Argumentet '{name:s}' er påkrevd", "app_id_invalid": "Ugyldig program-ID", - "dyndns_cron_remove_failed": "Kunne ikke fjerne cron-jobb for DynDNS: {error}", "dyndns_key_not_found": "Fant ikke DNS-nøkkel for domenet", "app_not_correctly_installed": "{app:s} ser ikke ut til å ha blitt installert på riktig måte", "dyndns_provider_unreachable": "Kunne ikke nå DynDNS-tilbyder {provider}: Enten har du ikke satt opp din YunoHost rett, dynette-tjeneren er nede, eller du mangler nett.", @@ -35,7 +34,6 @@ "dyndns_could_not_check_provide": "Kunne ikke sjekke om {provider:s} kan tilby {domain:s}.", "dyndns_could_not_check_available": "Kunne ikke sjekke om {domain:s} er tilgjengelig på {provider:s}.", "mail_domain_unknown": "Ukjent e-postadresse for domenet '{domain:s}'", - "migrate_tsig_wait_2": "2 min…", "log_remove_on_failed_restore": "Fjern '{}' etter mislykket gjenoppretting fra sikkerhetskopiarkiv", "log_letsencrypt_cert_install": "Installer et Let's Encrypt-sertifikat på '{}'-domenet", "log_letsencrypt_cert_renew": "Forny '{}'-Let's Encrypt-sertifikat", @@ -54,27 +52,19 @@ "app_start_remove": "Fjerner programmet '{app}'…", "app_start_backup": "Samler inn filer for sikkerhetskopiering for {app}…", "backup_applying_method_copy": "Kopier alle filer til sikkerhetskopi…", - "backup_borg_not_implemented": "Borg-sikkerhetskopimetoden er ikke implementert enda", "backup_creation_failed": "Kunne ikke opprette sikkerhetskopiarkiv", "backup_couldnt_bind": "Kunne ikke binde {src:s} til {dest:s}.", "backup_csv_addition_failed": "Kunne ikke legge til filer for sikkerhetskopi inn i CSV-filen", "backup_deleted": "Sikkerhetskopi slettet", "backup_no_uncompress_archive_dir": "Det finnes ingen slik utpakket arkivmappe", "backup_delete_error": "Kunne ikke slette '{path:s}'", - "certmanager_domain_unknown": "Ukjent domene '{domain:s}'", "certmanager_cert_signing_failed": "Kunne ikke signere det nye sertifikatet", - "executing_command": "Kjører kommendoen '{command:s}'…", - "executing_script": "Kjører skriptet '{script:s}'…", "extracting": "Pakker ut…", "log_domain_add": "Legg til '{}'-domenet i systemoppsett", "log_domain_remove": "Fjern '{}'-domenet fra systemoppsett", "log_dyndns_subscribe": "Abonner på YunoHost-underdomenet '{}'", "log_dyndns_update": "Oppdater IP-adressen tilknyttet ditt YunoHost-underdomene '{}'", - "migrate_tsig_wait_3": "1 min…", - "migrate_tsig_wait_4": "30 sekunder…", - "backup_invalid_archive": "Dette er ikke et sikkerhetskopiarkiv", "backup_nothings_done": "Ingenting å lagre", - "backup_method_borg_finished": "Sikkerhetskopi inn i Borg fullført", "field_invalid": "Ugyldig felt '{:s}'", "firewall_reloaded": "Brannmur gjeninnlastet", "log_app_change_url": "Endre nettadresse for '{}'-programmet", @@ -94,8 +84,6 @@ "domain_creation_failed": "Kunne ikke opprette domene", "domain_dyndns_root_unknown": "Ukjent DynDNS-rotdomene", "domain_unknown": "Ukjent domene", - "dyndns_cron_installed": "Opprettet cron-jobb for DynDNS", - "dyndns_cron_removed": "Fjernet cron-jobb for DynDNS", "dyndns_ip_update_failed": "Kunne ikke oppdatere IP-adresse til DynDNS", "dyndns_ip_updated": "Oppdaterte din IP på DynDNS", "dyndns_key_generating": "Oppretter DNS-nøkkel… Dette kan ta en stund.", @@ -112,13 +100,11 @@ "log_user_group_update": "Oppdater '{}' gruppe", "ldap_init_failed_to_create_admin": "LDAP-igangsettelse kunne ikke opprette admin-bruker", "ldap_initialized": "LDAP-igangsatt", - "migration_description_0003_migrate_to_stretch": "Oppgrader systemet til Debian Stretch og YunoHost 3.0", "app_unknown": "Ukjent program", "app_upgrade_app_name": "Oppgraderer {app}…", "app_upgrade_failed": "Kunne ikke oppgradere {app:s}", "app_upgrade_some_app_failed": "Noen programmer kunne ikke oppgraderes", "app_upgraded": "{app:s} oppgradert", - "ask_email": "E-postadresse", "ask_firstname": "Fornavn", "ask_lastname": "Etternavn", "ask_main_domain": "Hoveddomene", @@ -130,7 +116,6 @@ "domain_deleted": "Domene slettet", "domain_deletion_failed": "Kunne ikke slette domene", "domain_dyndns_already_subscribed": "Du har allerede abonnement på et DynDNS-domene", - "log_category_404": "Loggkategorien '{category}' finnes ikke", "log_link_to_log": "Full logg for denne operasjonen: '{desc}'", "log_help_to_get_log": "For å vise loggen for operasjonen '{desc}', bruk kommandoen 'yunohost log show {name}{name}'", "log_user_create": "Legg til '{}' bruker", diff --git a/locales/ne.json b/locales/ne.json index 72c4c8537..9bc5c0bfa 100644 --- a/locales/ne.json +++ b/locales/ne.json @@ -1,3 +1,3 @@ { "password_too_simple_1": "पासवर्ड कम्तिमा characters अक्षर लामो हुनु आवश्यक छ" -} +} \ No newline at end of file diff --git a/locales/nl.json b/locales/nl.json index 23ad8d0cb..811c006b6 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -15,14 +15,12 @@ "app_unknown": "Onbekende app", "app_upgrade_failed": "Kan app {app:s} niet updaten", "app_upgraded": "{app:s} succesvol geüpgraded", - "ask_email": "Email-adres", "ask_firstname": "Voornaam", "ask_lastname": "Achternaam", "ask_new_admin_password": "Nieuw administratorwachtwoord", "ask_password": "Wachtwoord", "backup_archive_name_exists": "Een backuparchief met dezelfde naam bestaat al", "backup_cleaning_failed": "Kan tijdelijke backup map niet leeg maken", - "backup_invalid_archive": "Ongeldig backup archief", "backup_output_directory_not_empty": "Doelmap is niet leeg", "custom_app_url_required": "U moet een URL opgeven om uw aangepaste app {app:s} bij te werken", "domain_cert_gen_failed": "Kan certificaat niet genereren", @@ -37,18 +35,15 @@ "domain_unknown": "Onbekend domein", "done": "Voltooid", "downloading": "Downloaden...", - "dyndns_cron_remove_failed": "De cron-job voor DynDNS kon niet worden verwijderd", "dyndns_ip_update_failed": "Kan het IP adres niet updaten bij DynDNS", "dyndns_ip_updated": "IP adres is aangepast bij DynDNS", "dyndns_key_generating": "DNS sleutel word aangemaakt, wacht een moment...", "dyndns_unavailable": "DynDNS subdomein is niet beschikbaar", - "executing_script": "Script uitvoeren...", "extracting": "Uitpakken...", "installation_complete": "Installatie voltooid", "installation_failed": "Installatie gefaald", "ldap_initialized": "LDAP is klaar voor gebruik", "mail_alias_remove_failed": "Kan mail-alias '{mail:s}' niet verwijderen", - "no_internet_connection": "Server is niet verbonden met het internet", "pattern_email": "Moet een geldig emailadres bevatten (bv. abc@example.org)", "pattern_mailbox_quota": "Mailbox quota moet een waarde bevatten met b/k/M/G/T erachter of 0 om geen quota in te stellen", "pattern_password": "Wachtwoord moet tenminste 3 karakters lang zijn", @@ -122,4 +117,4 @@ "app_start_backup": "Bestanden aan het verzamelen voor de backup van {app}...", "app_start_restore": "{app} herstellen...", "app_upgrade_several_apps": "De volgende apps zullen worden geüpgraded: {apps}" -} +} \ No newline at end of file diff --git a/locales/oc.json b/locales/oc.json index 4a4466101..efcab1c2a 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -16,7 +16,6 @@ "app_upgrade_failed": "Impossible d’actualizar {app:s} : {error}", "app_upgrade_some_app_failed": "D’aplicacions se pòdon pas actualizar", "app_upgraded": "{app:s} es estada actualizada", - "ask_email": "Adreça de corrièl", "ask_firstname": "Prenom", "ask_lastname": "Nom", "ask_main_domain": "Domeni màger", @@ -54,8 +53,6 @@ "backup_delete_error": "Supression impossibla de « {path:s} »", "backup_deleted": "La salvagarda es estada suprimida", "backup_hook_unknown": "Script de salvagarda « {hook:s} » desconegut", - "backup_invalid_archive": "Aquò es pas un archiu de salvagarda", - "backup_method_borg_finished": "La salvagarda dins Borg es acabada", "backup_method_copy_finished": "La còpia de salvagarda es acabada", "backup_method_tar_finished": "L’archiu TAR de la salvagarda es estat creat", "backup_output_directory_not_empty": "Devètz causir un dorsièr de sortida void", @@ -65,7 +62,6 @@ "app_requirements_unmeet": "Las condicions requesidas per {app} son pas complidas, lo paquet {pkgname} ({version}) deu èsser {spec}", "backup_abstract_method": "Aqueste metòde de salvagarda es pas encara implementat", "backup_applying_method_custom": "Crida del metòde de salvagarda personalizat « {method:s} »...", - "backup_borg_not_implemented": "Lo metòde de salvagarda Bord es pas encara implementat", "backup_couldnt_bind": "Impossible de ligar {src:s} amb {dest:s}.", "backup_csv_addition_failed": "Impossible d’ajustar de fichièrs a la salvagarda dins lo fichièr CSV", "backup_custom_backup_error": "Fracàs del metòde de salvagarda personalizat a l’etapa « backup »", @@ -85,7 +81,6 @@ "yunohost_already_installed": "YunoHost es ja installat", "yunohost_configured": "YunoHost es estat configurat", "yunohost_installing": "Installacion de YunoHost…", - "backup_applying_method_borg": "Mandadís de totes los fichièrs a la salvagarda dins lo repertòri borg-backup…", "backup_csv_creation_failed": "Creacion impossibla del fichièr CSV necessari a las operacions futuras de restauracion", "backup_output_symlink_dir_broken": "Vòstre repertòri d’archiu « {path:s} » es un ligam simbolic copat. Saique oblidèretz de re/montar o de connectar supòrt.", "backup_with_no_backup_script_for_app": "L’aplicacion {app:s} a pas cap de script de salvagarda. I fasèm pas cas.", @@ -100,7 +95,6 @@ "certmanager_domain_cert_not_selfsigned": "Lo certificat pel domeni {domain:s} es pas auto-signat. Volètz vertadièrament lo remplaçar ? (Utilizatz « --force » per o far)", "certmanager_domain_dns_ip_differs_from_public_ip": "L’enregistrament DNS « A » pel domeni {domain:s} es diferent de l’adreça IP d’aqueste servidor. Se fa pauc qu’avètz modificat l’enregistrament « A », mercés d’esperar l’espandiment (qualques verificadors d’espandiment son disponibles en linha). (Se sabètz çò que fasèm, utilizatz --no-checks per desactivar aqueles contraròtles)", "certmanager_domain_http_not_working": "Sembla que lo domeni {domain:s} es pas accessible via HTTP. Mercés de verificar que las configuracions DNS e NGINK son corrèctas", - "certmanager_domain_unknown": "Domeni desconegut « {domain:s} »", "certmanager_no_cert_file": "Lectura impossibla del fichièr del certificat pel domeni {domain:s} (fichièr : {file:s})", "certmanager_self_ca_conf_file_not_found": "Impossible de trobar lo fichièr de configuracion per l’autoritat del certificat auto-signat (fichièr : {file:s})", "certmanager_unable_to_parse_self_CA_name": "Analisi impossibla del nom de l’autoritat del certificat auto-signat (fichièr : {file:s})", @@ -119,9 +113,6 @@ "done": "Acabat", "downloading": "Telecargament…", "dyndns_could_not_check_provide": "Impossible de verificar se {provider:s} pòt provesir {domain:s}.", - "dyndns_cron_installed": "Tasca cron pel domeni DynDNS creada", - "dyndns_cron_remove_failed": "Impossible de levar la tasca cron pel domeni DynDNS : {error}", - "dyndns_cron_removed": "Tasca cron pel domeni DynDNS levada", "dyndns_ip_update_failed": "Impossible d’actualizar l’adreça IP sul domeni DynDNS", "dyndns_ip_updated": "Vòstra adreça IP actualizada pel domeni DynDNS", "dyndns_key_generating": "La clau DNS es a se generar… pòt trigar una estona.", @@ -136,29 +127,15 @@ "global_settings_cant_open_settings": "Fracàs de la dobertura del fichièr de configuracion, rason : {reason:s}", "global_settings_key_doesnt_exists": "La clau « {settings_key:s} » existís pas dins las configuracions globalas, podètz veire totas las claus disponiblas en executant « yunohost settings list »", "global_settings_reset_success": "Configuracion precedenta ara salvagarda dins {path:s}", - "global_settings_setting_example_bool": "Exemple d’opcion booleana", "global_settings_unknown_setting_from_settings_file": "Clau desconeguda dins los paramètres : {setting_key:s}, apartada e salvagardada dins /etc/yunohost/settings-unknown.json", "installation_failed": "Quicòm a trucat e l’installacion a pas reüssit", "ldap_initialized": "L’annuari LDAP es inicializat", "main_domain_change_failed": "Modificacion impossibla del domeni màger", "main_domain_changed": "Lo domeni màger es estat modificat", - "migrate_tsig_end": "La migracion cap a HMAC-SHA512 es acabada", - "migrate_tsig_wait_2": "2 minutas…", - "migrate_tsig_wait_3": "1 minuta…", - "migrate_tsig_wait_4": "30 segondas…", - "migration_description_0002_migrate_to_tsig_sha256": "Melhora la seguretat de DynDNS TSIG en utilizar SHA512 allòc de MD5", - "migration_description_0003_migrate_to_stretch": "Mesa a nivèl del sistèma cap a Debian Stretch e YunoHost 3.0", - "migration_0003_start": "Aviada de la migracion cap a Stretech. Los jornals seràn disponibles dins {logfile}.", - "migration_0003_patching_sources_list": "Petaçatge de sources.lists…", - "migration_0003_main_upgrade": "Aviada de la mesa a nivèl màger…", - "migration_0003_fail2ban_upgrade": "Aviada de la mesa a nivèl de Fail2Ban…", - "migration_0003_not_jessie": "La distribucion Debian actuala es pas Jessie !", "migrations_cant_reach_migration_file": "Impossible d’accedir als fichièrs de migracion amb lo camin %s", "migrations_list_conflict_pending_done": "Podètz pas utilizar --previous e --done a l’encòp.", "migrations_loading_migration": "Cargament de la migracion {id}…", "migrations_no_migrations_to_run": "Cap de migracion de lançar", - "no_internet_connection": "Lo servidor es pas connectat a Internet", - "package_unknown": "Paquet « {pkgname} » desconegut", "packages_upgrade_failed": "Actualizacion de totes los paquets impossibla", "pattern_domain": "Deu èsser un nom de domeni valid (ex : mon-domeni.org)", "pattern_email": "Deu èsser una adreça electronica valida (ex : escais@domeni.org)", @@ -177,11 +154,7 @@ "certmanager_attempt_to_replace_valid_cert": "Sètz a remplaçar un certificat corrècte e valid pel domeni {domain:s} ! (Utilizatz --force per cortcircuitar)", "certmanager_cert_renew_success": "Renovèlament capitat d’un certificat Let’s Encrypt pel domeni « {domain:s} »", "certmanager_certificate_fetching_or_enabling_failed": "Sembla qu’utilizar lo nòu certificat per {domain:s} fonciona pas...", - "certmanager_conflicting_nginx_file": "Impossible de preparar lo domeni pel desfís ACME : lo fichièr de configuracion NGINX {filepath:s} es en conflicte e deu èsser levat d’en primièr", - "certmanager_couldnt_fetch_intermediate_cert": "Expiracion del relambi pendent l’ensag de recuperacion del certificat intermediari dins de Let’s Encrypt. L’installacion / lo renovèlament es estat interromput - tornatz ensajar mai tard.", - "certmanager_error_no_A_record": "Cap d’enregistrament DNS « A » pas trobat per {domain:s}. Vos cal indicar que lo nom de domeni mene a vòstra maquina per poder installar un certificat Let’S Encrypt ! (Se sabètz çò que fasètz, utilizatz --no-checks per desactivar las verificacions.)", "certmanager_hit_rate_limit": "Tròp de certificats son ja estats demandats recentament per aqueste ensem de domeni {domain:s}. Mercés de tornar ensajar mai tard. Legissètz https://letsencrypt.org/docs/rate-limits/ per mai detalhs", - "certmanager_http_check_timeout": "Expiracion del relambi d’ensag del servidor de se contactar via HTTP amb son adreça IP publica {domain:s} amb l’adreça {ip:s}. Coneissètz benlèu de problèmas d’hairpinning o lo parafuòc/router amont de vòstre servidor es mal configurat.", "domain_dns_conf_is_just_a_recommendation": "Aqueste pagina mòstra la configuracion *recomandada*. Non configura *pas* lo DNS per vos. Sètz responsable de la configuracion de vòstra zòna DNS en çò de vòstre registrar DNS amb aquesta recomandacion.", "domain_dyndns_already_subscribed": "Avètz ja soscrich a un domeni DynDNS", "domain_uninstall_app_first": "Una o mantuna aplicacions son installadas sus aqueste domeni. Mercés de las desinstallar d’en primièr abans de suprimir aqueste domeni", @@ -191,9 +164,6 @@ "global_settings_bad_choice_for_enum": "La valor del paramètre {setting:s} es incorrècta. Recebut : {choice:s}, mas las opcions esperadas son : {available_choices:s}", "global_settings_bad_type_for_setting": "Lo tipe del paramètre {setting:s} es incorrècte, recebut : {received_type:s}, esperat {expected_type:s}", "global_settings_cant_write_settings": "Fracàs de l’escritura del fichièr de configuracion, rason : {reason:s}", - "global_settings_setting_example_enum": "Exemple d’opcion de tipe enumeracion", - "global_settings_setting_example_int": "Exemple d’opcion de tipe entièr", - "global_settings_setting_example_string": "Exemple d’opcion de tipe cadena", "global_settings_unknown_type": "Situacion inesperada, la configuracion {setting:s} sembla d’aver lo tipe {unknown_type:s} mas es pas un tipe pres en carga pel sistèma.", "hook_exec_failed": "Fracàs de l’execucion del script : « {path:s} »", "hook_exec_not_terminated": "Lo escript « {path:s} » a pas acabat corrèctament", @@ -202,12 +172,6 @@ "ldap_init_failed_to_create_admin": "L’inicializacion de LDAP a pas pogut crear l’utilizaire admin", "mail_domain_unknown": "Lo domeni de corrièl « {domain:s} » es desconegut", "mailbox_used_space_dovecot_down": "Lo servici corrièl Dovecot deu èsser aviat, se volètz conéisser l’espaci ocupat per la messatjariá", - "migrate_tsig_failed": "La migracion del domeni DynDNS {domain} cap a HMAC-SHA512 a pas capitat, anullacion de las modificacions. Error : {error_code} - {error}", - "migrate_tsig_wait": "Esperem 3 minutas que lo servidor DynDNS prenga en compte la novèla clau…", - "migrate_tsig_not_needed": "Sembla qu’utilizatz pas un domeni DynDNS, donc cap de migracion es pas necessària.", - "migration_0003_yunohost_upgrade": "Aviada de la mesa a nivèl del paquet YunoHost… La migracion acabarà, mas l’actualizacion reala se realizarà tot bèl aprèp. Un còp acabada, poiretz vos reconnectar a l’administracion web.", - "migration_0003_system_not_fully_up_to_date": "Lo sistèma es pas complètament a jorn. Mercés de lançar una mesa a jorn classica abans de començar la migracion per Stretch.", - "migration_0003_modified_files": "Mercés de notar que los fichièrs seguents son estats detectats coma modificats manualament e poiràn èsser escafats a la fin de la mesa a nivèl : {manually_modified_files}", "service_disable_failed": "Impossible de desactivar lo servici « {service:s} »↵\n↵\nJornals recents : {logs:s}", "service_disabled": "Lo servici « {service:s} » es desactivat", "service_enable_failed": "Impossible d’activar lo servici « {service:s} »↵\n↵\nJornals recents : {logs:s}", @@ -232,8 +196,6 @@ "user_unknown": "Utilizaire « {user:s} » desconegut", "user_update_failed": "Modificacion impossibla de l’utilizaire", "user_updated": "L’utilizaire es estat modificat", - "yunohost_ca_creation_failed": "Creacion impossibla de l’autoritat de certificacion", - "yunohost_ca_creation_success": "L’autoritat de certificacion locala es creada.", "service_description_avahi-daemon": "permet d’aténher vòstre servidor via yunohost.local sus vòstre ret local", "service_description_dnsmasq": "gerís la resolucion dels noms de domeni (DNS)", "updating_apt_cache": "Actualizacion de la lista dels paquets disponibles…", @@ -266,19 +228,11 @@ "service_description_ssh": "vos permet de vos connectar a distància a vòstre servidor via un teminal (protocòl SSH)", "service_description_yunohost-api": "permet las interaccions entre l’interfàcia web de YunoHost e le sistèma", "service_description_yunohost-firewall": "gerís los pòrts de connexion dobèrts e tampats als servicis", - "executing_command": "Execucion de la comanda « {command:s} »…", - "executing_script": "Execucion del script « {script:s} »…", "global_settings_cant_serialize_settings": "Fracàs de la serializacion de las donadas de parametratge, rason : {reason:s}", "ip6tables_unavailable": "Podètz pas jogar amb ip6tables aquí. Siá sèts dins un contenedor, siá vòstre nuclèu es pas compatible amb aquela opcion", "iptables_unavailable": "Podètz pas jogar amb iptables aquí. Siá sèts dins un contenedor, siá vòstre nuclèu es pas compatible amb aquela opcion", "mail_alias_remove_failed": "Supression impossibla de l’alias de corrièl « {mail:s} »", "mail_forward_remove_failed": "Supression impossibla del corrièl de transferiment « {mail:s} »", - "migrate_tsig_start": "L’algorisme de generacion de claus es pas pro securizat per la signatura TSIG del domeni « {domain} », lançament de la migracion cap a un mai segur HMAC-SHA-512", - "migration_description_0001_change_cert_group_to_sslcert": "Càmbia las permissions de grop dels certificats de « metronome » per « ssl-cert »", - "migration_0003_restoring_origin_nginx_conf": "Vòstre fichièr /etc/nginx/nginx.conf es estat modificat manualament. La migracion reïnicializarà d’en primièr son estat origina… Lo fichièr precedent serà disponible coma {backup_dest}.", - "migration_0003_still_on_jessie_after_main_upgrade": "Quicòm a trucat pendent la mesa a nivèl màger : lo sistèma es encara jos Jessie ?!? Per trobar lo problèma, agachatz {log}…", - "migration_0003_general_warning": "Notatz qu’aquesta migracion es una operacion delicata. Encara que la còla YunoHost aguèsse fach çò melhor per la tornar legir e provar, la migracion poiriá copar de parts del sistèma o de las aplicacions.\n\nEn consequéncia, vos recomandam :\n· · · · - de lançar una salvagarda de vòstras donadas o aplicacions criticas. Mai d’informacions a https://yunohost.org/backup ;\n· · · · - d’èsser pacient aprèp aver lançat la migracion : segon vòstra connexion Internet e material, pòt trigar qualques oras per que tot siá mes al nivèl.\n\nEn mai, lo pòrt per SMTP, utilizat pels clients de corrièls extèrns (coma Thunderbird o K9-Mail per exemple) foguèt cambiat de 465 (SSL/TLS) per 587 (STARTTLS). L’ancian pòrt 465 serà automaticament tampat e lo nòu pòrt 587 serà dobèrt dins lo parafuòc. Vosautres e vòstres utilizaires *auretz* d’adaptar la configuracion de vòstre client de corrièl segon aqueles cambiaments !", - "migration_0003_problematic_apps_warning": "Notatz que las aplicacions seguentas, saique problematicas, son estadas desactivadas. Semblan d’aver estadas installadas d’una lista d’aplicacions o que son pas marcadas coma «working ». En consequéncia, podèm pas assegurar que tendràn de foncionar aprèp la mesa a nivèl : {problematic_apps}", "migrations_migration_has_failed": "La migracion {id} a pas capitat, abandon. Error : {exception}", "migrations_skip_migration": "Passatge de la migracion {id}…", "migrations_to_be_ran_manually": "La migracion {id} deu èsser lançada manualament. Mercés d’anar a Aisinas > Migracion dins l’interfàcia admin, o lançar « yunohost tools migrations run ».", @@ -288,7 +242,6 @@ "service_description_fail2ban": "protegís contra los atacs brute-force e d’autres atacs venents d’Internet", "service_description_metronome": "gerís los comptes de messatjariás instantanèas XMPP", "service_description_nginx": "fornís o permet l’accès a totes los sites web albergats sus vòstre servidor", - "service_description_nslcd": "gerís la connexion en linha de comanda dels utilizaires YunoHost", "service_description_redis-server": "una basa de donadas especializada per un accès rapid a las donadas, las filas d’espèra e la comunicacion entre programas", "service_description_rspamd": "filtra lo corrièl pas desirat e mai foncionalitats ligadas al corrièl", "pattern_mailbox_quota": "Deu èsser una talha amb lo sufixe b/k/M/G/T o 0 per desactivar la quòta", @@ -298,10 +251,8 @@ "pattern_username": "Deu èsser compausat solament de caractèrs alfanumerics en letras minusculas e de tirets basses", "experimental_feature": "Atencion : aquesta foncionalitat es experimentala e deu pas èsser considerada coma establa, deuriatz pas l’utilizar levat que sapiatz çò que fasètz.", "log_corrupted_md_file": "Lo fichièr YAML de metadonadas ligat als jornals d’audit es damatjat : « {md_file} »\nError : {error:s}", - "log_category_404": "La categoria de jornals d’audit « {category} » existís pas", "log_link_to_log": "Jornal complèt d’aquesta operacion : {desc}", "log_help_to_get_log": "Per veire lo jornal d’aquesta operacion « {desc} », utilizatz la comanda « yunohost log show {name}{name} »", - "backup_php5_to_php7_migration_may_fail": "Impossible de convertir vòstre archiu per prendre en carga PHP 7, la restauracion de vòstras aplicacions PHP pòt reüssir pas a restaurar vòstras aplicacions PHP (rason : {error:s})", "log_link_to_failed_log": "L’operacion « {desc} » a pas capitat ! Per obténer d’ajuda, mercés de fornir lo jornal complèt de l’operacion", "log_help_to_get_failed_log": "L’operacion « {desc} » a pas reüssit ! Per obténer d’ajuda, mercés de partejar lo jornal d’audit complèt d’aquesta operacion en utilizant la comanda « yunohost log share {name} »", "log_does_exists": "I a pas cap de jornal d’audit per l’operacion amb lo nom « {log} », utilizatz « yunohost log list » per veire totes los jornals d’operacion disponibles", @@ -333,17 +284,8 @@ "log_tools_shutdown": "Atudar lo servidor", "log_tools_reboot": "Reaviar lo servidor", "mail_unavailable": "Aquesta adreça electronica es reservada e deu èsser automaticament atribuida al tot bèl just primièr utilizaire", - "migration_description_0004_php5_to_php7_pools": "Tornar configurar lo pools PHP per utilizar PHP 7 allòc del 5", - "migration_description_0005_postgresql_9p4_to_9p6": "Migracion de las basas de donadas de PostgreSQL9.4 cap a 9.6", - "migration_0005_postgresql_94_not_installed": "PostgreSQL es pas installat sul sistèma. I a pas res per far.", - "migration_0005_postgresql_96_not_installed": "Avèm trobat que Postgresql 9.4 es installat, mas cap de version de Postgresql 9.6 pas trobada !? Quicòm d’estranh a degut arribar a vòstre sistèma :( …", - "migration_0005_not_enough_space": "I a pas pro d’espaci disponible sus {path} per lançar la migracion d’aquela passa :(.", - "service_description_php7.0-fpm": "executa d’aplicacions escrichas en PHP amb nginx", - "users_available": "Lista dels utilizaires disponibles :", "good_practices_about_admin_password": "Sètz per definir un nòu senhal per l’administracion. Lo senhal deu almens conténer 8 caractèrs - encara que siá de bon far d’utilizar un senhal mai long qu’aquò (ex. una passafrasa) e/o d’utilizar mantun tipe de caractèrs (majuscula, minuscula, nombre e caractèrs especials).", "good_practices_about_user_password": "Sètz a mand de definir un nòu senhal d’utilizaire. Lo nòu senhal deu conténer almens 8 caractèrs, es de bon far d’utilizar un senhal mai long (es a dire una frasa de senhal) e/o utilizar mantun tipe de caractèrs (majusculas, minusculas, nombres e caractèrs especials).", - "migration_description_0006_sync_admin_and_root_passwords": "Sincronizar los senhals admin e root", - "migration_0006_disclaimer": "Ara YunoHost s’espèra que los senhals admin e root sián sincronizats. En lançant aquesta migracion, vòstre senhal root serà remplaçat pel senhal admin.", "password_listed": "Aqueste senhal es un dels mai utilizats al monde. Se vos plai utilizatz-ne un mai unic.", "password_too_simple_1": "Lo senhal deu conténer almens 8 caractèrs", "password_too_simple_2": "Lo senhal deu conténer almens 8 caractèrs e numbres, majusculas e minusculas", @@ -365,13 +307,9 @@ "file_does_not_exist": "Lo camin {path:s} existís pas.", "global_settings_setting_security_password_admin_strength": "Fòrça del senhal administrator", "global_settings_setting_security_password_user_strength": "Fòrça del senhal utilizaire", - "migration_description_0007_ssh_conf_managed_by_yunohost_step1": "La configuracion SSH serà gerada per YunoHost (etapa 1, automatica)", - "migration_description_0008_ssh_conf_managed_by_yunohost_step2": "Daissar YunoHost gerir la configuracion SSH (etapa 2, manuala)", - "migration_0007_cancelled": "Impossible de melhorar lo biais de gerir la configuracion SSH.", "root_password_replaced_by_admin_password": "Lo senhal root es estat remplaçat pel senhal administrator.", "service_restarted": "Lo servici '{service:s}' es estat reaviat", "admin_password_too_long": "Causissètz un senhal d’almens 127 caractèrs", - "migration_0007_cannot_restart": "SSH pòt pas èsser reavit aprèp aver ensajat d’anullar la migracion numèro 6.", "service_reloaded": "Lo servici « {service:s} » es estat tornat cargar", "already_up_to_date": "I a pas res a far ! Tot es ja a jorn !", "app_action_cannot_be_ran_because_required_services_down": "Aquestas aplicacions necessitan d’èsser lançadas per poder executar aquesta accion : {services}. Abans de contunhar deuriatz ensajar de reaviar los servicis seguents (e tanben cercar perque son tombats en pana) : {services}", @@ -385,10 +323,7 @@ "tools_upgrade_special_packages_completed": "L’actualizacion dels paquets de YunoHost es acabada !\nQuichatz [Entrada] per tornar a la linha de comanda", "dpkg_is_broken": "Podètz pas far aquò pel moment perque dpkg/APT (los gestionaris de paquets del sistèma) sembla èsser mal configurat… Podètz ensajar de solucionar aquò en vos connectar via SSH e en executar « sudo dpkg --configure -a ».", "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Autorizar l’utilizacion de la clau òst DSA (obsolèta) per la configuracion del servici SSH", - "migration_0008_general_disclaimer": "Per melhorar la seguretat del servidor, es recomandat de daissar YunoHost gerir la configuracion SSH. Vòstra configuracion actuala es diferenta de la configuracion recomandada. Se daissatz YunoHost la reconfigurar, lo biais de vos connectar al servidor via SSH cambiarà coma aquò :", "hook_json_return_error": "Fracàs de la lectura del retorn de l’script {path:s}. Error : {msg:s}. Contengut brut : {raw_content}", - "migration_0008_port": " - vos cal vos connectar en utilizar lo pòrt 22 allòc de vòstre pòrt SSH actual personalizat. Esitetz pas a lo reconfigurar ;", - "migration_0009_not_needed": "Sembla qu’i aguèt ja una migracion. Passem.", "pattern_password_app": "O planhèm, los senhals devon pas conténer los caractèrs seguents : {forbidden_chars}", "regenconf_file_backed_up": "Lo fichièr de configuracion « {conf} » es estat salvagardat dins « {backup} »", "regenconf_file_copy_failed": "Còpia impossibla del nòu fichièr de configuracion « {new} » cap a « {conf} »", @@ -409,16 +344,10 @@ "global_settings_setting_security_nginx_compatibility": "Solucion de compromés entre compatibilitat e seguretat pel servidor web NGINX Afècta los criptografs (e d’autres aspèctes ligats amb la seguretat)", "global_settings_setting_security_ssh_compatibility": "Solucion de compromés entre compatibilitat e seguretat pel servidor SSH. Afècta los criptografs (e d’autres aspèctes ligats amb la seguretat)", "global_settings_setting_security_postfix_compatibility": "Solucion de compromés entre compatibilitat e seguretat pel servidor Postfix. Afècta los criptografs (e d’autres aspèctes ligats amb la seguretat)", - "migration_description_0010_migrate_to_apps_json": "Levar las appslists despreciadas e utilizar la nòva lista unificada « apps.json » allòc", - "migration_0008_root": " - vos poiretz pas vos connectar coma root via SSH. Allòc auretz d’utilizar l’utilizaire admin;", - "migration_0008_warning": "Se comprenètz aquestes avertiments e qu’acceptatz de daissar YunoHost remplaçar la configuracion actuala, començatz la migracion. Autrament podètz tanben passar la migracion, encara que non siá pas recomandat.", "service_regen_conf_is_deprecated": "« yunohost service regen-conf » es despreciat ! Utilizatz « yunohost tools regen-conf » allòc.", "service_reload_failed": "Impossible de recargar lo servici « {service:s} »\n\nJornal d’audit recent : {logs:s}", "service_restart_failed": "Impossible de reaviar lo servici « {service:s} »\n\nJornal d’audit recent : {logs:s}", "service_reload_or_restart_failed": "Impossible de recargar o reaviar lo servici « {service:s} »\n\nJornal d’audit recent : {logs:s}", - "migration_description_0009_decouple_regenconf_from_services": "Desassociar lo mecanisme de regen-conf dels servicis", - "migration_0008_dsa": " - la clau DSA serà desactivada. En consequéncia, poiriatz aver d’invalidar un messatge espaurugant del client SSH, e tornar verificar l’emprunta del servidor;", - "migration_0008_no_warning": "Cap de risc important es estat detectat per remplaçar e la configuracion SSH, mas podèm pas n’èsser totalament segur ;) Se acceptatz que YunoHost remplace la configuracion actuala, començatz la migracion. Autrament, podètz passar la migracion, tot ben que non siá pas recomandat.", "regenconf_file_kept_back": "S’espèra que lo fichièr de configuracion « {conf} » siá suprimit per regen-conf (categoria {category} mas es estat mantengut.", "this_action_broke_dpkg": "Aquesta accion a copat dpkg/apt (los gestionaris de paquets del sistèma)… Podètz ensajar de resòlver aqueste problèma en vos connectant amb SSH e executant « sudo dpkg --configure -a ».", "tools_upgrade_at_least_one": "Especificatz --apps O --system", @@ -435,20 +364,9 @@ "group_deletion_failed": "Fracàs de la supression del grop « {group} » : {error}", "group_unknown": "Lo grop « {group} » es desconegut", "log_user_group_delete": "Suprimir lo grop « {} »", - "migration_0011_backup_before_migration": "Creacion d’una còpia de seguretat de la basa de donadas LDAP e de la configuracion de las aplicacions abans d’efectuar la migracion.", - "migration_0011_create_group": "Creacion d’un grop per cada utilizaire…", - "migration_0011_done": "Migracion complèta. Ara podètz gerir de grops d’utilizaires.", - "migration_0011_LDAP_update_failed": "Actualizacion impossibla de LDAP. Error : {error:s}", - "migration_0011_migration_failed_trying_to_rollback": "La migracion a fracassat… ensag de tornar lo sistèma a l’estat anterio.", - "migration_0011_rollback_success": "Restauracion del sistèma reüssida.", "group_updated": "Lo grop « {group} » es estat actualizat", "group_update_failed": "Actualizacion impossibla del grop « {group} » : {error}", "log_user_group_update": "Actualizar lo grop « {} »", - "migration_description_0011_setup_group_permission": "Configurar lo grop d’utilizaire e las permission de las aplicacions e dels servicis", - "migration_0011_can_not_backup_before_migration": "La salvagarda del sistèma abans la migracion a pas capitat. La migracion a fracassat. Error : {error:s}", - "migration_0011_migrate_permission": "Migracion de las permission dels paramètres d’aplicacion a LDAP…", - "migration_0011_update_LDAP_database": "Actualizacion de la basa de donadas LDAP…", - "migration_0011_update_LDAP_schema": "Actualizacion de l’esquèma LDAP…", "permission_already_exist": "La permission « {permission:s} » existís ja", "permission_created": "Permission « {permission:s} » creada", "permission_creation_failed": "Creacion impossibla de la permission", @@ -457,9 +375,7 @@ "permission_not_found": "Permission « {permission:s} » pas trobada", "permission_update_failed": "Fracàs de l’actualizacion de la permission", "permission_updated": "La permission « {permission:s} » es estada actualizada", - "permission_update_nothing_to_do": "Cap de permission d’actualizar", "mailbox_disabled": "La bóstia de las letras es desactivada per l’utilizaire {user:s}", - "migration_description_0012_postgresql_password_to_md5_authentication": "Forçar l’autentificacion PostgreSQL a utilizar MD5 per las connexions localas", "migrations_success_forward": "Migracion {id} corrèctament realizada !", "migrations_running_forward": "Execucion de la migracion {id}…", "migrations_must_provide_explicit_targets": "Devètz fornir una cibla explicita quand utilizatz using --skip o --force-rerun", @@ -471,10 +387,8 @@ "migrations_no_such_migration": "I a pas cap de migracion apelada « {id} »", "migrations_not_pending_cant_skip": "Aquestas migracions son pas en espèra, las podètz pas doncas ignorar : {ids}", "app_action_broke_system": "Aquesta accion sembla aver copat de servicis importants : {services}", - "diagnosis_display_tip_web": "Podètz anar a la seccion Diagnostic (dins l’ecran d’acuèlh) per veire los problèmas trobats.", "diagnosis_ip_no_ipv6": "Lo servidor a pas d’adreça IPv6 activa.", "diagnosis_ip_not_connected_at_all": "Lo servidor sembla pas connectat a Internet ?!", - "diagnosis_security_all_good": "Cap de vulnerabilitat de seguretat critica pas trobada.", "diagnosis_description_regenconf": "Configuracion sistèma", "diagnosis_http_ok": "Lo domeni {domain} accessible de l’exterior.", "app_full_domain_unavailable": "Aquesta aplicacion a d’èsser installada sul seu pròpri domeni, mas i a d’autras aplicacions installadas sus aqueste domeni « {domain} ». Podètz utilizar allòc un josdomeni dedicat a aquesta aplicacion.", @@ -487,7 +401,6 @@ "log_permission_url": "Actualizacion de l’URL ligada a la permission « {} »", "app_install_failed": "Installacion impossibla de {app} : {error}", "app_install_script_failed": "Una error s’es producha en installar lo script de l’aplicacion", - "migration_0011_failed_to_remove_stale_object": "Supression impossibla d’un objècte obsolèt {dn} : {error}", "apps_already_up_to_date": "Totas las aplicacions son ja al jorn", "app_remove_after_failed_install": "Supression de l’aplicacion aprèp fracàs de l’installacion...", "group_already_exist": "Lo grop {group} existís ja", @@ -499,7 +412,6 @@ "diagnosis_basesystem_kernel": "Lo servidor fonciona amb lo nuclèu Linuxl {kernel_version}", "diagnosis_basesystem_ynh_single_version": "{package} version : {version} ({repo})", "diagnosis_basesystem_ynh_inconsistent_versions": "Utilizatz de versions inconsistentas dels paquets de YunoHost… probablament a causa d'una actualizacion fracassada o parciala.", - "diagnosis_display_tip_cli": "Podètz executar « yunohost diagnosis show --issues --human-readable » per mostrar las errors trobadas.", "diagnosis_ignored_issues": "(+ {nb_ignored} problèma(es) ignorat(s))", "diagnosis_everything_ok": "Tot sembla corrècte per {category} !", "diagnosis_ip_connected_ipv4": "Lo servidor es connectat a Internet via IPv4 !", @@ -522,13 +434,11 @@ "diagnosis_description_services": "Verificacion d’estat de servicis", "diagnosis_description_systemresources": "Resorgas sistèma", "diagnosis_description_ports": "Exposicion dels pòrts", - "diagnosis_description_security": "Verificacion de seguretat", "diagnosis_ports_unreachable": "Lo pòrt {port} es pas accessible de l’exterior.", "diagnosis_ports_ok": "Lo pòrt {port} es accessible de l’exterior.", "diagnosis_http_unreachable": "Lo domeni {domain} es pas accessible via HTTP de l’exterior.", "diagnosis_unknown_categories": "La categorias seguentas son desconegudas : {categories}", "diagnosis_ram_low": "Lo sistèma a {available} ({available_percent}%) de memòria RAM disponibla d’un total de {total}). Atencion.", - "diagnosis_regenconf_manually_modified_debian": "Lo fichier de configuracion {file} foguèt modificat manualament respècte al fichièr per defaut de Debian.", "log_permission_create": "Crear la permission « {} »", "log_permission_delete": "Suprimir la permission « {} »", "log_user_group_create": "Crear lo grop « {} »", @@ -538,7 +448,6 @@ "diagnosis_found_warnings": "Trobat {warnings} element(s) que se poirián melhorar per {category}.", "diagnosis_dns_missing_record": "Segon la configuracion DNS recomandada, vos calriá ajustar un enregistrament DNS\ntipe: {type}\nnom: {name}\nvalor: {value}", "diagnosis_dns_discrepancy": "La configuracion DNS seguenta sembla pas la configuracion recomandada :
Tipe : {type}
Nom : {name}
Valors actualas : {current]
Valor esperada : {value}", - "diagnosis_regenconf_manually_modified_debian_details": "Es pas problematic, mas car téner d’agacher...", "diagnosis_ports_could_not_diagnose": "Impossible de diagnosticar se los pòrts son accessibles de l’exterior.", "diagnosis_ports_could_not_diagnose_details": "Error : {error}", "diagnosis_http_could_not_diagnose": "Impossible de diagnosticar se lo domeni es accessible de l’exterior.", @@ -547,7 +456,6 @@ "apps_catalog_failed_to_download": "Telecargament impossible del catalòg d’aplicacions {apps_catalog} : {error}", "apps_catalog_obsolete_cache": "La memòria cache del catalòg d’aplicacion es voida o obsolèta.", "apps_catalog_update_success": "Lo catalòg d’aplicacions es a jorn !", - "diagnosis_mail_ougoing_port_25_ok": "Lo pòrt de sortida 25 es pas blocat e lo corrièr electronic pòt partir als autres servidors.", "diagnosis_description_mail": "Corrièl", "app_upgrade_script_failed": "Una error s’es producha pendent l’execucion de l’script de mesa a nivèl de l’aplicacion", "diagnosis_cant_run_because_of_dep": "Execucion impossibla del diagnostic per {category} mentre que i a de problèmas importants ligats amb {dep}.", @@ -560,7 +468,6 @@ "diagnosis_services_conf_broken": "La configuracion es copada pel servici {service} !", "diagnosis_ports_needed_by": "Es necessari qu’aqueste pòrt siá accessible pel servici {service}", "diagnosis_diskusage_low": "Lo lòc d’emmagazinatge {mountpoint} (sul periferic {device}) a solament {free} ({free_percent}%). Siatz prudent.", - "migration_description_0014_remove_app_status_json": "Suprimir los fichièrs d’aplicacion status.json eretats", "dyndns_provider_unreachable": "Impossible d’atenher lo provesidor Dyndns : siá vòstre YunoHost es pas corrèctament connectat a Internet siá lo servidor dynette es copat.", "diagnosis_services_bad_status_tip": "Podètz ensajar de reaviar lo servici, e se non fonciona pas, podètz agachar los jornals de servici a la pagina web d’administracion(en linha de comanda podètz utilizar yunohost service restart {service} e yunohost service log {service}).", "diagnosis_http_connection_error": "Error de connexion : connexion impossibla al domeni demandat, benlèu qu’es pas accessible.", @@ -578,7 +485,6 @@ "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}", "backup_archive_corrupted": "Sembla que l’archiu de la salvagarda « {archive} » es corromput : {error}", "diagnosis_domain_expires_in": "{domain} expiraà d’aquí {days} jorns.", "migration_0015_cleaning_up": "Netejatge de la memòria cache e dels paquets pas mai necessaris…", @@ -608,4 +514,4 @@ "diagnosis_basesystem_hardware_model": "Lo modèl del servidor es {model}", "backup_archive_cant_retrieve_info_json": "Obtencion impossibla de las informacions de l’archiu « {archive} »... Se pòt pas recuperar lo fichièr info.json (o es pas un fichièr json valid).", "app_packaging_format_not_supported": "Se pòt pas installar aquesta aplicacion pr’amor que son format es pas pres en carga per vòstra version de YunoHost. Deuriatz considerar actualizar lo sistèma." -} +} \ No newline at end of file diff --git a/locales/pl.json b/locales/pl.json index 76ce2f408..46ec8b622 100644 --- a/locales/pl.json +++ b/locales/pl.json @@ -9,4 +9,4 @@ "admin_password": "Hasło administratora", "action_invalid": "Nieprawidłowa operacja '{action:s}'", "aborting": "Przerywanie." -} +} \ No newline at end of file diff --git a/locales/pt.json b/locales/pt.json index 9375f2354..6f3419781 100644 --- a/locales/pt.json +++ b/locales/pt.json @@ -14,14 +14,12 @@ "app_unknown": "Aplicação desconhecida", "app_upgrade_failed": "Não foi possível atualizar {app:s}", "app_upgraded": "{app:s} atualizada com sucesso", - "ask_email": "Endereço de Email", "ask_firstname": "Primeiro nome", "ask_lastname": "Último nome", "ask_main_domain": "Domínio principal", "ask_new_admin_password": "Nova senha de administração", "ask_password": "Senha", "backup_created": "Backup completo", - "backup_invalid_archive": "Arquivo de backup inválido", "backup_output_directory_not_empty": "A pasta de destino não se encontra vazia", "custom_app_url_required": "Deve fornecer um link para atualizar a sua aplicação personalizada {app:s}", "domain_cert_gen_failed": "Não foi possível gerar o certificado", @@ -36,16 +34,12 @@ "domain_unknown": "Domínio desconhecido", "done": "Concluído.", "downloading": "Transferência em curso...", - "dyndns_cron_installed": "Gestor de tarefas cron DynDNS instalado com êxito", - "dyndns_cron_remove_failed": "Não foi possível remover o gestor de tarefas cron DynDNS", - "dyndns_cron_removed": "Gestor de tarefas cron DynDNS removido com êxito", "dyndns_ip_update_failed": "Não foi possível atualizar o endereço IP a partir de DynDNS", "dyndns_ip_updated": "Endereço IP atualizado com êxito a partir de DynDNS", "dyndns_key_generating": "A chave DNS está a ser gerada, isto pode demorar um pouco...", "dyndns_registered": "Dom+inio DynDNS registado com êxito", "dyndns_registration_failed": "Não foi possível registar o domínio DynDNS: {error:s}", "dyndns_unavailable": "Subdomínio DynDNS indisponível", - "executing_script": "A executar o script...", "extracting": "Extração em curso...", "field_invalid": "Campo inválido '{:s}'", "firewall_reloaded": "Firewall recarregada com êxito", @@ -58,7 +52,6 @@ "mail_forward_remove_failed": "Não foi possível remover o reencaminhamento de correio '{mail:s}'", "main_domain_change_failed": "Incapaz alterar o domínio raiz", "main_domain_changed": "Domínio raiz alterado com êxito", - "no_internet_connection": "O servidor não está ligado à Internet", "packages_upgrade_failed": "Não foi possível atualizar todos os pacotes", "pattern_domain": "Deve ser um nome de domínio válido (p.e. meu-dominio.org)", "pattern_email": "Deve ser um endereço de correio válido (p.e. alguem@dominio.org)", @@ -99,7 +92,6 @@ "user_update_failed": "Não foi possível atualizar o utilizador", "user_updated": "Utilizador atualizado com êxito", "yunohost_already_installed": "AYunoHost já está instalado", - "yunohost_ca_creation_failed": "Incapaz criar o certificado de autoridade", "yunohost_configured": "YunoHost configurada com êxito", "yunohost_installing": "A instalar a YunoHost...", "yunohost_not_installed": "YunoHost ainda não está corretamente configurado. Por favor execute as 'ferramentas pós-instalação yunohost'.", @@ -134,11 +126,10 @@ "backup_archive_name_unknown": "Desconhece-se o arquivo local de backup de nome '{name:s}'", "backup_archive_system_part_not_available": "A seção do sistema '{part:s}' está indisponivel neste backup", "backup_ask_for_copying_if_needed": "Alguns arquivos não consiguiram ser preparados para backup utilizando o metodo que não gasta espaço de disco temporariamente. Para realizar o backup {size:s}MB precisam ser usados temporariamente. Você concorda?", - "backup_borg_not_implemented": "O método de backup Borg ainda não foi implementado.", "backup_cant_mount_uncompress_archive": "Não foi possível montar em modo leitura o diretorio de arquivos não comprimido", "backup_copying_to_organize_the_archive": "Copiando {size:s}MB para organizar o arquivo", "app_change_url_identical_domains": "O antigo e o novo domínio / url_path são idênticos ('{domain:s}{path:s}'), nada para fazer.", "password_too_simple_1": "A senha precisa ter pelo menos 8 caracteres", "admin_password_too_long": "Escolha uma senha que contenha menos de 127 caracteres", "aborting": "Abortando." -} +} \ No newline at end of file diff --git a/locales/zh_Hans.json b/locales/zh_Hans.json index e72cd52da..c1cd756b7 100644 --- a/locales/zh_Hans.json +++ b/locales/zh_Hans.json @@ -13,4 +13,4 @@ "app_start_restore": "正在恢复{app}……", "action_invalid": "无效操作 '{action:s}'", "ask_lastname": "姓" -} +} \ No newline at end of file From 265e31411954316bcf9ce168019b413a6df4f786 Mon Sep 17 00:00:00 2001 From: ppr Date: Sun, 11 Apr 2021 16:40:09 +0000 Subject: [PATCH 2366/3170] Translated using Weblate (French) Currently translated at 99.2% (624 of 629 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 0ca00ef41..d0e57f969 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -569,7 +569,7 @@ "log_app_action_run": "Lancer l’action de l’application '{}'", "log_app_config_show_panel": "Montrer le panneau de configuration de l’application '{}'", "log_app_config_apply": "Appliquer la configuration à l’application '{}'", - "diagnosis_never_ran_yet": "Il apparaît que le serveur a été installé récemment et qu’il n’y a pas encore eu de diagnostic. Vous devriez en lancer un depuis le webmin ou en utilisant 'yunohost diagnosis run' depuis la ligne de commande.", + "diagnosis_never_ran_yet": "Il apparaît que le serveur a été installé récemment et qu’il n’y a pas encore eu de diagnostic. Vous devriez en lancer un depuis la web-admin ou en utilisant 'yunohost diagnosis run' depuis la ligne de commande.", "diagnosis_description_web": "Web", "diagnosis_basesystem_hardware_board": "Le modèle de carte du serveur est {model}", "diagnosis_basesystem_hardware": "L’architecture du serveur est {virt} {arch}", @@ -696,5 +696,6 @@ "postinstall_low_rootfsspace": "Le système de fichiers racine a une taille totale inférieure à 10 GB, ce qui est inquiétant ! Vous allez certainement arriver à court d'espace disque rapidement ! Il est recommandé d'avoir au moins 16 GB pour ce système de fichiers. Si vous voulez installer YunoHost malgré cet avertissement, relancez le postinstall avec --force-diskspace", "domain_remove_confirm_apps_removal": "Le retrait de ce domaine retirera aussi ces applications :\n{apps}\n\nÊtes vous sûr de vouloir cela ? [{answers}]", "diagnosis_rootfstotalspace_critical": "Le système de fichiers racine ne fait que {space} ! Vous allez certainement le remplir très rapidement ! Il est recommandé d'avoir au moins 16 GB pour ce système de fichiers.", - "diagnosis_rootfstotalspace_warning": "Le système de fichiers racine n'est que de {space}. Cela peut suffire, mais faites attention car vous risquez de les remplir rapidement... Il est recommandé d'avoir au moins 16 GB pour ce système de fichiers." + "diagnosis_rootfstotalspace_warning": "Le système de fichiers racine n'est que de {space}. Cela peut suffire, mais faites attention car vous risquez de les remplir rapidement... Il est recommandé d'avoir au moins 16 GB pour ce système de fichiers.", + "app_restore_script_failed": "Une erreur s'est produite dans le script de restauration de l'application" } From 357c151ce21d61210d431d9881d8e185ed58407b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 1 Apr 2021 15:51:49 +0200 Subject: [PATCH 2367/3170] services.py, python3: missing decode() in subprocess output fetch --- src/yunohost/service.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index d7c3e1db0..e6e960a57 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -465,6 +465,7 @@ def _get_and_format_service_status(service, infos): if p.returncode == 0: output["configuration"] = "valid" else: + out = out.decode() output["configuration"] = "broken" output["configuration-details"] = out.strip().split("\n") From f878d61f3a916a308d777e7358fb754f595bcc33 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 1 Apr 2021 15:55:46 +0200 Subject: [PATCH 2368/3170] log.py: don't inject log_ref if the operation didnt start yet --- src/yunohost/log.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/log.py b/src/yunohost/log.py index 7a45565f8..592e76bb4 100644 --- a/src/yunohost/log.py +++ b/src/yunohost/log.py @@ -636,7 +636,7 @@ class OperationLogger(object): # we want to inject the log ref in the exception, such that it may be # transmitted to the webadmin which can then redirect to the appropriate # log page - if isinstance(error, Exception) and not isinstance(error, YunohostValidationError): + if self.started_at and isinstance(error, Exception) and not isinstance(error, YunohostValidationError): error.log_ref = self.name if self.ended_at is not None or self.started_at is None: From 008e9f1dc555f0befdfc62019093f2d31e007e99 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 8 Apr 2021 15:35:09 +0200 Subject: [PATCH 2369/3170] Missing raw_msg=True --- src/yunohost/dyndns.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index b2ac3de6d..c7a501b9c 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -260,7 +260,7 @@ def dyndns_update( ok, result = dig(dyn_host, "A") dyn_host_ip = result[0] if ok == "ok" and len(result) else None if not dyn_host_ip: - raise YunohostError("Failed to resolve %s" % dyn_host) + raise YunohostError("Failed to resolve %s" % dyn_host, raw_msg=True) ok, result = dig(domain, rdtype, resolvers=[dyn_host_ip]) if ok == "ok": From 6fd5f7e86410adc7f647a1d96e078e966f06a294 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 8 Apr 2021 23:56:16 +0200 Subject: [PATCH 2370/3170] firewall_list: Don't miserably crash when trying to sort port range ("12300:12400", ain't an int) --- src/yunohost/firewall.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/yunohost/firewall.py b/src/yunohost/firewall.py index bc21f1948..af1cea2e3 100644 --- a/src/yunohost/firewall.py +++ b/src/yunohost/firewall.py @@ -188,18 +188,19 @@ def firewall_list(raw=False, by_ip_version=False, list_forwarded=False): for i in ["ipv4", "ipv6"]: f = firewall[i] # Combine TCP and UDP ports - ports[i] = sorted(set(f["TCP"]) | set(f["UDP"])) + ports[i] = sorted(set(f["TCP"]) | set(f["UDP"]), key=lambda p: int(p.split(':')[0]) if isinstance(p, str) else p) if not by_ip_version: # Combine IPv4 and IPv6 ports - ports = sorted(set(ports["ipv4"]) | set(ports["ipv6"])) + ports = sorted(set(ports["ipv4"]) | set(ports["ipv6"]), key=lambda p: int(p.split(':')[0]) if isinstance(p, str) else p) # Format returned dict ret = {"opened_ports": ports} if list_forwarded: # Combine TCP and UDP forwarded ports ret["forwarded_ports"] = sorted( - set(firewall["uPnP"]["TCP"]) | set(firewall["uPnP"]["UDP"]) + set(firewall["uPnP"]["TCP"]) | set(firewall["uPnP"]["UDP"]), + key=lambda p: int(p.split(':')[0]) if isinstance(p, str) else p ) return ret From 575fab8a1985c82b17d7f8915dec9420f59ff6df Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 9 Apr 2021 00:00:40 +0200 Subject: [PATCH 2371/3170] nginx conf: CSP rules for admin was blocking small images used for checkboxes, radio, pacman in the new webadmin --- data/templates/nginx/plain/yunohost_admin.conf.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/templates/nginx/plain/yunohost_admin.conf.inc b/data/templates/nginx/plain/yunohost_admin.conf.inc index 26f348dea..326e003ee 100644 --- a/data/templates/nginx/plain/yunohost_admin.conf.inc +++ b/data/templates/nginx/plain/yunohost_admin.conf.inc @@ -6,6 +6,6 @@ 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: 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'; img-src 'self' data:;"; more_set_headers "Content-Security-Policy-Report-Only:"; } From 2b8ffdfe6602fe097f3ce2499dd2c315dfebc202 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 11 Apr 2021 20:24:59 +0200 Subject: [PATCH 2372/3170] Update changelog for 4.2.1.1 --- debian/changelog | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/debian/changelog b/debian/changelog index fe1f42a23..916ab4edd 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,13 @@ +yunohost (4.2.1.1) testing; urgency=low + + - [fix] services.py, python3: missing decode() in subprocess output fetch (357c151c) + - [fix] log.py: don't inject log_ref if the operation didnt start yet (f878d61f) + - [fix] dyndns.py: Missing raw_msg=True (008e9f1d) + - [fix] firewall.py: Don't miserably crash when there are port ranges (6fd5f7e8) + - [fix] nginx conf: CSP rules for admin was blocking small images used for checkboxes, radio, pacman in the new webadmin (575fab8a) + + -- Alexandre Aubin Sun, 11 Apr 2021 20:15:11 +0200 + yunohost (4.2.1) testing; urgency=low - security: Various permissions tweaks to protect from malicious yunohost users (aefc100a, fc26837a) From ce9f6b3dc309b58b5c5d9e8b26fe1ccd319a8e8f Mon Sep 17 00:00:00 2001 From: Kay0u Date: Mon, 12 Apr 2021 10:57:14 +0200 Subject: [PATCH 2373/3170] use python3 to generate the helper doc --- .gitlab/ci/doc.gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab/ci/doc.gitlab-ci.yml b/.gitlab/ci/doc.gitlab-ci.yml index 3b161dc08..696dcefa6 100644 --- a/.gitlab/ci/doc.gitlab-ci.yml +++ b/.gitlab/ci/doc.gitlab-ci.yml @@ -12,7 +12,7 @@ generate-helpers-doc: - git config --global user.name "$GITHUB_USER" script: - cd doc - - python generate_helper_doc.py + - python3 generate_helper_doc.py - hub clone https://$GITHUB_TOKEN:x-oauth-basic@github.com/YunoHost/doc.git doc_repo - cp helpers.md doc_repo/pages/02.contribute/04.packaging_apps/11.helpers/packaging_apps_helpers.md - cd doc_repo From 1468073f79a48f993064cb0932792e0f9a2a2cb4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 12 Apr 2021 17:23:37 +0200 Subject: [PATCH 2374/3170] Fix user_group_add/remove description --- data/actionsmap/yunohost.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 73aa048bc..4526989df 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -264,7 +264,7 @@ user: ### user_group_add() add: - action_help: Update group + action_help: Add users to group api: PUT /users/groups//add/ arguments: groupname: @@ -280,7 +280,7 @@ user: ### user_group_remove() remove: - action_help: Update group + action_help: Remove users from group api: PUT /users/groups//remove/ arguments: groupname: From ee31969be753440b51cb20bc867229833ee76aff Mon Sep 17 00:00:00 2001 From: Kay0u Date: Mon, 12 Apr 2021 18:07:36 +0200 Subject: [PATCH 2375/3170] add ssh port setting --- data/hooks/conf_regen/03-ssh | 2 ++ data/templates/ssh/sshd_config | 2 +- src/yunohost/settings.py | 5 +++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/data/hooks/conf_regen/03-ssh b/data/hooks/conf_regen/03-ssh index 54b7c55b7..1f057aa35 100755 --- a/data/hooks/conf_regen/03-ssh +++ b/data/hooks/conf_regen/03-ssh @@ -26,6 +26,8 @@ do_pre_regen() { # Support different strategy for security configurations export compatibility="$(yunohost settings get 'security.ssh.compatibility')" + export port="$(yunohost settings get 'security.ssh.port')" + export ssh_keys export ipv6_enabled ynh_render_template "sshd_config" "${pending_dir}/etc/ssh/sshd_config" diff --git a/data/templates/ssh/sshd_config b/data/templates/ssh/sshd_config index 84f06d4e5..0ffde09c6 100644 --- a/data/templates/ssh/sshd_config +++ b/data/templates/ssh/sshd_config @@ -2,7 +2,7 @@ # by YunoHost Protocol 2 -Port 22 +Port {{ port }} {% if ipv6_enabled == "true" %}ListenAddress ::{% endif %} ListenAddress 0.0.0.0 diff --git a/src/yunohost/settings.py b/src/yunohost/settings.py index e252316bd..f44178f07 100644 --- a/src/yunohost/settings.py +++ b/src/yunohost/settings.py @@ -71,6 +71,10 @@ DEFAULTS = OrderedDict( "choices": ["intermediate", "modern"], }, ), + ( + "security.ssh.port", + {"type": "int", "default": 22}, + ), ( "security.nginx.compatibility", { @@ -383,6 +387,7 @@ def reconfigure_nginx(setting_name, old_value, new_value): regen_conf(names=["nginx"]) +@post_change_hook("security.ssh.port") @post_change_hook("security.ssh.compatibility") def reconfigure_ssh(setting_name, old_value, new_value): if old_value != new_value: From bc0fd07680a70099d7b047e61e06ea9f590e63b7 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 12 Apr 2021 19:27:32 +0200 Subject: [PATCH 2376/3170] Add description for new SSH port setting --- locales/en.json | 1 + 1 file changed, 1 insertion(+) diff --git a/locales/en.json b/locales/en.json index f60f2f2e7..52e2d94e6 100644 --- a/locales/en.json +++ b/locales/en.json @@ -323,6 +323,7 @@ "global_settings_setting_security_password_user_strength": "User password strength", "global_settings_setting_security_ssh_compatibility": "Compatibility vs. security tradeoff for the SSH server. Affects the ciphers (and other security-related aspects)", "global_settings_setting_security_postfix_compatibility": "Compatibility vs. security tradeoff for the Postfix server. Affects the ciphers (and other security-related aspects)", + "global_settings_setting_security_ssh_port": "SSH port", "global_settings_unknown_setting_from_settings_file": "Unknown key in settings: '{setting_key:s}', discard it and save it in /etc/yunohost/settings-unknown.json", "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Allow the use of (deprecated) DSA hostkey for the SSH daemon configuration", "global_settings_setting_ssowat_panel_overlay_enabled": "Enable SSOwat panel overlay", From b33e7c16ac2230bca731830d22aebdde006531bf Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Tue, 13 Apr 2021 12:37:55 +0200 Subject: [PATCH 2377/3170] [mod] no space before ! in english --- src/yunohost/certificate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 0c45509bf..1c2249ba7 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -432,7 +432,7 @@ def certificate_renew( stack = StringIO() traceback.print_exc(file=stack) - msg = "Certificate renewing for %s failed !" % (domain) + 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." From bd72a59e1f62f85653422dbb2276d0d552903f7b Mon Sep 17 00:00:00 2001 From: Kay0u Date: Tue, 13 Apr 2021 13:21:27 +0200 Subject: [PATCH 2378/3170] remove app settings after removing the app permissions --- src/yunohost/app.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 8d334677f..7ada46d0c 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1263,16 +1263,15 @@ def app_remove(operation_logger, app): else: logger.warning(m18n.n("app_not_properly_removed", app=app)) + # Remove all permission in LDAP + for permission_name in user_permission_list(apps=[app])["permissions"].keys(): + permission_delete(permission_name, force=True, sync_perm=False) + if os.path.exists(app_setting_path): shutil.rmtree(app_setting_path) shutil.rmtree("/tmp/yunohost_remove") hook_remove(app) - # Remove all permission in LDAP - for permission_name in user_permission_list()["permissions"].keys(): - if permission_name.startswith(app + "."): - permission_delete(permission_name, force=True, sync_perm=False) - permission_sync_to_user() _assert_system_is_sane_for_app(manifest, "post") From f6687e69f852b3586c620c217d11123c94ff61f6 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Tue, 13 Apr 2021 13:22:07 +0200 Subject: [PATCH 2379/3170] user_permission_list: use the new apps arg when we can --- src/yunohost/app.py | 9 ++++----- src/yunohost/backup.py | 8 +++----- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 7ada46d0c..414035e77 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -324,9 +324,9 @@ def app_map(app=None, raw=False, user=None): app, ] else: - apps = os.listdir(APPS_SETTING_PATH) + apps = _installed_apps() - permissions = user_permission_list(full=True, absolute_urls=True)["permissions"] + permissions = user_permission_list(full=True, absolute_urls=True, apps=apps)["permissions"] for app_id in apps: app_settings = _get_app_settings(app_id) if not app_settings: @@ -1096,9 +1096,8 @@ def app_install( ) # Remove all permission in LDAP - for permission_name in user_permission_list()["permissions"].keys(): - if permission_name.startswith(app_instance_name + "."): - permission_delete(permission_name, force=True, sync_perm=False) + for permission_name in user_permission_list(apps=[app_instance_name])["permissions"].keys(): + permission_delete(permission_name, force=True, sync_perm=False) if remove_retcode != 0: msg = m18n.n("app_not_properly_removed", app=app_instance_name) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 5c83f6651..85487d07f 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -726,11 +726,10 @@ class BackupManager: # backup permissions logger.debug(m18n.n("backup_permission", app=app)) - permissions = user_permission_list(full=True)["permissions"] + permissions = user_permission_list(full=True, apps=[app])["permissions"] this_app_permissions = { name: infos for name, infos in permissions.items() - if name.startswith(app + ".") } write_to_yaml("%s/permissions.yml" % settings_dir, this_app_permissions) @@ -1547,9 +1546,8 @@ class RestoreManager: shutil.rmtree(app_settings_new_path, ignore_errors=True) # Remove all permission in LDAP for this app - for permission_name in user_permission_list()["permissions"].keys(): - if permission_name.startswith(app_instance_name + "."): - permission_delete(permission_name, force=True) + for permission_name in user_permission_list(apps=[app_instance_name])["permissions"].keys(): + permission_delete(permission_name, force=True) # TODO Cleaning app hooks From 4fa6a4cde2f64637ac6170eaa19c7241d460495d Mon Sep 17 00:00:00 2001 From: Kay0u Date: Tue, 13 Apr 2021 14:05:16 +0200 Subject: [PATCH 2380/3170] trying to fix tests --- src/yunohost/permission.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 3ed9590d4..493f11953 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -89,10 +89,7 @@ def user_permission_list( name = infos["cn"][0] app = name.split(".")[0] - if app in SYSTEM_PERMS: - if ignore_system_perms: - continue - elif app not in apps: + if app in SYSTEM_PERMS and ignore_system_perms: continue perm = {} From d1f0064b10645b3dd12cca88e454193ccb5f1334 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 13 Apr 2021 14:18:49 +0200 Subject: [PATCH 2381/3170] mysql regenconf: Get rid of confusing 'Access denied' message --- data/hooks/conf_regen/34-mysql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/hooks/conf_regen/34-mysql b/data/hooks/conf_regen/34-mysql index 6c9694796..fd09d5188 100755 --- a/data/hooks/conf_regen/34-mysql +++ b/data/hooks/conf_regen/34-mysql @@ -37,7 +37,7 @@ do_post_regen() { # This is a trick to check if we're able to use mysql without password # Expect instances installed in stretch to already have unix_socket #configured, but not old instances from the jessie/wheezy era - if ! echo "" | mysql + if ! echo "" | mysql 2>/dev/null then password="$(cat /etc/yunohost/mysql)" # Enable plugin unix_socket for root on localhost @@ -45,7 +45,7 @@ do_post_regen() { fi # If now we're able to login without password, drop the mysql password - if echo "" | mysql + if echo "" | mysql 2>/dev/null then rm /etc/yunohost/mysql else From 6745fce647df7e823fac4b5128a3ff54ab4b14e4 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Tue, 13 Apr 2021 16:28:32 +0200 Subject: [PATCH 2382/3170] fix tests --- src/yunohost/app.py | 5 +++-- src/yunohost/backup.py | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 414035e77..a3c10df0b 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1096,8 +1096,9 @@ def app_install( ) # Remove all permission in LDAP - for permission_name in user_permission_list(apps=[app_instance_name])["permissions"].keys(): - permission_delete(permission_name, force=True, sync_perm=False) + for permission_name in user_permission_list()["permissions"].keys(): + if permission_name.startswith(app_instance_name + "."): + permission_delete(permission_name, force=True, sync_perm=False) if remove_retcode != 0: msg = m18n.n("app_not_properly_removed", app=app_instance_name) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 85487d07f..a17b752f6 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -1546,8 +1546,9 @@ class RestoreManager: shutil.rmtree(app_settings_new_path, ignore_errors=True) # Remove all permission in LDAP for this app - for permission_name in user_permission_list(apps=[app_instance_name])["permissions"].keys(): - permission_delete(permission_name, force=True) + for permission_name in user_permission_list()["permissions"].keys(): + if permission_name.startswith(app_instance_name + "."): + permission_delete(permission_name, force=True) # TODO Cleaning app hooks From ab834f1885e0ce96ee2d8fe520299b7253b790a9 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Tue, 13 Apr 2021 16:43:53 +0200 Subject: [PATCH 2383/3170] fix clean in test_settings --- src/yunohost/tests/test_settings.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/yunohost/tests/test_settings.py b/src/yunohost/tests/test_settings.py index b402a9ef5..a393e83c6 100644 --- a/src/yunohost/tests/test_settings.py +++ b/src/yunohost/tests/test_settings.py @@ -1,5 +1,6 @@ import os import json +import glob import pytest from yunohost.utils.error import YunohostError @@ -28,6 +29,8 @@ def setup_function(function): def teardown_function(function): os.system("mv /etc/yunohost/settings.json.saved /etc/yunohost/settings.json") + for filename in glob.glob("/etc/yunohost/settings-*.json"): + os.remove(filename) def test_settings_get_bool(): From 42d671b7c0f9f5529a5a9f6a9ae507015bd51cae Mon Sep 17 00:00:00 2001 From: Kay0u Date: Tue, 13 Apr 2021 18:17:57 +0200 Subject: [PATCH 2384/3170] fix pytest warnings --- pytest.ini | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pytest.ini b/pytest.ini index 709e0e0b9..27d690435 100644 --- a/pytest.ini +++ b/pytest.ini @@ -3,12 +3,14 @@ addopts = -s -v norecursedirs = dist doc build .tox .eggs testpaths = tests/ markers = - with_system_archive_from_2p4 + with_system_archive_from_3p8 with_backup_recommended_app_installed clean_opt_dir - with_wordpress_archive_from_2p4 + with_wordpress_archive_from_3p8 with_legacy_app_installed with_backup_recommended_app_installed_with_ynh_restore with_permission_app_installed + other_domains + with_custom_domain filterwarnings = ignore::urllib3.exceptions.InsecureRequestWarning \ No newline at end of file From 8b360ac2e6e3fb3412cab7c370f9f4f2d2402013 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Tue, 13 Apr 2021 18:18:15 +0200 Subject: [PATCH 2385/3170] getargspec is deprecated in Python3 --- src/yunohost/log.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/log.py b/src/yunohost/log.py index 9a3eb9fa6..f8215955f 100644 --- a/src/yunohost/log.py +++ b/src/yunohost/log.py @@ -340,9 +340,9 @@ def is_unit_operation( # Indeed, we use convention naming in this decorator and we need to # know name of each args (so we need to use kwargs instead of args) if len(args) > 0: - from inspect import getargspec + from inspect import signature - keys = getargspec(func).args + keys = list(signature(func).parameters.keys()) if "operation_logger" in keys: keys.remove("operation_logger") for k, arg in enumerate(args): From 7989271e749257639a35f3b055de4dbb8bfdf1ee Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 14 Apr 2021 20:16:58 +0200 Subject: [PATCH 2386/3170] user_permission_list: also support filtering for apps not installed or system perms --- src/yunohost/permission.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 493f11953..9fe6c4e4f 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -74,13 +74,12 @@ def user_permission_list( ) # Parse / organize information to be outputed - if apps: - ignore_system_perms = True - apps = apps if apps else sorted(_installed_apps()) + installed_apps = sorted(_installed_apps()) + apps = apps if apps else installed_apps apps_base_path = { app: app_setting(app, "domain") + app_setting(app, "path") for app in apps - if app_setting(app, "domain") and app_setting(app, "path") + if app in installed_apps and app_setting(app, "domain") and app_setting(app, "path") } permissions = {} @@ -89,7 +88,10 @@ def user_permission_list( name = infos["cn"][0] app = name.split(".")[0] - if app in SYSTEM_PERMS and ignore_system_perms: + if ignore_system_perms and app in SYSTEM_PERMS: + continue + + if app not in apps: continue perm = {} From 23e816deaa6b87456b9a5163b6d54d3d5a7391cb Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 14 Apr 2021 23:58:22 +0200 Subject: [PATCH 2387/3170] Only filter stuff if a filter is set... --- src/yunohost/permission.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 9fe6c4e4f..7d02a542f 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -75,7 +75,8 @@ def user_permission_list( # Parse / organize information to be outputed installed_apps = sorted(_installed_apps()) - apps = apps if apps else installed_apps + filter_ = apps + apps = filter_ if filter_ else installed_apps apps_base_path = { app: app_setting(app, "domain") + app_setting(app, "path") for app in apps @@ -90,8 +91,7 @@ def user_permission_list( if ignore_system_perms and app in SYSTEM_PERMS: continue - - if app not in apps: + if filter_ and app not in apps: continue perm = {} From 37c0825eed421acadd0a7528dbab8172695eed8f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 15 Apr 2021 12:22:21 +0200 Subject: [PATCH 2388/3170] Also propagate ssh port on fail2ban config --- data/hooks/conf_regen/52-fail2ban | 6 +++++- data/templates/fail2ban/yunohost-jails.conf | 1 + src/yunohost/settings.py | 7 ++++++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/data/hooks/conf_regen/52-fail2ban b/data/hooks/conf_regen/52-fail2ban index 3cb499db7..e696df6c8 100755 --- a/data/hooks/conf_regen/52-fail2ban +++ b/data/hooks/conf_regen/52-fail2ban @@ -2,6 +2,8 @@ set -e +. /usr/share/yunohost/helpers + do_pre_regen() { pending_dir=$1 @@ -13,7 +15,9 @@ do_pre_regen() { cp yunohost.conf "${fail2ban_dir}/filter.d/yunohost.conf" cp jail.conf "${fail2ban_dir}/jail.conf" - cp yunohost-jails.conf "${fail2ban_dir}/jail.d/" + + export ssh_port="$(yunohost settings get 'security.ssh.port')" + ynh_render_template "yunohost-jails.conf" "${fail2ban_dir}/jail.d/yunohost-jails.conf" } do_post_regen() { diff --git a/data/templates/fail2ban/yunohost-jails.conf b/data/templates/fail2ban/yunohost-jails.conf index f3aea7fb1..1cf1a1966 100644 --- a/data/templates/fail2ban/yunohost-jails.conf +++ b/data/templates/fail2ban/yunohost-jails.conf @@ -1,4 +1,5 @@ [sshd] +port = {{ssh_port}} enabled = true [nginx-http-auth] diff --git a/src/yunohost/settings.py b/src/yunohost/settings.py index f44178f07..dd43c4787 100644 --- a/src/yunohost/settings.py +++ b/src/yunohost/settings.py @@ -387,13 +387,18 @@ def reconfigure_nginx(setting_name, old_value, new_value): regen_conf(names=["nginx"]) -@post_change_hook("security.ssh.port") @post_change_hook("security.ssh.compatibility") def reconfigure_ssh(setting_name, old_value, new_value): if old_value != new_value: regen_conf(names=["ssh"]) +@post_change_hook("security.ssh.port") +def reconfigure_ssh_and_fail2ban(setting_name, old_value, new_value): + if old_value != new_value: + regen_conf(names=["ssh", "fail2ban"]) + + @post_change_hook("smtp.allow_ipv6") @post_change_hook("smtp.relay.host") @post_change_hook("smtp.relay.port") From 95999feafbcadcd042447f35240edf93d4fc1ad5 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 15 Apr 2021 12:39:33 +0200 Subject: [PATCH 2389/3170] Also reload firewall when changing ssh port --- src/yunohost/settings.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/yunohost/settings.py b/src/yunohost/settings.py index dd43c4787..7e9eb76d9 100644 --- a/src/yunohost/settings.py +++ b/src/yunohost/settings.py @@ -9,6 +9,7 @@ from moulinette import m18n from yunohost.utils.error import YunohostError, YunohostValidationError from moulinette.utils.log import getActionLogger from yunohost.regenconf import regen_conf +from yunohost.firewall import firewall_reload logger = getActionLogger("yunohost.settings") @@ -397,6 +398,7 @@ def reconfigure_ssh(setting_name, old_value, new_value): def reconfigure_ssh_and_fail2ban(setting_name, old_value, new_value): if old_value != new_value: regen_conf(names=["ssh", "fail2ban"]) + firewall_reload() @post_change_hook("smtp.allow_ipv6") From c3754dd6fae6411f44c5d52ec8907a80f87e833c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 15 Apr 2021 14:27:40 +0200 Subject: [PATCH 2390/3170] reload fail2ban instead of restart ... restart is doing some funky stuff ... --- data/hooks/conf_regen/52-fail2ban | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/hooks/conf_regen/52-fail2ban b/data/hooks/conf_regen/52-fail2ban index e696df6c8..c96940c94 100755 --- a/data/hooks/conf_regen/52-fail2ban +++ b/data/hooks/conf_regen/52-fail2ban @@ -24,7 +24,7 @@ do_post_regen() { regen_conf_files=$1 [[ -z "$regen_conf_files" ]] \ - || service fail2ban restart + || systemctl reload fail2ban } FORCE=${2:-0} From 02a30125b578f22238ef2c4bae45f3eec6a9d94d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 15 Apr 2021 14:31:33 +0200 Subject: [PATCH 2391/3170] service foobar action -> systemctl action foobar --- data/hooks/conf_regen/06-slapd | 2 +- data/hooks/conf_regen/12-metronome | 2 +- data/hooks/conf_regen/19-postfix | 2 +- data/hooks/conf_regen/25-dovecot | 2 +- data/hooks/conf_regen/34-mysql | 2 +- data/hooks/conf_regen/37-avahi-daemon | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/data/hooks/conf_regen/06-slapd b/data/hooks/conf_regen/06-slapd index 695a31fd6..c23f1b155 100755 --- a/data/hooks/conf_regen/06-slapd +++ b/data/hooks/conf_regen/06-slapd @@ -147,7 +147,7 @@ do_post_regen() { su openldap -s "/bin/bash" -c "/usr/sbin/slapindex" echo "Reloading slapd" - service slapd force-reload + systemctl force-reload slapd # on slow hardware/vm this regen conf would exit before the admin user that # is stored in ldap is available because ldap seems to slow to restart diff --git a/data/hooks/conf_regen/12-metronome b/data/hooks/conf_regen/12-metronome index ca5d5dc82..9820f9881 100755 --- a/data/hooks/conf_regen/12-metronome +++ b/data/hooks/conf_regen/12-metronome @@ -67,7 +67,7 @@ do_post_regen() { chown -R metronome: /etc/metronome/conf.d/ [[ -z "$regen_conf_files" ]] \ - || service metronome restart + || systemctl restart metronome } FORCE=${2:-0} diff --git a/data/hooks/conf_regen/19-postfix b/data/hooks/conf_regen/19-postfix index 1af4f345f..166b5d5e9 100755 --- a/data/hooks/conf_regen/19-postfix +++ b/data/hooks/conf_regen/19-postfix @@ -76,7 +76,7 @@ do_post_regen() { fi [[ -z "$regen_conf_files" ]] \ - || { service postfix restart && service postsrsd restart; } + || { systemctl restart postfix && systemctl restart postsrsd; } } diff --git a/data/hooks/conf_regen/25-dovecot b/data/hooks/conf_regen/25-dovecot index ce2722bf4..916b88c35 100755 --- a/data/hooks/conf_regen/25-dovecot +++ b/data/hooks/conf_regen/25-dovecot @@ -60,7 +60,7 @@ do_post_regen() { chown -R vmail:mail /etc/dovecot/global_script } - service dovecot restart + systemctl restart dovecot } FORCE=${2:-0} diff --git a/data/hooks/conf_regen/34-mysql b/data/hooks/conf_regen/34-mysql index fd09d5188..d5180949e 100755 --- a/data/hooks/conf_regen/34-mysql +++ b/data/hooks/conf_regen/34-mysql @@ -66,7 +66,7 @@ do_post_regen() { fi [[ -z "$regen_conf_files" ]] \ - || service mysql restart + || systemctl restart mysql } FORCE=${2:-0} diff --git a/data/hooks/conf_regen/37-avahi-daemon b/data/hooks/conf_regen/37-avahi-daemon index 239c3ad0c..4127d66ca 100755 --- a/data/hooks/conf_regen/37-avahi-daemon +++ b/data/hooks/conf_regen/37-avahi-daemon @@ -15,7 +15,7 @@ do_post_regen() { regen_conf_files=$1 [[ -z "$regen_conf_files" ]] \ - || service avahi-daemon restart + || systemctl restart avahi-daemon } FORCE=${2:-0} From a0b32d5f1b0a5385540dd3480952ff2fb8f7601e Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Thu, 15 Apr 2021 17:56:09 +0200 Subject: [PATCH 2392/3170] [enh] add header to disallow FLoC https://diaspodon.fr/@etienne/106070042112522839 --- data/templates/nginx/security.conf.inc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/data/templates/nginx/security.conf.inc b/data/templates/nginx/security.conf.inc index 4b4f3fe5b..0d0b74db1 100644 --- a/data/templates/nginx/security.conf.inc +++ b/data/templates/nginx/security.conf.inc @@ -33,6 +33,9 @@ more_set_headers "X-Download-Options : noopen"; more_set_headers "X-Permitted-Cross-Domain-Policies : none"; more_set_headers "X-Frame-Options : SAMEORIGIN"; +# Disable the disaster privacy thing that is FLoC +more_set_headers "Permissions-Policy : interest-cohort=()"; + # Disable gzip to protect against BREACH # Read https://trac.nginx.org/nginx/ticket/1720 (text/html cannot be disabled!) gzip off; From f23982b460d65337ffc02aec40fdcfc993c01453 Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Thu, 15 Apr 2021 19:33:12 +0200 Subject: [PATCH 2393/3170] [fix] Remove zip from /etc/yunohost/apps/*/scripts --- data/helpers.d/utils | 1 + 1 file changed, 1 insertion(+) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 89821f7c2..6336999eb 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -176,6 +176,7 @@ ynh_setup_source () { else unzip -quo $src_filename -d "$dest_dir" fi + ynh_secure_remove --file="$src_filename" else local strip="" if [ "$src_in_subdir" != "false" ] From a3730477561a62b9f3a63fb94c88b625a27c07b1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 15 Apr 2021 22:27:05 +0200 Subject: [PATCH 2394/3170] More uniform tmp dir for apps, remove some weird 'admin' ownership --- src/yunohost/app.py | 156 +++++++++++++++-------------------------- src/yunohost/backup.py | 36 ++++------ 2 files changed, 72 insertions(+), 120 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index a3c10df0b..3ce73aeaa 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -33,6 +33,7 @@ import re import subprocess import glob import urllib.parse +import tempfile from collections import OrderedDict from moulinette import msignals, m18n, msettings @@ -57,10 +58,8 @@ from yunohost.log import is_unit_operation, OperationLogger logger = getActionLogger("yunohost.app") -APPS_PATH = "/usr/share/yunohost/apps" APPS_SETTING_PATH = "/etc/yunohost/apps/" -INSTALL_TMP = "/var/cache/yunohost" -APP_TMP_FOLDER = INSTALL_TMP + "/from_file" +APP_TMP_WORKDIRS = "/var/cache/yunohost/app_tmp_work_dirs" APPS_CATALOG_CACHE = "/var/cache/yunohost/repo" APPS_CATALOG_CONF = "/etc/yunohost/apps_catalog.yml" @@ -459,34 +458,12 @@ def app_change_url(operation_logger, app, domain, path): operation_logger.extra.update({"env": env_dict}) operation_logger.start() - if os.path.exists(os.path.join(APP_TMP_FOLDER, "scripts")): - shutil.rmtree(os.path.join(APP_TMP_FOLDER, "scripts")) - - shutil.copytree( - os.path.join(APPS_SETTING_PATH, app, "scripts"), - os.path.join(APP_TMP_FOLDER, "scripts"), - ) - - if os.path.exists(os.path.join(APP_TMP_FOLDER, "conf")): - shutil.rmtree(os.path.join(APP_TMP_FOLDER, "conf")) - - shutil.copytree( - os.path.join(APPS_SETTING_PATH, app, "conf"), - os.path.join(APP_TMP_FOLDER, "conf"), - ) + tmp_workdir_for_app = _make_tmp_workdir_for_app(app=app) + change_url_script = os.path.join(tmp_workdir_for_app, "scripts/change_url") # Execute App change_url script - os.system("chown -R admin: %s" % INSTALL_TMP) - os.system("chmod +x %s" % os.path.join(os.path.join(APP_TMP_FOLDER, "scripts"))) - os.system( - "chmod +x %s" - % os.path.join(os.path.join(APP_TMP_FOLDER, "scripts", "change_url")) - ) - - if ( - hook_exec(os.path.join(APP_TMP_FOLDER, "scripts/change_url"), env=env_dict)[0] - != 0 - ): + ret = hook_exec(change_url_script, env=env_dict)[0] + if ret != 0: msg = "Failed to change '%s' url." % app logger.error(msg) operation_logger.error(msg) @@ -496,6 +473,7 @@ def app_change_url(operation_logger, app, domain, path): app_setting(app, "domain", value=old_domain) app_setting(app, "path", value=old_path) return + shutil.rmtree(tmp_workdir_for_app) # this should idealy be done in the change_url script but let's avoid common mistakes app_setting(app, "domain", value=domain) @@ -620,7 +598,7 @@ def app_upgrade(app=[], url=None, file=None, force=False): _check_manifest_requirements(manifest, app_instance_name=app_instance_name) _assert_system_is_sane_for_app(manifest, "pre") - app_setting_path = APPS_SETTING_PATH + "/" + app_instance_name + app_setting_path = os.path.join(APPS_SETTING_PATH, app_instance_name) # Retrieve arguments list for upgrade script # TODO: Allow to specify arguments @@ -646,9 +624,6 @@ def app_upgrade(app=[], url=None, file=None, force=False): operation_logger = OperationLogger("app_upgrade", related_to, env=env_dict) operation_logger.start() - # Execute App upgrade script - os.system("chown -hR admin: %s" % INSTALL_TMP) - # Execute the app upgrade script upgrade_failed = True try: @@ -775,6 +750,12 @@ def app_upgrade(app=[], url=None, file=None, force=False): % (extracted_app_folder, file_to_copy, app_setting_path) ) + # Clean and set permissions + shutil.rmtree(extracted_app_folder) + os.system("chmod 600 %s" % app_setting_path) + os.system("chmod 400 %s/settings.yml" % app_setting_path) + os.system("chown -R root: %s" % app_setting_path) + # So much win logger.success(m18n.n("app_upgraded", app=app_instance_name)) @@ -816,10 +797,6 @@ def app_install( ) from yunohost.regenconf import manually_modified_files - # Fetch or extract sources - if not os.path.exists(INSTALL_TMP): - os.makedirs(INSTALL_TMP) - def confirm_install(confirm): # Ignore if there's nothing for confirm (good quality app), if --force is used # or if request on the API (confirm already implemented on the API side) @@ -953,10 +930,6 @@ def app_install( } _set_app_settings(app_instance_name, app_settings) - os.system("chown -R admin: " + extracted_app_folder) - - # Execute App install script - os.system("chown -hR admin: %s" % INSTALL_TMP) # Move scripts and manifest to the right place if os.path.exists(os.path.join(extracted_app_folder, "manifest.json")): os.system("cp %s/manifest.json %s" % (extracted_app_folder, app_setting_path)) @@ -1131,9 +1104,9 @@ def app_install( # Clean and set permissions shutil.rmtree(extracted_app_folder) - os.system("chmod -R 400 %s" % app_setting_path) + os.system("chmod 600 %s" % app_setting_path) + os.system("chmod 400 %s/settings.yml" % app_setting_path) os.system("chown -R root: %s" % app_setting_path) - os.system("chown -R admin: %s/scripts" % app_setting_path) logger.success(m18n.n("installation_complete")) @@ -1212,13 +1185,7 @@ def app_remove(operation_logger, app): logger.info(m18n.n("app_start_remove", app=app)) - app_setting_path = APPS_SETTING_PATH + app - - # TODO: display fail messages from script - try: - shutil.rmtree("/tmp/yunohost_remove") - except Exception: - pass + app_setting_path = os.path.join(APPS_SETTING_PATH, app) # Attempt to patch legacy helpers ... _patch_legacy_helpers(app_setting_path) @@ -1228,13 +1195,8 @@ def app_remove(operation_logger, app): _patch_legacy_php_versions(app_setting_path) manifest = _get_manifest_of_app(app_setting_path) - - os.system( - "cp -a %s /tmp/yunohost_remove && chown -hR admin: /tmp/yunohost_remove" - % app_setting_path - ) - os.system("chown -R admin: /tmp/yunohost_remove") - os.system("chmod -R u+rX /tmp/yunohost_remove") + tmp_workdir_for_app = _make_tmp_workdir_for_app(app=app) + remove_script = f"{tmp_workdir_for_app}/scripts/remove" env_dict = {} app_id, app_instance_nb = _parse_app_instance_name(app) @@ -1246,7 +1208,7 @@ def app_remove(operation_logger, app): operation_logger.flush() try: - ret = hook_exec("/tmp/yunohost_remove/scripts/remove", env=env_dict)[0] + ret = hook_exec(remove_script, env=env_dict)[0] # Here again, calling hook_exec could fail miserably, or get # manually interrupted (by mistake or because script was stuck) # In that case we still want to proceed with the rest of the @@ -1256,6 +1218,8 @@ def app_remove(operation_logger, app): import traceback logger.error(m18n.n("unexpected_error", error="\n" + traceback.format_exc())) + finally: + shutil.rmtree(tmp_workdir_for_app) if ret == 0: logger.success(m18n.n("app_removed", app=app)) @@ -1269,7 +1233,7 @@ def app_remove(operation_logger, app): if os.path.exists(app_setting_path): shutil.rmtree(app_setting_path) - shutil.rmtree("/tmp/yunohost_remove") + hook_remove(app) permission_sync_to_user() @@ -1717,7 +1681,6 @@ def app_action_run(operation_logger, app, action, args=None): logger.warning(m18n.n("experimental_feature")) from yunohost.hook import hook_exec - import tempfile # will raise if action doesn't exist actions = app_action_list(app)["actions"] @@ -1755,8 +1718,9 @@ def app_action_run(operation_logger, app, action, args=None): if action_declaration.get("cwd"): cwd = action_declaration["cwd"].replace("$app", app) else: - cwd = "/etc/yunohost/apps/" + app + cwd = os.path.join(APPS_SETTING_PATH, app) + # FIXME: this should probably be ran in a tmp workdir... retcode = hook_exec( path, env=env_dict, @@ -1811,6 +1775,7 @@ def app_config_show_panel(operation_logger, app): "YNH_APP_INSTANCE_NUMBER": str(app_instance_nb), } + # FIXME: this should probably be ran in a tmp workdir... return_code, parsed_values = hook_exec( config_script, args=["show"], env=env, return_format="plain_dict" ) @@ -1923,6 +1888,7 @@ def app_config_apply(operation_logger, app, args): "Ignore key '%s' from arguments because it is not in the config", key ) + # FIXME: this should probably be ran in a tmp workdir... return_code = hook_exec( config_script, args=["apply"], @@ -2237,43 +2203,28 @@ def _set_app_settings(app_id, settings): yaml.safe_dump(settings, f, default_flow_style=False) -def _extract_app_from_file(path, remove=False): +def _extract_app_from_file(path): """ - Unzip or untar application tarball in APP_TMP_FOLDER, or copy it from a directory + Unzip / untar / copy application tarball or directory to a tmp work directory Keyword arguments: path -- Path of the tarball or directory - remove -- Remove the tarball after extraction - - Returns: - Dict manifest - """ logger.debug(m18n.n("extracting")) - if os.path.exists(APP_TMP_FOLDER): - shutil.rmtree(APP_TMP_FOLDER) - os.makedirs(APP_TMP_FOLDER) - path = os.path.abspath(path) + extracted_app_folder = _make_tmp_workdir_for_app() + if ".zip" in path: - extract_result = os.system( - "unzip %s -d %s > /dev/null 2>&1" % (path, APP_TMP_FOLDER) - ) - if remove: - os.remove(path) + extract_result = os.system(f"unzip '{path}' -d {extracted_app_folder} > /dev/null 2>&1") elif ".tar" in path: - extract_result = os.system( - "tar -xf %s -C %s > /dev/null 2>&1" % (path, APP_TMP_FOLDER) - ) - if remove: - os.remove(path) + extract_result = os.system(f"tar -xf '{path}' -C {extracted_app_folder} > /dev/null 2>&1") elif os.path.isdir(path): - shutil.rmtree(APP_TMP_FOLDER) + shutil.rmtree(extracted_app_folder) if path[-1] != "/": path = path + "/" - extract_result = os.system('cp -a "%s" %s' % (path, APP_TMP_FOLDER)) + extract_result = os.system(f"cp -a '{path}' {extracted_app_folder}") else: extract_result = 1 @@ -2281,7 +2232,6 @@ def _extract_app_from_file(path, remove=False): raise YunohostError("app_extraction_failed") try: - extracted_app_folder = APP_TMP_FOLDER if len(os.listdir(extracted_app_folder)) == 1: for folder in os.listdir(extracted_app_folder): extracted_app_folder = extracted_app_folder + "/" + folder @@ -2508,24 +2458,11 @@ def _get_git_last_commit_hash(repository, reference="HEAD"): def _fetch_app_from_git(app): """ - Unzip or untar application tarball in APP_TMP_FOLDER + Unzip or untar application tarball to a tmp directory Keyword arguments: app -- App_id or git repo URL - - Returns: - Dict manifest - """ - extracted_app_folder = APP_TMP_FOLDER - - app_tmp_archive = "{0}.zip".format(extracted_app_folder) - if os.path.exists(extracted_app_folder): - shutil.rmtree(extracted_app_folder) - if os.path.exists(app_tmp_archive): - os.remove(app_tmp_archive) - - logger.debug(m18n.n("downloading")) # Extract URL, branch and revision to download if ("@" in app) or ("http://" in app) or ("https://" in app): @@ -2549,6 +2486,10 @@ def _fetch_app_from_git(app): branch = app_info["git"]["branch"] revision = str(app_info["git"]["revision"]) + extracted_app_folder = _make_tmp_workdir_for_app() + + logger.debug(m18n.n("downloading")) + # Download only this commit try: # We don't use git clone because, git clone can't download @@ -3384,6 +3325,23 @@ def _load_apps_catalog(): # ############################### # # +def _make_tmp_workdir_for_app(app=None): + + # Create parent dir if it doesn't exists yet + if not os.path.exists(APP_TMP_WORKDIRS): + os.makedirs(APP_TMP_WORKDIRS) + + # Cleanup old dirs + for dir_ in os.listdir(APP_TMP_WORKDIRS): + shutil.rmtree(os.path.join(APP_TMP_WORKDIRS, dir_)) + tmpdir = tempfile.mkdtemp(prefix="app_", dir=APP_TMP_WORKDIRS) + + # Copy existing app scripts, conf, ... if an app arg was provided + if app: + os.system(f"cp -a {APPS_SETTING_PATH}/{app}/* {tmpdir}") + + return tmpdir + def is_true(arg): """ diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index a17b752f6..92b668b6e 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -53,6 +53,7 @@ from yunohost.app import ( _patch_legacy_php_versions, _patch_legacy_php_versions_in_settings, LEGACY_PHP_VERSION_REPLACEMENTS, + _make_tmp_workdir_for_app ) from yunohost.hook import ( hook_list, @@ -644,7 +645,7 @@ class BackupManager: restore_hooks_dir = os.path.join(self.work_dir, "hooks", "restore") if not os.path.exists(restore_hooks_dir): - filesystem.mkdir(restore_hooks_dir, mode=0o750, parents=True, uid="admin") + filesystem.mkdir(restore_hooks_dir, mode=0o700, parents=True, uid="root") restore_hooks = hook_list("restore")["hooks"] @@ -705,21 +706,17 @@ class BackupManager: settings_dir = os.path.join(self.work_dir, "apps", app, "settings") logger.info(m18n.n("app_start_backup", app=app)) - tmp_folder = tempfile.mkdtemp() + tmp_workdir_for_app = _make_tmp_workdir_for_app(app=app) try: # Prepare backup directory for the app - filesystem.mkdir(tmp_app_bkp_dir, 0o750, True, uid="admin") + filesystem.mkdir(tmp_app_bkp_dir, 0o700, True, uid="root") # Copy the app settings to be able to call _common.sh shutil.copytree(app_setting_path, settings_dir) - # Copy app backup script in a temporary folder and execute it - app_script = os.path.join(app_setting_path, "scripts/backup") - tmp_script = os.path.join(tmp_folder, "backup") - subprocess.call(["install", "-Dm555", app_script, tmp_script]) - hook_exec( - tmp_script, raise_on_error=True, chdir=tmp_app_bkp_dir, env=env_dict + f"{tmp_workdir_for_app}/scripts/backup", + raise_on_error=True, chdir=tmp_app_bkp_dir, env=env_dict )[0] self._import_to_list_to_backup(env_dict["YNH_BACKUP_CSV"]) @@ -750,8 +747,7 @@ class BackupManager: # Remove tmp files in all situations finally: - if tmp_folder and os.path.exists(tmp_folder): - shutil.rmtree(tmp_folder) + shutil.rmtree(tmp_workdir_for_app) filesystem.rm(env_dict["YNH_BACKUP_CSV"], force=True) # @@ -1402,13 +1398,11 @@ class RestoreManager: filesystem.chown(app_scripts_new_path, "root", None, True) # Copy the app scripts to a writable temporary folder - # FIXME : use 'install -Dm555' or something similar to what's done - # in the backup method ? - tmp_folder_for_app_restore = tempfile.mkdtemp(prefix="restore") - copytree(app_scripts_in_archive, tmp_folder_for_app_restore) - filesystem.chmod(tmp_folder_for_app_restore, 0o550, 0o550, True) - filesystem.chown(tmp_folder_for_app_restore, "root", None, True) - restore_script = os.path.join(tmp_folder_for_app_restore, "restore") + tmp_workdir_for_app = _make_tmp_workdir_for_app() + copytree(app_scripts_in_archive, tmp_workdir_for_app) + filesystem.chmod(tmp_workdir_for_app, 0o700, 0o700, True) + filesystem.chown(tmp_workdir_for_app, "root", None, True) + restore_script = os.path.join(tmp_workdir_for_app, "restore") # Restore permissions if not os.path.isfile("%s/permissions.yml" % app_settings_new_path): @@ -1463,7 +1457,7 @@ class RestoreManager: # Cleanup shutil.rmtree(app_settings_new_path, ignore_errors=True) - shutil.rmtree(tmp_folder_for_app_restore, ignore_errors=True) + shutil.rmtree(tmp_workdir_for_app, ignore_errors=True) return @@ -1513,7 +1507,7 @@ class RestoreManager: failure_message_with_debug_instructions = operation_logger.error(error) finally: # Cleaning temporary scripts directory - shutil.rmtree(tmp_folder_for_app_restore, ignore_errors=True) + shutil.rmtree(tmp_workdir_for_app, ignore_errors=True) if not restore_failed: self.targets.set_result("apps", app_instance_name, "Success") @@ -1869,7 +1863,7 @@ class CopyBackupMethod(BackupMethod): dest_parent = os.path.dirname(dest) if not os.path.exists(dest_parent): - filesystem.mkdir(dest_parent, 0o750, True, uid="admin") + filesystem.mkdir(dest_parent, 0o700, True, uid="admin") if os.path.isdir(source): shutil.copytree(source, dest) From 9adf5e522cb3a40d407101223f06571f3580976c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 16 Apr 2021 00:32:25 +0200 Subject: [PATCH 2395/3170] Add app_manifest to fetch manifest of an app from the catalog or using url --- data/actionsmap/yunohost.yml | 10 +++++++++- src/yunohost/app.py | 15 +++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 4526989df..e0eb8893c 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -608,6 +608,14 @@ app: string: help: Return matching app name or description with "string" + ### app_manifest() + manifest: + action_help: Return the manifest of a given app from the manifest, or from a remote git repo + api: GET /apps/manifest + arguments: + app: + help: Name, local path or git URL of the app to fetch the manifest of + fetchlist: deprecated: true @@ -679,7 +687,7 @@ app: help: Do not ask confirmation if the app is not safe to use (low quality, experimental or 3rd party) action: store_true - ### app_remove() TODO: Write help + ### app_remove() remove: action_help: Remove app api: DELETE /apps/ diff --git a/src/yunohost/app.py b/src/yunohost/app.py index a3c10df0b..af23e9352 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -786,6 +786,21 @@ def app_upgrade(app=[], url=None, file=None, force=False): logger.success(m18n.n("upgrade_complete")) +def app_manifest(app): + + raw_app_list = _load_apps_catalog()["apps"] + + if app in raw_app_list or ("@" in app) or ("http://" in app) or ("https://" in app): + manifest, extracted_app_folder = _fetch_app_from_git(app) + elif os.path.exists(app): + manifest, extracted_app_folder = _extract_app_from_file(app) + else: + raise YunohostValidationError("app_unknown") + + shutil.rmtree(extracted_app_folder) + + return manifest + @is_unit_operation() def app_install( operation_logger, From be6c39bf8c8caf30f4457975435cc5b6dd0d0364 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 16 Apr 2021 00:34:56 +0200 Subject: [PATCH 2396/3170] No need for Github CSP rule in the webadmin anymore --- data/templates/nginx/plain/yunohost_admin.conf.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/templates/nginx/plain/yunohost_admin.conf.inc b/data/templates/nginx/plain/yunohost_admin.conf.inc index 326e003ee..4b9938eac 100644 --- a/data/templates/nginx/plain/yunohost_admin.conf.inc +++ b/data/templates/nginx/plain/yunohost_admin.conf.inc @@ -6,6 +6,6 @@ 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'; img-src 'self' data:;"; + more_set_headers "Content-Security-Policy: upgrade-insecure-requests; default-src 'self'; connect-src 'self' https://paste.yunohost.org wss://$host; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-eval'; object-src 'none'; img-src 'self' data:;"; more_set_headers "Content-Security-Policy-Report-Only:"; } From 9d64e850b88d488e3a15957040ee570fac1518e4 Mon Sep 17 00:00:00 2001 From: Kayou Date: Fri, 16 Apr 2021 01:03:34 +0200 Subject: [PATCH 2397/3170] Update src/yunohost/app.py Co-authored-by: ljf (zamentur) --- 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 5ad2e4021..129f18c67 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -3638,7 +3638,7 @@ def _patch_legacy_helpers(app_folder): try: content = read_file(filename) - except Exception: + except MoulinetteError: continue replaced_stuff = False From e1f7a5fa4c0018882a48e9d0a04399c0b6cfec52 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 16 Apr 2021 02:34:52 +0200 Subject: [PATCH 2398/3170] Fix help string --- data/actionsmap/yunohost.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index e0eb8893c..55033b54c 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -610,7 +610,7 @@ app: ### app_manifest() manifest: - action_help: Return the manifest of a given app from the manifest, or from a remote git repo + action_help: Return the manifest of a given app from the catalog, or from a remote git repo api: GET /apps/manifest arguments: app: From d77866621b5d2e91e23b2bcf826ec71da995c21a Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Fri, 16 Apr 2021 02:36:00 +0200 Subject: [PATCH 2399/3170] [mod] correctly format .gitlab-ci.yml --- .gitlab-ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index eb34de38e..557b4e86e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,3 +1,4 @@ +--- stages: - build - install @@ -12,7 +13,7 @@ default: interruptible: true variables: - YNH_BUILD_DIR: "ynh-build" + YNH_BUILD_DIR: "ynh-build" include: - local: .gitlab/ci/build.gitlab-ci.yml From f9419c9606079fd9c522436fa939844123177930 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Fri, 16 Apr 2021 02:39:46 +0200 Subject: [PATCH 2400/3170] [fix] correctly format .gitlab/ci/lint.gitlab-ci.yml --- .gitlab/ci/lint.gitlab-ci.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitlab/ci/lint.gitlab-ci.yml b/.gitlab/ci/lint.gitlab-ci.yml index 72faaaf2c..185721810 100644 --- a/.gitlab/ci/lint.gitlab-ci.yml +++ b/.gitlab/ci/lint.gitlab-ci.yml @@ -3,6 +3,7 @@ ######################################## # later we must fix lint and format-check jobs and remove "allow_failure" +--- lint37: stage: lint image: "before-install" @@ -43,5 +44,5 @@ format-run: - hub commit -am "[CI] Format code" || true - hub pull-request -m "[CI] Format code" -b Yunohost:dev -p || true # GITHUB_USER and GITHUB_TOKEN registered here https://gitlab.com/yunohost/yunohost/-/settings/ci_cd only: - refs: - - dev \ No newline at end of file + refs: + - dev From c92e495b21cc9dae1badca329fb3174aaf545d2c Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Fri, 16 Apr 2021 02:45:06 +0200 Subject: [PATCH 2401/3170] [fix] first attempt to fix auto sending of black PR --- .gitlab/ci/lint.gitlab-ci.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.gitlab/ci/lint.gitlab-ci.yml b/.gitlab/ci/lint.gitlab-ci.yml index 185721810..8f5fc3058 100644 --- a/.gitlab/ci/lint.gitlab-ci.yml +++ b/.gitlab/ci/lint.gitlab-ci.yml @@ -38,10 +38,11 @@ format-run: - hub clone --branch ${CI_COMMIT_REF_NAME} "https://$GITHUB_TOKEN:x-oauth-basic@github.com/YunoHost/yunohost.git" github_repo - cd github_repo script: - # checkout or create and checkout the branch - - hub checkout "ci-format-${CI_COMMIT_REF_NAME}" || hub checkout -b "ci-format-${CI_COMMIT_REF_NAME}" + # create a local branch that will overwrite distant one + - git checkout -b "ci-format-${CI_COMMIT_REF_NAME}" --no-track - tox -e py37-black-run - - hub commit -am "[CI] Format code" || true + - git commit -am "[CI] Format code" || true + - git push -f origin "ci-format-${CI_COMMIT_REF_NAME}":"ci-format-${CI_COMMIT_REF_NAME}" - hub pull-request -m "[CI] Format code" -b Yunohost:dev -p || true # GITHUB_USER and GITHUB_TOKEN registered here https://gitlab.com/yunohost/yunohost/-/settings/ci_cd only: refs: From 0616d6322203d87c3baee504f2e88dc44bca8129 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 16 Apr 2021 14:33:17 +0200 Subject: [PATCH 2402/3170] Try to improve the catastrophic error management in domain_add ... --- src/yunohost/domain.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 8d8be57a0..4543bbcd9 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -99,6 +99,7 @@ def domain_add(operation_logger, domain, dyndns=False): from yunohost.hook import hook_callback from yunohost.app import app_ssowatconf from yunohost.utils.ldap import _get_ldap_interface + from yunohost.certificate import _certificate_install_selfsigned if domain.startswith("xmpp-upload."): raise YunohostValidationError("domain_cannot_add_xmpp_upload") @@ -135,11 +136,9 @@ def domain_add(operation_logger, domain, dyndns=False): # Actually subscribe dyndns_subscribe(domain=domain) + _certificate_install_selfsigned([domain], False) + try: - import yunohost.certificate - - yunohost.certificate._certificate_install_selfsigned([domain], False) - attr_dict = { "objectClass": ["mailDomain", "top"], "virtualdomain": domain, @@ -166,13 +165,13 @@ def domain_add(operation_logger, domain, dyndns=False): regen_conf(names=["nginx", "metronome", "dnsmasq", "postfix", "rspamd"]) app_ssowatconf() - except Exception: + except Exception as e: # Force domain removal silently try: domain_remove(domain, force=True) except Exception: pass - raise + raise e hook_callback("post_domain_add", args=[domain]) From 4a0b343e5e7c2a8c4557ddbba51c93d93be27774 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 16 Apr 2021 22:01:22 +0200 Subject: [PATCH 2403/3170] Fix/update migration script, handle applying the new migration during restore --- .../0019_extend_permissions_features.py | 55 +------------------ .../0020_ssh_sftp_permissions.py | 11 +++- 2 files changed, 10 insertions(+), 56 deletions(-) diff --git a/src/yunohost/data_migrations/0019_extend_permissions_features.py b/src/yunohost/data_migrations/0019_extend_permissions_features.py index 3511cbb0b..240894202 100644 --- a/src/yunohost/data_migrations/0019_extend_permissions_features.py +++ b/src/yunohost/data_migrations/0019_extend_permissions_features.py @@ -91,7 +91,7 @@ class MyMigration(Migration): # Update LDAP database self.add_new_ldap_attributes() - def run_before_system_restore(self, app_id): + def run_before_app_restore(self, app_id): from yunohost.app import app_setting from yunohost.utils.legacy import migrate_legacy_permission_settings @@ -109,56 +109,3 @@ class MyMigration(Migration): for setting in legacy_permission_settings ): migrate_legacy_permission_settings(app=app_id) - - - def run(self): - - # FIXME : what do we really want to do here ... - # Imho we should just force-regen the conf in all case, and maybe - # just display a warning if we detect that the conf was manually modified - - # Backup LDAP and the apps settings before to do the migration - logger.info(m18n.n("migration_0019_backup_before_migration")) - try: - backup_folder = "/home/yunohost.backup/premigration/" + time.strftime( - "%Y%m%d-%H%M%S", time.gmtime() - ) - os.makedirs(backup_folder, 0o750) - os.system("systemctl stop slapd") - os.system("cp -r --preserve /etc/ldap %s/ldap_config" % backup_folder) - os.system("cp -r --preserve /var/lib/ldap %s/ldap_db" % backup_folder) - os.system( - "cp -r --preserve /etc/yunohost/apps %s/apps_settings" % backup_folder - ) - except Exception as e: - raise YunohostError( - "migration_0019_can_not_backup_before_migration", error=e - ) - finally: - os.system("systemctl start slapd") - - try: - # Update LDAP database - self.add_new_ldap_attributes() - - # Migrate old settings - migrate_legacy_permission_settings() - - except Exception: - logger.warn(m18n.n("migration_0019_migration_failed_trying_to_rollback")) - os.system("systemctl stop slapd") - os.system( - "rm -r /etc/ldap/slapd.d" - ) # To be sure that we don't keep some part of the old config - os.system("cp -r --preserve %s/ldap_config/. /etc/ldap/" % backup_folder) - os.system("cp -r --preserve %s/ldap_db/. /var/lib/ldap/" % backup_folder) - os.system( - "cp -r --preserve %s/apps_settings/. /etc/yunohost/apps/" - % backup_folder - ) - os.system("systemctl start slapd") - os.system("rm -r " + backup_folder) - logger.info(m18n.n("migration_0019_rollback_success")) - raise - else: - os.system("rm -r " + backup_folder) \ No newline at end of file diff --git a/src/yunohost/data_migrations/0020_ssh_sftp_permissions.py b/src/yunohost/data_migrations/0020_ssh_sftp_permissions.py index d3368e4a0..97d4ee2fd 100644 --- a/src/yunohost/data_migrations/0020_ssh_sftp_permissions.py +++ b/src/yunohost/data_migrations/0020_ssh_sftp_permissions.py @@ -19,6 +19,7 @@ class MyMigration(Migration): Add new permissions around SSH/SFTP features """ + introduced_in_version = "4.2.2" dependencies = ["extend_permissions_features"] @Migration.ldap_migration @@ -37,14 +38,20 @@ class MyMigration(Migration): users = ldap.search('ou=users,dc=yunohost,dc=org', filter="(loginShell=*)", attrs=["dn", "uid", "loginShell"]) for user in users: if user['loginShell'][0] == '/bin/false': - dn=user['dn'][0].replace(',dc=yunohost,dc=org', '') + dn = user['dn'][0].replace(',dc=yunohost,dc=org', '') ldap.update(dn, {'loginShell': ['/bin/bash']}) else: user_permission_update("ssh.main", add=user["uid"][0], sync_perm=False) permission_sync_to_user() - # Somehow this is needed otherwise the PAM thing doesn't forget about the # old loginShell value ? subprocess.call(['nscd', '-i', 'passwd']) + + def run_after_system_restore(self): + self.run() + + def run_before_app_restore(self, app_id): + # Nothing to do during app backup restore for this migration + pass From c53f5ac16ae98e63105b2bb7790b1572124d23f5 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 16 Apr 2021 22:05:36 +0200 Subject: [PATCH 2404/3170] Report an error in the diagnosis and migration if sshd config is insecure --- data/hooks/diagnosis/70-regenconf.py | 10 ++++++++++ locales/en.json | 1 + .../data_migrations/0020_ssh_sftp_permissions.py | 6 ++++++ 3 files changed, 17 insertions(+) diff --git a/data/hooks/diagnosis/70-regenconf.py b/data/hooks/diagnosis/70-regenconf.py index 5ab1e3808..b8551f5fe 100644 --- a/data/hooks/diagnosis/70-regenconf.py +++ b/data/hooks/diagnosis/70-regenconf.py @@ -35,6 +35,16 @@ class RegenconfDiagnoser(Diagnoser): details=["diagnosis_regenconf_manually_modified_details"], ) + if any(f["path"] == '/etc/ssh/sshd_config' for f in regenconf_modified_files) \ + and os.system("grep -q '^ *AllowGroups\\|^ *AllowUsers' /etc/ssh/sshd_config") != 0: + yield dict( + meta={ + "test": "sshd_config_insecure" + }, + status="ERROR", + summary="diagnosis_sshd_config_insecure", + ) + def manually_modified_files(self): for category, infos in _get_regenconf_infos().items(): diff --git a/locales/en.json b/locales/en.json index 027fe981e..840d359ed 100644 --- a/locales/en.json +++ b/locales/en.json @@ -269,6 +269,7 @@ "diagnosis_unknown_categories": "The following categories are unknown: {categories}", "diagnosis_never_ran_yet": "It looks like this server was setup recently and there's no diagnosis report to show yet. You should start by running a full diagnosis, either from the webadmin or using 'yunohost diagnosis run' from the command line.", "diagnosis_processes_killed_by_oom_reaper": "Some processes were recently killed by the system because it ran out of memory. This is typically symptomatic of a lack of memory on the system or of a process that ate up to much memory. Summary of the processes killed:\n{kills_summary}", + "diagnosis_sshd_config_insecure": "The SSH configuration appears to have been manually modified, and is insecure because it contains no 'AllowGroups' or 'AllowUsers' directive to limit access to authorized users.", "domain_cannot_remove_main": "You cannot remove '{domain:s}' since it's the main domain, you first need to set another domain as the main domain using 'yunohost domain main-domain -n '; here is the list of candidate domains: {other_domains:s}", "domain_cannot_add_xmpp_upload": "You cannot add domains starting with 'xmpp-upload.'. This kind of name is reserved for the XMPP upload feature integrated in YunoHost.", "domain_cannot_remove_main_add_new_one": "You cannot remove '{domain:s}' since it's the main domain and your only domain, you need to first add another domain using 'yunohost domain add ', then set is as the main domain using 'yunohost domain main-domain -n ' and then you can remove the domain '{domain:s}' using 'yunohost domain remove {domain:s}'.'", diff --git a/src/yunohost/data_migrations/0020_ssh_sftp_permissions.py b/src/yunohost/data_migrations/0020_ssh_sftp_permissions.py index 97d4ee2fd..52d813d32 100644 --- a/src/yunohost/data_migrations/0020_ssh_sftp_permissions.py +++ b/src/yunohost/data_migrations/0020_ssh_sftp_permissions.py @@ -1,4 +1,5 @@ import subprocess +import os from moulinette import m18n from moulinette.utils.log import getActionLogger @@ -6,6 +7,7 @@ from moulinette.utils.filesystem import read_yaml from yunohost.tools import Migration from yunohost.permission import user_permission_update, permission_sync_to_user +from yunohost.regenconf import manually_modified_files logger = getActionLogger('yunohost.migration') @@ -49,6 +51,10 @@ class MyMigration(Migration): # old loginShell value ? subprocess.call(['nscd', '-i', 'passwd']) + if '/etc/ssh/sshd_config' in manually_modified_files() \ + and os.system("grep -q '^ *AllowGroups\\|^ *AllowUsers' /etc/ssh/sshd_config") != 0: + logger.error(m18n.n('diagnosis_sshd_config_insecure')) + def run_after_system_restore(self): self.run() From 8f3a7067d9a024444edfe76b7414f5fa5c264a4a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 16 Apr 2021 22:20:06 +0200 Subject: [PATCH 2405/3170] Advertise the new SSH port setting for people that manually modified the SSH port --- data/hooks/diagnosis/70-regenconf.py | 19 ++++++++++++++++++- locales/en.json | 2 ++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/data/hooks/diagnosis/70-regenconf.py b/data/hooks/diagnosis/70-regenconf.py index b8551f5fe..4e40c71fb 100644 --- a/data/hooks/diagnosis/70-regenconf.py +++ b/data/hooks/diagnosis/70-regenconf.py @@ -1,10 +1,12 @@ #!/usr/bin/env python import os +import re +from yunohost.settings import settings_get from yunohost.diagnosis import Diagnoser from yunohost.regenconf import _get_regenconf_infos, _calculate_hash - +from moulinette.utils.filesystem import read_file class RegenconfDiagnoser(Diagnoser): @@ -45,6 +47,21 @@ class RegenconfDiagnoser(Diagnoser): summary="diagnosis_sshd_config_insecure", ) + # Check consistency between actual ssh port in sshd_config vs. setting + ssh_port_setting = settings_get('security.ssh.port') + ssh_port_line = re.findall( + r"\bPort *([0-9]{2,5})\b", read_file("/etc/ssh/sshd_config") + ) + if len(ssh_port_line) == 1 and int(ssh_port_line[0]) != ssh_port_setting: + yield dict( + meta={ + "test": "sshd_config_port_inconsistency" + }, + status="WARNING", + summary="diagnosis_sshd_config_inconsistent", + details=["diagnosis_sshd_config_inconsistent_details"], + ) + def manually_modified_files(self): for category, infos in _get_regenconf_infos().items(): diff --git a/locales/en.json b/locales/en.json index 840d359ed..63d5c6b10 100644 --- a/locales/en.json +++ b/locales/en.json @@ -270,6 +270,8 @@ "diagnosis_never_ran_yet": "It looks like this server was setup recently and there's no diagnosis report to show yet. You should start by running a full diagnosis, either from the webadmin or using 'yunohost diagnosis run' from the command line.", "diagnosis_processes_killed_by_oom_reaper": "Some processes were recently killed by the system because it ran out of memory. This is typically symptomatic of a lack of memory on the system or of a process that ate up to much memory. Summary of the processes killed:\n{kills_summary}", "diagnosis_sshd_config_insecure": "The SSH configuration appears to have been manually modified, and is insecure because it contains no 'AllowGroups' or 'AllowUsers' directive to limit access to authorized users.", + "diagnosis_sshd_config_inconsistent": "It looks like the SSH port was manually modified in /etc/ssh/sshd_config. Since Yunohost 4.2, a new global setting 'security.ssh.port' is available to avoid manually editing the configuration.", + "diagnosis_sshd_config_inconsistent_details": "Please run yunohost settings set security.ssh.port -v YOUR_SSH_PORT to define the SSH port, and check yunohost tools regen-conf ssh --dry-run --with-diff and yunohost tools regen-conf ssh --force to reset your conf to the Yunohost recommendation.", "domain_cannot_remove_main": "You cannot remove '{domain:s}' since it's the main domain, you first need to set another domain as the main domain using 'yunohost domain main-domain -n '; here is the list of candidate domains: {other_domains:s}", "domain_cannot_add_xmpp_upload": "You cannot add domains starting with 'xmpp-upload.'. This kind of name is reserved for the XMPP upload feature integrated in YunoHost.", "domain_cannot_remove_main_add_new_one": "You cannot remove '{domain:s}' since it's the main domain and your only domain, you need to first add another domain using 'yunohost domain add ', then set is as the main domain using 'yunohost domain main-domain -n ' and then you can remove the domain '{domain:s}' using 'yunohost domain remove {domain:s}'.'", From aae7c6e2966abb43702cb65dffb15c9aa888fd4e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 16 Apr 2021 22:34:39 +0200 Subject: [PATCH 2406/3170] Unused imports --- .../data_migrations/0019_extend_permissions_features.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/yunohost/data_migrations/0019_extend_permissions_features.py b/src/yunohost/data_migrations/0019_extend_permissions_features.py index 240894202..5d4343deb 100644 --- a/src/yunohost/data_migrations/0019_extend_permissions_features.py +++ b/src/yunohost/data_migrations/0019_extend_permissions_features.py @@ -1,8 +1,4 @@ -import time -import os - from moulinette import m18n -from yunohost.utils.error import YunohostError from moulinette.utils.log import getActionLogger from yunohost.tools import Migration From 6e4f1fa42eba56d952949b45be5dd47c395d8fda Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 17 Apr 2021 00:49:17 +0200 Subject: [PATCH 2407/3170] Stupid tmp fix to try to track why the tests are failing --- src/yunohost/app.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index a3c10df0b..65800ea97 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -3457,6 +3457,11 @@ def _assert_system_is_sane_for_app(manifest, when): # List services currently down and raise an exception if any are found services_status = {s:service_status(s) for s in services} faulty_services = [f"{s} ({status['status']})" for s, status in services_status.items() if status['status'] != "running"] + + # Stupid tmp fix to try to track why the tests are failing + if "php7.3-fpm" in [s for s, status in services_status.items() if status['status'] != "running"]: + os.system("journalctl -u php7.3-fpm -n 300 --no-hostname --no-pager") + if faulty_services: if when == "pre": raise YunohostValidationError( From 72e4a584ed19eb9f2bb195a589625f83c90e2741 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 17 Apr 2021 00:58:12 +0200 Subject: [PATCH 2408/3170] Be more robust against re-running the migration --- .../0020_ssh_sftp_permissions.py | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/src/yunohost/data_migrations/0020_ssh_sftp_permissions.py b/src/yunohost/data_migrations/0020_ssh_sftp_permissions.py index 52d813d32..c3b7a91ec 100644 --- a/src/yunohost/data_migrations/0020_ssh_sftp_permissions.py +++ b/src/yunohost/data_migrations/0020_ssh_sftp_permissions.py @@ -30,26 +30,32 @@ class MyMigration(Migration): from yunohost.utils.ldap import _get_ldap_interface ldap = _get_ldap_interface() + existing_perms_raw = ldap.search("ou=permission,dc=yunohost,dc=org", "(objectclass=permissionYnh)", ["cn"]) + existing_perms = [perm['cn'][0] for perm in existing_perms_raw] + # Add SSH and SFTP permissions ldap_map = read_yaml('/usr/share/yunohost/yunohost-config/moulinette/ldap_scheme.yml') - ldap.add("cn=ssh.main,ou=permission", ldap_map['depends_children']["cn=ssh.main,ou=permission"]) - ldap.add("cn=sftp.main,ou=permission", ldap_map['depends_children']["cn=sftp.main,ou=permission"]) + if "sftp.main" not in existing_perms: + ldap.add("cn=sftp.main,ou=permission", ldap_map['depends_children']["cn=sftp.main,ou=permission"]) - # Add a bash terminal to each users - users = ldap.search('ou=users,dc=yunohost,dc=org', filter="(loginShell=*)", attrs=["dn", "uid", "loginShell"]) - for user in users: - if user['loginShell'][0] == '/bin/false': - dn = user['dn'][0].replace(',dc=yunohost,dc=org', '') - ldap.update(dn, {'loginShell': ['/bin/bash']}) - else: - user_permission_update("ssh.main", add=user["uid"][0], sync_perm=False) + if "ssh.main" not in existing_perms: + ldap.add("cn=ssh.main,ou=permission", ldap_map['depends_children']["cn=ssh.main,ou=permission"]) - permission_sync_to_user() + # Add a bash terminal to each users + users = ldap.search('ou=users,dc=yunohost,dc=org', filter="(loginShell=*)", attrs=["dn", "uid", "loginShell"]) + for user in users: + if user['loginShell'][0] == '/bin/false': + dn = user['dn'][0].replace(',dc=yunohost,dc=org', '') + ldap.update(dn, {'loginShell': ['/bin/bash']}) + else: + user_permission_update("ssh.main", add=user["uid"][0], sync_perm=False) - # Somehow this is needed otherwise the PAM thing doesn't forget about the - # old loginShell value ? - subprocess.call(['nscd', '-i', 'passwd']) + permission_sync_to_user() + + # Somehow this is needed otherwise the PAM thing doesn't forget about the + # old loginShell value ? + subprocess.call(['nscd', '-i', 'passwd']) if '/etc/ssh/sshd_config' in manually_modified_files() \ and os.system("grep -q '^ *AllowGroups\\|^ *AllowUsers' /etc/ssh/sshd_config") != 0: From 3b35f6102829db24eee9d6f7608b304a5a026729 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 17 Apr 2021 01:00:28 +0200 Subject: [PATCH 2409/3170] Fix DepreciationWarning --- src/yunohost/tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 29456f959..a7eb6281d 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -1205,7 +1205,7 @@ class Migration(object): try: run(self, backup_folder) except Exception: - logger.warn(m18n.n("migration_ldap_migration_failed_trying_to_rollback")) + logger.warning(m18n.n("migration_ldap_migration_failed_trying_to_rollback")) os.system("systemctl stop slapd") # To be sure that we don't keep some part of the old config os.system("rm -r /etc/ldap/slapd.d") From 16e20fed7774e1130ee5a291400fa00656fd9be1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 17 Apr 2021 01:10:58 +0200 Subject: [PATCH 2410/3170] Don't re-run migration if backup is from the same version (mainly to avoid weird stuff during tests) --- src/yunohost/tools.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index a7eb6281d..8fb65bac8 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -1122,9 +1122,15 @@ def _tools_migrations_run_after_system_restore(backup_version): all_migrations = _get_migrations_list() + current_version = version.parse(ynh_packages_version()["yunohost"]["version"]) + backup_version = version.parse(backup_version) + + if backup_version == current_version: + return + for migration in all_migrations: if hasattr(migration, "introduced_in_version") \ - and version.parse(migration.introduced_in_version) > version.parse(backup_version) \ + and version.parse(migration.introduced_in_version) > backup_version \ and hasattr(migration, "run_after_system_restore"): try: logger.info(m18n.n("migrations_running_forward", id=migration.id)) @@ -1141,9 +1147,15 @@ def _tools_migrations_run_before_app_restore(backup_version, app_id): all_migrations = _get_migrations_list() + current_version = version.parse(ynh_packages_version()["yunohost"]["version"]) + backup_version = version.parse(backup_version) + + if backup_version == current_version: + return + for migration in all_migrations: if hasattr(migration, "introduced_in_version") \ - and version.parse(migration.introduced_in_version) > version.parse(backup_version) \ + and version.parse(migration.introduced_in_version) > backup_version \ and hasattr(migration, "run_before_app_restore"): try: logger.info(m18n.n("migrations_running_forward", id=migration.id)) From daa0c95015a5fdec0fd53a212b06b6a87f584047 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 17 Apr 2021 01:18:23 +0200 Subject: [PATCH 2411/3170] More stupid trick to dump what's wrong with php7.3-fpm during tests... --- src/yunohost/app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 65800ea97..faaed3baa 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -3460,6 +3460,7 @@ def _assert_system_is_sane_for_app(manifest, when): # Stupid tmp fix to try to track why the tests are failing if "php7.3-fpm" in [s for s, status in services_status.items() if status['status'] != "running"]: + logger.info([status for s, status in services_status.items() if status['status'] != "running"]) os.system("journalctl -u php7.3-fpm -n 300 --no-hostname --no-pager") if faulty_services: From 66f2613518c351f172f96071aeebf7c44210ef60 Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Sat, 17 Apr 2021 00:31:06 +0000 Subject: [PATCH 2412/3170] [CI] Format code --- data/hooks/diagnosis/50-systemresources.py | 4 +- data/hooks/diagnosis/70-regenconf.py | 34 ++++---- src/yunohost/__init__.py | 78 ++++++++--------- src/yunohost/app.py | 59 +++++++++---- src/yunohost/backup.py | 86 +++++++++++++------ src/yunohost/certificate.py | 9 +- .../0020_ssh_sftp_permissions.py | 54 ++++++++---- src/yunohost/diagnosis.py | 4 +- src/yunohost/domain.py | 41 ++++++--- src/yunohost/dyndns.py | 18 ++-- src/yunohost/firewall.py | 12 ++- src/yunohost/log.py | 17 +++- src/yunohost/permission.py | 18 +++- src/yunohost/settings.py | 14 ++- src/yunohost/ssh.py | 6 +- src/yunohost/tests/test_apps.py | 4 +- src/yunohost/tests/test_settings.py | 2 +- src/yunohost/tools.py | 55 ++++++++---- src/yunohost/user.py | 32 ++++--- 19 files changed, 365 insertions(+), 182 deletions(-) diff --git a/data/hooks/diagnosis/50-systemresources.py b/data/hooks/diagnosis/50-systemresources.py index a8f3cb6df..a662e392e 100644 --- a/data/hooks/diagnosis/50-systemresources.py +++ b/data/hooks/diagnosis/50-systemresources.py @@ -77,7 +77,9 @@ class SystemResourcesDiagnoser(Diagnoser): # Ignore /dev/loop stuff which are ~virtual partitions ? (e.g. mounted to /snap/) disk_partitions = [ - d for d in disk_partitions if d.mountpoint in ["/", "/var"] or not d.device.startswith("/dev/loop") + d + for d in disk_partitions + if d.mountpoint in ["/", "/var"] or not d.device.startswith("/dev/loop") ] for disk_partition in disk_partitions: diff --git a/data/hooks/diagnosis/70-regenconf.py b/data/hooks/diagnosis/70-regenconf.py index 4e40c71fb..8ccbeed58 100644 --- a/data/hooks/diagnosis/70-regenconf.py +++ b/data/hooks/diagnosis/70-regenconf.py @@ -8,6 +8,7 @@ from yunohost.diagnosis import Diagnoser from yunohost.regenconf import _get_regenconf_infos, _calculate_hash from moulinette.utils.filesystem import read_file + class RegenconfDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] @@ -37,29 +38,30 @@ class RegenconfDiagnoser(Diagnoser): details=["diagnosis_regenconf_manually_modified_details"], ) - if any(f["path"] == '/etc/ssh/sshd_config' for f in regenconf_modified_files) \ - and os.system("grep -q '^ *AllowGroups\\|^ *AllowUsers' /etc/ssh/sshd_config") != 0: - yield dict( - meta={ - "test": "sshd_config_insecure" - }, - status="ERROR", - summary="diagnosis_sshd_config_insecure", - ) + if ( + any(f["path"] == "/etc/ssh/sshd_config" for f in regenconf_modified_files) + and os.system( + "grep -q '^ *AllowGroups\\|^ *AllowUsers' /etc/ssh/sshd_config" + ) + != 0 + ): + yield dict( + meta={"test": "sshd_config_insecure"}, + status="ERROR", + summary="diagnosis_sshd_config_insecure", + ) # Check consistency between actual ssh port in sshd_config vs. setting - ssh_port_setting = settings_get('security.ssh.port') + ssh_port_setting = settings_get("security.ssh.port") ssh_port_line = re.findall( r"\bPort *([0-9]{2,5})\b", read_file("/etc/ssh/sshd_config") ) if len(ssh_port_line) == 1 and int(ssh_port_line[0]) != ssh_port_setting: yield dict( - meta={ - "test": "sshd_config_port_inconsistency" - }, - status="WARNING", - summary="diagnosis_sshd_config_inconsistent", - details=["diagnosis_sshd_config_inconsistent_details"], + meta={"test": "sshd_config_port_inconsistency"}, + status="WARNING", + summary="diagnosis_sshd_config_inconsistent", + details=["diagnosis_sshd_config_inconsistent_details"], ) def manually_modified_files(self): diff --git a/src/yunohost/__init__.py b/src/yunohost/__init__.py index 04caefc7a..dad73e2a4 100644 --- a/src/yunohost/__init__.py +++ b/src/yunohost/__init__.py @@ -90,56 +90,54 @@ def init_logging(interface="cli", debug=False, quiet=False, logdir="/var/log/yun os.makedirs(logdir, 0o750) logging_configuration = { - 'version': 1, - 'disable_existing_loggers': True, - 'formatters': { - 'console': { - 'format': '%(relativeCreated)-5d %(levelname)-8s %(name)s %(funcName)s - %(fmessage)s' + "version": 1, + "disable_existing_loggers": True, + "formatters": { + "console": { + "format": "%(relativeCreated)-5d %(levelname)-8s %(name)s %(funcName)s - %(fmessage)s" }, - 'tty-debug': { - 'format': '%(relativeCreated)-4d %(fmessage)s' - }, - 'precise': { - 'format': '%(asctime)-15s %(levelname)-8s %(name)s %(funcName)s - %(fmessage)s' + "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', + "filters": { + "action": { + "()": "moulinette.utils.log.ActionFilter", }, }, - 'handlers': { - 'cli': { - 'level': 'DEBUG' if debug else 'INFO', - 'class': 'moulinette.interfaces.cli.TTYHandler', - 'formatter': 'tty-debug' if debug else '', + "handlers": { + "cli": { + "level": "DEBUG" if debug else "INFO", + "class": "moulinette.interfaces.cli.TTYHandler", + "formatter": "tty-debug" if debug else "", }, - 'api': { - 'level': 'DEBUG' if debug else 'INFO', - 'class': 'moulinette.interfaces.api.APIQueueHandler', + "api": { + "level": "DEBUG" if debug else "INFO", + "class": "moulinette.interfaces.api.APIQueueHandler", }, - 'file': { - 'class': 'logging.FileHandler', - 'formatter': 'precise', - 'filename': logfile, - 'filters': ['action'], + "file": { + "class": "logging.FileHandler", + "formatter": "precise", + "filename": logfile, + "filters": ["action"], }, }, - 'loggers': { - 'yunohost': { - 'level': 'DEBUG', - 'handlers': ['file', interface] if not quiet else ['file'], - 'propagate': False, + "loggers": { + "yunohost": { + "level": "DEBUG", + "handlers": ["file", interface] if not quiet else ["file"], + "propagate": False, }, - 'moulinette': { - 'level': 'DEBUG', - 'handlers': ['file', interface] if not quiet else ['file'], - 'propagate': False, + "moulinette": { + "level": "DEBUG", + "handlers": ["file", interface] if not quiet else ["file"], + "propagate": False, }, }, - 'root': { - 'level': 'DEBUG', - 'handlers': ['file', interface] if debug else ['file'], + "root": { + "level": "DEBUG", + "handlers": ["file", interface] if debug else ["file"], }, } @@ -150,7 +148,9 @@ def init_logging(interface="cli", debug=False, quiet=False, logdir="/var/log/yun # Logging configuration for API # else: # We use a WatchedFileHandler instead of regular FileHandler to possibly support log rotation etc - logging_configuration["handlers"]["file"]["class"] = 'logging.handlers.WatchedFileHandler' + logging_configuration["handlers"]["file"][ + "class" + ] = "logging.handlers.WatchedFileHandler" # This is for when launching yunohost-api in debug mode, we want to display stuff in the console if debug: diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 301d37398..fc9f62373 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -193,7 +193,9 @@ def app_info(app, full=False): ) local_manifest = _get_manifest_of_app(os.path.join(APPS_SETTING_PATH, app)) - permissions = user_permission_list(full=True, absolute_urls=True, apps=[app])["permissions"] + permissions = user_permission_list(full=True, absolute_urls=True, apps=[app])[ + "permissions" + ] settings = _get_app_settings(app) @@ -325,7 +327,9 @@ def app_map(app=None, raw=False, user=None): else: apps = _installed_apps() - permissions = user_permission_list(full=True, absolute_urls=True, apps=apps)["permissions"] + permissions = user_permission_list(full=True, absolute_urls=True, apps=apps)[ + "permissions" + ] for app_id in apps: app_settings = _get_app_settings(app_id) if not app_settings: @@ -782,6 +786,7 @@ def app_manifest(app): return manifest + @is_unit_operation() def app_install( operation_logger, @@ -1106,10 +1111,7 @@ def app_install( permission_sync_to_user() - raise YunohostError( - failure_message_with_debug_instructions, - raw_msg=True - ) + raise YunohostError(failure_message_with_debug_instructions, raw_msg=True) # Clean hooks and add new ones hook_remove(app_instance_name) @@ -2232,9 +2234,13 @@ def _extract_app_from_file(path): extracted_app_folder = _make_tmp_workdir_for_app() if ".zip" in path: - extract_result = os.system(f"unzip '{path}' -d {extracted_app_folder} > /dev/null 2>&1") + extract_result = os.system( + f"unzip '{path}' -d {extracted_app_folder} > /dev/null 2>&1" + ) elif ".tar" in path: - extract_result = os.system(f"tar -xf '{path}' -C {extracted_app_folder} > /dev/null 2>&1") + extract_result = os.system( + f"tar -xf '{path}' -C {extracted_app_folder} > /dev/null 2>&1" + ) elif os.path.isdir(path): shutil.rmtree(extracted_app_folder) if path[-1] != "/": @@ -2746,7 +2752,9 @@ class YunoHostArgumentFormatParser(object): # we don't have an answer, check optional and default_value if question.value is None or question.value == "": if not question.optional and question.default is None: - raise YunohostValidationError("app_argument_required", name=question.name) + raise YunohostValidationError( + "app_argument_required", name=question.name + ) else: question.value = ( getattr(self, "default_value", None) @@ -2804,7 +2812,9 @@ class PasswordArgumentParser(YunoHostArgumentFormatParser): ) if question.default is not None: - raise YunohostValidationError("app_argument_password_no_default", name=question.name) + raise YunohostValidationError( + "app_argument_password_no_default", name=question.name + ) return question @@ -3114,7 +3124,9 @@ def _assert_no_conflicting_apps(domain, path, ignore_app=None, full_domain=False if full_domain: raise YunohostValidationError("app_full_domain_unavailable", domain=domain) else: - raise YunohostValidationError("app_location_unavailable", apps="\n".join(apps)) + raise YunohostValidationError( + "app_location_unavailable", apps="\n".join(apps) + ) def _make_environment_for_app_script(app, args={}, args_prefix="APP_ARG_"): @@ -3340,6 +3352,7 @@ def _load_apps_catalog(): # ############################### # # + def _make_tmp_workdir_for_app(app=None): # Create parent dir if it doesn't exists yet @@ -3425,15 +3438,27 @@ def _assert_system_is_sane_for_app(manifest, when): if not any(s for s in services if service_status(s)["status"] == "reloading"): break time.sleep(0.5) - test_nb+=1 + test_nb += 1 # List services currently down and raise an exception if any are found - services_status = {s:service_status(s) for s in services} - faulty_services = [f"{s} ({status['status']})" for s, status in services_status.items() if status['status'] != "running"] + services_status = {s: service_status(s) for s in services} + faulty_services = [ + f"{s} ({status['status']})" + for s, status in services_status.items() + if status["status"] != "running" + ] # Stupid tmp fix to try to track why the tests are failing - if "php7.3-fpm" in [s for s, status in services_status.items() if status['status'] != "running"]: - logger.info([status for s, status in services_status.items() if status['status'] != "running"]) + if "php7.3-fpm" in [ + s for s, status in services_status.items() if status["status"] != "running" + ]: + logger.info( + [ + status + for s, status in services_status.items() + if status["status"] != "running" + ] + ) os.system("journalctl -u php7.3-fpm -n 300 --no-hostname --no-pager") if faulty_services: @@ -3609,7 +3634,7 @@ def _patch_legacy_helpers(app_folder): content = read_file(filename) except MoulinetteError: continue - + replaced_stuff = False show_warning = False diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index c2d3085a3..c696e99da 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -53,7 +53,7 @@ from yunohost.app import ( _patch_legacy_php_versions, _patch_legacy_php_versions_in_settings, LEGACY_PHP_VERSION_REPLACEMENTS, - _make_tmp_workdir_for_app + _make_tmp_workdir_for_app, ) from yunohost.hook import ( hook_list, @@ -62,7 +62,11 @@ from yunohost.hook import ( hook_exec, CUSTOM_HOOK_FOLDER, ) -from yunohost.tools import tools_postinstall, _tools_migrations_run_after_system_restore, _tools_migrations_run_before_app_restore +from yunohost.tools import ( + tools_postinstall, + _tools_migrations_run_after_system_restore, + _tools_migrations_run_before_app_restore, +) from yunohost.regenconf import regen_conf from yunohost.log import OperationLogger, is_unit_operation from yunohost.utils.error import YunohostError, YunohostValidationError @@ -716,7 +720,9 @@ class BackupManager: hook_exec( f"{tmp_workdir_for_app}/scripts/backup", - raise_on_error=True, chdir=tmp_app_bkp_dir, env=env_dict + raise_on_error=True, + chdir=tmp_app_bkp_dir, + env=env_dict, )[0] self._import_to_list_to_backup(env_dict["YNH_BACKUP_CSV"]) @@ -724,10 +730,7 @@ class BackupManager: # backup permissions logger.debug(m18n.n("backup_permission", app=app)) permissions = user_permission_list(full=True, apps=[app])["permissions"] - this_app_permissions = { - name: infos - for name, infos in permissions.items() - } + this_app_permissions = {name: infos for name, infos in permissions.items()} write_to_yaml("%s/permissions.yml" % settings_dir, this_app_permissions) except Exception: @@ -857,7 +860,9 @@ class RestoreManager: # FIXME this way to get the info is not compatible with copy or custom # backup methods self.info = backup_info(name, with_details=True) - if not self.info["from_yunohost_version"] or version.parse(self.info["from_yunohost_version"]) < version.parse("3.8.0"): + if not self.info["from_yunohost_version"] or version.parse( + self.info["from_yunohost_version"] + ) < version.parse("3.8.0"): raise YunohostValidationError("restore_backup_too_old") self.archive_path = self.info["path"] @@ -1279,7 +1284,9 @@ class RestoreManager: regen_conf() - _tools_migrations_run_after_system_restore(backup_version=self.info["from_yunohost_version"]) + _tools_migrations_run_after_system_restore( + backup_version=self.info["from_yunohost_version"] + ) # Remove all permission for all app still in the LDAP for permission_name in user_permission_list(ignore_system_perms=True)[ @@ -1409,7 +1416,9 @@ class RestoreManager: # Restore permissions if not os.path.isfile("%s/permissions.yml" % app_settings_new_path): - raise YunohostError("Didnt find a permssions.yml for the app !?", raw_msg=True) + raise YunohostError( + "Didnt find a permssions.yml for the app !?", raw_msg=True + ) permissions = read_yaml("%s/permissions.yml" % app_settings_new_path) existing_groups = user_group_list()["groups"] @@ -1424,9 +1433,7 @@ class RestoreManager: should_be_allowed = ["all_users"] else: should_be_allowed = [ - g - for g in permission_infos["allowed"] - if g in existing_groups + g for g in permission_infos["allowed"] if g in existing_groups ] perm_name = permission_name.split(".")[1] @@ -1448,9 +1455,13 @@ class RestoreManager: os.remove("%s/permissions.yml" % app_settings_new_path) - _tools_migrations_run_before_app_restore(backup_version=self.info["from_yunohost_version"], app_id=app_instance_name) + _tools_migrations_run_before_app_restore( + backup_version=self.info["from_yunohost_version"], + app_id=app_instance_name, + ) except Exception: import traceback + error = m18n.n("unexpected_error", error="\n" + traceback.format_exc()) msg = m18n.n("app_restore_failed", app=app_instance_name, error=error) logger.error(msg) @@ -1493,20 +1504,27 @@ class RestoreManager: restore_failed = True if restore_retcode != 0 else False if restore_failed: error = m18n.n("app_restore_script_failed") - logger.error(m18n.n("app_restore_failed", app=app_instance_name, error=error)) + logger.error( + m18n.n("app_restore_failed", app=app_instance_name, error=error) + ) failure_message_with_debug_instructions = operation_logger.error(error) if msettings.get("interface") != "api": dump_app_log_extract_for_debugging(operation_logger) # Script got manually interrupted ... N.B. : KeyboardInterrupt does not inherit from Exception except (KeyboardInterrupt, EOFError): error = m18n.n("operation_interrupted") - logger.error(m18n.n("app_restore_failed", app=app_instance_name, error=error)) + logger.error( + m18n.n("app_restore_failed", app=app_instance_name, error=error) + ) failure_message_with_debug_instructions = operation_logger.error(error) # Something wrong happened in Yunohost's code (most probably hook_exec) except Exception: import traceback + error = m18n.n("unexpected_error", error="\n" + traceback.format_exc()) - logger.error(m18n.n("app_restore_failed", app=app_instance_name, error=error)) + logger.error( + m18n.n("app_restore_failed", app=app_instance_name, error=error) + ) failure_message_with_debug_instructions = operation_logger.error(error) finally: # Cleaning temporary scripts directory @@ -1551,6 +1569,7 @@ class RestoreManager: logger.error(failure_message_with_debug_instructions) + # # Backup methods # # @@ -2163,7 +2182,13 @@ class CustomBackupMethod(BackupMethod): @is_unit_operation() def backup_create( operation_logger, - name=None, description=None, methods=[], output_directory=None, system=[], apps=[], dry_run=False + name=None, + description=None, + methods=[], + output_directory=None, + system=[], + apps=[], + dry_run=False, ): """ Create a backup local archive @@ -2248,12 +2273,17 @@ def backup_create( if dry_run: return { "size": backup_manager.size, - "size_details": backup_manager.size_details + "size_details": backup_manager.size_details, } # Apply backup methods on prepared files logger.info(m18n.n("backup_actually_backuping")) - logger.info(m18n.n("backup_create_size_estimation", size=binary_to_human(backup_manager.size) + "B")) + logger.info( + m18n.n( + "backup_create_size_estimation", + size=binary_to_human(backup_manager.size) + "B", + ) + ) backup_manager.backup() logger.success(m18n.n("backup_created")) @@ -2291,9 +2321,9 @@ def backup_restore(name, system=[], apps=[], force=False): # if name.endswith(".tar.gz"): - name = name[:-len(".tar.gz")] + name = name[: -len(".tar.gz")] elif name.endswith(".tar"): - name = name[:-len(".tar")] + name = name[: -len(".tar")] restore_manager = RestoreManager(name) @@ -2407,7 +2437,9 @@ def backup_download(name): # Raise exception if link is broken (e.g. on unmounted external storage) if not os.path.exists(archive_file): - raise YunohostValidationError("backup_archive_broken_link", path=archive_file) + raise YunohostValidationError( + "backup_archive_broken_link", path=archive_file + ) # We return a raw bottle HTTPresponse (instead of serializable data like # list/dict, ...), which is gonna be picked and used directly by moulinette @@ -2429,9 +2461,9 @@ def backup_info(name, with_details=False, human_readable=False): """ if name.endswith(".tar.gz"): - name = name[:-len(".tar.gz")] + name = name[: -len(".tar.gz")] elif name.endswith(".tar"): - name = name[:-len(".tar")] + name = name[: -len(".tar")] archive_file = "%s/%s.tar" % (ARCHIVES_PATH, name) @@ -2447,7 +2479,9 @@ def backup_info(name, with_details=False, human_readable=False): # Raise exception if link is broken (e.g. on unmounted external storage) if not os.path.exists(archive_file): - raise YunohostValidationError("backup_archive_broken_link", path=archive_file) + raise YunohostValidationError( + "backup_archive_broken_link", path=archive_file + ) info_file = "%s/%s.info.json" % (ARCHIVES_PATH, name) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 1c2249ba7..7e6726f31 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -455,6 +455,7 @@ def certificate_renew( # Back-end stuff # # + def _email_renewing_failed(domain, exception_message, stack=""): from_ = "certmanager@%s (Certificate Manager)" % domain to_ = "root" @@ -872,7 +873,9 @@ def _check_domain_is_ready_for_ACME(domain): ) if not dnsrecords or not httpreachable: - raise YunohostValidationError("certmanager_domain_not_diagnosed_yet", domain=domain) + raise YunohostValidationError( + "certmanager_domain_not_diagnosed_yet", domain=domain + ) # Check if IP from DNS matches public IP if not dnsrecords.get("status") in [ @@ -885,7 +888,9 @@ def _check_domain_is_ready_for_ACME(domain): # Check if domain seems to be accessible through HTTP? if not httpreachable.get("status") == "SUCCESS": - raise YunohostValidationError("certmanager_domain_http_not_working", domain=domain) + raise YunohostValidationError( + "certmanager_domain_http_not_working", domain=domain + ) # FIXME / TODO : ideally this should not be needed. There should be a proper diff --git a/src/yunohost/data_migrations/0020_ssh_sftp_permissions.py b/src/yunohost/data_migrations/0020_ssh_sftp_permissions.py index c3b7a91ec..3fb36a6f3 100644 --- a/src/yunohost/data_migrations/0020_ssh_sftp_permissions.py +++ b/src/yunohost/data_migrations/0020_ssh_sftp_permissions.py @@ -9,7 +9,7 @@ from yunohost.tools import Migration from yunohost.permission import user_permission_update, permission_sync_to_user from yunohost.regenconf import manually_modified_files -logger = getActionLogger('yunohost.migration') +logger = getActionLogger("yunohost.migration") ################################################### # Tools used also for restoration @@ -18,7 +18,7 @@ logger = getActionLogger('yunohost.migration') class MyMigration(Migration): """ - Add new permissions around SSH/SFTP features + Add new permissions around SSH/SFTP features """ introduced_in_version = "4.2.2" @@ -28,38 +28,60 @@ class MyMigration(Migration): def run(self, *args): from yunohost.utils.ldap import _get_ldap_interface + ldap = _get_ldap_interface() - existing_perms_raw = ldap.search("ou=permission,dc=yunohost,dc=org", "(objectclass=permissionYnh)", ["cn"]) - existing_perms = [perm['cn'][0] for perm in existing_perms_raw] + existing_perms_raw = ldap.search( + "ou=permission,dc=yunohost,dc=org", "(objectclass=permissionYnh)", ["cn"] + ) + existing_perms = [perm["cn"][0] for perm in existing_perms_raw] # Add SSH and SFTP permissions - ldap_map = read_yaml('/usr/share/yunohost/yunohost-config/moulinette/ldap_scheme.yml') + ldap_map = read_yaml( + "/usr/share/yunohost/yunohost-config/moulinette/ldap_scheme.yml" + ) if "sftp.main" not in existing_perms: - ldap.add("cn=sftp.main,ou=permission", ldap_map['depends_children']["cn=sftp.main,ou=permission"]) + ldap.add( + "cn=sftp.main,ou=permission", + ldap_map["depends_children"]["cn=sftp.main,ou=permission"], + ) if "ssh.main" not in existing_perms: - ldap.add("cn=ssh.main,ou=permission", ldap_map['depends_children']["cn=ssh.main,ou=permission"]) + ldap.add( + "cn=ssh.main,ou=permission", + ldap_map["depends_children"]["cn=ssh.main,ou=permission"], + ) # Add a bash terminal to each users - users = ldap.search('ou=users,dc=yunohost,dc=org', filter="(loginShell=*)", attrs=["dn", "uid", "loginShell"]) + users = ldap.search( + "ou=users,dc=yunohost,dc=org", + filter="(loginShell=*)", + attrs=["dn", "uid", "loginShell"], + ) for user in users: - if user['loginShell'][0] == '/bin/false': - dn = user['dn'][0].replace(',dc=yunohost,dc=org', '') - ldap.update(dn, {'loginShell': ['/bin/bash']}) + if user["loginShell"][0] == "/bin/false": + dn = user["dn"][0].replace(",dc=yunohost,dc=org", "") + ldap.update(dn, {"loginShell": ["/bin/bash"]}) else: - user_permission_update("ssh.main", add=user["uid"][0], sync_perm=False) + user_permission_update( + "ssh.main", add=user["uid"][0], sync_perm=False + ) permission_sync_to_user() # Somehow this is needed otherwise the PAM thing doesn't forget about the # old loginShell value ? - subprocess.call(['nscd', '-i', 'passwd']) + subprocess.call(["nscd", "-i", "passwd"]) - if '/etc/ssh/sshd_config' in manually_modified_files() \ - and os.system("grep -q '^ *AllowGroups\\|^ *AllowUsers' /etc/ssh/sshd_config") != 0: - logger.error(m18n.n('diagnosis_sshd_config_insecure')) + if ( + "/etc/ssh/sshd_config" in manually_modified_files() + and os.system( + "grep -q '^ *AllowGroups\\|^ *AllowUsers' /etc/ssh/sshd_config" + ) + != 0 + ): + logger.error(m18n.n("diagnosis_sshd_config_insecure")) def run_after_system_restore(self): self.run() diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index b8a4a1f8a..29ffe686b 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -59,7 +59,9 @@ def diagnosis_get(category, item): all_categories_names = [c for c, _ in all_categories] if category not in all_categories_names: - raise YunohostValidationError("diagnosis_unknown_categories", categories=category) + raise YunohostValidationError( + "diagnosis_unknown_categories", categories=category + ) if isinstance(item, list): if any("=" not in criteria for criteria in item): diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 4543bbcd9..aaac3a995 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -122,7 +122,7 @@ def domain_add(operation_logger, domain, dyndns=False): # Do not allow to subscribe to multiple dyndns domains... if _guess_current_dyndns_domain("dyndns.yunohost.org") != (None, None): - raise YunohostValidationError('domain_dyndns_already_subscribed') + raise YunohostValidationError("domain_dyndns_already_subscribed") # Check that this domain can effectively be provided by # dyndns.yunohost.org. (i.e. is it a nohost.me / noho.st) @@ -133,6 +133,7 @@ def domain_add(operation_logger, domain, dyndns=False): if dyndns: from yunohost.dyndns import dyndns_subscribe + # Actually subscribe dyndns_subscribe(domain=domain) @@ -197,8 +198,8 @@ def domain_remove(operation_logger, domain, remove_apps=False, force=False): # the 'force' here is related to the exception happening in domain_add ... # we don't want to check the domain exists because the ldap add may have # failed - if not force and domain not in domain_list()['domains']: - raise YunohostValidationError('domain_name_unknown', domain=domain) + if not force and domain not in domain_list()["domains"]: + raise YunohostValidationError("domain_name_unknown", domain=domain) # Check domain is not the main domain if domain == _get_maindomain(): @@ -212,7 +213,9 @@ def domain_remove(operation_logger, domain, remove_apps=False, force=False): other_domains="\n * " + ("\n * ".join(other_domains)), ) else: - raise YunohostValidationError("domain_cannot_remove_main_add_new_one", domain=domain) + raise YunohostValidationError( + "domain_cannot_remove_main_add_new_one", domain=domain + ) # Check if apps are installed on the domain apps_on_that_domain = [] @@ -221,21 +224,37 @@ def domain_remove(operation_logger, domain, remove_apps=False, force=False): settings = _get_app_settings(app) label = app_info(app)["name"] if settings.get("domain") == domain: - apps_on_that_domain.append((app, " - %s \"%s\" on https://%s%s" % (app, label, domain, settings["path"]) if "path" in settings else app)) + apps_on_that_domain.append( + ( + app, + ' - %s "%s" on https://%s%s' + % (app, label, domain, settings["path"]) + if "path" in settings + else app, + ) + ) if apps_on_that_domain: if remove_apps: - if msettings.get('interface') == "cli" and not force: - answer = msignals.prompt(m18n.n('domain_remove_confirm_apps_removal', - apps="\n".join([x[1] for x in apps_on_that_domain]), - answers='y/N'), color="yellow") + if msettings.get("interface") == "cli" and not force: + answer = msignals.prompt( + m18n.n( + "domain_remove_confirm_apps_removal", + apps="\n".join([x[1] for x in apps_on_that_domain]), + answers="y/N", + ), + color="yellow", + ) if answer.upper() != "Y": raise YunohostError("aborting") for app, _ in apps_on_that_domain: app_remove(app) else: - raise YunohostValidationError('domain_uninstall_app_first', apps="\n".join([x[1] for x in apps_on_that_domain])) + raise YunohostValidationError( + "domain_uninstall_app_first", + apps="\n".join([x[1] for x in apps_on_that_domain]), + ) operation_logger.start() @@ -248,7 +267,7 @@ def domain_remove(operation_logger, domain, remove_apps=False, force=False): os.system("rm -rf /etc/yunohost/certs/%s" % domain) # Delete dyndns keys for this domain (if any) - os.system('rm -rf /etc/yunohost/dyndns/K%s.+*' % domain) + os.system("rm -rf /etc/yunohost/dyndns/K%s.+*" % domain) # Sometime we have weird issues with the regenconf where some files # appears as manually modified even though they weren't touched ... diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index c7a501b9c..c8249e439 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -124,7 +124,7 @@ def dyndns_subscribe( """ if _guess_current_dyndns_domain(subscribe_host) != (None, None): - raise YunohostValidationError('domain_dyndns_already_subscribed') + raise YunohostValidationError("domain_dyndns_already_subscribed") if domain is None: domain = _get_maindomain() @@ -192,12 +192,14 @@ def dyndns_subscribe( # Add some dyndns update in 2 and 4 minutes from now such that user should # not have to wait 10ish minutes for the conf to propagate - cmd = "at -M now + {t} >/dev/null 2>&1 <<< \"/bin/bash -c 'yunohost dyndns update'\"" + cmd = ( + "at -M now + {t} >/dev/null 2>&1 <<< \"/bin/bash -c 'yunohost dyndns update'\"" + ) # For some reason subprocess doesn't like the redirections so we have to use bash -c explicity... subprocess.check_call(["bash", "-c", cmd.format(t="2 min")]) subprocess.check_call(["bash", "-c", cmd.format(t="4 min")]) - logger.success(m18n.n('dyndns_registered')) + logger.success(m18n.n("dyndns_registered")) @is_unit_operation() @@ -231,7 +233,7 @@ def dyndns_update( (domain, key) = _guess_current_dyndns_domain(dyn_host) if domain is None: - raise YunohostValidationError('dyndns_no_domain_registered') + raise YunohostValidationError("dyndns_no_domain_registered") # If key is not given, pick the first file we find with the domain given else: @@ -374,11 +376,15 @@ def dyndns_update( def dyndns_installcron(): - logger.warning("This command is deprecated. The dyndns cron job should automatically be added/removed by the regenconf depending if there's a private key in /etc/yunohost/dyndns. You can run the regenconf yourself with 'yunohost tools regen-conf yunohost'.") + logger.warning( + "This command is deprecated. The dyndns cron job should automatically be added/removed by the regenconf depending if there's a private key in /etc/yunohost/dyndns. You can run the regenconf yourself with 'yunohost tools regen-conf yunohost'." + ) def dyndns_removecron(): - logger.warning("This command is deprecated. The dyndns cron job should automatically be added/removed by the regenconf depending if there's a private key in /etc/yunohost/dyndns. You can run the regenconf yourself with 'yunohost tools regen-conf yunohost'.") + logger.warning( + "This command is deprecated. The dyndns cron job should automatically be added/removed by the regenconf depending if there's a private key in /etc/yunohost/dyndns. You can run the regenconf yourself with 'yunohost tools regen-conf yunohost'." + ) def _guess_current_dyndns_domain(dyn_host): diff --git a/src/yunohost/firewall.py b/src/yunohost/firewall.py index af1cea2e3..b800cd42c 100644 --- a/src/yunohost/firewall.py +++ b/src/yunohost/firewall.py @@ -188,11 +188,17 @@ def firewall_list(raw=False, by_ip_version=False, list_forwarded=False): for i in ["ipv4", "ipv6"]: f = firewall[i] # Combine TCP and UDP ports - ports[i] = sorted(set(f["TCP"]) | set(f["UDP"]), key=lambda p: int(p.split(':')[0]) if isinstance(p, str) else p) + ports[i] = sorted( + set(f["TCP"]) | set(f["UDP"]), + key=lambda p: int(p.split(":")[0]) if isinstance(p, str) else p, + ) if not by_ip_version: # Combine IPv4 and IPv6 ports - ports = sorted(set(ports["ipv4"]) | set(ports["ipv6"]), key=lambda p: int(p.split(':')[0]) if isinstance(p, str) else p) + ports = sorted( + set(ports["ipv4"]) | set(ports["ipv6"]), + key=lambda p: int(p.split(":")[0]) if isinstance(p, str) else p, + ) # Format returned dict ret = {"opened_ports": ports} @@ -200,7 +206,7 @@ def firewall_list(raw=False, by_ip_version=False, list_forwarded=False): # Combine TCP and UDP forwarded ports ret["forwarded_ports"] = sorted( set(firewall["uPnP"]["TCP"]) | set(firewall["uPnP"]["UDP"]), - key=lambda p: int(p.split(':')[0]) if isinstance(p, str) else p + key=lambda p: int(p.split(":")[0]) if isinstance(p, str) else p, ) return ret diff --git a/src/yunohost/log.py b/src/yunohost/log.py index f8215955f..f8da40002 100644 --- a/src/yunohost/log.py +++ b/src/yunohost/log.py @@ -414,8 +414,15 @@ class RedactingFormatter(Formatter): # This matches stuff like db_pwd=the_secret or admin_password=other_secret # (the secret part being at least 3 chars to avoid catching some lines like just "db_pwd=") # Some names like "key" or "manifest_key" are ignored, used in helpers like ynh_app_setting_set or ynh_read_manifest - match = re.search(r'(pwd|pass|password|passphrase|secret\w*|\w+key|token)=(\S{3,})$', record.strip()) - if match and match.group(2) not in self.data_to_redact and match.group(1) not in ["key", "manifest_key"]: + match = re.search( + r"(pwd|pass|password|passphrase|secret\w*|\w+key|token)=(\S{3,})$", + record.strip(), + ) + if ( + match + and match.group(2) not in self.data_to_redact + and match.group(1) not in ["key", "manifest_key"] + ): self.data_to_redact.append(match.group(2)) except Exception as e: logger.warning( @@ -636,7 +643,11 @@ class OperationLogger(object): # we want to inject the log ref in the exception, such that it may be # transmitted to the webadmin which can then redirect to the appropriate # log page - if self.started_at and isinstance(error, Exception) and not isinstance(error, YunohostValidationError): + if ( + self.started_at + and isinstance(error, Exception) + and not isinstance(error, YunohostValidationError) + ): error.log_ref = self.name if self.ended_at is not None or self.started_at is None: diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index cee22a741..01330ad7f 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -80,7 +80,9 @@ def user_permission_list( apps_base_path = { app: app_setting(app, "domain") + app_setting(app, "path") for app in apps - if app in installed_apps and app_setting(app, "domain") and app_setting(app, "path") + if app in installed_apps + and app_setting(app, "domain") + and app_setting(app, "path") } permissions = {} @@ -179,7 +181,9 @@ def user_permission_update( # Refuse to add "visitors" to mail, xmpp ... they require an account to make sense. if add and "visitors" in add and permission.split(".")[0] in SYSTEM_PERMS: - raise YunohostValidationError("permission_require_account", permission=permission) + raise YunohostValidationError( + "permission_require_account", permission=permission + ) # Refuse to add "visitors" to protected permission if ( @@ -189,8 +193,14 @@ def user_permission_update( raise YunohostValidationError("permission_protected", permission=permission) # Refuse to add "all_users" to ssh/sftp permissions - if permission.split(".")[0] in ["ssh", "sftp"] and (add and "all_users" in add) and not force: - raise YunohostValidationError("permission_cant_add_to_all_users", permission=permission) + if ( + permission.split(".")[0] in ["ssh", "sftp"] + and (add and "all_users" in add) + and not force + ): + raise YunohostValidationError( + "permission_cant_add_to_all_users", permission=permission + ) # Fetch currently allowed groups for this permission diff --git a/src/yunohost/settings.py b/src/yunohost/settings.py index 7e9eb76d9..0466d8126 100644 --- a/src/yunohost/settings.py +++ b/src/yunohost/settings.py @@ -103,6 +103,7 @@ DEFAULTS = OrderedDict( ] ) + def settings_get(key, full=False): """ Get an entry value in the settings @@ -114,7 +115,9 @@ def settings_get(key, full=False): settings = _get_settings() if key not in settings: - raise YunohostValidationError("global_settings_key_doesnt_exists", settings_key=key) + raise YunohostValidationError( + "global_settings_key_doesnt_exists", settings_key=key + ) if full: return settings[key] @@ -142,7 +145,9 @@ def settings_set(key, value): settings = _get_settings() if key not in settings: - raise YunohostValidationError("global_settings_key_doesnt_exists", settings_key=key) + raise YunohostValidationError( + "global_settings_key_doesnt_exists", settings_key=key + ) key_type = settings[key]["type"] @@ -219,7 +224,9 @@ def settings_reset(key): settings = _get_settings() if key not in settings: - raise YunohostValidationError("global_settings_key_doesnt_exists", settings_key=key) + raise YunohostValidationError( + "global_settings_key_doesnt_exists", settings_key=key + ) settings[key]["value"] = settings[key]["default"] _save_settings(settings) @@ -381,6 +388,7 @@ def trigger_post_change_hook(setting_name, old_value, new_value): # # =========================================== + @post_change_hook("ssowat.panel_overlay.enabled") @post_change_hook("security.nginx.compatibility") def reconfigure_nginx(setting_name, old_value, new_value): diff --git a/src/yunohost/ssh.py b/src/yunohost/ssh.py index e2ecaeef3..caac00050 100644 --- a/src/yunohost/ssh.py +++ b/src/yunohost/ssh.py @@ -99,18 +99,16 @@ def user_ssh_remove_key(username, key): if not os.path.exists(authorized_keys_file): raise YunohostValidationError( "this key doesn't exists ({} dosesn't exists)".format(authorized_keys_file), - raw_msg=True + raw_msg=True, ) authorized_keys_content = read_file(authorized_keys_file) if key not in authorized_keys_content: raise YunohostValidationError( - "Key '{}' is not present in authorized_keys".format(key), - raw_msg=True + "Key '{}' is not present in authorized_keys".format(key), raw_msg=True ) - # don't delete the previous comment because we can't verify if it's legit # this regex approach failed for some reasons and I don't know why :( diff --git a/src/yunohost/tests/test_apps.py b/src/yunohost/tests/test_apps.py index b9e9e7530..eba5a5916 100644 --- a/src/yunohost/tests/test_apps.py +++ b/src/yunohost/tests/test_apps.py @@ -56,7 +56,9 @@ def clean(): shutil.rmtree(folderpath, ignore_errors=True) os.system("bash -c \"mysql -B 2>/dev/null <<< 'DROP DATABASE %s' \"" % test_app) - os.system("bash -c \"mysql -B 2>/dev/null <<< 'DROP USER %s@localhost'\"" % test_app) + os.system( + "bash -c \"mysql -B 2>/dev/null <<< 'DROP USER %s@localhost'\"" % test_app + ) # Reset failed quota for service to avoid running into start-limit rate ? os.system("systemctl reset-failed nginx") diff --git a/src/yunohost/tests/test_settings.py b/src/yunohost/tests/test_settings.py index a393e83c6..47f8efdf4 100644 --- a/src/yunohost/tests/test_settings.py +++ b/src/yunohost/tests/test_settings.py @@ -30,7 +30,7 @@ def setup_function(function): def teardown_function(function): os.system("mv /etc/yunohost/settings.json.saved /etc/yunohost/settings.json") for filename in glob.glob("/etc/yunohost/settings-*.json"): - os.remove(filename) + os.remove(filename) def test_settings_get_bool(): diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 8fb65bac8..ada43edaa 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -413,7 +413,9 @@ def tools_update(target=None, apps=False, system=False): # Legacy options (--system, --apps) if apps or system: - logger.warning("Using 'yunohost tools update' with --apps / --system is deprecated, just write 'yunohost tools update apps system' (no -- prefix anymore)") + logger.warning( + "Using 'yunohost tools update' with --apps / --system is deprecated, just write 'yunohost tools update apps system' (no -- prefix anymore)" + ) if apps and system: target = "all" elif apps: @@ -425,7 +427,10 @@ def tools_update(target=None, apps=False, system=False): target = "all" if target not in ["system", "apps", "all"]: - raise YunohostError("Unknown target %s, should be 'system', 'apps' or 'all'" % target, raw_msg=True) + raise YunohostError( + "Unknown target %s, should be 'system', 'apps' or 'all'" % target, + raw_msg=True, + ) upgradable_system_packages = [] if target in ["system", "all"]: @@ -548,7 +553,9 @@ def tools_upgrade( # Legacy options management (--system, --apps) if target is None: - logger.warning("Using 'yunohost tools upgrade' with --apps / --system is deprecated, just write 'yunohost tools upgrade apps' or 'system' (no -- prefix anymore)") + logger.warning( + "Using 'yunohost tools upgrade' with --apps / --system is deprecated, just write 'yunohost tools upgrade apps' or 'system' (no -- prefix anymore)" + ) if (system, apps) == (True, True): raise YunohostValidationError("tools_upgrade_cant_both") @@ -559,7 +566,9 @@ def tools_upgrade( target = "apps" if apps else "system" if target not in ["apps", "system"]: - raise Exception("Uhoh ?! tools_upgrade should have 'apps' or 'system' value for argument target") + raise Exception( + "Uhoh ?! tools_upgrade should have 'apps' or 'system' value for argument target" + ) # # Apps @@ -914,9 +923,13 @@ def tools_migrations_run( pending = [t.id for t in targets if t.state == "pending"] if skip and done: - raise YunohostValidationError("migrations_not_pending_cant_skip", ids=", ".join(done)) + raise YunohostValidationError( + "migrations_not_pending_cant_skip", ids=", ".join(done) + ) if force_rerun and pending: - raise YunohostValidationError("migrations_pending_cant_rerun", ids=", ".join(pending)) + raise YunohostValidationError( + "migrations_pending_cant_rerun", ids=", ".join(pending) + ) if not (skip or force_rerun) and done: raise YunohostValidationError("migrations_already_ran", ids=", ".join(done)) @@ -1129,9 +1142,11 @@ def _tools_migrations_run_after_system_restore(backup_version): return for migration in all_migrations: - if hasattr(migration, "introduced_in_version") \ - and version.parse(migration.introduced_in_version) > backup_version \ - and hasattr(migration, "run_after_system_restore"): + if ( + hasattr(migration, "introduced_in_version") + and version.parse(migration.introduced_in_version) > backup_version + and hasattr(migration, "run_after_system_restore") + ): try: logger.info(m18n.n("migrations_running_forward", id=migration.id)) migration.run_after_system_restore() @@ -1154,9 +1169,11 @@ def _tools_migrations_run_before_app_restore(backup_version, app_id): return for migration in all_migrations: - if hasattr(migration, "introduced_in_version") \ - and version.parse(migration.introduced_in_version) > backup_version \ - and hasattr(migration, "run_before_app_restore"): + if ( + hasattr(migration, "introduced_in_version") + and version.parse(migration.introduced_in_version) > backup_version + and hasattr(migration, "run_before_app_restore") + ): try: logger.info(m18n.n("migrations_running_forward", id=migration.id)) migration.run_before_app_restore(app_id) @@ -1167,6 +1184,7 @@ def _tools_migrations_run_before_app_restore(backup_version, app_id): logger.error(msg, exc_info=1) raise + class Migration(object): # Those are to be implemented by daughter classes @@ -1193,7 +1211,6 @@ class Migration(object): return m18n.n("migration_description_%s" % self.id) def ldap_migration(run): - def func(self): # Backup LDAP before the migration @@ -1206,7 +1223,9 @@ class Migration(object): os.system("systemctl stop slapd") os.system(f"cp -r --preserve /etc/ldap {backup_folder}/ldap_config") os.system(f"cp -r --preserve /var/lib/ldap {backup_folder}/ldap_db") - os.system(f"cp -r --preserve /etc/yunohost/apps {backup_folder}/apps_settings") + os.system( + f"cp -r --preserve /etc/yunohost/apps {backup_folder}/apps_settings" + ) except Exception as e: raise YunohostError( "migration_ldap_can_not_backup_before_migration", error=str(e) @@ -1217,13 +1236,17 @@ class Migration(object): try: run(self, backup_folder) except Exception: - logger.warning(m18n.n("migration_ldap_migration_failed_trying_to_rollback")) + logger.warning( + m18n.n("migration_ldap_migration_failed_trying_to_rollback") + ) os.system("systemctl stop slapd") # To be sure that we don't keep some part of the old config os.system("rm -r /etc/ldap/slapd.d") os.system(f"cp -r --preserve {backup_folder}/ldap_config/. /etc/ldap/") os.system(f"cp -r --preserve {backup_folder}/ldap_db/. /var/lib/ldap/") - os.system(f"cp -r --preserve {backup_folder}/apps_settings/. /etc/yunohost/apps/") + os.system( + f"cp -r --preserve {backup_folder}/apps_settings/. /etc/yunohost/apps/" + ) os.system("systemctl start slapd") os.system(f"rm -r {backup_folder}") logger.info(m18n.n("migration_ldap_rollback_success")) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 5143f610f..266c2774c 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -118,7 +118,9 @@ def user_create( # Validate domain used for email address/xmpp account if domain is None: if msettings.get("interface") == "api": - raise YunohostValidationError("Invalid usage, you should specify a domain argument") + raise YunohostValidationError( + "Invalid usage, you should specify a domain argument" + ) else: # On affiche les differents domaines possibles msignals.display(m18n.n("domains_available")) @@ -223,7 +225,9 @@ def user_create( logger.warning(m18n.n("user_home_creation_failed"), exc_info=1) try: - subprocess.check_call(["setfacl", "-m", "g:all_users:---", "/home/%s" % username]) + subprocess.check_call( + ["setfacl", "-m", "g:all_users:---", "/home/%s" % username] + ) except subprocess.CalledProcessError: logger.warning("Failed to protect /home/%s" % username, exc_info=1) @@ -412,7 +416,9 @@ def user_update( try: ldap.validate_uniqueness({"mail": mail}) except Exception as e: - raise YunohostValidationError("user_update_failed", user=username, error=e) + raise YunohostValidationError( + "user_update_failed", user=username, error=e + ) if mail[mail.find("@") + 1 :] not in domains: raise YunohostValidationError( "mail_domain_unknown", domain=mail[mail.find("@") + 1 :] @@ -649,7 +655,9 @@ def user_group_create( "sed --in-place '/^%s:/d' /etc/group" % groupname, shell=True ) else: - raise YunohostValidationError("group_already_exist_on_system", group=groupname) + raise YunohostValidationError( + "group_already_exist_on_system", group=groupname + ) if not gid: # Get random GID @@ -758,7 +766,9 @@ def user_group_update( elif groupname == "visitors": raise YunohostValidationError("group_cannot_edit_visitors") elif groupname in existing_users: - raise YunohostValidationError("group_cannot_edit_primary_group", group=groupname) + raise YunohostValidationError( + "group_cannot_edit_primary_group", group=groupname + ) # We extract the uid for each member of the group to keep a simple flat list of members current_group = user_group_info(groupname)["members"] @@ -864,9 +874,7 @@ def user_group_add(groupname, usernames, force=False, sync_perm=True): usernames -- User(s) to add in the group """ - return user_group_update( - groupname, add=usernames, force=force, sync_perm=sync_perm - ) + return user_group_update(groupname, add=usernames, force=force, sync_perm=sync_perm) def user_group_remove(groupname, usernames, force=False, sync_perm=True): @@ -891,7 +899,9 @@ def user_group_remove(groupname, usernames, force=False, sync_perm=True): def user_permission_list(short=False, full=False, apps=[]): import yunohost.permission - return yunohost.permission.user_permission_list(short, full, absolute_urls=True, apps=apps) + return yunohost.permission.user_permission_list( + short, full, absolute_urls=True, apps=apps + ) def user_permission_update(permission, label=None, show_tile=None, sync_perm=True): @@ -902,9 +912,7 @@ def user_permission_update(permission, label=None, show_tile=None, sync_perm=Tru ) -def user_permission_add( - permission, names, protected=None, force=False, sync_perm=True -): +def user_permission_add(permission, names, protected=None, force=False, sync_perm=True): import yunohost.permission return yunohost.permission.user_permission_update( From 229d0ab5cb3a9f7605045385538c97e5bd6b6f27 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 17 Apr 2021 02:35:41 +0200 Subject: [PATCH 2413/3170] No need to define run_before_app_restore for migration 0020, let's avoid some confusing/misleading message --- src/yunohost/data_migrations/0020_ssh_sftp_permissions.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/yunohost/data_migrations/0020_ssh_sftp_permissions.py b/src/yunohost/data_migrations/0020_ssh_sftp_permissions.py index c3b7a91ec..bb5370c58 100644 --- a/src/yunohost/data_migrations/0020_ssh_sftp_permissions.py +++ b/src/yunohost/data_migrations/0020_ssh_sftp_permissions.py @@ -63,7 +63,3 @@ class MyMigration(Migration): def run_after_system_restore(self): self.run() - - def run_before_app_restore(self, app_id): - # Nothing to do during app backup restore for this migration - pass From 92eb97042f918779851b82705870e70eed605863 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 17 Apr 2021 02:51:59 +0200 Subject: [PATCH 2414/3170] ynh_remove_fpm_config: we probably want to remove the conf file *before* reloading the service... --- data/helpers.d/php | 12 ++++++------ src/yunohost/app.py | 7 +------ 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/data/helpers.d/php b/data/helpers.d/php index a590da3dd..ae9cb2ec5 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -287,6 +287,12 @@ ynh_remove_fpm_config () { fpm_service="php$YNH_DEFAULT_PHP_VERSION-fpm" fi + ynh_secure_remove --file="$fpm_config_dir/pool.d/$app.conf" + 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 [ $dedicated_service -eq 1 ] then # Remove the dedicated service PHP-FPM service for the app @@ -299,12 +305,6 @@ ynh_remove_fpm_config () { ynh_systemd_action --service_name=$fpm_service --action=reload fi - ynh_secure_remove --file="$fpm_config_dir/pool.d/$app.conf" - 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" ] then diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 301d37398..94d91f9b2 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -3431,11 +3431,6 @@ def _assert_system_is_sane_for_app(manifest, when): services_status = {s:service_status(s) for s in services} faulty_services = [f"{s} ({status['status']})" for s, status in services_status.items() if status['status'] != "running"] - # Stupid tmp fix to try to track why the tests are failing - if "php7.3-fpm" in [s for s, status in services_status.items() if status['status'] != "running"]: - logger.info([status for s, status in services_status.items() if status['status'] != "running"]) - os.system("journalctl -u php7.3-fpm -n 300 --no-hostname --no-pager") - if faulty_services: if when == "pre": raise YunohostValidationError( @@ -3609,7 +3604,7 @@ def _patch_legacy_helpers(app_folder): content = read_file(filename) except MoulinetteError: continue - + replaced_stuff = False show_warning = False From 567bdb9a157470a974cbcd8303a1e8a99d9551af Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 17 Apr 2021 02:55:34 +0200 Subject: [PATCH 2415/3170] Undefined MoulinetteError --- src/yunohost/app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 2efa24baa..e0e8f7b14 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -37,6 +37,7 @@ import tempfile from collections import OrderedDict from moulinette import msignals, m18n, msettings +from moulinette.core import MoulinetteError from moulinette.utils.log import getActionLogger from moulinette.utils.network import download_json from moulinette.utils.process import run_commands, check_output From de8ca5a8cb6f8733c4ede0ffba931c6f4d5094dd Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 17 Apr 2021 04:34:20 +0200 Subject: [PATCH 2416/3170] Update changelog for 4.2.2 --- debian/changelog | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/debian/changelog b/debian/changelog index 916ab4edd..fefbfe94c 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,33 @@ +yunohost (4.2.2) testing; urgency=low + + - permissions: Add SFTP / SSH permissions ([#606](https://github.com/yunohost/yunohost/pull/606)) + - refactoring: Uniformize API routes ([#1192](https://github.com/yunohost/yunohost/pull/1192)) + - settings: New setting to disable the 'YunoHost' panel overlay in apps ([#1071](https://github.com/yunohost/yunohost/pull/1071), 08fbfa2e) + - settings: New setting for custom ssh port ([#1209](https://github.com/yunohost/yunohost/pull/1209), 37c0825e, 95999fea) + - security: Redact 'passphrase' settings from logs ([#1206](https://github.com/yunohost/yunohost/pull/1206)) + - security: Sane default permissions for files added using ynh_add_config and ynh_setup_source ([#1188](https://github.com/yunohost/yunohost/pull/1188)) + - backup: Support having .tar / .tar.gz in the archive name arg of backup_info/restore (00ec7b2f) + - backup: Don't backup crons + manage crons from the regenconf ([#1184](https://github.com/yunohost/yunohost/pull/1184)) + - backup: Drop support for archive restore from prior 3.8 ([#1203](https://github.com/yunohost/yunohost/pull/1203)) + - backup: Introduce hooks during restore to apply migrations between archive version and current version ([#1203](https://github.com/yunohost/yunohost/pull/1203)) + - backup: Create a proper operation log for backup_create (fe9f0731) + - backup: Improve error management for app restore ([#1191](https://github.com/yunohost/yunohost/pull/1191)) + - backup: Rework content of system backups ([#1185](https://github.com/yunohost/yunohost/pull/1185)) + - backup: Add a --dry-run option to backup_create to fetch an estimate of the backup size ([#1205](https://github.com/yunohost/yunohost/pull/1205)) + - helpers: Add --keep option to ynh_setup_source to keep files that may be overwritten during upgrade ([#1200](https://github.com/yunohost/yunohost/pull/1200)) + - helpers: Bump 'n' to version 7.1.0 ([#1197](https://github.com/yunohost/yunohost/pull/1197)) + - mail: Support SMTPS Relay ([#1159](https://github.com/yunohost/yunohost/pull/1159)) + - nginx: add header to disallow FLoC ([#1211](https://github.com/yunohost/yunohost/pull/1211)) + - app: Add route to fetch app manifest for custom app installs in a forge-agnostic way ([#1213](https://github.com/yunohost/yunohost/pull/1213)) + - perf: add optional 'apps' argument to user_permission_list to speed up user_info / user_list (e6312db3) + - ux: Add '--human-readable' to recommended command to display diagnosis issues in cli ([#1207](https://github.com/yunohost/yunohost/pull/1207)) + - Misc enh/fixes, code quality (42f8c9dc, 86f22d1b, 1468073f, b33e7c16, d1f0064b, c3754dd6, 02a30125, aabe5f19, ce9f6b3d, d7786662, f9419c96, c92e495b, 0616d632, 92eb9704, [#1190](https://github.com/yunohost/yunohost/pull/1190), [#1201](https://github.com/yunohost/yunohost/pull/1201), [#1210](https://github.com/yunohost/yunohost/pull/1210), [#1214](https://github.com/yunohost/yunohost/pull/1214), [#1215](https://github.com/yunohost/yunohost/pull/1215)) + - i18n: Translations updated for French, German + + Thanks to all contributors <3 ! (axolotle, Bram, cyxae, Daniel, Éric G., grenagit, Josué, Kay0u, lapineige, ljf, Scapharnaum) + + -- Alexandre Aubin Sat, 17 Apr 2021 03:45:49 +0200 + yunohost (4.2.1.1) testing; urgency=low - [fix] services.py, python3: missing decode() in subprocess output fetch (357c151c) From dfdc058aabac9ffddfc2bae5685c76189ecedee2 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sat, 17 Apr 2021 05:25:57 +0200 Subject: [PATCH 2417/3170] fix(py37-black-run): stop if there is not diff --- .gitlab/ci/lint.gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab/ci/lint.gitlab-ci.yml b/.gitlab/ci/lint.gitlab-ci.yml index 8f5fc3058..9c48bd912 100644 --- a/.gitlab/ci/lint.gitlab-ci.yml +++ b/.gitlab/ci/lint.gitlab-ci.yml @@ -41,6 +41,7 @@ format-run: # create a local branch that will overwrite distant one - git checkout -b "ci-format-${CI_COMMIT_REF_NAME}" --no-track - tox -e py37-black-run + - '[ $(git diff | wc -l) != 0 ] || exit 0' # stop if there is nothing to commit - git commit -am "[CI] Format code" || true - git push -f origin "ci-format-${CI_COMMIT_REF_NAME}":"ci-format-${CI_COMMIT_REF_NAME}" - hub pull-request -m "[CI] Format code" -b Yunohost:dev -p || true # GITHUB_USER and GITHUB_TOKEN registered here https://gitlab.com/yunohost/yunohost/-/settings/ci_cd From 50af0393d1e06a888d7a39ab71635835b4fe3a88 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 20 Apr 2021 20:21:37 +0200 Subject: [PATCH 2418/3170] Fix a stupid issue where an app's tmp work dir would be deleted during upgrade because of the backup process --- src/yunohost/app.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index e0e8f7b14..c048ca5ea 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -3360,9 +3360,21 @@ def _make_tmp_workdir_for_app(app=None): if not os.path.exists(APP_TMP_WORKDIRS): os.makedirs(APP_TMP_WORKDIRS) - # Cleanup old dirs + now = int(time.time()) + + # Cleanup old dirs (if any) for dir_ in os.listdir(APP_TMP_WORKDIRS): - shutil.rmtree(os.path.join(APP_TMP_WORKDIRS, dir_)) + path = os.path.join(APP_TMP_WORKDIRS, dir_) + # We only delete folders older than an arbitary 12 hours + # This is to cover the stupid case of upgrades + # Where many app will call 'yunohost backup create' + # from the upgrade script itself, + # which will also call this function while the upgrade + # script itself is running in one of those dir... + # It could be that there are other edge cases + # such as app-install-during-app-install + if os.stat(path).st_mtime < now - 12 * 3600: + shutil.rmtree(path) tmpdir = tempfile.mkdtemp(prefix="app_", dir=APP_TMP_WORKDIRS) # Copy existing app scripts, conf, ... if an app arg was provided From 4ae72cc3c886c996ee45e41f0b57313b0372c270 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 22 Apr 2021 14:41:54 +0200 Subject: [PATCH 2419/3170] Don't suggest that we can remove multiple apps --- data/actionsmap/yunohost.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 0cc1dc18b..b2f5a349b 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -673,7 +673,7 @@ app: api: DELETE /apps/ arguments: app: - help: App(s) to delete + help: App to delete ### app_upgrade() upgrade: From 381f789feca3b7a48c3422e2815b964075ad8e76 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 23 Apr 2021 21:00:21 +0200 Subject: [PATCH 2420/3170] ynh_port_available: also check ports used by other apps in settings.yml --- data/helpers.d/network | 9 +++++++-- tests/test_helpers.d/ynhtest_network.sh | 22 ++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 tests/test_helpers.d/ynhtest_network.sh diff --git a/data/helpers.d/network b/data/helpers.d/network index 702757534..2011d502b 100644 --- a/data/helpers.d/network +++ b/data/helpers.d/network @@ -20,7 +20,7 @@ ynh_find_port () { test -n "$port" || ynh_die --message="The argument of ynh_find_port must be a valid port." while ! ynh_port_available --port=$port do - port=$((port+1)) # Else, pass to next port + port=$((port+1)) done echo $port } @@ -42,7 +42,12 @@ ynh_port_available () { # Manage arguments with getopts ynh_handle_getopts_args "$@" - if ss --numeric --listening --tcp --udp | awk '{print$5}' | grep --quiet --extended-regexp ":$port$" # Check if the port is free + # Check if the port is free + if ss --numeric --listening --tcp --udp | awk '{print$5}' | grep --quiet --extended-regexp ":$port$" + then + return 1 + # This is to cover (most) case where an app is using a port yet ain't currently using it for some reason (typically service ain't up) + elif grep -q "port: '$port'" /etc/yunohost/apps/*/settings.yml then return 1 else diff --git a/tests/test_helpers.d/ynhtest_network.sh b/tests/test_helpers.d/ynhtest_network.sh new file mode 100644 index 000000000..c1644fc15 --- /dev/null +++ b/tests/test_helpers.d/ynhtest_network.sh @@ -0,0 +1,22 @@ +ynhtest_port_80_aint_available() { + ! ynh_port_available 80 +} + +ynhtest_port_12345_is_available() { + ynh_port_available 12345 +} + +ynhtest_port_12345_is_booked_by_other_app() { + + ynh_port_available 12345 + ynh_port_available 12346 + + mkdir -p /etc/yunohost/apps/block_port/ + echo "port: '12345'" > /etc/yunohost/apps/block_port/settings.yml + ! ynh_port_available 12345 + + echo "other_port: '12346'" > /etc/yunohost/apps/block_port/settings.yml + ! ynh_port_available 12346 + + rm -rf /etc/yunohost/apps/block_port +} From db3cc62bc7b3aad637fe46d53d5e501cff3ddc93 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 24 Apr 2021 17:26:03 +0200 Subject: [PATCH 2421/3170] Add ssh.app, sftp.app groups to cover my_webapp and borg needing ssh access --- data/helpers.d/user | 14 ++++++++++++-- data/hooks/conf_regen/01-yunohost | 4 ++++ data/templates/ssh/sshd_config | 2 +- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/data/helpers.d/user b/data/helpers.d/user index c12b4656e..d5ede9f73 100644 --- a/data/helpers.d/user +++ b/data/helpers.d/user @@ -92,10 +92,11 @@ ynh_system_group_exists() { # Create a system user # -# usage: ynh_system_user_create --username=user_name [--home_dir=home_dir] [--use_shell] +# usage: ynh_system_user_create --username=user_name [--home_dir=home_dir] [--use_shell] [--groups="group1 group2"] # | arg: -u, --username= - Name of the system user that will be create # | arg: -h, --home_dir= - Path of the home dir for the user. Usually the final path of the app. If this argument is omitted, the user will be created without home # | arg: -s, --use_shell - Create a user using the default login shell if present. If this argument is omitted, the user will be created with /usr/sbin/nologin shell +# | arg: -g, --groups - Add the user to system groups. Typically meant to add the user to the ssh.app / sftp.app group (e.g. for borgserver, my_webapp) # # Create a nextcloud user with no home directory and /usr/sbin/nologin login shell (hence no login capability) : # ``` @@ -110,14 +111,17 @@ ynh_system_group_exists() { ynh_system_user_create () { # Declare an array to define the options of this helper. local legacy_args=uhs - local -A args_array=( [u]=username= [h]=home_dir= [s]=use_shell ) + local -A args_array=( [u]=username= [h]=home_dir= [s]=use_shell [g]=groups= ) local username local home_dir local use_shell + local groups + # Manage arguments with getopts ynh_handle_getopts_args "$@" use_shell="${use_shell:-0}" home_dir="${home_dir:-}" + groups="${groups:-}" if ! ynh_system_user_exists "$username" # Check if the user exists on the system then # If the user doesn't exist @@ -135,6 +139,12 @@ ynh_system_user_create () { fi useradd $user_home_dir --system --user-group $username $shell || ynh_die --message="Unable to create $username system account" fi + + local group + for group in $groups + do + usermod -a -G "$group" "$username" + done } # Delete a system user diff --git a/data/hooks/conf_regen/01-yunohost b/data/hooks/conf_regen/01-yunohost index 157520400..3d65d34cd 100755 --- a/data/hooks/conf_regen/01-yunohost +++ b/data/hooks/conf_regen/01-yunohost @@ -187,6 +187,10 @@ do_post_regen() { [[ ! -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) + # Create ssh.app and sftp.app groups if they don't exist yet + grep -q '^ssh.app:' /etc/group || groupadd ssh.app + grep -q '^sftp.app:' /etc/group || groupadd sftp.app + # Propagates changes in systemd service config overrides [[ ! "$regen_conf_files" =~ "ntp.service.d/ynh-override.conf" ]] || { systemctl daemon-reload; systemctl restart ntp; } [[ ! "$regen_conf_files" =~ "nftables.service.d/ynh-override.conf" ]] || systemctl daemon-reload diff --git a/data/templates/ssh/sshd_config b/data/templates/ssh/sshd_config index dd89b214a..443d2e514 100644 --- a/data/templates/ssh/sshd_config +++ b/data/templates/ssh/sshd_config @@ -65,7 +65,7 @@ ClientAliveInterval 60 AcceptEnv LANG LC_* # Disallow user without ssh or sftp permissions -AllowGroups ssh.main sftp.main admins root +AllowGroups ssh.main sftp.main ssh.app sftp.app admins root # Allow users to create tunnels or forwarding AllowTcpForwarding yes From 913b02e4fcc32622a3a3763680333980c4c04028 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 24 Apr 2021 21:34:57 +0200 Subject: [PATCH 2422/3170] Add diagnosis section to check that app are in catalog with good quality + check for deprecated practices --- data/hooks/diagnosis/80-apps.py | 82 +++++++++++++++++++++++++++++++++ locales/en.json | 8 ++++ src/yunohost/app.py | 10 ++-- 3 files changed, 96 insertions(+), 4 deletions(-) create mode 100644 data/hooks/diagnosis/80-apps.py diff --git a/data/hooks/diagnosis/80-apps.py b/data/hooks/diagnosis/80-apps.py new file mode 100644 index 000000000..ce54faef1 --- /dev/null +++ b/data/hooks/diagnosis/80-apps.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python + +import os +import re + +from yunohost.app import app_info, app_list +from moulinette.utils.filesystem import read_file + +from yunohost.settings import settings_get +from yunohost.diagnosis import Diagnoser +from yunohost.regenconf import _get_regenconf_infos, _calculate_hash +from moulinette.utils.filesystem import read_file + + +class AppDiagnoser(Diagnoser): + + id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] + cache_duration = 300 + dependencies = [] + + def run(self): + + apps = app_list(full=True)["apps"] + for app in apps: + app["issues"] = list(self.issues(app)) + + if not any(app["issues"] for app in apps): + yield dict( + meta={"test": "apps"}, + status="SUCCESS", + summary="diagnosis_apps_allgood", + ) + else: + for app in apps: + + if not app["issues"]: + continue + + level = "ERROR" if any(issue[0] == "error" for issue in app["issues"]) else "WARNING" + + yield dict( + meta={"test": "apps", "app": app["name"]}, + status=level, + summary="diagnosis_apps_issue", + details=[issue[1] for issue in app["issues"]] + ) + + def issues(self, app): + + # Check quality level in catalog + + if not app.get("from_catalog") or app["from_catalog"].get("state") != "working": + yield ("error", "diagnosis_apps_not_in_app_catalog") + elif not isinstance(app["from_catalog"].get("level"), int) or app["from_catalog"]["level"] == 0: + yield ("error", "diagnosis_apps_broken") + elif app["from_catalog"]["level"] <= 4: + yield ("warning", "diagnosis_apps_bad_quality") + + # Check for super old, deprecated practices + + yunohost_version_req = app["manifest"].get("requirements", {}).get("yunohost", "").strip(">= ") + if yunohost_version_req.startswith("2."): + yield ("error", "diagnosis_apps_outdated_ynh_requirement") + + deprecated_helpers = [ + "yunohost app setting", + "yunohost app checkurl", + "yunohost app checkport", + "yunohost app initdb", + "yunohost tools port-available", + ] + for deprecated_helper in deprecated_helpers: + if os.system(f"grep -nr -q '{deprecated_helper}' {app['setting_path']}/scripts/") == 0: + yield ("error", "diagnosis_apps_deprecated_practices") + + old_arg_regex = r'^domain=\${?[0-9]' + if os.system(f"grep -q '{old_arg_regex}' {app['setting_path']}/scripts/install") == 0: + yield ("error", "diagnosis_apps_deprecated_practices") + + +def main(args, env, loggers): + return AppDiagnoser(args, env, loggers).diagnose() diff --git a/locales/en.json b/locales/en.json index 938a38e20..8852f5587 100644 --- a/locales/en.json +++ b/locales/en.json @@ -248,6 +248,14 @@ "diagnosis_description_web": "Web", "diagnosis_description_mail": "Email", "diagnosis_description_regenconf": "System configurations", + "diagnosis_description_apps": "Applications", + "diagnosis_apps_allgood": "All installed apps respect basic packaging practices", + "diagnosis_apps_issue": "An issue was found for app {app}", + "diagnosis_apps_not_in_app_catalog": "This application is not in YunoHost's application catalog. If it was in the past and got removed, you should consider uninstalling this app as it won't receive upgrade, and may compromise the integrity and security of your system.", + "diagnosis_apps_broken": "This application is currently flagged as broken on YunoHost's application catalog. This may be a temporary issue while the maintainers attempt to fix the issue. In the meantime, upgrading this app is disabled.", + "diagnosis_apps_bad_quality": "This application is currently flagged as broken on YunoHost's application catalog. This may be a temporary issue while the maintainers attempt to fix the issue. In the meantime, upgrading this app is disabled.", + "diagnosis_apps_outdated_ynh_requirement": "This app's installed version only requires yunohost >= 2.x, which tends to indicate that it's not up to date with recommended packaging practices and helpers. You should really consider upgrading it.", + "diagnosis_apps_deprecated_practices": "This app's installed version still uses some super-old deprecated packaging practices. You should really consider upgrading it.", "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.", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index c048ca5ea..646535fab 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -193,7 +193,8 @@ def app_info(app, full=False): "app_not_installed", app=app, all_apps=_get_all_installed_apps_id() ) - local_manifest = _get_manifest_of_app(os.path.join(APPS_SETTING_PATH, app)) + setting_path = os.path.join(APPS_SETTING_PATH, app) + local_manifest = _get_manifest_of_app(setting_path) permissions = user_permission_list(full=True, absolute_urls=True, apps=[app])[ "permissions" ] @@ -212,6 +213,7 @@ def app_info(app, full=False): if not full: return ret + ret["setting_path"] = setting_path ret["manifest"] = local_manifest ret["manifest"]["arguments"] = _set_default_ask_questions( ret["manifest"].get("arguments", {}) @@ -222,11 +224,11 @@ def app_info(app, full=False): 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") + os.path.join(setting_path, "scripts", "change_url") ) ret["supports_backup_restore"] = os.path.exists( - os.path.join(APPS_SETTING_PATH, app, "scripts", "backup") - ) and os.path.exists(os.path.join(APPS_SETTING_PATH, app, "scripts", "restore")) + os.path.join(setting_path, "scripts", "backup") + ) and os.path.exists(os.path.join(setting_path, "scripts", "restore")) ret["supports_multi_instance"] = is_true( local_manifest.get("multi_instance", False) ) From b3d4872538911b57a1099f603b3078d2700451fe Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 26 Apr 2021 16:36:13 +0200 Subject: [PATCH 2423/3170] Update changelog for 4.2.3 --- debian/changelog | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/debian/changelog b/debian/changelog index fefbfe94c..9eab856b3 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,15 @@ +yunohost (4.2.3) testing; urgency=low + + - Fix a stupid issue where an app's tmp work dir would be deleted during upgrade because of the backup process (50af0393) + - cli ux: Don't suggest that we can remove multiple apps (4ae72cc3) + - ynh_port_available: also check ports used by other apps in settings.yml (381f789f) + - ssh: Add ssh.app, sftp.app groups to cover my_webapp and borg needing ssh access ([#1216](https://github.com/yunohost/yunohost/pull/1216)) + - i18n: Translations updated for German + + Thanks to all contributors <3 ! (Bram, Christian W.) + + -- Alexandre Aubin Mon, 26 Apr 2021 16:29:17 +0200 + yunohost (4.2.2) testing; urgency=low - permissions: Add SFTP / SSH permissions ([#606](https://github.com/yunohost/yunohost/pull/606)) From 2443b2ee1da3c354015b4cde1c0aa43a65e9638f Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Mon, 26 Apr 2021 15:10:07 +0000 Subject: [PATCH 2424/3170] [CI] Format code --- src/yunohost/backup.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index c696e99da..cf8394f5b 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -668,7 +668,7 @@ class BackupManager: self.targets.set_result("system", part, "Error") def _collect_apps_files(self): - """ Prepare backup for each selected apps """ + """Prepare backup for each selected apps""" apps_targets = self.targets.list("apps", exclude=["Skipped"]) @@ -1214,7 +1214,7 @@ class RestoreManager: writer.writerow(row) def _restore_system(self): - """ Restore user and system parts """ + """Restore user and system parts""" system_targets = self.targets.list("system", exclude=["Skipped"]) @@ -1872,7 +1872,7 @@ class CopyBackupMethod(BackupMethod): method_name = "copy" def backup(self): - """ Copy prepared files into a the repo """ + """Copy prepared files into a the repo""" # Check free space in output self._check_is_enough_free_space() @@ -2626,7 +2626,7 @@ def backup_delete(name): def _create_archive_dir(): - """ Create the YunoHost archives directory if doesn't exist """ + """Create the YunoHost archives directory if doesn't exist""" if not os.path.isdir(ARCHIVES_PATH): if os.path.lexists(ARCHIVES_PATH): raise YunohostError("backup_output_symlink_dir_broken", path=ARCHIVES_PATH) @@ -2637,7 +2637,7 @@ def _create_archive_dir(): def _call_for_each_path(self, callback, csv_path=None): - """ Call a callback for each path in csv """ + """Call a callback for each path in csv""" if csv_path is None: csv_path = self.csv_path with open(csv_path, "r") as backup_file: From 3e408b20bc0636a2aa4db96c3d58946e8a6d0dcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= Date: Tue, 27 Apr 2021 13:21:52 +0000 Subject: [PATCH 2425/3170] Translated using Weblate (French) Currently translated at 99.8% (628 of 629 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 50909fe28..b9984859d 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -98,7 +98,7 @@ "port_already_closed": "Le port {port:d} est déjà fermé pour les connexions {ip_version:s}", "port_already_opened": "Le port {port:d} est déjà ouvert pour les connexions {ip_version:s}", "restore_already_installed_app": "Une application est déjà installée avec l’identifiant '{app:s}'", - "app_restore_failed": "Impossible de restaurer '{app:s}': {error:s}", + "app_restore_failed": "Impossible de restaurer {app:s} : {error:s}", "restore_cleaning_failed": "Impossible de nettoyer le dossier temporaire de restauration", "restore_complete": "Restauration terminée", "restore_confirm_yunohost_installed": "Voulez-vous vraiment restaurer un système déjà installé ? [{answers:s}]", @@ -623,5 +623,9 @@ "domain_remove_confirm_apps_removal": "Le retrait de ce domaine retirera aussi ces applications :\n{apps}\n\nÊtes vous sûr de vouloir cela ? [{answers}]", "diagnosis_rootfstotalspace_critical": "Le système de fichiers racine ne fait que {space} ! Vous allez certainement le remplir très rapidement ! Il est recommandé d'avoir au moins 16 GB pour ce système de fichiers.", "diagnosis_rootfstotalspace_warning": "Le système de fichiers racine n'est que de {space}. Cela peut suffire, mais faites attention car vous risquez de les remplir rapidement... Il est recommandé d'avoir au moins 16 GB pour ce système de fichiers.", - "app_restore_script_failed": "Une erreur s'est produite dans le script de restauration de l'application" + "app_restore_script_failed": "Une erreur s'est produite dans le script de restauration de l'application", + "restore_backup_too_old": "Cette archive de sauvegarde ne peut être restaurée car elle provient d'une version trop ancienne de YunoHost.", + "migration_update_LDAP_schema": "Mise à jour du schéma LDAP...", + "log_backup_create": "Créer une archive de sauvegarde", + "global_settings_setting_ssowat_panel_overlay_enabled": "Activer la superposition du panneau SSOwat" } From e16f14f794d1f3ad5764e59b1a24021fa0f2aefd Mon Sep 17 00:00:00 2001 From: Paco Date: Tue, 27 Apr 2021 21:42:18 +0200 Subject: [PATCH 2426/3170] fix . set operation still not working. --- data/actionsmap/yunohost.yml | 2 +- src/yunohost/domain.py | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 5df29422c..b61a9a26e 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -557,7 +557,7 @@ domain: api: GET /domains//settings arguments: domain: - help: Domaine name + help: Domain name key: help: Key to get/set -v: diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index cf0867134..59e445154 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -745,16 +745,20 @@ def domain_setting(domain, key, value=None, delete=False): # TODO add locales raise YunohostError("domain_name_unknown", domain=domain) - domain_settings = _get_domain_settings(domain, False) or {} + domain_settings = domains[domain] # GET if value is None and not delete: - return domain_settings.get(key, None) + if not key in domain_settings: + raise YunohostValidationError("This key doesn't exist!") + + return domain_settings[key] # DELETE if delete: if key in domain_settings: del domain_settings[key] + _set_domain_settings(domain, domain_settings) # SET else: @@ -770,6 +774,7 @@ def domain_setting(domain, key, value=None, delete=False): # TODO add locales raise YunohostError("must_be_positive", value_type=type(ttl)) domain_settings[key] = value + _set_domain_settings(domain, domain_settings) def _get_domain_settings(domain, subdomains): """ From eeab7cd1030c310dea1b8ef8bc9c3ce51d4e1343 Mon Sep 17 00:00:00 2001 From: Paco Date: Wed, 28 Apr 2021 00:26:19 +0200 Subject: [PATCH 2427/3170] ~ working push_config. Tested with Gandi. To be improved! --- data/actionsmap/yunohost.yml | 10 +++ src/yunohost/domain.py | 163 +++++++++++++++++++++++++++-------- 2 files changed, 136 insertions(+), 37 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 53d3e11c8..58a48c87f 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -447,6 +447,16 @@ domain: help: Subscribe to the DynDNS service action: store_true + ### domain_push_config() + push_config: + action_help: Push DNS records to registrar + api: GET /domains/push + arguments: + domain: + help: Domain name to add + extra: + pattern: *pattern_domain + ### domain_remove() remove: action_help: Delete domains diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index f878fe17a..05f2a16ae 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -27,6 +27,10 @@ import os import re import sys import yaml +import functools + +from lexicon.config import ConfigResolver +from lexicon.client import Client from moulinette import m18n, msettings, msignals from moulinette.core import MoulinetteError @@ -103,7 +107,6 @@ def domain_add(operation_logger, domain, dyndns=False): from yunohost.hook import hook_callback from yunohost.app import app_ssowatconf from yunohost.utils.ldap import _get_ldap_interface - from yunohost.certificate import _certificate_install_selfsigned if domain.startswith("xmpp-upload."): raise YunohostValidationError("domain_cannot_add_xmpp_upload") @@ -126,7 +129,7 @@ def domain_add(operation_logger, domain, dyndns=False): # Do not allow to subscribe to multiple dyndns domains... if _guess_current_dyndns_domain("dyndns.yunohost.org") != (None, None): - raise YunohostValidationError("domain_dyndns_already_subscribed") + raise YunohostValidationError('domain_dyndns_already_subscribed') # Check that this domain can effectively be provided by # dyndns.yunohost.org. (i.e. is it a nohost.me / noho.st) @@ -137,13 +140,14 @@ def domain_add(operation_logger, domain, dyndns=False): if dyndns: from yunohost.dyndns import dyndns_subscribe - # Actually subscribe dyndns_subscribe(domain=domain) - _certificate_install_selfsigned([domain], False) - try: + import yunohost.certificate + + yunohost.certificate._certificate_install_selfsigned([domain], False) + attr_dict = { "objectClass": ["mailDomain", "top"], "virtualdomain": domain, @@ -170,13 +174,13 @@ def domain_add(operation_logger, domain, dyndns=False): regen_conf(names=["nginx", "metronome", "dnsmasq", "postfix", "rspamd"]) app_ssowatconf() - except Exception as e: + except Exception: # Force domain removal silently try: domain_remove(domain, force=True) except Exception: pass - raise e + raise hook_callback("post_domain_add", args=[domain]) @@ -202,8 +206,8 @@ def domain_remove(operation_logger, domain, remove_apps=False, force=False): # the 'force' here is related to the exception happening in domain_add ... # we don't want to check the domain exists because the ldap add may have # failed - if not force and domain not in domain_list()["domains"]: - raise YunohostValidationError("domain_name_unknown", domain=domain) + if not force and domain not in domain_list()['domains']: + raise YunohostValidationError('domain_name_unknown', domain=domain) # Check domain is not the main domain if domain == _get_maindomain(): @@ -217,9 +221,7 @@ def domain_remove(operation_logger, domain, remove_apps=False, force=False): other_domains="\n * " + ("\n * ".join(other_domains)), ) else: - raise YunohostValidationError( - "domain_cannot_remove_main_add_new_one", domain=domain - ) + raise YunohostValidationError("domain_cannot_remove_main_add_new_one", domain=domain) # Check if apps are installed on the domain apps_on_that_domain = [] @@ -228,37 +230,21 @@ def domain_remove(operation_logger, domain, remove_apps=False, force=False): settings = _get_app_settings(app) label = app_info(app)["name"] if settings.get("domain") == domain: - apps_on_that_domain.append( - ( - app, - ' - %s "%s" on https://%s%s' - % (app, label, domain, settings["path"]) - if "path" in settings - else app, - ) - ) + apps_on_that_domain.append((app, " - %s \"%s\" on https://%s%s" % (app, label, domain, settings["path"]) if "path" in settings else app)) if apps_on_that_domain: if remove_apps: - if msettings.get("interface") == "cli" and not force: - answer = msignals.prompt( - m18n.n( - "domain_remove_confirm_apps_removal", - apps="\n".join([x[1] for x in apps_on_that_domain]), - answers="y/N", - ), - color="yellow", - ) + if msettings.get('interface') == "cli" and not force: + answer = msignals.prompt(m18n.n('domain_remove_confirm_apps_removal', + apps="\n".join([x[1] for x in apps_on_that_domain]), + answers='y/N'), color="yellow") if answer.upper() != "Y": raise YunohostError("aborting") for app, _ in apps_on_that_domain: app_remove(app) else: - raise YunohostValidationError( - "domain_uninstall_app_first", - apps="\n".join([x[1] for x in apps_on_that_domain]), - ) + raise YunohostValidationError('domain_uninstall_app_first', apps="\n".join([x[1] for x in apps_on_that_domain])) operation_logger.start() @@ -271,7 +257,7 @@ def domain_remove(operation_logger, domain, remove_apps=False, force=False): os.system("rm -rf /etc/yunohost/certs/%s" % domain) # Delete dyndns keys for this domain (if any) - os.system("rm -rf /etc/yunohost/dyndns/K%s.+*" % domain) + os.system('rm -rf /etc/yunohost/dyndns/K%s.+*' % domain) # Sometime we have weird issues with the regenconf where some files # appears as manually modified even though they weren't touched ... @@ -558,8 +544,9 @@ def _build_dns_conf(domains): if ipv6: extra.append(["*", ttl, "AAAA", ipv6]) - elif include_empty_AAAA_if_no_ipv6: - extra.append(["*", ttl, "AAAA", None]) + # TODO + # elif include_empty_AAAA_if_no_ipv6: + # extra.append(["*", ttl, "AAAA", None]) extra.append([name, ttl, "CAA", '128 issue "letsencrypt.org"']) @@ -838,3 +825,105 @@ def _set_domain_settings(domain, domain_settings): with open(DOMAIN_SETTINGS_PATH, 'w') as file: yaml.dump(domains, file, default_flow_style=False) + +def domain_push_config(domain): + """ + Send DNS records to the previously-configured registrar of the domain. + """ + # Generate the records + if domain not in domain_list()["domains"]: + raise YunohostValidationError("domain_name_unknown", domain=domain) + + domains_settings = _get_domain_settings(domain, True) + + dns_conf = _build_dns_conf(domains_settings) + + # Flatten the DNS conf + flatten_dns_conf = [] + for key in dns_conf: + list_of_records = dns_conf[key] + for record in list_of_records: + # FIXME Lexicon does not support CAA records + # See https://github.com/AnalogJ/lexicon/issues/282 and https://github.com/AnalogJ/lexicon/pull/371 + # They say it's trivial to implement it! + # And yet, it is still not done/merged + if record["type"] != "CAA": + # Add .domain.tdl to the name entry + record["name"] = "{}.{}".format(record["name"], domain) + flatten_dns_conf.append(record) + + # Get provider info + # TODO + provider = { + "name": "gandi", + "options": { + "api_protocol": "rest", + "auth_token": "vhcIALuRJKtoZiZyxfDYWLom" + } + } + + # Construct the base data structure to use lexicon's API. + base_config = { + "provider_name": provider["name"], + "domain": domain, # domain name + } + base_config[provider["name"]] = provider["options"] + + # Get types present in the generated records + types = set() + + for record in flatten_dns_conf: + types.add(record["type"]) + + # Fetch all types present in the generated records + distant_records = {} + + for key in types: + record_config = { + "action": "list", + "type": key, + } + final_lexicon = ConfigResolver().with_dict(dict_object=base_config).with_dict(dict_object=record_config) + # print('final_lexicon:', final_lexicon); + client = Client(final_lexicon) + distant_records[key] = client.execute() + + for key in types: + for distant_record in distant_records[key]: + print('distant_record:', distant_record); + for local_record in flatten_dns_conf: + print('local_record:', local_record); + + # Push the records + for record in flatten_dns_conf: + # For each record, first check if one record exists for the same (type, name) couple + it_exists = False + # TODO do not push if local and distant records are exactly the same ? + # is_the_same_record = False + + for distant_record in distant_records[record["type"]]: + if distant_record["type"] == record["type"] and distant_record["name"] == record["name"]: + it_exists = True + # previous TODO + # if distant_record["ttl"] = ... and distant_record["name"] ... + # is_the_same_record = True + + # Finally, push the new record or update the existing one + record_config = { + "action": "update" if it_exists else "create", # create, list, update, delete + "type": record["type"], # specify a type for record filtering, case sensitive in some cases. + "name": record["name"], + "content": record["value"], + # FIXME Delte TTL, doesn't work with Gandi. + # See https://github.com/AnalogJ/lexicon/issues/726 (similar issue) + # But I think there is another issue with Gandi. Or I'm misusing the API... + # "ttl": record["ttl"], + } + final_lexicon = ConfigResolver().with_dict(dict_object=base_config).with_dict(dict_object=record_config) + client = Client(final_lexicon) + print('pushed_record:', record_config, "→", end=' ') + results = client.execute() + print('results:', results); + # print("Failed" if results == False else "Ok") + +# def domain_config_fetch(domain, key, value): From ee83c3f9ba9cd9654635beb62f65fd1956c0f1f3 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 28 Apr 2021 17:47:22 +0200 Subject: [PATCH 2428/3170] Recreate the admins group which for some reason didnt exist on old setups .. --- data/hooks/conf_regen/06-slapd | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/data/hooks/conf_regen/06-slapd b/data/hooks/conf_regen/06-slapd index c23f1b155..e7524184c 100755 --- a/data/hooks/conf_regen/06-slapd +++ b/data/hooks/conf_regen/06-slapd @@ -126,6 +126,20 @@ do_post_regen() { then systemctl daemon-reload systemctl restart slapd + sleep 3 + fi + + # For some reason, old setups don't have the admins group defined... + if ! slapcat | grep -q 'cn=admins,ou=groups,dc=yunohost,dc=org' + then + slapadd -F /etc/ldap/slapd.d -b dc=yunohost,dc=org <<< \ +"dn: cn=admins,ou=groups,dc=yunohost,dc=org +cn: admins +gidNumber: 4001 +memberUid: admin +objectClass: posixGroup +objectClass: top" + nscd -i groups fi [ -z "$regen_conf_files" ] && exit 0 From aa0d7195cf1f2a88f81ebc8882b81c7ef8a95c06 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 28 Apr 2021 18:00:17 +0200 Subject: [PATCH 2429/3170] Update changelog for 4.2.3.1 --- debian/changelog | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/debian/changelog b/debian/changelog index 9eab856b3..cf8591f79 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,12 @@ +yunohost (4.2.3.1) testing; urgency=low + + - [fix] Recreate the admins group which for some reason didnt exist on old setups .. (ee83c3f9) + - [i18n] Translations updated for French + + Thanks to all contributors <3 ! (Éric G., ppr) + + -- Alexandre Aubin Wed, 28 Apr 2021 17:59:14 +0200 + yunohost (4.2.3) testing; urgency=low - Fix a stupid issue where an app's tmp work dir would be deleted during upgrade because of the backup process (50af0393) From af567c6f85deddad352dc59f3714a04b9c03f2f4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 3 May 2021 19:02:10 +0200 Subject: [PATCH 2430/3170] python3: smtplib's sendmail miserably crashes with encoding issue if accent in mail body --- src/yunohost/certificate.py | 2 +- src/yunohost/diagnosis.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 7e6726f31..e240774e1 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -499,7 +499,7 @@ Subject: %s import smtplib smtp = smtplib.SMTP("localhost") - smtp.sendmail(from_, [to_], message) + smtp.sendmail(from_, [to_], message.encode('utf-8')) smtp.quit() diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index 29ffe686b..602efef8a 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -712,5 +712,5 @@ Subject: %s import smtplib smtp = smtplib.SMTP("localhost") - smtp.sendmail(from_, [to_], message) + smtp.sendmail(from_, [to_], message.encode('utf-8')) smtp.quit() From 19d2901d4685ef2af3c5bc255beae1e5a028aa1b Mon Sep 17 00:00:00 2001 From: yalh76 Date: Thu, 6 May 2021 13:05:21 +0200 Subject: [PATCH 2431/3170] Manage case of service already stopped --- data/helpers.d/systemd | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/data/helpers.d/systemd b/data/helpers.d/systemd index f9fe3ead0..9a9a2d9f8 100644 --- a/data/helpers.d/systemd +++ b/data/helpers.d/systemd @@ -88,6 +88,12 @@ ynh_systemd_action() { log_path="${log_path:-/var/log/$service_name/$service_name.log}" timeout=${timeout:-300} + # Manage case of service already stopped + if [ "$action" == "stop" ] && ! systemctl is-active --quiet $service_name + then + return 0 + fi + # Start to read the log if [[ -n "$line_match" ]] then From 51478d14e2f04fe8de1332b8e4ece10bdcb26e28 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 7 May 2021 19:48:22 +0200 Subject: [PATCH 2432/3170] ssh_config: add conf block for sftp apps --- data/templates/ssh/sshd_config | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/data/templates/ssh/sshd_config b/data/templates/ssh/sshd_config index 443d2e514..1c2854f73 100644 --- a/data/templates/ssh/sshd_config +++ b/data/templates/ssh/sshd_config @@ -90,6 +90,14 @@ Match Group sftp.main,!ssh.main # Disable .ssh/rc, which could be edited (e.g. from Nextcloud or whatever) by users to execute arbitrary commands even if SSH login is disabled PermitUserRC no +Match Group sftp.app,!ssh.app + ForceCommand internal-sftp + ChrootDirectory %h + AllowTcpForwarding no + AllowStreamLocalForwarding no + PermitTunnel no + PermitUserRC no + PasswordAuthentication yes # root login is allowed on local networks # It's meant to be a backup solution in case LDAP is down and From b38d5eedf73e33fc2750cfa3c611445c340942b6 Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Fri, 7 May 2021 18:22:04 +0000 Subject: [PATCH 2433/3170] [CI] Format code --- src/yunohost/certificate.py | 2 +- src/yunohost/diagnosis.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index e240774e1..c01bff84e 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -499,7 +499,7 @@ Subject: %s import smtplib smtp = smtplib.SMTP("localhost") - smtp.sendmail(from_, [to_], message.encode('utf-8')) + smtp.sendmail(from_, [to_], message.encode("utf-8")) smtp.quit() diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index 602efef8a..ff1a14c4e 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -712,5 +712,5 @@ Subject: %s import smtplib smtp = smtplib.SMTP("localhost") - smtp.sendmail(from_, [to_], message.encode('utf-8')) + smtp.sendmail(from_, [to_], message.encode("utf-8")) smtp.quit() From 5b52fbeee12417529cb174af56a7b080f17003ea Mon Sep 17 00:00:00 2001 From: Christian Wehrli Date: Thu, 29 Apr 2021 10:28:58 +0000 Subject: [PATCH 2434/3170] Translated using Weblate (German) Currently translated at 86.6% (551 of 636 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/locales/de.json b/locales/de.json index 6acf5ee9f..53297ed6d 100644 --- a/locales/de.json +++ b/locales/de.json @@ -585,5 +585,10 @@ "root_password_desynchronized": "Das Admin-Passwort wurde verändert, aber das root Passwort ist immer noch das alte.", "regenconf_need_to_explicitly_specify_ssh": "Die SSH-Konfiguration wurde manuell modifiziert, aber Sie müssen explizit die Kategorie 'SSH' mit --force spezifizieren, um die Änderungen tatsächlich anzuwenden.", "migration_update_LDAP_schema": "Aktualisiere das LDAP-Schema...", - "log_backup_create": "Erstelle ein Backup-Archiv" -} \ No newline at end of file + "log_backup_create": "Erstelle ein Backup-Archiv", + "diagnosis_sshd_config_inconsistent": "Es sieht aus, als ob der SSH-Port manuell geändert wurde in /etc/ssh/ssh_config. Seit YunoHost 4.2 ist eine neue globale Einstellung 'security.ssh.port' verfügbar um zu verhindern, dass die Konfiguration manuell verändert wird.", + "diagnosis_sshd_config_insecure": "Die SSH-Konfiguration wurde scheinbar manuell abgeändert, und ist unsicher, weil sie keine 'AllowGroups'- oder 'AllowUsers' -Direktiven für die Begrenzung des Zugriffs durch autorisierte Benutzer enthält.", + "backup_create_size_estimation": "Das Archiv wird etwa {size} Daten enthalten", + "app_restore_script_failed": "Im Wiederherstellungsskript der Anwendung ist ein Fehler aufgetreten", + "app_restore_failed": "Konnte {apps:s} nicht wiederherstellen: {error:s}" +} From badbfacb744a54109d61f6973bfb76ff1db9ed35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quent=C3=AD?= Date: Sun, 2 May 2021 09:56:55 +0000 Subject: [PATCH 2435/3170] Translated using Weblate (Occitan) Currently translated at 58.3% (371 of 636 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/oc/ --- locales/oc.json | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/locales/oc.json b/locales/oc.json index efcab1c2a..ec272edfb 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -472,8 +472,8 @@ "diagnosis_services_bad_status_tip": "Podètz ensajar de reaviar lo servici, e se non fonciona pas, podètz agachar los jornals de servici a la pagina web d’administracion(en linha de comanda podètz utilizar yunohost service restart {service} e yunohost service log {service}).", "diagnosis_http_connection_error": "Error de connexion : connexion impossibla al domeni demandat, benlèu qu’es pas accessible.", "group_user_already_in_group": "L’utilizaire {user} es ja dins lo grop « {group} »", - "diagnosis_ip_broken_resolvconf": "La resolucion del nom de domeni sembla copada sul servidor, poiriá èsser ligada al fait que /etc/resolv.conf manda pas a 127.0.0.1.", - "diagnosis_ip_weird_resolvconf": "La resolucion del nom de domeni sembla foncionar, mas siatz prudent en utilizant un fichièr /etc/resolv.con personalizat.", + "diagnosis_ip_broken_resolvconf": "La resolucion del nom de domeni sembla copada sul servidor, poiriá èsser ligada al fait que /etc/resolv.conf manda pas a 127.0.0.1.", + "diagnosis_ip_weird_resolvconf": "La resolucion del nom de domeni sembla foncionar, mas sembla qu’utiilizatz un fichièr /etc/resolv.conf personalizat.", "diagnosis_diskusage_verylow": "Lo lòc d’emmagazinatge {mountpoint} (sul periferic {device}) a solament {free} ({free_percent}%). Deuriatz considerar de liberar un pauc d’espaci.", "global_settings_setting_pop3_enabled": "Activar lo protocòl POP3 pel servidor de corrièr", "diagnosis_diskusage_ok": "Lo lòc d’emmagazinatge {mountpoint} (sul periferic {device}) a encara {free} ({free_percent}%) de liure !", @@ -513,5 +513,13 @@ "log_app_action_run": "Executar l’accion de l’aplicacion « {} »", "diagnosis_basesystem_hardware_model": "Lo modèl del servidor es {model}", "backup_archive_cant_retrieve_info_json": "Obtencion impossibla de las informacions de l’archiu « {archive} »... Se pòt pas recuperar lo fichièr info.json (o es pas un fichièr json valid).", - "app_packaging_format_not_supported": "Se pòt pas installar aquesta aplicacion pr’amor que son format es pas pres en carga per vòstra version de YunoHost. Deuriatz considerar actualizar lo sistèma." -} \ No newline at end of file + "app_packaging_format_not_supported": "Se pòt pas installar aquesta aplicacion pr’amor que son format es pas pres en carga per vòstra version de YunoHost. Deuriatz considerar actualizar lo sistèma.", + "diagnosis_mail_fcrdns_ok": "Vòstre DNS inverse es corrèctament configurat !", + "diagnosis_mail_outgoing_port_25_ok": "Lo servidor de messatge SMTP pòt enviar de corrièls (lo pòrt 25 es pas blocat).", + "diagnosis_domain_expiration_warning": "D’unes domenis expiraràn lèu !", + "diagnosis_domain_expiration_success": "Vòstres domenis son enregistrats e expiraràn pas lèu.", + "diagnosis_domain_not_found_details": "Lo domeni {domain} existís pas a la basa de donadas WHOIS o a expirat !", + "diagnosis_domain_expiration_not_found": "Impossible de verificar la data d’expiracion d’unes domenis", + "backup_create_size_estimation": "L’archiu contendrà apr’aquí {size} de donadas.", + "app_restore_script_failed": "Una error s’es producha a l’interior del script de restauracion de l’aplicacion" +} From abd05beff2ec9e42b6e2b8b69d008121e4a71089 Mon Sep 17 00:00:00 2001 From: Flavio Cristoforetti Date: Wed, 5 May 2021 14:17:18 +0000 Subject: [PATCH 2436/3170] Translated using Weblate (Italian) Currently translated at 100.0% (636 of 636 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/it/ --- locales/it.json | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/locales/it.json b/locales/it.json index 8b557b077..6b15dd900 100644 --- a/locales/it.json +++ b/locales/it.json @@ -622,5 +622,21 @@ "postinstall_low_rootfsspace": "La radice del filesystem ha uno spazio totale inferiore ai 10 GB, ed è piuttosto preoccupante! Consumerai tutta la memoria molto velocemente! Raccomandiamo di avere almeno 16 GB per la radice del filesystem. Se vuoi installare YunoHost ignorando questo avviso, esegui nuovamente il postinstall con l'argomento --force-diskspace", "domain_remove_confirm_apps_removal": "Rimuovere questo dominio rimuoverà anche le seguenti applicazioni:\n{apps}\n\nSei sicuro di voler continuare? [{answers}]", "diagnosis_rootfstotalspace_critical": "La radice del filesystem ha un totale di solo {space}, ed è piuttosto preoccupante! Probabilmente consumerai tutta la memoria molto velocemente! Raccomandiamo di avere almeno 16 GB per la radice del filesystem.", - "diagnosis_rootfstotalspace_warning": "La radice del filesystem ha un totale di solo {space}. Potrebbe non essere un problema, ma stai attento perché potresti consumare tutta la memoria velocemente... Raccomandiamo di avere almeno 16 GB per la radice del filesystem." -} \ No newline at end of file + "diagnosis_rootfstotalspace_warning": "La radice del filesystem ha un totale di solo {space}. Potrebbe non essere un problema, ma stai attento perché potresti consumare tutta la memoria velocemente... Raccomandiamo di avere almeno 16 GB per la radice del filesystem.", + "restore_backup_too_old": "Questo archivio backup non può essere ripristinato perché è stato generato da una versione troppo vecchia di YunoHost.", + "permission_cant_add_to_all_users": "Il permesso {permission} non può essere aggiunto a tutto gli utenti.", + "migration_update_LDAP_schema": "Aggiorno lo schema LDAP...", + "migration_ldap_rollback_success": "Sistema ripristinato allo stato precedente.", + "migration_ldap_migration_failed_trying_to_rollback": "Impossibile migrare... provo a ripristinare il sistema.", + "migration_ldap_can_not_backup_before_migration": "Il backup del sistema non è stato completato prima che la migrazione fallisse. Errore: {error:s}", + "migration_ldap_backup_before_migration": "Sto generando il backup del database LDAP e delle impostazioni delle app prima di effettuare la migrazione.", + "migration_description_0020_ssh_sftp_permissions": "Aggiungi il supporto ai permessi SSH e SFTP", + "log_backup_create": "Crea un archivio backup", + "global_settings_setting_ssowat_panel_overlay_enabled": "Abilita il pannello sovrapposto SSOwat", + "global_settings_setting_security_ssh_port": "Porta SSH", + "diagnosis_sshd_config_inconsistent_details": "Esegui yunohost settings set security.ssh.port -v PORTA_SSH per definire la porta SSH, e controlla con yunohost tools regen-conf ssh --dry-run --with-diff, poi yunohost tools regen-conf ssh --force per resettare la tua configurazione con le raccomandazioni Yunohost.", + "diagnosis_sshd_config_inconsistent": "Sembra che la porta SSH sia stata modificata manualmente in /etc/ssh/sshd_config: A partire da Yunohost 4.2, una nuova configurazione globale 'security.ssh.port' è disponibile per evitare di modificare manualmente la configurazione.", + "diagnosis_sshd_config_insecure": "Sembra che la configurazione SSH sia stata modificata manualmente, ed non è sicuro dato che non contiene le direttive 'AllowGroups' o 'Allowusers' che limitano l'accesso agli utenti autorizzati.", + "backup_create_size_estimation": "L'archivio conterrà circa {size} di dati.", + "app_restore_script_failed": "C'è stato un errore all'interno dello script di recupero" +} From b4570b81da6387e09aa9ed246d059634a1c0db71 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 8 May 2021 15:06:49 +0200 Subject: [PATCH 2437/3170] Update changelog for 4.2.4 --- debian/changelog | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/debian/changelog b/debian/changelog index cf8591f79..9cd981455 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,15 @@ +yunohost (4.2.4) stable; urgency=low + + - python3: smtplib's sendmail miserably crashes with encoding issue if accent in mail body (af567c6f) + - ssh_config: add conf block for sftp apps (51478d14) + - ynh_systemd_action: Fix case where service is already stopped ([#1222](https://github.com/yunohost/yunohost/pull/1222)) + - [i18n] Translations updated for German, Italian, Occitan + - Releasing as stable + + Thanks to all contributors <3 ! (Christian Wehrli, Flavio Cristoforetti, Quentí, yalh76) + + -- Alexandre Aubin Sat, 08 May 2021 15:05:43 +0200 + yunohost (4.2.3.1) testing; urgency=low - [fix] Recreate the admins group which for some reason didnt exist on old setups .. (ee83c3f9) From 2315e811fa1f418a832ca9b8f732aeb23c18e4b7 Mon Sep 17 00:00:00 2001 From: ljf Date: Mon, 30 Nov 2020 10:58:50 +0100 Subject: [PATCH 2438/3170] [wip] Import users with a CSV --- data/actionsmap/yunohost.yml | 17 +++++++++++++++++ src/yunohost/user.py | 11 +++++++++++ 2 files changed, 28 insertions(+) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index b2f5a349b..075e429ec 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -206,6 +206,23 @@ user: arguments: username: help: Username or email to get information + + ### user_import() + import: + action_help: Import several users from CSV + api: POST /users/import + arguments: + csv: + help: "CSV file with columns username, email, quota, groups(separated by coma) and optionally password" + type: open + -u: + full: --update + help: Update all existing users contained in the csv file (by default those users are ignored) + action: store_true + -d: + full: --delete + help: Delete all existing users that are not contained in the csv file (by default those users are ignored) + action: store_true subcategories: group: diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 266c2774c..7b920b8a9 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -566,6 +566,17 @@ def user_info(username): return result_dict +def user_import(csv, update=False, delete=False): + """ + Import users from CSV + + Keyword argument: + csv -- CSV file with columns username, email, quota, groups and optionnally password + + """ + logger.warning(type(csv)) + return {} + # # Group subcategory # From fdc2337e0f9ccc624ac1d293d98f5714ee8a844e Mon Sep 17 00:00:00 2001 From: ljf Date: Thu, 3 Dec 2020 18:04:09 +0100 Subject: [PATCH 2439/3170] [wip] Import users by csv --- src/yunohost/user.py | 95 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 92 insertions(+), 3 deletions(-) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 7b920b8a9..a9010c060 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -566,7 +566,8 @@ def user_info(username): return result_dict -def user_import(csv, update=False, delete=False): +@is_unit_operation() +def user_import(operation_logger, csv, update=False, delete=False): """ Import users from CSV @@ -574,8 +575,96 @@ def user_import(csv, update=False, delete=False): csv -- CSV file with columns username, email, quota, groups and optionnally password """ - logger.warning(type(csv)) - return {} + import csv # CSV are needed only in this function + + # Prepare what should be done + actions = { + 'created': [], + 'updated': [], + 'deleted': [] + } + is_well_formatted = True + + existing_users = user_list()['users'].keys() + reader = csv.DictReader(csv, delimiter=';', quotechar='"') + for user in reader: + if user['username']:#TODO better check + logger.error(m18n.n('user_import_bad_line', line=reader.line_num)) + is_well_formatted = False + continue + + if user['username'] not in existing_users: + actions['created'].append(user) + else: + if update: + actions['updated'].append(user) + existing_users.remove(user['username']) + + if delete: + for user in existing_users: + actions['deleted'].append(user) + + if not is_well_formatted: + raise YunohostError('user_import_bad_file') + + total = len(actions['created'] + actions['updated'] + actions['deleted']) + + # Apply creation, update and deletion operation + result = { + 'created': 0, + 'updated': 0, + 'deleted': 0, + 'errors': 0 + } + + if total == 0: + logger.info(m18n.n('nothing_to_do')) + return + + def on_failure(user, exception): + result['errors'] += 1 + logger.error(user + ': ' + str(exception)) + + operation_logger.start() + for user in actions['created']: + try: + user_create(operation_logger, user['username'], + user['firstname'], user['lastname'], + user['domain'], user['password'], + user['mailbox_quota'], user['mail']) + result['created'] += 1 + except Exception as e: + on_failure(user['username'], e) + + if update: + for user in actions['updated']: + try: + user_update(operation_logger, user['username'], + user['firstname'], user['lastname'], + user['mail'], user['password'], + mailbox_quota=user['mailbox_quota']) + result['updated'] += 1 + except Exception as e: + on_failure(user['username'], e) + + if delete: + for user in actions['deleted']: + try: + user_delete(operation_logger, user, purge=True) + result['deleted'] += 1 + except Exception as e: + on_failure(user, e) + + if result['errors']: + msg = m18n.n('user_import_partial_failed') + if result['created'] + result['updated'] + result['deleted'] == 0: + msg = m18n.n('user_import_failed') + logger.error(msg) + operation_logger.error(msg) + else: + logger.success(m18n.n('user_import_success')) + operation_logger.success() + return result # # Group subcategory From 2ae0ec46f44a55eb98dbdca90711ab6708912604 Mon Sep 17 00:00:00 2001 From: ljf Date: Tue, 8 Dec 2020 16:47:28 +0100 Subject: [PATCH 2440/3170] [wip] Import users from csv --- src/yunohost/user.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index a9010c060..7745ec56a 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -588,7 +588,7 @@ def user_import(operation_logger, csv, update=False, delete=False): existing_users = user_list()['users'].keys() reader = csv.DictReader(csv, delimiter=';', quotechar='"') for user in reader: - if user['username']:#TODO better check + if re.match(r'^[a-z0-9_]+$', user['username']:#TODO better check logger.error(m18n.n('user_import_bad_line', line=reader.line_num)) is_well_formatted = False continue @@ -636,6 +636,7 @@ def user_import(operation_logger, csv, update=False, delete=False): except Exception as e: on_failure(user['username'], e) +<<<<<<< Updated upstream if update: for user in actions['updated']: try: @@ -654,6 +655,24 @@ def user_import(operation_logger, csv, update=False, delete=False): result['deleted'] += 1 except Exception as e: on_failure(user, e) +======= + for user in actions['updated']: + try: + user_update(operation_logger, user['username'], + user['firstname'], user['lastname'], + user['mail'], user['password'], + mailbox_quota=user['mailbox_quota']) + result['updated'] += 1 + except Exception as e: + on_failure(user['username'], e) + + for user in actions['deleted']: + try: + user_delete(operation_logger, user, purge=True) + result['deleted'] += 1 + except Exception as e: + on_failure(user, e) +>>>>>>> Stashed changes if result['errors']: msg = m18n.n('user_import_partial_failed') From 3e047c4b943881c1e8a14311006773cc583893bb Mon Sep 17 00:00:00 2001 From: ljf Date: Sun, 13 Dec 2020 03:24:18 +0100 Subject: [PATCH 2441/3170] [fix] CSV import --- data/actionsmap/yunohost.yml | 2 +- locales/en.json | 5 + src/yunohost/log.py | 3 + src/yunohost/user.py | 265 ++++++++++++++++++++++------------- 4 files changed, 173 insertions(+), 102 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 075e429ec..a3ff431e7 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -212,7 +212,7 @@ user: action_help: Import several users from CSV api: POST /users/import arguments: - csv: + csvfile: help: "CSV file with columns username, email, quota, groups(separated by coma) and optionally password" type: open -u: diff --git a/locales/en.json b/locales/en.json index 938a38e20..367183a8a 100644 --- a/locales/en.json +++ b/locales/en.json @@ -400,6 +400,7 @@ "log_regen_conf": "Regenerate system configurations '{}'", "log_user_create": "Add '{}' user", "log_user_delete": "Delete '{}' user", + "log_user_import": "Import users", "log_user_group_create": "Create '{}' group", "log_user_group_delete": "Delete '{}' group", "log_user_group_update": "Update '{}' group", @@ -630,6 +631,10 @@ "user_unknown": "Unknown user: {user:s}", "user_update_failed": "Could not update user {user}: {error}", "user_updated": "User info changed", + "user_import_bad_line": "Incorrect line {line}: {details} ", + "user_import_partial_failed": "The users import operation partially failed", + "user_import_failed": "The users import operation completely failed", + "user_import_success": "Users have been imported", "yunohost_already_installed": "YunoHost is already installed", "yunohost_configured": "YunoHost is now configured", "yunohost_installing": "Installing YunoHost...", diff --git a/src/yunohost/log.py b/src/yunohost/log.py index f8da40002..9ea2c2024 100644 --- a/src/yunohost/log.py +++ b/src/yunohost/log.py @@ -371,6 +371,9 @@ def is_unit_operation( for field in exclude: if field in context: context.pop(field, None) + for field, value in context.items(): + if isinstance(value, file): + context[field] = value.name operation_logger = OperationLogger(op_key, related_to, args=context) try: diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 7745ec56a..0489a34fa 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -99,6 +99,7 @@ def user_create( password, mailbox_quota="0", mail=None, + imported=False ): from yunohost.domain import domain_list, _get_maindomain @@ -167,7 +168,8 @@ def user_create( if mail in aliases: raise YunohostValidationError("mail_unavailable") - operation_logger.start() + if not imported: + operation_logger.start() # Get random UID/GID all_uid = {str(x.pw_uid) for x in pwd.getpwall()} @@ -247,13 +249,14 @@ def user_create( hook_callback("post_user_create", args=[username, mail], env=env_dict) # TODO: Send a welcome mail to user - logger.success(m18n.n("user_created")) + if not imported: + logger.success(m18n.n('user_created')) return {"fullname": fullname, "username": username, "mail": mail} -@is_unit_operation([("username", "user")]) -def user_delete(operation_logger, username, purge=False): +@is_unit_operation([('username', 'user')]) +def user_delete(operation_logger, username, purge=False, imported=False): """ Delete user @@ -268,7 +271,8 @@ def user_delete(operation_logger, username, purge=False): if username not in user_list()["users"]: raise YunohostValidationError("user_unknown", user=username) - operation_logger.start() + if not imported: + operation_logger.start() user_group_update("all_users", remove=username, force=True, sync_perm=False) for group, infos in user_group_list()["groups"].items(): @@ -295,13 +299,13 @@ def user_delete(operation_logger, username, purge=False): subprocess.call(["nscd", "-i", "passwd"]) if purge: - subprocess.call(["rm", "-rf", "/home/{0}".format(username)]) - subprocess.call(["rm", "-rf", "/var/mail/{0}".format(username)]) + subprocess.call(['rm', '-rf', '/home/{0}'.format(username)]) + subprocess.call(['rm', '-rf', '/var/mail/{0}'.format(username)]) - hook_callback("post_user_delete", args=[username, purge]) - - logger.success(m18n.n("user_deleted")) + hook_callback('post_user_delete', args=[username, purge]) + if not imported: + logger.success(m18n.n('user_deleted')) @is_unit_operation([("username", "user")], exclude=["change_password"]) def user_update( @@ -316,6 +320,7 @@ def user_update( add_mailalias=None, remove_mailalias=None, mailbox_quota=None, + imported=False ): """ Update user informations @@ -394,34 +399,38 @@ def user_update( "admin@" + main_domain, "webmaster@" + main_domain, "postmaster@" + main_domain, + 'abuse@' + main_domain, ] - try: - ldap.validate_uniqueness({"mail": mail}) - except Exception as e: - raise YunohostValidationError("user_update_failed", user=username, error=e) - if mail[mail.find("@") + 1 :] not in domains: - raise YunohostValidationError( - "mail_domain_unknown", domain=mail[mail.find("@") + 1 :] - ) + if mail in user['mail']: + user['mail'].remove(mail) + else: + try: + ldap.validate_uniqueness({'mail': mail}) + except Exception as e: + raise YunohostError('user_update_failed', user=username, error=e) + if mail[mail.find('@') + 1:] not in domains: + raise YunohostError('mail_domain_unknown', domain=mail[mail.find('@') + 1:]) if mail in aliases: raise YunohostValidationError("mail_unavailable") - del user["mail"][0] - new_attr_dict["mail"] = [mail] + user["mail"] + new_attr_dict['mail'] = [mail] + user['mail'][1:] if add_mailalias: if not isinstance(add_mailalias, list): add_mailalias = [add_mailalias] for mail in add_mailalias: - try: - ldap.validate_uniqueness({"mail": mail}) - except Exception as e: - raise YunohostValidationError( - "user_update_failed", user=username, error=e - ) - if mail[mail.find("@") + 1 :] not in domains: - raise YunohostValidationError( - "mail_domain_unknown", domain=mail[mail.find("@") + 1 :] + if mail in user["mail"]: + user["mail"].remove(mail) + else: + try: + ldap.validate_uniqueness({"mail": mail}) + except Exception as e: + raise YunohostError( + "user_update_failed", user=username, error=e + ) + if mail[mail.find("@") + 1:] not in domains: + raise YunohostError( + "mail_domain_unknown", domain=mail[mail.find("@") + 1:] ) user["mail"].append(mail) new_attr_dict["mail"] = user["mail"] @@ -465,7 +474,8 @@ def user_update( new_attr_dict["mailuserquota"] = [mailbox_quota] env_dict["YNH_USER_MAILQUOTA"] = mailbox_quota - operation_logger.start() + if not imported: + operation_logger.start() try: ldap.update("uid=%s,ou=users" % username, new_attr_dict) @@ -475,9 +485,10 @@ def user_update( # Trigger post_user_update hooks hook_callback("post_user_update", env=env_dict) - logger.success(m18n.n("user_updated")) - app_ssowatconf() - return user_info(username) + if not imported: + app_ssowatconf() + logger.success(m18n.n('user_updated')) + return user_info(username) def user_info(username): @@ -507,11 +518,13 @@ def user_info(username): raise YunohostValidationError("user_unknown", user=username) result_dict = { - "username": user["uid"][0], - "fullname": user["cn"][0], - "firstname": user["givenName"][0], - "lastname": user["sn"][0], - "mail": user["mail"][0], + 'username': user['uid'][0], + 'fullname': user['cn'][0], + 'firstname': user['givenName'][0], + 'lastname': user['sn'][0], + 'mail': user['mail'][0], + 'mail-aliases': [], + 'mail-forward': [] } if len(user["mail"]) > 1: @@ -567,7 +580,7 @@ def user_info(username): @is_unit_operation() -def user_import(operation_logger, csv, update=False, delete=False): +def user_import(operation_logger, csvfile, update=False, delete=False): """ Import users from CSV @@ -576,24 +589,51 @@ def user_import(operation_logger, csv, update=False, delete=False): """ import csv # CSV are needed only in this function - - # Prepare what should be done + from moulinette.utils.text import random_ascii + from yunohost.permission import permission_sync_to_user + from yunohost.app import app_ssowatconf + # Pre-validate data and prepare what should be done actions = { 'created': [], 'updated': [], 'deleted': [] } is_well_formatted = True - + validators = { + 'username': r'^[a-z0-9_]+$', + 'firstname': r'^([^\W\d_]{2,30}[ ,.\'-]{0,3})+$', #FIXME Merge first and lastname and support more name (arabish, chinese...) + 'lastname': r'^([^\W\d_]{2,30}[ ,.\'-]{0,3})+$', + 'password': r'^|(.{3,})$', + 'mailbox_quota': r'^(\d+[bkMGT])|0$', + 'mail': r'^([\w.-]+@([^\W_A-Z]+([-]*[^\W_A-Z]+)*\.)+((xn--)?[^\W_]{2,}))$', + 'alias': r'^|([\w.-]+@([^\W_A-Z]+([-]*[^\W_A-Z]+)*\.)+((xn--)?[^\W_]{2,}),?)+$', + 'forward': r'^|([\w\+.-]+@([^\W_A-Z]+([-]*[^\W_A-Z]+)*\.)+((xn--)?[^\W_]{2,}),?)+$', + 'groups': r'^|([a-z0-9_]+(,?[a-z0-9_]+)*)$' + } + def to_list(str_list): + return str_list.split(',') if str_list else [] existing_users = user_list()['users'].keys() - reader = csv.DictReader(csv, delimiter=';', quotechar='"') + reader = csv.DictReader(csvfile, delimiter=';', quotechar='"') for user in reader: - if re.match(r'^[a-z0-9_]+$', user['username']:#TODO better check - logger.error(m18n.n('user_import_bad_line', line=reader.line_num)) + format_errors = [key + ':' + user[key] + for key, validator in validators.items() + if not re.match(validator, user[key])] + if format_errors: + logger.error(m18n.n('user_import_bad_line', + line=reader.line_num, + details=', '.join(format_errors))) is_well_formatted = False continue + user['groups'] = to_list(user['groups']) + user['alias'] = to_list(user['alias']) + user['forward'] = to_list(user['forward']) + user['domain'] = user['mail'].split('@')[1] if user['username'] not in existing_users: + # Generate password if not exists + # This could be used when reset password will be merged + if not user['password']: + user['password'] = random_ascii(70) actions['created'].append(user) else: if update: @@ -609,6 +649,10 @@ def user_import(operation_logger, csv, update=False, delete=False): total = len(actions['created'] + actions['updated'] + actions['deleted']) + if total == 0: + logger.info(m18n.n('nothing_to_do')) + return + # Apply creation, update and deletion operation result = { 'created': 0, @@ -617,62 +661,71 @@ def user_import(operation_logger, csv, update=False, delete=False): 'errors': 0 } - if total == 0: - logger.info(m18n.n('nothing_to_do')) - return - def on_failure(user, exception): result['errors'] += 1 logger.error(user + ': ' + str(exception)) + def update(user, created=False): + remove_alias = None + remove_forward = None + if not created: + info = user_info(user['username']) + user['mail'] = None if info['mail'] == user['mail'] else user['mail'] + remove_alias = list(set(info['mail-aliases']) - set(user['alias'])) + remove_forward = list(set(info['mail-forward']) - set(user['forward'])) + user['alias'] = list(set(user['alias']) - set(info['mail-aliases'])) + user['forward'] = list(set(user['forward']) - set(info['mail-forward'])) + for group, infos in user_group_list()["groups"].items(): + if group == "all_users": + continue + # If the user is in this group (and it's not the primary group), + # remove the member from the group + if user['username'] != group and user['username'] in infos["members"]: + user_group_update(group, remove=user['username'], sync_perm=False, imported=True) + + user_update(user['username'], + user['firstname'], user['lastname'], + user['mail'], user['password'], + mailbox_quota=user['mailbox_quota'], + mail=user['mail'], add_mailalias=user['alias'], + remove_mailalias=remove_alias, + remove_mailforward=remove_forward, + add_mailforward=user['forward'], imported=True) + + for group in user['groups']: + user_group_update(group, add=user['username'], sync_perm=False, imported=True) + operation_logger.start() - for user in actions['created']: - try: - user_create(operation_logger, user['username'], - user['firstname'], user['lastname'], - user['domain'], user['password'], - user['mailbox_quota'], user['mail']) - result['created'] += 1 - except Exception as e: - on_failure(user['username'], e) - -<<<<<<< Updated upstream - if update: - for user in actions['updated']: - try: - user_update(operation_logger, user['username'], - user['firstname'], user['lastname'], - user['mail'], user['password'], - mailbox_quota=user['mailbox_quota']) - result['updated'] += 1 - except Exception as e: - on_failure(user['username'], e) - - if delete: - for user in actions['deleted']: - try: - user_delete(operation_logger, user, purge=True) - result['deleted'] += 1 - except Exception as e: - on_failure(user, e) -======= - for user in actions['updated']: - try: - user_update(operation_logger, user['username'], - user['firstname'], user['lastname'], - user['mail'], user['password'], - mailbox_quota=user['mailbox_quota']) - result['updated'] += 1 - except Exception as e: - on_failure(user['username'], e) - + # We do delete and update before to avoid mail uniqueness issues for user in actions['deleted']: try: - user_delete(operation_logger, user, purge=True) + user_delete(user, purge=True, imported=True) result['deleted'] += 1 - except Exception as e: + except YunohostError as e: on_failure(user, e) ->>>>>>> Stashed changes + + for user in actions['updated']: + try: + update(user) + result['updated'] += 1 + except YunohostError as e: + on_failure(user['username'], e) + + for user in actions['created']: + try: + user_create(user['username'], + user['firstname'], user['lastname'], + user['domain'], user['password'], + user['mailbox_quota'], imported=True) + update(user, created=True) + result['created'] += 1 + except YunohostError as e: + on_failure(user['username'], e) + + + + permission_sync_to_user() + app_ssowatconf() if result['errors']: msg = m18n.n('user_import_partial_failed') @@ -685,6 +738,7 @@ def user_import(operation_logger, csv, update=False, delete=False): operation_logger.success() return result + # # Group subcategory # @@ -857,9 +911,15 @@ def user_group_delete(operation_logger, groupname, force=False, sync_perm=True): logger.debug(m18n.n("group_deleted", group=groupname)) -@is_unit_operation([("groupname", "group")]) +@is_unit_operation([('groupname', 'group')]) def user_group_update( - operation_logger, groupname, add=None, remove=None, force=False, sync_perm=True + operation_logger, + groupname, + add=None, + remove=None, + force=False, + sync_perm=True, + imported=False ): """ Update user informations @@ -929,7 +989,8 @@ def user_group_update( ] if set(new_group) != set(current_group): - operation_logger.start() + if not imported: + operation_logger.start() ldap = _get_ldap_interface() try: ldap.update( @@ -939,14 +1000,16 @@ def user_group_update( except Exception as e: raise YunohostError("group_update_failed", group=groupname, error=e) - if groupname != "all_users": - logger.success(m18n.n("group_updated", group=groupname)) - else: - logger.debug(m18n.n("group_updated", group=groupname)) - if sync_perm: permission_sync_to_user() - return user_group_info(groupname) + + if not imported: + if groupname != "all_users": + logger.success(m18n.n("group_updated", group=groupname)) + else: + logger.debug(m18n.n("group_updated", group=groupname)) + + return user_group_info(groupname) def user_group_info(groupname): From 9e2f4a56f33ded262d6beb2dcf260e194807f905 Mon Sep 17 00:00:00 2001 From: ljf Date: Sun, 20 Dec 2020 23:13:22 +0100 Subject: [PATCH 2442/3170] [enh] Add export feature and refactor user_list --- data/actionsmap/yunohost.yml | 7 +- locales/en.json | 5 +- src/yunohost/user.py | 164 +++++++++++++++++++++++++---------- 3 files changed, 129 insertions(+), 47 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index a3ff431e7..a5fdf5872 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -67,7 +67,7 @@ user: api: GET /users arguments: --fields: - help: fields to fetch + help: fields to fetch (username, fullname, mail, mail-alias, mail-forward, mailbox-quota, groups, shell, home-path) nargs: "+" ### user_create() @@ -207,6 +207,11 @@ user: username: help: Username or email to get information + ### user_export() + export: + action_help: Export users into CSV + api: GET /users/export + ### user_import() import: action_help: Import several users from CSV diff --git a/locales/en.json b/locales/en.json index 367183a8a..6a092d108 100644 --- a/locales/en.json +++ b/locales/en.json @@ -631,9 +631,12 @@ "user_unknown": "Unknown user: {user:s}", "user_update_failed": "Could not update user {user}: {error}", "user_updated": "User info changed", - "user_import_bad_line": "Incorrect line {line}: {details} ", + "user_import_bad_line": "Incorrect line {line}: {details}", + "user_import_bad_file": "Your CSV file is not correctly formatted it will be ignored to avoid potential data loss", + "user_import_missing_column": "The column {column} is missing", "user_import_partial_failed": "The users import operation partially failed", "user_import_failed": "The users import operation completely failed", + "user_import_nothing_to_do": "No user needs to be imported", "user_import_success": "Users have been imported", "yunohost_already_installed": "YunoHost is already installed", "yunohost_configured": "YunoHost is now configured", diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 0489a34fa..0fae9cf43 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -48,27 +48,48 @@ def user_list(fields=None): from yunohost.utils.ldap import _get_ldap_interface - user_attrs = { - "uid": "username", - "cn": "fullname", - "mail": "mail", - "maildrop": "mail-forward", - "homeDirectory": "home_path", - "mailuserquota": "mailbox-quota", + ldap_attrs = { + 'username': 'uid', + 'password': 'uid', + 'fullname': 'cn', + 'firstname': 'givenName', + 'lastname': 'sn', + 'mail': 'mail', + 'mail-alias': 'mail', + 'mail-forward': 'maildrop', + 'mailbox-quota': 'mailuserquota', + 'groups': 'memberOf', + 'shell': 'loginShell', + 'home-path': 'homeDirectory' } - attrs = ["uid"] + def display_default(values, _): + return values[0] if len(values) == 1 else values + + display = { + 'password': lambda values, user: '', + 'mail': lambda values, user: display_default(values[:1], user), + 'mail-alias': lambda values, _: values[1:], + 'mail-forward': lambda values, user: [forward for forward in values if forward != user['uid'][0]], + 'groups': lambda values, user: [ + group[3:].split(',')[0] + for group in values + if not group.startswith('cn=all_users,') and + not group.startswith('cn=' + user['uid'][0] + ',')], + 'shell': lambda values, _: len(values) > 0 and values[0].strip() == "/bin/false" + } + + attrs = set(['uid']) users = {} - if fields: - keys = user_attrs.keys() - for attr in fields: - if attr in keys: - attrs.append(attr) - else: - raise YunohostError("field_invalid", attr) - else: - attrs = ["uid", "cn", "mail", "mailuserquota"] + if not fields: + fields = ['username', 'fullname', 'mail', 'mailbox-quota', 'shell'] + + for field in fields: + if field in ldap_attrs: + attrs|=set([ldap_attrs[field]]) + else: + raise YunohostError('field_invalid', field) ldap = _get_ldap_interface() result = ldap.search( @@ -79,12 +100,13 @@ def user_list(fields=None): for user in result: entry = {} - for attr, values in user.items(): - if values: - entry[user_attrs[attr]] = values[0] + for field in fields: + values = [] + if ldap_attrs[field] in user: + values = user[ldap_attrs[field]] + entry[field] = display.get(field, display_default)(values, user) - uid = entry[user_attrs["uid"]] - users[uid] = entry + users[entry['username']] = entry return {"users": users} @@ -579,13 +601,49 @@ def user_info(username): return result_dict +def user_export(): + """ + Export users into CSV + + Keyword argument: + csv -- CSV file with columns username;firstname;lastname;password;mailbox_quota;mail;alias;forward;groups + + """ + import csv # CSV are needed only in this function + from io import BytesIO + fieldnames = [u'username', u'firstname', u'lastname', u'password', u'mailbox-quota', u'mail', u'mail-alias', u'mail-forward', u'groups'] + with BytesIO() as csv_io: + writer = csv.DictWriter(csv_io, fieldnames, delimiter=';', quotechar='"') + writer.writeheader() + users = user_list(fieldnames)['users'] + for username, user in users.items(): + user['mail-alias'] = ','.join(user['mail-alias']) + user['mail-forward'] = ','.join(user['mail-forward']) + user['groups'] = ','.join(user['groups']) + writer.writerow(user) + + body = csv_io.getvalue() + if msettings.get('interface') == 'api': + # We return a raw bottle HTTPresponse (instead of serializable data like + # list/dict, ...), which is gonna be picked and used directly by moulinette + from bottle import LocalResponse + response = LocalResponse(body=body, + headers={ + "Content-Disposition": "attachment; filename='users.csv'", + "Content-Type": "text/csv", + } + ) + return response + else: + return body + @is_unit_operation() def user_import(operation_logger, csvfile, update=False, delete=False): """ Import users from CSV Keyword argument: - csv -- CSV file with columns username, email, quota, groups and optionnally password + csv -- CSV file with columns username;firstname;lastname;password;mailbox_quota;mail;alias;forward;groups """ import csv # CSV are needed only in this function @@ -604,20 +662,35 @@ def user_import(operation_logger, csvfile, update=False, delete=False): 'firstname': r'^([^\W\d_]{2,30}[ ,.\'-]{0,3})+$', #FIXME Merge first and lastname and support more name (arabish, chinese...) 'lastname': r'^([^\W\d_]{2,30}[ ,.\'-]{0,3})+$', 'password': r'^|(.{3,})$', - 'mailbox_quota': r'^(\d+[bkMGT])|0$', 'mail': r'^([\w.-]+@([^\W_A-Z]+([-]*[^\W_A-Z]+)*\.)+((xn--)?[^\W_]{2,}))$', - 'alias': r'^|([\w.-]+@([^\W_A-Z]+([-]*[^\W_A-Z]+)*\.)+((xn--)?[^\W_]{2,}),?)+$', - 'forward': r'^|([\w\+.-]+@([^\W_A-Z]+([-]*[^\W_A-Z]+)*\.)+((xn--)?[^\W_]{2,}),?)+$', + 'mail-alias': r'^|([\w.-]+@([^\W_A-Z]+([-]*[^\W_A-Z]+)*\.)+((xn--)?[^\W_]{2,}),?)+$', + 'mail-forward': r'^|([\w\+.-]+@([^\W_A-Z]+([-]*[^\W_A-Z]+)*\.)+((xn--)?[^\W_]{2,}),?)+$', + 'mailbox-quota': r'^(\d+[bkMGT])|0$', 'groups': r'^|([a-z0-9_]+(,?[a-z0-9_]+)*)$' } + def to_list(str_list): return str_list.split(',') if str_list else [] - existing_users = user_list()['users'].keys() + + existing_users = user_list()['users'] + past_lines = [] reader = csv.DictReader(csvfile, delimiter=';', quotechar='"') for user in reader: - format_errors = [key + ':' + user[key] - for key, validator in validators.items() - if not re.match(validator, user[key])] + # Validation + try: + format_errors = [key + ':' + str(user[key]) + for key, validator in validators.items() + if user[key] is None or not re.match(validator, user[key])] + except KeyError, e: + logger.error(m18n.n('user_import_missing_column', + column=str(e))) + is_well_formatted = False + break + + if 'username' in user: + if user['username'] in past_lines: + format_errors.append('username: %s (duplicated)' % user['username']) + past_lines.append(user['username']) if format_errors: logger.error(m18n.n('user_import_bad_line', line=reader.line_num, @@ -625,9 +698,10 @@ def user_import(operation_logger, csvfile, update=False, delete=False): is_well_formatted = False continue + # Choose what to do with this line and prepare data user['groups'] = to_list(user['groups']) - user['alias'] = to_list(user['alias']) - user['forward'] = to_list(user['forward']) + user['mail-alias'] = to_list(user['mail-alias']) + user['mail-forward'] = to_list(user['mail-forward']) user['domain'] = user['mail'].split('@')[1] if user['username'] not in existing_users: # Generate password if not exists @@ -638,7 +712,7 @@ def user_import(operation_logger, csvfile, update=False, delete=False): else: if update: actions['updated'].append(user) - existing_users.remove(user['username']) + del existing_users[user['username']] if delete: for user in existing_users: @@ -650,7 +724,7 @@ def user_import(operation_logger, csvfile, update=False, delete=False): total = len(actions['created'] + actions['updated'] + actions['deleted']) if total == 0: - logger.info(m18n.n('nothing_to_do')) + logger.info(m18n.n('user_import_nothing_to_do')) return # Apply creation, update and deletion operation @@ -665,14 +739,13 @@ def user_import(operation_logger, csvfile, update=False, delete=False): result['errors'] += 1 logger.error(user + ': ' + str(exception)) - def update(user, created=False): + def update(user, info=False): remove_alias = None remove_forward = None - if not created: - info = user_info(user['username']) + if info: user['mail'] = None if info['mail'] == user['mail'] else user['mail'] - remove_alias = list(set(info['mail-aliases']) - set(user['alias'])) - remove_forward = list(set(info['mail-forward']) - set(user['forward'])) + remove_alias = list(set(info['mail-aliases']) - set(user['mail-alias'])) + remove_forward = list(set(info['mail-forward']) - set(user['mail-forward'])) user['alias'] = list(set(user['alias']) - set(info['mail-aliases'])) user['forward'] = list(set(user['forward']) - set(info['mail-forward'])) for group, infos in user_group_list()["groups"].items(): @@ -686,15 +759,16 @@ def user_import(operation_logger, csvfile, update=False, delete=False): user_update(user['username'], user['firstname'], user['lastname'], user['mail'], user['password'], - mailbox_quota=user['mailbox_quota'], - mail=user['mail'], add_mailalias=user['alias'], + mailbox_quota=user['mailbox-quota'], + mail=user['mail'], add_mailalias=user['mail-alias'], remove_mailalias=remove_alias, remove_mailforward=remove_forward, - add_mailforward=user['forward'], imported=True) + add_mailforward=user['mail-forward'], imported=True) for group in user['groups']: user_group_update(group, add=user['username'], sync_perm=False, imported=True) + users = user_list()['users'] operation_logger.start() # We do delete and update before to avoid mail uniqueness issues for user in actions['deleted']: @@ -706,7 +780,7 @@ def user_import(operation_logger, csvfile, update=False, delete=False): for user in actions['updated']: try: - update(user) + update(user, users[user['username']]) result['updated'] += 1 except YunohostError as e: on_failure(user['username'], e) @@ -716,8 +790,8 @@ def user_import(operation_logger, csvfile, update=False, delete=False): user_create(user['username'], user['firstname'], user['lastname'], user['domain'], user['password'], - user['mailbox_quota'], imported=True) - update(user, created=True) + user['mailbox-quota'], imported=True) + update(user) result['created'] += 1 except YunohostError as e: on_failure(user['username'], e) From fd06430e8f1a93f5b3236bbe0b48d9ca6e574b07 Mon Sep 17 00:00:00 2001 From: ljf Date: Mon, 21 Dec 2020 02:29:17 +0100 Subject: [PATCH 2443/3170] [fix] Import user with update mode some unit test --- src/yunohost/tests/test_user-group.py | 64 +++++++++++++++++++++++++ src/yunohost/user.py | 69 ++++++++++++--------------- 2 files changed, 94 insertions(+), 39 deletions(-) diff --git a/src/yunohost/tests/test_user-group.py b/src/yunohost/tests/test_user-group.py index 251029796..e83425df9 100644 --- a/src/yunohost/tests/test_user-group.py +++ b/src/yunohost/tests/test_user-group.py @@ -1,5 +1,6 @@ import pytest +<<<<<<< HEAD from .conftest import message, raiseYunohostError from yunohost.user import ( @@ -8,6 +9,10 @@ from yunohost.user import ( user_create, user_delete, user_update, + user_import, + user_export, + CSV_FIELDNAMES, + FIRST_ALIASES, user_group_list, user_group_create, user_group_delete, @@ -110,6 +115,65 @@ def test_del_user(mocker): assert "alice" not in group_res["all_users"]["members"] +def test_import_user(mocker): + import csv + from io import BytesIO + fieldnames = [u'username', u'firstname', u'lastname', u'password', + u'mailbox-quota', u'mail', u'mail-alias', u'mail-forward', + u'groups'] + with BytesIO() as csv_io: + writer = csv.DictWriter(csv_io, fieldnames, delimiter=';', + quotechar='"') + writer.writeheader() + writer.writerow({ + 'username': "albert", + 'firstname': "Albert", + 'lastname': "Good", + 'password': "", + 'mailbox-quota': "1G", + 'mail': "albert@" + maindomain, + 'mail-alias': "albert2@" + maindomain, + 'mail-forward': "albert@example.com", + 'groups': "dev", + }) + writer.writerow({ + 'username': "alice", + 'firstname': "Alice", + 'lastname': "White", + 'password': "", + 'mailbox-quota': "1G", + 'mail': "alice@" + maindomain, + 'mail-alias': "alice1@" + maindomain + ",alice2@" + maindomain, + 'mail-forward': "", + 'groups': "apps", + }) + csv_io.seek(0) + with message(mocker, "user_import_success"): + user_import(csv_io, update=True, delete=True) + + group_res = user_group_list()['groups'] + user_res = user_list(CSV_FIELDNAMES)['users'] + assert "albert" in user_res + assert "alice" in user_res + assert "bob" not in user_res + assert len(user_res['alice']['mail-alias']) == 2 + assert "albert" in group_res['dev']['members'] + assert "alice" in group_res['apps']['members'] + + +def test_export_user(mocker): + result = user_export() + should_be = "username;firstname;lastname;password;" + should_be += "mailbox-quota;mail;mail-alias;mail-forward;groups" + should_be += "\r\nbob;Bob;Snow;;0;bob@" + maindomain + ";;;apps" + should_be += "\r\nalice;Alice;White;;0;alice@" + maindomain + ";" + should_be += ','.join([alias + maindomain for alias in FIRST_ALIASES]) + should_be += ";;dev" + should_be += "\r\njack;Jack;Black;;0;jack@" + maindomain + ";;;" + + assert result == should_be + + def test_create_group(mocker): with message(mocker, "group_created", group="adminsys"): diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 0fae9cf43..0bcce9cbc 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -43,6 +43,19 @@ from yunohost.log import is_unit_operation logger = getActionLogger("yunohost.user") +CSV_FIELDNAMES = [u'username', u'firstname', u'lastname', u'password', u'mailbox-quota', u'mail', u'mail-alias', u'mail-forward', u'groups'] +VALIDATORS = { + 'username': r'^[a-z0-9_]+$', + 'firstname': r'^([^\W\d_]{2,30}[ ,.\'-]{0,3})+$', #FIXME Merge first and lastname and support more name (arabish, chinese...) + 'lastname': r'^([^\W\d_]{2,30}[ ,.\'-]{0,3})+$', + 'password': r'^|(.{3,})$', + 'mail': r'^([\w.-]+@([^\W_A-Z]+([-]*[^\W_A-Z]+)*\.)+((xn--)?[^\W_]{2,}))$', + 'mail-alias': r'^|([\w.-]+@([^\W_A-Z]+([-]*[^\W_A-Z]+)*\.)+((xn--)?[^\W_]{2,}),?)+$', + 'mail-forward': r'^|([\w\+.-]+@([^\W_A-Z]+([-]*[^\W_A-Z]+)*\.)+((xn--)?[^\W_]{2,}),?)+$', + 'mailbox-quota': r'^(\d+[bkMGT])|0$', + 'groups': r'^|([a-z0-9_]+(,?[a-z0-9_]+)*)$' +} +FIRST_ALIASES = ['root@', 'admin@', 'webmaster@', 'postmaster@', 'abuse@'] def user_list(fields=None): @@ -87,7 +100,7 @@ def user_list(fields=None): for field in fields: if field in ldap_attrs: - attrs|=set([ldap_attrs[field]]) + attrs |= set([ldap_attrs[field]]) else: raise YunohostError('field_invalid', field) @@ -179,13 +192,7 @@ def user_create( raise YunohostValidationError("system_username_exists") main_domain = _get_maindomain() - aliases = [ - "root@" + main_domain, - "admin@" + main_domain, - "webmaster@" + main_domain, - "postmaster@" + main_domain, - "abuse@" + main_domain, - ] + aliases = [alias + main_domain for alias in FIRST_ALIASES] if mail in aliases: raise YunohostValidationError("mail_unavailable") @@ -416,13 +423,8 @@ def user_update( if mail: main_domain = _get_maindomain() - aliases = [ - "root@" + main_domain, - "admin@" + main_domain, - "webmaster@" + main_domain, - "postmaster@" + main_domain, - 'abuse@' + main_domain, - ] + aliases = [alias + main_domain for alias in FIRST_ALIASES] + if mail in user['mail']: user['mail'].remove(mail) else: @@ -606,23 +608,23 @@ def user_export(): Export users into CSV Keyword argument: - csv -- CSV file with columns username;firstname;lastname;password;mailbox_quota;mail;alias;forward;groups + csv -- CSV file with columns username;firstname;lastname;password;mailbox-quota;mail;mail-alias;mail-forward;groups """ - import csv # CSV are needed only in this function + import csv # CSV are needed only in this function from io import BytesIO - fieldnames = [u'username', u'firstname', u'lastname', u'password', u'mailbox-quota', u'mail', u'mail-alias', u'mail-forward', u'groups'] with BytesIO() as csv_io: - writer = csv.DictWriter(csv_io, fieldnames, delimiter=';', quotechar='"') + writer = csv.DictWriter(csv_io, CSV_FIELDNAMES, + delimiter=';', quotechar='"') writer.writeheader() - users = user_list(fieldnames)['users'] + users = user_list(CSV_FIELDNAMES)['users'] for username, user in users.items(): user['mail-alias'] = ','.join(user['mail-alias']) user['mail-forward'] = ','.join(user['mail-forward']) user['groups'] = ','.join(user['groups']) writer.writerow(user) - body = csv_io.getvalue() + body = csv_io.getvalue().rstrip() if msettings.get('interface') == 'api': # We return a raw bottle HTTPresponse (instead of serializable data like # list/dict, ...), which is gonna be picked and used directly by moulinette @@ -631,12 +633,12 @@ def user_export(): headers={ "Content-Disposition": "attachment; filename='users.csv'", "Content-Type": "text/csv", - } - ) + }) return response else: return body + @is_unit_operation() def user_import(operation_logger, csvfile, update=False, delete=False): """ @@ -657,17 +659,6 @@ def user_import(operation_logger, csvfile, update=False, delete=False): 'deleted': [] } is_well_formatted = True - validators = { - 'username': r'^[a-z0-9_]+$', - 'firstname': r'^([^\W\d_]{2,30}[ ,.\'-]{0,3})+$', #FIXME Merge first and lastname and support more name (arabish, chinese...) - 'lastname': r'^([^\W\d_]{2,30}[ ,.\'-]{0,3})+$', - 'password': r'^|(.{3,})$', - 'mail': r'^([\w.-]+@([^\W_A-Z]+([-]*[^\W_A-Z]+)*\.)+((xn--)?[^\W_]{2,}))$', - 'mail-alias': r'^|([\w.-]+@([^\W_A-Z]+([-]*[^\W_A-Z]+)*\.)+((xn--)?[^\W_]{2,}),?)+$', - 'mail-forward': r'^|([\w\+.-]+@([^\W_A-Z]+([-]*[^\W_A-Z]+)*\.)+((xn--)?[^\W_]{2,}),?)+$', - 'mailbox-quota': r'^(\d+[bkMGT])|0$', - 'groups': r'^|([a-z0-9_]+(,?[a-z0-9_]+)*)$' - } def to_list(str_list): return str_list.split(',') if str_list else [] @@ -679,7 +670,7 @@ def user_import(operation_logger, csvfile, update=False, delete=False): # Validation try: format_errors = [key + ':' + str(user[key]) - for key, validator in validators.items() + for key, validator in VALIDATORS.items() if user[key] is None or not re.match(validator, user[key])] except KeyError, e: logger.error(m18n.n('user_import_missing_column', @@ -744,10 +735,10 @@ def user_import(operation_logger, csvfile, update=False, delete=False): remove_forward = None if info: user['mail'] = None if info['mail'] == user['mail'] else user['mail'] - remove_alias = list(set(info['mail-aliases']) - set(user['mail-alias'])) + remove_alias = list(set(info['mail-alias']) - set(user['mail-alias'])) remove_forward = list(set(info['mail-forward']) - set(user['mail-forward'])) - user['alias'] = list(set(user['alias']) - set(info['mail-aliases'])) - user['forward'] = list(set(user['forward']) - set(info['mail-forward'])) + user['mail-alias'] = list(set(user['mail-alias']) - set(info['mail-alias'])) + user['mail-forward'] = list(set(user['mail-forward']) - set(info['mail-forward'])) for group, infos in user_group_list()["groups"].items(): if group == "all_users": continue @@ -768,7 +759,7 @@ def user_import(operation_logger, csvfile, update=False, delete=False): for group in user['groups']: user_group_update(group, add=user['username'], sync_perm=False, imported=True) - users = user_list()['users'] + users = user_list(CSV_FIELDNAMES)['users'] operation_logger.start() # We do delete and update before to avoid mail uniqueness issues for user in actions['deleted']: From 57dcf45a7c838d2fecb633a1f1f2f04929b305ce Mon Sep 17 00:00:00 2001 From: ljf Date: Mon, 21 Dec 2020 03:40:20 +0100 Subject: [PATCH 2444/3170] [fix] User import unit test --- src/yunohost/log.py | 3 +++ src/yunohost/tests/test_user-group.py | 3 +-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/yunohost/log.py b/src/yunohost/log.py index 9ea2c2024..a8a3281d2 100644 --- a/src/yunohost/log.py +++ b/src/yunohost/log.py @@ -32,6 +32,7 @@ import psutil from datetime import datetime, timedelta from logging import FileHandler, getLogger, Formatter +from io import IOBase from moulinette import m18n, msettings from moulinette.core import MoulinetteError @@ -374,6 +375,8 @@ def is_unit_operation( for field, value in context.items(): if isinstance(value, file): context[field] = value.name + elif isinstance(value, IOBase): + context[field] = 'IOBase' operation_logger = OperationLogger(op_key, related_to, args=context) try: diff --git a/src/yunohost/tests/test_user-group.py b/src/yunohost/tests/test_user-group.py index e83425df9..3252f0ef8 100644 --- a/src/yunohost/tests/test_user-group.py +++ b/src/yunohost/tests/test_user-group.py @@ -1,6 +1,5 @@ import pytest -<<<<<<< HEAD from .conftest import message, raiseYunohostError from yunohost.user import ( @@ -27,7 +26,7 @@ maindomain = "" def clean_user_groups(): for u in user_list()["users"]: - user_delete(u) + user_delete(u, purge=True) for g in user_group_list()["groups"]: if g not in ["all_users", "visitors"]: From c7c29285795c6823eaaa074e2f163af9136bb0a3 Mon Sep 17 00:00:00 2001 From: ljf Date: Mon, 21 Dec 2020 04:27:03 +0100 Subject: [PATCH 2445/3170] [fix] Column list --- data/actionsmap/yunohost.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index a5fdf5872..4ef9ad008 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -218,7 +218,7 @@ user: api: POST /users/import arguments: csvfile: - help: "CSV file with columns username, email, quota, groups(separated by coma) and optionally password" + help: "CSV file with columns username, firstname, lastname, password, mail, mailbox-quota, mail-alias, mail-forward, groups (separated by coma)" type: open -u: full: --update From 59d7e2f247687143d3cc6fca462e5557e9e37621 Mon Sep 17 00:00:00 2001 From: ljf Date: Mon, 21 Dec 2020 04:27:23 +0100 Subject: [PATCH 2446/3170] [fix] LDAP Size limits --- data/templates/slapd/slapd.ldif | 1 + 1 file changed, 1 insertion(+) diff --git a/data/templates/slapd/slapd.ldif b/data/templates/slapd/slapd.ldif index d3ed2e053..8692d2664 100644 --- a/data/templates/slapd/slapd.ldif +++ b/data/templates/slapd/slapd.ldif @@ -33,6 +33,7 @@ olcAuthzPolicy: none olcConcurrency: 0 olcConnMaxPending: 100 olcConnMaxPendingAuth: 1000 +olcSizeLimit: 10000 olcIdleTimeout: 0 olcIndexSubstrIfMaxLen: 4 olcIndexSubstrIfMinLen: 2 From 8e2f1c696b191b06d40a1646699d81f758aa1d6a Mon Sep 17 00:00:00 2001 From: ljf Date: Mon, 21 Dec 2020 04:27:50 +0100 Subject: [PATCH 2447/3170] [fix] Home not created --- locales/en.json | 2 +- src/yunohost/user.py | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 6a092d108..faa9e4556 100644 --- a/locales/en.json +++ b/locales/en.json @@ -627,7 +627,7 @@ "user_creation_failed": "Could not create user {user}: {error}", "user_deleted": "User deleted", "user_deletion_failed": "Could not delete user {user}: {error}", - "user_home_creation_failed": "Could not create 'home' folder for user", + "user_home_creation_failed": "Could not create '{home}' folder for user", "user_unknown": "Unknown user: {user:s}", "user_update_failed": "Could not update user {user}: {error}", "user_updated": "User info changed", diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 0bcce9cbc..0680af89d 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -254,6 +254,10 @@ def user_create( except subprocess.CalledProcessError: if not os.path.isdir("/home/{0}".format(username)): logger.warning(m18n.n("user_home_creation_failed"), exc_info=1) + home = '/home/{0}'.format(username) + if not os.path.isdir(home): + logger.warning(m18n.n('user_home_creation_failed', home=home), + exc_info=1) try: subprocess.check_call( @@ -726,6 +730,20 @@ def user_import(operation_logger, csvfile, update=False, delete=False): 'errors': 0 } + def progress(info=""): + progress.nb += 1 + width = 20 + bar = int(progress.nb * width / total) + bar = "[" + "#" * bar + "." * (width - bar) + "]" + if info: + bar += " > " + info + if progress.old == bar: + return + progress.old = bar + logger.info(bar) + progress.nb = 0 + progress.old = "" + def on_failure(user, exception): result['errors'] += 1 logger.error(user + ': ' + str(exception)) @@ -768,6 +786,7 @@ def user_import(operation_logger, csvfile, update=False, delete=False): result['deleted'] += 1 except YunohostError as e: on_failure(user, e) + progress("Deletion") for user in actions['updated']: try: @@ -775,6 +794,7 @@ def user_import(operation_logger, csvfile, update=False, delete=False): result['updated'] += 1 except YunohostError as e: on_failure(user['username'], e) + progress("Update") for user in actions['created']: try: @@ -786,6 +806,7 @@ def user_import(operation_logger, csvfile, update=False, delete=False): result['created'] += 1 except YunohostError as e: on_failure(user['username'], e) + progress("Creation") From a07314e66169b81efe270ee8018ee4d6f0629931 Mon Sep 17 00:00:00 2001 From: ljf Date: Sun, 3 Jan 2021 19:44:46 +0100 Subject: [PATCH 2448/3170] [fix] Download CSV from webadmin - missing commit --- src/yunohost/user.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 0680af89d..88279997b 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -632,10 +632,10 @@ def user_export(): if msettings.get('interface') == 'api': # We return a raw bottle HTTPresponse (instead of serializable data like # list/dict, ...), which is gonna be picked and used directly by moulinette - from bottle import LocalResponse - response = LocalResponse(body=body, + from bottle import HTTPResponse + response = HTTPResponse(body=body, headers={ - "Content-Disposition": "attachment; filename='users.csv'", + "Content-Disposition": "attachment; filename=users.csv", "Content-Type": "text/csv", }) return response @@ -652,6 +652,7 @@ def user_import(operation_logger, csvfile, update=False, delete=False): csv -- CSV file with columns username;firstname;lastname;password;mailbox_quota;mail;alias;forward;groups """ + import csv # CSV are needed only in this function from moulinette.utils.text import random_ascii from yunohost.permission import permission_sync_to_user From a78e4c8eacca2d221b3b91693928437e0577ec05 Mon Sep 17 00:00:00 2001 From: ljf Date: Sun, 3 Jan 2021 19:51:43 +0100 Subject: [PATCH 2449/3170] [fix] 1 letter firstname or lastname --- src/yunohost/user.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 88279997b..fe114da4b 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -46,8 +46,8 @@ logger = getActionLogger("yunohost.user") CSV_FIELDNAMES = [u'username', u'firstname', u'lastname', u'password', u'mailbox-quota', u'mail', u'mail-alias', u'mail-forward', u'groups'] VALIDATORS = { 'username': r'^[a-z0-9_]+$', - 'firstname': r'^([^\W\d_]{2,30}[ ,.\'-]{0,3})+$', #FIXME Merge first and lastname and support more name (arabish, chinese...) - 'lastname': r'^([^\W\d_]{2,30}[ ,.\'-]{0,3})+$', + 'firstname': r'^([^\W\d_]{1,30}[ ,.\'-]{0,3})+$', #FIXME Merge first and lastname and support more name (arabish, chinese...) + 'lastname': r'^([^\W\d_]{1,30}[ ,.\'-]{0,3})+$', 'password': r'^|(.{3,})$', 'mail': r'^([\w.-]+@([^\W_A-Z]+([-]*[^\W_A-Z]+)*\.)+((xn--)?[^\W_]{2,}))$', 'mail-alias': r'^|([\w.-]+@([^\W_A-Z]+([-]*[^\W_A-Z]+)*\.)+((xn--)?[^\W_]{2,}),?)+$', From 1d33f333cdff0ea7700b488a695ba8a3cd8b8b30 Mon Sep 17 00:00:00 2001 From: ljf Date: Sat, 8 May 2021 23:39:33 +0200 Subject: [PATCH 2450/3170] [fix] Python3 migration for export user feature --- src/yunohost/user.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index fe114da4b..5487ef18b 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -616,8 +616,8 @@ def user_export(): """ import csv # CSV are needed only in this function - from io import BytesIO - with BytesIO() as csv_io: + from io import StringIO + with StringIO() as csv_io: writer = csv.DictWriter(csv_io, CSV_FIELDNAMES, delimiter=';', quotechar='"') writer.writeheader() @@ -677,7 +677,7 @@ def user_import(operation_logger, csvfile, update=False, delete=False): format_errors = [key + ':' + str(user[key]) for key, validator in VALIDATORS.items() if user[key] is None or not re.match(validator, user[key])] - except KeyError, e: + except KeyError as e: logger.error(m18n.n('user_import_missing_column', column=str(e))) is_well_formatted = False From 91e7e5e1c80652a8977bba99d7a01075a1c86e2e Mon Sep 17 00:00:00 2001 From: ljf Date: Sat, 8 May 2021 23:58:36 +0200 Subject: [PATCH 2451/3170] [fix] Python3 migration: File args with log --- src/yunohost/log.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/yunohost/log.py b/src/yunohost/log.py index a8a3281d2..9e6c8f165 100644 --- a/src/yunohost/log.py +++ b/src/yunohost/log.py @@ -373,10 +373,11 @@ def is_unit_operation( if field in context: context.pop(field, None) for field, value in context.items(): - if isinstance(value, file): - context[field] = value.name - elif isinstance(value, IOBase): - context[field] = 'IOBase' + if isinstance(value, IOBase): + try: + context[field] = value.name + except: + context[field] = 'IOBase' operation_logger = OperationLogger(op_key, related_to, args=context) try: From 2ea4c2bae94f31687005475567c7a7ea516983c3 Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Sun, 9 May 2021 01:00:59 +0200 Subject: [PATCH 2452/3170] [fix] Inconsistency in translation --- locales/de.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/de.json b/locales/de.json index 53297ed6d..73efca434 100644 --- a/locales/de.json +++ b/locales/de.json @@ -590,5 +590,5 @@ "diagnosis_sshd_config_insecure": "Die SSH-Konfiguration wurde scheinbar manuell abgeändert, und ist unsicher, weil sie keine 'AllowGroups'- oder 'AllowUsers' -Direktiven für die Begrenzung des Zugriffs durch autorisierte Benutzer enthält.", "backup_create_size_estimation": "Das Archiv wird etwa {size} Daten enthalten", "app_restore_script_failed": "Im Wiederherstellungsskript der Anwendung ist ein Fehler aufgetreten", - "app_restore_failed": "Konnte {apps:s} nicht wiederherstellen: {error:s}" + "app_restore_failed": "Konnte {app:s} nicht wiederherstellen: {error:s}" } From 6e880c8219846ce94a1a870c535334f16c5b1bcd Mon Sep 17 00:00:00 2001 From: ljf Date: Sun, 9 May 2021 01:02:51 +0200 Subject: [PATCH 2453/3170] [fix] Avoid password too small error during import operation --- src/yunohost/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 5487ef18b..ee26533e8 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -413,7 +413,7 @@ def user_update( ] # change_password is None if user_update is not called to change the password - if change_password is not None: + if change_password is not None and change_password != '': # when in the cli interface if the option to change the password is called # without a specified value, change_password will be set to the const 0. # In this case we prompt for the new password. From ad73c29dad7d61db122436e75d394ee12bf70119 Mon Sep 17 00:00:00 2001 From: ljf Date: Sun, 9 May 2021 01:08:43 +0200 Subject: [PATCH 2454/3170] [fix] Error in import user tests --- src/yunohost/tests/test_user-group.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/tests/test_user-group.py b/src/yunohost/tests/test_user-group.py index 3252f0ef8..ee5d07c40 100644 --- a/src/yunohost/tests/test_user-group.py +++ b/src/yunohost/tests/test_user-group.py @@ -116,7 +116,7 @@ def test_del_user(mocker): def test_import_user(mocker): import csv - from io import BytesIO + from io import StringIO fieldnames = [u'username', u'firstname', u'lastname', u'password', u'mailbox-quota', u'mail', u'mail-alias', u'mail-forward', u'groups'] From 4aaf0154285bea6eb2a23c4ef56d5c1b4ccf3fea Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 9 May 2021 18:38:17 +0200 Subject: [PATCH 2455/3170] Also catch tarfile.ReadError as possible archive corruption error --- src/yunohost/backup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index cf8394f5b..3978e835d 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -2017,7 +2017,7 @@ class TarBackupMethod(BackupMethod): try: files_in_archive = tar.getnames() - except IOError as e: + except (IOError, EOFError, tarfile.ReadError) as e: raise YunohostError( "backup_archive_corrupted", archive=self._archive_file, error=str(e) ) @@ -2493,7 +2493,7 @@ def backup_info(name, with_details=False, human_readable=False): try: files_in_archive = tar.getnames() - except (IOError, EOFError) as e: + except (IOError, EOFError, tarfile.ReadError) as e: raise YunohostError( "backup_archive_corrupted", archive=archive_file, error=str(e) ) From bdd343a4b7e9e8de148a7872e0ba84e94542bc9e Mon Sep 17 00:00:00 2001 From: ericgaspar Date: Mon, 10 May 2021 09:53:39 +0200 Subject: [PATCH 2456/3170] Update nodejs --- data/helpers.d/nodejs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/nodejs b/data/helpers.d/nodejs index 6f38a3e62..d72da9ed1 100644 --- a/data/helpers.d/nodejs +++ b/data/helpers.d/nodejs @@ -1,6 +1,6 @@ #!/bin/bash -n_version=7.1.0 +n_version=7.2.2 n_install_dir="/opt/node_n" node_version_path="$n_install_dir/n/versions/node" # N_PREFIX is the directory of n, it needs to be loaded as a environment variable. @@ -17,7 +17,7 @@ ynh_install_n () { ynh_print_info --message="Installation of N - Node.js version management" # Build an app.src for n echo "SOURCE_URL=https://github.com/tj/n/archive/v${n_version}.tar.gz -SOURCE_SUM=20100f3bc56648cc414717fb7367fcf0e8229dc59a10b0530ccac90042ee0a74" > "$YNH_APP_BASEDIR/conf/n.src" +SOURCE_SUM=9654440b0e7169cf3be5897a563258116b21ec6e7e7e266acc56979d3ebec6a2" > "$YNH_APP_BASEDIR/conf/n.src" # Download and extract n ynh_setup_source --dest_dir="$n_install_dir/git" --source_id=n # Install n From 06f8c1cc883805840c570239a4985e0ea215867e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 10 May 2021 19:00:26 +0200 Subject: [PATCH 2457/3170] Define ynh_node_load_path to be compatible with ynh_replace_vars --- data/helpers.d/nodejs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/data/helpers.d/nodejs b/data/helpers.d/nodejs index d72da9ed1..13bea42cd 100644 --- a/data/helpers.d/nodejs +++ b/data/helpers.d/nodejs @@ -92,6 +92,8 @@ ynh_use_nodejs () { node_PATH="$PATH" # Create an alias to easily load the PATH ynh_node_load_PATH="PATH=$node_PATH" + # Same var but in lower case to be compatible with ynh_replace_vars... + ynh_node_load_path="PATH=$node_PATH" } # Install a specific version of nodejs From 2b0df6c35aa0273693f108bb887d42c84d1831be Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 10 May 2021 19:00:38 +0200 Subject: [PATCH 2458/3170] Add requirements for new helpers --- data/helpers.d/multimedia | 4 ++++ data/helpers.d/php | 2 ++ 2 files changed, 6 insertions(+) diff --git a/data/helpers.d/multimedia b/data/helpers.d/multimedia index 7b379ad0f..2d43c2540 100644 --- a/data/helpers.d/multimedia +++ b/data/helpers.d/multimedia @@ -6,6 +6,8 @@ readonly MEDIA_DIRECTORY=/home/yunohost.multimedia # Initialize the multimedia directory system # # usage: ynh_multimedia_build_main_dir +# +# Requires YunoHost version 4.2 or higher. ynh_multimedia_build_main_dir() { ## Création du groupe multimedia @@ -55,6 +57,7 @@ ynh_multimedia_build_main_dir() { # # This "directory" will be a symbolic link to a existing directory. # +# Requires YunoHost version 4.2 or higher. ynh_multimedia_addfolder() { # Declare an array to define the options of this helper. @@ -83,6 +86,7 @@ ynh_multimedia_addfolder() { # # | arg: -u, --user_name= - The name of the user which gain this access. # +# Requires YunoHost version 4.2 or higher. ynh_multimedia_addaccess () { # Declare an array to define the options of this helper. local legacy_args=u diff --git a/data/helpers.d/php b/data/helpers.d/php index ae9cb2ec5..40a023e9d 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -570,6 +570,7 @@ YNH_COMPOSER_VERSION=${YNH_COMPOSER_VERSION:-$YNH_DEFAULT_COMPOSER_VERSION} # | arg: -w, --workdir - The directory from where the command will be executed. Default $final_path. # | arg: -c, --commands - Commands to execute. # +# Requires YunoHost version 4.2 or higher. ynh_composer_exec () { # Declare an array to define the options of this helper. local legacy_args=vwc @@ -595,6 +596,7 @@ ynh_composer_exec () { # | arg: -a, --install_args - Additional arguments provided to the composer install. Argument --no-dev already include # | arg: -c, --composerversion - Composer version to install # +# Requires YunoHost version 4.2 or higher. ynh_install_composer () { # Declare an array to define the options of this helper. local legacy_args=vwac From 52e307040ed3173d0b54676fb93c15486c71d498 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 11 May 2021 00:21:12 +0200 Subject: [PATCH 2459/3170] Attempt to fix the 'yunohost-api' being down after yunohost upgrades ... Apparently this is due to the port being still busy, which is puzzling, but also because it tries to restart but hit the StartLimitBurst (defaults is 5 times in 10 s). Increasing RestartSec to 5 may fix the issue (at some point the port gets free and service starts) --- debian/yunohost-api.init | 132 ---------------------------------- debian/yunohost-api.service | 4 +- debian/yunohost-firewall.init | 53 -------------- 3 files changed, 2 insertions(+), 187 deletions(-) delete mode 100644 debian/yunohost-api.init delete mode 100644 debian/yunohost-firewall.init diff --git a/debian/yunohost-api.init b/debian/yunohost-api.init deleted file mode 100644 index 3cda507e6..000000000 --- a/debian/yunohost-api.init +++ /dev/null @@ -1,132 +0,0 @@ -#! /bin/sh - -### BEGIN INIT INFO -# Provides: yunohost-api -# Required-Start: $local_fs $remote_fs $network $syslog -# Required-Stop: $local_fs $remote_fs $network $syslog -# Default-Start: 2 3 4 5 -# Default-Stop: 0 1 6 -# Short-Description: Manage YunoHost API Server -# Description: Manage YunoHost API Server -### END INIT INFO - -set -e - -DESC="YunoHost API Server" -NAME="yunohost-api" -DAEMON=/usr/bin/$NAME -DAEMON_OPTS="" -PATH=/sbin:/usr/sbin:/bin:/usr/bin -PIDFILE=/var/run/$NAME.pid -SCRIPTNAME=/etc/init.d/$NAME -LOGFILE=/var/log/$NAME.log - -# Include yunohost-api defaults if available -if [ -r /etc/default/yunohost-api ]; then - . /etc/default/yunohost-api -fi - -# Exit if the package is not installed -[ -x "$DAEMON" ] || exit 0 - -# Load the VERBOSE setting and other rcS variables -. /lib/init/vars.sh - -# Define LSB log_* functions. -# Depend on lsb-base (>= 3.2-14) to ensure that this file is present -# and status_of_proc is working. -. /lib/lsb/init-functions - -# -# Function that starts the daemon/service -# -do_start() -{ - # Return - # 0 if daemon has been started - # 1 if daemon was already running - # 2 if daemon could not be started - start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \ - || return 1 - start-stop-daemon --start --background --make-pidfile --quiet --no-close \ - --pidfile $PIDFILE --exec $DAEMON -- \ - $DAEMON_OPTS >>$LOGFILE 2>&1 \ - || return 2 -} - -# -# Function that stops the daemon/service -# -do_stop() -{ - # Return - # 0 if daemon has been stopped - # 1 if daemon was already stopped - # 2 if daemon could not be stopped - # other if a failure occurred - start-stop-daemon --stop --oknodo --pidfile $PIDFILE - RETVAL="$?" - - sleep 1 - return "$RETVAL" -} - -# -# Function that sends a SIGHUP to the daemon/service -# -do_reload() { - # Send a SIGHUP to reload the daemon. - start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME - return 0 -} - -case "$1" in - start) - [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" - do_start - case "$?" in - 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; - 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; - esac - ;; - stop) - [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" - do_stop - case "$?" in - 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; - 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; - esac - ;; - status) - status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $? - ;; - reload) - log_daemon_msg "Reloading $DESC" "$NAME" - do_reload - log_end_msg $? - ;; - restart|force-reload) - log_daemon_msg "Restarting $DESC" "$NAME" - do_stop - case "$?" in - 0|1) - do_start - case "$?" in - 0) log_end_msg 0 ;; - 1) log_end_msg 1 ;; # Old process is still running - *) log_end_msg 1 ;; # Failed to start - esac - ;; - *) - # Failed to stop - log_end_msg 1 - ;; - esac - ;; - *) - echo "Usage: $SCRIPTNAME {start|stop|status|restart|reload}" >&2 - exit 3 - ;; -esac - -: diff --git a/debian/yunohost-api.service b/debian/yunohost-api.service index 4e71eadac..850255127 100644 --- a/debian/yunohost-api.service +++ b/debian/yunohost-api.service @@ -7,9 +7,9 @@ Type=simple Environment=DAEMON_OPTS= EnvironmentFile=-/etc/default/yunohost-api ExecStart=/usr/bin/yunohost-api $DAEMON_OPTS -ExecReload=/bin/kill -HUP $MAINPID Restart=always -RestartSec=1 +RestartSec=5 +TimeoutStopSec=30 [Install] WantedBy=multi-user.target diff --git a/debian/yunohost-firewall.init b/debian/yunohost-firewall.init deleted file mode 100644 index fd1443494..000000000 --- a/debian/yunohost-firewall.init +++ /dev/null @@ -1,53 +0,0 @@ -#! /bin/bash -### BEGIN INIT INFO -# Provides: yunohost-firewall -# Required-Start: $local_fs $remote_fs $network $syslog -# Required-Stop: $local_fs $remote_fs $network $syslog -# Default-Start: 2 3 4 5 -# Default-Stop: 0 1 6 -# Short-Description: Start/stop YunoHost firewall -# Description: Start/stop YunoHost firewall -### END INIT INFO - -DAEMON=/usr/bin/yunohost -DAEMON_OPTS="" - -test -x $DAEMON || exit 0 - -. /lib/lsb/init-functions - -logger "YunoHost firewall: Start script executed" - -case "$1" in - start) - logger "YunoHost firewall: Starting" - log_daemon_msg "Starting firewall: YunoHost" - /usr/bin/yunohost firewall reload - log_end_msg $? - ;; - stop) - logger "YunoHost firewall: Stopping" - log_daemon_msg "Stopping firewall: YunoHost" - /usr/bin/yunohost firewall stop - log_end_msg $? - ;; - restart|force-reload) - logger "YunoHost firewall: Restarting" - log_daemon_msg "Restarting firewall: YunoHost" - /usr/bin/yunohost firewall reload - log_end_msg $? - ;; - status) - logger "YunoHost API: Running" - log_daemon_msg "YunoHost API: Running" - iptables -L | grep "Chain INPUT (policy DROP)" > /dev/null 2>&1 - log_end_msg $? - ;; - *) - logger "YunoHost API: Invalid usage" - echo "Usage: /etc/init.d/yunohost-api {start|stop|restart|force-reload|status}" >&2 - exit 1 - ;; -esac - -exit 0 From 8cf151668fda88b57e765e742e673bd6d1227efc Mon Sep 17 00:00:00 2001 From: ljf Date: Tue, 11 May 2021 12:08:00 +0200 Subject: [PATCH 2460/3170] [fix] CI test --- locales/de.json | 2 +- src/yunohost/tests/test_user-group.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/locales/de.json b/locales/de.json index 53297ed6d..73efca434 100644 --- a/locales/de.json +++ b/locales/de.json @@ -590,5 +590,5 @@ "diagnosis_sshd_config_insecure": "Die SSH-Konfiguration wurde scheinbar manuell abgeändert, und ist unsicher, weil sie keine 'AllowGroups'- oder 'AllowUsers' -Direktiven für die Begrenzung des Zugriffs durch autorisierte Benutzer enthält.", "backup_create_size_estimation": "Das Archiv wird etwa {size} Daten enthalten", "app_restore_script_failed": "Im Wiederherstellungsskript der Anwendung ist ein Fehler aufgetreten", - "app_restore_failed": "Konnte {apps:s} nicht wiederherstellen: {error:s}" + "app_restore_failed": "Konnte {app:s} nicht wiederherstellen: {error:s}" } diff --git a/src/yunohost/tests/test_user-group.py b/src/yunohost/tests/test_user-group.py index ee5d07c40..63d9a1930 100644 --- a/src/yunohost/tests/test_user-group.py +++ b/src/yunohost/tests/test_user-group.py @@ -120,7 +120,7 @@ def test_import_user(mocker): fieldnames = [u'username', u'firstname', u'lastname', u'password', u'mailbox-quota', u'mail', u'mail-alias', u'mail-forward', u'groups'] - with BytesIO() as csv_io: + with StringIO() as csv_io: writer = csv.DictWriter(csv_io, fieldnames, delimiter=';', quotechar='"') writer.writeheader() @@ -164,10 +164,10 @@ def test_export_user(mocker): result = user_export() should_be = "username;firstname;lastname;password;" should_be += "mailbox-quota;mail;mail-alias;mail-forward;groups" - should_be += "\r\nbob;Bob;Snow;;0;bob@" + maindomain + ";;;apps" should_be += "\r\nalice;Alice;White;;0;alice@" + maindomain + ";" should_be += ','.join([alias + maindomain for alias in FIRST_ALIASES]) should_be += ";;dev" + should_be += "\r\nbob;Bob;Snow;;0;bob@" + maindomain + ";;;apps" should_be += "\r\njack;Jack;Black;;0;jack@" + maindomain + ";;;" assert result == should_be From 0f10b91fa194b1e27bd0593700ffba72b5f8cd2b Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Thu, 13 May 2021 17:25:28 +0200 Subject: [PATCH 2461/3170] [fix] nftable migrations for python3 compatibility --- src/yunohost/data_migrations/0018_xtable_to_nftable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/data_migrations/0018_xtable_to_nftable.py b/src/yunohost/data_migrations/0018_xtable_to_nftable.py index af5d11e43..94b47d944 100644 --- a/src/yunohost/data_migrations/0018_xtable_to_nftable.py +++ b/src/yunohost/data_migrations/0018_xtable_to_nftable.py @@ -122,5 +122,5 @@ class MyMigration(Migration): ) ) - out = out.strip().split("\n") + out = out.strip().split(b"\n") return (returncode, out, err) From d1ea6468767971118e531815077ddf2cf18b883f Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Tue, 18 May 2021 21:43:52 +0200 Subject: [PATCH 2462/3170] [fix] Remove warning to user (transfered in linter) --- src/yunohost/app.py | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index c048ca5ea..3a34134b4 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1269,10 +1269,6 @@ def app_addaccess(apps, users=[]): """ from yunohost.permission import user_permission_update - logger.warning( - "/!\\ Packagers ! This app is using the legacy permission system. Please use the new helpers ynh_permission_{create,url,update,delete} and the 'visitors' group to manage permissions." - ) - output = {} for app in apps: permission = user_permission_update( @@ -1294,10 +1290,6 @@ def app_removeaccess(apps, users=[]): """ from yunohost.permission import user_permission_update - logger.warning( - "/!\\ Packagers ! This app is using the legacy permission system. Please use the new helpers ynh_permission_{create,url,update,delete} and the 'visitors' group to manage permissions." - ) - output = {} for app in apps: permission = user_permission_update(app + ".main", remove=users) @@ -1315,11 +1307,7 @@ def app_clearaccess(apps): """ from yunohost.permission import user_permission_reset - - logger.warning( - "/!\\ Packagers ! This app is using the legacy permission system. Please use the new helpers ynh_permission_{create,url,update,delete} and the 'visitors' group to manage permissions." - ) - + output = {} for app in apps: permission = user_permission_reset(app + ".main") @@ -1447,9 +1435,6 @@ def app_setting(app, key, value=None, delete=False): # SET else: - logger.warning( - "/!\\ Packagers! This app is still using the skipped/protected/unprotected_uris/regex settings which are now obsolete and deprecated... Instead, you should use the new helpers 'ynh_permission_{create,urls,update,delete}' and the 'visitors' group to initialize the public/private access. Check out the documentation at the bottom of yunohost.org/groups_and_permissions to learn how to use the new permission mechanism." - ) urls = value # If the request is about the root of the app (/), ( = the vast majority of cases) From 9c21fde52be33f7f3d4eece79659bacc89b660c1 Mon Sep 17 00:00:00 2001 From: Salamandar <6552989+Salamandar@users.noreply.github.com> Date: Tue, 18 May 2021 22:03:06 +0200 Subject: [PATCH 2463/3170] Set YNH_APP_BASEDIR as an absolute path Ping @alexAubin :) --- 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 c216aa6d2..00bec89ac 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -1,6 +1,6 @@ #!/bin/bash -YNH_APP_BASEDIR=$([[ "$(basename $0)" =~ ^backup|restore$ ]] && echo '../settings' || echo '..') +YNH_APP_BASEDIR=$(realpath $([[ "$(basename $0)" =~ ^backup|restore$ ]] && echo '../settings' || echo '..')) # Handle script crashes / failures # From bb140b2ba4353d7282504de573ec4f4b8d7d47ef Mon Sep 17 00:00:00 2001 From: MercierCorentin Date: Wed, 19 May 2021 03:02:13 +0200 Subject: [PATCH 2464/3170] Add providers parameter list --- data/other/providers.yml | 218 +++++++++++++++++++++++++++++++++++++++ debian/install | 1 + 2 files changed, 219 insertions(+) create mode 100644 data/other/providers.yml diff --git a/data/other/providers.yml b/data/other/providers.yml new file mode 100644 index 000000000..4ba69d97b --- /dev/null +++ b/data/other/providers.yml @@ -0,0 +1,218 @@ +- aliyun + - auth_key_id + - auth_secret +- aurora + - auth_api_key + - auth_secret_key +- azure + - auth_client_id + - auth_client_secret + - auth_tenant_id + - auth_subscription_id + - resource_group +- cloudflare + - auth_username + - auth_token + - zone_id +- cloudns + - auth_id + - auth_subid + - auth_subuser + - auth_password + - weight + - port +- cloudxns + - auth_username + - auth_token +- conoha + - auth_region + - auth_token + - auth_username + - auth_password + - auth_tenant_id +- constellix + - auth_username + - auth_token +- digitalocean + - auth_token +- dinahosting + - auth_username + - auth_password +- directadmin + - auth_password + - auth_username + - endpoint +- dnsimple + - auth_token + - auth_username + - auth_password + - auth_2fa +- dnsmadeeasy + - auth_username + - auth_token +- dnspark + - auth_username + - auth_token +- dnspod + - auth_username + - auth_token +- dreamhost + - auth_token +- dynu + - auth_token +- easydns + - auth_username + - auth_token +- easyname + - auth_username + - auth_password +- euserv + - auth_username + - auth_password +- exoscale + - auth_key + - auth_secret +- gandi + - auth_token + - api_protocol +- gehirn + - auth_token + - auth_secret +- glesys + - auth_username + - auth_token +- godaddy + - auth_key + - auth_secret +- googleclouddns + - auth_service_account_info +- gransy + - auth_username + - auth_password +- gratisdns + - auth_username + - auth_password +- henet + - auth_username + - auth_password +- hetzner + - auth_token +- hostingde + - auth_token +- hover + - auth_username + - auth_password +- infoblox + - auth_user + - auth_psw + - ib_view + - ib_host +- infomaniak + - auth_token +- internetbs + - auth_key + - auth_password +- inwx + - auth_username + - auth_password +- joker + - auth_token +- linode + - auth_token +- linode4 + - auth_token +- localzone + - filename +- luadns + - auth_username + - auth_token +- memset + - auth_token +- mythicbeasts + - auth_username + - auth_password + - auth_token +- namecheap + - auth_token + - auth_username + - auth_client_ip + - auth_sandbox +- namesilo + - auth_token +- netcup + - auth_customer_id + - auth_api_key + - auth_api_password +- nfsn + - auth_username + - auth_token +- njalla + - auth_token +- nsone + - auth_token +- onapp + - auth_username + - auth_token + - auth_server +- online + - auth_token +- ovh + - auth_entrypoint + - auth_application_key + - auth_application_secret + - auth_consumer_key +- plesk + - auth_username + - auth_password + - plesk_server +- pointhq + - auth_username + - auth_token +- powerdns + - auth_token + - pdns_server + - pdns_server_id + - pdns_disable_notify +- rackspace + - auth_account + - auth_username + - auth_api_key + - auth_token + - sleep_time +- rage4 + - auth_username + - auth_token +- rcodezero + - auth_token +- route53 + - auth_access_key + - auth_access_secret + - private_zone + - auth_username + - auth_token +- safedns + - auth_token +- sakuracloud + - auth_token + - auth_secret +- softlayer + - auth_username + - auth_api_key +- transip + - auth_username + - auth_api_key +- ultradns + - auth_token + - auth_username + - auth_password +- vultr + - auth_token +- yandex + - auth_token +- zeit + - auth_token +- zilore + - auth_key +- zonomi + - auth_token + - auth_entrypoint diff --git a/debian/install b/debian/install index 1691a4849..521f2d3af 100644 --- a/debian/install +++ b/debian/install @@ -8,6 +8,7 @@ data/other/yunoprompt.service /etc/systemd/system/ data/other/password/* /usr/share/yunohost/other/password/ data/other/dpkg-origins/yunohost /etc/dpkg/origins data/other/dnsbl_list.yml /usr/share/yunohost/other/ +data/other/providers_list.yml /usr/share/yunohost/other/ data/other/ffdhe2048.pem /usr/share/yunohost/other/ data/other/* /usr/share/yunohost/yunohost-config/moulinette/ data/templates/* /usr/share/yunohost/templates/ From 51d6d19810a230c38fd5f9134a12320d7e263d39 Mon Sep 17 00:00:00 2001 From: MercierCorentin Date: Thu, 20 May 2021 13:29:04 +0200 Subject: [PATCH 2465/3170] Add first version of set domain provider --- data/actionsmap/yunohost.yml | 24 ++ data/bash-completion.d/yunohost | 117 ++++++++- .../{providers.yml => providers_list.yml} | 138 +++++------ src/yunohost/app.py | 1 + src/yunohost/domain.py | 225 +++++++++++------- 5 files changed, 344 insertions(+), 161 deletions(-) rename data/other/{providers.yml => providers_list.yml} (79%) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 58a48c87f..87dfcf026 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -582,6 +582,30 @@ domain: full: --delete help: Delete the key action: store_true + subcategories: + registrar: + subcategory_help: Manage domains registrars + actions: + ### domain_registrar_set() + set: + action_help: Set domain registrar + api: POST /domains/registrar + arguments: + domain: + help: Domain name + registrar: + help: registrar_key, see yunohost domain registrar list + -a: + full: --args + help: Serialized arguments for app script (i.e. "domain=domain.tld&path=/path") + ### domain_registrar_set() + get: + action_help: Get domain registrar + api: GET /domains/registrar + ### domain_registrar_list() + list: + action_help: List available registrars + api: GET /domains/registrar/list ############################# # App # diff --git a/data/bash-completion.d/yunohost b/data/bash-completion.d/yunohost index 2572a391d..1be522db2 100644 --- a/data/bash-completion.d/yunohost +++ b/data/bash-completion.d/yunohost @@ -1,3 +1,114 @@ -# This file is automatically generated -# during Debian's package build by the script -# data/actionsmap/yunohost_completion.py +# +# completion for yunohost +# automatically generated from the actionsmap +# + +_yunohost() +{ + local cur prev opts narg + COMPREPLY=() + + # the number of words already typed + narg=${#COMP_WORDS[@]} + + # the current word being typed + cur="${COMP_WORDS[COMP_CWORD]}" + + # If one is currently typing a category, + # match with categorys + if [[ $narg == 2 ]]; then + opts="user domain app backup settings service firewall dyndns tools hook log diagnosis" + fi + + # If one already typed a category, + # match the actions or the subcategories of that category + if [[ $narg == 3 ]]; then + # the category typed + category="${COMP_WORDS[1]}" + + if [[ $category == "user" ]]; then + opts="list create delete update info group permission ssh" + fi + if [[ $category == "domain" ]]; then + opts="list add registrar push_config remove dns-conf main-domain cert-status cert-install cert-renew url-available setting " + fi + if [[ $category == "app" ]]; then + opts="catalog search manifest fetchlist list info map install remove upgrade change-url setting register-url makedefault ssowatconf change-label addaccess removeaccess clearaccess action config" + fi + if [[ $category == "backup" ]]; then + opts="create restore list info download delete " + fi + if [[ $category == "settings" ]]; then + opts="list get set reset-all reset " + fi + if [[ $category == "service" ]]; then + opts="add remove start stop reload restart reload_or_restart enable disable status log regen-conf " + fi + if [[ $category == "firewall" ]]; then + opts="list allow disallow upnp reload stop " + fi + if [[ $category == "dyndns" ]]; then + opts="subscribe update installcron removecron " + fi + if [[ $category == "tools" ]]; then + opts="adminpw maindomain postinstall update upgrade shell shutdown reboot regen-conf versions migrations" + fi + if [[ $category == "hook" ]]; then + opts="add remove info list callback exec " + fi + if [[ $category == "log" ]]; then + opts="list show share " + fi + if [[ $category == "diagnosis" ]]; then + opts="list show get run ignore unignore " + fi + fi + + # If one already typed an action or a subcategory, + # match the actions of that subcategory + if [[ $narg == 4 ]]; then + # the category typed + category="${COMP_WORDS[1]}" + + # the action or the subcategory typed + action_or_subcategory="${COMP_WORDS[2]}" + + if [[ $category == "user" ]]; then + if [[ $action_or_subcategory == "group" ]]; then + opts="list create delete info add remove" + fi + if [[ $action_or_subcategory == "permission" ]]; then + opts="list info update add remove reset" + fi + if [[ $action_or_subcategory == "ssh" ]]; then + opts="list-keys add-key remove-key" + fi + fi + if [[ $category == "app" ]]; then + if [[ $action_or_subcategory == "action" ]]; then + opts="list run" + fi + if [[ $action_or_subcategory == "config" ]]; then + opts="show-panel apply" + fi + fi + if [[ $category == "tools" ]]; then + if [[ $action_or_subcategory == "migrations" ]]; then + opts="list run state" + fi + fi + fi + + # If no options were found propose --help + if [ -z "$opts" ]; then + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $prev != "--help" ]]; then + opts=( --help ) + fi + fi + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 +} + +complete -F _yunohost yunohost \ No newline at end of file diff --git a/data/other/providers.yml b/data/other/providers_list.yml similarity index 79% rename from data/other/providers.yml rename to data/other/providers_list.yml index 4ba69d97b..a006bd272 100644 --- a/data/other/providers.yml +++ b/data/other/providers_list.yml @@ -1,218 +1,218 @@ -- aliyun +aliyun: - auth_key_id - auth_secret -- aurora +aurora: - auth_api_key - auth_secret_key -- azure +azure: - auth_client_id - auth_client_secret - auth_tenant_id - auth_subscription_id - resource_group -- cloudflare +cloudflare: - auth_username - auth_token - zone_id -- cloudns +cloudns: - auth_id - auth_subid - auth_subuser - auth_password - weight - port -- cloudxns +cloudxns: - auth_username - auth_token -- conoha +conoha: - auth_region - auth_token - auth_username - auth_password - auth_tenant_id -- constellix +constellix: - auth_username - auth_token -- digitalocean +digitalocean: - auth_token -- dinahosting +dinahosting: - auth_username - auth_password -- directadmin +directadmin: - auth_password - auth_username - endpoint -- dnsimple +dnsimple: - auth_token - auth_username - auth_password - auth_2fa -- dnsmadeeasy +dnsmadeeasy: - auth_username - auth_token -- dnspark +dnspark: - auth_username - auth_token -- dnspod +dnspod: - auth_username - auth_token -- dreamhost +dreamhost: - auth_token -- dynu +dynu: - auth_token -- easydns +easydns: - auth_username - auth_token -- easyname +easyname: - auth_username - auth_password -- euserv +euserv: - auth_username - auth_password -- exoscale +exoscale: - auth_key - auth_secret -- gandi +gandi: - auth_token - api_protocol -- gehirn +gehirn: - auth_token - auth_secret -- glesys +glesys: - auth_username - auth_token -- godaddy +godaddy: - auth_key - auth_secret -- googleclouddns +googleclouddns: - auth_service_account_info -- gransy +gransy: - auth_username - auth_password -- gratisdns +gratisdns: - auth_username - auth_password -- henet +henet: - auth_username - auth_password -- hetzner +hetzner: - auth_token -- hostingde +hostingde: - auth_token -- hover +hover: - auth_username - auth_password -- infoblox +infoblox: - auth_user - auth_psw - ib_view - ib_host -- infomaniak +infomaniak: - auth_token -- internetbs +internetbs: - auth_key - auth_password -- inwx +inwx: - auth_username - auth_password -- joker +joker: - auth_token -- linode +linode: - auth_token -- linode4 +linode4: - auth_token -- localzone +localzone: - filename -- luadns +luadns: - auth_username - auth_token -- memset +memset: - auth_token -- mythicbeasts +mythicbeasts: - auth_username - auth_password - auth_token -- namecheap +namecheap: - auth_token - auth_username - auth_client_ip - auth_sandbox -- namesilo +namesilo: - auth_token -- netcup +netcup: - auth_customer_id - auth_api_key - auth_api_password -- nfsn +nfsn: - auth_username - auth_token -- njalla +njalla: - auth_token -- nsone +nsone: - auth_token -- onapp +onapp: - auth_username - auth_token - auth_server -- online +online: - auth_token -- ovh +ovh: - auth_entrypoint - auth_application_key - auth_application_secret - auth_consumer_key -- plesk +plesk: - auth_username - auth_password - plesk_server -- pointhq +pointhq: - auth_username - auth_token -- powerdns +powerdns: - auth_token - pdns_server - pdns_server_id - pdns_disable_notify -- rackspace +rackspace: - auth_account - auth_username - auth_api_key - auth_token - sleep_time -- rage4 +rage4: - auth_username - auth_token -- rcodezero +rcodezero: - auth_token -- route53 +route53: - auth_access_key - auth_access_secret - private_zone - auth_username - auth_token -- safedns +safedns: - auth_token -- sakuracloud +sakuracloud: - auth_token - auth_secret -- softlayer +softlayer: - auth_username - auth_api_key -- transip +transip: - auth_username - auth_api_key -- ultradns +ultradns: - auth_token - auth_username - auth_password -- vultr +vultr: - auth_token -- yandex +yandex: - auth_token -- zeit +zeit: - auth_token -- zilore +zilore: - auth_key -- zonomi +zonomi: - auth_token - auth_entrypoint diff --git a/src/yunohost/app.py b/src/yunohost/app.py index c048ca5ea..b9a7e634b 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -2977,6 +2977,7 @@ ARGUMENTS_TYPE_PARSERS = { } + 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. diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 05f2a16ae..6180616bf 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -29,8 +29,8 @@ import sys import yaml import functools -from lexicon.config import ConfigResolver -from lexicon.client import Client +# from lexicon.config import ConfigResolver +# from lexicon.client import Client from moulinette import m18n, msettings, msignals from moulinette.core import MoulinetteError @@ -43,6 +43,7 @@ from yunohost.app import ( _installed_apps, _get_app_settings, _get_conflicting_apps, + _parse_args_in_yunohost_format ) from yunohost.regenconf import regen_conf, _force_clear_hashes, _process_regen_conf from yunohost.utils.network import get_public_ip @@ -53,6 +54,7 @@ from yunohost.hook import hook_callback logger = getActionLogger("yunohost.domain") DOMAIN_SETTINGS_PATH = "/etc/yunohost/domains.yml" +REGISTRAR_LIST_PATH = "/usr/share/yunohost/other/providers_list.yml" def domain_list(exclude_subdomains=False): """ @@ -825,105 +827,150 @@ def _set_domain_settings(domain, domain_settings): with open(DOMAIN_SETTINGS_PATH, 'w') as file: yaml.dump(domains, file, default_flow_style=False) +# def domain_get_registrar(): +def domain_registrar_set(domain, registrar, args): + + domains = _load_domain_settings() + if not domain in domains.keys(): + raise YunohostError("domain_name_unknown", domain=domain) -def domain_push_config(domain): - """ - Send DNS records to the previously-configured registrar of the domain. - """ - # Generate the records - if domain not in domain_list()["domains"]: - raise YunohostValidationError("domain_name_unknown", domain=domain) + registrars = yaml.load(open(REGISTRAR_LIST_PATH, "r+")) + if not registrar in registrars.keys(): + # FIXME créer l'erreur + raise YunohostError("registrar_unknown") + + parameters = registrars[registrar] + ask_args = [] + for parameter in parameters: + ask_args.append({ + 'name' : parameter, + 'type': 'string', + 'example': '', + 'default': '', + }) + args_dict = ( + {} if not args else dict(urllib.parse.parse_qsl(args, keep_blank_values=True)) + ) + parsed_answer_dict = _parse_args_in_yunohost_format(args_dict, ask_args) - domains_settings = _get_domain_settings(domain, True) - - dns_conf = _build_dns_conf(domains_settings) - - # Flatten the DNS conf - flatten_dns_conf = [] - for key in dns_conf: - list_of_records = dns_conf[key] - for record in list_of_records: - # FIXME Lexicon does not support CAA records - # See https://github.com/AnalogJ/lexicon/issues/282 and https://github.com/AnalogJ/lexicon/pull/371 - # They say it's trivial to implement it! - # And yet, it is still not done/merged - if record["type"] != "CAA": - # Add .domain.tdl to the name entry - record["name"] = "{}.{}".format(record["name"], domain) - flatten_dns_conf.append(record) - - # Get provider info - # TODO - provider = { - "name": "gandi", - "options": { - "api_protocol": "rest", - "auth_token": "vhcIALuRJKtoZiZyxfDYWLom" + domain_provider = { + 'name': registrar, + 'options': { + } } + for arg_name, arg_value_and_type in parsed_answer_dict.items(): + domain_provider['options'][arg_name] = arg_value_and_type[0] + + domain_settings = domains[domain] + domain_settings["provider"] = domain_provider - # Construct the base data structure to use lexicon's API. - base_config = { - "provider_name": provider["name"], - "domain": domain, # domain name - } - base_config[provider["name"]] = provider["options"] + # Save the settings to the .yaml file + with open(DOMAIN_SETTINGS_PATH, 'w') as file: + yaml.dump(domains, file, default_flow_style=False) + - # Get types present in the generated records - types = set() - for record in flatten_dns_conf: - types.add(record["type"]) - # Fetch all types present in the generated records - distant_records = {} - for key in types: - record_config = { - "action": "list", - "type": key, - } - final_lexicon = ConfigResolver().with_dict(dict_object=base_config).with_dict(dict_object=record_config) - # print('final_lexicon:', final_lexicon); - client = Client(final_lexicon) - distant_records[key] = client.execute() +# def domain_push_config(domain): +# """ +# Send DNS records to the previously-configured registrar of the domain. +# """ +# # Generate the records +# if domain not in domain_list()["domains"]: +# raise YunohostValidationError("domain_name_unknown", domain=domain) - for key in types: - for distant_record in distant_records[key]: - print('distant_record:', distant_record); - for local_record in flatten_dns_conf: - print('local_record:', local_record); +# domains_settings = _get_domain_settings(domain, True) - # Push the records - for record in flatten_dns_conf: - # For each record, first check if one record exists for the same (type, name) couple - it_exists = False - # TODO do not push if local and distant records are exactly the same ? - # is_the_same_record = False +# dns_conf = _build_dns_conf(domains_settings) - for distant_record in distant_records[record["type"]]: - if distant_record["type"] == record["type"] and distant_record["name"] == record["name"]: - it_exists = True - # previous TODO - # if distant_record["ttl"] = ... and distant_record["name"] ... - # is_the_same_record = True +# # Flatten the DNS conf +# flatten_dns_conf = [] +# for key in dns_conf: +# list_of_records = dns_conf[key] +# for record in list_of_records: +# # FIXME Lexicon does not support CAA records +# # See https://github.com/AnalogJ/lexicon/issues/282 and https://github.com/AnalogJ/lexicon/pull/371 +# # They say it's trivial to implement it! +# # And yet, it is still not done/merged +# if record["type"] != "CAA": +# # Add .domain.tdl to the name entry +# record["name"] = "{}.{}".format(record["name"], domain) +# flatten_dns_conf.append(record) - # Finally, push the new record or update the existing one - record_config = { - "action": "update" if it_exists else "create", # create, list, update, delete - "type": record["type"], # specify a type for record filtering, case sensitive in some cases. - "name": record["name"], - "content": record["value"], - # FIXME Delte TTL, doesn't work with Gandi. - # See https://github.com/AnalogJ/lexicon/issues/726 (similar issue) - # But I think there is another issue with Gandi. Or I'm misusing the API... - # "ttl": record["ttl"], - } - final_lexicon = ConfigResolver().with_dict(dict_object=base_config).with_dict(dict_object=record_config) - client = Client(final_lexicon) - print('pushed_record:', record_config, "→", end=' ') - results = client.execute() - print('results:', results); - # print("Failed" if results == False else "Ok") +# # Get provider info +# # TODO +# provider = { +# "name": "gandi", +# "options": { +# "api_protocol": "rest", +# "auth_token": "vhcIALuRJKtoZiZyxfDYWLom" +# } +# } + +# # Construct the base data structure to use lexicon's API. +# base_config = { +# "provider_name": provider["name"], +# "domain": domain, # domain name +# } +# base_config[provider["name"]] = provider["options"] + +# # Get types present in the generated records +# types = set() + +# for record in flatten_dns_conf: +# types.add(record["type"]) + +# # Fetch all types present in the generated records +# distant_records = {} + +# for key in types: +# record_config = { +# "action": "list", +# "type": key, +# } +# final_lexicon = ConfigResolver().with_dict(dict_object=base_config).with_dict(dict_object=record_config) +# # print('final_lexicon:', final_lexicon); +# client = Client(final_lexicon) +# distant_records[key] = client.execute() + +# for key in types: +# for distant_record in distant_records[key]: +# print('distant_record:', distant_record); +# for local_record in flatten_dns_conf: +# print('local_record:', local_record); + +# # Push the records +# for record in flatten_dns_conf: +# # For each record, first check if one record exists for the same (type, name) couple +# it_exists = False +# # TODO do not push if local and distant records are exactly the same ? +# # is_the_same_record = False + +# for distant_record in distant_records[record["type"]]: +# if distant_record["type"] == record["type"] and distant_record["name"] == record["name"]: +# it_exists = True +# # previous TODO +# # if distant_record["ttl"] = ... and distant_record["name"] ... +# # is_the_same_record = True + +# # Finally, push the new record or update the existing one +# record_config = { +# "action": "update" if it_exists else "create", # create, list, update, delete +# "type": record["type"], # specify a type for record filtering, case sensitive in some cases. +# "name": record["name"], +# "content": record["value"], +# # FIXME Delte TTL, doesn't work with Gandi. +# # See https://github.com/AnalogJ/lexicon/issues/726 (similar issue) +# # But I think there is another issue with Gandi. Or I'm misusing the API... +# # "ttl": record["ttl"], +# } +# final_lexicon = ConfigResolver().with_dict(dict_object=base_config).with_dict(dict_object=record_config) +# client = Client(final_lexicon) +# print('pushed_record:', record_config, "→", end=' ') +# results = client.execute() +# print('results:', results); +# # print("Failed" if results == False else "Ok") # def domain_config_fetch(domain, key, value): From d241db4c334122554658a8484ddb4ca2ce8b992b Mon Sep 17 00:00:00 2001 From: ljf Date: Fri, 21 May 2021 00:46:31 +0200 Subject: [PATCH 2466/3170] [fix] Be able to init slapd in a chroot --- data/hooks/conf_regen/06-slapd | 12 +-- data/other/ldap_default_entries.ldif | 99 +++++++++++++++++++++++ data/other/ldap_scheme.yml | 113 --------------------------- src/yunohost/tools.py | 109 ++++++++------------------ 4 files changed, 137 insertions(+), 196 deletions(-) create mode 100644 data/other/ldap_default_entries.ldif delete mode 100644 data/other/ldap_scheme.yml diff --git a/data/hooks/conf_regen/06-slapd b/data/hooks/conf_regen/06-slapd index e7524184c..363de81d6 100755 --- a/data/hooks/conf_regen/06-slapd +++ b/data/hooks/conf_regen/06-slapd @@ -12,16 +12,12 @@ do_init_regen() { do_pre_regen "" - systemctl daemon-reload - - systemctl restart slapd - # Drop current existing slapd data rm -rf /var/backups/*.ldapdb rm -rf /var/backups/slapd-* -debconf-set-selections << EOF + debconf-set-selections << EOF slapd slapd/password1 password yunohost slapd slapd/password2 password yunohost slapd slapd/domain string yunohost.org @@ -45,11 +41,11 @@ EOF chown -R openldap:openldap /etc/ldap/schema/ usermod -aG ssl-cert openldap - systemctl restart slapd - # (Re-)init data according to ldap_scheme.yaml + # (Re-)init data according to default ldap entries + slapadd -n1 -l /usr/share/yunohost/yunohost-config/moulinette/ldap_default_entries.ldif 2>&1 \ + | grep -v "none elapsed\|Closing DB" || true - yunohost tools shell -c "from yunohost.tools import tools_ldapinit; tools_ldapinit()" } _regenerate_slapd_conf() { diff --git a/data/other/ldap_default_entries.ldif b/data/other/ldap_default_entries.ldif new file mode 100644 index 000000000..e76edb3d6 --- /dev/null +++ b/data/other/ldap_default_entries.ldif @@ -0,0 +1,99 @@ +dn: ou=users,dc=yunohost,dc=org +objectClass: organizationalUnit +objectClass: top +ou: users + +dn: ou=domains,dc=yunohost,dc=org +objectClass: organizationalUnit +objectClass: top +ou: domains + +dn: ou=apps,dc=yunohost,dc=org +objectClass: organizationalUnit +objectClass: top +ou: apps + +dn: ou=permission,dc=yunohost,dc=org +objectClass: organizationalUnit +objectClass: top +ou: permission + +dn: ou=groups,dc=yunohost,dc=org +objectClass: organizationalUnit +objectClass: top +ou: groups + +dn: ou=sudo,dc=yunohost,dc=org +objectClass: organizationalUnit +objectClass: top +ou: sudo + +dn: cn=admin,ou=sudo,dc=yunohost,dc=org +cn: admin +sudoCommand: ALL +sudoUser: admin +objectClass: sudoRole +objectClass: top +sudoOption: !authenticate +sudoHost: ALL + +dn: cn=admins,ou=groups,dc=yunohost,dc=org +objectClass: posixGroup +objectClass: top +memberUid: admin +gidNumber: 4001 +cn: admins + +dn: cn=all_users,ou=groups,dc=yunohost,dc=org +objectClass: posixGroup +objectClass: groupOfNamesYnh +gidNumber: 4002 +cn: all_users + +dn: cn=visitors,ou=groups,dc=yunohost,dc=org +objectClass: posixGroup +objectClass: groupOfNamesYnh +gidNumber: 4003 +cn: visitors + +dn: cn=mail.main,ou=permission,dc=yunohost,dc=org +groupPermission: cn=all_users,ou=groups,dc=yunohost,dc=org +cn: mail.main +objectClass: posixGroup +objectClass: permissionYnh +isProtected: TRUE +label: E-mail +gidNumber: 5001 +showTile: FALSE +authHeader: FALSE + +dn: cn=xmpp.main,ou=permission,dc=yunohost,dc=org +groupPermission: cn=all_users,ou=groups,dc=yunohost,dc=org +cn: xmpp.main +objectClass: posixGroup +objectClass: permissionYnh +isProtected: TRUE +label: XMPP +gidNumber: 5002 +showTile: FALSE +authHeader: FALSE + +dn: cn=ssh.main,ou=permission,dc=yunohost,dc=org +cn: ssh.main +objectClass: posixGroup +objectClass: permissionYnh +isProtected: TRUE +label: SSH +gidNumber: 5003 +showTile: FALSE +authHeader: FALSE + +dn: cn=sftp.main,ou=permission,dc=yunohost,dc=org +cn: sftp.main +objectClass: posixGroup +objectClass: permissionYnh +isProtected: TRUE +label: SFTP +gidNumber: 5004 +showTile: FALSE +authHeader: FALSE diff --git a/data/other/ldap_scheme.yml b/data/other/ldap_scheme.yml deleted file mode 100644 index b45b3ac3a..000000000 --- a/data/other/ldap_scheme.yml +++ /dev/null @@ -1,113 +0,0 @@ -parents: - ou=users: - ou: users - objectClass: - - organizationalUnit - - top - - ou=domains: - ou: domains - objectClass: - - organizationalUnit - - top - - ou=apps: - ou: apps - objectClass: - - organizationalUnit - - top - - ou=permission: - ou: permission - objectClass: - - organizationalUnit - - top - - ou=groups: - ou: groups - objectClass: - - organizationalUnit - - top - ou=sudo: - ou: sudo - objectClass: - - organizationalUnit - - top - -children: - cn=admin,ou=sudo: - cn: admin - sudoUser: admin - sudoHost: ALL - sudoCommand: ALL - sudoOption: "!authenticate" - objectClass: - - sudoRole - - top - cn=admins,ou=groups: - cn: admins - gidNumber: "4001" - memberUid: admin - objectClass: - - posixGroup - - top - cn=all_users,ou=groups: - cn: all_users - gidNumber: "4002" - objectClass: - - posixGroup - - groupOfNamesYnh - cn=visitors,ou=groups: - cn: visitors - gidNumber: "4003" - objectClass: - - posixGroup - - groupOfNamesYnh - -depends_children: - cn=mail.main,ou=permission: - cn: mail.main - gidNumber: "5001" - objectClass: - - posixGroup - - permissionYnh - groupPermission: - - "cn=all_users,ou=groups,dc=yunohost,dc=org" - authHeader: "FALSE" - label: "E-mail" - showTile: "FALSE" - isProtected: "TRUE" - cn=xmpp.main,ou=permission: - cn: xmpp.main - gidNumber: "5002" - objectClass: - - posixGroup - - permissionYnh - groupPermission: - - "cn=all_users,ou=groups,dc=yunohost,dc=org" - authHeader: "FALSE" - label: "XMPP" - showTile: "FALSE" - isProtected: "TRUE" - cn=ssh.main,ou=permission: - cn: ssh.main - gidNumber: "5003" - objectClass: - - posixGroup - - permissionYnh - groupPermission: [] - authHeader: "FALSE" - label: "SSH" - showTile: "FALSE" - isProtected: "TRUE" - cn=sftp.main,ou=permission: - cn: sftp.main - gidNumber: "5004" - objectClass: - - posixGroup - - permissionYnh - groupPermission: [] - authHeader: "FALSE" - label: "SFTP" - showTile: "FALSE" - isProtected: "TRUE" diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index ada43edaa..2b386a277 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -67,79 +67,6 @@ def tools_versions(): return ynh_packages_version() -def tools_ldapinit(): - """ - YunoHost LDAP initialization - """ - - with open("/usr/share/yunohost/yunohost-config/moulinette/ldap_scheme.yml") as f: - ldap_map = yaml.load(f) - - from yunohost.utils.ldap import _get_ldap_interface - - ldap = _get_ldap_interface() - - for rdn, attr_dict in ldap_map["parents"].items(): - try: - ldap.add(rdn, attr_dict) - except Exception as e: - logger.warn( - "Error when trying to inject '%s' -> '%s' into ldap: %s" - % (rdn, attr_dict, e) - ) - - for rdn, attr_dict in ldap_map["children"].items(): - try: - ldap.add(rdn, attr_dict) - except Exception as e: - logger.warn( - "Error when trying to inject '%s' -> '%s' into ldap: %s" - % (rdn, attr_dict, e) - ) - - for rdn, attr_dict in ldap_map["depends_children"].items(): - try: - ldap.add(rdn, attr_dict) - except Exception as e: - logger.warn( - "Error when trying to inject '%s' -> '%s' into ldap: %s" - % (rdn, attr_dict, e) - ) - - admin_dict = { - "cn": ["admin"], - "uid": ["admin"], - "description": ["LDAP Administrator"], - "gidNumber": ["1007"], - "uidNumber": ["1007"], - "homeDirectory": ["/home/admin"], - "loginShell": ["/bin/bash"], - "objectClass": ["organizationalRole", "posixAccount", "simpleSecurityObject"], - "userPassword": ["yunohost"], - } - - ldap.update("cn=admin", admin_dict) - - # Force nscd to refresh cache to take admin creation into account - subprocess.call(["nscd", "-i", "passwd"]) - - # Check admin actually exists now - try: - pwd.getpwnam("admin") - except KeyError: - logger.error(m18n.n("ldap_init_failed_to_create_admin")) - raise YunohostError("installation_failed") - - try: - # Attempt to create user home folder - subprocess.check_call(["mkhomedir_helper", "admin"]) - except subprocess.CalledProcessError: - if not os.path.isdir("/home/{0}".format("admin")): - logger.warning(m18n.n("user_home_creation_failed"), exc_info=1) - - logger.success(m18n.n("ldap_initialized")) - - def tools_adminpw(new_password, check_strength=True): """ Change admin password @@ -170,7 +97,15 @@ def tools_adminpw(new_password, check_strength=True): ldap.update( "cn=admin", { - "userPassword": [new_hash], + "cn": ["admin"], + "uid": ["admin"], + "description": ["LDAP Administrator"], + "gidNumber": ["1007"], + "uidNumber": ["1007"], + "homeDirectory": ["/home/admin"], + "loginShell": ["/bin/bash"], + "objectClass": ["organizationalRole", "posixAccount", "simpleSecurityObject"], + "userPassword": [new_hash] }, ) except Exception: @@ -352,8 +287,9 @@ def tools_postinstall( domain_add(domain, dyndns) domain_main_domain(domain) - # Change LDAP admin password + # Update LDAP admin and create home dir tools_adminpw(password, check_strength=not force_password) + _create_admin_home() # Enable UPnP silently and reload firewall firewall_upnp("enable", no_refresh=True) @@ -400,6 +336,29 @@ def tools_postinstall( logger.warning(m18n.n("yunohost_postinstall_end_tip")) +def _create_admin_home(): + """ + Create admin home dir + """ + + # Force nscd to refresh cache to take admin creation into account + subprocess.call(["nscd", "-i", "passwd"]) + + # Check admin actually exists now + try: + pwd.getpwnam("admin") + except KeyError: + logger.error(m18n.n("ldap_init_failed_to_create_admin")) + raise YunohostError("installation_failed") + + try: + # Attempt to create user home folder + subprocess.check_call(["mkhomedir_helper", "admin"]) + except subprocess.CalledProcessError: + if not os.path.isdir("/home/{0}".format("admin")): + logger.warning(m18n.n("user_home_creation_failed"), exc_info=1) + + def tools_regen_conf( names=[], with_diff=False, force=False, dry_run=False, list_pending=False ): From 4266cfec6b447d46a11d5280f3b077b358d4128e Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Fri, 21 May 2021 01:09:19 +0200 Subject: [PATCH 2467/3170] [fix] Remove SPFB cause this dnsbl make false positive compared to web version ! --- data/other/dnsbl_list.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/data/other/dnsbl_list.yml b/data/other/dnsbl_list.yml index 839aeaab6..e65322f79 100644 --- a/data/other/dnsbl_list.yml +++ b/data/other/dnsbl_list.yml @@ -164,12 +164,6 @@ ipv4: false ipv6: true domain: false -- name: SPFBL.net RBL - dns_server: dnsbl.spfbl.net - website: https://spfbl.net/en/dnsbl/ - ipv4: true - ipv6: true - domain: true - name: Suomispam Blacklist dns_server: bl.suomispam.net website: http://suomispam.net/ From 5859028022bf63b949c7fcb16f022cf543115721 Mon Sep 17 00:00:00 2001 From: Paco Date: Fri, 21 May 2021 10:36:04 +0200 Subject: [PATCH 2468/3170] Uncomment push_config function --- src/yunohost/domain.py | 176 ++++++++++++++++++++--------------------- 1 file changed, 88 insertions(+), 88 deletions(-) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 6180616bf..594eea159 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -29,8 +29,8 @@ import sys import yaml import functools -# from lexicon.config import ConfigResolver -# from lexicon.client import Client +from lexicon.config import ConfigResolver +from lexicon.client import Client from moulinette import m18n, msettings, msignals from moulinette.core import MoulinetteError @@ -873,104 +873,104 @@ def domain_registrar_set(domain, registrar, args): -# def domain_push_config(domain): -# """ -# Send DNS records to the previously-configured registrar of the domain. -# """ -# # Generate the records -# if domain not in domain_list()["domains"]: -# raise YunohostValidationError("domain_name_unknown", domain=domain) +def domain_push_config(domain): + """ + Send DNS records to the previously-configured registrar of the domain. + """ + # Generate the records + if domain not in domain_list()["domains"]: + raise YunohostValidationError("domain_name_unknown", domain=domain) -# domains_settings = _get_domain_settings(domain, True) + domains_settings = _get_domain_settings(domain, True) -# dns_conf = _build_dns_conf(domains_settings) + dns_conf = _build_dns_conf(domains_settings) -# # Flatten the DNS conf -# flatten_dns_conf = [] -# for key in dns_conf: -# list_of_records = dns_conf[key] -# for record in list_of_records: -# # FIXME Lexicon does not support CAA records -# # See https://github.com/AnalogJ/lexicon/issues/282 and https://github.com/AnalogJ/lexicon/pull/371 -# # They say it's trivial to implement it! -# # And yet, it is still not done/merged -# if record["type"] != "CAA": -# # Add .domain.tdl to the name entry -# record["name"] = "{}.{}".format(record["name"], domain) -# flatten_dns_conf.append(record) + # Flatten the DNS conf + flatten_dns_conf = [] + for key in dns_conf: + list_of_records = dns_conf[key] + for record in list_of_records: + # FIXME Lexicon does not support CAA records + # See https://github.com/AnalogJ/lexicon/issues/282 and https://github.com/AnalogJ/lexicon/pull/371 + # They say it's trivial to implement it! + # And yet, it is still not done/merged + if record["type"] != "CAA": + # Add .domain.tdl to the name entry + record["name"] = "{}.{}".format(record["name"], domain) + flatten_dns_conf.append(record) -# # Get provider info -# # TODO -# provider = { -# "name": "gandi", -# "options": { -# "api_protocol": "rest", -# "auth_token": "vhcIALuRJKtoZiZyxfDYWLom" -# } -# } + # Get provider info + # TODO + provider = { + "name": "gandi", + "options": { + "api_protocol": "rest", + "auth_token": "vhcIALuRJKtoZiZyxfDYWLom" + } + } -# # Construct the base data structure to use lexicon's API. -# base_config = { -# "provider_name": provider["name"], -# "domain": domain, # domain name -# } -# base_config[provider["name"]] = provider["options"] + # Construct the base data structure to use lexicon's API. + base_config = { + "provider_name": provider["name"], + "domain": domain, # domain name + } + base_config[provider["name"]] = provider["options"] -# # Get types present in the generated records -# types = set() + # Get types present in the generated records + types = set() -# for record in flatten_dns_conf: -# types.add(record["type"]) + for record in flatten_dns_conf: + types.add(record["type"]) -# # Fetch all types present in the generated records -# distant_records = {} + # Fetch all types present in the generated records + distant_records = {} -# for key in types: -# record_config = { -# "action": "list", -# "type": key, -# } -# final_lexicon = ConfigResolver().with_dict(dict_object=base_config).with_dict(dict_object=record_config) -# # print('final_lexicon:', final_lexicon); -# client = Client(final_lexicon) -# distant_records[key] = client.execute() + for key in types: + record_config = { + "action": "list", + "type": key, + } + final_lexicon = ConfigResolver().with_dict(dict_object=base_config).with_dict(dict_object=record_config) + # print('final_lexicon:', final_lexicon); + client = Client(final_lexicon) + distant_records[key] = client.execute() -# for key in types: -# for distant_record in distant_records[key]: -# print('distant_record:', distant_record); -# for local_record in flatten_dns_conf: -# print('local_record:', local_record); + for key in types: + for distant_record in distant_records[key]: + print('distant_record:', distant_record); + for local_record in flatten_dns_conf: + print('local_record:', local_record); -# # Push the records -# for record in flatten_dns_conf: -# # For each record, first check if one record exists for the same (type, name) couple -# it_exists = False -# # TODO do not push if local and distant records are exactly the same ? -# # is_the_same_record = False + # Push the records + for record in flatten_dns_conf: + # For each record, first check if one record exists for the same (type, name) couple + it_exists = False + # TODO do not push if local and distant records are exactly the same ? + # is_the_same_record = False -# for distant_record in distant_records[record["type"]]: -# if distant_record["type"] == record["type"] and distant_record["name"] == record["name"]: -# it_exists = True -# # previous TODO -# # if distant_record["ttl"] = ... and distant_record["name"] ... -# # is_the_same_record = True + for distant_record in distant_records[record["type"]]: + if distant_record["type"] == record["type"] and distant_record["name"] == record["name"]: + it_exists = True + # previous TODO + # if distant_record["ttl"] = ... and distant_record["name"] ... + # is_the_same_record = True -# # Finally, push the new record or update the existing one -# record_config = { -# "action": "update" if it_exists else "create", # create, list, update, delete -# "type": record["type"], # specify a type for record filtering, case sensitive in some cases. -# "name": record["name"], -# "content": record["value"], -# # FIXME Delte TTL, doesn't work with Gandi. -# # See https://github.com/AnalogJ/lexicon/issues/726 (similar issue) -# # But I think there is another issue with Gandi. Or I'm misusing the API... -# # "ttl": record["ttl"], -# } -# final_lexicon = ConfigResolver().with_dict(dict_object=base_config).with_dict(dict_object=record_config) -# client = Client(final_lexicon) -# print('pushed_record:', record_config, "→", end=' ') -# results = client.execute() -# print('results:', results); -# # print("Failed" if results == False else "Ok") + # Finally, push the new record or update the existing one + record_config = { + "action": "update" if it_exists else "create", # create, list, update, delete + "type": record["type"], # specify a type for record filtering, case sensitive in some cases. + "name": record["name"], + "content": record["value"], + # FIXME Delte TTL, doesn't work with Gandi. + # See https://github.com/AnalogJ/lexicon/issues/726 (similar issue) + # But I think there is another issue with Gandi. Or I'm misusing the API... + # "ttl": record["ttl"], + } + final_lexicon = ConfigResolver().with_dict(dict_object=base_config).with_dict(dict_object=record_config) + client = Client(final_lexicon) + print('pushed_record:', record_config, "→", end=' ') + results = client.execute() + print('results:', results); + # print("Failed" if results == False else "Ok") # def domain_config_fetch(domain, key, value): From d4b40245321153d886838244bbcf045c325aa59e Mon Sep 17 00:00:00 2001 From: Paco Date: Fri, 21 May 2021 11:31:12 +0200 Subject: [PATCH 2469/3170] fix: do not delete provider options when loading them --- 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 594eea159..801d2c916 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -727,7 +727,7 @@ def _load_domain_settings(): new_domains[domain] = {} # new_domains[domain] = { "main": is_maindomain } # Set other values (default value if missing) - for setting, default in [ ("xmpp", is_maindomain), ("mail", is_maindomain), ("owned_dns_zone", default_owned_dns_zone), ("ttl", 3600) ]: + for setting, default in [ ("xmpp", is_maindomain), ("mail", is_maindomain), ("owned_dns_zone", default_owned_dns_zone), ("ttl", 3600), ("provider", False)]: if domain_in_old_domains and setting in old_domains[domain].keys(): new_domains[domain][setting] = old_domains[domain][setting] else: From 914bd1f20aa260c31f21f86f0db0f33437c59b27 Mon Sep 17 00:00:00 2001 From: Paco Date: Fri, 21 May 2021 11:32:52 +0200 Subject: [PATCH 2470/3170] connect domain_push_config to the in-file provider options --- src/yunohost/domain.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 801d2c916..0f4702ec1 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -885,6 +885,13 @@ def domain_push_config(domain): dns_conf = _build_dns_conf(domains_settings) + provider = domains_settings[domain]["provider"] + + if provider == False: + # FIXME add locales + raise YunohostValidationError("registrar_is_not_set", domain=domain) + + # Flatten the DNS conf flatten_dns_conf = [] for key in dns_conf: @@ -899,16 +906,6 @@ def domain_push_config(domain): record["name"] = "{}.{}".format(record["name"], domain) flatten_dns_conf.append(record) - # Get provider info - # TODO - provider = { - "name": "gandi", - "options": { - "api_protocol": "rest", - "auth_token": "vhcIALuRJKtoZiZyxfDYWLom" - } - } - # Construct the base data structure to use lexicon's API. base_config = { "provider_name": provider["name"], @@ -951,7 +948,7 @@ def domain_push_config(domain): for distant_record in distant_records[record["type"]]: if distant_record["type"] == record["type"] and distant_record["name"] == record["name"]: it_exists = True - # previous TODO + # see previous TODO # if distant_record["ttl"] = ... and distant_record["name"] ... # is_the_same_record = True @@ -961,7 +958,7 @@ def domain_push_config(domain): "type": record["type"], # specify a type for record filtering, case sensitive in some cases. "name": record["name"], "content": record["value"], - # FIXME Delte TTL, doesn't work with Gandi. + # FIXME Removed TTL, because it doesn't work with Gandi. # See https://github.com/AnalogJ/lexicon/issues/726 (similar issue) # But I think there is another issue with Gandi. Or I'm misusing the API... # "ttl": record["ttl"], From ea5a6d301fb5f799aa24012c5bce3032d7761180 Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Sun, 23 May 2021 20:09:48 +0200 Subject: [PATCH 2471/3170] [fix] Email on certificate renewing failed (#1227) * [fix] Email on certificate renewing failed * [enh] Use check_output instead of subprocess * Update src/yunohost/certificate.py Co-authored-by: Kayou * [mod] Use f-string for readability Co-authored-by: Kayou Co-authored-by: Alexandre Aubin --- src/yunohost/certificate.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index c01bff84e..633877d7c 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -949,11 +949,5 @@ def _name_self_CA(): def _tail(n, file_path): - stdin, stdout = os.popen2("tail -n %s '%s'" % (n, file_path)) - - stdin.close() - - lines = stdout.readlines() - stdout.close() - - return "".join(lines) + from moulinette.utils.process import check_output + return check_output(f"tail -n {n} '{file_path}'") From 99247e3d083cf121b396ea3b0eef3e5a2a2dfffa Mon Sep 17 00:00:00 2001 From: ljf Date: Sun, 23 May 2021 22:46:28 +0200 Subject: [PATCH 2472/3170] [fix] Migrations --- .../0020_ssh_sftp_permissions.py | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/yunohost/data_migrations/0020_ssh_sftp_permissions.py b/src/yunohost/data_migrations/0020_ssh_sftp_permissions.py index ba8a2b663..681d0cd9d 100644 --- a/src/yunohost/data_migrations/0020_ssh_sftp_permissions.py +++ b/src/yunohost/data_migrations/0020_ssh_sftp_permissions.py @@ -3,7 +3,6 @@ import os from moulinette import m18n from moulinette.utils.log import getActionLogger -from moulinette.utils.filesystem import read_yaml from yunohost.tools import Migration from yunohost.permission import user_permission_update, permission_sync_to_user @@ -37,20 +36,34 @@ class MyMigration(Migration): existing_perms = [perm["cn"][0] for perm in existing_perms_raw] # Add SSH and SFTP permissions - ldap_map = read_yaml( - "/usr/share/yunohost/yunohost-config/moulinette/ldap_scheme.yml" - ) - if "sftp.main" not in existing_perms: ldap.add( "cn=sftp.main,ou=permission", - ldap_map["depends_children"]["cn=sftp.main,ou=permission"], + { + "cn": "sftp.main", + "gidNumber": "5004", + "objectClass": ["posixGroup", "permissionYnh"], + "groupPermission": [], + "authHeader": "FALSE", + "label": "SFTP", + "showTile": "FALSE", + "isProtected": "TRUE", + } ) if "ssh.main" not in existing_perms: ldap.add( "cn=ssh.main,ou=permission", - ldap_map["depends_children"]["cn=ssh.main,ou=permission"], + { + "cn": "ssh.main", + "gidNumber": "5003", + "objectClass": ["posixGroup", "permissionYnh"], + "groupPermission": [], + "authHeader": "FALSE", + "label": "SSH", + "showTile": "FALSE", + "isProtected": "TRUE", + } ) # Add a bash terminal to each users From 8efa4dce6eb9e713bca9acb3702a26755a4b49ff Mon Sep 17 00:00:00 2001 From: ljf Date: Sun, 23 May 2021 22:54:00 +0200 Subject: [PATCH 2473/3170] [tmp] debug on ci --- data/hooks/conf_regen/06-slapd | 2 ++ 1 file changed, 2 insertions(+) diff --git a/data/hooks/conf_regen/06-slapd b/data/hooks/conf_regen/06-slapd index 363de81d6..12707c64b 100755 --- a/data/hooks/conf_regen/06-slapd +++ b/data/hooks/conf_regen/06-slapd @@ -46,6 +46,8 @@ EOF slapadd -n1 -l /usr/share/yunohost/yunohost-config/moulinette/ldap_default_entries.ldif 2>&1 \ | grep -v "none elapsed\|Closing DB" || true + slapcat + } _regenerate_slapd_conf() { From 9dccfa721e16a1e1e2c469155ae8e4fde5607ba8 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 23 May 2021 23:06:44 +0200 Subject: [PATCH 2474/3170] Fix ldap init using slapadd --- data/hooks/conf_regen/06-slapd | 25 +++++++++++++-------- data/other/ldap_default_entries.ldif | 33 ++++++++++++++++++++++------ src/yunohost/tools.py | 12 ++-------- 3 files changed, 44 insertions(+), 26 deletions(-) diff --git a/data/hooks/conf_regen/06-slapd b/data/hooks/conf_regen/06-slapd index 12707c64b..d2b5bd97c 100755 --- a/data/hooks/conf_regen/06-slapd +++ b/data/hooks/conf_regen/06-slapd @@ -32,22 +32,29 @@ EOF DEBIAN_FRONTEND=noninteractive dpkg-reconfigure slapd -u - # Regen conf - - _regenerate_slapd_conf - - # Enforce permissions + # Enforce permissions chown root:openldap /etc/ldap/slapd.ldif chown -R openldap:openldap /etc/ldap/schema/ usermod -aG ssl-cert openldap - # (Re-)init data according to default ldap entries - slapadd -n1 -l /usr/share/yunohost/yunohost-config/moulinette/ldap_default_entries.ldif 2>&1 \ + echo ' Initializing LDAP with Yunohost DB structure' + + rm -rf /etc/ldap/slapd.d + mkdir -p /etc/ldap/slapd.d + slapadd -F /etc/ldap/slapd.d -b cn=config -l "/etc/ldap/slapd.ldif" 2>&1 \ | grep -v "none elapsed\|Closing DB" || true + chown -R openldap: /etc/ldap/slapd.d - slapcat + rm -rf /var/lib/ldap + mkdir -p /var/lib/ldap + slapadd -F /etc/ldap/slapd.d -b dc=yunohost,dc=org -l /usr/share/yunohost/yunohost-config/moulinette/ldap_default_entries.ldif 2>&1 \ + | grep -v "none elapsed\|Closing DB" || true + chown -R openldap: /var/lib/ldap + nscd -i groups + + systemctl restart slapd } _regenerate_slapd_conf() { @@ -57,7 +64,7 @@ _regenerate_slapd_conf() { # so we use a temporary directory slapd_new.d rm -Rf /etc/ldap/slapd_new.d mkdir /etc/ldap/slapd_new.d - slapadd -n0 -l /etc/ldap/slapd.ldif -F /etc/ldap/slapd_new.d/ 2>&1 \ + slapadd -b cn=config -l /etc/ldap/slapd.ldif -F /etc/ldap/slapd_new.d/ 2>&1 \ | grep -v "none elapsed\|Closing DB" || true # Actual validation (-Q is for quiet, -u is for dry-run) slaptest -Q -u -F /etc/ldap/slapd_new.d diff --git a/data/other/ldap_default_entries.ldif b/data/other/ldap_default_entries.ldif index e76edb3d6..15a0a6bbb 100644 --- a/data/other/ldap_default_entries.ldif +++ b/data/other/ldap_default_entries.ldif @@ -1,3 +1,19 @@ +dn: dc=yunohost,dc=org +objectClass: top +objectClass: dcObject +objectClass: organization +o: yunohost.org +dc: yunohost + +dn: cn=admin,ou=sudo,dc=yunohost,dc=org +cn: admin +objectClass: sudoRole +objectClass: top +sudoCommand: ALL +sudoUser: admin +sudoOption: !authenticate +sudoHost: ALL + dn: ou=users,dc=yunohost,dc=org objectClass: organizationalUnit objectClass: top @@ -28,14 +44,17 @@ objectClass: organizationalUnit objectClass: top ou: sudo -dn: cn=admin,ou=sudo,dc=yunohost,dc=org +dn: cn=admin,dc=yunohost,dc=org +objectClass: organizationalRole +objectClass: posixAccount +objectClass: simpleSecurityObject cn: admin -sudoCommand: ALL -sudoUser: admin -objectClass: sudoRole -objectClass: top -sudoOption: !authenticate -sudoHost: ALL +uid: admin +uidNumber: 1007 +gidNumber: 1007 +homeDirectory: /home/admin +loginShell: /bin/bash +userPassword: yunohost dn: cn=admins,ou=groups,dc=yunohost,dc=org objectClass: posixGroup diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 2b386a277..281a4d048 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -97,19 +97,11 @@ def tools_adminpw(new_password, check_strength=True): ldap.update( "cn=admin", { - "cn": ["admin"], - "uid": ["admin"], - "description": ["LDAP Administrator"], - "gidNumber": ["1007"], - "uidNumber": ["1007"], - "homeDirectory": ["/home/admin"], - "loginShell": ["/bin/bash"], - "objectClass": ["organizationalRole", "posixAccount", "simpleSecurityObject"], "userPassword": [new_hash] }, ) - except Exception: - logger.error("unable to change admin password") + except Exception as e: + logger.error("unable to change admin password : %s" % e) raise YunohostError("admin_password_change_failed") else: # Write as root password From 8aa911b6e21cf406e9022fe76e90a30ba8f3dd16 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 23 May 2021 23:49:36 +0200 Subject: [PATCH 2475/3170] Misc renaming/tweaks for more sensible naming --- data/hooks/backup/05-conf_ldap | 1 - data/hooks/conf_regen/06-slapd | 20 +++++++++---------- data/hooks/restore/05-conf_ldap | 1 - .../slapd/{slapd.ldif => config.ldif} | 0 .../slapd/db_init.ldif} | 0 5 files changed, 10 insertions(+), 12 deletions(-) rename data/templates/slapd/{slapd.ldif => config.ldif} (100%) rename data/{other/ldap_default_entries.ldif => templates/slapd/db_init.ldif} (100%) diff --git a/data/hooks/backup/05-conf_ldap b/data/hooks/backup/05-conf_ldap index e3e8e455d..b28ea39ca 100644 --- a/data/hooks/backup/05-conf_ldap +++ b/data/hooks/backup/05-conf_ldap @@ -11,7 +11,6 @@ backup_dir="${1}/conf/ldap" # Backup the configuration ynh_backup "/etc/ldap/ldap.conf" "${backup_dir}/ldap.conf" -ynh_backup "/etc/ldap/slapd.ldif" "${backup_dir}/slapd.ldif" slapcat -b cn=config -l "${backup_dir}/cn=config.master.ldif" # Backup the database diff --git a/data/hooks/conf_regen/06-slapd b/data/hooks/conf_regen/06-slapd index d2b5bd97c..0f3b588d9 100755 --- a/data/hooks/conf_regen/06-slapd +++ b/data/hooks/conf_regen/06-slapd @@ -4,6 +4,9 @@ set -e tmp_backup_dir_file="/tmp/slapd-backup-dir.txt" +config="/usr/share/yunohost/templates/slapd/config.ldif" +db_init="/usr/share/yunohost/templates/slapd/db_init.ldif" + do_init_regen() { if [[ $EUID -ne 0 ]]; then echo "You must be root to run this script" 1>&2 @@ -33,7 +36,6 @@ EOF DEBIAN_FRONTEND=noninteractive dpkg-reconfigure slapd -u # Enforce permissions - chown root:openldap /etc/ldap/slapd.ldif chown -R openldap:openldap /etc/ldap/schema/ usermod -aG ssl-cert openldap @@ -42,13 +44,13 @@ EOF rm -rf /etc/ldap/slapd.d mkdir -p /etc/ldap/slapd.d - slapadd -F /etc/ldap/slapd.d -b cn=config -l "/etc/ldap/slapd.ldif" 2>&1 \ + slapadd -F /etc/ldap/slapd.d -b cn=config -l "$config" 2>&1 \ | grep -v "none elapsed\|Closing DB" || true chown -R openldap: /etc/ldap/slapd.d rm -rf /var/lib/ldap mkdir -p /var/lib/ldap - slapadd -F /etc/ldap/slapd.d -b dc=yunohost,dc=org -l /usr/share/yunohost/yunohost-config/moulinette/ldap_default_entries.ldif 2>&1 \ + slapadd -F /etc/ldap/slapd.d -b dc=yunohost,dc=org -l "$db_init" 2>&1 \ | grep -v "none elapsed\|Closing DB" || true chown -R openldap: /var/lib/ldap @@ -64,7 +66,7 @@ _regenerate_slapd_conf() { # so we use a temporary directory slapd_new.d rm -Rf /etc/ldap/slapd_new.d mkdir /etc/ldap/slapd_new.d - slapadd -b cn=config -l /etc/ldap/slapd.ldif -F /etc/ldap/slapd_new.d/ 2>&1 \ + slapadd -b cn=config -l "$config" -F /etc/ldap/slapd_new.d/ 2>&1 \ | grep -v "none elapsed\|Closing DB" || true # Actual validation (-Q is for quiet, -u is for dry-run) slaptest -Q -u -F /etc/ldap/slapd_new.d @@ -106,7 +108,7 @@ do_pre_regen() { cd /usr/share/yunohost/templates/slapd # copy configuration files - cp -a ldap.conf slapd.ldif "$ldap_dir" + cp -a ldap.conf "$ldap_dir" cp -a sudo.ldif mailserver.ldif permission.ldif "$schema_dir" mkdir -p ${pending_dir}/etc/systemd/system/slapd.service.d/ @@ -122,7 +124,6 @@ do_post_regen() { echo "Enforce permissions on ldap/slapd directories and certs ..." # penldap user should be in the ssl-cert group to let it access the certificate for TLS usermod -aG ssl-cert openldap - chown root:openldap /etc/ldap/slapd.ldif chown -R openldap:openldap /etc/ldap/schema/ chown -R openldap:openldap /etc/ldap/slapd.d/ @@ -144,13 +145,15 @@ gidNumber: 4001 memberUid: admin objectClass: posixGroup objectClass: top" + chown -R openldap: /var/lib/ldap + systemctl restart slapd nscd -i groups fi [ -z "$regen_conf_files" ] && exit 0 # regenerate LDAP config directory from slapd.conf - echo "Regenerate LDAP config directory from slapd.ldif" + echo "Regenerate LDAP config directory from config.ldif" _regenerate_slapd_conf # If there's a backup, re-import its data @@ -199,9 +202,6 @@ case "$1" in init) do_init_regen ;; - apply_config) - do_post_regen /etc/ldap/slapd.ldif - ;; *) echo "hook called with unknown argument \`$1'" >&2 exit 1 diff --git a/data/hooks/restore/05-conf_ldap b/data/hooks/restore/05-conf_ldap index 8dc511695..c2debe018 100644 --- a/data/hooks/restore/05-conf_ldap +++ b/data/hooks/restore/05-conf_ldap @@ -33,7 +33,6 @@ die() { mv /etc/ldap/slapd.d "$TMPDIR" mkdir -p /etc/ldap/slapd.d cp -a "${backup_dir}/ldap.conf" /etc/ldap/ldap.conf -cp -a "${backup_dir}/slapd.ldif" /etc/ldap/slapd.ldif # Legacy thing but we need it to force the regen-conf in case of it exist [ ! -e "${backup_dir}/slapd.conf" ] \ || cp -a "${backup_dir}/slapd.conf" /etc/ldap/slapd.conf diff --git a/data/templates/slapd/slapd.ldif b/data/templates/slapd/config.ldif similarity index 100% rename from data/templates/slapd/slapd.ldif rename to data/templates/slapd/config.ldif diff --git a/data/other/ldap_default_entries.ldif b/data/templates/slapd/db_init.ldif similarity index 100% rename from data/other/ldap_default_entries.ldif rename to data/templates/slapd/db_init.ldif From e8a625dba536d1c56b95df471fbb2d3efc21b251 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 23 May 2021 23:52:23 +0200 Subject: [PATCH 2476/3170] Unused i18n key --- locales/en.json | 1 - 1 file changed, 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 938a38e20..d1c3a255b 100644 --- a/locales/en.json +++ b/locales/en.json @@ -413,7 +413,6 @@ "log_tools_shutdown": "Shutdown your server", "log_tools_reboot": "Reboot your server", "ldap_init_failed_to_create_admin": "LDAP initialization could not create admin user", - "ldap_initialized": "LDAP initialized", "mail_alias_remove_failed": "Could not remove e-mail alias '{mail:s}'", "mail_domain_unknown": "Invalid e-mail address for domain '{domain:s}'. Please, use a domain administrated by this server.", "mail_forward_remove_failed": "Could not remove e-mail forwarding '{mail:s}'", From c516cc8eb19a296f7f8ccd456053fd2cdef1d8ff Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 24 May 2021 00:03:10 +0200 Subject: [PATCH 2477/3170] Create admin folder directly in slapd init --- data/hooks/conf_regen/06-slapd | 3 +++ locales/en.json | 1 - src/yunohost/tools.py | 24 ------------------------ 3 files changed, 3 insertions(+), 25 deletions(-) diff --git a/data/hooks/conf_regen/06-slapd b/data/hooks/conf_regen/06-slapd index 0f3b588d9..a09489fbd 100755 --- a/data/hooks/conf_regen/06-slapd +++ b/data/hooks/conf_regen/06-slapd @@ -55,6 +55,9 @@ EOF chown -R openldap: /var/lib/ldap nscd -i groups + nscd -i passwd + + mkhomedir_helper admin systemctl restart slapd } diff --git a/locales/en.json b/locales/en.json index d1c3a255b..ca3bbb274 100644 --- a/locales/en.json +++ b/locales/en.json @@ -412,7 +412,6 @@ "log_tools_upgrade": "Upgrade system packages", "log_tools_shutdown": "Shutdown your server", "log_tools_reboot": "Reboot your server", - "ldap_init_failed_to_create_admin": "LDAP initialization could not create admin user", "mail_alias_remove_failed": "Could not remove e-mail alias '{mail:s}'", "mail_domain_unknown": "Invalid e-mail address for domain '{domain:s}'. Please, use a domain administrated by this server.", "mail_forward_remove_failed": "Could not remove e-mail forwarding '{mail:s}'", diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 281a4d048..04f411741 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -281,7 +281,6 @@ def tools_postinstall( # Update LDAP admin and create home dir tools_adminpw(password, check_strength=not force_password) - _create_admin_home() # Enable UPnP silently and reload firewall firewall_upnp("enable", no_refresh=True) @@ -328,29 +327,6 @@ def tools_postinstall( logger.warning(m18n.n("yunohost_postinstall_end_tip")) -def _create_admin_home(): - """ - Create admin home dir - """ - - # Force nscd to refresh cache to take admin creation into account - subprocess.call(["nscd", "-i", "passwd"]) - - # Check admin actually exists now - try: - pwd.getpwnam("admin") - except KeyError: - logger.error(m18n.n("ldap_init_failed_to_create_admin")) - raise YunohostError("installation_failed") - - try: - # Attempt to create user home folder - subprocess.check_call(["mkhomedir_helper", "admin"]) - except subprocess.CalledProcessError: - if not os.path.isdir("/home/{0}".format("admin")): - logger.warning(m18n.n("user_home_creation_failed"), exc_info=1) - - def tools_regen_conf( names=[], with_diff=False, force=False, dry_run=False, list_pending=False ): From 9574fd4777704384651c6eae9af9df5692836521 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 24 May 2021 00:20:53 +0200 Subject: [PATCH 2478/3170] Gotta restart slapd first to prevent admin user not being known when initializing home --- data/hooks/conf_regen/06-slapd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/hooks/conf_regen/06-slapd b/data/hooks/conf_regen/06-slapd index a09489fbd..abc04307d 100755 --- a/data/hooks/conf_regen/06-slapd +++ b/data/hooks/conf_regen/06-slapd @@ -57,9 +57,9 @@ EOF nscd -i groups nscd -i passwd - mkhomedir_helper admin - systemctl restart slapd + + mkhomedir_helper admin } _regenerate_slapd_conf() { From 2d45c18961fd2046f546af58fa78ebe3bb89c668 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 24 May 2021 00:32:53 +0200 Subject: [PATCH 2479/3170] Unused imports --- src/yunohost/tools.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 04f411741..1bce1b2cb 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -25,9 +25,7 @@ """ import re import os -import yaml import subprocess -import pwd import time from importlib import import_module from packaging import version From be492b5f7f4d978f28cd1ba207f1e24115eff164 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 24 May 2021 00:33:33 +0200 Subject: [PATCH 2480/3170] Unused i18n string --- locales/en.json | 1 - 1 file changed, 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index ca3bbb274..44571af71 100644 --- a/locales/en.json +++ b/locales/en.json @@ -362,7 +362,6 @@ "hook_list_by_invalid": "This property can not be used to list hooks", "hook_name_unknown": "Unknown hook name '{name:s}'", "installation_complete": "Installation completed", - "installation_failed": "Something went wrong with the installation", "invalid_regex": "Invalid regex:'{regex:s}'", "ip6tables_unavailable": "You cannot play with ip6tables here. You are either in a container or your kernel does not support it", "iptables_unavailable": "You cannot play with iptables here. You are either in a container or your kernel does not support it", From cd2e425890e6753e84cb3273113da3ab3e4db9fa Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 24 May 2021 01:30:17 +0200 Subject: [PATCH 2481/3170] Fix tools upgrade tip --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 938a38e20..7d1c67ee5 100644 --- a/locales/en.json +++ b/locales/en.json @@ -598,7 +598,7 @@ "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 apt install --fix-broken` and/or `sudo dpkg --configure -a`.", - "tools_upgrade_at_least_one": "Please specify '--apps', or '--system'", + "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…", "tools_upgrade_cant_unhold_critical_packages": "Could not unhold critical packages…", From 170156ac2208f11b6fef97a9e0ef31568c8a7b80 Mon Sep 17 00:00:00 2001 From: ljf Date: Mon, 24 May 2021 11:50:11 +0200 Subject: [PATCH 2482/3170] [fix] Check ldap db integrity --- data/templates/slapd/db_init.ldif | 2 ++ 1 file changed, 2 insertions(+) diff --git a/data/templates/slapd/db_init.ldif b/data/templates/slapd/db_init.ldif index 15a0a6bbb..be0181dfe 100644 --- a/data/templates/slapd/db_init.ldif +++ b/data/templates/slapd/db_init.ldif @@ -68,6 +68,8 @@ objectClass: posixGroup objectClass: groupOfNamesYnh gidNumber: 4002 cn: all_users +permission: cn=mail.main,ou=permission,dc=yunohost,dc=org +permission: cn=xmpp.main,ou=permission,dc=yunohost,dc=org dn: cn=visitors,ou=groups,dc=yunohost,dc=org objectClass: posixGroup From 8829e2ccce03d3331164380f0438b50dda6bd875 Mon Sep 17 00:00:00 2001 From: ljf Date: Mon, 24 May 2021 15:30:08 +0200 Subject: [PATCH 2483/3170] [fix] Diagnosis dns query timeout --- data/templates/dnsmasq/plain/resolv.dnsmasq.conf | 5 ----- src/yunohost/utils/network.py | 10 +++++++++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/data/templates/dnsmasq/plain/resolv.dnsmasq.conf b/data/templates/dnsmasq/plain/resolv.dnsmasq.conf index ce8515054..726899421 100644 --- a/data/templates/dnsmasq/plain/resolv.dnsmasq.conf +++ b/data/templates/dnsmasq/plain/resolv.dnsmasq.conf @@ -23,11 +23,6 @@ nameserver 185.233.100.100 nameserver 2a0c:e300::100 nameserver 185.233.100.101 nameserver 2a0c:e300::101 -# (FR) gozmail / grifon -nameserver 80.67.190.200 -nameserver 2a00:5884:8218::1 -# (DE) FoeBud / Digital Courage -nameserver 85.214.20.141 # (DE) CCC Berlin nameserver 195.160.173.53 # (DE) AS250 diff --git a/src/yunohost/utils/network.py b/src/yunohost/utils/network.py index d96151fa4..e49ac3e88 100644 --- a/src/yunohost/utils/network.py +++ b/src/yunohost/utils/network.py @@ -169,7 +169,15 @@ def dig( resolver = dns.resolver.Resolver(configure=False) resolver.use_edns(0, 0, edns_size) resolver.nameservers = resolvers - resolver.timeout = timeout + # resolver.timeout is used to trigger the next DNS query on resolvers list. + # In python-dns 1.16, this value is set to 2.0. However, this means that if + # the 3 first dns resolvers in list are down, we wait 6 seconds before to + # run the DNS query to a DNS resolvers up... + # In diagnosis dnsrecords, with 10 domains this means at least 12min, too long. + resolver.timeout = 1.0 + # resolver.lifetime is the timeout for resolver.query() + # By default set it to 7 seconds to allow 6 resolvers to be unreachable. + resolver.lifetime = timeout try: answers = resolver.query(qname, rdtype) except ( From 75d2f71d1acff7243f1420cf02445fa4ac9058ca Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Mon, 24 May 2021 15:36:03 +0200 Subject: [PATCH 2484/3170] [fix] Typo in comments --- src/yunohost/utils/network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/utils/network.py b/src/yunohost/utils/network.py index e49ac3e88..e332a5a25 100644 --- a/src/yunohost/utils/network.py +++ b/src/yunohost/utils/network.py @@ -176,7 +176,7 @@ def dig( # In diagnosis dnsrecords, with 10 domains this means at least 12min, too long. resolver.timeout = 1.0 # resolver.lifetime is the timeout for resolver.query() - # By default set it to 7 seconds to allow 6 resolvers to be unreachable. + # By default set it to 5 seconds to allow 4 resolvers to be unreachable. resolver.lifetime = timeout try: answers = resolver.query(qname, rdtype) From fbdbd9e0395a7dff37b0902d1681e5b00b47cb8f Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Mon, 24 May 2021 13:49:17 +0000 Subject: [PATCH 2485/3170] [CI] Format code --- src/yunohost/app.py | 2 +- src/yunohost/certificate.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 3a34134b4..5f001c12a 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1307,7 +1307,7 @@ def app_clearaccess(apps): """ from yunohost.permission import user_permission_reset - + output = {} for app in apps: permission = user_permission_reset(app + ".main") diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 633877d7c..52d58777b 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -950,4 +950,5 @@ def _name_self_CA(): def _tail(n, file_path): from moulinette.utils.process import check_output + return check_output(f"tail -n {n} '{file_path}'") From 74054f721ac228cbb1e394fdb349809bf7a66534 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 24 May 2021 16:05:45 +0200 Subject: [PATCH 2486/3170] Manually create /home/admin with cp and chown instead of relying on mkhomedir_helper, to cover running the procedure in a chroot --- data/hooks/conf_regen/06-slapd | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/data/hooks/conf_regen/06-slapd b/data/hooks/conf_regen/06-slapd index abc04307d..16aaab9c7 100755 --- a/data/hooks/conf_regen/06-slapd +++ b/data/hooks/conf_regen/06-slapd @@ -59,7 +59,14 @@ EOF systemctl restart slapd - mkhomedir_helper admin + # We don't use mkhomedir_helper because 'admin' may not be recognized + # when this script is ran in a chroot (e.g. ISO install) + # We also refer to admin as uid 1007 for the same reason + if [ ! -d /home/admin ] + then + cp -r /etc/skel /home/admin + chown -R 1007:1007 /home/admin + fi } _regenerate_slapd_conf() { From 27300282da0e8058303455d8e4262ff3bb9cd7f6 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 24 May 2021 16:55:35 +0200 Subject: [PATCH 2487/3170] Gotta source the helper when being in the appropriate folder, because of the new 'realpath' for YNH_APP_BASEDIR --- tests/test_helpers.d/ynhtest_setup_source.sh | 1 - tests/test_helpers.sh | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/test_helpers.d/ynhtest_setup_source.sh b/tests/test_helpers.d/ynhtest_setup_source.sh index 69edf8ac9..fe61e7401 100644 --- a/tests/test_helpers.d/ynhtest_setup_source.sh +++ b/tests/test_helpers.d/ynhtest_setup_source.sh @@ -1,5 +1,4 @@ _make_dummy_src() { -echo "test coucou" if [ ! -e $HTTPSERVER_DIR/dummy.tar.gz ] then pushd "$HTTPSERVER_DIR" diff --git a/tests/test_helpers.sh b/tests/test_helpers.sh index 6a5f29bf1..55d26483e 100644 --- a/tests/test_helpers.sh +++ b/tests/test_helpers.sh @@ -43,7 +43,6 @@ VAR_WWW=$(mktemp -d)/var/www mkdir -p $VAR_WWW # ========================================================= -source /usr/share/yunohost/helpers for TEST_SUITE in $(ls test_helpers.d/*) do source $TEST_SUITE @@ -58,11 +57,12 @@ for TEST in $TESTS do log_test $TEST cd $(mktemp -d) - (app=ynhtest - YNH_APP_ID=$app - mkdir conf + (mkdir conf mkdir scripts cd scripts + source /usr/share/yunohost/helpers + app=ynhtest + YNH_APP_ID=$app set -eux $TEST ) > ./test.log 2>&1 From d7486116c31117bf4853db9e60621bc03a223c21 Mon Sep 17 00:00:00 2001 From: Weblate Date: Sun, 9 May 2021 13:11:55 +0000 Subject: [PATCH 2488/3170] Added translation using Weblate (Finnish) --- locales/fi.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 locales/fi.json diff --git a/locales/fi.json b/locales/fi.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/locales/fi.json @@ -0,0 +1 @@ +{} From dca1f47e4f1070936e712ceaad34b1b3ae5af79d Mon Sep 17 00:00:00 2001 From: Weblate Date: Mon, 10 May 2021 08:53:05 +0000 Subject: [PATCH 2489/3170] Added translation using Weblate (Galician) --- locales/gl.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 locales/gl.json diff --git a/locales/gl.json b/locales/gl.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/locales/gl.json @@ -0,0 +1 @@ +{} From f84bf183908a19e3611e1ae014c9c3e6430d5549 Mon Sep 17 00:00:00 2001 From: Christian Wehrli Date: Mon, 10 May 2021 18:25:22 +0000 Subject: [PATCH 2490/3170] Translated using Weblate (German) Currently translated at 87.7% (558 of 636 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/locales/de.json b/locales/de.json index 73efca434..da6dbd7ce 100644 --- a/locales/de.json +++ b/locales/de.json @@ -590,5 +590,12 @@ "diagnosis_sshd_config_insecure": "Die SSH-Konfiguration wurde scheinbar manuell abgeändert, und ist unsicher, weil sie keine 'AllowGroups'- oder 'AllowUsers' -Direktiven für die Begrenzung des Zugriffs durch autorisierte Benutzer enthält.", "backup_create_size_estimation": "Das Archiv wird etwa {size} Daten enthalten", "app_restore_script_failed": "Im Wiederherstellungsskript der Anwendung ist ein Fehler aufgetreten", - "app_restore_failed": "Konnte {app:s} nicht wiederherstellen: {error:s}" + "app_restore_failed": "Konnte {app:s} nicht wiederherstellen: {error:s}", + "migration_ldap_rollback_success": "System-Rollback erfolgreich.", + "migration_ldap_migration_failed_trying_to_rollback": "Migrieren war nicht möglich... Versuch, ein Rollback des Systems durchzuführen.", + "migration_ldap_backup_before_migration": "Vor der eigentlichen Migration ein Backup der LDAP-Datenbank und der Applikations-Einstellungen erstellen.", + "migration_description_0020_ssh_sftp_permissions": "Unterstützung für SSH- und SFTP-Berechtigungen hinzufügen", + "global_settings_setting_ssowat_panel_overlay_enabled": "Das SSOwat-Overlay-Panel aktivieren", + "global_settings_setting_security_ssh_port": "SSH-Port", + "diagnosis_sshd_config_inconsistent_details": "Bitte führen Sie yunohost settings set security.ssh.port -v YOUR_SSH_PORT aus, um den SSH-Port festzulegen, und prüfen Sie yunohost tools regen-conf ssh --dry-run --with-diff und yunohost tools regen-conf ssh --force um Ihre conf auf die YunoHost-Empfehlung zurückzusetzen." } From 36e2cf52eab6385f46022d5ceddec7933f17b992 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= Date: Mon, 10 May 2021 21:20:54 +0000 Subject: [PATCH 2491/3170] Translated using Weblate (French) Currently translated at 100.0% (625 of 625 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index b9984859d..60b3d5e68 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -437,9 +437,9 @@ "diagnosis_found_warnings": "Trouvé {warnings} objet(s) pouvant être amélioré(s) pour {category}.", "diagnosis_everything_ok": "Tout semble bien pour {category} !", "diagnosis_failed": "Échec de la récupération du résultat du diagnostic pour la catégorie '{category}' : {error}", - "diagnosis_ip_connected_ipv4": "Le serveur est connecté à Internet en IPv4!", + "diagnosis_ip_connected_ipv4": "Le serveur est connecté à Internet en IPv4 !", "diagnosis_ip_no_ipv4": "Le serveur ne dispose pas d’une adresse IPv4.", - "diagnosis_ip_connected_ipv6": "Le serveur est connecté à Internet en IPv6!", + "diagnosis_ip_connected_ipv6": "Le serveur est connecté à Internet en IPv6 !", "diagnosis_ip_no_ipv6": "Le serveur ne dispose pas d’une adresse IPv6.", "diagnosis_ip_dnsresolution_working": "La résolution de nom de domaine fonctionne !", "diagnosis_ip_broken_dnsresolution": "La résolution du nom de domaine semble interrompue pour une raison quelconque … Un pare-feu bloque-t-il les requêtes DNS ?", @@ -526,7 +526,7 @@ "diagnosis_ip_local": "IP locale : {local}", "diagnosis_dns_point_to_doc": "Veuillez consulter la documentation sur https://yunohost.org/dns_config si vous avez besoin d’aide pour configurer les enregistrements DNS.", "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Certains fournisseurs ne vous laisseront pas débloquer le port sortant 25 parce qu’ils ne se soucient pas de la neutralité du Net.
- Certains d’entre eux offrent l’alternative d'utiliser un serveur de messagerie relai bien que cela implique que le relai sera en mesure d’espionner votre trafic de messagerie.
- Une alternative respectueuse de la vie privée consiste à utiliser un VPN *avec une IP publique dédiée* pour contourner ce type de limites. Voir https://yunohost.org/#/vpn_advantage
- Vous pouvez également envisager de passer à un fournisseur plus respectueux de la neutralité du net", - "diagnosis_mail_ehlo_ok": "Le serveur de messagerie SMTP est accessible de l'extérieur et peut donc recevoir des courriels!", + "diagnosis_mail_ehlo_ok": "Le serveur de messagerie SMTP est accessible de l'extérieur et peut donc recevoir des courriels !", "diagnosis_mail_ehlo_unreachable": "Le serveur de messagerie SMTP est inaccessible de l’extérieur en IPv{ipversion}. Il ne pourra pas recevoir des courriels.", "diagnosis_mail_ehlo_unreachable_details": "Impossible d'ouvrir une connexion sur le port 25 à votre serveur en IPv{ipversion}. Il semble inaccessible.
1. La cause la plus courante de ce problème est que le port 25 n'est pas correctement redirigé vers votre serveur.
2. Vous devez également vous assurer que le service postfix est en cours d'exécution.
3. Sur les configurations plus complexes: assurez-vous qu'aucun pare-feu ou proxy inversé n'interfère.", "diagnosis_mail_ehlo_wrong_details": "Le EHLO reçu par le serveur de diagnostique distant en IPv{ipversion} est différent du domaine de votre serveur.
EHLO reçu: {wrong_ehlo}
Attendu : {right_ehlo}
La cause la plus courante ce problème est que le port 25 n’est pas correctement redirigé vers votre serveur . Vous pouvez également vous assurer qu’aucun pare-feu ou proxy inversé n’interfère.", From 9df6e84fd438ae0c5c076cd8137a8ed7e270711e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M?= Date: Tue, 11 May 2021 04:38:31 +0000 Subject: [PATCH 2492/3170] Translated using Weblate (Galician) Currently translated at 0.1% (1 of 636 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/gl/ --- locales/gl.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/locales/gl.json b/locales/gl.json index 0967ef424..a2f95cbed 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -1 +1,3 @@ -{} +{ + "password_too_simple_1": "O contrasinal ten que ter 8 caracteres como mínimo" +} From 72e8fd2bedc76ad22810e289ad80d318420d0850 Mon Sep 17 00:00:00 2001 From: xaloc33 Date: Thu, 13 May 2021 18:58:55 +0000 Subject: [PATCH 2493/3170] Translated using Weblate (Catalan) Currently translated at 97.9% (623 of 636 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ca/ --- locales/ca.json | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/locales/ca.json b/locales/ca.json index 7823c8c02..189053d94 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -503,7 +503,7 @@ "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.", - "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_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 --human-readable» 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}", @@ -621,5 +621,9 @@ "diagnosis_basesystem_hardware_model": "El model del servidor és {model}", "postinstall_low_rootfsspace": "El sistema de fitxers arrel té un total de menys de 10 GB d'espai, el que es preocupant! És molt probable que us quedeu sense espai ràpidament! Es recomana tenir un mínim de 16 GB per al sistema de fitxers arrel. Si voleu instal·lar YunoHost tot i aquest avís, torneu a executar la postinstal·lació amb --force-diskspace", "diagnosis_rootfstotalspace_critical": "El sistema de fitxers arrel només té {space} en total i és preocupant! És molt probable que us quedeu sense espai ràpidament! Es recomanar tenir un mínim de 16 GB per al sistema de fitxers arrel.", - "diagnosis_rootfstotalspace_warning": "El sistema de fitxers arrel només té {space} en total. Això no hauria de causar cap problema, però haureu de parar atenció ja que us podrieu quedar sense espai ràpidament… Es recomanar tenir un mínim de 16 GB per al sistema de fitxers arrel." -} \ No newline at end of file + "diagnosis_rootfstotalspace_warning": "El sistema de fitxers arrel només té {space} en total. Això no hauria de causar cap problema, però haureu de parar atenció ja que us podrieu quedar sense espai ràpidament… Es recomanar tenir un mínim de 16 GB per al sistema de fitxers arrel.", + "diagnosis_sshd_config_inconsistent": "Sembla que el port SSH s'ha modificat manualment a /etc/ssh/sshd_config. Des de YunoHost 4.2, hi ha un nou paràmetre global «security.ssh.port» per evitar modificar manualment la configuració.", + "diagnosis_sshd_config_insecure": "Sembla que la configuració SSH s'ha modificat manualment, i no es segura ha que no conté la directiva «AllowGroups» o «AllowUsers» per limitar l'accés a usuaris autoritzats.", + "backup_create_size_estimation": "L'arxiu tindrà aproximadament {size} de dades.", + "app_restore_script_failed": "S'ha produït un error en el script de restauració de l'aplicació" +} From dbaccb3c2d23fbdb6b9aac7047520e4662bf20f1 Mon Sep 17 00:00:00 2001 From: Radek S Date: Fri, 14 May 2021 20:37:58 +0000 Subject: [PATCH 2494/3170] Translated using Weblate (Czech) Currently translated at 2.9% (19 of 636 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/cs/ --- locales/cs.json | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/locales/cs.json b/locales/cs.json index 76b464260..9f4bd8197 100644 --- a/locales/cs.json +++ b/locales/cs.json @@ -3,11 +3,19 @@ "app_already_installed": "{app:s} je již nainstalován/a", "already_up_to_date": "Neprovedena žádná akce. Vše je již aktuální.", "admin_password_too_long": "Zvolte prosím heslo kratší než 127 znaků", - "admin_password_changed": "Heslo správce bylo změněno", + "admin_password_changed": "Administrační heslo bylo změněno", "admin_password_change_failed": "Nebylo možné změnit heslo", - "admin_password": "Heslo správce", - "additional_urls_already_removed": "Dotatečný odkaz '{url:s}' byl již odebrán u oprávnění '{permission:s}'", - "additional_urls_already_added": "Dotatečný odkaz '{url:s}' byl již přidán v dodatečných odkazech pro oprávnění '{permission:s}'", + "admin_password": "Administrační heslo", + "additional_urls_already_removed": "Další URL '{url:s}' již bylo odebráno u oprávnění '{permission:s}'", + "additional_urls_already_added": "Další URL '{url:s}' již bylo přidáno pro oprávnění '{permission:s}'", "action_invalid": "Nesprávné akce '{action:s}'", - "aborting": "Přerušení." -} \ No newline at end of file + "aborting": "Zrušeno.", + "app_change_url_identical_domains": "Stará a nová doména/url_cesta jsou totožné ('{domain:s}{path:s}'), nebudou provedeny žádné změny.", + "app_change_url_failed_nginx_reload": "Nepodařilo se znovunačís NGINX. Následuje výpis příkazu 'nginx -t':\n{nginx_errors:s}", + "app_argument_invalid": "Vyberte správnou hodnotu pro argument '{name:s}': {error:s}", + "app_argument_choice_invalid": "Vyberte jednu z možností '{choices:s}' pro argument'{name:s}'", + "app_already_up_to_date": "{app:s} aplikace je/jsou aktuální", + "app_already_installed_cant_change_url": "Tato aplikace je již nainstalována. URL nemůže být touto akcí změněna. Zkontrolujte `app changeurl` pokud je dostupné.", + "app_action_cannot_be_ran_because_required_services_down": "Pro běh této akce by měli být spuštěné následující služby: {services}. Zkuste je zrestartovat, případně zjistěte, proč neběží.", + "app_action_broke_system": "Zdá se, že tato akce rozbila následující důležité služby: {service}" +} From 1bc7760ebb83b1548cf1012ec7b94cbf318ee32a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?yahoo=EF=BD=9E=EF=BD=9E?= Date: Tue, 18 May 2021 15:34:30 +0000 Subject: [PATCH 2495/3170] Translated using Weblate (Chinese (Simplified)) Currently translated at 25.3% (161 of 636 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/zh_Hans/ --- locales/zh_Hans.json | 153 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 150 insertions(+), 3 deletions(-) diff --git a/locales/zh_Hans.json b/locales/zh_Hans.json index c1cd756b7..d2beb03ad 100644 --- a/locales/zh_Hans.json +++ b/locales/zh_Hans.json @@ -9,8 +9,155 @@ "app_unknown": "未知应用", "admin_password_changed": "管理密码已更改", "aborting": "正在放弃。", - "admin_password": "管理密码", + "admin_password": "管理员密码", "app_start_restore": "正在恢复{app}……", "action_invalid": "无效操作 '{action:s}'", - "ask_lastname": "姓" -} \ No newline at end of file + "ask_lastname": "姓", + "diagnosis_everything_ok": "{category}一切看起来不错!", + "diagnosis_found_warnings": "找到{warnings}项,可能需要{category}进行改进。", + "diagnosis_found_errors_and_warnings": "发现与{category}相关的{errors}个重要问题(和{warnings}警告)!", + "diagnosis_found_errors": "发现与{category}相关的{errors}个重要问题!", + "diagnosis_ignored_issues": "(+ {nb_ignored} 个被忽略的问题)", + "diagnosis_cant_run_because_of_dep": "存在与{dep}相关的重要问题时,无法对{category}进行诊断。", + "diagnosis_cache_still_valid": "(高速缓存对于{category}诊断仍然有效。暂时不会对其进行重新诊断!)", + "diagnosis_failed_for_category": "诊断类别 '{category}'失败: {error}", + "diagnosis_display_tip": "要查看发现的问题,您可以转到Webadmin的“诊断”部分,或从命令行运行'yunohost diagnosis show --issues --human-readable'。", + "diagnosis_package_installed_from_sury": "一些系统软件包应降级", + "diagnosis_backports_in_sources_list": "看起来apt(程序包管理器)已配置为使用backports存储库。 除非您真的知道自己在做什么,否则我们强烈建议您不要从backports安装软件包,因为这很可能在您的系统上造成不稳定或冲突。", + "diagnosis_basesystem_ynh_inconsistent_versions": "您运行的YunoHost软件包版本不一致,很可能是由于升级失败或部分升级造成的。", + "diagnosis_basesystem_ynh_main_version": "服务器正在运行YunoHost {main_version} ({repo})", + "diagnosis_basesystem_ynh_single_version": "{package} 版本: {version} ({repo})", + "diagnosis_basesystem_kernel": "服务器正在运行Linux kernel {kernel_version}", + "diagnosis_basesystem_host": "服务器正在运行Debian {debian_version}", + "diagnosis_basesystem_hardware_model": "服务器型号为 {model}", + "diagnosis_basesystem_hardware": "服务器硬件架构为{virt} {arch}", + "custom_app_url_required": "您必须提供URL才能升级自定义应用 {app:s}", + "confirm_app_install_thirdparty": "危险! 该应用程序不是Yunohost的应用程序目录的一部分。 安装第三方应用程序可能会损害系统的完整性和安全性。 除非您知道自己在做什么,否则可能不应该安装它, 如果此应用无法运行或无法正常使用系统,将不会提供任何支持。如果您仍然愿意承担此风险,请输入'{answers:s}'", + "confirm_app_install_danger": "危险! 已知此应用仍处于实验阶段(如果未明确无法正常运行)! 除非您知道自己在做什么,否则可能不应该安装它。 如果此应用无法运行或无法正常使用系统,将不会提供任何支持。如果您仍然愿意承担此风险,请输入'{answers:s}'", + "confirm_app_install_warning": "警告:此应用程序可能可以运行,但未与YunoHost很好地集成。某些功能(例如单点登录和备份/还原)可能不可用, 仍要安装吗? [{answers:s}] ", + "certmanager_unable_to_parse_self_CA_name": "无法解析自签名授权的名称 (file: {file:s})", + "certmanager_self_ca_conf_file_not_found": "找不到用于自签名授权的配置文件(file: {file:s})", + "certmanager_no_cert_file": "无法读取域{domain:s}的证书文件(file: {file:s})", + "certmanager_hit_rate_limit": "最近已经为此域{domain:s}颁发了太多的证书。请稍后再试。有关更多详细信息,请参见https://letsencrypt.org/docs/rate-limits/", + "certmanager_warning_subdomain_dns_record": "子域'{subdomain:s}' 不能解析为与 '{domain:s}'相同的IP地址, 在修复此问题并重新生成证书之前,某些功能将不可用。", + "certmanager_domain_http_not_working": "域 {domain:s}似乎无法通过HTTP访问。请检查诊断中的“网络”类别以获取更多信息。(如果您知道自己在做什么,请使用“ --no-checks”关闭这些检查。)", + "certmanager_domain_dns_ip_differs_from_public_ip": "域'{domain:s}' 的DNS记录与此服务器的IP不同。请检查诊断中的“ DNS记录”(基本)类别,以获取更多信息。 如果您最近修改了A记录,请等待它传播(某些DNS传播检查器可在线获得)。 (如果您知道自己在做什么,请使用“ --no-checks”关闭这些检查。)", + "certmanager_domain_cert_not_selfsigned": "域 {domain:s} 的证书不是自签名的, 您确定要更换它吗?(使用“ --force”这样做。)", + "certmanager_domain_not_diagnosed_yet": "尚无域{domain} 的诊断结果。请在诊断部分中针对“ DNS记录”和“ Web”类别重新运行诊断,以检查该域是否已准备好安装“Let's Encrypt”证书。(或者,如果您知道自己在做什么,请使用“ --no-checks”关闭这些检查。)", + "certmanager_certificate_fetching_or_enabling_failed": "尝试将新证书用于 {domain:s}无效...", + "certmanager_cert_signing_failed": "无法签署新证书", + "certmanager_cert_install_success_selfsigned": "为域 '{domain:s}'安装了自签名证书", + "certmanager_cert_renew_success": "为域 '{domain:s}'续订“Let's Encrypt”证书", + "certmanager_cert_install_success": "为域'{domain:s}'安装“Let's Encrypt”证书", + "certmanager_cannot_read_cert": "尝试为域 {domain:s}(file: {file:s})打开当前证书时发生错误,原因: {reason:s}", + "certmanager_attempt_to_replace_valid_cert": "您正在尝试覆盖域{domain:s}的有效证书!(使用--force绕过)", + "certmanager_attempt_to_renew_valid_cert": "域'{domain:s}'的证书不会过期!(如果知道自己在做什么,则可以使用--force)", + "certmanager_attempt_to_renew_nonLE_cert": "“Let's Encrypt”未颁发域'{domain:s}'的证书,无法自动续订!", + "certmanager_acme_not_configured_for_domain": "目前无法针对{domain}运行ACME挑战,因为其nginx conf缺少相应的代码段...请使用“yunohost tools regen-conf nginx --dry-run --with-diff”确保您的nginx配置是最新的。", + "backup_with_no_restore_script_for_app": "{app:s} 没有还原脚本,您将无法自动还原该应用程序的备份。", + "backup_with_no_backup_script_for_app": "应用'{app:s}'没有备份脚本。无视。", + "backup_unable_to_organize_files": "无法使用快速方法来组织档案中的文件", + "backup_system_part_failed": "无法备份'{part:s}'系统部分", + "backup_running_hooks": "正在运行备份挂钩...", + "backup_permission": "{app:s}的备份权限", + "backup_output_symlink_dir_broken": "您的存档目录'{path:s}' 是断开的符号链接。 也许您忘记了重新安装/装入或插入它指向的存储介质。", + "backup_output_directory_required": "您必须提供备份的输出目录", + "backup_output_directory_not_empty": "您应该选择一个空的输出目录", + "backup_output_directory_forbidden": "选择一个不同的输出目录。无法在/bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var或/home/yunohost.backup/archives子文件夹中创建备份", + "backup_nothings_done": "没什么可保存的", + "backup_no_uncompress_archive_dir": "没有这样的未压缩存档目录", + "backup_mount_archive_for_restore": "正在准备存档以进行恢复...", + "backup_method_tar_finished": "TAR备份存档已创建", + "backup_method_custom_finished": "自定义备份方法'{method:s}' 已完成", + "backup_method_copy_finished": "备份副本已完成", + "backup_hook_unknown": "备用挂钩'{hook:s}'未知", + "backup_deleted": "备份已删除", + "backup_delete_error": "无法删除'{path:s}'", + "backup_custom_mount_error": "自定义备份方法无法通过“挂载”步骤", + "backup_custom_backup_error": "自定义备份方法无法通过“备份”步骤", + "backup_csv_creation_failed": "无法创建还原所需的CSV文件", + "backup_csv_addition_failed": "无法将文件添加到CSV文件中进行备份", + "backup_creation_failed": "无法创建备份存档", + "backup_create_size_estimation": "归档文件将包含约{size}个数据。", + "backup_couldnt_bind": "无法将 {src:s} 绑定到{dest:s}.", + "backup_copying_to_organize_the_archive": "复制{size:s} MB来整理档案", + "backup_cleaning_failed": "无法清理临时备份文件夹", + "backup_cant_mount_uncompress_archive": "无法将未压缩的归档文件挂载为写保护", + "backup_ask_for_copying_if_needed": "您是否要临时使用{size:s} MB进行备份?(由于无法使用更有效的方法准备某些文件,因此使用这种方式。)", + "backup_archive_writing_error": "无法将要备份的文件'{source:s}'(在归档文'{dest:s}'中命名)添加到压缩归档文件 '{archive:s}'s}”中", + "backup_archive_system_part_not_available": "该备份中系统部分'{part:s}'不可用", + "backup_archive_corrupted": "备份存档'{archive}' 似乎已损坏 : {error}", + "backup_archive_cant_retrieve_info_json": "无法加载档案'{archive}'的信息...无法检索到info.json(或者它不是有效的json)。", + "backup_archive_open_failed": "无法打开备份档案", + "backup_archive_name_unknown": "未知的本地备份档案名为'{name:s}'", + "backup_archive_name_exists": "具有该名称的备份存档已经存在。", + "backup_archive_broken_link": "无法访问备份存档(指向{path:s}的链接断开)", + "backup_archive_app_not_found": "在备份档案中找不到 {app:s}", + "backup_applying_method_tar": "创建备份TAR存档...", + "backup_applying_method_custom": "调用自定义备份方法'{method:s}'...", + "backup_applying_method_copy": "正在将所有文件复制到备份...", + "backup_app_failed": "无法备份{app:s}", + "backup_actually_backuping": "根据收集的文件创建备份档案...", + "backup_abstract_method": "此备份方法尚未实现", + "ask_password": "密码", + "ask_new_path": "新路径", + "ask_new_domain": "新域名", + "ask_new_admin_password": "新的管理密码", + "ask_main_domain": "主域", + "ask_firstname": "名", + "ask_user_domain": "用户的电子邮件地址和XMPP帐户要使用的域", + "apps_catalog_update_success": "应用程序目录已更新!", + "apps_catalog_obsolete_cache": "应用程序目录缓存为空或已过时。", + "apps_catalog_failed_to_download": "无法下载{apps_catalog} 应用目录: {error}", + "apps_catalog_updating": "正在更新应用程序目录…", + "apps_catalog_init_success": "应用目录系统已初始化!", + "apps_already_up_to_date": "所有应用程序都是最新的", + "app_packaging_format_not_supported": "无法安装此应用,因为您的YunoHost版本不支持其打包格式。 您应该考虑升级系统。", + "app_upgraded": "{app:s}upgraded", + "app_upgrade_some_app_failed": "某些应用无法升级", + "app_upgrade_script_failed": "应用升级脚本内部发生错误", + "app_upgrade_app_name": "现在升级{app} ...", + "app_upgrade_several_apps": "以下应用将被升级: {apps}", + "app_unsupported_remote_type": "应用程序使用的远程类型不受支持", + "app_start_backup": "正在收集要备份的文件,用于{app} ...", + "app_start_install": "{app}安装中...", + "app_sources_fetch_failed": "无法获取源文件,URL是否正确?", + "app_restore_script_failed": "应用还原脚本内部发生错误", + "app_restore_failed": "无法还原 {app:s}: {error:s}", + "app_remove_after_failed_install": "安装失败后删除应用程序...", + "app_requirements_unmeet": "{app}不符合要求,软件包{pkgname}({version}) 必须为{spec}", + "app_requirements_checking": "正在检查{app}所需的软件包...", + "app_removed": "{app:s} 已删除", + "app_not_properly_removed": "{app:s} 未正确删除", + "app_not_correctly_installed": "{app:s} 似乎安装不正确", + "app_not_upgraded": "应用程序'{failed_app}'升级失败,因此以下应用程序的升级已被取消: {apps}", + "app_manifest_install_ask_is_public": "该应用是否应该向匿名访问者公开?", + "app_manifest_install_ask_admin": "选择此应用的管理员用户", + "app_manifest_install_ask_password": "选择此应用的管理密码", + "additional_urls_already_removed": "权限'{permission:s}'的其他URL中已经删除了附加URL'{url:s}'", + "app_manifest_install_ask_path": "选择安装此应用的路径", + "app_manifest_install_ask_domain": "选择应安装此应用程序的域", + "app_manifest_invalid": "应用清单错误: {error}", + "app_location_unavailable": "该URL不可用,或与已安装的应用冲突:\n{apps:s}", + "app_label_deprecated": "不推荐使用此命令!请使用新命令 'yunohost user permission update'来管理应用标签。", + "app_make_default_location_already_used": "无法将'{app}' 设置为域上的默认应用,'{other_app}'已在使用'{domain}'", + "app_install_script_failed": "应用安装脚本内发生错误", + "app_install_failed": "无法安装 {app}: {error}", + "app_install_files_invalid": "这些文件无法安装", + "additional_urls_already_added": "附加URL '{url:s}' 已添加到权限'{permission:s}'的附加URL中", + "app_full_domain_unavailable": "抱歉,此应用必须安装在其自己的域中,但其他应用已安装在域“ {domain}”上。 您可以改用专用于此应用程序的子域。", + "app_extraction_failed": "无法解压缩安装文件", + "app_change_url_success": "{app:s} URL现在为 {domain:s}{path:s}", + "app_change_url_no_script": "应用程序'{app_name:s}'尚不支持URL修改. 也许您应该升级它。", + "app_change_url_identical_domains": "新旧domain / url_path是相同的('{domain:s}{path:s}'),无需执行任何操作。", + "app_change_url_failed_nginx_reload": "无法重新加载NGINX. 这是'nginx -t'的输出:\n{nginx_errors:s}", + "app_argument_required": "参数'{name:s}'为必填项", + "app_argument_password_no_default": "解析密码参数'{name}'时出错:出于安全原因,密码参数不能具有默认值", + "app_argument_invalid": "为参数'{name:s}'选择一个有效值: {error:s}", + "app_argument_choice_invalid": "对参数'{name:s}'使用以下选项之一'{choices:s}'", + "app_already_up_to_date": "{app:s} 已经是最新的", + "app_already_installed": "{app:s}已安装", + "app_action_broke_system": "该操作似乎破坏了以下重要服务:{services}", + "app_action_cannot_be_ran_because_required_services_down": "这些必需的服务应该正在运行以执行以下操作:{services},尝试重新启动它们以继续操作(考虑调查为什么它们出现故障)。", + "already_up_to_date": "无事可做。一切都已经是最新的了。" +} From c9a2a9c9b773240411792f79a604e56743f5fb02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?yahoo=EF=BD=9E=EF=BD=9E?= Date: Wed, 19 May 2021 15:17:31 +0000 Subject: [PATCH 2496/3170] Translated using Weblate (Chinese (Simplified)) Currently translated at 73.8% (470 of 636 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/zh_Hans/ --- locales/zh_Hans.json | 311 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 310 insertions(+), 1 deletion(-) diff --git a/locales/zh_Hans.json b/locales/zh_Hans.json index d2beb03ad..526e3fde2 100644 --- a/locales/zh_Hans.json +++ b/locales/zh_Hans.json @@ -159,5 +159,314 @@ "app_already_installed": "{app:s}已安装", "app_action_broke_system": "该操作似乎破坏了以下重要服务:{services}", "app_action_cannot_be_ran_because_required_services_down": "这些必需的服务应该正在运行以执行以下操作:{services},尝试重新启动它们以继续操作(考虑调查为什么它们出现故障)。", - "already_up_to_date": "无事可做。一切都已经是最新的了。" + "already_up_to_date": "无事可做。一切都已经是最新的了。", + "postinstall_low_rootfsspace": "根文件系统的总空间小于10 GB,这非常令人担忧!您可能很快就会用完磁盘空间!建议根文件系统至少有16GB, 如果尽管出现此警告仍要安装YunoHost,请使用--force-diskspace重新运行postinstall", + "port_already_opened": "{ip_version:s}个连接的端口 {port:d} 已打开", + "port_already_closed": "{ip_version:s}个连接的端口 {port:d} 已关闭", + "permission_require_account": "权限{permission}只对有账户的用户有意义,因此不能对访客启用。", + "permission_protected": "权限{permission}是受保护的。你不能向/从这个权限添加或删除访问者组。", + "permission_updated": "权限 '{permission:s}' 已更新", + "permission_update_failed": "无法更新权限 '{permission}': {error}", + "permission_not_found": "找不到权限'{permission:s}'", + "permission_deletion_failed": "无法删除权限 '{permission}': {error}", + "permission_deleted": "权限'{permission:s}' 已删除", + "permission_cant_add_to_all_users": "权限{permission}不能添加到所有用户。", + "regenconf_file_copy_failed": "无法将新的配置文件'{new}' 复制到'{conf}'", + "regenconf_file_backed_up": "将配置文件 '{conf}' 备份到 '{backup}'", + "regenconf_failed": "无法重新生成类别的配置: {categories}", + "regenconf_dry_pending_applying": "正在检查将应用于类别 '{category}'的待定配置…", + "regenconf_would_be_updated": "配置已更新为类别 '{category}'", + "regenconf_updated": "配置已针对'{category}'进行了更新", + "regenconf_now_managed_by_yunohost": "现在,配置文件'{conf}'由YunoHost(类别{category})管理。", + "regenconf_file_updated": "配置文件'{conf}' 已更新", + "regenconf_file_removed": "配置文件 '{conf}'已删除", + "regenconf_file_remove_failed": "无法删除配置文件 '{conf}'", + "regenconf_file_manually_removed": "配置文件'{conf}' 已手动删除,因此不会创建", + "regenconf_file_manually_modified": "配置文件'{conf}' 已被手动修改,不会被更新", + "regenconf_need_to_explicitly_specify_ssh": "ssh配置已被手动修改,但是您需要使用--force明确指定类别“ ssh”才能实际应用更改。", + "restore_nothings_done": "什么都没有恢复", + "restore_may_be_not_enough_disk_space": "您的系统似乎没有足够的空间(可用空间: {free_space:d} B,所需空间: {needed_space:d} B,安全系数: {margin:d} B)", + "restore_hook_unavailable": "'{part:s}'的恢复脚本在您的系统上和归档文件中均不可用", + "restore_failed": "无法还原系统", + "restore_extracting": "正在从存档中提取所需文件…", + "restore_confirm_yunohost_installed": "您真的要还原已经安装的系统吗? [{answers:s}]", + "restore_complete": "恢复完成", + "restore_cleaning_failed": "无法清理临时还原目录", + "restore_backup_too_old": "无法还原此备份存档,因为它来自过旧的YunoHost版本。", + "restore_already_installed_apps": "以下应用已安装,因此无法还原: {apps}", + "restore_already_installed_app": "已安装ID为'{app:s}' 的应用", + "regex_with_only_domain": "您不能将正则表达式用于域,而只能用于路径", + "regex_incompatible_with_tile": "/!\\ 打包者!权限“ {permission}”的show_tile设置为“ true”,因此您不能将正则表达式URL定义为主URL", + "service_cmd_exec_failed": "无法执行命令'{command:s}'", + "service_already_stopped": "服务'{service:s}'已被停止", + "service_already_started": "服务'{service:s}' 已在运行", + "service_added": "服务 '{service:s}'已添加", + "service_add_failed": "无法添加服务 '{service:s}'", + "server_reboot_confirm": "服务器会立即重启,确定吗? [{answers:s}]", + "server_reboot": "服务器将重新启动", + "server_shutdown_confirm": "服务器会立即关闭,确定吗?[{answers:s}]", + "server_shutdown": "服务器将关闭", + "root_password_replaced_by_admin_password": "您的root密码已替换为您的管理员密码。", + "root_password_desynchronized": "管理员密码已更改,但是YunoHost无法将此密码传播到root密码!", + "restore_system_part_failed": "无法还原 '{part:s}'系统部分", + "restore_running_hooks": "运行修复挂钩…", + "restore_running_app_script": "正在还原应用'{app:s}'…", + "restore_removing_tmp_dir_failed": "无法删除旧的临时目录", + "service_description_yunohost-firewall": "管理打开和关闭服务的连接端口", + "service_description_yunohost-api": "管理YunoHost Web界面与系统之间的交互", + "service_description_ssh": "允许您通过终端(SSH协议)远程连接到服务器", + "service_description_slapd": "存储用户、域名和相关信息", + "service_description_rspamd": "过滤垃圾邮件和其他与电子邮件相关的功能", + "service_description_redis-server": "用于快速数据访问,任务队列和程序之间通信的专用数据库", + "service_description_postfix": "用于发送和接收电子邮件", + "service_description_php7.3-fpm": "使用NGINX运行用PHP编写的应用程序", + "service_description_nginx": "为你的服务器上托管的所有网站提供服务或访问", + "service_description_mysql": "存储应用程序数据(SQL数据库)", + "service_description_metronome": "管理XMPP即时消息传递帐户", + "service_description_fail2ban": "防止来自互联网的暴力攻击和其他类型的攻击", + "service_description_dovecot": "允许电子邮件客户端访问/获取电子邮件(通过IMAP和POP3)", + "service_description_dnsmasq": "处理域名解析(DNS)", + "service_description_avahi-daemon": "允许您使用本地网络中的“ yunohost.local”访问服务器", + "service_started": "服务 '{service:s}' 已启动", + "service_start_failed": "无法启动服务 '{service:s}'\n\n最近的服务日志:{logs:s}", + "service_reloaded_or_restarted": "服务'{service:s}'已重新加载或重新启动", + "service_reload_or_restart_failed": "无法重新加载或重新启动服务'{service:s}'\n\n最近的服务日志:{logs:s}", + "service_restarted": "服务'{service:s}' 已重新启动", + "service_restart_failed": "无法重新启动服务 '{service:s}'\n\n最近的服务日志:{logs:s}", + "service_reloaded": "服务 '{service:s}' 已重新加载", + "service_reload_failed": "无法重新加载服务'{service:s}'\n\n最近的服务日志:{logs:s}", + "service_removed": "服务 '{service:s}' 已删除", + "service_remove_failed": "无法删除服务'{service:s}'", + "service_regen_conf_is_deprecated": "不建议使用'yunohost service regen-conf' ! 请改用'yunohost tools regen-conf'。", + "service_enabled": "现在,服务'{service:s}' 将在系统引导过程中自动启动。", + "service_enable_failed": "无法使服务 '{service:s}'在启动时自动启动。\n\n最近的服务日志:{logs:s}", + "service_disabled": "系统启动时,服务 '{service:s}' 将不再启动。", + "service_disable_failed": "服务'{service:s}'在启动时无法启动。\n\n最近的服务日志:{logs:s}", + "tools_upgrade_regular_packages": "现在正在升级 'regular' (与yunohost无关)的软件包…", + "tools_upgrade_cant_unhold_critical_packages": "无法解压关键软件包…", + "tools_upgrade_cant_hold_critical_packages": "无法保存重要软件包…", + "tools_upgrade_cant_both": "无法同时升级系统和应用程序", + "tools_upgrade_at_least_one": "请指定'--apps', 或 '--system'", + "this_action_broke_dpkg": "此操作破坏了dpkg / APT(系统软件包管理器)...您可以尝试通过SSH连接并运行`sudo apt install --fix-broken`和/或`sudo dpkg --configure -a`来解决此问题。", + "system_username_exists": "用户名已存在于系统用户列表中", + "system_upgraded": "系统升级", + "ssowat_conf_updated": "SSOwat配置已更新", + "ssowat_conf_generated": "SSOwat配置已重新生成", + "show_tile_cant_be_enabled_for_regex": "你不能启用'show_tile',因为权限'{permission}'的URL是一个重合词", + "show_tile_cant_be_enabled_for_url_not_defined": "您现在无法启用 'show_tile' ,因为您必须先为权限'{permission}'定义一个URL", + "service_unknown": "未知服务 '{service:s}'", + "service_stopped": "服务'{service:s}' 已停止", + "service_stop_failed": "无法停止服务'{service:s}'\n\n最近的服务日志:{logs:s}", + "upnp_dev_not_found": "找不到UPnP设备", + "upgrading_packages": "升级程序包...", + "upgrade_complete": "升级完成", + "updating_apt_cache": "正在获取系统软件包的可用升级...", + "update_apt_cache_warning": "更新APT缓存(Debian的软件包管理器)时出了点问题。这是sources.list行的转储,这可能有助于确定有问题的行:\n{sourceslist}", + "update_apt_cache_failed": "无法更新APT的缓存(Debian的软件包管理器)。这是sources.list行的转储,这可能有助于确定有问题的行:\n{sourceslist}", + "unrestore_app": "{app:s} 将不会恢复", + "unlimit": "没有配额", + "unknown_main_domain_path": "'{app}'的域或路径未知。您需要指定一个域和一个路径,以便能够指定用于许可的URL。", + "unexpected_error": "出乎意料的错误: {error}", + "unbackup_app": "{app:s} 将不会保存", + "tools_upgrade_special_packages_completed": "YunoHost软件包升级完成。\n按[Enter]返回命令行", + "tools_upgrade_special_packages_explanation": "特殊升级将在后台继续。请在接下来的10分钟内(取决于硬件速度)在服务器上不要执行任何其他操作。此后,您可能必须重新登录Webadmin。升级日志将在“工具”→“日志”(在Webadmin中)或使用'yunohost log list'(从命令行)中可用。", + "tools_upgrade_special_packages": "现在正在升级'special'(与yunohost相关的)程序包…", + "tools_upgrade_regular_packages_failed": "无法升级软件包: {packages_list}", + "yunohost_installing": "正在安装YunoHost ...", + "yunohost_configured": "现在已配置YunoHost", + "yunohost_already_installed": "YunoHost已经安装", + "user_updated": "用户信息已更改", + "user_update_failed": "无法更新用户{user}: {error}", + "user_unknown": "未知用户: {user:s}", + "user_home_creation_failed": "无法为用户创建'home'文件夹", + "user_deletion_failed": "无法删除用户 {user}: {error}", + "user_deleted": "用户已删除", + "user_creation_failed": "无法创建用户 {user}: {error}", + "user_created": "用户创建", + "user_already_exists": "用户'{user}' 已存在", + "upnp_port_open_failed": "无法通过UPnP打开端口", + "upnp_enabled": "UPnP已开启", + "upnp_disabled": "UPnP已关闭", + "yunohost_not_installed": "YunoHost没有正确安装,请运行 'yunohost tools postinstall'", + "yunohost_postinstall_end_tip": "后期安装完成! 为了最终完成你的设置,请考虑:\n -通过webadmin的“用户”部分添加第一个用户(或在命令行中'yunohost user create ' );\n -通过网络管理员的“诊断”部分(或命令行中的'yunohost diagnosis run')诊断潜在问题;\n -阅读管理文档中的“完成安装设置”和“了解Yunohost”部分: https://yunohost.org/admindoc.", + "operation_interrupted": "该操作是否被手动中断?", + "invalid_regex": "无效的正则表达式:'{regex:s}'", + "installation_failed": "安装出现问题", + "installation_complete": "安装完成", + "hook_name_unknown": "未知的钩子名称 '{name:s}'", + "hook_list_by_invalid": "此属性不能用于列出钩子", + "hook_json_return_error": "无法读取来自钩子 {path:s}的返回,错误: {msg:s}。原始内容: {raw_content}", + "hook_exec_not_terminated": "脚本未正确完成: {path:s}", + "hook_exec_failed": "无法运行脚本: {path:s}", + "group_user_not_in_group": "用户{user}不在组{group}中", + "group_user_already_in_group": "用户{user}已在组{group}中", + "group_update_failed": "无法更新群组'{group}': {error}", + "group_updated": "群组 '{group}' 已更新", + "group_unknown": "群组 '{group:s}' 未知", + "group_deletion_failed": "无法删除群组'{group}': {error}", + "group_deleted": "群组'{group}' 已删除", + "group_cannot_be_deleted": "无法手动删除组{group}。", + "group_cannot_edit_primary_group": "不能手动编辑 '{group}' 组。它是旨在仅包含一个特定用户的主要组。", + "group_cannot_edit_visitors": "组“访客”不能手动编辑。这是一个代表匿名访问者的特殊小组", + "group_cannot_edit_all_users": "组“ all_users”不能手动编辑。这是一个特殊的组,旨在包含所有在YunoHost中注册的用户", + "group_creation_failed": "无法创建组'{group}': {error}", + "group_created": "创建了 '{group}'组", + "group_already_exist_on_system_but_removing_it": "系统组中已经存在组{group},但是YunoHost会将其删除...", + "group_already_exist_on_system": "系统组中已经存在组{group}", + "group_already_exist": "群组{group}已经存在", + "good_practices_about_admin_password": "现在,您将定义一个新的管理密码。密码长度至少应为8个字符-尽管优良作法是使用较长的密码(即密码短语)和/或使用各种字符(大写,小写,数字和特殊字符)。", + "global_settings_unknown_type": "意外的情况,设置{setting:s}似乎具有类型 {unknown_type:s} ,但是系统不支持该类型。", + "global_settings_setting_backup_compress_tar_archives": "创建新备份时,请压缩档案(.tar.gz) ,而不要压缩未压缩的档案(.tar)。注意:启用此选项意味着创建较小的备份存档,但是初始备份过程将明显更长且占用大量CPU。", + "global_settings_setting_smtp_relay_password": "SMTP中继主机密码", + "global_settings_setting_smtp_relay_user": "SMTP中继用户帐户", + "global_settings_setting_smtp_relay_port": "SMTP中继端口", + "global_settings_setting_smtp_allow_ipv6": "允许使用IPv6接收和发送邮件", + "global_settings_setting_ssowat_panel_overlay_enabled": "启用SSOwat面板覆盖", + "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "允许使用DSA主机密钥进行SSH守护程序配置(不建议使用)", + "global_settings_unknown_setting_from_settings_file": "设置中的未知密钥:'{setting_key:s}',将其丢弃并保存在/etc/yunohost/settings-unknown.json中", + "global_settings_setting_security_ssh_port": "SSH端口", + "global_settings_setting_security_postfix_compatibility": "Postfix服务器的兼容性与安全性的权衡。影响密码(以及其他与安全性有关的方面)", + "global_settings_setting_security_ssh_compatibility": "SSH服务器的兼容性与安全性的权衡。影响密码(以及其他与安全性有关的方面)", + "global_settings_setting_security_password_user_strength": "用户密码强度", + "global_settings_setting_security_password_admin_strength": "管理员密码强度", + "global_settings_setting_security_nginx_compatibility": "Web服务器NGINX的兼容性与安全性的权衡,影响密码(以及其他与安全性有关的方面)", + "global_settings_setting_pop3_enabled": "为邮件服务器启用POP3协议", + "global_settings_reset_success": "以前的设置现在已经备份到{path:s}", + "global_settings_key_doesnt_exists": "全局设置中不存在键'{settings_key:s}',您可以通过运行 'yunohost settings list'来查看所有可用键", + "global_settings_cant_write_settings": "无法保存设置文件,原因: {reason:s}", + "global_settings_cant_serialize_settings": "无法序列化设置数据,原因: {reason:s}", + "global_settings_cant_open_settings": "无法打开设置文件,原因: {reason:s}", + "global_settings_bad_type_for_setting": "设置 {setting:s},的类型错误,已收到{received_type:s},预期{expected_type:s}", + "global_settings_bad_choice_for_enum": "设置 {setting:s}的错误选择,收到了 '{choice:s}',但可用的选择有: {available_choices:s}", + "firewall_rules_cmd_failed": "某些防火墙规则命令失败。日志中的更多信息。", + "firewall_reloaded": "重新加载防火墙", + "firewall_reload_failed": "无法重新加载防火墙", + "file_does_not_exist": "文件{path:s} 不存在。", + "field_invalid": "无效的字段'{:s}'", + "experimental_feature": "警告:此功能是实验性的,不稳定,请不要使用它,除非您知道自己在做什么。", + "extracting": "提取中...", + "dyndns_unavailable": "域'{domain:s}' 不可用。", + "dyndns_domain_not_provided": "DynDNS提供者 {provider:s} 无法提供域 {domain:s}。", + "dyndns_registration_failed": "无法注册DynDNS域: {error:s}", + "dyndns_registered": "DynDNS域已注册", + "dyndns_provider_unreachable": "无法联系DynDNS提供者 {provider}: 您的YunoHost未正确连接到Internet或dynette服务器已关闭。", + "dyndns_no_domain_registered": "没有在DynDNS中注册的域", + "dyndns_key_not_found": "找不到该域的DNS密钥", + "dyndns_key_generating": "正在生成DNS密钥...可能需要一段时间。", + "dyndns_ip_updated": "在DynDNS上更新了您的IP", + "dyndns_ip_update_failed": "无法将IP地址更新到DynDNS", + "dyndns_could_not_check_available": "无法检查{provider:s}上是否可用 {domain:s}。", + "dyndns_could_not_check_provide": "无法检查{provider:s}是否可以提供 {domain:s}.", + "dpkg_lock_not_available": "该命令现在无法运行,因为另一个程序似乎正在使用dpkg锁(系统软件包管理器)", + "dpkg_is_broken": "您现在不能执行此操作,因为dpkg / APT(系统软件包管理器)似乎处于损坏状态……您可以尝试通过SSH连接并运行sudo apt install --fix-broken和/或 sudo dpkg --configure-a 来解决此问题.", + "downloading": "下载中…", + "done": "完成", + "domains_available": "可用域:", + "domain_unknown": "未知网域", + "domain_name_unknown": "域'{domain}'未知", + "domain_uninstall_app_first": "这些应用程序仍安装在您的域中:\n{apps}\n\n请先使用 'yunohost app remove the_app_id' 将其卸载,或使用 'yunohost app change-url the_app_id'将其移至另一个域,然后再继续删除域", + "domain_remove_confirm_apps_removal": "删除该域将删除这些应用程序:\n{apps}\n\n您确定要这样做吗? [{answers}]", + "domain_hostname_failed": "无法设置新的主机名。稍后可能会引起问题(可能没问题)。", + "domain_exists": "该域已存在", + "domain_dyndns_root_unknown": "未知的DynDNS根域", + "domain_dyndns_already_subscribed": "您已经订阅了DynDNS域", + "domain_dns_conf_is_just_a_recommendation": "此命令向您显示*推荐*配置。它实际上并没有为您设置DNS配置。根据此建议,您有责任在注册服务商中配置DNS区域。", + "domain_deletion_failed": "无法删除域 {domain}: {error}", + "domain_deleted": "域已删除", + "domain_creation_failed": "无法创建域 {domain}: {error}", + "domain_created": "域已创建", + "domain_cert_gen_failed": "无法生成证书", + "diagnosis_sshd_config_inconsistent": "看起来SSH端口是在/etc/ssh/sshd_config中手动修改, 从Yunohost 4.2开始,可以使用新的全局设置“ security.ssh.port”来避免手动编辑配置。", + "diagnosis_sshd_config_insecure": "SSH配置似乎已被手动修改,并且是不安全的,因为它不包含“ AllowGroups”或“ AllowUsers”指令以限制对授权用户的访问。", + "diagnosis_processes_killed_by_oom_reaper": "该系统最近杀死了某些进程,因为内存不足。这通常是系统内存不足或进程占用大量内存的征兆。 杀死进程的摘要:\n{kills_summary}", + "diagnosis_never_ran_yet": "看来这台服务器是最近安装的,还没有诊断报告可以显示。您应该首先从Web管理员运行完整的诊断,或者从命令行使用'yunohost diagnosis run' 。", + "diagnosis_unknown_categories": "以下类别是未知的: {categories}", + "diagnosis_http_nginx_conf_not_up_to_date_details": "要解决这种情况,请使用yunohost tools regen-conf nginx --dry-run --with-diff,如果还可以,请使用yunohost tools regen-conf nginx --force应用更改。", + "diagnosis_http_nginx_conf_not_up_to_date": "该域的nginx配置似乎已被手动修改,并阻止YunoHost诊断它是否可以在HTTP上访问。", + "diagnosis_http_partially_unreachable": "尽管域{domain}可以在 IPv{failed}中工作,但它似乎无法通过HTTP从外部网络通过HTTP到达IPv{passed}。", + "diagnosis_mail_outgoing_port_25_blocked_details": "您应该首先尝试在Internet路由器界面或主机提供商界面中取消阻止传出端口25。(某些托管服务提供商可能会要求您为此发送支持请求)。", + "diagnosis_mail_outgoing_port_25_blocked": "由于传出端口25在IPv{ipversion}中被阻止,因此SMTP邮件服务器无法向其他服务器发送电子邮件。", + "diagnosis_mail_outgoing_port_25_ok": "SMTP邮件服务器能够发送电子邮件(未阻止出站端口25)。", + "diagnosis_swap_tip": "请注意,如果服务器在SD卡或SSD存储器上托管交换,则可能会大大缩短设备的预期寿命。", + "diagnosis_swap_ok": "系统有{total}个交换!", + "diagnosis_swap_notsomuch": "系统只有{total}个交换。您应该考虑至少使用{recommended},以避免系统内存不足的情况。", + "diagnosis_swap_none": "系统根本没有交换分区。您应该考虑至少添加{recommended}交换,以避免系统内存不足的情况。", + "diagnosis_http_unreachable": "网域{domain}从本地网络外通过HTTP无法访问。", + "diagnosis_http_connection_error": "连接错误:无法连接到请求的域,很可能无法访问。", + "diagnosis_http_ok": "域{domain}可以通过HTTP从本地网络外部访问。", + "diagnosis_http_could_not_diagnose_details": "错误: {error}", + "diagnosis_http_could_not_diagnose": "无法诊断域是否可以从IPv{ipversion}中从外部访问。", + "diagnosis_http_hairpinning_issue_details": "这可能是由于您的ISP 光猫/路由器。因此,使用域名或全局IP时,来自本地网络外部的人员将能够按预期访问您的服务器,但无法访问来自本地网络内部的人员(可能与您一样)。您可以通过查看 https://yunohost.org/dns_local_network 来改善这种情况", + "diagnosis_http_hairpinning_issue": "您的本地网络似乎没有启用NAT回环功能。", + "diagnosis_ports_forwarding_tip": "要解决此问题,您很可能需要按照 https://yunohost.org/isp_box_config 中的说明,在Internet路由器上配置端口转发", + "diagnosis_ports_needed_by": "{category}功能(服务{service})需要公开此端口", + "diagnosis_ports_ok": "可以从外部访问端口{port}。", + "diagnosis_ports_partially_unreachable": "无法从外部通过IPv{failed}访问端口{port}。", + "diagnosis_ports_unreachable": "无法从外部访问端口{port}。", + "diagnosis_ports_could_not_diagnose_details": "错误: {error}", + "diagnosis_ports_could_not_diagnose": "无法诊断端口在IPv{ipversion}中是否可以从外部访问。", + "diagnosis_description_regenconf": "系统配置", + "diagnosis_description_mail": "电子邮件", + "diagnosis_description_web": "网页", + "diagnosis_description_ports": "开放端口", + "diagnosis_description_systemresources": "系统资源", + "diagnosis_description_services": "服务状态检查", + "diagnosis_description_dnsrecords": "DNS记录", + "diagnosis_description_ip": "互联网连接", + "diagnosis_description_basesystem": "基本系统", + "diagnosis_security_vulnerable_to_meltdown_details": "要解决此问题,您应该升级系统并重新启动以加载新的Linux内核(如果无法使用,请与您的服务器提供商联系)。有关更多信息,请参见https://meltdownattack.com/。", + "diagnosis_security_vulnerable_to_meltdown": "你似乎容易受到Meltdown关键安全漏洞的影响", + "diagnosis_regenconf_manually_modified": "配置文件 {file} 似乎已被手动修改。", + "diagnosis_regenconf_allgood": "所有配置文件均符合建议的配置!", + "diagnosis_mail_queue_too_big": "邮件队列中的待处理电子邮件过多({nb_pending} emails)", + "diagnosis_mail_queue_unavailable_details": "错误: {error}", + "diagnosis_mail_queue_unavailable": "无法查询队列中待处理电子邮件的数量", + "diagnosis_mail_queue_ok": "邮件队列中有{nb_pending} 个待处理的电子邮件", + "diagnosis_mail_blacklist_website": "确定列出的原因并加以修复后,请随时在{blacklist_website}上要求删除您的IP或域名", + "diagnosis_mail_blacklist_reason": "黑名单的原因是: {reason}", + "diagnosis_mail_blacklist_listed_by": "您的IP或域{item} 已在{blacklist_name}上列入黑名单", + "diagnosis_mail_blacklist_ok": "该服务器使用的IP和域似乎未列入黑名单", + "diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "当前反向DNS值为: {rdns_domain}
期待值:{ehlo_domain}", + "diagnosis_mail_fcrdns_different_from_ehlo_domain": "反向DNS未在 IPv{ipversion}中正确配置。某些电子邮件可能无法传递或可能被标记为垃圾邮件。", + "diagnosis_mail_fcrdns_nok_details": "您应该首先尝试在Internet路由器界面或托管服务提供商界面中使用{ehlo_domain}配置反向DNS。(某些托管服务提供商可能会要求您为此发送支持票)。", + "diagnosis_mail_fcrdns_dns_missing": "IPv{ipversion}中未定义反向DNS。某些电子邮件可能无法传递或可能被标记为垃圾邮件。", + "diagnosis_mail_fcrdns_ok": "您的反向DNS已正确配置!", + "diagnosis_mail_ehlo_could_not_diagnose_details": "错误: {error}", + "diagnosis_mail_ehlo_could_not_diagnose": "无法诊断Postfix邮件服务器是否可以从IPv{ipversion}中从外部访问。", + "diagnosis_mail_ehlo_wrong": "不同的SMTP邮件服务器在IPv{ipversion}上进行应答。您的服务器可能无法接收电子邮件。", + "diagnosis_mail_ehlo_bad_answer_details": "这可能是由于其他计算机而不是您的服务器在应答。", + "diagnosis_mail_ehlo_bad_answer": "一个非SMTP服务在IPv{ipversion}的25端口应答", + "diagnosis_mail_ehlo_unreachable": "SMTP邮件服务器在IPv{ipversion}上无法从外部访问。它将无法接收电子邮件。", + "diagnosis_mail_ehlo_ok": "SMTP邮件服务器可以从外部访问,因此可以接收电子邮件!", + "diagnosis_services_bad_status": "服务{service}为 {status} :(", + "diagnosis_services_conf_broken": "服务{service}的配置已损坏!", + "diagnosis_services_running": "服务{service}正在运行!", + "diagnosis_domain_expires_in": "{domain}在{days}天后到期。", + "diagnosis_domain_expiration_error": "有些域很快就会过期!", + "diagnosis_domain_expiration_warning": "一些域即将过期!", + "diagnosis_domain_expiration_success": "您的域已注册,并且不会很快过期。", + "diagnosis_domain_expiration_not_found_details": "域{domain}的WHOIS信息似乎不包含有关到期日期的信息?", + "diagnosis_domain_not_found_details": "域{domain}在WHOIS数据库中不存在或已过期!", + "diagnosis_domain_expiration_not_found": "无法检查某些域的到期日期", + "diagnosis_dns_missing_record": "根据建议的DNS配置,您应该添加带有以下信息的DNS记录。
类型:{type}
名称:{name}
值:{value}", + "diagnosis_dns_bad_conf": "域{domain}(类别{category})的某些DNS记录丢失或不正确", + "diagnosis_dns_good_conf": "已为域{domain}(类别{category})正确配置了DNS记录", + "diagnosis_ip_weird_resolvconf_details": "文件 /etc/resolv.conf 应该是指向 /etc/resolvconf/run/resolv.conf 本身的符号链接,指向 127.0.0.1 (dnsmasq)。如果要手动配置DNS解析器,请编辑 /etc/resolv.dnsmasq.conf。", + "diagnosis_ip_weird_resolvconf": "DNS解析似乎可以正常工作,但是您似乎正在使用自定义的 /etc/resolv.conf 。", + "diagnosis_ip_broken_resolvconf": "域名解析在您的服务器上似乎已损坏,这似乎与 /etc/resolv.conf 有关,但未指向 127.0.0.1 。", + "diagnosis_ip_broken_dnsresolution": "域名解析似乎由于某种原因而被破坏...防火墙是否阻止了DNS请求?", + "diagnosis_ip_dnsresolution_working": "域名解析正常!", + "diagnosis_ip_not_connected_at_all": "服务器似乎根本没有连接到Internet?", + "diagnosis_ip_local": "本地IP:{local}", + "diagnosis_ip_global": "全局IP: {global}", + "diagnosis_ip_no_ipv6_tip": "正常运行的IPv6并不是服务器正常运行所必需的,但是对于整个Internet的健康而言,则更好。通常,IPv6应该由系统或您的提供商自动配置(如果可用)。否则,您可能需要按照此处的文档中的说明手动配置一些内容: https://yunohost.org/#/ipv6。如果您无法启用IPv6或对您来说太过困难,也可以安全地忽略此警告。", + "diagnosis_ip_no_ipv6": "服务器没有可用的IPv6。", + "diagnosis_ip_connected_ipv6": "服务器通过IPv6连接到Internet!", + "diagnosis_ip_no_ipv4": "服务器没有可用的IPv4。", + "diagnosis_ip_connected_ipv4": "服务器通过IPv4连接到Internet!", + "diagnosis_no_cache": "尚无类别 '{category}'的诊断缓存", + "diagnosis_failed": "无法获取类别 '{category}'的诊断结果: {error}", + "diagnosis_package_installed_from_sury_details": "一些软件包被无意中从一个名为Sury的第三方仓库安装。Yunohost团队改进了处理这些软件包的策略,但预计一些安装了PHP7.3应用程序的设置在仍然使用Stretch的情况下还有一些不一致的地方。为了解决这种情况,你应该尝试运行以下命令:{cmd_to_fix}", + "app_not_installed": "在已安装的应用列表中找不到 {app:s}:{all_apps}", + "app_already_installed_cant_change_url": "这个应用程序已经被安装。URL不能仅仅通过这个函数来改变。在`app changeurl`中检查是否可用。" } From c615b272c26f31fc97048e493c49dfa3ae0a56c1 Mon Sep 17 00:00:00 2001 From: Christian Wehrli Date: Thu, 20 May 2021 14:59:21 +0000 Subject: [PATCH 2497/3170] Translated using Weblate (German) Currently translated at 89.6% (570 of 636 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/locales/de.json b/locales/de.json index da6dbd7ce..ad3584bc5 100644 --- a/locales/de.json +++ b/locales/de.json @@ -69,25 +69,25 @@ "installation_failed": "Etwas ist mit der Installation falsch gelaufen", "ip6tables_unavailable": "ip6tables kann nicht verwendet werden. Du befindest dich entweder in einem Container oder es wird nicht vom Kernel unterstützt", "iptables_unavailable": "iptables kann nicht verwendet werden. Du befindest dich entweder in einem Container oder es wird nicht vom Kernel unterstützt", - "ldap_initialized": "LDAP wurde initialisiert", - "mail_alias_remove_failed": "E-Mail Alias '{mail:s}' konnte nicht entfernt werden", + "ldap_initialized": "LDAP initialisiert", + "mail_alias_remove_failed": "Konnte E-Mail-Alias '{mail:s}' nicht entfernen", "mail_domain_unknown": "Die Domäne '{domain:s}' dieser E-Mail-Adresse ist ungültig. Wähle bitte eine Domäne, welche durch diesen Server verwaltet wird.", "mail_forward_remove_failed": "Die Weiterleitungs-E-Mail '{mail:s}' konnte nicht gelöscht werden", "main_domain_change_failed": "Die Hauptdomain konnte nicht geändert werden", "main_domain_changed": "Die Hauptdomain wurde geändert", - "packages_upgrade_failed": "Es konnten nicht alle Pakete aktualisiert werden", - "pattern_backup_archive_name": "Ein gültiger Dateiname kann nur aus maximal 30 alphanumerischen sowie -_. Zeichen bestehen", + "packages_upgrade_failed": "Konnte nicht alle Pakete aktualisieren", + "pattern_backup_archive_name": "Muss ein gültiger Dateiname mit maximal 30 alphanumerischen sowie -_. Zeichen sein", "pattern_domain": "Muss ein gültiger Domainname sein (z.B. meine-domain.org)", - "pattern_email": "Muss eine gültige E-Mail Adresse sein (z.B. someone@domain.org)", + "pattern_email": "Muss eine gültige E-Mail-Adresse ohne '+' Symbol sein (z.B. someone@example.com)", "pattern_firstname": "Muss ein gültiger Vorname sein", "pattern_lastname": "Muss ein gültiger Nachname sein", - "pattern_mailbox_quota": "Muss eine Größe inkl. b/k/M/G/T Suffix, oder 0 zum deaktivieren sein", + "pattern_mailbox_quota": "Muss eine Größe mit b/k/M/G/T Suffix, oder 0 zum deaktivieren sein", "pattern_password": "Muss mindestens drei Zeichen lang sein", "pattern_port_or_range": "Muss ein valider Port (z.B. 0-65535) oder ein Bereich (z.B. 100:200) sein", "pattern_username": "Darf nur aus klein geschriebenen alphanumerischen Zeichen und Unterstrichen bestehen", "port_already_closed": "Der Port {port:d} wurde bereits für {ip_version:s} Verbindungen geschlossen", "port_already_opened": "Der Port {port:d} wird bereits von {ip_version:s} benutzt", - "restore_already_installed_app": "Es ist bereits eine App mit der ID '{app:s}' installiet", + "restore_already_installed_app": "Eine Applikation mit der ID '{app:s}' ist bereits installiert", "restore_cleaning_failed": "Das temporäre Dateiverzeichnis für Systemrestaurierung konnte nicht gelöscht werden", "restore_complete": "Vollständig wiederhergestellt", "restore_confirm_yunohost_installed": "Möchtest du die Wiederherstellung wirklich starten? [{answers:s}]", @@ -140,7 +140,7 @@ "yunohost_installing": "YunoHost wird installiert…", "yunohost_not_installed": "YunoHost ist nicht oder unvollständig installiert worden. Bitte 'yunohost tools postinstall' ausführen", "app_not_properly_removed": "{app:s} wurde nicht ordnungsgemäß entfernt", - "not_enough_disk_space": "Zu wenig freier Speicherplatz unter '{path:s}' verfügbar", + "not_enough_disk_space": "Nicht genügend Speicherplatz auf '{path:s}' frei", "backup_creation_failed": "Konnte Backup-Archiv nicht erstellen", "pattern_positive_number": "Muss eine positive Zahl sein", "app_not_correctly_installed": "{app:s} scheint nicht korrekt installiert zu sein", @@ -152,7 +152,7 @@ "dyndns_key_not_found": "DNS-Schlüssel für die Domain wurde nicht gefunden", "dyndns_no_domain_registered": "Keine Domain mit DynDNS registriert", "ldap_init_failed_to_create_admin": "Die LDAP-Initialisierung konnte keinen admin-Benutzer erstellen", - "mailbox_used_space_dovecot_down": "Der Dovecot Mailbox Dienst muss gestartet sein, wenn du den von der Mailbox belegten Speicher angezeigen lassen willst", + "mailbox_used_space_dovecot_down": "Der Dovecot-Mailbox-Dienst muss aktiv sein, wenn Sie den von der Mailbox belegten Speicher abrufen wollen", "certmanager_attempt_to_replace_valid_cert": "Du versuchst gerade eine richtiges und gültiges Zertifikat der Domain {domain:s} zu überschreiben! (Benutze --force , um diese Nachricht zu umgehen)", "certmanager_domain_cert_not_selfsigned": "Das Zertifikat der Domain {domain:s} ist kein selbstsigniertes Zertifikat. Sind Sie sich sicher, dass Sie es ersetzen wollen? (Benutzen Sie dafür '--force')", "certmanager_certificate_fetching_or_enabling_failed": "Die Aktivierung des neuen Zertifikats für die {domain:s} ist fehlgeschlagen...", @@ -597,5 +597,8 @@ "migration_description_0020_ssh_sftp_permissions": "Unterstützung für SSH- und SFTP-Berechtigungen hinzufügen", "global_settings_setting_ssowat_panel_overlay_enabled": "Das SSOwat-Overlay-Panel aktivieren", "global_settings_setting_security_ssh_port": "SSH-Port", - "diagnosis_sshd_config_inconsistent_details": "Bitte führen Sie yunohost settings set security.ssh.port -v YOUR_SSH_PORT aus, um den SSH-Port festzulegen, und prüfen Sie yunohost tools regen-conf ssh --dry-run --with-diff und yunohost tools regen-conf ssh --force um Ihre conf auf die YunoHost-Empfehlung zurückzusetzen." + "diagnosis_sshd_config_inconsistent_details": "Bitte führen Sie yunohost settings set security.ssh.port -v YOUR_SSH_PORT aus, um den SSH-Port festzulegen, und prüfen Sie yunohost tools regen-conf ssh --dry-run --with-diff und yunohost tools regen-conf ssh --force um Ihre conf auf die YunoHost-Empfehlung zurückzusetzen.", + "regex_incompatible_with_tile": "/!\\ Packagers! Für Berechtigung '{permission}' ist show_tile auf 'true' gesetzt und deshalb können Sie keine regex-URL als Hauptdomäne setzen", + "permission_cant_add_to_all_users": "Die Berechtigung {permission} konnte nicht allen Benutzern gegeben werden.", + "migration_ldap_can_not_backup_before_migration": "Das System-Backup konnte nicht abgeschlossen werden, bevor die Migration fehlschlug. Fehler: {error:s}" } From 1d3b19d732a758981f0874cd351541086ddba43d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?yahoo=EF=BD=9E=EF=BD=9E?= Date: Thu, 20 May 2021 13:57:23 +0000 Subject: [PATCH 2498/3170] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (636 of 636 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/zh_Hans/ --- locales/zh_Hans.json | 168 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 167 insertions(+), 1 deletion(-) diff --git a/locales/zh_Hans.json b/locales/zh_Hans.json index 526e3fde2..838cefe21 100644 --- a/locales/zh_Hans.json +++ b/locales/zh_Hans.json @@ -468,5 +468,171 @@ "diagnosis_failed": "无法获取类别 '{category}'的诊断结果: {error}", "diagnosis_package_installed_from_sury_details": "一些软件包被无意中从一个名为Sury的第三方仓库安装。Yunohost团队改进了处理这些软件包的策略,但预计一些安装了PHP7.3应用程序的设置在仍然使用Stretch的情况下还有一些不一致的地方。为了解决这种情况,你应该尝试运行以下命令:{cmd_to_fix}", "app_not_installed": "在已安装的应用列表中找不到 {app:s}:{all_apps}", - "app_already_installed_cant_change_url": "这个应用程序已经被安装。URL不能仅仅通过这个函数来改变。在`app changeurl`中检查是否可用。" + "app_already_installed_cant_change_url": "这个应用程序已经被安装。URL不能仅仅通过这个函数来改变。在`app changeurl`中检查是否可用。", + "restore_not_enough_disk_space": "没有足够的空间(空间: {free_space:d} B,需要的空间: {needed_space:d} B,安全系数: {margin:d} B)", + "regenconf_pending_applying": "正在为类别'{category}'应用挂起的配置..", + "regenconf_up_to_date": "类别'{category}'的配置已经是最新的", + "regenconf_file_kept_back": "配置文件'{conf}'预计将被regen-conf(类别{category})删除,但被保留了下来。", + "good_practices_about_user_password": "选择至少8个字符的用户密码-尽管使用较长的用户密码(即密码短语)和/或使用各种字符(大写,小写,数字和特殊字符)是一种很好的做法。", + "global_settings_setting_smtp_relay_host": "使用SMTP中继主机来代替这个yunohost实例发送邮件。如果你有以下情况,就很有用:你的25端口被你的ISP或VPS提供商封锁,你有一个住宅IP列在DUHL上,你不能配置反向DNS,或者这个服务器没有直接暴露在互联网上,你想使用其他服务器来发送邮件。", + "domain_cannot_remove_main_add_new_one": "你不能删除'{domain:s}',因为它是主域和你唯一的域,你需要先用'yunohost domain add '添加另一个域,然后用'yunohost domain main-domain -n '设置为主域,然后你可以用'yunohost domain remove {domain:s}'删除域", + "domain_cannot_add_xmpp_upload": "你不能添加以'xmpp-upload.'开头的域名。这种名称是为YunoHost中集成的XMPP上传功能保留的。", + "domain_cannot_remove_main": "你不能删除'{domain:s}',因为它是主域,你首先需要用'yunohost domain main-domain -n '设置另一个域作为主域;这里是候选域的列表: {other_domains:s}", + "diagnosis_sshd_config_inconsistent_details": "请运行yunohost settings set security.ssh.port -v YOUR_SSH_PORT来定义SSH端口,并检查yunohost tools regen-conf ssh --dry-run --with-diffyunohost tools regen-conf ssh --force将您的配置重置为Yunohost建议。", + "diagnosis_http_bad_status_code": "它看起来像另一台机器(也许是你的互联网路由器)回答,而不是你的服务器。
1。这个问题最常见的原因是80端口(和443端口)没有正确转发到您的服务器
2.在更复杂的设置中:确保没有防火墙或反向代理的干扰。", + "diagnosis_http_timeout": "当试图从外部联系你的服务器时,出现了超时。它似乎是不可达的。
1. 这个问题最常见的原因是80端口(和443端口)没有正确转发到你的服务器
2.你还应该确保nginx服务正在运行
3.对于更复杂的设置:确保没有防火墙或反向代理的干扰。", + "diagnosis_rootfstotalspace_critical": "根文件系统总共只有{space},这很令人担忧!您可能很快就会用完磁盘空间!建议根文件系统至少有16 GB。", + "diagnosis_rootfstotalspace_warning": "根文件系统总共只有{space}。这可能没问题,但要小心,因为最终您可能很快会用完磁盘空间...建议根文件系统至少有16 GB。", + "diagnosis_regenconf_manually_modified_details": "如果你知道自己在做什么的话,这可能是可以的! YunoHost会自动停止更新这个文件... 但是请注意,YunoHost的升级可能包含重要的推荐变化。如果你想,你可以用yunohost tools regen-conf {category} --dry-run --with-diff检查差异,然后用yunohost tools regen-conf {category} --force强制设置为推荐配置", + "diagnosis_mail_fcrdns_nok_alternatives_6": "有些供应商不会让你配置你的反向DNS(或者他们的功能可能被破坏......)。如果你的反向DNS正确配置为IPv4,你可以尝试在发送邮件时禁用IPv6,方法是运yunohost settings set smtp.allow_ipv6 -v off。注意:这应视为最后一个解决方案因为这意味着你将无法从少数只使用IPv6的服务器发送或接收电子邮件。", + "diagnosis_mail_fcrdns_nok_alternatives_4": "有些供应商不会让你配置你的反向DNS(或者他们的功能可能被破坏......)。如果您因此而遇到问题,请考虑以下解决方案:
- 一些ISP提供了使用邮件服务器中转的选择,尽管这意味着中转将能够监视您的电子邮件流量。
- 一个有利于隐私的选择是使用VPN*与专用公共IP*来绕过这类限制。见https://yunohost.org/#/vpn_advantage
- 或者可以切换到另一个供应商", + "diagnosis_mail_ehlo_wrong_details": "远程诊断器在IPv{ipversion}中收到的EHLO与你的服务器的域名不同。
收到的EHLO: {wrong_ehlo}
预期的: {right_ehlo}
这个问题最常见的原因是端口25没有正确转发到你的服务器。另外,请确保没有防火墙或反向代理的干扰。", + "diagnosis_mail_ehlo_unreachable_details": "在IPv{ipversion}中无法打开与您服务器的25端口连接。它似乎是不可达的。
1. 这个问题最常见的原因是端口25没有正确转发到你的服务器
2.你还应该确保postfix服务正在运行。
3.在更复杂的设置中:确保没有防火墙或反向代理的干扰。", + "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "一些供应商不会让你解除对出站端口25的封锁,因为他们不关心网络中立性。
- 其中一些供应商提供了使用邮件服务器中继的替代方案,尽管这意味着中继将能够监视你的电子邮件流量。
- 一个有利于隐私的替代方案是使用VPN*,用一个专用的公共IP*绕过这种限制。见https://yunohost.org/#/vpn_advantage
- 你也可以考虑切换到一个更有利于网络中立的供应商", + "diagnosis_ram_ok": "系统在{total}中仍然有 {available} ({available_percent}%) RAM可用。", + "diagnosis_ram_low": "系统有 {available} ({available_percent}%) RAM可用(共{total}个)可用。小心。", + "diagnosis_ram_verylow": "系统只有 {available} ({available_percent}%) 内存可用! (在{total}中)", + "diagnosis_diskusage_ok": "存储器{mountpoint}(在设备{device}上)仍有 {free} ({free_percent}%) 空间(在{total}中)!", + "diagnosis_diskusage_low": "存储器{mountpoint}(在设备{device}上)只有{free} ({free_percent}%) 的空间。({free_percent}%)的剩余空间(在{total}中)。要小心。", + "diagnosis_diskusage_verylow": "存储器{mountpoint}(在设备{device}上)仅剩余{free} ({free_percent}%) (剩余{total})个空间。您应该真正考虑清理一些空间!", + "diagnosis_services_bad_status_tip": "你可以尝试重新启动服务,如果没有效果,可以看看webadmin中的服务日志(从命令行,你可以用yunohost service restart {service}yunohost service log {service})来做。", + "diagnosis_dns_try_dyndns_update_force": "该域的DNS配置应由Yunohost自动管理,如果不是这种情况,您可以尝试使用 yunohost dyndns update --force强制进行更新。", + "diagnosis_dns_point_to_doc": "如果您需要有关配置DNS记录的帮助,请查看 https://yunohost.org/dns_config 上的文档。", + "diagnosis_dns_discrepancy": "以下DNS记录似乎未遵循建议的配置:
类型: {type}
名称: {name}
代码> 当前值: {current}期望值: {value}", + "log_backup_create": "创建备份档案", + "log_available_on_yunopaste": "现在可以通过{url}使用此日志", + "log_app_config_apply": "将配置应用于 '{}' 应用", + "log_app_config_show_panel": "显示 '{}' 应用的配置面板", + "log_app_action_run": "运行 '{}' 应用的操作", + "log_app_makedefault": "将 '{}' 设为默认应用", + "log_app_upgrade": "升级 '{}' 应用", + "log_app_remove": "删除 '{}' 应用", + "log_app_install": "安装 '{}' 应用", + "log_app_change_url": "更改'{}'应用的网址", + "log_operation_unit_unclosed_properly": "操作单元未正确关闭", + "log_does_exists": "没有名称为'{log}'的操作日志,请使用 'yunohost log list' 查看所有可用的操作日志", + "log_help_to_get_failed_log": "操作'{desc}'无法完成。请使用命令'yunohost log share {name}' 共享此操作的完整日志以获取帮助", + "log_link_to_failed_log": "无法完成操作 '{desc}'。请通过单击此处提供此操作的完整日志以获取帮助", + "log_help_to_get_log": "要查看操作'{desc}'的日志,请使用命令'yunohost log show {name}{name}'", + "log_link_to_log": "此操作的完整日志: '{desc}'", + "log_corrupted_md_file": "与日志关联的YAML元数据文件已损坏: '{md_file}\n错误: {error}'", + "iptables_unavailable": "你不能在这里使用iptables。你要么在一个容器中,要么你的内核不支持它", + "ip6tables_unavailable": "你不能在这里使用ip6tables。你要么在一个容器中,要么你的内核不支持它", + "log_regen_conf": "重新生成系统配置'{}'", + "log_letsencrypt_cert_renew": "续订'{}'的“Let's Encrypt”证书", + "log_selfsigned_cert_install": "在 '{}'域上安装自签名证书", + "log_permission_url": "更新与权限'{}'相关的网址", + "log_permission_delete": "删除权限'{}'", + "log_permission_create": "创建权限'{}'", + "log_letsencrypt_cert_install": "在'{}'域上安装“Let's Encrypt”证书", + "log_dyndns_update": "更新与您的YunoHost子域'{}'关联的IP", + "log_dyndns_subscribe": "订阅YunoHost子域'{}'", + "log_domain_remove": "从系统配置中删除 '{}' 域", + "log_domain_add": "将 '{}'域添加到系统配置中", + "log_remove_on_failed_install": "安装失败后删除 '{}'", + "log_remove_on_failed_restore": "从备份存档还原失败后,删除 '{}'", + "log_backup_restore_app": "从备份存档还原 '{}'", + "log_backup_restore_system": "从备份档案还原系统", + "permission_currently_allowed_for_all_users": "这个权限目前除了授予其他组以外,还授予所有用户。你可能想删除'all_users'权限或删除目前授予它的其他组。", + "permission_creation_failed": "无法创建权限'{permission}': {error}", + "permission_created": "权限'{permission:s}'已创建", + "permission_cannot_remove_main": "不允许删除主要权限", + "permission_already_up_to_date": "权限没有被更新,因为添加/删除请求已经符合当前状态。", + "permission_already_exist": "权限 '{permission}'已存在", + "permission_already_disallowed": "群组'{group}'已禁用权限'{permission}'", + "permission_already_allowed": "群组 '{group}' 已启用权限'{permission}'", + "pattern_password_app": "抱歉,密码不能包含以下字符: {forbidden_chars}", + "pattern_username": "只能为小写字母数字和下划线字符", + "pattern_positive_number": "必须为正数", + "pattern_port_or_range": "必须是有效的端口号(即0-65535)或端口范围(例如100:200)", + "pattern_password": "必须至少3个字符长", + "pattern_mailbox_quota": "必须为带b/k/M/G/T 后缀的大小或0,才能没有配额", + "pattern_lastname": "必须是有效的姓氏", + "pattern_firstname": "必须是有效的名字", + "pattern_email": "必须是有效的电子邮件地址,没有'+'符号(例如someone @ example.com)", + "pattern_email_forward": "必须是有效的电子邮件地址,接受 '+' 符号(例如someone + tag @ example.com)", + "pattern_domain": "必须是有效的域名(例如my-domain.org)", + "pattern_backup_archive_name": "必须是一个有效的文件名,最多30个字符,只有-_.和字母数字", + "password_too_simple_4": "密码长度至少为12个字符,并且包含数字,大写,小写和特殊字符", + "password_too_simple_3": "密码长度至少为8个字符,并且包含数字,大写,小写和特殊字符", + "password_too_simple_2": "密码长度至少为8个字符,并且包含数字,大写和小写字符", + "password_listed": "该密码是世界上最常用的密码之一。 请选择一些更独特的东西。", + "packages_upgrade_failed": "无法升级所有软件包", + "invalid_number": "必须是数字", + "not_enough_disk_space": "'{path:s}'上的可用空间不足", + "migrations_to_be_ran_manually": "迁移{id}必须手动运行。请转到webadmin页面上的工具→迁移,或运行`yunohost tools migrations run`。", + "migrations_success_forward": "迁移 {id} 已完成", + "migrations_skip_migration": "正在跳过迁移{id}...", + "migrations_running_forward": "正在运行迁移{id}...", + "migrations_pending_cant_rerun": "这些迁移仍处于待处理状态,因此无法再次运行: {ids}", + "migrations_not_pending_cant_skip": "这些迁移没有待处理,因此不能跳过: {ids}", + "migrations_no_such_migration": "没有称为 '{id}'的迁移", + "migrations_no_migrations_to_run": "无需迁移即可运行", + "migrations_need_to_accept_disclaimer": "要运行迁移{id},您必须接受以下免责声明:\n---\n{disclaimer}\n---\n如果您接受并继续运行迁移,请使用选项'--accept-disclaimer'重新运行该命令。", + "migrations_must_provide_explicit_targets": "使用'--skip'或'--force-rerun'时必须提供明确的目标", + "migrations_migration_has_failed": "迁移{id}尚未完成,正在中止。错误: {exception}", + "migrations_loading_migration": "正在加载迁移{id}...", + "migrations_list_conflict_pending_done": "您不能同时使用'--previous' 和'--done'。", + "migrations_exclusive_options": "'--auto', '--skip',和'--force-rerun'是互斥的选项。", + "migrations_failed_to_load_migration": "无法加载迁移{id}: {error}", + "migrations_dependencies_not_satisfied": "在迁移{id}之前运行以下迁移: '{dependencies_id}'。", + "migrations_cant_reach_migration_file": "无法访问路径'%s'处的迁移文件", + "migrations_already_ran": "这些迁移已经完成: {ids}", + "migration_0019_slapd_config_will_be_overwritten": "好像您手动编辑了slapd配置。对于此关键迁移,YunoHost需要强制更新slapd配置。原始文件将备份在{conf_backup_folder}中。", + "migration_0019_add_new_attributes_in_ldap": "在LDAP数据库中添加权限的新属性", + "migration_0018_failed_to_reset_legacy_rules": "无法重置旧版iptables规则: {error}", + "migration_0018_failed_to_migrate_iptables_rules": "无法将旧的iptables规则迁移到nftables: {error}", + "migration_0017_not_enough_space": "在{path}中提供足够的空间来运行迁移。", + "migration_0017_postgresql_11_not_installed": "已安装PostgreSQL 9.6,但未安装PostgreSQL11?您的系统上可能发生了一些奇怪的事情:(...", + "migration_0017_postgresql_96_not_installed": "PostgreSQL未安装在您的系统上。无事可做。", + "migration_0015_weak_certs": "发现以下证书仍然使用弱签名算法,并且必须升级以与下一版本的nginx兼容: {certs}", + "migration_0015_cleaning_up": "清理不再有用的缓存和软件包...", + "migration_0015_specific_upgrade": "开始升级需要独立升级的系统软件包...", + "migration_0015_modified_files": "请注意,发现以下文件是手动修改的,并且在升级后可能会被覆盖: {manually_modified_files}", + "migration_0015_problematic_apps_warning": "请注意,已检测到以下可能有问题的已安装应用程序。看起来好像那些不是从YunoHost应用程序目录中安装的,或者没有标记为“正在运行”。因此,不能保证它们在升级后仍然可以使用: {problematic_apps}", + "migration_0015_general_warning": "请注意,此迁移是一项微妙的操作。YunoHost团队竭尽全力对其进行检查和测试,但迁移仍可能会破坏系统或其应用程序的某些部分。\n\n因此,建议:\n -对任何关键数据或应用程序执行备份。有关更多信息,请访问https://yunohost.org/backup;\n -启动迁移后要耐心:根据您的Internet连接和硬件,升级所有内容最多可能需要几个小时。", + "migration_0015_system_not_fully_up_to_date": "您的系统不是最新的。请先执行常规升级,然后再运行向Buster的迁移。", + "migration_0015_not_enough_free_space": "/var/中的可用空间非常低!您应该至少有1GB的可用空间来运行此迁移。", + "migration_0015_not_stretch": "当前的Debian发行版不是Stretch!", + "migration_0015_yunohost_upgrade": "正在启动YunoHost核心升级...", + "migration_0015_still_on_stretch_after_main_upgrade": "在主要升级期间出了点问题,系统似乎仍在Debian Stretch上", + "migration_0015_main_upgrade": "正在开始主要升级...", + "migration_0015_patching_sources_list": "修补sources.lists ...", + "migration_0015_start": "开始迁移至Buster", + "migration_update_LDAP_schema": "正在更新LDAP模式...", + "migration_ldap_rollback_success": "系统回滚。", + "migration_ldap_migration_failed_trying_to_rollback": "无法迁移...试图回滚系统。", + "migration_ldap_can_not_backup_before_migration": "迁移失败之前,无法完成系统的备份。错误: {error:s}", + "migration_ldap_backup_before_migration": "在实际迁移之前,请创建LDAP数据库和应用程序设置的备份。", + "migration_description_0020_ssh_sftp_permissions": "添加SSH和SFTP权限支持", + "migration_description_0019_extend_permissions_features": "扩展/修改应用程序的权限管理系统", + "migration_description_0018_xtable_to_nftable": "将旧的网络流量规则迁移到新的nftable系统", + "migration_description_0017_postgresql_9p6_to_11": "将数据库从PostgreSQL 9.6迁移到11", + "migration_description_0016_php70_to_php73_pools": "将php7.0-fpm'pool'conf文件迁移到php7.3", + "migration_description_0015_migrate_to_buster": "将系统升级到Debian Buster和YunoHost 4.x", + "migrating_legacy_permission_settings": "正在迁移旧版权限设置...", + "main_domain_changed": "主域已更改", + "main_domain_change_failed": "无法更改主域", + "mail_unavailable": "该电子邮件地址是保留的,并且将自动分配给第一个用户", + "mailbox_used_space_dovecot_down": "如果要获取使用过的邮箱空间,则必须启动Dovecot邮箱服务", + "mailbox_disabled": "用户{user:s}的电子邮件已关闭", + "mail_forward_remove_failed": "无法删除电子邮件转发'{mail:s}'", + "mail_domain_unknown": "域'{domain:s}'的电子邮件地址无效。请使用本服务器管理的域。", + "mail_alias_remove_failed": "无法删除电子邮件别名'{mail:s}'", + "ldap_initialized": "LDAP已初始化", + "ldap_init_failed_to_create_admin": "LDAP初始化无法创建管理员用户", + "log_tools_reboot": "重新启动服务器", + "log_tools_shutdown": "关闭服务器", + "log_tools_upgrade": "升级系统软件包", + "log_tools_postinstall": "安装好你的YunoHost服务器后", + "log_tools_migrations_migrate_forward": "运行迁移", + "log_domain_main_domain": "将 '{}' 设为主要域", + "log_user_permission_reset": "重置权限'{}'", + "log_user_permission_update": "更新权限'{}'的访问权限", + "log_user_update": "更新用户'{}'的信息", + "log_user_group_update": "更新组'{}'", + "log_user_group_delete": "删除组'{}'", + "log_user_group_create": "创建组'{}'", + "log_user_delete": "删除用户'{}'", + "log_user_create": "添加用户'{}'" } From 56de0950c87d0e4120c783812831f914698e25fe Mon Sep 17 00:00:00 2001 From: Stephan Schneider Date: Fri, 21 May 2021 05:17:25 +0000 Subject: [PATCH 2499/3170] Translated using Weblate (German) Currently translated at 90.5% (576 of 636 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/locales/de.json b/locales/de.json index ad3584bc5..79359604c 100644 --- a/locales/de.json +++ b/locales/de.json @@ -600,5 +600,10 @@ "diagnosis_sshd_config_inconsistent_details": "Bitte führen Sie yunohost settings set security.ssh.port -v YOUR_SSH_PORT aus, um den SSH-Port festzulegen, und prüfen Sie yunohost tools regen-conf ssh --dry-run --with-diff und yunohost tools regen-conf ssh --force um Ihre conf auf die YunoHost-Empfehlung zurückzusetzen.", "regex_incompatible_with_tile": "/!\\ Packagers! Für Berechtigung '{permission}' ist show_tile auf 'true' gesetzt und deshalb können Sie keine regex-URL als Hauptdomäne setzen", "permission_cant_add_to_all_users": "Die Berechtigung {permission} konnte nicht allen Benutzern gegeben werden.", - "migration_ldap_can_not_backup_before_migration": "Das System-Backup konnte nicht abgeschlossen werden, bevor die Migration fehlschlug. Fehler: {error:s}" + "migration_ldap_can_not_backup_before_migration": "Das System-Backup konnte nicht abgeschlossen werden, bevor die Migration fehlschlug. Fehler: {error:s}", + "service_description_fail2ban": "Schützt gegen Brute-Force-Angriffe und andere Angriffe aus dem Internet", + "service_description_dovecot": "Ermöglicht es E-Mail-Clients auf Konten zuzugreifen (IMAP und POP3)", + "service_description_dnsmasq": "Verarbeitet die Auflösung des Domainnamens (DNS)", + "service_description_avahi-daemon": "Erlaubt, den Server im lokalen Netz über 'yunohost.local' zu erreichen", + "restore_backup_too_old": "Dieses Backup kann nicht wieder hergestellt werden, weil es von einer zu alten YunoHost Version stammt." } From 12c08460dff7b903d2253856efeacfc6fd856633 Mon Sep 17 00:00:00 2001 From: Stephan Schneider Date: Fri, 21 May 2021 06:35:08 +0000 Subject: [PATCH 2500/3170] Translated using Weblate (German) Currently translated at 91.5% (582 of 636 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/locales/de.json b/locales/de.json index 79359604c..c09e073d4 100644 --- a/locales/de.json +++ b/locales/de.json @@ -605,5 +605,12 @@ "service_description_dovecot": "Ermöglicht es E-Mail-Clients auf Konten zuzugreifen (IMAP und POP3)", "service_description_dnsmasq": "Verarbeitet die Auflösung des Domainnamens (DNS)", "service_description_avahi-daemon": "Erlaubt, den Server im lokalen Netz über 'yunohost.local' zu erreichen", - "restore_backup_too_old": "Dieses Backup kann nicht wieder hergestellt werden, weil es von einer zu alten YunoHost Version stammt." + "restore_backup_too_old": "Dieses Backup kann nicht wieder hergestellt werden, weil es von einer zu alten YunoHost Version stammt.", + "service_description_slapd": "Speichert Benutzer, Domains und verbundene Informationen", + "service_description_rspamd": "Spamfilter und andere E-Mail Merkmale", + "service_description_redis-server": "Eine spezialisierte Datenbank für den schnellen Datenzugriff, die Aufgabenwarteschlange und die Kommunikation zwischen Programmen", + "service_description_postfix": "Wird benutzt, um E-Mails zu senden und zu empfangen", + "service_description_nginx": "Stellt Daten aller Websiten auf dem Server bereit", + "service_description_mysql": "Apeichert Anwendungsdaten (SQL Datenbank)", + "service_description_metronome": "XMPP Sofortnachrichtenkonten verwalten" } From c0bc9cd700779b4bb46c580e822f69da5d516228 Mon Sep 17 00:00:00 2001 From: Christian Wehrli Date: Sat, 22 May 2021 17:46:05 +0000 Subject: [PATCH 2501/3170] Translated using Weblate (German) Currently translated at 91.5% (582 of 636 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/locales/de.json b/locales/de.json index c09e073d4..35f5ae409 100644 --- a/locales/de.json +++ b/locales/de.json @@ -162,7 +162,7 @@ "certmanager_domain_dns_ip_differs_from_public_ip": "Der DNS-A-Eintrag der Domain {domain:s} unterscheidet sich von dieser Server-IP. Für weitere Informationen überprüfen Sie bitte die 'DNS records' (basic) Kategorie in der Diagnose. Wenn Sie gerade Ihren A-Eintrag verändert haben, warten Sie bitte etwas, damit die Änderungen wirksam werden (Sie können die DNS-Propagation mittels Website überprüfen) (Wenn Sie wissen was Sie tun, können Sie --no-checks benutzen, um diese Überprüfung zu überspringen.)", "certmanager_cannot_read_cert": "Es ist ein Fehler aufgetreten, als es versucht wurde das aktuelle Zertifikat für die Domain {domain:s} zu öffnen (Datei: {file:s}), Grund: {reason:s}", "certmanager_cert_install_success_selfsigned": "Ein selbstsigniertes Zertifikat für die Domain {domain:s} wurde erfolgreich installiert", - "certmanager_cert_install_success": "Für die Domain {domain:s} wurde erfolgreich ein Let's Encrypt Zertifikat installiert!", + "certmanager_cert_install_success": "Let's-Encrypt-Zertifikat für die Domäne {domain:s} ist jetzt installiert", "certmanager_cert_renew_success": "Das Let's Encrypt Zertifikat für die Domain {domain:s} wurde erfolgreich erneuert", "certmanager_hit_rate_limit": "Es wurden innerhalb kurzer Zeit zu viele Zertifikate für dieselbe Domain {domain:s} ausgestellt. Bitte versuchen Sie es später nochmal. Besuchen Sie https://letsencrypt.org/docs/rate-limits/ für mehr Informationen", "certmanager_cert_signing_failed": "Das neue Zertifikat konnte nicht signiert werden", @@ -313,7 +313,7 @@ "backup_archive_corrupted": "Das Backup-Archiv '{archive}' scheint beschädigt: {error}", "backup_archive_cant_retrieve_info_json": "Die Informationen für das Archiv '{archive}' konnten nicht geladen werden... Die Datei info.json wurde nicht gefunden (oder ist kein gültiges json).", "app_packaging_format_not_supported": "Diese App kann nicht installiert werden da das Paketformat nicht von der YunoHost-Version unterstützt wird. Denken Sie darüber nach das System zu aktualisieren.", - "certmanager_domain_not_diagnosed_yet": "Für {domain} gibt es noch keine Diagnose-Resultate. Bitte wiederholen Sie die Diagnose für die Kategorien 'DNS records' und 'Web' im Diagnose-Bereich um zu überprüfen ob die Domain für Let's Encrypt bereit ist. (Oder wenn Sie wissen was Sie tun, verwenden Sie '--no-checks' um diese Überprüfungen abzuschalten.", + "certmanager_domain_not_diagnosed_yet": "Für die Domäne {domain} gibt es noch keine Diagnoseresultate. Bitte wiederholen Sie die Diagnose für die Kategorien 'DNS-Einträge' und 'Web' im Diagnose-Bereich um zu überprüfen ob die Domäne für Let's Encrypt bereit ist. (Oder wenn Sie wissen was Sie tun, verwenden Sie '--no-checks' um diese Überprüfungen abzuschalten.", "migration_0015_patching_sources_list": "sources.lists wird repariert...", "migration_0015_start": "Start der Migration auf Buster", "migration_description_0015_migrate_to_buster": "Auf Debian Buster und YunoHost 4.x upgraden", @@ -588,7 +588,7 @@ "log_backup_create": "Erstelle ein Backup-Archiv", "diagnosis_sshd_config_inconsistent": "Es sieht aus, als ob der SSH-Port manuell geändert wurde in /etc/ssh/ssh_config. Seit YunoHost 4.2 ist eine neue globale Einstellung 'security.ssh.port' verfügbar um zu verhindern, dass die Konfiguration manuell verändert wird.", "diagnosis_sshd_config_insecure": "Die SSH-Konfiguration wurde scheinbar manuell abgeändert, und ist unsicher, weil sie keine 'AllowGroups'- oder 'AllowUsers' -Direktiven für die Begrenzung des Zugriffs durch autorisierte Benutzer enthält.", - "backup_create_size_estimation": "Das Archiv wird etwa {size} Daten enthalten", + "backup_create_size_estimation": "Das Archiv wird etwa {size} an Daten enthalten.", "app_restore_script_failed": "Im Wiederherstellungsskript der Anwendung ist ein Fehler aufgetreten", "app_restore_failed": "Konnte {app:s} nicht wiederherstellen: {error:s}", "migration_ldap_rollback_success": "System-Rollback erfolgreich.", From 6883d2b8f36a71449728b6580077fa26d11d79d2 Mon Sep 17 00:00:00 2001 From: Radek S Date: Sat, 22 May 2021 13:37:34 +0000 Subject: [PATCH 2502/3170] Translated using Weblate (Czech) Currently translated at 10.3% (66 of 636 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/cs/ --- locales/cs.json | 49 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/locales/cs.json b/locales/cs.json index 9f4bd8197..396c26b07 100644 --- a/locales/cs.json +++ b/locales/cs.json @@ -17,5 +17,52 @@ "app_already_up_to_date": "{app:s} aplikace je/jsou aktuální", "app_already_installed_cant_change_url": "Tato aplikace je již nainstalována. URL nemůže být touto akcí změněna. Zkontrolujte `app changeurl` pokud je dostupné.", "app_action_cannot_be_ran_because_required_services_down": "Pro běh této akce by měli být spuštěné následující služby: {services}. Zkuste je zrestartovat, případně zjistěte, proč neběží.", - "app_action_broke_system": "Zdá se, že tato akce rozbila následující důležité služby: {service}" + "app_action_broke_system": "Zdá se, že tato akce rozbila následující důležité služby: {service}", + "app_install_script_failed": "Vyskytla se chyba uvnitř instalačního skriptu aplikace", + "app_install_failed": "Nelze instalovat {app}: {error}", + "app_install_files_invalid": "Tyto soubory nemohou být instalovány", + "app_id_invalid": "Neplatné ID aplikace", + "app_full_domain_unavailable": "Tato aplikace musí být nainstalována na své vlastní doméně, jiné aplikace tuto doménu již využívají. Můžete použít poddoménu určenou pouze pro tuto aplikaci.", + "app_extraction_failed": "Nelze rozbalit instalační soubory", + "app_change_url_success": "{app:s} URL je nyní {domain:s}{path:s}", + "app_change_url_no_script": "Aplikace '{app_name:s}' nyní nepodporuje URL modifikace. Zkuste ji aktualizovat.", + "app_argument_required": "Hodnota'{name:s}' je vyžadována", + "app_argument_password_no_default": "Chyba při zpracování obsahu hesla '{name}': z bezpečnostních důvodů nemůže obsahovat výchozí hodnotu", + "password_too_simple_4": "Heslo musí být aspoň 12 znaků dlouhé a obsahovat čísla, velká a malá písmena a speciální znaky", + "password_too_simple_3": "Heslo musí být aspoň 8 znaků dlouhé a obsahovat čísla, velká a malá písmena a speciální znaky", + "password_too_simple_2": "Heslo musí být aspoň 8 znaků dlouhé a obsahovat číslici, velká a malá písmena", + "password_listed": "Toto heslo je jedním z nejpoužívanějších na světě. Zvolte si prosím něco jedinečnějšího.", + "operation_interrupted": "Operace byla manuálně přerušena?", + "group_user_already_in_group": "Uživatel {user} je již ve skupině {group}", + "group_update_failed": "Nelze upravit skupinu '{group}': {error}", + "group_updated": "Skupina '{group}' upravena", + "group_unknown": "Neznámá skupina '{group:s}'", + "group_deletion_failed": "Nelze smazat skupinu '{group}': {error}", + "group_deleted": "Skupina '{group}' smazána", + "group_cannot_be_deleted": "Skupina {group} nemůže být smazána.", + "group_cannot_edit_primary_group": "Skupina '{group}' nemůže být upravena. Jde o primární skupinu obsahující pouze jednoho specifického uživatele.", + "group_cannot_edit_all_users": "Skupina 'all_users' nemůže být upravena. Jde o speciální skupinu obsahující všechny registrované uživatele na YunoHost", + "group_cannot_edit_visitors": "Skupina 'visitors' nemůže být upravena. Jde o speciální skupinu představující anonymní (neregistrované na YunoHost) návštěvníky", + "group_creation_failed": "Nelze založit skupinu '{group}': {error}", + "group_created": "Skupina '{group}' vytvořena", + "group_already_exist_on_system_but_removing_it": "Skupina {group} se již nalézá v systémových skupinách, ale YunoHost ji odstraní...", + "group_already_exist_on_system": "Skupina {group} se již nalézá v systémových skupinách", + "group_already_exist": "Skupina {group} již existuje", + "good_practices_about_user_password": "Nyní zvolte nové heslo uživatele. Heslo by mělo být minimálně 8 znaků dlouhé, avšak je dobrou taktikou jej mít delší (např. použít více slov) a použít kombinaci znaků (velké, malé, čísla a speciální znaky).", + "good_practices_about_admin_password": "Nyní zvolte nové administrační heslo. Heslo by mělo být minimálně 8 znaků dlouhé, avšak je dobrou taktikou jej mít delší (např. použít více slov) a použít kombinaci znaků (velké, malé, čísla a speciílní znaky).", + "global_settings_unknown_type": "Neočekávaná situace, nastavení {setting:s} deklaruje typ {unknown_type:s} ale toto není systémem podporováno.", + "global_settings_setting_backup_compress_tar_archives": "Komprimovat nové zálohy (.tar.gz) namísto nekomprimovaných (.tar). Poznámka: povolení této volby znamená objemově menší soubory záloh, avšak zálohování bude trvat déle a bude více zatěžovat CPU.", + "global_settings_setting_smtp_relay_password": "SMTP relay heslo uživatele/hostitele", + "global_settings_setting_smtp_relay_user": "SMTP relay uživatelské jméno/účet", + "global_settings_setting_smtp_relay_port": "SMTP relay port", + "global_settings_setting_smtp_relay_host": "Použít SMTP relay hostitele pro odesílání emailů místo této Yunohost instance. Užitečné v různých situacích: port 25 je blokován vaším ISP nebo VPS poskytovatelem, IP adresa je na blacklistu (např. DUHL), nemůžete nastavit reverzní DNS záznam nebo tento server není přímo připojen do internetu a vy chcete použít jiný server k odesílání emailů.", + "global_settings_setting_smtp_allow_ipv6": "Povolit použití IPv6 pro příjem a odesílání emailů", + "global_settings_setting_ssowat_panel_overlay_enabled": "Povolit SSOwat překryvný panel", + "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Povolit použití (zastaralého) DSA klíče hostitele pro konfiguraci SSH služby", + "global_settings_unknown_setting_from_settings_file": "Neznámý klíč v nastavení: '{setting_key:s}', zrušte jej a uložte v /etc/yunohost/settings-unknown.json", + "global_settings_setting_security_ssh_port": "SSH port", + "global_settings_setting_security_postfix_compatibility": "Kompromis mezi kompatibilitou a bezpečností Postfix serveru. Ovlivní šifry a další související bezpečnostní nastavení", + "global_settings_setting_security_ssh_compatibility": "Kompromis mezi kompatibilitou a bezpečností SSH serveru. Ovlivní šifry a další související bezpečnostní nastavení", + "global_settings_setting_security_password_user_strength": "Síla uživatelského hesla", + "global_settings_setting_security_password_admin_strength": "Síla administračního hesla" } From 91b8ce3ea498be90f88818337fccb79922a2bafc Mon Sep 17 00:00:00 2001 From: Stephan Schneider Date: Sun, 23 May 2021 15:23:12 +0000 Subject: [PATCH 2503/3170] Translated using Weblate (German) Currently translated at 91.9% (585 of 636 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/de.json b/locales/de.json index 35f5ae409..804d5b67e 100644 --- a/locales/de.json +++ b/locales/de.json @@ -105,7 +105,7 @@ "service_disabled": "Der Dienst '{service:s}' wurde erfolgreich deaktiviert", "service_enable_failed": "Der Dienst '{service:s}' konnte beim Hochfahren nicht gestartet werden.\n\nKürzlich erstelle Logs des Dienstes: {logs:s}", "service_enabled": "Der Dienst '{service:s}' wird nun beim Hochfahren des Systems automatisch gestartet.", - "service_remove_failed": "Der Dienst '{service:s}' konnte nicht entfernt werden", + "service_remove_failed": "Konnte den Dienst '{service:s}' nicht entfernen", "service_removed": "Der Dienst '{service:s}' wurde erfolgreich entfernt", "service_start_failed": "Der Dienst '{service:s}' konnte nicht gestartet werden\n\nKürzlich erstellte Logs des Dienstes: {logs:s}", "service_started": "Der Dienst '{service:s}' wurde erfolgreich gestartet", From 649e1bf3bffcc6bbd3b57c8e68f759eddc72362b Mon Sep 17 00:00:00 2001 From: Christian Wehrli Date: Sun, 23 May 2021 15:12:20 +0000 Subject: [PATCH 2504/3170] Translated using Weblate (German) Currently translated at 91.9% (585 of 636 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/locales/de.json b/locales/de.json index 804d5b67e..2d369e63a 100644 --- a/locales/de.json +++ b/locales/de.json @@ -101,9 +101,9 @@ "service_already_started": "Der Dienst '{service:s}' läuft bereits", "service_already_stopped": "Der Dienst '{service:s}' wurde bereits gestoppt", "service_cmd_exec_failed": "Der Befehl '{command:s}' konnte nicht ausgeführt werden", - "service_disable_failed": "Der Dienst '{service:s}' konnte nicht deaktiviert werden", - "service_disabled": "Der Dienst '{service:s}' wurde erfolgreich deaktiviert", - "service_enable_failed": "Der Dienst '{service:s}' konnte beim Hochfahren nicht gestartet werden.\n\nKürzlich erstelle Logs des Dienstes: {logs:s}", + "service_disable_failed": "Der Start des Dienstes '{service:s}' beim Hochfahren konnte nicht verhindert werden.\n\nKürzlich erstellte Logs des Dienstes: {logs:s}", + "service_disabled": "Der Dienst '{service:s}' wird beim Hochfahren des Systems nicht mehr gestartet werden.", + "service_enable_failed": "Der Dienst '{service:s}' konnte beim Hochfahren nicht gestartet werden.\n\nKürzlich erstellte Logs des Dienstes: {logs:s}", "service_enabled": "Der Dienst '{service:s}' wird nun beim Hochfahren des Systems automatisch gestartet.", "service_remove_failed": "Konnte den Dienst '{service:s}' nicht entfernen", "service_removed": "Der Dienst '{service:s}' wurde erfolgreich entfernt", From c02013be0d78891c4f1beb21f80dc9ca320c4c6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?yahoo=EF=BD=9E=EF=BD=9E?= Date: Mon, 24 May 2021 09:33:30 +0000 Subject: [PATCH 2505/3170] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (636 of 636 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/zh_Hans/ --- locales/zh_Hans.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/zh_Hans.json b/locales/zh_Hans.json index 838cefe21..e2f6d7644 100644 --- a/locales/zh_Hans.json +++ b/locales/zh_Hans.json @@ -246,7 +246,7 @@ "tools_upgrade_cant_unhold_critical_packages": "无法解压关键软件包…", "tools_upgrade_cant_hold_critical_packages": "无法保存重要软件包…", "tools_upgrade_cant_both": "无法同时升级系统和应用程序", - "tools_upgrade_at_least_one": "请指定'--apps', 或 '--system'", + "tools_upgrade_at_least_one": "请指定'apps', 或 'system'", "this_action_broke_dpkg": "此操作破坏了dpkg / APT(系统软件包管理器)...您可以尝试通过SSH连接并运行`sudo apt install --fix-broken`和/或`sudo dpkg --configure -a`来解决此问题。", "system_username_exists": "用户名已存在于系统用户列表中", "system_upgraded": "系统升级", From 74bd30417721be560e264698ce47661199e8b93c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= Date: Mon, 24 May 2021 13:04:07 +0000 Subject: [PATCH 2506/3170] Translated using Weblate (French) Currently translated at 99.8% (635 of 636 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/locales/fr.json b/locales/fr.json index 60b3d5e68..715d82a35 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -627,5 +627,16 @@ "restore_backup_too_old": "Cette archive de sauvegarde ne peut être restaurée car elle provient d'une version trop ancienne de YunoHost.", "migration_update_LDAP_schema": "Mise à jour du schéma LDAP...", "log_backup_create": "Créer une archive de sauvegarde", - "global_settings_setting_ssowat_panel_overlay_enabled": "Activer la superposition du panneau SSOwat" + "global_settings_setting_ssowat_panel_overlay_enabled": "Activer la superposition du panneau SSOwat", + "migration_ldap_rollback_success": "Système rétabli dans son état initial.", + "permission_cant_add_to_all_users": "L'autorisation {permission} ne peut pas être ajoutée à tous les utilisateurs.", + "migration_ldap_migration_failed_trying_to_rollback": "Impossible de migrer... tentative de restauration du système.", + "migration_ldap_can_not_backup_before_migration": "La sauvegarde du système n'a pas pu être terminée avant l'échec de la migration. Erreur : {error : s}", + "migration_ldap_backup_before_migration": "Création d'une sauvegarde de la base de données LDAP et des paramètres des applications avant la migration proprement dite.", + "migration_description_0020_ssh_sftp_permissions": "Ajouter la prise en charge des autorisations SSH et SFTP", + "global_settings_setting_security_ssh_port": "Port SSH", + "diagnosis_sshd_config_inconsistent_details": "Veuillez exécuter yunohost settings set security.ssh.port -v VOTRE_PORT_SSH pour définir le port SSH, et vérifiez yunohost tools regen-conf ssh --dry-run --with-diff et yunohost tools regen-conf ssh --force pour réinitialiser votre configuration aux recommandations YunoHost.", + "diagnosis_sshd_config_inconsistent": "Il semble que le port SSH a été modifié manuellement dans /etc/ssh/sshd_config. Depuis YunoHost 4.2, un nouveau paramètre global 'security.ssh.port' est disponible pour éviter de modifier manuellement la configuration.", + "diagnosis_sshd_config_insecure": "La configuration SSH semble avoir été modifiée manuellement et n'est pas sécurisée car elle ne contient aucune directive 'AllowGroups' ou 'AllowUsers' pour limiter l'accès aux utilisateurs autorisés.", + "backup_create_size_estimation": "L'archive contiendra environ {size} de données." } From 80de962c53ec8535fde4ca8714b5e7f346e34120 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M?= Date: Mon, 24 May 2021 12:52:57 +0000 Subject: [PATCH 2507/3170] Translated using Weblate (Galician) Currently translated at 0.3% (2 of 636 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/gl/ --- locales/gl.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/gl.json b/locales/gl.json index a2f95cbed..91c77ba6f 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -1,3 +1,4 @@ { - "password_too_simple_1": "O contrasinal ten que ter 8 caracteres como mínimo" + "password_too_simple_1": "O contrasinal ten que ter 8 caracteres como mínimo", + "aborting": "Abortando." } From a8b70bea717fbca8df6ee208f01793961d6b4cad Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Mon, 24 May 2021 15:08:57 +0000 Subject: [PATCH 2508/3170] [CI] Format code --- src/yunohost/data_migrations/0020_ssh_sftp_permissions.py | 4 ++-- src/yunohost/tools.py | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/yunohost/data_migrations/0020_ssh_sftp_permissions.py b/src/yunohost/data_migrations/0020_ssh_sftp_permissions.py index 681d0cd9d..f1dbcd1e7 100644 --- a/src/yunohost/data_migrations/0020_ssh_sftp_permissions.py +++ b/src/yunohost/data_migrations/0020_ssh_sftp_permissions.py @@ -48,7 +48,7 @@ class MyMigration(Migration): "label": "SFTP", "showTile": "FALSE", "isProtected": "TRUE", - } + }, ) if "ssh.main" not in existing_perms: @@ -63,7 +63,7 @@ class MyMigration(Migration): "label": "SSH", "showTile": "FALSE", "isProtected": "TRUE", - } + }, ) # Add a bash terminal to each users diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 1bce1b2cb..d9e057875 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -94,9 +94,7 @@ def tools_adminpw(new_password, check_strength=True): try: ldap.update( "cn=admin", - { - "userPassword": [new_hash] - }, + {"userPassword": [new_hash]}, ) except Exception as e: logger.error("unable to change admin password : %s" % e) From 147fa647792c23d6902b400e65c0c8376984a02f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 24 May 2021 17:21:32 +0200 Subject: [PATCH 2509/3170] Inconsistency in translation --- locales/cs.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/cs.json b/locales/cs.json index 396c26b07..7e593758f 100644 --- a/locales/cs.json +++ b/locales/cs.json @@ -17,7 +17,7 @@ "app_already_up_to_date": "{app:s} aplikace je/jsou aktuální", "app_already_installed_cant_change_url": "Tato aplikace je již nainstalována. URL nemůže být touto akcí změněna. Zkontrolujte `app changeurl` pokud je dostupné.", "app_action_cannot_be_ran_because_required_services_down": "Pro běh této akce by měli být spuštěné následující služby: {services}. Zkuste je zrestartovat, případně zjistěte, proč neběží.", - "app_action_broke_system": "Zdá se, že tato akce rozbila následující důležité služby: {service}", + "app_action_broke_system": "Zdá se, že tato akce rozbila následující důležité služby: {services}", "app_install_script_failed": "Vyskytla se chyba uvnitř instalačního skriptu aplikace", "app_install_failed": "Nelze instalovat {app}: {error}", "app_install_files_invalid": "Tyto soubory nemohou být instalovány", From e0aad83ea3013362eab82634b14ff6e80097b0bb Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 24 May 2021 17:33:31 +0200 Subject: [PATCH 2510/3170] Update changelog for 4.2.5 --- debian/changelog | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/debian/changelog b/debian/changelog index 9cd981455..df6643e30 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,23 @@ +yunohost (4.2.5) testing; urgency=low + + - [fix] backup: Also catch tarfile.ReadError as possible archive corruption error (4aaf0154) + - [enh] helpers: Update n to version 7.2.2 ([#1224](https://github.com/yunohost/yunohost/pull/1224)) + - [fix] helpers: Define ynh_node_load_path to be compatible with ynh_replace_vars (06f8c1cc) + - [doc] helpers: Add requirements for new helpers (2b0df6c3) + - [fix] helpers: Set YNH_APP_BASEDIR as an absolute path ([#1229](https://github.com/yunohost/yunohost/pull/1229), 27300282) + - [fix] Tweak yunohost-api systemd config as an attempt to fix the API being down after yunohost upgrades (52e30704) + - [fix] python3: encoding issue in nftable migrations (0f10b91f) + - [fix] python3: Email on certificate renewing failed ([#1227](https://github.com/yunohost/yunohost/pull/1227)) + - [fix] permissions: Remove warnings about legacy permission system (now reported in the linter) ([#1228](https://github.com/yunohost/yunohost/pull/1228)) + - [fix] diagnosis, mail: Remove SPFBL because it triggers false positive ([#1231](https://github.com/yunohost/yunohost/pull/1231)) + - [fix] diagnosis: DNS diagnosis taking an awful amount of time because of timeout ([#1233](https://github.com/yunohost/yunohost/pull/1233)) + - [fix] install: Be able to init slapd in a chroot ([#1230](https://github.com/yunohost/yunohost/pull/1230)) + - [i18n] Translations updated for Catalan, Chinese (Simplified), Czech, French, Galician, German + + Thanks to all contributors <3 ! (Christian Wehrli, Éric Gaspar, José M, ljf, Radek S, Salamandar, Stephan Schneider, xaloc33, yahoo~~) + + -- Alexandre Aubin Mon, 24 May 2021 17:20:47 +0200 + yunohost (4.2.4) stable; urgency=low - python3: smtplib's sendmail miserably crashes with encoding issue if accent in mail body (af567c6f) From fc02caea2e9fa3e26402c3f5af31df3fe42a7b77 Mon Sep 17 00:00:00 2001 From: ericgaspar Date: Mon, 24 May 2021 18:02:13 +0200 Subject: [PATCH 2511/3170] Yunohost -> YunoHost --- bin/yunoprompt | 12 ++++++------ data/actionsmap/yunohost.yml | 2 +- data/helpers.d/fail2ban | 2 +- data/helpers.d/logging | 2 +- data/helpers.d/systemd | 2 +- data/hooks/conf_regen/06-slapd | 2 +- data/templates/slapd/config.ldif | 6 +++--- data/templates/slapd/permission.ldif | 26 +++++++++++++------------- doc/helper_doc_template.md | 2 +- locales/en.json | 12 ++++++------ src/yunohost/utils/legacy.py | 2 +- 11 files changed, 35 insertions(+), 35 deletions(-) diff --git a/bin/yunoprompt b/bin/yunoprompt index be46fc9ab..8062ab06e 100755 --- a/bin/yunoprompt +++ b/bin/yunoprompt @@ -66,19 +66,19 @@ then echo "$LOGO_AND_FINGERPRINTS" cat << EOF =============================================================================== -You should now proceed with Yunohost post-installation. This is where you will -be asked for : - - the main domain of your server ; +You should now proceed with YunoHost post-installation. This is where you will +be asked for: + - the main domain of your server; - the administration password. -You can perform this step : - - from your web browser, by accessing : https://yunohost.local/ or ${local_ip} +You can perform this step: + - from your web browser, by accessing: https://yunohost.local/ or ${local_ip} - or in this terminal by answering 'yes' to the following question If this is your first time with YunoHost, it is strongly recommended to take time to read the administator documentation and in particular the sections 'Finalizing your setup' and 'Getting to know YunoHost'. It is available at -the following URL : https://yunohost.org/admindoc +the following URL: https://yunohost.org/admindoc =============================================================================== EOF diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index b2f5a349b..a977ca271 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1420,7 +1420,7 @@ tools: help: Use this if you really want to set a weak password action: store_true --force-diskspace: - help: Use this if you really want to install Yunohost on a setup with less than 10 GB on the root filesystem + help: Use this if you really want to install YunoHost on a setup with less than 10 GB on the root filesystem action: store_true diff --git a/data/helpers.d/fail2ban b/data/helpers.d/fail2ban index 06d870b32..6ac7ae6d0 100644 --- a/data/helpers.d/fail2ban +++ b/data/helpers.d/fail2ban @@ -79,7 +79,7 @@ ynh_add_fail2ban_config () { others_var="${others_var:-}" use_template="${use_template:-0}" - [[ -z "$others_var" ]] || ynh_print_warn --message="Packagers: using --others_var is unecessary since Yunohost 4.2" + [[ -z "$others_var" ]] || ynh_print_warn --message="Packagers: using --others_var is unecessary since YunoHost 4.2" if [ $use_template -ne 1 ] then diff --git a/data/helpers.d/logging b/data/helpers.d/logging index 0505117b7..71998763e 100644 --- a/data/helpers.d/logging +++ b/data/helpers.d/logging @@ -293,7 +293,7 @@ ynh_script_progression () { set -o xtrace # set -x } -# Return data to the Yunohost core for later processing +# Return data to the YunoHost core for later processing # (to be used by special hooks like app config panel and core diagnosis) # # usage: ynh_return somedata diff --git a/data/helpers.d/systemd b/data/helpers.d/systemd index 9a9a2d9f8..a1baff4b0 100644 --- a/data/helpers.d/systemd +++ b/data/helpers.d/systemd @@ -25,7 +25,7 @@ ynh_add_systemd_config () { template="${template:-systemd.service}" others_var="${others_var:-}" - [[ -z "$others_var" ]] || ynh_print_warn --message="Packagers: using --others_var is unecessary since Yunohost 4.2" + [[ -z "$others_var" ]] || ynh_print_warn --message="Packagers: using --others_var is unecessary since YunoHost 4.2" ynh_add_config --template="$YNH_APP_BASEDIR/conf/$template" --destination="/etc/systemd/system/$service.service" diff --git a/data/hooks/conf_regen/06-slapd b/data/hooks/conf_regen/06-slapd index 16aaab9c7..93e8e9399 100755 --- a/data/hooks/conf_regen/06-slapd +++ b/data/hooks/conf_regen/06-slapd @@ -40,7 +40,7 @@ EOF usermod -aG ssl-cert openldap # (Re-)init data according to default ldap entries - echo ' Initializing LDAP with Yunohost DB structure' + echo ' Initializing LDAP with YunoHost DB structure' rm -rf /etc/ldap/slapd.d mkdir -p /etc/ldap/slapd.d diff --git a/data/templates/slapd/config.ldif b/data/templates/slapd/config.ldif index d3ed2e053..4f21f4706 100644 --- a/data/templates/slapd/config.ldif +++ b/data/templates/slapd/config.ldif @@ -1,7 +1,7 @@ -# OpenLDAP server configuration for Yunohost +# OpenLDAP server configuration for YunoHost # ------------------------------------------ # -# Because of the Yunohost's regen-conf mechanism, it is NOT POSSIBLE to +# Because of the YunoHost's regen-conf mechanism, it is NOT POSSIBLE to # edit the config database using an LDAP request. # # If you wish to edit the config database, you should edit THIS file @@ -192,7 +192,7 @@ olcDbMaxSize: 10485760 structuralObjectClass: olcMdbConfig # -# Configure Memberof Overlay (used for Yunohost permission) +# Configure Memberof Overlay (used for YunoHost permission) # # Link user <-> group diff --git a/data/templates/slapd/permission.ldif b/data/templates/slapd/permission.ldif index cb4e769e8..64222db1d 100644 --- a/data/templates/slapd/permission.ldif +++ b/data/templates/slapd/permission.ldif @@ -1,4 +1,4 @@ -# Yunohost schema for group and permission support +# YunoHost schema for group and permission support dn: cn=yunohost,cn=schema,cn=config objectClass: olcSchemaConfig @@ -6,45 +6,45 @@ cn: yunohost # ATTRIBUTES # For Permission olcAttributeTypes: ( 1.3.6.1.4.1.17953.9.1.1 NAME 'permission' - DESC 'Yunohost permission on user and group side' + DESC 'YunoHost permission on user and group side' SUP distinguishedName ) olcAttributeTypes: ( 1.3.6.1.4.1.17953.9.1.2 NAME 'groupPermission' - DESC 'Yunohost permission for a group on permission side' + DESC 'YunoHost permission for a group on permission side' SUP distinguishedName ) olcAttributeTypes: ( 1.3.6.1.4.1.17953.9.1.3 NAME 'inheritPermission' - DESC 'Yunohost permission for user on permission side' + DESC 'YunoHost permission for user on permission side' SUP distinguishedName ) olcAttributeTypes: ( 1.3.6.1.4.1.17953.9.1.4 NAME 'URL' - DESC 'Yunohost permission main URL' + DESC 'YunoHost permission main URL' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} SINGLE-VALUE ) olcAttributeTypes: ( 1.3.6.1.4.1.17953.9.1.5 NAME 'additionalUrls' - DESC 'Yunohost permission additionnal URL' + DESC 'YunoHost permission additionnal URL' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} ) olcAttributeTypes: ( 1.3.6.1.4.1.17953.9.1.6 NAME 'authHeader' - DESC 'Yunohost application, enable authentication header' + DESC 'YunoHost application, enable authentication header' SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE ) olcAttributeTypes: ( 1.3.6.1.4.1.17953.9.1.7 NAME 'label' - DESC 'Yunohost permission label, also used for the tile name in the SSO' + DESC 'YunoHost permission label, also used for the tile name in the SSO' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} SINGLE-VALUE ) olcAttributeTypes: ( 1.3.6.1.4.1.17953.9.1.8 NAME 'showTile' - DESC 'Yunohost application, show/hide the tile in the SSO for this permission' + DESC 'YunoHost application, show/hide the tile in the SSO for this permission' SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE ) olcAttributeTypes: ( 1.3.6.1.4.1.17953.9.1.9 NAME 'isProtected' - DESC 'Yunohost application permission protection' + DESC 'YunoHost application permission protection' SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE ) # OBJECTCLASS # For Applications olcObjectClasses: ( 1.3.6.1.4.1.17953.9.2.1 NAME 'groupOfNamesYnh' - DESC 'Yunohost user group' + DESC 'YunoHost user group' SUP top AUXILIARY MAY ( member $ businessCategory $ seeAlso $ owner $ ou $ o $ permission ) ) olcObjectClasses: ( 1.3.6.1.4.1.17953.9.2.2 NAME 'permissionYnh' - DESC 'a Yunohost application' + DESC 'a YunoHost application' SUP top AUXILIARY MUST ( cn $ authHeader $ label $ showTile $ isProtected ) MAY ( groupPermission $ inheritPermission $ URL $ additionalUrls ) ) # For User olcObjectClasses: ( 1.3.6.1.4.1.17953.9.2.3 NAME 'userPermissionYnh' - DESC 'a Yunohost application' + DESC 'a YunoHost application' SUP top AUXILIARY MAY ( permission ) ) diff --git a/doc/helper_doc_template.md b/doc/helper_doc_template.md index 1b9fa873d..cf88e10ac 100644 --- a/doc/helper_doc_template.md +++ b/doc/helper_doc_template.md @@ -7,7 +7,7 @@ routes: default: '/packaging_apps_helpers' --- -Doc auto-generated by [this script](https://github.com/YunoHost/yunohost/blob/{{ current_commit }}/doc/generate_helper_doc.py) on {{data.date}} (Yunohost version {{data.version}}) +Doc auto-generated by [this script](https://github.com/YunoHost/yunohost/blob/{{ current_commit }}/doc/generate_helper_doc.py) on {{data.date}} (YunoHost version {{data.version}}) {% for category, helpers in data.helpers %} ### {{ category.upper() }} diff --git a/locales/en.json b/locales/en.json index cde7aab11..84a01dfaa 100644 --- a/locales/en.json +++ b/locales/en.json @@ -141,7 +141,7 @@ "certmanager_unable_to_parse_self_CA_name": "Could not parse name of self-signing authority (file: {file:s})", "confirm_app_install_warning": "Warning: This app may work, but is not well-integrated in YunoHost. Some features such as single sign-on and backup/restore might not be available. Install anyway? [{answers:s}] ", "confirm_app_install_danger": "DANGER! This app is known to be still experimental (if not explicitly not working)! You should probably NOT install it unless you know what you are doing. NO SUPPORT will be provided if this app doesn't work or breaks your system… If you are willing to take that risk anyway, type '{answers:s}'", - "confirm_app_install_thirdparty": "DANGER! This app is not part of Yunohost's app catalog. Installing third-party apps may compromise the integrity and security of your system. You should probably NOT install it unless you know what you are doing. NO SUPPORT will be provided if this app doesn't work or breaks your system… If you are willing to take that risk anyway, type '{answers:s}'", + "confirm_app_install_thirdparty": "DANGER! This app is not part of YunoHost's app catalog. Installing third-party apps may compromise the integrity and security of your system. You should probably NOT install it unless you know what you are doing. NO SUPPORT will be provided if this app doesn't work or breaks your system… If you are willing to take that risk anyway, type '{answers:s}'", "custom_app_url_required": "You must provide a URL to upgrade your custom app {app:s}", "diagnosis_basesystem_hardware": "Server hardware architecture is {virt} {arch}", "diagnosis_basesystem_hardware_model": "Server model is {model}", @@ -152,7 +152,7 @@ "diagnosis_basesystem_ynh_inconsistent_versions": "You are running inconsistent versions of the YunoHost packages... most probably because of a failed or partial upgrade.", "diagnosis_backports_in_sources_list": "It looks like apt (the package manager) is configured to use the backports repository. Unless you really know what you are doing, we strongly discourage from installing packages from backports, because it's likely to create unstabilities or conflicts on your system.", "diagnosis_package_installed_from_sury": "Some system packages should be downgraded", - "diagnosis_package_installed_from_sury_details": "Some packages were inadvertendly installed from a third-party repository called Sury. The Yunohost team improved the strategy that handle these packages, but it's expected that some setups that installed PHP7.3 apps while still on Stretch have some remaining inconsistencies. To fix this situation, you should try running the following command: {cmd_to_fix}", + "diagnosis_package_installed_from_sury_details": "Some packages were inadvertendly installed from a third-party repository called Sury. The YunoHost team improved the strategy that handle these packages, but it's expected that some setups that installed PHP7.3 apps while still on Stretch have some remaining inconsistencies. To fix this situation, you should try running the following command: {cmd_to_fix}", "diagnosis_display_tip": "To see the issues found, you can go to the Diagnosis section of the webadmin, or run 'yunohost diagnosis show --issues --human-readable' from the command-line.", "diagnosis_failed_for_category": "Diagnosis failed for category '{category}': {error}", "diagnosis_cache_still_valid": "(Cache still valid for {category} diagnosis. Won't re-diagnose it yet!)", @@ -182,7 +182,7 @@ "diagnosis_dns_missing_record": "According to the recommended DNS configuration, you should add a DNS record with the following info.
Type: {type}
Name: {name}
Value: {value}", "diagnosis_dns_discrepancy": "The following DNS record does not seem to follow the recommended configuration:
Type: {type}
Name: {name}
Current value: {current}
Expected value: {value}", "diagnosis_dns_point_to_doc": "Please check the documentation at https://yunohost.org/dns_config if you need help about configuring DNS records.", - "diagnosis_dns_try_dyndns_update_force": "This domain's DNS configuration should automatically be managed by Yunohost. If that's not the case, you can try to force an update using yunohost dyndns update --force.", + "diagnosis_dns_try_dyndns_update_force": "This domain's DNS configuration should automatically be managed by YunoHost. If that's not the case, you can try to force an update using yunohost dyndns update --force.", "diagnosis_domain_expiration_not_found": "Unable to check the expiration date for some domains", "diagnosis_domain_not_found_details": "The domain {domain} doesn't exist in WHOIS database or is expired!", "diagnosis_domain_expiration_not_found_details": "The WHOIS information for domain {domain} doesn't seem to contain the information about the expiration date?", @@ -271,8 +271,8 @@ "diagnosis_never_ran_yet": "It looks like this server was setup recently and there's no diagnosis report to show yet. You should start by running a full diagnosis, either from the webadmin or using 'yunohost diagnosis run' from the command line.", "diagnosis_processes_killed_by_oom_reaper": "Some processes were recently killed by the system because it ran out of memory. This is typically symptomatic of a lack of memory on the system or of a process that ate up to much memory. Summary of the processes killed:\n{kills_summary}", "diagnosis_sshd_config_insecure": "The SSH configuration appears to have been manually modified, and is insecure because it contains no 'AllowGroups' or 'AllowUsers' directive to limit access to authorized users.", - "diagnosis_sshd_config_inconsistent": "It looks like the SSH port was manually modified in /etc/ssh/sshd_config. Since Yunohost 4.2, a new global setting 'security.ssh.port' is available to avoid manually editing the configuration.", - "diagnosis_sshd_config_inconsistent_details": "Please run yunohost settings set security.ssh.port -v YOUR_SSH_PORT to define the SSH port, and check yunohost tools regen-conf ssh --dry-run --with-diff and yunohost tools regen-conf ssh --force to reset your conf to the Yunohost recommendation.", + "diagnosis_sshd_config_inconsistent": "It looks like the SSH port was manually modified in /etc/ssh/sshd_config. Since YunoHost 4.2, a new global setting 'security.ssh.port' is available to avoid manually editing the configuration.", + "diagnosis_sshd_config_inconsistent_details": "Please run yunohost settings set security.ssh.port -v YOUR_SSH_PORT to define the SSH port, and check yunohost tools regen-conf ssh --dry-run --with-diff and yunohost tools regen-conf ssh --force to reset your conf to the YunoHost recommendation.", "domain_cannot_remove_main": "You cannot remove '{domain:s}' since it's the main domain, you first need to set another domain as the main domain using 'yunohost domain main-domain -n '; here is the list of candidate domains: {other_domains:s}", "domain_cannot_add_xmpp_upload": "You cannot add domains starting with 'xmpp-upload.'. This kind of name is reserved for the XMPP upload feature integrated in YunoHost.", "domain_cannot_remove_main_add_new_one": "You cannot remove '{domain:s}' since it's the main domain and your only domain, you need to first add another domain using 'yunohost domain add ', then set is as the main domain using 'yunohost domain main-domain -n ' and then you can remove the domain '{domain:s}' using 'yunohost domain remove {domain:s}'.'", @@ -631,5 +631,5 @@ "yunohost_configured": "YunoHost is now configured", "yunohost_installing": "Installing YunoHost...", "yunohost_not_installed": "YunoHost is not correctly installed. Please run 'yunohost tools postinstall'", - "yunohost_postinstall_end_tip": "The post-install completed! To finalize your setup, please consider:\n - adding a first user through the 'Users' section of the webadmin (or 'yunohost user create ' in command-line);\n - diagnose potential issues through the 'Diagnosis' section of the webadmin (or 'yunohost diagnosis run' in command-line);\n - reading the 'Finalizing your setup' and 'Getting to know Yunohost' parts in the admin documentation: https://yunohost.org/admindoc." + "yunohost_postinstall_end_tip": "The post-install completed! To finalize your setup, please consider:\n - adding a first user through the 'Users' section of the webadmin (or 'yunohost user create ' in command-line);\n - diagnose potential issues through the 'Diagnosis' section of the webadmin (or 'yunohost diagnosis run' in command-line);\n - reading the 'Finalizing your setup' and 'Getting to know YunoHost' parts in the admin documentation: https://yunohost.org/admindoc." } diff --git a/src/yunohost/utils/legacy.py b/src/yunohost/utils/legacy.py index fc00ab586..eb92dd71f 100644 --- a/src/yunohost/utils/legacy.py +++ b/src/yunohost/utils/legacy.py @@ -235,5 +235,5 @@ def translate_legacy_rules_in_ssowant_conf_json_persistent(): write_to_json(persistent_file_name, persistent, sort_keys=True, indent=4) logger.warning( - "Yunohost automatically translated some legacy rules in /etc/ssowat/conf.json.persistent to match the new permission system" + "YunoHost automatically translated some legacy rules in /etc/ssowat/conf.json.persistent to match the new permission system" ) From 600b96782e74cc1842319c947ac82238817e41bf Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 24 May 2021 19:37:53 +0200 Subject: [PATCH 2512/3170] Update changelog for 4.2.5.1 --- debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian/changelog b/debian/changelog index df6643e30..ae630b5c3 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +yunohost (4.2.5.1) stable; urgency=low + + - Releasing as stable + + -- Alexandre Aubin Mon, 24 May 2021 19:36:35 +0200 + yunohost (4.2.5) testing; urgency=low - [fix] backup: Also catch tarfile.ReadError as possible archive corruption error (4aaf0154) From 806b7acfb3d9ec2914f370cdf084dafb67919b73 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 24 May 2021 22:10:56 +0200 Subject: [PATCH 2513/3170] nscd -i won't work in chroot ... also 'groups' was a typo, actual name is 'group' --- data/hooks/conf_regen/06-slapd | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data/hooks/conf_regen/06-slapd b/data/hooks/conf_regen/06-slapd index 16aaab9c7..3c3cd5b9c 100755 --- a/data/hooks/conf_regen/06-slapd +++ b/data/hooks/conf_regen/06-slapd @@ -54,8 +54,8 @@ EOF | grep -v "none elapsed\|Closing DB" || true chown -R openldap: /var/lib/ldap - nscd -i groups - nscd -i passwd + nscd -i group || true + nscd -i passwd || true systemctl restart slapd @@ -157,7 +157,7 @@ objectClass: posixGroup objectClass: top" chown -R openldap: /var/lib/ldap systemctl restart slapd - nscd -i groups + nscd -i group fi [ -z "$regen_conf_files" ] && exit 0 From 825ed82862a9ba67c6cc3396d40a66b6ff1b53ce Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 24 May 2021 22:11:45 +0200 Subject: [PATCH 2514/3170] Update changelog for 4.2.5.2 --- debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian/changelog b/debian/changelog index ae630b5c3..9a143f962 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +yunohost (4.2.5.2) stable; urgency=low + + - Fix install in chroot ... *again* (806b7acf) + + -- Alexandre Aubin Mon, 24 May 2021 22:11:02 +0200 + yunohost (4.2.5.1) stable; urgency=low - Releasing as stable From f288651094617f8af8955d7aa1823851d39032ea Mon Sep 17 00:00:00 2001 From: Kay0u Date: Tue, 25 May 2021 12:20:25 +0200 Subject: [PATCH 2515/3170] fix generate-helpers-doc job --- .gitlab/ci/doc.gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab/ci/doc.gitlab-ci.yml b/.gitlab/ci/doc.gitlab-ci.yml index 696dcefa6..59179f7a7 100644 --- a/.gitlab/ci/doc.gitlab-ci.yml +++ b/.gitlab/ci/doc.gitlab-ci.yml @@ -14,7 +14,7 @@ generate-helpers-doc: - cd doc - python3 generate_helper_doc.py - hub clone https://$GITHUB_TOKEN:x-oauth-basic@github.com/YunoHost/doc.git doc_repo - - cp helpers.md doc_repo/pages/02.contribute/04.packaging_apps/11.helpers/packaging_apps_helpers.md + - cp helpers.md doc_repo/pages/04.contribute/04.packaging_apps/11.helpers/packaging_apps_helpers.md - cd doc_repo # replace ${CI_COMMIT_REF_NAME} with ${CI_COMMIT_TAG} ? - hub checkout -b "${CI_COMMIT_REF_NAME}" From e40f8fb861ca3c98726309e1a6424b2a2f0ad540 Mon Sep 17 00:00:00 2001 From: Corentin Mercier Date: Tue, 25 May 2021 13:46:09 +0200 Subject: [PATCH 2516/3170] Apply easy fixes from code review Co-authored-by: ljf (zamentur) --- data/actionsmap/yunohost.yml | 31 ++++++++++++++++++++++++------- src/yunohost/domain.py | 28 +++++++++++++++------------- 2 files changed, 39 insertions(+), 20 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 87dfcf026..6a7050d61 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -450,7 +450,7 @@ domain: ### domain_push_config() push_config: action_help: Push DNS records to registrar - api: GET /domains/push + api: GET /domains//push arguments: domain: help: Domain name to add @@ -568,11 +568,13 @@ domain: help: The path to check (e.g. /coffee) ### domain_setting() setting: - action_help: Set or get an app setting value + action_help: Set or get a domain setting value api: GET /domains//settings arguments: domain: help: Domain name + extra: + pattern: *pattern_domain key: help: Key to get/set -v: @@ -589,23 +591,38 @@ domain: ### domain_registrar_set() set: action_help: Set domain registrar - api: POST /domains/registrar + api: POST /domains//registrar arguments: domain: help: Domain name + extra: + pattern: *pattern_domain registrar: help: registrar_key, see yunohost domain registrar list -a: full: --args - help: Serialized arguments for app script (i.e. "domain=domain.tld&path=/path") + help: Serialized arguments for registrar API (i.e. "auth_token=TOKEN&auth_username=USER"). ### domain_registrar_set() get: action_help: Get domain registrar - api: GET /domains/registrar + api: GET /domains//registrar + arguments: + domain: + help: Domain name + extra: + pattern: *pattern_domain ### domain_registrar_list() list: - action_help: List available registrars - api: GET /domains/registrar/list + action_help: List registrars configured by DNS zone + api: GET /domains/registrars + catalog: + action_help: List supported registrars API + api: GET /domains/registrars/catalog + arguments: + -f: + full: --full + help: Display all details, including info to create forms + action: store_true ############################# # App # diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 0f4702ec1..6eae65487 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -131,7 +131,7 @@ def domain_add(operation_logger, domain, dyndns=False): # Do not allow to subscribe to multiple dyndns domains... if _guess_current_dyndns_domain("dyndns.yunohost.org") != (None, None): - raise YunohostValidationError('domain_dyndns_already_subscribed') + raise YunohostValidationError("domain_dyndns_already_subscribed") # Check that this domain can effectively be provided by # dyndns.yunohost.org. (i.e. is it a nohost.me / noho.st) @@ -208,8 +208,8 @@ def domain_remove(operation_logger, domain, remove_apps=False, force=False): # the 'force' here is related to the exception happening in domain_add ... # we don't want to check the domain exists because the ldap add may have # failed - if not force and domain not in domain_list()['domains']: - raise YunohostValidationError('domain_name_unknown', domain=domain) + if not force and domain not in domain_list()["domains"]: + raise YunohostValidationError("domain_name_unknown", domain=domain) # Check domain is not the main domain if domain == _get_maindomain(): @@ -223,7 +223,9 @@ def domain_remove(operation_logger, domain, remove_apps=False, force=False): other_domains="\n * " + ("\n * ".join(other_domains)), ) else: - raise YunohostValidationError("domain_cannot_remove_main_add_new_one", domain=domain) + raise YunohostValidationError( + "domain_cannot_remove_main_add_new_one", domain=domain + ) # Check if apps are installed on the domain apps_on_that_domain = [] @@ -515,12 +517,12 @@ def _build_dns_conf(domains): ] # DKIM/DMARC record - dkim_host, dkim_publickey = _get_DKIM(domain) + dkim_host, dkim_publickey = _get_DKIM(domain_name) if dkim_host: mail += [ [dkim_host, ttl, "TXT", dkim_publickey], - ["_dmarc", ttl, "TXT", '"v=DMARC1; p=none"'], + [f"_dmarc{child_domain_suffix}", ttl, "TXT", '"v=DMARC1; p=none"'], ] ######## @@ -528,8 +530,8 @@ def _build_dns_conf(domains): ######## if domain["xmpp"]: xmpp += [ - ["_xmpp-client._tcp", ttl, "SRV", "0 5 5222 %s." % domain_name], - ["_xmpp-server._tcp", ttl, "SRV", "0 5 5269 %s." % domain_name], + [f"_xmpp-client._tcp{child_domain_suffix}", ttl, "SRV", f"0 5 5222 {domain_name}."], + [f"_xmpp-server._tcp{child_domain_suffix}", ttl, "SRV", f"0 5 5269 {domain_name}."], ["muc" + child_domain_suffix, ttl, "CNAME", name], ["pubsub" + child_domain_suffix, ttl, "CNAME", name], ["vjud" + child_domain_suffix, ttl, "CNAME", name], @@ -542,10 +544,10 @@ def _build_dns_conf(domains): if ipv4: - extra.append(["*", ttl, "A", ipv4]) + extra.append([f"*{child_domain_suffix}", ttl, "A", ipv4]) if ipv6: - extra.append(["*", ttl, "AAAA", ipv6]) + extra.append([f"*{child_domain_suffix}", ttl, "AAAA", ipv6]) # TODO # elif include_empty_AAAA_if_no_ipv6: # extra.append(["*", ttl, "AAAA", None]) @@ -727,7 +729,7 @@ def _load_domain_settings(): new_domains[domain] = {} # new_domains[domain] = { "main": is_maindomain } # Set other values (default value if missing) - for setting, default in [ ("xmpp", is_maindomain), ("mail", is_maindomain), ("owned_dns_zone", default_owned_dns_zone), ("ttl", 3600), ("provider", False)]: + for setting, default in [ ("xmpp", is_maindomain), ("mail", True), ("owned_dns_zone", default_owned_dns_zone), ("ttl", 3600), ("provider", False)]: if domain_in_old_domains and setting in old_domains[domain].keys(): new_domains[domain][setting] = old_domains[domain][setting] else: @@ -773,7 +775,7 @@ def domain_setting(domain, key, value=None, delete=False): if "ttl" == key: try: ttl = int(value) - except: + except ValueError: # TODO add locales raise YunohostError("bad_value_type", value_type=type(ttl)) @@ -934,7 +936,7 @@ def domain_push_config(domain): for key in types: for distant_record in distant_records[key]: - print('distant_record:', distant_record); + logger.debug(f"distant_record: {distant_record}"); for local_record in flatten_dns_conf: print('local_record:', local_record); From ced4da417121732cb9928cc3d017131219d9fc74 Mon Sep 17 00:00:00 2001 From: Paco Date: Tue, 25 May 2021 16:18:04 +0200 Subject: [PATCH 2517/3170] Run `black` & revert misguidedly cosmetic changes An obscur plugin must have done this... --- src/yunohost/domain.py | 159 ++++++++++++++++++++++++++--------------- 1 file changed, 102 insertions(+), 57 deletions(-) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 6eae65487..8677e1685 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -43,7 +43,7 @@ from yunohost.app import ( _installed_apps, _get_app_settings, _get_conflicting_apps, - _parse_args_in_yunohost_format + _parse_args_in_yunohost_format, ) from yunohost.regenconf import regen_conf, _force_clear_hashes, _process_regen_conf from yunohost.utils.network import get_public_ip @@ -56,6 +56,7 @@ logger = getActionLogger("yunohost.domain") DOMAIN_SETTINGS_PATH = "/etc/yunohost/domains.yml" REGISTRAR_LIST_PATH = "/usr/share/yunohost/other/providers_list.yml" + def domain_list(exclude_subdomains=False): """ List domains @@ -109,6 +110,7 @@ def domain_add(operation_logger, domain, dyndns=False): from yunohost.hook import hook_callback from yunohost.app import app_ssowatconf from yunohost.utils.ldap import _get_ldap_interface + from yunohost.certificate import _certificate_install_selfsigned if domain.startswith("xmpp-upload."): raise YunohostValidationError("domain_cannot_add_xmpp_upload") @@ -142,14 +144,13 @@ def domain_add(operation_logger, domain, dyndns=False): if dyndns: from yunohost.dyndns import dyndns_subscribe + # Actually subscribe dyndns_subscribe(domain=domain) + _certificate_install_selfsigned([domain], False) + try: - import yunohost.certificate - - yunohost.certificate._certificate_install_selfsigned([domain], False) - attr_dict = { "objectClass": ["mailDomain", "top"], "virtualdomain": domain, @@ -176,13 +177,13 @@ def domain_add(operation_logger, domain, dyndns=False): regen_conf(names=["nginx", "metronome", "dnsmasq", "postfix", "rspamd"]) app_ssowatconf() - except Exception: + except Exception as e: # Force domain removal silently try: domain_remove(domain, force=True) except Exception: pass - raise + raise e hook_callback("post_domain_add", args=[domain]) @@ -234,21 +235,37 @@ def domain_remove(operation_logger, domain, remove_apps=False, force=False): settings = _get_app_settings(app) label = app_info(app)["name"] if settings.get("domain") == domain: - apps_on_that_domain.append((app, " - %s \"%s\" on https://%s%s" % (app, label, domain, settings["path"]) if "path" in settings else app)) + apps_on_that_domain.append( + ( + app, + ' - %s "%s" on https://%s%s' + % (app, label, domain, settings["path"]) + if "path" in settings + else app, + ) + ) if apps_on_that_domain: if remove_apps: - if msettings.get('interface') == "cli" and not force: - answer = msignals.prompt(m18n.n('domain_remove_confirm_apps_removal', - apps="\n".join([x[1] for x in apps_on_that_domain]), - answers='y/N'), color="yellow") + if msettings.get("interface") == "cli" and not force: + answer = msignals.prompt( + m18n.n( + "domain_remove_confirm_apps_removal", + apps="\n".join([x[1] for x in apps_on_that_domain]), + answers="y/N", + ), + color="yellow", + ) if answer.upper() != "Y": raise YunohostError("aborting") for app, _ in apps_on_that_domain: app_remove(app) else: - raise YunohostValidationError('domain_uninstall_app_first', apps="\n".join([x[1] for x in apps_on_that_domain])) + raise YunohostValidationError( + "domain_uninstall_app_first", + apps="\n".join([x[1] for x in apps_on_that_domain]), + ) operation_logger.start() @@ -261,7 +278,7 @@ def domain_remove(operation_logger, domain, remove_apps=False, force=False): os.system("rm -rf /etc/yunohost/certs/%s" % domain) # Delete dyndns keys for this domain (if any) - os.system('rm -rf /etc/yunohost/dyndns/K%s.+*' % domain) + os.system("rm -rf /etc/yunohost/dyndns/K%s.+*" % domain) # Sometime we have weird issues with the regenconf where some files # appears as manually modified even though they weren't touched ... @@ -475,7 +492,9 @@ def _build_dns_conf(domains): extra = [] ipv4 = get_public_ip() ipv6 = get_public_ip(6) - owned_dns_zone = "owned_dns_zone" in domains[root] and domains[root]["owned_dns_zone"] + owned_dns_zone = ( + "owned_dns_zone" in domains[root] and domains[root]["owned_dns_zone"] + ) root_prefix = root.partition(".")[0] child_domain_suffix = "" @@ -484,15 +503,14 @@ def _build_dns_conf(domains): ttl = domain["ttl"] if domain_name == root: - name = root_prefix if not owned_dns_zone else "@" + name = root_prefix if not owned_dns_zone else "@" else: - name = domain_name[0:-(1 + len(root))] + name = domain_name[0 : -(1 + len(root))] if not owned_dns_zone: name += "." + root_prefix - + if name != "@": child_domain_suffix = "." + name - ########################### # Basic ipv4/ipv6 records # @@ -530,8 +548,18 @@ def _build_dns_conf(domains): ######## if domain["xmpp"]: xmpp += [ - [f"_xmpp-client._tcp{child_domain_suffix}", ttl, "SRV", f"0 5 5222 {domain_name}."], - [f"_xmpp-server._tcp{child_domain_suffix}", ttl, "SRV", f"0 5 5269 {domain_name}."], + [ + f"_xmpp-client._tcp{child_domain_suffix}", + ttl, + "SRV", + f"0 5 5222 {domain_name}.", + ], + [ + f"_xmpp-server._tcp{child_domain_suffix}", + ttl, + "SRV", + f"0 5 5269 {domain_name}.", + ], ["muc" + child_domain_suffix, ttl, "CNAME", name], ["pubsub" + child_domain_suffix, ttl, "CNAME", name], ["vjud" + child_domain_suffix, ttl, "CNAME", name], @@ -542,7 +570,6 @@ def _build_dns_conf(domains): # Extra # ######### - if ipv4: extra.append([f"*{child_domain_suffix}", ttl, "A", ipv4]) @@ -729,7 +756,13 @@ def _load_domain_settings(): new_domains[domain] = {} # new_domains[domain] = { "main": is_maindomain } # Set other values (default value if missing) - for setting, default in [ ("xmpp", is_maindomain), ("mail", True), ("owned_dns_zone", default_owned_dns_zone), ("ttl", 3600), ("provider", False)]: + for setting, default in [ + ("xmpp", is_maindomain), + ("mail", True), + ("owned_dns_zone", default_owned_dns_zone), + ("ttl", 3600), + ("provider", False), + ]: if domain_in_old_domains and setting in old_domains[domain].keys(): new_domains[domain][setting] = old_domains[domain][setting] else: @@ -737,6 +770,7 @@ def _load_domain_settings(): return new_domains + def domain_setting(domain, key, value=None, delete=False): """ Set or get an app setting value @@ -753,7 +787,7 @@ def domain_setting(domain, key, value=None, delete=False): if not domain in domains.keys(): # TODO add locales raise YunohostError("domain_name_unknown", domain=domain) - + domain_settings = domains[domain] # GET @@ -771,7 +805,7 @@ def domain_setting(domain, key, value=None, delete=False): # SET else: - + if "ttl" == key: try: ttl = int(value) @@ -785,6 +819,7 @@ def domain_setting(domain, key, value=None, delete=False): domain_settings[key] = value _set_domain_settings(domain, domain_settings) + def _get_domain_settings(domain, subdomains): """ Get settings of a domain @@ -826,12 +861,13 @@ def _set_domain_settings(domain, domain_settings): domains[domain] = domain_settings # Save the settings to the .yaml file - with open(DOMAIN_SETTINGS_PATH, 'w') as file: + with open(DOMAIN_SETTINGS_PATH, "w") as file: yaml.dump(domains, file, default_flow_style=False) + # def domain_get_registrar(): def domain_registrar_set(domain, registrar, args): - + domains = _load_domain_settings() if not domain in domains.keys(): raise YunohostError("domain_name_unknown", domain=domain) @@ -840,39 +876,33 @@ def domain_registrar_set(domain, registrar, args): if not registrar in registrars.keys(): # FIXME créer l'erreur raise YunohostError("registrar_unknown") - + parameters = registrars[registrar] ask_args = [] for parameter in parameters: - ask_args.append({ - 'name' : parameter, - 'type': 'string', - 'example': '', - 'default': '', - }) + ask_args.append( + { + "name": parameter, + "type": "string", + "example": "", + "default": "", + } + ) args_dict = ( {} if not args else dict(urllib.parse.parse_qsl(args, keep_blank_values=True)) ) parsed_answer_dict = _parse_args_in_yunohost_format(args_dict, ask_args) - domain_provider = { - 'name': registrar, - 'options': { - - } - } + domain_provider = {"name": registrar, "options": {}} for arg_name, arg_value_and_type in parsed_answer_dict.items(): - domain_provider['options'][arg_name] = arg_value_and_type[0] - + domain_provider["options"][arg_name] = arg_value_and_type[0] + domain_settings = domains[domain] domain_settings["provider"] = domain_provider # Save the settings to the .yaml file - with open(DOMAIN_SETTINGS_PATH, 'w') as file: + with open(DOMAIN_SETTINGS_PATH, "w") as file: yaml.dump(domains, file, default_flow_style=False) - - - def domain_push_config(domain): @@ -893,7 +923,6 @@ def domain_push_config(domain): # FIXME add locales raise YunohostValidationError("registrar_is_not_set", domain=domain) - # Flatten the DNS conf flatten_dns_conf = [] for key in dns_conf: @@ -911,7 +940,7 @@ def domain_push_config(domain): # Construct the base data structure to use lexicon's API. base_config = { "provider_name": provider["name"], - "domain": domain, # domain name + "domain": domain, # domain name } base_config[provider["name"]] = provider["options"] @@ -929,16 +958,20 @@ def domain_push_config(domain): "action": "list", "type": key, } - final_lexicon = ConfigResolver().with_dict(dict_object=base_config).with_dict(dict_object=record_config) + final_lexicon = ( + ConfigResolver() + .with_dict(dict_object=base_config) + .with_dict(dict_object=record_config) + ) # print('final_lexicon:', final_lexicon); client = Client(final_lexicon) distant_records[key] = client.execute() for key in types: for distant_record in distant_records[key]: - logger.debug(f"distant_record: {distant_record}"); + logger.debug(f"distant_record: {distant_record}") for local_record in flatten_dns_conf: - print('local_record:', local_record); + print("local_record:", local_record) # Push the records for record in flatten_dns_conf: @@ -948,7 +981,10 @@ def domain_push_config(domain): # is_the_same_record = False for distant_record in distant_records[record["type"]]: - if distant_record["type"] == record["type"] and distant_record["name"] == record["name"]: + if ( + distant_record["type"] == record["type"] + and distant_record["name"] == record["name"] + ): it_exists = True # see previous TODO # if distant_record["ttl"] = ... and distant_record["name"] ... @@ -956,8 +992,12 @@ def domain_push_config(domain): # Finally, push the new record or update the existing one record_config = { - "action": "update" if it_exists else "create", # create, list, update, delete - "type": record["type"], # specify a type for record filtering, case sensitive in some cases. + "action": "update" + if it_exists + else "create", # create, list, update, delete + "type": record[ + "type" + ], # specify a type for record filtering, case sensitive in some cases. "name": record["name"], "content": record["value"], # FIXME Removed TTL, because it doesn't work with Gandi. @@ -965,11 +1005,16 @@ def domain_push_config(domain): # But I think there is another issue with Gandi. Or I'm misusing the API... # "ttl": record["ttl"], } - final_lexicon = ConfigResolver().with_dict(dict_object=base_config).with_dict(dict_object=record_config) + final_lexicon = ( + ConfigResolver() + .with_dict(dict_object=base_config) + .with_dict(dict_object=record_config) + ) client = Client(final_lexicon) - print('pushed_record:', record_config, "→", end=' ') + print("pushed_record:", record_config, "→", end=" ") results = client.execute() - print('results:', results); + print("results:", results) # print("Failed" if results == False else "Ok") + # def domain_config_fetch(domain, key, value): From 6ecb6c5ae1f1870d37d3e5053b9f9a052415fd9f Mon Sep 17 00:00:00 2001 From: Noo Langoo <84576713+noo1ang8@users.noreply.github.com> Date: Wed, 26 May 2021 16:07:41 +0200 Subject: [PATCH 2518/3170] [fix] manpage generation --- data/actionsmap/yunohost.yml | 2 +- doc/manpage.template | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index b2f5a349b..053688036 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -403,7 +403,7 @@ user: help: The key to be added -c: full: --comment - help: Optionnal comment about the key + help: Optional comment about the key ### user_ssh_keys_remove() remove-key: diff --git a/doc/manpage.template b/doc/manpage.template index a246e59ac..33f68a2b5 100644 --- a/doc/manpage.template +++ b/doc/manpage.template @@ -93,7 +93,7 @@ usage: yunohost {{ name }} {{ '{' }}{{ ",".join(value["actions"].keys()) }}{{ '} {# each subcategory #} {% for subcategory_name, subcategory in value.get("subcategories", {}).items() %} {% for action, action_value in subcategory["actions"].items() %} -.SS "yunohost {{ subcategory_name }} {{ name }} {{ action }} \ +.SS "yunohost {{ name }} {{ subcategory_name }} {{ action }} \ {% for argument_name, argument_value in action_value.get("arguments", {}).items() %}\ {% set required=(not str(argument_name).startswith("-")) or argument_value.get("extra", {}).get("required", False) %}\ {% if not required %}[{% endif %}\ From f0ae164afe5fdd6b1fcb6d065090ba1b28cf5b40 Mon Sep 17 00:00:00 2001 From: tofbouf <76905498+tofbouf@users.noreply.github.com> Date: Thu, 27 May 2021 14:28:48 +0200 Subject: [PATCH 2519/3170] Apply realpath to find mounted points to unmount Bindings created by some backup methods appears with their 'real' path in /etc/mtab, that may differ from the original /home/yunohost.backup/tmp/auto_xxx that is passed to _recursive_umount(). This fix applies realpath to the 'directory' parameter passed to _recursive_umount(). Tested OK on my own instance, where backups with Borg were failing (except the first one after a reboot) because of this issue (it was unable to clean temporary dir). --- src/yunohost/backup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 3978e835d..99337b2f8 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -2658,7 +2658,7 @@ def _recursive_umount(directory): points_to_umount = [ line.split(" ")[2] for line in mount_lines - if len(line) >= 3 and line.split(" ")[2].startswith(directory) + if len(line) >= 3 and line.split(" ")[2].startswith(os.path.realpath(directory)) ] everything_went_fine = True From 27a976f5a595e04721be2b17acc51f6629eade9a Mon Sep 17 00:00:00 2001 From: MercierCorentin Date: Fri, 28 May 2021 11:22:11 +0200 Subject: [PATCH 2520/3170] Delete file that shouldn't be committed --- data/bash-completion.d/yunohost | 114 -------------------------------- 1 file changed, 114 deletions(-) delete mode 100644 data/bash-completion.d/yunohost diff --git a/data/bash-completion.d/yunohost b/data/bash-completion.d/yunohost deleted file mode 100644 index 1be522db2..000000000 --- a/data/bash-completion.d/yunohost +++ /dev/null @@ -1,114 +0,0 @@ -# -# completion for yunohost -# automatically generated from the actionsmap -# - -_yunohost() -{ - local cur prev opts narg - COMPREPLY=() - - # the number of words already typed - narg=${#COMP_WORDS[@]} - - # the current word being typed - cur="${COMP_WORDS[COMP_CWORD]}" - - # If one is currently typing a category, - # match with categorys - if [[ $narg == 2 ]]; then - opts="user domain app backup settings service firewall dyndns tools hook log diagnosis" - fi - - # If one already typed a category, - # match the actions or the subcategories of that category - if [[ $narg == 3 ]]; then - # the category typed - category="${COMP_WORDS[1]}" - - if [[ $category == "user" ]]; then - opts="list create delete update info group permission ssh" - fi - if [[ $category == "domain" ]]; then - opts="list add registrar push_config remove dns-conf main-domain cert-status cert-install cert-renew url-available setting " - fi - if [[ $category == "app" ]]; then - opts="catalog search manifest fetchlist list info map install remove upgrade change-url setting register-url makedefault ssowatconf change-label addaccess removeaccess clearaccess action config" - fi - if [[ $category == "backup" ]]; then - opts="create restore list info download delete " - fi - if [[ $category == "settings" ]]; then - opts="list get set reset-all reset " - fi - if [[ $category == "service" ]]; then - opts="add remove start stop reload restart reload_or_restart enable disable status log regen-conf " - fi - if [[ $category == "firewall" ]]; then - opts="list allow disallow upnp reload stop " - fi - if [[ $category == "dyndns" ]]; then - opts="subscribe update installcron removecron " - fi - if [[ $category == "tools" ]]; then - opts="adminpw maindomain postinstall update upgrade shell shutdown reboot regen-conf versions migrations" - fi - if [[ $category == "hook" ]]; then - opts="add remove info list callback exec " - fi - if [[ $category == "log" ]]; then - opts="list show share " - fi - if [[ $category == "diagnosis" ]]; then - opts="list show get run ignore unignore " - fi - fi - - # If one already typed an action or a subcategory, - # match the actions of that subcategory - if [[ $narg == 4 ]]; then - # the category typed - category="${COMP_WORDS[1]}" - - # the action or the subcategory typed - action_or_subcategory="${COMP_WORDS[2]}" - - if [[ $category == "user" ]]; then - if [[ $action_or_subcategory == "group" ]]; then - opts="list create delete info add remove" - fi - if [[ $action_or_subcategory == "permission" ]]; then - opts="list info update add remove reset" - fi - if [[ $action_or_subcategory == "ssh" ]]; then - opts="list-keys add-key remove-key" - fi - fi - if [[ $category == "app" ]]; then - if [[ $action_or_subcategory == "action" ]]; then - opts="list run" - fi - if [[ $action_or_subcategory == "config" ]]; then - opts="show-panel apply" - fi - fi - if [[ $category == "tools" ]]; then - if [[ $action_or_subcategory == "migrations" ]]; then - opts="list run state" - fi - fi - fi - - # If no options were found propose --help - if [ -z "$opts" ]; then - prev="${COMP_WORDS[COMP_CWORD-1]}" - - if [[ $prev != "--help" ]]; then - opts=( --help ) - fi - fi - COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) - return 0 -} - -complete -F _yunohost yunohost \ No newline at end of file From ec017d7ea52d9b11439f2e93f7151408bd6a5fe0 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 29 May 2021 16:48:19 +0200 Subject: [PATCH 2521/3170] Tweak systemd action pending message : echo -n is pointless --- data/helpers.d/systemd | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/data/helpers.d/systemd b/data/helpers.d/systemd index a1baff4b0..09f37844c 100644 --- a/data/helpers.d/systemd +++ b/data/helpers.d/systemd @@ -145,11 +145,8 @@ ynh_systemd_action() { ynh_print_info --message="The service $service_name has correctly executed the action ${action}." break fi - if [ $i -eq 3 ]; then - echo -n "Please wait, the service $service_name is ${action}ing" >&2 - fi - if [ $i -ge 3 ]; then - echo -n "." >&2 + if [ $i -eq 30 ]; then + echo "(this may take some time)" >&2 fi sleep 1 done From 00098075fdb30812a5086456b9b7649d1ae24425 Mon Sep 17 00:00:00 2001 From: Paco Date: Sat, 29 May 2021 19:15:13 +0200 Subject: [PATCH 2522/3170] Split domains.yml into domains/{domain}.yml --- src/yunohost/domain.py | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 8677e1685..60711667a 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -53,7 +53,7 @@ from yunohost.hook import hook_callback logger = getActionLogger("yunohost.domain") -DOMAIN_SETTINGS_PATH = "/etc/yunohost/domains.yml" +DOMAIN_SETTINGS_DIR = "/etc/yunohost/domains" REGISTRAR_LIST_PATH = "/usr/share/yunohost/other/providers_list.yml" @@ -732,39 +732,38 @@ def _load_domain_settings(): Retrieve entries in domains.yml And fill the holes if any """ - # Retrieve entries in the YAML - old_domains = None - if os.path.exists(DOMAIN_SETTINGS_PATH) and os.path.isfile(DOMAIN_SETTINGS_PATH): - old_domains = yaml.load(open(DOMAIN_SETTINGS_PATH, "r+")) - - if old_domains is None: - old_domains = dict() + # Retrieve actual domain list + get_domain_list = domain_list() # Create sanitized data new_domains = dict() - get_domain_list = domain_list() - # Load main domain maindomain = get_domain_list["main"] for domain in get_domain_list["domains"]: + # Retrieve entries in the YAML + filepath = f"{DOMAIN_SETTINGS_DIR}/{domain}.yml" + old_domain = {} + if os.path.exists(filepath) and os.path.isfile(filepath): + old_domain = yaml.load(open(filepath, "r+")) + # If the file is empty or "corrupted" + if not type(old_domain) is set: + old_domain = {} is_maindomain = domain == maindomain default_owned_dns_zone = True if domain == get_public_suffix(domain) else False - domain_in_old_domains = domain in old_domains.keys() # Update each setting if not present new_domains[domain] = {} - # new_domains[domain] = { "main": is_maindomain } # Set other values (default value if missing) for setting, default in [ ("xmpp", is_maindomain), ("mail", True), ("owned_dns_zone", default_owned_dns_zone), ("ttl", 3600), - ("provider", False), + ("provider", {}), ]: - if domain_in_old_domains and setting in old_domains[domain].keys(): - new_domains[domain][setting] = old_domains[domain][setting] + if old_domain != {} and setting in old_domain.keys(): + new_domains[domain][setting] = old_domain[setting] else: new_domains[domain][setting] = default @@ -858,11 +857,13 @@ def _set_domain_settings(domain, domain_settings): if not domain in domains.keys(): raise YunohostError("domain_name_unknown", domain=domain) - domains[domain] = domain_settings - + # First create the DOMAIN_SETTINGS_DIR if it doesn't exist + if not os.path.exists(DOMAIN_SETTINGS_DIR): + os.mkdir(DOMAIN_SETTINGS_DIR) # Save the settings to the .yaml file - with open(DOMAIN_SETTINGS_PATH, "w") as file: - yaml.dump(domains, file, default_flow_style=False) + filepath = f"{DOMAIN_SETTINGS_DIR}/{domain}.yml" + with open(filepath, "w") as file: + yaml.dump(domain_settings, file, default_flow_style=False) # def domain_get_registrar(): @@ -901,8 +902,7 @@ def domain_registrar_set(domain, registrar, args): domain_settings["provider"] = domain_provider # Save the settings to the .yaml file - with open(DOMAIN_SETTINGS_PATH, "w") as file: - yaml.dump(domains, file, default_flow_style=False) + _set_domain_settings(domain, domain_settings) def domain_push_config(domain): From 3022a4756047f870233f20cb8de13731a113f47a Mon Sep 17 00:00:00 2001 From: Paco Date: Sat, 29 May 2021 19:39:59 +0200 Subject: [PATCH 2523/3170] Now using Dict.update() when loading settings Settings not anticipated will be loaded. They will not be removed on write. Original behavior: not anticipated keys are removed. --- src/yunohost/domain.py | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 60711667a..3c0fb479a 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -744,28 +744,26 @@ def _load_domain_settings(): for domain in get_domain_list["domains"]: # Retrieve entries in the YAML filepath = f"{DOMAIN_SETTINGS_DIR}/{domain}.yml" - old_domain = {} + on_disk_settings = {} if os.path.exists(filepath) and os.path.isfile(filepath): - old_domain = yaml.load(open(filepath, "r+")) + on_disk_settings = yaml.load(open(filepath, "r+")) # If the file is empty or "corrupted" - if not type(old_domain) is set: - old_domain = {} + if not type(on_disk_settings) is dict: + on_disk_settings = {} + # Generate defaults is_maindomain = domain == maindomain default_owned_dns_zone = True if domain == get_public_suffix(domain) else False + default_settings = { + "xmpp": is_maindomain, + "mail": True, + "owned_dns_zone": default_owned_dns_zone, + "ttl": 3600, + "provider": {}, + } # Update each setting if not present - new_domains[domain] = {} - # Set other values (default value if missing) - for setting, default in [ - ("xmpp", is_maindomain), - ("mail", True), - ("owned_dns_zone", default_owned_dns_zone), - ("ttl", 3600), - ("provider", {}), - ]: - if old_domain != {} and setting in old_domain.keys(): - new_domains[domain][setting] = old_domain[setting] - else: - new_domains[domain][setting] = default + default_settings.update(on_disk_settings) + # Add the domain to the list + new_domains[domain] = default_settings return new_domains From c819dbb750922090cf038696dd1c1c1f3881315d Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Tue, 1 Jun 2021 16:09:04 +0200 Subject: [PATCH 2524/3170] [enh] Accept attachment of 25MB instead of 21,8MB --- data/templates/postfix/main.cf | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/data/templates/postfix/main.cf b/data/templates/postfix/main.cf index cdf6aaf96..76d09c1cb 100644 --- a/data/templates/postfix/main.cf +++ b/data/templates/postfix/main.cf @@ -89,8 +89,11 @@ mailbox_size_limit = 0 recipient_delimiter = + inet_interfaces = all -#### Fit to the maximum message size to 30mb, more than allowed by GMail or Yahoo #### -message_size_limit = 31457280 +#### Fit to the maximum message size to 25mb, more than allowed by GMail or Yahoo #### +# /!\ This size is the size of the attachment in base64. +# Base64_SIZE = ORIGINAL_SIZE * 1,37 *1024*1024 + 980 +# See https://serverfault.com/questions/346895/postfix-mail-size-counting +message_size_limit = 35914708 # Virtual Domains Control virtual_mailbox_domains = ldap:/etc/postfix/ldap-domains.cf From f84c1f9af0b0e594092ccbe1cbb0e69a3d30762a Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Tue, 1 Jun 2021 16:12:35 +0200 Subject: [PATCH 2525/3170] [enh] Add units in comments --- 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 76d09c1cb..257783109 100644 --- a/data/templates/postfix/main.cf +++ b/data/templates/postfix/main.cf @@ -91,7 +91,7 @@ inet_interfaces = all #### Fit to the maximum message size to 25mb, more than allowed by GMail or Yahoo #### # /!\ This size is the size of the attachment in base64. -# Base64_SIZE = ORIGINAL_SIZE * 1,37 *1024*1024 + 980 +# BASE64_SIZE_IN_BYTE = ORIGINAL_SIZE_IN_MEGABYTE * 1,37 *1024*1024 + 980 # See https://serverfault.com/questions/346895/postfix-mail-size-counting message_size_limit = 35914708 From b8ce5f803c1cdf768d6de8a93295c1f2478fb336 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= Date: Mon, 24 May 2021 15:45:38 +0000 Subject: [PATCH 2526/3170] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (633 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/zh_Hans/ --- locales/zh_Hans.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/locales/zh_Hans.json b/locales/zh_Hans.json index e2f6d7644..a65e3d725 100644 --- a/locales/zh_Hans.json +++ b/locales/zh_Hans.json @@ -32,7 +32,7 @@ "diagnosis_basesystem_hardware_model": "服务器型号为 {model}", "diagnosis_basesystem_hardware": "服务器硬件架构为{virt} {arch}", "custom_app_url_required": "您必须提供URL才能升级自定义应用 {app:s}", - "confirm_app_install_thirdparty": "危险! 该应用程序不是Yunohost的应用程序目录的一部分。 安装第三方应用程序可能会损害系统的完整性和安全性。 除非您知道自己在做什么,否则可能不应该安装它, 如果此应用无法运行或无法正常使用系统,将不会提供任何支持。如果您仍然愿意承担此风险,请输入'{answers:s}'", + "confirm_app_install_thirdparty": "危险! 该应用程序不是YunoHost的应用程序目录的一部分。 安装第三方应用程序可能会损害系统的完整性和安全性。 除非您知道自己在做什么,否则可能不应该安装它, 如果此应用无法运行或无法正常使用系统,将不会提供任何支持。如果您仍然愿意承担此风险,请输入'{answers:s}'", "confirm_app_install_danger": "危险! 已知此应用仍处于实验阶段(如果未明确无法正常运行)! 除非您知道自己在做什么,否则可能不应该安装它。 如果此应用无法运行或无法正常使用系统,将不会提供任何支持。如果您仍然愿意承担此风险,请输入'{answers:s}'", "confirm_app_install_warning": "警告:此应用程序可能可以运行,但未与YunoHost很好地集成。某些功能(例如单点登录和备份/还原)可能不可用, 仍要安装吗? [{answers:s}] ", "certmanager_unable_to_parse_self_CA_name": "无法解析自签名授权的名称 (file: {file:s})", @@ -466,7 +466,7 @@ "diagnosis_ip_connected_ipv4": "服务器通过IPv4连接到Internet!", "diagnosis_no_cache": "尚无类别 '{category}'的诊断缓存", "diagnosis_failed": "无法获取类别 '{category}'的诊断结果: {error}", - "diagnosis_package_installed_from_sury_details": "一些软件包被无意中从一个名为Sury的第三方仓库安装。Yunohost团队改进了处理这些软件包的策略,但预计一些安装了PHP7.3应用程序的设置在仍然使用Stretch的情况下还有一些不一致的地方。为了解决这种情况,你应该尝试运行以下命令:{cmd_to_fix}", + "diagnosis_package_installed_from_sury_details": "一些软件包被无意中从一个名为Sury的第三方仓库安装。YunoHost团队改进了处理这些软件包的策略,但预计一些安装了PHP7.3应用程序的设置在仍然使用Stretch的情况下还有一些不一致的地方。为了解决这种情况,你应该尝试运行以下命令:{cmd_to_fix}", "app_not_installed": "在已安装的应用列表中找不到 {app:s}:{all_apps}", "app_already_installed_cant_change_url": "这个应用程序已经被安装。URL不能仅仅通过这个函数来改变。在`app changeurl`中检查是否可用。", "restore_not_enough_disk_space": "没有足够的空间(空间: {free_space:d} B,需要的空间: {needed_space:d} B,安全系数: {margin:d} B)", @@ -474,7 +474,7 @@ "regenconf_up_to_date": "类别'{category}'的配置已经是最新的", "regenconf_file_kept_back": "配置文件'{conf}'预计将被regen-conf(类别{category})删除,但被保留了下来。", "good_practices_about_user_password": "选择至少8个字符的用户密码-尽管使用较长的用户密码(即密码短语)和/或使用各种字符(大写,小写,数字和特殊字符)是一种很好的做法。", - "global_settings_setting_smtp_relay_host": "使用SMTP中继主机来代替这个yunohost实例发送邮件。如果你有以下情况,就很有用:你的25端口被你的ISP或VPS提供商封锁,你有一个住宅IP列在DUHL上,你不能配置反向DNS,或者这个服务器没有直接暴露在互联网上,你想使用其他服务器来发送邮件。", + "global_settings_setting_smtp_relay_host": "使用SMTP中继主机来代替这个YunoHost实例发送邮件。如果你有以下情况,就很有用:你的25端口被你的ISP或VPS提供商封锁,你有一个住宅IP列在DUHL上,你不能配置反向DNS,或者这个服务器没有直接暴露在互联网上,你想使用其他服务器来发送邮件。", "domain_cannot_remove_main_add_new_one": "你不能删除'{domain:s}',因为它是主域和你唯一的域,你需要先用'yunohost domain add '添加另一个域,然后用'yunohost domain main-domain -n '设置为主域,然后你可以用'yunohost domain remove {domain:s}'删除域", "domain_cannot_add_xmpp_upload": "你不能添加以'xmpp-upload.'开头的域名。这种名称是为YunoHost中集成的XMPP上传功能保留的。", "domain_cannot_remove_main": "你不能删除'{domain:s}',因为它是主域,你首先需要用'yunohost domain main-domain -n '设置另一个域作为主域;这里是候选域的列表: {other_domains:s}", @@ -496,7 +496,7 @@ "diagnosis_diskusage_low": "存储器{mountpoint}(在设备{device}上)只有{free} ({free_percent}%) 的空间。({free_percent}%)的剩余空间(在{total}中)。要小心。", "diagnosis_diskusage_verylow": "存储器{mountpoint}(在设备{device}上)仅剩余{free} ({free_percent}%) (剩余{total})个空间。您应该真正考虑清理一些空间!", "diagnosis_services_bad_status_tip": "你可以尝试重新启动服务,如果没有效果,可以看看webadmin中的服务日志(从命令行,你可以用yunohost service restart {service}yunohost service log {service})来做。", - "diagnosis_dns_try_dyndns_update_force": "该域的DNS配置应由Yunohost自动管理,如果不是这种情况,您可以尝试使用 yunohost dyndns update --force强制进行更新。", + "diagnosis_dns_try_dyndns_update_force": "该域的DNS配置应由YunoHost自动管理,如果不是这种情况,您可以尝试使用 yunohost dyndns update --force强制进行更新。", "diagnosis_dns_point_to_doc": "如果您需要有关配置DNS记录的帮助,请查看 https://yunohost.org/dns_config 上的文档。", "diagnosis_dns_discrepancy": "以下DNS记录似乎未遵循建议的配置:
类型: {type}
名称: {name}
代码> 当前值: {current}期望值: {value}", "log_backup_create": "创建备份档案", From cba3e7eef771abbfb1c9bd5f2ff8db376d73a1cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M?= Date: Tue, 25 May 2021 03:09:19 +0000 Subject: [PATCH 2527/3170] Translated using Weblate (Galician) Currently translated at 2.3% (15 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/gl/ --- locales/gl.json | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/locales/gl.json b/locales/gl.json index 91c77ba6f..d945ad9ec 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -1,4 +1,17 @@ { "password_too_simple_1": "O contrasinal ten que ter 8 caracteres como mínimo", - "aborting": "Abortando." + "aborting": "Abortando.", + "app_already_up_to_date": "{app:s} xa está actualizada", + "app_already_installed_cant_change_url": "Esta app xa está instalada. O URL non pode cambiarse só con esta acción. Miran en `app changeurl` se está dispoñible.", + "app_already_installed": "{app:s} xa está instalada", + "app_action_broke_system": "Esta acción semella que estragou estos servizos importantes: {services}", + "app_action_cannot_be_ran_because_required_services_down": "Estos servizos requeridos deberían estar en execución para realizar esta acción: {services}. Intenta reinicialos para continuar (e tamén intenta saber por que están apagados).", + "already_up_to_date": "Nada que facer. Todo está ao día.", + "admin_password_too_long": "Elixe un contrasinal menor de 127 caracteres", + "admin_password_changed": "Realizado o cambio de contrasinal de administración", + "admin_password_change_failed": "Non se puido cambiar o contrasinal", + "admin_password": "Contrasinal de administración", + "additional_urls_already_removed": "URL adicional '{url:s}' xa foi eliminada das URL adicionais para o permiso '{permission:s}'", + "additional_urls_already_added": "URL adicional '{url:s}' xa fora engadida ás URL adicionais para o permiso '{permission:s}'", + "action_invalid": "Acción non válida '{action:s}'" } From 7455f2f40d7fec7c5a1e31b75824e763b9efb167 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?yahoo=EF=BD=9E=EF=BD=9E?= Date: Wed, 26 May 2021 09:23:15 +0000 Subject: [PATCH 2528/3170] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (633 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/zh_Hans/ --- locales/zh_Hans.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/locales/zh_Hans.json b/locales/zh_Hans.json index a65e3d725..2819f53c5 100644 --- a/locales/zh_Hans.json +++ b/locales/zh_Hans.json @@ -285,8 +285,8 @@ "user_created": "用户创建", "user_already_exists": "用户'{user}' 已存在", "upnp_port_open_failed": "无法通过UPnP打开端口", - "upnp_enabled": "UPnP已开启", - "upnp_disabled": "UPnP已关闭", + "upnp_enabled": "UPnP已启用", + "upnp_disabled": "UPnP已禁用", "yunohost_not_installed": "YunoHost没有正确安装,请运行 'yunohost tools postinstall'", "yunohost_postinstall_end_tip": "后期安装完成! 为了最终完成你的设置,请考虑:\n -通过webadmin的“用户”部分添加第一个用户(或在命令行中'yunohost user create ' );\n -通过网络管理员的“诊断”部分(或命令行中的'yunohost diagnosis run')诊断潜在问题;\n -阅读管理文档中的“完成安装设置”和“了解Yunohost”部分: https://yunohost.org/admindoc.", "operation_interrupted": "该操作是否被手动中断?", @@ -314,7 +314,7 @@ "group_already_exist_on_system_but_removing_it": "系统组中已经存在组{group},但是YunoHost会将其删除...", "group_already_exist_on_system": "系统组中已经存在组{group}", "group_already_exist": "群组{group}已经存在", - "good_practices_about_admin_password": "现在,您将定义一个新的管理密码。密码长度至少应为8个字符-尽管优良作法是使用较长的密码(即密码短语)和/或使用各种字符(大写,小写,数字和特殊字符)。", + "good_practices_about_admin_password": "现在,您将设置一个新的管理员密码。 密码至少应包含8个字符。并且出于安全考虑建议使用较长的密码同时尽可能使用各种字符(大写,小写,数字和特殊字符)。", "global_settings_unknown_type": "意外的情况,设置{setting:s}似乎具有类型 {unknown_type:s} ,但是系统不支持该类型。", "global_settings_setting_backup_compress_tar_archives": "创建新备份时,请压缩档案(.tar.gz) ,而不要压缩未压缩的档案(.tar)。注意:启用此选项意味着创建较小的备份存档,但是初始备份过程将明显更长且占用大量CPU。", "global_settings_setting_smtp_relay_password": "SMTP中继主机密码", @@ -370,7 +370,7 @@ "domain_exists": "该域已存在", "domain_dyndns_root_unknown": "未知的DynDNS根域", "domain_dyndns_already_subscribed": "您已经订阅了DynDNS域", - "domain_dns_conf_is_just_a_recommendation": "此命令向您显示*推荐*配置。它实际上并没有为您设置DNS配置。根据此建议,您有责任在注册服务商中配置DNS区域。", + "domain_dns_conf_is_just_a_recommendation": "本页向你展示了*推荐的*配置。它并*不*为你配置DNS。你有责任根据该建议在你的DNS注册商处配置你的DNS区域。", "domain_deletion_failed": "无法删除域 {domain}: {error}", "domain_deleted": "域已删除", "domain_creation_failed": "无法创建域 {domain}: {error}", @@ -473,7 +473,7 @@ "regenconf_pending_applying": "正在为类别'{category}'应用挂起的配置..", "regenconf_up_to_date": "类别'{category}'的配置已经是最新的", "regenconf_file_kept_back": "配置文件'{conf}'预计将被regen-conf(类别{category})删除,但被保留了下来。", - "good_practices_about_user_password": "选择至少8个字符的用户密码-尽管使用较长的用户密码(即密码短语)和/或使用各种字符(大写,小写,数字和特殊字符)是一种很好的做法。", + "good_practices_about_user_password": "现在,您将设置一个新的管理员密码。 密码至少应包含8个字符。并且出于安全考虑建议使用较长的密码同时尽可能使用各种字符(大写,小写,数字和特殊字符)", "global_settings_setting_smtp_relay_host": "使用SMTP中继主机来代替这个YunoHost实例发送邮件。如果你有以下情况,就很有用:你的25端口被你的ISP或VPS提供商封锁,你有一个住宅IP列在DUHL上,你不能配置反向DNS,或者这个服务器没有直接暴露在互联网上,你想使用其他服务器来发送邮件。", "domain_cannot_remove_main_add_new_one": "你不能删除'{domain:s}',因为它是主域和你唯一的域,你需要先用'yunohost domain add '添加另一个域,然后用'yunohost domain main-domain -n '设置为主域,然后你可以用'yunohost domain remove {domain:s}'删除域", "domain_cannot_add_xmpp_upload": "你不能添加以'xmpp-upload.'开头的域名。这种名称是为YunoHost中集成的XMPP上传功能保留的。", From 0ae605f0eeb2982340913fd9554b9014a23e5f8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= Date: Thu, 27 May 2021 12:11:22 +0000 Subject: [PATCH 2529/3170] Translated using Weblate (French) Currently translated at 100.0% (633 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 715d82a35..e6bfacd1e 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -305,7 +305,7 @@ "backup_mount_archive_for_restore": "Préparation de l’archive pour restauration...", "confirm_app_install_warning": "Avertissement : cette application peut fonctionner mais n’est pas bien intégrée dans YunoHost. Certaines fonctionnalités telles que l’authentification unique et la sauvegarde/restauration peuvent ne pas être disponibles. L’installer quand même ? [{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}'", + "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 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.", @@ -569,7 +569,7 @@ "migration_0015_patching_sources_list": "Mise à jour du fichier sources.lists...", "migration_0015_start": "Démarrage de la migration vers Buster", "migration_description_0015_migrate_to_buster": "Mise à niveau du système vers Debian Buster et YunoHost 4.x", - "diagnosis_dns_try_dyndns_update_force": "La configuration DNS de ce domaine devrait être automatiquement gérée par Yunohost. Si ce n'est pas le cas, vous pouvez essayer de forcer une mise à jour en utilisant yunohost dyndns update --force.", + "diagnosis_dns_try_dyndns_update_force": "La configuration DNS de ce domaine devrait être automatiquement gérée par YunoHost. Si ce n'est pas le cas, vous pouvez essayer de forcer une mise à jour en utilisant yunohost dyndns update --force.", "app_packaging_format_not_supported": "Cette application ne peut pas être installée car son format n'est pas pris en charge par votre version de YunoHost. Vous devriez probablement envisager de mettre à jour votre système.", "migration_0015_weak_certs": "Il a été constaté que les certificats suivants utilisent encore des algorithmes de signature peu robustes et doivent être mis à jour pour être compatibles avec la prochaine version de NGINX : {certs}", "global_settings_setting_backup_compress_tar_archives": "Compresser les archives (.tar.gz) au lieu des archives non-compressées lors de la création des backups. N.B. : activer cette option permet d'obtenir des sauvegardes plus légères, mais leur création sera significativement plus longue et plus gourmande en CPU.", @@ -592,7 +592,7 @@ "global_settings_setting_smtp_relay_user": "Relais de compte utilisateur SMTP", "global_settings_setting_smtp_relay_port": "Port relais SMTP", "global_settings_setting_smtp_relay_host": "Relais SMTP à utiliser pour envoyer du courrier à la place de cette instance YunoHost. Utile si vous êtes dans l'une de ces situations : votre port 25 est bloqué par votre FAI ou votre fournisseur VPS, vous avez une IP résidentielle répertoriée sur DUHL, vous ne pouvez pas configurer de DNS inversé ou ce serveur n'est pas directement exposé sur Internet et vous voulez en utiliser un autre pour envoyer des mails.", - "diagnosis_package_installed_from_sury_details": "Certains paquets ont été installés par inadvertance à partir d'un dépôt tiers appelé Sury. L'équipe YunoHost a amélioré la stratégie de gestion de ces paquets, mais on s'attend à ce que certaines configurations qui ont installé des applications PHP7.3 tout en étant toujours sur Stretch présentent des incohérences. Pour résoudre cette situation, vous devez essayer d'exécuter la commande suivante : {cmd_to_fix} ", + "diagnosis_package_installed_from_sury_details": "Certains paquets ont été installés par inadvertance à partir d'un dépôt tiers appelé Sury. L'équipe YunoHost a amélioré la stratégie de gestion de ces paquets, mais on s'attend à ce que certaines configurations qui ont installé des applications PHP7.3 tout en étant toujours sur Stretch présentent des incohérences. Pour résoudre cette situation, vous devez essayer d'exécuter la commande suivante : {cmd_to_fix}", "app_argument_password_no_default": "Erreur lors de l'analyse de l'argument de mot de passe '{name}' : l'argument de mot de passe ne peut pas avoir de valeur par défaut pour des raisons de sécurité", "pattern_email_forward": "Il doit s'agir d'une adresse électronique valide, le symbole '+' étant accepté (par exemples : johndoe@exemple.com ou bien johndoe+yunohost@exemple.com)", "global_settings_setting_smtp_relay_password": "Mot de passe du relais de l'hôte SMTP", From 65ce4662d695b36a4fec82ff3c2a18fa4477e164 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M?= Date: Fri, 28 May 2021 05:38:07 +0000 Subject: [PATCH 2530/3170] Translated using Weblate (Galician) Currently translated at 3.1% (20 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/gl/ --- locales/gl.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/locales/gl.json b/locales/gl.json index d945ad9ec..74bad8725 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -13,5 +13,10 @@ "admin_password": "Contrasinal de administración", "additional_urls_already_removed": "URL adicional '{url:s}' xa foi eliminada das URL adicionais para o permiso '{permission:s}'", "additional_urls_already_added": "URL adicional '{url:s}' xa fora engadida ás URL adicionais para o permiso '{permission:s}'", - "action_invalid": "Acción non válida '{action:s}'" + "action_invalid": "Acción non válida '{action:s}'", + "app_change_url_failed_nginx_reload": "Non se recargou NGINX. Aquí tes a saída de 'nginx -t':\n{nginx_errors:s}", + "app_argument_required": "Requírese o argumento '{name}'", + "app_argument_password_no_default": "Erro ao procesar o argumento do contrasinal '{name}': o argumento do contrasinal non pode ter un valor por defecto por razón de seguridade", + "app_argument_invalid": "Elixe un valor válido para o argumento '{name:s}': {error:s}", + "app_argument_choice_invalid": "Usa unha destas opcións '{choices:s}' para o argumento '{name:s}'" } From adcc680478ed5b294b4e008b8cc435ad910ed5ac Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Sat, 29 May 2021 09:11:49 +0000 Subject: [PATCH 2531/3170] Translated using Weblate (German) Currently translated at 91.3% (578 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/de.json b/locales/de.json index 2d369e63a..9e6bb3a81 100644 --- a/locales/de.json +++ b/locales/de.json @@ -194,7 +194,7 @@ "dyndns_could_not_check_provide": "Konnte nicht überprüft, ob {provider:s} die Domain(s) {domain:s} bereitstellen kann.", "domain_dns_conf_is_just_a_recommendation": "Dieser Befehl zeigt Ihnen die * empfohlene * Konfiguration. Die DNS-Konfiguration wird NICHT für Sie eingerichtet. Es liegt in Ihrer Verantwortung, Ihre DNS-Zone in Ihrem Registrar gemäß dieser Empfehlung zu konfigurieren.", "dpkg_lock_not_available": "Dieser Befehl kann momentan nicht ausgeführt werden, da anscheinend ein anderes Programm die Sperre von dpkg (dem Systempaket-Manager) verwendet", - "confirm_app_install_thirdparty": "WARNUNG! Das Installieren von Anwendungen von Drittanbietern kann die Integrität und Sicherheit Ihres Systems beeinträchtigen. Sie sollten Sie wahrscheinlich NICHT installieren, es sei denn, Sie wiẞen, was Sie tun. Sind Sie bereit, dieses Risiko einzugehen? [{answers:s}]", + "confirm_app_install_thirdparty": "WARNUNG! Diese App ist nicht Teil von YunoHosts App-Katalog. Das Installieren von Drittanbieteranwendungen könnte die Sicherheit und Integrität des System beeinträchtigen. Sie sollten wahrscheinlich NICHT fortfahren, es sei denn, Sie wissen, was Sie tun. Es wird KEIN SUPPORT zur Verfügung stehen, wenn die App nicht funktioniert oder das System zerstört... Wenn Sie das Risiko trotzdem eingehen möchten, tippen Sie '{answers:s}'", "confirm_app_install_danger": "WARNUNG! Diese Anwendung ist noch experimentell (wenn nicht ausdrücklich \"not working\"/\"nicht funktionsfähig\")! Sie sollten sie wahrscheinlich NICHT installieren, es sei denn, Sie wißen, was Sie tun. Es wird keine Unterstützung geleistet, falls diese Anwendung nicht funktioniert oder Ihr System zerstört... Falls Sie bereit bist, dieses Risiko einzugehen, tippe '{answers:s}'", "confirm_app_install_warning": "Warnung: Diese Anwendung funktioniert möglicherweise, ist jedoch nicht gut in YunoHost integriert. Einige Funktionen wie Single Sign-On und Backup / Restore sind möglicherweise nicht verfügbar. Trotzdem installieren? [{answers:s}] ", "backup_with_no_restore_script_for_app": "{app:s} hat kein Wiederherstellungsskript. Das Backup dieser App kann nicht automatisch wiederhergestellt werden.", From 802db0cc2a42c3e5163b036ea0808134c46e12ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?yahoo=EF=BD=9E=EF=BD=9E?= Date: Sat, 29 May 2021 07:49:00 +0000 Subject: [PATCH 2532/3170] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (633 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/zh_Hans/ --- locales/zh_Hans.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/locales/zh_Hans.json b/locales/zh_Hans.json index 2819f53c5..83ec4f850 100644 --- a/locales/zh_Hans.json +++ b/locales/zh_Hans.json @@ -288,7 +288,7 @@ "upnp_enabled": "UPnP已启用", "upnp_disabled": "UPnP已禁用", "yunohost_not_installed": "YunoHost没有正确安装,请运行 'yunohost tools postinstall'", - "yunohost_postinstall_end_tip": "后期安装完成! 为了最终完成你的设置,请考虑:\n -通过webadmin的“用户”部分添加第一个用户(或在命令行中'yunohost user create ' );\n -通过网络管理员的“诊断”部分(或命令行中的'yunohost diagnosis run')诊断潜在问题;\n -阅读管理文档中的“完成安装设置”和“了解Yunohost”部分: https://yunohost.org/admindoc.", + "yunohost_postinstall_end_tip": "后期安装完成! 为了最终完成你的设置,请考虑:\n -通过webadmin的“用户”部分添加第一个用户(或在命令行中'yunohost user create ' );\n -通过网络管理员的“诊断”部分(或命令行中的'yunohost diagnosis run')诊断潜在问题;\n -阅读管理文档中的“完成安装设置”和“了解YunoHost”部分: https://yunohost.org/admindoc.", "operation_interrupted": "该操作是否被手动中断?", "invalid_regex": "无效的正则表达式:'{regex:s}'", "installation_failed": "安装出现问题", @@ -376,7 +376,7 @@ "domain_creation_failed": "无法创建域 {domain}: {error}", "domain_created": "域已创建", "domain_cert_gen_failed": "无法生成证书", - "diagnosis_sshd_config_inconsistent": "看起来SSH端口是在/etc/ssh/sshd_config中手动修改, 从Yunohost 4.2开始,可以使用新的全局设置“ security.ssh.port”来避免手动编辑配置。", + "diagnosis_sshd_config_inconsistent": "看起来SSH端口是在/etc/ssh/sshd_config中手动修改, 从YunoHost 4.2开始,可以使用新的全局设置“ security.ssh.port”来避免手动编辑配置。", "diagnosis_sshd_config_insecure": "SSH配置似乎已被手动修改,并且是不安全的,因为它不包含“ AllowGroups”或“ AllowUsers”指令以限制对授权用户的访问。", "diagnosis_processes_killed_by_oom_reaper": "该系统最近杀死了某些进程,因为内存不足。这通常是系统内存不足或进程占用大量内存的征兆。 杀死进程的摘要:\n{kills_summary}", "diagnosis_never_ran_yet": "看来这台服务器是最近安装的,还没有诊断报告可以显示。您应该首先从Web管理员运行完整的诊断,或者从命令行使用'yunohost diagnosis run' 。", @@ -478,7 +478,7 @@ "domain_cannot_remove_main_add_new_one": "你不能删除'{domain:s}',因为它是主域和你唯一的域,你需要先用'yunohost domain add '添加另一个域,然后用'yunohost domain main-domain -n '设置为主域,然后你可以用'yunohost domain remove {domain:s}'删除域", "domain_cannot_add_xmpp_upload": "你不能添加以'xmpp-upload.'开头的域名。这种名称是为YunoHost中集成的XMPP上传功能保留的。", "domain_cannot_remove_main": "你不能删除'{domain:s}',因为它是主域,你首先需要用'yunohost domain main-domain -n '设置另一个域作为主域;这里是候选域的列表: {other_domains:s}", - "diagnosis_sshd_config_inconsistent_details": "请运行yunohost settings set security.ssh.port -v YOUR_SSH_PORT来定义SSH端口,并检查yunohost tools regen-conf ssh --dry-run --with-diffyunohost tools regen-conf ssh --force将您的配置重置为Yunohost建议。", + "diagnosis_sshd_config_inconsistent_details": "请运行yunohost settings set security.ssh.port -v YOUR_SSH_PORT来定义SSH端口,并检查yunohost tools regen-conf ssh --dry-run --with-diffyunohost tools regen-conf ssh --force将您的配置重置为YunoHost建议。", "diagnosis_http_bad_status_code": "它看起来像另一台机器(也许是你的互联网路由器)回答,而不是你的服务器。
1。这个问题最常见的原因是80端口(和443端口)没有正确转发到您的服务器
2.在更复杂的设置中:确保没有防火墙或反向代理的干扰。", "diagnosis_http_timeout": "当试图从外部联系你的服务器时,出现了超时。它似乎是不可达的。
1. 这个问题最常见的原因是80端口(和443端口)没有正确转发到你的服务器
2.你还应该确保nginx服务正在运行
3.对于更复杂的设置:确保没有防火墙或反向代理的干扰。", "diagnosis_rootfstotalspace_critical": "根文件系统总共只有{space},这很令人担忧!您可能很快就会用完磁盘空间!建议根文件系统至少有16 GB。", From a68316be636caf78160132a5104f821825d526a4 Mon Sep 17 00:00:00 2001 From: Leandro Noferini Date: Sun, 30 May 2021 04:53:38 +0000 Subject: [PATCH 2533/3170] Translated using Weblate (Italian) Currently translated at 100.0% (633 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/it/ --- locales/it.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/locales/it.json b/locales/it.json index 6b15dd900..2ddd258e4 100644 --- a/locales/it.json +++ b/locales/it.json @@ -140,8 +140,8 @@ "updating_apt_cache": "Recupero degli aggiornamenti disponibili per i pacchetti di sistema...", "upgrade_complete": "Aggiornamento completo", "upnp_dev_not_found": "Nessuno supporto UPnP trovato", - "upnp_disabled": "UPnP è stato disattivato", - "upnp_enabled": "UPnP è stato attivato", + "upnp_disabled": "UPnP è disattivato", + "upnp_enabled": "UPnP è attivato", "upnp_port_open_failed": "Impossibile aprire le porte attraverso UPnP", "user_created": "Utente creato", "user_creation_failed": "Impossibile creare l'utente {user}: {error}", @@ -225,7 +225,7 @@ "certmanager_unable_to_parse_self_CA_name": "Impossibile analizzare il nome dell'autorità di auto-firma (file: {file:s})", "confirm_app_install_warning": "Attenzione: Questa applicazione potrebbe funzionare, ma non è ben integrata in YunoHost. Alcune funzionalità come il single sign-on e il backup/ripristino potrebbero non essere disponibili. Installare comunque? [{answers:s}] ", "confirm_app_install_danger": "ATTENZIONE! Questa applicazione è ancora sperimentale (se non esplicitamente dichiarata non funzionante)! Probabilmente NON dovresti installarla a meno che tu non sappia cosa stai facendo. NESSUN SUPPORTO verrà dato se quest'app non funziona o se rompe il tuo sistema... Se comunque accetti di prenderti questo rischio,digita '{answers:s}'", - "confirm_app_install_thirdparty": "PERICOLO! Quest'applicazione non fa parte del catalogo Yunohost. Installando app di terze parti potresti compromettere l'integrita e la sicurezza del tuo sistema. Probabilmente NON dovresti installarla a meno che tu non sappia cosa stai facendo. NESSUN SUPPORTO verrà dato se quest'app non funziona o se rompe il tuo sistema... Se comunque accetti di prenderti questo rischio, digita '{answers:s}'", + "confirm_app_install_thirdparty": "PERICOLO! Quest'applicazione non fa parte del catalogo YunoHost. Installando app di terze parti potresti compromettere l'integrita e la sicurezza del tuo sistema. Probabilmente NON dovresti installarla a meno che tu non sappia cosa stai facendo. NESSUN SUPPORTO verrà dato se quest'app non funziona o se rompe il tuo sistema... Se comunque accetti di prenderti questo rischio, digita '{answers:s}'", "dpkg_is_broken": "Non puoi eseguire questo ora perchè dpkg/APT (i gestori di pacchetti del sistema) sembrano essere in stato danneggiato... Puoi provare a risolvere il problema connettendoti via SSH ed eseguire `sudo apt install --fix-broken` e/o `sudo dpkg --configure -a`.", "domain_cannot_remove_main": "Non puoi rimuovere '{domain:s}' essendo il dominio principale, prima devi impostare un nuovo dominio principale con il comando 'yunohost domain main-domain -n '; ecco la lista dei domini candidati: {other_domains:s}", "domain_dns_conf_is_just_a_recommendation": "Questo comando ti mostra la configurazione *raccomandata*. Non ti imposta la configurazione DNS al tuo posto. È tua responsabilità configurare la tua zona DNS nel tuo registrar in accordo con queste raccomandazioni.", @@ -331,7 +331,7 @@ "diagnosis_domain_expiration_not_found_details": "Le informazioni WHOIS per il dominio {domain} non sembrano contenere la data di scadenza, giusto?", "diagnosis_domain_not_found_details": "Il dominio {domain} non esiste nel database WHOIS o è scaduto!", "diagnosis_domain_expiration_not_found": "Non riesco a controllare la data di scadenza di alcuni domini", - "diagnosis_dns_try_dyndns_update_force": "La configurazione DNS di questo dominio dovrebbe essere gestita automaticamente da Yunohost. Se non avviene, puoi provare a forzare un aggiornamento usando il comando yunohost dyndns update --force.", + "diagnosis_dns_try_dyndns_update_force": "La configurazione DNS di questo dominio dovrebbe essere gestita automaticamente da YunoHost. Se non avviene, puoi provare a forzare un aggiornamento usando il comando yunohost dyndns update --force.", "diagnosis_dns_point_to_doc": "Controlla la documentazione a https://yunohost.org/dns_config se hai bisogno di aiuto nel configurare i record DNS.", "diagnosis_dns_discrepancy": "Il record DNS non sembra seguire la configurazione DNS raccomandata:
Type: {type}
Name: {name}
Current value: {current}
Expected value: {value}", "diagnosis_dns_missing_record": "Stando alla configurazione DNS raccomandata, dovresti aggiungere un record DNS con le seguenti informazioni.
Type: {type}
Name: {name}
Value: {value}", @@ -361,7 +361,7 @@ "diagnosis_cache_still_valid": "(La cache della diagnosi di {category} è ancora valida. Non la ricontrollo di nuovo per ora!)", "diagnosis_failed_for_category": "Diagnosi fallita per la categoria '{category}:{error}", "diagnosis_display_tip": "Per vedere i problemi rilevati, puoi andare alla sezione Diagnosi del amministratore, o eseguire 'yunohost diagnosis show --issues --human-readable' dalla riga di comando.", - "diagnosis_package_installed_from_sury_details": "Alcuni pacchetti sono stati inavvertitamente installati da un repository di terze parti chiamato Sury. Il team di Yunohost ha migliorato la gestione di tali pacchetti, ma ci si aspetta che alcuni setup di app PHP7.3 abbiano delle incompatibilità anche se sono ancora in Stretch. Per sistemare questa situazione, dovresti provare a lanciare il seguente comando: {cmd_to_fix}", + "diagnosis_package_installed_from_sury_details": "Alcuni pacchetti sono stati inavvertitamente installati da un repository di terze parti chiamato Sury. Il team di YunoHost ha migliorato la gestione di tali pacchetti, ma ci si aspetta che alcuni setup di app PHP7.3 abbiano delle incompatibilità anche se sono ancora in Stretch. Per sistemare questa situazione, dovresti provare a lanciare il seguente comando: {cmd_to_fix}", "diagnosis_package_installed_from_sury": "Alcuni pacchetti di sistema dovrebbero fare il downgrade", "diagnosis_mail_ehlo_bad_answer": "Un servizio diverso da SMTP ha risposto sulla porta 25 su IPv{ipversion}", "diagnosis_mail_ehlo_unreachable_details": "Impossibile aprire una connessione sulla porta 25 sul tuo server su IPv{ipversion}. Sembra irraggiungibile.
1. La causa più probabile di questo problema è la porta 25 non correttamente inoltrata al tuo server.
2. Dovresti esser sicuro che il servizio postfix sia attivo.
3. Su setup complessi: assicuratu che nessun firewall o reverse-proxy stia interferendo.", @@ -394,7 +394,7 @@ "diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "DNS invero corrente: {rdns_domain}
Valore atteso: {ehlo_domain}", "diagnosis_mail_fcrdns_different_from_ehlo_domain": "Il DNS inverso non è correttamente configurato su IPv{ipversion}. Alcune email potrebbero non essere spedite o segnalate come SPAM.", "diagnosis_mail_fcrdns_nok_alternatives_6": "Alcuni provider non permettono di configurare un DNS inverso (o non è configurato bene...). Se il tuo DNS inverso è correttamente configurato per IPv4, puoi provare a disabilitare l'utilizzo di IPv6 durante l'invio mail eseguendo yunohost settings set smtp.allow_ipv6 -v off. NB: se esegui il comando non sarà più possibile inviare o ricevere email da i pochi IPv6-only server mail esistenti.", - "yunohost_postinstall_end_tip": "La post-installazione è completata! Per rifinire il tuo setup, considera di:\n\t- aggiungere il primo utente nella sezione 'Utenti' del webadmin (o eseguendo da terminale 'yunohost user create ');\n\t- eseguire una diagnosi alla ricerca di problemi nella sezione 'Diagnosi' del webadmin (o eseguendo da terminale 'yunohost diagnosis run');\n\t- leggere 'Finalizing your setup' e 'Getting to know Yunohost' nella documentazione admin: https://yunohost.org/admindoc.", + "yunohost_postinstall_end_tip": "La post-installazione è completata! Per rifinire il tuo setup, considera di:\n\t- aggiungere il primo utente nella sezione 'Utenti' del webadmin (o eseguendo da terminale 'yunohost user create ');\n\t- eseguire una diagnosi alla ricerca di problemi nella sezione 'Diagnosi' del webadmin (o eseguendo da terminale 'yunohost diagnosis run');\n\t- leggere 'Finalizing your setup' e 'Getting to know YunoHost' nella documentazione admin: https://yunohost.org/admindoc.", "user_already_exists": "L'utente '{user}' esiste già", "update_apt_cache_warning": "Qualcosa è andato storto mentre eseguivo l'aggiornamento della cache APT (package manager di Debian). Ecco il dump di sources.list, che potrebbe aiutare ad identificare le linee problematiche:\n{sourceslist}", "update_apt_cache_failed": "Impossibile aggiornare la cache di APT (package manager di Debian). Ecco il dump di sources.list, che potrebbe aiutare ad identificare le linee problematiche:\n{sourceslist}", @@ -407,7 +407,7 @@ "tools_upgrade_cant_unhold_critical_packages": "Impossibile annullare il blocco dei pacchetti critici/importanti…", "tools_upgrade_cant_hold_critical_packages": "Impossibile bloccare i pacchetti critici/importanti…", "tools_upgrade_cant_both": "Impossibile aggiornare sia il sistema e le app nello stesso momento", - "tools_upgrade_at_least_one": "Specifica '--apps', o '--system'", + "tools_upgrade_at_least_one": "Specifica 'apps', o 'system'", "show_tile_cant_be_enabled_for_regex": "Non puoi abilitare 'show_tile' in questo momento, perché l'URL del permesso '{permission}' è una regex", "show_tile_cant_be_enabled_for_url_not_defined": "Non puoi abilitare 'show_tile' in questo momento, devi prima definire un URL per il permesso '{permission}'", "service_reloaded_or_restarted": "Il servizio '{service:s}' è stato ricaricato o riavviato", @@ -634,8 +634,8 @@ "log_backup_create": "Crea un archivio backup", "global_settings_setting_ssowat_panel_overlay_enabled": "Abilita il pannello sovrapposto SSOwat", "global_settings_setting_security_ssh_port": "Porta SSH", - "diagnosis_sshd_config_inconsistent_details": "Esegui yunohost settings set security.ssh.port -v PORTA_SSH per definire la porta SSH, e controlla con yunohost tools regen-conf ssh --dry-run --with-diff, poi yunohost tools regen-conf ssh --force per resettare la tua configurazione con le raccomandazioni Yunohost.", - "diagnosis_sshd_config_inconsistent": "Sembra che la porta SSH sia stata modificata manualmente in /etc/ssh/sshd_config: A partire da Yunohost 4.2, una nuova configurazione globale 'security.ssh.port' è disponibile per evitare di modificare manualmente la configurazione.", + "diagnosis_sshd_config_inconsistent_details": "Esegui yunohost settings set security.ssh.port -v PORTA_SSH per definire la porta SSH, e controlla con yunohost tools regen-conf ssh --dry-run --with-diff, poi yunohost tools regen-conf ssh --force per resettare la tua configurazione con le raccomandazioni YunoHost.", + "diagnosis_sshd_config_inconsistent": "Sembra che la porta SSH sia stata modificata manualmente in /etc/ssh/sshd_config: A partire da YunoHost 4.2, una nuova configurazione globale 'security.ssh.port' è disponibile per evitare di modificare manualmente la configurazione.", "diagnosis_sshd_config_insecure": "Sembra che la configurazione SSH sia stata modificata manualmente, ed non è sicuro dato che non contiene le direttive 'AllowGroups' o 'Allowusers' che limitano l'accesso agli utenti autorizzati.", "backup_create_size_estimation": "L'archivio conterrà circa {size} di dati.", "app_restore_script_failed": "C'è stato un errore all'interno dello script di recupero" From ae5e26e03715a098de81580e3828200a5aee56f9 Mon Sep 17 00:00:00 2001 From: Meta Meta Date: Tue, 1 Jun 2021 02:01:30 +0000 Subject: [PATCH 2534/3170] Translated using Weblate (German) Currently translated at 92.7% (587 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/locales/de.json b/locales/de.json index 9e6bb3a81..ade28bb1c 100644 --- a/locales/de.json +++ b/locales/de.json @@ -612,5 +612,14 @@ "service_description_postfix": "Wird benutzt, um E-Mails zu senden und zu empfangen", "service_description_nginx": "Stellt Daten aller Websiten auf dem Server bereit", "service_description_mysql": "Apeichert Anwendungsdaten (SQL Datenbank)", - "service_description_metronome": "XMPP Sofortnachrichtenkonten verwalten" + "service_description_metronome": "XMPP Sofortnachrichtenkonten verwalten", + "service_description_yunohost-firewall": "Verwaltet offene und geschlossene Ports zur Verbindung mit Diensten", + "service_description_yunohost-api": "Verwaltet die Interaktionen zwischen der Weboberfläche von YunoHost und dem System", + "service_description_ssh": "Ermöglicht die Verbindung zu Ihrem Server über ein Terminal (SSH-Protokoll)", + "service_description_php7.3-fpm": "Führt in PHP geschriebene Apps mit NGINX aus", + "server_reboot_confirm": "Der Server wird sofort heruntergefahren, sind Sie sicher? [{answers:s}]", + "server_reboot": "Der Server wird neu gestartet", + "server_shutdown_confirm": "Der Server wird sofort heruntergefahren, sind Sie sicher? [{answers:s}]", + "server_shutdown": "Der Server wird heruntergefahren", + "root_password_replaced_by_admin_password": "Ihr Root Passwort wurde durch Ihr Admin Passwort ersetzt." } From 2226823f28cd599f5393c2ace6e9d086da98b1d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M?= Date: Tue, 1 Jun 2021 12:35:58 +0000 Subject: [PATCH 2535/3170] Translated using Weblate (Galician) Currently translated at 14.3% (91 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/gl/ --- locales/gl.json | 73 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/locales/gl.json b/locales/gl.json index 74bad8725..592d72706 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -18,5 +18,76 @@ "app_argument_required": "Requírese o argumento '{name}'", "app_argument_password_no_default": "Erro ao procesar o argumento do contrasinal '{name}': o argumento do contrasinal non pode ter un valor por defecto por razón de seguridade", "app_argument_invalid": "Elixe un valor válido para o argumento '{name:s}': {error:s}", - "app_argument_choice_invalid": "Usa unha destas opcións '{choices:s}' para o argumento '{name:s}'" + "app_argument_choice_invalid": "Usa unha destas opcións '{choices:s}' para o argumento '{name:s}'", + "backup_archive_writing_error": "Non se puideron engadir os ficheiros '{source:s}' (chamados no arquivo '{dest:s}' para ser copiados dentro do arquivo comprimido '{archive:s}'", + "backup_archive_system_part_not_available": "A parte do sistema '{part:s}' non está dispoñible nesta copia", + "backup_archive_corrupted": "Semella que o arquivo de copia '{arquive}' está estragado : {error}", + "backup_archive_cant_retrieve_info_json": "Non se puido cargar a info desde arquivo '{arquive}'... O info.json non s puido obter (ou é un json non válido).", + "backup_archive_open_failed": "Non se puido abrir o arquivo de copia de apoio", + "backup_archive_name_unknown": "Arquivo local de copia de apoio descoñecido con nome '{name:s}'", + "backup_archive_name_exists": "Xa existe un arquivo de copia con este nome.", + "backup_archive_broken_link": "Non se puido acceder ao arquivo da copia (ligazón rota a {path:s})", + "backup_archive_app_not_found": "Non se atopa {app:s} no arquivo da copia", + "backup_applying_method_tar": "Creando o arquivo TAR da copia...", + "backup_applying_method_custom": "Chamando polo método de copia de apoio personalizado '{method:s}'...", + "backup_applying_method_copy": "Copiando tódolos ficheiros necesarios...", + "backup_app_failed": "Non se fixo copia de {app:s}", + "backup_actually_backuping": "Creando o arquivo de copia cos ficheiros recollidos...", + "backup_abstract_method": "Este método de copia de apoio aínda non foi implementado", + "ask_password": "Contrasinal", + "ask_new_path": "Nova ruta", + "ask_new_domain": "Novo dominio", + "ask_new_admin_password": "Novo contrasinal de administración", + "ask_main_domain": "Dominio principal", + "ask_lastname": "Apelido", + "ask_firstname": "Nome", + "ask_user_domain": "Dominio a utilizar como enderezo de email e conta XMPP da usuaria", + "apps_catalog_update_success": "O catálogo de aplicacións foi actualizado!", + "apps_catalog_obsolete_cache": "A caché do catálogo de apps está baleiro ou obsoleto.", + "apps_catalog_failed_to_download": "Non se puido descargar o catálogo de apps {apps_catalog}: {error}", + "apps_catalog_updating": "Actualizando o catálogo de aplicacións…", + "apps_catalog_init_success": "Sistema do catálogo de apps iniciado!", + "apps_already_up_to_date": "Xa tes tódalas apps ao día", + "app_packaging_format_not_supported": "Esta app non se pode instalar porque o formato de empaquetado non está soportado pola túa versión de YunoHost. Deberías considerar actualizar o teu sistema.", + "app_upgraded": "{app:s} actualizadas", + "app_upgrade_some_app_failed": "Algunhas apps non se puideron actualizar", + "app_upgrade_script_failed": "Houbo un fallo interno no script de actualización da app", + "app_upgrade_failed": "Non se actualizou {app:s}: {error}", + "app_upgrade_app_name": "Actualizando {app}...", + "app_upgrade_several_apps": "Vanse actualizar as seguintes apps: {apps}", + "app_unsupported_remote_type": "Tipo remoto non soportado para a app", + "app_unknown": "App descoñecida", + "app_start_restore": "Restaurando {app}...", + "app_start_backup": "Xuntando os ficheiros para a copia de apoio de {app}...", + "app_start_remove": "Eliminando {app}...", + "app_start_install": "Instalando {app}...", + "app_sources_fetch_failed": "Non se puideron obter os ficheiros fonte, é o URL correcto?", + "app_restore_script_failed": "Houbo un erro interno do script de restablecemento da app", + "app_restore_failed": "Non se puido restablecer {app:s}: {error:s}", + "app_remove_after_failed_install": "Eliminando a app debido ao fallo na instalación...", + "app_requirements_unmeet": "Non se cumpren os requerimentos de {app}, o paquete {pkgname} ({version}) debe ser {spec}", + "app_requirements_checking": "Comprobando os paquetes requeridos por {app}...", + "app_removed": "{app:s} eliminada", + "app_not_properly_removed": "{app:s} non se eliminou de xeito correcto", + "app_not_installed": "Non se puido atopar {app:s} na lista de apps instaladas: {all_apps}", + "app_not_correctly_installed": "{app:s} semella que non está instalada correctamente", + "app_not_upgraded": "Fallou a actualización da app '{failed_app}', como consecuencia as actualizacións das seguintes apps foron canceladas: {apps}", + "app_manifest_install_ask_is_public": "Debería esta app estar exposta ante visitantes anónimas?", + "app_manifest_install_ask_admin": "Elixe unha usuaria administradora para esta app", + "app_manifest_install_ask_password": "Elixe un contrasinal de administración para esta app", + "app_manifest_install_ask_path": "Elixe a ruta onde queres instalar esta app", + "app_manifest_install_ask_domain": "Elixe o dominio onde queres instalar esta app", + "app_manifest_invalid": "Hai algún erro no manifesto da app: {error}", + "app_location_unavailable": "Este URL ou ben non está dispoñible ou entra en conflito cunha app(s) xa instalada:\n{apps:s}", + "app_label_deprecated": "Este comando está anticuado! Utiliza o novo comando 'yunohost user permission update' para xestionar a etiqueta da app.", + "app_make_default_location_already_used": "Non se puido establecer a '{app}' como app por defecto no dominio, '{domain}' xa está utilizado por '{other_app}'", + "app_install_script_failed": "Houbo un fallo interno do script de instalación da app", + "app_install_failed": "Non se pode instalar {app}: {error}", + "app_install_files_invalid": "Non se poden instalar estos ficheiros", + "app_id_invalid": "ID da app non válido", + "app_full_domain_unavailable": "Lamentámolo, esta app ten que ser instalada nun dominio propio, pero xa tes outras apps instaladas no dominio '{domain}'. Podes usar un subdominio dedicado para esta app.", + "app_extraction_failed": "Non se puideron extraer os ficheiros de instalación", + "app_change_url_success": "A URL de {app:s} agora é {domain:s}{path:s}", + "app_change_url_no_script": "A app '{app_name:s}' non soporta o cambio de URL. Pode que debas actualizala.", + "app_change_url_identical_domains": "O antigo e o novo dominio/url_path son idénticos ('{domain:s}{path:s}'), nada que facer." } From 87dc9c48cd2e8795c2ac16c7b1556bca69f9bb85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M?= Date: Wed, 2 Jun 2021 05:12:38 +0000 Subject: [PATCH 2536/3170] Translated using Weblate (Galician) Currently translated at 16.5% (105 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/gl/ --- locales/gl.json | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/locales/gl.json b/locales/gl.json index 592d72706..aacbb94f3 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -89,5 +89,19 @@ "app_extraction_failed": "Non se puideron extraer os ficheiros de instalación", "app_change_url_success": "A URL de {app:s} agora é {domain:s}{path:s}", "app_change_url_no_script": "A app '{app_name:s}' non soporta o cambio de URL. Pode que debas actualizala.", - "app_change_url_identical_domains": "O antigo e o novo dominio/url_path son idénticos ('{domain:s}{path:s}'), nada que facer." + "app_change_url_identical_domains": "O antigo e o novo dominio/url_path son idénticos ('{domain:s}{path:s}'), nada que facer.", + "backup_deleted": "Copia de apoio eliminada", + "backup_delete_error": "Non se eliminou '{paht:s}'", + "backup_custom_mount_error": "O método personalizado de copia non superou o paso 'mount'", + "backup_custom_backup_error": "O método personalizado da copia non superou o paso 'backup'", + "backup_csv_creation_failed": "Non se creou o ficheiro CSV necesario para restablecer a copia", + "backup_csv_addition_failed": "Non se engadiron os ficheiros a copiar ao ficheiro CSV", + "backup_creation_failed": "Non se puido crear o arquivo de copia de apoio", + "backup_create_size_estimation": "O arquivo vai conter arredor de {size} de datos.", + "backup_created": "Copia de apoio creada", + "backup_couldnt_bind": "Non se puido ligar {src:s} a {dest:s}.", + "backup_copying_to_organize_the_archive": "Copiando {size:s}MB para organizar o arquivo", + "backup_cleaning_failed": "Non se puido baleirar o cartafol temporal para a copia", + "backup_cant_mount_uncompress_archive": "Non se puido montar o arquivo sen comprimir porque está protexido contra escritura", + "backup_ask_for_copying_if_needed": "Queres realizar a copia de apoio utilizando temporalmente {size:s}MB? (Faise deste xeito porque algúns ficheiros non hai xeito de preparalos usando unha forma máis eficiente)." } From 85e516ccdeb9d8f4bd01b07b549e93c45091108c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 2 Jun 2021 20:22:53 +0200 Subject: [PATCH 2537/3170] Update changelog for 4.2.5.3 --- debian/changelog | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/debian/changelog b/debian/changelog index 9a143f962..ae01bcb35 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,16 @@ +yunohost (4.2.5.3) stable; urgency=low + + - [fix] doc, helpers: Helper doc auto-generation job (f2886510) + - [fix] doc: Manpage generation ([#1237](https://github.com/yunohost/yunohost/pull/1237)) + - [fix] misc: Yunohost -> YunoHost ([#1235](https://github.com/yunohost/yunohost/pull/1235)) + - [enh] email: Accept attachment of 25MB instead of 21,8MB ([#1243](https://github.com/yunohost/yunohost/pull/1243)) + - [fix] helpers: echo -n is pointless in ynh_systemd_action ([#1241](https://github.com/yunohost/yunohost/pull/1241)) + - [i18n] Translations updated for Chinese (Simplified), French, Galician, German, Italian + + Thanks to all contributors <3 ! (Éric Gaspar, José M, Kay0u, Leandro Noferini, ljf, Meta Meta, Noo Langoo, qwerty287, yahoo~~) + + -- Alexandre Aubin Wed, 02 Jun 2021 20:20:54 +0200 + yunohost (4.2.5.2) stable; urgency=low - Fix install in chroot ... *again* (806b7acf) From 2665c6235aba64a3fc17bc1992bf1a4908a21638 Mon Sep 17 00:00:00 2001 From: MercierCorentin Date: Thu, 3 Jun 2021 11:23:03 +0200 Subject: [PATCH 2538/3170] Rename provider_list.yml into dns_zone_hosters_list.yml --- .../other/{providers_list.yml => dns_zone_hosters_list.yml.yml} | 0 debian/install | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename data/other/{providers_list.yml => dns_zone_hosters_list.yml.yml} (100%) diff --git a/data/other/providers_list.yml b/data/other/dns_zone_hosters_list.yml.yml similarity index 100% rename from data/other/providers_list.yml rename to data/other/dns_zone_hosters_list.yml.yml diff --git a/debian/install b/debian/install index 521f2d3af..4ff0ed52d 100644 --- a/debian/install +++ b/debian/install @@ -8,7 +8,7 @@ data/other/yunoprompt.service /etc/systemd/system/ data/other/password/* /usr/share/yunohost/other/password/ data/other/dpkg-origins/yunohost /etc/dpkg/origins data/other/dnsbl_list.yml /usr/share/yunohost/other/ -data/other/providers_list.yml /usr/share/yunohost/other/ +data/other/dns_zone_hosters_list.yml /usr/share/yunohost/other/ data/other/ffdhe2048.pem /usr/share/yunohost/other/ data/other/* /usr/share/yunohost/yunohost-config/moulinette/ data/templates/* /usr/share/yunohost/templates/ From c66bfc84520567760389b5d6471472daab88bca9 Mon Sep 17 00:00:00 2001 From: MercierCorentin Date: Thu, 3 Jun 2021 11:25:41 +0200 Subject: [PATCH 2539/3170] Fix mistake in dns_zone_hosters_list.yml name --- .../{dns_zone_hosters_list.yml.yml => dns_zone_hosters_list.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename data/other/{dns_zone_hosters_list.yml.yml => dns_zone_hosters_list.yml} (100%) diff --git a/data/other/dns_zone_hosters_list.yml.yml b/data/other/dns_zone_hosters_list.yml similarity index 100% rename from data/other/dns_zone_hosters_list.yml.yml rename to data/other/dns_zone_hosters_list.yml From 97d68f85adaecac58eaf86f847b5b0b3d11faed4 Mon Sep 17 00:00:00 2001 From: MercierCorentin Date: Thu, 3 Jun 2021 15:01:40 +0200 Subject: [PATCH 2540/3170] Add domain registrar info --- data/actionsmap/yunohost.yml | 6 +++--- src/yunohost/domain.py | 17 ++++++++++++++++- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 6a7050d61..48d9f6b7e 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -602,9 +602,9 @@ domain: -a: full: --args help: Serialized arguments for registrar API (i.e. "auth_token=TOKEN&auth_username=USER"). - ### domain_registrar_set() - get: - action_help: Get domain registrar + ### domain_registrar_info() + info: + action_help: Display info about registrar settings used for a domain api: GET /domains//registrar arguments: domain: diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 3c0fb479a..1b7235297 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -864,7 +864,22 @@ def _set_domain_settings(domain, domain_settings): yaml.dump(domain_settings, file, default_flow_style=False) -# def domain_get_registrar(): +def domain_registrar_info(domain): + + domains = _load_domain_settings() + if not domain in domains.keys(): + raise YunohostError("domain_name_unknown", domain=domain) + + provider = domains[domain]["provider"] + + if provider: + logger.info("Registrar name : " + provider['name']) + for option in provider['options']: + logger.info("Option " + option + " : "+provider['options'][option]) + else: + logger.info("Registrar settings are not set for " + domain) + + def domain_registrar_set(domain, registrar, args): domains = _load_domain_settings() From d7c61de857018915fa89258c9b4d5bfa77110f13 Mon Sep 17 00:00:00 2001 From: Christian Wehrli Date: Wed, 2 Jun 2021 18:29:04 +0000 Subject: [PATCH 2541/3170] Translated using Weblate (German) Currently translated at 93.0% (589 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/locales/de.json b/locales/de.json index ade28bb1c..848813285 100644 --- a/locales/de.json +++ b/locales/de.json @@ -621,5 +621,7 @@ "server_reboot": "Der Server wird neu gestartet", "server_shutdown_confirm": "Der Server wird sofort heruntergefahren, sind Sie sicher? [{answers:s}]", "server_shutdown": "Der Server wird heruntergefahren", - "root_password_replaced_by_admin_password": "Ihr Root Passwort wurde durch Ihr Admin Passwort ersetzt." + "root_password_replaced_by_admin_password": "Ihr Root Passwort wurde durch Ihr Admin Passwort ersetzt.", + "show_tile_cant_be_enabled_for_regex": "Momentan können Sie 'show_tile' nicht aktivieren, weil die URL für die Berechtigung '{permission}' ein regulärer Ausdruck ist", + "show_tile_cant_be_enabled_for_url_not_defined": "Momentan können Sie 'show_tile' nicht aktivieren, weil Sie zuerst eine URL für die Berechtigung '{permission}' definieren müssen" } From 66de9f38565faaeab95f2ce54c7e4b517a2836c8 Mon Sep 17 00:00:00 2001 From: Christian Wehrli Date: Thu, 3 Jun 2021 09:21:27 +0000 Subject: [PATCH 2542/3170] Translated using Weblate (German) Currently translated at 94.1% (596 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/locales/de.json b/locales/de.json index 848813285..512296389 100644 --- a/locales/de.json +++ b/locales/de.json @@ -623,5 +623,12 @@ "server_shutdown": "Der Server wird heruntergefahren", "root_password_replaced_by_admin_password": "Ihr Root Passwort wurde durch Ihr Admin Passwort ersetzt.", "show_tile_cant_be_enabled_for_regex": "Momentan können Sie 'show_tile' nicht aktivieren, weil die URL für die Berechtigung '{permission}' ein regulärer Ausdruck ist", - "show_tile_cant_be_enabled_for_url_not_defined": "Momentan können Sie 'show_tile' nicht aktivieren, weil Sie zuerst eine URL für die Berechtigung '{permission}' definieren müssen" + "show_tile_cant_be_enabled_for_url_not_defined": "Momentan können Sie 'show_tile' nicht aktivieren, weil Sie zuerst eine URL für die Berechtigung '{permission}' definieren müssen", + "tools_upgrade_regular_packages_failed": "Konnte für die folgenden Pakete das Upgrade nicht durchführen: {packages_list}", + "tools_upgrade_regular_packages": "Momentan werden Upgrades für das System (YunoHost-unabhängige) Pakete durchgeführt…", + "tools_upgrade_cant_unhold_critical_packages": "Konnte für die kritischen Pakete das Flag 'hold' nicht aufheben…", + "tools_upgrade_cant_hold_critical_packages": "Konnte für die kritischen Pakete das Flag 'hold' nicht setzen…", + "tools_upgrade_cant_both": "Kann das Upgrade für das System und die Anwendungen nicht gleichzeitig durchführen", + "tools_upgrade_at_least_one": "Bitte geben Sie '--apps' oder '--system' an", + "this_action_broke_dpkg": "Diese Aktion hat unkonfigurierte Pakete verursacht, welche durch dpkg/apt (die Paketverwaltungen dieses Systems) zurückgelassen wurden... Sie können versuchen dieses Problem zu lösen, indem Sie 'sudo apt install --fix-broken' und/oder 'sudo dpkg --configure -a' ausführen." } From f7dec1e602df61a70aa4d0631e6735c36c5ee4bb Mon Sep 17 00:00:00 2001 From: Flavio Cristoforetti Date: Thu, 3 Jun 2021 12:38:41 +0000 Subject: [PATCH 2543/3170] Translated using Weblate (Italian) Currently translated at 100.0% (633 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/it/ --- locales/it.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/it.json b/locales/it.json index 2ddd258e4..2d432f56e 100644 --- a/locales/it.json +++ b/locales/it.json @@ -249,7 +249,7 @@ "global_settings_unknown_setting_from_settings_file": "Chiave sconosciuta nelle impostazioni: '{setting_key:s}', scartata e salvata in /etc/yunohost/settings-unknown.json", "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Consenti l'uso del hostkey DSA (deprecato) per la configurazione del demone SSH", "global_settings_unknown_type": "Situazione inaspettata, l'impostazione {setting:s} sembra essere di tipo {unknown_type:s} ma non è un tipo supportato dal sistema.", - "good_practices_about_admin_password": "Stai per definire una nuova password di amministratore. La password deve essere almeno di 8 caratteri - anche se è buona pratica utilizzare password più lunghe (es. una frase, una serie di parole) e/o utilizzare vari tipi di caratteri (maiuscole, minuscole, numeri e simboli).", + "good_practices_about_admin_password": "Stai per impostare una nuova password di amministratore. La password deve essere almeno di 8 caratteri - anche se è buona pratica utilizzare password più lunghe (es. una frase, una serie di parole) e/o utilizzare vari tipi di caratteri (maiuscole, minuscole, numeri e simboli).", "log_corrupted_md_file": "Il file dei metadati YAML associato con i registri è danneggiato: '{md_file}'\nErrore: {error}", "log_link_to_log": "Registro completo di questa operazione: '{desc}'", "log_help_to_get_log": "Per vedere il registro dell'operazione '{desc}', usa il comando 'yunohost log show {name}{name}'", From 1d2fc443bbe51fe30510e76ca08fa9c9fdfac52e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M?= Date: Thu, 3 Jun 2021 03:31:55 +0000 Subject: [PATCH 2544/3170] Translated using Weblate (Galician) Currently translated at 18.6% (118 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/gl/ --- locales/gl.json | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/locales/gl.json b/locales/gl.json index aacbb94f3..145721ab5 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -103,5 +103,18 @@ "backup_copying_to_organize_the_archive": "Copiando {size:s}MB para organizar o arquivo", "backup_cleaning_failed": "Non se puido baleirar o cartafol temporal para a copia", "backup_cant_mount_uncompress_archive": "Non se puido montar o arquivo sen comprimir porque está protexido contra escritura", - "backup_ask_for_copying_if_needed": "Queres realizar a copia de apoio utilizando temporalmente {size:s}MB? (Faise deste xeito porque algúns ficheiros non hai xeito de preparalos usando unha forma máis eficiente)." + "backup_ask_for_copying_if_needed": "Queres realizar a copia de apoio utilizando temporalmente {size:s}MB? (Faise deste xeito porque algúns ficheiros non hai xeito de preparalos usando unha forma máis eficiente).", + "backup_running_hooks": "Executando os ganchos da copia...", + "backup_permission": "Permiso de copia para {app:s}", + "backup_output_symlink_dir_broken": "O directorio de arquivo '{path:s}' é unha ligazón simbólica rota. Pode ser que esqueceses re/montar ou conectar o medio de almacenaxe ao que apunta.", + "backup_output_directory_required": "Debes proporcionar un directorio de saída para a copia", + "backup_output_directory_not_empty": "Debes elexir un directorio de saída baleiro", + "backup_output_directory_forbidden": "Elixe un directorio de saída diferente. As copias non poden crearse en /bin, /boot, /dev, /etc, /lib, /root, /sbin, /sys, /usr, /var ou subcartafoles de /home/yunohost.backup/archives", + "backup_nothings_done": "Nada que gardar", + "backup_no_uncompress_archive_dir": "Non hai tal directorio do arquivo descomprimido", + "backup_mount_archive_for_restore": "Preparando o arquivo para restauración...", + "backup_method_tar_finished": "Creouse o arquivo de copia TAR", + "backup_method_custom_finished": "O método de copia personalizado '{method:s}' rematou", + "backup_method_copy_finished": "Rematou o copiado dos ficheiros", + "backup_hook_unknown": "O gancho da copia '{hook:s}' é descoñecido" } From 008baf13509b1030ef4ea5742b99c74c31ce2b77 Mon Sep 17 00:00:00 2001 From: MercierCorentin Date: Fri, 4 Jun 2021 09:25:00 +0200 Subject: [PATCH 2545/3170] Add network util to get dns zone from domain name --- src/yunohost/utils/network.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/yunohost/utils/network.py b/src/yunohost/utils/network.py index d96151fa4..895a2fe5a 100644 --- a/src/yunohost/utils/network.py +++ b/src/yunohost/utils/network.py @@ -185,7 +185,27 @@ def dig( return ("ok", answers) +def get_dns_zone_from_domain(domain): + """ + Get the DNS zone of a domain + Keyword arguments: + domain -- The domain name + + """ + separator = "." + domain_subs = domain.split(separator) + for i in range(0, len(domain_subs)): + answer = dig(separator.join(domain_subs), rdtype="NS", full_answers=True) + if answer[0] == "ok" : + return separator.join(domain_subs) + elif answer[1][0] == "NXDOMAIN" : + return None + domain_subs.pop(0) + + # Should not be executed + return None + def _extract_inet(string, skip_netmask=False, skip_loopback=True): """ Extract IP addresses (v4 and/or v6) from a string limited to one From 4b9dbd92ebe2fce1d02873483491abc9a86f3584 Mon Sep 17 00:00:00 2001 From: MercierCorentin Date: Fri, 4 Jun 2021 09:42:04 +0200 Subject: [PATCH 2546/3170] Moved get_dns_zone_from_domain from utils/network to utils/dns --- src/yunohost/utils/dns.py | 23 ++++++++++++++++++++++- src/yunohost/utils/network.py | 21 --------------------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/yunohost/utils/dns.py b/src/yunohost/utils/dns.py index 3033743d1..26347101f 100644 --- a/src/yunohost/utils/dns.py +++ b/src/yunohost/utils/dns.py @@ -35,4 +35,25 @@ def get_public_suffix(domain): domain_prefix = domain_name[0:-(1 + len(public_suffix))] public_suffix = domain_prefix.plit(".")[-1] + "." + public_suffix - return public_suffix \ No newline at end of file + return public_suffix + +def get_dns_zone_from_domain(domain): + """ + Get the DNS zone of a domain + + Keyword arguments: + domain -- The domain name + + """ + separator = "." + domain_subs = domain.split(separator) + for i in range(0, len(domain_subs)): + answer = dig(separator.join(domain_subs), rdtype="NS", full_answers=True) + if answer[0] == "ok" : + return separator.join(domain_subs) + elif answer[1][0] == "NXDOMAIN" : + return None + domain_subs.pop(0) + + # Should not be executed + return None \ No newline at end of file diff --git a/src/yunohost/utils/network.py b/src/yunohost/utils/network.py index 895a2fe5a..9423199bb 100644 --- a/src/yunohost/utils/network.py +++ b/src/yunohost/utils/network.py @@ -184,27 +184,6 @@ def dig( answers = [answer.to_text() for answer in answers] return ("ok", answers) - -def get_dns_zone_from_domain(domain): - """ - Get the DNS zone of a domain - - Keyword arguments: - domain -- The domain name - - """ - separator = "." - domain_subs = domain.split(separator) - for i in range(0, len(domain_subs)): - answer = dig(separator.join(domain_subs), rdtype="NS", full_answers=True) - if answer[0] == "ok" : - return separator.join(domain_subs) - elif answer[1][0] == "NXDOMAIN" : - return None - domain_subs.pop(0) - - # Should not be executed - return None def _extract_inet(string, skip_netmask=False, skip_loopback=True): """ From 06dcacbe8bc42c0b289247fa4c429d92b6a167a7 Mon Sep 17 00:00:00 2001 From: MercierCorentin Date: Fri, 4 Jun 2021 09:52:10 +0200 Subject: [PATCH 2547/3170] Fix import issue in utils/dns --- src/yunohost/utils/dns.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/yunohost/utils/dns.py b/src/yunohost/utils/dns.py index 26347101f..00bbc4f62 100644 --- a/src/yunohost/utils/dns.py +++ b/src/yunohost/utils/dns.py @@ -19,6 +19,7 @@ """ from publicsuffix import PublicSuffixList +from yunohost.utils.network import dig YNH_DYNDNS_DOMAINS = ["nohost.me", "noho.st", "ynh.fr"] From 3812dcd7afc9e3b74d89b3ba12d554da32f4ae59 Mon Sep 17 00:00:00 2001 From: Paco Date: Fri, 4 Jun 2021 16:04:40 +0200 Subject: [PATCH 2548/3170] =?UTF-8?q?setting=20owned=5Fdns=5Fzone=20(Bool)?= =?UTF-8?q?=20=E2=86=92=20dns=5Fzone=20(String)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/yunohost/domain.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 1b7235297..51f7c9b82 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -47,7 +47,7 @@ from yunohost.app import ( ) from yunohost.regenconf import regen_conf, _force_clear_hashes, _process_regen_conf from yunohost.utils.network import get_public_ip -from yunohost.utils.dns import get_public_suffix +from yunohost.utils.dns import get_dns_zone_from_domain from yunohost.log import is_unit_operation from yunohost.hook import hook_callback @@ -354,6 +354,7 @@ def domain_dns_conf(domain): result += "\n{name} {ttl} IN {type} {value}".format(**record) if msettings.get("interface") == "cli": + # FIXME Update this to point to our "dns push" doc logger.info(m18n.n("domain_dns_conf_is_just_a_recommendation")) return result @@ -493,7 +494,8 @@ def _build_dns_conf(domains): ipv4 = get_public_ip() ipv6 = get_public_ip(6) owned_dns_zone = ( - "owned_dns_zone" in domains[root] and domains[root]["owned_dns_zone"] + # TODO test this + "dns_zone" in domains[root] and domains[root]["dns_zone"] == root ) root_prefix = root.partition(".")[0] @@ -727,9 +729,10 @@ def _get_DKIM(domain): ) +# FIXME split the loading of the domain settings → domain by domain (& file by file) def _load_domain_settings(): """ - Retrieve entries in domains.yml + Retrieve entries in domains/[domain].yml And fill the holes if any """ # Retrieve actual domain list @@ -752,11 +755,11 @@ def _load_domain_settings(): on_disk_settings = {} # Generate defaults is_maindomain = domain == maindomain - default_owned_dns_zone = True if domain == get_public_suffix(domain) else False + dns_zone = get_dns_zone_from_domain(domain) default_settings = { "xmpp": is_maindomain, "mail": True, - "owned_dns_zone": default_owned_dns_zone, + "dns_zone": dns_zone, "ttl": 3600, "provider": {}, } @@ -833,6 +836,8 @@ def _get_domain_settings(domain, subdomains): only_wanted_domains = dict() for entry in domains.keys(): if subdomains: + # FIXME does example.co is seen as a subdomain of example.com? + # TODO _is_subdomain_of_domain if domain in entry: only_wanted_domains[entry] = domains[entry] else: From 5deb9972df0b234ba8f22ba21930ce52f9f5f675 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 4 Jun 2021 17:00:28 +0200 Subject: [PATCH 2549/3170] [i18n] Fix Translation string format --- locales/gl.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/locales/gl.json b/locales/gl.json index 145721ab5..da9a2b001 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -21,8 +21,8 @@ "app_argument_choice_invalid": "Usa unha destas opcións '{choices:s}' para o argumento '{name:s}'", "backup_archive_writing_error": "Non se puideron engadir os ficheiros '{source:s}' (chamados no arquivo '{dest:s}' para ser copiados dentro do arquivo comprimido '{archive:s}'", "backup_archive_system_part_not_available": "A parte do sistema '{part:s}' non está dispoñible nesta copia", - "backup_archive_corrupted": "Semella que o arquivo de copia '{arquive}' está estragado : {error}", - "backup_archive_cant_retrieve_info_json": "Non se puido cargar a info desde arquivo '{arquive}'... O info.json non s puido obter (ou é un json non válido).", + "backup_archive_corrupted": "Semella que o arquivo de copia '{archive}' está estragado : {error}", + "backup_archive_cant_retrieve_info_json": "Non se puido cargar a info desde arquivo '{archive}'... O info.json non s puido obter (ou é un json non válido).", "backup_archive_open_failed": "Non se puido abrir o arquivo de copia de apoio", "backup_archive_name_unknown": "Arquivo local de copia de apoio descoñecido con nome '{name:s}'", "backup_archive_name_exists": "Xa existe un arquivo de copia con este nome.", @@ -91,7 +91,7 @@ "app_change_url_no_script": "A app '{app_name:s}' non soporta o cambio de URL. Pode que debas actualizala.", "app_change_url_identical_domains": "O antigo e o novo dominio/url_path son idénticos ('{domain:s}{path:s}'), nada que facer.", "backup_deleted": "Copia de apoio eliminada", - "backup_delete_error": "Non se eliminou '{paht:s}'", + "backup_delete_error": "Non se eliminou '{path:s}'", "backup_custom_mount_error": "O método personalizado de copia non superou o paso 'mount'", "backup_custom_backup_error": "O método personalizado da copia non superou o paso 'backup'", "backup_csv_creation_failed": "Non se creou o ficheiro CSV necesario para restablecer a copia", From 923f703ea0661b53d54cb404fa5b1ab588f2dad4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 4 Jun 2021 18:22:32 +0200 Subject: [PATCH 2550/3170] tools_upgrade with apps : 'apps' undefined (well, it was defined, but not with the appropriate content) --- src/yunohost/tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index d9e057875..1cd197d70 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -511,7 +511,7 @@ def tools_upgrade( # Actually start the upgrades try: - app_upgrade(app=apps) + app_upgrade(app=upgradable_apps) except Exception as e: logger.warning("unable to upgrade apps: %s" % str(e)) logger.error(m18n.n("app_upgrade_some_app_failed")) From 14d4cec84462f04c4d00074cadffafcebdc9df68 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 4 Jun 2021 21:44:14 +0200 Subject: [PATCH 2551/3170] Python3: fix string split in postgresql migration --- src/yunohost/data_migrations/0017_postgresql_9p6_to_11.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/data_migrations/0017_postgresql_9p6_to_11.py b/src/yunohost/data_migrations/0017_postgresql_9p6_to_11.py index cbdfabb1f..1ccf5ccc9 100644 --- a/src/yunohost/data_migrations/0017_postgresql_9p6_to_11.py +++ b/src/yunohost/data_migrations/0017_postgresql_9p6_to_11.py @@ -78,5 +78,5 @@ class MyMigration(Migration): ) ) - out = out.strip().split("\n") + out = out.strip().split(b"\n") return (returncode, out, err) From 8a5213c88bec36a7811a77462686a505a4c38c4c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 7 Jun 2021 16:31:39 +0200 Subject: [PATCH 2552/3170] Fix helpers test: SimpleHTTPServer was python2 only, use http.server for python3/bullseye --- tests/test_helpers.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_helpers.sh b/tests/test_helpers.sh index 55d26483e..153ce1386 100644 --- a/tests/test_helpers.sh +++ b/tests/test_helpers.sh @@ -35,7 +35,7 @@ trap cleanup EXIT SIGINT HTTPSERVER_DIR=$(mktemp -d) HTTPSERVER_PORT=1312 pushd "$HTTPSERVER_DIR" >/dev/null -python -m SimpleHTTPServer $HTTPSERVER_PORT &>/dev/null & +python3 -m http.server $HTTPSERVER_PORT --bind 127.0.0.1 &>/dev/null & HTTPSERVER="$!" popd >/dev/null From bd196c875b7fa78361ba96e08406cf41cb172ac3 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 7 Jun 2021 16:39:06 +0200 Subject: [PATCH 2553/3170] Stupid python3 issue in helpers --- data/helpers.d/backup | 4 ++-- data/helpers.d/network | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/data/helpers.d/backup b/data/helpers.d/backup index 17da0fb2e..ae746a37b 100644 --- a/data/helpers.d/backup +++ b/data/helpers.d/backup @@ -207,14 +207,14 @@ ynh_restore () { # usage: _get_archive_path ORIGIN_PATH _get_archive_path () { # For security reasons we use csv python library to read the CSV - python -c " + python3 -c " import sys import csv with open(sys.argv[1], 'r') as backup_file: backup_csv = csv.DictReader(backup_file, fieldnames=['source', 'dest']) for row in backup_csv: if row['source']==sys.argv[2].strip('\"'): - print row['dest'] + print(row['dest']) sys.exit(0) raise Exception('Original path for %s not found' % sys.argv[2]) " "${YNH_BACKUP_CSV}" "$1" diff --git a/data/helpers.d/network b/data/helpers.d/network index 2011d502b..4e536a8db 100644 --- a/data/helpers.d/network +++ b/data/helpers.d/network @@ -80,7 +80,7 @@ ynh_validate_ip() [ "$family" == "4" ] || [ "$family" == "6" ] || return 1 - python /dev/stdin << EOF + python3 /dev/stdin << EOF import socket import sys family = { "4" : socket.AF_INET, "6" : socket.AF_INET6 } From b837d3dafde0fd6a64228d42a265a627f9bce693 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 7 Jun 2021 23:36:52 +0200 Subject: [PATCH 2554/3170] Fix fail2ban rule for yunohost-api login --- data/templates/fail2ban/yunohost.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/templates/fail2ban/yunohost.conf b/data/templates/fail2ban/yunohost.conf index a501c10ba..26d732740 100644 --- a/data/templates/fail2ban/yunohost.conf +++ b/data/templates/fail2ban/yunohost.conf @@ -15,7 +15,7 @@ # Values: TEXT # failregex = helpers.lua:[0-9]+: authenticate\(\): Connection failed for: .*, client: - ^ -.*\"POST /yunohost/api/login HTTP/1.1\" 401 + ^ -.*\"POST /yunohost/api/login HTTP/\d.\d\" 401 # Option: ignoreregex # Notes.: regex to ignore. If this regex matches, the line is ignored. From e5a03cab11f4b9284d3083d5215c2bbf18935814 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Tue, 8 Jun 2021 13:51:16 +0200 Subject: [PATCH 2555/3170] CI: include all files in .gitlab/ci/ folder --- .gitlab-ci.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 557b4e86e..ef4b45cd1 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -16,8 +16,4 @@ variables: YNH_BUILD_DIR: "ynh-build" 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 - - local: .gitlab/ci/doc.gitlab-ci.yml + - local: .gitlab/ci/*.gitlab-ci.yml From a8d31a5185a04558bc36e7b173525dc7e14e8500 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Tue, 8 Jun 2021 13:52:20 +0200 Subject: [PATCH 2556/3170] ci triggered only for default branch or PR --- .gitlab-ci.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ef4b45cd1..dc4cdf56f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -12,6 +12,15 @@ default: # All jobs are interruptible by default interruptible: true +# see: https://docs.gitlab.com/ee/ci/yaml/#switch-between-branch-pipelines-and-merge-request-pipelines +workflow: + rules: + - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' # If we move to gitlab one day + - if: '$CI_PIPELINE_SOURCE == "external_pull_request_event"' # For github PR + - if: '$CI_COMMIT_REF_NAME =~ /$CI_DEFAULT_BRANCH/ && $CI_PIPELINE_SOURCE == "push"' # If it's not the default branch and if it's a push, then do not trigger a build + when: never + - when: always + variables: YNH_BUILD_DIR: "ynh-build" From 4b8e8be4498a82c4e53dcd989e604baf5848b4fb Mon Sep 17 00:00:00 2001 From: Kay0u Date: Tue, 8 Jun 2021 14:01:34 +0200 Subject: [PATCH 2557/3170] ci triggered only on file changed --- .gitlab/ci/test.gitlab-ci.yml | 50 ++++++++++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/.gitlab/ci/test.gitlab-ci.yml b/.gitlab/ci/test.gitlab-ci.yml index 308701475..c6c54ec20 100644 --- a/.gitlab/ci/test.gitlab-ci.yml +++ b/.gitlab/ci/test.gitlab-ci.yml @@ -58,69 +58,105 @@ test-helpers: script: - cd tests - bash test_helpers.sh + only: + changes: + - data/helpers.d/* test-apps: extends: .test-stage script: - cd src/yunohost - python3 -m pytest tests/test_apps.py + only: + changes: + - src/yunohost/app.py test-appscatalog: extends: .test-stage script: - cd src/yunohost - python3 -m pytest tests/test_appscatalog.py + only: + changes: + - src/yunohost/app.py test-appurl: extends: .test-stage script: - cd src/yunohost - python3 -m pytest tests/test_appurl.py + only: + changes: + - src/yunohost/app.py test-apps-arguments-parsing: extends: .test-stage script: - cd src/yunohost - python3 -m pytest tests/test_apps_arguments_parsing.py - -test-backuprestore: - extends: .test-stage - script: - - cd src/yunohost - - python3 -m pytest tests/test_backuprestore.py + only: + changes: + - src/yunohost/app.py test-changeurl: extends: .test-stage script: - cd src/yunohost - python3 -m pytest tests/test_changeurl.py + only: + changes: + - src/yunohost/app.py + +test-backuprestore: + extends: .test-stage + script: + - cd src/yunohost + - python3 -m pytest tests/test_backuprestore.py + only: + changes: + - src/yunohost/backup.py test-permission: extends: .test-stage script: - cd src/yunohost - python3 -m pytest tests/test_permission.py + only: + changes: + - src/yunohost/permission.py test-settings: extends: .test-stage script: - cd src/yunohost - python3 -m pytest tests/test_settings.py + only: + changes: + - src/yunohost/settings.py test-user-group: extends: .test-stage script: - cd src/yunohost - python3 -m pytest tests/test_user-group.py - + only: + changes: + - src/yunohost/user.py + test-regenconf: extends: .test-stage script: - cd src/yunohost - python3 -m pytest tests/test_regenconf.py + only: + changes: + - src/yunohost/regenconf.py test-service: extends: .test-stage script: - cd src/yunohost - python3 -m pytest tests/test_service.py + only: + changes: + - src/yunohost/service.py \ No newline at end of file From cdb6973b4fba7823399f740fd9cfccd5815ebd75 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Tue, 8 Jun 2021 15:08:24 +0200 Subject: [PATCH 2558/3170] fix --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index dc4cdf56f..1ee6763fa 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -17,7 +17,7 @@ workflow: rules: - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' # If we move to gitlab one day - if: '$CI_PIPELINE_SOURCE == "external_pull_request_event"' # For github PR - - if: '$CI_COMMIT_REF_NAME =~ /$CI_DEFAULT_BRANCH/ && $CI_PIPELINE_SOURCE == "push"' # If it's not the default branch and if it's a push, then do not trigger a build + - if: '$CI_COMMIT_REF_NAME != $CI_DEFAULT_BRANCH && $CI_PIPELINE_SOURCE == "push"' # If it's not the default branch and if it's a push, then do not trigger a build when: never - when: always From 9fdab0dd51f4c562dc83bb4611e05550d1fd936a Mon Sep 17 00:00:00 2001 From: Kay0u Date: Tue, 8 Jun 2021 16:08:26 +0200 Subject: [PATCH 2559/3170] allow tags --- .gitlab-ci.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1ee6763fa..892a8431f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -15,9 +15,10 @@ default: # see: https://docs.gitlab.com/ee/ci/yaml/#switch-between-branch-pipelines-and-merge-request-pipelines workflow: rules: - - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' # If we move to gitlab one day - - if: '$CI_PIPELINE_SOURCE == "external_pull_request_event"' # For github PR - - if: '$CI_COMMIT_REF_NAME != $CI_DEFAULT_BRANCH && $CI_PIPELINE_SOURCE == "push"' # If it's not the default branch and if it's a push, then do not trigger a build + - if: $CI_PIPELINE_SOURCE == "merge_request_event" # If we move to gitlab one day + - if: $CI_PIPELINE_SOURCE == "external_pull_request_event" # For github PR + - if: $CI_COMMIT_TAG # For tags + - if: $CI_COMMIT_REF_NAME != $CI_DEFAULT_BRANCH && $CI_PIPELINE_SOURCE == "push" # If it's not the default branch and if it's a push, then do not trigger a build when: never - when: always From 5967096c05a4604cf22903e04dcf49d82996ca53 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Tue, 8 Jun 2021 16:57:39 +0200 Subject: [PATCH 2560/3170] add translation stage --- .gitlab-ci.yml | 1 + .gitlab/ci/translation.gitlab-ci.yml | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 .gitlab/ci/translation.gitlab-ci.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 892a8431f..d1cb36b73 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -5,6 +5,7 @@ stages: - tests - lint - doc + - translation default: tags: diff --git a/.gitlab/ci/translation.gitlab-ci.yml b/.gitlab/ci/translation.gitlab-ci.yml new file mode 100644 index 000000000..83a9041d7 --- /dev/null +++ b/.gitlab/ci/translation.gitlab-ci.yml @@ -0,0 +1,24 @@ +######################################## +# TRANSLATION +######################################## + +remove-stale-translated-strings: + stage: translation + image: "before-install" + needs: [] + before_script: + - apt-get update -y && apt-get install git hub -y + - git config --global user.email "yunohost@yunohost.org" + - git config --global user.name "$GITHUB_USER" + script: + - cd tests # Maybe move this script location to another folder? + # create a local branch that will overwrite distant one + - git checkout -b "ci-remove-stale-translated-strings-${CI_COMMIT_REF_NAME}" --no-track + - python remove_stale_translated_strings.py + - '[ $(git diff | wc -l) != 0 ] || exit 0' # stop if there is nothing to commit + - git commit -am "[CI] Remove stale translated strings" || true + - git push -f origin "ci-remove-stale-translated-strings-${CI_COMMIT_REF_NAME}":"ci-remove-stale-translated-strings-${CI_COMMIT_REF_NAME}" + - hub pull-request -m "[CI] Remove stale translated strings" -b Yunohost:dev -p || true # GITHUB_USER and GITHUB_TOKEN registered here https://gitlab.com/yunohost/yunohost/-/settings/ci_cd + only: + changes: + - locales/* \ No newline at end of file From 024ea14b276ed5a6f94f2abbe638e1c2ab1a2c6d Mon Sep 17 00:00:00 2001 From: Kay0u Date: Tue, 8 Jun 2021 16:58:35 +0200 Subject: [PATCH 2561/3170] full-tests run test_helpers too --- .gitlab/ci/test.gitlab-ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitlab/ci/test.gitlab-ci.yml b/.gitlab/ci/test.gitlab-ci.yml index c6c54ec20..18c1ab723 100644 --- a/.gitlab/ci/test.gitlab-ci.yml +++ b/.gitlab/ci/test.gitlab-ci.yml @@ -37,6 +37,8 @@ full-tests: - yunohost tools postinstall -d domain.tld -p the_password --ignore-dyndns --force-diskspace script: - python3 -m pytest --cov=yunohost tests/ src/yunohost/tests/ --junitxml=report.xml + - cd tests + - bash test_helpers.sh needs: - job: build-yunohost artifacts: true From cfa1e5dff84aa2c2884408ecd3fed47f2766c81b Mon Sep 17 00:00:00 2001 From: Kay0u Date: Tue, 8 Jun 2021 16:59:28 +0200 Subject: [PATCH 2562/3170] split root-tests to a smaller tests --- .gitlab/ci/test.gitlab-ci.yml | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/.gitlab/ci/test.gitlab-ci.yml b/.gitlab/ci/test.gitlab-ci.yml index 18c1ab723..5a5773cdd 100644 --- a/.gitlab/ci/test.gitlab-ci.yml +++ b/.gitlab/ci/test.gitlab-ci.yml @@ -50,10 +50,29 @@ full-tests: reports: junit: report.xml -root-tests: +test-i18n-keys: extends: .test-stage script: - - python3 -m pytest tests + - python3 -m pytest tests tests/test_i18n_keys.py + only: + changes: + - locales/* + +test-i18n-keys: + extends: .test-stage + script: + - python3 -m pytest tests tests/test_translation_format_consistency.py + only: + changes: + - locales/* + +test-actionmap: + extends: .test-stage + script: + - python3 -m pytest tests tests/test_actionmap.py + only: + changes: + - data/actionsmap/*.yml test-helpers: extends: .test-stage From a97fce05eec39cca898d68a41a6cf77d46c62411 Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Mon, 7 Jun 2021 13:26:29 +0200 Subject: [PATCH 2563/3170] [enh] Remove LDN from resolver list --- data/templates/dnsmasq/plain/resolv.dnsmasq.conf | 3 --- 1 file changed, 3 deletions(-) diff --git a/data/templates/dnsmasq/plain/resolv.dnsmasq.conf b/data/templates/dnsmasq/plain/resolv.dnsmasq.conf index 726899421..f354ce37c 100644 --- a/data/templates/dnsmasq/plain/resolv.dnsmasq.conf +++ b/data/templates/dnsmasq/plain/resolv.dnsmasq.conf @@ -12,9 +12,6 @@ nameserver 80.67.169.12 nameserver 2001:910:800::12 nameserver 80.67.169.40 nameserver 2001:910:800::40 -# (FR) LDN -nameserver 80.67.188.188 -nameserver 2001:913::8 # (FR) ARN nameserver 89.234.141.66 nameserver 2a00:5881:8100:1000::3 From 6fad0b7d79f81cd8d8831dff7c4dafc4cec6a100 Mon Sep 17 00:00:00 2001 From: Kayou Date: Thu, 10 Jun 2021 13:08:05 +0200 Subject: [PATCH 2564/3170] Update .gitlab/ci/test.gitlab-ci.yml --- .gitlab/ci/test.gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab/ci/test.gitlab-ci.yml b/.gitlab/ci/test.gitlab-ci.yml index 5a5773cdd..f146442e4 100644 --- a/.gitlab/ci/test.gitlab-ci.yml +++ b/.gitlab/ci/test.gitlab-ci.yml @@ -58,7 +58,7 @@ test-i18n-keys: changes: - locales/* -test-i18n-keys: +test-translation-format-consistency: extends: .test-stage script: - python3 -m pytest tests tests/test_translation_format_consistency.py @@ -180,4 +180,4 @@ test-service: - python3 -m pytest tests/test_service.py only: changes: - - src/yunohost/service.py \ No newline at end of file + - src/yunohost/service.py From dbe5e51ef134889ba385d84b08a69c249daa9c5e Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Thu, 10 Jun 2021 14:47:13 +0200 Subject: [PATCH 2565/3170] [fix] Capture PASSPHRASE See https://github.com/YunoHost-Apps/borg_ynh/issues/87 --- src/yunohost/log.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/yunohost/log.py b/src/yunohost/log.py index f8da40002..b70725b48 100644 --- a/src/yunohost/log.py +++ b/src/yunohost/log.py @@ -417,6 +417,7 @@ class RedactingFormatter(Formatter): match = re.search( r"(pwd|pass|password|passphrase|secret\w*|\w+key|token)=(\S{3,})$", record.strip(), + re.IGNORECASE ) if ( match From 16f1968ae765415728b8b92cea3c86479bd6505b Mon Sep 17 00:00:00 2001 From: yalh76 Date: Fri, 14 May 2021 00:19:58 +0200 Subject: [PATCH 2566/3170] Updating requirements As those functions use ynh_add_config --- data/helpers.d/fail2ban | 2 +- data/helpers.d/nginx | 2 +- data/helpers.d/php | 4 +--- data/helpers.d/systemd | 2 +- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/data/helpers.d/fail2ban b/data/helpers.d/fail2ban index 6ac7ae6d0..26c899d93 100644 --- a/data/helpers.d/fail2ban +++ b/data/helpers.d/fail2ban @@ -61,7 +61,7 @@ # fail2ban-regex /var/log/YOUR_LOG_FILE_PATH /etc/fail2ban/filter.d/YOUR_APP.conf # ``` # -# Requires YunoHost version 3.5.0 or higher. +# Requires YunoHost version 4.1.0 or higher. ynh_add_fail2ban_config () { # Declare an array to define the options of this helper. local legacy_args=lrmptv diff --git a/data/helpers.d/nginx b/data/helpers.d/nginx index 7214b1e26..dca581d94 100644 --- a/data/helpers.d/nginx +++ b/data/helpers.d/nginx @@ -15,7 +15,7 @@ # This allows to enable/disable specific behaviors dependenging on the install # location # -# Requires YunoHost version 2.7.2 or higher. +# Requires YunoHost version 4.1.0 or higher. ynh_add_nginx_config () { local finalnginxconf="/etc/nginx/conf.d/$domain.d/$app.conf" diff --git a/data/helpers.d/php b/data/helpers.d/php index 40a023e9d..8548df6ab 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -55,9 +55,7 @@ YNH_PHP_VERSION=${YNH_PHP_VERSION:-$YNH_DEFAULT_PHP_VERSION} # RAM) but the impact on the proc is lower. The service will be quick to answer as there's always many # children ready to answer. # -# Requires YunoHost version 2.7.2 or higher. -# Requires YunoHost version 3.5.1 or higher for the argument --phpversion -# Requires YunoHost version 3.8.1 or higher for the arguments --use_template, --usage, --footprint, --package and --dedicated_service +# Requires YunoHost version 4.1.0 or higher. ynh_add_fpm_config () { # Declare an array to define the options of this helper. local legacy_args=vtufpd diff --git a/data/helpers.d/systemd b/data/helpers.d/systemd index 09f37844c..d0f88b5f7 100644 --- a/data/helpers.d/systemd +++ b/data/helpers.d/systemd @@ -11,7 +11,7 @@ # See the documentation of `ynh_add_config` for a description of the template # format and how placeholders are replaced with actual variables. # -# Requires YunoHost version 2.7.11 or higher. +# Requires YunoHost version 4.1.0 or higher. ynh_add_systemd_config () { # Declare an array to define the options of this helper. local legacy_args=stv From c8d4bbf82b4f411c08a346b392747d4761a15992 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 10 Jun 2021 15:44:00 +0200 Subject: [PATCH 2567/3170] Case-incensitive search are likely to catch too mnuch legitimate stuff resulting in redacting a shitload of stuff --- src/yunohost/log.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/yunohost/log.py b/src/yunohost/log.py index b70725b48..d36671ce2 100644 --- a/src/yunohost/log.py +++ b/src/yunohost/log.py @@ -415,9 +415,8 @@ class RedactingFormatter(Formatter): # (the secret part being at least 3 chars to avoid catching some lines like just "db_pwd=") # Some names like "key" or "manifest_key" are ignored, used in helpers like ynh_app_setting_set or ynh_read_manifest match = re.search( - r"(pwd|pass|password|passphrase|secret\w*|\w+key|token)=(\S{3,})$", + r"(pwd|pass|password|passphrase|secret\w*|\w+key|token|PASSPHRASE)=(\S{3,})$", record.strip(), - re.IGNORECASE ) if ( match From 4a22f6b390ec4277de478d50f49e9d7df8fa2773 Mon Sep 17 00:00:00 2001 From: ljf Date: Thu, 10 Jun 2021 19:28:49 +0200 Subject: [PATCH 2568/3170] [fix] yunohost user list --fields mail-alias --- src/yunohost/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index ee26533e8..1037d417f 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -119,7 +119,7 @@ def user_list(fields=None): values = user[ldap_attrs[field]] entry[field] = display.get(field, display_default)(values, user) - users[entry['username']] = entry + users[user['uid'][0]] = entry return {"users": users} From b082f0314c227706e1fdf82249f0a80185661169 Mon Sep 17 00:00:00 2001 From: Paco Date: Fri, 11 Jun 2021 16:01:04 +0200 Subject: [PATCH 2569/3170] Split registrar settings to /etc/yunohost/registrars/{dns_zone}.yml --- src/yunohost/domain.py | 58 +++++++++++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 17 deletions(-) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 51f7c9b82..e800798a9 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -54,6 +54,7 @@ from yunohost.hook import hook_callback logger = getActionLogger("yunohost.domain") DOMAIN_SETTINGS_DIR = "/etc/yunohost/domains" +REGISTRAR_SETTINGS_DIR = "/etc/yunohost/registrars" REGISTRAR_LIST_PATH = "/usr/share/yunohost/other/providers_list.yml" @@ -771,6 +772,22 @@ def _load_domain_settings(): return new_domains +def _load_registrar_setting(dns_zone): + """ + Retrieve entries in registrars/[dns_zone].yml + """ + + on_disk_settings = {} + filepath = f"{REGISTRAR_SETTINGS_DIR}/{dns_zone}.yml" + if os.path.exists(filepath) and os.path.isfile(filepath): + on_disk_settings = yaml.load(open(filepath, "r+")) + # If the file is empty or "corrupted" + if not type(on_disk_settings) is dict: + on_disk_settings = {} + + return on_disk_settings + + def domain_setting(domain, key, value=None, delete=False): """ Set or get an app setting value @@ -869,27 +886,32 @@ def _set_domain_settings(domain, domain_settings): yaml.dump(domain_settings, file, default_flow_style=False) -def domain_registrar_info(domain): - +def _load_zone_of_domain(domain): domains = _load_domain_settings() if not domain in domains.keys(): + # TODO add locales raise YunohostError("domain_name_unknown", domain=domain) + + return domains[domain]["dns_zone"] + + +def domain_registrar_info(domain): + + dns_zone = _load_zone_of_domain(domain) + registrar_info = _load_registrar_setting(dns_zone) + if not registrar_info: + # TODO add locales + raise YunohostError("no_registrar_set_for_this_dns_zone", dns_zone=dns_zone) - provider = domains[domain]["provider"] - - if provider: - logger.info("Registrar name : " + provider['name']) - for option in provider['options']: - logger.info("Option " + option + " : "+provider['options'][option]) - else: - logger.info("Registrar settings are not set for " + domain) + logger.info("Registrar name: " + registrar_info['name']) + for option_key, option_value in registrar_info['options'].items(): + logger.info("Option " + option_key + ": " + option_value) def domain_registrar_set(domain, registrar, args): - domains = _load_domain_settings() - if not domain in domains.keys(): - raise YunohostError("domain_name_unknown", domain=domain) + dns_zone = _load_zone_of_domain(domain) + registrar_info = _load_registrar_setting(dns_zone) registrars = yaml.load(open(REGISTRAR_LIST_PATH, "r+")) if not registrar in registrars.keys(): @@ -916,11 +938,13 @@ def domain_registrar_set(domain, registrar, args): for arg_name, arg_value_and_type in parsed_answer_dict.items(): domain_provider["options"][arg_name] = arg_value_and_type[0] - domain_settings = domains[domain] - domain_settings["provider"] = domain_provider - + # First create the REGISTRAR_SETTINGS_DIR if it doesn't exist + if not os.path.exists(REGISTRAR_SETTINGS_DIR): + os.mkdir(REGISTRAR_SETTINGS_DIR) # Save the settings to the .yaml file - _set_domain_settings(domain, domain_settings) + filepath = f"{REGISTRAR_SETTINGS_DIR}/{dns_zone}.yml" + with open(filepath, "w") as file: + yaml.dump(domain_provider, file, default_flow_style=False) def domain_push_config(domain): From cacfcca52899d65a9b7da93dcad2cfafb90b6848 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 11 Jun 2021 20:21:27 +0200 Subject: [PATCH 2570/3170] Update changelog for 4.2.6 --- debian/changelog | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/debian/changelog b/debian/changelog index ae01bcb35..5646caace 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,21 @@ +yunohost (4.2.6) stable; urgency=low + + - [fix] metronome/xmpp: deactivate stanza mention optimization / have quick notification in chat group ([#1164](https://github.com/YunoHost/yunohost/pull/1164)) + - [enh] metronome/xmpp: activate module pubsub ([#1170](https://github.com/YunoHost/yunohost/pull/1170)) + - [fix] upgrade: undefined 'apps' variable (923f703e) + - [fix] python3: fix string split in postgresql migration (14d4cec8) + - [fix] python3: python2 was still used in helpers (bd196c87) + - [fix] security: fail2ban rule for yunohost-api login (b837d3da) + - [fix] backup: Apply realpath to find mounted points to unmount ([#1239](https://github.com/YunoHost/yunohost/pull/1239)) + - [mod] dnsmasq: Remove LDN from resolver list (a97fce05) + - [fix] logs: redact borg's passphrase (dbe5e51e, c8d4bbf8) + - [i18n] Translations updated for Galician, German, Italian + - Misc fixes/enh for tests and CI (8a5213c8, e5a03cab, [#1249](https://github.com/YunoHost/yunohost/pull/1249), [#1251](https://github.com/YunoHost/yunohost/pull/1251)) + + Thanks to all contributors <3 ! (Christian Wehrli, Flavio Cristoforetti, Gabriel, José M, Kay0u, ljf, tofbouf, yalh76) + + -- Alexandre Aubin Fri, 11 Jun 2021 20:12:20 +0200 + yunohost (4.2.5.3) stable; urgency=low - [fix] doc, helpers: Helper doc auto-generation job (f2886510) From 7026a8cfd5cbc413cea9805298fafebf828f1366 Mon Sep 17 00:00:00 2001 From: Krakinou Date: Sat, 12 Jun 2021 16:49:05 +0200 Subject: [PATCH 2571/3170] Check home folder before creating multimedia directory --- data/helpers.d/multimedia | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/data/helpers.d/multimedia b/data/helpers.d/multimedia index 2d43c2540..c86153e85 100644 --- a/data/helpers.d/multimedia +++ b/data/helpers.d/multimedia @@ -31,7 +31,10 @@ ynh_multimedia_build_main_dir() { mkdir -p "$MEDIA_DIRECTORY/$user/eBook" ln -sfn "$MEDIA_DIRECTORY/share" "$MEDIA_DIRECTORY/$user/Share" # Création du lien symbolique dans le home de l'utilisateur. - ln -sfn "$MEDIA_DIRECTORY/$user" "/home/$user/Multimedia" + #link will only be created if the home directory of the user exists and if it's located in '/home' folder + if [[ -d "$(getent passwd $user | cut -d: -f6)" && "$(getent passwd $user | cut -d: -f6 | grep home)" ]]; then + ln -sfn "$MEDIA_DIRECTORY/$user" "/home/$user/Multimedia" + fi # Propriétaires des dossiers utilisateurs. chown -R $user "$MEDIA_DIRECTORY/$user" done From f424c0c99944b8aac2c9b7724bf07ce4ea16af0a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 12 Jun 2021 17:48:12 +0200 Subject: [PATCH 2572/3170] CI: Use python3 for script remove stale strings --- .gitlab/ci/translation.gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab/ci/translation.gitlab-ci.yml b/.gitlab/ci/translation.gitlab-ci.yml index 83a9041d7..f6bd3f83f 100644 --- a/.gitlab/ci/translation.gitlab-ci.yml +++ b/.gitlab/ci/translation.gitlab-ci.yml @@ -14,11 +14,11 @@ remove-stale-translated-strings: - cd tests # Maybe move this script location to another folder? # create a local branch that will overwrite distant one - git checkout -b "ci-remove-stale-translated-strings-${CI_COMMIT_REF_NAME}" --no-track - - python remove_stale_translated_strings.py + - python3 remove_stale_translated_strings.py - '[ $(git diff | wc -l) != 0 ] || exit 0' # stop if there is nothing to commit - git commit -am "[CI] Remove stale translated strings" || true - git push -f origin "ci-remove-stale-translated-strings-${CI_COMMIT_REF_NAME}":"ci-remove-stale-translated-strings-${CI_COMMIT_REF_NAME}" - hub pull-request -m "[CI] Remove stale translated strings" -b Yunohost:dev -p || true # GITHUB_USER and GITHUB_TOKEN registered here https://gitlab.com/yunohost/yunohost/-/settings/ci_cd only: changes: - - locales/* \ No newline at end of file + - locales/* From 2ebac698a12d49486bdf764f21447ea0ef65be0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= Date: Mon, 7 Jun 2021 10:00:25 +0000 Subject: [PATCH 2573/3170] Translated using Weblate (Greek) Currently translated at 0.1% (1 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/el/ --- locales/el.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/locales/el.json b/locales/el.json index db0189666..480b63f1e 100644 --- a/locales/el.json +++ b/locales/el.json @@ -1,3 +1,4 @@ { - "password_too_simple_1": "Ο κωδικός πρόσβασης πρέπει να έχει τουλάχιστον 8 χαρακτήρες" -} \ No newline at end of file + "password_too_simple_1": "Ο κωδικός πρόσβασης πρέπει να έχει τουλάχιστον 8 χαρακτήρες", + "aborting": "Ματαίωση." +} From 87a7dde096808ed94bf67a397d7277af720fb4ff Mon Sep 17 00:00:00 2001 From: ppr Date: Tue, 8 Jun 2021 16:33:24 +0000 Subject: [PATCH 2574/3170] Translated using Weblate (French) Currently translated at 99.8% (632 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index e6bfacd1e..d3546c1b4 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -136,7 +136,7 @@ "upgrading_packages": "Mise à jour des paquets en cours...", "upnp_dev_not_found": "Aucun périphérique compatible UPnP n’a été trouvé", "upnp_disabled": "UPnP désactivé", - "upnp_enabled": "UPnP activé", + "upnp_enabled": "L'UPnP est activé", "upnp_port_open_failed": "Impossible d’ouvrir les ports UPnP", "user_created": "L’utilisateur a été créé", "user_creation_failed": "Impossible de créer l’utilisateur {user} : {error}", @@ -216,7 +216,7 @@ "restore_not_enough_disk_space": "Espace disponible insuffisant (L’espace libre est de {free_space:d} octets. Le besoin d’espace nécessaire est de {needed_space:d} octets. En appliquant une marge de sécurité, la quantité d’espace nécessaire est de {margin:d} octets)", "restore_system_part_failed": "Impossible de restaurer la partie '{part:s}' du système", "backup_couldnt_bind": "Impossible de lier {src:s} avec {dest:s}.", - "domain_dns_conf_is_just_a_recommendation": "Cette page montre la configuration *recommandée*. Elle ne configure *pas* le DNS pour vous. Il est de votre responsabilité que de configurer votre zone DNS chez votre fournisseur/registrar DNS avec cette recommandation.", + "domain_dns_conf_is_just_a_recommendation": "Cette commande vous montre la configuration *recommandée*. Elle n'établit pas réellement la configuration DNS pour vous. Il est de votre ressort de configurer votre zone DNS chez votre registrar/fournisseur conformément à cette recommandation.", "migrations_cant_reach_migration_file": "Impossible d’accéder aux fichiers de migration via le chemin '%s'", "migrations_loading_migration": "Chargement de la migration {id}...", "migrations_migration_has_failed": "La migration {id} a échoué avec l’exception {exception} : annulation", @@ -284,9 +284,9 @@ "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", - "good_practices_about_admin_password": "Vous êtes sur le point de définir un nouveau mot de passe d'administration. Le mot de passe doit comporter au moins 8 caractères, bien qu'il soit recommandé d'utiliser un mot de passe plus long (c'est-à-dire une phrase de passe) et / ou d'utiliser une variation de caractères (majuscule, minuscule, chiffres et caractères spéciaux).", - "good_practices_about_user_password": "Vous êtes sur le point de définir un nouveau mot de passe utilisateur. Le mot de passe doit comporter au moins 8 caractères, bien qu'il soit recommandé d'utiliser un mot de passe plus long (c'est-à-dire une phrase secrète) et / ou une variation de caractères (majuscule, minuscule, chiffres et caractères spéciaux).", - "password_listed": "Ce mot de passe fait partie des mots de passe les plus utilisés au monde. Veuillez choisir quelque chose de plus unique.", + "good_practices_about_admin_password": "Vous êtes sur le point de définir un nouveau mot de passe d'administration. Le mot de passe doit comporter au moins 8 caractères, bien qu'il soit recommandé d'utiliser un mot de passe plus long (c'est-à-dire une phrase secrète) et/ou d'utiliser une combinaison de caractères (majuscules, minuscules, chiffres et caractères spéciaux).", + "good_practices_about_user_password": "Vous êtes sur le point de définir un nouveau mot de passe utilisateur. Le mot de passe doit comporter au moins 8 caractères, bien qu'il soit recommandé d'utiliser un mot de passe plus long (c'est-à-dire une phrase secrète) et/ou une combinaison de caractères (majuscules, minuscules, chiffres et caractères spéciaux).", + "password_listed": "Ce mot de passe fait partie des mots de passe les plus utilisés dans le monde. Veuillez en choisir un autre moins commun et plus fort.", "password_too_simple_1": "Le mot de passe doit comporter au moins 8 caractères", "password_too_simple_2": "Le mot de passe doit comporter au moins 8 caractères et contenir des chiffres, des majuscules et des minuscules", "password_too_simple_3": "Le mot de passe doit comporter au moins 8 caractères et contenir des chiffres, des majuscules, des minuscules et des caractères spéciaux", @@ -384,7 +384,7 @@ "migrations_failed_to_load_migration": "Impossible de charger la migration {id} : {error}", "migrations_running_forward": "Exécution de la migration {id}...", "migrations_success_forward": "Migration {id} terminée", - "operation_interrupted": "L’opération a été interrompue manuellement ?", + "operation_interrupted": "L'opération a-t-elle été interrompue manuellement ?", "permission_already_exist": "L’autorisation '{permission}' existe déjà", "permission_created": "Permission '{permission:s}' créée", "permission_creation_failed": "Impossible de créer l’autorisation '{permission}' : {error}", @@ -489,7 +489,7 @@ "diagnosis_ports_forwarding_tip": "Pour résoudre ce problème, vous devez probablement configurer la redirection de port sur votre routeur Internet comme décrit dans https://yunohost.org/isp_box_config", "diagnosis_http_connection_error": "Erreur de connexion : impossible de se connecter au domaine demandé, il est probablement injoignable.", "diagnosis_no_cache": "Pas encore de cache de diagnostique pour la catégorie « {category} »", - "yunohost_postinstall_end_tip": "La post-installation terminée! Pour finaliser votre configuration, il est recommandé de :\n - ajouter un premier utilisateur depuis la section \"Utilisateurs\" de l’interface web (ou \"yunohost user create \" en ligne de commande) ;\n - diagnostiquer les potentiels problèmes dans la section \"Diagnostic\" de l'interface web (ou \"yunohost diagnosis run\" en ligne de commande) ;\n - lire les parties \"Finalisation de votre configuration\" et \"Découverte de YunoHost\" dans le guide de l’administrateur: https://yunohost.org/admindoc.", + "yunohost_postinstall_end_tip": "La post-installation terminée ! Pour finaliser votre configuration, il est recommandé de :\n- ajouter un premier utilisateur depuis la section \"Utilisateurs\" de l’interface web (ou 'yunohost user create ' en ligne de commande) ;\n- diagnostiquer les potentiels problèmes dans la section \"Diagnostic\" de l'interface web (ou 'yunohost diagnosis run' en ligne de commande) ;\n- lire les parties 'Finalisation de votre configuration' et 'Découverte de YunoHost' dans le guide de l’administrateur : https://yunohost.org/admindoc.", "diagnosis_services_bad_status_tip": "Vous pouvez essayer de redémarrer le service, et si cela ne fonctionne pas, consultez les journaux de service dans le webadmin (à partir de la ligne de commande, vous pouvez le faire avec yunohost service restart {service} et yunohost service log {service} ).", "diagnosis_http_bad_status_code": "Le système de diagnostique n’a pas réussi à contacter votre serveur. Il se peut qu’une autre machine réponde à la place de votre serveur. Vérifiez que le port 80 est correctement redirigé, que votre configuration Nginx est à jour et qu’un reverse-proxy n’interfère pas.", "diagnosis_http_timeout": "Expiration du délai en essayant de contacter votre serveur de l’extérieur. Il semble être inaccessible. Vérifiez que vous transférez correctement le port 80, que Nginx est en cours d’exécution et qu’un pare-feu n’interfère pas.", From 64be89adca13911972ebcdaf0776efecdfad420e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M?= Date: Wed, 9 Jun 2021 04:15:52 +0000 Subject: [PATCH 2575/3170] Translated using Weblate (Galician) Currently translated at 21.1% (134 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/gl/ --- locales/gl.json | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/locales/gl.json b/locales/gl.json index da9a2b001..d50943ffd 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -116,5 +116,21 @@ "backup_method_tar_finished": "Creouse o arquivo de copia TAR", "backup_method_custom_finished": "O método de copia personalizado '{method:s}' rematou", "backup_method_copy_finished": "Rematou o copiado dos ficheiros", - "backup_hook_unknown": "O gancho da copia '{hook:s}' é descoñecido" + "backup_hook_unknown": "O gancho da copia '{hook:s}' é descoñecido", + "certmanager_domain_cert_not_selfsigned": "O certificado para o dominio {domain:s} non está auto-asinado. Tes a certeza de querer substituílo? (Usa '--force' para facelo.)", + "certmanager_domain_not_diagnosed_yet": "Por agora non hai resultado de diagnóstico para o dominio {domain}. Volve facer o diagnóstico para a categoría 'Rexistros DNS' e 'Web' na sección de diagnóstico para comprobar se o dominio é compatible con Let's Encrypt. (Ou se sabes o que estás a facer, usa '--no-checks' para desactivar esas comprobacións.)", + "certmanager_certificate_fetching_or_enabling_failed": "Fallou o intento de usar o novo certificado para '{domain:s}'...", + "certmanager_cert_signing_failed": "Non se puido asinar o novo certificado", + "certmanager_cert_renew_success": "Certificado Let's Encrypt renovado para o dominio '{domain:s}'", + "certmanager_cert_install_success_selfsigned": "O certificado auto-asinado está instalado para o dominio '{domain:s}'", + "certmanager_cert_install_success": "O certificado Let's Encrypt está instalado para o dominio '{domain:s}'", + "certmanager_cannot_read_cert": "Algo fallou ao intentar abrir o certificado actual para o dominio {domain:s} (ficheiro: {file:s}), razón: {reason:s}", + "certmanager_attempt_to_replace_valid_cert": "Estás intentando sobrescribir un certificado correcto e en bo estado para o dominio {domain:s}! (Usa --force para obviar)", + "certmanager_attempt_to_renew_valid_cert": "O certificado para o dominio '{domain:s}' non caduca pronto! (Podes usar --force se sabes o que estás a facer)", + "certmanager_attempt_to_renew_nonLE_cert": "O certificado para o dominio '{domain:s}' non está proporcionado por Let's Encrypt. Non se pode renovar automáticamente!", + "certmanager_acme_not_configured_for_domain": "Non se realizou o desafío ACME para {domain} porque a súa configuración nginx non ten a parte do código correspondente... Comproba que a túa configuración nginx está ao día utilizando `yunohost tools regen-conf nginx --dry-run --with-diff`.", + "backup_with_no_restore_script_for_app": "'{app:s}' non ten script de restablecemento, non poderás restablecer automáticamente a copia de apoio desta app.", + "backup_with_no_backup_script_for_app": "A app '{app:s}' non ten script para a copia. Ignorada.", + "backup_unable_to_organize_files": "Non se puido usar o método rápido para organizar ficheiros no arquivo", + "backup_system_part_failed": "Non se puido facer copia da parte do sistema '{part:s}'" } From 079f67629db2859dbeb7b76e01685588fdf4dc8b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 12 Jun 2021 18:38:52 +0200 Subject: [PATCH 2576/3170] i18n: Remove stale strings --- locales/ca.json | 9 +-------- locales/cs.json | 2 +- locales/de.json | 9 +-------- locales/el.json | 2 +- locales/eo.json | 3 --- locales/es.json | 3 --- locales/fi.json | 2 +- locales/fr.json | 9 +-------- locales/gl.json | 2 +- locales/it.json | 9 +-------- locales/nb_NO.json | 2 -- locales/nl.json | 2 -- locales/oc.json | 5 +---- locales/pt.json | 2 -- locales/zh_Hans.json | 5 +---- 15 files changed, 10 insertions(+), 56 deletions(-) diff --git a/locales/ca.json b/locales/ca.json index 189053d94..1e4c55f5d 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -173,7 +173,6 @@ "hook_list_by_invalid": "Aquesta propietat no es pot utilitzar per llistar els hooks", "hook_name_unknown": "Nom de script « {name:s} » desconegut", "installation_complete": "Instal·lació completada", - "installation_failed": "Ha fallat alguna cosa amb la instal·lació", "ip6tables_unavailable": "No podeu modificar les ip6tables aquí. O bé sou en un contenidor o bé el vostre nucli no és compatible amb aquesta opció", "iptables_unavailable": "No podeu modificar les iptables aquí. O bé sou en un contenidor o bé el vostre nucli no és compatible amb aquesta opció", "log_corrupted_md_file": "El fitxer de metadades YAML associat amb els registres està malmès: « {md_file} »\nError: {error}", @@ -213,8 +212,6 @@ "already_up_to_date": "No hi ha res a fer. Tot està actualitzat.", "dpkg_lock_not_available": "No es pot utilitzar aquesta comanda en aquest moment ja que sembla que un altre programa està utilitzant el lock de dpkg (el gestor de paquets del sistema)", "global_settings_setting_security_postfix_compatibility": "Solució de compromís entre compatibilitat i seguretat pel servidor Postfix. Afecta els criptògrafs (i altres aspectes relacionats amb la seguretat)", - "ldap_init_failed_to_create_admin": "La inicialització de LDAP no ha pogut crear l'usuari admin", - "ldap_initialized": "S'ha iniciat LDAP", "mail_alias_remove_failed": "No s'han pogut eliminar els àlies del correu «{mail:s}»", "mail_domain_unknown": "El domini «{domain:s}» de l'adreça de correu no és vàlid. Utilitzeu un domini administrat per aquest servidor.", "mail_forward_remove_failed": "No s'han pogut eliminar el reenviament de correu «{mail:s}»", @@ -592,10 +589,6 @@ "pattern_email_forward": "Ha de ser una adreça de correu vàlida, s'accepta el símbol «+» (per exemple, algu+etiqueta@exemple.cat)", "invalid_number": "Ha de ser una xifra", "migration_0019_slapd_config_will_be_overwritten": "Sembla que heu modificat manualment la configuració sldap. Per a aquesta migració crítica, YunoHist necessita forçar l'actualització de la configuració sldap. Es crearà una còpia de seguretat dels fitxers originals a {conf_backup_folder}.", - "migration_0019_rollback_success": "S'ha restaurat el sistema.", - "migration_0019_migration_failed_trying_to_rollback": "No s'ha pogut fer la migració... intentant restaurar el sistema.", - "migration_0019_can_not_backup_before_migration": "No s'ha pogut completar la còpia de seguretat del sistema abans de que fallés la migració. Error: {error:s}", - "migration_0019_backup_before_migration": "Creant una còpia de seguretat de la base de dades LDAP i de la configuració de les aplicacions abans de la migració actual.", "migration_0019_add_new_attributes_in_ldap": "Afegir nous atributs per als permisos en la base de dades LDAP", "migration_description_0019_extend_permissions_features": "Amplia/refés el sistema de gestió dels permisos de l'aplicació", "migrating_legacy_permission_settings": "Migració dels paràmetres de permisos antics...", @@ -626,4 +619,4 @@ "diagnosis_sshd_config_insecure": "Sembla que la configuració SSH s'ha modificat manualment, i no es segura ha que no conté la directiva «AllowGroups» o «AllowUsers» per limitar l'accés a usuaris autoritzats.", "backup_create_size_estimation": "L'arxiu tindrà aproximadament {size} de dades.", "app_restore_script_failed": "S'ha produït un error en el script de restauració de l'aplicació" -} +} \ No newline at end of file diff --git a/locales/cs.json b/locales/cs.json index 7e593758f..2dcee0f2f 100644 --- a/locales/cs.json +++ b/locales/cs.json @@ -65,4 +65,4 @@ "global_settings_setting_security_ssh_compatibility": "Kompromis mezi kompatibilitou a bezpečností SSH serveru. Ovlivní šifry a další související bezpečnostní nastavení", "global_settings_setting_security_password_user_strength": "Síla uživatelského hesla", "global_settings_setting_security_password_admin_strength": "Síla administračního hesla" -} +} \ No newline at end of file diff --git a/locales/de.json b/locales/de.json index 512296389..7d139312b 100644 --- a/locales/de.json +++ b/locales/de.json @@ -66,10 +66,8 @@ "hook_list_by_invalid": "Dieser Wert kann nicht verwendet werden, um Hooks anzuzeigen", "hook_name_unknown": "Hook '{name:s}' ist nicht bekannt", "installation_complete": "Installation vollständig", - "installation_failed": "Etwas ist mit der Installation falsch gelaufen", "ip6tables_unavailable": "ip6tables kann nicht verwendet werden. Du befindest dich entweder in einem Container oder es wird nicht vom Kernel unterstützt", "iptables_unavailable": "iptables kann nicht verwendet werden. Du befindest dich entweder in einem Container oder es wird nicht vom Kernel unterstützt", - "ldap_initialized": "LDAP initialisiert", "mail_alias_remove_failed": "Konnte E-Mail-Alias '{mail:s}' nicht entfernen", "mail_domain_unknown": "Die Domäne '{domain:s}' dieser E-Mail-Adresse ist ungültig. Wähle bitte eine Domäne, welche durch diesen Server verwaltet wird.", "mail_forward_remove_failed": "Die Weiterleitungs-E-Mail '{mail:s}' konnte nicht gelöscht werden", @@ -151,7 +149,6 @@ "domains_available": "Verfügbare Domains:", "dyndns_key_not_found": "DNS-Schlüssel für die Domain wurde nicht gefunden", "dyndns_no_domain_registered": "Keine Domain mit DynDNS registriert", - "ldap_init_failed_to_create_admin": "Die LDAP-Initialisierung konnte keinen admin-Benutzer erstellen", "mailbox_used_space_dovecot_down": "Der Dovecot-Mailbox-Dienst muss aktiv sein, wenn Sie den von der Mailbox belegten Speicher abrufen wollen", "certmanager_attempt_to_replace_valid_cert": "Du versuchst gerade eine richtiges und gültiges Zertifikat der Domain {domain:s} zu überschreiben! (Benutze --force , um diese Nachricht zu umgehen)", "certmanager_domain_cert_not_selfsigned": "Das Zertifikat der Domain {domain:s} ist kein selbstsigniertes Zertifikat. Sind Sie sich sicher, dass Sie es ersetzen wollen? (Benutzen Sie dafür '--force')", @@ -510,11 +507,8 @@ "migration_0015_weak_certs": "Die folgenden Zertifikate verwenden immer noch schwache Signierungsalgorithmen und müssen aktualisiert werden um mit der nächsten Version von nginx kompatibel zu sein: {certs}", "migrations_pending_cant_rerun": "Diese Migrationen sind immer noch anstehend und können deshalb nicht erneut durchgeführt werden: {ids}", "migration_0019_add_new_attributes_in_ldap": "Hinzufügen neuer Attribute für die Berechtigungen in der LDAP-Datenbank", - "migration_0019_can_not_backup_before_migration": "Das Backup des Systems konnte nicht abgeschlossen werden bevor die Migration fehlschlug. Fehlermeldung: {error}", - "migration_0019_migration_failed_trying_to_rollback": "Konnte nicht migrieren... versuche ein Rollback des Systems.", "migrations_not_pending_cant_skip": "Diese Migrationen sind nicht anstehend und können deshalb nicht übersprungen werden: {ids}", "migration_0018_failed_to_reset_legacy_rules": "Zurücksetzen der veralteten iptables-Regeln fehlgeschlagen: {error}", - "migration_0019_rollback_success": "Rollback des Systems durchgeführt.", "migration_0019_slapd_config_will_be_overwritten": "Es schaut aus, als ob Sie die slapd-Konfigurationsdatei manuell bearbeitet haben. Für diese kritische Migration muss das Update der slapd-Konfiguration erzwungen werden. Von der Originaldatei wird ein Backup gemacht in {conf_backup_folder}.", "migrations_success_forward": "Migration {id} abgeschlossen", "migrations_cant_reach_migration_file": "Die Migrationsdateien konnten nicht aufgerufen werden im Verzeichnis '%s'", @@ -530,7 +524,6 @@ "migration_0017_postgresql_11_not_installed": "PostgreSQL 9.6 ist installiert aber nicht postgreSQL 11? Etwas komisches ist Ihrem System zugestossen :(...", "migration_0017_not_enough_space": "Stellen Siea ausreichend Speicherplatz im Verzeichnis {path} zur Verfügung um die Migration durchzuführen.", "migration_0018_failed_to_migrate_iptables_rules": "Migration der veralteten iptables-Regeln zu nftables fehlgeschlagen: {error}", - "migration_0019_backup_before_migration": "Ein Backup der LDAP-Datenbank und der Applikationseinstellungen erstellen vor der Migration.", "migrations_exclusive_options": "'--auto', '--skip' und '--force-rerun' sind Optionen, die sich gegenseitig ausschliessen.", "migrations_no_such_migration": "Es existiert keine Migration genannt '{id}'", "migrations_running_forward": "Durchführen der Migrationen {id}...", @@ -631,4 +624,4 @@ "tools_upgrade_cant_both": "Kann das Upgrade für das System und die Anwendungen nicht gleichzeitig durchführen", "tools_upgrade_at_least_one": "Bitte geben Sie '--apps' oder '--system' an", "this_action_broke_dpkg": "Diese Aktion hat unkonfigurierte Pakete verursacht, welche durch dpkg/apt (die Paketverwaltungen dieses Systems) zurückgelassen wurden... Sie können versuchen dieses Problem zu lösen, indem Sie 'sudo apt install --fix-broken' und/oder 'sudo dpkg --configure -a' ausführen." -} +} \ No newline at end of file diff --git a/locales/el.json b/locales/el.json index 480b63f1e..a85bd0710 100644 --- a/locales/el.json +++ b/locales/el.json @@ -1,4 +1,4 @@ { "password_too_simple_1": "Ο κωδικός πρόσβασης πρέπει να έχει τουλάχιστον 8 χαρακτήρες", "aborting": "Ματαίωση." -} +} \ No newline at end of file diff --git a/locales/eo.json b/locales/eo.json index ba1f96675..5c34ff831 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -183,7 +183,6 @@ "upnp_disabled": "UPnP malŝaltis", "service_started": "Servo '{service:s}' komenciĝis", "port_already_opened": "Haveno {port:d} estas jam malfermita por {ip_version:s} rilatoj", - "installation_failed": "Io okazis malbone kun la instalado", "upgrading_packages": "Ĝisdatigi pakojn…", "custom_app_url_required": "Vi devas provizi URL por altgradigi vian kutimon app {app:s}", "service_reload_failed": "Ne povis reŝargi la servon '{service:s}'\n\nLastatempaj servaj protokoloj: {logs:s}", @@ -204,7 +203,6 @@ "certmanager_attempt_to_renew_nonLE_cert": "La atestilo por la domajno '{domain:s}' ne estas elsendita de Let's Encrypt. Ne eblas renovigi ĝin aŭtomate!", "domain_dyndns_already_subscribed": "Vi jam abonis DynDNS-domajnon", "log_letsencrypt_cert_renew": "Renovigu '{}' Let's Encrypt atestilon", - "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} '", @@ -347,7 +345,6 @@ "mail_alias_remove_failed": "Ne povis forigi retpoŝton alias '{mail:s}'", "regenconf_file_manually_removed": "La dosiero de agordo '{conf}' estis forigita permane, kaj ne estos kreita", "domain_exists": "La domajno jam ekzistas", - "ldap_initialized": "LDAP inicializis", "certmanager_domain_cert_not_selfsigned": "La atestilo por domajno {domain:s} ne estas mem-subskribita. Ĉu vi certas, ke vi volas anstataŭigi ĝin? (Uzu '--force' por fari tion.)", "certmanager_unable_to_parse_self_CA_name": "Ne povis trapasi nomon de mem-subskribinta aŭtoritato (dosiero: {file:s})", "log_selfsigned_cert_install": "Instalu mem-subskribitan atestilon sur '{}' domajno", diff --git a/locales/es.json b/locales/es.json index f08803fb6..f95451922 100644 --- a/locales/es.json +++ b/locales/es.json @@ -74,10 +74,8 @@ "hook_list_by_invalid": "Esta propiedad no se puede usar para enumerar ganchos («hooks»)", "hook_name_unknown": "Nombre de hook desconocido '{name:s}'", "installation_complete": "Instalación finalizada", - "installation_failed": "Algo ha ido mal con la instalación", "ip6tables_unavailable": "No puede modificar ip6tables aquí. O bien está en un 'container' o su kernel no soporta esta opción", "iptables_unavailable": "No puede modificar iptables aquí. O bien está en un 'container' o su kernel no soporta esta opción", - "ldap_initialized": "Inicializado LDAP", "mail_alias_remove_failed": "No se pudo eliminar el alias de correo «{mail:s}»", "mail_domain_unknown": "Dirección de correo no válida para el dominio «{domain:s}». Use un dominio administrado por este servidor.", "mail_forward_remove_failed": "No se pudo eliminar el reenvío de correo «{mail:s}»", @@ -150,7 +148,6 @@ "yunohost_configured": "YunoHost está ahora configurado", "yunohost_installing": "Instalando YunoHost…", "yunohost_not_installed": "YunoHost no está correctamente instalado. Ejecute «yunohost tools postinstall»", - "ldap_init_failed_to_create_admin": "La inicialización de LDAP no pudo crear el usuario «admin»", "mailbox_used_space_dovecot_down": "El servicio de buzón Dovecot debe estar activo si desea recuperar el espacio usado del buzón", "certmanager_attempt_to_replace_valid_cert": "Está intentando sobrescribir un certificado correcto y válido para el dominio {domain:s}! (Use --force para omitir este mensaje)", "certmanager_domain_cert_not_selfsigned": "El certificado para el dominio {domain:s} no es un certificado autofirmado. ¿Está seguro de que quiere reemplazarlo? (Use «--force» para hacerlo)", diff --git a/locales/fi.json b/locales/fi.json index 0967ef424..9e26dfeeb 100644 --- a/locales/fi.json +++ b/locales/fi.json @@ -1 +1 @@ -{} +{} \ No newline at end of file diff --git a/locales/fr.json b/locales/fr.json index d3546c1b4..79ae8e6e7 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -74,10 +74,8 @@ "hook_list_by_invalid": "Propriété invalide pour lister les actions par celle-ci", "hook_name_unknown": "Nom de l’action '{name:s}' inconnu", "installation_complete": "Installation terminée", - "installation_failed": "Quelque chose s’est mal passé lors de l’installation", "ip6tables_unavailable": "Vous ne pouvez pas jouer avec ip6tables ici. Vous êtes soit dans un conteneur, soit votre noyau ne le prend pas en charge", "iptables_unavailable": "Vous ne pouvez pas jouer avec iptables ici. Vous êtes soit dans un conteneur, soit votre noyau ne le prend pas en charge", - "ldap_initialized": "L’annuaire LDAP a été initialisé", "mail_alias_remove_failed": "Impossible de supprimer l’alias de courriel '{mail:s}'", "mail_domain_unknown": "Le domaine '{domain:s}' de cette adresse de courriel n’est pas valide. Merci d’utiliser un domaine administré par ce serveur.", "mail_forward_remove_failed": "Impossible de supprimer le courriel de transfert '{mail:s}'", @@ -164,7 +162,6 @@ "certmanager_cert_signing_failed": "Impossible de signer le nouveau certificat", "certmanager_no_cert_file": "Impossible de lire le fichier du certificat pour le domaine {domain:s} (fichier : {file:s})", "certmanager_hit_rate_limit": "Trop de certificats ont déjà été émis récemment pour ce même ensemble de domaines {domain:s}. Veuillez réessayer plus tard. Lisez https://letsencrypt.org/docs/rate-limits/ pour obtenir plus de détails sur les ratios et limitations", - "ldap_init_failed_to_create_admin": "L’initialisation de l’annuaire LDAP n’a pas réussi à créer l’utilisateur admin", "domain_cannot_remove_main": "Vous ne pouvez pas supprimer '{domain:s}' car il s’agit du domaine principal. Vous devez d’abord définir un autre domaine comme domaine principal à l’aide de 'yunohost domain main-domain -n ', voici la liste des domaines candidats : {other_domains:s}", "certmanager_self_ca_conf_file_not_found": "Le fichier de configuration pour l’autorité du certificat auto-signé est introuvable (fichier : {file:s})", "certmanager_unable_to_parse_self_CA_name": "Impossible d’analyser le nom de l’autorité du certificat auto-signé (fichier : {file:s})", @@ -605,16 +602,12 @@ "regex_incompatible_with_tile": "/!\\ Packagers ! La permission '{permission}' a 'show_tile' définie sur 'true' et vous ne pouvez donc pas définir une URL regex comme URL principale", "permission_protected": "L'autorisation {permission} est protégée. Vous ne pouvez pas ajouter ou supprimer le groupe visiteurs à/de cette autorisation.", "migration_0019_slapd_config_will_be_overwritten": "Il semble que vous ayez modifié manuellement la configuration de slapd. Pour cette migration critique, YunoHost doit forcer la mise à jour de la configuration slapd. Les fichiers originaux seront sauvegardés dans {conf_backup_folder}.", - "migration_0019_migration_failed_trying_to_rollback": "Impossible de migrer... tentative de restauration du système.", - "migration_0019_can_not_backup_before_migration": "La sauvegarde du système n'a pas pu être effectuée avant l'échec de la migration. Erreur : {error:s}", - "migration_0019_backup_before_migration": "Création d'une sauvegarde de la base de données LDAP et des paramètres des applications avant la migration.", "migration_0019_add_new_attributes_in_ldap": "Ajouter de nouveaux attributs pour les autorisations dans la base de données LDAP", "migrating_legacy_permission_settings": "Migration des anciens paramètres d'autorisation...", "invalid_regex": "Regex non valide : '{regex:s}'", "domain_name_unknown": "Domaine '{domain}' inconnu", "app_label_deprecated": "Cette commande est obsolète ! Veuillez utiliser la nouvelle commande 'yunohost user permission update' pour gérer l'étiquette de l'application.", "additional_urls_already_removed": "URL supplémentaire '{url:s}' déjà supprimées pour la permission '{permission:s}'", - "migration_0019_rollback_success": "Retour à l'état antérieur du système.", "invalid_number": "Doit être un nombre", "migration_description_0019_extend_permissions_features": "Étendre et retravailler le système de gestion des permissions applicatives", "diagnosis_basesystem_hardware_model": "Le modèle du serveur est {model}", @@ -639,4 +632,4 @@ "diagnosis_sshd_config_inconsistent": "Il semble que le port SSH a été modifié manuellement dans /etc/ssh/sshd_config. Depuis YunoHost 4.2, un nouveau paramètre global 'security.ssh.port' est disponible pour éviter de modifier manuellement la configuration.", "diagnosis_sshd_config_insecure": "La configuration SSH semble avoir été modifiée manuellement et n'est pas sécurisée car elle ne contient aucune directive 'AllowGroups' ou 'AllowUsers' pour limiter l'accès aux utilisateurs autorisés.", "backup_create_size_estimation": "L'archive contiendra environ {size} de données." -} +} \ No newline at end of file diff --git a/locales/gl.json b/locales/gl.json index d50943ffd..511081353 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -133,4 +133,4 @@ "backup_with_no_backup_script_for_app": "A app '{app:s}' non ten script para a copia. Ignorada.", "backup_unable_to_organize_files": "Non se puido usar o método rápido para organizar ficheiros no arquivo", "backup_system_part_failed": "Non se puido facer copia da parte do sistema '{part:s}'" -} +} \ No newline at end of file diff --git a/locales/it.json b/locales/it.json index 2d432f56e..707f3afc2 100644 --- a/locales/it.json +++ b/locales/it.json @@ -9,7 +9,6 @@ "backup_output_directory_not_empty": "Dovresti scegliere una cartella di output vuota", "domain_created": "Dominio creato", "domain_exists": "Il dominio esiste già", - "ldap_initialized": "LDAP inizializzato", "pattern_email": "L'indirizzo email deve essere valido, senza simboli '+' (es. tizio@dominio.com)", "pattern_mailbox_quota": "La dimensione deve avere un suffisso b/k/M/G/T o 0 per disattivare la quota", "port_already_opened": "La porta {port:d} è già aperta per {ip_version:s} connessioni", @@ -89,10 +88,8 @@ "hook_exec_not_terminated": "Los script non è stato eseguito correttamente: {path:s}", "hook_name_unknown": "Nome di hook '{name:s}' sconosciuto", "installation_complete": "Installazione completata", - "installation_failed": "Qualcosa è andato storto durante l'installazione", "ip6tables_unavailable": "Non puoi giocare con ip6tables qui. O sei in un container o il tuo kernel non lo supporta", "iptables_unavailable": "Non puoi giocare con iptables qui. O sei in un container o il tuo kernel non lo supporta", - "ldap_init_failed_to_create_admin": "L'inizializzazione LDAP non è riuscita a creare un utente admin", "mail_alias_remove_failed": "Impossibile rimuovere l'alias mail '{mail:s}'", "mail_domain_unknown": "Indirizzo mail non valido per il dominio '{domain:s}'. Usa un dominio gestito da questo server.", "mail_forward_remove_failed": "Impossibile rimuovere la mail inoltrata '{mail:s}'", @@ -500,10 +497,6 @@ "migrations_cant_reach_migration_file": "Impossibile accedere ai file di migrazione nel path '%s'", "migrations_already_ran": "Migrazioni già effettuate: {ids}", "migration_0019_slapd_config_will_be_overwritten": "Sembra che tu abbia modificato manualmente la configurazione slapd. Per questa importante migrazione, YunoHost deve forzare l'aggiornamento della configurazione slapd. I file originali verranno back-uppati in {conf_backup_folder}.", - "migration_0019_rollback_success": "Sistema ripristinato.", - "migration_0019_migration_failed_trying_to_rollback": "Impossibile migrare... sto cercando di ripristinare il sistema.", - "migration_0019_can_not_backup_before_migration": "Il backup del sistema non è stato completato prima della migrazione. Errore: {error:s}", - "migration_0019_backup_before_migration": "Creando un backup del database LDAP e delle impostazioni delle app prima dell'effettiva migrazione.", "migration_0019_add_new_attributes_in_ldap": "Aggiungi nuovi attributi ai permessi nel database LDAP", "migration_0018_failed_to_reset_legacy_rules": "Impossibile resettare le regole iptables legacy: {error}", "migration_0018_failed_to_migrate_iptables_rules": "Migrazione fallita delle iptables legacy a nftables: {error}", @@ -639,4 +632,4 @@ "diagnosis_sshd_config_insecure": "Sembra che la configurazione SSH sia stata modificata manualmente, ed non è sicuro dato che non contiene le direttive 'AllowGroups' o 'Allowusers' che limitano l'accesso agli utenti autorizzati.", "backup_create_size_estimation": "L'archivio conterrà circa {size} di dati.", "app_restore_script_failed": "C'è stato un errore all'interno dello script di recupero" -} +} \ No newline at end of file diff --git a/locales/nb_NO.json b/locales/nb_NO.json index 295ec5070..74583c992 100644 --- a/locales/nb_NO.json +++ b/locales/nb_NO.json @@ -98,8 +98,6 @@ "log_user_delete": "Slett '{}' bruker", "log_user_group_delete": "Slett '{}' gruppe", "log_user_group_update": "Oppdater '{}' gruppe", - "ldap_init_failed_to_create_admin": "LDAP-igangsettelse kunne ikke opprette admin-bruker", - "ldap_initialized": "LDAP-igangsatt", "app_unknown": "Ukjent program", "app_upgrade_app_name": "Oppgraderer {app}…", "app_upgrade_failed": "Kunne ikke oppgradere {app:s}", diff --git a/locales/nl.json b/locales/nl.json index 811c006b6..3894a5f9c 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -41,8 +41,6 @@ "dyndns_unavailable": "DynDNS subdomein is niet beschikbaar", "extracting": "Uitpakken...", "installation_complete": "Installatie voltooid", - "installation_failed": "Installatie gefaald", - "ldap_initialized": "LDAP is klaar voor gebruik", "mail_alias_remove_failed": "Kan mail-alias '{mail:s}' niet verwijderen", "pattern_email": "Moet een geldig emailadres bevatten (bv. abc@example.org)", "pattern_mailbox_quota": "Mailbox quota moet een waarde bevatten met b/k/M/G/T erachter of 0 om geen quota in te stellen", diff --git a/locales/oc.json b/locales/oc.json index ec272edfb..991383bc3 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -128,8 +128,6 @@ "global_settings_key_doesnt_exists": "La clau « {settings_key:s} » existís pas dins las configuracions globalas, podètz veire totas las claus disponiblas en executant « yunohost settings list »", "global_settings_reset_success": "Configuracion precedenta ara salvagarda dins {path:s}", "global_settings_unknown_setting_from_settings_file": "Clau desconeguda dins los paramètres : {setting_key:s}, apartada e salvagardada dins /etc/yunohost/settings-unknown.json", - "installation_failed": "Quicòm a trucat e l’installacion a pas reüssit", - "ldap_initialized": "L’annuari LDAP es inicializat", "main_domain_change_failed": "Modificacion impossibla del domeni màger", "main_domain_changed": "Lo domeni màger es estat modificat", "migrations_cant_reach_migration_file": "Impossible d’accedir als fichièrs de migracion amb lo camin %s", @@ -169,7 +167,6 @@ "hook_exec_not_terminated": "Lo escript « {path:s} » a pas acabat corrèctament", "hook_list_by_invalid": "La proprietat de tria de las accions es invalida", "hook_name_unknown": "Nom de script « {name:s} » desconegut", - "ldap_init_failed_to_create_admin": "L’inicializacion de LDAP a pas pogut crear l’utilizaire admin", "mail_domain_unknown": "Lo domeni de corrièl « {domain:s} » es desconegut", "mailbox_used_space_dovecot_down": "Lo servici corrièl Dovecot deu èsser aviat, se volètz conéisser l’espaci ocupat per la messatjariá", "service_disable_failed": "Impossible de desactivar lo servici « {service:s} »↵\n↵\nJornals recents : {logs:s}", @@ -522,4 +519,4 @@ "diagnosis_domain_expiration_not_found": "Impossible de verificar la data d’expiracion d’unes domenis", "backup_create_size_estimation": "L’archiu contendrà apr’aquí {size} de donadas.", "app_restore_script_failed": "Una error s’es producha a l’interior del script de restauracion de l’aplicacion" -} +} \ No newline at end of file diff --git a/locales/pt.json b/locales/pt.json index 6f3419781..9bb949dec 100644 --- a/locales/pt.json +++ b/locales/pt.json @@ -44,9 +44,7 @@ "field_invalid": "Campo inválido '{:s}'", "firewall_reloaded": "Firewall recarregada com êxito", "installation_complete": "Instalação concluída", - "installation_failed": "A instalação falhou", "iptables_unavailable": "Não pode alterar aqui a iptables. Ou o seu kernel não o suporta ou está num espaço reservado.", - "ldap_initialized": "LDAP inicializada com êxito", "mail_alias_remove_failed": "Não foi possível remover a etiqueta de correio '{mail:s}'", "mail_domain_unknown": "Domínio de endereço de correio '{domain:s}' inválido. Por favor, usa um domínio administrado per esse servidor.", "mail_forward_remove_failed": "Não foi possível remover o reencaminhamento de correio '{mail:s}'", diff --git a/locales/zh_Hans.json b/locales/zh_Hans.json index 83ec4f850..78ba55133 100644 --- a/locales/zh_Hans.json +++ b/locales/zh_Hans.json @@ -291,7 +291,6 @@ "yunohost_postinstall_end_tip": "后期安装完成! 为了最终完成你的设置,请考虑:\n -通过webadmin的“用户”部分添加第一个用户(或在命令行中'yunohost user create ' );\n -通过网络管理员的“诊断”部分(或命令行中的'yunohost diagnosis run')诊断潜在问题;\n -阅读管理文档中的“完成安装设置”和“了解YunoHost”部分: https://yunohost.org/admindoc.", "operation_interrupted": "该操作是否被手动中断?", "invalid_regex": "无效的正则表达式:'{regex:s}'", - "installation_failed": "安装出现问题", "installation_complete": "安装完成", "hook_name_unknown": "未知的钩子名称 '{name:s}'", "hook_list_by_invalid": "此属性不能用于列出钩子", @@ -619,8 +618,6 @@ "mail_forward_remove_failed": "无法删除电子邮件转发'{mail:s}'", "mail_domain_unknown": "域'{domain:s}'的电子邮件地址无效。请使用本服务器管理的域。", "mail_alias_remove_failed": "无法删除电子邮件别名'{mail:s}'", - "ldap_initialized": "LDAP已初始化", - "ldap_init_failed_to_create_admin": "LDAP初始化无法创建管理员用户", "log_tools_reboot": "重新启动服务器", "log_tools_shutdown": "关闭服务器", "log_tools_upgrade": "升级系统软件包", @@ -635,4 +632,4 @@ "log_user_group_create": "创建组'{}'", "log_user_delete": "删除用户'{}'", "log_user_create": "添加用户'{}'" -} +} \ No newline at end of file From 075526303eff445b93ebdb1002a740abd77e29a1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 14 Jun 2021 17:00:41 +0200 Subject: [PATCH 2577/3170] Misc tweaks and fixes, port ldap auth test from moulinette --- debian/control | 1 + locales/en.json | 3 ++ src/yunohost/authenticators/ldap_admin.py | 20 ++------ src/yunohost/tests/test_ldap.py | 58 +++++++++++++++++++++++ src/yunohost/utils/ldap.py | 2 +- tests/test_i18n_keys.py | 1 + 6 files changed, 67 insertions(+), 18 deletions(-) create mode 100644 src/yunohost/tests/test_ldap.py diff --git a/debian/control b/debian/control index ef5061fe7..4c34d4c0a 100644 --- a/debian/control +++ b/debian/control @@ -14,6 +14,7 @@ Depends: ${python3:Depends}, ${misc:Depends} , python3-psutil, python3-requests, python3-dnspython, python3-openssl , python3-miniupnpc, python3-dbus, python3-jinja2 , python3-toml, python3-packaging, python3-publicsuffix + , python3-ldap , apt, apt-transport-https, apt-utils, dirmngr , php7.3-common, php7.3-fpm, php7.3-ldap, php7.3-intl , mariadb-server, php7.3-mysql diff --git a/locales/en.json b/locales/en.json index 199c21b66..4eba8c9f0 100644 --- a/locales/en.json +++ b/locales/en.json @@ -358,6 +358,8 @@ "invalid_regex": "Invalid regex:'{regex:s}'", "ip6tables_unavailable": "You cannot play with ip6tables here. You are either in a container or your kernel does not support it", "iptables_unavailable": "You cannot play with iptables here. You are either in a container or your kernel does not support it", + "ldap_server_down": "Unable to reach LDAP server", + "ldap_server_is_down_restart_it": "The LDAP service is down, attempt to restart it...", "log_corrupted_md_file": "The YAML metadata file associated with logs is damaged: '{md_file}\nError: {error}'", "log_link_to_log": "Full log of this operation: '{desc}'", "log_help_to_get_log": "To view the log of the operation '{desc}', use the command 'yunohost log show {name}{name}'", @@ -470,6 +472,7 @@ "migrations_to_be_ran_manually": "Migration {id} has to be run manually. Please go to Tools → Migrations on the webadmin page, or run `yunohost tools migrations run`.", "not_enough_disk_space": "Not enough free space on '{path:s}'", "invalid_number": "Must be a number", + "invalid_password": "Invalid password", "operation_interrupted": "The operation was manually interrupted?", "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.", diff --git a/src/yunohost/authenticators/ldap_admin.py b/src/yunohost/authenticators/ldap_admin.py index 734148536..47efe5bf9 100644 --- a/src/yunohost/authenticators/ldap_admin.py +++ b/src/yunohost/authenticators/ldap_admin.py @@ -7,7 +7,6 @@ import ldap.sasl import time from moulinette import m18n -from moulinette.core import MoulinetteError from moulinette.authentication import BaseAuthenticator from yunohost.utils.error import YunohostError @@ -15,19 +14,6 @@ logger = logging.getLogger("yunohost.authenticators.ldap_admin") class Authenticator(BaseAuthenticator): - """LDAP Authenticator - - Initialize a LDAP connexion for the given arguments. It attempts to - authenticate a user if 'user_rdn' is given - by associating user_rdn - and base_dn - and provides extra methods to manage opened connexion. - - Keyword arguments: - - uri -- The LDAP server URI - - base_dn -- The base dn - - user_rdn -- The user rdn to authenticate - - """ - name = "ldap_admin" def __init__(self, *args, **kwargs): @@ -46,10 +32,10 @@ class Authenticator(BaseAuthenticator): try: con = _reconnect() except ldap.INVALID_CREDENTIALS: - raise MoulinetteError("invalid_password") + raise YunohostError("invalid_password") except ldap.SERVER_DOWN: # ldap is down, attempt to restart it before really failing - logger.warning(m18n.g("ldap_server_is_down_restart_it")) + logger.warning(m18n.n("ldap_server_is_down_restart_it")) os.system("systemctl restart slapd") time.sleep(10) # waits 10 secondes so we are sure that slapd has restarted @@ -67,7 +53,7 @@ class Authenticator(BaseAuthenticator): raise else: if who != self.admindn: - raise MoulinetteError(f"Not logged with the appropriate identity ? Found {who}, expected {self.admindn} !?") + raise YunohostError(f"Not logged with the appropriate identity ? Found {who}, expected {self.admindn} !?", raw_msg=True) finally: # Free the connection, we don't really need it to keep it open as the point is only to check authentication... if con: diff --git a/src/yunohost/tests/test_ldap.py b/src/yunohost/tests/test_ldap.py new file mode 100644 index 000000000..b3a5efc09 --- /dev/null +++ b/src/yunohost/tests/test_ldap.py @@ -0,0 +1,58 @@ +import pytest +import os + +from yunohost.authenticators.ldap_admin import Authenticator as LDAPAuth +from yunohost.tools import tools_adminpw + +from moulinette import m18n +from moulinette.core import MoulinetteError + +def setup_function(function): + + if os.system("systemctl is-active slapd") != 0: + os.system("systemctl start slapd && sleep 3") + + tools_adminpw("yunohost", check_strength=False) + + +def test_authenticate(): + LDAPAuth().authenticate(password="yunohost") + + +def test_authenticate_with_wrong_password(): + with pytest.raises(MoulinetteError) as exception: + LDAPAuth().authenticate(password="bad_password_lul") + + translation = m18n.g("invalid_password") + expected_msg = translation.format() + assert expected_msg in str(exception) + + +def test_authenticate_server_down(mocker): + os.system("systemctl stop slapd && sleep 3") + + # Now if slapd is down, moulinette tries to restart it + mocker.patch("os.system") + mocker.patch("time.sleep") + with pytest.raises(MoulinetteError) as exception: + LDAPAuth().authenticate(password="yunohost") + + translation = m18n.n("ldap_server_down") + expected_msg = translation.format() + assert expected_msg in str(exception) + + +def test_authenticate_change_password(): + + LDAPAuth().authenticate(password="yunohost") + + tools_adminpw("plopette", check_strength=False) + + with pytest.raises(MoulinetteError) as exception: + LDAPAuth().authenticate(password="yunohost") + + translation = m18n.g("invalid_password") + expected_msg = translation.format() + assert expected_msg in str(exception) + + LDAPAuth().authenticate(password="plopette") diff --git a/src/yunohost/utils/ldap.py b/src/yunohost/utils/ldap.py index 28d7a17ce..1298eff69 100644 --- a/src/yunohost/utils/ldap.py +++ b/src/yunohost/utils/ldap.py @@ -94,7 +94,7 @@ class LDAPInterface(): con = _reconnect() except ldap.SERVER_DOWN: # ldap is down, attempt to restart it before really failing - logger.warning(m18n.g("ldap_server_is_down_restart_it")) + logger.warning(m18n.n("ldap_server_is_down_restart_it")) os.system("systemctl restart slapd") time.sleep(10) # waits 10 secondes so we are sure that slapd has restarted try: diff --git a/tests/test_i18n_keys.py b/tests/test_i18n_keys.py index 6876cbcd8..773a7f826 100644 --- a/tests/test_i18n_keys.py +++ b/tests/test_i18n_keys.py @@ -33,6 +33,7 @@ def find_expected_string_keys(): python_files = glob.glob("src/yunohost/*.py") python_files.extend(glob.glob("src/yunohost/utils/*.py")) python_files.extend(glob.glob("src/yunohost/data_migrations/*.py")) + python_files.extend(glob.glob("src/yunohost/authenticators/*.py")) python_files.extend(glob.glob("data/hooks/diagnosis/*.py")) python_files.append("bin/yunohost") From 52920cc52f777143875a3435e9d46a36d514db05 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 14 Jun 2021 17:31:08 +0200 Subject: [PATCH 2578/3170] password -> credentials, pave the way to multi-admins --- src/yunohost/authenticators/ldap_admin.py | 8 ++++++-- src/yunohost/tests/test_ldap.py | 12 ++++++------ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/yunohost/authenticators/ldap_admin.py b/src/yunohost/authenticators/ldap_admin.py index 47efe5bf9..808497b31 100644 --- a/src/yunohost/authenticators/ldap_admin.py +++ b/src/yunohost/authenticators/ldap_admin.py @@ -21,12 +21,16 @@ class Authenticator(BaseAuthenticator): self.basedn = "dc=yunohost,dc=org" self.admindn = "cn=admin,dc=yunohost,dc=org" - def authenticate(self, password=None): + def authenticate(self, credentials=None): + + # TODO : change authentication format + # to support another dn to support multi-admins + def _reconnect(): con = ldap.ldapobject.ReconnectLDAPObject( self.uri, retry_max=10, retry_delay=0.5 ) - con.simple_bind_s(self.admindn, password) + con.simple_bind_s(self.admindn, credentials) return con try: diff --git a/src/yunohost/tests/test_ldap.py b/src/yunohost/tests/test_ldap.py index b3a5efc09..0ad8366c9 100644 --- a/src/yunohost/tests/test_ldap.py +++ b/src/yunohost/tests/test_ldap.py @@ -16,12 +16,12 @@ def setup_function(function): def test_authenticate(): - LDAPAuth().authenticate(password="yunohost") + LDAPAuth().authenticate(credentials="yunohost") def test_authenticate_with_wrong_password(): with pytest.raises(MoulinetteError) as exception: - LDAPAuth().authenticate(password="bad_password_lul") + LDAPAuth().authenticate(credentials="bad_password_lul") translation = m18n.g("invalid_password") expected_msg = translation.format() @@ -35,7 +35,7 @@ def test_authenticate_server_down(mocker): mocker.patch("os.system") mocker.patch("time.sleep") with pytest.raises(MoulinetteError) as exception: - LDAPAuth().authenticate(password="yunohost") + LDAPAuth().authenticate(credentials="yunohost") translation = m18n.n("ldap_server_down") expected_msg = translation.format() @@ -44,15 +44,15 @@ def test_authenticate_server_down(mocker): def test_authenticate_change_password(): - LDAPAuth().authenticate(password="yunohost") + LDAPAuth().authenticate(credentials="yunohost") tools_adminpw("plopette", check_strength=False) with pytest.raises(MoulinetteError) as exception: - LDAPAuth().authenticate(password="yunohost") + LDAPAuth().authenticate(credentials="yunohost") translation = m18n.g("invalid_password") expected_msg = translation.format() assert expected_msg in str(exception) - LDAPAuth().authenticate(password="plopette") + LDAPAuth().authenticate(credentials="plopette") From 1fcfe734e96616c39b49681c1ba3d8db8dedf1c5 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 14 Jun 2021 18:23:55 +0200 Subject: [PATCH 2579/3170] Add test-ldapauth to ci --- .gitlab/ci/test.gitlab-ci.yml | 9 +++++++++ src/yunohost/tests/{test_ldap.py => test_ldapauth.py} | 0 2 files changed, 9 insertions(+) rename src/yunohost/tests/{test_ldap.py => test_ldapauth.py} (100%) diff --git a/.gitlab/ci/test.gitlab-ci.yml b/.gitlab/ci/test.gitlab-ci.yml index f146442e4..a9e14b6e4 100644 --- a/.gitlab/ci/test.gitlab-ci.yml +++ b/.gitlab/ci/test.gitlab-ci.yml @@ -181,3 +181,12 @@ test-service: only: changes: - src/yunohost/service.py + +test-ldapauth: + extends: .test-stage + script: + - cd src/yunohost + - python3 -m pytest tests/test_ldapauth.py + only: + changes: + - src/yunohost/authenticators/*.py diff --git a/src/yunohost/tests/test_ldap.py b/src/yunohost/tests/test_ldapauth.py similarity index 100% rename from src/yunohost/tests/test_ldap.py rename to src/yunohost/tests/test_ldapauth.py From 9c23f4390550a1c0161880ee0d9bbad6d62a6e90 Mon Sep 17 00:00:00 2001 From: Krakinou Date: Mon, 14 Jun 2021 23:28:31 +0200 Subject: [PATCH 2580/3170] check if home exists --- data/helpers.d/multimedia | 5 +++-- data/hooks/post_user_create/ynh_multimedia | 6 +++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/data/helpers.d/multimedia b/data/helpers.d/multimedia index c86153e85..a07f5fdfd 100644 --- a/data/helpers.d/multimedia +++ b/data/helpers.d/multimedia @@ -32,8 +32,9 @@ ynh_multimedia_build_main_dir() { ln -sfn "$MEDIA_DIRECTORY/share" "$MEDIA_DIRECTORY/$user/Share" # Création du lien symbolique dans le home de l'utilisateur. #link will only be created if the home directory of the user exists and if it's located in '/home' folder - if [[ -d "$(getent passwd $user | cut -d: -f6)" && "$(getent passwd $user | cut -d: -f6 | grep home)" ]]; then - ln -sfn "$MEDIA_DIRECTORY/$user" "/home/$user/Multimedia" + home="$(getent passwd $user | cut -d: -f6)" + if [[ -d "$home" && "$(echo "$home" | grep home)" ]]; then + ln -sfn "$MEDIA_DIRECTORY/$user" "$home/Multimedia" fi # Propriétaires des dossiers utilisateurs. chown -R $user "$MEDIA_DIRECTORY/$user" diff --git a/data/hooks/post_user_create/ynh_multimedia b/data/hooks/post_user_create/ynh_multimedia index 441212bbc..aa5ab48d3 100644 --- a/data/hooks/post_user_create/ynh_multimedia +++ b/data/hooks/post_user_create/ynh_multimedia @@ -15,7 +15,11 @@ mkdir -p "$MEDIA_DIRECTORY/$user/Video" mkdir -p "$MEDIA_DIRECTORY/$user/eBook" ln -sfn "$MEDIA_DIRECTORY/share" "$MEDIA_DIRECTORY/$user/Share" # Création du lien symbolique dans le home de l'utilisateur. -ln -sfn "$MEDIA_DIRECTORY/$user" "/home/$user/Multimedia" +#link will only be created if the home directory of the user exists and if it's located in '/home' folder +home="$(getent passwd $user | cut -d: -f6)" +if [[ -d "$home" && "$(echo "$home" | grep home)" ]]; then + ln -sfn "$MEDIA_DIRECTORY/$user" "$home/Multimedia" +fi # Propriétaires des dossiers utilisateurs. chown -R $user "$MEDIA_DIRECTORY/$user" From 29db3d51fff28e1c9bf1a2e129e1fcb1353d2c5b Mon Sep 17 00:00:00 2001 From: Krakinou Date: Mon, 14 Jun 2021 23:37:30 +0200 Subject: [PATCH 2581/3170] grep full /home/ --- data/helpers.d/multimedia | 2 +- data/hooks/post_user_create/ynh_multimedia | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/multimedia b/data/helpers.d/multimedia index a07f5fdfd..4ec7611fc 100644 --- a/data/helpers.d/multimedia +++ b/data/helpers.d/multimedia @@ -33,7 +33,7 @@ ynh_multimedia_build_main_dir() { # Création du lien symbolique dans le home de l'utilisateur. #link will only be created if the home directory of the user exists and if it's located in '/home' folder home="$(getent passwd $user | cut -d: -f6)" - if [[ -d "$home" && "$(echo "$home" | grep home)" ]]; then + if [[ -d "$home" && "$(echo "$home" | grep /home/)" ]]; then ln -sfn "$MEDIA_DIRECTORY/$user" "$home/Multimedia" fi # Propriétaires des dossiers utilisateurs. diff --git a/data/hooks/post_user_create/ynh_multimedia b/data/hooks/post_user_create/ynh_multimedia index aa5ab48d3..2fa02505a 100644 --- a/data/hooks/post_user_create/ynh_multimedia +++ b/data/hooks/post_user_create/ynh_multimedia @@ -17,7 +17,7 @@ ln -sfn "$MEDIA_DIRECTORY/share" "$MEDIA_DIRECTORY/$user/Share" # Création du lien symbolique dans le home de l'utilisateur. #link will only be created if the home directory of the user exists and if it's located in '/home' folder home="$(getent passwd $user | cut -d: -f6)" -if [[ -d "$home" && "$(echo "$home" | grep home)" ]]; then +if [[ -d "$home" && "$(echo "$home" | grep /home/)" ]]; then ln -sfn "$MEDIA_DIRECTORY/$user" "$home/Multimedia" fi # Propriétaires des dossiers utilisateurs. From 714893079c88dedbc9cf56829a3112df1cafda3a Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Wed, 16 Jun 2021 12:24:23 +0200 Subject: [PATCH 2582/3170] [fix] Remove invaluement from free dnsbl list --- data/other/dnsbl_list.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/data/other/dnsbl_list.yml b/data/other/dnsbl_list.yml index e65322f79..1dc0175a3 100644 --- a/data/other/dnsbl_list.yml +++ b/data/other/dnsbl_list.yml @@ -151,12 +151,6 @@ ipv4: true ipv6: false domain: false -- name: Invaluement - dns_server: sip.invaluement.com - website: https://www.invaluement.com/ - ipv4: true - ipv6: false - domain: false # Added cause it supports IPv6 - name: AntiCaptcha.NET IPv6 dns_server: dnsbl6.anticaptcha.net From c347ace037680927d82915f1d64afc3e808daefd Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Sat, 12 Jun 2021 17:46:02 +0000 Subject: [PATCH 2583/3170] Translated using Weblate (German) Currently translated at 96.5% (611 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/locales/de.json b/locales/de.json index 7d139312b..a44be0ab1 100644 --- a/locales/de.json +++ b/locales/de.json @@ -89,10 +89,10 @@ "restore_cleaning_failed": "Das temporäre Dateiverzeichnis für Systemrestaurierung konnte nicht gelöscht werden", "restore_complete": "Vollständig wiederhergestellt", "restore_confirm_yunohost_installed": "Möchtest du die Wiederherstellung wirklich starten? [{answers:s}]", - "restore_failed": "System kann nicht Wiederhergestellt werden", + "restore_failed": "Das System konnte nicht wiederhergestellt werden", "restore_hook_unavailable": "Das Wiederherstellungsskript für '{part:s}' steht weder in deinem System noch im Archiv zur Verfügung", "restore_nothings_done": "Nichts wurde wiederhergestellt", - "restore_running_app_script": "Wiederherstellung wird ausfeührt für App '{app:s}'...", + "restore_running_app_script": "App '{app:s}' wird wiederhergestellt…", "restore_running_hooks": "Wiederherstellung wird gestartet…", "service_add_failed": "Der Dienst '{service:s}' konnte nicht hinzugefügt werden", "service_added": "Der Dienst '{service:s}' wurde erfolgreich hinzugefügt", @@ -107,17 +107,17 @@ "service_removed": "Der Dienst '{service:s}' wurde erfolgreich entfernt", "service_start_failed": "Der Dienst '{service:s}' konnte nicht gestartet werden\n\nKürzlich erstellte Logs des Dienstes: {logs:s}", "service_started": "Der Dienst '{service:s}' wurde erfolgreich gestartet", - "service_stop_failed": "Der Dienst '{service:s}' kann nicht gestoppt werden", + "service_stop_failed": "Der Dienst '{service:s}' kann nicht gestoppt werden\n\nAktuelle Service-Logs: {log:s}", "service_stopped": "Der Dienst '{service:s}' wurde erfolgreich beendet", "service_unknown": "Unbekannter Dienst '{service:s}'", - "ssowat_conf_generated": "Die Konfiguration von SSOwat erstellt", + "ssowat_conf_generated": "Konfiguration von SSOwat neu erstellt", "ssowat_conf_updated": "Die Konfiguration von SSOwat aktualisiert", "system_upgraded": "System aktualisiert", "system_username_exists": "Der Benutzername existiert bereits in der Liste der System-Benutzer", - "unbackup_app": "App '{app:s}' konnte nicht gespeichert werden", - "unexpected_error": "Ein unerwarteter Fehler ist aufgetreten", + "unbackup_app": "'{app:s}' wird nicht gespeichert werden", + "unexpected_error": "Etwas Unerwartetes ist passiert: {error}", "unlimit": "Kein Kontingent", - "unrestore_app": "App '{app:s}' kann nicht Wiederhergestellt werden", + "unrestore_app": "{app:s} wird nicht wiederhergestellt werden", "updating_apt_cache": "Die Liste der verfügbaren Pakete wird aktualisiert…", "upgrade_complete": "Upgrade vollständig", "upgrading_packages": "Pakete werden aktualisiert…", @@ -322,7 +322,7 @@ "diagnosis_domain_expiration_success": "Deine Domänen sind registriert und werden in nächster Zeit nicht ablaufen.", "diagnosis_domain_not_found_details": "Die Domäne {domain} existiert nicht in der WHOIS-Datenbank oder sie ist abgelaufen!", "diagnosis_domain_expiration_not_found": "Das Ablaufdatum einiger Domains kann nicht überprüft werden", - "diagnosis_dns_try_dyndns_update_force": "Die DNS-Konfiguration dieser Domäne sollte automatisch von Yunohost verwaltet werden. Andernfalls können Sie mittels yunohost dyndns update --force ein Update erzwingen.", + "diagnosis_dns_try_dyndns_update_force": "Die DNS-Konfiguration dieser Domain sollte automatisch von YunoHost verwaltet werden. Andernfalls können Sie mittels yunohost dyndns update --force ein Update erzwingen.", "diagnosis_dns_point_to_doc": "Bitte schauen Sie in die Dokumentation unter https://yunohost.org/dns_config wenn Sie Hilfe bei der Konfiguration der DNS-Einträge brauchen.", "diagnosis_dns_discrepancy": "Der folgende DNS-Eintrag scheint nicht den empfohlenen Einstellungen zu entsprechen:
Typ: {type}
Name: {name}
Aktueller Wert: {current}
Erwarteter Wert: {value}", "diagnosis_dns_missing_record": "Gemäß der empfohlenen DNS-Konfiguration sollten Sie einen DNS-Eintrag mit den folgenden Informationen hinzufügen.
Typ: {type}
Name: {name}
Wert: {value}", @@ -623,5 +623,9 @@ "tools_upgrade_cant_hold_critical_packages": "Konnte für die kritischen Pakete das Flag 'hold' nicht setzen…", "tools_upgrade_cant_both": "Kann das Upgrade für das System und die Anwendungen nicht gleichzeitig durchführen", "tools_upgrade_at_least_one": "Bitte geben Sie '--apps' oder '--system' an", - "this_action_broke_dpkg": "Diese Aktion hat unkonfigurierte Pakete verursacht, welche durch dpkg/apt (die Paketverwaltungen dieses Systems) zurückgelassen wurden... Sie können versuchen dieses Problem zu lösen, indem Sie 'sudo apt install --fix-broken' und/oder 'sudo dpkg --configure -a' ausführen." -} \ No newline at end of file + "this_action_broke_dpkg": "Diese Aktion hat unkonfigurierte Pakete verursacht, welche durch dpkg/apt (die Paketverwaltungen dieses Systems) zurückgelassen wurden... Sie können versuchen dieses Problem zu lösen, indem Sie 'sudo apt install --fix-broken' und/oder 'sudo dpkg --configure -a' ausführen.", + "update_apt_cache_failed": "Kann den Cache von APT (Debians Paketmanager) nicht aktualisieren. Hier ist ein Auszug aus den sources.list-Zeilen, die helfen könnten, das Problem zu identifizieren:\n{sourceslist}", + "tools_upgrade_special_packages_completed": "YunoHost-Paketupdate beendet.\nDrücke [Enter], um zurück zur Kommandoziele zu kommen", + "tools_upgrade_special_packages_explanation": "Das Upgrade \"special\" wird im Hintergrund ausgeführt. Bitte starte keine anderen Aktionen auf deinem Server für die nächsten ~10 Minuten. Die Dauer ist abhängig von der Schnelligkeit deines Servers. Nach dem Upgrade musst du dich eventuell erneut in das Adminportal einloggen. Upgrade-Logs seid im Adminbereich unter Tools → Log verfügbar. Alternativ kannst du in der Befehlszeile eines Servers \"yunohost log list\" benutzen.", + "tools_upgrade_special_packages": "\"special\" (YunoHost-bezogene) Pakete werden jetzt aktualisiert…" +} From 29cce201535ac5a25a9178b7d698970907d8e240 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M?= Date: Wed, 16 Jun 2021 12:35:29 +0000 Subject: [PATCH 2584/3170] Translated using Weblate (Galician) Currently translated at 21.4% (136 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/gl/ --- locales/gl.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/locales/gl.json b/locales/gl.json index 511081353..05ddf476e 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -132,5 +132,7 @@ "backup_with_no_restore_script_for_app": "'{app:s}' non ten script de restablecemento, non poderás restablecer automáticamente a copia de apoio desta app.", "backup_with_no_backup_script_for_app": "A app '{app:s}' non ten script para a copia. Ignorada.", "backup_unable_to_organize_files": "Non se puido usar o método rápido para organizar ficheiros no arquivo", - "backup_system_part_failed": "Non se puido facer copia da parte do sistema '{part:s}'" -} \ No newline at end of file + "backup_system_part_failed": "Non se puido facer copia da parte do sistema '{part:s}'", + "certmanager_domain_http_not_working": "O dominio {domain:s} semella non ser accesible a través de HTTP. Comproba a categoría 'Web' no diagnóstico para máis info. (Se sabes o que estás a facer, utiliza '--no-checks' para obviar estas comprobacións.)", + "certmanager_domain_dns_ip_differs_from_public_ip": "Os rexistros DNS para o dominio '{domain:s}' son diferentes aos da IP deste servidor. Comproba a categoría 'Rexistros DNS' (básico) no diagnóstico para ter máis info. Se cambiaches recentemente o rexistro A, agarda a que se propague o cambio (están dispoñibles ferramentas en liña para comprobar estos cambios). (Se sabes o que estás a facer, utiliza '--no-checks' para obviar estas comprobacións.)" +} From 370554e861c806bd51f303e4d22fcb879bb10be8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M?= Date: Thu, 17 Jun 2021 07:02:24 +0000 Subject: [PATCH 2585/3170] Translated using Weblate (Galician) Currently translated at 22.5% (143 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/gl/ --- locales/gl.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/locales/gl.json b/locales/gl.json index 05ddf476e..e43e258f4 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -134,5 +134,12 @@ "backup_unable_to_organize_files": "Non se puido usar o método rápido para organizar ficheiros no arquivo", "backup_system_part_failed": "Non se puido facer copia da parte do sistema '{part:s}'", "certmanager_domain_http_not_working": "O dominio {domain:s} semella non ser accesible a través de HTTP. Comproba a categoría 'Web' no diagnóstico para máis info. (Se sabes o que estás a facer, utiliza '--no-checks' para obviar estas comprobacións.)", - "certmanager_domain_dns_ip_differs_from_public_ip": "Os rexistros DNS para o dominio '{domain:s}' son diferentes aos da IP deste servidor. Comproba a categoría 'Rexistros DNS' (básico) no diagnóstico para ter máis info. Se cambiaches recentemente o rexistro A, agarda a que se propague o cambio (están dispoñibles ferramentas en liña para comprobar estos cambios). (Se sabes o que estás a facer, utiliza '--no-checks' para obviar estas comprobacións.)" + "certmanager_domain_dns_ip_differs_from_public_ip": "Os rexistros DNS para o dominio '{domain:s}' son diferentes aos da IP deste servidor. Comproba a categoría 'Rexistros DNS' (básico) no diagnóstico para ter máis info. Se cambiaches recentemente o rexistro A, agarda a que se propague o cambio (están dispoñibles ferramentas en liña para comprobar estos cambios). (Se sabes o que estás a facer, utiliza '--no-checks' para obviar estas comprobacións.)", + "confirm_app_install_danger": "PERIGO! Esta app aínda é experimental (pode que nin funcione)! Probablemente NON deberías instalala a non ser que sepas o que estás a facer. NON TERÁS SOPORTE nin axuda se esta app estraga o teu sistema... Se queres asumir o risco, escribe '{answers:s}'", + "confirm_app_install_warning": "Aviso: Esta app podería funcionar, pero non está ben integrada en YunoHost. Algunhas funcións como a identificación centralizada e as copias de apoio poderían non estar dispoñibles. Desexas instalala igualmente? [{answers:s}] ", + "certmanager_unable_to_parse_self_CA_name": "Non se puido obter o nome da autoridade do auto-asinado (ficheiro: {file:s})", + "certmanager_self_ca_conf_file_not_found": "Non se atopa o ficheiro de configuración para a autoridade de auto-asinado (ficheiro: {file:s})", + "certmanager_no_cert_file": "Non se puido ler o ficheiro do certificado para o dominio {domain:s} (ficheiro: {file:s})", + "certmanager_hit_rate_limit": "Recentemente crearonse demasiados certificados para este mesmo grupo de dominios {domain:s}. Inténtao máis tarde. Podes ler https://letsencrypt.org/docs/rate-limits/ para máis info", + "certmanager_warning_subdomain_dns_record": "O subdominio '{subdomain:s}' non resolve a mesmo enderezo IP que '{domain:s}'. Algunhas funcións non estarán dispoñibles ata que arranxes isto e rexeneres o certificado." } From 208df9601f7fb51d4d13ae4fce24617f9f07fe56 Mon Sep 17 00:00:00 2001 From: MercierCorentin Date: Fri, 18 Jun 2021 13:39:22 +0200 Subject: [PATCH 2586/3170] Add domain registrar catalog cli command --- data/actionsmap/yunohost.yml | 1 + src/yunohost/domain.py | 12 ++++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 2dd33c488..7c0272398 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -615,6 +615,7 @@ domain: list: action_help: List registrars configured by DNS zone api: GET /domains/registrars + ### domain_registrar_catalog() catalog: action_help: List supported registrars API api: GET /domains/registrars/catalog diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index e800798a9..920df934f 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -29,8 +29,7 @@ import sys import yaml import functools -from lexicon.config import ConfigResolver -from lexicon.client import Client +from lexicon import * from moulinette import m18n, msettings, msignals from moulinette.core import MoulinetteError @@ -907,6 +906,15 @@ def domain_registrar_info(domain): for option_key, option_value in registrar_info['options'].items(): logger.info("Option " + option_key + ": " + option_value) +def domain_registrar_catalog(full): + registrars = yaml.load(open(REGISTRAR_LIST_PATH, "r+")) + for registrar in registrars: + logger.info("Registrar : " + registrar) + if full : + logger.info("Options : ") + for option in registrars[registrar]: + logger.info("\t- " + option) + def domain_registrar_set(domain, registrar, args): From 7f76f0a613a457e82592be26b0296f42616877fa Mon Sep 17 00:00:00 2001 From: Paco Date: Fri, 18 Jun 2021 14:46:46 +0200 Subject: [PATCH 2587/3170] fix new locales --- locales/en.json | 3 +++ src/yunohost/domain.py | 11 +++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/locales/en.json b/locales/en.json index 84a01dfaa..75912cab5 100644 --- a/locales/en.json +++ b/locales/en.json @@ -285,7 +285,9 @@ "domain_dyndns_already_subscribed": "You have already subscribed to a DynDNS domain", "domain_dyndns_root_unknown": "Unknown DynDNS root domain", "domain_exists": "The domain already exists", + "domain_property_unknown": "The property {property} dooesn't exist", "domain_hostname_failed": "Unable to set new hostname. This might cause an issue later (it might be fine).", + "domain_registrar_unknown": "This registrar is unknown. Look for yours with the command `yunohost domain catalog`", "domain_remove_confirm_apps_removal": "Removing this domain will remove those applications:\n{apps}\n\nAre you sure you want to do that? [{answers}]", "domain_uninstall_app_first": "Those applications are still installed on your domain:\n{apps}\n\nPlease uninstall them using 'yunohost app remove the_app_id' or move them to another domain using 'yunohost app change-url the_app_id' before proceeding to domain removal", "domain_name_unknown": "Domain '{domain}' unknown", @@ -528,6 +530,7 @@ "regenconf_need_to_explicitly_specify_ssh": "The ssh configuration has been manually modified, but you need to explicitly specify category 'ssh' with --force to actually apply the changes.", "regex_incompatible_with_tile": "/!\\ Packagers! Permission '{permission}' has show_tile set to 'true' and you therefore cannot define a regex URL as the main URL", "regex_with_only_domain": "You can't use a regex for domain, only for path", + "registrar_is_not_set": "The registrar for this domain has not been configured", "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_backup_too_old": "This backup archive can not be restored because it comes from a too-old YunoHost version.", diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 920df934f..6c90b533e 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -809,7 +809,7 @@ def domain_setting(domain, key, value=None, delete=False): # GET if value is None and not delete: if not key in domain_settings: - raise YunohostValidationError("This key doesn't exist!") + raise YunohostValidationError("domain_property_unknown", property=key) return domain_settings[key] @@ -827,11 +827,10 @@ def domain_setting(domain, key, value=None, delete=False): ttl = int(value) except ValueError: # TODO add locales - raise YunohostError("bad_value_type", value_type=type(ttl)) + raise YunohostError("invalid_number", value_type=type(ttl)) if ttl < 0: - # TODO add locales - raise YunohostError("must_be_positive", value_type=type(ttl)) + raise YunohostError("pattern_positive_number", value_type=type(ttl)) domain_settings[key] = value _set_domain_settings(domain, domain_settings) @@ -900,7 +899,7 @@ def domain_registrar_info(domain): registrar_info = _load_registrar_setting(dns_zone) if not registrar_info: # TODO add locales - raise YunohostError("no_registrar_set_for_this_dns_zone", dns_zone=dns_zone) + raise YunohostError("registrar_is_not_set", dns_zone=dns_zone) logger.info("Registrar name: " + registrar_info['name']) for option_key, option_value in registrar_info['options'].items(): @@ -924,7 +923,7 @@ def domain_registrar_set(domain, registrar, args): registrars = yaml.load(open(REGISTRAR_LIST_PATH, "r+")) if not registrar in registrars.keys(): # FIXME créer l'erreur - raise YunohostError("registrar_unknown") + raise YunohostError("domain_registrar_unknown") parameters = registrars[registrar] ask_args = [] From ab86d13b1ec1eb96f8b60332b65a439771c90b98 Mon Sep 17 00:00:00 2001 From: Paco Date: Fri, 18 Jun 2021 19:14:51 +0200 Subject: [PATCH 2588/3170] Update _load_domain_settings to load only requested domains --- locales/en.json | 2 +- src/yunohost/domain.py | 28 ++++++++++++++++++---------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/locales/en.json b/locales/en.json index 75912cab5..712ecb844 100644 --- a/locales/en.json +++ b/locales/en.json @@ -285,7 +285,7 @@ "domain_dyndns_already_subscribed": "You have already subscribed to a DynDNS domain", "domain_dyndns_root_unknown": "Unknown DynDNS root domain", "domain_exists": "The domain already exists", - "domain_property_unknown": "The property {property} dooesn't exist", + "domain_property_unknown": "The property {property} doesn't exist", "domain_hostname_failed": "Unable to set new hostname. This might cause an issue later (it might be fine).", "domain_registrar_unknown": "This registrar is unknown. Look for yours with the command `yunohost domain catalog`", "domain_remove_confirm_apps_removal": "Removing this domain will remove those applications:\n{apps}\n\nAre you sure you want to do that? [{answers}]", diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 6c90b533e..c13234d0b 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -729,14 +729,24 @@ def _get_DKIM(domain): ) -# FIXME split the loading of the domain settings → domain by domain (& file by file) -def _load_domain_settings(): +def _load_domain_settings(for_domains=[]): """ - Retrieve entries in domains/[domain].yml + Retrieve entries in /etc/yunohost/domains/[domain].yml And fill the holes if any """ # Retrieve actual domain list get_domain_list = domain_list() + domains = [] + + if for_domains: + # keep only the requested domains + domains = filter(lambda domain : domain in for_domains, get_domain_list["domains"]) + # check that all requested domains are there + unknown_domains = filter(lambda domain : not domain in domains, for_domains) + unknown_domain = next(unknown_domains, None) + if unknown_domain != None: + raise YunohostValidationError("domain_name_unknown", domain=unknown_domain) + # Create sanitized data new_domains = dict() @@ -744,7 +754,7 @@ def _load_domain_settings(): # Load main domain maindomain = get_domain_list["main"] - for domain in get_domain_list["domains"]: + for domain in domains: # Retrieve entries in the YAML filepath = f"{DOMAIN_SETTINGS_DIR}/{domain}.yml" on_disk_settings = {} @@ -799,7 +809,8 @@ def domain_setting(domain, key, value=None, delete=False): """ - domains = _load_domain_settings() + domains = _load_domain_settings([ domain ]) + if not domain in domains.keys(): # TODO add locales raise YunohostError("domain_name_unknown", domain=domain) @@ -871,8 +882,7 @@ def _set_domain_settings(domain, domain_settings): settings -- Dict with doamin settings """ - domains = _load_domain_settings() - if not domain in domains.keys(): + if domain not in domain_list()["domains"]: raise YunohostError("domain_name_unknown", domain=domain) # First create the DOMAIN_SETTINGS_DIR if it doesn't exist @@ -885,9 +895,7 @@ def _set_domain_settings(domain, domain_settings): def _load_zone_of_domain(domain): - domains = _load_domain_settings() - if not domain in domains.keys(): - # TODO add locales + if domain not in domain_list()["domains"]: raise YunohostError("domain_name_unknown", domain=domain) return domains[domain]["dns_zone"] From 90817acb84ff05c89ca6ca57a15a6fa09f5e3b6e Mon Sep 17 00:00:00 2001 From: Christian Wehrli Date: Fri, 18 Jun 2021 18:14:07 +0000 Subject: [PATCH 2589/3170] Translated using Weblate (German) Currently translated at 96.5% (611 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/de.json b/locales/de.json index a44be0ab1..e35301c9e 100644 --- a/locales/de.json +++ b/locales/de.json @@ -169,7 +169,7 @@ "certmanager_acme_not_configured_for_domain": "Die ACME Challenge kann im Moment nicht für {domain} ausgeführt werden, weil in ihrer nginx conf das entsprechende Code-Snippet fehlt... Bitte stellen Sie sicher, dass Ihre nginx-Konfiguration mit 'yunohost tools regen-conf nginx --dry-run --with-diff' auf dem neuesten Stand ist.", "certmanager_unable_to_parse_self_CA_name": "Der Name der Zertifizierungsstelle für selbstsignierte Zertifikate konnte nicht aufgelöst werden (Datei: {file:s})", "domain_hostname_failed": "Sie können keinen neuen Hostnamen verwenden. Das kann zukünftige Probleme verursachen (es kann auch sein, dass es funktioniert).", - "app_already_installed_cant_change_url": "Diese Application ist bereits installiert. Die URL kann durch diese Funktion nicht modifiziert werden. Überprüfe ob `app changeurl` verfügbar ist.", + "app_already_installed_cant_change_url": "Diese Applikation ist bereits installiert. Die URL kann durch diese Funktion nicht modifiziert werden. Überprüfe ob `app changeurl` verfügbar ist.", "app_change_url_failed_nginx_reload": "NGINX konnte nicht neu gestartet werden. Hier ist der Output von 'nginx -t':\n{nginx_errors:s}", "app_change_url_identical_domains": "Die alte und neue domain/url_path sind identisch: ('{domain:s} {path:s}'). Es gibt nichts zu tun.", "app_already_up_to_date": "{app:s} ist bereits aktuell", From 077fcfcaa064efb8008443703986193b5bcc9068 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Fri, 18 Jun 2021 09:41:05 +0000 Subject: [PATCH 2590/3170] Translated using Weblate (Esperanto) Currently translated at 81.3% (515 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/eo/ --- locales/eo.json | 52 +++++++++++++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/locales/eo.json b/locales/eo.json index 5c34ff831..d273593a9 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -1,5 +1,5 @@ { - "admin_password_change_failed": "Ne eblas ŝanĝi pasvorton", + "admin_password_change_failed": "Ne povis ŝanĝi pasvorton", "admin_password_changed": "La pasvorto de administrado estis ŝanĝita", "app_already_installed": "{app:s} estas jam instalita", "app_already_up_to_date": "{app:s} estas jam ĝisdata", @@ -38,20 +38,20 @@ "backup_abstract_method": "Ĉi tiu rezerva metodo ankoraŭ efektiviĝis", "apps_already_up_to_date": "Ĉiuj aplikoj estas jam ĝisdatigitaj", "app_location_unavailable": "Ĉi tiu URL aŭ ne haveblas, aŭ konfliktas kun la jam instalita (j) apliko (j):\n{apps:s}", - "backup_archive_app_not_found": "Ne povis trovi la programon '{app:s}' en la rezerva ar archiveivo", - "backup_actually_backuping": "Krei rezervan ar archiveivon el la kolektitaj dosieroj …", + "backup_archive_app_not_found": "Ne povis trovi {app:s} en la rezerva arkivo", + "backup_actually_backuping": "Krei rezervan arkivon de la kolektitaj dosieroj ...", "app_change_url_no_script": "La app '{app_name:s}' ankoraŭ ne subtenas URL-modifon. Eble vi devus altgradigi ĝin.", - "app_start_install": "Instali la programon '{app}' …", + "app_start_install": "Instali {app}...", "backup_created": "Sekurkopio kreita", - "app_make_default_location_already_used": "Ne povas igi la aplikon '{app}' defaŭlta sur la domajno, '{domain}' jam uziĝas de la alia app '{other_app}'", + "app_make_default_location_already_used": "Ne povis fari '{app}' la defaŭltan programon sur la domajno, '{domain}' estas jam uzata de '{other_app}'", "backup_method_copy_finished": "Rezerva kopio finis", "app_not_properly_removed": "{app:s} ne estis ĝuste forigita", "backup_archive_broken_link": "Ne povis aliri la rezervan ar archiveivon (rompita ligilo al {path:s})", - "app_requirements_checking": "Kontrolante postulatajn pakaĵojn por {app} …", - "app_not_installed": "Ne povis trovi la aplikon '{app:s}' en la listo de instalitaj programoj: {all_apps}", + "app_requirements_checking": "Kontrolante bezonatajn pakaĵojn por {app} ...", + "app_not_installed": "Ne povis trovi {app:s} en la listo de instalitaj programoj: {all_apps}", "ask_new_path": "Nova vojo", "backup_custom_mount_error": "Propra rezerva metodo ne povis preterpasi la paŝon 'monto'", - "app_upgrade_app_name": "Nun ĝisdatiganta {app} …", + "app_upgrade_app_name": "Nun ĝisdatigu {app}...", "app_manifest_invalid": "Io misas pri la aplika manifesto: {error}", "backup_cleaning_failed": "Ne povis purigi la provizoran rezervan dosierujon", "backup_creation_failed": "Ne povis krei la rezervan ar archiveivon", @@ -69,23 +69,23 @@ "app_upgrade_several_apps": "La sekvaj apliko estos altgradigitaj: {apps}", "backup_archive_open_failed": "Ne povis malfermi la rezervan ar archiveivon", "ask_lastname": "Familia nomo", - "app_start_backup": "Kolekti dosierojn por esti subtenata por la '{app}' …", + "app_start_backup": "Kolekti dosierojn por esti subtenata por {app}...", "backup_archive_name_exists": "Rezerva arkivo kun ĉi tiu nomo jam ekzistas.", - "backup_applying_method_tar": "Krei la rezervan TAR-ar archiveivon …", + "backup_applying_method_tar": "Krei la rezervon TAR Arkivo...", "backup_method_custom_finished": "Propra rezerva metodo '{method:s}' finiĝis", "app_already_installed_cant_change_url": "Ĉi tiu app estas jam instalita. La URL ne povas esti ŝanĝita nur per ĉi tiu funkcio. Kontrolu en `app changeurl` se ĝi haveblas.", "app_not_correctly_installed": "{app:s} ŝajnas esti malĝuste instalita", "app_removed": "{app:s} forigita", "backup_delete_error": "Ne povis forigi '{path:s}'", "backup_nothings_done": "Nenio por ŝpari", - "backup_applying_method_custom": "Nomante la kutiman rezervan metodon '{method:s}' …", - "backup_app_failed": "Ne eblis rezervi la programon '{app:s}'", + "backup_applying_method_custom": "Voki la laŭmendan rezervan metodon '{method:s}'...", + "backup_app_failed": "Ne povis subteni {app:s}", "app_upgrade_some_app_failed": "Iuj aplikoj ne povis esti altgradigitaj", - "app_start_remove": "Forigo de la apliko '{app}' …", + "app_start_remove": "Forigado {app}...", "backup_output_directory_not_empty": "Vi devas elekti malplenan eligitan dosierujon", "backup_archive_writing_error": "Ne povis aldoni la dosierojn '{source:s}' (nomitaj en la ar theivo '{dest:s}') por esti rezervitaj en la kunpremita arkivo '{archive:s}'", - "app_start_restore": "Restarigi la programon '{app}' …", - "backup_applying_method_copy": "Kopiante ĉiujn dosierojn al sekurkopio …", + "app_start_restore": "Restarigi {app}...", + "backup_applying_method_copy": "Kopii ĉiujn dosierojn por sekurigi...", "backup_couldnt_bind": "Ne povis ligi {src:s} al {dest:s}.", "ask_password": "Pasvorto", "app_requirements_unmeet": "Postuloj ne estas renkontitaj por {app}, la pakaĵo {pkgname} ({version}) devas esti {spec}", @@ -405,9 +405,9 @@ "log_permission_url": "Ĝisdatigu url-rilataj al permeso '{}'", "permission_already_up_to_date": "La permeso ne estis ĝisdatigita ĉar la petoj pri aldono/forigo jam kongruas kun la aktuala stato.", "permission_currently_allowed_for_all_users": "Ĉi tiu permeso estas nuntempe donita al ĉiuj uzantoj aldone al aliaj grupoj. Vi probable volas aŭ forigi la permeson \"all_users\" aŭ forigi la aliajn grupojn, kiujn ĝi nuntempe donas.", - "app_install_failed": "Ne povis instali {app} : {error}", + "app_install_failed": "Ne povis instali {app}: {error}", "app_install_script_failed": "Eraro okazis en la skripto de instalado de la app", - "app_remove_after_failed_install": "Forigado de la app post la instala fiasko …", + "app_remove_after_failed_install": "Forigado de la programo post la instalado-fiasko ...", "diagnosis_basesystem_host": "Servilo funkcias Debian {debian_version}", "apps_catalog_init_success": "Aplikoj katalogsistemo inicializita !", "apps_catalog_updating": "Ĝisdatigante katalogo de aplikoj …", @@ -537,5 +537,19 @@ "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" -} \ No newline at end of file + "global_settings_setting_smtp_allow_ipv6": "Permesu la uzon de IPv6 por ricevi kaj sendi poŝton", + "backup_archive_corrupted": "I aspektas kiel la rezerva arkivo '{archive}' estas koruptita: {error}", + "backup_archive_cant_retrieve_info_json": "Ne povis ŝarĝi infos por arkivo '{archive}' ... la info.json ne povas esti reprenita (aŭ ne estas valida JSON).", + "ask_user_domain": "Domajno uzi por la retpoŝta adreso de la uzanto kaj XMPP-konto", + "app_packaging_format_not_supported": "Ĉi tiu programo ne povas esti instalita ĉar ĝia pakita formato ne estas subtenata de via Yunohost-versio. Vi probable devas konsideri ĝisdatigi vian sistemon.", + "app_restore_script_failed": "Eraro okazis ene de la App Restarigu Skripton", + "app_manifest_install_ask_is_public": "Ĉu ĉi tiu programo devas esti eksponita al anonimaj vizitantoj?", + "app_manifest_install_ask_admin": "Elektu administran uzanton por ĉi tiu programo", + "app_manifest_install_ask_password": "Elektu administradan pasvorton por ĉi tiu programo", + "app_manifest_install_ask_path": "Elektu la vojon, kie ĉi tiu programo devas esti instalita", + "app_manifest_install_ask_domain": "Elektu la domajnon, kie ĉi tiu programo devas esti instalita", + "app_label_deprecated": "Ĉi tiu komando estas malrekomendita! Bonvolu uzi la novan komandon 'yunohost user permission update' por administri la app etikedo.", + "app_argument_password_no_default": "Eraro dum analiza pasvorta argumento '{name}': pasvorta argumento ne povas havi defaŭltan valoron por sekureca kialo", + "additional_urls_already_removed": "Plia URL '{url:s}' jam forigita en la aldona URL por permeso '{permission:s}'", + "additional_urls_already_added": "Plia URL '{url:s}' jam aldonita en la aldona URL por permeso '{permission:s}'" +} From a031c3a5beb62129010539a1b43d2198c084bb36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M?= Date: Fri, 18 Jun 2021 12:58:28 +0000 Subject: [PATCH 2591/3170] Translated using Weblate (Galician) Currently translated at 25.5% (162 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/gl/ --- locales/gl.json | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/locales/gl.json b/locales/gl.json index e43e258f4..1b0943aa7 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -141,5 +141,24 @@ "certmanager_self_ca_conf_file_not_found": "Non se atopa o ficheiro de configuración para a autoridade de auto-asinado (ficheiro: {file:s})", "certmanager_no_cert_file": "Non se puido ler o ficheiro do certificado para o dominio {domain:s} (ficheiro: {file:s})", "certmanager_hit_rate_limit": "Recentemente crearonse demasiados certificados para este mesmo grupo de dominios {domain:s}. Inténtao máis tarde. Podes ler https://letsencrypt.org/docs/rate-limits/ para máis info", - "certmanager_warning_subdomain_dns_record": "O subdominio '{subdomain:s}' non resolve a mesmo enderezo IP que '{domain:s}'. Algunhas funcións non estarán dispoñibles ata que arranxes isto e rexeneres o certificado." + "certmanager_warning_subdomain_dns_record": "O subdominio '{subdomain:s}' non resolve a mesmo enderezo IP que '{domain:s}'. Algunhas funcións non estarán dispoñibles ata que arranxes isto e rexeneres o certificado.", + "diagnosis_found_errors_and_warnings": "Atopado(s) {errors} problema(s) significativo(s) (e {warnings} avisos(s)) en relación a {category}!", + "diagnosis_found_errors": "Atopado(s) {errors} problema significativo(s) relacionado con {category}!", + "diagnosis_ignored_issues": "(+ {nb_ignored} problema ignorado(s))", + "diagnosis_cant_run_because_of_dep": "Non é posible facer o diganóstico para {category} cando aínda hai importantes problemas con {dep}.", + "diagnosis_cache_still_valid": "(A caché aínda é válida para o diagnóstico {category}. Non o repetiremos polo de agora!)", + "diagnosis_failed_for_category": "O diagnóstico fallou para a categoría '{category}': {error}", + "diagnosis_display_tip": "Para ver os problemas atopados, podes ir á sección de Diagnóstico na administración web, ou executa 'yunohost diagnosis show --issues --human-readable' desde a liña de comandos.", + "diagnosis_package_installed_from_sury_details": "Algúns paquetes foron instalados se darse conta desde un repositorio de terceiros chamado Sury. O equipo de YunoHost mellorou a estratexia para xestionar estos paquetes, pero é de agardar que algunhas instalacións que instalaron aplicacións PHP7.3 estando aínda en Stretch teñan inconsistencias co sistema. Para arranxar esta situación, deberías intentar executar o comando: {cmd_to_fix}", + "diagnosis_package_installed_from_sury": "Algúns paquetes do sistema deberían ser baixados de versión", + "diagnosis_backports_in_sources_list": "Semella que apt (o xestor de paquetes) está configurado para usar o repositorio backports. A non ser que saibas o que fas NON che recomendamos instalar paquetes desde backports, porque é probable que produzas inestabilidades e conflitos no teu sistema.", + "diagnosis_basesystem_ynh_inconsistent_versions": "Estás executando versións inconsistentes de paquetes YunoHost... probablemente debido a actualizacións parciais ou fallidas.", + "diagnosis_basesystem_ynh_main_version": "O servidor está a executar Yunohost {main_version} ({repo})", + "diagnosis_basesystem_ynh_single_version": "{package} versión: {version} ({repo})", + "diagnosis_basesystem_kernel": "O servidor está a executar o kernel Linux {kernel_version}", + "diagnosis_basesystem_host": "O servidor está a executar Debian {debian_version}", + "diagnosis_basesystem_hardware_model": "O modelo de servidor é {model}", + "diagnosis_basesystem_hardware": "A arquitectura do hardware do servidor é {virt} {arch}", + "custom_app_url_required": "Tes que proporcionar o URL para actualizar a app personalizada {app:s}", + "confirm_app_install_thirdparty": "PERIGO! Esta app non forma parte do catálogo de YunoHost. Ao instalar apps de terceiros poderías comprometer a integridade e seguridade do sistema. Probablemente NON deberías instalala a menos que saibas o que fas. NON SE PROPORCIONARÁ SOPORTE se esta app non funciona ou estraga o sistema... Se aínda así asumes o risco, escribe '{answers:s}'" } From 22c9edb7d724baf5ef8a39c6792cc72f403fcd56 Mon Sep 17 00:00:00 2001 From: Paco Date: Sat, 19 Jun 2021 03:01:28 +0200 Subject: [PATCH 2592/3170] fix new _load_domain_settings bug --- src/yunohost/domain.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index c13234d0b..930d4841c 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -729,20 +729,20 @@ def _get_DKIM(domain): ) -def _load_domain_settings(for_domains=[]): +def _load_domain_settings(domains=[]): """ Retrieve entries in /etc/yunohost/domains/[domain].yml And fill the holes if any """ # Retrieve actual domain list get_domain_list = domain_list() - domains = [] - if for_domains: - # keep only the requested domains - domains = filter(lambda domain : domain in for_domains, get_domain_list["domains"]) - # check that all requested domains are there - unknown_domains = filter(lambda domain : not domain in domains, for_domains) + if domains: + # check existence of requested domains + all_known_domains = get_domain_list["domains"] + # filter inexisting domains + unknown_domains = filter(lambda domain : not domain in all_known_domains, domains) + # get first unknown domain unknown_domain = next(unknown_domains, None) if unknown_domain != None: raise YunohostValidationError("domain_name_unknown", domain=unknown_domain) @@ -812,7 +812,6 @@ def domain_setting(domain, key, value=None, delete=False): domains = _load_domain_settings([ domain ]) if not domain in domains.keys(): - # TODO add locales raise YunohostError("domain_name_unknown", domain=domain) domain_settings = domains[domain] From c3e75877662422decedcafafc6acbdee5e473b95 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 19 Jun 2021 17:17:08 +0200 Subject: [PATCH 2593/3170] Propagate change from the moulinette: authenticate -> _authenticate_credentials --- src/yunohost/authenticators/ldap_admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/authenticators/ldap_admin.py b/src/yunohost/authenticators/ldap_admin.py index 808497b31..dd6eec03e 100644 --- a/src/yunohost/authenticators/ldap_admin.py +++ b/src/yunohost/authenticators/ldap_admin.py @@ -21,7 +21,7 @@ class Authenticator(BaseAuthenticator): self.basedn = "dc=yunohost,dc=org" self.admindn = "cn=admin,dc=yunohost,dc=org" - def authenticate(self, credentials=None): + def _authenticate_credentials(self, credentials=None): # TODO : change authentication format # to support another dn to support multi-admins From 9bec81398ab26cbe04516052308a8901ca697363 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 19 Jun 2021 17:19:31 +0200 Subject: [PATCH 2594/3170] Update changelog for 4.2.6.1 --- debian/changelog | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/debian/changelog b/debian/changelog index 5646caace..2f997dacd 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,13 @@ +yunohost (4.2.6.1) stable; urgency=low + + - [fix] Remove invaluement from free dnsbl list (71489307) + - [i18n] Remove stale strings (079f6762) + - [i18n] Translations updated for Esperanto, French, Galician, German, Greek + + Thanks to all contributors <3 ! (amirale qt, Christian Wehrli, Éric Gaspar, José M, ljf, ppr, qwerty287) + + -- Alexandre Aubin Sat, 19 Jun 2021 17:18:13 +0200 + yunohost (4.2.6) stable; urgency=low - [fix] metronome/xmpp: deactivate stanza mention optimization / have quick notification in chat group ([#1164](https://github.com/YunoHost/yunohost/pull/1164)) From 50129f3a29854f28c59f842184f9a23c6826b3c7 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 21 Jun 2021 19:00:26 +0200 Subject: [PATCH 2595/3170] Sometimes metadata ends up being empty for some reason and ends up being loaded as None, making the "in" operator crash :| --- src/yunohost/log.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/log.py b/src/yunohost/log.py index d36671ce2..f10ade23d 100644 --- a/src/yunohost/log.py +++ b/src/yunohost/log.py @@ -222,7 +222,7 @@ def log_show( # Display metadata if exist if os.path.exists(md_path): try: - metadata = read_yaml(md_path) + metadata = read_yaml(md_path) or {} except MoulinetteError as e: error = m18n.n("log_corrupted_md_file", md_file=md_path, error=e) if os.path.exists(log_path): From 780c3cb88df6651f52eb7c23d82626e9b6f6f87b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 21 Jun 2021 19:48:24 +0200 Subject: [PATCH 2596/3170] Fix translation format --- locales/de.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/de.json b/locales/de.json index e35301c9e..f723be1de 100644 --- a/locales/de.json +++ b/locales/de.json @@ -107,7 +107,7 @@ "service_removed": "Der Dienst '{service:s}' wurde erfolgreich entfernt", "service_start_failed": "Der Dienst '{service:s}' konnte nicht gestartet werden\n\nKürzlich erstellte Logs des Dienstes: {logs:s}", "service_started": "Der Dienst '{service:s}' wurde erfolgreich gestartet", - "service_stop_failed": "Der Dienst '{service:s}' kann nicht gestoppt werden\n\nAktuelle Service-Logs: {log:s}", + "service_stop_failed": "Der Dienst '{service:s}' kann nicht gestoppt werden\n\nAktuelle Service-Logs: {logs:s}", "service_stopped": "Der Dienst '{service:s}' wurde erfolgreich beendet", "service_unknown": "Unbekannter Dienst '{service:s}'", "ssowat_conf_generated": "Konfiguration von SSOwat neu erstellt", From b61082b1c4063e218e6248382c6754fb4d6677cb Mon Sep 17 00:00:00 2001 From: Kay0u Date: Tue, 22 Jun 2021 10:51:22 +0200 Subject: [PATCH 2597/3170] fix remove-stale-translated-strings job --- .gitlab/ci/translation.gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab/ci/translation.gitlab-ci.yml b/.gitlab/ci/translation.gitlab-ci.yml index f6bd3f83f..edab611df 100644 --- a/.gitlab/ci/translation.gitlab-ci.yml +++ b/.gitlab/ci/translation.gitlab-ci.yml @@ -10,6 +10,7 @@ remove-stale-translated-strings: - apt-get update -y && apt-get install git hub -y - git config --global user.email "yunohost@yunohost.org" - git config --global user.name "$GITHUB_USER" + - git remote set-url origin https://$GITHUB_TOKEN:x-oauth-basic@github.com/YunoHost/yunohost.git script: - cd tests # Maybe move this script location to another folder? # create a local branch that will overwrite distant one From f77651c34abfb858347cb02aff58b8fbc8a28113 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M?= Date: Sun, 20 Jun 2021 15:24:32 +0000 Subject: [PATCH 2598/3170] Translated using Weblate (Galician) Currently translated at 29.0% (184 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/gl/ --- locales/gl.json | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/locales/gl.json b/locales/gl.json index 1b0943aa7..279957264 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -160,5 +160,27 @@ "diagnosis_basesystem_hardware_model": "O modelo de servidor é {model}", "diagnosis_basesystem_hardware": "A arquitectura do hardware do servidor é {virt} {arch}", "custom_app_url_required": "Tes que proporcionar o URL para actualizar a app personalizada {app:s}", - "confirm_app_install_thirdparty": "PERIGO! Esta app non forma parte do catálogo de YunoHost. Ao instalar apps de terceiros poderías comprometer a integridade e seguridade do sistema. Probablemente NON deberías instalala a menos que saibas o que fas. NON SE PROPORCIONARÁ SOPORTE se esta app non funciona ou estraga o sistema... Se aínda así asumes o risco, escribe '{answers:s}'" + "confirm_app_install_thirdparty": "PERIGO! Esta app non forma parte do catálogo de YunoHost. Ao instalar apps de terceiros poderías comprometer a integridade e seguridade do sistema. Probablemente NON deberías instalala a menos que saibas o que fas. NON SE PROPORCIONARÁ SOPORTE se esta app non funciona ou estraga o sistema... Se aínda así asumes o risco, escribe '{answers:s}'", + "diagnosis_dns_point_to_doc": "Revisa a documentación en https://yunohost.org/dns_config se precisas axuda para configurar os rexistros DNS.", + "diagnosis_dns_discrepancy": "O seguinte rexistro DNS non segue a configuración recomendada:
Tipo: {type}
Nome: {name}
Valor actual: {current}
Valor agardado: {value}", + "diagnosis_dns_missing_record": "Facendo caso á configuración DNS recomendada, deberías engadir un rexistro DNS coa seguinte info.
Tipo: {type}
Nome: {name}
Valor: {value}", + "diagnosis_dns_bad_conf": "Faltan algúns rexistros DNS ou están mal configurados para o dominio {domain} (categoría {category})", + "diagnosis_dns_good_conf": "Os rexistros DNS están correctamente configurados para o dominio {domain} (categoría {category})", + "diagnosis_ip_weird_resolvconf_details": "O ficheiro /etc/resolv.conf debería ser unha ligazón simbólica a /etc/resolvconf/run/resolv.conf apuntando el mesmo a 127.0.0.1 (dnsmasq). Se queres configurar manualmente a resolución DNS, por favor edita /etc/resolv.dnsmasq.conf.", + "diagnosis_ip_weird_resolvconf": "A resolución DNS semella funcionar, mais parecese que estás a utilizar un /etc/resolv.conf personalizado.", + "diagnosis_ip_broken_resolvconf": "A resolución de nomes de dominio semella non funcionar no teu servidor, que parece ter relación con que /etc/resolv.conf non sinala a 127.0.0.1.", + "diagnosis_ip_broken_dnsresolution": "A resolución de nomes de dominio semella que por algunha razón non funciona... Pode estar o cortalumes bloqueando as peticións DNS?", + "diagnosis_ip_dnsresolution_working": "A resolución de nomes de dominio está a funcionar!", + "diagnosis_ip_not_connected_at_all": "O servidor semella non ter ningún tipo de conexión a internet!?", + "diagnosis_ip_local": "IP local: {local}", + "diagnosis_ip_global": "IP global: {global}", + "diagnosis_ip_no_ipv6_tip": "Que o servidor teña conexión IPv6 non é obrigatorio para que funcione, pero é mellor para o funcionamento de Internet en conxunto. IPv6 debería estar configurado automáticamente no teu sistema ou provedor se está dispoñible. Doutro xeito, poderías ter que configurar manualmente algúns parámetros tal como se explica na documentación: https://yunohost.org/#/ipv6. Se non podes activar IPv6 ou é moi complicado para ti, podes ignorar tranquilamente esta mensaxe.", + "diagnosis_ip_no_ipv6": "O servidor non ten conexión IPv6.", + "diagnosis_ip_connected_ipv6": "O servidor está conectado a internet a través de IPv6!", + "diagnosis_ip_no_ipv4": "O servidor non ten conexión IPv4.", + "diagnosis_ip_connected_ipv4": "O servidor está conectado a internet a través de IPv4!", + "diagnosis_no_cache": "Aínda non hai datos na caché para '{category}'", + "diagnosis_failed": "Non se puido obter o resultado do diagnóstico para '{category}': {error}", + "diagnosis_everything_ok": "Semella todo correcto en {category}!", + "diagnosis_found_warnings": "Atoparonse {warnings} elemento(s) que poderían optimizarse en {category}." } From ba5ccacf8a59d3852eefd5aa5000f108913066c4 Mon Sep 17 00:00:00 2001 From: Meta Meta Date: Mon, 21 Jun 2021 05:52:56 +0000 Subject: [PATCH 2599/3170] Translated using Weblate (German) Currently translated at 96.6% (612 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/de.json b/locales/de.json index f723be1de..fad03058d 100644 --- a/locales/de.json +++ b/locales/de.json @@ -627,5 +627,6 @@ "update_apt_cache_failed": "Kann den Cache von APT (Debians Paketmanager) nicht aktualisieren. Hier ist ein Auszug aus den sources.list-Zeilen, die helfen könnten, das Problem zu identifizieren:\n{sourceslist}", "tools_upgrade_special_packages_completed": "YunoHost-Paketupdate beendet.\nDrücke [Enter], um zurück zur Kommandoziele zu kommen", "tools_upgrade_special_packages_explanation": "Das Upgrade \"special\" wird im Hintergrund ausgeführt. Bitte starte keine anderen Aktionen auf deinem Server für die nächsten ~10 Minuten. Die Dauer ist abhängig von der Schnelligkeit deines Servers. Nach dem Upgrade musst du dich eventuell erneut in das Adminportal einloggen. Upgrade-Logs seid im Adminbereich unter Tools → Log verfügbar. Alternativ kannst du in der Befehlszeile eines Servers \"yunohost log list\" benutzen.", - "tools_upgrade_special_packages": "\"special\" (YunoHost-bezogene) Pakete werden jetzt aktualisiert…" + "tools_upgrade_special_packages": "\"special\" (YunoHost-bezogene) Pakete werden jetzt aktualisiert…", + "unknown_main_domain_path": "Unbekannte:r Domain oder Pfad für '{app}'. Du musst eine Domain und einen Pfad setzen, um die URL für Berechtigungen zu setzen." } From c21c6ba1dc46631293af778292c7f45d8a830dbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M?= Date: Mon, 21 Jun 2021 13:00:08 +0000 Subject: [PATCH 2600/3170] Translated using Weblate (Galician) Currently translated at 30.8% (195 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/gl/ --- locales/gl.json | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/locales/gl.json b/locales/gl.json index 279957264..a5fc52040 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -182,5 +182,16 @@ "diagnosis_no_cache": "Aínda non hai datos na caché para '{category}'", "diagnosis_failed": "Non se puido obter o resultado do diagnóstico para '{category}': {error}", "diagnosis_everything_ok": "Semella todo correcto en {category}!", - "diagnosis_found_warnings": "Atoparonse {warnings} elemento(s) que poderían optimizarse en {category}." + "diagnosis_found_warnings": "Atoparonse {warnings} elemento(s) que poderían optimizarse en {category}.", + "diagnosis_services_bad_status": "O servizo {service} está {status} :(", + "diagnosis_services_conf_broken": "A configuración do {servizo} está estragada!", + "diagnosis_services_running": "O servizo {service} está en execución!", + "diagnosis_domain_expires_in": "{domain} caduca en {days} días.", + "diagnosis_domain_expiration_error": "Algúns dominios van caducan MOI PRONTO!", + "diagnosis_domain_expiration_warning": "Algúns dominios van caducar pronto!", + "diagnosis_domain_expiration_success": "Os teus dominios están rexistrados e non van caducar pronto.", + "diagnosis_domain_expiration_not_found_details": "A información WHOIS para o dominio {domain} non semella conter información acerca da data de caducidade?", + "diagnosis_domain_not_found_details": "O dominio {domain} non existe na base de datos de WHOIS ou está caducado!", + "diagnosis_domain_expiration_not_found": "Non se puido comprobar a data de caducidade para algúns dominios", + "diagnosis_dns_try_dyndns_update_force": "A xestión DNS deste dominio debería estar xestionada directamente por YunoHost. Se non fose o caso, podes intentar forzar unha actualización executando yunohost dyndns update --force." } From 7792608687ba369f5fb82188b7b7817c803cd8f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M?= Date: Tue, 22 Jun 2021 05:53:42 +0000 Subject: [PATCH 2601/3170] Translated using Weblate (Galician) Currently translated at 32.3% (205 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/gl/ --- locales/gl.json | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/locales/gl.json b/locales/gl.json index a5fc52040..110254849 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -193,5 +193,15 @@ "diagnosis_domain_expiration_not_found_details": "A información WHOIS para o dominio {domain} non semella conter información acerca da data de caducidade?", "diagnosis_domain_not_found_details": "O dominio {domain} non existe na base de datos de WHOIS ou está caducado!", "diagnosis_domain_expiration_not_found": "Non se puido comprobar a data de caducidade para algúns dominios", - "diagnosis_dns_try_dyndns_update_force": "A xestión DNS deste dominio debería estar xestionada directamente por YunoHost. Se non fose o caso, podes intentar forzar unha actualización executando yunohost dyndns update --force." + "diagnosis_dns_try_dyndns_update_force": "A xestión DNS deste dominio debería estar xestionada directamente por YunoHost. Se non fose o caso, podes intentar forzar unha actualización executando yunohost dyndns update --force.", + "diagnosis_swap_ok": "O sistema ten {total} de swap!", + "diagnosis_swap_notsomuch": "O sistema só ten {total} de swap. Deberías considerar ter polo menos {recommended} para evitar situacións onde o sistema esgote a memoria.", + "diagnosis_swap_none": "O sistema non ten partición swap. Deberías considerar engadir polo menos {recommended} de swap para evitar situación onde o sistema esgote a memoria.", + "diagnosis_ram_ok": "Ao sistema aínda lle queda {available} ({available_percent}%) de RAM dispoñible dun total de {total}.", + "diagnosis_ram_low": "O sistema ten {available} ({available_percent}%) da RAM dispoñible (total {total}). Ten coidado.", + "diagnosis_ram_verylow": "Ao sistema só lle queda {available} ({available_percent}%) de RAM dispoñible! (total {total})", + "diagnosis_diskusage_ok": "A almacenaxe {mountpoint} (no dispositivo {device}) aínda ten {free} ({free_percent}%) de espazo restante (de {total})!", + "diagnosis_diskusage_low": "A almacenaxe {mountpoint} (no dispositivo {device}) só lle queda {free} ({free_percent}%) de espazo libre (de {total}). Ten coidado.", + "diagnosis_diskusage_verylow": "A almacenaxe {mountpoint} (no dispositivo {device}) só lle queda {free} ({free_percent}%) de espazo libre (de {total}). Deberías considerar liberar algún espazo!", + "diagnosis_services_bad_status_tip": "Podes intentar reiniciar o servizo, e se isto non funciona, mira os rexistros do servizo na webadmin (desde a liña de comandos con yunohost service restart {service} e yunohost service log {service})." } From 271e3a26f9b7d3079daf22f5b4140b07ff8c6776 Mon Sep 17 00:00:00 2001 From: Kayou Date: Tue, 22 Jun 2021 11:22:52 +0200 Subject: [PATCH 2602/3170] fix gl locales --- locales/gl.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/gl.json b/locales/gl.json index 110254849..b84b211e6 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -184,7 +184,7 @@ "diagnosis_everything_ok": "Semella todo correcto en {category}!", "diagnosis_found_warnings": "Atoparonse {warnings} elemento(s) que poderían optimizarse en {category}.", "diagnosis_services_bad_status": "O servizo {service} está {status} :(", - "diagnosis_services_conf_broken": "A configuración do {servizo} está estragada!", + "diagnosis_services_conf_broken": "A configuración do {service} está estragada!", "diagnosis_services_running": "O servizo {service} está en execución!", "diagnosis_domain_expires_in": "{domain} caduca en {days} días.", "diagnosis_domain_expiration_error": "Algúns dominios van caducan MOI PRONTO!", From 31c31b1298cbedc7fe0d7df91211cb1f8a6c9397 Mon Sep 17 00:00:00 2001 From: Paco Date: Fri, 25 Jun 2021 10:48:33 +0200 Subject: [PATCH 2603/3170] Lexicon: ~ 4 bugfixes --- ...ne_hosters_list.yml => registrar_list.yml} | 0 debian/install | 2 +- src/yunohost/domain.py | 57 ++++++++++--------- 3 files changed, 32 insertions(+), 27 deletions(-) rename data/other/{dns_zone_hosters_list.yml => registrar_list.yml} (100%) diff --git a/data/other/dns_zone_hosters_list.yml b/data/other/registrar_list.yml similarity index 100% rename from data/other/dns_zone_hosters_list.yml rename to data/other/registrar_list.yml diff --git a/debian/install b/debian/install index 4ff0ed52d..55ddb34c6 100644 --- a/debian/install +++ b/debian/install @@ -8,7 +8,7 @@ data/other/yunoprompt.service /etc/systemd/system/ data/other/password/* /usr/share/yunohost/other/password/ data/other/dpkg-origins/yunohost /etc/dpkg/origins data/other/dnsbl_list.yml /usr/share/yunohost/other/ -data/other/dns_zone_hosters_list.yml /usr/share/yunohost/other/ +data/other/registrar_list.yml /usr/share/yunohost/other/ data/other/ffdhe2048.pem /usr/share/yunohost/other/ data/other/* /usr/share/yunohost/yunohost-config/moulinette/ data/templates/* /usr/share/yunohost/templates/ diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 930d4841c..9e79a13ae 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -29,7 +29,8 @@ import sys import yaml import functools -from lexicon import * +from lexicon.client import Client +from lexicon.config import ConfigResolver from moulinette import m18n, msettings, msignals from moulinette.core import MoulinetteError @@ -54,7 +55,7 @@ logger = getActionLogger("yunohost.domain") DOMAIN_SETTINGS_DIR = "/etc/yunohost/domains" REGISTRAR_SETTINGS_DIR = "/etc/yunohost/registrars" -REGISTRAR_LIST_PATH = "/usr/share/yunohost/other/providers_list.yml" +REGISTRAR_LIST_PATH = "/usr/share/yunohost/other/registrar_list.yml" def domain_list(exclude_subdomains=False): @@ -321,9 +322,7 @@ def domain_dns_conf(domain): if domain not in domain_list()["domains"]: raise YunohostValidationError("domain_name_unknown", domain=domain) - domains_settings = _get_domain_settings(domain, True) - - dns_conf = _build_dns_conf(domains_settings) + dns_conf = _build_dns_conf(domain) result = "" @@ -445,11 +444,14 @@ def _get_maindomain(): return maindomain -def _build_dns_conf(domains): +def _build_dns_conf(domain): """ Internal function that will returns a data structure containing the needed information to generate/adapt the dns configuration + Arguments: + domains -- List of a domain and its subdomains + The returned datastructure will have the following form: { "basic": [ @@ -485,7 +487,7 @@ def _build_dns_conf(domains): } """ - root = min(domains.keys(), key=(lambda k: len(k))) + domains = _get_domain_settings(domain, True) basic = [] mail = [] @@ -495,19 +497,19 @@ def _build_dns_conf(domains): ipv6 = get_public_ip(6) owned_dns_zone = ( # TODO test this - "dns_zone" in domains[root] and domains[root]["dns_zone"] == root + "dns_zone" in domains[domain] and domains[domain]["dns_zone"] == domain ) - root_prefix = root.partition(".")[0] + root_prefix = domain.partition(".")[0] child_domain_suffix = "" for domain_name, domain in domains.items(): ttl = domain["ttl"] - if domain_name == root: - name = root_prefix if not owned_dns_zone else "@" + if domain_name == domain: + name = "@" if owned_dns_zone else root_prefix else: - name = domain_name[0 : -(1 + len(root))] + name = domain_name[0 : -(1 + len(domain))] if not owned_dns_zone: name += "." + root_prefix @@ -746,6 +748,8 @@ def _load_domain_settings(domains=[]): unknown_domain = next(unknown_domains, None) if unknown_domain != None: raise YunohostValidationError("domain_name_unknown", domain=unknown_domain) + else: + domains = domain_list()["domains"] # Create sanitized data @@ -771,7 +775,6 @@ def _load_domain_settings(domains=[]): "mail": True, "dns_zone": dns_zone, "ttl": 3600, - "provider": {}, } # Update each setting if not present default_settings.update(on_disk_settings) @@ -845,6 +848,10 @@ def domain_setting(domain, key, value=None, delete=False): _set_domain_settings(domain, domain_settings) +def _is_subdomain_of(subdomain, domain): + return True if re.search("(^|\\.)" + domain + "$", subdomain) else False + + def _get_domain_settings(domain, subdomains): """ Get settings of a domain @@ -861,9 +868,7 @@ def _get_domain_settings(domain, subdomains): only_wanted_domains = dict() for entry in domains.keys(): if subdomains: - # FIXME does example.co is seen as a subdomain of example.com? - # TODO _is_subdomain_of_domain - if domain in entry: + if _is_subdomain_of(entry, domain): only_wanted_domains[entry] = domains[entry] else: if domain == entry: @@ -948,9 +953,9 @@ def domain_registrar_set(domain, registrar, args): ) parsed_answer_dict = _parse_args_in_yunohost_format(args_dict, ask_args) - domain_provider = {"name": registrar, "options": {}} + domain_registrar = {"name": registrar, "options": {}} for arg_name, arg_value_and_type in parsed_answer_dict.items(): - domain_provider["options"][arg_name] = arg_value_and_type[0] + domain_registrar["options"][arg_name] = arg_value_and_type[0] # First create the REGISTRAR_SETTINGS_DIR if it doesn't exist if not os.path.exists(REGISTRAR_SETTINGS_DIR): @@ -958,7 +963,7 @@ def domain_registrar_set(domain, registrar, args): # Save the settings to the .yaml file filepath = f"{REGISTRAR_SETTINGS_DIR}/{dns_zone}.yml" with open(filepath, "w") as file: - yaml.dump(domain_provider, file, default_flow_style=False) + yaml.dump(domain_registrar, file, default_flow_style=False) def domain_push_config(domain): @@ -969,13 +974,13 @@ def domain_push_config(domain): if domain not in domain_list()["domains"]: raise YunohostValidationError("domain_name_unknown", domain=domain) - domains_settings = _get_domain_settings(domain, True) + dns_conf = _build_dns_conf(domain) - dns_conf = _build_dns_conf(domains_settings) + domain_settings = _load_domain_settings([ domain ]) + dns_zone = domain_settings[domain]["dns_zone"] + registrar_setting = _load_registrar_setting(dns_zone) - provider = domains_settings[domain]["provider"] - - if provider == False: + if not registrar_setting: # FIXME add locales raise YunohostValidationError("registrar_is_not_set", domain=domain) @@ -995,10 +1000,10 @@ def domain_push_config(domain): # Construct the base data structure to use lexicon's API. base_config = { - "provider_name": provider["name"], + "provider_name": registrar_setting["name"], "domain": domain, # domain name } - base_config[provider["name"]] = provider["options"] + base_config[registrar_setting["name"]] = registrar_setting["options"] # Get types present in the generated records types = set() From 7349b229c5bdedeb110303f58bad2e61eec9e7ad Mon Sep 17 00:00:00 2001 From: Kayou Date: Wed, 30 Jun 2021 17:16:26 +0200 Subject: [PATCH 2604/3170] fix conf path for dedicated php server --- data/helpers.d/php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/php b/data/helpers.d/php index 8548df6ab..92c4d6c18 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -225,7 +225,7 @@ syslog.ident = php-fpm-__APP__ include = __FINALPHPCONF__ " > $YNH_APP_BASEDIR/conf/php-fpm-$app.conf - ynh_add_config --template="../config/php-fpm-$app.conf" --destination="$globalphpconf" + ynh_add_config --template="$YNH_APP_BASEDIR/conf/php-fpm-$app.conf" --destination="$globalphpconf" # Create a config for a dedicated PHP-FPM service for the app echo "[Unit] From 1c9f6ea72e28c9d0f7d6f3d1a6c69e6b6d38278c Mon Sep 17 00:00:00 2001 From: Kayou Date: Wed, 30 Jun 2021 17:54:59 +0200 Subject: [PATCH 2605/3170] remove --log_type because it's deprecated --- data/helpers.d/php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/php b/data/helpers.d/php index 92c4d6c18..917399275 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -245,7 +245,7 @@ WantedBy=multi-user.target # Create this dedicated PHP-FPM service ynh_add_systemd_config --service=$fpm_service --template=$fpm_service # Integrate the service in YunoHost admin panel - yunohost service add $fpm_service --log /var/log/php/fpm-php.$app.log --log_type file --description "Php-fpm dedicated to $app" + yunohost service add $fpm_service --log /var/log/php/fpm-php.$app.log --description "Php-fpm dedicated to $app" # Configure log rotate ynh_use_logrotate --logfile=/var/log/php # Restart the service, as this service is either stopped or only for this app From 38001a5f20636a4b0cbb4302ccad87b5defa89d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M?= Date: Sun, 27 Jun 2021 07:06:26 +0000 Subject: [PATCH 2606/3170] Translated using Weblate (Galician) Currently translated at 32.7% (207 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/gl/ --- locales/gl.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/locales/gl.json b/locales/gl.json index b84b211e6..5aea67be4 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -203,5 +203,7 @@ "diagnosis_diskusage_ok": "A almacenaxe {mountpoint} (no dispositivo {device}) aínda ten {free} ({free_percent}%) de espazo restante (de {total})!", "diagnosis_diskusage_low": "A almacenaxe {mountpoint} (no dispositivo {device}) só lle queda {free} ({free_percent}%) de espazo libre (de {total}). Ten coidado.", "diagnosis_diskusage_verylow": "A almacenaxe {mountpoint} (no dispositivo {device}) só lle queda {free} ({free_percent}%) de espazo libre (de {total}). Deberías considerar liberar algún espazo!", - "diagnosis_services_bad_status_tip": "Podes intentar reiniciar o servizo, e se isto non funciona, mira os rexistros do servizo na webadmin (desde a liña de comandos con yunohost service restart {service} e yunohost service log {service})." + "diagnosis_services_bad_status_tip": "Podes intentar reiniciar o servizo, e se isto non funciona, mira os rexistros do servizo na webadmin (desde a liña de comandos con yunohost service restart {service} e yunohost service log {service}).", + "diagnosis_mail_outgoing_port_25_ok": "O servidor de email SMTP pode enviar emails (porto 25 de saída non está bloqueado).", + "diagnosis_swap_tip": "Por favor ten en conta que se o servidor ten a swap instalada nunha tarxeta SD ou almacenaxe SSD podería reducir drásticamente a expectativa de vida do dispositivo." } From 68c2ed9ef69e29b3d46e2d5bc7afc674b113305a Mon Sep 17 00:00:00 2001 From: ppr Date: Tue, 29 Jun 2021 20:21:05 +0000 Subject: [PATCH 2607/3170] Translated using Weblate (French) Currently translated at 99.0% (627 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 79ae8e6e7..b48c849d6 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -96,7 +96,7 @@ "port_already_closed": "Le port {port:d} est déjà fermé pour les connexions {ip_version:s}", "port_already_opened": "Le port {port:d} est déjà ouvert pour les connexions {ip_version:s}", "restore_already_installed_app": "Une application est déjà installée avec l’identifiant '{app:s}'", - "app_restore_failed": "Impossible de restaurer {app:s} : {error:s}", + "app_restore_failed": "Impossible de restaurer {app:s}: {error:s}", "restore_cleaning_failed": "Impossible de nettoyer le dossier temporaire de restauration", "restore_complete": "Restauration terminée", "restore_confirm_yunohost_installed": "Voulez-vous vraiment restaurer un système déjà installé ? [{answers:s}]", @@ -133,7 +133,7 @@ "upgrade_complete": "Mise à jour terminée", "upgrading_packages": "Mise à jour des paquets en cours...", "upnp_dev_not_found": "Aucun périphérique compatible UPnP n’a été trouvé", - "upnp_disabled": "UPnP désactivé", + "upnp_disabled": "L'UPnP est désactivé", "upnp_enabled": "L'UPnP est activé", "upnp_port_open_failed": "Impossible d’ouvrir les ports UPnP", "user_created": "L’utilisateur a été créé", @@ -154,7 +154,7 @@ "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, utilisez '--no-checks' pour désactiver la vérification.)", - "certmanager_domain_dns_ip_differs_from_public_ip": "L'enregistrement DNS du domaine {domain:s} est différent de l’adresse IP de ce serveur. Pour plus d'informations, veuillez consulter la catégorie \"Enregistrements DNS\" dans la section diagnostic. 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_domain_dns_ip_differs_from_public_ip": "L'enregistrement DNS du domaine '{domain:s}' est différent de l’adresse IP de ce serveur. Pour plus d'informations, veuillez consulter la catégorie \"Enregistrements DNS\" dans la section diagnostic. 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}", "certmanager_cert_install_success_selfsigned": "Le certificat auto-signé est maintenant installé pour le domaine « {domain:s} »", "certmanager_cert_install_success": "Le certificat Let’s Encrypt est maintenant installé pour le domaine « {domain:s} »", @@ -213,12 +213,12 @@ "restore_not_enough_disk_space": "Espace disponible insuffisant (L’espace libre est de {free_space:d} octets. Le besoin d’espace nécessaire est de {needed_space:d} octets. En appliquant une marge de sécurité, la quantité d’espace nécessaire est de {margin:d} octets)", "restore_system_part_failed": "Impossible de restaurer la partie '{part:s}' du système", "backup_couldnt_bind": "Impossible de lier {src:s} avec {dest:s}.", - "domain_dns_conf_is_just_a_recommendation": "Cette commande vous montre la configuration *recommandée*. Elle n'établit pas réellement la configuration DNS pour vous. Il est de votre ressort de configurer votre zone DNS chez votre registrar/fournisseur conformément à cette recommandation.", + "domain_dns_conf_is_just_a_recommendation": "Cette commande vous montre la configuration *recommandée*. Elle ne configure pas le DNS pour vous. Il est de votre ressort de configurer votre zone DNS chez votre registrar/fournisseur conformément à cette recommandation.", "migrations_cant_reach_migration_file": "Impossible d’accéder aux fichiers de migration via le chemin '%s'", - "migrations_loading_migration": "Chargement de la migration {id}...", + "migrations_loading_migration": "Chargement de la migration {id} ...", "migrations_migration_has_failed": "La migration {id} a échoué avec l’exception {exception} : annulation", "migrations_no_migrations_to_run": "Aucune migration à lancer", - "migrations_skip_migration": "Ignorer et passer la migration {id}...", + "migrations_skip_migration": "Ignorer et passer la migration {id} ...", "server_shutdown": "Le serveur va s’éteindre", "server_shutdown_confirm": "Le serveur va être éteint immédiatement, le voulez-vous vraiment ? [{answers:s}]", "server_reboot": "Le serveur va redémarrer", @@ -331,7 +331,7 @@ "regenconf_file_updated": "Le fichier de configuration '{conf}' a été mis à jour", "regenconf_now_managed_by_yunohost": "Le fichier de configuration '{conf}' est maintenant géré par YunoHost (catégorie {category}).", "regenconf_up_to_date": "La configuration est déjà à jour pour la catégorie '{category}'", - "already_up_to_date": "Il n’y a rien à faire ! Tout est déjà à jour !", + "already_up_to_date": "Il n’y a rien à faire. Tout est déjà à jour.", "global_settings_setting_security_nginx_compatibility": "Compatibilité versus compromis sécuritaire pour le serveur web Nginx. Affecte les cryptogrammes (et d’autres aspects liés à la sécurité)", "global_settings_setting_security_ssh_compatibility": "Compatibilité versus compromis sécuritaire pour le serveur SSH. Affecte les cryptogrammes (et d’autres aspects liés à la sécurité)", "global_settings_setting_security_postfix_compatibility": "Compatibilité versus compromis sécuritaire pour le serveur Postfix. Affecte les cryptogrammes (et d’autres aspects liés à la sécurité)", @@ -340,7 +340,7 @@ "regenconf_would_be_updated": "La configuration aurait dû être mise à jour pour la catégorie '{category}'", "regenconf_dry_pending_applying": "Vérification de la configuration en attente qui aurait été appliquée pour la catégorie '{category}'…", "regenconf_failed": "Impossible de régénérer la configuration pour la ou les catégorie(s) : '{categories}'", - "regenconf_pending_applying": "Applique la configuration en attente pour la catégorie '{category}'...", + "regenconf_pending_applying": "Applique la configuration en attente pour la catégorie '{category}' ...", "service_regen_conf_is_deprecated": "'yunohost service regen-conf' est obsolète ! Veuillez plutôt utiliser 'yunohost tools regen-conf' à la place.", "tools_upgrade_at_least_one": "Veuillez spécifier '--apps' ou '--system'", "tools_upgrade_cant_both": "Impossible de mettre à niveau le système et les applications en même temps", @@ -379,7 +379,7 @@ "migrations_already_ran": "Ces migrations sont déjà effectuées : {ids}", "migrations_dependencies_not_satisfied": "Exécutez ces migrations : '{dependencies_id}', avant migration {id}.", "migrations_failed_to_load_migration": "Impossible de charger la migration {id} : {error}", - "migrations_running_forward": "Exécution de la migration {id}...", + "migrations_running_forward": "Exécution de la migration {id} ...", "migrations_success_forward": "Migration {id} terminée", "operation_interrupted": "L'opération a-t-elle été interrompue manuellement ?", "permission_already_exist": "L’autorisation '{permission}' existe déjà", @@ -552,24 +552,24 @@ "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.", "restore_already_installed_apps": "Les applications suivantes ne peuvent pas être restaurées car elles sont déjà installées : {apps}", "regenconf_need_to_explicitly_specify_ssh": "La configuration de ssh a été modifiée manuellement. Vous devez explicitement indiquer la mention --force à \"ssh\" pour appliquer les changements.", - "migration_0015_cleaning_up": "Nettoyage du cache et des paquets qui ne sont plus utiles...", - "migration_0015_specific_upgrade": "Démarrage de la mise à jour des paquets du système qui doivent être mis à jour séparément...", + "migration_0015_cleaning_up": "Nettoyage du cache et des paquets qui ne sont plus nécessaires ...", + "migration_0015_specific_upgrade": "Démarrage de la mise à jour des paquets du système qui doivent l'être de manière indépendante ...", "migration_0015_modified_files": "Veuillez noter que les fichiers suivants ont été modifiés manuellement et pourraient être écrasés à la suite de la mise à niveau : {manually_modified_files}", "migration_0015_problematic_apps_warning": "Veuillez noter que des applications qui peuvent poser problèmes ont été détectées. Il semble qu'elles n'aient pas été installées à partir du catalogue d'applications YunoHost, ou bien qu'elles ne soient pas signalées comme \"fonctionnelles\". Par conséquent, il n'est pas possible de garantir que les applications suivantes fonctionneront encore après la mise à niveau : {problematic_apps}", "migration_0015_general_warning": "Veuillez noter que cette migration est une opération délicate. L'équipe YunoHost a fait de son mieux pour la revérifier et la tester, mais la migration pourrait quand même casser des éléments du système ou de ses applications.\n\nIl est donc recommandé :\n…- de faire une sauvegarde de toute donnée ou application critique. Plus d'informations ici https://yunohost.org/backup ;\n…- d'être patient après le lancement de la migration. Selon votre connexion internet et votre matériel, la mise à niveau peut prendre jusqu'à quelques heures.", "migration_0015_system_not_fully_up_to_date": "Votre système n'est pas entièrement à jour. Veuillez effectuer une mise à jour normale avant de lancer la migration vers Buster.", "migration_0015_not_enough_free_space": "L'espace libre est très faible dans /var/ ! Vous devriez avoir au moins 1 Go de libre pour effectuer cette migration.", "migration_0015_not_stretch": "La distribution Debian actuelle n'est pas Stretch !", - "migration_0015_yunohost_upgrade": "Démarrage de la mise à jour de YunoHost...", + "migration_0015_yunohost_upgrade": "Démarrage de la mise à jour de YunoHost ...", "migration_0015_still_on_stretch_after_main_upgrade": "Quelque chose s'est mal passé lors de la mise à niveau, le système semble toujours être sous Debian Stretch", - "migration_0015_main_upgrade": "Démarrage de la mise à niveau générale...", - "migration_0015_patching_sources_list": "Mise à jour du fichier sources.lists...", + "migration_0015_main_upgrade": "Démarrage de la mise à niveau générale ...", + "migration_0015_patching_sources_list": "Mise à jour du fichier sources.lists ...", "migration_0015_start": "Démarrage de la migration vers Buster", "migration_description_0015_migrate_to_buster": "Mise à niveau du système vers Debian Buster et YunoHost 4.x", "diagnosis_dns_try_dyndns_update_force": "La configuration DNS de ce domaine devrait être automatiquement gérée par YunoHost. Si ce n'est pas le cas, vous pouvez essayer de forcer une mise à jour en utilisant yunohost dyndns update --force.", "app_packaging_format_not_supported": "Cette application ne peut pas être installée car son format n'est pas pris en charge par votre version de YunoHost. Vous devriez probablement envisager de mettre à jour votre système.", "migration_0015_weak_certs": "Il a été constaté que les certificats suivants utilisent encore des algorithmes de signature peu robustes et doivent être mis à jour pour être compatibles avec la prochaine version de NGINX : {certs}", - "global_settings_setting_backup_compress_tar_archives": "Compresser les archives (.tar.gz) au lieu des archives non-compressées lors de la création des backups. N.B. : activer cette option permet d'obtenir des sauvegardes plus légères, mais leur création sera significativement plus longue et plus gourmande en CPU.", + "global_settings_setting_backup_compress_tar_archives": "Lors de la création de nouvelles sauvegardes, compressez les archives au format '.tar.gz' plutôt que des archives non-compressées au format '.tar'. Notez qu'activer cette option permet d'obtenir des sauvegardes plus légères, mais leur création sera significativement plus longue et plus gourmande en CPU.", "migration_description_0018_xtable_to_nftable": "Migrer les anciennes règles de trafic réseau vers le nouveau système basé sur nftables", "service_description_php7.3-fpm": "Exécute les applications écrites en PHP avec NGINX", "migration_0018_failed_to_reset_legacy_rules": "La réinitialisation des règles iptable par défaut a échoué : {error}", @@ -586,12 +586,12 @@ "app_manifest_install_ask_password": "Choisissez un mot de passe administrateur pour cette application", "app_manifest_install_ask_path": "Choisissez le chemin sur lequel vous souhaitez installer cette application", "app_manifest_install_ask_domain": "Choisissez le domaine sur lequel vous souhaitez installer cette application", - "global_settings_setting_smtp_relay_user": "Relais de compte utilisateur SMTP", - "global_settings_setting_smtp_relay_port": "Port relais SMTP", - "global_settings_setting_smtp_relay_host": "Relais SMTP à utiliser pour envoyer du courrier à la place de cette instance YunoHost. Utile si vous êtes dans l'une de ces situations : votre port 25 est bloqué par votre FAI ou votre fournisseur VPS, vous avez une IP résidentielle répertoriée sur DUHL, vous ne pouvez pas configurer de DNS inversé ou ce serveur n'est pas directement exposé sur Internet et vous voulez en utiliser un autre pour envoyer des mails.", + "global_settings_setting_smtp_relay_user": "Compte de l'utilisateur du relais SMTP", + "global_settings_setting_smtp_relay_port": "Port du relais SMTP", + "global_settings_setting_smtp_relay_host": "Un relais SMTP permet d'envoyer du courrier à la place de cette instance YunoHost. Cela est utile si vous êtes dans l'une de ces situations : le port 25 est bloqué par votre FAI ou par votre fournisseur VPS, vous avez une IP résidentielle répertoriée sur DUHL, vous ne pouvez pas configurer de reverse DNS, si le serveur n'est pas directement accessible depuis Internet et que vous voulez en utiliser un autre pour envoyer des mails.", "diagnosis_package_installed_from_sury_details": "Certains paquets ont été installés par inadvertance à partir d'un dépôt tiers appelé Sury. L'équipe YunoHost a amélioré la stratégie de gestion de ces paquets, mais on s'attend à ce que certaines configurations qui ont installé des applications PHP7.3 tout en étant toujours sur Stretch présentent des incohérences. Pour résoudre cette situation, vous devez essayer d'exécuter la commande suivante : {cmd_to_fix}", "app_argument_password_no_default": "Erreur lors de l'analyse de l'argument de mot de passe '{name}' : l'argument de mot de passe ne peut pas avoir de valeur par défaut pour des raisons de sécurité", - "pattern_email_forward": "Il doit s'agir d'une adresse électronique valide, le symbole '+' étant accepté (par exemples : johndoe@exemple.com ou bien johndoe+yunohost@exemple.com)", + "pattern_email_forward": "L'adresse électronique doit être valide, le symbole '+' étant accepté (par exemples : johndoe+yunohost@exemple.com)", "global_settings_setting_smtp_relay_password": "Mot de passe du relais de l'hôte SMTP", "diagnosis_package_installed_from_sury": "Des paquets du système devraient être rétrogradé de version", "additional_urls_already_added": "URL supplémentaire '{url:s}' déjà ajoutée pour la permission '{permission:s}'", @@ -612,15 +612,15 @@ "migration_description_0019_extend_permissions_features": "Étendre et retravailler le système de gestion des permissions applicatives", "diagnosis_basesystem_hardware_model": "Le modèle du serveur est {model}", "diagnosis_backports_in_sources_list": "Il semble qu'apt (le gestionnaire de paquets) soit configuré pour utiliser le dépôt des rétroportages (backports). A moins que vous ne sachiez vraiment ce que vous faites, nous vous déconseillons fortement d'installer des paquets provenant des rétroportages, car cela risque de créer des instabilités ou des conflits sur votre système.", - "postinstall_low_rootfsspace": "Le système de fichiers racine a une taille totale inférieure à 10 GB, ce qui est inquiétant ! Vous allez certainement arriver à court d'espace disque rapidement ! Il est recommandé d'avoir au moins 16 GB pour ce système de fichiers. Si vous voulez installer YunoHost malgré cet avertissement, relancez le postinstall avec --force-diskspace", + "postinstall_low_rootfsspace": "Le système de fichiers a une taille totale inférieure à 10 Go, ce qui est préoccupant et devrait attirer votre attention ! Vous allez certainement arriver à court d'espace disque (très) rapidement ! Il est recommandé d'avoir au moins 16 Go à la racine pour ce système de fichiers. Si vous voulez installer YunoHost malgré cet avertissement, relancez la postinstall avec --force-diskspace", "domain_remove_confirm_apps_removal": "Le retrait de ce domaine retirera aussi ces applications :\n{apps}\n\nÊtes vous sûr de vouloir cela ? [{answers}]", "diagnosis_rootfstotalspace_critical": "Le système de fichiers racine ne fait que {space} ! Vous allez certainement le remplir très rapidement ! Il est recommandé d'avoir au moins 16 GB pour ce système de fichiers.", "diagnosis_rootfstotalspace_warning": "Le système de fichiers racine n'est que de {space}. Cela peut suffire, mais faites attention car vous risquez de les remplir rapidement... Il est recommandé d'avoir au moins 16 GB pour ce système de fichiers.", "app_restore_script_failed": "Une erreur s'est produite dans le script de restauration de l'application", - "restore_backup_too_old": "Cette archive de sauvegarde ne peut être restaurée car elle provient d'une version trop ancienne de YunoHost.", - "migration_update_LDAP_schema": "Mise à jour du schéma LDAP...", + "restore_backup_too_old": "Cette sauvegarde ne peut être restaurée car le fichier de l'archive provient d'une version trop ancienne de YunoHost.", + "migration_update_LDAP_schema": "Mise à jour du schéma LDAP ...", "log_backup_create": "Créer une archive de sauvegarde", - "global_settings_setting_ssowat_panel_overlay_enabled": "Activer la superposition du panneau SSOwat", + "global_settings_setting_ssowat_panel_overlay_enabled": "Activer la surcouche SSOwat au panel web", "migration_ldap_rollback_success": "Système rétabli dans son état initial.", "permission_cant_add_to_all_users": "L'autorisation {permission} ne peut pas être ajoutée à tous les utilisateurs.", "migration_ldap_migration_failed_trying_to_rollback": "Impossible de migrer... tentative de restauration du système.", @@ -632,4 +632,4 @@ "diagnosis_sshd_config_inconsistent": "Il semble que le port SSH a été modifié manuellement dans /etc/ssh/sshd_config. Depuis YunoHost 4.2, un nouveau paramètre global 'security.ssh.port' est disponible pour éviter de modifier manuellement la configuration.", "diagnosis_sshd_config_insecure": "La configuration SSH semble avoir été modifiée manuellement et n'est pas sécurisée car elle ne contient aucune directive 'AllowGroups' ou 'AllowUsers' pour limiter l'accès aux utilisateurs autorisés.", "backup_create_size_estimation": "L'archive contiendra environ {size} de données." -} \ No newline at end of file +} From d965052f1d485fc6877c4cd5f9a3d31f5e312a22 Mon Sep 17 00:00:00 2001 From: ericgaspar Date: Wed, 30 Jun 2021 21:23:38 +0200 Subject: [PATCH 2608/3170] Update nodejs --- data/helpers.d/nodejs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/nodejs b/data/helpers.d/nodejs index 13bea42cd..a796b68fd 100644 --- a/data/helpers.d/nodejs +++ b/data/helpers.d/nodejs @@ -1,6 +1,6 @@ #!/bin/bash -n_version=7.2.2 +n_version=7.3.0 n_install_dir="/opt/node_n" node_version_path="$n_install_dir/n/versions/node" # N_PREFIX is the directory of n, it needs to be loaded as a environment variable. @@ -17,7 +17,7 @@ ynh_install_n () { ynh_print_info --message="Installation of N - Node.js version management" # Build an app.src for n echo "SOURCE_URL=https://github.com/tj/n/archive/v${n_version}.tar.gz -SOURCE_SUM=9654440b0e7169cf3be5897a563258116b21ec6e7e7e266acc56979d3ebec6a2" > "$YNH_APP_BASEDIR/conf/n.src" +SOURCE_SUM=b908b0fc86922ede37e89d1030191285209d7d521507bf136e62895e5797847f" > "$YNH_APP_BASEDIR/conf/n.src" # Download and extract n ynh_setup_source --dest_dir="$n_install_dir/git" --source_id=n # Install n From 2478f5a0baba3a99fd0ff578e445813e1e8a40af Mon Sep 17 00:00:00 2001 From: Christian Wehrli Date: Sun, 4 Jul 2021 17:02:46 +0000 Subject: [PATCH 2609/3170] Translated using Weblate (German) Currently translated at 97.4% (617 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 77 +++++++++++++++++++++++++------------------------ 1 file changed, 40 insertions(+), 37 deletions(-) diff --git a/locales/de.json b/locales/de.json index fad03058d..b3817707d 100644 --- a/locales/de.json +++ b/locales/de.json @@ -37,7 +37,7 @@ "backup_output_directory_not_empty": "Der gewählte Ausgabeordner sollte leer sein", "backup_output_directory_required": "Für die Datensicherung muss ein Zielverzeichnis angegeben werden", "backup_running_hooks": "Datensicherunghook wird ausgeführt...", - "custom_app_url_required": "Es muss eine URL angegeben werden, um deine benutzerdefinierte App {app:s} zu aktualisieren", + "custom_app_url_required": "Sie müssen eine URL angeben, um Ihre benutzerdefinierte App {app:s} zu aktualisieren", "domain_cert_gen_failed": "Zertifikat konnte nicht erzeugt werden", "domain_created": "Domäne erstellt", "domain_creation_failed": "Konnte Domäne nicht erzeugen", @@ -46,7 +46,7 @@ "domain_dyndns_already_subscribed": "Sie haben sich schon für eine DynDNS-Domäne registriert", "domain_dyndns_root_unknown": "Unbekannte DynDNS Hauptdomain", "domain_exists": "Die Domäne existiert bereits", - "domain_uninstall_app_first": "Diese Apps sind noch auf Ihrer Domäne installiert; \n{apps}\n\nBitte deinstallieren Sie sie mit dem Befehl 'yunohost app remove the_app_id' oder verschieben Sie sie mit 'yunohost app change-url the_app_id'", + "domain_uninstall_app_first": "Diese Applikationen sind noch auf Ihrer Domäne installiert; \n{apps}\n\nBitte deinstallieren Sie sie mit dem Befehl 'yunohost app remove the_app_id' oder verschieben Sie sie mit 'yunohost app change-url the_app_id'", "domain_unknown": "Unbekannte Domain", "done": "Erledigt", "downloading": "Wird heruntergeladen…", @@ -90,7 +90,7 @@ "restore_complete": "Vollständig wiederhergestellt", "restore_confirm_yunohost_installed": "Möchtest du die Wiederherstellung wirklich starten? [{answers:s}]", "restore_failed": "Das System konnte nicht wiederhergestellt werden", - "restore_hook_unavailable": "Das Wiederherstellungsskript für '{part:s}' steht weder in deinem System noch im Archiv zur Verfügung", + "restore_hook_unavailable": "Das Wiederherstellungsskript für '{part:s}' steht weder in Ihrem System noch im Archiv zur Verfügung", "restore_nothings_done": "Nichts wurde wiederhergestellt", "restore_running_app_script": "App '{app:s}' wird wiederhergestellt…", "restore_running_hooks": "Wiederherstellung wird gestartet…", @@ -155,7 +155,7 @@ "certmanager_certificate_fetching_or_enabling_failed": "Die Aktivierung des neuen Zertifikats für die {domain:s} ist fehlgeschlagen...", "certmanager_attempt_to_renew_nonLE_cert": "Das Zertifikat der Domain '{domain:s}' wurde nicht von Let's Encrypt ausgestellt. Es kann nicht automatisch erneuert werden!", "certmanager_attempt_to_renew_valid_cert": "Das Zertifikat der Domain {domain:s} läuft nicht in Kürze ab! (Benutze --force um diese Nachricht zu umgehen)", - "certmanager_domain_http_not_working": "Es scheint so, dass die Domain {domain:s} nicht über HTTP erreicht werden kann. Bitte überprüfe, ob deine DNS und nginx Konfiguration in Ordnung ist. (Wenn du weißt was du tust, nutze \"--no-checks\" um die überprüfung zu überspringen.)", + "certmanager_domain_http_not_working": "Es scheint, als ob die Domäne {domain:s} nicht über HTTP erreicht werden kann. Bitte überprüfen Sie, ob Ihre DNS- und nginx-Konfiguration in Ordnung ist. (Wenn Sie wissen was Sie tun, nutzen Sie \"--no-checks\" um die Überprüfung zu überspringen.)", "certmanager_domain_dns_ip_differs_from_public_ip": "Der DNS-A-Eintrag der Domain {domain:s} unterscheidet sich von dieser Server-IP. Für weitere Informationen überprüfen Sie bitte die 'DNS records' (basic) Kategorie in der Diagnose. Wenn Sie gerade Ihren A-Eintrag verändert haben, warten Sie bitte etwas, damit die Änderungen wirksam werden (Sie können die DNS-Propagation mittels Website überprüfen) (Wenn Sie wissen was Sie tun, können Sie --no-checks benutzen, um diese Überprüfung zu überspringen.)", "certmanager_cannot_read_cert": "Es ist ein Fehler aufgetreten, als es versucht wurde das aktuelle Zertifikat für die Domain {domain:s} zu öffnen (Datei: {file:s}), Grund: {reason:s}", "certmanager_cert_install_success_selfsigned": "Ein selbstsigniertes Zertifikat für die Domain {domain:s} wurde erfolgreich installiert", @@ -176,8 +176,8 @@ "backup_abstract_method": "Diese Backup-Methode wird noch nicht unterstützt", "backup_applying_method_tar": "Erstellen des Backup-tar Archives...", "backup_applying_method_copy": "Kopiere alle Dateien ins Backup...", - "app_change_url_no_script": "Die Anwendung '{app_name:s}' unterstützt bisher keine URL-Modifikation. Vielleicht sollte sie aktualisiert werden.", - "app_location_unavailable": "Diese URL ist nicht verfügbar oder wird von einer installierten Anwendung genutzt:\n{apps:s}", + "app_change_url_no_script": "Die Applikation '{app_name:s}' unterstützt bisher keine URL-Modifikation. Vielleicht sollte sie aktualisiert werden.", + "app_location_unavailable": "Diese URL ist nicht verfügbar oder wird von einer installierten Applikation genutzt:\n{apps:s}", "backup_applying_method_custom": "Rufe die benutzerdefinierte Backup-Methode '{method:s}' auf...", "backup_archive_system_part_not_available": "Der System-Teil '{part:s}' ist in diesem Backup nicht enthalten", "backup_archive_writing_error": "Die Dateien '{source:s} (im Ordner '{dest:s}') konnten nicht in das komprimierte Archiv-Backup '{archive:s}' hinzugefügt werden", @@ -191,9 +191,9 @@ "dyndns_could_not_check_provide": "Konnte nicht überprüft, ob {provider:s} die Domain(s) {domain:s} bereitstellen kann.", "domain_dns_conf_is_just_a_recommendation": "Dieser Befehl zeigt Ihnen die * empfohlene * Konfiguration. Die DNS-Konfiguration wird NICHT für Sie eingerichtet. Es liegt in Ihrer Verantwortung, Ihre DNS-Zone in Ihrem Registrar gemäß dieser Empfehlung zu konfigurieren.", "dpkg_lock_not_available": "Dieser Befehl kann momentan nicht ausgeführt werden, da anscheinend ein anderes Programm die Sperre von dpkg (dem Systempaket-Manager) verwendet", - "confirm_app_install_thirdparty": "WARNUNG! Diese App ist nicht Teil von YunoHosts App-Katalog. Das Installieren von Drittanbieteranwendungen könnte die Sicherheit und Integrität des System beeinträchtigen. Sie sollten wahrscheinlich NICHT fortfahren, es sei denn, Sie wissen, was Sie tun. Es wird KEIN SUPPORT zur Verfügung stehen, wenn die App nicht funktioniert oder das System zerstört... Wenn Sie das Risiko trotzdem eingehen möchten, tippen Sie '{answers:s}'", - "confirm_app_install_danger": "WARNUNG! Diese Anwendung ist noch experimentell (wenn nicht ausdrücklich \"not working\"/\"nicht funktionsfähig\")! Sie sollten sie wahrscheinlich NICHT installieren, es sei denn, Sie wißen, was Sie tun. Es wird keine Unterstützung geleistet, falls diese Anwendung nicht funktioniert oder Ihr System zerstört... Falls Sie bereit bist, dieses Risiko einzugehen, tippe '{answers:s}'", - "confirm_app_install_warning": "Warnung: Diese Anwendung funktioniert möglicherweise, ist jedoch nicht gut in YunoHost integriert. Einige Funktionen wie Single Sign-On und Backup / Restore sind möglicherweise nicht verfügbar. Trotzdem installieren? [{answers:s}] ", + "confirm_app_install_thirdparty": "WARNUNG! Diese Applikation ist nicht Teil von YunoHosts Applikations-Katalog. Das Installieren von Drittanbieterapplikationen könnte die Sicherheit und Integrität des System beeinträchtigen. Sie sollten wahrscheinlich NICHT fortfahren, es sei denn, Sie wissen, was Sie tun. Es wird KEIN SUPPORT zur Verfügung stehen, wenn die Applikation nicht funktioniert oder das System zerstört... Wenn Sie das Risiko trotzdem eingehen möchten, tippen Sie '{answers:s}'", + "confirm_app_install_danger": "WARNUNG! Diese Applikation ist noch experimentell (wenn nicht ausdrücklich \"not working\"/\"nicht funktionsfähig\")! Sie sollten sie wahrscheinlich NICHT installieren, es sei denn, Sie wißen, was Sie tun. Es wird keine Unterstützung geleistet, falls diese Applikation nicht funktioniert oder Ihr System zerstört... Falls Sie bereit bist, dieses Risiko einzugehen, tippe '{answers:s}'", + "confirm_app_install_warning": "Warnung: Diese Applikation funktioniert möglicherweise, ist jedoch nicht gut in YunoHost integriert. Einige Funktionen wie Single Sign-On und Backup / Restore sind möglicherweise nicht verfügbar. Trotzdem installieren? [{answers:s}] ", "backup_with_no_restore_script_for_app": "{app:s} hat kein Wiederherstellungsskript. Das Backup dieser App kann nicht automatisch wiederhergestellt werden.", "backup_with_no_backup_script_for_app": "Die App {app:s} hat kein Sicherungsskript. Ignoriere es.", "backup_unable_to_organize_files": "Dateien im Archiv konnten nicht mit der schnellen Methode organisiert werden", @@ -212,7 +212,7 @@ "backup_actually_backuping": "Erstellt ein Backup-Archiv aus den gesammelten Dateien...", "ask_new_path": "Neuer Pfad", "ask_new_domain": "Neue Domain", - "app_upgrade_some_app_failed": "Einige Anwendungen können nicht aktualisiert werden", + "app_upgrade_some_app_failed": "Einige Applikationen können nicht aktualisiert werden", "app_upgrade_app_name": "{app} wird jetzt aktualisiert...", "app_upgrade_several_apps": "Die folgenden Apps werden aktualisiert: {apps}", "app_start_restore": "{app} wird wiederhergestellt...", @@ -228,10 +228,10 @@ "app_action_broke_system": "Diese Aktion scheint diese wichtigen Dienste unterbrochen zu haben: {services}", "apps_already_up_to_date": "Alle Apps sind bereits aktuell", "backup_copying_to_organize_the_archive": "Kopieren von {size:s} MB, um das Archiv zu organisieren", - "global_settings_setting_security_ssh_compatibility": "Kompatibilität vs. Sicherheitskompromiss für den SSH-Server. Beeinflusst die Chiffren (und andere sicherheitsrelevante Aspekte)", + "global_settings_setting_security_ssh_compatibility": "Kompatibilitäts- vs. Sicherheits-Kompromiss für den SSH-Server. Betrifft die Ciphers (und andere sicherheitsrelevante Aspekte)", "group_deleted": "Gruppe '{group}' gelöscht", - "group_deletion_failed": "Konnte Gruppe '{group}' nicht löschen", - "dyndns_provider_unreachable": "DynDNS-Anbieter {provider} kann nicht erreicht werden: Entweder ist dein YunoHost nicht korrekt mit dem Internet verbunden oder der Dynette-Server ist ausgefallen.", + "group_deletion_failed": "Konnte Gruppe '{group}' nicht löschen: {error}", + "dyndns_provider_unreachable": "DynDNS-Anbieter {provider} kann nicht erreicht werden: Entweder ist Ihr YunoHost nicht korrekt mit dem Internet verbunden oder der Dynette-Server ist ausgefallen.", "group_created": "Gruppe '{group}' angelegt", "group_creation_failed": "Konnte Gruppe '{group}' nicht anlegen", "group_unknown": "Die Gruppe '{group:s}' ist unbekannt", @@ -239,15 +239,15 @@ "group_update_failed": "Kann Gruppe '{group:s}' nicht aktualisieren: {error}", "log_does_exists": "Es gibt kein Operationsprotokoll mit dem Namen'{log}', verwende 'yunohost log list', um alle verfügbaren Operationsprotokolle anzuzeigen", "log_operation_unit_unclosed_properly": "Die Operationseinheit wurde nicht richtig geschlossen", - "global_settings_setting_security_postfix_compatibility": "Kompatibilität vs. Sicherheitskompromiss für den Postfix-Server. Beeinflusst die Chiffren (und andere sicherheitsrelevante Aspekte)", + "global_settings_setting_security_postfix_compatibility": "Kompatibilitäts- vs. Sicherheits-Kompromiss für den Postfix-Server. Betrifft die Ciphers (und andere sicherheitsrelevante Aspekte)", "global_settings_unknown_type": "Unerwartete Situation, die Einstellung {setting:s} scheint den Typ {unknown_type:s} zu haben, ist aber kein vom System unterstützter Typ.", "dpkg_is_broken": "Du kannst das gerade nicht tun, weil dpkg/APT (der Systempaketmanager) in einem defekten Zustand zu sein scheint.... Du kannst versuchen, dieses Problem zu lösen, indem du dich über SSH verbindest und `sudo apt install --fix-broken` sowie/oder `sudo dpkg --configure -a` ausführst.", "global_settings_unknown_setting_from_settings_file": "Unbekannter Schlüssel in den Einstellungen: '{setting_key:s}', verwerfen und speichern in /etc/yunohost/settings-unknown.json", "log_link_to_log": "Vollständiges Log dieser Operation: '{desc}'", - "log_help_to_get_log": "Um das Protokoll der Operation '{desc}' anzuzeigen, verwende den Befehl 'yunohost log show {name}{name}'", - "global_settings_setting_security_nginx_compatibility": "Kompatibilität vs. Sicherheitskompromiss für den Webserver NGINX. Beeinflusst die Chiffren (und andere sicherheitsrelevante Aspekte)", + "log_help_to_get_log": "Um das Protokoll der Operation '{desc}' anzuzeigen, verwenden Sie den Befehl 'yunohost log show {name}{name}'", + "global_settings_setting_security_nginx_compatibility": "Kompatibilitäts- vs. Sicherheits-Kompromiss für den Webserver NGINX. Betrifft die Ciphers (und andere sicherheitsrelevante Aspekte)", "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Erlaubt die Verwendung eines (veralteten) DSA-Hostkeys für die SSH-Daemon-Konfiguration", - "log_app_remove": "Entferne die Applikation '{}'", + "log_app_remove": "Entferne die Applikation '{}'", "global_settings_cant_open_settings": "Einstellungsdatei konnte nicht geöffnet werden, Grund: {reason:s}", "global_settings_cant_write_settings": "Einstellungsdatei konnte nicht gespeichert werden, Grund: {reason:s}", "log_app_install": "Installiere die Applikation '{}'", @@ -268,7 +268,7 @@ "global_settings_key_doesnt_exists": "Der Schlüssel'{settings_key:s}' existiert nicht in den globalen Einstellungen, du kannst alle verfügbaren Schlüssel sehen, indem du 'yunohost settings list' ausführst", "log_app_makedefault": "Mache '{}' zur Standard-Applikation", "hook_json_return_error": "Konnte die Rückkehr vom Einsprungpunkt {path:s} nicht lesen. Fehler: {msg:s}. Unformatierter Inhalt: {raw_content}", - "app_full_domain_unavailable": "Es tut uns leid, aber diese Anwendung erfordert die Installation auf einer eigenen Domain, aber einige andere Anwendungen sind bereits auf der Domäne'{domain}' installiert. Eine mögliche Lösung ist das Hinzufügen und Verwenden einer Subdomain, die dieser Anwendung zugeordnet ist.", + "app_full_domain_unavailable": "Es tut uns leid, aber diese Applikation erfordert die Installation auf einer eigenen Domain, aber einige andere Applikationen sind bereits auf der Domäne'{domain}' installiert. Eine mögliche Lösung ist das Hinzufügen und Verwenden einer Subdomain, die dieser Applikation zugeordnet ist.", "app_install_failed": "{app} kann nicht installiert werden: {error}", "app_install_script_failed": "Im Installationsscript ist ein Fehler aufgetreten", "app_remove_after_failed_install": "Entfernen der App nach fehlgeschlagener Installation...", @@ -310,7 +310,7 @@ "backup_archive_corrupted": "Das Backup-Archiv '{archive}' scheint beschädigt: {error}", "backup_archive_cant_retrieve_info_json": "Die Informationen für das Archiv '{archive}' konnten nicht geladen werden... Die Datei info.json wurde nicht gefunden (oder ist kein gültiges json).", "app_packaging_format_not_supported": "Diese App kann nicht installiert werden da das Paketformat nicht von der YunoHost-Version unterstützt wird. Denken Sie darüber nach das System zu aktualisieren.", - "certmanager_domain_not_diagnosed_yet": "Für die Domäne {domain} gibt es noch keine Diagnoseresultate. Bitte wiederholen Sie die Diagnose für die Kategorien 'DNS-Einträge' und 'Web' im Diagnose-Bereich um zu überprüfen ob die Domäne für Let's Encrypt bereit ist. (Oder wenn Sie wissen was Sie tun, verwenden Sie '--no-checks' um diese Überprüfungen abzuschalten.", + "certmanager_domain_not_diagnosed_yet": "Für die Domäne {domain} gibt es noch keine Diagnoseresultate. Bitte wiederholen Sie die Diagnose für die Kategorien 'DNS-Einträge' und 'Web' im Diagnose-Bereich um zu überprüfen ob die Domäne für Let's Encrypt bereit ist. (Oder wenn Sie wissen was Sie tun, verwenden Sie '--no-checks' um diese Überprüfungen abzuschalten.)", "migration_0015_patching_sources_list": "sources.lists wird repariert...", "migration_0015_start": "Start der Migration auf Buster", "migration_description_0015_migrate_to_buster": "Auf Debian Buster und YunoHost 4.x upgraden", @@ -319,7 +319,7 @@ "diagnosis_services_running": "Dienst {service} läuft!", "diagnosis_domain_expires_in": "{domain} läuft in {days} Tagen ab.", "diagnosis_domain_expiration_error": "Einige Domänen werden SEHR BALD ablaufen!", - "diagnosis_domain_expiration_success": "Deine Domänen sind registriert und werden in nächster Zeit nicht ablaufen.", + "diagnosis_domain_expiration_success": "Ihre Domänen sind registriert und werden in nächster Zeit nicht ablaufen.", "diagnosis_domain_not_found_details": "Die Domäne {domain} existiert nicht in der WHOIS-Datenbank oder sie ist abgelaufen!", "diagnosis_domain_expiration_not_found": "Das Ablaufdatum einiger Domains kann nicht überprüft werden", "diagnosis_dns_try_dyndns_update_force": "Die DNS-Konfiguration dieser Domain sollte automatisch von YunoHost verwaltet werden. Andernfalls können Sie mittels yunohost dyndns update --force ein Update erzwingen.", @@ -334,7 +334,7 @@ "diagnosis_services_bad_status": "Der Dienst {service} ist {status} :(", "diagnosis_diskusage_verylow": "Der Speicher {mountpoint} (auf Gerät {device}) hat nur noch {free} ({free_percent}%) freien Speicherplatz (von ingesamt {total}). Sie sollten sich ernsthaft überlegen, einigen Seicherplatz frei zu machen!", "diagnosis_http_ok": "Die Domäne {domain} ist über HTTP von außerhalb des lokalen Netzwerks erreichbar.", - "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Einige Hosting-Anbieter werden es Ihnen nicht gestatten, den ausgehenden Port 25 zu öffnen, da diese sich nicht um die Netzneutralität kümmern.
- Einige davon bieten als Alternative an, ein Mailserver-Relay zu verwenden, was jedoch bedeutet, dass das Relay Ihren E-Mail-Verkehr ausspionieren kann.
- Eine die Privatsphäre berücksichtigende Alternative ist die Verwendung eines VPN *mit einer dedizierten öffentlichen IP* um solche Einschränkungen zu umgehen. Schauen Sie unter https://yunohost.org/#/vpn_advantage nach.
- Sie können auch in Betracht ziehen, zu einem netzneutralitätfreundlicheren Anbieter zu wechseln.", + "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Einige Hosting-Anbieter werden es Ihnen nicht gestatten, den ausgehenden Port 25 zu öffnen, da diese sich nicht um die Netzneutralität kümmern.
- Einige davon bieten als Alternative an, ein Mailserver-Relay zu verwenden, was jedoch bedeutet, dass das Relay Ihren E-Mail-Verkehr ausspionieren kann.
- Eine die Privatsphäre berücksichtigende Alternative ist die Verwendung eines VPN *mit einer dedizierten öffentlichen IP* um solche Einschränkungen zu umgehen. Schauen Sie unter https://yunohost.org/#/vpn_advantage nach.
- Sie können auch in Betracht ziehen, zu einem netzneutralitätfreundlicheren Anbieter zu wechseln", "diagnosis_http_timeout": "Wartezeit wurde beim Versuch, von außen eine Verbindung zum Server aufzubauen, überschritten. Er scheint nicht erreichbar zu sein.
1. Die häufigste Ursache für dieses Problem ist daß der Port 80 (und 433) nicht richtig zu Ihrem Server weitergeleitet werden.
2. Sie sollten auch sicherstellen, daß der Dienst nginx läuft.
3. In komplexeren Umgebungen: Stellen Sie sicher, daß keine Firewall oder Reverse-Proxy stört.", "service_reloaded_or_restarted": "Der Dienst '{service:s}' wurde erfolgreich neu geladen oder gestartet", "service_restarted": "Der Dienst '{service:s}' wurde neu gestartet", @@ -354,7 +354,7 @@ "diagnosis_diskusage_low": "Der Speicher {mountpoint} (auf Gerät {device}) hat nur noch {free} ({free_percent}%) freien Speicherplatz (von insgesamt {total}). Seien Sie vorsichtig.", "diagnosis_ram_low": "Das System hat nur {available} ({available_percent}%) RAM zur Verfügung! (von insgesamt {total}). Seien Sie vorsichtig.", "service_reload_or_restart_failed": "Der Dienst '{service:s}' konnte nicht erneut geladen oder gestartet werden.\n\nKürzlich erstellte Logs des Dienstes: {logs:s}", - "diagnosis_domain_expiration_not_found_details": "Die WHOIS-Informationen für die Domäne {domain} scheint keine Informationen über das Ablaufdatum zu enthalten.", + "diagnosis_domain_expiration_not_found_details": "Die WHOIS-Informationen für die Domäne {domain} scheint keine Informationen über das Ablaufdatum zu enthalten?", "diagnosis_domain_expiration_warning": "Einige Domänen werden bald ablaufen!", "diagnosis_diskusage_ok": "Der Speicher {mountpoint} (auf Gerät {device}) hat immer noch {free} ({free_percent}%) freien Speicherplatz übrig(von insgesamt {total})!", "diagnosis_ram_ok": "Das System hat immer noch {available} ({available_percent}%) RAM zu Verfügung von {total}.", @@ -391,7 +391,7 @@ "diagnosis_mail_queue_unavailable": "Die Anzahl der anstehenden Nachrichten in der Warteschlange kann nicht abgefragt werden", "diagnosis_mail_queue_ok": "{nb_pending} anstehende E-Mails in der Warteschlange", "diagnosis_mail_blacklist_reason": "Der Grund für die Blacklist ist: {reason}", - "app_argument_password_no_default": "Fehler beim Verarbeiten des Passwort-Arguments '{name}': Passwort-Argument kann aus Sicherheitsgründen keinen Standardwert haben", + "app_argument_password_no_default": "Fehler beim Verarbeiten des Passwortarguments '{name}': Passwortargument kann aus Sicherheitsgründen keinen Standardwert enthalten", "log_regen_conf": "Systemkonfiguration neu generieren '{}'", "diagnosis_http_partially_unreachable": "Die Domäne {domain} scheint von aussen via HTTP per IPv{failed} nicht erreichbar zu sein, obwohl es per IPv{passed} funktioniert.", "diagnosis_http_unreachable": "Die Domäne {domain} scheint von aussen per HTTP nicht erreichbar zu sein.", @@ -420,9 +420,9 @@ "diagnosis_mail_blacklist_website": "Nachdem Sie herausgefunden haben, weshalb Sie auf die Blacklist gesetzt wurden und dies behoben haben, zögern Sie nicht, nachzufragen, ob Ihre IP-Adresse oder Ihre Domäne von auf {blacklist_website} entfernt wird", "diagnosis_unknown_categories": "Folgende Kategorien sind unbekannt: {categories}", "diagnosis_http_hairpinning_issue": "In Ihrem lokalen Netzwerk scheint Hairpinning nicht aktiviert zu sein.", - "diagnosis_ports_needed_by": "Diesen Port zu öffnen ist nötig, um die Funktionalität des Typs {category} (service {service}) zu gewährleisten.", + "diagnosis_ports_needed_by": "Diesen Port zu öffnen ist nötig, um die Funktionalität des Typs {category} (service {service}) zu gewährleisten", "diagnosis_mail_queue_too_big": "Zu viele anstehende Nachrichten in der Warteschlange ({nb_pending} emails)", - "diagnosis_package_installed_from_sury_details": "Einige Pakete wurden versehentlich aus einem Drittanbieter-Repository genannt Sury installiert. Das YunoHost-Team hat die Strategie um diese Pakete zu handhaben verbessert, aber es wird erwartet, dass einige Setups, welche PHP7.3-Applikationen installiert haben und immer noch auf Strech laufen ein paar Inkonsistenzen aufweisen. Um diese Situation zu beheben sollten Sie versuchen den folgenden Befehl auszuführen: {cmd_to_fix}", + "diagnosis_package_installed_from_sury_details": "Einige Pakete wurden versehentlich aus einem Drittanbieter-Repository genannt Sury installiert. Das YunoHost-Team hat die Strategie um diese Pakete zu handhaben verbessert, aber es wird erwartet, dass einige Setups, welche PHP7.3-Applikationen installiert haben und immer noch auf Strech laufen ein paar Inkonsistenzen aufweisen. Um diese Situation zu beheben sollten Sie versuchen den folgenden Befehl auszuführen: {cmd_to_fix}", "domain_cannot_add_xmpp_upload": "Eine hinzugefügte Domain darf nicht mit 'xmpp-upload.' beginnen. Dieser Name ist für das XMPP-Upload-Feature von YunoHost reserviert.", "group_cannot_be_deleted": "Die Gruppe {group} kann nicht manuell entfernt werden.", "group_cannot_edit_primary_group": "Die Gruppe '{group}' kann nicht manuell bearbeitet werden. Es ist die primäre Gruppe, welche dazu gedacht ist, nur einen spezifischen Benutzer zu enthalten.", @@ -431,7 +431,7 @@ "additional_urls_already_added": "Zusätzliche URL '{url:s}' bereits hinzugefügt in der zusätzlichen URL für Berechtigung '{permission:s}'", "additional_urls_already_removed": "Zusätzliche URL '{url:s}' bereits entfernt in der zusätzlichen URL für Berechtigung '{permission:s}'", "app_label_deprecated": "Dieser Befehl ist veraltet! Bitte nutzen Sie den neuen Befehl 'yunohost user permission update' um das Applabel zu verwalten.", - "diagnosis_http_hairpinning_issue_details": "Das ist wahrscheinlich aufgrund Ihrer ISP Box / Router. Als Konsequenz können Personen von ausserhalb Ihres Netzwerkes aber nicht von innerhalb Ihres lokalen Netzwerkes (wie wahrscheinlich Sie selber?) wie gewohnt auf Ihren Server zugreifen, wenn Sie ihre Domäne oder Ihre öffentliche IP verwenden. Sie können die Situation wahrscheinlich verbessern, indem Sie ein einen Blick in https://yunohost.org/dns_local_network werfen", + "diagnosis_http_hairpinning_issue_details": "Das ist wahrscheinlich aufgrund Ihrer ISP Box / Router. Als Konsequenz können Personen von ausserhalb Ihres Netzwerkes aber nicht von innerhalb Ihres lokalen Netzwerkes (wie wahrscheinlich Sie selber?) wie gewohnt auf Ihren Server zugreifen, wenn Sie ihre Domäne oder Ihre öffentliche IP verwenden. Sie können die Situation wahrscheinlich verbessern, indem Sie ein einen Blick in https://yunohost.org/dns_local_network werfen", "diagnosis_http_nginx_conf_not_up_to_date": "Jemand hat anscheinend die Konfiguration von Nginx manuell geändert. Diese Änderung verhindert, dass Yunohost eine Diagnose durchführen kann, wenn er via HTTP erreichbar ist.", "diagnosis_http_bad_status_code": "Anscheinend beantwortet ein anderes Gerät als Ihr Server die Anfrage (Vielleicht ihr Internetrouter).
1. Die häufigste Ursache ist, dass Port 80 (und 443) nicht richtig auf Ihren Server weitergeleitet wird.
2. Bei komplexeren Setups: Vergewissern Sie sich, dass keine Firewall und keine Reverse-Proxy interferieren.", "diagnosis_never_ran_yet": "Sie haben kürzlich einen neuen Yunohost-Server installiert aber es gibt davon noch keinen Diagnosereport. Sie sollten eine Diagnose anstossen. Sie können das entweder vom Webadmin aus oder in der Kommandozeile machen. In der Kommandozeile verwenden Sie dafür den Befehl 'yunohost diagnosis run'.", @@ -451,7 +451,7 @@ "global_settings_setting_smtp_relay_port": "SMTP Relay Port", "global_settings_setting_smtp_allow_ipv6": "Erlaube die Nutzung von IPv6 um Mails zu empfangen und zu versenden", "global_settings_setting_pop3_enabled": "Aktiviere das POP3 Protokoll für den Mailserver", - "domain_cannot_remove_main_add_new_one": "Du kannst \"{domain:s}\" nicht entfernen da es die Hauptdomain und deine einzige Domain ist, erst musst erst eine andere Domain hinzufügen indem du eingibst \"yunohost domain add \", setze es dann als deine Hauptdomain indem du eingibst \"yunohost domain main-domain -n \", erst jetzt kannst du die domain \"{domain:s}\" entfernen.", + "domain_cannot_remove_main_add_new_one": "Sie können '{domain:s}' nicht entfernen, weil es die Hauptdomäne und gleichzeitig Ihre einzige Domäne ist. Zuerst müssen Sie eine andere Domäne hinzufügen, indem Sie \"yunohost domain add another-domain.com>\" eingeben. Bestimmen Sie diese dann als Ihre Hauptdomain indem Sie 'yunohost domain main-domain -n ' eingeben. Nun können Sie die Domäne \"{domain:s}\" enfernen, indem Sie 'yunohost domain remove {domain:s}' eingeben.'", "diagnosis_rootfstotalspace_critical": "Das Root-Filesystem hat noch freien Speicher von {space}. Das ist besorngiserregend! Der Speicher wird schnell aufgebraucht sein. 16 GB für das Root-Filesystem werden empfohlen.", "diagnosis_rootfstotalspace_warning": "Das Root-Filesystem hat noch freien Speicher von {space}. Möglich, dass das in Ordnung ist. Vielleicht ist er aber auch schneller aufgebraucht. 16 GB für das Root-Filesystem werden empfohlen.", "global_settings_setting_smtp_relay_host": "Zu verwendender SMTP-Relay-Host um E-Mails zu versenden. Er wird anstelle dieser YunoHost-Instanz verwendet. Nützlich, wenn Sie in einer der folgenden Situationen sind: Ihr ISP- oder VPS-Provider hat Ihren Port 25 geblockt, eine Ihrer residentiellen IPs ist auf DUHL gelistet, Sie können keinen Reverse-DNS konfigurieren oder dieser Server ist nicht direkt mit dem Internet verbunden und Sie möchten einen anderen verwenden, um E-Mails zu versenden.", @@ -528,12 +528,12 @@ "migrations_no_such_migration": "Es existiert keine Migration genannt '{id}'", "migrations_running_forward": "Durchführen der Migrationen {id}...", "migrations_skip_migration": "Überspringe Migrationen {id}...", - "password_too_simple_2": "Dieses Passwort gehört zu den meistverwendeten der Welt. Bitte nehmen Sie etwas einzigartigeres.", + "password_too_simple_2": "Dieses Passwort gehört zu den meistverwendeten der Welt. Bitte nehmen Sie etwas einzigartigeres", "password_listed": "Dieses Passwort gehört zu den meistverwendeten der Welt. Bitte nehmen Sie etwas einzigartigeres.", "operation_interrupted": "Wurde die Operation manuell unterbrochen?", "invalid_number": "Muss eine Zahl sein", "migrations_to_be_ran_manually": "Die Migration {id} muss manuell durchgeführt werden. Bitte gehen Sie zu Werkzeuge → Migrationen auf der Webadmin-Seite oder führen Sie 'yunohost tools migrations run' aus.", - "permission_already_up_to_date": "Die Berechtigung wurde nicht aktualisiert, weil die Anfragen für Hinzufügen/Entfernen stimmen mit dem aktuellen Status bereits überein", + "permission_already_up_to_date": "Die Berechtigung wurde nicht aktualisiert, weil die Anfragen für Hinzufügen/Entfernen bereits mit dem aktuellen Status übereinstimmen.", "permission_already_exist": "Berechtigung '{permission}' existiert bereits", "permission_already_disallowed": "Für die Gruppe '{group}' wurde die Berechtigung '{permission}' deaktiviert", "permission_already_allowed": "Die Gruppe '{group}' hat die Berechtigung '{permission}' bereits erhalten", @@ -571,18 +571,18 @@ "restore_system_part_failed": "Die Systemteile '{part:s}' konnten nicht wiederhergestellt werden", "restore_removing_tmp_dir_failed": "Ein altes, temporäres Directory konnte nicht entfernt werden", "restore_not_enough_disk_space": "Nicht genug Speicher (Speicher: {free_space:d} B, benötigter Speicher: {needed_space:d} B, Sicherheitspuffer: {margin:d} B)", - "restore_may_be_not_enough_disk_space": "Dein System scheint nicht genug Speicherplatz zu haben (frei: {free_space:d} B, benötigter Platz: {needed_space:d} B, Sicherheitspuffer: {margin:d} B)", + "restore_may_be_not_enough_disk_space": "Ihr System scheint nicht genug Speicherplatz zu haben (frei: {free_space:d} B, benötigter Platz: {needed_space:d} B, Sicherheitspuffer: {margin:d} B)", "restore_extracting": "Packe die benötigten Dateien aus dem Archiv aus…", "restore_already_installed_apps": "Folgende Apps können nicht wiederhergestellt werden, weil sie schon installiert sind: {apps}", "regex_with_only_domain": "Du kannst regex nicht als Domain verwenden, sondern nur als Pfad", - "root_password_desynchronized": "Das Admin-Passwort wurde verändert, aber das root Passwort ist immer noch das alte.", + "root_password_desynchronized": "Das Admin-Passwort wurde verändert, aber das Root-Passwort ist immer noch das alte!", "regenconf_need_to_explicitly_specify_ssh": "Die SSH-Konfiguration wurde manuell modifiziert, aber Sie müssen explizit die Kategorie 'SSH' mit --force spezifizieren, um die Änderungen tatsächlich anzuwenden.", "migration_update_LDAP_schema": "Aktualisiere das LDAP-Schema...", "log_backup_create": "Erstelle ein Backup-Archiv", "diagnosis_sshd_config_inconsistent": "Es sieht aus, als ob der SSH-Port manuell geändert wurde in /etc/ssh/ssh_config. Seit YunoHost 4.2 ist eine neue globale Einstellung 'security.ssh.port' verfügbar um zu verhindern, dass die Konfiguration manuell verändert wird.", - "diagnosis_sshd_config_insecure": "Die SSH-Konfiguration wurde scheinbar manuell abgeändert, und ist unsicher, weil sie keine 'AllowGroups'- oder 'AllowUsers' -Direktiven für die Begrenzung des Zugriffs durch autorisierte Benutzer enthält.", + "diagnosis_sshd_config_insecure": "Die SSH-Konfiguration wurde scheinbar manuell geändert und ist unsicher, weil sie keine 'AllowGroups'- oder 'AllowUsers' -Direktiven für die Beschränkung des Zugriffs durch autorisierte Benutzer enthält.", "backup_create_size_estimation": "Das Archiv wird etwa {size} an Daten enthalten.", - "app_restore_script_failed": "Im Wiederherstellungsskript der Anwendung ist ein Fehler aufgetreten", + "app_restore_script_failed": "Im Wiederherstellungsskript der Applikation ist ein Fehler aufgetreten", "app_restore_failed": "Konnte {app:s} nicht wiederherstellen: {error:s}", "migration_ldap_rollback_success": "System-Rollback erfolgreich.", "migration_ldap_migration_failed_trying_to_rollback": "Migrieren war nicht möglich... Versuch, ein Rollback des Systems durchzuführen.", @@ -604,7 +604,7 @@ "service_description_redis-server": "Eine spezialisierte Datenbank für den schnellen Datenzugriff, die Aufgabenwarteschlange und die Kommunikation zwischen Programmen", "service_description_postfix": "Wird benutzt, um E-Mails zu senden und zu empfangen", "service_description_nginx": "Stellt Daten aller Websiten auf dem Server bereit", - "service_description_mysql": "Apeichert Anwendungsdaten (SQL Datenbank)", + "service_description_mysql": "Speichert die Applikationsdaten (SQL Datenbank)", "service_description_metronome": "XMPP Sofortnachrichtenkonten verwalten", "service_description_yunohost-firewall": "Verwaltet offene und geschlossene Ports zur Verbindung mit Diensten", "service_description_yunohost-api": "Verwaltet die Interaktionen zwischen der Weboberfläche von YunoHost und dem System", @@ -621,12 +621,15 @@ "tools_upgrade_regular_packages": "Momentan werden Upgrades für das System (YunoHost-unabhängige) Pakete durchgeführt…", "tools_upgrade_cant_unhold_critical_packages": "Konnte für die kritischen Pakete das Flag 'hold' nicht aufheben…", "tools_upgrade_cant_hold_critical_packages": "Konnte für die kritischen Pakete das Flag 'hold' nicht setzen…", - "tools_upgrade_cant_both": "Kann das Upgrade für das System und die Anwendungen nicht gleichzeitig durchführen", + "tools_upgrade_cant_both": "Kann das Upgrade für das System und die Applikation nicht gleichzeitig durchführen", "tools_upgrade_at_least_one": "Bitte geben Sie '--apps' oder '--system' an", "this_action_broke_dpkg": "Diese Aktion hat unkonfigurierte Pakete verursacht, welche durch dpkg/apt (die Paketverwaltungen dieses Systems) zurückgelassen wurden... Sie können versuchen dieses Problem zu lösen, indem Sie 'sudo apt install --fix-broken' und/oder 'sudo dpkg --configure -a' ausführen.", "update_apt_cache_failed": "Kann den Cache von APT (Debians Paketmanager) nicht aktualisieren. Hier ist ein Auszug aus den sources.list-Zeilen, die helfen könnten, das Problem zu identifizieren:\n{sourceslist}", "tools_upgrade_special_packages_completed": "YunoHost-Paketupdate beendet.\nDrücke [Enter], um zurück zur Kommandoziele zu kommen", - "tools_upgrade_special_packages_explanation": "Das Upgrade \"special\" wird im Hintergrund ausgeführt. Bitte starte keine anderen Aktionen auf deinem Server für die nächsten ~10 Minuten. Die Dauer ist abhängig von der Schnelligkeit deines Servers. Nach dem Upgrade musst du dich eventuell erneut in das Adminportal einloggen. Upgrade-Logs seid im Adminbereich unter Tools → Log verfügbar. Alternativ kannst du in der Befehlszeile eines Servers \"yunohost log list\" benutzen.", + "tools_upgrade_special_packages_explanation": "Das Upgrade \"special\" wird im Hintergrund ausgeführt. Bitte starten Sie keine anderen Aktionen auf Ihrem Server für die nächsten ~10 Minuten. Die Dauer ist abhängig von der Geschwindigkeit Ihres Servers. Nach dem Upgrade müssen Sie sich eventuell erneut in das Adminportal einloggen. Upgrade-Logs sind im Adminbereich unter Tools → Log verfügbar. Alternativ können Sie in der Befehlszeile 'yunohost log list' eingeben.", "tools_upgrade_special_packages": "\"special\" (YunoHost-bezogene) Pakete werden jetzt aktualisiert…", - "unknown_main_domain_path": "Unbekannte:r Domain oder Pfad für '{app}'. Du musst eine Domain und einen Pfad setzen, um die URL für Berechtigungen zu setzen." + "unknown_main_domain_path": "Unbekannte:r Domain oder Pfad für '{app}'. Du musst eine Domain und einen Pfad setzen, um die URL für Berechtigungen zu setzen.", + "yunohost_postinstall_end_tip": "Post-install ist fertig! Um das Setup abzuschliessen, wird empfohlen:\n - einen ersten Benutzer über den Bereich 'Benutzer*in' im Adminbereich hinzuzufügen (oder mit 'yunohost user create ' in der Kommandezeile);\n - mögliche Fehler zu diagnostizieren über den Bereich 'Diagnose' im Adminbereich (oder mit 'yunohost diagnosis run' in der Kommandozeile;\n - Die Abschnitte 'Install YunoHost' und 'Geführte Tour' im Administratorenhandbuch zu lesen: https://yunohost.org/admindoc.", + "user_already_exists": "Der Benutzer '{user}' ist bereits vorhanden", + "update_apt_cache_warning": "Beim Versuch den Cache für APT (Debians Paketmanager) zu aktualisieren, ist etwas schief gelaufen. Hier ist ein Dump der Zeilen aus sources.list, die Ihnen vielleicht dabei helfen, das Problem zu identifizieren:\n{sourceslist}" } From df7351aeaedbcaf6f3c6e83a8283c7ebfc9c1126 Mon Sep 17 00:00:00 2001 From: ppr Date: Sun, 4 Jul 2021 15:51:48 +0000 Subject: [PATCH 2610/3170] Translated using Weblate (French) Currently translated at 99.0% (627 of 633 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 b48c849d6..7d02ea6c0 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -96,7 +96,7 @@ "port_already_closed": "Le port {port:d} est déjà fermé pour les connexions {ip_version:s}", "port_already_opened": "Le port {port:d} est déjà ouvert pour les connexions {ip_version:s}", "restore_already_installed_app": "Une application est déjà installée avec l’identifiant '{app:s}'", - "app_restore_failed": "Impossible de restaurer {app:s}: {error:s}", + "app_restore_failed": "Impossible de restaurer {app:s} : {error:s}", "restore_cleaning_failed": "Impossible de nettoyer le dossier temporaire de restauration", "restore_complete": "Restauration terminée", "restore_confirm_yunohost_installed": "Voulez-vous vraiment restaurer un système déjà installé ? [{answers:s}]", From f097f0d224e3369ea42e7d0749b8121ed9c69ac1 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Mon, 5 Jul 2021 16:43:39 +0200 Subject: [PATCH 2611/3170] add user in hook exec again --- src/yunohost/hook.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/yunohost/hook.py b/src/yunohost/hook.py index 493ad2c35..05e706660 100644 --- a/src/yunohost/hook.py +++ b/src/yunohost/hook.py @@ -320,7 +320,7 @@ def hook_callback( def hook_exec( - path, args=None, raise_on_error=False, chdir=None, env=None, return_format="json" + path, args=None, raise_on_error=False, chdir=None, env=None, user="root", return_format="json" ): """ Execute hook from a file with arguments @@ -331,6 +331,7 @@ def hook_exec( raise_on_error -- Raise if the script returns a non-zero exit code chdir -- The directory from where the script will be executed env -- Dictionnary of environment variables to export + user -- User with which to run the command """ # Validate hook path @@ -372,7 +373,7 @@ def hook_exec( returncode, returndata = _hook_exec_python(path, args, env, loggers) else: returncode, returndata = _hook_exec_bash( - path, args, chdir, env, return_format, loggers + path, args, chdir, env, user, return_format, loggers ) # Check and return process' return code @@ -388,7 +389,7 @@ def hook_exec( return returncode, returndata -def _hook_exec_bash(path, args, chdir, env, return_format, loggers): +def _hook_exec_bash(path, args, chdir, env, user, return_format, loggers): from moulinette.utils.process import call_async_output @@ -416,17 +417,23 @@ def _hook_exec_bash(path, args, chdir, env, return_format, loggers): f.write("") env["YNH_STDRETURN"] = stdreturn + # Construct command to execute + if user == "root": + command = ['sh', '-c'] + else: + command = ['sudo', '-n', '-u', user, '-H', 'sh', '-c'] + # use xtrace on fd 7 which is redirected to stdout env["BASH_XTRACEFD"] = "7" cmd = '/bin/bash -x "{script}" {args} 7>&1' - cmd = cmd.format(script=cmd_script, args=cmd_args) + command.append(cmd.format(script=cmd_script, args=cmd_args)) - logger.debug("Executing command '%s'" % cmd) + logger.debug("Executing command '%s'" % command) _env = os.environ.copy() _env.update(env) - returncode = call_async_output(cmd, loggers, shell=True, cwd=chdir, env=_env) + returncode = call_async_output(command, loggers, shell=False, cwd=chdir, env=_env) raw_content = None try: From 96e67ef1459ca4f8446206e68bb00ba0b2da543c Mon Sep 17 00:00:00 2001 From: Kay0u Date: Tue, 6 Jul 2021 12:14:46 +0200 Subject: [PATCH 2612/3170] monkey patch get_setting_description --- src/yunohost/settings.py | 13 ++++++------- src/yunohost/tests/test_settings.py | 9 +++++++++ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/yunohost/settings.py b/src/yunohost/settings.py index 0466d8126..28f5bc687 100644 --- a/src/yunohost/settings.py +++ b/src/yunohost/settings.py @@ -265,19 +265,18 @@ def settings_reset_all(): } +def _get_setting_description(key): + return m18n.n("global_settings_setting_%s" % key.replace(".", "_")) + + def _get_settings(): - def get_setting_description(key): - if key.startswith("example"): - # (This is for dummy stuff used during unit tests) - return "Dummy %s setting" % key.split(".")[-1] - return m18n.n("global_settings_setting_%s" % key.replace(".", "_")) settings = {} for key, value in DEFAULTS.copy().items(): settings[key] = value settings[key]["value"] = value["default"] - settings[key]["description"] = get_setting_description(key) + settings[key]["description"] = _get_setting_description(key) if not os.path.exists(SETTINGS_PATH): return settings @@ -306,7 +305,7 @@ def _get_settings(): for key, value in local_settings.items(): if key in settings: settings[key] = value - settings[key]["description"] = get_setting_description(key) + settings[key]["description"] = _get_setting_description(key) else: logger.warning( m18n.n( diff --git a/src/yunohost/tests/test_settings.py b/src/yunohost/tests/test_settings.py index 47f8efdf4..1a9063e56 100644 --- a/src/yunohost/tests/test_settings.py +++ b/src/yunohost/tests/test_settings.py @@ -5,6 +5,8 @@ import pytest from yunohost.utils.error import YunohostError +import yunohost.settings as settings + from yunohost.settings import ( settings_get, settings_list, @@ -33,6 +35,13 @@ def teardown_function(function): os.remove(filename) +def monkey_get_setting_description(key): + return "Dummy %s setting" % key.split(".")[-1] + + +settings._get_setting_description = monkey_get_setting_description + + def test_settings_get_bool(): assert settings_get("example.bool") From 75ab54ee1478560fee0587b3eb27177f76db22cf Mon Sep 17 00:00:00 2001 From: Le Libre Au Quotidien Date: Wed, 7 Jul 2021 19:36:58 +0200 Subject: [PATCH 2613/3170] Improve disk space left verification --- locales/en.json | 2 ++ locales/fr.json | 2 ++ src/yunohost/app.py | 9 +++++++++ 3 files changed, 13 insertions(+) diff --git a/locales/en.json b/locales/en.json index 84a01dfaa..cfb9826c2 100644 --- a/locales/en.json +++ b/locales/en.json @@ -273,6 +273,8 @@ "diagnosis_sshd_config_insecure": "The SSH configuration appears to have been manually modified, and is insecure because it contains no 'AllowGroups' or 'AllowUsers' directive to limit access to authorized users.", "diagnosis_sshd_config_inconsistent": "It looks like the SSH port was manually modified in /etc/ssh/sshd_config. Since YunoHost 4.2, a new global setting 'security.ssh.port' is available to avoid manually editing the configuration.", "diagnosis_sshd_config_inconsistent_details": "Please run yunohost settings set security.ssh.port -v YOUR_SSH_PORT to define the SSH port, and check yunohost tools regen-conf ssh --dry-run --with-diff and yunohost tools regen-conf ssh --force to reset your conf to the YunoHost recommendation.", + "disk_space_not_sufficient_install": "There is not enough disk space left to install this application", + "disk_space_not_sufficient_update": "There is not enough disk space left to update this application", "domain_cannot_remove_main": "You cannot remove '{domain:s}' since it's the main domain, you first need to set another domain as the main domain using 'yunohost domain main-domain -n '; here is the list of candidate domains: {other_domains:s}", "domain_cannot_add_xmpp_upload": "You cannot add domains starting with 'xmpp-upload.'. This kind of name is reserved for the XMPP upload feature integrated in YunoHost.", "domain_cannot_remove_main_add_new_one": "You cannot remove '{domain:s}' since it's the main domain and your only domain, you need to first add another domain using 'yunohost domain add ', then set is as the main domain using 'yunohost domain main-domain -n ' and then you can remove the domain '{domain:s}' using 'yunohost domain remove {domain:s}'.'", diff --git a/locales/fr.json b/locales/fr.json index 79ae8e6e7..a6dda3efd 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -44,6 +44,8 @@ "backup_output_directory_required": "Vous devez spécifier un dossier de destination pour la sauvegarde", "backup_running_hooks": "Exécution des scripts de sauvegarde...", "custom_app_url_required": "Vous devez spécifier une URL pour mettre à jour votre application personnalisée {app:s}", + "disk_space_not_sufficient_install": "Il ne reste pas assez d'espace disque pour installer cette application", + "disk_space_not_sufficient_update": "Il ne reste pas assez d'espace disque pour mettre à jour cette application", "domain_cert_gen_failed": "Impossible de générer le certificat", "domain_created": "Le domaine a été créé", "domain_creation_failed": "Impossible de créer le domaine {domain} : {error}", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 5f001c12a..b3fea9a32 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -517,6 +517,10 @@ def app_upgrade(app=[], url=None, file=None, force=False): from yunohost.regenconf import manually_modified_files apps = app + # Check if disk space available + size = os.statvfs('/') + if (size.f_bavail * size.f_frsize) / 1024 <= 512000: + raise YunohostValidationError("disk_space_not_sufficient_update") # If no app is specified, upgrade all apps if not apps: # FIXME : not sure what's supposed to happen if there is a url and a file but no apps... @@ -875,6 +879,11 @@ def app_install( manifest, extracted_app_folder = _extract_app_from_file(app) else: raise YunohostValidationError("app_unknown") + + # Check if disk space available + size = os.statvfs('/') + if (size.f_bavail * size.f_frsize) / 1024 <= 512000: + raise YunohostValidationError("disk_space_not_sufficient_install") # Check ID if "id" not in manifest or "__" in manifest["id"]: From 10287234549eb2b5e25d5d97f49f2f1ea5e38082 Mon Sep 17 00:00:00 2001 From: Christian Wehrli Date: Wed, 7 Jul 2021 20:54:55 +0000 Subject: [PATCH 2614/3170] Translated using Weblate (German) Currently translated at 98.2% (622 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/locales/de.json b/locales/de.json index b3817707d..83647ec17 100644 --- a/locales/de.json +++ b/locales/de.json @@ -122,8 +122,8 @@ "upgrade_complete": "Upgrade vollständig", "upgrading_packages": "Pakete werden aktualisiert…", "upnp_dev_not_found": "Es konnten keine UPnP Geräte gefunden werden", - "upnp_disabled": "UPnP wurde deaktiviert", - "upnp_enabled": "UPnP wurde aktiviert", + "upnp_disabled": "UPnP deaktiviert", + "upnp_enabled": "UPnP aktiviert", "upnp_port_open_failed": "UPnP Ports konnten nicht geöffnet werden", "user_created": "Der Benutzer wurde erstellt", "user_creation_failed": "Nutzer konnte nicht erstellt werden", @@ -189,7 +189,7 @@ "dyndns_domain_not_provided": "Der DynDNS-Anbieter {provider:s} kann die Domäne(n) {domain:s} nicht bereitstellen.", "dyndns_could_not_check_available": "Konnte nicht überprüfen, ob {domain:s} auf {provider:s} verfügbar ist.", "dyndns_could_not_check_provide": "Konnte nicht überprüft, ob {provider:s} die Domain(s) {domain:s} bereitstellen kann.", - "domain_dns_conf_is_just_a_recommendation": "Dieser Befehl zeigt Ihnen die * empfohlene * Konfiguration. Die DNS-Konfiguration wird NICHT für Sie eingerichtet. Es liegt in Ihrer Verantwortung, Ihre DNS-Zone in Ihrem Registrar gemäß dieser Empfehlung zu konfigurieren.", + "domain_dns_conf_is_just_a_recommendation": "Dieser Befehl zeigt Ihnen die *empfohlene* Konfiguration. Die DNS-Konfiguration wird NICHT für Sie eingerichtet. Es liegt in Ihrer Verantwortung, Ihre DNS-Zone in Ihrem Registrar gemäß dieser Empfehlung zu konfigurieren.", "dpkg_lock_not_available": "Dieser Befehl kann momentan nicht ausgeführt werden, da anscheinend ein anderes Programm die Sperre von dpkg (dem Systempaket-Manager) verwendet", "confirm_app_install_thirdparty": "WARNUNG! Diese Applikation ist nicht Teil von YunoHosts Applikations-Katalog. Das Installieren von Drittanbieterapplikationen könnte die Sicherheit und Integrität des System beeinträchtigen. Sie sollten wahrscheinlich NICHT fortfahren, es sei denn, Sie wissen, was Sie tun. Es wird KEIN SUPPORT zur Verfügung stehen, wenn die Applikation nicht funktioniert oder das System zerstört... Wenn Sie das Risiko trotzdem eingehen möchten, tippen Sie '{answers:s}'", "confirm_app_install_danger": "WARNUNG! Diese Applikation ist noch experimentell (wenn nicht ausdrücklich \"not working\"/\"nicht funktionsfähig\")! Sie sollten sie wahrscheinlich NICHT installieren, es sei denn, Sie wißen, was Sie tun. Es wird keine Unterstützung geleistet, falls diese Applikation nicht funktioniert oder Ihr System zerstört... Falls Sie bereit bist, dieses Risiko einzugehen, tippe '{answers:s}'", @@ -297,7 +297,7 @@ "diagnosis_cant_run_because_of_dep": "Kann Diagnose für {category} nicht ausführen während wichtige Probleme zu {dep} noch nicht behoben sind.", "diagnosis_found_errors_and_warnings": "Habe {errors} erhebliche(s) Problem(e) (und {warnings} Warnung(en)) in Verbindung mit {category} gefunden!", "diagnosis_ip_broken_dnsresolution": "Domänen-Namens-Auflösung scheint aus einem bestimmten Grund nicht zu funktionieren... Blockiert eine Firewall die DNS Anfragen?", - "diagnosis_ip_broken_resolvconf": "Domänen-Namens-Auflösung scheint nicht zu funktionieren, was daran liegen könnte, dass in /etc/resolv.conf kein Eintrag auf 127.0.0.1 zeigt.", + "diagnosis_ip_broken_resolvconf": "Domänen-Namensauflösung scheint nicht zu funktionieren, was daran liegen könnte, dass in /etc/resolv.conf kein Eintrag auf 127.0.0.1 zeigt.", "diagnosis_ip_weird_resolvconf_details": "Die Datei /etc/resolv.conf muss ein Symlink auf /etc/resolvconf/run/resolv.conf sein, welcher auf 127.0.0.1 (dnsmasq) zeigt. Falls Sie die DNS-Resolver manuell konfigurieren möchten, bearbeiten Sie bitte /etc/resolv.dnsmasq.conf.", "diagnosis_dns_good_conf": "Die DNS-Einträge für die Domäne {domain} (Kategorie {category}) sind korrekt konfiguriert", "diagnosis_ignored_issues": "(+ {nb_ignored} ignorierte(s) Problem(e))", @@ -600,7 +600,7 @@ "service_description_avahi-daemon": "Erlaubt, den Server im lokalen Netz über 'yunohost.local' zu erreichen", "restore_backup_too_old": "Dieses Backup kann nicht wieder hergestellt werden, weil es von einer zu alten YunoHost Version stammt.", "service_description_slapd": "Speichert Benutzer, Domains und verbundene Informationen", - "service_description_rspamd": "Spamfilter und andere E-Mail Merkmale", + "service_description_rspamd": "Spamfilter und andere E-Mail-Merkmale", "service_description_redis-server": "Eine spezialisierte Datenbank für den schnellen Datenzugriff, die Aufgabenwarteschlange und die Kommunikation zwischen Programmen", "service_description_postfix": "Wird benutzt, um E-Mails zu senden und zu empfangen", "service_description_nginx": "Stellt Daten aller Websiten auf dem Server bereit", From ec736a9e11d8f8ceb22878c261665dcc33db85e1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 8 Jul 2021 18:48:26 +0200 Subject: [PATCH 2615/3170] Misc french translation tweaks --- locales/fr.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 7d02ea6c0..f06acf2e5 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -569,7 +569,7 @@ "diagnosis_dns_try_dyndns_update_force": "La configuration DNS de ce domaine devrait être automatiquement gérée par YunoHost. Si ce n'est pas le cas, vous pouvez essayer de forcer une mise à jour en utilisant yunohost dyndns update --force.", "app_packaging_format_not_supported": "Cette application ne peut pas être installée car son format n'est pas pris en charge par votre version de YunoHost. Vous devriez probablement envisager de mettre à jour votre système.", "migration_0015_weak_certs": "Il a été constaté que les certificats suivants utilisent encore des algorithmes de signature peu robustes et doivent être mis à jour pour être compatibles avec la prochaine version de NGINX : {certs}", - "global_settings_setting_backup_compress_tar_archives": "Lors de la création de nouvelles sauvegardes, compressez les archives au format '.tar.gz' plutôt que des archives non-compressées au format '.tar'. Notez qu'activer cette option permet d'obtenir des sauvegardes plus légères, mais leur création sera significativement plus longue et plus gourmande en CPU.", + "global_settings_setting_backup_compress_tar_archives": "Lors de la création de nouvelles sauvegardes, compresse les archives au format '.tar.gz' plutôt que des archives non-compressées au format '.tar'. Notez qu'activer cette option permet d'obtenir des sauvegardes plus légères, mais leur création sera significativement plus longue et plus gourmande en CPU.", "migration_description_0018_xtable_to_nftable": "Migrer les anciennes règles de trafic réseau vers le nouveau système basé sur nftables", "service_description_php7.3-fpm": "Exécute les applications écrites en PHP avec NGINX", "migration_0018_failed_to_reset_legacy_rules": "La réinitialisation des règles iptable par défaut a échoué : {error}", @@ -591,7 +591,7 @@ "global_settings_setting_smtp_relay_host": "Un relais SMTP permet d'envoyer du courrier à la place de cette instance YunoHost. Cela est utile si vous êtes dans l'une de ces situations : le port 25 est bloqué par votre FAI ou par votre fournisseur VPS, vous avez une IP résidentielle répertoriée sur DUHL, vous ne pouvez pas configurer de reverse DNS, si le serveur n'est pas directement accessible depuis Internet et que vous voulez en utiliser un autre pour envoyer des mails.", "diagnosis_package_installed_from_sury_details": "Certains paquets ont été installés par inadvertance à partir d'un dépôt tiers appelé Sury. L'équipe YunoHost a amélioré la stratégie de gestion de ces paquets, mais on s'attend à ce que certaines configurations qui ont installé des applications PHP7.3 tout en étant toujours sur Stretch présentent des incohérences. Pour résoudre cette situation, vous devez essayer d'exécuter la commande suivante : {cmd_to_fix}", "app_argument_password_no_default": "Erreur lors de l'analyse de l'argument de mot de passe '{name}' : l'argument de mot de passe ne peut pas avoir de valeur par défaut pour des raisons de sécurité", - "pattern_email_forward": "L'adresse électronique doit être valide, le symbole '+' étant accepté (par exemples : johndoe+yunohost@exemple.com)", + "pattern_email_forward": "L'adresse électronique doit être valide, le symbole '+' étant accepté (par exemple : johndoe+yunohost@exemple.com)", "global_settings_setting_smtp_relay_password": "Mot de passe du relais de l'hôte SMTP", "diagnosis_package_installed_from_sury": "Des paquets du système devraient être rétrogradé de version", "additional_urls_already_added": "URL supplémentaire '{url:s}' déjà ajoutée pour la permission '{permission:s}'", @@ -617,7 +617,7 @@ "diagnosis_rootfstotalspace_critical": "Le système de fichiers racine ne fait que {space} ! Vous allez certainement le remplir très rapidement ! Il est recommandé d'avoir au moins 16 GB pour ce système de fichiers.", "diagnosis_rootfstotalspace_warning": "Le système de fichiers racine n'est que de {space}. Cela peut suffire, mais faites attention car vous risquez de les remplir rapidement... Il est recommandé d'avoir au moins 16 GB pour ce système de fichiers.", "app_restore_script_failed": "Une erreur s'est produite dans le script de restauration de l'application", - "restore_backup_too_old": "Cette sauvegarde ne peut être restaurée car le fichier de l'archive provient d'une version trop ancienne de YunoHost.", + "restore_backup_too_old": "Cette sauvegarde ne peut être restaurée car elle provient d'une version trop ancienne de YunoHost.", "migration_update_LDAP_schema": "Mise à jour du schéma LDAP ...", "log_backup_create": "Créer une archive de sauvegarde", "global_settings_setting_ssowat_panel_overlay_enabled": "Activer la surcouche SSOwat au panel web", From 5e2478d3096a480c0f8190b02be5c7010c104ea9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 9 Jul 2021 20:43:52 +0200 Subject: [PATCH 2616/3170] Propagate change from the moulinette (no more msignals madness) --- data/actionsmap/yunohost.yml | 1 + src/yunohost/app.py | 8 ++++---- src/yunohost/backup.py | 6 +++--- src/yunohost/domain.py | 4 ++-- src/yunohost/tools.py | 6 +++--- src/yunohost/user.py | 10 +++++----- 6 files changed, 18 insertions(+), 17 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 604034019..d2c4f8470 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -33,6 +33,7 @@ # Global parameters # ############################# _global: + name: yunohost.admin authentication: api: ldap_admin cli: null diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 5f001c12a..ac2b95cac 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -36,7 +36,7 @@ import urllib.parse import tempfile from collections import OrderedDict -from moulinette import msignals, m18n, msettings +from moulinette import prompt, m18n, msettings from moulinette.core import MoulinetteError from moulinette.utils.log import getActionLogger from moulinette.utils.network import download_json @@ -825,7 +825,7 @@ def app_install( return if confirm in ["danger", "thirdparty"]: - answer = msignals.prompt( + answer = prompt( m18n.n("confirm_app_install_" + confirm, answers="Yes, I understand"), color="red", ) @@ -833,7 +833,7 @@ def app_install( raise YunohostError("aborting") else: - answer = msignals.prompt( + answer = prompt( m18n.n("confirm_app_install_" + confirm, answers="Y/N"), color="yellow" ) if answer.upper() != "Y": @@ -2729,7 +2729,7 @@ class YunoHostArgumentFormatParser(object): ) try: - question.value = msignals.prompt( + question.value = prompt( text_for_user_input_in_cli, self.hide_user_input_in_prompt ) except NotImplementedError: diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 99337b2f8..b76d12aba 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -38,7 +38,7 @@ from collections import OrderedDict from functools import reduce from packaging import version -from moulinette import msignals, m18n, msettings +from moulinette import prompt, m18n, msettings from moulinette.utils import filesystem from moulinette.utils.log import getActionLogger from moulinette.utils.filesystem import read_file, mkdir, write_to_yaml, read_yaml @@ -1839,7 +1839,7 @@ class BackupMethod(object): # Ask confirmation for copying if size > MB_ALLOWED_TO_ORGANIZE: try: - i = msignals.prompt( + i = prompt( m18n.n( "backup_ask_for_copying_if_needed", answers="y/N", @@ -2343,7 +2343,7 @@ def backup_restore(name, system=[], apps=[], force=False): if not force: try: # Ask confirmation for restoring - i = msignals.prompt( + i = prompt( m18n.n("restore_confirm_yunohost_installed", answers="y/N") ) except NotImplemented: diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index aaac3a995..c466e14ee 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -26,7 +26,7 @@ import os import re -from moulinette import m18n, msettings, msignals +from moulinette import m18n, msettings, prompt from moulinette.core import MoulinetteError from yunohost.utils.error import YunohostError, YunohostValidationError from moulinette.utils.log import getActionLogger @@ -237,7 +237,7 @@ def domain_remove(operation_logger, domain, remove_apps=False, force=False): if apps_on_that_domain: if remove_apps: if msettings.get("interface") == "cli" and not force: - answer = msignals.prompt( + answer = prompt( m18n.n( "domain_remove_confirm_apps_removal", apps="\n".join([x[1] for x in apps_on_that_domain]), diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 1cd197d70..4c5861d17 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -30,7 +30,7 @@ import time from importlib import import_module from packaging import version -from moulinette import msignals, m18n +from moulinette import prompt, m18n from moulinette.utils.log import getActionLogger from moulinette.utils.process import check_output, call_async_output from moulinette.utils.filesystem import read_yaml, write_to_yaml @@ -692,7 +692,7 @@ def tools_shutdown(operation_logger, force=False): if not shutdown: try: # Ask confirmation for server shutdown - i = msignals.prompt(m18n.n("server_shutdown_confirm", answers="y/N")) + i = prompt(m18n.n("server_shutdown_confirm", answers="y/N")) except NotImplemented: pass else: @@ -711,7 +711,7 @@ def tools_reboot(operation_logger, force=False): if not reboot: try: # Ask confirmation for restoring - i = msignals.prompt(m18n.n("server_reboot_confirm", answers="y/N")) + i = prompt(m18n.n("server_reboot_confirm", answers="y/N")) except NotImplemented: pass else: diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 266c2774c..0a624c4b3 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -33,7 +33,7 @@ import string import subprocess import copy -from moulinette import msignals, msettings, m18n +from moulinette import prompt, display, msettings, m18n from moulinette.utils.log import getActionLogger from moulinette.utils.process import check_output @@ -123,12 +123,12 @@ def user_create( ) else: # On affiche les differents domaines possibles - msignals.display(m18n.n("domains_available")) + display(m18n.n("domains_available")) for domain in domain_list()["domains"]: - msignals.display("- {}".format(domain)) + display("- {}".format(domain)) maindomain = _get_maindomain() - domain = msignals.prompt( + domain = prompt( m18n.n("ask_user_domain") + " (default: %s)" % maindomain ) if not domain: @@ -380,7 +380,7 @@ def user_update( # without a specified value, change_password will be set to the const 0. # In this case we prompt for the new password. if msettings.get("interface") == "cli" and not change_password: - change_password = msignals.prompt(m18n.n("ask_password"), True, True) + change_password = prompt(m18n.n("ask_password"), True, True) # Ensure sufficiently complex password assert_password_is_strong_enough("user", change_password) From a2009d6a9a792a3c2a89eaeaf9a1751b18ea0f6a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 9 Jul 2021 21:49:12 +0200 Subject: [PATCH 2617/3170] Propagate changes from moulinette : get rid of msettings, and prompt/display are also wrapped in a 'Moulinette' object --- src/yunohost/app.py | 14 +++++++------- src/yunohost/backup.py | 10 +++++----- src/yunohost/diagnosis.py | 8 ++++---- src/yunohost/domain.py | 8 ++++---- src/yunohost/hook.py | 4 ++-- src/yunohost/log.py | 11 +++++------ src/yunohost/tests/conftest.py | 6 ++++-- src/yunohost/tools.py | 6 +++--- src/yunohost/user.py | 14 +++++++------- 9 files changed, 41 insertions(+), 40 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index ac2b95cac..fc890e055 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -36,7 +36,7 @@ import urllib.parse import tempfile from collections import OrderedDict -from moulinette import prompt, m18n, msettings +from moulinette import Moulinette, m18n from moulinette.core import MoulinetteError from moulinette.utils.log import getActionLogger from moulinette.utils.network import download_json @@ -643,7 +643,7 @@ def app_upgrade(app=[], url=None, file=None, force=False): m18n.n("app_upgrade_failed", app=app_instance_name, error=error) ) failure_message_with_debug_instructions = operation_logger.error(error) - if msettings.get("interface") != "api": + if Moulinette.interface.type != "api": dump_app_log_extract_for_debugging(operation_logger) # Script got manually interrupted ... N.B. : KeyboardInterrupt does not inherit from Exception except (KeyboardInterrupt, EOFError): @@ -821,11 +821,11 @@ def app_install( def confirm_install(confirm): # Ignore if there's nothing for confirm (good quality app), if --force is used # or if request on the API (confirm already implemented on the API side) - if confirm is None or force or msettings.get("interface") == "api": + if confirm is None or force or Moulinette.interface.type == "api": return if confirm in ["danger", "thirdparty"]: - answer = prompt( + answer = Moulinette.prompt( m18n.n("confirm_app_install_" + confirm, answers="Yes, I understand"), color="red", ) @@ -833,7 +833,7 @@ def app_install( raise YunohostError("aborting") else: - answer = prompt( + answer = Moulinette.prompt( m18n.n("confirm_app_install_" + confirm, answers="Y/N"), color="yellow" ) if answer.upper() != "Y": @@ -1005,7 +1005,7 @@ def app_install( error = m18n.n("app_install_script_failed") logger.error(m18n.n("app_install_failed", app=app_id, error=error)) failure_message_with_debug_instructions = operation_logger.error(error) - if msettings.get("interface") != "api": + if Moulinette.interface.type != "api": dump_app_log_extract_for_debugging(operation_logger) # Script got manually interrupted ... N.B. : KeyboardInterrupt does not inherit from Exception except (KeyboardInterrupt, EOFError): @@ -2729,7 +2729,7 @@ class YunoHostArgumentFormatParser(object): ) try: - question.value = prompt( + question.value = Moulinette.prompt( text_for_user_input_in_cli, self.hide_user_input_in_prompt ) except NotImplementedError: diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index b76d12aba..2fe32ad7e 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -38,7 +38,7 @@ from collections import OrderedDict from functools import reduce from packaging import version -from moulinette import prompt, m18n, msettings +from moulinette import Moulinette, m18n from moulinette.utils import filesystem from moulinette.utils.log import getActionLogger from moulinette.utils.filesystem import read_file, mkdir, write_to_yaml, read_yaml @@ -1508,7 +1508,7 @@ class RestoreManager: m18n.n("app_restore_failed", app=app_instance_name, error=error) ) failure_message_with_debug_instructions = operation_logger.error(error) - if msettings.get("interface") != "api": + if Moulinette.interface.type != "api": dump_app_log_extract_for_debugging(operation_logger) # Script got manually interrupted ... N.B. : KeyboardInterrupt does not inherit from Exception except (KeyboardInterrupt, EOFError): @@ -1839,7 +1839,7 @@ class BackupMethod(object): # Ask confirmation for copying if size > MB_ALLOWED_TO_ORGANIZE: try: - i = prompt( + i = Moulinette.prompt( m18n.n( "backup_ask_for_copying_if_needed", answers="y/N", @@ -2343,7 +2343,7 @@ def backup_restore(name, system=[], apps=[], force=False): if not force: try: # Ask confirmation for restoring - i = prompt( + i = Moulinette.prompt( m18n.n("restore_confirm_yunohost_installed", answers="y/N") ) except NotImplemented: @@ -2417,7 +2417,7 @@ def backup_list(with_info=False, human_readable=False): def backup_download(name): - if msettings.get("interface") != "api": + if Moulinette.interface.type != "api": logger.error( "This option is only meant for the API/webadmin and doesn't make sense for the command line." ) diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index ff1a14c4e..4ac5e2731 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -28,7 +28,7 @@ import re import os import time -from moulinette import m18n, msettings +from moulinette import m18n, Moulinette from moulinette.utils import log from moulinette.utils.filesystem import ( read_json, @@ -138,7 +138,7 @@ def diagnosis_show( url = yunopaste(content) logger.info(m18n.n("log_available_on_yunopaste", url=url)) - if msettings.get("interface") == "api": + if Moulinette.interface.type == "api": return {"url": url} else: return @@ -219,7 +219,7 @@ def diagnosis_run( if email: _email_diagnosis_issues() - if issues and msettings.get("interface") == "cli": + if issues and Moulinette.interface.type == "cli": logger.warning(m18n.n("diagnosis_display_tip")) @@ -595,7 +595,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" or force_remove_html_tags: + if Moulinette.interface.type != "api" or force_remove_html_tags: s = s.replace("", "'").replace("", "'") s = html_tags.sub("", s.replace("
", "\n")) else: diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index c466e14ee..b431e9b18 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -26,7 +26,7 @@ import os import re -from moulinette import m18n, msettings, prompt +from moulinette import m18n, Moulinette from moulinette.core import MoulinetteError from yunohost.utils.error import YunohostError, YunohostValidationError from moulinette.utils.log import getActionLogger @@ -236,8 +236,8 @@ def domain_remove(operation_logger, domain, remove_apps=False, force=False): if apps_on_that_domain: if remove_apps: - if msettings.get("interface") == "cli" and not force: - answer = prompt( + if Moulinette.interface.type == "cli" and not force: + answer = Moulinette.prompt( m18n.n( "domain_remove_confirm_apps_removal", apps="\n".join([x[1] for x in apps_on_that_domain]), @@ -343,7 +343,7 @@ def domain_dns_conf(domain, ttl=None): for record in record_list: result += "\n{name} {ttl} IN {type} {value}".format(**record) - if msettings.get("interface") == "cli": + if Moulinette.interface.type == "cli": logger.info(m18n.n("domain_dns_conf_is_just_a_recommendation")) return result diff --git a/src/yunohost/hook.py b/src/yunohost/hook.py index 493ad2c35..e46a43d35 100644 --- a/src/yunohost/hook.py +++ b/src/yunohost/hook.py @@ -31,7 +31,7 @@ import mimetypes from glob import iglob from importlib import import_module -from moulinette import m18n, msettings +from moulinette import m18n, Moulinette from yunohost.utils.error import YunohostError, YunohostValidationError from moulinette.utils import log from moulinette.utils.filesystem import read_json @@ -409,7 +409,7 @@ def _hook_exec_bash(path, args, chdir, env, return_format, loggers): env = {} env["YNH_CWD"] = chdir - env["YNH_INTERFACE"] = msettings.get("interface") + env["YNH_INTERFACE"] = Moulinette.interface.type stdreturn = os.path.join(tempfile.mkdtemp(), "stdreturn") with open(stdreturn, "w") as f: diff --git a/src/yunohost/log.py b/src/yunohost/log.py index d36671ce2..f6c19eebc 100644 --- a/src/yunohost/log.py +++ b/src/yunohost/log.py @@ -33,7 +33,7 @@ import psutil from datetime import datetime, timedelta from logging import FileHandler, getLogger, Formatter -from moulinette import m18n, msettings +from moulinette import m18n, Moulinette from moulinette.core import MoulinetteError from yunohost.utils.error import YunohostError, YunohostValidationError from yunohost.utils.packages import get_ynh_package_version @@ -44,7 +44,6 @@ CATEGORIES_PATH = "/var/log/yunohost/categories/" OPERATIONS_PATH = "/var/log/yunohost/categories/operation/" METADATA_FILE_EXT = ".yml" LOG_FILE_EXT = ".log" -RELATED_CATEGORIES = ["app", "domain", "group", "service", "user"] logger = getActionLogger("yunohost.log") @@ -125,7 +124,7 @@ def log_list(limit=None, with_details=False, with_suboperations=False): operations = list(reversed(sorted(operations, key=lambda o: o["name"]))) # Reverse the order of log when in cli, more comfortable to read (avoid # unecessary scrolling) - is_api = msettings.get("interface") == "api" + is_api = Moulinette.interface.type == "api" if not is_api: operations = list(reversed(operations)) @@ -214,7 +213,7 @@ def log_show( url = yunopaste(content) logger.info(m18n.n("log_available_on_yunopaste", url=url)) - if msettings.get("interface") == "api": + if Moulinette.interface.type == "api": return {"url": url} else: return @@ -609,7 +608,7 @@ class OperationLogger(object): "operation": self.operation, "parent": self.parent, "yunohost_version": get_ynh_package_version("yunohost")["version"], - "interface": msettings.get("interface"), + "interface": Moulinette.interface.type, } if self.related_to is not None: data["related_to"] = self.related_to @@ -663,7 +662,7 @@ class OperationLogger(object): self.logger.removeHandler(self.file_handler) self.file_handler.close() - is_api = msettings.get("interface") == "api" + is_api = Moulinette.interface.type == "api" desc = _get_description_from_name(self.name) if error is None: if is_api: diff --git a/src/yunohost/tests/conftest.py b/src/yunohost/tests/conftest.py index 49f87decf..1bf035748 100644 --- a/src/yunohost/tests/conftest.py +++ b/src/yunohost/tests/conftest.py @@ -3,7 +3,7 @@ import pytest import sys import moulinette -from moulinette import m18n, msettings +from moulinette import m18n, Moulinette from yunohost.utils.error import YunohostError from contextlib import contextmanager @@ -81,4 +81,6 @@ def pytest_cmdline_main(config): import yunohost yunohost.init(debug=config.option.yunodebug) - msettings["interface"] = "test" + class DummyInterface(): + type = "test" + Moulinette._interface = DummyInterface() diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 4c5861d17..4190e7614 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -30,7 +30,7 @@ import time from importlib import import_module from packaging import version -from moulinette import prompt, m18n +from moulinette import Moulinette, m18n from moulinette.utils.log import getActionLogger from moulinette.utils.process import check_output, call_async_output from moulinette.utils.filesystem import read_yaml, write_to_yaml @@ -692,7 +692,7 @@ def tools_shutdown(operation_logger, force=False): if not shutdown: try: # Ask confirmation for server shutdown - i = prompt(m18n.n("server_shutdown_confirm", answers="y/N")) + i = Moulinette.prompt(m18n.n("server_shutdown_confirm", answers="y/N")) except NotImplemented: pass else: @@ -711,7 +711,7 @@ def tools_reboot(operation_logger, force=False): if not reboot: try: # Ask confirmation for restoring - i = prompt(m18n.n("server_reboot_confirm", answers="y/N")) + i = Moulinette.prompt(m18n.n("server_reboot_confirm", answers="y/N")) except NotImplemented: pass else: diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 0a624c4b3..01513f3bd 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -33,7 +33,7 @@ import string import subprocess import copy -from moulinette import prompt, display, msettings, m18n +from moulinette import Moulinette, m18n from moulinette.utils.log import getActionLogger from moulinette.utils.process import check_output @@ -117,18 +117,18 @@ def user_create( # Validate domain used for email address/xmpp account if domain is None: - if msettings.get("interface") == "api": + if Moulinette.interface.type == "api": raise YunohostValidationError( "Invalid usage, you should specify a domain argument" ) else: # On affiche les differents domaines possibles - display(m18n.n("domains_available")) + Moulinette.display(m18n.n("domains_available")) for domain in domain_list()["domains"]: - display("- {}".format(domain)) + Moulinette.display("- {}".format(domain)) maindomain = _get_maindomain() - domain = prompt( + domain = Moulinette.prompt( m18n.n("ask_user_domain") + " (default: %s)" % maindomain ) if not domain: @@ -379,8 +379,8 @@ def user_update( # when in the cli interface if the option to change the password is called # without a specified value, change_password will be set to the const 0. # In this case we prompt for the new password. - if msettings.get("interface") == "cli" and not change_password: - change_password = prompt(m18n.n("ask_password"), True, True) + if Moulinette.interface.type == "cli" and not change_password: + change_password = Moulinette.prompt(m18n.n("ask_password"), True, True) # Ensure sufficiently complex password assert_password_is_strong_enough("user", change_password) From 4e4173d1b65ed55e797f898e97036af0c25f1245 Mon Sep 17 00:00:00 2001 From: Kayou Date: Sat, 10 Jul 2021 02:47:11 +0200 Subject: [PATCH 2618/3170] remove-stale-translated-strings on for the default branch --- .gitlab/ci/translation.gitlab-ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitlab/ci/translation.gitlab-ci.yml b/.gitlab/ci/translation.gitlab-ci.yml index edab611df..eef57ca22 100644 --- a/.gitlab/ci/translation.gitlab-ci.yml +++ b/.gitlab/ci/translation.gitlab-ci.yml @@ -21,5 +21,7 @@ remove-stale-translated-strings: - git push -f origin "ci-remove-stale-translated-strings-${CI_COMMIT_REF_NAME}":"ci-remove-stale-translated-strings-${CI_COMMIT_REF_NAME}" - hub pull-request -m "[CI] Remove stale translated strings" -b Yunohost:dev -p || true # GITHUB_USER and GITHUB_TOKEN registered here https://gitlab.com/yunohost/yunohost/-/settings/ci_cd only: + variables: + - $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH changes: - locales/* From 1c15f644f57df6deb9205d65a437eafd77c40f6e Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Sat, 10 Jul 2021 18:18:51 +0200 Subject: [PATCH 2619/3170] [fix] Invalid HTML in yunohost_panel #1837 --- data/templates/nginx/plain/yunohost_panel.conf.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/templates/nginx/plain/yunohost_panel.conf.inc b/data/templates/nginx/plain/yunohost_panel.conf.inc index 53a69d705..16a6e6b29 100644 --- a/data/templates/nginx/plain/yunohost_panel.conf.inc +++ b/data/templates/nginx/plain/yunohost_panel.conf.inc @@ -1,5 +1,5 @@ # Insert YunoHost button + portal overlay -sub_filter ''; +sub_filter ''; sub_filter_once on; # Apply to other mime types than text/html sub_filter_types application/xhtml+xml; From cc3c9dfcd7d0287ca345505f13f079672f82c519 Mon Sep 17 00:00:00 2001 From: Tagada <36127788+Tagadda@users.noreply.github.com> Date: Sat, 10 Jul 2021 23:17:58 +0200 Subject: [PATCH 2620/3170] [fix] set .ssh folder permissions to 600 Fix YunoHost/issues#1770 --- src/yunohost/ssh.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/yunohost/ssh.py b/src/yunohost/ssh.py index caac00050..ecee39f4a 100644 --- a/src/yunohost/ssh.py +++ b/src/yunohost/ssh.py @@ -64,6 +64,7 @@ def user_ssh_add_key(username, key, comment): parents=True, uid=user["uid"][0], ) + chmod(os.path.join(user["homeDirectory"][0], ".ssh"), 0o600) # create empty file to set good permissions write_to_file(authorized_keys_file, "") From 7c92b44ddb19b3c712f78b898e11a5fadc9b7092 Mon Sep 17 00:00:00 2001 From: tituspijean Date: Mon, 5 Jul 2021 21:24:00 +0000 Subject: [PATCH 2621/3170] Add mdns.py for mDNS broadcast --- data/other/mdns.py | 94 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 data/other/mdns.py diff --git a/data/other/mdns.py b/data/other/mdns.py new file mode 100644 index 000000000..144742758 --- /dev/null +++ b/data/other/mdns.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 + +""" +WIP +Pythonic declaration of mDNS .local domains. +Heavily based off https://github.com/jstasiak/python-zeroconf/blob/master/tests/test_asyncio.py +""" + +import os +import sys +import argparse + +import asyncio +import logging +import socket +import time +from typing import List + +sys.path.insert(0, "/usr/lib/moulinette/") +from yunohost.domain import domain_list + +from zeroconf import IPVersion +from zeroconf.asyncio import AsyncServiceInfo, AsyncZeroconf + +# TODO: Remove traceback beautification +from rich.traceback import install +install(show_locals=True) + +async def register_services(infos: List[AsyncServiceInfo]) -> None: + tasks = [aiozc.async_register_service(info) for info in infos] + background_tasks = await asyncio.gather(*tasks) + await asyncio.gather(*background_tasks) + + +async def unregister_services(infos: List[AsyncServiceInfo]) -> None: + tasks = [aiozc.async_unregister_service(info) for info in infos] + background_tasks = await asyncio.gather(*tasks) + await asyncio.gather(*background_tasks) + + +async def close_aiozc(aiozc: AsyncZeroconf) -> None: + await aiozc.async_close() + + +if __name__ == '__main__': + logging.basicConfig(level=logging.DEBUG) + + local_domains = [ d for d in domain_list()['domains'] if d.endswith('.local') ] + + parser = argparse.ArgumentParser() + parser.add_argument('--debug', action='store_true') + version_group = parser.add_mutually_exclusive_group() + version_group.add_argument('--v6', action='store_true') + version_group.add_argument('--v6-only', action='store_true') + args = parser.parse_args() + + if args.debug: + logging.getLogger('zeroconf').setLevel(logging.DEBUG) + if args.v6: + ip_version = IPVersion.All + elif args.v6_only: + ip_version = IPVersion.V6Only + else: + ip_version = IPVersion.V4Only + + infos = [] + for d in local_domains: + d_domain=d.replace('.local','') + infos.append( + AsyncServiceInfo( + type_="_device-info._tcp.local.", + name=d_domain+f"._device-info._tcp.local.", + addresses=[socket.inet_aton("127.0.0.1")], + port=80, + server=d, + ) + ) + + print("Registration of .local domains, press Ctrl-C to exit...") + aiozc = AsyncZeroconf(ip_version=ip_version) + loop = asyncio.get_event_loop() + loop.run_until_complete(register_services(infos)) + print("Registration complete.") + try: + while True: + time.sleep(0.1) + except KeyboardInterrupt: + pass + finally: + print("Unregistering...") + loop.run_until_complete(unregister_services(infos)) + print("Unregistration complete.") + loop.run_until_complete(close_aiozc(aiozc)) + From 99390f23133f41b21a2665dbccbb417bbdc46413 Mon Sep 17 00:00:00 2001 From: tituspijean Date: Mon, 5 Jul 2021 23:01:19 +0000 Subject: [PATCH 2622/3170] PoC for mDNS broadcast --- data/other/mdns.py | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/data/other/mdns.py b/data/other/mdns.py index 144742758..2146caec0 100644 --- a/data/other/mdns.py +++ b/data/other/mdns.py @@ -2,8 +2,8 @@ """ WIP -Pythonic declaration of mDNS .local domains. -Heavily based off https://github.com/jstasiak/python-zeroconf/blob/master/tests/test_asyncio.py +Pythonic declaration of mDNS .local domains for YunoHost +Based off https://github.com/jstasiak/python-zeroconf/blob/master/tests/test_asyncio.py """ import os @@ -18,6 +18,7 @@ from typing import List sys.path.insert(0, "/usr/lib/moulinette/") from yunohost.domain import domain_list +from yunohost.utils.network import get_network_interfaces from zeroconf import IPVersion from zeroconf.asyncio import AsyncServiceInfo, AsyncZeroconf @@ -47,21 +48,26 @@ if __name__ == '__main__': local_domains = [ d for d in domain_list()['domains'] if d.endswith('.local') ] + # TODO: Create setting to list interfaces + wanted_interfaces = [ 'zt3jnskpna' ] + interfaces = get_network_interfaces() + ips = [] + for i in wanted_interfaces: + try: + ips.append(socket.inet_pton(socket.AF_INET, interfaces[i]['ipv4'].split('/')[0])) + except: + pass + try: + ips.append(socket.inet_pton(socket.AF_INET6, interfaces[i]['ipv6'].split('/')[0])) + except: + pass + parser = argparse.ArgumentParser() parser.add_argument('--debug', action='store_true') - version_group = parser.add_mutually_exclusive_group() - version_group.add_argument('--v6', action='store_true') - version_group.add_argument('--v6-only', action='store_true') args = parser.parse_args() if args.debug: logging.getLogger('zeroconf').setLevel(logging.DEBUG) - if args.v6: - ip_version = IPVersion.All - elif args.v6_only: - ip_version = IPVersion.V6Only - else: - ip_version = IPVersion.V4Only infos = [] for d in local_domains: @@ -70,14 +76,14 @@ if __name__ == '__main__': AsyncServiceInfo( type_="_device-info._tcp.local.", name=d_domain+f"._device-info._tcp.local.", - addresses=[socket.inet_aton("127.0.0.1")], + addresses=ips, port=80, - server=d, + server=d+'.', ) ) print("Registration of .local domains, press Ctrl-C to exit...") - aiozc = AsyncZeroconf(ip_version=ip_version) + aiozc = AsyncZeroconf() loop = asyncio.get_event_loop() loop.run_until_complete(register_services(infos)) print("Registration complete.") From 654690b42749ea0ee022136f601ccc8c171e13e6 Mon Sep 17 00:00:00 2001 From: tituspijean Date: Tue, 6 Jul 2021 10:48:39 +0000 Subject: [PATCH 2623/3170] Add interfaces selection for mDNS broadcast - System setting added - Loop through list of interfaces --- data/other/mdns.py | 106 +++++++++++++++++++++++---------------- locales/en.json | 1 + src/yunohost/settings.py | 1 + 3 files changed, 65 insertions(+), 43 deletions(-) diff --git a/data/other/mdns.py b/data/other/mdns.py index 2146caec0..0e249e7b8 100644 --- a/data/other/mdns.py +++ b/data/other/mdns.py @@ -19,26 +19,23 @@ from typing import List sys.path.insert(0, "/usr/lib/moulinette/") from yunohost.domain import domain_list from yunohost.utils.network import get_network_interfaces +from yunohost.settings import settings_get +from moulinette import m18n +from moulinette.interfaces.cli import get_locale -from zeroconf import IPVersion from zeroconf.asyncio import AsyncServiceInfo, AsyncZeroconf -# TODO: Remove traceback beautification -from rich.traceback import install -install(show_locals=True) -async def register_services(infos: List[AsyncServiceInfo]) -> None: +async def register_services(aiozc: AsyncZeroconf, infos: List[AsyncServiceInfo]) -> None: tasks = [aiozc.async_register_service(info) for info in infos] background_tasks = await asyncio.gather(*tasks) await asyncio.gather(*background_tasks) - -async def unregister_services(infos: List[AsyncServiceInfo]) -> None: +async def unregister_services(aiozc: AsyncZeroconf, infos: List[AsyncServiceInfo]) -> None: tasks = [aiozc.async_unregister_service(info) for info in infos] background_tasks = await asyncio.gather(*tasks) await asyncio.gather(*background_tasks) - async def close_aiozc(aiozc: AsyncZeroconf) -> None: await aiozc.async_close() @@ -46,22 +43,6 @@ async def close_aiozc(aiozc: AsyncZeroconf) -> None: if __name__ == '__main__': logging.basicConfig(level=logging.DEBUG) - local_domains = [ d for d in domain_list()['domains'] if d.endswith('.local') ] - - # TODO: Create setting to list interfaces - wanted_interfaces = [ 'zt3jnskpna' ] - interfaces = get_network_interfaces() - ips = [] - for i in wanted_interfaces: - try: - ips.append(socket.inet_pton(socket.AF_INET, interfaces[i]['ipv4'].split('/')[0])) - except: - pass - try: - ips.append(socket.inet_pton(socket.AF_INET6, interfaces[i]['ipv6'].split('/')[0])) - except: - pass - parser = argparse.ArgumentParser() parser.add_argument('--debug', action='store_true') args = parser.parse_args() @@ -69,24 +50,62 @@ if __name__ == '__main__': if args.debug: logging.getLogger('zeroconf').setLevel(logging.DEBUG) - infos = [] - for d in local_domains: - d_domain=d.replace('.local','') - infos.append( - AsyncServiceInfo( - type_="_device-info._tcp.local.", - name=d_domain+f"._device-info._tcp.local.", - addresses=ips, - port=80, - server=d+'.', - ) - ) - print("Registration of .local domains, press Ctrl-C to exit...") - aiozc = AsyncZeroconf() - loop = asyncio.get_event_loop() - loop.run_until_complete(register_services(infos)) - print("Registration complete.") + local_domains = [ d for d in domain_list()['domains'] if d.endswith('.local') ] + + m18n.load_namespace("yunohost") + m18n.set_locale(get_locale()) + + if settings_get('mdns.interfaces'): + wanted_interfaces = settings_get('mdns.interfaces').split() + else: + wanted_interfaces = [] + print('No interface listed for broadcast.') + + aiozcs = [] + interfaces = get_network_interfaces() + for interface in wanted_interfaces: + infos = [] + ips = [] # Human-readable IPs + b_ips = [] # Binary-convered IPs + + # Parse the IPs and prepare their binary version + try: + ip = interfaces[interface]['ipv4'].split('/')[0] + ips.append(ip) + b_ips.append(socket.inet_pton(socket.AF_INET, ip)) + except: + pass + try: + ip = interfaces[interface]['ipv6'].split('/')[0] + ips.append(ip) + b_ips.append(socket.inet_pton(socket.AF_INET6, ip)) + except: + pass + + # Create a ServiceInfo object for each .local domain + for d in local_domains: + d_domain=d.replace('.local','') + infos.append( + AsyncServiceInfo( + type_="_device-info._tcp.local.", + name=d_domain+f"._device-info._tcp.local.", + addresses=b_ips, + port=80, + server=d+'.', + ) + ) + print('Adding '+d+' with addresses '+str(ips)+' on interface '+interface) + + # Create an AsyncZeroconf object, store it, and start Service registration + aiozc = AsyncZeroconf(interfaces=ips) + aiozcs.append(aiozc) + print("Registration on interface "+interface+"...") + loop = asyncio.get_event_loop() + loop.run_until_complete(register_services(aiozc, infos)) + + # We are done looping among the interfaces + print("Registration complete. Press Ctrl-c to exit...") try: while True: time.sleep(0.1) @@ -94,7 +113,8 @@ if __name__ == '__main__': pass finally: print("Unregistering...") - loop.run_until_complete(unregister_services(infos)) + for aiozc in aiozcs: + loop.run_until_complete(unregister_services(aiozc, infos)) + loop.run_until_complete(close_aiozc(aiozc)) print("Unregistration complete.") - loop.run_until_complete(close_aiozc(aiozc)) diff --git a/locales/en.json b/locales/en.json index 84a01dfaa..70a0e9309 100644 --- a/locales/en.json +++ b/locales/en.json @@ -321,6 +321,7 @@ "global_settings_cant_write_settings": "Could not save settings file, reason: {reason:s}", "global_settings_key_doesnt_exists": "The key '{settings_key:s}' does not exist in the global settings, you can see all the available keys by running 'yunohost settings list'", "global_settings_reset_success": "Previous settings now backed up to {path:s}", + "global_settings_setting_mdns_interfaces": "Space-separated list of interfaces for mDNS broadcast. Leave empty to disable mDNS.", "global_settings_setting_pop3_enabled": "Enable the POP3 protocol for the mail server", "global_settings_setting_security_nginx_compatibility": "Compatibility vs. security tradeoff for the web server NGINX. Affects the ciphers (and other security-related aspects)", "global_settings_setting_security_password_admin_strength": "Admin password strength", diff --git a/src/yunohost/settings.py b/src/yunohost/settings.py index 0466d8126..36904ee70 100644 --- a/src/yunohost/settings.py +++ b/src/yunohost/settings.py @@ -100,6 +100,7 @@ DEFAULTS = OrderedDict( ("smtp.relay.password", {"type": "string", "default": ""}), ("backup.compress_tar_archives", {"type": "bool", "default": False}), ("ssowat.panel_overlay.enabled", {"type": "bool", "default": True}), + ("mdns.interfaces", {"type": "string", "default": ""}), ] ) From 9e93efa895ae6c3a7013c47adeb2891bdf3c8a3f Mon Sep 17 00:00:00 2001 From: tituspijean Date: Tue, 6 Jul 2021 20:33:23 +0000 Subject: [PATCH 2624/3170] Move mDNS script to yunomdns --- data/other/mdns.py => bin/yunomdns | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename data/other/mdns.py => bin/yunomdns (100%) mode change 100644 => 100755 diff --git a/data/other/mdns.py b/bin/yunomdns old mode 100644 new mode 100755 similarity index 100% rename from data/other/mdns.py rename to bin/yunomdns From 99aacd8b51ade29f5f153cde3808f55706c15bf6 Mon Sep 17 00:00:00 2001 From: tituspijean Date: Sun, 11 Jul 2021 14:23:41 +0000 Subject: [PATCH 2625/3170] Do not rely on Moulinette, integration with Yunohost of mDNS broadcast --- bin/yunomdns | 269 +++++++++++++++++++++------ data/hooks/conf_regen/01-yunohost | 5 +- data/other/yunomdns.service | 13 ++ data/templates/yunohost/mdns.yml | 4 + data/templates/yunohost/services.yml | 2 + debian/install | 1 + debian/postinst | 4 + 7 files changed, 244 insertions(+), 54 deletions(-) create mode 100644 data/other/yunomdns.service create mode 100644 data/templates/yunohost/mdns.yml diff --git a/bin/yunomdns b/bin/yunomdns index 0e249e7b8..795481dfa 100755 --- a/bin/yunomdns +++ b/bin/yunomdns @@ -6,115 +6,278 @@ Pythonic declaration of mDNS .local domains for YunoHost Based off https://github.com/jstasiak/python-zeroconf/blob/master/tests/test_asyncio.py """ +import subprocess import os +import re import sys import argparse +import yaml import asyncio import logging import socket import time -from typing import List - -sys.path.insert(0, "/usr/lib/moulinette/") -from yunohost.domain import domain_list -from yunohost.utils.network import get_network_interfaces -from yunohost.settings import settings_get -from moulinette import m18n -from moulinette.interfaces.cli import get_locale +from typing import List, Dict +from zeroconf import DNSEntry, DNSRecord from zeroconf.asyncio import AsyncServiceInfo, AsyncZeroconf +# Helper command taken from Moulinette +def check_output(args, stderr=subprocess.STDOUT, shell=True, **kwargs): + """Run command with arguments and return its output as a byte string + Overwrite some of the arguments to capture standard error in the result + and use shell by default before calling subprocess.check_output. + """ + return ( + subprocess.check_output(args, stderr=stderr, shell=shell, **kwargs) + .decode("utf-8") + .strip() + ) -async def register_services(aiozc: AsyncZeroconf, infos: List[AsyncServiceInfo]) -> None: - tasks = [aiozc.async_register_service(info) for info in infos] +# Helper command taken from Moulinette +def _extract_inet(string, skip_netmask=False, skip_loopback=True): + """ + Extract IP addresses (v4 and/or v6) from a string limited to one + address by protocol + + Keyword argument: + string -- String to search in + skip_netmask -- True to skip subnet mask extraction + skip_loopback -- False to include addresses reserved for the + loopback interface + + Returns: + A dict of {protocol: address} with protocol one of 'ipv4' or 'ipv6' + + """ + ip4_pattern = ( + r"((25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}" + ) + ip6_pattern = r"(((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)" + ip4_pattern += r"/[0-9]{1,2})" if not skip_netmask else ")" + ip6_pattern += r"/[0-9]{1,3})" if not skip_netmask else ")" + result = {} + + for m in re.finditer(ip4_pattern, string): + addr = m.group(1) + if skip_loopback and addr.startswith("127."): + continue + + # Limit to only one result + result["ipv4"] = addr + break + + for m in re.finditer(ip6_pattern, string): + addr = m.group(1) + if skip_loopback and addr == "::1": + continue + + # Limit to only one result + result["ipv6"] = addr + break + + return result + +# Helper command taken from Moulinette +def get_network_interfaces(): + # Get network devices and their addresses (raw infos from 'ip addr') + devices_raw = {} + output = check_output("ip addr show") + for d in re.split(r"^(?:[0-9]+: )", output, flags=re.MULTILINE): + # Extract device name (1) and its addresses (2) + m = re.match(r"([^\s@]+)(?:@[\S]+)?: (.*)", d, flags=re.DOTALL) + if m: + devices_raw[m.group(1)] = m.group(2) + + # Parse relevant informations for each of them + devices = { + name: _extract_inet(addrs) + for name, addrs in devices_raw.items() + if name != "lo" + } + + return devices + +# async commands +async def register_services(aiozcs: Dict[AsyncZeroconf, List[AsyncServiceInfo]]) -> None: + tasks = [] + for aiozc, infos in aiozcs.items(): + for info in infos: + tasks.append(aiozc.async_register_service(info)) background_tasks = await asyncio.gather(*tasks) await asyncio.gather(*background_tasks) -async def unregister_services(aiozc: AsyncZeroconf, infos: List[AsyncServiceInfo]) -> None: - tasks = [aiozc.async_unregister_service(info) for info in infos] +async def unregister_services(aiozcs: Dict[AsyncZeroconf, List[AsyncServiceInfo]]) -> None: + for aiozc, infos in aiozcs.items(): + for info in infos: + tasks.append(aiozc.async_unregister_service(info)) background_tasks = await asyncio.gather(*tasks) await asyncio.gather(*background_tasks) -async def close_aiozc(aiozc: AsyncZeroconf) -> None: - await aiozc.async_close() +async def close_aiozcs(aiozcs: Dict[AsyncZeroconf, List[AsyncServiceInfo]]) -> None: + tasks = [] + for aiozc in aiozcs: + tasks.append(aiozc.async_close()) + background_tasks = await asyncio.gather(*tasks) + await asyncio.gather(*background_tasks) if __name__ == '__main__': logging.basicConfig(level=logging.DEBUG) - parser = argparse.ArgumentParser() + + ### + # ARGUMENTS + ### + + parser = argparse.ArgumentParser(description=''' + mDNS broadcast for .local domains. + Configuration file: /etc/yunohost/mdns.yml + Subdomains are not supported. + ''') parser.add_argument('--debug', action='store_true') + parser.add_argument('--regen', nargs='?', const='as_stored', choices=['domains', 'interfaces', 'all', 'as_stored'], + help=''' + Regenerates selection into the configuration file then starts mDNS broadcasting. + ''') + parser.add_argument('--set-regen', choices=['domains', 'interfaces', 'all', 'none'], + help=''' + Set which part of the configuration to be regenerated. + Implies --regen as_stored, with newly stored parameter. + ''') + able = parser.add_mutually_exclusive_group() + able.add_argument('--enable', action='store_true', help='Enables mDNS broadcast, and regenerates the configuration') + able.add_argument('--disable', action='store_true') args = parser.parse_args() if args.debug: logging.getLogger('zeroconf').setLevel(logging.DEBUG) - - - local_domains = [ d for d in domain_list()['domains'] if d.endswith('.local') ] - - m18n.load_namespace("yunohost") - m18n.set_locale(get_locale()) - - if settings_get('mdns.interfaces'): - wanted_interfaces = settings_get('mdns.interfaces').split() + logging.getLogger('asyncio').setLevel(logging.DEBUG) else: - wanted_interfaces = [] - print('No interface listed for broadcast.') + logging.getLogger('zeroconf').setLevel(logging.WARNING) + logging.getLogger('asyncio').setLevel(logging.WARNING) - aiozcs = [] + ### + # CONFIG + ### + + with open('/etc/yunohost/mdns.yml', 'r') as f: + config = yaml.load(f) or {} + updated = False + + if args.enable: + config['enabled'] = True + args.regen = 'as_stored' + updated = True + + if args.disable: + config['enabled'] = False + updated = True + + if args.set_regen: + config['regen'] = args.set_regen + args.regen = 'as_stored' + updated = True + + if args.regen: + if args.regen == 'as_stored': + r = config['regen'] + else: + r = args.regen + if r == 'none': + print('Regeneration disabled.') + if r == 'interfaces' or r == 'all': + config['interfaces'] = [ i for i in get_network_interfaces() ] + print('Regenerated interfaces list: ' + str(config['interfaces'])) + if r == 'domains' or r == 'all': + import glob + config['domains'] = [ d.rsplit('/',1)[1][:-2] for d in glob.glob('/etc/nginx/conf.d/*.local.d') ] + print('Regenerated domains list: ' + str(config['domains'])) + updated = True + + if updated: + with open('/etc/yunohost/mdns.yml', 'w') as f: + yaml.safe_dump(config, f, default_flow_style=False) + print('Configuration file updated.') + + ### + # MAIN SCRIPT + ### + + if config['enabled'] is not True: + print('YunomDNS is disabled.') + sys.exit(0) + + if config['interfaces'] is None: + print('No interface listed for broadcast.') + sys.exit(0) + + aiozcs = {} + tasks = [] + loop = asyncio.get_event_loop() interfaces = get_network_interfaces() - for interface in wanted_interfaces: - infos = [] + for interface in config['interfaces']: + infos = [] # List of ServiceInfo objects, to feed Zeroconf ips = [] # Human-readable IPs b_ips = [] # Binary-convered IPs # Parse the IPs and prepare their binary version + addressed = False try: ip = interfaces[interface]['ipv4'].split('/')[0] + if len(ip)>0: addressed = True ips.append(ip) b_ips.append(socket.inet_pton(socket.AF_INET, ip)) except: pass try: ip = interfaces[interface]['ipv6'].split('/')[0] + if len(ip)>0: addressed = True ips.append(ip) b_ips.append(socket.inet_pton(socket.AF_INET6, ip)) except: pass - # Create a ServiceInfo object for each .local domain - for d in local_domains: - d_domain=d.replace('.local','') - infos.append( - AsyncServiceInfo( - type_="_device-info._tcp.local.", - name=d_domain+f"._device-info._tcp.local.", - addresses=b_ips, - port=80, - server=d+'.', + # If at least one IP is listed + if addressed: + # Create a ServiceInfo object for each .local domain + for d in config['domains']: + d_domain=d.replace('.local','') + infos.append( + AsyncServiceInfo( + type_='_device-info._tcp.local.', + name=d_domain+'._device-info._tcp.local.', + addresses=b_ips, + port=80, + server=d+'.', + ) ) - ) - print('Adding '+d+' with addresses '+str(ips)+' on interface '+interface) + infos.append( + AsyncServiceInfo( + type_='_http._tcp.local.', + name=d_domain+'._http._tcp.local.', + addresses=b_ips, + port=80, + server=d+'.', + ) + ) + print('Adding '+d+' with addresses '+str(ips)+' on interface '+interface) + # Create an AsyncZeroconf object, and store registration task + aiozc = AsyncZeroconf(interfaces=ips) + aiozcs[aiozc]=infos - # Create an AsyncZeroconf object, store it, and start Service registration - aiozc = AsyncZeroconf(interfaces=ips) - aiozcs.append(aiozc) - print("Registration on interface "+interface+"...") - loop = asyncio.get_event_loop() - loop.run_until_complete(register_services(aiozc, infos)) + # Run registration + loop.run_until_complete(register_services(aiozcs)) + print("Registration complete. Press Ctrl-c or stop service to exit...") - # We are done looping among the interfaces - print("Registration complete. Press Ctrl-c to exit...") try: while True: - time.sleep(0.1) + time.sleep(1) except KeyboardInterrupt: pass finally: print("Unregistering...") - for aiozc in aiozcs: - loop.run_until_complete(unregister_services(aiozc, infos)) - loop.run_until_complete(close_aiozc(aiozc)) + loop.run_until_complete(unregister_services(aiozcs)) print("Unregistration complete.") + loop.run_until_complete(close_aiozcs(aiozcs)) diff --git a/data/hooks/conf_regen/01-yunohost b/data/hooks/conf_regen/01-yunohost index 3d65d34cd..d160b9e66 100755 --- a/data/hooks/conf_regen/01-yunohost +++ b/data/hooks/conf_regen/01-yunohost @@ -3,6 +3,7 @@ set -e services_path="/etc/yunohost/services.yml" +mdns_path="/etc/yunohost/mdns.yml" do_init_regen() { if [[ $EUID -ne 0 ]]; then @@ -18,9 +19,11 @@ do_init_regen() { [[ -f /etc/yunohost/current_host ]] \ || echo "yunohost.org" > /etc/yunohost/current_host - # copy default services and firewall + # copy default services, mdns, and firewall [[ -f $services_path ]] \ || cp services.yml "$services_path" + [[ -f $mdns_path ]] \ + || cp mdns.yml "$mdns_path" [[ -f /etc/yunohost/firewall.yml ]] \ || cp firewall.yml /etc/yunohost/firewall.yml diff --git a/data/other/yunomdns.service b/data/other/yunomdns.service new file mode 100644 index 000000000..36d938035 --- /dev/null +++ b/data/other/yunomdns.service @@ -0,0 +1,13 @@ +[Unit] +Description=YunoHost mDNS service +After=network.target + +[Service] +User=avahi +Group=avahi +Type=simple +ExecStart=/usr/bin/yunomdns +StandardOutput=syslog + +[Install] +WantedBy=default.target diff --git a/data/templates/yunohost/mdns.yml b/data/templates/yunohost/mdns.yml new file mode 100644 index 000000000..3ed9e792b --- /dev/null +++ b/data/templates/yunohost/mdns.yml @@ -0,0 +1,4 @@ +enabled: True +regen: all +interfaces: +domains: diff --git a/data/templates/yunohost/services.yml b/data/templates/yunohost/services.yml index 7df563c67..b961d274e 100644 --- a/data/templates/yunohost/services.yml +++ b/data/templates/yunohost/services.yml @@ -52,6 +52,8 @@ yunohost-firewall: need_lock: true test_status: iptables -S | grep "^-A INPUT" | grep " --dport" | grep -q ACCEPT category: security +yunomdns: + needs_exposed_ports: [5353] glances: null nsswitch: null ssl: null diff --git a/debian/install b/debian/install index 1691a4849..e30a69a8b 100644 --- a/debian/install +++ b/debian/install @@ -5,6 +5,7 @@ doc/yunohost.8.gz /usr/share/man/man8/ data/actionsmap/* /usr/share/moulinette/actionsmap/ data/hooks/* /usr/share/yunohost/hooks/ data/other/yunoprompt.service /etc/systemd/system/ +data/other/yunomdns.service /etc/systemd/system/ data/other/password/* /usr/share/yunohost/other/password/ data/other/dpkg-origins/yunohost /etc/dpkg/origins data/other/dnsbl_list.yml /usr/share/yunohost/other/ diff --git a/debian/postinst b/debian/postinst index ecae9b258..7590197bd 100644 --- a/debian/postinst +++ b/debian/postinst @@ -38,6 +38,10 @@ do_configure() { # Yunoprompt systemctl enable yunoprompt.service + + # Yunomdns + chown avahi:avahi /etc/yunohost/mdns.yml + systemctl enable yunomdns.service } # summary of how this script can be called: From 842783f64c54bc0744d1692f5b1e4ea37e7c35ce Mon Sep 17 00:00:00 2001 From: tituspijean Date: Sun, 11 Jul 2021 15:20:14 +0000 Subject: [PATCH 2626/3170] Accomodate mDNS feature in diagnosis --- data/hooks/diagnosis/12-dnsrecords.py | 23 +++++++++++++++++++++-- data/hooks/diagnosis/21-web.py | 6 ++++++ data/templates/yunohost/services.yml | 1 + locales/en.json | 4 +++- 4 files changed, 31 insertions(+), 3 deletions(-) diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py index 719ce4d6a..89816847d 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -29,8 +29,9 @@ class DNSRecordsDiagnoser(Diagnoser): for domain in all_domains: self.logger_debug("Diagnosing DNS conf for %s" % domain) is_subdomain = domain.split(".", 1)[1] in all_domains + is_localdomain = domain.endswith(".local") for report in self.check_domain( - domain, domain == main_domain, is_subdomain=is_subdomain + domain, domain == main_domain, is_subdomain=is_subdomain, is_localdomain=is_localdomain ): yield report @@ -48,7 +49,7 @@ class DNSRecordsDiagnoser(Diagnoser): for report in self.check_expiration_date(domains_from_registrar): yield report - def check_domain(self, domain, is_main_domain, is_subdomain): + def check_domain(self, domain, is_main_domain, is_subdomain, is_localdomain): expected_configuration = _build_dns_conf( domain, include_empty_AAAA_if_no_ipv6=True @@ -59,6 +60,24 @@ class DNSRecordsDiagnoser(Diagnoser): if is_subdomain: categories = ["basic"] + if is_localdomain: + categories = [] + if is_subdomain: + yield dict( + meta={"domain": domain, "category": "basic"}, + results={}, + status="WARNING", + summary="diagnosis_domain_subdomain_localdomain", + ) + else: + yield dict( + meta={"domain": domain, "category": "basic"}, + results={}, + status="INFO", + summary="diagnosis_domain_localdomain", + ) + + for category in categories: records = expected_configuration[category] diff --git a/data/hooks/diagnosis/21-web.py b/data/hooks/diagnosis/21-web.py index 81c4d6e48..04c36661e 100644 --- a/data/hooks/diagnosis/21-web.py +++ b/data/hooks/diagnosis/21-web.py @@ -34,6 +34,12 @@ class WebDiagnoser(Diagnoser): summary="diagnosis_http_nginx_conf_not_up_to_date", details=["diagnosis_http_nginx_conf_not_up_to_date_details"], ) + elif domain.endswith('.local'): + yield dict( + meta={"domain": domain}, + status="INFO", + summary="diagnosis_http_localdomain", + ) else: domains_to_check.append(domain) diff --git a/data/templates/yunohost/services.yml b/data/templates/yunohost/services.yml index b961d274e..447829684 100644 --- a/data/templates/yunohost/services.yml +++ b/data/templates/yunohost/services.yml @@ -54,6 +54,7 @@ yunohost-firewall: category: security yunomdns: needs_exposed_ports: [5353] + category: mdns glances: null nsswitch: null ssl: null diff --git a/locales/en.json b/locales/en.json index 70a0e9309..3734b7cf3 100644 --- a/locales/en.json +++ b/locales/en.json @@ -190,6 +190,8 @@ "diagnosis_domain_expiration_warning": "Some domains will expire soon!", "diagnosis_domain_expiration_error": "Some domains will expire VERY SOON!", "diagnosis_domain_expires_in": "{domain} expires in {days} days.", + "diagnosis_domain_localdomain": "Domain {domain}, with a .local TLD, is not expected to have DNS records as it can be discovered through mDNS.", + "diagnosis_domain_subdomain_localdomain": "Domain {domain} is a subdomain of a .local domain. Zeroconf/mDNS discovery only works with first-level domains.", "diagnosis_services_running": "Service {service} is running!", "diagnosis_services_conf_broken": "Configuration is broken for service {service}!", "diagnosis_services_bad_status": "Service {service} is {status} :(", @@ -259,6 +261,7 @@ "diagnosis_http_hairpinning_issue_details": "This is probably because of your ISP box / router. As a result, people from outside your local network will be able to access your server as expected, but not people from inside the local network (like you, probably?) when using the domain name or global IP. You may be able to improve the situation by having a look at https://yunohost.org/dns_local_network", "diagnosis_http_could_not_diagnose": "Could not diagnose if domains are reachable from outside in IPv{ipversion}.", "diagnosis_http_could_not_diagnose_details": "Error: {error}", + "diagnosis_http_localdomain": "Domain {domain}, with a .local TLD, is not expected to be reached from outside the local network.", "diagnosis_http_ok": "Domain {domain} is reachable through HTTP from outside the local network.", "diagnosis_http_timeout": "Timed-out while trying to contact your server from outside. It appears to be unreachable.
1. The most common cause for this issue is that port 80 (and 443) are not correctly forwarded to your server.
2. You should also make sure that the service nginx is running
3. On more complex setups: make sure that no firewall or reverse-proxy is interfering.", "diagnosis_http_connection_error": "Connection error: could not connect to the requested domain, it's very likely unreachable.", @@ -321,7 +324,6 @@ "global_settings_cant_write_settings": "Could not save settings file, reason: {reason:s}", "global_settings_key_doesnt_exists": "The key '{settings_key:s}' does not exist in the global settings, you can see all the available keys by running 'yunohost settings list'", "global_settings_reset_success": "Previous settings now backed up to {path:s}", - "global_settings_setting_mdns_interfaces": "Space-separated list of interfaces for mDNS broadcast. Leave empty to disable mDNS.", "global_settings_setting_pop3_enabled": "Enable the POP3 protocol for the mail server", "global_settings_setting_security_nginx_compatibility": "Compatibility vs. security tradeoff for the web server NGINX. Affects the ciphers (and other security-related aspects)", "global_settings_setting_security_password_admin_strength": "Admin password strength", From f14dcec711f1856373456033134ba4ffe019c13e Mon Sep 17 00:00:00 2001 From: tituspijean Date: Sun, 11 Jul 2021 15:20:35 +0000 Subject: [PATCH 2627/3170] Remove mDNS setting now we use our own setting file, not a system setting --- src/yunohost/settings.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/yunohost/settings.py b/src/yunohost/settings.py index 36904ee70..0466d8126 100644 --- a/src/yunohost/settings.py +++ b/src/yunohost/settings.py @@ -100,7 +100,6 @@ DEFAULTS = OrderedDict( ("smtp.relay.password", {"type": "string", "default": ""}), ("backup.compress_tar_archives", {"type": "bool", "default": False}), ("ssowat.panel_overlay.enabled", {"type": "bool", "default": True}), - ("mdns.interfaces", {"type": "string", "default": ""}), ] ) From 92a1a12226d6caa5becb502bc2c0c690da4c199c Mon Sep 17 00:00:00 2001 From: tituspijean Date: Sun, 11 Jul 2021 17:32:33 +0000 Subject: [PATCH 2628/3170] [mdns] Fix NonUniqueNameException Service names have to be unique even accross different interfaces --- bin/yunomdns | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/yunomdns b/bin/yunomdns index 795481dfa..ad1c1a012 100755 --- a/bin/yunomdns +++ b/bin/yunomdns @@ -246,7 +246,7 @@ if __name__ == '__main__': infos.append( AsyncServiceInfo( type_='_device-info._tcp.local.', - name=d_domain+'._device-info._tcp.local.', + name=interface+' '+d_domain+'._device-info._tcp.local.', addresses=b_ips, port=80, server=d+'.', @@ -255,7 +255,7 @@ if __name__ == '__main__': infos.append( AsyncServiceInfo( type_='_http._tcp.local.', - name=d_domain+'._http._tcp.local.', + name=interface+' '+d_domain+'._http._tcp.local.', addresses=b_ips, port=80, server=d+'.', From 2239845b9d9f748e6138af239fc401dc6042ac94 Mon Sep 17 00:00:00 2001 From: ljf Date: Sun, 11 Jul 2021 19:41:25 +0200 Subject: [PATCH 2629/3170] [fix] Better support for non latin domain name --- src/yunohost/domain.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index aaac3a995..e5f3a0133 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -115,6 +115,9 @@ def domain_add(operation_logger, domain, dyndns=False): # See: https://forum.yunohost.org/t/invalid-domain-causes-diagnosis-web-to-fail-fr-on-demand/11765 domain = domain.lower() + # Non-latin characters (e.g. café.com => xn--caf-dma.com) + domain = domain.encode('idna').decode('utf-8') + # DynDNS domain if dyndns: From f3166b71b445f3c20981cbb4895c2cfa2f083eb6 Mon Sep 17 00:00:00 2001 From: Tagada <36127788+Tagadda@users.noreply.github.com> Date: Mon, 12 Jul 2021 18:32:12 +0200 Subject: [PATCH 2630/3170] [enh] Add settings to block webadmin from outside --- data/hooks/conf_regen/15-nginx | 7 +++++++ data/templates/nginx/{plain => }/yunohost_admin.conf.inc | 7 +++++++ data/templates/nginx/{plain => }/yunohost_api.conf.inc | 7 +++++++ locales/en.json | 2 ++ src/yunohost/settings.py | 4 ++++ 5 files changed, 27 insertions(+) rename data/templates/nginx/{plain => }/yunohost_admin.conf.inc (76%) rename data/templates/nginx/{plain => }/yunohost_api.conf.inc (75%) diff --git a/data/hooks/conf_regen/15-nginx b/data/hooks/conf_regen/15-nginx index 8875693c6..e2d12df0f 100755 --- a/data/hooks/conf_regen/15-nginx +++ b/data/hooks/conf_regen/15-nginx @@ -83,6 +83,13 @@ do_pre_regen() { done + export webadmin_allowlist_enabled=$(yunohost settings get security.webadmin.allowlist.enabled) + if [ "$webadmin_allowlist_enabled" == "True" ] + then + export webadmin_allowlist=$(yunohost settings get security.webadmin.allowlist) + fi + ynh_render_template "yunohost_admin.conf.inc" "${nginx_conf_dir}/yunohost_admin.conf.inc" + ynh_render_template "yunohost_api.conf.inc" "${nginx_conf_dir}/yunohost_api.conf.inc" ynh_render_template "yunohost_admin.conf" "${nginx_conf_dir}/yunohost_admin.conf" mkdir -p $nginx_conf_dir/default.d/ cp "redirect_to_admin.conf" $nginx_conf_dir/default.d/ diff --git a/data/templates/nginx/plain/yunohost_admin.conf.inc b/data/templates/nginx/yunohost_admin.conf.inc similarity index 76% rename from data/templates/nginx/plain/yunohost_admin.conf.inc rename to data/templates/nginx/yunohost_admin.conf.inc index 4b9938eac..150fce6f6 100644 --- a/data/templates/nginx/plain/yunohost_admin.conf.inc +++ b/data/templates/nginx/yunohost_admin.conf.inc @@ -6,6 +6,13 @@ location /yunohost/admin/ { default_type text/html; index index.html; + {% if webadmin_allowlist_enabled == "True" %} + {% for ip in webadmin_allowlist.split(',') %} + allow {{ ip }}; + {% endfor %} + deny all; + {% endif %} + more_set_headers "Content-Security-Policy: upgrade-insecure-requests; default-src 'self'; connect-src 'self' https://paste.yunohost.org wss://$host; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-eval'; object-src 'none'; img-src 'self' data:;"; more_set_headers "Content-Security-Policy-Report-Only:"; } diff --git a/data/templates/nginx/plain/yunohost_api.conf.inc b/data/templates/nginx/yunohost_api.conf.inc similarity index 75% rename from data/templates/nginx/plain/yunohost_api.conf.inc rename to data/templates/nginx/yunohost_api.conf.inc index 4d7887cc6..c9ae34f82 100644 --- a/data/templates/nginx/plain/yunohost_api.conf.inc +++ b/data/templates/nginx/yunohost_api.conf.inc @@ -6,6 +6,13 @@ location /yunohost/api/ { proxy_set_header Connection "upgrade"; proxy_set_header Host $http_host; + {% if webadmin_allowlist_enabled == "True" %} + {% for ip in webadmin_allowlist.split(',') %} + allow {{ ip }}; + {% endfor %} + deny all; + {% endif %} + # Custom 502 error page error_page 502 /yunohost/api/error/502; } diff --git a/locales/en.json b/locales/en.json index 84a01dfaa..96fbebb80 100644 --- a/locales/en.json +++ b/locales/en.json @@ -336,6 +336,8 @@ "global_settings_setting_smtp_relay_port": "SMTP relay port", "global_settings_setting_smtp_relay_user": "SMTP relay user account", "global_settings_setting_smtp_relay_password": "SMTP relay host password", + "global_settings_setting_security_webadmin_allowlist_enabled": "Allow only some IPs to access the webadmin.", + "global_settings_setting_security_webadmin_allowlist": "IP adresses allowed to access the webadmin. Comma-separated.", "global_settings_setting_backup_compress_tar_archives": "When creating new backups, compress the archives (.tar.gz) instead of uncompressed archives (.tar). N.B. : enabling this option means create lighter backup archives, but the initial backup procedure will be significantly longer and heavy on CPU.", "global_settings_unknown_type": "Unexpected situation, the setting {setting:s} appears to have the type {unknown_type:s} but it is not a type supported by the system.", "good_practices_about_admin_password": "You are now about to define a new administration password. The password should be at least 8 characters long—though it is good practice to use a longer password (i.e. a passphrase) and/or to use a variation of characters (uppercase, lowercase, digits and special characters).", diff --git a/src/yunohost/settings.py b/src/yunohost/settings.py index 0466d8126..a8ed4aa43 100644 --- a/src/yunohost/settings.py +++ b/src/yunohost/settings.py @@ -100,6 +100,8 @@ DEFAULTS = OrderedDict( ("smtp.relay.password", {"type": "string", "default": ""}), ("backup.compress_tar_archives", {"type": "bool", "default": False}), ("ssowat.panel_overlay.enabled", {"type": "bool", "default": True}), + ("security.webadmin.allowlist.enabled", {"type": "bool", "default": False}), + ("security.webadmin.allowlist", {"type": "string", "default": ""}), ] ) @@ -391,6 +393,8 @@ def trigger_post_change_hook(setting_name, old_value, new_value): @post_change_hook("ssowat.panel_overlay.enabled") @post_change_hook("security.nginx.compatibility") +@post_change_hook("security.webadmin.allowlist.enabled") +@post_change_hook("security.webadmin.allowlist") def reconfigure_nginx(setting_name, old_value, new_value): if old_value != new_value: regen_conf(names=["nginx"]) From a8df60da055b8d54134b963ac91a29ff6d8d49cd Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Tue, 13 Jul 2021 12:47:33 +0200 Subject: [PATCH 2631/3170] [fix] Bad command in yunopaste doc --- bin/yunopaste | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/yunopaste b/bin/yunopaste index d52199eba..679f13544 100755 --- a/bin/yunopaste +++ b/bin/yunopaste @@ -34,7 +34,7 @@ Haste server. For example, to paste the output of the YunoHost diagnosis, you can simply execute the following: - yunohost tools diagnosis | ${0} + yunohost diagnosis show | ${0} It will return the URL where you can access the pasted data. From d7c88cbf7813d9af6ecdc5933db0fe793c8a809f Mon Sep 17 00:00:00 2001 From: MercierCorentin Date: Tue, 13 Jul 2021 17:01:51 +0200 Subject: [PATCH 2632/3170] Add registrar name option to registrar catalog --- data/actionsmap/yunohost.yml | 3 +++ src/yunohost/domain.py | 23 +++++++++++++++-------- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 7c0272398..f0e7a5bd5 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -620,6 +620,9 @@ domain: action_help: List supported registrars API api: GET /domains/registrars/catalog arguments: + -r: + full: --registrar-name + help: Display given registrar info to create form -f: full: --full help: Display all details, including info to create forms diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 9e79a13ae..e87d1e118 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -917,15 +917,22 @@ def domain_registrar_info(domain): for option_key, option_value in registrar_info['options'].items(): logger.info("Option " + option_key + ": " + option_value) -def domain_registrar_catalog(full): - registrars = yaml.load(open(REGISTRAR_LIST_PATH, "r+")) - for registrar in registrars: - logger.info("Registrar : " + registrar) - if full : - logger.info("Options : ") - for option in registrars[registrar]: - logger.info("\t- " + option) +def _print_registrar_info(registrar_name, full, options): + logger.info("Registrar : " + registrar_name) + if full : + logger.info("Options : ") + for option in options: + logger.info("\t- " + option) +def domain_registrar_catalog(registrar_name, full): + registrars = yaml.load(open(REGISTRAR_LIST_PATH, "r+")) + + if registrar_name and registrar_name in registrars.keys() : + _print_registrar_info(registrar_name, True, registrars[registrar_name]) + else: + for registrar in registrars: + _print_registrar_info(registrar, full, registrars[registrar]) + def domain_registrar_set(domain, registrar, args): From 8c1d1dd99a5059f9cb0e153075af9cae3bb0215c Mon Sep 17 00:00:00 2001 From: MercierCorentin Date: Tue, 13 Jul 2021 18:17:23 +0200 Subject: [PATCH 2633/3170] Utils/dns get_dns_zone_from_domain improve --- src/yunohost/utils/dns.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/yunohost/utils/dns.py b/src/yunohost/utils/dns.py index 00bbc4f62..461fb0a4a 100644 --- a/src/yunohost/utils/dns.py +++ b/src/yunohost/utils/dns.py @@ -39,6 +39,7 @@ def get_public_suffix(domain): return public_suffix def get_dns_zone_from_domain(domain): + # TODO Check if this function is YNH_DYNDNS_DOMAINS compatible """ Get the DNS zone of a domain @@ -49,12 +50,15 @@ def get_dns_zone_from_domain(domain): separator = "." domain_subs = domain.split(separator) for i in range(0, len(domain_subs)): - answer = dig(separator.join(domain_subs), rdtype="NS", full_answers=True) + current_domain = separator.join(domain_subs) + answer = dig(current_domain, rdtype="NS", full_answers=True, resolvers="force_external") + print(answer) if answer[0] == "ok" : - return separator.join(domain_subs) - elif answer[1][0] == "NXDOMAIN" : - return None + # Domain is dns_zone + return current_domain + if separator.join(domain_subs[1:]) == get_public_suffix(current_domain): + # Couldn't check if domain is dns zone, + # returning private suffix + return current_domain domain_subs.pop(0) - - # Should not be executed return None \ No newline at end of file From 8104e48e4032f9bdca82103e581ddb7ac9a5af30 Mon Sep 17 00:00:00 2001 From: MercierCorentin Date: Tue, 13 Jul 2021 18:18:09 +0200 Subject: [PATCH 2634/3170] Fix _load_zone_of_domain --- src/yunohost/domain.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index e87d1e118..a33a4c425 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -899,7 +899,8 @@ def _set_domain_settings(domain, domain_settings): def _load_zone_of_domain(domain): - if domain not in domain_list()["domains"]: + domains = _load_domain_settings([domain]) + if domain not in domains.keys(): raise YunohostError("domain_name_unknown", domain=domain) return domains[domain]["dns_zone"] From fab248ce8c7c822428e3364ae0e2786f941929a7 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 14 Jul 2021 21:59:57 +0200 Subject: [PATCH 2635/3170] Fix de string format... --- locales/de.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/de.json b/locales/de.json index 83647ec17..4742429a9 100644 --- a/locales/de.json +++ b/locales/de.json @@ -184,7 +184,7 @@ "app_change_url_success": "{app:s} URL ist nun {domain:s}{path:s}", "global_settings_bad_type_for_setting": "Falscher Typ der Einstellung {setting:s}. Empfangen: {received_type:s}, aber erwarteter Typ: {expected_type:s}", "global_settings_bad_choice_for_enum": "Wert des Einstellungsparameters {setting:s} ungültig. Der Wert den Sie eingegeben haben: '{choice:s}', die gültigen Werte für diese Einstellung: {available_choices:s}", - "file_does_not_exist": "Die Datei {path: s} existiert nicht.", + "file_does_not_exist": "Die Datei {path:s} existiert nicht.", "experimental_feature": "Warnung: Der Maintainer hat diese Funktion als experimentell gekennzeichnet. Sie ist nicht stabil. Sie sollten sie nur verwenden, wenn Sie wissen, was Sie tun.", "dyndns_domain_not_provided": "Der DynDNS-Anbieter {provider:s} kann die Domäne(n) {domain:s} nicht bereitstellen.", "dyndns_could_not_check_available": "Konnte nicht überprüfen, ob {domain:s} auf {provider:s} verfügbar ist.", @@ -555,7 +555,7 @@ "permission_deleted": "Berechtigung gelöscht", "permission_currently_allowed_for_all_users": "Diese Berechtigung wird derzeit allen Benutzern zusätzlich zu anderen Gruppen erteilt. Möglicherweise möchten Sie entweder die Berechtigung 'all_users' entfernen oder die anderen Gruppen entfernen, für die sie derzeit zulässig sind.", "permission_creation_failed": "Berechtigungserstellung nicht möglich '{permission}' : {error}", - "permission_created": "Berechtigung '{permission: s}' erstellt", + "permission_created": "Berechtigung '{permission:s}' erstellt", "permission_cannot_remove_main": "Entfernung einer Hauptberechtigung nicht genehmigt", "regenconf_file_updated": "Konfigurationsdatei '{conf}' aktualisiert", "regenconf_file_removed": "Konfigurationsdatei '{conf}' entfernt", From d358452a03908948467289c9d9586e10d46db132 Mon Sep 17 00:00:00 2001 From: MercierCorentin Date: Thu, 15 Jul 2021 21:07:26 +0200 Subject: [PATCH 2636/3170] Bug fixe + cleaning --- data/actionsmap/yunohost.yml | 2 +- src/yunohost/domain.py | 2 +- src/yunohost/utils/dns.py | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index f0e7a5bd5..0349d2f28 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -448,7 +448,7 @@ domain: action: store_true ### domain_push_config() - push_config: + push-config: action_help: Push DNS records to registrar api: GET /domains//push arguments: diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index a33a4c425..6babe4f25 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -509,7 +509,7 @@ def _build_dns_conf(domain): if domain_name == domain: name = "@" if owned_dns_zone else root_prefix else: - name = domain_name[0 : -(1 + len(domain))] + name = domain_name if not owned_dns_zone: name += "." + root_prefix diff --git a/src/yunohost/utils/dns.py b/src/yunohost/utils/dns.py index 461fb0a4a..fd7cb1334 100644 --- a/src/yunohost/utils/dns.py +++ b/src/yunohost/utils/dns.py @@ -52,7 +52,6 @@ def get_dns_zone_from_domain(domain): for i in range(0, len(domain_subs)): current_domain = separator.join(domain_subs) answer = dig(current_domain, rdtype="NS", full_answers=True, resolvers="force_external") - print(answer) if answer[0] == "ok" : # Domain is dns_zone return current_domain From 8cf92576831e7a130564576f79c73cc4fbcd08ca Mon Sep 17 00:00:00 2001 From: cyxae Date: Tue, 13 Jul 2021 19:35:20 +0200 Subject: [PATCH 2637/3170] fix yunohost app search fail --- src/yunohost/app.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 5f001c12a..2ca931a90 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -127,14 +127,15 @@ def app_search(string): catalog_of_apps = app_catalog() # Selecting apps according to a match in app name or description + matching_apps = {"apps": {}} for app in catalog_of_apps["apps"].items(): - if not ( + if ( re.search(string, app[0], flags=re.IGNORECASE) or re.search(string, app[1]["description"], flags=re.IGNORECASE) ): - del catalog_of_apps["apps"][app[0]] + matching_apps["apps"][app[0]] = app[1] - return catalog_of_apps + return matching_apps # Old legacy function... From 40df98e8f2d8eb7bef4d45d3a6b92011948d0cd5 Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Sat, 17 Jul 2021 20:13:42 +0200 Subject: [PATCH 2638/3170] [fix] Avoid to suspend server if we close lidswitch --- data/hooks/conf_regen/01-yunohost | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/data/hooks/conf_regen/01-yunohost b/data/hooks/conf_regen/01-yunohost index 3d65d34cd..29ce5db80 100755 --- a/data/hooks/conf_regen/01-yunohost +++ b/data/hooks/conf_regen/01-yunohost @@ -135,6 +135,16 @@ Conflicts=yunohost-firewall.service ConditionFileIsExecutable=!/etc/init.d/yunohost-firewall ConditionPathExists=!/etc/systemd/system/multi-user.target.wants/yunohost-firewall.service EOF + + # Don't suspend computer on LidSwitch + mkdir -p ${pending_dir}/etc/systemd/logind.conf.d/ + cat > ${pending_dir}/etc/systemd/logind.conf.d/yunohost.conf << EOF +[Login] +HandleLidSwitch=ignore +HandleLidSwitchDocked=ignore +HandleLidSwitchExternalPower=ignore +EOF + } do_post_regen() { From 4755c1c6d5a15217d398ae08f327e10d70607b34 Mon Sep 17 00:00:00 2001 From: MercierCorentin Date: Wed, 21 Jul 2021 23:14:23 +0200 Subject: [PATCH 2639/3170] Separate mail setting in mail_in and mail_out + fix domain settings types --- src/yunohost/domain.py | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 6babe4f25..a313cc28e 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -531,11 +531,14 @@ def _build_dns_conf(domain): ######### # Email # ######### - if domain["mail"]: - + if domain["mail_in"]: mail += [ - [name, ttl, "MX", "10 %s." % domain_name], - [name, ttl, "TXT", '"v=spf1 a mx -all"'], + [name, ttl, "MX", "10 %s." % domain_name] + ] + + if domain["mail_out"]: + mail += [ + [name, ttl, "TXT", '"v=spf1 a mx -all"'] ] # DKIM/DMARC record @@ -772,7 +775,8 @@ def _load_domain_settings(domains=[]): dns_zone = get_dns_zone_from_domain(domain) default_settings = { "xmpp": is_maindomain, - "mail": True, + "mail_in": True, + "mail_out": True, "dns_zone": dns_zone, "ttl": 3600, } @@ -805,13 +809,15 @@ def domain_setting(domain, key, value=None, delete=False): Set or get an app setting value Keyword argument: - value -- Value to set - app -- App ID + domain -- Domain Name key -- Key to get/set + value -- Value to set delete -- Delete the key """ + boolean_keys = ["mail_in", "mail_out", "xmpp"] + domains = _load_domain_settings([ domain ]) if not domain in domains.keys(): @@ -834,17 +840,22 @@ def domain_setting(domain, key, value=None, delete=False): # SET else: + if key in boolean_keys: + value = True if value.lower() in ['true', '1', 't', 'y', 'yes', "iloveynh"] else False if "ttl" == key: try: - ttl = int(value) + value = int(value) except ValueError: # TODO add locales - raise YunohostError("invalid_number", value_type=type(ttl)) + raise YunohostError("invalid_number", value_type=type(value)) - if ttl < 0: - raise YunohostError("pattern_positive_number", value_type=type(ttl)) + if value < 0: + raise YunohostError("pattern_positive_number", value_type=type(value)) + + # Set new value domain_settings[key] = value + # Save settings _set_domain_settings(domain, domain_settings) From 7c58aa198c1096f4ce27993f7f6a05f628445dae Mon Sep 17 00:00:00 2001 From: tituspijean Date: Tue, 27 Jul 2021 20:38:02 +0000 Subject: [PATCH 2640/3170] Remove asyncio and do not support subdomains --- bin/yunomdns | 88 +++++++++++++++++----------------------------------- 1 file changed, 28 insertions(+), 60 deletions(-) diff --git a/bin/yunomdns b/bin/yunomdns index ad1c1a012..123935871 100755 --- a/bin/yunomdns +++ b/bin/yunomdns @@ -16,11 +16,10 @@ import yaml import asyncio import logging import socket -import time +from time import sleep from typing import List, Dict -from zeroconf import DNSEntry, DNSRecord -from zeroconf.asyncio import AsyncServiceInfo, AsyncZeroconf +from zeroconf import Zeroconf, ServiceInfo # Helper command taken from Moulinette def check_output(args, stderr=subprocess.STDOUT, shell=True, **kwargs): @@ -98,30 +97,6 @@ def get_network_interfaces(): return devices -# async commands -async def register_services(aiozcs: Dict[AsyncZeroconf, List[AsyncServiceInfo]]) -> None: - tasks = [] - for aiozc, infos in aiozcs.items(): - for info in infos: - tasks.append(aiozc.async_register_service(info)) - background_tasks = await asyncio.gather(*tasks) - await asyncio.gather(*background_tasks) - -async def unregister_services(aiozcs: Dict[AsyncZeroconf, List[AsyncServiceInfo]]) -> None: - for aiozc, infos in aiozcs.items(): - for info in infos: - tasks.append(aiozc.async_unregister_service(info)) - background_tasks = await asyncio.gather(*tasks) - await asyncio.gather(*background_tasks) - -async def close_aiozcs(aiozcs: Dict[AsyncZeroconf, List[AsyncServiceInfo]]) -> None: - tasks = [] - for aiozc in aiozcs: - tasks.append(aiozc.async_close()) - background_tasks = await asyncio.gather(*tasks) - await asyncio.gather(*background_tasks) - - if __name__ == '__main__': logging.basicConfig(level=logging.DEBUG) @@ -212,9 +187,7 @@ if __name__ == '__main__': print('No interface listed for broadcast.') sys.exit(0) - aiozcs = {} - tasks = [] - loop = asyncio.get_event_loop() + zcs = {} interfaces = get_network_interfaces() for interface in config['interfaces']: infos = [] # List of ServiceInfo objects, to feed Zeroconf @@ -240,44 +213,39 @@ if __name__ == '__main__': # If at least one IP is listed if addressed: - # Create a ServiceInfo object for each .local domain + # Create a Zeroconf object, and store the ServiceInfos + zc = Zeroconf(interfaces=ips) + zcs[zc]=[] for d in config['domains']: d_domain=d.replace('.local','') - infos.append( - AsyncServiceInfo( - type_='_device-info._tcp.local.', - name=interface+' '+d_domain+'._device-info._tcp.local.', - addresses=b_ips, - port=80, - server=d+'.', - ) - ) - infos.append( - AsyncServiceInfo( - type_='_http._tcp.local.', - name=interface+' '+d_domain+'._http._tcp.local.', - addresses=b_ips, - port=80, - server=d+'.', - ) - ) - print('Adding '+d+' with addresses '+str(ips)+' on interface '+interface) - # Create an AsyncZeroconf object, and store registration task - aiozc = AsyncZeroconf(interfaces=ips) - aiozcs[aiozc]=infos + if '.' in d_domain: + print(d_domain+'.local: subdomains are not supported.') + else: + # Create a ServiceInfo object for each .local domain + zcs[zc].append(ServiceInfo( + type_='_device-info._tcp.local.', + name=interface+': '+d_domain+'._device-info._tcp.local.', + addresses=b_ips, + port=80, + server=d+'.', + )) + print('Adding '+d+' with addresses '+str(ips)+' on interface '+interface) # Run registration - loop.run_until_complete(register_services(aiozcs)) - print("Registration complete. Press Ctrl-c or stop service to exit...") + print("Registering...") + for zc, infos in zcs.items(): + for info in infos: + zc.register_service(info) try: + print("Registered. Press Ctrl+C or stop service to stop.") while True: - time.sleep(1) + sleep(1) except KeyboardInterrupt: pass finally: print("Unregistering...") - loop.run_until_complete(unregister_services(aiozcs)) - print("Unregistration complete.") - loop.run_until_complete(close_aiozcs(aiozcs)) - + for zc, infos in zcs.items(): + for info in infos: + zc.unregister_service(info) + zc.close() From a26a024092de78c7711f67f5bacf3297f1253c63 Mon Sep 17 00:00:00 2001 From: ljf Date: Mon, 11 May 2020 19:13:10 +0200 Subject: [PATCH 2641/3170] [wip] Allow file upload from config-panel --- src/yunohost/app.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 2ca931a90..32bb1ece3 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -33,6 +33,7 @@ import re import subprocess import glob import urllib.parse +import base64 import tempfile from collections import OrderedDict @@ -1867,6 +1868,7 @@ def app_config_apply(operation_logger, app, args): } args = dict(urllib.parse.parse_qsl(args, keep_blank_values=True)) if args else {} + upload_dir = None for tab in config_panel.get("panel", []): tab_id = tab["id"] # this makes things easier to debug on crash for section in tab.get("sections", []): @@ -1878,6 +1880,23 @@ def app_config_apply(operation_logger, app, args): ).upper() if generated_name in args: + # Upload files from API + # A file arg contains a string with "FILENAME:BASE64_CONTENT" + if option["type"] == "file" and msettings.get('interface') == 'api': + if upload_dir is None: + upload_dir = tempfile.mkdtemp(prefix='tmp_configpanel_') + filename, args[generated_name] = args[generated_name].split(':') + logger.debug("Save uploaded file %s from API into %s", filename, upload_dir) + file_path = os.join(upload_dir, filename) + try: + with open(file_path, 'wb') as f: + f.write(args[generated_name]) + except IOError as e: + raise YunohostError("cannot_write_file", file=file_path, error=str(e)) + except Exception as e: + raise YunohostError("error_writing_file", file=file_path, error=str(e)) + args[generated_name] = file_path + logger.debug( "include into env %s=%s", generated_name, args[generated_name] ) @@ -1899,6 +1918,11 @@ def app_config_apply(operation_logger, app, args): env=env, )[0] + # Delete files uploaded from API + if msettings.get('interface') == 'api': + if upload_dir is not None: + shutil.rmtree(upload_dir) + if return_code != 0: msg = ( "'script/config apply' return value code: %s (considered as an error)" From 4939bbeb2e4b7a0362ee55d955fa33271bcf8c50 Mon Sep 17 00:00:00 2001 From: ljf Date: Mon, 8 Jun 2020 19:18:22 +0200 Subject: [PATCH 2642/3170] [fix] Several files with same name --- src/yunohost/app.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 32bb1ece3..ae6accab0 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1882,15 +1882,21 @@ def app_config_apply(operation_logger, app, args): if generated_name in args: # Upload files from API # A file arg contains a string with "FILENAME:BASE64_CONTENT" - if option["type"] == "file" and msettings.get('interface') == 'api': + if 'type' in option and option["type"] == "file" \ + and msettings.get('interface') == 'api': if upload_dir is None: upload_dir = tempfile.mkdtemp(prefix='tmp_configpanel_') - filename, args[generated_name] = args[generated_name].split(':') + filename = args[generated_name + '[name]'] + content = args[generated_name] logger.debug("Save uploaded file %s from API into %s", filename, upload_dir) - file_path = os.join(upload_dir, filename) + file_path = os.path.join(upload_dir, filename) + i = 2 + while os.path.exists(file_path): + file_path = os.path.join(upload_dir, filename + (".%d" % i)) + i += 1 try: with open(file_path, 'wb') as f: - f.write(args[generated_name]) + f.write(content.decode("base64")) except IOError as e: raise YunohostError("cannot_write_file", file=file_path, error=str(e)) except Exception as e: @@ -1907,7 +1913,7 @@ def app_config_apply(operation_logger, app, args): # for debug purpose for key in args: if key not in env: - logger.warning( + logger.debug( "Ignore key '%s' from arguments because it is not in the config", key ) From 3bc45b5672f332e7cdbe314c756fcf4aac74e11f Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Wed, 7 Oct 2020 00:31:20 +0200 Subject: [PATCH 2643/3170] [enh] Replace os.path.join to improve security --- src/yunohost/app.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index ae6accab0..f017521d2 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1889,10 +1889,14 @@ def app_config_apply(operation_logger, app, args): filename = args[generated_name + '[name]'] content = args[generated_name] logger.debug("Save uploaded file %s from API into %s", filename, upload_dir) - file_path = os.path.join(upload_dir, filename) + + # Filename is given by user of the API. For security reason, we have replaced + # os.path.join to avoid the user to be able to rewrite a file in filesystem + # i.e. os.path.join("/foo", "/etc/passwd") == "/etc/passwd" + file_path = os.path.normpath(upload_dir + "/" + filename) i = 2 while os.path.exists(file_path): - file_path = os.path.join(upload_dir, filename + (".%d" % i)) + file_path = os.path.normpath(upload_dir + "/" + filename + (".%d" % i)) i += 1 try: with open(file_path, 'wb') as f: From a5508b1db45d2f5ae94578f44b6026fd5b45d017 Mon Sep 17 00:00:00 2001 From: ljf Date: Mon, 31 May 2021 16:32:19 +0200 Subject: [PATCH 2644/3170] [fix] Base64 python3 change --- 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 f017521d2..49033d8b4 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1845,7 +1845,7 @@ def app_config_apply(operation_logger, app, args): logger.warning(m18n.n("experimental_feature")) from yunohost.hook import hook_exec - + from base64 import b64decode installed = _is_installed(app) if not installed: raise YunohostValidationError( @@ -1889,8 +1889,8 @@ def app_config_apply(operation_logger, app, args): filename = args[generated_name + '[name]'] content = args[generated_name] logger.debug("Save uploaded file %s from API into %s", filename, upload_dir) - - # Filename is given by user of the API. For security reason, we have replaced + + # Filename is given by user of the API. For security reason, we have replaced # os.path.join to avoid the user to be able to rewrite a file in filesystem # i.e. os.path.join("/foo", "/etc/passwd") == "/etc/passwd" file_path = os.path.normpath(upload_dir + "/" + filename) @@ -1900,7 +1900,7 @@ def app_config_apply(operation_logger, app, args): i += 1 try: with open(file_path, 'wb') as f: - f.write(content.decode("base64")) + f.write(b64decode(content)) except IOError as e: raise YunohostError("cannot_write_file", file=file_path, error=str(e)) except Exception as e: From 27ba82bd307ae28268f7e56c1b3a6a40060c62e8 Mon Sep 17 00:00:00 2001 From: ljf Date: Tue, 1 Jun 2021 00:41:37 +0200 Subject: [PATCH 2645/3170] [enh] Add configpanel helpers --- data/helpers.d/configpanel | 259 +++++++++++++++++++++++++++++++++++++ 1 file changed, 259 insertions(+) create mode 100644 data/helpers.d/configpanel diff --git a/data/helpers.d/configpanel b/data/helpers.d/configpanel new file mode 100644 index 000000000..f648826e4 --- /dev/null +++ b/data/helpers.d/configpanel @@ -0,0 +1,259 @@ +#!/bin/bash + +ynh_lowerdot_to_uppersnake() { + local lowerdot + lowerdot=$(echo "$1" | cut -d= -f1 | sed "s/\./_/g") + echo "${lowerdot^^}" +} + +# Get a value from heterogeneous file (yaml, json, php, python...) +# +# usage: ynh_value_get --file=PATH --key=KEY +# | arg: -f, --file= - the path to the file +# | arg: -k, --key= - the key to get +# +# This helpers match several var affectation use case in several languages +# We don't use jq or equivalent to keep comments and blank space in files +# This helpers work line by line, it is not able to work correctly +# if you have several identical keys in your files +# +# Example of line this helpers can managed correctly +# .yml +# title: YunoHost documentation +# email: 'yunohost@yunohost.org' +# .json +# "theme": "colib'ris", +# "port": 8102 +# "some_boolean": false, +# "user": null +# .ini +# some_boolean = On +# action = "Clear" +# port = 20 +# .php +# $user= +# user => 20 +# .py +# USER = 8102 +# user = 'https://donate.local' +# CUSTOM['user'] = 'YunoHost' +# Requires YunoHost version 4.3 or higher. +ynh_value_get() { + # Declare an array to define the options of this helper. + local legacy_args=fk + local -A args_array=( [f]=file= [k]=key= ) + local file + local key + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + local var_part="[ \t]*(\$?\w*\[)?[ \t]*[\"']?${key}[\"']?[ \t]*\]?[ \t]*[:=]>?[ \t]*" + + local crazy_value="$(grep -i -o -P "^${var_part}\K.*(?=[ \t,\n;]*\$)" ${file} | head -n1)" + + local first_char="${crazy_value:0:1}" + if [[ "$first_char" == '"' ]] ; then + echo "$crazy_value" | grep -m1 -o -P '"\K([^"](\\")?)*[^\\](?=")' | head -n1 | sed 's/\\"/"/g' + elif [[ "$first_char" == "'" ]] ; then + echo "$crazy_value" | grep -m1 -o -P "'\K([^'](\\\\')?)*[^\\\\](?=')" | head -n1 | sed "s/\\\\'/'/g" + else + echo "$crazy_value" + fi +} + +# Set a value into heterogeneous file (yaml, json, php, python...) +# +# usage: ynh_value_set --file=PATH --key=KEY --value=VALUE +# | arg: -f, --file= - the path to the file +# | arg: -k, --key= - the key to set +# | arg: -v, --value= - the value to set +# +# Requires YunoHost version 4.3 or higher. +ynh_value_set() { + # Declare an array to define the options of this helper. + local legacy_args=fkv + local -A args_array=( [f]=file= [k]=key= [v]=value=) + local file + local key + local value + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + local var_part="[ \t]*(\$?\w*\[)?[ \t]*[\"']?${key}[\"']?[ \t]*\]?[ \t]*[:=]>?[ \t]*" + + local crazy_value="$(grep -i -o -P "^${var_part}\K.*(?=[ \t,\n;]*\$)" ${file} | head -n1)" + local var_part="^[ \t]*(\$?\w*\[)?[ \t]*[\"']?${key}[\"']?[ \t]*\]?[ \t]*[:=]>?[ \t]*" + local first_char="${crazy_value:0:1}" + if [[ "$first_char" == '"' ]] ; then + value="$(echo "$value" | sed 's/"/\\"/g')" + sed -ri "s%^(${var_part}\")[^\"]*(\"[ \t\n,;]*)\$%\1${value}\2%i" ${file} + elif [[ "$first_char" == "'" ]] ; then + value="$(echo "$value" | sed "s/'/\\\\'/g")" + sed -ri "s%^(${var_part}')[^']*('[ \t\n,;]*)\$%\1${value}\2%i" ${file} + else + if [[ "$value" == *"'"* ]] || [[ "$value" == *'"'* ]] ; then + value="\"$(echo "$value" | sed 's/"/\\"/g')\"" + fi + sed -ri "s%^(${var_part}')[^']*('[ \t\n,;]*)\$%\1${value}\2%i" ${file} + fi +} + +_ynh_panel_get() { + + # From settings + local params_sources + params_sources=`python3 << EOL +import toml +from collections import OrderedDict +with open("/etc/yunohost/apps/vpnclient/config_panel.toml", "r") as f: + file_content = f.read() +loaded_toml = toml.loads(file_content, _dict=OrderedDict) + +for panel_name,panel in loaded_toml.items(): + if isinstance(panel, dict): + for section_name, section in panel.items(): + if isinstance(section, dict): + for name, param in section.items(): + if isinstance(param, dict) and param.get('source', '') == 'settings': + print("%s.%s.%s=%s" %(panel_name, section_name, name, param.get('source', 'settings'))) +EOL +` + for param_source in params_sources + do + local _dot_setting=$(echo "$param_source" | cut -d= -f1) + local _snake_setting="YNH_CONFIG_$(ynh_lowerdot_to_uppersnake $dot_setting)" + local short_setting=$(echo "$_dot_setting" | cut -d. -f3) + local _getter="get__${short_setting}" + local source="$(echo $param_source | cut -d= -f2)" + + # Get value from getter if exists + if type $getter | grep -q '^function$' 2>/dev/null; then + old[$short_setting]="$($getter)" + + # Get value from app settings + elif [[ "$source" == "settings" ]] ; then + old[$short_setting]="$(ynh_app_setting_get $app $short_setting)" + + # Get value from a kind of key/value file + elif [[ "$source" == *":"* ]] ; then + local source_key="$(echo "$source" | cut -d: -f1)" + source_key=${source_key:-$short_setting} + local source_file="$(echo "$source" | cut -d: -f2)" + old[$short_setting]="$(ynh_value_get --file="${source_file}" --key="${source_key}")" + + # Specific case for files (all content of the file is the source) + else + old[$short_setting]="$source" + fi + + done + + +} + +_ynh_panel_apply() { + for short_setting in "${!dot_settings[@]}" + do + local setter="set__${short_setting}" + local source="$sources[$short_setting]" + + # Apply setter if exists + if type $setter | grep -q '^function$' 2>/dev/null; then + $setter + + # Copy file in right place + elif [[ "$source" == "settings" ]] ; then + ynh_app_setting_set $app $short_setting "$new[$short_setting]" + + # Get value from a kind of key/value file + elif [[ "$source" == *":"* ]] + then + local source_key="$(echo "$source" | cut -d: -f1)" + source_key=${source_key:-$short_setting} + local source_file="$(echo "$source" | cut -d: -f2)" + ynh_value_set --file="${source_file}" --key="${source_key}" --value="$new[$short_setting]" + + # Specific case for files (all content of the file is the source) + else + cp "$new[$short_setting]" "$source" + fi + done +} + +_ynh_panel_show() { + for short_setting in "${!old[@]}" + do + local key="YNH_CONFIG_$(ynh_lowerdot_to_uppersnake $dot_settings[$short_setting])" + ynh_return "$key=${old[$short_setting]}" + done +} + +_ynh_panel_validate() { + # Change detection + local is_error=true + #for changed_status in "${!changed[@]}" + for short_setting in "${!dot_settings[@]}" + do + #TODO file hash + file_hash[$setting]=$(sha256sum "$_source" | cut -d' ' -f1) + file_hash[$form_setting]=$(sha256sum "${!form_setting}" | cut -d' ' -f1) + if [[ "${file_hash[$setting]}" != "${file_hash[$form_setting]}" ]] + then + changed[$setting]=true + fi + if [[ "$new[$short_setting]" == "$old[$short_setting]" ]] + then + changed[$short_setting]=false + else + changed[$short_setting]=true + is_error=false + fi + done + + # Run validation if something is changed + if [[ "$is_error" == "false" ]] + then + + for short_setting in "${!dot_settings[@]}" + do + local result="$(validate__$short_setting)" + local key="YNH_ERROR_$(ynh_lowerdot_to_uppersnake $dot_settings[$short_setting])" + if [ -n "$result" ] + then + ynh_return "$key=$result" + is_error=true + fi + done + fi + + if [[ "$is_error" == "true" ]] + then + ynh_die + fi + +} + +ynh_panel_get() { + _ynh_panel_get +} + +ynh_panel_init() { + declare -A old=() + declare -A changed=() + declare -A file_hash=() + + ynh_panel_get +} + +ynh_panel_show() { + _ynh_panel_show +} + +ynh_panel_validate() { + _ynh_panel_validate +} + +ynh_panel_apply() { + _ynh_panel_apply +} + From 5fec35ccea72b9073ef75b18570c8df0e38aff7b Mon Sep 17 00:00:00 2001 From: ljf Date: Tue, 1 Jun 2021 01:29:26 +0200 Subject: [PATCH 2646/3170] [fix] No validate function in config panel --- data/helpers.d/configpanel | 41 ++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/data/helpers.d/configpanel b/data/helpers.d/configpanel index f648826e4..83130cfe6 100644 --- a/data/helpers.d/configpanel +++ b/data/helpers.d/configpanel @@ -123,7 +123,7 @@ EOL local _dot_setting=$(echo "$param_source" | cut -d= -f1) local _snake_setting="YNH_CONFIG_$(ynh_lowerdot_to_uppersnake $dot_setting)" local short_setting=$(echo "$_dot_setting" | cut -d. -f3) - local _getter="get__${short_setting}" + local getter="get__${short_setting}" local source="$(echo $param_source | cut -d= -f2)" # Get value from getter if exists @@ -195,12 +195,12 @@ _ynh_panel_validate() { for short_setting in "${!dot_settings[@]}" do #TODO file hash - file_hash[$setting]=$(sha256sum "$_source" | cut -d' ' -f1) - file_hash[$form_setting]=$(sha256sum "${!form_setting}" | cut -d' ' -f1) - if [[ "${file_hash[$setting]}" != "${file_hash[$form_setting]}" ]] - then - changed[$setting]=true - fi + file_hash[$setting]=$(sha256sum "$_source" | cut -d' ' -f1) + file_hash[$form_setting]=$(sha256sum "${!form_setting}" | cut -d' ' -f1) + if [[ "${file_hash[$setting]}" != "${file_hash[$form_setting]}" ]] + then + changed[$setting]=true + fi if [[ "$new[$short_setting]" == "$old[$short_setting]" ]] then changed[$short_setting]=false @@ -216,10 +216,13 @@ _ynh_panel_validate() { for short_setting in "${!dot_settings[@]}" do - local result="$(validate__$short_setting)" - local key="YNH_ERROR_$(ynh_lowerdot_to_uppersnake $dot_settings[$short_setting])" + local result="" + if type validate__$short_setting | grep -q '^function$' 2>/dev/null; then + result="$(validate__$short_setting)" + fi if [ -n "$result" ] then + local key="YNH_ERROR_$(ynh_lowerdot_to_uppersnake $dot_settings[$short_setting])" ynh_return "$key=$result" is_error=true fi @@ -237,14 +240,6 @@ ynh_panel_get() { _ynh_panel_get } -ynh_panel_init() { - declare -A old=() - declare -A changed=() - declare -A file_hash=() - - ynh_panel_get -} - ynh_panel_show() { _ynh_panel_show } @@ -257,3 +252,15 @@ ynh_panel_apply() { _ynh_panel_apply } +ynh_panel_run() { + declare -A old=() + declare -A changed=() + declare -A file_hash=() + + ynh_panel_get + case $1 in + show) ynh_panel_show;; + apply) ynh_panel_validate && ynh_panel_apply;; + esac +} + From 5bdeedfc558817e05a9fc113b6118a64083de753 Mon Sep 17 00:00:00 2001 From: ericgaspar Date: Sat, 31 Jul 2021 18:57:16 +0200 Subject: [PATCH 2647/3170] Fix broken links --- locales/ca.json | 4 ++-- locales/de.json | 4 ++-- locales/en.json | 4 ++-- locales/eo.json | 4 ++-- locales/es.json | 4 ++-- locales/fr.json | 4 ++-- locales/it.json | 4 ++-- locales/zh_Hans.json | 4 ++-- 8 files changed, 16 insertions(+), 16 deletions(-) diff --git a/locales/ca.json b/locales/ca.json index 1e4c55f5d..0145bedd0 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -501,7 +501,7 @@ "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.", "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 --human-readable» 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_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.", @@ -535,7 +535,7 @@ "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
- O es pot canviar a un proveïdor diferent", + "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).", diff --git a/locales/de.json b/locales/de.json index 4742429a9..e52819f97 100644 --- a/locales/de.json +++ b/locales/de.json @@ -334,7 +334,7 @@ "diagnosis_services_bad_status": "Der Dienst {service} ist {status} :(", "diagnosis_diskusage_verylow": "Der Speicher {mountpoint} (auf Gerät {device}) hat nur noch {free} ({free_percent}%) freien Speicherplatz (von ingesamt {total}). Sie sollten sich ernsthaft überlegen, einigen Seicherplatz frei zu machen!", "diagnosis_http_ok": "Die Domäne {domain} ist über HTTP von außerhalb des lokalen Netzwerks erreichbar.", - "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Einige Hosting-Anbieter werden es Ihnen nicht gestatten, den ausgehenden Port 25 zu öffnen, da diese sich nicht um die Netzneutralität kümmern.
- Einige davon bieten als Alternative an, ein Mailserver-Relay zu verwenden, was jedoch bedeutet, dass das Relay Ihren E-Mail-Verkehr ausspionieren kann.
- Eine die Privatsphäre berücksichtigende Alternative ist die Verwendung eines VPN *mit einer dedizierten öffentlichen IP* um solche Einschränkungen zu umgehen. Schauen Sie unter https://yunohost.org/#/vpn_advantage nach.
- Sie können auch in Betracht ziehen, zu einem netzneutralitätfreundlicheren Anbieter zu wechseln", + "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Einige Hosting-Anbieter werden es Ihnen nicht gestatten, den ausgehenden Port 25 zu öffnen, da diese sich nicht um die Netzneutralität kümmern.
- Einige davon bieten als Alternative an, ein Mailserver-Relay zu verwenden, was jedoch bedeutet, dass das Relay Ihren E-Mail-Verkehr ausspionieren kann.
- Eine die Privatsphäre berücksichtigende Alternative ist die Verwendung eines VPN *mit einer dedizierten öffentlichen IP* um solche Einschränkungen zu umgehen. Schauen Sie unter https://yunohost.org/#/vpn_advantage nach.
- Sie können auch in Betracht ziehen, zu einem netzneutralitätfreundlicheren Anbieter zu wechseln", "diagnosis_http_timeout": "Wartezeit wurde beim Versuch, von außen eine Verbindung zum Server aufzubauen, überschritten. Er scheint nicht erreichbar zu sein.
1. Die häufigste Ursache für dieses Problem ist daß der Port 80 (und 433) nicht richtig zu Ihrem Server weitergeleitet werden.
2. Sie sollten auch sicherstellen, daß der Dienst nginx läuft.
3. In komplexeren Umgebungen: Stellen Sie sicher, daß keine Firewall oder Reverse-Proxy stört.", "service_reloaded_or_restarted": "Der Dienst '{service:s}' wurde erfolgreich neu geladen oder gestartet", "service_restarted": "Der Dienst '{service:s}' wurde neu gestartet", @@ -386,7 +386,7 @@ "diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "Aktueller Reverse-DNS-Eintrag: {rdns_domain}
Erwarteter Wert: {ehlo_domain}", "diagnosis_mail_fcrdns_different_from_ehlo_domain": "Der Reverse-DNS-Eintrag für IPv{ipversion} ist nicht korrekt konfiguriert. Einige E-Mails könnten abgewiesen oder als Spam markiert werden.", "diagnosis_mail_fcrdns_nok_alternatives_6": "Einige Provider werden es Ihnen nicht erlauben, Ihren Reverse-DNS-Eintrag zu konfigurieren (oder ihre Funktionalität könnte defekt sein ...). Falls Ihr Reverse-DNS-Eintrag für IPv4 korrekt konfiguiert ist, können Sie versuchen, die Verwendung von IPv6 für das Versenden von E-Mails auszuschalten, indem Sie den Befehl yunohost settings set smtp.allow_ipv6 -v off ausführen. Bemerkung: Die Folge dieser letzten Lösung ist, dass Sie mit Servern, welche ausschliesslich über IPv6 verfügen, keine E-Mails mehr versenden oder empfangen können.", - "diagnosis_mail_fcrdns_nok_alternatives_4": "Einige Anbieter werden es Ihnen nicht erlauben, dass Sie Ihren Reverse-DNS (oder deren Funktionalität ist defekt...) konfigurieren. Falls Sie deswegen auf Probleme stossen sollten, ziehen Sie folgende Lösungen in Betracht:
- Manche ISPs stellen als Alternative die Benutzung eines Mail-Server-Relays zur Verfügung, was jedoch mit sich zieht, dass das Relay Ihren E-Mail-Verkehr ausspionieren kann.
- Eine privatsphärenfreundlichere Alternative ist die Benutzung eines VPN *mit einer dedizierten öffentlichen IP* um Einschränkungen dieser Art zu umgehen. Schauen Sie hier nach https://yunohost.org/#/vpn_advantage
- Schliesslich ist es auch möglich zu einem anderen Anbieter zu wechseln", + "diagnosis_mail_fcrdns_nok_alternatives_4": "Einige Anbieter werden es Ihnen nicht erlauben, dass Sie Ihren Reverse-DNS (oder deren Funktionalität ist defekt...) konfigurieren. Falls Sie deswegen auf Probleme stossen sollten, ziehen Sie folgende Lösungen in Betracht:
- Manche ISPs stellen als Alternative die Benutzung eines Mail-Server-Relays zur Verfügung, was jedoch mit sich zieht, dass das Relay Ihren E-Mail-Verkehr ausspionieren kann.
- Eine privatsphärenfreundlichere Alternative ist die Benutzung eines VPN *mit einer dedizierten öffentlichen IP* um Einschränkungen dieser Art zu umgehen. Schauen Sie hier nach https://yunohost.org/#/vpn_advantage
- Schliesslich ist es auch möglich zu einem anderen Anbieter zu wechseln", "diagnosis_mail_queue_unavailable_details": "Fehler: {error}", "diagnosis_mail_queue_unavailable": "Die Anzahl der anstehenden Nachrichten in der Warteschlange kann nicht abgefragt werden", "diagnosis_mail_queue_ok": "{nb_pending} anstehende E-Mails in der Warteschlange", diff --git a/locales/en.json b/locales/en.json index 84a01dfaa..f500d6ca1 100644 --- a/locales/en.json +++ b/locales/en.json @@ -207,7 +207,7 @@ "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).", - "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Some providers won't let you unblock outgoing port 25 because they don't care about Net Neutrality.
- Some of them provide the alternative of using a mail server relay though it implies that the relay will be able to spy on your email traffic.
- A privacy-friendly alternative is to use a VPN *with a dedicated public IP* to bypass this kind of limits. See https://yunohost.org/#/vpn_advantage
- You can also consider switching to a more net neutrality-friendly provider", + "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Some providers won't let you unblock outgoing port 25 because they don't care about Net Neutrality.
- Some of them provide the alternative of using a mail server relay though it implies that the relay will be able to spy on your email traffic.
- A privacy-friendly alternative is to use a VPN *with a dedicated public IP* to bypass this kind of limits. See https://yunohost.org/#/vpn_advantage
- You can also consider switching to a more net neutrality-friendly provider", "diagnosis_mail_ehlo_ok": "The SMTP mail server is reachable from the outside and therefore is able to receive emails!", "diagnosis_mail_ehlo_unreachable": "The SMTP mail server is unreachable from the outside on IPv{ipversion}. It won't be able to receive emails.", "diagnosis_mail_ehlo_unreachable_details": "Could not open a connection on port 25 to your server in IPv{ipversion}. It appears to be unreachable.
1. The most common cause for this issue is that port 25 is not correctly forwarded to your server.
2. You should also make sure that service postfix is running.
3. On more complex setups: make sure that no firewall or reverse-proxy is interfering.", @@ -220,7 +220,7 @@ "diagnosis_mail_fcrdns_ok": "Your reverse DNS is correctly configured!", "diagnosis_mail_fcrdns_dns_missing": "No reverse DNS is defined in IPv{ipversion}. Some emails may fail to get delivered or may get flagged as spam.", "diagnosis_mail_fcrdns_nok_details": "You should first try to configure the reverse DNS with {ehlo_domain} in your internet router interface or your hosting provider interface. (Some hosting provider may require you to send them a support ticket for this).", - "diagnosis_mail_fcrdns_nok_alternatives_4": "Some providers won't let you configure your reverse DNS (or their feature might be broken...). If you are experiencing issues because of this, consider the following solutions:
- Some ISP provide the alternative of using a mail server relay though it implies that the relay will be able to spy on your email traffic.
- A privacy-friendly alternative is to use a VPN *with a dedicated public IP* to bypass this kind of limits. See https://yunohost.org/#/vpn_advantage
- Or it's possible to switch to a different provider", + "diagnosis_mail_fcrdns_nok_alternatives_4": "Some providers won't let you configure your reverse DNS (or their feature might be broken...). If you are experiencing issues because of this, consider the following solutions:
- Some ISP provide the alternative of using a mail server relay though it implies that the relay will be able to spy on your email traffic.
- A privacy-friendly alternative is to use a VPN *with a dedicated public IP* to bypass this kind of limits. See https://yunohost.org/#/vpn_advantage
- Or it's possible to switch to a different provider", "diagnosis_mail_fcrdns_nok_alternatives_6": "Some providers won't let you configure your reverse DNS (or their feature might be broken...). If your reverse DNS is correctly configured for IPv4, you can try disabling the use of IPv6 when sending emails by running yunohost settings set smtp.allow_ipv6 -v off. Note: this last solution means that you won't be able to send or receive emails from the few IPv6-only servers out there.", "diagnosis_mail_fcrdns_different_from_ehlo_domain": "The reverse DNS is not correctly configured in IPv{ipversion}. Some emails may fail to get delivered or may get flagged as spam.", "diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "Current reverse DNS: {rdns_domain}
Expected value: {ehlo_domain}", diff --git a/locales/eo.json b/locales/eo.json index d273593a9..8b7346552 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -500,9 +500,9 @@ "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 --human-readable\" el la komandlinio.", "diagnosis_ip_global": "Tutmonda IP: {global} ", "diagnosis_ip_local": "Loka IP: {local} ", diff --git a/locales/es.json b/locales/es.json index f95451922..b057ae54c 100644 --- a/locales/es.json +++ b/locales/es.json @@ -569,7 +569,7 @@ "diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "El DNS inverso actual es: {rdns_domain}
Valor esperado: {ehlo_domain}", "diagnosis_mail_fcrdns_different_from_ehlo_domain": "La resolución de DNS inverso no está correctamente configurada mediante IPv{ipversion}. Algunos correos pueden fallar al ser enviados o pueden ser marcados como basura.", "diagnosis_mail_fcrdns_nok_alternatives_6": "Algunos proveedores no permiten configurar el DNS inverso (o su funcionalidad puede estar rota...). Si tu DNS inverso está configurado correctamente para IPv4, puedes intentar deshabilitarlo para IPv6 cuando envies correos mediante el comando yunohost settings set smtp.allow_ipv6 -v off. Nota: esta solución quiere decir que no podrás enviar ni recibir correos con los pocos servidores que utilizan exclusivamente IPv6.", - "diagnosis_mail_fcrdns_nok_alternatives_4": "Algunos proveedores no te permitirán que configures un DNS inverso (o puede que esta opción esté rota...). Si estás sufriendo problemas por este asunto, quizás te sirvan las siguientes soluciones:
- Algunos ISP proporcionan una alternativa mediante el uso de un relay de servidor de correo aunque esto implica que el relay podrá espiar tu tráfico de correo electrónico.
- Una solución amigable con la privacidad es utilizar una VPN con una *IP pública dedicada* para evitar este tipo de limitaciones. Mira en https://yunohost.org/#/vpn_advantage
- Quizás tu solución sea cambiar de proveedor de internet", + "diagnosis_mail_fcrdns_nok_alternatives_4": "Algunos proveedores no te permitirán que configures un DNS inverso (o puede que esta opción esté rota...). Si estás sufriendo problemas por este asunto, quizás te sirvan las siguientes soluciones:
- Algunos ISP proporcionan una alternativa mediante el uso de un relay de servidor de correo aunque esto implica que el relay podrá espiar tu tráfico de correo electrónico.
- Una solución amigable con la privacidad es utilizar una VPN con una *IP pública dedicada* para evitar este tipo de limitaciones. Mira en https://yunohost.org/#/vpn_advantage
- Quizás tu solución sea cambiar de proveedor de internet", "diagnosis_mail_fcrdns_nok_details": "Primero deberías intentar configurar el DNS inverso mediante {ehlo_domain} en la interfaz de internet de tu router o en la de tu proveedor de internet. (Algunos proveedores de internet en ocasiones necesitan que les solicites un ticket de soporte para ello).", "diagnosis_mail_fcrdns_dns_missing": "No hay definida ninguna DNS inversa mediante IPv{ipversion}. Algunos correos puede que fallen al enviarse o puede que se marquen como basura.", "diagnosis_mail_fcrdns_ok": "¡Las DNS inversas están bien configuradas!", @@ -582,7 +582,7 @@ "diagnosis_mail_ehlo_unreachable_details": "No pudo abrirse la conexión en el puerto 25 de tu servidor mediante IPv{ipversion}. Parece que no se puede contactar.
1. La causa más común en estos casos suele ser que el puerto 25 no está correctamente redireccionado a tu servidor.
2. También deberías asegurarte que el servicio postfix está en marcha.
3. En casos más complejos: asegurate que no estén interfiriendo ni el firewall ni el reverse-proxy.", "diagnosis_mail_ehlo_unreachable": "El servidor de correo SMTP no puede contactarse desde el exterior mediante IPv{ipversion}. No puede recibir correos", "diagnosis_mail_ehlo_ok": "¡El servidor de correo SMTP puede contactarse desde el exterior por lo que puede recibir correos!", - "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Algunos proveedores de internet no le permitirán desbloquear el puerto 25 porque no les importa la Neutralidad de la Red.
- Algunos proporcionan una alternativa usando un relay como servidor de correo lo que implica que el relay podrá espiar tu trafico de correo.
- Una alternativa buena para la privacidad es utilizar una VPN *con una IP pública dedicada* para evitar estas limitaciones. Mira en https://yunohost.org/#/vpn_advantage
- Otra alternativa es cambiar de proveedor de inteernet a uno más amable con la Neutralidad de la Red", + "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Algunos proveedores de internet no le permitirán desbloquear el puerto 25 porque no les importa la Neutralidad de la Red.
- Algunos proporcionan una alternativa usando un relay como servidor de correo lo que implica que el relay podrá espiar tu trafico de correo.
- Una alternativa buena para la privacidad es utilizar una VPN *con una IP pública dedicada* para evitar estas limitaciones. Mira en https://yunohost.org/#/vpn_advantage
- Otra alternativa es cambiar de proveedor de inteernet a uno más amable con la Neutralidad de la Red", "diagnosis_backports_in_sources_list": "Parece que apt (el gestor de paquetes) está configurado para usar el repositorio backports. A menos que realmente sepas lo que estás haciendo, desaconsejamos absolutamente instalar paquetes desde backports, ya que pueden provocar comportamientos intestables o conflictos en el sistema.", "diagnosis_basesystem_hardware_model": "El modelo de servidor es {model}", "additional_urls_already_removed": "La URL adicional «{url:s}» ya se ha eliminado para el permiso «{permission:s}»", diff --git a/locales/fr.json b/locales/fr.json index f06acf2e5..3525ea2b5 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -522,12 +522,12 @@ "diagnosis_ip_global": "IP globale : {global}", "diagnosis_ip_local": "IP locale : {local}", "diagnosis_dns_point_to_doc": "Veuillez consulter la documentation sur https://yunohost.org/dns_config si vous avez besoin d’aide pour configurer les enregistrements DNS.", - "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Certains fournisseurs ne vous laisseront pas débloquer le port sortant 25 parce qu’ils ne se soucient pas de la neutralité du Net.
- Certains d’entre eux offrent l’alternative d'utiliser un serveur de messagerie relai bien que cela implique que le relai sera en mesure d’espionner votre trafic de messagerie.
- Une alternative respectueuse de la vie privée consiste à utiliser un VPN *avec une IP publique dédiée* pour contourner ce type de limites. Voir https://yunohost.org/#/vpn_advantage
- Vous pouvez également envisager de passer à un fournisseur plus respectueux de la neutralité du net", + "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Certains fournisseurs ne vous laisseront pas débloquer le port sortant 25 parce qu’ils ne se soucient pas de la neutralité du Net.
- Certains d’entre eux offrent l’alternative d'utiliser un serveur de messagerie relai bien que cela implique que le relai sera en mesure d’espionner votre trafic de messagerie.
- Une alternative respectueuse de la vie privée consiste à utiliser un VPN *avec une IP publique dédiée* pour contourner ce type de limites. Voir https://yunohost.org/#/vpn_advantage
- Vous pouvez également envisager de passer à un fournisseur plus respectueux de la neutralité du net", "diagnosis_mail_ehlo_ok": "Le serveur de messagerie SMTP est accessible de l'extérieur et peut donc recevoir des courriels !", "diagnosis_mail_ehlo_unreachable": "Le serveur de messagerie SMTP est inaccessible de l’extérieur en IPv{ipversion}. Il ne pourra pas recevoir des courriels.", "diagnosis_mail_ehlo_unreachable_details": "Impossible d'ouvrir une connexion sur le port 25 à votre serveur en IPv{ipversion}. Il semble inaccessible.
1. La cause la plus courante de ce problème est que le port 25 n'est pas correctement redirigé vers votre serveur.
2. Vous devez également vous assurer que le service postfix est en cours d'exécution.
3. Sur les configurations plus complexes: assurez-vous qu'aucun pare-feu ou proxy inversé n'interfère.", "diagnosis_mail_ehlo_wrong_details": "Le EHLO reçu par le serveur de diagnostique distant en IPv{ipversion} est différent du domaine de votre serveur.
EHLO reçu: {wrong_ehlo}
Attendu : {right_ehlo}
La cause la plus courante ce problème est que le port 25 n’est pas correctement redirigé vers votre serveur . Vous pouvez également vous assurer qu’aucun pare-feu ou proxy inversé n’interfère.", - "diagnosis_mail_fcrdns_nok_alternatives_4": "Certains fournisseurs ne vous laisseront pas configurer votre DNS inversé (ou leur fonctionnalité pourrait être cassée …). Si vous rencontrez des problèmes à cause de cela, envisagez les solutions suivantes :
- Certains FAI fournissent l’alternative de à l’aide d’un relais de serveur de messagerie bien que cela implique que le relais pourra espionner votre trafic de messagerie.
- Une alternative respectueuse de la vie privée consiste à utiliser un VPN *avec une IP publique dédiée* pour contourner ce type de limites. Voir https://yunohost.org/#/vpn_advantage
- Enfin, il est également possible de changer de fournisseur", + "diagnosis_mail_fcrdns_nok_alternatives_4": "Certains fournisseurs ne vous laisseront pas configurer votre DNS inversé (ou leur fonctionnalité pourrait être cassée …). Si vous rencontrez des problèmes à cause de cela, envisagez les solutions suivantes :
- Certains FAI fournissent l’alternative de à l’aide d’un relais de serveur de messagerie bien que cela implique que le relais pourra espionner votre trafic de messagerie.
- Une alternative respectueuse de la vie privée consiste à utiliser un VPN *avec une IP publique dédiée* pour contourner ce type de limites. Voir https://yunohost.org/#/vpn_advantage
- Enfin, il est également possible de changer de fournisseur", "diagnosis_mail_fcrdns_nok_alternatives_6": "Certains fournisseurs ne vous laisseront pas configurer votre DNS inversé (ou leur fonctionnalité pourrait être cassée ...). Si votre DNS inversé est correctement configuré en IPv4, vous pouvez essayer de désactiver l'utilisation d'IPv6 lors de l'envoi d'e-mails en exécutant yunohost settings set smtp.allow_ipv6 -v off. Remarque: cette dernière solution signifie que vous ne pourrez pas envoyer ou recevoir de courriels avec les quelques serveurs qui ont uniquement de l'IPv6.", "diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "DNS inverse actuel : {rdns_domain}
Valeur attendue : {ehlo_domain}", "diagnosis_mail_blacklist_listed_by": "Votre IP ou domaine {item} est sur liste noire sur {blacklist_name}", diff --git a/locales/it.json b/locales/it.json index 707f3afc2..17a7522e1 100644 --- a/locales/it.json +++ b/locales/it.json @@ -364,7 +364,7 @@ "diagnosis_mail_ehlo_unreachable_details": "Impossibile aprire una connessione sulla porta 25 sul tuo server su IPv{ipversion}. Sembra irraggiungibile.
1. La causa più probabile di questo problema è la porta 25 non correttamente inoltrata al tuo server.
2. Dovresti esser sicuro che il servizio postfix sia attivo.
3. Su setup complessi: assicuratu che nessun firewall o reverse-proxy stia interferendo.", "diagnosis_mail_ehlo_unreachable": "Il server SMTP non è raggiungibile dall'esterno su IPv{ipversion}. Non potrà ricevere email.", "diagnosis_mail_ehlo_ok": "Il server SMTP è raggiungibile dall'esterno e quindi può ricevere email!", - "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Alcuni provider non ti permettono di aprire la porta 25 in uscita perché non gli importa della Net Neutrality.
- Alcuni mettono a disposizione un alternativa attraverso un mail server relay anche se implica che il relay ha la capacità di leggere il vostro traffico email.
- Un alternativa privacy-friendly è quella di usare una VPN *con un indirizzo IP pubblico dedicato* per bypassare questo tipo di limite. Vedi https://yunohost.org/#/vpn_advantage
- Puoi anche prendere in considerazione di cambiare per un provider pro Net Neutrality", + "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Alcuni provider non ti permettono di aprire la porta 25 in uscita perché non gli importa della Net Neutrality.
- Alcuni mettono a disposizione un alternativa attraverso un mail server relay anche se implica che il relay ha la capacità di leggere il vostro traffico email.
- Un alternativa privacy-friendly è quella di usare una VPN *con un indirizzo IP pubblico dedicato* per bypassare questo tipo di limite. Vedi https://yunohost.org/#/vpn_advantage
- Puoi anche prendere in considerazione di cambiare per un provider pro Net Neutrality", "diagnosis_mail_outgoing_port_25_blocked_details": "Come prima cosa dovresti sbloccare la porta 25 in uscita dall'interfaccia del tuo router internet o del tuo hosting provider. (Alcuni hosting provider potrebbero richiedere l'invio di un ticket di supporto per la richiesta).", "diagnosis_mail_outgoing_port_25_blocked": "Il server SMTP non può inviare email ad altri server perché la porta 25 è bloccata in uscita su IPv{ipversion}.", "diagnosis_mail_outgoing_port_25_ok": "Il server SMTP è abile all'invio delle email (porta 25 in uscita non bloccata).", @@ -385,7 +385,7 @@ "diagnosis_mail_ehlo_could_not_diagnose": "Non è possibile verificare se il server mail postfix è raggiungibile dall'esterno su IPv{ipversion}.", "diagnosis_mail_ehlo_wrong": "Un server mail SMTP diverso sta rispondendo su IPv{ipversion}. Probabilmente il tuo server non può ricevere email.", "diagnosis_mail_ehlo_bad_answer_details": "Potrebbe essere un'altra macchina a rispondere al posto del tuo server.", - "diagnosis_mail_fcrdns_nok_alternatives_4": "Alcuni provider non ti permettono di configurare un DNS inverso (o la loro configurazione non funziona...). Se stai avendo problemi a causa di ciò, considera le seguenti soluzioni:
- Alcuni ISP mettono a disposizione un alternativa attraverso un mail server relay anche se implica che il relay ha la capacità di leggere il vostro traffico email.
- Un alternativa privacy-friendly è quella di usare una VPN *con un indirizzo IP pubblico dedicato* per bypassare questo tipo di limite. Vedi https://yunohost.org/#/vpn_advantage
- Puoi anche prendere in considerazione di cambiare internet provider", + "diagnosis_mail_fcrdns_nok_alternatives_4": "Alcuni provider non ti permettono di configurare un DNS inverso (o la loro configurazione non funziona...). Se stai avendo problemi a causa di ciò, considera le seguenti soluzioni:
- Alcuni ISP mettono a disposizione un alternativa attraverso un mail server relay anche se implica che il relay ha la capacità di leggere il vostro traffico email.
- Un alternativa privacy-friendly è quella di usare una VPN *con un indirizzo IP pubblico dedicato* per bypassare questo tipo di limite. Vedi https://yunohost.org/#/vpn_advantage
- Puoi anche prendere in considerazione di cambiare internet provider", "diagnosis_mail_ehlo_wrong_details": "L'EHLO ricevuto dalla diagnostica remota su IPv{ipversion} è differente dal dominio del tuo server.
EHLO ricevuto: {wrong_ehlo}
EHLO atteso: {right_ehlo}
La causa più comune di questo problema è la porta 25 non correttamente inoltrata al tuo server. Oppure assicurati che nessun firewall o reverse-proxy stia interferendo.", "diagnosis_mail_blacklist_ok": "Gli IP e i domini utilizzati da questo server non sembrano essere nelle blacklist", "diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "DNS invero corrente: {rdns_domain}
Valore atteso: {ehlo_domain}", diff --git a/locales/zh_Hans.json b/locales/zh_Hans.json index 78ba55133..c034fa227 100644 --- a/locales/zh_Hans.json +++ b/locales/zh_Hans.json @@ -484,10 +484,10 @@ "diagnosis_rootfstotalspace_warning": "根文件系统总共只有{space}。这可能没问题,但要小心,因为最终您可能很快会用完磁盘空间...建议根文件系统至少有16 GB。", "diagnosis_regenconf_manually_modified_details": "如果你知道自己在做什么的话,这可能是可以的! YunoHost会自动停止更新这个文件... 但是请注意,YunoHost的升级可能包含重要的推荐变化。如果你想,你可以用yunohost tools regen-conf {category} --dry-run --with-diff检查差异,然后用yunohost tools regen-conf {category} --force强制设置为推荐配置", "diagnosis_mail_fcrdns_nok_alternatives_6": "有些供应商不会让你配置你的反向DNS(或者他们的功能可能被破坏......)。如果你的反向DNS正确配置为IPv4,你可以尝试在发送邮件时禁用IPv6,方法是运yunohost settings set smtp.allow_ipv6 -v off。注意:这应视为最后一个解决方案因为这意味着你将无法从少数只使用IPv6的服务器发送或接收电子邮件。", - "diagnosis_mail_fcrdns_nok_alternatives_4": "有些供应商不会让你配置你的反向DNS(或者他们的功能可能被破坏......)。如果您因此而遇到问题,请考虑以下解决方案:
- 一些ISP提供了使用邮件服务器中转的选择,尽管这意味着中转将能够监视您的电子邮件流量。
- 一个有利于隐私的选择是使用VPN*与专用公共IP*来绕过这类限制。见https://yunohost.org/#/vpn_advantage
- 或者可以切换到另一个供应商", + "diagnosis_mail_fcrdns_nok_alternatives_4": "有些供应商不会让你配置你的反向DNS(或者他们的功能可能被破坏......)。如果您因此而遇到问题,请考虑以下解决方案:
- 一些ISP提供了使用邮件服务器中转的选择,尽管这意味着中转将能够监视您的电子邮件流量。
- 一个有利于隐私的选择是使用VPN*与专用公共IP*来绕过这类限制。见https://yunohost.org/#/vpn_advantage
- 或者可以切换到另一个供应商", "diagnosis_mail_ehlo_wrong_details": "远程诊断器在IPv{ipversion}中收到的EHLO与你的服务器的域名不同。
收到的EHLO: {wrong_ehlo}
预期的: {right_ehlo}
这个问题最常见的原因是端口25没有正确转发到你的服务器。另外,请确保没有防火墙或反向代理的干扰。", "diagnosis_mail_ehlo_unreachable_details": "在IPv{ipversion}中无法打开与您服务器的25端口连接。它似乎是不可达的。
1. 这个问题最常见的原因是端口25没有正确转发到你的服务器
2.你还应该确保postfix服务正在运行。
3.在更复杂的设置中:确保没有防火墙或反向代理的干扰。", - "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "一些供应商不会让你解除对出站端口25的封锁,因为他们不关心网络中立性。
- 其中一些供应商提供了使用邮件服务器中继的替代方案,尽管这意味着中继将能够监视你的电子邮件流量。
- 一个有利于隐私的替代方案是使用VPN*,用一个专用的公共IP*绕过这种限制。见https://yunohost.org/#/vpn_advantage
- 你也可以考虑切换到一个更有利于网络中立的供应商", + "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "一些供应商不会让你解除对出站端口25的封锁,因为他们不关心网络中立性。
- 其中一些供应商提供了使用邮件服务器中继的替代方案,尽管这意味着中继将能够监视你的电子邮件流量。
- 一个有利于隐私的替代方案是使用VPN*,用一个专用的公共IP*绕过这种限制。见https://yunohost.org/#/vpn_advantage
- 你也可以考虑切换到一个更有利于网络中立的供应商", "diagnosis_ram_ok": "系统在{total}中仍然有 {available} ({available_percent}%) RAM可用。", "diagnosis_ram_low": "系统有 {available} ({available_percent}%) RAM可用(共{total}个)可用。小心。", "diagnosis_ram_verylow": "系统只有 {available} ({available_percent}%) 内存可用! (在{total}中)", From 91a4dc49929086760f846071ce1ca8f18289edb3 Mon Sep 17 00:00:00 2001 From: MercierCorentin Date: Sun, 1 Aug 2021 19:02:51 +0200 Subject: [PATCH 2648/3170] Create domains test + Remove domain settings file when removing domain --- src/yunohost/domain.py | 5 +- src/yunohost/tests/test_domains.py | 165 +++++++++++++++++++++++++++++ 2 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 src/yunohost/tests/test_domains.py diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index a313cc28e..a45f70c25 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -281,6 +281,9 @@ def domain_remove(operation_logger, domain, remove_apps=False, force=False): # Delete dyndns keys for this domain (if any) os.system("rm -rf /etc/yunohost/dyndns/K%s.+*" % domain) + # Delete settings file for this domain + os.system(f"rm -rf {DOMAIN_SETTINGS_DIR}/{domain}.yml") + # Sometime we have weird issues with the regenconf where some files # appears as manually modified even though they weren't touched ... # There are a few ideas why this happens (like backup/restore nginx @@ -752,7 +755,7 @@ def _load_domain_settings(domains=[]): if unknown_domain != None: raise YunohostValidationError("domain_name_unknown", domain=unknown_domain) else: - domains = domain_list()["domains"] + domains = get_domain_list["domains"] # Create sanitized data diff --git a/src/yunohost/tests/test_domains.py b/src/yunohost/tests/test_domains.py new file mode 100644 index 000000000..c75954118 --- /dev/null +++ b/src/yunohost/tests/test_domains.py @@ -0,0 +1,165 @@ +import pytest + +import yaml +import os + +from moulinette.core import MoulinetteError + +from yunohost.utils.error import YunohostError, YunohostValidationError +from yunohost.utils.dns import get_dns_zone_from_domain +from yunohost.domain import ( + DOMAIN_SETTINGS_DIR, + REGISTRAR_LIST_PATH, + _get_maindomain, + domain_add, + domain_remove, + domain_list, + domain_main_domain, + domain_setting, + domain_dns_conf, + domain_registrar_set, + domain_registrar_catalog +) + +TEST_DOMAINS = [ + "example.tld", + "sub.example.tld", + "other-example.com" +] + +def setup_function(function): + + # Save domain list in variable to avoid multiple calls to domain_list() + domains = domain_list()["domains"] + + # First domain is main domain + if not TEST_DOMAINS[0] in domains: + domain_add(TEST_DOMAINS[0]) + else: + # Reset settings if any + os.system(f"rm -rf {DOMAIN_SETTINGS_DIR}/{TEST_DOMAINS[0]}.yml") + + if not _get_maindomain() == TEST_DOMAINS[0]: + domain_main_domain(TEST_DOMAINS[0]) + + # Clear other domains + for domain in domains: + if domain not in TEST_DOMAINS or domain == TEST_DOMAINS[2]: + # Clean domains not used for testing + domain_remove(domain) + elif domain in TEST_DOMAINS: + # Reset settings if any + os.system(f"rm -rf {DOMAIN_SETTINGS_DIR}/{domain}.yml") + + + # Create classical second domain of not exist + if TEST_DOMAINS[1] not in domains: + domain_add(TEST_DOMAINS[1]) + + # Third domain is not created + + clean() + + +def teardown_function(function): + + clean() + +def clean(): + pass + +# Domains management testing +def test_domain_add(): + assert TEST_DOMAINS[2] not in domain_list()["domains"] + domain_add(TEST_DOMAINS[2]) + assert TEST_DOMAINS[2] in domain_list()["domains"] + +def test_domain_add_existing_domain(): + with pytest.raises(MoulinetteError) as e_info: + assert TEST_DOMAINS[1] in domain_list()["domains"] + domain_add(TEST_DOMAINS[1]) + +def test_domain_remove(): + assert TEST_DOMAINS[1] in domain_list()["domains"] + domain_remove(TEST_DOMAINS[1]) + assert TEST_DOMAINS[1] not in domain_list()["domains"] + +def test_main_domain(): + current_main_domain = _get_maindomain() + assert domain_main_domain()["current_main_domain"] == current_main_domain + +def test_main_domain_change_unknown(): + with pytest.raises(YunohostValidationError) as e_info: + domain_main_domain(TEST_DOMAINS[2]) + +def test_change_main_domain(): + assert _get_maindomain() != TEST_DOMAINS[1] + domain_main_domain(TEST_DOMAINS[1]) + assert _get_maindomain() == TEST_DOMAINS[1] + +# Domain settings testing +def test_domain_setting_get_default_xmpp_main_domain(): + assert TEST_DOMAINS[0] in domain_list()["domains"] + assert domain_setting(TEST_DOMAINS[0], "xmpp") == True + +def test_domain_setting_get_default_xmpp(): + assert domain_setting(TEST_DOMAINS[1], "xmpp") == False + +def test_domain_setting_get_default_ttl(): + assert domain_setting(TEST_DOMAINS[1], "ttl") == 3600 + +def test_domain_setting_set_int(): + domain_setting(TEST_DOMAINS[1], "ttl", "10") + assert domain_setting(TEST_DOMAINS[1], "ttl") == 10 + +def test_domain_setting_set_bool_true(): + domain_setting(TEST_DOMAINS[1], "xmpp", "True") + assert domain_setting(TEST_DOMAINS[1], "xmpp") == True + domain_setting(TEST_DOMAINS[1], "xmpp", "true") + assert domain_setting(TEST_DOMAINS[1], "xmpp") == True + domain_setting(TEST_DOMAINS[1], "xmpp", "t") + assert domain_setting(TEST_DOMAINS[1], "xmpp") == True + domain_setting(TEST_DOMAINS[1], "xmpp", "1") + assert domain_setting(TEST_DOMAINS[1], "xmpp") == True + domain_setting(TEST_DOMAINS[1], "xmpp", "yes") + assert domain_setting(TEST_DOMAINS[1], "xmpp") == True + domain_setting(TEST_DOMAINS[1], "xmpp", "y") + assert domain_setting(TEST_DOMAINS[1], "xmpp") == True + +def test_domain_setting_set_bool_false(): + domain_setting(TEST_DOMAINS[1], "xmpp", "False") + assert domain_setting(TEST_DOMAINS[1], "xmpp") == False + domain_setting(TEST_DOMAINS[1], "xmpp", "false") + assert domain_setting(TEST_DOMAINS[1], "xmpp") == False + domain_setting(TEST_DOMAINS[1], "xmpp", "f") + assert domain_setting(TEST_DOMAINS[1], "xmpp") == False + domain_setting(TEST_DOMAINS[1], "xmpp", "0") + assert domain_setting(TEST_DOMAINS[1], "xmpp") == False + domain_setting(TEST_DOMAINS[1], "xmpp", "no") + assert domain_setting(TEST_DOMAINS[1], "xmpp") == False + domain_setting(TEST_DOMAINS[1], "xmpp", "n") + assert domain_setting(TEST_DOMAINS[1], "xmpp") == False + +def test_domain_settings_unknown(): + with pytest.raises(YunohostValidationError) as e_info: + domain_setting(TEST_DOMAINS[2], "xmpp", "False") + +# DNS utils testing +def test_get_dns_zone_from_domain_existing(): + assert get_dns_zone_from_domain("donate.yunohost.org") == "yunohost.org" + +def test_get_dns_zone_from_domain_not_existing(): + assert get_dns_zone_from_domain("non-existing-domain.yunohost.org") == "yunohost.org" + +# Domain registrar testing +def test_registrar_list_yaml_integrity(): + yaml.load(open(REGISTRAR_LIST_PATH, 'r')) + +def test_domain_registrar_catalog(): + domain_registrar_catalog() + +def test_domain_registrar_catalog_full(): + domain_registrar_catalog(None, True) + +def test_domain_registrar_catalog_registrar(): + domain_registrar_catalog("ovh") From 97bdedd72304aafd9e86f07f9476881a3ba06fa2 Mon Sep 17 00:00:00 2001 From: MercierCorentin Date: Sun, 1 Aug 2021 19:04:51 +0200 Subject: [PATCH 2649/3170] Migrate from public suffix to public suffix list --- data/hooks/diagnosis/12-dnsrecords.py | 4 ++-- src/yunohost/utils/dns.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py index 719ce4d6a..33789fd84 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -4,7 +4,7 @@ import os import re from datetime import datetime, timedelta -from publicsuffix import PublicSuffixList +from publicsuffixlist import PublicSuffixList from moulinette.utils.process import check_output @@ -37,7 +37,7 @@ class DNSRecordsDiagnoser(Diagnoser): # Check if a domain buy by the user will expire soon psl = PublicSuffixList() domains_from_registrar = [ - psl.get_public_suffix(domain) for domain in all_domains + psl.publicsuffix(domain) for domain in all_domains ] domains_from_registrar = [ domain for domain in domains_from_registrar if "." in domain diff --git a/src/yunohost/utils/dns.py b/src/yunohost/utils/dns.py index fd7cb1334..46b294602 100644 --- a/src/yunohost/utils/dns.py +++ b/src/yunohost/utils/dns.py @@ -18,7 +18,7 @@ along with this program; if not, see http://www.gnu.org/licenses """ -from publicsuffix import PublicSuffixList +from publicsuffixlist import PublicSuffixList from yunohost.utils.network import dig YNH_DYNDNS_DOMAINS = ["nohost.me", "noho.st", "ynh.fr"] @@ -31,7 +31,7 @@ def get_public_suffix(domain): # Load domain public suffixes psl = PublicSuffixList() - public_suffix = psl.get_public_suffix(domain) + public_suffix = psl.publicsuffix(domain) if public_suffix in YNH_DYNDNS_DOMAINS: domain_prefix = domain_name[0:-(1 + len(public_suffix))] public_suffix = domain_prefix.plit(".")[-1] + "." + public_suffix From 9a433a5c29a9b3d727775d9a8b703db39d2ed9d8 Mon Sep 17 00:00:00 2001 From: yalh76 Date: Mon, 2 Aug 2021 21:18:02 +0200 Subject: [PATCH 2650/3170] Update php --- data/helpers.d/php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/php b/data/helpers.d/php index 917399275..7c91d89d2 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -581,7 +581,7 @@ ynh_composer_exec () { workdir="${workdir:-$final_path}" phpversion="${phpversion:-$YNH_PHP_VERSION}" - COMPOSER_HOME="$workdir/.composer" \ + COMPOSER_HOME="$workdir/.composer" COMPOSER_MEMORY_LIMIT=-1 \ php${phpversion} "$workdir/composer.phar" $commands \ -d "$workdir" --quiet --no-interaction } From 9e79181b38b756eee468666bf7522c2910dffe7e Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Mon, 2 Aug 2021 19:39:19 +0000 Subject: [PATCH 2651/3170] [CI] Format code --- src/yunohost/app.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 2ca931a90..fd9a0c37d 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -129,9 +129,8 @@ def app_search(string): # Selecting apps according to a match in app name or description matching_apps = {"apps": {}} for app in catalog_of_apps["apps"].items(): - if ( - re.search(string, app[0], flags=re.IGNORECASE) - or re.search(string, app[1]["description"], flags=re.IGNORECASE) + if re.search(string, app[0], flags=re.IGNORECASE) or re.search( + string, app[1]["description"], flags=re.IGNORECASE ): matching_apps["apps"][app[0]] = app[1] From cfa7255e3ae53121caa2ca1cd0a7d7e28475558e Mon Sep 17 00:00:00 2001 From: Christian Wehrli Date: Fri, 9 Jul 2021 20:57:15 +0000 Subject: [PATCH 2652/3170] Translated using Weblate (German) Currently translated at 98.2% (622 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/locales/de.json b/locales/de.json index e52819f97..735252929 100644 --- a/locales/de.json +++ b/locales/de.json @@ -158,10 +158,10 @@ "certmanager_domain_http_not_working": "Es scheint, als ob die Domäne {domain:s} nicht über HTTP erreicht werden kann. Bitte überprüfen Sie, ob Ihre DNS- und nginx-Konfiguration in Ordnung ist. (Wenn Sie wissen was Sie tun, nutzen Sie \"--no-checks\" um die Überprüfung zu überspringen.)", "certmanager_domain_dns_ip_differs_from_public_ip": "Der DNS-A-Eintrag der Domain {domain:s} unterscheidet sich von dieser Server-IP. Für weitere Informationen überprüfen Sie bitte die 'DNS records' (basic) Kategorie in der Diagnose. Wenn Sie gerade Ihren A-Eintrag verändert haben, warten Sie bitte etwas, damit die Änderungen wirksam werden (Sie können die DNS-Propagation mittels Website überprüfen) (Wenn Sie wissen was Sie tun, können Sie --no-checks benutzen, um diese Überprüfung zu überspringen.)", "certmanager_cannot_read_cert": "Es ist ein Fehler aufgetreten, als es versucht wurde das aktuelle Zertifikat für die Domain {domain:s} zu öffnen (Datei: {file:s}), Grund: {reason:s}", - "certmanager_cert_install_success_selfsigned": "Ein selbstsigniertes Zertifikat für die Domain {domain:s} wurde erfolgreich installiert", + "certmanager_cert_install_success_selfsigned": "Das selbstsignierte Zertifikat für die Domäne '{domain:s}' wurde erfolgreich installiert", "certmanager_cert_install_success": "Let's-Encrypt-Zertifikat für die Domäne {domain:s} ist jetzt installiert", "certmanager_cert_renew_success": "Das Let's Encrypt Zertifikat für die Domain {domain:s} wurde erfolgreich erneuert", - "certmanager_hit_rate_limit": "Es wurden innerhalb kurzer Zeit zu viele Zertifikate für dieselbe Domain {domain:s} ausgestellt. Bitte versuchen Sie es später nochmal. Besuchen Sie https://letsencrypt.org/docs/rate-limits/ für mehr Informationen", + "certmanager_hit_rate_limit": "Es wurden innerhalb kurzer Zeit zu viele Zertifikate für dieselbe Domäne {domain:s} ausgestellt. Bitte versuchen Sie es später nochmal. Besuchen Sie https://letsencrypt.org/docs/rate-limits/ für mehr Informationen", "certmanager_cert_signing_failed": "Das neue Zertifikat konnte nicht signiert werden", "certmanager_no_cert_file": "Die Zertifikatsdatei für die Domain {domain:s} (Datei: {file:s}) konnte nicht gelesen werden", "domain_cannot_remove_main": "Die primäre Domain konnten nicht entfernt werden. Lege zuerst einen neue primäre Domain Sie können die Domäne '{domain:s}' nicht entfernen, weil Sie die Hauptdomäne ist. Sie müssen zuerst eine andere Domäne als Hauptdomäne festlegen. Sie können das mit dem Befehl 'yunohost domain main-domain -n tun. Hier ist eine Liste der möglichen Domänen: {other_domains:s}", @@ -269,7 +269,7 @@ "log_app_makedefault": "Mache '{}' zur Standard-Applikation", "hook_json_return_error": "Konnte die Rückkehr vom Einsprungpunkt {path:s} nicht lesen. Fehler: {msg:s}. Unformatierter Inhalt: {raw_content}", "app_full_domain_unavailable": "Es tut uns leid, aber diese Applikation erfordert die Installation auf einer eigenen Domain, aber einige andere Applikationen sind bereits auf der Domäne'{domain}' installiert. Eine mögliche Lösung ist das Hinzufügen und Verwenden einer Subdomain, die dieser Applikation zugeordnet ist.", - "app_install_failed": "{app} kann nicht installiert werden: {error}", + "app_install_failed": "Installation von {app} fehlgeschlagen: {error}", "app_install_script_failed": "Im Installationsscript ist ein Fehler aufgetreten", "app_remove_after_failed_install": "Entfernen der App nach fehlgeschlagener Installation...", "app_upgrade_script_failed": "Es ist ein Fehler im App-Upgrade-Skript aufgetreten", @@ -310,7 +310,7 @@ "backup_archive_corrupted": "Das Backup-Archiv '{archive}' scheint beschädigt: {error}", "backup_archive_cant_retrieve_info_json": "Die Informationen für das Archiv '{archive}' konnten nicht geladen werden... Die Datei info.json wurde nicht gefunden (oder ist kein gültiges json).", "app_packaging_format_not_supported": "Diese App kann nicht installiert werden da das Paketformat nicht von der YunoHost-Version unterstützt wird. Denken Sie darüber nach das System zu aktualisieren.", - "certmanager_domain_not_diagnosed_yet": "Für die Domäne {domain} gibt es noch keine Diagnoseresultate. Bitte wiederholen Sie die Diagnose für die Kategorien 'DNS-Einträge' und 'Web' im Diagnose-Bereich um zu überprüfen ob die Domäne für Let's Encrypt bereit ist. (Oder wenn Sie wissen was Sie tun, verwenden Sie '--no-checks' um diese Überprüfungen abzuschalten.)", + "certmanager_domain_not_diagnosed_yet": "Für die Domain {domain} gibt es noch keine Diagnose-Resultate. Bitte widerhole die Diagnose für die Kategorien 'DNS records' und 'Web' im Diagnose-Bereich um zu überprüfen ob die Domain für Let's Encrypt bereit ist. (Wenn du weißt was du tust, kannst du --no-checks benutzen, um diese Überprüfung zu überspringen.)", "migration_0015_patching_sources_list": "sources.lists wird repariert...", "migration_0015_start": "Start der Migration auf Buster", "migration_description_0015_migrate_to_buster": "Auf Debian Buster und YunoHost 4.x upgraden", From a0ee1af8c72b9a93f05f4e5a95e6477db6c4e6d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M?= Date: Sat, 10 Jul 2021 05:13:43 +0000 Subject: [PATCH 2653/3170] Translated using Weblate (Galician) Currently translated at 32.8% (208 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/gl/ --- locales/gl.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/gl.json b/locales/gl.json index 5aea67be4..5560c5bd6 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -205,5 +205,6 @@ "diagnosis_diskusage_verylow": "A almacenaxe {mountpoint} (no dispositivo {device}) só lle queda {free} ({free_percent}%) de espazo libre (de {total}). Deberías considerar liberar algún espazo!", "diagnosis_services_bad_status_tip": "Podes intentar reiniciar o servizo, e se isto non funciona, mira os rexistros do servizo na webadmin (desde a liña de comandos con yunohost service restart {service} e yunohost service log {service}).", "diagnosis_mail_outgoing_port_25_ok": "O servidor de email SMTP pode enviar emails (porto 25 de saída non está bloqueado).", - "diagnosis_swap_tip": "Por favor ten en conta que se o servidor ten a swap instalada nunha tarxeta SD ou almacenaxe SSD podería reducir drásticamente a expectativa de vida do dispositivo." + "diagnosis_swap_tip": "Por favor ten en conta que se o servidor ten a swap instalada nunha tarxeta SD ou almacenaxe SSD podería reducir drásticamente a expectativa de vida do dispositivo.", + "diagnosis_mail_outgoing_port_25_blocked": "O servidor SMTP de email non pode enviar emails a outros servidores porque o porto saínte 25 está bloqueado en IPv{ipversion}." } From 0c0a28aadbcf0da162f631bc23db46acdc5e9447 Mon Sep 17 00:00:00 2001 From: Christian Wehrli Date: Sat, 10 Jul 2021 22:40:00 +0000 Subject: [PATCH 2654/3170] Translated using Weblate (German) Currently translated at 99.3% (629 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/locales/de.json b/locales/de.json index 735252929..7fb22c716 100644 --- a/locales/de.json +++ b/locales/de.json @@ -124,19 +124,19 @@ "upnp_dev_not_found": "Es konnten keine UPnP Geräte gefunden werden", "upnp_disabled": "UPnP deaktiviert", "upnp_enabled": "UPnP aktiviert", - "upnp_port_open_failed": "UPnP Ports konnten nicht geöffnet werden", + "upnp_port_open_failed": "UPnP Port konnte nicht geöffnet werden.", "user_created": "Der Benutzer wurde erstellt", - "user_creation_failed": "Nutzer konnte nicht erstellt werden", + "user_creation_failed": "Benutzer konnte nicht erstellt werden {user}: {error}", "user_deleted": "Der Benutzer wurde entfernt", - "user_deletion_failed": "Nutzer konnte nicht gelöscht werden", - "user_home_creation_failed": "Benutzer Home konnte nicht erstellt werden", + "user_deletion_failed": "Benutzer konnte nicht gelöscht werden {user}: {error}", + "user_home_creation_failed": "Persönlicher Ordner des Benutzers konnte nicht erstellt werden", "user_unknown": "Unbekannter Benutzer: {user:s}", - "user_update_failed": "Benutzer kann nicht aktualisiert werden", + "user_update_failed": "Benutzer konnte nicht aktualisiert werden {user}: {error}", "user_updated": "Der Benutzer wurde aktualisiert", "yunohost_already_installed": "YunoHost ist bereits installiert", "yunohost_configured": "YunoHost wurde konfiguriert", - "yunohost_installing": "YunoHost wird installiert…", - "yunohost_not_installed": "YunoHost ist nicht oder unvollständig installiert worden. Bitte 'yunohost tools postinstall' ausführen", + "yunohost_installing": "YunoHost wird installiert...", + "yunohost_not_installed": "YunoHost ist nicht oder nur unvollständig installiert worden. Bitte 'yunohost tools postinstall' ausführen", "app_not_properly_removed": "{app:s} wurde nicht ordnungsgemäß entfernt", "not_enough_disk_space": "Nicht genügend Speicherplatz auf '{path:s}' frei", "backup_creation_failed": "Konnte Backup-Archiv nicht erstellen", @@ -191,8 +191,8 @@ "dyndns_could_not_check_provide": "Konnte nicht überprüft, ob {provider:s} die Domain(s) {domain:s} bereitstellen kann.", "domain_dns_conf_is_just_a_recommendation": "Dieser Befehl zeigt Ihnen die *empfohlene* Konfiguration. Die DNS-Konfiguration wird NICHT für Sie eingerichtet. Es liegt in Ihrer Verantwortung, Ihre DNS-Zone in Ihrem Registrar gemäß dieser Empfehlung zu konfigurieren.", "dpkg_lock_not_available": "Dieser Befehl kann momentan nicht ausgeführt werden, da anscheinend ein anderes Programm die Sperre von dpkg (dem Systempaket-Manager) verwendet", - "confirm_app_install_thirdparty": "WARNUNG! Diese Applikation ist nicht Teil von YunoHosts Applikations-Katalog. Das Installieren von Drittanbieterapplikationen könnte die Sicherheit und Integrität des System beeinträchtigen. Sie sollten wahrscheinlich NICHT fortfahren, es sei denn, Sie wissen, was Sie tun. Es wird KEIN SUPPORT zur Verfügung stehen, wenn die Applikation nicht funktioniert oder das System zerstört... Wenn Sie das Risiko trotzdem eingehen möchten, tippen Sie '{answers:s}'", - "confirm_app_install_danger": "WARNUNG! Diese Applikation ist noch experimentell (wenn nicht ausdrücklich \"not working\"/\"nicht funktionsfähig\")! Sie sollten sie wahrscheinlich NICHT installieren, es sei denn, Sie wißen, was Sie tun. Es wird keine Unterstützung geleistet, falls diese Applikation nicht funktioniert oder Ihr System zerstört... Falls Sie bereit bist, dieses Risiko einzugehen, tippe '{answers:s}'", + "confirm_app_install_thirdparty": "WARNUNG! Diese Applikation ist nicht Teil des YunoHost-Applikationskatalogs. Das Installieren von Drittanbieterapplikationen könnte die Sicherheit und Integrität Ihres Systems beeinträchtigen. Sie sollten wahrscheinlich NICHT fortfahren, es sei denn, Sie wissen, was Sie tun. Es wird KEIN SUPPORT angeboten, wenn die Applikation nicht funktionieren oder Ihr System beschädigen sollte... Wenn Sie das Risiko trotzdem eingehen möchten, tippen Sie '{answers:s}'", + "confirm_app_install_danger": "WARNUNG! Diese Applikation ist noch experimentell (wenn nicht ausdrücklich nicht funktionsfähig)! Sie sollten Sie wahrscheinlich NICHT installieren, es sei denn, Sie wissen, was Sie tun. Es wird keine Unterstützung angeboten, falls diese Applikation nicht funktionieren oder Ihr System beschädigen sollte... Falls Sie bereit sind, dieses Risiko einzugehen, tippen Sie '{answers:s}'", "confirm_app_install_warning": "Warnung: Diese Applikation funktioniert möglicherweise, ist jedoch nicht gut in YunoHost integriert. Einige Funktionen wie Single Sign-On und Backup / Restore sind möglicherweise nicht verfügbar. Trotzdem installieren? [{answers:s}] ", "backup_with_no_restore_script_for_app": "{app:s} hat kein Wiederherstellungsskript. Das Backup dieser App kann nicht automatisch wiederhergestellt werden.", "backup_with_no_backup_script_for_app": "Die App {app:s} hat kein Sicherungsskript. Ignoriere es.", @@ -293,7 +293,7 @@ "diagnosis_ip_no_ipv6": "Der Server hat kein funktionierendes IPv6.", "diagnosis_ip_not_connected_at_all": "Der Server scheint überhaupt nicht mit dem Internet verbunden zu sein!?", "diagnosis_failed_for_category": "Diagnose fehlgeschlagen für die Kategorie '{category}': {error}", - "diagnosis_cache_still_valid": "(Cache noch gültig für {category} Diagnose. Werde keine neue Diagnose durchführen!)", + "diagnosis_cache_still_valid": "(Der Cache für die Diagnose {category} ist immer noch gültig . Es wird momentan keine neue Diagnose durchgeführt!)", "diagnosis_cant_run_because_of_dep": "Kann Diagnose für {category} nicht ausführen während wichtige Probleme zu {dep} noch nicht behoben sind.", "diagnosis_found_errors_and_warnings": "Habe {errors} erhebliche(s) Problem(e) (und {warnings} Warnung(en)) in Verbindung mit {category} gefunden!", "diagnosis_ip_broken_dnsresolution": "Domänen-Namens-Auflösung scheint aus einem bestimmten Grund nicht zu funktionieren... Blockiert eine Firewall die DNS Anfragen?", @@ -422,7 +422,7 @@ "diagnosis_http_hairpinning_issue": "In Ihrem lokalen Netzwerk scheint Hairpinning nicht aktiviert zu sein.", "diagnosis_ports_needed_by": "Diesen Port zu öffnen ist nötig, um die Funktionalität des Typs {category} (service {service}) zu gewährleisten", "diagnosis_mail_queue_too_big": "Zu viele anstehende Nachrichten in der Warteschlange ({nb_pending} emails)", - "diagnosis_package_installed_from_sury_details": "Einige Pakete wurden versehentlich aus einem Drittanbieter-Repository genannt Sury installiert. Das YunoHost-Team hat die Strategie um diese Pakete zu handhaben verbessert, aber es wird erwartet, dass einige Setups, welche PHP7.3-Applikationen installiert haben und immer noch auf Strech laufen ein paar Inkonsistenzen aufweisen. Um diese Situation zu beheben sollten Sie versuchen den folgenden Befehl auszuführen: {cmd_to_fix}", + "diagnosis_package_installed_from_sury_details": "Einige Pakete wurden unbeabsichtigterweise aus einem Drittanbieter-Repository, genannt Sury, installiert. Das YunoHost-Team hat die Strategie, um diese Pakete zu handhaben, verbessert, aber es wird erwartet, dass einige Setups, welche PHP7.3-Applikationen installiert haben und immer noch auf Strech laufen, ein paar Inkonsistenzen aufweisen. Um diese Situation zu beheben, sollten Sie versuchen, den folgenden Befehl auszuführen: {cmd_to_fix}", "domain_cannot_add_xmpp_upload": "Eine hinzugefügte Domain darf nicht mit 'xmpp-upload.' beginnen. Dieser Name ist für das XMPP-Upload-Feature von YunoHost reserviert.", "group_cannot_be_deleted": "Die Gruppe {group} kann nicht manuell entfernt werden.", "group_cannot_edit_primary_group": "Die Gruppe '{group}' kann nicht manuell bearbeitet werden. Es ist die primäre Gruppe, welche dazu gedacht ist, nur einen spezifischen Benutzer zu enthalten.", From bf15a6e4531ec2087022d2ea29377a126ebacd50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M?= Date: Sat, 17 Jul 2021 14:37:20 +0000 Subject: [PATCH 2655/3170] Translated using Weblate (Galician) Currently translated at 33.4% (212 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/gl/ --- locales/gl.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/locales/gl.json b/locales/gl.json index 5560c5bd6..7512f6a60 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -206,5 +206,9 @@ "diagnosis_services_bad_status_tip": "Podes intentar reiniciar o servizo, e se isto non funciona, mira os rexistros do servizo na webadmin (desde a liña de comandos con yunohost service restart {service} e yunohost service log {service}).", "diagnosis_mail_outgoing_port_25_ok": "O servidor de email SMTP pode enviar emails (porto 25 de saída non está bloqueado).", "diagnosis_swap_tip": "Por favor ten en conta que se o servidor ten a swap instalada nunha tarxeta SD ou almacenaxe SSD podería reducir drásticamente a expectativa de vida do dispositivo.", - "diagnosis_mail_outgoing_port_25_blocked": "O servidor SMTP de email non pode enviar emails a outros servidores porque o porto saínte 25 está bloqueado en IPv{ipversion}." + "diagnosis_mail_outgoing_port_25_blocked": "O servidor SMTP de email non pode enviar emails a outros servidores porque o porto saínte 25 está bloqueado en IPv{ipversion}.", + "diagnosis_mail_ehlo_unreachable": "O servidor de email SMTP non é accesible desde o exterior en IPv{ipversion}. Non poderá recibir emails.", + "diagnosis_mail_ehlo_ok": "O servidor de email SMTP é accesible desde o exterior e por tanto pode recibir emails!", + "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Algúns provedores non che van permitir desbloquear o porto 25 saínte porque non se preocupan pola Neutralidade da Rede.
- Algúns deles dan unha alternativa usando un repetidor de servidor de email mais isto implica que o repetidor poderá espiar todo o teu tráfico de email.
- Unha alternativa é utilizar unha VPN *cun IP público dedicado* para evitar este tipo de limitación. Le https://yunohost.org/#/vpn_advantage
- Tamén podes considerar cambiar a un provedor máis amigable coa neutralidade da rede", + "diagnosis_mail_outgoing_port_25_blocked_details": "Antes deberías intentar desbloquear o porto 25 saínte no teu rúter de internet ou na web do provedor de hospedaxe. (Algúns provedores poderían pedirche que fagas unha solicitude para isto)." } From 20833f515ebc6a35d0d76c54bb12bcbc2f933a30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M?= Date: Tue, 20 Jul 2021 04:52:00 +0000 Subject: [PATCH 2656/3170] Translated using Weblate (Galician) Currently translated at 34.1% (216 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/gl/ --- locales/gl.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/locales/gl.json b/locales/gl.json index 7512f6a60..df3c0efd5 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -210,5 +210,9 @@ "diagnosis_mail_ehlo_unreachable": "O servidor de email SMTP non é accesible desde o exterior en IPv{ipversion}. Non poderá recibir emails.", "diagnosis_mail_ehlo_ok": "O servidor de email SMTP é accesible desde o exterior e por tanto pode recibir emails!", "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Algúns provedores non che van permitir desbloquear o porto 25 saínte porque non se preocupan pola Neutralidade da Rede.
- Algúns deles dan unha alternativa usando un repetidor de servidor de email mais isto implica que o repetidor poderá espiar todo o teu tráfico de email.
- Unha alternativa é utilizar unha VPN *cun IP público dedicado* para evitar este tipo de limitación. Le https://yunohost.org/#/vpn_advantage
- Tamén podes considerar cambiar a un provedor máis amigable coa neutralidade da rede", - "diagnosis_mail_outgoing_port_25_blocked_details": "Antes deberías intentar desbloquear o porto 25 saínte no teu rúter de internet ou na web do provedor de hospedaxe. (Algúns provedores poderían pedirche que fagas unha solicitude para isto)." + "diagnosis_mail_outgoing_port_25_blocked_details": "Antes deberías intentar desbloquear o porto 25 saínte no teu rúter de internet ou na web do provedor de hospedaxe. (Algúns provedores poderían pedirche que fagas unha solicitude para isto).", + "diagnosis_mail_ehlo_wrong": "Un servidor de email SMPT diferente responde en IPv{version}. O teu servidor probablemente non poida recibir emails.", + "diagnosis_mail_ehlo_bad_answer_details": "Podería deberse a que outro servidor está a responder no lugar do teu.", + "diagnosis_mail_ehlo_bad_answer": "Un servizo non-SMTP respondeu no porto 25 en IPv{ipversion}", + "diagnosis_mail_ehlo_unreachable_details": "Non se puido abrir unha conexión no porto 25 do teu servidor en IPv{ipversion}. Non semella accesible.
1. A causa máis habitual é que o porto 25 non está correctamente redirixido no servidor.
2. Asegúrate tamén de que o servizo postfix está a funcionar.
3. En configuracións máis complexas: asegúrate de que o cortalumes ou reverse-proxy non están interferindo." } From f806dce2da02daf26f4e7a9abc12397e35ce715e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M?= Date: Wed, 21 Jul 2021 04:45:12 +0000 Subject: [PATCH 2657/3170] Translated using Weblate (Galician) Currently translated at 35.0% (222 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/gl/ --- locales/gl.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/locales/gl.json b/locales/gl.json index df3c0efd5..8f291c4d5 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -214,5 +214,11 @@ "diagnosis_mail_ehlo_wrong": "Un servidor de email SMPT diferente responde en IPv{version}. O teu servidor probablemente non poida recibir emails.", "diagnosis_mail_ehlo_bad_answer_details": "Podería deberse a que outro servidor está a responder no lugar do teu.", "diagnosis_mail_ehlo_bad_answer": "Un servizo non-SMTP respondeu no porto 25 en IPv{ipversion}", - "diagnosis_mail_ehlo_unreachable_details": "Non se puido abrir unha conexión no porto 25 do teu servidor en IPv{ipversion}. Non semella accesible.
1. A causa máis habitual é que o porto 25 non está correctamente redirixido no servidor.
2. Asegúrate tamén de que o servizo postfix está a funcionar.
3. En configuracións máis complexas: asegúrate de que o cortalumes ou reverse-proxy non están interferindo." + "diagnosis_mail_ehlo_unreachable_details": "Non se puido abrir unha conexión no porto 25 do teu servidor en IPv{ipversion}. Non semella accesible.
1. A causa máis habitual é que o porto 25 non está correctamente redirixido no servidor.
2. Asegúrate tamén de que o servizo postfix está a funcionar.
3. En configuracións máis complexas: asegúrate de que o cortalumes ou reverse-proxy non están interferindo.", + "diagnosis_mail_fcrdns_nok_details": "Deberías intentar configurar o DNS inverso con {ehlo_domain} na interface do teu rúter de internet ou na interface do teu provedor de hospedaxe. (Algúns provedores de hospedaxe poderían pedirche que lle fagas unha solicitude por escrito para isto).", + "diagnosis_mail_fcrdns_dns_missing": "Non hai DNS inverso definido en IPv{ipversion}. Algúns emails poderían non ser entregrado ou ser marcados como spam.", + "diagnosis_mail_fcrdns_ok": "O DNS inverso está correctamente configurado!", + "diagnosis_mail_ehlo_could_not_diagnose_details": "Erro: {erro}", + "diagnosis_mail_ehlo_could_not_diagnose": "Non se puido determinar se o servidor de email postfix é accesible desde o exterior en IPv{version}.", + "diagnosis_mail_ehlo_wrong_details": "O EHLO recibido polo diagnosticador remoto en IPv{ipversion} é diferente ao dominio do teu servidor.
EHLO recibido: {wrong_ehlo}
Agardado: {right_ehlo}
A razón máis habitual para este problema é que o porto 25 non está correctamente redirixido ao teu servidor. Alternativamente, asegúrate de non ter un cortalumes ou reverse-proxy interferindo." } From f2b5862696c84a8147bb29d1262cb3a2092b7c0d Mon Sep 17 00:00:00 2001 From: Christian Wehrli Date: Wed, 21 Jul 2021 09:05:25 +0000 Subject: [PATCH 2658/3170] Translated using Weblate (French) Currently translated at 99.6% (631 of 633 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 3525ea2b5..bb1298144 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -569,7 +569,7 @@ "diagnosis_dns_try_dyndns_update_force": "La configuration DNS de ce domaine devrait être automatiquement gérée par YunoHost. Si ce n'est pas le cas, vous pouvez essayer de forcer une mise à jour en utilisant yunohost dyndns update --force.", "app_packaging_format_not_supported": "Cette application ne peut pas être installée car son format n'est pas pris en charge par votre version de YunoHost. Vous devriez probablement envisager de mettre à jour votre système.", "migration_0015_weak_certs": "Il a été constaté que les certificats suivants utilisent encore des algorithmes de signature peu robustes et doivent être mis à jour pour être compatibles avec la prochaine version de NGINX : {certs}", - "global_settings_setting_backup_compress_tar_archives": "Lors de la création de nouvelles sauvegardes, compresse les archives au format '.tar.gz' plutôt que des archives non-compressées au format '.tar'. Notez qu'activer cette option permet d'obtenir des sauvegardes plus légères, mais leur création sera significativement plus longue et plus gourmande en CPU.", + "global_settings_setting_backup_compress_tar_archives": "Compresser les archives (.tar.gz) au lieu de créer d'archives non-compressées lors de la création des backups. N.B. Activer cette option implique de créer des archives plus légères, mais aussi d'avoir des procédures de backup significativement plus longues et plus gourmandes en CPU.", "migration_description_0018_xtable_to_nftable": "Migrer les anciennes règles de trafic réseau vers le nouveau système basé sur nftables", "service_description_php7.3-fpm": "Exécute les applications écrites en PHP avec NGINX", "migration_0018_failed_to_reset_legacy_rules": "La réinitialisation des règles iptable par défaut a échoué : {error}", From 13fe09deb92a3610f0346a57cd5862da668e04fd Mon Sep 17 00:00:00 2001 From: Stylix58 Date: Wed, 21 Jul 2021 09:05:05 +0000 Subject: [PATCH 2659/3170] Translated using Weblate (French) Currently translated at 99.6% (631 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index bb1298144..1930673ba 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -586,9 +586,9 @@ "app_manifest_install_ask_password": "Choisissez un mot de passe administrateur pour cette application", "app_manifest_install_ask_path": "Choisissez le chemin sur lequel vous souhaitez installer cette application", "app_manifest_install_ask_domain": "Choisissez le domaine sur lequel vous souhaitez installer cette application", - "global_settings_setting_smtp_relay_user": "Compte de l'utilisateur du relais SMTP", + "global_settings_setting_smtp_relay_user": "Compte d'utilisateur du relais SMTP", "global_settings_setting_smtp_relay_port": "Port du relais SMTP", - "global_settings_setting_smtp_relay_host": "Un relais SMTP permet d'envoyer du courrier à la place de cette instance YunoHost. Cela est utile si vous êtes dans l'une de ces situations : le port 25 est bloqué par votre FAI ou par votre fournisseur VPS, vous avez une IP résidentielle répertoriée sur DUHL, vous ne pouvez pas configurer de reverse DNS, si le serveur n'est pas directement accessible depuis Internet et que vous voulez en utiliser un autre pour envoyer des mails.", + "global_settings_setting_smtp_relay_host": "Un relais SMTP permet d'envoyer du courrier à la place de cette instance YunoHost. Cela est utile si vous êtes dans l'une de ces situations : le port 25 est bloqué par votre FAI ou par votre fournisseur VPS, vous avez une IP résidentielle répertoriée sur DUHL, vous ne pouvez pas configurer de reverse DNS ou le serveur n'est pas directement accessible depuis Internet et que vous voulez en utiliser un autre pour envoyer des mails.", "diagnosis_package_installed_from_sury_details": "Certains paquets ont été installés par inadvertance à partir d'un dépôt tiers appelé Sury. L'équipe YunoHost a amélioré la stratégie de gestion de ces paquets, mais on s'attend à ce que certaines configurations qui ont installé des applications PHP7.3 tout en étant toujours sur Stretch présentent des incohérences. Pour résoudre cette situation, vous devez essayer d'exécuter la commande suivante : {cmd_to_fix}", "app_argument_password_no_default": "Erreur lors de l'analyse de l'argument de mot de passe '{name}' : l'argument de mot de passe ne peut pas avoir de valeur par défaut pour des raisons de sécurité", "pattern_email_forward": "L'adresse électronique doit être valide, le symbole '+' étant accepté (par exemple : johndoe+yunohost@exemple.com)", @@ -620,7 +620,7 @@ "restore_backup_too_old": "Cette sauvegarde ne peut être restaurée car elle provient d'une version trop ancienne de YunoHost.", "migration_update_LDAP_schema": "Mise à jour du schéma LDAP ...", "log_backup_create": "Créer une archive de sauvegarde", - "global_settings_setting_ssowat_panel_overlay_enabled": "Activer la surcouche SSOwat au panel web", + "global_settings_setting_ssowat_panel_overlay_enabled": "Activer la surcouche du panel SSOwat", "migration_ldap_rollback_success": "Système rétabli dans son état initial.", "permission_cant_add_to_all_users": "L'autorisation {permission} ne peut pas être ajoutée à tous les utilisateurs.", "migration_ldap_migration_failed_trying_to_rollback": "Impossible de migrer... tentative de restauration du système.", From 38262b4b49024e99d2b4e25c28bd7e6e6959c4fe Mon Sep 17 00:00:00 2001 From: Stylix58 Date: Wed, 21 Jul 2021 09:08:27 +0000 Subject: [PATCH 2660/3170] Translated using Weblate (French) Currently translated at 100.0% (633 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 1930673ba..700d21820 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -96,7 +96,7 @@ "port_already_closed": "Le port {port:d} est déjà fermé pour les connexions {ip_version:s}", "port_already_opened": "Le port {port:d} est déjà ouvert pour les connexions {ip_version:s}", "restore_already_installed_app": "Une application est déjà installée avec l’identifiant '{app:s}'", - "app_restore_failed": "Impossible de restaurer {app:s} : {error:s}", + "app_restore_failed": "Impossible de restaurer {app :s} : {error :s}", "restore_cleaning_failed": "Impossible de nettoyer le dossier temporaire de restauration", "restore_complete": "Restauration terminée", "restore_confirm_yunohost_installed": "Voulez-vous vraiment restaurer un système déjà installé ? [{answers:s}]", @@ -154,7 +154,7 @@ "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, utilisez '--no-checks' pour désactiver la vérification.)", - "certmanager_domain_dns_ip_differs_from_public_ip": "L'enregistrement DNS du domaine '{domain:s}' est différent de l’adresse IP de ce serveur. Pour plus d'informations, veuillez consulter la catégorie \"Enregistrements DNS\" dans la section diagnostic. 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_domain_dns_ip_differs_from_public_ip": "L'enregistrement DNS du domaine '{domain:s}' est différent de l’adresse IP de ce serveur. Pour plus d'informations, veuillez consulter la catégorie \"Enregistrements DNS\" dans la section diagnostic. 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}", "certmanager_cert_install_success_selfsigned": "Le certificat auto-signé est maintenant installé pour le domaine « {domain:s} »", "certmanager_cert_install_success": "Le certificat Let’s Encrypt est maintenant installé pour le domaine « {domain:s} »", @@ -553,7 +553,7 @@ "restore_already_installed_apps": "Les applications suivantes ne peuvent pas être restaurées car elles sont déjà installées : {apps}", "regenconf_need_to_explicitly_specify_ssh": "La configuration de ssh a été modifiée manuellement. Vous devez explicitement indiquer la mention --force à \"ssh\" pour appliquer les changements.", "migration_0015_cleaning_up": "Nettoyage du cache et des paquets qui ne sont plus nécessaires ...", - "migration_0015_specific_upgrade": "Démarrage de la mise à jour des paquets du système qui doivent l'être de manière indépendante ...", + "migration_0015_specific_upgrade": "Démarrage de la mise à jour des paquets du système qui doivent l'être de manière indépendante...", "migration_0015_modified_files": "Veuillez noter que les fichiers suivants ont été modifiés manuellement et pourraient être écrasés à la suite de la mise à niveau : {manually_modified_files}", "migration_0015_problematic_apps_warning": "Veuillez noter que des applications qui peuvent poser problèmes ont été détectées. Il semble qu'elles n'aient pas été installées à partir du catalogue d'applications YunoHost, ou bien qu'elles ne soient pas signalées comme \"fonctionnelles\". Par conséquent, il n'est pas possible de garantir que les applications suivantes fonctionneront encore après la mise à niveau : {problematic_apps}", "migration_0015_general_warning": "Veuillez noter que cette migration est une opération délicate. L'équipe YunoHost a fait de son mieux pour la revérifier et la tester, mais la migration pourrait quand même casser des éléments du système ou de ses applications.\n\nIl est donc recommandé :\n…- de faire une sauvegarde de toute donnée ou application critique. Plus d'informations ici https://yunohost.org/backup ;\n…- d'être patient après le lancement de la migration. Selon votre connexion internet et votre matériel, la mise à niveau peut prendre jusqu'à quelques heures.", @@ -569,7 +569,7 @@ "diagnosis_dns_try_dyndns_update_force": "La configuration DNS de ce domaine devrait être automatiquement gérée par YunoHost. Si ce n'est pas le cas, vous pouvez essayer de forcer une mise à jour en utilisant yunohost dyndns update --force.", "app_packaging_format_not_supported": "Cette application ne peut pas être installée car son format n'est pas pris en charge par votre version de YunoHost. Vous devriez probablement envisager de mettre à jour votre système.", "migration_0015_weak_certs": "Il a été constaté que les certificats suivants utilisent encore des algorithmes de signature peu robustes et doivent être mis à jour pour être compatibles avec la prochaine version de NGINX : {certs}", - "global_settings_setting_backup_compress_tar_archives": "Compresser les archives (.tar.gz) au lieu de créer d'archives non-compressées lors de la création des backups. N.B. Activer cette option implique de créer des archives plus légères, mais aussi d'avoir des procédures de backup significativement plus longues et plus gourmandes en CPU.", + "global_settings_setting_backup_compress_tar_archives": "Compresser les archives (.tar.gz) au lieu de créer des archives non-compressées lors de la création des backups. N.B. Activer cette option implique de créer des archives plus légères, mais aussi d'avoir des procédures de backup significativement plus longues et plus gourmandes en CPU.", "migration_description_0018_xtable_to_nftable": "Migrer les anciennes règles de trafic réseau vers le nouveau système basé sur nftables", "service_description_php7.3-fpm": "Exécute les applications écrites en PHP avec NGINX", "migration_0018_failed_to_reset_legacy_rules": "La réinitialisation des règles iptable par défaut a échoué : {error}", From ca7d2f481fdd52e99f41e98abcc5e22499578015 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M?= Date: Thu, 22 Jul 2021 05:03:23 +0000 Subject: [PATCH 2661/3170] Translated using Weblate (Galician) Currently translated at 37.4% (237 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/gl/ --- locales/gl.json | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/locales/gl.json b/locales/gl.json index 8f291c4d5..201f2c255 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -220,5 +220,20 @@ "diagnosis_mail_fcrdns_ok": "O DNS inverso está correctamente configurado!", "diagnosis_mail_ehlo_could_not_diagnose_details": "Erro: {erro}", "diagnosis_mail_ehlo_could_not_diagnose": "Non se puido determinar se o servidor de email postfix é accesible desde o exterior en IPv{version}.", - "diagnosis_mail_ehlo_wrong_details": "O EHLO recibido polo diagnosticador remoto en IPv{ipversion} é diferente ao dominio do teu servidor.
EHLO recibido: {wrong_ehlo}
Agardado: {right_ehlo}
A razón máis habitual para este problema é que o porto 25 non está correctamente redirixido ao teu servidor. Alternativamente, asegúrate de non ter un cortalumes ou reverse-proxy interferindo." + "diagnosis_mail_ehlo_wrong_details": "O EHLO recibido polo diagnosticador remoto en IPv{ipversion} é diferente ao dominio do teu servidor.
EHLO recibido: {wrong_ehlo}
Agardado: {right_ehlo}
A razón máis habitual para este problema é que o porto 25 non está correctamente redirixido ao teu servidor. Alternativamente, asegúrate de non ter un cortalumes ou reverse-proxy interferindo.", + "diagnosis_regenconf_manually_modified_details": "Probablemente todo sexa correcto se sabes o que estás a facer! YunoHost non vai actualizar este ficheiro automáticamente... Pero ten en conta que as actualizacións de YunoHost poderían incluír cambios importantes recomendados. Se queres podes ver as diferenzas con yunohost tools regen-conf {category} --dry-run --with-diff e forzar o restablecemento da configuración recomendada con yunohost tools regen-conf {category} --force", + "diagnosis_regenconf_manually_modified": "O ficheiro de configuración {file} semella que foi modificado manualmente.", + "diagnosis_regenconf_allgood": "Tódolos ficheiros de configuración seguen a configuración recomendada!", + "diagnosis_mail_queue_too_big": "Hai demasiados emails pendentes na cola de correo ({nb_pending} emails)", + "diagnosis_mail_queue_unavailable_details": "Erro: {error}", + "diagnosis_mail_queue_unavailable": "Non se pode consultar o número de emails pendentes na cola", + "diagnosis_mail_queue_ok": "{nb_pending} emails pendentes na cola de correo", + "diagnosis_mail_blacklist_website": "Tras ver a razón do bloqueo e arranxalo, considera solicitar que o teu dominio ou IP sexan eliminados de {blacklist_website}", + "diagnosis_mail_blacklist_reason": "A razón do bloqueo é: {reason}", + "diagnosis_mail_blacklist_listed_by": "O teu dominio ou IP {item} está na lista de bloqueo {blacklist_name}", + "diagnosis_mail_blacklist_ok": "Os IPs e dominios utilizados neste servidor non parecen estar en listas de bloqueo", + "diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "DNS inverso actual: {rdns_domain}
Valor agardado: {ehlo_domain}", + "diagnosis_mail_fcrdns_different_from_ehlo_domain": "O DNS inverso non está correctamente configurado para IPv{ipversion}. É posible que non se entreguen algúns emails ou sexan marcados como spam.", + "diagnosis_mail_fcrdns_nok_alternatives_6": "Algúns provedores non che permiten configurar DNS inverso (ou podería non funcionar...). Se o teu DNS inverso está correctamente configurado para IPv4, podes intentar desactivar o uso de IPv6 ao enviar os emails executando yunohost settings set smtp.allow_ipv6 -v off. Nota: esta última solución significa que non poderás enviar ou recibir emails desde os poucos servidores que só usan IPv6 que teñen esta limitación.", + "diagnosis_mail_fcrdns_nok_alternatives_4": "Algúns provedores non che permiten configurar o teu DNS inverso (ou podería non ser funcional...). Se tes problemas debido a isto, considera as seguintes solucións:
- Algúns ISP proporcionan alternativas como usar un repetidor de servidor de correo pero implica que o repetidor pode ver todo o teu tráfico de email.
-Unha alternativa respetuosa coa privacidade é utilizar un VPN *cun IP público dedicado* para evitar estas limitacións. Le https://yunohost.org/#/vpn_advantage
- Ou tamén podes cambiar a un provedor diferente" } From f0a9745cb210644f6df9643dd858e167556439ca Mon Sep 17 00:00:00 2001 From: Bram Date: Wed, 28 Jul 2021 22:23:50 +0000 Subject: [PATCH 2662/3170] Added translation using Weblate (Ukrainian) --- locales/uk.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 locales/uk.json diff --git a/locales/uk.json b/locales/uk.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/locales/uk.json @@ -0,0 +1 @@ +{} From a066d35ef347a8d6f5d8877bf5b99fe39af129f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M?= Date: Thu, 29 Jul 2021 04:33:34 +0000 Subject: [PATCH 2663/3170] Translated using Weblate (Galician) Currently translated at 41.3% (262 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/gl/ --- locales/gl.json | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/locales/gl.json b/locales/gl.json index 201f2c255..f08f58f3c 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -235,5 +235,30 @@ "diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "DNS inverso actual: {rdns_domain}
Valor agardado: {ehlo_domain}", "diagnosis_mail_fcrdns_different_from_ehlo_domain": "O DNS inverso non está correctamente configurado para IPv{ipversion}. É posible que non se entreguen algúns emails ou sexan marcados como spam.", "diagnosis_mail_fcrdns_nok_alternatives_6": "Algúns provedores non che permiten configurar DNS inverso (ou podería non funcionar...). Se o teu DNS inverso está correctamente configurado para IPv4, podes intentar desactivar o uso de IPv6 ao enviar os emails executando yunohost settings set smtp.allow_ipv6 -v off. Nota: esta última solución significa que non poderás enviar ou recibir emails desde os poucos servidores que só usan IPv6 que teñen esta limitación.", - "diagnosis_mail_fcrdns_nok_alternatives_4": "Algúns provedores non che permiten configurar o teu DNS inverso (ou podería non ser funcional...). Se tes problemas debido a isto, considera as seguintes solucións:
- Algúns ISP proporcionan alternativas como usar un repetidor de servidor de correo pero implica que o repetidor pode ver todo o teu tráfico de email.
-Unha alternativa respetuosa coa privacidade é utilizar un VPN *cun IP público dedicado* para evitar estas limitacións. Le https://yunohost.org/#/vpn_advantage
- Ou tamén podes cambiar a un provedor diferente" + "diagnosis_mail_fcrdns_nok_alternatives_4": "Algúns provedores non che permiten configurar o teu DNS inverso (ou podería non ser funcional...). Se tes problemas debido a isto, considera as seguintes solucións:
- Algúns ISP proporcionan alternativas como usar un repetidor de servidor de correo pero implica que o repetidor pode ver todo o teu tráfico de email.
-Unha alternativa respetuosa coa privacidade é utilizar un VPN *cun IP público dedicado* para evitar estas limitacións. Le https://yunohost.org/#/vpn_advantage
- Ou tamén podes cambiar a un provedor diferente", + "diagnosis_http_ok": "O dominio {domain} é accesible a través de HTTP desde o exterior da rede local.", + "diagnosis_http_could_not_diagnose_details": "Erro: {error}", + "diagnosis_http_could_not_diagnose": "Non se puido comprobar se os dominios son accesibles desde o exterior en IPv{ipversion}.", + "diagnosis_http_hairpinning_issue_details": "Isto acontece probablemente debido ao rúter do teu ISP. Como resultado, as persoas externas á túa rede local poderán acceder ao teu servidor tal como se espera, pero non as usuarias na rede local (como ti, probablemente?) cando usan o nome de dominio ou IP global. Podes mellorar a situación lendo https://yunohost.org/dns_local_network", + "diagnosis_http_hairpinning_issue": "A túa rede local semella que non ten hairpinning activado.", + "diagnosis_ports_forwarding_tip": "Para arranxar isto, probablemente tes que configurar o reenvío do porto no teu rúter de internet tal como se di en https://yunohost.org/isp_box_config", + "diagnosis_ports_needed_by": "A apertura deste porto é precisa para {category} (servizo {service})", + "diagnosis_ports_ok": "O porto {port} é accesible desde o exterior.", + "diagnosis_ports_partially_unreachable": "O porto {port} non é accesible desde o exterior en IPv{failed}.", + "diagnosis_ports_unreachable": "O porto {port} non é accesible desde o exterior.", + "diagnosis_ports_could_not_diagnose_details": "Erro: {erro}", + "diagnosis_ports_could_not_diagnose": "Non se puido comprobar se os portos son accesibles desde o exterior en IPv{ipversion}.", + "diagnosis_description_regenconf": "Configuracións do sistema", + "diagnosis_description_mail": "Email", + "diagnosis_description_web": "Web", + "diagnosis_description_ports": "Exposición de portos", + "diagnosis_description_systemresources": "Recursos do sistema", + "diagnosis_description_services": "Comprobación do estado dos servizos", + "diagnosis_description_dnsrecords": "Rexistros DNS", + "diagnosis_description_ip": "Conectividade a internet", + "diagnosis_description_basesystem": "Sistema base", + "diagnosis_security_vulnerable_to_meltdown_details": "Para arranxar isto, deberías actualizar o sistema e reiniciar para cargar o novo kernel linux (ou contactar co provedor do servizo se isto non o soluciona). Le https://meltdownattack.com/ para máis info.", + "diagnosis_security_vulnerable_to_meltdown": "Semella que es vulnerable á vulnerabilidade crítica de seguridade Meltdown", + "diagnosis_rootfstotalspace_critical": "O sistema de ficheiros root só ten un total de {space} e podería ser preocupante! Probablemente esgotes o espazo no disco moi pronto! Recomendamos ter un sistema de ficheiros root de polo menos 16 GB.", + "diagnosis_rootfstotalspace_warning": "O sistema de ficheiros root só ten un total de {space}. Podería ser suficiente, mais pon tino porque poderías esgotar o espazo no disco rápidamente... Recoméndase ter polo meno 16 GB para o sistema de ficheiros root." } From 87faada3c1e26f91c3ed4d58125a9c5d5c6c4681 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M?= Date: Fri, 30 Jul 2021 04:57:20 +0000 Subject: [PATCH 2664/3170] Translated using Weblate (Galician) Currently translated at 43.6% (276 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/gl/ --- locales/gl.json | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/locales/gl.json b/locales/gl.json index f08f58f3c..02d6d9c7d 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -260,5 +260,19 @@ "diagnosis_security_vulnerable_to_meltdown_details": "Para arranxar isto, deberías actualizar o sistema e reiniciar para cargar o novo kernel linux (ou contactar co provedor do servizo se isto non o soluciona). Le https://meltdownattack.com/ para máis info.", "diagnosis_security_vulnerable_to_meltdown": "Semella que es vulnerable á vulnerabilidade crítica de seguridade Meltdown", "diagnosis_rootfstotalspace_critical": "O sistema de ficheiros root só ten un total de {space} e podería ser preocupante! Probablemente esgotes o espazo no disco moi pronto! Recomendamos ter un sistema de ficheiros root de polo menos 16 GB.", - "diagnosis_rootfstotalspace_warning": "O sistema de ficheiros root só ten un total de {space}. Podería ser suficiente, mais pon tino porque poderías esgotar o espazo no disco rápidamente... Recoméndase ter polo meno 16 GB para o sistema de ficheiros root." + "diagnosis_rootfstotalspace_warning": "O sistema de ficheiros root só ten un total de {space}. Podería ser suficiente, mais pon tino porque poderías esgotar o espazo no disco rápidamente... Recoméndase ter polo meno 16 GB para o sistema de ficheiros root.", + "domain_cannot_remove_main": "Non podes eliminar '{domain:s}' porque é o dominio principal, primeiro tes que establecer outro dominio como principal usando 'yunohost domain main-domain -n '; aquí tes a lista dos dominios posibles: {other_domains:s}", + "diagnosis_sshd_config_inconsistent_details": "Executa yunohost settings set security.ssh.port -v O_TEU_PORTO_SSH para definir o porto SSH, comproba con yunohost tools regen-conf ssh --dry-run --with-diff e restablece a configuración con yunohost tools regen-conf ssh --force a configuración recomendada de YunoHost.", + "diagnosis_sshd_config_inconsistent": "Semella que o porto SSH foi modificado manualmente en /etc/ssh/sshd_config. Desde YunoHost 4.2, un novo axuste global 'security.ssh.port' está dispoñible para evitar a edición manual da configuración.", + "diagnosis_sshd_config_insecure": "Semella que a configuración SSH modificouse manualmente, e é insegura porque non contén unha directiva 'AllowGroups' ou 'AllowUsers' para limitar o acceso ás usuarias autorizadas.", + "diagnosis_processes_killed_by_oom_reaper": "Algúns procesos foron apagados recentemente polo sistema porque quedou sen memoria dispoñible. Isto acontece normalmente porque o sistema quedou sen memoria ou un proceso consumía demasiada. Resumo cos procesos apagados:\n{kills_summary}", + "diagnosis_never_ran_yet": "Semella que o servidor foi configurado recentemente e aínda non hai informes diagnósticos. Deberías iniciar un diagnóstico completo, ben desde a administración web ou usando 'yunohost diagnosis run' desde a liña de comandos.", + "diagnosis_unknown_categories": "As seguintes categorías son descoñecidas: {categories}", + "diagnosis_http_nginx_conf_not_up_to_date_details": "Para arranxar a situación, revisa as diferenzas na liña de comandos usando yunohost tools regen-conf nginx --dry-run --with-diff e se todo está ben, aplica os cambios con yunohost tools regen-conf nginx --force.", + "diagnosis_http_nginx_conf_not_up_to_date": "A configuración nginx deste dominio semella foi modificada manualmente, e está evitando que YunoHost comprobe se é accesible a través de HTTP.", + "diagnosis_http_partially_unreachable": "O dominio {domain} non semella accesible a través de HTTP desde o exterior da rede local en IPv{failed}, pero funciona en IPv{passed}.", + "diagnosis_http_unreachable": "O dominio {domain} non semella accesible a través de HTTP desde o exterior da rede local.", + "diagnosis_http_bad_status_code": "Semella que outra máquina (podería ser o rúter de internet) respondeu no lugar do teu servidor.
1. A razón máis habitual para este problema é que o porto 80 (e 443) non están correctamente redirixidos ao teu servidor.
2. En configuracións avanzadas: revisa que nin o cortalumes nin o proxy-inverso están interferindo.", + "diagnosis_http_connection_error": "Erro de conexión: non se puido conectar co dominio solicitado, moi probablemente non sexa accesible.", + "diagnosis_http_timeout": "Caducou a conexión mentras se intentaba contactar o servidor desde o exterior. Non semella accesible.
1. A razón máis habitual é que o porto 80 (e 443) non están correctamente redirixidos ao teu servidor.
2. Deberías comprobar tamén que o servizo nginx está a funcionar
3. En configuracións máis avanzadas: revisa que nin o cortalumes nin o proxy-inverso están interferindo." } From 8ffe585f3f79a90f295be537ba77bf69ae1758b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M?= Date: Tue, 3 Aug 2021 05:19:25 +0000 Subject: [PATCH 2665/3170] Translated using Weblate (Galician) Currently translated at 49.2% (312 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/gl/ --- locales/gl.json | 42 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/locales/gl.json b/locales/gl.json index 02d6d9c7d..3e2068ed9 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -209,7 +209,7 @@ "diagnosis_mail_outgoing_port_25_blocked": "O servidor SMTP de email non pode enviar emails a outros servidores porque o porto saínte 25 está bloqueado en IPv{ipversion}.", "diagnosis_mail_ehlo_unreachable": "O servidor de email SMTP non é accesible desde o exterior en IPv{ipversion}. Non poderá recibir emails.", "diagnosis_mail_ehlo_ok": "O servidor de email SMTP é accesible desde o exterior e por tanto pode recibir emails!", - "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Algúns provedores non che van permitir desbloquear o porto 25 saínte porque non se preocupan pola Neutralidade da Rede.
- Algúns deles dan unha alternativa usando un repetidor de servidor de email mais isto implica que o repetidor poderá espiar todo o teu tráfico de email.
- Unha alternativa é utilizar unha VPN *cun IP público dedicado* para evitar este tipo de limitación. Le https://yunohost.org/#/vpn_advantage
- Tamén podes considerar cambiar a un provedor máis amigable coa neutralidade da rede", + "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Algúns provedores non che van permitir desbloquear o porto 25 saínte porque non se preocupan pola Neutralidade da Rede.
- Algúns deles dan unha alternativa usando un repetidor de servidor de email mais isto implica que o repetidor poderá espiar todo o teu tráfico de email.
- Unha alternativa é utilizar unha VPN *cun IP público dedicado* para evitar este tipo de limitación. Le https://yunohost.org/#/vpn_advantage
- Tamén podes considerar cambiar a un provedor máis amigable coa neutralidade da rede", "diagnosis_mail_outgoing_port_25_blocked_details": "Antes deberías intentar desbloquear o porto 25 saínte no teu rúter de internet ou na web do provedor de hospedaxe. (Algúns provedores poderían pedirche que fagas unha solicitude para isto).", "diagnosis_mail_ehlo_wrong": "Un servidor de email SMPT diferente responde en IPv{version}. O teu servidor probablemente non poida recibir emails.", "diagnosis_mail_ehlo_bad_answer_details": "Podería deberse a que outro servidor está a responder no lugar do teu.", @@ -235,7 +235,7 @@ "diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "DNS inverso actual: {rdns_domain}
Valor agardado: {ehlo_domain}", "diagnosis_mail_fcrdns_different_from_ehlo_domain": "O DNS inverso non está correctamente configurado para IPv{ipversion}. É posible que non se entreguen algúns emails ou sexan marcados como spam.", "diagnosis_mail_fcrdns_nok_alternatives_6": "Algúns provedores non che permiten configurar DNS inverso (ou podería non funcionar...). Se o teu DNS inverso está correctamente configurado para IPv4, podes intentar desactivar o uso de IPv6 ao enviar os emails executando yunohost settings set smtp.allow_ipv6 -v off. Nota: esta última solución significa que non poderás enviar ou recibir emails desde os poucos servidores que só usan IPv6 que teñen esta limitación.", - "diagnosis_mail_fcrdns_nok_alternatives_4": "Algúns provedores non che permiten configurar o teu DNS inverso (ou podería non ser funcional...). Se tes problemas debido a isto, considera as seguintes solucións:
- Algúns ISP proporcionan alternativas como usar un repetidor de servidor de correo pero implica que o repetidor pode ver todo o teu tráfico de email.
-Unha alternativa respetuosa coa privacidade é utilizar un VPN *cun IP público dedicado* para evitar estas limitacións. Le https://yunohost.org/#/vpn_advantage
- Ou tamén podes cambiar a un provedor diferente", + "diagnosis_mail_fcrdns_nok_alternatives_4": "Algúns provedores non che permiten configurar o teu DNS inverso (ou podería non ser funcional...). Se tes problemas debido a isto, considera as seguintes solucións:
- Algúns ISP proporcionan alternativas como usar un repetidor de servidor de correo pero implica que o repetidor pode ver todo o teu tráfico de email.
-Unha alternativa respetuosa coa privacidade é utilizar un VPN *cun IP público dedicado* para evitar estas limitacións. Le https://yunohost.org/#/vpn_advantage
- Ou tamén podes cambiar a un provedor diferente", "diagnosis_http_ok": "O dominio {domain} é accesible a través de HTTP desde o exterior da rede local.", "diagnosis_http_could_not_diagnose_details": "Erro: {error}", "diagnosis_http_could_not_diagnose": "Non se puido comprobar se os dominios son accesibles desde o exterior en IPv{ipversion}.", @@ -274,5 +274,41 @@ "diagnosis_http_unreachable": "O dominio {domain} non semella accesible a través de HTTP desde o exterior da rede local.", "diagnosis_http_bad_status_code": "Semella que outra máquina (podería ser o rúter de internet) respondeu no lugar do teu servidor.
1. A razón máis habitual para este problema é que o porto 80 (e 443) non están correctamente redirixidos ao teu servidor.
2. En configuracións avanzadas: revisa que nin o cortalumes nin o proxy-inverso están interferindo.", "diagnosis_http_connection_error": "Erro de conexión: non se puido conectar co dominio solicitado, moi probablemente non sexa accesible.", - "diagnosis_http_timeout": "Caducou a conexión mentras se intentaba contactar o servidor desde o exterior. Non semella accesible.
1. A razón máis habitual é que o porto 80 (e 443) non están correctamente redirixidos ao teu servidor.
2. Deberías comprobar tamén que o servizo nginx está a funcionar
3. En configuracións máis avanzadas: revisa que nin o cortalumes nin o proxy-inverso están interferindo." + "diagnosis_http_timeout": "Caducou a conexión mentras se intentaba contactar o servidor desde o exterior. Non semella accesible.
1. A razón máis habitual é que o porto 80 (e 443) non están correctamente redirixidos ao teu servidor.
2. Deberías comprobar tamén que o servizo nginx está a funcionar
3. En configuracións máis avanzadas: revisa que nin o cortalumes nin o proxy-inverso están interferindo.", + "field_invalid": "Campo non válido '{:s}'", + "experimental_feature": "Aviso: esta característica é experimental e non se considera estable, non deberías utilizala a menos que saibas o que estás a facer.", + "extracting": "Extraendo...", + "dyndns_unavailable": "O dominio '{domain:s}' non está dispoñible.", + "dyndns_domain_not_provided": "O provedor DynDNS {provider:s} non pode proporcionar o dominio {domain:s}.", + "dyndns_registration_failed": "Non se rexistrou o dominio DynDNS: {error:s}", + "dyndns_registered": "Dominio DynDNS rexistrado", + "dyndns_provider_unreachable": "Non se puido acadar o provedor DynDNS {provider}: pode que o teu YunoHost non teña conexión a internet ou que o servidor dynette non funcione.", + "dyndns_no_domain_registered": "Non hai dominio rexistrado con DynDNS", + "dyndns_key_not_found": "Non se atopou a chave DNS para o dominio", + "dyndns_key_generating": "Creando chave DNS... podería demorarse.", + "dyndns_ip_updated": "Actualizouse o IP en DynDNS", + "dyndns_ip_update_failed": "Non se actualizou o enderezo IP en DynDNS", + "dyndns_could_not_check_available": "Non se comprobou se {domain:s} está dispoñible en {provider:s}.", + "dyndns_could_not_check_provide": "Non se comprobou se {provider:s} pode proporcionar {domain:s}.", + "dpkg_lock_not_available": "Non se pode executar agora mesmo este comando porque semella que outro programa está a utilizar dpkg (o xestos de paquetes do sistema)", + "dpkg_is_broken": "Non podes facer isto agora mesmo porque dpkg/APT (o xestor de paquetes do sistema) semella que non está a funcionar... Podes intentar solucionalo conectándote a través de SSH e executando `sudo apt install --fix-broken`e/ou `sudo dpkg --configure -a`.", + "downloading": "Descargando…", + "done": "Feito", + "domains_available": "Dominios dispoñibles:", + "domain_unknown": "Dominio descoñecido", + "domain_name_unknown": "Dominio '{domain}' descoñecido", + "domain_uninstall_app_first": "Aínda están instaladas estas aplicacións no teu dominio:\n{apps}\n\nPrimeiro desinstalaas utilizando 'yunohost app remove id_da_app' ou móveas a outro dominio con 'yunohost app change-url id_da_app' antes de eliminar o dominio", + "domain_remove_confirm_apps_removal": "Ao eliminar o dominio tamén vas eliminar estas aplicacións:\n{apps}\n\nTes a certeza de querer facelo? [{answers}]", + "domain_hostname_failed": "Non se puido establecer o novo nome de servidor. Esto pode causar problemas máis tarde (tamén podería ser correcto).", + "domain_exists": "Xa existe o dominio", + "domain_dyndns_root_unknown": "Dominio raiz DynDNS descoñecido", + "domain_dyndns_already_subscribed": "Xa tes unha subscrición a un dominio DynDNS", + "domain_dns_conf_is_just_a_recommendation": "Este comando móstrache a configuración *recomendada*. Non realiza a configuración DNS no teu nome. É responsabilidade túa configurar as zonas DNS no servizo da empresa que xestiona o rexistro do dominio seguindo esta recomendación.", + "domain_deletion_failed": "Non se puido eliminar o dominio {domain}: {error}", + "domain_deleted": "Dominio eliminado", + "domain_creation_failed": "Non se puido crear o dominio {domain}: {error}", + "domain_created": "Dominio creado", + "domain_cert_gen_failed": "Non se puido crear o certificado", + "domain_cannot_remove_main_add_new_one": "Non podes eliminar '{domain:s}' porque é o dominio principal e único dominio, primeiro tes que engadir outro dominio usando 'yunohost domain add ', e despois establecelo como o dominio principal utilizando 'yunohost domain main-domain -n ' e entón poderás eliminar '{domain:s}' con 'yunohost domain remove {domain:s}'.'", + "domain_cannot_add_xmpp_upload": "Non podes engadir dominios que comecen con 'xmpp-upload.'. Este tipo de nome está reservado para a función se subida de XMPP integrada en YunoHost." } From f40b37cd6819a6692a8dba6d36944307d5f08989 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M?= Date: Wed, 4 Aug 2021 04:04:58 +0000 Subject: [PATCH 2666/3170] Translated using Weblate (Galician) Currently translated at 49.4% (313 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/gl/ --- locales/gl.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/gl.json b/locales/gl.json index 3e2068ed9..aa6540667 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -310,5 +310,6 @@ "domain_created": "Dominio creado", "domain_cert_gen_failed": "Non se puido crear o certificado", "domain_cannot_remove_main_add_new_one": "Non podes eliminar '{domain:s}' porque é o dominio principal e único dominio, primeiro tes que engadir outro dominio usando 'yunohost domain add ', e despois establecelo como o dominio principal utilizando 'yunohost domain main-domain -n ' e entón poderás eliminar '{domain:s}' con 'yunohost domain remove {domain:s}'.'", - "domain_cannot_add_xmpp_upload": "Non podes engadir dominios que comecen con 'xmpp-upload.'. Este tipo de nome está reservado para a función se subida de XMPP integrada en YunoHost." + "domain_cannot_add_xmpp_upload": "Non podes engadir dominios que comecen con 'xmpp-upload.'. Este tipo de nome está reservado para a función se subida de XMPP integrada en YunoHost.", + "file_does_not_exist": "O ficheiro {path:s} non existe." } From 7de361a59d4ffad149a9003662e9bbf976593f16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M?= Date: Wed, 4 Aug 2021 05:40:34 +0000 Subject: [PATCH 2667/3170] Translated using Weblate (Galician) Currently translated at 49.6% (314 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/gl/ --- locales/gl.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/gl.json b/locales/gl.json index aa6540667..dec9a0b0c 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -311,5 +311,6 @@ "domain_cert_gen_failed": "Non se puido crear o certificado", "domain_cannot_remove_main_add_new_one": "Non podes eliminar '{domain:s}' porque é o dominio principal e único dominio, primeiro tes que engadir outro dominio usando 'yunohost domain add ', e despois establecelo como o dominio principal utilizando 'yunohost domain main-domain -n ' e entón poderás eliminar '{domain:s}' con 'yunohost domain remove {domain:s}'.'", "domain_cannot_add_xmpp_upload": "Non podes engadir dominios que comecen con 'xmpp-upload.'. Este tipo de nome está reservado para a función se subida de XMPP integrada en YunoHost.", - "file_does_not_exist": "O ficheiro {path:s} non existe." + "file_does_not_exist": "O ficheiro {path:s} non existe.", + "firewall_reload_failed": "Non se puido recargar o cortalumes" } From a6314ba70e901f35bcefc4512f2086b1e6ed8f01 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 4 Aug 2021 18:52:33 +0200 Subject: [PATCH 2668/3170] Apply suggestions from code review --- locales/fr.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 700d21820..08361d6f2 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -96,7 +96,7 @@ "port_already_closed": "Le port {port:d} est déjà fermé pour les connexions {ip_version:s}", "port_already_opened": "Le port {port:d} est déjà ouvert pour les connexions {ip_version:s}", "restore_already_installed_app": "Une application est déjà installée avec l’identifiant '{app:s}'", - "app_restore_failed": "Impossible de restaurer {app :s} : {error :s}", + "app_restore_failed": "Impossible de restaurer {app:s} : {error:s}", "restore_cleaning_failed": "Impossible de nettoyer le dossier temporaire de restauration", "restore_complete": "Restauration terminée", "restore_confirm_yunohost_installed": "Voulez-vous vraiment restaurer un système déjà installé ? [{answers:s}]", @@ -569,7 +569,7 @@ "diagnosis_dns_try_dyndns_update_force": "La configuration DNS de ce domaine devrait être automatiquement gérée par YunoHost. Si ce n'est pas le cas, vous pouvez essayer de forcer une mise à jour en utilisant yunohost dyndns update --force.", "app_packaging_format_not_supported": "Cette application ne peut pas être installée car son format n'est pas pris en charge par votre version de YunoHost. Vous devriez probablement envisager de mettre à jour votre système.", "migration_0015_weak_certs": "Il a été constaté que les certificats suivants utilisent encore des algorithmes de signature peu robustes et doivent être mis à jour pour être compatibles avec la prochaine version de NGINX : {certs}", - "global_settings_setting_backup_compress_tar_archives": "Compresser les archives (.tar.gz) au lieu de créer des archives non-compressées lors de la création des backups. N.B. Activer cette option implique de créer des archives plus légères, mais aussi d'avoir des procédures de backup significativement plus longues et plus gourmandes en CPU.", + "global_settings_setting_backup_compress_tar_archives": "Compresser les archives (.tar.gz) au lieu de créer des archives non-compressées lors de la création des backups. N.B. Activer cette option permet de créer des archives plus légères, mais aussi d'avoir des procédures de backup significativement plus longues et plus gourmandes en CPU.", "migration_description_0018_xtable_to_nftable": "Migrer les anciennes règles de trafic réseau vers le nouveau système basé sur nftables", "service_description_php7.3-fpm": "Exécute les applications écrites en PHP avec NGINX", "migration_0018_failed_to_reset_legacy_rules": "La réinitialisation des règles iptable par défaut a échoué : {error}", @@ -586,7 +586,7 @@ "app_manifest_install_ask_password": "Choisissez un mot de passe administrateur pour cette application", "app_manifest_install_ask_path": "Choisissez le chemin sur lequel vous souhaitez installer cette application", "app_manifest_install_ask_domain": "Choisissez le domaine sur lequel vous souhaitez installer cette application", - "global_settings_setting_smtp_relay_user": "Compte d'utilisateur du relais SMTP", + "global_settings_setting_smtp_relay_user": "Compte utilisateur du relais SMTP", "global_settings_setting_smtp_relay_port": "Port du relais SMTP", "global_settings_setting_smtp_relay_host": "Un relais SMTP permet d'envoyer du courrier à la place de cette instance YunoHost. Cela est utile si vous êtes dans l'une de ces situations : le port 25 est bloqué par votre FAI ou par votre fournisseur VPS, vous avez une IP résidentielle répertoriée sur DUHL, vous ne pouvez pas configurer de reverse DNS ou le serveur n'est pas directement accessible depuis Internet et que vous voulez en utiliser un autre pour envoyer des mails.", "diagnosis_package_installed_from_sury_details": "Certains paquets ont été installés par inadvertance à partir d'un dépôt tiers appelé Sury. L'équipe YunoHost a amélioré la stratégie de gestion de ces paquets, mais on s'attend à ce que certaines configurations qui ont installé des applications PHP7.3 tout en étant toujours sur Stretch présentent des incohérences. Pour résoudre cette situation, vous devez essayer d'exécuter la commande suivante : {cmd_to_fix}", From d49ad748a27b6338940819ac970f370fc81d5d01 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 4 Aug 2021 18:55:00 +0200 Subject: [PATCH 2669/3170] Fuck the damn ':s' in locale strings --- locales/ar.json | 58 +++++----- locales/ca.json | 254 +++++++++++++++++++++---------------------- locales/cs.json | 30 ++--- locales/de.json | 252 +++++++++++++++++++++--------------------- locales/en.json | 252 +++++++++++++++++++++--------------------- locales/eo.json | 250 +++++++++++++++++++++--------------------- locales/es.json | 250 +++++++++++++++++++++--------------------- locales/fr.json | 250 +++++++++++++++++++++--------------------- locales/gl.json | 132 +++++++++++----------- locales/hi.json | 34 +++--- locales/hu.json | 12 +- locales/it.json | 252 +++++++++++++++++++++--------------------- locales/nb_NO.json | 52 ++++----- locales/nl.json | 70 ++++++------ locales/oc.json | 248 +++++++++++++++++++++--------------------- locales/pl.json | 6 +- locales/pt.json | 90 +++++++-------- locales/ru.json | 32 +++--- locales/sv.json | 2 +- locales/zh_Hans.json | 252 +++++++++++++++++++++--------------------- 20 files changed, 1389 insertions(+), 1389 deletions(-) diff --git a/locales/ar.json b/locales/ar.json index 06e444f4a..8f8d06688 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -1,25 +1,25 @@ { - "action_invalid": "إجراء غير صالح '{action:s}'", + "action_invalid": "إجراء غير صالح '{action}'", "admin_password": "كلمة السر الإدارية", "admin_password_change_failed": "لا يمكن تعديل الكلمة السرية", "admin_password_changed": "تم تعديل الكلمة السرية الإدارية", - "app_already_installed": "{app:s} تم تنصيبه مِن قبل", - "app_already_up_to_date": "{app:s} تم تحديثه مِن قَبل", - "app_argument_required": "المُعامِل '{name:s}' مطلوب", - "app_change_url_failed_nginx_reload": "فشلت عملية إعادة تشغيل NGINX. ها هي نتيجة الأمر 'nginx -t':\n{nginx_errors:s}", + "app_already_installed": "{app} تم تنصيبه مِن قبل", + "app_already_up_to_date": "{app} تم تحديثه مِن قَبل", + "app_argument_required": "المُعامِل '{name}' مطلوب", + "app_change_url_failed_nginx_reload": "فشلت عملية إعادة تشغيل NGINX. ها هي نتيجة الأمر 'nginx -t':\n{nginx_errors}", "app_extraction_failed": "تعذر فك الضغط عن ملفات التنصيب", "app_install_files_invalid": "ملفات التنصيب خاطئة", - "app_not_correctly_installed": "يبدو أن التطبيق {app:s} لم يتم تنصيبه بشكل صحيح", - "app_not_installed": "إنّ التطبيق {app:s} غير مُنصَّب", - "app_not_properly_removed": "لم يتم حذف تطبيق {app:s} بشكلٍ جيّد", - "app_removed": "تمت إزالة تطبيق {app:s}", + "app_not_correctly_installed": "يبدو أن التطبيق {app} لم يتم تنصيبه بشكل صحيح", + "app_not_installed": "إنّ التطبيق {app} غير مُنصَّب", + "app_not_properly_removed": "لم يتم حذف تطبيق {app} بشكلٍ جيّد", + "app_removed": "تمت إزالة تطبيق {app}", "app_requirements_checking": "جار فحص الحزم اللازمة لـ {app}…", "app_sources_fetch_failed": "تعذرت عملية جلب مصادر الملفات", "app_unknown": "برنامج مجهول", "app_upgrade_app_name": "جارٍ تحديث تطبيق {app}…", - "app_upgrade_failed": "تعذرت عملية ترقية {app:s}", + "app_upgrade_failed": "تعذرت عملية ترقية {app}", "app_upgrade_some_app_failed": "تعذرت عملية ترقية بعض التطبيقات", - "app_upgraded": "تم تحديث التطبيق {app:s}", + "app_upgraded": "تم تحديث التطبيق {app}", "ask_firstname": "الإسم", "ask_lastname": "اللقب", "ask_main_domain": "النطاق الرئيسي", @@ -31,11 +31,11 @@ "backup_method_copy_finished": "إنتهت عملية النسخ الإحتياطي", "backup_nothings_done": "ليس هناك أي شيء للحفظ", "backup_output_directory_required": "يتوجب عليك تحديد مجلد لتلقي النسخ الإحتياطية", - "certmanager_cert_install_success": "تمت عملية تنصيب شهادة Let's Encrypt بنجاح على النطاق {domain:s} !", - "certmanager_cert_install_success_selfsigned": "نجحت عملية تثبيت الشهادة الموقعة ذاتيا الخاصة بالنطاق {domain:s}", - "certmanager_cert_renew_success": "نجحت عملية تجديد شهادة Let's Encrypt الخاصة باسم النطاق {domain:s} !", + "certmanager_cert_install_success": "تمت عملية تنصيب شهادة Let's Encrypt بنجاح على النطاق {domain} !", + "certmanager_cert_install_success_selfsigned": "نجحت عملية تثبيت الشهادة الموقعة ذاتيا الخاصة بالنطاق {domain}", + "certmanager_cert_renew_success": "نجحت عملية تجديد شهادة Let's Encrypt الخاصة باسم النطاق {domain} !", "certmanager_cert_signing_failed": "فشل إجراء توقيع الشهادة الجديدة", - "certmanager_no_cert_file": "تعذرت عملية قراءة شهادة نطاق {domain:s} (الملف : {file:s})", + "certmanager_no_cert_file": "تعذرت عملية قراءة شهادة نطاق {domain} (الملف : {file})", "domain_created": "تم إنشاء النطاق", "domain_creation_failed": "تعذرت عملية إنشاء النطاق", "domain_deleted": "تم حذف النطاق", @@ -58,16 +58,16 @@ "pattern_positive_number": "يجب أن يكون عددا إيجابيا", "restore_extracting": "جارٍ فك الضغط عن الملفات التي نحتاجها من النسخة الاحتياطية…", "server_shutdown": "سوف ينطفئ الخادوم", - "server_shutdown_confirm": "سوف ينطفئ الخادوم حالا. متأكد ؟ [{answers:s}]", + "server_shutdown_confirm": "سوف ينطفئ الخادوم حالا. متأكد ؟ [{answers}]", "server_reboot": "سيعاد تشغيل الخادوم", - "server_reboot_confirm": "سيعاد تشغيل الخادوم في الحين. هل أنت متأكد ؟ [{answers:s}]", - "service_add_failed": "تعذرت إضافة خدمة '{service:s}'", - "service_already_stopped": "إنّ خدمة '{service:s}' متوقفة مِن قبلُ", - "service_disabled": "لن يتم إطلاق خدمة '{service:s}' أثناء بداية تشغيل النظام.", - "service_enabled": "تم تنشيط خدمة '{service:s}'", - "service_removed": "تمت إزالة خدمة '{service:s}'", - "service_started": "تم إطلاق تشغيل خدمة '{service:s}'", - "service_stopped": "تمّ إيقاف خدمة '{service:s}'", + "server_reboot_confirm": "سيعاد تشغيل الخادوم في الحين. هل أنت متأكد ؟ [{answers}]", + "service_add_failed": "تعذرت إضافة خدمة '{service}'", + "service_already_stopped": "إنّ خدمة '{service}' متوقفة مِن قبلُ", + "service_disabled": "لن يتم إطلاق خدمة '{service}' أثناء بداية تشغيل النظام.", + "service_enabled": "تم تنشيط خدمة '{service}'", + "service_removed": "تمت إزالة خدمة '{service}'", + "service_started": "تم إطلاق تشغيل خدمة '{service}'", + "service_stopped": "تمّ إيقاف خدمة '{service}'", "system_upgraded": "تمت عملية ترقية النظام", "unlimit": "دون تحديد الحصة", "updating_apt_cache": "جارٍ جلب قائمة حُزم النظام المحدّثة المتوفرة…", @@ -77,7 +77,7 @@ "user_created": "تم إنشاء المستخدم", "user_deleted": "تم حذف المستخدم", "user_deletion_failed": "لا يمكن حذف المستخدم", - "user_unknown": "المستخدم {user:s} مجهول", + "user_unknown": "المستخدم {user} مجهول", "user_update_failed": "لا يمكن تحديث المستخدم", "user_updated": "تم تحديث المستخدم", "yunohost_installing": "عملية تنصيب يونوهوست جارية …", @@ -129,13 +129,13 @@ "password_too_simple_1": "يجب أن يكون طول الكلمة السرية على الأقل 8 حروف", "already_up_to_date": "كل شيء على ما يرام. ليس هناك ما يتطلّب تحديثًا.", "service_description_slapd": "يخزّن المستخدمين والنطاقات والمعلومات المتعلقة بها", - "service_reloaded": "تم إعادة تشغيل خدمة '{service:s}'", - "service_restarted": "تم إعادة تشغيل خدمة '{service:s}'", - "group_unknown": "الفريق {group:s} مجهول", + "service_reloaded": "تم إعادة تشغيل خدمة '{service}'", + "service_restarted": "تم إعادة تشغيل خدمة '{service}'", + "group_unknown": "الفريق {group} مجهول", "group_deletion_failed": "فشلت عملية حذف الفريق '{group}': {error}", "group_deleted": "تم حذف الفريق '{group}'", "group_created": "تم إنشاء الفريق '{group}'", - "dyndns_could_not_check_available": "لا يمكن التحقق مِن أنّ {domain:s} متوفر على {provider:s}.", + "dyndns_could_not_check_available": "لا يمكن التحقق مِن أنّ {domain} متوفر على {provider}.", "backup_mount_archive_for_restore": "جارٍ تهيئة النسخة الاحتياطية للاسترجاع…", "root_password_replaced_by_admin_password": "لقد تم استبدال كلمة سر الجذر root بالكلمة الإدارية لـ admin.", "app_action_broke_system": "يبدو أنّ هذا الإجراء أدّى إلى تحطيم هذه الخدمات المهمة: {services}", diff --git a/locales/ca.json b/locales/ca.json index 0145bedd0..8b3beab49 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -1,59 +1,59 @@ { - "action_invalid": "Acció '{action:s}' invàlida", + "action_invalid": "Acció '{action}' invàlida", "admin_password": "Contrasenya d'administració", "admin_password_change_failed": "No es pot canviar la contrasenya", "admin_password_changed": "S'ha canviat la contrasenya d'administració", - "app_already_installed": "{app:s} ja està instal·lada", + "app_already_installed": "{app} ja està instal·lada", "app_already_installed_cant_change_url": "Aquesta aplicació ja està instal·lada. La URL no és pot canviar únicament amb aquesta funció. Mireu a `app changeurl` si està disponible.", - "app_already_up_to_date": "{app:s} ja està actualitzada", - "app_argument_choice_invalid": "Utilitzeu una de les opcions «{choices:s}» per l'argument «{name:s}»", - "app_argument_invalid": "Escolliu un valor vàlid per l'argument «{name:s}»: {error:s}", - "app_argument_required": "Es necessita l'argument '{name:s}'", - "app_change_url_failed_nginx_reload": "No s'ha pogut tornar a carregar NGINX. Aquí teniu el resultat de \"nginx -t\":\n{nginx_errors:s}", - "app_change_url_identical_domains": "L'antic i el nou domini/camí són idèntics ('{domain:s}{path:s}'), no hi ha res per fer.", - "app_change_url_no_script": "L'aplicació '{app_name:s}' encara no permet modificar la URL. Potser s'ha d'actualitzar.", - "app_change_url_success": "La URL de {app:s} ara és {domain:s}{path:s}", + "app_already_up_to_date": "{app} ja està actualitzada", + "app_argument_choice_invalid": "Utilitzeu una de les opcions «{choices}» per l'argument «{name}»", + "app_argument_invalid": "Escolliu un valor vàlid per l'argument «{name}»: {error}", + "app_argument_required": "Es necessita l'argument '{name}'", + "app_change_url_failed_nginx_reload": "No s'ha pogut tornar a carregar NGINX. Aquí teniu el resultat de \"nginx -t\":\n{nginx_errors}", + "app_change_url_identical_domains": "L'antic i el nou domini/camí són idèntics ('{domain}{path}'), no hi ha res per fer.", + "app_change_url_no_script": "L'aplicació '{app_name}' encara no permet modificar la URL. Potser s'ha d'actualitzar.", + "app_change_url_success": "La URL de {app} ara és {domain}{path}", "app_extraction_failed": "No s'han pogut extreure els fitxers d'instal·lació", "app_id_invalid": "ID de l'aplicació incorrecte", "app_install_files_invalid": "Aquests fitxers no es poden instal·lar", "app_make_default_location_already_used": "No es pot fer l'aplicació '{app}' l'aplicació per defecte en el domini «{domain}», ja que ja és utilitzat per '{other_app}'", - "app_location_unavailable": "Aquesta URL no està disponible o entra en conflicte amb aplicacions ja instal·lades:\n{apps:s}", + "app_location_unavailable": "Aquesta URL no està disponible o entra en conflicte amb aplicacions ja instal·lades:\n{apps}", "app_manifest_invalid": "Hi ha algun error amb el manifest de l'aplicació: {error}", - "app_not_correctly_installed": "{app:s} sembla estar mal instal·lada", - "app_not_installed": "No s'ha trobat {app:s} en la llista d'aplicacions instal·lades: {all_apps}", - "app_not_properly_removed": "{app:s} no s'ha pogut suprimir correctament", - "app_removed": "{app:s} ha estat suprimida", + "app_not_correctly_installed": "{app} sembla estar mal instal·lada", + "app_not_installed": "No s'ha trobat {app} en la llista d'aplicacions instal·lades: {all_apps}", + "app_not_properly_removed": "{app} no s'ha pogut suprimir correctament", + "app_removed": "{app} ha estat suprimida", "app_requirements_checking": "Verificació dels paquets requerits per {app}...", "app_requirements_unmeet": "No es compleixen els requeriments per {app}, el paquet {pkgname} ({version}) ha de ser {spec}", "app_sources_fetch_failed": "No s'han pogut carregar els fitxers font, l'URL és correcta?", "app_unknown": "Aplicació desconeguda", "app_unsupported_remote_type": "El tipus remot utilitzat per l'aplicació no està suportat", "app_upgrade_app_name": "Actualitzant {app}...", - "app_upgrade_failed": "No s'ha pogut actualitzar {app:s}: {error}", + "app_upgrade_failed": "No s'ha pogut actualitzar {app}: {error}", "app_upgrade_some_app_failed": "No s'han pogut actualitzar algunes aplicacions", - "app_upgraded": "S'ha actualitzat {app:s}", + "app_upgraded": "S'ha actualitzat {app}", "ask_firstname": "Nom", "ask_lastname": "Cognom", "ask_main_domain": "Domini principal", "ask_new_admin_password": "Nova contrasenya d'administrador", "ask_password": "Contrasenya", "backup_abstract_method": "Encara està per implementar aquest mètode de còpia de seguretat", - "backup_app_failed": "No s'ha pogut fer la còpia de seguretat de {app:s}", + "backup_app_failed": "No s'ha pogut fer la còpia de seguretat de {app}", "backup_applying_method_copy": "Còpia de tots els fitxers a la còpia de seguretat...", - "backup_applying_method_custom": "Crida del mètode de còpia de seguretat personalitzat \"{method:s}\"...", + "backup_applying_method_custom": "Crida del mètode de còpia de seguretat personalitzat \"{method}\"...", "backup_applying_method_tar": "Creació de l'arxiu TAR de la còpia de seguretat...", - "backup_archive_app_not_found": "No s'ha pogut trobar {app:s} en l'arxiu de la còpia de seguretat", - "backup_archive_broken_link": "No s'ha pogut accedir a l'arxiu de la còpia de seguretat (enllaç invàlid cap a {path:s})", + "backup_archive_app_not_found": "No s'ha pogut trobar {app} en l'arxiu de la còpia de seguretat", + "backup_archive_broken_link": "No s'ha pogut accedir a l'arxiu de la còpia de seguretat (enllaç invàlid cap a {path})", "backup_archive_name_exists": "Ja hi ha una còpia de seguretat amb aquest nom.", - "backup_archive_name_unknown": "Còpia de seguretat local \"{name:s}\" desconeguda", + "backup_archive_name_unknown": "Còpia de seguretat local \"{name}\" desconeguda", "backup_archive_open_failed": "No s'ha pogut obrir l'arxiu de la còpia de seguretat", - "backup_archive_system_part_not_available": "La part «{part:s}» del sistema no està disponible en aquesta copia de seguretat", - "backup_archive_writing_error": "No es poden afegir els arxius «{source:s}» (anomenats en l'arxiu «{dest:s}») a l'arxiu comprimit de la còpia de seguretat «{archive:s}»", - "backup_ask_for_copying_if_needed": "Voleu fer la còpia de seguretat utilitzant {size:s}MB temporalment? (S'utilitza aquest mètode ja que alguns dels fitxers no s'han pogut preparar utilitzar un mètode més eficient.)", + "backup_archive_system_part_not_available": "La part «{part}» del sistema no està disponible en aquesta copia de seguretat", + "backup_archive_writing_error": "No es poden afegir els arxius «{source}» (anomenats en l'arxiu «{dest}») a l'arxiu comprimit de la còpia de seguretat «{archive}»", + "backup_ask_for_copying_if_needed": "Voleu fer la còpia de seguretat utilitzant {size}MB temporalment? (S'utilitza aquest mètode ja que alguns dels fitxers no s'han pogut preparar utilitzar un mètode més eficient.)", "backup_cant_mount_uncompress_archive": "No es pot carregar l'arxiu descomprimit com a protegit contra escriptura", "backup_cleaning_failed": "No s'ha pogut netejar el directori temporal de la còpia de seguretat", - "backup_copying_to_organize_the_archive": "Copiant {size:s}MB per organitzar l'arxiu", - "backup_couldnt_bind": "No es pot lligar {src:s} amb {dest:s}.", + "backup_copying_to_organize_the_archive": "Copiant {size}MB per organitzar l'arxiu", + "backup_couldnt_bind": "No es pot lligar {src} amb {dest}.", "backup_created": "S'ha creat la còpia de seguretat", "aborting": "Avortant.", "app_not_upgraded": "L'aplicació «{failed_app}» no s'ha pogut actualitzar, i com a conseqüència s'ha cancel·lat l'actualització de les següents aplicacions: {apps}", @@ -70,11 +70,11 @@ "backup_csv_creation_failed": "No s'ha pogut crear el fitxer CSV necessari per a la restauració", "backup_custom_backup_error": "El mètode de còpia de seguretat personalitzat ha fallat a l'etapa «backup»", "backup_custom_mount_error": "El mètode de còpia de seguretat personalitzat ha fallat a l'etapa «mount»", - "backup_delete_error": "No s'ha pogut suprimir «{path:s}»", + "backup_delete_error": "No s'ha pogut suprimir «{path}»", "backup_deleted": "S'ha suprimit la còpia de seguretat", - "backup_hook_unknown": "Script de còpia de seguretat «{hook:s}» desconegut", + "backup_hook_unknown": "Script de còpia de seguretat «{hook}» desconegut", "backup_method_copy_finished": "La còpia de la còpia de seguretat ha acabat", - "backup_method_custom_finished": "El mètode de còpia de seguretat personalitzat \"{method:s}\" ha acabat", + "backup_method_custom_finished": "El mètode de còpia de seguretat personalitzat \"{method}\" ha acabat", "backup_method_tar_finished": "S'ha creat l'arxiu de còpia de seguretat TAR", "backup_mount_archive_for_restore": "Preparant l'arxiu per la restauració...", "good_practices_about_user_password": "Esteu a punt de definir una nova contrasenya d'usuari. La contrasenya ha de tenir un mínim de 8 caràcters; tot i que és de bona pràctica utilitzar una contrasenya més llarga (és a dir una frase de contrasenya) i/o utilitzar diferents tipus de caràcters (majúscules, minúscules, dígits i caràcters especials).", @@ -88,36 +88,36 @@ "backup_output_directory_forbidden": "Escolliu un directori de sortida different. Les còpies de seguretat no es poden crear ni dins els directoris /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var ni dins els subdirectoris /home/yunohost.backup/archives", "backup_output_directory_not_empty": "Heu d'escollir un directori de sortida buit", "backup_output_directory_required": "Heu d'especificar un directori de sortida per la còpia de seguretat", - "backup_output_symlink_dir_broken": "El directori del arxiu «{path:s}» es un enllaç simbòlic trencat. Pot ser heu oblidat muntar, tornar a muntar o connectar el mitja d'emmagatzematge al que apunta.", + "backup_output_symlink_dir_broken": "El directori del arxiu «{path}» es un enllaç simbòlic trencat. Pot ser heu oblidat muntar, tornar a muntar o connectar el mitja d'emmagatzematge al que apunta.", "backup_running_hooks": "Executant els scripts de la còpia de seguretat...", - "backup_system_part_failed": "No s'ha pogut fer la còpia de seguretat de la part \"{part:s}\" del sistema", + "backup_system_part_failed": "No s'ha pogut fer la còpia de seguretat de la part \"{part}\" del sistema", "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": "{app:s} no té un script de restauració, no podreu restaurar automàticament la còpia de seguretat d'aquesta aplicació.", + "backup_with_no_backup_script_for_app": "L'aplicació «{app}» no té un script de còpia de seguretat. Serà ignorat.", + "backup_with_no_restore_script_for_app": "{app} 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": "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)", - "certmanager_cannot_read_cert": "S'ha produït un error al intentar obrir el certificat actual pel domini {domain:s} (arxiu: {file:s}), raó: {reason:s}", - "certmanager_cert_install_success": "S'ha instal·lat correctament un certificat Let's Encrypt pel domini «{domain:s}»", - "certmanager_cert_install_success_selfsigned": "S'ha instal·lat correctament un certificat auto-signat pel domini «{domain:s}»", - "certmanager_cert_renew_success": "S'ha renovat correctament el certificat Let's Encrypt pel domini «{domain:s}»", + "certmanager_attempt_to_renew_nonLE_cert": "El certificat pel domini «{domain}» 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}» 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}! (Utilitzeu --force per ometre)", + "certmanager_cannot_read_cert": "S'ha produït un error al intentar obrir el certificat actual pel domini {domain} (arxiu: {file}), raó: {reason}", + "certmanager_cert_install_success": "S'ha instal·lat correctament un certificat Let's Encrypt pel domini «{domain}»", + "certmanager_cert_install_success_selfsigned": "S'ha instal·lat correctament un certificat auto-signat pel domini «{domain}»", + "certmanager_cert_renew_success": "S'ha renovat correctament el certificat Let's Encrypt pel domini «{domain}»", "certmanager_cert_signing_failed": "No s'ha pogut firmar el nou certificat", - "certmanager_certificate_fetching_or_enabling_failed": "Sembla que utilitzar el nou certificat per {domain:s} ha fallat...", - "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": "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_hit_rate_limit": "S'han emès massa certificats recentment per aquest mateix conjunt de dominis {domain:s}. Si us plau torneu-ho a intentar més tard. Consulteu https://letsencrypt.org/docs/rate-limits/ per obtenir més detalls", - "certmanager_no_cert_file": "No s'ha pogut llegir l'arxiu del certificat pel domini {domain:s} (fitxer: {file:s})", - "certmanager_self_ca_conf_file_not_found": "No s'ha trobat el fitxer de configuració per l'autoritat del certificat auto-signat (fitxer: {file:s})", - "certmanager_unable_to_parse_self_CA_name": "No s'ha pogut analitzar el nom de l'autoritat del certificat auto-signat (fitxer: {file:s})", - "confirm_app_install_warning": "Atenció: Aquesta aplicació funciona, però no està ben integrada amb YunoHost. Algunes característiques com la autenticació única i la còpia de seguretat/restauració poden no estar disponibles. Voleu instal·lar-la de totes maneres? [{answers:s}] ", - "confirm_app_install_danger": "PERILL! Aquesta aplicació encara és experimental (si no és que no funciona directament)! No hauríeu d'instal·lar-la a no ser que sapigueu el que feu. No obtindreu CAP AJUDA si l'aplicació no funciona o trenca el sistema… Si accepteu el risc, escriviu «{answers:s}»", - "confirm_app_install_thirdparty": "PERILL! Aquesta aplicació no es part del catàleg d'aplicacions de YunoHost. La instal·lació d'aplicacions de terceres parts pot comprometre la integritat i seguretat del seu sistema. No hauríeu d'instal·lar-ne a no ser que sapigueu el que feu. No obtindreu CAP AJUDA si l'aplicació no funciona o trenca el sistema… Si accepteu el risc, escriviu «{answers:s}»", - "custom_app_url_required": "Heu de especificar una URL per actualitzar la vostra aplicació personalitzada {app:s}", + "certmanager_certificate_fetching_or_enabling_failed": "Sembla que utilitzar el nou certificat per {domain} ha fallat...", + "certmanager_domain_cert_not_selfsigned": "El certificat pel domini {domain} no és auto-signat Esteu segur de voler canviar-lo? (Utilitzeu «--force» per fer-ho)", + "certmanager_domain_dns_ip_differs_from_public_ip": "Els registres DNS pel domini «{domain}» 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} 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_hit_rate_limit": "S'han emès massa certificats recentment per aquest mateix conjunt de dominis {domain}. Si us plau torneu-ho a intentar més tard. Consulteu https://letsencrypt.org/docs/rate-limits/ per obtenir més detalls", + "certmanager_no_cert_file": "No s'ha pogut llegir l'arxiu del certificat pel domini {domain} (fitxer: {file})", + "certmanager_self_ca_conf_file_not_found": "No s'ha trobat el fitxer de configuració per l'autoritat del certificat auto-signat (fitxer: {file})", + "certmanager_unable_to_parse_self_CA_name": "No s'ha pogut analitzar el nom de l'autoritat del certificat auto-signat (fitxer: {file})", + "confirm_app_install_warning": "Atenció: Aquesta aplicació funciona, però no està ben integrada amb YunoHost. Algunes característiques com la autenticació única i la còpia de seguretat/restauració poden no estar disponibles. Voleu instal·lar-la de totes maneres? [{answers}] ", + "confirm_app_install_danger": "PERILL! Aquesta aplicació encara és experimental (si no és que no funciona directament)! No hauríeu d'instal·lar-la a no ser que sapigueu el que feu. No obtindreu CAP AJUDA si l'aplicació no funciona o trenca el sistema… Si accepteu el risc, escriviu «{answers}»", + "confirm_app_install_thirdparty": "PERILL! Aquesta aplicació no es part del catàleg d'aplicacions de YunoHost. La instal·lació d'aplicacions de terceres parts pot comprometre la integritat i seguretat del seu sistema. No hauríeu d'instal·lar-ne a no ser que sapigueu el que feu. No obtindreu CAP AJUDA si l'aplicació no funciona o trenca el sistema… Si accepteu el risc, escriviu «{answers}»", + "custom_app_url_required": "Heu de especificar una URL per actualitzar la vostra aplicació personalitzada {app}", "admin_password_too_long": "Trieu una contrasenya de menys de 127 caràcters", "dpkg_is_broken": "No es pot fer això en aquest instant perquè dpkg/APT (els gestors de paquets del sistema) sembla estar mal configurat… Podeu intentar solucionar-ho connectant-vos per SSH i executant «sudo apt install --fix-broken» i/o «sudo dpkg --configure -a».", - "domain_cannot_remove_main": "No es pot eliminar «{domain:s}» ja que és el domini principal, primer s'ha d'establir un nou domini principal utilitzant «yunohost domain main-domain -n »; aquí hi ha una llista dels possibles dominis: {other_domains:s}", + "domain_cannot_remove_main": "No es pot eliminar «{domain}» ja que és el domini principal, primer s'ha d'establir un nou domini principal utilitzant «yunohost domain main-domain -n »; aquí hi ha una llista dels possibles dominis: {other_domains}", "domain_cert_gen_failed": "No s'ha pogut generar el certificat", "domain_created": "S'ha creat el domini", "domain_creation_failed": "No s'ha pogut crear el domini {domain}: {error}", @@ -134,44 +134,44 @@ "domains_available": "Dominis disponibles:", "done": "Fet", "downloading": "Descarregant…", - "dyndns_could_not_check_provide": "No s'ha pogut verificar si {provider:s} pot oferir {domain:s}.", - "dyndns_could_not_check_available": "No s'ha pogut verificar la disponibilitat de {domain:s} a {provider:s}.", + "dyndns_could_not_check_provide": "No s'ha pogut verificar si {provider} pot oferir {domain}.", + "dyndns_could_not_check_available": "No s'ha pogut verificar la disponibilitat de {domain} a {provider}.", "dyndns_ip_update_failed": "No s'ha pogut actualitzar l'adreça IP al DynDNS", "dyndns_ip_updated": "S'ha actualitzat l'adreça IP al DynDNS", "dyndns_key_generating": "S'està generant la clau DNS... això pot trigar una estona.", "dyndns_key_not_found": "No s'ha trobat la clau DNS pel domini", "dyndns_no_domain_registered": "No hi ha cap domini registrat amb DynDNS", "dyndns_registered": "S'ha registrat el domini DynDNS", - "dyndns_registration_failed": "No s'ha pogut registrar el domini DynDNS: {error:s}", - "dyndns_domain_not_provided": "El proveïdor de DynDNS {provider:s} no pot oferir el domini {domain:s}.", - "dyndns_unavailable": "El domini {domain:s} no està disponible.", + "dyndns_registration_failed": "No s'ha pogut registrar el domini DynDNS: {error}", + "dyndns_domain_not_provided": "El proveïdor de DynDNS {provider} no pot oferir el domini {domain}.", + "dyndns_unavailable": "El domini {domain} no està disponible.", "extracting": "Extracció en curs...", "experimental_feature": "Atenció: Aquesta funcionalitat és experimental i no es considera estable, no s'ha d'utilitzar a excepció de saber el que esteu fent.", - "field_invalid": "Camp incorrecte « {:s} »", - "file_does_not_exist": "El camí {path:s} no existeix.", + "field_invalid": "Camp incorrecte « {} »", + "file_does_not_exist": "El camí {path} no existeix.", "firewall_reload_failed": "No s'ha pogut tornar a carregar el tallafocs", "firewall_reloaded": "S'ha tornat a carregar el tallafocs", "firewall_rules_cmd_failed": "Han fallat algunes comandes per aplicar regles del tallafocs. Més informació en el registre.", - "global_settings_bad_choice_for_enum": "Opció pel paràmetre {setting:s} incorrecta, s'ha rebut «{choice:s}», però les opcions disponibles són: {available_choices:s}", - "global_settings_bad_type_for_setting": "El tipus del paràmetre {setting:s} és incorrecte. S'ha rebut {received_type:s}, però s'esperava {expected_type:s}", - "global_settings_cant_open_settings": "No s'ha pogut obrir el fitxer de configuració, raó: {reason:s}", - "global_settings_cant_serialize_settings": "No s'ha pogut serialitzar les dades de configuració, raó: {reason:s}", - "global_settings_cant_write_settings": "No s'ha pogut escriure el fitxer de configuració, raó: {reason:s}", - "global_settings_key_doesnt_exists": "La clau « {settings_key:s} » no existeix en la configuració global, podeu veure totes les claus disponibles executant « yunohost settings list »", - "global_settings_reset_success": "S'ha fet una còpia de seguretat de la configuració anterior a {path:s}", + "global_settings_bad_choice_for_enum": "Opció pel paràmetre {setting} incorrecta, s'ha rebut «{choice}», però les opcions disponibles són: {available_choices}", + "global_settings_bad_type_for_setting": "El tipus del paràmetre {setting} és incorrecte. S'ha rebut {received_type}, però s'esperava {expected_type}", + "global_settings_cant_open_settings": "No s'ha pogut obrir el fitxer de configuració, raó: {reason}", + "global_settings_cant_serialize_settings": "No s'ha pogut serialitzar les dades de configuració, raó: {reason}", + "global_settings_cant_write_settings": "No s'ha pogut escriure el fitxer de configuració, raó: {reason}", + "global_settings_key_doesnt_exists": "La clau « {settings_key} » no existeix en la configuració global, podeu veure totes les claus disponibles executant « yunohost settings list »", + "global_settings_reset_success": "S'ha fet una còpia de seguretat de la configuració anterior a {path}", "global_settings_setting_security_nginx_compatibility": "Solució de compromís entre compatibilitat i seguretat pel servidor web NGINX. Afecta els criptògrafs (i altres aspectes relacionats amb la seguretat)", "global_settings_setting_security_password_admin_strength": "Robustesa de la contrasenya d'administrador", "global_settings_setting_security_password_user_strength": "Robustesa de la contrasenya de l'usuari", "global_settings_setting_security_ssh_compatibility": "Solució de compromís entre compatibilitat i seguretat pel servidor SSH. Afecta els criptògrafs (i altres aspectes relacionats amb la seguretat)", - "global_settings_unknown_setting_from_settings_file": "Clau de configuració desconeguda: «{setting_key:s}», refusada i guardada a /etc/yunohost/settings-unknown.json", + "global_settings_unknown_setting_from_settings_file": "Clau de configuració desconeguda: «{setting_key}», refusada i guardada a /etc/yunohost/settings-unknown.json", "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Permetre la clau d'hoste DSA (obsolet) per la configuració del servei SSH", - "global_settings_unknown_type": "Situació inesperada, la configuració {setting:s} sembla tenir el tipus {unknown_type:s} però no és un tipus reconegut pel sistema.", + "global_settings_unknown_type": "Situació inesperada, la configuració {setting} sembla tenir el tipus {unknown_type} però no és un tipus reconegut pel sistema.", "good_practices_about_admin_password": "Esteu a punt de definir una nova contrasenya d'administrador. La contrasenya ha de tenir un mínim de 8 caràcters; tot i que és de bona pràctica utilitzar una contrasenya més llarga (és a dir una frase de contrasenya) i/o utilitzar diferents tipus de caràcters (majúscules, minúscules, dígits i caràcters especials).", - "hook_exec_failed": "No s'ha pogut executar el script: {path:s}", - "hook_exec_not_terminated": "El script no s'ha acabat correctament: {path:s}", - "hook_json_return_error": "No s'ha pogut llegir el retorn del script {path:s}. Error: {msg:s}. Contingut en brut: {raw_content}", + "hook_exec_failed": "No s'ha pogut executar el script: {path}", + "hook_exec_not_terminated": "El script no s'ha acabat correctament: {path}", + "hook_json_return_error": "No s'ha pogut llegir el retorn del script {path}. Error: {msg}. Contingut en brut: {raw_content}", "hook_list_by_invalid": "Aquesta propietat no es pot utilitzar per llistar els hooks", - "hook_name_unknown": "Nom de script « {name:s} » desconegut", + "hook_name_unknown": "Nom de script « {name} » desconegut", "installation_complete": "Instal·lació completada", "ip6tables_unavailable": "No podeu modificar les ip6tables aquí. O bé sou en un contenidor o bé el vostre nucli no és compatible amb aquesta opció", "iptables_unavailable": "No podeu modificar les iptables aquí. O bé sou en un contenidor o bé el vostre nucli no és compatible amb aquesta opció", @@ -212,9 +212,9 @@ "already_up_to_date": "No hi ha res a fer. Tot està actualitzat.", "dpkg_lock_not_available": "No es pot utilitzar aquesta comanda en aquest moment ja que sembla que un altre programa està utilitzant el lock de dpkg (el gestor de paquets del sistema)", "global_settings_setting_security_postfix_compatibility": "Solució de compromís entre compatibilitat i seguretat pel servidor Postfix. Afecta els criptògrafs (i altres aspectes relacionats amb la seguretat)", - "mail_alias_remove_failed": "No s'han pogut eliminar els àlies del correu «{mail:s}»", - "mail_domain_unknown": "El domini «{domain:s}» de l'adreça de correu no és vàlid. Utilitzeu un domini administrat per aquest servidor.", - "mail_forward_remove_failed": "No s'han pogut eliminar el reenviament de correu «{mail:s}»", + "mail_alias_remove_failed": "No s'han pogut eliminar els àlies del correu «{mail}»", + "mail_domain_unknown": "El domini «{domain}» de l'adreça de correu no és vàlid. Utilitzeu un domini administrat per aquest servidor.", + "mail_forward_remove_failed": "No s'han pogut eliminar el reenviament de correu «{mail}»", "mailbox_used_space_dovecot_down": "S'ha d'engegar el servei de correu Dovecot, per poder obtenir l'espai utilitzat per la bústia de correu", "mail_unavailable": "Aquesta adreça de correu està reservada i ha de ser atribuïda automàticament el primer usuari", "main_domain_change_failed": "No s'ha pogut canviar el domini principal", @@ -227,7 +227,7 @@ "migrations_skip_migration": "Saltant migració {id}...", "migrations_to_be_ran_manually": "La migració {id} s'ha de fer manualment. Aneu a Eines → Migracions a la interfície admin, o executeu «yunohost tools migrations run».", "migrations_need_to_accept_disclaimer": "Per fer la migració {id}, heu d'acceptar aquesta clàusula de no responsabilitat:\n---\n{disclaimer}\n---\nSi accepteu fer la migració, torneu a executar l'ordre amb l'opció «--accept-disclaimer».", - "not_enough_disk_space": "No hi ha prou espai en «{path:s}»", + "not_enough_disk_space": "No hi ha prou espai en «{path}»", "packages_upgrade_failed": "No s'han pogut actualitzar tots els paquets", "pattern_backup_archive_name": "Ha de ser un nom d'arxiu vàlid amb un màxim de 30 caràcters, compost per caràcters alfanumèrics i -_. exclusivament", "pattern_domain": "Ha de ser un nom de domini vàlid (ex.: el-meu-domini.cat)", @@ -240,8 +240,8 @@ "pattern_positive_number": "Ha de ser un nombre positiu", "pattern_username": "Ha d'estar compost per caràcters alfanumèrics en minúscula i guió baix exclusivament", "pattern_password_app": "Les contrasenyes no poden de tenir els següents caràcters: {forbidden_chars}", - "port_already_closed": "El port {port:d} ja està tancat per les connexions {ip_version:s}", - "port_already_opened": "El port {port:d} ja està obert per les connexions {ip_version:s}", + "port_already_closed": "El port {port:d} ja està tancat per les connexions {ip_version}", + "port_already_opened": "El port {port:d} ja està obert per les connexions {ip_version}", "regenconf_file_backed_up": "S'ha guardat una còpia de seguretat del fitxer de configuració «{conf}» a «{backup}»", "regenconf_file_copy_failed": "No s'ha pogut copiar el nou fitxer de configuració «{new}» a «{conf}»", "regenconf_file_kept_back": "S'espera que el fitxer de configuració «{conf}» sigui suprimit per regen-conf (categoria {category}) però s'ha mantingut.", @@ -257,32 +257,32 @@ "regenconf_dry_pending_applying": "Verificació de la configuració pendent que s'hauria d'haver aplicat per la categoria «{category}»…", "regenconf_failed": "No s'ha pogut regenerar la configuració per la/les categoria/es : {categories}", "regenconf_pending_applying": "Aplicació de la configuració pendent per la categoria «{category}»...", - "restore_already_installed_app": "Una aplicació amb la ID «{app:s}» ja està instal·lada", - "app_restore_failed": "No s'ha pogut restaurar {app:s}: {error:s}", + "restore_already_installed_app": "Una aplicació amb la ID «{app}» ja està instal·lada", + "app_restore_failed": "No s'ha pogut restaurar {app}: {error}", "restore_cleaning_failed": "No s'ha pogut netejar el directori temporal de restauració", "restore_complete": "Restauració completada", - "restore_confirm_yunohost_installed": "Esteu segur de voler restaurar un sistema ja instal·lat? [{answers:s}]", + "restore_confirm_yunohost_installed": "Esteu segur de voler restaurar un sistema ja instal·lat? [{answers}]", "restore_extracting": "Extracció dels fitxers necessaris de l'arxiu…", "restore_failed": "No s'ha pogut restaurar el sistema", - "restore_hook_unavailable": "El script de restauració «{part:s}» no està disponible en el sistema i tampoc és en l'arxiu", + "restore_hook_unavailable": "El script de restauració «{part}» no està disponible en el sistema i tampoc és en l'arxiu", "restore_may_be_not_enough_disk_space": "Sembla que no hi ha prou espai disponible en el sistema (lliure: {free_space:d} B, espai necessari: {needed_space:d} B, marge de seguretat: {margin:d} B)", "restore_not_enough_disk_space": "No hi ha prou espai disponible (espai: {free_space:d} B, espai necessari: {needed_space:d} B, marge de seguretat: {margin:d} B)", "restore_nothings_done": "No s'ha restaurat res", "restore_removing_tmp_dir_failed": "No s'ha pogut eliminar un directori temporal antic", - "restore_running_app_script": "Restaurant l'aplicació «{app:s}»…", + "restore_running_app_script": "Restaurant l'aplicació «{app}»…", "restore_running_hooks": "Execució dels hooks de restauració…", - "restore_system_part_failed": "No s'ha pogut restaurar la part «{part:s}» del sistema", + "restore_system_part_failed": "No s'ha pogut restaurar la part «{part}» del sistema", "root_password_desynchronized": "S'ha canviat la contrasenya d'administració, però YunoHost no ha pogut propagar-ho cap a la contrasenya root!", "root_password_replaced_by_admin_password": "La contrasenya root s'ha substituït per la contrasenya d'administració.", "server_shutdown": "S'aturarà el servidor", - "server_shutdown_confirm": "S'aturarà el servidor immediatament, n'esteu segur? [{answers:s}]", + "server_shutdown_confirm": "S'aturarà el servidor immediatament, n'esteu segur? [{answers}]", "server_reboot": "Es reiniciarà el servidor", - "server_reboot_confirm": "Es reiniciarà el servidor immediatament, n'esteu segur? [{answers:s}]", - "service_add_failed": "No s'ha pogut afegir el servei «{service:s}»", - "service_added": "S'ha afegit el servei «{service:s}»", - "service_already_started": "El servei «{service:s}» ja està funcionant", - "service_already_stopped": "Ja s'ha aturat el servei «{service:s}»", - "service_cmd_exec_failed": "No s'ha pogut executar l'ordre «{command:s}»", + "server_reboot_confirm": "Es reiniciarà el servidor immediatament, n'esteu segur? [{answers}]", + "service_add_failed": "No s'ha pogut afegir el servei «{service}»", + "service_added": "S'ha afegit el servei «{service}»", + "service_already_started": "El servei «{service}» ja està funcionant", + "service_already_stopped": "Ja s'ha aturat el servei «{service}»", + "service_cmd_exec_failed": "No s'ha pogut executar l'ordre «{command}»", "service_description_avahi-daemon": "Permet accedir al servidor via «yunohost.local» en la xarxa local", "service_description_dnsmasq": "Gestiona la resolució del nom de domini (DNS)", "service_description_dovecot": "Permet als clients de correu accedir/recuperar correus (via IMAP i POP3)", @@ -297,24 +297,24 @@ "service_description_ssh": "Permet la connexió remota al servidor via terminal (protocol SSH)", "service_description_yunohost-api": "Gestiona les interaccions entre la interfície web de YunoHost i el sistema", "service_description_yunohost-firewall": "Gestiona els ports de connexió oberts i tancats als serveis", - "service_disable_failed": "No s'han pogut fer que el servei «{service:s}» no comenci a l'arrancada.\n\nRegistres recents: {logs:s}", - "service_disabled": "El servei «{service:s}» ja no començarà al arrancar el sistema.", - "service_enable_failed": "No s'ha pogut fer que el servei «{service:s}» comenci automàticament a l'arrancada.\n\nRegistres recents: {logs:s}", - "service_enabled": "El servei «{service:s}» començarà automàticament durant l'arrancada del sistema.", + "service_disable_failed": "No s'han pogut fer que el servei «{service}» no comenci a l'arrancada.\n\nRegistres recents: {logs}", + "service_disabled": "El servei «{service}» ja no començarà al arrancar el sistema.", + "service_enable_failed": "No s'ha pogut fer que el servei «{service}» comenci automàticament a l'arrancada.\n\nRegistres recents: {logs}", + "service_enabled": "El servei «{service}» començarà automàticament durant l'arrancada del sistema.", "service_regen_conf_is_deprecated": "«yunohost service regen-conf» està desfasat! Utilitzeu «yunohost tools regen-conf» en el seu lloc.", - "service_remove_failed": "No s'ha pogut eliminar el servei «{service:s}»", - "service_removed": "S'ha eliminat el servei «{service:s}»", - "service_reload_failed": "No s'ha pogut tornar a carregar el servei «{service:s}»\n\nRegistres recents: {logs:s}", - "service_reloaded": "S'ha tornat a carregar el servei «{service:s}»", - "service_restart_failed": "No s'ha pogut reiniciar el servei «{service:s}»\n\nRegistres recents: {logs:s}", - "service_restarted": "S'ha reiniciat el servei «{service:s}»", - "service_reload_or_restart_failed": "No s'ha pogut tornar a carregar o reiniciar el servei «{service:s}»\n\nRegistres recents: {logs:s}", - "service_reloaded_or_restarted": "S'ha tornat a carregar o s'ha reiniciat el servei «{service:s}»", - "service_start_failed": "No s'ha pogut iniciar el servei «{service:s}»\n\nRegistres recents: {logs:s}", - "service_started": "S'ha iniciat el servei «{service:s}»", - "service_stop_failed": "No s'ha pogut aturar el servei «{service:s}»\n\nRegistres recents: {logs:s}", - "service_stopped": "S'ha aturat el servei «{service:s}»", - "service_unknown": "Servei «{service:s}» desconegut", + "service_remove_failed": "No s'ha pogut eliminar el servei «{service}»", + "service_removed": "S'ha eliminat el servei «{service}»", + "service_reload_failed": "No s'ha pogut tornar a carregar el servei «{service}»\n\nRegistres recents: {logs}", + "service_reloaded": "S'ha tornat a carregar el servei «{service}»", + "service_restart_failed": "No s'ha pogut reiniciar el servei «{service}»\n\nRegistres recents: {logs}", + "service_restarted": "S'ha reiniciat el servei «{service}»", + "service_reload_or_restart_failed": "No s'ha pogut tornar a carregar o reiniciar el servei «{service}»\n\nRegistres recents: {logs}", + "service_reloaded_or_restarted": "S'ha tornat a carregar o s'ha reiniciat el servei «{service}»", + "service_start_failed": "No s'ha pogut iniciar el servei «{service}»\n\nRegistres recents: {logs}", + "service_started": "S'ha iniciat el servei «{service}»", + "service_stop_failed": "No s'ha pogut aturar el servei «{service}»\n\nRegistres recents: {logs}", + "service_stopped": "S'ha aturat el servei «{service}»", + "service_unknown": "Servei «{service}» desconegut", "ssowat_conf_generated": "S'ha regenerat la configuració SSOwat", "ssowat_conf_updated": "S'ha actualitzat la configuració SSOwat", "system_upgraded": "S'ha actualitzat el sistema", @@ -329,10 +329,10 @@ "tools_upgrade_special_packages": "Actualitzant els paquets «especials» (relacionats amb YunoHost)…", "tools_upgrade_special_packages_explanation": "Aquesta actualització especial continuarà en segon pla. No comenceu cap altra acció al servidor en els pròxims ~10 minuts (depèn de la velocitat del maquinari). Després d'això, pot ser que us hagueu de tornar a connectar a la interfície d'administració. Els registres de l'actualització estaran disponibles a Eines → Registres (a la interfície d'administració) o utilitzant «yunohost log list» (des de la línia d'ordres).", "tools_upgrade_special_packages_completed": "Actualització dels paquets YunoHost acabada.\nPremeu [Enter] per tornar a la línia d'ordres", - "unbackup_app": "{app:s} no es guardarà", + "unbackup_app": "{app} no es guardarà", "unexpected_error": "Hi ha hagut un error inesperat: {error}", "unlimit": "Sense quota", - "unrestore_app": "{app:s} no es restaurarà", + "unrestore_app": "{app} no es restaurarà", "update_apt_cache_failed": "No s'ha pogut actualitzar la memòria cau d'APT (el gestor de paquets de Debian). Aquí teniu les línies de sources.list, que poden ajudar-vos a identificar les línies problemàtiques:\n{sourceslist}", "update_apt_cache_warning": "Hi ha hagut errors al actualitzar la memòria cau d'APT (el gestor de paquets de Debian). Aquí teniu les línies de sources.list que poden ajudar-vos a identificar les línies problemàtiques:\n{sourceslist}", "updating_apt_cache": "Obtenció de les actualitzacions disponibles per als paquets del sistema...", @@ -347,32 +347,32 @@ "user_deleted": "S'ha suprimit l'usuari", "user_deletion_failed": "No s'ha pogut suprimir l'usuari {user}: {error}", "user_home_creation_failed": "No s'ha pogut crear la carpeta personal «home» per l'usuari", - "user_unknown": "Usuari desconegut: {user:s}", + "user_unknown": "Usuari desconegut: {user}", "user_update_failed": "No s'ha pogut actualitzar l'usuari {user}: {error}", "user_updated": "S'ha canviat la informació de l'usuari", "yunohost_already_installed": "YunoHost ja està instal·lat", "yunohost_configured": "YunoHost està configurat", "yunohost_installing": "Instal·lació de YunoHost...", "yunohost_not_installed": "YunoHost no està instal·lat correctament. Executeu «yunohost tools postinstall»", - "backup_permission": "Permís de còpia de seguretat per {app:s}", + "backup_permission": "Permís de còpia de seguretat per {app}", "group_created": "S'ha creat el grup «{group}»", "group_creation_failed": "No s'ha pogut crear el grup «{group}»: {error}", "group_deleted": "S'ha eliminat el grup «{group}»", "group_deletion_failed": "No s'ha pogut eliminar el grup «{group}»: {error}", - "group_unknown": "Grup {group:s} desconegut", + "group_unknown": "Grup {group} desconegut", "group_updated": "S'ha actualitzat el grup «{group}»", "group_update_failed": "No s'ha pogut actualitzat el grup «{group}»: {error}", "log_user_group_delete": "Eliminar grup «{}»", "log_user_group_update": "Actualitzar grup «{}»", - "mailbox_disabled": "La bústia de correu està desactivada per al usuari {user:s}", - "permission_already_exist": "El permís «{permission:s}» ja existeix", - "permission_created": "S'ha creat el permís «{permission:s}»", + "mailbox_disabled": "La bústia de correu està desactivada per al usuari {user}", + "permission_already_exist": "El permís «{permission}» ja existeix", + "permission_created": "S'ha creat el permís «{permission}»", "permission_creation_failed": "No s'ha pogut crear el permís «{permission}»: {error}", - "permission_deleted": "S'ha eliminat el permís «{permission:s}»", - "permission_deletion_failed": "No s'ha pogut eliminar el permís «{permission:s}»: {error}", - "permission_not_found": "No s'ha trobat el permís «{permission:s}»", + "permission_deleted": "S'ha eliminat el permís «{permission}»", + "permission_deletion_failed": "No s'ha pogut eliminar el permís «{permission}»: {error}", + "permission_not_found": "No s'ha trobat el permís «{permission}»", "permission_update_failed": "No s'ha pogut actualitzar el permís «{permission}»: {error}", - "permission_updated": "S'ha actualitzat el permís «{permission:s}»", + "permission_updated": "S'ha actualitzat el permís «{permission}»", "app_full_domain_unavailable": "Aquesta aplicació ha de ser instal·lada en el seu propi domini, però ja hi ha altres aplicacions instal·lades en el domini «{domain}». Podeu utilitzar un subdomini dedicat a aquesta aplicació.", "migrations_not_pending_cant_skip": "Aquestes migracions no estan pendents, així que no poden ser omeses: {ids}", "app_action_broke_system": "Aquesta acció sembla haver trencat els següents serveis importants: {services}", @@ -419,7 +419,7 @@ "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}", - "domain_cannot_remove_main_add_new_one": "No es pot eliminar «{domain:s}» ja que és el domini principal i únic domini, primer s'ha d'afegir un altre domini utilitzant «yunohost domain add », i després fer-lo el domini principal amb «yunohost domain main-domain -n » i després es pot eliminar el domini «{domain:s}» utilitzant «yunohost domain remove {domain:s}».", + "domain_cannot_remove_main_add_new_one": "No es pot eliminar «{domain}» ja que és el domini principal i únic domini, primer s'ha d'afegir un altre domini utilitzant «yunohost domain add », i després fer-lo el domini principal amb «yunohost domain main-domain -n » i després es pot eliminar el domini «{domain}» utilitzant «yunohost domain remove {domain}».", "diagnosis_basesystem_host": "El servidor funciona amb Debian {debian_version}", "diagnosis_basesystem_kernel": "El servidor funciona amb el nucli de Linux {kernel_version}", "diagnosis_basesystem_ynh_single_version": "{package} versió: {version}({repo})", @@ -498,7 +498,7 @@ "diagnosis_description_web": "Web", "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.", + "certmanager_warning_subdomain_dns_record": "El subdomini «{subdomain}» no resol a la mateixa adreça IP que «{domain}». 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.", "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 --human-readable» 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", @@ -592,7 +592,7 @@ "migration_0019_add_new_attributes_in_ldap": "Afegir nous atributs per als permisos en la base de dades LDAP", "migration_description_0019_extend_permissions_features": "Amplia/refés el sistema de gestió dels permisos de l'aplicació", "migrating_legacy_permission_settings": "Migració dels paràmetres de permisos antics...", - "invalid_regex": "Regex no vàlid: «{regex:s}»", + "invalid_regex": "Regex no vàlid: «{regex}»", "global_settings_setting_smtp_relay_password": "Tramesa de la contrasenya d'amfitrió SMTP", "global_settings_setting_smtp_relay_user": "Tramesa de compte d'usuari SMTP", "global_settings_setting_smtp_relay_port": "Port de tramesa SMTP", @@ -608,8 +608,8 @@ "app_manifest_install_ask_domain": "Escolliu el domini en el que s'hauria d'instal·lar aquesta aplicació", "app_label_deprecated": "Aquesta ordre està desestimada! Si us plau utilitzeu la nova ordre «yunohost user permission update» per gestionar l'etiqueta de l'aplicació.", "app_argument_password_no_default": "Hi ha hagut un error al analitzar l'argument de la contrasenya «{name}»: l'argument de contrasenya no pot tenir un valor per defecte per raons de seguretat", - "additional_urls_already_removed": "URL addicional «{url:s}» ja ha estat eliminada per al permís «{permission:s}»", - "additional_urls_already_added": "URL addicional «{url:s}» ja ha estat afegida per al permís «{permission:s}»", + "additional_urls_already_removed": "URL addicional «{url}» ja ha estat eliminada per al permís «{permission}»", + "additional_urls_already_added": "URL addicional «{url}» ja ha estat afegida per al permís «{permission}»", "diagnosis_backports_in_sources_list": "Sembla que apt (el gestor de paquets) està configurat per utilitzar el repositori backports. A menys de saber el que esteu fent, recomanem fortament no instal·lar paquets de backports, ja que poder causar inestabilitats o conflictes en el sistema.", "diagnosis_basesystem_hardware_model": "El model del servidor és {model}", "postinstall_low_rootfsspace": "El sistema de fitxers arrel té un total de menys de 10 GB d'espai, el que es preocupant! És molt probable que us quedeu sense espai ràpidament! Es recomana tenir un mínim de 16 GB per al sistema de fitxers arrel. Si voleu instal·lar YunoHost tot i aquest avís, torneu a executar la postinstal·lació amb --force-diskspace", diff --git a/locales/cs.json b/locales/cs.json index 2dcee0f2f..cd1e9f7ae 100644 --- a/locales/cs.json +++ b/locales/cs.json @@ -1,20 +1,20 @@ { "password_too_simple_1": "Heslo musí být aspoň 8 znaků dlouhé", - "app_already_installed": "{app:s} je již nainstalován/a", + "app_already_installed": "{app} je již nainstalován/a", "already_up_to_date": "Neprovedena žádná akce. Vše je již aktuální.", "admin_password_too_long": "Zvolte prosím heslo kratší než 127 znaků", "admin_password_changed": "Administrační heslo bylo změněno", "admin_password_change_failed": "Nebylo možné změnit heslo", "admin_password": "Administrační heslo", - "additional_urls_already_removed": "Další URL '{url:s}' již bylo odebráno u oprávnění '{permission:s}'", - "additional_urls_already_added": "Další URL '{url:s}' již bylo přidáno pro oprávnění '{permission:s}'", - "action_invalid": "Nesprávné akce '{action:s}'", + "additional_urls_already_removed": "Další URL '{url}' již bylo odebráno u oprávnění '{permission}'", + "additional_urls_already_added": "Další URL '{url}' již bylo přidáno pro oprávnění '{permission}'", + "action_invalid": "Nesprávné akce '{action}'", "aborting": "Zrušeno.", - "app_change_url_identical_domains": "Stará a nová doména/url_cesta jsou totožné ('{domain:s}{path:s}'), nebudou provedeny žádné změny.", - "app_change_url_failed_nginx_reload": "Nepodařilo se znovunačís NGINX. Následuje výpis příkazu 'nginx -t':\n{nginx_errors:s}", - "app_argument_invalid": "Vyberte správnou hodnotu pro argument '{name:s}': {error:s}", - "app_argument_choice_invalid": "Vyberte jednu z možností '{choices:s}' pro argument'{name:s}'", - "app_already_up_to_date": "{app:s} aplikace je/jsou aktuální", + "app_change_url_identical_domains": "Stará a nová doména/url_cesta jsou totožné ('{domain}{path}'), nebudou provedeny žádné změny.", + "app_change_url_failed_nginx_reload": "Nepodařilo se znovunačís NGINX. Následuje výpis příkazu 'nginx -t':\n{nginx_errors}", + "app_argument_invalid": "Vyberte správnou hodnotu pro argument '{name}': {error}", + "app_argument_choice_invalid": "Vyberte jednu z možností '{choices}' pro argument'{name}'", + "app_already_up_to_date": "{app} aplikace je/jsou aktuální", "app_already_installed_cant_change_url": "Tato aplikace je již nainstalována. URL nemůže být touto akcí změněna. Zkontrolujte `app changeurl` pokud je dostupné.", "app_action_cannot_be_ran_because_required_services_down": "Pro běh této akce by měli být spuštěné následující služby: {services}. Zkuste je zrestartovat, případně zjistěte, proč neběží.", "app_action_broke_system": "Zdá se, že tato akce rozbila následující důležité služby: {services}", @@ -24,9 +24,9 @@ "app_id_invalid": "Neplatné ID aplikace", "app_full_domain_unavailable": "Tato aplikace musí být nainstalována na své vlastní doméně, jiné aplikace tuto doménu již využívají. Můžete použít poddoménu určenou pouze pro tuto aplikaci.", "app_extraction_failed": "Nelze rozbalit instalační soubory", - "app_change_url_success": "{app:s} URL je nyní {domain:s}{path:s}", - "app_change_url_no_script": "Aplikace '{app_name:s}' nyní nepodporuje URL modifikace. Zkuste ji aktualizovat.", - "app_argument_required": "Hodnota'{name:s}' je vyžadována", + "app_change_url_success": "{app} URL je nyní {domain}{path}", + "app_change_url_no_script": "Aplikace '{app_name}' nyní nepodporuje URL modifikace. Zkuste ji aktualizovat.", + "app_argument_required": "Hodnota'{name}' je vyžadována", "app_argument_password_no_default": "Chyba při zpracování obsahu hesla '{name}': z bezpečnostních důvodů nemůže obsahovat výchozí hodnotu", "password_too_simple_4": "Heslo musí být aspoň 12 znaků dlouhé a obsahovat čísla, velká a malá písmena a speciální znaky", "password_too_simple_3": "Heslo musí být aspoň 8 znaků dlouhé a obsahovat čísla, velká a malá písmena a speciální znaky", @@ -36,7 +36,7 @@ "group_user_already_in_group": "Uživatel {user} je již ve skupině {group}", "group_update_failed": "Nelze upravit skupinu '{group}': {error}", "group_updated": "Skupina '{group}' upravena", - "group_unknown": "Neznámá skupina '{group:s}'", + "group_unknown": "Neznámá skupina '{group}'", "group_deletion_failed": "Nelze smazat skupinu '{group}': {error}", "group_deleted": "Skupina '{group}' smazána", "group_cannot_be_deleted": "Skupina {group} nemůže být smazána.", @@ -50,7 +50,7 @@ "group_already_exist": "Skupina {group} již existuje", "good_practices_about_user_password": "Nyní zvolte nové heslo uživatele. Heslo by mělo být minimálně 8 znaků dlouhé, avšak je dobrou taktikou jej mít delší (např. použít více slov) a použít kombinaci znaků (velké, malé, čísla a speciální znaky).", "good_practices_about_admin_password": "Nyní zvolte nové administrační heslo. Heslo by mělo být minimálně 8 znaků dlouhé, avšak je dobrou taktikou jej mít delší (např. použít více slov) a použít kombinaci znaků (velké, malé, čísla a speciílní znaky).", - "global_settings_unknown_type": "Neočekávaná situace, nastavení {setting:s} deklaruje typ {unknown_type:s} ale toto není systémem podporováno.", + "global_settings_unknown_type": "Neočekávaná situace, nastavení {setting} deklaruje typ {unknown_type} ale toto není systémem podporováno.", "global_settings_setting_backup_compress_tar_archives": "Komprimovat nové zálohy (.tar.gz) namísto nekomprimovaných (.tar). Poznámka: povolení této volby znamená objemově menší soubory záloh, avšak zálohování bude trvat déle a bude více zatěžovat CPU.", "global_settings_setting_smtp_relay_password": "SMTP relay heslo uživatele/hostitele", "global_settings_setting_smtp_relay_user": "SMTP relay uživatelské jméno/účet", @@ -59,7 +59,7 @@ "global_settings_setting_smtp_allow_ipv6": "Povolit použití IPv6 pro příjem a odesílání emailů", "global_settings_setting_ssowat_panel_overlay_enabled": "Povolit SSOwat překryvný panel", "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Povolit použití (zastaralého) DSA klíče hostitele pro konfiguraci SSH služby", - "global_settings_unknown_setting_from_settings_file": "Neznámý klíč v nastavení: '{setting_key:s}', zrušte jej a uložte v /etc/yunohost/settings-unknown.json", + "global_settings_unknown_setting_from_settings_file": "Neznámý klíč v nastavení: '{setting_key}', zrušte jej a uložte v /etc/yunohost/settings-unknown.json", "global_settings_setting_security_ssh_port": "SSH port", "global_settings_setting_security_postfix_compatibility": "Kompromis mezi kompatibilitou a bezpečností Postfix serveru. Ovlivní šifry a další související bezpečnostní nastavení", "global_settings_setting_security_ssh_compatibility": "Kompromis mezi kompatibilitou a bezpečností SSH serveru. Ovlivní šifry a další související bezpečnostní nastavení", diff --git a/locales/de.json b/locales/de.json index 7fb22c716..d11508a54 100644 --- a/locales/de.json +++ b/locales/de.json @@ -1,43 +1,43 @@ { - "action_invalid": "Ungültige Aktion '{action:s}'", + "action_invalid": "Ungültige Aktion '{action}'", "admin_password": "Administrator-Passwort", "admin_password_change_failed": "Ändern des Passworts nicht möglich", "admin_password_changed": "Das Administrator-Kennwort wurde geändert", - "app_already_installed": "{app:s} ist schon installiert", - "app_argument_choice_invalid": "Wähle einen der folgenden Werte '{choices:s}' für das Argument '{name:s}'", - "app_argument_invalid": "Wähle einen gültigen Wert für das Argument '{name:s}': {error:s}", - "app_argument_required": "Argument '{name:s}' wird benötigt", + "app_already_installed": "{app} ist schon installiert", + "app_argument_choice_invalid": "Wähle einen der folgenden Werte '{choices}' für das Argument '{name}'", + "app_argument_invalid": "Wähle einen gültigen Wert für das Argument '{name}': {error}", + "app_argument_required": "Argument '{name}' wird benötigt", "app_extraction_failed": "Installationsdateien konnten nicht entpackt werden", "app_id_invalid": "Falsche App-ID", "app_install_files_invalid": "Diese Dateien können nicht installiert werden", "app_manifest_invalid": "Mit dem App-Manifest stimmt etwas nicht: {error}", - "app_not_installed": "{app:s} konnte nicht in der Liste installierter Apps gefunden werden: {all_apps}", - "app_removed": "{app:s} wurde entfernt", + "app_not_installed": "{app} konnte nicht in der Liste installierter Apps gefunden werden: {all_apps}", + "app_removed": "{app} wurde entfernt", "app_sources_fetch_failed": "Quelldateien konnten nicht abgerufen werden, ist die URL korrekt?", "app_unknown": "Unbekannte App", - "app_upgrade_failed": "{app:s} konnte nicht aktualisiert werden: {error}", - "app_upgraded": "{app:s} aktualisiert", + "app_upgrade_failed": "{app} konnte nicht aktualisiert werden: {error}", + "app_upgraded": "{app} aktualisiert", "ask_firstname": "Vorname", "ask_lastname": "Nachname", "ask_main_domain": "Hauptdomain", "ask_new_admin_password": "Neues Verwaltungskennwort", "ask_password": "Passwort", - "backup_app_failed": "Konnte keine Sicherung für {app:s} erstellen", - "backup_archive_app_not_found": "{app:s} konnte in keiner Datensicherung gefunden werden", + "backup_app_failed": "Konnte keine Sicherung für {app} erstellen", + "backup_archive_app_not_found": "{app} konnte in keiner Datensicherung gefunden werden", "backup_archive_name_exists": "Datensicherung mit dem selben Namen existiert bereits.", - "backup_archive_name_unknown": "Unbekanntes lokale Datensicherung mit Namen '{name:s}' gefunden", + "backup_archive_name_unknown": "Unbekanntes lokale Datensicherung mit Namen '{name}' gefunden", "backup_archive_open_failed": "Kann Sicherungsarchiv nicht öfnen", "backup_cleaning_failed": "Temporäres Sicherungsverzeichnis konnte nicht geleert werden", "backup_created": "Datensicherung komplett", - "backup_delete_error": "Pfad '{path:s}' konnte nicht gelöscht werden", + "backup_delete_error": "Pfad '{path}' konnte nicht gelöscht werden", "backup_deleted": "Backup wurde entfernt", - "backup_hook_unknown": "Der Datensicherungshook '{hook:s}' unbekannt", + "backup_hook_unknown": "Der Datensicherungshook '{hook}' unbekannt", "backup_nothings_done": "Keine Änderungen zur Speicherung", "backup_output_directory_forbidden": "Wähle ein anderes Ausgabeverzeichnis. Datensicherungen können nicht in /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var oder in Unterordnern von /home/yunohost.backup/archives erstellt werden", "backup_output_directory_not_empty": "Der gewählte Ausgabeordner sollte leer sein", "backup_output_directory_required": "Für die Datensicherung muss ein Zielverzeichnis angegeben werden", "backup_running_hooks": "Datensicherunghook wird ausgeführt...", - "custom_app_url_required": "Sie müssen eine URL angeben, um Ihre benutzerdefinierte App {app:s} zu aktualisieren", + "custom_app_url_required": "Sie müssen eine URL angeben, um Ihre benutzerdefinierte App {app} zu aktualisieren", "domain_cert_gen_failed": "Zertifikat konnte nicht erzeugt werden", "domain_created": "Domäne erstellt", "domain_creation_failed": "Konnte Domäne nicht erzeugen", @@ -54,23 +54,23 @@ "dyndns_ip_updated": "Aktualisierung Ihrer IP-Adresse bei DynDNS", "dyndns_key_generating": "Generierung des DNS-Schlüssels..., das könnte eine Weile dauern.", "dyndns_registered": "DynDNS Domain registriert", - "dyndns_registration_failed": "DynDNS Domain konnte nicht registriert werden: {error:s}", - "dyndns_unavailable": "Die Domäne {domain:s} ist nicht verfügbar.", + "dyndns_registration_failed": "DynDNS Domain konnte nicht registriert werden: {error}", + "dyndns_unavailable": "Die Domäne {domain} ist nicht verfügbar.", "extracting": "Wird entpackt...", - "field_invalid": "Feld '{:s}' ist unbekannt", + "field_invalid": "Feld '{}' ist unbekannt", "firewall_reload_failed": "Firewall konnte nicht neu geladen werden", "firewall_reloaded": "Firewall neu geladen", "firewall_rules_cmd_failed": "Einige Befehle für die Firewallregeln sind gescheitert. Mehr Informationen im Log.", - "hook_exec_failed": "Konnte Skript nicht ausführen: {path:s}", - "hook_exec_not_terminated": "Skript ist nicht normal beendet worden: {path:s}", + "hook_exec_failed": "Konnte Skript nicht ausführen: {path}", + "hook_exec_not_terminated": "Skript ist nicht normal beendet worden: {path}", "hook_list_by_invalid": "Dieser Wert kann nicht verwendet werden, um Hooks anzuzeigen", - "hook_name_unknown": "Hook '{name:s}' ist nicht bekannt", + "hook_name_unknown": "Hook '{name}' ist nicht bekannt", "installation_complete": "Installation vollständig", "ip6tables_unavailable": "ip6tables kann nicht verwendet werden. Du befindest dich entweder in einem Container oder es wird nicht vom Kernel unterstützt", "iptables_unavailable": "iptables kann nicht verwendet werden. Du befindest dich entweder in einem Container oder es wird nicht vom Kernel unterstützt", - "mail_alias_remove_failed": "Konnte E-Mail-Alias '{mail:s}' nicht entfernen", - "mail_domain_unknown": "Die Domäne '{domain:s}' dieser E-Mail-Adresse ist ungültig. Wähle bitte eine Domäne, welche durch diesen Server verwaltet wird.", - "mail_forward_remove_failed": "Die Weiterleitungs-E-Mail '{mail:s}' konnte nicht gelöscht werden", + "mail_alias_remove_failed": "Konnte E-Mail-Alias '{mail}' nicht entfernen", + "mail_domain_unknown": "Die Domäne '{domain}' dieser E-Mail-Adresse ist ungültig. Wähle bitte eine Domäne, welche durch diesen Server verwaltet wird.", + "mail_forward_remove_failed": "Die Weiterleitungs-E-Mail '{mail}' konnte nicht gelöscht werden", "main_domain_change_failed": "Die Hauptdomain konnte nicht geändert werden", "main_domain_changed": "Die Hauptdomain wurde geändert", "packages_upgrade_failed": "Konnte nicht alle Pakete aktualisieren", @@ -83,41 +83,41 @@ "pattern_password": "Muss mindestens drei Zeichen lang sein", "pattern_port_or_range": "Muss ein valider Port (z.B. 0-65535) oder ein Bereich (z.B. 100:200) sein", "pattern_username": "Darf nur aus klein geschriebenen alphanumerischen Zeichen und Unterstrichen bestehen", - "port_already_closed": "Der Port {port:d} wurde bereits für {ip_version:s} Verbindungen geschlossen", - "port_already_opened": "Der Port {port:d} wird bereits von {ip_version:s} benutzt", - "restore_already_installed_app": "Eine Applikation mit der ID '{app:s}' ist bereits installiert", + "port_already_closed": "Der Port {port:d} wurde bereits für {ip_version} Verbindungen geschlossen", + "port_already_opened": "Der Port {port:d} wird bereits von {ip_version} benutzt", + "restore_already_installed_app": "Eine Applikation mit der ID '{app}' ist bereits installiert", "restore_cleaning_failed": "Das temporäre Dateiverzeichnis für Systemrestaurierung konnte nicht gelöscht werden", "restore_complete": "Vollständig wiederhergestellt", - "restore_confirm_yunohost_installed": "Möchtest du die Wiederherstellung wirklich starten? [{answers:s}]", + "restore_confirm_yunohost_installed": "Möchtest du die Wiederherstellung wirklich starten? [{answers}]", "restore_failed": "Das System konnte nicht wiederhergestellt werden", - "restore_hook_unavailable": "Das Wiederherstellungsskript für '{part:s}' steht weder in Ihrem System noch im Archiv zur Verfügung", + "restore_hook_unavailable": "Das Wiederherstellungsskript für '{part}' steht weder in Ihrem System noch im Archiv zur Verfügung", "restore_nothings_done": "Nichts wurde wiederhergestellt", - "restore_running_app_script": "App '{app:s}' wird wiederhergestellt…", + "restore_running_app_script": "App '{app}' wird wiederhergestellt…", "restore_running_hooks": "Wiederherstellung wird gestartet…", - "service_add_failed": "Der Dienst '{service:s}' konnte nicht hinzugefügt werden", - "service_added": "Der Dienst '{service:s}' wurde erfolgreich hinzugefügt", - "service_already_started": "Der Dienst '{service:s}' läuft bereits", - "service_already_stopped": "Der Dienst '{service:s}' wurde bereits gestoppt", - "service_cmd_exec_failed": "Der Befehl '{command:s}' konnte nicht ausgeführt werden", - "service_disable_failed": "Der Start des Dienstes '{service:s}' beim Hochfahren konnte nicht verhindert werden.\n\nKürzlich erstellte Logs des Dienstes: {logs:s}", - "service_disabled": "Der Dienst '{service:s}' wird beim Hochfahren des Systems nicht mehr gestartet werden.", - "service_enable_failed": "Der Dienst '{service:s}' konnte beim Hochfahren nicht gestartet werden.\n\nKürzlich erstellte Logs des Dienstes: {logs:s}", - "service_enabled": "Der Dienst '{service:s}' wird nun beim Hochfahren des Systems automatisch gestartet.", - "service_remove_failed": "Konnte den Dienst '{service:s}' nicht entfernen", - "service_removed": "Der Dienst '{service:s}' wurde erfolgreich entfernt", - "service_start_failed": "Der Dienst '{service:s}' konnte nicht gestartet werden\n\nKürzlich erstellte Logs des Dienstes: {logs:s}", - "service_started": "Der Dienst '{service:s}' wurde erfolgreich gestartet", - "service_stop_failed": "Der Dienst '{service:s}' kann nicht gestoppt werden\n\nAktuelle Service-Logs: {logs:s}", - "service_stopped": "Der Dienst '{service:s}' wurde erfolgreich beendet", - "service_unknown": "Unbekannter Dienst '{service:s}'", + "service_add_failed": "Der Dienst '{service}' konnte nicht hinzugefügt werden", + "service_added": "Der Dienst '{service}' wurde erfolgreich hinzugefügt", + "service_already_started": "Der Dienst '{service}' läuft bereits", + "service_already_stopped": "Der Dienst '{service}' wurde bereits gestoppt", + "service_cmd_exec_failed": "Der Befehl '{command}' konnte nicht ausgeführt werden", + "service_disable_failed": "Der Start des Dienstes '{service}' beim Hochfahren konnte nicht verhindert werden.\n\nKürzlich erstellte Logs des Dienstes: {logs}", + "service_disabled": "Der Dienst '{service}' wird beim Hochfahren des Systems nicht mehr gestartet werden.", + "service_enable_failed": "Der Dienst '{service}' konnte beim Hochfahren nicht gestartet werden.\n\nKürzlich erstellte Logs des Dienstes: {logs}", + "service_enabled": "Der Dienst '{service}' wird nun beim Hochfahren des Systems automatisch gestartet.", + "service_remove_failed": "Konnte den Dienst '{service}' nicht entfernen", + "service_removed": "Der Dienst '{service}' wurde erfolgreich entfernt", + "service_start_failed": "Der Dienst '{service}' konnte nicht gestartet werden\n\nKürzlich erstellte Logs des Dienstes: {logs}", + "service_started": "Der Dienst '{service}' wurde erfolgreich gestartet", + "service_stop_failed": "Der Dienst '{service}' kann nicht gestoppt werden\n\nAktuelle Service-Logs: {logs}", + "service_stopped": "Der Dienst '{service}' wurde erfolgreich beendet", + "service_unknown": "Unbekannter Dienst '{service}'", "ssowat_conf_generated": "Konfiguration von SSOwat neu erstellt", "ssowat_conf_updated": "Die Konfiguration von SSOwat aktualisiert", "system_upgraded": "System aktualisiert", "system_username_exists": "Der Benutzername existiert bereits in der Liste der System-Benutzer", - "unbackup_app": "'{app:s}' wird nicht gespeichert werden", + "unbackup_app": "'{app}' wird nicht gespeichert werden", "unexpected_error": "Etwas Unerwartetes ist passiert: {error}", "unlimit": "Kein Kontingent", - "unrestore_app": "{app:s} wird nicht wiederhergestellt werden", + "unrestore_app": "{app} wird nicht wiederhergestellt werden", "updating_apt_cache": "Die Liste der verfügbaren Pakete wird aktualisiert…", "upgrade_complete": "Upgrade vollständig", "upgrading_packages": "Pakete werden aktualisiert…", @@ -130,85 +130,85 @@ "user_deleted": "Der Benutzer wurde entfernt", "user_deletion_failed": "Benutzer konnte nicht gelöscht werden {user}: {error}", "user_home_creation_failed": "Persönlicher Ordner des Benutzers konnte nicht erstellt werden", - "user_unknown": "Unbekannter Benutzer: {user:s}", + "user_unknown": "Unbekannter Benutzer: {user}", "user_update_failed": "Benutzer konnte nicht aktualisiert werden {user}: {error}", "user_updated": "Der Benutzer wurde aktualisiert", "yunohost_already_installed": "YunoHost ist bereits installiert", "yunohost_configured": "YunoHost wurde konfiguriert", "yunohost_installing": "YunoHost wird installiert...", "yunohost_not_installed": "YunoHost ist nicht oder nur unvollständig installiert worden. Bitte 'yunohost tools postinstall' ausführen", - "app_not_properly_removed": "{app:s} wurde nicht ordnungsgemäß entfernt", - "not_enough_disk_space": "Nicht genügend Speicherplatz auf '{path:s}' frei", + "app_not_properly_removed": "{app} wurde nicht ordnungsgemäß entfernt", + "not_enough_disk_space": "Nicht genügend Speicherplatz auf '{path}' frei", "backup_creation_failed": "Konnte Backup-Archiv nicht erstellen", "pattern_positive_number": "Muss eine positive Zahl sein", - "app_not_correctly_installed": "{app:s} scheint nicht korrekt installiert zu sein", + "app_not_correctly_installed": "{app} scheint nicht korrekt installiert zu sein", "app_requirements_checking": "Überprüfe notwendige Pakete für {app}...", "app_requirements_unmeet": "Anforderungen für {app} werden nicht erfüllt, das Paket {pkgname} ({version}) muss {spec} sein", "app_unsupported_remote_type": "Für die App wurde ein nicht unterstützer Steuerungstyp verwendet", - "backup_archive_broken_link": "Auf das Backup-Archiv konnte nicht zugegriffen werden (ungültiger Link zu {path:s})", + "backup_archive_broken_link": "Auf das Backup-Archiv konnte nicht zugegriffen werden (ungültiger Link zu {path})", "domains_available": "Verfügbare Domains:", "dyndns_key_not_found": "DNS-Schlüssel für die Domain wurde nicht gefunden", "dyndns_no_domain_registered": "Keine Domain mit DynDNS registriert", "mailbox_used_space_dovecot_down": "Der Dovecot-Mailbox-Dienst muss aktiv sein, wenn Sie den von der Mailbox belegten Speicher abrufen wollen", - "certmanager_attempt_to_replace_valid_cert": "Du versuchst gerade eine richtiges und gültiges Zertifikat der Domain {domain:s} zu überschreiben! (Benutze --force , um diese Nachricht zu umgehen)", - "certmanager_domain_cert_not_selfsigned": "Das Zertifikat der Domain {domain:s} ist kein selbstsigniertes Zertifikat. Sind Sie sich sicher, dass Sie es ersetzen wollen? (Benutzen Sie dafür '--force')", - "certmanager_certificate_fetching_or_enabling_failed": "Die Aktivierung des neuen Zertifikats für die {domain:s} ist fehlgeschlagen...", - "certmanager_attempt_to_renew_nonLE_cert": "Das Zertifikat der Domain '{domain:s}' wurde nicht von Let's Encrypt ausgestellt. Es kann nicht automatisch erneuert werden!", - "certmanager_attempt_to_renew_valid_cert": "Das Zertifikat der Domain {domain:s} läuft nicht in Kürze ab! (Benutze --force um diese Nachricht zu umgehen)", - "certmanager_domain_http_not_working": "Es scheint, als ob die Domäne {domain:s} nicht über HTTP erreicht werden kann. Bitte überprüfen Sie, ob Ihre DNS- und nginx-Konfiguration in Ordnung ist. (Wenn Sie wissen was Sie tun, nutzen Sie \"--no-checks\" um die Überprüfung zu überspringen.)", - "certmanager_domain_dns_ip_differs_from_public_ip": "Der DNS-A-Eintrag der Domain {domain:s} unterscheidet sich von dieser Server-IP. Für weitere Informationen überprüfen Sie bitte die 'DNS records' (basic) Kategorie in der Diagnose. Wenn Sie gerade Ihren A-Eintrag verändert haben, warten Sie bitte etwas, damit die Änderungen wirksam werden (Sie können die DNS-Propagation mittels Website überprüfen) (Wenn Sie wissen was Sie tun, können Sie --no-checks benutzen, um diese Überprüfung zu überspringen.)", - "certmanager_cannot_read_cert": "Es ist ein Fehler aufgetreten, als es versucht wurde das aktuelle Zertifikat für die Domain {domain:s} zu öffnen (Datei: {file:s}), Grund: {reason:s}", - "certmanager_cert_install_success_selfsigned": "Das selbstsignierte Zertifikat für die Domäne '{domain:s}' wurde erfolgreich installiert", - "certmanager_cert_install_success": "Let's-Encrypt-Zertifikat für die Domäne {domain:s} ist jetzt installiert", - "certmanager_cert_renew_success": "Das Let's Encrypt Zertifikat für die Domain {domain:s} wurde erfolgreich erneuert", - "certmanager_hit_rate_limit": "Es wurden innerhalb kurzer Zeit zu viele Zertifikate für dieselbe Domäne {domain:s} ausgestellt. Bitte versuchen Sie es später nochmal. Besuchen Sie https://letsencrypt.org/docs/rate-limits/ für mehr Informationen", + "certmanager_attempt_to_replace_valid_cert": "Du versuchst gerade eine richtiges und gültiges Zertifikat der Domain {domain} zu überschreiben! (Benutze --force , um diese Nachricht zu umgehen)", + "certmanager_domain_cert_not_selfsigned": "Das Zertifikat der Domain {domain} ist kein selbstsigniertes Zertifikat. Sind Sie sich sicher, dass Sie es ersetzen wollen? (Benutzen Sie dafür '--force')", + "certmanager_certificate_fetching_or_enabling_failed": "Die Aktivierung des neuen Zertifikats für die {domain} ist fehlgeschlagen...", + "certmanager_attempt_to_renew_nonLE_cert": "Das Zertifikat der Domain '{domain}' wurde nicht von Let's Encrypt ausgestellt. Es kann nicht automatisch erneuert werden!", + "certmanager_attempt_to_renew_valid_cert": "Das Zertifikat der Domain {domain} läuft nicht in Kürze ab! (Benutze --force um diese Nachricht zu umgehen)", + "certmanager_domain_http_not_working": "Es scheint, als ob die Domäne {domain} nicht über HTTP erreicht werden kann. Bitte überprüfen Sie, ob Ihre DNS- und nginx-Konfiguration in Ordnung ist. (Wenn Sie wissen was Sie tun, nutzen Sie \"--no-checks\" um die Überprüfung zu überspringen.)", + "certmanager_domain_dns_ip_differs_from_public_ip": "Der DNS-A-Eintrag der Domain {domain} unterscheidet sich von dieser Server-IP. Für weitere Informationen überprüfen Sie bitte die 'DNS records' (basic) Kategorie in der Diagnose. Wenn Sie gerade Ihren A-Eintrag verändert haben, warten Sie bitte etwas, damit die Änderungen wirksam werden (Sie können die DNS-Propagation mittels Website überprüfen) (Wenn Sie wissen was Sie tun, können Sie --no-checks benutzen, um diese Überprüfung zu überspringen.)", + "certmanager_cannot_read_cert": "Es ist ein Fehler aufgetreten, als es versucht wurde das aktuelle Zertifikat für die Domain {domain} zu öffnen (Datei: {file}), Grund: {reason}", + "certmanager_cert_install_success_selfsigned": "Das selbstsignierte Zertifikat für die Domäne '{domain}' wurde erfolgreich installiert", + "certmanager_cert_install_success": "Let's-Encrypt-Zertifikat für die Domäne {domain} ist jetzt installiert", + "certmanager_cert_renew_success": "Das Let's Encrypt Zertifikat für die Domain {domain} wurde erfolgreich erneuert", + "certmanager_hit_rate_limit": "Es wurden innerhalb kurzer Zeit zu viele Zertifikate für dieselbe Domäne {domain} ausgestellt. Bitte versuchen Sie es später nochmal. Besuchen Sie https://letsencrypt.org/docs/rate-limits/ für mehr Informationen", "certmanager_cert_signing_failed": "Das neue Zertifikat konnte nicht signiert werden", - "certmanager_no_cert_file": "Die Zertifikatsdatei für die Domain {domain:s} (Datei: {file:s}) konnte nicht gelesen werden", - "domain_cannot_remove_main": "Die primäre Domain konnten nicht entfernt werden. Lege zuerst einen neue primäre Domain Sie können die Domäne '{domain:s}' nicht entfernen, weil Sie die Hauptdomäne ist. Sie müssen zuerst eine andere Domäne als Hauptdomäne festlegen. Sie können das mit dem Befehl 'yunohost domain main-domain -n tun. Hier ist eine Liste der möglichen Domänen: {other_domains:s}", - "certmanager_self_ca_conf_file_not_found": "Die Konfigurationsdatei der Zertifizierungsstelle für selbstsignierte Zertifikate wurde nicht gefunden (Datei {file:s})", + "certmanager_no_cert_file": "Die Zertifikatsdatei für die Domain {domain} (Datei: {file}) konnte nicht gelesen werden", + "domain_cannot_remove_main": "Die primäre Domain konnten nicht entfernt werden. Lege zuerst einen neue primäre Domain Sie können die Domäne '{domain}' nicht entfernen, weil Sie die Hauptdomäne ist. Sie müssen zuerst eine andere Domäne als Hauptdomäne festlegen. Sie können das mit dem Befehl 'yunohost domain main-domain -n tun. Hier ist eine Liste der möglichen Domänen: {other_domains}", + "certmanager_self_ca_conf_file_not_found": "Die Konfigurationsdatei der Zertifizierungsstelle für selbstsignierte Zertifikate wurde nicht gefunden (Datei {file})", "certmanager_acme_not_configured_for_domain": "Die ACME Challenge kann im Moment nicht für {domain} ausgeführt werden, weil in ihrer nginx conf das entsprechende Code-Snippet fehlt... Bitte stellen Sie sicher, dass Ihre nginx-Konfiguration mit 'yunohost tools regen-conf nginx --dry-run --with-diff' auf dem neuesten Stand ist.", - "certmanager_unable_to_parse_self_CA_name": "Der Name der Zertifizierungsstelle für selbstsignierte Zertifikate konnte nicht aufgelöst werden (Datei: {file:s})", + "certmanager_unable_to_parse_self_CA_name": "Der Name der Zertifizierungsstelle für selbstsignierte Zertifikate konnte nicht aufgelöst werden (Datei: {file})", "domain_hostname_failed": "Sie können keinen neuen Hostnamen verwenden. Das kann zukünftige Probleme verursachen (es kann auch sein, dass es funktioniert).", "app_already_installed_cant_change_url": "Diese Applikation ist bereits installiert. Die URL kann durch diese Funktion nicht modifiziert werden. Überprüfe ob `app changeurl` verfügbar ist.", - "app_change_url_failed_nginx_reload": "NGINX konnte nicht neu gestartet werden. Hier ist der Output von 'nginx -t':\n{nginx_errors:s}", - "app_change_url_identical_domains": "Die alte und neue domain/url_path sind identisch: ('{domain:s} {path:s}'). Es gibt nichts zu tun.", - "app_already_up_to_date": "{app:s} ist bereits aktuell", + "app_change_url_failed_nginx_reload": "NGINX konnte nicht neu gestartet werden. Hier ist der Output von 'nginx -t':\n{nginx_errors}", + "app_change_url_identical_domains": "Die alte und neue domain/url_path sind identisch: ('{domain} {path}'). Es gibt nichts zu tun.", + "app_already_up_to_date": "{app} ist bereits aktuell", "backup_abstract_method": "Diese Backup-Methode wird noch nicht unterstützt", "backup_applying_method_tar": "Erstellen des Backup-tar Archives...", "backup_applying_method_copy": "Kopiere alle Dateien ins Backup...", - "app_change_url_no_script": "Die Applikation '{app_name:s}' unterstützt bisher keine URL-Modifikation. Vielleicht sollte sie aktualisiert werden.", - "app_location_unavailable": "Diese URL ist nicht verfügbar oder wird von einer installierten Applikation genutzt:\n{apps:s}", - "backup_applying_method_custom": "Rufe die benutzerdefinierte Backup-Methode '{method:s}' auf...", - "backup_archive_system_part_not_available": "Der System-Teil '{part:s}' ist in diesem Backup nicht enthalten", - "backup_archive_writing_error": "Die Dateien '{source:s} (im Ordner '{dest:s}') konnten nicht in das komprimierte Archiv-Backup '{archive:s}' hinzugefügt werden", - "app_change_url_success": "{app:s} URL ist nun {domain:s}{path:s}", - "global_settings_bad_type_for_setting": "Falscher Typ der Einstellung {setting:s}. Empfangen: {received_type:s}, aber erwarteter Typ: {expected_type:s}", - "global_settings_bad_choice_for_enum": "Wert des Einstellungsparameters {setting:s} ungültig. Der Wert den Sie eingegeben haben: '{choice:s}', die gültigen Werte für diese Einstellung: {available_choices:s}", - "file_does_not_exist": "Die Datei {path:s} existiert nicht.", + "app_change_url_no_script": "Die Applikation '{app_name}' unterstützt bisher keine URL-Modifikation. Vielleicht sollte sie aktualisiert werden.", + "app_location_unavailable": "Diese URL ist nicht verfügbar oder wird von einer installierten Applikation genutzt:\n{apps}", + "backup_applying_method_custom": "Rufe die benutzerdefinierte Backup-Methode '{method}' auf...", + "backup_archive_system_part_not_available": "Der System-Teil '{part}' ist in diesem Backup nicht enthalten", + "backup_archive_writing_error": "Die Dateien '{source} (im Ordner '{dest}') konnten nicht in das komprimierte Archiv-Backup '{archive}' hinzugefügt werden", + "app_change_url_success": "{app} URL ist nun {domain}{path}", + "global_settings_bad_type_for_setting": "Falscher Typ der Einstellung {setting}. Empfangen: {received_type}, aber erwarteter Typ: {expected_type}", + "global_settings_bad_choice_for_enum": "Wert des Einstellungsparameters {setting} ungültig. Der Wert den Sie eingegeben haben: '{choice}', die gültigen Werte für diese Einstellung: {available_choices}", + "file_does_not_exist": "Die Datei {path} existiert nicht.", "experimental_feature": "Warnung: Der Maintainer hat diese Funktion als experimentell gekennzeichnet. Sie ist nicht stabil. Sie sollten sie nur verwenden, wenn Sie wissen, was Sie tun.", - "dyndns_domain_not_provided": "Der DynDNS-Anbieter {provider:s} kann die Domäne(n) {domain:s} nicht bereitstellen.", - "dyndns_could_not_check_available": "Konnte nicht überprüfen, ob {domain:s} auf {provider:s} verfügbar ist.", - "dyndns_could_not_check_provide": "Konnte nicht überprüft, ob {provider:s} die Domain(s) {domain:s} bereitstellen kann.", + "dyndns_domain_not_provided": "Der DynDNS-Anbieter {provider} kann die Domäne(n) {domain} nicht bereitstellen.", + "dyndns_could_not_check_available": "Konnte nicht überprüfen, ob {domain} auf {provider} verfügbar ist.", + "dyndns_could_not_check_provide": "Konnte nicht überprüft, ob {provider} die Domain(s) {domain} bereitstellen kann.", "domain_dns_conf_is_just_a_recommendation": "Dieser Befehl zeigt Ihnen die *empfohlene* Konfiguration. Die DNS-Konfiguration wird NICHT für Sie eingerichtet. Es liegt in Ihrer Verantwortung, Ihre DNS-Zone in Ihrem Registrar gemäß dieser Empfehlung zu konfigurieren.", "dpkg_lock_not_available": "Dieser Befehl kann momentan nicht ausgeführt werden, da anscheinend ein anderes Programm die Sperre von dpkg (dem Systempaket-Manager) verwendet", - "confirm_app_install_thirdparty": "WARNUNG! Diese Applikation ist nicht Teil des YunoHost-Applikationskatalogs. Das Installieren von Drittanbieterapplikationen könnte die Sicherheit und Integrität Ihres Systems beeinträchtigen. Sie sollten wahrscheinlich NICHT fortfahren, es sei denn, Sie wissen, was Sie tun. Es wird KEIN SUPPORT angeboten, wenn die Applikation nicht funktionieren oder Ihr System beschädigen sollte... Wenn Sie das Risiko trotzdem eingehen möchten, tippen Sie '{answers:s}'", - "confirm_app_install_danger": "WARNUNG! Diese Applikation ist noch experimentell (wenn nicht ausdrücklich nicht funktionsfähig)! Sie sollten Sie wahrscheinlich NICHT installieren, es sei denn, Sie wissen, was Sie tun. Es wird keine Unterstützung angeboten, falls diese Applikation nicht funktionieren oder Ihr System beschädigen sollte... Falls Sie bereit sind, dieses Risiko einzugehen, tippen Sie '{answers:s}'", - "confirm_app_install_warning": "Warnung: Diese Applikation funktioniert möglicherweise, ist jedoch nicht gut in YunoHost integriert. Einige Funktionen wie Single Sign-On und Backup / Restore sind möglicherweise nicht verfügbar. Trotzdem installieren? [{answers:s}] ", - "backup_with_no_restore_script_for_app": "{app:s} hat kein Wiederherstellungsskript. Das Backup dieser App kann nicht automatisch wiederhergestellt werden.", - "backup_with_no_backup_script_for_app": "Die App {app:s} hat kein Sicherungsskript. Ignoriere es.", + "confirm_app_install_thirdparty": "WARNUNG! Diese Applikation ist nicht Teil des YunoHost-Applikationskatalogs. Das Installieren von Drittanbieterapplikationen könnte die Sicherheit und Integrität Ihres Systems beeinträchtigen. Sie sollten wahrscheinlich NICHT fortfahren, es sei denn, Sie wissen, was Sie tun. Es wird KEIN SUPPORT angeboten, wenn die Applikation nicht funktionieren oder Ihr System beschädigen sollte... Wenn Sie das Risiko trotzdem eingehen möchten, tippen Sie '{answers}'", + "confirm_app_install_danger": "WARNUNG! Diese Applikation ist noch experimentell (wenn nicht ausdrücklich nicht funktionsfähig)! Sie sollten Sie wahrscheinlich NICHT installieren, es sei denn, Sie wissen, was Sie tun. Es wird keine Unterstützung angeboten, falls diese Applikation nicht funktionieren oder Ihr System beschädigen sollte... Falls Sie bereit sind, dieses Risiko einzugehen, tippen Sie '{answers}'", + "confirm_app_install_warning": "Warnung: Diese Applikation funktioniert möglicherweise, ist jedoch nicht gut in YunoHost integriert. Einige Funktionen wie Single Sign-On und Backup / Restore sind möglicherweise nicht verfügbar. Trotzdem installieren? [{answers}] ", + "backup_with_no_restore_script_for_app": "{app} hat kein Wiederherstellungsskript. Das Backup dieser App kann nicht automatisch wiederhergestellt werden.", + "backup_with_no_backup_script_for_app": "Die App {app} hat kein Sicherungsskript. Ignoriere es.", "backup_unable_to_organize_files": "Dateien im Archiv konnten nicht mit der schnellen Methode organisiert werden", - "backup_system_part_failed": "Der Systemteil '{part:s}' konnte nicht gesichert werden", - "backup_permission": "Sicherungsberechtigung für {app:s}", - "backup_output_symlink_dir_broken": "Ihr Archivverzeichnis '{path:s}' ist ein fehlerhafter Symlink. Vielleicht haben Sie vergessen, das Speichermedium, auf das er verweist, neu zu mounten oder einzustecken.", + "backup_system_part_failed": "Der Systemteil '{part}' konnte nicht gesichert werden", + "backup_permission": "Sicherungsberechtigung für {app}", + "backup_output_symlink_dir_broken": "Ihr Archivverzeichnis '{path}' ist ein fehlerhafter Symlink. Vielleicht haben Sie vergessen, das Speichermedium, auf das er verweist, neu zu mounten oder einzustecken.", "backup_mount_archive_for_restore": "Archiv für Wiederherstellung vorbereiten...", "backup_method_tar_finished": "Tar-Backup-Archiv erstellt", - "backup_method_custom_finished": "Benutzerdefinierte Sicherungsmethode '{method:s}' beendet", + "backup_method_custom_finished": "Benutzerdefinierte Sicherungsmethode '{method}' beendet", "backup_method_copy_finished": "Sicherungskopie beendet", "backup_custom_mount_error": "Bei der benutzerdefinierten Sicherungsmethode ist beim Arbeitsschritt \"Einhängen/Verbinden\" ein Fehler aufgetreten", "backup_custom_backup_error": "Bei der benutzerdefinierten Sicherungsmethode ist beim Arbeitsschritt \"Sicherung\" ein Fehler aufgetreten", "backup_csv_creation_failed": "Die zur Wiederherstellung erforderliche CSV-Datei kann nicht erstellt werden", - "backup_couldnt_bind": "{src:s} konnte nicht an {dest:s} angebunden werden.", - "backup_ask_for_copying_if_needed": "Möchten Sie die Sicherung mit {size:s}MB temporär durchführen? (Dieser Weg wird verwendet, da einige Dateien nicht mit einer effizienteren Methode vorbereitet werden konnten.)", + "backup_couldnt_bind": "{src} konnte nicht an {dest} angebunden werden.", + "backup_ask_for_copying_if_needed": "Möchten Sie die Sicherung mit {size}MB temporär durchführen? (Dieser Weg wird verwendet, da einige Dateien nicht mit einer effizienteren Methode vorbereitet werden konnten.)", "backup_actually_backuping": "Erstellt ein Backup-Archiv aus den gesammelten Dateien...", "ask_new_path": "Neuer Pfad", "ask_new_domain": "Neue Domain", @@ -227,35 +227,35 @@ "admin_password_too_long": "Bitte ein Passwort kürzer als 127 Zeichen wählen", "app_action_broke_system": "Diese Aktion scheint diese wichtigen Dienste unterbrochen zu haben: {services}", "apps_already_up_to_date": "Alle Apps sind bereits aktuell", - "backup_copying_to_organize_the_archive": "Kopieren von {size:s} MB, um das Archiv zu organisieren", + "backup_copying_to_organize_the_archive": "Kopieren von {size} MB, um das Archiv zu organisieren", "global_settings_setting_security_ssh_compatibility": "Kompatibilitäts- vs. Sicherheits-Kompromiss für den SSH-Server. Betrifft die Ciphers (und andere sicherheitsrelevante Aspekte)", "group_deleted": "Gruppe '{group}' gelöscht", "group_deletion_failed": "Konnte Gruppe '{group}' nicht löschen: {error}", "dyndns_provider_unreachable": "DynDNS-Anbieter {provider} kann nicht erreicht werden: Entweder ist Ihr YunoHost nicht korrekt mit dem Internet verbunden oder der Dynette-Server ist ausgefallen.", "group_created": "Gruppe '{group}' angelegt", "group_creation_failed": "Konnte Gruppe '{group}' nicht anlegen", - "group_unknown": "Die Gruppe '{group:s}' ist unbekannt", - "group_updated": "Gruppe '{group:s}' erneuert", - "group_update_failed": "Kann Gruppe '{group:s}' nicht aktualisieren: {error}", + "group_unknown": "Die Gruppe '{group}' ist unbekannt", + "group_updated": "Gruppe '{group}' erneuert", + "group_update_failed": "Kann Gruppe '{group}' nicht aktualisieren: {error}", "log_does_exists": "Es gibt kein Operationsprotokoll mit dem Namen'{log}', verwende 'yunohost log list', um alle verfügbaren Operationsprotokolle anzuzeigen", "log_operation_unit_unclosed_properly": "Die Operationseinheit wurde nicht richtig geschlossen", "global_settings_setting_security_postfix_compatibility": "Kompatibilitäts- vs. Sicherheits-Kompromiss für den Postfix-Server. Betrifft die Ciphers (und andere sicherheitsrelevante Aspekte)", - "global_settings_unknown_type": "Unerwartete Situation, die Einstellung {setting:s} scheint den Typ {unknown_type:s} zu haben, ist aber kein vom System unterstützter Typ.", + "global_settings_unknown_type": "Unerwartete Situation, die Einstellung {setting} scheint den Typ {unknown_type} zu haben, ist aber kein vom System unterstützter Typ.", "dpkg_is_broken": "Du kannst das gerade nicht tun, weil dpkg/APT (der Systempaketmanager) in einem defekten Zustand zu sein scheint.... Du kannst versuchen, dieses Problem zu lösen, indem du dich über SSH verbindest und `sudo apt install --fix-broken` sowie/oder `sudo dpkg --configure -a` ausführst.", - "global_settings_unknown_setting_from_settings_file": "Unbekannter Schlüssel in den Einstellungen: '{setting_key:s}', verwerfen und speichern in /etc/yunohost/settings-unknown.json", + "global_settings_unknown_setting_from_settings_file": "Unbekannter Schlüssel in den Einstellungen: '{setting_key}', verwerfen und speichern in /etc/yunohost/settings-unknown.json", "log_link_to_log": "Vollständiges Log dieser Operation: '{desc}'", "log_help_to_get_log": "Um das Protokoll der Operation '{desc}' anzuzeigen, verwenden Sie den Befehl 'yunohost log show {name}{name}'", "global_settings_setting_security_nginx_compatibility": "Kompatibilitäts- vs. Sicherheits-Kompromiss für den Webserver NGINX. Betrifft die Ciphers (und andere sicherheitsrelevante Aspekte)", "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Erlaubt die Verwendung eines (veralteten) DSA-Hostkeys für die SSH-Daemon-Konfiguration", "log_app_remove": "Entferne die Applikation '{}'", - "global_settings_cant_open_settings": "Einstellungsdatei konnte nicht geöffnet werden, Grund: {reason:s}", - "global_settings_cant_write_settings": "Einstellungsdatei konnte nicht gespeichert werden, Grund: {reason:s}", + "global_settings_cant_open_settings": "Einstellungsdatei konnte nicht geöffnet werden, Grund: {reason}", + "global_settings_cant_write_settings": "Einstellungsdatei konnte nicht gespeichert werden, Grund: {reason}", "log_app_install": "Installiere die Applikation '{}'", - "global_settings_reset_success": "Frühere Einstellungen werden nun auf {path:s} gesichert", + "global_settings_reset_success": "Frühere Einstellungen werden nun auf {path} gesichert", "log_app_upgrade": "Upgrade der Applikation '{}'", "good_practices_about_admin_password": "Sie sind nun dabei, ein neues Administrationspasswort zu definieren. Das Passwort sollte mindestens 8 Zeichen lang sein, obwohl es sinnvoll ist, ein längeres Passwort (z.B. eine Passphrase) und/oder eine Variation von Zeichen (Groß- und Kleinschreibung, Ziffern und Sonderzeichen) zu verwenden.", "log_corrupted_md_file": "Die mit Protokollen verknüpfte YAML-Metadatendatei ist beschädigt: '{md_file}\nFehler: {error}''", - "global_settings_cant_serialize_settings": "Einstellungsdaten konnten nicht serialisiert werden, Grund: {reason:s}", + "global_settings_cant_serialize_settings": "Einstellungsdaten konnten nicht serialisiert werden, Grund: {reason}", "log_help_to_get_failed_log": "Der Vorgang'{desc}' konnte nicht abgeschlossen werden. Bitte teile das vollständige Protokoll dieser Operation mit dem Befehl 'yunohost log share {name}', um Hilfe zu erhalten", "backup_no_uncompress_archive_dir": "Dieses unkomprimierte Archivverzeichnis gibt es nicht", "log_app_change_url": "Ändere die URL der Applikation '{}'", @@ -265,9 +265,9 @@ "backup_cant_mount_uncompress_archive": "Das unkomprimierte Archiv konnte nicht als schreibgeschützt gemountet werden", "backup_csv_addition_failed": "Es konnten keine Dateien zur Sicherung in die CSV-Datei hinzugefügt werden", "global_settings_setting_security_password_admin_strength": "Stärke des Admin-Passworts", - "global_settings_key_doesnt_exists": "Der Schlüssel'{settings_key:s}' existiert nicht in den globalen Einstellungen, du kannst alle verfügbaren Schlüssel sehen, indem du 'yunohost settings list' ausführst", + "global_settings_key_doesnt_exists": "Der Schlüssel'{settings_key}' existiert nicht in den globalen Einstellungen, du kannst alle verfügbaren Schlüssel sehen, indem du 'yunohost settings list' ausführst", "log_app_makedefault": "Mache '{}' zur Standard-Applikation", - "hook_json_return_error": "Konnte die Rückkehr vom Einsprungpunkt {path:s} nicht lesen. Fehler: {msg:s}. Unformatierter Inhalt: {raw_content}", + "hook_json_return_error": "Konnte die Rückkehr vom Einsprungpunkt {path} nicht lesen. Fehler: {msg}. Unformatierter Inhalt: {raw_content}", "app_full_domain_unavailable": "Es tut uns leid, aber diese Applikation erfordert die Installation auf einer eigenen Domain, aber einige andere Applikationen sind bereits auf der Domäne'{domain}' installiert. Eine mögliche Lösung ist das Hinzufügen und Verwenden einer Subdomain, die dieser Applikation zugeordnet ist.", "app_install_failed": "Installation von {app} fehlgeschlagen: {error}", "app_install_script_failed": "Im Installationsscript ist ein Fehler aufgetreten", @@ -336,10 +336,10 @@ "diagnosis_http_ok": "Die Domäne {domain} ist über HTTP von außerhalb des lokalen Netzwerks erreichbar.", "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Einige Hosting-Anbieter werden es Ihnen nicht gestatten, den ausgehenden Port 25 zu öffnen, da diese sich nicht um die Netzneutralität kümmern.
- Einige davon bieten als Alternative an, ein Mailserver-Relay zu verwenden, was jedoch bedeutet, dass das Relay Ihren E-Mail-Verkehr ausspionieren kann.
- Eine die Privatsphäre berücksichtigende Alternative ist die Verwendung eines VPN *mit einer dedizierten öffentlichen IP* um solche Einschränkungen zu umgehen. Schauen Sie unter https://yunohost.org/#/vpn_advantage nach.
- Sie können auch in Betracht ziehen, zu einem netzneutralitätfreundlicheren Anbieter zu wechseln", "diagnosis_http_timeout": "Wartezeit wurde beim Versuch, von außen eine Verbindung zum Server aufzubauen, überschritten. Er scheint nicht erreichbar zu sein.
1. Die häufigste Ursache für dieses Problem ist daß der Port 80 (und 433) nicht richtig zu Ihrem Server weitergeleitet werden.
2. Sie sollten auch sicherstellen, daß der Dienst nginx läuft.
3. In komplexeren Umgebungen: Stellen Sie sicher, daß keine Firewall oder Reverse-Proxy stört.", - "service_reloaded_or_restarted": "Der Dienst '{service:s}' wurde erfolgreich neu geladen oder gestartet", - "service_restarted": "Der Dienst '{service:s}' wurde neu gestartet", + "service_reloaded_or_restarted": "Der Dienst '{service}' wurde erfolgreich neu geladen oder gestartet", + "service_restarted": "Der Dienst '{service}' wurde neu gestartet", "service_regen_conf_is_deprecated": "'yunohost service regen-conf' ist veraltet! Bitte verwenden Sie stattdessen 'yunohost tools regen-conf'.", - "certmanager_warning_subdomain_dns_record": "Die Subdomäne \"{subdomain:s}\" löst nicht zur gleichen IP Adresse auf wie \"{domain:s}\". Einige Funktionen sind nicht verfügbar bis du dies behebst und die Zertifikate neu erzeugst.", + "certmanager_warning_subdomain_dns_record": "Die Subdomäne \"{subdomain}\" löst nicht zur gleichen IP Adresse auf wie \"{domain}\". Einige Funktionen sind nicht verfügbar bis du dies behebst und die Zertifikate neu erzeugst.", "diagnosis_ports_ok": "Port {port} ist von außen erreichbar.", "diagnosis_ram_verylow": "Das System hat nur {available} ({available_percent}%) RAM zur Verfügung! (von insgesamt {total})", "diagnosis_mail_outgoing_port_25_blocked_details": "Sie sollten zuerst versuchen den ausgehenden Port 25 auf Ihrer Router-Konfigurationsoberfläche oder Ihrer Hosting-Anbieter-Konfigurationsoberfläche zu öffnen. (Bei einigen Hosting-Anbieter kann es sein, daß Sie verlangen, daß man dafür ein Support-Ticket sendet).", @@ -353,7 +353,7 @@ "diagnosis_mail_ehlo_unreachable": "Der SMTP-Server ist von außen nicht erreichbar per IPv{ipversion}. Er wird nicht in der Lage sein E-Mails zu empfangen.", "diagnosis_diskusage_low": "Der Speicher {mountpoint} (auf Gerät {device}) hat nur noch {free} ({free_percent}%) freien Speicherplatz (von insgesamt {total}). Seien Sie vorsichtig.", "diagnosis_ram_low": "Das System hat nur {available} ({available_percent}%) RAM zur Verfügung! (von insgesamt {total}). Seien Sie vorsichtig.", - "service_reload_or_restart_failed": "Der Dienst '{service:s}' konnte nicht erneut geladen oder gestartet werden.\n\nKürzlich erstellte Logs des Dienstes: {logs:s}", + "service_reload_or_restart_failed": "Der Dienst '{service}' konnte nicht erneut geladen oder gestartet werden.\n\nKürzlich erstellte Logs des Dienstes: {logs}", "diagnosis_domain_expiration_not_found_details": "Die WHOIS-Informationen für die Domäne {domain} scheint keine Informationen über das Ablaufdatum zu enthalten?", "diagnosis_domain_expiration_warning": "Einige Domänen werden bald ablaufen!", "diagnosis_diskusage_ok": "Der Speicher {mountpoint} (auf Gerät {device}) hat immer noch {free} ({free_percent}%) freien Speicherplatz übrig(von insgesamt {total})!", @@ -362,9 +362,9 @@ "diagnosis_mail_ehlo_unreachable_details": "Konnte keine Verbindung zu Ihrem Server auf dem Port 25 herzustellen per IPv{ipversion}. Er scheint nicht erreichbar zu sein.
1. Das häufigste Problem ist, dass der Port 25 nicht richtig zu Ihrem Server weitergeleitet ist.
2. Sie sollten auch sicherstellen, dass der Postfix-Dienst läuft.
3. In komplexeren Umgebungen: Stellen Sie sicher, daß keine Firewall oder Reverse-Proxy stört.", "diagnosis_mail_ehlo_wrong": "Ein anderer SMTP-Server antwortet auf IPv{ipversion}. Ihr Server wird wahrscheinlich nicht in der Lage sein, E-Mails zu empfangen.", "migration_description_0018_xtable_to_nftable": "Alte Netzwerkverkehrsregeln zum neuen nftable-System migrieren", - "service_reload_failed": "Der Dienst '{service:s}' konnte nicht erneut geladen werden.\n\nKürzlich erstellte Logs des Dienstes: {logs:s}", - "service_reloaded": "Der Dienst '{service:s}' wurde erneut geladen", - "service_restart_failed": "Der Dienst '{service:s}' konnte nicht erneut gestartet werden.\n\nKürzlich erstellte Logs des Dienstes: {logs:s}", + "service_reload_failed": "Der Dienst '{service}' konnte nicht erneut geladen werden.\n\nKürzlich erstellte Logs des Dienstes: {logs}", + "service_reloaded": "Der Dienst '{service}' wurde erneut geladen", + "service_restart_failed": "Der Dienst '{service}' konnte nicht erneut gestartet werden.\n\nKürzlich erstellte Logs des Dienstes: {logs}", "app_manifest_install_ask_password": "Wählen Sie ein Verwaltungspasswort für diese Applikation", "app_manifest_install_ask_domain": "Wählen Sie die Domäne, auf welcher die Applikation installiert werden soll", "log_letsencrypt_cert_renew": "Erneuern des Let's Encrypt-Zeritifikates von '{}'", @@ -428,8 +428,8 @@ "group_cannot_edit_primary_group": "Die Gruppe '{group}' kann nicht manuell bearbeitet werden. Es ist die primäre Gruppe, welche dazu gedacht ist, nur einen spezifischen Benutzer zu enthalten.", "diagnosis_processes_killed_by_oom_reaper": "Das System hat einige Prozesse beendet, weil ihm der Arbeitsspeicher ausgegangen ist. Das passiert normalerweise, wenn das System ingesamt nicht genügend Arbeitsspeicher zur Verfügung hat oder wenn ein einzelner Prozess zu viel Speicher verbraucht. Zusammenfassung der beendeten Prozesse: \n{kills_summary}", "diagnosis_description_ports": "Offene Ports", - "additional_urls_already_added": "Zusätzliche URL '{url:s}' bereits hinzugefügt in der zusätzlichen URL für Berechtigung '{permission:s}'", - "additional_urls_already_removed": "Zusätzliche URL '{url:s}' bereits entfernt in der zusätzlichen URL für Berechtigung '{permission:s}'", + "additional_urls_already_added": "Zusätzliche URL '{url}' bereits hinzugefügt in der zusätzlichen URL für Berechtigung '{permission}'", + "additional_urls_already_removed": "Zusätzliche URL '{url}' bereits entfernt in der zusätzlichen URL für Berechtigung '{permission}'", "app_label_deprecated": "Dieser Befehl ist veraltet! Bitte nutzen Sie den neuen Befehl 'yunohost user permission update' um das Applabel zu verwalten.", "diagnosis_http_hairpinning_issue_details": "Das ist wahrscheinlich aufgrund Ihrer ISP Box / Router. Als Konsequenz können Personen von ausserhalb Ihres Netzwerkes aber nicht von innerhalb Ihres lokalen Netzwerkes (wie wahrscheinlich Sie selber?) wie gewohnt auf Ihren Server zugreifen, wenn Sie ihre Domäne oder Ihre öffentliche IP verwenden. Sie können die Situation wahrscheinlich verbessern, indem Sie ein einen Blick in https://yunohost.org/dns_local_network werfen", "diagnosis_http_nginx_conf_not_up_to_date": "Jemand hat anscheinend die Konfiguration von Nginx manuell geändert. Diese Änderung verhindert, dass Yunohost eine Diagnose durchführen kann, wenn er via HTTP erreichbar ist.", @@ -451,7 +451,7 @@ "global_settings_setting_smtp_relay_port": "SMTP Relay Port", "global_settings_setting_smtp_allow_ipv6": "Erlaube die Nutzung von IPv6 um Mails zu empfangen und zu versenden", "global_settings_setting_pop3_enabled": "Aktiviere das POP3 Protokoll für den Mailserver", - "domain_cannot_remove_main_add_new_one": "Sie können '{domain:s}' nicht entfernen, weil es die Hauptdomäne und gleichzeitig Ihre einzige Domäne ist. Zuerst müssen Sie eine andere Domäne hinzufügen, indem Sie \"yunohost domain add another-domain.com>\" eingeben. Bestimmen Sie diese dann als Ihre Hauptdomain indem Sie 'yunohost domain main-domain -n ' eingeben. Nun können Sie die Domäne \"{domain:s}\" enfernen, indem Sie 'yunohost domain remove {domain:s}' eingeben.'", + "domain_cannot_remove_main_add_new_one": "Sie können '{domain}' nicht entfernen, weil es die Hauptdomäne und gleichzeitig Ihre einzige Domäne ist. Zuerst müssen Sie eine andere Domäne hinzufügen, indem Sie \"yunohost domain add another-domain.com>\" eingeben. Bestimmen Sie diese dann als Ihre Hauptdomain indem Sie 'yunohost domain main-domain -n ' eingeben. Nun können Sie die Domäne \"{domain}\" enfernen, indem Sie 'yunohost domain remove {domain}' eingeben.'", "diagnosis_rootfstotalspace_critical": "Das Root-Filesystem hat noch freien Speicher von {space}. Das ist besorngiserregend! Der Speicher wird schnell aufgebraucht sein. 16 GB für das Root-Filesystem werden empfohlen.", "diagnosis_rootfstotalspace_warning": "Das Root-Filesystem hat noch freien Speicher von {space}. Möglich, dass das in Ordnung ist. Vielleicht ist er aber auch schneller aufgebraucht. 16 GB für das Root-Filesystem werden empfohlen.", "global_settings_setting_smtp_relay_host": "Zu verwendender SMTP-Relay-Host um E-Mails zu versenden. Er wird anstelle dieser YunoHost-Instanz verwendet. Nützlich, wenn Sie in einer der folgenden Situationen sind: Ihr ISP- oder VPS-Provider hat Ihren Port 25 geblockt, eine Ihrer residentiellen IPs ist auf DUHL gelistet, Sie können keinen Reverse-DNS konfigurieren oder dieser Server ist nicht direkt mit dem Internet verbunden und Sie möchten einen anderen verwenden, um E-Mails zu versenden.", @@ -463,9 +463,9 @@ "log_app_config_apply": "Wende die Konfiguration auf die Applikation '{}' an", "log_app_config_show_panel": "Zeige das Konfigurations-Panel der Applikation '{}'", "log_app_action_run": "Führe Aktion der Applikation '{}' aus", - "invalid_regex": "Ungültige Regex:'{regex:s}'", + "invalid_regex": "Ungültige Regex:'{regex}'", "migration_description_0016_php70_to_php73_pools": "Migrieren der php7.0-fpm-Konfigurationsdateien zu php7.3", - "mailbox_disabled": "E-Mail für Benutzer {user:s} deaktiviert", + "mailbox_disabled": "E-Mail für Benutzer {user} deaktiviert", "log_tools_reboot": "Server neustarten", "log_tools_shutdown": "Server ausschalten", "log_tools_upgrade": "Systempakete aktualisieren", @@ -548,14 +548,14 @@ "regenconf_file_backed_up": "Die Konfigurationsdatei '{conf}' wurde unter '{backup}' gespeichert", "permission_require_account": "Berechtigung {permission} ist nur für Benutzer mit einem Konto sinnvoll und kann daher nicht für Besucher aktiviert werden.", "permission_protected": "Die Berechtigung ist geschützt. Sie können die Besuchergruppe nicht zu dieser Berechtigung hinzufügen oder daraus entfernen.", - "permission_updated": "Berechtigung '{permission:s}' aktualisiert", + "permission_updated": "Berechtigung '{permission}' aktualisiert", "permission_update_failed": "Die Berechtigung '{permission}' kann nicht aktualisiert werden : {error}", "permission_not_found": "Berechtigung nicht gefunden", "permission_deletion_failed": "Entfernung der Berechtigung nicht möglich '{permission}': {error}", "permission_deleted": "Berechtigung gelöscht", "permission_currently_allowed_for_all_users": "Diese Berechtigung wird derzeit allen Benutzern zusätzlich zu anderen Gruppen erteilt. Möglicherweise möchten Sie entweder die Berechtigung 'all_users' entfernen oder die anderen Gruppen entfernen, für die sie derzeit zulässig sind.", "permission_creation_failed": "Berechtigungserstellung nicht möglich '{permission}' : {error}", - "permission_created": "Berechtigung '{permission:s}' erstellt", + "permission_created": "Berechtigung '{permission}' erstellt", "permission_cannot_remove_main": "Entfernung einer Hauptberechtigung nicht genehmigt", "regenconf_file_updated": "Konfigurationsdatei '{conf}' aktualisiert", "regenconf_file_removed": "Konfigurationsdatei '{conf}' entfernt", @@ -568,7 +568,7 @@ "regenconf_failed": "Konnte die Konfiguration für die Kategorie(n) {categories} nicht neu erstellen", "regenconf_dry_pending_applying": "Überprüfe die anstehende Konfiguration, welche für die Kategorie {category}' aktualisiert worden wäre…", "regenconf_would_be_updated": "Die Konfiguration wäre für die Kategorie '{category}' aktualisiert worden", - "restore_system_part_failed": "Die Systemteile '{part:s}' konnten nicht wiederhergestellt werden", + "restore_system_part_failed": "Die Systemteile '{part}' konnten nicht wiederhergestellt werden", "restore_removing_tmp_dir_failed": "Ein altes, temporäres Directory konnte nicht entfernt werden", "restore_not_enough_disk_space": "Nicht genug Speicher (Speicher: {free_space:d} B, benötigter Speicher: {needed_space:d} B, Sicherheitspuffer: {margin:d} B)", "restore_may_be_not_enough_disk_space": "Ihr System scheint nicht genug Speicherplatz zu haben (frei: {free_space:d} B, benötigter Platz: {needed_space:d} B, Sicherheitspuffer: {margin:d} B)", @@ -583,7 +583,7 @@ "diagnosis_sshd_config_insecure": "Die SSH-Konfiguration wurde scheinbar manuell geändert und ist unsicher, weil sie keine 'AllowGroups'- oder 'AllowUsers' -Direktiven für die Beschränkung des Zugriffs durch autorisierte Benutzer enthält.", "backup_create_size_estimation": "Das Archiv wird etwa {size} an Daten enthalten.", "app_restore_script_failed": "Im Wiederherstellungsskript der Applikation ist ein Fehler aufgetreten", - "app_restore_failed": "Konnte {app:s} nicht wiederherstellen: {error:s}", + "app_restore_failed": "Konnte {app} nicht wiederherstellen: {error}", "migration_ldap_rollback_success": "System-Rollback erfolgreich.", "migration_ldap_migration_failed_trying_to_rollback": "Migrieren war nicht möglich... Versuch, ein Rollback des Systems durchzuführen.", "migration_ldap_backup_before_migration": "Vor der eigentlichen Migration ein Backup der LDAP-Datenbank und der Applikations-Einstellungen erstellen.", @@ -593,7 +593,7 @@ "diagnosis_sshd_config_inconsistent_details": "Bitte führen Sie yunohost settings set security.ssh.port -v YOUR_SSH_PORT aus, um den SSH-Port festzulegen, und prüfen Sie yunohost tools regen-conf ssh --dry-run --with-diff und yunohost tools regen-conf ssh --force um Ihre conf auf die YunoHost-Empfehlung zurückzusetzen.", "regex_incompatible_with_tile": "/!\\ Packagers! Für Berechtigung '{permission}' ist show_tile auf 'true' gesetzt und deshalb können Sie keine regex-URL als Hauptdomäne setzen", "permission_cant_add_to_all_users": "Die Berechtigung {permission} konnte nicht allen Benutzern gegeben werden.", - "migration_ldap_can_not_backup_before_migration": "Das System-Backup konnte nicht abgeschlossen werden, bevor die Migration fehlschlug. Fehler: {error:s}", + "migration_ldap_can_not_backup_before_migration": "Das System-Backup konnte nicht abgeschlossen werden, bevor die Migration fehlschlug. Fehler: {error}", "service_description_fail2ban": "Schützt gegen Brute-Force-Angriffe und andere Angriffe aus dem Internet", "service_description_dovecot": "Ermöglicht es E-Mail-Clients auf Konten zuzugreifen (IMAP und POP3)", "service_description_dnsmasq": "Verarbeitet die Auflösung des Domainnamens (DNS)", @@ -610,9 +610,9 @@ "service_description_yunohost-api": "Verwaltet die Interaktionen zwischen der Weboberfläche von YunoHost und dem System", "service_description_ssh": "Ermöglicht die Verbindung zu Ihrem Server über ein Terminal (SSH-Protokoll)", "service_description_php7.3-fpm": "Führt in PHP geschriebene Apps mit NGINX aus", - "server_reboot_confirm": "Der Server wird sofort heruntergefahren, sind Sie sicher? [{answers:s}]", + "server_reboot_confirm": "Der Server wird sofort heruntergefahren, sind Sie sicher? [{answers}]", "server_reboot": "Der Server wird neu gestartet", - "server_shutdown_confirm": "Der Server wird sofort heruntergefahren, sind Sie sicher? [{answers:s}]", + "server_shutdown_confirm": "Der Server wird sofort heruntergefahren, sind Sie sicher? [{answers}]", "server_shutdown": "Der Server wird heruntergefahren", "root_password_replaced_by_admin_password": "Ihr Root Passwort wurde durch Ihr Admin Passwort ersetzt.", "show_tile_cant_be_enabled_for_regex": "Momentan können Sie 'show_tile' nicht aktivieren, weil die URL für die Berechtigung '{permission}' ein regulärer Ausdruck ist", diff --git a/locales/en.json b/locales/en.json index f500d6ca1..d9d3553f0 100644 --- a/locales/en.json +++ b/locales/en.json @@ -1,8 +1,8 @@ { "aborting": "Aborting.", - "action_invalid": "Invalid action '{action:s}'", - "additional_urls_already_added": "Additionnal URL '{url:s}' already added in the additional URL for permission '{permission:s}'", - "additional_urls_already_removed": "Additionnal URL '{url:s}' already removed in the additional URL for permission '{permission:s}'", + "action_invalid": "Invalid action '{action}'", + "additional_urls_already_added": "Additionnal URL '{url}' already added in the additional URL for permission '{permission}'", + "additional_urls_already_removed": "Additionnal URL '{url}' already removed in the additional URL for permission '{permission}'", "admin_password": "Administration password", "admin_password_change_failed": "Unable to change password", "admin_password_changed": "The administration password was changed", @@ -10,17 +10,17 @@ "already_up_to_date": "Nothing to do. Everything is already up-to-date.", "app_action_cannot_be_ran_because_required_services_down": "These required services should be running to run this action: {services}. Try restarting them to continue (and possibly investigate why they are down).", "app_action_broke_system": "This action seems to have broken these important services: {services}", - "app_already_installed": "{app:s} is already installed", + "app_already_installed": "{app} is already installed", "app_already_installed_cant_change_url": "This app is already installed. The URL cannot be changed just by this function. Check in `app changeurl` if it's available.", - "app_already_up_to_date": "{app:s} is already up-to-date", - "app_argument_choice_invalid": "Use one of these choices '{choices:s}' for the argument '{name:s}'", - "app_argument_invalid": "Pick a valid value for the argument '{name:s}': {error:s}", + "app_already_up_to_date": "{app} is already up-to-date", + "app_argument_choice_invalid": "Use one of these choices '{choices}' for the argument '{name}'", + "app_argument_invalid": "Pick a valid value for the argument '{name}': {error}", "app_argument_password_no_default": "Error while parsing password argument '{name}': password argument can't have a default value for security reason", - "app_argument_required": "Argument '{name:s}' is required", - "app_change_url_failed_nginx_reload": "Could not reload NGINX. Here is the output of 'nginx -t':\n{nginx_errors:s}", - "app_change_url_identical_domains": "The old and new domain/url_path are identical ('{domain:s}{path:s}'), nothing to do.", - "app_change_url_no_script": "The app '{app_name:s}' doesn't support URL modification yet. Maybe you should upgrade it.", - "app_change_url_success": "{app:s} URL is now {domain:s}{path:s}", + "app_argument_required": "Argument '{name}' is required", + "app_change_url_failed_nginx_reload": "Could not reload NGINX. Here is the output of 'nginx -t':\n{nginx_errors}", + "app_change_url_identical_domains": "The old and new domain/url_path are identical ('{domain}{path}'), nothing to do.", + "app_change_url_no_script": "The app '{app_name}' doesn't support URL modification yet. Maybe you should upgrade it.", + "app_change_url_success": "{app} URL is now {domain}{path}", "app_extraction_failed": "Could not extract the installation files", "app_full_domain_unavailable": "Sorry, this app must be installed on a domain of its own, but other apps are already installed on the domain '{domain}'. You could use a subdomain dedicated to this app instead.", "app_id_invalid": "Invalid app ID", @@ -29,7 +29,7 @@ "app_install_script_failed": "An error occurred inside the app installation script", "app_make_default_location_already_used": "Unable to make '{app}' the default app on the domain, '{domain}' is already in use by '{other_app}'", "app_label_deprecated": "This command is deprecated! Please use the new command 'yunohost user permission update' to manage the app label.", - "app_location_unavailable": "This URL is either unavailable, or conflicts with the already installed app(s):\n{apps:s}", + "app_location_unavailable": "This URL is either unavailable, or conflicts with the already installed app(s):\n{apps}", "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", @@ -37,14 +37,14 @@ "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 {app:s} in the list of installed apps: {all_apps}", - "app_not_properly_removed": "{app:s} has not been properly removed", - "app_removed": "{app:s} removed", + "app_not_correctly_installed": "{app} seems to be incorrectly installed", + "app_not_installed": "Could not find {app} in the list of installed apps: {all_apps}", + "app_not_properly_removed": "{app} has not been properly removed", + "app_removed": "{app} removed", "app_requirements_checking": "Checking required packages for {app}...", "app_requirements_unmeet": "Requirements are not met for {app}, the package {pkgname} ({version}) must be {spec}", "app_remove_after_failed_install": "Removing the app following the installation failure...", - "app_restore_failed": "Could not restore {app:s}: {error:s}", + "app_restore_failed": "Could not restore {app}: {error}", "app_restore_script_failed": "An error occured inside the app restore script", "app_sources_fetch_failed": "Could not fetch sources files, is the URL correct?", "app_start_install": "Installing {app}...", @@ -55,10 +55,10 @@ "app_unsupported_remote_type": "Unsupported remote type used for the app", "app_upgrade_several_apps": "The following apps will be upgraded: {apps}", "app_upgrade_app_name": "Now upgrading {app}...", - "app_upgrade_failed": "Could not upgrade {app:s}: {error}", + "app_upgrade_failed": "Could not upgrade {app}: {error}", "app_upgrade_script_failed": "An error occurred inside the app upgrade script", "app_upgrade_some_app_failed": "Some apps could not be upgraded", - "app_upgraded": "{app:s} upgraded", + "app_upgraded": "{app} upgraded", "app_packaging_format_not_supported": "This app cannot be installed because its packaging format is not supported by your YunoHost version. You should probably consider upgrading your system.", "apps_already_up_to_date": "All apps are already up-to-date", "apps_catalog_init_success": "App catalog system initialized!", @@ -76,24 +76,24 @@ "ask_password": "Password", "backup_abstract_method": "This backup method has yet to be implemented", "backup_actually_backuping": "Creating a backup archive from the collected files...", - "backup_app_failed": "Could not back up {app:s}", + "backup_app_failed": "Could not back up {app}", "backup_applying_method_copy": "Copying all files to backup...", - "backup_applying_method_custom": "Calling the custom backup method '{method:s}'...", + "backup_applying_method_custom": "Calling the custom backup method '{method}'...", "backup_applying_method_tar": "Creating the backup TAR archive...", - "backup_archive_app_not_found": "Could not find {app:s} in the backup archive", - "backup_archive_broken_link": "Could not access the backup archive (broken link to {path:s})", + "backup_archive_app_not_found": "Could not find {app} in the backup archive", + "backup_archive_broken_link": "Could not access the backup archive (broken link to {path})", "backup_archive_name_exists": "A backup archive with this name already exists.", - "backup_archive_name_unknown": "Unknown local backup archive named '{name:s}'", + "backup_archive_name_unknown": "Unknown local backup archive named '{name}'", "backup_archive_open_failed": "Could not open the backup archive", "backup_archive_cant_retrieve_info_json": "Could not load infos for archive '{archive}'... The info.json cannot be retrieved (or is not a valid json).", "backup_archive_corrupted": "It looks like the backup archive '{archive}' is corrupted : {error}", - "backup_archive_system_part_not_available": "System part '{part:s}' unavailable in this backup", - "backup_archive_writing_error": "Could not add the files '{source:s}' (named in the archive '{dest:s}') to be backed up into the compressed archive '{archive:s}'", - "backup_ask_for_copying_if_needed": "Do you want to perform the backup using {size:s}MB temporarily? (This way is used since some files could not be prepared using a more efficient method.)", + "backup_archive_system_part_not_available": "System part '{part}' unavailable in this backup", + "backup_archive_writing_error": "Could not add the files '{source}' (named in the archive '{dest}') to be backed up into the compressed archive '{archive}'", + "backup_ask_for_copying_if_needed": "Do you want to perform the backup using {size}MB temporarily? (This way is used since some files could not be prepared using a more efficient method.)", "backup_cant_mount_uncompress_archive": "Could not mount the uncompressed archive as write protected", "backup_cleaning_failed": "Could not clean up the temporary backup folder", - "backup_copying_to_organize_the_archive": "Copying {size:s}MB to organize the archive", - "backup_couldnt_bind": "Could not bind {src:s} to {dest:s}.", + "backup_copying_to_organize_the_archive": "Copying {size}MB to organize the archive", + "backup_couldnt_bind": "Could not bind {src} to {dest}.", "backup_created": "Backup created", "backup_create_size_estimation": "The archive will contain about {size} of data.", "backup_creation_failed": "Could not create the backup archive", @@ -101,11 +101,11 @@ "backup_csv_creation_failed": "Could not create the CSV file needed for restoration", "backup_custom_backup_error": "Custom backup method could not get past the 'backup' step", "backup_custom_mount_error": "Custom backup method could not get past the 'mount' step", - "backup_delete_error": "Could not delete '{path:s}'", + "backup_delete_error": "Could not delete '{path}'", "backup_deleted": "Backup deleted", - "backup_hook_unknown": "The backup hook '{hook:s}' is unknown", + "backup_hook_unknown": "The backup hook '{hook}' is unknown", "backup_method_copy_finished": "Backup copy finalized", - "backup_method_custom_finished": "Custom backup method '{method:s}' finished", + "backup_method_custom_finished": "Custom backup method '{method}' finished", "backup_method_tar_finished": "TAR backup archive created", "backup_mount_archive_for_restore": "Preparing archive for restoration...", "backup_no_uncompress_archive_dir": "There is no such uncompressed archive directory", @@ -113,36 +113,36 @@ "backup_output_directory_forbidden": "Pick a different output directory. Backups cannot be created in /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var or /home/yunohost.backup/archives sub-folders", "backup_output_directory_not_empty": "You should pick an empty output directory", "backup_output_directory_required": "You must provide an output directory for the backup", - "backup_output_symlink_dir_broken": "Your archive directory '{path:s}' is a broken symlink. Maybe you forgot to re/mount or plug in the storage medium it points to.", - "backup_permission": "Backup permission for {app:s}", + "backup_output_symlink_dir_broken": "Your archive directory '{path}' is a broken symlink. Maybe you forgot to re/mount or plug in the storage medium it points to.", + "backup_permission": "Backup permission for {app}", "backup_running_hooks": "Running backup hooks...", - "backup_system_part_failed": "Could not backup the '{part:s}' system part", + "backup_system_part_failed": "Could not backup the '{part}' system part", "backup_unable_to_organize_files": "Could not use the quick method to organize files in the archive", - "backup_with_no_backup_script_for_app": "The app '{app:s}' has no backup script. Ignoring.", - "backup_with_no_restore_script_for_app": "{app:s} has no restoration script, you will not be able to automatically restore the backup of this app.", + "backup_with_no_backup_script_for_app": "The app '{app}' has no backup script. Ignoring.", + "backup_with_no_restore_script_for_app": "{app} has no restoration script, you will not be able to automatically restore the backup of this app.", "certmanager_acme_not_configured_for_domain": "The ACME challenge cannot be ran for {domain} right now because its nginx conf lacks the corresponding code snippet... Please make sure that your nginx configuration is up to date using `yunohost tools regen-conf nginx --dry-run --with-diff`.", - "certmanager_attempt_to_renew_nonLE_cert": "The certificate for the domain '{domain:s}' is not issued by Let's Encrypt. Cannot renew it automatically!", - "certmanager_attempt_to_renew_valid_cert": "The certificate for the domain '{domain:s}' is not about to expire! (You may use --force if you know what you're doing)", - "certmanager_attempt_to_replace_valid_cert": "You are attempting to overwrite a good and valid certificate for domain {domain:s}! (Use --force to bypass)", - "certmanager_cannot_read_cert": "Something wrong happened when trying to open current certificate for domain {domain:s} (file: {file:s}), reason: {reason:s}", - "certmanager_cert_install_success": "Let's Encrypt certificate now installed for the domain '{domain:s}'", - "certmanager_cert_install_success_selfsigned": "Self-signed certificate now installed for the domain '{domain:s}'", - "certmanager_cert_renew_success": "Let's Encrypt certificate renewed for the domain '{domain:s}'", + "certmanager_attempt_to_renew_nonLE_cert": "The certificate for the domain '{domain}' is not issued by Let's Encrypt. Cannot renew it automatically!", + "certmanager_attempt_to_renew_valid_cert": "The certificate for the domain '{domain}' is not about to expire! (You may use --force if you know what you're doing)", + "certmanager_attempt_to_replace_valid_cert": "You are attempting to overwrite a good and valid certificate for domain {domain}! (Use --force to bypass)", + "certmanager_cannot_read_cert": "Something wrong happened when trying to open current certificate for domain {domain} (file: {file}), reason: {reason}", + "certmanager_cert_install_success": "Let's Encrypt certificate now installed for the domain '{domain}'", + "certmanager_cert_install_success_selfsigned": "Self-signed certificate now installed for the domain '{domain}'", + "certmanager_cert_renew_success": "Let's Encrypt certificate renewed for the domain '{domain}'", "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_certificate_fetching_or_enabling_failed": "Trying to use the new certificate for {domain} did not work...", "certmanager_domain_not_diagnosed_yet": "There is no diagnosis result for domain {domain} 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 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_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_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})", - "confirm_app_install_warning": "Warning: This app may work, but is not well-integrated in YunoHost. Some features such as single sign-on and backup/restore might not be available. Install anyway? [{answers:s}] ", - "confirm_app_install_danger": "DANGER! This app is known to be still experimental (if not explicitly not working)! You should probably NOT install it unless you know what you are doing. NO SUPPORT will be provided if this app doesn't work or breaks your system… If you are willing to take that risk anyway, type '{answers:s}'", - "confirm_app_install_thirdparty": "DANGER! This app is not part of YunoHost's app catalog. Installing third-party apps may compromise the integrity and security of your system. You should probably NOT install it unless you know what you are doing. NO SUPPORT will be provided if this app doesn't work or breaks your system… If you are willing to take that risk anyway, type '{answers:s}'", - "custom_app_url_required": "You must provide a URL to upgrade your custom app {app:s}", + "certmanager_domain_cert_not_selfsigned": "The certificate for domain {domain} 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 records for domain '{domain}' 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} 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_warning_subdomain_dns_record": "Subdomain '{subdomain}' does not resolve to the same IP address as '{domain}'. 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} recently. Please try again later. See https://letsencrypt.org/docs/rate-limits/ for more details", + "certmanager_no_cert_file": "Could not read the certificate file for the domain {domain} (file: {file})", + "certmanager_self_ca_conf_file_not_found": "Could not find configuration file for self-signing authority (file: {file})", + "certmanager_unable_to_parse_self_CA_name": "Could not parse name of self-signing authority (file: {file})", + "confirm_app_install_warning": "Warning: This app may work, but is not well-integrated in YunoHost. Some features such as single sign-on and backup/restore might not be available. Install anyway? [{answers}] ", + "confirm_app_install_danger": "DANGER! This app is known to be still experimental (if not explicitly not working)! You should probably NOT install it unless you know what you are doing. NO SUPPORT will be provided if this app doesn't work or breaks your system… If you are willing to take that risk anyway, type '{answers}'", + "confirm_app_install_thirdparty": "DANGER! This app is not part of YunoHost's app catalog. Installing third-party apps may compromise the integrity and security of your system. You should probably NOT install it unless you know what you are doing. NO SUPPORT will be provided if this app doesn't work or breaks your system… If you are willing to take that risk anyway, type '{answers}'", + "custom_app_url_required": "You must provide a URL to upgrade your custom app {app}", "diagnosis_basesystem_hardware": "Server hardware architecture is {virt} {arch}", "diagnosis_basesystem_hardware_model": "Server model is {model}", "diagnosis_basesystem_host": "Server is running Debian {debian_version}", @@ -273,9 +273,9 @@ "diagnosis_sshd_config_insecure": "The SSH configuration appears to have been manually modified, and is insecure because it contains no 'AllowGroups' or 'AllowUsers' directive to limit access to authorized users.", "diagnosis_sshd_config_inconsistent": "It looks like the SSH port was manually modified in /etc/ssh/sshd_config. Since YunoHost 4.2, a new global setting 'security.ssh.port' is available to avoid manually editing the configuration.", "diagnosis_sshd_config_inconsistent_details": "Please run yunohost settings set security.ssh.port -v YOUR_SSH_PORT to define the SSH port, and check yunohost tools regen-conf ssh --dry-run --with-diff and yunohost tools regen-conf ssh --force to reset your conf to the YunoHost recommendation.", - "domain_cannot_remove_main": "You cannot remove '{domain:s}' since it's the main domain, you first need to set another domain as the main domain using 'yunohost domain main-domain -n '; here is the list of candidate domains: {other_domains:s}", + "domain_cannot_remove_main": "You cannot remove '{domain}' since it's the main domain, you first need to set another domain as the main domain using 'yunohost domain main-domain -n '; here is the list of candidate domains: {other_domains}", "domain_cannot_add_xmpp_upload": "You cannot add domains starting with 'xmpp-upload.'. This kind of name is reserved for the XMPP upload feature integrated in YunoHost.", - "domain_cannot_remove_main_add_new_one": "You cannot remove '{domain:s}' since it's the main domain and your only domain, you need to first add another domain using 'yunohost domain add ', then set is as the main domain using 'yunohost domain main-domain -n ' and then you can remove the domain '{domain:s}' using 'yunohost domain remove {domain:s}'.'", + "domain_cannot_remove_main_add_new_one": "You cannot remove '{domain}' since it's the main domain and your only domain, you need to first add another domain using 'yunohost domain add ', then set is as the main domain using 'yunohost domain main-domain -n ' and then you can remove the domain '{domain}' using 'yunohost domain remove {domain}'.'", "domain_cert_gen_failed": "Could not generate certificate", "domain_created": "Domain created", "domain_creation_failed": "Unable to create domain {domain}: {error}", @@ -295,8 +295,8 @@ "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 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}.", + "dyndns_could_not_check_provide": "Could not check if {provider} can provide {domain}.", + "dyndns_could_not_check_available": "Could not check if {domain} is available on {provider}.", "dyndns_ip_update_failed": "Could not update IP address to DynDNS", "dyndns_ip_updated": "Updated your IP on DynDNS", "dyndns_key_generating": "Generating DNS key... It may take a while.", @@ -304,23 +304,23 @@ "dyndns_no_domain_registered": "No domain registered with DynDNS", "dyndns_provider_unreachable": "Unable to reach DynDNS provider {provider}: either your YunoHost is not correctly connected to the internet or the dynette server is down.", "dyndns_registered": "DynDNS domain registered", - "dyndns_registration_failed": "Could not register DynDNS domain: {error:s}", - "dyndns_domain_not_provided": "DynDNS provider {provider:s} cannot provide domain {domain:s}.", - "dyndns_unavailable": "The domain '{domain:s}' is unavailable.", + "dyndns_registration_failed": "Could not register DynDNS domain: {error}", + "dyndns_domain_not_provided": "DynDNS provider {provider} cannot provide domain {domain}.", + "dyndns_unavailable": "The domain '{domain}' is unavailable.", "extracting": "Extracting...", "experimental_feature": "Warning: This feature is experimental and not considered stable, you should not use it unless you know what you are doing.", - "field_invalid": "Invalid field '{:s}'", - "file_does_not_exist": "The file {path:s} does not exist.", + "field_invalid": "Invalid field '{}'", + "file_does_not_exist": "The file {path} does not exist.", "firewall_reload_failed": "Could not reload the firewall", "firewall_reloaded": "Firewall reloaded", "firewall_rules_cmd_failed": "Some firewall rule commands have failed. More info in log.", - "global_settings_bad_choice_for_enum": "Bad choice for setting {setting:s}, received '{choice:s}', but available choices are: {available_choices:s}", - "global_settings_bad_type_for_setting": "Bad type for setting {setting:s}, received {received_type:s}, expected {expected_type:s}", - "global_settings_cant_open_settings": "Could not open settings file, reason: {reason:s}", - "global_settings_cant_serialize_settings": "Could not serialize settings data, reason: {reason:s}", - "global_settings_cant_write_settings": "Could not save settings file, reason: {reason:s}", - "global_settings_key_doesnt_exists": "The key '{settings_key:s}' does not exist in the global settings, you can see all the available keys by running 'yunohost settings list'", - "global_settings_reset_success": "Previous settings now backed up to {path:s}", + "global_settings_bad_choice_for_enum": "Bad choice for setting {setting}, received '{choice}', but available choices are: {available_choices}", + "global_settings_bad_type_for_setting": "Bad type for setting {setting}, received {received_type}, expected {expected_type}", + "global_settings_cant_open_settings": "Could not open settings file, reason: {reason}", + "global_settings_cant_serialize_settings": "Could not serialize settings data, reason: {reason}", + "global_settings_cant_write_settings": "Could not save settings file, reason: {reason}", + "global_settings_key_doesnt_exists": "The key '{settings_key}' does not exist in the global settings, you can see all the available keys by running 'yunohost settings list'", + "global_settings_reset_success": "Previous settings now backed up to {path}", "global_settings_setting_pop3_enabled": "Enable the POP3 protocol for the mail server", "global_settings_setting_security_nginx_compatibility": "Compatibility vs. security tradeoff for the web server NGINX. Affects the ciphers (and other security-related aspects)", "global_settings_setting_security_password_admin_strength": "Admin password strength", @@ -328,7 +328,7 @@ "global_settings_setting_security_ssh_compatibility": "Compatibility vs. security tradeoff for the SSH server. Affects the ciphers (and other security-related aspects)", "global_settings_setting_security_postfix_compatibility": "Compatibility vs. security tradeoff for the Postfix server. Affects the ciphers (and other security-related aspects)", "global_settings_setting_security_ssh_port": "SSH port", - "global_settings_unknown_setting_from_settings_file": "Unknown key in settings: '{setting_key:s}', discard it and save it in /etc/yunohost/settings-unknown.json", + "global_settings_unknown_setting_from_settings_file": "Unknown key in settings: '{setting_key}', discard it and save it in /etc/yunohost/settings-unknown.json", "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Allow the use of (deprecated) DSA hostkey for the SSH daemon configuration", "global_settings_setting_ssowat_panel_overlay_enabled": "Enable SSOwat panel overlay", "global_settings_setting_smtp_allow_ipv6": "Allow the use of IPv6 to receive and send mail", @@ -337,7 +337,7 @@ "global_settings_setting_smtp_relay_user": "SMTP relay user account", "global_settings_setting_smtp_relay_password": "SMTP relay host password", "global_settings_setting_backup_compress_tar_archives": "When creating new backups, compress the archives (.tar.gz) instead of uncompressed archives (.tar). N.B. : enabling this option means create lighter backup archives, but the initial backup procedure will be significantly longer and heavy on CPU.", - "global_settings_unknown_type": "Unexpected situation, the setting {setting:s} appears to have the type {unknown_type:s} but it is not a type supported by the system.", + "global_settings_unknown_type": "Unexpected situation, the setting {setting} appears to have the type {unknown_type} but it is not a type supported by the system.", "good_practices_about_admin_password": "You are now about to define a new administration password. The password should be at least 8 characters long—though it is good practice to use a longer password (i.e. a passphrase) and/or to use a variation of characters (uppercase, lowercase, digits and special characters).", "good_practices_about_user_password": "You are now about to define a new user password. The password should be at least 8 characters long—though it is good practice to use a longer password (i.e. a passphrase) and/or to a variation of characters (uppercase, lowercase, digits and special characters).", "group_already_exist": "Group {group} already exists", @@ -351,18 +351,18 @@ "group_cannot_be_deleted": "The group {group} cannot be deleted manually.", "group_deleted": "Group '{group}' deleted", "group_deletion_failed": "Could not delete the group '{group}': {error}", - "group_unknown": "The group '{group:s}' is unknown", + "group_unknown": "The group '{group}' is unknown", "group_updated": "Group '{group}' updated", "group_update_failed": "Could not update the group '{group}': {error}", "group_user_already_in_group": "User {user} is already in group {group}", "group_user_not_in_group": "User {user} is not in group {group}", - "hook_exec_failed": "Could not run script: {path:s}", - "hook_exec_not_terminated": "Script did not finish properly: {path:s}", - "hook_json_return_error": "Could not read return from hook {path:s}. Error: {msg:s}. Raw content: {raw_content}", + "hook_exec_failed": "Could not run script: {path}", + "hook_exec_not_terminated": "Script did not finish properly: {path}", + "hook_json_return_error": "Could not read return from hook {path}. Error: {msg}. Raw content: {raw_content}", "hook_list_by_invalid": "This property can not be used to list hooks", - "hook_name_unknown": "Unknown hook name '{name:s}'", + "hook_name_unknown": "Unknown hook name '{name}'", "installation_complete": "Installation completed", - "invalid_regex": "Invalid regex:'{regex:s}'", + "invalid_regex": "Invalid regex:'{regex}'", "ip6tables_unavailable": "You cannot play with ip6tables here. You are either in a container or your kernel does not support it", "iptables_unavailable": "You cannot play with iptables here. You are either in a container or your kernel does not support it", "log_corrupted_md_file": "The YAML metadata file associated with logs is damaged: '{md_file}\nError: {error}'", @@ -411,10 +411,10 @@ "log_tools_upgrade": "Upgrade system packages", "log_tools_shutdown": "Shutdown your server", "log_tools_reboot": "Reboot your server", - "mail_alias_remove_failed": "Could not remove e-mail alias '{mail:s}'", - "mail_domain_unknown": "Invalid e-mail address for domain '{domain:s}'. Please, use a domain administrated by this server.", - "mail_forward_remove_failed": "Could not remove e-mail forwarding '{mail:s}'", - "mailbox_disabled": "E-mail turned off for user {user:s}", + "mail_alias_remove_failed": "Could not remove e-mail alias '{mail}'", + "mail_domain_unknown": "Invalid e-mail address for domain '{domain}'. Please, use a domain administrated by this server.", + "mail_forward_remove_failed": "Could not remove e-mail forwarding '{mail}'", + "mailbox_disabled": "E-mail turned off for user {user}", "mailbox_used_space_dovecot_down": "The Dovecot mailbox service needs to be up if you want to fetch used mailbox space", "mail_unavailable": "This e-mail address is reserved and shall be automatically allocated to the very first user", "main_domain_change_failed": "Unable to change the main domain", @@ -427,7 +427,7 @@ "migration_description_0019_extend_permissions_features": "Extend/rework the app permission management system", "migration_description_0020_ssh_sftp_permissions": "Add SSH and SFTP permissions support", "migration_ldap_backup_before_migration": "Creating a backup of LDAP database and apps settings prior to the actual migration.", - "migration_ldap_can_not_backup_before_migration": "The backup of the system could not be completed before the migration failed. Error: {error:s}", + "migration_ldap_can_not_backup_before_migration": "The backup of the system could not be completed before the migration failed. Error: {error}", "migration_ldap_migration_failed_trying_to_rollback": "Could not migrate... trying to roll back the system.", "migration_ldap_rollback_success": "System rolled back.", "migration_update_LDAP_schema": "Updating LDAP schema...", @@ -470,7 +470,7 @@ "migrations_skip_migration": "Skipping migration {id}...", "migrations_success_forward": "Migration {id} completed", "migrations_to_be_ran_manually": "Migration {id} has to be run manually. Please go to Tools → Migrations on the webadmin page, or run `yunohost tools migrations run`.", - "not_enough_disk_space": "Not enough free space on '{path:s}'", + "not_enough_disk_space": "Not enough free space on '{path}'", "invalid_number": "Must be a number", "operation_interrupted": "The operation was manually interrupted?", "packages_upgrade_failed": "Could not upgrade all the packages", @@ -496,19 +496,19 @@ "permission_already_exist": "Permission '{permission}' already exists", "permission_already_up_to_date": "The permission was not updated because the addition/removal requests already match the current state.", "permission_cannot_remove_main": "Removing a main permission is not allowed", - "permission_created": "Permission '{permission:s}' created", + "permission_created": "Permission '{permission}' created", "permission_creation_failed": "Could not create permission '{permission}': {error}", "permission_currently_allowed_for_all_users": "This permission is currently granted to all users in addition to other groups. You probably want to either remove the 'all_users' permission or remove the other groups it is currently granted to.", "permission_cant_add_to_all_users": "The permission {permission} can not be added to all users.", - "permission_deleted": "Permission '{permission:s}' deleted", + "permission_deleted": "Permission '{permission}' deleted", "permission_deletion_failed": "Could not delete permission '{permission}': {error}", - "permission_not_found": "Permission '{permission:s}' not found", + "permission_not_found": "Permission '{permission}' not found", "permission_update_failed": "Could not update permission '{permission}': {error}", - "permission_updated": "Permission '{permission:s}' updated", + "permission_updated": "Permission '{permission}' updated", "permission_protected": "Permission {permission} is protected. You cannot add or remove the visitors group to/from this permission.", "permission_require_account": "Permission {permission} only makes sense for users having an account, and therefore cannot be enabled for visitors.", - "port_already_closed": "Port {port:d} is already closed for {ip_version:s} connections", - "port_already_opened": "Port {port:d} is already opened for {ip_version:s} connections", + "port_already_closed": "Port {port:d} is already closed for {ip_version} connections", + "port_already_opened": "Port {port:d} is already opened for {ip_version} connections", "postinstall_low_rootfsspace": "The root filesystem has a total space less than 10 GB, which is quite worrisome! You will likely run out of disk space very quickly! It's recommended to have at least 16GB for the root filesystem. If you want to install YunoHost despite this warning, re-run the postinstall with --force-diskspace", "regenconf_file_backed_up": "Configuration file '{conf}' backed up to '{backup}'", "regenconf_file_copy_failed": "Could not copy the new configuration file '{new}' to '{conf}'", @@ -528,33 +528,33 @@ "regenconf_need_to_explicitly_specify_ssh": "The ssh configuration has been manually modified, but you need to explicitly specify category 'ssh' with --force to actually apply the changes.", "regex_incompatible_with_tile": "/!\\ Packagers! Permission '{permission}' has show_tile set to 'true' and you therefore cannot define a regex URL as the main URL", "regex_with_only_domain": "You can't use a regex for domain, only for path", - "restore_already_installed_app": "An app with the ID '{app:s}' is already installed", + "restore_already_installed_app": "An app with the ID '{app}' is already installed", "restore_already_installed_apps": "The following apps can't be restored because they are already installed: {apps}", "restore_backup_too_old": "This backup archive can not be restored because it comes from a too-old YunoHost version.", "restore_cleaning_failed": "Could not clean up the temporary restoration directory", "restore_complete": "Restoration completed", - "restore_confirm_yunohost_installed": "Do you really want to restore an already installed system? [{answers:s}]", + "restore_confirm_yunohost_installed": "Do you really want to restore an already installed system? [{answers}]", "restore_extracting": "Extracting needed files from the archive…", "restore_failed": "Could not restore system", - "restore_hook_unavailable": "Restoration script for '{part:s}' not available on your system and not in the archive either", + "restore_hook_unavailable": "Restoration script for '{part}' not available on your system and not in the archive either", "restore_may_be_not_enough_disk_space": "Your system does not seem to have enough space (free: {free_space:d} B, needed space: {needed_space:d} B, security margin: {margin:d} B)", "restore_not_enough_disk_space": "Not enough space (space: {free_space:d} B, needed space: {needed_space:d} B, security margin: {margin:d} B)", "restore_nothings_done": "Nothing was restored", "restore_removing_tmp_dir_failed": "Could not remove an old temporary directory", - "restore_running_app_script": "Restoring the app '{app:s}'…", + "restore_running_app_script": "Restoring the app '{app}'…", "restore_running_hooks": "Running restoration hooks…", - "restore_system_part_failed": "Could not restore the '{part:s}' system part", + "restore_system_part_failed": "Could not restore the '{part}' system part", "root_password_desynchronized": "The admin password was changed, but YunoHost could not propagate this to the root password!", "root_password_replaced_by_admin_password": "Your root password have been replaced by your admin password.", "server_shutdown": "The server will shut down", - "server_shutdown_confirm": "The server will shutdown immediatly, are you sure? [{answers:s}]", + "server_shutdown_confirm": "The server will shutdown immediatly, are you sure? [{answers}]", "server_reboot": "The server will reboot", - "server_reboot_confirm": "The server will reboot immediatly, are you sure? [{answers:s}]", - "service_add_failed": "Could not add the service '{service:s}'", - "service_added": "The service '{service:s}' was added", - "service_already_started": "The service '{service:s}' is running already", - "service_already_stopped": "The service '{service:s}' has already been stopped", - "service_cmd_exec_failed": "Could not execute the command '{command:s}'", + "server_reboot_confirm": "The server will reboot immediatly, are you sure? [{answers}]", + "service_add_failed": "Could not add the service '{service}'", + "service_added": "The service '{service}' was added", + "service_already_started": "The service '{service}' is running already", + "service_already_stopped": "The service '{service}' has already been stopped", + "service_cmd_exec_failed": "Could not execute the command '{command}'", "service_description_avahi-daemon": "Allows you to reach your server using 'yunohost.local' in your local network", "service_description_dnsmasq": "Handles domain name resolution (DNS)", "service_description_dovecot": "Allows e-mail clients to access/fetch email (via IMAP and POP3)", @@ -570,24 +570,24 @@ "service_description_ssh": "Allows you to connect remotely to your server via a terminal (SSH protocol)", "service_description_yunohost-api": "Manages interactions between the YunoHost web interface and the system", "service_description_yunohost-firewall": "Manages open and close connection ports to services", - "service_disable_failed": "Could not make the service '{service:s}' not start at boot.\n\nRecent service logs:{logs:s}", - "service_disabled": "The service '{service:s}' will not be started anymore when system boots.", - "service_enable_failed": "Could not make the service '{service:s}' automatically start at boot.\n\nRecent service logs:{logs:s}", - "service_enabled": "The service '{service:s}' will now be automatically started during system boots.", + "service_disable_failed": "Could not make the service '{service}' not start at boot.\n\nRecent service logs:{logs}", + "service_disabled": "The service '{service}' will not be started anymore when system boots.", + "service_enable_failed": "Could not make the service '{service}' automatically start at boot.\n\nRecent service logs:{logs}", + "service_enabled": "The service '{service}' will now be automatically started during system boots.", "service_regen_conf_is_deprecated": "'yunohost service regen-conf' is deprecated! Please use 'yunohost tools regen-conf' instead.", - "service_remove_failed": "Could not remove the service '{service:s}'", - "service_removed": "Service '{service:s}' removed", - "service_reload_failed": "Could not reload the service '{service:s}'\n\nRecent service logs:{logs:s}", - "service_reloaded": "Service '{service:s}' reloaded", - "service_restart_failed": "Could not restart the service '{service:s}'\n\nRecent service logs:{logs:s}", - "service_restarted": "Service '{service:s}' restarted", - "service_reload_or_restart_failed": "Could not reload or restart the service '{service:s}'\n\nRecent service logs:{logs:s}", - "service_reloaded_or_restarted": "The service '{service:s}' was reloaded or restarted", - "service_start_failed": "Could not start the service '{service:s}'\n\nRecent service logs:{logs:s}", - "service_started": "Service '{service:s}' started", - "service_stop_failed": "Unable to stop the service '{service:s}'\n\nRecent service logs:{logs:s}", - "service_stopped": "Service '{service:s}' stopped", - "service_unknown": "Unknown service '{service:s}'", + "service_remove_failed": "Could not remove the service '{service}'", + "service_removed": "Service '{service}' removed", + "service_reload_failed": "Could not reload the service '{service}'\n\nRecent service logs:{logs}", + "service_reloaded": "Service '{service}' reloaded", + "service_restart_failed": "Could not restart the service '{service}'\n\nRecent service logs:{logs}", + "service_restarted": "Service '{service}' restarted", + "service_reload_or_restart_failed": "Could not reload or restart the service '{service}'\n\nRecent service logs:{logs}", + "service_reloaded_or_restarted": "The service '{service}' was reloaded or restarted", + "service_start_failed": "Could not start the service '{service}'\n\nRecent service logs:{logs}", + "service_started": "Service '{service}' started", + "service_stop_failed": "Unable to stop the service '{service}'\n\nRecent service logs:{logs}", + "service_stopped": "Service '{service}' stopped", + "service_unknown": "Unknown service '{service}'", "show_tile_cant_be_enabled_for_url_not_defined": "You cannot enable 'show_tile' right now, because you must first define an URL for the permission '{permission}'", "show_tile_cant_be_enabled_for_regex": "You cannot enable 'show_tile' right no, because the URL for the permission '{permission}' is a regex", "ssowat_conf_generated": "SSOwat configuration regenerated", @@ -604,11 +604,11 @@ "tools_upgrade_special_packages": "Now upgrading 'special' (yunohost-related) packages…", "tools_upgrade_special_packages_explanation": "The special upgrade will continue in the background. Please don't start any other actions on your server for the next ~10 minutes (depending on hardware speed). After this, you may have to re-log in to the webadmin. The upgrade log will be available in Tools → Log (in the webadmin) or using 'yunohost log list' (from the command-line).", "tools_upgrade_special_packages_completed": "YunoHost package upgrade completed.\nPress [Enter] to get the command line back", - "unbackup_app": "{app:s} will not be saved", + "unbackup_app": "{app} will not be saved", "unexpected_error": "Something unexpected went wrong: {error}", "unknown_main_domain_path": "Unknown domain or path for '{app}'. You need to specify a domain and a path to be able to specify a URL for permission.", "unlimit": "No quota", - "unrestore_app": "{app:s} will not be restored", + "unrestore_app": "{app} will not be restored", "update_apt_cache_failed": "Unable to update the cache of APT (Debian's package manager). Here is a dump of the sources.list lines, which might help identify problematic lines: \n{sourceslist}", "update_apt_cache_warning": "Something went wrong while updating the cache of APT (Debian's package manager). Here is a dump of the sources.list lines, which might help identify problematic lines: \n{sourceslist}", "updating_apt_cache": "Fetching available upgrades for system packages...", @@ -624,7 +624,7 @@ "user_deleted": "User deleted", "user_deletion_failed": "Could not delete user {user}: {error}", "user_home_creation_failed": "Could not create 'home' folder for user", - "user_unknown": "Unknown user: {user:s}", + "user_unknown": "Unknown user: {user}", "user_update_failed": "Could not update user {user}: {error}", "user_updated": "User info changed", "yunohost_already_installed": "YunoHost is already installed", diff --git a/locales/eo.json b/locales/eo.json index 8b7346552..9ccb61043 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -1,11 +1,11 @@ { "admin_password_change_failed": "Ne povis ŝanĝi pasvorton", "admin_password_changed": "La pasvorto de administrado estis ŝanĝita", - "app_already_installed": "{app:s} estas jam instalita", - "app_already_up_to_date": "{app:s} estas jam ĝisdata", - "app_argument_required": "Parametro {name:s} estas bezonata", - "app_change_url_identical_domains": "Malnovaj kaj novaj domajno/URL estas la sama ('{domain:s}{path:s}'), nenio fareblas.", - "app_change_url_success": "{app:s} URL nun estas {domain:s} {path:s}", + "app_already_installed": "{app} estas jam instalita", + "app_already_up_to_date": "{app} estas jam ĝisdata", + "app_argument_required": "Parametro {name} estas bezonata", + "app_change_url_identical_domains": "Malnovaj kaj novaj domajno/URL estas la sama ('{domain}{path}'), nenio fareblas.", + "app_change_url_success": "{app} URL nun estas {domain} {path}", "app_extraction_failed": "Ne povis ĉerpi la instalajn dosierojn", "app_id_invalid": "Nevalida apo ID", "app_install_files_invalid": "Ĉi tiuj dosieroj ne povas esti instalitaj", @@ -22,106 +22,106 @@ "service_description_ssh": "Permesas al vi konekti al via servilo kun fora terminalo (SSH protokolo)", "service_description_yunohost-api": "Mastrumas interagojn inter la YunoHost retinterfaco kaj la sistemo", "service_description_yunohost-firewall": "Administras malfermajn kaj fermajn konektajn havenojn al servoj", - "service_disable_failed": "Ne povis fari la servon '{service:s}' ne komenci ĉe la ekkuro.\n\nLastatempaj servaj protokoloj: {logs:s}", - "service_disabled": "La servo '{service:s}' ne plu komenciĝos kiam sistemo ekos.", - "action_invalid": "Nevalida ago « {action:s} »", + "service_disable_failed": "Ne povis fari la servon '{service}' ne komenci ĉe la ekkuro.\n\nLastatempaj servaj protokoloj: {logs}", + "service_disabled": "La servo '{service}' ne plu komenciĝos kiam sistemo ekos.", + "action_invalid": "Nevalida ago « {action} »", "admin_password": "Pasvorto de la estro", "admin_password_too_long": "Bonvolu elekti pasvorton pli mallonga ol 127 signoj", "already_up_to_date": "Nenio por fari. Ĉio estas jam ĝisdatigita.", - "app_argument_choice_invalid": "Uzu unu el ĉi tiuj elektoj '{choices:s}' por la argumento '{name:s}'", - "app_argument_invalid": "Elektu validan valoron por la argumento '{name:s}': {error:s}", - "app_change_url_failed_nginx_reload": "Ne eblis reŝarĝi NGINX. Jen la eligo de 'nginx -t':\n{nginx_errors:s}", + "app_argument_choice_invalid": "Uzu unu el ĉi tiuj elektoj '{choices}' por la argumento '{name}'", + "app_argument_invalid": "Elektu validan valoron por la argumento '{name}': {error}", + "app_change_url_failed_nginx_reload": "Ne eblis reŝarĝi NGINX. Jen la eligo de 'nginx -t':\n{nginx_errors}", "ask_new_admin_password": "Nova administrada pasvorto", "app_action_broke_system": "Ĉi tiu ago ŝajne rompis ĉi tiujn gravajn servojn: {services}", "app_unsupported_remote_type": "Malkontrolita fora speco uzita por la apliko", - "backup_archive_system_part_not_available": "Sistemo parto '{part:s}' ne haveblas en ĉi tiu rezervo", + "backup_archive_system_part_not_available": "Sistemo parto '{part}' ne haveblas en ĉi tiu rezervo", "backup_abstract_method": "Ĉi tiu rezerva metodo ankoraŭ efektiviĝis", "apps_already_up_to_date": "Ĉiuj aplikoj estas jam ĝisdatigitaj", - "app_location_unavailable": "Ĉi tiu URL aŭ ne haveblas, aŭ konfliktas kun la jam instalita (j) apliko (j):\n{apps:s}", - "backup_archive_app_not_found": "Ne povis trovi {app:s} en la rezerva arkivo", + "app_location_unavailable": "Ĉi tiu URL aŭ ne haveblas, aŭ konfliktas kun la jam instalita (j) apliko (j):\n{apps}", + "backup_archive_app_not_found": "Ne povis trovi {app} en la rezerva arkivo", "backup_actually_backuping": "Krei rezervan arkivon de la kolektitaj dosieroj ...", - "app_change_url_no_script": "La app '{app_name:s}' ankoraŭ ne subtenas URL-modifon. Eble vi devus altgradigi ĝin.", + "app_change_url_no_script": "La app '{app_name}' ankoraŭ ne subtenas URL-modifon. Eble vi devus altgradigi ĝin.", "app_start_install": "Instali {app}...", "backup_created": "Sekurkopio kreita", "app_make_default_location_already_used": "Ne povis fari '{app}' la defaŭltan programon sur la domajno, '{domain}' estas jam uzata de '{other_app}'", "backup_method_copy_finished": "Rezerva kopio finis", - "app_not_properly_removed": "{app:s} ne estis ĝuste forigita", - "backup_archive_broken_link": "Ne povis aliri la rezervan ar archiveivon (rompita ligilo al {path:s})", + "app_not_properly_removed": "{app} ne estis ĝuste forigita", + "backup_archive_broken_link": "Ne povis aliri la rezervan ar archiveivon (rompita ligilo al {path})", "app_requirements_checking": "Kontrolante bezonatajn pakaĵojn por {app} ...", - "app_not_installed": "Ne povis trovi {app:s} en la listo de instalitaj programoj: {all_apps}", + "app_not_installed": "Ne povis trovi {app} en la listo de instalitaj programoj: {all_apps}", "ask_new_path": "Nova vojo", "backup_custom_mount_error": "Propra rezerva metodo ne povis preterpasi la paŝon 'monto'", "app_upgrade_app_name": "Nun ĝisdatigu {app}...", "app_manifest_invalid": "Io misas pri la aplika manifesto: {error}", "backup_cleaning_failed": "Ne povis purigi la provizoran rezervan dosierujon", "backup_creation_failed": "Ne povis krei la rezervan ar archiveivon", - "backup_hook_unknown": "La rezerva hoko '{hook:s}' estas nekonata", + "backup_hook_unknown": "La rezerva hoko '{hook}' estas nekonata", "backup_custom_backup_error": "Propra rezerva metodo ne povis preterpasi la paŝon \"sekurkopio\"", "ask_main_domain": "Ĉefa domajno", "backup_method_tar_finished": "TAR-rezerva ar archiveivo kreita", "backup_cant_mount_uncompress_archive": "Ne povis munti la nekompresitan ar archiveivon kiel protektita kontraŭ skribo", "app_action_cannot_be_ran_because_required_services_down": "Ĉi tiuj postulataj servoj devas funkcii por funkciigi ĉi tiun agon: {services}. Provu rekomenci ilin por daŭrigi (kaj eble esploru, kial ili malsupreniras).", - "backup_copying_to_organize_the_archive": "Kopiante {size:s} MB por organizi la ar archiveivon", + "backup_copying_to_organize_the_archive": "Kopiante {size} MB por organizi la ar archiveivon", "backup_output_directory_forbidden": "Elektu malsaman elirejan dosierujon. Sekurkopioj ne povas esti kreitaj en sub-dosierujoj /bin, /boot, /dev, /ktp, /lib, /root, /run, /sbin, /sys, /usr, /var aŭ /home/yunohost.backup/archives", "backup_no_uncompress_archive_dir": "Ne ekzistas tia nekompremita arkiva dosierujo", "password_too_simple_1": "Pasvorto devas esti almenaŭ 8 signojn longa", - "app_upgrade_failed": "Ne povis ĝisdatigi {app:s}: {error}", + "app_upgrade_failed": "Ne povis ĝisdatigi {app}: {error}", "app_upgrade_several_apps": "La sekvaj apliko estos altgradigitaj: {apps}", "backup_archive_open_failed": "Ne povis malfermi la rezervan ar archiveivon", "ask_lastname": "Familia nomo", "app_start_backup": "Kolekti dosierojn por esti subtenata por {app}...", "backup_archive_name_exists": "Rezerva arkivo kun ĉi tiu nomo jam ekzistas.", "backup_applying_method_tar": "Krei la rezervon TAR Arkivo...", - "backup_method_custom_finished": "Propra rezerva metodo '{method:s}' finiĝis", + "backup_method_custom_finished": "Propra rezerva metodo '{method}' finiĝis", "app_already_installed_cant_change_url": "Ĉi tiu app estas jam instalita. La URL ne povas esti ŝanĝita nur per ĉi tiu funkcio. Kontrolu en `app changeurl` se ĝi haveblas.", - "app_not_correctly_installed": "{app:s} ŝajnas esti malĝuste instalita", - "app_removed": "{app:s} forigita", - "backup_delete_error": "Ne povis forigi '{path:s}'", + "app_not_correctly_installed": "{app} ŝajnas esti malĝuste instalita", + "app_removed": "{app} forigita", + "backup_delete_error": "Ne povis forigi '{path}'", "backup_nothings_done": "Nenio por ŝpari", - "backup_applying_method_custom": "Voki la laŭmendan rezervan metodon '{method:s}'...", - "backup_app_failed": "Ne povis subteni {app:s}", + "backup_applying_method_custom": "Voki la laŭmendan rezervan metodon '{method}'...", + "backup_app_failed": "Ne povis subteni {app}", "app_upgrade_some_app_failed": "Iuj aplikoj ne povis esti altgradigitaj", "app_start_remove": "Forigado {app}...", "backup_output_directory_not_empty": "Vi devas elekti malplenan eligitan dosierujon", - "backup_archive_writing_error": "Ne povis aldoni la dosierojn '{source:s}' (nomitaj en la ar theivo '{dest:s}') por esti rezervitaj en la kunpremita arkivo '{archive:s}'", + "backup_archive_writing_error": "Ne povis aldoni la dosierojn '{source}' (nomitaj en la ar theivo '{dest}') por esti rezervitaj en la kunpremita arkivo '{archive}'", "app_start_restore": "Restarigi {app}...", "backup_applying_method_copy": "Kopii ĉiujn dosierojn por sekurigi...", - "backup_couldnt_bind": "Ne povis ligi {src:s} al {dest:s}.", + "backup_couldnt_bind": "Ne povis ligi {src} al {dest}.", "ask_password": "Pasvorto", "app_requirements_unmeet": "Postuloj ne estas renkontitaj por {app}, la pakaĵo {pkgname} ({version}) devas esti {spec}", "ask_firstname": "Antaŭnomo", - "backup_ask_for_copying_if_needed": "Ĉu vi volas realigi la sekurkopion uzante {size:s} MB provizore? (Ĉi tiu maniero estas uzata ĉar iuj dosieroj ne povus esti pretigitaj per pli efika metodo.)", + "backup_ask_for_copying_if_needed": "Ĉu vi volas realigi la sekurkopion uzante {size} MB provizore? (Ĉi tiu maniero estas uzata ĉar iuj dosieroj ne povus esti pretigitaj per pli efika metodo.)", "backup_mount_archive_for_restore": "Preparante arkivon por restarigo …", "backup_csv_creation_failed": "Ne povis krei la CSV-dosieron bezonatan por restarigo", - "backup_archive_name_unknown": "Nekonata loka rezerva ar archiveivo nomata '{name:s}'", + "backup_archive_name_unknown": "Nekonata loka rezerva ar archiveivo nomata '{name}'", "app_sources_fetch_failed": "Ne povis akiri fontajn dosierojn, ĉu la URL estas ĝusta?", "ask_new_domain": "Nova domajno", "app_unknown": "Nekonata apliko", "app_not_upgraded": "La '{failed_app}' de la programo ne sukcesis ĝisdatigi, kaj sekve la nuntempaj plibonigoj de la sekvaj programoj estis nuligitaj: {apps}", "aborting": "Aborti.", - "app_upgraded": "{app:s} altgradigita", + "app_upgraded": "{app} altgradigita", "backup_deleted": "Rezerva forigita", "backup_csv_addition_failed": "Ne povis aldoni dosierojn al sekurkopio en la CSV-dosiero", "dpkg_lock_not_available": "Ĉi tiu komando ne povas funkcii nun ĉar alia programo uzas la seruron de dpkg (la administrilo de paka sistemo)", "domain_dyndns_root_unknown": "Nekonata radika domajno DynDNS", - "field_invalid": "Nevalida kampo '{:s}'", + "field_invalid": "Nevalida kampo '{}'", "log_app_makedefault": "Faru '{}' la defaŭlta apliko", - "backup_system_part_failed": "Ne eblis sekurkopi la sistemon de '{part:s}'", + "backup_system_part_failed": "Ne eblis sekurkopi la sistemon de '{part}'", "global_settings_setting_security_postfix_compatibility": "Kongruo vs sekureca kompromiso por la Postfix-servilo. Afektas la ĉifradojn (kaj aliajn aspektojn pri sekureco)", - "group_unknown": "La grupo '{group:s}' estas nekonata", - "mailbox_disabled": "Retpoŝto malŝaltita por uzanto {user:s}", + "group_unknown": "La grupo '{group}' estas nekonata", + "mailbox_disabled": "Retpoŝto malŝaltita por uzanto {user}", "migrations_dependencies_not_satisfied": "Rulu ĉi tiujn migradojn: '{dependencies_id}', antaŭ migrado {id}.", "migrations_failed_to_load_migration": "Ne povis ŝarĝi migradon {id}: {error}", "migrations_exclusive_options": "'--auto', '--skip' kaj '--force-rerun' estas reciproke ekskluzivaj ebloj.", "migrations_must_provide_explicit_targets": "Vi devas provizi eksplicitajn celojn kiam vi uzas '--skip' aŭ '--force-rerun'", "permission_update_failed": "Ne povis ĝisdatigi permeson '{permission}': {error}", - "permission_updated": "Ĝisdatigita \"{permission:s}\" rajtigita", + "permission_updated": "Ĝisdatigita \"{permission}\" rajtigita", "tools_upgrade_cant_hold_critical_packages": "Ne povis teni kritikajn pakojn…", "upnp_dev_not_found": "Neniu UPnP-aparato trovita", "pattern_password": "Devas esti almenaŭ 3 signoj longaj", "root_password_desynchronized": "La pasvorta administranto estis ŝanĝita, sed YunoHost ne povis propagandi ĉi tion al la radika pasvorto!", - "service_remove_failed": "Ne povis forigi la servon '{service:s}'", - "backup_permission": "Rezerva permeso por app {app:s}", + "service_remove_failed": "Ne povis forigi la servon '{service}'", + "backup_permission": "Rezerva permeso por app {app}", "log_user_group_delete": "Forigi grupon '{}'", "log_user_group_update": "Ĝisdatigi grupon '{}'", "dyndns_provider_unreachable": "Ne povas atingi la provizanton DynDNS {provider}: ĉu via YunoHost ne estas ĝuste konektita al la interreto aŭ la dyneta servilo malŝaltiĝas.", @@ -139,7 +139,7 @@ "log_user_group_create": "Krei grupon '{}'", "log_user_permission_update": "Mise à jour des accès pour la permission '{}'", "log_user_permission_reset": "Restarigi permeson '{}'", - "mail_forward_remove_failed": "Ne povis forigi retpoŝton plusendante '{mail:s}'", + "mail_forward_remove_failed": "Ne povis forigi retpoŝton plusendante '{mail}'", "migrations_already_ran": "Tiuj migradoj estas jam faritaj: {ids}", "migrations_no_such_migration": "Estas neniu migrado nomata '{id}'", "permission_already_allowed": "Grupo '{group}' jam havas rajtigitan permeson '{permission}'", @@ -151,14 +151,14 @@ "migrations_running_forward": "Kuranta migrado {id}…", "migrations_success_forward": "Migrado {id} kompletigita", "operation_interrupted": "La operacio estis permane interrompita?", - "permission_created": "Permesita '{permission:s}' kreita", - "permission_deleted": "Permesita \"{permission:s}\" forigita", + "permission_created": "Permesita '{permission}' kreita", + "permission_deleted": "Permesita \"{permission}\" forigita", "permission_deletion_failed": "Ne povis forigi permeson '{permission}': {error}", - "permission_not_found": "Permesita \"{permission:s}\" ne trovita", + "permission_not_found": "Permesita \"{permission}\" ne trovita", "restore_not_enough_disk_space": "Ne sufiĉa spaco (spaco: {free_space:d} B, necesa spaco: {needed_space:d} B, sekureca marĝeno: {margin:d} B)", "tools_upgrade_regular_packages": "Nun ĝisdatigi 'regulajn' (ne-yunohost-rilatajn) pakojn …", "tools_upgrade_special_packages_explanation": "La speciala ĝisdatigo daŭros en la fono. Bonvolu ne komenci aliajn agojn en via servilo dum la sekvaj ~ 10 minutoj (depende de la aparata rapideco). Post tio, vi eble devos re-ensaluti al la retadreso. La ĝisdatiga registro estos havebla en Iloj → Ensaluto (en la retadreso) aŭ uzante 'yunohost logliston' (el la komandlinio).", - "unrestore_app": "App '{app:s}' ne restarigos", + "unrestore_app": "App '{app}' ne restarigos", "group_created": "Grupo '{group}' kreita", "group_creation_failed": "Ne povis krei la grupon '{group}': {error}", "group_deleted": "Grupo '{group}' forigita", @@ -169,44 +169,44 @@ "log_user_create": "Aldonu uzanton '{}'", "ip6tables_unavailable": "Vi ne povas ludi kun ip6tabloj ĉi tie. Vi estas en ujo aŭ via kerno ne subtenas ĝin", "mail_unavailable": "Ĉi tiu retpoŝta adreso estas rezervita kaj aŭtomate estos atribuita al la unua uzanto", - "certmanager_domain_dns_ip_differs_from_public_ip": "La DNS 'A' rekordo por la domajno '{domain:s}' diferencas de la IP de ĉi tiu servilo. Se vi lastatempe modifis vian A-registron, bonvolu atendi ĝin propagandi (iuj DNS-disvastigaj kontroliloj estas disponeblaj interrete). (Se vi scias, kion vi faras, uzu '--no-checks' por malŝalti tiujn ĉekojn.)", + "certmanager_domain_dns_ip_differs_from_public_ip": "La DNS 'A' rekordo por la domajno '{domain}' diferencas de la IP de ĉi tiu servilo. Se vi lastatempe modifis vian A-registron, bonvolu atendi ĝin propagandi (iuj DNS-disvastigaj kontroliloj estas disponeblaj interrete). (Se vi scias, kion vi faras, uzu '--no-checks' por malŝalti tiujn ĉekojn.)", "tools_upgrade_special_packages_completed": "Plenumis la ĝisdatigon de pakaĵoj de YunoHost.\nPremu [Enter] por retrovi la komandlinion", "log_remove_on_failed_install": "Forigu '{}' post malsukcesa instalado", "regenconf_file_manually_modified": "La agorddosiero '{conf}' estis modifita permane kaj ne estos ĝisdatigita", "regenconf_would_be_updated": "La agordo estus aktualigita por la kategorio '{category}'", - "certmanager_cert_install_success_selfsigned": "Mem-subskribita atestilo nun instalita por la domajno '{domain:s}'", - "global_settings_unknown_setting_from_settings_file": "Nekonata ŝlosilo en agordoj: '{setting_key:s}', forĵetu ĝin kaj konservu ĝin en /etc/yunohost/settings-unknown.json", + "certmanager_cert_install_success_selfsigned": "Mem-subskribita atestilo nun instalita por la domajno '{domain}'", + "global_settings_unknown_setting_from_settings_file": "Nekonata ŝlosilo en agordoj: '{setting_key}', forĵetu ĝin kaj konservu ĝin en /etc/yunohost/settings-unknown.json", "regenconf_file_backed_up": "Agordodosiero '{conf}' estis rezervita al '{backup}'", "iptables_unavailable": "Vi ne povas ludi kun iptables ĉi tie. Vi estas en ujo aŭ via kerno ne subtenas ĝin", - "global_settings_cant_write_settings": "Ne eblis konservi agordojn, tial: {reason:s}", - "service_added": "La servo '{service:s}' estis aldonita", + "global_settings_cant_write_settings": "Ne eblis konservi agordojn, tial: {reason}", + "service_added": "La servo '{service}' estis aldonita", "upnp_disabled": "UPnP malŝaltis", - "service_started": "Servo '{service:s}' komenciĝis", - "port_already_opened": "Haveno {port:d} estas jam malfermita por {ip_version:s} rilatoj", + "service_started": "Servo '{service}' komenciĝis", + "port_already_opened": "Haveno {port:d} estas jam malfermita por {ip_version} rilatoj", "upgrading_packages": "Ĝisdatigi pakojn…", - "custom_app_url_required": "Vi devas provizi URL por altgradigi vian kutimon app {app:s}", - "service_reload_failed": "Ne povis reŝargi la servon '{service:s}'\n\nLastatempaj servaj protokoloj: {logs:s}", + "custom_app_url_required": "Vi devas provizi URL por altgradigi vian kutimon app {app}", + "service_reload_failed": "Ne povis reŝargi la servon '{service}'\n\nLastatempaj servaj protokoloj: {logs}", "packages_upgrade_failed": "Ne povis ĝisdatigi ĉiujn pakojn", - "hook_json_return_error": "Ne povis legi revenon de hoko {path:s}. Eraro: {msg:s}. Kruda enhavo: {raw_content}", + "hook_json_return_error": "Ne povis legi revenon de hoko {path}. Eraro: {msg}. Kruda enhavo: {raw_content}", "dyndns_key_not_found": "DNS-ŝlosilo ne trovita por la domajno", "tools_upgrade_regular_packages_failed": "Ne povis ĝisdatigi pakojn: {packages_list}", - "service_start_failed": "Ne povis komenci la servon '{service:s}'\n\nLastatempaj servaj protokoloj: {logs:s}", - "service_reloaded": "Servo '{service:s}' reŝargita", + "service_start_failed": "Ne povis komenci la servon '{service}'\n\nLastatempaj servaj protokoloj: {logs}", + "service_reloaded": "Servo '{service}' reŝargita", "system_upgraded": "Sistemo ĝisdatigita", "domain_deleted": "Domajno forigita", - "certmanager_acme_not_configured_for_domain": "Atestilo por la domajno '{domain:s}' ne ŝajnas esti ĝuste instalita. Bonvolu ekzekuti 'cert-instali' por ĉi tiu regado unue.", + "certmanager_acme_not_configured_for_domain": "Atestilo por la domajno '{domain}' ne ŝajnas esti ĝuste instalita. Bonvolu ekzekuti 'cert-instali' por ĉi tiu regado unue.", "user_update_failed": "Ne povis ĝisdatigi uzanton {user}: {error}", - "restore_confirm_yunohost_installed": "Ĉu vi vere volas restarigi jam instalitan sistemon? [{answers:s}]", + "restore_confirm_yunohost_installed": "Ĉu vi vere volas restarigi jam instalitan sistemon? [{answers}]", "pattern_positive_number": "Devas esti pozitiva nombro", "update_apt_cache_failed": "Ne eblis ĝisdatigi la kaŝmemoron de APT (paka administranto de Debian). Jen rubujo de la sources.list-linioj, kiuj povus helpi identigi problemajn liniojn:\n{sourceslist}", "migrations_no_migrations_to_run": "Neniuj migradoj por funkcii", - "certmanager_attempt_to_renew_nonLE_cert": "La atestilo por la domajno '{domain:s}' ne estas elsendita de Let's Encrypt. Ne eblas renovigi ĝin aŭtomate!", + "certmanager_attempt_to_renew_nonLE_cert": "La atestilo por la domajno '{domain}' ne estas elsendita de Let's Encrypt. Ne eblas renovigi ĝin aŭtomate!", "domain_dyndns_already_subscribed": "Vi jam abonis DynDNS-domajnon", "log_letsencrypt_cert_renew": "Renovigu '{}' Let's Encrypt atestilon", "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} '", - "global_settings_cant_serialize_settings": "Ne eblis serialigi datumojn pri agordoj, motivo: {reason:s}", + "global_settings_cant_serialize_settings": "Ne eblis serialigi datumojn pri agordoj, motivo: {reason}", "backup_running_hooks": "Kurado de apogaj hokoj …", "unexpected_error": "Io neatendita iris malbone: {error}", "password_listed": "Ĉi tiu pasvorto estas inter la plej uzataj pasvortoj en la mondo. Bonvolu elekti ion pli unikan.", @@ -220,36 +220,36 @@ "migrations_loading_migration": "Ŝarĝante migradon {id}…", "pattern_mailbox_quota": "Devas esti grandeco kun la sufikso b/k/M/G/T aŭ 0 por ne havi kvoton", "user_deletion_failed": "Ne povis forigi uzanton {user}: {error}", - "backup_with_no_backup_script_for_app": "La app '{app:s}' ne havas sekretan skripton. Ignorante.", + "backup_with_no_backup_script_for_app": "La app '{app}' ne havas sekretan skripton. Ignorante.", "service_regen_conf_is_deprecated": "'yunohost service regen-conf' malakceptas! Bonvolu uzi anstataŭe 'yunohost tools regen-conf'.", - "global_settings_key_doesnt_exists": "La ŝlosilo '{settings_key:s}' ne ekzistas en la tutmondaj agordoj, vi povas vidi ĉiujn disponeblajn klavojn per uzado de 'yunohost settings list'", + "global_settings_key_doesnt_exists": "La ŝlosilo '{settings_key}' ne ekzistas en la tutmondaj agordoj, vi povas vidi ĉiujn disponeblajn klavojn per uzado de 'yunohost settings list'", "dyndns_no_domain_registered": "Neniu domajno registrita ĉe DynDNS", - "dyndns_could_not_check_available": "Ne povis kontroli ĉu {domain:s} haveblas sur {provider:s}.", - "hook_exec_not_terminated": "Skripto ne finiĝis ĝuste: {path:s}", - "service_stopped": "Servo '{service:s}' ĉesis", + "dyndns_could_not_check_available": "Ne povis kontroli ĉu {domain} haveblas sur {provider}.", + "hook_exec_not_terminated": "Skripto ne finiĝis ĝuste: {path}", + "service_stopped": "Servo '{service}' ĉesis", "restore_failed": "Ne povis restarigi sistemon", - "confirm_app_install_danger": "Danĝero! Ĉi tiu apliko estas konata ankoraŭ eksperimenta (se ne eksplicite ne funkcias)! Vi probable ne devas instali ĝin krom se vi scias kion vi faras. NENIU SUBTENO estos provizita se ĉi tiu app ne funkcias aŭ rompas vian sistemon ... Se vi pretas riski ĉiuokaze, tajpu '{answers:s}'", + "confirm_app_install_danger": "Danĝero! Ĉi tiu apliko estas konata ankoraŭ eksperimenta (se ne eksplicite ne funkcias)! Vi probable ne devas instali ĝin krom se vi scias kion vi faras. NENIU SUBTENO estos provizita se ĉi tiu app ne funkcias aŭ rompas vian sistemon ... Se vi pretas riski ĉiuokaze, tajpu '{answers}'", "log_operation_unit_unclosed_properly": "Operaciumo ne estis fermita ĝuste", "upgrade_complete": "Ĝisdatigo kompleta", "upnp_enabled": "UPnP ŝaltis", "mailbox_used_space_dovecot_down": "La poŝta servo de Dovecot devas funkcii, se vi volas akcepti uzitan poŝtan keston", - "restore_system_part_failed": "Ne povis restarigi la sisteman parton '{part:s}'", - "service_stop_failed": "Ne povis maldaŭrigi la servon '{service:s}'\n\nLastatempaj servaj protokoloj: {logs:s}", - "unbackup_app": "App '{app:s}' ne konserviĝos", + "restore_system_part_failed": "Ne povis restarigi la sisteman parton '{part}'", + "service_stop_failed": "Ne povis maldaŭrigi la servon '{service}'\n\nLastatempaj servaj protokoloj: {logs}", + "unbackup_app": "App '{app}' ne konserviĝos", "updating_apt_cache": "Akirante haveblajn ĝisdatigojn por sistemaj pakoj…", "tools_upgrade_at_least_one": "Bonvolu specifi '--apps' aŭ '--system'", - "service_already_stopped": "La servo '{service:s}' jam ĉesis", + "service_already_stopped": "La servo '{service}' jam ĉesis", "tools_upgrade_cant_both": "Ne eblas ĝisdatigi ambaŭ sistemon kaj programojn samtempe", "restore_extracting": "Eltirante bezonatajn dosierojn el la ar theivo…", "upnp_port_open_failed": "Ne povis malfermi havenon per UPnP", "log_app_upgrade": "Ĝisdatigu la aplikon '{}'", "log_help_to_get_failed_log": "La operacio '{desc}' ne povis finiĝi. Bonvolu dividi la plenan ŝtipon de ĉi tiu operacio per la komando 'yunohost log share {name}' por akiri helpon", - "port_already_closed": "Haveno {port:d} estas jam fermita por {ip_version:s} rilatoj", - "hook_name_unknown": "Nekonata hoko-nomo '{name:s}'", - "dyndns_could_not_check_provide": "Ne povis kontroli ĉu {provider:s} povas provizi {domain:s}.", + "port_already_closed": "Haveno {port:d} estas jam fermita por {ip_version} rilatoj", + "hook_name_unknown": "Nekonata hoko-nomo '{name}'", + "dyndns_could_not_check_provide": "Ne povis kontroli ĉu {provider} povas provizi {domain}.", "restore_nothings_done": "Nenio estis restarigita", "log_tools_postinstall": "Afiŝu vian servilon YunoHost", - "dyndns_unavailable": "La domajno '{domain:s}' ne haveblas.", + "dyndns_unavailable": "La domajno '{domain}' ne haveblas.", "experimental_feature": "Averto: Ĉi tiu funkcio estas eksperimenta kaj ne konsiderata stabila, vi ne uzu ĝin krom se vi scias kion vi faras.", "root_password_replaced_by_admin_password": "Via radika pasvorto estis anstataŭigita per via administra pasvorto.", "domain_unknown": "Nekonata domajno", @@ -258,111 +258,111 @@ "log_corrupted_md_file": "La YAD-metadata dosiero asociita kun protokoloj estas damaĝita: '{md_file}\nEraro: {error} '", "downloading": "Elŝutante …", "user_deleted": "Uzanto forigita", - "service_enable_failed": "Ne povis fari la servon '{service:s}' aŭtomate komenci ĉe la ekkuro.\n\nLastatempaj servaj protokoloj: {logs:s}", + "service_enable_failed": "Ne povis fari la servon '{service}' aŭtomate komenci ĉe la ekkuro.\n\nLastatempaj servaj protokoloj: {logs}", "tools_upgrade_special_packages": "Nun ĝisdatigi 'specialajn' (rilatajn al yunohost)…", "domains_available": "Haveblaj domajnoj:", "dyndns_registered": "Registrita domajno DynDNS", "service_description_fail2ban": "Protektas kontraŭ bruta forto kaj aliaj specoj de atakoj de la interreto", - "file_does_not_exist": "La dosiero {path:s} ne ekzistas.", + "file_does_not_exist": "La dosiero {path} ne ekzistas.", "yunohost_not_installed": "YunoHost ne estas ĝuste instalita. Bonvolu prilabori 'yunohost tools postinstall'", "restore_removing_tmp_dir_failed": "Ne povis forigi malnovan provizoran dosierujon", - "certmanager_cannot_read_cert": "Io malbona okazis, kiam mi provis malfermi aktualan atestilon por domajno {domain:s} (dosiero: {file:s}), kialo: {reason:s}", - "service_removed": "Servo '{service:s}' forigita", - "certmanager_hit_rate_limit": "Tro multaj atestiloj jam eldonitaj por ĉi tiu ĝusta aro de domajnoj {domain:s} antaŭ nelonge. Bonvolu reprovi poste. Vidu https://letsencrypt.org/docs/rate-limits/ por pliaj detaloj", + "certmanager_cannot_read_cert": "Io malbona okazis, kiam mi provis malfermi aktualan atestilon por domajno {domain} (dosiero: {file}), kialo: {reason}", + "service_removed": "Servo '{service}' forigita", + "certmanager_hit_rate_limit": "Tro multaj atestiloj jam eldonitaj por ĉi tiu ĝusta aro de domajnoj {domain} antaŭ nelonge. Bonvolu reprovi poste. Vidu https://letsencrypt.org/docs/rate-limits/ por pliaj detaloj", "pattern_firstname": "Devas esti valida antaŭnomo", "domain_cert_gen_failed": "Ne povis generi atestilon", "regenconf_file_kept_back": "La agorda dosiero '{conf}' estas atendita forigi per regen-conf (kategorio {category}), sed ĝi estis konservita.", - "backup_with_no_restore_script_for_app": "La apliko \"{app:s}\" ne havas restarigan skripton, vi ne povos aŭtomate restarigi la sekurkopion de ĉi tiu apliko.", + "backup_with_no_restore_script_for_app": "La apliko \"{app}\" ne havas restarigan skripton, vi ne povos aŭtomate restarigi la sekurkopion de ĉi tiu apliko.", "log_letsencrypt_cert_install": "Instalu atestilon Let's Encrypt sur '{}' regado", "log_dyndns_update": "Ĝisdatigu la IP asociita kun via subdominio YunoHost '{}'", "firewall_reload_failed": "Ne eblis reŝargi la firewall", - "confirm_app_install_warning": "Averto: Ĉi tiu aplikaĵo povas funkcii, sed ne bone integras en YunoHost. Iuj funkcioj kiel ekzemple aliĝilo kaj sekurkopio / restarigo eble ne haveblos. Instali ĉiuokaze? [{answers:s}] ", + "confirm_app_install_warning": "Averto: Ĉi tiu aplikaĵo povas funkcii, sed ne bone integras en YunoHost. Iuj funkcioj kiel ekzemple aliĝilo kaj sekurkopio / restarigo eble ne haveblos. Instali ĉiuokaze? [{answers}] ", "log_user_delete": "Forigi uzanton '{}'", "dyndns_ip_updated": "Ĝisdatigis vian IP sur DynDNS", "regenconf_up_to_date": "La agordo jam estas ĝisdatigita por kategorio '{category}'", "global_settings_setting_security_ssh_compatibility": "Kongruo vs sekureca kompromiso por la SSH-servilo. Afektas la ĉifradojn (kaj aliajn aspektojn pri sekureco)", "migrations_need_to_accept_disclaimer": "Por funkciigi la migradon {id}, via devas akcepti la sekvan malakcepton:\n---\n{disclaimer}\n---\nSe vi akceptas funkcii la migradon, bonvolu rekonduki la komandon kun la opcio '--accept-disclaimer'.", "regenconf_file_remove_failed": "Ne povis forigi la agordodosieron '{conf}'", - "not_enough_disk_space": "Ne sufiĉe libera spaco sur '{path:s}'", + "not_enough_disk_space": "Ne sufiĉe libera spaco sur '{path}'", "dyndns_ip_update_failed": "Ne povis ĝisdatigi IP-adreson al DynDNS", "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", "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", - "dyndns_registration_failed": "Ne povis registri DynDNS-domajnon: {error:s}", - "user_unknown": "Nekonata uzanto: {user:s}", + "dyndns_registration_failed": "Ne povis registri DynDNS-domajnon: {error}", + "user_unknown": "Nekonata uzanto: {user}", "migrations_to_be_ran_manually": "Migrado {id} devas funkcii permane. Bonvolu iri al Iloj → Migradoj en la retpaĝa paĝo, aŭ kuri `yunohost tools migrations run`.", - "certmanager_cert_renew_success": "Ni Ĉifru atestilon renovigitan por la domajno '{domain:s}'", - "global_settings_reset_success": "Antaŭaj agordoj nun estas rezervitaj al {path:s}", + "certmanager_cert_renew_success": "Ni Ĉifru atestilon renovigitan por la domajno '{domain}'", + "global_settings_reset_success": "Antaŭaj agordoj nun estas rezervitaj al {path}", "pattern_domain": "Devas esti valida domajna nomo (t.e. mia-domino.org)", "dyndns_key_generating": "Generi DNS-ŝlosilon ... Eble daŭros iom da tempo.", - "restore_running_app_script": "Restarigi la programon '{app:s}'…", + "restore_running_app_script": "Restarigi la programon '{app}'…", "migrations_skip_migration": "Salti migradon {id}…", "regenconf_file_removed": "Agordodosiero '{conf}' forigita", "log_tools_shutdown": "Enŝaltu vian servilon", "password_too_simple_3": "La pasvorto bezonas almenaŭ 8 signojn kaj enhavas ciferon, majusklon, pli malaltan kaj specialajn signojn", - "backup_output_symlink_dir_broken": "Via arkiva dosierujo '{path:s}' estas rompita ligilo. Eble vi forgesis restarigi aŭ munti aŭ enŝovi la stokadon, al kiu ĝi notas.", + "backup_output_symlink_dir_broken": "Via arkiva dosierujo '{path}' estas rompita ligilo. Eble vi forgesis restarigi aŭ munti aŭ enŝovi la stokadon, al kiu ĝi notas.", "good_practices_about_admin_password": "Vi nun estas por difini novan administran pasvorton. La pasvorto devas esti almenaŭ 8 signojn - kvankam estas bone praktiki uzi pli longan pasvorton (t.e. pasfrazon) kaj/aŭ uzi variaĵon de signoj (majuskloj, minuskloj, ciferoj kaj specialaj signoj).", - "certmanager_attempt_to_renew_valid_cert": "La atestilo por la domajno '{domain:s}' ne finiĝos! (Vi eble uzos --force se vi scias kion vi faras)", + "certmanager_attempt_to_renew_valid_cert": "La atestilo por la domajno '{domain}' ne finiĝos! (Vi eble uzos --force se vi scias kion vi faras)", "restore_running_hooks": "Kurantaj restarigaj hokoj…", "regenconf_pending_applying": "Aplikante pritraktata agordo por kategorio '{category}'…", "service_description_dovecot": "Permesas al retpoŝtaj klientoj aliri / serĉi retpoŝton (per IMAP kaj POP3)", "domain_dns_conf_is_just_a_recommendation": "Ĉi tiu komando montras al vi la *rekomenditan* agordon. Ĝi efektive ne agordas la DNS-agordon por vi. Via respondeco agordi vian DNS-zonon en via registristo laŭ ĉi tiu rekomendo.", "log_backup_restore_system": "Restarigi sistemon de rezerva arkivo", "log_app_change_url": "Ŝanĝu la URL de la apliko '{}'", - "service_already_started": "La servo '{service:s}' jam funkcias", + "service_already_started": "La servo '{service}' jam funkcias", "global_settings_setting_security_password_admin_strength": "Admin pasvorta forto", - "service_reload_or_restart_failed": "Ne povis reŝargi aŭ rekomenci la servon '{service:s}'\n\nLastatempaj servaj protokoloj: {logs:s}", + "service_reload_or_restart_failed": "Ne povis reŝargi aŭ rekomenci la servon '{service}'\n\nLastatempaj servaj protokoloj: {logs}", "migrations_list_conflict_pending_done": "Vi ne povas uzi ambaŭ '--previous' kaj '--done' samtempe.", - "server_shutdown_confirm": "La servilo haltos tuj, ĉu vi certas? [{answers:s}]", + "server_shutdown_confirm": "La servilo haltos tuj, ĉu vi certas? [{answers}]", "log_backup_restore_app": "Restarigu '{}' de rezerva ar archiveivo", "log_does_exists": "Ne estas operacio kun la nomo '{log}', uzu 'yunohost log list' por vidi ĉiujn disponeblajn operaciojn", - "service_add_failed": "Ne povis aldoni la servon '{service:s}'", + "service_add_failed": "Ne povis aldoni la servon '{service}'", "pattern_password_app": "Bedaŭrinde, pasvortoj ne povas enhavi jenajn signojn: {forbidden_chars}", "this_action_broke_dpkg": "Ĉi tiu ago rompis dpkg / APT (la administrantoj pri la paka sistemo) ... Vi povas provi solvi ĉi tiun problemon per konekto per SSH kaj funkcianta `sudo dpkg --configure -a`.", "log_regen_conf": "Regeneri sistemajn agordojn '{}'", - "restore_hook_unavailable": "Restariga skripto por '{part:s}' ne haveblas en via sistemo kaj ankaŭ ne en la ar theivo", + "restore_hook_unavailable": "Restariga skripto por '{part}' ne haveblas en via sistemo kaj ankaŭ ne en la ar theivo", "log_dyndns_subscribe": "Aboni al YunoHost-subdominio '{}'", "password_too_simple_4": "La pasvorto bezonas almenaŭ 12 signojn kaj enhavas ciferon, majuskle, pli malaltan kaj specialajn signojn", "regenconf_file_updated": "Agordodosiero '{conf}' ĝisdatigita", "log_help_to_get_log": "Por vidi la protokolon de la operacio '{desc}', uzu la komandon 'yunohost log show {name}{name}'", "global_settings_setting_security_nginx_compatibility": "Kongruo vs sekureca kompromiso por la TTT-servilo NGINX. Afektas la ĉifradojn (kaj aliajn aspektojn pri sekureco)", "restore_complete": "Restarigita", - "hook_exec_failed": "Ne povis funkcii skripto: {path:s}", - "global_settings_cant_open_settings": "Ne eblis malfermi agordojn, tial: {reason:s}", + "hook_exec_failed": "Ne povis funkcii skripto: {path}", + "global_settings_cant_open_settings": "Ne eblis malfermi agordojn, tial: {reason}", "user_created": "Uzanto kreita", "service_description_avahi-daemon": "Permesas al vi atingi vian servilon uzante 'yunohost.local' en via loka reto", - "certmanager_attempt_to_replace_valid_cert": "Vi provas anstataŭigi bonan kaj validan atestilon por domajno {domain:s}! (Uzu --forte pretervidi)", + "certmanager_attempt_to_replace_valid_cert": "Vi provas anstataŭigi bonan kaj validan atestilon por domajno {domain}! (Uzu --forte pretervidi)", "regenconf_updated": "Agordo ĝisdatigita por '{category}'", "update_apt_cache_warning": "Io iris malbone dum la ĝisdatigo de la kaŝmemoro de APT (paka administranto de Debian). Jen rubujo de la sources.list-linioj, kiuj povus helpi identigi problemajn liniojn:\n{sourceslist}", "regenconf_dry_pending_applying": "Kontrolado de pritraktata agordo, kiu estus aplikita por kategorio '{category}'…", "regenconf_file_copy_failed": "Ne povis kopii la novan agordodosieron '{new}' al '{conf}'", - "restore_already_installed_app": "App kun la ID '{app:s}' estas jam instalita", - "mail_domain_unknown": "Nevalida retadreso por domajno '{domain:s}'. Bonvolu uzi domajnon administritan de ĉi tiu servilo.", + "restore_already_installed_app": "App kun la ID '{app}' estas jam instalita", + "mail_domain_unknown": "Nevalida retadreso por domajno '{domain}'. Bonvolu uzi domajnon administritan de ĉi tiu servilo.", "migrations_cant_reach_migration_file": "Ne povis aliri migrajn dosierojn ĉe la vojo '% s'", "pattern_email": "Devas esti valida retpoŝta adreso (t.e. iu@ekzemple.com)", - "mail_alias_remove_failed": "Ne povis forigi retpoŝton alias '{mail:s}'", + "mail_alias_remove_failed": "Ne povis forigi retpoŝton alias '{mail}'", "regenconf_file_manually_removed": "La dosiero de agordo '{conf}' estis forigita permane, kaj ne estos kreita", "domain_exists": "La domajno jam ekzistas", - "certmanager_domain_cert_not_selfsigned": "La atestilo por domajno {domain:s} ne estas mem-subskribita. Ĉu vi certas, ke vi volas anstataŭigi ĝin? (Uzu '--force' por fari tion.)", - "certmanager_unable_to_parse_self_CA_name": "Ne povis trapasi nomon de mem-subskribinta aŭtoritato (dosiero: {file:s})", + "certmanager_domain_cert_not_selfsigned": "La atestilo por domajno {domain} ne estas mem-subskribita. Ĉu vi certas, ke vi volas anstataŭigi ĝin? (Uzu '--force' por fari tion.)", + "certmanager_unable_to_parse_self_CA_name": "Ne povis trapasi nomon de mem-subskribinta aŭtoritato (dosiero: {file})", "log_selfsigned_cert_install": "Instalu mem-subskribitan atestilon sur '{}' domajno", "log_tools_reboot": "Reklamu vian servilon", - "certmanager_cert_install_success": "Ni Ĉifru atestilon nun instalitan por la domajno '{domain:s}'", - "global_settings_bad_choice_for_enum": "Malbona elekto por agordo {setting:s}, ricevita '{choice:s}', sed disponeblaj elektoj estas: {available_choices:s}", + "certmanager_cert_install_success": "Ni Ĉifru atestilon nun instalitan por la domajno '{domain}'", + "global_settings_bad_choice_for_enum": "Malbona elekto por agordo {setting}, ricevita '{choice}', sed disponeblaj elektoj estas: {available_choices}", "server_shutdown": "La servilo haltos", "log_tools_migrations_migrate_forward": "Kuru migradoj", "regenconf_now_managed_by_yunohost": "La agorda dosiero '{conf}' nun estas administrata de YunoHost (kategorio {category}).", - "server_reboot_confirm": "Ĉu la servilo rekomencos tuj, ĉu vi certas? [{answers:s}]", + "server_reboot_confirm": "Ĉu la servilo rekomencos tuj, ĉu vi certas? [{answers}]", "log_app_install": "Instalu la aplikon '{}'", "service_description_dnsmasq": "Traktas rezolucion de domajna nomo (DNS)", - "global_settings_unknown_type": "Neatendita situacio, la agordo {setting:s} ŝajnas havi la tipon {unknown_type:s} sed ĝi ne estas tipo subtenata de la sistemo.", + "global_settings_unknown_type": "Neatendita situacio, la agordo {setting} ŝajnas havi la tipon {unknown_type} sed ĝi ne estas tipo subtenata de la sistemo.", "domain_hostname_failed": "Ne povis agordi novan gastigilon. Ĉi tio eble kaŭzos problemon poste (eble bone).", "server_reboot": "La servilo rekomenciĝos", "regenconf_failed": "Ne povis regeneri la agordon por kategorio(j): {categories}", "domain_uninstall_app_first": "Unu aŭ pluraj programoj estas instalitaj en ĉi tiu domajno. Bonvolu malinstali ilin antaŭ ol daŭrigi la domajnan forigon", - "service_unknown": "Nekonata servo '{service:s}'", + "service_unknown": "Nekonata servo '{service}'", "domain_deletion_failed": "Ne eblas forigi domajnon {domain}: {error}", "log_user_update": "Ĝisdatigu uzantinformojn de '{}'", "user_creation_failed": "Ne povis krei uzanton {user}: {error}", @@ -370,34 +370,34 @@ "done": "Farita", "log_domain_remove": "Forigi domon '{}' de agordo de sistemo", "hook_list_by_invalid": "Ĉi tiu posedaĵo ne povas esti uzata por listigi hokojn", - "confirm_app_install_thirdparty": "Danĝero! Ĉi tiu apliko ne estas parto de la aplika katalogo de Yunohost. Instali triajn aplikojn povas kompromiti la integrecon kaj sekurecon de via sistemo. Vi probable ne devas instali ĝin krom se vi scias kion vi faras. NENIU SUBTENO estos provizita se ĉi tiu app ne funkcias aŭ rompas vian sistemon ... Se vi pretas riski ĉiuokaze, tajpu '{answers:s}'", + "confirm_app_install_thirdparty": "Danĝero! Ĉi tiu apliko ne estas parto de la aplika katalogo de Yunohost. Instali triajn aplikojn povas kompromiti la integrecon kaj sekurecon de via sistemo. Vi probable ne devas instali ĝin krom se vi scias kion vi faras. NENIU SUBTENO estos provizita se ĉi tiu app ne funkcias aŭ rompas vian sistemon ... Se vi pretas riski ĉiuokaze, tajpu '{answers}'", "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Permesu uzon de (malaktuala) DSA-hostkey por la agordo de daemon SSH", - "dyndns_domain_not_provided": "Provizanto DynDNS {provider:s} ne povas provizi domajnon {domain:s}.", + "dyndns_domain_not_provided": "Provizanto DynDNS {provider} ne povas provizi domajnon {domain}.", "backup_unable_to_organize_files": "Ne povis uzi la rapidan metodon por organizi dosierojn en la ar archiveivo", "password_too_simple_2": "La pasvorto bezonas almenaŭ 8 signojn kaj enhavas ciferon, majusklojn kaj minusklojn", - "service_cmd_exec_failed": "Ne povis plenumi la komandon '{command:s}'", + "service_cmd_exec_failed": "Ne povis plenumi la komandon '{command}'", "pattern_lastname": "Devas esti valida familinomo", - "service_enabled": "La servo '{service:s}' nun aŭtomate komenciĝos dum sistemaj botoj.", - "certmanager_no_cert_file": "Ne povis legi la atestan dosieron por la domajno {domain:s} (dosiero: {file:s})", + "service_enabled": "La servo '{service}' nun aŭtomate komenciĝos dum sistemaj botoj.", + "certmanager_no_cert_file": "Ne povis legi la atestan dosieron por la domajno {domain} (dosiero: {file})", "domain_creation_failed": "Ne eblas krei domajnon {domain}: {error}", - "certmanager_domain_http_not_working": "Ŝajnas ke la domajno {domain:s} ne atingeblas per HTTP. Kontrolu, ke via DNS kaj NGINX-agordo ĝustas", - "domain_cannot_remove_main": "Vi ne povas forigi '{domain:s}' ĉar ĝi estas la ĉefa domajno, vi bezonas unue agordi alian domajnon kiel la ĉefan domajnon per uzado de 'yunohost domain main-domain -n ', jen la listo de kandidataj domajnoj. : {other_domains:s}", - "service_reloaded_or_restarted": "La servo '{service:s}' estis reŝarĝita aŭ rekomencita", + "certmanager_domain_http_not_working": "Ŝajnas ke la domajno {domain} ne atingeblas per HTTP. Kontrolu, ke via DNS kaj NGINX-agordo ĝustas", + "domain_cannot_remove_main": "Vi ne povas forigi '{domain}' ĉar ĝi estas la ĉefa domajno, vi bezonas unue agordi alian domajnon kiel la ĉefan domajnon per uzado de 'yunohost domain main-domain -n ', jen la listo de kandidataj domajnoj. : {other_domains}", + "service_reloaded_or_restarted": "La servo '{service}' estis reŝarĝita aŭ rekomencita", "log_domain_add": "Aldonu '{}' domajnon en sisteman agordon", - "global_settings_bad_type_for_setting": "Malbona tipo por agordo {setting:s}, ricevita {received_type:s}, atendata {expected_type:s}", + "global_settings_bad_type_for_setting": "Malbona tipo por agordo {setting}, ricevita {received_type}, atendata {expected_type}", "unlimit": "Neniu kvoto", "system_username_exists": "Uzantnomo jam ekzistas en la listo de uzantoj de sistemo", "firewall_reloaded": "Fajroŝirmilo reŝarĝis", - "service_restarted": "Servo '{service:s}' rekomencis", + "service_restarted": "Servo '{service}' rekomencis", "pattern_username": "Devas esti minuskulaj literoj kaj minuskloj nur", "extracting": "Eltirante…", - "app_restore_failed": "Ne povis restarigi la programon '{app:s}': {error:s}", + "app_restore_failed": "Ne povis restarigi la programon '{app}': {error}", "yunohost_configured": "YunoHost nun estas agordita", - "certmanager_self_ca_conf_file_not_found": "Ne povis trovi agorddosieron por mem-subskriba aŭtoritato (dosiero: {file:s})", + "certmanager_self_ca_conf_file_not_found": "Ne povis trovi agorddosieron por mem-subskriba aŭtoritato (dosiero: {file})", "log_app_remove": "Forigu la aplikon '{}'", - "service_restart_failed": "Ne povis rekomenci la servon '{service:s}'\n\nLastatempaj servaj protokoloj: {logs:s}", + "service_restart_failed": "Ne povis rekomenci la servon '{service}'\n\nLastatempaj servaj protokoloj: {logs}", "firewall_rules_cmd_failed": "Iuj komandoj pri fajroŝirmilo malsukcesis. Pliaj informoj en ensaluto.", - "certmanager_certificate_fetching_or_enabling_failed": "Provante uzi la novan atestilon por {domain:s} ne funkciis …", + "certmanager_certificate_fetching_or_enabling_failed": "Provante uzi la novan atestilon por {domain} ne funkciis …", "app_full_domain_unavailable": "Bedaŭrinde, ĉi tiu app devas esti instalita sur propra domajno, sed aliaj programoj jam estas instalitaj sur la domajno '{domain}'. Vi povus uzi subdominon dediĉitan al ĉi tiu app anstataŭe.", "group_cannot_edit_all_users": "La grupo 'all_users' ne povas esti redaktita permane. Ĝi estas speciala grupo celita enhavi ĉiujn uzantojn registritajn en YunoHost", "group_cannot_edit_visitors": "La grupo 'vizitantoj' ne povas esti redaktita permane. Ĝi estas speciala grupo reprezentanta anonimajn vizitantojn", @@ -484,7 +484,7 @@ "diagnosis_http_could_not_diagnose_details": "Eraro: {error}", "diagnosis_http_ok": "Domajno {domain} atingebla per HTTP de ekster la loka reto.", "diagnosis_http_unreachable": "Domajno {domain} ŝajnas neatingebla per HTTP de ekster la loka reto.", - "domain_cannot_remove_main_add_new_one": "Vi ne povas forigi '{domain:s}' ĉar ĝi estas la ĉefa domajno kaj via sola domajno, vi devas unue aldoni alian domajnon uzante ''yunohost domain add ', tiam agordi kiel ĉefan domajnon uzante 'yunohost domain main-domain -n ' kaj tiam vi povas forigi la domajnon' {domain:s} 'uzante' yunohost domain remove {domain:s} '.'", + "domain_cannot_remove_main_add_new_one": "Vi ne povas forigi '{domain}' ĉar ĝi estas la ĉefa domajno kaj via sola domajno, vi devas unue aldoni alian domajnon uzante ''yunohost domain add ', tiam agordi kiel ĉefan domajnon uzante 'yunohost domain main-domain -n ' kaj tiam vi povas forigi la domajnon' {domain} 'uzante' yunohost domain remove {domain} '.'", "permission_require_account": "Permesilo {permission} nur havas sencon por uzantoj, kiuj havas konton, kaj tial ne rajtas esti ebligitaj por vizitantoj.", "diagnosis_found_warnings": "Trovitaj {warnings} ero (j) kiuj povus esti plibonigitaj por {category}.", "diagnosis_everything_ok": "Ĉio aspektas bone por {category}!", @@ -495,7 +495,7 @@ "log_app_config_show_panel": "Montri la agordan panelon de la apliko '{}'", "log_app_config_apply": "Apliki agordon al la apliko '{}'", "diagnosis_never_ran_yet": "Ŝajnas, ke ĉi tiu servilo estis instalita antaŭ nelonge kaj estas neniu diagnoza raporto por montri. Vi devas komenci kurante plenan diagnozon, ĉu de la retadministro aŭ uzante 'yunohost diagnosis run' el la komandlinio.", - "certmanager_warning_subdomain_dns_record": "Subdominio '{subdomain:s}' ne solvas al la sama IP-adreso kiel '{domain:s}'. Iuj funkcioj ne estos haveblaj ĝis vi riparos ĉi tion kaj regeneros la atestilon.", + "certmanager_warning_subdomain_dns_record": "Subdominio '{subdomain}' ne solvas al la sama IP-adreso kiel '{domain}'. Iuj funkcioj ne estos haveblaj ĝis vi riparos ĉi tion kaj regeneros la atestilon.", "diagnosis_basesystem_hardware": "Arkitekturo de servila aparataro estas {virt} {arch}", "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.", @@ -550,6 +550,6 @@ "app_manifest_install_ask_domain": "Elektu la domajnon, kie ĉi tiu programo devas esti instalita", "app_label_deprecated": "Ĉi tiu komando estas malrekomendita! Bonvolu uzi la novan komandon 'yunohost user permission update' por administri la app etikedo.", "app_argument_password_no_default": "Eraro dum analiza pasvorta argumento '{name}': pasvorta argumento ne povas havi defaŭltan valoron por sekureca kialo", - "additional_urls_already_removed": "Plia URL '{url:s}' jam forigita en la aldona URL por permeso '{permission:s}'", - "additional_urls_already_added": "Plia URL '{url:s}' jam aldonita en la aldona URL por permeso '{permission:s}'" + "additional_urls_already_removed": "Plia URL '{url}' jam forigita en la aldona URL por permeso '{permission}'", + "additional_urls_already_added": "Plia URL '{url}' jam aldonita en la aldona URL por permeso '{permission}'" } diff --git a/locales/es.json b/locales/es.json index b057ae54c..73b9b9ae1 100644 --- a/locales/es.json +++ b/locales/es.json @@ -1,49 +1,49 @@ { - "action_invalid": "Acción no válida '{action:s} 1'", + "action_invalid": "Acción no válida '{action} 1'", "admin_password": "Contraseña administrativa", "admin_password_change_failed": "No se pudo cambiar la contraseña", "admin_password_changed": "La contraseña de administración fue cambiada", - "app_already_installed": "{app:s} ya está instalada", - "app_argument_choice_invalid": "Use una de estas opciones «{choices:s}» para el argumento «{name:s}»", - "app_argument_invalid": "Elija un valor válido para el argumento «{name:s}»: {error:s}", - "app_argument_required": "Se requiere el argumento '{name:s} 7'", + "app_already_installed": "{app} ya está instalada", + "app_argument_choice_invalid": "Use una de estas opciones «{choices}» para el argumento «{name}»", + "app_argument_invalid": "Elija un valor válido para el argumento «{name}»: {error}", + "app_argument_required": "Se requiere el argumento '{name} 7'", "app_extraction_failed": "No se pudieron extraer los archivos de instalación", "app_id_invalid": "ID de la aplicación no válida", "app_install_files_invalid": "Estos archivos no se pueden instalar", "app_manifest_invalid": "Algo va mal con el manifiesto de la aplicación: {error}", - "app_not_correctly_installed": "La aplicación {app:s} 8 parece estar incorrectamente instalada", - "app_not_installed": "No se pudo encontrar «{app:s}» en la lista de aplicaciones instaladas: {all_apps}", - "app_not_properly_removed": "La {app:s} 0 no ha sido desinstalada correctamente", - "app_removed": "Eliminado {app:s}", + "app_not_correctly_installed": "La aplicación {app} 8 parece estar incorrectamente instalada", + "app_not_installed": "No se pudo encontrar «{app}» en la lista de aplicaciones instaladas: {all_apps}", + "app_not_properly_removed": "La {app} 0 no ha sido desinstalada correctamente", + "app_removed": "Eliminado {app}", "app_requirements_checking": "Comprobando los paquetes necesarios para {app}…", "app_requirements_unmeet": "No se cumplen los requisitos para {app}, el paquete {pkgname} ({version}) debe ser {spec}", "app_sources_fetch_failed": "No se pudieron obtener los archivos con el código fuente, ¿es el URL correcto?", "app_unknown": "Aplicación desconocida", "app_unsupported_remote_type": "Tipo remoto no soportado por la aplicación", - "app_upgrade_failed": "No se pudo actualizar {app:s}: {error}", - "app_upgraded": "Actualizado {app:s}", + "app_upgrade_failed": "No se pudo actualizar {app}: {error}", + "app_upgraded": "Actualizado {app}", "ask_firstname": "Nombre", "ask_lastname": "Apellido", "ask_main_domain": "Dominio principal", "ask_new_admin_password": "Nueva contraseña administrativa", "ask_password": "Contraseña", - "backup_app_failed": "No se pudo respaldar «{app:s}»", - "backup_archive_app_not_found": "No se pudo encontrar «{app:s}» en el archivo de respaldo", + "backup_app_failed": "No se pudo respaldar «{app}»", + "backup_archive_app_not_found": "No se pudo encontrar «{app}» en el archivo de respaldo", "backup_archive_name_exists": "Ya existe un archivo de respaldo con este nombre.", - "backup_archive_name_unknown": "Copia de seguridad local desconocida '{name:s}'", + "backup_archive_name_unknown": "Copia de seguridad local desconocida '{name}'", "backup_archive_open_failed": "No se pudo abrir el archivo de respaldo", "backup_cleaning_failed": "No se pudo limpiar la carpeta de respaldo temporal", "backup_created": "Se ha creado la copia de seguridad", "backup_creation_failed": "No se pudo crear el archivo de respaldo", - "backup_delete_error": "No se pudo eliminar «{path:s}»", + "backup_delete_error": "No se pudo eliminar «{path}»", "backup_deleted": "Eliminada la copia de seguridad", - "backup_hook_unknown": "El gancho «{hook:s}» de la copia de seguridad es desconocido", + "backup_hook_unknown": "El gancho «{hook}» de la copia de seguridad es desconocido", "backup_nothings_done": "Nada que guardar", "backup_output_directory_forbidden": "Elija un directorio de salida diferente. Las copias de seguridad no se pueden crear en /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var o /home/yunohost.backup/archives subcarpetas", "backup_output_directory_not_empty": "Debe elegir un directorio de salida vacío", "backup_output_directory_required": "Debe proporcionar un directorio de salida para la copia de seguridad", "backup_running_hooks": "Ejecutando los hooks de copia de respaldo...", - "custom_app_url_required": "Debe proporcionar una URL para actualizar su aplicación personalizada {app:s}", + "custom_app_url_required": "Debe proporcionar una URL para actualizar su aplicación personalizada {app}", "domain_cert_gen_failed": "No se pudo generar el certificado", "domain_created": "Dominio creado", "domain_creation_failed": "No se puede crear el dominio {domain}: {error}", @@ -62,26 +62,26 @@ "dyndns_key_not_found": "No se ha encontrado la clave DNS para el dominio", "dyndns_no_domain_registered": "Ningún dominio registrado con DynDNS", "dyndns_registered": "Registrado dominio de DynDNS", - "dyndns_registration_failed": "No se pudo registrar el dominio de DynDNS: {error:s}", - "dyndns_unavailable": "El dominio «{domain:s}» no está disponible.", + "dyndns_registration_failed": "No se pudo registrar el dominio de DynDNS: {error}", + "dyndns_unavailable": "El dominio «{domain}» no está disponible.", "extracting": "Extrayendo…", - "field_invalid": "Campo no válido '{:s}'", + "field_invalid": "Campo no válido '{}'", "firewall_reload_failed": "No se pudo recargar el cortafuegos", "firewall_reloaded": "Cortafuegos recargado", "firewall_rules_cmd_failed": "Algunos comandos para aplicar reglas del cortafuegos han fallado. Más información en el registro.", - "hook_exec_failed": "No se pudo ejecutar el guión: {path:s}", - "hook_exec_not_terminated": "El guión no terminó correctamente:{path:s}", + "hook_exec_failed": "No se pudo ejecutar el guión: {path}", + "hook_exec_not_terminated": "El guión no terminó correctamente:{path}", "hook_list_by_invalid": "Esta propiedad no se puede usar para enumerar ganchos («hooks»)", - "hook_name_unknown": "Nombre de hook desconocido '{name:s}'", + "hook_name_unknown": "Nombre de hook desconocido '{name}'", "installation_complete": "Instalación finalizada", "ip6tables_unavailable": "No puede modificar ip6tables aquí. O bien está en un 'container' o su kernel no soporta esta opción", "iptables_unavailable": "No puede modificar iptables aquí. O bien está en un 'container' o su kernel no soporta esta opción", - "mail_alias_remove_failed": "No se pudo eliminar el alias de correo «{mail:s}»", - "mail_domain_unknown": "Dirección de correo no válida para el dominio «{domain:s}». Use un dominio administrado por este servidor.", - "mail_forward_remove_failed": "No se pudo eliminar el reenvío de correo «{mail:s}»", + "mail_alias_remove_failed": "No se pudo eliminar el alias de correo «{mail}»", + "mail_domain_unknown": "Dirección de correo no válida para el dominio «{domain}». Use un dominio administrado por este servidor.", + "mail_forward_remove_failed": "No se pudo eliminar el reenvío de correo «{mail}»", "main_domain_change_failed": "No se pudo cambiar el dominio principal", "main_domain_changed": "El dominio principal ha cambiado", - "not_enough_disk_space": "No hay espacio libre suficiente en «{path:s}»", + "not_enough_disk_space": "No hay espacio libre suficiente en «{path}»", "packages_upgrade_failed": "No se pudieron actualizar todos los paquetes", "pattern_backup_archive_name": "Debe ser un nombre de archivo válido con un máximo de 30 caracteres, solo se admiten caracteres alfanuméricos y los caracteres -_. (guiones y punto)", "pattern_domain": "El nombre de dominio debe ser válido (por ejemplo mi-dominio.org)", @@ -93,42 +93,42 @@ "pattern_port_or_range": "Debe ser un número de puerto válido (es decir entre 0-65535) o un intervalo de puertos (por ejemplo 100:200)", "pattern_positive_number": "Deber ser un número positivo", "pattern_username": "Solo puede contener caracteres alfanuméricos o el guión bajo", - "port_already_closed": "El puerto {port:d} ya está cerrado para las conexiones {ip_version:s}", - "port_already_opened": "El puerto {port:d} ya está abierto para las conexiones {ip_version:s}", - "restore_already_installed_app": "Una aplicación con el ID «{app:s}» ya está instalada", - "app_restore_failed": "No se pudo restaurar la aplicación «{app:s}»: {error:s}", + "port_already_closed": "El puerto {port:d} ya está cerrado para las conexiones {ip_version}", + "port_already_opened": "El puerto {port:d} ya está abierto para las conexiones {ip_version}", + "restore_already_installed_app": "Una aplicación con el ID «{app}» ya está instalada", + "app_restore_failed": "No se pudo restaurar la aplicación «{app}»: {error}", "restore_cleaning_failed": "No se pudo limpiar el directorio temporal de restauración", "restore_complete": "Restaurada", - "restore_confirm_yunohost_installed": "¿Realmente desea restaurar un sistema ya instalado? [{answers:s}]", + "restore_confirm_yunohost_installed": "¿Realmente desea restaurar un sistema ya instalado? [{answers}]", "restore_failed": "No se pudo restaurar el sistema", - "restore_hook_unavailable": "El script de restauración para «{part:s}» no está disponible en su sistema y tampoco en el archivo", + "restore_hook_unavailable": "El script de restauración para «{part}» no está disponible en su sistema y tampoco en el archivo", "restore_nothings_done": "No se ha restaurado nada", - "restore_running_app_script": "Restaurando la aplicación «{app:s}»…", + "restore_running_app_script": "Restaurando la aplicación «{app}»…", "restore_running_hooks": "Ejecutando los ganchos de restauración…", - "service_add_failed": "No se pudo añadir el servicio «{service:s}»", - "service_added": "Se agregó el servicio '{service:s}'", - "service_already_started": "El servicio «{service:s}» ya está funcionando", - "service_already_stopped": "El servicio «{service:s}» ya ha sido detenido", - "service_cmd_exec_failed": "No se pudo ejecutar la orden «{command:s}»", - "service_disable_failed": "No se pudo hacer que el servicio '{service:s}' no se iniciara en el arranque.\n\nRegistros de servicio recientes: {logs:s}", - "service_disabled": "El servicio '{service:s}' ya no se iniciará cuando se inicie el sistema.", - "service_enable_failed": "No se pudo hacer que el servicio '{service:s}' se inicie automáticamente en el arranque.\n\nRegistros de servicio recientes: {logs s}", - "service_enabled": "El servicio '{service:s}' ahora se iniciará automáticamente durante el arranque del sistema.", - "service_remove_failed": "No se pudo eliminar el servicio «{service:s}»", - "service_removed": "Servicio '{service:s}' eliminado", - "service_start_failed": "No se pudo iniciar el servicio «{service:s}»\n\nRegistro de servicios recientes:{logs:s}", - "service_started": "El servicio '{service:s}' comenzó", - "service_stop_failed": "No se pudo detener el servicio «{service:s}»\n\nRegistro de servicios recientes:{logs:s}", - "service_stopped": "Servicio '{service:s}' detenido", - "service_unknown": "Servicio desconocido '{service:s}'", + "service_add_failed": "No se pudo añadir el servicio «{service}»", + "service_added": "Se agregó el servicio '{service}'", + "service_already_started": "El servicio «{service}» ya está funcionando", + "service_already_stopped": "El servicio «{service}» ya ha sido detenido", + "service_cmd_exec_failed": "No se pudo ejecutar la orden «{command}»", + "service_disable_failed": "No se pudo hacer que el servicio '{service}' no se iniciara en el arranque.\n\nRegistros de servicio recientes: {logs}", + "service_disabled": "El servicio '{service}' ya no se iniciará cuando se inicie el sistema.", + "service_enable_failed": "No se pudo hacer que el servicio '{service}' se inicie automáticamente en el arranque.\n\nRegistros de servicio recientes: {logs s}", + "service_enabled": "El servicio '{service}' ahora se iniciará automáticamente durante el arranque del sistema.", + "service_remove_failed": "No se pudo eliminar el servicio «{service}»", + "service_removed": "Servicio '{service}' eliminado", + "service_start_failed": "No se pudo iniciar el servicio «{service}»\n\nRegistro de servicios recientes:{logs}", + "service_started": "El servicio '{service}' comenzó", + "service_stop_failed": "No se pudo detener el servicio «{service}»\n\nRegistro de servicios recientes:{logs}", + "service_stopped": "Servicio '{service}' detenido", + "service_unknown": "Servicio desconocido '{service}'", "ssowat_conf_generated": "Generada la configuración de SSOwat", "ssowat_conf_updated": "Actualizada la configuración de SSOwat", "system_upgraded": "Sistema actualizado", "system_username_exists": "El nombre de usuario ya existe en la lista de usuarios del sistema", - "unbackup_app": "La aplicación '{app:s}' no se guardará", + "unbackup_app": "La aplicación '{app}' no se guardará", "unexpected_error": "Algo inesperado salió mal: {error}", "unlimit": "Sin cuota", - "unrestore_app": "La aplicación '{app:s}' no será restaurada", + "unrestore_app": "La aplicación '{app}' no será restaurada", "updating_apt_cache": "Obteniendo las actualizaciones disponibles para los paquetes del sistema…", "upgrade_complete": "Actualización finalizada", "upgrading_packages": "Actualizando paquetes…", @@ -141,7 +141,7 @@ "user_deleted": "Usuario eliminado", "user_deletion_failed": "No se pudo eliminar el usuario {user}: {error}", "user_home_creation_failed": "No se pudo crear la carpeta «home» para el usuario", - "user_unknown": "Usuario desconocido: {user:s}", + "user_unknown": "Usuario desconocido: {user}", "user_update_failed": "No se pudo actualizar el usuario {user}: {error}", "user_updated": "Cambiada la información de usuario", "yunohost_already_installed": "YunoHost ya está instalado", @@ -149,56 +149,56 @@ "yunohost_installing": "Instalando YunoHost…", "yunohost_not_installed": "YunoHost no está correctamente instalado. Ejecute «yunohost tools postinstall»", "mailbox_used_space_dovecot_down": "El servicio de buzón Dovecot debe estar activo si desea recuperar el espacio usado del buzón", - "certmanager_attempt_to_replace_valid_cert": "Está intentando sobrescribir un certificado correcto y válido para el dominio {domain:s}! (Use --force para omitir este mensaje)", - "certmanager_domain_cert_not_selfsigned": "El certificado para el dominio {domain:s} no es un certificado autofirmado. ¿Está seguro de que quiere reemplazarlo? (Use «--force» para hacerlo)", - "certmanager_certificate_fetching_or_enabling_failed": "El intento de usar el nuevo certificado para {domain:s} no ha funcionado…", - "certmanager_attempt_to_renew_nonLE_cert": "El certificado para el dominio «{domain:s}» no ha sido emitido por Let's Encrypt. ¡No se puede renovar automáticamente!", - "certmanager_attempt_to_renew_valid_cert": "¡El certificado para el dominio «{domain:s}» no está a punto de expirar! (Puede usar --force si sabe lo que está haciendo)", - "certmanager_domain_http_not_working": "Parece que no se puede acceder al dominio {domain:s} a través de HTTP. Por favor compruebe en los diagnósticos la categoría 'Web'para más información. (Si sabe lo que está haciendo, utilice '--no-checks' para no realizar estas comprobaciones.)", - "certmanager_domain_dns_ip_differs_from_public_ip": "El registro DNS 'A' para el dominio '{domain:s}' es diferente de la IP de este servidor. Por favor comprueba los 'registros DNS' (básicos) la categoría de diagnósticos para mayor información. Si recientemente modificó su registro 'A', espere a que se propague (algunos verificadores de propagación de DNS están disponibles en línea). (Si sabe lo que está haciendo, use '--no-checks' para desactivar esos cheques)", - "certmanager_cannot_read_cert": "Se ha producido un error al intentar abrir el certificado actual para el dominio {domain:s} (archivo: {file:s}), razón: {reason:s}", - "certmanager_cert_install_success_selfsigned": "Instalado correctamente un certificado autofirmado para el dominio «{domain:s}»", - "certmanager_cert_install_success": "Instalado correctamente un certificado de Let's Encrypt para el dominio «{domain:s}»", - "certmanager_cert_renew_success": "Renovado correctamente el certificado de Let's Encrypt para el dominio «{domain:s}»", - "certmanager_hit_rate_limit": "Se han emitido demasiados certificados recientemente para este conjunto exacto de dominios {domain:s}. Pruebe de nuevo más tarde. Vea para más detalles https://letsencrypt.org/docs/rate-limits/", + "certmanager_attempt_to_replace_valid_cert": "Está intentando sobrescribir un certificado correcto y válido para el dominio {domain}! (Use --force para omitir este mensaje)", + "certmanager_domain_cert_not_selfsigned": "El certificado para el dominio {domain} no es un certificado autofirmado. ¿Está seguro de que quiere reemplazarlo? (Use «--force» para hacerlo)", + "certmanager_certificate_fetching_or_enabling_failed": "El intento de usar el nuevo certificado para {domain} no ha funcionado…", + "certmanager_attempt_to_renew_nonLE_cert": "El certificado para el dominio «{domain}» no ha sido emitido por Let's Encrypt. ¡No se puede renovar automáticamente!", + "certmanager_attempt_to_renew_valid_cert": "¡El certificado para el dominio «{domain}» no está a punto de expirar! (Puede usar --force si sabe lo que está haciendo)", + "certmanager_domain_http_not_working": "Parece que no se puede acceder al dominio {domain} a través de HTTP. Por favor compruebe en los diagnósticos la categoría 'Web'para más información. (Si sabe lo que está haciendo, utilice '--no-checks' para no realizar estas comprobaciones.)", + "certmanager_domain_dns_ip_differs_from_public_ip": "El registro DNS 'A' para el dominio '{domain}' es diferente de la IP de este servidor. Por favor comprueba los 'registros DNS' (básicos) la categoría de diagnósticos para mayor información. Si recientemente modificó su registro 'A', espere a que se propague (algunos verificadores de propagación de DNS están disponibles en línea). (Si sabe lo que está haciendo, use '--no-checks' para desactivar esos cheques)", + "certmanager_cannot_read_cert": "Se ha producido un error al intentar abrir el certificado actual para el dominio {domain} (archivo: {file}), razón: {reason}", + "certmanager_cert_install_success_selfsigned": "Instalado correctamente un certificado autofirmado para el dominio «{domain}»", + "certmanager_cert_install_success": "Instalado correctamente un certificado de Let's Encrypt para el dominio «{domain}»", + "certmanager_cert_renew_success": "Renovado correctamente el certificado de Let's Encrypt para el dominio «{domain}»", + "certmanager_hit_rate_limit": "Se han emitido demasiados certificados recientemente para este conjunto exacto de dominios {domain}. Pruebe de nuevo más tarde. Vea para más detalles https://letsencrypt.org/docs/rate-limits/", "certmanager_cert_signing_failed": "No se pudo firmar el nuevo certificado", - "certmanager_no_cert_file": "No se pudo leer el certificado para el dominio {domain:s} (archivo: {file:s})", - "domain_cannot_remove_main": "No puede eliminar '{domain:s}' ya que es el dominio principal, primero debe configurar otro dominio como el dominio principal usando 'yunohost domain main-domain -n '; Aquí está la lista de dominios candidatos: {other_domains:s}", - "certmanager_self_ca_conf_file_not_found": "No se pudo encontrar el archivo de configuración para la autoridad de autofirma (archivo: {file:s})", - "certmanager_unable_to_parse_self_CA_name": "No se pudo procesar el nombre de la autoridad de autofirma (archivo: {file:s})", + "certmanager_no_cert_file": "No se pudo leer el certificado para el dominio {domain} (archivo: {file})", + "domain_cannot_remove_main": "No puede eliminar '{domain}' ya que es el dominio principal, primero debe configurar otro dominio como el dominio principal usando 'yunohost domain main-domain -n '; Aquí está la lista de dominios candidatos: {other_domains}", + "certmanager_self_ca_conf_file_not_found": "No se pudo encontrar el archivo de configuración para la autoridad de autofirma (archivo: {file})", + "certmanager_unable_to_parse_self_CA_name": "No se pudo procesar el nombre de la autoridad de autofirma (archivo: {file})", "domains_available": "Dominios disponibles:", - "backup_archive_broken_link": "No se pudo acceder al archivo de respaldo (enlace roto a {path:s})", + "backup_archive_broken_link": "No se pudo acceder al archivo de respaldo (enlace roto a {path})", "certmanager_acme_not_configured_for_domain": "El reto ACME no ha podido ser realizado para {domain} porque su configuración de nginx no tiene el el código correcto... Por favor, asegurate que la configuración de nginx es correcta ejecutando en el terminal `yunohost tools regen-conf nginx --dry-run --with-diff`.", "domain_hostname_failed": "No se pudo establecer un nuevo nombre de anfitrión («hostname»). Esto podría causar problemas más tarde (no es seguro... podría ir bien).", "app_already_installed_cant_change_url": "Esta aplicación ya está instalada. La URL no se puede cambiar solo con esta función. Marque `app changeurl` si está disponible.", - "app_change_url_failed_nginx_reload": "No se pudo recargar NGINX. Esta es la salida de «nginx -t»:\n{nginx_errors:s}", - "app_change_url_identical_domains": "El antiguo y nuevo dominio/url_path son idénticos ('{domain:s} {path:s}'), no se realizarán cambios.", - "app_change_url_no_script": "La aplicación «{app_name:s}» aún no permite la modificación de URLs. Quizás debería actualizarla.", - "app_change_url_success": "El URL de la aplicación {app:s} es ahora {domain:s} {path:s}", - "app_location_unavailable": "Este URL o no está disponible o está en conflicto con otra(s) aplicación(es) instalada(s):\n{apps:s}", - "app_already_up_to_date": "La aplicación {app:s} ya está actualizada", + "app_change_url_failed_nginx_reload": "No se pudo recargar NGINX. Esta es la salida de «nginx -t»:\n{nginx_errors}", + "app_change_url_identical_domains": "El antiguo y nuevo dominio/url_path son idénticos ('{domain} {path}'), no se realizarán cambios.", + "app_change_url_no_script": "La aplicación «{app_name}» aún no permite la modificación de URLs. Quizás debería actualizarla.", + "app_change_url_success": "El URL de la aplicación {app} es ahora {domain} {path}", + "app_location_unavailable": "Este URL o no está disponible o está en conflicto con otra(s) aplicación(es) instalada(s):\n{apps}", + "app_already_up_to_date": "La aplicación {app} ya está actualizada", "app_upgrade_some_app_failed": "No se pudieron actualizar algunas aplicaciones", "app_make_default_location_already_used": "No pudo hacer que la aplicación «{app}» sea la predeterminada en el dominio, «{domain}» ya está siendo usado por la aplicación «{other_app}»", "app_upgrade_app_name": "Ahora actualizando {app}…", "backup_abstract_method": "Este método de respaldo aún no se ha implementado", "backup_applying_method_copy": "Copiando todos los archivos en la copia de respaldo…", - "backup_applying_method_custom": "Llamando al método de copia de seguridad personalizado «{method:s}»…", + "backup_applying_method_custom": "Llamando al método de copia de seguridad personalizado «{method}»…", "backup_applying_method_tar": "Creando el archivo TAR de respaldo…", - "backup_archive_system_part_not_available": "La parte del sistema «{part:s}» no está disponible en esta copia de seguridad", - "backup_archive_writing_error": "No se pudieron añadir los archivos «{source:s}» (llamados en el archivo «{dest:s}») para ser respaldados en el archivo comprimido «{archive:s}»", - "backup_ask_for_copying_if_needed": "¿Quiere realizar la copia de seguridad usando {size:s}MB temporalmente? (Se usa este modo ya que algunos archivos no se pudieron preparar usando un método más eficiente.)", + "backup_archive_system_part_not_available": "La parte del sistema «{part}» no está disponible en esta copia de seguridad", + "backup_archive_writing_error": "No se pudieron añadir los archivos «{source}» (llamados en el archivo «{dest}») para ser respaldados en el archivo comprimido «{archive}»", + "backup_ask_for_copying_if_needed": "¿Quiere realizar la copia de seguridad usando {size}MB temporalmente? (Se usa este modo ya que algunos archivos no se pudieron preparar usando un método más eficiente.)", "backup_cant_mount_uncompress_archive": "No se pudo montar el archivo descomprimido como protegido contra escritura", - "backup_copying_to_organize_the_archive": "Copiando {size:s}MB para organizar el archivo", - "backup_couldnt_bind": "No se pudo enlazar {src:s} con {dest:s}.", + "backup_copying_to_organize_the_archive": "Copiando {size}MB para organizar el archivo", + "backup_couldnt_bind": "No se pudo enlazar {src} con {dest}.", "backup_csv_addition_failed": "No se pudo añadir archivos para respaldar en el archivo CSV", "backup_csv_creation_failed": "No se pudo crear el archivo CSV necesario para la restauración", "backup_custom_mount_error": "El método de respaldo personalizado no pudo superar el paso «mount»", "backup_no_uncompress_archive_dir": "No existe tal directorio de archivos sin comprimir", - "backup_system_part_failed": "No se pudo respaldar la parte del sistema «{part:s}»", - "backup_with_no_backup_script_for_app": "La aplicación «{app:s}» no tiene un guión de respaldo. Omitiendo.", - "backup_with_no_restore_script_for_app": "«{app:s}» no tiene un script de restauración, no podá restaurar automáticamente la copia de seguridad de esta aplicación.", - "dyndns_could_not_check_provide": "No se pudo verificar si {provider:s} puede ofrecer {domain:s}.", - "dyndns_domain_not_provided": "El proveedor de DynDNS {provider:s} no puede proporcionar el dominio {domain:s}.", + "backup_system_part_failed": "No se pudo respaldar la parte del sistema «{part}»", + "backup_with_no_backup_script_for_app": "La aplicación «{app}» no tiene un guión de respaldo. Omitiendo.", + "backup_with_no_restore_script_for_app": "«{app}» no tiene un script de restauración, no podá restaurar automáticamente la copia de seguridad de esta aplicación.", + "dyndns_could_not_check_provide": "No se pudo verificar si {provider} puede ofrecer {domain}.", + "dyndns_domain_not_provided": "El proveedor de DynDNS {provider} no puede proporcionar el dominio {domain}.", "experimental_feature": "Aviso : esta funcionalidad es experimental y no se considera estable, no debería usarla a menos que sepa lo que está haciendo.", "good_practices_about_user_password": "Ahora está a punto de definir una nueva contraseña de usuario. La contraseña debe tener al menos 8 caracteres, aunque es una buena práctica usar una contraseña más larga (es decir, una frase de contraseña) y / o una variación de caracteres (mayúsculas, minúsculas, dígitos y caracteres especiales).", "password_listed": "Esta contraseña se encuentra entre las contraseñas más utilizadas en el mundo. Por favor, elija algo más único.", @@ -218,12 +218,12 @@ "tools_upgrade_cant_both": "No se puede actualizar el sistema y las aplicaciones al mismo tiempo", "tools_upgrade_at_least_one": "Especifique «--apps», o «--system»", "this_action_broke_dpkg": "Esta acción rompió dpkg/APT(los gestores de paquetes del sistema)… Puede tratar de solucionar este problema conectando mediante SSH y ejecutando `sudo dpkg --configure -a`.", - "service_reloaded_or_restarted": "El servicio '{service:s}' fue recargado o reiniciado", - "service_reload_or_restart_failed": "No se pudo recargar o reiniciar el servicio «{service:s}»\n\nRegistro de servicios recientes:{logs:s}", - "service_restarted": "Servicio '{service:s}' reiniciado", - "service_restart_failed": "No se pudo reiniciar el servicio «{service:s}»\n\nRegistro de servicios recientes:{logs:s}", - "service_reloaded": "Servicio '{service:s}' recargado", - "service_reload_failed": "No se pudo recargar el servicio «{service:s}»\n\nRegistro de servicios recientes:{logs:s}", + "service_reloaded_or_restarted": "El servicio '{service}' fue recargado o reiniciado", + "service_reload_or_restart_failed": "No se pudo recargar o reiniciar el servicio «{service}»\n\nRegistro de servicios recientes:{logs}", + "service_restarted": "Servicio '{service}' reiniciado", + "service_restart_failed": "No se pudo reiniciar el servicio «{service}»\n\nRegistro de servicios recientes:{logs}", + "service_reloaded": "Servicio '{service}' recargado", + "service_reload_failed": "No se pudo recargar el servicio «{service}»\n\nRegistro de servicios recientes:{logs}", "service_regen_conf_is_deprecated": "¡«yunohost service regen-conf» está obsoleto! Use «yunohost tools regen-conf» en su lugar.", "service_description_yunohost-firewall": "Gestiona los puertos de conexiones abiertos y cerrados a los servicios", "service_description_yunohost-api": "Gestiona las interacciones entre la interfaz web de YunoHost y el sistema", @@ -239,13 +239,13 @@ "service_description_dovecot": "Permite a los clientes de correo acceder/obtener correo (vía IMAP y POP3)", "service_description_dnsmasq": "Maneja la resolución de nombres de dominio (DNS)", "service_description_avahi-daemon": "Permite acceder a su servidor usando «yunohost.local» en su red local", - "server_reboot_confirm": "El servidor se reiniciará inmediatamente ¿está seguro? [{answers:s}]", + "server_reboot_confirm": "El servidor se reiniciará inmediatamente ¿está seguro? [{answers}]", "server_reboot": "El servidor se reiniciará", - "server_shutdown_confirm": "El servidor se apagará inmediatamente ¿está seguro? [{answers:s}]", + "server_shutdown_confirm": "El servidor se apagará inmediatamente ¿está seguro? [{answers}]", "server_shutdown": "El servidor se apagará", "root_password_replaced_by_admin_password": "Su contraseña de root ha sido sustituida por su contraseña de administración.", "root_password_desynchronized": "La contraseña de administración ha sido cambiada pero ¡YunoHost no pudo propagar esto a la contraseña de root!", - "restore_system_part_failed": "No se pudo restaurar la parte del sistema «{part:s}»", + "restore_system_part_failed": "No se pudo restaurar la parte del sistema «{part}»", "restore_removing_tmp_dir_failed": "No se pudo eliminar un directorio temporal antiguo", "restore_not_enough_disk_space": "Espacio insuficiente (espacio: {free_space:d} B, espacio necesario: {needed_space:d} B, margen de seguridad: {margin:d} B)", "restore_may_be_not_enough_disk_space": "Parece que su sistema no tiene suficiente espacio libre (libre: {free_space:d} B, espacio necesario: {needed_space:d} B, margen de seguridad: {margin:d} B)", @@ -265,13 +265,13 @@ "regenconf_file_kept_back": "Se espera que el archivo de configuración «{conf}» sea eliminado por regen-conf (categoría {category}) pero ha sido retenido.", "regenconf_file_copy_failed": "No se pudo copiar el nuevo archivo de configuración «{new}» a «{conf}»", "regenconf_file_backed_up": "Archivo de configuración «{conf}» respaldado en «{backup}»", - "permission_updated": "Actualizado el permiso «{permission:s}»", + "permission_updated": "Actualizado el permiso «{permission}»", "permission_update_failed": "No se pudo actualizar el permiso '{permission}': {error}", - "permission_not_found": "No se encontró el permiso «{permission:s}»", + "permission_not_found": "No se encontró el permiso «{permission}»", "permission_deletion_failed": "No se pudo eliminar el permiso «{permission}»: {error}", - "permission_deleted": "Eliminado el permiso «{permission:s}»", + "permission_deleted": "Eliminado el permiso «{permission}»", "permission_creation_failed": "No se pudo crear el permiso «{permission}»: {error}", - "permission_created": "Creado el permiso «{permission:s}»", + "permission_created": "Creado el permiso «{permission}»", "permission_already_exist": "El permiso «{permission}» ya existe", "pattern_password_app": "Las contraseñas no pueden incluir los siguientes caracteres: {forbidden_chars}", "migrations_to_be_ran_manually": "La migración {id} hay que ejecutarla manualmente. Vaya a Herramientas → Migraciones en la página web de administración o ejecute `yunohost tools migrations run`.", @@ -293,7 +293,7 @@ "migrations_cant_reach_migration_file": "No se pudo acceder a los archivos de migración en la ruta «%s»", "migrations_already_ran": "Esas migraciones ya se han realizado: {ids}", "mail_unavailable": "Esta dirección de correo está reservada y será asignada automáticamente al primer usuario", - "mailbox_disabled": "Correo desactivado para usuario {user:s}", + "mailbox_disabled": "Correo desactivado para usuario {user}", "log_tools_reboot": "Reiniciar el servidor", "log_tools_shutdown": "Apagar el servidor", "log_tools_upgrade": "Actualizar paquetes del sistema", @@ -329,44 +329,44 @@ "log_help_to_get_log": "Para ver el registro de la operación «{desc}», ejecute la orden «yunohost log show {name}{name}»", "log_link_to_log": "Registro completo de esta operación: «{desc}»", "log_corrupted_md_file": "El archivo de metadatos YAML asociado con el registro está dañado: «{md_file}\nError: {error}»", - "hook_json_return_error": "No se pudo leer la respuesta del gancho {path:s}. Error: {msg:s}. Contenido sin procesar: {raw_content}", + "hook_json_return_error": "No se pudo leer la respuesta del gancho {path}. Error: {msg}. Contenido sin procesar: {raw_content}", "group_update_failed": "No se pudo actualizar el grupo «{group}»: {error}", "group_updated": "Grupo «{group}» actualizado", - "group_unknown": "El grupo «{group:s}» es desconocido", + "group_unknown": "El grupo «{group}» es desconocido", "group_deletion_failed": "No se pudo eliminar el grupo «{group}»: {error}", "group_deleted": "Eliminado el grupo «{group}»", "group_creation_failed": "No se pudo crear el grupo «{group}»: {error}", "group_created": "Creado el grupo «{group}»", "good_practices_about_admin_password": "Ahora está a punto de definir una nueva contraseña de administración. La contraseña debe tener al menos 8 caracteres, aunque es una buena práctica usar una contraseña más larga (es decir, una frase de contraseña) y / o usar una variación de caracteres (mayúsculas, minúsculas, dígitos y caracteres especiales).", - "global_settings_unknown_type": "Situación imprevista, la configuración {setting:s} parece tener el tipo {unknown_type:s} pero no es un tipo compatible con el sistema.", + "global_settings_unknown_type": "Situación imprevista, la configuración {setting} parece tener el tipo {unknown_type} pero no es un tipo compatible con el sistema.", "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Permitir el uso de la llave (obsoleta) DSA para la configuración del demonio SSH", - "global_settings_unknown_setting_from_settings_file": "Clave desconocida en la configuración: «{setting_key:s}», desechada y guardada en /etc/yunohost/settings-unknown.json", + "global_settings_unknown_setting_from_settings_file": "Clave desconocida en la configuración: «{setting_key}», desechada y guardada en /etc/yunohost/settings-unknown.json", "global_settings_setting_security_postfix_compatibility": "Compromiso entre compatibilidad y seguridad para el servidor Postfix. Afecta al cifrado (y otros aspectos relacionados con la seguridad)", "global_settings_setting_security_ssh_compatibility": "Compromiso entre compatibilidad y seguridad para el servidor SSH. Afecta al cifrado (y otros aspectos relacionados con la seguridad)", "global_settings_setting_security_password_user_strength": "Seguridad de la contraseña de usuario", "global_settings_setting_security_password_admin_strength": "Seguridad de la contraseña del administrador", "global_settings_setting_security_nginx_compatibility": "Compromiso entre compatibilidad y seguridad para el servidor web NGINX. Afecta al cifrado (y otros aspectos relacionados con la seguridad)", - "global_settings_reset_success": "Respaldada la configuración previa en {path:s}", - "global_settings_key_doesnt_exists": "La clave «{settings_key:s}» no existe en la configuración global, puede ver todas las claves disponibles ejecutando «yunohost settings list»", - "global_settings_cant_write_settings": "No se pudo guardar el archivo de configuración, motivo: {reason:s}", - "global_settings_cant_serialize_settings": "No se pudo seriar los datos de configuración, motivo: {reason:s}", - "global_settings_cant_open_settings": "No se pudo abrir el archivo de configuración, motivo: {reason:s}", - "global_settings_bad_type_for_setting": "Tipo erróneo para la configuración {setting:s}, obtuvo {received_type:s}, esperado {expected_type:s}", - "global_settings_bad_choice_for_enum": "Opción errónea para la configuración {setting:s}, obtuvo «{choice:s}» pero las opciones disponibles son: {available_choices:s}", - "file_does_not_exist": "El archivo {path:s} no existe.", - "dyndns_could_not_check_available": "No se pudo comprobar si {domain:s} está disponible en {provider:s}.", + "global_settings_reset_success": "Respaldada la configuración previa en {path}", + "global_settings_key_doesnt_exists": "La clave «{settings_key}» no existe en la configuración global, puede ver todas las claves disponibles ejecutando «yunohost settings list»", + "global_settings_cant_write_settings": "No se pudo guardar el archivo de configuración, motivo: {reason}", + "global_settings_cant_serialize_settings": "No se pudo seriar los datos de configuración, motivo: {reason}", + "global_settings_cant_open_settings": "No se pudo abrir el archivo de configuración, motivo: {reason}", + "global_settings_bad_type_for_setting": "Tipo erróneo para la configuración {setting}, obtuvo {received_type}, esperado {expected_type}", + "global_settings_bad_choice_for_enum": "Opción errónea para la configuración {setting}, obtuvo «{choice}» pero las opciones disponibles son: {available_choices}", + "file_does_not_exist": "El archivo {path} no existe.", + "dyndns_could_not_check_available": "No se pudo comprobar si {domain} está disponible en {provider}.", "domain_dns_conf_is_just_a_recommendation": "Esta orden muestra la configuración *recomendada*. No configura el DNS en realidad. Es su responsabilidad configurar la zona de DNS en su registrador según esta recomendación.", "dpkg_lock_not_available": "Esta orden no se puede ejecutar en este momento ,parece que programa está usando el bloqueo de dpkg (el gestor de paquetes del sistema)", "dpkg_is_broken": "No puede hacer esto en este momento porque dpkg/APT (los gestores de paquetes del sistema) parecen estar mal configurados... Puede tratar de solucionar este problema conectando a través de SSH y ejecutando `sudo apt install --fix-broken` y/o `sudo dpkg --configure -a`.", - "confirm_app_install_thirdparty": "¡PELIGRO! Esta aplicación no forma parte del catálogo de aplicaciones de Yunohost. La instalación de aplicaciones de terceros puede comprometer la integridad y la seguridad de su sistema. Probablemente NO debería instalarlo a menos que sepa lo que está haciendo. NO se proporcionará SOPORTE si esta aplicación no funciona o rompe su sistema ... Si de todos modos está dispuesto a correr ese riesgo, escriba '{answers:s}'", - "confirm_app_install_danger": "¡PELIGRO! ¡Se sabe que esta aplicación sigue siendo experimental (si no explícitamente no funciona)! Probablemente NO debería instalarlo a menos que sepa lo que está haciendo. NO se proporcionará SOPORTE si esta aplicación no funciona o rompe su sistema ... Si de todos modos está dispuesto a correr ese riesgo, escriba '{answers:s}'", - "confirm_app_install_warning": "Aviso: esta aplicación puede funcionar pero no está bien integrada en YunoHost. Algunas herramientas como la autentificación única y respaldo/restauración podrían no estar disponibles. ¿Instalar de todos modos? [{answers:s}] ", + "confirm_app_install_thirdparty": "¡PELIGRO! Esta aplicación no forma parte del catálogo de aplicaciones de Yunohost. La instalación de aplicaciones de terceros puede comprometer la integridad y la seguridad de su sistema. Probablemente NO debería instalarlo a menos que sepa lo que está haciendo. NO se proporcionará SOPORTE si esta aplicación no funciona o rompe su sistema ... Si de todos modos está dispuesto a correr ese riesgo, escriba '{answers}'", + "confirm_app_install_danger": "¡PELIGRO! ¡Se sabe que esta aplicación sigue siendo experimental (si no explícitamente no funciona)! Probablemente NO debería instalarlo a menos que sepa lo que está haciendo. NO se proporcionará SOPORTE si esta aplicación no funciona o rompe su sistema ... Si de todos modos está dispuesto a correr ese riesgo, escriba '{answers}'", + "confirm_app_install_warning": "Aviso: esta aplicación puede funcionar pero no está bien integrada en YunoHost. Algunas herramientas como la autentificación única y respaldo/restauración podrían no estar disponibles. ¿Instalar de todos modos? [{answers}] ", "backup_unable_to_organize_files": "No se pudo usar el método rápido de organización de los archivos en el archivo", - "backup_permission": "Permiso de respaldo para {app:s}", - "backup_output_symlink_dir_broken": "El directorio de su archivo «{path:s}» es un enlace simbólico roto. Tal vez olvidó (re)montarlo o conectarlo al medio de almacenamiento al que apunta.", + "backup_permission": "Permiso de respaldo para {app}", + "backup_output_symlink_dir_broken": "El directorio de su archivo «{path}» es un enlace simbólico roto. Tal vez olvidó (re)montarlo o conectarlo al medio de almacenamiento al que apunta.", "backup_mount_archive_for_restore": "Preparando el archivo para restaurarlo…", "backup_method_tar_finished": "Creado el archivo TAR de respaldo", - "backup_method_custom_finished": "Terminado el método «{method:s}» de respaldo personalizado", + "backup_method_custom_finished": "Terminado el método «{method}» de respaldo personalizado", "backup_method_copy_finished": "Terminada la copia de seguridad", "backup_custom_backup_error": "El método de respaldo personalizado no pudo superar el paso de «copia de seguridad»", "backup_actually_backuping": "Creando un archivo de respaldo de los archivos obtenidos…", @@ -486,7 +486,7 @@ "log_app_action_run": "Inicializa la acción de la aplicación '{}'", "group_already_exist_on_system_but_removing_it": "El grupo {group} ya existe en los grupos del sistema, pero YunoHost lo suprimirá …", "global_settings_setting_pop3_enabled": "Habilita el protocolo POP3 para el servidor de correo electrónico", - "domain_cannot_remove_main_add_new_one": "No se puede remover '{domain:s}' porque es su principal y único dominio. Primero debe agregar un nuevo dominio con la linea de comando 'yunohost domain add ', entonces configurarlo como dominio principal con 'yunohost domain main-domain -n ' y finalmente borrar el dominio '{domain:s}' con 'yunohost domain remove {domain:s}'.'", + "domain_cannot_remove_main_add_new_one": "No se puede remover '{domain}' porque es su principal y único dominio. Primero debe agregar un nuevo dominio con la linea de comando 'yunohost domain add ', entonces configurarlo como dominio principal con 'yunohost domain main-domain -n ' y finalmente borrar el dominio '{domain}' con 'yunohost domain remove {domain}'.'", "diagnosis_never_ran_yet": "Este servidor todavía no tiene reportes de diagnostico. Puede iniciar un diagnostico completo desde la interface administrador web o con la linea de comando 'yunohost diagnosis run'.", "diagnosis_unknown_categories": "Las siguientes categorías están desconocidas: {categories}", "diagnosis_http_unreachable": "El dominio {domain} esta fuera de alcance desde internet y a través de HTTP.", @@ -497,7 +497,7 @@ "diagnosis_http_could_not_diagnose": "No se pudo verificar si el dominio es accesible desde internet.", "diagnosis_http_could_not_diagnose_details": "Error: {error}", "diagnosis_ports_forwarding_tip": "Para solucionar este incidente, lo más seguro deberías configurar la redirección de los puertos en el router como se especifica 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.", + "certmanager_warning_subdomain_dns_record": "El subdominio '{subdomain}' no se resuelve en la misma dirección IP que '{domain}'. 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.", "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.", @@ -543,7 +543,7 @@ "migration_description_0016_php70_to_php73_pools": "Migra el «pool» de ficheros php7.0-fpm a php7.3", "migration_description_0015_migrate_to_buster": "Actualiza el sistema a Debian Buster y YunoHost 4.x", "migrating_legacy_permission_settings": "Migrando los antiguos parámetros de permisos...", - "invalid_regex": "Regex no valido: «{regex:s}»", + "invalid_regex": "Regex no valido: «{regex}»", "global_settings_setting_backup_compress_tar_archives": "Cuando se creen nuevas copias de respaldo, comprimir los archivos (.tar.gz) en lugar de descomprimir los archivos (.tar). N.B.: activar esta opción quiere decir que los archivos serán más pequeños pero que el proceso tardará más y utilizará más CPU.", "global_settings_setting_smtp_relay_password": "Clave de uso del SMTP", "global_settings_setting_smtp_relay_user": "Cuenta de uso de SMTP", @@ -585,6 +585,6 @@ "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Algunos proveedores de internet no le permitirán desbloquear el puerto 25 porque no les importa la Neutralidad de la Red.
- Algunos proporcionan una alternativa usando un relay como servidor de correo lo que implica que el relay podrá espiar tu trafico de correo.
- Una alternativa buena para la privacidad es utilizar una VPN *con una IP pública dedicada* para evitar estas limitaciones. Mira en https://yunohost.org/#/vpn_advantage
- Otra alternativa es cambiar de proveedor de inteernet a uno más amable con la Neutralidad de la Red", "diagnosis_backports_in_sources_list": "Parece que apt (el gestor de paquetes) está configurado para usar el repositorio backports. A menos que realmente sepas lo que estás haciendo, desaconsejamos absolutamente instalar paquetes desde backports, ya que pueden provocar comportamientos intestables o conflictos en el sistema.", "diagnosis_basesystem_hardware_model": "El modelo de servidor es {model}", - "additional_urls_already_removed": "La URL adicional «{url:s}» ya se ha eliminado para el permiso «{permission:s}»", - "additional_urls_already_added": "La URL adicional «{url:s}» ya se ha añadido para el permiso «{permission:s}»" + "additional_urls_already_removed": "La URL adicional «{url}» ya se ha eliminado para el permiso «{permission}»", + "additional_urls_already_added": "La URL adicional «{url}» ya se ha añadido para el permiso «{permission}»" } \ No newline at end of file diff --git a/locales/fr.json b/locales/fr.json index 08361d6f2..8d724dde7 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -1,49 +1,49 @@ { - "action_invalid": "Action '{action:s}' incorrecte", + "action_invalid": "Action '{action}' incorrecte", "admin_password": "Mot de passe d’administration", "admin_password_change_failed": "Impossible de changer le mot de passe", "admin_password_changed": "Le mot de passe d’administration a été modifié", - "app_already_installed": "{app:s} est déjà installé", - "app_argument_choice_invalid": "Choix invalide pour le paramètre '{name:s}', il doit être l’un de {choices:s}", - "app_argument_invalid": "Valeur invalide pour le paramètre '{name:s}' : {error:s}", - "app_argument_required": "Le paramètre '{name:s}' est requis", + "app_already_installed": "{app} est déjà installé", + "app_argument_choice_invalid": "Choix invalide pour le paramètre '{name}', il doit être l’un de {choices}", + "app_argument_invalid": "Valeur invalide pour le paramètre '{name}' : {error}", + "app_argument_required": "Le paramètre '{name}' est requis", "app_extraction_failed": "Impossible d’extraire les fichiers d’installation", "app_id_invalid": "Identifiant d’application invalide", "app_install_files_invalid": "Fichiers d’installation incorrects", "app_manifest_invalid": "Manifeste d’application incorrect : {error}", - "app_not_correctly_installed": "{app:s} semble être mal installé", - "app_not_installed": "Nous n’avons pas trouvé {app:s} dans la liste des applications installées : {all_apps}", - "app_not_properly_removed": "{app:s} n’a pas été supprimé correctement", - "app_removed": "{app:s} supprimé", + "app_not_correctly_installed": "{app} semble être mal installé", + "app_not_installed": "Nous n’avons pas trouvé {app} dans la liste des applications installées : {all_apps}", + "app_not_properly_removed": "{app} n’a pas été supprimé correctement", + "app_removed": "{app} supprimé", "app_requirements_checking": "Vérification des paquets requis pour {app}...", "app_requirements_unmeet": "Les pré-requis de {app} ne sont pas satisfaits, le paquet {pkgname} ({version}) doit être {spec}", "app_sources_fetch_failed": "Impossible de récupérer les fichiers sources, l’URL est-elle correcte ?", "app_unknown": "Application inconnue", "app_unsupported_remote_type": "Ce type de commande à distance utilisé pour cette application n’est pas supporté", - "app_upgrade_failed": "Impossible de mettre à jour {app:s} : {error}", - "app_upgraded": "{app:s} mis à jour", + "app_upgrade_failed": "Impossible de mettre à jour {app} : {error}", + "app_upgraded": "{app} mis à jour", "ask_firstname": "Prénom", "ask_lastname": "Nom", "ask_main_domain": "Domaine principal", "ask_new_admin_password": "Nouveau mot de passe d’administration", "ask_password": "Mot de passe", - "backup_app_failed": "Impossible de sauvegarder {app:s}", - "backup_archive_app_not_found": "{app:s} n’a pas été trouvée dans l’archive de la sauvegarde", + "backup_app_failed": "Impossible de sauvegarder {app}", + "backup_archive_app_not_found": "{app} n’a pas été trouvée dans l’archive de la sauvegarde", "backup_archive_name_exists": "Une archive de sauvegarde avec ce nom existe déjà.", - "backup_archive_name_unknown": "L’archive locale de sauvegarde nommée '{name:s}' est inconnue", + "backup_archive_name_unknown": "L’archive locale de sauvegarde nommée '{name}' est inconnue", "backup_archive_open_failed": "Impossible d’ouvrir l’archive de la sauvegarde", "backup_cleaning_failed": "Impossible de nettoyer le dossier temporaire de sauvegarde", "backup_created": "Sauvegarde terminée", "backup_creation_failed": "Impossible de créer l’archive de la sauvegarde", - "backup_delete_error": "Impossible de supprimer '{path:s}'", + "backup_delete_error": "Impossible de supprimer '{path}'", "backup_deleted": "La sauvegarde a été supprimée", - "backup_hook_unknown": "Script de sauvegarde '{hook:s}' inconnu", + "backup_hook_unknown": "Script de sauvegarde '{hook}' inconnu", "backup_nothings_done": "Il n’y a rien à sauvegarder", "backup_output_directory_forbidden": "Choisissez un répertoire de destination différent. Les sauvegardes ne peuvent pas être créées dans les sous-dossiers /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var ou /home/yunohost.backup/archives", "backup_output_directory_not_empty": "Le répertoire de destination n’est pas vide", "backup_output_directory_required": "Vous devez spécifier un dossier de destination pour la sauvegarde", "backup_running_hooks": "Exécution des scripts de sauvegarde...", - "custom_app_url_required": "Vous devez spécifier une URL pour mettre à jour votre application personnalisée {app:s}", + "custom_app_url_required": "Vous devez spécifier une URL pour mettre à jour votre application personnalisée {app}", "domain_cert_gen_failed": "Impossible de générer le certificat", "domain_created": "Le domaine a été créé", "domain_creation_failed": "Impossible de créer le domaine {domain} : {error}", @@ -62,26 +62,26 @@ "dyndns_key_not_found": "Clé DNS introuvable pour le domaine", "dyndns_no_domain_registered": "Aucun domaine n'a été enregistré avec DynDNS", "dyndns_registered": "Domaine DynDNS enregistré", - "dyndns_registration_failed": "Impossible d’enregistrer le domaine DynDNS : {error:s}", - "dyndns_unavailable": "Le domaine {domain:s} est indisponible.", + "dyndns_registration_failed": "Impossible d’enregistrer le domaine DynDNS : {error}", + "dyndns_unavailable": "Le domaine {domain} est indisponible.", "extracting": "Extraction en cours...", - "field_invalid": "Champ incorrect : '{:s}'", + "field_invalid": "Champ incorrect : '{}'", "firewall_reload_failed": "Impossible de recharger le pare-feu", "firewall_reloaded": "Pare-feu rechargé", "firewall_rules_cmd_failed": "Certaines commandes de règles de pare-feu ont échoué. Plus d'informations dans le journal.", - "hook_exec_failed": "Échec de l’exécution du script : {path:s}", - "hook_exec_not_terminated": "L’exécution du script {path:s} ne s’est pas terminée correctement", + "hook_exec_failed": "Échec de l’exécution du script : {path}", + "hook_exec_not_terminated": "L’exécution du script {path} ne s’est pas terminée correctement", "hook_list_by_invalid": "Propriété invalide pour lister les actions par celle-ci", - "hook_name_unknown": "Nom de l’action '{name:s}' inconnu", + "hook_name_unknown": "Nom de l’action '{name}' inconnu", "installation_complete": "Installation terminée", "ip6tables_unavailable": "Vous ne pouvez pas jouer avec ip6tables ici. Vous êtes soit dans un conteneur, soit votre noyau ne le prend pas en charge", "iptables_unavailable": "Vous ne pouvez pas jouer avec iptables ici. Vous êtes soit dans un conteneur, soit votre noyau ne le prend pas en charge", - "mail_alias_remove_failed": "Impossible de supprimer l’alias de courriel '{mail:s}'", - "mail_domain_unknown": "Le domaine '{domain:s}' de cette adresse de courriel n’est pas valide. Merci d’utiliser un domaine administré par ce serveur.", - "mail_forward_remove_failed": "Impossible de supprimer le courriel de transfert '{mail:s}'", + "mail_alias_remove_failed": "Impossible de supprimer l’alias de courriel '{mail}'", + "mail_domain_unknown": "Le domaine '{domain}' de cette adresse de courriel n’est pas valide. Merci d’utiliser un domaine administré par ce serveur.", + "mail_forward_remove_failed": "Impossible de supprimer le courriel de transfert '{mail}'", "main_domain_change_failed": "Impossible de modifier le domaine principal", "main_domain_changed": "Le domaine principal a été modifié", - "not_enough_disk_space": "L’espace disque est insuffisant sur '{path:s}'", + "not_enough_disk_space": "L’espace disque est insuffisant sur '{path}'", "packages_upgrade_failed": "Impossible de mettre à jour tous les paquets", "pattern_backup_archive_name": "Doit être un nom de fichier valide avec un maximum de 30 caractères, et composé de caractères alphanumériques et -_. uniquement", "pattern_domain": "Doit être un nom de domaine valide (ex : mon-domaine.fr)", @@ -93,42 +93,42 @@ "pattern_port_or_range": "Doit être un numéro de port valide compris entre 0 et 65535, ou une gamme de ports (exemple : 100:200)", "pattern_positive_number": "Doit être un nombre positif", "pattern_username": "Doit être composé uniquement de caractères alphanumériques minuscules et de tirets bas (aussi appelé tiret du 8 ou underscore)", - "port_already_closed": "Le port {port:d} est déjà fermé pour les connexions {ip_version:s}", - "port_already_opened": "Le port {port:d} est déjà ouvert pour les connexions {ip_version:s}", - "restore_already_installed_app": "Une application est déjà installée avec l’identifiant '{app:s}'", - "app_restore_failed": "Impossible de restaurer {app:s} : {error:s}", + "port_already_closed": "Le port {port:d} est déjà fermé pour les connexions {ip_version}", + "port_already_opened": "Le port {port:d} est déjà ouvert pour les connexions {ip_version}", + "restore_already_installed_app": "Une application est déjà installée avec l’identifiant '{app}'", + "app_restore_failed": "Impossible de restaurer {app} : {error}", "restore_cleaning_failed": "Impossible de nettoyer le dossier temporaire de restauration", "restore_complete": "Restauration terminée", - "restore_confirm_yunohost_installed": "Voulez-vous vraiment restaurer un système déjà installé ? [{answers:s}]", + "restore_confirm_yunohost_installed": "Voulez-vous vraiment restaurer un système déjà installé ? [{answers}]", "restore_failed": "Impossible de restaurer le système", - "restore_hook_unavailable": "Le script de restauration '{part:s}' n’est pas disponible sur votre système, et ne l’est pas non plus dans l’archive", + "restore_hook_unavailable": "Le script de restauration '{part}' n’est pas disponible sur votre système, et ne l’est pas non plus dans l’archive", "restore_nothings_done": "Rien n’a été restauré", - "restore_running_app_script": "Exécution du script de restauration de l’application '{app:s}'…", + "restore_running_app_script": "Exécution du script de restauration de l’application '{app}'…", "restore_running_hooks": "Exécution des scripts de restauration…", - "service_add_failed": "Impossible d’ajouter le service '{service:s}'", - "service_added": "Le service '{service:s}' a été ajouté", - "service_already_started": "Le service '{service:s}' est déjà en cours d’exécution", - "service_already_stopped": "Le service '{service:s}' est déjà arrêté", - "service_cmd_exec_failed": "Impossible d’exécuter la commande '{command:s}'", - "service_disable_failed": "Impossible de ne pas lancer le service « {service:s} » au démarrage.\n\nJournaux récents du service : {logs:s}", - "service_disabled": "Le service « {service:s} » ne sera plus lancé au démarrage du système.", - "service_enable_failed": "Impossible de lancer automatiquement le service « {service:s} » au démarrage.\n\nJournaux récents du service : {logs:s}", - "service_enabled": "Le service « {service:s} » sera désormais lancé automatiquement au démarrage du système.", - "service_remove_failed": "Impossible de supprimer le service '{service:s}'", - "service_removed": "Le service « {service:s} » a été supprimé", - "service_start_failed": "Impossible de démarrer le service '{service:s}'\n\nJournaux historisés récents : {logs:s}", - "service_started": "Le service « {service:s} » a été démarré", - "service_stop_failed": "Impossible d’arrêter le service '{service:s}'\n\nJournaux récents de service : {logs:s}", - "service_stopped": "Le service « {service:s} » a été arrêté", - "service_unknown": "Le service '{service:s}' est inconnu", + "service_add_failed": "Impossible d’ajouter le service '{service}'", + "service_added": "Le service '{service}' a été ajouté", + "service_already_started": "Le service '{service}' est déjà en cours d’exécution", + "service_already_stopped": "Le service '{service}' est déjà arrêté", + "service_cmd_exec_failed": "Impossible d’exécuter la commande '{command}'", + "service_disable_failed": "Impossible de ne pas lancer le service « {service} » au démarrage.\n\nJournaux récents du service : {logs}", + "service_disabled": "Le service « {service} » ne sera plus lancé au démarrage du système.", + "service_enable_failed": "Impossible de lancer automatiquement le service « {service} » au démarrage.\n\nJournaux récents du service : {logs}", + "service_enabled": "Le service « {service} » sera désormais lancé automatiquement au démarrage du système.", + "service_remove_failed": "Impossible de supprimer le service '{service}'", + "service_removed": "Le service « {service} » a été supprimé", + "service_start_failed": "Impossible de démarrer le service '{service}'\n\nJournaux historisés récents : {logs}", + "service_started": "Le service « {service} » a été démarré", + "service_stop_failed": "Impossible d’arrêter le service '{service}'\n\nJournaux récents de service : {logs}", + "service_stopped": "Le service « {service} » a été arrêté", + "service_unknown": "Le service '{service}' est inconnu", "ssowat_conf_generated": "La configuration de SSOwat a été regénérée", "ssowat_conf_updated": "La configuration de SSOwat a été mise à jour", "system_upgraded": "Système mis à jour", "system_username_exists": "Ce nom d’utilisateur existe déjà dans les utilisateurs système", - "unbackup_app": "'{app:s}' ne sera pas sauvegardée", + "unbackup_app": "'{app}' ne sera pas sauvegardée", "unexpected_error": "Une erreur inattendue est survenue : {error}", "unlimit": "Pas de quota", - "unrestore_app": "'{app:s}' ne sera pas restaurée", + "unrestore_app": "'{app}' ne sera pas restaurée", "updating_apt_cache": "Récupération des mises à jour disponibles pour les paquets du système...", "upgrade_complete": "Mise à jour terminée", "upgrading_packages": "Mise à jour des paquets en cours...", @@ -141,59 +141,59 @@ "user_deleted": "L’utilisateur a été supprimé", "user_deletion_failed": "Impossible de supprimer l’utilisateur {user} : {error}", "user_home_creation_failed": "Impossible de créer le dossier personnel de l’utilisateur", - "user_unknown": "L’utilisateur {user:s} est inconnu", + "user_unknown": "L’utilisateur {user} est inconnu", "user_update_failed": "Impossible de mettre à jour l’utilisateur {user} : {error}", "user_updated": "L’utilisateur a été modifié", "yunohost_already_installed": "YunoHost est déjà installé", "yunohost_configured": "YunoHost est maintenant configuré", "yunohost_installing": "L’installation de YunoHost est en cours...", "yunohost_not_installed": "YunoHost n’est pas correctement installé. Veuillez exécuter 'yunohost tools postinstall'", - "certmanager_attempt_to_replace_valid_cert": "Vous êtes en train de vouloir remplacer un certificat correct et valide pour le domaine {domain:s} ! (Utilisez --force pour contourner cela)", - "certmanager_domain_cert_not_selfsigned": "Le certificat du domaine {domain:s} n’est pas auto-signé. Voulez-vous vraiment le remplacer ? (Utilisez --force pour cela)", - "certmanager_certificate_fetching_or_enabling_failed": "Il semble que l’activation du nouveau certificat pour {domain:s} a échoué...", - "certmanager_attempt_to_renew_nonLE_cert": "Le certificat pour le domaine {domain:s} n’est pas émis par Let’s Encrypt. Impossible de le renouveler automatiquement !", - "certmanager_attempt_to_renew_valid_cert": "Le certificat pour le domaine {domain:s} 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, utilisez '--no-checks' pour désactiver la vérification.)", - "certmanager_domain_dns_ip_differs_from_public_ip": "L'enregistrement DNS du domaine '{domain:s}' est différent de l’adresse IP de ce serveur. Pour plus d'informations, veuillez consulter la catégorie \"Enregistrements DNS\" dans la section diagnostic. 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}", - "certmanager_cert_install_success_selfsigned": "Le certificat auto-signé est maintenant installé pour le domaine « {domain:s} »", - "certmanager_cert_install_success": "Le certificat Let’s Encrypt est maintenant installé pour le domaine « {domain:s} »", - "certmanager_cert_renew_success": "Certificat Let’s Encrypt renouvelé pour le domaine '{domain:s}'", + "certmanager_attempt_to_replace_valid_cert": "Vous êtes en train de vouloir remplacer un certificat correct et valide pour le domaine {domain} ! (Utilisez --force pour contourner cela)", + "certmanager_domain_cert_not_selfsigned": "Le certificat du domaine {domain} n’est pas auto-signé. Voulez-vous vraiment le remplacer ? (Utilisez --force pour cela)", + "certmanager_certificate_fetching_or_enabling_failed": "Il semble que l’activation du nouveau certificat pour {domain} a échoué...", + "certmanager_attempt_to_renew_nonLE_cert": "Le certificat pour le domaine {domain} 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} 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} 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_domain_dns_ip_differs_from_public_ip": "L'enregistrement DNS du domaine '{domain}' est différent de l’adresse IP de ce serveur. Pour plus d'informations, veuillez consulter la catégorie \"Enregistrements DNS\" dans la section diagnostic. 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} (fichier : {file}), la cause est : {reason}", + "certmanager_cert_install_success_selfsigned": "Le certificat auto-signé est maintenant installé pour le domaine « {domain} »", + "certmanager_cert_install_success": "Le certificat Let’s Encrypt est maintenant installé pour le domaine « {domain} »", + "certmanager_cert_renew_success": "Certificat Let’s Encrypt renouvelé pour le domaine '{domain}'", "certmanager_cert_signing_failed": "Impossible de signer le nouveau certificat", - "certmanager_no_cert_file": "Impossible de lire le fichier du certificat pour le domaine {domain:s} (fichier : {file:s})", - "certmanager_hit_rate_limit": "Trop de certificats ont déjà été émis récemment pour ce même ensemble de domaines {domain:s}. Veuillez réessayer plus tard. Lisez https://letsencrypt.org/docs/rate-limits/ pour obtenir plus de détails sur les ratios et limitations", - "domain_cannot_remove_main": "Vous ne pouvez pas supprimer '{domain:s}' car il s’agit du domaine principal. Vous devez d’abord définir un autre domaine comme domaine principal à l’aide de 'yunohost domain main-domain -n ', voici la liste des domaines candidats : {other_domains:s}", - "certmanager_self_ca_conf_file_not_found": "Le fichier de configuration pour l’autorité du certificat auto-signé est introuvable (fichier : {file:s})", - "certmanager_unable_to_parse_self_CA_name": "Impossible d’analyser le nom de l’autorité du certificat auto-signé (fichier : {file:s})", + "certmanager_no_cert_file": "Impossible de lire le fichier du certificat pour le domaine {domain} (fichier : {file})", + "certmanager_hit_rate_limit": "Trop de certificats ont déjà été émis récemment pour ce même ensemble de domaines {domain}. Veuillez réessayer plus tard. Lisez https://letsencrypt.org/docs/rate-limits/ pour obtenir plus de détails sur les ratios et limitations", + "domain_cannot_remove_main": "Vous ne pouvez pas supprimer '{domain}' car il s’agit du domaine principal. Vous devez d’abord définir un autre domaine comme domaine principal à l’aide de 'yunohost domain main-domain -n ', voici la liste des domaines candidats : {other_domains}", + "certmanager_self_ca_conf_file_not_found": "Le fichier de configuration pour l’autorité du certificat auto-signé est introuvable (fichier : {file})", + "certmanager_unable_to_parse_self_CA_name": "Impossible d’analyser le nom de l’autorité du certificat auto-signé (fichier : {file})", "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})", + "backup_archive_broken_link": "Impossible d’accéder à l’archive de sauvegarde (lien invalide vers {path})", "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`.", "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).", "app_already_installed_cant_change_url": "Cette application est déjà installée. L’URL ne peut pas être changé simplement par cette fonction. Vérifiez si cela est disponible avec `app changeurl`.", - "app_change_url_failed_nginx_reload": "Le redémarrage de NGINX a échoué. Voici la sortie de 'nginx -t' :\n{nginx_errors:s}", - "app_change_url_identical_domains": "L’ancien et le nouveau couple domaine/chemin_de_l’URL sont identiques pour ('{domain:s}{path:s}'), rien à faire.", - "app_change_url_no_script": "L’application '{app_name:s}' ne prend pas encore en charge le changement d’URL. Vous devriez peut-être la mettre à jour.", - "app_change_url_success": "L’URL de l’application {app:s} a été changée en {domain:s}{path:s}", - "app_location_unavailable": "Cette URL n’est pas disponible ou est en conflit avec une application existante :\n{apps:s}", - "app_already_up_to_date": "{app:s} est déjà à jour", - "global_settings_bad_choice_for_enum": "Valeur du paramètre {setting:s} incorrecte. Reçu : {choice:s}, mais les valeurs possibles sont : {available_choices:s}", - "global_settings_bad_type_for_setting": "Le type du paramètre {setting:s} est incorrect. Reçu {received_type:s} alors que {expected_type:s} était attendu", - "global_settings_cant_open_settings": "Échec de l’ouverture du ficher de configurations car : {reason:s}", - "global_settings_cant_write_settings": "Échec d’écriture du fichier de configurations car : {reason:s}", - "global_settings_key_doesnt_exists": "La clef '{settings_key:s}' n’existe pas dans les configurations générales, vous pouvez voir toutes les clefs disponibles en saisissant 'yunohost settings list'", - "global_settings_reset_success": "Vos configurations précédentes ont été sauvegardées dans {path:s}", - "global_settings_unknown_type": "Situation inattendue : la configuration {setting:s} semble avoir le type {unknown_type:s} mais celui-ci n’est pas pris en charge par le système.", - "global_settings_unknown_setting_from_settings_file": "Clé inconnue dans les paramètres : '{setting_key:s}', rejet de cette clé et sauvegarde de celle-ci dans /etc/yunohost/unkown_settings.json", + "app_change_url_failed_nginx_reload": "Le redémarrage de NGINX a échoué. Voici la sortie de 'nginx -t' :\n{nginx_errors}", + "app_change_url_identical_domains": "L’ancien et le nouveau couple domaine/chemin_de_l’URL sont identiques pour ('{domain}{path}'), rien à faire.", + "app_change_url_no_script": "L’application '{app_name}' ne prend pas encore en charge le changement d’URL. Vous devriez peut-être la mettre à jour.", + "app_change_url_success": "L’URL de l’application {app} a été changée en {domain}{path}", + "app_location_unavailable": "Cette URL n’est pas disponible ou est en conflit avec une application existante :\n{apps}", + "app_already_up_to_date": "{app} est déjà à jour", + "global_settings_bad_choice_for_enum": "Valeur du paramètre {setting} incorrecte. Reçu : {choice}, mais les valeurs possibles sont : {available_choices}", + "global_settings_bad_type_for_setting": "Le type du paramètre {setting} est incorrect. Reçu {received_type} alors que {expected_type} était attendu", + "global_settings_cant_open_settings": "Échec de l’ouverture du ficher de configurations car : {reason}", + "global_settings_cant_write_settings": "Échec d’écriture du fichier de configurations car : {reason}", + "global_settings_key_doesnt_exists": "La clef '{settings_key}' n’existe pas dans les configurations générales, vous pouvez voir toutes les clefs disponibles en saisissant 'yunohost settings list'", + "global_settings_reset_success": "Vos configurations précédentes ont été sauvegardées dans {path}", + "global_settings_unknown_type": "Situation inattendue : la configuration {setting} semble avoir le type {unknown_type} mais celui-ci n’est pas pris en charge par le système.", + "global_settings_unknown_setting_from_settings_file": "Clé inconnue dans les paramètres : '{setting_key}', rejet de cette clé et sauvegarde de celle-ci dans /etc/yunohost/unkown_settings.json", "backup_abstract_method": "Cette méthode de sauvegarde reste à implémenter", "backup_applying_method_tar": "Création de l’archive TAR de la sauvegarde...", "backup_applying_method_copy": "Copie de tous les fichiers à sauvegarder...", - "backup_applying_method_custom": "Appel de la méthode de sauvegarde personnalisée '{method:s}'...", - "backup_archive_system_part_not_available": "La partie '{part:s}' du système n’est pas disponible dans cette sauvegarde", - "backup_archive_writing_error": "Impossible d’ajouter des fichiers '{source:s}' (nommés dans l’archive : '{dest:s}') à sauvegarder dans l’archive compressée '{archive:s}'", - "backup_ask_for_copying_if_needed": "Voulez-vous effectuer la sauvegarde en utilisant {size:s}Mo temporairement ? (Cette méthode est utilisée car certains fichiers n’ont pas pu être préparés avec une méthode plus efficace.)", + "backup_applying_method_custom": "Appel de la méthode de sauvegarde personnalisée '{method}'...", + "backup_archive_system_part_not_available": "La partie '{part}' du système n’est pas disponible dans cette sauvegarde", + "backup_archive_writing_error": "Impossible d’ajouter des fichiers '{source}' (nommés dans l’archive : '{dest}') à sauvegarder dans l’archive compressée '{archive}'", + "backup_ask_for_copying_if_needed": "Voulez-vous effectuer la sauvegarde en utilisant {size}Mo temporairement ? (Cette méthode est utilisée car certains fichiers n’ont pas pu être préparés avec une méthode plus efficace.)", "backup_cant_mount_uncompress_archive": "Impossible de monter en lecture seule le dossier de l’archive décompressée", - "backup_copying_to_organize_the_archive": "Copie de {size:s} Mo pour organiser l’archive", + "backup_copying_to_organize_the_archive": "Copie de {size} Mo pour organiser l’archive", "backup_csv_creation_failed": "Impossible de créer le fichier CSV nécessaire à la restauration", "backup_csv_addition_failed": "Impossible d’ajouter des fichiers à sauvegarder dans le fichier CSV", "backup_custom_backup_error": "Échec de la méthode de sauvegarde personnalisée à l’étape 'backup'", @@ -201,18 +201,18 @@ "backup_no_uncompress_archive_dir": "Ce dossier d’archive décompressée n’existe pas", "backup_method_tar_finished": "L’archive TAR de la sauvegarde a été créée", "backup_method_copy_finished": "La copie de la sauvegarde est terminée", - "backup_method_custom_finished": "La méthode de sauvegarde personnalisée '{method:s}' est terminée", - "backup_system_part_failed": "Impossible de sauvegarder la partie '{part:s}' du système", + "backup_method_custom_finished": "La méthode de sauvegarde personnalisée '{method}' est terminée", + "backup_system_part_failed": "Impossible de sauvegarder la partie '{part}' du système", "backup_unable_to_organize_files": "Impossible d’utiliser la méthode rapide pour organiser les fichiers dans l’archive", - "backup_with_no_backup_script_for_app": "L’application {app:s} n’a pas de script de sauvegarde. Ignorer.", - "backup_with_no_restore_script_for_app": "{app:s} n’a pas de script de restauration, vous ne pourrez pas restaurer automatiquement la sauvegarde de cette application.", - "global_settings_cant_serialize_settings": "Échec de la sérialisation des données de paramétrage car : {reason:s}", + "backup_with_no_backup_script_for_app": "L’application {app} n’a pas de script de sauvegarde. Ignorer.", + "backup_with_no_restore_script_for_app": "{app} n’a pas de script de restauration, vous ne pourrez pas restaurer automatiquement la sauvegarde de cette application.", + "global_settings_cant_serialize_settings": "Échec de la sérialisation des données de paramétrage car : {reason}", "restore_removing_tmp_dir_failed": "Impossible de sauvegarder un ancien dossier temporaire", "restore_extracting": "Extraction des fichiers nécessaires depuis l’archive…", "restore_may_be_not_enough_disk_space": "Votre système ne semble pas avoir suffisamment d’espace (libre : {free_space:d} B, espace nécessaire : {needed_space:d} B, marge de sécurité : {margin:d} B)", "restore_not_enough_disk_space": "Espace disponible insuffisant (L’espace libre est de {free_space:d} octets. Le besoin d’espace nécessaire est de {needed_space:d} octets. En appliquant une marge de sécurité, la quantité d’espace nécessaire est de {margin:d} octets)", - "restore_system_part_failed": "Impossible de restaurer la partie '{part:s}' du système", - "backup_couldnt_bind": "Impossible de lier {src:s} avec {dest:s}.", + "restore_system_part_failed": "Impossible de restaurer la partie '{part}' du système", + "backup_couldnt_bind": "Impossible de lier {src} avec {dest}.", "domain_dns_conf_is_just_a_recommendation": "Cette commande vous montre la configuration *recommandée*. Elle ne configure pas le DNS pour vous. Il est de votre ressort de configurer votre zone DNS chez votre registrar/fournisseur conformément à cette recommandation.", "migrations_cant_reach_migration_file": "Impossible d’accéder aux fichiers de migration via le chemin '%s'", "migrations_loading_migration": "Chargement de la migration {id} ...", @@ -220,15 +220,15 @@ "migrations_no_migrations_to_run": "Aucune migration à lancer", "migrations_skip_migration": "Ignorer et passer la migration {id} ...", "server_shutdown": "Le serveur va s’éteindre", - "server_shutdown_confirm": "Le serveur va être éteint immédiatement, le voulez-vous vraiment ? [{answers:s}]", + "server_shutdown_confirm": "Le serveur va être éteint immédiatement, le voulez-vous vraiment ? [{answers}]", "server_reboot": "Le serveur va redémarrer", - "server_reboot_confirm": "Le serveur va redémarrer immédiatement, le voulez-vous vraiment ? [{answers:s}]", + "server_reboot_confirm": "Le serveur va redémarrer immédiatement, le voulez-vous vraiment ? [{answers}]", "app_upgrade_some_app_failed": "Certaines applications n’ont pas été mises à jour", - "dyndns_could_not_check_provide": "Impossible de vérifier si {provider:s} peut fournir {domain:s}.", - "dyndns_domain_not_provided": "Le fournisseur DynDNS {provider:s} ne peut pas fournir le domaine {domain:s}.", + "dyndns_could_not_check_provide": "Impossible de vérifier si {provider} peut fournir {domain}.", + "dyndns_domain_not_provided": "Le fournisseur DynDNS {provider} ne peut pas fournir le domaine {domain}.", "app_make_default_location_already_used": "Impossible de configurer l’application '{app}' par défaut pour le domaine '{domain}' car il est déjà utilisé par l'application '{other_app}'", "app_upgrade_app_name": "Mise à jour de {app}...", - "backup_output_symlink_dir_broken": "Votre répertoire d’archivage '{path:s}' est un lien symbolique brisé. Peut-être avez-vous oublié de re/monter ou de brancher le support de stockage sur lequel il pointe.", + "backup_output_symlink_dir_broken": "Votre répertoire d’archivage '{path}' est un lien symbolique brisé. Peut-être avez-vous oublié de re/monter ou de brancher le support de stockage sur lequel il pointe.", "migrations_list_conflict_pending_done": "Vous ne pouvez pas utiliser --previous et --done simultanément.", "migrations_to_be_ran_manually": "La migration {id} doit être lancée manuellement. Veuillez aller dans Outils > Migrations dans l’interface admin, ou lancer `yunohost tools migrations run`.", "migrations_need_to_accept_disclaimer": "Pour lancer la migration {id}, vous devez accepter cet avertissement :\n---\n{disclaimer}\n---\nSi vous acceptez de lancer la migration, veuillez relancer la commande avec l’option --accept-disclaimer.", @@ -300,24 +300,24 @@ "ask_new_path": "Nouveau chemin", "backup_actually_backuping": "Création d’une archive de sauvegarde à partir des fichiers collectés...", "backup_mount_archive_for_restore": "Préparation de l’archive pour restauration...", - "confirm_app_install_warning": "Avertissement : cette application peut fonctionner mais n’est pas bien intégrée dans YunoHost. Certaines fonctionnalités telles que l’authentification unique et la sauvegarde/restauration peuvent ne pas être disponibles. L’installer quand même ? [{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}'", + "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}] ", + "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}'", + "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}'", "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.", + "dyndns_could_not_check_available": "Impossible de vérifier si {domain} est disponible chez {provider}.", + "file_does_not_exist": "Le fichier dont le chemin est {path} n’existe pas.", "global_settings_setting_security_password_admin_strength": "Qualité du mot de passe administrateur", "global_settings_setting_security_password_user_strength": "Qualité du mot de passe de l’utilisateur", "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Autoriser l’utilisation de la clé hôte DSA (obsolète) pour la configuration du service SSH", - "hook_json_return_error": "Échec de la lecture au retour du script {path:s}. Erreur : {msg:s}. Contenu brut : {raw_content}", + "hook_json_return_error": "Échec de la lecture au retour du script {path}. Erreur : {msg}. Contenu brut : {raw_content}", "pattern_password_app": "Désolé, les mots de passe ne peuvent pas contenir les caractères suivants : {forbidden_chars}", "root_password_replaced_by_admin_password": "Votre mot de passe root a été remplacé par votre mot de passe administrateur.", - "service_reload_failed": "Impossible de recharger le service '{service:s}'.\n\nJournaux historisés récents de ce service : {logs:s}", - "service_reloaded": "Le service « {service:s} » a été rechargé", - "service_restart_failed": "Impossible de redémarrer le service '{service:s}'\n\nJournaux historisés récents de ce service : {logs:s}", - "service_restarted": "Le service « {service:s} » a été redémarré", - "service_reload_or_restart_failed": "Impossible de recharger ou de redémarrer le service '{service:s}'\n\nJournaux historisés récents de ce service : {logs:s}", - "service_reloaded_or_restarted": "Le service « {service:s} » a été rechargé ou redémarré", + "service_reload_failed": "Impossible de recharger le service '{service}'.\n\nJournaux historisés récents de ce service : {logs}", + "service_reloaded": "Le service « {service} » a été rechargé", + "service_restart_failed": "Impossible de redémarrer le service '{service}'\n\nJournaux historisés récents de ce service : {logs}", + "service_restarted": "Le service « {service} » a été redémarré", + "service_reload_or_restart_failed": "Impossible de recharger ou de redémarrer le service '{service}'\n\nJournaux historisés récents de ce service : {logs}", + "service_reloaded_or_restarted": "Le service « {service} » 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 du 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 comportant moins de 127 caractères", @@ -354,17 +354,17 @@ "tools_upgrade_special_packages_explanation": "La mise à niveau spécifique à YunoHost se poursuivra en arrière-plan. Veuillez ne pas lancer d'autres actions sur votre serveur pendant les 10 prochaines minutes (selon la vitesse du matériel). Après cela, vous devrez peut-être vous reconnecter à l'administrateur Web. Le journal de mise à niveau sera disponible dans Outils → Journal (dans le webadmin) ou en utilisant la « liste des journaux yunohost » (à partir de la ligne de commande).", "update_apt_cache_failed": "Impossible de mettre à jour le cache APT (gestionnaire de paquets Debian). Voici un extrait du fichier sources.list qui pourrait vous aider à identifier les lignes problématiques :\n{sourceslist}", "update_apt_cache_warning": "Des erreurs se sont produites lors de la mise à jour du cache APT (gestionnaire de paquets Debian). Voici un extrait des lignes du fichier sources.list qui pourrait vous aider à identifier les lignes problématiques :\n{sourceslist}", - "backup_permission": "Permission de sauvegarde pour {app:s}", + "backup_permission": "Permission de sauvegarde pour {app}", "group_created": "Le groupe '{group}' a été créé", "group_deleted": "Suppression du groupe '{group}'", - "group_unknown": "Le groupe {group:s} est inconnu", + "group_unknown": "Le groupe {group} est inconnu", "group_updated": "Le groupe '{group}' a été mis à jour", "group_update_failed": "La mise à jour du groupe '{group}' a échoué : {error}", "group_creation_failed": "Échec de la création du groupe '{group}' : {error}", "group_deletion_failed": "Échec de la suppression du groupe '{group}' : {error}", "log_user_group_delete": "Supprimer le groupe '{}'", "log_user_group_update": "Mettre à jour '{}' pour le groupe", - "mailbox_disabled": "La boîte aux lettres est désactivée pour l’utilisateur {user:s}", + "mailbox_disabled": "La boîte aux lettres est désactivée pour l’utilisateur {user}", "app_action_broke_system": "Cette action semble avoir cassé des services importants : {services}", "apps_already_up_to_date": "Toutes les applications sont déjà à jour", "migrations_must_provide_explicit_targets": "Vous devez fournir des cibles explicites lorsque vous utilisez '--skip' ou '--force-rerun'", @@ -372,9 +372,9 @@ "migrations_pending_cant_rerun": "Ces migrations étant toujours en attente, vous ne pouvez pas les exécuter à nouveau : {ids}", "migrations_exclusive_options": "'auto', '--skip' et '--force-rerun' sont des options mutuellement exclusives.", "migrations_not_pending_cant_skip": "Ces migrations ne sont pas en attente et ne peuvent donc pas être ignorées : {ids}", - "permission_not_found": "Permission '{permission:s}' introuvable", + "permission_not_found": "Permission '{permission}' introuvable", "permission_update_failed": "Impossible de mettre à jour la permission '{permission}' : {error}", - "permission_updated": "Permission '{permission:s}' mise à jour", + "permission_updated": "Permission '{permission}' mise à jour", "dyndns_provider_unreachable": "Impossible d’atteindre le fournisseur DynDNS {provider} : votre YunoHost n’est pas correctement connecté à Internet ou le serveur Dynette est en panne.", "migrations_already_ran": "Ces migrations sont déjà effectuées : {ids}", "migrations_dependencies_not_satisfied": "Exécutez ces migrations : '{dependencies_id}', avant migration {id}.", @@ -383,9 +383,9 @@ "migrations_success_forward": "Migration {id} terminée", "operation_interrupted": "L'opération a-t-elle été interrompue manuellement ?", "permission_already_exist": "L’autorisation '{permission}' existe déjà", - "permission_created": "Permission '{permission:s}' créée", + "permission_created": "Permission '{permission}' créée", "permission_creation_failed": "Impossible de créer l’autorisation '{permission}' : {error}", - "permission_deleted": "Permission '{permission:s}' supprimée", + "permission_deleted": "Permission '{permission}' supprimée", "permission_deletion_failed": "Impossible de supprimer la permission '{permission}' : {error}", "group_already_exist": "Le groupe {group} existe déjà", "group_already_exist_on_system": "Le groupe {group} existe déjà dans les groupes système", @@ -457,7 +457,7 @@ "apps_catalog_init_success": "Système de catalogue d’applications initialisé !", "apps_catalog_failed_to_download": "Impossible de télécharger le catalogue des applications {apps_catalog} : {error}", "diagnosis_mail_outgoing_port_25_blocked": "Le port sortant 25 semble être bloqué. Vous devriez essayer de le débloquer dans le panneau de configuration de votre fournisseur de services Internet (ou hébergeur). En attendant, le serveur ne pourra pas envoyer des courriels à d’autres serveurs.", - "domain_cannot_remove_main_add_new_one": "Vous ne pouvez pas supprimer '{domain:s}' car il s’agit du domaine principal et de votre seul domaine. Vous devez d’abord ajouter un autre domaine à l’aide de 'yunohost domain add ', puis définir comme domaine principal à l’aide de 'yunohost domain main-domain -n ' et vous pouvez ensuite supprimer le domaine '{domain:s}' à l’aide de 'yunohost domain remove {domain:s}'.'", + "domain_cannot_remove_main_add_new_one": "Vous ne pouvez pas supprimer '{domain}' car il s’agit du domaine principal et de votre seul domaine. Vous devez d’abord ajouter un autre domaine à l’aide de 'yunohost domain add ', puis définir comme domaine principal à l’aide de 'yunohost domain main-domain -n ' et vous pouvez ensuite supprimer le domaine '{domain}' à l’aide de 'yunohost domain remove {domain}'.'", "diagnosis_security_vulnerable_to_meltdown_details": "Pour résoudre ce problème, vous devez mettre à niveau votre système et redémarrer pour charger le nouveau noyau Linux (ou contacter votre fournisseur de serveur si cela ne fonctionne pas). Voir https://meltdownattack.com/ pour plus d’informations.", "diagnosis_description_basesystem": "Système de base", "diagnosis_description_ip": "Connectivité Internet", @@ -498,7 +498,7 @@ "diagnosis_description_web": "Web", "diagnosis_basesystem_hardware": "L’architecture du serveur est {virt} {arch}", "group_already_exist_on_system_but_removing_it": "Le groupe {group} est déjà présent dans les groupes du système, mais YunoHost va le supprimer...", - "certmanager_warning_subdomain_dns_record": "Le sous-domaine '{subdomain:s}' ne résout pas vers la même adresse IP que '{domain:s}'. Certaines fonctionnalités seront indisponibles tant que vous n’aurez pas corrigé cela et regénéré le certificat.", + "certmanager_warning_subdomain_dns_record": "Le sous-domaine '{subdomain}' ne résout pas vers la même adresse IP que '{domain}'. Certaines fonctionnalités seront indisponibles tant que vous n’aurez pas corrigé cela et regénéré le certificat.", "domain_cannot_add_xmpp_upload": "Vous ne pouvez pas ajouter de domaine commençant par 'xmpp-upload.'. Ce type de nom est réservé à la fonctionnalité d’upload XMPP intégrée dans YunoHost.", "diagnosis_mail_outgoing_port_25_ok": "Le serveur de messagerie SMTP peut envoyer des courriels (le port sortant 25 n'est pas bloqué).", "diagnosis_mail_outgoing_port_25_blocked_details": "Vous devez d’abord essayer de débloquer le port sortant 25 dans votre interface de routeur Internet ou votre interface d’hébergement. (Certains hébergeurs peuvent vous demander de leur envoyer un ticket de support pour cela).", @@ -594,7 +594,7 @@ "pattern_email_forward": "L'adresse électronique doit être valide, le symbole '+' étant accepté (par exemple : johndoe+yunohost@exemple.com)", "global_settings_setting_smtp_relay_password": "Mot de passe du relais de l'hôte SMTP", "diagnosis_package_installed_from_sury": "Des paquets du système devraient être rétrogradé de version", - "additional_urls_already_added": "URL supplémentaire '{url:s}' déjà ajoutée pour la permission '{permission:s}'", + "additional_urls_already_added": "URL supplémentaire '{url}' déjà ajoutée pour la permission '{permission}'", "unknown_main_domain_path": "Domaine ou chemin inconnu pour '{app}'. Vous devez spécifier un domaine et un chemin pour pouvoir spécifier une URL pour l'autorisation.", "show_tile_cant_be_enabled_for_regex": "Vous ne pouvez pas activer 'show_tile' pour le moment, car l'URL de l'autorisation '{permission}' est une expression régulière", "show_tile_cant_be_enabled_for_url_not_defined": "Vous ne pouvez pas activer 'show_tile' pour le moment, car vous devez d'abord définir une URL pour l'autorisation '{permission}'", @@ -604,10 +604,10 @@ "migration_0019_slapd_config_will_be_overwritten": "Il semble que vous ayez modifié manuellement la configuration de slapd. Pour cette migration critique, YunoHost doit forcer la mise à jour de la configuration slapd. Les fichiers originaux seront sauvegardés dans {conf_backup_folder}.", "migration_0019_add_new_attributes_in_ldap": "Ajouter de nouveaux attributs pour les autorisations dans la base de données LDAP", "migrating_legacy_permission_settings": "Migration des anciens paramètres d'autorisation...", - "invalid_regex": "Regex non valide : '{regex:s}'", + "invalid_regex": "Regex non valide : '{regex}'", "domain_name_unknown": "Domaine '{domain}' inconnu", "app_label_deprecated": "Cette commande est obsolète ! Veuillez utiliser la nouvelle commande 'yunohost user permission update' pour gérer l'étiquette de l'application.", - "additional_urls_already_removed": "URL supplémentaire '{url:s}' déjà supprimées pour la permission '{permission:s}'", + "additional_urls_already_removed": "URL supplémentaire '{url}' déjà supprimées pour la permission '{permission}'", "invalid_number": "Doit être un nombre", "migration_description_0019_extend_permissions_features": "Étendre et retravailler le système de gestion des permissions applicatives", "diagnosis_basesystem_hardware_model": "Le modèle du serveur est {model}", diff --git a/locales/gl.json b/locales/gl.json index dec9a0b0c..fbf1a302a 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -1,9 +1,9 @@ { "password_too_simple_1": "O contrasinal ten que ter 8 caracteres como mínimo", "aborting": "Abortando.", - "app_already_up_to_date": "{app:s} xa está actualizada", + "app_already_up_to_date": "{app} xa está actualizada", "app_already_installed_cant_change_url": "Esta app xa está instalada. O URL non pode cambiarse só con esta acción. Miran en `app changeurl` se está dispoñible.", - "app_already_installed": "{app:s} xa está instalada", + "app_already_installed": "{app} xa está instalada", "app_action_broke_system": "Esta acción semella que estragou estos servizos importantes: {services}", "app_action_cannot_be_ran_because_required_services_down": "Estos servizos requeridos deberían estar en execución para realizar esta acción: {services}. Intenta reinicialos para continuar (e tamén intenta saber por que están apagados).", "already_up_to_date": "Nada que facer. Todo está ao día.", @@ -11,27 +11,27 @@ "admin_password_changed": "Realizado o cambio de contrasinal de administración", "admin_password_change_failed": "Non se puido cambiar o contrasinal", "admin_password": "Contrasinal de administración", - "additional_urls_already_removed": "URL adicional '{url:s}' xa foi eliminada das URL adicionais para o permiso '{permission:s}'", - "additional_urls_already_added": "URL adicional '{url:s}' xa fora engadida ás URL adicionais para o permiso '{permission:s}'", - "action_invalid": "Acción non válida '{action:s}'", - "app_change_url_failed_nginx_reload": "Non se recargou NGINX. Aquí tes a saída de 'nginx -t':\n{nginx_errors:s}", + "additional_urls_already_removed": "URL adicional '{url}' xa foi eliminada das URL adicionais para o permiso '{permission}'", + "additional_urls_already_added": "URL adicional '{url}' xa fora engadida ás URL adicionais para o permiso '{permission}'", + "action_invalid": "Acción non válida '{action}'", + "app_change_url_failed_nginx_reload": "Non se recargou NGINX. Aquí tes a saída de 'nginx -t':\n{nginx_errors}", "app_argument_required": "Requírese o argumento '{name}'", "app_argument_password_no_default": "Erro ao procesar o argumento do contrasinal '{name}': o argumento do contrasinal non pode ter un valor por defecto por razón de seguridade", - "app_argument_invalid": "Elixe un valor válido para o argumento '{name:s}': {error:s}", - "app_argument_choice_invalid": "Usa unha destas opcións '{choices:s}' para o argumento '{name:s}'", - "backup_archive_writing_error": "Non se puideron engadir os ficheiros '{source:s}' (chamados no arquivo '{dest:s}' para ser copiados dentro do arquivo comprimido '{archive:s}'", - "backup_archive_system_part_not_available": "A parte do sistema '{part:s}' non está dispoñible nesta copia", + "app_argument_invalid": "Elixe un valor válido para o argumento '{name}': {error}", + "app_argument_choice_invalid": "Usa unha destas opcións '{choices}' para o argumento '{name}'", + "backup_archive_writing_error": "Non se puideron engadir os ficheiros '{source}' (chamados no arquivo '{dest}' para ser copiados dentro do arquivo comprimido '{archive}'", + "backup_archive_system_part_not_available": "A parte do sistema '{part}' non está dispoñible nesta copia", "backup_archive_corrupted": "Semella que o arquivo de copia '{archive}' está estragado : {error}", "backup_archive_cant_retrieve_info_json": "Non se puido cargar a info desde arquivo '{archive}'... O info.json non s puido obter (ou é un json non válido).", "backup_archive_open_failed": "Non se puido abrir o arquivo de copia de apoio", - "backup_archive_name_unknown": "Arquivo local de copia de apoio descoñecido con nome '{name:s}'", + "backup_archive_name_unknown": "Arquivo local de copia de apoio descoñecido con nome '{name}'", "backup_archive_name_exists": "Xa existe un arquivo de copia con este nome.", - "backup_archive_broken_link": "Non se puido acceder ao arquivo da copia (ligazón rota a {path:s})", - "backup_archive_app_not_found": "Non se atopa {app:s} no arquivo da copia", + "backup_archive_broken_link": "Non se puido acceder ao arquivo da copia (ligazón rota a {path})", + "backup_archive_app_not_found": "Non se atopa {app} no arquivo da copia", "backup_applying_method_tar": "Creando o arquivo TAR da copia...", - "backup_applying_method_custom": "Chamando polo método de copia de apoio personalizado '{method:s}'...", + "backup_applying_method_custom": "Chamando polo método de copia de apoio personalizado '{method}'...", "backup_applying_method_copy": "Copiando tódolos ficheiros necesarios...", - "backup_app_failed": "Non se fixo copia de {app:s}", + "backup_app_failed": "Non se fixo copia de {app}", "backup_actually_backuping": "Creando o arquivo de copia cos ficheiros recollidos...", "backup_abstract_method": "Este método de copia de apoio aínda non foi implementado", "ask_password": "Contrasinal", @@ -49,10 +49,10 @@ "apps_catalog_init_success": "Sistema do catálogo de apps iniciado!", "apps_already_up_to_date": "Xa tes tódalas apps ao día", "app_packaging_format_not_supported": "Esta app non se pode instalar porque o formato de empaquetado non está soportado pola túa versión de YunoHost. Deberías considerar actualizar o teu sistema.", - "app_upgraded": "{app:s} actualizadas", + "app_upgraded": "{app} actualizadas", "app_upgrade_some_app_failed": "Algunhas apps non se puideron actualizar", "app_upgrade_script_failed": "Houbo un fallo interno no script de actualización da app", - "app_upgrade_failed": "Non se actualizou {app:s}: {error}", + "app_upgrade_failed": "Non se actualizou {app}: {error}", "app_upgrade_app_name": "Actualizando {app}...", "app_upgrade_several_apps": "Vanse actualizar as seguintes apps: {apps}", "app_unsupported_remote_type": "Tipo remoto non soportado para a app", @@ -63,14 +63,14 @@ "app_start_install": "Instalando {app}...", "app_sources_fetch_failed": "Non se puideron obter os ficheiros fonte, é o URL correcto?", "app_restore_script_failed": "Houbo un erro interno do script de restablecemento da app", - "app_restore_failed": "Non se puido restablecer {app:s}: {error:s}", + "app_restore_failed": "Non se puido restablecer {app}: {error}", "app_remove_after_failed_install": "Eliminando a app debido ao fallo na instalación...", "app_requirements_unmeet": "Non se cumpren os requerimentos de {app}, o paquete {pkgname} ({version}) debe ser {spec}", "app_requirements_checking": "Comprobando os paquetes requeridos por {app}...", - "app_removed": "{app:s} eliminada", - "app_not_properly_removed": "{app:s} non se eliminou de xeito correcto", - "app_not_installed": "Non se puido atopar {app:s} na lista de apps instaladas: {all_apps}", - "app_not_correctly_installed": "{app:s} semella que non está instalada correctamente", + "app_removed": "{app} eliminada", + "app_not_properly_removed": "{app} non se eliminou de xeito correcto", + "app_not_installed": "Non se puido atopar {app} na lista de apps instaladas: {all_apps}", + "app_not_correctly_installed": "{app} semella que non está instalada correctamente", "app_not_upgraded": "Fallou a actualización da app '{failed_app}', como consecuencia as actualizacións das seguintes apps foron canceladas: {apps}", "app_manifest_install_ask_is_public": "Debería esta app estar exposta ante visitantes anónimas?", "app_manifest_install_ask_admin": "Elixe unha usuaria administradora para esta app", @@ -78,7 +78,7 @@ "app_manifest_install_ask_path": "Elixe a ruta onde queres instalar esta app", "app_manifest_install_ask_domain": "Elixe o dominio onde queres instalar esta app", "app_manifest_invalid": "Hai algún erro no manifesto da app: {error}", - "app_location_unavailable": "Este URL ou ben non está dispoñible ou entra en conflito cunha app(s) xa instalada:\n{apps:s}", + "app_location_unavailable": "Este URL ou ben non está dispoñible ou entra en conflito cunha app(s) xa instalada:\n{apps}", "app_label_deprecated": "Este comando está anticuado! Utiliza o novo comando 'yunohost user permission update' para xestionar a etiqueta da app.", "app_make_default_location_already_used": "Non se puido establecer a '{app}' como app por defecto no dominio, '{domain}' xa está utilizado por '{other_app}'", "app_install_script_failed": "Houbo un fallo interno do script de instalación da app", @@ -87,11 +87,11 @@ "app_id_invalid": "ID da app non válido", "app_full_domain_unavailable": "Lamentámolo, esta app ten que ser instalada nun dominio propio, pero xa tes outras apps instaladas no dominio '{domain}'. Podes usar un subdominio dedicado para esta app.", "app_extraction_failed": "Non se puideron extraer os ficheiros de instalación", - "app_change_url_success": "A URL de {app:s} agora é {domain:s}{path:s}", - "app_change_url_no_script": "A app '{app_name:s}' non soporta o cambio de URL. Pode que debas actualizala.", - "app_change_url_identical_domains": "O antigo e o novo dominio/url_path son idénticos ('{domain:s}{path:s}'), nada que facer.", + "app_change_url_success": "A URL de {app} agora é {domain}{path}", + "app_change_url_no_script": "A app '{app_name}' non soporta o cambio de URL. Pode que debas actualizala.", + "app_change_url_identical_domains": "O antigo e o novo dominio/url_path son idénticos ('{domain}{path}'), nada que facer.", "backup_deleted": "Copia de apoio eliminada", - "backup_delete_error": "Non se eliminou '{path:s}'", + "backup_delete_error": "Non se eliminou '{path}'", "backup_custom_mount_error": "O método personalizado de copia non superou o paso 'mount'", "backup_custom_backup_error": "O método personalizado da copia non superou o paso 'backup'", "backup_csv_creation_failed": "Non se creou o ficheiro CSV necesario para restablecer a copia", @@ -99,14 +99,14 @@ "backup_creation_failed": "Non se puido crear o arquivo de copia de apoio", "backup_create_size_estimation": "O arquivo vai conter arredor de {size} de datos.", "backup_created": "Copia de apoio creada", - "backup_couldnt_bind": "Non se puido ligar {src:s} a {dest:s}.", - "backup_copying_to_organize_the_archive": "Copiando {size:s}MB para organizar o arquivo", + "backup_couldnt_bind": "Non se puido ligar {src} a {dest}.", + "backup_copying_to_organize_the_archive": "Copiando {size}MB para organizar o arquivo", "backup_cleaning_failed": "Non se puido baleirar o cartafol temporal para a copia", "backup_cant_mount_uncompress_archive": "Non se puido montar o arquivo sen comprimir porque está protexido contra escritura", - "backup_ask_for_copying_if_needed": "Queres realizar a copia de apoio utilizando temporalmente {size:s}MB? (Faise deste xeito porque algúns ficheiros non hai xeito de preparalos usando unha forma máis eficiente).", + "backup_ask_for_copying_if_needed": "Queres realizar a copia de apoio utilizando temporalmente {size}MB? (Faise deste xeito porque algúns ficheiros non hai xeito de preparalos usando unha forma máis eficiente).", "backup_running_hooks": "Executando os ganchos da copia...", - "backup_permission": "Permiso de copia para {app:s}", - "backup_output_symlink_dir_broken": "O directorio de arquivo '{path:s}' é unha ligazón simbólica rota. Pode ser que esqueceses re/montar ou conectar o medio de almacenaxe ao que apunta.", + "backup_permission": "Permiso de copia para {app}", + "backup_output_symlink_dir_broken": "O directorio de arquivo '{path}' é unha ligazón simbólica rota. Pode ser que esqueceses re/montar ou conectar o medio de almacenaxe ao que apunta.", "backup_output_directory_required": "Debes proporcionar un directorio de saída para a copia", "backup_output_directory_not_empty": "Debes elexir un directorio de saída baleiro", "backup_output_directory_forbidden": "Elixe un directorio de saída diferente. As copias non poden crearse en /bin, /boot, /dev, /etc, /lib, /root, /sbin, /sys, /usr, /var ou subcartafoles de /home/yunohost.backup/archives", @@ -114,34 +114,34 @@ "backup_no_uncompress_archive_dir": "Non hai tal directorio do arquivo descomprimido", "backup_mount_archive_for_restore": "Preparando o arquivo para restauración...", "backup_method_tar_finished": "Creouse o arquivo de copia TAR", - "backup_method_custom_finished": "O método de copia personalizado '{method:s}' rematou", + "backup_method_custom_finished": "O método de copia personalizado '{method}' rematou", "backup_method_copy_finished": "Rematou o copiado dos ficheiros", - "backup_hook_unknown": "O gancho da copia '{hook:s}' é descoñecido", - "certmanager_domain_cert_not_selfsigned": "O certificado para o dominio {domain:s} non está auto-asinado. Tes a certeza de querer substituílo? (Usa '--force' para facelo.)", + "backup_hook_unknown": "O gancho da copia '{hook}' é descoñecido", + "certmanager_domain_cert_not_selfsigned": "O certificado para o dominio {domain} non está auto-asinado. Tes a certeza de querer substituílo? (Usa '--force' para facelo.)", "certmanager_domain_not_diagnosed_yet": "Por agora non hai resultado de diagnóstico para o dominio {domain}. Volve facer o diagnóstico para a categoría 'Rexistros DNS' e 'Web' na sección de diagnóstico para comprobar se o dominio é compatible con Let's Encrypt. (Ou se sabes o que estás a facer, usa '--no-checks' para desactivar esas comprobacións.)", - "certmanager_certificate_fetching_or_enabling_failed": "Fallou o intento de usar o novo certificado para '{domain:s}'...", + "certmanager_certificate_fetching_or_enabling_failed": "Fallou o intento de usar o novo certificado para '{domain}'...", "certmanager_cert_signing_failed": "Non se puido asinar o novo certificado", - "certmanager_cert_renew_success": "Certificado Let's Encrypt renovado para o dominio '{domain:s}'", - "certmanager_cert_install_success_selfsigned": "O certificado auto-asinado está instalado para o dominio '{domain:s}'", - "certmanager_cert_install_success": "O certificado Let's Encrypt está instalado para o dominio '{domain:s}'", - "certmanager_cannot_read_cert": "Algo fallou ao intentar abrir o certificado actual para o dominio {domain:s} (ficheiro: {file:s}), razón: {reason:s}", - "certmanager_attempt_to_replace_valid_cert": "Estás intentando sobrescribir un certificado correcto e en bo estado para o dominio {domain:s}! (Usa --force para obviar)", - "certmanager_attempt_to_renew_valid_cert": "O certificado para o dominio '{domain:s}' non caduca pronto! (Podes usar --force se sabes o que estás a facer)", - "certmanager_attempt_to_renew_nonLE_cert": "O certificado para o dominio '{domain:s}' non está proporcionado por Let's Encrypt. Non se pode renovar automáticamente!", + "certmanager_cert_renew_success": "Certificado Let's Encrypt renovado para o dominio '{domain}'", + "certmanager_cert_install_success_selfsigned": "O certificado auto-asinado está instalado para o dominio '{domain}'", + "certmanager_cert_install_success": "O certificado Let's Encrypt está instalado para o dominio '{domain}'", + "certmanager_cannot_read_cert": "Algo fallou ao intentar abrir o certificado actual para o dominio {domain} (ficheiro: {file}), razón: {reason}", + "certmanager_attempt_to_replace_valid_cert": "Estás intentando sobrescribir un certificado correcto e en bo estado para o dominio {domain}! (Usa --force para obviar)", + "certmanager_attempt_to_renew_valid_cert": "O certificado para o dominio '{domain}' non caduca pronto! (Podes usar --force se sabes o que estás a facer)", + "certmanager_attempt_to_renew_nonLE_cert": "O certificado para o dominio '{domain}' non está proporcionado por Let's Encrypt. Non se pode renovar automáticamente!", "certmanager_acme_not_configured_for_domain": "Non se realizou o desafío ACME para {domain} porque a súa configuración nginx non ten a parte do código correspondente... Comproba que a túa configuración nginx está ao día utilizando `yunohost tools regen-conf nginx --dry-run --with-diff`.", - "backup_with_no_restore_script_for_app": "'{app:s}' non ten script de restablecemento, non poderás restablecer automáticamente a copia de apoio desta app.", - "backup_with_no_backup_script_for_app": "A app '{app:s}' non ten script para a copia. Ignorada.", + "backup_with_no_restore_script_for_app": "'{app}' non ten script de restablecemento, non poderás restablecer automáticamente a copia de apoio desta app.", + "backup_with_no_backup_script_for_app": "A app '{app}' non ten script para a copia. Ignorada.", "backup_unable_to_organize_files": "Non se puido usar o método rápido para organizar ficheiros no arquivo", - "backup_system_part_failed": "Non se puido facer copia da parte do sistema '{part:s}'", - "certmanager_domain_http_not_working": "O dominio {domain:s} semella non ser accesible a través de HTTP. Comproba a categoría 'Web' no diagnóstico para máis info. (Se sabes o que estás a facer, utiliza '--no-checks' para obviar estas comprobacións.)", - "certmanager_domain_dns_ip_differs_from_public_ip": "Os rexistros DNS para o dominio '{domain:s}' son diferentes aos da IP deste servidor. Comproba a categoría 'Rexistros DNS' (básico) no diagnóstico para ter máis info. Se cambiaches recentemente o rexistro A, agarda a que se propague o cambio (están dispoñibles ferramentas en liña para comprobar estos cambios). (Se sabes o que estás a facer, utiliza '--no-checks' para obviar estas comprobacións.)", - "confirm_app_install_danger": "PERIGO! Esta app aínda é experimental (pode que nin funcione)! Probablemente NON deberías instalala a non ser que sepas o que estás a facer. NON TERÁS SOPORTE nin axuda se esta app estraga o teu sistema... Se queres asumir o risco, escribe '{answers:s}'", - "confirm_app_install_warning": "Aviso: Esta app podería funcionar, pero non está ben integrada en YunoHost. Algunhas funcións como a identificación centralizada e as copias de apoio poderían non estar dispoñibles. Desexas instalala igualmente? [{answers:s}] ", - "certmanager_unable_to_parse_self_CA_name": "Non se puido obter o nome da autoridade do auto-asinado (ficheiro: {file:s})", - "certmanager_self_ca_conf_file_not_found": "Non se atopa o ficheiro de configuración para a autoridade de auto-asinado (ficheiro: {file:s})", - "certmanager_no_cert_file": "Non se puido ler o ficheiro do certificado para o dominio {domain:s} (ficheiro: {file:s})", - "certmanager_hit_rate_limit": "Recentemente crearonse demasiados certificados para este mesmo grupo de dominios {domain:s}. Inténtao máis tarde. Podes ler https://letsencrypt.org/docs/rate-limits/ para máis info", - "certmanager_warning_subdomain_dns_record": "O subdominio '{subdomain:s}' non resolve a mesmo enderezo IP que '{domain:s}'. Algunhas funcións non estarán dispoñibles ata que arranxes isto e rexeneres o certificado.", + "backup_system_part_failed": "Non se puido facer copia da parte do sistema '{part}'", + "certmanager_domain_http_not_working": "O dominio {domain} semella non ser accesible a través de HTTP. Comproba a categoría 'Web' no diagnóstico para máis info. (Se sabes o que estás a facer, utiliza '--no-checks' para obviar estas comprobacións.)", + "certmanager_domain_dns_ip_differs_from_public_ip": "Os rexistros DNS para o dominio '{domain}' son diferentes aos da IP deste servidor. Comproba a categoría 'Rexistros DNS' (básico) no diagnóstico para ter máis info. Se cambiaches recentemente o rexistro A, agarda a que se propague o cambio (están dispoñibles ferramentas en liña para comprobar estos cambios). (Se sabes o que estás a facer, utiliza '--no-checks' para obviar estas comprobacións.)", + "confirm_app_install_danger": "PERIGO! Esta app aínda é experimental (pode que nin funcione)! Probablemente NON deberías instalala a non ser que sepas o que estás a facer. NON TERÁS SOPORTE nin axuda se esta app estraga o teu sistema... Se queres asumir o risco, escribe '{answers}'", + "confirm_app_install_warning": "Aviso: Esta app podería funcionar, pero non está ben integrada en YunoHost. Algunhas funcións como a identificación centralizada e as copias de apoio poderían non estar dispoñibles. Desexas instalala igualmente? [{answers}] ", + "certmanager_unable_to_parse_self_CA_name": "Non se puido obter o nome da autoridade do auto-asinado (ficheiro: {file})", + "certmanager_self_ca_conf_file_not_found": "Non se atopa o ficheiro de configuración para a autoridade de auto-asinado (ficheiro: {file})", + "certmanager_no_cert_file": "Non se puido ler o ficheiro do certificado para o dominio {domain} (ficheiro: {file})", + "certmanager_hit_rate_limit": "Recentemente crearonse demasiados certificados para este mesmo grupo de dominios {domain}. Inténtao máis tarde. Podes ler https://letsencrypt.org/docs/rate-limits/ para máis info", + "certmanager_warning_subdomain_dns_record": "O subdominio '{subdomain}' non resolve a mesmo enderezo IP que '{domain}'. Algunhas funcións non estarán dispoñibles ata que arranxes isto e rexeneres o certificado.", "diagnosis_found_errors_and_warnings": "Atopado(s) {errors} problema(s) significativo(s) (e {warnings} avisos(s)) en relación a {category}!", "diagnosis_found_errors": "Atopado(s) {errors} problema significativo(s) relacionado con {category}!", "diagnosis_ignored_issues": "(+ {nb_ignored} problema ignorado(s))", @@ -159,8 +159,8 @@ "diagnosis_basesystem_host": "O servidor está a executar Debian {debian_version}", "diagnosis_basesystem_hardware_model": "O modelo de servidor é {model}", "diagnosis_basesystem_hardware": "A arquitectura do hardware do servidor é {virt} {arch}", - "custom_app_url_required": "Tes que proporcionar o URL para actualizar a app personalizada {app:s}", - "confirm_app_install_thirdparty": "PERIGO! Esta app non forma parte do catálogo de YunoHost. Ao instalar apps de terceiros poderías comprometer a integridade e seguridade do sistema. Probablemente NON deberías instalala a menos que saibas o que fas. NON SE PROPORCIONARÁ SOPORTE se esta app non funciona ou estraga o sistema... Se aínda así asumes o risco, escribe '{answers:s}'", + "custom_app_url_required": "Tes que proporcionar o URL para actualizar a app personalizada {app}", + "confirm_app_install_thirdparty": "PERIGO! Esta app non forma parte do catálogo de YunoHost. Ao instalar apps de terceiros poderías comprometer a integridade e seguridade do sistema. Probablemente NON deberías instalala a menos que saibas o que fas. NON SE PROPORCIONARÁ SOPORTE se esta app non funciona ou estraga o sistema... Se aínda así asumes o risco, escribe '{answers}'", "diagnosis_dns_point_to_doc": "Revisa a documentación en https://yunohost.org/dns_config se precisas axuda para configurar os rexistros DNS.", "diagnosis_dns_discrepancy": "O seguinte rexistro DNS non segue a configuración recomendada:
Tipo: {type}
Nome: {name}
Valor actual: {current}
Valor agardado: {value}", "diagnosis_dns_missing_record": "Facendo caso á configuración DNS recomendada, deberías engadir un rexistro DNS coa seguinte info.
Tipo: {type}
Nome: {name}
Valor: {value}", @@ -261,7 +261,7 @@ "diagnosis_security_vulnerable_to_meltdown": "Semella que es vulnerable á vulnerabilidade crítica de seguridade Meltdown", "diagnosis_rootfstotalspace_critical": "O sistema de ficheiros root só ten un total de {space} e podería ser preocupante! Probablemente esgotes o espazo no disco moi pronto! Recomendamos ter un sistema de ficheiros root de polo menos 16 GB.", "diagnosis_rootfstotalspace_warning": "O sistema de ficheiros root só ten un total de {space}. Podería ser suficiente, mais pon tino porque poderías esgotar o espazo no disco rápidamente... Recoméndase ter polo meno 16 GB para o sistema de ficheiros root.", - "domain_cannot_remove_main": "Non podes eliminar '{domain:s}' porque é o dominio principal, primeiro tes que establecer outro dominio como principal usando 'yunohost domain main-domain -n '; aquí tes a lista dos dominios posibles: {other_domains:s}", + "domain_cannot_remove_main": "Non podes eliminar '{domain}' porque é o dominio principal, primeiro tes que establecer outro dominio como principal usando 'yunohost domain main-domain -n '; aquí tes a lista dos dominios posibles: {other_domains}", "diagnosis_sshd_config_inconsistent_details": "Executa yunohost settings set security.ssh.port -v O_TEU_PORTO_SSH para definir o porto SSH, comproba con yunohost tools regen-conf ssh --dry-run --with-diff e restablece a configuración con yunohost tools regen-conf ssh --force a configuración recomendada de YunoHost.", "diagnosis_sshd_config_inconsistent": "Semella que o porto SSH foi modificado manualmente en /etc/ssh/sshd_config. Desde YunoHost 4.2, un novo axuste global 'security.ssh.port' está dispoñible para evitar a edición manual da configuración.", "diagnosis_sshd_config_insecure": "Semella que a configuración SSH modificouse manualmente, e é insegura porque non contén unha directiva 'AllowGroups' ou 'AllowUsers' para limitar o acceso ás usuarias autorizadas.", @@ -275,12 +275,12 @@ "diagnosis_http_bad_status_code": "Semella que outra máquina (podería ser o rúter de internet) respondeu no lugar do teu servidor.
1. A razón máis habitual para este problema é que o porto 80 (e 443) non están correctamente redirixidos ao teu servidor.
2. En configuracións avanzadas: revisa que nin o cortalumes nin o proxy-inverso están interferindo.", "diagnosis_http_connection_error": "Erro de conexión: non se puido conectar co dominio solicitado, moi probablemente non sexa accesible.", "diagnosis_http_timeout": "Caducou a conexión mentras se intentaba contactar o servidor desde o exterior. Non semella accesible.
1. A razón máis habitual é que o porto 80 (e 443) non están correctamente redirixidos ao teu servidor.
2. Deberías comprobar tamén que o servizo nginx está a funcionar
3. En configuracións máis avanzadas: revisa que nin o cortalumes nin o proxy-inverso están interferindo.", - "field_invalid": "Campo non válido '{:s}'", + "field_invalid": "Campo non válido '{}'", "experimental_feature": "Aviso: esta característica é experimental e non se considera estable, non deberías utilizala a menos que saibas o que estás a facer.", "extracting": "Extraendo...", - "dyndns_unavailable": "O dominio '{domain:s}' non está dispoñible.", - "dyndns_domain_not_provided": "O provedor DynDNS {provider:s} non pode proporcionar o dominio {domain:s}.", - "dyndns_registration_failed": "Non se rexistrou o dominio DynDNS: {error:s}", + "dyndns_unavailable": "O dominio '{domain}' non está dispoñible.", + "dyndns_domain_not_provided": "O provedor DynDNS {provider} non pode proporcionar o dominio {domain}.", + "dyndns_registration_failed": "Non se rexistrou o dominio DynDNS: {error}", "dyndns_registered": "Dominio DynDNS rexistrado", "dyndns_provider_unreachable": "Non se puido acadar o provedor DynDNS {provider}: pode que o teu YunoHost non teña conexión a internet ou que o servidor dynette non funcione.", "dyndns_no_domain_registered": "Non hai dominio rexistrado con DynDNS", @@ -288,8 +288,8 @@ "dyndns_key_generating": "Creando chave DNS... podería demorarse.", "dyndns_ip_updated": "Actualizouse o IP en DynDNS", "dyndns_ip_update_failed": "Non se actualizou o enderezo IP en DynDNS", - "dyndns_could_not_check_available": "Non se comprobou se {domain:s} está dispoñible en {provider:s}.", - "dyndns_could_not_check_provide": "Non se comprobou se {provider:s} pode proporcionar {domain:s}.", + "dyndns_could_not_check_available": "Non se comprobou se {domain} está dispoñible en {provider}.", + "dyndns_could_not_check_provide": "Non se comprobou se {provider} pode proporcionar {domain}.", "dpkg_lock_not_available": "Non se pode executar agora mesmo este comando porque semella que outro programa está a utilizar dpkg (o xestos de paquetes do sistema)", "dpkg_is_broken": "Non podes facer isto agora mesmo porque dpkg/APT (o xestor de paquetes do sistema) semella que non está a funcionar... Podes intentar solucionalo conectándote a través de SSH e executando `sudo apt install --fix-broken`e/ou `sudo dpkg --configure -a`.", "downloading": "Descargando…", @@ -309,8 +309,8 @@ "domain_creation_failed": "Non se puido crear o dominio {domain}: {error}", "domain_created": "Dominio creado", "domain_cert_gen_failed": "Non se puido crear o certificado", - "domain_cannot_remove_main_add_new_one": "Non podes eliminar '{domain:s}' porque é o dominio principal e único dominio, primeiro tes que engadir outro dominio usando 'yunohost domain add ', e despois establecelo como o dominio principal utilizando 'yunohost domain main-domain -n ' e entón poderás eliminar '{domain:s}' con 'yunohost domain remove {domain:s}'.'", + "domain_cannot_remove_main_add_new_one": "Non podes eliminar '{domain}' porque é o dominio principal e único dominio, primeiro tes que engadir outro dominio usando 'yunohost domain add ', e despois establecelo como o dominio principal utilizando 'yunohost domain main-domain -n ' e entón poderás eliminar '{domain}' con 'yunohost domain remove {domain}'.'", "domain_cannot_add_xmpp_upload": "Non podes engadir dominios que comecen con 'xmpp-upload.'. Este tipo de nome está reservado para a función se subida de XMPP integrada en YunoHost.", - "file_does_not_exist": "O ficheiro {path:s} non existe.", + "file_does_not_exist": "O ficheiro {path} non existe.", "firewall_reload_failed": "Non se puido recargar o cortalumes" } diff --git a/locales/hi.json b/locales/hi.json index f84745264..5f521b1dc 100644 --- a/locales/hi.json +++ b/locales/hi.json @@ -1,49 +1,49 @@ { - "action_invalid": "अवैध कार्रवाई '{action:s}'", + "action_invalid": "अवैध कार्रवाई '{action}'", "admin_password": "व्यवस्थापक पासवर्ड", "admin_password_change_failed": "पासवर्ड बदलने में असमर्थ", "admin_password_changed": "व्यवस्थापक पासवर्ड बदल दिया गया है", - "app_already_installed": "'{app:s}' पहले से ही इंस्टाल्ड है", - "app_argument_choice_invalid": "गलत तर्क का चयन किया गया '{name:s}' , तर्क इन विकल्पों में से होने चाहिए {choices:s}", - "app_argument_invalid": "तर्क के लिए अमान्य मान '{name:s}': {error:s}", - "app_argument_required": "तर्क '{name:s}' की आवश्यकता है", + "app_already_installed": "'{app}' पहले से ही इंस्टाल्ड है", + "app_argument_choice_invalid": "गलत तर्क का चयन किया गया '{name}' , तर्क इन विकल्पों में से होने चाहिए {choices}", + "app_argument_invalid": "तर्क के लिए अमान्य मान '{name}': {error}", + "app_argument_required": "तर्क '{name}' की आवश्यकता है", "app_extraction_failed": "इन्सटाल्ड फ़ाइलों को निकालने में असमर्थ", "app_id_invalid": "अवैध एप्लिकेशन id", "app_install_files_invalid": "फाइलों की अमान्य स्थापना", "app_manifest_invalid": "एप्लीकेशन का मैनिफेस्ट अमान्य", - "app_not_correctly_installed": "{app:s} ठीक ढंग से इनस्टॉल नहीं हुई", - "app_not_installed": "{app:s} इनस्टॉल नहीं हुई", - "app_not_properly_removed": "{app:s} ठीक ढंग से नहीं अनइन्सटॉल की गई", - "app_removed": "{app:s} को अनइन्सटॉल कर दिया गया", + "app_not_correctly_installed": "{app} ठीक ढंग से इनस्टॉल नहीं हुई", + "app_not_installed": "{app} इनस्टॉल नहीं हुई", + "app_not_properly_removed": "{app} ठीक ढंग से नहीं अनइन्सटॉल की गई", + "app_removed": "{app} को अनइन्सटॉल कर दिया गया", "app_requirements_checking": "जरूरी पैकेजेज़ की जाँच हो रही है ....", "app_requirements_unmeet": "आवश्यकताए पूरी नहीं हो सकी, पैकेज {pkgname}({version})यह होना चाहिए {spec}", "app_sources_fetch_failed": "सोर्स फाइल्स प्राप्त करने में असमर्थ", "app_unknown": "अनजान एप्लीकेशन", "app_unsupported_remote_type": "एप्लीकेशन के लिए उन्सुपपोर्टेड रिमोट टाइप इस्तेमाल किया गया", - "app_upgrade_failed": "{app:s} अपडेट करने में असमर्थ", - "app_upgraded": "{app:s} अपडेट हो गयी हैं", + "app_upgrade_failed": "{app} अपडेट करने में असमर्थ", + "app_upgraded": "{app} अपडेट हो गयी हैं", "ask_firstname": "नाम", "ask_lastname": "अंतिम नाम", "ask_main_domain": "मुख्य डोमेन", "ask_new_admin_password": "नया व्यवस्थापक पासवर्ड", "ask_password": "पासवर्ड", - "backup_app_failed": "एप्लीकेशन का बैकअप करने में असमर्थ '{app:s}'", - "backup_archive_app_not_found": "'{app:s}' बैकअप आरचिव में नहीं मिला", + "backup_app_failed": "एप्लीकेशन का बैकअप करने में असमर्थ '{app}'", + "backup_archive_app_not_found": "'{app}' बैकअप आरचिव में नहीं मिला", "backup_archive_name_exists": "इस बैकअप आरचिव का नाम पहले से ही मौजूद है", - "backup_archive_name_unknown": "'{name:s}' इस नाम की लोकल बैकअप आरचिव मौजूद नहीं", + "backup_archive_name_unknown": "'{name}' इस नाम की लोकल बैकअप आरचिव मौजूद नहीं", "backup_archive_open_failed": "बैकअप आरचिव को खोलने में असमर्थ", "backup_cleaning_failed": "टेम्पोरेरी बैकअप डायरेक्टरी को उड़ने में असमर्थ", "backup_created": "बैकअप सफलतापूर्वक किया गया", "backup_creation_failed": "बैकअप बनाने में विफल", - "backup_delete_error": "'{path:s}' डिलीट करने में असमर्थ", + "backup_delete_error": "'{path}' डिलीट करने में असमर्थ", "backup_deleted": "इस बैकअप को डिलीट दिया गया है", - "backup_hook_unknown": "'{hook:s}' यह बैकअप हुक नहीं मिला", + "backup_hook_unknown": "'{hook}' यह बैकअप हुक नहीं मिला", "backup_nothings_done": "सेव करने के लिए कुछ नहीं", "backup_output_directory_forbidden": "निषिद्ध आउटपुट डायरेक्टरी। निम्न दिए गए डायरेक्टरी में बैकअप नहीं बन सकता /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var और /home/yunohost.backup/archives के सब-फोल्डर।", "backup_output_directory_not_empty": "आउटपुट डायरेक्टरी खाली नहीं है", "backup_output_directory_required": "बैकअप करने के लिए आउट पुट डायरेक्टरी की आवश्यकता है", "backup_running_hooks": "बैकअप हुक्स चल रहे है...", - "custom_app_url_required": "आप को अपनी कस्टम एप्लिकेशन '{app:s}' को अपग्रेड करने के लिए यूआरएल(URL) देने की आवश्यकता है", + "custom_app_url_required": "आप को अपनी कस्टम एप्लिकेशन '{app}' को अपग्रेड करने के लिए यूआरएल(URL) देने की आवश्यकता है", "domain_cert_gen_failed": "सर्टिफिकेट उत्पन करने में असमर्थ", "domain_created": "डोमेन बनाया गया", "domain_creation_failed": "डोमेन बनाने में असमर्थ", diff --git a/locales/hu.json b/locales/hu.json index 3ba14286f..9c482a370 100644 --- a/locales/hu.json +++ b/locales/hu.json @@ -1,14 +1,14 @@ { "aborting": "Megszakítás.", - "action_invalid": "Érvénytelen művelet '{action:s}'", + "action_invalid": "Érvénytelen művelet '{action}'", "admin_password": "Adminisztrátori jelszó", "admin_password_change_failed": "Nem lehet a jelszót megváltoztatni", "admin_password_changed": "Az adminisztrátori jelszó megváltozott", - "app_already_installed": "{app:s} már telepítve van", + "app_already_installed": "{app} már telepítve van", "app_already_installed_cant_change_url": "Ez az app már telepítve van. Ezzel a funkcióval az url nem változtatható. Javaslat 'app url változtatás' ha lehetséges.", - "app_already_up_to_date": "{app:s} napra kész", - "app_argument_choice_invalid": "{name:s} érvénytelen választás, csak egyike lehet {choices:s} közül", - "app_argument_invalid": "'{name:s}' hibás paraméter érték :{error:s}", - "app_argument_required": "Parameter '{name:s}' kötelező", + "app_already_up_to_date": "{app} napra kész", + "app_argument_choice_invalid": "{name} érvénytelen választás, csak egyike lehet {choices} közül", + "app_argument_invalid": "'{name}' hibás paraméter érték :{error}", + "app_argument_required": "Parameter '{name}' kötelező", "password_too_simple_1": "A jelszónak legalább 8 karakter hosszúnak kell lennie" } \ No newline at end of file diff --git a/locales/it.json b/locales/it.json index 17a7522e1..b2731aba0 100644 --- a/locales/it.json +++ b/locales/it.json @@ -1,7 +1,7 @@ { - "app_already_installed": "{app:s} è già installata", + "app_already_installed": "{app} è già installata", "app_extraction_failed": "Impossibile estrarre i file di installazione", - "app_not_installed": "Impossibile trovare l'applicazione {app:s} nell'elenco delle applicazioni installate: {all_apps}", + "app_not_installed": "Impossibile trovare l'applicazione {app} nell'elenco delle applicazioni installate: {all_apps}", "app_unknown": "Applicazione sconosciuta", "ask_password": "Password", "backup_archive_name_exists": "Il nome dell'archivio del backup è già esistente.", @@ -11,15 +11,15 @@ "domain_exists": "Il dominio esiste già", "pattern_email": "L'indirizzo email deve essere valido, senza simboli '+' (es. tizio@dominio.com)", "pattern_mailbox_quota": "La dimensione deve avere un suffisso b/k/M/G/T o 0 per disattivare la quota", - "port_already_opened": "La porta {port:d} è già aperta per {ip_version:s} connessioni", - "service_add_failed": "Impossibile aggiungere il servizio '{service:s}'", - "service_cmd_exec_failed": "Impossibile eseguire il comando '{command:s}'", - "service_disabled": "Il servizio '{service:s}' non partirà più al boot di sistema.", - "service_remove_failed": "Impossibile rimuovere il servizio '{service:s}'", - "service_removed": "Servizio '{service:s}' rimosso", - "service_stop_failed": "Impossibile fermare il servizio '{service:s}'\n\nRegistri di servizio recenti:{logs:s}", + "port_already_opened": "La porta {port:d} è già aperta per {ip_version} connessioni", + "service_add_failed": "Impossibile aggiungere il servizio '{service}'", + "service_cmd_exec_failed": "Impossibile eseguire il comando '{command}'", + "service_disabled": "Il servizio '{service}' non partirà più al boot di sistema.", + "service_remove_failed": "Impossibile rimuovere il servizio '{service}'", + "service_removed": "Servizio '{service}' rimosso", + "service_stop_failed": "Impossibile fermare il servizio '{service}'\n\nRegistri di servizio recenti:{logs}", "system_username_exists": "Il nome utente esiste già negli utenti del sistema", - "unrestore_app": "{app:s} non verrà ripristinata", + "unrestore_app": "{app} non verrà ripristinata", "upgrading_packages": "Aggiornamento dei pacchetti...", "user_deleted": "Utente cancellato", "admin_password": "Password dell'amministrazione", @@ -27,39 +27,39 @@ "admin_password_changed": "La password d'amministrazione è stata cambiata", "app_install_files_invalid": "Questi file non possono essere installati", "app_manifest_invalid": "C'è qualcosa di scorretto nel manifesto dell'applicazione: {error}", - "app_not_correctly_installed": "{app:s} sembra di non essere installata correttamente", - "app_not_properly_removed": "{app:s} non è stata correttamente rimossa", - "action_invalid": "L'azione '{action:s}' non è valida", - "app_removed": "{app:s} rimossa", + "app_not_correctly_installed": "{app} sembra di non essere installata correttamente", + "app_not_properly_removed": "{app} non è stata correttamente rimossa", + "action_invalid": "L'azione '{action}' non è valida", + "app_removed": "{app} rimossa", "app_sources_fetch_failed": "Impossibile riportare i file sorgenti, l'URL è corretto?", - "app_upgrade_failed": "Impossibile aggiornare {app:s}: {error}", - "app_upgraded": "{app:s} aggiornata", + "app_upgrade_failed": "Impossibile aggiornare {app}: {error}", + "app_upgraded": "{app} aggiornata", "app_requirements_checking": "Controllo i pacchetti richiesti per {app}...", "app_requirements_unmeet": "Requisiti non soddisfatti per {app}, il pacchetto {pkgname} ({version}) deve essere {spec}", "ask_firstname": "Nome", "ask_lastname": "Cognome", "ask_main_domain": "Dominio principale", "ask_new_admin_password": "Nuova password dell'amministrazione", - "backup_app_failed": "Non è possibile fare il backup {app:s}", - "backup_archive_app_not_found": "{app:s} non è stata trovata nel archivio di backup", - "app_argument_choice_invalid": "Usa una delle seguenti scelte '{choices:s}' per il parametro '{name:s}'", - "app_argument_invalid": "Scegli un valore valido per il parametro '{name:s}': {error:s}", - "app_argument_required": "L'argomento '{name:s}' è requisito", + "backup_app_failed": "Non è possibile fare il backup {app}", + "backup_archive_app_not_found": "{app} non è stata trovata nel archivio di backup", + "app_argument_choice_invalid": "Usa una delle seguenti scelte '{choices}' per il parametro '{name}'", + "app_argument_invalid": "Scegli un valore valido per il parametro '{name}': {error}", + "app_argument_required": "L'argomento '{name}' è requisito", "app_id_invalid": "Identificativo dell'applicazione non valido", "app_unsupported_remote_type": "Il tipo remoto usato per l'applicazione non è supportato", - "backup_archive_broken_link": "Non è possibile accedere all'archivio di backup (link rotto verso {path:s})", - "backup_archive_name_unknown": "Archivio di backup locale chiamato '{name:s}' sconosciuto", + "backup_archive_broken_link": "Non è possibile accedere all'archivio di backup (link rotto verso {path})", + "backup_archive_name_unknown": "Archivio di backup locale chiamato '{name}' sconosciuto", "backup_archive_open_failed": "Impossibile aprire l'archivio di backup", "backup_cleaning_failed": "Non è possibile pulire la directory temporanea di backup", "backup_creation_failed": "Impossibile creare l'archivio di backup", - "backup_delete_error": "Impossibile cancellare '{path:s}'", + "backup_delete_error": "Impossibile cancellare '{path}'", "backup_deleted": "Backup cancellato", - "backup_hook_unknown": "Hook di backup '{hook:s}' sconosciuto", + "backup_hook_unknown": "Hook di backup '{hook}' sconosciuto", "backup_nothings_done": "Niente da salvare", "backup_output_directory_forbidden": "Scegli una diversa directory di output. I backup non possono esser creati nelle sotto-cartelle /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var o /home/yunohost.backup/archives", "backup_output_directory_required": "Devi fornire una directory di output per il backup", "backup_running_hooks": "Esecuzione degli hook di backup…", - "custom_app_url_required": "Devi fornire un URL per essere in grado di aggiornare l'applicazione personalizzata {app:s}", + "custom_app_url_required": "Devi fornire un URL per essere in grado di aggiornare l'applicazione personalizzata {app}", "domain_creation_failed": "Impossibile creare il dominio {domain}: {error}", "domain_deleted": "Dominio cancellato", "domain_deletion_failed": "Impossibile cancellare il dominio {domain}: {error}", @@ -77,26 +77,26 @@ "dyndns_key_not_found": "La chiave DNS non è stata trovata per il dominio", "dyndns_no_domain_registered": "Nessuno dominio registrato con DynDNS", "dyndns_registered": "Dominio DynDNS registrato", - "dyndns_registration_failed": "Non è possibile registrare il dominio DynDNS: {error:s}", - "dyndns_unavailable": "Il dominio {domain:s} non disponibile.", + "dyndns_registration_failed": "Non è possibile registrare il dominio DynDNS: {error}", + "dyndns_unavailable": "Il dominio {domain} non disponibile.", "extracting": "Estrazione...", - "field_invalid": "Campo '{:s}' non valido", + "field_invalid": "Campo '{}' non valido", "firewall_reload_failed": "Impossibile ricaricare il firewall", "firewall_reloaded": "Firewall ricaricato", "firewall_rules_cmd_failed": "Alcune regole del firewall sono fallite. Per ulteriori informazioni, vedi il registro.", - "hook_exec_failed": "Impossibile eseguire lo script: {path:s}", - "hook_exec_not_terminated": "Los script non è stato eseguito correttamente: {path:s}", - "hook_name_unknown": "Nome di hook '{name:s}' sconosciuto", + "hook_exec_failed": "Impossibile eseguire lo script: {path}", + "hook_exec_not_terminated": "Los script non è stato eseguito correttamente: {path}", + "hook_name_unknown": "Nome di hook '{name}' sconosciuto", "installation_complete": "Installazione completata", "ip6tables_unavailable": "Non puoi giocare con ip6tables qui. O sei in un container o il tuo kernel non lo supporta", "iptables_unavailable": "Non puoi giocare con iptables qui. O sei in un container o il tuo kernel non lo supporta", - "mail_alias_remove_failed": "Impossibile rimuovere l'alias mail '{mail:s}'", - "mail_domain_unknown": "Indirizzo mail non valido per il dominio '{domain:s}'. Usa un dominio gestito da questo server.", - "mail_forward_remove_failed": "Impossibile rimuovere la mail inoltrata '{mail:s}'", + "mail_alias_remove_failed": "Impossibile rimuovere l'alias mail '{mail}'", + "mail_domain_unknown": "Indirizzo mail non valido per il dominio '{domain}'. Usa un dominio gestito da questo server.", + "mail_forward_remove_failed": "Impossibile rimuovere la mail inoltrata '{mail}'", "mailbox_used_space_dovecot_down": "La casella di posta elettronica Dovecot deve essere attivato se vuoi recuperare lo spazio usato dalla posta elettronica", "main_domain_change_failed": "Impossibile cambiare il dominio principale", "main_domain_changed": "Il dominio principale è stato cambiato", - "not_enough_disk_space": "Non c'è abbastanza spazio libero in '{path:s}'", + "not_enough_disk_space": "Non c'è abbastanza spazio libero in '{path}'", "packages_upgrade_failed": "Impossibile aggiornare tutti i pacchetti", "pattern_backup_archive_name": "Deve essere un nome di file valido di massimo 30 caratteri di lunghezza, con caratteri alfanumerici e \"-_.\" come unica punteggiatura", "pattern_domain": "Deve essere un nome di dominio valido (es. il-mio-dominio.org)", @@ -106,32 +106,32 @@ "pattern_port_or_range": "Deve essere un numero di porta valido (es. 0-65535) o una fascia di porte valida (es. 100:200)", "pattern_positive_number": "Deve essere un numero positivo", "pattern_username": "Caratteri minuscoli alfanumerici o trattini bassi soli", - "port_already_closed": "La porta {port:d} è già chiusa per le connessioni {ip_version:s}", - "restore_already_installed_app": "Un'applicazione con l'ID '{app:s}' è già installata", - "app_restore_failed": "Impossibile ripristinare l'applicazione '{app:s}': {error:s}", + "port_already_closed": "La porta {port:d} è già chiusa per le connessioni {ip_version}", + "restore_already_installed_app": "Un'applicazione con l'ID '{app}' è già installata", + "app_restore_failed": "Impossibile ripristinare l'applicazione '{app}': {error}", "restore_cleaning_failed": "Impossibile pulire la directory temporanea di ripristino", "restore_complete": "Ripristino completo", - "restore_confirm_yunohost_installed": "Sei sicuro di volere ripristinare un sistema già installato? {answers:s}", + "restore_confirm_yunohost_installed": "Sei sicuro di volere ripristinare un sistema già installato? {answers}", "restore_failed": "Impossibile ripristinare il sistema", "user_update_failed": "Impossibile aggiornare l'utente {user}: {error}", - "restore_hook_unavailable": "Lo script di ripristino per '{part:s}' non è disponibile per il tuo sistema e non è nemmeno nell'archivio", + "restore_hook_unavailable": "Lo script di ripristino per '{part}' non è disponibile per il tuo sistema e non è nemmeno nell'archivio", "restore_nothings_done": "Nulla è stato ripristinato", - "restore_running_app_script": "Ripristino dell'app '{app:s}'…", + "restore_running_app_script": "Ripristino dell'app '{app}'…", "restore_running_hooks": "Esecuzione degli hook di ripristino…", - "service_added": "Il servizio '{service:s}' è stato aggiunto", - "service_already_started": "Il servizio '{service:s}' è già avviato", - "service_already_stopped": "Il servizio '{service:s}' è già stato fermato", - "service_disable_failed": "Impossibile disabilitare l'avvio al boot del servizio '{service:s}'\n\nRegistri di servizio recenti:{logs:s}", - "service_enable_failed": "Impossibile eseguire il servizio '{service:s}' al boot di sistema.\n\nRegistri di servizio recenti:{logs:s}", - "service_enabled": "Il servizio '{service:s}' si avvierà automaticamente al boot di sistema.", - "service_start_failed": "Impossibile eseguire il servizio '{service:s}'\n\nRegistri di servizio recenti:{logs:s}", - "service_started": "Servizio '{service:s}' avviato", - "service_stopped": "Servizio '{service:s}' fermato", - "service_unknown": "Servizio '{service:s}' sconosciuto", + "service_added": "Il servizio '{service}' è stato aggiunto", + "service_already_started": "Il servizio '{service}' è già avviato", + "service_already_stopped": "Il servizio '{service}' è già stato fermato", + "service_disable_failed": "Impossibile disabilitare l'avvio al boot del servizio '{service}'\n\nRegistri di servizio recenti:{logs}", + "service_enable_failed": "Impossibile eseguire il servizio '{service}' al boot di sistema.\n\nRegistri di servizio recenti:{logs}", + "service_enabled": "Il servizio '{service}' si avvierà automaticamente al boot di sistema.", + "service_start_failed": "Impossibile eseguire il servizio '{service}'\n\nRegistri di servizio recenti:{logs}", + "service_started": "Servizio '{service}' avviato", + "service_stopped": "Servizio '{service}' fermato", + "service_unknown": "Servizio '{service}' sconosciuto", "ssowat_conf_generated": "La configurazione SSOwat rigenerata", "ssowat_conf_updated": "Configurazione SSOwat aggiornata", "system_upgraded": "Sistema aggiornato", - "unbackup_app": "{app:s} non verrà salvata", + "unbackup_app": "{app} non verrà salvata", "unexpected_error": "È successo qualcosa di inatteso: {error}", "unlimit": "Nessuna quota", "updating_apt_cache": "Recupero degli aggiornamenti disponibili per i pacchetti di sistema...", @@ -144,54 +144,54 @@ "user_creation_failed": "Impossibile creare l'utente {user}: {error}", "user_deletion_failed": "Impossibile cancellare l'utente {user}: {error}", "user_home_creation_failed": "Impossibile creare la 'home' directory del utente", - "user_unknown": "Utente sconosciuto: {user:s}", + "user_unknown": "Utente sconosciuto: {user}", "user_updated": "Info dell'utente cambiate", "yunohost_already_installed": "YunoHost è già installato", "yunohost_configured": "YunoHost ora è configurato", "yunohost_installing": "Installazione di YunoHost...", "yunohost_not_installed": "YunoHost non è correttamente installato. Esegui 'yunohost tools postinstall'", "domain_cert_gen_failed": "Impossibile generare il certificato", - "certmanager_attempt_to_replace_valid_cert": "Stai provando a sovrascrivere un certificato buono e valido per il dominio {domain:s}! (Usa --force per ignorare)", - "certmanager_domain_cert_not_selfsigned": "Il certificato per il dominio {domain:s} non è auto-firmato. Sei sicuro di volere sostituirlo? (Usa '--force')", - "certmanager_certificate_fetching_or_enabling_failed": "Il tentativo di usare il nuovo certificato per {domain:s} non funziona...", - "certmanager_attempt_to_renew_nonLE_cert": "Il certificato per il dominio {domain:s} non è emesso da Let's Encrypt. Impossibile rinnovarlo automaticamente!", - "certmanager_attempt_to_renew_valid_cert": "Il certificato per il dominio {domain:s} non è in scadenza! (Puoi usare --force per forzare se sai quel che stai facendo)", - "certmanager_domain_http_not_working": "Il dominio {domain:s} non sembra accessibile attraverso HTTP. Verifica nella sezione 'Web' nella diagnosi per maggiori informazioni. (Se sai cosa stai facendo, usa '--no-checks' per disattivare i controlli.)", + "certmanager_attempt_to_replace_valid_cert": "Stai provando a sovrascrivere un certificato buono e valido per il dominio {domain}! (Usa --force per ignorare)", + "certmanager_domain_cert_not_selfsigned": "Il certificato per il dominio {domain} non è auto-firmato. Sei sicuro di volere sostituirlo? (Usa '--force')", + "certmanager_certificate_fetching_or_enabling_failed": "Il tentativo di usare il nuovo certificato per {domain} non funziona...", + "certmanager_attempt_to_renew_nonLE_cert": "Il certificato per il dominio {domain} non è emesso da Let's Encrypt. Impossibile rinnovarlo automaticamente!", + "certmanager_attempt_to_renew_valid_cert": "Il certificato per il dominio {domain} non è in scadenza! (Puoi usare --force per forzare se sai quel che stai facendo)", + "certmanager_domain_http_not_working": "Il dominio {domain} non sembra accessibile attraverso HTTP. Verifica nella sezione 'Web' nella diagnosi per maggiori informazioni. (Se sai cosa stai facendo, usa '--no-checks' per disattivare i controlli.)", "app_already_installed_cant_change_url": "Questa applicazione è già installata. L'URL non può essere cambiato solo da questa funzione. Controlla se `app changeurl` è disponibile.", - "app_already_up_to_date": "{app:s} è già aggiornata", - "app_change_url_failed_nginx_reload": "Non riesco a riavviare NGINX. Questo è il risultato di 'nginx -t':\n{nginx_errors:s}", - "app_change_url_identical_domains": "Il vecchio ed il nuovo dominio/percorso_url sono identici ('{domain:s}{path:s}'), nessuna operazione necessaria.", - "app_change_url_no_script": "L'applicazione '{app_name:s}' non supporta ancora la modifica dell'URL. Forse dovresti aggiornarla.", - "app_change_url_success": "L'URL dell'applicazione {app:s} è stato cambiato in {domain:s}{path:s}", + "app_already_up_to_date": "{app} è già aggiornata", + "app_change_url_failed_nginx_reload": "Non riesco a riavviare NGINX. Questo è il risultato di 'nginx -t':\n{nginx_errors}", + "app_change_url_identical_domains": "Il vecchio ed il nuovo dominio/percorso_url sono identici ('{domain}{path}'), nessuna operazione necessaria.", + "app_change_url_no_script": "L'applicazione '{app_name}' non supporta ancora la modifica dell'URL. Forse dovresti aggiornarla.", + "app_change_url_success": "L'URL dell'applicazione {app} è stato cambiato in {domain}{path}", "app_make_default_location_already_used": "Impostazione dell'applicazione '{app}' come predefinita del dominio non riuscita perché il dominio '{domain}' è in uso per dall'applicazione '{other_app}'", - "app_location_unavailable": "Questo URL non è più disponibile o va in conflitto con la/le applicazione/i già installata/e:\n{apps:s}", + "app_location_unavailable": "Questo URL non è più disponibile o va in conflitto con la/le applicazione/i già installata/e:\n{apps}", "app_upgrade_app_name": "Aggiornamento di {app}...", "app_upgrade_some_app_failed": "Alcune applicazioni non possono essere aggiornate", "backup_abstract_method": "Questo metodo di backup deve essere ancora implementato", "backup_applying_method_copy": "Copiando tutti i files nel backup...", - "backup_applying_method_custom": "Chiamando il metodo di backup personalizzato '{method:s}'...", + "backup_applying_method_custom": "Chiamando il metodo di backup personalizzato '{method}'...", "backup_applying_method_tar": "Creando l'archivio TAR del backup...", - "backup_archive_system_part_not_available": "La parte di sistema '{part:s}' non è disponibile in questo backup", - "backup_archive_writing_error": "Impossibile aggiungere i file '{source:s}' (indicati nell'archivio '{dest:s}') al backup nell'archivio compresso '{archive:s}'", - "backup_ask_for_copying_if_needed": "Vuoi effettuare il backup usando {size:s}MB temporaneamente? (È necessario usare questo sistema poiché alcuni file non possono essere preparati in un modo più efficiente)", + "backup_archive_system_part_not_available": "La parte di sistema '{part}' non è disponibile in questo backup", + "backup_archive_writing_error": "Impossibile aggiungere i file '{source}' (indicati nell'archivio '{dest}') al backup nell'archivio compresso '{archive}'", + "backup_ask_for_copying_if_needed": "Vuoi effettuare il backup usando {size}MB temporaneamente? (È necessario usare questo sistema poiché alcuni file non possono essere preparati in un modo più efficiente)", "backup_cant_mount_uncompress_archive": "Impossibile montare in modalità sola lettura la cartella di archivio non compressa", - "backup_copying_to_organize_the_archive": "Copiando {size:s}MB per organizzare l'archivio", - "backup_couldnt_bind": "Impossibile legare {src:s} a {dest:s}.", + "backup_copying_to_organize_the_archive": "Copiando {size}MB per organizzare l'archivio", + "backup_couldnt_bind": "Impossibile legare {src} a {dest}.", "backup_csv_addition_failed": "Impossibile aggiungere file del backup nel file CSV", "backup_csv_creation_failed": "Impossibile creare il file CVS richiesto per le operazioni di ripristino", "backup_custom_backup_error": "Il metodo di backup personalizzato è fallito allo step 'backup'", "backup_custom_mount_error": "Il metodo di backup personalizzato è fallito allo step 'mount'", "backup_method_copy_finished": "Copia di backup terminata", - "backup_method_custom_finished": "Metodo di backup personalizzato '{method:s}' terminato", + "backup_method_custom_finished": "Metodo di backup personalizzato '{method}' terminato", "backup_method_tar_finished": "Archivio TAR di backup creato", "backup_no_uncompress_archive_dir": "La cartella di archivio non compressa non esiste", - "backup_system_part_failed": "Impossibile creare il backup della parte di sistema '{part:s}'", + "backup_system_part_failed": "Impossibile creare il backup della parte di sistema '{part}'", "backup_unable_to_organize_files": "Impossibile organizzare i file nell'archivio con il metodo veloce", - "backup_with_no_backup_script_for_app": "L'app {app:s} non ha script di backup. Ignorata.", - "backup_with_no_restore_script_for_app": "L'app {app:s} non ha script di ripristino, non sarai in grado di ripristinarla automaticamente dal backup di questa app.", + "backup_with_no_backup_script_for_app": "L'app {app} non ha script di backup. Ignorata.", + "backup_with_no_restore_script_for_app": "L'app {app} non ha script di ripristino, non sarai in grado di ripristinarla automaticamente dal backup di questa app.", "certmanager_acme_not_configured_for_domain": "La challenge ACME non può validare il {domain} perché la relativa configurazione di nginx è mancante... Assicurati che la tua configurazione di nginx sia aggiornata con il comando `yunohost tools regen-conf nginx --dry-run --with-diff`.", - "certmanager_cannot_read_cert": "Qualcosa è andato storto nel tentativo di aprire il certificato attuale per il dominio {domain:s} (file: {file:s}), motivo: {reason:s}", - "certmanager_cert_install_success": "Certificato Let's Encrypt per il dominio {domain:s} installato", + "certmanager_cannot_read_cert": "Qualcosa è andato storto nel tentativo di aprire il certificato attuale per il dominio {domain} (file: {file}), motivo: {reason}", + "certmanager_cert_install_success": "Certificato Let's Encrypt per il dominio {domain} installato", "aborting": "Annullamento.", "admin_password_too_long": "Per favore scegli una password più corta di 127 caratteri", "app_not_upgraded": "Impossibile aggiornare le applicazioni '{failed_app}' e di conseguenza l'aggiornamento delle seguenti applicazione è stato cancellato: {apps}", @@ -204,8 +204,8 @@ "ask_new_path": "Nuovo percorso", "backup_actually_backuping": "Creazione di un archivio di backup con i file raccolti...", "backup_mount_archive_for_restore": "Preparazione dell'archivio per il ripristino…", - "certmanager_cert_install_success_selfsigned": "Certificato autofirmato installato con successo per il dominio {domain:s}", - "certmanager_cert_renew_success": "Certificato di Let's Encrypt rinnovato con successo per il dominio {domain:s}", + "certmanager_cert_install_success_selfsigned": "Certificato autofirmato installato con successo per il dominio {domain}", + "certmanager_cert_renew_success": "Certificato di Let's Encrypt rinnovato con successo per il dominio {domain}", "certmanager_cert_signing_failed": "Impossibile firmare il nuovo certificato", "good_practices_about_user_password": "Ora stai per impostare una nuova password utente. La password dovrebbe essere di almeno 8 caratteri - anche se è buona pratica utilizzare password più lunghe (es. una sequenza di parole) e/o utilizzare vari tipi di caratteri (maiuscole, minuscole, numeri e simboli).", "password_listed": "Questa password è una tra le più utilizzate al mondo. Per favore scegline una più unica.", @@ -214,38 +214,38 @@ "password_too_simple_3": "La password deve essere lunga almeno 8 caratteri e contenere numeri, maiuscole e minuscole e simboli", "password_too_simple_4": "La password deve essere lunga almeno 12 caratteri e contenere numeri, maiuscole e minuscole", "app_action_cannot_be_ran_because_required_services_down": "I seguenti servizi dovrebbero essere in funzione per completare questa azione: {services}. Prova a riavviarli per proseguire (e possibilmente cercare di capire come ma non funzionano più).", - "backup_output_symlink_dir_broken": "La tua cartella d'archivio '{path:s}' è un link simbolico interrotto. Probabilmente hai dimenticato di montare o montare nuovamente il supporto al quale punta il link.", - "certmanager_domain_dns_ip_differs_from_public_ip": "I record DNS per il dominio '{domain:s}' è diverso dall'IP di questo server. Controlla la sezione (basic) 'Record DNS' nella diagnosi per maggiori informazioni. Se hai modificato recentemente il tuo valore A, attendi che si propaghi (esistono online alcuni siti per il controllo della propagazione DNS). (Se sai cosa stai facendo, usa '--no-checks' per disattivare i controlli.)", - "certmanager_hit_rate_limit": "Troppi certificati già rilasciati per questa esatta serie di domini {domain:s} recentemente. Per favore riprova più tardi. Guarda https://letsencrypt.org/docs/rate-limits/ per maggiori dettagli", - "certmanager_no_cert_file": "Impossibile leggere il file di certificato per il dominio {domain:s} (file: {file:s})", - "certmanager_self_ca_conf_file_not_found": "File di configurazione non trovato per l'autorità di auto-firma (file: {file:s})", - "certmanager_unable_to_parse_self_CA_name": "Impossibile analizzare il nome dell'autorità di auto-firma (file: {file:s})", - "confirm_app_install_warning": "Attenzione: Questa applicazione potrebbe funzionare, ma non è ben integrata in YunoHost. Alcune funzionalità come il single sign-on e il backup/ripristino potrebbero non essere disponibili. Installare comunque? [{answers:s}] ", - "confirm_app_install_danger": "ATTENZIONE! Questa applicazione è ancora sperimentale (se non esplicitamente dichiarata non funzionante)! Probabilmente NON dovresti installarla a meno che tu non sappia cosa stai facendo. NESSUN SUPPORTO verrà dato se quest'app non funziona o se rompe il tuo sistema... Se comunque accetti di prenderti questo rischio,digita '{answers:s}'", - "confirm_app_install_thirdparty": "PERICOLO! Quest'applicazione non fa parte del catalogo YunoHost. Installando app di terze parti potresti compromettere l'integrita e la sicurezza del tuo sistema. Probabilmente NON dovresti installarla a meno che tu non sappia cosa stai facendo. NESSUN SUPPORTO verrà dato se quest'app non funziona o se rompe il tuo sistema... Se comunque accetti di prenderti questo rischio, digita '{answers:s}'", + "backup_output_symlink_dir_broken": "La tua cartella d'archivio '{path}' è un link simbolico interrotto. Probabilmente hai dimenticato di montare o montare nuovamente il supporto al quale punta il link.", + "certmanager_domain_dns_ip_differs_from_public_ip": "I record DNS per il dominio '{domain}' è diverso dall'IP di questo server. Controlla la sezione (basic) 'Record DNS' nella diagnosi per maggiori informazioni. Se hai modificato recentemente il tuo valore A, attendi che si propaghi (esistono online alcuni siti per il controllo della propagazione DNS). (Se sai cosa stai facendo, usa '--no-checks' per disattivare i controlli.)", + "certmanager_hit_rate_limit": "Troppi certificati già rilasciati per questa esatta serie di domini {domain} recentemente. Per favore riprova più tardi. Guarda https://letsencrypt.org/docs/rate-limits/ per maggiori dettagli", + "certmanager_no_cert_file": "Impossibile leggere il file di certificato per il dominio {domain} (file: {file})", + "certmanager_self_ca_conf_file_not_found": "File di configurazione non trovato per l'autorità di auto-firma (file: {file})", + "certmanager_unable_to_parse_self_CA_name": "Impossibile analizzare il nome dell'autorità di auto-firma (file: {file})", + "confirm_app_install_warning": "Attenzione: Questa applicazione potrebbe funzionare, ma non è ben integrata in YunoHost. Alcune funzionalità come il single sign-on e il backup/ripristino potrebbero non essere disponibili. Installare comunque? [{answers}] ", + "confirm_app_install_danger": "ATTENZIONE! Questa applicazione è ancora sperimentale (se non esplicitamente dichiarata non funzionante)! Probabilmente NON dovresti installarla a meno che tu non sappia cosa stai facendo. NESSUN SUPPORTO verrà dato se quest'app non funziona o se rompe il tuo sistema... Se comunque accetti di prenderti questo rischio,digita '{answers}'", + "confirm_app_install_thirdparty": "PERICOLO! Quest'applicazione non fa parte del catalogo YunoHost. Installando app di terze parti potresti compromettere l'integrita e la sicurezza del tuo sistema. Probabilmente NON dovresti installarla a meno che tu non sappia cosa stai facendo. NESSUN SUPPORTO verrà dato se quest'app non funziona o se rompe il tuo sistema... Se comunque accetti di prenderti questo rischio, digita '{answers}'", "dpkg_is_broken": "Non puoi eseguire questo ora perchè dpkg/APT (i gestori di pacchetti del sistema) sembrano essere in stato danneggiato... Puoi provare a risolvere il problema connettendoti via SSH ed eseguire `sudo apt install --fix-broken` e/o `sudo dpkg --configure -a`.", - "domain_cannot_remove_main": "Non puoi rimuovere '{domain:s}' essendo il dominio principale, prima devi impostare un nuovo dominio principale con il comando 'yunohost domain main-domain -n '; ecco la lista dei domini candidati: {other_domains:s}", + "domain_cannot_remove_main": "Non puoi rimuovere '{domain}' essendo il dominio principale, prima devi impostare un nuovo dominio principale con il comando 'yunohost domain main-domain -n '; ecco la lista dei domini candidati: {other_domains}", "domain_dns_conf_is_just_a_recommendation": "Questo comando ti mostra la configurazione *raccomandata*. Non ti imposta la configurazione DNS al tuo posto. È tua responsabilità configurare la tua zona DNS nel tuo registrar in accordo con queste raccomandazioni.", - "dyndns_could_not_check_provide": "Impossibile controllare se {provider:s} possano fornire {domain:s}.", - "dyndns_could_not_check_available": "Impossibile controllare se {domain:s} è disponibile su {provider:s}.", - "dyndns_domain_not_provided": "Il fornitore DynDNS {provider:s} non può fornire il dominio {domain:s}.", + "dyndns_could_not_check_provide": "Impossibile controllare se {provider} possano fornire {domain}.", + "dyndns_could_not_check_available": "Impossibile controllare se {domain} è disponibile su {provider}.", + "dyndns_domain_not_provided": "Il fornitore DynDNS {provider} non può fornire il dominio {domain}.", "experimental_feature": "Attenzione: Questa funzionalità è sperimentale e non è considerata stabile, non dovresti utilizzarla a meno che tu non sappia cosa stai facendo.", - "file_does_not_exist": "Il file {path:s} non esiste.", - "global_settings_bad_choice_for_enum": "Scelta sbagliata per l'impostazione {setting:s}, ricevuta '{choice:s}', ma le scelte disponibili sono: {available_choices:s}", - "global_settings_bad_type_for_setting": "Tipo errato per l'impostazione {setting:s}, ricevuto {received_type:s}, atteso {expected_type:s}", - "global_settings_cant_open_settings": "Apertura del file delle impostazioni non riuscita, motivo: {reason:s}", - "global_settings_cant_serialize_settings": "Serializzazione dei dati delle impostazioni non riuscita, motivo: {reason:s}", - "global_settings_cant_write_settings": "Scrittura del file delle impostazioni non riuscita, motivo: {reason:s}", - "global_settings_key_doesnt_exists": "La chiave '{settings_key:s}' non esiste nelle impostazioni globali, puoi vedere tutte le chiavi disponibili eseguendo 'yunohost settings list'", - "global_settings_reset_success": "Le impostazioni precedenti sono state salvate in {path:s}", + "file_does_not_exist": "Il file {path} non esiste.", + "global_settings_bad_choice_for_enum": "Scelta sbagliata per l'impostazione {setting}, ricevuta '{choice}', ma le scelte disponibili sono: {available_choices}", + "global_settings_bad_type_for_setting": "Tipo errato per l'impostazione {setting}, ricevuto {received_type}, atteso {expected_type}", + "global_settings_cant_open_settings": "Apertura del file delle impostazioni non riuscita, motivo: {reason}", + "global_settings_cant_serialize_settings": "Serializzazione dei dati delle impostazioni non riuscita, motivo: {reason}", + "global_settings_cant_write_settings": "Scrittura del file delle impostazioni non riuscita, motivo: {reason}", + "global_settings_key_doesnt_exists": "La chiave '{settings_key}' non esiste nelle impostazioni globali, puoi vedere tutte le chiavi disponibili eseguendo 'yunohost settings list'", + "global_settings_reset_success": "Le impostazioni precedenti sono state salvate in {path}", "already_up_to_date": "Niente da fare. Tutto è già aggiornato.", "global_settings_setting_security_nginx_compatibility": "Bilanciamento tra compatibilità e sicurezza per il server web NGIX. Riguarda gli algoritmi di cifratura (e altri aspetti legati alla sicurezza)", "global_settings_setting_security_password_admin_strength": "Complessità della password di amministratore", "global_settings_setting_security_password_user_strength": "Complessità della password utente", "global_settings_setting_security_ssh_compatibility": "Bilanciamento tra compatibilità e sicurezza per il server SSH. Riguarda gli algoritmi di cifratura (e altri aspetti legati alla sicurezza)", - "global_settings_unknown_setting_from_settings_file": "Chiave sconosciuta nelle impostazioni: '{setting_key:s}', scartata e salvata in /etc/yunohost/settings-unknown.json", + "global_settings_unknown_setting_from_settings_file": "Chiave sconosciuta nelle impostazioni: '{setting_key}', scartata e salvata in /etc/yunohost/settings-unknown.json", "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Consenti l'uso del hostkey DSA (deprecato) per la configurazione del demone SSH", - "global_settings_unknown_type": "Situazione inaspettata, l'impostazione {setting:s} sembra essere di tipo {unknown_type:s} ma non è un tipo supportato dal sistema.", + "global_settings_unknown_type": "Situazione inaspettata, l'impostazione {setting} sembra essere di tipo {unknown_type} ma non è un tipo supportato dal sistema.", "good_practices_about_admin_password": "Stai per impostare una nuova password di amministratore. La password deve essere almeno di 8 caratteri - anche se è buona pratica utilizzare password più lunghe (es. una frase, una serie di parole) e/o utilizzare vari tipi di caratteri (maiuscole, minuscole, numeri e simboli).", "log_corrupted_md_file": "Il file dei metadati YAML associato con i registri è danneggiato: '{md_file}'\nErrore: {error}", "log_link_to_log": "Registro completo di questa operazione: '{desc}'", @@ -299,7 +299,7 @@ "backup_archive_cant_retrieve_info_json": "Impossibile caricare informazione per l'archivio '{archive}'... Impossibile scaricare info.json (oppure non è un json valido).", "app_packaging_format_not_supported": "Quest'applicazione non può essere installata perché il formato non è supportato dalla vostra versione di YunoHost. Dovreste considerare di aggiornare il vostro sistema.", "certmanager_domain_not_diagnosed_yet": "Non c'è ancora alcun risultato di diagnosi per il dominio {domain}. Riavvia una diagnosi per la categoria 'DNS records' e 'Web' nella sezione di diagnosi per verificare se il dominio è pronto per Let's Encrypt. (Se sai cosa stai facendo, usa '--no-checks' per disattivare i controlli.)", - "backup_permission": "Backup dei permessi per {app:s}", + "backup_permission": "Backup dei permessi per {app}", "ask_user_domain": "Dominio da usare per l'indirizzo email e l'account XMPP dell'utente", "app_manifest_install_ask_is_public": "Quest'applicazione dovrà essere visibile ai visitatori anonimi?", "app_manifest_install_ask_admin": "Scegli un utente amministratore per quest'applicazione", @@ -307,16 +307,16 @@ "app_manifest_install_ask_path": "Scegli il percorso dove installare quest'applicazione", "app_manifest_install_ask_domain": "Scegli il dominio dove installare quest'app", "app_argument_password_no_default": "Errore durante il parsing dell'argomento '{name}': l'argomento password non può avere un valore di default per ragioni di sicurezza", - "additional_urls_already_added": "L'URL aggiuntivo '{url:s}' è già utilizzato come URL aggiuntivo per il permesso '{permission:s}'", + "additional_urls_already_added": "L'URL aggiuntivo '{url}' è già utilizzato come URL aggiuntivo per il permesso '{permission}'", "diagnosis_basesystem_ynh_inconsistent_versions": "Stai eseguendo versioni incompatibili dei pacchetti YunoHost... probabilmente a causa di aggiornamenti falliti o parziali.", "diagnosis_basesystem_ynh_main_version": "Il server sta eseguendo YunoHost {main_version} ({repo})", "diagnosis_basesystem_ynh_single_version": "Versione {package}: {version} ({repo})", "diagnosis_basesystem_kernel": "Il server sta eseguendo Linux kernel {kernel_version}", "diagnosis_basesystem_host": "Il server sta eseguendo Debian {debian_version}", "diagnosis_basesystem_hardware": "L'architettura hardware del server è {virt} {arch}", - "certmanager_warning_subdomain_dns_record": "Il sottodominio '{subdomain:s}' non si risolve nello stesso indirizzo IP di '{domain:s}'. Alcune funzioni non saranno disponibili finchè questa cosa non verrà sistemata e rigenerato il certificato.", + "certmanager_warning_subdomain_dns_record": "Il sottodominio '{subdomain}' non si risolve nello stesso indirizzo IP di '{domain}'. Alcune funzioni non saranno disponibili finchè questa cosa non verrà sistemata e rigenerato il certificato.", "app_label_deprecated": "Questo comando è deprecato! Utilizza il nuovo comando 'yunohost user permission update' per gestire la label dell'app.", - "additional_urls_already_removed": "L'URL aggiuntivo '{url:s}' è già stato rimosso come URL aggiuntivo per il permesso '{permission:s}'", + "additional_urls_already_removed": "L'URL aggiuntivo '{url}' è già stato rimosso come URL aggiuntivo per il permesso '{permission}'", "diagnosis_services_bad_status_tip": "Puoi provare a riavviare il servizio, e se non funziona, controlla ai log del servizio in amministrazione (dalla linea di comando, puoi farlo con yunohost service restart {service} e yunohost service log {service}).", "diagnosis_services_bad_status": "Il servizio {service} è {status} :(", "diagnosis_services_conf_broken": "Il servizio {service} è mal-configurato!", @@ -407,12 +407,12 @@ "tools_upgrade_at_least_one": "Specifica 'apps', o 'system'", "show_tile_cant_be_enabled_for_regex": "Non puoi abilitare 'show_tile' in questo momento, perché l'URL del permesso '{permission}' è una regex", "show_tile_cant_be_enabled_for_url_not_defined": "Non puoi abilitare 'show_tile' in questo momento, devi prima definire un URL per il permesso '{permission}'", - "service_reloaded_or_restarted": "Il servizio '{service:s}' è stato ricaricato o riavviato", - "service_reload_or_restart_failed": "Impossibile ricaricare o riavviare il servizio '{service:s}'\n\nUltimi registri del servizio: {logs:s}", - "service_restarted": "Servizio '{service:s}' riavviato", - "service_restart_failed": "Impossibile riavviare il servizio '{service:s}'\n\nUltimi registri del servizio: {logs:s}", - "service_reloaded": "Servizio '{service:s}' ricaricato", - "service_reload_failed": "Impossibile ricaricare il servizio '{service:s}'\n\nUltimi registri del servizio: {logs:s}", + "service_reloaded_or_restarted": "Il servizio '{service}' è stato ricaricato o riavviato", + "service_reload_or_restart_failed": "Impossibile ricaricare o riavviare il servizio '{service}'\n\nUltimi registri del servizio: {logs}", + "service_restarted": "Servizio '{service}' riavviato", + "service_restart_failed": "Impossibile riavviare il servizio '{service}'\n\nUltimi registri del servizio: {logs}", + "service_reloaded": "Servizio '{service}' ricaricato", + "service_reload_failed": "Impossibile ricaricare il servizio '{service}'\n\nUltimi registri del servizio: {logs}", "service_regen_conf_is_deprecated": "'yunohost service regen-conf' è obsoleto! Per favore usa 'yunohost tools regen-conf' al suo posto.", "service_description_yunohost-firewall": "Gestisce l'apertura e la chiusura delle porte ai servizi", "service_description_yunohost-api": "Gestisce l'interazione tra l'interfaccia web YunoHost ed il sistema", @@ -429,13 +429,13 @@ "service_description_dovecot": "Consente ai client mail di accedere/recuperare le email (via IMAP e POP3)", "service_description_dnsmasq": "Gestisce la risoluzione dei domini (DNS)", "service_description_avahi-daemon": "Consente di raggiungere il tuo server eseguendo 'yunohost.local' sulla tua LAN", - "server_reboot_confirm": "Il server si riavvierà immediatamente, sei sicuro? [{answers:s}]", + "server_reboot_confirm": "Il server si riavvierà immediatamente, sei sicuro? [{answers}]", "server_reboot": "Il server si riavvierà", - "server_shutdown_confirm": "Il server si spegnerà immediatamente, sei sicuro? [{answers:s}]", + "server_shutdown_confirm": "Il server si spegnerà immediatamente, sei sicuro? [{answers}]", "server_shutdown": "Il server si spegnerà", "root_password_replaced_by_admin_password": "La tua password di root è stata sostituita dalla tua password d'amministratore.", "root_password_desynchronized": "La password d'amministratore è stata cambiata, ma YunoHost non ha potuto propagarla alla password di root!", - "restore_system_part_failed": "Impossibile ripristinare la sezione di sistema '{part:s}'", + "restore_system_part_failed": "Impossibile ripristinare la sezione di sistema '{part}'", "restore_removing_tmp_dir_failed": "Impossibile rimuovere una vecchia directory temporanea", "restore_not_enough_disk_space": "Spazio libero insufficiente (spazio: {free_space:d}B, necessario: {needed_space:d}B, margine di sicurezza: {margin:d}B)", "restore_may_be_not_enough_disk_space": "Il tuo sistema non sembra avere abbastanza spazio (libero: {free_space:d}B, necessario: {needed_space:d}B, margine di sicurezza: {margin:d}B)", @@ -461,14 +461,14 @@ "regenconf_file_backed_up": "File di configurazione '{conf}' salvato in '{backup}'", "permission_require_account": "Il permesso {permission} ha senso solo per gli utenti con un account, quindi non può essere attivato per i visitatori.", "permission_protected": "Il permesso {permission} è protetto. Non puoi aggiungere o rimuovere il gruppo visitatori dal permesso.", - "permission_updated": "Permesso '{permission:s}' aggiornato", + "permission_updated": "Permesso '{permission}' aggiornato", "permission_update_failed": "Impossibile aggiornare il permesso '{permission}': {error}", - "permission_not_found": "Permesso '{permission:s}' non trovato", + "permission_not_found": "Permesso '{permission}' non trovato", "permission_deletion_failed": "Impossibile cancellare il permesso '{permission}': {error}", - "permission_deleted": "Permesso '{permission:s}' cancellato", + "permission_deleted": "Permesso '{permission}' cancellato", "permission_currently_allowed_for_all_users": "Il permesso è attualmente garantito a tutti gli utenti oltre gli altri gruppi. Probabilmente vuoi o rimuovere il permesso 'all_user' o rimuovere gli altri gruppi per cui è garantito attualmente.", "permission_creation_failed": "Impossibile creare i permesso '{permission}': {error}", - "permission_created": "Permesso '{permission:s}' creato", + "permission_created": "Permesso '{permission}' creato", "permission_cannot_remove_main": "Non è possibile rimuovere un permesso principale", "permission_already_up_to_date": "Il permesso non è stato aggiornato perché la richiesta di aggiunta/rimozione è già coerente con lo stato attuale.", "permission_already_exist": "Permesso '{permission}' esiste già", @@ -523,7 +523,7 @@ "migration_description_0016_php70_to_php73_pools": "MIgra i file di configurazione 'pool' di php7.0-fpm su php7.3", "migration_description_0015_migrate_to_buster": "Aggiorna il sistema a Debian Buster e YunoHost 4.X", "migrating_legacy_permission_settings": "Impostando le impostazioni legacy dei permessi..", - "mailbox_disabled": "E-mail disabilitate per l'utente {user:s}", + "mailbox_disabled": "E-mail disabilitate per l'utente {user}", "log_user_permission_reset": "Resetta il permesso '{}'", "log_user_permission_update": "Aggiorna gli accessi del permesso '{}'", "log_user_group_update": "Aggiorna il gruppo '{}'", @@ -536,14 +536,14 @@ "log_app_config_show_panel": "Mostra il pannello di configurazione dell'app '{}'", "log_app_action_run": "Esegui l'azione dell'app '{}'", "log_operation_unit_unclosed_properly": "Operazion unit non è stata chiusa correttamente", - "invalid_regex": "Regex invalida:'{regex:s}'", + "invalid_regex": "Regex invalida:'{regex}'", "hook_list_by_invalid": "Questa proprietà non può essere usata per listare gli hooks", - "hook_json_return_error": "Impossibile leggere la risposta del hook {path:s}. Errore: {msg:s}. Contenuto raw: {raw_content}", + "hook_json_return_error": "Impossibile leggere la risposta del hook {path}. Errore: {msg}. Contenuto raw: {raw_content}", "group_user_not_in_group": "L'utente {user} non è nel gruppo {group}", "group_user_already_in_group": "L'utente {user} è già nel gruppo {group}", "group_update_failed": "Impossibile aggiornare il gruppo '{group}': {error}", "group_updated": "Gruppo '{group}' aggiornato", - "group_unknown": "Gruppo '{group:s}' sconosciuto", + "group_unknown": "Gruppo '{group}' sconosciuto", "group_deletion_failed": "Impossibile cancellare il gruppo '{group}': {error}", "group_deleted": "Gruppo '{group}' cancellato", "group_cannot_be_deleted": "Il gruppo {group} non può essere eliminato manualmente.", @@ -565,7 +565,7 @@ "dyndns_provider_unreachable": "Incapace di raggiungere il provider DynDNS {provider}: o il tuo YunoHost non è connesso ad internet o il server dynette è down.", "dpkg_lock_not_available": "Impossibile eseguire il comando in questo momento perché un altro programma sta bloccando dpkg (il package manager di sistema)", "domain_name_unknown": "Dominio '{domain}' sconosciuto", - "domain_cannot_remove_main_add_new_one": "Non puoi rimuovere '{domain:s}' visto che è il dominio principale nonché il tuo unico dominio, devi prima aggiungere un altro dominio eseguendo 'yunohost domain add ', impostarlo come dominio principale con 'yunohost domain main-domain n ', e solo allora potrai rimuovere il dominio '{domain:s}' eseguendo 'yunohost domain remove {domain:s}'.'", + "domain_cannot_remove_main_add_new_one": "Non puoi rimuovere '{domain}' visto che è il dominio principale nonché il tuo unico dominio, devi prima aggiungere un altro dominio eseguendo 'yunohost domain add ', impostarlo come dominio principale con 'yunohost domain main-domain n ', e solo allora potrai rimuovere il dominio '{domain}' eseguendo 'yunohost domain remove {domain}'.'", "domain_cannot_add_xmpp_upload": "Non puoi aggiungere domini che iniziano per 'xmpp-upload.'. Questo tipo di nome è riservato per la funzionalità di upload XMPP integrata in YunoHost.", "diagnosis_processes_killed_by_oom_reaper": "Alcuni processi sono stati terminati dal sistema che era a corto di memoria. Questo è un sintomo di insufficienza di memoria nel sistema o di un processo che richiede troppa memoria. Lista dei processi terminati:\n{kills_summary}", "diagnosis_never_ran_yet": "Sembra che questo server sia stato impostato recentemente e non è presente nessun report di diagnostica. Dovresti partire eseguendo una diagnostica completa, da webadmin o da terminale con il comando 'yunohost diagnosis run'.", @@ -621,7 +621,7 @@ "migration_update_LDAP_schema": "Aggiorno lo schema LDAP...", "migration_ldap_rollback_success": "Sistema ripristinato allo stato precedente.", "migration_ldap_migration_failed_trying_to_rollback": "Impossibile migrare... provo a ripristinare il sistema.", - "migration_ldap_can_not_backup_before_migration": "Il backup del sistema non è stato completato prima che la migrazione fallisse. Errore: {error:s}", + "migration_ldap_can_not_backup_before_migration": "Il backup del sistema non è stato completato prima che la migrazione fallisse. Errore: {error}", "migration_ldap_backup_before_migration": "Sto generando il backup del database LDAP e delle impostazioni delle app prima di effettuare la migrazione.", "migration_description_0020_ssh_sftp_permissions": "Aggiungi il supporto ai permessi SSH e SFTP", "log_backup_create": "Crea un archivio backup", diff --git a/locales/nb_NO.json b/locales/nb_NO.json index 74583c992..037e09cb6 100644 --- a/locales/nb_NO.json +++ b/locales/nb_NO.json @@ -4,60 +4,60 @@ "admin_password_change_failed": "Kan ikke endre passord", "admin_password_changed": "Administrasjonspassord endret", "admin_password_too_long": "Velg et passord kortere enn 127 tegn", - "app_already_installed": "{app:s} er allerede installert", - "app_already_up_to_date": "{app:s} er allerede oppdatert", - "app_argument_invalid": "Velg en gydlig verdi for argumentet '{name:s}': {error:s}", - "app_argument_required": "Argumentet '{name:s}' er påkrevd", + "app_already_installed": "{app} er allerede installert", + "app_already_up_to_date": "{app} er allerede oppdatert", + "app_argument_invalid": "Velg en gydlig verdi for argumentet '{name}': {error}", + "app_argument_required": "Argumentet '{name}' er påkrevd", "app_id_invalid": "Ugyldig program-ID", "dyndns_key_not_found": "Fant ikke DNS-nøkkel for domenet", - "app_not_correctly_installed": "{app:s} ser ikke ut til å ha blitt installert på riktig måte", + "app_not_correctly_installed": "{app} ser ikke ut til å ha blitt installert på riktig måte", "dyndns_provider_unreachable": "Kunne ikke nå DynDNS-tilbyder {provider}: Enten har du ikke satt opp din YunoHost rett, dynette-tjeneren er nede, eller du mangler nett.", - "app_not_properly_removed": "{app:s} har ikke blitt fjernet på riktig måte", - "app_removed": "{app:s} fjernet", - "app_requirements_checking": "Sjekker påkrevde pakker for {app:s}…", + "app_not_properly_removed": "{app} har ikke blitt fjernet på riktig måte", + "app_removed": "{app} fjernet", + "app_requirements_checking": "Sjekker påkrevde pakker for {app}…", "app_start_install": "Installerer programmet '{app}'…", - "action_invalid": "Ugyldig handling '{action:s}'", + "action_invalid": "Ugyldig handling '{action}'", "app_start_restore": "Gjenoppretter programmet '{app}'…", "backup_created": "Sikkerhetskopi opprettet", "backup_archive_name_exists": "En sikkerhetskopi med dette navnet finnes allerede.", - "backup_archive_name_unknown": "Ukjent lokalt sikkerhetskopiarkiv ved navn '{name:s}'", + "backup_archive_name_unknown": "Ukjent lokalt sikkerhetskopiarkiv ved navn '{name}'", "already_up_to_date": "Ingenting å gjøre. Alt er oppdatert.", "backup_method_copy_finished": "Sikkerhetskopi fullført", "backup_method_tar_finished": "TAR-sikkerhetskopiarkiv opprettet", "app_action_cannot_be_ran_because_required_services_down": "Dette programmet krever noen tjenester som ikke kjører. Før du fortsetter, du bør prøve å starte følgende tjenester på ny (og antagelig undersøke hvorfor de er nede): {services}", "app_already_installed_cant_change_url": "Dette programmet er allerede installert. Nettadressen kan ikke endres kun med denne funksjonen. Ta en titt på `app changeurl` hvis den er tilgjengelig.", "domain_exists": "Domenet finnes allerede", - "app_change_url_failed_nginx_reload": "Kunne ikke gjeninnlaste NGINX. Her har du utdataen for 'nginx -t'\n{nginx_errors:s}", + "app_change_url_failed_nginx_reload": "Kunne ikke gjeninnlaste NGINX. Her har du utdataen for 'nginx -t'\n{nginx_errors}", "domains_available": "Tilgjengelige domener:", "done": "Ferdig", "downloading": "Laster ned…", - "dyndns_could_not_check_provide": "Kunne ikke sjekke om {provider:s} kan tilby {domain:s}.", - "dyndns_could_not_check_available": "Kunne ikke sjekke om {domain:s} er tilgjengelig på {provider:s}.", - "mail_domain_unknown": "Ukjent e-postadresse for domenet '{domain:s}'", + "dyndns_could_not_check_provide": "Kunne ikke sjekke om {provider} kan tilby {domain}.", + "dyndns_could_not_check_available": "Kunne ikke sjekke om {domain} er tilgjengelig på {provider}.", + "mail_domain_unknown": "Ukjent e-postadresse for domenet '{domain}'", "log_remove_on_failed_restore": "Fjern '{}' etter mislykket gjenoppretting fra sikkerhetskopiarkiv", "log_letsencrypt_cert_install": "Installer et Let's Encrypt-sertifikat på '{}'-domenet", "log_letsencrypt_cert_renew": "Forny '{}'-Let's Encrypt-sertifikat", "log_user_update": "Oppdater brukerinfo for '{}'", - "mail_alias_remove_failed": "Kunne ikke fjerne e-postaliaset '{mail:s}'", + "mail_alias_remove_failed": "Kunne ikke fjerne e-postaliaset '{mail}'", "app_action_broke_system": "Denne handlingen ser ut til å ha knekt disse viktige tjenestene: {services}", - "app_argument_choice_invalid": "Bruk én av disse valgene '{choices:s}' for argumentet '{name:s}'", + "app_argument_choice_invalid": "Bruk én av disse valgene '{choices}' for argumentet '{name}'", "app_extraction_failed": "Kunne ikke pakke ut installasjonsfilene", "app_install_files_invalid": "Disse filene kan ikke installeres", "backup_abstract_method": "Denne sikkerhetskopimetoden er ikke implementert enda", "backup_actually_backuping": "Oppretter sikkerhetskopiarkiv fra innsamlede filer…", - "backup_app_failed": "Kunne ikke sikkerhetskopiere programmet '{app:s}'", + "backup_app_failed": "Kunne ikke sikkerhetskopiere programmet '{app}'", "backup_applying_method_tar": "Lager TAR-sikkerhetskopiarkiv…", - "backup_archive_app_not_found": "Fant ikke programmet '{app:s}' i sikkerhetskopiarkivet", + "backup_archive_app_not_found": "Fant ikke programmet '{app}' i sikkerhetskopiarkivet", "backup_archive_open_failed": "Kunne ikke åpne sikkerhetskopiarkivet", "app_start_remove": "Fjerner programmet '{app}'…", "app_start_backup": "Samler inn filer for sikkerhetskopiering for {app}…", "backup_applying_method_copy": "Kopier alle filer til sikkerhetskopi…", "backup_creation_failed": "Kunne ikke opprette sikkerhetskopiarkiv", - "backup_couldnt_bind": "Kunne ikke binde {src:s} til {dest:s}.", + "backup_couldnt_bind": "Kunne ikke binde {src} til {dest}.", "backup_csv_addition_failed": "Kunne ikke legge til filer for sikkerhetskopi inn i CSV-filen", "backup_deleted": "Sikkerhetskopi slettet", "backup_no_uncompress_archive_dir": "Det finnes ingen slik utpakket arkivmappe", - "backup_delete_error": "Kunne ikke slette '{path:s}'", + "backup_delete_error": "Kunne ikke slette '{path}'", "certmanager_cert_signing_failed": "Kunne ikke signere det nye sertifikatet", "extracting": "Pakker ut…", "log_domain_add": "Legg til '{}'-domenet i systemoppsett", @@ -65,7 +65,7 @@ "log_dyndns_subscribe": "Abonner på YunoHost-underdomenet '{}'", "log_dyndns_update": "Oppdater IP-adressen tilknyttet ditt YunoHost-underdomene '{}'", "backup_nothings_done": "Ingenting å lagre", - "field_invalid": "Ugyldig felt '{:s}'", + "field_invalid": "Ugyldig felt '{}'", "firewall_reloaded": "Brannmur gjeninnlastet", "log_app_change_url": "Endre nettadresse for '{}'-programmet", "log_app_install": "Installer '{}'-programmet", @@ -77,7 +77,7 @@ "log_tools_reboot": "Utfør omstart av tjeneren din", "apps_already_up_to_date": "Alle programmer allerede oppdatert", "backup_mount_archive_for_restore": "Forbereder arkiv for gjenopprettelse…", - "backup_copying_to_organize_the_archive": "Kopierer {size:s} MB for å organisere arkivet", + "backup_copying_to_organize_the_archive": "Kopierer {size} MB for å organisere arkivet", "domain_cannot_remove_main": "Kan ikke fjerne hoveddomene. Sett et først", "domain_cert_gen_failed": "Kunne ikke opprette sertifikat", "domain_created": "Domene opprettet", @@ -90,7 +90,7 @@ "dyndns_no_domain_registered": "Inget domene registrert med DynDNS", "dyndns_registered": "DynDNS-domene registrert", "global_settings_setting_security_password_admin_strength": "Admin-passordets styrke", - "dyndns_registration_failed": "Kunne ikke registrere DynDNS-domene: {error:s}", + "dyndns_registration_failed": "Kunne ikke registrere DynDNS-domene: {error}", "global_settings_setting_security_password_user_strength": "Brukerpassordets styrke", "log_backup_restore_app": "Gjenopprett '{}' fra sikkerhetskopiarkiv", "log_remove_on_failed_install": "Fjern '{}' etter mislykket installasjon", @@ -100,9 +100,9 @@ "log_user_group_update": "Oppdater '{}' gruppe", "app_unknown": "Ukjent program", "app_upgrade_app_name": "Oppgraderer {app}…", - "app_upgrade_failed": "Kunne ikke oppgradere {app:s}", + "app_upgrade_failed": "Kunne ikke oppgradere {app}", "app_upgrade_some_app_failed": "Noen programmer kunne ikke oppgraderes", - "app_upgraded": "{app:s} oppgradert", + "app_upgraded": "{app} oppgradert", "ask_firstname": "Fornavn", "ask_lastname": "Etternavn", "ask_main_domain": "Hoveddomene", @@ -117,6 +117,6 @@ "log_link_to_log": "Full logg for denne operasjonen: '{desc}'", "log_help_to_get_log": "For å vise loggen for operasjonen '{desc}', bruk kommandoen 'yunohost log show {name}{name}'", "log_user_create": "Legg til '{}' bruker", - "app_change_url_success": "{app:s} nettadressen er nå {domain:s}{path:s}", + "app_change_url_success": "{app} nettadressen er nå {domain}{path}", "app_install_failed": "Kunne ikke installere {app}: {error}" } \ No newline at end of file diff --git a/locales/nl.json b/locales/nl.json index 3894a5f9c..e99a00575 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -1,20 +1,20 @@ { - "action_invalid": "Ongeldige actie '{action:s}'", + "action_invalid": "Ongeldige actie '{action}'", "admin_password": "Administrator wachtwoord", "admin_password_changed": "Het administratie wachtwoord werd gewijzigd", - "app_already_installed": "{app:s} is al geïnstalleerd", - "app_argument_invalid": "Kies een geldige waarde voor '{name:s}': {error:s}", - "app_argument_required": "Het '{name:s}' moet ingevuld worden", + "app_already_installed": "{app} is al geïnstalleerd", + "app_argument_invalid": "Kies een geldige waarde voor '{name}': {error}", + "app_argument_required": "Het '{name}' moet ingevuld worden", "app_extraction_failed": "Kan installatiebestanden niet uitpakken", "app_id_invalid": "Ongeldige app-id", "app_install_files_invalid": "Deze bestanden kunnen niet worden geïnstalleerd", "app_manifest_invalid": "Ongeldig app-manifest", - "app_not_installed": "{app:s} is niet geïnstalleerd", - "app_removed": "{app:s} succesvol verwijderd", + "app_not_installed": "{app} is niet geïnstalleerd", + "app_removed": "{app} succesvol verwijderd", "app_sources_fetch_failed": "Kan bronbestanden niet ophalen, klopt de URL?", "app_unknown": "Onbekende app", - "app_upgrade_failed": "Kan app {app:s} niet updaten", - "app_upgraded": "{app:s} succesvol geüpgraded", + "app_upgrade_failed": "Kan app {app} niet updaten", + "app_upgraded": "{app} succesvol geüpgraded", "ask_firstname": "Voornaam", "ask_lastname": "Achternaam", "ask_new_admin_password": "Nieuw administratorwachtwoord", @@ -22,7 +22,7 @@ "backup_archive_name_exists": "Een backuparchief met dezelfde naam bestaat al", "backup_cleaning_failed": "Kan tijdelijke backup map niet leeg maken", "backup_output_directory_not_empty": "Doelmap is niet leeg", - "custom_app_url_required": "U moet een URL opgeven om uw aangepaste app {app:s} bij te werken", + "custom_app_url_required": "U moet een URL opgeven om uw aangepaste app {app} bij te werken", "domain_cert_gen_failed": "Kan certificaat niet genereren", "domain_created": "Domein succesvol aangemaakt", "domain_creation_failed": "Kan domein niet aanmaken", @@ -41,24 +41,24 @@ "dyndns_unavailable": "DynDNS subdomein is niet beschikbaar", "extracting": "Uitpakken...", "installation_complete": "Installatie voltooid", - "mail_alias_remove_failed": "Kan mail-alias '{mail:s}' niet verwijderen", + "mail_alias_remove_failed": "Kan mail-alias '{mail}' niet verwijderen", "pattern_email": "Moet een geldig emailadres bevatten (bv. abc@example.org)", "pattern_mailbox_quota": "Mailbox quota moet een waarde bevatten met b/k/M/G/T erachter of 0 om geen quota in te stellen", "pattern_password": "Wachtwoord moet tenminste 3 karakters lang zijn", - "port_already_closed": "Poort {port:d} is al gesloten voor {ip_version:s} verbindingen", - "port_already_opened": "Poort {port:d} is al open voor {ip_version:s} verbindingen", - "app_restore_failed": "De app '{app:s}' kon niet worden terug gezet: {error:s}", - "restore_hook_unavailable": "De herstel-hook '{part:s}' is niet beschikbaar op dit systeem", - "service_add_failed": "Kan service '{service:s}' niet toevoegen", - "service_already_started": "Service '{service:s}' draait al", - "service_cmd_exec_failed": "Kan '{command:s}' niet uitvoeren", - "service_disabled": "Service '{service:s}' is uitgeschakeld", - "service_remove_failed": "Kan service '{service:s}' niet verwijderen", + "port_already_closed": "Poort {port:d} is al gesloten voor {ip_version} verbindingen", + "port_already_opened": "Poort {port:d} is al open voor {ip_version} verbindingen", + "app_restore_failed": "De app '{app}' kon niet worden terug gezet: {error}", + "restore_hook_unavailable": "De herstel-hook '{part}' is niet beschikbaar op dit systeem", + "service_add_failed": "Kan service '{service}' niet toevoegen", + "service_already_started": "Service '{service}' draait al", + "service_cmd_exec_failed": "Kan '{command}' niet uitvoeren", + "service_disabled": "Service '{service}' is uitgeschakeld", + "service_remove_failed": "Kan service '{service}' niet verwijderen", "service_removed": "Service werd verwijderd", - "service_stop_failed": "Kan service '{service:s}' niet stoppen", - "service_unknown": "De service '{service:s}' bestaat niet", + "service_stop_failed": "Kan service '{service}' niet stoppen", + "service_unknown": "De service '{service}' bestaat niet", "unexpected_error": "Er is een onbekende fout opgetreden", - "unrestore_app": "App '{app:s}' wordt niet teruggezet", + "unrestore_app": "App '{app}' wordt niet teruggezet", "updating_apt_cache": "Lijst van beschikbare pakketten wordt bijgewerkt...", "upgrade_complete": "Upgrade voltooid", "upgrading_packages": "Pakketten worden geüpdate...", @@ -68,27 +68,27 @@ "upnp_port_open_failed": "Kan UPnP poorten niet openen", "user_deleted": "Gebruiker werd verwijderd", "user_home_creation_failed": "Kan de map voor deze gebruiker niet aanmaken", - "user_unknown": "Gebruikersnaam {user:s} is onbekend", + "user_unknown": "Gebruikersnaam {user} is onbekend", "user_update_failed": "Kan gebruiker niet bijwerken", "yunohost_configured": "YunoHost configuratie is OK", "admin_password_change_failed": "Wachtwoord kan niet veranderd worden", - "app_argument_choice_invalid": "Ongeldige keuze voor argument '{name:s}'. Het moet een van de volgende keuzes zijn {choices:s}", - "app_not_correctly_installed": "{app:s} schijnt niet juist geïnstalleerd te zijn", - "app_not_properly_removed": "{app:s} werd niet volledig verwijderd", + "app_argument_choice_invalid": "Ongeldige keuze voor argument '{name}'. Het moet een van de volgende keuzes zijn {choices}", + "app_not_correctly_installed": "{app} schijnt niet juist geïnstalleerd te zijn", + "app_not_properly_removed": "{app} werd niet volledig verwijderd", "app_requirements_checking": "Noodzakelijke pakketten voor {app} aan het controleren...", "app_requirements_unmeet": "Er wordt niet aan de aanvorderingen voldaan, het pakket {pkgname} ({version}) moet {spec} zijn", "app_unsupported_remote_type": "Niet ondersteund besturings type voor de app", "ask_main_domain": "Hoofd-domein", - "backup_app_failed": "Kon geen backup voor app '{app:s}' aanmaken", - "backup_archive_app_not_found": "App '{app:s}' kon niet in het backup archief gevonden worden", - "backup_archive_broken_link": "Het backup archief kon niet geopend worden (Ongeldig verwijs naar {path:s})", - "backup_archive_name_unknown": "Onbekend lokaal backup archief namens '{name:s}' gevonden", + "backup_app_failed": "Kon geen backup voor app '{app}' aanmaken", + "backup_archive_app_not_found": "App '{app}' kon niet in het backup archief gevonden worden", + "backup_archive_broken_link": "Het backup archief kon niet geopend worden (Ongeldig verwijs naar {path})", + "backup_archive_name_unknown": "Onbekend lokaal backup archief namens '{name}' gevonden", "backup_archive_open_failed": "Kan het backup archief niet openen", "backup_created": "Backup aangemaakt", "backup_creation_failed": "Aanmaken van backup mislukt", - "backup_delete_error": "Kon pad '{path:s}' niet verwijderen", + "backup_delete_error": "Kon pad '{path}' niet verwijderen", "backup_deleted": "Backup werd verwijderd", - "backup_hook_unknown": "backup hook '{hook:s}' onbekend", + "backup_hook_unknown": "backup hook '{hook}' onbekend", "backup_nothings_done": "Niets om op te slaan", "password_too_simple_1": "Het wachtwoord moet minimaal 8 tekens lang zijn", "already_up_to_date": "Er is niets te doen, alles is al up-to-date.", @@ -102,11 +102,11 @@ "app_manifest_install_ask_domain": "Kies het domein waar deze app op geïnstalleerd moet worden", "app_manifest_install_ask_path": "Kies het pad waar deze app geïnstalleerd moet worden", "app_manifest_install_ask_admin": "Kies een administrator voor deze app", - "app_change_url_failed_nginx_reload": "Kon NGINX niet opnieuw laden. Hier is de output van 'nginx -t':\n{nginx_errors:s}", - "app_change_url_success": "{app:s} URL is nu {domain:s}{path:s}", + "app_change_url_failed_nginx_reload": "Kon NGINX niet opnieuw laden. Hier is de output van 'nginx -t':\n{nginx_errors}", + "app_change_url_success": "{app} URL is nu {domain}{path}", "app_full_domain_unavailable": "Sorry, deze app moet op haar eigen domein geïnstalleerd worden, maar andere apps zijn al geïnstalleerd op het domein '{domain}'. U kunt wel een subdomein aan deze app toewijden.", "app_install_script_failed": "Er is een fout opgetreden in het installatiescript van de app", - "app_location_unavailable": "Deze URL is niet beschikbaar of is in conflict met de al geïnstalleerde app(s):\n{apps:s}", + "app_location_unavailable": "Deze URL is niet beschikbaar of is in conflict met de al geïnstalleerde app(s):\n{apps}", "app_manifest_install_ask_password": "Kies een administratiewachtwoord voor deze app", "app_manifest_install_ask_is_public": "Moet deze app zichtbaar zijn voor anomieme bezoekers?", "app_not_upgraded": "De app '{failed_app}' kon niet upgraden en daardoor zijn de upgrades van de volgende apps geannuleerd: {apps}", diff --git a/locales/oc.json b/locales/oc.json index 991383bc3..cccafdb03 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -2,78 +2,78 @@ "admin_password": "Senhal d’administracion", "admin_password_change_failed": "Impossible de cambiar lo senhal", "admin_password_changed": "Lo senhal d’administracion es ben estat cambiat", - "app_already_installed": "{app:s} es ja installat", - "app_already_up_to_date": "{app:s} es ja a jorn", + "app_already_installed": "{app} es ja installat", + "app_already_up_to_date": "{app} es ja a jorn", "installation_complete": "Installacion acabada", "app_id_invalid": "ID d’aplicacion incorrècte", "app_install_files_invalid": "Installacion impossibla d’aquestes fichièrs", - "app_not_correctly_installed": "{app:s} sembla pas ben installat", - "app_not_installed": "Impossible de trobar l’aplicacion {app:s} dins la lista de las aplicacions installadas : {all_apps}", - "app_not_properly_removed": "{app:s} es pas estat corrèctament suprimit", - "app_removed": "{app:s} es estada suprimida", + "app_not_correctly_installed": "{app} sembla pas ben installat", + "app_not_installed": "Impossible de trobar l’aplicacion {app} dins la lista de las aplicacions installadas : {all_apps}", + "app_not_properly_removed": "{app} es pas estat corrèctament suprimit", + "app_removed": "{app} es estada suprimida", "app_unknown": "Aplicacion desconeguda", "app_upgrade_app_name": "Actualizacion de l’aplicacion {app}...", - "app_upgrade_failed": "Impossible d’actualizar {app:s} : {error}", + "app_upgrade_failed": "Impossible d’actualizar {app} : {error}", "app_upgrade_some_app_failed": "D’aplicacions se pòdon pas actualizar", - "app_upgraded": "{app:s} es estada actualizada", + "app_upgraded": "{app} es estada actualizada", "ask_firstname": "Prenom", "ask_lastname": "Nom", "ask_main_domain": "Domeni màger", "ask_new_admin_password": "Nòu senhal administrator", "ask_password": "Senhal", - "backup_app_failed": "Impossible de salvagardar l’aplicacion « {app:s} »", + "backup_app_failed": "Impossible de salvagardar l’aplicacion « {app} »", "backup_applying_method_copy": "Còpia de totes los fichièrs dins la salvagarda...", "backup_applying_method_tar": "Creacion de l’archiu TAR de la salvagarda...", "backup_archive_name_exists": "Un archiu de salvagarda amb aquesta nom existís ja.", - "backup_archive_name_unknown": "L’archiu local de salvagarda apelat « {name:s} » es desconegut", - "action_invalid": "Accion « {action:s} » incorrècta", - "app_argument_choice_invalid": "Utilizatz una de las opcions « {choices:s} » per l’argument « {name:s} »", - "app_argument_invalid": "Causissètz una valor invalida pel paramètre « {name:s} » : {error:s}", - "app_argument_required": "Lo paramètre « {name:s} » es requesit", - "app_change_url_failed_nginx_reload": "Reaviada de NGINX impossibla. Vaquí la sortida de « nginx -t » :\n{nginx_errors:s}", - "app_change_url_identical_domains": "L’ancian e lo novèl coble domeni/camin son identics per {domain:s}{path:s}, pas res a far.", - "app_change_url_success": "L’URL de l’aplicacion {app:s} es ara {domain:s}{path:s}", + "backup_archive_name_unknown": "L’archiu local de salvagarda apelat « {name} » es desconegut", + "action_invalid": "Accion « {action} » incorrècta", + "app_argument_choice_invalid": "Utilizatz una de las opcions « {choices} » per l’argument « {name} »", + "app_argument_invalid": "Causissètz una valor invalida pel paramètre « {name} » : {error}", + "app_argument_required": "Lo paramètre « {name} » es requesit", + "app_change_url_failed_nginx_reload": "Reaviada de NGINX impossibla. Vaquí la sortida de « nginx -t » :\n{nginx_errors}", + "app_change_url_identical_domains": "L’ancian e lo novèl coble domeni/camin son identics per {domain}{path}, pas res a far.", + "app_change_url_success": "L’URL de l’aplicacion {app} es ara {domain}{path}", "app_extraction_failed": "Extraccion dels fichièrs d’installacion impossibla", "app_manifest_invalid": "I a quicòm que truca amb lo manifest de l’aplicacion : {error}", "app_requirements_checking": "Verificacion dels paquets requesits per {app}...", "app_sources_fetch_failed": "Recuperacion dels fichièrs fonts impossibla, l’URL es corrècta ?", "app_unsupported_remote_type": "Lo tipe alonhat utilizat per l’aplicacion es pas suportat", - "backup_archive_app_not_found": "L’aplicacion « {app:s} » es pas estada trobada dins l’archiu de la salvagarda", - "backup_archive_broken_link": "Impossible d’accedir a l’archiu de salvagarda (ligam invalid cap a {path:s})", + "backup_archive_app_not_found": "L’aplicacion « {app} » es pas estada trobada dins l’archiu de la salvagarda", + "backup_archive_broken_link": "Impossible d’accedir a l’archiu de salvagarda (ligam invalid cap a {path})", "backup_archive_open_failed": "Impossible de dobrir l’archiu de salvagarda", - "backup_archive_system_part_not_available": "La part « {part:s} » del sistèma es pas disponibla dins aquesta salvagarda", + "backup_archive_system_part_not_available": "La part « {part} » del sistèma es pas disponibla dins aquesta salvagarda", "backup_cleaning_failed": "Impossible de netejar lo repertòri temporari de salvagarda", - "backup_copying_to_organize_the_archive": "Còpia de {size:s} Mio per organizar l’archiu", + "backup_copying_to_organize_the_archive": "Còpia de {size} Mio per organizar l’archiu", "backup_created": "Salvagarda acabada", "backup_creation_failed": "Creacion impossibla de l’archiu de salvagarda", "app_already_installed_cant_change_url": "Aquesta aplicacion es ja installada. Aquesta foncion pòt pas simplament cambiar l’URL. Agachatz « app changeurl » s’es disponible.", - "app_change_url_no_script": "L’aplicacion {app_name:s} pren pas en compte lo cambiament d’URL, benlèu que vos cal l’actualizar.", + "app_change_url_no_script": "L’aplicacion {app_name} pren pas en compte lo cambiament d’URL, benlèu que vos cal l’actualizar.", "app_make_default_location_already_used": "Impossible de configurar l’aplicacion « {app} » per defaut pel domeni {domain} perque es ja utilizat per l’aplicacion {other_app}", - "app_location_unavailable": "Aquesta URL es pas disponibla o en conflicte amb una aplicacion existenta :\n{apps:s}", - "backup_delete_error": "Supression impossibla de « {path:s} »", + "app_location_unavailable": "Aquesta URL es pas disponibla o en conflicte amb una aplicacion existenta :\n{apps}", + "backup_delete_error": "Supression impossibla de « {path} »", "backup_deleted": "La salvagarda es estada suprimida", - "backup_hook_unknown": "Script de salvagarda « {hook:s} » desconegut", + "backup_hook_unknown": "Script de salvagarda « {hook} » desconegut", "backup_method_copy_finished": "La còpia de salvagarda es acabada", "backup_method_tar_finished": "L’archiu TAR de la salvagarda es estat creat", "backup_output_directory_not_empty": "Devètz causir un dorsièr de sortida void", "backup_output_directory_required": "Vos cal especificar un dorsièr de sortida per la salvagarda", "backup_running_hooks": "Execucion dels scripts de salvagarda...", - "backup_system_part_failed": "Impossible de salvagardar la part « {part:s} » del sistèma", + "backup_system_part_failed": "Impossible de salvagardar la part « {part} » del sistèma", "app_requirements_unmeet": "Las condicions requesidas per {app} son pas complidas, lo paquet {pkgname} ({version}) deu èsser {spec}", "backup_abstract_method": "Aqueste metòde de salvagarda es pas encara implementat", - "backup_applying_method_custom": "Crida del metòde de salvagarda personalizat « {method:s} »...", - "backup_couldnt_bind": "Impossible de ligar {src:s} amb {dest:s}.", + "backup_applying_method_custom": "Crida del metòde de salvagarda personalizat « {method} »...", + "backup_couldnt_bind": "Impossible de ligar {src} amb {dest}.", "backup_csv_addition_failed": "Impossible d’ajustar de fichièrs a la salvagarda dins lo fichièr CSV", "backup_custom_backup_error": "Fracàs del metòde de salvagarda personalizat a l’etapa « backup »", "backup_custom_mount_error": "Fracàs del metòde de salvagarda personalizat a l’etapa « mount »", - "backup_method_custom_finished": "Lo metòde de salvagarda personalizat « {method:s} » es acabat", + "backup_method_custom_finished": "Lo metòde de salvagarda personalizat « {method} » es acabat", "backup_nothings_done": "I a pas res de salvagardar", "backup_unable_to_organize_files": "Impossible d’organizar los fichièrs dins l’archiu amb lo metòde rapid", - "service_stopped": "Lo servici « {service:s} » es estat arrestat", - "service_unknown": "Servici « {service:s} » desconegut", - "unbackup_app": "L’aplicacion « {app:s} » serà pas salvagardada", + "service_stopped": "Lo servici « {service} » es estat arrestat", + "service_unknown": "Servici « {service} » desconegut", + "unbackup_app": "L’aplicacion « {app} » serà pas salvagardada", "unlimit": "Cap de quòta", - "unrestore_app": "L’aplicacion « {app:s} » serà pas restaurada", + "unrestore_app": "L’aplicacion « {app} » serà pas restaurada", "upnp_dev_not_found": "Cap de periferic compatible UPnP pas trobat", "upnp_disabled": "UPnP es desactivat", "upnp_enabled": "UPnP es activat", @@ -82,23 +82,23 @@ "yunohost_configured": "YunoHost es estat configurat", "yunohost_installing": "Installacion de YunoHost…", "backup_csv_creation_failed": "Creacion impossibla del fichièr CSV necessari a las operacions futuras de restauracion", - "backup_output_symlink_dir_broken": "Vòstre repertòri d’archiu « {path:s} » es un ligam simbolic copat. Saique oblidèretz de re/montar o de connectar supòrt.", - "backup_with_no_backup_script_for_app": "L’aplicacion {app:s} a pas cap de script de salvagarda. I fasèm pas cas.", - "backup_with_no_restore_script_for_app": "{app:s} a pas cap de script de restauracion, poiretz pas restaurar automaticament la salvagarda d’aquesta aplicacion.", - "certmanager_acme_not_configured_for_domain": "Lo certificat pel domeni {domain:s} sembla pas corrèctament installat. Mercés de lançar d’en primièr « cert-install » per aqueste domeni.", - "certmanager_attempt_to_renew_nonLE_cert": "Lo certificat pel domeni {domain:s} es pas provesit per Let’s Encrypt. Impossible de lo renovar automaticament !", - "certmanager_attempt_to_renew_valid_cert": "Lo certificat pel domeni {domain:s} es a man d’expirar ! (Podètz utilizar --force se sabètz çò que fasètz)", - "certmanager_cannot_read_cert": "Quicòm a trucat en ensajar de dobrir lo certificat actual pel domeni {domain:s} (fichièr : {file:s}), rason : {reason:s}", - "certmanager_cert_install_success": "Lo certificat Let’s Encrypt es ara installat pel domeni « {domain:s} »", - "certmanager_cert_install_success_selfsigned": "Lo certificat auto-signat es ara installat pel domeni « {domain:s} »", + "backup_output_symlink_dir_broken": "Vòstre repertòri d’archiu « {path} » es un ligam simbolic copat. Saique oblidèretz de re/montar o de connectar supòrt.", + "backup_with_no_backup_script_for_app": "L’aplicacion {app} a pas cap de script de salvagarda. I fasèm pas cas.", + "backup_with_no_restore_script_for_app": "{app} a pas cap de script de restauracion, poiretz pas restaurar automaticament la salvagarda d’aquesta aplicacion.", + "certmanager_acme_not_configured_for_domain": "Lo certificat pel domeni {domain} sembla pas corrèctament installat. Mercés de lançar d’en primièr « cert-install » per aqueste domeni.", + "certmanager_attempt_to_renew_nonLE_cert": "Lo certificat pel domeni {domain} es pas provesit per Let’s Encrypt. Impossible de lo renovar automaticament !", + "certmanager_attempt_to_renew_valid_cert": "Lo certificat pel domeni {domain} es a man d’expirar ! (Podètz utilizar --force se sabètz çò que fasètz)", + "certmanager_cannot_read_cert": "Quicòm a trucat en ensajar de dobrir lo certificat actual pel domeni {domain} (fichièr : {file}), rason : {reason}", + "certmanager_cert_install_success": "Lo certificat Let’s Encrypt es ara installat pel domeni « {domain} »", + "certmanager_cert_install_success_selfsigned": "Lo certificat auto-signat es ara installat pel domeni « {domain} »", "certmanager_cert_signing_failed": "Signatura impossibla del nòu certificat", - "certmanager_domain_cert_not_selfsigned": "Lo certificat pel domeni {domain:s} es pas auto-signat. Volètz vertadièrament lo remplaçar ? (Utilizatz « --force » per o far)", - "certmanager_domain_dns_ip_differs_from_public_ip": "L’enregistrament DNS « A » pel domeni {domain:s} es diferent de l’adreça IP d’aqueste servidor. Se fa pauc qu’avètz modificat l’enregistrament « A », mercés d’esperar l’espandiment (qualques verificadors d’espandiment son disponibles en linha). (Se sabètz çò que fasèm, utilizatz --no-checks per desactivar aqueles contraròtles)", - "certmanager_domain_http_not_working": "Sembla que lo domeni {domain:s} es pas accessible via HTTP. Mercés de verificar que las configuracions DNS e NGINK son corrèctas", - "certmanager_no_cert_file": "Lectura impossibla del fichièr del certificat pel domeni {domain:s} (fichièr : {file:s})", - "certmanager_self_ca_conf_file_not_found": "Impossible de trobar lo fichièr de configuracion per l’autoritat del certificat auto-signat (fichièr : {file:s})", - "certmanager_unable_to_parse_self_CA_name": "Analisi impossibla del nom de l’autoritat del certificat auto-signat (fichièr : {file:s})", - "custom_app_url_required": "Cal que donetz una URL per actualizar vòstra aplicacion personalizada {app:s}", + "certmanager_domain_cert_not_selfsigned": "Lo certificat pel domeni {domain} es pas auto-signat. Volètz vertadièrament lo remplaçar ? (Utilizatz « --force » per o far)", + "certmanager_domain_dns_ip_differs_from_public_ip": "L’enregistrament DNS « A » pel domeni {domain} es diferent de l’adreça IP d’aqueste servidor. Se fa pauc qu’avètz modificat l’enregistrament « A », mercés d’esperar l’espandiment (qualques verificadors d’espandiment son disponibles en linha). (Se sabètz çò que fasèm, utilizatz --no-checks per desactivar aqueles contraròtles)", + "certmanager_domain_http_not_working": "Sembla que lo domeni {domain} es pas accessible via HTTP. Mercés de verificar que las configuracions DNS e NGINK son corrèctas", + "certmanager_no_cert_file": "Lectura impossibla del fichièr del certificat pel domeni {domain} (fichièr : {file})", + "certmanager_self_ca_conf_file_not_found": "Impossible de trobar lo fichièr de configuracion per l’autoritat del certificat auto-signat (fichièr : {file})", + "certmanager_unable_to_parse_self_CA_name": "Analisi impossibla del nom de l’autoritat del certificat auto-signat (fichièr : {file})", + "custom_app_url_required": "Cal que donetz una URL per actualizar vòstra aplicacion personalizada {app}", "domain_cannot_remove_main": "Impossible de levar lo domeni màger. Definissètz un novèl domeni màger d’en primièr", "domain_cert_gen_failed": "Generacion del certificat impossibla", "domain_created": "Domeni creat", @@ -112,22 +112,22 @@ "domains_available": "Domenis disponibles :", "done": "Acabat", "downloading": "Telecargament…", - "dyndns_could_not_check_provide": "Impossible de verificar se {provider:s} pòt provesir {domain:s}.", + "dyndns_could_not_check_provide": "Impossible de verificar se {provider} pòt provesir {domain}.", "dyndns_ip_update_failed": "Impossible d’actualizar l’adreça IP sul domeni DynDNS", "dyndns_ip_updated": "Vòstra adreça IP actualizada pel domeni DynDNS", "dyndns_key_generating": "La clau DNS es a se generar… pòt trigar una estona.", "dyndns_key_not_found": "Clau DNS introbabla pel domeni", "dyndns_no_domain_registered": "Cap de domeni pas enregistrat amb DynDNS", "dyndns_registered": "Domeni DynDNS enregistrat", - "dyndns_registration_failed": "Enregistrament del domeni DynDNS impossible : {error:s}", - "dyndns_domain_not_provided": "Lo provesidor DynDNS {provider:s} pòt pas fornir lo domeni {domain:s}.", - "dyndns_unavailable": "Lo domeni {domain:s} es pas disponible.", + "dyndns_registration_failed": "Enregistrament del domeni DynDNS impossible : {error}", + "dyndns_domain_not_provided": "Lo provesidor DynDNS {provider} pòt pas fornir lo domeni {domain}.", + "dyndns_unavailable": "Lo domeni {domain} es pas disponible.", "extracting": "Extraccion…", - "field_invalid": "Camp incorrècte : « {:s} »", - "global_settings_cant_open_settings": "Fracàs de la dobertura del fichièr de configuracion, rason : {reason:s}", - "global_settings_key_doesnt_exists": "La clau « {settings_key:s} » existís pas dins las configuracions globalas, podètz veire totas las claus disponiblas en executant « yunohost settings list »", - "global_settings_reset_success": "Configuracion precedenta ara salvagarda dins {path:s}", - "global_settings_unknown_setting_from_settings_file": "Clau desconeguda dins los paramètres : {setting_key:s}, apartada e salvagardada dins /etc/yunohost/settings-unknown.json", + "field_invalid": "Camp incorrècte : « {} »", + "global_settings_cant_open_settings": "Fracàs de la dobertura del fichièr de configuracion, rason : {reason}", + "global_settings_key_doesnt_exists": "La clau « {settings_key} » existís pas dins las configuracions globalas, podètz veire totas las claus disponiblas en executant « yunohost settings list »", + "global_settings_reset_success": "Configuracion precedenta ara salvagarda dins {path}", + "global_settings_unknown_setting_from_settings_file": "Clau desconeguda dins los paramètres : {setting_key}, apartada e salvagardada dins /etc/yunohost/settings-unknown.json", "main_domain_change_failed": "Modificacion impossibla del domeni màger", "main_domain_changed": "Lo domeni màger es estat modificat", "migrations_cant_reach_migration_file": "Impossible d’accedir als fichièrs de migracion amb lo camin %s", @@ -142,42 +142,42 @@ "pattern_password": "Deu conténer almens 3 caractèrs", "pattern_port_or_range": "Deu èsser un numèro de pòrt valid (ex : 0-65535) o un interval de pòrt (ex : 100:200)", "pattern_positive_number": "Deu èsser un nombre positiu", - "port_already_closed": "Lo pòrt {port:d} es ja tampat per las connexions {ip_version:s}", - "port_already_opened": "Lo pòrt {port:d} es ja dubèrt per las connexions {ip_version:s}", - "restore_already_installed_app": "Una aplicacion es ja installada amb l’id « {app:s} »", - "app_restore_failed": "Impossible de restaurar l’aplicacion « {app:s} »: {error:s}", - "backup_ask_for_copying_if_needed": "Volètz far una salvagarda en utilizant {size:s} Mo temporàriament ? (Aqueste biais de far es emplegat perque unes fichièrs an pas pogut èsser preparats amb un metòde mai eficaç.)", + "port_already_closed": "Lo pòrt {port:d} es ja tampat per las connexions {ip_version}", + "port_already_opened": "Lo pòrt {port:d} es ja dubèrt per las connexions {ip_version}", + "restore_already_installed_app": "Una aplicacion es ja installada amb l’id « {app} »", + "app_restore_failed": "Impossible de restaurar l’aplicacion « {app} »: {error}", + "backup_ask_for_copying_if_needed": "Volètz far una salvagarda en utilizant {size} Mo temporàriament ? (Aqueste biais de far es emplegat perque unes fichièrs an pas pogut èsser preparats amb un metòde mai eficaç.)", "yunohost_not_installed": "YunoHost es pas installat o corrèctament installat. Mercés d’executar « yunohost tools postinstall »", "backup_output_directory_forbidden": "Causissètz un repertòri de destinacion deferent. Las salvagardas pòdon pas se realizar dins los repertòris bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var ou /home/yunohost.backup/archives", - "certmanager_attempt_to_replace_valid_cert": "Sètz a remplaçar un certificat corrècte e valid pel domeni {domain:s} ! (Utilizatz --force per cortcircuitar)", - "certmanager_cert_renew_success": "Renovèlament capitat d’un certificat Let’s Encrypt pel domeni « {domain:s} »", - "certmanager_certificate_fetching_or_enabling_failed": "Sembla qu’utilizar lo nòu certificat per {domain:s} fonciona pas...", - "certmanager_hit_rate_limit": "Tròp de certificats son ja estats demandats recentament per aqueste ensem de domeni {domain:s}. Mercés de tornar ensajar mai tard. Legissètz https://letsencrypt.org/docs/rate-limits/ per mai detalhs", + "certmanager_attempt_to_replace_valid_cert": "Sètz a remplaçar un certificat corrècte e valid pel domeni {domain} ! (Utilizatz --force per cortcircuitar)", + "certmanager_cert_renew_success": "Renovèlament capitat d’un certificat Let’s Encrypt pel domeni « {domain} »", + "certmanager_certificate_fetching_or_enabling_failed": "Sembla qu’utilizar lo nòu certificat per {domain} fonciona pas...", + "certmanager_hit_rate_limit": "Tròp de certificats son ja estats demandats recentament per aqueste ensem de domeni {domain}. Mercés de tornar ensajar mai tard. Legissètz https://letsencrypt.org/docs/rate-limits/ per mai detalhs", "domain_dns_conf_is_just_a_recommendation": "Aqueste pagina mòstra la configuracion *recomandada*. Non configura *pas* lo DNS per vos. Sètz responsable de la configuracion de vòstra zòna DNS en çò de vòstre registrar DNS amb aquesta recomandacion.", "domain_dyndns_already_subscribed": "Avètz ja soscrich a un domeni DynDNS", "domain_uninstall_app_first": "Una o mantuna aplicacions son installadas sus aqueste domeni. Mercés de las desinstallar d’en primièr abans de suprimir aqueste domeni", "firewall_reload_failed": "Impossible de recargar lo parafuòc", "firewall_reloaded": "Parafuòc recargat", "firewall_rules_cmd_failed": "Unas règlas del parafuòc an fracassat. Per mai informacions, consultatz lo jornal.", - "global_settings_bad_choice_for_enum": "La valor del paramètre {setting:s} es incorrècta. Recebut : {choice:s}, mas las opcions esperadas son : {available_choices:s}", - "global_settings_bad_type_for_setting": "Lo tipe del paramètre {setting:s} es incorrècte, recebut : {received_type:s}, esperat {expected_type:s}", - "global_settings_cant_write_settings": "Fracàs de l’escritura del fichièr de configuracion, rason : {reason:s}", - "global_settings_unknown_type": "Situacion inesperada, la configuracion {setting:s} sembla d’aver lo tipe {unknown_type:s} mas es pas un tipe pres en carga pel sistèma.", - "hook_exec_failed": "Fracàs de l’execucion del script : « {path:s} »", - "hook_exec_not_terminated": "Lo escript « {path:s} » a pas acabat corrèctament", + "global_settings_bad_choice_for_enum": "La valor del paramètre {setting} es incorrècta. Recebut : {choice}, mas las opcions esperadas son : {available_choices}", + "global_settings_bad_type_for_setting": "Lo tipe del paramètre {setting} es incorrècte, recebut : {received_type}, esperat {expected_type}", + "global_settings_cant_write_settings": "Fracàs de l’escritura del fichièr de configuracion, rason : {reason}", + "global_settings_unknown_type": "Situacion inesperada, la configuracion {setting} sembla d’aver lo tipe {unknown_type} mas es pas un tipe pres en carga pel sistèma.", + "hook_exec_failed": "Fracàs de l’execucion del script : « {path} »", + "hook_exec_not_terminated": "Lo escript « {path} » a pas acabat corrèctament", "hook_list_by_invalid": "La proprietat de tria de las accions es invalida", - "hook_name_unknown": "Nom de script « {name:s} » desconegut", - "mail_domain_unknown": "Lo domeni de corrièl « {domain:s} » es desconegut", + "hook_name_unknown": "Nom de script « {name} » desconegut", + "mail_domain_unknown": "Lo domeni de corrièl « {domain} » es desconegut", "mailbox_used_space_dovecot_down": "Lo servici corrièl Dovecot deu èsser aviat, se volètz conéisser l’espaci ocupat per la messatjariá", - "service_disable_failed": "Impossible de desactivar lo servici « {service:s} »↵\n↵\nJornals recents : {logs:s}", - "service_disabled": "Lo servici « {service:s} » es desactivat", - "service_enable_failed": "Impossible d’activar lo servici « {service:s} »↵\n↵\nJornals recents : {logs:s}", - "service_enabled": "Lo servici « {service:s} » es activat", - "service_remove_failed": "Impossible de levar lo servici « {service:s} »", - "service_removed": "Lo servici « {service:s} » es estat levat", - "service_start_failed": "Impossible d’aviar lo servici « {service:s} »↵\n↵\nJornals recents : {logs:s}", - "service_started": "Lo servici « {service:s} » es aviat", - "service_stop_failed": "Impossible d’arrestar lo servici « {service:s} »↵\n\nJornals recents : {logs:s}", + "service_disable_failed": "Impossible de desactivar lo servici « {service} »↵\n↵\nJornals recents : {logs}", + "service_disabled": "Lo servici « {service} » es desactivat", + "service_enable_failed": "Impossible d’activar lo servici « {service} »↵\n↵\nJornals recents : {logs}", + "service_enabled": "Lo servici « {service} » es activat", + "service_remove_failed": "Impossible de levar lo servici « {service} »", + "service_removed": "Lo servici « {service} » es estat levat", + "service_start_failed": "Impossible d’aviar lo servici « {service} »↵\n↵\nJornals recents : {logs}", + "service_started": "Lo servici « {service} » es aviat", + "service_stop_failed": "Impossible d’arrestar lo servici « {service} »↵\n\nJornals recents : {logs}", "ssowat_conf_generated": "La configuracion SSowat es generada", "ssowat_conf_updated": "La configuracion SSOwat es estada actualizada", "system_upgraded": "Lo sistèma es estat actualizat", @@ -190,46 +190,46 @@ "user_deleted": "L’utilizaire es suprimit", "user_deletion_failed": "Supression impossibla de l’utilizaire", "user_home_creation_failed": "Creacion impossibla del repertòri personal a l’utilizaire", - "user_unknown": "Utilizaire « {user:s} » desconegut", + "user_unknown": "Utilizaire « {user} » desconegut", "user_update_failed": "Modificacion impossibla de l’utilizaire", "user_updated": "L’utilizaire es estat modificat", "service_description_avahi-daemon": "permet d’aténher vòstre servidor via yunohost.local sus vòstre ret local", "service_description_dnsmasq": "gerís la resolucion dels noms de domeni (DNS)", "updating_apt_cache": "Actualizacion de la lista dels paquets disponibles…", - "server_reboot_confirm": "Lo servidor es per reaviar sul pic, o volètz vertadièrament ? {answers:s}", - "service_add_failed": "Apondon impossible del servici « {service:s} »", - "service_added": "Lo servici « {service:s} » es ajustat", - "service_already_started": "Lo servici « {service:s} » es ja aviat", - "service_already_stopped": "Lo servici « {service:s} » es ja arrestat", + "server_reboot_confirm": "Lo servidor es per reaviar sul pic, o volètz vertadièrament ? {answers}", + "service_add_failed": "Apondon impossible del servici « {service} »", + "service_added": "Lo servici « {service} » es ajustat", + "service_already_started": "Lo servici « {service} » es ja aviat", + "service_already_stopped": "Lo servici « {service} » es ja arrestat", "restore_cleaning_failed": "Impossible de netejar lo repertòri temporari de restauracion", "restore_complete": "Restauracion acabada", - "restore_confirm_yunohost_installed": "Volètz vertadièrament restaurar un sistèma ja installat ? {answers:s}", + "restore_confirm_yunohost_installed": "Volètz vertadièrament restaurar un sistèma ja installat ? {answers}", "restore_extracting": "Extraccions dels fichièrs necessaris dins de l’archiu…", "restore_failed": "Impossible de restaurar lo sistèma", - "restore_hook_unavailable": "Lo script de restauracion « {part:s} » es pas disponible sus vòstre sistèma e es pas tanpauc dins l’archiu", + "restore_hook_unavailable": "Lo script de restauracion « {part} » es pas disponible sus vòstre sistèma e es pas tanpauc dins l’archiu", "restore_may_be_not_enough_disk_space": "Lo sistèma sembla d’aver pas pro d’espaci disponible (liure : {free_space:d} octets, necessari : {needed_space:d} octets, marge de seguretat : {margin:d} octets)", "restore_not_enough_disk_space": "Espaci disponible insufisent (liure : {free_space:d} octets, necessari : {needed_space:d} octets, marge de seguretat : {margin:d} octets)", "restore_nothings_done": "Res es pas estat restaurat", "restore_removing_tmp_dir_failed": "Impossible de levar u ancian repertòri temporari", - "restore_running_app_script": "Lançament del script de restauracion per l’aplicacion « {app:s} »…", + "restore_running_app_script": "Lançament del script de restauracion per l’aplicacion « {app} »…", "restore_running_hooks": "Execucion dels scripts de restauracion…", - "restore_system_part_failed": "Restauracion impossibla de la part « {part:s} » del sistèma", + "restore_system_part_failed": "Restauracion impossibla de la part « {part} » del sistèma", "server_shutdown": "Lo servidor serà atudat", - "server_shutdown_confirm": "Lo servidor es per s’atudar sul pic, o volètz vertadièrament ? {answers:s}", + "server_shutdown_confirm": "Lo servidor es per s’atudar sul pic, o volètz vertadièrament ? {answers}", "server_reboot": "Lo servidor es per reaviar", - "not_enough_disk_space": "Espaci disc insufisent sus « {path:s} »", - "service_cmd_exec_failed": "Impossible d’executar la comanda « {command:s} »", + "not_enough_disk_space": "Espaci disc insufisent sus « {path} »", + "service_cmd_exec_failed": "Impossible d’executar la comanda « {command} »", "service_description_mysql": "garda las donadas de las aplicacions (base de donadas SQL)", "service_description_postfix": "emplegat per enviar e recebre de corrièls", "service_description_slapd": "garda los utilizaires, domenis e lors informacions ligadas", "service_description_ssh": "vos permet de vos connectar a distància a vòstre servidor via un teminal (protocòl SSH)", "service_description_yunohost-api": "permet las interaccions entre l’interfàcia web de YunoHost e le sistèma", "service_description_yunohost-firewall": "gerís los pòrts de connexion dobèrts e tampats als servicis", - "global_settings_cant_serialize_settings": "Fracàs de la serializacion de las donadas de parametratge, rason : {reason:s}", + "global_settings_cant_serialize_settings": "Fracàs de la serializacion de las donadas de parametratge, rason : {reason}", "ip6tables_unavailable": "Podètz pas jogar amb ip6tables aquí. Siá sèts dins un contenedor, siá vòstre nuclèu es pas compatible amb aquela opcion", "iptables_unavailable": "Podètz pas jogar amb iptables aquí. Siá sèts dins un contenedor, siá vòstre nuclèu es pas compatible amb aquela opcion", - "mail_alias_remove_failed": "Supression impossibla de l’alias de corrièl « {mail:s} »", - "mail_forward_remove_failed": "Supression impossibla del corrièl de transferiment « {mail:s} »", + "mail_alias_remove_failed": "Supression impossibla de l’alias de corrièl « {mail} »", + "mail_forward_remove_failed": "Supression impossibla del corrièl de transferiment « {mail} »", "migrations_migration_has_failed": "La migracion {id} a pas capitat, abandon. Error : {exception}", "migrations_skip_migration": "Passatge de la migracion {id}…", "migrations_to_be_ran_manually": "La migracion {id} deu èsser lançada manualament. Mercés d’anar a Aisinas > Migracion dins l’interfàcia admin, o lançar « yunohost tools migrations run ».", @@ -242,12 +242,12 @@ "service_description_redis-server": "una basa de donadas especializada per un accès rapid a las donadas, las filas d’espèra e la comunicacion entre programas", "service_description_rspamd": "filtra lo corrièl pas desirat e mai foncionalitats ligadas al corrièl", "pattern_mailbox_quota": "Deu èsser una talha amb lo sufixe b/k/M/G/T o 0 per desactivar la quòta", - "backup_archive_writing_error": "Impossible d’ajustar los fichièrs « {source:s} » a la salvagarda (nomenats dins l’archiu « {dest:s} »)dins l’archiu comprimit « {archive:s} »", + "backup_archive_writing_error": "Impossible d’ajustar los fichièrs « {source} » a la salvagarda (nomenats dins l’archiu « {dest} »)dins l’archiu comprimit « {archive} »", "backup_cant_mount_uncompress_archive": "Impossible de montar en lectura sola lo repertòri de l’archiu descomprimit", "backup_no_uncompress_archive_dir": "Lo repertòri de l’archiu descomprimit existís pas", "pattern_username": "Deu èsser compausat solament de caractèrs alfanumerics en letras minusculas e de tirets basses", "experimental_feature": "Atencion : aquesta foncionalitat es experimentala e deu pas èsser considerada coma establa, deuriatz pas l’utilizar levat que sapiatz çò que fasètz.", - "log_corrupted_md_file": "Lo fichièr YAML de metadonadas ligat als jornals d’audit es damatjat : « {md_file} »\nError : {error:s}", + "log_corrupted_md_file": "Lo fichièr YAML de metadonadas ligat als jornals d’audit es damatjat : « {md_file} »\nError : {error}", "log_link_to_log": "Jornal complèt d’aquesta operacion : {desc}", "log_help_to_get_log": "Per veire lo jornal d’aquesta operacion « {desc} », utilizatz la comanda « yunohost log show {name}{name} »", "log_link_to_failed_log": "L’operacion « {desc} » a pas capitat ! Per obténer d’ajuda, mercés de fornir lo jornal complèt de l’operacion", @@ -300,27 +300,27 @@ "ask_new_path": "Nòu camin", "backup_actually_backuping": "Creacion d’un archiu de seguretat a partir dels fichièrs recuperats...", "backup_mount_archive_for_restore": "Preparacion de l’archiu per restauracion...", - "dyndns_could_not_check_available": "Verificacion impossibla de la disponibilitat de {domain:s} sus {provider:s}.", - "file_does_not_exist": "Lo camin {path:s} existís pas.", + "dyndns_could_not_check_available": "Verificacion impossibla de la disponibilitat de {domain} sus {provider}.", + "file_does_not_exist": "Lo camin {path} existís pas.", "global_settings_setting_security_password_admin_strength": "Fòrça del senhal administrator", "global_settings_setting_security_password_user_strength": "Fòrça del senhal utilizaire", "root_password_replaced_by_admin_password": "Lo senhal root es estat remplaçat pel senhal administrator.", - "service_restarted": "Lo servici '{service:s}' es estat reaviat", + "service_restarted": "Lo servici '{service}' es estat reaviat", "admin_password_too_long": "Causissètz un senhal d’almens 127 caractèrs", - "service_reloaded": "Lo servici « {service:s} » es estat tornat cargar", + "service_reloaded": "Lo servici « {service} » es estat tornat cargar", "already_up_to_date": "I a pas res a far ! Tot es ja a jorn !", "app_action_cannot_be_ran_because_required_services_down": "Aquestas aplicacions necessitan d’èsser lançadas per poder executar aquesta accion : {services}. Abans de contunhar deuriatz ensajar de reaviar los servicis seguents (e tanben cercar perque son tombats en pana) : {services}", - "confirm_app_install_warning": "Atencion : aquesta aplicacion fonciona mas non es pas ben integrada amb YunoHost. Unas foncionalitats coma l’autentificacion unica e la còpia de seguretat/restauracion pòdon èsser indisponiblas. volètz l’installar de totas manièras ? [{answers:s}] ", - "confirm_app_install_danger": "PERILH ! Aquesta aplicacion es encara experimentala (autrament dich, fonciona pas) e es possible que còpe lo sistèma ! Deuriatz PAS l’installar se non sabètz çò que fasètz. Volètz vertadièrament córrer aqueste risc ? [{answers:s}]", - "confirm_app_install_thirdparty": "ATENCION ! L’installacion d’aplicacions tèrças pòt comprometre l’integralitat e la seguretat del sistèma. Deuriatz PAS l’installar se non sabètz pas çò que fasètz. Volètz vertadièrament córrer aqueste risc ? [{answers:s}] ", + "confirm_app_install_warning": "Atencion : aquesta aplicacion fonciona mas non es pas ben integrada amb YunoHost. Unas foncionalitats coma l’autentificacion unica e la còpia de seguretat/restauracion pòdon èsser indisponiblas. volètz l’installar de totas manièras ? [{answers}] ", + "confirm_app_install_danger": "PERILH ! Aquesta aplicacion es encara experimentala (autrament dich, fonciona pas) e es possible que còpe lo sistèma ! Deuriatz PAS l’installar se non sabètz çò que fasètz. Volètz vertadièrament córrer aqueste risc ? [{answers}]", + "confirm_app_install_thirdparty": "ATENCION ! L’installacion d’aplicacions tèrças pòt comprometre l’integralitat e la seguretat del sistèma. Deuriatz PAS l’installar se non sabètz pas çò que fasètz. Volètz vertadièrament córrer aqueste risc ? [{answers}] ", "dpkg_lock_not_available": "Aquesta comanda pòt pas s’executar pel moment perque un autre programa sembla utilizar lo varrolh de dpkg (lo gestionari de paquets del sistèma)", "log_regen_conf": "Regenerar las configuracions del sistèma « {} »", - "service_reloaded_or_restarted": "Lo servici « {service:s} » es estat recargat o reaviat", + "service_reloaded_or_restarted": "Lo servici « {service} » es estat recargat o reaviat", "tools_upgrade_regular_packages_failed": "Actualizacion impossibla dels paquets seguents : {packages_list}", "tools_upgrade_special_packages_completed": "L’actualizacion dels paquets de YunoHost es acabada !\nQuichatz [Entrada] per tornar a la linha de comanda", "dpkg_is_broken": "Podètz pas far aquò pel moment perque dpkg/APT (los gestionaris de paquets del sistèma) sembla èsser mal configurat… Podètz ensajar de solucionar aquò en vos connectar via SSH e en executar « sudo dpkg --configure -a ».", "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Autorizar l’utilizacion de la clau òst DSA (obsolèta) per la configuracion del servici SSH", - "hook_json_return_error": "Fracàs de la lectura del retorn de l’script {path:s}. Error : {msg:s}. Contengut brut : {raw_content}", + "hook_json_return_error": "Fracàs de la lectura del retorn de l’script {path}. Error : {msg}. Contengut brut : {raw_content}", "pattern_password_app": "O planhèm, los senhals devon pas conténer los caractèrs seguents : {forbidden_chars}", "regenconf_file_backed_up": "Lo fichièr de configuracion « {conf} » es estat salvagardat dins « {backup} »", "regenconf_file_copy_failed": "Còpia impossibla del nòu fichièr de configuracion « {new} » cap a « {conf} »", @@ -342,9 +342,9 @@ "global_settings_setting_security_ssh_compatibility": "Solucion de compromés entre compatibilitat e seguretat pel servidor SSH. Afècta los criptografs (e d’autres aspèctes ligats amb la seguretat)", "global_settings_setting_security_postfix_compatibility": "Solucion de compromés entre compatibilitat e seguretat pel servidor Postfix. Afècta los criptografs (e d’autres aspèctes ligats amb la seguretat)", "service_regen_conf_is_deprecated": "« yunohost service regen-conf » es despreciat ! Utilizatz « yunohost tools regen-conf » allòc.", - "service_reload_failed": "Impossible de recargar lo servici « {service:s} »\n\nJornal d’audit recent : {logs:s}", - "service_restart_failed": "Impossible de reaviar lo servici « {service:s} »\n\nJornal d’audit recent : {logs:s}", - "service_reload_or_restart_failed": "Impossible de recargar o reaviar lo servici « {service:s} »\n\nJornal d’audit recent : {logs:s}", + "service_reload_failed": "Impossible de recargar lo servici « {service} »\n\nJornal d’audit recent : {logs}", + "service_restart_failed": "Impossible de reaviar lo servici « {service} »\n\nJornal d’audit recent : {logs}", + "service_reload_or_restart_failed": "Impossible de recargar o reaviar lo servici « {service} »\n\nJornal d’audit recent : {logs}", "regenconf_file_kept_back": "S’espèra que lo fichièr de configuracion « {conf} » siá suprimit per regen-conf (categoria {category} mas es estat mantengut.", "this_action_broke_dpkg": "Aquesta accion a copat dpkg/apt (los gestionaris de paquets del sistèma)… Podètz ensajar de resòlver aqueste problèma en vos connectant amb SSH e executant « sudo dpkg --configure -a ».", "tools_upgrade_at_least_one": "Especificatz --apps O --system", @@ -354,7 +354,7 @@ "tools_upgrade_special_packages_explanation": "Aquesta accion s’acabarà mas l’actualizacion especiala actuala contunharà en rèire-plan. Comencetz pas cap d’autra accion sul servidor dins las ~ 10 minutas que venon (depend de la velocitat de la maquina). Un còp acabat, benlèu que vos calrà vos tornar connectar a l’interfàcia d’administracion. Los jornals d’audit de l’actualizacion seràn disponibles a Aisinas > Jornals d’audit (dins l’interfàcia d’administracion) o amb « yunohost log list » (en linha de comanda).", "update_apt_cache_failed": "I a agut d’errors en actualizar la memòria cache d’APT (lo gestionari de paquets de Debian). Aquí avètz las linhas de sources.list que pòdon vos ajudar a identificar las linhas problematicas : \n{sourceslist}", "update_apt_cache_warning": "I a agut d’errors en actualizar la memòria cache d’APT (lo gestionari de paquets de Debian). Aquí avètz las linhas de sources.list que pòdon vos ajudar a identificar las linhas problematicas : \n{sourceslist}", - "backup_permission": "Autorizacion de salvagarda per l’aplicacion {app:s}", + "backup_permission": "Autorizacion de salvagarda per l’aplicacion {app}", "group_created": "Grop « {group} » creat", "group_creation_failed": "Fracàs de la creacion del grop « {group} » : {error}", "group_deleted": "Lo grop « {group} » es estat suprimit", @@ -364,15 +364,15 @@ "group_updated": "Lo grop « {group} » es estat actualizat", "group_update_failed": "Actualizacion impossibla del grop « {group} » : {error}", "log_user_group_update": "Actualizar lo grop « {} »", - "permission_already_exist": "La permission « {permission:s} » existís ja", - "permission_created": "Permission « {permission:s} » creada", + "permission_already_exist": "La permission « {permission} » existís ja", + "permission_created": "Permission « {permission} » creada", "permission_creation_failed": "Creacion impossibla de la permission", - "permission_deleted": "Permission « {permission:s} » suprimida", - "permission_deletion_failed": "Fracàs de la supression de la permission « {permission:s} »", - "permission_not_found": "Permission « {permission:s} » pas trobada", + "permission_deleted": "Permission « {permission} » suprimida", + "permission_deletion_failed": "Fracàs de la supression de la permission « {permission} »", + "permission_not_found": "Permission « {permission} » pas trobada", "permission_update_failed": "Fracàs de l’actualizacion de la permission", - "permission_updated": "La permission « {permission:s} » es estada actualizada", - "mailbox_disabled": "La bóstia de las letras es desactivada per l’utilizaire {user:s}", + "permission_updated": "La permission « {permission} » es estada actualizada", + "mailbox_disabled": "La bóstia de las letras es desactivada per l’utilizaire {user}", "migrations_success_forward": "Migracion {id} corrèctament realizada !", "migrations_running_forward": "Execucion de la migracion {id}…", "migrations_must_provide_explicit_targets": "Devètz fornir una cibla explicita quand utilizatz using --skip o --force-rerun", @@ -495,8 +495,8 @@ "app_manifest_install_ask_domain": "Causissètz lo domeni ont volètz installar aquesta aplicacion", "app_argument_password_no_default": "Error pendent l’analisi de l’argument del senhal « {name} » : l’argument de senhal pòt pas aver de valor per defaut per de rason de seguretat", "app_label_deprecated": "Aquesta comanda es estada renduda obsolèta. Mercés d'utilizar lo nòva \"yunohost user permission update\" per gerir letiquetada de l'aplication", - "additional_urls_already_removed": "URL addicionala {url:s} es ja estada elimida per la permission «#permission:s»", - "additional_urls_already_added": "URL addicionadal «{url:s}'» es ja estada aponduda per la permission «{permission:s}»", + "additional_urls_already_removed": "URL addicionala {url} es ja estada elimida per la permission «#permission:s»", + "additional_urls_already_added": "URL addicionadal «{url}'» es ja estada aponduda per la permission «{permission}»", "migration_0015_yunohost_upgrade": "Aviada de la mesa a jorn de YunoHost...", "migration_0015_main_upgrade": "Aviada de la mesa a nivèl generala...", "migration_0015_patching_sources_list": "Mesa a jorn del fichièr sources.lists...", diff --git a/locales/pl.json b/locales/pl.json index 46ec8b622..caf108367 100644 --- a/locales/pl.json +++ b/locales/pl.json @@ -1,12 +1,12 @@ { "password_too_simple_1": "Hasło musi mieć co najmniej 8 znaków", - "app_already_up_to_date": "{app:s} jest obecnie aktualna", - "app_already_installed": "{app:s} jest już zainstalowane", + "app_already_up_to_date": "{app} jest obecnie aktualna", + "app_already_installed": "{app} jest już zainstalowane", "already_up_to_date": "Nic do zrobienia. Wszystko jest obecnie aktualne.", "admin_password_too_long": "Proszę wybrać hasło krótsze niż 127 znaków", "admin_password_changed": "Hasło administratora zostało zmienione", "admin_password_change_failed": "Nie można zmienić hasła", "admin_password": "Hasło administratora", - "action_invalid": "Nieprawidłowa operacja '{action:s}'", + "action_invalid": "Nieprawidłowa operacja '{action}'", "aborting": "Przerywanie." } \ No newline at end of file diff --git a/locales/pt.json b/locales/pt.json index 9bb949dec..72d983a39 100644 --- a/locales/pt.json +++ b/locales/pt.json @@ -1,19 +1,19 @@ { - "action_invalid": "Acção Inválida '{action:s}'", + "action_invalid": "Acção Inválida '{action}'", "admin_password": "Senha de administração", "admin_password_change_failed": "Não é possível alterar a senha", "admin_password_changed": "A senha da administração foi alterada", - "app_already_installed": "{app:s} já está instalada", + "app_already_installed": "{app} já está instalada", "app_extraction_failed": "Não foi possível extrair os ficheiros para instalação", "app_id_invalid": "A ID da aplicação é inválida", "app_install_files_invalid": "Ficheiros para instalação corrompidos", "app_manifest_invalid": "Manifesto da aplicação inválido: {error}", - "app_not_installed": "{app:s} não está instalada", - "app_removed": "{app:s} removida com êxito", + "app_not_installed": "{app} não está instalada", + "app_removed": "{app} removida com êxito", "app_sources_fetch_failed": "Incapaz obter os ficheiros fonte", "app_unknown": "Aplicação desconhecida", - "app_upgrade_failed": "Não foi possível atualizar {app:s}", - "app_upgraded": "{app:s} atualizada com sucesso", + "app_upgrade_failed": "Não foi possível atualizar {app}", + "app_upgraded": "{app} atualizada com sucesso", "ask_firstname": "Primeiro nome", "ask_lastname": "Último nome", "ask_main_domain": "Domínio principal", @@ -21,7 +21,7 @@ "ask_password": "Senha", "backup_created": "Backup completo", "backup_output_directory_not_empty": "A pasta de destino não se encontra vazia", - "custom_app_url_required": "Deve fornecer um link para atualizar a sua aplicação personalizada {app:s}", + "custom_app_url_required": "Deve fornecer um link para atualizar a sua aplicação personalizada {app}", "domain_cert_gen_failed": "Não foi possível gerar o certificado", "domain_created": "Domínio criado com êxito", "domain_creation_failed": "Não foi possível criar o domínio", @@ -38,16 +38,16 @@ "dyndns_ip_updated": "Endereço IP atualizado com êxito a partir de DynDNS", "dyndns_key_generating": "A chave DNS está a ser gerada, isto pode demorar um pouco...", "dyndns_registered": "Dom+inio DynDNS registado com êxito", - "dyndns_registration_failed": "Não foi possível registar o domínio DynDNS: {error:s}", + "dyndns_registration_failed": "Não foi possível registar o domínio DynDNS: {error}", "dyndns_unavailable": "Subdomínio DynDNS indisponível", "extracting": "Extração em curso...", - "field_invalid": "Campo inválido '{:s}'", + "field_invalid": "Campo inválido '{}'", "firewall_reloaded": "Firewall recarregada com êxito", "installation_complete": "Instalação concluída", "iptables_unavailable": "Não pode alterar aqui a iptables. Ou o seu kernel não o suporta ou está num espaço reservado.", - "mail_alias_remove_failed": "Não foi possível remover a etiqueta de correio '{mail:s}'", - "mail_domain_unknown": "Domínio de endereço de correio '{domain:s}' inválido. Por favor, usa um domínio administrado per esse servidor.", - "mail_forward_remove_failed": "Não foi possível remover o reencaminhamento de correio '{mail:s}'", + "mail_alias_remove_failed": "Não foi possível remover a etiqueta de correio '{mail}'", + "mail_domain_unknown": "Domínio de endereço de correio '{domain}' inválido. Por favor, usa um domínio administrado per esse servidor.", + "mail_forward_remove_failed": "Não foi possível remover o reencaminhamento de correio '{mail}'", "main_domain_change_failed": "Incapaz alterar o domínio raiz", "main_domain_changed": "Domínio raiz alterado com êxito", "packages_upgrade_failed": "Não foi possível atualizar todos os pacotes", @@ -57,23 +57,23 @@ "pattern_lastname": "Deve ser um último nome válido", "pattern_password": "Deve ter no mínimo 3 caracteres", "pattern_username": "Devem apenas ser carácteres minúsculos alfanuméricos e subtraços", - "restore_confirm_yunohost_installed": "Quer mesmo restaurar um sistema já instalado? [{answers:s}]", - "service_add_failed": "Incapaz adicionar serviço '{service:s}'", + "restore_confirm_yunohost_installed": "Quer mesmo restaurar um sistema já instalado? [{answers}]", + "service_add_failed": "Incapaz adicionar serviço '{service}'", "service_added": "Serviço adicionado com êxito", - "service_already_started": "O serviço '{service:s}' já está em execussão", - "service_already_stopped": "O serviço '{service:s}' já está parado", - "service_cmd_exec_failed": "Incapaz executar o comando '{command:s}'", - "service_disable_failed": "Incapaz desativar o serviço '{service:s}'", - "service_disabled": "O serviço '{service:s}' foi desativado com êxito", - "service_enable_failed": "Incapaz de ativar o serviço '{service:s}'", - "service_enabled": "Serviço '{service:s}' ativado com êxito", - "service_remove_failed": "Incapaz de remover o serviço '{service:s}'", + "service_already_started": "O serviço '{service}' já está em execussão", + "service_already_stopped": "O serviço '{service}' já está parado", + "service_cmd_exec_failed": "Incapaz executar o comando '{command}'", + "service_disable_failed": "Incapaz desativar o serviço '{service}'", + "service_disabled": "O serviço '{service}' foi desativado com êxito", + "service_enable_failed": "Incapaz de ativar o serviço '{service}'", + "service_enabled": "Serviço '{service}' ativado com êxito", + "service_remove_failed": "Incapaz de remover o serviço '{service}'", "service_removed": "Serviço eliminado com êxito", - "service_start_failed": "Não foi possível iniciar o serviço '{service:s}'", - "service_started": "O serviço '{service:s}' foi iniciado com êxito", - "service_stop_failed": "Incapaz parar o serviço '{service:s}'", - "service_stopped": "O serviço '{service:s}' foi parado com êxito", - "service_unknown": "Serviço desconhecido '{service:s}'", + "service_start_failed": "Não foi possível iniciar o serviço '{service}'", + "service_started": "O serviço '{service}' foi iniciado com êxito", + "service_stop_failed": "Incapaz parar o serviço '{service}'", + "service_stopped": "O serviço '{service}' foi parado com êxito", + "service_unknown": "Serviço desconhecido '{service}'", "ssowat_conf_generated": "Configuração SSOwat gerada com êxito", "ssowat_conf_updated": "Configuração persistente SSOwat atualizada com êxito", "system_upgraded": "Sistema atualizado com êxito", @@ -93,40 +93,40 @@ "yunohost_configured": "YunoHost configurada com êxito", "yunohost_installing": "A instalar a YunoHost...", "yunohost_not_installed": "YunoHost ainda não está corretamente configurado. Por favor execute as 'ferramentas pós-instalação yunohost'.", - "app_not_correctly_installed": "{app:s} parece não estar corretamente instalada", - "app_not_properly_removed": "{app:s} não foi corretamente removido", + "app_not_correctly_installed": "{app} parece não estar corretamente instalada", + "app_not_properly_removed": "{app} não foi corretamente removido", "app_requirements_checking": "Verificando os pacotes necessários para {app}...", "app_unsupported_remote_type": "A aplicação não possui suporte ao tipo remoto utilizado", - "backup_archive_app_not_found": "A aplicação '{app:s}' não foi encontrada no arquivo de backup", - "backup_archive_broken_link": "Impossível acessar o arquivo de backup (link quebrado ao {path:s})", + "backup_archive_app_not_found": "A aplicação '{app}' não foi encontrada no arquivo de backup", + "backup_archive_broken_link": "Impossível acessar o arquivo de backup (link quebrado ao {path})", "backup_archive_name_exists": "O nome do arquivo de backup já existe", "backup_archive_open_failed": "Não é possível abrir o arquivo de backup", "backup_cleaning_failed": "Não é possível limpar a pasta temporária de backups", "backup_creation_failed": "A criação do backup falhou", - "backup_delete_error": "Impossível apagar '{path:s}'", + "backup_delete_error": "Impossível apagar '{path}'", "backup_deleted": "O backup foi suprimido", - "backup_hook_unknown": "Gancho de backup '{hook:s}' desconhecido", + "backup_hook_unknown": "Gancho de backup '{hook}' desconhecido", "backup_nothings_done": "Não há nada para guardar", "backup_output_directory_forbidden": "Diretório de saída proibido. Os backups não podem ser criados em /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var ou /home/yunohost.backup/archives subpastas", "app_already_installed_cant_change_url": "Este aplicativo já está instalado. A URL não pode ser alterada apenas por esta função. Olhe para o `app changeurl` se estiver disponível.", - "app_already_up_to_date": "{app:s} já está atualizado", - "app_argument_choice_invalid": "Escolha inválida para o argumento '{name:s}', deve ser um dos {choices:s}", - "app_argument_invalid": "Valor inválido de argumento '{name:s}': {error:s}", - "app_argument_required": "O argumento '{name:s}' é obrigatório", - "app_change_url_failed_nginx_reload": "Falha ao reiniciar o nginx. Aqui está o retorno de 'nginx -t':\n{nginx_errors:s}", + "app_already_up_to_date": "{app} já está atualizado", + "app_argument_choice_invalid": "Escolha inválida para o argumento '{name}', deve ser um dos {choices}", + "app_argument_invalid": "Valor inválido de argumento '{name}': {error}", + "app_argument_required": "O argumento '{name}' é obrigatório", + "app_change_url_failed_nginx_reload": "Falha ao reiniciar o nginx. Aqui está o retorno de 'nginx -t':\n{nginx_errors}", "app_location_unavailable": "Esta url não está disponível ou está em conflito com outra aplicação já instalada", "app_upgrade_app_name": "Atualizando aplicação {app}…", "app_upgrade_some_app_failed": "Não foi possível atualizar algumas aplicações", "backup_abstract_method": "Este metodo de backup ainda não foi implementado", - "backup_app_failed": "Não foi possível fazer o backup dos aplicativos '{app:s}'", - "backup_applying_method_custom": "Chamando o metodo personalizado de backup '{method:s}'…", + "backup_app_failed": "Não foi possível fazer o backup dos aplicativos '{app}'", + "backup_applying_method_custom": "Chamando o metodo personalizado de backup '{method}'…", "backup_applying_method_tar": "Criando o arquivo tar de backup…", - "backup_archive_name_unknown": "Desconhece-se o arquivo local de backup de nome '{name:s}'", - "backup_archive_system_part_not_available": "A seção do sistema '{part:s}' está indisponivel neste backup", - "backup_ask_for_copying_if_needed": "Alguns arquivos não consiguiram ser preparados para backup utilizando o metodo que não gasta espaço de disco temporariamente. Para realizar o backup {size:s}MB precisam ser usados temporariamente. Você concorda?", + "backup_archive_name_unknown": "Desconhece-se o arquivo local de backup de nome '{name}'", + "backup_archive_system_part_not_available": "A seção do sistema '{part}' está indisponivel neste backup", + "backup_ask_for_copying_if_needed": "Alguns arquivos não consiguiram ser preparados para backup utilizando o metodo que não gasta espaço de disco temporariamente. Para realizar o backup {size}MB precisam ser usados temporariamente. Você concorda?", "backup_cant_mount_uncompress_archive": "Não foi possível montar em modo leitura o diretorio de arquivos não comprimido", - "backup_copying_to_organize_the_archive": "Copiando {size:s}MB para organizar o arquivo", - "app_change_url_identical_domains": "O antigo e o novo domínio / url_path são idênticos ('{domain:s}{path:s}'), nada para fazer.", + "backup_copying_to_organize_the_archive": "Copiando {size}MB para organizar o arquivo", + "app_change_url_identical_domains": "O antigo e o novo domínio / url_path são idênticos ('{domain}{path}'), nada para fazer.", "password_too_simple_1": "A senha precisa ter pelo menos 8 caracteres", "admin_password_too_long": "Escolha uma senha que contenha menos de 127 caracteres", "aborting": "Abortando." diff --git a/locales/ru.json b/locales/ru.json index afe8e06f0..5a74524bf 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -1,33 +1,33 @@ { - "action_invalid": "Неверное действие '{action:s}'", + "action_invalid": "Неверное действие '{action}'", "admin_password": "Пароль администратора", "admin_password_change_failed": "Невозможно изменить пароль", "admin_password_changed": "Пароль администратора был изменен", - "app_already_installed": "{app:s} уже установлено", + "app_already_installed": "{app} уже установлено", "app_already_installed_cant_change_url": "Это приложение уже установлено. URL не может быть изменен только с помощью этой функции. Изучите `app changeurl`, если это доступно.", - "app_argument_choice_invalid": "Неверный выбор для аргумента '{name:s}', Это должно быть '{choices:s}'", - "app_argument_invalid": "Недопустимое значение аргумента '{name:s}': {error:s}'", - "app_already_up_to_date": "{app:s} уже обновлено", - "app_argument_required": "Аргумент '{name:s}' необходим", - "app_change_url_identical_domains": "Старый и новый domain/url_path идентичны ('{domain:s}{path:s}'), ничего делать не надо.", - "app_change_url_no_script": "Приложение '{app_name:s}' не поддерживает изменение url. Наверное, вам нужно обновить приложение.", - "app_change_url_success": "Успешно изменён {app:s} url на {domain:s}{path:s}", + "app_argument_choice_invalid": "Неверный выбор для аргумента '{name}', Это должно быть '{choices}'", + "app_argument_invalid": "Недопустимое значение аргумента '{name}': {error}'", + "app_already_up_to_date": "{app} уже обновлено", + "app_argument_required": "Аргумент '{name}' необходим", + "app_change_url_identical_domains": "Старый и новый domain/url_path идентичны ('{domain}{path}'), ничего делать не надо.", + "app_change_url_no_script": "Приложение '{app_name}' не поддерживает изменение url. Наверное, вам нужно обновить приложение.", + "app_change_url_success": "Успешно изменён {app} url на {domain}{path}", "app_extraction_failed": "Невозможно извлечь файлы для инсталляции", "app_id_invalid": "Неправильный id приложения", "app_install_files_invalid": "Неправильные файлы инсталляции", - "app_location_unavailable": "Этот url отсутствует или конфликтует с уже установленным приложением или приложениями: {apps:s}", + "app_location_unavailable": "Этот url отсутствует или конфликтует с уже установленным приложением или приложениями: {apps}", "app_manifest_invalid": "Недопустимый манифест приложения: {error}", - "app_not_correctly_installed": "{app:s} , кажется, установлены неправильно", - "app_not_installed": "{app:s} не установлены", - "app_not_properly_removed": "{app:s} удалены неправильно", - "app_removed": "{app:s} удалено", + "app_not_correctly_installed": "{app} , кажется, установлены неправильно", + "app_not_installed": "{app} не установлены", + "app_not_properly_removed": "{app} удалены неправильно", + "app_removed": "{app} удалено", "app_requirements_checking": "Проверяю необходимые пакеты для {app}...", "app_sources_fetch_failed": "Невозможно получить исходные файлы", "app_unknown": "Неизвестное приложение", "app_upgrade_app_name": "Обновление приложения {app}...", - "app_upgrade_failed": "Невозможно обновить {app:s}", + "app_upgrade_failed": "Невозможно обновить {app}", "app_upgrade_some_app_failed": "Невозможно обновить некоторые приложения", - "app_upgraded": "{app:s} обновлено", + "app_upgraded": "{app} обновлено", "installation_complete": "Установка завершена", "password_too_simple_1": "Пароль должен быть не менее 8 символов" } \ No newline at end of file diff --git a/locales/sv.json b/locales/sv.json index 26162419e..39707d07c 100644 --- a/locales/sv.json +++ b/locales/sv.json @@ -5,7 +5,7 @@ "admin_password": "Administratörslösenord", "admin_password_too_long": "Välj gärna ett lösenord som inte innehåller fler än 127 tecken", "admin_password_change_failed": "Kan inte byta lösenord", - "action_invalid": "Ej tillåten åtgärd '{action:s}'", + "action_invalid": "Ej tillåten åtgärd '{action}'", "admin_password_changed": "Administratörskontots lösenord ändrades", "aborting": "Avbryter." } \ No newline at end of file diff --git a/locales/zh_Hans.json b/locales/zh_Hans.json index c034fa227..814ccfa49 100644 --- a/locales/zh_Hans.json +++ b/locales/zh_Hans.json @@ -4,14 +4,14 @@ "app_start_remove": "正在删除{app}……", "admin_password_change_failed": "无法修改密码", "admin_password_too_long": "请选择一个小于127个字符的密码", - "app_upgrade_failed": "不能升级{app:s}:{error}", + "app_upgrade_failed": "不能升级{app}:{error}", "app_id_invalid": "无效 app ID", "app_unknown": "未知应用", "admin_password_changed": "管理密码已更改", "aborting": "正在放弃。", "admin_password": "管理员密码", "app_start_restore": "正在恢复{app}……", - "action_invalid": "无效操作 '{action:s}'", + "action_invalid": "无效操作 '{action}'", "ask_lastname": "姓", "diagnosis_everything_ok": "{category}一切看起来不错!", "diagnosis_found_warnings": "找到{warnings}项,可能需要{category}进行改进。", @@ -31,36 +31,36 @@ "diagnosis_basesystem_host": "服务器正在运行Debian {debian_version}", "diagnosis_basesystem_hardware_model": "服务器型号为 {model}", "diagnosis_basesystem_hardware": "服务器硬件架构为{virt} {arch}", - "custom_app_url_required": "您必须提供URL才能升级自定义应用 {app:s}", - "confirm_app_install_thirdparty": "危险! 该应用程序不是YunoHost的应用程序目录的一部分。 安装第三方应用程序可能会损害系统的完整性和安全性。 除非您知道自己在做什么,否则可能不应该安装它, 如果此应用无法运行或无法正常使用系统,将不会提供任何支持。如果您仍然愿意承担此风险,请输入'{answers:s}'", - "confirm_app_install_danger": "危险! 已知此应用仍处于实验阶段(如果未明确无法正常运行)! 除非您知道自己在做什么,否则可能不应该安装它。 如果此应用无法运行或无法正常使用系统,将不会提供任何支持。如果您仍然愿意承担此风险,请输入'{answers:s}'", - "confirm_app_install_warning": "警告:此应用程序可能可以运行,但未与YunoHost很好地集成。某些功能(例如单点登录和备份/还原)可能不可用, 仍要安装吗? [{answers:s}] ", - "certmanager_unable_to_parse_self_CA_name": "无法解析自签名授权的名称 (file: {file:s})", - "certmanager_self_ca_conf_file_not_found": "找不到用于自签名授权的配置文件(file: {file:s})", - "certmanager_no_cert_file": "无法读取域{domain:s}的证书文件(file: {file:s})", - "certmanager_hit_rate_limit": "最近已经为此域{domain:s}颁发了太多的证书。请稍后再试。有关更多详细信息,请参见https://letsencrypt.org/docs/rate-limits/", - "certmanager_warning_subdomain_dns_record": "子域'{subdomain:s}' 不能解析为与 '{domain:s}'相同的IP地址, 在修复此问题并重新生成证书之前,某些功能将不可用。", - "certmanager_domain_http_not_working": "域 {domain:s}似乎无法通过HTTP访问。请检查诊断中的“网络”类别以获取更多信息。(如果您知道自己在做什么,请使用“ --no-checks”关闭这些检查。)", - "certmanager_domain_dns_ip_differs_from_public_ip": "域'{domain:s}' 的DNS记录与此服务器的IP不同。请检查诊断中的“ DNS记录”(基本)类别,以获取更多信息。 如果您最近修改了A记录,请等待它传播(某些DNS传播检查器可在线获得)。 (如果您知道自己在做什么,请使用“ --no-checks”关闭这些检查。)", - "certmanager_domain_cert_not_selfsigned": "域 {domain:s} 的证书不是自签名的, 您确定要更换它吗?(使用“ --force”这样做。)", + "custom_app_url_required": "您必须提供URL才能升级自定义应用 {app}", + "confirm_app_install_thirdparty": "危险! 该应用程序不是YunoHost的应用程序目录的一部分。 安装第三方应用程序可能会损害系统的完整性和安全性。 除非您知道自己在做什么,否则可能不应该安装它, 如果此应用无法运行或无法正常使用系统,将不会提供任何支持。如果您仍然愿意承担此风险,请输入'{answers}'", + "confirm_app_install_danger": "危险! 已知此应用仍处于实验阶段(如果未明确无法正常运行)! 除非您知道自己在做什么,否则可能不应该安装它。 如果此应用无法运行或无法正常使用系统,将不会提供任何支持。如果您仍然愿意承担此风险,请输入'{answers}'", + "confirm_app_install_warning": "警告:此应用程序可能可以运行,但未与YunoHost很好地集成。某些功能(例如单点登录和备份/还原)可能不可用, 仍要安装吗? [{answers}] ", + "certmanager_unable_to_parse_self_CA_name": "无法解析自签名授权的名称 (file: {file})", + "certmanager_self_ca_conf_file_not_found": "找不到用于自签名授权的配置文件(file: {file})", + "certmanager_no_cert_file": "无法读取域{domain}的证书文件(file: {file})", + "certmanager_hit_rate_limit": "最近已经为此域{domain}颁发了太多的证书。请稍后再试。有关更多详细信息,请参见https://letsencrypt.org/docs/rate-limits/", + "certmanager_warning_subdomain_dns_record": "子域'{subdomain}' 不能解析为与 '{domain}'相同的IP地址, 在修复此问题并重新生成证书之前,某些功能将不可用。", + "certmanager_domain_http_not_working": "域 {domain}似乎无法通过HTTP访问。请检查诊断中的“网络”类别以获取更多信息。(如果您知道自己在做什么,请使用“ --no-checks”关闭这些检查。)", + "certmanager_domain_dns_ip_differs_from_public_ip": "域'{domain}' 的DNS记录与此服务器的IP不同。请检查诊断中的“ DNS记录”(基本)类别,以获取更多信息。 如果您最近修改了A记录,请等待它传播(某些DNS传播检查器可在线获得)。 (如果您知道自己在做什么,请使用“ --no-checks”关闭这些检查。)", + "certmanager_domain_cert_not_selfsigned": "域 {domain} 的证书不是自签名的, 您确定要更换它吗?(使用“ --force”这样做。)", "certmanager_domain_not_diagnosed_yet": "尚无域{domain} 的诊断结果。请在诊断部分中针对“ DNS记录”和“ Web”类别重新运行诊断,以检查该域是否已准备好安装“Let's Encrypt”证书。(或者,如果您知道自己在做什么,请使用“ --no-checks”关闭这些检查。)", - "certmanager_certificate_fetching_or_enabling_failed": "尝试将新证书用于 {domain:s}无效...", + "certmanager_certificate_fetching_or_enabling_failed": "尝试将新证书用于 {domain}无效...", "certmanager_cert_signing_failed": "无法签署新证书", - "certmanager_cert_install_success_selfsigned": "为域 '{domain:s}'安装了自签名证书", - "certmanager_cert_renew_success": "为域 '{domain:s}'续订“Let's Encrypt”证书", - "certmanager_cert_install_success": "为域'{domain:s}'安装“Let's Encrypt”证书", - "certmanager_cannot_read_cert": "尝试为域 {domain:s}(file: {file:s})打开当前证书时发生错误,原因: {reason:s}", - "certmanager_attempt_to_replace_valid_cert": "您正在尝试覆盖域{domain:s}的有效证书!(使用--force绕过)", - "certmanager_attempt_to_renew_valid_cert": "域'{domain:s}'的证书不会过期!(如果知道自己在做什么,则可以使用--force)", - "certmanager_attempt_to_renew_nonLE_cert": "“Let's Encrypt”未颁发域'{domain:s}'的证书,无法自动续订!", + "certmanager_cert_install_success_selfsigned": "为域 '{domain}'安装了自签名证书", + "certmanager_cert_renew_success": "为域 '{domain}'续订“Let's Encrypt”证书", + "certmanager_cert_install_success": "为域'{domain}'安装“Let's Encrypt”证书", + "certmanager_cannot_read_cert": "尝试为域 {domain}(file: {file})打开当前证书时发生错误,原因: {reason}", + "certmanager_attempt_to_replace_valid_cert": "您正在尝试覆盖域{domain}的有效证书!(使用--force绕过)", + "certmanager_attempt_to_renew_valid_cert": "域'{domain}'的证书不会过期!(如果知道自己在做什么,则可以使用--force)", + "certmanager_attempt_to_renew_nonLE_cert": "“Let's Encrypt”未颁发域'{domain}'的证书,无法自动续订!", "certmanager_acme_not_configured_for_domain": "目前无法针对{domain}运行ACME挑战,因为其nginx conf缺少相应的代码段...请使用“yunohost tools regen-conf nginx --dry-run --with-diff”确保您的nginx配置是最新的。", - "backup_with_no_restore_script_for_app": "{app:s} 没有还原脚本,您将无法自动还原该应用程序的备份。", - "backup_with_no_backup_script_for_app": "应用'{app:s}'没有备份脚本。无视。", + "backup_with_no_restore_script_for_app": "{app} 没有还原脚本,您将无法自动还原该应用程序的备份。", + "backup_with_no_backup_script_for_app": "应用'{app}'没有备份脚本。无视。", "backup_unable_to_organize_files": "无法使用快速方法来组织档案中的文件", - "backup_system_part_failed": "无法备份'{part:s}'系统部分", + "backup_system_part_failed": "无法备份'{part}'系统部分", "backup_running_hooks": "正在运行备份挂钩...", - "backup_permission": "{app:s}的备份权限", - "backup_output_symlink_dir_broken": "您的存档目录'{path:s}' 是断开的符号链接。 也许您忘记了重新安装/装入或插入它指向的存储介质。", + "backup_permission": "{app}的备份权限", + "backup_output_symlink_dir_broken": "您的存档目录'{path}' 是断开的符号链接。 也许您忘记了重新安装/装入或插入它指向的存储介质。", "backup_output_directory_required": "您必须提供备份的输出目录", "backup_output_directory_not_empty": "您应该选择一个空的输出目录", "backup_output_directory_forbidden": "选择一个不同的输出目录。无法在/bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var或/home/yunohost.backup/archives子文件夹中创建备份", @@ -68,35 +68,35 @@ "backup_no_uncompress_archive_dir": "没有这样的未压缩存档目录", "backup_mount_archive_for_restore": "正在准备存档以进行恢复...", "backup_method_tar_finished": "TAR备份存档已创建", - "backup_method_custom_finished": "自定义备份方法'{method:s}' 已完成", + "backup_method_custom_finished": "自定义备份方法'{method}' 已完成", "backup_method_copy_finished": "备份副本已完成", - "backup_hook_unknown": "备用挂钩'{hook:s}'未知", + "backup_hook_unknown": "备用挂钩'{hook}'未知", "backup_deleted": "备份已删除", - "backup_delete_error": "无法删除'{path:s}'", + "backup_delete_error": "无法删除'{path}'", "backup_custom_mount_error": "自定义备份方法无法通过“挂载”步骤", "backup_custom_backup_error": "自定义备份方法无法通过“备份”步骤", "backup_csv_creation_failed": "无法创建还原所需的CSV文件", "backup_csv_addition_failed": "无法将文件添加到CSV文件中进行备份", "backup_creation_failed": "无法创建备份存档", "backup_create_size_estimation": "归档文件将包含约{size}个数据。", - "backup_couldnt_bind": "无法将 {src:s} 绑定到{dest:s}.", - "backup_copying_to_organize_the_archive": "复制{size:s} MB来整理档案", + "backup_couldnt_bind": "无法将 {src} 绑定到{dest}.", + "backup_copying_to_organize_the_archive": "复制{size} MB来整理档案", "backup_cleaning_failed": "无法清理临时备份文件夹", "backup_cant_mount_uncompress_archive": "无法将未压缩的归档文件挂载为写保护", - "backup_ask_for_copying_if_needed": "您是否要临时使用{size:s} MB进行备份?(由于无法使用更有效的方法准备某些文件,因此使用这种方式。)", - "backup_archive_writing_error": "无法将要备份的文件'{source:s}'(在归档文'{dest:s}'中命名)添加到压缩归档文件 '{archive:s}'s}”中", - "backup_archive_system_part_not_available": "该备份中系统部分'{part:s}'不可用", + "backup_ask_for_copying_if_needed": "您是否要临时使用{size} MB进行备份?(由于无法使用更有效的方法准备某些文件,因此使用这种方式。)", + "backup_archive_writing_error": "无法将要备份的文件'{source}'(在归档文'{dest}'中命名)添加到压缩归档文件 '{archive}'s}”中", + "backup_archive_system_part_not_available": "该备份中系统部分'{part}'不可用", "backup_archive_corrupted": "备份存档'{archive}' 似乎已损坏 : {error}", "backup_archive_cant_retrieve_info_json": "无法加载档案'{archive}'的信息...无法检索到info.json(或者它不是有效的json)。", "backup_archive_open_failed": "无法打开备份档案", - "backup_archive_name_unknown": "未知的本地备份档案名为'{name:s}'", + "backup_archive_name_unknown": "未知的本地备份档案名为'{name}'", "backup_archive_name_exists": "具有该名称的备份存档已经存在。", - "backup_archive_broken_link": "无法访问备份存档(指向{path:s}的链接断开)", - "backup_archive_app_not_found": "在备份档案中找不到 {app:s}", + "backup_archive_broken_link": "无法访问备份存档(指向{path}的链接断开)", + "backup_archive_app_not_found": "在备份档案中找不到 {app}", "backup_applying_method_tar": "创建备份TAR存档...", - "backup_applying_method_custom": "调用自定义备份方法'{method:s}'...", + "backup_applying_method_custom": "调用自定义备份方法'{method}'...", "backup_applying_method_copy": "正在将所有文件复制到备份...", - "backup_app_failed": "无法备份{app:s}", + "backup_app_failed": "无法备份{app}", "backup_actually_backuping": "根据收集的文件创建备份档案...", "backup_abstract_method": "此备份方法尚未实现", "ask_password": "密码", @@ -113,7 +113,7 @@ "apps_catalog_init_success": "应用目录系统已初始化!", "apps_already_up_to_date": "所有应用程序都是最新的", "app_packaging_format_not_supported": "无法安装此应用,因为您的YunoHost版本不支持其打包格式。 您应该考虑升级系统。", - "app_upgraded": "{app:s}upgraded", + "app_upgraded": "{app}upgraded", "app_upgrade_some_app_failed": "某些应用无法升级", "app_upgrade_script_failed": "应用升级脚本内部发生错误", "app_upgrade_app_name": "现在升级{app} ...", @@ -123,53 +123,53 @@ "app_start_install": "{app}安装中...", "app_sources_fetch_failed": "无法获取源文件,URL是否正确?", "app_restore_script_failed": "应用还原脚本内部发生错误", - "app_restore_failed": "无法还原 {app:s}: {error:s}", + "app_restore_failed": "无法还原 {app}: {error}", "app_remove_after_failed_install": "安装失败后删除应用程序...", "app_requirements_unmeet": "{app}不符合要求,软件包{pkgname}({version}) 必须为{spec}", "app_requirements_checking": "正在检查{app}所需的软件包...", - "app_removed": "{app:s} 已删除", - "app_not_properly_removed": "{app:s} 未正确删除", - "app_not_correctly_installed": "{app:s} 似乎安装不正确", + "app_removed": "{app} 已删除", + "app_not_properly_removed": "{app} 未正确删除", + "app_not_correctly_installed": "{app} 似乎安装不正确", "app_not_upgraded": "应用程序'{failed_app}'升级失败,因此以下应用程序的升级已被取消: {apps}", "app_manifest_install_ask_is_public": "该应用是否应该向匿名访问者公开?", "app_manifest_install_ask_admin": "选择此应用的管理员用户", "app_manifest_install_ask_password": "选择此应用的管理密码", - "additional_urls_already_removed": "权限'{permission:s}'的其他URL中已经删除了附加URL'{url:s}'", + "additional_urls_already_removed": "权限'{permission}'的其他URL中已经删除了附加URL'{url}'", "app_manifest_install_ask_path": "选择安装此应用的路径", "app_manifest_install_ask_domain": "选择应安装此应用程序的域", "app_manifest_invalid": "应用清单错误: {error}", - "app_location_unavailable": "该URL不可用,或与已安装的应用冲突:\n{apps:s}", + "app_location_unavailable": "该URL不可用,或与已安装的应用冲突:\n{apps}", "app_label_deprecated": "不推荐使用此命令!请使用新命令 'yunohost user permission update'来管理应用标签。", "app_make_default_location_already_used": "无法将'{app}' 设置为域上的默认应用,'{other_app}'已在使用'{domain}'", "app_install_script_failed": "应用安装脚本内发生错误", "app_install_failed": "无法安装 {app}: {error}", "app_install_files_invalid": "这些文件无法安装", - "additional_urls_already_added": "附加URL '{url:s}' 已添加到权限'{permission:s}'的附加URL中", + "additional_urls_already_added": "附加URL '{url}' 已添加到权限'{permission}'的附加URL中", "app_full_domain_unavailable": "抱歉,此应用必须安装在其自己的域中,但其他应用已安装在域“ {domain}”上。 您可以改用专用于此应用程序的子域。", "app_extraction_failed": "无法解压缩安装文件", - "app_change_url_success": "{app:s} URL现在为 {domain:s}{path:s}", - "app_change_url_no_script": "应用程序'{app_name:s}'尚不支持URL修改. 也许您应该升级它。", - "app_change_url_identical_domains": "新旧domain / url_path是相同的('{domain:s}{path:s}'),无需执行任何操作。", - "app_change_url_failed_nginx_reload": "无法重新加载NGINX. 这是'nginx -t'的输出:\n{nginx_errors:s}", - "app_argument_required": "参数'{name:s}'为必填项", + "app_change_url_success": "{app} URL现在为 {domain}{path}", + "app_change_url_no_script": "应用程序'{app_name}'尚不支持URL修改. 也许您应该升级它。", + "app_change_url_identical_domains": "新旧domain / url_path是相同的('{domain}{path}'),无需执行任何操作。", + "app_change_url_failed_nginx_reload": "无法重新加载NGINX. 这是'nginx -t'的输出:\n{nginx_errors}", + "app_argument_required": "参数'{name}'为必填项", "app_argument_password_no_default": "解析密码参数'{name}'时出错:出于安全原因,密码参数不能具有默认值", - "app_argument_invalid": "为参数'{name:s}'选择一个有效值: {error:s}", - "app_argument_choice_invalid": "对参数'{name:s}'使用以下选项之一'{choices:s}'", - "app_already_up_to_date": "{app:s} 已经是最新的", - "app_already_installed": "{app:s}已安装", + "app_argument_invalid": "为参数'{name}'选择一个有效值: {error}", + "app_argument_choice_invalid": "对参数'{name}'使用以下选项之一'{choices}'", + "app_already_up_to_date": "{app} 已经是最新的", + "app_already_installed": "{app}已安装", "app_action_broke_system": "该操作似乎破坏了以下重要服务:{services}", "app_action_cannot_be_ran_because_required_services_down": "这些必需的服务应该正在运行以执行以下操作:{services},尝试重新启动它们以继续操作(考虑调查为什么它们出现故障)。", "already_up_to_date": "无事可做。一切都已经是最新的了。", "postinstall_low_rootfsspace": "根文件系统的总空间小于10 GB,这非常令人担忧!您可能很快就会用完磁盘空间!建议根文件系统至少有16GB, 如果尽管出现此警告仍要安装YunoHost,请使用--force-diskspace重新运行postinstall", - "port_already_opened": "{ip_version:s}个连接的端口 {port:d} 已打开", - "port_already_closed": "{ip_version:s}个连接的端口 {port:d} 已关闭", + "port_already_opened": "{ip_version}个连接的端口 {port:d} 已打开", + "port_already_closed": "{ip_version}个连接的端口 {port:d} 已关闭", "permission_require_account": "权限{permission}只对有账户的用户有意义,因此不能对访客启用。", "permission_protected": "权限{permission}是受保护的。你不能向/从这个权限添加或删除访问者组。", - "permission_updated": "权限 '{permission:s}' 已更新", + "permission_updated": "权限 '{permission}' 已更新", "permission_update_failed": "无法更新权限 '{permission}': {error}", - "permission_not_found": "找不到权限'{permission:s}'", + "permission_not_found": "找不到权限'{permission}'", "permission_deletion_failed": "无法删除权限 '{permission}': {error}", - "permission_deleted": "权限'{permission:s}' 已删除", + "permission_deleted": "权限'{permission}' 已删除", "permission_cant_add_to_all_users": "权限{permission}不能添加到所有用户。", "regenconf_file_copy_failed": "无法将新的配置文件'{new}' 复制到'{conf}'", "regenconf_file_backed_up": "将配置文件 '{conf}' 备份到 '{backup}'", @@ -186,31 +186,31 @@ "regenconf_need_to_explicitly_specify_ssh": "ssh配置已被手动修改,但是您需要使用--force明确指定类别“ ssh”才能实际应用更改。", "restore_nothings_done": "什么都没有恢复", "restore_may_be_not_enough_disk_space": "您的系统似乎没有足够的空间(可用空间: {free_space:d} B,所需空间: {needed_space:d} B,安全系数: {margin:d} B)", - "restore_hook_unavailable": "'{part:s}'的恢复脚本在您的系统上和归档文件中均不可用", + "restore_hook_unavailable": "'{part}'的恢复脚本在您的系统上和归档文件中均不可用", "restore_failed": "无法还原系统", "restore_extracting": "正在从存档中提取所需文件…", - "restore_confirm_yunohost_installed": "您真的要还原已经安装的系统吗? [{answers:s}]", + "restore_confirm_yunohost_installed": "您真的要还原已经安装的系统吗? [{answers}]", "restore_complete": "恢复完成", "restore_cleaning_failed": "无法清理临时还原目录", "restore_backup_too_old": "无法还原此备份存档,因为它来自过旧的YunoHost版本。", "restore_already_installed_apps": "以下应用已安装,因此无法还原: {apps}", - "restore_already_installed_app": "已安装ID为'{app:s}' 的应用", + "restore_already_installed_app": "已安装ID为'{app}' 的应用", "regex_with_only_domain": "您不能将正则表达式用于域,而只能用于路径", "regex_incompatible_with_tile": "/!\\ 打包者!权限“ {permission}”的show_tile设置为“ true”,因此您不能将正则表达式URL定义为主URL", - "service_cmd_exec_failed": "无法执行命令'{command:s}'", - "service_already_stopped": "服务'{service:s}'已被停止", - "service_already_started": "服务'{service:s}' 已在运行", - "service_added": "服务 '{service:s}'已添加", - "service_add_failed": "无法添加服务 '{service:s}'", - "server_reboot_confirm": "服务器会立即重启,确定吗? [{answers:s}]", + "service_cmd_exec_failed": "无法执行命令'{command}'", + "service_already_stopped": "服务'{service}'已被停止", + "service_already_started": "服务'{service}' 已在运行", + "service_added": "服务 '{service}'已添加", + "service_add_failed": "无法添加服务 '{service}'", + "server_reboot_confirm": "服务器会立即重启,确定吗? [{answers}]", "server_reboot": "服务器将重新启动", - "server_shutdown_confirm": "服务器会立即关闭,确定吗?[{answers:s}]", + "server_shutdown_confirm": "服务器会立即关闭,确定吗?[{answers}]", "server_shutdown": "服务器将关闭", "root_password_replaced_by_admin_password": "您的root密码已替换为您的管理员密码。", "root_password_desynchronized": "管理员密码已更改,但是YunoHost无法将此密码传播到root密码!", - "restore_system_part_failed": "无法还原 '{part:s}'系统部分", + "restore_system_part_failed": "无法还原 '{part}'系统部分", "restore_running_hooks": "运行修复挂钩…", - "restore_running_app_script": "正在还原应用'{app:s}'…", + "restore_running_app_script": "正在还原应用'{app}'…", "restore_removing_tmp_dir_failed": "无法删除旧的临时目录", "service_description_yunohost-firewall": "管理打开和关闭服务的连接端口", "service_description_yunohost-api": "管理YunoHost Web界面与系统之间的交互", @@ -227,21 +227,21 @@ "service_description_dovecot": "允许电子邮件客户端访问/获取电子邮件(通过IMAP和POP3)", "service_description_dnsmasq": "处理域名解析(DNS)", "service_description_avahi-daemon": "允许您使用本地网络中的“ yunohost.local”访问服务器", - "service_started": "服务 '{service:s}' 已启动", - "service_start_failed": "无法启动服务 '{service:s}'\n\n最近的服务日志:{logs:s}", - "service_reloaded_or_restarted": "服务'{service:s}'已重新加载或重新启动", - "service_reload_or_restart_failed": "无法重新加载或重新启动服务'{service:s}'\n\n最近的服务日志:{logs:s}", - "service_restarted": "服务'{service:s}' 已重新启动", - "service_restart_failed": "无法重新启动服务 '{service:s}'\n\n最近的服务日志:{logs:s}", - "service_reloaded": "服务 '{service:s}' 已重新加载", - "service_reload_failed": "无法重新加载服务'{service:s}'\n\n最近的服务日志:{logs:s}", - "service_removed": "服务 '{service:s}' 已删除", - "service_remove_failed": "无法删除服务'{service:s}'", + "service_started": "服务 '{service}' 已启动", + "service_start_failed": "无法启动服务 '{service}'\n\n最近的服务日志:{logs}", + "service_reloaded_or_restarted": "服务'{service}'已重新加载或重新启动", + "service_reload_or_restart_failed": "无法重新加载或重新启动服务'{service}'\n\n最近的服务日志:{logs}", + "service_restarted": "服务'{service}' 已重新启动", + "service_restart_failed": "无法重新启动服务 '{service}'\n\n最近的服务日志:{logs}", + "service_reloaded": "服务 '{service}' 已重新加载", + "service_reload_failed": "无法重新加载服务'{service}'\n\n最近的服务日志:{logs}", + "service_removed": "服务 '{service}' 已删除", + "service_remove_failed": "无法删除服务'{service}'", "service_regen_conf_is_deprecated": "不建议使用'yunohost service regen-conf' ! 请改用'yunohost tools regen-conf'。", - "service_enabled": "现在,服务'{service:s}' 将在系统引导过程中自动启动。", - "service_enable_failed": "无法使服务 '{service:s}'在启动时自动启动。\n\n最近的服务日志:{logs:s}", - "service_disabled": "系统启动时,服务 '{service:s}' 将不再启动。", - "service_disable_failed": "服务'{service:s}'在启动时无法启动。\n\n最近的服务日志:{logs:s}", + "service_enabled": "现在,服务'{service}' 将在系统引导过程中自动启动。", + "service_enable_failed": "无法使服务 '{service}'在启动时自动启动。\n\n最近的服务日志:{logs}", + "service_disabled": "系统启动时,服务 '{service}' 将不再启动。", + "service_disable_failed": "服务'{service}'在启动时无法启动。\n\n最近的服务日志:{logs}", "tools_upgrade_regular_packages": "现在正在升级 'regular' (与yunohost无关)的软件包…", "tools_upgrade_cant_unhold_critical_packages": "无法解压关键软件包…", "tools_upgrade_cant_hold_critical_packages": "无法保存重要软件包…", @@ -254,20 +254,20 @@ "ssowat_conf_generated": "SSOwat配置已重新生成", "show_tile_cant_be_enabled_for_regex": "你不能启用'show_tile',因为权限'{permission}'的URL是一个重合词", "show_tile_cant_be_enabled_for_url_not_defined": "您现在无法启用 'show_tile' ,因为您必须先为权限'{permission}'定义一个URL", - "service_unknown": "未知服务 '{service:s}'", - "service_stopped": "服务'{service:s}' 已停止", - "service_stop_failed": "无法停止服务'{service:s}'\n\n最近的服务日志:{logs:s}", + "service_unknown": "未知服务 '{service}'", + "service_stopped": "服务'{service}' 已停止", + "service_stop_failed": "无法停止服务'{service}'\n\n最近的服务日志:{logs}", "upnp_dev_not_found": "找不到UPnP设备", "upgrading_packages": "升级程序包...", "upgrade_complete": "升级完成", "updating_apt_cache": "正在获取系统软件包的可用升级...", "update_apt_cache_warning": "更新APT缓存(Debian的软件包管理器)时出了点问题。这是sources.list行的转储,这可能有助于确定有问题的行:\n{sourceslist}", "update_apt_cache_failed": "无法更新APT的缓存(Debian的软件包管理器)。这是sources.list行的转储,这可能有助于确定有问题的行:\n{sourceslist}", - "unrestore_app": "{app:s} 将不会恢复", + "unrestore_app": "{app} 将不会恢复", "unlimit": "没有配额", "unknown_main_domain_path": "'{app}'的域或路径未知。您需要指定一个域和一个路径,以便能够指定用于许可的URL。", "unexpected_error": "出乎意料的错误: {error}", - "unbackup_app": "{app:s} 将不会保存", + "unbackup_app": "{app} 将不会保存", "tools_upgrade_special_packages_completed": "YunoHost软件包升级完成。\n按[Enter]返回命令行", "tools_upgrade_special_packages_explanation": "特殊升级将在后台继续。请在接下来的10分钟内(取决于硬件速度)在服务器上不要执行任何其他操作。此后,您可能必须重新登录Webadmin。升级日志将在“工具”→“日志”(在Webadmin中)或使用'yunohost log list'(从命令行)中可用。", "tools_upgrade_special_packages": "现在正在升级'special'(与yunohost相关的)程序包…", @@ -277,7 +277,7 @@ "yunohost_already_installed": "YunoHost已经安装", "user_updated": "用户信息已更改", "user_update_failed": "无法更新用户{user}: {error}", - "user_unknown": "未知用户: {user:s}", + "user_unknown": "未知用户: {user}", "user_home_creation_failed": "无法为用户创建'home'文件夹", "user_deletion_failed": "无法删除用户 {user}: {error}", "user_deleted": "用户已删除", @@ -290,18 +290,18 @@ "yunohost_not_installed": "YunoHost没有正确安装,请运行 'yunohost tools postinstall'", "yunohost_postinstall_end_tip": "后期安装完成! 为了最终完成你的设置,请考虑:\n -通过webadmin的“用户”部分添加第一个用户(或在命令行中'yunohost user create ' );\n -通过网络管理员的“诊断”部分(或命令行中的'yunohost diagnosis run')诊断潜在问题;\n -阅读管理文档中的“完成安装设置”和“了解YunoHost”部分: https://yunohost.org/admindoc.", "operation_interrupted": "该操作是否被手动中断?", - "invalid_regex": "无效的正则表达式:'{regex:s}'", + "invalid_regex": "无效的正则表达式:'{regex}'", "installation_complete": "安装完成", - "hook_name_unknown": "未知的钩子名称 '{name:s}'", + "hook_name_unknown": "未知的钩子名称 '{name}'", "hook_list_by_invalid": "此属性不能用于列出钩子", - "hook_json_return_error": "无法读取来自钩子 {path:s}的返回,错误: {msg:s}。原始内容: {raw_content}", - "hook_exec_not_terminated": "脚本未正确完成: {path:s}", - "hook_exec_failed": "无法运行脚本: {path:s}", + "hook_json_return_error": "无法读取来自钩子 {path}的返回,错误: {msg}。原始内容: {raw_content}", + "hook_exec_not_terminated": "脚本未正确完成: {path}", + "hook_exec_failed": "无法运行脚本: {path}", "group_user_not_in_group": "用户{user}不在组{group}中", "group_user_already_in_group": "用户{user}已在组{group}中", "group_update_failed": "无法更新群组'{group}': {error}", "group_updated": "群组 '{group}' 已更新", - "group_unknown": "群组 '{group:s}' 未知", + "group_unknown": "群组 '{group}' 未知", "group_deletion_failed": "无法删除群组'{group}': {error}", "group_deleted": "群组'{group}' 已删除", "group_cannot_be_deleted": "无法手动删除组{group}。", @@ -314,7 +314,7 @@ "group_already_exist_on_system": "系统组中已经存在组{group}", "group_already_exist": "群组{group}已经存在", "good_practices_about_admin_password": "现在,您将设置一个新的管理员密码。 密码至少应包含8个字符。并且出于安全考虑建议使用较长的密码同时尽可能使用各种字符(大写,小写,数字和特殊字符)。", - "global_settings_unknown_type": "意外的情况,设置{setting:s}似乎具有类型 {unknown_type:s} ,但是系统不支持该类型。", + "global_settings_unknown_type": "意外的情况,设置{setting}似乎具有类型 {unknown_type} ,但是系统不支持该类型。", "global_settings_setting_backup_compress_tar_archives": "创建新备份时,请压缩档案(.tar.gz) ,而不要压缩未压缩的档案(.tar)。注意:启用此选项意味着创建较小的备份存档,但是初始备份过程将明显更长且占用大量CPU。", "global_settings_setting_smtp_relay_password": "SMTP中继主机密码", "global_settings_setting_smtp_relay_user": "SMTP中继用户帐户", @@ -322,7 +322,7 @@ "global_settings_setting_smtp_allow_ipv6": "允许使用IPv6接收和发送邮件", "global_settings_setting_ssowat_panel_overlay_enabled": "启用SSOwat面板覆盖", "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "允许使用DSA主机密钥进行SSH守护程序配置(不建议使用)", - "global_settings_unknown_setting_from_settings_file": "设置中的未知密钥:'{setting_key:s}',将其丢弃并保存在/etc/yunohost/settings-unknown.json中", + "global_settings_unknown_setting_from_settings_file": "设置中的未知密钥:'{setting_key}',将其丢弃并保存在/etc/yunohost/settings-unknown.json中", "global_settings_setting_security_ssh_port": "SSH端口", "global_settings_setting_security_postfix_compatibility": "Postfix服务器的兼容性与安全性的权衡。影响密码(以及其他与安全性有关的方面)", "global_settings_setting_security_ssh_compatibility": "SSH服务器的兼容性与安全性的权衡。影响密码(以及其他与安全性有关的方面)", @@ -330,23 +330,23 @@ "global_settings_setting_security_password_admin_strength": "管理员密码强度", "global_settings_setting_security_nginx_compatibility": "Web服务器NGINX的兼容性与安全性的权衡,影响密码(以及其他与安全性有关的方面)", "global_settings_setting_pop3_enabled": "为邮件服务器启用POP3协议", - "global_settings_reset_success": "以前的设置现在已经备份到{path:s}", - "global_settings_key_doesnt_exists": "全局设置中不存在键'{settings_key:s}',您可以通过运行 'yunohost settings list'来查看所有可用键", - "global_settings_cant_write_settings": "无法保存设置文件,原因: {reason:s}", - "global_settings_cant_serialize_settings": "无法序列化设置数据,原因: {reason:s}", - "global_settings_cant_open_settings": "无法打开设置文件,原因: {reason:s}", - "global_settings_bad_type_for_setting": "设置 {setting:s},的类型错误,已收到{received_type:s},预期{expected_type:s}", - "global_settings_bad_choice_for_enum": "设置 {setting:s}的错误选择,收到了 '{choice:s}',但可用的选择有: {available_choices:s}", + "global_settings_reset_success": "以前的设置现在已经备份到{path}", + "global_settings_key_doesnt_exists": "全局设置中不存在键'{settings_key}',您可以通过运行 'yunohost settings list'来查看所有可用键", + "global_settings_cant_write_settings": "无法保存设置文件,原因: {reason}", + "global_settings_cant_serialize_settings": "无法序列化设置数据,原因: {reason}", + "global_settings_cant_open_settings": "无法打开设置文件,原因: {reason}", + "global_settings_bad_type_for_setting": "设置 {setting},的类型错误,已收到{received_type},预期{expected_type}", + "global_settings_bad_choice_for_enum": "设置 {setting}的错误选择,收到了 '{choice}',但可用的选择有: {available_choices}", "firewall_rules_cmd_failed": "某些防火墙规则命令失败。日志中的更多信息。", "firewall_reloaded": "重新加载防火墙", "firewall_reload_failed": "无法重新加载防火墙", - "file_does_not_exist": "文件{path:s} 不存在。", - "field_invalid": "无效的字段'{:s}'", + "file_does_not_exist": "文件{path} 不存在。", + "field_invalid": "无效的字段'{}'", "experimental_feature": "警告:此功能是实验性的,不稳定,请不要使用它,除非您知道自己在做什么。", "extracting": "提取中...", - "dyndns_unavailable": "域'{domain:s}' 不可用。", - "dyndns_domain_not_provided": "DynDNS提供者 {provider:s} 无法提供域 {domain:s}。", - "dyndns_registration_failed": "无法注册DynDNS域: {error:s}", + "dyndns_unavailable": "域'{domain}' 不可用。", + "dyndns_domain_not_provided": "DynDNS提供者 {provider} 无法提供域 {domain}。", + "dyndns_registration_failed": "无法注册DynDNS域: {error}", "dyndns_registered": "DynDNS域已注册", "dyndns_provider_unreachable": "无法联系DynDNS提供者 {provider}: 您的YunoHost未正确连接到Internet或dynette服务器已关闭。", "dyndns_no_domain_registered": "没有在DynDNS中注册的域", @@ -354,8 +354,8 @@ "dyndns_key_generating": "正在生成DNS密钥...可能需要一段时间。", "dyndns_ip_updated": "在DynDNS上更新了您的IP", "dyndns_ip_update_failed": "无法将IP地址更新到DynDNS", - "dyndns_could_not_check_available": "无法检查{provider:s}上是否可用 {domain:s}。", - "dyndns_could_not_check_provide": "无法检查{provider:s}是否可以提供 {domain:s}.", + "dyndns_could_not_check_available": "无法检查{provider}上是否可用 {domain}。", + "dyndns_could_not_check_provide": "无法检查{provider}是否可以提供 {domain}.", "dpkg_lock_not_available": "该命令现在无法运行,因为另一个程序似乎正在使用dpkg锁(系统软件包管理器)", "dpkg_is_broken": "您现在不能执行此操作,因为dpkg / APT(系统软件包管理器)似乎处于损坏状态……您可以尝试通过SSH连接并运行sudo apt install --fix-broken和/或 sudo dpkg --configure-a 来解决此问题.", "downloading": "下载中…", @@ -466,7 +466,7 @@ "diagnosis_no_cache": "尚无类别 '{category}'的诊断缓存", "diagnosis_failed": "无法获取类别 '{category}'的诊断结果: {error}", "diagnosis_package_installed_from_sury_details": "一些软件包被无意中从一个名为Sury的第三方仓库安装。YunoHost团队改进了处理这些软件包的策略,但预计一些安装了PHP7.3应用程序的设置在仍然使用Stretch的情况下还有一些不一致的地方。为了解决这种情况,你应该尝试运行以下命令:{cmd_to_fix}", - "app_not_installed": "在已安装的应用列表中找不到 {app:s}:{all_apps}", + "app_not_installed": "在已安装的应用列表中找不到 {app}:{all_apps}", "app_already_installed_cant_change_url": "这个应用程序已经被安装。URL不能仅仅通过这个函数来改变。在`app changeurl`中检查是否可用。", "restore_not_enough_disk_space": "没有足够的空间(空间: {free_space:d} B,需要的空间: {needed_space:d} B,安全系数: {margin:d} B)", "regenconf_pending_applying": "正在为类别'{category}'应用挂起的配置..", @@ -474,9 +474,9 @@ "regenconf_file_kept_back": "配置文件'{conf}'预计将被regen-conf(类别{category})删除,但被保留了下来。", "good_practices_about_user_password": "现在,您将设置一个新的管理员密码。 密码至少应包含8个字符。并且出于安全考虑建议使用较长的密码同时尽可能使用各种字符(大写,小写,数字和特殊字符)", "global_settings_setting_smtp_relay_host": "使用SMTP中继主机来代替这个YunoHost实例发送邮件。如果你有以下情况,就很有用:你的25端口被你的ISP或VPS提供商封锁,你有一个住宅IP列在DUHL上,你不能配置反向DNS,或者这个服务器没有直接暴露在互联网上,你想使用其他服务器来发送邮件。", - "domain_cannot_remove_main_add_new_one": "你不能删除'{domain:s}',因为它是主域和你唯一的域,你需要先用'yunohost domain add '添加另一个域,然后用'yunohost domain main-domain -n '设置为主域,然后你可以用'yunohost domain remove {domain:s}'删除域", + "domain_cannot_remove_main_add_new_one": "你不能删除'{domain}',因为它是主域和你唯一的域,你需要先用'yunohost domain add '添加另一个域,然后用'yunohost domain main-domain -n '设置为主域,然后你可以用'yunohost domain remove {domain}'删除域", "domain_cannot_add_xmpp_upload": "你不能添加以'xmpp-upload.'开头的域名。这种名称是为YunoHost中集成的XMPP上传功能保留的。", - "domain_cannot_remove_main": "你不能删除'{domain:s}',因为它是主域,你首先需要用'yunohost domain main-domain -n '设置另一个域作为主域;这里是候选域的列表: {other_domains:s}", + "domain_cannot_remove_main": "你不能删除'{domain}',因为它是主域,你首先需要用'yunohost domain main-domain -n '设置另一个域作为主域;这里是候选域的列表: {other_domains}", "diagnosis_sshd_config_inconsistent_details": "请运行yunohost settings set security.ssh.port -v YOUR_SSH_PORT来定义SSH端口,并检查yunohost tools regen-conf ssh --dry-run --with-diffyunohost tools regen-conf ssh --force将您的配置重置为YunoHost建议。", "diagnosis_http_bad_status_code": "它看起来像另一台机器(也许是你的互联网路由器)回答,而不是你的服务器。
1。这个问题最常见的原因是80端口(和443端口)没有正确转发到您的服务器
2.在更复杂的设置中:确保没有防火墙或反向代理的干扰。", "diagnosis_http_timeout": "当试图从外部联系你的服务器时,出现了超时。它似乎是不可达的。
1. 这个问题最常见的原因是80端口(和443端口)没有正确转发到你的服务器
2.你还应该确保nginx服务正在运行
3.对于更复杂的设置:确保没有防火墙或反向代理的干扰。", @@ -534,7 +534,7 @@ "log_backup_restore_system": "从备份档案还原系统", "permission_currently_allowed_for_all_users": "这个权限目前除了授予其他组以外,还授予所有用户。你可能想删除'all_users'权限或删除目前授予它的其他组。", "permission_creation_failed": "无法创建权限'{permission}': {error}", - "permission_created": "权限'{permission:s}'已创建", + "permission_created": "权限'{permission}'已创建", "permission_cannot_remove_main": "不允许删除主要权限", "permission_already_up_to_date": "权限没有被更新,因为添加/删除请求已经符合当前状态。", "permission_already_exist": "权限 '{permission}'已存在", @@ -558,7 +558,7 @@ "password_listed": "该密码是世界上最常用的密码之一。 请选择一些更独特的东西。", "packages_upgrade_failed": "无法升级所有软件包", "invalid_number": "必须是数字", - "not_enough_disk_space": "'{path:s}'上的可用空间不足", + "not_enough_disk_space": "'{path}'上的可用空间不足", "migrations_to_be_ran_manually": "迁移{id}必须手动运行。请转到webadmin页面上的工具→迁移,或运行`yunohost tools migrations run`。", "migrations_success_forward": "迁移 {id} 已完成", "migrations_skip_migration": "正在跳过迁移{id}...", @@ -601,7 +601,7 @@ "migration_update_LDAP_schema": "正在更新LDAP模式...", "migration_ldap_rollback_success": "系统回滚。", "migration_ldap_migration_failed_trying_to_rollback": "无法迁移...试图回滚系统。", - "migration_ldap_can_not_backup_before_migration": "迁移失败之前,无法完成系统的备份。错误: {error:s}", + "migration_ldap_can_not_backup_before_migration": "迁移失败之前,无法完成系统的备份。错误: {error}", "migration_ldap_backup_before_migration": "在实际迁移之前,请创建LDAP数据库和应用程序设置的备份。", "migration_description_0020_ssh_sftp_permissions": "添加SSH和SFTP权限支持", "migration_description_0019_extend_permissions_features": "扩展/修改应用程序的权限管理系统", @@ -614,10 +614,10 @@ "main_domain_change_failed": "无法更改主域", "mail_unavailable": "该电子邮件地址是保留的,并且将自动分配给第一个用户", "mailbox_used_space_dovecot_down": "如果要获取使用过的邮箱空间,则必须启动Dovecot邮箱服务", - "mailbox_disabled": "用户{user:s}的电子邮件已关闭", - "mail_forward_remove_failed": "无法删除电子邮件转发'{mail:s}'", - "mail_domain_unknown": "域'{domain:s}'的电子邮件地址无效。请使用本服务器管理的域。", - "mail_alias_remove_failed": "无法删除电子邮件别名'{mail:s}'", + "mailbox_disabled": "用户{user}的电子邮件已关闭", + "mail_forward_remove_failed": "无法删除电子邮件转发'{mail}'", + "mail_domain_unknown": "域'{domain}'的电子邮件地址无效。请使用本服务器管理的域。", + "mail_alias_remove_failed": "无法删除电子邮件别名'{mail}'", "log_tools_reboot": "重新启动服务器", "log_tools_shutdown": "关闭服务器", "log_tools_upgrade": "升级系统软件包", From ee70dfe52ea362e3ca0564643c6878fe0b4d70dd Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 8 Aug 2021 17:05:40 +0200 Subject: [PATCH 2670/3170] [fix] firewall.py : upnpc.getspecificportmapping expects an int, can't handle port ranges ? --- src/yunohost/firewall.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/yunohost/firewall.py b/src/yunohost/firewall.py index b800cd42c..d967acd9c 100644 --- a/src/yunohost/firewall.py +++ b/src/yunohost/firewall.py @@ -399,6 +399,12 @@ def firewall_upnp(action="status", no_refresh=False): for protocol in ["TCP", "UDP"]: if protocol + "_TO_CLOSE" in firewall["uPnP"]: for port in firewall["uPnP"][protocol + "_TO_CLOSE"]: + + if not isinstance(port, int): + # FIXME : how should we handle port ranges ? + logger.warning("Can't use UPnP to close '%s'" % port) + continue + # Clean the mapping of this port if upnpc.getspecificportmapping(port, protocol): try: @@ -408,6 +414,12 @@ def firewall_upnp(action="status", no_refresh=False): firewall["uPnP"][protocol + "_TO_CLOSE"] = [] for port in firewall["uPnP"][protocol]: + + if not isinstance(port, int): + # FIXME : how should we handle port ranges ? + logger.warning("Can't use UPnP to open '%s'" % port) + continue + # Clean the mapping of this port if upnpc.getspecificportmapping(port, protocol): try: From 1bab0bb41274811fc37df4dc83a4cf4302fdf8c1 Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Sun, 8 Aug 2021 15:15:27 +0000 Subject: [PATCH 2671/3170] [CI] Format code --- src/yunohost/firewall.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/yunohost/firewall.py b/src/yunohost/firewall.py index d967acd9c..9850defa5 100644 --- a/src/yunohost/firewall.py +++ b/src/yunohost/firewall.py @@ -399,12 +399,12 @@ def firewall_upnp(action="status", no_refresh=False): for protocol in ["TCP", "UDP"]: if protocol + "_TO_CLOSE" in firewall["uPnP"]: for port in firewall["uPnP"][protocol + "_TO_CLOSE"]: - + if not isinstance(port, int): # FIXME : how should we handle port ranges ? logger.warning("Can't use UPnP to close '%s'" % port) continue - + # Clean the mapping of this port if upnpc.getspecificportmapping(port, protocol): try: @@ -414,12 +414,12 @@ def firewall_upnp(action="status", no_refresh=False): firewall["uPnP"][protocol + "_TO_CLOSE"] = [] for port in firewall["uPnP"][protocol]: - + if not isinstance(port, int): # FIXME : how should we handle port ranges ? logger.warning("Can't use UPnP to open '%s'" % port) continue - + # Clean the mapping of this port if upnpc.getspecificportmapping(port, protocol): try: From dd6fea7038fc03c98eca914fc34b62b09247c41e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= Date: Wed, 4 Aug 2021 18:30:07 +0000 Subject: [PATCH 2672/3170] Translated using Weblate (French) Currently translated at 100.0% (633 of 633 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 8d724dde7..43ced098f 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -624,7 +624,7 @@ "migration_ldap_rollback_success": "Système rétabli dans son état initial.", "permission_cant_add_to_all_users": "L'autorisation {permission} ne peut pas être ajoutée à tous les utilisateurs.", "migration_ldap_migration_failed_trying_to_rollback": "Impossible de migrer... tentative de restauration du système.", - "migration_ldap_can_not_backup_before_migration": "La sauvegarde du système n'a pas pu être terminée avant l'échec de la migration. Erreur : {error : s}", + "migration_ldap_can_not_backup_before_migration": "La sauvegarde du système n'a pas pu être terminée avant l'échec de la migration. Erreur : {error }", "migration_ldap_backup_before_migration": "Création d'une sauvegarde de la base de données LDAP et des paramètres des applications avant la migration proprement dite.", "migration_description_0020_ssh_sftp_permissions": "Ajouter la prise en charge des autorisations SSH et SFTP", "global_settings_setting_security_ssh_port": "Port SSH", From 14e787a228bf234ccdb3f8ae250d353c00f53eff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M?= Date: Thu, 5 Aug 2021 05:06:48 +0000 Subject: [PATCH 2673/3170] Translated using Weblate (Galician) Currently translated at 52.6% (333 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/gl/ --- locales/gl.json | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/locales/gl.json b/locales/gl.json index fbf1a302a..489202ee9 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -312,5 +312,25 @@ "domain_cannot_remove_main_add_new_one": "Non podes eliminar '{domain}' porque é o dominio principal e único dominio, primeiro tes que engadir outro dominio usando 'yunohost domain add ', e despois establecelo como o dominio principal utilizando 'yunohost domain main-domain -n ' e entón poderás eliminar '{domain}' con 'yunohost domain remove {domain}'.'", "domain_cannot_add_xmpp_upload": "Non podes engadir dominios que comecen con 'xmpp-upload.'. Este tipo de nome está reservado para a función se subida de XMPP integrada en YunoHost.", "file_does_not_exist": "O ficheiro {path} non existe.", - "firewall_reload_failed": "Non se puido recargar o cortalumes" + "firewall_reload_failed": "Non se puido recargar o cortalumes", + "global_settings_setting_smtp_allow_ipv6": "Permitir o uso de IPv6 para recibir e enviar emais", + "global_settings_setting_ssowat_panel_overlay_enabled": "Activar as capas no panel SSOwat", + "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Permitir o uso de DSA hostkey (en desuso) para a configuración do demoño SSH", + "global_settings_unknown_setting_from_settings_file": "Chave descoñecida nos axustes: '{setting_key}', descártaa e gárdaa en /etc/yunohost/settings-unknown.json", + "global_settings_setting_security_ssh_port": "Porto SSH", + "global_settings_setting_security_postfix_compatibility": "Compromiso entre compatibilidade e seguridade para o servidor Postfix. Aféctalle ao cifrado (e outros aspectos da seguridade)", + "global_settings_setting_security_ssh_compatibility": "Compromiso entre compatibilidade e seguridade para o servidor SSH. Aféctalle ao cifrado (e outros aspectos da seguridade)", + "global_settings_setting_security_password_user_strength": "Fortaleza do contrasinal da usuaria", + "global_settings_setting_security_password_admin_strength": "Fortaleza do contrasinal de Admin", + "global_settings_setting_security_nginx_compatibility": "Compromiso entre compatiblidade e seguridade para o servidor NGINX. Afecta ao cifrado (e outros aspectos relacionados coa seguridade)", + "global_settings_setting_pop3_enabled": "Activar protocolo POP3 no servidor de email", + "global_settings_reset_success": "Fíxose copia de apoio dos axustes en {path}", + "global_settings_key_doesnt_exists": "O axuste '{settings_key}' non existe nos axustes globais, podes ver os valores dispoñibles executando 'yunohost settings list'", + "global_settings_cant_write_settings": "Non se gardou o ficheiro de configuración, razón: {reason}", + "global_settings_cant_serialize_settings": "Non se serializaron os datos da configuración, razón: {reason}", + "global_settings_cant_open_settings": "Non se puido abrir o ficheiro de axustes, razón: {reason}", + "global_settings_bad_type_for_setting": "Tipo incorrecto do axuste {setting}, recibido {received_type}, agardábase {expected_type}", + "global_settings_bad_choice_for_enum": "Elección incorrecta para o axuste {setting}, recibido '{choice}', mais as opcións dispoñibles son: {available_choices}", + "firewall_rules_cmd_failed": "Fallou algún comando das regras do cortalumes. Máis info no rexistro.", + "firewall_reloaded": "Recargouse o cortalumes" } From 46b2583b96dd7dd048fddfec346142005019a970 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= Date: Fri, 6 Aug 2021 07:55:01 +0000 Subject: [PATCH 2674/3170] Translated using Weblate (French) Currently translated at 100.0% (633 of 633 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 43ced098f..162e0e270 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -354,7 +354,7 @@ "tools_upgrade_special_packages_explanation": "La mise à niveau spécifique à YunoHost se poursuivra en arrière-plan. Veuillez ne pas lancer d'autres actions sur votre serveur pendant les 10 prochaines minutes (selon la vitesse du matériel). Après cela, vous devrez peut-être vous reconnecter à l'administrateur Web. Le journal de mise à niveau sera disponible dans Outils → Journal (dans le webadmin) ou en utilisant la « liste des journaux yunohost » (à partir de la ligne de commande).", "update_apt_cache_failed": "Impossible de mettre à jour le cache APT (gestionnaire de paquets Debian). Voici un extrait du fichier sources.list qui pourrait vous aider à identifier les lignes problématiques :\n{sourceslist}", "update_apt_cache_warning": "Des erreurs se sont produites lors de la mise à jour du cache APT (gestionnaire de paquets Debian). Voici un extrait des lignes du fichier sources.list qui pourrait vous aider à identifier les lignes problématiques :\n{sourceslist}", - "backup_permission": "Permission de sauvegarde pour {app}", + "backup_permission": "Permission de sauvegarde pour {app}", "group_created": "Le groupe '{group}' a été créé", "group_deleted": "Suppression du groupe '{group}'", "group_unknown": "Le groupe {group} est inconnu", From a59a0037ee4ade68922345e863f6714e96e82577 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M?= Date: Fri, 6 Aug 2021 03:55:11 +0000 Subject: [PATCH 2675/3170] Translated using Weblate (Galician) Currently translated at 54.6% (346 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/gl/ --- locales/gl.json | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/locales/gl.json b/locales/gl.json index 489202ee9..35c56b7f8 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -332,5 +332,18 @@ "global_settings_bad_type_for_setting": "Tipo incorrecto do axuste {setting}, recibido {received_type}, agardábase {expected_type}", "global_settings_bad_choice_for_enum": "Elección incorrecta para o axuste {setting}, recibido '{choice}', mais as opcións dispoñibles son: {available_choices}", "firewall_rules_cmd_failed": "Fallou algún comando das regras do cortalumes. Máis info no rexistro.", - "firewall_reloaded": "Recargouse o cortalumes" + "firewall_reloaded": "Recargouse o cortalumes", + "group_creation_failed": "Non se puido crear o grupo '{group}': {error}", + "group_created": "Creouse o grupo '{group}'", + "group_already_exist_on_system_but_removing_it": "O grupo {group} xa é un dos grupos do sistema, pero YunoHost vaino eliminar...", + "group_already_exist_on_system": "O grupo {group} xa é un dos grupos do sistema", + "group_already_exist": "Xa existe o grupo {group}", + "good_practices_about_user_password": "Vas definir o novo contrasinal de usuaria. O contrasinal debe ter 8 caracteres como mínimo—aínda que se recomenda utilizar un máis longo (ex. unha frase de paso) e/ou utilizar caracteres variados (maiúsculas, minúsculas, números e caracteres especiais).", + "good_practices_about_admin_password": "Vas definir o novo contrasinal de administración. O contrasinal debe ter 8 caracteres como mínimo—aínda que se recomenda utilizar un máis longo (ex. unha frase de paso) e/ou utilizar caracteres variados (maiúsculas, minúsculas, números e caracteres especiais).", + "global_settings_unknown_type": "Situación non agardada, o axuste {setting} semella ter o tipo {unknown_type} pero non é un valor soportado polo sistema.", + "global_settings_setting_backup_compress_tar_archives": "Ao crear novas copias de apoio, comprime os arquivos (.tar.gz) en lugar de non facelo (.tar). Nota: activando esta opción creas arquivos máis lixeiros, mais o procedemento da primeira copia será significativamente máis longo e esixente coa CPU.", + "global_settings_setting_smtp_relay_password": "Contrasinal no repetidor SMTP", + "global_settings_setting_smtp_relay_user": "Conta de usuaria no repetidor SMTP", + "global_settings_setting_smtp_relay_port": "Porto do repetidor SMTP", + "global_settings_setting_smtp_relay_host": "Servidor repetidor SMTP para enviar emails no lugar da túa instancia yunohost. É útil se estás nunha destas situacións: o teu porto 25 está bloqueado polo teu provedor ISP u VPN, se tes unha IP residencial nunha lista DUHL, se non podes configurar DNS inversa ou se este servidor non ten conexión directa a internet e queres utilizar outro para enviar os emails." } From eedb202e4c2456a03c43975b451757887ae91c40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= Date: Sat, 7 Aug 2021 17:35:25 +0000 Subject: [PATCH 2676/3170] Translated using Weblate (French) Currently translated at 100.0% (633 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 162e0e270..100c84178 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -215,10 +215,10 @@ "backup_couldnt_bind": "Impossible de lier {src} avec {dest}.", "domain_dns_conf_is_just_a_recommendation": "Cette commande vous montre la configuration *recommandée*. Elle ne configure pas le DNS pour vous. Il est de votre ressort de configurer votre zone DNS chez votre registrar/fournisseur conformément à cette recommandation.", "migrations_cant_reach_migration_file": "Impossible d’accéder aux fichiers de migration via le chemin '%s'", - "migrations_loading_migration": "Chargement de la migration {id} ...", + "migrations_loading_migration": "Chargement de la migration {id}...", "migrations_migration_has_failed": "La migration {id} a échoué avec l’exception {exception} : annulation", "migrations_no_migrations_to_run": "Aucune migration à lancer", - "migrations_skip_migration": "Ignorer et passer la migration {id} ...", + "migrations_skip_migration": "Ignorer et passer la migration {id}...", "server_shutdown": "Le serveur va s’éteindre", "server_shutdown_confirm": "Le serveur va être éteint immédiatement, le voulez-vous vraiment ? [{answers}]", "server_reboot": "Le serveur va redémarrer", @@ -340,7 +340,7 @@ "regenconf_would_be_updated": "La configuration aurait dû être mise à jour pour la catégorie '{category}'", "regenconf_dry_pending_applying": "Vérification de la configuration en attente qui aurait été appliquée pour la catégorie '{category}'…", "regenconf_failed": "Impossible de régénérer la configuration pour la ou les catégorie(s) : '{categories}'", - "regenconf_pending_applying": "Applique la configuration en attente pour la catégorie '{category}' ...", + "regenconf_pending_applying": "Applique la configuration en attente pour la catégorie '{category}'...", "service_regen_conf_is_deprecated": "'yunohost service regen-conf' est obsolète ! Veuillez plutôt utiliser 'yunohost tools regen-conf' à la place.", "tools_upgrade_at_least_one": "Veuillez spécifier '--apps' ou '--system'", "tools_upgrade_cant_both": "Impossible de mettre à niveau le système et les applications en même temps", @@ -379,7 +379,7 @@ "migrations_already_ran": "Ces migrations sont déjà effectuées : {ids}", "migrations_dependencies_not_satisfied": "Exécutez ces migrations : '{dependencies_id}', avant migration {id}.", "migrations_failed_to_load_migration": "Impossible de charger la migration {id} : {error}", - "migrations_running_forward": "Exécution de la migration {id} ...", + "migrations_running_forward": "Exécution de la migration {id}...", "migrations_success_forward": "Migration {id} terminée", "operation_interrupted": "L'opération a-t-elle été interrompue manuellement ?", "permission_already_exist": "L’autorisation '{permission}' existe déjà", @@ -552,18 +552,18 @@ "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.", "restore_already_installed_apps": "Les applications suivantes ne peuvent pas être restaurées car elles sont déjà installées : {apps}", "regenconf_need_to_explicitly_specify_ssh": "La configuration de ssh a été modifiée manuellement. Vous devez explicitement indiquer la mention --force à \"ssh\" pour appliquer les changements.", - "migration_0015_cleaning_up": "Nettoyage du cache et des paquets qui ne sont plus nécessaires ...", - "migration_0015_specific_upgrade": "Démarrage de la mise à jour des paquets du système qui doivent l'être de manière indépendante...", + "migration_0015_cleaning_up": "Nettoyage du cache et des paquets qui ne sont plus nécessaires...", + "migration_0015_specific_upgrade": "Démarrage de la mise à jour des paquets du système qui doivent être mis à jour séparément...", "migration_0015_modified_files": "Veuillez noter que les fichiers suivants ont été modifiés manuellement et pourraient être écrasés à la suite de la mise à niveau : {manually_modified_files}", "migration_0015_problematic_apps_warning": "Veuillez noter que des applications qui peuvent poser problèmes ont été détectées. Il semble qu'elles n'aient pas été installées à partir du catalogue d'applications YunoHost, ou bien qu'elles ne soient pas signalées comme \"fonctionnelles\". Par conséquent, il n'est pas possible de garantir que les applications suivantes fonctionneront encore après la mise à niveau : {problematic_apps}", "migration_0015_general_warning": "Veuillez noter que cette migration est une opération délicate. L'équipe YunoHost a fait de son mieux pour la revérifier et la tester, mais la migration pourrait quand même casser des éléments du système ou de ses applications.\n\nIl est donc recommandé :\n…- de faire une sauvegarde de toute donnée ou application critique. Plus d'informations ici https://yunohost.org/backup ;\n…- d'être patient après le lancement de la migration. Selon votre connexion internet et votre matériel, la mise à niveau peut prendre jusqu'à quelques heures.", "migration_0015_system_not_fully_up_to_date": "Votre système n'est pas entièrement à jour. Veuillez effectuer une mise à jour normale avant de lancer la migration vers Buster.", "migration_0015_not_enough_free_space": "L'espace libre est très faible dans /var/ ! Vous devriez avoir au moins 1 Go de libre pour effectuer cette migration.", "migration_0015_not_stretch": "La distribution Debian actuelle n'est pas Stretch !", - "migration_0015_yunohost_upgrade": "Démarrage de la mise à jour de YunoHost ...", + "migration_0015_yunohost_upgrade": "Démarrage de la mise à jour de YunoHost...", "migration_0015_still_on_stretch_after_main_upgrade": "Quelque chose s'est mal passé lors de la mise à niveau, le système semble toujours être sous Debian Stretch", - "migration_0015_main_upgrade": "Démarrage de la mise à niveau générale ...", - "migration_0015_patching_sources_list": "Mise à jour du fichier sources.lists ...", + "migration_0015_main_upgrade": "Démarrage de la mise à niveau générale...", + "migration_0015_patching_sources_list": "Mise à jour du fichier sources.lists...", "migration_0015_start": "Démarrage de la migration vers Buster", "migration_description_0015_migrate_to_buster": "Mise à niveau du système vers Debian Buster et YunoHost 4.x", "diagnosis_dns_try_dyndns_update_force": "La configuration DNS de ce domaine devrait être automatiquement gérée par YunoHost. Si ce n'est pas le cas, vous pouvez essayer de forcer une mise à jour en utilisant yunohost dyndns update --force.", @@ -612,15 +612,15 @@ "migration_description_0019_extend_permissions_features": "Étendre et retravailler le système de gestion des permissions applicatives", "diagnosis_basesystem_hardware_model": "Le modèle du serveur est {model}", "diagnosis_backports_in_sources_list": "Il semble qu'apt (le gestionnaire de paquets) soit configuré pour utiliser le dépôt des rétroportages (backports). A moins que vous ne sachiez vraiment ce que vous faites, nous vous déconseillons fortement d'installer des paquets provenant des rétroportages, car cela risque de créer des instabilités ou des conflits sur votre système.", - "postinstall_low_rootfsspace": "Le système de fichiers a une taille totale inférieure à 10 Go, ce qui est préoccupant et devrait attirer votre attention ! Vous allez certainement arriver à court d'espace disque (très) rapidement ! Il est recommandé d'avoir au moins 16 Go à la racine pour ce système de fichiers. Si vous voulez installer YunoHost malgré cet avertissement, relancez la postinstall avec --force-diskspace", + "postinstall_low_rootfsspace": "Le système de fichiers a une taille totale inférieure à 10 Go, ce qui est préoccupant et devrait attirer votre attention ! Vous allez certainement arriver à court d'espace disque (très) rapidement ! Il est recommandé d'avoir au moins 16 Go à la racine pour ce système de fichiers. Si vous voulez installer YunoHost malgré cet avertissement, relancez la post-installation avec --force-diskspace", "domain_remove_confirm_apps_removal": "Le retrait de ce domaine retirera aussi ces applications :\n{apps}\n\nÊtes vous sûr de vouloir cela ? [{answers}]", "diagnosis_rootfstotalspace_critical": "Le système de fichiers racine ne fait que {space} ! Vous allez certainement le remplir très rapidement ! Il est recommandé d'avoir au moins 16 GB pour ce système de fichiers.", "diagnosis_rootfstotalspace_warning": "Le système de fichiers racine n'est que de {space}. Cela peut suffire, mais faites attention car vous risquez de les remplir rapidement... Il est recommandé d'avoir au moins 16 GB pour ce système de fichiers.", "app_restore_script_failed": "Une erreur s'est produite dans le script de restauration de l'application", - "restore_backup_too_old": "Cette sauvegarde ne peut être restaurée car elle provient d'une version trop ancienne de YunoHost.", - "migration_update_LDAP_schema": "Mise à jour du schéma LDAP ...", + "restore_backup_too_old": "Cette sauvegarde ne peut pas être restaurée car elle provient d'une version trop ancienne YunoHost.", + "migration_update_LDAP_schema": "Mise à jour du schéma LDAP...", "log_backup_create": "Créer une archive de sauvegarde", - "global_settings_setting_ssowat_panel_overlay_enabled": "Activer la surcouche du panel SSOwat", + "global_settings_setting_ssowat_panel_overlay_enabled": "Activer la superposition de la vignette SSOwat", "migration_ldap_rollback_success": "Système rétabli dans son état initial.", "permission_cant_add_to_all_users": "L'autorisation {permission} ne peut pas être ajoutée à tous les utilisateurs.", "migration_ldap_migration_failed_trying_to_rollback": "Impossible de migrer... tentative de restauration du système.", From c37f8e85143df6bdbc294694c8e1ca2d049e460b Mon Sep 17 00:00:00 2001 From: Luca Date: Sun, 8 Aug 2021 13:30:12 +0000 Subject: [PATCH 2677/3170] Translated using Weblate (German) Currently translated at 100.0% (633 of 633 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/locales/de.json b/locales/de.json index d11508a54..1ef86980a 100644 --- a/locales/de.json +++ b/locales/de.json @@ -125,16 +125,16 @@ "upnp_disabled": "UPnP deaktiviert", "upnp_enabled": "UPnP aktiviert", "upnp_port_open_failed": "UPnP Port konnte nicht geöffnet werden.", - "user_created": "Der Benutzer wurde erstellt", + "user_created": "Benutzer erstellt", "user_creation_failed": "Benutzer konnte nicht erstellt werden {user}: {error}", - "user_deleted": "Der Benutzer wurde entfernt", + "user_deleted": "Benutzer gelöscht", "user_deletion_failed": "Benutzer konnte nicht gelöscht werden {user}: {error}", "user_home_creation_failed": "Persönlicher Ordner des Benutzers konnte nicht erstellt werden", "user_unknown": "Unbekannter Benutzer: {user}", "user_update_failed": "Benutzer konnte nicht aktualisiert werden {user}: {error}", - "user_updated": "Der Benutzer wurde aktualisiert", + "user_updated": "Benutzerinformationen wurden aktualisiert", "yunohost_already_installed": "YunoHost ist bereits installiert", - "yunohost_configured": "YunoHost wurde konfiguriert", + "yunohost_configured": "YunoHost ist nun konfiguriert", "yunohost_installing": "YunoHost wird installiert...", "yunohost_not_installed": "YunoHost ist nicht oder nur unvollständig installiert worden. Bitte 'yunohost tools postinstall' ausführen", "app_not_properly_removed": "{app} wurde nicht ordnungsgemäß entfernt", @@ -180,7 +180,7 @@ "app_location_unavailable": "Diese URL ist nicht verfügbar oder wird von einer installierten Applikation genutzt:\n{apps}", "backup_applying_method_custom": "Rufe die benutzerdefinierte Backup-Methode '{method}' auf...", "backup_archive_system_part_not_available": "Der System-Teil '{part}' ist in diesem Backup nicht enthalten", - "backup_archive_writing_error": "Die Dateien '{source} (im Ordner '{dest}') konnten nicht in das komprimierte Archiv-Backup '{archive}' hinzugefügt werden", + "backup_archive_writing_error": "Die Dateien '{source} (im Ordner '{dest}') konnten nicht in das komprimierte Archiv-Backup '{archive}' hinzugefügt werden", "app_change_url_success": "{app} URL ist nun {domain}{path}", "global_settings_bad_type_for_setting": "Falscher Typ der Einstellung {setting}. Empfangen: {received_type}, aber erwarteter Typ: {expected_type}", "global_settings_bad_choice_for_enum": "Wert des Einstellungsparameters {setting} ungültig. Der Wert den Sie eingegeben haben: '{choice}', die gültigen Werte für diese Einstellung: {available_choices}", @@ -189,7 +189,7 @@ "dyndns_domain_not_provided": "Der DynDNS-Anbieter {provider} kann die Domäne(n) {domain} nicht bereitstellen.", "dyndns_could_not_check_available": "Konnte nicht überprüfen, ob {domain} auf {provider} verfügbar ist.", "dyndns_could_not_check_provide": "Konnte nicht überprüft, ob {provider} die Domain(s) {domain} bereitstellen kann.", - "domain_dns_conf_is_just_a_recommendation": "Dieser Befehl zeigt Ihnen die *empfohlene* Konfiguration. Die DNS-Konfiguration wird NICHT für Sie eingerichtet. Es liegt in Ihrer Verantwortung, Ihre DNS-Zone in Ihrem Registrar gemäß dieser Empfehlung zu konfigurieren.", + "domain_dns_conf_is_just_a_recommendation": "Dieser Befehl zeigt dir die *empfohlene* Konfiguration. Er konfiguriert *nicht* das DNS für dich. Es liegt in deiner Verantwortung, die DNS-Zone bei deinem DNS-Registrar nach dieser Empfehlung zu konfigurieren.", "dpkg_lock_not_available": "Dieser Befehl kann momentan nicht ausgeführt werden, da anscheinend ein anderes Programm die Sperre von dpkg (dem Systempaket-Manager) verwendet", "confirm_app_install_thirdparty": "WARNUNG! Diese Applikation ist nicht Teil des YunoHost-Applikationskatalogs. Das Installieren von Drittanbieterapplikationen könnte die Sicherheit und Integrität Ihres Systems beeinträchtigen. Sie sollten wahrscheinlich NICHT fortfahren, es sei denn, Sie wissen, was Sie tun. Es wird KEIN SUPPORT angeboten, wenn die Applikation nicht funktionieren oder Ihr System beschädigen sollte... Wenn Sie das Risiko trotzdem eingehen möchten, tippen Sie '{answers}'", "confirm_app_install_danger": "WARNUNG! Diese Applikation ist noch experimentell (wenn nicht ausdrücklich nicht funktionsfähig)! Sie sollten Sie wahrscheinlich NICHT installieren, es sei denn, Sie wissen, was Sie tun. Es wird keine Unterstützung angeboten, falls diese Applikation nicht funktionieren oder Ihr System beschädigen sollte... Falls Sie bereit sind, dieses Risiko einzugehen, tippen Sie '{answers}'", @@ -253,19 +253,19 @@ "log_app_install": "Installiere die Applikation '{}'", "global_settings_reset_success": "Frühere Einstellungen werden nun auf {path} gesichert", "log_app_upgrade": "Upgrade der Applikation '{}'", - "good_practices_about_admin_password": "Sie sind nun dabei, ein neues Administrationspasswort zu definieren. Das Passwort sollte mindestens 8 Zeichen lang sein, obwohl es sinnvoll ist, ein längeres Passwort (z.B. eine Passphrase) und/oder eine Variation von Zeichen (Groß- und Kleinschreibung, Ziffern und Sonderzeichen) zu verwenden.", + "good_practices_about_admin_password": "Du bist nun dabei, ein neues Administratorpasswort zu definieren. Das Passwort sollte mindestens 8 Zeichen lang sein - es ist jedoch empfehlenswert, ein längeres Passwort (z.B. eine Passphrase) und/oder verschiedene Arten von Zeichen (Groß- und Kleinschreibung, Ziffern und Sonderzeichen) zu verwenden.", "log_corrupted_md_file": "Die mit Protokollen verknüpfte YAML-Metadatendatei ist beschädigt: '{md_file}\nFehler: {error}''", "global_settings_cant_serialize_settings": "Einstellungsdaten konnten nicht serialisiert werden, Grund: {reason}", "log_help_to_get_failed_log": "Der Vorgang'{desc}' konnte nicht abgeschlossen werden. Bitte teile das vollständige Protokoll dieser Operation mit dem Befehl 'yunohost log share {name}', um Hilfe zu erhalten", "backup_no_uncompress_archive_dir": "Dieses unkomprimierte Archivverzeichnis gibt es nicht", "log_app_change_url": "Ändere die URL der Applikation '{}'", "global_settings_setting_security_password_user_strength": "Stärke des Benutzerpassworts", - "good_practices_about_user_password": "Sie sind dabei, ein neues Benutzerpasswort zu definieren. Das Passwort sollte mindestens 8 Zeichen lang sein, obwohl es ratsam ist, ein längeres Passwort (z.B. eine Passphrase) und/oder eine Variation von Zeichen (Groß- und Kleinschreibung, Ziffern und Sonderzeichen) zu verwenden.", + "good_practices_about_user_password": "Du bist nun dabei, ein neues Nutzerpasswort zu definieren. Das Passwort sollte mindestens 8 Zeichen lang sein - es ist jedoch empfehlenswert, ein längeres Passwort (z.B. eine Passphrase) und/oder verschiedene Arten von Zeichen (Groß- und Kleinschreibung, Ziffern und Sonderzeichen) zu verwenden.", "log_link_to_failed_log": "Der Vorgang konnte nicht abgeschlossen werden '{desc}'. Bitte gib das vollständige Protokoll dieser Operation mit Klicken Sie hier an, um Hilfe zu erhalten", "backup_cant_mount_uncompress_archive": "Das unkomprimierte Archiv konnte nicht als schreibgeschützt gemountet werden", "backup_csv_addition_failed": "Es konnten keine Dateien zur Sicherung in die CSV-Datei hinzugefügt werden", "global_settings_setting_security_password_admin_strength": "Stärke des Admin-Passworts", - "global_settings_key_doesnt_exists": "Der Schlüssel'{settings_key}' existiert nicht in den globalen Einstellungen, du kannst alle verfügbaren Schlüssel sehen, indem du 'yunohost settings list' ausführst", + "global_settings_key_doesnt_exists": "Der Schlüssel'{settings_key}' existiert nicht in den globalen Einstellungen, du kannst alle verfügbaren Schlüssel sehen, indem du 'yunohost settings list' ausführst", "log_app_makedefault": "Mache '{}' zur Standard-Applikation", "hook_json_return_error": "Konnte die Rückkehr vom Einsprungpunkt {path} nicht lesen. Fehler: {msg}. Unformatierter Inhalt: {raw_content}", "app_full_domain_unavailable": "Es tut uns leid, aber diese Applikation erfordert die Installation auf einer eigenen Domain, aber einige andere Applikationen sind bereits auf der Domäne'{domain}' installiert. Eine mögliche Lösung ist das Hinzufügen und Verwenden einer Subdomain, die dieser Applikation zugeordnet ist.", @@ -386,7 +386,7 @@ "diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "Aktueller Reverse-DNS-Eintrag: {rdns_domain}
Erwarteter Wert: {ehlo_domain}", "diagnosis_mail_fcrdns_different_from_ehlo_domain": "Der Reverse-DNS-Eintrag für IPv{ipversion} ist nicht korrekt konfiguriert. Einige E-Mails könnten abgewiesen oder als Spam markiert werden.", "diagnosis_mail_fcrdns_nok_alternatives_6": "Einige Provider werden es Ihnen nicht erlauben, Ihren Reverse-DNS-Eintrag zu konfigurieren (oder ihre Funktionalität könnte defekt sein ...). Falls Ihr Reverse-DNS-Eintrag für IPv4 korrekt konfiguiert ist, können Sie versuchen, die Verwendung von IPv6 für das Versenden von E-Mails auszuschalten, indem Sie den Befehl yunohost settings set smtp.allow_ipv6 -v off ausführen. Bemerkung: Die Folge dieser letzten Lösung ist, dass Sie mit Servern, welche ausschliesslich über IPv6 verfügen, keine E-Mails mehr versenden oder empfangen können.", - "diagnosis_mail_fcrdns_nok_alternatives_4": "Einige Anbieter werden es Ihnen nicht erlauben, dass Sie Ihren Reverse-DNS (oder deren Funktionalität ist defekt...) konfigurieren. Falls Sie deswegen auf Probleme stossen sollten, ziehen Sie folgende Lösungen in Betracht:
- Manche ISPs stellen als Alternative die Benutzung eines Mail-Server-Relays zur Verfügung, was jedoch mit sich zieht, dass das Relay Ihren E-Mail-Verkehr ausspionieren kann.
- Eine privatsphärenfreundlichere Alternative ist die Benutzung eines VPN *mit einer dedizierten öffentlichen IP* um Einschränkungen dieser Art zu umgehen. Schauen Sie hier nach https://yunohost.org/#/vpn_advantage
- Schliesslich ist es auch möglich zu einem anderen Anbieter zu wechseln", + "diagnosis_mail_fcrdns_nok_alternatives_4": "Einige Anbieter werden es dir nicht erlauben, deinen Reverse-DNS zu konfigurieren (oder deren Funktionalität ist defekt...). Falls du deswegen auf Probleme stoßen solltest, ziehe folgende Lösungen in Betracht:
- Manche ISPs stellen als Alternative die Benutzung eines Mail-Server-Relays zur Verfügung, was jedoch mit sich zieht, dass das Relay Ihren E-Mail-Verkehr ausspionieren kann.
- Eine privatsphärenfreundlichere Alternative ist die Benutzung eines VPN *mit einer dedizierten öffentlichen IP* um Einschränkungen dieser Art zu umgehen. Schaue hier nach https://yunohost.org/#/vpn_advantage
- Schließlich ist es auch möglich zu einem anderen Anbieter zu wechseln", "diagnosis_mail_queue_unavailable_details": "Fehler: {error}", "diagnosis_mail_queue_unavailable": "Die Anzahl der anstehenden Nachrichten in der Warteschlange kann nicht abgefragt werden", "diagnosis_mail_queue_ok": "{nb_pending} anstehende E-Mails in der Warteschlange", @@ -429,7 +429,7 @@ "diagnosis_processes_killed_by_oom_reaper": "Das System hat einige Prozesse beendet, weil ihm der Arbeitsspeicher ausgegangen ist. Das passiert normalerweise, wenn das System ingesamt nicht genügend Arbeitsspeicher zur Verfügung hat oder wenn ein einzelner Prozess zu viel Speicher verbraucht. Zusammenfassung der beendeten Prozesse: \n{kills_summary}", "diagnosis_description_ports": "Offene Ports", "additional_urls_already_added": "Zusätzliche URL '{url}' bereits hinzugefügt in der zusätzlichen URL für Berechtigung '{permission}'", - "additional_urls_already_removed": "Zusätzliche URL '{url}' bereits entfernt in der zusätzlichen URL für Berechtigung '{permission}'", + "additional_urls_already_removed": "Zusätzliche URL '{url}' bereits entfernt in der zusätzlichen URL für Berechtigung '{permission}'", "app_label_deprecated": "Dieser Befehl ist veraltet! Bitte nutzen Sie den neuen Befehl 'yunohost user permission update' um das Applabel zu verwalten.", "diagnosis_http_hairpinning_issue_details": "Das ist wahrscheinlich aufgrund Ihrer ISP Box / Router. Als Konsequenz können Personen von ausserhalb Ihres Netzwerkes aber nicht von innerhalb Ihres lokalen Netzwerkes (wie wahrscheinlich Sie selber?) wie gewohnt auf Ihren Server zugreifen, wenn Sie ihre Domäne oder Ihre öffentliche IP verwenden. Sie können die Situation wahrscheinlich verbessern, indem Sie ein einen Blick in https://yunohost.org/dns_local_network werfen", "diagnosis_http_nginx_conf_not_up_to_date": "Jemand hat anscheinend die Konfiguration von Nginx manuell geändert. Diese Änderung verhindert, dass Yunohost eine Diagnose durchführen kann, wenn er via HTTP erreichbar ist.", @@ -529,7 +529,7 @@ "migrations_running_forward": "Durchführen der Migrationen {id}...", "migrations_skip_migration": "Überspringe Migrationen {id}...", "password_too_simple_2": "Dieses Passwort gehört zu den meistverwendeten der Welt. Bitte nehmen Sie etwas einzigartigeres", - "password_listed": "Dieses Passwort gehört zu den meistverwendeten der Welt. Bitte nehmen Sie etwas einzigartigeres.", + "password_listed": "Dieses Passwort zählt zu den meistgenutzten Passwörtern der Welt. Bitte wähle ein anderes, einzigartigeres Passwort.", "operation_interrupted": "Wurde die Operation manuell unterbrochen?", "invalid_number": "Muss eine Zahl sein", "migrations_to_be_ran_manually": "Die Migration {id} muss manuell durchgeführt werden. Bitte gehen Sie zu Werkzeuge → Migrationen auf der Webadmin-Seite oder führen Sie 'yunohost tools migrations run' aus.", @@ -550,9 +550,9 @@ "permission_protected": "Die Berechtigung ist geschützt. Sie können die Besuchergruppe nicht zu dieser Berechtigung hinzufügen oder daraus entfernen.", "permission_updated": "Berechtigung '{permission}' aktualisiert", "permission_update_failed": "Die Berechtigung '{permission}' kann nicht aktualisiert werden : {error}", - "permission_not_found": "Berechtigung nicht gefunden", + "permission_not_found": "Berechtigung '{permission}' nicht gefunden", "permission_deletion_failed": "Entfernung der Berechtigung nicht möglich '{permission}': {error}", - "permission_deleted": "Berechtigung gelöscht", + "permission_deleted": "Berechtigung '{permission}' gelöscht", "permission_currently_allowed_for_all_users": "Diese Berechtigung wird derzeit allen Benutzern zusätzlich zu anderen Gruppen erteilt. Möglicherweise möchten Sie entweder die Berechtigung 'all_users' entfernen oder die anderen Gruppen entfernen, für die sie derzeit zulässig sind.", "permission_creation_failed": "Berechtigungserstellung nicht möglich '{permission}' : {error}", "permission_created": "Berechtigung '{permission}' erstellt", From 6f908e9ccf77df44e0e3d85555c35693ff0478c4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 8 Aug 2021 19:00:03 +0200 Subject: [PATCH 2678/3170] Apply suggestions from code review --- src/yunohost/app.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index b3fea9a32..3b611e9d3 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -518,8 +518,7 @@ def app_upgrade(app=[], url=None, file=None, force=False): apps = app # Check if disk space available - size = os.statvfs('/') - if (size.f_bavail * size.f_frsize) / 1024 <= 512000: + if free_space_in_directory("/") <= 512 * 1000 * 1000: raise YunohostValidationError("disk_space_not_sufficient_update") # If no app is specified, upgrade all apps if not apps: @@ -882,7 +881,7 @@ def app_install( # Check if disk space available size = os.statvfs('/') - if (size.f_bavail * size.f_frsize) / 1024 <= 512000: + if free_space_in_directory("/") <= 512 * 1000 * 1000: raise YunohostValidationError("disk_space_not_sufficient_install") # Check ID From eb4bc97c5ac3e55c24a640e75359bdcfaa0c4474 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 8 Aug 2021 19:14:07 +0200 Subject: [PATCH 2679/3170] Update data/hooks/conf_regen/01-yunohost --- data/hooks/conf_regen/01-yunohost | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/hooks/conf_regen/01-yunohost b/data/hooks/conf_regen/01-yunohost index 29ce5db80..8ef398f1d 100755 --- a/data/hooks/conf_regen/01-yunohost +++ b/data/hooks/conf_regen/01-yunohost @@ -138,7 +138,7 @@ EOF # Don't suspend computer on LidSwitch mkdir -p ${pending_dir}/etc/systemd/logind.conf.d/ - cat > ${pending_dir}/etc/systemd/logind.conf.d/yunohost.conf << EOF + cat > ${pending_dir}/etc/systemd/logind.conf.d/ynh-override.conf << EOF [Login] HandleLidSwitch=ignore HandleLidSwitchDocked=ignore From 4671805cd25eaa71856564627fa62a52bcdb7981 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= Date: Sun, 8 Aug 2021 17:05:23 +0000 Subject: [PATCH 2680/3170] Translated using Weblate (French) Currently translated at 100.0% (633 of 633 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 cb5ea2f74..58ff9f359 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -619,7 +619,7 @@ "diagnosis_rootfstotalspace_critical": "Le système de fichiers racine ne fait que {space} ! Vous allez certainement le remplir très rapidement ! Il est recommandé d'avoir au moins 16 GB pour ce système de fichiers.", "diagnosis_rootfstotalspace_warning": "Le système de fichiers racine n'est que de {space}. Cela peut suffire, mais faites attention car vous risquez de les remplir rapidement... Il est recommandé d'avoir au moins 16 GB pour ce système de fichiers.", "app_restore_script_failed": "Une erreur s'est produite dans le script de restauration de l'application", - "restore_backup_too_old": "Cette sauvegarde ne peut pas être restaurée car elle provient d'une version trop ancienne YunoHost.", + "restore_backup_too_old": "Cette sauvegarde ne peut pas être restaurée car elle provient d'une version trop ancienne de YunoHost.", "migration_update_LDAP_schema": "Mise à jour du schéma LDAP...", "log_backup_create": "Créer une archive de sauvegarde", "global_settings_setting_ssowat_panel_overlay_enabled": "Activer la superposition de la vignette SSOwat", From d4addb8e4c035f7c262fee2c83bb37d601eb6b28 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 8 Aug 2021 19:27:01 +0200 Subject: [PATCH 2681/3170] Missing import / code cleanup --- src/yunohost/app.py | 6 +++--- src/yunohost/backup.py | 6 +----- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 0ce7a27c5..1d4a2ab30 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -55,6 +55,7 @@ from moulinette.utils.filesystem import ( from yunohost.service import service_status, _run_service_command from yunohost.utils import packages from yunohost.utils.error import YunohostError, YunohostValidationError +from yunohost.utils.filesystem import free_space_in_directory from yunohost.log import is_unit_operation, OperationLogger logger = getActionLogger("yunohost.app") @@ -878,11 +879,10 @@ def app_install( manifest, extracted_app_folder = _extract_app_from_file(app) else: raise YunohostValidationError("app_unknown") - + # Check if disk space available - size = os.statvfs('/') if free_space_in_directory("/") <= 512 * 1000 * 1000: - raise YunohostValidationError("disk_space_not_sufficient_install") + raise YunohostValidationError("disk_space_not_sufficient_install") # Check ID if "id" not in manifest or "__" in manifest["id"]: diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 99337b2f8..ecc5ae033 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -71,6 +71,7 @@ from yunohost.regenconf import regen_conf from yunohost.log import OperationLogger, is_unit_operation from yunohost.utils.error import YunohostError, YunohostValidationError from yunohost.utils.packages import ynh_packages_version +from yunohost.utils.filesystem import free_space_in_directory from yunohost.settings import settings_get BACKUP_PATH = "/home/yunohost.backup" @@ -2672,11 +2673,6 @@ def _recursive_umount(directory): return everything_went_fine -def free_space_in_directory(dirpath): - stat = os.statvfs(dirpath) - return stat.f_frsize * stat.f_bavail - - def disk_usage(path): # We don't do this in python with os.stat because we don't want # to follow symlinks From 9f8c99a5b2f9ad56a034247d137e54c1010fb2f5 Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Sun, 8 Aug 2021 17:30:19 +0000 Subject: [PATCH 2682/3170] [CI] Format code --- src/yunohost/app.py | 2 +- src/yunohost/domain.py | 2 +- src/yunohost/hook.py | 12 +++++++++--- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 1d4a2ab30..a48400a8e 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -520,7 +520,7 @@ def app_upgrade(app=[], url=None, file=None, force=False): apps = app # Check if disk space available if free_space_in_directory("/") <= 512 * 1000 * 1000: - raise YunohostValidationError("disk_space_not_sufficient_update") + raise YunohostValidationError("disk_space_not_sufficient_update") # If no app is specified, upgrade all apps if not apps: # FIXME : not sure what's supposed to happen if there is a url and a file but no apps... diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index e5f3a0133..279943a9f 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -116,7 +116,7 @@ def domain_add(operation_logger, domain, dyndns=False): domain = domain.lower() # Non-latin characters (e.g. café.com => xn--caf-dma.com) - domain = domain.encode('idna').decode('utf-8') + domain = domain.encode("idna").decode("utf-8") # DynDNS domain if dyndns: diff --git a/src/yunohost/hook.py b/src/yunohost/hook.py index 05e706660..33f5885e2 100644 --- a/src/yunohost/hook.py +++ b/src/yunohost/hook.py @@ -320,7 +320,13 @@ def hook_callback( def hook_exec( - path, args=None, raise_on_error=False, chdir=None, env=None, user="root", return_format="json" + path, + args=None, + raise_on_error=False, + chdir=None, + env=None, + user="root", + return_format="json", ): """ Execute hook from a file with arguments @@ -419,9 +425,9 @@ def _hook_exec_bash(path, args, chdir, env, user, return_format, loggers): # Construct command to execute if user == "root": - command = ['sh', '-c'] + command = ["sh", "-c"] else: - command = ['sudo', '-n', '-u', user, '-H', 'sh', '-c'] + command = ["sudo", "-n", "-u", user, "-H", "sh", "-c"] # use xtrace on fd 7 which is redirected to stdout env["BASH_XTRACEFD"] = "7" From c8d2ae0606743e549453d3754502cfbfa71b40cc Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 8 Aug 2021 20:13:00 +0200 Subject: [PATCH 2683/3170] [fix] nginx conf: we need those conf.inc to be there during the init --- data/hooks/conf_regen/15-nginx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/data/hooks/conf_regen/15-nginx b/data/hooks/conf_regen/15-nginx index e2d12df0f..e211a3aca 100755 --- a/data/hooks/conf_regen/15-nginx +++ b/data/hooks/conf_regen/15-nginx @@ -25,6 +25,8 @@ do_init_regen() { export compatibility="intermediate" ynh_render_template "security.conf.inc" "${nginx_conf_dir}/security.conf.inc" ynh_render_template "yunohost_admin.conf" "${nginx_conf_dir}/yunohost_admin.conf" + ynh_render_template "yunohost_admin.conf.inc" "${nginx_conf_dir}/yunohost_admin.conf.inc" + ynh_render_template "yunohost_api.conf.inc" "${nginx_conf_dir}/yunohost_api.conf.inc" mkdir -p $nginx_conf_dir/default.d/ cp "redirect_to_admin.conf" $nginx_conf_dir/default.d/ From e5f4f279a37dc45b0057a186e7a8f6c04de1c742 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 8 Aug 2021 21:24:21 +0200 Subject: [PATCH 2684/3170] Fix gl json format... --- locales/gl.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/locales/gl.json b/locales/gl.json index 35c56b7f8..0014c2338 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -211,15 +211,15 @@ "diagnosis_mail_ehlo_ok": "O servidor de email SMTP é accesible desde o exterior e por tanto pode recibir emails!", "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Algúns provedores non che van permitir desbloquear o porto 25 saínte porque non se preocupan pola Neutralidade da Rede.
- Algúns deles dan unha alternativa usando un repetidor de servidor de email mais isto implica que o repetidor poderá espiar todo o teu tráfico de email.
- Unha alternativa é utilizar unha VPN *cun IP público dedicado* para evitar este tipo de limitación. Le https://yunohost.org/#/vpn_advantage
- Tamén podes considerar cambiar a un provedor máis amigable coa neutralidade da rede", "diagnosis_mail_outgoing_port_25_blocked_details": "Antes deberías intentar desbloquear o porto 25 saínte no teu rúter de internet ou na web do provedor de hospedaxe. (Algúns provedores poderían pedirche que fagas unha solicitude para isto).", - "diagnosis_mail_ehlo_wrong": "Un servidor de email SMPT diferente responde en IPv{version}. O teu servidor probablemente non poida recibir emails.", + "diagnosis_mail_ehlo_wrong": "Un servidor de email SMPT diferente responde en IPv{ipversion}. O teu servidor probablemente non poida recibir emails.", "diagnosis_mail_ehlo_bad_answer_details": "Podería deberse a que outro servidor está a responder no lugar do teu.", "diagnosis_mail_ehlo_bad_answer": "Un servizo non-SMTP respondeu no porto 25 en IPv{ipversion}", "diagnosis_mail_ehlo_unreachable_details": "Non se puido abrir unha conexión no porto 25 do teu servidor en IPv{ipversion}. Non semella accesible.
1. A causa máis habitual é que o porto 25 non está correctamente redirixido no servidor.
2. Asegúrate tamén de que o servizo postfix está a funcionar.
3. En configuracións máis complexas: asegúrate de que o cortalumes ou reverse-proxy non están interferindo.", "diagnosis_mail_fcrdns_nok_details": "Deberías intentar configurar o DNS inverso con {ehlo_domain} na interface do teu rúter de internet ou na interface do teu provedor de hospedaxe. (Algúns provedores de hospedaxe poderían pedirche que lle fagas unha solicitude por escrito para isto).", "diagnosis_mail_fcrdns_dns_missing": "Non hai DNS inverso definido en IPv{ipversion}. Algúns emails poderían non ser entregrado ou ser marcados como spam.", "diagnosis_mail_fcrdns_ok": "O DNS inverso está correctamente configurado!", - "diagnosis_mail_ehlo_could_not_diagnose_details": "Erro: {erro}", - "diagnosis_mail_ehlo_could_not_diagnose": "Non se puido determinar se o servidor de email postfix é accesible desde o exterior en IPv{version}.", + "diagnosis_mail_ehlo_could_not_diagnose_details": "Erro: {error}", + "diagnosis_mail_ehlo_could_not_diagnose": "Non se puido determinar se o servidor de email postfix é accesible desde o exterior en IPv{ipversion}.", "diagnosis_mail_ehlo_wrong_details": "O EHLO recibido polo diagnosticador remoto en IPv{ipversion} é diferente ao dominio do teu servidor.
EHLO recibido: {wrong_ehlo}
Agardado: {right_ehlo}
A razón máis habitual para este problema é que o porto 25 non está correctamente redirixido ao teu servidor. Alternativamente, asegúrate de non ter un cortalumes ou reverse-proxy interferindo.", "diagnosis_regenconf_manually_modified_details": "Probablemente todo sexa correcto se sabes o que estás a facer! YunoHost non vai actualizar este ficheiro automáticamente... Pero ten en conta que as actualizacións de YunoHost poderían incluír cambios importantes recomendados. Se queres podes ver as diferenzas con yunohost tools regen-conf {category} --dry-run --with-diff e forzar o restablecemento da configuración recomendada con yunohost tools regen-conf {category} --force", "diagnosis_regenconf_manually_modified": "O ficheiro de configuración {file} semella que foi modificado manualmente.", @@ -246,7 +246,7 @@ "diagnosis_ports_ok": "O porto {port} é accesible desde o exterior.", "diagnosis_ports_partially_unreachable": "O porto {port} non é accesible desde o exterior en IPv{failed}.", "diagnosis_ports_unreachable": "O porto {port} non é accesible desde o exterior.", - "diagnosis_ports_could_not_diagnose_details": "Erro: {erro}", + "diagnosis_ports_could_not_diagnose_details": "Erro: {error}", "diagnosis_ports_could_not_diagnose": "Non se puido comprobar se os portos son accesibles desde o exterior en IPv{ipversion}.", "diagnosis_description_regenconf": "Configuracións do sistema", "diagnosis_description_mail": "Email", From 80c12ecf3c7c1eb19ddd6459d2886f62d3e7d86c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 8 Aug 2021 21:25:36 +0200 Subject: [PATCH 2685/3170] Update changelog for 4.2.7 --- debian/changelog | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/debian/changelog b/debian/changelog index 2f997dacd..57da44532 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,30 @@ +yunohost (4.2.7) stable; urgency=low + + Notable changes: + - [fix] app: 'yunohost app search' was broken (8cf92576) + - [fix] app: actions were broken, fix by reintroducing user arg in hook exec ([#1264](https://github.com/yunohost/yunohost/pull/1264)) + - [enh] app: Add check for available disk space before app install/upgrade ([#1266](https://github.com/yunohost/yunohost/pull/1266)) + - [enh] domains: Better support for non latin domain name ([#1270](https://github.com/yunohost/yunohost/pull/1270)) + - [enh] security: Add settings to restrict webadmin access to a list of IPs ([#1271](https://github.com/yunohost/yunohost/pull/1271)) + - [enh] misc: Avoid to suspend server if we close lidswitch ([#1275](https://github.com/yunohost/yunohost/pull/1275)) + - [i18n] Translations updated for French, Galician, German + + Misc fixes, improvements: + - [fix] logs: Sometimes metadata ends up being empty for some reason and ends up being loaded as None, making the "in" operator crash :| (50129f3a) + - [fix] nginx: Invalid HTML in yunohost_panel #1837 (1c15f644) + - [fix] ssh: set .ssh folder permissions to 600 ([#1269](https://github.com/yunohost/yunohost/pull/1269)) + - [fix] firewall: upnpc.getspecificportmapping expects an int, can't handle port ranges ? (ee70dfe5) + - [fix] php helpers: fix conf path for dedicated php server (7349b229) + - [fix] php helpers: Increase memory limit for composer ([#1278](https://github.com/yunohost/yunohost/pull/1278)) + - [enh] nodejs helpers: Upgrade n version to 7.3.0 ([#1262](https://github.com/yunohost/yunohost/pull/1262)) + - [fix] doc: Example command in yunopaste usage was outdated (a8df60da) + - [fix] doc, diagnosis: update links to SMTP relay configuration ([#1277](https://github.com/yunohost/yunohost/pull/1277)) + - [i18n] Translations fixes/cleanups (780c3cb8, b61082b1, 271e3a26, 4e4173d1, fab248ce, d49ad748) + + Thanks to all contributors <3 ! (Bram, Christian Wehrli, cyxae, Éric Gaspar, José M, Kay0u, Le Libre Au Quotidien, ljf, Luca, Meta Meta, ppr, Stylix58, Tagada, yalh76) + + -- Alexandre Aubin Sun, 08 Aug 2021 19:27:27 +0200 + yunohost (4.2.6.1) stable; urgency=low - [fix] Remove invaluement from free dnsbl list (71489307) From 1be7984905fb0e2cdda60a91b0e1995dd5729295 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 9 Aug 2021 00:32:48 +0200 Subject: [PATCH 2686/3170] [fix] Funky fr translation --- locales/fr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/fr.json b/locales/fr.json index 58ff9f359..855aa1ab3 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -353,7 +353,7 @@ "tools_upgrade_special_packages_completed": "La mise à jour des paquets de YunoHost est finie !\nPressez [Entrée] pour revenir à la ligne de commande", "dpkg_lock_not_available": "Cette commande ne peut pas être exécutée pour le moment car un autre programme semble utiliser le verrou de dpkg (le gestionnaire de package système)", "tools_upgrade_cant_unhold_critical_packages": "Impossible d'enlever le drapeau 'hold' pour les paquets critiques…", - "tools_upgrade_special_packages_explanation": "La mise à niveau spécifique à YunoHost se poursuivra en arrière-plan. Veuillez ne pas lancer d'autres actions sur votre serveur pendant les 10 prochaines minutes (selon la vitesse du matériel). Après cela, vous devrez peut-être vous reconnecter à l'administrateur Web. Le journal de mise à niveau sera disponible dans Outils → Journal (dans le webadmin) ou en utilisant la « liste des journaux yunohost » (à partir de la ligne de commande).", + "tools_upgrade_special_packages_explanation": "La mise à niveau spécifique à YunoHost se poursuivra en arrière-plan. Veuillez ne pas lancer d'autres actions sur votre serveur pendant les 10 prochaines minutes (selon la vitesse du matériel). Après cela, vous devrez peut-être vous reconnecter à la webadmin. Le journal de mise à niveau sera disponible dans Outils → Journal (dans le webadmin) ou en utilisant 'yunohost log list' (à partir de la ligne de commande).", "update_apt_cache_failed": "Impossible de mettre à jour le cache APT (gestionnaire de paquets Debian). Voici un extrait du fichier sources.list qui pourrait vous aider à identifier les lignes problématiques :\n{sourceslist}", "update_apt_cache_warning": "Des erreurs se sont produites lors de la mise à jour du cache APT (gestionnaire de paquets Debian). Voici un extrait des lignes du fichier sources.list qui pourrait vous aider à identifier les lignes problématiques :\n{sourceslist}", "backup_permission": "Permission de sauvegarde pour {app}", From 55d5585edd0dd9944371b3006593b8bfb59151f2 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 9 Aug 2021 00:43:03 +0200 Subject: [PATCH 2687/3170] {app} removed -> uninstalled --- locales/en.json | 2 +- locales/fr.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/en.json b/locales/en.json index 1511bed0a..693e9d24d 100644 --- a/locales/en.json +++ b/locales/en.json @@ -40,7 +40,7 @@ "app_not_correctly_installed": "{app} seems to be incorrectly installed", "app_not_installed": "Could not find {app} in the list of installed apps: {all_apps}", "app_not_properly_removed": "{app} has not been properly removed", - "app_removed": "{app} removed", + "app_removed": "{app} uninstalled", "app_requirements_checking": "Checking required packages for {app}...", "app_requirements_unmeet": "Requirements are not met for {app}, the package {pkgname} ({version}) must be {spec}", "app_remove_after_failed_install": "Removing the app following the installation failure...", diff --git a/locales/fr.json b/locales/fr.json index 855aa1ab3..8d8ce1345 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -14,7 +14,7 @@ "app_not_correctly_installed": "{app} semble être mal installé", "app_not_installed": "Nous n’avons pas trouvé {app} dans la liste des applications installées : {all_apps}", "app_not_properly_removed": "{app} n’a pas été supprimé correctement", - "app_removed": "{app} supprimé", + "app_removed": "{app} désinstallé", "app_requirements_checking": "Vérification des paquets requis pour {app}...", "app_requirements_unmeet": "Les pré-requis de {app} ne sont pas satisfaits, le paquet {pkgname} ({version}) doit être {spec}", "app_sources_fetch_failed": "Impossible de récupérer les fichiers sources, l’URL est-elle correcte ?", From c4762c05eb55e08f5f3cd19adc54bd1a7f91ab8b Mon Sep 17 00:00:00 2001 From: Luca Date: Sun, 8 Aug 2021 21:45:57 +0000 Subject: [PATCH 2688/3170] Translated using Weblate (German) Currently translated at 100.0% (637 of 637 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/locales/de.json b/locales/de.json index 1ef86980a..76de18746 100644 --- a/locales/de.json +++ b/locales/de.json @@ -124,7 +124,7 @@ "upnp_dev_not_found": "Es konnten keine UPnP Geräte gefunden werden", "upnp_disabled": "UPnP deaktiviert", "upnp_enabled": "UPnP aktiviert", - "upnp_port_open_failed": "UPnP Port konnte nicht geöffnet werden.", + "upnp_port_open_failed": "Port konnte nicht via UPnP geöffnet werden", "user_created": "Benutzer erstellt", "user_creation_failed": "Benutzer konnte nicht erstellt werden {user}: {error}", "user_deleted": "Benutzer gelöscht", @@ -528,7 +528,7 @@ "migrations_no_such_migration": "Es existiert keine Migration genannt '{id}'", "migrations_running_forward": "Durchführen der Migrationen {id}...", "migrations_skip_migration": "Überspringe Migrationen {id}...", - "password_too_simple_2": "Dieses Passwort gehört zu den meistverwendeten der Welt. Bitte nehmen Sie etwas einzigartigeres", + "password_too_simple_2": "Das Passwort muss mindestens 8 Zeichen lang sein und Gross- sowie Kleinbuchstaben enthalten", "password_listed": "Dieses Passwort zählt zu den meistgenutzten Passwörtern der Welt. Bitte wähle ein anderes, einzigartigeres Passwort.", "operation_interrupted": "Wurde die Operation manuell unterbrochen?", "invalid_number": "Muss eine Zahl sein", @@ -539,8 +539,8 @@ "permission_already_allowed": "Die Gruppe '{group}' hat die Berechtigung '{permission}' bereits erhalten", "pattern_password_app": "Entschuldigen Sie bitte! Passwörter dürfen folgende Zeichen nicht enthalten: {forbidden_chars}", "pattern_email_forward": "Es muss sich um eine gültige E-Mail-Adresse handeln. Das Symbol '+' wird akzeptiert (zum Beispiel : maxmuster@beispiel.com oder maxmuster+yunohost@beispiel.com)", - "password_too_simple_4": "Dass Passwort muss mindestens 12 Zeichen lang sein und Zahlen, Klein- und Grossbuchstaben und Sonderzeichen enthalten", - "password_too_simple_3": "Das Passwort muss mindestens 8 Zeichen lang sein und Zahlen, Klein- und Grossbuchstaben und Sonderzeichen enthalten", + "password_too_simple_4": "Das Passwort muss mindestens 12 Zeichen lang sein und Grossbuchstaben, Kleinbuchstaben, Zahlen und Sonderzeichen enthalten", + "password_too_simple_3": "Das Passwort muss mindestens 8 Zeichen lang sein und Grossbuchstaben, Kleinbuchstaben, Zahlen und Sonderzeichen enthalten", "regenconf_file_manually_removed": "Die Konfigurationsdatei '{conf}' wurde manuell gelöscht und wird nicht erstellt", "regenconf_file_manually_modified": "Die Konfigurationsdatei '{conf}' wurde manuell bearbeitet und wird nicht aktualisiert", "regenconf_file_kept_back": "Die Konfigurationsdatei '{conf}' sollte von \"regen-conf\" (Kategorie {category}) gelöscht werden, wurde aber beibehalten.", @@ -631,5 +631,9 @@ "unknown_main_domain_path": "Unbekannte:r Domain oder Pfad für '{app}'. Du musst eine Domain und einen Pfad setzen, um die URL für Berechtigungen zu setzen.", "yunohost_postinstall_end_tip": "Post-install ist fertig! Um das Setup abzuschliessen, wird empfohlen:\n - einen ersten Benutzer über den Bereich 'Benutzer*in' im Adminbereich hinzuzufügen (oder mit 'yunohost user create ' in der Kommandezeile);\n - mögliche Fehler zu diagnostizieren über den Bereich 'Diagnose' im Adminbereich (oder mit 'yunohost diagnosis run' in der Kommandozeile;\n - Die Abschnitte 'Install YunoHost' und 'Geführte Tour' im Administratorenhandbuch zu lesen: https://yunohost.org/admindoc.", "user_already_exists": "Der Benutzer '{user}' ist bereits vorhanden", - "update_apt_cache_warning": "Beim Versuch den Cache für APT (Debians Paketmanager) zu aktualisieren, ist etwas schief gelaufen. Hier ist ein Dump der Zeilen aus sources.list, die Ihnen vielleicht dabei helfen, das Problem zu identifizieren:\n{sourceslist}" + "update_apt_cache_warning": "Beim Versuch den Cache für APT (Debians Paketmanager) zu aktualisieren, ist etwas schief gelaufen. Hier ist ein Dump der Zeilen aus sources.list, die Ihnen vielleicht dabei helfen, das Problem zu identifizieren:\n{sourceslist}", + "global_settings_setting_security_webadmin_allowlist": "IP-Adressen, die auf die Verwaltungsseite zugreifen dürfen. Kommasepariert.", + "global_settings_setting_security_webadmin_allowlist_enabled": "Erlaube nur bestimmten IP-Adressen den Zugriff auf die Verwaltungsseite.", + "disk_space_not_sufficient_update": "Es ist nicht genügend Speicherplatz frei, um diese Applikation zu aktuallisieren", + "disk_space_not_sufficient_install": "Es ist nicht genügend Speicherplatz frei, um diese Applikation zu installieren" } From 2da91609627f807dd2accd05bc37b3589c4574d0 Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Sun, 8 Aug 2021 22:59:26 +0000 Subject: [PATCH 2689/3170] [CI] Remove stale translated strings --- locales/de.json | 2 +- locales/eo.json | 2 +- locales/fr.json | 2 +- locales/gl.json | 2 +- locales/uk.json | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/locales/de.json b/locales/de.json index 1ef86980a..46be1967a 100644 --- a/locales/de.json +++ b/locales/de.json @@ -632,4 +632,4 @@ "yunohost_postinstall_end_tip": "Post-install ist fertig! Um das Setup abzuschliessen, wird empfohlen:\n - einen ersten Benutzer über den Bereich 'Benutzer*in' im Adminbereich hinzuzufügen (oder mit 'yunohost user create ' in der Kommandezeile);\n - mögliche Fehler zu diagnostizieren über den Bereich 'Diagnose' im Adminbereich (oder mit 'yunohost diagnosis run' in der Kommandozeile;\n - Die Abschnitte 'Install YunoHost' und 'Geführte Tour' im Administratorenhandbuch zu lesen: https://yunohost.org/admindoc.", "user_already_exists": "Der Benutzer '{user}' ist bereits vorhanden", "update_apt_cache_warning": "Beim Versuch den Cache für APT (Debians Paketmanager) zu aktualisieren, ist etwas schief gelaufen. Hier ist ein Dump der Zeilen aus sources.list, die Ihnen vielleicht dabei helfen, das Problem zu identifizieren:\n{sourceslist}" -} +} \ No newline at end of file diff --git a/locales/eo.json b/locales/eo.json index 9ccb61043..a053325d2 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -552,4 +552,4 @@ "app_argument_password_no_default": "Eraro dum analiza pasvorta argumento '{name}': pasvorta argumento ne povas havi defaŭltan valoron por sekureca kialo", "additional_urls_already_removed": "Plia URL '{url}' jam forigita en la aldona URL por permeso '{permission}'", "additional_urls_already_added": "Plia URL '{url}' jam aldonita en la aldona URL por permeso '{permission}'" -} +} \ No newline at end of file diff --git a/locales/fr.json b/locales/fr.json index 8d8ce1345..208492d20 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -634,4 +634,4 @@ "diagnosis_sshd_config_inconsistent": "Il semble que le port SSH a été modifié manuellement dans /etc/ssh/sshd_config. Depuis YunoHost 4.2, un nouveau paramètre global 'security.ssh.port' est disponible pour éviter de modifier manuellement la configuration.", "diagnosis_sshd_config_insecure": "La configuration SSH semble avoir été modifiée manuellement et n'est pas sécurisée car elle ne contient aucune directive 'AllowGroups' ou 'AllowUsers' pour limiter l'accès aux utilisateurs autorisés.", "backup_create_size_estimation": "L'archive contiendra environ {size} de données." -} +} \ No newline at end of file diff --git a/locales/gl.json b/locales/gl.json index 0014c2338..1caa2f3a6 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -346,4 +346,4 @@ "global_settings_setting_smtp_relay_user": "Conta de usuaria no repetidor SMTP", "global_settings_setting_smtp_relay_port": "Porto do repetidor SMTP", "global_settings_setting_smtp_relay_host": "Servidor repetidor SMTP para enviar emails no lugar da túa instancia yunohost. É útil se estás nunha destas situacións: o teu porto 25 está bloqueado polo teu provedor ISP u VPN, se tes unha IP residencial nunha lista DUHL, se non podes configurar DNS inversa ou se este servidor non ten conexión directa a internet e queres utilizar outro para enviar os emails." -} +} \ No newline at end of file diff --git a/locales/uk.json b/locales/uk.json index 0967ef424..9e26dfeeb 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -1 +1 @@ -{} +{} \ No newline at end of file From 226ab63393d3bf021750aa5ccdea0afaf736c3b0 Mon Sep 17 00:00:00 2001 From: ericgaspar Date: Mon, 9 Aug 2021 12:31:06 +0200 Subject: [PATCH 2690/3170] Update my.cnf --- data/templates/mysql/my.cnf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/templates/mysql/my.cnf b/data/templates/mysql/my.cnf index de13b467d..429596cf5 100644 --- a/data/templates/mysql/my.cnf +++ b/data/templates/mysql/my.cnf @@ -30,7 +30,7 @@ skip-external-locking key_buffer_size = 16K max_allowed_packet = 16M table_open_cache = 4 -sort_buffer_size = 64K +sort_buffer_size = 256K read_buffer_size = 256K read_rnd_buffer_size = 256K net_buffer_length = 2K From 3535b374aed13faf1e1ce72b1830fab12b09e920 Mon Sep 17 00:00:00 2001 From: tituspijean Date: Wed, 11 Aug 2021 16:19:51 +0000 Subject: [PATCH 2691/3170] [mdns] Always broadcast yunohost.local --- bin/yunomdns | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bin/yunomdns b/bin/yunomdns index 123935871..bc4e4c0e1 100755 --- a/bin/yunomdns +++ b/bin/yunomdns @@ -167,6 +167,8 @@ if __name__ == '__main__': if r == 'domains' or r == 'all': import glob config['domains'] = [ d.rsplit('/',1)[1][:-2] for d in glob.glob('/etc/nginx/conf.d/*.local.d') ] + if 'yunohost.local' not in config['domains']: + config['domains'].append('yunohost.local') print('Regenerated domains list: ' + str(config['domains'])) updated = True From cad5c39dae9b89c6f815250b6ed24351d7ae40fc Mon Sep 17 00:00:00 2001 From: tituspijean Date: Wed, 11 Aug 2021 16:21:32 +0000 Subject: [PATCH 2692/3170] [mdns] Remove logging and asyncio dependencies --- bin/yunomdns | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/bin/yunomdns b/bin/yunomdns index bc4e4c0e1..4f698a0ac 100755 --- a/bin/yunomdns +++ b/bin/yunomdns @@ -3,7 +3,6 @@ """ WIP Pythonic declaration of mDNS .local domains for YunoHost -Based off https://github.com/jstasiak/python-zeroconf/blob/master/tests/test_asyncio.py """ import subprocess @@ -13,8 +12,6 @@ import sys import argparse import yaml -import asyncio -import logging import socket from time import sleep from typing import List, Dict @@ -98,7 +95,6 @@ def get_network_interfaces(): return devices if __name__ == '__main__': - logging.basicConfig(level=logging.DEBUG) ### @@ -110,7 +106,6 @@ if __name__ == '__main__': Configuration file: /etc/yunohost/mdns.yml Subdomains are not supported. ''') - parser.add_argument('--debug', action='store_true') parser.add_argument('--regen', nargs='?', const='as_stored', choices=['domains', 'interfaces', 'all', 'as_stored'], help=''' Regenerates selection into the configuration file then starts mDNS broadcasting. @@ -125,13 +120,6 @@ if __name__ == '__main__': able.add_argument('--disable', action='store_true') args = parser.parse_args() - if args.debug: - logging.getLogger('zeroconf').setLevel(logging.DEBUG) - logging.getLogger('asyncio').setLevel(logging.DEBUG) - else: - logging.getLogger('zeroconf').setLevel(logging.WARNING) - logging.getLogger('asyncio').setLevel(logging.WARNING) - ### # CONFIG ### From f53f36e332fb40c89a9467b9e558dab828da2df9 Mon Sep 17 00:00:00 2001 From: tituspijean Date: Wed, 11 Aug 2021 17:58:50 +0000 Subject: [PATCH 2693/3170] [mdns] Fix yunohost.local broadcast --- bin/yunomdns | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bin/yunomdns b/bin/yunomdns index 4f698a0ac..7280aebc8 100755 --- a/bin/yunomdns +++ b/bin/yunomdns @@ -155,8 +155,6 @@ if __name__ == '__main__': if r == 'domains' or r == 'all': import glob config['domains'] = [ d.rsplit('/',1)[1][:-2] for d in glob.glob('/etc/nginx/conf.d/*.local.d') ] - if 'yunohost.local' not in config['domains']: - config['domains'].append('yunohost.local') print('Regenerated domains list: ' + str(config['domains'])) updated = True @@ -177,6 +175,9 @@ if __name__ == '__main__': print('No interface listed for broadcast.') sys.exit(0) + if 'yunohost.local' not in config['domains']: + config['domains'].append('yunohost.local') + zcs = {} interfaces = get_network_interfaces() for interface in config['interfaces']: From 9bef3105f1c2992bdac88c678ee30918a80024ed Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 11 Aug 2021 20:13:51 +0200 Subject: [PATCH 2694/3170] Add dependency to python3-zeroconf --- debian/control | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/debian/control b/debian/control index ef5061fe7..cabff028b 100644 --- a/debian/control +++ b/debian/control @@ -13,7 +13,8 @@ Depends: ${python3:Depends}, ${misc:Depends} , moulinette (>= 4.2), ssowat (>= 4.0) , python3-psutil, python3-requests, python3-dnspython, python3-openssl , python3-miniupnpc, python3-dbus, python3-jinja2 - , python3-toml, python3-packaging, python3-publicsuffix + , python3-toml, python3-packaging, python3-publicsuffix, + , python3-zeroconf, , apt, apt-transport-https, apt-utils, dirmngr , php7.3-common, php7.3-fpm, php7.3-ldap, php7.3-intl , mariadb-server, php7.3-mysql From a2a50d0e453164510640508c02958f21bfd20b05 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 11 Aug 2021 20:29:29 +0200 Subject: [PATCH 2695/3170] mdns: Remove argument parsing because we won't really need this ? :Z --- bin/yunomdns | 71 +++------------------------------------------------- 1 file changed, 4 insertions(+), 67 deletions(-) diff --git a/bin/yunomdns b/bin/yunomdns index 7280aebc8..3e3eea72f 100755 --- a/bin/yunomdns +++ b/bin/yunomdns @@ -6,10 +6,8 @@ Pythonic declaration of mDNS .local domains for YunoHost """ import subprocess -import os import re import sys -import argparse import yaml import socket @@ -96,30 +94,6 @@ def get_network_interfaces(): if __name__ == '__main__': - - ### - # ARGUMENTS - ### - - parser = argparse.ArgumentParser(description=''' - mDNS broadcast for .local domains. - Configuration file: /etc/yunohost/mdns.yml - Subdomains are not supported. - ''') - parser.add_argument('--regen', nargs='?', const='as_stored', choices=['domains', 'interfaces', 'all', 'as_stored'], - help=''' - Regenerates selection into the configuration file then starts mDNS broadcasting. - ''') - parser.add_argument('--set-regen', choices=['domains', 'interfaces', 'all', 'none'], - help=''' - Set which part of the configuration to be regenerated. - Implies --regen as_stored, with newly stored parameter. - ''') - able = parser.add_mutually_exclusive_group() - able.add_argument('--enable', action='store_true', help='Enables mDNS broadcast, and regenerates the configuration') - able.add_argument('--disable', action='store_true') - args = parser.parse_args() - ### # CONFIG ### @@ -128,48 +102,11 @@ if __name__ == '__main__': config = yaml.load(f) or {} updated = False - if args.enable: - config['enabled'] = True - args.regen = 'as_stored' - updated = True + required_fields = ["interfaces", "domains"] + missing_fields = [field for field in required_fields if field not in config] - if args.disable: - config['enabled'] = False - updated = True - - if args.set_regen: - config['regen'] = args.set_regen - args.regen = 'as_stored' - updated = True - - if args.regen: - if args.regen == 'as_stored': - r = config['regen'] - else: - r = args.regen - if r == 'none': - print('Regeneration disabled.') - if r == 'interfaces' or r == 'all': - config['interfaces'] = [ i for i in get_network_interfaces() ] - print('Regenerated interfaces list: ' + str(config['interfaces'])) - if r == 'domains' or r == 'all': - import glob - config['domains'] = [ d.rsplit('/',1)[1][:-2] for d in glob.glob('/etc/nginx/conf.d/*.local.d') ] - print('Regenerated domains list: ' + str(config['domains'])) - updated = True - - if updated: - with open('/etc/yunohost/mdns.yml', 'w') as f: - yaml.safe_dump(config, f, default_flow_style=False) - print('Configuration file updated.') - - ### - # MAIN SCRIPT - ### - - if config['enabled'] is not True: - print('YunomDNS is disabled.') - sys.exit(0) + if missing_fields: + print("The fields %s are required" % ', '.join(missing_fields)) if config['interfaces'] is None: print('No interface listed for broadcast.') From 5bf687d619ae685b574c84822b0c59469d832394 Mon Sep 17 00:00:00 2001 From: ppr Date: Wed, 11 Aug 2021 16:22:47 +0000 Subject: [PATCH 2696/3170] Translated using Weblate (French) Currently translated at 99.6% (635 of 637 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 208492d20..dea492e60 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -633,5 +633,7 @@ "diagnosis_sshd_config_inconsistent_details": "Veuillez exécuter yunohost settings set security.ssh.port -v VOTRE_PORT_SSH pour définir le port SSH, et vérifiez yunohost tools regen-conf ssh --dry-run --with-diff et yunohost tools regen-conf ssh --force pour réinitialiser votre configuration aux recommandations YunoHost.", "diagnosis_sshd_config_inconsistent": "Il semble que le port SSH a été modifié manuellement dans /etc/ssh/sshd_config. Depuis YunoHost 4.2, un nouveau paramètre global 'security.ssh.port' est disponible pour éviter de modifier manuellement la configuration.", "diagnosis_sshd_config_insecure": "La configuration SSH semble avoir été modifiée manuellement et n'est pas sécurisée car elle ne contient aucune directive 'AllowGroups' ou 'AllowUsers' pour limiter l'accès aux utilisateurs autorisés.", - "backup_create_size_estimation": "L'archive contiendra environ {size} de données." -} \ No newline at end of file + "backup_create_size_estimation": "L'archive contiendra environ {size} de données.", + "global_settings_setting_security_webadmin_allowlist": "Adresses IP autorisées à accéder à la page web du portail d'administration (webadmin). Elles doivent être séparées par une virgule.", + "global_settings_setting_security_webadmin_allowlist_enabled": "Autorisez seulement certaines IP à accéder à la page web du portail d'administration (webadmin)." +} From 98d295f5850ec99ccb83e94c70f3e100bbf159cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M?= Date: Thu, 12 Aug 2021 05:49:08 +0000 Subject: [PATCH 2697/3170] Translated using Weblate (Galician) Currently translated at 56.2% (358 of 637 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/gl/ --- locales/gl.json | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/locales/gl.json b/locales/gl.json index 1caa2f3a6..6ef2c45c1 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -67,7 +67,7 @@ "app_remove_after_failed_install": "Eliminando a app debido ao fallo na instalación...", "app_requirements_unmeet": "Non se cumpren os requerimentos de {app}, o paquete {pkgname} ({version}) debe ser {spec}", "app_requirements_checking": "Comprobando os paquetes requeridos por {app}...", - "app_removed": "{app} eliminada", + "app_removed": "{app} desinstalada", "app_not_properly_removed": "{app} non se eliminou de xeito correcto", "app_not_installed": "Non se puido atopar {app} na lista de apps instaladas: {all_apps}", "app_not_correctly_installed": "{app} semella que non está instalada correctamente", @@ -345,5 +345,17 @@ "global_settings_setting_smtp_relay_password": "Contrasinal no repetidor SMTP", "global_settings_setting_smtp_relay_user": "Conta de usuaria no repetidor SMTP", "global_settings_setting_smtp_relay_port": "Porto do repetidor SMTP", - "global_settings_setting_smtp_relay_host": "Servidor repetidor SMTP para enviar emails no lugar da túa instancia yunohost. É útil se estás nunha destas situacións: o teu porto 25 está bloqueado polo teu provedor ISP u VPN, se tes unha IP residencial nunha lista DUHL, se non podes configurar DNS inversa ou se este servidor non ten conexión directa a internet e queres utilizar outro para enviar os emails." -} \ No newline at end of file + "global_settings_setting_smtp_relay_host": "Servidor repetidor SMTP para enviar emails no lugar da túa instancia yunohost. É útil se estás nunha destas situacións: o teu porto 25 está bloqueado polo teu provedor ISP u VPN, se tes unha IP residencial nunha lista DUHL, se non podes configurar DNS inversa ou se este servidor non ten conexión directa a internet e queres utilizar outro para enviar os emails.", + "group_updated": "Grupo '{group}' actualizado", + "group_unknown": "Grupo descoñecido '{group}'", + "group_deletion_failed": "Non se eliminou o grupo '{group}': {error}", + "group_deleted": "Grupo '{group}' eliminado", + "group_cannot_be_deleted": "O grupo {group} non se pode eliminar manualmente.", + "group_cannot_edit_primary_group": "O grupo '{group}' non se pode editar manualmente. É o grupo primario que contén só a unha usuaria concreta.", + "group_cannot_edit_visitors": "O grupo 'visitors' non se pode editar manualmente. É un grupo especial que representa a tódas visitantes anónimas", + "group_cannot_edit_all_users": "O grupo 'all_users' non se pode editar manualmente. É un grupo especial que contén tódalas usuarias rexistradas en YunoHost", + "global_settings_setting_security_webadmin_allowlist": "Enderezos IP con permiso para acceder á webadmin. Separados por vírgulas.", + "global_settings_setting_security_webadmin_allowlist_enabled": "Permitir que só algúns IPs accedan á webadmin.", + "disk_space_not_sufficient_update": "Non hai espazo suficiente no disco para actualizar esta aplicación", + "disk_space_not_sufficient_install": "Non queda espazo suficiente no disco para instalar esta aplicación" +} From fe2e014b5667c48f9f983e0c17f7b1e1db884699 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 12 Aug 2021 15:26:39 +0200 Subject: [PATCH 2698/3170] mdns: Rework mdns's conf handling such that it's generated by the regen-conf. Also drop avahi-daemon because not needed anymore. --- data/hooks/conf_regen/01-yunohost | 5 +- data/hooks/conf_regen/37-avahi-daemon | 37 --------- data/hooks/conf_regen/37-mdns | 75 +++++++++++++++++++ data/templates/avahi-daemon/avahi-daemon.conf | 68 ----------------- .../mdns}/yunomdns.service | 0 data/templates/yunohost/mdns.yml | 4 - data/templates/yunohost/services.yml | 2 +- debian/control | 2 +- debian/install | 1 - debian/postinst | 4 - 10 files changed, 78 insertions(+), 120 deletions(-) delete mode 100755 data/hooks/conf_regen/37-avahi-daemon create mode 100755 data/hooks/conf_regen/37-mdns delete mode 100644 data/templates/avahi-daemon/avahi-daemon.conf rename data/{other => templates/mdns}/yunomdns.service (100%) delete mode 100644 data/templates/yunohost/mdns.yml diff --git a/data/hooks/conf_regen/01-yunohost b/data/hooks/conf_regen/01-yunohost index d160b9e66..3d65d34cd 100755 --- a/data/hooks/conf_regen/01-yunohost +++ b/data/hooks/conf_regen/01-yunohost @@ -3,7 +3,6 @@ set -e services_path="/etc/yunohost/services.yml" -mdns_path="/etc/yunohost/mdns.yml" do_init_regen() { if [[ $EUID -ne 0 ]]; then @@ -19,11 +18,9 @@ do_init_regen() { [[ -f /etc/yunohost/current_host ]] \ || echo "yunohost.org" > /etc/yunohost/current_host - # copy default services, mdns, and firewall + # copy default services and firewall [[ -f $services_path ]] \ || cp services.yml "$services_path" - [[ -f $mdns_path ]] \ - || cp mdns.yml "$mdns_path" [[ -f /etc/yunohost/firewall.yml ]] \ || cp firewall.yml /etc/yunohost/firewall.yml diff --git a/data/hooks/conf_regen/37-avahi-daemon b/data/hooks/conf_regen/37-avahi-daemon deleted file mode 100755 index 4127d66ca..000000000 --- a/data/hooks/conf_regen/37-avahi-daemon +++ /dev/null @@ -1,37 +0,0 @@ -#!/bin/bash - -set -e - -do_pre_regen() { - pending_dir=$1 - - cd /usr/share/yunohost/templates/avahi-daemon - - install -D -m 644 avahi-daemon.conf \ - "${pending_dir}/etc/avahi/avahi-daemon.conf" -} - -do_post_regen() { - regen_conf_files=$1 - - [[ -z "$regen_conf_files" ]] \ - || systemctl restart avahi-daemon -} - -FORCE=${2:-0} -DRY_RUN=${3:-0} - -case "$1" in - pre) - do_pre_regen $4 - ;; - post) - do_post_regen $4 - ;; - *) - echo "hook called with unknown argument \`$1'" >&2 - exit 1 - ;; -esac - -exit 0 diff --git a/data/hooks/conf_regen/37-mdns b/data/hooks/conf_regen/37-mdns new file mode 100755 index 000000000..903b41a0f --- /dev/null +++ b/data/hooks/conf_regen/37-mdns @@ -0,0 +1,75 @@ +#!/bin/bash + +set -e + +_generate_config() { + echo "domains:" + echo " - yunohost.local" + for domain in $YNH_DOMAINS + do + # Only keep .local domains (don't keep + [[ "$domain" =~ [^.]+\.[^.]+\.local$ ]] && echo "Subdomain $domain cannot be handled by Bonjour/Zeroconf/mDNS" >&2 + [[ "$domain" =~ ^[^.]+\.local$ ]] || continue + echo " - $domain" + done + + echo "interfaces:" + local_network_interfaces="$(ip --brief a | grep ' 10\.\| 192\.168\.' | awk '{print $1}')" + for interface in $local_network_interfaces + do + echo " - $interface" + done +} + +do_init_regen() { + do_pre_regen + do_post_regen /etc/systemd/system/yunomdns.service + systemctl enable yunomdns +} + +do_pre_regen() { + pending_dir="$1" + + cd /usr/share/yunohost/templates/dnsmasq + cp yunomdns.service ${pending_dir}/etc/systemd/system/ + + getent passwd mdns &>/dev/null || useradd --no-create-home --shell /usr/sbin/nologin --system --user-group mdns + + _generate_config > ${pending_dir}/etc/yunohost/mdns.yml +} + +do_post_regen() { + regen_conf_files="$1" + + chown mdns:mdns ${pending_dir}/etc/yunohost/mdns.yml + + # If we changed the systemd ynh-override conf + if echo "$regen_conf_files" | sed 's/,/\n/g' | grep -q "^/etc/systemd/system/yunomdns.service$" + then + systemctl daemon-reload + fi + + [[ -z "$regen_conf_files" ]] \ + || systemctl restart yunomdns +} + +FORCE=${2:-0} +DRY_RUN=${3:-0} + +case "$1" in + pre) + do_pre_regen $4 + ;; + post) + do_post_regen $4 + ;; + init) + do_init_regen + ;; + *) + echo "hook called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/data/templates/avahi-daemon/avahi-daemon.conf b/data/templates/avahi-daemon/avahi-daemon.conf deleted file mode 100644 index d3542a411..000000000 --- a/data/templates/avahi-daemon/avahi-daemon.conf +++ /dev/null @@ -1,68 +0,0 @@ -# This file is part of avahi. -# -# avahi is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation; either version 2 of the -# License, or (at your option) any later version. -# -# avahi is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public -# License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with avahi; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 -# USA. - -# See avahi-daemon.conf(5) for more information on this configuration -# file! - -[server] -host-name=yunohost -domain-name=local -#browse-domains=0pointer.de, zeroconf.org -use-ipv4=yes -use-ipv6=yes -#allow-interfaces=eth0 -#deny-interfaces=eth1 -#check-response-ttl=no -#use-iff-running=no -#enable-dbus=yes -#disallow-other-stacks=no -#allow-point-to-point=no -#cache-entries-max=4096 -#clients-max=4096 -#objects-per-client-max=1024 -#entries-per-entry-group-max=32 -ratelimit-interval-usec=1000000 -ratelimit-burst=1000 - -[wide-area] -enable-wide-area=yes - -[publish] -#disable-publishing=no -#disable-user-service-publishing=no -#add-service-cookie=no -#publish-addresses=yes -#publish-hinfo=yes -#publish-workstation=yes -#publish-domain=yes -#publish-dns-servers=192.168.50.1, 192.168.50.2 -#publish-resolv-conf-dns-servers=yes -#publish-aaaa-on-ipv4=yes -#publish-a-on-ipv6=no - -[reflector] -#enable-reflector=no -#reflect-ipv=no - -[rlimits] -#rlimit-as= -rlimit-core=0 -rlimit-data=4194304 -rlimit-fsize=0 -rlimit-nofile=768 -rlimit-stack=4194304 -rlimit-nproc=3 diff --git a/data/other/yunomdns.service b/data/templates/mdns/yunomdns.service similarity index 100% rename from data/other/yunomdns.service rename to data/templates/mdns/yunomdns.service diff --git a/data/templates/yunohost/mdns.yml b/data/templates/yunohost/mdns.yml deleted file mode 100644 index 3ed9e792b..000000000 --- a/data/templates/yunohost/mdns.yml +++ /dev/null @@ -1,4 +0,0 @@ -enabled: True -regen: all -interfaces: -domains: diff --git a/data/templates/yunohost/services.yml b/data/templates/yunohost/services.yml index 447829684..c7690fc9c 100644 --- a/data/templates/yunohost/services.yml +++ b/data/templates/yunohost/services.yml @@ -1,4 +1,3 @@ -avahi-daemon: {} dnsmasq: test_conf: dnsmasq --test dovecot: @@ -71,3 +70,4 @@ rmilter: null php5-fpm: null php7.0-fpm: null nslcd: null +avahi-daemon: null diff --git a/debian/control b/debian/control index cabff028b..c9306bef1 100644 --- a/debian/control +++ b/debian/control @@ -21,7 +21,7 @@ Depends: ${python3:Depends}, ${misc:Depends} , openssh-server, iptables, fail2ban, dnsutils, bind9utils , openssl, ca-certificates, netcat-openbsd, iproute2 , slapd, ldap-utils, sudo-ldap, libnss-ldapd, unscd, libpam-ldapd - , dnsmasq, avahi-daemon, libnss-mdns, resolvconf, libnss-myhostname + , dnsmasq, libnss-mdns, resolvconf, libnss-myhostname , postfix, postfix-ldap, postfix-policyd-spf-perl, postfix-pcre , dovecot-core, dovecot-ldap, dovecot-lmtpd, dovecot-managesieved, dovecot-antispam , rspamd, opendkim-tools, postsrsd, procmail, mailutils diff --git a/debian/install b/debian/install index e30a69a8b..1691a4849 100644 --- a/debian/install +++ b/debian/install @@ -5,7 +5,6 @@ doc/yunohost.8.gz /usr/share/man/man8/ data/actionsmap/* /usr/share/moulinette/actionsmap/ data/hooks/* /usr/share/yunohost/hooks/ data/other/yunoprompt.service /etc/systemd/system/ -data/other/yunomdns.service /etc/systemd/system/ data/other/password/* /usr/share/yunohost/other/password/ data/other/dpkg-origins/yunohost /etc/dpkg/origins data/other/dnsbl_list.yml /usr/share/yunohost/other/ diff --git a/debian/postinst b/debian/postinst index 7590197bd..ecae9b258 100644 --- a/debian/postinst +++ b/debian/postinst @@ -38,10 +38,6 @@ do_configure() { # Yunoprompt systemctl enable yunoprompt.service - - # Yunomdns - chown avahi:avahi /etc/yunohost/mdns.yml - systemctl enable yunomdns.service } # summary of how this script can be called: From 8dd65f78c8cc2e3631c2836b7596a238a8b654b4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 12 Aug 2021 15:27:19 +0200 Subject: [PATCH 2699/3170] mdns: Propagate new service name to i18n strings --- locales/ar.json | 2 +- locales/ca.json | 2 +- locales/de.json | 2 +- locales/en.json | 2 +- locales/eo.json | 2 +- locales/es.json | 2 +- locales/fr.json | 2 +- locales/it.json | 2 +- locales/oc.json | 2 +- locales/zh_Hans.json | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/locales/ar.json b/locales/ar.json index 06e444f4a..6a057bdc8 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -83,7 +83,7 @@ "yunohost_installing": "عملية تنصيب يونوهوست جارية …", "yunohost_not_installed": "إنَّ واي يونوهوست ليس مُنَصَّب أو هو مثبت حاليا بشكل خاطئ. قم بتنفيذ الأمر 'yunohost tools postinstall'", "migrations_list_conflict_pending_done": "لا يمكنك استخدام --previous و --done معًا على نفس سطر الأوامر.", - "service_description_avahi-daemon": "يسمح لك بالنفاذ إلى خادومك عبر الشبكة المحلية باستخدام yunohost.local", + "service_description_mdns": "يسمح لك بالنفاذ إلى خادومك عبر الشبكة المحلية باستخدام yunohost.local", "service_description_metronome": "يُدير حسابات الدردشة الفورية XMPP", "service_description_nginx": "يقوم بتوفير النفاذ و السماح بالوصول إلى كافة مواقع الويب المستضافة على خادومك", "service_description_postfix": "يقوم بإرسال و تلقي الرسائل البريدية الإلكترونية", diff --git a/locales/ca.json b/locales/ca.json index 1e4c55f5d..c9f71c0ad 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -283,7 +283,7 @@ "service_already_started": "El servei «{service:s}» ja està funcionant", "service_already_stopped": "Ja s'ha aturat el servei «{service:s}»", "service_cmd_exec_failed": "No s'ha pogut executar l'ordre «{command:s}»", - "service_description_avahi-daemon": "Permet accedir al servidor via «yunohost.local» en la xarxa local", + "service_description_mdns": "Permet accedir al servidor via «yunohost.local» en la xarxa local", "service_description_dnsmasq": "Gestiona la resolució del nom de domini (DNS)", "service_description_dovecot": "Permet als clients de correu accedir/recuperar correus (via IMAP i POP3)", "service_description_fail2ban": "Protegeix contra els atacs de força bruta i a altres atacs provinents d'Internet", diff --git a/locales/de.json b/locales/de.json index 83647ec17..0760dc775 100644 --- a/locales/de.json +++ b/locales/de.json @@ -597,7 +597,7 @@ "service_description_fail2ban": "Schützt gegen Brute-Force-Angriffe und andere Angriffe aus dem Internet", "service_description_dovecot": "Ermöglicht es E-Mail-Clients auf Konten zuzugreifen (IMAP und POP3)", "service_description_dnsmasq": "Verarbeitet die Auflösung des Domainnamens (DNS)", - "service_description_avahi-daemon": "Erlaubt, den Server im lokalen Netz über 'yunohost.local' zu erreichen", + "service_description_mdns": "Erlaubt, den Server im lokalen Netz über 'yunohost.local' zu erreichen", "restore_backup_too_old": "Dieses Backup kann nicht wieder hergestellt werden, weil es von einer zu alten YunoHost Version stammt.", "service_description_slapd": "Speichert Benutzer, Domains und verbundene Informationen", "service_description_rspamd": "Spamfilter und andere E-Mail-Merkmale", diff --git a/locales/en.json b/locales/en.json index 3734b7cf3..5c73cba8b 100644 --- a/locales/en.json +++ b/locales/en.json @@ -558,7 +558,7 @@ "service_already_started": "The service '{service:s}' is running already", "service_already_stopped": "The service '{service:s}' has already been stopped", "service_cmd_exec_failed": "Could not execute the command '{command:s}'", - "service_description_avahi-daemon": "Allows you to reach your server using 'yunohost.local' in your local network", + "service_description_mdns": "Allows you to reach your server using 'yunohost.local' in your local network", "service_description_dnsmasq": "Handles domain name resolution (DNS)", "service_description_dovecot": "Allows e-mail clients to access/fetch email (via IMAP and POP3)", "service_description_fail2ban": "Protects against brute-force and other kinds of attacks from the Internet", diff --git a/locales/eo.json b/locales/eo.json index d273593a9..b4d53f0f1 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -332,7 +332,7 @@ "hook_exec_failed": "Ne povis funkcii skripto: {path:s}", "global_settings_cant_open_settings": "Ne eblis malfermi agordojn, tial: {reason:s}", "user_created": "Uzanto kreita", - "service_description_avahi-daemon": "Permesas al vi atingi vian servilon uzante 'yunohost.local' en via loka reto", + "service_description_mdns": "Permesas al vi atingi vian servilon uzante 'yunohost.local' en via loka reto", "certmanager_attempt_to_replace_valid_cert": "Vi provas anstataŭigi bonan kaj validan atestilon por domajno {domain:s}! (Uzu --forte pretervidi)", "regenconf_updated": "Agordo ĝisdatigita por '{category}'", "update_apt_cache_warning": "Io iris malbone dum la ĝisdatigo de la kaŝmemoro de APT (paka administranto de Debian). Jen rubujo de la sources.list-linioj, kiuj povus helpi identigi problemajn liniojn:\n{sourceslist}", diff --git a/locales/es.json b/locales/es.json index f95451922..f2b8063cf 100644 --- a/locales/es.json +++ b/locales/es.json @@ -238,7 +238,7 @@ "service_description_fail2ban": "Protege contra ataques de fuerza bruta y otras clases de ataques desde Internet", "service_description_dovecot": "Permite a los clientes de correo acceder/obtener correo (vía IMAP y POP3)", "service_description_dnsmasq": "Maneja la resolución de nombres de dominio (DNS)", - "service_description_avahi-daemon": "Permite acceder a su servidor usando «yunohost.local» en su red local", + "service_description_mdns": "Permite acceder a su servidor usando «yunohost.local» en su red local", "server_reboot_confirm": "El servidor se reiniciará inmediatamente ¿está seguro? [{answers:s}]", "server_reboot": "El servidor se reiniciará", "server_shutdown_confirm": "El servidor se apagará inmediatamente ¿está seguro? [{answers:s}]", diff --git a/locales/fr.json b/locales/fr.json index f06acf2e5..565cc8032 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -232,7 +232,7 @@ "migrations_list_conflict_pending_done": "Vous ne pouvez pas utiliser --previous et --done simultanément.", "migrations_to_be_ran_manually": "La migration {id} doit être lancée manuellement. Veuillez aller dans Outils > Migrations dans l’interface admin, ou lancer `yunohost tools migrations run`.", "migrations_need_to_accept_disclaimer": "Pour lancer la migration {id}, vous devez accepter cet avertissement :\n---\n{disclaimer}\n---\nSi vous acceptez de lancer la migration, veuillez relancer la commande avec l’option --accept-disclaimer.", - "service_description_avahi-daemon": "Vous permet d’atteindre votre serveur en utilisant « yunohost.local » sur votre réseau local", + "service_description_mdns": "Vous permet d’atteindre votre serveur en utilisant « yunohost.local » sur votre réseau local", "service_description_dnsmasq": "Gère la résolution des noms de domaine (DNS)", "service_description_dovecot": "Permet aux clients de messagerie d’accéder/récupérer les courriels (via IMAP et POP3)", "service_description_fail2ban": "Protège contre les attaques brute-force et autres types d’attaques venant d’Internet", diff --git a/locales/it.json b/locales/it.json index 707f3afc2..3444b846d 100644 --- a/locales/it.json +++ b/locales/it.json @@ -428,7 +428,7 @@ "service_description_fail2ban": "Ti protegge dal brute-force e altri tipi di attacchi da Internet", "service_description_dovecot": "Consente ai client mail di accedere/recuperare le email (via IMAP e POP3)", "service_description_dnsmasq": "Gestisce la risoluzione dei domini (DNS)", - "service_description_avahi-daemon": "Consente di raggiungere il tuo server eseguendo 'yunohost.local' sulla tua LAN", + "service_description_mdns": "Consente di raggiungere il tuo server eseguendo 'yunohost.local' sulla tua LAN", "server_reboot_confirm": "Il server si riavvierà immediatamente, sei sicuro? [{answers:s}]", "server_reboot": "Il server si riavvierà", "server_shutdown_confirm": "Il server si spegnerà immediatamente, sei sicuro? [{answers:s}]", diff --git a/locales/oc.json b/locales/oc.json index 991383bc3..b5bd9474c 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -193,7 +193,7 @@ "user_unknown": "Utilizaire « {user:s} » desconegut", "user_update_failed": "Modificacion impossibla de l’utilizaire", "user_updated": "L’utilizaire es estat modificat", - "service_description_avahi-daemon": "permet d’aténher vòstre servidor via yunohost.local sus vòstre ret local", + "service_description_mdns": "permet d’aténher vòstre servidor via yunohost.local sus vòstre ret local", "service_description_dnsmasq": "gerís la resolucion dels noms de domeni (DNS)", "updating_apt_cache": "Actualizacion de la lista dels paquets disponibles…", "server_reboot_confirm": "Lo servidor es per reaviar sul pic, o volètz vertadièrament ? {answers:s}", diff --git a/locales/zh_Hans.json b/locales/zh_Hans.json index 78ba55133..0e971299f 100644 --- a/locales/zh_Hans.json +++ b/locales/zh_Hans.json @@ -226,7 +226,7 @@ "service_description_fail2ban": "防止来自互联网的暴力攻击和其他类型的攻击", "service_description_dovecot": "允许电子邮件客户端访问/获取电子邮件(通过IMAP和POP3)", "service_description_dnsmasq": "处理域名解析(DNS)", - "service_description_avahi-daemon": "允许您使用本地网络中的“ yunohost.local”访问服务器", + "service_description_mdns": "允许您使用本地网络中的“ yunohost.local”访问服务器", "service_started": "服务 '{service:s}' 已启动", "service_start_failed": "无法启动服务 '{service:s}'\n\n最近的服务日志:{logs:s}", "service_reloaded_or_restarted": "服务'{service:s}'已重新加载或重新启动", From ffc132f2c5192e393275250bb1073bd2ee499c7f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 12 Aug 2021 16:59:54 +0200 Subject: [PATCH 2700/3170] Configure mdns during yunohost's debian postinst init --- debian/postinst | 1 + 1 file changed, 1 insertion(+) diff --git a/debian/postinst b/debian/postinst index ecae9b258..8fc00bbe2 100644 --- a/debian/postinst +++ b/debian/postinst @@ -18,6 +18,7 @@ do_configure() { bash /usr/share/yunohost/hooks/conf_regen/46-nsswitch init bash /usr/share/yunohost/hooks/conf_regen/06-slapd init bash /usr/share/yunohost/hooks/conf_regen/15-nginx init + bash /usr/share/yunohost/hooks/conf_regen/37-mdns init fi else echo "Regenerating configuration, this might take a while..." From 4d0581bef21865903202a8cd911fd7dec0db7285 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 12 Aug 2021 17:19:45 +0200 Subject: [PATCH 2701/3170] mdns: Misc fixes after tests on the battefield --- data/hooks/conf_regen/37-mdns | 6 ++++-- src/yunohost/domain.py | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/data/hooks/conf_regen/37-mdns b/data/hooks/conf_regen/37-mdns index 903b41a0f..c85e43a9a 100755 --- a/data/hooks/conf_regen/37-mdns +++ b/data/hooks/conf_regen/37-mdns @@ -30,18 +30,20 @@ do_init_regen() { do_pre_regen() { pending_dir="$1" - cd /usr/share/yunohost/templates/dnsmasq + cd /usr/share/yunohost/templates/mdns + mkdir -p ${pending_dir}/etc/systemd/system/ cp yunomdns.service ${pending_dir}/etc/systemd/system/ getent passwd mdns &>/dev/null || useradd --no-create-home --shell /usr/sbin/nologin --system --user-group mdns + mkdir -p ${pending_dir}/etc/yunohost _generate_config > ${pending_dir}/etc/yunohost/mdns.yml } do_post_regen() { regen_conf_files="$1" - chown mdns:mdns ${pending_dir}/etc/yunohost/mdns.yml + chown mdns:mdns /etc/yunohost/mdns.yml # If we changed the systemd ynh-override conf if echo "$regen_conf_files" | sed 's/,/\n/g' | grep -q "^/etc/systemd/system/yunomdns.service$" diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index aaac3a995..bca701dc6 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -163,7 +163,7 @@ def domain_add(operation_logger, domain, dyndns=False): # because it's one of the major service, but in the long term we # should identify the root of this bug... _force_clear_hashes(["/etc/nginx/conf.d/%s.conf" % domain]) - regen_conf(names=["nginx", "metronome", "dnsmasq", "postfix", "rspamd"]) + regen_conf(names=["nginx", "metronome", "dnsmasq", "postfix", "rspamd", "mdns"]) app_ssowatconf() except Exception as e: @@ -290,7 +290,7 @@ def domain_remove(operation_logger, domain, remove_apps=False, force=False): "/etc/nginx/conf.d/%s.conf" % domain, new_conf=None, save=True ) - regen_conf(names=["nginx", "metronome", "dnsmasq", "postfix"]) + regen_conf(names=["nginx", "metronome", "dnsmasq", "postfix", "rspamd", "mdns"]) app_ssowatconf() hook_callback("post_domain_remove", args=[domain]) From a343490f304d9a3a0a8cc1e64e73a7eb83064fbe Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 12 Aug 2021 17:22:20 +0200 Subject: [PATCH 2702/3170] We shall not expose port 5353 publicly --- data/templates/yunohost/services.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/data/templates/yunohost/services.yml b/data/templates/yunohost/services.yml index c7690fc9c..c781d54aa 100644 --- a/data/templates/yunohost/services.yml +++ b/data/templates/yunohost/services.yml @@ -52,7 +52,6 @@ yunohost-firewall: test_status: iptables -S | grep "^-A INPUT" | grep " --dport" | grep -q ACCEPT category: security yunomdns: - needs_exposed_ports: [5353] category: mdns glances: null nsswitch: null From 212ea635df64ca8a2d2a9167529c52204d89c5ea Mon Sep 17 00:00:00 2001 From: tituspijean Date: Thu, 12 Aug 2021 15:52:25 +0000 Subject: [PATCH 2703/3170] [mdns] Fix service user --- data/templates/mdns/yunomdns.service | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/templates/mdns/yunomdns.service b/data/templates/mdns/yunomdns.service index 36d938035..ce2641b5d 100644 --- a/data/templates/mdns/yunomdns.service +++ b/data/templates/mdns/yunomdns.service @@ -3,8 +3,8 @@ Description=YunoHost mDNS service After=network.target [Service] -User=avahi -Group=avahi +User=mdns +Group=mdns Type=simple ExecStart=/usr/bin/yunomdns StandardOutput=syslog From f1444bc36ffea201890e6e676ef71459e20c30ba Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 12 Aug 2021 18:40:43 +0200 Subject: [PATCH 2704/3170] mdns: misc fixes for ip parsing --- bin/yunomdns | 53 ++++++++++++++++++++++++---------------------------- 1 file changed, 24 insertions(+), 29 deletions(-) diff --git a/bin/yunomdns b/bin/yunomdns index 3e3eea72f..be5a87be0 100755 --- a/bin/yunomdns +++ b/bin/yunomdns @@ -47,7 +47,7 @@ def _extract_inet(string, skip_netmask=False, skip_loopback=True): ip4_pattern = ( r"((25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}" ) - ip6_pattern = r"(((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)" + ip6_pattern = r"(((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::?((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)" ip4_pattern += r"/[0-9]{1,2})" if not skip_netmask else ")" ip6_pattern += r"/[0-9]{1,3})" if not skip_netmask else ")" result = {} @@ -74,14 +74,16 @@ def _extract_inet(string, skip_netmask=False, skip_loopback=True): # Helper command taken from Moulinette def get_network_interfaces(): + # Get network devices and their addresses (raw infos from 'ip addr') devices_raw = {} - output = check_output("ip addr show") - for d in re.split(r"^(?:[0-9]+: )", output, flags=re.MULTILINE): - # Extract device name (1) and its addresses (2) - m = re.match(r"([^\s@]+)(?:@[\S]+)?: (.*)", d, flags=re.DOTALL) - if m: - devices_raw[m.group(1)] = m.group(2) + output = check_output("ip --brief a").split("\n") + for line in output: + line = line.split() + iname = line[0] + ips = ' '.join(line[2:]) + + devices_raw[iname] = ips # Parse relevant informations for each of them devices = { @@ -122,25 +124,18 @@ if __name__ == '__main__': ips = [] # Human-readable IPs b_ips = [] # Binary-convered IPs - # Parse the IPs and prepare their binary version - addressed = False - try: - ip = interfaces[interface]['ipv4'].split('/')[0] - if len(ip)>0: addressed = True - ips.append(ip) - b_ips.append(socket.inet_pton(socket.AF_INET, ip)) - except: - pass - try: - ip = interfaces[interface]['ipv6'].split('/')[0] - if len(ip)>0: addressed = True - ips.append(ip) - b_ips.append(socket.inet_pton(socket.AF_INET6, ip)) - except: - pass + ipv4 = interfaces[interface]['ipv4'].split('/')[0] + if ipv4: + ips.append(ipv4) + b_ips.append(socket.inet_pton(socket.AF_INET, ipv4)) + + ipv6 = interfaces[interface]['ipv6'].split('/')[0] + if ipv6: + ips.append(ipv6) + b_ips.append(socket.inet_pton(socket.AF_INET6, ipv6)) # If at least one IP is listed - if addressed: + if ips: # Create a Zeroconf object, and store the ServiceInfos zc = Zeroconf(interfaces=ips) zcs[zc]=[] @@ -151,11 +146,11 @@ if __name__ == '__main__': else: # Create a ServiceInfo object for each .local domain zcs[zc].append(ServiceInfo( - type_='_device-info._tcp.local.', - name=interface+': '+d_domain+'._device-info._tcp.local.', - addresses=b_ips, - port=80, - server=d+'.', + type_='_device-info._tcp.local.', + name=interface+': '+d_domain+'._device-info._tcp.local.', + addresses=b_ips, + port=80, + server=d+'.', )) print('Adding '+d+' with addresses '+str(ips)+' on interface '+interface) From 7ca637d8f82f0a888d3d8af93950e0692b125821 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 12 Aug 2021 18:54:20 +0200 Subject: [PATCH 2705/3170] yaml.load -> yaml.safe_load --- bin/yunomdns | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bin/yunomdns b/bin/yunomdns index be5a87be0..862a1f477 100755 --- a/bin/yunomdns +++ b/bin/yunomdns @@ -1,7 +1,6 @@ #!/usr/bin/env python3 """ -WIP Pythonic declaration of mDNS .local domains for YunoHost """ @@ -101,7 +100,7 @@ if __name__ == '__main__': ### with open('/etc/yunohost/mdns.yml', 'r') as f: - config = yaml.load(f) or {} + config = yaml.safe_load(f) or {} updated = False required_fields = ["interfaces", "domains"] From 849ccb043a94bbc60c608cf12665e9453df03455 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 12 Aug 2021 19:13:26 +0200 Subject: [PATCH 2706/3170] mdns: diagnosis expects a 'data' key instead of 'results' --- data/hooks/diagnosis/12-dnsrecords.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py index 89816847d..49e39c775 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -65,14 +65,14 @@ class DNSRecordsDiagnoser(Diagnoser): if is_subdomain: yield dict( meta={"domain": domain, "category": "basic"}, - results={}, + data={}, status="WARNING", summary="diagnosis_domain_subdomain_localdomain", ) else: yield dict( meta={"domain": domain, "category": "basic"}, - results={}, + data={}, status="INFO", summary="diagnosis_domain_localdomain", ) From 7f3eeafbed74af010c31e46f8753c2a4bd165fdd Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 12 Aug 2021 19:40:36 +0200 Subject: [PATCH 2707/3170] mdns/diagnosis: following suggestion IRL: extend the no-check mecanism for .local to .onion and other special-use TLDs --- data/hooks/diagnosis/12-dnsrecords.py | 30 ++++++++++----------------- locales/en.json | 3 +-- 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py index 49e39c775..c29029de9 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -13,7 +13,7 @@ from yunohost.diagnosis import Diagnoser from yunohost.domain import domain_list, _build_dns_conf, _get_maindomain YNH_DYNDNS_DOMAINS = ["nohost.me", "noho.st", "ynh.fr"] - +SPECIAL_USE_TLDS = ["local", "localhost", "onion", "dev", "test"] class DNSRecordsDiagnoser(Diagnoser): @@ -29,9 +29,9 @@ class DNSRecordsDiagnoser(Diagnoser): for domain in all_domains: self.logger_debug("Diagnosing DNS conf for %s" % domain) is_subdomain = domain.split(".", 1)[1] in all_domains - is_localdomain = domain.endswith(".local") + is_specialusedomain = any(domain.endswith("." + tld) for tld in SPECIAL_USE_TLDS) for report in self.check_domain( - domain, domain == main_domain, is_subdomain=is_subdomain, is_localdomain=is_localdomain + domain, domain == main_domain, is_subdomain=is_subdomain, is_specialusedomain=is_specialusedomain ): yield report @@ -49,7 +49,7 @@ class DNSRecordsDiagnoser(Diagnoser): for report in self.check_expiration_date(domains_from_registrar): yield report - def check_domain(self, domain, is_main_domain, is_subdomain, is_localdomain): + def check_domain(self, domain, is_main_domain, is_subdomain, is_specialusedomain): expected_configuration = _build_dns_conf( domain, include_empty_AAAA_if_no_ipv6=True @@ -60,22 +60,14 @@ class DNSRecordsDiagnoser(Diagnoser): if is_subdomain: categories = ["basic"] - if is_localdomain: + if is_specialusedomain: categories = [] - if is_subdomain: - yield dict( - meta={"domain": domain, "category": "basic"}, - data={}, - status="WARNING", - summary="diagnosis_domain_subdomain_localdomain", - ) - else: - yield dict( - meta={"domain": domain, "category": "basic"}, - data={}, - status="INFO", - summary="diagnosis_domain_localdomain", - ) + yield dict( + meta={"domain": domain}, + data={}, + status="INFO", + summary="diagnosis_dns_specialusedomain", + ) for category in categories: diff --git a/locales/en.json b/locales/en.json index 2b8e8b32f..cad1c8dcb 100644 --- a/locales/en.json +++ b/locales/en.json @@ -183,6 +183,7 @@ "diagnosis_dns_discrepancy": "The following DNS record does not seem to follow the recommended configuration:
Type: {type}
Name: {name}
Current value: {current}
Expected value: {value}", "diagnosis_dns_point_to_doc": "Please check the documentation at https://yunohost.org/dns_config if you need help about configuring DNS records.", "diagnosis_dns_try_dyndns_update_force": "This domain's DNS configuration should automatically be managed by YunoHost. If that's not the case, you can try to force an update using yunohost dyndns update --force.", + "diagnosis_dns_specialusedomain": "Domain {domain} is based on a special-use top-level domain (TLD) and is therefore not expected to have actual DNS records.", "diagnosis_domain_expiration_not_found": "Unable to check the expiration date for some domains", "diagnosis_domain_not_found_details": "The domain {domain} doesn't exist in WHOIS database or is expired!", "diagnosis_domain_expiration_not_found_details": "The WHOIS information for domain {domain} doesn't seem to contain the information about the expiration date?", @@ -190,8 +191,6 @@ "diagnosis_domain_expiration_warning": "Some domains will expire soon!", "diagnosis_domain_expiration_error": "Some domains will expire VERY SOON!", "diagnosis_domain_expires_in": "{domain} expires in {days} days.", - "diagnosis_domain_localdomain": "Domain {domain}, with a .local TLD, is not expected to have DNS records as it can be discovered through mDNS.", - "diagnosis_domain_subdomain_localdomain": "Domain {domain} is a subdomain of a .local domain. Zeroconf/mDNS discovery only works with first-level domains.", "diagnosis_services_running": "Service {service} is running!", "diagnosis_services_conf_broken": "Configuration is broken for service {service}!", "diagnosis_services_bad_status": "Service {service} is {status} :(", From 66b036512bcf1912e32915a64f405d7f0d229341 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 12 Aug 2021 19:50:16 +0200 Subject: [PATCH 2708/3170] README: Update shields --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index aec5300e2..fa3c839c9 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,8 @@
-[![Build status](https://travis-ci.org/YunoHost/yunohost.svg?branch=stretch-unstable)](https://travis-ci.org/YunoHost/yunohost) -[![GitHub license](https://img.shields.io/github/license/YunoHost/yunohost)](https://github.com/YunoHost/yunohost/blob/stretch-unstable/LICENSE) +[![Build status](https://shields.io/gitlab/pipeline/yunohost/yunohost/dev)](https://gitlab.com/yunohost/yunohost/-/pipelines) +[![GitHub license](https://img.shields.io/github/license/YunoHost/yunohost)](https://github.com/YunoHost/yunohost/blob/dev/LICENSE) [![Mastodon Follow](https://img.shields.io/mastodon/follow/28084)](https://mastodon.social/@yunohost)
From e493b89d6a5eb2301e6663277aa03ac404329e87 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 12 Aug 2021 20:16:35 +0200 Subject: [PATCH 2709/3170] diagnosis: Zblerg, .dev is actually an actual TLD --- data/hooks/diagnosis/12-dnsrecords.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py index c29029de9..1db4af685 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -13,7 +13,8 @@ from yunohost.diagnosis import Diagnoser from yunohost.domain import domain_list, _build_dns_conf, _get_maindomain YNH_DYNDNS_DOMAINS = ["nohost.me", "noho.st", "ynh.fr"] -SPECIAL_USE_TLDS = ["local", "localhost", "onion", "dev", "test"] +SPECIAL_USE_TLDS = ["local", "localhost", "onion", "test"] + class DNSRecordsDiagnoser(Diagnoser): From b9e231241b53019a634bc0d43a80284732796611 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 13 Aug 2021 00:49:42 +0200 Subject: [PATCH 2710/3170] user imports: imported -> from_import, try to improve semantics --- src/yunohost/user.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 1037d417f..545d46a87 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -134,7 +134,7 @@ def user_create( password, mailbox_quota="0", mail=None, - imported=False + from_import=False ): from yunohost.domain import domain_list, _get_maindomain @@ -197,7 +197,7 @@ def user_create( if mail in aliases: raise YunohostValidationError("mail_unavailable") - if not imported: + if not from_import: operation_logger.start() # Get random UID/GID @@ -282,14 +282,14 @@ def user_create( hook_callback("post_user_create", args=[username, mail], env=env_dict) # TODO: Send a welcome mail to user - if not imported: + if not from_import: logger.success(m18n.n('user_created')) return {"fullname": fullname, "username": username, "mail": mail} @is_unit_operation([('username', 'user')]) -def user_delete(operation_logger, username, purge=False, imported=False): +def user_delete(operation_logger, username, purge=False, from_import=False): """ Delete user @@ -304,7 +304,7 @@ def user_delete(operation_logger, username, purge=False, imported=False): if username not in user_list()["users"]: raise YunohostValidationError("user_unknown", user=username) - if not imported: + if not from_import: operation_logger.start() user_group_update("all_users", remove=username, force=True, sync_perm=False) @@ -337,7 +337,7 @@ def user_delete(operation_logger, username, purge=False, imported=False): hook_callback('post_user_delete', args=[username, purge]) - if not imported: + if not from_import: logger.success(m18n.n('user_deleted')) @is_unit_operation([("username", "user")], exclude=["change_password"]) @@ -353,7 +353,7 @@ def user_update( add_mailalias=None, remove_mailalias=None, mailbox_quota=None, - imported=False + from_import=False ): """ Update user informations @@ -502,7 +502,7 @@ def user_update( new_attr_dict["mailuserquota"] = [mailbox_quota] env_dict["YNH_USER_MAILQUOTA"] = mailbox_quota - if not imported: + if not from_import: operation_logger.start() try: @@ -513,7 +513,7 @@ def user_update( # Trigger post_user_update hooks hook_callback("post_user_update", env=env_dict) - if not imported: + if not from_import: app_ssowatconf() logger.success(m18n.n('user_updated')) return user_info(username) @@ -764,7 +764,7 @@ def user_import(operation_logger, csvfile, update=False, delete=False): # If the user is in this group (and it's not the primary group), # remove the member from the group if user['username'] != group and user['username'] in infos["members"]: - user_group_update(group, remove=user['username'], sync_perm=False, imported=True) + user_group_update(group, remove=user['username'], sync_perm=False, from_import=True) user_update(user['username'], user['firstname'], user['lastname'], @@ -773,17 +773,17 @@ def user_import(operation_logger, csvfile, update=False, delete=False): mail=user['mail'], add_mailalias=user['mail-alias'], remove_mailalias=remove_alias, remove_mailforward=remove_forward, - add_mailforward=user['mail-forward'], imported=True) + add_mailforward=user['mail-forward'], from_import=True) for group in user['groups']: - user_group_update(group, add=user['username'], sync_perm=False, imported=True) + user_group_update(group, add=user['username'], sync_perm=False, from_import=True) users = user_list(CSV_FIELDNAMES)['users'] operation_logger.start() # We do delete and update before to avoid mail uniqueness issues for user in actions['deleted']: try: - user_delete(user, purge=True, imported=True) + user_delete(user, purge=True, from_import=True) result['deleted'] += 1 except YunohostError as e: on_failure(user, e) @@ -802,7 +802,7 @@ def user_import(operation_logger, csvfile, update=False, delete=False): user_create(user['username'], user['firstname'], user['lastname'], user['domain'], user['password'], - user['mailbox-quota'], imported=True) + user['mailbox-quota'], from_import=True) update(user) result['created'] += 1 except YunohostError as e: @@ -1006,7 +1006,7 @@ def user_group_update( remove=None, force=False, sync_perm=True, - imported=False + from_import=False ): """ Update user informations @@ -1076,7 +1076,7 @@ def user_group_update( ] if set(new_group) != set(current_group): - if not imported: + if not from_import: operation_logger.start() ldap = _get_ldap_interface() try: @@ -1090,7 +1090,7 @@ def user_group_update( if sync_perm: permission_sync_to_user() - if not imported: + if not from_import: if groupname != "all_users": logger.success(m18n.n("group_updated", group=groupname)) else: From 5c9fd158d9b31c83c4c9975e8f4609647f9fe17d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 13 Aug 2021 00:52:42 +0200 Subject: [PATCH 2711/3170] user imports: more attempts to improve semantics --- src/yunohost/user.py | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 545d46a87..dfa71708a 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -749,34 +749,34 @@ def user_import(operation_logger, csvfile, update=False, delete=False): result['errors'] += 1 logger.error(user + ': ' + str(exception)) - def update(user, info=False): + def update(new_infos, old_infos=False): remove_alias = None remove_forward = None if info: - user['mail'] = None if info['mail'] == user['mail'] else user['mail'] - remove_alias = list(set(info['mail-alias']) - set(user['mail-alias'])) - remove_forward = list(set(info['mail-forward']) - set(user['mail-forward'])) - user['mail-alias'] = list(set(user['mail-alias']) - set(info['mail-alias'])) - user['mail-forward'] = list(set(user['mail-forward']) - set(info['mail-forward'])) + new_infos['mail'] = None if old_infos['mail'] == new_infos['mail'] else new_infos['mail'] + remove_alias = list(set(old_infos['mail-alias']) - set(new_infos['mail-alias'])) + remove_forward = list(set(old_infos['mail-forward']) - set(new_infos['mail-forward'])) + new_infos['mail-alias'] = list(set(new_infos['mail-alias']) - set(old_infos['mail-alias'])) + new_infos['mail-forward'] = list(set(new_infos['mail-forward']) - set(old_infos['mail-forward'])) for group, infos in user_group_list()["groups"].items(): if group == "all_users": continue # If the user is in this group (and it's not the primary group), # remove the member from the group - if user['username'] != group and user['username'] in infos["members"]: - user_group_update(group, remove=user['username'], sync_perm=False, from_import=True) + if new_infos['username'] != group and new_infos['username'] in infos["members"]: + user_group_update(group, remove=new_infos['username'], sync_perm=False, from_import=True) - user_update(user['username'], - user['firstname'], user['lastname'], - user['mail'], user['password'], - mailbox_quota=user['mailbox-quota'], - mail=user['mail'], add_mailalias=user['mail-alias'], - remove_mailalias=remove_alias, - remove_mailforward=remove_forward, - add_mailforward=user['mail-forward'], from_import=True) + user_update(new_infos['username'], + new_infos['firstname'], new_infos['lastname'], + new_infos['mail'], new_infos['password'], + mailbox_quota=new_infos['mailbox-quota'], + mail=new_infos['mail'], add_mailalias=new_infos['mail-alias'], + remove_mailalias=remove_alias, + remove_mailforward=remove_forward, + add_mailforward=new_infos['mail-forward'], from_import=True) - for group in user['groups']: - user_group_update(group, add=user['username'], sync_perm=False, from_import=True) + for group in new_infos['groups']: + user_group_update(group, add=new_infos['username'], sync_perm=False, from_import=True) users = user_list(CSV_FIELDNAMES)['users'] operation_logger.start() @@ -809,8 +809,6 @@ def user_import(operation_logger, csvfile, update=False, delete=False): on_failure(user['username'], e) progress("Creation") - - permission_sync_to_user() app_ssowatconf() From b1102ba56e29f311f400648c9f0b6ff27394985a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 13 Aug 2021 01:10:59 +0200 Subject: [PATCH 2712/3170] user imports: misc formating, comments --- locales/en.json | 2 +- src/yunohost/user.py | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/locales/en.json b/locales/en.json index f8b26c0b1..5b93dbb9f 100644 --- a/locales/en.json +++ b/locales/en.json @@ -638,7 +638,7 @@ "user_import_partial_failed": "The users import operation partially failed", "user_import_failed": "The users import operation completely failed", "user_import_nothing_to_do": "No user needs to be imported", - "user_import_success": "Users have been imported", + "user_import_success": "Users successfully imported", "yunohost_already_installed": "YunoHost is already installed", "yunohost_configured": "YunoHost is now configured", "yunohost_installing": "Installing YunoHost...", diff --git a/src/yunohost/user.py b/src/yunohost/user.py index ca226f654..459b0decf 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -96,11 +96,11 @@ def user_list(fields=None): users = {} if not fields: - fields = ['username', 'fullname', 'mail', 'mailbox-quota', 'shell'] + fields = ['username', 'fullname', 'mail', 'mailbox-quota'] for field in fields: if field in ldap_attrs: - attrs |= set([ldap_attrs[field]]) + attrs.add(ldap_attrs[field]) else: raise YunohostError('field_invalid', field) @@ -252,7 +252,7 @@ def user_create( # Attempt to create user home folder subprocess.check_call(["mkhomedir_helper", username]) except subprocess.CalledProcessError: - home = '/home/{0}'.format(username) + home = f'/home/{username}' if not os.path.isdir(home): logger.warning(m18n.n('user_home_creation_failed', home=home), exc_info=1) @@ -427,8 +427,10 @@ def user_update( main_domain = _get_maindomain() aliases = [alias + main_domain for alias in FIRST_ALIASES] + # If the requested mail address is already as main address or as an alias by this user if mail in user['mail']: user['mail'].remove(mail) + # Othewise, check that this mail address is not already used by this user else: try: ldap.validate_uniqueness({'mail': mail}) @@ -445,6 +447,7 @@ def user_update( if not isinstance(add_mailalias, list): add_mailalias = [add_mailalias] for mail in add_mailalias: + # (c.f. similar stuff as before) if mail in user["mail"]: user["mail"].remove(mail) else: @@ -647,7 +650,7 @@ def user_import(operation_logger, csvfile, update=False, delete=False): Import users from CSV Keyword argument: - csv -- CSV file with columns username;firstname;lastname;password;mailbox_quota;mail;alias;forward;groups + csvfile -- CSV file with columns username;firstname;lastname;password;mailbox_quota;mail;alias;forward;groups """ @@ -655,6 +658,7 @@ def user_import(operation_logger, csvfile, update=False, delete=False): from moulinette.utils.text import random_ascii from yunohost.permission import permission_sync_to_user from yunohost.app import app_ssowatconf + # Pre-validate data and prepare what should be done actions = { 'created': [], From 619b26f73c2356b5d256909c84142c64a8b06020 Mon Sep 17 00:00:00 2001 From: ljf Date: Fri, 13 Aug 2021 13:38:06 +0200 Subject: [PATCH 2713/3170] [fix] tons of things --- data/helpers.d/configpanel | 110 ++++++++++++++++++++++--------------- 1 file changed, 65 insertions(+), 45 deletions(-) diff --git a/data/helpers.d/configpanel b/data/helpers.d/configpanel index 83130cfe6..5ab199aea 100644 --- a/data/helpers.d/configpanel +++ b/data/helpers.d/configpanel @@ -99,13 +99,13 @@ ynh_value_set() { } _ynh_panel_get() { - + set +x # From settings local params_sources params_sources=`python3 << EOL import toml from collections import OrderedDict -with open("/etc/yunohost/apps/vpnclient/config_panel.toml", "r") as f: +with open("../config_panel.toml", "r") as f: file_content = f.read() loaded_toml = toml.loads(file_content, _dict=OrderedDict) @@ -114,20 +114,23 @@ for panel_name,panel in loaded_toml.items(): for section_name, section in panel.items(): if isinstance(section, dict): for name, param in section.items(): - if isinstance(param, dict) and param.get('source', '') == 'settings': + if isinstance(param, dict) and param.get('type', 'string') not in ['info', 'warning', 'error']: print("%s.%s.%s=%s" %(panel_name, section_name, name, param.get('source', 'settings'))) EOL ` - for param_source in params_sources + for param_source in $params_sources do local _dot_setting=$(echo "$param_source" | cut -d= -f1) - local _snake_setting="YNH_CONFIG_$(ynh_lowerdot_to_uppersnake $dot_setting)" + local _snake_setting="YNH_CONFIG_$(ynh_lowerdot_to_uppersnake $_dot_setting)" local short_setting=$(echo "$_dot_setting" | cut -d. -f3) local getter="get__${short_setting}" local source="$(echo $param_source | cut -d= -f2)" + sources[${short_setting}]="$source" + file_hash[${short_setting}]="" + dot_settings[${short_setting}]="${_dot_setting}" # Get value from getter if exists - if type $getter | grep -q '^function$' 2>/dev/null; then + if type -t $getter 2>/dev/null | grep -q '^function$' 2>/dev/null; then old[$short_setting]="$($getter)" # Get value from app settings @@ -144,38 +147,43 @@ EOL # Specific case for files (all content of the file is the source) else old[$short_setting]="$source" + file_hash[$short_setting]="true" fi - + set +u + new[$short_setting]="${!_snake_setting}" + set -u done + set -x } _ynh_panel_apply() { - for short_setting in "${!dot_settings[@]}" + for short_setting in "${!old[@]}" do local setter="set__${short_setting}" - local source="$sources[$short_setting]" - - # Apply setter if exists - if type $setter | grep -q '^function$' 2>/dev/null; then - $setter + local source="${sources[$short_setting]}" + if [ "${changed[$short_setting]}" == "true" ] ; then + # Apply setter if exists + if type -t $setter 2>/dev/null | grep -q '^function$' 2>/dev/null; then + $setter - # Copy file in right place - elif [[ "$source" == "settings" ]] ; then - ynh_app_setting_set $app $short_setting "$new[$short_setting]" - - # Get value from a kind of key/value file - elif [[ "$source" == *":"* ]] - then - local source_key="$(echo "$source" | cut -d: -f1)" - source_key=${source_key:-$short_setting} - local source_file="$(echo "$source" | cut -d: -f2)" - ynh_value_set --file="${source_file}" --key="${source_key}" --value="$new[$short_setting]" + # Copy file in right place + elif [[ "$source" == "settings" ]] ; then + ynh_app_setting_set $app $short_setting "${new[$short_setting]}" + + # Get value from a kind of key/value file + elif [[ "$source" == *":"* ]] + then + local source_key="$(echo "$source" | cut -d: -f1)" + source_key=${source_key:-$short_setting} + local source_file="$(echo "$source" | cut -d: -f2)" + ynh_value_set --file="${source_file}" --key="${source_key}" --value="${new[$short_setting]}" - # Specific case for files (all content of the file is the source) - else - cp "$new[$short_setting]" "$source" + # Specific case for files (all content of the file is the source) + else + cp "${new[$short_setting]}" "$source" + fi fi done } @@ -189,24 +197,32 @@ _ynh_panel_show() { } _ynh_panel_validate() { + set +x # Change detection local is_error=true #for changed_status in "${!changed[@]}" - for short_setting in "${!dot_settings[@]}" + for short_setting in "${!old[@]}" do - #TODO file hash - file_hash[$setting]=$(sha256sum "$_source" | cut -d' ' -f1) - file_hash[$form_setting]=$(sha256sum "${!form_setting}" | cut -d' ' -f1) - if [[ "${file_hash[$setting]}" != "${file_hash[$form_setting]}" ]] - then - changed[$setting]=true - fi - if [[ "$new[$short_setting]" == "$old[$short_setting]" ]] - then - changed[$short_setting]=false + changed[$short_setting]=false + if [ ! -z "${file_hash[${short_setting}]}" ] ; then + file_hash[old__$short_setting]="" + file_hash[new__$short_setting]="" + if [ -f "${old[$short_setting]}" ] ; then + file_hash[old__$short_setting]=$(sha256sum "${old[$short_setting]}" | cut -d' ' -f1) + fi + if [ -f "${new[$short_setting]}" ] ; then + file_hash[new__$short_setting]=$(sha256sum "${new[$short_setting]}" | cut -d' ' -f1) + if [[ "${file_hash[old__$short_setting]}" != "${file_hash[new__$short_setting]}" ]] + then + changed[$short_setting]=true + fi + fi else - changed[$short_setting]=true - is_error=false + if [[ "${new[$short_setting]}" != "${old[$short_setting]}" ]] + then + changed[$short_setting]=true + is_error=false + fi fi done @@ -214,10 +230,10 @@ _ynh_panel_validate() { if [[ "$is_error" == "false" ]] then - for short_setting in "${!dot_settings[@]}" + for short_setting in "${!old[@]}" do local result="" - if type validate__$short_setting | grep -q '^function$' 2>/dev/null; then + if type -t validate__$short_setting | grep -q '^function$' 2>/dev/null; then result="$(validate__$short_setting)" fi if [ -n "$result" ] @@ -233,6 +249,7 @@ _ynh_panel_validate() { then ynh_die fi + set -x } @@ -253,9 +270,12 @@ ynh_panel_apply() { } ynh_panel_run() { - declare -A old=() - declare -A changed=() - declare -A file_hash=() + declare -Ag old=() + declare -Ag new=() + declare -Ag changed=() + declare -Ag file_hash=() + declare -Ag sources=() + declare -Ag dot_settings=() ynh_panel_get case $1 in From f0590907c9e2eebe46d28146698387a33a8aea76 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Sat, 14 Aug 2021 11:44:52 +0200 Subject: [PATCH 2714/3170] fix ynh_permission_has_user --- data/helpers.d/permission | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/permission b/data/helpers.d/permission index a5c09cded..c04b4145b 100644 --- a/data/helpers.d/permission +++ b/data/helpers.d/permission @@ -362,8 +362,17 @@ ynh_permission_has_user() { return 1 fi - yunohost user permission info "$app.$permission" --output-as json --quiet \ - | jq -e --arg user $user '.corresponding_users | index($user)' >/dev/null + # Check both allowed and corresponding_users sections in the json + for section in "allowed" "corresponding_users" + do + if yunohost user permission info "$app.$permission" --output-as json --quiet \ + | jq -e --arg user $user --arg section $section '.[$section] | index($user)' >/dev/null + then + return 0 + fi + done + + return 1 } # Check if a legacy permissions exist From 596d05ae81d24712c87ec0de72f8deb8248bca9a Mon Sep 17 00:00:00 2001 From: ljf Date: Sat, 14 Aug 2021 14:38:45 +0200 Subject: [PATCH 2715/3170] [fix] Missing name or bad format management --- src/yunohost/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 49033d8b4..cf218823f 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -2145,7 +2145,7 @@ def _get_app_config_panel(app_id): for key, value in panels: panel = { "id": key, - "name": value["name"], + "name": value.get("name", ""), "sections": [], } @@ -2158,7 +2158,7 @@ def _get_app_config_panel(app_id): for section_key, section_value in sections: section = { "id": section_key, - "name": section_value["name"], + "name": section_value.get("name", ""), "options": [], } From 781bc1cf7fdf3d3de8e1d7eddd9a9562d2149e39 Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Sat, 14 Aug 2021 16:38:21 +0200 Subject: [PATCH 2716/3170] Wording Co-authored-by: Alexandre Aubin --- data/actionsmap/yunohost.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 91e369dfe..64cef606d 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -222,7 +222,7 @@ user: type: open -u: full: --update - help: Update all existing users contained in the csv file (by default those users are ignored) + help: Update all existing users contained in the csv file (by default existing users are ignored) action: store_true -d: full: --delete From 2e745d2806dfbe416a03cd60197d3175106d3084 Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Sat, 14 Aug 2021 16:38:55 +0200 Subject: [PATCH 2717/3170] Wording Co-authored-by: Alexandre Aubin --- data/actionsmap/yunohost.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 64cef606d..817adeb92 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -226,7 +226,7 @@ user: action: store_true -d: full: --delete - help: Delete all existing users that are not contained in the csv file (by default those users are ignored) + help: Delete all existing users that are not contained in the csv file (by default existing users are kept) action: store_true subcategories: From 61bc676552a8b388beb5d49a4a0187c695b7482d Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Sat, 14 Aug 2021 16:41:58 +0200 Subject: [PATCH 2718/3170] [enh] Add a comment --- src/yunohost/log.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/yunohost/log.py b/src/yunohost/log.py index 10ac097f5..f9f9334fb 100644 --- a/src/yunohost/log.py +++ b/src/yunohost/log.py @@ -372,6 +372,8 @@ def is_unit_operation( for field in exclude: if field in context: context.pop(field, None) + + # Manage file or stream for field, value in context.items(): if isinstance(value, IOBase): try: From 16f564622ee7fe2ff0ee8aa1b9916b1cc7d83e6b Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Sat, 14 Aug 2021 16:46:48 +0200 Subject: [PATCH 2719/3170] [enh] Remove uneeded comment --- src/yunohost/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 459b0decf..11e82146a 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -46,7 +46,7 @@ logger = getActionLogger("yunohost.user") CSV_FIELDNAMES = [u'username', u'firstname', u'lastname', u'password', u'mailbox-quota', u'mail', u'mail-alias', u'mail-forward', u'groups'] VALIDATORS = { 'username': r'^[a-z0-9_]+$', - 'firstname': r'^([^\W\d_]{1,30}[ ,.\'-]{0,3})+$', #FIXME Merge first and lastname and support more name (arabish, chinese...) + 'firstname': r'^([^\W\d_]{1,30}[ ,.\'-]{0,3})+$', 'lastname': r'^([^\W\d_]{1,30}[ ,.\'-]{0,3})+$', 'password': r'^|(.{3,})$', 'mail': r'^([\w.-]+@([^\W_A-Z]+([-]*[^\W_A-Z]+)*\.)+((xn--)?[^\W_]{2,}))$', From c2460d7526069ed48ff2b0bd5a35c8c3f2086c76 Mon Sep 17 00:00:00 2001 From: sagessylu Date: Sun, 15 Aug 2021 21:41:32 +0200 Subject: [PATCH 2720/3170] Add purge option to yunohost app remove --- data/actionsmap/yunohost.yml | 6 +++++- src/yunohost/app.py | 6 ++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 5df1c0877..bd9207528 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -673,7 +673,11 @@ app: api: DELETE /apps/ arguments: app: - help: App to delete + help: App to remove + -p: + full: --purge + help: Remove with all app data + action: store_true ### app_upgrade() upgrade: diff --git a/src/yunohost/app.py b/src/yunohost/app.py index a48400a8e..d6b459264 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1187,12 +1187,13 @@ def dump_app_log_extract_for_debugging(operation_logger): @is_unit_operation() -def app_remove(operation_logger, app): +def app_remove(operation_logger, app, purge=False): """ Remove app - Keyword argument: + Keyword arguments: app -- App(s) to delete + purge -- Remove with all app data """ from yunohost.hook import hook_exec, hook_remove, hook_callback @@ -1230,6 +1231,7 @@ def app_remove(operation_logger, app): 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_PURGE"] = purge operation_logger.extra.update({"env": env_dict}) operation_logger.flush() From cec4971cac0aa567149349e2c4f27efbd2f45387 Mon Sep 17 00:00:00 2001 From: sagessylu Date: Sun, 15 Aug 2021 22:02:13 +0200 Subject: [PATCH 2721/3170] Add no-safety-backup option to yunohost app upgrade --- data/actionsmap/yunohost.yml | 4 ++++ src/yunohost/app.py | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 5df1c0877..43955c017 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -693,6 +693,10 @@ app: full: --force help: Force the update, even though the app is up to date action: store_true + -b: + full: --no-safety-backup + help: Disable the safety backup during upgrade + action: store_true ### app_change_url() change-url: diff --git a/src/yunohost/app.py b/src/yunohost/app.py index a48400a8e..1841b2a07 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -502,7 +502,7 @@ def app_change_url(operation_logger, app, domain, path): hook_callback("post_app_change_url", env=env_dict) -def app_upgrade(app=[], url=None, file=None, force=False): +def app_upgrade(app=[], url=None, file=None, force=False, no_safety_backup=False): """ Upgrade app @@ -510,6 +510,7 @@ def app_upgrade(app=[], url=None, file=None, force=False): file -- Folder or tarball for upgrade app -- App(s) to upgrade (default all) url -- Git url to fetch for upgrade + no_safety_backup -- Disable the safety backup during upgrade """ from packaging import version @@ -618,6 +619,7 @@ def app_upgrade(app=[], url=None, file=None, force=False): env_dict["YNH_APP_UPGRADE_TYPE"] = upgrade_type env_dict["YNH_APP_MANIFEST_VERSION"] = str(app_new_version) env_dict["YNH_APP_CURRENT_VERSION"] = str(app_current_version) + env_dict["YNH_APP_NO_BACKUP_UPGRADE"] = no_safety_backup # We'll check that the app didn't brutally edit some system configuration manually_modified_files_before_install = manually_modified_files() From b39201a1933e4f1cf06c0f668a581c032a39e993 Mon Sep 17 00:00:00 2001 From: mifegui Date: Sun, 15 Aug 2021 19:22:04 +0000 Subject: [PATCH 2722/3170] Translated using Weblate (Portuguese) Currently translated at 8.1% (52 of 637 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/pt/ --- locales/pt.json | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/locales/pt.json b/locales/pt.json index 72d983a39..82edbf349 100644 --- a/locales/pt.json +++ b/locales/pt.json @@ -1,7 +1,7 @@ { "action_invalid": "Acção Inválida '{action}'", "admin_password": "Senha de administração", - "admin_password_change_failed": "Não é possível alterar a senha", + "admin_password_change_failed": "Não foi possível alterar a senha", "admin_password_changed": "A senha da administração foi alterada", "app_already_installed": "{app} já está instalada", "app_extraction_failed": "Não foi possível extrair os ficheiros para instalação", @@ -108,12 +108,12 @@ "backup_hook_unknown": "Gancho de backup '{hook}' desconhecido", "backup_nothings_done": "Não há nada para guardar", "backup_output_directory_forbidden": "Diretório de saída proibido. Os backups não podem ser criados em /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var ou /home/yunohost.backup/archives subpastas", - "app_already_installed_cant_change_url": "Este aplicativo já está instalado. A URL não pode ser alterada apenas por esta função. Olhe para o `app changeurl` se estiver disponível.", + "app_already_installed_cant_change_url": "Este aplicativo já está instalado. A URL não pode ser alterada apenas por esta função. Confira em `app changeurl` se está disponível.", "app_already_up_to_date": "{app} já está atualizado", - "app_argument_choice_invalid": "Escolha inválida para o argumento '{name}', deve ser um dos {choices}", - "app_argument_invalid": "Valor inválido de argumento '{name}': {error}", + "app_argument_choice_invalid": "Use uma das opções '{choices}' para o argumento '{name}'", + "app_argument_invalid": "Escolha um valor válido para o argumento '{name}': {error}", "app_argument_required": "O argumento '{name}' é obrigatório", - "app_change_url_failed_nginx_reload": "Falha ao reiniciar o nginx. Aqui está o retorno de 'nginx -t':\n{nginx_errors}", + "app_change_url_failed_nginx_reload": "Não foi possível reiniciar o nginx. Aqui está o retorno de 'nginx -t':\n{nginx_errors}", "app_location_unavailable": "Esta url não está disponível ou está em conflito com outra aplicação já instalada", "app_upgrade_app_name": "Atualizando aplicação {app}…", "app_upgrade_some_app_failed": "Não foi possível atualizar algumas aplicações", @@ -129,5 +129,12 @@ "app_change_url_identical_domains": "O antigo e o novo domínio / url_path são idênticos ('{domain}{path}'), nada para fazer.", "password_too_simple_1": "A senha precisa ter pelo menos 8 caracteres", "admin_password_too_long": "Escolha uma senha que contenha menos de 127 caracteres", - "aborting": "Abortando." -} \ No newline at end of file + "aborting": "Abortando.", + "app_change_url_no_script": "A aplicação '{app_name}' ainda não permite modificar a URL. Talvez devesse atualizá-la.", + "app_argument_password_no_default": "Erro ao interpretar argumento da senha '{name}': O argumento da senha não pode ter um valor padrão por segurança", + "app_action_cannot_be_ran_because_required_services_down": "Estes serviços devem estar funcionado para executar esta ação: {services}. Tente reiniciá-los para continuar (e possivelmente investigar o porquê de não estarem funcionado).", + "app_action_broke_system": "Esta ação parece ter quebrado estes serviços importantes: {services}", + "already_up_to_date": "Nada a ser feito. Tudo já está atualizado.", + "additional_urls_already_removed": "A URL adicional '{url}'já está removida para a permissão '{permission}'", + "additional_urls_already_added": "A URL adicional '{url}' já está adicionada para a permissão '{permission}'" +} From ccb6dc54b1091f51e4a3689faf82f686ba4d7d90 Mon Sep 17 00:00:00 2001 From: ljf Date: Tue, 17 Aug 2021 17:26:00 +0200 Subject: [PATCH 2723/3170] [fix] Avoid warning and use safeloader --- data/actionsmap/yunohost_completion.py | 2 +- data/helpers.d/setting | 4 ++-- data/hooks/conf_regen/01-yunohost | 4 ++-- doc/generate_manpages.py | 2 +- src/yunohost/app.py | 4 ++-- src/yunohost/firewall.py | 2 +- src/yunohost/regenconf.py | 2 +- src/yunohost/service.py | 2 +- tests/test_actionmap.py | 2 +- tests/test_i18n_keys.py | 2 +- 10 files changed, 13 insertions(+), 13 deletions(-) diff --git a/data/actionsmap/yunohost_completion.py b/data/actionsmap/yunohost_completion.py index bc32028d3..3891aee9c 100644 --- a/data/actionsmap/yunohost_completion.py +++ b/data/actionsmap/yunohost_completion.py @@ -32,7 +32,7 @@ def get_dict_actions(OPTION_SUBTREE, category): with open(ACTIONSMAP_FILE, "r") as stream: # Getting the dictionary containning what actions are possible per category - OPTION_TREE = yaml.load(stream) + OPTION_TREE = yaml.safe_load(stream) CATEGORY = [ category for category in OPTION_TREE.keys() if not category.startswith("_") diff --git a/data/helpers.d/setting b/data/helpers.d/setting index 2950b3829..66bce9717 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -86,7 +86,7 @@ key, value = os.environ['KEY'], os.environ.get('VALUE', None) setting_file = "/etc/yunohost/apps/%s/settings.yml" % app assert os.path.exists(setting_file), "Setting file %s does not exists ?" % setting_file with open(setting_file) as f: - settings = yaml.load(f) + settings = yaml.safe_load(f) if action == "get": if key in settings: print(settings[key]) @@ -96,7 +96,7 @@ else: del settings[key] elif action == "set": if key in ['redirected_urls', 'redirected_regex']: - value = yaml.load(value) + value = yaml.safe_load(value) settings[key] = value else: raise ValueError("action should either be get, set or delete") diff --git a/data/hooks/conf_regen/01-yunohost b/data/hooks/conf_regen/01-yunohost index 8ef398f1d..1a10a6954 100755 --- a/data/hooks/conf_regen/01-yunohost +++ b/data/hooks/conf_regen/01-yunohost @@ -212,10 +212,10 @@ import yaml with open('services.yml') as f: - new_services = yaml.load(f) + new_services = yaml.safe_load(f) with open('/etc/yunohost/services.yml') as f: - services = yaml.load(f) or {} + services = yaml.safe_load(f) or {} updated = False diff --git a/doc/generate_manpages.py b/doc/generate_manpages.py index f681af7dd..8691bab37 100644 --- a/doc/generate_manpages.py +++ b/doc/generate_manpages.py @@ -33,7 +33,7 @@ def ordered_yaml_load(stream): yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, lambda loader, node: OrderedDict(loader.construct_pairs(node)), ) - return yaml.load(stream, OrderedLoader) + return yaml.safe_load(stream, OrderedLoader) def main(): diff --git a/src/yunohost/app.py b/src/yunohost/app.py index a48400a8e..6a2640cee 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1518,7 +1518,7 @@ def app_setting(app, key, value=None, delete=False): # SET else: if key in ["redirected_urls", "redirected_regex"]: - value = yaml.load(value) + value = yaml.safe_load(value) app_settings[key] = value _set_app_settings(app, app_settings) @@ -2175,7 +2175,7 @@ def _get_app_settings(app_id): ) try: with open(os.path.join(APPS_SETTING_PATH, app_id, "settings.yml")) as f: - settings = yaml.load(f) + settings = yaml.safe_load(f) # If label contains unicode char, this may later trigger issues when building strings... # FIXME: this should be propagated to read_yaml so that this fix applies everywhere I think... settings = {k: v for k, v in settings.items()} diff --git a/src/yunohost/firewall.py b/src/yunohost/firewall.py index 9850defa5..4be6810ec 100644 --- a/src/yunohost/firewall.py +++ b/src/yunohost/firewall.py @@ -179,7 +179,7 @@ def firewall_list(raw=False, by_ip_version=False, list_forwarded=False): """ with open(FIREWALL_FILE) as f: - firewall = yaml.load(f) + firewall = yaml.safe_load(f) if raw: return firewall diff --git a/src/yunohost/regenconf.py b/src/yunohost/regenconf.py index 924818e44..0608bcf8c 100644 --- a/src/yunohost/regenconf.py +++ b/src/yunohost/regenconf.py @@ -444,7 +444,7 @@ def _get_regenconf_infos(): """ try: with open(REGEN_CONF_FILE, "r") as f: - return yaml.load(f) + return yaml.safe_load(f) except Exception: return {} diff --git a/src/yunohost/service.py b/src/yunohost/service.py index e6e960a57..912662600 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -670,7 +670,7 @@ def _get_services(): """ try: with open("/etc/yunohost/services.yml", "r") as f: - services = yaml.load(f) or {} + services = yaml.safe_load(f) or {} except Exception: return {} diff --git a/tests/test_actionmap.py b/tests/test_actionmap.py index bf6755979..0b8abb152 100644 --- a/tests/test_actionmap.py +++ b/tests/test_actionmap.py @@ -2,4 +2,4 @@ import yaml def test_yaml_syntax(): - yaml.load(open("data/actionsmap/yunohost.yml")) + yaml.safe_load(open("data/actionsmap/yunohost.yml")) diff --git a/tests/test_i18n_keys.py b/tests/test_i18n_keys.py index 2ad56a34e..7b5ad1956 100644 --- a/tests/test_i18n_keys.py +++ b/tests/test_i18n_keys.py @@ -108,7 +108,7 @@ def find_expected_string_keys(): yield m # Keys for the actionmap ... - for category in yaml.load(open("data/actionsmap/yunohost.yml")).values(): + for category in yaml.safe_load(open("data/actionsmap/yunohost.yml")).values(): if "actions" not in category.keys(): continue for action in category["actions"].values(): From 5518931c029f60aaaffc9e38e89b20b7574f8b3b Mon Sep 17 00:00:00 2001 From: ljf Date: Tue, 17 Aug 2021 17:33:23 +0200 Subject: [PATCH 2724/3170] [fix] OrderedLoader from SafeLoader --- doc/generate_manpages.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/generate_manpages.py b/doc/generate_manpages.py index 8691bab37..fa042fb17 100644 --- a/doc/generate_manpages.py +++ b/doc/generate_manpages.py @@ -26,14 +26,14 @@ ACTIONSMAP_FILE = os.path.join(THIS_SCRIPT_DIR, "../data/actionsmap/yunohost.yml def ordered_yaml_load(stream): - class OrderedLoader(yaml.Loader): + class OrderedLoader(yaml.SafeLoader): pass OrderedLoader.add_constructor( yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, lambda loader, node: OrderedDict(loader.construct_pairs(node)), ) - return yaml.safe_load(stream, OrderedLoader) + return yaml.load(stream, OrderedLoader) def main(): From d8cdc20e0ec4f3af992d3e7f22ed1d9218bd38c2 Mon Sep 17 00:00:00 2001 From: ljf Date: Mon, 11 May 2020 19:13:10 +0200 Subject: [PATCH 2725/3170] [wip] Allow file upload from config-panel --- src/yunohost/app.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index a48400a8e..a8cd6aa40 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -33,6 +33,7 @@ import re import subprocess import glob import urllib.parse +import base64 import tempfile from collections import OrderedDict @@ -1874,6 +1875,7 @@ def app_config_apply(operation_logger, app, args): } args = dict(urllib.parse.parse_qsl(args, keep_blank_values=True)) if args else {} + upload_dir = None for tab in config_panel.get("panel", []): tab_id = tab["id"] # this makes things easier to debug on crash for section in tab.get("sections", []): @@ -1885,6 +1887,23 @@ def app_config_apply(operation_logger, app, args): ).upper() if generated_name in args: + # Upload files from API + # A file arg contains a string with "FILENAME:BASE64_CONTENT" + if option["type"] == "file" and msettings.get('interface') == 'api': + if upload_dir is None: + upload_dir = tempfile.mkdtemp(prefix='tmp_configpanel_') + filename, args[generated_name] = args[generated_name].split(':') + logger.debug("Save uploaded file %s from API into %s", filename, upload_dir) + file_path = os.join(upload_dir, filename) + try: + with open(file_path, 'wb') as f: + f.write(args[generated_name]) + except IOError as e: + raise YunohostError("cannot_write_file", file=file_path, error=str(e)) + except Exception as e: + raise YunohostError("error_writing_file", file=file_path, error=str(e)) + args[generated_name] = file_path + logger.debug( "include into env %s=%s", generated_name, args[generated_name] ) @@ -1906,6 +1925,11 @@ def app_config_apply(operation_logger, app, args): env=env, )[0] + # Delete files uploaded from API + if msettings.get('interface') == 'api': + if upload_dir is not None: + shutil.rmtree(upload_dir) + if return_code != 0: msg = ( "'script/config apply' return value code: %s (considered as an error)" From fb0d23533e2712f4c08e09b9febae1ced8877459 Mon Sep 17 00:00:00 2001 From: ljf Date: Mon, 8 Jun 2020 19:18:22 +0200 Subject: [PATCH 2726/3170] [fix] Several files with same name --- src/yunohost/app.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index a8cd6aa40..1a0ee9087 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1889,15 +1889,21 @@ def app_config_apply(operation_logger, app, args): if generated_name in args: # Upload files from API # A file arg contains a string with "FILENAME:BASE64_CONTENT" - if option["type"] == "file" and msettings.get('interface') == 'api': + if 'type' in option and option["type"] == "file" \ + and msettings.get('interface') == 'api': if upload_dir is None: upload_dir = tempfile.mkdtemp(prefix='tmp_configpanel_') - filename, args[generated_name] = args[generated_name].split(':') + filename = args[generated_name + '[name]'] + content = args[generated_name] logger.debug("Save uploaded file %s from API into %s", filename, upload_dir) - file_path = os.join(upload_dir, filename) + file_path = os.path.join(upload_dir, filename) + i = 2 + while os.path.exists(file_path): + file_path = os.path.join(upload_dir, filename + (".%d" % i)) + i += 1 try: with open(file_path, 'wb') as f: - f.write(args[generated_name]) + f.write(content.decode("base64")) except IOError as e: raise YunohostError("cannot_write_file", file=file_path, error=str(e)) except Exception as e: @@ -1914,7 +1920,7 @@ def app_config_apply(operation_logger, app, args): # for debug purpose for key in args: if key not in env: - logger.warning( + logger.debug( "Ignore key '%s' from arguments because it is not in the config", key ) From 975bf4edcbdeabd2f9487dca2f9e56926eb1f64b Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Wed, 7 Oct 2020 00:31:20 +0200 Subject: [PATCH 2727/3170] [enh] Replace os.path.join to improve security --- src/yunohost/app.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 1a0ee9087..324159859 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1896,10 +1896,14 @@ def app_config_apply(operation_logger, app, args): filename = args[generated_name + '[name]'] content = args[generated_name] logger.debug("Save uploaded file %s from API into %s", filename, upload_dir) - file_path = os.path.join(upload_dir, filename) + + # Filename is given by user of the API. For security reason, we have replaced + # os.path.join to avoid the user to be able to rewrite a file in filesystem + # i.e. os.path.join("/foo", "/etc/passwd") == "/etc/passwd" + file_path = os.path.normpath(upload_dir + "/" + filename) i = 2 while os.path.exists(file_path): - file_path = os.path.join(upload_dir, filename + (".%d" % i)) + file_path = os.path.normpath(upload_dir + "/" + filename + (".%d" % i)) i += 1 try: with open(file_path, 'wb') as f: From 284554598521af8305f521b0eeabf2a42f95167e Mon Sep 17 00:00:00 2001 From: ljf Date: Mon, 31 May 2021 16:32:19 +0200 Subject: [PATCH 2728/3170] [fix] Base64 python3 change --- 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 324159859..4006c1ec4 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1852,7 +1852,7 @@ def app_config_apply(operation_logger, app, args): logger.warning(m18n.n("experimental_feature")) from yunohost.hook import hook_exec - + from base64 import b64decode installed = _is_installed(app) if not installed: raise YunohostValidationError( @@ -1896,8 +1896,8 @@ def app_config_apply(operation_logger, app, args): filename = args[generated_name + '[name]'] content = args[generated_name] logger.debug("Save uploaded file %s from API into %s", filename, upload_dir) - - # Filename is given by user of the API. For security reason, we have replaced + + # Filename is given by user of the API. For security reason, we have replaced # os.path.join to avoid the user to be able to rewrite a file in filesystem # i.e. os.path.join("/foo", "/etc/passwd") == "/etc/passwd" file_path = os.path.normpath(upload_dir + "/" + filename) @@ -1907,7 +1907,7 @@ def app_config_apply(operation_logger, app, args): i += 1 try: with open(file_path, 'wb') as f: - f.write(content.decode("base64")) + f.write(b64decode(content)) except IOError as e: raise YunohostError("cannot_write_file", file=file_path, error=str(e)) except Exception as e: From 1c636fe1ca8b4838ad99e8f69358e8b8e9e4e4c7 Mon Sep 17 00:00:00 2001 From: ljf Date: Tue, 1 Jun 2021 00:41:37 +0200 Subject: [PATCH 2729/3170] [enh] Add configpanel helpers --- data/helpers.d/configpanel | 259 +++++++++++++++++++++++++++++++++++++ 1 file changed, 259 insertions(+) create mode 100644 data/helpers.d/configpanel diff --git a/data/helpers.d/configpanel b/data/helpers.d/configpanel new file mode 100644 index 000000000..f648826e4 --- /dev/null +++ b/data/helpers.d/configpanel @@ -0,0 +1,259 @@ +#!/bin/bash + +ynh_lowerdot_to_uppersnake() { + local lowerdot + lowerdot=$(echo "$1" | cut -d= -f1 | sed "s/\./_/g") + echo "${lowerdot^^}" +} + +# Get a value from heterogeneous file (yaml, json, php, python...) +# +# usage: ynh_value_get --file=PATH --key=KEY +# | arg: -f, --file= - the path to the file +# | arg: -k, --key= - the key to get +# +# This helpers match several var affectation use case in several languages +# We don't use jq or equivalent to keep comments and blank space in files +# This helpers work line by line, it is not able to work correctly +# if you have several identical keys in your files +# +# Example of line this helpers can managed correctly +# .yml +# title: YunoHost documentation +# email: 'yunohost@yunohost.org' +# .json +# "theme": "colib'ris", +# "port": 8102 +# "some_boolean": false, +# "user": null +# .ini +# some_boolean = On +# action = "Clear" +# port = 20 +# .php +# $user= +# user => 20 +# .py +# USER = 8102 +# user = 'https://donate.local' +# CUSTOM['user'] = 'YunoHost' +# Requires YunoHost version 4.3 or higher. +ynh_value_get() { + # Declare an array to define the options of this helper. + local legacy_args=fk + local -A args_array=( [f]=file= [k]=key= ) + local file + local key + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + local var_part="[ \t]*(\$?\w*\[)?[ \t]*[\"']?${key}[\"']?[ \t]*\]?[ \t]*[:=]>?[ \t]*" + + local crazy_value="$(grep -i -o -P "^${var_part}\K.*(?=[ \t,\n;]*\$)" ${file} | head -n1)" + + local first_char="${crazy_value:0:1}" + if [[ "$first_char" == '"' ]] ; then + echo "$crazy_value" | grep -m1 -o -P '"\K([^"](\\")?)*[^\\](?=")' | head -n1 | sed 's/\\"/"/g' + elif [[ "$first_char" == "'" ]] ; then + echo "$crazy_value" | grep -m1 -o -P "'\K([^'](\\\\')?)*[^\\\\](?=')" | head -n1 | sed "s/\\\\'/'/g" + else + echo "$crazy_value" + fi +} + +# Set a value into heterogeneous file (yaml, json, php, python...) +# +# usage: ynh_value_set --file=PATH --key=KEY --value=VALUE +# | arg: -f, --file= - the path to the file +# | arg: -k, --key= - the key to set +# | arg: -v, --value= - the value to set +# +# Requires YunoHost version 4.3 or higher. +ynh_value_set() { + # Declare an array to define the options of this helper. + local legacy_args=fkv + local -A args_array=( [f]=file= [k]=key= [v]=value=) + local file + local key + local value + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + local var_part="[ \t]*(\$?\w*\[)?[ \t]*[\"']?${key}[\"']?[ \t]*\]?[ \t]*[:=]>?[ \t]*" + + local crazy_value="$(grep -i -o -P "^${var_part}\K.*(?=[ \t,\n;]*\$)" ${file} | head -n1)" + local var_part="^[ \t]*(\$?\w*\[)?[ \t]*[\"']?${key}[\"']?[ \t]*\]?[ \t]*[:=]>?[ \t]*" + local first_char="${crazy_value:0:1}" + if [[ "$first_char" == '"' ]] ; then + value="$(echo "$value" | sed 's/"/\\"/g')" + sed -ri "s%^(${var_part}\")[^\"]*(\"[ \t\n,;]*)\$%\1${value}\2%i" ${file} + elif [[ "$first_char" == "'" ]] ; then + value="$(echo "$value" | sed "s/'/\\\\'/g")" + sed -ri "s%^(${var_part}')[^']*('[ \t\n,;]*)\$%\1${value}\2%i" ${file} + else + if [[ "$value" == *"'"* ]] || [[ "$value" == *'"'* ]] ; then + value="\"$(echo "$value" | sed 's/"/\\"/g')\"" + fi + sed -ri "s%^(${var_part}')[^']*('[ \t\n,;]*)\$%\1${value}\2%i" ${file} + fi +} + +_ynh_panel_get() { + + # From settings + local params_sources + params_sources=`python3 << EOL +import toml +from collections import OrderedDict +with open("/etc/yunohost/apps/vpnclient/config_panel.toml", "r") as f: + file_content = f.read() +loaded_toml = toml.loads(file_content, _dict=OrderedDict) + +for panel_name,panel in loaded_toml.items(): + if isinstance(panel, dict): + for section_name, section in panel.items(): + if isinstance(section, dict): + for name, param in section.items(): + if isinstance(param, dict) and param.get('source', '') == 'settings': + print("%s.%s.%s=%s" %(panel_name, section_name, name, param.get('source', 'settings'))) +EOL +` + for param_source in params_sources + do + local _dot_setting=$(echo "$param_source" | cut -d= -f1) + local _snake_setting="YNH_CONFIG_$(ynh_lowerdot_to_uppersnake $dot_setting)" + local short_setting=$(echo "$_dot_setting" | cut -d. -f3) + local _getter="get__${short_setting}" + local source="$(echo $param_source | cut -d= -f2)" + + # Get value from getter if exists + if type $getter | grep -q '^function$' 2>/dev/null; then + old[$short_setting]="$($getter)" + + # Get value from app settings + elif [[ "$source" == "settings" ]] ; then + old[$short_setting]="$(ynh_app_setting_get $app $short_setting)" + + # Get value from a kind of key/value file + elif [[ "$source" == *":"* ]] ; then + local source_key="$(echo "$source" | cut -d: -f1)" + source_key=${source_key:-$short_setting} + local source_file="$(echo "$source" | cut -d: -f2)" + old[$short_setting]="$(ynh_value_get --file="${source_file}" --key="${source_key}")" + + # Specific case for files (all content of the file is the source) + else + old[$short_setting]="$source" + fi + + done + + +} + +_ynh_panel_apply() { + for short_setting in "${!dot_settings[@]}" + do + local setter="set__${short_setting}" + local source="$sources[$short_setting]" + + # Apply setter if exists + if type $setter | grep -q '^function$' 2>/dev/null; then + $setter + + # Copy file in right place + elif [[ "$source" == "settings" ]] ; then + ynh_app_setting_set $app $short_setting "$new[$short_setting]" + + # Get value from a kind of key/value file + elif [[ "$source" == *":"* ]] + then + local source_key="$(echo "$source" | cut -d: -f1)" + source_key=${source_key:-$short_setting} + local source_file="$(echo "$source" | cut -d: -f2)" + ynh_value_set --file="${source_file}" --key="${source_key}" --value="$new[$short_setting]" + + # Specific case for files (all content of the file is the source) + else + cp "$new[$short_setting]" "$source" + fi + done +} + +_ynh_panel_show() { + for short_setting in "${!old[@]}" + do + local key="YNH_CONFIG_$(ynh_lowerdot_to_uppersnake $dot_settings[$short_setting])" + ynh_return "$key=${old[$short_setting]}" + done +} + +_ynh_panel_validate() { + # Change detection + local is_error=true + #for changed_status in "${!changed[@]}" + for short_setting in "${!dot_settings[@]}" + do + #TODO file hash + file_hash[$setting]=$(sha256sum "$_source" | cut -d' ' -f1) + file_hash[$form_setting]=$(sha256sum "${!form_setting}" | cut -d' ' -f1) + if [[ "${file_hash[$setting]}" != "${file_hash[$form_setting]}" ]] + then + changed[$setting]=true + fi + if [[ "$new[$short_setting]" == "$old[$short_setting]" ]] + then + changed[$short_setting]=false + else + changed[$short_setting]=true + is_error=false + fi + done + + # Run validation if something is changed + if [[ "$is_error" == "false" ]] + then + + for short_setting in "${!dot_settings[@]}" + do + local result="$(validate__$short_setting)" + local key="YNH_ERROR_$(ynh_lowerdot_to_uppersnake $dot_settings[$short_setting])" + if [ -n "$result" ] + then + ynh_return "$key=$result" + is_error=true + fi + done + fi + + if [[ "$is_error" == "true" ]] + then + ynh_die + fi + +} + +ynh_panel_get() { + _ynh_panel_get +} + +ynh_panel_init() { + declare -A old=() + declare -A changed=() + declare -A file_hash=() + + ynh_panel_get +} + +ynh_panel_show() { + _ynh_panel_show +} + +ynh_panel_validate() { + _ynh_panel_validate +} + +ynh_panel_apply() { + _ynh_panel_apply +} + From caf2a9d6d13b9c5be0068585d0a3617c2fdc35a7 Mon Sep 17 00:00:00 2001 From: ljf Date: Tue, 1 Jun 2021 01:29:26 +0200 Subject: [PATCH 2730/3170] [fix] No validate function in config panel --- data/helpers.d/configpanel | 41 ++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/data/helpers.d/configpanel b/data/helpers.d/configpanel index f648826e4..83130cfe6 100644 --- a/data/helpers.d/configpanel +++ b/data/helpers.d/configpanel @@ -123,7 +123,7 @@ EOL local _dot_setting=$(echo "$param_source" | cut -d= -f1) local _snake_setting="YNH_CONFIG_$(ynh_lowerdot_to_uppersnake $dot_setting)" local short_setting=$(echo "$_dot_setting" | cut -d. -f3) - local _getter="get__${short_setting}" + local getter="get__${short_setting}" local source="$(echo $param_source | cut -d= -f2)" # Get value from getter if exists @@ -195,12 +195,12 @@ _ynh_panel_validate() { for short_setting in "${!dot_settings[@]}" do #TODO file hash - file_hash[$setting]=$(sha256sum "$_source" | cut -d' ' -f1) - file_hash[$form_setting]=$(sha256sum "${!form_setting}" | cut -d' ' -f1) - if [[ "${file_hash[$setting]}" != "${file_hash[$form_setting]}" ]] - then - changed[$setting]=true - fi + file_hash[$setting]=$(sha256sum "$_source" | cut -d' ' -f1) + file_hash[$form_setting]=$(sha256sum "${!form_setting}" | cut -d' ' -f1) + if [[ "${file_hash[$setting]}" != "${file_hash[$form_setting]}" ]] + then + changed[$setting]=true + fi if [[ "$new[$short_setting]" == "$old[$short_setting]" ]] then changed[$short_setting]=false @@ -216,10 +216,13 @@ _ynh_panel_validate() { for short_setting in "${!dot_settings[@]}" do - local result="$(validate__$short_setting)" - local key="YNH_ERROR_$(ynh_lowerdot_to_uppersnake $dot_settings[$short_setting])" + local result="" + if type validate__$short_setting | grep -q '^function$' 2>/dev/null; then + result="$(validate__$short_setting)" + fi if [ -n "$result" ] then + local key="YNH_ERROR_$(ynh_lowerdot_to_uppersnake $dot_settings[$short_setting])" ynh_return "$key=$result" is_error=true fi @@ -237,14 +240,6 @@ ynh_panel_get() { _ynh_panel_get } -ynh_panel_init() { - declare -A old=() - declare -A changed=() - declare -A file_hash=() - - ynh_panel_get -} - ynh_panel_show() { _ynh_panel_show } @@ -257,3 +252,15 @@ ynh_panel_apply() { _ynh_panel_apply } +ynh_panel_run() { + declare -A old=() + declare -A changed=() + declare -A file_hash=() + + ynh_panel_get + case $1 in + show) ynh_panel_show;; + apply) ynh_panel_validate && ynh_panel_apply;; + esac +} + From ed0915cf81a42c1ae12b9897cd5b2827c5b96ff6 Mon Sep 17 00:00:00 2001 From: ljf Date: Fri, 13 Aug 2021 13:38:06 +0200 Subject: [PATCH 2731/3170] [fix] tons of things --- data/helpers.d/configpanel | 110 ++++++++++++++++++++++--------------- 1 file changed, 65 insertions(+), 45 deletions(-) diff --git a/data/helpers.d/configpanel b/data/helpers.d/configpanel index 83130cfe6..5ab199aea 100644 --- a/data/helpers.d/configpanel +++ b/data/helpers.d/configpanel @@ -99,13 +99,13 @@ ynh_value_set() { } _ynh_panel_get() { - + set +x # From settings local params_sources params_sources=`python3 << EOL import toml from collections import OrderedDict -with open("/etc/yunohost/apps/vpnclient/config_panel.toml", "r") as f: +with open("../config_panel.toml", "r") as f: file_content = f.read() loaded_toml = toml.loads(file_content, _dict=OrderedDict) @@ -114,20 +114,23 @@ for panel_name,panel in loaded_toml.items(): for section_name, section in panel.items(): if isinstance(section, dict): for name, param in section.items(): - if isinstance(param, dict) and param.get('source', '') == 'settings': + if isinstance(param, dict) and param.get('type', 'string') not in ['info', 'warning', 'error']: print("%s.%s.%s=%s" %(panel_name, section_name, name, param.get('source', 'settings'))) EOL ` - for param_source in params_sources + for param_source in $params_sources do local _dot_setting=$(echo "$param_source" | cut -d= -f1) - local _snake_setting="YNH_CONFIG_$(ynh_lowerdot_to_uppersnake $dot_setting)" + local _snake_setting="YNH_CONFIG_$(ynh_lowerdot_to_uppersnake $_dot_setting)" local short_setting=$(echo "$_dot_setting" | cut -d. -f3) local getter="get__${short_setting}" local source="$(echo $param_source | cut -d= -f2)" + sources[${short_setting}]="$source" + file_hash[${short_setting}]="" + dot_settings[${short_setting}]="${_dot_setting}" # Get value from getter if exists - if type $getter | grep -q '^function$' 2>/dev/null; then + if type -t $getter 2>/dev/null | grep -q '^function$' 2>/dev/null; then old[$short_setting]="$($getter)" # Get value from app settings @@ -144,38 +147,43 @@ EOL # Specific case for files (all content of the file is the source) else old[$short_setting]="$source" + file_hash[$short_setting]="true" fi - + set +u + new[$short_setting]="${!_snake_setting}" + set -u done + set -x } _ynh_panel_apply() { - for short_setting in "${!dot_settings[@]}" + for short_setting in "${!old[@]}" do local setter="set__${short_setting}" - local source="$sources[$short_setting]" - - # Apply setter if exists - if type $setter | grep -q '^function$' 2>/dev/null; then - $setter + local source="${sources[$short_setting]}" + if [ "${changed[$short_setting]}" == "true" ] ; then + # Apply setter if exists + if type -t $setter 2>/dev/null | grep -q '^function$' 2>/dev/null; then + $setter - # Copy file in right place - elif [[ "$source" == "settings" ]] ; then - ynh_app_setting_set $app $short_setting "$new[$short_setting]" - - # Get value from a kind of key/value file - elif [[ "$source" == *":"* ]] - then - local source_key="$(echo "$source" | cut -d: -f1)" - source_key=${source_key:-$short_setting} - local source_file="$(echo "$source" | cut -d: -f2)" - ynh_value_set --file="${source_file}" --key="${source_key}" --value="$new[$short_setting]" + # Copy file in right place + elif [[ "$source" == "settings" ]] ; then + ynh_app_setting_set $app $short_setting "${new[$short_setting]}" + + # Get value from a kind of key/value file + elif [[ "$source" == *":"* ]] + then + local source_key="$(echo "$source" | cut -d: -f1)" + source_key=${source_key:-$short_setting} + local source_file="$(echo "$source" | cut -d: -f2)" + ynh_value_set --file="${source_file}" --key="${source_key}" --value="${new[$short_setting]}" - # Specific case for files (all content of the file is the source) - else - cp "$new[$short_setting]" "$source" + # Specific case for files (all content of the file is the source) + else + cp "${new[$short_setting]}" "$source" + fi fi done } @@ -189,24 +197,32 @@ _ynh_panel_show() { } _ynh_panel_validate() { + set +x # Change detection local is_error=true #for changed_status in "${!changed[@]}" - for short_setting in "${!dot_settings[@]}" + for short_setting in "${!old[@]}" do - #TODO file hash - file_hash[$setting]=$(sha256sum "$_source" | cut -d' ' -f1) - file_hash[$form_setting]=$(sha256sum "${!form_setting}" | cut -d' ' -f1) - if [[ "${file_hash[$setting]}" != "${file_hash[$form_setting]}" ]] - then - changed[$setting]=true - fi - if [[ "$new[$short_setting]" == "$old[$short_setting]" ]] - then - changed[$short_setting]=false + changed[$short_setting]=false + if [ ! -z "${file_hash[${short_setting}]}" ] ; then + file_hash[old__$short_setting]="" + file_hash[new__$short_setting]="" + if [ -f "${old[$short_setting]}" ] ; then + file_hash[old__$short_setting]=$(sha256sum "${old[$short_setting]}" | cut -d' ' -f1) + fi + if [ -f "${new[$short_setting]}" ] ; then + file_hash[new__$short_setting]=$(sha256sum "${new[$short_setting]}" | cut -d' ' -f1) + if [[ "${file_hash[old__$short_setting]}" != "${file_hash[new__$short_setting]}" ]] + then + changed[$short_setting]=true + fi + fi else - changed[$short_setting]=true - is_error=false + if [[ "${new[$short_setting]}" != "${old[$short_setting]}" ]] + then + changed[$short_setting]=true + is_error=false + fi fi done @@ -214,10 +230,10 @@ _ynh_panel_validate() { if [[ "$is_error" == "false" ]] then - for short_setting in "${!dot_settings[@]}" + for short_setting in "${!old[@]}" do local result="" - if type validate__$short_setting | grep -q '^function$' 2>/dev/null; then + if type -t validate__$short_setting | grep -q '^function$' 2>/dev/null; then result="$(validate__$short_setting)" fi if [ -n "$result" ] @@ -233,6 +249,7 @@ _ynh_panel_validate() { then ynh_die fi + set -x } @@ -253,9 +270,12 @@ ynh_panel_apply() { } ynh_panel_run() { - declare -A old=() - declare -A changed=() - declare -A file_hash=() + declare -Ag old=() + declare -Ag new=() + declare -Ag changed=() + declare -Ag file_hash=() + declare -Ag sources=() + declare -Ag dot_settings=() ynh_panel_get case $1 in From 65fc06e3e743ed6bff30ace4ca0800c54e58b253 Mon Sep 17 00:00:00 2001 From: ljf Date: Sat, 14 Aug 2021 14:38:45 +0200 Subject: [PATCH 2732/3170] [fix] Missing name or bad format management --- src/yunohost/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 4006c1ec4..d8e4748f6 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -2152,7 +2152,7 @@ def _get_app_config_panel(app_id): for key, value in panels: panel = { "id": key, - "name": value["name"], + "name": value.get("name", ""), "sections": [], } @@ -2165,7 +2165,7 @@ def _get_app_config_panel(app_id): for section_key, section_value in sections: section = { "id": section_key, - "name": section_value["name"], + "name": section_value.get("name", ""), "options": [], } From fddb79e841470a58f8bea31293417f80c3e1dd7b Mon Sep 17 00:00:00 2001 From: ljf Date: Tue, 17 Aug 2021 17:07:26 +0200 Subject: [PATCH 2733/3170] [wip] Reduce config panel var --- data/helpers.d/configpanel | 18 ++---- src/yunohost/app.py | 116 ++++++++++++++++++------------------- 2 files changed, 60 insertions(+), 74 deletions(-) diff --git a/data/helpers.d/configpanel b/data/helpers.d/configpanel index 5ab199aea..685f30a98 100644 --- a/data/helpers.d/configpanel +++ b/data/helpers.d/configpanel @@ -115,19 +115,16 @@ for panel_name,panel in loaded_toml.items(): if isinstance(section, dict): for name, param in section.items(): if isinstance(param, dict) and param.get('type', 'string') not in ['info', 'warning', 'error']: - print("%s.%s.%s=%s" %(panel_name, section_name, name, param.get('source', 'settings'))) + print("%s=%s" % (name, param.get('source', 'settings'))) EOL ` for param_source in $params_sources do - local _dot_setting=$(echo "$param_source" | cut -d= -f1) - local _snake_setting="YNH_CONFIG_$(ynh_lowerdot_to_uppersnake $_dot_setting)" - local short_setting=$(echo "$_dot_setting" | cut -d. -f3) + local short_setting="$(echo $param_source | cut -d= -f1)" local getter="get__${short_setting}" local source="$(echo $param_source | cut -d= -f2)" sources[${short_setting}]="$source" file_hash[${short_setting}]="" - dot_settings[${short_setting}]="${_dot_setting}" # Get value from getter if exists if type -t $getter 2>/dev/null | grep -q '^function$' 2>/dev/null; then @@ -149,9 +146,6 @@ EOL old[$short_setting]="$source" file_hash[$short_setting]="true" fi - set +u - new[$short_setting]="${!_snake_setting}" - set -u done set -x @@ -191,8 +185,7 @@ _ynh_panel_apply() { _ynh_panel_show() { for short_setting in "${!old[@]}" do - local key="YNH_CONFIG_$(ynh_lowerdot_to_uppersnake $dot_settings[$short_setting])" - ynh_return "$key=${old[$short_setting]}" + ynh_return "${short_setting}=${old[$short_setting]}" done } @@ -238,7 +231,7 @@ _ynh_panel_validate() { fi if [ -n "$result" ] then - local key="YNH_ERROR_$(ynh_lowerdot_to_uppersnake $dot_settings[$short_setting])" + local key="YNH_ERROR_${$short_setting}" ynh_return "$key=$result" is_error=true fi @@ -247,7 +240,7 @@ _ynh_panel_validate() { if [[ "$is_error" == "true" ]] then - ynh_die + ynh_die "" fi set -x @@ -275,7 +268,6 @@ ynh_panel_run() { declare -Ag changed=() declare -Ag file_hash=() declare -Ag sources=() - declare -Ag dot_settings=() ynh_panel_get case $1 in diff --git a/src/yunohost/app.py b/src/yunohost/app.py index d8e4748f6..faa5098c9 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1782,59 +1782,49 @@ def app_config_show_panel(operation_logger, app): } env = { - "YNH_APP_ID": app_id, - "YNH_APP_INSTANCE_NAME": app, - "YNH_APP_INSTANCE_NUMBER": str(app_instance_nb), + "app_id": app_id, + "app": app, + "app_instance_nb": str(app_instance_nb), } - # FIXME: this should probably be ran in a tmp workdir... - return_code, parsed_values = hook_exec( - config_script, args=["show"], env=env, return_format="plain_dict" - ) - - if return_code != 0: - raise Exception( - "script/config show return value code: %s (considered as an error)", - return_code, + try: + ret, parsed_values = hook_exec( + config_script, args=["show"], env=env, return_format="plain_dict" ) + # Here again, calling hook_exec could fail miserably, or get + # manually interrupted (by mistake or because script was stuck) + except (KeyboardInterrupt, EOFError, Exception): + raise YunohostError("unexpected_error") logger.debug("Generating global variables:") for tab in config_panel.get("panel", []): - tab_id = tab["id"] # this makes things easier to debug on crash for section in tab.get("sections", []): - section_id = section["id"] for option in section.get("options", []): - option_name = option["name"] - generated_name = ( - "YNH_CONFIG_%s_%s_%s" % (tab_id, section_id, option_name) - ).upper() - option["name"] = generated_name logger.debug( - " * '%s'.'%s'.'%s' -> %s", + " * '%s'.'%s'.'%s'", tab.get("name"), section.get("name"), option.get("name"), - generated_name, ) - if generated_name in parsed_values: + if option['name'] in parsed_values: # code is not adapted for that so we have to mock expected format :/ if option.get("type") == "boolean": - if parsed_values[generated_name].lower() in ("true", "1", "y"): - option["default"] = parsed_values[generated_name] + if parsed_values[option['name']].lower() in ("true", "1", "y"): + option["default"] = parsed_values[option['name']] else: del option["default"] else: - option["default"] = parsed_values[generated_name] + option["default"] = parsed_values[option['name']] args_dict = _parse_args_in_yunohost_format( - {option["name"]: parsed_values[generated_name]}, [option] + parsed_values, [option] ) option["default"] = args_dict[option["name"]][0] else: logger.debug( "Variable '%s' is not declared by config script, using default", - generated_name, + option['name'], ) # do nothing, we'll use the default if present @@ -1869,32 +1859,26 @@ def app_config_apply(operation_logger, app, args): operation_logger.start() app_id, app_instance_nb = _parse_app_instance_name(app) env = { - "YNH_APP_ID": app_id, - "YNH_APP_INSTANCE_NAME": app, - "YNH_APP_INSTANCE_NUMBER": str(app_instance_nb), + "app_id": app_id, + "app": app, + "app_instance_nb": str(app_instance_nb), } args = dict(urllib.parse.parse_qsl(args, keep_blank_values=True)) if args else {} upload_dir = None for tab in config_panel.get("panel", []): - tab_id = tab["id"] # this makes things easier to debug on crash for section in tab.get("sections", []): - section_id = section["id"] for option in section.get("options", []): - option_name = option["name"] - generated_name = ( - "YNH_CONFIG_%s_%s_%s" % (tab_id, section_id, option_name) - ).upper() - if generated_name in args: + if option['name'] in args: # Upload files from API # A file arg contains a string with "FILENAME:BASE64_CONTENT" if 'type' in option and option["type"] == "file" \ and msettings.get('interface') == 'api': if upload_dir is None: upload_dir = tempfile.mkdtemp(prefix='tmp_configpanel_') - filename = args[generated_name + '[name]'] - content = args[generated_name] + filename = args[option['name'] + '[name]'] + content = args[option['name']] logger.debug("Save uploaded file %s from API into %s", filename, upload_dir) # Filename is given by user of the API. For security reason, we have replaced @@ -1912,14 +1896,14 @@ def app_config_apply(operation_logger, app, args): raise YunohostError("cannot_write_file", file=file_path, error=str(e)) except Exception as e: raise YunohostError("error_writing_file", file=file_path, error=str(e)) - args[generated_name] = file_path + args[option['name']] = file_path logger.debug( - "include into env %s=%s", generated_name, args[generated_name] + "include into env %s=%s", option['name'], args[option['name']] ) - env[generated_name] = args[generated_name] + env[option['name']] = args[option['name']] else: - logger.debug("no value for key id %s", generated_name) + logger.debug("no value for key id %s", option['name']) # for debug purpose for key in args: @@ -1928,25 +1912,21 @@ def app_config_apply(operation_logger, app, args): "Ignore key '%s' from arguments because it is not in the config", key ) - # FIXME: this should probably be ran in a tmp workdir... - return_code = hook_exec( - config_script, - args=["apply"], - env=env, - )[0] - - # Delete files uploaded from API - if msettings.get('interface') == 'api': - if upload_dir is not None: - shutil.rmtree(upload_dir) - - if return_code != 0: - msg = ( - "'script/config apply' return value code: %s (considered as an error)" - % return_code + try: + hook_exec( + config_script, + args=["apply"], + env=env ) - operation_logger.error(msg) - raise Exception(msg) + # Here again, calling hook_exec could fail miserably, or get + # manually interrupted (by mistake or because script was stuck) + except (KeyboardInterrupt, EOFError, Exception): + raise YunohostError("unexpected_error") + finally: + # Delete files uploaded from API + if msettings.get('interface') == 'api': + if upload_dir is not None: + shutil.rmtree(upload_dir) logger.success("Config updated as expected") return { @@ -2991,16 +2971,30 @@ class DisplayTextArgumentParser(YunoHostArgumentFormatParser): def parse(self, question, user_answers): print(question["ask"]) +class FileArgumentParser(YunoHostArgumentFormatParser): + argument_type = "file" + + ARGUMENTS_TYPE_PARSERS = { "string": StringArgumentParser, + "text": StringArgumentParser, + "select": StringArgumentParser, + "tags": StringArgumentParser, + "email": StringArgumentParser, + "url": StringArgumentParser, + "date": StringArgumentParser, + "time": StringArgumentParser, + "color": StringArgumentParser, "password": PasswordArgumentParser, "path": PathArgumentParser, "boolean": BooleanArgumentParser, "domain": DomainArgumentParser, "user": UserArgumentParser, "number": NumberArgumentParser, + "range": NumberArgumentParser, "display_text": DisplayTextArgumentParser, + "file": FileArgumentParser, } From 69a5ae5736f3e89b0a7d5cfec7a6106a71187b40 Mon Sep 17 00:00:00 2001 From: sagessylu <49091098+sagessylu@users.noreply.github.com> Date: Tue, 17 Aug 2021 20:27:33 +0200 Subject: [PATCH 2734/3170] Update src/yunohost/app.py Co-authored-by: ljf (zamentur) --- 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 1841b2a07..a69229ad4 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -619,7 +619,7 @@ def app_upgrade(app=[], url=None, file=None, force=False, no_safety_backup=False env_dict["YNH_APP_UPGRADE_TYPE"] = upgrade_type env_dict["YNH_APP_MANIFEST_VERSION"] = str(app_new_version) env_dict["YNH_APP_CURRENT_VERSION"] = str(app_current_version) - env_dict["YNH_APP_NO_BACKUP_UPGRADE"] = no_safety_backup + env_dict["NO_BACKUP_UPGRADE"] = no_safety_backup # We'll check that the app didn't brutally edit some system configuration manually_modified_files_before_install = manually_modified_files() From 09efb86ff10ab4786cdf6a0350435832120ea9e4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 18 Aug 2021 19:40:10 +0200 Subject: [PATCH 2735/3170] Improve help text for app remove --purge --- data/actionsmap/yunohost.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index bd9207528..bcd6a61c3 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -676,7 +676,7 @@ app: help: App to remove -p: full: --purge - help: Remove with all app data + help: Also remove all application data action: store_true ### app_upgrade() From 4b84922315e9e89e4d92420d6c8bb95d20b4749c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 18 Aug 2021 19:47:46 +0200 Subject: [PATCH 2736/3170] Apply suggestions from code review --- data/helpers.d/multimedia | 6 +++--- data/hooks/post_user_create/ynh_multimedia | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/data/helpers.d/multimedia b/data/helpers.d/multimedia index 4ec7611fc..552b8c984 100644 --- a/data/helpers.d/multimedia +++ b/data/helpers.d/multimedia @@ -32,9 +32,9 @@ ynh_multimedia_build_main_dir() { ln -sfn "$MEDIA_DIRECTORY/share" "$MEDIA_DIRECTORY/$user/Share" # Création du lien symbolique dans le home de l'utilisateur. #link will only be created if the home directory of the user exists and if it's located in '/home' folder - home="$(getent passwd $user | cut -d: -f6)" - if [[ -d "$home" && "$(echo "$home" | grep /home/)" ]]; then - ln -sfn "$MEDIA_DIRECTORY/$user" "$home/Multimedia" + local user_home="$(getent passwd $user | cut -d: -f6 | grep '^/home/')" + if [[ -d "$user_home" ]]; then + ln -sfn "$MEDIA_DIRECTORY/$user" "$user_home/Multimedia" fi # Propriétaires des dossiers utilisateurs. chown -R $user "$MEDIA_DIRECTORY/$user" diff --git a/data/hooks/post_user_create/ynh_multimedia b/data/hooks/post_user_create/ynh_multimedia index 2fa02505a..2be3f42d4 100644 --- a/data/hooks/post_user_create/ynh_multimedia +++ b/data/hooks/post_user_create/ynh_multimedia @@ -16,9 +16,9 @@ mkdir -p "$MEDIA_DIRECTORY/$user/eBook" ln -sfn "$MEDIA_DIRECTORY/share" "$MEDIA_DIRECTORY/$user/Share" # Création du lien symbolique dans le home de l'utilisateur. #link will only be created if the home directory of the user exists and if it's located in '/home' folder -home="$(getent passwd $user | cut -d: -f6)" -if [[ -d "$home" && "$(echo "$home" | grep /home/)" ]]; then - ln -sfn "$MEDIA_DIRECTORY/$user" "$home/Multimedia" +local user_home="$(getent passwd $user | cut -d: -f6 | grep '^/home/')" +if [[ -d "$user_home" ]]; then + ln -sfn "$MEDIA_DIRECTORY/$user" "$user_home/Multimedia" fi # Propriétaires des dossiers utilisateurs. chown -R $user "$MEDIA_DIRECTORY/$user" From bcb803c0c37b8b3a540ad4d4c5267c3448edba8e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 18 Aug 2021 22:10:25 +0200 Subject: [PATCH 2737/3170] Add new setting to enable experimental security features --- data/hooks/conf_regen/01-yunohost | 15 +++++++++++++++ data/hooks/conf_regen/15-nginx | 1 + data/templates/nginx/security.conf.inc | 10 ++++++++++ data/templates/yunohost/proc-hidepid.service | 14 ++++++++++++++ locales/en.json | 1 + src/yunohost/settings.py | 7 +++++++ 6 files changed, 48 insertions(+) create mode 100644 data/templates/yunohost/proc-hidepid.service diff --git a/data/hooks/conf_regen/01-yunohost b/data/hooks/conf_regen/01-yunohost index 1a10a6954..8d2280e89 100755 --- a/data/hooks/conf_regen/01-yunohost +++ b/data/hooks/conf_regen/01-yunohost @@ -144,6 +144,14 @@ HandleLidSwitch=ignore HandleLidSwitchDocked=ignore HandleLidSwitchExternalPower=ignore EOF + + mkdir -p ${pending_dir}/etc/systemd/ + if [[ "$(yunohost settings get 'security.experimental.enabled')" == "True" ]] + then + cp proc-hidepid.service ${pending_dir}/etc/systemd/system/proc-hidepid.service + else + touch ${pending_dir}/etc/systemd/system/proc-hidepid.service + fi } @@ -204,6 +212,13 @@ do_post_regen() { # Propagates changes in systemd service config overrides [[ ! "$regen_conf_files" =~ "ntp.service.d/ynh-override.conf" ]] || { systemctl daemon-reload; systemctl restart ntp; } [[ ! "$regen_conf_files" =~ "nftables.service.d/ynh-override.conf" ]] || systemctl daemon-reload + [[ ! "$regen_conf_files" =~ "login.conf.d/ynh-override.conf" ]] || systemctl daemon-reload + if [[ "$regen_conf_files" =~ "proc-hidepid.service" ]] + then + systemctl daemon-reload + action=$([[ -e /etc/systemd/system/proc-hidepid.service ]] && echo 'enable' || echo 'disable') + systemctl $action proc-hidepid --quiet --now + fi } _update_services() { diff --git a/data/hooks/conf_regen/15-nginx b/data/hooks/conf_regen/15-nginx index e211a3aca..a2d8f1259 100755 --- a/data/hooks/conf_regen/15-nginx +++ b/data/hooks/conf_regen/15-nginx @@ -61,6 +61,7 @@ do_pre_regen() { # Support different strategy for security configurations export compatibility="$(yunohost settings get 'security.nginx.compatibility')" + export experimental="$(yunohost settings get 'security.experimental.enabled')" ynh_render_template "security.conf.inc" "${nginx_conf_dir}/security.conf.inc" cert_status=$(yunohost domain cert-status --json) diff --git a/data/templates/nginx/security.conf.inc b/data/templates/nginx/security.conf.inc index 0d0b74db1..bcb821770 100644 --- a/data/templates/nginx/security.conf.inc +++ b/data/templates/nginx/security.conf.inc @@ -25,7 +25,11 @@ ssl_dhparam /usr/share/yunohost/other/ffdhe2048.pem; # Follows the Web Security Directives from the Mozilla Dev Lab and the Mozilla Obervatory + Partners # https://wiki.mozilla.org/Security/Guidelines/Web_Security # https://observatory.mozilla.org/ +{% if experimental == "True" %} +more_set_headers "Content-Security-Policy : upgrade-insecure-requests; default-src https: data:"; +{% else %} more_set_headers "Content-Security-Policy : upgrade-insecure-requests"; +{% endif %} 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"; @@ -34,7 +38,13 @@ more_set_headers "X-Permitted-Cross-Domain-Policies : none"; more_set_headers "X-Frame-Options : SAMEORIGIN"; # Disable the disaster privacy thing that is FLoC +{% if experimental == "True" %} +more_set_headers "Permissions-Policy : fullscreen=(), geolocation=(), payment=(), accelerometer=(), battery=(), magnetometer=(), usb=(), interest-cohort=()"; +# Force HTTPOnly and Secure for all cookies +proxy_cookie_path ~$ "; HTTPOnly; Secure;"; +{% else %} more_set_headers "Permissions-Policy : interest-cohort=()"; +{% endif %} # Disable gzip to protect against BREACH # Read https://trac.nginx.org/nginx/ticket/1720 (text/html cannot be disabled!) diff --git a/data/templates/yunohost/proc-hidepid.service b/data/templates/yunohost/proc-hidepid.service new file mode 100644 index 000000000..ec6fabede --- /dev/null +++ b/data/templates/yunohost/proc-hidepid.service @@ -0,0 +1,14 @@ +[Unit] +Description=Mounts /proc with hidepid=2 +DefaultDependencies=no +Before=sysinit.target +Requires=local-fs.target +After=local-fs.target + +[Service] +Type=oneshot +ExecStart=/bin/mount -o remount,nosuid,nodev,noexec,hidepid=2 /proc +RemainAfterExit=yes + +[Install] +WantedBy=sysinit.target diff --git a/locales/en.json b/locales/en.json index 693e9d24d..044461d9a 100644 --- a/locales/en.json +++ b/locales/en.json @@ -340,6 +340,7 @@ "global_settings_setting_smtp_relay_password": "SMTP relay host password", "global_settings_setting_security_webadmin_allowlist_enabled": "Allow only some IPs to access the webadmin.", "global_settings_setting_security_webadmin_allowlist": "IP adresses allowed to access the webadmin. Comma-separated.", + "global_settings_setting_security_experimental_enabled": "Enable experimental security features (don't enable this if you don't know what you're doing!)", "global_settings_setting_backup_compress_tar_archives": "When creating new backups, compress the archives (.tar.gz) instead of uncompressed archives (.tar). N.B. : enabling this option means create lighter backup archives, but the initial backup procedure will be significantly longer and heavy on CPU.", "global_settings_unknown_type": "Unexpected situation, the setting {setting} appears to have the type {unknown_type} but it is not a type supported by the system.", "good_practices_about_admin_password": "You are now about to define a new administration password. The password should be at least 8 characters long—though it is good practice to use a longer password (i.e. a passphrase) and/or to use a variation of characters (uppercase, lowercase, digits and special characters).", diff --git a/src/yunohost/settings.py b/src/yunohost/settings.py index ec0e7566c..fe072cddb 100644 --- a/src/yunohost/settings.py +++ b/src/yunohost/settings.py @@ -102,6 +102,7 @@ DEFAULTS = OrderedDict( ("ssowat.panel_overlay.enabled", {"type": "bool", "default": True}), ("security.webadmin.allowlist.enabled", {"type": "bool", "default": False}), ("security.webadmin.allowlist", {"type": "string", "default": ""}), + ("security.experimental.enabled", {"type": "bool", "default": False}), ] ) @@ -399,6 +400,12 @@ def reconfigure_nginx(setting_name, old_value, new_value): regen_conf(names=["nginx"]) +@post_change_hook("security.experimental.enabled") +def reconfigure_nginx_and_yunohost(setting_name, old_value, new_value): + if old_value != new_value: + regen_conf(names=["nginx", "yunohost"]) + + @post_change_hook("security.ssh.compatibility") def reconfigure_ssh(setting_name, old_value, new_value): if old_value != new_value: From a29940d8f46c5c96318cc2a724cccfe079559738 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 18 Aug 2021 23:07:38 +0200 Subject: [PATCH 2738/3170] Trash ugly hack that merge services.yml every regenconf --- data/hooks/conf_regen/01-yunohost | 88 +++---------------------------- src/yunohost/service.py | 59 +++++++++++++++------ 2 files changed, 49 insertions(+), 98 deletions(-) diff --git a/data/hooks/conf_regen/01-yunohost b/data/hooks/conf_regen/01-yunohost index 1a10a6954..97cdb0b40 100755 --- a/data/hooks/conf_regen/01-yunohost +++ b/data/hooks/conf_regen/01-yunohost @@ -2,8 +2,6 @@ set -e -services_path="/etc/yunohost/services.yml" - do_init_regen() { if [[ $EUID -ne 0 ]]; then echo "You must be root to run this script" 1>&2 @@ -19,8 +17,6 @@ do_init_regen() { || echo "yunohost.org" > /etc/yunohost/current_host # copy default services and firewall - [[ -f $services_path ]] \ - || cp services.yml "$services_path" [[ -f /etc/yunohost/firewall.yml ]] \ || cp firewall.yml /etc/yunohost/firewall.yml @@ -49,6 +45,9 @@ do_init_regen() { chmod 644 /etc/ssowat/conf.json.persistent chown root:root /etc/ssowat/conf.json.persistent + # Empty service conf + touch /etc/yunohost/services.yml + mkdir -p /var/cache/yunohost/repo chown root:root /var/cache/yunohost chmod 700 /var/cache/yunohost @@ -59,25 +58,9 @@ do_pre_regen() { cd /usr/share/yunohost/templates/yunohost - # update services.yml - if [[ -f $services_path ]]; then - tmp_services_path="${services_path}-tmp" - new_services_path="${services_path}-new" - cp "$services_path" "$tmp_services_path" - _update_services "$new_services_path" || { - mv "$tmp_services_path" "$services_path" - exit 1 - } - if [[ -f $new_services_path ]]; then - # replace services.yml with new one - mv "$new_services_path" "$services_path" - mv "$tmp_services_path" "${services_path}-old" - else - rm -f "$tmp_services_path" - fi - else - cp services.yml /etc/yunohost/services.yml - fi + # Legacy code that can be removed once on bullseye + touch /etc/yunohost/services.yml + yunohost tools shell -c "from yunohost.service import _get_services, _save_services; _save_services(_get_services())" mkdir -p $pending_dir/etc/cron.d/ mkdir -p $pending_dir/etc/cron.daily/ @@ -206,65 +189,6 @@ do_post_regen() { [[ ! "$regen_conf_files" =~ "nftables.service.d/ynh-override.conf" ]] || systemctl daemon-reload } -_update_services() { - python3 - << EOF -import yaml - - -with open('services.yml') as f: - new_services = yaml.safe_load(f) - -with open('/etc/yunohost/services.yml') as f: - services = yaml.safe_load(f) or {} - -updated = False - - -for service, conf in new_services.items(): - # remove service with empty conf - if conf is None: - if service in services: - print("removing '{0}' from services".format(service)) - del services[service] - updated = True - - # add new service - elif not services.get(service, None): - print("adding '{0}' to services".format(service)) - services[service] = conf - updated = True - - # update service conf - else: - conffiles = services[service].pop('conffiles', {}) - - # status need to be removed - if "status" not in conf and "status" in services[service]: - print("update '{0}' service status access".format(service)) - del services[service]["status"] - updated = True - - if services[service] != conf: - print("update '{0}' service".format(service)) - services[service].update(conf) - updated = True - - if conffiles: - services[service]['conffiles'] = conffiles - - # Remove legacy /var/log/daemon.log and /var/log/syslog from log entries - # because they are too general. Instead, now the journalctl log is - # returned by default which is more relevant. - if "log" in services[service]: - if services[service]["log"] in ["/var/log/syslog", "/var/log/daemon.log"]: - del services[service]["log"] - -if updated: - with open('/etc/yunohost/services.yml-new', 'w') as f: - yaml.safe_dump(services, f, default_flow_style=False) -EOF -} - FORCE=${2:-0} DRY_RUN=${3:-0} diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 912662600..671447067 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -37,10 +37,13 @@ from moulinette import m18n from yunohost.utils.error import YunohostError, YunohostValidationError from moulinette.utils.process import check_output from moulinette.utils.log import getActionLogger -from moulinette.utils.filesystem import read_file, append_to_file, write_to_file +from moulinette.utils.filesystem import read_file, append_to_file, write_to_file, read_yaml, write_to_yaml MOULINETTE_LOCK = "/var/run/moulinette_yunohost.lock" +SERVICES_CONF = "/etc/yunohost/services.yml" +SERVICES_CONF_BASE = "/usr/share/yunohost/templates/yunohost/services.yml" + logger = getActionLogger("yunohost.service") @@ -127,7 +130,8 @@ def service_add( try: _save_services(services) - except Exception: + except Exception as e: + logger.warning(e) # we'll get a logger.warning with more details in _save_services raise YunohostError("service_add_failed", service=name) @@ -669,17 +673,19 @@ def _get_services(): """ try: - with open("/etc/yunohost/services.yml", "r") as f: - services = yaml.safe_load(f) or {} + services = read_yaml(SERVICES_CONF_BASE) or {} + + # These are keys flagged 'null' in the base conf + legacy_keys_to_delete = [k for k, v in services.items() if v is None] + + services.update(read_yaml(SERVICES_CONF) or {}) + + services = {name: infos + for name, infos in services.items() + if name not in legacy_keys_to_delete} except Exception: return {} - # some services are marked as None to remove them from YunoHost - # filter this - for key, value in list(services.items()): - if value is None: - del services[key] - # Dirty hack to automatically find custom SSH port ... ssh_port_line = re.findall( r"\bPort *([0-9]{2,5})\b", read_file("/etc/ssh/sshd_config") @@ -703,6 +709,13 @@ def _get_services(): del services["postgresql"]["description"] services["postgresql"]["actual_systemd_service"] = "postgresql@11-main" + # Remove legacy /var/log/daemon.log and /var/log/syslog from log entries + # because they are too general. Instead, now the journalctl log is + # returned by default which is more relevant. + for infos in services.values(): + if infos.get("log") in ["/var/log/syslog", "/var/log/daemon.log"]: + del infos["log"] + return services @@ -714,12 +727,26 @@ def _save_services(services): services -- A dict of managed services with their parameters """ - try: - with open("/etc/yunohost/services.yml", "w") as f: - yaml.safe_dump(services, f, default_flow_style=False) - except Exception as e: - logger.warning("Error while saving services, exception: %s", e, exc_info=1) - raise + + # Compute the diff with the base file + # such that /etc/yunohost/services.yml contains the minimal + # changes with respect to the base conf + + conf_base = yaml.safe_load(open(SERVICES_CONF_BASE)) or {} + + diff = {} + + for service_name, service_infos in services.items(): + service_conf_base = conf_base.get(service_name, {}) + diff[service_name] = {} + + for key, value in service_infos.items(): + if service_conf_base.get(key) != value: + diff[service_name][key] = value + + diff = {name: infos for name, infos in diff.items() if infos} + + write_to_yaml(SERVICES_CONF, diff) def _tail(file, n): From 9e63142748813889fc9282c90fd97aa627fc929c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 19 Aug 2021 01:10:54 +0200 Subject: [PATCH 2739/3170] Diagnosis: report suspiciously high number of auth failures --- data/hooks/diagnosis/00-basesystem.py | 21 +++++++++++++++++++++ locales/en.json | 1 + 2 files changed, 22 insertions(+) diff --git a/data/hooks/diagnosis/00-basesystem.py b/data/hooks/diagnosis/00-basesystem.py index 3623c10e2..5b4b3394c 100644 --- a/data/hooks/diagnosis/00-basesystem.py +++ b/data/hooks/diagnosis/00-basesystem.py @@ -133,6 +133,13 @@ class BaseSystemDiagnoser(Diagnoser): summary="diagnosis_backports_in_sources_list", ) + if self.number_of_recent_auth_failure() > 500: + yield dict( + meta={"test": "high_number_auth_failure"}, + status="WARNING", + summary="diagnosis_high_number_auth_failures", + ) + def bad_sury_packages(self): packages_to_check = ["openssl", "libssl1.1", "libssl-dev"] @@ -154,6 +161,20 @@ class BaseSystemDiagnoser(Diagnoser): cmd = "grep -q -nr '^ *deb .*-backports' /etc/apt/sources.list*" return os.system(cmd) == 0 + def number_of_recent_auth_failure(self): + + # Those syslog facilities correspond to auth and authpriv + # c.f. https://unix.stackexchange.com/a/401398 + # and https://wiki.archlinux.org/title/Systemd/Journal#Facility + cmd = "journalctl -q SYSLOG_FACILITY=10 SYSLOG_FACILITY=4 --since '1day ago' | grep 'authentication failure' | wc -l" + + n_failures = check_output(cmd) + try: + return int(n_failures) + except Exception: + self.logger_warning("Failed to parse number of recent auth failures, expected an int, got '%s'" % n_failures) + return -1 + def is_vulnerable_to_meltdown(self): # meltdown CVE: https://security-tracker.debian.org/tracker/CVE-2017-5754 diff --git a/locales/en.json b/locales/en.json index 693e9d24d..f6641f224 100644 --- a/locales/en.json +++ b/locales/en.json @@ -239,6 +239,7 @@ "diagnosis_rootfstotalspace_critical": "The root filesystem only has a total of {space} which is quite worrisome! You will likely run out of disk space very quickly! It's recommended to have at least 16 GB for the root filesystem.", "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_high_number_auth_failures": "There's been a suspiciously high number of authentication failures recently. You may want to make sure that fail2ban is running and is correctly configured, or use a custom port for SSH as explained in https://yunohost.org/security.", "diagnosis_description_basesystem": "Base system", "diagnosis_description_ip": "Internet connectivity", "diagnosis_description_dnsrecords": "DNS records", From 3c07e55017d2d2c42a45c9aea027396f1121913f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 19 Aug 2021 18:14:15 +0200 Subject: [PATCH 2740/3170] [fix] hook_exec / subprocess.Popen explodes if you feed non-string values in env variables --- 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 ef753d4d6..fad2033a6 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1233,7 +1233,7 @@ def app_remove(operation_logger, app, purge=False): 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_PURGE"] = purge + env_dict["YNH_APP_PURGE"] = str(purge) operation_logger.extra.update({"env": env_dict}) operation_logger.flush() From e3f11bec0982e3ba9e3b9800a4acc1d104c56107 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 19 Aug 2021 18:41:47 +0200 Subject: [PATCH 2741/3170] NO_BACKUP_UPGRADE: helpers expect 0 or 1 --- 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 fad2033a6..57498c644 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -619,7 +619,7 @@ def app_upgrade(app=[], url=None, file=None, force=False, no_safety_backup=False env_dict["YNH_APP_UPGRADE_TYPE"] = upgrade_type env_dict["YNH_APP_MANIFEST_VERSION"] = str(app_new_version) env_dict["YNH_APP_CURRENT_VERSION"] = str(app_current_version) - env_dict["NO_BACKUP_UPGRADE"] = no_safety_backup + env_dict["NO_BACKUP_UPGRADE"] = "1" if no_safety_backup else "0" # We'll check that the app didn't brutally edit some system configuration manually_modified_files_before_install = manually_modified_files() From b452838a1797641f82778e60f4e50e1722837eba Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 19 Aug 2021 19:16:29 +0200 Subject: [PATCH 2742/3170] Update changelog for 4.2.8 --- debian/changelog | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/debian/changelog b/debian/changelog index 57da44532..b1894758a 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,16 @@ +yunohost (4.2.8) stable; urgency=low + + - [fix] ynh_permission_has_user not behaving properly when checking if a group is allowed (f0590907) + - [enh] use yaml safeloader everywhere ([#1287](https://github.com/YunoHost/yunohost/pull/1287)) + - [enh] Add --no-safety-backup option to "yunohost app upgrade" ([#1286](https://github.com/YunoHost/yunohost/pull/1286)) + - [enh] Add --purge option to "yunohost app remove" ([#1285](https://github.com/YunoHost/yunohost/pull/1285)) + - [enh] Multimedia helper: check that home folder exists ([#1255](https://github.com/YunoHost/yunohost/pull/1255)) + - [i18n] Translations updated for French, Galician, German, Portuguese + + Thanks to all contributors <3 ! (José M, Kay0u, Krakinou, ljf, Luca, mifegui, ppr, sagessylu) + + -- Alexandre Aubin Thu, 19 Aug 2021 19:11:19 +0200 + yunohost (4.2.7) stable; urgency=low Notable changes: From ecf136d5db27caae21656091b948eb825369b248 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 19 Aug 2021 20:28:20 +0200 Subject: [PATCH 2743/3170] Auto-enable yunomdns on legacy systems --- data/hooks/conf_regen/37-mdns | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/data/hooks/conf_regen/37-mdns b/data/hooks/conf_regen/37-mdns index c85e43a9a..1d7381e26 100755 --- a/data/hooks/conf_regen/37-mdns +++ b/data/hooks/conf_regen/37-mdns @@ -51,6 +51,12 @@ do_post_regen() { systemctl daemon-reload fi + # Legacy stuff to enable the new yunomdns service on legacy systems + if [[ -e /etc/avahi/avahi-daemon.conf ]] && grep -q 'yunohost' /etc/avahi/avahi-daemon.conf + then + systemctl enable yunomdns + fi + [[ -z "$regen_conf_files" ]] \ || systemctl restart yunomdns } From b8c8ac0b2de6e2a5c91f2c50dab54209ec619faa Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 19 Aug 2021 20:29:28 +0200 Subject: [PATCH 2744/3170] Gotta also remove libnss-mdns if we want to get rid of avahi-daemon --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index c9306bef1..37eccb5dd 100644 --- a/debian/control +++ b/debian/control @@ -21,7 +21,7 @@ Depends: ${python3:Depends}, ${misc:Depends} , openssh-server, iptables, fail2ban, dnsutils, bind9utils , openssl, ca-certificates, netcat-openbsd, iproute2 , slapd, ldap-utils, sudo-ldap, libnss-ldapd, unscd, libpam-ldapd - , dnsmasq, libnss-mdns, resolvconf, libnss-myhostname + , dnsmasq, resolvconf, libnss-myhostname , postfix, postfix-ldap, postfix-policyd-spf-perl, postfix-pcre , dovecot-core, dovecot-ldap, dovecot-lmtpd, dovecot-managesieved, dovecot-antispam , rspamd, opendkim-tools, postsrsd, procmail, mailutils From 5249be031f1ea6d7e72d406487839e3d082c0b8f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 20 Aug 2021 10:12:35 +0200 Subject: [PATCH 2745/3170] Propagate msignal change --- src/yunohost/tests/conftest.py | 5 + .../tests/test_apps_arguments_parsing.py | 102 +++++++++--------- 2 files changed, 56 insertions(+), 51 deletions(-) diff --git a/src/yunohost/tests/conftest.py b/src/yunohost/tests/conftest.py index 1bf035748..9dfe2b39c 100644 --- a/src/yunohost/tests/conftest.py +++ b/src/yunohost/tests/conftest.py @@ -82,5 +82,10 @@ def pytest_cmdline_main(config): yunohost.init(debug=config.option.yunodebug) class DummyInterface(): + type = "test" + + def prompt(*args, **kwargs): + raise NotImplementedError + Moulinette._interface = DummyInterface() diff --git a/src/yunohost/tests/test_apps_arguments_parsing.py b/src/yunohost/tests/test_apps_arguments_parsing.py index 95d1548ae..573c18cb2 100644 --- a/src/yunohost/tests/test_apps_arguments_parsing.py +++ b/src/yunohost/tests/test_apps_arguments_parsing.py @@ -5,7 +5,7 @@ from mock import patch from io import StringIO from collections import OrderedDict -from moulinette import msignals +from moulinette import Moulinette from yunohost import domain, user from yunohost.app import _parse_args_in_yunohost_format, PasswordArgumentParser @@ -84,7 +84,7 @@ def test_parse_args_in_yunohost_format_string_input(): answers = {} expected_result = OrderedDict({"some_string": ("some_value", "string")}) - with patch.object(msignals, "prompt", return_value="some_value"): + with patch.object(Moulinette.interface, "prompt", return_value="some_value"): assert _parse_args_in_yunohost_format(answers, questions) == expected_result @@ -97,7 +97,7 @@ def test_parse_args_in_yunohost_format_string_input_no_ask(): answers = {} expected_result = OrderedDict({"some_string": ("some_value", "string")}) - with patch.object(msignals, "prompt", return_value="some_value"): + with patch.object(Moulinette.interface, "prompt", return_value="some_value"): assert _parse_args_in_yunohost_format(answers, questions) == expected_result @@ -124,7 +124,7 @@ def test_parse_args_in_yunohost_format_string_optional_with_input(): answers = {} expected_result = OrderedDict({"some_string": ("some_value", "string")}) - with patch.object(msignals, "prompt", return_value="some_value"): + with patch.object(Moulinette.interface, "prompt", return_value="some_value"): assert _parse_args_in_yunohost_format(answers, questions) == expected_result @@ -139,7 +139,7 @@ def test_parse_args_in_yunohost_format_string_optional_with_empty_input(): answers = {} expected_result = OrderedDict({"some_string": ("", "string")}) - with patch.object(msignals, "prompt", return_value=""): + with patch.object(Moulinette.interface, "prompt", return_value=""): assert _parse_args_in_yunohost_format(answers, questions) == expected_result @@ -153,7 +153,7 @@ def test_parse_args_in_yunohost_format_string_optional_with_input_without_ask(): answers = {} expected_result = OrderedDict({"some_string": ("some_value", "string")}) - with patch.object(msignals, "prompt", return_value="some_value"): + with patch.object(Moulinette.interface, "prompt", return_value="some_value"): assert _parse_args_in_yunohost_format(answers, questions) == expected_result @@ -180,7 +180,7 @@ def test_parse_args_in_yunohost_format_string_input_test_ask(): ] answers = {} - with patch.object(msignals, "prompt", return_value="some_value") as prompt: + with patch.object(Moulinette.interface, "prompt", return_value="some_value") as prompt: _parse_args_in_yunohost_format(answers, questions) prompt.assert_called_with(ask_text, False) @@ -197,7 +197,7 @@ def test_parse_args_in_yunohost_format_string_input_test_ask_with_default(): ] answers = {} - with patch.object(msignals, "prompt", return_value="some_value") as prompt: + with patch.object(Moulinette.interface, "prompt", return_value="some_value") as prompt: _parse_args_in_yunohost_format(answers, questions) prompt.assert_called_with("%s (default: %s)" % (ask_text, default_text), False) @@ -215,7 +215,7 @@ def test_parse_args_in_yunohost_format_string_input_test_ask_with_example(): ] answers = {} - with patch.object(msignals, "prompt", return_value="some_value") as prompt: + with patch.object(Moulinette.interface, "prompt", return_value="some_value") as prompt: _parse_args_in_yunohost_format(answers, questions) assert ask_text in prompt.call_args[0][0] assert example_text in prompt.call_args[0][0] @@ -234,7 +234,7 @@ def test_parse_args_in_yunohost_format_string_input_test_ask_with_help(): ] answers = {} - with patch.object(msignals, "prompt", return_value="some_value") as prompt: + with patch.object(Moulinette.interface, "prompt", return_value="some_value") as prompt: _parse_args_in_yunohost_format(answers, questions) assert ask_text in prompt.call_args[0][0] assert help_text in prompt.call_args[0][0] @@ -251,7 +251,7 @@ def test_parse_args_in_yunohost_format_string_with_choice_prompt(): questions = [{"name": "some_string", "type": "string", "choices": ["fr", "en"]}] answers = {"some_string": "fr"} expected_result = OrderedDict({"some_string": ("fr", "string")}) - with patch.object(msignals, "prompt", return_value="fr"): + with patch.object(Moulinette.interface, "prompt", return_value="fr"): assert _parse_args_in_yunohost_format(answers, questions) == expected_result @@ -275,7 +275,7 @@ def test_parse_args_in_yunohost_format_string_with_choice_ask(): ] answers = {} - with patch.object(msignals, "prompt", return_value="ru") as prompt: + with patch.object(Moulinette.interface, "prompt", return_value="ru") as prompt: _parse_args_in_yunohost_format(answers, questions) assert ask_text in prompt.call_args[0][0] @@ -333,7 +333,7 @@ def test_parse_args_in_yunohost_format_password_input(): answers = {} expected_result = OrderedDict({"some_password": ("some_value", "password")}) - with patch.object(msignals, "prompt", return_value="some_value"): + with patch.object(Moulinette.interface, "prompt", return_value="some_value"): assert _parse_args_in_yunohost_format(answers, questions) == expected_result @@ -347,7 +347,7 @@ def test_parse_args_in_yunohost_format_password_input_no_ask(): answers = {} expected_result = OrderedDict({"some_password": ("some_value", "password")}) - with patch.object(msignals, "prompt", return_value="some_value"): + with patch.object(Moulinette.interface, "prompt", return_value="some_value"): assert _parse_args_in_yunohost_format(answers, questions) == expected_result @@ -383,7 +383,7 @@ def test_parse_args_in_yunohost_format_password_optional_with_input(): answers = {} expected_result = OrderedDict({"some_password": ("some_value", "password")}) - with patch.object(msignals, "prompt", return_value="some_value"): + with patch.object(Moulinette.interface, "prompt", return_value="some_value"): assert _parse_args_in_yunohost_format(answers, questions) == expected_result @@ -399,7 +399,7 @@ def test_parse_args_in_yunohost_format_password_optional_with_empty_input(): answers = {} expected_result = OrderedDict({"some_password": ("", "password")}) - with patch.object(msignals, "prompt", return_value=""): + with patch.object(Moulinette.interface, "prompt", return_value=""): assert _parse_args_in_yunohost_format(answers, questions) == expected_result @@ -414,7 +414,7 @@ def test_parse_args_in_yunohost_format_password_optional_with_input_without_ask( answers = {} expected_result = OrderedDict({"some_password": ("some_value", "password")}) - with patch.object(msignals, "prompt", return_value="some_value"): + with patch.object(Moulinette.interface, "prompt", return_value="some_value"): assert _parse_args_in_yunohost_format(answers, questions) == expected_result @@ -462,7 +462,7 @@ def test_parse_args_in_yunohost_format_password_input_test_ask(): ] answers = {} - with patch.object(msignals, "prompt", return_value="some_value") as prompt: + with patch.object(Moulinette.interface, "prompt", return_value="some_value") as prompt: _parse_args_in_yunohost_format(answers, questions) prompt.assert_called_with(ask_text, True) @@ -481,7 +481,7 @@ def test_parse_args_in_yunohost_format_password_input_test_ask_with_example(): ] answers = {} - with patch.object(msignals, "prompt", return_value="some_value") as prompt: + with patch.object(Moulinette.interface, "prompt", return_value="some_value") as prompt: _parse_args_in_yunohost_format(answers, questions) assert ask_text in prompt.call_args[0][0] assert example_text in prompt.call_args[0][0] @@ -501,7 +501,7 @@ def test_parse_args_in_yunohost_format_password_input_test_ask_with_help(): ] answers = {} - with patch.object(msignals, "prompt", return_value="some_value") as prompt: + with patch.object(Moulinette.interface, "prompt", return_value="some_value") as prompt: _parse_args_in_yunohost_format(answers, questions) assert ask_text in prompt.call_args[0][0] assert help_text in prompt.call_args[0][0] @@ -594,7 +594,7 @@ def test_parse_args_in_yunohost_format_path_input(): answers = {} expected_result = OrderedDict({"some_path": ("some_value", "path")}) - with patch.object(msignals, "prompt", return_value="some_value"): + with patch.object(Moulinette.interface, "prompt", return_value="some_value"): assert _parse_args_in_yunohost_format(answers, questions) == expected_result @@ -608,7 +608,7 @@ def test_parse_args_in_yunohost_format_path_input_no_ask(): answers = {} expected_result = OrderedDict({"some_path": ("some_value", "path")}) - with patch.object(msignals, "prompt", return_value="some_value"): + with patch.object(Moulinette.interface, "prompt", return_value="some_value"): assert _parse_args_in_yunohost_format(answers, questions) == expected_result @@ -637,7 +637,7 @@ def test_parse_args_in_yunohost_format_path_optional_with_input(): answers = {} expected_result = OrderedDict({"some_path": ("some_value", "path")}) - with patch.object(msignals, "prompt", return_value="some_value"): + with patch.object(Moulinette.interface, "prompt", return_value="some_value"): assert _parse_args_in_yunohost_format(answers, questions) == expected_result @@ -653,7 +653,7 @@ def test_parse_args_in_yunohost_format_path_optional_with_empty_input(): answers = {} expected_result = OrderedDict({"some_path": ("", "path")}) - with patch.object(msignals, "prompt", return_value=""): + with patch.object(Moulinette.interface, "prompt", return_value=""): assert _parse_args_in_yunohost_format(answers, questions) == expected_result @@ -668,7 +668,7 @@ def test_parse_args_in_yunohost_format_path_optional_with_input_without_ask(): answers = {} expected_result = OrderedDict({"some_path": ("some_value", "path")}) - with patch.object(msignals, "prompt", return_value="some_value"): + with patch.object(Moulinette.interface, "prompt", return_value="some_value"): assert _parse_args_in_yunohost_format(answers, questions) == expected_result @@ -697,7 +697,7 @@ def test_parse_args_in_yunohost_format_path_input_test_ask(): ] answers = {} - with patch.object(msignals, "prompt", return_value="some_value") as prompt: + with patch.object(Moulinette.interface, "prompt", return_value="some_value") as prompt: _parse_args_in_yunohost_format(answers, questions) prompt.assert_called_with(ask_text, False) @@ -715,7 +715,7 @@ def test_parse_args_in_yunohost_format_path_input_test_ask_with_default(): ] answers = {} - with patch.object(msignals, "prompt", return_value="some_value") as prompt: + with patch.object(Moulinette.interface, "prompt", return_value="some_value") as prompt: _parse_args_in_yunohost_format(answers, questions) prompt.assert_called_with("%s (default: %s)" % (ask_text, default_text), False) @@ -734,7 +734,7 @@ def test_parse_args_in_yunohost_format_path_input_test_ask_with_example(): ] answers = {} - with patch.object(msignals, "prompt", return_value="some_value") as prompt: + with patch.object(Moulinette.interface, "prompt", return_value="some_value") as prompt: _parse_args_in_yunohost_format(answers, questions) assert ask_text in prompt.call_args[0][0] assert example_text in prompt.call_args[0][0] @@ -754,7 +754,7 @@ def test_parse_args_in_yunohost_format_path_input_test_ask_with_help(): ] answers = {} - with patch.object(msignals, "prompt", return_value="some_value") as prompt: + with patch.object(Moulinette.interface, "prompt", return_value="some_value") as prompt: _parse_args_in_yunohost_format(answers, questions) assert ask_text in prompt.call_args[0][0] assert help_text in prompt.call_args[0][0] @@ -918,11 +918,11 @@ def test_parse_args_in_yunohost_format_boolean_input(): answers = {} expected_result = OrderedDict({"some_boolean": (1, "boolean")}) - with patch.object(msignals, "prompt", return_value="y"): + with patch.object(Moulinette.interface, "prompt", return_value="y"): assert _parse_args_in_yunohost_format(answers, questions) == expected_result expected_result = OrderedDict({"some_boolean": (0, "boolean")}) - with patch.object(msignals, "prompt", return_value="n"): + with patch.object(Moulinette.interface, "prompt", return_value="n"): assert _parse_args_in_yunohost_format(answers, questions) == expected_result @@ -936,7 +936,7 @@ def test_parse_args_in_yunohost_format_boolean_input_no_ask(): answers = {} expected_result = OrderedDict({"some_boolean": (1, "boolean")}) - with patch.object(msignals, "prompt", return_value="y"): + with patch.object(Moulinette.interface, "prompt", return_value="y"): assert _parse_args_in_yunohost_format(answers, questions) == expected_result @@ -965,7 +965,7 @@ def test_parse_args_in_yunohost_format_boolean_optional_with_input(): answers = {} expected_result = OrderedDict({"some_boolean": (1, "boolean")}) - with patch.object(msignals, "prompt", return_value="y"): + with patch.object(Moulinette.interface, "prompt", return_value="y"): assert _parse_args_in_yunohost_format(answers, questions) == expected_result @@ -981,7 +981,7 @@ def test_parse_args_in_yunohost_format_boolean_optional_with_empty_input(): answers = {} expected_result = OrderedDict({"some_boolean": (0, "boolean")}) # default to false - with patch.object(msignals, "prompt", return_value=""): + with patch.object(Moulinette.interface, "prompt", return_value=""): assert _parse_args_in_yunohost_format(answers, questions) == expected_result @@ -996,7 +996,7 @@ def test_parse_args_in_yunohost_format_boolean_optional_with_input_without_ask() answers = {} expected_result = OrderedDict({"some_boolean": (0, "boolean")}) - with patch.object(msignals, "prompt", return_value="n"): + with patch.object(Moulinette.interface, "prompt", return_value="n"): assert _parse_args_in_yunohost_format(answers, questions) == expected_result @@ -1039,7 +1039,7 @@ def test_parse_args_in_yunohost_format_boolean_input_test_ask(): ] answers = {} - with patch.object(msignals, "prompt", return_value=0) as prompt: + with patch.object(Moulinette.interface, "prompt", return_value=0) as prompt: _parse_args_in_yunohost_format(answers, questions) prompt.assert_called_with(ask_text + " [yes | no] (default: no)", False) @@ -1057,7 +1057,7 @@ def test_parse_args_in_yunohost_format_boolean_input_test_ask_with_default(): ] answers = {} - with patch.object(msignals, "prompt", return_value=1) as prompt: + with patch.object(Moulinette.interface, "prompt", return_value=1) as prompt: _parse_args_in_yunohost_format(answers, questions) prompt.assert_called_with("%s [yes | no] (default: yes)" % ask_text, False) @@ -1193,11 +1193,11 @@ def test_parse_args_in_yunohost_format_domain_two_domains_default_input(): domain, "_get_maindomain", return_value=main_domain ), patch.object(domain, "domain_list", return_value={"domains": domains}): expected_result = OrderedDict({"some_domain": (main_domain, "domain")}) - with patch.object(msignals, "prompt", return_value=main_domain): + with patch.object(Moulinette.interface, "prompt", return_value=main_domain): assert _parse_args_in_yunohost_format(answers, questions) == expected_result expected_result = OrderedDict({"some_domain": (other_domain, "domain")}) - with patch.object(msignals, "prompt", return_value=other_domain): + with patch.object(Moulinette.interface, "prompt", return_value=other_domain): assert _parse_args_in_yunohost_format(answers, questions) == expected_result @@ -1380,14 +1380,14 @@ def test_parse_args_in_yunohost_format_user_two_users_default_input(): with patch.object(user, "user_list", return_value={"users": users}): with patch.object(user, "user_info", return_value={}): expected_result = OrderedDict({"some_user": (username, "user")}) - with patch.object(msignals, "prompt", return_value=username): + with patch.object(Moulinette.interface, "prompt", return_value=username): assert ( _parse_args_in_yunohost_format(answers, questions) == expected_result ) expected_result = OrderedDict({"some_user": (other_user, "user")}) - with patch.object(msignals, "prompt", return_value=other_user): + with patch.object(Moulinette.interface, "prompt", return_value=other_user): assert ( _parse_args_in_yunohost_format(answers, questions) == expected_result @@ -1447,14 +1447,14 @@ def test_parse_args_in_yunohost_format_number_input(): answers = {} expected_result = OrderedDict({"some_number": (1337, "number")}) - with patch.object(msignals, "prompt", return_value="1337"): + with patch.object(Moulinette.interface, "prompt", return_value="1337"): assert _parse_args_in_yunohost_format(answers, questions) == expected_result - with patch.object(msignals, "prompt", return_value=1337): + with patch.object(Moulinette.interface, "prompt", return_value=1337): assert _parse_args_in_yunohost_format(answers, questions) == expected_result expected_result = OrderedDict({"some_number": (0, "number")}) - with patch.object(msignals, "prompt", return_value=""): + with patch.object(Moulinette.interface, "prompt", return_value=""): assert _parse_args_in_yunohost_format(answers, questions) == expected_result @@ -1468,7 +1468,7 @@ def test_parse_args_in_yunohost_format_number_input_no_ask(): answers = {} expected_result = OrderedDict({"some_number": (1337, "number")}) - with patch.object(msignals, "prompt", return_value="1337"): + with patch.object(Moulinette.interface, "prompt", return_value="1337"): assert _parse_args_in_yunohost_format(answers, questions) == expected_result @@ -1497,7 +1497,7 @@ def test_parse_args_in_yunohost_format_number_optional_with_input(): answers = {} expected_result = OrderedDict({"some_number": (1337, "number")}) - with patch.object(msignals, "prompt", return_value="1337"): + with patch.object(Moulinette.interface, "prompt", return_value="1337"): assert _parse_args_in_yunohost_format(answers, questions) == expected_result @@ -1512,7 +1512,7 @@ def test_parse_args_in_yunohost_format_number_optional_with_input_without_ask(): answers = {} expected_result = OrderedDict({"some_number": (0, "number")}) - with patch.object(msignals, "prompt", return_value="0"): + with patch.object(Moulinette.interface, "prompt", return_value="0"): assert _parse_args_in_yunohost_format(answers, questions) == expected_result @@ -1555,7 +1555,7 @@ def test_parse_args_in_yunohost_format_number_input_test_ask(): ] answers = {} - with patch.object(msignals, "prompt", return_value="1111") as prompt: + with patch.object(Moulinette.interface, "prompt", return_value="1111") as prompt: _parse_args_in_yunohost_format(answers, questions) prompt.assert_called_with("%s (default: 0)" % (ask_text), False) @@ -1573,7 +1573,7 @@ def test_parse_args_in_yunohost_format_number_input_test_ask_with_default(): ] answers = {} - with patch.object(msignals, "prompt", return_value="1111") as prompt: + with patch.object(Moulinette.interface, "prompt", return_value="1111") as prompt: _parse_args_in_yunohost_format(answers, questions) prompt.assert_called_with("%s (default: %s)" % (ask_text, default_value), False) @@ -1592,7 +1592,7 @@ def test_parse_args_in_yunohost_format_number_input_test_ask_with_example(): ] answers = {} - with patch.object(msignals, "prompt", return_value="1111") as prompt: + with patch.object(Moulinette.interface, "prompt", return_value="1111") as prompt: _parse_args_in_yunohost_format(answers, questions) assert ask_text in prompt.call_args[0][0] assert example_value in prompt.call_args[0][0] @@ -1612,7 +1612,7 @@ def test_parse_args_in_yunohost_format_number_input_test_ask_with_help(): ] answers = {} - with patch.object(msignals, "prompt", return_value="1111") as prompt: + with patch.object(Moulinette.interface, "prompt", return_value="1111") as prompt: _parse_args_in_yunohost_format(answers, questions) assert ask_text in prompt.call_args[0][0] assert help_value in prompt.call_args[0][0] From a89dd4827c1f072b0e808b6b4b669909aad58179 Mon Sep 17 00:00:00 2001 From: ljf Date: Fri, 20 Aug 2021 16:35:02 +0200 Subject: [PATCH 2746/3170] [enh] Use yaml for reading hook output --- src/yunohost/hook.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/yunohost/hook.py b/src/yunohost/hook.py index 33f5885e2..4d497de76 100644 --- a/src/yunohost/hook.py +++ b/src/yunohost/hook.py @@ -34,7 +34,7 @@ from importlib import import_module from moulinette import m18n, msettings from yunohost.utils.error import YunohostError, YunohostValidationError from moulinette.utils import log -from moulinette.utils.filesystem import read_json +from moulinette.utils.filesystem import read_yaml HOOK_FOLDER = "/usr/share/yunohost/hooks/" CUSTOM_HOOK_FOLDER = "/etc/yunohost/hooks.d/" @@ -326,7 +326,7 @@ def hook_exec( chdir=None, env=None, user="root", - return_format="json", + return_format="yaml", ): """ Execute hook from a file with arguments @@ -447,10 +447,10 @@ def _hook_exec_bash(path, args, chdir, env, user, return_format, loggers): raw_content = f.read() returncontent = {} - if return_format == "json": + if return_format == "yaml": if raw_content != "": try: - returncontent = read_json(stdreturn) + returncontent = read_yaml(stdreturn) except Exception as e: raise YunohostError( "hook_json_return_error", From 98ca514f8fdff264eda071dbeb5244e8548e1657 Mon Sep 17 00:00:00 2001 From: ljf Date: Fri, 20 Aug 2021 16:35:51 +0200 Subject: [PATCH 2747/3170] [enh] Rewrite config show, get, set actions --- data/actionsmap/yunohost.yml | 45 +++- data/helpers.d/configpanel | 29 +-- locales/en.json | 8 +- src/yunohost/app.py | 444 ++++++++++++++++++++++------------- 4 files changed, 327 insertions(+), 199 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 5df1c0877..d9e3a50d0 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -831,24 +831,47 @@ app: subcategory_help: Applications configuration panel actions: - ### app_config_show_panel() - show-panel: + ### app_config_show() + show: action_help: show config panel for the application api: GET /apps//config-panel arguments: - app: - help: App name + app: + help: App name + panel: + help: Select a specific panel + nargs: '?' + -f: + full: --full + help: Display all info known about the config-panel. + action: store_true - ### app_config_apply() - apply: + ### app_config_get() + get: + action_help: show config panel for the application + api: GET /apps//config-panel/ + arguments: + app: + help: App name + key: + help: The question identifier + + ### app_config_set() + set: action_help: apply the new configuration api: PUT /apps//config arguments: - app: - help: App name - -a: - full: --args - help: Serialized arguments for new configuration (i.e. "domain=domain.tld&path=/path") + app: + help: App name + key: + help: The question or panel key + nargs: '?' + -v: + full: --value + help: new value + -a: + full: --args + help: Serialized arguments for new configuration (i.e. "domain=domain.tld&path=/path") ############################# # Backup # diff --git a/data/helpers.d/configpanel b/data/helpers.d/configpanel index 685f30a98..5b290629d 100644 --- a/data/helpers.d/configpanel +++ b/data/helpers.d/configpanel @@ -1,10 +1,5 @@ #!/bin/bash -ynh_lowerdot_to_uppersnake() { - local lowerdot - lowerdot=$(echo "$1" | cut -d= -f1 | sed "s/\./_/g") - echo "${lowerdot^^}" -} # Get a value from heterogeneous file (yaml, json, php, python...) # @@ -99,7 +94,6 @@ ynh_value_set() { } _ynh_panel_get() { - set +x # From settings local params_sources params_sources=`python3 << EOL @@ -114,7 +108,7 @@ for panel_name,panel in loaded_toml.items(): for section_name, section in panel.items(): if isinstance(section, dict): for name, param in section.items(): - if isinstance(param, dict) and param.get('type', 'string') not in ['info', 'warning', 'error']: + if isinstance(param, dict) and param.get('type', 'string') not in ['success', 'info', 'warning', 'danger', 'display_text', 'markdown']: print("%s=%s" % (name, param.get('source', 'settings'))) EOL ` @@ -147,7 +141,6 @@ EOL file_hash[$short_setting]="true" fi done - set -x } @@ -164,7 +157,7 @@ _ynh_panel_apply() { # Copy file in right place elif [[ "$source" == "settings" ]] ; then - ynh_app_setting_set $app $short_setting "${new[$short_setting]}" + ynh_app_setting_set $app $short_setting "${!short_setting}" # Get value from a kind of key/value file elif [[ "$source" == *":"* ]] @@ -172,11 +165,11 @@ _ynh_panel_apply() { local source_key="$(echo "$source" | cut -d: -f1)" source_key=${source_key:-$short_setting} local source_file="$(echo "$source" | cut -d: -f2)" - ynh_value_set --file="${source_file}" --key="${source_key}" --value="${new[$short_setting]}" + ynh_value_set --file="${source_file}" --key="${source_key}" --value="${!short_setting}" # Specific case for files (all content of the file is the source) else - cp "${new[$short_setting]}" "$source" + cp "${!short_setting}" "$source" fi fi done @@ -185,25 +178,25 @@ _ynh_panel_apply() { _ynh_panel_show() { for short_setting in "${!old[@]}" do - ynh_return "${short_setting}=${old[$short_setting]}" + ynh_return "${short_setting}: \"${old[$short_setting]}\"" done } _ynh_panel_validate() { - set +x # Change detection local is_error=true #for changed_status in "${!changed[@]}" for short_setting in "${!old[@]}" do changed[$short_setting]=false + [ -z ${!short_setting+x} ] && continue if [ ! -z "${file_hash[${short_setting}]}" ] ; then file_hash[old__$short_setting]="" file_hash[new__$short_setting]="" if [ -f "${old[$short_setting]}" ] ; then file_hash[old__$short_setting]=$(sha256sum "${old[$short_setting]}" | cut -d' ' -f1) fi - if [ -f "${new[$short_setting]}" ] ; then + if [ -f "${!short_setting}" ] ; then file_hash[new__$short_setting]=$(sha256sum "${new[$short_setting]}" | cut -d' ' -f1) if [[ "${file_hash[old__$short_setting]}" != "${file_hash[new__$short_setting]}" ]] then @@ -211,7 +204,7 @@ _ynh_panel_validate() { fi fi else - if [[ "${new[$short_setting]}" != "${old[$short_setting]}" ]] + if [[ "${!short_setting}" != "${old[$short_setting]}" ]] then changed[$short_setting]=true is_error=false @@ -242,7 +235,6 @@ _ynh_panel_validate() { then ynh_die "" fi - set -x } @@ -264,15 +256,14 @@ ynh_panel_apply() { ynh_panel_run() { declare -Ag old=() - declare -Ag new=() declare -Ag changed=() declare -Ag file_hash=() declare -Ag sources=() ynh_panel_get case $1 in - show) ynh_panel_show;; - apply) ynh_panel_validate && ynh_panel_apply;; + show) ynh_panel_get && ynh_panel_show;; + apply) ynh_panel_get && ynh_panel_validate && ynh_panel_apply;; esac } diff --git a/locales/en.json b/locales/en.json index 693e9d24d..1f13b3e90 100644 --- a/locales/en.json +++ b/locales/en.json @@ -13,7 +13,7 @@ "app_already_installed": "{app} is already installed", "app_already_installed_cant_change_url": "This app is already installed. The URL cannot be changed just by this function. Check in `app changeurl` if it's available.", "app_already_up_to_date": "{app} is already up-to-date", - "app_argument_choice_invalid": "Use one of these choices '{choices}' for the argument '{name}'", + "app_argument_choice_invalid": "Use one of these choices '{choices}' for the argument '{name}' instead of '{value}'", "app_argument_invalid": "Pick a valid value for the argument '{name}': {error}", "app_argument_password_no_default": "Error while parsing password argument '{name}': password argument can't have a default value for security reason", "app_argument_required": "Argument '{name}' is required", @@ -143,6 +143,7 @@ "confirm_app_install_danger": "DANGER! This app is known to be still experimental (if not explicitly not working)! You should probably NOT install it unless you know what you are doing. NO SUPPORT will be provided if this app doesn't work or breaks your system… If you are willing to take that risk anyway, type '{answers}'", "confirm_app_install_thirdparty": "DANGER! This app is not part of YunoHost's app catalog. Installing third-party apps may compromise the integrity and security of your system. You should probably NOT install it unless you know what you are doing. NO SUPPORT will be provided if this app doesn't work or breaks your system… If you are willing to take that risk anyway, type '{answers}'", "custom_app_url_required": "You must provide a URL to upgrade your custom app {app}", + "danger": "Danger:", "diagnosis_basesystem_hardware": "Server hardware architecture is {virt} {arch}", "diagnosis_basesystem_hardware_model": "Server model is {model}", "diagnosis_basesystem_host": "Server is running Debian {debian_version}", @@ -382,8 +383,9 @@ "log_app_upgrade": "Upgrade the '{}' app", "log_app_makedefault": "Make '{}' the default app", "log_app_action_run": "Run action of the '{}' app", - "log_app_config_show_panel": "Show the config panel of the '{}' app", - "log_app_config_apply": "Apply config to the '{}' app", + "log_app_config_show": "Show the config panel of the '{}' app", + "log_app_config_get": "Get a specific setting from config panel of the '{}' app", + "log_app_config_set": "Apply config to the '{}' app", "log_available_on_yunopaste": "This log is now available via {url}", "log_backup_create": "Create a backup archive", "log_backup_restore_system": "Restore system from a backup archive", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index faa5098c9..c489cceaa 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -38,6 +38,7 @@ import tempfile from collections import OrderedDict from moulinette import msignals, m18n, msettings +from moulinette.interfaces.cli import colorize from moulinette.core import MoulinetteError from moulinette.utils.log import getActionLogger from moulinette.utils.network import download_json @@ -190,10 +191,7 @@ def app_info(app, full=False): """ from yunohost.permission import user_permission_list - if not _is_installed(app): - raise YunohostValidationError( - "app_not_installed", app=app, all_apps=_get_all_installed_apps_id() - ) + _assert_is_installed(app) local_manifest = _get_manifest_of_app(os.path.join(APPS_SETTING_PATH, app)) permissions = user_permission_list(full=True, absolute_urls=True, apps=[app])[ @@ -534,10 +532,8 @@ def app_upgrade(app=[], url=None, file=None, force=False): apps = [app_ for i, app_ in enumerate(apps) if app_ not in apps[:i]] # Abort if any of those app is in fact not installed.. - for app in [app_ for app_ in apps if not _is_installed(app_)]: - raise YunohostValidationError( - "app_not_installed", app=app, all_apps=_get_all_installed_apps_id() - ) + for app_ in apps: + _assert_is_installed(app_) if len(apps) == 0: raise YunohostValidationError("apps_already_up_to_date") @@ -750,7 +746,6 @@ def app_upgrade(app=[], url=None, file=None, force=False): for file_to_copy in [ "actions.json", "actions.toml", - "config_panel.json", "config_panel.toml", "conf", ]: @@ -970,7 +965,6 @@ def app_install( for file_to_copy in [ "actions.json", "actions.toml", - "config_panel.json", "config_panel.toml", "conf", ]: @@ -1759,165 +1753,143 @@ def app_action_run(operation_logger, app, action, args=None): # * docstrings # * merge translations on the json once the workflow is in place @is_unit_operation() -def app_config_show_panel(operation_logger, app): - logger.warning(m18n.n("experimental_feature")) +def app_config_show(operation_logger, app, panel='', full=False): + # logger.warning(m18n.n("experimental_feature")) - from yunohost.hook import hook_exec - - # this will take care of checking if the app is installed - app_info_dict = app_info(app) + # Check app is installed + _assert_is_installed(app) + panel = panel if panel else '' operation_logger.start() - config_panel = _get_app_config_panel(app) - config_script = os.path.join(APPS_SETTING_PATH, app, "scripts", "config") - app_id, app_instance_nb = _parse_app_instance_name(app) + # Read config panel toml + config_panel = _get_app_config_panel(app, filter_key=panel) - if not config_panel or not os.path.exists(config_script): - return { - "app_id": app_id, - "app": app, - "app_name": app_info_dict["name"], - "config_panel": [], + if not config_panel: + return None + + # Call config script to extract current values + parsed_values = _call_config_script(app, 'show') + + # # Check and transform values if needed + # options = [option for _, _, option in _get_options_iterator(config_panel)] + # args_dict = _parse_args_in_yunohost_format( + # parsed_values, options, False + # ) + + # Hydrate + logger.debug("Hydrating config with current value") + for _, _, option in _get_options_iterator(config_panel): + if option['name'] in parsed_values: + option["value"] = parsed_values[option['name']] #args_dict[option["name"]][0] + + # Format result in full or reduce mode + if full: + operation_logger.success() + return config_panel + + result = OrderedDict() + for panel, section, option in _get_options_iterator(config_panel): + if panel['id'] not in result: + r_panel = result[panel['id']] = OrderedDict() + if section['id'] not in r_panel: + r_section = r_panel[section['id']] = OrderedDict() + r_option = r_section[option['name']] = { + "ask": option['ask']['en'] } + if not option.get('optional', False): + r_option['ask'] += ' *' + if option.get('value', None) is not None: + r_option['value'] = option['value'] - env = { - "app_id": app_id, - "app": app, - "app_instance_nb": str(app_instance_nb), - } - - try: - ret, parsed_values = hook_exec( - config_script, args=["show"], env=env, return_format="plain_dict" - ) - # Here again, calling hook_exec could fail miserably, or get - # manually interrupted (by mistake or because script was stuck) - except (KeyboardInterrupt, EOFError, Exception): - raise YunohostError("unexpected_error") - - logger.debug("Generating global variables:") - for tab in config_panel.get("panel", []): - for section in tab.get("sections", []): - for option in section.get("options", []): - logger.debug( - " * '%s'.'%s'.'%s'", - tab.get("name"), - section.get("name"), - option.get("name"), - ) - - if option['name'] in parsed_values: - # code is not adapted for that so we have to mock expected format :/ - if option.get("type") == "boolean": - if parsed_values[option['name']].lower() in ("true", "1", "y"): - option["default"] = parsed_values[option['name']] - else: - del option["default"] - else: - option["default"] = parsed_values[option['name']] - - args_dict = _parse_args_in_yunohost_format( - parsed_values, [option] - ) - option["default"] = args_dict[option["name"]][0] - else: - logger.debug( - "Variable '%s' is not declared by config script, using default", - option['name'], - ) - # do nothing, we'll use the default if present - - return { - "app_id": app_id, - "app": app, - "app_name": app_info_dict["name"], - "config_panel": config_panel, - "logs": operation_logger.success(), - } + operation_logger.success() + return result @is_unit_operation() -def app_config_apply(operation_logger, app, args): - logger.warning(m18n.n("experimental_feature")) - - from yunohost.hook import hook_exec - from base64 import b64decode - installed = _is_installed(app) - if not installed: - raise YunohostValidationError( - "app_not_installed", app=app, all_apps=_get_all_installed_apps_id() - ) - - config_panel = _get_app_config_panel(app) - config_script = os.path.join(APPS_SETTING_PATH, app, "scripts", "config") - - if not config_panel or not os.path.exists(config_script): - # XXX real exception - raise Exception("Not config-panel.json nor scripts/config") +def app_config_get(operation_logger, app, key): + # Check app is installed + _assert_is_installed(app) operation_logger.start() - app_id, app_instance_nb = _parse_app_instance_name(app) - env = { - "app_id": app_id, - "app": app, - "app_instance_nb": str(app_instance_nb), - } - args = dict(urllib.parse.parse_qsl(args, keep_blank_values=True)) if args else {} + + # Read config panel toml + config_panel = _get_app_config_panel(app, filter_key=key) + + if not config_panel: + raise YunohostError("app_config_no_panel") + + # Call config script to extract current values + parsed_values = _call_config_script(app, 'show') + + logger.debug("Searching value") + short_key = key.split('.')[-1] + if short_key not in parsed_values: + return None + + return parsed_values[short_key] + + # for panel, section, option in _get_options_iterator(config_panel): + # if option['name'] == short_key: + # # Check and transform values if needed + # args_dict = _parse_args_in_yunohost_format( + # parsed_values, [option], False + # ) + # operation_logger.success() + + # return args_dict[short_key][0] + + # return None + + +@is_unit_operation() +def app_config_set(operation_logger, app, key=None, value=None, args=None): + # Check app is installed + _assert_is_installed(app) + + filter_key = key if key else '' + + # Read config panel toml + config_panel = _get_app_config_panel(app, filter_key=filter_key) + + if not config_panel: + raise YunohostError("app_config_no_panel") + + if args is not None and value is not None: + raise YunohostError("app_config_args_value") + + operation_logger.start() + + # Prepare pre answered questions + args = {} + if args: + args = dict(urllib.parse.parse_qsl(args, keep_blank_values=True)) if args else {} + elif value is not None: + args = {key: value} upload_dir = None - for tab in config_panel.get("panel", []): - for section in tab.get("sections", []): - for option in section.get("options", []): - if option['name'] in args: - # Upload files from API - # A file arg contains a string with "FILENAME:BASE64_CONTENT" - if 'type' in option and option["type"] == "file" \ - and msettings.get('interface') == 'api': - if upload_dir is None: - upload_dir = tempfile.mkdtemp(prefix='tmp_configpanel_') - filename = args[option['name'] + '[name]'] - content = args[option['name']] - logger.debug("Save uploaded file %s from API into %s", filename, upload_dir) + for panel in config_panel.get("panel", []): - # Filename is given by user of the API. For security reason, we have replaced - # os.path.join to avoid the user to be able to rewrite a file in filesystem - # i.e. os.path.join("/foo", "/etc/passwd") == "/etc/passwd" - file_path = os.path.normpath(upload_dir + "/" + filename) - i = 2 - while os.path.exists(file_path): - file_path = os.path.normpath(upload_dir + "/" + filename + (".%d" % i)) - i += 1 - try: - with open(file_path, 'wb') as f: - f.write(b64decode(content)) - except IOError as e: - raise YunohostError("cannot_write_file", file=file_path, error=str(e)) - except Exception as e: - raise YunohostError("error_writing_file", file=file_path, error=str(e)) - args[option['name']] = file_path + if msettings.get('interface') == 'cli' and len(filter_key.split('.')) < 3: + msignals.display(colorize("\n" + "=" * 40, 'purple')) + msignals.display(colorize(f">>>> {panel['name']}", 'purple')) + msignals.display(colorize("=" * 40, 'purple')) + for section in panel.get("sections", []): + if msettings.get('interface') == 'cli' and len(filter_key.split('.')) < 3: + msignals.display(colorize(f"\n# {section['name']}", 'purple')) - logger.debug( - "include into env %s=%s", option['name'], args[option['name']] - ) - env[option['name']] = args[option['name']] - else: - logger.debug("no value for key id %s", option['name']) - - # for debug purpose - for key in args: - if key not in env: - logger.debug( - "Ignore key '%s' from arguments because it is not in the config", key + # Check and ask unanswered questions + args_dict = _parse_args_in_yunohost_format( + args, section['options'] ) + # Call config script to extract current values + logger.info("Running config script...") + env = {key: value[0] for key, value in args_dict.items()} + try: - hook_exec( - config_script, - args=["apply"], - env=env - ) + errors = _call_config_script(app, 'apply', env=env) # Here again, calling hook_exec could fail miserably, or get # manually interrupted (by mistake or because script was stuck) except (KeyboardInterrupt, EOFError, Exception): @@ -1931,10 +1903,51 @@ def app_config_apply(operation_logger, app, args): logger.success("Config updated as expected") return { "app": app, + "errors": errors, "logs": operation_logger.success(), } +def _get_options_iterator(config_panel): + for panel in config_panel.get("panel", []): + for section in panel.get("sections", []): + for option in section.get("options", []): + yield (panel, section, option) + + +def _call_config_script(app, action, env={}): + from yunohost.hook import hook_exec + + # Add default config script if needed + config_script = os.path.join(APPS_SETTING_PATH, app, "scripts", "config") + if not os.path.exists(config_script): + logger.debug("Adding a default config script") + default_script = """#!/bin/bash +source /usr/share/yunohost/helpers +ynh_abort_if_errors +final_path=$(ynh_app_setting_get $app final_path) +ynh_panel_run $1 +""" + write_to_file(config_script, default_script) + + # Call config script to extract current values + logger.debug("Calling 'show' action from config script") + app_id, app_instance_nb = _parse_app_instance_name(app) + env.update({ + "app_id": app_id, + "app": app, + "app_instance_nb": str(app_instance_nb), + }) + + try: + _, parsed_values = hook_exec( + config_script, args=[action], env=env + ) + except (KeyboardInterrupt, EOFError, Exception): + logger.error('Unable to extract some values') + parsed_values = {} + return parsed_values + def _get_all_installed_apps_id(): """ Return something like: @@ -2036,14 +2049,11 @@ def _get_app_actions(app_id): return None -def _get_app_config_panel(app_id): +def _get_app_config_panel(app_id, filter_key=''): "Get app config panel stored in json or in toml" config_panel_toml_path = os.path.join( APPS_SETTING_PATH, app_id, "config_panel.toml" ) - config_panel_json_path = os.path.join( - APPS_SETTING_PATH, app_id, "config_panel.json" - ) # sample data to get an idea of what is going on # this toml extract: @@ -2121,6 +2131,10 @@ def _get_app_config_panel(app_id): "version": toml_config_panel["version"], "panel": [], } + filter_key = filter_key.split('.') + filter_panel = filter_key.pop(0) + filter_section = filter_key.pop(0) if len(filter_key) > 0 else False + filter_option = filter_key.pop(0) if len(filter_key) > 0 else False panels = [ key_value @@ -2130,6 +2144,9 @@ def _get_app_config_panel(app_id): ] for key, value in panels: + if filter_panel and key != filter_panel: + continue + panel = { "id": key, "name": value.get("name", ""), @@ -2143,9 +2160,14 @@ def _get_app_config_panel(app_id): ] for section_key, section_value in sections: + + if filter_section and section_key != filter_section: + continue + section = { "id": section_key, "name": section_value.get("name", ""), + "optional": section_value.get("optional", True), "options": [], } @@ -2156,7 +2178,11 @@ def _get_app_config_panel(app_id): ] for option_key, option_value in options: + if filter_option and option_key != filter_option: + continue + option = dict(option_value) + option["optional"] = option_value.get("optional", section['optional']) option["name"] = option_key option["ask"] = {"en": option["ask"]} if "help" in option: @@ -2169,9 +2195,6 @@ def _get_app_config_panel(app_id): return config_panel - elif os.path.exists(config_panel_json_path): - return json.load(open(config_panel_json_path)) - return None @@ -2615,6 +2638,13 @@ def _is_installed(app): return os.path.isdir(APPS_SETTING_PATH + app) +def _assert_is_installed(app): + if not _is_installed(app): + raise YunohostValidationError( + "app_not_installed", app=app, all_apps=_get_all_installed_apps_id() + ) + + def _installed_apps(): return os.listdir(APPS_SETTING_PATH) @@ -2727,10 +2757,13 @@ class YunoHostArgumentFormatParser(object): parsed_question = Question() parsed_question.name = question["name"] + parsed_question.type = question.get("type", 'string') parsed_question.default = question.get("default", None) parsed_question.choices = question.get("choices", []) parsed_question.optional = question.get("optional", False) parsed_question.ask = question.get("ask") + parsed_question.help = question.get("help") + parsed_question.helpLink = question.get("helpLink") parsed_question.value = user_answers.get(parsed_question.name) if parsed_question.ask is None: @@ -2742,24 +2775,28 @@ class YunoHostArgumentFormatParser(object): return parsed_question - def parse(self, question, user_answers): + def parse(self, question, user_answers, check_required=True): question = self.parse_question(question, user_answers) - if question.value is None: + if question.value is None and not getattr(self, "readonly", False): text_for_user_input_in_cli = self._format_text_for_user_input_in_cli( question ) - try: question.value = msignals.prompt( - text_for_user_input_in_cli, self.hide_user_input_in_prompt + message=text_for_user_input_in_cli, + is_password=self.hide_user_input_in_prompt, + confirm=self.hide_user_input_in_prompt ) except NotImplementedError: question.value = None + if getattr(self, "readonly", False): + msignals.display(self._format_text_for_user_input_in_cli(question)) + # we don't have an answer, check optional and default_value if question.value is None or question.value == "": - if not question.optional and question.default is None: + if not question.optional and question.default is None and check_required: raise YunohostValidationError( "app_argument_required", name=question.name ) @@ -2785,6 +2822,7 @@ class YunoHostArgumentFormatParser(object): raise YunohostValidationError( "app_argument_choice_invalid", name=question.name, + value=question.value, choices=", ".join(question.choices), ) @@ -2796,7 +2834,15 @@ class YunoHostArgumentFormatParser(object): if question.default is not None: text_for_user_input_in_cli += " (default: {0})".format(question.default) - + if question.help or question.helpLink: + text_for_user_input_in_cli += ":\033[m" + if question.help: + text_for_user_input_in_cli += "\n - " + text_for_user_input_in_cli += question.help['en'] + if question.helpLink: + if not isinstance(question.helpLink, dict): + question.helpLink = {'href': question.helpLink} + text_for_user_input_in_cli += f"\n - See {question.helpLink['href']}" return text_for_user_input_in_cli def _post_parse_value(self, question): @@ -2884,6 +2930,7 @@ class BooleanArgumentParser(YunoHostArgumentFormatParser): raise YunohostValidationError( "app_argument_choice_invalid", name=question.name, + value=question.value, choices="yes, no, y, n, 1, 0", ) @@ -2967,13 +3014,73 @@ class NumberArgumentParser(YunoHostArgumentFormatParser): class DisplayTextArgumentParser(YunoHostArgumentFormatParser): argument_type = "display_text" + readonly = True - def parse(self, question, user_answers): - print(question["ask"]) + def parse_question(self, question, user_answers): + question = super(DisplayTextArgumentParser, self).parse_question( + question, user_answers + ) + + question.optional = True + + return question + + def _format_text_for_user_input_in_cli(self, question): + text = question.ask['en'] + if question.type in ['info', 'warning', 'danger']: + color = { + 'info': 'cyan', + 'warning': 'yellow', + 'danger': 'red' + } + return colorize(m18n.g(question.type), color[question.type]) + f" {text}" + else: + return text class FileArgumentParser(YunoHostArgumentFormatParser): argument_type = "file" + def parse_question(self, question, user_answers): + question = super(FileArgumentParser, self).parse_question( + question, user_answers + ) + if msettings.get('interface') == 'api': + question.value = { + 'content': user_answers[question.name], + 'filename': user_answers.get(f"{question.name}[name]", question.name), + } if user_answers[question.name] else None + return question + + def _post_parse_value(self, question): + from base64 import b64decode + # Upload files from API + # A file arg contains a string with "FILENAME:BASE64_CONTENT" + if not question.value: + return question.value + + if msettings.get('interface') == 'api': + upload_dir = tempfile.mkdtemp(prefix='tmp_configpanel_') + filename = question.value['filename'] + logger.debug(f"Save uploaded file {question.value['filename']} from API into {upload_dir}") + + # Filename is given by user of the API. For security reason, we have replaced + # os.path.join to avoid the user to be able to rewrite a file in filesystem + # i.e. os.path.join("/foo", "/etc/passwd") == "/etc/passwd" + file_path = os.path.normpath(upload_dir + "/" + filename) + i = 2 + while os.path.exists(file_path): + file_path = os.path.normpath(upload_dir + "/" + filename + (".%d" % i)) + i += 1 + content = question.value['content'] + try: + with open(file_path, 'wb') as f: + f.write(b64decode(content)) + except IOError as e: + raise YunohostError("cannot_write_file", file=file_path, error=str(e)) + except Exception as e: + raise YunohostError("error_writing_file", file=file_path, error=str(e)) + question.value = file_path + return question.value ARGUMENTS_TYPE_PARSERS = { @@ -2994,11 +3101,16 @@ ARGUMENTS_TYPE_PARSERS = { "number": NumberArgumentParser, "range": NumberArgumentParser, "display_text": DisplayTextArgumentParser, + "success": DisplayTextArgumentParser, + "danger": DisplayTextArgumentParser, + "warning": DisplayTextArgumentParser, + "info": DisplayTextArgumentParser, + "markdown": DisplayTextArgumentParser, "file": FileArgumentParser, } -def _parse_args_in_yunohost_format(user_answers, argument_questions): +def _parse_args_in_yunohost_format(user_answers, argument_questions, check_required=True): """Parse arguments store in either manifest.json or actions.json or from a config panel against the user answers when they are present. @@ -3014,7 +3126,7 @@ def _parse_args_in_yunohost_format(user_answers, argument_questions): for question in argument_questions: parser = ARGUMENTS_TYPE_PARSERS[question.get("type", "string")]() - answer = parser.parse(question=question, user_answers=user_answers) + answer = parser.parse(question=question, user_answers=user_answers, check_required=check_required) if answer is not None: parsed_answers_dict[question["name"]] = answer From 2ac4e1c5bf37c0a33704da92c305a51fa5b94750 Mon Sep 17 00:00:00 2001 From: ljf Date: Fri, 20 Aug 2021 17:26:26 +0200 Subject: [PATCH 2748/3170] [fix] I like regexp --- data/helpers.d/configpanel | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/configpanel b/data/helpers.d/configpanel index 5b290629d..efbd5248f 100644 --- a/data/helpers.d/configpanel +++ b/data/helpers.d/configpanel @@ -42,9 +42,9 @@ ynh_value_get() { # Manage arguments with getopts ynh_handle_getopts_args "$@" - local var_part="[ \t]*(\$?\w*\[)?[ \t]*[\"']?${key}[\"']?[ \t]*\]?[ \t]*[:=]>?[ \t]*" + local var_part='^[ \t]*\$?(\w*\[)?[ \t]*["'"']?${key}['"'"]?[ \t]*\]?[ \t]*[:=]>?[ \t]*' - local crazy_value="$(grep -i -o -P "^${var_part}\K.*(?=[ \t,\n;]*\$)" ${file} | head -n1)" + local crazy_value="$(grep -i -o -P '^[ \t]*\$?(\w*\[)?[ \t]*["'"']?${key}['"'"]?[ \t]*\]?[ \t]*[:=]>?[ \t]*\K.*(?=[ \t,\n;]*$)' ${file} | head -n1)" local first_char="${crazy_value:0:1}" if [[ "$first_char" == '"' ]] ; then From b79d5ae416ffaaf9fc8ddebf1db70dce0d462b21 Mon Sep 17 00:00:00 2001 From: ljf Date: Fri, 20 Aug 2021 18:02:54 +0200 Subject: [PATCH 2749/3170] [enh] Support __FINALPATH__ in file source --- data/helpers.d/configpanel | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/configpanel b/data/helpers.d/configpanel index efbd5248f..fcb1bd0d1 100644 --- a/data/helpers.d/configpanel +++ b/data/helpers.d/configpanel @@ -132,7 +132,7 @@ EOL elif [[ "$source" == *":"* ]] ; then local source_key="$(echo "$source" | cut -d: -f1)" source_key=${source_key:-$short_setting} - local source_file="$(echo "$source" | cut -d: -f2)" + local source_file="$(echo "$source" | cut -d: -f2 | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" old[$short_setting]="$(ynh_value_get --file="${source_file}" --key="${source_key}")" # Specific case for files (all content of the file is the source) From 10c8babf8c0ea2bfaab4e7aeb913a4750ed53c34 Mon Sep 17 00:00:00 2001 From: ljf Date: Fri, 20 Aug 2021 18:03:32 +0200 Subject: [PATCH 2750/3170] [enh] Fail if script fail --- src/yunohost/app.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index c489cceaa..676e07f5a 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1939,15 +1939,12 @@ ynh_panel_run $1 "app_instance_nb": str(app_instance_nb), }) - try: - _, parsed_values = hook_exec( - config_script, args=[action], env=env - ) - except (KeyboardInterrupt, EOFError, Exception): - logger.error('Unable to extract some values') - parsed_values = {} + _, parsed_values = hook_exec( + config_script, args=[action], env=env + ) return parsed_values + def _get_all_installed_apps_id(): """ Return something like: From ef058c07f7fd011ff605b13780e933dc3772a8bc Mon Sep 17 00:00:00 2001 From: ljf Date: Fri, 20 Aug 2021 20:12:15 +0200 Subject: [PATCH 2751/3170] [fix] File question in config panel with cli --- data/helpers.d/configpanel | 41 ++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/data/helpers.d/configpanel b/data/helpers.d/configpanel index fcb1bd0d1..67c7a92f7 100644 --- a/data/helpers.d/configpanel +++ b/data/helpers.d/configpanel @@ -44,7 +44,8 @@ ynh_value_get() { local var_part='^[ \t]*\$?(\w*\[)?[ \t]*["'"']?${key}['"'"]?[ \t]*\]?[ \t]*[:=]>?[ \t]*' - local crazy_value="$(grep -i -o -P '^[ \t]*\$?(\w*\[)?[ \t]*["'"']?${key}['"'"]?[ \t]*\]?[ \t]*[:=]>?[ \t]*\K.*(?=[ \t,\n;]*$)' ${file} | head -n1)" + local crazy_value="$((grep -i -o -P '^[ \t]*\$?(\w*\[)?[ \t]*["'"']?${key}['"'"]?[ \t]*\]?[ \t]*[:=]>?[ \t]*\K.*(?=[ \t,\n;]*$)' ${file} || echo YNH_NULL) | head -n1)" + #" local first_char="${crazy_value:0:1}" if [[ "$first_char" == '"' ]] ; then @@ -74,22 +75,22 @@ ynh_value_set() { # Manage arguments with getopts ynh_handle_getopts_args "$@" - local var_part="[ \t]*(\$?\w*\[)?[ \t]*[\"']?${key}[\"']?[ \t]*\]?[ \t]*[:=]>?[ \t]*" + local var_part='^[ \t]*\$?(\w*\[)?[ \t]*["'"']?${key}['"'"]?[ \t]*\]?[ \t]*[:=]>?[ \t]*' - local crazy_value="$(grep -i -o -P "^${var_part}\K.*(?=[ \t,\n;]*\$)" ${file} | head -n1)" + local crazy_value="$(grep -i -o -P "${var_part}\K.*(?=[ \t,\n;]*\$)" ${file} | head -n1)" local var_part="^[ \t]*(\$?\w*\[)?[ \t]*[\"']?${key}[\"']?[ \t]*\]?[ \t]*[:=]>?[ \t]*" local first_char="${crazy_value:0:1}" if [[ "$first_char" == '"' ]] ; then value="$(echo "$value" | sed 's/"/\\"/g')" - sed -ri "s%^(${var_part}\")[^\"]*(\"[ \t\n,;]*)\$%\1${value}\2%i" ${file} + sed -ri "s%(${var_part}\")[^\"]*(\"[ \t\n,;]*)\$%\1${value}\2%i" ${file} elif [[ "$first_char" == "'" ]] ; then value="$(echo "$value" | sed "s/'/\\\\'/g")" - sed -ri "s%^(${var_part}')[^']*('[ \t\n,;]*)\$%\1${value}\2%i" ${file} + sed -ri "s%(${var_part}')[^']*('[ \t\n,;]*)\$%\1${value}\2%i" ${file} else if [[ "$value" == *"'"* ]] || [[ "$value" == *'"'* ]] ; then value="\"$(echo "$value" | sed 's/"/\\"/g')\"" fi - sed -ri "s%^(${var_part}')[^']*('[ \t\n,;]*)\$%\1${value}\2%i" ${file} + sed -ri "s%(${var_part}')[^']*('[ \t\n,;]*)\$%\1${value}\2%i" ${file} fi } @@ -124,12 +125,12 @@ EOL if type -t $getter 2>/dev/null | grep -q '^function$' 2>/dev/null; then old[$short_setting]="$($getter)" - # Get value from app settings - elif [[ "$source" == "settings" ]] ; then - old[$short_setting]="$(ynh_app_setting_get $app $short_setting)" + # Get value from app settings or from another file + elif [[ "$source" == "settings" ]] || [[ "$source" == *":"* ]] ; then + if [[ "$source" == "settings" ]] ; then + source=":/etc/yunohost/apps/$app/settings.yml" + fi - # Get value from a kind of key/value file - elif [[ "$source" == *":"* ]] ; then local source_key="$(echo "$source" | cut -d: -f1)" source_key=${source_key:-$short_setting} local source_file="$(echo "$source" | cut -d: -f2 | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" @@ -137,7 +138,8 @@ EOL # Specific case for files (all content of the file is the source) else - old[$short_setting]="$source" + + old[$short_setting]="$(ls $source 2> /dev/null || echo YNH_NULL)" file_hash[$short_setting]="true" fi done @@ -164,12 +166,13 @@ _ynh_panel_apply() { then local source_key="$(echo "$source" | cut -d: -f1)" source_key=${source_key:-$short_setting} - local source_file="$(echo "$source" | cut -d: -f2)" + local source_file="$(echo "$source" | cut -d: -f2 | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" ynh_value_set --file="${source_file}" --key="${source_key}" --value="${!short_setting}" # Specific case for files (all content of the file is the source) else - cp "${!short_setting}" "$source" + local source_file="$(echo "$source" | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" + cp "${!short_setting}" "$source_file" fi fi done @@ -178,7 +181,9 @@ _ynh_panel_apply() { _ynh_panel_show() { for short_setting in "${!old[@]}" do - ynh_return "${short_setting}: \"${old[$short_setting]}\"" + if [[ "${old[$short_setting]}" != YNH_NULL ]] ; then + ynh_return "${short_setting}: \"${old[$short_setting]}\"" + fi done } @@ -197,10 +202,11 @@ _ynh_panel_validate() { file_hash[old__$short_setting]=$(sha256sum "${old[$short_setting]}" | cut -d' ' -f1) fi if [ -f "${!short_setting}" ] ; then - file_hash[new__$short_setting]=$(sha256sum "${new[$short_setting]}" | cut -d' ' -f1) + file_hash[new__$short_setting]=$(sha256sum "${!short_setting}" | cut -d' ' -f1) if [[ "${file_hash[old__$short_setting]}" != "${file_hash[new__$short_setting]}" ]] then changed[$short_setting]=true + is_error=false fi fi else @@ -218,6 +224,7 @@ _ynh_panel_validate() { for short_setting in "${!old[@]}" do + [[ "${changed[$short_setting]}" == "false" ]] && continue local result="" if type -t validate__$short_setting | grep -q '^function$' 2>/dev/null; then result="$(validate__$short_setting)" @@ -225,7 +232,7 @@ _ynh_panel_validate() { if [ -n "$result" ] then local key="YNH_ERROR_${$short_setting}" - ynh_return "$key=$result" + ynh_return "$key: $result" is_error=true fi done From cd917a9123de55245f5bc39612c82430fd4e123a Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Sun, 22 Aug 2021 21:57:50 +0200 Subject: [PATCH 2752/3170] [fix] Wording in path question --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 693e9d24d..2cb3fc329 100644 --- a/locales/en.json +++ b/locales/en.json @@ -32,7 +32,7 @@ "app_location_unavailable": "This URL is either unavailable, or conflicts with the already installed app(s):\n{apps}", "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_path": "Choose the web path after the domain 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?", From b5d00da0bfbc9c5fa53c5b4e2820170ba2e46394 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 23 Aug 2021 14:05:22 +0200 Subject: [PATCH 2753/3170] Attempt to fix tests for ldap auth --- src/yunohost/tests/test_ldapauth.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/yunohost/tests/test_ldapauth.py b/src/yunohost/tests/test_ldapauth.py index 0ad8366c9..7560608f5 100644 --- a/src/yunohost/tests/test_ldapauth.py +++ b/src/yunohost/tests/test_ldapauth.py @@ -16,12 +16,12 @@ def setup_function(function): def test_authenticate(): - LDAPAuth().authenticate(credentials="yunohost") + LDAPAuth().authenticate_credentials(credentials="yunohost") def test_authenticate_with_wrong_password(): with pytest.raises(MoulinetteError) as exception: - LDAPAuth().authenticate(credentials="bad_password_lul") + LDAPAuth().authenticate_credentials(credentials="bad_password_lul") translation = m18n.g("invalid_password") expected_msg = translation.format() @@ -35,7 +35,7 @@ def test_authenticate_server_down(mocker): mocker.patch("os.system") mocker.patch("time.sleep") with pytest.raises(MoulinetteError) as exception: - LDAPAuth().authenticate(credentials="yunohost") + LDAPAuth().authenticate_credentials(credentials="yunohost") translation = m18n.n("ldap_server_down") expected_msg = translation.format() @@ -44,15 +44,15 @@ def test_authenticate_server_down(mocker): def test_authenticate_change_password(): - LDAPAuth().authenticate(credentials="yunohost") + LDAPAuth().authenticate_credentials(credentials="yunohost") tools_adminpw("plopette", check_strength=False) with pytest.raises(MoulinetteError) as exception: - LDAPAuth().authenticate(credentials="yunohost") + LDAPAuth().authenticate_credentials(credentials="yunohost") translation = m18n.g("invalid_password") expected_msg = translation.format() assert expected_msg in str(exception) - LDAPAuth().authenticate(credentials="plopette") + LDAPAuth().authenticate_credentials(credentials="plopette") From c9d73af4013816ccb5ba2eba47a3cb804b44d2ee Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 23 Aug 2021 15:31:43 +0200 Subject: [PATCH 2754/3170] i18n: mdns -> yunomdns --- locales/en.json | 2 +- locales/fr.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/en.json b/locales/en.json index cad1c8dcb..f2658c412 100644 --- a/locales/en.json +++ b/locales/en.json @@ -561,7 +561,7 @@ "service_already_started": "The service '{service}' is running already", "service_already_stopped": "The service '{service}' has already been stopped", "service_cmd_exec_failed": "Could not execute the command '{command}'", - "service_description_mdns": "Allows you to reach your server using 'yunohost.local' in your local network", + "service_description_yunomdns": "Allows you to reach your server using 'yunohost.local' in your local network", "service_description_dnsmasq": "Handles domain name resolution (DNS)", "service_description_dovecot": "Allows e-mail clients to access/fetch email (via IMAP and POP3)", "service_description_fail2ban": "Protects against brute-force and other kinds of attacks from the Internet", diff --git a/locales/fr.json b/locales/fr.json index 72afe80dd..d40b39c62 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -234,7 +234,7 @@ "migrations_list_conflict_pending_done": "Vous ne pouvez pas utiliser --previous et --done simultanément.", "migrations_to_be_ran_manually": "La migration {id} doit être lancée manuellement. Veuillez aller dans Outils > Migrations dans l’interface admin, ou lancer `yunohost tools migrations run`.", "migrations_need_to_accept_disclaimer": "Pour lancer la migration {id}, vous devez accepter cet avertissement :\n---\n{disclaimer}\n---\nSi vous acceptez de lancer la migration, veuillez relancer la commande avec l’option --accept-disclaimer.", - "service_description_mdns": "Vous permet d’atteindre votre serveur en utilisant « yunohost.local » sur votre réseau local", + "service_description_yunomdns": "Vous permet d’atteindre votre serveur en utilisant « yunohost.local » sur votre réseau local", "service_description_dnsmasq": "Gère la résolution des noms de domaine (DNS)", "service_description_dovecot": "Permet aux clients de messagerie d’accéder/récupérer les courriels (via IMAP et POP3)", "service_description_fail2ban": "Protège contre les attaques brute-force et autres types d’attaques venant d’Internet", From f4d0106c367902cad01e49808b570816f12fda12 Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Mon, 23 Aug 2021 14:36:55 +0000 Subject: [PATCH 2755/3170] [CI] Remove stale translated strings --- locales/ar.json | 1 - locales/ca.json | 1 - locales/de.json | 3 +-- locales/eo.json | 1 - locales/es.json | 1 - locales/fr.json | 2 +- locales/gl.json | 2 +- locales/it.json | 1 - locales/oc.json | 1 - locales/pt.json | 2 +- locales/zh_Hans.json | 1 - 11 files changed, 4 insertions(+), 12 deletions(-) diff --git a/locales/ar.json b/locales/ar.json index 1d3dbd49d..3e5248917 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -83,7 +83,6 @@ "yunohost_installing": "عملية تنصيب يونوهوست جارية …", "yunohost_not_installed": "إنَّ واي يونوهوست ليس مُنَصَّب أو هو مثبت حاليا بشكل خاطئ. قم بتنفيذ الأمر 'yunohost tools postinstall'", "migrations_list_conflict_pending_done": "لا يمكنك استخدام --previous و --done معًا على نفس سطر الأوامر.", - "service_description_mdns": "يسمح لك بالنفاذ إلى خادومك عبر الشبكة المحلية باستخدام yunohost.local", "service_description_metronome": "يُدير حسابات الدردشة الفورية XMPP", "service_description_nginx": "يقوم بتوفير النفاذ و السماح بالوصول إلى كافة مواقع الويب المستضافة على خادومك", "service_description_postfix": "يقوم بإرسال و تلقي الرسائل البريدية الإلكترونية", diff --git a/locales/ca.json b/locales/ca.json index bfdb57c9e..d01c0da0b 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -283,7 +283,6 @@ "service_already_started": "El servei «{service}» ja està funcionant", "service_already_stopped": "Ja s'ha aturat el servei «{service}»", "service_cmd_exec_failed": "No s'ha pogut executar l'ordre «{command}»", - "service_description_mdns": "Permet accedir al servidor via «yunohost.local» en la xarxa local", "service_description_dnsmasq": "Gestiona la resolució del nom de domini (DNS)", "service_description_dovecot": "Permet als clients de correu accedir/recuperar correus (via IMAP i POP3)", "service_description_fail2ban": "Protegeix contra els atacs de força bruta i a altres atacs provinents d'Internet", diff --git a/locales/de.json b/locales/de.json index ef8593bd6..ea20c7d7f 100644 --- a/locales/de.json +++ b/locales/de.json @@ -597,7 +597,6 @@ "service_description_fail2ban": "Schützt gegen Brute-Force-Angriffe und andere Angriffe aus dem Internet", "service_description_dovecot": "Ermöglicht es E-Mail-Clients auf Konten zuzugreifen (IMAP und POP3)", "service_description_dnsmasq": "Verarbeitet die Auflösung des Domainnamens (DNS)", - "service_description_mdns": "Erlaubt, den Server im lokalen Netz über 'yunohost.local' zu erreichen", "restore_backup_too_old": "Dieses Backup kann nicht wieder hergestellt werden, weil es von einer zu alten YunoHost Version stammt.", "service_description_slapd": "Speichert Benutzer, Domains und verbundene Informationen", "service_description_rspamd": "Spamfilter und andere E-Mail-Merkmale", @@ -636,4 +635,4 @@ "global_settings_setting_security_webadmin_allowlist_enabled": "Erlaube nur bestimmten IP-Adressen den Zugriff auf die Verwaltungsseite.", "disk_space_not_sufficient_update": "Es ist nicht genügend Speicherplatz frei, um diese Applikation zu aktuallisieren", "disk_space_not_sufficient_install": "Es ist nicht genügend Speicherplatz frei, um diese Applikation zu installieren" -} +} \ No newline at end of file diff --git a/locales/eo.json b/locales/eo.json index 8fac2e50e..76cec1264 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -332,7 +332,6 @@ "hook_exec_failed": "Ne povis funkcii skripto: {path}", "global_settings_cant_open_settings": "Ne eblis malfermi agordojn, tial: {reason}", "user_created": "Uzanto kreita", - "service_description_mdns": "Permesas al vi atingi vian servilon uzante 'yunohost.local' en via loka reto", "certmanager_attempt_to_replace_valid_cert": "Vi provas anstataŭigi bonan kaj validan atestilon por domajno {domain}! (Uzu --forte pretervidi)", "regenconf_updated": "Agordo ĝisdatigita por '{category}'", "update_apt_cache_warning": "Io iris malbone dum la ĝisdatigo de la kaŝmemoro de APT (paka administranto de Debian). Jen rubujo de la sources.list-linioj, kiuj povus helpi identigi problemajn liniojn:\n{sourceslist}", diff --git a/locales/es.json b/locales/es.json index 44d637796..560bfe240 100644 --- a/locales/es.json +++ b/locales/es.json @@ -238,7 +238,6 @@ "service_description_fail2ban": "Protege contra ataques de fuerza bruta y otras clases de ataques desde Internet", "service_description_dovecot": "Permite a los clientes de correo acceder/obtener correo (vía IMAP y POP3)", "service_description_dnsmasq": "Maneja la resolución de nombres de dominio (DNS)", - "service_description_mdns": "Permite acceder a su servidor usando «yunohost.local» en su red local", "server_reboot_confirm": "El servidor se reiniciará inmediatamente ¿está seguro? [{answers}]", "server_reboot": "El servidor se reiniciará", "server_shutdown_confirm": "El servidor se apagará inmediatamente ¿está seguro? [{answers}]", diff --git a/locales/fr.json b/locales/fr.json index d40b39c62..f1ccde84d 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -636,4 +636,4 @@ "backup_create_size_estimation": "L'archive contiendra environ {size} de données.", "global_settings_setting_security_webadmin_allowlist": "Adresses IP autorisées à accéder à la page web du portail d'administration (webadmin). Elles doivent être séparées par une virgule.", "global_settings_setting_security_webadmin_allowlist_enabled": "Autorisez seulement certaines IP à accéder à la page web du portail d'administration (webadmin)." -} +} \ No newline at end of file diff --git a/locales/gl.json b/locales/gl.json index 6ef2c45c1..a9d901275 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -358,4 +358,4 @@ "global_settings_setting_security_webadmin_allowlist_enabled": "Permitir que só algúns IPs accedan á webadmin.", "disk_space_not_sufficient_update": "Non hai espazo suficiente no disco para actualizar esta aplicación", "disk_space_not_sufficient_install": "Non queda espazo suficiente no disco para instalar esta aplicación" -} +} \ No newline at end of file diff --git a/locales/it.json b/locales/it.json index c818dd14c..543ab99bd 100644 --- a/locales/it.json +++ b/locales/it.json @@ -428,7 +428,6 @@ "service_description_fail2ban": "Ti protegge dal brute-force e altri tipi di attacchi da Internet", "service_description_dovecot": "Consente ai client mail di accedere/recuperare le email (via IMAP e POP3)", "service_description_dnsmasq": "Gestisce la risoluzione dei domini (DNS)", - "service_description_mdns": "Consente di raggiungere il tuo server eseguendo 'yunohost.local' sulla tua LAN", "server_reboot_confirm": "Il server si riavvierà immediatamente, sei sicuro? [{answers}]", "server_reboot": "Il server si riavvierà", "server_shutdown_confirm": "Il server si spegnerà immediatamente, sei sicuro? [{answers}]", diff --git a/locales/oc.json b/locales/oc.json index fb0a7006d..906f67106 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -193,7 +193,6 @@ "user_unknown": "Utilizaire « {user} » desconegut", "user_update_failed": "Modificacion impossibla de l’utilizaire", "user_updated": "L’utilizaire es estat modificat", - "service_description_mdns": "permet d’aténher vòstre servidor via yunohost.local sus vòstre ret local", "service_description_dnsmasq": "gerís la resolucion dels noms de domeni (DNS)", "updating_apt_cache": "Actualizacion de la lista dels paquets disponibles…", "server_reboot_confirm": "Lo servidor es per reaviar sul pic, o volètz vertadièrament ? {answers}", diff --git a/locales/pt.json b/locales/pt.json index 82edbf349..353d48744 100644 --- a/locales/pt.json +++ b/locales/pt.json @@ -137,4 +137,4 @@ "already_up_to_date": "Nada a ser feito. Tudo já está atualizado.", "additional_urls_already_removed": "A URL adicional '{url}'já está removida para a permissão '{permission}'", "additional_urls_already_added": "A URL adicional '{url}' já está adicionada para a permissão '{permission}'" -} +} \ No newline at end of file diff --git a/locales/zh_Hans.json b/locales/zh_Hans.json index 7951961b6..5f076fa2e 100644 --- a/locales/zh_Hans.json +++ b/locales/zh_Hans.json @@ -226,7 +226,6 @@ "service_description_fail2ban": "防止来自互联网的暴力攻击和其他类型的攻击", "service_description_dovecot": "允许电子邮件客户端访问/获取电子邮件(通过IMAP和POP3)", "service_description_dnsmasq": "处理域名解析(DNS)", - "service_description_mdns": "允许您使用本地网络中的“ yunohost.local”访问服务器", "service_started": "服务 '{service}' 已启动", "service_start_failed": "无法启动服务 '{service}'\n\n最近的服务日志:{logs}", "service_reloaded_or_restarted": "服务'{service}'已重新加载或重新启动", From 0de69104b153bf24b18f533c89f1f7b007734a18 Mon Sep 17 00:00:00 2001 From: ljf Date: Tue, 24 Aug 2021 16:06:46 +0200 Subject: [PATCH 2756/3170] [fix] Bad merge --- data/helpers.d/configpanel | 51 -------------------------------------- 1 file changed, 51 deletions(-) diff --git a/data/helpers.d/configpanel b/data/helpers.d/configpanel index e8dbaafe9..67c7a92f7 100644 --- a/data/helpers.d/configpanel +++ b/data/helpers.d/configpanel @@ -181,39 +181,26 @@ _ynh_panel_apply() { _ynh_panel_show() { for short_setting in "${!old[@]}" do -<<<<<<< HEAD if [[ "${old[$short_setting]}" != YNH_NULL ]] ; then ynh_return "${short_setting}: \"${old[$short_setting]}\"" fi -======= - local key="YNH_CONFIG_$(ynh_lowerdot_to_uppersnake $dot_settings[$short_setting])" - ynh_return "$key=${old[$short_setting]}" ->>>>>>> 596d05ae81d24712c87ec0de72f8deb8248bca9a done } _ynh_panel_validate() { -<<<<<<< HEAD -======= - set +x ->>>>>>> 596d05ae81d24712c87ec0de72f8deb8248bca9a # Change detection local is_error=true #for changed_status in "${!changed[@]}" for short_setting in "${!old[@]}" do changed[$short_setting]=false -<<<<<<< HEAD [ -z ${!short_setting+x} ] && continue -======= ->>>>>>> 596d05ae81d24712c87ec0de72f8deb8248bca9a if [ ! -z "${file_hash[${short_setting}]}" ] ; then file_hash[old__$short_setting]="" file_hash[new__$short_setting]="" if [ -f "${old[$short_setting]}" ] ; then file_hash[old__$short_setting]=$(sha256sum "${old[$short_setting]}" | cut -d' ' -f1) fi -<<<<<<< HEAD if [ -f "${!short_setting}" ] ; then file_hash[new__$short_setting]=$(sha256sum "${!short_setting}" | cut -d' ' -f1) if [[ "${file_hash[old__$short_setting]}" != "${file_hash[new__$short_setting]}" ]] @@ -224,17 +211,6 @@ _ynh_panel_validate() { fi else if [[ "${!short_setting}" != "${old[$short_setting]}" ]] -======= - if [ -f "${new[$short_setting]}" ] ; then - file_hash[new__$short_setting]=$(sha256sum "${new[$short_setting]}" | cut -d' ' -f1) - if [[ "${file_hash[old__$short_setting]}" != "${file_hash[new__$short_setting]}" ]] - then - changed[$short_setting]=true - fi - fi - else - if [[ "${new[$short_setting]}" != "${old[$short_setting]}" ]] ->>>>>>> 596d05ae81d24712c87ec0de72f8deb8248bca9a then changed[$short_setting]=true is_error=false @@ -248,23 +224,15 @@ _ynh_panel_validate() { for short_setting in "${!old[@]}" do -<<<<<<< HEAD [[ "${changed[$short_setting]}" == "false" ]] && continue -======= ->>>>>>> 596d05ae81d24712c87ec0de72f8deb8248bca9a local result="" if type -t validate__$short_setting | grep -q '^function$' 2>/dev/null; then result="$(validate__$short_setting)" fi if [ -n "$result" ] then -<<<<<<< HEAD local key="YNH_ERROR_${$short_setting}" ynh_return "$key: $result" -======= - local key="YNH_ERROR_$(ynh_lowerdot_to_uppersnake $dot_settings[$short_setting])" - ynh_return "$key=$result" ->>>>>>> 596d05ae81d24712c87ec0de72f8deb8248bca9a is_error=true fi done @@ -272,14 +240,8 @@ _ynh_panel_validate() { if [[ "$is_error" == "true" ]] then -<<<<<<< HEAD ynh_die "" fi -======= - ynh_die - fi - set -x ->>>>>>> 596d05ae81d24712c87ec0de72f8deb8248bca9a } @@ -301,7 +263,6 @@ ynh_panel_apply() { ynh_panel_run() { declare -Ag old=() -<<<<<<< HEAD declare -Ag changed=() declare -Ag file_hash=() declare -Ag sources=() @@ -310,18 +271,6 @@ ynh_panel_run() { case $1 in show) ynh_panel_get && ynh_panel_show;; apply) ynh_panel_get && ynh_panel_validate && ynh_panel_apply;; -======= - declare -Ag new=() - declare -Ag changed=() - declare -Ag file_hash=() - declare -Ag sources=() - declare -Ag dot_settings=() - - ynh_panel_get - case $1 in - show) ynh_panel_show;; - apply) ynh_panel_validate && ynh_panel_apply;; ->>>>>>> 596d05ae81d24712c87ec0de72f8deb8248bca9a esac } From 4c46f41036b505d51faeab7f1545e7f3db421e1e Mon Sep 17 00:00:00 2001 From: ljf Date: Tue, 24 Aug 2021 17:36:54 +0200 Subject: [PATCH 2757/3170] [fix] Args always empty in config panel --- src/yunohost/app.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 676e07f5a..770706190 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1861,11 +1861,13 @@ def app_config_set(operation_logger, app, key=None, value=None, args=None): operation_logger.start() # Prepare pre answered questions - args = {} if args: args = dict(urllib.parse.parse_qsl(args, keep_blank_values=True)) if args else {} elif value is not None: args = {key: value} + else: + args = {} + upload_dir = None From 513a9f62c84d9d5c9413fde774bacb96803bbe86 Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Wed, 25 Aug 2021 12:13:45 +0200 Subject: [PATCH 2758/3170] [enh] SSH acronym MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Éric Gaspar <46165813+ericgaspar@users.noreply.github.com> --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 712ecb844..6e6bb592b 100644 --- a/locales/en.json +++ b/locales/en.json @@ -527,7 +527,7 @@ "regenconf_dry_pending_applying": "Checking pending configuration which would have been applied for category '{category}'…", "regenconf_failed": "Could not regenerate the configuration for category(s): {categories}", "regenconf_pending_applying": "Applying pending configuration for category '{category}'...", - "regenconf_need_to_explicitly_specify_ssh": "The ssh configuration has been manually modified, but you need to explicitly specify category 'ssh' with --force to actually apply the changes.", + "regenconf_need_to_explicitly_specify_ssh": "The SSH configuration has been manually modified, but you need to explicitly specify category 'ssh' with --force to actually apply the changes.", "regex_incompatible_with_tile": "/!\\ Packagers! Permission '{permission}' has show_tile set to 'true' and you therefore cannot define a regex URL as the main URL", "regex_with_only_domain": "You can't use a regex for domain, only for path", "registrar_is_not_set": "The registrar for this domain has not been configured", From 4547a6ec077f7d09a7334a08b92ccc7da4ba2827 Mon Sep 17 00:00:00 2001 From: ljf Date: Wed, 25 Aug 2021 18:13:17 +0200 Subject: [PATCH 2759/3170] [fix] Multiple fixes in config panel --- data/helpers.d/configpanel | 73 ++++++++++++++++++++++++-------------- src/yunohost/app.py | 16 +++++---- 2 files changed, 55 insertions(+), 34 deletions(-) diff --git a/data/helpers.d/configpanel b/data/helpers.d/configpanel index 67c7a92f7..7e26811f5 100644 --- a/data/helpers.d/configpanel +++ b/data/helpers.d/configpanel @@ -189,12 +189,18 @@ _ynh_panel_show() { _ynh_panel_validate() { # Change detection + ynh_script_progression --message="Checking what changed in the new configuration..." --weight=1 local is_error=true #for changed_status in "${!changed[@]}" for short_setting in "${!old[@]}" do changed[$short_setting]=false - [ -z ${!short_setting+x} ] && continue + if [ -z ${!short_setting+x} ]; then + # Assign the var with the old value in order to allows multiple + # args validation + declare "$short_setting"="${old[$short_setting]}" + continue + fi if [ ! -z "${file_hash[${short_setting}]}" ] ; then file_hash[old__$short_setting]="" file_hash[new__$short_setting]="" @@ -217,30 +223,32 @@ _ynh_panel_validate() { fi fi done - - # Run validation if something is changed - if [[ "$is_error" == "false" ]] - then - - for short_setting in "${!old[@]}" - do - [[ "${changed[$short_setting]}" == "false" ]] && continue - local result="" - if type -t validate__$short_setting | grep -q '^function$' 2>/dev/null; then - result="$(validate__$short_setting)" - fi - if [ -n "$result" ] - then - local key="YNH_ERROR_${$short_setting}" - ynh_return "$key: $result" - is_error=true - fi - done - fi - if [[ "$is_error" == "true" ]] then - ynh_die "" + ynh_die "Nothing has changed" + fi + + # Run validation if something is changed + ynh_script_progression --message="Validating the new configuration..." --weight=1 + + for short_setting in "${!old[@]}" + do + [[ "${changed[$short_setting]}" == "false" ]] && continue + local result="" + if type -t validate__$short_setting | grep -q '^function$' 2>/dev/null; then + result="$(validate__$short_setting)" + fi + if [ -n "$result" ] + then + local key="YNH_ERROR_${short_setting}" + ynh_return "$key: $result" + is_error=true + fi + done + + if [[ "$is_error" == "true" ]] + then + exit 0 fi } @@ -266,11 +274,22 @@ ynh_panel_run() { declare -Ag changed=() declare -Ag file_hash=() declare -Ag sources=() - - ynh_panel_get case $1 in - show) ynh_panel_get && ynh_panel_show;; - apply) ynh_panel_get && ynh_panel_validate && ynh_panel_apply;; + show) + ynh_panel_get + ynh_panel_show + ;; + apply) + max_progression=4 + ynh_script_progression --message="Reading config panel description and current configuration..." --weight=1 + ynh_panel_get + + ynh_panel_validate + + ynh_script_progression --message="Applying the new configuration..." --weight=1 + ynh_panel_apply + ynh_script_progression --message="Configuration of $app completed" --last + ;; esac } diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 770706190..92254d1d0 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1769,7 +1769,7 @@ def app_config_show(operation_logger, app, panel='', full=False): return None # Call config script to extract current values - parsed_values = _call_config_script(app, 'show') + parsed_values = _call_config_script(operation_logger, app, 'show') # # Check and transform values if needed # options = [option for _, _, option in _get_options_iterator(config_panel)] @@ -1820,7 +1820,7 @@ def app_config_get(operation_logger, app, key): raise YunohostError("app_config_no_panel") # Call config script to extract current values - parsed_values = _call_config_script(app, 'show') + parsed_values = _call_config_script(operation_logger, app, 'show') logger.debug("Searching value") short_key = key.split('.')[-1] @@ -1891,7 +1891,7 @@ def app_config_set(operation_logger, app, key=None, value=None, args=None): env = {key: value[0] for key, value in args_dict.items()} try: - errors = _call_config_script(app, 'apply', env=env) + errors = _call_config_script(operation_logger, app, 'apply', env=env) # Here again, calling hook_exec could fail miserably, or get # manually interrupted (by mistake or because script was stuck) except (KeyboardInterrupt, EOFError, Exception): @@ -1917,7 +1917,7 @@ def _get_options_iterator(config_panel): yield (panel, section, option) -def _call_config_script(app, action, env={}): +def _call_config_script(operation_logger, app, action, env={}): from yunohost.hook import hook_exec # Add default config script if needed @@ -1933,7 +1933,7 @@ ynh_panel_run $1 write_to_file(config_script, default_script) # Call config script to extract current values - logger.debug("Calling 'show' action from config script") + logger.debug(f"Calling '{action}' action from config script") app_id, app_instance_nb = _parse_app_instance_name(app) env.update({ "app_id": app_id, @@ -1941,9 +1941,11 @@ ynh_panel_run $1 "app_instance_nb": str(app_instance_nb), }) - _, parsed_values = hook_exec( + ret, parsed_values = hook_exec( config_script, args=[action], env=env ) + if ret != 0: + operation_logger.error(parsed_values) return parsed_values @@ -3047,7 +3049,7 @@ class FileArgumentParser(YunoHostArgumentFormatParser): question.value = { 'content': user_answers[question.name], 'filename': user_answers.get(f"{question.name}[name]", question.name), - } if user_answers[question.name] else None + } if user_answers.get(question.name) else None return question def _post_parse_value(self, question): From 86c099812363d96142d6fdb9a53b2168365a247a Mon Sep 17 00:00:00 2001 From: ljf Date: Wed, 25 Aug 2021 19:18:15 +0200 Subject: [PATCH 2760/3170] [enh] Add a pattern validation for config panel --- src/yunohost/app.py | 74 ++++++++++++++++++++++++++++----------------- 1 file changed, 46 insertions(+), 28 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 92254d1d0..1853a63f4 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -2764,6 +2764,7 @@ class YunoHostArgumentFormatParser(object): parsed_question.optional = question.get("optional", False) parsed_question.ask = question.get("ask") parsed_question.help = question.get("help") + parsed_question.pattern = question.get("pattern") parsed_question.helpLink = question.get("helpLink") parsed_question.value = user_answers.get(parsed_question.name) @@ -2776,49 +2777,66 @@ class YunoHostArgumentFormatParser(object): return parsed_question - def parse(self, question, user_answers, check_required=True): + def parse(self, question, user_answers): question = self.parse_question(question, user_answers) - if question.value is None and not getattr(self, "readonly", False): - text_for_user_input_in_cli = self._format_text_for_user_input_in_cli( - question - ) - try: - question.value = msignals.prompt( - message=text_for_user_input_in_cli, - is_password=self.hide_user_input_in_prompt, - confirm=self.hide_user_input_in_prompt + while True: + # Display question if no value filled or if it's a readonly message + if msettings.get('interface') == 'cli': + text_for_user_input_in_cli = self._format_text_for_user_input_in_cli( + question ) - except NotImplementedError: - question.value = None + if getattr(self, "readonly", False): + msignals.display(text_for_user_input_in_cli) - if getattr(self, "readonly", False): - msignals.display(self._format_text_for_user_input_in_cli(question)) + elif question.value is None: + question.value = msignals.prompt( + message=text_for_user_input_in_cli, + is_password=self.hide_user_input_in_prompt, + confirm=self.hide_user_input_in_prompt + ) - # we don't have an answer, check optional and default_value - if question.value is None or question.value == "": - if not question.optional and question.default is None and check_required: - raise YunohostValidationError( - "app_argument_required", name=question.name - ) - else: + + # Apply default value + if question.value in [None, ""] and question.default is not None: question.value = ( getattr(self, "default_value", None) if question.default is None else question.default ) - # we have an answer, do some post checks - if question.value is not None: - if question.choices and question.value not in question.choices: - self._raise_invalid_answer(question) - + # Prevalidation + try: + self._prevalidate(question) + except YunoHostValidationError: + if msettings.get('interface') == 'api': + raise + question.value = None + continue + break # this is done to enforce a certain formating like for boolean # by default it doesn't do anything question.value = self._post_parse_value(question) return (question.value, self.argument_type) + def _prevalidate(self, question): + if question.value in [None, ""] and not question.optional: + raise YunohostValidationError( + "app_argument_required", name=question.name + ) + + # we have an answer, do some post checks + if question.value is not None: + if question.choices and question.value not in question.choices: + self._raise_invalid_answer(question) + if question.pattern and re.match(question.pattern['regexp'], str(question.value)): + raise YunohostValidationError( + question.pattern['error'], + name=question.name, + value=question.value, + ) + def _raise_invalid_answer(self, question): raise YunohostValidationError( "app_argument_choice_invalid", @@ -3111,7 +3129,7 @@ ARGUMENTS_TYPE_PARSERS = { } -def _parse_args_in_yunohost_format(user_answers, argument_questions, check_required=True): +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. @@ -3127,7 +3145,7 @@ def _parse_args_in_yunohost_format(user_answers, argument_questions, check_requi for question in argument_questions: parser = ARGUMENTS_TYPE_PARSERS[question.get("type", "string")]() - answer = parser.parse(question=question, user_answers=user_answers, check_required=check_required) + answer = parser.parse(question=question, user_answers=user_answers) if answer is not None: parsed_answers_dict[question["name"]] = answer From 5dc1ee62660c2b06be840ddd20cf6b051ace86f0 Mon Sep 17 00:00:00 2001 From: ljf Date: Wed, 25 Aug 2021 20:10:11 +0200 Subject: [PATCH 2761/3170] [enh] Services key in config panel --- src/yunohost/app.py | 39 ++++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 1853a63f4..3d775ea5b 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -54,7 +54,7 @@ from moulinette.utils.filesystem import ( mkdir, ) -from yunohost.service import service_status, _run_service_command +from yunohost.service import service_status, _run_service_command, _get_services from yunohost.utils import packages from yunohost.utils.error import YunohostError, YunohostValidationError from yunohost.utils.filesystem import free_space_in_directory @@ -1870,9 +1870,7 @@ def app_config_set(operation_logger, app, key=None, value=None, args=None): upload_dir = None - for panel in config_panel.get("panel", []): - if msettings.get('interface') == 'cli' and len(filter_key.split('.')) < 3: msignals.display(colorize("\n" + "=" * 40, 'purple')) msignals.display(colorize(f">>>> {panel['name']}", 'purple')) @@ -1902,7 +1900,29 @@ def app_config_set(operation_logger, app, key=None, value=None, args=None): if upload_dir is not None: shutil.rmtree(upload_dir) - logger.success("Config updated as expected") + # Reload services + services_to_reload = set([]) + for panel in config_panel.get("panel", []): + services_to_reload |= set(panel.get('services', [])) + for section in panel.get("sections", []): + services_to_reload |= set(section.get('services', [])) + for option in section.get("options", []): + services_to_reload |= set(section.get('options', [])) + + services_to_reload = list(services_to_reload) + services_to_reload.sort(key = 'nginx'.__eq__) + for service in services_to_reload: + if not _run_service_command('reload_or_restart', service): + services = _get_services() + test_conf = services[service].get('test_conf') + errors = check_output(f"{test_conf}; exit 0") if test_conf else '' + raise YunohostError( + "app_config_failed_service_reload", + service=service, errors=errors + ) + + if not errors: + logger.success("Config updated as expected") return { "app": app, "errors": errors, @@ -2760,17 +2780,14 @@ class YunoHostArgumentFormatParser(object): parsed_question.name = question["name"] parsed_question.type = question.get("type", 'string') parsed_question.default = question.get("default", None) - parsed_question.choices = question.get("choices", []) parsed_question.optional = question.get("optional", False) - parsed_question.ask = question.get("ask") - parsed_question.help = question.get("help") + parsed_question.choices = question.get("choices", []) parsed_question.pattern = question.get("pattern") + parsed_question.ask = question.get("ask", {'en': f"Enter value for '{parsed_question.name}':"}) + parsed_question.help = question.get("help") parsed_question.helpLink = question.get("helpLink") parsed_question.value = user_answers.get(parsed_question.name) - if parsed_question.ask is None: - parsed_question.ask = "Enter value for '%s':" % parsed_question.name - # Empty value is parsed as empty string if parsed_question.default == "": parsed_question.default = None @@ -2857,7 +2874,7 @@ class YunoHostArgumentFormatParser(object): text_for_user_input_in_cli += ":\033[m" if question.help: text_for_user_input_in_cli += "\n - " - text_for_user_input_in_cli += question.help['en'] + text_for_user_input_in_cli += _value_for_locale(question.help) if question.helpLink: if not isinstance(question.helpLink, dict): question.helpLink = {'href': question.helpLink} From 97128d7ddbaaf4806c6bfe1d5e9847c78aa9c0c0 Mon Sep 17 00:00:00 2001 From: ljf Date: Wed, 25 Aug 2021 20:16:31 +0200 Subject: [PATCH 2762/3170] [enh] Support __APP__ in services config panel key --- src/yunohost/app.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 3d775ea5b..fba8499c0 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1912,6 +1912,8 @@ def app_config_set(operation_logger, app, key=None, value=None, args=None): services_to_reload = list(services_to_reload) services_to_reload.sort(key = 'nginx'.__eq__) for service in services_to_reload: + if service == "__APP__": + service = app if not _run_service_command('reload_or_restart', service): services = _get_services() test_conf = services[service].get('test_conf') From 5a64a063b285d99e84bd22097cbae8f63a74121a Mon Sep 17 00:00:00 2001 From: ljf Date: Wed, 25 Aug 2021 20:51:50 +0200 Subject: [PATCH 2763/3170] [fix] Clean properly config panel upload dir --- src/yunohost/app.py | 71 +++++++++++++++++++++++++-------------------- 1 file changed, 40 insertions(+), 31 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index fba8499c0..aa8f0d864 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1861,34 +1861,29 @@ def app_config_set(operation_logger, app, key=None, value=None, args=None): operation_logger.start() # Prepare pre answered questions - if args: - args = dict(urllib.parse.parse_qsl(args, keep_blank_values=True)) if args else {} - elif value is not None: + args = dict(urllib.parse.parse_qsl(args, keep_blank_values=True)) if args else {} + if value is not None: args = {key: value} - else: - args = {} - - - upload_dir = None - for panel in config_panel.get("panel", []): - if msettings.get('interface') == 'cli' and len(filter_key.split('.')) < 3: - msignals.display(colorize("\n" + "=" * 40, 'purple')) - msignals.display(colorize(f">>>> {panel['name']}", 'purple')) - msignals.display(colorize("=" * 40, 'purple')) - for section in panel.get("sections", []): - if msettings.get('interface') == 'cli' and len(filter_key.split('.')) < 3: - msignals.display(colorize(f"\n# {section['name']}", 'purple')) - - # Check and ask unanswered questions - args_dict = _parse_args_in_yunohost_format( - args, section['options'] - ) - - # Call config script to extract current values - logger.info("Running config script...") - env = {key: value[0] for key, value in args_dict.items()} try: + for panel in config_panel.get("panel", []): + if msettings.get('interface') == 'cli' and len(filter_key.split('.')) < 3: + msignals.display(colorize("\n" + "=" * 40, 'purple')) + msignals.display(colorize(f">>>> {panel['name']}", 'purple')) + msignals.display(colorize("=" * 40, 'purple')) + for section in panel.get("sections", []): + if msettings.get('interface') == 'cli' and len(filter_key.split('.')) < 3: + msignals.display(colorize(f"\n# {section['name']}", 'purple')) + + # Check and ask unanswered questions + args_dict = _parse_args_in_yunohost_format( + args, section['options'] + ) + + # Call config script to extract current values + logger.info("Running config script...") + env = {key: value[0] for key, value in args_dict.items()} + errors = _call_config_script(operation_logger, app, 'apply', env=env) # Here again, calling hook_exec could fail miserably, or get # manually interrupted (by mistake or because script was stuck) @@ -1896,11 +1891,16 @@ def app_config_set(operation_logger, app, key=None, value=None, args=None): raise YunohostError("unexpected_error") finally: # Delete files uploaded from API - if msettings.get('interface') == 'api': - if upload_dir is not None: - shutil.rmtree(upload_dir) + FileArgumentParser.clean_upload_dirs() + + if errors: + return { + "app": app, + "errors": errors, + } # Reload services + logger.info("Reloading services...") services_to_reload = set([]) for panel in config_panel.get("panel", []): services_to_reload |= set(panel.get('services', [])) @@ -1923,11 +1923,10 @@ def app_config_set(operation_logger, app, key=None, value=None, args=None): service=service, errors=errors ) - if not errors: - logger.success("Config updated as expected") + logger.success("Config updated as expected") return { "app": app, - "errors": errors, + "errors": [], "logs": operation_logger.success(), } @@ -3077,6 +3076,14 @@ class DisplayTextArgumentParser(YunoHostArgumentFormatParser): class FileArgumentParser(YunoHostArgumentFormatParser): argument_type = "file" + upload_dirs = [] + + @classmethod + def clean_upload_dirs(cls): + # Delete files uploaded from API + if msettings.get('interface') == 'api': + for upload_dir in cls.upload_dirs: + shutil.rmtree(upload_dir) def parse_question(self, question, user_answers): question = super(FileArgumentParser, self).parse_question( @@ -3097,7 +3104,9 @@ class FileArgumentParser(YunoHostArgumentFormatParser): return question.value if msettings.get('interface') == 'api': + upload_dir = tempfile.mkdtemp(prefix='tmp_configpanel_') + FileArgumentParser.upload_dirs += [upload_dir] filename = question.value['filename'] logger.debug(f"Save uploaded file {question.value['filename']} from API into {upload_dir}") From 8d364029a03bbaf87f9cc491e3e7d5975a1f49bc Mon Sep 17 00:00:00 2001 From: ljf Date: Wed, 25 Aug 2021 20:58:15 +0200 Subject: [PATCH 2764/3170] [fix] Services key not properly converted --- src/yunohost/app.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index aa8f0d864..f94e22462 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1866,6 +1866,7 @@ def app_config_set(operation_logger, app, key=None, value=None, args=None): args = {key: value} try: + logger.debug("Asking unanswered question and prevalidating...") for panel in config_panel.get("panel", []): if msettings.get('interface') == 'cli' and len(filter_key.split('.')) < 3: msignals.display(colorize("\n" + "=" * 40, 'purple')) @@ -2172,6 +2173,7 @@ def _get_app_config_panel(app_id, filter_key=''): panel = { "id": key, "name": value.get("name", ""), + "services": value.get("services", []), "sections": [], } @@ -2190,6 +2192,7 @@ def _get_app_config_panel(app_id, filter_key=''): "id": section_key, "name": section_value.get("name", ""), "optional": section_value.get("optional", True), + "services": value.get("services", []), "options": [], } From f5529c584d8fd4d91a9177f2fa9f946790011b0c Mon Sep 17 00:00:00 2001 From: ljf Date: Wed, 25 Aug 2021 21:16:31 +0200 Subject: [PATCH 2765/3170] [wip] Min max in number questions --- src/yunohost/app.py | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index f94e22462..44c93e6d9 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1908,14 +1908,15 @@ def app_config_set(operation_logger, app, key=None, value=None, args=None): for section in panel.get("sections", []): services_to_reload |= set(section.get('services', [])) for option in section.get("options", []): - services_to_reload |= set(section.get('options', [])) + services_to_reload |= set(option.get('services', [])) services_to_reload = list(services_to_reload) services_to_reload.sort(key = 'nginx'.__eq__) for service in services_to_reload: if service == "__APP__": service = app - if not _run_service_command('reload_or_restart', service): + logger.debug(f"Reloading {service}") + if not _run_service_command('reload-or-restart', service): services = _get_services() test_conf = services[service].get('test_conf') errors = check_output(f"{test_conf}; exit 0") if test_conf else '' @@ -2937,7 +2938,7 @@ class BooleanArgumentParser(YunoHostArgumentFormatParser): default_value = False def parse_question(self, question, user_answers): - question = super(BooleanArgumentParser, self).parse_question( + question = super().parse_question( question, user_answers ) @@ -3031,18 +3032,33 @@ class NumberArgumentParser(YunoHostArgumentFormatParser): default_value = "" def parse_question(self, question, user_answers): - question = super(NumberArgumentParser, self).parse_question( + question_parsed = super().parse_question( question, user_answers ) - + question_parsed.min = question.get('min', None) + question_parsed.max = question.get('max', None) if question.default is None: - question.default = 0 + question_parsed.default = 0 - return question + return question_parsed + def _prevalidate(self, question): + super()._prevalidate(question) + if question.min is not None and question.value < question.min: + raise YunohostValidationError( + "app_argument_invalid", name=question.name, error=m18n.n("invalid_number") + ) + if question.max is not None and question.value > question.max: + raise YunohostValidationError( + "app_argument_invalid", name=question.name, error=m18n.n("invalid_number") + ) + if not isinstance(question.value, int) and not (isinstance(question.value, str) and question.value.isdigit()): + raise YunohostValidationError( + "app_argument_invalid", name=question.name, error=m18n.n("invalid_number") + ) def _post_parse_value(self, question): if isinstance(question.value, int): - return super(NumberArgumentParser, self)._post_parse_value(question) + return super()._post_parse_value(question) if isinstance(question.value, str) and question.value.isdigit(): return int(question.value) From 9eb9ec1804e45856b07962e16393f0100bf6d113 Mon Sep 17 00:00:00 2001 From: ljf Date: Thu, 26 Aug 2021 17:39:33 +0200 Subject: [PATCH 2766/3170] [fix] Several fixes in config panel --- src/yunohost/app.py | 59 +++++++++++++++++++++++++++++++++------------ 1 file changed, 43 insertions(+), 16 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 44c93e6d9..0e945c64c 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -70,6 +70,7 @@ APPS_CATALOG_CONF = "/etc/yunohost/apps_catalog.yml" APPS_CATALOG_API_VERSION = 2 APPS_CATALOG_DEFAULT_URL = "https://app.yunohost.org/default" +APPS_CONFIG_PANEL_VERSION_SUPPORTED = 1.0 re_app_instance_name = re.compile( r"^(?P[\w-]+?)(__(?P[1-9][0-9]*))?$" ) @@ -1863,7 +1864,7 @@ def app_config_set(operation_logger, app, key=None, value=None, args=None): # Prepare pre answered questions args = dict(urllib.parse.parse_qsl(args, keep_blank_values=True)) if args else {} if value is not None: - args = {key: value} + args = {filter_key.split('.')[-1]: value} try: logger.debug("Asking unanswered question and prevalidating...") @@ -1883,13 +1884,23 @@ def app_config_set(operation_logger, app, key=None, value=None, args=None): # Call config script to extract current values logger.info("Running config script...") - env = {key: value[0] for key, value in args_dict.items()} + env = {key: str(value[0]) for key, value in args_dict.items()} errors = _call_config_script(operation_logger, app, 'apply', env=env) - # Here again, calling hook_exec could fail miserably, or get - # manually interrupted (by mistake or because script was stuck) - except (KeyboardInterrupt, EOFError, Exception): - raise YunohostError("unexpected_error") + # Script got manually interrupted ... N.B. : KeyboardInterrupt does not inherit from Exception + except (KeyboardInterrupt, EOFError): + error = m18n.n("operation_interrupted") + logger.error(m18n.n("app_config_failed", app=app, error=error)) + failure_message_with_debug_instructions = operation_logger.error(error) + raise + # Something wrong happened in Yunohost's code (most probably hook_exec) + except Exception: + import traceback + + error = m18n.n("unexpected_error", error="\n" + traceback.format_exc()) + logger.error(m18n.n("app_config_failed", app=app, error=error)) + failure_message_with_debug_instructions = operation_logger.error(error) + raise finally: # Delete files uploaded from API FileArgumentParser.clean_upload_dirs() @@ -2148,6 +2159,11 @@ def _get_app_config_panel(app_id, filter_key=''): toml_config_panel = toml.load( open(config_panel_toml_path, "r"), _dict=OrderedDict ) + if float(toml_config_panel["version"]) < APPS_CONFIG_PANEL_VERSION_SUPPORTED: + raise YunohostError( + "app_config_too_old_version", app=app_id, + version=toml_config_panel["version"] + ) # transform toml format into json format config_panel = { @@ -2219,6 +2235,13 @@ def _get_app_config_panel(app_id, filter_key=''): config_panel["panel"].append(panel) + if (filter_panel and len(config_panel['panel']) == 0) or \ + (filter_section and len(config_panel['panel'][0]['sections']) == 0) or \ + (filter_option and len(config_panel['panel'][0]['sections'][0]['options']) == 0): + raise YunohostError( + "app_config_bad_filter_key", app=app_id, filter_key=filter_key + ) + return config_panel return None @@ -2830,9 +2853,10 @@ class YunoHostArgumentFormatParser(object): # Prevalidation try: self._prevalidate(question) - except YunoHostValidationError: + except YunohostValidationError as e: if msettings.get('interface') == 'api': raise + msignals.display(str(e), 'error') question.value = None continue break @@ -3037,25 +3061,28 @@ class NumberArgumentParser(YunoHostArgumentFormatParser): ) question_parsed.min = question.get('min', None) question_parsed.max = question.get('max', None) - if question.default is None: + if question_parsed.default is None: question_parsed.default = 0 return question_parsed def _prevalidate(self, question): super()._prevalidate(question) - if question.min is not None and question.value < question.min: - raise YunohostValidationError( - "app_argument_invalid", name=question.name, error=m18n.n("invalid_number") - ) - if question.max is not None and question.value > question.max: - raise YunohostValidationError( - "app_argument_invalid", name=question.name, error=m18n.n("invalid_number") - ) if not isinstance(question.value, int) and not (isinstance(question.value, str) and question.value.isdigit()): raise YunohostValidationError( "app_argument_invalid", name=question.name, error=m18n.n("invalid_number") ) + + if question.min is not None and int(question.value) < question.min: + raise YunohostValidationError( + "app_argument_invalid", name=question.name, error=m18n.n("invalid_number") + ) + + if question.max is not None and int(question.value) > question.max: + raise YunohostValidationError( + "app_argument_invalid", name=question.name, error=m18n.n("invalid_number") + ) + def _post_parse_value(self, question): if isinstance(question.value, int): return super()._post_parse_value(question) From 574b01bcf4ae164518333220d4fd9fdc8964aa24 Mon Sep 17 00:00:00 2001 From: ljf Date: Thu, 26 Aug 2021 17:48:08 +0200 Subject: [PATCH 2767/3170] [fix] Pattern key not working --- 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 0e945c64c..b5b4128dc 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -2876,7 +2876,7 @@ class YunoHostArgumentFormatParser(object): if question.value is not None: if question.choices and question.value not in question.choices: self._raise_invalid_answer(question) - if question.pattern and re.match(question.pattern['regexp'], str(question.value)): + if question.pattern and not re.match(question.pattern['regexp'], str(question.value)): raise YunohostValidationError( question.pattern['error'], name=question.name, From 07cd1428d24fec44f3c6ca0d516f19d28d107f97 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 26 Aug 2021 20:06:39 +0200 Subject: [PATCH 2768/3170] Update locales/en.json --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 2cb3fc329..109c4b16b 100644 --- a/locales/en.json +++ b/locales/en.json @@ -32,7 +32,7 @@ "app_location_unavailable": "This URL is either unavailable, or conflicts with the already installed app(s):\n{apps}", "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 web path after the domain where this app should be installed", + "app_manifest_install_ask_path": "Choose the url path (after the domain) 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?", From aac0146aef754315764705a8f45084ff0f9209e1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 26 Aug 2021 20:14:23 +0200 Subject: [PATCH 2769/3170] Forbid installing apps with a dot in app id --- 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 57498c644..490368a15 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -887,7 +887,7 @@ def app_install( raise YunohostValidationError("disk_space_not_sufficient_install") # Check ID - if "id" not in manifest or "__" in manifest["id"]: + if "id" not in manifest or "__" in manifest["id"] or "." in manifest["id"]: raise YunohostValidationError("app_id_invalid") app_id = manifest["id"] From efec34a3a654f5ec312c9dde03e9b13f7b05224d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 26 Aug 2021 20:31:20 +0200 Subject: [PATCH 2770/3170] tests: Improve code formatting --- src/yunohost/tests/test_user-group.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/yunohost/tests/test_user-group.py b/src/yunohost/tests/test_user-group.py index 63d9a1930..344a20fed 100644 --- a/src/yunohost/tests/test_user-group.py +++ b/src/yunohost/tests/test_user-group.py @@ -162,14 +162,13 @@ def test_import_user(mocker): def test_export_user(mocker): result = user_export() - should_be = "username;firstname;lastname;password;" - should_be += "mailbox-quota;mail;mail-alias;mail-forward;groups" - should_be += "\r\nalice;Alice;White;;0;alice@" + maindomain + ";" - should_be += ','.join([alias + maindomain for alias in FIRST_ALIASES]) - should_be += ";;dev" - should_be += "\r\nbob;Bob;Snow;;0;bob@" + maindomain + ";;;apps" - should_be += "\r\njack;Jack;Black;;0;jack@" + maindomain + ";;;" - + aliases = ','.join([alias + maindomain for alias in FIRST_ALIASES]) + should_be = ( + "username;firstname;lastname;password;mailbox-quota;mail;mail-alias;mail-forward;groups\r\n" + f"alice;Alice;White;;0;alice@{maindomain};{aliases};;dev\r\n" + f"bob;Bob;Snow;;0;bob@{maindomain};;;apps\r\n" + f"jack;Jack;Black;;0;jack@{maindomain};;;" + ) assert result == should_be From ad975a2dbb748b19f6abea17527452010ef30ba4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 26 Aug 2021 20:45:06 +0200 Subject: [PATCH 2771/3170] user import: clarify user deletion handling --- src/yunohost/user.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 11e82146a..076d930ca 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -670,6 +670,7 @@ def user_import(operation_logger, csvfile, update=False, delete=False): def to_list(str_list): return str_list.split(',') if str_list else [] + users_in_csv = [] existing_users = user_list()['users'] past_lines = [] reader = csv.DictReader(csvfile, delimiter=';', quotechar='"') @@ -701,20 +702,26 @@ def user_import(operation_logger, csvfile, update=False, delete=False): user['mail-alias'] = to_list(user['mail-alias']) user['mail-forward'] = to_list(user['mail-forward']) user['domain'] = user['mail'].split('@')[1] + + # User creation if user['username'] not in existing_users: # Generate password if not exists # This could be used when reset password will be merged if not user['password']: user['password'] = random_ascii(70) actions['created'].append(user) - else: - if update: - actions['updated'].append(user) - del existing_users[user['username']] + # User update + elif update: + actions['updated'].append(user) + + users_in_csv.add(user['username']) if delete: - for user in existing_users: - actions['deleted'].append(user) + actions['deleted'] = [user for user in existing_users if user not in users_in_csv] + + if delete and not users_in_csv: + logger.error("You used the delete option with an empty csv file ... You probably did not really mean to do that, did you !?") + is_well_formatted = False if not is_well_formatted: raise YunohostError('user_import_bad_file') From 4f0494d66bb78974b3f8e25522f8ad74475b95d8 Mon Sep 17 00:00:00 2001 From: ericgaspar Date: Thu, 26 Aug 2021 20:57:23 +0200 Subject: [PATCH 2772/3170] Update en.json --- locales/en.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/en.json b/locales/en.json index 27bdd63ab..83c270b58 100644 --- a/locales/en.json +++ b/locales/en.json @@ -32,7 +32,7 @@ "app_location_unavailable": "This URL is either unavailable, or conflicts with the already installed app(s):\n{apps}", "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 url path (after the domain) where this app should be installed", + "app_manifest_install_ask_path": "Choose the URL path (after the domain) 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?", @@ -399,7 +399,7 @@ "log_letsencrypt_cert_install": "Install a Let's Encrypt certificate on '{}' domain", "log_permission_create": "Create permission '{}'", "log_permission_delete": "Delete permission '{}'", - "log_permission_url": "Update url related to permission '{}'", + "log_permission_url": "Update URL related to permission '{}'", "log_selfsigned_cert_install": "Install self-signed certificate on '{}' domain", "log_letsencrypt_cert_renew": "Renew '{}' Let's Encrypt certificate", "log_regen_conf": "Regenerate system configurations '{}'", From 128eb6a7d46f06c4bb2a0079134a20cf5d687b43 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 26 Aug 2021 21:06:16 +0200 Subject: [PATCH 2773/3170] user import: Clarify fields validation --- locales/en.json | 2 +- src/yunohost/tests/test_user-group.py | 4 +-- src/yunohost/user.py | 43 +++++++++++++-------------- 3 files changed, 23 insertions(+), 26 deletions(-) diff --git a/locales/en.json b/locales/en.json index 5b93dbb9f..06d2df0a4 100644 --- a/locales/en.json +++ b/locales/en.json @@ -634,7 +634,7 @@ "user_updated": "User info changed", "user_import_bad_line": "Incorrect line {line}: {details}", "user_import_bad_file": "Your CSV file is not correctly formatted it will be ignored to avoid potential data loss", - "user_import_missing_column": "The column {column} is missing", + "user_import_missing_columns": "The following columns are missing: {columns}", "user_import_partial_failed": "The users import operation partially failed", "user_import_failed": "The users import operation completely failed", "user_import_nothing_to_do": "No user needs to be imported", diff --git a/src/yunohost/tests/test_user-group.py b/src/yunohost/tests/test_user-group.py index 344a20fed..bbedfc27f 100644 --- a/src/yunohost/tests/test_user-group.py +++ b/src/yunohost/tests/test_user-group.py @@ -10,7 +10,7 @@ from yunohost.user import ( user_update, user_import, user_export, - CSV_FIELDNAMES, + FIELDS_FOR_IMPORT, FIRST_ALIASES, user_group_list, user_group_create, @@ -151,7 +151,7 @@ def test_import_user(mocker): user_import(csv_io, update=True, delete=True) group_res = user_group_list()['groups'] - user_res = user_list(CSV_FIELDNAMES)['users'] + user_res = user_list(list(FIELDS_FOR_IMPORT.keys()))['users'] assert "albert" in user_res assert "alice" in user_res assert "bob" not in user_res diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 076d930ca..b055d2cdb 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -43,8 +43,7 @@ from yunohost.log import is_unit_operation logger = getActionLogger("yunohost.user") -CSV_FIELDNAMES = [u'username', u'firstname', u'lastname', u'password', u'mailbox-quota', u'mail', u'mail-alias', u'mail-forward', u'groups'] -VALIDATORS = { +FIELDS_FOR_IMPORT = { 'username': r'^[a-z0-9_]+$', 'firstname': r'^([^\W\d_]{1,30}[ ,.\'-]{0,3})+$', 'lastname': r'^([^\W\d_]{1,30}[ ,.\'-]{0,3})+$', @@ -619,10 +618,10 @@ def user_export(): import csv # CSV are needed only in this function from io import StringIO with StringIO() as csv_io: - writer = csv.DictWriter(csv_io, CSV_FIELDNAMES, + writer = csv.DictWriter(csv_io, list(FIELDS_FOR_IMPORT.keys()), delimiter=';', quotechar='"') writer.writeheader() - users = user_list(CSV_FIELDNAMES)['users'] + users = user_list(list(FIELDS_FOR_IMPORT.keys()))['users'] for username, user in users.items(): user['mail-alias'] = ','.join(user['mail-alias']) user['mail-forward'] = ','.join(user['mail-forward']) @@ -672,24 +671,24 @@ def user_import(operation_logger, csvfile, update=False, delete=False): users_in_csv = [] existing_users = user_list()['users'] - past_lines = [] reader = csv.DictReader(csvfile, delimiter=';', quotechar='"') - for user in reader: - # Validation - try: - format_errors = [key + ':' + str(user[key]) - for key, validator in VALIDATORS.items() - if user[key] is None or not re.match(validator, user[key])] - except KeyError as e: - logger.error(m18n.n('user_import_missing_column', - column=str(e))) - is_well_formatted = False - break - if 'username' in user: - if user['username'] in past_lines: - format_errors.append('username: %s (duplicated)' % user['username']) - past_lines.append(user['username']) + missing_columns = [key for key in FIELDS_FOR_IMPORT.keys() if key not in reader.fieldnames] + if missing_columns: + raise YunohostValidationError("user_import_missing_columns", columns=', '.join(missing_columns)) + + for user in reader: + + # Validate column values against regexes + format_errors = [key + ':' + str(user[key]) + for key, validator in FIELDS_FOR_IMPORT.items() + if user[key] is None or not re.match(validator, user[key])] + + # Check for duplicated username lines + if user['username'] in users_in_csv: + format_errors.append(f'username: {user[username]} (duplicated)') + users_in_csv.append(user['username']) + if format_errors: logger.error(m18n.n('user_import_bad_line', line=reader.line_num, @@ -714,8 +713,6 @@ def user_import(operation_logger, csvfile, update=False, delete=False): elif update: actions['updated'].append(user) - users_in_csv.add(user['username']) - if delete: actions['deleted'] = [user for user in existing_users if user not in users_in_csv] @@ -787,7 +784,7 @@ def user_import(operation_logger, csvfile, update=False, delete=False): for group in new_infos['groups']: user_group_update(group, add=new_infos['username'], sync_perm=False, from_import=True) - users = user_list(CSV_FIELDNAMES)['users'] + users = user_list(list(FIELDS_FOR_IMPORT.keys()))['users'] operation_logger.start() # We do delete and update before to avoid mail uniqueness issues for user in actions['deleted']: From aac8b8d4608db5ab57a89beed6118996c7b27b63 Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Thu, 26 Aug 2021 20:09:57 +0000 Subject: [PATCH 2774/3170] [CI] Format code --- data/hooks/diagnosis/12-dnsrecords.py | 10 +++++++--- data/hooks/diagnosis/21-web.py | 2 +- src/yunohost/domain.py | 4 +++- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py index 1db4af685..6110024f4 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -30,9 +30,14 @@ class DNSRecordsDiagnoser(Diagnoser): for domain in all_domains: self.logger_debug("Diagnosing DNS conf for %s" % domain) is_subdomain = domain.split(".", 1)[1] in all_domains - is_specialusedomain = any(domain.endswith("." + tld) for tld in SPECIAL_USE_TLDS) + is_specialusedomain = any( + domain.endswith("." + tld) for tld in SPECIAL_USE_TLDS + ) for report in self.check_domain( - domain, domain == main_domain, is_subdomain=is_subdomain, is_specialusedomain=is_specialusedomain + domain, + domain == main_domain, + is_subdomain=is_subdomain, + is_specialusedomain=is_specialusedomain, ): yield report @@ -70,7 +75,6 @@ class DNSRecordsDiagnoser(Diagnoser): summary="diagnosis_dns_specialusedomain", ) - for category in categories: records = expected_configuration[category] diff --git a/data/hooks/diagnosis/21-web.py b/data/hooks/diagnosis/21-web.py index 04c36661e..40a6c26b4 100644 --- a/data/hooks/diagnosis/21-web.py +++ b/data/hooks/diagnosis/21-web.py @@ -34,7 +34,7 @@ class WebDiagnoser(Diagnoser): summary="diagnosis_http_nginx_conf_not_up_to_date", details=["diagnosis_http_nginx_conf_not_up_to_date_details"], ) - elif domain.endswith('.local'): + elif domain.endswith(".local"): yield dict( meta={"domain": domain}, status="INFO", diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 3c9192b8f..09d419c71 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -166,7 +166,9 @@ def domain_add(operation_logger, domain, dyndns=False): # because it's one of the major service, but in the long term we # should identify the root of this bug... _force_clear_hashes(["/etc/nginx/conf.d/%s.conf" % domain]) - regen_conf(names=["nginx", "metronome", "dnsmasq", "postfix", "rspamd", "mdns"]) + regen_conf( + names=["nginx", "metronome", "dnsmasq", "postfix", "rspamd", "mdns"] + ) app_ssowatconf() except Exception as e: From 2435f84871a82e1eed43b810eeecacee488bcafd Mon Sep 17 00:00:00 2001 From: Flavio Cristoforetti Date: Wed, 18 Aug 2021 14:21:25 +0000 Subject: [PATCH 2775/3170] Translated using Weblate (Italian) Currently translated at 100.0% (637 of 637 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/it/ --- locales/it.json | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/locales/it.json b/locales/it.json index c818dd14c..9cffa03d8 100644 --- a/locales/it.json +++ b/locales/it.json @@ -30,7 +30,7 @@ "app_not_correctly_installed": "{app} sembra di non essere installata correttamente", "app_not_properly_removed": "{app} non è stata correttamente rimossa", "action_invalid": "L'azione '{action}' non è valida", - "app_removed": "{app} rimossa", + "app_removed": "{app} disinstallata", "app_sources_fetch_failed": "Impossibile riportare i file sorgenti, l'URL è corretto?", "app_upgrade_failed": "Impossibile aggiornare {app}: {error}", "app_upgraded": "{app} aggiornata", @@ -172,7 +172,7 @@ "backup_applying_method_custom": "Chiamando il metodo di backup personalizzato '{method}'...", "backup_applying_method_tar": "Creando l'archivio TAR del backup...", "backup_archive_system_part_not_available": "La parte di sistema '{part}' non è disponibile in questo backup", - "backup_archive_writing_error": "Impossibile aggiungere i file '{source}' (indicati nell'archivio '{dest}') al backup nell'archivio compresso '{archive}'", + "backup_archive_writing_error": "Impossibile aggiungere i file '{source}' (indicati nell'archivio '{dest}') al backup nell'archivio compresso '{archive}'", "backup_ask_for_copying_if_needed": "Vuoi effettuare il backup usando {size}MB temporaneamente? (È necessario usare questo sistema poiché alcuni file non possono essere preparati in un modo più efficiente)", "backup_cant_mount_uncompress_archive": "Impossibile montare in modalità sola lettura la cartella di archivio non compressa", "backup_copying_to_organize_the_archive": "Copiando {size}MB per organizzare l'archivio", @@ -631,5 +631,9 @@ "diagnosis_sshd_config_inconsistent": "Sembra che la porta SSH sia stata modificata manualmente in /etc/ssh/sshd_config: A partire da YunoHost 4.2, una nuova configurazione globale 'security.ssh.port' è disponibile per evitare di modificare manualmente la configurazione.", "diagnosis_sshd_config_insecure": "Sembra che la configurazione SSH sia stata modificata manualmente, ed non è sicuro dato che non contiene le direttive 'AllowGroups' o 'Allowusers' che limitano l'accesso agli utenti autorizzati.", "backup_create_size_estimation": "L'archivio conterrà circa {size} di dati.", - "app_restore_script_failed": "C'è stato un errore all'interno dello script di recupero" -} \ No newline at end of file + "app_restore_script_failed": "C'è stato un errore all'interno dello script di recupero", + "global_settings_setting_security_webadmin_allowlist": "Indirizzi IP con il permesso di accedere al webadmin, separati da virgola.", + "global_settings_setting_security_webadmin_allowlist_enabled": "Permetti solo ad alcuni IP di accedere al webadmin.", + "disk_space_not_sufficient_update": "Non c'è abbastanza spazio libero per aggiornare questa applicazione", + "disk_space_not_sufficient_install": "Non c'è abbastanza spazio libero per installare questa applicazione" +} From cb36c781dcca69f0be16ee7533aeea5f6137413f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M?= Date: Wed, 18 Aug 2021 06:04:28 +0000 Subject: [PATCH 2776/3170] Translated using Weblate (Galician) Currently translated at 58.5% (373 of 637 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/gl/ --- locales/gl.json | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/locales/gl.json b/locales/gl.json index 6ef2c45c1..cc08bf022 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -357,5 +357,20 @@ "global_settings_setting_security_webadmin_allowlist": "Enderezos IP con permiso para acceder á webadmin. Separados por vírgulas.", "global_settings_setting_security_webadmin_allowlist_enabled": "Permitir que só algúns IPs accedan á webadmin.", "disk_space_not_sufficient_update": "Non hai espazo suficiente no disco para actualizar esta aplicación", - "disk_space_not_sufficient_install": "Non queda espazo suficiente no disco para instalar esta aplicación" + "disk_space_not_sufficient_install": "Non queda espazo suficiente no disco para instalar esta aplicación", + "log_help_to_get_log": "Para ver o rexistro completo da operación '{desc}', usa o comando 'yunohost log show {name}{name}'", + "log_link_to_log": "Rexistro completo desta operación: '{desc}'", + "log_corrupted_md_file": "O ficheiro YAML con metadatos asociado aos rexistros está danado: '{md_file}\nErro: {error}'", + "iptables_unavailable": "Non podes andar remexendo en iptables aquí. Ou ben estás nun contedor ou o teu kernel non ten soporte para isto", + "ip6tables_unavailable": "Non podes remexer en ip6tables aquí. Ou ben estás nun contedor ou o teu kernel non ten soporte para isto", + "invalid_regex": "Regex non válido: '{regex}'", + "installation_complete": "Instalación completa", + "hook_name_unknown": "Nome descoñecido do gancho '{name}'", + "hook_list_by_invalid": "Esta propiedade non se pode usar para enumerar os ganchos", + "hook_json_return_error": "Non se puido ler a info de retorno do gancho {path}. Erro: {msg}. Contido en bruto: {raw_content}", + "hook_exec_not_terminated": "O script non rematou correctamente: {path}", + "hook_exec_failed": "Non se executou o script: {path}", + "group_user_not_in_group": "A usuaria {user} non está no grupo {group}", + "group_user_already_in_group": "A usuaria {user} xa está no grupo {group}", + "group_update_failed": "Non se actualizou o grupo '{group}': {error}" } From 1c0b9368ae98cd5622c8f9419a028f8411e9ea0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= Date: Fri, 20 Aug 2021 06:05:11 +0000 Subject: [PATCH 2777/3170] Translated using Weblate (French) Currently translated at 100.0% (637 of 637 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 d40b39c62..c9fddab57 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -571,7 +571,7 @@ "diagnosis_dns_try_dyndns_update_force": "La configuration DNS de ce domaine devrait être automatiquement gérée par YunoHost. Si ce n'est pas le cas, vous pouvez essayer de forcer une mise à jour en utilisant yunohost dyndns update --force.", "app_packaging_format_not_supported": "Cette application ne peut pas être installée car son format n'est pas pris en charge par votre version de YunoHost. Vous devriez probablement envisager de mettre à jour votre système.", "migration_0015_weak_certs": "Il a été constaté que les certificats suivants utilisent encore des algorithmes de signature peu robustes et doivent être mis à jour pour être compatibles avec la prochaine version de NGINX : {certs}", - "global_settings_setting_backup_compress_tar_archives": "Compresser les archives (.tar.gz) au lieu de créer des archives non-compressées lors de la création des backups. N.B. Activer cette option permet de créer des archives plus légères, mais aussi d'avoir des procédures de backup significativement plus longues et plus gourmandes en CPU.", + "global_settings_setting_backup_compress_tar_archives": "Compresser les archives (.tar.gz) au lieu de créer des archives non-compressées lors de la création des backups. N.B. Activer cette option implique de créer des archives plus légères, mais aussi d'avoir des procédures de backup significativement plus longues et plus gourmandes en CPU.", "migration_description_0018_xtable_to_nftable": "Migrer les anciennes règles de trafic réseau vers le nouveau système basé sur nftables", "service_description_php7.3-fpm": "Exécute les applications écrites en PHP avec NGINX", "migration_0018_failed_to_reset_legacy_rules": "La réinitialisation des règles iptable par défaut a échoué : {error}", From 64889b9618049a6617e6ea573198ae53f3291140 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M?= Date: Fri, 20 Aug 2021 05:19:32 +0000 Subject: [PATCH 2778/3170] Translated using Weblate (Galician) Currently translated at 62.4% (398 of 637 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/gl/ --- locales/gl.json | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/locales/gl.json b/locales/gl.json index cc08bf022..45e8ffafe 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -372,5 +372,30 @@ "hook_exec_failed": "Non se executou o script: {path}", "group_user_not_in_group": "A usuaria {user} non está no grupo {group}", "group_user_already_in_group": "A usuaria {user} xa está no grupo {group}", - "group_update_failed": "Non se actualizou o grupo '{group}': {error}" + "group_update_failed": "Non se actualizou o grupo '{group}': {error}", + "log_permission_delete": "Eliminar permiso '{}'", + "log_permission_create": "Crear permiso '{}'", + "log_letsencrypt_cert_install": "Instalar un certificado Let's Encrypt para o dominio '{}'", + "log_dyndns_update": "Actualizar o IP asociado ao teu subdominio YunoHost '{}'", + "log_dyndns_subscribe": "Subscribirse a un subdominio YunoHost '{}'", + "log_domain_remove": "Eliminar o dominio '{}' da configuración do sistema", + "log_domain_add": "Engadir dominio '{}' á configuración do sistema", + "log_remove_on_failed_install": "Eliminar '{}' tras unha instalación fallida", + "log_remove_on_failed_restore": "Eliminar '{}' tras un intento fallido de restablecemento desde copia", + "log_backup_restore_app": "Restablecer '{}' desde unha copia de apoio", + "log_backup_restore_system": "Restablecer o sistema desde unha copia de apoio", + "log_backup_create": "Crear copia de apoio", + "log_available_on_yunopaste": "Este rexistro está dispoñible en {url}", + "log_app_config_apply": "Aplicar a configuración da app '{}'", + "log_app_config_show_panel": "Mostrar o panel de configuración da app '{}'", + "log_app_action_run": "Executar acción da app '{}'", + "log_app_makedefault": "Converter '{}' na app por defecto", + "log_app_upgrade": "Actualizar a app '{}'", + "log_app_remove": "Eliminar a app '{}'", + "log_app_install": "Instalar a app '{}'", + "log_app_change_url": "Cambiar o URL da app '{}'", + "log_operation_unit_unclosed_properly": "Non se pechou correctamente a unidade da operación", + "log_does_exists": "Non hai rexistro de operación co nome '{log}', usa 'yunohost log list' para ver tódolos rexistros de operacións dispoñibles", + "log_help_to_get_failed_log": "A operación '{desc}' non se completou. Comparte o rexistro completo da operación utilizando o comando 'yunohost log share {name}' para obter axuda", + "log_link_to_failed_log": "Non se completou a operación '{desc}'. Por favor envía o rexistro completo desta operación premendo aquí para obter axuda" } From 3e84789a90c34b0569bfc5c4ededa7afe841da6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M?= Date: Sat, 21 Aug 2021 05:14:06 +0000 Subject: [PATCH 2779/3170] Translated using Weblate (Galician) Currently translated at 68.6% (437 of 637 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/gl/ --- locales/gl.json | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/locales/gl.json b/locales/gl.json index 45e8ffafe..c2b6b0161 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -397,5 +397,44 @@ "log_operation_unit_unclosed_properly": "Non se pechou correctamente a unidade da operación", "log_does_exists": "Non hai rexistro de operación co nome '{log}', usa 'yunohost log list' para ver tódolos rexistros de operacións dispoñibles", "log_help_to_get_failed_log": "A operación '{desc}' non se completou. Comparte o rexistro completo da operación utilizando o comando 'yunohost log share {name}' para obter axuda", - "log_link_to_failed_log": "Non se completou a operación '{desc}'. Por favor envía o rexistro completo desta operación premendo aquí para obter axuda" + "log_link_to_failed_log": "Non se completou a operación '{desc}'. Por favor envía o rexistro completo desta operación premendo aquí para obter axuda", + "migration_0015_start": "Comezando a migración a Buster", + "migration_update_LDAP_schema": "Actualizando esquema LDAP...", + "migration_ldap_rollback_success": "Sistema restablecido.", + "migration_ldap_migration_failed_trying_to_rollback": "Non se puido migrar... intentando volver á versión anterior do sistema.", + "migration_ldap_can_not_backup_before_migration": "O sistema de copia de apoio do sistema non se completou antes de que fallase a migración. Erro: {error}", + "migration_ldap_backup_before_migration": "Crear copia de apoio da base de datos LDAP e axustes de apps antes de realizar a migración.", + "migration_description_0020_ssh_sftp_permissions": "Engadir soporte para permisos SSH e SFTP", + "migration_description_0019_extend_permissions_features": "Extender/recrear o sistema de xestión de permisos de apps", + "migration_description_0018_xtable_to_nftable": "Migrar as regras de tráfico de rede antigas ao novo sistema nftable", + "migration_description_0017_postgresql_9p6_to_11": "Migrar bases de datos desde PostgreSQL 9.6 a 11", + "migration_description_0016_php70_to_php73_pools": "Migrar o ficheiros de configuración 'pool' de php7.0-fpm a php7.3", + "migration_description_0015_migrate_to_buster": "Actualizar o sistema a Debian Buster e YunoHost 4.x", + "migrating_legacy_permission_settings": "Migrando os axustes dos permisos anteriores...", + "main_domain_changed": "Foi cambiado o dominio principal", + "main_domain_change_failed": "Non se pode cambiar o dominio principal", + "mail_unavailable": "Este enderezo de email está reservado e debería adxudicarse automáticamente á primeira usuaria", + "mailbox_used_space_dovecot_down": "O servizo de caixa de correo Dovecot ten que estar activo se queres obter o espazo utilizado polo correo", + "mailbox_disabled": "Desactivado email para usuaria {user}", + "mail_forward_remove_failed": "Non se eliminou o reenvío de email '{mail}'", + "mail_domain_unknown": "Enderezo de email non válido para o dominio '{domain}'. Usa un dominio administrado por este servidor.", + "mail_alias_remove_failed": "Non se puido eliminar o alias de email '{mail}'", + "log_tools_reboot": "Reiniciar o servidor", + "log_tools_shutdown": "Apagar o servidor", + "log_tools_upgrade": "Actualizar paquetes do sistema", + "log_tools_postinstall": "Postinstalación do servidor YunoHost", + "log_tools_migrations_migrate_forward": "Executar migracións", + "log_domain_main_domain": "Facer que '{}' sexa o dominio principal", + "log_user_permission_reset": "Restablecer permiso '{}'", + "log_user_permission_update": "Actualizar accesos para permiso '{}'", + "log_user_update": "Actualizar info da usuaria '{}'", + "log_user_group_update": "Actualizar grupo '{}'", + "log_user_group_delete": "Eliminar grupo '{}'", + "log_user_group_create": "Crear grupo '{}'", + "log_user_delete": "Eliminar usuaria '{}'", + "log_user_create": "Engadir usuaria '{}'", + "log_regen_conf": "Rexerar configuración do sistema '{}'", + "log_letsencrypt_cert_renew": "Anovar certificado Let's Encrypt para '{}'", + "log_selfsigned_cert_install": "Instalar certificado auto-asinado para o dominio '{}'", + "log_permission_url": "Actualizar url relativo ao permiso '{}'" } From 23b8782516d565a5e830c0a56a8510ba124ef44e Mon Sep 17 00:00:00 2001 From: mifegui Date: Sun, 22 Aug 2021 12:07:23 +0000 Subject: [PATCH 2780/3170] Translated using Weblate (Portuguese) Currently translated at 9.2% (59 of 637 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/pt/ --- locales/pt.json | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/locales/pt.json b/locales/pt.json index 82edbf349..ef0c41349 100644 --- a/locales/pt.json +++ b/locales/pt.json @@ -4,9 +4,9 @@ "admin_password_change_failed": "Não foi possível alterar a senha", "admin_password_changed": "A senha da administração foi alterada", "app_already_installed": "{app} já está instalada", - "app_extraction_failed": "Não foi possível extrair os ficheiros para instalação", - "app_id_invalid": "A ID da aplicação é inválida", - "app_install_files_invalid": "Ficheiros para instalação corrompidos", + "app_extraction_failed": "Não foi possível extrair os arquivos para instalação", + "app_id_invalid": "App ID invaĺido", + "app_install_files_invalid": "Esses arquivos não podem ser instalados", "app_manifest_invalid": "Manifesto da aplicação inválido: {error}", "app_not_installed": "{app} não está instalada", "app_removed": "{app} removida com êxito", @@ -136,5 +136,9 @@ "app_action_broke_system": "Esta ação parece ter quebrado estes serviços importantes: {services}", "already_up_to_date": "Nada a ser feito. Tudo já está atualizado.", "additional_urls_already_removed": "A URL adicional '{url}'já está removida para a permissão '{permission}'", - "additional_urls_already_added": "A URL adicional '{url}' já está adicionada para a permissão '{permission}'" + "additional_urls_already_added": "A URL adicional '{url}' já está adicionada para a permissão '{permission}'", + "app_install_script_failed": "Ocorreu um erro dentro do script de instalação do aplicativo", + "app_install_failed": "Não foi possível instalar {app}: {error}", + "app_full_domain_unavailable": "Desculpe, esse app deve ser instalado num domínio próprio mas já há outros apps instalados no domínio '{domain}'. Você pode usar um subdomínio dedicado a esse aplicativo.", + "app_change_url_success": "A URL agora é {domain}{path}" } From 97f73458d703b5f2bc4f9dc4026551fd73e9b653 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M?= Date: Mon, 23 Aug 2021 12:11:11 +0000 Subject: [PATCH 2781/3170] Translated using Weblate (Galician) Currently translated at 69.8% (445 of 637 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/gl/ --- locales/gl.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/locales/gl.json b/locales/gl.json index c2b6b0161..42ded4733 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -436,5 +436,13 @@ "log_regen_conf": "Rexerar configuración do sistema '{}'", "log_letsencrypt_cert_renew": "Anovar certificado Let's Encrypt para '{}'", "log_selfsigned_cert_install": "Instalar certificado auto-asinado para o dominio '{}'", - "log_permission_url": "Actualizar url relativo ao permiso '{}'" + "log_permission_url": "Actualizar url relativo ao permiso '{}'", + "migration_0015_general_warning": "Ten en conta que a migración é unha operación delicada. O equipo YunoHost esforzouse revisando e comprobandoa, aínda así algo podería fallar en partes do teu sistema ou as súas apps.\n\nPor tanto, é recomendable:\n- realiza unha copia de apoio de tódolos datos ou apps importantes. Máis info en https://yunohost.org/backup;\n - ten paciencia tras iniciar o proceso: dependendo da túa conexión de internet e hardware podería demorar varias horas a actualización de tódolos compoñentes.", + "migration_0015_system_not_fully_up_to_date": "O teu sistema non está ao día. Realiza unha actualización común antes de realizar a migración a Buster.", + "migration_0015_not_enough_free_space": "Queda moi pouco espazo en /var/! Deberías ter polo menos 1GB libre para realizar a migración.", + "migration_0015_not_stretch": "A distribución Debian actual non é Stretch!", + "migration_0015_yunohost_upgrade": "Iniciando a actualización do núcleo YunoHost...", + "migration_0015_still_on_stretch_after_main_upgrade": "Algo foi mal durante a actualiza ión principal, o sistema semella que aínda está en Debian Stretch", + "migration_0015_main_upgrade": "Iniciando a actualización principal...", + "migration_0015_patching_sources_list": "Correxindo os sources.lists..." } From f50bf89609eda035ee0deede54fb1674cd02a3e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= Date: Mon, 23 Aug 2021 17:09:20 +0000 Subject: [PATCH 2782/3170] Translated using Weblate (French) Currently translated at 100.0% (639 of 639 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 c9fddab57..4ac519c11 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -285,7 +285,7 @@ "mail_unavailable": "Cette adresse de courriel est réservée et doit être automatiquement attribuée au tout premier utilisateur", "good_practices_about_admin_password": "Vous êtes sur le point de définir un nouveau mot de passe d'administration. Le mot de passe doit comporter au moins 8 caractères, bien qu'il soit recommandé d'utiliser un mot de passe plus long (c'est-à-dire une phrase secrète) et/ou d'utiliser une combinaison de caractères (majuscules, minuscules, chiffres et caractères spéciaux).", "good_practices_about_user_password": "Vous êtes sur le point de définir un nouveau mot de passe utilisateur. Le mot de passe doit comporter au moins 8 caractères, bien qu'il soit recommandé d'utiliser un mot de passe plus long (c'est-à-dire une phrase secrète) et/ou une combinaison de caractères (majuscules, minuscules, chiffres et caractères spéciaux).", - "password_listed": "Ce mot de passe fait partie des mots de passe les plus utilisés dans le monde. Veuillez en choisir un autre moins commun et plus fort.", + "password_listed": "Ce mot de passe fait partie des mots de passe les plus utilisés dans le monde. Veuillez en choisir un autre moins commun et plus robuste.", "password_too_simple_1": "Le mot de passe doit comporter au moins 8 caractères", "password_too_simple_2": "Le mot de passe doit comporter au moins 8 caractères et contenir des chiffres, des majuscules et des minuscules", "password_too_simple_3": "Le mot de passe doit comporter au moins 8 caractères et contenir des chiffres, des majuscules, des minuscules et des caractères spéciaux", @@ -635,5 +635,7 @@ "diagnosis_sshd_config_insecure": "La configuration SSH semble avoir été modifiée manuellement et n'est pas sécurisée car elle ne contient aucune directive 'AllowGroups' ou 'AllowUsers' pour limiter l'accès aux utilisateurs autorisés.", "backup_create_size_estimation": "L'archive contiendra environ {size} de données.", "global_settings_setting_security_webadmin_allowlist": "Adresses IP autorisées à accéder à la page web du portail d'administration (webadmin). Elles doivent être séparées par une virgule.", - "global_settings_setting_security_webadmin_allowlist_enabled": "Autorisez seulement certaines IP à accéder à la page web du portail d'administration (webadmin)." + "global_settings_setting_security_webadmin_allowlist_enabled": "Autorisez seulement certaines IP à accéder à la page web du portail d'administration (webadmin).", + "diagnosis_http_localdomain": "Le domaine {domain}, avec un TLD .local, ne devrait pas être atteint depuis l'extérieur du réseau local.", + "diagnosis_dns_specialusedomain": "Le domaine {domain} est basé sur un domaine de premier niveau (TLD) à usage spécial et ne devrait donc pas avoir d'enregistrements DNS réels." } From 17d9dd18c2f8b469c9f2bd963fb1d67c4034609b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M?= Date: Tue, 24 Aug 2021 04:11:15 +0000 Subject: [PATCH 2783/3170] Translated using Weblate (Galician) Currently translated at 71.9% (460 of 639 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/gl/ --- locales/gl.json | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/locales/gl.json b/locales/gl.json index 42ded4733..62fb10c13 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -444,5 +444,20 @@ "migration_0015_yunohost_upgrade": "Iniciando a actualización do núcleo YunoHost...", "migration_0015_still_on_stretch_after_main_upgrade": "Algo foi mal durante a actualiza ión principal, o sistema semella que aínda está en Debian Stretch", "migration_0015_main_upgrade": "Iniciando a actualización principal...", - "migration_0015_patching_sources_list": "Correxindo os sources.lists..." + "migration_0015_patching_sources_list": "Correxindo os sources.lists...", + "migrations_already_ran": "Xa se realizaron estas migracións: {ids}", + "migration_0019_slapd_config_will_be_overwritten": "Semella que editaches manualmente a configuración slapd. Para esta migración crítica YunoHost precisa forzar a actualización da configuración slapd. Os ficheiros orixinais van ser copiados en {conf_backup_folder}.", + "migration_0019_add_new_attributes_in_ldap": "Engadir novos atributos para os permisos na base de datos LDAP", + "migration_0018_failed_to_reset_legacy_rules": "Fallou o restablecemento das regras antigas de iptables: {error}", + "migration_0018_failed_to_migrate_iptables_rules": "Fallou a migración das regras antigas de iptables a nftables: {erro}", + "migration_0017_not_enough_space": "Crea espazo suficiente en {path} para executar a migración.", + "migration_0017_postgresql_11_not_installed": "PostgreSQL 9.6 está instado, pero non postgresql 11? Algo raro debeu acontecer no teu sistema :(...", + "migration_0017_postgresql_96_not_installed": "PostgreSQL non está instalado no teu sistema. Nada que facer.", + "migration_0015_weak_certs": "Os seguintes certificados están a utilizar algoritmos de sinatura débiles e teñen que ser actualizados para ser compatibles coa seguinte versión de nginx: {certs}", + "migration_0015_cleaning_up": "Limpando a caché e paquetes que xa non son útiles...", + "migration_0015_specific_upgrade": "Iniciando a actualización dos paquetes do sistema que precisan ser actualizados de xeito independente...", + "migration_0015_modified_files": "Ten en conta que os seguintes ficheiros semella que foron modificados manualmente e poderían ser sobrescritos na actualización: {manually_modified_files}", + "migration_0015_problematic_apps_warning": "Ten en conta que se detectaron as seguintes apps que poderían ser problemáticas. Semella que non foron instaladas usando o catálogo de YunoHost, ou non están marcadas como 'funcionais'. En consecuencia, non se pode garantir que seguirán funcionando após a actualización: {problematic_apps}", + "diagnosis_http_localdomain": "O dominio {domain}, cun TLD .local, non é de agardar que sexa accesible desde o exterior da rede local.", + "diagnosis_dns_specialusedomain": "O dominio {domain} baséase un dominio de nivel alto e uso especial (TLD) polo que non é de agardar que realmente teña rexistros DNS." } From bb6140af6c227ed930ee9f88dcb0af21fc6178f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M?= Date: Wed, 25 Aug 2021 04:25:57 +0000 Subject: [PATCH 2784/3170] Translated using Weblate (Galician) Currently translated at 79.1% (506 of 639 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/gl/ --- locales/gl.json | 48 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/locales/gl.json b/locales/gl.json index 62fb10c13..46665b870 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -459,5 +459,51 @@ "migration_0015_modified_files": "Ten en conta que os seguintes ficheiros semella que foron modificados manualmente e poderían ser sobrescritos na actualización: {manually_modified_files}", "migration_0015_problematic_apps_warning": "Ten en conta que se detectaron as seguintes apps que poderían ser problemáticas. Semella que non foron instaladas usando o catálogo de YunoHost, ou non están marcadas como 'funcionais'. En consecuencia, non se pode garantir que seguirán funcionando após a actualización: {problematic_apps}", "diagnosis_http_localdomain": "O dominio {domain}, cun TLD .local, non é de agardar que sexa accesible desde o exterior da rede local.", - "diagnosis_dns_specialusedomain": "O dominio {domain} baséase un dominio de nivel alto e uso especial (TLD) polo que non é de agardar que realmente teña rexistros DNS." + "diagnosis_dns_specialusedomain": "O dominio {domain} baséase un dominio de nivel alto e uso especial (TLD) polo que non é de agardar que realmente teña rexistros DNS.", + "upnp_enabled": "UPnP activado", + "upnp_disabled": "UPnP desactivado", + "permission_creation_failed": "Non se creou o permiso '{permission}': {error}", + "permission_created": "Creado o permiso '{permission}'", + "permission_cannot_remove_main": "Non está permitido eliminar un permiso principal", + "permission_already_up_to_date": "Non se actualizou o permiso porque as solicitudes de adición/retirada xa coinciden co estado actual.", + "permission_already_exist": "Xa existe o permiso '{permission}'", + "permission_already_disallowed": "O grupo '{group}' xa ten o permiso '{permission}' desactivado", + "permission_already_allowed": "O grupo '{group}' xa ten o permiso '{permission}' activado", + "pattern_password_app": "Lamentámolo, os contrasinais non poden conter os seguintes caracteres: {forbidden_chars}", + "pattern_username": "Só admite caracteres alfanuméricos en minúscula e trazo baixo", + "pattern_positive_number": "Ten que ser un número positivo", + "pattern_port_or_range": "Debe ser un número válido de porto (entre 0-65535) ou rango de portos (ex. 100:200)", + "pattern_password": "Ten que ter polo menos 3 caracteres", + "pattern_mailbox_quota": "Ten que ser un tamaño co sufixo b/k/M/G/T ou 0 para non ter unha cota", + "pattern_lastname": "Ten que ser un apelido válido", + "pattern_firstname": "Ten que ser un nome válido", + "pattern_email": "Ten que ser un enderezo de email válido, sen o símbolo '+' (ex. persoa@exemplo.com)", + "pattern_email_forward": "Ten que ser un enderezo de email válido, está aceptado o símbolo '+' (ex. persoa+etiqueta@exemplo.com)", + "pattern_domain": "Ten que ser un nome de dominio válido (ex. dominiopropio.org)", + "pattern_backup_archive_name": "Ten que ser un nome de ficheiro válido con 30 caracteres como máximo, alfanuméricos ou só caracteres -_.", + "password_too_simple_4": "O contrasinal debe ter 12 caracteres como mínimo e conter un díxito, maiúsculas, minúsculas e caracteres especiais", + "password_too_simple_3": "O contrasinal debe ter 8 caracteres como mínimo e conter un díxito, maiúsculas, minúsculas e caracteres especiais", + "password_too_simple_2": "O contrasinal debe ter 8 caracteres como mínimo e conter un díxito, maiúsculas e minúsculas", + "password_listed": "Este contrasinal está entre os máis utilizados no mundo. Por favor elixe outro que sexa máis orixinal.", + "packages_upgrade_failed": "Non se puideron actualizar tódolos paquetes", + "operation_interrupted": "Foi interrumpida manualmente a operación?", + "invalid_number": "Ten que ser un número", + "not_enough_disk_space": "Non hai espazo libre abondo en '{path}'", + "migrations_to_be_ran_manually": "A migración {id} ten que ser executada manualmente. Vaite a Ferramentas → Migracións na páxina webadmin, ou executa `yunohost tools migrations run`.", + "migrations_success_forward": "Migración {id} completada", + "migrations_skip_migration": "Omitindo migración {id}...", + "migrations_running_forward": "Realizando migración {id}...", + "migrations_pending_cant_rerun": "Esas migracións están pendentes, polo que non ser executadas outra vez: {ids}", + "migrations_not_pending_cant_skip": "Esas migracións non están pendentes, polo que non poden ser omitidas: {ids}", + "migrations_no_such_migration": "Non hai migración co nome '{id}'", + "migrations_no_migrations_to_run": "Sen migracións a executar", + "migrations_need_to_accept_disclaimer": "Para executar a migración {id}, tes que aceptar o seguinte aviso:\n---\n{disclaimer}\n---\nSe aceptas executar a migración, por favor volve a executar o comando coa opción '--accept-disclaimer'.", + "migrations_must_provide_explicit_targets": "Debes proporcionar obxectivos explícitos ao utilizar '--skip' ou '--force-rerun'", + "migrations_migration_has_failed": "A migración {id} non se completou, abortando. Erro: {exception}", + "migrations_loading_migration": "Cargando a migración {id}...", + "migrations_list_conflict_pending_done": "Non podes usar ao mesmo tempo '--previous' e '--done'.", + "migrations_exclusive_options": "'--auto', '--skip', e '--force-rerun' son opcións que se exclúen unhas a outras.", + "migrations_failed_to_load_migration": "Non se cargou a migración {id}: {error}", + "migrations_dependencies_not_satisfied": "Executar estas migracións: '{dependencies_id}', antes da migración {id}.", + "migrations_cant_reach_migration_file": "Non se pode acceder aos ficheiros de migración na ruta '%s'" } From 5f8a97dd719b72e203be3d382455eccd28fef3e9 Mon Sep 17 00:00:00 2001 From: ppr Date: Wed, 25 Aug 2021 16:07:57 +0000 Subject: [PATCH 2785/3170] Translated using Weblate (French) Currently translated at 99.6% (637 of 639 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 4ac519c11..e78ea4e1f 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -156,7 +156,7 @@ "certmanager_attempt_to_renew_nonLE_cert": "Le certificat pour le domaine {domain} 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} 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} 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_domain_dns_ip_differs_from_public_ip": "L'enregistrement DNS du domaine '{domain}' est différent de l’adresse IP de ce serveur. Pour plus d'informations, veuillez consulter la catégorie \"Enregistrements DNS\" dans la section diagnostic. 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_domain_dns_ip_differs_from_public_ip": "Les enregistrements DNS du domaine '{domain}' est différent de l’adresse IP de ce serveur. Pour plus d'informations, veuillez consulter la catégorie \"Enregistrements DNS\" dans la section diagnostic. 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} (fichier : {file}), la cause est : {reason}", "certmanager_cert_install_success_selfsigned": "Le certificat auto-signé est maintenant installé pour le domaine « {domain} »", "certmanager_cert_install_success": "Le certificat Let’s Encrypt est maintenant installé pour le domaine « {domain} »", @@ -217,10 +217,10 @@ "backup_couldnt_bind": "Impossible de lier {src} avec {dest}.", "domain_dns_conf_is_just_a_recommendation": "Cette commande vous montre la configuration *recommandée*. Elle ne configure pas le DNS pour vous. Il est de votre ressort de configurer votre zone DNS chez votre registrar/fournisseur conformément à cette recommandation.", "migrations_cant_reach_migration_file": "Impossible d’accéder aux fichiers de migration via le chemin '%s'", - "migrations_loading_migration": "Chargement de la migration {id}...", + "migrations_loading_migration": "Chargement de la migration {id} ...", "migrations_migration_has_failed": "La migration {id} a échoué avec l’exception {exception} : annulation", "migrations_no_migrations_to_run": "Aucune migration à lancer", - "migrations_skip_migration": "Ignorer et passer la migration {id}...", + "migrations_skip_migration": "Ignorer et passer la migration {id} ...", "server_shutdown": "Le serveur va s’éteindre", "server_shutdown_confirm": "Le serveur va être éteint immédiatement, le voulez-vous vraiment ? [{answers}]", "server_reboot": "Le serveur va redémarrer", @@ -342,7 +342,7 @@ "regenconf_would_be_updated": "La configuration aurait dû être mise à jour pour la catégorie '{category}'", "regenconf_dry_pending_applying": "Vérification de la configuration en attente qui aurait été appliquée pour la catégorie '{category}'…", "regenconf_failed": "Impossible de régénérer la configuration pour la ou les catégorie(s) : '{categories}'", - "regenconf_pending_applying": "Applique la configuration en attente pour la catégorie '{category}'...", + "regenconf_pending_applying": "Applique la configuration en attente pour la catégorie '{category}' ...", "service_regen_conf_is_deprecated": "'yunohost service regen-conf' est obsolète ! Veuillez plutôt utiliser 'yunohost tools regen-conf' à la place.", "tools_upgrade_at_least_one": "Veuillez spécifier '--apps' ou '--system'", "tools_upgrade_cant_both": "Impossible de mettre à niveau le système et les applications en même temps", @@ -381,7 +381,7 @@ "migrations_already_ran": "Ces migrations sont déjà effectuées : {ids}", "migrations_dependencies_not_satisfied": "Exécutez ces migrations : '{dependencies_id}', avant migration {id}.", "migrations_failed_to_load_migration": "Impossible de charger la migration {id} : {error}", - "migrations_running_forward": "Exécution de la migration {id}...", + "migrations_running_forward": "Exécution de la migration {id} ...", "migrations_success_forward": "Migration {id} terminée", "operation_interrupted": "L'opération a-t-elle été interrompue manuellement ?", "permission_already_exist": "L’autorisation '{permission}' existe déjà", @@ -554,24 +554,24 @@ "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.", "restore_already_installed_apps": "Les applications suivantes ne peuvent pas être restaurées car elles sont déjà installées : {apps}", "regenconf_need_to_explicitly_specify_ssh": "La configuration de ssh a été modifiée manuellement. Vous devez explicitement indiquer la mention --force à \"ssh\" pour appliquer les changements.", - "migration_0015_cleaning_up": "Nettoyage du cache et des paquets qui ne sont plus nécessaires...", - "migration_0015_specific_upgrade": "Démarrage de la mise à jour des paquets du système qui doivent être mis à jour séparément...", + "migration_0015_cleaning_up": "Nettoyage du cache et des paquets qui ne sont plus nécessaires ...", + "migration_0015_specific_upgrade": "Démarrage de la mise à jour des paquets du système qui doivent être mis à jour séparément ...", "migration_0015_modified_files": "Veuillez noter que les fichiers suivants ont été modifiés manuellement et pourraient être écrasés à la suite de la mise à niveau : {manually_modified_files}", "migration_0015_problematic_apps_warning": "Veuillez noter que des applications qui peuvent poser problèmes ont été détectées. Il semble qu'elles n'aient pas été installées à partir du catalogue d'applications YunoHost, ou bien qu'elles ne soient pas signalées comme \"fonctionnelles\". Par conséquent, il n'est pas possible de garantir que les applications suivantes fonctionneront encore après la mise à niveau : {problematic_apps}", "migration_0015_general_warning": "Veuillez noter que cette migration est une opération délicate. L'équipe YunoHost a fait de son mieux pour la revérifier et la tester, mais la migration pourrait quand même casser des éléments du système ou de ses applications.\n\nIl est donc recommandé :\n…- de faire une sauvegarde de toute donnée ou application critique. Plus d'informations ici https://yunohost.org/backup ;\n…- d'être patient après le lancement de la migration. Selon votre connexion internet et votre matériel, la mise à niveau peut prendre jusqu'à quelques heures.", "migration_0015_system_not_fully_up_to_date": "Votre système n'est pas entièrement à jour. Veuillez effectuer une mise à jour normale avant de lancer la migration vers Buster.", "migration_0015_not_enough_free_space": "L'espace libre est très faible dans /var/ ! Vous devriez avoir au moins 1 Go de libre pour effectuer cette migration.", "migration_0015_not_stretch": "La distribution Debian actuelle n'est pas Stretch !", - "migration_0015_yunohost_upgrade": "Démarrage de la mise à jour de YunoHost...", + "migration_0015_yunohost_upgrade": "Démarrage de la mise à jour de YunoHost ...", "migration_0015_still_on_stretch_after_main_upgrade": "Quelque chose s'est mal passé lors de la mise à niveau, le système semble toujours être sous Debian Stretch", - "migration_0015_main_upgrade": "Démarrage de la mise à niveau générale...", - "migration_0015_patching_sources_list": "Mise à jour du fichier sources.lists...", + "migration_0015_main_upgrade": "Démarrage de la mise à niveau générale ...", + "migration_0015_patching_sources_list": "Mise à jour du fichier sources.lists ...", "migration_0015_start": "Démarrage de la migration vers Buster", "migration_description_0015_migrate_to_buster": "Mise à niveau du système vers Debian Buster et YunoHost 4.x", "diagnosis_dns_try_dyndns_update_force": "La configuration DNS de ce domaine devrait être automatiquement gérée par YunoHost. Si ce n'est pas le cas, vous pouvez essayer de forcer une mise à jour en utilisant yunohost dyndns update --force.", "app_packaging_format_not_supported": "Cette application ne peut pas être installée car son format n'est pas pris en charge par votre version de YunoHost. Vous devriez probablement envisager de mettre à jour votre système.", "migration_0015_weak_certs": "Il a été constaté que les certificats suivants utilisent encore des algorithmes de signature peu robustes et doivent être mis à jour pour être compatibles avec la prochaine version de NGINX : {certs}", - "global_settings_setting_backup_compress_tar_archives": "Compresser les archives (.tar.gz) au lieu de créer des archives non-compressées lors de la création des backups. N.B. Activer cette option implique de créer des archives plus légères, mais aussi d'avoir des procédures de backup significativement plus longues et plus gourmandes en CPU.", + "global_settings_setting_backup_compress_tar_archives": "Compresser les archives (.tar.gz) au lieu de créer des archives non-compressées lors de la création des sauvegardes. N.B. Activer cette option implique de créer des archives plus légères, mais aussi d'avoir des procédures de sauvegarde significativement plus longues et plus gourmandes en CPU.", "migration_description_0018_xtable_to_nftable": "Migrer les anciennes règles de trafic réseau vers le nouveau système basé sur nftables", "service_description_php7.3-fpm": "Exécute les applications écrites en PHP avec NGINX", "migration_0018_failed_to_reset_legacy_rules": "La réinitialisation des règles iptable par défaut a échoué : {error}", @@ -612,7 +612,7 @@ "additional_urls_already_removed": "URL supplémentaire '{url}' déjà supprimées pour la permission '{permission}'", "invalid_number": "Doit être un nombre", "migration_description_0019_extend_permissions_features": "Étendre et retravailler le système de gestion des permissions applicatives", - "diagnosis_basesystem_hardware_model": "Le modèle du serveur est {model}", + "diagnosis_basesystem_hardware_model": "Le modèle/architecture du serveur est {model}", "diagnosis_backports_in_sources_list": "Il semble qu'apt (le gestionnaire de paquets) soit configuré pour utiliser le dépôt des rétroportages (backports). A moins que vous ne sachiez vraiment ce que vous faites, nous vous déconseillons fortement d'installer des paquets provenant des rétroportages, car cela risque de créer des instabilités ou des conflits sur votre système.", "postinstall_low_rootfsspace": "Le système de fichiers a une taille totale inférieure à 10 Go, ce qui est préoccupant et devrait attirer votre attention ! Vous allez certainement arriver à court d'espace disque (très) rapidement ! Il est recommandé d'avoir au moins 16 Go à la racine pour ce système de fichiers. Si vous voulez installer YunoHost malgré cet avertissement, relancez la post-installation avec --force-diskspace", "domain_remove_confirm_apps_removal": "Le retrait de ce domaine retirera aussi ces applications :\n{apps}\n\nÊtes vous sûr de vouloir cela ? [{answers}]", From 46916b57d4500d35eb12eb03a3d48d5a73929ac4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M?= Date: Thu, 26 Aug 2021 04:32:57 +0000 Subject: [PATCH 2786/3170] Translated using Weblate (Galician) Currently translated at 81.8% (523 of 639 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/gl/ --- locales/gl.json | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/locales/gl.json b/locales/gl.json index 46665b870..5c0bec604 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -505,5 +505,22 @@ "migrations_exclusive_options": "'--auto', '--skip', e '--force-rerun' son opcións que se exclúen unhas a outras.", "migrations_failed_to_load_migration": "Non se cargou a migración {id}: {error}", "migrations_dependencies_not_satisfied": "Executar estas migracións: '{dependencies_id}', antes da migración {id}.", - "migrations_cant_reach_migration_file": "Non se pode acceder aos ficheiros de migración na ruta '%s'" + "migrations_cant_reach_migration_file": "Non se pode acceder aos ficheiros de migración na ruta '%s'", + "regenconf_file_manually_removed": "O ficheiro de configuración '{conf}' foi eliminado manualmente e non será creado", + "regenconf_file_manually_modified": "O ficheiro de configuración '{conf}' foi modificado manualmente e non vai ser actualizado", + "regenconf_file_kept_back": "Era de agardar que o ficheiro de configuración '{conf}' fose eliminado por regen-conf (categoría {category}) mais foi mantido.", + "regenconf_file_copy_failed": "Non se puido copiar o novo ficheiro de configuración '{new}' a '{conf}'", + "regenconf_file_backed_up": "Ficheiro de configuración '{conf}' copiado a '{backup}'", + "postinstall_low_rootfsspace": "O sistema de ficheiros raiz ten un espazo total menor de 10GB, que é pouco! Probablemente vas quedar sen espazo moi pronto! É recomendable ter polo menos 16GB para o sistema raíz. Se queres instalar YunoHost obviando este aviso, volve a executar a postinstalación con --force-diskspace", + "port_already_opened": "O porto {port:d} xa está aberto para conexións {ip_version}", + "port_already_closed": "O porto {port:d} xa está pechado para conexións {ip_version}", + "permission_require_account": "O permiso {permission} só ten sentido para usuarias cunha conta, e por tanto non pode concederse a visitantes.", + "permission_protected": "O permiso {permission} está protexido. Non podes engadir ou eliminar o grupo visitantes a/de este permiso.", + "permission_updated": "Permiso '{permission}' actualizado", + "permission_update_failed": "Non se actualizou o permiso '{permission}': {error}", + "permission_not_found": "Non se atopa o permiso '{permission}'", + "permission_deletion_failed": "Non se puido eliminar o permiso '{permission}': {error}", + "permission_deleted": "O permiso '{permission}' foi eliminado", + "permission_cant_add_to_all_users": "O permiso {permission} non pode ser concecido a tódalas usuarias.", + "permission_currently_allowed_for_all_users": "Este permiso está concedido actualmente a tódalas usuarias ademáis de a outros grupos. Probablemente queiras ben eliminar o permiso 'all_users' ou ben eliminar os outros grupos que teñen permiso." } From 569dc24267a499d9c231154ed64feab3a3c38fa9 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Thu, 26 Aug 2021 23:01:14 +0200 Subject: [PATCH 2787/3170] remove unused imports --- data/hooks/diagnosis/80-apps.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/data/hooks/diagnosis/80-apps.py b/data/hooks/diagnosis/80-apps.py index ce54faef1..4ab5a6c0d 100644 --- a/data/hooks/diagnosis/80-apps.py +++ b/data/hooks/diagnosis/80-apps.py @@ -1,16 +1,10 @@ #!/usr/bin/env python import os -import re -from yunohost.app import app_info, app_list -from moulinette.utils.filesystem import read_file +from yunohost.app import app_list -from yunohost.settings import settings_get from yunohost.diagnosis import Diagnoser -from yunohost.regenconf import _get_regenconf_infos, _calculate_hash -from moulinette.utils.filesystem import read_file - class AppDiagnoser(Diagnoser): From b0f6a07372f4e5d5ebcaa37c4c9a5dad6c674713 Mon Sep 17 00:00:00 2001 From: ljf Date: Fri, 27 Aug 2021 01:05:20 +0200 Subject: [PATCH 2788/3170] [fix] Source getter for config panel --- data/helpers.d/configpanel | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/configpanel b/data/helpers.d/configpanel index 7e26811f5..e1a96b866 100644 --- a/data/helpers.d/configpanel +++ b/data/helpers.d/configpanel @@ -139,7 +139,7 @@ EOL # Specific case for files (all content of the file is the source) else - old[$short_setting]="$(ls $source 2> /dev/null || echo YNH_NULL)" + old[$short_setting]="$(ls $(echo $source | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/) 2> /dev/null || echo YNH_NULL)" file_hash[$short_setting]="true" fi done From bc725e9768639bcf4330627156af9b84c750f1f8 Mon Sep 17 00:00:00 2001 From: ljf Date: Fri, 27 Aug 2021 01:05:59 +0200 Subject: [PATCH 2789/3170] [fix] File through config panel API --- src/yunohost/app.py | 40 ++++++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index b5b4128dc..4e20c6950 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -2937,7 +2937,7 @@ class PasswordArgumentParser(YunoHostArgumentFormatParser): return question - def _post_parse_value(self, question): + def _prevalidate(self, question): if any(char in question.value for char in self.forbidden_chars): raise YunohostValidationError( "pattern_password_app", forbidden_chars=self.forbidden_chars @@ -2949,8 +2949,6 @@ class PasswordArgumentParser(YunoHostArgumentFormatParser): assert_password_is_strong_enough("user", question.value) - return super(PasswordArgumentParser, self)._post_parse_value(question) - class PathArgumentParser(YunoHostArgumentFormatParser): argument_type = "path" @@ -3129,18 +3127,40 @@ class FileArgumentParser(YunoHostArgumentFormatParser): # Delete files uploaded from API if msettings.get('interface') == 'api': for upload_dir in cls.upload_dirs: - shutil.rmtree(upload_dir) + if os.path.exists(upload_dir): + shutil.rmtree(upload_dir) def parse_question(self, question, user_answers): - question = super(FileArgumentParser, self).parse_question( + question_parsed = super().parse_question( question, user_answers ) + if question.get('accept'): + question_parsed.accept = question.get('accept').replace(' ', '').split(',') + else: + question.accept = [] if msettings.get('interface') == 'api': - question.value = { - 'content': user_answers[question.name], - 'filename': user_answers.get(f"{question.name}[name]", question.name), - } if user_answers.get(question.name) else None - return question + if user_answers.get(question_parsed.name): + question_parsed.value = { + 'content': question_parsed.value, + 'filename': user_answers.get(f"{question_parsed.name}[name]", question_parsed.name), + } + return question_parsed + + def _prevalidate(self, question): + super()._prevalidate(question) + if isinstance(question.value, str) and not os.path.exists(question.value): + raise YunohostValidationError( + "app_argument_invalid", name=question.name, error=m18n.n("invalid_number1") + ) + if question.value is None or not question.accept: + return + + filename = question.value if isinstance(question.value, str) else question.value['filename'] + if '.' not in filename or '.' + filename.split('.')[-1] not in question.accept: + raise YunohostValidationError( + "app_argument_invalid", name=question.name, error=m18n.n("invalid_number2") + ) + def _post_parse_value(self, question): from base64 import b64decode From f7258dd3a6cf91cc07da2d06a960ae2af1fc9ee8 Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Fri, 27 Aug 2021 01:17:11 +0200 Subject: [PATCH 2790/3170] [fix] Another tmp nightmare original idea from Aleks --- data/hooks/conf_regen/06-slapd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/hooks/conf_regen/06-slapd b/data/hooks/conf_regen/06-slapd index 3fa3a0fd2..b2439dcf9 100755 --- a/data/hooks/conf_regen/06-slapd +++ b/data/hooks/conf_regen/06-slapd @@ -2,7 +2,7 @@ set -e -tmp_backup_dir_file="/tmp/slapd-backup-dir.txt" +tmp_backup_dir_file="/root/slapd-backup-dir.txt" config="/usr/share/yunohost/templates/slapd/config.ldif" db_init="/usr/share/yunohost/templates/slapd/db_init.ldif" From 3c646b3d6a647bcd33cad8d929eda9300124f97b Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Fri, 27 Aug 2021 01:17:11 +0200 Subject: [PATCH 2791/3170] [fix] Another tmp nightmare original idea from Aleks --- data/hooks/conf_regen/06-slapd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/hooks/conf_regen/06-slapd b/data/hooks/conf_regen/06-slapd index 3fa3a0fd2..b2439dcf9 100755 --- a/data/hooks/conf_regen/06-slapd +++ b/data/hooks/conf_regen/06-slapd @@ -2,7 +2,7 @@ set -e -tmp_backup_dir_file="/tmp/slapd-backup-dir.txt" +tmp_backup_dir_file="/root/slapd-backup-dir.txt" config="/usr/share/yunohost/templates/slapd/config.ldif" db_init="/usr/share/yunohost/templates/slapd/db_init.ldif" From eef5cd4da913705035767fa62d497096890f6c74 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 27 Aug 2021 01:34:10 +0200 Subject: [PATCH 2792/3170] Update changelog for 4.2.8.1 --- debian/changelog | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/debian/changelog b/debian/changelog index b1894758a..48f3bbdca 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,11 @@ +yunohost (4.2.8.1) stable; urgency=low + + - [fix] Safer location for slapd backup during hdb/mdb migration (3c646b3d) + + Thanks to all contributors <3 ! (ljf) + + -- Alexandre Aubin Fri, 27 Aug 2021 01:32:16 +0200 + yunohost (4.2.8) stable; urgency=low - [fix] ynh_permission_has_user not behaving properly when checking if a group is allowed (f0590907) From 8e35cd8103e8eb40c103ed92d80a2086b35a65b0 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 27 Aug 2021 13:52:10 +0200 Subject: [PATCH 2793/3170] user import: strip spaces --- src/yunohost/user.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index b055d2cdb..ce2c2c34f 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -667,7 +667,9 @@ def user_import(operation_logger, csvfile, update=False, delete=False): is_well_formatted = True def to_list(str_list): - return str_list.split(',') if str_list else [] + L = str_list.split(',') if str_list else [] + L = [l.strip() for l in L] + return L users_in_csv = [] existing_users = user_list()['users'] From 24d87ea40eefbd5ae4fcda45be72314a30bb28d1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 27 Aug 2021 14:09:13 +0200 Subject: [PATCH 2794/3170] user import: validate that groups and domains exists --- src/yunohost/user.py | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index ce2c2c34f..1b0e47f80 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -671,9 +671,12 @@ def user_import(operation_logger, csvfile, update=False, delete=False): L = [l.strip() for l in L] return L - users_in_csv = [] existing_users = user_list()['users'] + existing_groups = user_group_list()["groups"] + existing_domains = domain_list()["domains"] + reader = csv.DictReader(csvfile, delimiter=';', quotechar='"') + users_in_csv = [] missing_columns = [key for key in FIELDS_FOR_IMPORT.keys() if key not in reader.fieldnames] if missing_columns: @@ -688,9 +691,29 @@ def user_import(operation_logger, csvfile, update=False, delete=False): # Check for duplicated username lines if user['username'] in users_in_csv: - format_errors.append(f'username: {user[username]} (duplicated)') + format_errors.append(f"username '{user[username]}' duplicated") users_in_csv.append(user['username']) + # Validate that groups exist + user['groups'] = to_list(user['groups']) + unknown_groups = [g for g in user['groups'] if g not in existing_groups] + if unknown_groups: + format_errors.append(f"username '{user[username]}': unknown groups %s" % ', '.join(unknown_groups)) + + # Validate that domains exist + user['mail-alias'] = to_list(user['mail-alias']) + user['mail-forward'] = to_list(user['mail-forward']) + user['domain'] = user['mail'].split('@')[1] + + unknown_domains = [] + if user['domain'] not in existing_domains: + unknown_domains.append(user['domain']) + + unknown_domains += [mail.split('@')[1:] for mail in user['mail-alias'] if mail.split('@')[1:] not in existing_domains] + + if unknown_domains: + format_errors.append(f"username '{user[username]}': unknown domains %s" % ', '.join(unknown_domains)) + if format_errors: logger.error(m18n.n('user_import_bad_line', line=reader.line_num, @@ -699,10 +722,6 @@ def user_import(operation_logger, csvfile, update=False, delete=False): continue # Choose what to do with this line and prepare data - user['groups'] = to_list(user['groups']) - user['mail-alias'] = to_list(user['mail-alias']) - user['mail-forward'] = to_list(user['mail-forward']) - user['domain'] = user['mail'].split('@')[1] # User creation if user['username'] not in existing_users: From a40084460b96254c4f2b8a3a1ff7031168b60afe Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 27 Aug 2021 14:10:20 +0200 Subject: [PATCH 2795/3170] csv -> CSV --- data/actionsmap/yunohost.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 6e9509bab..0c1895c47 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -222,11 +222,11 @@ user: type: open -u: full: --update - help: Update all existing users contained in the csv file (by default existing users are ignored) + help: Update all existing users contained in the CSV file (by default existing users are ignored) action: store_true -d: full: --delete - help: Delete all existing users that are not contained in the csv file (by default existing users are kept) + help: Delete all existing users that are not contained in the CSV file (by default existing users are kept) action: store_true subcategories: From 4de6bbdf84eb7965517395adacb7319506dc5ad3 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 27 Aug 2021 14:38:40 +0200 Subject: [PATCH 2796/3170] user import: Try to optimize group operations --- src/yunohost/user.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 1b0e47f80..bc684ec0c 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -54,8 +54,10 @@ FIELDS_FOR_IMPORT = { 'mailbox-quota': r'^(\d+[bkMGT])|0$', 'groups': r'^|([a-z0-9_]+(,?[a-z0-9_]+)*)$' } + FIRST_ALIASES = ['root@', 'admin@', 'webmaster@', 'postmaster@', 'abuse@'] + def user_list(fields=None): from yunohost.utils.ldap import _get_ldap_interface @@ -779,18 +781,26 @@ def user_import(operation_logger, csvfile, update=False, delete=False): def update(new_infos, old_infos=False): remove_alias = None remove_forward = None - if info: + remove_groups = [] + add_groups = new_infos["groups"] + if old_infos: new_infos['mail'] = None if old_infos['mail'] == new_infos['mail'] else new_infos['mail'] remove_alias = list(set(old_infos['mail-alias']) - set(new_infos['mail-alias'])) remove_forward = list(set(old_infos['mail-forward']) - set(new_infos['mail-forward'])) new_infos['mail-alias'] = list(set(new_infos['mail-alias']) - set(old_infos['mail-alias'])) new_infos['mail-forward'] = list(set(new_infos['mail-forward']) - set(old_infos['mail-forward'])) - for group, infos in user_group_list()["groups"].items(): - if group == "all_users": + + remove_groups = list(set(old_infos["groups"]) - set(new_infos["groups"])) + add_groups = list(set(new_infos["groups"]) - set(old_infos["groups"])) + + for group, infos in existing_groups: + # Loop only on groups in 'remove_groups' + # Ignore 'all_users' and primary group + if group in ["all_users", new_infos['username']] or group not in remove_groups: continue # If the user is in this group (and it's not the primary group), # remove the member from the group - if new_infos['username'] != group and new_infos['username'] in infos["members"]: + if new_infos['username'] in infos["members"]: user_group_update(group, remove=new_infos['username'], sync_perm=False, from_import=True) user_update(new_infos['username'], @@ -802,7 +812,7 @@ def user_import(operation_logger, csvfile, update=False, delete=False): remove_mailforward=remove_forward, add_mailforward=new_infos['mail-forward'], from_import=True) - for group in new_infos['groups']: + for group in add_groups: user_group_update(group, add=new_infos['username'], sync_perm=False, from_import=True) users = user_list(list(FIELDS_FOR_IMPORT.keys()))['users'] From 865d8e2c8c7838d8ccab813eb2d3b271e8168545 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 27 Aug 2021 14:48:30 +0200 Subject: [PATCH 2797/3170] user import: fix typo --- src/yunohost/user.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index bc684ec0c..07f3e546a 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -693,14 +693,14 @@ def user_import(operation_logger, csvfile, update=False, delete=False): # Check for duplicated username lines if user['username'] in users_in_csv: - format_errors.append(f"username '{user[username]}' duplicated") + format_errors.append(f"username '{user['username']}' duplicated") users_in_csv.append(user['username']) # Validate that groups exist user['groups'] = to_list(user['groups']) unknown_groups = [g for g in user['groups'] if g not in existing_groups] if unknown_groups: - format_errors.append(f"username '{user[username]}': unknown groups %s" % ', '.join(unknown_groups)) + format_errors.append(f"username '{user['username']}': unknown groups %s" % ', '.join(unknown_groups)) # Validate that domains exist user['mail-alias'] = to_list(user['mail-alias']) @@ -714,7 +714,7 @@ def user_import(operation_logger, csvfile, update=False, delete=False): unknown_domains += [mail.split('@')[1:] for mail in user['mail-alias'] if mail.split('@')[1:] not in existing_domains] if unknown_domains: - format_errors.append(f"username '{user[username]}': unknown domains %s" % ', '.join(unknown_domains)) + format_errors.append(f"username '{user['username']}': unknown domains %s" % ', '.join(unknown_domains)) if format_errors: logger.error(m18n.n('user_import_bad_line', From 5a22aa883f597f84d5a770fbd0d0705eb31e99b2 Mon Sep 17 00:00:00 2001 From: Tymofii-Lytvynenko Date: Fri, 27 Aug 2021 13:25:33 +0000 Subject: [PATCH 2798/3170] Translated using Weblate (Ukrainian) Currently translated at 5.3% (34 of 639 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/uk/ --- locales/uk.json | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/locales/uk.json b/locales/uk.json index 9e26dfeeb..c85b4fd0a 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -1 +1,36 @@ -{} \ No newline at end of file +{ + "app_manifest_install_ask_domain": "Оберіть домен, в якому треба встановити цей застосунок", + "app_manifest_invalid": "Щось не так з маніфестом застосунку: {error}", + "app_location_unavailable": "Ця URL-адреса або недоступна, або конфліктує з уже встановленим застосунком (застосунками):\n{apps}", + "app_label_deprecated": "Ця команда застаріла! Будь ласка, використовуйте нову команду 'yunohost user permission update' для управління заголовком застосунку.", + "app_make_default_location_already_used": "Неможливо зробити '{app}' типовим застосунком на домені, '{domain}' вже використовується '{other_app}'", + "app_install_script_failed": "Сталася помилка в скрипті встановлення застосунку", + "app_install_failed": "Неможливо встановити {app}: {error}", + "app_install_files_invalid": "Ці файли не можуть бути встановлені", + "app_id_invalid": "Неприпустимий ID застосунку", + "app_full_domain_unavailable": "Вибачте, цей застосунок повинен бути встановлений на власному домені, але інші застосунки вже встановлені на домені '{domain}'. Замість цього ви можете використовувати піддомен, призначений для цього застосунку.", + "app_extraction_failed": "Не вдалося витягти файли встановлення", + "app_change_url_success": "URL-адреса {app} тепер {domain}{path}", + "app_change_url_no_script": "Застосунок '{app_name}' поки не підтримує зміну URL-адрес. Можливо, вам слід оновити його.", + "app_change_url_identical_domains": "Старий і новий domain/url_path збігаються ('{domain}{path}'), нічого робити не треба.", + "app_change_url_failed_nginx_reload": "Не вдалося перезавантажити NGINX. Ось результат 'nginx -t':\n{nginx_errors}", + "app_argument_required": "Аргумент '{name}' необхідний", + "app_argument_password_no_default": "Помилка під час розбору аргументу пароля '{name}': аргумент пароля не може мати типове значення з причин безпеки", + "app_argument_invalid": "Виберіть правильне значення для аргументу '{name}': {error}", + "app_argument_choice_invalid": "Використовуйте один з цих варіантів '{choices}' для аргументу '{name}'", + "app_already_up_to_date": "{app} має найостаннішу версію", + "app_already_installed_cant_change_url": "Цей застосунок уже встановлено. URL-адреса не може бути змінена тільки цією функцією. Перевірте в `app changeurl`, якщо вона доступна.", + "app_already_installed": "{app} уже встановлено", + "app_action_broke_system": "Ця дія, схоже, порушила роботу наступних важливих служб: {services}", + "app_action_cannot_be_ran_because_required_services_down": "Для виконання цієї дії повинні бути запущені наступні необхідні служби: {services}. Спробуйте перезапустити їх, щоб продовжити (і, можливо, з'ясувати, чому вони не працюють).", + "already_up_to_date": "Нічого не потрібно робити. Все вже актуально.", + "admin_password_too_long": "Будь ласка, виберіть пароль коротше 127 символів", + "admin_password_changed": "Пароль адміністратора було змінено", + "admin_password_change_failed": "Неможливо змінити пароль", + "admin_password": "Пароль адміністратора", + "additional_urls_already_removed": "Додаткова URL-адреса '{url}' вже видалена в додатковій URL-адресі для дозволу '{permission}'", + "additional_urls_already_added": "Додаткова URL-адреса '{url}' вже додана в додаткову URL-адресу для дозволу '{permission}'", + "action_invalid": "Неприпустима дія '{action}'", + "aborting": "Переривання.", + "diagnosis_description_web": "Мережа" +} From 4f6166f8bba942e420239bb3f8b32327ac3f915e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 27 Aug 2021 15:47:40 +0200 Subject: [PATCH 2799/3170] ldap config: Bump DbMaxSize to allow up to 100MB --- data/templates/slapd/config.ldif | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/templates/slapd/config.ldif b/data/templates/slapd/config.ldif index 4d72ab7cc..e1fe3b1b5 100644 --- a/data/templates/slapd/config.ldif +++ b/data/templates/slapd/config.ldif @@ -33,7 +33,7 @@ olcAuthzPolicy: none olcConcurrency: 0 olcConnMaxPending: 100 olcConnMaxPendingAuth: 1000 -olcSizeLimit: 10000 +olcSizeLimit: 50000 olcIdleTimeout: 0 olcIndexSubstrIfMaxLen: 4 olcIndexSubstrIfMinLen: 2 @@ -189,7 +189,7 @@ olcDbIndex: memberUid eq olcDbIndex: uniqueMember eq olcDbIndex: virtualdomain eq olcDbIndex: permission eq -olcDbMaxSize: 10485760 +olcDbMaxSize: 104857600 structuralObjectClass: olcMdbConfig # From a2d28b21de49c49bb318be07aa89532163e51571 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 27 Aug 2021 16:34:07 +0200 Subject: [PATCH 2800/3170] ynh_multimedia: local can only be used in a function --- data/hooks/post_user_create/ynh_multimedia | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/hooks/post_user_create/ynh_multimedia b/data/hooks/post_user_create/ynh_multimedia index 2be3f42d4..26282cdc9 100644 --- a/data/hooks/post_user_create/ynh_multimedia +++ b/data/hooks/post_user_create/ynh_multimedia @@ -16,7 +16,7 @@ mkdir -p "$MEDIA_DIRECTORY/$user/eBook" ln -sfn "$MEDIA_DIRECTORY/share" "$MEDIA_DIRECTORY/$user/Share" # Création du lien symbolique dans le home de l'utilisateur. #link will only be created if the home directory of the user exists and if it's located in '/home' folder -local user_home="$(getent passwd $user | cut -d: -f6 | grep '^/home/')" +user_home="$(getent passwd $user | cut -d: -f6 | grep '^/home/')" if [[ -d "$user_home" ]]; then ln -sfn "$MEDIA_DIRECTORY/$user" "$user_home/Multimedia" fi From 3ad48fd8bc323ad2bd42f4197f0b61cef54dac01 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 27 Aug 2021 16:34:34 +0200 Subject: [PATCH 2801/3170] Fixes after tests on the battlefield --- src/yunohost/user.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 07f3e546a..f6cbeb9bc 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -655,10 +655,11 @@ def user_import(operation_logger, csvfile, update=False, delete=False): """ - import csv # CSV are needed only in this function + import csv # CSV are needed only in this function from moulinette.utils.text import random_ascii from yunohost.permission import permission_sync_to_user from yunohost.app import app_ssowatconf + from yunohost.domain import domain_list # Pre-validate data and prepare what should be done actions = { @@ -711,7 +712,8 @@ def user_import(operation_logger, csvfile, update=False, delete=False): if user['domain'] not in existing_domains: unknown_domains.append(user['domain']) - unknown_domains += [mail.split('@')[1:] for mail in user['mail-alias'] if mail.split('@')[1:] not in existing_domains] + unknown_domains += [mail.split('@', 1)[1] for mail in user['mail-alias'] if mail.split('@', 1)[1] not in existing_domains] + unknown_domains = set(unknown_domains) if unknown_domains: format_errors.append(f"username '{user['username']}': unknown domains %s" % ', '.join(unknown_domains)) @@ -744,7 +746,7 @@ def user_import(operation_logger, csvfile, update=False, delete=False): is_well_formatted = False if not is_well_formatted: - raise YunohostError('user_import_bad_file') + raise YunohostValidationError('user_import_bad_file') total = len(actions['created'] + actions['updated'] + actions['deleted']) @@ -793,7 +795,7 @@ def user_import(operation_logger, csvfile, update=False, delete=False): remove_groups = list(set(old_infos["groups"]) - set(new_infos["groups"])) add_groups = list(set(new_infos["groups"]) - set(old_infos["groups"])) - for group, infos in existing_groups: + for group, infos in existing_groups.items(): # Loop only on groups in 'remove_groups' # Ignore 'all_users' and primary group if group in ["all_users", new_infos['username']] or group not in remove_groups: @@ -813,6 +815,8 @@ def user_import(operation_logger, csvfile, update=False, delete=False): add_mailforward=new_infos['mail-forward'], from_import=True) for group in add_groups: + if group in ["all_users", new_infos['username']]: + continue user_group_update(group, add=new_infos['username'], sync_perm=False, from_import=True) users = user_list(list(FIELDS_FOR_IMPORT.keys()))['users'] @@ -824,7 +828,7 @@ def user_import(operation_logger, csvfile, update=False, delete=False): result['deleted'] += 1 except YunohostError as e: on_failure(user, e) - progress("Deletion") + progress(f"Deleting {user}") for user in actions['updated']: try: @@ -832,7 +836,7 @@ def user_import(operation_logger, csvfile, update=False, delete=False): result['updated'] += 1 except YunohostError as e: on_failure(user['username'], e) - progress("Update") + progress(f"Updating {user['username']}") for user in actions['created']: try: @@ -844,7 +848,7 @@ def user_import(operation_logger, csvfile, update=False, delete=False): result['created'] += 1 except YunohostError as e: on_failure(user['username'], e) - progress("Creation") + progress(f"Creating {user['username']}") permission_sync_to_user() app_ssowatconf() From 0e2105311f1e376328a911dc0f961a5e99e87c28 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 27 Aug 2021 16:36:38 +0200 Subject: [PATCH 2802/3170] user import: fix tests --- src/yunohost/tests/test_user-group.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/yunohost/tests/test_user-group.py b/src/yunohost/tests/test_user-group.py index bbedfc27f..d3b3c81aa 100644 --- a/src/yunohost/tests/test_user-group.py +++ b/src/yunohost/tests/test_user-group.py @@ -164,10 +164,10 @@ def test_export_user(mocker): result = user_export() aliases = ','.join([alias + maindomain for alias in FIRST_ALIASES]) should_be = ( - "username;firstname;lastname;password;mailbox-quota;mail;mail-alias;mail-forward;groups\r\n" - f"alice;Alice;White;;0;alice@{maindomain};{aliases};;dev\r\n" - f"bob;Bob;Snow;;0;bob@{maindomain};;;apps\r\n" - f"jack;Jack;Black;;0;jack@{maindomain};;;" + "username;firstname;lastname;password;mail;mail-alias;mail-forward;mailbox-quota;groups\r\n" + f"alice;Alice;White;;alice@{maindomain};{aliases};;0;dev\r\n" + f"bob;Bob;Snow;;bob@{maindomain};;;0;apps\r\n" + f"jack;Jack;Black;;jack@{maindomain};;;0;" ) assert result == should_be From 5af70af47d0612378cccde87823fe85326825ccb Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 27 Aug 2021 17:09:46 +0200 Subject: [PATCH 2803/3170] user import: Allow empty value for mailbox quota --- src/yunohost/user.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index f6cbeb9bc..0cdd0d3ae 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -51,7 +51,7 @@ FIELDS_FOR_IMPORT = { 'mail': r'^([\w.-]+@([^\W_A-Z]+([-]*[^\W_A-Z]+)*\.)+((xn--)?[^\W_]{2,}))$', 'mail-alias': r'^|([\w.-]+@([^\W_A-Z]+([-]*[^\W_A-Z]+)*\.)+((xn--)?[^\W_]{2,}),?)+$', 'mail-forward': r'^|([\w\+.-]+@([^\W_A-Z]+([-]*[^\W_A-Z]+)*\.)+((xn--)?[^\W_]{2,}),?)+$', - 'mailbox-quota': r'^(\d+[bkMGT])|0$', + 'mailbox-quota': r'^(\d+[bkMGT])|0|$', 'groups': r'^|([a-z0-9_]+(,?[a-z0-9_]+)*)$' } @@ -688,7 +688,7 @@ def user_import(operation_logger, csvfile, update=False, delete=False): for user in reader: # Validate column values against regexes - format_errors = [key + ':' + str(user[key]) + format_errors = [f"{key}: '{user[key]}' doesn't match the expected format" for key, validator in FIELDS_FOR_IMPORT.items() if user[key] is None or not re.match(validator, user[key])] @@ -726,6 +726,7 @@ def user_import(operation_logger, csvfile, update=False, delete=False): continue # Choose what to do with this line and prepare data + user['mailbox-quota'] = user['mailbox-quota'] or "0" # User creation if user['username'] not in existing_users: From 74e3afd45e5c575984f59cade5d25f2a332678d3 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 27 Aug 2021 17:31:57 +0200 Subject: [PATCH 2804/3170] French wording/typos --- locales/fr.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index e78ea4e1f..7719e1390 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -156,7 +156,7 @@ "certmanager_attempt_to_renew_nonLE_cert": "Le certificat pour le domaine {domain} 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} 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} 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_domain_dns_ip_differs_from_public_ip": "Les enregistrements DNS du domaine '{domain}' est différent de l’adresse IP de ce serveur. Pour plus d'informations, veuillez consulter la catégorie \"Enregistrements DNS\" dans la section diagnostic. 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_domain_dns_ip_differs_from_public_ip": "Les enregistrements DNS du domaine '{domain}' sont différents de l’adresse IP de ce serveur. Pour plus d'informations, veuillez consulter la catégorie \"Enregistrements DNS\" dans la section diagnostic. 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} (fichier : {file}), la cause est : {reason}", "certmanager_cert_install_success_selfsigned": "Le certificat auto-signé est maintenant installé pour le domaine « {domain} »", "certmanager_cert_install_success": "Le certificat Let’s Encrypt est maintenant installé pour le domaine « {domain} »", @@ -571,7 +571,7 @@ "diagnosis_dns_try_dyndns_update_force": "La configuration DNS de ce domaine devrait être automatiquement gérée par YunoHost. Si ce n'est pas le cas, vous pouvez essayer de forcer une mise à jour en utilisant yunohost dyndns update --force.", "app_packaging_format_not_supported": "Cette application ne peut pas être installée car son format n'est pas pris en charge par votre version de YunoHost. Vous devriez probablement envisager de mettre à jour votre système.", "migration_0015_weak_certs": "Il a été constaté que les certificats suivants utilisent encore des algorithmes de signature peu robustes et doivent être mis à jour pour être compatibles avec la prochaine version de NGINX : {certs}", - "global_settings_setting_backup_compress_tar_archives": "Compresser les archives (.tar.gz) au lieu de créer des archives non-compressées lors de la création des sauvegardes. N.B. Activer cette option implique de créer des archives plus légères, mais aussi d'avoir des procédures de sauvegarde significativement plus longues et plus gourmandes en CPU.", + "global_settings_setting_backup_compress_tar_archives": "Compresser les archives (.tar.gz) au lieu de créer des archives non-compressées lors de la création des sauvegardes. N.B. Activer cette option permet de créer des archives plus légères, mais aussi d'avoir des procédures de sauvegarde significativement plus longues et plus gourmandes en CPU.", "migration_description_0018_xtable_to_nftable": "Migrer les anciennes règles de trafic réseau vers le nouveau système basé sur nftables", "service_description_php7.3-fpm": "Exécute les applications écrites en PHP avec NGINX", "migration_0018_failed_to_reset_legacy_rules": "La réinitialisation des règles iptable par défaut a échoué : {error}", From c62e168689c449483d4cd865249d82cd6a3cde3a Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Fri, 27 Aug 2021 15:53:09 +0000 Subject: [PATCH 2805/3170] [CI] Format code --- src/yunohost/service.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 671447067..fb12e9053 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -37,7 +37,13 @@ from moulinette import m18n from yunohost.utils.error import YunohostError, YunohostValidationError from moulinette.utils.process import check_output from moulinette.utils.log import getActionLogger -from moulinette.utils.filesystem import read_file, append_to_file, write_to_file, read_yaml, write_to_yaml +from moulinette.utils.filesystem import ( + read_file, + append_to_file, + write_to_file, + read_yaml, + write_to_yaml, +) MOULINETTE_LOCK = "/var/run/moulinette_yunohost.lock" @@ -680,9 +686,11 @@ def _get_services(): services.update(read_yaml(SERVICES_CONF) or {}) - services = {name: infos - for name, infos in services.items() - if name not in legacy_keys_to_delete} + services = { + name: infos + for name, infos in services.items() + if name not in legacy_keys_to_delete + } except Exception: return {} From 2885fef1567bbd6db16af53f97b08b455f574761 Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 27 Aug 2021 11:26:45 +0000 Subject: [PATCH 2806/3170] Translated using Weblate (German) Currently translated at 99.0% (633 of 639 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/de.json b/locales/de.json index ea20c7d7f..0580eac1d 100644 --- a/locales/de.json +++ b/locales/de.json @@ -378,7 +378,7 @@ "diagnosis_mail_ehlo_wrong_details": "Die vom Remote-Diagnose-Server per IPv{ipversion} empfangene EHLO weicht von der Domäne Ihres Servers ab.
Empfangene EHLO: {wrong_ehlo}
Erwartet: {right_ehlo}
Die geläufigste Ursache für dieses Problem ist, dass der Port 25 nicht korrekt auf Ihren Server weitergeleitet wird. Sie können sich zusätzlich auch versichern, dass keine Firewall oder Reverse-Proxy interferiert.", "diagnosis_mail_ehlo_bad_answer_details": "Das könnte daran liegen, dass anstelle Ihres Servers eine andere Maschine antwortet.", "ask_user_domain": "Domäne, welche für die E-Mail-Adresse und den XMPP-Account des Benutzers verwendet werden soll", - "app_manifest_install_ask_is_public": "Soll diese Applikation für anonyme Benutzer sichtbar sein?", + "app_manifest_install_ask_is_public": "Soll diese Applikation für anonyme Benutzer:innen sichtbar sein?", "app_manifest_install_ask_admin": "Wählen Sie einen Administrator für diese Applikation", "app_manifest_install_ask_path": "Wählen Sie den Pfad, in welchem die Applikation installiert werden soll", "diagnosis_mail_blacklist_listed_by": "Ihre IP-Adresse oder Domäne {item} ist auf der Blacklist auf {blacklist_name}", @@ -635,4 +635,4 @@ "global_settings_setting_security_webadmin_allowlist_enabled": "Erlaube nur bestimmten IP-Adressen den Zugriff auf die Verwaltungsseite.", "disk_space_not_sufficient_update": "Es ist nicht genügend Speicherplatz frei, um diese Applikation zu aktuallisieren", "disk_space_not_sufficient_install": "Es ist nicht genügend Speicherplatz frei, um diese Applikation zu installieren" -} \ No newline at end of file +} From 23b06252e3518eacd061e26fd67f4d7c444f2b38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M?= Date: Fri, 27 Aug 2021 13:38:21 +0000 Subject: [PATCH 2807/3170] Translated using Weblate (Galician) Currently translated at 85.1% (544 of 639 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/gl/ --- locales/gl.json | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/locales/gl.json b/locales/gl.json index 5c0bec604..10322d2e8 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -75,7 +75,7 @@ "app_manifest_install_ask_is_public": "Debería esta app estar exposta ante visitantes anónimas?", "app_manifest_install_ask_admin": "Elixe unha usuaria administradora para esta app", "app_manifest_install_ask_password": "Elixe un contrasinal de administración para esta app", - "app_manifest_install_ask_path": "Elixe a ruta onde queres instalar esta app", + "app_manifest_install_ask_path": "Elixe a ruta URL (após o dominio) onde será instalada esta app", "app_manifest_install_ask_domain": "Elixe o dominio onde queres instalar esta app", "app_manifest_invalid": "Hai algún erro no manifesto da app: {error}", "app_location_unavailable": "Este URL ou ben non está dispoñible ou entra en conflito cunha app(s) xa instalada:\n{apps}", @@ -436,7 +436,7 @@ "log_regen_conf": "Rexerar configuración do sistema '{}'", "log_letsencrypt_cert_renew": "Anovar certificado Let's Encrypt para '{}'", "log_selfsigned_cert_install": "Instalar certificado auto-asinado para o dominio '{}'", - "log_permission_url": "Actualizar url relativo ao permiso '{}'", + "log_permission_url": "Actualizar URL relativo ao permiso '{}'", "migration_0015_general_warning": "Ten en conta que a migración é unha operación delicada. O equipo YunoHost esforzouse revisando e comprobandoa, aínda así algo podería fallar en partes do teu sistema ou as súas apps.\n\nPor tanto, é recomendable:\n- realiza unha copia de apoio de tódolos datos ou apps importantes. Máis info en https://yunohost.org/backup;\n - ten paciencia tras iniciar o proceso: dependendo da túa conexión de internet e hardware podería demorar varias horas a actualización de tódolos compoñentes.", "migration_0015_system_not_fully_up_to_date": "O teu sistema non está ao día. Realiza unha actualización común antes de realizar a migración a Buster.", "migration_0015_not_enough_free_space": "Queda moi pouco espazo en /var/! Deberías ter polo menos 1GB libre para realizar a migración.", @@ -522,5 +522,26 @@ "permission_deletion_failed": "Non se puido eliminar o permiso '{permission}': {error}", "permission_deleted": "O permiso '{permission}' foi eliminado", "permission_cant_add_to_all_users": "O permiso {permission} non pode ser concecido a tódalas usuarias.", - "permission_currently_allowed_for_all_users": "Este permiso está concedido actualmente a tódalas usuarias ademáis de a outros grupos. Probablemente queiras ben eliminar o permiso 'all_users' ou ben eliminar os outros grupos que teñen permiso." + "permission_currently_allowed_for_all_users": "Este permiso está concedido actualmente a tódalas usuarias ademáis de a outros grupos. Probablemente queiras ben eliminar o permiso 'all_users' ou ben eliminar os outros grupos que teñen permiso.", + "restore_failed": "Non se puido restablecer o sistema", + "restore_extracting": "Extraendo os ficheiros necesarios desde o arquivo…", + "restore_confirm_yunohost_installed": "Tes a certeza de querer restablecer un sistema xa instalado? [{answers}]", + "restore_complete": "Restablecemento completado", + "restore_cleaning_failed": "Non se puido despexar o directorio temporal de restablecemento", + "restore_backup_too_old": "Este arquivo de apoio non pode ser restaurado porque procede dunha versión YunoHost demasiado antiga.", + "restore_already_installed_apps": "As seguintes apps non se poden restablecer porque xa están instaladas: {apps}", + "restore_already_installed_app": "Unha app con ID '{app}' xa está instalada", + "regex_with_only_domain": "Agora xa non podes usar un regex para o dominio, só para ruta", + "regex_incompatible_with_tile": "/!\\ Empacadoras! O permiso '{permission}' agora ten show_tile establecido como 'true' polo que non podes definir o regex URL como URL principal", + "regenconf_need_to_explicitly_specify_ssh": "A configuración ssh foi modificada manualmente, pero tes que indicar explícitamente a categoría 'ssh' con --force para realmente aplicar os cambios.", + "regenconf_pending_applying": "Aplicando a configuración pendente para categoría '{category}'...", + "regenconf_failed": "Non se rexenerou a configuración para a categoría(s): {categories}", + "regenconf_dry_pending_applying": "Comprobando as configuracións pendentes que deberían aplicarse á categoría '{category}'…", + "regenconf_would_be_updated": "A configuración debería ser actualizada para a categoría '{category}'", + "regenconf_updated": "Configuración actualizada para '{category}'", + "regenconf_up_to_date": "A configuración xa está ao día para a categoría '{category}'", + "regenconf_now_managed_by_yunohost": "O ficheiro de configuración '{conf}' agora está xestionado por YunoHost (categoría {category}).", + "regenconf_file_updated": "Actualizado o ficheiro de configuración '{conf}'", + "regenconf_file_removed": "Eliminado o ficheiro de configuración '{conf}'", + "regenconf_file_remove_failed": "Non se puido eliminar o ficheiro de configuración '{conf}'" } From 75879d486df336cac2745b5bfbf8586a22d45997 Mon Sep 17 00:00:00 2001 From: Tymofii-Lytvynenko Date: Fri, 27 Aug 2021 14:18:43 +0000 Subject: [PATCH 2808/3170] Translated using Weblate (Ukrainian) Currently translated at 5.4% (35 of 639 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/uk/ --- locales/uk.json | 607 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 606 insertions(+), 1 deletion(-) diff --git a/locales/uk.json b/locales/uk.json index c85b4fd0a..71d7ee897 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -32,5 +32,610 @@ "additional_urls_already_added": "Додаткова URL-адреса '{url}' вже додана в додаткову URL-адресу для дозволу '{permission}'", "action_invalid": "Неприпустима дія '{action}'", "aborting": "Переривання.", - "diagnosis_description_web": "Мережа" + "diagnosis_description_web": "Мережа", + "service_reloaded_or_restarted": "Служба '{service}' була перезавантажена або перезапущено", + "service_reload_or_restart_failed": "Не вдалося перезавантажити або перезапустити службу '{service}' Recent service logs: {logs}", + "service_restarted": "Служба '{service}' перезапущено", + "service_restart_failed": "Не вдалося запустити службу '{service}' Недавні журнали служб: {logs}", + "service_reloaded": "Служба '{service}' перезавантажена", + "service_reload_failed": "Не вдалося перезавантажити службу '{service}' Останні журнали служби: {logs}", + "service_removed": "Служба '{service}' вилучена", + "service_remove_failed": "Не вдалося видалити службу '{service}'", + "service_regen_conf_is_deprecated": "'Yunohost service regen-conf' застарів! Будь ласка, використовуйте 'yunohost tools regen-conf' замість цього.", + "service_enabled": "Служба '{service}' тепер буде автоматично запускатися при завантаженні системи.", + "service_enable_failed": "Неможливо змусити службу '{service}' автоматично запускатися при завантаженні. Недавні журнали служб: {logs}", + "service_disabled": "Служба '{service}' більше не буде запускатися при завантаженні системи.", + "service_disable_failed": "Неможливо змусити службу '{service} \"не запускатися при завантаженні. Останні журнали служби: {logs}", + "service_description_yunohost-firewall": "Управляє відкритими і закритими портами підключення до сервісів", + "service_description_yunohost-api": "Управляє взаємодією між веб-інтерфейсом YunoHost і системою", + "service_description_ssh": "Дозволяє віддалено підключатися до сервера через термінал (протокол SSH)", + "service_description_slapd": "Зберігає користувачів, домени і пов'язану з ними інформацію", + "service_description_rspamd": "Фільтрує спам і інші функції, пов'язані з електронною поштою", + "service_description_redis-server": "Спеціалізована база даних, яка використовується для швидкого доступу до даних, черги завдань і зв'язку між програмами", + "service_description_postfix": "Використовується для відправки та отримання електронної пошти", + "service_description_php7.3-fpm": "Запускає програми, написані на мові програмування PHP, за допомогою NGINX", + "service_description_nginx": "Обслуговує або надає доступ до всіх веб-сайтів, розміщених на вашому сервері", + "service_description_mysql": "Зберігає дані додатків (база даних SQL)", + "service_description_metronome": "Служба захисту миттєвого обміну повідомленнями XMPP", + "service_description_fail2ban": "Захист від перебору та інших видів атак з Інтернету", + "service_description_dovecot": "Дозволяє поштовим клієнтам отримувати доступ до електронної пошти (через IMAP і POP3)", + "service_description_dnsmasq": "Обробляє дозвіл доменних імен (DNS)", + "service_description_yunomdns": "Дозволяє вам отримати доступ до вашого сервера, використовуючи 'yunohost.local' у вашій локальній мережі", + "service_cmd_exec_failed": "Не вдалося виконати команду '{команда}'", + "service_already_stopped": "Служба '{service}' вже зупинена", + "service_already_started": "Служба '{service}' вже запущена", + "service_added": "Служба '{service}' була додана", + "service_add_failed": "Не вдалося додати службу '{service}'", + "server_reboot_confirm": "Сервер негайно перезавантажиться, ви впевнені? [{Відповіді}]", + "server_reboot": "сервер перезавантажиться", + "server_shutdown_confirm": "Сервер буде негайно виключений, ви впевнені? [{Відповіді}].", + "server_shutdown": "сервер вимкнеться", + "root_password_replaced_by_admin_password": "Ваш кореневої пароль був замінений на пароль адміністратора.", + "root_password_desynchronized": "Пароль адміністратора був змінений, але YunoHost не зміг поширити це на пароль root!", + "restore_system_part_failed": "Не вдалося відновити системну частину '{part}'.", + "restore_running_hooks": "Запуск хуков відновлення…", + "restore_running_app_script": "Відновлення програми \"{app} '…", + "restore_removing_tmp_dir_failed": "Неможливо видалити старий тимчасовий каталог", + "restore_nothings_done": "Нічого не було відновлено", + "restore_not_enough_disk_space": "Недостатньо місця (простір: {free_space: d} B, необхідний простір: {needed_space: d} B, маржа безпеки: {margin: d} B)", + "restore_may_be_not_enough_disk_space": "Схоже, у вашій системі недостатньо місця (вільного: {free_space: d} B, необхідний простір: {needed_space: d} B, запас міцності: {margin: d} B)", + "restore_hook_unavailable": "Сценарій відновлення для '{part}' недоступним у вашій системі і в архіві його теж немає", + "restore_failed": "Не вдалося відновити систему", + "restore_extracting": "Витяг необхідних файлів з архіву…", + "restore_confirm_yunohost_installed": "Ви дійсно хочете відновити вже встановлену систему? [{Відповіді}].", + "restore_complete": "відновлення завершено", + "restore_cleaning_failed": "Не вдалося очистити тимчасовий каталог відновлення", + "restore_backup_too_old": "Цей архів резервних копій не може бути відновлений, бо він отриманий з дуже старою версією YunoHost.", + "restore_already_installed_apps": "Наступні програми не можуть бути відновлені, тому що вони вже встановлені: {apps}", + "restore_already_installed_app": "Додаток з ідентифікатором \"{app} 'вже встановлено", + "regex_with_only_domain": "Ви не можете використовувати regex для домену, тільки для шляху.", + "regex_incompatible_with_tile": "/! \\ Packagers! Дозвіл '{permission}' має значення show_tile 'true', тому ви не можете визначити regex URL в якості основного URL.", + "regenconf_need_to_explicitly_specify_ssh": "Конфігурація ssh була змінена вручну, але вам потрібно явно вказати категорію 'ssh' з --force, щоб застосувати зміни.", + "regenconf_pending_applying": "Застосування очікує конфігурації для категорії '{категорія}'...", + "regenconf_failed": "Не вдалося відновити конфігурацію для категорії (категорій): {categories}", + "regenconf_dry_pending_applying": "Перевірка очікує конфігурації, яка була б застосована для категорії '{категорія}'…", + "regenconf_would_be_updated": "Конфігурація була б оновлена для категорії '{категорія}'", + "regenconf_updated": "Конфігурація оновлена для категорії '{category}'", + "regenconf_up_to_date": "Конфігурація вже оновлена для категорії '{категорія}'", + "regenconf_now_managed_by_yunohost": "Конфігураційний файл '{conf}' тепер управляється YunoHost (категорія {category}).", + "regenconf_file_updated": "Конфігураційний файл '{conf}' оновлений", + "regenconf_file_removed": "Конфігураційний файл '{conf}' видалений", + "regenconf_file_remove_failed": "Неможливо видалити файл конфігурації '{conf}'", + "regenconf_file_manually_removed": "Конфігураційний файл '{conf}' був видалений вручну і не буде створено", + "regenconf_file_manually_modified": "Конфігураційний файл '{conf}' був змінений вручну і не буде оновлено", + "regenconf_file_kept_back": "Конфігураційний файл '{conf}' повинен був бути вилучений regen-conf (категорія {category}), але був збережений.", + "regenconf_file_copy_failed": "Не вдалося скопіювати новий файл конфігурації '{new}' в '{conf}'", + "regenconf_file_backed_up": "Конфігураційний файл '{conf}' збережений в '{backup}'", + "postinstall_low_rootfsspace": "Загальна площа кореневої файлової системи становить менше 10 ГБ, що викликає занепокоєння! Швидше за все, дисковий простір закінчиться дуже швидко! Рекомендується мати не менше 16 ГБ для кореневої файлової системи. Якщо ви хочете встановити YunoHost, незважаючи на це попередження, повторно запустіть постінсталляцію з параметром --force-diskspace", + "port_already_opened": "Порт {port: d} вже відкритий для з'єднань {ip_version}.", + "port_already_closed": "Порт {port: d} вже закритий для з'єднань {ip_version}.", + "permission_require_account": "Дозвіл {permission} має сенс тільки для користувачів, що мають обліковий запис, і тому не може бути включено для відвідувачів.", + "permission_protected": "Дозвіл {permission} захищено. Ви не можете додавати або видаляти групу відвідувачів в/з цього дозволу.", + "permission_updated": "Дозвіл '{permission}' оновлено", + "permission_update_failed": "Не вдалося оновити дозвіл '{permission}': {error}", + "permission_not_found": "Дозвіл '{permission}', не знайдено", + "permission_deletion_failed": "Не вдалося видалити дозвіл '{permission}': {помилка}", + "permission_deleted": "Дозвіл '{permission}' видалено", + "permission_cant_add_to_all_users": "Дозвіл {permission} не може бути додано всім користувачам.", + "permission_currently_allowed_for_all_users": "В даний час цей дозвіл надається всім користувачам на додаток до інших груп. Ймовірно, вам потрібно або видалити дозвіл 'all_users', або видалити інші групи, яким воно зараз надано.", + "permission_creation_failed": "Не вдалося створити дозвіл '{permission}': {error}", + "permission_created": "Дозвіл '{permission}' створено", + "permission_cannot_remove_main": "Видалення основного дозволу заборонено", + "permission_already_up_to_date": "Дозвіл не було оновлено, тому що запити на додавання/видалення вже відповідають поточному стану.", + "permission_already_exist": "Дозвіл '{permission}' вже існує", + "permission_already_disallowed": "Група '{group}' вже має дозвіл \"{permission} 'відключено", + "permission_already_allowed": "Для гурту \"{group} 'вже включено дозвіл' {permission} '", + "pattern_password_app": "На жаль, паролі не можуть містити такі символи: {forbidden_chars}", + "pattern_username": "Повинен складатися тільки з букв і цифр в нижньому регістрі і символів підкреслення.", + "pattern_positive_number": "Повинно бути позитивним числом", + "pattern_port_or_range": "Повинен бути дійсний номер порту (наприклад, 0-65535) або діапазон портів (наприклад, 100: 200).", + "pattern_password": "Повинен бути довжиною не менше 3 символів", + "pattern_mailbox_quota": "Повинен бути розмір з суфіксом b/k/M/G/T або 0, щоб не мати квоти.", + "pattern_lastname": "Повинна бути дійсне прізвище", + "pattern_firstname": "Повинно бути дійсне ім'я", + "pattern_email": "Повинен бути дійсною адресою електронної пошти, без символу '+' (наприклад, someone@example.com ).", + "pattern_email_forward": "Повинен бути дійсну адресу електронної пошти, символ '+' приймається (наприклад, someone+tag@example.com )", + "pattern_domain": "Повинно бути дійсне доменне ім'я (наприклад, my-domain.org)", + "pattern_backup_archive_name": "Повинно бути правильне ім'я файлу, що містить не більше 30 символів, тільки букви і цифри символи і символ -_.", + "password_too_simple_4": "Пароль повинен бути довжиною не менше 12 символів і містити цифру, верхні, нижні і спеціальні символи.", + "password_too_simple_3": "Пароль повинен бути довжиною не менше 8 символів і містити цифру, верхні, нижні і спеціальні символи", + "password_too_simple_2": "Пароль повинен складатися не менше ніж з 8 символів і містити цифру, верхній і нижній символи", + "password_too_simple_1": "Пароль повинен складатися не менше ніж з 8 символів", + "password_listed": "Цей пароль входить в число найбільш часто використовуваних паролів в світі. Будь ласка, виберіть щось більш унікальне.", + "packages_upgrade_failed": "Не вдалося оновити всі пакети", + "operation_interrupted": "Операція була перервана вручну?", + "invalid_number": "Повинно бути число", + "not_enough_disk_space": "Недостатньо вільного місця на \"{шлях} '.", + "migrations_to_be_ran_manually": "Міграція {id} повинна бути запущена вручну. Будь ласка, перейдіть в розділ Інструменти → Міграції на сторінці веб-адміністратора або виконайте команду `yunohost tools migrations run`.", + "migrations_success_forward": "Міграція {id} завершена", + "migrations_skip_migration": "Пропуск міграції {id}...", + "migrations_running_forward": "Виконання міграції {id}...", + "migrations_pending_cant_rerun": "Ці міграції ще не завершені, тому не можуть бути запущені знову: {ids}", + "migrations_not_pending_cant_skip": "Ці міграції не очікують виконання, тому не можуть бути пропущені: {ids}", + "migrations_no_such_migration": "Не існує міграції під назвою '{id}'.", + "migrations_no_migrations_to_run": "Немає міграцій для запуску", + "migrations_need_to_accept_disclaimer": "Щоб запустити міграцію {id}, ви повинні прийняти наступний відмова від відповідальності: --- {disclaimer} --- Якщо ви згодні запустити міграцію, будь ласка, повторіть команду з опцією '--accept-disclaimer'.", + "migrations_must_provide_explicit_targets": "Ви повинні вказати явні цілі при використанні '--skip' або '--force-rerun'.", + "migrations_migration_has_failed": "Міграція {id} не завершена, переривається. Помилка: {exception}.", + "migrations_loading_migration": "Завантаження міграції {id}...", + "migrations_list_conflict_pending_done": "Ви не можете одночасно використовувати '--previous' і '--done'.", + "migrations_exclusive_options": "'--Auto', '--skip' і '--force-rerun' є взаємовиключними опціями.", + "migrations_failed_to_load_migration": "Не вдалося завантажити міграцію {id}: {error}", + "migrations_dependencies_not_satisfied": "Запустіть ці міграції: '{dependencies_id}', перед міграцією {id}.", + "migrations_cant_reach_migration_file": "Не вдалося отримати доступ до файлів міграцій по шляху '% s'.", + "migrations_already_ran": "Ці міграції вже виконані: {ids}", + "migration_0019_slapd_config_will_be_overwritten": "Схоже, що ви вручну відредагували конфігурацію slapd. Для цього критичного переходу YunoHost повинен примусово оновити конфігурацію slapd. Оригінальні файли будуть збережені в {conf_backup_folder}.", + "migration_0019_add_new_attributes_in_ldap": "Додавання нових атрибутів для дозволів в базі даних LDAP", + "migration_0018_failed_to_reset_legacy_rules": "Не вдалося скинути застарілі правила iptables: {error}", + "migration_0018_failed_to_migrate_iptables_rules": "Не вдалося перенести застарілі правила iptables в nftables: {error}", + "migration_0017_not_enough_space": "Звільніть достатньо місця в {path} для запуску міграції.", + "migration_0017_postgresql_11_not_installed": "PostgreSQL 9.6 встановлений, але не postgresql 11‽ Можливо, у вашій системі відбулося щось дивне: (...", + "migration_0017_postgresql_96_not_installed": "PostgreSQL ні встановлено у вашій системі. Нічого не потрібно робити.", + "migration_0015_weak_certs": "Було виявлено, що такі сертифікати все ще використовують слабкі алгоритми підпису і повинні бути оновлені для сумісності з наступною версією nginx: {certs}", + "migration_0015_cleaning_up": "Очищення кеш-пам'яті і пакетів, які більше не потрібні...", + "migration_0015_specific_upgrade": "Початок поновлення системних пакетів, які повинні бути оновлені незалежно...", + "migration_0015_modified_files": "Зверніть увагу, що такі файли були змінені вручну і можуть бути перезаписані після поновлення: {manually_modified_files}.", + "migration_0015_problematic_apps_warning": "Зверніть увагу, що були виявлені наступні, можливо, проблемні встановлені додатки. Схоже, що вони не були встановлені з каталогу додатків YunoHost або не зазначені як \"робочі\". Отже, не можна гарантувати, що вони будуть працювати після оновлення: {problematic_apps}.", + "migration_0015_general_warning": "Будь ласка, зверніть увагу, що ця міграція є делікатною операцією. Команда YunoHost зробила все можливе, щоб перевірити і протестувати її, але міграція все ще може порушити частина системи або її додатків. Тому рекомендується: - Виконати резервне копіювання всіх важливих даних або додатків. Більш детальна інформація на сайті https://yunohost.org/backup; - Наберіться терпіння після запуску міграції: В залежності від вашого підключення до Інтернету і апаратного забезпечення, оновлення може зайняти до декількох годин.", + "migration_0015_system_not_fully_up_to_date": "Ваша система не повністю оновлена. Будь ласка, виконайте регулярне оновлення перед запуском міграції на Buster.", + "migration_0015_not_enough_free_space": "Вільного місця в/var/досить мало! У вас повинно бути не менше 1 ГБ вільного місця, щоб запустити цю міграцію.", + "migration_0015_not_stretch": "Поточний дистрибутив Debian не є Stretch!", + "migration_0015_yunohost_upgrade": "Починаємо оновлення ядра YunoHost...", + "migration_0015_still_on_stretch_after_main_upgrade": "Щось пішло не так під час основного поновлення, система, схоже, все ще знаходиться на Debian Stretch", + "migration_0015_main_upgrade": "Початок основного поновлення...", + "migration_0015_patching_sources_list": "Виправлення sources.lists...", + "migration_0015_start": "Початок міграції на Buster", + "migration_update_LDAP_schema": "Оновлення схеми LDAP...", + "migration_ldap_rollback_success": "Система відкотилася.", + "migration_ldap_migration_failed_trying_to_rollback": "Не вдалося виконати міграцію... спроба відкату системи.", + "migration_ldap_can_not_backup_before_migration": "Не вдалося завершити резервне копіювання системи перед невдалої міграцією. Помилка: {error}", + "migration_ldap_backup_before_migration": "Створення резервної копії бази даних LDAP і установки додатків перед фактичної міграцією.", + "migration_description_0020_ssh_sftp_permissions": "Додайте підтримку дозволів SSH і SFTP", + "migration_description_0019_extend_permissions_features": "Розширення/переробка системи управління дозволами додатків", + "migration_description_0018_xtable_to_nftable": "Перенесення старих правил мережевого трафіку в нову систему nftable", + "migration_description_0017_postgresql_9p6_to_11": "Перенесення баз даних з PostgreSQL 9.6 на 11", + "migration_description_0016_php70_to_php73_pools": "Перенесіть php7.0-fpm 'pool' conf файли на php7.3", + "migration_description_0015_migrate_to_buster": "Оновлення системи до Debian Buster і YunoHost 4.x", + "migrating_legacy_permission_settings": "Перенесення застарілих налаштувань дозволів...", + "main_domain_changed": "Основний домен був змінений", + "main_domain_change_failed": "Неможливо змінити основний домен", + "mail_unavailable": "Ця електронна адреса зарезервований і буде автоматично виділено найпершого користувачеві", + "mailbox_used_space_dovecot_down": "Поштова служба Dovecot повинна бути запущена, якщо ви хочете отримати використане місце в поштовій скриньці.", + "mailbox_disabled": "Електронна пошта відключена для користувача {user}", + "mail_forward_remove_failed": "Не вдалося видалити переадресацію електронної пошти '{mail}'", + "mail_domain_unknown": "Неправильну адресу електронної пошти для домену '{domain}'. Будь ласка, використовуйте домен, адмініструється цим сервером.", + "mail_alias_remove_failed": "Не вдалося видалити псевдонім електронної пошти '{mail}'", + "log_tools_reboot": "перезавантажити сервер", + "log_tools_shutdown": "Вимкнути ваш сервер", + "log_tools_upgrade": "Оновлення системних пакетів", + "log_tools_postinstall": "Постінсталляція вашого сервера YunoHost", + "log_tools_migrations_migrate_forward": "запустіть міграції", + "log_domain_main_domain": "Зробити '{}' основним доменом", + "log_user_permission_reset": "Скинути дозвіл \"{} '", + "log_user_permission_update": "Оновити доступи для дозволу '{}'", + "log_user_update": "Оновити інформацію для користувача '{}'", + "log_user_group_update": "Оновити групу '{}'", + "log_user_group_delete": "Видалити групу \"{} '", + "log_user_group_create": "Створити групу '{}'", + "log_user_delete": "Видалити користувача '{}'", + "log_user_create": "Додати користувача '{}'", + "log_regen_conf": "Регенерувати системні конфігурації '{}'", + "log_letsencrypt_cert_renew": "Оновити сертифікат Let's Encrypt на домені '{}'", + "log_selfsigned_cert_install": "Встановити самоподпісанний сертифікат на домені '{}'", + "log_permission_url": "Оновити URL, пов'язаний з дозволом '{}'", + "log_permission_delete": "Видалити дозвіл \"{} '", + "log_permission_create": "Створити дозвіл \"{} '", + "log_letsencrypt_cert_install": "Встановіть сертифікат Let's Encrypt на домен '{}'", + "log_dyndns_update": "Оновити IP, пов'язаний з вашим піддоменом YunoHost '{}'", + "log_dyndns_subscribe": "Підписка на піддомен YunoHost '{}'", + "log_domain_remove": "Видалити домен '{}' з конфігурації системи", + "log_domain_add": "Додати домен '{}' в конфігурацію системи", + "log_remove_on_failed_install": "Видалити '{}' після невдалої установки", + "log_remove_on_failed_restore": "Видалити '{}' після невдалого відновлення з резервного архіву", + "log_backup_restore_app": "Відновлення '{}' з архіву резервних копій", + "log_backup_restore_system": "Відновлення системи з резервного архіву", + "log_backup_create": "Створення резервного архіву", + "log_available_on_yunopaste": "Цей журнал тепер доступний за посиланням {url}", + "log_app_config_apply": "Застосувати конфігурацію до додатка \"{} '", + "log_app_config_show_panel": "Показати панель конфігурації програми \"{} '", + "log_app_action_run": "Активації дії додатка \"{} '", + "log_app_makedefault": "Зробити '{}' додатком за замовчуванням", + "log_app_upgrade": "Оновити додаток '{}'", + "log_app_remove": "Для видалення програми '{}'", + "log_app_install": "Встановіть додаток '{}'", + "log_app_change_url": "Змініть URL-адресу додатка \"{} '", + "log_operation_unit_unclosed_properly": "Блок операцій не був закритий належним чином", + "log_does_exists": "Немає журналу операцій з ім'ям '{log}', використовуйте 'yunohost log list', щоб подивитися всі публічні журнали операцій", + "log_help_to_get_failed_log": "Операція '{desc}' не може бути завершена. Будь ласка, поділіться повним журналом цієї операції, використовуючи команду 'yunohost log share {name}', щоб отримати допомогу.", + "log_link_to_failed_log": "Не вдалося завершити операцію '{desc}'. Будь ласка, надайте повний журнал цієї операції, натиснувши тут , щоб отримати допомогу.", + "log_help_to_get_log": "Щоб переглянути журнал операції '{desc}', використовуйте команду 'yunohost log show {name} {name}'.", + "log_link_to_log": "Повний журнал цієї операції: ' {desc} '", + "log_corrupted_md_file": "Файл метаданих YAML, пов'язаний з журналами, пошкоджений: '{md_file} Помилка: {error}'", + "iptables_unavailable": "Ви не можете грати з iptables тут. Ви перебуваєте або в контейнері, або ваше ядро не підтримує його.", + "ip6tables_unavailable": "Ви не можете грати з ip6tables тут. Ви перебуваєте або в контейнері, або ваше ядро не підтримує його.", + "invalid_regex": "Невірний regex: '{regex}'", + "installation_complete": "установка завершена", + "hook_name_unknown": "Невідоме ім'я хука '{name}'", + "hook_list_by_invalid": "Це властивість не може бути використано для перерахування хуков", + "hook_json_return_error": "Не вдалося розпізнати повернення з хука {шлях}. Помилка: {msg}. Необроблений контент: {raw_content}.", + "hook_exec_not_terminated": "Скрипт не завершився належним чином: {шлях}", + "hook_exec_failed": "Не вдалося запустити скрипт: {path}", + "group_user_not_in_group": "Користувач {user} не входить в групу {group}", + "group_user_already_in_group": "Користувач {user} вже в групі {group}", + "group_update_failed": "Не вдалося оновити групу '{group}': {error}", + "group_updated": "Група '{group}' оновлена", + "group_unknown": "Група '{group}' невідома.", + "group_deletion_failed": "Не вдалося видалити групу '{group}': {error}", + "group_deleted": "Група '{group}' вилучена", + "group_cannot_be_deleted": "Група {group} не може бути видалена вручну.", + "group_cannot_edit_primary_group": "Група '{group}' не може бути відредаговано вручну. Це основна група, призначена тільки для одного конкретного користувача.", + "group_cannot_edit_visitors": "Група 'visitors' не може бути відредаговано вручну. Це спеціальна група, що представляє анонімних відвідувачів.", + "group_cannot_edit_all_users": "Група 'all_users' не може бути відредаговано вручну. Це спеціальна група, призначена для всіх користувачів, зареєстрованих в YunoHost.", + "group_creation_failed": "Не вдалося створити групу \"{group} ': {error}", + "group_created": "Група '{group}' створена", + "group_already_exist_on_system_but_removing_it": "Група {group} вже існує в групах системи, але YunoHost видалить її...", + "group_already_exist_on_system": "Група {group} вже існує в групах системи", + "group_already_exist": "Група {group} вже існує", + "good_practices_about_user_password": "Зараз ви маєте визначити новий пароль користувача. Пароль повинен складатися не менше ніж з 8 символів, але хорошою практикою є використання більш довгого пароля (тобто парольної фрази) і/або використання різних символів (заголовних, малих, цифр і спеціальних символів).", + "good_practices_about_admin_password": "Зараз ви збираєтеся поставити новий пароль адміністратора. Пароль повинен складатися не менше ніж з 8 символів, але хорошою практикою є використання більш довгого пароля (тобто парольної фрази) і/або використання різних символів (прописних, малих, цифр і спеціальних символів).", + "global_settings_unknown_type": "Несподівана ситуація, параметр {setting} має тип {unknown_type}, але це не тип, підтримуваний системою.", + "global_settings_setting_backup_compress_tar_archives": "При створенні нових резервних копій стискати архіви (.tar.gz) замість незжатих архівів (.tar). NB: включення цієї опції означає створення більш легких архівів резервних копій, але початкова процедура резервного копіювання буде значно довше і важче для CPU.", + "global_settings_setting_security_webadmin_allowlist": "IP-адреси, яким дозволений доступ до веб-адміну. Через кому.", + "global_settings_setting_security_webadmin_allowlist_enabled": "Дозволити доступ до веб-адміну тільки деяким IP-адресами.", + "global_settings_setting_smtp_relay_password": "Пароль хоста SMTP-ретрансляції", + "global_settings_setting_smtp_relay_user": "Обліковий запис користувача SMTP-реле", + "global_settings_setting_smtp_relay_port": "Порт SMTP-реле", + "global_settings_setting_smtp_relay_host": "SMTP релейний хост, який буде використовуватися для відправки пошти замість цього примірника yunohost. Корисно, якщо ви знаходитеся в одній із цих ситуацій: ваш 25 порт заблокований вашим провайдером або VPS провайдером, у вас є житловий IP в списку DUHL, ви не можете налаштувати зворотний DNS або цей сервер не доступний безпосередньо в інтернеті і ви хочете використовувати інший сервер для відправки пошти.", + "global_settings_setting_smtp_allow_ipv6": "Дозволити використання IPv6 для отримання і відправки пошти", + "global_settings_setting_ssowat_panel_overlay_enabled": "Включити накладення панелі SSOwat", + "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Дозволити використання (застарілого) ключа DSA для конфігурації демона SSH", + "global_settings_unknown_setting_from_settings_file": "Невідомий ключ в настройках: '{setting_key}', відкиньте його і збережіть в /etc/yunohost/settings-unknown.json", + "global_settings_setting_security_ssh_port": "SSH-порт", + "global_settings_setting_security_postfix_compatibility": "Компроміс між сумісністю і безпекою для сервера Postfix. Впливає на шифри (і інші аспекти, пов'язані з безпекою)", + "global_settings_setting_security_ssh_compatibility": "Сумісність і співвідношення безпеки для SSH-сервера. Впливає на шифри (і інші аспекти, пов'язані з безпекою)", + "global_settings_setting_security_password_user_strength": "Надійність пароля користувача", + "global_settings_setting_security_password_admin_strength": "Надійність пароля адміністратора", + "global_settings_setting_security_nginx_compatibility": "Компроміс між сумісністю і безпекою для веб-сервера NGINX. Впливає на шифри (і інші аспекти, пов'язані з безпекою)", + "global_settings_setting_pop3_enabled": "Включити протокол POP3 для поштового сервера.", + "global_settings_reset_success": "Попередні настройки тепер збережені в {шлях}.", + "global_settings_key_doesnt_exists": "Ключ '{settings_key}' не існує в глобальних налаштуваннях, ви можете побачити всі доступні ключі, виконавши команду 'yunohost settings list'.", + "global_settings_cant_write_settings": "Неможливо зберегти файл настройок, причина: {причина}", + "global_settings_cant_serialize_settings": "Не вдалося серіалізовать дані налаштувань, причина: {причина}", + "global_settings_cant_open_settings": "Не вдалося відкрити файл настройок, причина: {причина}", + "global_settings_bad_type_for_setting": "Поганий тип для настройки {setting}, отриманий {received_type}, очікується {expected_type}", + "global_settings_bad_choice_for_enum": "Поганий вибір для настройки {setting}, отримано '{choice}', але доступні наступні варіанти: {available_choices}.", + "firewall_rules_cmd_failed": "Деякі команди правил брандмауера не спрацювали. Більш детальна інформація в журналі.", + "firewall_reloaded": "брандмауер перезавантажений", + "firewall_reload_failed": "Не вдалося перезавантажити брандмауер", + "file_does_not_exist": "Файл {шлях} не існує.", + "field_invalid": "Неприпустиме поле '{}'", + "experimental_feature": "Попередження: Ця функція є експериментальною і не вважається стабільною, ви не повинні використовувати її, якщо не знаєте, що робите.", + "extracting": "Витяг...", + "dyndns_unavailable": "Домен '{domain}' недоступний.", + "dyndns_domain_not_provided": "DynDNS провайдер {provider} не може надати домен {domain}.", + "dyndns_registration_failed": "Не вдалося зареєструвати домен DynDNS: {error}.", + "dyndns_registered": "Домен DynDNS зареєстрований", + "dyndns_provider_unreachable": "Неможливо зв'язатися з провайдером DynDNS {provider}: або ваш YunoHost неправильно підключений до інтернету, або сервер dynette не працює.", + "dyndns_no_domain_registered": "Домен не зареєстрований в DynDNS", + "dyndns_key_not_found": "DNS-ключ не знайдений для домену", + "dyndns_key_generating": "Генерація DNS-ключа... Це може зайняти деякий час.", + "dyndns_ip_updated": "Оновлення свій IP-адресу в DynDNS", + "dyndns_ip_update_failed": "Не вдалося оновити IP-адреса в DynDNS", + "dyndns_could_not_check_available": "Не вдалося перевірити наявність певної {домен} на {провайдера}.", + "dyndns_could_not_check_provide": "Не вдалося перевірити, чи може {провайдер} надати {домен}.", + "dpkg_lock_not_available": "Ця команда не може бути виконана прямо зараз, тому що інша програма, схоже, використовує блокування dpkg (системного менеджера пакетів).", + "dpkg_is_broken": "Ви не можете зробити це прямо зараз, тому що dpkg/APT (системні менеджери пакетів), схоже, знаходяться в зламаному стані... Ви можете спробувати вирішити цю проблему, підключившись через SSH і виконавши `sudo apt install --fix-broken` і/або `sudo dpkg --configure -a`.", + "downloading": "Завантаження…", + "done": "Готово", + "domains_available": "Доступні домени:", + "domain_unknown": "невідомий домен", + "domain_name_unknown": "Домен '{domain}' невідомий", + "domain_uninstall_app_first": "Ці додатки все ще встановлені на вашому домені: {apps} ласка, видаліть їх за допомогою 'yunohost app remove the_app_id' або перемістити їх на інший домен за допомогою 'yunohost app change-url the_app_id', перш ніж приступити до видалення домену.", + "domain_remove_confirm_apps_removal": "Видалення цього домену призведе до видалення цих додатків: {apps} Ви впевнені, що хочете це зробити? [{Відповіді}].", + "domain_hostname_failed": "Неможливо встановити нове ім'я хоста. Це може викликати проблеми в подальшому (можливо, все буде в порядку).", + "domain_exists": "Домен вже існує", + "domain_dyndns_root_unknown": "Невідомий кореневої домен DynDNS", + "domain_dyndns_already_subscribed": "Ви вже підписалися на домен DynDNS", + "domain_dns_conf_is_just_a_recommendation": "Ця команда показує * рекомендовану * конфігурацію. Насправді вона не встановлює конфігурацію DNS для вас. Ви самі повинні налаштувати свою зону DNS у реєстратора відповідно до цих рекомендацій.", + "domain_deletion_failed": "Неможливо видалити домен {domain}: {помилка}", + "domain_deleted": "домен видалений", + "domain_creation_failed": "Неможливо створити домен {domain}: {помилка}", + "domain_created": "домен створений", + "domain_cert_gen_failed": "Не вдалося згенерувати сертифікат", + "domain_cannot_remove_main_add_new_one": "Ви не можете видалити '{domain}', так як це основний домен і ваш єдиний домен, вам потрібно спочатку додати інший домен за допомогою 'yunohost domain add ', потім встановити його як основний домен за допомогою ' yunohost domain main-domain -n 'і потім ви можете видалити домен' {domain} 'за допомогою' yunohost domain remove {domain} ''.", + "domain_cannot_add_xmpp_upload": "Ви не можете додавати домени, що починаються з 'xmpp-upload.'. Таке ім'я зарезервовано для функції XMPP upload, вбудованої в YunoHost.", + "domain_cannot_remove_main": "Ви не можете видалити '{domain}', так як це основний домен, спочатку вам потрібно встановити інший домен в якості основного за допомогою 'yunohost domain main-domain -n '; ось список доменів-кандидатів: {other_domains}", + "disk_space_not_sufficient_update": "Недостатньо місця на диску для поновлення цього додатка", + "disk_space_not_sufficient_install": "Бракує місця на диску для установки цього додатка", + "diagnosis_sshd_config_inconsistent_details": "Будь ласка, виконайте yunohost settings set security.ssh.port -v YOUR_SSH_PORT , щоб визначити порт SSH, і перевірте yunohost tools regen-conf ssh --dry-run --with-diff yunohost tools regen-conf ssh --force , щоб скинути ваш conf на рекомендований YunoHost.", + "diagnosis_sshd_config_inconsistent": "Схоже, що порт SSH був вручну змінений в/etc/ssh/sshd_config. Починаючи з версії YunoHost 4.2, доступний новий глобальний параметр 'security.ssh.port', що дозволяє уникнути ручного редагування конфігурації.", + "diagnosis_sshd_config_insecure": "Схоже, що конфігурація SSH була змінена вручну і є небезпечною, оскільки не містить директив 'AllowGroups' або 'AllowUsers' для обмеження доступу авторизованих користувачів.", + "diagnosis_processes_killed_by_oom_reaper": "Деякі процеси були недавно вбито системою через брак пам'яті. Зазвичай це є симптомом нестачі пам'яті в системі або процесу, який з'їв дуже багато пам'яті. Зведення убитих процесів: {kills_summary}", + "diagnosis_never_ran_yet": "Схоже, що цей сервер був налаштований недавно, і поки немає звіту про діагностику. Вам слід почати з повної діагностики, або з веб-адміністратора, або використовуючи 'yunohost diagnosis run' з командного рядка.", + "diagnosis_unknown_categories": "Наступні категорії невідомі: {categories}", + "diagnosis_http_nginx_conf_not_up_to_date_details": "Щоб виправити ситуацію, перевірте різницю за допомогою командного рядка, використовуючи yunohost tools regen-conf nginx --dry-run --with-diff , і якщо все в порядку, застосуйте зміни за допомогою yunohost tools regen-conf nginx --force .", + "diagnosis_http_nginx_conf_not_up_to_date": "Схоже, що конфігурація nginx цього домену була змінена вручну, що не дозволяє YunoHost визначити, чи доступний він по HTTP.", + "diagnosis_http_partially_unreachable": "Домен {domain} здається недоступним по HTTP ззовні локальної мережі в IPv {failed}, хоча він працює в IPv {passed}.", + "diagnosis_http_unreachable": "Домен {domain} здається недоступним через HTTP ззовні локальної мережі.", + "diagnosis_http_bad_status_code": "Схоже, що замість вашого сервера відповіла інша машина (можливо, ваш маршрутизатор).
1. Найбільш поширеною причиною цієї проблеми є те, що порт 80 (і 443) неправильно перенаправлений на ваш сервер .
2. На більш складних установках: переконайтеся, що немає брандмауера або зворотного проксі.", + "diagnosis_http_connection_error": "Помилка підключення: не вдалося підключитися до запитуваного домену, швидше за все, він недоступний.", + "diagnosis_http_timeout": "При спробі зв'язатися з вашим сервером ззовні стався тайм-аут. Він здається недоступним.
1. Найбільш поширеною причиною цієї проблеми є те, що порт 80 (і 443) неправильно перенаправлений на ваш сервер .
2. Ви також повинні переконатися, що служба nginx запущена
3. На більш складних установках: переконайтеся, що немає брандмауера або зворотного проксі.", + "diagnosis_http_ok": "Домен {domain} доступний по HTTP ззовні локальної мережі.", + "diagnosis_http_localdomain": "Домен {domain} з доменом .local TLD не може бути доступний ззовні локальної мережі.", + "diagnosis_http_could_not_diagnose_details": "Помилка: {error}.", + "diagnosis_http_could_not_diagnose": "Не вдалося діагностувати досяжність доменів ззовні в IPv {ipversion}.", + "diagnosis_http_hairpinning_issue_details": "Можливо, це пов'язано з блоком/маршрутизатором вашого інтернет-провайдера. В результаті, люди ззовні вашої локальної мережі зможуть отримати доступ до вашого сервера, як і очікувалося, але не люди зсередини локальної мережі (як ви, ймовірно?) При використанні доменного імені або глобального IP. Можливо, ви зможете поліпшити ситуацію, глянувши на https://yunohost.org/dns_local_network .", + "diagnosis_http_hairpinning_issue": "Схоже, що у вашій локальній мережі не включена проброска.", + "diagnosis_ports_forwarding_tip": "Щоб вирішити цю проблему, вам, швидше за все, потрібно налаштувати кидок портів на вашому інтернет-маршрутизатор, як описано в https://yunohost.org/isp_box_config.", + "diagnosis_ports_needed_by": "Відкриття цього порту необхідно для функцій {категорії} (служба {сервіс}).", + "diagnosis_ports_ok": "Порт {port} доступний ззовні.", + "diagnosis_ports_partially_unreachable": "Порт {port} не доступний ззовні в IPv {failed}.", + "diagnosis_ports_unreachable": "Порт {port} недоступний ззовні.", + "diagnosis_ports_could_not_diagnose_details": "Помилка: {error}", + "diagnosis_ports_could_not_diagnose": "Не вдалося діагностувати досяжність портів ззовні в IPv {ipversion}.", + "diagnosis_description_regenconf": "Конфігурації системи", + "diagnosis_description_mail": "Електронна пошта", + "diagnosis_description_ports": "виявлення портів", + "diagnosis_description_systemresources": "Системні ресурси", + "diagnosis_description_services": "Перевірка стану служб", + "diagnosis_description_dnsrecords": "записи DNS", + "diagnosis_description_ip": "Інтернет-з'єднання", + "diagnosis_description_basesystem": "Базова система", + "diagnosis_security_vulnerable_to_meltdown_details": "Щоб виправити це, вам слід оновити систему і перезавантажитися, щоб завантажити нове ядро linux (або звернутися до вашого серверного провайдеру, якщо це не спрацює). Додаткову інформацію див. На сайті https://meltdownattack.com/.", + "diagnosis_security_vulnerable_to_meltdown": "Схоже, що ви уразливі до критичної уразливості безпеки Meltdown.", + "diagnosis_rootfstotalspace_critical": "Коренева файлова система має тільки {простір}, що вельми тривожно! Швидше за все, дисковий простір закінчиться дуже швидко! Рекомендується мати не менше 16 ГБ для кореневої файлової системи.", + "diagnosis_rootfstotalspace_warning": "Коренева файлова система має тільки {простір}. Це може бути нормально, але будьте обережні, тому що в кінцевому підсумку дисковий простір може швидко закінчитися... Рекомендується мати не менше 16 ГБ для кореневої файлової системи.", + "diagnosis_regenconf_manually_modified_details": "Це можливо нормально, якщо ви знаєте, що робите! YunoHost перестане оновлювати цей файл автоматично... Але врахуйте, що поновлення YunoHost можуть містити важливі рекомендовані зміни. Якщо ви хочете, ви можете перевірити відмінності за допомогою команди yunohost tools regen-conf {category} --dry-run --with-diff і примусово повернути рекомендовану конфігурацію за допомогою yunohost tools regen- conf {category} --force .", + "diagnosis_regenconf_manually_modified": "Конфігураційний файл {file} , схоже, був змінений вручну.", + "diagnosis_regenconf_allgood": "Всі конфігураційні файли відповідають рекомендованої конфігурації!", + "diagnosis_mail_queue_too_big": "Занадто багато відкладених листів в поштовій черзі ({nb_pending} emails)", + "diagnosis_mail_queue_unavailable_details": "Помилка: {error}", + "diagnosis_mail_queue_unavailable": "Неможливо дізнатися кількість очікують листів в черзі", + "diagnosis_mail_queue_ok": "{nb_pending} відкладені листи в поштових чергах", + "diagnosis_mail_blacklist_website": "Після визначення причини, по якій ви потрапили в чорний список, і її усунення, ви можете попросити видалити ваш IP або домен на {blacklist_website}.", + "diagnosis_mail_blacklist_reason": "Причина внесення в чорний список: {причина}", + "diagnosis_mail_blacklist_listed_by": "Ваш IP або домен {item} знаходиться в чорному списку {blacklist_name}.", + "diagnosis_mail_blacklist_ok": "IP-адреси і домени, які використовуються цим сервером, не внесені в чорний список", + "diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "Поточний зворотний DNS: {rdns_domain}
Очікуване значення: {ehlo_domain} .", + "diagnosis_mail_fcrdns_different_from_ehlo_domain": "Зворотний DNS неправильно налаштований в IPv {ipversion}. Деякі електронні листи можуть бути не доставлені або можуть бути відзначені як спам.", + "diagnosis_mail_fcrdns_nok_alternatives_6": "Деякі провайдери не дозволяють вам налаштувати зворотний DNS (або їх функція може бути зламана...). Якщо ваш зворотний DNS правильно налаштований для IPv4, ви можете спробувати відключити використання IPv6 при відправці листів, виконавши команду yunohost settings set smtp.allow_ipv6 -v off . Примітка: останнє рішення означає, що ви не зможете відправляти або отримувати електронну пошту з нечисленних серверів, що використовують тільки IPv6.", + "diagnosis_mail_fcrdns_nok_alternatives_4": "Деякі провайдери не дозволять вам налаштувати зворотний DNS (або їх функція може бути зламана...). Якщо ви відчуваєте проблеми з-за цього, розгляньте наступні рішення:
- Деякі провайдери надають альтернативу
використання ретранслятора поштового сервера , хоча це має на увазі, що ретранслятор зможе шпигувати за вашим поштовим трафіком.
- Альтернативою для захисту конфіденційності є використання VPN * з виділеним публічним IP * для обходу подібних обмежень. Дивіться https://yunohost.org/#/vpn_advantage
- Або можна переключитися на іншого провайдера .", + "diagnosis_mail_fcrdns_nok_details": "Спочатку спробуйте налаштувати зворотний DNS з {ehlo_domain} в інтерфейсі вашого інтернет-маршрутизатора або в інтерфейсі вашого хостинг-провайдера. (Деякі хостинг-провайдери можуть вимагати, щоб ви відправили їм тікет підтримки для цього).", + "diagnosis_mail_fcrdns_dns_missing": "У IPv {ipversion} не визначений зворотний DNS. Деякі листи можуть не доставлятися або позначатися як спам.", + "diagnosis_mail_fcrdns_ok": "Ваш зворотний DNS налаштований правильно!", + "diagnosis_mail_ehlo_could_not_diagnose_details": "Помилка: {error}.", + "diagnosis_mail_ehlo_could_not_diagnose": "Не вдалося діагностувати наявність певної поштовий сервер postfix ззовні в IPv {ipversion}.", + "diagnosis_mail_ehlo_wrong_details": "EHLO, отриманий віддаленим діагностичним центром в IPv {ipversion}, відрізняється від домену вашого сервера.
Отриманий EHLO: {wrong_ehlo}
Очікуваний: {right_ehlo} < br> найпоширенішою причиною цієї проблеми є те, що порт 25 неправильно перенаправлений на ваш сервер . Крім того, переконайтеся, що в роботу сервера не втручається брандмауер або зворотний проксі-сервер.", + "diagnosis_mail_ehlo_wrong": "Інший поштовий SMTP-сервер відповідає на IPv {ipversion}. Ваш сервер, ймовірно, не зможе отримувати електронну пошту.", + "diagnosis_mail_ehlo_bad_answer_details": "Це може бути викликано тим, що замість вашого сервера відповідає інша машина.", + "diagnosis_mail_ehlo_bad_answer": "Ні-SMTP служба відповідає на порту 25 на IPv {ipversion}.", + "diagnosis_mail_ehlo_unreachable_details": "Не вдалося відкрити з'єднання по порту 25 з вашим сервером на IPv {ipversion}. Він здається недоступним.
1. Найбільш поширеною причиною цієї проблеми є те, що порт 25 неправильно перенаправлений на ваш сервер .
2. Ви також повинні переконатися, що служба postfix запущена.
3. На більш складних установках: переконайтеся, що немає брандмауера або зворотного проксі.", + "diagnosis_mail_ehlo_unreachable": "Поштовий сервер SMTP недоступний ззовні по IPv {ipversion}. Він не зможе отримувати повідомлення електронної пошти.", + "diagnosis_mail_ehlo_ok": "Поштовий сервер SMTP доступний ззовні і тому може отримувати електронну пошту!", + "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Деякі провайдери не дозволять вам розблокувати вихідний порт 25, тому що вони не піклуються про Net Neutrality.
- Деякі з них пропонують альтернативу використання ретранслятора поштового сервера , хоча це має на увазі, що ретранслятор зможе шпигувати за вашим поштовим трафіком.
- Альтернативою для захисту конфіденційності є використання VPN * з виділеним публічним IP * для обходу такого роду обмежень. Дивіться https://yunohost.org/#/vpn_advantage
- Ви також можете розглянути можливість переходу на более дружнього до мережевого нейтралітету провайдера .", + "diagnosis_mail_outgoing_port_25_blocked_details": "Спочатку спробуйте розблокувати вихідний порт 25 в інтерфейсі вашого інтернет-маршрутизатора або в інтерфейсі вашого хостинг-провайдера. (Деякі хостинг-провайдери можуть вимагати, щоб ви відправили їм заявку в службу підтримки).", + "diagnosis_mail_outgoing_port_25_blocked": "Поштовий сервер SMTP не може відправляти електронні листи на інші сервери, оскільки вихідний порт 25 заблокований в IPv {ipversion}.", + "app_manifest_install_ask_path": "Оберіть шлях URL (після домену), за яким має бути встановлено цей застосунок", + "yunohost_postinstall_end_tip": "Постінсталляція завершена! Щоб завершити установку, будь ласка, розгляньте наступні варіанти: - додавання першого користувача через розділ 'Користувачі' веб-адміністратора (або 'yunohost user create ' в командному рядку); - діагностику можливих проблем через розділ 'Діагностика' веб-адміністратора (або 'yunohost diagnosis run' в командному рядку); - читання розділів 'Завершення установки' і 'Знайомство з YunoHost' в документації адміністратора: https://yunohost.org/admindoc.", + "yunohost_not_installed": "YunoHost встановлений неправильно. Будь ласка, запустіть 'yunohost tools postinstall'.", + "yunohost_installing": "Установлення YunoHost...", + "yunohost_configured": "YunoHost вже налаштований", + "yunohost_already_installed": "YunoHost вже встановлено", + "user_updated": "Інформація про користувача змінена", + "user_update_failed": "Не вдалося оновити користувача {user}: {error}", + "user_unknown": "Невідомий користувач: {user}", + "user_home_creation_failed": "Не вдалося створити домашню папку для користувача", + "user_deletion_failed": "Не вдалося видалити користувача {user}: {error}", + "user_deleted": "користувача видалено", + "user_creation_failed": "Не вдалося створити користувача {user}: {помилка}", + "user_created": "Аккаунт було створено", + "user_already_exists": "Користувач '{user}' вже існує", + "upnp_port_open_failed": "Не вдалося відкрити порт через UPnP", + "upnp_enabled": "UPnP включено", + "upnp_disabled": "UPnP вимкнено", + "upnp_dev_not_found": "UPnP-пристрій, не знайдено", + "upgrading_packages": "Оновлення пакетів...", + "upgrade_complete": "оновлення завершено", + "updating_apt_cache": "Вибірка доступних оновлень для системних пакетів...", + "update_apt_cache_warning": "Щось пішло не так при оновленні кеша APT (менеджера пакунків Debian). Ось дамп рядків sources.list, який може допомогти визначити проблемні рядки: {sourceslist}", + "update_apt_cache_failed": "Неможливо оновити кеш APT (менеджер пакетів Debian). Ось дамп рядків sources.list, який може допомогти визначити проблемні рядки: {sourceslist}", + "unrestore_app": "{App} не буде поновлено", + "unlimit": "немає квоти", + "unknown_main_domain_path": "Невідомий домен або шлях для '{app}'. Вам необхідно вказати домен і шлях, щоб мати можливість вказати URL для дозволу.", + "unexpected_error": "Щось пішло не так: {error}", + "unbackup_app": "{App} НЕ буде збережений", + "tools_upgrade_special_packages_completed": "Оновлення пакета YunoHost завершено. Натисніть [Enter] для повернення командного рядка", + "tools_upgrade_special_packages_explanation": "Спеціальне оновлення триватиме у фоновому режимі. Будь ласка, не запускайте ніяких інших дій на вашому сервері протягом наступних ~ 10 хвилин (в залежності від швидкості обладнання). Після цього вам, можливо, доведеться заново увійти в веб-адмін. Журнал поновлення буде доступний в Інструменти → Журнал (в веб-адміном) або за допомогою 'yunohost log list' (з командного рядка).", + "tools_upgrade_special_packages": "Тепер оновлюємо \"спеціальні\" (пов'язані з yunohost) пакети…", + "tools_upgrade_regular_packages_failed": "Не вдалося оновити пакети: {packages_list}", + "tools_upgrade_regular_packages": "Тепер оновлюємо \"звичайні\" (не пов'язані з yunohost) пакети…", + "tools_upgrade_cant_unhold_critical_packages": "Не вдалося утримати критичні пакети…", + "tools_upgrade_cant_hold_critical_packages": "Не вдалося утримати критичні пакети…", + "tools_upgrade_cant_both": "Неможливо оновити систему і програми одночасно", + "tools_upgrade_at_least_one": "Будь ласка, вкажіть 'apps', або 'system'.", + "this_action_broke_dpkg": "Ця дія порушило dpkg/APT (системні менеджери пакетів)... Ви можете спробувати вирішити цю проблему, підключившись по SSH і запустивши `sudo apt install --fix-broken` і/або` sudo dpkg --configure -a`.", + "system_username_exists": "Ім'я користувача вже існує в списку користувачів системи", + "system_upgraded": "система оновлена", + "ssowat_conf_updated": "Конфігурація SSOwat оновлена", + "ssowat_conf_generated": "Регенерувати конфігурація SSOwat", + "show_tile_cant_be_enabled_for_regex": "Ви не можете включити 'show_tile' прямо зараз, тому що URL для дозволу '{permission}' являє собою регекс", + "show_tile_cant_be_enabled_for_url_not_defined": "Ви не можете включити 'show_tile' прямо зараз, тому що спочатку ви повинні визначити URL для дозволу '{permission}'", + "service_unknown": "Невідома служба '{service}'", + "service_stopped": "Служба '{service}' зупинена", + "service_stop_failed": "Неможливо зупинити службу '{service}' Недавні журнали служб: {logs}", + "service_started": "Служба '{service}' запущена", + "service_start_failed": "Не вдалося запустити службу '{service}' Recent service logs: {logs}", + "diagnosis_mail_outgoing_port_25_ok": "Поштовий сервер SMTP може відправляти електронні листи (вихідний порт 25 не заблокований).", + "diagnosis_swap_tip": "Будь ласка, будьте обережні і знайте, що якщо сервер розміщує своп на SD-карті або SSD-накопичувачі, це може різко скоротити термін служби устройства`.", + "diagnosis_swap_ok": "Система має {усього} свопу!", + "diagnosis_swap_notsomuch": "Система має тільки {усього} свопу. Щоб уникнути ситуацій, коли в системі закінчується пам'ять, слід передбачити наявність не менше {рекомендованого} обсягу підкачки.", + "diagnosis_swap_none": "В системі повністю відсутній своп. Ви повинні розглянути можливість додавання принаймні {рекомендованого} обсягу підкачки, щоб уникнути ситуацій, коли системі не вистачає пам'яті.", + "diagnosis_ram_ok": "Система все ще має {доступно} ({доступний_процент}%) оперативної пам'яті з {усього}.", + "diagnosis_ram_low": "У системі є {доступно} ({доступний_процент}%) оперативної пам'яті (з {усього}). Будьте уважні.", + "diagnosis_ram_verylow": "Система має тільки {доступне} ({доступний_процент}%) оперативної пам'яті! (З {усього})", + "diagnosis_diskusage_ok": "У сховищі {mountpoint} (на пристрої {device} ) залишилося {free} ({free_percent}%) вільного місця (з {total})!", + "diagnosis_diskusage_low": "Сховище {mountpoint} (на пристрої {device} ) має тільки {free} ({free_percent}%) вільного місця (з {total}). Будьте уважні.", + "diagnosis_diskusage_verylow": "Сховище {mountpoint} (на пристрої {device} ) має тільки {free} ({free_percent}%) вільного місця (з {total}). Вам дійсно варто подумати про очищення простору!", + "diagnosis_services_bad_status_tip": "Ви можете спробувати перезапустити службу , а якщо це не допоможе, подивіться журнали служби в webadmin (з командного рядка це можна зробити за допомогою yunohost service restart {service} yunohost service log {service} ).", + "diagnosis_services_bad_status": "Сервіс {service} знаходиться в {status} :(", + "diagnosis_services_conf_broken": "Конфігурація порушена для служби {service}!", + "diagnosis_services_running": "Служба {service} запущена!", + "diagnosis_domain_expires_in": "Термін дії {домену} закінчується через {днів} днів.", + "diagnosis_domain_expiration_error": "Термін дії деяких доменів закінчується ДУЖЕ СКОРО!", + "diagnosis_domain_expiration_warning": "Термін дії деяких доменів закінчиться найближчим часом!", + "diagnosis_domain_expiration_success": "Ваші домени зареєстровані і не збираються спливати найближчим часом.", + "diagnosis_domain_expiration_not_found_details": "Інформація WHOIS для домену {domain} не містить інформації про термін дії?", + "diagnosis_domain_not_found_details": "Домен {domain} не існує в базі даних WHOIS або термін його дії закінчився!", + "diagnosis_domain_expiration_not_found": "Неможливо перевірити термін дії деяких доменів", + "diagnosis_dns_specialusedomain": "Домен {domain} заснований на домені верхнього рівня спеціального призначення (TLD) і тому не очікується, що у нього будуть актуальні записи DNS.", + "diagnosis_dns_try_dyndns_update_force": "Конфігурація DNS цього домену повинна автоматично управлятися YunoHost. Якщо це не так, ви можете спробувати примусово оновити її за допомогою команди yunohost dyndns update --force .", + "diagnosis_dns_point_to_doc": "Якщо вам потрібна допомога з налаштування DNS-записів, зверніться до документації на сайті https://yunohost.org/dns_config .", + "diagnosis_dns_discrepancy": "Наступний запис DNS, схоже, не відповідає рекомендованої конфігурації:
Type: {type}
Name: {name}
Поточне значення: {current}
Очікуване значення: {value} ", + "diagnosis_dns_missing_record": "Згідно рекомендованої конфігурації DNS, ви повинні додати запис DNS з наступною інформацією.
Тип: {type}
Name: {name}
Value: < code> {value} .", + "diagnosis_dns_bad_conf": "Деякі DNS-записи відсутні або невірні для домену {домен} (категорія {категорія})", + "diagnosis_dns_good_conf": "DNS-записи правильно налаштовані для домену {домен} (категорія {категорія})", + "diagnosis_ip_weird_resolvconf_details": "Файл /etc/resolv.conf повинен бути симлінк на /etc/resolvconf/run/resolv.conf , що вказує на 127.0.0.1 (dnsmasq ). Якщо ви хочете вручну налаштувати DNS Резолвер, відредагуйте /etc/resolv.dnsmasq.conf .", + "diagnosis_ip_weird_resolvconf": "Дозвіл DNS, схоже, працює, але схоже, що ви використовуєте для користувача /etc/resolv.conf .", + "diagnosis_ip_broken_resolvconf": "Схоже, що дозвіл доменних імен на вашому сервері порушено, що пов'язано з тим, що /etc/resolv.conf не вказує на 127.0.0.1 .", + "diagnosis_ip_broken_dnsresolution": "Дозвіл доменних імен, схоже, з якоїсь причини не працює... Брандмауер блокує DNS-запити?", + "diagnosis_ip_dnsresolution_working": "Дозвіл доменних імен працює!", + "diagnosis_ip_not_connected_at_all": "Здається, що сервер взагалі не підключений до Інтернету !?", + "diagnosis_ip_local": "Локальний IP: {local} .", + "diagnosis_ip_global": "Глобальний IP: {global} ", + "diagnosis_ip_no_ipv6_tip": "Наявність працюючого IPv6 не є обов'язковим для роботи вашого сервера, але це краще для здоров'я Інтернету в цілому. IPv6 зазвичай автоматично налаштовується системою або вашим провайдером, якщо він доступний. В іншому випадку вам, можливо, доведеться налаштувати деякі речі вручну, як пояснюється в документації тут: https://yunohost.org/#/ipv6. Якщо ви не можете включити IPv6 або якщо це здається вам занадто технічним, ви також можете сміливо ігнорувати це попередження.", + "diagnosis_ip_no_ipv6": "Сервер не має працюючого IPv6.", + "diagnosis_ip_connected_ipv6": "Сервер підключений до Інтернету через IPv6!", + "diagnosis_ip_no_ipv4": "Сервер не має працюючого IPv4.", + "diagnosis_ip_connected_ipv4": "Сервер підключений до Інтернету через IPv4!", + "diagnosis_no_cache": "Для категорії \"{категорія} 'ще немає кеша діагнозів.", + "diagnosis_failed": "Не вдалося результат діагностики для категорії '{категорія}': {error}", + "diagnosis_everything_ok": "Все виглядає добре для {категорії}!", + "diagnosis_found_warnings": "Знайдено {попередження} пунктів, які можна поліпшити для {категорії}.", + "diagnosis_found_errors_and_warnings": "Знайдено {помилки} істотний (і) питання (и) (і {попередження} попередження (я)), що відносяться до {категорії}!", + "diagnosis_found_errors": "Знайдена {помилка} важлива проблема (і), пов'язана з {категорією}!", + "diagnosis_ignored_issues": "(+ {Nb_ignored} проігнорована проблема (проблеми))", + "diagnosis_cant_run_because_of_dep": "Неможливо запустити діагностику для {категорії}, поки є важливі проблеми, пов'язані з {глибиною}.", + "diagnosis_cache_still_valid": "(Кеш все ще дійсний для діагностики {категорії}. Повторна діагностика поки не проводиться!)", + "diagnosis_failed_for_category": "Не вдалося провести діагностику для категорії '{категорія}': {error}", + "diagnosis_display_tip": "Щоб побачити знайдені проблеми, ви можете перейти в розділ Diagnosis в веб-адміном або виконати команду 'yunohost diagnosis show --issues --human-readable' з командного рядка.", + "diagnosis_package_installed_from_sury_details": "Деякі пакети були ненавмисно встановлені з стороннього сховища під назвою Sury. Команда YunoHost поліпшила стратегію роботи з цими пакетами, але очікується, що в деяких системах, які встановили додатки PHP7.3 ще на Stretch, залишаться деякі невідповідності. Щоб виправити цю ситуацію, спробуйте виконати наступну команду: {cmd_to_fix} .", + "diagnosis_package_installed_from_sury": "Деякі системні пакети повинні бути знижені в статусі", + "diagnosis_backports_in_sources_list": "Схоже, що apt (менеджер пакетів) налаштований на використання сховища backports. Якщо ви не знаєте, що робите, ми настійно не рекомендуємо встановлювати пакети з backports, тому що це може привести до нестабільності або конфліктів у вашій системі.", + "diagnosis_basesystem_ynh_inconsistent_versions": "Ви використовуєте несумісні версії пакетів YunoHost... швидше за все, через невдалий або часткового оновлення.", + "diagnosis_basesystem_ynh_main_version": "Сервер працює під управлінням YunoHost {main_version} ({repo})", + "diagnosis_basesystem_ynh_single_version": "{Пакет} версія: {версія} ({repo})", + "diagnosis_basesystem_kernel": "Сервер працює під управлінням ядра Linux {kernel_version}", + "diagnosis_basesystem_host": "Сервер працює під управлінням Debian {debian_version}", + "diagnosis_basesystem_hardware_model": "Модель сервера - {model}", + "diagnosis_basesystem_hardware": "Архітектура апаратного забезпечення сервера - {virt} {arch}", + "custom_app_url_required": "Ви повинні надати URL для оновлення вашого призначеного для користувача додатки {app}.", + "confirm_app_install_thirdparty": "НЕБЕЗПЕЧНО! Ця програма не входить в каталог додатків YunoHost. Установлення сторонніх додатків може порушити цілісність і безпеку вашої системи. Вам не слід встановлювати його, якщо ви не знаєте, що робите. НІЯКОЇ ПІДТРИМКИ НЕ БУДЕ, якщо цей додаток не буде працювати або зламає вашу систему... Якщо ви все одно готові піти на такий ризик, введіть '{answers}'.", + "confirm_app_install_danger": "НЕБЕЗПЕЧНО! Відомо, що це додаток все ще експериментальне (якщо не сказати, що воно явно не працює)! Вам не слід встановлювати його, якщо ви не знаєте, що робите. Ніякої підтримки не буде надано, якщо цей додаток не буде працювати або зламає вашу систему... Якщо ви все одно готові ризикнути, введіть '{answers}'.", + "confirm_app_install_warning": "Попередження: Ця програма може працювати, але не дуже добре інтегровано в YunoHost. Деякі функції, такі як єдина реєстрація та резервне копіювання/відновлення, можуть бути недоступні. Все одно встановити? [{Відповіді}]. ", + "certmanager_unable_to_parse_self_CA_name": "Не вдалося розібрати ім'я самоподпісивающегося центру (файл: {file})", + "certmanager_self_ca_conf_file_not_found": "Не вдалося знайти файл конфігурації для самоподпісивающегося центру (файл: {file})", + "certmanager_no_cert_file": "Не вдалося розпізнати файл сертифіката для домену {domain} (файл: {file})", + "certmanager_hit_rate_limit": "Для цього набору доменів {domain} недавно було випущено дуже багато сертифікатів. Будь ласка, спробуйте ще раз пізніше. Див. Https://letsencrypt.org/docs/rate-limits/ для отримання більш докладної інформації.", + "certmanager_warning_subdomain_dns_record": "Піддомен '{subdomain} \"не дозволяється на той же IP-адресу, що і' {domain} '. Деякі функції будуть недоступні, поки ви не виправите це і не перегенеріруете сертифікат.", + "certmanager_domain_http_not_working": "Домен {domain}, схоже, не доступний через HTTP. Будь ласка, перевірте категорію 'Web' в діагностиці для отримання додаткової інформації. (Якщо ви знаєте, що робите, використовуйте '--no-checks', щоб відключити ці перевірки).", + "certmanager_domain_dns_ip_differs_from_public_ip": "DNS-записи для домену '{domain}' відрізняються від IP цього сервера. Будь ласка, перевірте категорію 'DNS-записи' (основні) в діагностиці для отримання додаткової інформації. Якщо ви недавно змінили запис A, будь ласка, зачекайте, поки вона пошириться (деякі програми перевірки поширення DNS доступні в Інтернеті). (Якщо ви знаєте, що робите, використовуйте '--no-checks', щоб відключити ці перевірки).", + "certmanager_domain_cert_not_selfsigned": "Сертифікат для домену {domain} не є самоподпісанного. Ви впевнені, що хочете замінити його? (Для цього використовуйте '--force').", + "certmanager_domain_not_diagnosed_yet": "Поки немає результатів діагностики для домену {domain}. Будь ласка, повторно проведіть діагностику для категорій 'DNS-записи' і 'Web' в розділі діагностики, щоб перевірити, чи готовий домен до Let's Encrypt. (Або, якщо ви знаєте, що робите, використовуйте '--no-checks', щоб відключити ці перевірки).", + "certmanager_certificate_fetching_or_enabling_failed": "Спроба використовувати новий сертифікат для {domain} не спрацювала...", + "certmanager_cert_signing_failed": "Не вдалося підписати новий сертифікат", + "certmanager_cert_renew_success": "Сертифікат Let's Encrypt оновлений для домену '{domain}'", + "certmanager_cert_install_success_selfsigned": "Самоподпісанний сертифікат тепер встановлений для домену '{domain}'", + "certmanager_cert_install_success": "Сертифікат Let's Encrypt тепер встановлений для домену '{domain}'", + "certmanager_cannot_read_cert": "Щось не так сталося при спробі відкрити поточний сертифікат для домену {domain} (файл: {файл}), причина: {причина}", + "certmanager_attempt_to_replace_valid_cert": "Ви намагаєтеся перезаписати хороший і дійсний сертифікат для домену {domain}! (Використовуйте --force для обходу)", + "certmanager_attempt_to_renew_valid_cert": "Термін дії сертифіката для домену '{domain} \"не закінчується! (Ви можете використовувати --force, якщо знаєте, що робите)", + "certmanager_attempt_to_renew_nonLE_cert": "Сертифікат для домену '{domain}' не випущено Let's Encrypt. Неможливо продовжити його автоматично!", + "certmanager_acme_not_configured_for_domain": "Завдання ACME не може бути запущена для {domain} прямо зараз, тому що в його nginx conf відсутній відповідний фрагмент коду... Будь ласка, переконайтеся, що конфігурація nginx оновлена за допомогою `yunohost tools regen-conf nginx --dry-run - with-diff`.", + "backup_with_no_restore_script_for_app": "{App} не має скрипта відновлення, ви не зможете автоматично відновити резервну копію цього додатка.", + "backup_with_no_backup_script_for_app": "Додаток '{app}' не має скрипта резервного копіювання. Ігнорування.", + "backup_unable_to_organize_files": "Неможливо використовувати швидкий метод для організації файлів в архіві", + "backup_system_part_failed": "Не вдалося створити резервну копію системної частини '{part}'.", + "backup_running_hooks": "Запуск гачків резервного копіювання...", + "backup_permission": "Дозвіл на резервне копіювання для {app}", + "backup_output_symlink_dir_broken": "Ваш архівний каталог '{path}' є непрацюючою симлінк. Можливо, ви забули перемонтувати або підключити носій, на який вона вказує.", + "backup_output_directory_required": "Ви повинні вказати вихідний каталог для резервного копіювання", + "backup_output_directory_not_empty": "Ви повинні вибрати порожній вихідний каталог", + "backup_output_directory_forbidden": "Виберіть інший вихідний каталог. Резервні копії не можуть бути створені в підкаталогах/bin,/boot,/dev,/etc,/lib,/root,/run,/sbin,/sys,/usr,/var або /home/yunohost.backup/archives.", + "backup_nothings_done": "нічого зберігати", + "backup_no_uncompress_archive_dir": "Немає такого каталогу нестислого архіву", + "backup_mount_archive_for_restore": "Підготовка архіву для відновлення...", + "backup_method_tar_finished": "Створено архів резервного копіювання TAR", + "backup_method_custom_finished": "Призначений для користувача метод резервного копіювання '{метод}' завершено", + "backup_method_copy_finished": "Резервне копіювання завершено", + "backup_hook_unknown": "Гачок резервного копіювання '{hook}' невідомий", + "backup_deleted": "Резервна копія видалена", + "backup_delete_error": "Не вдалося видалити '{path}'", + "backup_custom_mount_error": "Призначений для користувача метод резервного копіювання не зміг пройти етап 'монтування'", + "backup_custom_backup_error": "Призначений для користувача метод резервного копіювання не зміг пройти етап 'резервне копіювання'", + "backup_csv_creation_failed": "Не вдалося створити CSV-файл, необхідний для відновлення", + "backup_csv_addition_failed": "Не вдалося додати файли для резервного копіювання в CSV-файл", + "backup_creation_failed": "Не вдалося створити архів резервного копіювання", + "backup_create_size_estimation": "Архів буде містити близько {розмір} даних.", + "backup_created": "Резервна копія створена", + "backup_couldnt_bind": "Не вдалося зв'язати {src} з {dest}.", + "backup_copying_to_organize_the_archive": "Копіювання {size} MB для організації архіву", + "backup_cleaning_failed": "Не вдалося очистити тимчасову папку резервного копіювання", + "backup_cant_mount_uncompress_archive": "Не вдалося змонтувати нестислий архів як захищений від запису", + "backup_ask_for_copying_if_needed": "Чи хочете ви тимчасово виконати резервне копіювання з використанням {size} MB? (Цей спосіб використовується, оскільки деякі файли не можуть бути підготовлені більш ефективним методом).", + "backup_archive_writing_error": "Не вдалося додати файли '{source}' (названі в архіві '{dest}') для резервного копіювання в стислий архів '{archive}'.", + "backup_archive_system_part_not_available": "Системна частина '{part}' недоступна в цій резервної копії", + "backup_archive_corrupted": "Схоже, що архів резервної копії \"{archive} 'пошкоджений: {error}", + "backup_archive_cant_retrieve_info_json": "Не вдалося завантажити інформацію для архіву '{archive}'... info.json не може бути отриманий (або не є коректним json).", + "backup_archive_open_failed": "Не вдалося відкрити архів резервних копій", + "backup_archive_name_unknown": "Невідомий локальний архів резервного копіювання з ім'ям '{name}'", + "backup_archive_name_exists": "Архів резервного копіювання з таким ім'ям вже існує.", + "backup_archive_broken_link": "Не вдалося отримати доступ до архіву резервного копіювання (непрацююча посилання на {path})", + "backup_archive_app_not_found": "Не вдалося знайти {app} в архіві резервного копіювання", + "backup_applying_method_tar": "Створення резервного TAR-архіву...", + "backup_applying_method_custom": "Виклик для користувача методу резервного копіювання '{метод}'...", + "backup_applying_method_copy": "Копіювання всіх файлів в резервну копію...", + "backup_app_failed": "Не вдалося створити резервну копію {app}", + "backup_actually_backuping": "Створення резервного архіву з зібраних файлів...", + "backup_abstract_method": "Цей метод резервного копіювання ще не реалізований", + "ask_password": "пароль", + "ask_new_path": "Новий шлях", + "ask_new_domain": "новий домен", + "ask_new_admin_password": "Новий адміністративний пароль", + "ask_main_domain": "основний домен", + "ask_lastname": "Прізвище", + "ask_firstname": "ім'я", + "ask_user_domain": "Домен для адреси електронної пошти користувача і облікового запису XMPP", + "apps_catalog_update_success": "Каталог додатків був оновлений!", + "apps_catalog_obsolete_cache": "Кеш каталогу додатків порожній або застарів.", + "apps_catalog_failed_to_download": "Неможливо завантажити каталог додатків {apps_catalog}: {помилка}.", + "apps_catalog_updating": "Оновлення каталогу додатків…", + "apps_catalog_init_success": "Система каталогу додатків инициализирована!", + "apps_already_up_to_date": "Всі додатки вже оновлені", + "app_packaging_format_not_supported": "Ця програма не може бути встановлено, тому що формат його упаковки не підтримується вашою версією YunoHost. Можливо, вам слід оновити вашу систему.", + "app_upgraded": "{App} оновлено", + "app_upgrade_some_app_failed": "Деякі програми не можуть бути оновлені", + "app_upgrade_script_failed": "Сталася помилка в сценарії оновлення програми", + "app_upgrade_failed": "Не вдалося оновити {app}: {error}", + "app_upgrade_app_name": "Зараз оновлюємо {app}...", + "app_upgrade_several_apps": "Наступні додатки будуть оновлені: {apps}", + "app_unsupported_remote_type": "Для додатка використовується непідтримуваний віддалений тип.", + "app_unknown": "невідоме додаток", + "app_start_restore": "Відновлення {app}...", + "app_start_backup": "Збір файлів для резервного копіювання {app}...", + "app_start_remove": "Видалення {app}...", + "app_start_install": "Установлення {app}...", + "app_sources_fetch_failed": "Не вдалося вихідні файли, URL коректний?", + "app_restore_script_failed": "Сталася помилка всередині скрипта відновити оригінальну програму", + "app_restore_failed": "Не вдалося відновити {app}: {error}", + "app_remove_after_failed_install": "Видалення програми після збою установки...", + "app_requirements_unmeet": "Вимоги не виконані для {app}, пакет {pkgname} ({version}) повинен бути {spec}.", + "app_requirements_checking": "Перевірка необхідних пакетів для {app}...", + "app_removed": "{App} видалено", + "app_not_properly_removed": "{App} не було видалено належним чином", + "app_not_installed": "Не вдалося знайти {app} в списку встановлених додатків: {all_apps}", + "app_not_correctly_installed": "{App}, схоже, неправильно встановлено", + "app_not_upgraded": "Додаток '{failed_app}' не вдалося оновити, і, як наслідок, оновлення таких програмах було скасовано: {apps}", + "app_manifest_install_ask_is_public": "Чи повинно це додаток бути відкрито для анонімних відвідувачів?", + "app_manifest_install_ask_admin": "Виберіть користувача-адміністратора для цього додатка", + "app_manifest_install_ask_password": "Виберіть пароль адміністратора для цього додатка" } From c3fe22f3f0b334375020ef93bdf66f3909c95d35 Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Fri, 27 Aug 2021 17:19:45 +0000 Subject: [PATCH 2809/3170] [CI] Remove stale translated strings --- locales/fr.json | 2 +- locales/gl.json | 2 +- locales/it.json | 2 +- locales/pt.json | 2 +- locales/uk.json | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 7719e1390..1cac6bbba 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -638,4 +638,4 @@ "global_settings_setting_security_webadmin_allowlist_enabled": "Autorisez seulement certaines IP à accéder à la page web du portail d'administration (webadmin).", "diagnosis_http_localdomain": "Le domaine {domain}, avec un TLD .local, ne devrait pas être atteint depuis l'extérieur du réseau local.", "diagnosis_dns_specialusedomain": "Le domaine {domain} est basé sur un domaine de premier niveau (TLD) à usage spécial et ne devrait donc pas avoir d'enregistrements DNS réels." -} +} \ No newline at end of file diff --git a/locales/gl.json b/locales/gl.json index 5c0bec604..c38d13ad1 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -523,4 +523,4 @@ "permission_deleted": "O permiso '{permission}' foi eliminado", "permission_cant_add_to_all_users": "O permiso {permission} non pode ser concecido a tódalas usuarias.", "permission_currently_allowed_for_all_users": "Este permiso está concedido actualmente a tódalas usuarias ademáis de a outros grupos. Probablemente queiras ben eliminar o permiso 'all_users' ou ben eliminar os outros grupos que teñen permiso." -} +} \ No newline at end of file diff --git a/locales/it.json b/locales/it.json index a9fb6f9c8..c38bae59d 100644 --- a/locales/it.json +++ b/locales/it.json @@ -635,4 +635,4 @@ "global_settings_setting_security_webadmin_allowlist_enabled": "Permetti solo ad alcuni IP di accedere al webadmin.", "disk_space_not_sufficient_update": "Non c'è abbastanza spazio libero per aggiornare questa applicazione", "disk_space_not_sufficient_install": "Non c'è abbastanza spazio libero per installare questa applicazione" -} +} \ No newline at end of file diff --git a/locales/pt.json b/locales/pt.json index ef0c41349..cc47da946 100644 --- a/locales/pt.json +++ b/locales/pt.json @@ -141,4 +141,4 @@ "app_install_failed": "Não foi possível instalar {app}: {error}", "app_full_domain_unavailable": "Desculpe, esse app deve ser instalado num domínio próprio mas já há outros apps instalados no domínio '{domain}'. Você pode usar um subdomínio dedicado a esse aplicativo.", "app_change_url_success": "A URL agora é {domain}{path}" -} +} \ No newline at end of file diff --git a/locales/uk.json b/locales/uk.json index c85b4fd0a..d51ec706c 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -33,4 +33,4 @@ "action_invalid": "Неприпустима дія '{action}'", "aborting": "Переривання.", "diagnosis_description_web": "Мережа" -} +} \ No newline at end of file From 712e2bb1c933550cb7f97d0afc79d779852094ab Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Fri, 27 Aug 2021 18:07:37 +0000 Subject: [PATCH 2810/3170] [CI] Format code --- src/yunohost/authenticators/ldap_admin.py | 6 ++- src/yunohost/tests/conftest.py | 3 +- .../tests/test_apps_arguments_parsing.py | 44 ++++++++++++++----- src/yunohost/tests/test_ldapauth.py | 1 + src/yunohost/utils/ldap.py | 5 +-- 5 files changed, 43 insertions(+), 16 deletions(-) diff --git a/src/yunohost/authenticators/ldap_admin.py b/src/yunohost/authenticators/ldap_admin.py index dd6eec03e..94d68a8db 100644 --- a/src/yunohost/authenticators/ldap_admin.py +++ b/src/yunohost/authenticators/ldap_admin.py @@ -12,6 +12,7 @@ from yunohost.utils.error import YunohostError logger = logging.getLogger("yunohost.authenticators.ldap_admin") + class Authenticator(BaseAuthenticator): name = "ldap_admin" @@ -57,7 +58,10 @@ class Authenticator(BaseAuthenticator): raise else: if who != self.admindn: - raise YunohostError(f"Not logged with the appropriate identity ? Found {who}, expected {self.admindn} !?", raw_msg=True) + raise YunohostError( + f"Not logged with the appropriate identity ? Found {who}, expected {self.admindn} !?", + raw_msg=True, + ) finally: # Free the connection, we don't really need it to keep it open as the point is only to check authentication... if con: diff --git a/src/yunohost/tests/conftest.py b/src/yunohost/tests/conftest.py index 9dfe2b39c..6b4e2c3fd 100644 --- a/src/yunohost/tests/conftest.py +++ b/src/yunohost/tests/conftest.py @@ -81,7 +81,8 @@ def pytest_cmdline_main(config): import yunohost yunohost.init(debug=config.option.yunodebug) - class DummyInterface(): + + class DummyInterface: type = "test" diff --git a/src/yunohost/tests/test_apps_arguments_parsing.py b/src/yunohost/tests/test_apps_arguments_parsing.py index 573c18cb2..fe5c5f8cd 100644 --- a/src/yunohost/tests/test_apps_arguments_parsing.py +++ b/src/yunohost/tests/test_apps_arguments_parsing.py @@ -180,7 +180,9 @@ def test_parse_args_in_yunohost_format_string_input_test_ask(): ] answers = {} - with patch.object(Moulinette.interface, "prompt", return_value="some_value") as prompt: + with patch.object( + Moulinette.interface, "prompt", return_value="some_value" + ) as prompt: _parse_args_in_yunohost_format(answers, questions) prompt.assert_called_with(ask_text, False) @@ -197,7 +199,9 @@ def test_parse_args_in_yunohost_format_string_input_test_ask_with_default(): ] answers = {} - with patch.object(Moulinette.interface, "prompt", return_value="some_value") as prompt: + with patch.object( + Moulinette.interface, "prompt", return_value="some_value" + ) as prompt: _parse_args_in_yunohost_format(answers, questions) prompt.assert_called_with("%s (default: %s)" % (ask_text, default_text), False) @@ -215,7 +219,9 @@ def test_parse_args_in_yunohost_format_string_input_test_ask_with_example(): ] answers = {} - with patch.object(Moulinette.interface, "prompt", return_value="some_value") as prompt: + with patch.object( + Moulinette.interface, "prompt", return_value="some_value" + ) as prompt: _parse_args_in_yunohost_format(answers, questions) assert ask_text in prompt.call_args[0][0] assert example_text in prompt.call_args[0][0] @@ -234,7 +240,9 @@ def test_parse_args_in_yunohost_format_string_input_test_ask_with_help(): ] answers = {} - with patch.object(Moulinette.interface, "prompt", return_value="some_value") as prompt: + with patch.object( + Moulinette.interface, "prompt", return_value="some_value" + ) as prompt: _parse_args_in_yunohost_format(answers, questions) assert ask_text in prompt.call_args[0][0] assert help_text in prompt.call_args[0][0] @@ -462,7 +470,9 @@ def test_parse_args_in_yunohost_format_password_input_test_ask(): ] answers = {} - with patch.object(Moulinette.interface, "prompt", return_value="some_value") as prompt: + with patch.object( + Moulinette.interface, "prompt", return_value="some_value" + ) as prompt: _parse_args_in_yunohost_format(answers, questions) prompt.assert_called_with(ask_text, True) @@ -481,7 +491,9 @@ def test_parse_args_in_yunohost_format_password_input_test_ask_with_example(): ] answers = {} - with patch.object(Moulinette.interface, "prompt", return_value="some_value") as prompt: + with patch.object( + Moulinette.interface, "prompt", return_value="some_value" + ) as prompt: _parse_args_in_yunohost_format(answers, questions) assert ask_text in prompt.call_args[0][0] assert example_text in prompt.call_args[0][0] @@ -501,7 +513,9 @@ def test_parse_args_in_yunohost_format_password_input_test_ask_with_help(): ] answers = {} - with patch.object(Moulinette.interface, "prompt", return_value="some_value") as prompt: + with patch.object( + Moulinette.interface, "prompt", return_value="some_value" + ) as prompt: _parse_args_in_yunohost_format(answers, questions) assert ask_text in prompt.call_args[0][0] assert help_text in prompt.call_args[0][0] @@ -697,7 +711,9 @@ def test_parse_args_in_yunohost_format_path_input_test_ask(): ] answers = {} - with patch.object(Moulinette.interface, "prompt", return_value="some_value") as prompt: + with patch.object( + Moulinette.interface, "prompt", return_value="some_value" + ) as prompt: _parse_args_in_yunohost_format(answers, questions) prompt.assert_called_with(ask_text, False) @@ -715,7 +731,9 @@ def test_parse_args_in_yunohost_format_path_input_test_ask_with_default(): ] answers = {} - with patch.object(Moulinette.interface, "prompt", return_value="some_value") as prompt: + with patch.object( + Moulinette.interface, "prompt", return_value="some_value" + ) as prompt: _parse_args_in_yunohost_format(answers, questions) prompt.assert_called_with("%s (default: %s)" % (ask_text, default_text), False) @@ -734,7 +752,9 @@ def test_parse_args_in_yunohost_format_path_input_test_ask_with_example(): ] answers = {} - with patch.object(Moulinette.interface, "prompt", return_value="some_value") as prompt: + with patch.object( + Moulinette.interface, "prompt", return_value="some_value" + ) as prompt: _parse_args_in_yunohost_format(answers, questions) assert ask_text in prompt.call_args[0][0] assert example_text in prompt.call_args[0][0] @@ -754,7 +774,9 @@ def test_parse_args_in_yunohost_format_path_input_test_ask_with_help(): ] answers = {} - with patch.object(Moulinette.interface, "prompt", return_value="some_value") as prompt: + with patch.object( + Moulinette.interface, "prompt", return_value="some_value" + ) as prompt: _parse_args_in_yunohost_format(answers, questions) assert ask_text in prompt.call_args[0][0] assert help_text in prompt.call_args[0][0] diff --git a/src/yunohost/tests/test_ldapauth.py b/src/yunohost/tests/test_ldapauth.py index 7560608f5..3a4e0dbb6 100644 --- a/src/yunohost/tests/test_ldapauth.py +++ b/src/yunohost/tests/test_ldapauth.py @@ -7,6 +7,7 @@ from yunohost.tools import tools_adminpw from moulinette import m18n from moulinette.core import MoulinetteError + def setup_function(function): if os.system("systemctl is-active slapd") != 0: diff --git a/src/yunohost/utils/ldap.py b/src/yunohost/utils/ldap.py index 1298eff69..4f571ce6f 100644 --- a/src/yunohost/utils/ldap.py +++ b/src/yunohost/utils/ldap.py @@ -56,7 +56,7 @@ def _get_ldap_interface(): def _ldap_path_extract(path, info): for element in path.split(","): if element.startswith(info + "="): - return element[len(info + "="):] + return element[len(info + "=") :] # Add this to properly close / delete the ldap interface / authenticator @@ -72,8 +72,7 @@ def _destroy_ldap_interface(): atexit.register(_destroy_ldap_interface) -class LDAPInterface(): - +class LDAPInterface: def __init__(self): logger.debug("initializing ldap interface") From 383aab55a66b3e94963f775e4e29134d22bbe25c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 28 Aug 2021 02:03:33 +0200 Subject: [PATCH 2811/3170] Fix tests --- locales/gl.json | 4 ++-- src/yunohost/tests/test_ldapauth.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/locales/gl.json b/locales/gl.json index c38d13ad1..5ccdad0c2 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -449,7 +449,7 @@ "migration_0019_slapd_config_will_be_overwritten": "Semella que editaches manualmente a configuración slapd. Para esta migración crítica YunoHost precisa forzar a actualización da configuración slapd. Os ficheiros orixinais van ser copiados en {conf_backup_folder}.", "migration_0019_add_new_attributes_in_ldap": "Engadir novos atributos para os permisos na base de datos LDAP", "migration_0018_failed_to_reset_legacy_rules": "Fallou o restablecemento das regras antigas de iptables: {error}", - "migration_0018_failed_to_migrate_iptables_rules": "Fallou a migración das regras antigas de iptables a nftables: {erro}", + "migration_0018_failed_to_migrate_iptables_rules": "Fallou a migración das regras antigas de iptables a nftables: {error}", "migration_0017_not_enough_space": "Crea espazo suficiente en {path} para executar a migración.", "migration_0017_postgresql_11_not_installed": "PostgreSQL 9.6 está instado, pero non postgresql 11? Algo raro debeu acontecer no teu sistema :(...", "migration_0017_postgresql_96_not_installed": "PostgreSQL non está instalado no teu sistema. Nada que facer.", @@ -523,4 +523,4 @@ "permission_deleted": "O permiso '{permission}' foi eliminado", "permission_cant_add_to_all_users": "O permiso {permission} non pode ser concecido a tódalas usuarias.", "permission_currently_allowed_for_all_users": "Este permiso está concedido actualmente a tódalas usuarias ademáis de a outros grupos. Probablemente queiras ben eliminar o permiso 'all_users' ou ben eliminar os outros grupos que teñen permiso." -} \ No newline at end of file +} diff --git a/src/yunohost/tests/test_ldapauth.py b/src/yunohost/tests/test_ldapauth.py index 3a4e0dbb6..a95dea443 100644 --- a/src/yunohost/tests/test_ldapauth.py +++ b/src/yunohost/tests/test_ldapauth.py @@ -24,7 +24,7 @@ def test_authenticate_with_wrong_password(): with pytest.raises(MoulinetteError) as exception: LDAPAuth().authenticate_credentials(credentials="bad_password_lul") - translation = m18n.g("invalid_password") + translation = m18n.n("invalid_password") expected_msg = translation.format() assert expected_msg in str(exception) @@ -52,7 +52,7 @@ def test_authenticate_change_password(): with pytest.raises(MoulinetteError) as exception: LDAPAuth().authenticate_credentials(credentials="yunohost") - translation = m18n.g("invalid_password") + translation = m18n.n("invalid_password") expected_msg = translation.format() assert expected_msg in str(exception) From ea76895fdfaa0006c5c6f6d8f67ce462cf5be368 Mon Sep 17 00:00:00 2001 From: Gregor Lenz Date: Sat, 28 Aug 2021 03:09:20 +0300 Subject: [PATCH 2812/3170] remove offensive language in package ban config (#1226) * remove offensive language in package ban config I understand that some users do silly things with their system configuration and this ban is supposed to prevent that. I just improved the wording for any user who like me stumbles across this file on their system... * [enh] Imrpove warnings * fix spelling mistakes Co-authored-by: ljf (zamentur) --- data/hooks/conf_regen/10-apt | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/data/hooks/conf_regen/10-apt b/data/hooks/conf_regen/10-apt index bb5caf67f..d2977ee92 100755 --- a/data/hooks/conf_regen/10-apt +++ b/data/hooks/conf_regen/10-apt @@ -17,19 +17,16 @@ Pin-Priority: -1" >> "${pending_dir}/etc/apt/preferences.d/extra_php_version" done echo " -# Yes ! -# This is what's preventing you from installing apache2 ! -# -# Maybe take two fucking minutes to realize that if you try to install -# apache2, this will break nginx and break the entire YunoHost ecosystem. -# on your server. -# -# So, *NO* -# DO NOT do this. -# DO NOT remove these lines. -# -# I warned you. I WARNED YOU! But did you listen to me? -# Oooooh, noooo. You knew it all, didn't you? + +# PLEASE READ THIS WARNING AND DON'T EDIT THIS FILE + +# You are probably reading this file because you tried to install apache2 or +# bind9. These 2 packages conflict with YunoHost. + +# Installing apache2 will break nginx and break the entire YunoHost ecosystem +# on your server, therefore don't remove those lines! + +# You have been warned. Package: apache2 Pin: release * @@ -39,9 +36,9 @@ Package: apache2-bin Pin: release * Pin-Priority: -1 -# Also yes, bind9 will conflict with dnsmasq. -# Same story than for apache2. -# Don't fucking install it. +# Also bind9 will conflict with dnsmasq. +# Same story as for apache2. +# Don't install it, don't remove those lines. Package: bind9 Pin: release * From d0cd3437ba6f1d47a388817398aab7e1a368f3a7 Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Sat, 28 Aug 2021 00:45:56 +0000 Subject: [PATCH 2813/3170] [CI] Format code --- data/hooks/diagnosis/80-apps.py | 34 ++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/data/hooks/diagnosis/80-apps.py b/data/hooks/diagnosis/80-apps.py index 4ab5a6c0d..177ec590f 100644 --- a/data/hooks/diagnosis/80-apps.py +++ b/data/hooks/diagnosis/80-apps.py @@ -6,6 +6,7 @@ from yunohost.app import app_list from yunohost.diagnosis import Diagnoser + class AppDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] @@ -30,13 +31,17 @@ class AppDiagnoser(Diagnoser): if not app["issues"]: continue - level = "ERROR" if any(issue[0] == "error" for issue in app["issues"]) else "WARNING" + level = ( + "ERROR" + if any(issue[0] == "error" for issue in app["issues"]) + else "WARNING" + ) yield dict( meta={"test": "apps", "app": app["name"]}, status=level, summary="diagnosis_apps_issue", - details=[issue[1] for issue in app["issues"]] + details=[issue[1] for issue in app["issues"]], ) def issues(self, app): @@ -45,14 +50,19 @@ class AppDiagnoser(Diagnoser): if not app.get("from_catalog") or app["from_catalog"].get("state") != "working": yield ("error", "diagnosis_apps_not_in_app_catalog") - elif not isinstance(app["from_catalog"].get("level"), int) or app["from_catalog"]["level"] == 0: + elif ( + not isinstance(app["from_catalog"].get("level"), int) + or app["from_catalog"]["level"] == 0 + ): yield ("error", "diagnosis_apps_broken") elif app["from_catalog"]["level"] <= 4: yield ("warning", "diagnosis_apps_bad_quality") # Check for super old, deprecated practices - yunohost_version_req = app["manifest"].get("requirements", {}).get("yunohost", "").strip(">= ") + yunohost_version_req = ( + app["manifest"].get("requirements", {}).get("yunohost", "").strip(">= ") + ) if yunohost_version_req.startswith("2."): yield ("error", "diagnosis_apps_outdated_ynh_requirement") @@ -64,11 +74,21 @@ class AppDiagnoser(Diagnoser): "yunohost tools port-available", ] for deprecated_helper in deprecated_helpers: - if os.system(f"grep -nr -q '{deprecated_helper}' {app['setting_path']}/scripts/") == 0: + if ( + os.system( + f"grep -nr -q '{deprecated_helper}' {app['setting_path']}/scripts/" + ) + == 0 + ): yield ("error", "diagnosis_apps_deprecated_practices") - old_arg_regex = r'^domain=\${?[0-9]' - if os.system(f"grep -q '{old_arg_regex}' {app['setting_path']}/scripts/install") == 0: + old_arg_regex = r"^domain=\${?[0-9]" + if ( + os.system( + f"grep -q '{old_arg_regex}' {app['setting_path']}/scripts/install" + ) + == 0 + ): yield ("error", "diagnosis_apps_deprecated_practices") From 808a69ca64611eb1b63cd5b2fae39febeadd33ec Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 28 Aug 2021 17:58:58 +0200 Subject: [PATCH 2814/3170] domain remove: Improve file/folder cleanup code --- src/yunohost/domain.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 533d0d7a5..990f5ce53 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -281,13 +281,14 @@ def domain_remove(operation_logger, domain, remove_apps=False, force=False): except Exception as e: raise YunohostError("domain_deletion_failed", domain=domain, error=e) - os.system("rm -rf /etc/yunohost/certs/%s" % domain) + stuff_to_delete = [ + f"/etc/yunohost/certs/{domain}", + f"/etc/yunohost/dyndns/K{domain}.+*", + f"{DOMAIN_SETTINGS_DIR}/{domain}.yml", + ] - # Delete dyndns keys for this domain (if any) - os.system("rm -rf /etc/yunohost/dyndns/K%s.+*" % domain) - - # Delete settings file for this domain - os.system(f"rm -rf {DOMAIN_SETTINGS_DIR}/{domain}.yml") + for stuff in stuff_to_delete: + os.system("rm -rf {stuff}") # Sometime we have weird issues with the regenconf where some files # appears as manually modified even though they weren't touched ... @@ -860,7 +861,7 @@ def domain_setting(domain, key, value=None, delete=False): if value < 0: raise YunohostError("pattern_positive_number", value_type=type(value)) - + # Set new value domain_settings[key] = value # Save settings @@ -932,18 +933,18 @@ def domain_registrar_info(domain): if not registrar_info: # TODO add locales raise YunohostError("registrar_is_not_set", dns_zone=dns_zone) - + logger.info("Registrar name: " + registrar_info['name']) for option_key, option_value in registrar_info['options'].items(): logger.info("Option " + option_key + ": " + option_value) def _print_registrar_info(registrar_name, full, options): logger.info("Registrar : " + registrar_name) - if full : + if full : logger.info("Options : ") for option in options: logger.info("\t- " + option) - + def domain_registrar_catalog(registrar_name, full): registrars = yaml.load(open(REGISTRAR_LIST_PATH, "r+")) From 5f338d3c547b4aaaadd0d2556cdd3d0d8d3438dd Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 28 Aug 2021 18:01:29 +0200 Subject: [PATCH 2815/3170] Semantics --- src/yunohost/domain.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 990f5ce53..24a19af55 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -496,7 +496,7 @@ def _build_dns_conf(domain): } """ - domains = _get_domain_settings(domain, True) + domains = _get_domain_settings(domain, include_subdomains=True) basic = [] mail = [] @@ -872,29 +872,29 @@ def _is_subdomain_of(subdomain, domain): return True if re.search("(^|\\.)" + domain + "$", subdomain) else False -def _get_domain_settings(domain, subdomains): +def _get_domain_settings(domain, include_subdomains=False): """ Get settings of a domain Keyword arguments: domain -- The domain name - subdomains -- Do we include the subdomains? Default is False + include_subdomains -- Do we include the subdomains? Default is False """ domains = _load_domain_settings() - if not domain in domains.keys(): + if domain not in domains.keys(): raise YunohostError("domain_name_unknown", domain=domain) - only_wanted_domains = dict() + out = dict() for entry in domains.keys(): - if subdomains: + if include_subdomains: if _is_subdomain_of(entry, domain): - only_wanted_domains[entry] = domains[entry] + out[entry] = domains[entry] else: if domain == entry: - only_wanted_domains[entry] = domains[entry] + out[entry] = domains[entry] - return only_wanted_domains + return out def _set_domain_settings(domain, domain_settings): From 13f2cabc46119a16a7d3bffa0e35ab68afaf277c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 28 Aug 2021 18:10:47 +0200 Subject: [PATCH 2816/3170] Use moulinette helpers to read/write yaml --- src/yunohost/domain.py | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 24a19af55..e21966697 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -34,9 +34,8 @@ from lexicon.config import ConfigResolver from moulinette import m18n, Moulinette from moulinette.core import MoulinetteError -from yunohost.utils.error import YunohostError, YunohostValidationError from moulinette.utils.log import getActionLogger -from moulinette.utils.filesystem import write_to_file +from moulinette.utils.filesystem import write_to_file, read_yaml, write_to_yaml from yunohost.app import ( app_ssowatconf, @@ -46,6 +45,7 @@ from yunohost.app import ( _parse_args_in_yunohost_format, ) from yunohost.regenconf import regen_conf, _force_clear_hashes, _process_regen_conf +from yunohost.utils.error import YunohostError, YunohostValidationError from yunohost.utils.network import get_public_ip from yunohost.utils.dns import get_dns_zone_from_domain from yunohost.log import is_unit_operation @@ -775,10 +775,7 @@ def _load_domain_settings(domains=[]): filepath = f"{DOMAIN_SETTINGS_DIR}/{domain}.yml" on_disk_settings = {} if os.path.exists(filepath) and os.path.isfile(filepath): - on_disk_settings = yaml.load(open(filepath, "r+")) - # If the file is empty or "corrupted" - if not type(on_disk_settings) is dict: - on_disk_settings = {} + on_disk_settings = read_yaml(filepath) or {} # Generate defaults is_maindomain = domain == maindomain dns_zone = get_dns_zone_from_domain(domain) @@ -805,10 +802,7 @@ def _load_registrar_setting(dns_zone): on_disk_settings = {} filepath = f"{REGISTRAR_SETTINGS_DIR}/{dns_zone}.yml" if os.path.exists(filepath) and os.path.isfile(filepath): - on_disk_settings = yaml.load(open(filepath, "r+")) - # If the file is empty or "corrupted" - if not type(on_disk_settings) is dict: - on_disk_settings = {} + on_disk_settings = read_yaml(filepath) or {} return on_disk_settings @@ -914,8 +908,7 @@ def _set_domain_settings(domain, domain_settings): os.mkdir(DOMAIN_SETTINGS_DIR) # Save the settings to the .yaml file filepath = f"{DOMAIN_SETTINGS_DIR}/{domain}.yml" - with open(filepath, "w") as file: - yaml.dump(domain_settings, file, default_flow_style=False) + write_to_yaml(filepath, domain_settings) def _load_zone_of_domain(domain): @@ -938,6 +931,7 @@ def domain_registrar_info(domain): for option_key, option_value in registrar_info['options'].items(): logger.info("Option " + option_key + ": " + option_value) + def _print_registrar_info(registrar_name, full, options): logger.info("Registrar : " + registrar_name) if full : @@ -945,8 +939,9 @@ def _print_registrar_info(registrar_name, full, options): for option in options: logger.info("\t- " + option) + def domain_registrar_catalog(registrar_name, full): - registrars = yaml.load(open(REGISTRAR_LIST_PATH, "r+")) + registrars = read_yaml(REGISTRAR_LIST_PATH) if registrar_name and registrar_name in registrars.keys() : _print_registrar_info(registrar_name, True, registrars[registrar_name]) @@ -990,8 +985,7 @@ def domain_registrar_set(domain, registrar, args): os.mkdir(REGISTRAR_SETTINGS_DIR) # Save the settings to the .yaml file filepath = f"{REGISTRAR_SETTINGS_DIR}/{dns_zone}.yml" - with open(filepath, "w") as file: - yaml.dump(domain_registrar, file, default_flow_style=False) + write_to_yaml(filepath, domain_registrar) def domain_push_config(domain): From 9f4ca2e8196871fbfc46d282939b69641042fd78 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 28 Aug 2021 18:11:04 +0200 Subject: [PATCH 2817/3170] Misc cleanup --- src/yunohost/domain.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index e21966697..b5a3273cf 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -750,25 +750,21 @@ def _load_domain_settings(domains=[]): """ # Retrieve actual domain list get_domain_list = domain_list() + all_known_domains = get_domain_list["domains"] + maindomain = get_domain_list["main"] if domains: - # check existence of requested domains - all_known_domains = get_domain_list["domains"] # filter inexisting domains - unknown_domains = filter(lambda domain : not domain in all_known_domains, domains) + unknown_domains = filter(lambda domain: domain not in all_known_domains, domains) # get first unknown domain unknown_domain = next(unknown_domains, None) - if unknown_domain != None: + if unknown_domain is None: raise YunohostValidationError("domain_name_unknown", domain=unknown_domain) else: - domains = get_domain_list["domains"] - + domains = all_known_domains # Create sanitized data - new_domains = dict() - - # Load main domain - maindomain = get_domain_list["main"] + out = dict() for domain in domains: # Retrieve entries in the YAML @@ -789,9 +785,9 @@ def _load_domain_settings(domains=[]): # Update each setting if not present default_settings.update(on_disk_settings) # Add the domain to the list - new_domains[domain] = default_settings + out[domain] = default_settings - return new_domains + return out def _load_registrar_setting(dns_zone): From 756e6041cb9e97de8dbb811eb7b6282477559994 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 28 Aug 2021 19:08:28 +0200 Subject: [PATCH 2818/3170] domains.py: Attempt to clarify build_dns_zone? --- src/yunohost/domain.py | 83 +++++++++++++++++++----------------------- 1 file changed, 38 insertions(+), 45 deletions(-) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index b5a3273cf..82f8861d4 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -453,7 +453,7 @@ def _get_maindomain(): return maindomain -def _build_dns_conf(domain): +def _build_dns_conf(base_domain): """ Internal function that will returns a data structure containing the needed information to generate/adapt the dns configuration @@ -496,43 +496,40 @@ def _build_dns_conf(domain): } """ - domains = _get_domain_settings(domain, include_subdomains=True) - basic = [] mail = [] xmpp = [] extra = [] ipv4 = get_public_ip() ipv6 = get_public_ip(6) - owned_dns_zone = ( - # TODO test this - "dns_zone" in domains[domain] and domains[domain]["dns_zone"] == domain - ) - root_prefix = domain.partition(".")[0] - child_domain_suffix = "" + domains_settings = _get_domain_settings(base_domain, include_subdomains=True) + base_dns_zone = domain_settings[base_domain].get("dns_zone") - for domain_name, domain in domains.items(): - ttl = domain["ttl"] + for domain, settings in domain_settings.items(): - if domain_name == domain: - name = "@" if owned_dns_zone else root_prefix - else: - name = domain_name - if not owned_dns_zone: - name += "." + root_prefix + # Domain # Base DNS zone # Basename # Suffix # + # ------------------ # ----------------- # --------- # -------- # + # domain.tld # domain.tld # @ # # + # sub.domain.tld # domain.tld # sub # .sub # + # foo.sub.domain.tld # domain.tld # foo.sub # .foo.sub # + # sub.domain.tld # sub.domain.tld # @ # # + # foo.sub.domain.tld # sub.domain.tld # foo # .foo # - if name != "@": - child_domain_suffix = "." + name + # FIXME: shouldn't the basename just be based on the dns_zone setting of this domain ? + basename = domain.replace(f"{base_dns_zone}", "").rstrip(".") or "@" + suffix = f".{basename}" if base_name != "@" else "" + + ttl = settings["ttl"] ########################### # Basic ipv4/ipv6 records # ########################### if ipv4: - basic.append([name, ttl, "A", ipv4]) + basic.append([basename, ttl, "A", ipv4]) if ipv6: - basic.append([name, ttl, "AAAA", ipv6]) + basic.append([basename, ttl, "AAAA", ipv6]) # TODO # elif include_empty_AAAA_if_no_ipv6: # basic.append(["@", ttl, "AAAA", None]) @@ -540,46 +537,42 @@ def _build_dns_conf(domain): ######### # Email # ######### - if domain["mail_in"]: - mail += [ - [name, ttl, "MX", "10 %s." % domain_name] - ] + if settings["mail_in"]: + mail.append([basename, ttl, "MX", f"10 {domain}."]) - if domain["mail_out"]: - mail += [ - [name, ttl, "TXT", '"v=spf1 a mx -all"'] - ] + if settings["mail_out"]: + mail.append([basename, ttl, "TXT", '"v=spf1 a mx -all"']) # DKIM/DMARC record - dkim_host, dkim_publickey = _get_DKIM(domain_name) + dkim_host, dkim_publickey = _get_DKIM(domain) if dkim_host: mail += [ - [dkim_host, ttl, "TXT", dkim_publickey], - [f"_dmarc{child_domain_suffix}", ttl, "TXT", '"v=DMARC1; p=none"'], + [f"{dkim_host}{suffix}", ttl, "TXT", dkim_publickey], + [f"_dmarc{suffix}", ttl, "TXT", '"v=DMARC1; p=none"'], ] ######## # XMPP # ######## - if domain["xmpp"]: + if settings["xmpp"]: xmpp += [ [ - f"_xmpp-client._tcp{child_domain_suffix}", + f"_xmpp-client._tcp{suffix}", ttl, "SRV", - f"0 5 5222 {domain_name}.", + f"0 5 5222 {domain}.", ], [ - f"_xmpp-server._tcp{child_domain_suffix}", + f"_xmpp-server._tcp{suffix}", ttl, "SRV", - f"0 5 5269 {domain_name}.", + f"0 5 5269 {domain}.", ], - ["muc" + child_domain_suffix, ttl, "CNAME", name], - ["pubsub" + child_domain_suffix, ttl, "CNAME", name], - ["vjud" + child_domain_suffix, ttl, "CNAME", name], - ["xmpp-upload" + child_domain_suffix, ttl, "CNAME", name], + [f"muc{suffix}", ttl, "CNAME", basename], + [f"pubsub{suffix}", ttl, "CNAME", basename], + [f"vjud{suffix}", ttl, "CNAME", basename], + [f"xmpp-upload{suffix}", ttl, "CNAME", basename], ] ######### @@ -587,15 +580,15 @@ def _build_dns_conf(domain): ######### if ipv4: - extra.append([f"*{child_domain_suffix}", ttl, "A", ipv4]) + extra.append([f"*{suffix}", ttl, "A", ipv4]) if ipv6: - extra.append([f"*{child_domain_suffix}", ttl, "AAAA", ipv6]) + extra.append([f"*{suffix}", ttl, "AAAA", ipv6]) # TODO # elif include_empty_AAAA_if_no_ipv6: # extra.append(["*", ttl, "AAAA", None]) - extra.append([name, ttl, "CAA", '128 issue "letsencrypt.org"']) + extra.append([basename, ttl, "CAA", '128 issue "letsencrypt.org"']) #################### # Standard records # @@ -626,7 +619,7 @@ def _build_dns_conf(domain): # Defined by custom hooks ships in apps for example ... - hook_results = hook_callback("custom_dns_rules", args=[domain]) + hook_results = hook_callback("custom_dns_rules", args=[base_domain]) for hook_name, results in hook_results.items(): # # There can be multiple results per hook name, so results look like From 2f3467dd17ade0575acc326fce886beae08891ad Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 28 Aug 2021 19:53:38 +0200 Subject: [PATCH 2819/3170] More cleanup / simplify / homogenize --- src/yunohost/domain.py | 194 +++++++++++++++-------------------------- 1 file changed, 72 insertions(+), 122 deletions(-) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 82f8861d4..582cb9bed 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -503,10 +503,13 @@ def _build_dns_conf(base_domain): ipv4 = get_public_ip() ipv6 = get_public_ip(6) - domains_settings = _get_domain_settings(base_domain, include_subdomains=True) - base_dns_zone = domain_settings[base_domain].get("dns_zone") + subdomains = _list_subdomains_of(base_domain) + domains_settings = {domain: _get_domain_settings(domain) + for domain in [base_domain] + subdomains} - for domain, settings in domain_settings.items(): + base_dns_zone = domains_settings[base_domain].get("dns_zone") + + for domain, settings in domains_settings.items(): # Domain # Base DNS zone # Basename # Suffix # # ------------------ # ----------------- # --------- # -------- # @@ -736,64 +739,39 @@ def _get_DKIM(domain): ) -def _load_domain_settings(domains=[]): +def _default_domain_settings(domain, is_main_domain): + return { + "xmpp": is_main_domain, + "mail_in": True, + "mail_out": True, + "dns_zone": get_dns_zone_from_domain(domain), + "ttl": 3600, + } + + +def _get_domain_settings(domain): """ Retrieve entries in /etc/yunohost/domains/[domain].yml - And fill the holes if any + And set default values if needed """ # Retrieve actual domain list - get_domain_list = domain_list() - all_known_domains = get_domain_list["domains"] - maindomain = get_domain_list["main"] + domain_list_ = domain_list() + known_domains = domain_list_["domains"] + maindomain = domain_list_["main"] - if domains: - # filter inexisting domains - unknown_domains = filter(lambda domain: domain not in all_known_domains, domains) - # get first unknown domain - unknown_domain = next(unknown_domains, None) - if unknown_domain is None: - raise YunohostValidationError("domain_name_unknown", domain=unknown_domain) - else: - domains = all_known_domains - - # Create sanitized data - out = dict() - - for domain in domains: - # Retrieve entries in the YAML - filepath = f"{DOMAIN_SETTINGS_DIR}/{domain}.yml" - on_disk_settings = {} - if os.path.exists(filepath) and os.path.isfile(filepath): - on_disk_settings = read_yaml(filepath) or {} - # Generate defaults - is_maindomain = domain == maindomain - dns_zone = get_dns_zone_from_domain(domain) - default_settings = { - "xmpp": is_maindomain, - "mail_in": True, - "mail_out": True, - "dns_zone": dns_zone, - "ttl": 3600, - } - # Update each setting if not present - default_settings.update(on_disk_settings) - # Add the domain to the list - out[domain] = default_settings - - return out - - -def _load_registrar_setting(dns_zone): - """ - Retrieve entries in registrars/[dns_zone].yml - """ + if domain not in known_domains: + raise YunohostValidationError("domain_name_unknown", domain=domain) + # Retrieve entries in the YAML + filepath = f"{DOMAIN_SETTINGS_DIR}/{domain}.yml" on_disk_settings = {} - filepath = f"{REGISTRAR_SETTINGS_DIR}/{dns_zone}.yml" if os.path.exists(filepath) and os.path.isfile(filepath): on_disk_settings = read_yaml(filepath) or {} - return on_disk_settings + # Inject defaults if needed (using the magic .update() ;)) + settings = _default_domain_settings(domain, domain == maindomain) + settings.update(on_disk_settings) + return settings def domain_setting(domain, key, value=None, delete=False): @@ -808,18 +786,11 @@ def domain_setting(domain, key, value=None, delete=False): """ - boolean_keys = ["mail_in", "mail_out", "xmpp"] - - domains = _load_domain_settings([ domain ]) - - if not domain in domains.keys(): - raise YunohostError("domain_name_unknown", domain=domain) - - domain_settings = domains[domain] + domain_settings = _get_domain_settings(domain) # GET if value is None and not delete: - if not key in domain_settings: + if key not in domain_settings: raise YunohostValidationError("domain_property_unknown", property=key) return domain_settings[key] @@ -832,7 +803,10 @@ def domain_setting(domain, key, value=None, delete=False): # SET else: - if key in boolean_keys: + # FIXME : in the future, implement proper setting types (+ defaults), + # maybe inspired from the global settings + + if key in ["mail_in", "mail_out", "xmpp"]: value = True if value.lower() in ['true', '1', 't', 'y', 'yes', "iloveynh"] else False if "ttl" == key: @@ -851,31 +825,17 @@ def domain_setting(domain, key, value=None, delete=False): _set_domain_settings(domain, domain_settings) -def _is_subdomain_of(subdomain, domain): - return True if re.search("(^|\\.)" + domain + "$", subdomain) else False +def _list_subdomains_of(parent_domain): + domain_list_ = domain_list()["domains"] -def _get_domain_settings(domain, include_subdomains=False): - """ - Get settings of a domain - - Keyword arguments: - domain -- The domain name - include_subdomains -- Do we include the subdomains? Default is False - - """ - domains = _load_domain_settings() - if domain not in domains.keys(): + if parent_domain not in domain_list_: raise YunohostError("domain_name_unknown", domain=domain) - out = dict() - for entry in domains.keys(): - if include_subdomains: - if _is_subdomain_of(entry, domain): - out[entry] = domains[entry] - else: - if domain == entry: - out[entry] = domains[entry] + out = [] + for domain in domain_list_: + if domain.endswith(f".{parent_domain}"): + out.append(domain) return out @@ -886,7 +846,7 @@ def _set_domain_settings(domain, domain_settings): Keyword arguments: domain -- The domain name - settings -- Dict with doamin settings + settings -- Dict with domain settings """ if domain not in domain_list()["domains"]: @@ -900,54 +860,49 @@ def _set_domain_settings(domain, domain_settings): write_to_yaml(filepath, domain_settings) -def _load_zone_of_domain(domain): - domains = _load_domain_settings([domain]) - if domain not in domains.keys(): - raise YunohostError("domain_name_unknown", domain=domain) +def _get_registrar_settings(dns_zone): + on_disk_settings = {} + filepath = f"{REGISTRAR_SETTINGS_DIR}/{dns_zone}.yml" + if os.path.exists(filepath) and os.path.isfile(filepath): + on_disk_settings = read_yaml(filepath) or {} - return domains[domain]["dns_zone"] + return on_disk_settings + + +def _set_registrar_settings(dns_zone): + if not os.path.exists(REGISTRAR_SETTINGS_DIR): + os.mkdir(REGISTRAR_SETTINGS_DIR) + filepath = f"{REGISTRAR_SETTINGS_DIR}/{dns_zone}.yml" + write_to_yaml(filepath, domain_registrar) def domain_registrar_info(domain): - dns_zone = _load_zone_of_domain(domain) - registrar_info = _load_registrar_setting(dns_zone) + dns_zone = _get_domain_settings(domain)["dns_zone"] + registrar_info = _get_registrar_settings(dns_zone) if not registrar_info: - # TODO add locales raise YunohostError("registrar_is_not_set", dns_zone=dns_zone) - logger.info("Registrar name: " + registrar_info['name']) - for option_key, option_value in registrar_info['options'].items(): - logger.info("Option " + option_key + ": " + option_value) - - -def _print_registrar_info(registrar_name, full, options): - logger.info("Registrar : " + registrar_name) - if full : - logger.info("Options : ") - for option in options: - logger.info("\t- " + option) + return registrar_info def domain_registrar_catalog(registrar_name, full): registrars = read_yaml(REGISTRAR_LIST_PATH) - if registrar_name and registrar_name in registrars.keys() : - _print_registrar_info(registrar_name, True, registrars[registrar_name]) + if registrar_name: + if registrar_name not in registrars.keys(): + raise YunohostError("domain_registrar_unknown", registrar=registrar_name) + else: + return registrars[registrar_name] else: - for registrar in registrars: - _print_registrar_info(registrar, full, registrars[registrar]) + return registrars def domain_registrar_set(domain, registrar, args): - dns_zone = _load_zone_of_domain(domain) - registrar_info = _load_registrar_setting(dns_zone) - - registrars = yaml.load(open(REGISTRAR_LIST_PATH, "r+")) - if not registrar in registrars.keys(): - # FIXME créer l'erreur - raise YunohostError("domain_registrar_unknown") + registrars = read_yaml(REGISTRAR_LIST_PATH) + if registrar not in registrars.keys(): + raise YunohostError("domain_registrar_unknown"i, registrar=registrar) parameters = registrars[registrar] ask_args = [] @@ -969,12 +924,8 @@ def domain_registrar_set(domain, registrar, args): for arg_name, arg_value_and_type in parsed_answer_dict.items(): domain_registrar["options"][arg_name] = arg_value_and_type[0] - # First create the REGISTRAR_SETTINGS_DIR if it doesn't exist - if not os.path.exists(REGISTRAR_SETTINGS_DIR): - os.mkdir(REGISTRAR_SETTINGS_DIR) - # Save the settings to the .yaml file - filepath = f"{REGISTRAR_SETTINGS_DIR}/{dns_zone}.yml" - write_to_yaml(filepath, domain_registrar) + dns_zone = _get_domain_settings(domain)["dns_zone"] + _set_registrar_settings(dns_zone, domain_registrar) def domain_push_config(domain): @@ -987,9 +938,8 @@ def domain_push_config(domain): dns_conf = _build_dns_conf(domain) - domain_settings = _load_domain_settings([ domain ]) - dns_zone = domain_settings[domain]["dns_zone"] - registrar_setting = _load_registrar_setting(dns_zone) + dns_zone = _get_domain_settings(domain)["dns_zone"] + registrar_setting = _get_registrar_settings(dns_zone) if not registrar_setting: # FIXME add locales From 22aa1f2538c1186eb466b761e12263d18b5b590c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 28 Aug 2021 20:13:58 +0200 Subject: [PATCH 2820/3170] Cleanup get_dns_zone_from_domain utils --- src/yunohost/utils/dns.py | 45 +++++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/src/yunohost/utils/dns.py b/src/yunohost/utils/dns.py index 46b294602..1d67d73d0 100644 --- a/src/yunohost/utils/dns.py +++ b/src/yunohost/utils/dns.py @@ -26,15 +26,17 @@ YNH_DYNDNS_DOMAINS = ["nohost.me", "noho.st", "ynh.fr"] def get_public_suffix(domain): """get_public_suffix("www.example.com") -> "example.com" - Return the public suffix of a domain name based + Return the public suffix of a domain name based """ # Load domain public suffixes psl = PublicSuffixList() public_suffix = psl.publicsuffix(domain) + + # FIXME: wtf is this supposed to do ? :| if public_suffix in YNH_DYNDNS_DOMAINS: - domain_prefix = domain_name[0:-(1 + len(public_suffix))] - public_suffix = domain_prefix.plit(".")[-1] + "." + public_suffix + domain_prefix = domain[0:-(1 + len(public_suffix))] + public_suffix = domain_prefix.split(".")[-1] + "." + public_suffix return public_suffix @@ -45,19 +47,30 @@ def get_dns_zone_from_domain(domain): Keyword arguments: domain -- The domain name - + """ - separator = "." - domain_subs = domain.split(separator) - for i in range(0, len(domain_subs)): - current_domain = separator.join(domain_subs) - answer = dig(current_domain, rdtype="NS", full_answers=True, resolvers="force_external") - if answer[0] == "ok" : + + # For foo.bar.baz.gni we want to scan all the parent domains + # (including the domain itself) + # foo.bar.baz.gni + # bar.baz.gni + # baz.gni + # gni + parent_list = [domain.split(".", i)[-1] + for i, _ in enumerate(domain.split("."))] + + for parent in parent_list: + + # Check if there's a NS record for that domain + answer = dig(parent, rdtype="NS", full_answers=True, resolvers="force_external") + if answer[0] == "ok": # Domain is dns_zone - return current_domain - if separator.join(domain_subs[1:]) == get_public_suffix(current_domain): - # Couldn't check if domain is dns zone, + return parent + # Otherwise, check if the parent of this parent is in the public suffix list + if parent.split(".", 1)[-1] == get_public_suffix(parent): + # Couldn't check if domain is dns zone, # FIXME : why "couldn't" ...? # returning private suffix - return current_domain - domain_subs.pop(0) - return None \ No newline at end of file + return parent + + # FIXME: returning None will probably trigger bugs when this happens, code expects a domain string + return None From 8438efa6803ee120609adbdddf98b12f40b18f1e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 28 Aug 2021 20:20:22 +0200 Subject: [PATCH 2821/3170] Move dig() to utils.dns --- data/hooks/diagnosis/12-dnsrecords.py | 3 +- data/hooks/diagnosis/24-mail.py | 2 +- src/yunohost/dyndns.py | 3 +- src/yunohost/utils/dns.py | 74 ++++++++++++++++++++++++++- src/yunohost/utils/network.py | 70 ------------------------- 5 files changed, 77 insertions(+), 75 deletions(-) diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py index 247a3bf4d..4f1f89ef7 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -8,11 +8,10 @@ from publicsuffixlist import PublicSuffixList from moulinette.utils.process import check_output -from yunohost.utils.network import dig +from yunohost.utils.dns import dig, YNH_DYNDNS_DOMAINS from yunohost.diagnosis import Diagnoser from yunohost.domain import domain_list, _build_dns_conf, _get_maindomain -YNH_DYNDNS_DOMAINS = ["nohost.me", "noho.st", "ynh.fr"] SPECIAL_USE_TLDS = ["local", "localhost", "onion", "test"] diff --git a/data/hooks/diagnosis/24-mail.py b/data/hooks/diagnosis/24-mail.py index 63f685a26..50b8dc12e 100644 --- a/data/hooks/diagnosis/24-mail.py +++ b/data/hooks/diagnosis/24-mail.py @@ -12,7 +12,7 @@ from moulinette.utils.filesystem import read_yaml from yunohost.diagnosis import Diagnoser from yunohost.domain import _get_maindomain, domain_list from yunohost.settings import settings_get -from yunohost.utils.network import dig +from yunohost.utils.dns import dig DEFAULT_DNS_BLACKLIST = "/usr/share/yunohost/other/dnsbl_list.yml" diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index c8249e439..071e93059 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -38,7 +38,8 @@ from moulinette.utils.network import download_json from yunohost.utils.error import YunohostError, YunohostValidationError from yunohost.domain import _get_maindomain, _build_dns_conf -from yunohost.utils.network import get_public_ip, dig +from yunohost.utils.network import get_public_ip +from yunohost.utils.dns import dig from yunohost.log import is_unit_operation from yunohost.regenconf import regen_conf diff --git a/src/yunohost/utils/dns.py b/src/yunohost/utils/dns.py index 1d67d73d0..ef89c35c5 100644 --- a/src/yunohost/utils/dns.py +++ b/src/yunohost/utils/dns.py @@ -18,11 +18,82 @@ along with this program; if not, see http://www.gnu.org/licenses """ +import dns.resolver from publicsuffixlist import PublicSuffixList -from yunohost.utils.network import dig +from moulinette.utils.filesystem import read_file YNH_DYNDNS_DOMAINS = ["nohost.me", "noho.st", "ynh.fr"] +# Lazy dev caching to avoid re-reading the file multiple time when calling +# dig() often during same yunohost operation +external_resolvers_ = [] + + +def external_resolvers(): + + global external_resolvers_ + + if not external_resolvers_: + resolv_dnsmasq_conf = read_file("/etc/resolv.dnsmasq.conf").split("\n") + external_resolvers_ = [ + r.split(" ")[1] for r in resolv_dnsmasq_conf if r.startswith("nameserver") + ] + # We keep only ipv4 resolvers, otherwise on IPv4-only instances, IPv6 + # will be tried anyway resulting in super-slow dig requests that'll wait + # until timeout... + external_resolvers_ = [r for r in external_resolvers_ if ":" not in r] + + return external_resolvers_ + + +def dig( + qname, rdtype="A", timeout=5, resolvers="local", edns_size=1500, full_answers=False +): + """ + Do a quick DNS request and avoid the "search" trap inside /etc/resolv.conf + """ + + # It's very important to do the request with a qname ended by . + # If we don't and the domain fail, dns resolver try a second request + # by concatenate the qname with the end of the "hostname" + if not qname.endswith("."): + qname += "." + + if resolvers == "local": + resolvers = ["127.0.0.1"] + elif resolvers == "force_external": + resolvers = external_resolvers() + else: + assert isinstance(resolvers, list) + + resolver = dns.resolver.Resolver(configure=False) + resolver.use_edns(0, 0, edns_size) + resolver.nameservers = resolvers + # resolver.timeout is used to trigger the next DNS query on resolvers list. + # In python-dns 1.16, this value is set to 2.0. However, this means that if + # the 3 first dns resolvers in list are down, we wait 6 seconds before to + # run the DNS query to a DNS resolvers up... + # In diagnosis dnsrecords, with 10 domains this means at least 12min, too long. + resolver.timeout = 1.0 + # resolver.lifetime is the timeout for resolver.query() + # By default set it to 5 seconds to allow 4 resolvers to be unreachable. + resolver.lifetime = timeout + try: + answers = resolver.query(qname, rdtype) + except ( + dns.resolver.NXDOMAIN, + dns.resolver.NoNameservers, + dns.resolver.NoAnswer, + dns.exception.Timeout, + ) as e: + return ("nok", (e.__class__.__name__, e)) + + if not full_answers: + answers = [answer.to_text() for answer in answers] + + return ("ok", answers) + + def get_public_suffix(domain): """get_public_suffix("www.example.com") -> "example.com" @@ -40,6 +111,7 @@ def get_public_suffix(domain): return public_suffix + def get_dns_zone_from_domain(domain): # TODO Check if this function is YNH_DYNDNS_DOMAINS compatible """ diff --git a/src/yunohost/utils/network.py b/src/yunohost/utils/network.py index 88ea5e5f6..4474af14f 100644 --- a/src/yunohost/utils/network.py +++ b/src/yunohost/utils/network.py @@ -22,7 +22,6 @@ import os import re import logging import time -import dns.resolver from moulinette.utils.filesystem import read_file, write_to_file from moulinette.utils.network import download_text @@ -124,75 +123,6 @@ def get_gateway(): return addr.popitem()[1] if len(addr) == 1 else None -# Lazy dev caching to avoid re-reading the file multiple time when calling -# dig() often during same yunohost operation -external_resolvers_ = [] - - -def external_resolvers(): - - global external_resolvers_ - - if not external_resolvers_: - resolv_dnsmasq_conf = read_file("/etc/resolv.dnsmasq.conf").split("\n") - external_resolvers_ = [ - r.split(" ")[1] for r in resolv_dnsmasq_conf if r.startswith("nameserver") - ] - # We keep only ipv4 resolvers, otherwise on IPv4-only instances, IPv6 - # will be tried anyway resulting in super-slow dig requests that'll wait - # until timeout... - external_resolvers_ = [r for r in external_resolvers_ if ":" not in r] - - return external_resolvers_ - - -def dig( - qname, rdtype="A", timeout=5, resolvers="local", edns_size=1500, full_answers=False -): - """ - Do a quick DNS request and avoid the "search" trap inside /etc/resolv.conf - """ - - # It's very important to do the request with a qname ended by . - # If we don't and the domain fail, dns resolver try a second request - # by concatenate the qname with the end of the "hostname" - if not qname.endswith("."): - qname += "." - - if resolvers == "local": - resolvers = ["127.0.0.1"] - elif resolvers == "force_external": - resolvers = external_resolvers() - else: - assert isinstance(resolvers, list) - - resolver = dns.resolver.Resolver(configure=False) - resolver.use_edns(0, 0, edns_size) - resolver.nameservers = resolvers - # resolver.timeout is used to trigger the next DNS query on resolvers list. - # In python-dns 1.16, this value is set to 2.0. However, this means that if - # the 3 first dns resolvers in list are down, we wait 6 seconds before to - # run the DNS query to a DNS resolvers up... - # In diagnosis dnsrecords, with 10 domains this means at least 12min, too long. - resolver.timeout = 1.0 - # resolver.lifetime is the timeout for resolver.query() - # By default set it to 5 seconds to allow 4 resolvers to be unreachable. - resolver.lifetime = timeout - try: - answers = resolver.query(qname, rdtype) - except ( - dns.resolver.NXDOMAIN, - dns.resolver.NoNameservers, - dns.resolver.NoAnswer, - dns.exception.Timeout, - ) as e: - return ("nok", (e.__class__.__name__, e)) - - if not full_answers: - answers = [answer.to_text() for answer in answers] - - return ("ok", answers) - def _extract_inet(string, skip_netmask=False, skip_loopback=True): """ Extract IP addresses (v4 and/or v6) from a string limited to one From 0ec1516f6ed125c7c5fc32cbb21c7c8f825e5869 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 28 Aug 2021 20:39:01 +0200 Subject: [PATCH 2822/3170] domains.py: Add cache for domain_list --- src/yunohost/domain.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 582cb9bed..e3e549e20 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -58,6 +58,10 @@ REGISTRAR_SETTINGS_DIR = "/etc/yunohost/registrars" REGISTRAR_LIST_PATH = "/usr/share/yunohost/other/registrar_list.yml" +# Lazy dev caching to avoid re-query ldap every time we need the domain list +domain_list_cache = {} + + def domain_list(exclude_subdomains=False): """ List domains @@ -66,6 +70,9 @@ def domain_list(exclude_subdomains=False): exclude_subdomains -- Filter out domains that are subdomains of other declared domains """ + if not exclude_subdomains and domain_list_cache: + return domain_list_cache + from yunohost.utils.ldap import _get_ldap_interface ldap = _get_ldap_interface() @@ -95,7 +102,8 @@ def domain_list(exclude_subdomains=False): result_list = sorted(result_list, key=cmp_domain) - return {"domains": result_list, "main": _get_maindomain()} + domain_list_cache = {"domains": result_list, "main": _get_maindomain()} + return domain_list_cache @is_unit_operation() @@ -164,6 +172,8 @@ def domain_add(operation_logger, domain, dyndns=False): ldap.add("virtualdomain=%s,ou=domains" % domain, attr_dict) except Exception as e: raise YunohostError("domain_creation_failed", domain=domain, error=e) + finally: + domain_list_cache = {} # Don't regen these conf if we're still in postinstall if os.path.exists("/etc/yunohost/installed"): @@ -280,6 +290,8 @@ def domain_remove(operation_logger, domain, remove_apps=False, force=False): ldap.remove("virtualdomain=" + domain + ",ou=domains") except Exception as e: raise YunohostError("domain_deletion_failed", domain=domain, error=e) + finally: + domain_list_cache = {} stuff_to_delete = [ f"/etc/yunohost/certs/{domain}", @@ -393,7 +405,7 @@ def domain_main_domain(operation_logger, new_main_domain=None): # Apply changes to ssl certs try: write_to_file("/etc/yunohost/current_host", new_main_domain) - + domain_list_cache = {} _set_hostname(new_main_domain) except Exception as e: logger.warning("%s" % e, exc_info=1) @@ -755,9 +767,8 @@ def _get_domain_settings(domain): And set default values if needed """ # Retrieve actual domain list - domain_list_ = domain_list() - known_domains = domain_list_["domains"] - maindomain = domain_list_["main"] + known_domains = domain_list()["domains"] + maindomain = domain_list()["main"] if domain not in known_domains: raise YunohostValidationError("domain_name_unknown", domain=domain) From 1eb059931d8be499e4102d0c9692a8cebfbd4041 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 28 Aug 2021 21:02:13 +0200 Subject: [PATCH 2823/3170] Savagely split the dns/registrar stuff in a new dns.py --- data/hooks/diagnosis/12-dnsrecords.py | 3 +- src/yunohost/dns.py | 563 ++++++++++++++++++++++++ src/yunohost/domain.py | 587 ++------------------------ src/yunohost/dyndns.py | 5 +- 4 files changed, 614 insertions(+), 544 deletions(-) create mode 100644 src/yunohost/dns.py diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py index 4f1f89ef7..727fd2e13 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -10,7 +10,8 @@ from moulinette.utils.process import check_output from yunohost.utils.dns import dig, YNH_DYNDNS_DOMAINS from yunohost.diagnosis import Diagnoser -from yunohost.domain import domain_list, _build_dns_conf, _get_maindomain +from yunohost.domain import domain_list, _get_maindomain +from yunohost.dns import _build_dns_conf SPECIAL_USE_TLDS = ["local", "localhost", "onion", "test"] diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py new file mode 100644 index 000000000..590d50876 --- /dev/null +++ b/src/yunohost/dns.py @@ -0,0 +1,563 @@ +# -*- coding: utf-8 -*- + +""" License + + Copyright (C) 2013 YunoHost + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program; if not, see http://www.gnu.org/licenses + +""" + +""" yunohost_domain.py + + Manage domains +""" +import os +import re + +from lexicon.client import Client +from lexicon.config import ConfigResolver + +from moulinette import m18n, Moulinette +from moulinette.utils.log import getActionLogger +from moulinette.utils.filesystem import read_yaml, write_to_yaml + +from yunohost.domain import domain_list, _get_domain_settings +from yunohost.app import _parse_args_in_yunohost_format +from yunohost.utils.error import YunohostValidationError +from yunohost.utils.network import get_public_ip +from yunohost.log import is_unit_operation +from yunohost.hook import hook_callback + +logger = getActionLogger("yunohost.domain") + +REGISTRAR_SETTINGS_DIR = "/etc/yunohost/registrars" +REGISTRAR_LIST_PATH = "/usr/share/yunohost/other/registrar_list.yml" + + +def domain_dns_conf(domain): + """ + Generate DNS configuration for a domain + + Keyword argument: + domain -- Domain name + + """ + + if domain not in domain_list()["domains"]: + raise YunohostValidationError("domain_name_unknown", domain=domain) + + dns_conf = _build_dns_conf(domain) + + result = "" + + result += "; Basic ipv4/ipv6 records" + for record in dns_conf["basic"]: + result += "\n{name} {ttl} IN {type} {value}".format(**record) + + result += "\n\n" + result += "; XMPP" + for record in dns_conf["xmpp"]: + result += "\n{name} {ttl} IN {type} {value}".format(**record) + + result += "\n\n" + result += "; Mail" + for record in dns_conf["mail"]: + result += "\n{name} {ttl} IN {type} {value}".format(**record) + result += "\n\n" + + result += "; Extra" + for record in dns_conf["extra"]: + result += "\n{name} {ttl} IN {type} {value}".format(**record) + + for name, record_list in dns_conf.items(): + if name not in ("basic", "xmpp", "mail", "extra") and record_list: + result += "\n\n" + result += "; " + name + for record in record_list: + result += "\n{name} {ttl} IN {type} {value}".format(**record) + + if Moulinette.interface.type == "cli": + # FIXME Update this to point to our "dns push" doc + logger.info(m18n.n("domain_dns_conf_is_just_a_recommendation")) + + return result + + +def _build_dns_conf(base_domain): + """ + Internal function that will returns a data structure containing the needed + information to generate/adapt the dns configuration + + Arguments: + domains -- List of a domain and its subdomains + + The returned datastructure will have the following form: + { + "basic": [ + # if ipv4 available + {"type": "A", "name": "@", "value": "123.123.123.123", "ttl": 3600}, + # if ipv6 available + {"type": "AAAA", "name": "@", "value": "valid-ipv6", "ttl": 3600}, + ], + "xmpp": [ + {"type": "SRV", "name": "_xmpp-client._tcp", "value": "0 5 5222 domain.tld.", "ttl": 3600}, + {"type": "SRV", "name": "_xmpp-server._tcp", "value": "0 5 5269 domain.tld.", "ttl": 3600}, + {"type": "CNAME", "name": "muc", "value": "@", "ttl": 3600}, + {"type": "CNAME", "name": "pubsub", "value": "@", "ttl": 3600}, + {"type": "CNAME", "name": "vjud", "value": "@", "ttl": 3600} + {"type": "CNAME", "name": "xmpp-upload", "value": "@", "ttl": 3600} + ], + "mail": [ + {"type": "MX", "name": "@", "value": "10 domain.tld.", "ttl": 3600}, + {"type": "TXT", "name": "@", "value": "\"v=spf1 a mx ip4:123.123.123.123 ipv6:valid-ipv6 -all\"", "ttl": 3600 }, + {"type": "TXT", "name": "mail._domainkey", "value": "\"v=DKIM1; k=rsa; p=some-super-long-key\"", "ttl": 3600}, + {"type": "TXT", "name": "_dmarc", "value": "\"v=DMARC1; p=none\"", "ttl": 3600} + ], + "extra": [ + # if ipv4 available + {"type": "A", "name": "*", "value": "123.123.123.123", "ttl": 3600}, + # if ipv6 available + {"type": "AAAA", "name": "*", "value": "valid-ipv6", "ttl": 3600}, + {"type": "CAA", "name": "@", "value": "128 issue \"letsencrypt.org\"", "ttl": 3600}, + ], + "example_of_a_custom_rule": [ + {"type": "SRV", "name": "_matrix", "value": "domain.tld.", "ttl": 3600} + ], + } + """ + + basic = [] + mail = [] + xmpp = [] + extra = [] + ipv4 = get_public_ip() + ipv6 = get_public_ip(6) + + subdomains = _list_subdomains_of(base_domain) + domains_settings = {domain: _get_domain_settings(domain) + for domain in [base_domain] + subdomains} + + base_dns_zone = domains_settings[base_domain].get("dns_zone") + + for domain, settings in domains_settings.items(): + + # Domain # Base DNS zone # Basename # Suffix # + # ------------------ # ----------------- # --------- # -------- # + # domain.tld # domain.tld # @ # # + # sub.domain.tld # domain.tld # sub # .sub # + # foo.sub.domain.tld # domain.tld # foo.sub # .foo.sub # + # sub.domain.tld # sub.domain.tld # @ # # + # foo.sub.domain.tld # sub.domain.tld # foo # .foo # + + # FIXME: shouldn't the basename just be based on the dns_zone setting of this domain ? + basename = domain.replace(f"{base_dns_zone}", "").rstrip(".") or "@" + suffix = f".{basename}" if base_name != "@" else "" + + ttl = settings["ttl"] + + ########################### + # Basic ipv4/ipv6 records # + ########################### + if ipv4: + basic.append([basename, ttl, "A", ipv4]) + + if ipv6: + basic.append([basename, ttl, "AAAA", ipv6]) + # TODO + # elif include_empty_AAAA_if_no_ipv6: + # basic.append(["@", ttl, "AAAA", None]) + + ######### + # Email # + ######### + if settings["mail_in"]: + mail.append([basename, ttl, "MX", f"10 {domain}."]) + + if settings["mail_out"]: + mail.append([basename, ttl, "TXT", '"v=spf1 a mx -all"']) + + # DKIM/DMARC record + dkim_host, dkim_publickey = _get_DKIM(domain) + + if dkim_host: + mail += [ + [f"{dkim_host}{suffix}", ttl, "TXT", dkim_publickey], + [f"_dmarc{suffix}", ttl, "TXT", '"v=DMARC1; p=none"'], + ] + + ######## + # XMPP # + ######## + if settings["xmpp"]: + xmpp += [ + [ + f"_xmpp-client._tcp{suffix}", + ttl, + "SRV", + f"0 5 5222 {domain}.", + ], + [ + f"_xmpp-server._tcp{suffix}", + ttl, + "SRV", + f"0 5 5269 {domain}.", + ], + [f"muc{suffix}", ttl, "CNAME", basename], + [f"pubsub{suffix}", ttl, "CNAME", basename], + [f"vjud{suffix}", ttl, "CNAME", basename], + [f"xmpp-upload{suffix}", ttl, "CNAME", basename], + ] + + ######### + # Extra # + ######### + + if ipv4: + extra.append([f"*{suffix}", ttl, "A", ipv4]) + + if ipv6: + extra.append([f"*{suffix}", ttl, "AAAA", ipv6]) + # TODO + # elif include_empty_AAAA_if_no_ipv6: + # extra.append(["*", ttl, "AAAA", None]) + + extra.append([basename, ttl, "CAA", '128 issue "letsencrypt.org"']) + + #################### + # Standard records # + #################### + + records = { + "basic": [ + {"name": name, "ttl": ttl_, "type": type_, "value": value} + for name, ttl_, type_, value in basic + ], + "xmpp": [ + {"name": name, "ttl": ttl_, "type": type_, "value": value} + for name, ttl_, type_, value in xmpp + ], + "mail": [ + {"name": name, "ttl": ttl_, "type": type_, "value": value} + for name, ttl_, type_, value in mail + ], + "extra": [ + {"name": name, "ttl": ttl_, "type": type_, "value": value} + for name, ttl_, type_, value in extra + ], + } + + ################## + # Custom records # + ################## + + # Defined by custom hooks ships in apps for example ... + + hook_results = hook_callback("custom_dns_rules", args=[base_domain]) + for hook_name, results in hook_results.items(): + # + # There can be multiple results per hook name, so results look like + # {'/some/path/to/hook1': + # { 'state': 'succeed', + # 'stdreturn': [{'type': 'SRV', + # 'name': 'stuff.foo.bar.', + # 'value': 'yoloswag', + # 'ttl': 3600}] + # }, + # '/some/path/to/hook2': + # { ... }, + # [...] + # + # Loop over the sub-results + custom_records = [ + v["stdreturn"] for v in results.values() if v and v["stdreturn"] + ] + + records[hook_name] = [] + for record_list in custom_records: + # Check that record_list is indeed a list of dict + # with the required keys + if ( + not isinstance(record_list, list) + or any(not isinstance(record, dict) for record in record_list) + or any( + key not in record + for record in record_list + for key in ["name", "ttl", "type", "value"] + ) + ): + # Display an error, mainly for app packagers trying to implement a hook + logger.warning( + "Ignored custom record from hook '%s' because the data is not a *list* of dict with keys name, ttl, type and value. Raw data : %s" + % (hook_name, record_list) + ) + continue + + records[hook_name].extend(record_list) + + return records + + +def _get_DKIM(domain): + DKIM_file = "/etc/dkim/{domain}.mail.txt".format(domain=domain) + + if not os.path.isfile(DKIM_file): + return (None, None) + + with open(DKIM_file) as f: + dkim_content = f.read() + + # Gotta manage two formats : + # + # Legacy + # ----- + # + # mail._domainkey IN TXT ( "v=DKIM1; k=rsa; " + # "p=" ) + # + # New + # ------ + # + # mail._domainkey IN TXT ( "v=DKIM1; h=sha256; k=rsa; " + # "p=" ) + + is_legacy_format = " h=sha256; " not in dkim_content + + # Legacy DKIM format + if is_legacy_format: + dkim = re.match( + ( + r"^(?P[a-z_\-\.]+)[\s]+([0-9]+[\s]+)?IN[\s]+TXT[\s]+" + r'[^"]*"v=(?P[^";]+);' + r'[\s"]*k=(?P[^";]+);' + r'[\s"]*p=(?P

[^";]+)' + ), + dkim_content, + re.M | re.S, + ) + else: + dkim = re.match( + ( + r"^(?P[a-z_\-\.]+)[\s]+([0-9]+[\s]+)?IN[\s]+TXT[\s]+" + r'[^"]*"v=(?P[^";]+);' + r'[\s"]*h=(?P[^";]+);' + r'[\s"]*k=(?P[^";]+);' + r'[\s"]*p=(?P

[^";]+)' + ), + dkim_content, + re.M | re.S, + ) + + if not dkim: + return (None, None) + + if is_legacy_format: + return ( + dkim.group("host"), + '"v={v}; k={k}; p={p}"'.format( + v=dkim.group("v"), k=dkim.group("k"), p=dkim.group("p") + ), + ) + else: + return ( + dkim.group("host"), + '"v={v}; h={h}; k={k}; p={p}"'.format( + v=dkim.group("v"), + h=dkim.group("h"), + k=dkim.group("k"), + p=dkim.group("p"), + ), + ) + + +def _get_registrar_settings(dns_zone): + on_disk_settings = {} + filepath = f"{REGISTRAR_SETTINGS_DIR}/{dns_zone}.yml" + if os.path.exists(filepath) and os.path.isfile(filepath): + on_disk_settings = read_yaml(filepath) or {} + + return on_disk_settings + + +def _set_registrar_settings(dns_zone, domain_registrar): + if not os.path.exists(REGISTRAR_SETTINGS_DIR): + os.mkdir(REGISTRAR_SETTINGS_DIR) + filepath = f"{REGISTRAR_SETTINGS_DIR}/{dns_zone}.yml" + write_to_yaml(filepath, domain_registrar) + + +def domain_registrar_info(domain): + + dns_zone = _get_domain_settings(domain)["dns_zone"] + registrar_info = _get_registrar_settings(dns_zone) + if not registrar_info: + raise YunohostValidationError("registrar_is_not_set", dns_zone=dns_zone) + + return registrar_info + + +def domain_registrar_catalog(registrar_name, full): + registrars = read_yaml(REGISTRAR_LIST_PATH) + + if registrar_name: + if registrar_name not in registrars.keys(): + raise YunohostValidationError("domain_registrar_unknown", registrar=registrar_name) + else: + return registrars[registrar_name] + else: + return registrars + + +def domain_registrar_set(domain, registrar, args): + + registrars = read_yaml(REGISTRAR_LIST_PATH) + if registrar not in registrars.keys(): + raise YunohostValidationError("domain_registrar_unknown"i, registrar=registrar) + + parameters = registrars[registrar] + ask_args = [] + for parameter in parameters: + ask_args.append( + { + "name": parameter, + "type": "string", + "example": "", + "default": "", + } + ) + args_dict = ( + {} if not args else dict(urllib.parse.parse_qsl(args, keep_blank_values=True)) + ) + parsed_answer_dict = _parse_args_in_yunohost_format(args_dict, ask_args) + + domain_registrar = {"name": registrar, "options": {}} + for arg_name, arg_value_and_type in parsed_answer_dict.items(): + domain_registrar["options"][arg_name] = arg_value_and_type[0] + + dns_zone = _get_domain_settings(domain)["dns_zone"] + _set_registrar_settings(dns_zone, domain_registrar) + + +def domain_push_config(domain): + """ + Send DNS records to the previously-configured registrar of the domain. + """ + # Generate the records + if domain not in domain_list()["domains"]: + raise YunohostValidationError("domain_name_unknown", domain=domain) + + dns_conf = _build_dns_conf(domain) + + dns_zone = _get_domain_settings(domain)["dns_zone"] + registrar_setting = _get_registrar_settings(dns_zone) + + if not registrar_setting: + # FIXME add locales + raise YunohostValidationError("registrar_is_not_set", domain=domain) + + # Flatten the DNS conf + flatten_dns_conf = [] + for key in dns_conf: + list_of_records = dns_conf[key] + for record in list_of_records: + # FIXME Lexicon does not support CAA records + # See https://github.com/AnalogJ/lexicon/issues/282 and https://github.com/AnalogJ/lexicon/pull/371 + # They say it's trivial to implement it! + # And yet, it is still not done/merged + if record["type"] != "CAA": + # Add .domain.tdl to the name entry + record["name"] = "{}.{}".format(record["name"], domain) + flatten_dns_conf.append(record) + + # Construct the base data structure to use lexicon's API. + base_config = { + "provider_name": registrar_setting["name"], + "domain": domain, # domain name + } + base_config[registrar_setting["name"]] = registrar_setting["options"] + + # Get types present in the generated records + types = set() + + for record in flatten_dns_conf: + types.add(record["type"]) + + # Fetch all types present in the generated records + distant_records = {} + + for key in types: + record_config = { + "action": "list", + "type": key, + } + final_lexicon = ( + ConfigResolver() + .with_dict(dict_object=base_config) + .with_dict(dict_object=record_config) + ) + # print('final_lexicon:', final_lexicon); + client = Client(final_lexicon) + distant_records[key] = client.execute() + + for key in types: + for distant_record in distant_records[key]: + logger.debug(f"distant_record: {distant_record}") + for local_record in flatten_dns_conf: + print("local_record:", local_record) + + # Push the records + for record in flatten_dns_conf: + # For each record, first check if one record exists for the same (type, name) couple + it_exists = False + # TODO do not push if local and distant records are exactly the same ? + # is_the_same_record = False + + for distant_record in distant_records[record["type"]]: + if ( + distant_record["type"] == record["type"] + and distant_record["name"] == record["name"] + ): + it_exists = True + # see previous TODO + # if distant_record["ttl"] = ... and distant_record["name"] ... + # is_the_same_record = True + + # Finally, push the new record or update the existing one + record_config = { + "action": "update" + if it_exists + else "create", # create, list, update, delete + "type": record[ + "type" + ], # specify a type for record filtering, case sensitive in some cases. + "name": record["name"], + "content": record["value"], + # FIXME Removed TTL, because it doesn't work with Gandi. + # See https://github.com/AnalogJ/lexicon/issues/726 (similar issue) + # But I think there is another issue with Gandi. Or I'm misusing the API... + # "ttl": record["ttl"], + } + final_lexicon = ( + ConfigResolver() + .with_dict(dict_object=base_config) + .with_dict(dict_object=record_config) + ) + client = Client(final_lexicon) + print("pushed_record:", record_config, "→", end=" ") + results = client.execute() + print("results:", results) + # print("Failed" if results == False else "Ok") + + +# def domain_config_fetch(domain, key, value): diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index e3e549e20..c18d5f665 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -24,13 +24,6 @@ Manage domains """ import os -import re -import sys -import yaml -import functools - -from lexicon.client import Client -from lexicon.config import ConfigResolver from moulinette import m18n, Moulinette from moulinette.core import MoulinetteError @@ -42,20 +35,15 @@ from yunohost.app import ( _installed_apps, _get_app_settings, _get_conflicting_apps, - _parse_args_in_yunohost_format, ) from yunohost.regenconf import regen_conf, _force_clear_hashes, _process_regen_conf from yunohost.utils.error import YunohostError, YunohostValidationError -from yunohost.utils.network import get_public_ip -from yunohost.utils.dns import get_dns_zone_from_domain from yunohost.log import is_unit_operation from yunohost.hook import hook_callback logger = getActionLogger("yunohost.domain") DOMAIN_SETTINGS_DIR = "/etc/yunohost/domains" -REGISTRAR_SETTINGS_DIR = "/etc/yunohost/registrars" -REGISTRAR_LIST_PATH = "/usr/share/yunohost/other/registrar_list.yml" # Lazy dev caching to avoid re-query ldap every time we need the domain list @@ -331,55 +319,6 @@ def domain_remove(operation_logger, domain, remove_apps=False, force=False): logger.success(m18n.n("domain_deleted")) -def domain_dns_conf(domain): - """ - Generate DNS configuration for a domain - - Keyword argument: - domain -- Domain name - - """ - - if domain not in domain_list()["domains"]: - raise YunohostValidationError("domain_name_unknown", domain=domain) - - dns_conf = _build_dns_conf(domain) - - result = "" - - result += "; Basic ipv4/ipv6 records" - for record in dns_conf["basic"]: - result += "\n{name} {ttl} IN {type} {value}".format(**record) - - result += "\n\n" - result += "; XMPP" - for record in dns_conf["xmpp"]: - result += "\n{name} {ttl} IN {type} {value}".format(**record) - - result += "\n\n" - result += "; Mail" - for record in dns_conf["mail"]: - result += "\n{name} {ttl} IN {type} {value}".format(**record) - result += "\n\n" - - result += "; Extra" - for record in dns_conf["extra"]: - result += "\n{name} {ttl} IN {type} {value}".format(**record) - - for name, record_list in dns_conf.items(): - if name not in ("basic", "xmpp", "mail", "extra") and record_list: - result += "\n\n" - result += "; " + name - for record in record_list: - result += "\n{name} {ttl} IN {type} {value}".format(**record) - - if Moulinette.interface.type == "cli": - # FIXME Update this to point to our "dns push" doc - logger.info(m18n.n("domain_dns_conf_is_just_a_recommendation")) - - return result - - @is_unit_operation() def domain_main_domain(operation_logger, new_main_domain=None): """ @@ -421,32 +360,6 @@ def domain_main_domain(operation_logger, new_main_domain=None): logger.success(m18n.n("main_domain_changed")) -def domain_cert_status(domain_list, full=False): - import yunohost.certificate - - return yunohost.certificate.certificate_status(domain_list, full) - - -def domain_cert_install( - domain_list, force=False, no_checks=False, self_signed=False, staging=False -): - import yunohost.certificate - - return yunohost.certificate.certificate_install( - domain_list, force, no_checks, self_signed, staging - ) - - -def domain_cert_renew( - domain_list, force=False, no_checks=False, email=False, staging=False -): - import yunohost.certificate - - return yunohost.certificate.certificate_renew( - domain_list, force, no_checks, email, staging - ) - - def domain_url_available(domain, path): """ Check availability of a web path @@ -465,293 +378,8 @@ def _get_maindomain(): return maindomain -def _build_dns_conf(base_domain): - """ - Internal function that will returns a data structure containing the needed - information to generate/adapt the dns configuration - - Arguments: - domains -- List of a domain and its subdomains - - The returned datastructure will have the following form: - { - "basic": [ - # if ipv4 available - {"type": "A", "name": "@", "value": "123.123.123.123", "ttl": 3600}, - # if ipv6 available - {"type": "AAAA", "name": "@", "value": "valid-ipv6", "ttl": 3600}, - ], - "xmpp": [ - {"type": "SRV", "name": "_xmpp-client._tcp", "value": "0 5 5222 domain.tld.", "ttl": 3600}, - {"type": "SRV", "name": "_xmpp-server._tcp", "value": "0 5 5269 domain.tld.", "ttl": 3600}, - {"type": "CNAME", "name": "muc", "value": "@", "ttl": 3600}, - {"type": "CNAME", "name": "pubsub", "value": "@", "ttl": 3600}, - {"type": "CNAME", "name": "vjud", "value": "@", "ttl": 3600} - {"type": "CNAME", "name": "xmpp-upload", "value": "@", "ttl": 3600} - ], - "mail": [ - {"type": "MX", "name": "@", "value": "10 domain.tld.", "ttl": 3600}, - {"type": "TXT", "name": "@", "value": "\"v=spf1 a mx ip4:123.123.123.123 ipv6:valid-ipv6 -all\"", "ttl": 3600 }, - {"type": "TXT", "name": "mail._domainkey", "value": "\"v=DKIM1; k=rsa; p=some-super-long-key\"", "ttl": 3600}, - {"type": "TXT", "name": "_dmarc", "value": "\"v=DMARC1; p=none\"", "ttl": 3600} - ], - "extra": [ - # if ipv4 available - {"type": "A", "name": "*", "value": "123.123.123.123", "ttl": 3600}, - # if ipv6 available - {"type": "AAAA", "name": "*", "value": "valid-ipv6", "ttl": 3600}, - {"type": "CAA", "name": "@", "value": "128 issue \"letsencrypt.org\"", "ttl": 3600}, - ], - "example_of_a_custom_rule": [ - {"type": "SRV", "name": "_matrix", "value": "domain.tld.", "ttl": 3600} - ], - } - """ - - basic = [] - mail = [] - xmpp = [] - extra = [] - ipv4 = get_public_ip() - ipv6 = get_public_ip(6) - - subdomains = _list_subdomains_of(base_domain) - domains_settings = {domain: _get_domain_settings(domain) - for domain in [base_domain] + subdomains} - - base_dns_zone = domains_settings[base_domain].get("dns_zone") - - for domain, settings in domains_settings.items(): - - # Domain # Base DNS zone # Basename # Suffix # - # ------------------ # ----------------- # --------- # -------- # - # domain.tld # domain.tld # @ # # - # sub.domain.tld # domain.tld # sub # .sub # - # foo.sub.domain.tld # domain.tld # foo.sub # .foo.sub # - # sub.domain.tld # sub.domain.tld # @ # # - # foo.sub.domain.tld # sub.domain.tld # foo # .foo # - - # FIXME: shouldn't the basename just be based on the dns_zone setting of this domain ? - basename = domain.replace(f"{base_dns_zone}", "").rstrip(".") or "@" - suffix = f".{basename}" if base_name != "@" else "" - - ttl = settings["ttl"] - - ########################### - # Basic ipv4/ipv6 records # - ########################### - if ipv4: - basic.append([basename, ttl, "A", ipv4]) - - if ipv6: - basic.append([basename, ttl, "AAAA", ipv6]) - # TODO - # elif include_empty_AAAA_if_no_ipv6: - # basic.append(["@", ttl, "AAAA", None]) - - ######### - # Email # - ######### - if settings["mail_in"]: - mail.append([basename, ttl, "MX", f"10 {domain}."]) - - if settings["mail_out"]: - mail.append([basename, ttl, "TXT", '"v=spf1 a mx -all"']) - - # DKIM/DMARC record - dkim_host, dkim_publickey = _get_DKIM(domain) - - if dkim_host: - mail += [ - [f"{dkim_host}{suffix}", ttl, "TXT", dkim_publickey], - [f"_dmarc{suffix}", ttl, "TXT", '"v=DMARC1; p=none"'], - ] - - ######## - # XMPP # - ######## - if settings["xmpp"]: - xmpp += [ - [ - f"_xmpp-client._tcp{suffix}", - ttl, - "SRV", - f"0 5 5222 {domain}.", - ], - [ - f"_xmpp-server._tcp{suffix}", - ttl, - "SRV", - f"0 5 5269 {domain}.", - ], - [f"muc{suffix}", ttl, "CNAME", basename], - [f"pubsub{suffix}", ttl, "CNAME", basename], - [f"vjud{suffix}", ttl, "CNAME", basename], - [f"xmpp-upload{suffix}", ttl, "CNAME", basename], - ] - - ######### - # Extra # - ######### - - if ipv4: - extra.append([f"*{suffix}", ttl, "A", ipv4]) - - if ipv6: - extra.append([f"*{suffix}", ttl, "AAAA", ipv6]) - # TODO - # elif include_empty_AAAA_if_no_ipv6: - # extra.append(["*", ttl, "AAAA", None]) - - extra.append([basename, ttl, "CAA", '128 issue "letsencrypt.org"']) - - #################### - # Standard records # - #################### - - records = { - "basic": [ - {"name": name, "ttl": ttl_, "type": type_, "value": value} - for name, ttl_, type_, value in basic - ], - "xmpp": [ - {"name": name, "ttl": ttl_, "type": type_, "value": value} - for name, ttl_, type_, value in xmpp - ], - "mail": [ - {"name": name, "ttl": ttl_, "type": type_, "value": value} - for name, ttl_, type_, value in mail - ], - "extra": [ - {"name": name, "ttl": ttl_, "type": type_, "value": value} - for name, ttl_, type_, value in extra - ], - } - - ################## - # Custom records # - ################## - - # Defined by custom hooks ships in apps for example ... - - hook_results = hook_callback("custom_dns_rules", args=[base_domain]) - for hook_name, results in hook_results.items(): - # - # There can be multiple results per hook name, so results look like - # {'/some/path/to/hook1': - # { 'state': 'succeed', - # 'stdreturn': [{'type': 'SRV', - # 'name': 'stuff.foo.bar.', - # 'value': 'yoloswag', - # 'ttl': 3600}] - # }, - # '/some/path/to/hook2': - # { ... }, - # [...] - # - # Loop over the sub-results - custom_records = [ - v["stdreturn"] for v in results.values() if v and v["stdreturn"] - ] - - records[hook_name] = [] - for record_list in custom_records: - # Check that record_list is indeed a list of dict - # with the required keys - if ( - not isinstance(record_list, list) - or any(not isinstance(record, dict) for record in record_list) - or any( - key not in record - for record in record_list - for key in ["name", "ttl", "type", "value"] - ) - ): - # Display an error, mainly for app packagers trying to implement a hook - logger.warning( - "Ignored custom record from hook '%s' because the data is not a *list* of dict with keys name, ttl, type and value. Raw data : %s" - % (hook_name, record_list) - ) - continue - - records[hook_name].extend(record_list) - - return records - - -def _get_DKIM(domain): - DKIM_file = "/etc/dkim/{domain}.mail.txt".format(domain=domain) - - if not os.path.isfile(DKIM_file): - return (None, None) - - with open(DKIM_file) as f: - dkim_content = f.read() - - # Gotta manage two formats : - # - # Legacy - # ----- - # - # mail._domainkey IN TXT ( "v=DKIM1; k=rsa; " - # "p=" ) - # - # New - # ------ - # - # mail._domainkey IN TXT ( "v=DKIM1; h=sha256; k=rsa; " - # "p=" ) - - is_legacy_format = " h=sha256; " not in dkim_content - - # Legacy DKIM format - if is_legacy_format: - dkim = re.match( - ( - r"^(?P[a-z_\-\.]+)[\s]+([0-9]+[\s]+)?IN[\s]+TXT[\s]+" - r'[^"]*"v=(?P[^";]+);' - r'[\s"]*k=(?P[^";]+);' - r'[\s"]*p=(?P

[^";]+)' - ), - dkim_content, - re.M | re.S, - ) - else: - dkim = re.match( - ( - r"^(?P[a-z_\-\.]+)[\s]+([0-9]+[\s]+)?IN[\s]+TXT[\s]+" - r'[^"]*"v=(?P[^";]+);' - r'[\s"]*h=(?P[^";]+);' - r'[\s"]*k=(?P[^";]+);' - r'[\s"]*p=(?P

[^";]+)' - ), - dkim_content, - re.M | re.S, - ) - - if not dkim: - return (None, None) - - if is_legacy_format: - return ( - dkim.group("host"), - '"v={v}; k={k}; p={p}"'.format( - v=dkim.group("v"), k=dkim.group("k"), p=dkim.group("p") - ), - ) - else: - return ( - dkim.group("host"), - '"v={v}; h={h}; k={k}; p={p}"'.format( - v=dkim.group("v"), - h=dkim.group("h"), - k=dkim.group("k"), - p=dkim.group("p"), - ), - ) - - def _default_domain_settings(domain, is_main_domain): + from yunohost.utils.dns import get_dns_zone_from_domain return { "xmpp": is_main_domain, "mail_in": True, @@ -825,10 +453,10 @@ def domain_setting(domain, key, value=None, delete=False): value = int(value) except ValueError: # TODO add locales - raise YunohostError("invalid_number", value_type=type(value)) + raise YunohostValidationError("invalid_number", value_type=type(value)) if value < 0: - raise YunohostError("pattern_positive_number", value_type=type(value)) + raise YunohostValidationError("pattern_positive_number", value_type=type(value)) # Set new value domain_settings[key] = value @@ -870,184 +498,59 @@ def _set_domain_settings(domain, domain_settings): filepath = f"{DOMAIN_SETTINGS_DIR}/{domain}.yml" write_to_yaml(filepath, domain_settings) - -def _get_registrar_settings(dns_zone): - on_disk_settings = {} - filepath = f"{REGISTRAR_SETTINGS_DIR}/{dns_zone}.yml" - if os.path.exists(filepath) and os.path.isfile(filepath): - on_disk_settings = read_yaml(filepath) or {} - - return on_disk_settings +# +# +# Stuff managed in other files +# +# -def _set_registrar_settings(dns_zone): - if not os.path.exists(REGISTRAR_SETTINGS_DIR): - os.mkdir(REGISTRAR_SETTINGS_DIR) - filepath = f"{REGISTRAR_SETTINGS_DIR}/{dns_zone}.yml" - write_to_yaml(filepath, domain_registrar) +def domain_cert_status(domain_list, full=False): + import yunohost.certificate + + return yunohost.certificate.certificate_status(domain_list, full) + + +def domain_cert_install( + domain_list, force=False, no_checks=False, self_signed=False, staging=False +): + import yunohost.certificate + + return yunohost.certificate.certificate_install( + domain_list, force, no_checks, self_signed, staging + ) + + +def domain_cert_renew( + domain_list, force=False, no_checks=False, email=False, staging=False +): + import yunohost.certificate + + return yunohost.certificate.certificate_renew( + domain_list, force, no_checks, email, staging + ) + + +def domain_dns_conf(domain): + import yunohost.dns + return yunohost.dns.domain_dns_conf(domain) def domain_registrar_info(domain): - - dns_zone = _get_domain_settings(domain)["dns_zone"] - registrar_info = _get_registrar_settings(dns_zone) - if not registrar_info: - raise YunohostError("registrar_is_not_set", dns_zone=dns_zone) - - return registrar_info + import yunohost.dns + return yunohost.dns.domain_registrar_info(domain) def domain_registrar_catalog(registrar_name, full): - registrars = read_yaml(REGISTRAR_LIST_PATH) - - if registrar_name: - if registrar_name not in registrars.keys(): - raise YunohostError("domain_registrar_unknown", registrar=registrar_name) - else: - return registrars[registrar_name] - else: - return registrars + import yunohost.dns + return yunohost.dns.domain_registrar_catalog(registrar_name, full) def domain_registrar_set(domain, registrar, args): - - registrars = read_yaml(REGISTRAR_LIST_PATH) - if registrar not in registrars.keys(): - raise YunohostError("domain_registrar_unknown"i, registrar=registrar) - - parameters = registrars[registrar] - ask_args = [] - for parameter in parameters: - ask_args.append( - { - "name": parameter, - "type": "string", - "example": "", - "default": "", - } - ) - args_dict = ( - {} if not args else dict(urllib.parse.parse_qsl(args, keep_blank_values=True)) - ) - parsed_answer_dict = _parse_args_in_yunohost_format(args_dict, ask_args) - - domain_registrar = {"name": registrar, "options": {}} - for arg_name, arg_value_and_type in parsed_answer_dict.items(): - domain_registrar["options"][arg_name] = arg_value_and_type[0] - - dns_zone = _get_domain_settings(domain)["dns_zone"] - _set_registrar_settings(dns_zone, domain_registrar) + import yunohost.dns + return yunohost.dns.domain_registrar_set(domain, registrar, args) def domain_push_config(domain): - """ - Send DNS records to the previously-configured registrar of the domain. - """ - # Generate the records - if domain not in domain_list()["domains"]: - raise YunohostValidationError("domain_name_unknown", domain=domain) - - dns_conf = _build_dns_conf(domain) - - dns_zone = _get_domain_settings(domain)["dns_zone"] - registrar_setting = _get_registrar_settings(dns_zone) - - if not registrar_setting: - # FIXME add locales - raise YunohostValidationError("registrar_is_not_set", domain=domain) - - # Flatten the DNS conf - flatten_dns_conf = [] - for key in dns_conf: - list_of_records = dns_conf[key] - for record in list_of_records: - # FIXME Lexicon does not support CAA records - # See https://github.com/AnalogJ/lexicon/issues/282 and https://github.com/AnalogJ/lexicon/pull/371 - # They say it's trivial to implement it! - # And yet, it is still not done/merged - if record["type"] != "CAA": - # Add .domain.tdl to the name entry - record["name"] = "{}.{}".format(record["name"], domain) - flatten_dns_conf.append(record) - - # Construct the base data structure to use lexicon's API. - base_config = { - "provider_name": registrar_setting["name"], - "domain": domain, # domain name - } - base_config[registrar_setting["name"]] = registrar_setting["options"] - - # Get types present in the generated records - types = set() - - for record in flatten_dns_conf: - types.add(record["type"]) - - # Fetch all types present in the generated records - distant_records = {} - - for key in types: - record_config = { - "action": "list", - "type": key, - } - final_lexicon = ( - ConfigResolver() - .with_dict(dict_object=base_config) - .with_dict(dict_object=record_config) - ) - # print('final_lexicon:', final_lexicon); - client = Client(final_lexicon) - distant_records[key] = client.execute() - - for key in types: - for distant_record in distant_records[key]: - logger.debug(f"distant_record: {distant_record}") - for local_record in flatten_dns_conf: - print("local_record:", local_record) - - # Push the records - for record in flatten_dns_conf: - # For each record, first check if one record exists for the same (type, name) couple - it_exists = False - # TODO do not push if local and distant records are exactly the same ? - # is_the_same_record = False - - for distant_record in distant_records[record["type"]]: - if ( - distant_record["type"] == record["type"] - and distant_record["name"] == record["name"] - ): - it_exists = True - # see previous TODO - # if distant_record["ttl"] = ... and distant_record["name"] ... - # is_the_same_record = True - - # Finally, push the new record or update the existing one - record_config = { - "action": "update" - if it_exists - else "create", # create, list, update, delete - "type": record[ - "type" - ], # specify a type for record filtering, case sensitive in some cases. - "name": record["name"], - "content": record["value"], - # FIXME Removed TTL, because it doesn't work with Gandi. - # See https://github.com/AnalogJ/lexicon/issues/726 (similar issue) - # But I think there is another issue with Gandi. Or I'm misusing the API... - # "ttl": record["ttl"], - } - final_lexicon = ( - ConfigResolver() - .with_dict(dict_object=base_config) - .with_dict(dict_object=record_config) - ) - client = Client(final_lexicon) - print("pushed_record:", record_config, "→", end=" ") - results = client.execute() - print("results:", results) - # print("Failed" if results == False else "Ok") - - -# def domain_config_fetch(domain, key, value): + import yunohost.dns + return yunohost.dns.domain_push_config(domain) diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index 071e93059..9cb6dc567 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -37,7 +37,7 @@ from moulinette.utils.filesystem import write_to_file, read_file from moulinette.utils.network import download_json from yunohost.utils.error import YunohostError, YunohostValidationError -from yunohost.domain import _get_maindomain, _build_dns_conf +from yunohost.domain import _get_maindomain from yunohost.utils.network import get_public_ip from yunohost.utils.dns import dig from yunohost.log import is_unit_operation @@ -225,6 +225,9 @@ def dyndns_update( ipv6 -- IPv6 address to send """ + + from yunohost.dns import _build_dns_conf + # Get old ipv4/v6 old_ipv4, old_ipv6 = (None, None) # (default values) From 7e048b85b9e25d900404962852239da63532d9a5 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 28 Aug 2021 21:32:11 +0200 Subject: [PATCH 2824/3170] Misc fixes --- src/yunohost/dns.py | 4 ++-- src/yunohost/domain.py | 22 +++++++++++++++++----- src/yunohost/settings.py | 9 +++++++-- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index 590d50876..ce0fc7a7d 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -31,7 +31,7 @@ from lexicon.config import ConfigResolver from moulinette import m18n, Moulinette from moulinette.utils.log import getActionLogger -from moulinette.utils.filesystem import read_yaml, write_to_yaml +from moulinette.utils.filesystem import mkdir, read_yaml, write_to_yaml from yunohost.domain import domain_list, _get_domain_settings from yunohost.app import _parse_args_in_yunohost_format @@ -392,7 +392,7 @@ def _get_registrar_settings(dns_zone): def _set_registrar_settings(dns_zone, domain_registrar): if not os.path.exists(REGISTRAR_SETTINGS_DIR): - os.mkdir(REGISTRAR_SETTINGS_DIR) + mkdir(REGISTRAR_SETTINGS_DIR, mode=0o700) filepath = f"{REGISTRAR_SETTINGS_DIR}/{dns_zone}.yml" write_to_yaml(filepath, domain_registrar) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index c18d5f665..71b30451e 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -28,8 +28,9 @@ import os from moulinette import m18n, Moulinette from moulinette.core import MoulinetteError from moulinette.utils.log import getActionLogger -from moulinette.utils.filesystem import write_to_file, read_yaml, write_to_yaml +from moulinette.utils.filesystem import mkdir, write_to_file, read_yaml, write_to_yaml +from yunohost.settings import is_boolean from yunohost.app import ( app_ssowatconf, _installed_apps, @@ -58,6 +59,7 @@ def domain_list(exclude_subdomains=False): exclude_subdomains -- Filter out domains that are subdomains of other declared domains """ + global domain_list_cache if not exclude_subdomains and domain_list_cache: return domain_list_cache @@ -161,6 +163,7 @@ def domain_add(operation_logger, domain, dyndns=False): except Exception as e: raise YunohostError("domain_creation_failed", domain=domain, error=e) finally: + global domain_list_cache domain_list_cache = {} # Don't regen these conf if we're still in postinstall @@ -279,6 +282,7 @@ def domain_remove(operation_logger, domain, remove_apps=False, force=False): except Exception as e: raise YunohostError("domain_deletion_failed", domain=domain, error=e) finally: + global domain_list_cache domain_list_cache = {} stuff_to_delete = [ @@ -344,6 +348,7 @@ def domain_main_domain(operation_logger, new_main_domain=None): # Apply changes to ssl certs try: write_to_file("/etc/yunohost/current_host", new_main_domain) + global domain_list_cache domain_list_cache = {} _set_hostname(new_main_domain) except Exception as e: @@ -378,10 +383,10 @@ def _get_maindomain(): return maindomain -def _default_domain_settings(domain, is_main_domain): +def _default_domain_settings(domain): from yunohost.utils.dns import get_dns_zone_from_domain return { - "xmpp": is_main_domain, + "xmpp": domain == domain_list()["main"], "mail_in": True, "mail_out": True, "dns_zone": get_dns_zone_from_domain(domain), @@ -408,7 +413,7 @@ def _get_domain_settings(domain): on_disk_settings = read_yaml(filepath) or {} # Inject defaults if needed (using the magic .update() ;)) - settings = _default_domain_settings(domain, domain == maindomain) + settings = _default_domain_settings(domain) settings.update(on_disk_settings) return settings @@ -446,7 +451,14 @@ def domain_setting(domain, key, value=None, delete=False): # maybe inspired from the global settings if key in ["mail_in", "mail_out", "xmpp"]: - value = True if value.lower() in ['true', '1', 't', 'y', 'yes', "iloveynh"] else False + _is_boolean, value = is_boolean(value) + if not _is_boolean: + raise YunohostValidationError( + "global_settings_bad_type_for_setting", + setting=key, + received_type="not boolean", + expected_type="boolean", + ) if "ttl" == key: try: diff --git a/src/yunohost/settings.py b/src/yunohost/settings.py index fe072cddb..bc3a56d89 100644 --- a/src/yunohost/settings.py +++ b/src/yunohost/settings.py @@ -18,6 +18,9 @@ SETTINGS_PATH_OTHER_LOCATION = "/etc/yunohost/settings-%s.json" def is_boolean(value): + TRUE = ["true", "on", "yes", "y", "1"] + FALSE = ["false", "off", "no", "n", "0"] + """ Ensure a string value is intended as a boolean @@ -30,9 +33,11 @@ def is_boolean(value): """ if isinstance(value, bool): return True, value + if value in [0, 1]: + return True, bool(value) elif isinstance(value, str): - if str(value).lower() in ["true", "on", "yes", "false", "off", "no"]: - return True, str(value).lower() in ["true", "on", "yes"] + if str(value).lower() in TRUE + FALSE: + return True, str(value).lower() in TRUE else: return False, None else: From 951c6695b869cb3bed331e293d38770145718ad4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 28 Aug 2021 21:32:40 +0200 Subject: [PATCH 2825/3170] domain settings: Only store the diff with respect to defaults, should make possible migrations easier idk --- src/yunohost/domain.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 71b30451e..ad6cc99dc 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -503,12 +503,15 @@ def _set_domain_settings(domain, domain_settings): if domain not in domain_list()["domains"]: raise YunohostError("domain_name_unknown", domain=domain) + defaults = _default_domain_settings(domain) + diff_with_defaults = {k: v for k, v in domain_settings.items() if defaults.get(k) != v} + # First create the DOMAIN_SETTINGS_DIR if it doesn't exist if not os.path.exists(DOMAIN_SETTINGS_DIR): - os.mkdir(DOMAIN_SETTINGS_DIR) + mkdir(DOMAIN_SETTINGS_DIR, mode=0o700) # Save the settings to the .yaml file filepath = f"{DOMAIN_SETTINGS_DIR}/{domain}.yml" - write_to_yaml(filepath, domain_settings) + write_to_yaml(filepath, diff_with_defaults) # # From dded1cb7754ad6422e79110cc6c8beb6b5e546a9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 28 Aug 2021 21:49:35 +0200 Subject: [PATCH 2826/3170] Moar fikses --- src/yunohost/dns.py | 19 +++++++++++++++++-- src/yunohost/domain.py | 15 --------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index ce0fc7a7d..3943f7ed3 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -95,6 +95,21 @@ def domain_dns_conf(domain): return result +def _list_subdomains_of(parent_domain): + + domain_list_ = domain_list()["domains"] + + if parent_domain not in domain_list_: + raise YunohostError("domain_name_unknown", domain=domain) + + out = [] + for domain in domain_list_: + if domain.endswith(f".{parent_domain}"): + out.append(domain) + + return out + + def _build_dns_conf(base_domain): """ Internal function that will returns a data structure containing the needed @@ -163,7 +178,7 @@ def _build_dns_conf(base_domain): # FIXME: shouldn't the basename just be based on the dns_zone setting of this domain ? basename = domain.replace(f"{base_dns_zone}", "").rstrip(".") or "@" - suffix = f".{basename}" if base_name != "@" else "" + suffix = f".{basename}" if basename != "@" else "" ttl = settings["ttl"] @@ -423,7 +438,7 @@ def domain_registrar_set(domain, registrar, args): registrars = read_yaml(REGISTRAR_LIST_PATH) if registrar not in registrars.keys(): - raise YunohostValidationError("domain_registrar_unknown"i, registrar=registrar) + raise YunohostValidationError("domain_registrar_unknown", registrar=registrar) parameters = registrars[registrar] ask_args = [] diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index ad6cc99dc..8bb6f5a34 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -476,21 +476,6 @@ def domain_setting(domain, key, value=None, delete=False): _set_domain_settings(domain, domain_settings) -def _list_subdomains_of(parent_domain): - - domain_list_ = domain_list()["domains"] - - if parent_domain not in domain_list_: - raise YunohostError("domain_name_unknown", domain=domain) - - out = [] - for domain in domain_list_: - if domain.endswith(f".{parent_domain}"): - out.append(domain) - - return out - - def _set_domain_settings(domain, domain_settings): """ Set settings of a domain From 5a93e0640d3372054517411a49650e72606a6880 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 28 Aug 2021 21:53:19 +0200 Subject: [PATCH 2827/3170] domain_push_config -> domain_registrar_push --- data/actionsmap/yunohost.yml | 30 ++++++++++++++++++------------ src/yunohost/dns.py | 2 +- src/yunohost/domain.py | 4 ++-- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index c4323f166..686502b2c 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -439,16 +439,6 @@ domain: help: Subscribe to the DynDNS service action: store_true - ### domain_push_config() - push-config: - action_help: Push DNS records to registrar - api: GET /domains//push - arguments: - domain: - help: Domain name to add - extra: - pattern: *pattern_domain - ### domain_remove() remove: action_help: Delete domains @@ -558,6 +548,7 @@ domain: pattern: *pattern_domain path: help: The path to check (e.g. /coffee) + ### domain_setting() setting: action_help: Set or get a domain setting value @@ -576,12 +567,13 @@ domain: full: --delete help: Delete the key action: store_true + subcategories: registrar: subcategory_help: Manage domains registrars - actions: + actions: ### domain_registrar_set() - set: + set: action_help: Set domain registrar api: POST /domains//registrar arguments: @@ -594,6 +586,7 @@ domain: -a: full: --args help: Serialized arguments for registrar API (i.e. "auth_token=TOKEN&auth_username=USER"). + ### domain_registrar_info() info: action_help: Display info about registrar settings used for a domain @@ -603,10 +596,12 @@ domain: help: Domain name extra: pattern: *pattern_domain + ### domain_registrar_list() list: action_help: List registrars configured by DNS zone api: GET /domains/registrars + ### domain_registrar_catalog() catalog: action_help: List supported registrars API @@ -620,6 +615,17 @@ domain: help: Display all details, including info to create forms action: store_true + ### domain_registrar_push() + push: + action_help: Push DNS records to registrar + api: PUT /domains//registrar/push + arguments: + domain: + help: Domain name to push DNS conf for + extra: + pattern: *pattern_domain + + ############################# # App # ############################# diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index 3943f7ed3..8d8a6c735 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -464,7 +464,7 @@ def domain_registrar_set(domain, registrar, args): _set_registrar_settings(dns_zone, domain_registrar) -def domain_push_config(domain): +def domain_registrar_push(domain): """ Send DNS records to the previously-configured registrar of the domain. """ diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 8bb6f5a34..e1b247d7d 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -551,6 +551,6 @@ def domain_registrar_set(domain, registrar, args): return yunohost.dns.domain_registrar_set(domain, registrar, args) -def domain_push_config(domain): +def domain_registrar_push(domain): import yunohost.dns - return yunohost.dns.domain_push_config(domain) + return yunohost.dns.domain_registrar_push(domain) From 4089c34685a33ca5f8276516e0340ea769d0f656 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 28 Aug 2021 21:55:44 +0200 Subject: [PATCH 2828/3170] Add logging to domain_registrar_push --- src/yunohost/dns.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index 8d8a6c735..41e6dc374 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -464,7 +464,8 @@ def domain_registrar_set(domain, registrar, args): _set_registrar_settings(dns_zone, domain_registrar) -def domain_registrar_push(domain): +@is_unit_operation() +def domain_registrar_push(operation_logger, domain): """ Send DNS records to the previously-configured registrar of the domain. """ @@ -508,6 +509,8 @@ def domain_registrar_push(domain): for record in flatten_dns_conf: types.add(record["type"]) + operation_logger.start() + # Fetch all types present in the generated records distant_records = {} From a73b74a52db395a2f26fa4cb3a54042bb6da568a Mon Sep 17 00:00:00 2001 From: Weblate Date: Sat, 28 Aug 2021 21:46:54 +0000 Subject: [PATCH 2829/3170] Added translation using Weblate (Persian) --- locales/fa.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 locales/fa.json diff --git a/locales/fa.json b/locales/fa.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/locales/fa.json @@ -0,0 +1 @@ +{} From f1e5309d40a429513354e2c79857ee7d6623a8df Mon Sep 17 00:00:00 2001 From: ljf Date: Mon, 30 Aug 2021 13:14:17 +0200 Subject: [PATCH 2830/3170] Multiline, file, tags management + prefilled cli --- data/actionsmap/yunohost.yml | 4 +- data/helpers.d/configpanel | 112 ++++++++++++++++++-------- src/yunohost/app.py | 147 +++++++++++++++++++++++------------ 3 files changed, 177 insertions(+), 86 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index d9e3a50d0..28b713b03 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -838,8 +838,8 @@ app: arguments: app: help: App name - panel: - help: Select a specific panel + key: + help: Select a specific panel, section or a question nargs: '?' -f: full: --full diff --git a/data/helpers.d/configpanel b/data/helpers.d/configpanel index e1a96b866..0c3469c14 100644 --- a/data/helpers.d/configpanel +++ b/data/helpers.d/configpanel @@ -96,51 +96,72 @@ ynh_value_set() { _ynh_panel_get() { # From settings - local params_sources - params_sources=`python3 << EOL + local lines + lines=`python3 << EOL import toml from collections import OrderedDict with open("../config_panel.toml", "r") as f: file_content = f.read() loaded_toml = toml.loads(file_content, _dict=OrderedDict) -for panel_name,panel in loaded_toml.items(): - if isinstance(panel, dict): - for section_name, section in panel.items(): - if isinstance(section, dict): - for name, param in section.items(): - if isinstance(param, dict) and param.get('type', 'string') not in ['success', 'info', 'warning', 'danger', 'display_text', 'markdown']: - print("%s=%s" % (name, param.get('source', 'settings'))) +for panel_name, panel in loaded_toml.items(): + if not isinstance(panel, dict): continue + for section_name, section in panel.items(): + if not isinstance(section, dict): continue + for name, param in section.items(): + if not isinstance(param, dict): + continue + print(';'.join([ + name, + param.get('type', 'string'), + param.get('source', 'settings' if param.get('type', 'string') != 'file' else '') + ])) EOL ` - for param_source in $params_sources + for line in $lines do - local short_setting="$(echo $param_source | cut -d= -f1)" + IFS=';' read short_setting type source <<< "$line" local getter="get__${short_setting}" - local source="$(echo $param_source | cut -d= -f2)" sources[${short_setting}]="$source" + types[${short_setting}]="$type" file_hash[${short_setting}]="" - + formats[${short_setting}]="" # Get value from getter if exists if type -t $getter 2>/dev/null | grep -q '^function$' 2>/dev/null; then old[$short_setting]="$($getter)" + formats[${short_setting}]="yaml" + elif [[ "$source" == "" ]] ; then + old[$short_setting]="YNH_NULL" + # Get value from app settings or from another file - elif [[ "$source" == "settings" ]] || [[ "$source" == *":"* ]] ; then + elif [[ "$type" == "file" ]] ; then + if [[ "$source" == "settings" ]] ; then + ynh_die "File '${short_setting}' can't be stored in settings" + fi + old[$short_setting]="$(ls $(echo $source | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/) 2> /dev/null || echo YNH_NULL)" + file_hash[$short_setting]="true" + + # Get multiline text from settings or from a full file + elif [[ "$type" == "text" ]] ; then + if [[ "$source" == "settings" ]] ; then + old[$short_setting]="$(ynh_app_setting_get $app $short_setting)" + elif [[ "$source" == *":"* ]] ; then + ynh_die "For technical reasons, multiline text '${short_setting}' can't be stored automatically in a variable file, you have to create custom getter/setter" + else + old[$short_setting]="$(cat $(echo $source | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/) 2> /dev/null || echo YNH_NULL)" + fi + + # Get value from a kind of key/value file + else if [[ "$source" == "settings" ]] ; then source=":/etc/yunohost/apps/$app/settings.yml" fi - local source_key="$(echo "$source" | cut -d: -f1)" source_key=${source_key:-$short_setting} local source_file="$(echo "$source" | cut -d: -f2 | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" old[$short_setting]="$(ynh_value_get --file="${source_file}" --key="${source_key}")" - # Specific case for files (all content of the file is the source) - else - - old[$short_setting]="$(ls $(echo $source | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/) 2> /dev/null || echo YNH_NULL)" - file_hash[$short_setting]="true" fi done @@ -152,27 +173,42 @@ _ynh_panel_apply() { do local setter="set__${short_setting}" local source="${sources[$short_setting]}" + local type="${types[$short_setting]}" if [ "${changed[$short_setting]}" == "true" ] ; then # Apply setter if exists if type -t $setter 2>/dev/null | grep -q '^function$' 2>/dev/null; then $setter - # Copy file in right place + elif [[ "$source" == "" ]] ; then + continue + + # Save in a file + elif [[ "$type" == "file" ]] ; then + if [[ "$source" == "settings" ]] ; then + ynh_die "File '${short_setting}' can't be stored in settings" + fi + local source_file="$(echo "$source" | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" + cp "${!short_setting}" "$source_file" + + # Save value in app settings elif [[ "$source" == "settings" ]] ; then ynh_app_setting_set $app $short_setting "${!short_setting}" - # Get value from a kind of key/value file - elif [[ "$source" == *":"* ]] - then + # Save multiline text in a file + elif [[ "$type" == "text" ]] ; then + if [[ "$source" == *":"* ]] ; then + ynh_die "For technical reasons, multiline text '${short_setting}' can't be stored automatically in a variable file, you have to create custom getter/setter" + fi + local source_file="$(echo "$source" | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" + echo "${!short_setting}" > "$source_file" + + # Set value into a kind of key/value file + else local source_key="$(echo "$source" | cut -d: -f1)" source_key=${source_key:-$short_setting} local source_file="$(echo "$source" | cut -d: -f2 | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" ynh_value_set --file="${source_file}" --key="${source_key}" --value="${!short_setting}" - # Specific case for files (all content of the file is the source) - else - local source_file="$(echo "$source" | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" - cp "${!short_setting}" "$source_file" fi fi done @@ -182,7 +218,13 @@ _ynh_panel_show() { for short_setting in "${!old[@]}" do if [[ "${old[$short_setting]}" != YNH_NULL ]] ; then - ynh_return "${short_setting}: \"${old[$short_setting]}\"" + if [[ "${formats[$short_setting]}" == "yaml" ]] ; then + ynh_return "${short_setting}:" + ynh_return "$(echo "${old[$short_setting]}" | sed 's/^/ /g')" + else + ynh_return "${short_setting}: \"$(echo "${old[$short_setting]}" | sed ':a;N;$!ba;s/\n/\n\n/g')\"" + + fi fi done } @@ -225,7 +267,8 @@ _ynh_panel_validate() { done if [[ "$is_error" == "true" ]] then - ynh_die "Nothing has changed" + ynh_print_info "Nothing has changed" + exit 0 fi # Run validation if something is changed @@ -241,7 +284,7 @@ _ynh_panel_validate() { if [ -n "$result" ] then local key="YNH_ERROR_${short_setting}" - ynh_return "$key: $result" + ynh_return "$key: \"$result\"" is_error=true fi done @@ -274,6 +317,9 @@ ynh_panel_run() { declare -Ag changed=() declare -Ag file_hash=() declare -Ag sources=() + declare -Ag types=() + declare -Ag formats=() + case $1 in show) ynh_panel_get @@ -281,12 +327,12 @@ ynh_panel_run() { ;; apply) max_progression=4 - ynh_script_progression --message="Reading config panel description and current configuration..." --weight=1 + ynh_script_progression --message="Reading config panel description and current configuration..." ynh_panel_get ynh_panel_validate - ynh_script_progression --message="Applying the new configuration..." --weight=1 + ynh_script_progression --message="Applying the new configuration..." ynh_panel_apply ynh_script_progression --message="Configuration of $app completed" --last ;; diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 4e20c6950..2acdcb679 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -35,6 +35,7 @@ import glob import urllib.parse import base64 import tempfile +import readline from collections import OrderedDict from moulinette import msignals, m18n, msettings @@ -1754,36 +1755,21 @@ def app_action_run(operation_logger, app, action, args=None): # * docstrings # * merge translations on the json once the workflow is in place @is_unit_operation() -def app_config_show(operation_logger, app, panel='', full=False): +def app_config_show(operation_logger, app, key='', full=False): # logger.warning(m18n.n("experimental_feature")) # Check app is installed _assert_is_installed(app) - panel = panel if panel else '' - operation_logger.start() + key = key if key else '' # Read config panel toml - config_panel = _get_app_config_panel(app, filter_key=panel) + config_panel = _get_app_hydrated_config_panel(operation_logger, + app, filter_key=key) if not config_panel: return None - # Call config script to extract current values - parsed_values = _call_config_script(operation_logger, app, 'show') - - # # Check and transform values if needed - # options = [option for _, _, option in _get_options_iterator(config_panel)] - # args_dict = _parse_args_in_yunohost_format( - # parsed_values, options, False - # ) - - # Hydrate - logger.debug("Hydrating config with current value") - for _, _, option in _get_options_iterator(config_panel): - if option['name'] in parsed_values: - option["value"] = parsed_values[option['name']] #args_dict[option["name"]][0] - # Format result in full or reduce mode if full: operation_logger.success() @@ -1800,8 +1786,8 @@ def app_config_show(operation_logger, app, panel='', full=False): } if not option.get('optional', False): r_option['ask'] += ' *' - if option.get('value', None) is not None: - r_option['value'] = option['value'] + if option.get('current_value', None) is not None: + r_option['value'] = option['current_value'] operation_logger.success() return result @@ -1812,7 +1798,6 @@ def app_config_get(operation_logger, app, key): # Check app is installed _assert_is_installed(app) - operation_logger.start() # Read config panel toml config_panel = _get_app_config_panel(app, filter_key=key) @@ -1820,6 +1805,8 @@ def app_config_get(operation_logger, app, key): if not config_panel: raise YunohostError("app_config_no_panel") + operation_logger.start() + # Call config script to extract current values parsed_values = _call_config_script(operation_logger, app, 'show') @@ -1851,7 +1838,8 @@ def app_config_set(operation_logger, app, key=None, value=None, args=None): filter_key = key if key else '' # Read config panel toml - config_panel = _get_app_config_panel(app, filter_key=filter_key) + config_panel = _get_app_hydrated_config_panel(operation_logger, + app, filter_key=filter_key) if not config_panel: raise YunohostError("app_config_no_panel") @@ -1862,12 +1850,16 @@ def app_config_set(operation_logger, app, key=None, value=None, args=None): operation_logger.start() # Prepare pre answered questions - args = dict(urllib.parse.parse_qsl(args, keep_blank_values=True)) if args else {} + if args: + args = { key: ','.join(value) for key, value in urllib.parse.parse_qs(args, keep_blank_values=True).items() } + else: + args = {} if value is not None: args = {filter_key.split('.')[-1]: value} try: logger.debug("Asking unanswered question and prevalidating...") + args_dict = {} for panel in config_panel.get("panel", []): if msettings.get('interface') == 'cli' and len(filter_key.split('.')) < 3: msignals.display(colorize("\n" + "=" * 40, 'purple')) @@ -1878,13 +1870,13 @@ def app_config_set(operation_logger, app, key=None, value=None, args=None): msignals.display(colorize(f"\n# {section['name']}", 'purple')) # Check and ask unanswered questions - args_dict = _parse_args_in_yunohost_format( + args_dict.update(_parse_args_in_yunohost_format( args, section['options'] - ) + )) # Call config script to extract current values logger.info("Running config script...") - env = {key: str(value[0]) for key, value in args_dict.items()} + env = {key: str(value[0]) for key, value in args_dict.items() if not value[0] is None} errors = _call_config_script(operation_logger, app, 'apply', env=env) # Script got manually interrupted ... N.B. : KeyboardInterrupt does not inherit from Exception @@ -2246,6 +2238,37 @@ def _get_app_config_panel(app_id, filter_key=''): return None +def _get_app_hydrated_config_panel(operation_logger, app, filter_key=''): + + # Read config panel toml + config_panel = _get_app_config_panel(app, filter_key=filter_key) + + if not config_panel: + return None + + operation_logger.start() + + # Call config script to extract current values + parsed_values = _call_config_script(operation_logger, app, 'show') + + # # Check and transform values if needed + # options = [option for _, _, option in _get_options_iterator(config_panel)] + # args_dict = _parse_args_in_yunohost_format( + # parsed_values, options, False + # ) + + # Hydrate + logger.debug("Hydrating config with current value") + for _, _, option in _get_options_iterator(config_panel): + if option['name'] in parsed_values: + value = parsed_values[option['name']] + if isinstance(value, dict): + option.update(value) + else: + option["current_value"] = value #args_dict[option["name"]][0] + + return config_panel + def _get_app_settings(app_id): """ @@ -2808,6 +2831,7 @@ class YunoHostArgumentFormatParser(object): parsed_question.name = question["name"] parsed_question.type = question.get("type", 'string') parsed_question.default = question.get("default", None) + parsed_question.current_value = question.get("current_value") parsed_question.optional = question.get("optional", False) parsed_question.choices = question.get("choices", []) parsed_question.pattern = question.get("pattern") @@ -2835,11 +2859,20 @@ class YunoHostArgumentFormatParser(object): msignals.display(text_for_user_input_in_cli) elif question.value is None: - question.value = msignals.prompt( - message=text_for_user_input_in_cli, - is_password=self.hide_user_input_in_prompt, - confirm=self.hide_user_input_in_prompt - ) + prefill = None + if question.current_value is not None: + prefill = question.current_value + elif question.default is not None: + prefill = question.default + readline.set_startup_hook(lambda: readline.insert_text(prefill)) + try: + question.value = msignals.prompt( + message=text_for_user_input_in_cli, + is_password=self.hide_user_input_in_prompt, + confirm=self.hide_user_input_in_prompt + ) + finally: + readline.set_startup_hook() # Apply default value @@ -2897,8 +2930,6 @@ class YunoHostArgumentFormatParser(object): if question.choices: text_for_user_input_in_cli += " [{0}]".format(" | ".join(question.choices)) - if question.default is not None: - text_for_user_input_in_cli += " (default: {0})".format(question.default) if question.help or question.helpLink: text_for_user_input_in_cli += ":\033[m" if question.help: @@ -2919,6 +2950,18 @@ class StringArgumentParser(YunoHostArgumentFormatParser): default_value = "" +class TagsArgumentParser(YunoHostArgumentFormatParser): + argument_type = "tags" + + def _prevalidate(self, question): + values = question.value + for value in values.split(','): + question.value = value + super()._prevalidate(question) + question.value = values + + + class PasswordArgumentParser(YunoHostArgumentFormatParser): hide_user_input_in_prompt = True argument_type = "password" @@ -2938,13 +2981,15 @@ class PasswordArgumentParser(YunoHostArgumentFormatParser): return question def _prevalidate(self, question): - if any(char in question.value for char in self.forbidden_chars): - raise YunohostValidationError( - "pattern_password_app", forbidden_chars=self.forbidden_chars - ) + super()._prevalidate(question) - # If it's an optional argument the value should be empty or strong enough - if not question.optional or question.value: + if question.value is not None: + if any(char in question.value for char in self.forbidden_chars): + raise YunohostValidationError( + "pattern_password_app", forbidden_chars=self.forbidden_chars + ) + + # If it's an optional argument the value should be empty or strong enough from yunohost.utils.password import assert_password_is_strong_enough assert_password_is_strong_enough("user", question.value) @@ -3098,23 +3143,26 @@ class DisplayTextArgumentParser(YunoHostArgumentFormatParser): readonly = True def parse_question(self, question, user_answers): - question = super(DisplayTextArgumentParser, self).parse_question( + question_parsed = super().parse_question( question, user_answers ) - question.optional = True + question_parsed.optional = True + question_parsed.style = question.get('style', 'info') - return question + return question_parsed def _format_text_for_user_input_in_cli(self, question): text = question.ask['en'] - if question.type in ['info', 'warning', 'danger']: + + if question.style in ['success', 'info', 'warning', 'danger']: color = { + 'success': 'green', 'info': 'cyan', 'warning': 'yellow', 'danger': 'red' } - return colorize(m18n.g(question.type), color[question.type]) + f" {text}" + return colorize(m18n.g(question.style), color[question.style]) + f" {text}" else: return text @@ -3137,7 +3185,7 @@ class FileArgumentParser(YunoHostArgumentFormatParser): if question.get('accept'): question_parsed.accept = question.get('accept').replace(' ', '').split(',') else: - question.accept = [] + question_parsed.accept = [] if msettings.get('interface') == 'api': if user_answers.get(question_parsed.name): question_parsed.value = { @@ -3200,7 +3248,7 @@ ARGUMENTS_TYPE_PARSERS = { "string": StringArgumentParser, "text": StringArgumentParser, "select": StringArgumentParser, - "tags": StringArgumentParser, + "tags": TagsArgumentParser, "email": StringArgumentParser, "url": StringArgumentParser, "date": StringArgumentParser, @@ -3214,10 +3262,7 @@ ARGUMENTS_TYPE_PARSERS = { "number": NumberArgumentParser, "range": NumberArgumentParser, "display_text": DisplayTextArgumentParser, - "success": DisplayTextArgumentParser, - "danger": DisplayTextArgumentParser, - "warning": DisplayTextArgumentParser, - "info": DisplayTextArgumentParser, + "alert": DisplayTextArgumentParser, "markdown": DisplayTextArgumentParser, "file": FileArgumentParser, } From 766711069f674f7d48bc9d0ce406aae757cb8f7c Mon Sep 17 00:00:00 2001 From: ljf Date: Mon, 30 Aug 2021 13:25:32 +0200 Subject: [PATCH 2831/3170] [fix] Bad call to Moulinette.get --- src/yunohost/app.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 79944d340..f5e5dfd48 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1867,12 +1867,12 @@ def app_config_set(operation_logger, app, key=None, value=None, args=None): logger.debug("Asking unanswered question and prevalidating...") args_dict = {} for panel in config_panel.get("panel", []): - if Moulinette.get('interface') == 'cli' and len(filter_key.split('.')) < 3: + if Moulinette.interface == 'cli' and len(filter_key.split('.')) < 3: Moulinette.display(colorize("\n" + "=" * 40, 'purple')) Moulinette.display(colorize(f">>>> {panel['name']}", 'purple')) Moulinette.display(colorize("=" * 40, 'purple')) for section in panel.get("sections", []): - if Moulinette.get('interface') == 'cli' and len(filter_key.split('.')) < 3: + if Moulinette.interface == 'cli' and len(filter_key.split('.')) < 3: Moulinette.display(colorize(f"\n# {section['name']}", 'purple')) # Check and ask unanswered questions @@ -2855,9 +2855,10 @@ class YunoHostArgumentFormatParser(object): def parse(self, question, user_answers): question = self.parse_question(question, user_answers) +<<<<<<< HEAD while True: # Display question if no value filled or if it's a readonly message - if Moulinette.get('interface') == 'cli': + if Moulinette.interface == 'cli': text_for_user_input_in_cli = self._format_text_for_user_input_in_cli( question ) @@ -2893,7 +2894,7 @@ class YunoHostArgumentFormatParser(object): try: self._prevalidate(question) except YunohostValidationError as e: - if Moulinette.get('interface') == 'api': + if Moulinette.interface == 'api': raise Moulinette.display(str(e), 'error') question.value = None @@ -3179,7 +3180,7 @@ class FileArgumentParser(YunoHostArgumentFormatParser): @classmethod def clean_upload_dirs(cls): # Delete files uploaded from API - if Moulinette.get('interface') == 'api': + if Moulinette.interface == 'api': for upload_dir in cls.upload_dirs: if os.path.exists(upload_dir): shutil.rmtree(upload_dir) @@ -3192,7 +3193,7 @@ class FileArgumentParser(YunoHostArgumentFormatParser): question_parsed.accept = question.get('accept').replace(' ', '').split(',') else: question_parsed.accept = [] - if Moulinette.get('interface') == 'api': + if Moulinette.interface == 'api': if user_answers.get(question_parsed.name): question_parsed.value = { 'content': question_parsed.value, @@ -3223,7 +3224,7 @@ class FileArgumentParser(YunoHostArgumentFormatParser): if not question.value: return question.value - if Moulinette.get('interface') == 'api': + if Moulinette.interface == 'api': upload_dir = tempfile.mkdtemp(prefix='tmp_configpanel_') FileArgumentParser.upload_dirs += [upload_dir] From 8d9f8c7123dbc8286025ae2b8acbafa8c03c746d Mon Sep 17 00:00:00 2001 From: ljf Date: Mon, 30 Aug 2021 14:04:12 +0200 Subject: [PATCH 2832/3170] [fix] Bad call to Moulinette.interface --- src/yunohost/app.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index f5e5dfd48..7ddbdc7f3 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1867,12 +1867,12 @@ def app_config_set(operation_logger, app, key=None, value=None, args=None): logger.debug("Asking unanswered question and prevalidating...") args_dict = {} for panel in config_panel.get("panel", []): - if Moulinette.interface == 'cli' and len(filter_key.split('.')) < 3: + if Moulinette.interface.type== 'cli' and len(filter_key.split('.')) < 3: Moulinette.display(colorize("\n" + "=" * 40, 'purple')) Moulinette.display(colorize(f">>>> {panel['name']}", 'purple')) Moulinette.display(colorize("=" * 40, 'purple')) for section in panel.get("sections", []): - if Moulinette.interface == 'cli' and len(filter_key.split('.')) < 3: + if Moulinette.interface.type== 'cli' and len(filter_key.split('.')) < 3: Moulinette.display(colorize(f"\n# {section['name']}", 'purple')) # Check and ask unanswered questions @@ -2855,10 +2855,9 @@ class YunoHostArgumentFormatParser(object): def parse(self, question, user_answers): question = self.parse_question(question, user_answers) -<<<<<<< HEAD while True: # Display question if no value filled or if it's a readonly message - if Moulinette.interface == 'cli': + if Moulinette.interface.type== 'cli': text_for_user_input_in_cli = self._format_text_for_user_input_in_cli( question ) @@ -2894,7 +2893,7 @@ class YunoHostArgumentFormatParser(object): try: self._prevalidate(question) except YunohostValidationError as e: - if Moulinette.interface == 'api': + if Moulinette.interface.type== 'api': raise Moulinette.display(str(e), 'error') question.value = None @@ -3180,7 +3179,7 @@ class FileArgumentParser(YunoHostArgumentFormatParser): @classmethod def clean_upload_dirs(cls): # Delete files uploaded from API - if Moulinette.interface == 'api': + if Moulinette.interface.type== 'api': for upload_dir in cls.upload_dirs: if os.path.exists(upload_dir): shutil.rmtree(upload_dir) @@ -3193,7 +3192,7 @@ class FileArgumentParser(YunoHostArgumentFormatParser): question_parsed.accept = question.get('accept').replace(' ', '').split(',') else: question_parsed.accept = [] - if Moulinette.interface == 'api': + if Moulinette.interface.type== 'api': if user_answers.get(question_parsed.name): question_parsed.value = { 'content': question_parsed.value, @@ -3224,7 +3223,7 @@ class FileArgumentParser(YunoHostArgumentFormatParser): if not question.value: return question.value - if Moulinette.interface == 'api': + if Moulinette.interface.type== 'api': upload_dir = tempfile.mkdtemp(prefix='tmp_configpanel_') FileArgumentParser.upload_dirs += [upload_dir] From 27f6492f8b852bee40871ff810c4c49586aed1c4 Mon Sep 17 00:00:00 2001 From: Weblate Date: Mon, 30 Aug 2021 14:14:32 +0000 Subject: [PATCH 2833/3170] Added translation using Weblate (Kurdish (Central)) --- locales/ckb.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 locales/ckb.json diff --git a/locales/ckb.json b/locales/ckb.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/locales/ckb.json @@ -0,0 +1 @@ +{} From 969564eec659d51cade553d4bfe092b2c4ba888c Mon Sep 17 00:00:00 2001 From: ljf Date: Mon, 30 Aug 2021 19:41:07 +0200 Subject: [PATCH 2834/3170] [fix] simple/double quotes into source --- data/helpers.d/configpanel | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/data/helpers.d/configpanel b/data/helpers.d/configpanel index 0c3469c14..cfdbfc331 100644 --- a/data/helpers.d/configpanel +++ b/data/helpers.d/configpanel @@ -75,22 +75,21 @@ ynh_value_set() { # Manage arguments with getopts ynh_handle_getopts_args "$@" - local var_part='^[ \t]*\$?(\w*\[)?[ \t]*["'"']?${key}['"'"]?[ \t]*\]?[ \t]*[:=]>?[ \t]*' + local var_part='[ \t]*\$?(\w*\[)?[ \t]*["'"']?${key}['"'"]?[ \t]*\]?[ \t]*[:=]>?[ \t]*' - local crazy_value="$(grep -i -o -P "${var_part}\K.*(?=[ \t,\n;]*\$)" ${file} | head -n1)" - local var_part="^[ \t]*(\$?\w*\[)?[ \t]*[\"']?${key}[\"']?[ \t]*\]?[ \t]*[:=]>?[ \t]*" + local crazy_value="$(grep -i -o -P "^${var_part}\K.*(?=[ \t,\n;]*\$)" ${file} | head -n1)" local first_char="${crazy_value:0:1}" if [[ "$first_char" == '"' ]] ; then - value="$(echo "$value" | sed 's/"/\\"/g')" - sed -ri "s%(${var_part}\")[^\"]*(\"[ \t\n,;]*)\$%\1${value}\2%i" ${file} + value="$(echo "$value" | sed 's/"/\"/g')" + sed -ri 's%^('"${var_part}"'")[^"]*("[ \t;,]*)$%\1'"${value}"'\3%i' ${file} elif [[ "$first_char" == "'" ]] ; then - value="$(echo "$value" | sed "s/'/\\\\'/g")" - sed -ri "s%(${var_part}')[^']*('[ \t\n,;]*)\$%\1${value}\2%i" ${file} + value="$(echo "$value" | sed "s/'/"'\'"'/g")" + sed -ri "s%^(${var_part}')[^']*('"'[ \t,;]*)$%\1'"${value}"'\3%i' ${file} else if [[ "$value" == *"'"* ]] || [[ "$value" == *'"'* ]] ; then - value="\"$(echo "$value" | sed 's/"/\\"/g')\"" + value='\"'"$(echo "$value" | sed 's/"/\"/g')"'\"' fi - sed -ri "s%(${var_part}')[^']*('[ \t\n,;]*)\$%\1${value}\2%i" ${file} + sed -ri "s%^(${var_part}).*"'$%\1'"${value}"'%i' ${file} fi } @@ -222,7 +221,7 @@ _ynh_panel_show() { ynh_return "${short_setting}:" ynh_return "$(echo "${old[$short_setting]}" | sed 's/^/ /g')" else - ynh_return "${short_setting}: \"$(echo "${old[$short_setting]}" | sed ':a;N;$!ba;s/\n/\n\n/g')\"" + ynh_return "${short_setting}: "'"'"$(echo "${old[$short_setting]}" | sed 's/"/\\"/g' | sed ':a;N;$!ba;s/\n/\n\n/g')"'"' fi fi From c20226fc54f3e1772e7231907796e076074b7198 Mon Sep 17 00:00:00 2001 From: ljf Date: Mon, 30 Aug 2021 19:41:30 +0200 Subject: [PATCH 2835/3170] [enh] Move prefill feature into moulinette --- src/yunohost/app.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 7ddbdc7f3..2ea1a3b70 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -35,7 +35,6 @@ import glob import urllib.parse import base64 import tempfile -import readline from collections import OrderedDict from moulinette.interfaces.cli import colorize @@ -2865,20 +2864,18 @@ class YunoHostArgumentFormatParser(object): Moulinette.display(text_for_user_input_in_cli) elif question.value is None: - prefill = None + prefill = "" if question.current_value is not None: prefill = question.current_value elif question.default is not None: prefill = question.default - readline.set_startup_hook(lambda: readline.insert_text(prefill)) - try: - question.value = Moulinette.prompt( - message=text_for_user_input_in_cli, - is_password=self.hide_user_input_in_prompt, - confirm=self.hide_user_input_in_prompt - ) - finally: - readline.set_startup_hook() + question.value = Moulinette.prompt( + message=text_for_user_input_in_cli, + is_password=self.hide_user_input_in_prompt, + confirm=self.hide_user_input_in_prompt, + prefill=prefill, + is_multiline=(question.type == "text") + ) # Apply default value From bb11b5dcacc6ad8f6dfeb9ccb9e98a4bca7a39bd Mon Sep 17 00:00:00 2001 From: ljf Date: Tue, 31 Aug 2021 04:20:21 +0200 Subject: [PATCH 2836/3170] [enh] Be able to delete source file --- data/helpers.d/configpanel | 10 +++++++++- src/yunohost/app.py | 10 +++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/data/helpers.d/configpanel b/data/helpers.d/configpanel index cfdbfc331..e99a66d4c 100644 --- a/data/helpers.d/configpanel +++ b/data/helpers.d/configpanel @@ -187,7 +187,11 @@ _ynh_panel_apply() { ynh_die "File '${short_setting}' can't be stored in settings" fi local source_file="$(echo "$source" | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" - cp "${!short_setting}" "$source_file" + if [[ "${!short_setting}" == "" ]] ; then + rm -f "$source_file" + else + cp "${!short_setting}" "$source_file" + fi # Save value in app settings elif [[ "$source" == "settings" ]] ; then @@ -247,6 +251,10 @@ _ynh_panel_validate() { file_hash[new__$short_setting]="" if [ -f "${old[$short_setting]}" ] ; then file_hash[old__$short_setting]=$(sha256sum "${old[$short_setting]}" | cut -d' ' -f1) + if [ -z "${!short_setting}" ] ; then + changed[$short_setting]=true + is_error=false + fi fi if [ -f "${!short_setting}" ] ; then file_hash[new__$short_setting]=$(sha256sum "${!short_setting}" | cut -d' ' -f1) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 2ea1a3b70..425f04023 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -3190,20 +3190,24 @@ class FileArgumentParser(YunoHostArgumentFormatParser): else: question_parsed.accept = [] if Moulinette.interface.type== 'api': - if user_answers.get(question_parsed.name): + if user_answers.get(f"{question_parsed.name}[name]"): question_parsed.value = { 'content': question_parsed.value, 'filename': user_answers.get(f"{question_parsed.name}[name]", question_parsed.name), } + # If path file are the same + if question_parsed.value and str(question_parsed.value) == question_parsed.current_value: + question_parsed.value = None + return question_parsed def _prevalidate(self, question): super()._prevalidate(question) - if isinstance(question.value, str) and not os.path.exists(question.value): + if isinstance(question.value, str) and question.value and not os.path.exists(question.value): raise YunohostValidationError( "app_argument_invalid", name=question.name, error=m18n.n("invalid_number1") ) - if question.value is None or not question.accept: + if question.value in [None, ''] or not question.accept: return filename = question.value if isinstance(question.value, str) else question.value['filename'] From 82bc5a93483e74bbd8ff78bf88e3dc4934bab00f Mon Sep 17 00:00:00 2001 From: Parviz Homayun Date: Tue, 31 Aug 2021 09:56:27 +0000 Subject: [PATCH 2837/3170] Translated using Weblate (Persian) Currently translated at 0.3% (2 of 643 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fa/ --- locales/fa.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/locales/fa.json b/locales/fa.json index 0967ef424..3c8761b5a 100644 --- a/locales/fa.json +++ b/locales/fa.json @@ -1 +1,4 @@ -{} +{ + "action_invalid": "اقدام نامعتبر '{action}'", + "aborting": "رها کردن." +} From e7e891574e7600a5ae47cc5d9910e480410abb03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= Date: Tue, 31 Aug 2021 10:11:18 +0000 Subject: [PATCH 2838/3170] Translated using Weblate (French) Currently translated at 100.0% (643 of 643 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 1cac6bbba..0b5a5e93f 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -571,7 +571,7 @@ "diagnosis_dns_try_dyndns_update_force": "La configuration DNS de ce domaine devrait être automatiquement gérée par YunoHost. Si ce n'est pas le cas, vous pouvez essayer de forcer une mise à jour en utilisant yunohost dyndns update --force.", "app_packaging_format_not_supported": "Cette application ne peut pas être installée car son format n'est pas pris en charge par votre version de YunoHost. Vous devriez probablement envisager de mettre à jour votre système.", "migration_0015_weak_certs": "Il a été constaté que les certificats suivants utilisent encore des algorithmes de signature peu robustes et doivent être mis à jour pour être compatibles avec la prochaine version de NGINX : {certs}", - "global_settings_setting_backup_compress_tar_archives": "Compresser les archives (.tar.gz) au lieu de créer des archives non-compressées lors de la création des sauvegardes. N.B. Activer cette option permet de créer des archives plus légères, mais aussi d'avoir des procédures de sauvegarde significativement plus longues et plus gourmandes en CPU.", + "global_settings_setting_backup_compress_tar_archives": "Lors de la création de nouvelles sauvegardes, compressez les archives (.tar.gz) au lieu des archives non compressées (.tar). N.B. : activer cette option permet de créer des archives plus légères, mais la procédure de sauvegarde initiale sera significativement plus longues et plus gourmandes en CPU.", "migration_description_0018_xtable_to_nftable": "Migrer les anciennes règles de trafic réseau vers le nouveau système basé sur nftables", "service_description_php7.3-fpm": "Exécute les applications écrites en PHP avec NGINX", "migration_0018_failed_to_reset_legacy_rules": "La réinitialisation des règles iptable par défaut a échoué : {error}", @@ -586,7 +586,7 @@ "app_manifest_install_ask_is_public": "Cette application devrait-elle être visible par les visiteurs anonymes ?", "app_manifest_install_ask_admin": "Choisissez un administrateur pour cette application", "app_manifest_install_ask_password": "Choisissez un mot de passe administrateur pour cette application", - "app_manifest_install_ask_path": "Choisissez le chemin sur lequel vous souhaitez installer cette application", + "app_manifest_install_ask_path": "Choisissez le chemin d'URL (après le domaine) où cette application doit être installée", "app_manifest_install_ask_domain": "Choisissez le domaine sur lequel vous souhaitez installer cette application", "global_settings_setting_smtp_relay_user": "Compte utilisateur du relais SMTP", "global_settings_setting_smtp_relay_port": "Port du relais SMTP", @@ -637,5 +637,9 @@ "global_settings_setting_security_webadmin_allowlist": "Adresses IP autorisées à accéder à la page web du portail d'administration (webadmin). Elles doivent être séparées par une virgule.", "global_settings_setting_security_webadmin_allowlist_enabled": "Autorisez seulement certaines IP à accéder à la page web du portail d'administration (webadmin).", "diagnosis_http_localdomain": "Le domaine {domain}, avec un TLD .local, ne devrait pas être atteint depuis l'extérieur du réseau local.", - "diagnosis_dns_specialusedomain": "Le domaine {domain} est basé sur un domaine de premier niveau (TLD) à usage spécial et ne devrait donc pas avoir d'enregistrements DNS réels." -} \ No newline at end of file + "diagnosis_dns_specialusedomain": "Le domaine {domain} est basé sur un domaine de premier niveau (TLD) à usage spécial et ne devrait donc pas avoir d'enregistrements DNS réels.", + "invalid_password": "Mot de passe incorrect", + "ldap_server_is_down_restart_it": "Le service LDAP est en panne, essayez de le redémarrer...", + "ldap_server_down": "Impossible d'atteindre le serveur LDAP", + "global_settings_setting_security_experimental_enabled": "Activez les fonctionnalités de sécurité expérimentales (ne l'activez pas si vous ne savez pas ce que vous faites !)" +} From dd6c58a7dac71c3ecc6831be93d0b216875b1381 Mon Sep 17 00:00:00 2001 From: Parviz Homayun Date: Tue, 31 Aug 2021 11:27:56 +0000 Subject: [PATCH 2839/3170] Translated using Weblate (Persian) Currently translated at 2.9% (19 of 643 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fa/ --- locales/fa.json | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/locales/fa.json b/locales/fa.json index 3c8761b5a..5a2b61bc1 100644 --- a/locales/fa.json +++ b/locales/fa.json @@ -1,4 +1,21 @@ { "action_invalid": "اقدام نامعتبر '{action}'", - "aborting": "رها کردن." + "aborting": "رها کردن.", + "app_change_url_failed_nginx_reload": "NGINX بارگیری نشد. در اینجا خروجی 'nginx -t' است:\n{nginx_errors}", + "app_argument_required": "استدلال '{name}' الزامی است", + "app_argument_password_no_default": "خطا هنگام تجزیه گذرواژه '{name}': به دلایل امنیتی استدلال رمز عبور نمی تواند مقدار پیش فرض داشته باشد", + "app_argument_invalid": "یک مقدار معتبر انتخاب کنید برای استدلال '{name}':{error}", + "app_argument_choice_invalid": "برای آرگومان '{name}' از یکی از این گزینه ها '{choices}' استفاده کنید", + "app_already_up_to_date": "{app} در حال حاضر به روز است", + "app_already_installed_cant_change_url": "این برنامه قبلاً نصب شده است. URL فقط با این عملکرد قابل تغییر نیست. در صورت موجود بودن برنامه `app changeurl` را بررسی کنید.", + "app_already_installed": "{app} قبلاً نصب شده است", + "app_action_broke_system": "این اقدام به نظر می رسد سرویس های مهمی را خراب کرده است: {services}", + "app_action_cannot_be_ran_because_required_services_down": "برای اجرای این عملیات سرویس هایی که مورد نیازاند و باید اجرا شوند: {services}. سعی کنید آنها را مجدداً راه اندازی کنید (و علت خرابی احتمالی آنها را بررسی کنید).", + "already_up_to_date": "کاری برای انجام دادن نیست. همه چیز در حال حاضر به روز است.", + "admin_password_too_long": "لطفاً گذرواژه ای کوتاهتر از 127 کاراکتر انتخاب کنید", + "admin_password_changed": "رمز مدیریت تغییر کرد", + "admin_password_change_failed": "تغییر رمز امکان پذیر نیست", + "admin_password": "رمز عبور مدیریت", + "additional_urls_already_removed": "نشانی اینترنتی اضافی '{url}' قبلاً در نشانی اینترنتی اضافی برای اجازه '{permission}'حذف شده است", + "additional_urls_already_added": "نشانی اینترنتی اضافی '{url}' قبلاً در نشانی اینترنتی اضافی برای اجازه '{permission}' اضافه شده است" } From 08e7fcc48ef94aa48bc328878f06bb10d81af82d Mon Sep 17 00:00:00 2001 From: ljf Date: Tue, 31 Aug 2021 17:44:42 +0200 Subject: [PATCH 2840/3170] [enh] Be able to correctly display the error --- src/yunohost/utils/error.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/utils/error.py b/src/yunohost/utils/error.py index f9b4ac61a..8405830e7 100644 --- a/src/yunohost/utils/error.py +++ b/src/yunohost/utils/error.py @@ -59,4 +59,4 @@ class YunohostValidationError(YunohostError): def content(self): - return {"error": self.strerror, "error_key": self.key} + return {"error": self.strerror, "error_key": self.key, **self.kwargs} From 6d16e22f8779c2d7741c6e6b20c5ef301ac11d57 Mon Sep 17 00:00:00 2001 From: ljf Date: Tue, 31 Aug 2021 17:45:13 +0200 Subject: [PATCH 2841/3170] [enh] VisibleIf on config panel section --- src/yunohost/app.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 425f04023..69c65046a 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -2206,9 +2206,11 @@ def _get_app_config_panel(app_id, filter_key=''): "id": section_key, "name": section_value.get("name", ""), "optional": section_value.get("optional", True), - "services": value.get("services", []), + "services": section_value.get("services", []), "options": [], } + if section_value.get('visibleIf'): + section['visibleIf'] = section_value.get('visibleIf') options = [ k_v @@ -2952,6 +2954,11 @@ class StringArgumentParser(YunoHostArgumentFormatParser): argument_type = "string" default_value = "" + def _prevalidate(self, question): + super()._prevalidate(question) + raise YunohostValidationError( + "app_argument_invalid", field=question.name, error=m18n.n("invalid_number2") + ) class TagsArgumentParser(YunoHostArgumentFormatParser): argument_type = "tags" @@ -3065,7 +3072,7 @@ class DomainArgumentParser(YunoHostArgumentFormatParser): def _raise_invalid_answer(self, question): raise YunohostValidationError( - "app_argument_invalid", name=question.name, error=m18n.n("domain_unknown") + "app_argument_invalid", field=question.name, error=m18n.n("domain_unknown") ) @@ -3092,7 +3099,7 @@ class UserArgumentParser(YunoHostArgumentFormatParser): def _raise_invalid_answer(self, question): raise YunohostValidationError( "app_argument_invalid", - name=question.name, + field=question.name, error=m18n.n("user_unknown", user=question.value), ) @@ -3116,17 +3123,17 @@ class NumberArgumentParser(YunoHostArgumentFormatParser): super()._prevalidate(question) if not isinstance(question.value, int) and not (isinstance(question.value, str) and question.value.isdigit()): raise YunohostValidationError( - "app_argument_invalid", name=question.name, error=m18n.n("invalid_number") + "app_argument_invalid", field=question.name, error=m18n.n("invalid_number") ) if question.min is not None and int(question.value) < question.min: raise YunohostValidationError( - "app_argument_invalid", name=question.name, error=m18n.n("invalid_number") + "app_argument_invalid", field=question.name, error=m18n.n("invalid_number") ) if question.max is not None and int(question.value) > question.max: raise YunohostValidationError( - "app_argument_invalid", name=question.name, error=m18n.n("invalid_number") + "app_argument_invalid", field=question.name, error=m18n.n("invalid_number") ) def _post_parse_value(self, question): @@ -3137,7 +3144,7 @@ class NumberArgumentParser(YunoHostArgumentFormatParser): return int(question.value) raise YunohostValidationError( - "app_argument_invalid", name=question.name, error=m18n.n("invalid_number") + "app_argument_invalid", field=question.name, error=m18n.n("invalid_number") ) @@ -3205,7 +3212,7 @@ class FileArgumentParser(YunoHostArgumentFormatParser): super()._prevalidate(question) if isinstance(question.value, str) and question.value and not os.path.exists(question.value): raise YunohostValidationError( - "app_argument_invalid", name=question.name, error=m18n.n("invalid_number1") + "app_argument_invalid", field=question.name, error=m18n.n("invalid_number1") ) if question.value in [None, ''] or not question.accept: return @@ -3213,7 +3220,7 @@ class FileArgumentParser(YunoHostArgumentFormatParser): filename = question.value if isinstance(question.value, str) else question.value['filename'] if '.' not in filename or '.' + filename.split('.')[-1] not in question.accept: raise YunohostValidationError( - "app_argument_invalid", name=question.name, error=m18n.n("invalid_number2") + "app_argument_invalid", field=question.name, error=m18n.n("invalid_number2") ) From 1c2fff750d4942901a3e3776a49c57d92dfbf8a4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 31 Aug 2021 18:33:15 +0200 Subject: [PATCH 2842/3170] dns/lexicon: Tidying up the push function --- src/yunohost/dns.py | 117 +++++++++++++++++++------------------------- 1 file changed, 50 insertions(+), 67 deletions(-) diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index 41e6dc374..e3131bcdd 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -26,9 +26,6 @@ import os import re -from lexicon.client import Client -from lexicon.config import ConfigResolver - from moulinette import m18n, Moulinette from moulinette.utils.log import getActionLogger from moulinette.utils.filesystem import mkdir, read_yaml, write_to_yaml @@ -469,96 +466,81 @@ def domain_registrar_push(operation_logger, domain): """ Send DNS records to the previously-configured registrar of the domain. """ - # Generate the records + + from lexicon.client import Client as LexiconClient + from lexicon.config import ConfigResolver as LexiconConfigResolver + if domain not in domain_list()["domains"]: raise YunohostValidationError("domain_name_unknown", domain=domain) - dns_conf = _build_dns_conf(domain) - dns_zone = _get_domain_settings(domain)["dns_zone"] - registrar_setting = _get_registrar_settings(dns_zone) + registrar_settings = _get_registrar_settingss(dns_zone) - if not registrar_setting: - # FIXME add locales + if not registrar_settings: raise YunohostValidationError("registrar_is_not_set", domain=domain) + # Generate the records + dns_conf = _build_dns_conf(domain) + # Flatten the DNS conf - flatten_dns_conf = [] - for key in dns_conf: - list_of_records = dns_conf[key] - for record in list_of_records: - # FIXME Lexicon does not support CAA records - # See https://github.com/AnalogJ/lexicon/issues/282 and https://github.com/AnalogJ/lexicon/pull/371 - # They say it's trivial to implement it! - # And yet, it is still not done/merged - if record["type"] != "CAA": - # Add .domain.tdl to the name entry - record["name"] = "{}.{}".format(record["name"], domain) - flatten_dns_conf.append(record) + dns_conf = [record for record in records_for_category for records_for_category in dns_conf.values()] + + # FIXME Lexicon does not support CAA records + # See https://github.com/AnalogJ/lexicon/issues/282 and https://github.com/AnalogJ/lexicon/pull/371 + # They say it's trivial to implement it! + # And yet, it is still not done/merged + dns_conf = [record for record in dns_conf if record["type"] != "CAA"] + + # We need absolute names? FIXME: should we add a trailing dot needed here ? + for record in dns_conf: + record["name"] = f"{record['name']}.{domain}" # Construct the base data structure to use lexicon's API. base_config = { - "provider_name": registrar_setting["name"], - "domain": domain, # domain name + "provider_name": registrar_settings["name"], + "domain": domain, + registrar_settings["name"]: registrar_settings["options"] } - base_config[registrar_setting["name"]] = registrar_setting["options"] - - # Get types present in the generated records - types = set() - - for record in flatten_dns_conf: - types.add(record["type"]) operation_logger.start() # Fetch all types present in the generated records - distant_records = {} + current_remote_records = {} + + # Get unique types present in the generated records + types = {record["type"] for record in dns_conf} for key in types: - record_config = { + fetch_records_for_type = { "action": "list", "type": key, } - final_lexicon = ( - ConfigResolver() + query = ( + LexiconConfigResolver() .with_dict(dict_object=base_config) - .with_dict(dict_object=record_config) + .with_dict(dict_object=fetch_records_for_type) ) - # print('final_lexicon:', final_lexicon); - client = Client(final_lexicon) - distant_records[key] = client.execute() + current_remote_records[key] = LexiconClient(query).execute() for key in types: - for distant_record in distant_records[key]: - logger.debug(f"distant_record: {distant_record}") - for local_record in flatten_dns_conf: + for current_remote_record in current_remote_records[key]: + logger.debug(f"current_remote_record: {current_remote_record}") + for local_record in dns_conf: print("local_record:", local_record) # Push the records - for record in flatten_dns_conf: - # For each record, first check if one record exists for the same (type, name) couple - it_exists = False - # TODO do not push if local and distant records are exactly the same ? - # is_the_same_record = False + for record in dns_conf: - for distant_record in distant_records[record["type"]]: - if ( - distant_record["type"] == record["type"] - and distant_record["name"] == record["name"] - ): - it_exists = True - # see previous TODO - # if distant_record["ttl"] = ... and distant_record["name"] ... - # is_the_same_record = True + # For each record, first check if one record exists for the same (type, name) couple + # TODO do not push if local and distant records are exactly the same ? + type_and_name = (record["type"], record["name"]) + already_exists = any((r["type"], r["name"]) == type_and_name + for r in current_remote_records[record["type"]]) # Finally, push the new record or update the existing one - record_config = { - "action": "update" - if it_exists - else "create", # create, list, update, delete - "type": record[ - "type" - ], # specify a type for record filtering, case sensitive in some cases. + record_to_push = { + "action": "update" if already_exists else "create" + "type": record["type"] "name": record["name"], "content": record["value"], # FIXME Removed TTL, because it doesn't work with Gandi. @@ -566,14 +548,15 @@ def domain_registrar_push(operation_logger, domain): # But I think there is another issue with Gandi. Or I'm misusing the API... # "ttl": record["ttl"], } - final_lexicon = ( + + print("pushed_record:", record_to_push, "→", end=" ") + + query = ( ConfigResolver() .with_dict(dict_object=base_config) - .with_dict(dict_object=record_config) + .with_dict(dict_object=record_to_push) ) - client = Client(final_lexicon) - print("pushed_record:", record_config, "→", end=" ") - results = client.execute() + results = LexiconClient(query).execute() print("results:", results) # print("Failed" if results == False else "Ok") From 3264c4fc2d5030d1ea3c64184a35b80d876677be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M?= Date: Tue, 31 Aug 2021 15:58:42 +0000 Subject: [PATCH 2843/3170] Translated using Weblate (Galician) Currently translated at 90.9% (585 of 643 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/gl/ --- locales/gl.json | 43 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/locales/gl.json b/locales/gl.json index 35a1b7390..e752716cd 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -543,5 +543,46 @@ "regenconf_now_managed_by_yunohost": "O ficheiro de configuración '{conf}' agora está xestionado por YunoHost (categoría {category}).", "regenconf_file_updated": "Actualizado o ficheiro de configuración '{conf}'", "regenconf_file_removed": "Eliminado o ficheiro de configuración '{conf}'", - "regenconf_file_remove_failed": "Non se puido eliminar o ficheiro de configuración '{conf}'" + "regenconf_file_remove_failed": "Non se puido eliminar o ficheiro de configuración '{conf}'", + "service_enable_failed": "Non se puido facer que o servizo '{service}' se inicie automáticamente no inicio.\n\nRexistros recentes do servizo: {logs}", + "service_disabled": "O servizo '{service}' xa non vai volver a ser iniciado ao inicio do sistema.", + "service_disable_failed": "Non se puido iniciar o servizo '{servizo}' ao inicio.\n\nRexistro recente do servizo: {logs}", + "service_description_yunohost-firewall": "Xestiona, abre e pecha a conexións dos portos aos servizos", + "service_description_yunohost-api": "Xestiona as interaccións entre a interface web de YunoHost e o sistema", + "service_description_ssh": "Permíteche conectar de xeito remoto co teu servidor a través dun terminal (protocolo SSH)", + "service_description_slapd": "Almacena usuarias, dominios e info relacionada", + "service_description_rspamd": "Filtra spam e outras características relacionadas co email", + "service_description_redis-server": "Unha base de datos especial utilizada para o acceso rápido a datos, cola de tarefas e comunicación entre programas", + "service_description_postfix": "Utilizado para enviar e recibir emails", + "service_description_php7.3-fpm": "Executa aplicacións escritas en PHP con NGINX", + "service_description_nginx": "Serve ou proporciona acceso a tódolos sitios web hospedados no teu servidor", + "service_description_mysql": "Almacena datos da app (base de datos SQL)", + "service_description_metronome": "Xestiona as contas de mensaxería instantánea XMPP", + "service_description_fail2ban": "Protexe contra ataques de forza bruta e outro tipo de ataques desde internet", + "service_description_dovecot": "Permite aos clientes de email acceder/obter o correo (vía IMAP e POP3)", + "service_description_dnsmasq": "Xestiona a resolución de nomes de dominio (DNS)", + "service_description_yunomdns": "Permíteche chegar ao teu servidor utilizando 'yunohost.local' na túa rede local", + "service_cmd_exec_failed": "Non se puido executar o comando '{command}'", + "service_already_stopped": "O servizo '{sevice}' xa está detido", + "service_already_started": "O servizo '{service}' xa se está a executar", + "service_added": "Foi engadido o servizo '{service}'", + "service_add_failed": "Non se puido engadir o servizo '{service}'", + "server_reboot_confirm": "Queres reiniciar o servidor inmediatamente? [{answers}]", + "server_reboot": "Vaise reiniciar o servidor", + "server_shutdown_confirm": "Queres apagar o servidor inmediatamente? [{answers}]", + "server_shutdown": "Vaise apagar o servidor", + "root_password_replaced_by_admin_password": "O contrasinal root foi substituído polo teu contrasinal de administración.", + "root_password_desynchronized": "Mudou o contrasinal de administración, pero YunoHost non puido transferir este cambio ao contrasinal root!", + "restore_system_part_failed": "Non se restableceu a parte do sistema '{part}'", + "restore_running_hooks": "Executando os ganchos do restablecemento…", + "restore_running_app_script": "Restablecendo a app '{app}'…", + "restore_removing_tmp_dir_failed": "Non se puido eliminar o directorio temporal antigo", + "restore_nothings_done": "Nada foi restablecido", + "restore_not_enough_disk_space": "Non hai espazo abondo (espazo: {free_space.d} B, espazo necesario: {needed_space} B, marxe de seguridade: {margin:d} B)", + "restore_may_be_not_enough_disk_space": "O teu sistema semella que non ten espazo abondo (libre: {free_space:d} B, espazo necesario: {needed_space:d} B, marxe de seguridade {margin:d} B)", + "restore_hook_unavailable": "O script de restablecemento para '{part}' non está dispoñible no teu sistema nin no arquivo", + "invalid_password": "Contrasinal non válido", + "ldap_server_is_down_restart_it": "O servidor LDAP está caído, intenta reinicialo...", + "ldap_server_down": "Non se chegou ao servidor LDAP", + "global_settings_setting_security_experimental_enabled": "Activar características de seguridade experimentais (non actives isto se non sabes o que estás a facer!)" } From 2b2335a08c2241a4cec2c531de08538052f9756b Mon Sep 17 00:00:00 2001 From: Parviz Homayun Date: Tue, 31 Aug 2021 18:16:51 +0000 Subject: [PATCH 2844/3170] Translated using Weblate (Persian) Currently translated at 30.7% (198 of 643 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fa/ --- locales/fa.json | 181 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 180 insertions(+), 1 deletion(-) diff --git a/locales/fa.json b/locales/fa.json index 5a2b61bc1..48e18a78e 100644 --- a/locales/fa.json +++ b/locales/fa.json @@ -17,5 +17,184 @@ "admin_password_change_failed": "تغییر رمز امکان پذیر نیست", "admin_password": "رمز عبور مدیریت", "additional_urls_already_removed": "نشانی اینترنتی اضافی '{url}' قبلاً در نشانی اینترنتی اضافی برای اجازه '{permission}'حذف شده است", - "additional_urls_already_added": "نشانی اینترنتی اضافی '{url}' قبلاً در نشانی اینترنتی اضافی برای اجازه '{permission}' اضافه شده است" + "additional_urls_already_added": "نشانی اینترنتی اضافی '{url}' قبلاً در نشانی اینترنتی اضافی برای اجازه '{permission}' اضافه شده است", + "diagnosis_diskusage_low": "‏ذخیره سازی {mountpoint} (روی دستگاه {device}) فقط {free} ({free_percent}%) فضا باقی مانده است (از {total}). مراقب باشید.", + "diagnosis_diskusage_verylow": "‏ذخیره سازی {mountpoint} (روی دستگاه {device}) فقط {free} ({free_percent}%) فضا باقی مانده است (از {total}). شما واقعاً باید پاکسازی فضای ذخیره ساز را در نظر بگیرید!", + "diagnosis_services_bad_status_tip": "می توانید سعی کنید سرویس را راه اندازی مجدد کنید، و اگر کار نمی کند ، نگاهی داشته باشید بهسرویس در webadmin ثبت می شود (از خط فرمان ، می توانید این کار را انجام دهید با yunohost service restart {service} و yunohost service log {service}).", + "diagnosis_services_bad_status": "سرویس {service} {status} است :(", + "diagnosis_services_conf_broken": "پیکربندی سرویس {service} خراب است!", + "diagnosis_services_running": "سرویس {service} در حال اجرا است!", + "diagnosis_domain_expires_in": "{domain} در {days} روز منقضی می شود.", + "diagnosis_domain_expiration_error": "برخی از دامنه ها به زودی منقضی می شوند!", + "diagnosis_domain_expiration_warning": "برخی از دامنه ها به زودی منقضی می شوند!", + "diagnosis_domain_expiration_success": "دامنه های شما ثبت شده است و به این زودی منقضی نمی شود.", + "diagnosis_domain_expiration_not_found_details": "به نظر می رسد اطلاعات WHOIS برای دامنه {domain} حاوی اطلاعات مربوط به تاریخ انقضا نیست؟", + "diagnosis_domain_not_found_details": "دامنه {domain} در پایگاه داده WHOIS وجود ندارد یا منقضی شده است!", + "diagnosis_domain_expiration_not_found": "بررسی تاریخ انقضا برخی از دامنه ها امکان پذیر نیست", + "diagnosis_dns_specialusedomain": "دامنه {domain} بر اساس یک دامنه سطح بالا (TLD) مخصوص استفاده است و بنابراین انتظار نمی رود که دارای سوابق DNS واقعی باشد.", + "diagnosis_dns_try_dyndns_update_force": "پیکربندی DNS این دامنه باید به طور خودکار توسط YunoHost مدیریت شود. اگر اینطور نیست ، می توانید سعی کنید به زور یک به روز رسانی را با استفاده از yunohost dyndns update --force.", + "diagnosis_dns_point_to_doc": "لطفاً اسناد را در https://yunohost.org/dns_config برسی و مطالعه کنید، اگر در مورد پیکربندی سوابق DNS به کمک نیاز دارید.", + "diagnosis_dns_discrepancy": "به نظر می رسد پرونده DNS زیر از پیکربندی توصیه شده پیروی نمی کند:
نوع: {type}
نام: {name}
ارزش فعلی: {current}
مقدار مورد انتظار: {value}", + "diagnosis_dns_missing_record": "با توجه به پیکربندی DNS توصیه شده ، باید یک رکورد DNS با اطلاعات زیر اضافه کنید.
نوع: {type}
نام: {name}
ارزش: {value}", + "diagnosis_dns_bad_conf": "برخی از سوابق DNS برای دامنه {domain} (دسته {category}) وجود ندارد یا نادرست است", + "diagnosis_dns_good_conf": "سوابق DNS برای دامنه {domain} (دسته {category}) به درستی پیکربندی شده است", + "diagnosis_ip_weird_resolvconf_details": "پرونده /etc/resolv.conf باید یک پیوند همراه برای /etc/resolvconf/run/resolv.conf خود اشاره می کند به 127.0.0.1 (dnsmasq). اگر می خواهید راه حل های DNS را به صورت دستی پیکربندی کنید ، لطفاً ویرایش کنید /etc/resolv.dnsmasq.conf.", + "diagnosis_ip_weird_resolvconf": "اینطور که پیداست تفکیک پذیری DNS کار می کند ، اما به نظر می رسد از سفارشی استفاده می کنید /etc/resolv.conf.", + "diagnosis_ip_broken_resolvconf": "به نظر می رسد تفکیک پذیری نام دامنه در سرور شما شکسته شده است ، که به نظر می رسد مربوط به /etc/resolv.conf و اشاره نکردن به 127.0.0.1 میباشد.", + "diagnosis_ip_broken_dnsresolution": "به نظر می رسد تفکیک پذیری نام دامنه به دلایلی خراب شده است... آیا فایروال درخواست های DNS را مسدود می کند؟", + "diagnosis_ip_dnsresolution_working": "تفکیک پذیری نام دامنه کار می کند!", + "diagnosis_ip_not_connected_at_all": "به نظر می رسد سرور اصلا به اینترنت متصل نیست !؟", + "diagnosis_ip_local": "IP محلی: {local}", + "diagnosis_ip_global": "IP جهانی: {global}", + "diagnosis_ip_no_ipv6_tip": "داشتن یک IPv6 فعال برای کار سرور شما اجباری نیست ، اما برای سلامت اینترنت به طور کلی بهتر است. IPv6 معمولاً باید در صورت موجود بودن توسط سیستم یا ارائه دهنده اینترنت شما به طور خودکار پیکربندی شود. در غیر این صورت ، ممکن است لازم باشد چند مورد را به صورت دستی پیکربندی کنید ، همانطور که در اسناد اینجا توضیح داده شده است: https://yunohost.org/#/ipv6.اگر نمی توانید IPv6 را فعال کنید یا اگر برای شما بسیار فنی به نظر می رسد ، می توانید با خیال راحت این هشدار را نادیده بگیرید.", + "diagnosis_ip_no_ipv6": "سرور IPv6 کار نمی کند.", + "diagnosis_ip_connected_ipv6": "سرور از طریق IPv6 به اینترنت متصل است!", + "diagnosis_ip_no_ipv4": "سرور IPv4 کار نمی کند.", + "diagnosis_ip_connected_ipv4": "سرور از طریق IPv4 به اینترنت متصل است!", + "diagnosis_no_cache": "هنوز هیچ حافظه نهانی معاینه و عیب یابی برای دسته '{category}' وجود ندارد", + "diagnosis_failed": "نتیجه معاینه و عیب یابی برای دسته '{category}' واکشی نشد: {error}", + "diagnosis_everything_ok": "همه چیز برای {category} خوب به نظر می رسد!", + "diagnosis_found_warnings": "مورد (های) {warnings} یافت شده که می تواند دسته {category} را بهبود بخشد.", + "diagnosis_found_errors_and_warnings": "{errors} مسائل مهم (و {warnings} هشدارها) مربوط به {category} پیدا شد!", + "diagnosis_found_errors": "{errors} مشکلات مهم مربوط به {category} پیدا شد!", + "diagnosis_ignored_issues": "(+ {nb_ignored} مسئله (ها) نادیده گرفته شده)", + "diagnosis_cant_run_because_of_dep": "در حالی که مشکلات مهمی در ارتباط با {dep} وجود دارد ، نمی توان عیب یابی را برای {category} اجرا کرد.", + "diagnosis_cache_still_valid": "(حافظه پنهان هنوز برای عیب یابی {category} معتبر است. هنوز دوباره تشخیص داده نمی شود!)", + "diagnosis_failed_for_category": "عیب یابی برای دسته '{category}' ناموفق بود: {error}", + "diagnosis_display_tip": "برای مشاهده مسائل پیدا شده ، می توانید به بخش تشخیص webadmin بروید یا از خط فرمان 'yunohost diagnosis show --issues --human-readable' را اجرا کنید.", + "diagnosis_package_installed_from_sury_details": "برخی از بسته ها ناخواسته از مخزن شخص ثالث به نام Sury نصب شده اند. تیم YunoHost استراتژی مدیریت این بسته ها را بهبود بخشیده ، اما انتظار می رود برخی از تنظیماتی که برنامه های PHP7.3 را در حالی که هنوز بر روی Stretch نصب شده اند نصب کرده اند ، ناسازگاری های باقی مانده ای داشته باشند. برای رفع این وضعیت ، باید دستور زیر را اجرا کنید: {cmd_to_fix}", + "diagnosis_package_installed_from_sury": "برخی از بسته های سیستمی باید کاهش یابد", + "diagnosis_backports_in_sources_list": "به نظر می رسد apt (مدیریت بسته) برای استفاده از مخزن پشتیبان پیکربندی شده است. مگر اینکه واقعاً بدانید چه کار می کنید ، ما به شدت از نصب بسته های پشتیبان خودداری می کنیم، زیرا به احتمال زیاد باعث ایجاد ناپایداری یا تداخل در سیستم شما می شود.", + "diagnosis_basesystem_ynh_inconsistent_versions": "شما نسخه های ناسازگار از بسته های YunoHost را اجرا می کنید... به احتمال زیاد به دلیل ارتقاء ناموفق یا جزئی است.", + "diagnosis_basesystem_ynh_main_version": "سرور نسخه YunoHost {main_version} ({repo}) را اجرا می کند", + "diagnosis_basesystem_ynh_single_version": "{package} نسخه: {version} ({repo})", + "diagnosis_basesystem_kernel": "سرور نسخه {kernel_version} هسته لینوکس را اجرا می کند", + "diagnosis_basesystem_host": "سرور نسخه {debian_version} دبیان را اجرا می کند", + "diagnosis_basesystem_hardware_model": "مدل سرور {model} میباشد", + "diagnosis_basesystem_hardware": "معماری سخت افزاری سرور {virt} {arch} است", + "custom_app_url_required": "برای ارتقاء سفارشی برنامه {app} خود باید نشانی اینترنتی ارائه دهید", + "confirm_app_install_thirdparty": "خطرناک! این برنامه بخشی از فهرست برنامه YunoHost نیست. نصب برنامه های شخص ثالث ممکن است یکپارچگی و امنیت سیستم شما را به خطر بیندازد. احتمالاً نباید آن را نصب کنید مگر اینکه بدانید در حال انجام چه کاری هستید. اگر این برنامه کار نکرد یا سیستم شما را خراب کرد ، هیچ پشتیبانی ارائه نخواهدشد... به هر حال اگر مایل به پذیرش این خطر هستید ، '{answers}' را تایپ کنید", + "confirm_app_install_danger": "خطرناک! این برنامه هنوز آزمایشی است (اگر صراحتاً کار نکند)! احتمالاً نباید آن را نصب کنید مگر اینکه بدانید در حال انجام چه کاری هستید. اگر این برنامه کار نکرد یا سیستم شما را خراب کرد، هیچ پشتیبانی ارائه نخواهد شد... اگر به هر حال مایل به پذیرش این خطر هستید ، '{answers}' را تایپ کنید", + "confirm_app_install_warning": "هشدار: این برنامه ممکن است کار کند ، اما در YunoHost یکپارچه نشده است. برخی از ویژگی ها مانند ورود به سیستم و پشتیبان گیری/بازیابی ممکن است در دسترس نباشد. به هر حال نصب شود؟ [{answers}] ", + "certmanager_unable_to_parse_self_CA_name": "نتوانست نام مرجع خودامضائی را تجزیه و تحلیل کند (فایل: {file})", + "certmanager_self_ca_conf_file_not_found": "فایل پیکربندی برای اجازه خود امضائی پیدا نشد (فایل: {file})", + "certmanager_no_cert_file": "فایل گواهینامه برای دامنه {domain} خوانده نشد (فایل: {file})", + "certmanager_hit_rate_limit": "اخیراً تعداد زیادی گواهی برای این مجموعه دقیق از دامنه ها {domain} صادر شده است. لطفاً بعداً دوباره امتحان کنید. برای جزئیات بیشتر به https://letsencrypt.org/docs/rate-limits/ مراجعه کنید", + "certmanager_warning_subdomain_dns_record": "آدرس زیر دامنه '{subdomain}' به آدرس IP مشابه '{domain}' تبدیل نمی شود. تا زمانی که این مشکل را برطرف نکنید و گواهی را دوباره ایجاد نکنید ، برخی از ویژگی ها در دسترس نخواهند بود.", + "certmanager_domain_http_not_working": "به نظر می رسد دامنه {domain} از طریق HTTP قابل دسترسی نیست. لطفاً برای اطلاعات بیشتر ، دسته \"وب\" را در عیب یابی بررسی کنید. (اگر می دانید چه کار می کنید ، از '--no-checks' برای خاموش کردن این چک ها استفاده کنید.)", + "certmanager_domain_dns_ip_differs_from_public_ip": "سوابق DNS برای دامنه '{domain}' با IP این سرور متفاوت است. لطفاً برای اطلاعات بیشتر ، دسته 'DNS records' (پایه) را در عیب یابی بررسی کنید. اگر اخیراً رکورد A خود را تغییر داده اید ، لطفاً منتظر انتشار آن باشید (برخی از چکرهای انتشار DNS بصورت آنلاین در دسترس هستند). (اگر می دانید چه کار می کنید ، از '--no-checks' برای خاموش کردن این چک ها استفاده کنید.)", + "certmanager_domain_cert_not_selfsigned": "گواهی دامنه {domain} خود امضا نشده است. آیا مطمئن هستید که می خواهید آن را جایگزین کنید؟ (برای این کار از '--force' استفاده کنید.)", + "certmanager_domain_not_diagnosed_yet": "هنوز هیچ نتیجه تشخیصی و عیب یابی دامنه {domain} وجود ندارد. لطفاً در بخش عیب یابی ، دسته های 'DNS records' و 'Web'مجدداً عیب یابی را اجرا کنید تا بررسی شود که آیا دامنه ای برای گواهی اجازه رمزنگاری آماده است. (یا اگر می دانید چه کار می کنید ، از '--no-checks' برای خاموش کردن این بررسی ها استفاده کنید.)", + "certmanager_certificate_fetching_or_enabling_failed": "تلاش برای استفاده از گواهینامه جدید برای {domain} جواب نداد...", + "certmanager_cert_signing_failed": "گواهی جدید امضا نشده است", + "certmanager_cert_renew_success": "گواهی اجازه رمزنگاری برای دامنه '{domain}' تمدید شد", + "certmanager_cert_install_success_selfsigned": "گواهی خود امضا شده اکنون برای دامنه '{domain}' نصب شده است", + "certmanager_cert_install_success": "هم اینک گواهی اجازه رمزگذاری برای دامنه '{domain}' نصب شده است", + "certmanager_cannot_read_cert": "هنگام باز کردن گواهینامه فعلی مشکلی پیش آمده است برای دامنه {domain} (فایل: {file}) ، علّت: {reason}", + "certmanager_attempt_to_replace_valid_cert": "شما در حال تلاش برای بازنویسی یک گواهی خوب و معتبر برای دامنه {domain} هستید! (استفاده از --force برای bypass)", + "certmanager_attempt_to_renew_valid_cert": "گواهی دامنه '{domain}' در حال انقضا نیست! (اگر می دانید چه کار می کنید می توانید از --force استفاده کنید)", + "certmanager_attempt_to_renew_nonLE_cert": "گواهی دامنه '{domain}' توسط Let's Encrypt صادر نشده است. به طور خودکار تمدید نمی شود!", + "certmanager_acme_not_configured_for_domain": "در حال حاضر نمی توان چالش ACME را برای {domain} اجرا کرد زیرا nginx conf آن فاقد قطعه کد مربوطه است... لطفاً مطمئن شوید که پیکربندی nginx شما به روز است با استفاده از دستور `yunohost tools regen-conf nginx --dry-run --with-diff`.", + "backup_with_no_restore_script_for_app": "{app} فاقد اسکریپت بازگردانی است ، نمی توانید پشتیبان گیری این برنامه را به طور خودکار بازیابی کنید.", + "backup_with_no_backup_script_for_app": "برنامه '{app}' فاقد اسکریپت پشتیبان است. نادیده گرفتن.", + "backup_unable_to_organize_files": "نمی توان از روش سریع برای سازماندهی فایل ها در بایگانی استفاده کرد", + "backup_system_part_failed": "از بخش سیستم '{part}' پشتیبان گیری نشد", + "backup_running_hooks": "درحال اجرای قلاب پشتیبان گیری...", + "backup_permission": "مجوز پشتیبان گیری برای {app}", + "backup_output_symlink_dir_broken": "فهرست بایگانی شما '{path}' یک پیوند symlink خراب است. شاید فراموش کرده اید که مجدداً محل ذخیره سازی که به آن اشاره می کند را دوباره نصب یا وصل کنید.", + "backup_output_directory_required": "شما باید یک پوشه خروجی برای نسخه پشتیبان تهیه کنید", + "backup_output_directory_not_empty": "شما باید یک دایرکتوری خروجی خالی انتخاب کنید", + "backup_output_directory_forbidden": "دایرکتوری خروجی دیگری را انتخاب کنید. پشتیبان گیری نمی تواند در /bin، /boot، /dev ، /etc ، /lib ، /root ، /run ، /sbin ، /sys ، /usr ، /var یا /home/yunohost.backup/archives ایجاد شود", + "backup_nothings_done": "چیزی برای ذخیره کردن وجود ندارد", + "backup_no_uncompress_archive_dir": "چنین فهرست بایگانی فشرده نشده ایی وجود ندارد", + "backup_mount_archive_for_restore": "در حال آماده سازی بایگانی برای بازگردانی...", + "backup_method_tar_finished": "بایگانی پشتیبان TAR ایجاد شد", + "backup_method_custom_finished": "روش پشتیبان گیری سفارشی '{method}' به پایان رسید", + "backup_method_copy_finished": "نسخه پشتیبان نهایی شد", + "backup_hook_unknown": "قلاب پشتیبان '{hook}' ناشناخته است", + "backup_deleted": "نسخه پشتیبان حذف شد", + "backup_delete_error": "'{path}' حذف نشد", + "backup_custom_mount_error": "روش پشتیبان گیری سفارشی نمی تواند از مرحله 'mount' عبور کند", + "backup_custom_backup_error": "روش پشتیبان گیری سفارشی نمی تواند مرحله 'backup' را پشت سر بگذارد", + "backup_csv_creation_failed": "فایل CSV مورد نیاز برای بازیابی ایجاد نشد", + "backup_csv_addition_failed": "فایلهای پشتیبان به فایل CSV اضافه نشد", + "backup_creation_failed": "نسخه پشتیبان بایگانی ایجاد نشد", + "backup_create_size_estimation": "بایگانی حاوی حدود {size} داده است.", + "backup_created": "نسخه پشتیبان ایجاد شد", + "backup_couldnt_bind": "نمی توان {src} را به {dest} متصل کرد.", + "backup_copying_to_organize_the_archive": "در حال کپی {size} مگابایت برای سازماندهی بایگانی", + "backup_cleaning_failed": "پوشه موقت پشتیبان گیری پاکسازی نشد", + "backup_cant_mount_uncompress_archive": "بایگانی فشرده سازی نشده را نمی توان به عنوان حفاظت از نوشتن مستقر کرد", + "backup_ask_for_copying_if_needed": "آیا می خواهید پشتیبان گیری را با استفاده از {size} مگابایت به طور موقت انجام دهید؟ (این روش استفاده می شود زیرا برخی از پرونده ها با استفاده از روش کارآمدتری تهیه نمی شوند.)", + "backup_archive_writing_error": "فایل های '{source}' (که در بایگانی '{dest}' نامگذاری شده اند) برای پشتیبان گیری به بایگانی فشرده '{archive}' اضافه نشد", + "backup_archive_system_part_not_available": "بخش سیستم '{part}' در این نسخه پشتیبان در دسترس نیست", + "backup_archive_corrupted": "به نظر می رسد بایگانی پشتیبان '{archive}' خراب است: {error}", + "backup_archive_cant_retrieve_info_json": "اطلاعات مربوط به بایگانی '{archive}' بارگیری نشد... info.json بازیابی نمی شود (یا json معتبری نیست).", + "backup_archive_open_failed": "بایگانی پشتیبان باز نشد", + "backup_archive_name_unknown": "بایگانی پشتیبان محلی ناشناخته با نام '{name}'", + "backup_archive_name_exists": "بایگانی پشتیبان با این نام در حال حاضر وجود دارد.", + "backup_archive_broken_link": "دسترسی به بایگانی پشتیبان امکان پذیر نیست (پیوند خراب به {path})", + "backup_archive_app_not_found": "در بایگانی پشتیبان {app} پیدا نشد", + "backup_applying_method_tar": "ایجاد آرشیو پشتیبان TAR...", + "backup_applying_method_custom": "فراخوانی روش پشتیبان گیری سفارشی '{method}'...", + "backup_applying_method_copy": "در حال کپی تمام فایل ها برای پشتیبان گیری...", + "backup_app_failed": "{app} پشتیبان گیری نشد", + "backup_actually_backuping": "ایجاد آرشیو پشتیبان از پرونده های جمع آوری شده...", + "backup_abstract_method": "این روش پشتیبان گیری هنوز اجرا نشده است", + "ask_password": "رمز عبور", + "ask_new_path": "مسیر جدید", + "ask_new_domain": "دامنه جدید", + "ask_new_admin_password": "رمز جدید مدیریت", + "ask_main_domain": "دامنه اصلی", + "ask_lastname": "نام خانوادگی", + "ask_firstname": "نام کوچک", + "ask_user_domain": "دامنه ای که برای آدرس ایمیل کاربر و حساب XMPP استفاده می شود", + "apps_catalog_update_success": "کاتالوگ برنامه به روز شد!", + "apps_catalog_obsolete_cache": "حافظه پنهان کاتالوگ برنامه خالی یا منسوخ شده است.", + "apps_catalog_failed_to_download": "بارگیری کاتالوگ برنامه {apps_catalog} امکان پذیر نیست: {error}", + "apps_catalog_updating": "در حال به روز رسانی کاتالوگ برنامه…", + "apps_catalog_init_success": "سیستم کاتالوگ برنامه راه اندازی اولیه شد!", + "apps_already_up_to_date": "همه برنامه ها در حال حاضر به روز هستند", + "app_packaging_format_not_supported": "این برنامه قابل نصب نیست زیرا قالب بسته بندی آن توسط نسخه YunoHost شما پشتیبانی نمی شود. احتمالاً باید ارتقاء سیستم خود را در نظر بگیرید.", + "app_upgraded": "{app} ارتقا یافت", + "app_upgrade_some_app_failed": "برخی از برنامه ها را نمی توان ارتقا داد", + "app_upgrade_script_failed": "خطایی در داخل اسکریپت ارتقاء برنامه رخ داده است", + "app_upgrade_failed": "{app} ارتقاء نیافت: {error}", + "app_upgrade_app_name": "در حال ارتقاء {app}...", + "app_upgrade_several_apps": "برنامه های زیر ارتقا می یابند: {apps}", + "app_unsupported_remote_type": "نوع راه دور پشتیبانی نشده برای برنامه استفاده می شود", + "app_unknown": "برنامه ناشناخته", + "app_start_restore": "درحال بازیابی {app}...", + "app_start_backup": "در حال جمع آوری فایل ها برای پشتیبان گیری {app}...", + "app_start_remove": "در حال حذف {app}...", + "app_start_install": "در حال نصب {app}...", + "app_sources_fetch_failed": "نمی توان فایل های منبع را واکشی کرد ، آیا URL درست است؟", + "app_restore_script_failed": "خطایی در داخل اسکریپت بازیابی برنامه رخ داده است", + "app_restore_failed": "{app} بازیابی نشد: {error}", + "app_remove_after_failed_install": "حذف برنامه در پی شکست نصب...", + "app_requirements_unmeet": "شرایط مورد نیاز برای {app} برآورده نمی شود ، بسته {pkgname} ({version}) باید {spec} باشد", + "app_requirements_checking": "در حال بررسی بسته های مورد نیاز برای {app}...", + "app_removed": "{app} حذف نصب شد", + "app_not_properly_removed": "{app} به درستی حذف نشده است", + "app_not_installed": "{app} در لیست برنامه های نصب شده یافت نشد: {all_apps}", + "app_not_correctly_installed": "به نظر می رسد {app} به اشتباه نصب شده است", + "app_not_upgraded": "برنامه '{failed_app}' ارتقا پیدا نکرد و در نتیجه ارتقا برنامه های زیر لغو شد: {apps}", + "app_manifest_install_ask_is_public": "آیا این برنامه باید در معرض دید بازدیدکنندگان ناشناس قرار گیرد؟", + "app_manifest_install_ask_admin": "برای این برنامه یک کاربر سرپرست انتخاب کنید", + "app_manifest_install_ask_password": "گذرواژه مدیریتی را برای این برنامه انتخاب کنید", + "app_manifest_install_ask_path": "مسیر URL (بعد از دامنه) را انتخاب کنید که این برنامه باید در آن نصب شود", + "app_manifest_install_ask_domain": "دامنه ای را انتخاب کنید که این برنامه باید در آن نصب شود", + "app_manifest_invalid": "مشکلی در مانیفست برنامه وجود دارد: {error}", + "app_location_unavailable": "این نشانی وب یا در دسترس نیست یا با برنامه (هایی) که قبلاً نصب شده در تعارض است:\n{apps}", + "app_label_deprecated": "این دستور منسوخ شده است! لطفاً برای مدیریت برچسب برنامه از فرمان جدید'yunohost به روز رسانی مجوز کاربر' استفاده کنید.", + "app_make_default_location_already_used": "نمی توان '{app}' را برنامه پیش فرض در دامنه قرار داد ، '{domain}' قبلاً توسط '{other_app}' استفاده می شود", + "app_install_script_failed": "خطایی در درون اسکریپت نصب برنامه رخ داده است", + "app_install_failed": "نصب {app} امکان پذیر نیست: {error}", + "app_install_files_invalid": "این فایل ها قابل نصب نیستند", + "app_id_invalid": "شناسه برنامه نامعتبر است", + "app_full_domain_unavailable": "متأسفیم ، این برنامه باید در دامنه خود نصب شود ، اما سایر برنامه ها قبلاً در دامنه '{domain}' نصب شده اند.شما به جای آن می توانید از یک زیر دامنه اختصاص داده شده به این برنامه استفاده کنید.", + "app_extraction_failed": "فایل های نصبی استخراج نشد", + "app_change_url_success": "{app} URL اکنون {domain} {path} است", + "app_change_url_no_script": "برنامه '{app_name}' هنوز از تغییر URL پشتیبانی نمی کند. شاید باید آن را ارتقا دهید.", + "app_change_url_identical_domains": "دامنه /url_path قدیمی و جدیدیکسان هستند ('{domain}{path}') ، کاری برای انجام دادن نیست." } From 4ee759855af1d845e9aa88fa0a36b119fc021f66 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 31 Aug 2021 21:42:27 +0200 Subject: [PATCH 2845/3170] Implement global settings for https redirect --- data/hooks/conf_regen/15-nginx | 1 + data/templates/nginx/server.tpl.conf | 10 ++++++---- locales/en.json | 1 + src/yunohost/settings.py | 8 ++++++++ 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/data/hooks/conf_regen/15-nginx b/data/hooks/conf_regen/15-nginx index a2d8f1259..040ed090d 100755 --- a/data/hooks/conf_regen/15-nginx +++ b/data/hooks/conf_regen/15-nginx @@ -60,6 +60,7 @@ do_pre_regen() { main_domain=$(cat /etc/yunohost/current_host) # Support different strategy for security configurations + export redirect_to_https="$(yunohost settings get 'security.nginx.redirect_to_https')" export compatibility="$(yunohost settings get 'security.nginx.compatibility')" export experimental="$(yunohost settings get 'security.experimental.enabled')" ynh_render_template "security.conf.inc" "${nginx_conf_dir}/security.conf.inc" diff --git a/data/templates/nginx/server.tpl.conf b/data/templates/nginx/server.tpl.conf index 8bd689a92..7133dfba2 100644 --- a/data/templates/nginx/server.tpl.conf +++ b/data/templates/nginx/server.tpl.conf @@ -14,10 +14,6 @@ server { include /etc/nginx/conf.d/{{ domain }}.d/*.conf; - location /yunohost { - return 301 https://$http_host$request_uri; - } - location ^~ '/.well-known/ynh-diagnosis/' { alias /tmp/.well-known/ynh-diagnosis/; } @@ -26,6 +22,12 @@ server { alias /var/www/.well-known/{{ domain }}/autoconfig/mail/; } + {% if redirect_to_https != "False" %} + location / { + return 301 https://$http_host$request_uri; + } + {% endif %} + access_log /var/log/nginx/{{ domain }}-access.log; error_log /var/log/nginx/{{ domain }}-error.log; } diff --git a/locales/en.json b/locales/en.json index 4c9f3e7fe..cde7c5e66 100644 --- a/locales/en.json +++ b/locales/en.json @@ -334,6 +334,7 @@ "global_settings_key_doesnt_exists": "The key '{settings_key}' does not exist in the global settings, you can see all the available keys by running 'yunohost settings list'", "global_settings_reset_success": "Previous settings now backed up to {path}", "global_settings_setting_pop3_enabled": "Enable the POP3 protocol for the mail server", + "global_settings_setting_security_nginx_redirect_to_https": "Redirect HTTP requests to HTTPs by default (DO NOT TURN OFF unless you really know what you're doing!)", "global_settings_setting_security_nginx_compatibility": "Compatibility vs. security tradeoff for the web server NGINX. Affects the ciphers (and other security-related aspects)", "global_settings_setting_security_password_admin_strength": "Admin password strength", "global_settings_setting_security_password_user_strength": "User password strength", diff --git a/src/yunohost/settings.py b/src/yunohost/settings.py index fe072cddb..475ac70d1 100644 --- a/src/yunohost/settings.py +++ b/src/yunohost/settings.py @@ -76,6 +76,13 @@ DEFAULTS = OrderedDict( "security.ssh.port", {"type": "int", "default": 22}, ), + ( + "security.nginx.redirect_to_https", + { + "type": "bool", + "default": True, + }, + ), ( "security.nginx.compatibility", { @@ -392,6 +399,7 @@ def trigger_post_change_hook(setting_name, old_value, new_value): @post_change_hook("ssowat.panel_overlay.enabled") +@post_change_hook("security.nginx.redirect_to_https") @post_change_hook("security.nginx.compatibility") @post_change_hook("security.webadmin.allowlist.enabled") @post_change_hook("security.webadmin.allowlist") From b2a67c4f86f52d17e4848054ec166e9b374d30ca Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 31 Aug 2021 23:12:56 +0200 Subject: [PATCH 2846/3170] https redirect: don't include app conf snippets if https redirect is enabled --- data/templates/nginx/server.tpl.conf | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/data/templates/nginx/server.tpl.conf b/data/templates/nginx/server.tpl.conf index 7133dfba2..379b597a7 100644 --- a/data/templates/nginx/server.tpl.conf +++ b/data/templates/nginx/server.tpl.conf @@ -12,8 +12,6 @@ server { include /etc/nginx/conf.d/acme-challenge.conf.inc; - include /etc/nginx/conf.d/{{ domain }}.d/*.conf; - location ^~ '/.well-known/ynh-diagnosis/' { alias /tmp/.well-known/ynh-diagnosis/; } @@ -22,10 +20,14 @@ server { alias /var/www/.well-known/{{ domain }}/autoconfig/mail/; } + {# Note that this != "False" is meant to be failure-safe, in the case the redrect_to_https would happen to contain empty string or whatever value. We absolutely don't want to disable the HTTPS redirect *except* when it's explicitly being asked to be disabled. #} {% if redirect_to_https != "False" %} location / { return 301 https://$http_host$request_uri; } + {# The app config snippets are not included in the HTTP conf unless HTTPS redirect is disabled, because app's location may blocks will conflict or bypass/ignore the HTTPS redirection. #} + {% else %} + include /etc/nginx/conf.d/{{ domain }}.d/*.conf; {% endif %} access_log /var/log/nginx/{{ domain }}-access.log; From 199e65e41ccb34b3f097b58a7dd2124174dc8110 Mon Sep 17 00:00:00 2001 From: mifegui Date: Tue, 31 Aug 2021 22:11:22 +0000 Subject: [PATCH 2847/3170] Translated using Weblate (Portuguese) Currently translated at 14.4% (93 of 643 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/pt/ --- locales/pt.json | 42 +++++++++++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/locales/pt.json b/locales/pt.json index cc47da946..4b4248f09 100644 --- a/locales/pt.json +++ b/locales/pt.json @@ -8,12 +8,12 @@ "app_id_invalid": "App ID invaĺido", "app_install_files_invalid": "Esses arquivos não podem ser instalados", "app_manifest_invalid": "Manifesto da aplicação inválido: {error}", - "app_not_installed": "{app} não está instalada", - "app_removed": "{app} removida com êxito", - "app_sources_fetch_failed": "Incapaz obter os ficheiros fonte", + "app_not_installed": "Não foi possível encontrar {app} na lista de aplicações instaladas: {all_apps}", + "app_removed": "{app} desinstalada", + "app_sources_fetch_failed": "Não foi possível carregar os arquivos de código fonte, a URL está correta?", "app_unknown": "Aplicação desconhecida", - "app_upgrade_failed": "Não foi possível atualizar {app}", - "app_upgraded": "{app} atualizada com sucesso", + "app_upgrade_failed": "Não foi possível atualizar {app}: {error}", + "app_upgraded": "{app} atualizado", "ask_firstname": "Primeiro nome", "ask_lastname": "Último nome", "ask_main_domain": "Domínio principal", @@ -114,8 +114,8 @@ "app_argument_invalid": "Escolha um valor válido para o argumento '{name}': {error}", "app_argument_required": "O argumento '{name}' é obrigatório", "app_change_url_failed_nginx_reload": "Não foi possível reiniciar o nginx. Aqui está o retorno de 'nginx -t':\n{nginx_errors}", - "app_location_unavailable": "Esta url não está disponível ou está em conflito com outra aplicação já instalada", - "app_upgrade_app_name": "Atualizando aplicação {app}…", + "app_location_unavailable": "Esta url ou não está disponível ou está em conflito com outra(s) aplicação(ões) já instalada(s):\n{apps}", + "app_upgrade_app_name": "Atualizando {app}…", "app_upgrade_some_app_failed": "Não foi possível atualizar algumas aplicações", "backup_abstract_method": "Este metodo de backup ainda não foi implementado", "backup_app_failed": "Não foi possível fazer o backup dos aplicativos '{app}'", @@ -140,5 +140,29 @@ "app_install_script_failed": "Ocorreu um erro dentro do script de instalação do aplicativo", "app_install_failed": "Não foi possível instalar {app}: {error}", "app_full_domain_unavailable": "Desculpe, esse app deve ser instalado num domínio próprio mas já há outros apps instalados no domínio '{domain}'. Você pode usar um subdomínio dedicado a esse aplicativo.", - "app_change_url_success": "A URL agora é {domain}{path}" -} \ No newline at end of file + "app_change_url_success": "A URL agora é {domain}{path}", + "apps_catalog_obsolete_cache": "O cache do catálogo de aplicações está vazio ou obsoleto.", + "apps_catalog_failed_to_download": "Não foi possível fazer o download do catálogo de aplicações {apps_catalog}: {error}", + "apps_catalog_updating": "Atualizado o catálogo de aplicações…", + "apps_catalog_init_success": "Catálogo de aplicações do sistema inicializado!", + "apps_already_up_to_date": "Todas as aplicações já estão atualizadas", + "app_packaging_format_not_supported": "Essa aplicação não pode ser instalada porque o formato dela não é suportado pela sua versão do YunoHost. Considere atualizar seu sistema.", + "app_upgrade_script_failed": "Ocorreu um erro dentro do script de atualização da aplicação", + "app_upgrade_several_apps": "As seguintes aplicações serão atualizadas: {apps}", + "app_start_restore": "Restaurando {app}...", + "app_start_backup": "Obtendo os arquivos para fazer o backup de {app}...", + "app_start_remove": "Removendo {app}...", + "app_start_install": "Instalando {app}...", + "app_restore_script_failed": "Ocorreu um erro dentro do script de restauração da aplicação", + "app_restore_failed": "Não foi possível restaurar {app}: {error}", + "app_remove_after_failed_install": "Removendo a aplicação após a falha da instalação...", + "app_requirements_unmeet": "Os requisitos para a aplicação {app} não foram satisfeitos, o pacote {pkgname} ({version}) devem ser {spec}", + "app_not_upgraded": "Não foi possível atualizar a aplicação '{failed_app}' e, como consequência, a atualização das seguintes aplicações foi cancelada: {apps}", + "app_manifest_install_ask_is_public": "Essa aplicação deve ser visível para visitantes anônimos?", + "app_manifest_install_ask_admin": "Escolha um usuário de administrador para essa aplicação", + "app_manifest_install_ask_password": "Escolha uma senha de administrador para essa aplicação", + "app_manifest_install_ask_path": "Escolha o caminho da url (depois do domínio) em que essa aplicação deve ser instalada", + "app_manifest_install_ask_domain": "Escolha o domínio em que esta aplicação deve ser instalada", + "app_label_deprecated": "Este comando está deprecado! Por favor use o novo comando 'yunohost user permission update' para gerenciar a etiqueta da aplicação.", + "app_make_default_location_already_used": "Não foi passível fazer a aplicação '{app}' ser a padrão no domínio, '{domain}' já está sendo usado por '{other_app}'" +} From 516a77a4914c63537a52d0041323c09ac758b9b5 Mon Sep 17 00:00:00 2001 From: Parviz Homayun Date: Tue, 31 Aug 2021 21:22:47 +0000 Subject: [PATCH 2848/3170] Translated using Weblate (Persian) Currently translated at 41.2% (265 of 643 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fa/ --- locales/fa.json | 73 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 3 deletions(-) diff --git a/locales/fa.json b/locales/fa.json index 48e18a78e..c744a8637 100644 --- a/locales/fa.json +++ b/locales/fa.json @@ -18,8 +18,8 @@ "admin_password": "رمز عبور مدیریت", "additional_urls_already_removed": "نشانی اینترنتی اضافی '{url}' قبلاً در نشانی اینترنتی اضافی برای اجازه '{permission}'حذف شده است", "additional_urls_already_added": "نشانی اینترنتی اضافی '{url}' قبلاً در نشانی اینترنتی اضافی برای اجازه '{permission}' اضافه شده است", - "diagnosis_diskusage_low": "‏ذخیره سازی {mountpoint} (روی دستگاه {device}) فقط {free} ({free_percent}%) فضا باقی مانده است (از {total}). مراقب باشید.", - "diagnosis_diskusage_verylow": "‏ذخیره سازی {mountpoint} (روی دستگاه {device}) فقط {free} ({free_percent}%) فضا باقی مانده است (از {total}). شما واقعاً باید پاکسازی فضای ذخیره ساز را در نظر بگیرید!", + "diagnosis_diskusage_low": "‏ذخیره سازی {mountpoint} (روی دستگاه {device}) فقط {free} ({free_percent}%) فضا باقی مانده(از {total}). مراقب باشید.", + "diagnosis_diskusage_verylow": "‏ذخیره سازی {mountpoint} (روی دستگاه {device}) فقط {free} ({free_percent}%) فضا باقی مانده (از {total}). شما واقعاً باید پاکسازی فضای ذخیره ساز را در نظر بگیرید!", "diagnosis_services_bad_status_tip": "می توانید سعی کنید سرویس را راه اندازی مجدد کنید، و اگر کار نمی کند ، نگاهی داشته باشید بهسرویس در webadmin ثبت می شود (از خط فرمان ، می توانید این کار را انجام دهید با yunohost service restart {service} و yunohost service log {service}).", "diagnosis_services_bad_status": "سرویس {service} {status} است :(", "diagnosis_services_conf_broken": "پیکربندی سرویس {service} خراب است!", @@ -196,5 +196,72 @@ "app_extraction_failed": "فایل های نصبی استخراج نشد", "app_change_url_success": "{app} URL اکنون {domain} {path} است", "app_change_url_no_script": "برنامه '{app_name}' هنوز از تغییر URL پشتیبانی نمی کند. شاید باید آن را ارتقا دهید.", - "app_change_url_identical_domains": "دامنه /url_path قدیمی و جدیدیکسان هستند ('{domain}{path}') ، کاری برای انجام دادن نیست." + "app_change_url_identical_domains": "دامنه /url_path قدیمی و جدیدیکسان هستند ('{domain}{path}') ، کاری برای انجام دادن نیست.", + "diagnosis_http_connection_error": "خطای اتصال: ارتباط با دامنه درخواست شده امکان پذیر نیست، به احتمال زیاد غیرقابل دسترسی است.", + "diagnosis_http_timeout": "زمان تلاش برای تماس با سرور از خارج به پایان رسید. به نظر می رسد غیرقابل دسترسی است.
1. شایع ترین علت برای این مشکل ، پورت 80 است (و 443) به درستی به سرور شما ارسال نمی شوند.
2. همچنین باید مطمئن شوید که سرویس nginx در حال اجرا است
3. در تنظیمات پیچیده تر: مطمئن شوید که هیچ فایروال یا پروکسی معکوسی تداخل نداشته باشد.", + "diagnosis_http_ok": "دامنه {domain} از طریق HTTP از خارج از شبکه محلی قابل دسترسی است.", + "diagnosis_http_localdomain": "انتظار نمی رود که دامنه {domain} ، با TLD محلی. از خارج از شبکه محلی به آن دسترسی پیدا کند.", + "diagnosis_http_could_not_diagnose_details": "خطا: {error}", + "diagnosis_http_could_not_diagnose": "نمی توان تشخیص داد که در IPv{ipversion} دامنه ها از خارج قابل دسترسی هستند یا خیر.", + "diagnosis_http_hairpinning_issue_details": "این احتمالاً به دلیل جعبه / روتر ISP شما است. در نتیجه ، افراد خارج از شبکه محلی شما می توانند به سرور شما مطابق انتظار دسترسی پیدا کنند ، اما افراد داخل شبکه محلی (احتمالاً مثل شما؟) هنگام استفاده از نام دامنه یا IP جهانی. ممکن است بتوانید وضعیت را بهبود بخشید با نگاهی به https://yunohost.org/dns_local_network", + "diagnosis_http_hairpinning_issue": "به نظر می رسد در شبکه محلی شما hairpinning فعال نشده است.", + "diagnosis_ports_forwarding_tip": "برای رفع این مشکل، به احتمال زیاد باید انتقال پورت را در روتر اینترنت خود پیکربندی کنید همانطور که شرح داده شده در https://yunohost.org/isp_box_config", + "diagnosis_ports_needed_by": "افشای این پورت برای ویژگی های {category} (سرویس {service}) مورد نیاز است", + "diagnosis_ports_ok": "پورت {port} از خارج قابل دسترسی است.", + "diagnosis_ports_partially_unreachable": "پورت {port} از خارج در {failed}IPv قابل دسترسی نیست.", + "diagnosis_ports_unreachable": "پورت {port} از خارج قابل دسترسی نیست.", + "diagnosis_ports_could_not_diagnose_details": "خطا: {error}", + "diagnosis_ports_could_not_diagnose": "نمی توان تشخیص داد پورت ها از خارج در IPv{ipversion} قابل دسترسی هستند یا خیر.", + "diagnosis_description_regenconf": "تنظیمات سیستم", + "diagnosis_description_mail": "ایمیل", + "diagnosis_description_web": "وب", + "diagnosis_description_ports": "ارائه پورت ها", + "diagnosis_description_systemresources": "منابع سیستم", + "diagnosis_description_services": "بررسی وضعیّت سرویس ها", + "diagnosis_description_dnsrecords": "رکورد DNS", + "diagnosis_description_ip": "اتصال به اینترنت", + "diagnosis_description_basesystem": "سیستم پایه", + "diagnosis_security_vulnerable_to_meltdown_details": "برای رفع این مشکل ، باید سیستم خود را ارتقا دهید و مجدداً راه اندازی کنید تا هسته لینوکس جدید بارگیری شود (یا در صورت عدم کارکرد با ارائه دهنده سرور خود تماس بگیرید). برای اطلاعات بیشتر به https://meltdownattack.com/ مراجعه کنید.", + "diagnosis_security_vulnerable_to_meltdown": "به نظر می رسد شما در برابر آسیب پذیری امنیتی بحرانی Meltdown آسیب پذیر هستید", + "diagnosis_rootfstotalspace_critical": "کل سیستم فایل فقط دارای {space} است که بسیار نگران کننده است! احتمالاً خیلی زود فضای دیسک شما تمام می شود! توصیه می شود حداقل 16 گیگابایت و بیشتر فضا برای سیستم فایل ریشه داشته باشید.", + "diagnosis_rootfstotalspace_warning": "سیستم فایل ریشه در مجموع فقط {space} دارد. ممکن است اشکالی نداشته باشد ، اما مراقب باشید زیرا در نهایت ممکن است فضای دیسک شما به سرعت تمام شود... توصیه می شود حداقل 16 گیگابایت و بیشتر فضا برای سیستم فایل ریشه داشته باشید.", + "diagnosis_regenconf_manually_modified_details": "اگر بدانید چه کار می کنید ، احتمالاً خوب است! YunoHost به روز رسانی خودکار این فایل را متوقف می کند... اما مراقب باشید که ارتقاء YunoHost می تواند شامل تغییرات مهم توصیه شده باشد. اگر می خواهید ، می توانید تفاوت ها را با yunohost tools regen-conf {category} --dry-run --with-diff و تنظیم مجدد پیکربندی توصیه شده به زور با فرمان yunohost tools regen-conf {category} --force", + "diagnosis_regenconf_manually_modified": "به نظر می رسد فایل پیکربندی {file} به صورت دستی اصلاح شده است.", + "diagnosis_regenconf_allgood": "همه فایلهای پیکربندی مطابق با تنظیمات توصیه شده است!", + "diagnosis_mail_queue_too_big": "تعداد زیادی ایمیل معلق در صف پست ({nb_pending} ایمیل)", + "diagnosis_mail_queue_unavailable_details": "خطا: {error}", + "diagnosis_mail_queue_unavailable": "نمی توان با تعدادی از ایمیل های معلق در صف مشورت کرد", + "diagnosis_mail_queue_ok": "{nb_pending} ایمیل های معلق در صف های ایمیل", + "diagnosis_mail_blacklist_website": "پس از شناسایی دلیل لیست شدن و رفع آن، با خیال راحت درخواست کنید IP یا دامنه شما حذف شود از {blacklist_website}", + "diagnosis_mail_blacklist_reason": "دلیل لیست سیاه: {reason}", + "diagnosis_mail_blacklist_listed_by": "IP یا دامنه شما {item}در لیست سیاه {blacklist_name} قرار دارد", + "diagnosis_mail_blacklist_ok": "به نظر می رسد IP ها و دامنه های مورد استفاده این سرور در لیست سیاه قرار ندارند", + "diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "DNS معکوس فعلی: {rdns_domain}
مقدار مورد انتظار: {ehlo_domain}", + "diagnosis_mail_fcrdns_different_from_ehlo_domain": "DNS معکوس به درستی در IPv{ipversion} پیکربندی نشده است. ممکن است برخی از ایمیل ها تحویل داده نشوند یا به عنوان هرزنامه پرچم گذاری شوند.", + "diagnosis_mail_fcrdns_nok_alternatives_6": "برخی از ارائه دهندگان به شما اجازه نمی دهند DNS معکوس خود را پیکربندی کنید (یا ممکن است ویژگی آنها شکسته شود...). اگر DNS معکوس شما به درستی برای IPv4 پیکربندی شده است، با استفاده از آن می توانید هنگام ارسال ایمیل، استفاده از IPv6 را غیرفعال کنید. yunohost settings set smtp.allow_ipv6 -v off. توجه: این راه حل آخری به این معنی است که شما نمی توانید از چند سرور IPv6 موجود ایمیل ارسال یا دریافت کنید.", + "diagnosis_mail_fcrdns_nok_alternatives_4": "برخی از ارائه دهندگان به شما اجازه نمی دهند DNS معکوس خود را پیکربندی کنید (یا ممکن است ویژگی آنها شکسته شود...). اگر به همین دلیل مشکلاتی را تجربه می کنید ، راه حل های زیر را در نظر بگیرید: - برخی از ISP ها جایگزین ارائه می دهند با استفاده از رله سرور ایمیل اگرچه به این معنی است که رله می تواند از ترافیک ایمیل شما جاسوسی کند.
- یک جایگزین دوستدار حریم خصوصی استفاده از VPN * با IP عمومی اختصاصی * برای دور زدن این نوع محدودیت ها است. ببینید https://yunohost.org/#/vpn_advantage
- یا ممکن است به ارائه دهنده دیگری بروید", + "diagnosis_mail_fcrdns_nok_details": "ابتدا باید DNS معکوس را پیکربندی کنید با {ehlo_domain} در رابط روتر اینترنت یا رابط ارائه دهنده میزبانی تان. (ممکن است برخی از ارائه دهندگان میزبانی از شما بخواهند که برای این کار تیکت پشتیبانی ارسال کنید).", + "diagnosis_mail_fcrdns_dns_missing": "در IPv{ipversion} هیچ DNS معکوسی تعریف نشده است. ممکن است برخی از ایمیل ها تحویل داده نشوند یا به عنوان هرزنامه پرچم گذاری شوند.", + "diagnosis_mail_fcrdns_ok": "DNS معکوس شما به درستی پیکربندی شده است!", + "diagnosis_mail_ehlo_could_not_diagnose_details": "خطا: {error}", + "diagnosis_mail_ehlo_could_not_diagnose": "نمی توان تشخیص داد که آیا سرور ایمیل postfix از خارج در IPv{ipversion} قابل دسترسی است یا خیر.", + "diagnosis_mail_ehlo_wrong_details": "EHLO دریافت شده توسط تشخیص دهنده از راه دور در IPv{ipversion} با دامنه سرور شما متفاوت است.
EHLO دریافت شده: {wrong_ehlo}
انتظار می رود: {right_ehlo}
شایع ترین علت این مشکل ، پورت 25 است به درستی به سرور شما ارسال نشده است. از سوی دیگر اطمینان حاصل کنید که هیچ فایروال یا پروکسی معکوسی تداخل ایجاد نمی کند.", + "diagnosis_mail_ehlo_wrong": "یک سرور ایمیل SMTP متفاوت در IPv{ipversion} پاسخ می دهد. سرور شما احتمالاً نمی تواند ایمیل دریافت کند.", + "diagnosis_mail_ehlo_bad_answer_details": "ممکن است به دلیل پاسخ دادن دستگاه دیگری به جای سرور شما باشد.", + "diagnosis_mail_ehlo_bad_answer": "یک سرویس غیر SMTP در پورت 25 در IPv{ipversion} پاسخ داد", + "diagnosis_mail_ehlo_unreachable_details": "اتصال روی پورت 25 سرور شما در IPv{ipversion} باز نشد. به نظر می رسد غیرقابل دسترس است.
1. شایع ترین علت این مشکل ، پورت 25 است به درستی به سرور شما ارسال نشده است.
2. همچنین باید مطمئن شوید که سرویس postfix در حال اجرا است.
3. در تنظیمات پیچیده تر: مطمئن شوید که هیچ فایروال یا پروکسی معکوسی تداخل نداشته باشد.", + "diagnosis_mail_ehlo_unreachable": "سرور ایمیل SMTP از خارج در IPv {ipversion} غیرقابل دسترسی است. قادر به دریافت ایمیل نخواهد بود.", + "diagnosis_mail_ehlo_ok": "سرور ایمیل SMTP از خارج قابل دسترسی است و بنابراین می تواند ایمیل دریافت کند!", + "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "برخی از ارائه دهندگان به شما اجازه نمی دهند پورت خروجی 25 را رفع انسداد کنید زیرا به بی طرفی شبکه اهمیتی نمی دهند.
- برخی از آنها جایگزین را ارائه می دهند با استفاده از رله سرور ایمیل اگرچه به این معنی است که رله می تواند از ترافیک ایمیل شما جاسوسی کند.
- یک جایگزین دوستدار حریم خصوصی استفاده از VPN * با IP عمومی اختصاصی * برای دور زدن این نوع محدودیت ها است. ببینید https://yunohost.org/#/vpn_advantage
- همچنین می توانید تغییر را در نظر بگیرید به یک ارائه دهنده بی طرف خالص تر", + "diagnosis_mail_outgoing_port_25_blocked_details": "ابتدا باید سعی کنید پورت خروجی 25 را در رابط اینترنت روتر یا رابط ارائه دهنده میزبانی خود باز کنید. (ممکن است برخی از ارائه دهندگان میزبانی از شما بخواهند که برای این کار تیکت پشتیبانی ارسال کنید).", + "diagnosis_mail_outgoing_port_25_blocked": "سرور ایمیل SMTP نمی تواند به سرورهای دیگر ایمیل ارسال کند زیرا درگاه خروجی 25 در IPv {ipversion} مسدود شده است.", + "diagnosis_mail_outgoing_port_25_ok": "سرور ایمیل SMTP قادر به ارسال ایمیل است (پورت خروجی 25 مسدود نشده است).", + "diagnosis_swap_tip": "لطفاً مراقب و آگاه باشید، اگر سرور میزبانی swap را روی کارت SD یا حافظه SSD انجام دهد ، ممکن است طول عمر دستگاه را به شدت کاهش دهد.", + "diagnosis_swap_ok": "سیستم {total} swap دارد!", + "diagnosis_swap_notsomuch": "سیستم فقط {total} swap دارد. برای جلوگیری از شرایطی که حافظه سیستم شما تمام می شود ، باید حداقل {recommended} را در نظر بگیرید.", + "diagnosis_swap_none": "این سیستم به هیچ وجه swap ندارد. برای جلوگیری از شرایطی که حافظه سیستم شما تمام می شود ، باید حداقل {recommended} swap را در نظر بگیرید.", + "diagnosis_ram_ok": "این سیستم هنوز {available} ({available_percent}٪) حافظه در دسترس دارد از مجموع {total}.", + "diagnosis_ram_low": "این سیستم فقط {available} ({available_percent}٪) حافظه در دسترس دارد! (از {total}). مراقب باشید.", + "diagnosis_ram_verylow": "این سیستم فقط {available} ({available_percent}٪) حافظه در دسترس دارد! (از {total})", + "diagnosis_diskusage_ok": "‏ذخیره سازی {mountpoint} (روی دستگاه {device}) هنوز {free} فضا در دسترس دارد ({free_percent}%) فضای باقی مانده (از {total})!" } From 2f944d6258c68997482d3dfa180995ceb810cb0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M?= Date: Wed, 1 Sep 2021 04:04:55 +0000 Subject: [PATCH 2849/3170] Translated using Weblate (Galician) Currently translated at 100.0% (643 of 643 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/gl/ --- locales/gl.json | 65 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 61 insertions(+), 4 deletions(-) diff --git a/locales/gl.json b/locales/gl.json index e752716cd..56204a9ea 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -481,9 +481,9 @@ "pattern_email_forward": "Ten que ser un enderezo de email válido, está aceptado o símbolo '+' (ex. persoa+etiqueta@exemplo.com)", "pattern_domain": "Ten que ser un nome de dominio válido (ex. dominiopropio.org)", "pattern_backup_archive_name": "Ten que ser un nome de ficheiro válido con 30 caracteres como máximo, alfanuméricos ou só caracteres -_.", - "password_too_simple_4": "O contrasinal debe ter 12 caracteres como mínimo e conter un díxito, maiúsculas, minúsculas e caracteres especiais", - "password_too_simple_3": "O contrasinal debe ter 8 caracteres como mínimo e conter un díxito, maiúsculas, minúsculas e caracteres especiais", - "password_too_simple_2": "O contrasinal debe ter 8 caracteres como mínimo e conter un díxito, maiúsculas e minúsculas", + "password_too_simple_4": "O contrasinal ten que ter 12 caracteres como mínimo e conter un díxito, maiúsculas, minúsculas e caracteres especiais", + "password_too_simple_3": "O contrasinal ten que ter 8 caracteres como mínimo e conter un díxito, maiúsculas, minúsculas e caracteres especiais", + "password_too_simple_2": "O contrasinal ten que ter 8 caracteres como mínimo e conter un díxito, maiúsculas e minúsculas", "password_listed": "Este contrasinal está entre os máis utilizados no mundo. Por favor elixe outro que sexa máis orixinal.", "packages_upgrade_failed": "Non se puideron actualizar tódolos paquetes", "operation_interrupted": "Foi interrumpida manualmente a operación?", @@ -584,5 +584,62 @@ "invalid_password": "Contrasinal non válido", "ldap_server_is_down_restart_it": "O servidor LDAP está caído, intenta reinicialo...", "ldap_server_down": "Non se chegou ao servidor LDAP", - "global_settings_setting_security_experimental_enabled": "Activar características de seguridade experimentais (non actives isto se non sabes o que estás a facer!)" + "global_settings_setting_security_experimental_enabled": "Activar características de seguridade experimentais (non actives isto se non sabes o que estás a facer!)", + "yunohost_postinstall_end_tip": "Post-install completada! Para rematar a configuración considera:\n- engadir unha primeira usuaria na sección 'Usuarias' na webadmin (ou 'yunohost user create ' na liña de comandos);\n- diagnosticar potenciais problemas na sección 'Diagnóstico' na webadmin (ou 'yunohost diagnosis run' na liña de comandos);\n- ler 'Rematando a configuración' e 'Coñece YunoHost' na documentación da administración: https://yunohost.org/admindoc.", + "yunohost_not_installed": "YunoHost non está instalado correctamente. Executa 'yunohost tools postinstall'", + "yunohost_installing": "Instalando YunoHost...", + "yunohost_configured": "YunoHost está configurado", + "yunohost_already_installed": "YunoHost xa está instalado", + "user_updated": "Cambiada a info da usuaria", + "user_update_failed": "Non se actualizou usuaria {user}: {error}", + "user_unknown": "Usuaria descoñecida: {user}", + "user_home_creation_failed": "Non se puido crear cartafol 'home' para a usuaria", + "user_deletion_failed": "Non se puido eliminar a usuaria {user}: {error}", + "user_deleted": "Usuaria eliminada", + "user_creation_failed": "Non se puido crear a usuaria {user}: {error}", + "user_created": "Usuaria creada", + "user_already_exists": "A usuaria '{user}' xa existe", + "upnp_port_open_failed": "Non se puido abrir porto a través de UPnP", + "upnp_dev_not_found": "Non se atopa dispositivo UPnP", + "upgrading_packages": "Actualizando paquetes...", + "upgrade_complete": "Actualización completa", + "updating_apt_cache": "Obtendo actualizacións dispoñibles para os paquetes do sistema...", + "update_apt_cache_warning": "Algo fallou ao actualizar a caché de APT (xestor de paquetes Debian). Aquí tes un volcado de sources.list, que podería axudar a identificar liñas problemáticas:\n{sourceslist}", + "update_apt_cache_failed": "Non se puido actualizar a caché de APT (xestor de paquetes de Debian). Aquí tes un volcado do sources.list, que podería axudarche a identificar liñas incorrectas:\n{sourceslist}", + "unrestore_app": "{app} non vai ser restablecida", + "unlimit": "Sen cota", + "unknown_main_domain_path": "Dominio ou ruta descoñecida '{app}'. Tes que indicar un dominio e ruta para poder especificar un URL para o permiso.", + "unexpected_error": "Aconteceu un fallo non agardado: {error}", + "unbackup_app": "{app} non vai ser gardada", + "tools_upgrade_special_packages_completed": "Completada a actualización dos paquetes YunoHost.\nPreme [Enter] para recuperar a liña de comandos", + "tools_upgrade_special_packages_explanation": "A actualización especial continuará en segundo plano. Non inicies outras tarefas no servidor nos seguintes ~10 minutos (depende do hardware). Após isto, podes volver a conectar na webadmin. O rexistro da actualización estará dispoñible en Ferramentas → Rexistro (na webadmin) ou con 'yunohost log list' (na liña de comandos).", + "tools_upgrade_special_packages": "Actualizando paquetes 'special' (yunohost-related)…", + "tools_upgrade_regular_packages_failed": "Non se actualizaron os paquetes: {packages_list}", + "tools_upgrade_regular_packages": "Actualizando os paquetes 'regular' (non-yunohost-related)…", + "tools_upgrade_cant_unhold_critical_packages": "Non se desbloquearon os paquetes críticos…", + "tools_upgrade_cant_hold_critical_packages": "Non se puideron bloquear os paquetes críticos…", + "tools_upgrade_cant_both": "Non se pode actualizar o sistema e as apps ao mesmo tempo", + "tools_upgrade_at_least_one": "Por favor indica 'apps', ou 'system'", + "this_action_broke_dpkg": "Esta acción rachou dpkg/APT (xestores de paquetes do sistema)... Podes intentar resolver o problema conectando a través de SSH e executando `sudo apt install --fix-broken`e/ou `sudo dpkg --configure -a`.", + "system_username_exists": "Xa existe este nome de usuaria na lista de usuarias do sistema", + "system_upgraded": "Sistema actualizado", + "ssowat_conf_updated": "Actualizada a configuración SSOwat", + "ssowat_conf_generated": "Rexenerada a configuración para SSOwat", + "show_tile_cant_be_enabled_for_regex": "Non podes activar 'show_tile' neste intre, porque o URL para o permiso '{permission}' é un regex", + "show_tile_cant_be_enabled_for_url_not_defined": "Non podes activar 'show_tile' neste intre, primeiro tes que definir un URL para o permiso '{permission}'", + "service_unknown": "Servizo descoñecido '{service}'", + "service_stopped": "Detívose o servizo '{service}'", + "service_stop_failed": "Non se puido deter o servizo '{service}'\n\nRexistros recentes do servizo: {logs}", + "service_started": "Iniciado o servizo '{service}'", + "service_start_failed": "Non se puido iniciar o servizo '{service}'\n\nRexistros recentes do servizo: {logs}", + "service_reloaded_or_restarted": "O servizo '{service}' foi recargado ou reiniciado", + "service_reload_or_restart_failed": "Non se recargou ou reiniciou o servizo '{service}'\n\nRexistros recentes do servizo: {logs}", + "service_restarted": "Reiniciado o servizo '{service}'", + "service_restart_failed": "Non se reiniciou o servizo '{service}'\n\nRexistros recentes do servizo: {logs}", + "service_reloaded": "Recargado o servizo '{service}'", + "service_reload_failed": "Non se recargou o servizo '{service}'\n\nRexistros recentes do servizo: {logs}", + "service_removed": "Eliminado o servizo '{service}'", + "service_remove_failed": "Non se eliminou o servizo '{service}'", + "service_regen_conf_is_deprecated": "'yunohost service regen-conf' xa non se utiliza! Executa 'yunohost tools regen-conf' no seu lugar.", + "service_enabled": "O servizo '{service}' vai ser iniciado automáticamente no inicio do sistema." } From 868c9c7f78eab6c09a34d6380275ec563d2150d5 Mon Sep 17 00:00:00 2001 From: Parviz Homayun Date: Wed, 1 Sep 2021 05:40:50 +0000 Subject: [PATCH 2850/3170] Translated using Weblate (Persian) Currently translated at 41.8% (269 of 643 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fa/ --- locales/fa.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/locales/fa.json b/locales/fa.json index c744a8637..18db28fdc 100644 --- a/locales/fa.json +++ b/locales/fa.json @@ -263,5 +263,9 @@ "diagnosis_ram_ok": "این سیستم هنوز {available} ({available_percent}٪) حافظه در دسترس دارد از مجموع {total}.", "diagnosis_ram_low": "این سیستم فقط {available} ({available_percent}٪) حافظه در دسترس دارد! (از {total}). مراقب باشید.", "diagnosis_ram_verylow": "این سیستم فقط {available} ({available_percent}٪) حافظه در دسترس دارد! (از {total})", - "diagnosis_diskusage_ok": "‏ذخیره سازی {mountpoint} (روی دستگاه {device}) هنوز {free} فضا در دسترس دارد ({free_percent}%) فضای باقی مانده (از {total})!" + "diagnosis_diskusage_ok": "‏ذخیره سازی {mountpoint} (روی دستگاه {device}) هنوز {free} فضا در دسترس دارد ({free_percent}%) فضای باقی مانده (از {total})!", + "diagnosis_http_nginx_conf_not_up_to_date": "به نظر می رسد که پیکربندی nginx این دامنه به صورت دستی تغییر کرده است و از تشخیص YunoHost در صورت دسترسی به HTTP جلوگیری می کند.", + "diagnosis_http_partially_unreachable": "به نظر می رسد که دامنه {domain} از طریق HTTP از خارج از شبکه محلی در IPv{failed} غیرقابل دسترسی است، اگرچه در IPv{passed} کار می کند.", + "diagnosis_http_unreachable": "به نظر می رسد دامنه {domain} از خارج از شبکه محلی از طریق HTTP قابل دسترسی نیست.", + "diagnosis_http_bad_status_code": "به نظر می رسد دستگاه دیگری (شاید روتر اینترنتی شما) به جای سرور شما پاسخ داده است.
1. شایع ترین علت برای این مشکل ، پورت 80 است (و 443) به درستی به سرور شما ارسال نمی شوند.
2. در تنظیمات پیچیده تر: مطمئن شوید که هیچ فایروال یا پروکسی معکوسی تداخل نداشته باشد." } From 10b813b6bb285f34e60be897ddeb58ecbbb6c961 Mon Sep 17 00:00:00 2001 From: Parviz Homayun Date: Wed, 1 Sep 2021 06:14:39 +0000 Subject: [PATCH 2851/3170] Translated using Weblate (Persian) Currently translated at 43.2% (278 of 643 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fa/ --- locales/fa.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/locales/fa.json b/locales/fa.json index 18db28fdc..2e943fe31 100644 --- a/locales/fa.json +++ b/locales/fa.json @@ -267,5 +267,14 @@ "diagnosis_http_nginx_conf_not_up_to_date": "به نظر می رسد که پیکربندی nginx این دامنه به صورت دستی تغییر کرده است و از تشخیص YunoHost در صورت دسترسی به HTTP جلوگیری می کند.", "diagnosis_http_partially_unreachable": "به نظر می رسد که دامنه {domain} از طریق HTTP از خارج از شبکه محلی در IPv{failed} غیرقابل دسترسی است، اگرچه در IPv{passed} کار می کند.", "diagnosis_http_unreachable": "به نظر می رسد دامنه {domain} از خارج از شبکه محلی از طریق HTTP قابل دسترسی نیست.", - "diagnosis_http_bad_status_code": "به نظر می رسد دستگاه دیگری (شاید روتر اینترنتی شما) به جای سرور شما پاسخ داده است.
1. شایع ترین علت برای این مشکل ، پورت 80 است (و 443) به درستی به سرور شما ارسال نمی شوند.
2. در تنظیمات پیچیده تر: مطمئن شوید که هیچ فایروال یا پروکسی معکوسی تداخل نداشته باشد." + "diagnosis_http_bad_status_code": "به نظر می رسد دستگاه دیگری (شاید روتر اینترنتی شما) به جای سرور شما پاسخ داده است.
1. شایع ترین علت برای این مشکل ، پورت 80 است (و 443) به درستی به سرور شما ارسال نمی شوند.
2. در تنظیمات پیچیده تر: مطمئن شوید که هیچ فایروال یا پروکسی معکوسی تداخل نداشته باشد.", + "disk_space_not_sufficient_update": "برای به روزرسانی این برنامه فضای دیسک کافی باقی نمانده است", + "disk_space_not_sufficient_install": "فضای کافی برای نصب این برنامه در دیسک باقی نمانده است", + "diagnosis_sshd_config_inconsistent_details": "لطفاً اجراکنید yunohost settings set security.ssh.port -v YOUR_SSH_PORT برای تعریف پورت SSH و بررسی کنید yunohost tools regen-conf ssh --dry-run --with-diff و yunohost tools regen-conf ssh --force برای تنظیم مجدد تنظیمات خود به توصیه YunoHost.", + "diagnosis_sshd_config_inconsistent": "به نظر می رسد که پورت SSH به صورت دستی در/etc/ssh/sshd_config تغییر یافته است. از زمان YunoHost 4.2 ، یک تنظیم جهانی جدید 'security.ssh.port' برای جلوگیری از ویرایش دستی پیکربندی در دسترس است.", + "diagnosis_sshd_config_insecure": "به نظر می رسد که پیکربندی SSH به صورت دستی تغییر یافته است و مطمئن نیست زیرا هیچ دستورالعمل 'AllowGroups' یا 'AllowUsers' برای محدود کردن دسترسی به کاربران مجاز ندارد.", + "diagnosis_processes_killed_by_oom_reaper": "برخی از فرآیندها اخیراً توسط سیستم از بین رفته اند زیرا حافظه آن تمام شده است. این به طور معمول نشانه کمبود حافظه در سیستم یا فرآیندی است که حافظه زیادی را از بین می برد. خلاصه فرآیندهای کشته شده:\n{kills_summary}", + "diagnosis_never_ran_yet": "به نظر می رسد این سرور به تازگی راه اندازی شده است و هنوز هیچ گزارش تشخیصی برای نمایش وجود ندارد. شما باید با اجرای یک عیب یابی و تشخیص کامل، از طریق رابط مدیریت تحت وب webadmin یا با استفاده از 'yunohost diagnosis run' از خط فرمان معاینه و تشخیص عیب یابی را شروع کنید.", + "diagnosis_unknown_categories": "دسته های زیر ناشناخته است: {categories}", + "diagnosis_http_nginx_conf_not_up_to_date_details": "برای برطرف کردن وضعیّت ، تفاوت را با استفاده از خط فرمان بررسی کنیدyunohost tools regen-conf nginx --dry-run --with-diff و اگر خوب است ، تغییرات را اعمال کنید با استفاده از فرمان yunohost tools regen-conf nginx --force." } From d9afb8507bc9ba37649cb2f1ceb36358a3e779e7 Mon Sep 17 00:00:00 2001 From: Parviz Homayun Date: Wed, 1 Sep 2021 06:37:42 +0000 Subject: [PATCH 2852/3170] Translated using Weblate (Persian) Currently translated at 43.7% (281 of 643 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fa/ --- locales/fa.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/locales/fa.json b/locales/fa.json index 2e943fe31..19536c9c7 100644 --- a/locales/fa.json +++ b/locales/fa.json @@ -276,5 +276,8 @@ "diagnosis_processes_killed_by_oom_reaper": "برخی از فرآیندها اخیراً توسط سیستم از بین رفته اند زیرا حافظه آن تمام شده است. این به طور معمول نشانه کمبود حافظه در سیستم یا فرآیندی است که حافظه زیادی را از بین می برد. خلاصه فرآیندهای کشته شده:\n{kills_summary}", "diagnosis_never_ran_yet": "به نظر می رسد این سرور به تازگی راه اندازی شده است و هنوز هیچ گزارش تشخیصی برای نمایش وجود ندارد. شما باید با اجرای یک عیب یابی و تشخیص کامل، از طریق رابط مدیریت تحت وب webadmin یا با استفاده از 'yunohost diagnosis run' از خط فرمان معاینه و تشخیص عیب یابی را شروع کنید.", "diagnosis_unknown_categories": "دسته های زیر ناشناخته است: {categories}", - "diagnosis_http_nginx_conf_not_up_to_date_details": "برای برطرف کردن وضعیّت ، تفاوت را با استفاده از خط فرمان بررسی کنیدyunohost tools regen-conf nginx --dry-run --with-diff و اگر خوب است ، تغییرات را اعمال کنید با استفاده از فرمان yunohost tools regen-conf nginx --force." + "diagnosis_http_nginx_conf_not_up_to_date_details": "برای برطرف کردن وضعیّت ، تفاوت را با استفاده از خط فرمان بررسی کنیدyunohost tools regen-conf nginx --dry-run --with-diff و اگر خوب است ، تغییرات را اعمال کنید با استفاده از فرمان yunohost tools regen-conf nginx --force.", + "domain_cannot_remove_main_add_new_one": "شما نمی توانید '{domain}' را حذف کنید زیرا دامنه اصلی و تنها دامنه شما است، ابتدا باید دامنه دیگری را با 'yunohost domain add ' اضافه کنید، سپس با استفاده از 'yunohost domain main-domain -n ' به عنوان دامنه اصلی تنظیم شده. و بعد از آن می توانید با استفاده از' yunohost domain remove '{domain}''دامنه '{domain}'را حذف کنید.", + "domain_cannot_add_xmpp_upload": "شما نمی توانید دامنه هایی را که با \"xmpp-upload\" شروع می شوند اضافه کنید. این نوع نام مختص ویژگی بارگذاری XMPP است که در YunoHost یکپارچه شده است.", + "domain_cannot_remove_main": "شما نمی توانید '{domain}' را حذف کنید زیرا دامنه اصلی است ، ابتدا باید با استفاده از 'yunohost domain main-domain -n ' دامنه دیگری را به عنوان دامنه اصلی تعیین کنید. در اینجا لیست دامنه های کاندید وجود دارد: {other_domains}" } From 8d7eaae1f0124df433c940c1f3eb5ed74a94c445 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 1 Sep 2021 11:32:21 +0200 Subject: [PATCH 2853/3170] Typos in french strings --- locales/fr.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 0b5a5e93f..9d382304d 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -571,7 +571,7 @@ "diagnosis_dns_try_dyndns_update_force": "La configuration DNS de ce domaine devrait être automatiquement gérée par YunoHost. Si ce n'est pas le cas, vous pouvez essayer de forcer une mise à jour en utilisant yunohost dyndns update --force.", "app_packaging_format_not_supported": "Cette application ne peut pas être installée car son format n'est pas pris en charge par votre version de YunoHost. Vous devriez probablement envisager de mettre à jour votre système.", "migration_0015_weak_certs": "Il a été constaté que les certificats suivants utilisent encore des algorithmes de signature peu robustes et doivent être mis à jour pour être compatibles avec la prochaine version de NGINX : {certs}", - "global_settings_setting_backup_compress_tar_archives": "Lors de la création de nouvelles sauvegardes, compressez les archives (.tar.gz) au lieu des archives non compressées (.tar). N.B. : activer cette option permet de créer des archives plus légères, mais la procédure de sauvegarde initiale sera significativement plus longues et plus gourmandes en CPU.", + "global_settings_setting_backup_compress_tar_archives": "Lors de la création de nouvelles sauvegardes, compresser automatiquement les archives (.tar.gz) au lieu des archives non compressées (.tar). N.B. : activer cette option permet de créer des archives plus légères, mais la procédure de sauvegarde initiale sera significativement plus longues et plus gourmandes en CPU.", "migration_description_0018_xtable_to_nftable": "Migrer les anciennes règles de trafic réseau vers le nouveau système basé sur nftables", "service_description_php7.3-fpm": "Exécute les applications écrites en PHP avec NGINX", "migration_0018_failed_to_reset_legacy_rules": "La réinitialisation des règles iptable par défaut a échoué : {error}", @@ -641,5 +641,5 @@ "invalid_password": "Mot de passe incorrect", "ldap_server_is_down_restart_it": "Le service LDAP est en panne, essayez de le redémarrer...", "ldap_server_down": "Impossible d'atteindre le serveur LDAP", - "global_settings_setting_security_experimental_enabled": "Activez les fonctionnalités de sécurité expérimentales (ne l'activez pas si vous ne savez pas ce que vous faites !)" + "global_settings_setting_security_experimental_enabled": "Activer les fonctionnalités de sécurité expérimentales (ne l'activez pas si vous ne savez pas ce que vous faites !)" } From e30063743a02c8eceb97bdb3b0d45ccf33c2a46a Mon Sep 17 00:00:00 2001 From: Parviz Homayun Date: Wed, 1 Sep 2021 09:25:20 +0000 Subject: [PATCH 2854/3170] Translated using Weblate (Persian) Currently translated at 57.5% (370 of 643 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fa/ --- locales/fa.json | 93 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 91 insertions(+), 2 deletions(-) diff --git a/locales/fa.json b/locales/fa.json index 19536c9c7..31a24e269 100644 --- a/locales/fa.json +++ b/locales/fa.json @@ -277,7 +277,96 @@ "diagnosis_never_ran_yet": "به نظر می رسد این سرور به تازگی راه اندازی شده است و هنوز هیچ گزارش تشخیصی برای نمایش وجود ندارد. شما باید با اجرای یک عیب یابی و تشخیص کامل، از طریق رابط مدیریت تحت وب webadmin یا با استفاده از 'yunohost diagnosis run' از خط فرمان معاینه و تشخیص عیب یابی را شروع کنید.", "diagnosis_unknown_categories": "دسته های زیر ناشناخته است: {categories}", "diagnosis_http_nginx_conf_not_up_to_date_details": "برای برطرف کردن وضعیّت ، تفاوت را با استفاده از خط فرمان بررسی کنیدyunohost tools regen-conf nginx --dry-run --with-diff و اگر خوب است ، تغییرات را اعمال کنید با استفاده از فرمان yunohost tools regen-conf nginx --force.", - "domain_cannot_remove_main_add_new_one": "شما نمی توانید '{domain}' را حذف کنید زیرا دامنه اصلی و تنها دامنه شما است، ابتدا باید دامنه دیگری را با 'yunohost domain add ' اضافه کنید، سپس با استفاده از 'yunohost domain main-domain -n ' به عنوان دامنه اصلی تنظیم شده. و بعد از آن می توانید با استفاده از' yunohost domain remove '{domain}''دامنه '{domain}'را حذف کنید.", + "domain_cannot_remove_main_add_new_one": "شما نمی توانید '{domain}' را حذف کنید زیرا دامنه اصلی و تنها دامنه شما است، ابتدا باید دامنه دیگری را با 'yunohost domain add ' اضافه کنید، سپس با استفاده از 'yunohost domain main-domain -n ' به عنوان دامنه اصلی تنظیم شده. و بعد از آن می توانید'{domain}' را حذف کنید با استفاده از'yunohost domain remove {domain}'.'", "domain_cannot_add_xmpp_upload": "شما نمی توانید دامنه هایی را که با \"xmpp-upload\" شروع می شوند اضافه کنید. این نوع نام مختص ویژگی بارگذاری XMPP است که در YunoHost یکپارچه شده است.", - "domain_cannot_remove_main": "شما نمی توانید '{domain}' را حذف کنید زیرا دامنه اصلی است ، ابتدا باید با استفاده از 'yunohost domain main-domain -n ' دامنه دیگری را به عنوان دامنه اصلی تعیین کنید. در اینجا لیست دامنه های کاندید وجود دارد: {other_domains}" + "domain_cannot_remove_main": "شما نمی توانید '{domain}' را حذف کنید زیرا دامنه اصلی است ، ابتدا باید با استفاده از 'yunohost domain main-domain -n ' دامنه دیگری را به عنوان دامنه اصلی تعیین کنید. در اینجا لیست دامنه های کاندید وجود دارد: {other_domains}", + "installation_complete": "نصب تکمیل شد", + "hook_name_unknown": "نام قلاب ناشناخته '{name}'", + "hook_list_by_invalid": "از این ویژگی نمی توان برای فهرست قلاب ها استفاده کرد", + "hook_json_return_error": "بازگشت از قلاب {path} خوانده نشد. خطا: {msg}. محتوای خام: {raw_content}", + "hook_exec_not_terminated": "اسکریپت به درستی به پایان نرسید: {path}", + "hook_exec_failed": "اسکریپت اجرا نشد: {path}", + "group_user_not_in_group": "کاربر {user} در گروه {group} نیست", + "group_user_already_in_group": "کاربر {user} در حال حاضر در گروه {group} است", + "group_update_failed": "گروه '{group}' به روز نشد: {error}", + "group_updated": "گروه '{group}' به روز شد", + "group_unknown": "گروه '{group}' ناشناخته است", + "group_deletion_failed": "گروه '{group}' حذف نشد: {error}", + "group_deleted": "گروه '{group}' حذف شد", + "group_cannot_be_deleted": "گروه {group} را نمی توان به صورت دستی حذف کرد.", + "group_cannot_edit_primary_group": "گروه '{group}' را نمی توان به صورت دستی ویرایش کرد. این گروه اصلی شامل تنها یک کاربر خاص است.", + "group_cannot_edit_visitors": "ویرایش گروه 'visitors' بازدیدکنندگان به صورت دستی امکان پذیر نیست. این گروه ویژه، نمایانگر بازدیدکنندگان ناشناس است", + "group_cannot_edit_all_users": "گروه 'all_users' را نمی توان به صورت دستی ویرایش کرد. این یک گروه ویژه است که شامل همه کاربران ثبت شده در YunoHost میباشد", + "group_creation_failed": "گروه '{group}' ایجاد نشد: {error}", + "group_created": "گروه '{group}' ایجاد شد", + "group_already_exist_on_system_but_removing_it": "گروه {group} از قبل در گروه های سیستم وجود دارد ، اما YunoHost آن را حذف می کند...", + "group_already_exist_on_system": "گروه {group} از قبل در گروه های سیستم وجود دارد", + "group_already_exist": "گروه {group} از قبل وجود دارد", + "good_practices_about_user_password": "گذرواژه باید حداقل 8 کاراکتر باشد - اگرچه استفاده از گذرواژه طولانی تر (به عنوان مثال عبارت عبور) و/یا استفاده از تنوع کاراکترها (بزرگ ، کوچک ، رقم و کاراکتر های خاص) تمرین خوبی است.", + "good_practices_about_admin_password": "اکنون می خواهید گذرواژه جدیدی برای مدیریت تعریف کنید. گذرواژه باید حداقل 8 کاراکتر باشد - اگرچه استفاده از گذرواژه طولانی تر (به عنوان مثال عبارت عبور) و/یا استفاده از تنوع کاراکترها (بزرگ ، کوچک ، رقم و کاراکتر های خاص) تمرین خوبی است.", + "global_settings_unknown_type": "وضعیت غیرمنتظره ، به نظر می رسد که تنظیمات {setting} دارای نوع {unknown_type} است اما از نوع پشتیبانی شده توسط سیستم نیست.", + "global_settings_setting_backup_compress_tar_archives": "هنگام ایجاد پشتیبان جدید ، بایگانی های فشرده (.tar.gz) را به جای بایگانی های فشرده نشده (.tar) انتخاب کنید. N.B. : فعال کردن این گزینه به معنای ایجاد آرشیوهای پشتیبان سبک تر است ، اما روش پشتیبان گیری اولیه به طور قابل توجهی طولانی تر و سنگین تر بر روی CPU خواهد بود.", + "global_settings_setting_security_experimental_enabled": "فعال کردن ویژگی های امنیتی آزمایشی (اگر نمی دانید در حال انجام چه کاری هستید این کار را انجام ندهید!)", + "global_settings_setting_security_webadmin_allowlist": "آدرس های IP که مجاز به دسترسی مدیر وب هستند. جدا شده با ویرگول.", + "global_settings_setting_security_webadmin_allowlist_enabled": "فقط به برخی از IP ها اجازه دسترسی به مدیریت وب را بدهید.", + "global_settings_setting_smtp_relay_password": "رمز عبور میزبان رله SMTP", + "global_settings_setting_smtp_relay_user": "حساب کاربری رله SMTP", + "global_settings_setting_smtp_relay_port": "پورت رله SMTP", + "global_settings_setting_smtp_relay_host": "میزبان رله SMTP برای ارسال نامه به جای این نمونه yunohost استفاده می شود. اگر در یکی از این شرایط قرار دارید مفید است: پورت 25 شما توسط ارائه دهنده ISP یا VPS شما مسدود شده است، شما یک IP مسکونی دارید که در DUHL ذکر شده است، نمی توانید DNS معکوس را پیکربندی کنید یا این سرور مستقیماً در اینترنت نمایش داده نمی شود و می خواهید از یکی دیگر برای ارسال ایمیل استفاده کنید.", + "global_settings_setting_smtp_allow_ipv6": "اجازه دهید از IPv6 برای دریافت و ارسال نامه استفاده شود", + "global_settings_setting_ssowat_panel_overlay_enabled": "همپوشانی پانل SSOwat را فعال کنید", + "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "اجازه دهید از کلید میزبان DSA (منسوخ شده) برای پیکربندی SH daemon استفاده شود", + "global_settings_unknown_setting_from_settings_file": "کلید ناشناخته در تنظیمات: '{setting_key}'، آن را کنار گذاشته و در /etc/yunohost/settings-unknown.json ذخیره کنید", + "global_settings_setting_security_ssh_port": "درگاه SSH", + "global_settings_setting_security_postfix_compatibility": "سازگاری در مقابل مبادله امنیتی برای سرور Postfix. روی رمزها (و سایر جنبه های مرتبط با امنیت) تأثیر می گذارد", + "global_settings_setting_security_ssh_compatibility": "سازگاری در مقابل مبادله امنیتی برای سرور SSH. روی رمزها (و سایر جنبه های مرتبط با امنیت) تأثیر می گذارد", + "global_settings_setting_security_password_user_strength": "قدرت رمز عبور کاربر", + "global_settings_setting_security_password_admin_strength": "قدرت رمز عبور مدیر", + "global_settings_setting_security_nginx_compatibility": "سازگاری در مقابل مبادله امنیتی برای وب سرور NGINX. روی رمزها (و سایر جنبه های مرتبط با امنیت) تأثیر می گذارد", + "global_settings_setting_pop3_enabled": "پروتکل POP3 را برای سرور ایمیل فعال کنید", + "global_settings_reset_success": "تنظیمات قبلی اکنون در {path} پشتیبان گیری شده است", + "global_settings_key_doesnt_exists": "کلید '{settings_key}' در تنظیمات جهانی وجود ندارد ، با اجرای 'لیست تنظیمات yunohost' می توانید همه کلیدهای موجود را مشاهده کنید", + "global_settings_cant_write_settings": "فایل تنظیمات ذخیره نشد، به دلیل: {reason}", + "global_settings_cant_serialize_settings": "سریال سازی داده های تنظیمات انجام نشد، به دلیل: {reason}", + "global_settings_cant_open_settings": "فایل تنظیمات باز نشد ، به دلیل: {reason}", + "global_settings_bad_type_for_setting": "نوع نادرست برای تنظیم {setting} ، دریافت شده {received_type}، مورد انتظار {expected_type}", + "global_settings_bad_choice_for_enum": "انتخاب نادرست برای تنظیم {setting} ، '{choice}' دریافت شد ، اما گزینه های موجود عبارتند از: {available_choices}", + "firewall_rules_cmd_failed": "برخی از دستورات قانون فایروال شکست خورده است. اطلاعات بیشتر در گزارش.", + "firewall_reloaded": "فایروال بارگیری مجدد شد", + "firewall_reload_failed": "بارگیری مجدد فایروال امکان پذیر نیست", + "file_does_not_exist": "فایل {path} وجود ندارد.", + "field_invalid": "فیلد نامعتبر '{}'", + "experimental_feature": "هشدار: این ویژگی آزمایشی است و پایدار تلقی نمی شود ، نباید از آن استفاده کنید مگر اینکه بدانید در حال انجام چه کاری هستید.", + "extracting": "استخراج...", + "dyndns_unavailable": "دامنه '{domain}' در دسترس نیست.", + "dyndns_domain_not_provided": "ارائه دهنده DynDNS {provider} نمی تواند دامنه {domain} را ارائه دهد.", + "dyndns_registration_failed": "دامنه DynDNS ثبت نشد: {error}", + "dyndns_registered": "دامنه DynDNS ثبت شد", + "dyndns_provider_unreachable": "دسترسی به ارائه دهنده DynDNS {provider} امکان پذیر نیست: یا YunoHost شما به درستی به اینترنت متصل نیست یا سرور dynette خراب است.", + "dyndns_no_domain_registered": "هیچ دامنه ای با DynDNS ثبت نشده است", + "dyndns_key_not_found": "کلید DNS برای دامنه یافت نشد", + "dyndns_key_generating": "ایجاد کلید DNS... ممکن است مدتی طول بکشد.", + "dyndns_ip_updated": "IP خود را در DynDNS به روز کرد", + "dyndns_ip_update_failed": "آدرس IP را به DynDNS به روز نکرد", + "dyndns_could_not_check_available": "بررسی نشد که آیا {domain} در {provider} در دسترس است یا خیر.", + "dyndns_could_not_check_provide": "بررسی نشد که آیا {provider} می تواند {domain} را ارائه دهد یا خیر.", + "dpkg_lock_not_available": "این دستور در حال حاضر قابل اجرا نیست زیرا به نظر می رسد برنامه دیگری از قفل dpkg (مدیر بسته سیستم) استفاده می کند", + "dpkg_is_broken": "شما نمی توانید این کار را در حال حاضر انجام دهید زیرا dpkg/APT (اداره کنندگان سیستم بسته ها) به نظر می رسد در وضعیت خرابی است… می توانید با اتصال از طریق SSH و اجرا این فرمان `sudo apt install --fix-broken` and/or `sudo dpkg --configure -a` مشکل را حل کنید.", + "downloading": "در حال بارگیری…", + "done": "انجام شد", + "domains_available": "دامنه های موجود:", + "domain_unknown": "دامنه ناشناخته", + "domain_name_unknown": "دامنه '{domain}' ناشناخته است", + "domain_uninstall_app_first": "این برنامه ها هنوز روی دامنه شما نصب هستند:\n{apps}\n\nلطفاً قبل از اقدام به حذف دامنه ، آنها را با استفاده از 'برنامه yunohost remove the_app_id' حذف کرده یا با استفاده از 'yunohost app change-url the_app_id' به دامنه دیگری منتقل کنید", + "domain_remove_confirm_apps_removal": "حذف این دامنه برنامه های زیر را حذف می کند:\n{apps}\n\nآیا طمئن هستید که میخواهید انجام دهید؟ [{answers}]", + "domain_hostname_failed": "نام میزبان جدید قابل تنظیم نیست. این ممکن است بعداً مشکلی ایجاد کند (ممکن هم هست خوب باشد).", + "domain_exists": "دامنه از قبل وجود دارد", + "domain_dyndns_root_unknown": "دامنه ریشه DynDNS ناشناخته", + "domain_dyndns_already_subscribed": "شما قبلاً در یک دامنه DynDNS مشترک شده اید", + "domain_dns_conf_is_just_a_recommendation": "این دستور پیکربندی * توصیه شده * را به شما نشان می دهد. در واقع پیکربندی DNS را برای شما تنظیم نمی کند. این وظیفه شماست که مطابق این توصیه ، منطقه DNS خود را در ثبت کننده خود پیکربندی کنید.", + "domain_deletion_failed": "حذف دامنه {domain} امکان پذیر نیست: {error}", + "domain_deleted": "دامنه حذف شد", + "domain_creation_failed": "ایجاد دامنه {domain} امکان پذیر نیست: {error}", + "domain_created": "دامنه ایجاد شد", + "domain_cert_gen_failed": "گواهی تولید نشد" } From 5501556dffb359153b9c1073dd2e89364318122d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 1 Sep 2021 15:02:30 +0200 Subject: [PATCH 2855/3170] ci: fix test jobs for i18n keys --- .gitlab/ci/test.gitlab-ci.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.gitlab/ci/test.gitlab-ci.yml b/.gitlab/ci/test.gitlab-ci.yml index a9e14b6e4..e0e0e001a 100644 --- a/.gitlab/ci/test.gitlab-ci.yml +++ b/.gitlab/ci/test.gitlab-ci.yml @@ -53,15 +53,17 @@ full-tests: test-i18n-keys: extends: .test-stage script: - - python3 -m pytest tests tests/test_i18n_keys.py + - python3 -m pytest tests/test_i18n_keys.py only: changes: - - locales/* + - locales/en.json + - src/yunohost/*.py + - data/hooks/diagnosis/*.py test-translation-format-consistency: extends: .test-stage script: - - python3 -m pytest tests tests/test_translation_format_consistency.py + - python3 -m pytest tests/test_translation_format_consistency.py only: changes: - locales/* From 9dc0c9b087f62e69cb04d893fe6b0ec2d4e5f0be Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 1 Sep 2021 15:08:14 +0200 Subject: [PATCH 2856/3170] tests: use https instead of http to check app is indeed installed --- src/yunohost/tests/test_apps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/tests/test_apps.py b/src/yunohost/tests/test_apps.py index eba5a5916..43125341b 100644 --- a/src/yunohost/tests/test_apps.py +++ b/src/yunohost/tests/test_apps.py @@ -132,7 +132,7 @@ def app_is_exposed_on_http(domain, path, message_in_page): try: r = requests.get( - "http://127.0.0.1" + path + "/", + "https://127.0.0.1" + path + "/", headers={"Host": domain}, timeout=10, verify=False, From e8a63f21be7d95b31530f51b95c6de0480964c81 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 1 Sep 2021 15:21:07 +0200 Subject: [PATCH 2857/3170] ci: push a remove-stale-strings only if there are some non-whitespace changes --- .gitlab/ci/translation.gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab/ci/translation.gitlab-ci.yml b/.gitlab/ci/translation.gitlab-ci.yml index eef57ca22..d7962436c 100644 --- a/.gitlab/ci/translation.gitlab-ci.yml +++ b/.gitlab/ci/translation.gitlab-ci.yml @@ -16,7 +16,7 @@ remove-stale-translated-strings: # create a local branch that will overwrite distant one - git checkout -b "ci-remove-stale-translated-strings-${CI_COMMIT_REF_NAME}" --no-track - python3 remove_stale_translated_strings.py - - '[ $(git diff | wc -l) != 0 ] || exit 0' # stop if there is nothing to commit + - '[ $(git diff -w | wc -l) != 0 ] || exit 0' # stop if there is nothing to commit - git commit -am "[CI] Remove stale translated strings" || true - git push -f origin "ci-remove-stale-translated-strings-${CI_COMMIT_REF_NAME}":"ci-remove-stale-translated-strings-${CI_COMMIT_REF_NAME}" - hub pull-request -m "[CI] Remove stale translated strings" -b Yunohost:dev -p || true # GITHUB_USER and GITHUB_TOKEN registered here https://gitlab.com/yunohost/yunohost/-/settings/ci_cd From fe9ca56f8810e567705a33a925142c94804ed216 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 1 Sep 2021 15:48:35 +0200 Subject: [PATCH 2858/3170] README: translation status: use horizontal display + only 'core' --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fa3c839c9..9fc93740d 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Webadmin ([Yunohost-Admin](https://github.com/YunoHost/yunohost-admin)) | Single - You can help translate YunoHost on our [translation platform](https://translate.yunohost.org/engage/yunohost/?utm_source=widget)

-Translation status +Translation status

## License From 01bc6762aac99d7fcd85439ba69863f6a6ce0cd0 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 1 Sep 2021 16:36:38 +0200 Subject: [PATCH 2859/3170] registrars: remove unimplemented or unecessary stuff --- data/actionsmap/yunohost.yml | 24 ++++++------------------ src/yunohost/dns.py | 16 ++++------------ src/yunohost/domain.py | 14 +++++++------- 3 files changed, 17 insertions(+), 37 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 686502b2c..50b93342b 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -572,6 +572,12 @@ domain: registrar: subcategory_help: Manage domains registrars actions: + ### domain_registrar_catalog() + catalog: + action_help: List supported registrars API + api: GET /domains/registrars/catalog + + ### domain_registrar_set() set: action_help: Set domain registrar @@ -597,24 +603,6 @@ domain: extra: pattern: *pattern_domain - ### domain_registrar_list() - list: - action_help: List registrars configured by DNS zone - api: GET /domains/registrars - - ### domain_registrar_catalog() - catalog: - action_help: List supported registrars API - api: GET /domains/registrars/catalog - arguments: - -r: - full: --registrar-name - help: Display given registrar info to create form - -f: - full: --full - help: Display all details, including info to create forms - action: store_true - ### domain_registrar_push() push: action_help: Push DNS records to registrar diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index e3131bcdd..4e68203be 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -419,16 +419,8 @@ def domain_registrar_info(domain): return registrar_info -def domain_registrar_catalog(registrar_name, full): - registrars = read_yaml(REGISTRAR_LIST_PATH) - - if registrar_name: - if registrar_name not in registrars.keys(): - raise YunohostValidationError("domain_registrar_unknown", registrar=registrar_name) - else: - return registrars[registrar_name] - else: - return registrars +def domain_registrar_catalog(): + return read_yaml(REGISTRAR_LIST_PATH) def domain_registrar_set(domain, registrar, args): @@ -539,8 +531,8 @@ def domain_registrar_push(operation_logger, domain): # Finally, push the new record or update the existing one record_to_push = { - "action": "update" if already_exists else "create" - "type": record["type"] + "action": "update" if already_exists else "create", + "type": record["type"], "name": record["name"], "content": record["value"], # FIXME Removed TTL, because it doesn't work with Gandi. diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index e1b247d7d..bc2e6d7af 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -536,14 +536,9 @@ def domain_dns_conf(domain): return yunohost.dns.domain_dns_conf(domain) -def domain_registrar_info(domain): +def domain_registrar_catalog(): import yunohost.dns - return yunohost.dns.domain_registrar_info(domain) - - -def domain_registrar_catalog(registrar_name, full): - import yunohost.dns - return yunohost.dns.domain_registrar_catalog(registrar_name, full) + return yunohost.dns.domain_registrar_catalog() def domain_registrar_set(domain, registrar, args): @@ -551,6 +546,11 @@ def domain_registrar_set(domain, registrar, args): return yunohost.dns.domain_registrar_set(domain, registrar, args) +def domain_registrar_info(domain): + import yunohost.dns + return yunohost.dns.domain_registrar_info(domain) + + def domain_registrar_push(domain): import yunohost.dns return yunohost.dns.domain_registrar_push(domain) From d5b1eecd0754fd7cbd9a4bddcd222389b01b2c26 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 1 Sep 2021 16:53:28 +0200 Subject: [PATCH 2860/3170] Add a small _assert_domain_exists to avoid always repeating the same code snippet --- src/yunohost/app.py | 14 +++++++------- src/yunohost/certificate.py | 14 ++++---------- src/yunohost/dns.py | 15 +++++---------- src/yunohost/domain.py | 23 +++++++++++------------ src/yunohost/permission.py | 11 ++++------- src/yunohost/user.py | 5 ++--- 6 files changed, 33 insertions(+), 49 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index f4acb198f..09136ef48 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1340,7 +1340,7 @@ def app_makedefault(operation_logger, app, domain=None): domain """ - from yunohost.domain import domain_list + from yunohost.domain import _assert_domain_exists app_settings = _get_app_settings(app) app_domain = app_settings["domain"] @@ -1348,9 +1348,10 @@ def app_makedefault(operation_logger, app, domain=None): if domain is None: domain = app_domain - operation_logger.related_to.append(("domain", domain)) - elif domain not in domain_list()["domains"]: - raise YunohostValidationError("domain_name_unknown", domain=domain) + + _assert_domain_exists(domain) + + operation_logger.related_to.append(("domain", domain)) if "/" in app_map(raw=True)[domain]: raise YunohostValidationError( @@ -3078,13 +3079,12 @@ def _get_conflicting_apps(domain, path, ignore_app=None): ignore_app -- An optional app id to ignore (c.f. the change_url usecase) """ - from yunohost.domain import domain_list + from yunohost.domain import _assert_domain_exists domain, path = _normalize_domain_path(domain, path) # Abort if domain is unknown - if domain not in domain_list()["domains"]: - raise YunohostValidationError("domain_name_unknown", domain=domain) + _assert_domain_exists(domain) # Fetch apps map apps_map = app_map(raw=True) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 52d58777b..817f9d57a 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -86,11 +86,8 @@ def certificate_status(domain_list, full=False): domain_list = yunohost.domain.domain_list()["domains"] # Else, validate that yunohost knows the domains given else: - yunohost_domains_list = yunohost.domain.domain_list()["domains"] for domain in domain_list: - # Is it in Yunohost domain list? - if domain not in yunohost_domains_list: - raise YunohostValidationError("domain_name_unknown", domain=domain) + yunohost.domain._assert_domain_exists(domain) certificates = {} @@ -267,9 +264,7 @@ def _certificate_install_letsencrypt( # Else, validate that yunohost knows the domains given else: for domain in domain_list: - yunohost_domains_list = yunohost.domain.domain_list()["domains"] - if domain not in yunohost_domains_list: - raise YunohostValidationError("domain_name_unknown", domain=domain) + yunohost.domain._assert_domain_exists(domain) # Is it self-signed? status = _get_status(domain) @@ -368,9 +363,8 @@ def certificate_renew( else: for domain in domain_list: - # Is it in Yunohost dmomain list? - if domain not in yunohost.domain.domain_list()["domains"]: - raise YunohostValidationError("domain_name_unknown", domain=domain) + # Is it in Yunohost domain list? + yunohost.domain._assert_domain_exists(domain) status = _get_status(domain) diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index 4e68203be..8399c5a4c 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -30,7 +30,7 @@ from moulinette import m18n, Moulinette from moulinette.utils.log import getActionLogger from moulinette.utils.filesystem import mkdir, read_yaml, write_to_yaml -from yunohost.domain import domain_list, _get_domain_settings +from yunohost.domain import domain_list, _get_domain_settings, _assert_domain_exists from yunohost.app import _parse_args_in_yunohost_format from yunohost.utils.error import YunohostValidationError from yunohost.utils.network import get_public_ip @@ -52,8 +52,7 @@ def domain_dns_conf(domain): """ - if domain not in domain_list()["domains"]: - raise YunohostValidationError("domain_name_unknown", domain=domain) + _assert_domain_exists(domain) dns_conf = _build_dns_conf(domain) @@ -94,13 +93,10 @@ def domain_dns_conf(domain): def _list_subdomains_of(parent_domain): - domain_list_ = domain_list()["domains"] - - if parent_domain not in domain_list_: - raise YunohostError("domain_name_unknown", domain=domain) + _assert_domain_exists(parent_domain) out = [] - for domain in domain_list_: + for domain in domain_list()["domains"]: if domain.endswith(f".{parent_domain}"): out.append(domain) @@ -462,8 +458,7 @@ def domain_registrar_push(operation_logger, domain): from lexicon.client import Client as LexiconClient from lexicon.config import ConfigResolver as LexiconConfigResolver - if domain not in domain_list()["domains"]: - raise YunohostValidationError("domain_name_unknown", domain=domain) + _assert_domain_exists(domain) dns_zone = _get_domain_settings(domain)["dns_zone"] registrar_settings = _get_registrar_settingss(dns_zone) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index bc2e6d7af..d7863a0e1 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -96,6 +96,11 @@ def domain_list(exclude_subdomains=False): return domain_list_cache +def _assert_domain_exists(domain): + if domain not in domain_list()["domains"]: + raise YunohostValidationError("domain_name_unknown", domain=domain) + + @is_unit_operation() def domain_add(operation_logger, domain, dyndns=False): """ @@ -216,8 +221,8 @@ def domain_remove(operation_logger, domain, remove_apps=False, force=False): # the 'force' here is related to the exception happening in domain_add ... # we don't want to check the domain exists because the ldap add may have # failed - if not force and domain not in domain_list()["domains"]: - raise YunohostValidationError("domain_name_unknown", domain=domain) + if not force: + _assert_domain_exists(domain) # Check domain is not the main domain if domain == _get_maindomain(): @@ -339,8 +344,7 @@ def domain_main_domain(operation_logger, new_main_domain=None): return {"current_main_domain": _get_maindomain()} # Check domain exists - if new_main_domain not in domain_list()["domains"]: - raise YunohostValidationError("domain_name_unknown", domain=new_main_domain) + _assert_domain_exists(new_main_domain) operation_logger.related_to.append(("domain", new_main_domain)) operation_logger.start() @@ -399,12 +403,7 @@ def _get_domain_settings(domain): Retrieve entries in /etc/yunohost/domains/[domain].yml And set default values if needed """ - # Retrieve actual domain list - known_domains = domain_list()["domains"] - maindomain = domain_list()["main"] - - if domain not in known_domains: - raise YunohostValidationError("domain_name_unknown", domain=domain) + _assert_domain_exists(domain) # Retrieve entries in the YAML filepath = f"{DOMAIN_SETTINGS_DIR}/{domain}.yml" @@ -485,8 +484,8 @@ def _set_domain_settings(domain, domain_settings): settings -- Dict with domain settings """ - if domain not in domain_list()["domains"]: - raise YunohostError("domain_name_unknown", domain=domain) + + _assert_domain_exists(domain) defaults = _default_domain_settings(domain) diff_with_defaults = {k: v for k, v in domain_settings.items() if defaults.get(k) != v} diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 01330ad7f..d579ff47a 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -860,11 +860,9 @@ def _validate_and_sanitize_permission_url(url, app_base_path, app): re:^/api/.*|/scripts/api.js$ """ - from yunohost.domain import domain_list + from yunohost.domain import _assert_domain_exists from yunohost.app import _assert_no_conflicting_apps - domains = domain_list()["domains"] - # # Regexes # @@ -896,8 +894,8 @@ def _validate_and_sanitize_permission_url(url, app_base_path, app): domain, path = url[3:].split("/", 1) path = "/" + path - if domain.replace("%", "").replace("\\", "") not in domains: - raise YunohostValidationError("domain_name_unknown", domain=domain) + domain_with_no_regex = domain.replace("%", "").replace("\\", "") + _assert_domain_exists(domain_with_no_regex) validate_regex(path) @@ -931,8 +929,7 @@ def _validate_and_sanitize_permission_url(url, app_base_path, app): domain, path = split_domain_path(url) sanitized_url = domain + path - if domain not in domains: - raise YunohostValidationError("domain_name_unknown", domain=domain) + _assert_domain_exists(domain) _assert_no_conflicting_apps(domain, path, ignore_app=app) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 01513f3bd..4863afea9 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -101,7 +101,7 @@ def user_create( mail=None, ): - from yunohost.domain import domain_list, _get_maindomain + from yunohost.domain import domain_list, _get_maindomain, _assert_domain_exists from yunohost.hook import hook_callback from yunohost.utils.password import assert_password_is_strong_enough from yunohost.utils.ldap import _get_ldap_interface @@ -135,8 +135,7 @@ def user_create( domain = maindomain # Check that the domain exists - if domain not in domain_list()["domains"]: - raise YunohostValidationError("domain_name_unknown", domain=domain) + _assert_domain_exists(domain) mail = username + "@" + domain ldap = _get_ldap_interface() From 1966b200b85b73e248ab12841ec739ff4b1ab890 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= Date: Wed, 1 Sep 2021 10:26:50 +0000 Subject: [PATCH 2861/3170] Translated using Weblate (French) Currently translated at 100.0% (651 of 651 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/locales/fr.json b/locales/fr.json index 9d382304d..7473f2d7d 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -641,5 +641,13 @@ "invalid_password": "Mot de passe incorrect", "ldap_server_is_down_restart_it": "Le service LDAP est en panne, essayez de le redémarrer...", "ldap_server_down": "Impossible d'atteindre le serveur LDAP", - "global_settings_setting_security_experimental_enabled": "Activer les fonctionnalités de sécurité expérimentales (ne l'activez pas si vous ne savez pas ce que vous faites !)" + "global_settings_setting_security_experimental_enabled": "Activer les fonctionnalités de sécurité expérimentales (ne l'activez pas si vous ne savez pas ce que vous faites !)", + "diagnosis_apps_deprecated_practices": "La version installée de cette application utilise toujours certaines pratiques de packaging obsolètes. Vous devriez vraiment envisager de mettre l'application à jour..", + "diagnosis_apps_outdated_ynh_requirement": "La version installée de cette application nécessite uniquement YunoHost> = 2.x, cela indique que l'application n'est pas à jour avec les bonnes pratiques de packaging et les helpers recommandées. Vous devriez vraiment envisager de mettre l'application à jour.", + "diagnosis_apps_bad_quality": "Cette application est actuellement signalée comme cassée dans le catalogue d'applications de YunoHost. Cela peut être un problème temporaire. En attendant que les mainteneurs tentent de résoudre le problème, la mise à jour de cette application est désactivée.", + "diagnosis_apps_broken": "Cette application est actuellement signalée comme cassée dans le catalogue d'applications de YunoHost. Cela peut être un problème temporaire. En attendant que les mainteneurs tentent de résoudre le problème, la mise à jour de cette application est désactivée.", + "diagnosis_apps_not_in_app_catalog": "Cette application est absente ou ne figure plus dans le catalogue d'applications de YunoHost. Vous devriez envisager de la désinstaller car elle ne recevra pas de mise à jour et pourrait compromettre l'intégrité et la sécurité de votre système.", + "diagnosis_apps_issue": "Un problème a été détecté pour l'application {app}", + "diagnosis_apps_allgood": "Toutes les applications installées respectent les pratiques de packaging de base", + "diagnosis_description_apps": "Applications" } From 33606404c451e8bc9c237b5e0cdb4f44eeff598c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= Date: Wed, 1 Sep 2021 12:42:40 +0000 Subject: [PATCH 2862/3170] Translated using Weblate (Ukrainian) Currently translated at 7.5% (49 of 651 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/uk/ --- locales/uk.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/locales/uk.json b/locales/uk.json index 71d7ee897..2e466685b 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -114,7 +114,7 @@ "permission_updated": "Дозвіл '{permission}' оновлено", "permission_update_failed": "Не вдалося оновити дозвіл '{permission}': {error}", "permission_not_found": "Дозвіл '{permission}', не знайдено", - "permission_deletion_failed": "Не вдалося видалити дозвіл '{permission}': {помилка}", + "permission_deletion_failed": "Не вдалося видалити дозвіл '{permission}': {error}", "permission_deleted": "Дозвіл '{permission}' видалено", "permission_cant_add_to_all_users": "Дозвіл {permission} не може бути додано всім користувачам.", "permission_currently_allowed_for_all_users": "В даний час цей дозвіл надається всім користувачам на додаток до інших груп. Ймовірно, вам потрібно або видалити дозвіл 'all_users', або видалити інші групи, яким воно зараз надано.", @@ -257,7 +257,7 @@ "installation_complete": "установка завершена", "hook_name_unknown": "Невідоме ім'я хука '{name}'", "hook_list_by_invalid": "Це властивість не може бути використано для перерахування хуков", - "hook_json_return_error": "Не вдалося розпізнати повернення з хука {шлях}. Помилка: {msg}. Необроблений контент: {raw_content}.", + "hook_json_return_error": "Не вдалося розпізнати повернення з хука {path}. Помилка: {msg}. Необроблений контент: {raw_content}", "hook_exec_not_terminated": "Скрипт не завершився належним чином: {шлях}", "hook_exec_failed": "Не вдалося запустити скрипт: {path}", "group_user_not_in_group": "Користувач {user} не входить в групу {group}", @@ -337,9 +337,9 @@ "domain_dyndns_root_unknown": "Невідомий кореневої домен DynDNS", "domain_dyndns_already_subscribed": "Ви вже підписалися на домен DynDNS", "domain_dns_conf_is_just_a_recommendation": "Ця команда показує * рекомендовану * конфігурацію. Насправді вона не встановлює конфігурацію DNS для вас. Ви самі повинні налаштувати свою зону DNS у реєстратора відповідно до цих рекомендацій.", - "domain_deletion_failed": "Неможливо видалити домен {domain}: {помилка}", + "domain_deletion_failed": "Неможливо видалити домен {domain}: {error}", "domain_deleted": "домен видалений", - "domain_creation_failed": "Неможливо створити домен {domain}: {помилка}", + "domain_creation_failed": "Неможливо створити домен {domain}: {error}", "domain_created": "домен створений", "domain_cert_gen_failed": "Не вдалося згенерувати сертифікат", "domain_cannot_remove_main_add_new_one": "Ви не можете видалити '{domain}', так як це основний домен і ваш єдиний домен, вам потрібно спочатку додати інший домен за допомогою 'yunohost domain add ', потім встановити його як основний домен за допомогою ' yunohost domain main-domain -n 'і потім ви можете видалити домен' {domain} 'за допомогою' yunohost domain remove {domain} ''.", @@ -362,7 +362,7 @@ "diagnosis_http_timeout": "При спробі зв'язатися з вашим сервером ззовні стався тайм-аут. Він здається недоступним.
1. Найбільш поширеною причиною цієї проблеми є те, що порт 80 (і 443) неправильно перенаправлений на ваш сервер .
2. Ви також повинні переконатися, що служба nginx запущена
3. На більш складних установках: переконайтеся, що немає брандмауера або зворотного проксі.", "diagnosis_http_ok": "Домен {domain} доступний по HTTP ззовні локальної мережі.", "diagnosis_http_localdomain": "Домен {domain} з доменом .local TLD не може бути доступний ззовні локальної мережі.", - "diagnosis_http_could_not_diagnose_details": "Помилка: {error}.", + "diagnosis_http_could_not_diagnose_details": "Помилка: {error}", "diagnosis_http_could_not_diagnose": "Не вдалося діагностувати досяжність доменів ззовні в IPv {ipversion}.", "diagnosis_http_hairpinning_issue_details": "Можливо, це пов'язано з блоком/маршрутизатором вашого інтернет-провайдера. В результаті, люди ззовні вашої локальної мережі зможуть отримати доступ до вашого сервера, як і очікувалося, але не люди зсередини локальної мережі (як ви, ймовірно?) При використанні доменного імені або глобального IP. Можливо, ви зможете поліпшити ситуацію, глянувши на https://yunohost.org/dns_local_network .", "diagnosis_http_hairpinning_issue": "Схоже, що у вашій локальній мережі не включена проброска.", @@ -403,7 +403,7 @@ "diagnosis_mail_fcrdns_nok_details": "Спочатку спробуйте налаштувати зворотний DNS з {ehlo_domain} в інтерфейсі вашого інтернет-маршрутизатора або в інтерфейсі вашого хостинг-провайдера. (Деякі хостинг-провайдери можуть вимагати, щоб ви відправили їм тікет підтримки для цього).", "diagnosis_mail_fcrdns_dns_missing": "У IPv {ipversion} не визначений зворотний DNS. Деякі листи можуть не доставлятися або позначатися як спам.", "diagnosis_mail_fcrdns_ok": "Ваш зворотний DNS налаштований правильно!", - "diagnosis_mail_ehlo_could_not_diagnose_details": "Помилка: {error}.", + "diagnosis_mail_ehlo_could_not_diagnose_details": "Помилка: {error}", "diagnosis_mail_ehlo_could_not_diagnose": "Не вдалося діагностувати наявність певної поштовий сервер postfix ззовні в IPv {ipversion}.", "diagnosis_mail_ehlo_wrong_details": "EHLO, отриманий віддаленим діагностичним центром в IPv {ipversion}, відрізняється від домену вашого сервера.
Отриманий EHLO: {wrong_ehlo}
Очікуваний: {right_ehlo} < br> найпоширенішою причиною цієї проблеми є те, що порт 25 неправильно перенаправлений на ваш сервер . Крім того, переконайтеся, що в роботу сервера не втручається брандмауер або зворотний проксі-сервер.", "diagnosis_mail_ehlo_wrong": "Інший поштовий SMTP-сервер відповідає на IPv {ipversion}. Ваш сервер, ймовірно, не зможе отримувати електронну пошту.", @@ -427,7 +427,7 @@ "user_home_creation_failed": "Не вдалося створити домашню папку для користувача", "user_deletion_failed": "Не вдалося видалити користувача {user}: {error}", "user_deleted": "користувача видалено", - "user_creation_failed": "Не вдалося створити користувача {user}: {помилка}", + "user_creation_failed": "Не вдалося створити користувача {user}: {error}", "user_created": "Аккаунт було створено", "user_already_exists": "Користувач '{user}' вже існує", "upnp_port_open_failed": "Не вдалося відкрити порт через UPnP", @@ -511,8 +511,8 @@ "diagnosis_failed": "Не вдалося результат діагностики для категорії '{категорія}': {error}", "diagnosis_everything_ok": "Все виглядає добре для {категорії}!", "diagnosis_found_warnings": "Знайдено {попередження} пунктів, які можна поліпшити для {категорії}.", - "diagnosis_found_errors_and_warnings": "Знайдено {помилки} істотний (і) питання (и) (і {попередження} попередження (я)), що відносяться до {категорії}!", - "diagnosis_found_errors": "Знайдена {помилка} важлива проблема (і), пов'язана з {категорією}!", + "diagnosis_found_errors_and_warnings": "Знайдено {errors} істотний (і) питання (и) (і {попередження} попередження (я)), що відносяться до {category}!", + "diagnosis_found_errors": "Знайдена {errors} важлива проблема (і), пов'язана з {category}!", "diagnosis_ignored_issues": "(+ {Nb_ignored} проігнорована проблема (проблеми))", "diagnosis_cant_run_because_of_dep": "Неможливо запустити діагностику для {категорії}, поки є важливі проблеми, пов'язані з {глибиною}.", "diagnosis_cache_still_valid": "(Кеш все ще дійсний для діагностики {категорії}. Повторна діагностика поки не проводиться!)", @@ -607,7 +607,7 @@ "ask_user_domain": "Домен для адреси електронної пошти користувача і облікового запису XMPP", "apps_catalog_update_success": "Каталог додатків був оновлений!", "apps_catalog_obsolete_cache": "Кеш каталогу додатків порожній або застарів.", - "apps_catalog_failed_to_download": "Неможливо завантажити каталог додатків {apps_catalog}: {помилка}.", + "apps_catalog_failed_to_download": "Неможливо завантажити каталог додатків {apps_catalog}: {error}", "apps_catalog_updating": "Оновлення каталогу додатків…", "apps_catalog_init_success": "Система каталогу додатків инициализирована!", "apps_already_up_to_date": "Всі додатки вже оновлені", From 90d78348084d5aa86718fbf7b598be97bff29040 Mon Sep 17 00:00:00 2001 From: Parviz Homayun Date: Wed, 1 Sep 2021 15:43:49 +0000 Subject: [PATCH 2863/3170] Translated using Weblate (Persian) Currently translated at 79.1% (509 of 643 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fa/ --- locales/fa.json | 141 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 140 insertions(+), 1 deletion(-) diff --git a/locales/fa.json b/locales/fa.json index 31a24e269..9e33d02d7 100644 --- a/locales/fa.json +++ b/locales/fa.json @@ -368,5 +368,144 @@ "domain_deleted": "دامنه حذف شد", "domain_creation_failed": "ایجاد دامنه {domain} امکان پذیر نیست: {error}", "domain_created": "دامنه ایجاد شد", - "domain_cert_gen_failed": "گواهی تولید نشد" + "domain_cert_gen_failed": "گواهی تولید نشد", + "permission_creation_failed": "مجوز '{permission}' را نمیتوان ایجاد کرد: {error}", + "permission_created": "مجوز '{permission}' ایجاد شد", + "permission_cannot_remove_main": "حذف مجوز اصلی مجاز نیست", + "permission_already_up_to_date": "مجوز به روز نشد زیرا درخواست های افزودن/حذف هم اینک با وضعیّت فعلی مطابقت دارد.", + "permission_already_exist": "مجوز '{permission}' در حال حاضر وجود دارد", + "permission_already_disallowed": "گروه '{group}' قبلاً مجوز '{permission}' را غیرفعال کرده است", + "permission_already_allowed": "گروه '{group}' قبلاً مجوز '{permission}' را فعال کرده است", + "pattern_password_app": "متأسفیم ، گذرواژه ها نمی توانند شامل کاراکترهای زیر باشند: {forbidden_chars}", + "pattern_username": "باید فقط حروف الفبایی کوچک و خط زیر باشد", + "pattern_positive_number": "باید یک عدد مثبت باشد", + "pattern_port_or_range": "باید یک شماره پورت معتبر (یعنی 0-65535) یا محدوده پورت (به عنوان مثال 100: 200) باشد", + "pattern_password": "باید حداقل 3 کاراکتر داشته باشد", + "pattern_mailbox_quota": "باید اندازه ای با پسوند b / k / M / G / T یا 0 داشته باشد تا سهمیه نداشته باشد", + "pattern_lastname": "باید نام خانوادگی معتبر باشد", + "pattern_firstname": "باید یک نام کوچک معتبر باشد", + "pattern_email": "باید یک آدرس ایمیل معتبر باشد ، بدون نماد '+' (به عنوان مثال someone@example.com)", + "pattern_email_forward": "باید یک آدرس ایمیل معتبر باشد ، نماد '+' پذیرفته شده است (به عنوان مثال someone+tag@example.com)", + "pattern_domain": "باید یک نام دامنه معتبر باشد (به عنوان مثال my-domain.org)", + "pattern_backup_archive_name": "باید یک نام فایل معتبر با حداکثر 30 کاراکتر حرف و عدد و -_ باشد. فقط کاراکترها", + "password_too_simple_4": "گذرواژه باید حداقل 12 کاراکتر طول داشته باشد و شامل عدد ، حروف الفبائی کوچک و بزرگ و کاراکترهای خاص باشد", + "password_too_simple_3": "گذرواژه باید حداقل 8 کاراکتر طول داشته باشد و شامل عدد ، حروف الفبائی کوچک و بزرگ و کاراکترهای خاص باشد", + "password_too_simple_2": "گذرواژه باید حداقل 8 کاراکتر طول داشته باشد و شامل عدد ، حروف الفبائی کوچک و بزرگ باشد", + "password_too_simple_1": "رمز عبور باید حداقل 8 کاراکتر باشد", + "password_listed": "این رمز در بین پر استفاده ترین رمزهای عبور در جهان قرار دارد. لطفاً چیزی منحصر به فرد تر انتخاب کنید.", + "packages_upgrade_failed": "همه بسته ها را نمی توان ارتقا داد", + "operation_interrupted": "عملیات به صورت دستی قطع شد؟", + "invalid_password": "رمز عبور نامعتبر", + "invalid_number": "باید یک عدد باشد", + "not_enough_disk_space": "فضای آزاد کافی در '{path}' وجود ندارد", + "migrations_to_be_ran_manually": "مهاجرت {id} باید به صورت دستی اجرا شود. لطفاً به صفحه Tools → Migrations در صفحه webadmin بروید، یا `yunohost tools migrations run` را اجرا کنید.", + "migrations_success_forward": "مهاجرت {id} تکمیل شد", + "migrations_skip_migration": "رد کردن مهاجرت {id}...", + "migrations_running_forward": "مهاجرت در حال اجرا {id}»...", + "migrations_pending_cant_rerun": "این مهاجرت ها هنوز در انتظار هستند ، بنابراین نمی توان آنها را دوباره اجرا کرد: {ids}", + "migrations_not_pending_cant_skip": "این مهاجرت ها معلق نیستند ، بنابراین نمی توان آنها را رد کرد: {ids}", + "migrations_no_such_migration": "مهاجرتی به نام '{id}' وجود ندارد", + "migrations_no_migrations_to_run": "مهاجرتی برای اجرا وجود ندارد", + "migrations_need_to_accept_disclaimer": "برای اجرای مهاجرت {id} ، باید سلب مسئولیت زیر را بپذیرید:\n---\n{disclaimer}\n---\nاگر می خواهید مهاجرت را اجرا کنید ، لطفاً فرمان را با گزینه '--accept-disclaimer' دوباره اجرا کنید.", + "migrations_must_provide_explicit_targets": "هنگام استفاده '--skip' یا '--force-rerun' باید اهداف مشخصی را ارائه دهید", + "migrations_migration_has_failed": "مهاجرت {id} کامل نشد ، لغو شد. خطا: {exception}", + "migrations_loading_migration": "بارگیری مهاجرت {id}...", + "migrations_list_conflict_pending_done": "شما نمیتوانید از هر دو انتخاب '--previous' و '--done' به طور همزمان استفاده کنید.", + "migrations_exclusive_options": "'--auto', '--skip'، و '--force-rerun' گزینه های متقابل هستند.", + "migrations_failed_to_load_migration": "مهاجرت بار نشد {id}: {error}", + "migrations_dependencies_not_satisfied": "این مهاجرت ها را اجرا کنید: '{dependencies_id}' ، قبل از مهاجرت {id}.", + "migrations_cant_reach_migration_file": "دسترسی به پرونده های مهاجرت در مسیر '٪ s' امکان پذیر نیست", + "migrations_already_ran": "این مهاجرت ها قبلاً انجام شده است: {ids}", + "migration_0019_slapd_config_will_be_overwritten": "به نظر می رسد که شما پیکربندی slapd را به صورت دستی ویرایش کرده اید. برای این مهاجرت بحرانی ، YunoHost باید به روز رسانی پیکربندی slapd را مجبور کند. فایلهای اصلی در {conf_backup_folder} پشتیبان گیری می شوند.", + "migration_0019_add_new_attributes_in_ldap": "اضافه کردن ویژگی های جدید برای مجوزها در پایگاه داده LDAP", + "migration_0018_failed_to_reset_legacy_rules": "تنظیم مجدد قوانین iptables قدیمی انجام نشد: {error}", + "migration_0018_failed_to_migrate_iptables_rules": "انتقال قوانین قدیمی iptables به nftables انجام نشد: {error}", + "migration_0017_not_enough_space": "فضای کافی در {path} برای اجرای مهاجرت در دسترس قرار دهید.", + "migration_0017_postgresql_11_not_installed": "PostgreSQL 9.6 نصب شده است ، اما postgresql 11 نه؟ ممکن است اتفاق عجیبی در سیستم شما رخ داده باشد:(...", + "migration_0017_postgresql_96_not_installed": "PostgreSQL روی سیستم شما نصب نشده است. کاری برای انجام دادن نیست.", + "migration_0015_weak_certs": "گواهینامه های زیر هنوز از الگوریتم های امضای ضعیف استفاده می کنند و برای سازگاری با نسخه بعدی nginx باید ارتقاء یابند: {certs}", + "migration_0015_cleaning_up": "پاک کردن حافظه پنهان و بسته ها دیگر مفید نیست...", + "migration_0015_specific_upgrade": "شروع به روزرسانی بسته های سیستم که باید به طور مستقل ارتقا یابد...", + "migration_0015_modified_files": "لطفاً توجه داشته باشید که فایل های زیر به صورت دستی اصلاح شده اند و ممکن است پس از ارتقاء رونویسی شوند: {manually_modified_files}", + "migration_0015_problematic_apps_warning": "لطفاً توجه داشته باشید که احتمالاً برنامه های نصب شده مشکل ساز تشخیص داده شده. به نظر می رسد که آنها از فهرست برنامه YunoHost نصب نشده اند یا به عنوان 'working' علامت گذاری نشده اند. در نتیجه ، نمی توان تضمین کرد که پس از ارتقاء همچنان کار خواهند کرد: {problematic_apps}", + "migration_0015_general_warning": "لطفاً توجه داشته باشید که این مهاجرت یک عملیات ظریف است. تیم YunoHost تمام تلاش خود را برای بررسی و آزمایش آن انجام داد ، اما مهاجرت ممکن است بخشهایی از سیستم یا برنامه های آن را خراب کند.\n\nبنابراین ، توصیه می شود:\n- پشتیبان گیری از هرگونه داده یا برنامه حیاتی را انجام دهید. اطلاعات بیشتر در https://yunohost.org/backup ؛\n- پس از راه اندازی مهاجرت صبور باشید: بسته به اتصال به اینترنت و سخت افزار شما ، ممکن است چند ساعت طول بکشد تا همه چیز ارتقا یابد.", + "migration_0015_system_not_fully_up_to_date": "سیستم شما کاملاً به روز نیست. لطفاً قبل از اجرای مهاجرت به Buster ، یک ارتقاء منظم انجام دهید.", + "migration_0015_not_enough_free_space": "فضای آزاد در /var /بسیار کم است! برای اجرای این مهاجرت باید حداقل 1 گیگابایت فضای آزاد داشته باشید.", + "migration_0015_not_stretch": "توزیع دبیان فعلی استرچ نیست!", + "migration_0015_yunohost_upgrade": "شروع به روز رسانی اصلی YunoHost...", + "migration_0015_still_on_stretch_after_main_upgrade": "هنگام ارتقاء اصلی مشکلی پیش آمد ، به نظر می رسد سیستم هنوز در Debian Stretch است", + "migration_0015_main_upgrade": "شروع به روزرسانی اصلی...", + "migration_0015_patching_sources_list": "وصله منابع. لیست ها...", + "migration_0015_start": "شروع مهاجرت به باستر", + "migration_update_LDAP_schema": "در حال به روزرسانی طرح وشمای LDAP...", + "migration_ldap_rollback_success": "سیستم برگردانده شد.", + "migration_ldap_migration_failed_trying_to_rollback": "نمی توان مهاجرت کرد... تلاش برای بازگرداندن سیستم.", + "migration_ldap_can_not_backup_before_migration": "نمی توان پشتیبان گیری سیستم را قبل از شکست مهاجرت تکمیل کرد. خطا: {error}", + "migration_ldap_backup_before_migration": "ایجاد پشتیبان از پایگاه داده LDAP و تنظیمات برنامه ها قبل از مهاجرت واقعی.", + "migration_description_0020_ssh_sftp_permissions": "پشتیبانی مجوزهای SSH و SFTP را اضافه کنید", + "migration_description_0019_extend_permissions_features": "سیستم مدیریت مجوز برنامه را تمدید / دوباره کار بندازید", + "migration_description_0018_xtable_to_nftable": "مهاجرت از قوانین قدیمی ترافیک شبکه به سیستم جدید nftable", + "migration_description_0017_postgresql_9p6_to_11": "مهاجرت پایگاه های داده از PostgreSQL 9.6 به 11", + "migration_description_0016_php70_to_php73_pools": "انتقال فایلهای conf php7.0-fpm 'pool' به php7.3", + "migration_description_0015_migrate_to_buster": "سیستم را به Debian Buster و YunoHost 4.x ارتقا دهید", + "migrating_legacy_permission_settings": "در حال انتقال تنظیمات مجوز قدیمی...", + "main_domain_changed": "دامنه اصلی تغییر کرده است", + "main_domain_change_failed": "تغییر دامنه اصلی امکان پذیر نیست", + "mail_unavailable": "این آدرس ایمیل محفوظ است و باید به طور خودکار به اولین کاربر اختصاص داده شود", + "mailbox_used_space_dovecot_down": "اگر می خواهید فضای صندوق پستی استفاده شده را واکشی کنید ، سرویس صندوق پستی Dovecot باید فعال باشد", + "mailbox_disabled": "ایمیل برای کاربر {user} خاموش است", + "mail_forward_remove_failed": "ارسال ایمیل '{mail}' حذف نشد", + "mail_domain_unknown": "آدرس ایمیل نامعتبر برای دامنه '{domain}'. لطفاً از دامنه ای که توسط این سرور اداره می شود استفاده کنید.", + "mail_alias_remove_failed": "نام مستعار ایمیل '{mail}' حذف نشد", + "log_tools_reboot": "سرور خود را راه اندازی مجدد کنید", + "log_tools_shutdown": "سرور خود را خاموش کنید", + "log_tools_upgrade": "بسته های سیستم را ارتقا دهید", + "log_tools_postinstall": "اسکریپت پس از نصب سرور YunoHost خود را نصب کنید", + "log_tools_migrations_migrate_forward": "اجرای مهاجرت ها", + "log_domain_main_domain": "'{}' را دامنه اصلی کنید", + "log_user_permission_reset": "بازنشانی مجوز '{}'", + "log_user_permission_update": "دسترسی ها را برای مجوزهای '{}' به روز کنید", + "log_user_update": "به روزرسانی اطلاعات کاربر '{}'", + "log_user_group_update": "به روزرسانی گروه '{}'", + "log_user_group_delete": "حذف گروه '{}'", + "log_user_group_create": "ایجاد گروه '{}'", + "log_user_delete": "کاربر '{}' را حذف کنید", + "log_user_create": "کاربر '{}' را اضافه کنید", + "log_regen_conf": "بازسازی تنظیمات سیستم '{}'", + "log_letsencrypt_cert_renew": "تمدید '{}' گواهی اجازه رمزگذاری", + "log_selfsigned_cert_install": "گواهی خود امضا شده را در دامنه '{}' نصب کنید", + "log_permission_url": "به روزرسانی نشانی اینترنتی مربوط به مجوز دسترسی '{}'", + "log_permission_delete": "حذف مجوز دسترسی '{}'", + "log_permission_create": "ایجاد مجوز دسترسی '{}'", + "log_letsencrypt_cert_install": "گواهی اجازه رمزگذاری را در دامنه '{}' نصب کنید", + "log_dyndns_update": "IP مرتبط با '{}' زیر دامنه YunoHost خود را به روز کنید", + "log_dyndns_subscribe": "مشترک شدن در زیر دامنه YunoHost '{}'", + "log_domain_remove": "دامنه '{}' را از پیکربندی سیستم حذف کنید", + "log_domain_add": "دامنه '{}' را به پیکربندی سیستم اضافه کنید", + "log_remove_on_failed_install": "پس از نصب ناموفق '{}' را حذف کنید", + "log_remove_on_failed_restore": "پس از بازیابی ناموفق از بایگانی پشتیبان، '{}' را حذف کنید", + "log_backup_restore_app": "بازیابی '{}' از بایگانی پشتیبان", + "log_backup_restore_system": "بازیابی سیستم بوسیله آرشیو پشتیبان", + "log_backup_create": "بایگانی پشتیبان ایجاد کنید", + "log_available_on_yunopaste": "این گزارش اکنون از طریق {url} در دسترس است", + "log_app_config_apply": "پیکربندی را در برنامه '{}' اعمال کنید", + "log_app_config_show_panel": "پانل پیکربندی برنامه '{}' را نشان دهید", + "log_app_action_run": "عملکرد برنامه '{}' را اجرا کنید", + "log_app_makedefault": "\"{}\" را برنامه پیش فرض قرار دهید", + "log_app_upgrade": "برنامه '{}' را ارتقاء دهید", + "log_app_remove": "برنامه '{}' را حذف کنید", + "log_app_install": "برنامه '{}' را نصب کنید", + "log_app_change_url": "نشانی وب برنامه '{}' را تغییر دهید", + "log_operation_unit_unclosed_properly": "واحد عملیّات به درستی بسته نشده است", + "log_does_exists": "هیچ گزارش عملیاتی با نام '{log}' وجود ندارد ، برای مشاهده همه گزارش عملیّات های موجود در خط فرمان از دستور 'yunohost log list' استفاده کنید", + "log_help_to_get_failed_log": "عملیات '{desc}' کامل نشد. لطفاً برای دریافت راهنمایی و کمک ، گزارش کامل این عملیات را با استفاده از دستور 'yunohost log share {name}' به اشتراک بگذارید", + "log_link_to_failed_log": "عملیّات '{desc}' کامل نشد. لطفاً گزارش کامل این عملیات را ارائه دهید بواسطه اینجا را کلیک کنید برای دریافت کمک", + "log_help_to_get_log": "برای مشاهده گزارش عملیات '{desc}'، از دستور 'yunohost log show {name}{name}' استفاده کنید", + "log_link_to_log": "گزارش کامل این عملیات: {desc}'", + "log_corrupted_md_file": "فایل فوق داده YAML مربوط به گزارش ها آسیب دیده است: '{md_file}\nخطا: {error} '", + "ldap_server_is_down_restart_it": "سرویس LDAP خاموش است ، سعی کنید آن را دوباره راه اندازی کنید...", + "ldap_server_down": "دسترسی به سرور LDAP امکان پذیر نیست", + "iptables_unavailable": "در اینجا نمی توانید با iptables بازی کنید. شما یا در ظرفی هستید یا هسته شما آن را پشتیبانی نمی کند", + "ip6tables_unavailable": "در اینجا نمی توانید با جدول های ipv6 کار کنید. شما یا در کانتینتر هستید یا هسته شما آن را پشتیبانی نمی کند", + "invalid_regex": "عبارت منظم نامعتبر: '{regex}'" } From e4783aa00a47b5ba88cda0117c1ed59ef3a75b1d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 1 Sep 2021 21:07:41 +0200 Subject: [PATCH 2864/3170] Various fixes after tests on OVH --- data/actionsmap/yunohost.yml | 4 +++ src/yunohost/dns.py | 59 +++++++++++++++++++++++++----------- src/yunohost/domain.py | 4 +-- 3 files changed, 47 insertions(+), 20 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 50b93342b..58564b9f7 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -612,6 +612,10 @@ domain: help: Domain name to push DNS conf for extra: pattern: *pattern_domain + -d: + full: --dry-run + help: Only display what's to be pushed + action: store_true ############################# diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index 8399c5a4c..0866c3662 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -421,6 +421,8 @@ def domain_registrar_catalog(): def domain_registrar_set(domain, registrar, args): + _assert_domain_exists(domain) + registrars = read_yaml(REGISTRAR_LIST_PATH) if registrar not in registrars.keys(): raise YunohostValidationError("domain_registrar_unknown", registrar=registrar) @@ -450,7 +452,7 @@ def domain_registrar_set(domain, registrar, args): @is_unit_operation() -def domain_registrar_push(operation_logger, domain): +def domain_registrar_push(operation_logger, domain, dry_run=False): """ Send DNS records to the previously-configured registrar of the domain. """ @@ -461,7 +463,7 @@ def domain_registrar_push(operation_logger, domain): _assert_domain_exists(domain) dns_zone = _get_domain_settings(domain)["dns_zone"] - registrar_settings = _get_registrar_settingss(dns_zone) + registrar_settings = _get_registrar_settings(dns_zone) if not registrar_settings: raise YunohostValidationError("registrar_is_not_set", domain=domain) @@ -470,7 +472,7 @@ def domain_registrar_push(operation_logger, domain): dns_conf = _build_dns_conf(domain) # Flatten the DNS conf - dns_conf = [record for record in records_for_category for records_for_category in dns_conf.values()] + dns_conf = [record for records_for_category in dns_conf.values() for record in records_for_category] # FIXME Lexicon does not support CAA records # See https://github.com/AnalogJ/lexicon/issues/282 and https://github.com/AnalogJ/lexicon/pull/371 @@ -479,8 +481,16 @@ def domain_registrar_push(operation_logger, domain): dns_conf = [record for record in dns_conf if record["type"] != "CAA"] # We need absolute names? FIXME: should we add a trailing dot needed here ? + # Seems related to the fact that when fetching current records, they'll contain '.domain.tld' instead of @ + # and we want to check if it already exists or not (c.f. create/update) for record in dns_conf: - record["name"] = f"{record['name']}.{domain}" + if record["name"] == "@": + record["name"] = f".{domain}" + else: + record["name"] = f"{record['name']}.{domain}" + + if record["type"] == "CNAME" and record["value"] == "@": + record["value"] = domain + "." # Construct the base data structure to use lexicon's API. base_config = { @@ -492,12 +502,13 @@ def domain_registrar_push(operation_logger, domain): operation_logger.start() # Fetch all types present in the generated records - current_remote_records = {} + current_remote_records = [] # Get unique types present in the generated records types = {record["type"] for record in dns_conf} for key in types: + print("fetcing type: " + key) fetch_records_for_type = { "action": "list", "type": key, @@ -507,13 +518,12 @@ def domain_registrar_push(operation_logger, domain): .with_dict(dict_object=base_config) .with_dict(dict_object=fetch_records_for_type) ) - current_remote_records[key] = LexiconClient(query).execute() + current_remote_records.extend(LexiconClient(query).execute()) - for key in types: - for current_remote_record in current_remote_records[key]: - logger.debug(f"current_remote_record: {current_remote_record}") - for local_record in dns_conf: - print("local_record:", local_record) + changes = {} + + if dry_run: + return {"current_records": current_remote_records, "dns_conf": dns_conf, "changes": changes} # Push the records for record in dns_conf: @@ -522,7 +532,7 @@ def domain_registrar_push(operation_logger, domain): # TODO do not push if local and distant records are exactly the same ? type_and_name = (record["type"], record["name"]) already_exists = any((r["type"], r["name"]) == type_and_name - for r in current_remote_records[record["type"]]) + for r in current_remote_records) # Finally, push the new record or update the existing one record_to_push = { @@ -530,22 +540,35 @@ def domain_registrar_push(operation_logger, domain): "type": record["type"], "name": record["name"], "content": record["value"], - # FIXME Removed TTL, because it doesn't work with Gandi. - # See https://github.com/AnalogJ/lexicon/issues/726 (similar issue) - # But I think there is another issue with Gandi. Or I'm misusing the API... - # "ttl": record["ttl"], + "ttl": record["ttl"], } - print("pushed_record:", record_to_push, "→", end=" ") + # FIXME Removed TTL, because it doesn't work with Gandi. + # See https://github.com/AnalogJ/lexicon/issues/726 (similar issue) + # But I think there is another issue with Gandi. Or I'm misusing the API... + if base_config["provider_name"] == "gandi": + del record_to_push["ttle"] + print("pushed_record:", record_to_push) + + + # FIXME FIXME FIXME: if a matching record already exists multiple time, + # the current code crashes (at least on OVH) ... we need to provide a specific identifier to update query = ( - ConfigResolver() + LexiconConfigResolver() .with_dict(dict_object=base_config) .with_dict(dict_object=record_to_push) ) + + print(query) + print(query.__dict__) results = LexiconClient(query).execute() print("results:", results) # print("Failed" if results == False else "Ok") + # FIXME FIXME FIXME : if one create / update crash, it shouldn't block everything + + # FIXME : is it possible to push multiple create/update request at once ? + # def domain_config_fetch(domain, key, value): diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index d7863a0e1..d05e31f17 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -550,6 +550,6 @@ def domain_registrar_info(domain): return yunohost.dns.domain_registrar_info(domain) -def domain_registrar_push(domain): +def domain_registrar_push(domain, dry_run): import yunohost.dns - return yunohost.dns.domain_registrar_push(domain) + return yunohost.dns.domain_registrar_push(domain, dry_run) From 11e70881cede7eb98a0af73ac806d2515294cd14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= Date: Wed, 1 Sep 2021 17:21:14 +0000 Subject: [PATCH 2865/3170] Translated using Weblate (French) Currently translated at 100.0% (651 of 651 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 54 ++++++++++++++++++++++++------------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 7473f2d7d..e3a32d639 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -57,7 +57,7 @@ "domain_uninstall_app_first": "Ces applications sont toujours installées sur votre domaine :\n{apps}\n\nVeuillez les désinstaller avec la commande 'yunohost app remove nom-de-l-application' ou les déplacer vers un autre domaine avec la commande 'yunohost app change-url nom-de-l-application' avant de procéder à la suppression du domaine", "domain_unknown": "Domaine inconnu", "done": "Terminé", - "downloading": "Téléchargement en cours …", + "downloading": "Téléchargement en cours…", "dyndns_ip_update_failed": "Impossible de mettre à jour l’adresse IP sur le domaine DynDNS", "dyndns_ip_updated": "Mise à jour de votre IP pour le domaine DynDNS", "dyndns_key_generating": "Génération de la clé DNS..., cela peut prendre un certain temps.", @@ -79,8 +79,8 @@ "ip6tables_unavailable": "Vous ne pouvez pas jouer avec ip6tables ici. Vous êtes soit dans un conteneur, soit votre noyau ne le prend pas en charge", "iptables_unavailable": "Vous ne pouvez pas jouer avec iptables ici. Vous êtes soit dans un conteneur, soit votre noyau ne le prend pas en charge", "mail_alias_remove_failed": "Impossible de supprimer l’alias de courriel '{mail}'", - "mail_domain_unknown": "Le domaine '{domain}' de cette adresse de courriel n’est pas valide. Merci d’utiliser un domaine administré par ce serveur.", - "mail_forward_remove_failed": "Impossible de supprimer le courriel de transfert '{mail}'", + "mail_domain_unknown": "Le domaine '{domain}' de cette adresse email n’est pas valide. Merci d’utiliser un domaine administré par ce serveur.", + "mail_forward_remove_failed": "Impossible de supprimer l'email de transfert '{mail}'", "main_domain_change_failed": "Impossible de modifier le domaine principal", "main_domain_changed": "Le domaine principal a été modifié", "not_enough_disk_space": "L’espace disque est insuffisant sur '{path}'", @@ -158,8 +158,8 @@ "certmanager_domain_http_not_working": "Le domaine {domain} 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_domain_dns_ip_differs_from_public_ip": "Les enregistrements DNS du domaine '{domain}' sont différents de l’adresse IP de ce serveur. Pour plus d'informations, veuillez consulter la catégorie \"Enregistrements DNS\" dans la section diagnostic. 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} (fichier : {file}), la cause est : {reason}", - "certmanager_cert_install_success_selfsigned": "Le certificat auto-signé est maintenant installé pour le domaine « {domain} »", - "certmanager_cert_install_success": "Le certificat Let’s Encrypt est maintenant installé pour le domaine « {domain} »", + "certmanager_cert_install_success_selfsigned": "Le certificat auto-signé est maintenant installé pour le domaine '{domain}'", + "certmanager_cert_install_success": "Le certificat Let’s Encrypt est maintenant installé pour le domaine '{domain}'", "certmanager_cert_renew_success": "Certificat Let’s Encrypt renouvelé pour le domaine '{domain}'", "certmanager_cert_signing_failed": "Impossible de signer le nouveau certificat", "certmanager_no_cert_file": "Impossible de lire le fichier du certificat pour le domaine {domain} (fichier : {file})", @@ -170,7 +170,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})", - "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_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`.", "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).", "app_already_installed_cant_change_url": "Cette application est déjà installée. L’URL ne peut pas être changé simplement par cette fonction. Vérifiez si cela est disponible avec `app changeurl`.", "app_change_url_failed_nginx_reload": "Le redémarrage de NGINX a échoué. Voici la sortie de 'nginx -t' :\n{nginx_errors}", @@ -217,10 +217,10 @@ "backup_couldnt_bind": "Impossible de lier {src} avec {dest}.", "domain_dns_conf_is_just_a_recommendation": "Cette commande vous montre la configuration *recommandée*. Elle ne configure pas le DNS pour vous. Il est de votre ressort de configurer votre zone DNS chez votre registrar/fournisseur conformément à cette recommandation.", "migrations_cant_reach_migration_file": "Impossible d’accéder aux fichiers de migration via le chemin '%s'", - "migrations_loading_migration": "Chargement de la migration {id} ...", + "migrations_loading_migration": "Chargement de la migration {id}...", "migrations_migration_has_failed": "La migration {id} a échoué avec l’exception {exception} : annulation", "migrations_no_migrations_to_run": "Aucune migration à lancer", - "migrations_skip_migration": "Ignorer et passer la migration {id} ...", + "migrations_skip_migration": "Ignorer et passer la migration {id}...", "server_shutdown": "Le serveur va s’éteindre", "server_shutdown_confirm": "Le serveur va être éteint immédiatement, le voulez-vous vraiment ? [{answers}]", "server_reboot": "Le serveur va redémarrer", @@ -236,14 +236,14 @@ "migrations_need_to_accept_disclaimer": "Pour lancer la migration {id}, vous devez accepter cet avertissement :\n---\n{disclaimer}\n---\nSi vous acceptez de lancer la migration, veuillez relancer la commande avec l’option --accept-disclaimer.", "service_description_yunomdns": "Vous permet d’atteindre votre serveur en utilisant « yunohost.local » sur votre réseau local", "service_description_dnsmasq": "Gère la résolution des noms de domaine (DNS)", - "service_description_dovecot": "Permet aux clients de messagerie d’accéder/récupérer les courriels (via IMAP et POP3)", + "service_description_dovecot": "Permet aux clients de messagerie d’accéder/récupérer les emails (via IMAP et POP3)", "service_description_fail2ban": "Protège contre les attaques brute-force et autres types d’attaques venant d’Internet", "service_description_metronome": "Gère les comptes de messagerie instantanée XMPP", "service_description_mysql": "Stocke les données des applications (bases de données SQL)", "service_description_nginx": "Sert ou permet l’accès à tous les sites web hébergés sur votre serveur", - "service_description_postfix": "Utilisé pour envoyer et recevoir des courriels", + "service_description_postfix": "Utilisé pour envoyer et recevoir des emails", "service_description_redis-server": "Une base de données spécialisée utilisée pour l’accès rapide aux données, les files d’attentes et la communication entre les programmes", - "service_description_rspamd": "Filtre le pourriel, et d’autres fonctionnalités liées au courriel", + "service_description_rspamd": "Filtre le pourriel, et d’autres fonctionnalités liées aux emails", "service_description_slapd": "Stocke les utilisateurs, domaines et leurs informations liées", "service_description_ssh": "Vous permet de vous connecter à distance à votre serveur via un terminal (protocole SSH)", "service_description_yunohost-api": "Permet les interactions entre l’interface web de YunoHost et le système", @@ -282,7 +282,7 @@ "log_tools_upgrade": "Mettre à jour les paquets du système", "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", + "mail_unavailable": "Cette adresse d'email est réservée et doit être automatiquement attribuée au tout premier utilisateur", "good_practices_about_admin_password": "Vous êtes sur le point de définir un nouveau mot de passe d'administration. Le mot de passe doit comporter au moins 8 caractères, bien qu'il soit recommandé d'utiliser un mot de passe plus long (c'est-à-dire une phrase secrète) et/ou d'utiliser une combinaison de caractères (majuscules, minuscules, chiffres et caractères spéciaux).", "good_practices_about_user_password": "Vous êtes sur le point de définir un nouveau mot de passe utilisateur. Le mot de passe doit comporter au moins 8 caractères, bien qu'il soit recommandé d'utiliser un mot de passe plus long (c'est-à-dire une phrase secrète) et/ou une combinaison de caractères (majuscules, minuscules, chiffres et caractères spéciaux).", "password_listed": "Ce mot de passe fait partie des mots de passe les plus utilisés dans le monde. Veuillez en choisir un autre moins commun et plus robuste.", @@ -303,7 +303,7 @@ "backup_actually_backuping": "Création d’une archive de sauvegarde à partir des fichiers collectés...", "backup_mount_archive_for_restore": "Préparation de l’archive pour restauration...", "confirm_app_install_warning": "Avertissement : cette application peut fonctionner mais n’est pas bien intégrée dans YunoHost. Certaines fonctionnalités telles que l’authentification unique et la sauvegarde/restauration peuvent ne pas être disponibles. L’installer quand même ? [{answers}] ", - "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}'", + "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}'", "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}'", "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} est disponible chez {provider}.", @@ -342,7 +342,7 @@ "regenconf_would_be_updated": "La configuration aurait dû être mise à jour pour la catégorie '{category}'", "regenconf_dry_pending_applying": "Vérification de la configuration en attente qui aurait été appliquée pour la catégorie '{category}'…", "regenconf_failed": "Impossible de régénérer la configuration pour la ou les catégorie(s) : '{categories}'", - "regenconf_pending_applying": "Applique la configuration en attente pour la catégorie '{category}' ...", + "regenconf_pending_applying": "Applique la configuration en attente pour la catégorie '{category}'...", "service_regen_conf_is_deprecated": "'yunohost service regen-conf' est obsolète ! Veuillez plutôt utiliser 'yunohost tools regen-conf' à la place.", "tools_upgrade_at_least_one": "Veuillez spécifier '--apps' ou '--system'", "tools_upgrade_cant_both": "Impossible de mettre à niveau le système et les applications en même temps", @@ -381,7 +381,7 @@ "migrations_already_ran": "Ces migrations sont déjà effectuées : {ids}", "migrations_dependencies_not_satisfied": "Exécutez ces migrations : '{dependencies_id}', avant migration {id}.", "migrations_failed_to_load_migration": "Impossible de charger la migration {id} : {error}", - "migrations_running_forward": "Exécution de la migration {id} ...", + "migrations_running_forward": "Exécution de la migration {id}...", "migrations_success_forward": "Migration {id} terminée", "operation_interrupted": "L'opération a-t-elle été interrompue manuellement ?", "permission_already_exist": "L’autorisation '{permission}' existe déjà", @@ -473,7 +473,7 @@ "apps_catalog_updating": "Mise à jour du catalogue d’applications…", "apps_catalog_obsolete_cache": "Le cache du catalogue d'applications est vide ou obsolète.", "apps_catalog_update_success": "Le catalogue des applications a été mis à jour !", - "diagnosis_description_mail": "E-mail", + "diagnosis_description_mail": "Email", "diagnosis_ports_unreachable": "Le port {port} n’est pas accessible de l’extérieur.", "diagnosis_ports_ok": "Le port {port} est accessible de l’extérieur.", "diagnosis_http_could_not_diagnose": "Impossible de diagnostiquer si le domaine est accessible de l’extérieur.", @@ -509,16 +509,16 @@ "diagnosis_mail_ehlo_wrong": "Un autre serveur de messagerie SMTP répond sur IPv{ipversion}. Votre serveur ne sera probablement pas en mesure de recevoir des courriel.", "diagnosis_mail_ehlo_could_not_diagnose": "Impossible de diagnostiquer si le serveur de messagerie postfix est accessible de l’extérieur en IPv{ipversion}.", "diagnosis_mail_ehlo_could_not_diagnose_details": "Erreur : {error}", - "diagnosis_mail_fcrdns_dns_missing": "Aucun DNS inverse n’est défini pour IPv{ipversion}. Certains e-mails seront peut-être refusés ou considérés comme des spam.", + "diagnosis_mail_fcrdns_dns_missing": "Aucun DNS inverse n’est défini pour IPv{ipversion}. Certains emails seront peut-être refusés ou considérés comme des spam.", "diagnosis_mail_fcrdns_ok": "Votre DNS inverse est correctement configuré !", "diagnosis_mail_fcrdns_nok_details": "Vous devez d’abord essayer de configurer le DNS inverse avec {ehlo_domain} dans votre interface de routeur Internet ou votre interface d’hébergement. (Certains hébergeurs peuvent vous demander de leur envoyer un ticket de support pour cela).", - "diagnosis_mail_fcrdns_different_from_ehlo_domain": "Le DNS inverse n'est pas correctement configuré en IPv{ipversion}. Certains e-mails seront peut-être refusés ou considérés comme des spam.", + "diagnosis_mail_fcrdns_different_from_ehlo_domain": "Le DNS inverse n'est pas correctement configuré en IPv{ipversion}. Certains emails seront peut-être refusés ou considérés comme des spam.", "diagnosis_mail_blacklist_ok": "Les adresses IP et les domaines utilisés par ce serveur ne semblent pas être sur liste noire", "diagnosis_mail_blacklist_reason": "La raison de la liste noire est : {reason}", "diagnosis_mail_blacklist_website": "Après avoir identifié la raison pour laquelle vous êtes répertorié et l'avoir corrigé, n’hésitez pas à demander le retrait de votre IP ou domaine sur {blacklist_website}", - "diagnosis_mail_queue_ok": "{nb_pending} e-mails en attente dans les files d'attente de messagerie", + "diagnosis_mail_queue_ok": "{nb_pending} emails en attente dans les files d'attente de messagerie", "diagnosis_mail_queue_unavailable_details": "Erreur : {error}", - "diagnosis_mail_queue_too_big": "Trop d’e-mails en attente dans la file d'attente ({nb_pending} e-mails)", + "diagnosis_mail_queue_too_big": "Trop d’emails en attente dans la file d'attente ({nb_pending} e-mails)", "global_settings_setting_smtp_allow_ipv6": "Autoriser l'utilisation d’IPv6 pour recevoir et envoyer du courrier", "diagnosis_display_tip": "Pour voir les problèmes détectés, vous pouvez accéder à la section Diagnostic du webadmin ou exécuter « yunohost diagnosis show --issues --human-readable» à partir de la ligne de commande.", "diagnosis_ip_global": "IP globale : {global}", @@ -529,11 +529,11 @@ "diagnosis_mail_ehlo_unreachable": "Le serveur de messagerie SMTP est inaccessible de l’extérieur en IPv{ipversion}. Il ne pourra pas recevoir des courriels.", "diagnosis_mail_ehlo_unreachable_details": "Impossible d'ouvrir une connexion sur le port 25 à votre serveur en IPv{ipversion}. Il semble inaccessible.
1. La cause la plus courante de ce problème est que le port 25 n'est pas correctement redirigé vers votre serveur.
2. Vous devez également vous assurer que le service postfix est en cours d'exécution.
3. Sur les configurations plus complexes: assurez-vous qu'aucun pare-feu ou proxy inversé n'interfère.", "diagnosis_mail_ehlo_wrong_details": "Le EHLO reçu par le serveur de diagnostique distant en IPv{ipversion} est différent du domaine de votre serveur.
EHLO reçu: {wrong_ehlo}
Attendu : {right_ehlo}
La cause la plus courante ce problème est que le port 25 n’est pas correctement redirigé vers votre serveur . Vous pouvez également vous assurer qu’aucun pare-feu ou proxy inversé n’interfère.", - "diagnosis_mail_fcrdns_nok_alternatives_4": "Certains fournisseurs ne vous laisseront pas configurer votre DNS inversé (ou leur fonctionnalité pourrait être cassée …). Si vous rencontrez des problèmes à cause de cela, envisagez les solutions suivantes :
- Certains FAI fournissent l’alternative de à l’aide d’un relais de serveur de messagerie bien que cela implique que le relais pourra espionner votre trafic de messagerie.
- Une alternative respectueuse de la vie privée consiste à utiliser un VPN *avec une IP publique dédiée* pour contourner ce type de limites. Voir https://yunohost.org/#/vpn_advantage
- Enfin, il est également possible de changer de fournisseur", - "diagnosis_mail_fcrdns_nok_alternatives_6": "Certains fournisseurs ne vous laisseront pas configurer votre DNS inversé (ou leur fonctionnalité pourrait être cassée ...). Si votre DNS inversé est correctement configuré en IPv4, vous pouvez essayer de désactiver l'utilisation d'IPv6 lors de l'envoi d'e-mails en exécutant yunohost settings set smtp.allow_ipv6 -v off. Remarque: cette dernière solution signifie que vous ne pourrez pas envoyer ou recevoir de courriels avec les quelques serveurs qui ont uniquement de l'IPv6.", + "diagnosis_mail_fcrdns_nok_alternatives_4": "Certains fournisseurs ne vous laisseront pas configurer votre DNS inversé (ou leur fonctionnalité pourrait être cassée...). Si vous rencontrez des problèmes à cause de cela, envisagez les solutions suivantes :
- Certains FAI fournissent l’alternative de à l’aide d’un relais de serveur de messagerie bien que cela implique que le relais pourra espionner votre trafic de messagerie.
- Une alternative respectueuse de la vie privée consiste à utiliser un VPN *avec une IP publique dédiée* pour contourner ce type de limites. Voir https://yunohost.org/#/vpn_advantage
- Enfin, il est également possible de changer de fournisseur", + "diagnosis_mail_fcrdns_nok_alternatives_6": "Certains fournisseurs ne vous laisseront pas configurer votre DNS inversé (ou leur fonctionnalité pourrait être cassée...). Si votre DNS inversé est correctement configuré en IPv4, vous pouvez essayer de désactiver l'utilisation d'IPv6 lors de l'envoi d'emails en exécutant yunohost settings set smtp.allow_ipv6 -v off. Remarque : cette dernière solution signifie que vous ne pourrez pas envoyer ou recevoir de courriels avec les quelques serveurs qui ont uniquement de l'IPv6.", "diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "DNS inverse actuel : {rdns_domain}
Valeur attendue : {ehlo_domain}", "diagnosis_mail_blacklist_listed_by": "Votre IP ou domaine {item} est sur liste noire sur {blacklist_name}", - "diagnosis_mail_queue_unavailable": "Impossible de consulter le nombre d’e-mails en attente dans la file d'attente", + "diagnosis_mail_queue_unavailable": "Impossible de consulter le nombre d’emails en attente dans la file d'attente", "diagnosis_ports_partially_unreachable": "Le port {port} n'est pas accessible de l'extérieur en IPv{failed}.", "diagnosis_http_hairpinning_issue": "Votre réseau local ne semble pas supporter l'hairpinning.", "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", @@ -555,7 +555,7 @@ "restore_already_installed_apps": "Les applications suivantes ne peuvent pas être restaurées car elles sont déjà installées : {apps}", "regenconf_need_to_explicitly_specify_ssh": "La configuration de ssh a été modifiée manuellement. Vous devez explicitement indiquer la mention --force à \"ssh\" pour appliquer les changements.", "migration_0015_cleaning_up": "Nettoyage du cache et des paquets qui ne sont plus nécessaires ...", - "migration_0015_specific_upgrade": "Démarrage de la mise à jour des paquets du système qui doivent être mis à jour séparément ...", + "migration_0015_specific_upgrade": "Démarrage de la mise à jour des paquets du système qui doivent être mis à jour séparément...", "migration_0015_modified_files": "Veuillez noter que les fichiers suivants ont été modifiés manuellement et pourraient être écrasés à la suite de la mise à niveau : {manually_modified_files}", "migration_0015_problematic_apps_warning": "Veuillez noter que des applications qui peuvent poser problèmes ont été détectées. Il semble qu'elles n'aient pas été installées à partir du catalogue d'applications YunoHost, ou bien qu'elles ne soient pas signalées comme \"fonctionnelles\". Par conséquent, il n'est pas possible de garantir que les applications suivantes fonctionneront encore après la mise à niveau : {problematic_apps}", "migration_0015_general_warning": "Veuillez noter que cette migration est une opération délicate. L'équipe YunoHost a fait de son mieux pour la revérifier et la tester, mais la migration pourrait quand même casser des éléments du système ou de ses applications.\n\nIl est donc recommandé :\n…- de faire une sauvegarde de toute donnée ou application critique. Plus d'informations ici https://yunohost.org/backup ;\n…- d'être patient après le lancement de la migration. Selon votre connexion internet et votre matériel, la mise à niveau peut prendre jusqu'à quelques heures.", @@ -564,8 +564,8 @@ "migration_0015_not_stretch": "La distribution Debian actuelle n'est pas Stretch !", "migration_0015_yunohost_upgrade": "Démarrage de la mise à jour de YunoHost ...", "migration_0015_still_on_stretch_after_main_upgrade": "Quelque chose s'est mal passé lors de la mise à niveau, le système semble toujours être sous Debian Stretch", - "migration_0015_main_upgrade": "Démarrage de la mise à niveau générale ...", - "migration_0015_patching_sources_list": "Mise à jour du fichier sources.lists ...", + "migration_0015_main_upgrade": "Démarrage de la mise à niveau générale...", + "migration_0015_patching_sources_list": "Mise à jour du fichier sources.lists...", "migration_0015_start": "Démarrage de la migration vers Buster", "migration_description_0015_migrate_to_buster": "Mise à niveau du système vers Debian Buster et YunoHost 4.x", "diagnosis_dns_try_dyndns_update_force": "La configuration DNS de ce domaine devrait être automatiquement gérée par YunoHost. Si ce n'est pas le cas, vous pouvez essayer de forcer une mise à jour en utilisant yunohost dyndns update --force.", @@ -642,7 +642,7 @@ "ldap_server_is_down_restart_it": "Le service LDAP est en panne, essayez de le redémarrer...", "ldap_server_down": "Impossible d'atteindre le serveur LDAP", "global_settings_setting_security_experimental_enabled": "Activer les fonctionnalités de sécurité expérimentales (ne l'activez pas si vous ne savez pas ce que vous faites !)", - "diagnosis_apps_deprecated_practices": "La version installée de cette application utilise toujours certaines pratiques de packaging obsolètes. Vous devriez vraiment envisager de mettre l'application à jour..", + "diagnosis_apps_deprecated_practices": "La version installée de cette application utilise toujours certaines pratiques de packaging obsolètes. Vous devriez vraiment envisager de mettre l'application à jour.", "diagnosis_apps_outdated_ynh_requirement": "La version installée de cette application nécessite uniquement YunoHost> = 2.x, cela indique que l'application n'est pas à jour avec les bonnes pratiques de packaging et les helpers recommandées. Vous devriez vraiment envisager de mettre l'application à jour.", "diagnosis_apps_bad_quality": "Cette application est actuellement signalée comme cassée dans le catalogue d'applications de YunoHost. Cela peut être un problème temporaire. En attendant que les mainteneurs tentent de résoudre le problème, la mise à jour de cette application est désactivée.", "diagnosis_apps_broken": "Cette application est actuellement signalée comme cassée dans le catalogue d'applications de YunoHost. Cela peut être un problème temporaire. En attendant que les mainteneurs tentent de résoudre le problème, la mise à jour de cette application est désactivée.", From a0c20dd4e54222afcfb6bf46229385cc809f200a Mon Sep 17 00:00:00 2001 From: Parviz Homayun Date: Wed, 1 Sep 2021 18:18:24 +0000 Subject: [PATCH 2866/3170] Translated using Weblate (Persian) Currently translated at 100.0% (643 of 643 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fa/ --- locales/fa.json | 144 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 139 insertions(+), 5 deletions(-) diff --git a/locales/fa.json b/locales/fa.json index 9e33d02d7..a644716cf 100644 --- a/locales/fa.json +++ b/locales/fa.json @@ -280,7 +280,7 @@ "domain_cannot_remove_main_add_new_one": "شما نمی توانید '{domain}' را حذف کنید زیرا دامنه اصلی و تنها دامنه شما است، ابتدا باید دامنه دیگری را با 'yunohost domain add ' اضافه کنید، سپس با استفاده از 'yunohost domain main-domain -n ' به عنوان دامنه اصلی تنظیم شده. و بعد از آن می توانید'{domain}' را حذف کنید با استفاده از'yunohost domain remove {domain}'.'", "domain_cannot_add_xmpp_upload": "شما نمی توانید دامنه هایی را که با \"xmpp-upload\" شروع می شوند اضافه کنید. این نوع نام مختص ویژگی بارگذاری XMPP است که در YunoHost یکپارچه شده است.", "domain_cannot_remove_main": "شما نمی توانید '{domain}' را حذف کنید زیرا دامنه اصلی است ، ابتدا باید با استفاده از 'yunohost domain main-domain -n ' دامنه دیگری را به عنوان دامنه اصلی تعیین کنید. در اینجا لیست دامنه های کاندید وجود دارد: {other_domains}", - "installation_complete": "نصب تکمیل شد", + "installation_complete": "عملیّات نصب کامل شد", "hook_name_unknown": "نام قلاب ناشناخته '{name}'", "hook_list_by_invalid": "از این ویژگی نمی توان برای فهرست قلاب ها استفاده کرد", "hook_json_return_error": "بازگشت از قلاب {path} خوانده نشد. خطا: {msg}. محتوای خام: {raw_content}", @@ -302,8 +302,8 @@ "group_already_exist_on_system_but_removing_it": "گروه {group} از قبل در گروه های سیستم وجود دارد ، اما YunoHost آن را حذف می کند...", "group_already_exist_on_system": "گروه {group} از قبل در گروه های سیستم وجود دارد", "group_already_exist": "گروه {group} از قبل وجود دارد", - "good_practices_about_user_password": "گذرواژه باید حداقل 8 کاراکتر باشد - اگرچه استفاده از گذرواژه طولانی تر (به عنوان مثال عبارت عبور) و/یا استفاده از تنوع کاراکترها (بزرگ ، کوچک ، رقم و کاراکتر های خاص) تمرین خوبی است.", - "good_practices_about_admin_password": "اکنون می خواهید گذرواژه جدیدی برای مدیریت تعریف کنید. گذرواژه باید حداقل 8 کاراکتر باشد - اگرچه استفاده از گذرواژه طولانی تر (به عنوان مثال عبارت عبور) و/یا استفاده از تنوع کاراکترها (بزرگ ، کوچک ، رقم و کاراکتر های خاص) تمرین خوبی است.", + "good_practices_about_user_password": "گذرواژه باید حداقل 8 کاراکتر باشد - اگرچه استفاده از گذرواژه طولانی تر تمرین خوبی است (به عنوان مثال عبارت عبور) و/یا استفاده از تنوع کاراکترها (بزرگ ، کوچک ، رقم و کاراکتر های خاص).", + "good_practices_about_admin_password": "اکنون می خواهید گذرواژه جدیدی برای مدیریت تعریف کنید. گذرواژه باید حداقل 8 کاراکتر باشد - اگرچه استفاده از گذرواژه طولانی تر تمرین خوبی است (به عنوان مثال عبارت عبور) و/یا استفاده از تنوع کاراکترها (بزرگ ، کوچک ، رقم و کاراکتر های خاص).", "global_settings_unknown_type": "وضعیت غیرمنتظره ، به نظر می رسد که تنظیمات {setting} دارای نوع {unknown_type} است اما از نوع پشتیبانی شده توسط سیستم نیست.", "global_settings_setting_backup_compress_tar_archives": "هنگام ایجاد پشتیبان جدید ، بایگانی های فشرده (.tar.gz) را به جای بایگانی های فشرده نشده (.tar) انتخاب کنید. N.B. : فعال کردن این گزینه به معنای ایجاد آرشیوهای پشتیبان سبک تر است ، اما روش پشتیبان گیری اولیه به طور قابل توجهی طولانی تر و سنگین تر بر روی CPU خواهد بود.", "global_settings_setting_security_experimental_enabled": "فعال کردن ویژگی های امنیتی آزمایشی (اگر نمی دانید در حال انجام چه کاری هستید این کار را انجام ندهید!)", @@ -363,7 +363,7 @@ "domain_exists": "دامنه از قبل وجود دارد", "domain_dyndns_root_unknown": "دامنه ریشه DynDNS ناشناخته", "domain_dyndns_already_subscribed": "شما قبلاً در یک دامنه DynDNS مشترک شده اید", - "domain_dns_conf_is_just_a_recommendation": "این دستور پیکربندی * توصیه شده * را به شما نشان می دهد. در واقع پیکربندی DNS را برای شما تنظیم نمی کند. این وظیفه شماست که مطابق این توصیه ، منطقه DNS خود را در ثبت کننده خود پیکربندی کنید.", + "domain_dns_conf_is_just_a_recommendation": "این دستور پیکربندی * توصیه شده * را به شما نشان می دهد. این وظیفه شماست که مطابق این توصیه ، منطقه DNS خود را در ثبت کننده خود پیکربندی کنید. در واقع پیکربندی DNS را برای شما تنظیم نمی کند.", "domain_deletion_failed": "حذف دامنه {domain} امکان پذیر نیست: {error}", "domain_deleted": "دامنه حذف شد", "domain_creation_failed": "ایجاد دامنه {domain} امکان پذیر نیست: {error}", @@ -507,5 +507,139 @@ "ldap_server_down": "دسترسی به سرور LDAP امکان پذیر نیست", "iptables_unavailable": "در اینجا نمی توانید با iptables بازی کنید. شما یا در ظرفی هستید یا هسته شما آن را پشتیبانی نمی کند", "ip6tables_unavailable": "در اینجا نمی توانید با جدول های ipv6 کار کنید. شما یا در کانتینتر هستید یا هسته شما آن را پشتیبانی نمی کند", - "invalid_regex": "عبارت منظم نامعتبر: '{regex}'" + "invalid_regex": "عبارت منظم نامعتبر: '{regex}'", + "yunohost_postinstall_end_tip": "پس از نصب کامل شد! برای نهایی کردن تنظیمات خود ، لطفاً موارد زیر را در نظر بگیرید:\n - افزودن اولین کاربر از طریق بخش \"کاربران\" webadmin (یا 'yunohost user create ' در خط فرمان) ؛\n - تشخیص مشکلات احتمالی از طریق بخش \"عیب یابی\" webadmin (یا 'yunohost diagnosis run' در خط فرمان) ؛\n - خواندن قسمت های \"نهایی کردن راه اندازی خود\" و \"آشنایی با YunoHost\" در اسناد مدیریت: https://yunohost.org/admindoc.", + "yunohost_not_installed": "YunoHost به درستی نصب نشده است. لطفا 'yunohost tools postinstall' را اجرا کنید", + "yunohost_installing": "در حال نصب YunoHost...", + "yunohost_configured": "YunoHost اکنون پیکربندی شده است", + "yunohost_already_installed": "YunoHost قبلاً نصب شده است", + "user_updated": "اطلاعات کاربر تغییر کرد", + "user_update_failed": "کاربر {user} به روز نشد: {error}", + "user_unknown": "کاربر ناشناس: {user}", + "user_home_creation_failed": "پوشه 'home' برای کاربر ایجاد نشد", + "user_deletion_failed": "کاربر {user} حذف نشد: {error}", + "user_deleted": "کاربر حذف شد", + "user_creation_failed": "کاربر {user} ایجاد نشد: {error}", + "user_created": "کاربر ایجاد شد", + "user_already_exists": "کاربر '{user}' در حال حاضر وجود دارد", + "upnp_port_open_failed": "پورت از طریق UPnP باز نشد", + "upnp_enabled": "UPnP روشن شد", + "upnp_disabled": "UPnP خاموش شد", + "upnp_dev_not_found": "هیچ دستگاه UPnP یافت نشد", + "upgrading_packages": "در حال ارتقاء بسته ها...", + "upgrade_complete": "ارتقا کامل شد", + "updating_apt_cache": "در حال واکشی و دریافت ارتقاء موجود برای بسته های سیستم...", + "update_apt_cache_warning": "هنگام به روز رسانی حافظه پنهان APT (مدیر بسته دبیان) مشکلی پیش آمده. در اینجا مجموعه ای از خطوط source.list موجود میباشد که ممکن است به شناسایی خطوط مشکل ساز کمک کند:\n{sourceslist}", + "update_apt_cache_failed": "امکان بروزرسانی حافظه پنهان APT (مدیر بسته دبیان) وجود ندارد. در اینجا مجموعه ای از خطوط source.list هست که ممکن است به شناسایی خطوط مشکل ساز کمک کند:\n{sourceslist}", + "unrestore_app": "{app} بازیابی نمی شود", + "unlimit": "بدون سهمیه", + "unknown_main_domain_path": "دامنه یا مسیر ناشناخته برای '{app}'. شما باید یک دامنه و یک مسیر را مشخص کنید تا بتوانید یک آدرس اینترنتی برای مجوز تعیین کنید.", + "unexpected_error": "مشکل غیر منتظره ای پیش آمده: {error}", + "unbackup_app": "{app} ذخیره نمی شود", + "tools_upgrade_special_packages_completed": "ارتقاء بسته YunoHost به پایان رسید\nبرای بازگرداندن خط فرمان [Enter] را فشار دهید", + "tools_upgrade_special_packages_explanation": "ارتقاء ویژه در پس زمینه ادامه خواهد یافت. لطفاً تا 10 دقیقه دیگر (بسته به سرعت سخت افزار) هیچ اقدام دیگری را روی سرور خود شروع نکنید. پس از این کار ، ممکن است مجبور شوید دوباره وارد webadmin شوید. گزارش ارتقاء در Tools → Log (در webadmin) یا با استفاده از 'yunohost log list' (در خط فرمان) در دسترس خواهد بود.", + "tools_upgrade_special_packages": "در حال ارتقاء بسته های 'special' (مربوط به yunohost)…", + "tools_upgrade_regular_packages_failed": "بسته ها را نمی توان ارتقا داد: {packages_list}", + "tools_upgrade_regular_packages": "در حال ارتقاء بسته های 'regular' (غیر مرتبط با yunohost)…", + "tools_upgrade_cant_unhold_critical_packages": "بسته های مهم و حیاتی را نمی توان نگه نداشت…", + "tools_upgrade_cant_hold_critical_packages": "بسته های مهم و حیاتی را نمی توان نگه داشت…", + "tools_upgrade_cant_both": "نمی توان سیستم و برنامه ها را به طور همزمان ارتقا داد", + "tools_upgrade_at_least_one": "لطفاً مشخص کنید 'apps' ، یا 'system'", + "this_action_broke_dpkg": "این اقدام dpkg/APT (مدیران بسته های سیستم) را خراب کرد... می توانید با اتصال از طریق SSH و اجرای فرمان `sudo apt install --fix -break` و/یا` sudo dpkg --configure -a` این مشکل را حل کنید.", + "system_username_exists": "نام کاربری قبلاً در لیست کاربران سیستم وجود دارد", + "system_upgraded": "سیستم ارتقا یافت", + "ssowat_conf_updated": "پیکربندی SSOwat به روزرسانی شد", + "ssowat_conf_generated": "پیکربندی SSOwat بازسازی شد", + "show_tile_cant_be_enabled_for_regex": "شما نمی توانید \"show_tile\" را درست فعال کنید ، چرا که آدرس اینترنتی مجوز '{permission}' یک عبارت منظم است", + "show_tile_cant_be_enabled_for_url_not_defined": "شما نمی توانید \"show_tile\" را در حال حاضر فعال کنید ، زیرا ابتدا باید یک آدرس اینترنتی برای مجوز '{permission}' تعریف کنید", + "service_unknown": "سرویس ناشناخته '{service}'", + "service_stopped": "سرویس '{service}' متوقف شد", + "service_stop_failed": "سرویس '{service}' متوقف نمی شود\n\nگزارشات اخیر سرویس: {logs}", + "service_started": "سرویس '{service}' شروع شد", + "service_start_failed": "سرویس '{service}' شروع نشد\n\nگزارشات اخیر سرویس: {logs}", + "service_reloaded_or_restarted": "سرویس '{service}' بارگیری یا راه اندازی مجدد شد", + "service_reload_or_restart_failed": "سرویس \"{service}\" بارگیری یا راه اندازی مجدد نشد\n\nگزارشات اخیر سرویس: {logs}", + "service_restarted": "سرویس '{service}' راه اندازی مجدد شد", + "service_restart_failed": "سرویس \"{service}\" راه اندازی مجدد نشد\n\nگزارشات اخیر سرویس: {logs}", + "service_reloaded": "سرویس '{service}' بارگیری مجدد شد", + "service_reload_failed": "سرویس '{service}' بارگیری نشد\n\nگزارشات اخیر سرویس: {logs}", + "service_removed": "سرویس '{service}' حذف شد", + "service_remove_failed": "سرویس '{service}' حذف نشد", + "service_regen_conf_is_deprecated": "فرمان 'yunohost service regen-conf' منسوخ شده است! لطفاً به جای آن از 'yunohost tools regen-conf' استفاده کنید.", + "service_enabled": "سرویس '{service}' اکنون بطور خودکار در هنگام بوت شدن سیستم راه اندازی می شود.", + "service_enable_failed": "انجام سرویس '{service}' به طور خودکار در هنگام راه اندازی امکان پذیر نیست.\n\nگزارشات اخیر سرویس: {logs}", + "service_disabled": "هنگام راه اندازی سیستم ، سرویس '{service}' دیگر راه اندازی نمی شود.", + "service_disable_failed": "نتوانست باعث شود سرویس '{service}' در هنگام راه اندازی شروع نشود.\n\nگزارشات سرویس اخیر: {logs}", + "service_description_yunohost-firewall": "باز و بسته شدن پورت های اتصال به سرویس ها را مدیریت می کند", + "service_description_yunohost-api": "تعاملات بین رابط وب YunoHost و سیستم را مدیریت می کند", + "service_description_ssh": "به شما امکان می دهد از راه دور از طریق ترمینال (پروتکل SSH) به سرور خود متصل شوید", + "service_description_slapd": "کاربران ، دامنه ها و اطلاعات مرتبط را ذخیره می کند", + "service_description_rspamd": "هرزنامه ها و سایر ویژگی های مربوط به ایمیل را فیلتر می کند", + "service_description_redis-server": "یک پایگاه داده تخصصی برای دسترسی سریع به داده ها ، صف وظیفه و ارتباط بین برنامه ها استفاده می شود", + "service_description_postfix": "برای ارسال و دریافت ایمیل استفاده می شود", + "service_description_php7.3-fpm": "برنامه های نوشته شده با PHP را با NGINX اجرا می کند", + "service_description_nginx": "به همه وب سایت هایی که روی سرور شما میزبانی شده اند سرویس می دهد یا دسترسی به آنها را فراهم می کند", + "service_description_mysql": "ذخیره داده های برنامه (پایگاه داده SQL)", + "service_description_metronome": "مدیریت حساب های پیام رسانی فوری XMPP", + "service_description_fail2ban": "در برابر حملات وحشیانه و انواع دیگر حملات از طریق اینترنت محافظت می کند", + "service_description_dovecot": "به کلاینت های ایمیل اجازه می دهد تا به ایمیل دسترسی/واکشی داشته باشند (از طریق IMAP و POP3)", + "service_description_dnsmasq": "کنترل تفکیک پذیری نام دامنه (DNS)", + "service_description_yunomdns": "به شما امکان می دهد با استفاده از 'yunohost.local' در شبکه محلی به سرور خود برسید", + "service_cmd_exec_failed": "نمی توان دستور '{command}' را اجرا کرد", + "service_already_stopped": "سرویس '{service}' قبلاً متوقف شده است", + "service_already_started": "سرویس '{service}' در حال اجرا است", + "service_added": "سرویس '{service}' اضافه شد", + "service_add_failed": "سرویس '{service}' اضافه نشد", + "server_reboot_confirm": "سرور بلافاصله راه اندازی مجدد می شود، آیا مطمئن هستید؟ [{answers}]", + "server_reboot": "سرور راه اندازی مجدد می شود", + "server_shutdown_confirm": "آیا مطمئن هستید که سرور بلافاصله خاموش می شود؟ [{answers}]", + "server_shutdown": "سرور خاموش می شود", + "root_password_replaced_by_admin_password": "گذرواژه ریشه شما با رمز مدیریت جایگزین شده است.", + "root_password_desynchronized": "گذرواژه مدیریت تغییر کرد ، اما YunoHost نتوانست این را به رمز عبور ریشه منتقل کند!", + "restore_system_part_failed": "بخش سیستم '{part}' بازیابی و ترمیم نشد", + "restore_running_hooks": "در حال اجرای قلاب های ترمیم و بازیابی…", + "restore_running_app_script": "ترمیم و بازیابی برنامه '{app}'…", + "restore_removing_tmp_dir_failed": "پوشه موقت قدیمی حذف نشد", + "restore_nothings_done": "هیچ چیز ترمیم و بازسازی نشد", + "restore_not_enough_disk_space": "فضای کافی موجود نیست (فضا: {free_space:d} B ، فضای مورد نیاز: {needed_space:d} B ، حاشیه امنیتی: {margin:d} B)", + "restore_may_be_not_enough_disk_space": "به نظر می رسد سیستم شما فضای کافی ندارد (فضای آزاد: {free_space:d} B ، فضای مورد نیاز: {space_space:d} B ، حاشیه امنیتی: {margin:d} B)", + "restore_hook_unavailable": "اسکریپت ترمیم و بازسازی برای '{part}' در سیستم شما در دسترس نیست و همچنین در بایگانی نیز وجود ندارد", + "restore_failed": "سیستم بازیابی نشد", + "restore_extracting": "استخراج فایل های مورد نیاز از بایگانی…", + "restore_confirm_yunohost_installed": "آیا واقعاً می خواهید سیستمی که هم اکنون نصب شده را بازیابی کنید؟ [{answers}]", + "restore_complete": "مرمت به پایان رسید", + "restore_cleaning_failed": "فهرست بازسازی موقت پاک نشد", + "restore_backup_too_old": "این بایگانی پشتیبان را نمی توان بازیابی کرد زیرا با نسخه خیلی قدیمی YunoHost تهیه شده است.", + "restore_already_installed_apps": "برنامه های زیر به دلیل نصب بودن قابل بازیابی نیستند: {apps}", + "restore_already_installed_app": "برنامه ای با شناسه '{app}' در حال حاضر نصب شده است", + "regex_with_only_domain": "شما نمی توانید از عبارات منظم برای دامنه استفاده کنید، فقط برای مسیر قابل استفاده است", + "regex_incompatible_with_tile": "/!\\ بسته بندی کنندگان! مجوز '{permission}' show_tile را روی 'true' تنظیم کرده اند و بنابراین نمی توانید عبارت منظم آدرس اینترنتی را به عنوان URL اصلی تعریف کنید", + "regenconf_need_to_explicitly_specify_ssh": "پیکربندی ssh به صورت دستی تغییر یافته است ، اما شما باید صراحتاً دسته \"ssh\" را با --force برای اعمال تغییرات در واقع مشخص کنید.", + "regenconf_pending_applying": "در حال اعمال پیکربندی معلق برای دسته '{category}'...", + "regenconf_failed": "پیکربندی برای دسته (ها) بازسازی نشد: {categories}", + "regenconf_dry_pending_applying": "در حال بررسی پیکربندی معلق که برای دسته '{category}' اعمال می شد…", + "regenconf_would_be_updated": "پیکربندی برای دسته '{category}' به روز می شد", + "regenconf_updated": "پیکربندی برای دسته '{category}' به روز شد", + "regenconf_up_to_date": "پیکربندی در حال حاضر برای دسته '{category}' به روز است", + "regenconf_now_managed_by_yunohost": "فایل پیکربندی '{conf}' اکنون توسط YunoHost (دسته {category}) مدیریت می شود.", + "regenconf_file_updated": "فایل پیکربندی '{conf}' به روز شد", + "regenconf_file_removed": "فایل پیکربندی '{conf}' حذف شد", + "regenconf_file_remove_failed": "فایل پیکربندی '{conf}' حذف نشد", + "regenconf_file_manually_removed": "فایل پیکربندی '{conf}' به صورت دستی حذف شد، و ایجاد نخواهد شد", + "regenconf_file_manually_modified": "فایل پیکربندی '{conf}' به صورت دستی اصلاح شده است و به روز نمی شود", + "regenconf_file_kept_back": "انتظار میرفت که فایل پیکربندی '{conf}' توسط regen-conf (دسته {category}) حذف شود ، اما پس گرفته شد.", + "regenconf_file_copy_failed": "فایل پیکربندی جدید '{new}' در '{conf}' کپی نشد", + "regenconf_file_backed_up": "فایل پیکربندی '{conf}' در '{backup}' پشتیبان گیری شد", + "postinstall_low_rootfsspace": "فضای فایل سیستم اصلی کمتر از 10 گیگابایت است که بسیار نگران کننده است! به احتمال زیاد خیلی زود فضای دیسک شما تمام می شود! توصیه می شود حداقل 16 گیگابایت برای سیستم فایل ریشه داشته باشید. اگر می خواهید YunoHost را با وجود این هشدار نصب کنید ، فرمان نصب را مجدد با این آپشن --force-diskspace اجرا کنید", + "port_already_opened": "پورت {port:d} قبلاً برای اتصالات {ip_version} باز شده است", + "port_already_closed": "پورت {port:d} قبلاً برای اتصالات {ip_version} بسته شده است", + "permission_require_account": "مجوز {permission} فقط برای کاربران دارای حساب کاربری منطقی است و بنابراین نمی تواند برای بازدیدکنندگان فعال شود.", + "permission_protected": "مجوز {permission} محافظت می شود. شما نمی توانید گروه بازدیدکنندگان را از/به این مجوز اضافه یا حذف کنید.", + "permission_updated": "مجوز '{permission}' به روز شد", + "permission_update_failed": "مجوز '{permission}' به روز نشد: {error}", + "permission_not_found": "مجوز '{permission}' پیدا نشد", + "permission_deletion_failed": "اجازه '{permission}' حذف نشد: {error}", + "permission_deleted": "مجوز '{permission}' حذف شد", + "permission_cant_add_to_all_users": "مجوز {permission} را نمی توان به همه کاربران اضافه کرد.", + "permission_currently_allowed_for_all_users": "این مجوز در حال حاضر به همه کاربران علاوه بر آن گروه های دیگر نیز اعطا شده. احتمالاً بخواهید مجوز 'all_users' را حذف کنید یا سایر گروه هایی را که در حال حاضر مجوز به آنها اعطا شده است را هم حذف کنید." } From d2dea2e94ef60ebba49bc262edc5091395ec28e1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 2 Sep 2021 02:24:25 +0200 Subject: [PATCH 2867/3170] Various changes to try to implement a proper dry-run + proper list of stuff to create/update/delete --- src/yunohost/dns.py | 43 +++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index 0866c3662..41c6e73f1 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -468,11 +468,26 @@ def domain_registrar_push(operation_logger, domain, dry_run=False): if not registrar_settings: raise YunohostValidationError("registrar_is_not_set", domain=domain) - # Generate the records - dns_conf = _build_dns_conf(domain) + # Convert the generated conf into a format that matches what we'll fetch using the API + # Makes it easier to compare "wanted records" with "current records on remote" + dns_conf = [] + for records in _build_dns_conf(domain).values(): + for record in records: - # Flatten the DNS conf - dns_conf = [record for records_for_category in dns_conf.values() for record in records_for_category] + # Make sure we got "absolute" values instead of @ + name = f"{record['name']}.{domain}" if record["name"] != "@" else f".{domain}" + type_ = record["type"] + content = record["value"] + + if content == "@" and record["type"] == "CNAME": + content = domain + "." + + dns_conf.append({ + "name": name, + "type": type_, + "ttl": record["ttl"], + "content": content + }) # FIXME Lexicon does not support CAA records # See https://github.com/AnalogJ/lexicon/issues/282 and https://github.com/AnalogJ/lexicon/pull/371 @@ -480,18 +495,6 @@ def domain_registrar_push(operation_logger, domain, dry_run=False): # And yet, it is still not done/merged dns_conf = [record for record in dns_conf if record["type"] != "CAA"] - # We need absolute names? FIXME: should we add a trailing dot needed here ? - # Seems related to the fact that when fetching current records, they'll contain '.domain.tld' instead of @ - # and we want to check if it already exists or not (c.f. create/update) - for record in dns_conf: - if record["name"] == "@": - record["name"] = f".{domain}" - else: - record["name"] = f"{record['name']}.{domain}" - - if record["type"] == "CNAME" and record["value"] == "@": - record["value"] = domain + "." - # Construct the base data structure to use lexicon's API. base_config = { "provider_name": registrar_settings["name"], @@ -499,13 +502,11 @@ def domain_registrar_push(operation_logger, domain, dry_run=False): registrar_settings["name"]: registrar_settings["options"] } - operation_logger.start() - # Fetch all types present in the generated records current_remote_records = [] # Get unique types present in the generated records - types = {record["type"] for record in dns_conf} + types = ["A", "AAAA", "MX", "TXT", "CNAME", "SRV"] for key in types: print("fetcing type: " + key) @@ -525,6 +526,8 @@ def domain_registrar_push(operation_logger, domain, dry_run=False): if dry_run: return {"current_records": current_remote_records, "dns_conf": dns_conf, "changes": changes} + operation_logger.start() + # Push the records for record in dns_conf: @@ -547,7 +550,7 @@ def domain_registrar_push(operation_logger, domain, dry_run=False): # See https://github.com/AnalogJ/lexicon/issues/726 (similar issue) # But I think there is another issue with Gandi. Or I'm misusing the API... if base_config["provider_name"] == "gandi": - del record_to_push["ttle"] + del record_to_push["ttl"] print("pushed_record:", record_to_push) From 7d26b1477fe12756b798defcec0d3126e3a2368f Mon Sep 17 00:00:00 2001 From: ljf Date: Thu, 2 Sep 2021 02:27:22 +0200 Subject: [PATCH 2868/3170] [enh] Some refactoring for config panel --- data/actionsmap/yunohost.yml | 36 ++- data/helpers.d/configpanel | 32 ++- locales/en.json | 2 +- src/yunohost/app.py | 456 +++++++++++++++++------------------ 4 files changed, 263 insertions(+), 263 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 2c91651bd..adc6aa93a 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -831,34 +831,28 @@ app: subcategory_help: Applications configuration panel actions: - ### app_config_show() - show: - action_help: show config panel for the application + ### app_config_get() + get: + action_help: Display an app configuration api: GET /apps//config-panel arguments: app: help: App name key: - help: Select a specific panel, section or a question + help: A specific panel, section or a question identifier nargs: '?' - -f: - full: --full - help: Display all info known about the config-panel. - action: store_true - - ### app_config_get() - get: - action_help: show config panel for the application - api: GET /apps//config-panel/ - arguments: - app: - help: App name - key: - help: The question identifier + -m: + full: --mode + help: Display mode to use + choices: + - classic + - full + - export + default: classic ### app_config_set() set: - action_help: apply the new configuration + action_help: Apply a new configuration api: PUT /apps//config arguments: app: @@ -872,6 +866,10 @@ app: -a: full: --args help: Serialized arguments for new configuration (i.e. "domain=domain.tld&path=/path") + -f: + full: --args-file + help: YAML or JSON file with key/value couples + type: open ############################# # Backup # diff --git a/data/helpers.d/configpanel b/data/helpers.d/configpanel index e99a66d4c..b55b0b0fd 100644 --- a/data/helpers.d/configpanel +++ b/data/helpers.d/configpanel @@ -74,23 +74,29 @@ ynh_value_set() { local value # Manage arguments with getopts ynh_handle_getopts_args "$@" - local var_part='[ \t]*\$?(\w*\[)?[ \t]*["'"']?${key}['"'"]?[ \t]*\]?[ \t]*[:=]>?[ \t]*' - local crazy_value="$(grep -i -o -P "^${var_part}\K.*(?=[ \t,\n;]*\$)" ${file} | head -n1)" + local crazy_value="$(grep -i -o -P '^[ \t]*\$?(\w*\[)?[ \t]*["'"']?${key}['"'"]?[ \t]*\]?[ \t]*[:=]>?[ \t]*\K.*(?=[ \t,\n;]*$)' ${file} | head -n1)" + # local crazy_value="$(grep -i -o -P "^${var_part}\K.*(?=[ \t,\n;]*\$)" ${file} | head -n1)" local first_char="${crazy_value:0:1}" if [[ "$first_char" == '"' ]] ; then - value="$(echo "$value" | sed 's/"/\"/g')" - sed -ri 's%^('"${var_part}"'")[^"]*("[ \t;,]*)$%\1'"${value}"'\3%i' ${file} + # \ and sed is quite complex you need 2 \\ to get one in a sed + # So we need \\\\ to go through 2 sed + value="$(echo "$value" | sed 's/"/\\\\"/g')" + sed -ri 'sø^('"${var_part}"'")([^"]|\\")*("[ \t;,]*)$ø\1'"${value}"'\4øi' ${file} elif [[ "$first_char" == "'" ]] ; then - value="$(echo "$value" | sed "s/'/"'\'"'/g")" - sed -ri "s%^(${var_part}')[^']*('"'[ \t,;]*)$%\1'"${value}"'\3%i' ${file} + # \ and sed is quite complex you need 2 \\ to get one in a sed + # However double quotes implies to double \\ to + # So we need \\\\\\\\ to go through 2 sed and 1 double quotes str + value="$(echo "$value" | sed "s/'/\\\\\\\\'/g")" + sed -ri "sø^(${var_part}')([^']|\\')*('"'[ \t,;]*)$ø\1'"${value}"'\4øi' ${file} else if [[ "$value" == *"'"* ]] || [[ "$value" == *'"'* ]] ; then - value='\"'"$(echo "$value" | sed 's/"/\"/g')"'\"' + value='\"'"$(echo "$value" | sed 's/"/\\\\"/g')"'\"' fi - sed -ri "s%^(${var_part}).*"'$%\1'"${value}"'%i' ${file} + sed -ri "sø^(${var_part}).*"'$ø\1'"${value}"'øi' ${file} fi + ynh_print_info "Configuration key '$key' edited into $file" } _ynh_panel_get() { @@ -189,13 +195,16 @@ _ynh_panel_apply() { local source_file="$(echo "$source" | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" if [[ "${!short_setting}" == "" ]] ; then rm -f "$source_file" + ynh_print_info "File '$source_file' removed" else cp "${!short_setting}" "$source_file" + ynh_print_info "File '$source_file' overwrited with ${!short_setting}" fi # Save value in app settings elif [[ "$source" == "settings" ]] ; then ynh_app_setting_set $app $short_setting "${!short_setting}" + ynh_print_info "Configuration key '$short_setting' edited in app settings" # Save multiline text in a file elif [[ "$type" == "text" ]] ; then @@ -204,13 +213,20 @@ _ynh_panel_apply() { fi local source_file="$(echo "$source" | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" echo "${!short_setting}" > "$source_file" + ynh_print_info "File '$source_file' overwrited with the content you provieded in '${short_setting}' question" # Set value into a kind of key/value file else local source_key="$(echo "$source" | cut -d: -f1)" source_key=${source_key:-$short_setting} local source_file="$(echo "$source" | cut -d: -f2 | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" + + ynh_backup_if_checksum_is_different --file="$source_file" ynh_value_set --file="${source_file}" --key="${source_key}" --value="${!short_setting}" + ynh_store_file_checksum --file="$source_file" + + # We stored the info in settings in order to be able to upgrade the app + ynh_app_setting_set $app $short_setting "${!short_setting}" fi fi diff --git a/locales/en.json b/locales/en.json index 3498c00e7..dcbf0f866 100644 --- a/locales/en.json +++ b/locales/en.json @@ -14,7 +14,7 @@ "app_already_installed_cant_change_url": "This app is already installed. The URL cannot be changed just by this function. Check in `app changeurl` if it's available.", "app_already_up_to_date": "{app} is already up-to-date", "app_argument_choice_invalid": "Use one of these choices '{choices}' for the argument '{name}' instead of '{value}'", - "app_argument_invalid": "Pick a valid value for the argument '{name}': {error}", + "app_argument_invalid": "Pick a valid value for the argument '{field}': {error}", "app_argument_password_no_default": "Error while parsing password argument '{name}': password argument can't have a default value for security reason", "app_argument_required": "Argument '{name}' is required", "app_change_url_failed_nginx_reload": "Could not reload NGINX. Here is the output of 'nginx -t':\n{nginx_errors}", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 69c65046a..a4c215328 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1756,135 +1756,122 @@ def app_action_run(operation_logger, app, action, args=None): return logger.success("Action successed!") -# Config panel todo list: -# * docstrings -# * merge translations on the json once the workflow is in place @is_unit_operation() -def app_config_show(operation_logger, app, key='', full=False): - # logger.warning(m18n.n("experimental_feature")) +def app_config_get(operation_logger, app, key='', mode='classic'): + """ + Display an app configuration in classic, full or export mode + """ # Check app is installed _assert_is_installed(app) - key = key if key else '' + filter_key = key or '' # Read config panel toml - config_panel = _get_app_hydrated_config_panel(operation_logger, - app, filter_key=key) + config_panel = _get_app_config_panel(app, filter_key=filter_key) if not config_panel: - return None + raise YunohostError("app_config_no_panel") - # Format result in full or reduce mode - if full: + # Call config script in order to hydrate config panel with current values + values = _call_config_script(operation_logger, app, 'show', config_panel=config_panel) + + # Format result in full mode + if mode == 'full': operation_logger.success() return config_panel - result = OrderedDict() - for panel, section, option in _get_options_iterator(config_panel): - if panel['id'] not in result: - r_panel = result[panel['id']] = OrderedDict() - if section['id'] not in r_panel: - r_section = r_panel[section['id']] = OrderedDict() - r_option = r_section[option['name']] = { - "ask": option['ask']['en'] - } - if not option.get('optional', False): - r_option['ask'] += ' *' - if option.get('current_value', None) is not None: - r_option['value'] = option['current_value'] + # In 'classic' mode, we display the current value if key refer to an option + if filter_key.count('.') == 2 and mode == 'classic': + option = filter_key.split('.')[-1] + operation_logger.success() + return values.get(option, None) + + # Format result in 'classic' or 'export' mode + logger.debug(f"Formating result in '{mode}' mode") + result = {} + for panel, section, option in _get_config_iterator(config_panel): + key = f"{panel['id']}.{section['id']}.{option['id']}" + if mode == 'export': + result[option['id']] = option.get('current_value') + else: + result[key] = { 'ask': _value_for_locale(option['ask']) } + if 'current_value' in option: + result[key]['value'] = option['current_value'] operation_logger.success() return result @is_unit_operation() -def app_config_get(operation_logger, app, key): +def app_config_set(operation_logger, app, key=None, value=None, args=None, args_file=None): + """ + Apply a new app configuration + """ + # Check app is installed _assert_is_installed(app) + filter_key = key or '' # Read config panel toml - config_panel = _get_app_config_panel(app, filter_key=key) + config_panel = _get_app_config_panel(app, filter_key=filter_key) if not config_panel: raise YunohostError("app_config_no_panel") - operation_logger.start() - - # Call config script to extract current values - parsed_values = _call_config_script(operation_logger, app, 'show') - - logger.debug("Searching value") - short_key = key.split('.')[-1] - if short_key not in parsed_values: - return None - - return parsed_values[short_key] - - # for panel, section, option in _get_options_iterator(config_panel): - # if option['name'] == short_key: - # # Check and transform values if needed - # args_dict = _parse_args_in_yunohost_format( - # parsed_values, [option], False - # ) - # operation_logger.success() - - # return args_dict[short_key][0] - - # return None - - -@is_unit_operation() -def app_config_set(operation_logger, app, key=None, value=None, args=None): - # Check app is installed - _assert_is_installed(app) - - filter_key = key if key else '' - - # Read config panel toml - config_panel = _get_app_hydrated_config_panel(operation_logger, - app, filter_key=filter_key) - - if not config_panel: - raise YunohostError("app_config_no_panel") - - if args is not None and value is not None: + if (args is not None or args_file is not None) and value is not None: raise YunohostError("app_config_args_value") - operation_logger.start() + if filter_key.count('.') != 2 and not value is None: + raise YunohostError("app_config_set_value_on_section") + + # Import and parse pre-answered options + logger.debug("Import and parse pre-answered options") + args = urllib.parse.parse_qs(args or '', keep_blank_values=True) + args = { key: ','.join(value_) for key, value_ in args.items() } + + if args_file: + # Import YAML / JSON file but keep --args values + args = { **read_yaml(args_file), **args } - # Prepare pre answered questions - if args: - args = { key: ','.join(value) for key, value in urllib.parse.parse_qs(args, keep_blank_values=True).items() } - else: - args = {} if value is not None: args = {filter_key.split('.')[-1]: value} + # Call config script in order to hydrate config panel with current values + _call_config_script(operation_logger, app, 'show', config_panel=config_panel) + + # Ask unanswered question and prevalidate + logger.debug("Ask unanswered question and prevalidate data") + def display_header(message): + """ CLI panel/section header display + """ + if Moulinette.interface.type == 'cli' and filter_key.count('.') < 2: + Moulinette.display(colorize(message, 'purple')) + try: - logger.debug("Asking unanswered question and prevalidating...") - args_dict = {} - for panel in config_panel.get("panel", []): - if Moulinette.interface.type== 'cli' and len(filter_key.split('.')) < 3: - Moulinette.display(colorize("\n" + "=" * 40, 'purple')) - Moulinette.display(colorize(f">>>> {panel['name']}", 'purple')) - Moulinette.display(colorize("=" * 40, 'purple')) - for section in panel.get("sections", []): - if Moulinette.interface.type== 'cli' and len(filter_key.split('.')) < 3: - Moulinette.display(colorize(f"\n# {section['name']}", 'purple')) + env = {} + for panel, section, obj in _get_config_iterator(config_panel, + ['panel', 'section']): + if panel == obj: + name = _value_for_locale(panel['name']) + display_header(f"\n{'='*40}\n>>>> {name}\n{'='*40}") + continue + name = _value_for_locale(section['name']) + display_header(f"\n# {name}") - # Check and ask unanswered questions - args_dict.update(_parse_args_in_yunohost_format( - args, section['options'] - )) + # Check and ask unanswered questions + env.update(_parse_args_in_yunohost_format( + args, section['options'] + )) - # Call config script to extract current values + # Call config script in 'apply' mode logger.info("Running config script...") - env = {key: str(value[0]) for key, value in args_dict.items() if not value[0] is None} + env = {key: str(value[0]) for key, value in env.items() if not value[0] is None} errors = _call_config_script(operation_logger, app, 'apply', env=env) - # Script got manually interrupted ... N.B. : KeyboardInterrupt does not inherit from Exception + # Script got manually interrupted ... + # N.B. : KeyboardInterrupt does not inherit from Exception except (KeyboardInterrupt, EOFError): error = m18n.n("operation_interrupted") logger.error(m18n.n("app_config_failed", app=app, error=error)) @@ -1904,25 +1891,20 @@ def app_config_set(operation_logger, app, key=None, value=None, args=None): if errors: return { - "app": app, "errors": errors, } # Reload services logger.info("Reloading services...") - services_to_reload = set([]) - for panel in config_panel.get("panel", []): - services_to_reload |= set(panel.get('services', [])) - for section in panel.get("sections", []): - services_to_reload |= set(section.get('services', [])) - for option in section.get("options", []): - services_to_reload |= set(option.get('services', [])) + services_to_reload = set() + for panel, section, obj in _get_config_iterator(config_panel, + ['panel', 'section', 'option']): + services_to_reload |= set(obj.get('services', [])) services_to_reload = list(services_to_reload) services_to_reload.sort(key = 'nginx'.__eq__) for service in services_to_reload: - if service == "__APP__": - service = app + service = service.replace('__APP__', app) logger.debug(f"Reloading {service}") if not _run_service_command('reload-or-restart', service): services = _get_services() @@ -1934,23 +1916,27 @@ def app_config_set(operation_logger, app, key=None, value=None, args=None): ) logger.success("Config updated as expected") - return { - "app": app, - "errors": [], - "logs": operation_logger.success(), - } + return {} -def _get_options_iterator(config_panel): - for panel in config_panel.get("panel", []): +def _get_config_iterator(config_panel, trigger=['option']): + for panel in config_panel.get("panels", []): + if 'panel' in trigger: + yield (panel, None, panel) for section in panel.get("sections", []): - for option in section.get("options", []): - yield (panel, section, option) + if 'section' in trigger: + yield (panel, section, section) + if 'option' in trigger: + for option in section.get("options", []): + yield (panel, section, option) -def _call_config_script(operation_logger, app, action, env={}): +def _call_config_script(operation_logger, app, action, env={}, config_panel=None): from yunohost.hook import hook_exec + YunoHostArgumentFormatParser.operation_logger = operation_logger + operation_logger.start() + # Add default config script if needed config_script = os.path.join(APPS_SETTING_PATH, app, "scripts", "config") if not os.path.exists(config_script): @@ -1976,7 +1962,27 @@ ynh_panel_run $1 config_script, args=[action], env=env ) if ret != 0: - operation_logger.error(parsed_values) + if action == 'show': + raise YunohostError("app_config_unable_to_read_values") + else: + raise YunohostError("app_config_unable_to_apply_values_correctly") + + return parsed_values + + if not config_panel: + return parsed_values + + # Hydrating config panel with current value + logger.debug("Hydrating config with current values") + for _, _, option in _get_config_iterator(config_panel): + if option['name'] not in parsed_values: + continue + value = parsed_values[option['name']] + # In general, the value is just a simple value. + # Sometimes it could be a dict used to overwrite the option itself + value = value if isinstance(value, dict) else {'current_value': value } + option.update(value) + return parsed_values @@ -2083,6 +2089,13 @@ def _get_app_actions(app_id): def _get_app_config_panel(app_id, filter_key=''): "Get app config panel stored in json or in toml" + + # Split filter_key + filter_key = dict(enumerate(filter_key.split('.'))) + if len(filter_key) > 3: + raise YunohostError("app_config_too_much_sub_keys") + + # Open TOML config_panel_toml_path = os.path.join( APPS_SETTING_PATH, app_id, "config_panel.toml" ) @@ -2103,7 +2116,7 @@ def _get_app_config_panel(app_id, filter_key=''): # name = "Choose the sources of packages to automatically upgrade." # default = "Security only" # type = "text" - # help = "We can't use a choices field for now. In the meantime please choose between one of this values:
Security only, Security and updates." + # help = "We can't use a choices field for now. In the meantime[...]" # # choices = ["Security only", "Security and updates"] # [main.unattended_configuration.ynh_update] @@ -2143,7 +2156,7 @@ def _get_app_config_panel(app_id, filter_key=''): # u'name': u'50unattended-upgrades configuration file', # u'options': [{u'//': u'"choices" : ["Security only", "Security and updates"]', # u'default': u'Security only', - # u'help': u"We can't use a choices field for now. In the meantime please choose between one of this values:
Security only, Security and updates.", + # u'help': u"We can't use a choices field for now. In the meantime[...]", # u'id': u'upgrade_level', # u'name': u'Choose the sources of packages to automatically upgrade.', # u'type': u'text'}, @@ -2152,127 +2165,81 @@ def _get_app_config_panel(app_id, filter_key=''): # u'name': u'Would you like to update YunoHost packages automatically ?', # u'type': u'bool'}, - if os.path.exists(config_panel_toml_path): - toml_config_panel = toml.load( - open(config_panel_toml_path, "r"), _dict=OrderedDict - ) - if float(toml_config_panel["version"]) < APPS_CONFIG_PANEL_VERSION_SUPPORTED: - raise YunohostError( - "app_config_too_old_version", app=app_id, - version=toml_config_panel["version"] - ) - - # transform toml format into json format - config_panel = { - "name": toml_config_panel["name"], - "version": toml_config_panel["version"], - "panel": [], - } - filter_key = filter_key.split('.') - filter_panel = filter_key.pop(0) - filter_section = filter_key.pop(0) if len(filter_key) > 0 else False - filter_option = filter_key.pop(0) if len(filter_key) > 0 else False - - panels = [ - key_value - for key_value in toml_config_panel.items() - if key_value[0] not in ("name", "version") - and isinstance(key_value[1], OrderedDict) - ] - - for key, value in panels: - if filter_panel and key != filter_panel: - continue - - panel = { - "id": key, - "name": value.get("name", ""), - "services": value.get("services", []), - "sections": [], - } - - sections = [ - k_v1 - for k_v1 in value.items() - if k_v1[0] not in ("name",) and isinstance(k_v1[1], OrderedDict) - ] - - for section_key, section_value in sections: - - if filter_section and section_key != filter_section: - continue - - section = { - "id": section_key, - "name": section_value.get("name", ""), - "optional": section_value.get("optional", True), - "services": section_value.get("services", []), - "options": [], - } - if section_value.get('visibleIf'): - section['visibleIf'] = section_value.get('visibleIf') - - options = [ - k_v - for k_v in section_value.items() - if k_v[0] not in ("name",) and isinstance(k_v[1], OrderedDict) - ] - - for option_key, option_value in options: - if filter_option and option_key != filter_option: - continue - - option = dict(option_value) - option["optional"] = option_value.get("optional", section['optional']) - option["name"] = option_key - option["ask"] = {"en": option["ask"]} - if "help" in option: - option["help"] = {"en": option["help"]} - section["options"].append(option) - - panel["sections"].append(section) - - config_panel["panel"].append(panel) - - if (filter_panel and len(config_panel['panel']) == 0) or \ - (filter_section and len(config_panel['panel'][0]['sections']) == 0) or \ - (filter_option and len(config_panel['panel'][0]['sections'][0]['options']) == 0): - raise YunohostError( - "app_config_bad_filter_key", app=app_id, filter_key=filter_key - ) - - return config_panel - - return None - -def _get_app_hydrated_config_panel(operation_logger, app, filter_key=''): - - # Read config panel toml - config_panel = _get_app_config_panel(app, filter_key=filter_key) - - if not config_panel: + if not os.path.exists(config_panel_toml_path): return None + toml_config_panel = read_toml(config_panel_toml_path) - operation_logger.start() + # Check TOML config panel is in a supported version + if float(toml_config_panel["version"]) < APPS_CONFIG_PANEL_VERSION_SUPPORTED: + raise YunohostError( + "app_config_too_old_version", app=app_id, + version=toml_config_panel["version"] + ) - # Call config script to extract current values - parsed_values = _call_config_script(operation_logger, app, 'show') + # Transform toml format into internal format + defaults = { + 'toml': { + 'version': 1.0 + }, + 'panels': { + 'name': '', + 'services': [], + 'actions': {'apply': {'en': 'Apply'}} + }, # help + 'sections': { + 'name': '', + 'services': [], + 'optional': True + }, # visibleIf help + 'options': {} + # ask type source help helpLink example style icon placeholder visibleIf + # optional choices pattern limit min max step accept redact + } - # # Check and transform values if needed - # options = [option for _, _, option in _get_options_iterator(config_panel)] - # args_dict = _parse_args_in_yunohost_format( - # parsed_values, options, False - # ) + def convert(toml_node, node_type): + """Convert TOML in internal format ('full' mode used by webadmin) - # Hydrate - logger.debug("Hydrating config with current value") - for _, _, option in _get_options_iterator(config_panel): - if option['name'] in parsed_values: - value = parsed_values[option['name']] - if isinstance(value, dict): - option.update(value) + Here are some properties of 1.0 config panel in toml: + - node properties and node children are mixed, + - text are in english only + - some properties have default values + This function detects all children nodes and put them in a list + """ + # Prefill the node default keys if needed + default = defaults[node_type] + node = {key: toml_node.get(key, value) for key, value in default.items()} + + # Define the filter_key part to use and the children type + i = list(defaults).index(node_type) + search_key = filter_key.get(i) + subnode_type = list(defaults)[i+1] if node_type != 'options' else None + + for key, value in toml_node.items(): + # Key/value are a child node + if isinstance(value, OrderedDict) and key not in default and subnode_type: + # We exclude all nodes not referenced by the filter_key + if search_key and key != search_key: + continue + subnode = convert(value, subnode_type) + subnode['id'] = key + if node_type == 'sections': + subnode['name'] = key # legacy + subnode.setdefault('optional', toml_node.get('optional', True)) + node.setdefault(subnode_type, []).append(subnode) + # Key/value are a property else: - option["current_value"] = value #args_dict[option["name"]][0] + # Todo search all i18n keys + node[key] = value if key not in ['ask', 'help', 'name'] else { 'en': value } + return node + + config_panel = convert(toml_config_panel, 'toml') + + try: + config_panel['panels'][0]['sections'][0]['options'][0] + except (KeyError, IndexError): + raise YunohostError( + "app_config_empty_or_bad_filter_key", app=app_id, filter_key=filter_key + ) return config_panel @@ -2831,6 +2798,7 @@ class Question: class YunoHostArgumentFormatParser(object): hide_user_input_in_prompt = False + operation_logger = None def parse_question(self, question, user_answers): parsed_question = Question() @@ -2842,10 +2810,11 @@ class YunoHostArgumentFormatParser(object): parsed_question.optional = question.get("optional", False) parsed_question.choices = question.get("choices", []) parsed_question.pattern = question.get("pattern") - parsed_question.ask = question.get("ask", {'en': f"Enter value for '{parsed_question.name}':"}) + parsed_question.ask = question.get("ask", {'en': f"{parsed_question.name}"}) parsed_question.help = question.get("help") parsed_question.helpLink = question.get("helpLink") parsed_question.value = user_answers.get(parsed_question.name) + parsed_question.redact = question.get('redact', False) # Empty value is parsed as empty string if parsed_question.default == "": @@ -2947,6 +2916,27 @@ class YunoHostArgumentFormatParser(object): return text_for_user_input_in_cli def _post_parse_value(self, question): + if not question.redact: + return question.value + + # Tell the operation_logger to redact all password-type / secret args + # Also redact the % escaped version of the password that might appear in + # the 'args' section of metadata (relevant for password with non-alphanumeric char) + data_to_redact = [] + if question.value and isinstance(question.value, str): + data_to_redact.append(question.value) + if question.current_value and isinstance(question.current_value, str): + data_to_redact.append(question.current_value) + data_to_redact += [ + urllib.parse.quote(data) + for data in data_to_redact + if urllib.parse.quote(data) != data + ] + if self.operation_logger: + self.operation_logger.data_to_redact.extend(data_to_redact) + elif data_to_redact: + raise YunohostError("app_argument_cant_redact", arg=question.name) + return question.value @@ -2954,12 +2944,6 @@ class StringArgumentParser(YunoHostArgumentFormatParser): argument_type = "string" default_value = "" - def _prevalidate(self, question): - super()._prevalidate(question) - raise YunohostValidationError( - "app_argument_invalid", field=question.name, error=m18n.n("invalid_number2") - ) - class TagsArgumentParser(YunoHostArgumentFormatParser): argument_type = "tags" @@ -2982,7 +2966,7 @@ class PasswordArgumentParser(YunoHostArgumentFormatParser): question = super(PasswordArgumentParser, self).parse_question( question, user_answers ) - + question.redact = True if question.default is not None: raise YunohostValidationError( "app_argument_password_no_default", name=question.name @@ -3242,6 +3226,8 @@ class FileArgumentParser(YunoHostArgumentFormatParser): # os.path.join to avoid the user to be able to rewrite a file in filesystem # i.e. os.path.join("/foo", "/etc/passwd") == "/etc/passwd" file_path = os.path.normpath(upload_dir + "/" + filename) + if not file_path.startswith(upload_dir + "/"): + raise YunohostError("relative_parent_path_in_filename_forbidden") i = 2 while os.path.exists(file_path): file_path = os.path.normpath(upload_dir + "/" + filename + (".%d" % i)) From f96f7dd1fdd70d438e5651b191653803543022ee Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 2 Sep 2021 03:53:52 +0200 Subject: [PATCH 2869/3170] Fuck orthotypography, let's enforce practices for fr i18n --- .gitlab/ci/translation.gitlab-ci.yml | 5 +- locales/fr.json | 478 +++++++++++++-------------- tests/reformat_fr_translations.py | 27 ++ 3 files changed, 269 insertions(+), 241 deletions(-) create mode 100644 tests/reformat_fr_translations.py diff --git a/.gitlab/ci/translation.gitlab-ci.yml b/.gitlab/ci/translation.gitlab-ci.yml index d7962436c..c8fbb9147 100644 --- a/.gitlab/ci/translation.gitlab-ci.yml +++ b/.gitlab/ci/translation.gitlab-ci.yml @@ -16,10 +16,11 @@ remove-stale-translated-strings: # create a local branch that will overwrite distant one - git checkout -b "ci-remove-stale-translated-strings-${CI_COMMIT_REF_NAME}" --no-track - python3 remove_stale_translated_strings.py + - python3 reformat_fr_translations.py - '[ $(git diff -w | wc -l) != 0 ] || exit 0' # stop if there is nothing to commit - - git commit -am "[CI] Remove stale translated strings" || true + - git commit -am "[CI] Reformat / remove stale translated strings" || true - git push -f origin "ci-remove-stale-translated-strings-${CI_COMMIT_REF_NAME}":"ci-remove-stale-translated-strings-${CI_COMMIT_REF_NAME}" - - hub pull-request -m "[CI] Remove stale translated strings" -b Yunohost:dev -p || true # GITHUB_USER and GITHUB_TOKEN registered here https://gitlab.com/yunohost/yunohost/-/settings/ci_cd + - hub pull-request -m "[CI] Reformat / remove stale translated strings" -b Yunohost:dev -p || true # GITHUB_USER and GITHUB_TOKEN registered here https://gitlab.com/yunohost/yunohost/-/settings/ci_cd only: variables: - $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH diff --git a/locales/fr.json b/locales/fr.json index e3a32d639..53c486f26 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -1,46 +1,46 @@ { "action_invalid": "Action '{action}' incorrecte", - "admin_password": "Mot de passe d’administration", + "admin_password": "Mot de passe d'administration", "admin_password_change_failed": "Impossible de changer le mot de passe", - "admin_password_changed": "Le mot de passe d’administration a été modifié", + "admin_password_changed": "Le mot de passe d'administration a été modifié", "app_already_installed": "{app} est déjà installé", - "app_argument_choice_invalid": "Choix invalide pour le paramètre '{name}', il doit être l’un de {choices}", + "app_argument_choice_invalid": "Choix invalide pour le paramètre '{name}', il doit être l'un de {choices}", "app_argument_invalid": "Valeur invalide pour le paramètre '{name}' : {error}", "app_argument_required": "Le paramètre '{name}' est requis", - "app_extraction_failed": "Impossible d’extraire les fichiers d’installation", - "app_id_invalid": "Identifiant d’application invalide", - "app_install_files_invalid": "Fichiers d’installation incorrects", - "app_manifest_invalid": "Manifeste d’application incorrect : {error}", + "app_extraction_failed": "Impossible d'extraire les fichiers d'installation", + "app_id_invalid": "Identifiant d'application invalide", + "app_install_files_invalid": "Fichiers d'installation incorrects", + "app_manifest_invalid": "Manifeste d'application incorrect : {error}", "app_not_correctly_installed": "{app} semble être mal installé", - "app_not_installed": "Nous n’avons pas trouvé {app} dans la liste des applications installées : {all_apps}", - "app_not_properly_removed": "{app} n’a pas été supprimé correctement", + "app_not_installed": "Nous n'avons pas trouvé {app} dans la liste des applications installées : {all_apps}", + "app_not_properly_removed": "{app} n'a pas été supprimé correctement", "app_removed": "{app} désinstallé", "app_requirements_checking": "Vérification des paquets requis pour {app}...", "app_requirements_unmeet": "Les pré-requis de {app} ne sont pas satisfaits, le paquet {pkgname} ({version}) doit être {spec}", - "app_sources_fetch_failed": "Impossible de récupérer les fichiers sources, l’URL est-elle correcte ?", + "app_sources_fetch_failed": "Impossible de récupérer les fichiers sources, l'URL est-elle correcte ?", "app_unknown": "Application inconnue", - "app_unsupported_remote_type": "Ce type de commande à distance utilisé pour cette application n’est pas supporté", + "app_unsupported_remote_type": "Ce type de commande à distance utilisé pour cette application n'est pas supporté", "app_upgrade_failed": "Impossible de mettre à jour {app} : {error}", "app_upgraded": "{app} mis à jour", "ask_firstname": "Prénom", "ask_lastname": "Nom", "ask_main_domain": "Domaine principal", - "ask_new_admin_password": "Nouveau mot de passe d’administration", + "ask_new_admin_password": "Nouveau mot de passe d'administration", "ask_password": "Mot de passe", "backup_app_failed": "Impossible de sauvegarder {app}", - "backup_archive_app_not_found": "{app} n’a pas été trouvée dans l’archive de la sauvegarde", + "backup_archive_app_not_found": "{app} n'a pas été trouvée dans l'archive de la sauvegarde", "backup_archive_name_exists": "Une archive de sauvegarde avec ce nom existe déjà.", - "backup_archive_name_unknown": "L’archive locale de sauvegarde nommée '{name}' est inconnue", - "backup_archive_open_failed": "Impossible d’ouvrir l’archive de la sauvegarde", + "backup_archive_name_unknown": "L'archive locale de sauvegarde nommée '{name}' est inconnue", + "backup_archive_open_failed": "Impossible d'ouvrir l'archive de la sauvegarde", "backup_cleaning_failed": "Impossible de nettoyer le dossier temporaire de sauvegarde", "backup_created": "Sauvegarde terminée", - "backup_creation_failed": "Impossible de créer l’archive de la sauvegarde", + "backup_creation_failed": "Impossible de créer l'archive de la sauvegarde", "backup_delete_error": "Impossible de supprimer '{path}'", "backup_deleted": "La sauvegarde a été supprimée", "backup_hook_unknown": "Script de sauvegarde '{hook}' inconnu", - "backup_nothings_done": "Il n’y a rien à sauvegarder", + "backup_nothings_done": "Il n'y a rien à sauvegarder", "backup_output_directory_forbidden": "Choisissez un répertoire de destination différent. Les sauvegardes ne peuvent pas être créées dans les sous-dossiers /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var ou /home/yunohost.backup/archives", - "backup_output_directory_not_empty": "Le répertoire de destination n’est pas vide", + "backup_output_directory_not_empty": "Le répertoire de destination n'est pas vide", "backup_output_directory_required": "Vous devez spécifier un dossier de destination pour la sauvegarde", "backup_running_hooks": "Exécution des scripts de sauvegarde...", "custom_app_url_required": "Vous devez spécifier une URL pour mettre à jour votre application personnalisée {app}", @@ -57,33 +57,33 @@ "domain_uninstall_app_first": "Ces applications sont toujours installées sur votre domaine :\n{apps}\n\nVeuillez les désinstaller avec la commande 'yunohost app remove nom-de-l-application' ou les déplacer vers un autre domaine avec la commande 'yunohost app change-url nom-de-l-application' avant de procéder à la suppression du domaine", "domain_unknown": "Domaine inconnu", "done": "Terminé", - "downloading": "Téléchargement en cours…", - "dyndns_ip_update_failed": "Impossible de mettre à jour l’adresse IP sur le domaine DynDNS", + "downloading": "Téléchargement en cours...", + "dyndns_ip_update_failed": "Impossible de mettre à jour l'adresse IP sur le domaine DynDNS", "dyndns_ip_updated": "Mise à jour de votre IP pour le domaine DynDNS", "dyndns_key_generating": "Génération de la clé DNS..., cela peut prendre un certain temps.", "dyndns_key_not_found": "Clé DNS introuvable pour le domaine", "dyndns_no_domain_registered": "Aucun domaine n'a été enregistré avec DynDNS", "dyndns_registered": "Domaine DynDNS enregistré", - "dyndns_registration_failed": "Impossible d’enregistrer le domaine DynDNS : {error}", + "dyndns_registration_failed": "Impossible d'enregistrer le domaine DynDNS : {error}", "dyndns_unavailable": "Le domaine {domain} est indisponible.", "extracting": "Extraction en cours...", "field_invalid": "Champ incorrect : '{}'", "firewall_reload_failed": "Impossible de recharger le pare-feu", "firewall_reloaded": "Pare-feu rechargé", "firewall_rules_cmd_failed": "Certaines commandes de règles de pare-feu ont échoué. Plus d'informations dans le journal.", - "hook_exec_failed": "Échec de l’exécution du script : {path}", - "hook_exec_not_terminated": "L’exécution du script {path} ne s’est pas terminée correctement", + "hook_exec_failed": "Échec de l'exécution du script : {path}", + "hook_exec_not_terminated": "L'exécution du script {path} ne s'est pas terminée correctement", "hook_list_by_invalid": "Propriété invalide pour lister les actions par celle-ci", - "hook_name_unknown": "Nom de l’action '{name}' inconnu", + "hook_name_unknown": "Nom de l'action '{name}' inconnu", "installation_complete": "Installation terminée", "ip6tables_unavailable": "Vous ne pouvez pas jouer avec ip6tables ici. Vous êtes soit dans un conteneur, soit votre noyau ne le prend pas en charge", "iptables_unavailable": "Vous ne pouvez pas jouer avec iptables ici. Vous êtes soit dans un conteneur, soit votre noyau ne le prend pas en charge", - "mail_alias_remove_failed": "Impossible de supprimer l’alias de courriel '{mail}'", - "mail_domain_unknown": "Le domaine '{domain}' de cette adresse email n’est pas valide. Merci d’utiliser un domaine administré par ce serveur.", + "mail_alias_remove_failed": "Impossible de supprimer l'alias de email '{mail}'", + "mail_domain_unknown": "Le domaine '{domain}' de cette adresse email n'est pas valide. Merci d'utiliser un domaine administré par ce serveur.", "mail_forward_remove_failed": "Impossible de supprimer l'email de transfert '{mail}'", "main_domain_change_failed": "Impossible de modifier le domaine principal", "main_domain_changed": "Le domaine principal a été modifié", - "not_enough_disk_space": "L’espace disque est insuffisant sur '{path}'", + "not_enough_disk_space": "L'espace disque est insuffisant sur '{path}'", "packages_upgrade_failed": "Impossible de mettre à jour tous les paquets", "pattern_backup_archive_name": "Doit être un nom de fichier valide avec un maximum de 30 caractères, et composé de caractères alphanumériques et -_. uniquement", "pattern_domain": "Doit être un nom de domaine valide (ex : mon-domaine.fr)", @@ -91,42 +91,42 @@ "pattern_firstname": "Doit être un prénom valide", "pattern_lastname": "Doit être un nom valide", "pattern_mailbox_quota": "Doit avoir une taille suffixée avec b/k/M/G/T ou 0 pour désactiver le quota", - "pattern_password": "Doit être composé d’au moins 3 caractères", + "pattern_password": "Doit être composé d'au moins 3 caractères", "pattern_port_or_range": "Doit être un numéro de port valide compris entre 0 et 65535, ou une gamme de ports (exemple : 100:200)", "pattern_positive_number": "Doit être un nombre positif", "pattern_username": "Doit être composé uniquement de caractères alphanumériques minuscules et de tirets bas (aussi appelé tiret du 8 ou underscore)", "port_already_closed": "Le port {port:d} est déjà fermé pour les connexions {ip_version}", "port_already_opened": "Le port {port:d} est déjà ouvert pour les connexions {ip_version}", - "restore_already_installed_app": "Une application est déjà installée avec l’identifiant '{app}'", - "app_restore_failed": "Impossible de restaurer {app} : {error}", + "restore_already_installed_app": "Une application est déjà installée avec l'identifiant '{app}'", + "app_restore_failed": "Impossible de restaurer {app} : {error}", "restore_cleaning_failed": "Impossible de nettoyer le dossier temporaire de restauration", "restore_complete": "Restauration terminée", "restore_confirm_yunohost_installed": "Voulez-vous vraiment restaurer un système déjà installé ? [{answers}]", "restore_failed": "Impossible de restaurer le système", - "restore_hook_unavailable": "Le script de restauration '{part}' n’est pas disponible sur votre système, et ne l’est pas non plus dans l’archive", - "restore_nothings_done": "Rien n’a été restauré", - "restore_running_app_script": "Exécution du script de restauration de l’application '{app}'…", - "restore_running_hooks": "Exécution des scripts de restauration…", - "service_add_failed": "Impossible d’ajouter le service '{service}'", + "restore_hook_unavailable": "Le script de restauration '{part}' n'est pas disponible sur votre système, et ne l'est pas non plus dans l'archive", + "restore_nothings_done": "Rien n'a été restauré", + "restore_running_app_script": "Exécution du script de restauration de l'application '{app}'...", + "restore_running_hooks": "Exécution des scripts de restauration...", + "service_add_failed": "Impossible d'ajouter le service '{service}'", "service_added": "Le service '{service}' a été ajouté", - "service_already_started": "Le service '{service}' est déjà en cours d’exécution", + "service_already_started": "Le service '{service}' est déjà en cours d'exécution", "service_already_stopped": "Le service '{service}' est déjà arrêté", - "service_cmd_exec_failed": "Impossible d’exécuter la commande '{command}'", - "service_disable_failed": "Impossible de ne pas lancer le service « {service} » au démarrage.\n\nJournaux récents du service : {logs}", - "service_disabled": "Le service « {service} » ne sera plus lancé au démarrage du système.", - "service_enable_failed": "Impossible de lancer automatiquement le service « {service} » au démarrage.\n\nJournaux récents du service : {logs}", - "service_enabled": "Le service « {service} » sera désormais lancé automatiquement au démarrage du système.", + "service_cmd_exec_failed": "Impossible d'exécuter la commande '{command}'", + "service_disable_failed": "Impossible de ne pas lancer le service '{service}' au démarrage.\n\nJournaux récents du service : {logs}", + "service_disabled": "Le service '{service}' ne sera plus lancé au démarrage du système.", + "service_enable_failed": "Impossible de lancer automatiquement le service '{service}' au démarrage.\n\nJournaux récents du service : {logs}", + "service_enabled": "Le service '{service}' sera désormais lancé automatiquement au démarrage du système.", "service_remove_failed": "Impossible de supprimer le service '{service}'", - "service_removed": "Le service « {service} » a été supprimé", + "service_removed": "Le service '{service}' a été supprimé", "service_start_failed": "Impossible de démarrer le service '{service}'\n\nJournaux historisés récents : {logs}", - "service_started": "Le service « {service} » a été démarré", - "service_stop_failed": "Impossible d’arrêter le service '{service}'\n\nJournaux récents de service : {logs}", - "service_stopped": "Le service « {service} » a été arrêté", + "service_started": "Le service '{service}' a été démarré", + "service_stop_failed": "Impossible d'arrêter le service '{service}'\n\nJournaux récents de service : {logs}", + "service_stopped": "Le service '{service}' a été arrêté", "service_unknown": "Le service '{service}' est inconnu", "ssowat_conf_generated": "La configuration de SSOwat a été regénérée", "ssowat_conf_updated": "La configuration de SSOwat a été mise à jour", "system_upgraded": "Système mis à jour", - "system_username_exists": "Ce nom d’utilisateur existe déjà dans les utilisateurs système", + "system_username_exists": "Ce nom d'utilisateur existe déjà dans les utilisateurs système", "unbackup_app": "'{app}' ne sera pas sauvegardée", "unexpected_error": "Une erreur inattendue est survenue : {error}", "unlimit": "Pas de quota", @@ -134,133 +134,133 @@ "updating_apt_cache": "Récupération des mises à jour disponibles pour les paquets du système...", "upgrade_complete": "Mise à jour terminée", "upgrading_packages": "Mise à jour des paquets en cours...", - "upnp_dev_not_found": "Aucun périphérique compatible UPnP n’a été trouvé", + "upnp_dev_not_found": "Aucun périphérique compatible UPnP n'a été trouvé", "upnp_disabled": "L'UPnP est désactivé", "upnp_enabled": "L'UPnP est activé", - "upnp_port_open_failed": "Impossible d’ouvrir les ports UPnP", - "user_created": "L’utilisateur a été créé", - "user_creation_failed": "Impossible de créer l’utilisateur {user} : {error}", - "user_deleted": "L’utilisateur a été supprimé", - "user_deletion_failed": "Impossible de supprimer l’utilisateur {user} : {error}", - "user_home_creation_failed": "Impossible de créer le dossier personnel de l’utilisateur", - "user_unknown": "L’utilisateur {user} est inconnu", - "user_update_failed": "Impossible de mettre à jour l’utilisateur {user} : {error}", - "user_updated": "L’utilisateur a été modifié", + "upnp_port_open_failed": "Impossible d'ouvrir les ports UPnP", + "user_created": "L'utilisateur a été créé", + "user_creation_failed": "Impossible de créer l'utilisateur {user} : {error}", + "user_deleted": "L'utilisateur a été supprimé", + "user_deletion_failed": "Impossible de supprimer l'utilisateur {user} : {error}", + "user_home_creation_failed": "Impossible de créer le dossier personnel de l'utilisateur", + "user_unknown": "L'utilisateur {user} est inconnu", + "user_update_failed": "Impossible de mettre à jour l'utilisateur {user} : {error}", + "user_updated": "L'utilisateur a été modifié", "yunohost_already_installed": "YunoHost est déjà installé", "yunohost_configured": "YunoHost est maintenant configuré", - "yunohost_installing": "L’installation de YunoHost est en cours...", - "yunohost_not_installed": "YunoHost n’est pas correctement installé. Veuillez exécuter 'yunohost tools postinstall'", + "yunohost_installing": "L'installation de YunoHost est en cours...", + "yunohost_not_installed": "YunoHost n'est pas correctement installé. Veuillez exécuter 'yunohost tools postinstall'", "certmanager_attempt_to_replace_valid_cert": "Vous êtes en train de vouloir remplacer un certificat correct et valide pour le domaine {domain} ! (Utilisez --force pour contourner cela)", - "certmanager_domain_cert_not_selfsigned": "Le certificat du domaine {domain} n’est pas auto-signé. Voulez-vous vraiment le remplacer ? (Utilisez --force pour cela)", - "certmanager_certificate_fetching_or_enabling_failed": "Il semble que l’activation du nouveau certificat pour {domain} a échoué...", - "certmanager_attempt_to_renew_nonLE_cert": "Le certificat pour le domaine {domain} 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} n’est pas sur le point d’expirer ! (Vous pouvez utiliser --force si vous savez ce que vous faites)", + "certmanager_domain_cert_not_selfsigned": "Le certificat du domaine {domain} n'est pas auto-signé. Voulez-vous vraiment le remplacer ? (Utilisez --force pour cela)", + "certmanager_certificate_fetching_or_enabling_failed": "Il semble que l'activation du nouveau certificat pour {domain} a échoué...", + "certmanager_attempt_to_renew_nonLE_cert": "Le certificat pour le domaine {domain} 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} 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} 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_domain_dns_ip_differs_from_public_ip": "Les enregistrements DNS du domaine '{domain}' sont différents de l’adresse IP de ce serveur. Pour plus d'informations, veuillez consulter la catégorie \"Enregistrements DNS\" dans la section diagnostic. 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} (fichier : {file}), la cause est : {reason}", + "certmanager_domain_dns_ip_differs_from_public_ip": "Les enregistrements DNS du domaine '{domain}' sont différents de l'adresse IP de ce serveur. Pour plus d'informations, veuillez consulter la catégorie \"Enregistrements DNS\" dans la section diagnostic. 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} (fichier : {file}), la cause est : {reason}", "certmanager_cert_install_success_selfsigned": "Le certificat auto-signé est maintenant installé pour le domaine '{domain}'", - "certmanager_cert_install_success": "Le certificat Let’s Encrypt est maintenant installé pour le domaine '{domain}'", - "certmanager_cert_renew_success": "Certificat Let’s Encrypt renouvelé pour le domaine '{domain}'", + "certmanager_cert_install_success": "Le certificat Let's Encrypt est maintenant installé pour le domaine '{domain}'", + "certmanager_cert_renew_success": "Certificat Let's Encrypt renouvelé pour le domaine '{domain}'", "certmanager_cert_signing_failed": "Impossible de signer le nouveau certificat", "certmanager_no_cert_file": "Impossible de lire le fichier du certificat pour le domaine {domain} (fichier : {file})", "certmanager_hit_rate_limit": "Trop de certificats ont déjà été émis récemment pour ce même ensemble de domaines {domain}. Veuillez réessayer plus tard. Lisez https://letsencrypt.org/docs/rate-limits/ pour obtenir plus de détails sur les ratios et limitations", - "domain_cannot_remove_main": "Vous ne pouvez pas supprimer '{domain}' car il s’agit du domaine principal. Vous devez d’abord définir un autre domaine comme domaine principal à l’aide de 'yunohost domain main-domain -n ', voici la liste des domaines candidats : {other_domains}", - "certmanager_self_ca_conf_file_not_found": "Le fichier de configuration pour l’autorité du certificat auto-signé est introuvable (fichier : {file})", - "certmanager_unable_to_parse_self_CA_name": "Impossible d’analyser le nom de l’autorité du certificat auto-signé (fichier : {file})", - "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", + "domain_cannot_remove_main": "Vous ne pouvez pas supprimer '{domain}' car il s'agit du domaine principal. Vous devez d'abord définir un autre domaine comme domaine principal à l'aide de 'yunohost domain main-domain -n ', voici la liste des domaines candidats : {other_domains}", + "certmanager_self_ca_conf_file_not_found": "Le fichier de configuration pour l'autorité du certificat auto-signé est introuvable (fichier : {file})", + "certmanager_unable_to_parse_self_CA_name": "Impossible d'analyser le nom de l'autorité du certificat auto-signé (fichier : {file})", + "mailbox_used_space_dovecot_down": "Le service de email 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})", + "backup_archive_broken_link": "Impossible d'accéder à l'archive de sauvegarde (lien invalide vers {path})", "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`.", - "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).", - "app_already_installed_cant_change_url": "Cette application est déjà installée. L’URL ne peut pas être changé simplement par cette fonction. Vérifiez si cela est disponible avec `app changeurl`.", + "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).", + "app_already_installed_cant_change_url": "Cette application est déjà installée. L'URL ne peut pas être changé simplement par cette fonction. Vérifiez si cela est disponible avec `app changeurl`.", "app_change_url_failed_nginx_reload": "Le redémarrage de NGINX a échoué. Voici la sortie de 'nginx -t' :\n{nginx_errors}", - "app_change_url_identical_domains": "L’ancien et le nouveau couple domaine/chemin_de_l’URL sont identiques pour ('{domain}{path}'), rien à faire.", - "app_change_url_no_script": "L’application '{app_name}' ne prend pas encore en charge le changement d’URL. Vous devriez peut-être la mettre à jour.", - "app_change_url_success": "L’URL de l’application {app} a été changée en {domain}{path}", - "app_location_unavailable": "Cette URL n’est pas disponible ou est en conflit avec une application existante :\n{apps}", + "app_change_url_identical_domains": "L'ancien et le nouveau couple domaine/chemin_de_l'URL sont identiques pour ('{domain}{path}'), rien à faire.", + "app_change_url_no_script": "L'application '{app_name}' ne prend pas encore en charge le changement d'URL. Vous devriez peut-être la mettre à jour.", + "app_change_url_success": "L'URL de l'application {app} a été changée en {domain}{path}", + "app_location_unavailable": "Cette URL n'est pas disponible ou est en conflit avec une application existante :\n{apps}", "app_already_up_to_date": "{app} est déjà à jour", - "global_settings_bad_choice_for_enum": "Valeur du paramètre {setting} incorrecte. Reçu : {choice}, mais les valeurs possibles sont : {available_choices}", + "global_settings_bad_choice_for_enum": "Valeur du paramètre {setting} incorrecte. Reçu : {choice}, mais les valeurs possibles sont : {available_choices}", "global_settings_bad_type_for_setting": "Le type du paramètre {setting} est incorrect. Reçu {received_type} alors que {expected_type} était attendu", - "global_settings_cant_open_settings": "Échec de l’ouverture du ficher de configurations car : {reason}", - "global_settings_cant_write_settings": "Échec d’écriture du fichier de configurations car : {reason}", - "global_settings_key_doesnt_exists": "La clef '{settings_key}' n’existe pas dans les configurations générales, vous pouvez voir toutes les clefs disponibles en saisissant 'yunohost settings list'", + "global_settings_cant_open_settings": "Échec de l'ouverture du ficher de configurations car : {reason}", + "global_settings_cant_write_settings": "Échec d'écriture du fichier de configurations car : {reason}", + "global_settings_key_doesnt_exists": "La clef '{settings_key}' n'existe pas dans les configurations générales, vous pouvez voir toutes les clefs disponibles en saisissant 'yunohost settings list'", "global_settings_reset_success": "Vos configurations précédentes ont été sauvegardées dans {path}", - "global_settings_unknown_type": "Situation inattendue : la configuration {setting} semble avoir le type {unknown_type} mais celui-ci n’est pas pris en charge par le système.", + "global_settings_unknown_type": "Situation inattendue : la configuration {setting} semble avoir le type {unknown_type} mais celui-ci n'est pas pris en charge par le système.", "global_settings_unknown_setting_from_settings_file": "Clé inconnue dans les paramètres : '{setting_key}', rejet de cette clé et sauvegarde de celle-ci dans /etc/yunohost/unkown_settings.json", "backup_abstract_method": "Cette méthode de sauvegarde reste à implémenter", - "backup_applying_method_tar": "Création de l’archive TAR de la sauvegarde...", + "backup_applying_method_tar": "Création de l'archive TAR de la sauvegarde...", "backup_applying_method_copy": "Copie de tous les fichiers à sauvegarder...", "backup_applying_method_custom": "Appel de la méthode de sauvegarde personnalisée '{method}'...", - "backup_archive_system_part_not_available": "La partie '{part}' du système n’est pas disponible dans cette sauvegarde", - "backup_archive_writing_error": "Impossible d’ajouter des fichiers '{source}' (nommés dans l’archive : '{dest}') à sauvegarder dans l’archive compressée '{archive}'", - "backup_ask_for_copying_if_needed": "Voulez-vous effectuer la sauvegarde en utilisant {size}Mo temporairement ? (Cette méthode est utilisée car certains fichiers n’ont pas pu être préparés avec une méthode plus efficace.)", - "backup_cant_mount_uncompress_archive": "Impossible de monter en lecture seule le dossier de l’archive décompressée", - "backup_copying_to_organize_the_archive": "Copie de {size} Mo pour organiser l’archive", + "backup_archive_system_part_not_available": "La partie '{part}' du système n'est pas disponible dans cette sauvegarde", + "backup_archive_writing_error": "Impossible d'ajouter des fichiers '{source}' (nommés dans l'archive : '{dest}') à sauvegarder dans l'archive compressée '{archive}'", + "backup_ask_for_copying_if_needed": "Voulez-vous effectuer la sauvegarde en utilisant {size}Mo temporairement ? (Cette méthode est utilisée car certains fichiers n'ont pas pu être préparés avec une méthode plus efficace.)", + "backup_cant_mount_uncompress_archive": "Impossible de monter en lecture seule le dossier de l'archive décompressée", + "backup_copying_to_organize_the_archive": "Copie de {size} Mo pour organiser l'archive", "backup_csv_creation_failed": "Impossible de créer le fichier CSV nécessaire à la restauration", - "backup_csv_addition_failed": "Impossible d’ajouter des fichiers à sauvegarder dans le fichier CSV", - "backup_custom_backup_error": "Échec de la méthode de sauvegarde personnalisée à l’étape 'backup'", - "backup_custom_mount_error": "Échec de la méthode de sauvegarde personnalisée à l’étape 'mount'", - "backup_no_uncompress_archive_dir": "Ce dossier d’archive décompressée n’existe pas", - "backup_method_tar_finished": "L’archive TAR de la sauvegarde a été créée", + "backup_csv_addition_failed": "Impossible d'ajouter des fichiers à sauvegarder dans le fichier CSV", + "backup_custom_backup_error": "Échec de la méthode de sauvegarde personnalisée à l'étape 'backup'", + "backup_custom_mount_error": "Échec de la méthode de sauvegarde personnalisée à l'étape 'mount'", + "backup_no_uncompress_archive_dir": "Ce dossier d'archive décompressée n'existe pas", + "backup_method_tar_finished": "L'archive TAR de la sauvegarde a été créée", "backup_method_copy_finished": "La copie de la sauvegarde est terminée", "backup_method_custom_finished": "La méthode de sauvegarde personnalisée '{method}' est terminée", "backup_system_part_failed": "Impossible de sauvegarder la partie '{part}' du système", - "backup_unable_to_organize_files": "Impossible d’utiliser la méthode rapide pour organiser les fichiers dans l’archive", - "backup_with_no_backup_script_for_app": "L’application {app} n’a pas de script de sauvegarde. Ignorer.", - "backup_with_no_restore_script_for_app": "{app} n’a pas de script de restauration, vous ne pourrez pas restaurer automatiquement la sauvegarde de cette application.", + "backup_unable_to_organize_files": "Impossible d'utiliser la méthode rapide pour organiser les fichiers dans l'archive", + "backup_with_no_backup_script_for_app": "L'application {app} n'a pas de script de sauvegarde. Ignorer.", + "backup_with_no_restore_script_for_app": "{app} n'a pas de script de restauration, vous ne pourrez pas restaurer automatiquement la sauvegarde de cette application.", "global_settings_cant_serialize_settings": "Échec de la sérialisation des données de paramétrage car : {reason}", "restore_removing_tmp_dir_failed": "Impossible de sauvegarder un ancien dossier temporaire", - "restore_extracting": "Extraction des fichiers nécessaires depuis l’archive…", - "restore_may_be_not_enough_disk_space": "Votre système ne semble pas avoir suffisamment d’espace (libre : {free_space:d} B, espace nécessaire : {needed_space:d} B, marge de sécurité : {margin:d} B)", - "restore_not_enough_disk_space": "Espace disponible insuffisant (L’espace libre est de {free_space:d} octets. Le besoin d’espace nécessaire est de {needed_space:d} octets. En appliquant une marge de sécurité, la quantité d’espace nécessaire est de {margin:d} octets)", + "restore_extracting": "Extraction des fichiers nécessaires depuis l'archive...", + "restore_may_be_not_enough_disk_space": "Votre système ne semble pas avoir suffisamment d'espace (libre : {free_space:d} B, espace nécessaire : {needed_space:d} B, marge de sécurité : {margin:d} B)", + "restore_not_enough_disk_space": "Espace disponible insuffisant (L'espace libre est de {free_space:d} octets. Le besoin d'espace nécessaire est de {needed_space:d} octets. En appliquant une marge de sécurité, la quantité d'espace nécessaire est de {margin:d} octets)", "restore_system_part_failed": "Impossible de restaurer la partie '{part}' du système", "backup_couldnt_bind": "Impossible de lier {src} avec {dest}.", "domain_dns_conf_is_just_a_recommendation": "Cette commande vous montre la configuration *recommandée*. Elle ne configure pas le DNS pour vous. Il est de votre ressort de configurer votre zone DNS chez votre registrar/fournisseur conformément à cette recommandation.", - "migrations_cant_reach_migration_file": "Impossible d’accéder aux fichiers de migration via le chemin '%s'", + "migrations_cant_reach_migration_file": "Impossible d'accéder aux fichiers de migration via le chemin '%s'", "migrations_loading_migration": "Chargement de la migration {id}...", - "migrations_migration_has_failed": "La migration {id} a échoué avec l’exception {exception} : annulation", + "migrations_migration_has_failed": "La migration {id} a échoué avec l'exception {exception} : annulation", "migrations_no_migrations_to_run": "Aucune migration à lancer", "migrations_skip_migration": "Ignorer et passer la migration {id}...", - "server_shutdown": "Le serveur va s’éteindre", + "server_shutdown": "Le serveur va s'éteindre", "server_shutdown_confirm": "Le serveur va être éteint immédiatement, le voulez-vous vraiment ? [{answers}]", "server_reboot": "Le serveur va redémarrer", "server_reboot_confirm": "Le serveur va redémarrer immédiatement, le voulez-vous vraiment ? [{answers}]", - "app_upgrade_some_app_failed": "Certaines applications n’ont pas été mises à jour", + "app_upgrade_some_app_failed": "Certaines applications n'ont pas été mises à jour", "dyndns_could_not_check_provide": "Impossible de vérifier si {provider} peut fournir {domain}.", "dyndns_domain_not_provided": "Le fournisseur DynDNS {provider} ne peut pas fournir le domaine {domain}.", - "app_make_default_location_already_used": "Impossible de configurer l’application '{app}' par défaut pour le domaine '{domain}' car il est déjà utilisé par l'application '{other_app}'", + "app_make_default_location_already_used": "Impossible de configurer l'application '{app}' par défaut pour le domaine '{domain}' car il est déjà utilisé par l'application '{other_app}'", "app_upgrade_app_name": "Mise à jour de {app}...", - "backup_output_symlink_dir_broken": "Votre répertoire d’archivage '{path}' est un lien symbolique brisé. Peut-être avez-vous oublié de re/monter ou de brancher le support de stockage sur lequel il pointe.", + "backup_output_symlink_dir_broken": "Votre répertoire d'archivage '{path}' est un lien symbolique brisé. Peut-être avez-vous oublié de re/monter ou de brancher le support de stockage sur lequel il pointe.", "migrations_list_conflict_pending_done": "Vous ne pouvez pas utiliser --previous et --done simultanément.", - "migrations_to_be_ran_manually": "La migration {id} doit être lancée manuellement. Veuillez aller dans Outils > Migrations dans l’interface admin, ou lancer `yunohost tools migrations run`.", - "migrations_need_to_accept_disclaimer": "Pour lancer la migration {id}, vous devez accepter cet avertissement :\n---\n{disclaimer}\n---\nSi vous acceptez de lancer la migration, veuillez relancer la commande avec l’option --accept-disclaimer.", - "service_description_yunomdns": "Vous permet d’atteindre votre serveur en utilisant « yunohost.local » sur votre réseau local", + "migrations_to_be_ran_manually": "La migration {id} doit être lancée manuellement. Veuillez aller dans Outils > Migrations dans l'interface admin, ou lancer `yunohost tools migrations run`.", + "migrations_need_to_accept_disclaimer": "Pour lancer la migration {id}, vous devez accepter cet avertissement :\n---\n{disclaimer}\n---\nSi vous acceptez de lancer la migration, veuillez relancer la commande avec l'option --accept-disclaimer.", + "service_description_yunomdns": "Vous permet d'atteindre votre serveur en utilisant 'yunohost.local' sur votre réseau local", "service_description_dnsmasq": "Gère la résolution des noms de domaine (DNS)", - "service_description_dovecot": "Permet aux clients de messagerie d’accéder/récupérer les emails (via IMAP et POP3)", - "service_description_fail2ban": "Protège contre les attaques brute-force et autres types d’attaques venant d’Internet", + "service_description_dovecot": "Permet aux clients de messagerie d'accéder/récupérer les emails (via IMAP et POP3)", + "service_description_fail2ban": "Protège contre les attaques brute-force et autres types d'attaques venant d'Internet", "service_description_metronome": "Gère les comptes de messagerie instantanée XMPP", "service_description_mysql": "Stocke les données des applications (bases de données SQL)", - "service_description_nginx": "Sert ou permet l’accès à tous les sites web hébergés sur votre serveur", + "service_description_nginx": "Sert ou permet l'accès à tous les sites web hébergés sur votre serveur", "service_description_postfix": "Utilisé pour envoyer et recevoir des emails", - "service_description_redis-server": "Une base de données spécialisée utilisée pour l’accès rapide aux données, les files d’attentes et la communication entre les programmes", - "service_description_rspamd": "Filtre le pourriel, et d’autres fonctionnalités liées aux emails", + "service_description_redis-server": "Une base de données spécialisée utilisée pour l'accès rapide aux données, les files d'attentes et la communication entre les programmes", + "service_description_rspamd": "Filtre le pourriel, et d'autres fonctionnalités liées aux emails", "service_description_slapd": "Stocke les utilisateurs, domaines et leurs informations liées", "service_description_ssh": "Vous permet de vous connecter à distance à votre serveur via un terminal (protocole SSH)", - "service_description_yunohost-api": "Permet les interactions entre l’interface web de YunoHost et le système", - "service_description_yunohost-firewall": "Gère l’ouverture et la fermeture des ports de connexion aux services", - "experimental_feature": "Attention : cette fonctionnalité est expérimentale et ne doit pas être considérée comme stable, vous ne devriez pas l’utiliser à moins que vous ne sachiez ce que vous faites.", - "log_corrupted_md_file": "Le fichier YAML de métadonnées associé aux logs est corrompu : '{md_file}'\nErreur : {error}", + "service_description_yunohost-api": "Permet les interactions entre l'interface web de YunoHost et le système", + "service_description_yunohost-firewall": "Gère l'ouverture et la fermeture des ports de connexion aux services", + "experimental_feature": "Attention : cette fonctionnalité est expérimentale et ne doit pas être considérée comme stable, vous ne devriez pas l'utiliser à moins que vous ne sachiez ce que vous faites.", + "log_corrupted_md_file": "Le fichier YAML de métadonnées associé aux logs est corrompu : '{md_file}'\nErreur : {error}", "log_link_to_log": "Journal complet de cette opération : ' {desc} '", "log_help_to_get_log": "Pour voir le journal de cette opération '{desc}', utilisez la commande 'yunohost log show {name}{name}'", - "log_link_to_failed_log": "L’opération '{desc}' a échoué ! Pour obtenir de l’aide, merci de partager le journal de l’opération en cliquant ici", - "log_help_to_get_failed_log": "L’opération '{desc}' a échoué ! Pour obtenir de l’aide, merci de partager le journal de l’opération en utilisant la commande 'yunohost log share {name}'", - "log_does_exists": "Il n’y a pas de journal des opérations avec le nom '{log}', utilisez 'yunohost log list' pour voir tous les journaux d’opérations disponibles", - "log_operation_unit_unclosed_properly": "L’opération ne s’est pas terminée correctement", - "log_app_change_url": "Changer l’URL de l’application '{}'", - "log_app_install": "Installer l’application '{}'", - "log_app_remove": "Enlever l’application '{}'", - "log_app_upgrade": "Mettre à jour l’application '{}'", - "log_app_makedefault": "Faire de '{}' l’application par défaut", + "log_link_to_failed_log": "L'opération '{desc}' a échoué ! Pour obtenir de l'aide, merci de partager le journal de l'opération en cliquant ici", + "log_help_to_get_failed_log": "L'opération '{desc}' a échoué ! Pour obtenir de l'aide, merci de partager le journal de l'opération en utilisant la commande 'yunohost log share {name}'", + "log_does_exists": "Il n'y a pas de journal des opérations avec le nom '{log}', utilisez 'yunohost log list' pour voir tous les journaux d'opérations disponibles", + "log_operation_unit_unclosed_properly": "L'opération ne s'est pas terminée correctement", + "log_app_change_url": "Changer l'URL de l'application '{}'", + "log_app_install": "Installer l'application '{}'", + "log_app_remove": "Enlever l'application '{}'", + "log_app_upgrade": "Mettre à jour l'application '{}'", + "log_app_makedefault": "Faire de '{}' l'application par défaut", "log_available_on_yunopaste": "Le journal est désormais disponible via {url}", "log_backup_restore_system": "Restaurer le système depuis une archive de sauvegarde", "log_backup_restore_app": "Restaurer '{}' depuis une sauvegarde", @@ -269,13 +269,13 @@ "log_domain_add": "Ajouter le domaine '{}' dans la configuration du système", "log_domain_remove": "Enlever le domaine '{}' de la configuration du système", "log_dyndns_subscribe": "Souscrire au sous-domaine YunoHost '{}'", - "log_dyndns_update": "Mettre à jour l’adresse IP associée à votre sous-domaine YunoHost '{}'", - "log_letsencrypt_cert_install": "Installer le certificat Let’s Encrypt sur le domaine '{}'", + "log_dyndns_update": "Mettre à jour l'adresse IP associée à votre sous-domaine YunoHost '{}'", + "log_letsencrypt_cert_install": "Installer le certificat Let's Encrypt sur le domaine '{}'", "log_selfsigned_cert_install": "Installer un certificat auto-signé sur le domaine '{}'", - "log_letsencrypt_cert_renew": "Renouveler le certificat Let’s Encrypt de '{}'", - "log_user_create": "Ajouter l’utilisateur '{}'", - "log_user_delete": "Supprimer l’utilisateur '{}'", - "log_user_update": "Mettre à jour les informations de l’utilisateur '{}'", + "log_letsencrypt_cert_renew": "Renouveler le certificat Let's Encrypt de '{}'", + "log_user_create": "Ajouter l'utilisateur '{}'", + "log_user_delete": "Supprimer l'utilisateur '{}'", + "log_user_update": "Mettre à jour les informations de l'utilisateur '{}'", "log_domain_main_domain": "Faire de '{}' le domaine principal", "log_tools_migrations_migrate_forward": "Exécuter les migrations", "log_tools_postinstall": "Faire la post-installation de votre serveur YunoHost", @@ -290,9 +290,9 @@ "password_too_simple_2": "Le mot de passe doit comporter au moins 8 caractères et contenir des chiffres, des majuscules et des minuscules", "password_too_simple_3": "Le mot de passe doit comporter au moins 8 caractères et contenir des chiffres, des majuscules, des minuscules et des caractères spéciaux", "password_too_simple_4": "Le mot de passe doit comporter au moins 12 caractères et contenir des chiffres, des majuscules, des minuscules et des caractères spéciaux", - "root_password_desynchronized": "Le mot de passe administrateur a été changé, mais YunoHost n’a pas pu le propager au mot de passe root !", + "root_password_desynchronized": "Le mot de passe administrateur a été changé, mais YunoHost n'a pas pu le propager au mot de passe root !", "aborting": "Annulation en cours.", - "app_not_upgraded": "L’application {failed_app} n’a pas été mise à jour et par conséquence les applications suivantes n’ont pas été mises à jour : {apps}", + "app_not_upgraded": "L'application {failed_app} n'a pas été mise à jour et par conséquence les applications suivantes n'ont pas été mises à jour : {apps}", "app_start_install": "Installation de {app}...", "app_start_remove": "Suppression de {app}...", "app_start_backup": "Collecte des fichiers devant être sauvegardés pour {app}...", @@ -300,28 +300,28 @@ "app_upgrade_several_apps": "Les applications suivantes seront mises à jour : {apps}", "ask_new_domain": "Nouveau domaine", "ask_new_path": "Nouveau chemin", - "backup_actually_backuping": "Création d’une archive de sauvegarde à partir des fichiers collectés...", - "backup_mount_archive_for_restore": "Préparation de l’archive pour restauration...", - "confirm_app_install_warning": "Avertissement : cette application peut fonctionner mais n’est pas bien intégrée dans YunoHost. Certaines fonctionnalités telles que l’authentification unique et la sauvegarde/restauration peuvent ne pas être disponibles. L’installer quand même ? [{answers}] ", - "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}'", + "backup_actually_backuping": "Création d'une archive de sauvegarde à partir des fichiers collectés...", + "backup_mount_archive_for_restore": "Préparation de l'archive pour restauration...", + "confirm_app_install_warning": "Avertissement : cette application peut fonctionner mais n'est pas bien intégrée dans YunoHost. Certaines fonctionnalités telles que l'authentification unique et la sauvegarde/restauration peuvent ne pas être disponibles. L'installer quand même ? [{answers}] ", + "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}'", "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}'", "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} est disponible chez {provider}.", - "file_does_not_exist": "Le fichier dont le chemin est {path} n’existe pas.", + "file_does_not_exist": "Le fichier dont le chemin est {path} n'existe pas.", "global_settings_setting_security_password_admin_strength": "Qualité du mot de passe administrateur", - "global_settings_setting_security_password_user_strength": "Qualité du mot de passe de l’utilisateur", - "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Autoriser l’utilisation de la clé hôte DSA (obsolète) pour la configuration du service SSH", + "global_settings_setting_security_password_user_strength": "Qualité du mot de passe de l'utilisateur", + "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Autoriser l'utilisation de la clé hôte DSA (obsolète) pour la configuration du service SSH", "hook_json_return_error": "Échec de la lecture au retour du script {path}. Erreur : {msg}. Contenu brut : {raw_content}", - "pattern_password_app": "Désolé, les mots de passe ne peuvent pas contenir les caractères suivants : {forbidden_chars}", + "pattern_password_app": "Désolé, les mots de passe ne peuvent pas contenir les caractères suivants : {forbidden_chars}", "root_password_replaced_by_admin_password": "Votre mot de passe root a été remplacé par votre mot de passe administrateur.", "service_reload_failed": "Impossible de recharger le service '{service}'.\n\nJournaux historisés récents de ce service : {logs}", - "service_reloaded": "Le service « {service} » a été rechargé", + "service_reloaded": "Le service '{service}' a été rechargé", "service_restart_failed": "Impossible de redémarrer le service '{service}'\n\nJournaux historisés récents de ce service : {logs}", - "service_restarted": "Le service « {service} » a été redémarré", + "service_restarted": "Le service '{service}' a été redémarré", "service_reload_or_restart_failed": "Impossible de recharger ou de redémarrer le service '{service}'\n\nJournaux historisés récents de ce service : {logs}", - "service_reloaded_or_restarted": "Le service « {service} » 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 du 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).", + "service_reloaded_or_restarted": "Le service '{service}' 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 du 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 comportant moins de 127 caractères", "log_regen_conf": "Régénérer les configurations du système '{}'", "regenconf_file_backed_up": "Le fichier de configuration '{conf}' a été sauvegardé sous '{backup}'", @@ -333,26 +333,26 @@ "regenconf_file_updated": "Le fichier de configuration '{conf}' a été mis à jour", "regenconf_now_managed_by_yunohost": "Le fichier de configuration '{conf}' est maintenant géré par YunoHost (catégorie {category}).", "regenconf_up_to_date": "La configuration est déjà à jour pour la catégorie '{category}'", - "already_up_to_date": "Il n’y a rien à faire. Tout est déjà à jour.", - "global_settings_setting_security_nginx_compatibility": "Compatibilité versus compromis sécuritaire pour le serveur web Nginx. Affecte les cryptogrammes (et d’autres aspects liés à la sécurité)", - "global_settings_setting_security_ssh_compatibility": "Compatibilité versus compromis sécuritaire pour le serveur SSH. Affecte les cryptogrammes (et d’autres aspects liés à la sécurité)", - "global_settings_setting_security_postfix_compatibility": "Compatibilité versus compromis sécuritaire pour le serveur Postfix. Affecte les cryptogrammes (et d’autres aspects liés à la sécurité)", - "regenconf_file_kept_back": "Le fichier de configuration '{conf}' devait être supprimé par « regen-conf » (catégorie {category}) mais a été conservé.", + "already_up_to_date": "Il n'y a rien à faire. Tout est déjà à jour.", + "global_settings_setting_security_nginx_compatibility": "Compatibilité versus compromis sécuritaire pour le serveur web Nginx. Affecte les cryptogrammes (et d'autres aspects liés à la sécurité)", + "global_settings_setting_security_ssh_compatibility": "Compatibilité versus compromis sécuritaire pour le serveur SSH. Affecte les cryptogrammes (et d'autres aspects liés à la sécurité)", + "global_settings_setting_security_postfix_compatibility": "Compatibilité versus compromis sécuritaire pour le serveur Postfix. Affecte les cryptogrammes (et d'autres aspects liés à la sécurité)", + "regenconf_file_kept_back": "Le fichier de configuration '{conf}' devait être supprimé par 'regen-conf' (catégorie {category}) mais a été conservé.", "regenconf_updated": "La configuration a été mise à jour pour '{category}'", "regenconf_would_be_updated": "La configuration aurait dû être mise à jour pour la catégorie '{category}'", - "regenconf_dry_pending_applying": "Vérification de la configuration en attente qui aurait été appliquée pour la catégorie '{category}'…", + "regenconf_dry_pending_applying": "Vérification de la configuration en attente qui aurait été appliquée pour la catégorie '{category}'...", "regenconf_failed": "Impossible de régénérer la configuration pour la ou les catégorie(s) : '{categories}'", "regenconf_pending_applying": "Applique la configuration en attente pour la catégorie '{category}'...", "service_regen_conf_is_deprecated": "'yunohost service regen-conf' est obsolète ! Veuillez plutôt utiliser 'yunohost tools regen-conf' à la place.", "tools_upgrade_at_least_one": "Veuillez spécifier '--apps' ou '--system'", "tools_upgrade_cant_both": "Impossible de mettre à niveau le système et les applications en même temps", - "tools_upgrade_cant_hold_critical_packages": "Impossibilité d'ajouter le drapeau 'hold' pour les paquets critiques…", - "tools_upgrade_regular_packages": "Mise à jour des paquets du système (non liés a YunoHost)…", + "tools_upgrade_cant_hold_critical_packages": "Impossibilité d'ajouter le drapeau 'hold' pour les paquets critiques...", + "tools_upgrade_regular_packages": "Mise à jour des paquets du système (non liés a YunoHost)...", "tools_upgrade_regular_packages_failed": "Impossible de mettre à jour les paquets suivants : {packages_list}", - "tools_upgrade_special_packages": "Mise à jour des paquets 'spécifiques' (liés a YunoHost)…", + "tools_upgrade_special_packages": "Mise à jour des paquets 'spécifiques' (liés a YunoHost)...", "tools_upgrade_special_packages_completed": "La mise à jour des paquets de YunoHost est finie !\nPressez [Entrée] pour revenir à la ligne de commande", "dpkg_lock_not_available": "Cette commande ne peut pas être exécutée pour le moment car un autre programme semble utiliser le verrou de dpkg (le gestionnaire de package système)", - "tools_upgrade_cant_unhold_critical_packages": "Impossible d'enlever le drapeau 'hold' pour les paquets critiques…", + "tools_upgrade_cant_unhold_critical_packages": "Impossible d'enlever le drapeau 'hold' pour les paquets critiques...", "tools_upgrade_special_packages_explanation": "La mise à niveau spécifique à YunoHost se poursuivra en arrière-plan. Veuillez ne pas lancer d'autres actions sur votre serveur pendant les 10 prochaines minutes (selon la vitesse du matériel). Après cela, vous devrez peut-être vous reconnecter à la webadmin. Le journal de mise à niveau sera disponible dans Outils → Journal (dans le webadmin) ou en utilisant 'yunohost log list' (à partir de la ligne de commande).", "update_apt_cache_failed": "Impossible de mettre à jour le cache APT (gestionnaire de paquets Debian). Voici un extrait du fichier sources.list qui pourrait vous aider à identifier les lignes problématiques :\n{sourceslist}", "update_apt_cache_warning": "Des erreurs se sont produites lors de la mise à jour du cache APT (gestionnaire de paquets Debian). Voici un extrait des lignes du fichier sources.list qui pourrait vous aider à identifier les lignes problématiques :\n{sourceslist}", @@ -366,62 +366,62 @@ "group_deletion_failed": "Échec de la suppression du groupe '{group}' : {error}", "log_user_group_delete": "Supprimer le groupe '{}'", "log_user_group_update": "Mettre à jour '{}' pour le groupe", - "mailbox_disabled": "La boîte aux lettres est désactivée pour l’utilisateur {user}", + "mailbox_disabled": "La boîte aux lettres est désactivée pour l'utilisateur {user}", "app_action_broke_system": "Cette action semble avoir cassé des services importants : {services}", "apps_already_up_to_date": "Toutes les applications sont déjà à jour", "migrations_must_provide_explicit_targets": "Vous devez fournir des cibles explicites lorsque vous utilisez '--skip' ou '--force-rerun'", - "migrations_no_such_migration": "Il n’y a pas de migration appelée '{id}'", + "migrations_no_such_migration": "Il n'y a pas de migration appelée '{id}'", "migrations_pending_cant_rerun": "Ces migrations étant toujours en attente, vous ne pouvez pas les exécuter à nouveau : {ids}", "migrations_exclusive_options": "'auto', '--skip' et '--force-rerun' sont des options mutuellement exclusives.", "migrations_not_pending_cant_skip": "Ces migrations ne sont pas en attente et ne peuvent donc pas être ignorées : {ids}", "permission_not_found": "Permission '{permission}' introuvable", "permission_update_failed": "Impossible de mettre à jour la permission '{permission}' : {error}", "permission_updated": "Permission '{permission}' mise à jour", - "dyndns_provider_unreachable": "Impossible d’atteindre le fournisseur DynDNS {provider} : votre YunoHost n’est pas correctement connecté à Internet ou le serveur Dynette est en panne.", + "dyndns_provider_unreachable": "Impossible d'atteindre le fournisseur DynDNS {provider} : votre YunoHost n'est pas correctement connecté à Internet ou le serveur Dynette est en panne.", "migrations_already_ran": "Ces migrations sont déjà effectuées : {ids}", "migrations_dependencies_not_satisfied": "Exécutez ces migrations : '{dependencies_id}', avant migration {id}.", "migrations_failed_to_load_migration": "Impossible de charger la migration {id} : {error}", "migrations_running_forward": "Exécution de la migration {id}...", "migrations_success_forward": "Migration {id} terminée", "operation_interrupted": "L'opération a-t-elle été interrompue manuellement ?", - "permission_already_exist": "L’autorisation '{permission}' existe déjà", + "permission_already_exist": "L'autorisation '{permission}' existe déjà", "permission_created": "Permission '{permission}' créée", - "permission_creation_failed": "Impossible de créer l’autorisation '{permission}' : {error}", + "permission_creation_failed": "Impossible de créer l'autorisation '{permission}' : {error}", "permission_deleted": "Permission '{permission}' supprimée", "permission_deletion_failed": "Impossible de supprimer la permission '{permission}' : {error}", "group_already_exist": "Le groupe {group} existe déjà", "group_already_exist_on_system": "Le groupe {group} existe déjà dans les groupes système", "group_cannot_be_deleted": "Le groupe {group} ne peut pas être supprimé manuellement.", - "group_user_already_in_group": "L’utilisateur {user} est déjà dans le groupe {group}", - "group_user_not_in_group": "L’utilisateur {user} n’est pas dans le groupe {group}", + "group_user_already_in_group": "L'utilisateur {user} est déjà dans le groupe {group}", + "group_user_not_in_group": "L'utilisateur {user} n'est pas dans le groupe {group}", "log_permission_create": "Créer permission '{}'", "log_permission_delete": "Supprimer permission '{}'", "log_user_group_create": "Créer le groupe '{}'", "log_user_permission_update": "Mise à jour des accès pour la permission '{}'", "log_user_permission_reset": "Réinitialiser la permission '{}'", - "permission_already_allowed": "Le groupe '{group}' a déjà l’autorisation '{permission}' activée", - "permission_already_disallowed": "Le groupe '{group}' a déjà l’autorisation '{permission}' désactivé", - "permission_cannot_remove_main": "Supprimer une autorisation principale n’est pas autorisé", - "user_already_exists": "L’utilisateur '{user}' existe déjà", - "app_full_domain_unavailable": "Désolé, cette application doit être installée sur un domaine qui lui est propre, mais d’autres applications sont déjà installées sur le domaine '{domain}'. Vous pouvez utiliser un sous-domaine dédié à cette application à la place.", - "group_cannot_edit_all_users": "Le groupe 'all_users' ne peut pas être édité manuellement. C’est un groupe spécial destiné à contenir tous les utilisateurs enregistrés dans YunoHost", - "group_cannot_edit_visitors": "Le groupe 'visiteurs' ne peut pas être édité manuellement. C’est un groupe spécial représentant les visiteurs anonymes", - "group_cannot_edit_primary_group": "Le groupe '{group}' ne peut pas être édité manuellement. C’est le groupe principal destiné à ne contenir qu’un utilisateur spécifique.", - "log_permission_url": "Mise à jour de l’URL associée à l’autorisation '{}'", - "permission_already_up_to_date": "L’autorisation n’a pas été mise à jour car les demandes d’ajout/suppression correspondent déjà à l’état actuel.", - "permission_currently_allowed_for_all_users": "Cette autorisation est actuellement accordée à tous les utilisateurs en plus des autres groupes. Vous voudrez probablement soit supprimer l’autorisation 'all_users', soit supprimer les autres groupes auxquels il est actuellement autorisé.", - "app_install_failed": "Impossible d’installer {app} : {error}", - "app_install_script_failed": "Une erreur est survenue dans le script d’installation de l’application", - "permission_require_account": "Permission {permission} n’a de sens que pour les utilisateurs ayant un compte et ne peut donc pas être activé pour les visiteurs.", - "app_remove_after_failed_install": "Supprimer l’application après l’échec de l’installation...", - "diagnosis_cant_run_because_of_dep": "Impossible d’exécuter le diagnostic pour {category} alors qu’il existe des problèmes importants liés à {dep}.", + "permission_already_allowed": "Le groupe '{group}' a déjà l'autorisation '{permission}' activée", + "permission_already_disallowed": "Le groupe '{group}' a déjà l'autorisation '{permission}' désactivé", + "permission_cannot_remove_main": "Supprimer une autorisation principale n'est pas autorisé", + "user_already_exists": "L'utilisateur '{user}' existe déjà", + "app_full_domain_unavailable": "Désolé, cette application doit être installée sur un domaine qui lui est propre, mais d'autres applications sont déjà installées sur le domaine '{domain}'. Vous pouvez utiliser un sous-domaine dédié à cette application à la place.", + "group_cannot_edit_all_users": "Le groupe 'all_users' ne peut pas être édité manuellement. C'est un groupe spécial destiné à contenir tous les utilisateurs enregistrés dans YunoHost", + "group_cannot_edit_visitors": "Le groupe 'visiteurs' ne peut pas être édité manuellement. C'est un groupe spécial représentant les visiteurs anonymes", + "group_cannot_edit_primary_group": "Le groupe '{group}' ne peut pas être édité manuellement. C'est le groupe principal destiné à ne contenir qu'un utilisateur spécifique.", + "log_permission_url": "Mise à jour de l'URL associée à l'autorisation '{}'", + "permission_already_up_to_date": "L'autorisation n'a pas été mise à jour car les demandes d'ajout/suppression correspondent déjà à l'état actuel.", + "permission_currently_allowed_for_all_users": "Cette autorisation est actuellement accordée à tous les utilisateurs en plus des autres groupes. Vous voudrez probablement soit supprimer l'autorisation 'all_users', soit supprimer les autres groupes auxquels il est actuellement autorisé.", + "app_install_failed": "Impossible d'installer {app} : {error}", + "app_install_script_failed": "Une erreur est survenue dans le script d'installation de l'application", + "permission_require_account": "Permission {permission} n'a de sens que pour les utilisateurs ayant un compte et ne peut donc pas être activé pour les visiteurs.", + "app_remove_after_failed_install": "Supprimer l'application après l'échec de l'installation...", + "diagnosis_cant_run_because_of_dep": "Impossible d'exécuter le diagnostic pour {category} alors qu'il existe des problèmes importants liés à {dep}.", "diagnosis_found_errors": "Trouvé {errors} problème(s) significatif(s) lié(s) à {category} !", "diagnosis_found_errors_and_warnings": "Trouvé {errors} problème(s) significatif(s) (et {warnings} (avertissement(s)) en relation avec {category} !", "diagnosis_ip_not_connected_at_all": "Le serveur ne semble pas du tout connecté à Internet !?", "diagnosis_ip_weird_resolvconf": "La résolution DNS semble fonctionner, mais il semble que vous utilisez un /etc/resolv.conf personnalisé.", "diagnosis_ip_weird_resolvconf_details": "Le fichier /etc/resolv.conf doit être un lien symbolique vers /etc/resolvconf/run/resolv.conf lui-même pointant vers 127.0.0.1 (dnsmasq). Si vous souhaitez configurer manuellement les résolveurs DNS, veuillez modifier /etc/resolv.dnsmasq.conf.", "diagnosis_dns_missing_record": "Selon la configuration DNS recommandée, vous devez ajouter un enregistrement DNS
Type : {type}
Nom : {name}
Valeur: {value}", - "diagnosis_diskusage_ok": "L’espace de stockage {mountpoint} (sur le périphérique {device}) a encore {free} ({free_percent}%) espace restant (sur {total}) !", + "diagnosis_diskusage_ok": "L'espace de stockage {mountpoint} (sur le périphérique {device}) a encore {free} ({free_percent}%) espace restant (sur {total}) !", "diagnosis_ram_ok": "Le système dispose encore de {available} ({available_percent}%) de RAM sur {total}.", "diagnosis_regenconf_allgood": "Tous les fichiers de configuration sont conformes à la configuration recommandée !", "diagnosis_security_vulnerable_to_meltdown": "Vous semblez vulnérable à la vulnérabilité de sécurité critique de Meltdown", @@ -429,7 +429,7 @@ "diagnosis_basesystem_kernel": "Le serveur utilise le noyau Linux {kernel_version}", "diagnosis_basesystem_ynh_single_version": "{package} version : {version} ({repo})", "diagnosis_basesystem_ynh_main_version": "Le serveur utilise YunoHost {main_version} ({repo})", - "diagnosis_basesystem_ynh_inconsistent_versions": "Vous exécutez des versions incohérentes des packages YunoHost ... très probablement en raison d’une mise à niveau échouée ou partielle.", + "diagnosis_basesystem_ynh_inconsistent_versions": "Vous exécutez des versions incohérentes des packages YunoHost ... très probablement en raison d'une mise à niveau échouée ou partielle.", "diagnosis_failed_for_category": "Échec du diagnostic pour la catégorie '{category}': {error}", "diagnosis_cache_still_valid": "(Le cache est encore valide pour le diagnostic {category}. Il ne sera pas re-diagnostiqué pour le moment!)", "diagnosis_ignored_issues": "(+ {nb_ignored} problème(s) ignoré(s))", @@ -437,30 +437,30 @@ "diagnosis_everything_ok": "Tout semble bien pour {category} !", "diagnosis_failed": "Échec de la récupération du résultat du diagnostic pour la catégorie '{category}' : {error}", "diagnosis_ip_connected_ipv4": "Le serveur est connecté à Internet en IPv4 !", - "diagnosis_ip_no_ipv4": "Le serveur ne dispose pas d’une adresse IPv4.", + "diagnosis_ip_no_ipv4": "Le serveur ne dispose pas d'une adresse IPv4.", "diagnosis_ip_connected_ipv6": "Le serveur est connecté à Internet en IPv6 !", - "diagnosis_ip_no_ipv6": "Le serveur ne dispose pas d’une adresse IPv6.", + "diagnosis_ip_no_ipv6": "Le serveur ne dispose pas d'une adresse IPv6.", "diagnosis_ip_dnsresolution_working": "La résolution de nom de domaine fonctionne !", - "diagnosis_ip_broken_dnsresolution": "La résolution du nom de domaine semble interrompue pour une raison quelconque … Un pare-feu bloque-t-il les requêtes DNS ?", + "diagnosis_ip_broken_dnsresolution": "La résolution du nom de domaine semble interrompue pour une raison quelconque ... Un pare-feu bloque-t-il les requêtes DNS ?", "diagnosis_ip_broken_resolvconf": "La résolution du nom de domaine semble être cassée sur votre serveur, ce qui semble lié au fait que /etc/resolv.conf ne pointe pas vers 127.0.0.1.", "diagnosis_dns_good_conf": "Les enregistrements DNS sont correctement configurés pour le domaine {domain} (catégorie {category})", "diagnosis_dns_bad_conf": "Certains enregistrements DNS sont manquants ou incorrects pour le domaine {domain} (catégorie {category})", "diagnosis_dns_discrepancy": "Cet enregistrement DNS ne semble pas correspondre à la configuration recommandée :
Type : {type}
Nom : {name}
La valeur actuelle est : {current}
La valeur attendue est : {value}", "diagnosis_services_bad_status": "Le service {service} est {status} :-(", - "diagnosis_diskusage_verylow": "L'espace de stockage {mountpoint} (sur l’appareil {device}) ne dispose que de {free} ({free_percent}%) espace restant (sur {total}). Vous devriez vraiment envisager de nettoyer de l’espace !", + "diagnosis_diskusage_verylow": "L'espace de stockage {mountpoint} (sur l'appareil {device}) ne dispose que de {free} ({free_percent}%) espace restant (sur {total}). Vous devriez vraiment envisager de nettoyer de l'espace !", "diagnosis_diskusage_low": "L'espace de stockage {mountpoint} (sur l'appareil {device}) ne dispose que de {free} ({free_percent}%) espace restant (sur {total}). Faites attention.", "diagnosis_ram_verylow": "Le système ne dispose plus que de {available} ({available_percent}%)! (sur {total})", - "diagnosis_ram_low": "Le système n’a plus de {available} ({available_percent}%) RAM sur {total}. Faites attention.", - "diagnosis_swap_none": "Le système n’a aucun espace de swap. Vous devriez envisager d’ajouter au moins {recommended} de swap pour éviter les situations où le système manque de mémoire.", - "diagnosis_swap_notsomuch": "Le système ne dispose que de {total} de swap. Vous devez envisager d’avoir au moins {recommended} pour éviter les situations où le système manque de mémoire.", + "diagnosis_ram_low": "Le système n'a plus de {available} ({available_percent}%) RAM sur {total}. Faites attention.", + "diagnosis_swap_none": "Le système n'a aucun espace de swap. Vous devriez envisager d'ajouter au moins {recommended} de swap pour éviter les situations où le système manque de mémoire.", + "diagnosis_swap_notsomuch": "Le système ne dispose que de {total} de swap. Vous devez envisager d'avoir au moins {recommended} pour éviter les situations où le système manque de mémoire.", "diagnosis_swap_ok": "Le système dispose de {total} de swap !", "diagnosis_regenconf_manually_modified": "Le fichier de configuration {file} semble avoir été modifié manuellement.", - "diagnosis_regenconf_manually_modified_details": "C’est probablement OK si vous savez ce que vous faites ! YunoHost cessera de mettre à jour ce fichier automatiquement ... Mais attention, les mises à jour de YunoHost pourraient contenir d’importantes modifications recommandées. Si vous le souhaitez, vous pouvez inspecter les différences avec yunohost tools regen-conf {category} --dry-run --with-diff et forcer la réinitialisation à la configuration recommandée avec yunohost tools regen-conf {category} --force", - "apps_catalog_init_success": "Système de catalogue d’applications initialisé !", + "diagnosis_regenconf_manually_modified_details": "C'est probablement OK si vous savez ce que vous faites ! YunoHost cessera de mettre à jour ce fichier automatiquement ... Mais attention, les mises à jour de YunoHost pourraient contenir d'importantes modifications recommandées. Si vous le souhaitez, vous pouvez inspecter les différences avec yunohost tools regen-conf {category} --dry-run --with-diff et forcer la réinitialisation à la configuration recommandée avec yunohost tools regen-conf {category} --force", + "apps_catalog_init_success": "Système de catalogue d'applications initialisé !", "apps_catalog_failed_to_download": "Impossible de télécharger le catalogue des applications {apps_catalog} : {error}", - "diagnosis_mail_outgoing_port_25_blocked": "Le port sortant 25 semble être bloqué. Vous devriez essayer de le débloquer dans le panneau de configuration de votre fournisseur de services Internet (ou hébergeur). En attendant, le serveur ne pourra pas envoyer des courriels à d’autres serveurs.", - "domain_cannot_remove_main_add_new_one": "Vous ne pouvez pas supprimer '{domain}' car il s’agit du domaine principal et de votre seul domaine. Vous devez d’abord ajouter un autre domaine à l’aide de 'yunohost domain add ', puis définir comme domaine principal à l’aide de 'yunohost domain main-domain -n ' et vous pouvez ensuite supprimer le domaine '{domain}' à l’aide de 'yunohost domain remove {domain}'.'", - "diagnosis_security_vulnerable_to_meltdown_details": "Pour résoudre ce problème, vous devez mettre à niveau votre système et redémarrer pour charger le nouveau noyau Linux (ou contacter votre fournisseur de serveur si cela ne fonctionne pas). Voir https://meltdownattack.com/ pour plus d’informations.", + "diagnosis_mail_outgoing_port_25_blocked": "Le port sortant 25 semble être bloqué. Vous devriez essayer de le débloquer dans le panneau de configuration de votre fournisseur de services Internet (ou hébergeur). En attendant, le serveur ne pourra pas envoyer des emails à d'autres serveurs.", + "domain_cannot_remove_main_add_new_one": "Vous ne pouvez pas supprimer '{domain}' car il s'agit du domaine principal et de votre seul domaine. Vous devez d'abord ajouter un autre domaine à l'aide de 'yunohost domain add ', puis définir comme domaine principal à l'aide de 'yunohost domain main-domain -n ' et vous pouvez ensuite supprimer le domaine '{domain}' à l'aide de 'yunohost domain remove {domain}'.'", + "diagnosis_security_vulnerable_to_meltdown_details": "Pour résoudre ce problème, vous devez mettre à niveau votre système et redémarrer pour charger le nouveau noyau Linux (ou contacter votre fournisseur de serveur si cela ne fonctionne pas). Voir https://meltdownattack.com/ pour plus d'informations.", "diagnosis_description_basesystem": "Système de base", "diagnosis_description_ip": "Connectivité Internet", "diagnosis_description_dnsrecords": "Enregistrements DNS", @@ -470,76 +470,76 @@ "diagnosis_description_regenconf": "Configurations système", "diagnosis_ports_could_not_diagnose": "Impossible de diagnostiquer si les ports sont accessibles de l'extérieur.", "diagnosis_ports_could_not_diagnose_details": "Erreur : {error}", - "apps_catalog_updating": "Mise à jour du catalogue d’applications…", + "apps_catalog_updating": "Mise à jour du catalogue d'applications...", "apps_catalog_obsolete_cache": "Le cache du catalogue d'applications est vide ou obsolète.", "apps_catalog_update_success": "Le catalogue des applications a été mis à jour !", "diagnosis_description_mail": "Email", - "diagnosis_ports_unreachable": "Le port {port} n’est pas accessible de l’extérieur.", - "diagnosis_ports_ok": "Le port {port} est accessible de l’extérieur.", - "diagnosis_http_could_not_diagnose": "Impossible de diagnostiquer si le domaine est accessible de l’extérieur.", + "diagnosis_ports_unreachable": "Le port {port} n'est pas accessible de l'extérieur.", + "diagnosis_ports_ok": "Le port {port} est accessible de l'extérieur.", + "diagnosis_http_could_not_diagnose": "Impossible de diagnostiquer si le domaine est accessible de l'extérieur.", "diagnosis_http_could_not_diagnose_details": "Erreur : {error}", - "diagnosis_http_ok": "Le domaine {domain} est accessible en HTTP depuis l’extérieur.", - "diagnosis_http_unreachable": "Le domaine {domain} est inaccessible en HTTP depuis l’extérieur.", + "diagnosis_http_ok": "Le domaine {domain} est accessible en HTTP depuis l'extérieur.", + "diagnosis_http_unreachable": "Le domaine {domain} est inaccessible en HTTP depuis l'extérieur.", "diagnosis_unknown_categories": "Les catégories suivantes sont inconnues : {categories}", - "app_upgrade_script_failed": "Une erreur s’est produite durant l’exécution du script de mise à niveau de l’application", + "app_upgrade_script_failed": "Une erreur s'est produite durant l'exécution du script de mise à niveau de l'application", "diagnosis_services_running": "Le service {service} est en cours de fonctionnement !", "diagnosis_services_conf_broken": "La configuration est cassée pour le service {service} !", "diagnosis_ports_needed_by": "Rendre ce port accessible est nécessaire pour les fonctionnalités de type {category} (service {service})", "diagnosis_ports_forwarding_tip": "Pour résoudre ce problème, vous devez probablement configurer la redirection de port sur votre routeur Internet comme décrit dans https://yunohost.org/isp_box_config", - "diagnosis_http_connection_error": "Erreur de connexion : impossible de se connecter au domaine demandé, il est probablement injoignable.", - "diagnosis_no_cache": "Pas encore de cache de diagnostique pour la catégorie « {category} »", - "yunohost_postinstall_end_tip": "La post-installation terminée ! Pour finaliser votre configuration, il est recommandé de :\n- ajouter un premier utilisateur depuis la section \"Utilisateurs\" de l’interface web (ou 'yunohost user create ' en ligne de commande) ;\n- diagnostiquer les potentiels problèmes dans la section \"Diagnostic\" de l'interface web (ou 'yunohost diagnosis run' en ligne de commande) ;\n- lire les parties 'Finalisation de votre configuration' et 'Découverte de YunoHost' dans le guide de l’administrateur : https://yunohost.org/admindoc.", + "diagnosis_http_connection_error": "Erreur de connexion : impossible de se connecter au domaine demandé, il est probablement injoignable.", + "diagnosis_no_cache": "Pas encore de cache de diagnostique pour la catégorie '{category}'", + "yunohost_postinstall_end_tip": "La post-installation terminée ! Pour finaliser votre configuration, il est recommandé de :\n- ajouter un premier utilisateur depuis la section \"Utilisateurs\" de l'interface web (ou 'yunohost user create ' en ligne de commande) ;\n- diagnostiquer les potentiels problèmes dans la section \"Diagnostic\" de l'interface web (ou 'yunohost diagnosis run' en ligne de commande) ;\n- lire les parties 'Finalisation de votre configuration' et 'Découverte de YunoHost' dans le guide de l'administrateur : https://yunohost.org/admindoc.", "diagnosis_services_bad_status_tip": "Vous pouvez essayer de redémarrer le service, et si cela ne fonctionne pas, consultez les journaux de service dans le webadmin (à partir de la ligne de commande, vous pouvez le faire avec yunohost service restart {service} et yunohost service log {service} ).", - "diagnosis_http_bad_status_code": "Le système de diagnostique n’a pas réussi à contacter votre serveur. Il se peut qu’une autre machine réponde à la place de votre serveur. Vérifiez que le port 80 est correctement redirigé, que votre configuration Nginx est à jour et qu’un reverse-proxy n’interfère pas.", - "diagnosis_http_timeout": "Expiration du délai en essayant de contacter votre serveur de l’extérieur. Il semble être inaccessible. Vérifiez que vous transférez correctement le port 80, que Nginx est en cours d’exécution et qu’un pare-feu n’interfère pas.", + "diagnosis_http_bad_status_code": "Le système de diagnostique n'a pas réussi à contacter votre serveur. Il se peut qu'une autre machine réponde à la place de votre serveur. Vérifiez que le port 80 est correctement redirigé, que votre configuration Nginx est à jour et qu'un reverse-proxy n'interfère pas.", + "diagnosis_http_timeout": "Expiration du délai en essayant de contacter votre serveur de l'extérieur. Il semble être inaccessible. Vérifiez que vous transférez correctement le port 80, que Nginx est en cours d'exécution et qu'un pare-feu n'interfère pas.", "global_settings_setting_pop3_enabled": "Activer le protocole POP3 pour le serveur de messagerie", - "log_app_action_run": "Lancer l’action de l’application '{}'", - "log_app_config_show_panel": "Montrer le panneau de configuration de l’application '{}'", - "log_app_config_apply": "Appliquer la configuration à l’application '{}'", - "diagnosis_never_ran_yet": "Il apparaît que le serveur a été installé récemment et qu’il n’y a pas encore eu de diagnostic. Vous devriez en lancer un depuis la web-admin ou en utilisant 'yunohost diagnosis run' depuis la ligne de commande.", + "log_app_action_run": "Lancer l'action de l'application '{}'", + "log_app_config_show_panel": "Montrer le panneau de configuration de l'application '{}'", + "log_app_config_apply": "Appliquer la configuration à l'application '{}'", + "diagnosis_never_ran_yet": "Il apparaît que le serveur a été installé récemment et qu'il n'y a pas encore eu de diagnostic. Vous devriez en lancer un depuis la web-admin ou en utilisant 'yunohost diagnosis run' depuis la ligne de commande.", "diagnosis_description_web": "Web", - "diagnosis_basesystem_hardware": "L’architecture du serveur est {virt} {arch}", + "diagnosis_basesystem_hardware": "L'architecture du serveur est {virt} {arch}", "group_already_exist_on_system_but_removing_it": "Le groupe {group} est déjà présent dans les groupes du système, mais YunoHost va le supprimer...", - "certmanager_warning_subdomain_dns_record": "Le sous-domaine '{subdomain}' ne résout pas vers la même adresse IP que '{domain}'. Certaines fonctionnalités seront indisponibles tant que vous n’aurez pas corrigé cela et regénéré le certificat.", - "domain_cannot_add_xmpp_upload": "Vous ne pouvez pas ajouter de domaine commençant par 'xmpp-upload.'. Ce type de nom est réservé à la fonctionnalité d’upload XMPP intégrée dans YunoHost.", - "diagnosis_mail_outgoing_port_25_ok": "Le serveur de messagerie SMTP peut envoyer des courriels (le port sortant 25 n'est pas bloqué).", - "diagnosis_mail_outgoing_port_25_blocked_details": "Vous devez d’abord essayer de débloquer le port sortant 25 dans votre interface de routeur Internet ou votre interface d’hébergement. (Certains hébergeurs peuvent vous demander de leur envoyer un ticket de support pour cela).", + "certmanager_warning_subdomain_dns_record": "Le sous-domaine '{subdomain}' ne résout pas vers la même adresse IP que '{domain}'. Certaines fonctionnalités seront indisponibles tant que vous n'aurez pas corrigé cela et regénéré le certificat.", + "domain_cannot_add_xmpp_upload": "Vous ne pouvez pas ajouter de domaine commençant par 'xmpp-upload.'. Ce type de nom est réservé à la fonctionnalité d'upload XMPP intégrée dans YunoHost.", + "diagnosis_mail_outgoing_port_25_ok": "Le serveur de messagerie SMTP peut envoyer des emails (le port sortant 25 n'est pas bloqué).", + "diagnosis_mail_outgoing_port_25_blocked_details": "Vous devez d'abord essayer de débloquer le port sortant 25 dans votre interface de routeur Internet ou votre interface d'hébergement. (Certains hébergeurs peuvent vous demander de leur envoyer un ticket de support pour cela).", "diagnosis_mail_ehlo_bad_answer": "Un service non SMTP a répondu sur le port 25 en IPv{ipversion}", "diagnosis_mail_ehlo_bad_answer_details": "Cela peut être dû à une autre machine qui répond au lieu de votre serveur.", - "diagnosis_mail_ehlo_wrong": "Un autre serveur de messagerie SMTP répond sur IPv{ipversion}. Votre serveur ne sera probablement pas en mesure de recevoir des courriel.", - "diagnosis_mail_ehlo_could_not_diagnose": "Impossible de diagnostiquer si le serveur de messagerie postfix est accessible de l’extérieur en IPv{ipversion}.", + "diagnosis_mail_ehlo_wrong": "Un autre serveur de messagerie SMTP répond sur IPv{ipversion}. Votre serveur ne sera probablement pas en mesure de recevoir des email.", + "diagnosis_mail_ehlo_could_not_diagnose": "Impossible de diagnostiquer si le serveur de messagerie postfix est accessible de l'extérieur en IPv{ipversion}.", "diagnosis_mail_ehlo_could_not_diagnose_details": "Erreur : {error}", - "diagnosis_mail_fcrdns_dns_missing": "Aucun DNS inverse n’est défini pour IPv{ipversion}. Certains emails seront peut-être refusés ou considérés comme des spam.", + "diagnosis_mail_fcrdns_dns_missing": "Aucun DNS inverse n'est défini pour IPv{ipversion}. Certains emails seront peut-être refusés ou considérés comme des spam.", "diagnosis_mail_fcrdns_ok": "Votre DNS inverse est correctement configuré !", - "diagnosis_mail_fcrdns_nok_details": "Vous devez d’abord essayer de configurer le DNS inverse avec {ehlo_domain} dans votre interface de routeur Internet ou votre interface d’hébergement. (Certains hébergeurs peuvent vous demander de leur envoyer un ticket de support pour cela).", + "diagnosis_mail_fcrdns_nok_details": "Vous devez d'abord essayer de configurer le DNS inverse avec {ehlo_domain} dans votre interface de routeur Internet ou votre interface d'hébergement. (Certains hébergeurs peuvent vous demander de leur envoyer un ticket de support pour cela).", "diagnosis_mail_fcrdns_different_from_ehlo_domain": "Le DNS inverse n'est pas correctement configuré en IPv{ipversion}. Certains emails seront peut-être refusés ou considérés comme des spam.", "diagnosis_mail_blacklist_ok": "Les adresses IP et les domaines utilisés par ce serveur ne semblent pas être sur liste noire", "diagnosis_mail_blacklist_reason": "La raison de la liste noire est : {reason}", - "diagnosis_mail_blacklist_website": "Après avoir identifié la raison pour laquelle vous êtes répertorié et l'avoir corrigé, n’hésitez pas à demander le retrait de votre IP ou domaine sur {blacklist_website}", + "diagnosis_mail_blacklist_website": "Après avoir identifié la raison pour laquelle vous êtes répertorié et l'avoir corrigé, n'hésitez pas à demander le retrait de votre IP ou domaine sur {blacklist_website}", "diagnosis_mail_queue_ok": "{nb_pending} emails en attente dans les files d'attente de messagerie", "diagnosis_mail_queue_unavailable_details": "Erreur : {error}", - "diagnosis_mail_queue_too_big": "Trop d’emails en attente dans la file d'attente ({nb_pending} e-mails)", - "global_settings_setting_smtp_allow_ipv6": "Autoriser l'utilisation d’IPv6 pour recevoir et envoyer du courrier", - "diagnosis_display_tip": "Pour voir les problèmes détectés, vous pouvez accéder à la section Diagnostic du webadmin ou exécuter « yunohost diagnosis show --issues --human-readable» à partir de la ligne de commande.", + "diagnosis_mail_queue_too_big": "Trop d'emails en attente dans la file d'attente ({nb_pending} emails)", + "global_settings_setting_smtp_allow_ipv6": "Autoriser l'utilisation d'IPv6 pour recevoir et envoyer du courrier", + "diagnosis_display_tip": "Pour voir les problèmes détectés, vous pouvez accéder à la section Diagnostic du webadmin ou exécuter 'yunohost diagnosis show --issues --human-readable' à partir de la ligne de commande.", "diagnosis_ip_global": "IP globale : {global}", "diagnosis_ip_local": "IP locale : {local}", - "diagnosis_dns_point_to_doc": "Veuillez consulter la documentation sur https://yunohost.org/dns_config si vous avez besoin d’aide pour configurer les enregistrements DNS.", - "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Certains fournisseurs ne vous laisseront pas débloquer le port sortant 25 parce qu’ils ne se soucient pas de la neutralité du Net.
- Certains d’entre eux offrent l’alternative d'utiliser un serveur de messagerie relai bien que cela implique que le relai sera en mesure d’espionner votre trafic de messagerie.
- Une alternative respectueuse de la vie privée consiste à utiliser un VPN *avec une IP publique dédiée* pour contourner ce type de limites. Voir https://yunohost.org/#/vpn_advantage
- Vous pouvez également envisager de passer à un fournisseur plus respectueux de la neutralité du net", - "diagnosis_mail_ehlo_ok": "Le serveur de messagerie SMTP est accessible de l'extérieur et peut donc recevoir des courriels !", - "diagnosis_mail_ehlo_unreachable": "Le serveur de messagerie SMTP est inaccessible de l’extérieur en IPv{ipversion}. Il ne pourra pas recevoir des courriels.", + "diagnosis_dns_point_to_doc": "Veuillez consulter la documentation sur https://yunohost.org/dns_config si vous avez besoin d'aide pour configurer les enregistrements DNS.", + "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Certains fournisseurs ne vous laisseront pas débloquer le port sortant 25 parce qu'ils ne se soucient pas de la neutralité du Net.
- Certains d'entre eux offrent l'alternative d'utiliser un serveur de messagerie relai bien que cela implique que le relai sera en mesure d'espionner votre trafic de messagerie.
- Une alternative respectueuse de la vie privée consiste à utiliser un VPN *avec une IP publique dédiée* pour contourner ce type de limites. Voir https://yunohost.org/#/vpn_advantage
- Vous pouvez également envisager de passer à un fournisseur plus respectueux de la neutralité du net", + "diagnosis_mail_ehlo_ok": "Le serveur de messagerie SMTP est accessible de l'extérieur et peut donc recevoir des emails !", + "diagnosis_mail_ehlo_unreachable": "Le serveur de messagerie SMTP est inaccessible de l'extérieur en IPv{ipversion}. Il ne pourra pas recevoir des emails.", "diagnosis_mail_ehlo_unreachable_details": "Impossible d'ouvrir une connexion sur le port 25 à votre serveur en IPv{ipversion}. Il semble inaccessible.
1. La cause la plus courante de ce problème est que le port 25 n'est pas correctement redirigé vers votre serveur.
2. Vous devez également vous assurer que le service postfix est en cours d'exécution.
3. Sur les configurations plus complexes: assurez-vous qu'aucun pare-feu ou proxy inversé n'interfère.", - "diagnosis_mail_ehlo_wrong_details": "Le EHLO reçu par le serveur de diagnostique distant en IPv{ipversion} est différent du domaine de votre serveur.
EHLO reçu: {wrong_ehlo}
Attendu : {right_ehlo}
La cause la plus courante ce problème est que le port 25 n’est pas correctement redirigé vers votre serveur . Vous pouvez également vous assurer qu’aucun pare-feu ou proxy inversé n’interfère.", - "diagnosis_mail_fcrdns_nok_alternatives_4": "Certains fournisseurs ne vous laisseront pas configurer votre DNS inversé (ou leur fonctionnalité pourrait être cassée...). Si vous rencontrez des problèmes à cause de cela, envisagez les solutions suivantes :
- Certains FAI fournissent l’alternative de à l’aide d’un relais de serveur de messagerie bien que cela implique que le relais pourra espionner votre trafic de messagerie.
- Une alternative respectueuse de la vie privée consiste à utiliser un VPN *avec une IP publique dédiée* pour contourner ce type de limites. Voir https://yunohost.org/#/vpn_advantage
- Enfin, il est également possible de changer de fournisseur", - "diagnosis_mail_fcrdns_nok_alternatives_6": "Certains fournisseurs ne vous laisseront pas configurer votre DNS inversé (ou leur fonctionnalité pourrait être cassée...). Si votre DNS inversé est correctement configuré en IPv4, vous pouvez essayer de désactiver l'utilisation d'IPv6 lors de l'envoi d'emails en exécutant yunohost settings set smtp.allow_ipv6 -v off. Remarque : cette dernière solution signifie que vous ne pourrez pas envoyer ou recevoir de courriels avec les quelques serveurs qui ont uniquement de l'IPv6.", + "diagnosis_mail_ehlo_wrong_details": "Le EHLO reçu par le serveur de diagnostique distant en IPv{ipversion} est différent du domaine de votre serveur.
EHLO reçu: {wrong_ehlo}
Attendu : {right_ehlo}
La cause la plus courante ce problème est que le port 25 n'est pas correctement redirigé vers votre serveur . Vous pouvez également vous assurer qu'aucun pare-feu ou proxy inversé n'interfère.", + "diagnosis_mail_fcrdns_nok_alternatives_4": "Certains fournisseurs ne vous laisseront pas configurer votre DNS inversé (ou leur fonctionnalité pourrait être cassée...). Si vous rencontrez des problèmes à cause de cela, envisagez les solutions suivantes :
- Certains FAI fournissent l'alternative de à l'aide d'un relais de serveur de messagerie bien que cela implique que le relais pourra espionner votre trafic de messagerie.
- Une alternative respectueuse de la vie privée consiste à utiliser un VPN *avec une IP publique dédiée* pour contourner ce type de limites. Voir https://yunohost.org/#/vpn_advantage
- Enfin, il est également possible de changer de fournisseur", + "diagnosis_mail_fcrdns_nok_alternatives_6": "Certains fournisseurs ne vous laisseront pas configurer votre DNS inversé (ou leur fonctionnalité pourrait être cassée...). Si votre DNS inversé est correctement configuré en IPv4, vous pouvez essayer de désactiver l'utilisation d'IPv6 lors de l'envoi d'emails en exécutant yunohost settings set smtp.allow_ipv6 -v off. Remarque : cette dernière solution signifie que vous ne pourrez pas envoyer ou recevoir de emails avec les quelques serveurs qui ont uniquement de l'IPv6.", "diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "DNS inverse actuel : {rdns_domain}
Valeur attendue : {ehlo_domain}", "diagnosis_mail_blacklist_listed_by": "Votre IP ou domaine {item} est sur liste noire sur {blacklist_name}", - "diagnosis_mail_queue_unavailable": "Impossible de consulter le nombre d’emails en attente dans la file d'attente", + "diagnosis_mail_queue_unavailable": "Impossible de consulter le nombre d'emails en attente dans la file d'attente", "diagnosis_ports_partially_unreachable": "Le port {port} n'est pas accessible de l'extérieur en IPv{failed}.", "diagnosis_http_hairpinning_issue": "Votre réseau local ne semble pas supporter l'hairpinning.", "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_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 tools 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 tools 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 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.", @@ -551,14 +551,14 @@ "diagnosis_domain_expiration_error": "Certains domaines vont expirer TRÈS PROCHAINEMENT !", "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 {domain}. 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.", + "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.", "restore_already_installed_apps": "Les applications suivantes ne peuvent pas être restaurées car elles sont déjà installées : {apps}", "regenconf_need_to_explicitly_specify_ssh": "La configuration de ssh a été modifiée manuellement. Vous devez explicitement indiquer la mention --force à \"ssh\" pour appliquer les changements.", "migration_0015_cleaning_up": "Nettoyage du cache et des paquets qui ne sont plus nécessaires ...", "migration_0015_specific_upgrade": "Démarrage de la mise à jour des paquets du système qui doivent être mis à jour séparément...", "migration_0015_modified_files": "Veuillez noter que les fichiers suivants ont été modifiés manuellement et pourraient être écrasés à la suite de la mise à niveau : {manually_modified_files}", "migration_0015_problematic_apps_warning": "Veuillez noter que des applications qui peuvent poser problèmes ont été détectées. Il semble qu'elles n'aient pas été installées à partir du catalogue d'applications YunoHost, ou bien qu'elles ne soient pas signalées comme \"fonctionnelles\". Par conséquent, il n'est pas possible de garantir que les applications suivantes fonctionneront encore après la mise à niveau : {problematic_apps}", - "migration_0015_general_warning": "Veuillez noter que cette migration est une opération délicate. L'équipe YunoHost a fait de son mieux pour la revérifier et la tester, mais la migration pourrait quand même casser des éléments du système ou de ses applications.\n\nIl est donc recommandé :\n…- de faire une sauvegarde de toute donnée ou application critique. Plus d'informations ici https://yunohost.org/backup ;\n…- d'être patient après le lancement de la migration. Selon votre connexion internet et votre matériel, la mise à niveau peut prendre jusqu'à quelques heures.", + "migration_0015_general_warning": "Veuillez noter que cette migration est une opération délicate. L'équipe YunoHost a fait de son mieux pour la revérifier et la tester, mais la migration pourrait quand même casser des éléments du système ou de ses applications.\n\nIl est donc recommandé :\n...- de faire une sauvegarde de toute donnée ou application critique. Plus d'informations ici https://yunohost.org/backup ;\n...- d'être patient après le lancement de la migration. Selon votre connexion internet et votre matériel, la mise à niveau peut prendre jusqu'à quelques heures.", "migration_0015_system_not_fully_up_to_date": "Votre système n'est pas entièrement à jour. Veuillez effectuer une mise à jour normale avant de lancer la migration vers Buster.", "migration_0015_not_enough_free_space": "L'espace libre est très faible dans /var/ ! Vous devriez avoir au moins 1 Go de libre pour effectuer cette migration.", "migration_0015_not_stretch": "La distribution Debian actuelle n'est pas Stretch !", @@ -615,9 +615,9 @@ "diagnosis_basesystem_hardware_model": "Le modèle/architecture du serveur est {model}", "diagnosis_backports_in_sources_list": "Il semble qu'apt (le gestionnaire de paquets) soit configuré pour utiliser le dépôt des rétroportages (backports). A moins que vous ne sachiez vraiment ce que vous faites, nous vous déconseillons fortement d'installer des paquets provenant des rétroportages, car cela risque de créer des instabilités ou des conflits sur votre système.", "postinstall_low_rootfsspace": "Le système de fichiers a une taille totale inférieure à 10 Go, ce qui est préoccupant et devrait attirer votre attention ! Vous allez certainement arriver à court d'espace disque (très) rapidement ! Il est recommandé d'avoir au moins 16 Go à la racine pour ce système de fichiers. Si vous voulez installer YunoHost malgré cet avertissement, relancez la post-installation avec --force-diskspace", - "domain_remove_confirm_apps_removal": "Le retrait de ce domaine retirera aussi ces applications :\n{apps}\n\nÊtes vous sûr de vouloir cela ? [{answers}]", - "diagnosis_rootfstotalspace_critical": "Le système de fichiers racine ne fait que {space} ! Vous allez certainement le remplir très rapidement ! Il est recommandé d'avoir au moins 16 GB pour ce système de fichiers.", - "diagnosis_rootfstotalspace_warning": "Le système de fichiers racine n'est que de {space}. Cela peut suffire, mais faites attention car vous risquez de les remplir rapidement... Il est recommandé d'avoir au moins 16 GB pour ce système de fichiers.", + "domain_remove_confirm_apps_removal": "Le retrait de ce domaine retirera aussi ces applications :\n{apps}\n\nÊtes vous sûr de vouloir cela ? [{answers}]", + "diagnosis_rootfstotalspace_critical": "Le système de fichiers racine ne fait que {space} ! Vous allez certainement le remplir très rapidement ! Il est recommandé d'avoir au moins 16 GB pour ce système de fichiers.", + "diagnosis_rootfstotalspace_warning": "Le système de fichiers racine n'est que de {space}. Cela peut suffire, mais faites attention car vous risquez de les remplir rapidement... Il est recommandé d'avoir au moins 16 GB pour ce système de fichiers.", "app_restore_script_failed": "Une erreur s'est produite dans le script de restauration de l'application", "restore_backup_too_old": "Cette sauvegarde ne peut pas être restaurée car elle provient d'une version trop ancienne de YunoHost.", "migration_update_LDAP_schema": "Mise à jour du schéma LDAP...", @@ -626,7 +626,7 @@ "migration_ldap_rollback_success": "Système rétabli dans son état initial.", "permission_cant_add_to_all_users": "L'autorisation {permission} ne peut pas être ajoutée à tous les utilisateurs.", "migration_ldap_migration_failed_trying_to_rollback": "Impossible de migrer... tentative de restauration du système.", - "migration_ldap_can_not_backup_before_migration": "La sauvegarde du système n'a pas pu être terminée avant l'échec de la migration. Erreur : {error }", + "migration_ldap_can_not_backup_before_migration": "La sauvegarde du système n'a pas pu être terminée avant l'échec de la migration. Erreur : {error }", "migration_ldap_backup_before_migration": "Création d'une sauvegarde de la base de données LDAP et des paramètres des applications avant la migration proprement dite.", "migration_description_0020_ssh_sftp_permissions": "Ajouter la prise en charge des autorisations SSH et SFTP", "global_settings_setting_security_ssh_port": "Port SSH", diff --git a/tests/reformat_fr_translations.py b/tests/reformat_fr_translations.py new file mode 100644 index 000000000..473ed99d4 --- /dev/null +++ b/tests/reformat_fr_translations.py @@ -0,0 +1,27 @@ +import re + +locale_folder = "locales/" +locale = open(locale_folder + "fr.json").read() + +godamn_spaces_of_hell = ["\u00a0", "\u2000", "\u2001", "\u2002", "\u2003", "\u2004", "\u2005", "\u2006", "\u2007", "\u2008", "\u2009", "\u200A", "\u202f", "\u202F", "\u3000"] + +transformations = {s: " " for s in godamn_spaces_of_hell} + +transformations.update({ + "courriel": "email", + "e-mail": "email", + "Courriel": "Email", + "E-mail": "Email", + "« ": "'", + "«": "'", + " »": "'", + "»": "'", + "…": "...", + "’": "'", + #r"$(\w{1,2})'|( \w{1,2})'": r"\1\2’", +}) + +for pattern, replace in transformations.items(): + locale = re.compile(pattern).sub(replace, locale) + +open(locale_folder + "fr.json", "w").write(locale) From 33750f24d703a32bef94af345a1146280d16b4c4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 2 Sep 2021 04:23:13 +0200 Subject: [PATCH 2870/3170] Remove unecessary :d} in i18n strings --- locales/ca.json | 8 ++++---- locales/de.json | 8 ++++---- locales/en.json | 8 ++++---- locales/eo.json | 8 ++++---- locales/es.json | 8 ++++---- locales/fa.json | 8 ++++---- locales/fr.json | 8 ++++---- locales/gl.json | 8 ++++---- locales/it.json | 8 ++++---- locales/nl.json | 4 ++-- locales/oc.json | 8 ++++---- locales/zh_Hans.json | 8 ++++---- 12 files changed, 46 insertions(+), 46 deletions(-) diff --git a/locales/ca.json b/locales/ca.json index d01c0da0b..5a128ebb8 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -240,8 +240,8 @@ "pattern_positive_number": "Ha de ser un nombre positiu", "pattern_username": "Ha d'estar compost per caràcters alfanumèrics en minúscula i guió baix exclusivament", "pattern_password_app": "Les contrasenyes no poden de tenir els següents caràcters: {forbidden_chars}", - "port_already_closed": "El port {port:d} ja està tancat per les connexions {ip_version}", - "port_already_opened": "El port {port:d} ja està obert per les connexions {ip_version}", + "port_already_closed": "El port {port} ja està tancat per les connexions {ip_version}", + "port_already_opened": "El port {port} ja està obert per les connexions {ip_version}", "regenconf_file_backed_up": "S'ha guardat una còpia de seguretat del fitxer de configuració «{conf}» a «{backup}»", "regenconf_file_copy_failed": "No s'ha pogut copiar el nou fitxer de configuració «{new}» a «{conf}»", "regenconf_file_kept_back": "S'espera que el fitxer de configuració «{conf}» sigui suprimit per regen-conf (categoria {category}) però s'ha mantingut.", @@ -265,8 +265,8 @@ "restore_extracting": "Extracció dels fitxers necessaris de l'arxiu…", "restore_failed": "No s'ha pogut restaurar el sistema", "restore_hook_unavailable": "El script de restauració «{part}» no està disponible en el sistema i tampoc és en l'arxiu", - "restore_may_be_not_enough_disk_space": "Sembla que no hi ha prou espai disponible en el sistema (lliure: {free_space:d} B, espai necessari: {needed_space:d} B, marge de seguretat: {margin:d} B)", - "restore_not_enough_disk_space": "No hi ha prou espai disponible (espai: {free_space:d} B, espai necessari: {needed_space:d} B, marge de seguretat: {margin:d} B)", + "restore_may_be_not_enough_disk_space": "Sembla que no hi ha prou espai disponible en el sistema (lliure: {free_space} B, espai necessari: {needed_space} B, marge de seguretat: {margin} B)", + "restore_not_enough_disk_space": "No hi ha prou espai disponible (espai: {free_space} B, espai necessari: {needed_space} B, marge de seguretat: {margin} B)", "restore_nothings_done": "No s'ha restaurat res", "restore_removing_tmp_dir_failed": "No s'ha pogut eliminar un directori temporal antic", "restore_running_app_script": "Restaurant l'aplicació «{app}»…", diff --git a/locales/de.json b/locales/de.json index 0580eac1d..fe4112934 100644 --- a/locales/de.json +++ b/locales/de.json @@ -83,8 +83,8 @@ "pattern_password": "Muss mindestens drei Zeichen lang sein", "pattern_port_or_range": "Muss ein valider Port (z.B. 0-65535) oder ein Bereich (z.B. 100:200) sein", "pattern_username": "Darf nur aus klein geschriebenen alphanumerischen Zeichen und Unterstrichen bestehen", - "port_already_closed": "Der Port {port:d} wurde bereits für {ip_version} Verbindungen geschlossen", - "port_already_opened": "Der Port {port:d} wird bereits von {ip_version} benutzt", + "port_already_closed": "Der Port {port} wurde bereits für {ip_version} Verbindungen geschlossen", + "port_already_opened": "Der Port {port} wird bereits von {ip_version} benutzt", "restore_already_installed_app": "Eine Applikation mit der ID '{app}' ist bereits installiert", "restore_cleaning_failed": "Das temporäre Dateiverzeichnis für Systemrestaurierung konnte nicht gelöscht werden", "restore_complete": "Vollständig wiederhergestellt", @@ -570,8 +570,8 @@ "regenconf_would_be_updated": "Die Konfiguration wäre für die Kategorie '{category}' aktualisiert worden", "restore_system_part_failed": "Die Systemteile '{part}' konnten nicht wiederhergestellt werden", "restore_removing_tmp_dir_failed": "Ein altes, temporäres Directory konnte nicht entfernt werden", - "restore_not_enough_disk_space": "Nicht genug Speicher (Speicher: {free_space:d} B, benötigter Speicher: {needed_space:d} B, Sicherheitspuffer: {margin:d} B)", - "restore_may_be_not_enough_disk_space": "Ihr System scheint nicht genug Speicherplatz zu haben (frei: {free_space:d} B, benötigter Platz: {needed_space:d} B, Sicherheitspuffer: {margin:d} B)", + "restore_not_enough_disk_space": "Nicht genug Speicher (Speicher: {free_space} B, benötigter Speicher: {needed_space} B, Sicherheitspuffer: {margin} B)", + "restore_may_be_not_enough_disk_space": "Ihr System scheint nicht genug Speicherplatz zu haben (frei: {free_space} B, benötigter Platz: {needed_space} B, Sicherheitspuffer: {margin} B)", "restore_extracting": "Packe die benötigten Dateien aus dem Archiv aus…", "restore_already_installed_apps": "Folgende Apps können nicht wiederhergestellt werden, weil sie schon installiert sind: {apps}", "regex_with_only_domain": "Du kannst regex nicht als Domain verwenden, sondern nur als Pfad", diff --git a/locales/en.json b/locales/en.json index 4c9f3e7fe..65d98d9d1 100644 --- a/locales/en.json +++ b/locales/en.json @@ -525,8 +525,8 @@ "permission_updated": "Permission '{permission}' updated", "permission_protected": "Permission {permission} is protected. You cannot add or remove the visitors group to/from this permission.", "permission_require_account": "Permission {permission} only makes sense for users having an account, and therefore cannot be enabled for visitors.", - "port_already_closed": "Port {port:d} is already closed for {ip_version} connections", - "port_already_opened": "Port {port:d} is already opened for {ip_version} connections", + "port_already_closed": "Port {port} is already closed for {ip_version} connections", + "port_already_opened": "Port {port} is already opened for {ip_version} connections", "postinstall_low_rootfsspace": "The root filesystem has a total space less than 10 GB, which is quite worrisome! You will likely run out of disk space very quickly! It's recommended to have at least 16GB for the root filesystem. If you want to install YunoHost despite this warning, re-run the postinstall with --force-diskspace", "regenconf_file_backed_up": "Configuration file '{conf}' backed up to '{backup}'", "regenconf_file_copy_failed": "Could not copy the new configuration file '{new}' to '{conf}'", @@ -555,8 +555,8 @@ "restore_extracting": "Extracting needed files from the archive…", "restore_failed": "Could not restore system", "restore_hook_unavailable": "Restoration script for '{part}' not available on your system and not in the archive either", - "restore_may_be_not_enough_disk_space": "Your system does not seem to have enough space (free: {free_space:d} B, needed space: {needed_space:d} B, security margin: {margin:d} B)", - "restore_not_enough_disk_space": "Not enough space (space: {free_space:d} B, needed space: {needed_space:d} B, security margin: {margin:d} B)", + "restore_may_be_not_enough_disk_space": "Your system does not seem to have enough space (free: {free_space} B, needed space: {needed_space} B, security margin: {margin} B)", + "restore_not_enough_disk_space": "Not enough space (space: {free_space} B, needed space: {needed_space} B, security margin: {margin} B)", "restore_nothings_done": "Nothing was restored", "restore_removing_tmp_dir_failed": "Could not remove an old temporary directory", "restore_running_app_script": "Restoring the app '{app}'…", diff --git a/locales/eo.json b/locales/eo.json index 76cec1264..f40111f04 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -155,7 +155,7 @@ "permission_deleted": "Permesita \"{permission}\" forigita", "permission_deletion_failed": "Ne povis forigi permeson '{permission}': {error}", "permission_not_found": "Permesita \"{permission}\" ne trovita", - "restore_not_enough_disk_space": "Ne sufiĉa spaco (spaco: {free_space:d} B, necesa spaco: {needed_space:d} B, sekureca marĝeno: {margin:d} B)", + "restore_not_enough_disk_space": "Ne sufiĉa spaco (spaco: {free_space} B, necesa spaco: {needed_space} B, sekureca marĝeno: {margin} B)", "tools_upgrade_regular_packages": "Nun ĝisdatigi 'regulajn' (ne-yunohost-rilatajn) pakojn …", "tools_upgrade_special_packages_explanation": "La speciala ĝisdatigo daŭros en la fono. Bonvolu ne komenci aliajn agojn en via servilo dum la sekvaj ~ 10 minutoj (depende de la aparata rapideco). Post tio, vi eble devos re-ensaluti al la retadreso. La ĝisdatiga registro estos havebla en Iloj → Ensaluto (en la retadreso) aŭ uzante 'yunohost logliston' (el la komandlinio).", "unrestore_app": "App '{app}' ne restarigos", @@ -182,7 +182,7 @@ "service_added": "La servo '{service}' estis aldonita", "upnp_disabled": "UPnP malŝaltis", "service_started": "Servo '{service}' komenciĝis", - "port_already_opened": "Haveno {port:d} estas jam malfermita por {ip_version} rilatoj", + "port_already_opened": "Haveno {port} estas jam malfermita por {ip_version} rilatoj", "upgrading_packages": "Ĝisdatigi pakojn…", "custom_app_url_required": "Vi devas provizi URL por altgradigi vian kutimon app {app}", "service_reload_failed": "Ne povis reŝargi la servon '{service}'\n\nLastatempaj servaj protokoloj: {logs}", @@ -244,7 +244,7 @@ "upnp_port_open_failed": "Ne povis malfermi havenon per UPnP", "log_app_upgrade": "Ĝisdatigu la aplikon '{}'", "log_help_to_get_failed_log": "La operacio '{desc}' ne povis finiĝi. Bonvolu dividi la plenan ŝtipon de ĉi tiu operacio per la komando 'yunohost log share {name}' por akiri helpon", - "port_already_closed": "Haveno {port:d} estas jam fermita por {ip_version} rilatoj", + "port_already_closed": "Haveno {port} estas jam fermita por {ip_version} rilatoj", "hook_name_unknown": "Nekonata hoko-nomo '{name}'", "dyndns_could_not_check_provide": "Ne povis kontroli ĉu {provider} povas provizi {domain}.", "restore_nothings_done": "Nenio estis restarigita", @@ -254,7 +254,7 @@ "root_password_replaced_by_admin_password": "Via radika pasvorto estis anstataŭigita per via administra pasvorto.", "domain_unknown": "Nekonata domajno", "global_settings_setting_security_password_user_strength": "Uzanto pasvorta forto", - "restore_may_be_not_enough_disk_space": "Via sistemo ne ŝajnas havi sufiĉe da spaco (libera: {free_space:d} B, necesa spaco: {needed_space:d} B, sekureca marĝeno: {margin:d} B)", + "restore_may_be_not_enough_disk_space": "Via sistemo ne ŝajnas havi sufiĉe da spaco (libera: {free_space} B, necesa spaco: {needed_space} B, sekureca marĝeno: {margin} B)", "log_corrupted_md_file": "La YAD-metadata dosiero asociita kun protokoloj estas damaĝita: '{md_file}\nEraro: {error} '", "downloading": "Elŝutante …", "user_deleted": "Uzanto forigita", diff --git a/locales/es.json b/locales/es.json index 560bfe240..9af875898 100644 --- a/locales/es.json +++ b/locales/es.json @@ -93,8 +93,8 @@ "pattern_port_or_range": "Debe ser un número de puerto válido (es decir entre 0-65535) o un intervalo de puertos (por ejemplo 100:200)", "pattern_positive_number": "Deber ser un número positivo", "pattern_username": "Solo puede contener caracteres alfanuméricos o el guión bajo", - "port_already_closed": "El puerto {port:d} ya está cerrado para las conexiones {ip_version}", - "port_already_opened": "El puerto {port:d} ya está abierto para las conexiones {ip_version}", + "port_already_closed": "El puerto {port} ya está cerrado para las conexiones {ip_version}", + "port_already_opened": "El puerto {port} ya está abierto para las conexiones {ip_version}", "restore_already_installed_app": "Una aplicación con el ID «{app}» ya está instalada", "app_restore_failed": "No se pudo restaurar la aplicación «{app}»: {error}", "restore_cleaning_failed": "No se pudo limpiar el directorio temporal de restauración", @@ -246,8 +246,8 @@ "root_password_desynchronized": "La contraseña de administración ha sido cambiada pero ¡YunoHost no pudo propagar esto a la contraseña de root!", "restore_system_part_failed": "No se pudo restaurar la parte del sistema «{part}»", "restore_removing_tmp_dir_failed": "No se pudo eliminar un directorio temporal antiguo", - "restore_not_enough_disk_space": "Espacio insuficiente (espacio: {free_space:d} B, espacio necesario: {needed_space:d} B, margen de seguridad: {margin:d} B)", - "restore_may_be_not_enough_disk_space": "Parece que su sistema no tiene suficiente espacio libre (libre: {free_space:d} B, espacio necesario: {needed_space:d} B, margen de seguridad: {margin:d} B)", + "restore_not_enough_disk_space": "Espacio insuficiente (espacio: {free_space} B, espacio necesario: {needed_space} B, margen de seguridad: {margin} B)", + "restore_may_be_not_enough_disk_space": "Parece que su sistema no tiene suficiente espacio libre (libre: {free_space} B, espacio necesario: {needed_space} B, margen de seguridad: {margin} B)", "restore_extracting": "Extrayendo los archivos necesarios para el archivo…", "regenconf_pending_applying": "Aplicando la configuración pendiente para la categoría «{category}»…", "regenconf_failed": "No se pudo regenerar la configuración para la(s) categoría(s): {categories}", diff --git a/locales/fa.json b/locales/fa.json index a644716cf..faabff0ee 100644 --- a/locales/fa.json +++ b/locales/fa.json @@ -601,8 +601,8 @@ "restore_running_app_script": "ترمیم و بازیابی برنامه '{app}'…", "restore_removing_tmp_dir_failed": "پوشه موقت قدیمی حذف نشد", "restore_nothings_done": "هیچ چیز ترمیم و بازسازی نشد", - "restore_not_enough_disk_space": "فضای کافی موجود نیست (فضا: {free_space:d} B ، فضای مورد نیاز: {needed_space:d} B ، حاشیه امنیتی: {margin:d} B)", - "restore_may_be_not_enough_disk_space": "به نظر می رسد سیستم شما فضای کافی ندارد (فضای آزاد: {free_space:d} B ، فضای مورد نیاز: {space_space:d} B ، حاشیه امنیتی: {margin:d} B)", + "restore_not_enough_disk_space": "فضای کافی موجود نیست (فضا: {free_space} B ، فضای مورد نیاز: {needed_space} B ، حاشیه امنیتی: {margin} B)", + "restore_may_be_not_enough_disk_space": "به نظر می رسد سیستم شما فضای کافی ندارد (فضای آزاد: {free_space} B ، فضای مورد نیاز: {space_space} B ، حاشیه امنیتی: {margin} B)", "restore_hook_unavailable": "اسکریپت ترمیم و بازسازی برای '{part}' در سیستم شما در دسترس نیست و همچنین در بایگانی نیز وجود ندارد", "restore_failed": "سیستم بازیابی نشد", "restore_extracting": "استخراج فایل های مورد نیاز از بایگانی…", @@ -631,8 +631,8 @@ "regenconf_file_copy_failed": "فایل پیکربندی جدید '{new}' در '{conf}' کپی نشد", "regenconf_file_backed_up": "فایل پیکربندی '{conf}' در '{backup}' پشتیبان گیری شد", "postinstall_low_rootfsspace": "فضای فایل سیستم اصلی کمتر از 10 گیگابایت است که بسیار نگران کننده است! به احتمال زیاد خیلی زود فضای دیسک شما تمام می شود! توصیه می شود حداقل 16 گیگابایت برای سیستم فایل ریشه داشته باشید. اگر می خواهید YunoHost را با وجود این هشدار نصب کنید ، فرمان نصب را مجدد با این آپشن --force-diskspace اجرا کنید", - "port_already_opened": "پورت {port:d} قبلاً برای اتصالات {ip_version} باز شده است", - "port_already_closed": "پورت {port:d} قبلاً برای اتصالات {ip_version} بسته شده است", + "port_already_opened": "پورت {port} قبلاً برای اتصالات {ip_version} باز شده است", + "port_already_closed": "پورت {port} قبلاً برای اتصالات {ip_version} بسته شده است", "permission_require_account": "مجوز {permission} فقط برای کاربران دارای حساب کاربری منطقی است و بنابراین نمی تواند برای بازدیدکنندگان فعال شود.", "permission_protected": "مجوز {permission} محافظت می شود. شما نمی توانید گروه بازدیدکنندگان را از/به این مجوز اضافه یا حذف کنید.", "permission_updated": "مجوز '{permission}' به روز شد", diff --git a/locales/fr.json b/locales/fr.json index 53c486f26..bc69b573a 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -95,8 +95,8 @@ "pattern_port_or_range": "Doit être un numéro de port valide compris entre 0 et 65535, ou une gamme de ports (exemple : 100:200)", "pattern_positive_number": "Doit être un nombre positif", "pattern_username": "Doit être composé uniquement de caractères alphanumériques minuscules et de tirets bas (aussi appelé tiret du 8 ou underscore)", - "port_already_closed": "Le port {port:d} est déjà fermé pour les connexions {ip_version}", - "port_already_opened": "Le port {port:d} est déjà ouvert pour les connexions {ip_version}", + "port_already_closed": "Le port {port} est déjà fermé pour les connexions {ip_version}", + "port_already_opened": "Le port {port} est déjà ouvert pour les connexions {ip_version}", "restore_already_installed_app": "Une application est déjà installée avec l'identifiant '{app}'", "app_restore_failed": "Impossible de restaurer {app} : {error}", "restore_cleaning_failed": "Impossible de nettoyer le dossier temporaire de restauration", @@ -211,8 +211,8 @@ "global_settings_cant_serialize_settings": "Échec de la sérialisation des données de paramétrage car : {reason}", "restore_removing_tmp_dir_failed": "Impossible de sauvegarder un ancien dossier temporaire", "restore_extracting": "Extraction des fichiers nécessaires depuis l'archive...", - "restore_may_be_not_enough_disk_space": "Votre système ne semble pas avoir suffisamment d'espace (libre : {free_space:d} B, espace nécessaire : {needed_space:d} B, marge de sécurité : {margin:d} B)", - "restore_not_enough_disk_space": "Espace disponible insuffisant (L'espace libre est de {free_space:d} octets. Le besoin d'espace nécessaire est de {needed_space:d} octets. En appliquant une marge de sécurité, la quantité d'espace nécessaire est de {margin:d} octets)", + "restore_may_be_not_enough_disk_space": "Votre système ne semble pas avoir suffisamment d'espace (libre : {free_space} B, espace nécessaire : {needed_space} B, marge de sécurité : {margin} B)", + "restore_not_enough_disk_space": "Espace disponible insuffisant (L'espace libre est de {free_space} octets. Le besoin d'espace nécessaire est de {needed_space} octets. En appliquant une marge de sécurité, la quantité d'espace nécessaire est de {margin} octets)", "restore_system_part_failed": "Impossible de restaurer la partie '{part}' du système", "backup_couldnt_bind": "Impossible de lier {src} avec {dest}.", "domain_dns_conf_is_just_a_recommendation": "Cette commande vous montre la configuration *recommandée*. Elle ne configure pas le DNS pour vous. Il est de votre ressort de configurer votre zone DNS chez votre registrar/fournisseur conformément à cette recommandation.", diff --git a/locales/gl.json b/locales/gl.json index 56204a9ea..3dd2603b2 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -512,8 +512,8 @@ "regenconf_file_copy_failed": "Non se puido copiar o novo ficheiro de configuración '{new}' a '{conf}'", "regenconf_file_backed_up": "Ficheiro de configuración '{conf}' copiado a '{backup}'", "postinstall_low_rootfsspace": "O sistema de ficheiros raiz ten un espazo total menor de 10GB, que é pouco! Probablemente vas quedar sen espazo moi pronto! É recomendable ter polo menos 16GB para o sistema raíz. Se queres instalar YunoHost obviando este aviso, volve a executar a postinstalación con --force-diskspace", - "port_already_opened": "O porto {port:d} xa está aberto para conexións {ip_version}", - "port_already_closed": "O porto {port:d} xa está pechado para conexións {ip_version}", + "port_already_opened": "O porto {port} xa está aberto para conexións {ip_version}", + "port_already_closed": "O porto {port} xa está pechado para conexións {ip_version}", "permission_require_account": "O permiso {permission} só ten sentido para usuarias cunha conta, e por tanto non pode concederse a visitantes.", "permission_protected": "O permiso {permission} está protexido. Non podes engadir ou eliminar o grupo visitantes a/de este permiso.", "permission_updated": "Permiso '{permission}' actualizado", @@ -578,8 +578,8 @@ "restore_running_app_script": "Restablecendo a app '{app}'…", "restore_removing_tmp_dir_failed": "Non se puido eliminar o directorio temporal antigo", "restore_nothings_done": "Nada foi restablecido", - "restore_not_enough_disk_space": "Non hai espazo abondo (espazo: {free_space.d} B, espazo necesario: {needed_space} B, marxe de seguridade: {margin:d} B)", - "restore_may_be_not_enough_disk_space": "O teu sistema semella que non ten espazo abondo (libre: {free_space:d} B, espazo necesario: {needed_space:d} B, marxe de seguridade {margin:d} B)", + "restore_not_enough_disk_space": "Non hai espazo abondo (espazo: {free_space.d} B, espazo necesario: {needed_space} B, marxe de seguridade: {margin} B)", + "restore_may_be_not_enough_disk_space": "O teu sistema semella que non ten espazo abondo (libre: {free_space} B, espazo necesario: {needed_space} B, marxe de seguridade {margin} B)", "restore_hook_unavailable": "O script de restablecemento para '{part}' non está dispoñible no teu sistema nin no arquivo", "invalid_password": "Contrasinal non válido", "ldap_server_is_down_restart_it": "O servidor LDAP está caído, intenta reinicialo...", diff --git a/locales/it.json b/locales/it.json index c38bae59d..dc998d8d4 100644 --- a/locales/it.json +++ b/locales/it.json @@ -11,7 +11,7 @@ "domain_exists": "Il dominio esiste già", "pattern_email": "L'indirizzo email deve essere valido, senza simboli '+' (es. tizio@dominio.com)", "pattern_mailbox_quota": "La dimensione deve avere un suffisso b/k/M/G/T o 0 per disattivare la quota", - "port_already_opened": "La porta {port:d} è già aperta per {ip_version} connessioni", + "port_already_opened": "La porta {port} è già aperta per {ip_version} connessioni", "service_add_failed": "Impossibile aggiungere il servizio '{service}'", "service_cmd_exec_failed": "Impossibile eseguire il comando '{command}'", "service_disabled": "Il servizio '{service}' non partirà più al boot di sistema.", @@ -106,7 +106,7 @@ "pattern_port_or_range": "Deve essere un numero di porta valido (es. 0-65535) o una fascia di porte valida (es. 100:200)", "pattern_positive_number": "Deve essere un numero positivo", "pattern_username": "Caratteri minuscoli alfanumerici o trattini bassi soli", - "port_already_closed": "La porta {port:d} è già chiusa per le connessioni {ip_version}", + "port_already_closed": "La porta {port} è già chiusa per le connessioni {ip_version}", "restore_already_installed_app": "Un'applicazione con l'ID '{app}' è già installata", "app_restore_failed": "Impossibile ripristinare l'applicazione '{app}': {error}", "restore_cleaning_failed": "Impossibile pulire la directory temporanea di ripristino", @@ -436,8 +436,8 @@ "root_password_desynchronized": "La password d'amministratore è stata cambiata, ma YunoHost non ha potuto propagarla alla password di root!", "restore_system_part_failed": "Impossibile ripristinare la sezione di sistema '{part}'", "restore_removing_tmp_dir_failed": "Impossibile rimuovere una vecchia directory temporanea", - "restore_not_enough_disk_space": "Spazio libero insufficiente (spazio: {free_space:d}B, necessario: {needed_space:d}B, margine di sicurezza: {margin:d}B)", - "restore_may_be_not_enough_disk_space": "Il tuo sistema non sembra avere abbastanza spazio (libero: {free_space:d}B, necessario: {needed_space:d}B, margine di sicurezza: {margin:d}B)", + "restore_not_enough_disk_space": "Spazio libero insufficiente (spazio: {free_space}B, necessario: {needed_space}B, margine di sicurezza: {margin}B)", + "restore_may_be_not_enough_disk_space": "Il tuo sistema non sembra avere abbastanza spazio (libero: {free_space}B, necessario: {needed_space}B, margine di sicurezza: {margin}B)", "restore_extracting": "Sto estraendo i file necessari dall'archivio…", "restore_already_installed_apps": "Le seguenti app non possono essere ripristinate perché sono già installate: {apps}", "regex_with_only_domain": "Non puoi usare una regex per il dominio, solo per i percorsi", diff --git a/locales/nl.json b/locales/nl.json index e99a00575..1995cbf62 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -45,8 +45,8 @@ "pattern_email": "Moet een geldig emailadres bevatten (bv. abc@example.org)", "pattern_mailbox_quota": "Mailbox quota moet een waarde bevatten met b/k/M/G/T erachter of 0 om geen quota in te stellen", "pattern_password": "Wachtwoord moet tenminste 3 karakters lang zijn", - "port_already_closed": "Poort {port:d} is al gesloten voor {ip_version} verbindingen", - "port_already_opened": "Poort {port:d} is al open voor {ip_version} verbindingen", + "port_already_closed": "Poort {port} is al gesloten voor {ip_version} verbindingen", + "port_already_opened": "Poort {port} is al open voor {ip_version} verbindingen", "app_restore_failed": "De app '{app}' kon niet worden terug gezet: {error}", "restore_hook_unavailable": "De herstel-hook '{part}' is niet beschikbaar op dit systeem", "service_add_failed": "Kan service '{service}' niet toevoegen", diff --git a/locales/oc.json b/locales/oc.json index 906f67106..995c61b16 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -142,8 +142,8 @@ "pattern_password": "Deu conténer almens 3 caractèrs", "pattern_port_or_range": "Deu èsser un numèro de pòrt valid (ex : 0-65535) o un interval de pòrt (ex : 100:200)", "pattern_positive_number": "Deu èsser un nombre positiu", - "port_already_closed": "Lo pòrt {port:d} es ja tampat per las connexions {ip_version}", - "port_already_opened": "Lo pòrt {port:d} es ja dubèrt per las connexions {ip_version}", + "port_already_closed": "Lo pòrt {port} es ja tampat per las connexions {ip_version}", + "port_already_opened": "Lo pòrt {port} es ja dubèrt per las connexions {ip_version}", "restore_already_installed_app": "Una aplicacion es ja installada amb l’id « {app} »", "app_restore_failed": "Impossible de restaurar l’aplicacion « {app} »: {error}", "backup_ask_for_copying_if_needed": "Volètz far una salvagarda en utilizant {size} Mo temporàriament ? (Aqueste biais de far es emplegat perque unes fichièrs an pas pogut èsser preparats amb un metòde mai eficaç.)", @@ -206,8 +206,8 @@ "restore_extracting": "Extraccions dels fichièrs necessaris dins de l’archiu…", "restore_failed": "Impossible de restaurar lo sistèma", "restore_hook_unavailable": "Lo script de restauracion « {part} » es pas disponible sus vòstre sistèma e es pas tanpauc dins l’archiu", - "restore_may_be_not_enough_disk_space": "Lo sistèma sembla d’aver pas pro d’espaci disponible (liure : {free_space:d} octets, necessari : {needed_space:d} octets, marge de seguretat : {margin:d} octets)", - "restore_not_enough_disk_space": "Espaci disponible insufisent (liure : {free_space:d} octets, necessari : {needed_space:d} octets, marge de seguretat : {margin:d} octets)", + "restore_may_be_not_enough_disk_space": "Lo sistèma sembla d’aver pas pro d’espaci disponible (liure : {free_space} octets, necessari : {needed_space} octets, marge de seguretat : {margin} octets)", + "restore_not_enough_disk_space": "Espaci disponible insufisent (liure : {free_space} octets, necessari : {needed_space} octets, marge de seguretat : {margin} octets)", "restore_nothings_done": "Res es pas estat restaurat", "restore_removing_tmp_dir_failed": "Impossible de levar u ancian repertòri temporari", "restore_running_app_script": "Lançament del script de restauracion per l’aplicacion « {app} »…", diff --git a/locales/zh_Hans.json b/locales/zh_Hans.json index 5f076fa2e..560ee0db0 100644 --- a/locales/zh_Hans.json +++ b/locales/zh_Hans.json @@ -161,8 +161,8 @@ "app_action_cannot_be_ran_because_required_services_down": "这些必需的服务应该正在运行以执行以下操作:{services},尝试重新启动它们以继续操作(考虑调查为什么它们出现故障)。", "already_up_to_date": "无事可做。一切都已经是最新的了。", "postinstall_low_rootfsspace": "根文件系统的总空间小于10 GB,这非常令人担忧!您可能很快就会用完磁盘空间!建议根文件系统至少有16GB, 如果尽管出现此警告仍要安装YunoHost,请使用--force-diskspace重新运行postinstall", - "port_already_opened": "{ip_version}个连接的端口 {port:d} 已打开", - "port_already_closed": "{ip_version}个连接的端口 {port:d} 已关闭", + "port_already_opened": "{ip_version}个连接的端口 {port} 已打开", + "port_already_closed": "{ip_version}个连接的端口 {port} 已关闭", "permission_require_account": "权限{permission}只对有账户的用户有意义,因此不能对访客启用。", "permission_protected": "权限{permission}是受保护的。你不能向/从这个权限添加或删除访问者组。", "permission_updated": "权限 '{permission}' 已更新", @@ -185,7 +185,7 @@ "regenconf_file_manually_modified": "配置文件'{conf}' 已被手动修改,不会被更新", "regenconf_need_to_explicitly_specify_ssh": "ssh配置已被手动修改,但是您需要使用--force明确指定类别“ ssh”才能实际应用更改。", "restore_nothings_done": "什么都没有恢复", - "restore_may_be_not_enough_disk_space": "您的系统似乎没有足够的空间(可用空间: {free_space:d} B,所需空间: {needed_space:d} B,安全系数: {margin:d} B)", + "restore_may_be_not_enough_disk_space": "您的系统似乎没有足够的空间(可用空间: {free_space} B,所需空间: {needed_space} B,安全系数: {margin} B)", "restore_hook_unavailable": "'{part}'的恢复脚本在您的系统上和归档文件中均不可用", "restore_failed": "无法还原系统", "restore_extracting": "正在从存档中提取所需文件…", @@ -467,7 +467,7 @@ "diagnosis_package_installed_from_sury_details": "一些软件包被无意中从一个名为Sury的第三方仓库安装。YunoHost团队改进了处理这些软件包的策略,但预计一些安装了PHP7.3应用程序的设置在仍然使用Stretch的情况下还有一些不一致的地方。为了解决这种情况,你应该尝试运行以下命令:{cmd_to_fix}", "app_not_installed": "在已安装的应用列表中找不到 {app}:{all_apps}", "app_already_installed_cant_change_url": "这个应用程序已经被安装。URL不能仅仅通过这个函数来改变。在`app changeurl`中检查是否可用。", - "restore_not_enough_disk_space": "没有足够的空间(空间: {free_space:d} B,需要的空间: {needed_space:d} B,安全系数: {margin:d} B)", + "restore_not_enough_disk_space": "没有足够的空间(空间: {free_space} B,需要的空间: {needed_space} B,安全系数: {margin} B)", "regenconf_pending_applying": "正在为类别'{category}'应用挂起的配置..", "regenconf_up_to_date": "类别'{category}'的配置已经是最新的", "regenconf_file_kept_back": "配置文件'{conf}'预计将被regen-conf(类别{category})删除,但被保留了下来。", From 130c7293964c8f32f5cce63a4fedd8d5668fefe1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 2 Sep 2021 04:27:56 +0200 Subject: [PATCH 2871/3170] Misc wording --- locales/en.json | 2 +- locales/fr.json | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/locales/en.json b/locales/en.json index 65d98d9d1..f267e2f5e 100644 --- a/locales/en.json +++ b/locales/en.json @@ -268,7 +268,7 @@ "diagnosis_http_hairpinning_issue_details": "This is probably because of your ISP box / router. As a result, people from outside your local network will be able to access your server as expected, but not people from inside the local network (like you, probably?) when using the domain name or global IP. You may be able to improve the situation by having a look at https://yunohost.org/dns_local_network", "diagnosis_http_could_not_diagnose": "Could not diagnose if domains are reachable from outside in IPv{ipversion}.", "diagnosis_http_could_not_diagnose_details": "Error: {error}", - "diagnosis_http_localdomain": "Domain {domain}, with a .local TLD, is not expected to be reached from outside the local network.", + "diagnosis_http_localdomain": "Domain {domain}, with a .local TLD, is not expected to be exposed outside the local network.", "diagnosis_http_ok": "Domain {domain} is reachable through HTTP from outside the local network.", "diagnosis_http_timeout": "Timed-out while trying to contact your server from outside. It appears to be unreachable.
1. The most common cause for this issue is that port 80 (and 443) are not correctly forwarded to your server.
2. You should also make sure that the service nginx is running
3. On more complex setups: make sure that no firewall or reverse-proxy is interfering.", "diagnosis_http_connection_error": "Connection error: could not connect to the requested domain, it's very likely unreachable.", diff --git a/locales/fr.json b/locales/fr.json index bc69b573a..9df6b24be 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -170,7 +170,7 @@ "mailbox_used_space_dovecot_down": "Le service de email 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})", - "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_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`.", "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).", "app_already_installed_cant_change_url": "Cette application est déjà installée. L'URL ne peut pas être changé simplement par cette fonction. Vérifiez si cela est disponible avec `app changeurl`.", "app_change_url_failed_nginx_reload": "Le redémarrage de NGINX a échoué. Voici la sortie de 'nginx -t' :\n{nginx_errors}", @@ -232,7 +232,7 @@ "app_upgrade_app_name": "Mise à jour de {app}...", "backup_output_symlink_dir_broken": "Votre répertoire d'archivage '{path}' est un lien symbolique brisé. Peut-être avez-vous oublié de re/monter ou de brancher le support de stockage sur lequel il pointe.", "migrations_list_conflict_pending_done": "Vous ne pouvez pas utiliser --previous et --done simultanément.", - "migrations_to_be_ran_manually": "La migration {id} doit être lancée manuellement. Veuillez aller dans Outils > Migrations dans l'interface admin, ou lancer `yunohost tools migrations run`.", + "migrations_to_be_ran_manually": "La migration {id} doit être lancée manuellement. Veuillez aller dans Outils > Migrations dans la webadmin, ou lancer `yunohost tools migrations run`.", "migrations_need_to_accept_disclaimer": "Pour lancer la migration {id}, vous devez accepter cet avertissement :\n---\n{disclaimer}\n---\nSi vous acceptez de lancer la migration, veuillez relancer la commande avec l'option --accept-disclaimer.", "service_description_yunomdns": "Vous permet d'atteindre votre serveur en utilisant 'yunohost.local' sur votre réseau local", "service_description_dnsmasq": "Gère la résolution des noms de domaine (DNS)", @@ -420,7 +420,7 @@ "diagnosis_ip_not_connected_at_all": "Le serveur ne semble pas du tout connecté à Internet !?", "diagnosis_ip_weird_resolvconf": "La résolution DNS semble fonctionner, mais il semble que vous utilisez un /etc/resolv.conf personnalisé.", "diagnosis_ip_weird_resolvconf_details": "Le fichier /etc/resolv.conf doit être un lien symbolique vers /etc/resolvconf/run/resolv.conf lui-même pointant vers 127.0.0.1 (dnsmasq). Si vous souhaitez configurer manuellement les résolveurs DNS, veuillez modifier /etc/resolv.dnsmasq.conf.", - "diagnosis_dns_missing_record": "Selon la configuration DNS recommandée, vous devez ajouter un enregistrement DNS
Type : {type}
Nom : {name}
Valeur: {value}", + "diagnosis_dns_missing_record": "Selon la configuration DNS recommandée, vous devez ajouter un enregistrement DNS
Type : {type}
Nom : {name}
Valeur : {value}", "diagnosis_diskusage_ok": "L'espace de stockage {mountpoint} (sur le périphérique {device}) a encore {free} ({free_percent}%) espace restant (sur {total}) !", "diagnosis_ram_ok": "Le système dispose encore de {available} ({available_percent}%) de RAM sur {total}.", "diagnosis_regenconf_allgood": "Tous les fichiers de configuration sont conformes à la configuration recommandée !", @@ -431,7 +431,7 @@ "diagnosis_basesystem_ynh_main_version": "Le serveur utilise YunoHost {main_version} ({repo})", "diagnosis_basesystem_ynh_inconsistent_versions": "Vous exécutez des versions incohérentes des packages YunoHost ... très probablement en raison d'une mise à niveau échouée ou partielle.", "diagnosis_failed_for_category": "Échec du diagnostic pour la catégorie '{category}': {error}", - "diagnosis_cache_still_valid": "(Le cache est encore valide pour le diagnostic {category}. Il ne sera pas re-diagnostiqué pour le moment!)", + "diagnosis_cache_still_valid": "(Le cache est encore valide pour le diagnostic {category}. Il ne sera pas re-diagnostiqué pour le moment !)", "diagnosis_ignored_issues": "(+ {nb_ignored} problème(s) ignoré(s))", "diagnosis_found_warnings": "Trouvé {warnings} objet(s) pouvant être amélioré(s) pour {category}.", "diagnosis_everything_ok": "Tout semble bien pour {category} !", @@ -496,7 +496,7 @@ "log_app_action_run": "Lancer l'action de l'application '{}'", "log_app_config_show_panel": "Montrer le panneau de configuration de l'application '{}'", "log_app_config_apply": "Appliquer la configuration à l'application '{}'", - "diagnosis_never_ran_yet": "Il apparaît que le serveur a été installé récemment et qu'il n'y a pas encore eu de diagnostic. Vous devriez en lancer un depuis la web-admin ou en utilisant 'yunohost diagnosis run' depuis la ligne de commande.", + "diagnosis_never_ran_yet": "Il apparaît que le serveur a été installé récemment et qu'il n'y a pas encore eu de diagnostic. Vous devriez en lancer un depuis la webadmin ou en utilisant 'yunohost diagnosis run' depuis la ligne de commande.", "diagnosis_description_web": "Web", "diagnosis_basesystem_hardware": "L'architecture du serveur est {virt} {arch}", "group_already_exist_on_system_but_removing_it": "Le groupe {group} est déjà présent dans les groupes du système, mais YunoHost va le supprimer...", @@ -542,9 +542,9 @@ "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 tools 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 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_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_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 vont pas expirer prochainement.", "diagnosis_domain_expiration_warning": "Certains domaines vont expirer prochainement !", @@ -634,16 +634,16 @@ "diagnosis_sshd_config_inconsistent": "Il semble que le port SSH a été modifié manuellement dans /etc/ssh/sshd_config. Depuis YunoHost 4.2, un nouveau paramètre global 'security.ssh.port' est disponible pour éviter de modifier manuellement la configuration.", "diagnosis_sshd_config_insecure": "La configuration SSH semble avoir été modifiée manuellement et n'est pas sécurisée car elle ne contient aucune directive 'AllowGroups' ou 'AllowUsers' pour limiter l'accès aux utilisateurs autorisés.", "backup_create_size_estimation": "L'archive contiendra environ {size} de données.", - "global_settings_setting_security_webadmin_allowlist": "Adresses IP autorisées à accéder à la page web du portail d'administration (webadmin). Elles doivent être séparées par une virgule.", - "global_settings_setting_security_webadmin_allowlist_enabled": "Autorisez seulement certaines IP à accéder à la page web du portail d'administration (webadmin).", - "diagnosis_http_localdomain": "Le domaine {domain}, avec un TLD .local, ne devrait pas être atteint depuis l'extérieur du réseau local.", + "global_settings_setting_security_webadmin_allowlist": "Adresses IP autorisées à accéder à la webadmin. Elles doivent être séparées par une virgule.", + "global_settings_setting_security_webadmin_allowlist_enabled": "Autoriser seulement certaines IP à accéder à la webadmin.", + "diagnosis_http_localdomain": "Le domaine {domain}, avec un TLD .local, ne devrait pas être exposé en dehors du réseau local.", "diagnosis_dns_specialusedomain": "Le domaine {domain} est basé sur un domaine de premier niveau (TLD) à usage spécial et ne devrait donc pas avoir d'enregistrements DNS réels.", "invalid_password": "Mot de passe incorrect", "ldap_server_is_down_restart_it": "Le service LDAP est en panne, essayez de le redémarrer...", "ldap_server_down": "Impossible d'atteindre le serveur LDAP", "global_settings_setting_security_experimental_enabled": "Activer les fonctionnalités de sécurité expérimentales (ne l'activez pas si vous ne savez pas ce que vous faites !)", "diagnosis_apps_deprecated_practices": "La version installée de cette application utilise toujours certaines pratiques de packaging obsolètes. Vous devriez vraiment envisager de mettre l'application à jour.", - "diagnosis_apps_outdated_ynh_requirement": "La version installée de cette application nécessite uniquement YunoHost> = 2.x, cela indique que l'application n'est pas à jour avec les bonnes pratiques de packaging et les helpers recommandées. Vous devriez vraiment envisager de mettre l'application à jour.", + "diagnosis_apps_outdated_ynh_requirement": "La version installée de cette application nécessite uniquement YunoHost >= 2.x, cela indique que l'application n'est pas à jour avec les bonnes pratiques de packaging et les helpers recommandées. Vous devriez vraiment envisager de mettre l'application à jour.", "diagnosis_apps_bad_quality": "Cette application est actuellement signalée comme cassée dans le catalogue d'applications de YunoHost. Cela peut être un problème temporaire. En attendant que les mainteneurs tentent de résoudre le problème, la mise à jour de cette application est désactivée.", "diagnosis_apps_broken": "Cette application est actuellement signalée comme cassée dans le catalogue d'applications de YunoHost. Cela peut être un problème temporaire. En attendant que les mainteneurs tentent de résoudre le problème, la mise à jour de cette application est désactivée.", "diagnosis_apps_not_in_app_catalog": "Cette application est absente ou ne figure plus dans le catalogue d'applications de YunoHost. Vous devriez envisager de la désinstaller car elle ne recevra pas de mise à jour et pourrait compromettre l'intégrité et la sécurité de votre système.", From 59564f910662c0d4467245fcf804fae3c008a6e5 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 2 Sep 2021 04:44:37 +0200 Subject: [PATCH 2872/3170] Wording --- locales/fr.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 9df6b24be..2772d839e 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -78,7 +78,7 @@ "installation_complete": "Installation terminée", "ip6tables_unavailable": "Vous ne pouvez pas jouer avec ip6tables ici. Vous êtes soit dans un conteneur, soit votre noyau ne le prend pas en charge", "iptables_unavailable": "Vous ne pouvez pas jouer avec iptables ici. Vous êtes soit dans un conteneur, soit votre noyau ne le prend pas en charge", - "mail_alias_remove_failed": "Impossible de supprimer l'alias de email '{mail}'", + "mail_alias_remove_failed": "Impossible de supprimer l'alias mail '{mail}'", "mail_domain_unknown": "Le domaine '{domain}' de cette adresse email n'est pas valide. Merci d'utiliser un domaine administré par ce serveur.", "mail_forward_remove_failed": "Impossible de supprimer l'email de transfert '{mail}'", "main_domain_change_failed": "Impossible de modifier le domaine principal", @@ -167,7 +167,7 @@ "domain_cannot_remove_main": "Vous ne pouvez pas supprimer '{domain}' car il s'agit du domaine principal. Vous devez d'abord définir un autre domaine comme domaine principal à l'aide de 'yunohost domain main-domain -n ', voici la liste des domaines candidats : {other_domains}", "certmanager_self_ca_conf_file_not_found": "Le fichier de configuration pour l'autorité du certificat auto-signé est introuvable (fichier : {file})", "certmanager_unable_to_parse_self_CA_name": "Impossible d'analyser le nom de l'autorité du certificat auto-signé (fichier : {file})", - "mailbox_used_space_dovecot_down": "Le service de email Dovecot doit être démarré si vous souhaitez voir l'espace disque occupé par la messagerie", + "mailbox_used_space_dovecot_down": "Le service 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})", "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`.", From 46fb84e071a65699d7685896d6acdbee27e6e585 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 2 Sep 2021 04:46:31 +0200 Subject: [PATCH 2873/3170] Expand the reformat locale script to also cover en.json ... --- .gitlab/ci/translation.gitlab-ci.yml | 2 +- locales/en.json | 26 +++++++++---------- ...fr_translations.py => reformat_locales.py} | 25 +++++++++++++----- 3 files changed, 32 insertions(+), 21 deletions(-) rename tests/{reformat_fr_translations.py => reformat_locales.py} (52%) diff --git a/.gitlab/ci/translation.gitlab-ci.yml b/.gitlab/ci/translation.gitlab-ci.yml index c8fbb9147..b81d4a2be 100644 --- a/.gitlab/ci/translation.gitlab-ci.yml +++ b/.gitlab/ci/translation.gitlab-ci.yml @@ -16,7 +16,7 @@ remove-stale-translated-strings: # create a local branch that will overwrite distant one - git checkout -b "ci-remove-stale-translated-strings-${CI_COMMIT_REF_NAME}" --no-track - python3 remove_stale_translated_strings.py - - python3 reformat_fr_translations.py + - python3 reformat_locales.py - '[ $(git diff -w | wc -l) != 0 ] || exit 0' # stop if there is nothing to commit - git commit -am "[CI] Reformat / remove stale translated strings" || true - git push -f origin "ci-remove-stale-translated-strings-${CI_COMMIT_REF_NAME}":"ci-remove-stale-translated-strings-${CI_COMMIT_REF_NAME}" diff --git a/locales/en.json b/locales/en.json index f267e2f5e..e0f6d4ac9 100644 --- a/locales/en.json +++ b/locales/en.json @@ -62,7 +62,7 @@ "app_packaging_format_not_supported": "This app cannot be installed because its packaging format is not supported by your YunoHost version. You should probably consider upgrading your system.", "apps_already_up_to_date": "All apps are already up-to-date", "apps_catalog_init_success": "App catalog system initialized!", - "apps_catalog_updating": "Updating application catalog…", + "apps_catalog_updating": "Updating application catalog...", "apps_catalog_failed_to_download": "Unable to download the {apps_catalog} app catalog: {error}", "apps_catalog_obsolete_cache": "The app catalog cache is empty or obsolete.", "apps_catalog_update_success": "The application catalog has been updated!", @@ -140,8 +140,8 @@ "certmanager_self_ca_conf_file_not_found": "Could not find configuration file for self-signing authority (file: {file})", "certmanager_unable_to_parse_self_CA_name": "Could not parse name of self-signing authority (file: {file})", "confirm_app_install_warning": "Warning: This app may work, but is not well-integrated in YunoHost. Some features such as single sign-on and backup/restore might not be available. Install anyway? [{answers}] ", - "confirm_app_install_danger": "DANGER! This app is known to be still experimental (if not explicitly not working)! You should probably NOT install it unless you know what you are doing. NO SUPPORT will be provided if this app doesn't work or breaks your system… If you are willing to take that risk anyway, type '{answers}'", - "confirm_app_install_thirdparty": "DANGER! This app is not part of YunoHost's app catalog. Installing third-party apps may compromise the integrity and security of your system. You should probably NOT install it unless you know what you are doing. NO SUPPORT will be provided if this app doesn't work or breaks your system… If you are willing to take that risk anyway, type '{answers}'", + "confirm_app_install_danger": "DANGER! This app is known to be still experimental (if not explicitly not working)! You should probably NOT install it unless you know what you are doing. NO SUPPORT will be provided if this app doesn't work or breaks your system... If you are willing to take that risk anyway, type '{answers}'", + "confirm_app_install_thirdparty": "DANGER! This app is not part of YunoHost's app catalog. Installing third-party apps may compromise the integrity and security of your system. You should probably NOT install it unless you know what you are doing. NO SUPPORT will be provided if this app doesn't work or breaks your system... If you are willing to take that risk anyway, type '{answers}'", "custom_app_url_required": "You must provide a URL to upgrade your custom app {app}", "diagnosis_basesystem_hardware": "Server hardware architecture is {virt} {arch}", "diagnosis_basesystem_hardware_model": "Server model is {model}", @@ -304,8 +304,8 @@ "domain_unknown": "Unknown domain", "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 apt install --fix-broken` and/or `sudo dpkg --configure -a`.", + "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 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} can provide {domain}.", "dyndns_could_not_check_available": "Could not check if {domain} is available on {provider}.", @@ -540,7 +540,7 @@ "regenconf_up_to_date": "The configuration is already up-to-date for category '{category}'", "regenconf_updated": "Configuration updated for '{category}'", "regenconf_would_be_updated": "The configuration would have been updated for category '{category}'", - "regenconf_dry_pending_applying": "Checking pending configuration which would have been applied for category '{category}'…", + "regenconf_dry_pending_applying": "Checking pending configuration which would have been applied for category '{category}'...", "regenconf_failed": "Could not regenerate the configuration for category(s): {categories}", "regenconf_pending_applying": "Applying pending configuration for category '{category}'...", "regenconf_need_to_explicitly_specify_ssh": "The ssh configuration has been manually modified, but you need to explicitly specify category 'ssh' with --force to actually apply the changes.", @@ -552,15 +552,15 @@ "restore_cleaning_failed": "Could not clean up the temporary restoration directory", "restore_complete": "Restoration completed", "restore_confirm_yunohost_installed": "Do you really want to restore an already installed system? [{answers}]", - "restore_extracting": "Extracting needed files from the archive…", + "restore_extracting": "Extracting needed files from the archive...", "restore_failed": "Could not restore system", "restore_hook_unavailable": "Restoration script for '{part}' not available on your system and not in the archive either", "restore_may_be_not_enough_disk_space": "Your system does not seem to have enough space (free: {free_space} B, needed space: {needed_space} B, security margin: {margin} B)", "restore_not_enough_disk_space": "Not enough space (space: {free_space} B, needed space: {needed_space} B, security margin: {margin} B)", "restore_nothings_done": "Nothing was restored", "restore_removing_tmp_dir_failed": "Could not remove an old temporary directory", - "restore_running_app_script": "Restoring the app '{app}'…", - "restore_running_hooks": "Running restoration hooks…", + "restore_running_app_script": "Restoring the app '{app}'...", + "restore_running_hooks": "Running restoration hooks...", "restore_system_part_failed": "Could not restore the '{part}' system part", "root_password_desynchronized": "The admin password was changed, but YunoHost could not propagate this to the root password!", "root_password_replaced_by_admin_password": "Your root password have been replaced by your admin password.", @@ -615,11 +615,11 @@ "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…", - "tools_upgrade_cant_unhold_critical_packages": "Could not unhold critical packages…", - "tools_upgrade_regular_packages": "Now upgrading 'regular' (non-yunohost-related) packages…", + "tools_upgrade_cant_hold_critical_packages": "Could not hold critical packages...", + "tools_upgrade_cant_unhold_critical_packages": "Could not unhold critical packages...", + "tools_upgrade_regular_packages": "Now upgrading 'regular' (non-yunohost-related) packages...", "tools_upgrade_regular_packages_failed": "Could not upgrade packages: {packages_list}", - "tools_upgrade_special_packages": "Now upgrading 'special' (yunohost-related) packages…", + "tools_upgrade_special_packages": "Now upgrading 'special' (yunohost-related) packages...", "tools_upgrade_special_packages_explanation": "The special upgrade will continue in the background. Please don't start any other actions on your server for the next ~10 minutes (depending on hardware speed). After this, you may have to re-log in to the webadmin. The upgrade log will be available in Tools → Log (in the webadmin) or using 'yunohost log list' (from the command-line).", "tools_upgrade_special_packages_completed": "YunoHost package upgrade completed.\nPress [Enter] to get the command line back", "unbackup_app": "{app} will not be saved", diff --git a/tests/reformat_fr_translations.py b/tests/reformat_locales.py similarity index 52% rename from tests/reformat_fr_translations.py rename to tests/reformat_locales.py index 473ed99d4..90251d040 100644 --- a/tests/reformat_fr_translations.py +++ b/tests/reformat_locales.py @@ -1,11 +1,26 @@ import re -locale_folder = "locales/" -locale = open(locale_folder + "fr.json").read() +def reformat(lang, transformations): + + locale = open(f"locales/{lang}.json").read() + for pattern, replace in transformations.items(): + locale = re.compile(pattern).sub(replace, locale) + + open(f"locales/{lang}.json", "w").write(locale) + +###################################################### godamn_spaces_of_hell = ["\u00a0", "\u2000", "\u2001", "\u2002", "\u2003", "\u2004", "\u2005", "\u2006", "\u2007", "\u2008", "\u2009", "\u200A", "\u202f", "\u202F", "\u3000"] transformations = {s: " " for s in godamn_spaces_of_hell} +transformations.update({ + "…": "...", +}) + + +reformat("en", transformations) + +###################################################### transformations.update({ "courriel": "email", @@ -16,12 +31,8 @@ transformations.update({ "«": "'", " »": "'", "»": "'", - "…": "...", "’": "'", #r"$(\w{1,2})'|( \w{1,2})'": r"\1\2’", }) -for pattern, replace in transformations.items(): - locale = re.compile(pattern).sub(replace, locale) - -open(locale_folder + "fr.json", "w").write(locale) +reformat("fr", transformations) From 5ec397d3545c0b9268fbfd98fcfbed86091aefcd Mon Sep 17 00:00:00 2001 From: Kay0u Date: Thu, 2 Sep 2021 12:30:49 +0200 Subject: [PATCH 2874/3170] Fix locales --- locales/fa.json | 2 +- locales/gl.json | 4 +- locales/uk.json | 106 ++++++++++++++++++++++++------------------------ 3 files changed, 56 insertions(+), 56 deletions(-) diff --git a/locales/fa.json b/locales/fa.json index a644716cf..5d9772459 100644 --- a/locales/fa.json +++ b/locales/fa.json @@ -602,7 +602,7 @@ "restore_removing_tmp_dir_failed": "پوشه موقت قدیمی حذف نشد", "restore_nothings_done": "هیچ چیز ترمیم و بازسازی نشد", "restore_not_enough_disk_space": "فضای کافی موجود نیست (فضا: {free_space:d} B ، فضای مورد نیاز: {needed_space:d} B ، حاشیه امنیتی: {margin:d} B)", - "restore_may_be_not_enough_disk_space": "به نظر می رسد سیستم شما فضای کافی ندارد (فضای آزاد: {free_space:d} B ، فضای مورد نیاز: {space_space:d} B ، حاشیه امنیتی: {margin:d} B)", + "restore_may_be_not_enough_disk_space": "به نظر می رسد سیستم شما فضای کافی ندارد (فضای آزاد: {free_space:d} B ، فضای مورد نیاز: {needed_space:d} B ، حاشیه امنیتی: {margin:d} B)", "restore_hook_unavailable": "اسکریپت ترمیم و بازسازی برای '{part}' در سیستم شما در دسترس نیست و همچنین در بایگانی نیز وجود ندارد", "restore_failed": "سیستم بازیابی نشد", "restore_extracting": "استخراج فایل های مورد نیاز از بایگانی…", diff --git a/locales/gl.json b/locales/gl.json index 56204a9ea..ca25fc303 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -546,7 +546,7 @@ "regenconf_file_remove_failed": "Non se puido eliminar o ficheiro de configuración '{conf}'", "service_enable_failed": "Non se puido facer que o servizo '{service}' se inicie automáticamente no inicio.\n\nRexistros recentes do servizo: {logs}", "service_disabled": "O servizo '{service}' xa non vai volver a ser iniciado ao inicio do sistema.", - "service_disable_failed": "Non se puido iniciar o servizo '{servizo}' ao inicio.\n\nRexistro recente do servizo: {logs}", + "service_disable_failed": "Non se puido iniciar o servizo '{service}' ao inicio.\n\nRexistro recente do servizo: {logs}", "service_description_yunohost-firewall": "Xestiona, abre e pecha a conexións dos portos aos servizos", "service_description_yunohost-api": "Xestiona as interaccións entre a interface web de YunoHost e o sistema", "service_description_ssh": "Permíteche conectar de xeito remoto co teu servidor a través dun terminal (protocolo SSH)", @@ -563,7 +563,7 @@ "service_description_dnsmasq": "Xestiona a resolución de nomes de dominio (DNS)", "service_description_yunomdns": "Permíteche chegar ao teu servidor utilizando 'yunohost.local' na túa rede local", "service_cmd_exec_failed": "Non se puido executar o comando '{command}'", - "service_already_stopped": "O servizo '{sevice}' xa está detido", + "service_already_stopped": "O servizo '{service}' xa está detido", "service_already_started": "O servizo '{service}' xa se está a executar", "service_added": "Foi engadido o servizo '{service}'", "service_add_failed": "Non se puido engadir o servizo '{service}'", diff --git a/locales/uk.json b/locales/uk.json index 2e466685b..a9b807981 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -61,14 +61,14 @@ "service_description_dovecot": "Дозволяє поштовим клієнтам отримувати доступ до електронної пошти (через IMAP і POP3)", "service_description_dnsmasq": "Обробляє дозвіл доменних імен (DNS)", "service_description_yunomdns": "Дозволяє вам отримати доступ до вашого сервера, використовуючи 'yunohost.local' у вашій локальній мережі", - "service_cmd_exec_failed": "Не вдалося виконати команду '{команда}'", + "service_cmd_exec_failed": "Не вдалося виконати команду '{command}'", "service_already_stopped": "Служба '{service}' вже зупинена", "service_already_started": "Служба '{service}' вже запущена", "service_added": "Служба '{service}' була додана", "service_add_failed": "Не вдалося додати службу '{service}'", - "server_reboot_confirm": "Сервер негайно перезавантажиться, ви впевнені? [{Відповіді}]", + "server_reboot_confirm": "Сервер негайно перезавантажиться, ви впевнені? [{answers}]", "server_reboot": "сервер перезавантажиться", - "server_shutdown_confirm": "Сервер буде негайно виключений, ви впевнені? [{Відповіді}].", + "server_shutdown_confirm": "Сервер буде негайно виключений, ви впевнені? [{answers}].", "server_shutdown": "сервер вимкнеться", "root_password_replaced_by_admin_password": "Ваш кореневої пароль був замінений на пароль адміністратора.", "root_password_desynchronized": "Пароль адміністратора був змінений, але YunoHost не зміг поширити це на пароль root!", @@ -82,7 +82,7 @@ "restore_hook_unavailable": "Сценарій відновлення для '{part}' недоступним у вашій системі і в архіві його теж немає", "restore_failed": "Не вдалося відновити систему", "restore_extracting": "Витяг необхідних файлів з архіву…", - "restore_confirm_yunohost_installed": "Ви дійсно хочете відновити вже встановлену систему? [{Відповіді}].", + "restore_confirm_yunohost_installed": "Ви дійсно хочете відновити вже встановлену систему? [{answers}].", "restore_complete": "відновлення завершено", "restore_cleaning_failed": "Не вдалося очистити тимчасовий каталог відновлення", "restore_backup_too_old": "Цей архів резервних копій не може бути відновлений, бо він отриманий з дуже старою версією YunoHost.", @@ -91,12 +91,12 @@ "regex_with_only_domain": "Ви не можете використовувати regex для домену, тільки для шляху.", "regex_incompatible_with_tile": "/! \\ Packagers! Дозвіл '{permission}' має значення show_tile 'true', тому ви не можете визначити regex URL в якості основного URL.", "regenconf_need_to_explicitly_specify_ssh": "Конфігурація ssh була змінена вручну, але вам потрібно явно вказати категорію 'ssh' з --force, щоб застосувати зміни.", - "regenconf_pending_applying": "Застосування очікує конфігурації для категорії '{категорія}'...", + "regenconf_pending_applying": "Застосування очікує конфігурації для категорії '{category}'...", "regenconf_failed": "Не вдалося відновити конфігурацію для категорії (категорій): {categories}", - "regenconf_dry_pending_applying": "Перевірка очікує конфігурації, яка була б застосована для категорії '{категорія}'…", - "regenconf_would_be_updated": "Конфігурація була б оновлена для категорії '{категорія}'", + "regenconf_dry_pending_applying": "Перевірка очікує конфігурації, яка була б застосована для категорії '{category}'…", + "regenconf_would_be_updated": "Конфігурація була б оновлена для категорії '{category}'", "regenconf_updated": "Конфігурація оновлена для категорії '{category}'", - "regenconf_up_to_date": "Конфігурація вже оновлена для категорії '{категорія}'", + "regenconf_up_to_date": "Конфігурація вже оновлена для категорії '{category}'", "regenconf_now_managed_by_yunohost": "Конфігураційний файл '{conf}' тепер управляється YunoHost (категорія {category}).", "regenconf_file_updated": "Конфігураційний файл '{conf}' оновлений", "regenconf_file_removed": "Конфігураційний файл '{conf}' видалений", @@ -145,7 +145,7 @@ "packages_upgrade_failed": "Не вдалося оновити всі пакети", "operation_interrupted": "Операція була перервана вручну?", "invalid_number": "Повинно бути число", - "not_enough_disk_space": "Недостатньо вільного місця на \"{шлях} '.", + "not_enough_disk_space": "Недостатньо вільного місця на \"{path} '.", "migrations_to_be_ran_manually": "Міграція {id} повинна бути запущена вручну. Будь ласка, перейдіть в розділ Інструменти → Міграції на сторінці веб-адміністратора або виконайте команду `yunohost tools migrations run`.", "migrations_success_forward": "Міграція {id} завершена", "migrations_skip_migration": "Пропуск міграції {id}...", @@ -258,7 +258,7 @@ "hook_name_unknown": "Невідоме ім'я хука '{name}'", "hook_list_by_invalid": "Це властивість не може бути використано для перерахування хуков", "hook_json_return_error": "Не вдалося розпізнати повернення з хука {path}. Помилка: {msg}. Необроблений контент: {raw_content}", - "hook_exec_not_terminated": "Скрипт не завершився належним чином: {шлях}", + "hook_exec_not_terminated": "Скрипт не завершився належним чином: {path}", "hook_exec_failed": "Не вдалося запустити скрипт: {path}", "group_user_not_in_group": "Користувач {user} не входить в групу {group}", "group_user_already_in_group": "Користувач {user} вже в групі {group}", @@ -297,17 +297,17 @@ "global_settings_setting_security_password_admin_strength": "Надійність пароля адміністратора", "global_settings_setting_security_nginx_compatibility": "Компроміс між сумісністю і безпекою для веб-сервера NGINX. Впливає на шифри (і інші аспекти, пов'язані з безпекою)", "global_settings_setting_pop3_enabled": "Включити протокол POP3 для поштового сервера.", - "global_settings_reset_success": "Попередні настройки тепер збережені в {шлях}.", + "global_settings_reset_success": "Попередні настройки тепер збережені в {path}.", "global_settings_key_doesnt_exists": "Ключ '{settings_key}' не існує в глобальних налаштуваннях, ви можете побачити всі доступні ключі, виконавши команду 'yunohost settings list'.", - "global_settings_cant_write_settings": "Неможливо зберегти файл настройок, причина: {причина}", - "global_settings_cant_serialize_settings": "Не вдалося серіалізовать дані налаштувань, причина: {причина}", - "global_settings_cant_open_settings": "Не вдалося відкрити файл настройок, причина: {причина}", + "global_settings_cant_write_settings": "Неможливо зберегти файл настройок, причина: {reason}", + "global_settings_cant_serialize_settings": "Не вдалося серіалізовать дані налаштувань, причина: {reason}", + "global_settings_cant_open_settings": "Не вдалося відкрити файл настройок, причина: {reason}", "global_settings_bad_type_for_setting": "Поганий тип для настройки {setting}, отриманий {received_type}, очікується {expected_type}", "global_settings_bad_choice_for_enum": "Поганий вибір для настройки {setting}, отримано '{choice}', але доступні наступні варіанти: {available_choices}.", "firewall_rules_cmd_failed": "Деякі команди правил брандмауера не спрацювали. Більш детальна інформація в журналі.", "firewall_reloaded": "брандмауер перезавантажений", "firewall_reload_failed": "Не вдалося перезавантажити брандмауер", - "file_does_not_exist": "Файл {шлях} не існує.", + "file_does_not_exist": "Файл {path} не існує.", "field_invalid": "Неприпустиме поле '{}'", "experimental_feature": "Попередження: Ця функція є експериментальною і не вважається стабільною, ви не повинні використовувати її, якщо не знаєте, що робите.", "extracting": "Витяг...", @@ -321,8 +321,8 @@ "dyndns_key_generating": "Генерація DNS-ключа... Це може зайняти деякий час.", "dyndns_ip_updated": "Оновлення свій IP-адресу в DynDNS", "dyndns_ip_update_failed": "Не вдалося оновити IP-адреса в DynDNS", - "dyndns_could_not_check_available": "Не вдалося перевірити наявність певної {домен} на {провайдера}.", - "dyndns_could_not_check_provide": "Не вдалося перевірити, чи може {провайдер} надати {домен}.", + "dyndns_could_not_check_available": "Не вдалося перевірити наявність певної {domain} на {provider}.", + "dyndns_could_not_check_provide": "Не вдалося перевірити, чи може {provider} надати {domain}.", "dpkg_lock_not_available": "Ця команда не може бути виконана прямо зараз, тому що інша програма, схоже, використовує блокування dpkg (системного менеджера пакетів).", "dpkg_is_broken": "Ви не можете зробити це прямо зараз, тому що dpkg/APT (системні менеджери пакетів), схоже, знаходяться в зламаному стані... Ви можете спробувати вирішити цю проблему, підключившись через SSH і виконавши `sudo apt install --fix-broken` і/або `sudo dpkg --configure -a`.", "downloading": "Завантаження…", @@ -331,7 +331,7 @@ "domain_unknown": "невідомий домен", "domain_name_unknown": "Домен '{domain}' невідомий", "domain_uninstall_app_first": "Ці додатки все ще встановлені на вашому домені: {apps} ласка, видаліть їх за допомогою 'yunohost app remove the_app_id' або перемістити їх на інший домен за допомогою 'yunohost app change-url the_app_id', перш ніж приступити до видалення домену.", - "domain_remove_confirm_apps_removal": "Видалення цього домену призведе до видалення цих додатків: {apps} Ви впевнені, що хочете це зробити? [{Відповіді}].", + "domain_remove_confirm_apps_removal": "Видалення цього домену призведе до видалення цих додатків: {apps} Ви впевнені, що хочете це зробити? [{answers}].", "domain_hostname_failed": "Неможливо встановити нове ім'я хоста. Це може викликати проблеми в подальшому (можливо, все буде в порядку).", "domain_exists": "Домен вже існує", "domain_dyndns_root_unknown": "Невідомий кореневої домен DynDNS", @@ -367,7 +367,7 @@ "diagnosis_http_hairpinning_issue_details": "Можливо, це пов'язано з блоком/маршрутизатором вашого інтернет-провайдера. В результаті, люди ззовні вашої локальної мережі зможуть отримати доступ до вашого сервера, як і очікувалося, але не люди зсередини локальної мережі (як ви, ймовірно?) При використанні доменного імені або глобального IP. Можливо, ви зможете поліпшити ситуацію, глянувши на https://yunohost.org/dns_local_network .", "diagnosis_http_hairpinning_issue": "Схоже, що у вашій локальній мережі не включена проброска.", "diagnosis_ports_forwarding_tip": "Щоб вирішити цю проблему, вам, швидше за все, потрібно налаштувати кидок портів на вашому інтернет-маршрутизатор, як описано в https://yunohost.org/isp_box_config.", - "diagnosis_ports_needed_by": "Відкриття цього порту необхідно для функцій {категорії} (служба {сервіс}).", + "diagnosis_ports_needed_by": "Відкриття цього порту необхідно для функцій {category} (служба {service}).", "diagnosis_ports_ok": "Порт {port} доступний ззовні.", "diagnosis_ports_partially_unreachable": "Порт {port} не доступний ззовні в IPv {failed}.", "diagnosis_ports_unreachable": "Порт {port} недоступний ззовні.", @@ -383,8 +383,8 @@ "diagnosis_description_basesystem": "Базова система", "diagnosis_security_vulnerable_to_meltdown_details": "Щоб виправити це, вам слід оновити систему і перезавантажитися, щоб завантажити нове ядро linux (або звернутися до вашого серверного провайдеру, якщо це не спрацює). Додаткову інформацію див. На сайті https://meltdownattack.com/.", "diagnosis_security_vulnerable_to_meltdown": "Схоже, що ви уразливі до критичної уразливості безпеки Meltdown.", - "diagnosis_rootfstotalspace_critical": "Коренева файлова система має тільки {простір}, що вельми тривожно! Швидше за все, дисковий простір закінчиться дуже швидко! Рекомендується мати не менше 16 ГБ для кореневої файлової системи.", - "diagnosis_rootfstotalspace_warning": "Коренева файлова система має тільки {простір}. Це може бути нормально, але будьте обережні, тому що в кінцевому підсумку дисковий простір може швидко закінчитися... Рекомендується мати не менше 16 ГБ для кореневої файлової системи.", + "diagnosis_rootfstotalspace_critical": "Коренева файлова система має тільки {space}, що вельми тривожно! Швидше за все, дисковий простір закінчиться дуже швидко! Рекомендується мати не менше 16 ГБ для кореневої файлової системи.", + "diagnosis_rootfstotalspace_warning": "Коренева файлова система має тільки {space}. Це може бути нормально, але будьте обережні, тому що в кінцевому підсумку дисковий простір може швидко закінчитися... Рекомендується мати не менше 16 ГБ для кореневої файлової системи.", "diagnosis_regenconf_manually_modified_details": "Це можливо нормально, якщо ви знаєте, що робите! YunoHost перестане оновлювати цей файл автоматично... Але врахуйте, що поновлення YunoHost можуть містити важливі рекомендовані зміни. Якщо ви хочете, ви можете перевірити відмінності за допомогою команди yunohost tools regen-conf {category} --dry-run --with-diff і примусово повернути рекомендовану конфігурацію за допомогою yunohost tools regen- conf {category} --force .", "diagnosis_regenconf_manually_modified": "Конфігураційний файл {file} , схоже, був змінений вручну.", "diagnosis_regenconf_allgood": "Всі конфігураційні файли відповідають рекомендованої конфігурації!", @@ -393,7 +393,7 @@ "diagnosis_mail_queue_unavailable": "Неможливо дізнатися кількість очікують листів в черзі", "diagnosis_mail_queue_ok": "{nb_pending} відкладені листи в поштових чергах", "diagnosis_mail_blacklist_website": "Після визначення причини, по якій ви потрапили в чорний список, і її усунення, ви можете попросити видалити ваш IP або домен на {blacklist_website}.", - "diagnosis_mail_blacklist_reason": "Причина внесення в чорний список: {причина}", + "diagnosis_mail_blacklist_reason": "Причина внесення в чорний список: {reason}", "diagnosis_mail_blacklist_listed_by": "Ваш IP або домен {item} знаходиться в чорному списку {blacklist_name}.", "diagnosis_mail_blacklist_ok": "IP-адреси і домени, які використовуються цим сервером, не внесені в чорний список", "diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "Поточний зворотний DNS: {rdns_domain}
Очікуване значення: {ehlo_domain} .", @@ -439,11 +439,11 @@ "updating_apt_cache": "Вибірка доступних оновлень для системних пакетів...", "update_apt_cache_warning": "Щось пішло не так при оновленні кеша APT (менеджера пакунків Debian). Ось дамп рядків sources.list, який може допомогти визначити проблемні рядки: {sourceslist}", "update_apt_cache_failed": "Неможливо оновити кеш APT (менеджер пакетів Debian). Ось дамп рядків sources.list, який може допомогти визначити проблемні рядки: {sourceslist}", - "unrestore_app": "{App} не буде поновлено", + "unrestore_app": "{app} не буде поновлено", "unlimit": "немає квоти", "unknown_main_domain_path": "Невідомий домен або шлях для '{app}'. Вам необхідно вказати домен і шлях, щоб мати можливість вказати URL для дозволу.", "unexpected_error": "Щось пішло не так: {error}", - "unbackup_app": "{App} НЕ буде збережений", + "unbackup_app": "{app} НЕ буде збережений", "tools_upgrade_special_packages_completed": "Оновлення пакета YunoHost завершено. Натисніть [Enter] для повернення командного рядка", "tools_upgrade_special_packages_explanation": "Спеціальне оновлення триватиме у фоновому режимі. Будь ласка, не запускайте ніяких інших дій на вашому сервері протягом наступних ~ 10 хвилин (в залежності від швидкості обладнання). Після цього вам, можливо, доведеться заново увійти в веб-адмін. Журнал поновлення буде доступний в Інструменти → Журнал (в веб-адміном) або за допомогою 'yunohost log list' (з командного рядка).", "tools_upgrade_special_packages": "Тепер оновлюємо \"спеціальні\" (пов'язані з yunohost) пакети…", @@ -467,12 +467,12 @@ "service_start_failed": "Не вдалося запустити службу '{service}' Recent service logs: {logs}", "diagnosis_mail_outgoing_port_25_ok": "Поштовий сервер SMTP може відправляти електронні листи (вихідний порт 25 не заблокований).", "diagnosis_swap_tip": "Будь ласка, будьте обережні і знайте, що якщо сервер розміщує своп на SD-карті або SSD-накопичувачі, це може різко скоротити термін служби устройства`.", - "diagnosis_swap_ok": "Система має {усього} свопу!", - "diagnosis_swap_notsomuch": "Система має тільки {усього} свопу. Щоб уникнути ситуацій, коли в системі закінчується пам'ять, слід передбачити наявність не менше {рекомендованого} обсягу підкачки.", - "diagnosis_swap_none": "В системі повністю відсутній своп. Ви повинні розглянути можливість додавання принаймні {рекомендованого} обсягу підкачки, щоб уникнути ситуацій, коли системі не вистачає пам'яті.", - "diagnosis_ram_ok": "Система все ще має {доступно} ({доступний_процент}%) оперативної пам'яті з {усього}.", - "diagnosis_ram_low": "У системі є {доступно} ({доступний_процент}%) оперативної пам'яті (з {усього}). Будьте уважні.", - "diagnosis_ram_verylow": "Система має тільки {доступне} ({доступний_процент}%) оперативної пам'яті! (З {усього})", + "diagnosis_swap_ok": "Система має {total} свопу!", + "diagnosis_swap_notsomuch": "Система має тільки {total} свопу. Щоб уникнути ситуацій, коли в системі закінчується пам'ять, слід передбачити наявність не менше {recommended} обсягу підкачки.", + "diagnosis_swap_none": "В системі повністю відсутній своп. Ви повинні розглянути можливість додавання принаймні {recommended} обсягу підкачки, щоб уникнути ситуацій, коли системі не вистачає пам'яті.", + "diagnosis_ram_ok": "Система все ще має {available} ({available_percent}%) оперативної пам'яті з {total}.", + "diagnosis_ram_low": "У системі є {available} ({available_percent}%) оперативної пам'яті (з {total}). Будьте уважні.", + "diagnosis_ram_verylow": "Система має тільки {available} ({available_percent}%) оперативної пам'яті! (З {total})", "diagnosis_diskusage_ok": "У сховищі {mountpoint} (на пристрої {device} ) залишилося {free} ({free_percent}%) вільного місця (з {total})!", "diagnosis_diskusage_low": "Сховище {mountpoint} (на пристрої {device} ) має тільки {free} ({free_percent}%) вільного місця (з {total}). Будьте уважні.", "diagnosis_diskusage_verylow": "Сховище {mountpoint} (на пристрої {device} ) має тільки {free} ({free_percent}%) вільного місця (з {total}). Вам дійсно варто подумати про очищення простору!", @@ -480,7 +480,7 @@ "diagnosis_services_bad_status": "Сервіс {service} знаходиться в {status} :(", "diagnosis_services_conf_broken": "Конфігурація порушена для служби {service}!", "diagnosis_services_running": "Служба {service} запущена!", - "diagnosis_domain_expires_in": "Термін дії {домену} закінчується через {днів} днів.", + "diagnosis_domain_expires_in": "Термін дії {domain} закінчується через {days} днів.", "diagnosis_domain_expiration_error": "Термін дії деяких доменів закінчується ДУЖЕ СКОРО!", "diagnosis_domain_expiration_warning": "Термін дії деяких доменів закінчиться найближчим часом!", "diagnosis_domain_expiration_success": "Ваші домени зареєстровані і не збираються спливати найближчим часом.", @@ -492,8 +492,8 @@ "diagnosis_dns_point_to_doc": "Якщо вам потрібна допомога з налаштування DNS-записів, зверніться до документації на сайті
https://yunohost.org/dns_config .", "diagnosis_dns_discrepancy": "Наступний запис DNS, схоже, не відповідає рекомендованої конфігурації:
Type: {type}
Name: {name}
Поточне значення: {current}
Очікуване значення: {value} ", "diagnosis_dns_missing_record": "Згідно рекомендованої конфігурації DNS, ви повинні додати запис DNS з наступною інформацією.
Тип: {type}
Name: {name}
Value: < code> {value} .", - "diagnosis_dns_bad_conf": "Деякі DNS-записи відсутні або невірні для домену {домен} (категорія {категорія})", - "diagnosis_dns_good_conf": "DNS-записи правильно налаштовані для домену {домен} (категорія {категорія})", + "diagnosis_dns_bad_conf": "Деякі DNS-записи відсутні або невірні для домену {domain} (категорія {category})", + "diagnosis_dns_good_conf": "DNS-записи правильно налаштовані для домену {domain} (категорія {category})", "diagnosis_ip_weird_resolvconf_details": "Файл /etc/resolv.conf повинен бути симлінк на /etc/resolvconf/run/resolv.conf , що вказує на 127.0.0.1 (dnsmasq ). Якщо ви хочете вручну налаштувати DNS Резолвер, відредагуйте /etc/resolv.dnsmasq.conf .", "diagnosis_ip_weird_resolvconf": "Дозвіл DNS, схоже, працює, але схоже, що ви використовуєте для користувача /etc/resolv.conf .", "diagnosis_ip_broken_resolvconf": "Схоже, що дозвіл доменних імен на вашому сервері порушено, що пов'язано з тим, що /etc/resolv.conf не вказує на 127.0.0.1 .", @@ -507,23 +507,23 @@ "diagnosis_ip_connected_ipv6": "Сервер підключений до Інтернету через IPv6!", "diagnosis_ip_no_ipv4": "Сервер не має працюючого IPv4.", "diagnosis_ip_connected_ipv4": "Сервер підключений до Інтернету через IPv4!", - "diagnosis_no_cache": "Для категорії \"{категорія} 'ще немає кеша діагнозів.", - "diagnosis_failed": "Не вдалося результат діагностики для категорії '{категорія}': {error}", - "diagnosis_everything_ok": "Все виглядає добре для {категорії}!", - "diagnosis_found_warnings": "Знайдено {попередження} пунктів, які можна поліпшити для {категорії}.", - "diagnosis_found_errors_and_warnings": "Знайдено {errors} істотний (і) питання (и) (і {попередження} попередження (я)), що відносяться до {category}!", + "diagnosis_no_cache": "Для категорії \"{category} 'ще немає кеша діагнозів.", + "diagnosis_failed": "Не вдалося результат діагностики для категорії '{category}': {error}", + "diagnosis_everything_ok": "Все виглядає добре для {category}!", + "diagnosis_found_warnings": "Знайдено {warnings} пунктів, які можна поліпшити для {category}.", + "diagnosis_found_errors_and_warnings": "Знайдено {errors} істотний (і) питання (и) (і {warnings} попередження (я)), що відносяться до {category}!", "diagnosis_found_errors": "Знайдена {errors} важлива проблема (і), пов'язана з {category}!", - "diagnosis_ignored_issues": "(+ {Nb_ignored} проігнорована проблема (проблеми))", - "diagnosis_cant_run_because_of_dep": "Неможливо запустити діагностику для {категорії}, поки є важливі проблеми, пов'язані з {глибиною}.", - "diagnosis_cache_still_valid": "(Кеш все ще дійсний для діагностики {категорії}. Повторна діагностика поки не проводиться!)", - "diagnosis_failed_for_category": "Не вдалося провести діагностику для категорії '{категорія}': {error}", + "diagnosis_ignored_issues": "(+ {nb_ignored} проігнорована проблема (проблеми))", + "diagnosis_cant_run_because_of_dep": "Неможливо запустити діагностику для {category}, поки є важливі проблеми, пов'язані з {dep}.", + "diagnosis_cache_still_valid": "(Кеш все ще дійсний для діагностики {category}. Повторна діагностика поки не проводиться!)", + "diagnosis_failed_for_category": "Не вдалося провести діагностику для категорії '{category}': {error}", "diagnosis_display_tip": "Щоб побачити знайдені проблеми, ви можете перейти в розділ Diagnosis в веб-адміном або виконати команду 'yunohost diagnosis show --issues --human-readable' з командного рядка.", "diagnosis_package_installed_from_sury_details": "Деякі пакети були ненавмисно встановлені з стороннього сховища під назвою Sury. Команда YunoHost поліпшила стратегію роботи з цими пакетами, але очікується, що в деяких системах, які встановили додатки PHP7.3 ще на Stretch, залишаться деякі невідповідності. Щоб виправити цю ситуацію, спробуйте виконати наступну команду: {cmd_to_fix} .", "diagnosis_package_installed_from_sury": "Деякі системні пакети повинні бути знижені в статусі", "diagnosis_backports_in_sources_list": "Схоже, що apt (менеджер пакетів) налаштований на використання сховища backports. Якщо ви не знаєте, що робите, ми настійно не рекомендуємо встановлювати пакети з backports, тому що це може привести до нестабільності або конфліктів у вашій системі.", "diagnosis_basesystem_ynh_inconsistent_versions": "Ви використовуєте несумісні версії пакетів YunoHost... швидше за все, через невдалий або часткового оновлення.", "diagnosis_basesystem_ynh_main_version": "Сервер працює під управлінням YunoHost {main_version} ({repo})", - "diagnosis_basesystem_ynh_single_version": "{Пакет} версія: {версія} ({repo})", + "diagnosis_basesystem_ynh_single_version": "{package} версія: {version} ({repo})", "diagnosis_basesystem_kernel": "Сервер працює під управлінням ядра Linux {kernel_version}", "diagnosis_basesystem_host": "Сервер працює під управлінням Debian {debian_version}", "diagnosis_basesystem_hardware_model": "Модель сервера - {model}", @@ -531,7 +531,7 @@ "custom_app_url_required": "Ви повинні надати URL для оновлення вашого призначеного для користувача додатки {app}.", "confirm_app_install_thirdparty": "НЕБЕЗПЕЧНО! Ця програма не входить в каталог додатків YunoHost. Установлення сторонніх додатків може порушити цілісність і безпеку вашої системи. Вам не слід встановлювати його, якщо ви не знаєте, що робите. НІЯКОЇ ПІДТРИМКИ НЕ БУДЕ, якщо цей додаток не буде працювати або зламає вашу систему... Якщо ви все одно готові піти на такий ризик, введіть '{answers}'.", "confirm_app_install_danger": "НЕБЕЗПЕЧНО! Відомо, що це додаток все ще експериментальне (якщо не сказати, що воно явно не працює)! Вам не слід встановлювати його, якщо ви не знаєте, що робите. Ніякої підтримки не буде надано, якщо цей додаток не буде працювати або зламає вашу систему... Якщо ви все одно готові ризикнути, введіть '{answers}'.", - "confirm_app_install_warning": "Попередження: Ця програма може працювати, але не дуже добре інтегровано в YunoHost. Деякі функції, такі як єдина реєстрація та резервне копіювання/відновлення, можуть бути недоступні. Все одно встановити? [{Відповіді}]. ", + "confirm_app_install_warning": "Попередження: Ця програма може працювати, але не дуже добре інтегровано в YunoHost. Деякі функції, такі як єдина реєстрація та резервне копіювання/відновлення, можуть бути недоступні. Все одно встановити? [{answers}]. ", "certmanager_unable_to_parse_self_CA_name": "Не вдалося розібрати ім'я самоподпісивающегося центру (файл: {file})", "certmanager_self_ca_conf_file_not_found": "Не вдалося знайти файл конфігурації для самоподпісивающегося центру (файл: {file})", "certmanager_no_cert_file": "Не вдалося розпізнати файл сертифіката для домену {domain} (файл: {file})", @@ -546,12 +546,12 @@ "certmanager_cert_renew_success": "Сертифікат Let's Encrypt оновлений для домену '{domain}'", "certmanager_cert_install_success_selfsigned": "Самоподпісанний сертифікат тепер встановлений для домену '{domain}'", "certmanager_cert_install_success": "Сертифікат Let's Encrypt тепер встановлений для домену '{domain}'", - "certmanager_cannot_read_cert": "Щось не так сталося при спробі відкрити поточний сертифікат для домену {domain} (файл: {файл}), причина: {причина}", + "certmanager_cannot_read_cert": "Щось не так сталося при спробі відкрити поточний сертифікат для домену {domain} (файл: {file}), причина: {reason}", "certmanager_attempt_to_replace_valid_cert": "Ви намагаєтеся перезаписати хороший і дійсний сертифікат для домену {domain}! (Використовуйте --force для обходу)", "certmanager_attempt_to_renew_valid_cert": "Термін дії сертифіката для домену '{domain} \"не закінчується! (Ви можете використовувати --force, якщо знаєте, що робите)", "certmanager_attempt_to_renew_nonLE_cert": "Сертифікат для домену '{domain}' не випущено Let's Encrypt. Неможливо продовжити його автоматично!", "certmanager_acme_not_configured_for_domain": "Завдання ACME не може бути запущена для {domain} прямо зараз, тому що в його nginx conf відсутній відповідний фрагмент коду... Будь ласка, переконайтеся, що конфігурація nginx оновлена за допомогою `yunohost tools regen-conf nginx --dry-run - with-diff`.", - "backup_with_no_restore_script_for_app": "{App} не має скрипта відновлення, ви не зможете автоматично відновити резервну копію цього додатка.", + "backup_with_no_restore_script_for_app": "{app} не має скрипта відновлення, ви не зможете автоматично відновити резервну копію цього додатка.", "backup_with_no_backup_script_for_app": "Додаток '{app}' не має скрипта резервного копіювання. Ігнорування.", "backup_unable_to_organize_files": "Неможливо використовувати швидкий метод для організації файлів в архіві", "backup_system_part_failed": "Не вдалося створити резервну копію системної частини '{part}'.", @@ -565,7 +565,7 @@ "backup_no_uncompress_archive_dir": "Немає такого каталогу нестислого архіву", "backup_mount_archive_for_restore": "Підготовка архіву для відновлення...", "backup_method_tar_finished": "Створено архів резервного копіювання TAR", - "backup_method_custom_finished": "Призначений для користувача метод резервного копіювання '{метод}' завершено", + "backup_method_custom_finished": "Призначений для користувача метод резервного копіювання '{method}' завершено", "backup_method_copy_finished": "Резервне копіювання завершено", "backup_hook_unknown": "Гачок резервного копіювання '{hook}' невідомий", "backup_deleted": "Резервна копія видалена", @@ -575,7 +575,7 @@ "backup_csv_creation_failed": "Не вдалося створити CSV-файл, необхідний для відновлення", "backup_csv_addition_failed": "Не вдалося додати файли для резервного копіювання в CSV-файл", "backup_creation_failed": "Не вдалося створити архів резервного копіювання", - "backup_create_size_estimation": "Архів буде містити близько {розмір} даних.", + "backup_create_size_estimation": "Архів буде містити близько {size} даних.", "backup_created": "Резервна копія створена", "backup_couldnt_bind": "Не вдалося зв'язати {src} з {dest}.", "backup_copying_to_organize_the_archive": "Копіювання {size} MB для організації архіву", @@ -592,7 +592,7 @@ "backup_archive_broken_link": "Не вдалося отримати доступ до архіву резервного копіювання (непрацююча посилання на {path})", "backup_archive_app_not_found": "Не вдалося знайти {app} в архіві резервного копіювання", "backup_applying_method_tar": "Створення резервного TAR-архіву...", - "backup_applying_method_custom": "Виклик для користувача методу резервного копіювання '{метод}'...", + "backup_applying_method_custom": "Виклик для користувача методу резервного копіювання '{method}'...", "backup_applying_method_copy": "Копіювання всіх файлів в резервну копію...", "backup_app_failed": "Не вдалося створити резервну копію {app}", "backup_actually_backuping": "Створення резервного архіву з зібраних файлів...", @@ -612,7 +612,7 @@ "apps_catalog_init_success": "Система каталогу додатків инициализирована!", "apps_already_up_to_date": "Всі додатки вже оновлені", "app_packaging_format_not_supported": "Ця програма не може бути встановлено, тому що формат його упаковки не підтримується вашою версією YunoHost. Можливо, вам слід оновити вашу систему.", - "app_upgraded": "{App} оновлено", + "app_upgraded": "{app} оновлено", "app_upgrade_some_app_failed": "Деякі програми не можуть бути оновлені", "app_upgrade_script_failed": "Сталася помилка в сценарії оновлення програми", "app_upgrade_failed": "Не вдалося оновити {app}: {error}", @@ -630,10 +630,10 @@ "app_remove_after_failed_install": "Видалення програми після збою установки...", "app_requirements_unmeet": "Вимоги не виконані для {app}, пакет {pkgname} ({version}) повинен бути {spec}.", "app_requirements_checking": "Перевірка необхідних пакетів для {app}...", - "app_removed": "{App} видалено", - "app_not_properly_removed": "{App} не було видалено належним чином", + "app_removed": "{app} видалено", + "app_not_properly_removed": "{app} не було видалено належним чином", "app_not_installed": "Не вдалося знайти {app} в списку встановлених додатків: {all_apps}", - "app_not_correctly_installed": "{App}, схоже, неправильно встановлено", + "app_not_correctly_installed": "{app}, схоже, неправильно встановлено", "app_not_upgraded": "Додаток '{failed_app}' не вдалося оновити, і, як наслідок, оновлення таких програмах було скасовано: {apps}", "app_manifest_install_ask_is_public": "Чи повинно це додаток бути відкрито для анонімних відвідувачів?", "app_manifest_install_ask_admin": "Виберіть користувача-адміністратора для цього додатка", From caef1e0577a1b0f3d0136d337193bc1c65280b4c Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Thu, 2 Sep 2021 14:08:18 +0200 Subject: [PATCH 2875/3170] Update src/yunohost/log.py --- src/yunohost/log.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/yunohost/log.py b/src/yunohost/log.py index f9f9334fb..4994d608c 100644 --- a/src/yunohost/log.py +++ b/src/yunohost/log.py @@ -373,7 +373,11 @@ def is_unit_operation( if field in context: context.pop(field, None) - # Manage file or stream + # Context is made from args given to main function by argparse + # This context will be added in extra parameters in yml file, so this context should + # be serializable and short enough (it will be displayed in webadmin) + # Argparse can provide some File or Stream, so here we display the filename or + # the IOBase, if we have no name. for field, value in context.items(): if isinstance(value, IOBase): try: From f2487a2251fdc8c19dc5dbd14a8eb3707f97156f Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Thu, 2 Sep 2021 14:24:06 +0200 Subject: [PATCH 2876/3170] Avoid confusing things in user_list --- src/yunohost/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 0cdd0d3ae..65edf5821 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -64,7 +64,7 @@ def user_list(fields=None): ldap_attrs = { 'username': 'uid', - 'password': 'uid', + 'password': '', # We can't request password in ldap 'fullname': 'cn', 'firstname': 'givenName', 'lastname': 'sn', From e27f38ae69aeb9308d8166e366f764f8afd8378f Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Thu, 2 Sep 2021 14:33:06 +0200 Subject: [PATCH 2877/3170] Test group remove on csv import --- src/yunohost/tests/test_user-group.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/yunohost/tests/test_user-group.py b/src/yunohost/tests/test_user-group.py index d3b3c81aa..ab7e72555 100644 --- a/src/yunohost/tests/test_user-group.py +++ b/src/yunohost/tests/test_user-group.py @@ -158,6 +158,7 @@ def test_import_user(mocker): assert len(user_res['alice']['mail-alias']) == 2 assert "albert" in group_res['dev']['members'] assert "alice" in group_res['apps']['members'] + assert "alice" not in group_res['dev']['members'] def test_export_user(mocker): From 57a2e4032fecb9eb4d2b59b4ab483fad50621eeb Mon Sep 17 00:00:00 2001 From: Kayou Date: Thu, 2 Sep 2021 16:52:16 +0200 Subject: [PATCH 2878/3170] replace msettings by Moulinette --- src/yunohost/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 57e8aac41..07d1773b3 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -631,7 +631,7 @@ def user_export(): writer.writerow(user) body = csv_io.getvalue().rstrip() - if msettings.get('interface') == 'api': + if Moulinette.interface.type == 'api': # We return a raw bottle HTTPresponse (instead of serializable data like # list/dict, ...), which is gonna be picked and used directly by moulinette from bottle import HTTPResponse From c197e171bbc3d18a095d0b9bb3a0f72d57afab3e Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Thu, 2 Sep 2021 15:04:02 +0000 Subject: [PATCH 2879/3170] [CI] Format code --- src/yunohost/log.py | 4 +- src/yunohost/tests/test_user-group.py | 80 +++--- src/yunohost/user.py | 386 +++++++++++++++----------- 3 files changed, 269 insertions(+), 201 deletions(-) diff --git a/src/yunohost/log.py b/src/yunohost/log.py index 6460d8d4a..3f6382af2 100644 --- a/src/yunohost/log.py +++ b/src/yunohost/log.py @@ -373,7 +373,7 @@ def is_unit_operation( context.pop(field, None) # Context is made from args given to main function by argparse - # This context will be added in extra parameters in yml file, so this context should + # This context will be added in extra parameters in yml file, so this context should # be serializable and short enough (it will be displayed in webadmin) # Argparse can provide some File or Stream, so here we display the filename or # the IOBase, if we have no name. @@ -382,7 +382,7 @@ def is_unit_operation( try: context[field] = value.name except: - context[field] = 'IOBase' + context[field] = "IOBase" operation_logger = OperationLogger(op_key, related_to, args=context) try: diff --git a/src/yunohost/tests/test_user-group.py b/src/yunohost/tests/test_user-group.py index ab7e72555..60e748108 100644 --- a/src/yunohost/tests/test_user-group.py +++ b/src/yunohost/tests/test_user-group.py @@ -117,53 +117,65 @@ def test_del_user(mocker): def test_import_user(mocker): import csv from io import StringIO - fieldnames = [u'username', u'firstname', u'lastname', u'password', - u'mailbox-quota', u'mail', u'mail-alias', u'mail-forward', - u'groups'] + + fieldnames = [ + "username", + "firstname", + "lastname", + "password", + "mailbox-quota", + "mail", + "mail-alias", + "mail-forward", + "groups", + ] with StringIO() as csv_io: - writer = csv.DictWriter(csv_io, fieldnames, delimiter=';', - quotechar='"') + writer = csv.DictWriter(csv_io, fieldnames, delimiter=";", quotechar='"') writer.writeheader() - writer.writerow({ - 'username': "albert", - 'firstname': "Albert", - 'lastname': "Good", - 'password': "", - 'mailbox-quota': "1G", - 'mail': "albert@" + maindomain, - 'mail-alias': "albert2@" + maindomain, - 'mail-forward': "albert@example.com", - 'groups': "dev", - }) - writer.writerow({ - 'username': "alice", - 'firstname': "Alice", - 'lastname': "White", - 'password': "", - 'mailbox-quota': "1G", - 'mail': "alice@" + maindomain, - 'mail-alias': "alice1@" + maindomain + ",alice2@" + maindomain, - 'mail-forward': "", - 'groups': "apps", - }) + writer.writerow( + { + "username": "albert", + "firstname": "Albert", + "lastname": "Good", + "password": "", + "mailbox-quota": "1G", + "mail": "albert@" + maindomain, + "mail-alias": "albert2@" + maindomain, + "mail-forward": "albert@example.com", + "groups": "dev", + } + ) + writer.writerow( + { + "username": "alice", + "firstname": "Alice", + "lastname": "White", + "password": "", + "mailbox-quota": "1G", + "mail": "alice@" + maindomain, + "mail-alias": "alice1@" + maindomain + ",alice2@" + maindomain, + "mail-forward": "", + "groups": "apps", + } + ) csv_io.seek(0) with message(mocker, "user_import_success"): user_import(csv_io, update=True, delete=True) - group_res = user_group_list()['groups'] - user_res = user_list(list(FIELDS_FOR_IMPORT.keys()))['users'] + group_res = user_group_list()["groups"] + user_res = user_list(list(FIELDS_FOR_IMPORT.keys()))["users"] assert "albert" in user_res assert "alice" in user_res assert "bob" not in user_res - assert len(user_res['alice']['mail-alias']) == 2 - assert "albert" in group_res['dev']['members'] - assert "alice" in group_res['apps']['members'] - assert "alice" not in group_res['dev']['members'] + assert len(user_res["alice"]["mail-alias"]) == 2 + assert "albert" in group_res["dev"]["members"] + assert "alice" in group_res["apps"]["members"] + assert "alice" not in group_res["dev"]["members"] def test_export_user(mocker): result = user_export() - aliases = ','.join([alias + maindomain for alias in FIRST_ALIASES]) + aliases = ",".join([alias + maindomain for alias in FIRST_ALIASES]) should_be = ( "username;firstname;lastname;password;mail;mail-alias;mail-forward;mailbox-quota;groups\r\n" f"alice;Alice;White;;alice@{maindomain};{aliases};;0;dev\r\n" diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 07d1773b3..c89f9a05f 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -44,18 +44,18 @@ from yunohost.log import is_unit_operation logger = getActionLogger("yunohost.user") FIELDS_FOR_IMPORT = { - 'username': r'^[a-z0-9_]+$', - 'firstname': r'^([^\W\d_]{1,30}[ ,.\'-]{0,3})+$', - 'lastname': r'^([^\W\d_]{1,30}[ ,.\'-]{0,3})+$', - 'password': r'^|(.{3,})$', - 'mail': r'^([\w.-]+@([^\W_A-Z]+([-]*[^\W_A-Z]+)*\.)+((xn--)?[^\W_]{2,}))$', - 'mail-alias': r'^|([\w.-]+@([^\W_A-Z]+([-]*[^\W_A-Z]+)*\.)+((xn--)?[^\W_]{2,}),?)+$', - 'mail-forward': r'^|([\w\+.-]+@([^\W_A-Z]+([-]*[^\W_A-Z]+)*\.)+((xn--)?[^\W_]{2,}),?)+$', - 'mailbox-quota': r'^(\d+[bkMGT])|0|$', - 'groups': r'^|([a-z0-9_]+(,?[a-z0-9_]+)*)$' + "username": r"^[a-z0-9_]+$", + "firstname": r"^([^\W\d_]{1,30}[ ,.\'-]{0,3})+$", + "lastname": r"^([^\W\d_]{1,30}[ ,.\'-]{0,3})+$", + "password": r"^|(.{3,})$", + "mail": r"^([\w.-]+@([^\W_A-Z]+([-]*[^\W_A-Z]+)*\.)+((xn--)?[^\W_]{2,}))$", + "mail-alias": r"^|([\w.-]+@([^\W_A-Z]+([-]*[^\W_A-Z]+)*\.)+((xn--)?[^\W_]{2,}),?)+$", + "mail-forward": r"^|([\w\+.-]+@([^\W_A-Z]+([-]*[^\W_A-Z]+)*\.)+((xn--)?[^\W_]{2,}),?)+$", + "mailbox-quota": r"^(\d+[bkMGT])|0|$", + "groups": r"^|([a-z0-9_]+(,?[a-z0-9_]+)*)$", } -FIRST_ALIASES = ['root@', 'admin@', 'webmaster@', 'postmaster@', 'abuse@'] +FIRST_ALIASES = ["root@", "admin@", "webmaster@", "postmaster@", "abuse@"] def user_list(fields=None): @@ -63,47 +63,51 @@ def user_list(fields=None): from yunohost.utils.ldap import _get_ldap_interface ldap_attrs = { - 'username': 'uid', - 'password': '', # We can't request password in ldap - 'fullname': 'cn', - 'firstname': 'givenName', - 'lastname': 'sn', - 'mail': 'mail', - 'mail-alias': 'mail', - 'mail-forward': 'maildrop', - 'mailbox-quota': 'mailuserquota', - 'groups': 'memberOf', - 'shell': 'loginShell', - 'home-path': 'homeDirectory' + "username": "uid", + "password": "", # We can't request password in ldap + "fullname": "cn", + "firstname": "givenName", + "lastname": "sn", + "mail": "mail", + "mail-alias": "mail", + "mail-forward": "maildrop", + "mailbox-quota": "mailuserquota", + "groups": "memberOf", + "shell": "loginShell", + "home-path": "homeDirectory", } def display_default(values, _): return values[0] if len(values) == 1 else values display = { - 'password': lambda values, user: '', - 'mail': lambda values, user: display_default(values[:1], user), - 'mail-alias': lambda values, _: values[1:], - 'mail-forward': lambda values, user: [forward for forward in values if forward != user['uid'][0]], - 'groups': lambda values, user: [ - group[3:].split(',')[0] + "password": lambda values, user: "", + "mail": lambda values, user: display_default(values[:1], user), + "mail-alias": lambda values, _: values[1:], + "mail-forward": lambda values, user: [ + forward for forward in values if forward != user["uid"][0] + ], + "groups": lambda values, user: [ + group[3:].split(",")[0] for group in values - if not group.startswith('cn=all_users,') and - not group.startswith('cn=' + user['uid'][0] + ',')], - 'shell': lambda values, _: len(values) > 0 and values[0].strip() == "/bin/false" + if not group.startswith("cn=all_users,") + and not group.startswith("cn=" + user["uid"][0] + ",") + ], + "shell": lambda values, _: len(values) > 0 + and values[0].strip() == "/bin/false", } - attrs = set(['uid']) + attrs = set(["uid"]) users = {} if not fields: - fields = ['username', 'fullname', 'mail', 'mailbox-quota'] + fields = ["username", "fullname", "mail", "mailbox-quota"] for field in fields: if field in ldap_attrs: attrs.add(ldap_attrs[field]) else: - raise YunohostError('field_invalid', field) + raise YunohostError("field_invalid", field) ldap = _get_ldap_interface() result = ldap.search( @@ -120,7 +124,7 @@ def user_list(fields=None): values = user[ldap_attrs[field]] entry[field] = display.get(field, display_default)(values, user) - users[user['uid'][0]] = entry + users[user["uid"][0]] = entry return {"users": users} @@ -135,7 +139,7 @@ def user_create( password, mailbox_quota="0", mail=None, - from_import=False + from_import=False, ): from yunohost.domain import domain_list, _get_maindomain @@ -253,10 +257,9 @@ def user_create( # Attempt to create user home folder subprocess.check_call(["mkhomedir_helper", username]) except subprocess.CalledProcessError: - home = f'/home/{username}' + home = f"/home/{username}" if not os.path.isdir(home): - logger.warning(m18n.n('user_home_creation_failed', home=home), - exc_info=1) + logger.warning(m18n.n("user_home_creation_failed", home=home), exc_info=1) try: subprocess.check_call( @@ -282,12 +285,12 @@ def user_create( # TODO: Send a welcome mail to user if not from_import: - logger.success(m18n.n('user_created')) + logger.success(m18n.n("user_created")) return {"fullname": fullname, "username": username, "mail": mail} -@is_unit_operation([('username', 'user')]) +@is_unit_operation([("username", "user")]) def user_delete(operation_logger, username, purge=False, from_import=False): """ Delete user @@ -331,13 +334,14 @@ def user_delete(operation_logger, username, purge=False, from_import=False): subprocess.call(["nscd", "-i", "passwd"]) if purge: - subprocess.call(['rm', '-rf', '/home/{0}'.format(username)]) - subprocess.call(['rm', '-rf', '/var/mail/{0}'.format(username)]) + subprocess.call(["rm", "-rf", "/home/{0}".format(username)]) + subprocess.call(["rm", "-rf", "/var/mail/{0}".format(username)]) - hook_callback('post_user_delete', args=[username, purge]) + hook_callback("post_user_delete", args=[username, purge]) if not from_import: - logger.success(m18n.n('user_deleted')) + logger.success(m18n.n("user_deleted")) + @is_unit_operation([("username", "user")], exclude=["change_password"]) def user_update( @@ -352,7 +356,7 @@ def user_update( add_mailalias=None, remove_mailalias=None, mailbox_quota=None, - from_import=False + from_import=False, ): """ Update user informations @@ -412,7 +416,7 @@ def user_update( ] # change_password is None if user_update is not called to change the password - if change_password is not None and change_password != '': + if change_password is not None and change_password != "": # when in the cli interface if the option to change the password is called # without a specified value, change_password will be set to the const 0. # In this case we prompt for the new password. @@ -429,20 +433,22 @@ def user_update( aliases = [alias + main_domain for alias in FIRST_ALIASES] # If the requested mail address is already as main address or as an alias by this user - if mail in user['mail']: - user['mail'].remove(mail) + if mail in user["mail"]: + user["mail"].remove(mail) # Othewise, check that this mail address is not already used by this user else: try: - ldap.validate_uniqueness({'mail': mail}) + ldap.validate_uniqueness({"mail": mail}) except Exception as e: - raise YunohostError('user_update_failed', user=username, error=e) - if mail[mail.find('@') + 1:] not in domains: - raise YunohostError('mail_domain_unknown', domain=mail[mail.find('@') + 1:]) + raise YunohostError("user_update_failed", user=username, error=e) + if mail[mail.find("@") + 1 :] not in domains: + raise YunohostError( + "mail_domain_unknown", domain=mail[mail.find("@") + 1 :] + ) if mail in aliases: raise YunohostValidationError("mail_unavailable") - new_attr_dict['mail'] = [mail] + user['mail'][1:] + new_attr_dict["mail"] = [mail] + user["mail"][1:] if add_mailalias: if not isinstance(add_mailalias, list): @@ -455,12 +461,10 @@ def user_update( try: ldap.validate_uniqueness({"mail": mail}) except Exception as e: - raise YunohostError( - "user_update_failed", user=username, error=e - ) - if mail[mail.find("@") + 1:] not in domains: + raise YunohostError("user_update_failed", user=username, error=e) + if mail[mail.find("@") + 1 :] not in domains: raise YunohostError( - "mail_domain_unknown", domain=mail[mail.find("@") + 1:] + "mail_domain_unknown", domain=mail[mail.find("@") + 1 :] ) user["mail"].append(mail) new_attr_dict["mail"] = user["mail"] @@ -517,7 +521,7 @@ def user_update( if not from_import: app_ssowatconf() - logger.success(m18n.n('user_updated')) + logger.success(m18n.n("user_updated")) return user_info(username) @@ -548,13 +552,13 @@ def user_info(username): raise YunohostValidationError("user_unknown", user=username) result_dict = { - 'username': user['uid'][0], - 'fullname': user['cn'][0], - 'firstname': user['givenName'][0], - 'lastname': user['sn'][0], - 'mail': user['mail'][0], - 'mail-aliases': [], - 'mail-forward': [] + "username": user["uid"][0], + "fullname": user["cn"][0], + "firstname": user["givenName"][0], + "lastname": user["sn"][0], + "mail": user["mail"][0], + "mail-aliases": [], + "mail-forward": [], } if len(user["mail"]) > 1: @@ -619,27 +623,32 @@ def user_export(): """ import csv # CSV are needed only in this function from io import StringIO + with StringIO() as csv_io: - writer = csv.DictWriter(csv_io, list(FIELDS_FOR_IMPORT.keys()), - delimiter=';', quotechar='"') + writer = csv.DictWriter( + csv_io, list(FIELDS_FOR_IMPORT.keys()), delimiter=";", quotechar='"' + ) writer.writeheader() - users = user_list(list(FIELDS_FOR_IMPORT.keys()))['users'] + users = user_list(list(FIELDS_FOR_IMPORT.keys()))["users"] for username, user in users.items(): - user['mail-alias'] = ','.join(user['mail-alias']) - user['mail-forward'] = ','.join(user['mail-forward']) - user['groups'] = ','.join(user['groups']) + user["mail-alias"] = ",".join(user["mail-alias"]) + user["mail-forward"] = ",".join(user["mail-forward"]) + user["groups"] = ",".join(user["groups"]) writer.writerow(user) body = csv_io.getvalue().rstrip() - if Moulinette.interface.type == 'api': + if Moulinette.interface.type == "api": # We return a raw bottle HTTPresponse (instead of serializable data like # list/dict, ...), which is gonna be picked and used directly by moulinette from bottle import HTTPResponse - response = HTTPResponse(body=body, - headers={ - "Content-Disposition": "attachment; filename=users.csv", - "Content-Type": "text/csv", - }) + + response = HTTPResponse( + body=body, + headers={ + "Content-Disposition": "attachment; filename=users.csv", + "Content-Type": "text/csv", + }, + ) return response else: return body @@ -662,106 +671,121 @@ def user_import(operation_logger, csvfile, update=False, delete=False): from yunohost.domain import domain_list # Pre-validate data and prepare what should be done - actions = { - 'created': [], - 'updated': [], - 'deleted': [] - } + actions = {"created": [], "updated": [], "deleted": []} is_well_formatted = True def to_list(str_list): - L = str_list.split(',') if str_list else [] + L = str_list.split(",") if str_list else [] L = [l.strip() for l in L] return L - existing_users = user_list()['users'] + existing_users = user_list()["users"] existing_groups = user_group_list()["groups"] existing_domains = domain_list()["domains"] - reader = csv.DictReader(csvfile, delimiter=';', quotechar='"') + reader = csv.DictReader(csvfile, delimiter=";", quotechar='"') users_in_csv = [] - missing_columns = [key for key in FIELDS_FOR_IMPORT.keys() if key not in reader.fieldnames] + missing_columns = [ + key for key in FIELDS_FOR_IMPORT.keys() if key not in reader.fieldnames + ] if missing_columns: - raise YunohostValidationError("user_import_missing_columns", columns=', '.join(missing_columns)) + raise YunohostValidationError( + "user_import_missing_columns", columns=", ".join(missing_columns) + ) for user in reader: # Validate column values against regexes - format_errors = [f"{key}: '{user[key]}' doesn't match the expected format" - for key, validator in FIELDS_FOR_IMPORT.items() - if user[key] is None or not re.match(validator, user[key])] + format_errors = [ + f"{key}: '{user[key]}' doesn't match the expected format" + for key, validator in FIELDS_FOR_IMPORT.items() + if user[key] is None or not re.match(validator, user[key]) + ] # Check for duplicated username lines - if user['username'] in users_in_csv: + if user["username"] in users_in_csv: format_errors.append(f"username '{user['username']}' duplicated") - users_in_csv.append(user['username']) + users_in_csv.append(user["username"]) # Validate that groups exist - user['groups'] = to_list(user['groups']) - unknown_groups = [g for g in user['groups'] if g not in existing_groups] + user["groups"] = to_list(user["groups"]) + unknown_groups = [g for g in user["groups"] if g not in existing_groups] if unknown_groups: - format_errors.append(f"username '{user['username']}': unknown groups %s" % ', '.join(unknown_groups)) + format_errors.append( + f"username '{user['username']}': unknown groups %s" + % ", ".join(unknown_groups) + ) # Validate that domains exist - user['mail-alias'] = to_list(user['mail-alias']) - user['mail-forward'] = to_list(user['mail-forward']) - user['domain'] = user['mail'].split('@')[1] + user["mail-alias"] = to_list(user["mail-alias"]) + user["mail-forward"] = to_list(user["mail-forward"]) + user["domain"] = user["mail"].split("@")[1] unknown_domains = [] - if user['domain'] not in existing_domains: - unknown_domains.append(user['domain']) + if user["domain"] not in existing_domains: + unknown_domains.append(user["domain"]) - unknown_domains += [mail.split('@', 1)[1] for mail in user['mail-alias'] if mail.split('@', 1)[1] not in existing_domains] + unknown_domains += [ + mail.split("@", 1)[1] + for mail in user["mail-alias"] + if mail.split("@", 1)[1] not in existing_domains + ] unknown_domains = set(unknown_domains) if unknown_domains: - format_errors.append(f"username '{user['username']}': unknown domains %s" % ', '.join(unknown_domains)) + format_errors.append( + f"username '{user['username']}': unknown domains %s" + % ", ".join(unknown_domains) + ) if format_errors: - logger.error(m18n.n('user_import_bad_line', - line=reader.line_num, - details=', '.join(format_errors))) + logger.error( + m18n.n( + "user_import_bad_line", + line=reader.line_num, + details=", ".join(format_errors), + ) + ) is_well_formatted = False continue # Choose what to do with this line and prepare data - user['mailbox-quota'] = user['mailbox-quota'] or "0" + user["mailbox-quota"] = user["mailbox-quota"] or "0" # User creation - if user['username'] not in existing_users: + if user["username"] not in existing_users: # Generate password if not exists # This could be used when reset password will be merged - if not user['password']: - user['password'] = random_ascii(70) - actions['created'].append(user) + if not user["password"]: + user["password"] = random_ascii(70) + actions["created"].append(user) # User update elif update: - actions['updated'].append(user) + actions["updated"].append(user) if delete: - actions['deleted'] = [user for user in existing_users if user not in users_in_csv] + actions["deleted"] = [ + user for user in existing_users if user not in users_in_csv + ] if delete and not users_in_csv: - logger.error("You used the delete option with an empty csv file ... You probably did not really mean to do that, did you !?") + logger.error( + "You used the delete option with an empty csv file ... You probably did not really mean to do that, did you !?" + ) is_well_formatted = False if not is_well_formatted: - raise YunohostValidationError('user_import_bad_file') + raise YunohostValidationError("user_import_bad_file") - total = len(actions['created'] + actions['updated'] + actions['deleted']) + total = len(actions["created"] + actions["updated"] + actions["deleted"]) if total == 0: - logger.info(m18n.n('user_import_nothing_to_do')) + logger.info(m18n.n("user_import_nothing_to_do")) return # Apply creation, update and deletion operation - result = { - 'created': 0, - 'updated': 0, - 'deleted': 0, - 'errors': 0 - } + result = {"created": 0, "updated": 0, "deleted": 0, "errors": 0} def progress(info=""): progress.nb += 1 @@ -774,12 +798,13 @@ def user_import(operation_logger, csvfile, update=False, delete=False): return progress.old = bar logger.info(bar) + progress.nb = 0 progress.old = "" def on_failure(user, exception): - result['errors'] += 1 - logger.error(user + ': ' + str(exception)) + result["errors"] += 1 + logger.error(user + ": " + str(exception)) def update(new_infos, old_infos=False): remove_alias = None @@ -787,11 +812,21 @@ def user_import(operation_logger, csvfile, update=False, delete=False): remove_groups = [] add_groups = new_infos["groups"] if old_infos: - new_infos['mail'] = None if old_infos['mail'] == new_infos['mail'] else new_infos['mail'] - remove_alias = list(set(old_infos['mail-alias']) - set(new_infos['mail-alias'])) - remove_forward = list(set(old_infos['mail-forward']) - set(new_infos['mail-forward'])) - new_infos['mail-alias'] = list(set(new_infos['mail-alias']) - set(old_infos['mail-alias'])) - new_infos['mail-forward'] = list(set(new_infos['mail-forward']) - set(old_infos['mail-forward'])) + new_infos["mail"] = ( + None if old_infos["mail"] == new_infos["mail"] else new_infos["mail"] + ) + remove_alias = list( + set(old_infos["mail-alias"]) - set(new_infos["mail-alias"]) + ) + remove_forward = list( + set(old_infos["mail-forward"]) - set(new_infos["mail-forward"]) + ) + new_infos["mail-alias"] = list( + set(new_infos["mail-alias"]) - set(old_infos["mail-alias"]) + ) + new_infos["mail-forward"] = list( + set(new_infos["mail-forward"]) - set(old_infos["mail-forward"]) + ) remove_groups = list(set(old_infos["groups"]) - set(new_infos["groups"])) add_groups = list(set(new_infos["groups"]) - set(old_infos["groups"])) @@ -799,69 +834,90 @@ def user_import(operation_logger, csvfile, update=False, delete=False): for group, infos in existing_groups.items(): # Loop only on groups in 'remove_groups' # Ignore 'all_users' and primary group - if group in ["all_users", new_infos['username']] or group not in remove_groups: + if ( + group in ["all_users", new_infos["username"]] + or group not in remove_groups + ): continue # If the user is in this group (and it's not the primary group), # remove the member from the group - if new_infos['username'] in infos["members"]: - user_group_update(group, remove=new_infos['username'], sync_perm=False, from_import=True) + if new_infos["username"] in infos["members"]: + user_group_update( + group, + remove=new_infos["username"], + sync_perm=False, + from_import=True, + ) - user_update(new_infos['username'], - new_infos['firstname'], new_infos['lastname'], - new_infos['mail'], new_infos['password'], - mailbox_quota=new_infos['mailbox-quota'], - mail=new_infos['mail'], add_mailalias=new_infos['mail-alias'], - remove_mailalias=remove_alias, - remove_mailforward=remove_forward, - add_mailforward=new_infos['mail-forward'], from_import=True) + user_update( + new_infos["username"], + new_infos["firstname"], + new_infos["lastname"], + new_infos["mail"], + new_infos["password"], + mailbox_quota=new_infos["mailbox-quota"], + mail=new_infos["mail"], + add_mailalias=new_infos["mail-alias"], + remove_mailalias=remove_alias, + remove_mailforward=remove_forward, + add_mailforward=new_infos["mail-forward"], + from_import=True, + ) for group in add_groups: - if group in ["all_users", new_infos['username']]: + if group in ["all_users", new_infos["username"]]: continue - user_group_update(group, add=new_infos['username'], sync_perm=False, from_import=True) + user_group_update( + group, add=new_infos["username"], sync_perm=False, from_import=True + ) - users = user_list(list(FIELDS_FOR_IMPORT.keys()))['users'] + users = user_list(list(FIELDS_FOR_IMPORT.keys()))["users"] operation_logger.start() # We do delete and update before to avoid mail uniqueness issues - for user in actions['deleted']: + for user in actions["deleted"]: try: user_delete(user, purge=True, from_import=True) - result['deleted'] += 1 + result["deleted"] += 1 except YunohostError as e: on_failure(user, e) progress(f"Deleting {user}") - for user in actions['updated']: + for user in actions["updated"]: try: - update(user, users[user['username']]) - result['updated'] += 1 + update(user, users[user["username"]]) + result["updated"] += 1 except YunohostError as e: - on_failure(user['username'], e) + on_failure(user["username"], e) progress(f"Updating {user['username']}") - for user in actions['created']: + for user in actions["created"]: try: - user_create(user['username'], - user['firstname'], user['lastname'], - user['domain'], user['password'], - user['mailbox-quota'], from_import=True) + user_create( + user["username"], + user["firstname"], + user["lastname"], + user["domain"], + user["password"], + user["mailbox-quota"], + from_import=True, + ) update(user) - result['created'] += 1 + result["created"] += 1 except YunohostError as e: - on_failure(user['username'], e) + on_failure(user["username"], e) progress(f"Creating {user['username']}") permission_sync_to_user() app_ssowatconf() - if result['errors']: - msg = m18n.n('user_import_partial_failed') - if result['created'] + result['updated'] + result['deleted'] == 0: - msg = m18n.n('user_import_failed') + if result["errors"]: + msg = m18n.n("user_import_partial_failed") + if result["created"] + result["updated"] + result["deleted"] == 0: + msg = m18n.n("user_import_failed") logger.error(msg) operation_logger.error(msg) else: - logger.success(m18n.n('user_import_success')) + logger.success(m18n.n("user_import_success")) operation_logger.success() return result @@ -1038,7 +1094,7 @@ def user_group_delete(operation_logger, groupname, force=False, sync_perm=True): logger.debug(m18n.n("group_deleted", group=groupname)) -@is_unit_operation([('groupname', 'group')]) +@is_unit_operation([("groupname", "group")]) def user_group_update( operation_logger, groupname, @@ -1046,7 +1102,7 @@ def user_group_update( remove=None, force=False, sync_perm=True, - from_import=False + from_import=False, ): """ Update user informations From 60564d55c8899d51dbff7cec32e5b7107c045959 Mon Sep 17 00:00:00 2001 From: ljf Date: Thu, 2 Sep 2021 17:14:27 +0200 Subject: [PATCH 2880/3170] (enh] Config panel helpers wording --- data/helpers.d/configpanel | 139 ++++++------------------------------- data/helpers.d/utils | 98 ++++++++++++++++++++++++++ 2 files changed, 119 insertions(+), 118 deletions(-) diff --git a/data/helpers.d/configpanel b/data/helpers.d/configpanel index b55b0b0fd..7727c8d55 100644 --- a/data/helpers.d/configpanel +++ b/data/helpers.d/configpanel @@ -1,105 +1,7 @@ #!/bin/bash -# Get a value from heterogeneous file (yaml, json, php, python...) -# -# usage: ynh_value_get --file=PATH --key=KEY -# | arg: -f, --file= - the path to the file -# | arg: -k, --key= - the key to get -# -# This helpers match several var affectation use case in several languages -# We don't use jq or equivalent to keep comments and blank space in files -# This helpers work line by line, it is not able to work correctly -# if you have several identical keys in your files -# -# Example of line this helpers can managed correctly -# .yml -# title: YunoHost documentation -# email: 'yunohost@yunohost.org' -# .json -# "theme": "colib'ris", -# "port": 8102 -# "some_boolean": false, -# "user": null -# .ini -# some_boolean = On -# action = "Clear" -# port = 20 -# .php -# $user= -# user => 20 -# .py -# USER = 8102 -# user = 'https://donate.local' -# CUSTOM['user'] = 'YunoHost' -# Requires YunoHost version 4.3 or higher. -ynh_value_get() { - # Declare an array to define the options of this helper. - local legacy_args=fk - local -A args_array=( [f]=file= [k]=key= ) - local file - local key - # Manage arguments with getopts - ynh_handle_getopts_args "$@" - - local var_part='^[ \t]*\$?(\w*\[)?[ \t]*["'"']?${key}['"'"]?[ \t]*\]?[ \t]*[:=]>?[ \t]*' - - local crazy_value="$((grep -i -o -P '^[ \t]*\$?(\w*\[)?[ \t]*["'"']?${key}['"'"]?[ \t]*\]?[ \t]*[:=]>?[ \t]*\K.*(?=[ \t,\n;]*$)' ${file} || echo YNH_NULL) | head -n1)" - #" - - local first_char="${crazy_value:0:1}" - if [[ "$first_char" == '"' ]] ; then - echo "$crazy_value" | grep -m1 -o -P '"\K([^"](\\")?)*[^\\](?=")' | head -n1 | sed 's/\\"/"/g' - elif [[ "$first_char" == "'" ]] ; then - echo "$crazy_value" | grep -m1 -o -P "'\K([^'](\\\\')?)*[^\\\\](?=')" | head -n1 | sed "s/\\\\'/'/g" - else - echo "$crazy_value" - fi -} - -# Set a value into heterogeneous file (yaml, json, php, python...) -# -# usage: ynh_value_set --file=PATH --key=KEY --value=VALUE -# | arg: -f, --file= - the path to the file -# | arg: -k, --key= - the key to set -# | arg: -v, --value= - the value to set -# -# Requires YunoHost version 4.3 or higher. -ynh_value_set() { - # Declare an array to define the options of this helper. - local legacy_args=fkv - local -A args_array=( [f]=file= [k]=key= [v]=value=) - local file - local key - local value - # Manage arguments with getopts - ynh_handle_getopts_args "$@" - local var_part='[ \t]*\$?(\w*\[)?[ \t]*["'"']?${key}['"'"]?[ \t]*\]?[ \t]*[:=]>?[ \t]*' - - local crazy_value="$(grep -i -o -P '^[ \t]*\$?(\w*\[)?[ \t]*["'"']?${key}['"'"]?[ \t]*\]?[ \t]*[:=]>?[ \t]*\K.*(?=[ \t,\n;]*$)' ${file} | head -n1)" - # local crazy_value="$(grep -i -o -P "^${var_part}\K.*(?=[ \t,\n;]*\$)" ${file} | head -n1)" - local first_char="${crazy_value:0:1}" - if [[ "$first_char" == '"' ]] ; then - # \ and sed is quite complex you need 2 \\ to get one in a sed - # So we need \\\\ to go through 2 sed - value="$(echo "$value" | sed 's/"/\\\\"/g')" - sed -ri 'sø^('"${var_part}"'")([^"]|\\")*("[ \t;,]*)$ø\1'"${value}"'\4øi' ${file} - elif [[ "$first_char" == "'" ]] ; then - # \ and sed is quite complex you need 2 \\ to get one in a sed - # However double quotes implies to double \\ to - # So we need \\\\\\\\ to go through 2 sed and 1 double quotes str - value="$(echo "$value" | sed "s/'/\\\\\\\\'/g")" - sed -ri "sø^(${var_part}')([^']|\\')*('"'[ \t,;]*)$ø\1'"${value}"'\4øi' ${file} - else - if [[ "$value" == *"'"* ]] || [[ "$value" == *'"'* ]] ; then - value='\"'"$(echo "$value" | sed 's/"/\\\\"/g')"'\"' - fi - sed -ri "sø^(${var_part}).*"'$ø\1'"${value}"'øi' ${file} - fi - ynh_print_info "Configuration key '$key' edited into $file" -} - -_ynh_panel_get() { +_ynh_app_config_get() { # From settings local lines lines=`python3 << EOL @@ -165,7 +67,7 @@ EOL local source_key="$(echo "$source" | cut -d: -f1)" source_key=${source_key:-$short_setting} local source_file="$(echo "$source" | cut -d: -f2 | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" - old[$short_setting]="$(ynh_value_get --file="${source_file}" --key="${source_key}")" + old[$short_setting]="$(ynh_get_var --file="${source_file}" --key="${source_key}")" fi done @@ -173,7 +75,7 @@ EOL } -_ynh_panel_apply() { +_ynh_app_config_apply() { for short_setting in "${!old[@]}" do local setter="set__${short_setting}" @@ -222,18 +124,19 @@ _ynh_panel_apply() { local source_file="$(echo "$source" | cut -d: -f2 | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" ynh_backup_if_checksum_is_different --file="$source_file" - ynh_value_set --file="${source_file}" --key="${source_key}" --value="${!short_setting}" + ynh_set_var --file="${source_file}" --key="${source_key}" --value="${!short_setting}" ynh_store_file_checksum --file="$source_file" # We stored the info in settings in order to be able to upgrade the app ynh_app_setting_set $app $short_setting "${!short_setting}" + ynh_print_info "Configuration key '$source_key' edited into $source_file" fi fi done } -_ynh_panel_show() { +_ynh_app_config_show() { for short_setting in "${!old[@]}" do if [[ "${old[$short_setting]}" != YNH_NULL ]] ; then @@ -248,7 +151,7 @@ _ynh_panel_show() { done } -_ynh_panel_validate() { +_ynh_app_config_validate() { # Change detection ynh_script_progression --message="Checking what changed in the new configuration..." --weight=1 local is_error=true @@ -319,23 +222,23 @@ _ynh_panel_validate() { } -ynh_panel_get() { - _ynh_panel_get +ynh_app_config_get() { + _ynh_app_config_get } -ynh_panel_show() { - _ynh_panel_show +ynh_app_config_show() { + _ynh_app_config_show } -ynh_panel_validate() { - _ynh_panel_validate +ynh_app_config_validate() { + _ynh_app_config_validate } -ynh_panel_apply() { - _ynh_panel_apply +ynh_app_config_apply() { + _ynh_app_config_apply } -ynh_panel_run() { +ynh_app_config_run() { declare -Ag old=() declare -Ag changed=() declare -Ag file_hash=() @@ -345,18 +248,18 @@ ynh_panel_run() { case $1 in show) - ynh_panel_get - ynh_panel_show + ynh_app_config_get + ynh_app_config_show ;; apply) max_progression=4 ynh_script_progression --message="Reading config panel description and current configuration..." - ynh_panel_get + ynh_app_config_get - ynh_panel_validate + ynh_app_config_validate ynh_script_progression --message="Applying the new configuration..." - ynh_panel_apply + ynh_app_config_apply ynh_script_progression --message="Configuration of $app completed" --last ;; esac diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 00bec89ac..1c4f73ddf 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -473,6 +473,104 @@ ynh_replace_vars () { done } +# Get a value from heterogeneous file (yaml, json, php, python...) +# +# usage: ynh_get_var --file=PATH --key=KEY +# | arg: -f, --file= - the path to the file +# | arg: -k, --key= - the key to get +# +# This helpers match several var affectation use case in several languages +# We don't use jq or equivalent to keep comments and blank space in files +# This helpers work line by line, it is not able to work correctly +# if you have several identical keys in your files +# +# Example of line this helpers can managed correctly +# .yml +# title: YunoHost documentation +# email: 'yunohost@yunohost.org' +# .json +# "theme": "colib'ris", +# "port": 8102 +# "some_boolean": false, +# "user": null +# .ini +# some_boolean = On +# action = "Clear" +# port = 20 +# .php +# $user= +# user => 20 +# .py +# USER = 8102 +# user = 'https://donate.local' +# CUSTOM['user'] = 'YunoHost' +# Requires YunoHost version 4.3 or higher. +ynh_get_var() { + # Declare an array to define the options of this helper. + local legacy_args=fk + local -A args_array=( [f]=file= [k]=key= ) + local file + local key + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + local var_part='^[ \t]*\$?(\w*\[)?[ \t]*["'"']?${key}['"'"]?[ \t]*\]?[ \t]*[:=]>?[ \t]*' + + local crazy_value="$((grep -i -o -P '^[ \t]*\$?(\w*\[)?[ \t]*["'"']?${key}['"'"]?[ \t]*\]?[ \t]*[:=]>?[ \t]*\K.*(?=[ \t,\n;]*$)' ${file} || echo YNH_NULL) | head -n1)" + #" + + local first_char="${crazy_value:0:1}" + if [[ "$first_char" == '"' ]] ; then + echo "$crazy_value" | grep -m1 -o -P '"\K([^"](\\")?)*[^\\](?=")' | head -n1 | sed 's/\\"/"/g' + elif [[ "$first_char" == "'" ]] ; then + echo "$crazy_value" | grep -m1 -o -P "'\K([^'](\\\\')?)*[^\\\\](?=')" | head -n1 | sed "s/\\\\'/'/g" + else + echo "$crazy_value" + fi +} + +# Set a value into heterogeneous file (yaml, json, php, python...) +# +# usage: ynh_set_var --file=PATH --key=KEY --value=VALUE +# | arg: -f, --file= - the path to the file +# | arg: -k, --key= - the key to set +# | arg: -v, --value= - the value to set +# +# Requires YunoHost version 4.3 or higher. +ynh_set_var() { + # Declare an array to define the options of this helper. + local legacy_args=fkv + local -A args_array=( [f]=file= [k]=key= [v]=value=) + local file + local key + local value + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + local var_part='[ \t]*\$?(\w*\[)?[ \t]*["'"']?${key}['"'"]?[ \t]*\]?[ \t]*[:=]>?[ \t]*' + + local crazy_value="$(grep -i -o -P '^[ \t]*\$?(\w*\[)?[ \t]*["'"']?${key}['"'"]?[ \t]*\]?[ \t]*[:=]>?[ \t]*\K.*(?=[ \t,\n;]*$)' ${file} | head -n1)" + # local crazy_value="$(grep -i -o -P "^${var_part}\K.*(?=[ \t,\n;]*\$)" ${file} | head -n1)" + local first_char="${crazy_value:0:1}" + if [[ "$first_char" == '"' ]] ; then + # \ and sed is quite complex you need 2 \\ to get one in a sed + # So we need \\\\ to go through 2 sed + value="$(echo "$value" | sed 's/"/\\\\"/g')" + sed -ri 'sø^('"${var_part}"'")([^"]|\\")*("[ \t;,]*)$ø\1'"${value}"'\4øi' ${file} + elif [[ "$first_char" == "'" ]] ; then + # \ and sed is quite complex you need 2 \\ to get one in a sed + # However double quotes implies to double \\ to + # So we need \\\\\\\\ to go through 2 sed and 1 double quotes str + value="$(echo "$value" | sed "s/'/\\\\\\\\'/g")" + sed -ri "sø^(${var_part}')([^']|\\')*('"'[ \t,;]*)$ø\1'"${value}"'\4øi' ${file} + else + if [[ "$value" == *"'"* ]] || [[ "$value" == *'"'* ]] ; then + value='\"'"$(echo "$value" | sed 's/"/\\\\"/g')"'\"' + fi + sed -ri "sø^(${var_part}).*"'$ø\1'"${value}"'øi' ${file} + fi +} + + # Render templates with Jinja2 # # [internal] From ea2026a0297c75f8616373b5120cc7b28a5379f2 Mon Sep 17 00:00:00 2001 From: ljf Date: Thu, 2 Sep 2021 17:15:38 +0200 Subject: [PATCH 2881/3170] (enh] Config panel helpers wording --- data/helpers.d/{configpanel => config} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename data/helpers.d/{configpanel => config} (100%) diff --git a/data/helpers.d/configpanel b/data/helpers.d/config similarity index 100% rename from data/helpers.d/configpanel rename to data/helpers.d/config From edef077c7419782b90436098413a682bc65febb6 Mon Sep 17 00:00:00 2001 From: ljf Date: Thu, 2 Sep 2021 17:19:30 +0200 Subject: [PATCH 2882/3170] (enh] Config panel helpers wording --- 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 a4c215328..828175393 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1945,7 +1945,7 @@ def _call_config_script(operation_logger, app, action, env={}, config_panel=None source /usr/share/yunohost/helpers ynh_abort_if_errors final_path=$(ynh_app_setting_get $app final_path) -ynh_panel_run $1 +ynh_app_config_run $1 """ write_to_file(config_script, default_script) From c5885000ecac21d6b3620def3f784c0617b9f0d1 Mon Sep 17 00:00:00 2001 From: ljf Date: Thu, 2 Sep 2021 19:46:33 +0200 Subject: [PATCH 2883/3170] [enh] Better upgrade management --- data/helpers.d/backup | 15 ++++++++++++++- data/helpers.d/config | 10 ++++++++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/data/helpers.d/backup b/data/helpers.d/backup index ae746a37b..21ca2d7f0 100644 --- a/data/helpers.d/backup +++ b/data/helpers.d/backup @@ -326,12 +326,25 @@ ynh_bind_or_cp() { ynh_store_file_checksum () { # Declare an array to define the options of this helper. local legacy_args=f - local -A args_array=( [f]=file= ) + local -A args_array=( [f]=file= [u]=update_only ) local file + local update_only + update_only="${update_only:-0}" + # Manage arguments with getopts ynh_handle_getopts_args "$@" local checksum_setting_name=checksum_${file//[\/ ]/_} # Replace all '/' and ' ' by '_' + + # If update only, we don't save the new checksum if no old checksum exist + if [ $update_only -eq 1 ] ; then + local checksum_value=$(ynh_app_setting_get --app=$app --key=$checksum_setting_name) + if [ -z "${checksum_value}" ] ; then + unset backup_file_checksum + return 0 + fi + fi + ynh_app_setting_set --app=$app --key=$checksum_setting_name --value=$(md5sum "$file" | cut --delimiter=' ' --fields=1) # If backup_file_checksum isn't empty, ynh_backup_if_checksum_is_different has made a backup diff --git a/data/helpers.d/config b/data/helpers.d/config index 7727c8d55..52454ff91 100644 --- a/data/helpers.d/config +++ b/data/helpers.d/config @@ -96,10 +96,14 @@ _ynh_app_config_apply() { fi local source_file="$(echo "$source" | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" if [[ "${!short_setting}" == "" ]] ; then + ynh_backup_if_checksum_is_different --file="$source_file" rm -f "$source_file" + ynh_delete_file_checksum --file="$source_file" --update_only ynh_print_info "File '$source_file' removed" else + ynh_backup_if_checksum_is_different --file="$source_file" cp "${!short_setting}" "$source_file" + ynh_store_file_checksum --file="$source_file" --update_only ynh_print_info "File '$source_file' overwrited with ${!short_setting}" fi @@ -114,7 +118,9 @@ _ynh_app_config_apply() { ynh_die "For technical reasons, multiline text '${short_setting}' can't be stored automatically in a variable file, you have to create custom getter/setter" fi local source_file="$(echo "$source" | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" + ynh_backup_if_checksum_is_different --file="$source_file" echo "${!short_setting}" > "$source_file" + ynh_store_file_checksum --file="$source_file" --update_only ynh_print_info "File '$source_file' overwrited with the content you provieded in '${short_setting}' question" # Set value into a kind of key/value file @@ -122,10 +128,10 @@ _ynh_app_config_apply() { local source_key="$(echo "$source" | cut -d: -f1)" source_key=${source_key:-$short_setting} local source_file="$(echo "$source" | cut -d: -f2 | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" - + ynh_backup_if_checksum_is_different --file="$source_file" ynh_set_var --file="${source_file}" --key="${source_key}" --value="${!short_setting}" - ynh_store_file_checksum --file="$source_file" + ynh_store_file_checksum --file="$source_file" --update_only # We stored the info in settings in order to be able to upgrade the app ynh_app_setting_set $app $short_setting "${!short_setting}" From e0fe82f566e15fc50376869e22a593aa91446fc4 Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Thu, 2 Sep 2021 20:09:41 +0200 Subject: [PATCH 2884/3170] [fix] Some service has no test_conf 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 828175393..de6df6579 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1908,7 +1908,7 @@ def app_config_set(operation_logger, app, key=None, value=None, args=None, args_ logger.debug(f"Reloading {service}") if not _run_service_command('reload-or-restart', service): services = _get_services() - test_conf = services[service].get('test_conf') + test_conf = services[service].get('test_conf', 'true') errors = check_output(f"{test_conf}; exit 0") if test_conf else '' raise YunohostError( "app_config_failed_service_reload", From 10fddc427ed9dc8dad1b2bc7c1250e2383fb30bb Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 3 Sep 2021 02:01:16 +0200 Subject: [PATCH 2885/3170] ci: Add magic script to automagically fix i18n string format in trivial case --- .gitlab/ci/translation.gitlab-ci.yml | 11 ++++--- tests/autofix_locale_format.py | 47 ++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 5 deletions(-) create mode 100644 tests/autofix_locale_format.py diff --git a/.gitlab/ci/translation.gitlab-ci.yml b/.gitlab/ci/translation.gitlab-ci.yml index d7962436c..5e50cd20e 100644 --- a/.gitlab/ci/translation.gitlab-ci.yml +++ b/.gitlab/ci/translation.gitlab-ci.yml @@ -2,7 +2,7 @@ # TRANSLATION ######################################## -remove-stale-translated-strings: +autofix-translated-strings: stage: translation image: "before-install" needs: [] @@ -14,12 +14,13 @@ remove-stale-translated-strings: script: - cd tests # Maybe move this script location to another folder? # create a local branch that will overwrite distant one - - git checkout -b "ci-remove-stale-translated-strings-${CI_COMMIT_REF_NAME}" --no-track + - git checkout -b "ci-autofix-translated-strings-${CI_COMMIT_REF_NAME}" --no-track - python3 remove_stale_translated_strings.py + - python3 autofix_locale_format.py - '[ $(git diff -w | wc -l) != 0 ] || exit 0' # stop if there is nothing to commit - - git commit -am "[CI] Remove stale translated strings" || true - - git push -f origin "ci-remove-stale-translated-strings-${CI_COMMIT_REF_NAME}":"ci-remove-stale-translated-strings-${CI_COMMIT_REF_NAME}" - - hub pull-request -m "[CI] Remove stale translated strings" -b Yunohost:dev -p || true # GITHUB_USER and GITHUB_TOKEN registered here https://gitlab.com/yunohost/yunohost/-/settings/ci_cd + - git commit -am "[CI] Autofix translated strings" || true + - git push -f origin "ci-autofix-translated-strings-${CI_COMMIT_REF_NAME}":"ci-autofix-translated-strings-${CI_COMMIT_REF_NAME}" + - hub pull-request -m "[CI] Autofix translated strings" -b Yunohost:dev -p || true # GITHUB_USER and GITHUB_TOKEN registered here https://gitlab.com/yunohost/yunohost/-/settings/ci_cd only: variables: - $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH diff --git a/tests/autofix_locale_format.py b/tests/autofix_locale_format.py new file mode 100644 index 000000000..f777f06f1 --- /dev/null +++ b/tests/autofix_locale_format.py @@ -0,0 +1,47 @@ +import re +import json +import glob + +# List all locale files (except en.json being the ref) +locale_folder = "locales/" +locale_files = glob.glob(locale_folder + "*.json") +locale_files = [filename.split("/")[-1] for filename in locale_files] +locale_files.remove("en.json") + +reference = json.loads(open(locale_folder + "en.json").read()) + + +def fix_locale(locale_file): + + this_locale = json.loads(open(locale_folder + locale_file).read()) + fixed_stuff = False + + # We iterate over all keys/string in en.json + for key, string in reference.items(): + + # Ignore check if there's no translation yet for this key + if key not in this_locale: + continue + + # Then we check that every "{stuff}" (for python's .format()) + # should also be in the translated string, otherwise the .format + # will trigger an exception! + subkeys_in_ref = [k[0] for k in re.findall(r"{(\w+)(:\w)?}", string)] + subkeys_in_this_locale = [k[0] for k in re.findall(r"{(\w+)(:\w)?}", this_locale[key])] + + if set(subkeys_in_ref) != set(subkeys_in_this_locale) and (len(subkeys_in_ref) == len(subkeys_in_this_locale)): + for i, subkey in enumerate(subkeys_in_ref): + this_locale[key] = this_locale[key].replace('{%s}' % subkeys_in_this_locale[i], '{%s}' % subkey) + fixed_stuff = True + + if fixed_stuff: + json.dump( + this_locale, + open(locale_folder + locale_file, "w"), + indent=4, + ensure_ascii=False, + ) + + +for locale_file in locale_files: + fix_locale(locale_file) From fd0c283c536aa3bf7d0dfa91f7e4b34639c0a077 Mon Sep 17 00:00:00 2001 From: Weblate Admin Date: Thu, 2 Sep 2021 11:04:30 +0000 Subject: [PATCH 2886/3170] Translated using Weblate (French) Currently translated at 100.0% (651 of 651 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 e3a32d639..a7fe39bd9 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -291,7 +291,7 @@ "password_too_simple_3": "Le mot de passe doit comporter au moins 8 caractères et contenir des chiffres, des majuscules, des minuscules et des caractères spéciaux", "password_too_simple_4": "Le mot de passe doit comporter au moins 12 caractères et contenir des chiffres, des majuscules, des minuscules et des caractères spéciaux", "root_password_desynchronized": "Le mot de passe administrateur a été changé, mais YunoHost n’a pas pu le propager au mot de passe root !", - "aborting": "Annulation en cours.", + "aborting": "Annulation en cours (do not merge).", "app_not_upgraded": "L’application {failed_app} n’a pas été mise à jour et par conséquence les applications suivantes n’ont pas été mises à jour : {apps}", "app_start_install": "Installation de {app}...", "app_start_remove": "Suppression de {app}...", From ab388dc167c1c5002890f6f5424963e98a2d51b8 Mon Sep 17 00:00:00 2001 From: Weblate Admin Date: Thu, 2 Sep 2021 11:05:08 +0000 Subject: [PATCH 2887/3170] Translated using Weblate (French) Currently translated at 100.0% (651 of 651 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 a7fe39bd9..e3a32d639 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -291,7 +291,7 @@ "password_too_simple_3": "Le mot de passe doit comporter au moins 8 caractères et contenir des chiffres, des majuscules, des minuscules et des caractères spéciaux", "password_too_simple_4": "Le mot de passe doit comporter au moins 12 caractères et contenir des chiffres, des majuscules, des minuscules et des caractères spéciaux", "root_password_desynchronized": "Le mot de passe administrateur a été changé, mais YunoHost n’a pas pu le propager au mot de passe root !", - "aborting": "Annulation en cours (do not merge).", + "aborting": "Annulation en cours.", "app_not_upgraded": "L’application {failed_app} n’a pas été mise à jour et par conséquence les applications suivantes n’ont pas été mises à jour : {apps}", "app_start_install": "Installation de {app}...", "app_start_remove": "Suppression de {app}...", From 6e1018fd4e6342f18cccce2a18d47ccfd5c812f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= Date: Thu, 2 Sep 2021 14:14:24 +0000 Subject: [PATCH 2888/3170] Translated using Weblate (French) Currently translated at 100.0% (659 of 659 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index e3a32d639..a20a62db8 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -142,7 +142,7 @@ "user_creation_failed": "Impossible de créer l’utilisateur {user} : {error}", "user_deleted": "L’utilisateur a été supprimé", "user_deletion_failed": "Impossible de supprimer l’utilisateur {user} : {error}", - "user_home_creation_failed": "Impossible de créer le dossier personnel de l’utilisateur", + "user_home_creation_failed": "Impossible de créer le dossier personnel '{home}' de l’utilisateur", "user_unknown": "L’utilisateur {user} est inconnu", "user_update_failed": "Impossible de mettre à jour l’utilisateur {user} : {error}", "user_updated": "L’utilisateur a été modifié", @@ -170,7 +170,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})", - "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_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`.", "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).", "app_already_installed_cant_change_url": "Cette application est déjà installée. L’URL ne peut pas être changé simplement par cette fonction. Vérifiez si cela est disponible avec `app changeurl`.", "app_change_url_failed_nginx_reload": "Le redémarrage de NGINX a échoué. Voici la sortie de 'nginx -t' :\n{nginx_errors}", @@ -649,5 +649,13 @@ "diagnosis_apps_not_in_app_catalog": "Cette application est absente ou ne figure plus dans le catalogue d'applications de YunoHost. Vous devriez envisager de la désinstaller car elle ne recevra pas de mise à jour et pourrait compromettre l'intégrité et la sécurité de votre système.", "diagnosis_apps_issue": "Un problème a été détecté pour l'application {app}", "diagnosis_apps_allgood": "Toutes les applications installées respectent les pratiques de packaging de base", - "diagnosis_description_apps": "Applications" + "diagnosis_description_apps": "Applications", + "user_import_success": "Utilisateurs importés avec succès", + "user_import_nothing_to_do": "Aucun utilisateur n'a besoin d'être importé", + "user_import_failed": "L'opération d'importation des utilisateurs a totalement échoué", + "user_import_partial_failed": "L'opération d'importation des utilisateurs a partiellement échoué", + "user_import_missing_columns": "Les colonnes suivantes sont manquantes : {columns}", + "user_import_bad_file": "Votre fichier CSV n'est pas correctement formaté, il sera ignoré afin d'éviter une potentielle perte de données", + "user_import_bad_line": "Ligne incorrecte {line} : {details}", + "log_user_import": "Importer des utilisateurs" } From bf7b51de18b6af3980ccd37a3d54237d518532c5 Mon Sep 17 00:00:00 2001 From: Tymofii-Lytvynenko Date: Thu, 2 Sep 2021 21:52:46 +0000 Subject: [PATCH 2889/3170] Translated using Weblate (Ukrainian) Currently translated at 30.3% (200 of 659 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/uk/ --- locales/uk.json | 222 ++++++++++++++++++++++++------------------------ 1 file changed, 111 insertions(+), 111 deletions(-) diff --git a/locales/uk.json b/locales/uk.json index a9b807981..47c5991b0 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -55,7 +55,7 @@ "service_description_postfix": "Використовується для відправки та отримання електронної пошти", "service_description_php7.3-fpm": "Запускає програми, написані на мові програмування PHP, за допомогою NGINX", "service_description_nginx": "Обслуговує або надає доступ до всіх веб-сайтів, розміщених на вашому сервері", - "service_description_mysql": "Зберігає дані додатків (база даних SQL)", + "service_description_mysql": "Зберігає дані застосунків (база даних SQL)", "service_description_metronome": "Служба захисту миттєвого обміну повідомленнями XMPP", "service_description_fail2ban": "Захист від перебору та інших видів атак з Інтернету", "service_description_dovecot": "Дозволяє поштовим клієнтам отримувати доступ до електронної пошти (через IMAP і POP3)", @@ -87,7 +87,7 @@ "restore_cleaning_failed": "Не вдалося очистити тимчасовий каталог відновлення", "restore_backup_too_old": "Цей архів резервних копій не може бути відновлений, бо він отриманий з дуже старою версією YunoHost.", "restore_already_installed_apps": "Наступні програми не можуть бути відновлені, тому що вони вже встановлені: {apps}", - "restore_already_installed_app": "Додаток з ідентифікатором \"{app} 'вже встановлено", + "restore_already_installed_app": "Застосунок з ідентифікатором \"{app} 'вже встановлено", "regex_with_only_domain": "Ви не можете використовувати regex для домену, тільки для шляху.", "regex_incompatible_with_tile": "/! \\ Packagers! Дозвіл '{permission}' має значення show_tile 'true', тому ви не можете визначити regex URL в якості основного URL.", "regenconf_need_to_explicitly_specify_ssh": "Конфігурація ssh була змінена вручну, але вам потрібно явно вказати категорію 'ssh' з --force, щоб застосувати зміни.", @@ -117,7 +117,7 @@ "permission_deletion_failed": "Не вдалося видалити дозвіл '{permission}': {error}", "permission_deleted": "Дозвіл '{permission}' видалено", "permission_cant_add_to_all_users": "Дозвіл {permission} не може бути додано всім користувачам.", - "permission_currently_allowed_for_all_users": "В даний час цей дозвіл надається всім користувачам на додаток до інших груп. Ймовірно, вам потрібно або видалити дозвіл 'all_users', або видалити інші групи, яким воно зараз надано.", + "permission_currently_allowed_for_all_users": "Наразі цей дозвіл надається всім користувачам на додачу до інших груп. Імовірно, вам потрібно або видалити дозвіл 'all_users', або видалити інші групи, яким його зараз надано.", "permission_creation_failed": "Не вдалося створити дозвіл '{permission}': {error}", "permission_created": "Дозвіл '{permission}' створено", "permission_cannot_remove_main": "Видалення основного дозволу заборонено", @@ -175,8 +175,8 @@ "migration_0015_cleaning_up": "Очищення кеш-пам'яті і пакетів, які більше не потрібні...", "migration_0015_specific_upgrade": "Початок поновлення системних пакетів, які повинні бути оновлені незалежно...", "migration_0015_modified_files": "Зверніть увагу, що такі файли були змінені вручну і можуть бути перезаписані після поновлення: {manually_modified_files}.", - "migration_0015_problematic_apps_warning": "Зверніть увагу, що були виявлені наступні, можливо, проблемні встановлені додатки. Схоже, що вони не були встановлені з каталогу додатків YunoHost або не зазначені як \"робочі\". Отже, не можна гарантувати, що вони будуть працювати після оновлення: {problematic_apps}.", - "migration_0015_general_warning": "Будь ласка, зверніть увагу, що ця міграція є делікатною операцією. Команда YunoHost зробила все можливе, щоб перевірити і протестувати її, але міграція все ще може порушити частина системи або її додатків. Тому рекомендується: - Виконати резервне копіювання всіх важливих даних або додатків. Більш детальна інформація на сайті https://yunohost.org/backup; - Наберіться терпіння після запуску міграції: В залежності від вашого підключення до Інтернету і апаратного забезпечення, оновлення може зайняти до декількох годин.", + "migration_0015_problematic_apps_warning": "Зверніть увагу, що були виявлені наступні, можливо, проблемні встановлені додатки. Схоже, що вони не були встановлені з каталогу застосунків YunoHost або не зазначені як \"робочі\". Отже, не можна гарантувати, що вони будуть працювати після оновлення: {problematic_apps}.", + "migration_0015_general_warning": "Будь ласка, зверніть увагу, що ця міграція є делікатною операцією. Команда YunoHost зробила все можливе, щоб перевірити і протестувати її, але міграція все ще може порушити частина системи або її застосунків. Тому рекомендується: - Виконати резервне копіювання всіх важливих даних або застосунків. Більш детальна інформація на сайті https://yunohost.org/backup; - Наберіться терпіння після запуску міграції: В залежності від вашого підключення до Інтернету і апаратного забезпечення, оновлення може зайняти до декількох годин.", "migration_0015_system_not_fully_up_to_date": "Ваша система не повністю оновлена. Будь ласка, виконайте регулярне оновлення перед запуском міграції на Buster.", "migration_0015_not_enough_free_space": "Вільного місця в/var/досить мало! У вас повинно бути не менше 1 ГБ вільного місця, щоб запустити цю міграцію.", "migration_0015_not_stretch": "Поточний дистрибутив Debian не є Stretch!", @@ -189,9 +189,9 @@ "migration_ldap_rollback_success": "Система відкотилася.", "migration_ldap_migration_failed_trying_to_rollback": "Не вдалося виконати міграцію... спроба відкату системи.", "migration_ldap_can_not_backup_before_migration": "Не вдалося завершити резервне копіювання системи перед невдалої міграцією. Помилка: {error}", - "migration_ldap_backup_before_migration": "Створення резервної копії бази даних LDAP і установки додатків перед фактичної міграцією.", + "migration_ldap_backup_before_migration": "Створення резервної копії бази даних LDAP і установки застосунків перед фактичної міграцією.", "migration_description_0020_ssh_sftp_permissions": "Додайте підтримку дозволів SSH і SFTP", - "migration_description_0019_extend_permissions_features": "Розширення/переробка системи управління дозволами додатків", + "migration_description_0019_extend_permissions_features": "Розширення/переробка системи управління дозволами застосунків", "migration_description_0018_xtable_to_nftable": "Перенесення старих правил мережевого трафіку в нову систему nftable", "migration_description_0017_postgresql_9p6_to_11": "Перенесення баз даних з PostgreSQL 9.6 на 11", "migration_description_0016_php70_to_php73_pools": "Перенесіть php7.0-fpm 'pool' conf файли на php7.3", @@ -240,9 +240,9 @@ "log_app_config_show_panel": "Показати панель конфігурації програми \"{} '", "log_app_action_run": "Активації дії додатка \"{} '", "log_app_makedefault": "Зробити '{}' додатком за замовчуванням", - "log_app_upgrade": "Оновити додаток '{}'", + "log_app_upgrade": "Оновити застосунок '{}'", "log_app_remove": "Для видалення програми '{}'", - "log_app_install": "Встановіть додаток '{}'", + "log_app_install": "Встановіть застосунок '{}'", "log_app_change_url": "Змініть URL-адресу додатка \"{} '", "log_operation_unit_unclosed_properly": "Блок операцій не був закритий належним чином", "log_does_exists": "Немає журналу операцій з ім'ям '{log}', використовуйте 'yunohost log list', щоб подивитися всі публічні журнали операцій", @@ -331,7 +331,7 @@ "domain_unknown": "невідомий домен", "domain_name_unknown": "Домен '{domain}' невідомий", "domain_uninstall_app_first": "Ці додатки все ще встановлені на вашому домені: {apps} ласка, видаліть їх за допомогою 'yunohost app remove the_app_id' або перемістити їх на інший домен за допомогою 'yunohost app change-url the_app_id', перш ніж приступити до видалення домену.", - "domain_remove_confirm_apps_removal": "Видалення цього домену призведе до видалення цих додатків: {apps} Ви впевнені, що хочете це зробити? [{answers}].", + "domain_remove_confirm_apps_removal": "Видалення цього домену призведе до видалення цих застосунків: {apps} Ви впевнені, що хочете це зробити? [{answers}].", "domain_hostname_failed": "Неможливо встановити нове ім'я хоста. Це може викликати проблеми в подальшому (можливо, все буде в порядку).", "domain_exists": "Домен вже існує", "domain_dyndns_root_unknown": "Невідомий кореневої домен DynDNS", @@ -485,157 +485,157 @@ "diagnosis_domain_expiration_warning": "Термін дії деяких доменів закінчиться найближчим часом!", "diagnosis_domain_expiration_success": "Ваші домени зареєстровані і не збираються спливати найближчим часом.", "diagnosis_domain_expiration_not_found_details": "Інформація WHOIS для домену {domain} не містить інформації про термін дії?", - "diagnosis_domain_not_found_details": "Домен {domain} не існує в базі даних WHOIS або термін його дії закінчився!", - "diagnosis_domain_expiration_not_found": "Неможливо перевірити термін дії деяких доменів", + "diagnosis_domain_not_found_details": "Домен {domain} не існує в базі даних WHOIS або строк його дії сплив!", + "diagnosis_domain_expiration_not_found": "Неможливо перевірити строк дії деяких доменів", "diagnosis_dns_specialusedomain": "Домен {domain} заснований на домені верхнього рівня спеціального призначення (TLD) і тому не очікується, що у нього будуть актуальні записи DNS.", - "diagnosis_dns_try_dyndns_update_force": "Конфігурація DNS цього домену повинна автоматично управлятися YunoHost. Якщо це не так, ви можете спробувати примусово оновити її за допомогою команди yunohost dyndns update --force .", - "diagnosis_dns_point_to_doc": "Якщо вам потрібна допомога з налаштування DNS-записів, зверніться до документації на сайті https://yunohost.org/dns_config .", - "diagnosis_dns_discrepancy": "Наступний запис DNS, схоже, не відповідає рекомендованої конфігурації:
Type: {type}
Name: {name}
Поточне значення: {current}
Очікуване значення: {value} ", - "diagnosis_dns_missing_record": "Згідно рекомендованої конфігурації DNS, ви повинні додати запис DNS з наступною інформацією.
Тип: {type}
Name: {name}
Value: < code> {value} .", - "diagnosis_dns_bad_conf": "Деякі DNS-записи відсутні або невірні для домену {domain} (категорія {category})", + "diagnosis_dns_try_dyndns_update_force": "Конфігурація DNS цього домену повинна автоматично управлятися YunoHost. Якщо це не так, ви можете спробувати примусово оновити її за допомогою команди yunohost dyndns update --force.", + "diagnosis_dns_point_to_doc": "Якщо вам потрібна допомога з налаштування DNS-записів, зверніться до документації на сайті https://yunohost.org/dns_config.", + "diagnosis_dns_discrepancy": "Наступний запис DNS, схоже, не відповідає рекомендованій конфігурації:
Тип: {type}
Назва: {name}
Поточне значення: {current}
Очікуване значення: {value}", + "diagnosis_dns_missing_record": "Згідно рекомендованої конфігурації DNS, ви повинні додати запис DNS з наступними відомостями.\n
Тип: {type}\n
Назва: {name}\n
Значення: {value}", + "diagnosis_dns_bad_conf": "Деякі DNS-записи відсутні або неправильні для домену {domain} (категорія {category})", "diagnosis_dns_good_conf": "DNS-записи правильно налаштовані для домену {domain} (категорія {category})", - "diagnosis_ip_weird_resolvconf_details": "Файл /etc/resolv.conf повинен бути симлінк на /etc/resolvconf/run/resolv.conf , що вказує на 127.0.0.1 (dnsmasq ). Якщо ви хочете вручну налаштувати DNS Резолвер, відредагуйте /etc/resolv.dnsmasq.conf .", - "diagnosis_ip_weird_resolvconf": "Дозвіл DNS, схоже, працює, але схоже, що ви використовуєте для користувача /etc/resolv.conf .", - "diagnosis_ip_broken_resolvconf": "Схоже, що дозвіл доменних імен на вашому сервері порушено, що пов'язано з тим, що /etc/resolv.conf не вказує на 127.0.0.1 .", - "diagnosis_ip_broken_dnsresolution": "Дозвіл доменних імен, схоже, з якоїсь причини не працює... Брандмауер блокує DNS-запити?", - "diagnosis_ip_dnsresolution_working": "Дозвіл доменних імен працює!", - "diagnosis_ip_not_connected_at_all": "Здається, що сервер взагалі не підключений до Інтернету !?", - "diagnosis_ip_local": "Локальний IP: {local} .", - "diagnosis_ip_global": "Глобальний IP: {global} ", - "diagnosis_ip_no_ipv6_tip": "Наявність працюючого IPv6 не є обов'язковим для роботи вашого сервера, але це краще для здоров'я Інтернету в цілому. IPv6 зазвичай автоматично налаштовується системою або вашим провайдером, якщо він доступний. В іншому випадку вам, можливо, доведеться налаштувати деякі речі вручну, як пояснюється в документації тут: https://yunohost.org/#/ipv6. Якщо ви не можете включити IPv6 або якщо це здається вам занадто технічним, ви також можете сміливо ігнорувати це попередження.", - "diagnosis_ip_no_ipv6": "Сервер не має працюючого IPv6.", - "diagnosis_ip_connected_ipv6": "Сервер підключений до Інтернету через IPv6!", - "diagnosis_ip_no_ipv4": "Сервер не має працюючого IPv4.", - "diagnosis_ip_connected_ipv4": "Сервер підключений до Інтернету через IPv4!", - "diagnosis_no_cache": "Для категорії \"{category} 'ще немає кеша діагнозів.", - "diagnosis_failed": "Не вдалося результат діагностики для категорії '{category}': {error}", - "diagnosis_everything_ok": "Все виглядає добре для {category}!", + "diagnosis_ip_weird_resolvconf_details": "Файл /etc/resolv.conf повинен бути символічним посиланням на /etc/resolvconf/run/resolv.conf, що вказує на 127.0.0.1(dnsmasq). Якщо ви хочете вручну налаштувати DNS вирішувачі (resolvers), відредагуйте /etc/resolv.dnsmasq.conf.", + "diagnosis_ip_weird_resolvconf": "Роздільність DNS, схоже, працює, але схоже, що ви використовуєте користувацьку /etc/resolv.conf.", + "diagnosis_ip_broken_resolvconf": "Схоже, що роздільність доменних імен на вашому сервері порушено, що пов'язано з тим, що /etc/resolv.conf не вказує на 127.0.0.1.", + "diagnosis_ip_broken_dnsresolution": "Роздільність доменних імен, схоже, з якоїсь причини не працює... Фаєрвол блокує DNS-запити?", + "diagnosis_ip_dnsresolution_working": "Роздільність доменних імен працює!", + "diagnosis_ip_not_connected_at_all": "Здається, сервер взагалі не під'єднаний до Інтернету!?", + "diagnosis_ip_local": "Локальний IP: {local}.", + "diagnosis_ip_global": "Глобальний IP: {global}", + "diagnosis_ip_no_ipv6_tip": "Наявність робочого IPv6 не є обов'язковим для роботи вашого сервера, але це краще для здоров'я Інтернету в цілому. IPv6 зазвичай автоматично налаштовується системою або вашим провайдером, якщо він доступний. В іншому випадку вам, можливо, доведеться налаштувати деякі речі вручну, як пояснюється в документації тут: https://yunohost.org/#/ipv6. Якщо ви не можете увімкнути IPv6 або якщо це здається вам занадто технічним, ви також можете сміливо нехтувати цим попередженням.", + "diagnosis_ip_no_ipv6": "Сервер не має робочого IPv6.", + "diagnosis_ip_connected_ipv6": "Сервер під'єднаний до Інтернету через IPv6!", + "diagnosis_ip_no_ipv4": "Сервер не має робочого IPv4.", + "diagnosis_ip_connected_ipv4": "Сервер під'єднаний до Інтернету через IPv4!", + "diagnosis_no_cache": "Для категорії \"{category} 'ще немає кеша діагностики.", + "diagnosis_failed": "Не вдалося отримати результат діагностики для категорії '{category}': {error}", + "diagnosis_everything_ok": "Усе виглядає добре для {category}!", "diagnosis_found_warnings": "Знайдено {warnings} пунктів, які можна поліпшити для {category}.", "diagnosis_found_errors_and_warnings": "Знайдено {errors} істотний (і) питання (и) (і {warnings} попередження (я)), що відносяться до {category}!", "diagnosis_found_errors": "Знайдена {errors} важлива проблема (і), пов'язана з {category}!", - "diagnosis_ignored_issues": "(+ {nb_ignored} проігнорована проблема (проблеми))", + "diagnosis_ignored_issues": "(+ {nb_ignored} знехтувана проблема (проблеми))", "diagnosis_cant_run_because_of_dep": "Неможливо запустити діагностику для {category}, поки є важливі проблеми, пов'язані з {dep}.", "diagnosis_cache_still_valid": "(Кеш все ще дійсний для діагностики {category}. Повторна діагностика поки не проводиться!)", "diagnosis_failed_for_category": "Не вдалося провести діагностику для категорії '{category}': {error}", - "diagnosis_display_tip": "Щоб побачити знайдені проблеми, ви можете перейти в розділ Diagnosis в веб-адміном або виконати команду 'yunohost diagnosis show --issues --human-readable' з командного рядка.", - "diagnosis_package_installed_from_sury_details": "Деякі пакети були ненавмисно встановлені з стороннього сховища під назвою Sury. Команда YunoHost поліпшила стратегію роботи з цими пакетами, але очікується, що в деяких системах, які встановили додатки PHP7.3 ще на Stretch, залишаться деякі невідповідності. Щоб виправити цю ситуацію, спробуйте виконати наступну команду: {cmd_to_fix} .", - "diagnosis_package_installed_from_sury": "Деякі системні пакети повинні бути знижені в статусі", - "diagnosis_backports_in_sources_list": "Схоже, що apt (менеджер пакетів) налаштований на використання сховища backports. Якщо ви не знаєте, що робите, ми настійно не рекомендуємо встановлювати пакети з backports, тому що це може привести до нестабільності або конфліктів у вашій системі.", - "diagnosis_basesystem_ynh_inconsistent_versions": "Ви використовуєте несумісні версії пакетів YunoHost... швидше за все, через невдалий або часткового оновлення.", + "diagnosis_display_tip": "Щоб побачити знайдені проблеми, ви можете перейти в розділ Діагностика в вебадміністраторі або виконати команду 'yunohost diagnosis show --issues --human-readable' з командного рядка.", + "diagnosis_package_installed_from_sury_details": "Деякі пакети були ненавмисно встановлені зі стороннього репозиторію під назвою Sury. Команда YunoHost поліпшила стратегію роботи з цими пакетами, але очікується, що в деяких системах, які встановили застосунки PHP7.3 ще на Stretch, залишаться деякі невідповідності. Щоб виправити це становище, спробуйте виконати наступну команду: {cmd_to_fix}.", + "diagnosis_package_installed_from_sury": "Деякі системні пакети мають бути зістарені у версії", + "diagnosis_backports_in_sources_list": "Схоже, що apt (менеджер пакетів) налаштований на використання репозиторія backports. Якщо ви не знаєте, що робите, ми наполегливо не радимо встановлювати пакети з backports, тому що це може привести до нестабільності або конфліктів у вашій системі.", + "diagnosis_basesystem_ynh_inconsistent_versions": "Ви використовуєте несумісні версії пакетів YunoHost... швидше за все, через невдале або часткове оновлення.", "diagnosis_basesystem_ynh_main_version": "Сервер працює під управлінням YunoHost {main_version} ({repo})", "diagnosis_basesystem_ynh_single_version": "{package} версія: {version} ({repo})", "diagnosis_basesystem_kernel": "Сервер працює під управлінням ядра Linux {kernel_version}", "diagnosis_basesystem_host": "Сервер працює під управлінням Debian {debian_version}", "diagnosis_basesystem_hardware_model": "Модель сервера - {model}", "diagnosis_basesystem_hardware": "Архітектура апаратного забезпечення сервера - {virt} {arch}", - "custom_app_url_required": "Ви повинні надати URL для оновлення вашого призначеного для користувача додатки {app}.", - "confirm_app_install_thirdparty": "НЕБЕЗПЕЧНО! Ця програма не входить в каталог додатків YunoHost. Установлення сторонніх додатків може порушити цілісність і безпеку вашої системи. Вам не слід встановлювати його, якщо ви не знаєте, що робите. НІЯКОЇ ПІДТРИМКИ НЕ БУДЕ, якщо цей додаток не буде працювати або зламає вашу систему... Якщо ви все одно готові піти на такий ризик, введіть '{answers}'.", - "confirm_app_install_danger": "НЕБЕЗПЕЧНО! Відомо, що це додаток все ще експериментальне (якщо не сказати, що воно явно не працює)! Вам не слід встановлювати його, якщо ви не знаєте, що робите. Ніякої підтримки не буде надано, якщо цей додаток не буде працювати або зламає вашу систему... Якщо ви все одно готові ризикнути, введіть '{answers}'.", - "confirm_app_install_warning": "Попередження: Ця програма може працювати, але не дуже добре інтегровано в YunoHost. Деякі функції, такі як єдина реєстрація та резервне копіювання/відновлення, можуть бути недоступні. Все одно встановити? [{answers}]. ", - "certmanager_unable_to_parse_self_CA_name": "Не вдалося розібрати ім'я самоподпісивающегося центру (файл: {file})", - "certmanager_self_ca_conf_file_not_found": "Не вдалося знайти файл конфігурації для самоподпісивающегося центру (файл: {file})", + "custom_app_url_required": "Ви повинні надати URL-адресу для оновлення вашого користувацького застосунку {app}.", + "confirm_app_install_thirdparty": "НЕБЕЗПЕЧНО! Цей застосунок не входить в каталог застосунків YunoHost. Установлення сторонніх застосунків може порушити цілісність і безпеку вашої системи. Вам не слід встановлювати його, якщо ви не знаєте, що робите. НІЯКОЇ ПІДТРИМКИ НЕ БУДЕ, якщо цей застосунок не буде працювати або зламає вашу систему... Якщо ви все одно готові піти на такий ризик, введіть '{answers}'.", + "confirm_app_install_danger": "НЕБЕЗПЕЧНО! Відомо, що цей застосунок все ще експериментальний (якщо не сказати, що він явно не працює)! Вам не слід встановлювати його, якщо ви не знаєте, що робите. Ніякої підтримки не буде надано, якщо цей застосунок не буде працювати або зламає вашу систему... Якщо ви все одно готові ризикнути, введіть '{answers}'.", + "confirm_app_install_warning": "Попередження: Цей застосунок може працювати, але він не дуже добре інтегрований в YunoHost. Деякі функції, такі як єдина реєстрація та резервне копіювання/відновлення, можуть бути недоступні. Все одно встановити? [{answers}]. ", + "certmanager_unable_to_parse_self_CA_name": "Не вдалося розібрати назву самопідписного центру (файл: {file})", + "certmanager_self_ca_conf_file_not_found": "Не вдалося знайти файл конфігурації для самопідписного центру (файл: {file})", "certmanager_no_cert_file": "Не вдалося розпізнати файл сертифіката для домену {domain} (файл: {file})", - "certmanager_hit_rate_limit": "Для цього набору доменів {domain} недавно було випущено дуже багато сертифікатів. Будь ласка, спробуйте ще раз пізніше. Див. Https://letsencrypt.org/docs/rate-limits/ для отримання більш докладної інформації.", - "certmanager_warning_subdomain_dns_record": "Піддомен '{subdomain} \"не дозволяється на той же IP-адресу, що і' {domain} '. Деякі функції будуть недоступні, поки ви не виправите це і не перегенеріруете сертифікат.", - "certmanager_domain_http_not_working": "Домен {domain}, схоже, не доступний через HTTP. Будь ласка, перевірте категорію 'Web' в діагностиці для отримання додаткової інформації. (Якщо ви знаєте, що робите, використовуйте '--no-checks', щоб відключити ці перевірки).", - "certmanager_domain_dns_ip_differs_from_public_ip": "DNS-записи для домену '{domain}' відрізняються від IP цього сервера. Будь ласка, перевірте категорію 'DNS-записи' (основні) в діагностиці для отримання додаткової інформації. Якщо ви недавно змінили запис A, будь ласка, зачекайте, поки вона пошириться (деякі програми перевірки поширення DNS доступні в Інтернеті). (Якщо ви знаєте, що робите, використовуйте '--no-checks', щоб відключити ці перевірки).", - "certmanager_domain_cert_not_selfsigned": "Сертифікат для домену {domain} не є самоподпісанного. Ви впевнені, що хочете замінити його? (Для цього використовуйте '--force').", - "certmanager_domain_not_diagnosed_yet": "Поки немає результатів діагностики для домену {domain}. Будь ласка, повторно проведіть діагностику для категорій 'DNS-записи' і 'Web' в розділі діагностики, щоб перевірити, чи готовий домен до Let's Encrypt. (Або, якщо ви знаєте, що робите, використовуйте '--no-checks', щоб відключити ці перевірки).", + "certmanager_hit_rate_limit": "Для цього набору доменів {domain} недавно було випущено дуже багато сертифікатів. Будь ласка, спробуйте ще раз пізніше. Див. https://letsencrypt.org/docs/rate-limits/ для отримання подробиць.", + "certmanager_warning_subdomain_dns_record": "Піддомен '{subdomain}' не дозволяється на тій же IP-адресі, що і '{domain}'. Деякі функції будуть недоступні, поки ви не виправите це і не перестворите сертифікат.", + "certmanager_domain_http_not_working": "Домен {domain}, схоже, не доступний через HTTP. Будь ласка, перевірте категорію 'Мережа' в діагностиці для отримання додаткових даних. (Якщо ви знаєте, що робите, використовуйте '--no-checks', щоб вимкнути ці перевірки).", + "certmanager_domain_dns_ip_differs_from_public_ip": "DNS-записи для домену '{domain}' відрізняються від IP цього сервера. Будь ласка, перевірте категорію 'DNS-записи' (основні) в діагностиці для отримання додаткових даних. Якщо ви недавно змінили запис A, будь ласка, зачекайте, поки він пошириться (деякі програми перевірки поширення DNS доступні в Інтернеті). (Якщо ви знаєте, що робите, використовуйте '--no-checks', щоб вимкнути ці перевірки).", + "certmanager_domain_cert_not_selfsigned": "Сертифікат для домену {domain} не є самопідписаним. Ви впевнені, що хочете замінити його? (Для цього використовуйте '--force').", + "certmanager_domain_not_diagnosed_yet": "Поки немає результатів діагностики для домену {domain}. Будь ласка, повторно проведіть діагностику для категорій 'DNS-записи' і 'Мережа' в розділі діагностики, щоб перевірити, чи готовий домен до Let's Encrypt. (Або, якщо ви знаєте, що робите, використовуйте '--no-checks', щоб вимкнути ці перевірки).", "certmanager_certificate_fetching_or_enabling_failed": "Спроба використовувати новий сертифікат для {domain} не спрацювала...", "certmanager_cert_signing_failed": "Не вдалося підписати новий сертифікат", "certmanager_cert_renew_success": "Сертифікат Let's Encrypt оновлений для домену '{domain}'", - "certmanager_cert_install_success_selfsigned": "Самоподпісанний сертифікат тепер встановлений для домену '{domain}'", - "certmanager_cert_install_success": "Сертифікат Let's Encrypt тепер встановлений для домену '{domain}'", - "certmanager_cannot_read_cert": "Щось не так сталося при спробі відкрити поточний сертифікат для домену {domain} (файл: {file}), причина: {reason}", - "certmanager_attempt_to_replace_valid_cert": "Ви намагаєтеся перезаписати хороший і дійсний сертифікат для домену {domain}! (Використовуйте --force для обходу)", - "certmanager_attempt_to_renew_valid_cert": "Термін дії сертифіката для домену '{domain} \"не закінчується! (Ви можете використовувати --force, якщо знаєте, що робите)", + "certmanager_cert_install_success_selfsigned": "Самопідписаний сертифікат тепер встановлений для домену '{domain}'", + "certmanager_cert_install_success": "Сертифікат Let's Encrypt тепер встановлений для домена '{domain}'", + "certmanager_cannot_read_cert": "Щось не так сталося при спробі відкрити поточний сертифікат для домена {domain} (файл: {file}), причина: {reason}", + "certmanager_attempt_to_replace_valid_cert": "Ви намагаєтеся перезаписати хороший дійсний сертифікат для домену {domain}! (Використовуйте --force для обходу)", + "certmanager_attempt_to_renew_valid_cert": "Строк дії сертифіката для домена '{domain}' не закінчується! (Ви можете використовувати --force, якщо знаєте, що робите)", "certmanager_attempt_to_renew_nonLE_cert": "Сертифікат для домену '{domain}' не випущено Let's Encrypt. Неможливо продовжити його автоматично!", - "certmanager_acme_not_configured_for_domain": "Завдання ACME не може бути запущена для {domain} прямо зараз, тому що в його nginx conf відсутній відповідний фрагмент коду... Будь ласка, переконайтеся, що конфігурація nginx оновлена за допомогою `yunohost tools regen-conf nginx --dry-run - with-diff`.", - "backup_with_no_restore_script_for_app": "{app} не має скрипта відновлення, ви не зможете автоматично відновити резервну копію цього додатка.", - "backup_with_no_backup_script_for_app": "Додаток '{app}' не має скрипта резервного копіювання. Ігнорування.", - "backup_unable_to_organize_files": "Неможливо використовувати швидкий метод для організації файлів в архіві", + "certmanager_acme_not_configured_for_domain": "Завдання ACME не може бути запущене для {domain} прямо зараз, тому що в його nginx-конфігурації відсутній відповідний фрагмент коду... Будь ласка, переконайтеся, що конфігурація nginx оновлена за допомогою `yunohost tools regen-conf nginx --dry-run --with-diff`.", + "backup_with_no_restore_script_for_app": "{app} не має скрипта відновлення, ви не зможете автоматично відновити резервну копію цього застосунку.", + "backup_with_no_backup_script_for_app": "Застосунок '{app}' не має скрипта резервного копіювання. Нехтую ним.", + "backup_unable_to_organize_files": "Неможливо використовувати швидкий спосіб для організації файлів в архіві", "backup_system_part_failed": "Не вдалося створити резервну копію системної частини '{part}'.", - "backup_running_hooks": "Запуск гачків резервного копіювання...", + "backup_running_hooks": "Запуск гачків (hook) резервного копіювання...", "backup_permission": "Дозвіл на резервне копіювання для {app}", - "backup_output_symlink_dir_broken": "Ваш архівний каталог '{path}' є непрацюючою симлінк. Можливо, ви забули перемонтувати або підключити носій, на який вона вказує.", + "backup_output_symlink_dir_broken": "Ваш архівний каталог '{path}' є неробочим символічним посиланням. Можливо, ви забули перемонтувати або підключити носій, на який вона вказує.", "backup_output_directory_required": "Ви повинні вказати вихідний каталог для резервного копіювання", "backup_output_directory_not_empty": "Ви повинні вибрати порожній вихідний каталог", - "backup_output_directory_forbidden": "Виберіть інший вихідний каталог. Резервні копії не можуть бути створені в підкаталогах/bin,/boot,/dev,/etc,/lib,/root,/run,/sbin,/sys,/usr,/var або /home/yunohost.backup/archives.", - "backup_nothings_done": "нічого зберігати", + "backup_output_directory_forbidden": "Виберіть інший вихідний каталог. Резервні копії не можуть бути створені в підкаталогах /bin,/boot,/dev,/etc,/lib,/root,/run,/sbin,/sys,/usr,/var або /home/yunohost.backup/archives.", + "backup_nothings_done": "Нема що зберігати", "backup_no_uncompress_archive_dir": "Немає такого каталогу нестислого архіву", - "backup_mount_archive_for_restore": "Підготовка архіву для відновлення...", + "backup_mount_archive_for_restore": "Підготовлення архіву для відновлення...", "backup_method_tar_finished": "Створено архів резервного копіювання TAR", - "backup_method_custom_finished": "Призначений для користувача метод резервного копіювання '{method}' завершено", + "backup_method_custom_finished": "Користувацький спосіб резервного копіювання '{method}' завершено", "backup_method_copy_finished": "Резервне копіювання завершено", - "backup_hook_unknown": "Гачок резервного копіювання '{hook}' невідомий", + "backup_hook_unknown": "Гачок (hook) резервного копіювання '{hook}' невідомий", "backup_deleted": "Резервна копія видалена", "backup_delete_error": "Не вдалося видалити '{path}'", - "backup_custom_mount_error": "Призначений для користувача метод резервного копіювання не зміг пройти етап 'монтування'", - "backup_custom_backup_error": "Призначений для користувача метод резервного копіювання не зміг пройти етап 'резервне копіювання'", + "backup_custom_mount_error": "Користувацький спосіб резервного копіювання не зміг пройти етап 'монтування'", + "backup_custom_backup_error": "Користувацький спосіб резервного копіювання не зміг пройти етап 'резервне копіювання'", "backup_csv_creation_failed": "Не вдалося створити CSV-файл, необхідний для відновлення", "backup_csv_addition_failed": "Не вдалося додати файли для резервного копіювання в CSV-файл", "backup_creation_failed": "Не вдалося створити архів резервного копіювання", "backup_create_size_estimation": "Архів буде містити близько {size} даних.", "backup_created": "Резервна копія створена", "backup_couldnt_bind": "Не вдалося зв'язати {src} з {dest}.", - "backup_copying_to_organize_the_archive": "Копіювання {size} MB для організації архіву", - "backup_cleaning_failed": "Не вдалося очистити тимчасову папку резервного копіювання", + "backup_copying_to_organize_the_archive": "Копіювання {size} МБ для організації архіву", + "backup_cleaning_failed": "Не вдалося очистити тимчасовий каталог резервного копіювання", "backup_cant_mount_uncompress_archive": "Не вдалося змонтувати нестислий архів як захищений від запису", - "backup_ask_for_copying_if_needed": "Чи хочете ви тимчасово виконати резервне копіювання з використанням {size} MB? (Цей спосіб використовується, оскільки деякі файли не можуть бути підготовлені більш ефективним методом).", + "backup_ask_for_copying_if_needed": "Ви бажаєте тимчасово виконати резервне копіювання з використанням {size} МБ? (Цей спосіб використовується, оскільки деякі файли не можуть бути підготовлені дієвіше).", "backup_archive_writing_error": "Не вдалося додати файли '{source}' (названі в архіві '{dest}') для резервного копіювання в стислий архів '{archive}'.", - "backup_archive_system_part_not_available": "Системна частина '{part}' недоступна в цій резервної копії", - "backup_archive_corrupted": "Схоже, що архів резервної копії \"{archive} 'пошкоджений: {error}", - "backup_archive_cant_retrieve_info_json": "Не вдалося завантажити інформацію для архіву '{archive}'... info.json не може бути отриманий (або не є коректним json).", - "backup_archive_open_failed": "Не вдалося відкрити архів резервних копій", - "backup_archive_name_unknown": "Невідомий локальний архів резервного копіювання з ім'ям '{name}'", - "backup_archive_name_exists": "Архів резервного копіювання з таким ім'ям вже існує.", - "backup_archive_broken_link": "Не вдалося отримати доступ до архіву резервного копіювання (непрацююча посилання на {path})", + "backup_archive_system_part_not_available": "Системна частина '{part}' недоступна в цій резервній копії", + "backup_archive_corrupted": "Схоже, що архів резервної копії '{archive}' пошкоджений: {error}", + "backup_archive_cant_retrieve_info_json": "Не вдалося завантажити відомості для архіву '{archive}'... info.json не може бути отриманий(або не є правильним json).", + "backup_archive_open_failed": "Не вдалося відкрити архів резервної копії", + "backup_archive_name_unknown": "Невідомий локальний архів резервного копіювання з назвою '{name}'", + "backup_archive_name_exists": "Архів резервного копіювання з такою назвою вже існує.", + "backup_archive_broken_link": "Не вдалося отримати доступ до архіву резервного копіювання (неробоче посилання на {path})", "backup_archive_app_not_found": "Не вдалося знайти {app} в архіві резервного копіювання", "backup_applying_method_tar": "Створення резервного TAR-архіву...", - "backup_applying_method_custom": "Виклик для користувача методу резервного копіювання '{method}'...", - "backup_applying_method_copy": "Копіювання всіх файлів в резервну копію...", + "backup_applying_method_custom": "Виклик користувацького способу резервного копіювання '{method}'...", + "backup_applying_method_copy": "Копіювання всіх файлів у резервну копію...", "backup_app_failed": "Не вдалося створити резервну копію {app}", "backup_actually_backuping": "Створення резервного архіву з зібраних файлів...", - "backup_abstract_method": "Цей метод резервного копіювання ще не реалізований", - "ask_password": "пароль", + "backup_abstract_method": "Цей спосіб резервного копіювання ще не реалізований", + "ask_password": "Пароль", "ask_new_path": "Новий шлях", - "ask_new_domain": "новий домен", - "ask_new_admin_password": "Новий адміністративний пароль", - "ask_main_domain": "основний домен", + "ask_new_domain": "Новий домен", + "ask_new_admin_password": "Новий пароль адміністратора", + "ask_main_domain": "Основний домен", "ask_lastname": "Прізвище", - "ask_firstname": "ім'я", - "ask_user_domain": "Домен для адреси електронної пошти користувача і облікового запису XMPP", - "apps_catalog_update_success": "Каталог додатків був оновлений!", - "apps_catalog_obsolete_cache": "Кеш каталогу додатків порожній або застарів.", - "apps_catalog_failed_to_download": "Неможливо завантажити каталог додатків {apps_catalog}: {error}", - "apps_catalog_updating": "Оновлення каталогу додатків…", - "apps_catalog_init_success": "Система каталогу додатків инициализирована!", - "apps_already_up_to_date": "Всі додатки вже оновлені", - "app_packaging_format_not_supported": "Ця програма не може бути встановлено, тому що формат його упаковки не підтримується вашою версією YunoHost. Можливо, вам слід оновити вашу систему.", + "ask_firstname": "Ім'я", + "ask_user_domain": "Домен для адреси е-пошти користувача і облікового запису XMPP", + "apps_catalog_update_success": "Каталог застосунків був оновлений!", + "apps_catalog_obsolete_cache": "Кеш каталогу застосунків порожній або застарів.", + "apps_catalog_failed_to_download": "Неможливо завантажити каталог застосунків {apps_catalog}: {error}", + "apps_catalog_updating": "Оновлення каталогу застосунків…", + "apps_catalog_init_success": "Систему каталогу застосунків ініціалізовано!", + "apps_already_up_to_date": "Усі застосунки вже оновлено", + "app_packaging_format_not_supported": "Цей застосунок не може бути встановлено, тому що формат його упакування не підтримується вашою версією YunoHost. Можливо, вам слід оновити систему.", "app_upgraded": "{app} оновлено", - "app_upgrade_some_app_failed": "Деякі програми не можуть бути оновлені", - "app_upgrade_script_failed": "Сталася помилка в сценарії оновлення програми", + "app_upgrade_some_app_failed": "Деякі застосунки не можуть бути оновлені", + "app_upgrade_script_failed": "Сталася помилка в скрипті оновлення застосунку", "app_upgrade_failed": "Не вдалося оновити {app}: {error}", "app_upgrade_app_name": "Зараз оновлюємо {app}...", - "app_upgrade_several_apps": "Наступні додатки будуть оновлені: {apps}", - "app_unsupported_remote_type": "Для додатка використовується непідтримуваний віддалений тип.", - "app_unknown": "невідоме додаток", + "app_upgrade_several_apps": "Наступні застосунки буде оновлено: {apps}", + "app_unsupported_remote_type": "Для застосунку використовується непідтримуваний віддалений тип.", + "app_unknown": "Невідомий застосунок", "app_start_restore": "Відновлення {app}...", - "app_start_backup": "Збір файлів для резервного копіювання {app}...", - "app_start_remove": "Видалення {app}...", + "app_start_backup": "Збирання файлів для резервного копіювання {app}...", + "app_start_remove": "Вилучення {app}...", "app_start_install": "Установлення {app}...", - "app_sources_fetch_failed": "Не вдалося вихідні файли, URL коректний?", - "app_restore_script_failed": "Сталася помилка всередині скрипта відновити оригінальну програму", + "app_sources_fetch_failed": "Не вдалося отримати джерельні файли, URL-адреса правильна?", + "app_restore_script_failed": "Сталася помилка всередині скрипта відновлення застосунку", "app_restore_failed": "Не вдалося відновити {app}: {error}", - "app_remove_after_failed_install": "Видалення програми після збою установки...", + "app_remove_after_failed_install": "Вилучення застосунку після збою встановлення...", "app_requirements_unmeet": "Вимоги не виконані для {app}, пакет {pkgname} ({version}) повинен бути {spec}.", - "app_requirements_checking": "Перевірка необхідних пакетів для {app}...", + "app_requirements_checking": "Перевіряння необхідних пакетів для {app}...", "app_removed": "{app} видалено", "app_not_properly_removed": "{app} не було видалено належним чином", - "app_not_installed": "Не вдалося знайти {app} в списку встановлених додатків: {all_apps}", + "app_not_installed": "Не вдалося знайти {app} в списку встановлених застосунків: {all_apps}", "app_not_correctly_installed": "{app}, схоже, неправильно встановлено", - "app_not_upgraded": "Додаток '{failed_app}' не вдалося оновити, і, як наслідок, оновлення таких програмах було скасовано: {apps}", - "app_manifest_install_ask_is_public": "Чи повинно це додаток бути відкрито для анонімних відвідувачів?", - "app_manifest_install_ask_admin": "Виберіть користувача-адміністратора для цього додатка", - "app_manifest_install_ask_password": "Виберіть пароль адміністратора для цього додатка" + "app_not_upgraded": "Застосунок '{failed_app}' не вдалося оновити, і, як наслідок, оновлення таких застосунків було скасовано: {apps}", + "app_manifest_install_ask_is_public": "Чи має цей застосунок бути відкритим для анонімних відвідувачів?", + "app_manifest_install_ask_admin": "Виберіть користувача-адміністратора для цього застосунку", + "app_manifest_install_ask_password": "Виберіть пароль адміністратора для цього застосунку" } From b28cf8cbce8a9126a5a97af4f1fd58d21e73e8df Mon Sep 17 00:00:00 2001 From: ljf Date: Fri, 3 Sep 2021 14:26:34 +0200 Subject: [PATCH 2890/3170] [enh] Prepare config panel for domain --- data/helpers.d/utils | 7 +- src/yunohost/app.py | 947 +++-------------------------------- src/yunohost/utils/config.py | 814 ++++++++++++++++++++++++++++++ src/yunohost/utils/i18n.py | 46 ++ 4 files changed, 921 insertions(+), 893 deletions(-) create mode 100644 src/yunohost/utils/config.py create mode 100644 src/yunohost/utils/i18n.py diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 1c4f73ddf..14e7ebe4a 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -551,22 +551,23 @@ ynh_set_var() { local crazy_value="$(grep -i -o -P '^[ \t]*\$?(\w*\[)?[ \t]*["'"']?${key}['"'"]?[ \t]*\]?[ \t]*[:=]>?[ \t]*\K.*(?=[ \t,\n;]*$)' ${file} | head -n1)" # local crazy_value="$(grep -i -o -P "^${var_part}\K.*(?=[ \t,\n;]*\$)" ${file} | head -n1)" local first_char="${crazy_value:0:1}" + delimiter=$'\001' if [[ "$first_char" == '"' ]] ; then # \ and sed is quite complex you need 2 \\ to get one in a sed # So we need \\\\ to go through 2 sed value="$(echo "$value" | sed 's/"/\\\\"/g')" - sed -ri 'sø^('"${var_part}"'")([^"]|\\")*("[ \t;,]*)$ø\1'"${value}"'\4øi' ${file} + sed -ri s$delimiter'^('"${var_part}"'")([^"]|\\")*("[ \t;,]*)$'$delimiter'\1'"${value}"'\4'$delimiter'i' ${file} elif [[ "$first_char" == "'" ]] ; then # \ and sed is quite complex you need 2 \\ to get one in a sed # However double quotes implies to double \\ to # So we need \\\\\\\\ to go through 2 sed and 1 double quotes str value="$(echo "$value" | sed "s/'/\\\\\\\\'/g")" - sed -ri "sø^(${var_part}')([^']|\\')*('"'[ \t,;]*)$ø\1'"${value}"'\4øi' ${file} + sed -ri "s$delimiter^(${var_part}')([^']|\\')*('"'[ \t,;]*)$'$delimiter'\1'"${value}"'\4'$delimiter'i' ${file} else if [[ "$value" == *"'"* ]] || [[ "$value" == *'"'* ]] ; then value='\"'"$(echo "$value" | sed 's/"/\\\\"/g')"'\"' fi - sed -ri "sø^(${var_part}).*"'$ø\1'"${value}"'øi' ${file} + sed -ri "s$delimiter^(${var_part}).*"'$'$delimiter'\1'"${value}"$delimiter'i' ${file} fi } diff --git a/src/yunohost/app.py b/src/yunohost/app.py index de6df6579..522f695e2 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -33,7 +33,6 @@ import re import subprocess import glob import urllib.parse -import base64 import tempfile from collections import OrderedDict @@ -54,8 +53,10 @@ from moulinette.utils.filesystem import ( mkdir, ) -from yunohost.service import service_status, _run_service_command, _get_services -from yunohost.utils import packages +from yunohost.service import service_status, _run_service_command +from yunohost.utils import packages, config +from yunohost.utils.config import ConfigPanel, parse_args_in_yunohost_format, YunoHostArgumentFormatParser +from yunohost.utils.i18n import _value_for_locale from yunohost.utils.error import YunohostError, YunohostValidationError from yunohost.utils.filesystem import free_space_in_directory from yunohost.log import is_unit_operation, OperationLogger @@ -70,7 +71,6 @@ APPS_CATALOG_CONF = "/etc/yunohost/apps_catalog.yml" APPS_CATALOG_API_VERSION = 2 APPS_CATALOG_DEFAULT_URL = "https://app.yunohost.org/default" -APPS_CONFIG_PANEL_VERSION_SUPPORTED = 1.0 re_app_instance_name = re.compile( r"^(?P[\w-]+?)(__(?P[1-9][0-9]*))?$" ) @@ -1756,51 +1756,12 @@ def app_action_run(operation_logger, app, action, args=None): return logger.success("Action successed!") -@is_unit_operation() -def app_config_get(operation_logger, app, key='', mode='classic'): +def app_config_get(app, key='', mode='classic'): """ Display an app configuration in classic, full or export mode """ - - # Check app is installed - _assert_is_installed(app) - - filter_key = key or '' - - # Read config panel toml - config_panel = _get_app_config_panel(app, filter_key=filter_key) - - if not config_panel: - raise YunohostError("app_config_no_panel") - - # Call config script in order to hydrate config panel with current values - values = _call_config_script(operation_logger, app, 'show', config_panel=config_panel) - - # Format result in full mode - if mode == 'full': - operation_logger.success() - return config_panel - - # In 'classic' mode, we display the current value if key refer to an option - if filter_key.count('.') == 2 and mode == 'classic': - option = filter_key.split('.')[-1] - operation_logger.success() - return values.get(option, None) - - # Format result in 'classic' or 'export' mode - logger.debug(f"Formating result in '{mode}' mode") - result = {} - for panel, section, option in _get_config_iterator(config_panel): - key = f"{panel['id']}.{section['id']}.{option['id']}" - if mode == 'export': - result[option['id']] = option.get('current_value') - else: - result[key] = { 'ask': _value_for_locale(option['ask']) } - if 'current_value' in option: - result[key]['value'] = option['current_value'] - - operation_logger.success() - return result + config = AppConfigPanel(app) + return config.get(key, mode) @is_unit_operation() @@ -1809,182 +1770,65 @@ def app_config_set(operation_logger, app, key=None, value=None, args=None, args_ Apply a new app configuration """ - # Check app is installed - _assert_is_installed(app) - - filter_key = key or '' - - # Read config panel toml - config_panel = _get_app_config_panel(app, filter_key=filter_key) - - if not config_panel: - raise YunohostError("app_config_no_panel") - - if (args is not None or args_file is not None) and value is not None: - raise YunohostError("app_config_args_value") - - if filter_key.count('.') != 2 and not value is None: - raise YunohostError("app_config_set_value_on_section") - - # Import and parse pre-answered options - logger.debug("Import and parse pre-answered options") - args = urllib.parse.parse_qs(args or '', keep_blank_values=True) - args = { key: ','.join(value_) for key, value_ in args.items() } - - if args_file: - # Import YAML / JSON file but keep --args values - args = { **read_yaml(args_file), **args } - - if value is not None: - args = {filter_key.split('.')[-1]: value} - - # Call config script in order to hydrate config panel with current values - _call_config_script(operation_logger, app, 'show', config_panel=config_panel) - - # Ask unanswered question and prevalidate - logger.debug("Ask unanswered question and prevalidate data") - def display_header(message): - """ CLI panel/section header display - """ - if Moulinette.interface.type == 'cli' and filter_key.count('.') < 2: - Moulinette.display(colorize(message, 'purple')) - - try: - env = {} - for panel, section, obj in _get_config_iterator(config_panel, - ['panel', 'section']): - if panel == obj: - name = _value_for_locale(panel['name']) - display_header(f"\n{'='*40}\n>>>> {name}\n{'='*40}") - continue - name = _value_for_locale(section['name']) - display_header(f"\n# {name}") - - # Check and ask unanswered questions - env.update(_parse_args_in_yunohost_format( - args, section['options'] - )) - - # Call config script in 'apply' mode - logger.info("Running config script...") - env = {key: str(value[0]) for key, value in env.items() if not value[0] is None} - - errors = _call_config_script(operation_logger, app, 'apply', env=env) - # Script got manually interrupted ... - # N.B. : KeyboardInterrupt does not inherit from Exception - except (KeyboardInterrupt, EOFError): - error = m18n.n("operation_interrupted") - logger.error(m18n.n("app_config_failed", app=app, error=error)) - failure_message_with_debug_instructions = operation_logger.error(error) - raise - # Something wrong happened in Yunohost's code (most probably hook_exec) - except Exception: - import traceback - - error = m18n.n("unexpected_error", error="\n" + traceback.format_exc()) - logger.error(m18n.n("app_config_failed", app=app, error=error)) - failure_message_with_debug_instructions = operation_logger.error(error) - raise - finally: - # Delete files uploaded from API - FileArgumentParser.clean_upload_dirs() - - if errors: - return { - "errors": errors, - } - - # Reload services - logger.info("Reloading services...") - services_to_reload = set() - for panel, section, obj in _get_config_iterator(config_panel, - ['panel', 'section', 'option']): - services_to_reload |= set(obj.get('services', [])) - - services_to_reload = list(services_to_reload) - services_to_reload.sort(key = 'nginx'.__eq__) - for service in services_to_reload: - service = service.replace('__APP__', app) - logger.debug(f"Reloading {service}") - if not _run_service_command('reload-or-restart', service): - services = _get_services() - test_conf = services[service].get('test_conf', 'true') - errors = check_output(f"{test_conf}; exit 0") if test_conf else '' - raise YunohostError( - "app_config_failed_service_reload", - service=service, errors=errors - ) - - logger.success("Config updated as expected") - return {} - - -def _get_config_iterator(config_panel, trigger=['option']): - for panel in config_panel.get("panels", []): - if 'panel' in trigger: - yield (panel, None, panel) - for section in panel.get("sections", []): - if 'section' in trigger: - yield (panel, section, section) - if 'option' in trigger: - for option in section.get("options", []): - yield (panel, section, option) - - -def _call_config_script(operation_logger, app, action, env={}, config_panel=None): - from yunohost.hook import hook_exec + config = AppConfigPanel(app) YunoHostArgumentFormatParser.operation_logger = operation_logger operation_logger.start() - # Add default config script if needed - config_script = os.path.join(APPS_SETTING_PATH, app, "scripts", "config") - if not os.path.exists(config_script): - logger.debug("Adding a default config script") - default_script = """#!/bin/bash + result = config.set(key, value, args, args_file) + if "errors" not in result: + operation_logger.success() + return result + +class AppConfigPanel(ConfigPanel): + def __init__(self, app): + + # Check app is installed + _assert_is_installed(app) + + self.app = app + config_path = os.path.join(APPS_SETTING_PATH, app, "config_panel.toml") + super().__init__(config_path=config_path) + + def _load_current_values(self): + self.values = self._call_config_script('show') + + def _apply(self): + self.errors = self._call_config_script('apply', self.new_values) + + def _call_config_script(self, action, env={}): + from yunohost.hook import hook_exec + + # Add default config script if needed + config_script = os.path.join(APPS_SETTING_PATH, self.app, "scripts", "config") + if not os.path.exists(config_script): + logger.debug("Adding a default config script") + default_script = """#!/bin/bash source /usr/share/yunohost/helpers ynh_abort_if_errors final_path=$(ynh_app_setting_get $app final_path) ynh_app_config_run $1 """ - write_to_file(config_script, default_script) + write_to_file(config_script, default_script) - # Call config script to extract current values - logger.debug(f"Calling '{action}' action from config script") - app_id, app_instance_nb = _parse_app_instance_name(app) - env.update({ - "app_id": app_id, - "app": app, - "app_instance_nb": str(app_instance_nb), - }) - - ret, parsed_values = hook_exec( - config_script, args=[action], env=env - ) - if ret != 0: - if action == 'show': - raise YunohostError("app_config_unable_to_read_values") - else: - raise YunohostError("app_config_unable_to_apply_values_correctly") - - return parsed_values - - if not config_panel: - return parsed_values - - # Hydrating config panel with current value - logger.debug("Hydrating config with current values") - for _, _, option in _get_config_iterator(config_panel): - if option['name'] not in parsed_values: - continue - value = parsed_values[option['name']] - # In general, the value is just a simple value. - # Sometimes it could be a dict used to overwrite the option itself - value = value if isinstance(value, dict) else {'current_value': value } - option.update(value) - - return parsed_values + # Call config script to extract current values + logger.debug(f"Calling '{action}' action from config script") + app_id, app_instance_nb = _parse_app_instance_name(self.app) + env.update({ + "app_id": app_id, + "app": self.app, + "app_instance_nb": str(app_instance_nb), + }) + ret, values = hook_exec( + config_script, args=[action], env=env + ) + if ret != 0: + if action == 'show': + raise YunohostError("app_config_unable_to_read_values") + else: + raise YunohostError("app_config_unable_to_apply_values_correctly") + return values def _get_all_installed_apps_id(): """ @@ -2087,163 +1931,6 @@ def _get_app_actions(app_id): return None -def _get_app_config_panel(app_id, filter_key=''): - "Get app config panel stored in json or in toml" - - # Split filter_key - filter_key = dict(enumerate(filter_key.split('.'))) - if len(filter_key) > 3: - raise YunohostError("app_config_too_much_sub_keys") - - # Open TOML - config_panel_toml_path = os.path.join( - APPS_SETTING_PATH, app_id, "config_panel.toml" - ) - - # sample data to get an idea of what is going on - # this toml extract: - # - # version = "0.1" - # name = "Unattended-upgrades configuration panel" - # - # [main] - # name = "Unattended-upgrades configuration" - # - # [main.unattended_configuration] - # name = "50unattended-upgrades configuration file" - # - # [main.unattended_configuration.upgrade_level] - # name = "Choose the sources of packages to automatically upgrade." - # default = "Security only" - # type = "text" - # help = "We can't use a choices field for now. In the meantime[...]" - # # choices = ["Security only", "Security and updates"] - - # [main.unattended_configuration.ynh_update] - # name = "Would you like to update YunoHost packages automatically ?" - # type = "bool" - # default = true - # - # will be parsed into this: - # - # OrderedDict([(u'version', u'0.1'), - # (u'name', u'Unattended-upgrades configuration panel'), - # (u'main', - # OrderedDict([(u'name', u'Unattended-upgrades configuration'), - # (u'unattended_configuration', - # OrderedDict([(u'name', - # u'50unattended-upgrades configuration file'), - # (u'upgrade_level', - # OrderedDict([(u'name', - # u'Choose the sources of packages to automatically upgrade.'), - # (u'default', - # u'Security only'), - # (u'type', u'text'), - # (u'help', - # u"We can't use a choices field for now. In the meantime please choose between one of this values:
Security only, Security and updates.")])), - # (u'ynh_update', - # OrderedDict([(u'name', - # u'Would you like to update YunoHost packages automatically ?'), - # (u'type', u'bool'), - # (u'default', True)])), - # - # and needs to be converted into this: - # - # {u'name': u'Unattended-upgrades configuration panel', - # u'panel': [{u'id': u'main', - # u'name': u'Unattended-upgrades configuration', - # u'sections': [{u'id': u'unattended_configuration', - # u'name': u'50unattended-upgrades configuration file', - # u'options': [{u'//': u'"choices" : ["Security only", "Security and updates"]', - # u'default': u'Security only', - # u'help': u"We can't use a choices field for now. In the meantime[...]", - # u'id': u'upgrade_level', - # u'name': u'Choose the sources of packages to automatically upgrade.', - # u'type': u'text'}, - # {u'default': True, - # u'id': u'ynh_update', - # u'name': u'Would you like to update YunoHost packages automatically ?', - # u'type': u'bool'}, - - if not os.path.exists(config_panel_toml_path): - return None - toml_config_panel = read_toml(config_panel_toml_path) - - # Check TOML config panel is in a supported version - if float(toml_config_panel["version"]) < APPS_CONFIG_PANEL_VERSION_SUPPORTED: - raise YunohostError( - "app_config_too_old_version", app=app_id, - version=toml_config_panel["version"] - ) - - # Transform toml format into internal format - defaults = { - 'toml': { - 'version': 1.0 - }, - 'panels': { - 'name': '', - 'services': [], - 'actions': {'apply': {'en': 'Apply'}} - }, # help - 'sections': { - 'name': '', - 'services': [], - 'optional': True - }, # visibleIf help - 'options': {} - # ask type source help helpLink example style icon placeholder visibleIf - # optional choices pattern limit min max step accept redact - } - - def convert(toml_node, node_type): - """Convert TOML in internal format ('full' mode used by webadmin) - - Here are some properties of 1.0 config panel in toml: - - node properties and node children are mixed, - - text are in english only - - some properties have default values - This function detects all children nodes and put them in a list - """ - # Prefill the node default keys if needed - default = defaults[node_type] - node = {key: toml_node.get(key, value) for key, value in default.items()} - - # Define the filter_key part to use and the children type - i = list(defaults).index(node_type) - search_key = filter_key.get(i) - subnode_type = list(defaults)[i+1] if node_type != 'options' else None - - for key, value in toml_node.items(): - # Key/value are a child node - if isinstance(value, OrderedDict) and key not in default and subnode_type: - # We exclude all nodes not referenced by the filter_key - if search_key and key != search_key: - continue - subnode = convert(value, subnode_type) - subnode['id'] = key - if node_type == 'sections': - subnode['name'] = key # legacy - subnode.setdefault('optional', toml_node.get('optional', True)) - node.setdefault(subnode_type, []).append(subnode) - # Key/value are a property - else: - # Todo search all i18n keys - node[key] = value if key not in ['ask', 'help', 'name'] else { 'en': value } - return node - - config_panel = convert(toml_config_panel, 'toml') - - try: - config_panel['panels'][0]['sections'][0]['options'][0] - except (KeyError, IndexError): - raise YunohostError( - "app_config_empty_or_bad_filter_key", app=app_id, filter_key=filter_key - ) - - return config_panel - - def _get_app_settings(app_id): """ Get settings of an installed app @@ -2695,30 +2382,6 @@ def _installed_apps(): return os.listdir(APPS_SETTING_PATH) -def _value_for_locale(values): - """ - Return proper value for current locale - - Keyword arguments: - values -- A dict of values associated to their locale - - Returns: - An utf-8 encoded string - - """ - if not isinstance(values, dict): - return values - - for lang in [m18n.locale, m18n.default_locale]: - try: - return values[lang] - except KeyError: - continue - - # Fallback to first value - return list(values.values())[0] - - def _check_manifest_requirements(manifest, app_instance_name): """Check if required packages are met from the manifest""" @@ -2765,7 +2428,7 @@ def _parse_args_from_manifest(manifest, action, args={}): return OrderedDict() action_args = manifest["arguments"][action] - return _parse_args_in_yunohost_format(args, action_args) + return parse_args_in_yunohost_format(args, action_args) def _parse_args_for_action(action, args={}): @@ -2789,507 +2452,11 @@ def _parse_args_for_action(action, args={}): action_args = action["arguments"] - return _parse_args_in_yunohost_format(args, action_args) + return parse_args_in_yunohost_format(args, action_args) -class Question: - "empty class to store questions information" -class YunoHostArgumentFormatParser(object): - hide_user_input_in_prompt = False - operation_logger = None - - def parse_question(self, question, user_answers): - parsed_question = Question() - - parsed_question.name = question["name"] - parsed_question.type = question.get("type", 'string') - parsed_question.default = question.get("default", None) - parsed_question.current_value = question.get("current_value") - parsed_question.optional = question.get("optional", False) - parsed_question.choices = question.get("choices", []) - parsed_question.pattern = question.get("pattern") - parsed_question.ask = question.get("ask", {'en': f"{parsed_question.name}"}) - parsed_question.help = question.get("help") - parsed_question.helpLink = question.get("helpLink") - parsed_question.value = user_answers.get(parsed_question.name) - parsed_question.redact = question.get('redact', False) - - # Empty value is parsed as empty string - if parsed_question.default == "": - parsed_question.default = None - - return parsed_question - - def parse(self, question, user_answers): - question = self.parse_question(question, user_answers) - - while True: - # Display question if no value filled or if it's a readonly message - if Moulinette.interface.type== 'cli': - text_for_user_input_in_cli = self._format_text_for_user_input_in_cli( - question - ) - if getattr(self, "readonly", False): - Moulinette.display(text_for_user_input_in_cli) - - elif question.value is None: - prefill = "" - if question.current_value is not None: - prefill = question.current_value - elif question.default is not None: - prefill = question.default - question.value = Moulinette.prompt( - message=text_for_user_input_in_cli, - is_password=self.hide_user_input_in_prompt, - confirm=self.hide_user_input_in_prompt, - prefill=prefill, - is_multiline=(question.type == "text") - ) - - - # Apply default value - if question.value in [None, ""] and question.default is not None: - question.value = ( - getattr(self, "default_value", None) - if question.default is None - else question.default - ) - - # Prevalidation - try: - self._prevalidate(question) - except YunohostValidationError as e: - if Moulinette.interface.type== 'api': - raise - Moulinette.display(str(e), 'error') - question.value = None - continue - break - # this is done to enforce a certain formating like for boolean - # by default it doesn't do anything - question.value = self._post_parse_value(question) - - return (question.value, self.argument_type) - - def _prevalidate(self, question): - if question.value in [None, ""] and not question.optional: - raise YunohostValidationError( - "app_argument_required", name=question.name - ) - - # we have an answer, do some post checks - if question.value is not None: - if question.choices and question.value not in question.choices: - self._raise_invalid_answer(question) - if question.pattern and not re.match(question.pattern['regexp'], str(question.value)): - raise YunohostValidationError( - question.pattern['error'], - name=question.name, - value=question.value, - ) - - def _raise_invalid_answer(self, question): - raise YunohostValidationError( - "app_argument_choice_invalid", - name=question.name, - value=question.value, - choices=", ".join(question.choices), - ) - - def _format_text_for_user_input_in_cli(self, question): - text_for_user_input_in_cli = _value_for_locale(question.ask) - - if question.choices: - text_for_user_input_in_cli += " [{0}]".format(" | ".join(question.choices)) - - if question.help or question.helpLink: - text_for_user_input_in_cli += ":\033[m" - if question.help: - text_for_user_input_in_cli += "\n - " - text_for_user_input_in_cli += _value_for_locale(question.help) - if question.helpLink: - if not isinstance(question.helpLink, dict): - question.helpLink = {'href': question.helpLink} - text_for_user_input_in_cli += f"\n - See {question.helpLink['href']}" - return text_for_user_input_in_cli - - def _post_parse_value(self, question): - if not question.redact: - return question.value - - # Tell the operation_logger to redact all password-type / secret args - # Also redact the % escaped version of the password that might appear in - # the 'args' section of metadata (relevant for password with non-alphanumeric char) - data_to_redact = [] - if question.value and isinstance(question.value, str): - data_to_redact.append(question.value) - if question.current_value and isinstance(question.current_value, str): - data_to_redact.append(question.current_value) - data_to_redact += [ - urllib.parse.quote(data) - for data in data_to_redact - if urllib.parse.quote(data) != data - ] - if self.operation_logger: - self.operation_logger.data_to_redact.extend(data_to_redact) - elif data_to_redact: - raise YunohostError("app_argument_cant_redact", arg=question.name) - - return question.value - - -class StringArgumentParser(YunoHostArgumentFormatParser): - argument_type = "string" - default_value = "" - -class TagsArgumentParser(YunoHostArgumentFormatParser): - argument_type = "tags" - - def _prevalidate(self, question): - values = question.value - for value in values.split(','): - question.value = value - super()._prevalidate(question) - question.value = values - - - -class PasswordArgumentParser(YunoHostArgumentFormatParser): - hide_user_input_in_prompt = True - argument_type = "password" - default_value = "" - forbidden_chars = "{}" - - def parse_question(self, question, user_answers): - question = super(PasswordArgumentParser, self).parse_question( - question, user_answers - ) - question.redact = True - if question.default is not None: - raise YunohostValidationError( - "app_argument_password_no_default", name=question.name - ) - - return question - - def _prevalidate(self, question): - super()._prevalidate(question) - - if question.value is not None: - if any(char in question.value for char in self.forbidden_chars): - raise YunohostValidationError( - "pattern_password_app", forbidden_chars=self.forbidden_chars - ) - - # If it's an optional argument the value should be empty or strong enough - from yunohost.utils.password import assert_password_is_strong_enough - - assert_password_is_strong_enough("user", question.value) - - -class PathArgumentParser(YunoHostArgumentFormatParser): - argument_type = "path" - default_value = "" - - -class BooleanArgumentParser(YunoHostArgumentFormatParser): - argument_type = "boolean" - default_value = False - - def parse_question(self, question, user_answers): - question = super().parse_question( - question, user_answers - ) - - if question.default is None: - question.default = False - - return question - - def _format_text_for_user_input_in_cli(self, question): - text_for_user_input_in_cli = _value_for_locale(question.ask) - - text_for_user_input_in_cli += " [yes | no]" - - if question.default is not None: - formatted_default = "yes" if question.default else "no" - text_for_user_input_in_cli += " (default: {0})".format(formatted_default) - - return text_for_user_input_in_cli - - def _post_parse_value(self, question): - if isinstance(question.value, bool): - return 1 if question.value else 0 - - if str(question.value).lower() in ["1", "yes", "y", "true"]: - return 1 - - if str(question.value).lower() in ["0", "no", "n", "false"]: - return 0 - - raise YunohostValidationError( - "app_argument_choice_invalid", - name=question.name, - value=question.value, - choices="yes, no, y, n, 1, 0", - ) - - -class DomainArgumentParser(YunoHostArgumentFormatParser): - argument_type = "domain" - - def parse_question(self, question, user_answers): - from yunohost.domain import domain_list, _get_maindomain - - question = super(DomainArgumentParser, self).parse_question( - question, user_answers - ) - - if question.default is None: - question.default = _get_maindomain() - - question.choices = domain_list()["domains"] - - return question - - def _raise_invalid_answer(self, question): - raise YunohostValidationError( - "app_argument_invalid", field=question.name, error=m18n.n("domain_unknown") - ) - - -class UserArgumentParser(YunoHostArgumentFormatParser): - argument_type = "user" - - def parse_question(self, question, user_answers): - from yunohost.user import user_list, user_info - from yunohost.domain import _get_maindomain - - question = super(UserArgumentParser, self).parse_question( - question, user_answers - ) - question.choices = user_list()["users"] - if question.default is None: - root_mail = "root@%s" % _get_maindomain() - for user in question.choices.keys(): - if root_mail in user_info(user).get("mail-aliases", []): - question.default = user - break - - return question - - def _raise_invalid_answer(self, question): - raise YunohostValidationError( - "app_argument_invalid", - field=question.name, - error=m18n.n("user_unknown", user=question.value), - ) - - -class NumberArgumentParser(YunoHostArgumentFormatParser): - argument_type = "number" - default_value = "" - - def parse_question(self, question, user_answers): - question_parsed = super().parse_question( - question, user_answers - ) - question_parsed.min = question.get('min', None) - question_parsed.max = question.get('max', None) - if question_parsed.default is None: - question_parsed.default = 0 - - return question_parsed - - def _prevalidate(self, question): - super()._prevalidate(question) - if not isinstance(question.value, int) and not (isinstance(question.value, str) and question.value.isdigit()): - raise YunohostValidationError( - "app_argument_invalid", field=question.name, error=m18n.n("invalid_number") - ) - - if question.min is not None and int(question.value) < question.min: - raise YunohostValidationError( - "app_argument_invalid", field=question.name, error=m18n.n("invalid_number") - ) - - if question.max is not None and int(question.value) > question.max: - raise YunohostValidationError( - "app_argument_invalid", field=question.name, error=m18n.n("invalid_number") - ) - - def _post_parse_value(self, question): - if isinstance(question.value, int): - return super()._post_parse_value(question) - - if isinstance(question.value, str) and question.value.isdigit(): - return int(question.value) - - raise YunohostValidationError( - "app_argument_invalid", field=question.name, error=m18n.n("invalid_number") - ) - - -class DisplayTextArgumentParser(YunoHostArgumentFormatParser): - argument_type = "display_text" - readonly = True - - def parse_question(self, question, user_answers): - question_parsed = super().parse_question( - question, user_answers - ) - - question_parsed.optional = True - question_parsed.style = question.get('style', 'info') - - return question_parsed - - def _format_text_for_user_input_in_cli(self, question): - text = question.ask['en'] - - if question.style in ['success', 'info', 'warning', 'danger']: - color = { - 'success': 'green', - 'info': 'cyan', - 'warning': 'yellow', - 'danger': 'red' - } - return colorize(m18n.g(question.style), color[question.style]) + f" {text}" - else: - return text - -class FileArgumentParser(YunoHostArgumentFormatParser): - argument_type = "file" - upload_dirs = [] - - @classmethod - def clean_upload_dirs(cls): - # Delete files uploaded from API - if Moulinette.interface.type== 'api': - for upload_dir in cls.upload_dirs: - if os.path.exists(upload_dir): - shutil.rmtree(upload_dir) - - def parse_question(self, question, user_answers): - question_parsed = super().parse_question( - question, user_answers - ) - if question.get('accept'): - question_parsed.accept = question.get('accept').replace(' ', '').split(',') - else: - question_parsed.accept = [] - if Moulinette.interface.type== 'api': - if user_answers.get(f"{question_parsed.name}[name]"): - question_parsed.value = { - 'content': question_parsed.value, - 'filename': user_answers.get(f"{question_parsed.name}[name]", question_parsed.name), - } - # If path file are the same - if question_parsed.value and str(question_parsed.value) == question_parsed.current_value: - question_parsed.value = None - - return question_parsed - - def _prevalidate(self, question): - super()._prevalidate(question) - if isinstance(question.value, str) and question.value and not os.path.exists(question.value): - raise YunohostValidationError( - "app_argument_invalid", field=question.name, error=m18n.n("invalid_number1") - ) - if question.value in [None, ''] or not question.accept: - return - - filename = question.value if isinstance(question.value, str) else question.value['filename'] - if '.' not in filename or '.' + filename.split('.')[-1] not in question.accept: - raise YunohostValidationError( - "app_argument_invalid", field=question.name, error=m18n.n("invalid_number2") - ) - - - def _post_parse_value(self, question): - from base64 import b64decode - # Upload files from API - # A file arg contains a string with "FILENAME:BASE64_CONTENT" - if not question.value: - return question.value - - if Moulinette.interface.type== 'api': - - upload_dir = tempfile.mkdtemp(prefix='tmp_configpanel_') - FileArgumentParser.upload_dirs += [upload_dir] - filename = question.value['filename'] - logger.debug(f"Save uploaded file {question.value['filename']} from API into {upload_dir}") - - # Filename is given by user of the API. For security reason, we have replaced - # os.path.join to avoid the user to be able to rewrite a file in filesystem - # i.e. os.path.join("/foo", "/etc/passwd") == "/etc/passwd" - file_path = os.path.normpath(upload_dir + "/" + filename) - if not file_path.startswith(upload_dir + "/"): - raise YunohostError("relative_parent_path_in_filename_forbidden") - i = 2 - while os.path.exists(file_path): - file_path = os.path.normpath(upload_dir + "/" + filename + (".%d" % i)) - i += 1 - content = question.value['content'] - try: - with open(file_path, 'wb') as f: - f.write(b64decode(content)) - except IOError as e: - raise YunohostError("cannot_write_file", file=file_path, error=str(e)) - except Exception as e: - raise YunohostError("error_writing_file", file=file_path, error=str(e)) - question.value = file_path - return question.value - - -ARGUMENTS_TYPE_PARSERS = { - "string": StringArgumentParser, - "text": StringArgumentParser, - "select": StringArgumentParser, - "tags": TagsArgumentParser, - "email": StringArgumentParser, - "url": StringArgumentParser, - "date": StringArgumentParser, - "time": StringArgumentParser, - "color": StringArgumentParser, - "password": PasswordArgumentParser, - "path": PathArgumentParser, - "boolean": BooleanArgumentParser, - "domain": DomainArgumentParser, - "user": UserArgumentParser, - "number": NumberArgumentParser, - "range": NumberArgumentParser, - "display_text": DisplayTextArgumentParser, - "alert": DisplayTextArgumentParser, - "markdown": DisplayTextArgumentParser, - "file": FileArgumentParser, -} - - -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 - """ - parsed_answers_dict = OrderedDict() - - for question in argument_questions: - parser = ARGUMENTS_TYPE_PARSERS[question.get("type", "string")]() - - answer = parser.parse(question=question, user_answers=user_answers) - if answer is not None: - parsed_answers_dict[question["name"]] = answer - - return parsed_answers_dict - def _validate_and_normalize_webpath(args_dict, app_folder): diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py new file mode 100644 index 000000000..34883dcf7 --- /dev/null +++ b/src/yunohost/utils/config.py @@ -0,0 +1,814 @@ +# -*- coding: utf-8 -*- + +""" License + + Copyright (C) 2018 YUNOHOST.ORG + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program; if not, see http://www.gnu.org/licenses + +""" + +import os +import re +import toml +import urllib.parse +import tempfile +from collections import OrderedDict + +from moulinette.interfaces.cli import colorize +from moulinette import Moulinette, m18n +from moulinette.utils.log import getActionLogger +from moulinette.utils.process import check_output +from moulinette.utils.filesystem import ( + read_toml, + read_yaml, + write_to_yaml, + mkdir, +) + +from yunohost.service import _get_services +from yunohost.service import _run_service_command, _get_services +from yunohost.utils.i18n import _value_for_locale +from yunohost.utils.error import YunohostError, YunohostValidationError + +logger = getActionLogger("yunohost.config") +CONFIG_PANEL_VERSION_SUPPORTED = 1.0 + +class ConfigPanel: + + def __init__(self, config_path, save_path=None): + self.config_path = config_path + self.save_path = save_path + self.config = {} + self.values = {} + self.new_values = {} + + def get(self, key='', mode='classic'): + self.filter_key = key or '' + + # Read config panel toml + self._get_config_panel() + + if not self.config: + raise YunohostError("config_no_panel") + + # Read or get values and hydrate the config + self._load_current_values() + self._hydrate() + + # Format result in full mode + if mode == 'full': + return self.config + + # In 'classic' mode, we display the current value if key refer to an option + if self.filter_key.count('.') == 2 and mode == 'classic': + option = self.filter_key.split('.')[-1] + return self.values.get(option, None) + + # Format result in 'classic' or 'export' mode + logger.debug(f"Formating result in '{mode}' mode") + result = {} + for panel, section, option in self._iterate(): + key = f"{panel['id']}.{section['id']}.{option['id']}" + if mode == 'export': + result[option['id']] = option.get('current_value') + else: + result[key] = { 'ask': _value_for_locale(option['ask']) } + if 'current_value' in option: + result[key]['value'] = option['current_value'] + + return result + + def set(self, key=None, value=None, args=None, args_file=None): + self.filter_key = key or '' + + # Read config panel toml + self._get_config_panel() + + if not self.config: + raise YunohostError("config_no_panel") + + if (args is not None or args_file is not None) and value is not None: + raise YunohostError("config_args_value") + + if self.filter_key.count('.') != 2 and not value is None: + raise YunohostError("config_set_value_on_section") + + # Import and parse pre-answered options + logger.debug("Import and parse pre-answered options") + args = urllib.parse.parse_qs(args or '', keep_blank_values=True) + self.args = { key: ','.join(value_) for key, value_ in args.items() } + + if args_file: + # Import YAML / JSON file but keep --args values + self.args = { **read_yaml(args_file), **self.args } + + if value is not None: + self.args = {self.filter_key.split('.')[-1]: value} + + # Read or get values and hydrate the config + self._load_current_values() + self._hydrate() + + try: + self._ask() + self._apply() + + # Script got manually interrupted ... + # N.B. : KeyboardInterrupt does not inherit from Exception + except (KeyboardInterrupt, EOFError): + error = m18n.n("operation_interrupted") + logger.error(m18n.n("config_failed", error=error)) + raise + # Something wrong happened in Yunohost's code (most probably hook_exec) + except Exception: + import traceback + + error = m18n.n("unexpected_error", error="\n" + traceback.format_exc()) + logger.error(m18n.n("config_failed", error=error)) + raise + finally: + # Delete files uploaded from API + FileArgumentParser.clean_upload_dirs() + + if self.errors: + return { + "errors": errors, + } + + self._reload_services() + + logger.success("Config updated as expected") + return {} + + def _get_toml(self): + return read_toml(self.config_path) + + + def _get_config_panel(self): + # Split filter_key + filter_key = dict(enumerate(self.filter_key.split('.'))) + if len(filter_key) > 3: + raise YunohostError("config_too_much_sub_keys") + + if not os.path.exists(self.config_path): + return None + toml_config_panel = self._get_toml() + + # Check TOML config panel is in a supported version + if float(toml_config_panel["version"]) < CONFIG_PANEL_VERSION_SUPPORTED: + raise YunohostError( + "config_too_old_version", version=toml_config_panel["version"] + ) + + # Transform toml format into internal format + defaults = { + 'toml': { + 'version': 1.0 + }, + 'panels': { + 'name': '', + 'services': [], + 'actions': {'apply': {'en': 'Apply'}} + }, # help + 'sections': { + 'name': '', + 'services': [], + 'optional': True + }, # visibleIf help + 'options': {} + # ask type source help helpLink example style icon placeholder visibleIf + # optional choices pattern limit min max step accept redact + } + + def convert(toml_node, node_type): + """Convert TOML in internal format ('full' mode used by webadmin) + Here are some properties of 1.0 config panel in toml: + - node properties and node children are mixed, + - text are in english only + - some properties have default values + This function detects all children nodes and put them in a list + """ + # Prefill the node default keys if needed + default = defaults[node_type] + node = {key: toml_node.get(key, value) for key, value in default.items()} + + # Define the filter_key part to use and the children type + i = list(defaults).index(node_type) + search_key = filter_key.get(i) + subnode_type = list(defaults)[i+1] if node_type != 'options' else None + + for key, value in toml_node.items(): + # Key/value are a child node + if isinstance(value, OrderedDict) and key not in default and subnode_type: + # We exclude all nodes not referenced by the filter_key + if search_key and key != search_key: + continue + subnode = convert(value, subnode_type) + subnode['id'] = key + if node_type == 'sections': + subnode['name'] = key # legacy + subnode.setdefault('optional', toml_node.get('optional', True)) + node.setdefault(subnode_type, []).append(subnode) + # Key/value are a property + else: + # Todo search all i18n keys + node[key] = value if key not in ['ask', 'help', 'name'] else { 'en': value } + return node + + self.config = convert(toml_config_panel, 'toml') + + try: + self.config['panels'][0]['sections'][0]['options'][0] + except (KeyError, IndexError): + raise YunohostError( + "config_empty_or_bad_filter_key", filter_key=self.filter_key + ) + + return self.config + + def _hydrate(self): + # Hydrating config panel with current value + logger.debug("Hydrating config with current values") + for _, _, option in self._iterate(): + if option['name'] not in self.values: + continue + value = self.values[option['name']] + # In general, the value is just a simple value. + # Sometimes it could be a dict used to overwrite the option itself + value = value if isinstance(value, dict) else {'current_value': value } + option.update(value) + + return self.values + + def _ask(self): + logger.debug("Ask unanswered question and prevalidate data") + def display_header(message): + """ CLI panel/section header display + """ + if Moulinette.interface.type == 'cli' and self.filter_key.count('.') < 2: + Moulinette.display(colorize(message, 'purple')) + for panel, section, obj in self._iterate(['panel', 'section']): + if panel == obj: + name = _value_for_locale(panel['name']) + display_header(f"\n{'='*40}\n>>>> {name}\n{'='*40}") + continue + name = _value_for_locale(section['name']) + display_header(f"\n# {name}") + + # Check and ask unanswered questions + self.new_values.update(parse_args_in_yunohost_format( + self.args, section['options'] + )) + self.new_values = {key: str(value[0]) for key, value in self.new_values.items() if not value[0] is None} + + def _apply(self): + logger.info("Running config script...") + dir_path = os.path.dirname(os.path.realpath(self.save_path)) + if not os.path.exists(dir_path): + mkdir(dir_path, mode=0o700) + # Save the settings to the .yaml file + write_to_yaml(self.save_path, self.new_values) + + + def _reload_services(self): + logger.info("Reloading services...") + services_to_reload = set() + for panel, section, obj in self._iterate(['panel', 'section', 'option']): + services_to_reload |= set(obj.get('services', [])) + + services_to_reload = list(services_to_reload) + services_to_reload.sort(key = 'nginx'.__eq__) + for service in services_to_reload: + if '__APP__': + service = service.replace('__APP__', self.app) + logger.debug(f"Reloading {service}") + if not _run_service_command('reload-or-restart', service): + services = _get_services() + test_conf = services[service].get('test_conf', 'true') + errors = check_output(f"{test_conf}; exit 0") if test_conf else '' + raise YunohostError( + "config_failed_service_reload", + service=service, errors=errors + ) + + def _iterate(self, trigger=['option']): + for panel in self.config.get("panels", []): + if 'panel' in trigger: + yield (panel, None, panel) + for section in panel.get("sections", []): + if 'section' in trigger: + yield (panel, section, section) + if 'option' in trigger: + for option in section.get("options", []): + yield (panel, section, option) + + +class Question: + "empty class to store questions information" + + +class YunoHostArgumentFormatParser(object): + hide_user_input_in_prompt = False + operation_logger = None + + def parse_question(self, question, user_answers): + parsed_question = Question() + + parsed_question.name = question["name"] + parsed_question.type = question.get("type", 'string') + parsed_question.default = question.get("default", None) + parsed_question.current_value = question.get("current_value") + parsed_question.optional = question.get("optional", False) + parsed_question.choices = question.get("choices", []) + parsed_question.pattern = question.get("pattern") + parsed_question.ask = question.get("ask", {'en': f"{parsed_question.name}"}) + parsed_question.help = question.get("help") + parsed_question.helpLink = question.get("helpLink") + parsed_question.value = user_answers.get(parsed_question.name) + parsed_question.redact = question.get('redact', False) + + # Empty value is parsed as empty string + if parsed_question.default == "": + parsed_question.default = None + + return parsed_question + + def parse(self, question, user_answers): + question = self.parse_question(question, user_answers) + + while True: + # Display question if no value filled or if it's a readonly message + if Moulinette.interface.type== 'cli': + text_for_user_input_in_cli = self._format_text_for_user_input_in_cli( + question + ) + if getattr(self, "readonly", False): + Moulinette.display(text_for_user_input_in_cli) + + elif question.value is None: + prefill = "" + if question.current_value is not None: + prefill = question.current_value + elif question.default is not None: + prefill = question.default + question.value = Moulinette.prompt( + message=text_for_user_input_in_cli, + is_password=self.hide_user_input_in_prompt, + confirm=self.hide_user_input_in_prompt, + prefill=prefill, + is_multiline=(question.type == "text") + ) + + + # Apply default value + if question.value in [None, ""] and question.default is not None: + question.value = ( + getattr(self, "default_value", None) + if question.default is None + else question.default + ) + + # Prevalidation + try: + self._prevalidate(question) + except YunohostValidationError as e: + if Moulinette.interface.type== 'api': + raise + Moulinette.display(str(e), 'error') + question.value = None + continue + break + # this is done to enforce a certain formating like for boolean + # by default it doesn't do anything + question.value = self._post_parse_value(question) + + return (question.value, self.argument_type) + + def _prevalidate(self, question): + if question.value in [None, ""] and not question.optional: + raise YunohostValidationError( + "app_argument_required", name=question.name + ) + + # we have an answer, do some post checks + if question.value is not None: + if question.choices and question.value not in question.choices: + self._raise_invalid_answer(question) + if question.pattern and not re.match(question.pattern['regexp'], str(question.value)): + raise YunohostValidationError( + question.pattern['error'], + name=question.name, + value=question.value, + ) + + def _raise_invalid_answer(self, question): + raise YunohostValidationError( + "app_argument_choice_invalid", + name=question.name, + value=question.value, + choices=", ".join(question.choices), + ) + + def _format_text_for_user_input_in_cli(self, question): + text_for_user_input_in_cli = _value_for_locale(question.ask) + + if question.choices: + text_for_user_input_in_cli += " [{0}]".format(" | ".join(question.choices)) + + if question.help or question.helpLink: + text_for_user_input_in_cli += ":\033[m" + if question.help: + text_for_user_input_in_cli += "\n - " + text_for_user_input_in_cli += _value_for_locale(question.help) + if question.helpLink: + if not isinstance(question.helpLink, dict): + question.helpLink = {'href': question.helpLink} + text_for_user_input_in_cli += f"\n - See {question.helpLink['href']}" + return text_for_user_input_in_cli + + def _post_parse_value(self, question): + if not question.redact: + return question.value + + # Tell the operation_logger to redact all password-type / secret args + # Also redact the % escaped version of the password that might appear in + # the 'args' section of metadata (relevant for password with non-alphanumeric char) + data_to_redact = [] + if question.value and isinstance(question.value, str): + data_to_redact.append(question.value) + if question.current_value and isinstance(question.current_value, str): + data_to_redact.append(question.current_value) + data_to_redact += [ + urllib.parse.quote(data) + for data in data_to_redact + if urllib.parse.quote(data) != data + ] + if self.operation_logger: + self.operation_logger.data_to_redact.extend(data_to_redact) + elif data_to_redact: + raise YunohostError("app_argument_cant_redact", arg=question.name) + + return question.value + + +class StringArgumentParser(YunoHostArgumentFormatParser): + argument_type = "string" + default_value = "" + +class TagsArgumentParser(YunoHostArgumentFormatParser): + argument_type = "tags" + + def _prevalidate(self, question): + values = question.value + for value in values.split(','): + question.value = value + super()._prevalidate(question) + question.value = values + + + +class PasswordArgumentParser(YunoHostArgumentFormatParser): + hide_user_input_in_prompt = True + argument_type = "password" + default_value = "" + forbidden_chars = "{}" + + def parse_question(self, question, user_answers): + question = super(PasswordArgumentParser, self).parse_question( + question, user_answers + ) + question.redact = True + if question.default is not None: + raise YunohostValidationError( + "app_argument_password_no_default", name=question.name + ) + + return question + + def _prevalidate(self, question): + super()._prevalidate(question) + + if question.value is not None: + if any(char in question.value for char in self.forbidden_chars): + raise YunohostValidationError( + "pattern_password_app", forbidden_chars=self.forbidden_chars + ) + + # If it's an optional argument the value should be empty or strong enough + from yunohost.utils.password import assert_password_is_strong_enough + + assert_password_is_strong_enough("user", question.value) + + +class PathArgumentParser(YunoHostArgumentFormatParser): + argument_type = "path" + default_value = "" + + +class BooleanArgumentParser(YunoHostArgumentFormatParser): + argument_type = "boolean" + default_value = False + + def parse_question(self, question, user_answers): + question = super().parse_question( + question, user_answers + ) + + if question.default is None: + question.default = False + + return question + + def _format_text_for_user_input_in_cli(self, question): + text_for_user_input_in_cli = _value_for_locale(question.ask) + + text_for_user_input_in_cli += " [yes | no]" + + if question.default is not None: + formatted_default = "yes" if question.default else "no" + text_for_user_input_in_cli += " (default: {0})".format(formatted_default) + + return text_for_user_input_in_cli + + def _post_parse_value(self, question): + if isinstance(question.value, bool): + return 1 if question.value else 0 + + if str(question.value).lower() in ["1", "yes", "y", "true"]: + return 1 + + if str(question.value).lower() in ["0", "no", "n", "false"]: + return 0 + + raise YunohostValidationError( + "app_argument_choice_invalid", + name=question.name, + value=question.value, + choices="yes, no, y, n, 1, 0", + ) + + +class DomainArgumentParser(YunoHostArgumentFormatParser): + argument_type = "domain" + + def parse_question(self, question, user_answers): + from yunohost.domain import domain_list, _get_maindomain + + question = super(DomainArgumentParser, self).parse_question( + question, user_answers + ) + + if question.default is None: + question.default = _get_maindomain() + + question.choices = domain_list()["domains"] + + return question + + def _raise_invalid_answer(self, question): + raise YunohostValidationError( + "app_argument_invalid", field=question.name, error=m18n.n("domain_unknown") + ) + + +class UserArgumentParser(YunoHostArgumentFormatParser): + argument_type = "user" + + def parse_question(self, question, user_answers): + from yunohost.user import user_list, user_info + from yunohost.domain import _get_maindomain + + question = super(UserArgumentParser, self).parse_question( + question, user_answers + ) + question.choices = user_list()["users"] + if question.default is None: + root_mail = "root@%s" % _get_maindomain() + for user in question.choices.keys(): + if root_mail in user_info(user).get("mail-aliases", []): + question.default = user + break + + return question + + def _raise_invalid_answer(self, question): + raise YunohostValidationError( + "app_argument_invalid", + field=question.name, + error=m18n.n("user_unknown", user=question.value), + ) + + +class NumberArgumentParser(YunoHostArgumentFormatParser): + argument_type = "number" + default_value = "" + + def parse_question(self, question, user_answers): + question_parsed = super().parse_question( + question, user_answers + ) + question_parsed.min = question.get('min', None) + question_parsed.max = question.get('max', None) + if question_parsed.default is None: + question_parsed.default = 0 + + return question_parsed + + def _prevalidate(self, question): + super()._prevalidate(question) + if not isinstance(question.value, int) and not (isinstance(question.value, str) and question.value.isdigit()): + raise YunohostValidationError( + "app_argument_invalid", field=question.name, error=m18n.n("invalid_number") + ) + + if question.min is not None and int(question.value) < question.min: + raise YunohostValidationError( + "app_argument_invalid", field=question.name, error=m18n.n("invalid_number") + ) + + if question.max is not None and int(question.value) > question.max: + raise YunohostValidationError( + "app_argument_invalid", field=question.name, error=m18n.n("invalid_number") + ) + + def _post_parse_value(self, question): + if isinstance(question.value, int): + return super()._post_parse_value(question) + + if isinstance(question.value, str) and question.value.isdigit(): + return int(question.value) + + raise YunohostValidationError( + "app_argument_invalid", field=question.name, error=m18n.n("invalid_number") + ) + + +class DisplayTextArgumentParser(YunoHostArgumentFormatParser): + argument_type = "display_text" + readonly = True + + def parse_question(self, question, user_answers): + question_parsed = super().parse_question( + question, user_answers + ) + + question_parsed.optional = True + question_parsed.style = question.get('style', 'info') + + return question_parsed + + def _format_text_for_user_input_in_cli(self, question): + text = question.ask['en'] + + if question.style in ['success', 'info', 'warning', 'danger']: + color = { + 'success': 'green', + 'info': 'cyan', + 'warning': 'yellow', + 'danger': 'red' + } + return colorize(m18n.g(question.style), color[question.style]) + f" {text}" + else: + return text + +class FileArgumentParser(YunoHostArgumentFormatParser): + argument_type = "file" + upload_dirs = [] + + @classmethod + def clean_upload_dirs(cls): + # Delete files uploaded from API + if Moulinette.interface.type== 'api': + for upload_dir in cls.upload_dirs: + if os.path.exists(upload_dir): + shutil.rmtree(upload_dir) + + def parse_question(self, question, user_answers): + question_parsed = super().parse_question( + question, user_answers + ) + if question.get('accept'): + question_parsed.accept = question.get('accept').replace(' ', '').split(',') + else: + question_parsed.accept = [] + if Moulinette.interface.type== 'api': + if user_answers.get(f"{question_parsed.name}[name]"): + question_parsed.value = { + 'content': question_parsed.value, + 'filename': user_answers.get(f"{question_parsed.name}[name]", question_parsed.name), + } + # If path file are the same + if question_parsed.value and str(question_parsed.value) == question_parsed.current_value: + question_parsed.value = None + + return question_parsed + + def _prevalidate(self, question): + super()._prevalidate(question) + if isinstance(question.value, str) and question.value and not os.path.exists(question.value): + raise YunohostValidationError( + "app_argument_invalid", field=question.name, error=m18n.n("invalid_number1") + ) + if question.value in [None, ''] or not question.accept: + return + + filename = question.value if isinstance(question.value, str) else question.value['filename'] + if '.' not in filename or '.' + filename.split('.')[-1] not in question.accept: + raise YunohostValidationError( + "app_argument_invalid", field=question.name, error=m18n.n("invalid_number2") + ) + + + def _post_parse_value(self, question): + from base64 import b64decode + # Upload files from API + # A file arg contains a string with "FILENAME:BASE64_CONTENT" + if not question.value: + return question.value + + if Moulinette.interface.type== 'api': + + upload_dir = tempfile.mkdtemp(prefix='tmp_configpanel_') + FileArgumentParser.upload_dirs += [upload_dir] + filename = question.value['filename'] + logger.debug(f"Save uploaded file {question.value['filename']} from API into {upload_dir}") + + # Filename is given by user of the API. For security reason, we have replaced + # os.path.join to avoid the user to be able to rewrite a file in filesystem + # i.e. os.path.join("/foo", "/etc/passwd") == "/etc/passwd" + file_path = os.path.normpath(upload_dir + "/" + filename) + if not file_path.startswith(upload_dir + "/"): + raise YunohostError("relative_parent_path_in_filename_forbidden") + i = 2 + while os.path.exists(file_path): + file_path = os.path.normpath(upload_dir + "/" + filename + (".%d" % i)) + i += 1 + content = question.value['content'] + try: + with open(file_path, 'wb') as f: + f.write(b64decode(content)) + except IOError as e: + raise YunohostError("cannot_write_file", file=file_path, error=str(e)) + except Exception as e: + raise YunohostError("error_writing_file", file=file_path, error=str(e)) + question.value = file_path + return question.value + + +ARGUMENTS_TYPE_PARSERS = { + "string": StringArgumentParser, + "text": StringArgumentParser, + "select": StringArgumentParser, + "tags": TagsArgumentParser, + "email": StringArgumentParser, + "url": StringArgumentParser, + "date": StringArgumentParser, + "time": StringArgumentParser, + "color": StringArgumentParser, + "password": PasswordArgumentParser, + "path": PathArgumentParser, + "boolean": BooleanArgumentParser, + "domain": DomainArgumentParser, + "user": UserArgumentParser, + "number": NumberArgumentParser, + "range": NumberArgumentParser, + "display_text": DisplayTextArgumentParser, + "alert": DisplayTextArgumentParser, + "markdown": DisplayTextArgumentParser, + "file": FileArgumentParser, +} + +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 + """ + parsed_answers_dict = OrderedDict() + + for question in argument_questions: + parser = ARGUMENTS_TYPE_PARSERS[question.get("type", "string")]() + + answer = parser.parse(question=question, user_answers=user_answers) + if answer is not None: + parsed_answers_dict[question["name"]] = answer + + return parsed_answers_dict + diff --git a/src/yunohost/utils/i18n.py b/src/yunohost/utils/i18n.py new file mode 100644 index 000000000..89d1d0b34 --- /dev/null +++ b/src/yunohost/utils/i18n.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- + +""" License + + Copyright (C) 2018 YUNOHOST.ORG + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program; if not, see http://www.gnu.org/licenses + +""" +from moulinette import Moulinette, m18n + +def _value_for_locale(values): + """ + Return proper value for current locale + + Keyword arguments: + values -- A dict of values associated to their locale + + Returns: + An utf-8 encoded string + + """ + if not isinstance(values, dict): + return values + + for lang in [m18n.locale, m18n.default_locale]: + try: + return values[lang] + except KeyError: + continue + + # Fallback to first value + return list(values.values())[0] + + From 0874e9a646cb861a15d201214d1666378fe9ad99 Mon Sep 17 00:00:00 2001 From: ljf Date: Fri, 3 Sep 2021 17:07:34 +0200 Subject: [PATCH 2891/3170] [wip] Config Panel for domain --- data/actionsmap/yunohost.yml | 63 +++- data/other/config_domain.toml | 38 ++ data/other/registrar_list.toml | 636 +++++++++++++++++++++++++++++++++ src/yunohost/domain.py | 129 ++----- src/yunohost/utils/config.py | 30 +- 5 files changed, 783 insertions(+), 113 deletions(-) create mode 100644 data/other/config_domain.toml create mode 100644 data/other/registrar_list.toml diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 5d1b757bd..ea4a1f577 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -467,6 +467,17 @@ domain: help: Target domain extra: pattern: *pattern_domain + + ### domain_push_config() + push-config: + action_help: Push DNS records to registrar + api: GET /domains//push + arguments: + domain: + help: Domain name to add + extra: + pattern: *pattern_domain + ### domain_maindomain() main-domain: @@ -549,26 +560,40 @@ domain: path: help: The path to check (e.g. /coffee) - ### domain_setting() - setting: - action_help: Set or get a domain setting value - api: GET /domains//settings - arguments: - domain: - help: Domain name - extra: - pattern: *pattern_domain - key: - help: Key to get/set - -v: - full: --value - help: Value to set - -d: - full: --delete - help: Delete the key - action: store_true - subcategories: + + config: + subcategory_help: Domain settings + actions: + + ### domain_config_get() + get: + action_help: Display a domain configuration + api: GET /domains//config + arguments: + domain: + help: Domain name + key: + help: A question or form key + nargs: '?' + + ### domain_config_set() + set: + action_help: Apply a new configuration + api: PUT /domains//config + arguments: + app: + help: Domain name + key: + help: The question or form key + nargs: '?' + -v: + full: --value + help: new value + -a: + full: --args + help: Serialized arguments for new configuration (i.e. "mail_in=0&mail_out=0") + registrar: subcategory_help: Manage domains registrars actions: diff --git a/data/other/config_domain.toml b/data/other/config_domain.toml new file mode 100644 index 000000000..4821aa53b --- /dev/null +++ b/data/other/config_domain.toml @@ -0,0 +1,38 @@ +version = "1.0" +i18n = "domain_config" + +[feature] + [feature.mail] + [feature.mail.mail_out] + type = "boolean" + default = true + + [feature.mail.mail_in] + type = "boolean" + default = true + + [feature.mail.backup_mx] + type = "tags" + pattern.regexp = "^([^\W_A-Z]+([-]*[^\W_A-Z]+)*\.)+((xn--)?[^\W_]{2,})$" + pattern.error = "pattern_error" + default = [] + + [feature.xmpp] + [feature.mail.xmpp] + type = "boolean" + default = false + +[dns] + [dns.registrar] + [dns.registrar.unsupported] + ask = "DNS zone of this domain can't be auto-configured, you should do it manually." + type = "alert" + style = "info" + helpLink.href = "https://yunohost.org/dns_config" + helpLink.text = "How to configure manually my DNS zone" + + [dns.advanced] + [dns.advanced.ttl] + type = "number" + min = 0 + default = 3600 diff --git a/data/other/registrar_list.toml b/data/other/registrar_list.toml new file mode 100644 index 000000000..33d115075 --- /dev/null +++ b/data/other/registrar_list.toml @@ -0,0 +1,636 @@ +[aliyun] + [aliyun.auth_key_id] + type = "string" + redact = True + + [aliyun.auth_secret] + type = "password" + +[aurora] + [aurora.auth_api_key] + type = "string" + redact = True + + [aurora.auth_secret_key] + type = "password" + +[azure] + [azure.auth_client_id] + type = "string" + redact = True + + [azure.auth_client_secret] + type = "password" + + [azure.auth_tenant_id] + type = "string" + redact = True + + [azure.auth_subscription_id] + type = "string" + redact = True + + [azure.resource_group] + type = "string" + redact = True + +[cloudflare] + [cloudflare.auth_username] + type = "string" + redact = True + + [cloudflare.auth_token] + type = "string" + redact = True + + [cloudflare.zone_id] + type = "string" + redact = True + +[cloudns] + [cloudns.auth_id] + type = "string" + redact = True + + [cloudns.auth_subid] + type = "string" + redact = True + + [cloudns.auth_subuser] + type = "string" + redact = True + + [cloudns.auth_password] + type = "password" + + [cloudns.weight] + type = "number" + + [cloudns.port] + type = "number" +[cloudxns] + [cloudxns.auth_username] + type = "string" + redact = True + + [cloudxns.auth_token] + type = "string" + redact = True + +[conoha] + [conoha.auth_region] + type = "string" + redact = True + + [conoha.auth_token] + type = "string" + redact = True + + [conoha.auth_username] + type = "string" + redact = True + + [conoha.auth_password] + type = "password" + + [conoha.auth_tenant_id] + type = "string" + redact = True + +[constellix] + [constellix.auth_username] + type = "string" + redact = True + + [constellix.auth_token] + type = "string" + redact = True + +[digitalocean] + [digitalocean.auth_token] + type = "string" + redact = True + +[dinahosting] + [dinahosting.auth_username] + type = "string" + redact = True + + [dinahosting.auth_password] + type = "password" + +[directadmin] + [directadmin.auth_password] + type = "password" + + [directadmin.auth_username] + type = "string" + redact = True + + [directadmin.endpoint] + type = "string" + redact = True + +[dnsimple] + [dnsimple.auth_token] + type = "string" + redact = True + + [dnsimple.auth_username] + type = "string" + redact = True + + [dnsimple.auth_password] + type = "password" + + [dnsimple.auth_2fa] + type = "string" + redact = True + +[dnsmadeeasy] + [dnsmadeeasy.auth_username] + type = "string" + redact = True + + [dnsmadeeasy.auth_token] + type = "string" + redact = True + +[dnspark] + [dnspark.auth_username] + type = "string" + redact = True + + [dnspark.auth_token] + type = "string" + redact = True + +[dnspod] + [dnspod.auth_username] + type = "string" + redact = True + + [dnspod.auth_token] + type = "string" + redact = True + +[dreamhost] + [dreamhost.auth_token] + type = "string" + redact = True + +[dynu] + [dynu.auth_token] + type = "string" + redact = True + +[easydns] + [easydns.auth_username] + type = "string" + redact = True + + [easydns.auth_token] + type = "string" + redact = True + +[easyname] + [easyname.auth_username] + type = "string" + redact = True + + [easyname.auth_password] + type = "password" + +[euserv] + [euserv.auth_username] + type = "string" + redact = True + + [euserv.auth_password] + type = "password" + +[exoscale] + [exoscale.auth_key] + type = "string" + redact = True + + [exoscale.auth_secret] + type = "password" + +[gandi] + [gandi.auth_token] + type = "string" + redact = True + + [gandi.api_protocol] + type = "string" + choices.rpc = "RPC" + choices.rest = "REST" + +[gehirn] + [gehirn.auth_token] + type = "string" + redact = True + + [gehirn.auth_secret] + type = "password" + +[glesys] + [glesys.auth_username] + type = "string" + redact = True + + [glesys.auth_token] + type = "string" + redact = True + +[godaddy] + [godaddy.auth_key] + type = "string" + redact = True + + [godaddy.auth_secret] + type = "password" + +[googleclouddns] + [goggleclouddns.auth_service_account_info] + type = "string" + redact = True + +[gransy] + [gransy.auth_username] + type = "string" + redact = True + + [gransy.auth_password] + type = "password" + +[gratisdns] + [gratisdns.auth_username] + type = "string" + redact = True + + [gratisdns.auth_password] + type = "password" + +[henet] + [henet.auth_username] + type = "string" + redact = True + + [henet.auth_password] + type = "password" + +[hetzner] + [hetzner.auth_token] + type = "string" + redact = True + +[hostingde] + [hostingde.auth_token] + type = "string" + redact = True + +[hover] + [hover.auth_username] + type = "string" + redact = True + + [hover.auth_password] + type = "password" + +[infoblox] + [infoblox.auth_user] + type = "string" + redact = True + + [infoblox.auth_psw] + type = "password" + + [infoblox.ib_view] + type = "string" + redact = True + + [infoblox.ib_host] + type = "string" + redact = True + +[infomaniak] + [infomaniak.auth_token] + type = "string" + redact = True + +[internetbs] + [internetbs.auth_key] + type = "string" + redact = True + + [internetbs.auth_password] + type = "string" + redact = True + +[inwx] + [inwx.auth_username] + type = "string" + redact = True + + [inwx.auth_password] + type = "password" + +[joker] + [joker.auth_token] + type = "string" + redact = True + +[linode] + [linode.auth_token] + type = "string" + redact = True + +[linode4] + [linode4.auth_token] + type = "string" + redact = True + +[localzone] + [localzone.filename] + type = "string" + redact = True + +[luadns] + [luadns.auth_username] + type = "string" + redact = True + + [luadns.auth_token] + type = "string" + redact = True + +[memset] + [memset.auth_token] + type = "string" + redact = True + +[mythicbeasts] + [mythicbeasts.auth_username] + type = "string" + redact = True + + [mythicbeasts.auth_password] + type = "password" + + [mythicbeasts.auth_token] + type = "string" + redact = True + +[namecheap] + [namecheap.auth_token] + type = "string" + redact = True + + [namecheap.auth_username] + type = "string" + redact = True + + [namecheap.auth_client_ip] + type = "string" + redact = True + + [namecheap.auth_sandbox] + type = "string" + redact = True + +[namesilo] + [namesilo.auth_token] + type = "string" + redact = True + +[netcup] + [netcup.auth_customer_id] + type = "string" + redact = True + + [netcup.auth_api_key] + type = "string" + redact = True + + [netcup.auth_api_password] + type = "password" + +[nfsn] + [nfsn.auth_username] + type = "string" + redact = True + + [nfsn.auth_token] + type = "string" + redact = True + +[njalla] + [njalla.auth_token] + type = "string" + redact = True + +[nsone] + [nsone.auth_token] + type = "string" + redact = True + +[onapp] + [onapp.auth_username] + type = "string" + redact = True + + [onapp.auth_token] + type = "string" + redact = True + + [onapp.auth_server] + type = "string" + redact = True + +[online] + [online.auth_token] + type = "string" + redact = True + +[ovh] + [ovh.auth_entrypoint] + type = "string" + redact = True + + [ovh.auth_application_key] + type = "string" + redact = True + + [ovh.auth_application_secret] + type = "password" + + [ovh.auth_consumer_key] + type = "string" + redact = True + +[plesk] + [plesk.auth_username] + type = "string" + redact = True + + [plesk.auth_password] + type = "password" + + [plesk.plesk_server] + type = "string" + redact = True + +[pointhq] + [pointhq.auth_username] + type = "string" + redact = True + + [pointhq.auth_token] + type = "string" + redact = True + +[powerdns] + [powerdns.auth_token] + type = "string" + redact = True + + [powerdns.pdns_server] + type = "string" + redact = True + + [powerdns.pdns_server_id] + type = "string" + redact = True + + [powerdns.pdns_disable_notify] + type = "boolean" + +[rackspace] + [rackspace.auth_account] + type = "string" + redact = True + + [rackspace.auth_username] + type = "string" + redact = True + + [rackspace.auth_api_key] + type = "string" + redact = True + + [rackspace.auth_token] + type = "string" + redact = True + + [rackspace.sleep_time] + type = "string" + redact = True + +[rage4] + [rage4.auth_username] + type = "string" + redact = True + + [rage4.auth_token] + type = "string" + redact = True + +[rcodezero] + [rcodezero.auth_token] + type = "string" + redact = True + +[route53] + [route53.auth_access_key] + type = "string" + redact = True + + [route53.auth_access_secret] + type = "password" + + [route53.private_zone] + type = "string" + redact = True + + [route53.auth_username] + type = "string" + redact = True + + [route53.auth_token] + type = "string" + redact = True + +[safedns] + [safedns.auth_token] + type = "string" + redact = True + +[sakuracloud] + [sakuracloud.auth_token] + type = "string" + redact = True + + [sakuracloud.auth_secret] + type = "password" + +[softlayer] + [softlayer.auth_username] + type = "string" + redact = True + + [softlayer.auth_api_key] + type = "string" + redact = True + +[transip] + [transip.auth_username] + type = "string" + redact = True + + [transip.auth_api_key] + type = "string" + redact = True + +[ultradns] + [ultradns.auth_token] + type = "string" + redact = True + + [ultradns.auth_username] + type = "string" + redact = True + + [ultradns.auth_password] + type = "password" + +[vultr] + [vultr.auth_token] + type = "string" + redact = True + +[yandex] + [yandex.auth_token] + type = "string" + redact = True + +[zeit] + [zeit.auth_token] + type = "string" + redact = True + +[zilore] + [zilore.auth_key] + type = "string" + redact = True + +[zonomi] + [zonomy.auth_token] + type = "string" + redact = True + + [zonomy.auth_entrypoint] + type = "string" + redact = True + diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index d05e31f17..59ad68979 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -387,115 +387,58 @@ def _get_maindomain(): return maindomain -def _default_domain_settings(domain): - from yunohost.utils.dns import get_dns_zone_from_domain - return { - "xmpp": domain == domain_list()["main"], - "mail_in": True, - "mail_out": True, - "dns_zone": get_dns_zone_from_domain(domain), - "ttl": 3600, - } - - def _get_domain_settings(domain): """ Retrieve entries in /etc/yunohost/domains/[domain].yml And set default values if needed """ - _assert_domain_exists(domain) - - # Retrieve entries in the YAML - filepath = f"{DOMAIN_SETTINGS_DIR}/{domain}.yml" - on_disk_settings = {} - if os.path.exists(filepath) and os.path.isfile(filepath): - on_disk_settings = read_yaml(filepath) or {} - - # Inject defaults if needed (using the magic .update() ;)) - settings = _default_domain_settings(domain) - settings.update(on_disk_settings) - return settings + config = DomainConfigPanel(domain) + return config.get(mode='export') -def domain_setting(domain, key, value=None, delete=False): +def domain_config_get(domain, key='', mode='classic'): """ - Set or get an app setting value - - Keyword argument: - domain -- Domain Name - key -- Key to get/set - value -- Value to set - delete -- Delete the key - + Display a domain configuration """ - domain_settings = _get_domain_settings(domain) + config = DomainConfigPanel(domain) + return config.get(key, mode) - # GET - if value is None and not delete: - if key not in domain_settings: - raise YunohostValidationError("domain_property_unknown", property=key) - - return domain_settings[key] - - # DELETE - if delete: - if key in domain_settings: - del domain_settings[key] - _set_domain_settings(domain, domain_settings) - - # SET - else: - # FIXME : in the future, implement proper setting types (+ defaults), - # maybe inspired from the global settings - - if key in ["mail_in", "mail_out", "xmpp"]: - _is_boolean, value = is_boolean(value) - if not _is_boolean: - raise YunohostValidationError( - "global_settings_bad_type_for_setting", - setting=key, - received_type="not boolean", - expected_type="boolean", - ) - - if "ttl" == key: - try: - value = int(value) - except ValueError: - # TODO add locales - raise YunohostValidationError("invalid_number", value_type=type(value)) - - if value < 0: - raise YunohostValidationError("pattern_positive_number", value_type=type(value)) - - # Set new value - domain_settings[key] = value - # Save settings - _set_domain_settings(domain, domain_settings) - - -def _set_domain_settings(domain, domain_settings): +@is_unit_operation() +def domain_config_set(operation_logger, app, key=None, value=None, args=None, args_file=None): """ - Set settings of a domain - - Keyword arguments: - domain -- The domain name - settings -- Dict with domain settings - + Apply a new domain configuration """ - _assert_domain_exists(domain) + config = DomainConfigPanel(domain) + return config.set(key, value, args, args_file) - defaults = _default_domain_settings(domain) - diff_with_defaults = {k: v for k, v in domain_settings.items() if defaults.get(k) != v} +class DomainConfigPanel(ConfigPanel): + def __init__(domain): + _assert_domain_exist(domain) + self.domain = domain + super().__init( + config_path=DOMAIN_CONFIG_PATH.format(domain=domain), + save_path=DOMAIN_SETTINGS_PATH.format(domain=domain) + ) - # First create the DOMAIN_SETTINGS_DIR if it doesn't exist - if not os.path.exists(DOMAIN_SETTINGS_DIR): - mkdir(DOMAIN_SETTINGS_DIR, mode=0o700) - # Save the settings to the .yaml file - filepath = f"{DOMAIN_SETTINGS_DIR}/{domain}.yml" - write_to_yaml(filepath, diff_with_defaults) + def _get_toml(self): + from yunohost.utils.dns import get_dns_zone_from_domain + toml = super()._get_toml() + self.dns_zone = get_dns_zone_from_domain(self.domain) + + try: + registrar = _relevant_provider_for_domain(self.dns_zone) + except ValueError: + return toml + + registrar_list = read_toml("/usr/share/yunohost/other/registrar_list.toml") + toml['dns']['registrar'] = registrar_list[registrar] + return toml + + def _load_current_values(): + # TODO add mechanism to share some settings with other domains on the same zone + super()._load_current_values() # # diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 34883dcf7..b3ef34c17 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -46,6 +46,7 @@ logger = getActionLogger("yunohost.config") CONFIG_PANEL_VERSION_SUPPORTED = 1.0 class ConfigPanel: + save_mode = "diff" def __init__(self, config_path, save_path=None): self.config_path = config_path @@ -56,6 +57,7 @@ class ConfigPanel: def get(self, key='', mode='classic'): self.filter_key = key or '' + self.mode = mode # Read config panel toml self._get_config_panel() @@ -273,13 +275,39 @@ class ConfigPanel: )) self.new_values = {key: str(value[0]) for key, value in self.new_values.items() if not value[0] is None} + def _get_default_values(self): + return { key: option['default'] + for _, _, option in self._iterate() if 'default' in option } + + def _load_current_values(self): + """ + Retrieve entries in YAML file + And set default values if needed + """ + + # Retrieve entries in the YAML + on_disk_settings = {} + if os.path.exists(self.save_path) and os.path.isfile(self.save_path): + on_disk_settings = read_yaml(self.save_path) or {} + + # Inject defaults if needed (using the magic .update() ;)) + self.values = self._get_default_values(self) + self.values.update(on_disk_settings) + def _apply(self): logger.info("Running config script...") dir_path = os.path.dirname(os.path.realpath(self.save_path)) if not os.path.exists(dir_path): mkdir(dir_path, mode=0o700) + + if self.save_mode == 'diff': + defaults = self._get_default_values() + values_to_save = {k: v for k, v in values.items() if defaults.get(k) != v} + else: + values_to_save = {**self.values, **self.new_values} + # Save the settings to the .yaml file - write_to_yaml(self.save_path, self.new_values) + write_to_yaml(self.save_path, values_to_save) def _reload_services(self): From 31631794641253a3439d0f35ee4c06ed92f2258c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 3 Sep 2021 17:13:31 +0200 Subject: [PATCH 2892/3170] config helpers: get_var / set_var -> read/write_var_in_file --- data/helpers.d/config | 4 ++-- data/helpers.d/utils | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/data/helpers.d/config b/data/helpers.d/config index 52454ff91..6223a17b2 100644 --- a/data/helpers.d/config +++ b/data/helpers.d/config @@ -67,7 +67,7 @@ EOL local source_key="$(echo "$source" | cut -d: -f1)" source_key=${source_key:-$short_setting} local source_file="$(echo "$source" | cut -d: -f2 | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" - old[$short_setting]="$(ynh_get_var --file="${source_file}" --key="${source_key}")" + old[$short_setting]="$(ynh_read_var_in_file --file="${source_file}" --key="${source_key}")" fi done @@ -130,7 +130,7 @@ _ynh_app_config_apply() { local source_file="$(echo "$source" | cut -d: -f2 | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" ynh_backup_if_checksum_is_different --file="$source_file" - ynh_set_var --file="${source_file}" --key="${source_key}" --value="${!short_setting}" + ynh_write_var_in_file --file="${source_file}" --key="${source_key}" --value="${!short_setting}" ynh_store_file_checksum --file="$source_file" --update_only # We stored the info in settings in order to be able to upgrade the app diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 14e7ebe4a..3389101a6 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -475,7 +475,7 @@ ynh_replace_vars () { # Get a value from heterogeneous file (yaml, json, php, python...) # -# usage: ynh_get_var --file=PATH --key=KEY +# usage: ynh_read_var_in_file --file=PATH --key=KEY # | arg: -f, --file= - the path to the file # | arg: -k, --key= - the key to get # @@ -504,8 +504,9 @@ ynh_replace_vars () { # USER = 8102 # user = 'https://donate.local' # CUSTOM['user'] = 'YunoHost' +# # Requires YunoHost version 4.3 or higher. -ynh_get_var() { +ynh_read_var_in_file() { # Declare an array to define the options of this helper. local legacy_args=fk local -A args_array=( [f]=file= [k]=key= ) @@ -515,10 +516,9 @@ ynh_get_var() { ynh_handle_getopts_args "$@" local var_part='^[ \t]*\$?(\w*\[)?[ \t]*["'"']?${key}['"'"]?[ \t]*\]?[ \t]*[:=]>?[ \t]*' - - local crazy_value="$((grep -i -o -P '^[ \t]*\$?(\w*\[)?[ \t]*["'"']?${key}['"'"]?[ \t]*\]?[ \t]*[:=]>?[ \t]*\K.*(?=[ \t,\n;]*$)' ${file} || echo YNH_NULL) | head -n1)" - #" - + + local crazy_value="$(grep -i -o -P '^[ \t]*\$?(\w*\[)?[ \t]*["'"']?${key}['"'"]?[ \t]*\]?[ \t]*[:=]>?[ \t]*\K.*(?=[ \t,\n;]*$)' ${file} || echo YNH_NULL | head -n1)" + local first_char="${crazy_value:0:1}" if [[ "$first_char" == '"' ]] ; then echo "$crazy_value" | grep -m1 -o -P '"\K([^"](\\")?)*[^\\](?=")' | head -n1 | sed 's/\\"/"/g' @@ -531,13 +531,13 @@ ynh_get_var() { # Set a value into heterogeneous file (yaml, json, php, python...) # -# usage: ynh_set_var --file=PATH --key=KEY --value=VALUE +# usage: ynh_write_var_in_file --file=PATH --key=KEY --value=VALUE # | arg: -f, --file= - the path to the file # | arg: -k, --key= - the key to set # | arg: -v, --value= - the value to set # # Requires YunoHost version 4.3 or higher. -ynh_set_var() { +ynh_write_var_in_file() { # Declare an array to define the options of this helper. local legacy_args=fkv local -A args_array=( [f]=file= [k]=key= [v]=value=) @@ -547,7 +547,7 @@ ynh_set_var() { # Manage arguments with getopts ynh_handle_getopts_args "$@" local var_part='[ \t]*\$?(\w*\[)?[ \t]*["'"']?${key}['"'"]?[ \t]*\]?[ \t]*[:=]>?[ \t]*' - + local crazy_value="$(grep -i -o -P '^[ \t]*\$?(\w*\[)?[ \t]*["'"']?${key}['"'"]?[ \t]*\]?[ \t]*[:=]>?[ \t]*\K.*(?=[ \t,\n;]*$)' ${file} | head -n1)" # local crazy_value="$(grep -i -o -P "^${var_part}\K.*(?=[ \t,\n;]*\$)" ${file} | head -n1)" local first_char="${crazy_value:0:1}" From a0f471065ce2d7637aa118c261dd1c8988c99e7c Mon Sep 17 00:00:00 2001 From: ljf Date: Fri, 3 Sep 2021 19:05:58 +0200 Subject: [PATCH 2893/3170] [fix] Fix yunohost domain config get --- data/actionsmap/yunohost.yml | 173 ++++++++++++++++++++-------------- data/other/config_domain.toml | 7 +- src/yunohost/dns.py | 65 ------------- src/yunohost/domain.py | 37 +++----- src/yunohost/utils/config.py | 8 +- 5 files changed, 124 insertions(+), 166 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index ea4a1f577..0da811522 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -460,6 +460,7 @@ domain: ### domain_dns_conf() dns-conf: + deprecated: true action_help: Generate sample DNS configuration for a domain api: GET /domains//dns arguments: @@ -468,17 +469,6 @@ domain: extra: pattern: *pattern_domain - ### domain_push_config() - push-config: - action_help: Push DNS records to registrar - api: GET /domains//push - arguments: - domain: - help: Domain name to add - extra: - pattern: *pattern_domain - - ### domain_maindomain() main-domain: action_help: Check the current main domain, or change it @@ -496,6 +486,7 @@ domain: ### certificate_status() cert-status: + deprecated: true action_help: List status of current certificates (all by default). api: GET /domains//cert arguments: @@ -508,6 +499,7 @@ domain: ### certificate_install() cert-install: + deprecated: true action_help: Install Let's Encrypt certificates for given domains (all by default). api: PUT /domains//cert arguments: @@ -529,6 +521,7 @@ domain: ### certificate_renew() cert-renew: + deprecated: true action_help: Renew the Let's Encrypt certificates for given domains (all by default). api: PUT /domains//cert/renew arguments: @@ -562,76 +555,57 @@ domain: subcategories: - config: - subcategory_help: Domain settings - actions: - - ### domain_config_get() - get: - action_help: Display a domain configuration - api: GET /domains//config - arguments: - domain: - help: Domain name - key: - help: A question or form key - nargs: '?' - - ### domain_config_set() - set: - action_help: Apply a new configuration - api: PUT /domains//config - arguments: - app: - help: Domain name - key: - help: The question or form key - nargs: '?' - -v: - full: --value - help: new value - -a: - full: --args - help: Serialized arguments for new configuration (i.e. "mail_in=0&mail_out=0") - - registrar: - subcategory_help: Manage domains registrars + config: + subcategory_help: Domain settings actions: - ### domain_registrar_catalog() - catalog: - action_help: List supported registrars API - api: GET /domains/registrars/catalog + ### domain_config_get() + get: + action_help: Display a domain configuration + api: GET /domains//config + arguments: + domain: + help: Domain name + key: + help: A question or form key + nargs: '?' - ### domain_registrar_set() + ### domain_config_set() set: - action_help: Set domain registrar - api: POST /domains//registrar + action_help: Apply a new configuration + api: PUT /domains//config + arguments: + app: + help: Domain name + key: + help: The question or form key + nargs: '?' + -v: + full: --value + help: new value + -a: + full: --args + help: Serialized arguments for new configuration (i.e. "mail_in=0&mail_out=0") + + dns: + subcategory_help: Manage domains DNS + actions: + ### domain_dns_conf() + suggest: + action_help: Generate sample DNS configuration for a domain + api: + - GET /domains//dns + - GET /domains//dns/suggest arguments: domain: - help: Domain name + help: Target domain extra: pattern: *pattern_domain - registrar: - help: registrar_key, see yunohost domain registrar list - -a: - full: --args - help: Serialized arguments for registrar API (i.e. "auth_token=TOKEN&auth_username=USER"). - - ### domain_registrar_info() - info: - action_help: Display info about registrar settings used for a domain - api: GET /domains//registrar - arguments: - domain: - help: Domain name - extra: - pattern: *pattern_domain - - ### domain_registrar_push() + + ### domain_dns_push() push: action_help: Push DNS records to registrar - api: PUT /domains//registrar/push + api: POST /domains//dns/push arguments: domain: help: Domain name to push DNS conf for @@ -642,6 +616,63 @@ domain: help: Only display what's to be pushed action: store_true + cert: + subcategory_help: Manage domains DNS + actions: + ### certificate_status() + status: + action_help: List status of current certificates (all by default). + api: GET /domains//cert + arguments: + domain_list: + help: Domains to check + nargs: "*" + --full: + help: Show more details + action: store_true + + ### certificate_install() + install: + action_help: Install Let's Encrypt certificates for given domains (all by default). + api: PUT /domains//cert + arguments: + domain_list: + help: Domains for which to install the certificates + nargs: "*" + --force: + help: Install even if current certificate is not self-signed + action: store_true + --no-checks: + help: Does not perform any check that your domain seems correctly configured (DNS, reachability) before attempting to install. (Not recommended) + action: store_true + --self-signed: + help: Install self-signed certificate instead of Let's Encrypt + action: store_true + --staging: + help: Use the fake/staging Let's Encrypt certification authority. The new certificate won't actually be enabled - it is only intended to test the main steps of the procedure. + action: store_true + + ### certificate_renew() + renew: + action_help: Renew the Let's Encrypt certificates for given domains (all by default). + api: PUT /domains//cert/renew + arguments: + domain_list: + help: Domains for which to renew the certificates + nargs: "*" + --force: + help: Ignore the validity threshold (30 days) + action: store_true + --email: + help: Send an email to root with logs if some renewing fails + action: store_true + --no-checks: + help: Does not perform any check that your domain seems correctly configured (DNS, reachability) before attempting to renew. (Not recommended) + action: store_true + --staging: + help: Use the fake/staging Let's Encrypt certification authority. The new certificate won't actually be enabled - it is only intended to test the main steps of the procedure. + action: store_true + ############################# # App # diff --git a/data/other/config_domain.toml b/data/other/config_domain.toml index 4821aa53b..b2211417d 100644 --- a/data/other/config_domain.toml +++ b/data/other/config_domain.toml @@ -13,12 +13,13 @@ i18n = "domain_config" [feature.mail.backup_mx] type = "tags" - pattern.regexp = "^([^\W_A-Z]+([-]*[^\W_A-Z]+)*\.)+((xn--)?[^\W_]{2,})$" - pattern.error = "pattern_error" default = [] + pattern.regexp = '^([^\W_A-Z]+([-]*[^\W_A-Z]+)*\.)+((xn--)?[^\W_]{2,})$' + pattern.error = "pattern_error" [feature.xmpp] - [feature.mail.xmpp] + + [feature.xmpp.xmpp] type = "boolean" default = false diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index 41c6e73f1..df3b578db 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -39,9 +39,6 @@ from yunohost.hook import hook_callback logger = getActionLogger("yunohost.domain") -REGISTRAR_SETTINGS_DIR = "/etc/yunohost/registrars" -REGISTRAR_LIST_PATH = "/usr/share/yunohost/other/registrar_list.yml" - def domain_dns_conf(domain): """ @@ -389,68 +386,6 @@ def _get_DKIM(domain): ) -def _get_registrar_settings(dns_zone): - on_disk_settings = {} - filepath = f"{REGISTRAR_SETTINGS_DIR}/{dns_zone}.yml" - if os.path.exists(filepath) and os.path.isfile(filepath): - on_disk_settings = read_yaml(filepath) or {} - - return on_disk_settings - - -def _set_registrar_settings(dns_zone, domain_registrar): - if not os.path.exists(REGISTRAR_SETTINGS_DIR): - mkdir(REGISTRAR_SETTINGS_DIR, mode=0o700) - filepath = f"{REGISTRAR_SETTINGS_DIR}/{dns_zone}.yml" - write_to_yaml(filepath, domain_registrar) - - -def domain_registrar_info(domain): - - dns_zone = _get_domain_settings(domain)["dns_zone"] - registrar_info = _get_registrar_settings(dns_zone) - if not registrar_info: - raise YunohostValidationError("registrar_is_not_set", dns_zone=dns_zone) - - return registrar_info - - -def domain_registrar_catalog(): - return read_yaml(REGISTRAR_LIST_PATH) - - -def domain_registrar_set(domain, registrar, args): - - _assert_domain_exists(domain) - - registrars = read_yaml(REGISTRAR_LIST_PATH) - if registrar not in registrars.keys(): - raise YunohostValidationError("domain_registrar_unknown", registrar=registrar) - - parameters = registrars[registrar] - ask_args = [] - for parameter in parameters: - ask_args.append( - { - "name": parameter, - "type": "string", - "example": "", - "default": "", - } - ) - args_dict = ( - {} if not args else dict(urllib.parse.parse_qsl(args, keep_blank_values=True)) - ) - parsed_answer_dict = _parse_args_in_yunohost_format(args_dict, ask_args) - - domain_registrar = {"name": registrar, "options": {}} - for arg_name, arg_value_and_type in parsed_answer_dict.items(): - domain_registrar["options"][arg_name] = arg_value_and_type[0] - - dns_zone = _get_domain_settings(domain)["dns_zone"] - _set_registrar_settings(dns_zone, domain_registrar) - - @is_unit_operation() def domain_registrar_push(operation_logger, domain, dry_run=False): """ diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 59ad68979..064311a49 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -38,14 +38,16 @@ from yunohost.app import ( _get_conflicting_apps, ) from yunohost.regenconf import regen_conf, _force_clear_hashes, _process_regen_conf +from yunohost.utils.config import ConfigPanel from yunohost.utils.error import YunohostError, YunohostValidationError from yunohost.log import is_unit_operation from yunohost.hook import hook_callback logger = getActionLogger("yunohost.domain") +DOMAIN_CONFIG_PATH = "/usr/share/yunohost/other/config_domain.toml" DOMAIN_SETTINGS_DIR = "/etc/yunohost/domains" - +DOMAIN_REGISTRAR_LIST_PATH = "/usr/share/yunohost/other/registrar_list.toml" # Lazy dev caching to avoid re-query ldap every time we need the domain list domain_list_cache = {} @@ -413,16 +415,18 @@ def domain_config_set(operation_logger, app, key=None, value=None, args=None, ar config = DomainConfigPanel(domain) return config.set(key, value, args, args_file) + class DomainConfigPanel(ConfigPanel): - def __init__(domain): - _assert_domain_exist(domain) + def __init__(self, domain): + _assert_domain_exists(domain) self.domain = domain - super().__init( - config_path=DOMAIN_CONFIG_PATH.format(domain=domain), - save_path=DOMAIN_SETTINGS_PATH.format(domain=domain) + super().__init__( + config_path=DOMAIN_CONFIG_PATH, + save_path=f"{DOMAIN_SETTINGS_DIR}/{domain}.yml" ) def _get_toml(self): + from lexicon.providers.auto import _relevant_provider_for_domain from yunohost.utils.dns import get_dns_zone_from_domain toml = super()._get_toml() self.dns_zone = get_dns_zone_from_domain(self.domain) @@ -432,11 +436,11 @@ class DomainConfigPanel(ConfigPanel): except ValueError: return toml - registrar_list = read_toml("/usr/share/yunohost/other/registrar_list.toml") + registrar_list = read_toml(DOMAIN_REGISTRAR_LIST_PATH) toml['dns']['registrar'] = registrar_list[registrar] return toml - def _load_current_values(): + def _load_current_values(self): # TODO add mechanism to share some settings with other domains on the same zone super()._load_current_values() @@ -478,21 +482,6 @@ def domain_dns_conf(domain): return yunohost.dns.domain_dns_conf(domain) -def domain_registrar_catalog(): - import yunohost.dns - return yunohost.dns.domain_registrar_catalog() - - -def domain_registrar_set(domain, registrar, args): - import yunohost.dns - return yunohost.dns.domain_registrar_set(domain, registrar, args) - - -def domain_registrar_info(domain): - import yunohost.dns - return yunohost.dns.domain_registrar_info(domain) - - -def domain_registrar_push(domain, dry_run): +def domain_dns_push(domain, dry_run): import yunohost.dns return yunohost.dns.domain_registrar_push(domain, dry_run) diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index b3ef34c17..b8ba489e6 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -85,8 +85,10 @@ class ConfigPanel: key = f"{panel['id']}.{section['id']}.{option['id']}" if mode == 'export': result[option['id']] = option.get('current_value') - else: + elif 'ask' in option: result[key] = { 'ask': _value_for_locale(option['ask']) } + elif 'i18n' in self.config: + result[key] = { 'ask': m18n.n(self.config['i18n'] + '_' + option['id']) } if 'current_value' in option: result[key]['value'] = option['current_value'] @@ -276,7 +278,7 @@ class ConfigPanel: self.new_values = {key: str(value[0]) for key, value in self.new_values.items() if not value[0] is None} def _get_default_values(self): - return { key: option['default'] + return { option['id']: option['default'] for _, _, option in self._iterate() if 'default' in option } def _load_current_values(self): @@ -291,7 +293,7 @@ class ConfigPanel: on_disk_settings = read_yaml(self.save_path) or {} # Inject defaults if needed (using the magic .update() ;)) - self.values = self._get_default_values(self) + self.values = self._get_default_values() self.values.update(on_disk_settings) def _apply(self): From d74bc485ddaf06e2ca7e1834a16f3a2027e15948 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 3 Sep 2021 20:23:12 +0200 Subject: [PATCH 2894/3170] helpers/config: Add unit tests for read/write var from json/php/yaml --- tests/test_helpers.d/ynhtest_config.sh | 394 +++++++++++++++++++++++++ 1 file changed, 394 insertions(+) create mode 100644 tests/test_helpers.d/ynhtest_config.sh diff --git a/tests/test_helpers.d/ynhtest_config.sh b/tests/test_helpers.d/ynhtest_config.sh new file mode 100644 index 000000000..69e715229 --- /dev/null +++ b/tests/test_helpers.d/ynhtest_config.sh @@ -0,0 +1,394 @@ +_make_dummy_files() { + + local_dummy_dir="$1" + + cat << EOF > $dummy_dir/dummy.ini +# Some comment +foo = +enabled = False +# title = Old title +title = Lorem Ipsum +email = root@example.com +theme = colib'ris + port = 1234 +url = https://yunohost.org +[dict] + ldap_base = ou=users,dc=yunohost,dc=org +EOF + + cat << EOF > $dummy_dir/dummy.py +# Some comment +FOO = None +ENABLED = False +# TITLE = "Old title" +TITLE = "Lorem Ipsum" +THEME = "colib'ris" +EMAIL = "root@example.com" +PORT = 1234 +URL = 'https://yunohost.org' +DICT['ldap_base'] = "ou=users,dc=yunohost,dc=org" +EOF + +} + +_ynh_read_yaml_with_python() { + local file="$1" + local key="$2" + python3 -c "import yaml; print(yaml.safe_load(open('$file'))['$key'])" +} + +_ynh_read_json_with_python() { + local file="$1" + local key="$2" + python3 -c "import json; print(json.load(open('$file'))['$key'])" +} + +_ynh_read_php_with_php() { + local file="$1" + local key="$2" + php -r "include '$file'; echo var_export(\$$key);" | sed "s/^'//g" | sed "s/'$//g" +} + + +ynhtest_config_read_yaml() { + local dummy_dir="$(mktemp -d -p $VAR_WWW)" + file="$dummy_dir/dummy.yml" + + cat << EOF > $file +# Some comment +foo: +enabled: false +# title: old title +title: Lorem Ipsum +theme: colib'ris +email: root@example.com +port: 1234 +url: https://yunohost.org +dict: + ldap_base: ou=users,dc=yunohost,dc=org +EOF + + test "$(_ynh_read_yaml_with_python "$file" "foo")" == "None" + test "$(ynh_read_var_in_file "$file" "foo")" == "" + + test "$(_ynh_read_yaml_with_python "$file" "enabled")" == "False" + test "$(ynh_read_var_in_file "$file" "enabled")" == "false" + + test "$(_ynh_read_yaml_with_python "$file" "title")" == "Lorem Ipsum" + test "$(ynh_read_var_in_file "$file" "title")" == "Lorem Ipsum" + + test "$(_ynh_read_yaml_with_python "$file" "theme")" == "colib'ris" + test "$(ynh_read_var_in_file "$file" "theme")" == "colib'ris" + + test "$(_ynh_read_yaml_with_python "$file" "email")" == "root@example.com" + test "$(ynh_read_var_in_file "$file" "email")" == "root@example.com" + + test "$(_ynh_read_yaml_with_python "$file" "port")" == "1234" + test "$(ynh_read_var_in_file "$file" "port")" == "1234" + + test "$(_ynh_read_yaml_with_python "$file" "url")" == "https://yunohost.org" + test "$(ynh_read_var_in_file "$file" "url")" == "https://yunohost.org" + + test "$(ynh_read_var_in_file "$file" "ldap_base")" == "ou=users,dc=yunohost,dc=org" + + ! _ynh_read_yaml_with_python "$file" "nonexistent" + test "$(ynh_read_var_in_file "$file" "nonexistent")" == "YNH_NULL" + + ! _ynh_read_yaml_with_python "$file" "enable" + test "$(ynh_read_var_in_file "$file" "enable")" == "YNH_NULL" +} + + +ynhtest_config_write_yaml() { + local dummy_dir="$(mktemp -d -p $VAR_WWW)" + file="$dummy_dir/dummy.yml" + + cat << EOF > $file +# Some comment +foo: +enabled: false +# title: old title +title: Lorem Ipsum +theme: colib'ris +email: root@example.com +port: 1234 +url: https://yunohost.org +dict: + ldap_base: ou=users,dc=yunohost,dc=org +EOF + + + + #ynh_write_var_in_file "$file" "foo" "bar" + # cat $dummy_dir/dummy.yml # to debug + #! test "$(_ynh_read_yaml_with_python "$file" "foo")" == "bar" # FIXME FIXME FIXME : writing broke the yaml syntax... "foo:bar" (no space aftr :) + #test "$(ynh_read_var_in_file "$file" "foo")" == "bar" + + ynh_write_var_in_file "$file" "enabled" "true" + test "$(_ynh_read_yaml_with_python "$file" "enabled")" == "True" + test "$(ynh_read_var_in_file "$file" "enabled")" == "true" + + ynh_write_var_in_file "$file" "title" "Foo Bar" + test "$(_ynh_read_yaml_with_python "$file" "title")" == "Foo Bar" + test "$(ynh_read_var_in_file "$file" "title")" == "Foo Bar" + + ynh_write_var_in_file "$file" "theme" "super-awesome-theme" + test "$(_ynh_read_yaml_with_python "$file" "theme")" == "super-awesome-theme" + test "$(ynh_read_var_in_file "$file" "theme")" == "super-awesome-theme" + + ynh_write_var_in_file "$file" "email" "sam@domain.tld" + test "$(_ynh_read_yaml_with_python "$file" "email")" == "sam@domain.tld" + test "$(ynh_read_var_in_file "$file" "email")" == "sam@domain.tld" + + ynh_write_var_in_file "$file" "port" "5678" + test "$(_ynh_read_yaml_with_python "$file" "port")" == "5678" + test "$(ynh_read_var_in_file "$file" "port")" == "5678" + + ynh_write_var_in_file "$file" "url" "https://domain.tld/foobar" + test "$(_ynh_read_yaml_with_python "$file" "url")" == "https://domain.tld/foobar" + test "$(ynh_read_var_in_file "$file" "url")" == "https://domain.tld/foobar" + + ynh_write_var_in_file "$file" "ldap_base" "ou=foobar,dc=domain,dc=tld" + test "$(ynh_read_var_in_file "$file" "ldap_base")" == "ou=foobar,dc=domain,dc=tld" + + ynh_write_var_in_file "$file" "nonexistent" "foobar" + test "$(ynh_read_var_in_file "$file" "nonexistent")" == "YNH_NULL" + + ynh_write_var_in_file "$file" "enable" "foobar" + test "$(ynh_read_var_in_file "$file" "enable")" == "YNH_NULL" + test "$(ynh_read_var_in_file "$file" "enabled")" == "true" +} + +ynhtest_config_read_json() { + local dummy_dir="$(mktemp -d -p $VAR_WWW)" + file="$dummy_dir/dummy.json" + + cat << EOF > $file +{ + "foo": null, + "enabled": false, + "title": "Lorem Ipsum", + "theme": "colib'ris", + "email": "root@example.com", + "port": 1234, + "url": "https://yunohost.org", + "dict": { + "ldap_base": "ou=users,dc=yunohost,dc=org" + } +} +EOF + + + test "$(_ynh_read_json_with_python "$file" "foo")" == "None" + test "$(ynh_read_var_in_file "$file" "foo")" == "null," # FIXME FIXME FIXME trailing , + + test "$(_ynh_read_json_with_python "$file" "enabled")" == "False" + test "$(ynh_read_var_in_file "$file" "enabled")" == "false," # FIXME FIXME FIXME trailing , + + test "$(_ynh_read_json_with_python "$file" "title")" == "Lorem Ipsum" + test "$(ynh_read_var_in_file "$file" "title")" == "Lorem Ipsum" + + test "$(_ynh_read_json_with_python "$file" "theme")" == "colib'ris" + test "$(ynh_read_var_in_file "$file" "theme")" == "colib'ris" + + test "$(_ynh_read_json_with_python "$file" "email")" == "root@example.com" + test "$(ynh_read_var_in_file "$file" "email")" == "root@example.com" + + test "$(_ynh_read_json_with_python "$file" "port")" == "1234" + test "$(ynh_read_var_in_file "$file" "port")" == "1234," # FIXME FIXME FIXME trailing , + + test "$(_ynh_read_json_with_python "$file" "url")" == "https://yunohost.org" + test "$(ynh_read_var_in_file "$file" "url")" == "https://yunohost.org" + + test "$(ynh_read_var_in_file "$file" "ldap_base")" == "ou=users,dc=yunohost,dc=org" + + ! _ynh_read_json_with_python "$file" "nonexistent" + test "$(ynh_read_var_in_file "$file" "nonexistent")" == "YNH_NULL" + + ! _ynh_read_json_with_python "$file" "enable" + test "$(ynh_read_var_in_file "$file" "enable")" == "YNH_NULL" +} + + +ynhtest_config_write_json() { + local dummy_dir="$(mktemp -d -p $VAR_WWW)" + file="$dummy_dir/dummy.json" + + cat << EOF > $file +{ + "foo": null, + "enabled": false, + "title": "Lorem Ipsum", + "theme": "colib'ris", + "email": "root@example.com", + "port": 1234, + "url": "https://yunohost.org", + "dict": { + "ldap_base": "ou=users,dc=yunohost,dc=org" + } +} +EOF + + #ynh_write_var_in_file "$file" "foo" "bar" + #cat $file + #test "$(_ynh_read_json_with_python "$file" "foo")" == "bar" # FIXME FIXME FIXME + #test "$(ynh_read_var_in_file "$file" "foo")" == "bar" + + #ynh_write_var_in_file "$file" "enabled" "true" + #test "$(_ynh_read_json_with_python "$file" "enabled")" == "True" # FIXME FIXME FIXME + #test "$(ynh_read_var_in_file "$file" "enabled")" == "true" + + ynh_write_var_in_file "$file" "title" "Foo Bar" + cat $file + test "$(_ynh_read_json_with_python "$file" "title")" == "Foo Bar" + test "$(ynh_read_var_in_file "$file" "title")" == "Foo Bar" + + ynh_write_var_in_file "$file" "theme" "super-awesome-theme" + cat $file + test "$(_ynh_read_json_with_python "$file" "theme")" == "super-awesome-theme" + test "$(ynh_read_var_in_file "$file" "theme")" == "super-awesome-theme" + + ynh_write_var_in_file "$file" "email" "sam@domain.tld" + cat $file + test "$(_ynh_read_json_with_python "$file" "email")" == "sam@domain.tld" + test "$(ynh_read_var_in_file "$file" "email")" == "sam@domain.tld" + + #ynh_write_var_in_file "$file" "port" "5678" + #cat $file + #test "$(_ynh_read_json_with_python "$file" "port")" == "5678" # FIXME FIXME FIXME + #test "$(ynh_read_var_in_file "$file" "port")" == "5678" + + ynh_write_var_in_file "$file" "url" "https://domain.tld/foobar" + test "$(_ynh_read_json_with_python "$file" "url")" == "https://domain.tld/foobar" + test "$(ynh_read_var_in_file "$file" "url")" == "https://domain.tld/foobar" + + ynh_write_var_in_file "$file" "ldap_base" "ou=foobar,dc=domain,dc=tld" + test "$(ynh_read_var_in_file "$file" "ldap_base")" == "ou=foobar,dc=domain,dc=tld" + + ynh_write_var_in_file "$file" "nonexistent" "foobar" + test "$(ynh_read_var_in_file "$file" "nonexistent")" == "YNH_NULL" + + ynh_write_var_in_file "$file" "enable" "foobar" + test "$(ynh_read_var_in_file "$file" "enable")" == "YNH_NULL" + #test "$(ynh_read_var_in_file "$file" "enabled")" == "true" # FIXME +} + + + +ynhtest_config_read_php() { + local dummy_dir="$(mktemp -d -p $VAR_WWW)" + file="$dummy_dir/dummy.php" + + cat << EOF > $file + "ou=users,dc=yunohost,dc=org", + ]; +?> +EOF + + test "$(_ynh_read_php_with_php "$file" "foo")" == "NULL" + test "$(ynh_read_var_in_file "$file" "foo")" == "NULL;" # FIXME FIXME FIXME trailing ; + + test "$(_ynh_read_php_with_php "$file" "enabled")" == "false" + test "$(ynh_read_var_in_file "$file" "enabled")" == "false;" # FIXME FIXME FIXME trailing ; + + test "$(_ynh_read_php_with_php "$file" "title")" == "Lorem Ipsum" + test "$(ynh_read_var_in_file "$file" "title")" == "Lorem Ipsum" + + test "$(_ynh_read_php_with_php "$file" "theme")" == "colib\\'ris" + test "$(ynh_read_var_in_file "$file" "theme")" == "colib'ris" + + test "$(_ynh_read_php_with_php "$file" "email")" == "root@example.com" + test "$(ynh_read_var_in_file "$file" "email")" == "root@example.com" + + test "$(_ynh_read_php_with_php "$file" "port")" == "1234" + test "$(ynh_read_var_in_file "$file" "port")" == "1234;" # FIXME FIXME FIXME trailing ; + + test "$(_ynh_read_php_with_php "$file" "url")" == "https://yunohost.org" + test "$(ynh_read_var_in_file "$file" "url")" == "https://yunohost.org" + + test "$(ynh_read_var_in_file "$file" "ldap_base")" == "ou=users,dc=yunohost,dc=org" + + ! _ynh_read_php_with_php "$file" "nonexistent" + test "$(ynh_read_var_in_file "$file" "nonexistent")" == "YNH_NULL" + + ! _ynh_read_php_with_php "$file" "enable" + test "$(ynh_read_var_in_file "$file" "enable")" == "YNH_NULL" +} + + +ynhtest_config_write_php() { + local dummy_dir="$(mktemp -d -p $VAR_WWW)" + file="$dummy_dir/dummy.php" + + cat << EOF > $file + "ou=users,dc=yunohost,dc=org", + ]; +?> +EOF + + #ynh_write_var_in_file "$file" "foo" "bar" + #cat $file + #test "$(_ynh_read_php_with_php "$file" "foo")" == "bar" + #test "$(ynh_read_var_in_file "$file" "foo")" == "bar" # FIXME FIXME FIXME + + #ynh_write_var_in_file "$file" "enabled" "true" + #cat $file + #test "$(_ynh_read_php_with_php "$file" "enabled")" == "true" + #test "$(ynh_read_var_in_file "$file" "enabled")" == "true" # FIXME FIXME FIXME + + ynh_write_var_in_file "$file" "title" "Foo Bar" + cat $file + test "$(_ynh_read_php_with_php "$file" "title")" == "Foo Bar" + test "$(ynh_read_var_in_file "$file" "title")" == "Foo Bar" + + ynh_write_var_in_file "$file" "theme" "super-awesome-theme" + cat $file + test "$(_ynh_read_php_with_php "$file" "theme")" == "super-awesome-theme" + test "$(ynh_read_var_in_file "$file" "theme")" == "super-awesome-theme" + + ynh_write_var_in_file "$file" "email" "sam@domain.tld" + cat $file + test "$(_ynh_read_php_with_php "$file" "email")" == "sam@domain.tld" + test "$(ynh_read_var_in_file "$file" "email")" == "sam@domain.tld" + + #ynh_write_var_in_file "$file" "port" "5678" + #cat $file + #test "$(_ynh_read_php_with_php "$file" "port")" == "5678" # FIXME FIXME FIXME + #test "$(ynh_read_var_in_file "$file" "port")" == "5678" + + ynh_write_var_in_file "$file" "url" "https://domain.tld/foobar" + test "$(_ynh_read_php_with_php "$file" "url")" == "https://domain.tld/foobar" + test "$(ynh_read_var_in_file "$file" "url")" == "https://domain.tld/foobar" + + ynh_write_var_in_file "$file" "ldap_base" "ou=foobar,dc=domain,dc=tld" + test "$(ynh_read_var_in_file "$file" "ldap_base")" == "ou=foobar,dc=domain,dc=tld" + + ynh_write_var_in_file "$file" "nonexistent" "foobar" + test "$(ynh_read_var_in_file "$file" "nonexistent")" == "YNH_NULL" + + ynh_write_var_in_file "$file" "enable" "foobar" + test "$(ynh_read_var_in_file "$file" "enable")" == "YNH_NULL" + #test "$(ynh_read_var_in_file "$file" "enabled")" == "true" # FIXME +} From cc8247acfdc24786c1547246db0072536fc6eba8 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 3 Sep 2021 23:36:54 +0200 Subject: [PATCH 2895/3170] helpers/config: Add unit tests for read/write var from py/ini --- tests/test_helpers.d/ynhtest_config.sh | 509 ++++++++++++++++++------- 1 file changed, 380 insertions(+), 129 deletions(-) diff --git a/tests/test_helpers.d/ynhtest_config.sh b/tests/test_helpers.d/ynhtest_config.sh index 69e715229..7b749adf5 100644 --- a/tests/test_helpers.d/ynhtest_config.sh +++ b/tests/test_helpers.d/ynhtest_config.sh @@ -1,20 +1,24 @@ -_make_dummy_files() { - local_dummy_dir="$1" +################# +# _ __ _ _ # +# | '_ \| | | | # +# | |_) | |_| | # +# | .__/ \__, | # +# | | __/ | # +# |_| |___/ # +# # +################# - cat << EOF > $dummy_dir/dummy.ini -# Some comment -foo = -enabled = False -# title = Old title -title = Lorem Ipsum -email = root@example.com -theme = colib'ris - port = 1234 -url = https://yunohost.org -[dict] - ldap_base = ou=users,dc=yunohost,dc=org -EOF +_read_py() { + local file="$1" + local key="$2" + python3 -c "exec(open('$file').read()); print($key)" +} + +ynhtest_config_read_py() { + + local dummy_dir="$(mktemp -d -p $VAR_WWW)" + file="$dummy_dir/dummy.py" cat << EOF > $dummy_dir/dummy.py # Some comment @@ -26,30 +30,245 @@ THEME = "colib'ris" EMAIL = "root@example.com" PORT = 1234 URL = 'https://yunohost.org' +DICT = {} DICT['ldap_base'] = "ou=users,dc=yunohost,dc=org" EOF + test "$(_read_py "$file" "FOO")" == "None" + test "$(ynh_read_var_in_file "$file" "FOO")" == "None" + + test "$(_read_py "$file" "ENABLED")" == "False" + test "$(ynh_read_var_in_file "$file" "ENABLED")" == "False" + + test "$(_read_py "$file" "TITLE")" == "Lorem Ipsum" + test "$(ynh_read_var_in_file "$file" "TITLE")" == "Lorem Ipsum" + + test "$(_read_py "$file" "THEME")" == "colib'ris" + test "$(ynh_read_var_in_file "$file" "THEME")" == "colib'ris" + + test "$(_read_py "$file" "EMAIL")" == "root@example.com" + test "$(ynh_read_var_in_file "$file" "EMAIL")" == "root@example.com" + + test "$(_read_py "$file" "PORT")" == "1234" + test "$(ynh_read_var_in_file "$file" "PORT")" == "1234" + + test "$(_read_py "$file" "URL")" == "https://yunohost.org" + test "$(ynh_read_var_in_file "$file" "URL")" == "https://yunohost.org" + + test "$(ynh_read_var_in_file "$file" "ldap_base")" == "ou=users,dc=yunohost,dc=org" + + ! _read_py "$file" "NONEXISTENT" + test "$(ynh_read_var_in_file "$file" "NONEXISTENT")" == "YNH_NULL" + + ! _read_py "$file" "ENABLE" + test "$(ynh_read_var_in_file "$file" "ENABLE")" == "YNH_NULL" } -_ynh_read_yaml_with_python() { +ynhtest_config_write_py() { + local dummy_dir="$(mktemp -d -p $VAR_WWW)" + file="$dummy_dir/dummy.py" + + cat << EOF > $dummy_dir/dummy.py +# Some comment +FOO = None +ENABLED = False +# TITLE = "Old title" +TITLE = "Lorem Ipsum" +THEME = "colib'ris" +EMAIL = "root@example.com" +PORT = 1234 +URL = 'https://yunohost.org' +DICT = {} +DICT['ldap_base'] = "ou=users,dc=yunohost,dc=org" +EOF + + #ynh_write_var_in_file "$file" "FOO" "bar" + #test "$(_read_py "$file" "FOO")" == "bar" # FIXME FIXME FIXME + #test "$(ynh_read_var_in_file "$file" "FOO")" == "bar" + + ynh_write_var_in_file "$file" "ENABLED" "True" + test "$(_read_py "$file" "ENABLED")" == "True" + test "$(ynh_read_var_in_file "$file" "ENABLED")" == "True" + + ynh_write_var_in_file "$file" "TITLE" "Foo Bar" + test "$(_read_py "$file" "TITLE")" == "Foo Bar" + test "$(ynh_read_var_in_file "$file" "TITLE")" == "Foo Bar" + + ynh_write_var_in_file "$file" "THEME" "super-awesome-theme" + test "$(_read_py "$file" "THEME")" == "super-awesome-theme" + test "$(ynh_read_var_in_file "$file" "THEME")" == "super-awesome-theme" + + ynh_write_var_in_file "$file" "EMAIL" "sam@domain.tld" + test "$(_read_py "$file" "EMAIL")" == "sam@domain.tld" + test "$(ynh_read_var_in_file "$file" "EMAIL")" == "sam@domain.tld" + + ynh_write_var_in_file "$file" "PORT" "5678" + test "$(_read_py "$file" "PORT")" == "5678" + test "$(ynh_read_var_in_file "$file" "PORT")" == "5678" + + ynh_write_var_in_file "$file" "URL" "https://domain.tld/foobar" + test "$(_read_py "$file" "URL")" == "https://domain.tld/foobar" + test "$(ynh_read_var_in_file "$file" "URL")" == "https://domain.tld/foobar" + + ynh_write_var_in_file "$file" "ldap_base" "ou=users,dc=yunohost,dc=org" + test "$(ynh_read_var_in_file "$file" "ldap_base")" == "ou=users,dc=yunohost,dc=org" + + ynh_write_var_in_file "$file" "NONEXISTENT" "foobar" + ! _read_py "$file" "NONEXISTENT" + test "$(ynh_read_var_in_file "$file" "NONEXISTENT")" == "YNH_NULL" + + ynh_write_var_in_file "$file" "ENABLE" "foobar" + ! _read_py "$file" "ENABLE" + test "$(ynh_read_var_in_file "$file" "ENABLE")" == "YNH_NULL" + +} + +############### +# _ _ # +# (_) (_) # +# _ _ __ _ # +# | | '_ \| | # +# | | | | | | # +# |_|_| |_|_| # +# # +############### + +_read_ini() { + local file="$1" + local key="$2" + python3 -c "import configparser; c = configparser.ConfigParser(); c.read('$file'); print(c['main']['$key'])" +} + +ynhtest_config_read_ini() { + local dummy_dir="$(mktemp -d -p $VAR_WWW)" + file="$dummy_dir/dummy.yml" + + cat << EOF > $file +# Some comment +; Another comment +[main] +foo = null +enabled = False +# title = Old title +title = Lorem Ipsum +theme = colib'ris +email = root@example.com +port = 1234 +url = https://yunohost.org +[dict] + ldap_base = ou=users,dc=yunohost,dc=org +EOF + + test "$(_read_ini "$file" "foo")" == "null" + test "$(ynh_read_var_in_file "$file" "foo")" == "null" + + test "$(_read_ini "$file" "enabled")" == "False" + test "$(ynh_read_var_in_file "$file" "enabled")" == "False" + + test "$(_read_ini "$file" "title")" == "Lorem Ipsum" + test "$(ynh_read_var_in_file "$file" "title")" == "Lorem Ipsum" + + test "$(_read_ini "$file" "theme")" == "colib'ris" + test "$(ynh_read_var_in_file "$file" "theme")" == "colib'ris" + + test "$(_read_ini "$file" "email")" == "root@example.com" + test "$(ynh_read_var_in_file "$file" "email")" == "root@example.com" + + test "$(_read_ini "$file" "port")" == "1234" + test "$(ynh_read_var_in_file "$file" "port")" == "1234" + + test "$(_read_ini "$file" "url")" == "https://yunohost.org" + test "$(ynh_read_var_in_file "$file" "url")" == "https://yunohost.org" + + test "$(ynh_read_var_in_file "$file" "ldap_base")" == "ou=users,dc=yunohost,dc=org" + + ! _read_ini "$file" "nonexistent" + test "$(ynh_read_var_in_file "$file" "nonexistent")" == "YNH_NULL" + + ! _read_ini "$file" "enable" + test "$(ynh_read_var_in_file "$file" "enable")" == "YNH_NULL" + +} + +ynhtest_config_write_ini() { + local dummy_dir="$(mktemp -d -p $VAR_WWW)" + file="$dummy_dir/dummy.ini" + + cat << EOF > $file +# Some comment +; Another comment +[main] +foo = null +enabled = False +# title = Old title +title = Lorem Ipsum +theme = colib'ris +email = root@example.com +port = 1234 +url = https://yunohost.org +[dict] + ldap_base = ou=users,dc=yunohost,dc=org +EOF + + ynh_write_var_in_file "$file" "foo" "bar" + test "$(_read_ini "$file" "foo")" == "bar" + test "$(ynh_read_var_in_file "$file" "foo")" == "bar" + + ynh_write_var_in_file "$file" "enabled" "True" + test "$(_read_ini "$file" "enabled")" == "True" + test "$(ynh_read_var_in_file "$file" "enabled")" == "True" + + ynh_write_var_in_file "$file" "title" "Foo Bar" + test "$(_read_ini "$file" "title")" == "Foo Bar" + test "$(ynh_read_var_in_file "$file" "title")" == "Foo Bar" + + ynh_write_var_in_file "$file" "theme" "super-awesome-theme" + test "$(_read_ini "$file" "theme")" == "super-awesome-theme" + test "$(ynh_read_var_in_file "$file" "theme")" == "super-awesome-theme" + + ynh_write_var_in_file "$file" "email" "sam@domain.tld" + test "$(_read_ini "$file" "email")" == "sam@domain.tld" + test "$(ynh_read_var_in_file "$file" "email")" == "sam@domain.tld" + + ynh_write_var_in_file "$file" "port" "5678" + test "$(_read_ini "$file" "port")" == "5678" + test "$(ynh_read_var_in_file "$file" "port")" == "5678" + + ynh_write_var_in_file "$file" "url" "https://domain.tld/foobar" + test "$(_read_ini "$file" "url")" == "https://domain.tld/foobar" + test "$(ynh_read_var_in_file "$file" "url")" == "https://domain.tld/foobar" + + ynh_write_var_in_file "$file" "ldap_base" "ou=users,dc=yunohost,dc=org" + test "$(ynh_read_var_in_file "$file" "ldap_base")" == "ou=users,dc=yunohost,dc=org" + + ynh_write_var_in_file "$file" "nonexistent" "foobar" + ! _read_ini "$file" "nonexistent" + test "$(ynh_read_var_in_file "$file" "nonexistent")" == "YNH_NULL" + + ynh_write_var_in_file "$file" "enable" "foobar" + ! _read_ini "$file" "enable" + test "$(ynh_read_var_in_file "$file" "enable")" == "YNH_NULL" + +} + +############################# +# _ # +# | | # +# _ _ __ _ _ __ ___ | | # +# | | | |/ _` | '_ ` _ \| | # +# | |_| | (_| | | | | | | | # +# \__, |\__,_|_| |_| |_|_| # +# __/ | # +# |___/ # +# # +############################# + +_read_yaml() { local file="$1" local key="$2" python3 -c "import yaml; print(yaml.safe_load(open('$file'))['$key'])" } -_ynh_read_json_with_python() { - local file="$1" - local key="$2" - python3 -c "import json; print(json.load(open('$file'))['$key'])" -} - -_ynh_read_php_with_php() { - local file="$1" - local key="$2" - php -r "include '$file'; echo var_export(\$$key);" | sed "s/^'//g" | sed "s/'$//g" -} - - ynhtest_config_read_yaml() { local dummy_dir="$(mktemp -d -p $VAR_WWW)" file="$dummy_dir/dummy.yml" @@ -68,33 +287,33 @@ dict: ldap_base: ou=users,dc=yunohost,dc=org EOF - test "$(_ynh_read_yaml_with_python "$file" "foo")" == "None" + test "$(_read_yaml "$file" "foo")" == "None" test "$(ynh_read_var_in_file "$file" "foo")" == "" - - test "$(_ynh_read_yaml_with_python "$file" "enabled")" == "False" + + test "$(_read_yaml "$file" "enabled")" == "False" test "$(ynh_read_var_in_file "$file" "enabled")" == "false" - - test "$(_ynh_read_yaml_with_python "$file" "title")" == "Lorem Ipsum" + + test "$(_read_yaml "$file" "title")" == "Lorem Ipsum" test "$(ynh_read_var_in_file "$file" "title")" == "Lorem Ipsum" - - test "$(_ynh_read_yaml_with_python "$file" "theme")" == "colib'ris" + + test "$(_read_yaml "$file" "theme")" == "colib'ris" test "$(ynh_read_var_in_file "$file" "theme")" == "colib'ris" - - test "$(_ynh_read_yaml_with_python "$file" "email")" == "root@example.com" + + test "$(_read_yaml "$file" "email")" == "root@example.com" test "$(ynh_read_var_in_file "$file" "email")" == "root@example.com" - - test "$(_ynh_read_yaml_with_python "$file" "port")" == "1234" + + test "$(_read_yaml "$file" "port")" == "1234" test "$(ynh_read_var_in_file "$file" "port")" == "1234" - - test "$(_ynh_read_yaml_with_python "$file" "url")" == "https://yunohost.org" + + test "$(_read_yaml "$file" "url")" == "https://yunohost.org" test "$(ynh_read_var_in_file "$file" "url")" == "https://yunohost.org" - + test "$(ynh_read_var_in_file "$file" "ldap_base")" == "ou=users,dc=yunohost,dc=org" - - ! _ynh_read_yaml_with_python "$file" "nonexistent" + + ! _read_yaml "$file" "nonexistent" test "$(ynh_read_var_in_file "$file" "nonexistent")" == "YNH_NULL" - - ! _ynh_read_yaml_with_python "$file" "enable" + + ! _read_yaml "$file" "enable" test "$(ynh_read_var_in_file "$file" "enable")" == "YNH_NULL" } @@ -117,48 +336,64 @@ dict: ldap_base: ou=users,dc=yunohost,dc=org EOF - - #ynh_write_var_in_file "$file" "foo" "bar" # cat $dummy_dir/dummy.yml # to debug - #! test "$(_ynh_read_yaml_with_python "$file" "foo")" == "bar" # FIXME FIXME FIXME : writing broke the yaml syntax... "foo:bar" (no space aftr :) + #! test "$(_read_yaml "$file" "foo")" == "bar" # FIXME FIXME FIXME : writing broke the yaml syntax... "foo:bar" (no space aftr :) #test "$(ynh_read_var_in_file "$file" "foo")" == "bar" ynh_write_var_in_file "$file" "enabled" "true" - test "$(_ynh_read_yaml_with_python "$file" "enabled")" == "True" + test "$(_read_yaml "$file" "enabled")" == "True" test "$(ynh_read_var_in_file "$file" "enabled")" == "true" ynh_write_var_in_file "$file" "title" "Foo Bar" - test "$(_ynh_read_yaml_with_python "$file" "title")" == "Foo Bar" + test "$(_read_yaml "$file" "title")" == "Foo Bar" test "$(ynh_read_var_in_file "$file" "title")" == "Foo Bar" - + ynh_write_var_in_file "$file" "theme" "super-awesome-theme" - test "$(_ynh_read_yaml_with_python "$file" "theme")" == "super-awesome-theme" + test "$(_read_yaml "$file" "theme")" == "super-awesome-theme" test "$(ynh_read_var_in_file "$file" "theme")" == "super-awesome-theme" - + ynh_write_var_in_file "$file" "email" "sam@domain.tld" - test "$(_ynh_read_yaml_with_python "$file" "email")" == "sam@domain.tld" + test "$(_read_yaml "$file" "email")" == "sam@domain.tld" test "$(ynh_read_var_in_file "$file" "email")" == "sam@domain.tld" - + ynh_write_var_in_file "$file" "port" "5678" - test "$(_ynh_read_yaml_with_python "$file" "port")" == "5678" + test "$(_read_yaml "$file" "port")" == "5678" test "$(ynh_read_var_in_file "$file" "port")" == "5678" - + ynh_write_var_in_file "$file" "url" "https://domain.tld/foobar" - test "$(_ynh_read_yaml_with_python "$file" "url")" == "https://domain.tld/foobar" + test "$(_read_yaml "$file" "url")" == "https://domain.tld/foobar" test "$(ynh_read_var_in_file "$file" "url")" == "https://domain.tld/foobar" - + ynh_write_var_in_file "$file" "ldap_base" "ou=foobar,dc=domain,dc=tld" test "$(ynh_read_var_in_file "$file" "ldap_base")" == "ou=foobar,dc=domain,dc=tld" - + ynh_write_var_in_file "$file" "nonexistent" "foobar" test "$(ynh_read_var_in_file "$file" "nonexistent")" == "YNH_NULL" - + ynh_write_var_in_file "$file" "enable" "foobar" test "$(ynh_read_var_in_file "$file" "enable")" == "YNH_NULL" test "$(ynh_read_var_in_file "$file" "enabled")" == "true" } +######################### +# _ # +# (_) # +# _ ___ ___ _ __ # +# | / __|/ _ \| '_ \ # +# | \__ \ (_) | | | | # +# | |___/\___/|_| |_| # +# _/ | # +# |__/ # +# # +######################### + +_read_json() { + local file="$1" + local key="$2" + python3 -c "import json; print(json.load(open('$file'))['$key'])" +} + ynhtest_config_read_json() { local dummy_dir="$(mktemp -d -p $VAR_WWW)" file="$dummy_dir/dummy.json" @@ -179,33 +414,33 @@ ynhtest_config_read_json() { EOF - test "$(_ynh_read_json_with_python "$file" "foo")" == "None" + test "$(_read_json "$file" "foo")" == "None" test "$(ynh_read_var_in_file "$file" "foo")" == "null," # FIXME FIXME FIXME trailing , - - test "$(_ynh_read_json_with_python "$file" "enabled")" == "False" + + test "$(_read_json "$file" "enabled")" == "False" test "$(ynh_read_var_in_file "$file" "enabled")" == "false," # FIXME FIXME FIXME trailing , - - test "$(_ynh_read_json_with_python "$file" "title")" == "Lorem Ipsum" + + test "$(_read_json "$file" "title")" == "Lorem Ipsum" test "$(ynh_read_var_in_file "$file" "title")" == "Lorem Ipsum" - - test "$(_ynh_read_json_with_python "$file" "theme")" == "colib'ris" + + test "$(_read_json "$file" "theme")" == "colib'ris" test "$(ynh_read_var_in_file "$file" "theme")" == "colib'ris" - - test "$(_ynh_read_json_with_python "$file" "email")" == "root@example.com" + + test "$(_read_json "$file" "email")" == "root@example.com" test "$(ynh_read_var_in_file "$file" "email")" == "root@example.com" - - test "$(_ynh_read_json_with_python "$file" "port")" == "1234" + + test "$(_read_json "$file" "port")" == "1234" test "$(ynh_read_var_in_file "$file" "port")" == "1234," # FIXME FIXME FIXME trailing , - - test "$(_ynh_read_json_with_python "$file" "url")" == "https://yunohost.org" + + test "$(_read_json "$file" "url")" == "https://yunohost.org" test "$(ynh_read_var_in_file "$file" "url")" == "https://yunohost.org" - + test "$(ynh_read_var_in_file "$file" "ldap_base")" == "ou=users,dc=yunohost,dc=org" - - ! _ynh_read_json_with_python "$file" "nonexistent" + + ! _read_json "$file" "nonexistent" test "$(ynh_read_var_in_file "$file" "nonexistent")" == "YNH_NULL" - - ! _ynh_read_json_with_python "$file" "enable" + + ! _read_json "$file" "enable" test "$(ynh_read_var_in_file "$file" "enable")" == "YNH_NULL" } @@ -231,49 +466,65 @@ EOF #ynh_write_var_in_file "$file" "foo" "bar" #cat $file - #test "$(_ynh_read_json_with_python "$file" "foo")" == "bar" # FIXME FIXME FIXME + #test "$(_read_json "$file" "foo")" == "bar" # FIXME FIXME FIXME #test "$(ynh_read_var_in_file "$file" "foo")" == "bar" #ynh_write_var_in_file "$file" "enabled" "true" - #test "$(_ynh_read_json_with_python "$file" "enabled")" == "True" # FIXME FIXME FIXME + #test "$(_read_json "$file" "enabled")" == "True" # FIXME FIXME FIXME #test "$(ynh_read_var_in_file "$file" "enabled")" == "true" ynh_write_var_in_file "$file" "title" "Foo Bar" cat $file - test "$(_ynh_read_json_with_python "$file" "title")" == "Foo Bar" + test "$(_read_json "$file" "title")" == "Foo Bar" test "$(ynh_read_var_in_file "$file" "title")" == "Foo Bar" - + ynh_write_var_in_file "$file" "theme" "super-awesome-theme" cat $file - test "$(_ynh_read_json_with_python "$file" "theme")" == "super-awesome-theme" + test "$(_read_json "$file" "theme")" == "super-awesome-theme" test "$(ynh_read_var_in_file "$file" "theme")" == "super-awesome-theme" - + ynh_write_var_in_file "$file" "email" "sam@domain.tld" cat $file - test "$(_ynh_read_json_with_python "$file" "email")" == "sam@domain.tld" + test "$(_read_json "$file" "email")" == "sam@domain.tld" test "$(ynh_read_var_in_file "$file" "email")" == "sam@domain.tld" - + #ynh_write_var_in_file "$file" "port" "5678" #cat $file - #test "$(_ynh_read_json_with_python "$file" "port")" == "5678" # FIXME FIXME FIXME + #test "$(_read_json "$file" "port")" == "5678" # FIXME FIXME FIXME #test "$(ynh_read_var_in_file "$file" "port")" == "5678" - + ynh_write_var_in_file "$file" "url" "https://domain.tld/foobar" - test "$(_ynh_read_json_with_python "$file" "url")" == "https://domain.tld/foobar" + test "$(_read_json "$file" "url")" == "https://domain.tld/foobar" test "$(ynh_read_var_in_file "$file" "url")" == "https://domain.tld/foobar" - + ynh_write_var_in_file "$file" "ldap_base" "ou=foobar,dc=domain,dc=tld" test "$(ynh_read_var_in_file "$file" "ldap_base")" == "ou=foobar,dc=domain,dc=tld" - + ynh_write_var_in_file "$file" "nonexistent" "foobar" test "$(ynh_read_var_in_file "$file" "nonexistent")" == "YNH_NULL" - + ynh_write_var_in_file "$file" "enable" "foobar" test "$(ynh_read_var_in_file "$file" "enable")" == "YNH_NULL" #test "$(ynh_read_var_in_file "$file" "enabled")" == "true" # FIXME } +####################### +# _ # +# | | # +# _ __ | |__ _ __ # +# | '_ \| '_ \| '_ \ # +# | |_) | | | | |_) | # +# | .__/|_| |_| .__/ # +# | | | | # +# |_| |_| # +# # +####################### +_read_php() { + local file="$1" + local key="$2" + php -r "include '$file'; echo var_export(\$$key);" | sed "s/^'//g" | sed "s/'$//g" +} ynhtest_config_read_php() { local dummy_dir="$(mktemp -d -p $VAR_WWW)" @@ -296,33 +547,33 @@ ynhtest_config_read_php() { ?> EOF - test "$(_ynh_read_php_with_php "$file" "foo")" == "NULL" + test "$(_read_php "$file" "foo")" == "NULL" test "$(ynh_read_var_in_file "$file" "foo")" == "NULL;" # FIXME FIXME FIXME trailing ; - - test "$(_ynh_read_php_with_php "$file" "enabled")" == "false" + + test "$(_read_php "$file" "enabled")" == "false" test "$(ynh_read_var_in_file "$file" "enabled")" == "false;" # FIXME FIXME FIXME trailing ; - - test "$(_ynh_read_php_with_php "$file" "title")" == "Lorem Ipsum" + + test "$(_read_php "$file" "title")" == "Lorem Ipsum" test "$(ynh_read_var_in_file "$file" "title")" == "Lorem Ipsum" - - test "$(_ynh_read_php_with_php "$file" "theme")" == "colib\\'ris" + + test "$(_read_php "$file" "theme")" == "colib\\'ris" test "$(ynh_read_var_in_file "$file" "theme")" == "colib'ris" - - test "$(_ynh_read_php_with_php "$file" "email")" == "root@example.com" + + test "$(_read_php "$file" "email")" == "root@example.com" test "$(ynh_read_var_in_file "$file" "email")" == "root@example.com" - - test "$(_ynh_read_php_with_php "$file" "port")" == "1234" + + test "$(_read_php "$file" "port")" == "1234" test "$(ynh_read_var_in_file "$file" "port")" == "1234;" # FIXME FIXME FIXME trailing ; - - test "$(_ynh_read_php_with_php "$file" "url")" == "https://yunohost.org" + + test "$(_read_php "$file" "url")" == "https://yunohost.org" test "$(ynh_read_var_in_file "$file" "url")" == "https://yunohost.org" - + test "$(ynh_read_var_in_file "$file" "ldap_base")" == "ou=users,dc=yunohost,dc=org" - - ! _ynh_read_php_with_php "$file" "nonexistent" + + ! _read_php "$file" "nonexistent" test "$(ynh_read_var_in_file "$file" "nonexistent")" == "YNH_NULL" - - ! _ynh_read_php_with_php "$file" "enable" + + ! _read_php "$file" "enable" test "$(ynh_read_var_in_file "$file" "enable")" == "YNH_NULL" } @@ -350,44 +601,44 @@ EOF #ynh_write_var_in_file "$file" "foo" "bar" #cat $file - #test "$(_ynh_read_php_with_php "$file" "foo")" == "bar" + #test "$(_read_php "$file" "foo")" == "bar" #test "$(ynh_read_var_in_file "$file" "foo")" == "bar" # FIXME FIXME FIXME - + #ynh_write_var_in_file "$file" "enabled" "true" #cat $file - #test "$(_ynh_read_php_with_php "$file" "enabled")" == "true" + #test "$(_read_php "$file" "enabled")" == "true" #test "$(ynh_read_var_in_file "$file" "enabled")" == "true" # FIXME FIXME FIXME - + ynh_write_var_in_file "$file" "title" "Foo Bar" cat $file - test "$(_ynh_read_php_with_php "$file" "title")" == "Foo Bar" - test "$(ynh_read_var_in_file "$file" "title")" == "Foo Bar" - + test "$(_read_php "$file" "title")" == "Foo Bar" + test "$(ynh_read_var_in_file "$file" "title")" == "Foo Bar" + ynh_write_var_in_file "$file" "theme" "super-awesome-theme" cat $file - test "$(_ynh_read_php_with_php "$file" "theme")" == "super-awesome-theme" + test "$(_read_php "$file" "theme")" == "super-awesome-theme" test "$(ynh_read_var_in_file "$file" "theme")" == "super-awesome-theme" - + ynh_write_var_in_file "$file" "email" "sam@domain.tld" cat $file - test "$(_ynh_read_php_with_php "$file" "email")" == "sam@domain.tld" + test "$(_read_php "$file" "email")" == "sam@domain.tld" test "$(ynh_read_var_in_file "$file" "email")" == "sam@domain.tld" #ynh_write_var_in_file "$file" "port" "5678" #cat $file - #test "$(_ynh_read_php_with_php "$file" "port")" == "5678" # FIXME FIXME FIXME + #test "$(_read_php "$file" "port")" == "5678" # FIXME FIXME FIXME #test "$(ynh_read_var_in_file "$file" "port")" == "5678" - + ynh_write_var_in_file "$file" "url" "https://domain.tld/foobar" - test "$(_ynh_read_php_with_php "$file" "url")" == "https://domain.tld/foobar" + test "$(_read_php "$file" "url")" == "https://domain.tld/foobar" test "$(ynh_read_var_in_file "$file" "url")" == "https://domain.tld/foobar" - + ynh_write_var_in_file "$file" "ldap_base" "ou=foobar,dc=domain,dc=tld" test "$(ynh_read_var_in_file "$file" "ldap_base")" == "ou=foobar,dc=domain,dc=tld" - + ynh_write_var_in_file "$file" "nonexistent" "foobar" test "$(ynh_read_var_in_file "$file" "nonexistent")" == "YNH_NULL" - + ynh_write_var_in_file "$file" "enable" "foobar" test "$(ynh_read_var_in_file "$file" "enable")" == "YNH_NULL" #test "$(ynh_read_var_in_file "$file" "enabled")" == "true" # FIXME From 0a52430186a1743416ad928f4fb22f16fef46407 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 4 Sep 2021 18:39:39 +0200 Subject: [PATCH 2896/3170] Black --- data/hooks/diagnosis/80-apps.py | 34 +++- src/yunohost/app.py | 39 +++-- src/yunohost/utils/config.py | 291 +++++++++++++++++--------------- src/yunohost/utils/i18n.py | 3 +- 4 files changed, 208 insertions(+), 159 deletions(-) diff --git a/data/hooks/diagnosis/80-apps.py b/data/hooks/diagnosis/80-apps.py index 4ab5a6c0d..177ec590f 100644 --- a/data/hooks/diagnosis/80-apps.py +++ b/data/hooks/diagnosis/80-apps.py @@ -6,6 +6,7 @@ from yunohost.app import app_list from yunohost.diagnosis import Diagnoser + class AppDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] @@ -30,13 +31,17 @@ class AppDiagnoser(Diagnoser): if not app["issues"]: continue - level = "ERROR" if any(issue[0] == "error" for issue in app["issues"]) else "WARNING" + level = ( + "ERROR" + if any(issue[0] == "error" for issue in app["issues"]) + else "WARNING" + ) yield dict( meta={"test": "apps", "app": app["name"]}, status=level, summary="diagnosis_apps_issue", - details=[issue[1] for issue in app["issues"]] + details=[issue[1] for issue in app["issues"]], ) def issues(self, app): @@ -45,14 +50,19 @@ class AppDiagnoser(Diagnoser): if not app.get("from_catalog") or app["from_catalog"].get("state") != "working": yield ("error", "diagnosis_apps_not_in_app_catalog") - elif not isinstance(app["from_catalog"].get("level"), int) or app["from_catalog"]["level"] == 0: + elif ( + not isinstance(app["from_catalog"].get("level"), int) + or app["from_catalog"]["level"] == 0 + ): yield ("error", "diagnosis_apps_broken") elif app["from_catalog"]["level"] <= 4: yield ("warning", "diagnosis_apps_bad_quality") # Check for super old, deprecated practices - yunohost_version_req = app["manifest"].get("requirements", {}).get("yunohost", "").strip(">= ") + yunohost_version_req = ( + app["manifest"].get("requirements", {}).get("yunohost", "").strip(">= ") + ) if yunohost_version_req.startswith("2."): yield ("error", "diagnosis_apps_outdated_ynh_requirement") @@ -64,11 +74,21 @@ class AppDiagnoser(Diagnoser): "yunohost tools port-available", ] for deprecated_helper in deprecated_helpers: - if os.system(f"grep -nr -q '{deprecated_helper}' {app['setting_path']}/scripts/") == 0: + if ( + os.system( + f"grep -nr -q '{deprecated_helper}' {app['setting_path']}/scripts/" + ) + == 0 + ): yield ("error", "diagnosis_apps_deprecated_practices") - old_arg_regex = r'^domain=\${?[0-9]' - if os.system(f"grep -q '{old_arg_regex}' {app['setting_path']}/scripts/install") == 0: + old_arg_regex = r"^domain=\${?[0-9]" + if ( + os.system( + f"grep -q '{old_arg_regex}' {app['setting_path']}/scripts/install" + ) + == 0 + ): yield ("error", "diagnosis_apps_deprecated_practices") diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 522f695e2..0b8e2565e 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -55,7 +55,11 @@ from moulinette.utils.filesystem import ( from yunohost.service import service_status, _run_service_command from yunohost.utils import packages, config -from yunohost.utils.config import ConfigPanel, parse_args_in_yunohost_format, YunoHostArgumentFormatParser +from yunohost.utils.config import ( + ConfigPanel, + parse_args_in_yunohost_format, + YunoHostArgumentFormatParser, +) from yunohost.utils.i18n import _value_for_locale from yunohost.utils.error import YunohostError, YunohostValidationError from yunohost.utils.filesystem import free_space_in_directory @@ -1756,7 +1760,7 @@ def app_action_run(operation_logger, app, action, args=None): return logger.success("Action successed!") -def app_config_get(app, key='', mode='classic'): +def app_config_get(app, key="", mode="classic"): """ Display an app configuration in classic, full or export mode """ @@ -1765,7 +1769,9 @@ def app_config_get(app, key='', mode='classic'): @is_unit_operation() -def app_config_set(operation_logger, app, key=None, value=None, args=None, args_file=None): +def app_config_set( + operation_logger, app, key=None, value=None, args=None, args_file=None +): """ Apply a new app configuration """ @@ -1780,6 +1786,7 @@ def app_config_set(operation_logger, app, key=None, value=None, args=None, args_ operation_logger.success() return result + class AppConfigPanel(ConfigPanel): def __init__(self, app): @@ -1791,10 +1798,10 @@ class AppConfigPanel(ConfigPanel): super().__init__(config_path=config_path) def _load_current_values(self): - self.values = self._call_config_script('show') + self.values = self._call_config_script("show") def _apply(self): - self.errors = self._call_config_script('apply', self.new_values) + self.errors = self._call_config_script("apply", self.new_values) def _call_config_script(self, action, env={}): from yunohost.hook import hook_exec @@ -1814,22 +1821,23 @@ ynh_app_config_run $1 # Call config script to extract current values logger.debug(f"Calling '{action}' action from config script") app_id, app_instance_nb = _parse_app_instance_name(self.app) - env.update({ - "app_id": app_id, - "app": self.app, - "app_instance_nb": str(app_instance_nb), - }) - - ret, values = hook_exec( - config_script, args=[action], env=env + env.update( + { + "app_id": app_id, + "app": self.app, + "app_instance_nb": str(app_instance_nb), + } ) + + ret, values = hook_exec(config_script, args=[action], env=env) if ret != 0: - if action == 'show': + if action == "show": raise YunohostError("app_config_unable_to_read_values") else: raise YunohostError("app_config_unable_to_apply_values_correctly") return values + def _get_all_installed_apps_id(): """ Return something like: @@ -2455,9 +2463,6 @@ def _parse_args_for_action(action, args={}): return parse_args_in_yunohost_format(args, action_args) - - - def _validate_and_normalize_webpath(args_dict, app_folder): # If there's only one "domain" and "path", validate that domain/path diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 34883dcf7..4528fb708 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -45,8 +45,8 @@ from yunohost.utils.error import YunohostError, YunohostValidationError logger = getActionLogger("yunohost.config") CONFIG_PANEL_VERSION_SUPPORTED = 1.0 -class ConfigPanel: +class ConfigPanel: def __init__(self, config_path, save_path=None): self.config_path = config_path self.save_path = save_path @@ -54,8 +54,8 @@ class ConfigPanel: self.values = {} self.new_values = {} - def get(self, key='', mode='classic'): - self.filter_key = key or '' + def get(self, key="", mode="classic"): + self.filter_key = key or "" # Read config panel toml self._get_config_panel() @@ -68,12 +68,12 @@ class ConfigPanel: self._hydrate() # Format result in full mode - if mode == 'full': + if mode == "full": return self.config # In 'classic' mode, we display the current value if key refer to an option - if self.filter_key.count('.') == 2 and mode == 'classic': - option = self.filter_key.split('.')[-1] + if self.filter_key.count(".") == 2 and mode == "classic": + option = self.filter_key.split(".")[-1] return self.values.get(option, None) # Format result in 'classic' or 'export' mode @@ -81,17 +81,17 @@ class ConfigPanel: result = {} for panel, section, option in self._iterate(): key = f"{panel['id']}.{section['id']}.{option['id']}" - if mode == 'export': - result[option['id']] = option.get('current_value') + if mode == "export": + result[option["id"]] = option.get("current_value") else: - result[key] = { 'ask': _value_for_locale(option['ask']) } - if 'current_value' in option: - result[key]['value'] = option['current_value'] + result[key] = {"ask": _value_for_locale(option["ask"])} + if "current_value" in option: + result[key]["value"] = option["current_value"] return result def set(self, key=None, value=None, args=None, args_file=None): - self.filter_key = key or '' + self.filter_key = key or "" # Read config panel toml self._get_config_panel() @@ -102,20 +102,20 @@ class ConfigPanel: if (args is not None or args_file is not None) and value is not None: raise YunohostError("config_args_value") - if self.filter_key.count('.') != 2 and not value is None: + if self.filter_key.count(".") != 2 and not value is None: raise YunohostError("config_set_value_on_section") # Import and parse pre-answered options logger.debug("Import and parse pre-answered options") - args = urllib.parse.parse_qs(args or '', keep_blank_values=True) - self.args = { key: ','.join(value_) for key, value_ in args.items() } + args = urllib.parse.parse_qs(args or "", keep_blank_values=True) + self.args = {key: ",".join(value_) for key, value_ in args.items()} if args_file: # Import YAML / JSON file but keep --args values - self.args = { **read_yaml(args_file), **self.args } + self.args = {**read_yaml(args_file), **self.args} if value is not None: - self.args = {self.filter_key.split('.')[-1]: value} + self.args = {self.filter_key.split(".")[-1]: value} # Read or get values and hydrate the config self._load_current_values() @@ -155,10 +155,9 @@ class ConfigPanel: def _get_toml(self): return read_toml(self.config_path) - def _get_config_panel(self): # Split filter_key - filter_key = dict(enumerate(self.filter_key.split('.'))) + filter_key = dict(enumerate(self.filter_key.split("."))) if len(filter_key) > 3: raise YunohostError("config_too_much_sub_keys") @@ -174,20 +173,18 @@ class ConfigPanel: # Transform toml format into internal format defaults = { - 'toml': { - 'version': 1.0 - }, - 'panels': { - 'name': '', - 'services': [], - 'actions': {'apply': {'en': 'Apply'}} - }, # help - 'sections': { - 'name': '', - 'services': [], - 'optional': True - }, # visibleIf help - 'options': {} + "toml": {"version": 1.0}, + "panels": { + "name": "", + "services": [], + "actions": {"apply": {"en": "Apply"}}, + }, # help + "sections": { + "name": "", + "services": [], + "optional": True, + }, # visibleIf help + "options": {} # ask type source help helpLink example style icon placeholder visibleIf # optional choices pattern limit min max step accept redact } @@ -207,30 +204,36 @@ class ConfigPanel: # Define the filter_key part to use and the children type i = list(defaults).index(node_type) search_key = filter_key.get(i) - subnode_type = list(defaults)[i+1] if node_type != 'options' else None + subnode_type = list(defaults)[i + 1] if node_type != "options" else None for key, value in toml_node.items(): # Key/value are a child node - if isinstance(value, OrderedDict) and key not in default and subnode_type: + if ( + isinstance(value, OrderedDict) + and key not in default + and subnode_type + ): # We exclude all nodes not referenced by the filter_key if search_key and key != search_key: continue subnode = convert(value, subnode_type) - subnode['id'] = key - if node_type == 'sections': - subnode['name'] = key # legacy - subnode.setdefault('optional', toml_node.get('optional', True)) + subnode["id"] = key + if node_type == "sections": + subnode["name"] = key # legacy + subnode.setdefault("optional", toml_node.get("optional", True)) node.setdefault(subnode_type, []).append(subnode) # Key/value are a property else: # Todo search all i18n keys - node[key] = value if key not in ['ask', 'help', 'name'] else { 'en': value } + node[key] = ( + value if key not in ["ask", "help", "name"] else {"en": value} + ) return node - self.config = convert(toml_config_panel, 'toml') + self.config = convert(toml_config_panel, "toml") try: - self.config['panels'][0]['sections'][0]['options'][0] + self.config["panels"][0]["sections"][0]["options"][0] except (KeyError, IndexError): raise YunohostError( "config_empty_or_bad_filter_key", filter_key=self.filter_key @@ -242,36 +245,41 @@ class ConfigPanel: # Hydrating config panel with current value logger.debug("Hydrating config with current values") for _, _, option in self._iterate(): - if option['name'] not in self.values: + if option["name"] not in self.values: continue - value = self.values[option['name']] + value = self.values[option["name"]] # In general, the value is just a simple value. # Sometimes it could be a dict used to overwrite the option itself - value = value if isinstance(value, dict) else {'current_value': value } + value = value if isinstance(value, dict) else {"current_value": value} option.update(value) return self.values def _ask(self): logger.debug("Ask unanswered question and prevalidate data") + def display_header(message): - """ CLI panel/section header display - """ - if Moulinette.interface.type == 'cli' and self.filter_key.count('.') < 2: - Moulinette.display(colorize(message, 'purple')) - for panel, section, obj in self._iterate(['panel', 'section']): + """CLI panel/section header display""" + if Moulinette.interface.type == "cli" and self.filter_key.count(".") < 2: + Moulinette.display(colorize(message, "purple")) + + for panel, section, obj in self._iterate(["panel", "section"]): if panel == obj: - name = _value_for_locale(panel['name']) + name = _value_for_locale(panel["name"]) display_header(f"\n{'='*40}\n>>>> {name}\n{'='*40}") continue - name = _value_for_locale(section['name']) + name = _value_for_locale(section["name"]) display_header(f"\n# {name}") # Check and ask unanswered questions - self.new_values.update(parse_args_in_yunohost_format( - self.args, section['options'] - )) - self.new_values = {key: str(value[0]) for key, value in self.new_values.items() if not value[0] is None} + self.new_values.update( + parse_args_in_yunohost_format(self.args, section["options"]) + ) + self.new_values = { + key: str(value[0]) + for key, value in self.new_values.items() + if not value[0] is None + } def _apply(self): logger.info("Running config script...") @@ -281,36 +289,34 @@ class ConfigPanel: # Save the settings to the .yaml file write_to_yaml(self.save_path, self.new_values) - def _reload_services(self): logger.info("Reloading services...") services_to_reload = set() - for panel, section, obj in self._iterate(['panel', 'section', 'option']): - services_to_reload |= set(obj.get('services', [])) + for panel, section, obj in self._iterate(["panel", "section", "option"]): + services_to_reload |= set(obj.get("services", [])) services_to_reload = list(services_to_reload) - services_to_reload.sort(key = 'nginx'.__eq__) + services_to_reload.sort(key="nginx".__eq__) for service in services_to_reload: - if '__APP__': - service = service.replace('__APP__', self.app) + if "__APP__": + service = service.replace("__APP__", self.app) logger.debug(f"Reloading {service}") - if not _run_service_command('reload-or-restart', service): + if not _run_service_command("reload-or-restart", service): services = _get_services() - test_conf = services[service].get('test_conf', 'true') - errors = check_output(f"{test_conf}; exit 0") if test_conf else '' + test_conf = services[service].get("test_conf", "true") + errors = check_output(f"{test_conf}; exit 0") if test_conf else "" raise YunohostError( - "config_failed_service_reload", - service=service, errors=errors + "config_failed_service_reload", service=service, errors=errors ) - def _iterate(self, trigger=['option']): + def _iterate(self, trigger=["option"]): for panel in self.config.get("panels", []): - if 'panel' in trigger: + if "panel" in trigger: yield (panel, None, panel) for section in panel.get("sections", []): - if 'section' in trigger: + if "section" in trigger: yield (panel, section, section) - if 'option' in trigger: + if "option" in trigger: for option in section.get("options", []): yield (panel, section, option) @@ -327,17 +333,17 @@ class YunoHostArgumentFormatParser(object): parsed_question = Question() parsed_question.name = question["name"] - parsed_question.type = question.get("type", 'string') + parsed_question.type = question.get("type", "string") parsed_question.default = question.get("default", None) parsed_question.current_value = question.get("current_value") parsed_question.optional = question.get("optional", False) parsed_question.choices = question.get("choices", []) parsed_question.pattern = question.get("pattern") - parsed_question.ask = question.get("ask", {'en': f"{parsed_question.name}"}) + parsed_question.ask = question.get("ask", {"en": f"{parsed_question.name}"}) parsed_question.help = question.get("help") parsed_question.helpLink = question.get("helpLink") parsed_question.value = user_answers.get(parsed_question.name) - parsed_question.redact = question.get('redact', False) + parsed_question.redact = question.get("redact", False) # Empty value is parsed as empty string if parsed_question.default == "": @@ -350,7 +356,7 @@ class YunoHostArgumentFormatParser(object): while True: # Display question if no value filled or if it's a readonly message - if Moulinette.interface.type== 'cli': + if Moulinette.interface.type == "cli": text_for_user_input_in_cli = self._format_text_for_user_input_in_cli( question ) @@ -368,10 +374,9 @@ class YunoHostArgumentFormatParser(object): is_password=self.hide_user_input_in_prompt, confirm=self.hide_user_input_in_prompt, prefill=prefill, - is_multiline=(question.type == "text") + is_multiline=(question.type == "text"), ) - # Apply default value if question.value in [None, ""] and question.default is not None: question.value = ( @@ -384,9 +389,9 @@ class YunoHostArgumentFormatParser(object): try: self._prevalidate(question) except YunohostValidationError as e: - if Moulinette.interface.type== 'api': + if Moulinette.interface.type == "api": raise - Moulinette.display(str(e), 'error') + Moulinette.display(str(e), "error") question.value = None continue break @@ -398,17 +403,17 @@ class YunoHostArgumentFormatParser(object): def _prevalidate(self, question): if question.value in [None, ""] and not question.optional: - raise YunohostValidationError( - "app_argument_required", name=question.name - ) + raise YunohostValidationError("app_argument_required", name=question.name) # we have an answer, do some post checks if question.value is not None: if question.choices and question.value not in question.choices: self._raise_invalid_answer(question) - if question.pattern and not re.match(question.pattern['regexp'], str(question.value)): + if question.pattern and not re.match( + question.pattern["regexp"], str(question.value) + ): raise YunohostValidationError( - question.pattern['error'], + question.pattern["error"], name=question.name, value=question.value, ) @@ -434,7 +439,7 @@ class YunoHostArgumentFormatParser(object): text_for_user_input_in_cli += _value_for_locale(question.help) if question.helpLink: if not isinstance(question.helpLink, dict): - question.helpLink = {'href': question.helpLink} + question.helpLink = {"href": question.helpLink} text_for_user_input_in_cli += f"\n - See {question.helpLink['href']}" return text_for_user_input_in_cli @@ -467,18 +472,18 @@ class StringArgumentParser(YunoHostArgumentFormatParser): argument_type = "string" default_value = "" + class TagsArgumentParser(YunoHostArgumentFormatParser): argument_type = "tags" def _prevalidate(self, question): values = question.value - for value in values.split(','): + for value in values.split(","): question.value = value super()._prevalidate(question) question.value = values - class PasswordArgumentParser(YunoHostArgumentFormatParser): hide_user_input_in_prompt = True argument_type = "password" @@ -522,9 +527,7 @@ class BooleanArgumentParser(YunoHostArgumentFormatParser): default_value = False def parse_question(self, question, user_answers): - question = super().parse_question( - question, user_answers - ) + question = super().parse_question(question, user_answers) if question.default is None: question.default = False @@ -616,11 +619,9 @@ class NumberArgumentParser(YunoHostArgumentFormatParser): default_value = "" def parse_question(self, question, user_answers): - question_parsed = super().parse_question( - question, user_answers - ) - question_parsed.min = question.get('min', None) - question_parsed.max = question.get('max', None) + question_parsed = super().parse_question(question, user_answers) + question_parsed.min = question.get("min", None) + question_parsed.max = question.get("max", None) if question_parsed.default is None: question_parsed.default = 0 @@ -628,19 +629,27 @@ class NumberArgumentParser(YunoHostArgumentFormatParser): def _prevalidate(self, question): super()._prevalidate(question) - if not isinstance(question.value, int) and not (isinstance(question.value, str) and question.value.isdigit()): + if not isinstance(question.value, int) and not ( + isinstance(question.value, str) and question.value.isdigit() + ): raise YunohostValidationError( - "app_argument_invalid", field=question.name, error=m18n.n("invalid_number") + "app_argument_invalid", + field=question.name, + error=m18n.n("invalid_number"), ) if question.min is not None and int(question.value) < question.min: raise YunohostValidationError( - "app_argument_invalid", field=question.name, error=m18n.n("invalid_number") + "app_argument_invalid", + field=question.name, + error=m18n.n("invalid_number"), ) if question.max is not None and int(question.value) > question.max: raise YunohostValidationError( - "app_argument_invalid", field=question.name, error=m18n.n("invalid_number") + "app_argument_invalid", + field=question.name, + error=m18n.n("invalid_number"), ) def _post_parse_value(self, question): @@ -660,29 +669,28 @@ class DisplayTextArgumentParser(YunoHostArgumentFormatParser): readonly = True def parse_question(self, question, user_answers): - question_parsed = super().parse_question( - question, user_answers - ) + question_parsed = super().parse_question(question, user_answers) question_parsed.optional = True - question_parsed.style = question.get('style', 'info') + question_parsed.style = question.get("style", "info") return question_parsed def _format_text_for_user_input_in_cli(self, question): - text = question.ask['en'] + text = question.ask["en"] - if question.style in ['success', 'info', 'warning', 'danger']: + if question.style in ["success", "info", "warning", "danger"]: color = { - 'success': 'green', - 'info': 'cyan', - 'warning': 'yellow', - 'danger': 'red' + "success": "green", + "info": "cyan", + "warning": "yellow", + "danger": "red", } return colorize(m18n.g(question.style), color[question.style]) + f" {text}" else: return text + class FileArgumentParser(YunoHostArgumentFormatParser): argument_type = "file" upload_dirs = [] @@ -690,60 +698,77 @@ class FileArgumentParser(YunoHostArgumentFormatParser): @classmethod def clean_upload_dirs(cls): # Delete files uploaded from API - if Moulinette.interface.type== 'api': + if Moulinette.interface.type == "api": for upload_dir in cls.upload_dirs: if os.path.exists(upload_dir): shutil.rmtree(upload_dir) def parse_question(self, question, user_answers): - question_parsed = super().parse_question( - question, user_answers - ) - if question.get('accept'): - question_parsed.accept = question.get('accept').replace(' ', '').split(',') + question_parsed = super().parse_question(question, user_answers) + if question.get("accept"): + question_parsed.accept = question.get("accept").replace(" ", "").split(",") else: question_parsed.accept = [] - if Moulinette.interface.type== 'api': + if Moulinette.interface.type == "api": if user_answers.get(f"{question_parsed.name}[name]"): question_parsed.value = { - 'content': question_parsed.value, - 'filename': user_answers.get(f"{question_parsed.name}[name]", question_parsed.name), + "content": question_parsed.value, + "filename": user_answers.get( + f"{question_parsed.name}[name]", question_parsed.name + ), } # If path file are the same - if question_parsed.value and str(question_parsed.value) == question_parsed.current_value: + if ( + question_parsed.value + and str(question_parsed.value) == question_parsed.current_value + ): question_parsed.value = None return question_parsed def _prevalidate(self, question): super()._prevalidate(question) - if isinstance(question.value, str) and question.value and not os.path.exists(question.value): + if ( + isinstance(question.value, str) + and question.value + and not os.path.exists(question.value) + ): raise YunohostValidationError( - "app_argument_invalid", field=question.name, error=m18n.n("invalid_number1") + "app_argument_invalid", + field=question.name, + error=m18n.n("invalid_number1"), ) - if question.value in [None, ''] or not question.accept: + if question.value in [None, ""] or not question.accept: return - filename = question.value if isinstance(question.value, str) else question.value['filename'] - if '.' not in filename or '.' + filename.split('.')[-1] not in question.accept: + filename = ( + question.value + if isinstance(question.value, str) + else question.value["filename"] + ) + if "." not in filename or "." + filename.split(".")[-1] not in question.accept: raise YunohostValidationError( - "app_argument_invalid", field=question.name, error=m18n.n("invalid_number2") + "app_argument_invalid", + field=question.name, + error=m18n.n("invalid_number2"), ) - def _post_parse_value(self, question): from base64 import b64decode + # Upload files from API # A file arg contains a string with "FILENAME:BASE64_CONTENT" if not question.value: return question.value - if Moulinette.interface.type== 'api': + if Moulinette.interface.type == "api": - upload_dir = tempfile.mkdtemp(prefix='tmp_configpanel_') + upload_dir = tempfile.mkdtemp(prefix="tmp_configpanel_") FileArgumentParser.upload_dirs += [upload_dir] - filename = question.value['filename'] - logger.debug(f"Save uploaded file {question.value['filename']} from API into {upload_dir}") + filename = question.value["filename"] + logger.debug( + f"Save uploaded file {question.value['filename']} from API into {upload_dir}" + ) # Filename is given by user of the API. For security reason, we have replaced # os.path.join to avoid the user to be able to rewrite a file in filesystem @@ -755,9 +780,9 @@ class FileArgumentParser(YunoHostArgumentFormatParser): while os.path.exists(file_path): file_path = os.path.normpath(upload_dir + "/" + filename + (".%d" % i)) i += 1 - content = question.value['content'] + content = question.value["content"] try: - with open(file_path, 'wb') as f: + with open(file_path, "wb") as f: f.write(b64decode(content)) except IOError as e: raise YunohostError("cannot_write_file", file=file_path, error=str(e)) @@ -790,6 +815,7 @@ ARGUMENTS_TYPE_PARSERS = { "file": FileArgumentParser, } + 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. @@ -811,4 +837,3 @@ def parse_args_in_yunohost_format(user_answers, argument_questions): parsed_answers_dict[question["name"]] = answer return parsed_answers_dict - diff --git a/src/yunohost/utils/i18n.py b/src/yunohost/utils/i18n.py index 89d1d0b34..894841439 100644 --- a/src/yunohost/utils/i18n.py +++ b/src/yunohost/utils/i18n.py @@ -20,6 +20,7 @@ """ from moulinette import Moulinette, m18n + def _value_for_locale(values): """ Return proper value for current locale @@ -42,5 +43,3 @@ def _value_for_locale(values): # Fallback to first value return list(values.values())[0] - - From aeeac12309139175d5b3d37effd2153a4c30bbdb Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 4 Sep 2021 18:45:45 +0200 Subject: [PATCH 2897/3170] Flake8 --- src/yunohost/app.py | 11 +++++------ src/yunohost/utils/config.py | 9 +++++---- src/yunohost/utils/i18n.py | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 0b8e2565e..83ff27cdf 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -36,7 +36,6 @@ import urllib.parse import tempfile from collections import OrderedDict -from moulinette.interfaces.cli import colorize from moulinette import Moulinette, m18n from moulinette.core import MoulinetteError from moulinette.utils.log import getActionLogger @@ -54,7 +53,7 @@ from moulinette.utils.filesystem import ( ) from yunohost.service import service_status, _run_service_command -from yunohost.utils import packages, config +from yunohost.utils import packages from yunohost.utils.config import ( ConfigPanel, parse_args_in_yunohost_format, @@ -1764,8 +1763,8 @@ def app_config_get(app, key="", mode="classic"): """ Display an app configuration in classic, full or export mode """ - config = AppConfigPanel(app) - return config.get(key, mode) + config_ = AppConfigPanel(app) + return config_.get(key, mode) @is_unit_operation() @@ -1776,12 +1775,12 @@ def app_config_set( Apply a new app configuration """ - config = AppConfigPanel(app) + config_ = AppConfigPanel(app) YunoHostArgumentFormatParser.operation_logger = operation_logger operation_logger.start() - result = config.set(key, value, args, args_file) + result = config_.set(key, value, args, args_file) if "errors" not in result: operation_logger.success() return result diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 4528fb708..8fcf493ed 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -21,9 +21,9 @@ import os import re -import toml import urllib.parse import tempfile +import shutil from collections import OrderedDict from moulinette.interfaces.cli import colorize @@ -37,8 +37,6 @@ from moulinette.utils.filesystem import ( mkdir, ) -from yunohost.service import _get_services -from yunohost.service import _run_service_command, _get_services from yunohost.utils.i18n import _value_for_locale from yunohost.utils.error import YunohostError, YunohostValidationError @@ -144,7 +142,7 @@ class ConfigPanel: if self.errors: return { - "errors": errors, + "errors": self.errors, } self._reload_services() @@ -290,6 +288,9 @@ class ConfigPanel: write_to_yaml(self.save_path, self.new_values) def _reload_services(self): + + from yunohost.service import _run_service_command, _get_services + logger.info("Reloading services...") services_to_reload = set() for panel, section, obj in self._iterate(["panel", "section", "option"]): diff --git a/src/yunohost/utils/i18n.py b/src/yunohost/utils/i18n.py index 894841439..a0daf8181 100644 --- a/src/yunohost/utils/i18n.py +++ b/src/yunohost/utils/i18n.py @@ -18,7 +18,7 @@ along with this program; if not, see http://www.gnu.org/licenses """ -from moulinette import Moulinette, m18n +from moulinette import m18n def _value_for_locale(values): From 666ebdd8d43dbf6cb4d8130555cc051abf68bdd4 Mon Sep 17 00:00:00 2001 From: ljf Date: Sat, 4 Sep 2021 19:03:07 +0200 Subject: [PATCH 2898/3170] [enh] yes/no args on boolean aquestion + semantic --- src/yunohost/utils/config.py | 540 +++++++++++++++++++---------------- 1 file changed, 287 insertions(+), 253 deletions(-) diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index b8ba489e6..380da1027 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -90,7 +90,8 @@ class ConfigPanel: elif 'i18n' in self.config: result[key] = { 'ask': m18n.n(self.config['i18n'] + '_' + option['id']) } if 'current_value' in option: - result[key]['value'] = option['current_value'] + question_class = ARGUMENTS_TYPE_PARSERS[option.get("type", "string")] + result[key]['value'] = question_class.humanize(option['current_value'], option) return result @@ -144,7 +145,7 @@ class ConfigPanel: raise finally: # Delete files uploaded from API - FileArgumentParser.clean_upload_dirs() + FileQuestion.clean_upload_dirs() if self.errors: return { @@ -275,7 +276,8 @@ class ConfigPanel: self.new_values.update(parse_args_in_yunohost_format( self.args, section['options'] )) - self.new_values = {key: str(value[0]) for key, value in self.new_values.items() if not value[0] is None} + self.new_values = {key: value[0] for key, value in self.new_values.items() if not value[0] is None} + self.errors = None def _get_default_values(self): return { option['id']: option['default'] @@ -297,31 +299,31 @@ class ConfigPanel: self.values.update(on_disk_settings) def _apply(self): - logger.info("Running config script...") + logger.info("Saving the new configuration...") dir_path = os.path.dirname(os.path.realpath(self.save_path)) if not os.path.exists(dir_path): mkdir(dir_path, mode=0o700) + values_to_save = {**self.values, **self.new_values} if self.save_mode == 'diff': defaults = self._get_default_values() - values_to_save = {k: v for k, v in values.items() if defaults.get(k) != v} - else: - values_to_save = {**self.values, **self.new_values} + values_to_save = {k: v for k, v in values_to_save.items() if defaults.get(k) != v} # Save the settings to the .yaml file write_to_yaml(self.save_path, values_to_save) def _reload_services(self): - logger.info("Reloading services...") services_to_reload = set() for panel, section, obj in self._iterate(['panel', 'section', 'option']): services_to_reload |= set(obj.get('services', [])) services_to_reload = list(services_to_reload) services_to_reload.sort(key = 'nginx'.__eq__) + if services_to_reload: + logger.info("Reloading services...") for service in services_to_reload: - if '__APP__': + if '__APP__' in service: service = service.replace('__APP__', self.app) logger.debug(f"Reloading {service}") if not _run_service_command('reload-or-restart', service): @@ -345,141 +347,140 @@ class ConfigPanel: yield (panel, section, option) -class Question: - "empty class to store questions information" - - -class YunoHostArgumentFormatParser(object): +class Question(object): hide_user_input_in_prompt = False operation_logger = None - def parse_question(self, question, user_answers): - parsed_question = Question() - - parsed_question.name = question["name"] - parsed_question.type = question.get("type", 'string') - parsed_question.default = question.get("default", None) - parsed_question.current_value = question.get("current_value") - parsed_question.optional = question.get("optional", False) - parsed_question.choices = question.get("choices", []) - parsed_question.pattern = question.get("pattern") - parsed_question.ask = question.get("ask", {'en': f"{parsed_question.name}"}) - parsed_question.help = question.get("help") - parsed_question.helpLink = question.get("helpLink") - parsed_question.value = user_answers.get(parsed_question.name) - parsed_question.redact = question.get('redact', False) + def __init__(self, question, user_answers): + self.name = question["name"] + self.type = question.get("type", 'string') + self.default = question.get("default", None) + self.current_value = question.get("current_value") + self.optional = question.get("optional", False) + self.choices = question.get("choices", []) + self.pattern = question.get("pattern") + self.ask = question.get("ask", {'en': f"{self.name}"}) + self.help = question.get("help") + self.helpLink = question.get("helpLink") + self.value = user_answers.get(self.name) + self.redact = question.get('redact', False) # Empty value is parsed as empty string - if parsed_question.default == "": - parsed_question.default = None + if self.default == "": + self.default = None - return parsed_question + @staticmethod + def humanize(value, option={}): + return str(value) - def parse(self, question, user_answers): - question = self.parse_question(question, user_answers) + @staticmethod + def normalize(value, option={}): + return value + + def ask_if_needed(self): while True: # Display question if no value filled or if it's a readonly message if Moulinette.interface.type== 'cli': - text_for_user_input_in_cli = self._format_text_for_user_input_in_cli( - question - ) + text_for_user_input_in_cli = self._format_text_for_user_input_in_cli() if getattr(self, "readonly", False): Moulinette.display(text_for_user_input_in_cli) - elif question.value is None: + elif self.value is None: prefill = "" - if question.current_value is not None: - prefill = question.current_value - elif question.default is not None: - prefill = question.default - question.value = Moulinette.prompt( + if self.current_value is not None: + prefill = self.humanize(self.current_value, self) + elif self.default is not None: + prefill = self.default + self.value = Moulinette.prompt( message=text_for_user_input_in_cli, is_password=self.hide_user_input_in_prompt, confirm=self.hide_user_input_in_prompt, prefill=prefill, - is_multiline=(question.type == "text") + is_multiline=(self.type == "text") ) + # Normalization + # This is done to enforce a certain formating like for boolean + self.value = self.normalize(self.value, self) # Apply default value - if question.value in [None, ""] and question.default is not None: - question.value = ( + if self.value in [None, ""] and self.default is not None: + self.value = ( getattr(self, "default_value", None) - if question.default is None - else question.default + if self.default is None + else self.default ) # Prevalidation try: - self._prevalidate(question) + self._prevalidate() except YunohostValidationError as e: if Moulinette.interface.type== 'api': raise Moulinette.display(str(e), 'error') - question.value = None + self.value = None continue break - # this is done to enforce a certain formating like for boolean - # by default it doesn't do anything - question.value = self._post_parse_value(question) + self.value = self._post_parse_value() - return (question.value, self.argument_type) + return (self.value, self.argument_type) - def _prevalidate(self, question): - if question.value in [None, ""] and not question.optional: + + def _prevalidate(self): + if self.value in [None, ""] and not self.optional: raise YunohostValidationError( - "app_argument_required", name=question.name + "app_argument_required", name=self.name ) # we have an answer, do some post checks - if question.value is not None: - if question.choices and question.value not in question.choices: - self._raise_invalid_answer(question) - if question.pattern and not re.match(question.pattern['regexp'], str(question.value)): + if self.value is not None: + if self.choices and self.value not in self.choices: + self._raise_invalid_answer(self) + if self.pattern and not re.match(self.pattern['regexp'], str(self.value)): raise YunohostValidationError( - question.pattern['error'], - name=question.name, - value=question.value, + self.pattern['error'], + name=self.name, + value=self.value, ) - def _raise_invalid_answer(self, question): + def _raise_invalid_answer(self): raise YunohostValidationError( "app_argument_choice_invalid", - name=question.name, - value=question.value, - choices=", ".join(question.choices), + name=self.name, + value=self.value, + choices=", ".join(self.choices), ) - def _format_text_for_user_input_in_cli(self, question): - text_for_user_input_in_cli = _value_for_locale(question.ask) + def _format_text_for_user_input_in_cli(self): + text_for_user_input_in_cli = _value_for_locale(self.ask) - if question.choices: - text_for_user_input_in_cli += " [{0}]".format(" | ".join(question.choices)) + if self.choices: + text_for_user_input_in_cli += " [{0}]".format(" | ".join(self.choices)) - if question.help or question.helpLink: + if self.help or self.helpLink: text_for_user_input_in_cli += ":\033[m" - if question.help: + if self.help: text_for_user_input_in_cli += "\n - " - text_for_user_input_in_cli += _value_for_locale(question.help) - if question.helpLink: - if not isinstance(question.helpLink, dict): - question.helpLink = {'href': question.helpLink} - text_for_user_input_in_cli += f"\n - See {question.helpLink['href']}" + text_for_user_input_in_cli += _value_for_locale(self.help) + if self.helpLink: + if not isinstance(self.helpLink, dict): + self.helpLink = {'href': self.helpLink} + text_for_user_input_in_cli += f"\n - See {self.helpLink['href']}" return text_for_user_input_in_cli - def _post_parse_value(self, question): - if not question.redact: - return question.value + def _post_parse_value(self): + if not self.redact: + return self.value # Tell the operation_logger to redact all password-type / secret args # Also redact the % escaped version of the password that might appear in # the 'args' section of metadata (relevant for password with non-alphanumeric char) data_to_redact = [] - if question.value and isinstance(question.value, str): - data_to_redact.append(question.value) - if question.current_value and isinstance(question.current_value, str): - data_to_redact.append(question.current_value) + if self.value and isinstance(self.value, str): + data_to_redact.append(self.value) + if self.current_value and isinstance(self.current_value, str): + data_to_redact.append(self.current_value) data_to_redact += [ urllib.parse.quote(data) for data in data_to_redact @@ -488,50 +489,60 @@ class YunoHostArgumentFormatParser(object): if self.operation_logger: self.operation_logger.data_to_redact.extend(data_to_redact) elif data_to_redact: - raise YunohostError("app_argument_cant_redact", arg=question.name) + raise YunohostError("app_argument_cant_redact", arg=self.name) - return question.value + return self.value -class StringArgumentParser(YunoHostArgumentFormatParser): +class StringQuestion(Question): argument_type = "string" default_value = "" -class TagsArgumentParser(YunoHostArgumentFormatParser): +class TagsQuestion(Question): argument_type = "tags" - def _prevalidate(self, question): - values = question.value - for value in values.split(','): - question.value = value - super()._prevalidate(question) - question.value = values + @staticmethod + def humanize(value, option={}): + if isinstance(value, list): + return ','.join(value) + return value + + def _prevalidate(self): + values = self.value + if isinstance(values, str): + values = values.split(',') + for value in values: + self.value = value + super()._prevalidate() + self.value = values -class PasswordArgumentParser(YunoHostArgumentFormatParser): +class PasswordQuestion(Question): hide_user_input_in_prompt = True argument_type = "password" default_value = "" forbidden_chars = "{}" - def parse_question(self, question, user_answers): - question = super(PasswordArgumentParser, self).parse_question( - question, user_answers - ) - question.redact = True - if question.default is not None: + def __init__(self, question, user_answers): + super().__init__(question, user_answers) + self.redact = True + if self.default is not None: raise YunohostValidationError( - "app_argument_password_no_default", name=question.name + "app_argument_password_no_default", name=self.name ) - return question + @staticmethod + def humanize(value, option={}): + if value: + return '***' # Avoid to display the password on screen + return "" - def _prevalidate(self, question): - super()._prevalidate(question) + def _prevalidate(self): + super()._prevalidate() - if question.value is not None: - if any(char in question.value for char in self.forbidden_chars): + if self.value is not None: + if any(char in self.value for char in self.forbidden_chars): raise YunohostValidationError( "pattern_password_app", forbidden_chars=self.forbidden_chars ) @@ -539,181 +550,206 @@ class PasswordArgumentParser(YunoHostArgumentFormatParser): # If it's an optional argument the value should be empty or strong enough from yunohost.utils.password import assert_password_is_strong_enough - assert_password_is_strong_enough("user", question.value) + assert_password_is_strong_enough("user", self.value) -class PathArgumentParser(YunoHostArgumentFormatParser): +class PathQuestion(Question): argument_type = "path" default_value = "" -class BooleanArgumentParser(YunoHostArgumentFormatParser): +class BooleanQuestion(Question): argument_type = "boolean" default_value = False + yes_answers = ["1", "yes", "y", "true", "t", "on"] + no_answers = ["0", "no", "n", "false", "f", "off"] - def parse_question(self, question, user_answers): - question = super().parse_question( - question, user_answers + @staticmethod + def humanize(value, option={}): + yes = option.get('yes', 1) + no = option.get('no', 0) + value = str(value).lower() + if value == str(yes).lower(): + return 'yes' + if value == str(no).lower(): + return 'no' + if value in BooleanQuestion.yes_answers: + return 'yes' + if value in BooleanQuestion.no_answers: + return 'no' + + if value in ['none', ""]: + return '' + + raise YunohostValidationError( + "app_argument_choice_invalid", + name=self.name, + value=self.value, + choices="yes, no, y, n, 1, 0", ) - if question.default is None: - question.default = False + @staticmethod + def normalize(value, option={}): + yes = option.get('yes', 1) + no = option.get('no', 0) - return question + if str(value).lower() in BooleanQuestion.yes_answers: + return yes - def _format_text_for_user_input_in_cli(self, question): - text_for_user_input_in_cli = _value_for_locale(question.ask) + if str(value).lower() in BooleanQuestion.no_answers: + return no + + if value in [None, ""]: + return None + raise YunohostValidationError( + "app_argument_choice_invalid", + name=self.name, + value=self.value, + choices="yes, no, y, n, 1, 0", + ) + + def __init__(self, question, user_answers): + super().__init__(question, user_answers) + self.yes = question.get('yes', 1) + self.no = question.get('no', 0) + if self.default is None: + self.default = False + + + def _format_text_for_user_input_in_cli(self): + text_for_user_input_in_cli = _value_for_locale(self.ask) text_for_user_input_in_cli += " [yes | no]" - if question.default is not None: - formatted_default = "yes" if question.default else "no" + if self.default is not None: + formatted_default = self.humanize(self.default) text_for_user_input_in_cli += " (default: {0})".format(formatted_default) return text_for_user_input_in_cli - def _post_parse_value(self, question): - if isinstance(question.value, bool): - return 1 if question.value else 0 - - if str(question.value).lower() in ["1", "yes", "y", "true"]: - return 1 - - if str(question.value).lower() in ["0", "no", "n", "false"]: - return 0 - - raise YunohostValidationError( - "app_argument_choice_invalid", - name=question.name, - value=question.value, - choices="yes, no, y, n, 1, 0", - ) + def get(self, key, default=None): + try: + return getattr(self, key) + except AttributeError: + return default -class DomainArgumentParser(YunoHostArgumentFormatParser): + +class DomainQuestion(Question): argument_type = "domain" - def parse_question(self, question, user_answers): + def __init__(self, question, user_answers): from yunohost.domain import domain_list, _get_maindomain - question = super(DomainArgumentParser, self).parse_question( - question, user_answers - ) + super().__init__(question, user_answers) - if question.default is None: - question.default = _get_maindomain() + if self.default is None: + self.default = _get_maindomain() - question.choices = domain_list()["domains"] + self.choices = domain_list()["domains"] - return question - def _raise_invalid_answer(self, question): + def _raise_invalid_answer(self): raise YunohostValidationError( - "app_argument_invalid", field=question.name, error=m18n.n("domain_unknown") + "app_argument_invalid", field=self.name, error=m18n.n("domain_unknown") ) -class UserArgumentParser(YunoHostArgumentFormatParser): +class UserQuestion(Question): argument_type = "user" - def parse_question(self, question, user_answers): + def __init__(self, question, user_answers): from yunohost.user import user_list, user_info from yunohost.domain import _get_maindomain - question = super(UserArgumentParser, self).parse_question( - question, user_answers - ) - question.choices = user_list()["users"] - if question.default is None: + super().__init__(question, user_answers) + self.choices = user_list()["users"] + if self.default is None: root_mail = "root@%s" % _get_maindomain() - for user in question.choices.keys(): + for user in self.choices.keys(): if root_mail in user_info(user).get("mail-aliases", []): - question.default = user + self.default = user break - return question - def _raise_invalid_answer(self, question): + def _raise_invalid_answer(self): raise YunohostValidationError( "app_argument_invalid", - field=question.name, - error=m18n.n("user_unknown", user=question.value), + field=self.name, + error=m18n.n("user_unknown", user=self.value), ) -class NumberArgumentParser(YunoHostArgumentFormatParser): +class NumberQuestion(Question): argument_type = "number" default_value = "" - def parse_question(self, question, user_answers): - question_parsed = super().parse_question( - question, user_answers - ) - question_parsed.min = question.get('min', None) - question_parsed.max = question.get('max', None) - if question_parsed.default is None: - question_parsed.default = 0 + @staticmethod + def humanize(value, option={}): + return str(value) - return question_parsed + def __init__(self, question, user_answers): + super().__init__(question, user_answers) + self.min = question.get('min', None) + self.max = question.get('max', None) + self.step = question.get('step', None) - def _prevalidate(self, question): - super()._prevalidate(question) - if not isinstance(question.value, int) and not (isinstance(question.value, str) and question.value.isdigit()): + + def _prevalidate(self): + super()._prevalidate() + if not isinstance(self.value, int) and not (isinstance(self.value, str) and self.value.isdigit()): raise YunohostValidationError( - "app_argument_invalid", field=question.name, error=m18n.n("invalid_number") + "app_argument_invalid", field=self.name, error=m18n.n("invalid_number") ) - if question.min is not None and int(question.value) < question.min: + if self.min is not None and int(self.value) < self.min: raise YunohostValidationError( - "app_argument_invalid", field=question.name, error=m18n.n("invalid_number") + "app_argument_invalid", field=self.name, error=m18n.n("invalid_number") ) - if question.max is not None and int(question.value) > question.max: + if self.max is not None and int(self.value) > self.max: raise YunohostValidationError( - "app_argument_invalid", field=question.name, error=m18n.n("invalid_number") + "app_argument_invalid", field=self.name, error=m18n.n("invalid_number") ) - def _post_parse_value(self, question): - if isinstance(question.value, int): - return super()._post_parse_value(question) + def _post_parse_value(self): + if isinstance(self.value, int): + return super()._post_parse_value() - if isinstance(question.value, str) and question.value.isdigit(): - return int(question.value) + if isinstance(self.value, str) and self.value.isdigit(): + return int(self.value) raise YunohostValidationError( - "app_argument_invalid", field=question.name, error=m18n.n("invalid_number") + "app_argument_invalid", field=self.name, error=m18n.n("invalid_number") ) -class DisplayTextArgumentParser(YunoHostArgumentFormatParser): +class DisplayTextQuestion(Question): argument_type = "display_text" readonly = True - def parse_question(self, question, user_answers): - question_parsed = super().parse_question( - question, user_answers - ) + def __init__(self, question, user_answers): + super().__init__(question, user_answers) - question_parsed.optional = True - question_parsed.style = question.get('style', 'info') + self.optional = True + self.style = question.get('style', 'info') - return question_parsed - def _format_text_for_user_input_in_cli(self, question): - text = question.ask['en'] + def _format_text_for_user_input_in_cli(self): + text = self.ask['en'] - if question.style in ['success', 'info', 'warning', 'danger']: + if self.style in ['success', 'info', 'warning', 'danger']: color = { 'success': 'green', 'info': 'cyan', 'warning': 'yellow', 'danger': 'red' } - return colorize(m18n.g(question.style), color[question.style]) + f" {text}" + return colorize(m18n.g(self.style), color[self.style]) + f" {text}" else: return text -class FileArgumentParser(YunoHostArgumentFormatParser): +class FileQuestion(Question): argument_type = "file" upload_dirs = [] @@ -725,55 +761,52 @@ class FileArgumentParser(YunoHostArgumentFormatParser): if os.path.exists(upload_dir): shutil.rmtree(upload_dir) - def parse_question(self, question, user_answers): - question_parsed = super().parse_question( - question, user_answers - ) - if question.get('accept'): - question_parsed.accept = question.get('accept').replace(' ', '').split(',') + def __init__(self, question, user_answers): + super().__init__(question, user_answers) + if self.get('accept'): + self.accept = question.get('accept').replace(' ', '').split(',') else: - question_parsed.accept = [] + self.accept = [] if Moulinette.interface.type== 'api': - if user_answers.get(f"{question_parsed.name}[name]"): - question_parsed.value = { - 'content': question_parsed.value, - 'filename': user_answers.get(f"{question_parsed.name}[name]", question_parsed.name), + if user_answers.get(f"{self.name}[name]"): + self.value = { + 'content': self.value, + 'filename': user_answers.get(f"{self.name}[name]", self.name), } # If path file are the same - if question_parsed.value and str(question_parsed.value) == question_parsed.current_value: - question_parsed.value = None + if self.value and str(self.value) == self.current_value: + self.value = None - return question_parsed - def _prevalidate(self, question): - super()._prevalidate(question) - if isinstance(question.value, str) and question.value and not os.path.exists(question.value): + def _prevalidate(self): + super()._prevalidate() + if isinstance(self.value, str) and self.value and not os.path.exists(self.value): raise YunohostValidationError( - "app_argument_invalid", field=question.name, error=m18n.n("invalid_number1") + "app_argument_invalid", field=self.name, error=m18n.n("invalid_number1") ) - if question.value in [None, ''] or not question.accept: + if self.value in [None, ''] or not self.accept: return - filename = question.value if isinstance(question.value, str) else question.value['filename'] - if '.' not in filename or '.' + filename.split('.')[-1] not in question.accept: + filename = self.value if isinstance(self.value, str) else self.value['filename'] + if '.' not in filename or '.' + filename.split('.')[-1] not in self.accept: raise YunohostValidationError( - "app_argument_invalid", field=question.name, error=m18n.n("invalid_number2") + "app_argument_invalid", field=self.name, error=m18n.n("invalid_number2") ) - def _post_parse_value(self, question): + def _post_parse_value(self): from base64 import b64decode # Upload files from API # A file arg contains a string with "FILENAME:BASE64_CONTENT" - if not question.value: - return question.value + if not self.value: + return self.value if Moulinette.interface.type== 'api': upload_dir = tempfile.mkdtemp(prefix='tmp_configpanel_') - FileArgumentParser.upload_dirs += [upload_dir] - filename = question.value['filename'] - logger.debug(f"Save uploaded file {question.value['filename']} from API into {upload_dir}") + FileQuestion.upload_dirs += [upload_dir] + filename = self.value['filename'] + logger.debug(f"Save uploaded file {self.value['filename']} from API into {upload_dir}") # Filename is given by user of the API. For security reason, we have replaced # os.path.join to avoid the user to be able to rewrite a file in filesystem @@ -785,7 +818,7 @@ class FileArgumentParser(YunoHostArgumentFormatParser): while os.path.exists(file_path): file_path = os.path.normpath(upload_dir + "/" + filename + (".%d" % i)) i += 1 - content = question.value['content'] + content = self.value['content'] try: with open(file_path, 'wb') as f: f.write(b64decode(content)) @@ -793,31 +826,31 @@ class FileArgumentParser(YunoHostArgumentFormatParser): raise YunohostError("cannot_write_file", file=file_path, error=str(e)) except Exception as e: raise YunohostError("error_writing_file", file=file_path, error=str(e)) - question.value = file_path - return question.value + self.value = file_path + return self.value ARGUMENTS_TYPE_PARSERS = { - "string": StringArgumentParser, - "text": StringArgumentParser, - "select": StringArgumentParser, - "tags": TagsArgumentParser, - "email": StringArgumentParser, - "url": StringArgumentParser, - "date": StringArgumentParser, - "time": StringArgumentParser, - "color": StringArgumentParser, - "password": PasswordArgumentParser, - "path": PathArgumentParser, - "boolean": BooleanArgumentParser, - "domain": DomainArgumentParser, - "user": UserArgumentParser, - "number": NumberArgumentParser, - "range": NumberArgumentParser, - "display_text": DisplayTextArgumentParser, - "alert": DisplayTextArgumentParser, - "markdown": DisplayTextArgumentParser, - "file": FileArgumentParser, + "string": StringQuestion, + "text": StringQuestion, + "select": StringQuestion, + "tags": TagsQuestion, + "email": StringQuestion, + "url": StringQuestion, + "date": StringQuestion, + "time": StringQuestion, + "color": StringQuestion, + "password": PasswordQuestion, + "path": PathQuestion, + "boolean": BooleanQuestion, + "domain": DomainQuestion, + "user": UserQuestion, + "number": NumberQuestion, + "range": NumberQuestion, + "display_text": DisplayTextQuestion, + "alert": DisplayTextQuestion, + "markdown": DisplayTextQuestion, + "file": FileQuestion, } def parse_args_in_yunohost_format(user_answers, argument_questions): @@ -834,11 +867,12 @@ def parse_args_in_yunohost_format(user_answers, argument_questions): parsed_answers_dict = OrderedDict() for question in argument_questions: - parser = ARGUMENTS_TYPE_PARSERS[question.get("type", "string")]() + question_class = ARGUMENTS_TYPE_PARSERS[question.get("type", "string")] + question = question_class(question, user_answers) - answer = parser.parse(question=question, user_answers=user_answers) + answer = question.ask_if_needed() if answer is not None: - parsed_answers_dict[question["name"]] = answer + parsed_answers_dict[question.name] = answer return parsed_answers_dict From 97c0a74f8f52fcfcd2ded683115aa543a5a9730e Mon Sep 17 00:00:00 2001 From: ljf Date: Sat, 4 Sep 2021 19:03:11 +0200 Subject: [PATCH 2899/3170] [enh] yes/no args on boolean aquestion + semantic --- src/yunohost/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 215f91d48..d3717c6e7 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -55,7 +55,7 @@ from moulinette.utils.filesystem import ( from yunohost.service import service_status, _run_service_command from yunohost.utils import packages, config -from yunohost.utils.config import ConfigPanel, parse_args_in_yunohost_format, YunoHostArgumentFormatParser +from yunohost.utils.config import ConfigPanel, parse_args_in_yunohost_format, Question from yunohost.utils.i18n import _value_for_locale from yunohost.utils.error import YunohostError, YunohostValidationError from yunohost.utils.filesystem import free_space_in_directory @@ -1773,7 +1773,7 @@ def app_config_set(operation_logger, app, key=None, value=None, args=None, args_ config = AppConfigPanel(app) - YunoHostArgumentFormatParser.operation_logger = operation_logger + Question.operation_logger = operation_logger operation_logger.start() result = config.set(key, value, args, args_file) From 29ff4faf307d0c296b6f7da668d32eb22ce37d1a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 4 Sep 2021 19:05:02 +0200 Subject: [PATCH 2900/3170] i18n: Add add_missing_keys script --- tests/add_missing_keys.py | 193 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 193 insertions(+) create mode 100644 tests/add_missing_keys.py diff --git a/tests/add_missing_keys.py b/tests/add_missing_keys.py new file mode 100644 index 000000000..30c6c6640 --- /dev/null +++ b/tests/add_missing_keys.py @@ -0,0 +1,193 @@ +# -*- coding: utf-8 -*- + +import os +import re +import glob +import json +import yaml +import subprocess + +############################################################################### +# Find used keys in python code # +############################################################################### + + +def find_expected_string_keys(): + + # Try to find : + # m18n.n( "foo" + # YunohostError("foo" + # YunohostValidationError("foo" + # # i18n: foo + p1 = re.compile(r"m18n\.n\(\n*\s*[\"\'](\w+)[\"\']") + p2 = re.compile(r"YunohostError\(\n*\s*[\'\"](\w+)[\'\"]") + p3 = re.compile(r"YunohostValidationError\(\n*\s*[\'\"](\w+)[\'\"]") + p4 = re.compile(r"# i18n: [\'\"]?(\w+)[\'\"]?") + + python_files = glob.glob("src/yunohost/*.py") + python_files.extend(glob.glob("src/yunohost/utils/*.py")) + python_files.extend(glob.glob("src/yunohost/data_migrations/*.py")) + python_files.extend(glob.glob("src/yunohost/authenticators/*.py")) + python_files.extend(glob.glob("data/hooks/diagnosis/*.py")) + python_files.append("bin/yunohost") + + for python_file in python_files: + content = open(python_file).read() + for m in p1.findall(content): + if m.endswith("_"): + continue + yield m + for m in p2.findall(content): + if m.endswith("_"): + continue + yield m + for m in p3.findall(content): + if m.endswith("_"): + continue + yield m + for m in p4.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 + p3 = re.compile(r"[\"\'](diagnosis_[a-z]+_\w+)[\"\']") + for python_file in glob.glob("data/hooks/diagnosis/*.py"): + content = open(python_file).read() + for m in p3.findall(content): + if m.endswith("_"): + # Ignore some name fragments which are actually concatenated with other stuff.. + continue + yield m + yield "diagnosis_description_" + os.path.basename(python_file)[:-3].split("-")[ + -1 + ] + + # For each migration, expect to find "migration_description_" + for path in glob.glob("src/yunohost/data_migrations/*.py"): + if "__init__" in path: + continue + yield "migration_description_" + os.path.basename(path)[:-3] + + # For each default service, expect to find "service_description_" + for service, info in yaml.safe_load( + open("data/templates/yunohost/services.yml") + ).items(): + if info is None: + continue + yield "service_description_" + service + + # For all unit operations, expect to find "log_" + # A unit operation is created either using the @is_unit_operation decorator + # or using OperationLogger( + cmd = "grep -hr '@is_unit_operation' src/yunohost/ -A3 2>/dev/null | grep '^def' | sed -E 's@^def (\\w+)\\(.*@\\1@g'" + for funcname in ( + subprocess.check_output(cmd, shell=True).decode("utf-8").strip().split("\n") + ): + yield "log_" + funcname + + p4 = re.compile(r"OperationLogger\(\n*\s*[\"\'](\w+)[\"\']") + for python_file in python_files: + content = open(python_file).read() + for m in ("log_" + match for match in p4.findall(content)): + yield m + + # Global settings descriptions + # Will be on a line like : ("service.ssh.allow_deprecated_dsa_hostkey", {"type": "bool", ... + p5 = re.compile(r" \(\n*\s*[\"\'](\w[\w\.]+)[\"\'],") + content = open("src/yunohost/settings.py").read() + for m in ( + "global_settings_setting_" + s.replace(".", "_") for s in p5.findall(content) + ): + yield m + + # Keys for the actionmap ... + for category in yaml.safe_load(open("data/actionsmap/yunohost.yml")).values(): + if "actions" not in category.keys(): + continue + for action in category["actions"].values(): + if "arguments" not in action.keys(): + continue + for argument in action["arguments"].values(): + extra = argument.get("extra") + if not extra: + continue + if "password" in extra: + yield extra["password"] + if "ask" in extra: + yield extra["ask"] + if "comment" in extra: + yield extra["comment"] + if "pattern" in extra: + yield extra["pattern"][1] + if "help" in extra: + yield extra["help"] + + # Hardcoded expected keys ... + yield "admin_password" # Not sure that's actually used nowadays... + + for method in ["tar", "copy", "custom"]: + yield "backup_applying_method_%s" % method + yield "backup_method_%s_finished" % method + + for level in ["danger", "thirdparty", "warning"]: + yield "confirm_app_install_%s" % level + + for errortype in ["not_found", "error", "warning", "success", "not_found_details"]: + yield "diagnosis_domain_expiration_%s" % errortype + yield "diagnosis_domain_not_found_details" + + for errortype in ["bad_status_code", "connection_error", "timeout"]: + yield "diagnosis_http_%s" % errortype + + yield "password_listed" + for i in [1, 2, 3, 4]: + yield "password_too_simple_%s" % i + + checks = [ + "outgoing_port_25_ok", + "ehlo_ok", + "fcrdns_ok", + "blacklist_ok", + "queue_ok", + "ehlo_bad_answer", + "ehlo_unreachable", + "ehlo_bad_answer_details", + "ehlo_unreachable_details", + ] + for check in checks: + yield "diagnosis_mail_%s" % check + + +############################################################################### +# Load en locale json keys # +############################################################################### + + +def keys_defined_for_en(): + return json.loads(open("locales/en.json").read()).keys() + + +############################################################################### +# Compare keys used and keys defined # +############################################################################### + + +expected_string_keys = set(find_expected_string_keys()) +keys_defined = set(keys_defined_for_en()) + + +undefined_keys = expected_string_keys.difference(keys_defined) +undefined_keys = sorted(undefined_keys) + + +j = json.loads(open("locales/en.json").read()) +for key in undefined_keys: + j[key] = "FIXME" + +json.dump( + j, + open("locales/en.json", "w"), + indent=4, + ensure_ascii=False, + sort_keys=True, +) From fd5b2e8953624b5686552c00630c4ba24b4f074f Mon Sep 17 00:00:00 2001 From: ljf Date: Sat, 4 Sep 2021 19:05:13 +0200 Subject: [PATCH 2901/3170] [enh] Several fixes in domain config --- data/actionsmap/yunohost.yml | 2 +- data/other/config_domain.toml | 8 +- data/other/registrar_list.toml | 238 ++++++++++++++++----------------- src/yunohost/domain.py | 16 ++- 4 files changed, 135 insertions(+), 129 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 0da811522..759dd1f22 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -575,7 +575,7 @@ domain: action_help: Apply a new configuration api: PUT /domains//config arguments: - app: + domain: help: Domain name key: help: The question or form key diff --git a/data/other/config_domain.toml b/data/other/config_domain.toml index b2211417d..07aaf085e 100644 --- a/data/other/config_domain.toml +++ b/data/other/config_domain.toml @@ -3,13 +3,14 @@ i18n = "domain_config" [feature] [feature.mail] + services = ['postfix', 'dovecot'] [feature.mail.mail_out] type = "boolean" - default = true + default = 1 [feature.mail.mail_in] type = "boolean" - default = true + default = 1 [feature.mail.backup_mx] type = "tags" @@ -21,10 +22,11 @@ i18n = "domain_config" [feature.xmpp.xmpp] type = "boolean" - default = false + default = 0 [dns] [dns.registrar] + optional = true [dns.registrar.unsupported] ask = "DNS zone of this domain can't be auto-configured, you should do it manually." type = "alert" diff --git a/data/other/registrar_list.toml b/data/other/registrar_list.toml index 33d115075..1c2e73111 100644 --- a/data/other/registrar_list.toml +++ b/data/other/registrar_list.toml @@ -1,7 +1,7 @@ [aliyun] [aliyun.auth_key_id] type = "string" - redact = True + redact = true [aliyun.auth_secret] type = "password" @@ -9,7 +9,7 @@ [aurora] [aurora.auth_api_key] type = "string" - redact = True + redact = true [aurora.auth_secret_key] type = "password" @@ -17,48 +17,48 @@ [azure] [azure.auth_client_id] type = "string" - redact = True + redact = true [azure.auth_client_secret] type = "password" [azure.auth_tenant_id] type = "string" - redact = True + redact = true [azure.auth_subscription_id] type = "string" - redact = True + redact = true [azure.resource_group] type = "string" - redact = True + redact = true [cloudflare] [cloudflare.auth_username] type = "string" - redact = True + redact = true [cloudflare.auth_token] type = "string" - redact = True + redact = true [cloudflare.zone_id] type = "string" - redact = True + redact = true [cloudns] [cloudns.auth_id] type = "string" - redact = True + redact = true [cloudns.auth_subid] type = "string" - redact = True + redact = true [cloudns.auth_subuser] type = "string" - redact = True + redact = true [cloudns.auth_password] type = "password" @@ -71,50 +71,50 @@ [cloudxns] [cloudxns.auth_username] type = "string" - redact = True + redact = true [cloudxns.auth_token] type = "string" - redact = True + redact = true [conoha] [conoha.auth_region] type = "string" - redact = True + redact = true [conoha.auth_token] type = "string" - redact = True + redact = true [conoha.auth_username] type = "string" - redact = True + redact = true [conoha.auth_password] type = "password" [conoha.auth_tenant_id] type = "string" - redact = True + redact = true [constellix] [constellix.auth_username] type = "string" - redact = True + redact = true [constellix.auth_token] type = "string" - redact = True + redact = true [digitalocean] [digitalocean.auth_token] type = "string" - redact = True + redact = true [dinahosting] [dinahosting.auth_username] type = "string" - redact = True + redact = true [dinahosting.auth_password] type = "password" @@ -125,78 +125,78 @@ [directadmin.auth_username] type = "string" - redact = True + redact = true [directadmin.endpoint] type = "string" - redact = True + redact = true [dnsimple] [dnsimple.auth_token] type = "string" - redact = True + redact = true [dnsimple.auth_username] type = "string" - redact = True + redact = true [dnsimple.auth_password] type = "password" [dnsimple.auth_2fa] type = "string" - redact = True + redact = true [dnsmadeeasy] [dnsmadeeasy.auth_username] type = "string" - redact = True + redact = true [dnsmadeeasy.auth_token] type = "string" - redact = True + redact = true [dnspark] [dnspark.auth_username] type = "string" - redact = True + redact = true [dnspark.auth_token] type = "string" - redact = True + redact = true [dnspod] [dnspod.auth_username] type = "string" - redact = True + redact = true [dnspod.auth_token] type = "string" - redact = True + redact = true [dreamhost] [dreamhost.auth_token] type = "string" - redact = True + redact = true [dynu] [dynu.auth_token] type = "string" - redact = True + redact = true [easydns] [easydns.auth_username] type = "string" - redact = True + redact = true [easydns.auth_token] type = "string" - redact = True + redact = true [easyname] [easyname.auth_username] type = "string" - redact = True + redact = true [easyname.auth_password] type = "password" @@ -204,7 +204,7 @@ [euserv] [euserv.auth_username] type = "string" - redact = True + redact = true [euserv.auth_password] type = "password" @@ -212,7 +212,7 @@ [exoscale] [exoscale.auth_key] type = "string" - redact = True + redact = true [exoscale.auth_secret] type = "password" @@ -220,7 +220,7 @@ [gandi] [gandi.auth_token] type = "string" - redact = True + redact = true [gandi.api_protocol] type = "string" @@ -230,7 +230,7 @@ [gehirn] [gehirn.auth_token] type = "string" - redact = True + redact = true [gehirn.auth_secret] type = "password" @@ -238,16 +238,16 @@ [glesys] [glesys.auth_username] type = "string" - redact = True + redact = true [glesys.auth_token] type = "string" - redact = True + redact = true [godaddy] [godaddy.auth_key] type = "string" - redact = True + redact = true [godaddy.auth_secret] type = "password" @@ -255,12 +255,12 @@ [googleclouddns] [goggleclouddns.auth_service_account_info] type = "string" - redact = True + redact = true [gransy] [gransy.auth_username] type = "string" - redact = True + redact = true [gransy.auth_password] type = "password" @@ -268,7 +268,7 @@ [gratisdns] [gratisdns.auth_username] type = "string" - redact = True + redact = true [gratisdns.auth_password] type = "password" @@ -276,7 +276,7 @@ [henet] [henet.auth_username] type = "string" - redact = True + redact = true [henet.auth_password] type = "password" @@ -284,17 +284,17 @@ [hetzner] [hetzner.auth_token] type = "string" - redact = True + redact = true [hostingde] [hostingde.auth_token] type = "string" - redact = True + redact = true [hover] [hover.auth_username] type = "string" - redact = True + redact = true [hover.auth_password] type = "password" @@ -302,37 +302,37 @@ [infoblox] [infoblox.auth_user] type = "string" - redact = True + redact = true [infoblox.auth_psw] type = "password" [infoblox.ib_view] type = "string" - redact = True + redact = true [infoblox.ib_host] type = "string" - redact = True + redact = true [infomaniak] [infomaniak.auth_token] type = "string" - redact = True + redact = true [internetbs] [internetbs.auth_key] type = "string" - redact = True + redact = true [internetbs.auth_password] type = "string" - redact = True + redact = true [inwx] [inwx.auth_username] type = "string" - redact = True + redact = true [inwx.auth_password] type = "password" @@ -340,79 +340,79 @@ [joker] [joker.auth_token] type = "string" - redact = True + redact = true [linode] [linode.auth_token] type = "string" - redact = True + redact = true [linode4] [linode4.auth_token] type = "string" - redact = True + redact = true [localzone] [localzone.filename] type = "string" - redact = True + redact = true [luadns] [luadns.auth_username] type = "string" - redact = True + redact = true [luadns.auth_token] type = "string" - redact = True + redact = true [memset] [memset.auth_token] type = "string" - redact = True + redact = true [mythicbeasts] [mythicbeasts.auth_username] type = "string" - redact = True + redact = true [mythicbeasts.auth_password] type = "password" [mythicbeasts.auth_token] type = "string" - redact = True + redact = true [namecheap] [namecheap.auth_token] type = "string" - redact = True + redact = true [namecheap.auth_username] type = "string" - redact = True + redact = true [namecheap.auth_client_ip] type = "string" - redact = True + redact = true [namecheap.auth_sandbox] type = "string" - redact = True + redact = true [namesilo] [namesilo.auth_token] type = "string" - redact = True + redact = true [netcup] [netcup.auth_customer_id] type = "string" - redact = True + redact = true [netcup.auth_api_key] type = "string" - redact = True + redact = true [netcup.auth_api_password] type = "password" @@ -420,89 +420,89 @@ [nfsn] [nfsn.auth_username] type = "string" - redact = True + redact = true [nfsn.auth_token] type = "string" - redact = True + redact = true [njalla] [njalla.auth_token] type = "string" - redact = True + redact = true [nsone] [nsone.auth_token] type = "string" - redact = True + redact = true [onapp] [onapp.auth_username] type = "string" - redact = True + redact = true [onapp.auth_token] type = "string" - redact = True + redact = true [onapp.auth_server] type = "string" - redact = True + redact = true [online] [online.auth_token] type = "string" - redact = True + redact = true [ovh] [ovh.auth_entrypoint] type = "string" - redact = True + redact = true [ovh.auth_application_key] type = "string" - redact = True + redact = true [ovh.auth_application_secret] type = "password" [ovh.auth_consumer_key] type = "string" - redact = True + redact = true [plesk] [plesk.auth_username] type = "string" - redact = True + redact = true [plesk.auth_password] type = "password" [plesk.plesk_server] type = "string" - redact = True + redact = true [pointhq] [pointhq.auth_username] type = "string" - redact = True + redact = true [pointhq.auth_token] type = "string" - redact = True + redact = true [powerdns] [powerdns.auth_token] type = "string" - redact = True + redact = true [powerdns.pdns_server] type = "string" - redact = True + redact = true [powerdns.pdns_server_id] type = "string" - redact = True + redact = true [powerdns.pdns_disable_notify] type = "boolean" @@ -510,67 +510,67 @@ [rackspace] [rackspace.auth_account] type = "string" - redact = True + redact = true [rackspace.auth_username] type = "string" - redact = True + redact = true [rackspace.auth_api_key] type = "string" - redact = True + redact = true [rackspace.auth_token] type = "string" - redact = True + redact = true [rackspace.sleep_time] type = "string" - redact = True + redact = true [rage4] [rage4.auth_username] type = "string" - redact = True + redact = true [rage4.auth_token] type = "string" - redact = True + redact = true [rcodezero] [rcodezero.auth_token] type = "string" - redact = True + redact = true [route53] [route53.auth_access_key] type = "string" - redact = True + redact = true [route53.auth_access_secret] type = "password" [route53.private_zone] type = "string" - redact = True + redact = true [route53.auth_username] type = "string" - redact = True + redact = true [route53.auth_token] type = "string" - redact = True + redact = true [safedns] [safedns.auth_token] type = "string" - redact = True + redact = true [sakuracloud] [sakuracloud.auth_token] type = "string" - redact = True + redact = true [sakuracloud.auth_secret] type = "password" @@ -578,29 +578,29 @@ [softlayer] [softlayer.auth_username] type = "string" - redact = True + redact = true [softlayer.auth_api_key] type = "string" - redact = True + redact = true [transip] [transip.auth_username] type = "string" - redact = True + redact = true [transip.auth_api_key] type = "string" - redact = True + redact = true [ultradns] [ultradns.auth_token] type = "string" - redact = True + redact = true [ultradns.auth_username] type = "string" - redact = True + redact = true [ultradns.auth_password] type = "password" @@ -608,29 +608,29 @@ [vultr] [vultr.auth_token] type = "string" - redact = True + redact = true [yandex] [yandex.auth_token] type = "string" - redact = True + redact = true [zeit] [zeit.auth_token] type = "string" - redact = True + redact = true [zilore] [zilore.auth_key] type = "string" - redact = True + redact = true [zonomi] [zonomy.auth_token] type = "string" - redact = True + redact = true [zonomy.auth_entrypoint] type = "string" - redact = True + redact = true diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 064311a49..217a787d9 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -28,7 +28,9 @@ import os from moulinette import m18n, Moulinette from moulinette.core import MoulinetteError from moulinette.utils.log import getActionLogger -from moulinette.utils.filesystem import mkdir, write_to_file, read_yaml, write_to_yaml +from moulinette.utils.filesystem import ( + mkdir, write_to_file, read_yaml, write_to_yaml, read_toml +) from yunohost.settings import is_boolean from yunohost.app import ( @@ -37,8 +39,10 @@ from yunohost.app import ( _get_app_settings, _get_conflicting_apps, ) -from yunohost.regenconf import regen_conf, _force_clear_hashes, _process_regen_conf -from yunohost.utils.config import ConfigPanel +from yunohost.regenconf import ( + regen_conf, _force_clear_hashes, _process_regen_conf +) +from yunohost.utils.config import ConfigPanel, Question from yunohost.utils.error import YunohostError, YunohostValidationError from yunohost.log import is_unit_operation from yunohost.hook import hook_callback @@ -407,11 +411,11 @@ def domain_config_get(domain, key='', mode='classic'): return config.get(key, mode) @is_unit_operation() -def domain_config_set(operation_logger, app, key=None, value=None, args=None, args_file=None): +def domain_config_set(operation_logger, domain, key=None, value=None, args=None, args_file=None): """ Apply a new domain configuration """ - + Question.operation_logger = operation_logger config = DomainConfigPanel(domain) return config.set(key, value, args, args_file) @@ -432,7 +436,7 @@ class DomainConfigPanel(ConfigPanel): self.dns_zone = get_dns_zone_from_domain(self.domain) try: - registrar = _relevant_provider_for_domain(self.dns_zone) + registrar = _relevant_provider_for_domain(self.dns_zone)[0] except ValueError: return toml From b5aca9895d64544805f9159ba6be39b2f82b3b8d Mon Sep 17 00:00:00 2001 From: ljf Date: Sat, 4 Sep 2021 19:03:11 +0200 Subject: [PATCH 2902/3170] [enh] yes/no args on boolean aquestion + semantic --- src/yunohost/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 522f695e2..e9df193c7 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -55,7 +55,7 @@ from moulinette.utils.filesystem import ( from yunohost.service import service_status, _run_service_command from yunohost.utils import packages, config -from yunohost.utils.config import ConfigPanel, parse_args_in_yunohost_format, YunoHostArgumentFormatParser +from yunohost.utils.config import ConfigPanel, parse_args_in_yunohost_format, Question from yunohost.utils.i18n import _value_for_locale from yunohost.utils.error import YunohostError, YunohostValidationError from yunohost.utils.filesystem import free_space_in_directory @@ -1772,7 +1772,7 @@ def app_config_set(operation_logger, app, key=None, value=None, args=None, args_ config = AppConfigPanel(app) - YunoHostArgumentFormatParser.operation_logger = operation_logger + Question.operation_logger = operation_logger operation_logger.start() result = config.set(key, value, args, args_file) From 5ac79d8f0521667d6aef59a98a12c169d010e3e2 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 4 Sep 2021 19:06:16 +0200 Subject: [PATCH 2903/3170] i18n: Apply add_missing_key script ('just' to reorder keys) --- locales/en.json | 466 ++++++++++++++++++++++++------------------------ 1 file changed, 233 insertions(+), 233 deletions(-) diff --git a/locales/en.json b/locales/en.json index 8a95caaf2..7e98ad2fc 100644 --- a/locales/en.json +++ b/locales/en.json @@ -8,8 +8,8 @@ "admin_password_changed": "The administration password was changed", "admin_password_too_long": "Please choose a password shorter than 127 characters", "already_up_to_date": "Nothing to do. Everything is already up-to-date.", - "app_action_cannot_be_ran_because_required_services_down": "These required services should be running to run this action: {services}. Try restarting them to continue (and possibly investigate why they are down).", "app_action_broke_system": "This action seems to have broken these important services: {services}", + "app_action_cannot_be_ran_because_required_services_down": "These required services should be running to run this action: {services}. Try restarting them to continue (and possibly investigate why they are down).", "app_already_installed": "{app} is already installed", "app_already_installed_cant_change_url": "This app is already installed. The URL cannot be changed just by this function. Check in `app changeurl` if it's available.", "app_already_up_to_date": "{app} is already up-to-date", @@ -24,49 +24,48 @@ "app_extraction_failed": "Could not extract the installation files", "app_full_domain_unavailable": "Sorry, this app must be installed on a domain of its own, but other apps are already installed on the domain '{domain}'. You could use a subdomain dedicated to this app instead.", "app_id_invalid": "Invalid app ID", - "app_install_files_invalid": "These files cannot be installed", "app_install_failed": "Unable to install {app}: {error}", + "app_install_files_invalid": "These files cannot be installed", "app_install_script_failed": "An error occurred inside the app installation script", - "app_make_default_location_already_used": "Unable to make '{app}' the default app on the domain, '{domain}' is already in use by '{other_app}'", "app_label_deprecated": "This command is deprecated! Please use the new command 'yunohost user permission update' to manage the app label.", "app_location_unavailable": "This URL is either unavailable, or conflicts with the already installed app(s):\n{apps}", - "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 URL path (after the domain) where this app should be installed", - "app_manifest_install_ask_password": "Choose an administration password for this app", + "app_make_default_location_already_used": "Unable to make '{app}' the default app on the domain, '{domain}' is already in use by '{other_app}'", "app_manifest_install_ask_admin": "Choose an administrator user for this app", + "app_manifest_install_ask_domain": "Choose the domain where this app should be installed", "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_manifest_install_ask_password": "Choose an administration password for this app", + "app_manifest_install_ask_path": "Choose the URL path (after the domain) where this app should be installed", + "app_manifest_invalid": "Something is wrong with the app manifest: {error}", "app_not_correctly_installed": "{app} seems to be incorrectly installed", "app_not_installed": "Could not find {app} in the list of installed apps: {all_apps}", "app_not_properly_removed": "{app} has not been properly removed", + "app_not_upgraded": "The app '{failed_app}' failed to upgrade, and as a consequence the following apps' upgrades have been cancelled: {apps}", + "app_packaging_format_not_supported": "This app cannot be installed because its packaging format is not supported by your YunoHost version. You should probably consider upgrading your system.", + "app_remove_after_failed_install": "Removing the app following the installation failure...", "app_removed": "{app} uninstalled", "app_requirements_checking": "Checking required packages for {app}...", "app_requirements_unmeet": "Requirements are not met for {app}, the package {pkgname} ({version}) must be {spec}", - "app_remove_after_failed_install": "Removing the app following the installation failure...", "app_restore_failed": "Could not restore {app}: {error}", "app_restore_script_failed": "An error occured inside the app restore script", "app_sources_fetch_failed": "Could not fetch sources files, is the URL correct?", + "app_start_backup": "Collecting files to be backed up for {app}...", "app_start_install": "Installing {app}...", "app_start_remove": "Removing {app}...", - "app_start_backup": "Collecting files to be backed up for {app}...", "app_start_restore": "Restoring {app}...", "app_unknown": "Unknown app", "app_unsupported_remote_type": "Unsupported remote type used for the app", - "app_upgrade_several_apps": "The following apps will be upgraded: {apps}", "app_upgrade_app_name": "Now upgrading {app}...", "app_upgrade_failed": "Could not upgrade {app}: {error}", "app_upgrade_script_failed": "An error occurred inside the app upgrade script", + "app_upgrade_several_apps": "The following apps will be upgraded: {apps}", "app_upgrade_some_app_failed": "Some apps could not be upgraded", "app_upgraded": "{app} upgraded", - "app_packaging_format_not_supported": "This app cannot be installed because its packaging format is not supported by your YunoHost version. You should probably consider upgrading your system.", "apps_already_up_to_date": "All apps are already up-to-date", - "apps_catalog_init_success": "App catalog system initialized!", - "apps_catalog_updating": "Updating application catalog...", "apps_catalog_failed_to_download": "Unable to download the {apps_catalog} app catalog: {error}", + "apps_catalog_init_success": "App catalog system initialized!", "apps_catalog_obsolete_cache": "The app catalog cache is empty or obsolete.", "apps_catalog_update_success": "The application catalog has been updated!", - "ask_user_domain": "Domain to use for the user's email address and XMPP account", + "apps_catalog_updating": "Updating application catalog...", "ask_firstname": "First name", "ask_lastname": "Last name", "ask_main_domain": "Main domain", @@ -74,6 +73,7 @@ "ask_new_domain": "New domain", "ask_new_path": "New path", "ask_password": "Password", + "ask_user_domain": "Domain to use for the user's email address and XMPP account", "backup_abstract_method": "This backup method has yet to be implemented", "backup_actually_backuping": "Creating a backup archive from the collected files...", "backup_app_failed": "Could not back up {app}", @@ -82,11 +82,11 @@ "backup_applying_method_tar": "Creating the backup TAR archive...", "backup_archive_app_not_found": "Could not find {app} in the backup archive", "backup_archive_broken_link": "Could not access the backup archive (broken link to {path})", + "backup_archive_cant_retrieve_info_json": "Could not load infos for archive '{archive}'... The info.json cannot be retrieved (or is not a valid json).", + "backup_archive_corrupted": "It looks like the backup archive '{archive}' is corrupted : {error}", "backup_archive_name_exists": "A backup archive with this name already exists.", "backup_archive_name_unknown": "Unknown local backup archive named '{name}'", "backup_archive_open_failed": "Could not open the backup archive", - "backup_archive_cant_retrieve_info_json": "Could not load infos for archive '{archive}'... The info.json cannot be retrieved (or is not a valid json).", - "backup_archive_corrupted": "It looks like the backup archive '{archive}' is corrupted : {error}", "backup_archive_system_part_not_available": "System part '{part}' unavailable in this backup", "backup_archive_writing_error": "Could not add the files '{source}' (named in the archive '{dest}') to be backed up into the compressed archive '{archive}'", "backup_ask_for_copying_if_needed": "Do you want to perform the backup using {size}MB temporarily? (This way is used since some files could not be prepared using a more efficient method.)", @@ -94,8 +94,8 @@ "backup_cleaning_failed": "Could not clean up the temporary backup folder", "backup_copying_to_organize_the_archive": "Copying {size}MB to organize the archive", "backup_couldnt_bind": "Could not bind {src} to {dest}.", - "backup_created": "Backup created", "backup_create_size_estimation": "The archive will contain about {size} of data.", + "backup_created": "Backup created", "backup_creation_failed": "Could not create the backup archive", "backup_csv_addition_failed": "Could not add files to backup into the CSV file", "backup_csv_creation_failed": "Could not create the CSV file needed for restoration", @@ -130,163 +130,163 @@ "certmanager_cert_renew_success": "Let's Encrypt certificate renewed for the domain '{domain}'", "certmanager_cert_signing_failed": "Could not sign the new certificate", "certmanager_certificate_fetching_or_enabling_failed": "Trying to use the new certificate for {domain} did not work...", - "certmanager_domain_not_diagnosed_yet": "There is no diagnosis result for domain {domain} 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} 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 records for domain '{domain}' 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} 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_warning_subdomain_dns_record": "Subdomain '{subdomain}' does not resolve to the same IP address as '{domain}'. Some features will not be available until you fix this and regenerate the certificate.", + "certmanager_domain_not_diagnosed_yet": "There is no diagnosis result for domain {domain} 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_hit_rate_limit": "Too many certificates already issued for this exact set of domains {domain} recently. Please try again later. See https://letsencrypt.org/docs/rate-limits/ for more details", "certmanager_no_cert_file": "Could not read the certificate file for the domain {domain} (file: {file})", "certmanager_self_ca_conf_file_not_found": "Could not find configuration file for self-signing authority (file: {file})", "certmanager_unable_to_parse_self_CA_name": "Could not parse name of self-signing authority (file: {file})", - "confirm_app_install_warning": "Warning: This app may work, but is not well-integrated in YunoHost. Some features such as single sign-on and backup/restore might not be available. Install anyway? [{answers}] ", + "certmanager_warning_subdomain_dns_record": "Subdomain '{subdomain}' does not resolve to the same IP address as '{domain}'. Some features will not be available until you fix this and regenerate the certificate.", "confirm_app_install_danger": "DANGER! This app is known to be still experimental (if not explicitly not working)! You should probably NOT install it unless you know what you are doing. NO SUPPORT will be provided if this app doesn't work or breaks your system... If you are willing to take that risk anyway, type '{answers}'", "confirm_app_install_thirdparty": "DANGER! This app is not part of YunoHost's app catalog. Installing third-party apps may compromise the integrity and security of your system. You should probably NOT install it unless you know what you are doing. NO SUPPORT will be provided if this app doesn't work or breaks your system... If you are willing to take that risk anyway, type '{answers}'", + "confirm_app_install_warning": "Warning: This app may work, but is not well-integrated in YunoHost. Some features such as single sign-on and backup/restore might not be available. Install anyway? [{answers}] ", "custom_app_url_required": "You must provide a URL to upgrade your custom app {app}", + "diagnosis_apps_allgood": "All installed apps respect basic packaging practices", + "diagnosis_apps_bad_quality": "This application is currently flagged as broken on YunoHost's application catalog. This may be a temporary issue while the maintainers attempt to fix the issue. In the meantime, upgrading this app is disabled.", + "diagnosis_apps_broken": "This application is currently flagged as broken on YunoHost's application catalog. This may be a temporary issue while the maintainers attempt to fix the issue. In the meantime, upgrading this app is disabled.", + "diagnosis_apps_deprecated_practices": "This app's installed version still uses some super-old deprecated packaging practices. You should really consider upgrading it.", + "diagnosis_apps_issue": "An issue was found for app {app}", + "diagnosis_apps_not_in_app_catalog": "This application is not in YunoHost's application catalog. If it was in the past and got removed, you should consider uninstalling this app as it won't receive upgrade, and may compromise the integrity and security of your system.", + "diagnosis_apps_outdated_ynh_requirement": "This app's installed version only requires yunohost >= 2.x, which tends to indicate that it's not up to date with recommended packaging practices and helpers. You should really consider upgrading it.", + "diagnosis_backports_in_sources_list": "It looks like apt (the package manager) is configured to use the backports repository. Unless you really know what you are doing, we strongly discourage from installing packages from backports, because it's likely to create unstabilities or conflicts on your system.", "diagnosis_basesystem_hardware": "Server hardware architecture is {virt} {arch}", "diagnosis_basesystem_hardware_model": "Server model is {model}", "diagnosis_basesystem_host": "Server is running Debian {debian_version}", "diagnosis_basesystem_kernel": "Server is running Linux kernel {kernel_version}", - "diagnosis_basesystem_ynh_single_version": "{package} version: {version} ({repo})", - "diagnosis_basesystem_ynh_main_version": "Server is running YunoHost {main_version} ({repo})", "diagnosis_basesystem_ynh_inconsistent_versions": "You are running inconsistent versions of the YunoHost packages... most probably because of a failed or partial upgrade.", - "diagnosis_backports_in_sources_list": "It looks like apt (the package manager) is configured to use the backports repository. Unless you really know what you are doing, we strongly discourage from installing packages from backports, because it's likely to create unstabilities or conflicts on your system.", - "diagnosis_package_installed_from_sury": "Some system packages should be downgraded", - "diagnosis_package_installed_from_sury_details": "Some packages were inadvertendly installed from a third-party repository called Sury. The YunoHost team improved the strategy that handle these packages, but it's expected that some setups that installed PHP7.3 apps while still on Stretch have some remaining inconsistencies. To fix this situation, you should try running the following command: {cmd_to_fix}", - "diagnosis_display_tip": "To see the issues found, you can go to the Diagnosis section of the webadmin, or run 'yunohost diagnosis show --issues --human-readable' from the command-line.", - "diagnosis_failed_for_category": "Diagnosis failed for category '{category}': {error}", + "diagnosis_basesystem_ynh_main_version": "Server is running YunoHost {main_version} ({repo})", + "diagnosis_basesystem_ynh_single_version": "{package} version: {version} ({repo})", "diagnosis_cache_still_valid": "(Cache still valid for {category} diagnosis. Won't re-diagnose it yet!)", "diagnosis_cant_run_because_of_dep": "Can't run diagnosis for {category} while there are important issues related to {dep}.", - "diagnosis_ignored_issues": "(+ {nb_ignored} ignored issue(s))", - "diagnosis_found_errors": "Found {errors} significant issue(s) related to {category}!", - "diagnosis_found_errors_and_warnings": "Found {errors} significant issue(s) (and {warnings} warning(s)) related to {category}!", - "diagnosis_found_warnings": "Found {warnings} item(s) that could be improved for {category}.", - "diagnosis_everything_ok": "Everything looks good for {category}!", - "diagnosis_failed": "Failed to fetch diagnosis result for category '{category}': {error}", - "diagnosis_no_cache": "No diagnosis cache yet for category '{category}'", - "diagnosis_ip_connected_ipv4": "The server is connected to the Internet through IPv4!", - "diagnosis_ip_no_ipv4": "The server does not have working IPv4.", - "diagnosis_ip_connected_ipv6": "The server is connected to the Internet through IPv6!", - "diagnosis_ip_no_ipv6": "The server does not have working IPv6.", - "diagnosis_ip_no_ipv6_tip": "Having a working IPv6 is not mandatory for your server to work, but it is better for the health of the Internet as a whole. IPv6 should usually be automatically configured by the system or your provider if it's available. Otherwise, you might need to configure a few things manually as explained in the documentation here: https://yunohost.org/#/ipv6. If you cannot enable IPv6 or if it seems too technical for you, you can also safely ignore this warning.", - "diagnosis_ip_global": "Global IP: {global}", - "diagnosis_ip_local": "Local IP: {local}", - "diagnosis_ip_not_connected_at_all": "The server does not seem to be connected to the Internet at all!?", - "diagnosis_ip_dnsresolution_working": "Domain name resolution is working!", - "diagnosis_ip_broken_dnsresolution": "Domain name resolution seems to be broken for some reason... Is a firewall blocking DNS requests ?", - "diagnosis_ip_broken_resolvconf": "Domain name resolution seems to be broken on your server, which seems related to /etc/resolv.conf not pointing to 127.0.0.1.", - "diagnosis_ip_weird_resolvconf": "DNS resolution seems to be working, but it looks like you're using a custom /etc/resolv.conf.", - "diagnosis_ip_weird_resolvconf_details": "The file /etc/resolv.conf should be a symlink to /etc/resolvconf/run/resolv.conf itself pointing to 127.0.0.1 (dnsmasq). If you want to manually configure DNS resolvers, please edit /etc/resolv.dnsmasq.conf.", - "diagnosis_dns_good_conf": "DNS records are correctly configured for domain {domain} (category {category})", + "diagnosis_description_apps": "Applications", + "diagnosis_description_basesystem": "Base system", + "diagnosis_description_dnsrecords": "DNS records", + "diagnosis_description_ip": "Internet connectivity", + "diagnosis_description_mail": "Email", + "diagnosis_description_ports": "Ports exposure", + "diagnosis_description_regenconf": "System configurations", + "diagnosis_description_services": "Services status check", + "diagnosis_description_systemresources": "System resources", + "diagnosis_description_web": "Web", + "diagnosis_diskusage_low": "Storage {mountpoint} (on device {device}) has only {free} ({free_percent}%) space remaining (out of {total}). Be careful.", + "diagnosis_diskusage_ok": "Storage {mountpoint} (on device {device}) still has {free} ({free_percent}%) space left (out of {total})!", + "diagnosis_diskusage_verylow": "Storage {mountpoint} (on device {device}) has only {free} ({free_percent}%) space remaining (out of {total}). You should really consider cleaning up some space!", + "diagnosis_display_tip": "To see the issues found, you can go to the Diagnosis section of the webadmin, or run 'yunohost diagnosis show --issues --human-readable' from the command-line.", "diagnosis_dns_bad_conf": "Some DNS records are missing or incorrect for domain {domain} (category {category})", - "diagnosis_dns_missing_record": "According to the recommended DNS configuration, you should add a DNS record with the following info.
Type: {type}
Name: {name}
Value: {value}", "diagnosis_dns_discrepancy": "The following DNS record does not seem to follow the recommended configuration:
Type: {type}
Name: {name}
Current value: {current}
Expected value: {value}", + "diagnosis_dns_good_conf": "DNS records are correctly configured for domain {domain} (category {category})", + "diagnosis_dns_missing_record": "According to the recommended DNS configuration, you should add a DNS record with the following info.
Type: {type}
Name: {name}
Value: {value}", "diagnosis_dns_point_to_doc": "Please check the documentation at https://yunohost.org/dns_config if you need help about configuring DNS records.", - "diagnosis_dns_try_dyndns_update_force": "This domain's DNS configuration should automatically be managed by YunoHost. If that's not the case, you can try to force an update using yunohost dyndns update --force.", "diagnosis_dns_specialusedomain": "Domain {domain} is based on a special-use top-level domain (TLD) and is therefore not expected to have actual DNS records.", + "diagnosis_dns_try_dyndns_update_force": "This domain's DNS configuration should automatically be managed by YunoHost. If that's not the case, you can try to force an update using yunohost dyndns update --force.", + "diagnosis_domain_expiration_error": "Some domains will expire VERY SOON!", "diagnosis_domain_expiration_not_found": "Unable to check the expiration date for some domains", - "diagnosis_domain_not_found_details": "The domain {domain} doesn't exist in WHOIS database or is expired!", "diagnosis_domain_expiration_not_found_details": "The WHOIS information for domain {domain} doesn't seem to contain the information about the expiration date?", "diagnosis_domain_expiration_success": "Your domains are registered and not going to expire anytime soon.", "diagnosis_domain_expiration_warning": "Some domains will expire soon!", - "diagnosis_domain_expiration_error": "Some domains will expire VERY SOON!", "diagnosis_domain_expires_in": "{domain} expires in {days} days.", - "diagnosis_services_running": "Service {service} is running!", - "diagnosis_services_conf_broken": "Configuration is broken for service {service}!", - "diagnosis_services_bad_status": "Service {service} is {status} :(", - "diagnosis_services_bad_status_tip": "You can try to restart the service, and if it doesn't work, have a look at the service logs in the webadmin (from the command line, you can do this with yunohost service restart {service} and yunohost service log {service}).", - "diagnosis_diskusage_verylow": "Storage {mountpoint} (on device {device}) has only {free} ({free_percent}%) space remaining (out of {total}). You should really consider cleaning up some space!", - "diagnosis_diskusage_low": "Storage {mountpoint} (on device {device}) has only {free} ({free_percent}%) space remaining (out of {total}). Be careful.", - "diagnosis_diskusage_ok": "Storage {mountpoint} (on device {device}) still has {free} ({free_percent}%) space left (out of {total})!", - "diagnosis_ram_verylow": "The system has only {available} ({available_percent}%) RAM available! (out of {total})", + "diagnosis_domain_not_found_details": "The domain {domain} doesn't exist in WHOIS database or is expired!", + "diagnosis_everything_ok": "Everything looks good for {category}!", + "diagnosis_failed": "Failed to fetch diagnosis result for category '{category}': {error}", + "diagnosis_failed_for_category": "Diagnosis failed for category '{category}': {error}", + "diagnosis_found_errors": "Found {errors} significant issue(s) related to {category}!", + "diagnosis_found_errors_and_warnings": "Found {errors} significant issue(s) (and {warnings} warning(s)) related to {category}!", + "diagnosis_found_warnings": "Found {warnings} item(s) that could be improved for {category}.", + "diagnosis_http_bad_status_code": "It looks like another machine (maybe your internet router) answered instead of your server.
1. The most common cause for this issue is that port 80 (and 443) are not correctly forwarded to your server.
2. On more complex setups: make sure that no firewall or reverse-proxy is interfering.", + "diagnosis_http_connection_error": "Connection error: could not connect to the requested domain, it's very likely unreachable.", + "diagnosis_http_could_not_diagnose": "Could not diagnose if domains are reachable from outside in IPv{ipversion}.", + "diagnosis_http_could_not_diagnose_details": "Error: {error}", + "diagnosis_http_hairpinning_issue": "Your local network does not seem to have hairpinning enabled.", + "diagnosis_http_hairpinning_issue_details": "This is probably because of your ISP box / router. As a result, people from outside your local network will be able to access your server as expected, but not people from inside the local network (like you, probably?) when using the domain name or global IP. You may be able to improve the situation by having a look at https://yunohost.org/dns_local_network", + "diagnosis_http_localdomain": "Domain {domain}, with a .local TLD, is not expected to be exposed outside the local network.", + "diagnosis_http_nginx_conf_not_up_to_date": "This domain's nginx configuration appears to have been modified manually, and prevents YunoHost from diagnosing if it's reachable on HTTP.", + "diagnosis_http_nginx_conf_not_up_to_date_details": "To fix the situation, inspect the difference with the command line using yunohost tools regen-conf nginx --dry-run --with-diff and if you're ok, apply the changes with yunohost tools regen-conf nginx --force.", + "diagnosis_http_ok": "Domain {domain} is reachable through HTTP from outside the local network.", + "diagnosis_http_partially_unreachable": "Domain {domain} appears unreachable through HTTP from outside the local network in IPv{failed}, though it works in IPv{passed}.", + "diagnosis_http_timeout": "Timed-out while trying to contact your server from outside. It appears to be unreachable.
1. The most common cause for this issue is that port 80 (and 443) are not correctly forwarded to your server.
2. You should also make sure that the service nginx is running
3. On more complex setups: make sure that no firewall or reverse-proxy is interfering.", + "diagnosis_http_unreachable": "Domain {domain} appears unreachable through HTTP from outside the local network.", + "diagnosis_ignored_issues": "(+ {nb_ignored} ignored issue(s))", + "diagnosis_ip_broken_dnsresolution": "Domain name resolution seems to be broken for some reason... Is a firewall blocking DNS requests ?", + "diagnosis_ip_broken_resolvconf": "Domain name resolution seems to be broken on your server, which seems related to /etc/resolv.conf not pointing to 127.0.0.1.", + "diagnosis_ip_connected_ipv4": "The server is connected to the Internet through IPv4!", + "diagnosis_ip_connected_ipv6": "The server is connected to the Internet through IPv6!", + "diagnosis_ip_dnsresolution_working": "Domain name resolution is working!", + "diagnosis_ip_global": "Global IP: {global}", + "diagnosis_ip_local": "Local IP: {local}", + "diagnosis_ip_no_ipv4": "The server does not have working IPv4.", + "diagnosis_ip_no_ipv6": "The server does not have working IPv6.", + "diagnosis_ip_no_ipv6_tip": "Having a working IPv6 is not mandatory for your server to work, but it is better for the health of the Internet as a whole. IPv6 should usually be automatically configured by the system or your provider if it's available. Otherwise, you might need to configure a few things manually as explained in the documentation here: https://yunohost.org/#/ipv6. If you cannot enable IPv6 or if it seems too technical for you, you can also safely ignore this warning.", + "diagnosis_ip_not_connected_at_all": "The server does not seem to be connected to the Internet at all!?", + "diagnosis_ip_weird_resolvconf": "DNS resolution seems to be working, but it looks like you're using a custom /etc/resolv.conf.", + "diagnosis_ip_weird_resolvconf_details": "The file /etc/resolv.conf should be a symlink to /etc/resolvconf/run/resolv.conf itself pointing to 127.0.0.1 (dnsmasq). If you want to manually configure DNS resolvers, please edit /etc/resolv.dnsmasq.conf.", + "diagnosis_mail_blacklist_listed_by": "Your IP or domain {item} is blacklisted on {blacklist_name}", + "diagnosis_mail_blacklist_ok": "The IPs and domains used by this server do not appear to be blacklisted", + "diagnosis_mail_blacklist_reason": "The blacklist reason is: {reason}", + "diagnosis_mail_blacklist_website": "After identifying why you are listed and fixed it, feel free to ask for your IP or domaine to be removed on {blacklist_website}", + "diagnosis_mail_ehlo_bad_answer": "A non-SMTP service answered on port 25 on IPv{ipversion}", + "diagnosis_mail_ehlo_bad_answer_details": "It could be due to an other machine answering instead of your server.", + "diagnosis_mail_ehlo_could_not_diagnose": "Could not diagnose if postfix mail server is reachable from outside in IPv{ipversion}.", + "diagnosis_mail_ehlo_could_not_diagnose_details": "Error: {error}", + "diagnosis_mail_ehlo_ok": "The SMTP mail server is reachable from the outside and therefore is able to receive emails!", + "diagnosis_mail_ehlo_unreachable": "The SMTP mail server is unreachable from the outside on IPv{ipversion}. It won't be able to receive emails.", + "diagnosis_mail_ehlo_unreachable_details": "Could not open a connection on port 25 to your server in IPv{ipversion}. It appears to be unreachable.
1. The most common cause for this issue is that port 25 is not correctly forwarded to your server.
2. You should also make sure that service postfix is running.
3. On more complex setups: make sure that no firewall or reverse-proxy is interfering.", + "diagnosis_mail_ehlo_wrong": "A different SMTP mail server answers on IPv{ipversion}. Your server will probably not be able to receive emails.", + "diagnosis_mail_ehlo_wrong_details": "The EHLO received by the remote diagnoser in IPv{ipversion} is different from your server's domain.
Received EHLO: {wrong_ehlo}
Expected: {right_ehlo}
The most common cause for this issue is that port 25 is not correctly forwarded to your server. Alternatively, make sure that no firewall or reverse-proxy is interfering.", + "diagnosis_mail_fcrdns_different_from_ehlo_domain": "The reverse DNS is not correctly configured in IPv{ipversion}. Some emails may fail to get delivered or may get flagged as spam.", + "diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "Current reverse DNS: {rdns_domain}
Expected value: {ehlo_domain}", + "diagnosis_mail_fcrdns_dns_missing": "No reverse DNS is defined in IPv{ipversion}. Some emails may fail to get delivered or may get flagged as spam.", + "diagnosis_mail_fcrdns_nok_alternatives_4": "Some providers won't let you configure your reverse DNS (or their feature might be broken...). If you are experiencing issues because of this, consider the following solutions:
- Some ISP provide the alternative of using a mail server relay though it implies that the relay will be able to spy on your email traffic.
- A privacy-friendly alternative is to use a VPN *with a dedicated public IP* to bypass this kind of limits. See https://yunohost.org/#/vpn_advantage
- Or it's possible to switch to a different provider", + "diagnosis_mail_fcrdns_nok_alternatives_6": "Some providers won't let you configure your reverse DNS (or their feature might be broken...). If your reverse DNS is correctly configured for IPv4, you can try disabling the use of IPv6 when sending emails by running yunohost settings set smtp.allow_ipv6 -v off. Note: this last solution means that you won't be able to send or receive emails from the few IPv6-only servers out there.", + "diagnosis_mail_fcrdns_nok_details": "You should first try to configure the reverse DNS with {ehlo_domain} in your internet router interface or your hosting provider interface. (Some hosting provider may require you to send them a support ticket for this).", + "diagnosis_mail_fcrdns_ok": "Your reverse DNS is correctly configured!", + "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).", + "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Some providers won't let you unblock outgoing port 25 because they don't care about Net Neutrality.
- Some of them provide the alternative of using a mail server relay though it implies that the relay will be able to spy on your email traffic.
- A privacy-friendly alternative is to use a VPN *with a dedicated public IP* to bypass this kind of limits. See https://yunohost.org/#/vpn_advantage
- You can also consider switching to a more net neutrality-friendly provider", + "diagnosis_mail_outgoing_port_25_ok": "The SMTP mail server is able to send emails (outgoing port 25 is not blocked).", + "diagnosis_mail_queue_ok": "{nb_pending} pending emails in the mail queues", + "diagnosis_mail_queue_too_big": "Too many pending emails in mail queue ({nb_pending} emails)", + "diagnosis_mail_queue_unavailable": "Can not consult number of pending emails in queue", + "diagnosis_mail_queue_unavailable_details": "Error: {error}", + "diagnosis_never_ran_yet": "It looks like this server was setup recently and there's no diagnosis report to show yet. You should start by running a full diagnosis, either from the webadmin or using 'yunohost diagnosis run' from the command line.", + "diagnosis_no_cache": "No diagnosis cache yet for category '{category}'", + "diagnosis_package_installed_from_sury": "Some system packages should be downgraded", + "diagnosis_package_installed_from_sury_details": "Some packages were inadvertendly installed from a third-party repository called Sury. The YunoHost team improved the strategy that handle these packages, but it's expected that some setups that installed PHP7.3 apps while still on Stretch have some remaining inconsistencies. To fix this situation, you should try running the following command: {cmd_to_fix}", + "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_forwarding_tip": "To fix this issue, you most probably need to configure port forwarding on your internet router as described in https://yunohost.org/isp_box_config", + "diagnosis_ports_needed_by": "Exposing this port is needed for {category} features (service {service})", + "diagnosis_ports_ok": "Port {port} is reachable from outside.", + "diagnosis_ports_partially_unreachable": "Port {port} is not reachable from outside in IPv{failed}.", + "diagnosis_ports_unreachable": "Port {port} is not reachable from outside.", + "diagnosis_processes_killed_by_oom_reaper": "Some processes were recently killed by the system because it ran out of memory. This is typically symptomatic of a lack of memory on the system or of a process that ate up to much memory. Summary of the processes killed:\n{kills_summary}", "diagnosis_ram_low": "The system has {available} ({available_percent}%) RAM available (out of {total}). Be careful.", "diagnosis_ram_ok": "The system still has {available} ({available_percent}%) RAM available out of {total}.", + "diagnosis_ram_verylow": "The system has only {available} ({available_percent}%) RAM available! (out of {total})", + "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_rootfstotalspace_critical": "The root filesystem only has a total of {space} which is quite worrisome! You will likely run out of disk space very quickly! It's recommended to have at least 16 GB for the root filesystem.", + "diagnosis_rootfstotalspace_warning": "The root filesystem only has a total of {space}. This may be okay, but be careful because ultimately you may run out of disk space quickly... It's recommended to have at least 16 GB for the root filesystem.", + "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_services_bad_status": "Service {service} is {status} :(", + "diagnosis_services_bad_status_tip": "You can try to restart the service, and if it doesn't work, have a look at the service logs in the webadmin (from the command line, you can do this with yunohost service restart {service} and yunohost service log {service}).", + "diagnosis_services_conf_broken": "Configuration is broken for service {service}!", + "diagnosis_services_running": "Service {service} is running!", + "diagnosis_sshd_config_inconsistent": "It looks like the SSH port was manually modified in /etc/ssh/sshd_config. Since YunoHost 4.2, a new global setting 'security.ssh.port' is available to avoid manually editing the configuration.", + "diagnosis_sshd_config_inconsistent_details": "Please run yunohost settings set security.ssh.port -v YOUR_SSH_PORT to define the SSH port, and check yunohost tools regen-conf ssh --dry-run --with-diff and yunohost tools regen-conf ssh --force to reset your conf to the YunoHost recommendation.", + "diagnosis_sshd_config_insecure": "The SSH configuration appears to have been manually modified, and is insecure because it contains no 'AllowGroups' or 'AllowUsers' directive to limit access to authorized users.", "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).", - "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Some providers won't let you unblock outgoing port 25 because they don't care about Net Neutrality.
- Some of them provide the alternative of using a mail server relay though it implies that the relay will be able to spy on your email traffic.
- A privacy-friendly alternative is to use a VPN *with a dedicated public IP* to bypass this kind of limits. See https://yunohost.org/#/vpn_advantage
- You can also consider switching to a more net neutrality-friendly provider", - "diagnosis_mail_ehlo_ok": "The SMTP mail server is reachable from the outside and therefore is able to receive emails!", - "diagnosis_mail_ehlo_unreachable": "The SMTP mail server is unreachable from the outside on IPv{ipversion}. It won't be able to receive emails.", - "diagnosis_mail_ehlo_unreachable_details": "Could not open a connection on port 25 to your server in IPv{ipversion}. It appears to be unreachable.
1. The most common cause for this issue is that port 25 is not correctly forwarded to your server.
2. You should also make sure that service postfix is running.
3. On more complex setups: make sure that no firewall or reverse-proxy is interfering.", - "diagnosis_mail_ehlo_bad_answer": "A non-SMTP service answered on port 25 on IPv{ipversion}", - "diagnosis_mail_ehlo_bad_answer_details": "It could be due to an other machine answering instead of your server.", - "diagnosis_mail_ehlo_wrong": "A different SMTP mail server answers on IPv{ipversion}. Your server will probably not be able to receive emails.", - "diagnosis_mail_ehlo_wrong_details": "The EHLO received by the remote diagnoser in IPv{ipversion} is different from your server's domain.
Received EHLO: {wrong_ehlo}
Expected: {right_ehlo}
The most common cause for this issue is that port 25 is not correctly forwarded to your server. Alternatively, make sure that no firewall or reverse-proxy is interfering.", - "diagnosis_mail_ehlo_could_not_diagnose": "Could not diagnose if postfix mail server is reachable from outside in IPv{ipversion}.", - "diagnosis_mail_ehlo_could_not_diagnose_details": "Error: {error}", - "diagnosis_mail_fcrdns_ok": "Your reverse DNS is correctly configured!", - "diagnosis_mail_fcrdns_dns_missing": "No reverse DNS is defined in IPv{ipversion}. Some emails may fail to get delivered or may get flagged as spam.", - "diagnosis_mail_fcrdns_nok_details": "You should first try to configure the reverse DNS with {ehlo_domain} in your internet router interface or your hosting provider interface. (Some hosting provider may require you to send them a support ticket for this).", - "diagnosis_mail_fcrdns_nok_alternatives_4": "Some providers won't let you configure your reverse DNS (or their feature might be broken...). If you are experiencing issues because of this, consider the following solutions:
- Some ISP provide the alternative of using a mail server relay though it implies that the relay will be able to spy on your email traffic.
- A privacy-friendly alternative is to use a VPN *with a dedicated public IP* to bypass this kind of limits. See https://yunohost.org/#/vpn_advantage
- Or it's possible to switch to a different provider", - "diagnosis_mail_fcrdns_nok_alternatives_6": "Some providers won't let you configure your reverse DNS (or their feature might be broken...). If your reverse DNS is correctly configured for IPv4, you can try disabling the use of IPv6 when sending emails by running yunohost settings set smtp.allow_ipv6 -v off. Note: this last solution means that you won't be able to send or receive emails from the few IPv6-only servers out there.", - "diagnosis_mail_fcrdns_different_from_ehlo_domain": "The reverse DNS is not correctly configured in IPv{ipversion}. Some emails may fail to get delivered or may get flagged as spam.", - "diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "Current reverse DNS: {rdns_domain}
Expected value: {ehlo_domain}", - "diagnosis_mail_blacklist_ok": "The IPs and domains used by this server do not appear to be blacklisted", - "diagnosis_mail_blacklist_listed_by": "Your IP or domain {item} is blacklisted on {blacklist_name}", - "diagnosis_mail_blacklist_reason": "The blacklist reason is: {reason}", - "diagnosis_mail_blacklist_website": "After identifying why you are listed and fixed it, feel free to ask for your IP or domaine to be removed on {blacklist_website}", - "diagnosis_mail_queue_ok": "{nb_pending} pending emails in the mail queues", - "diagnosis_mail_queue_unavailable": "Can not consult number of pending emails in queue", - "diagnosis_mail_queue_unavailable_details": "Error: {error}", - "diagnosis_mail_queue_too_big": "Too many pending emails in mail queue ({nb_pending} emails)", - "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_rootfstotalspace_warning": "The root filesystem only has a total of {space}. This may be okay, but be careful because ultimately you may run out of disk space quickly... It's recommended to have at least 16 GB for the root filesystem.", - "diagnosis_rootfstotalspace_critical": "The root filesystem only has a total of {space} which is quite worrisome! You will likely run out of disk space very quickly! It's recommended to have at least 16 GB for the root filesystem.", - "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", - "diagnosis_description_ip": "Internet connectivity", - "diagnosis_description_dnsrecords": "DNS records", - "diagnosis_description_services": "Services status check", - "diagnosis_description_systemresources": "System resources", - "diagnosis_description_ports": "Ports exposure", - "diagnosis_description_web": "Web", - "diagnosis_description_mail": "Email", - "diagnosis_description_regenconf": "System configurations", - "diagnosis_description_apps": "Applications", - "diagnosis_apps_allgood": "All installed apps respect basic packaging practices", - "diagnosis_apps_issue": "An issue was found for app {app}", - "diagnosis_apps_not_in_app_catalog": "This application is not in YunoHost's application catalog. If it was in the past and got removed, you should consider uninstalling this app as it won't receive upgrade, and may compromise the integrity and security of your system.", - "diagnosis_apps_broken": "This application is currently flagged as broken on YunoHost's application catalog. This may be a temporary issue while the maintainers attempt to fix the issue. In the meantime, upgrading this app is disabled.", - "diagnosis_apps_bad_quality": "This application is currently flagged as broken on YunoHost's application catalog. This may be a temporary issue while the maintainers attempt to fix the issue. In the meantime, upgrading this app is disabled.", - "diagnosis_apps_outdated_ynh_requirement": "This app's installed version only requires yunohost >= 2.x, which tends to indicate that it's not up to date with recommended packaging practices and helpers. You should really consider upgrading it.", - "diagnosis_apps_deprecated_practices": "This app's installed version still uses some super-old deprecated packaging practices. You should really consider upgrading it.", - "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.", - "diagnosis_ports_partially_unreachable": "Port {port} is not reachable from outside in IPv{failed}.", - "diagnosis_ports_ok": "Port {port} is reachable from outside.", - "diagnosis_ports_needed_by": "Exposing this port is needed for {category} features (service {service})", - "diagnosis_ports_forwarding_tip": "To fix this issue, you most probably need to configure port forwarding on your internet router as described in https://yunohost.org/isp_box_config", - "diagnosis_http_hairpinning_issue": "Your local network does not seem to have hairpinning enabled.", - "diagnosis_http_hairpinning_issue_details": "This is probably because of your ISP box / router. As a result, people from outside your local network will be able to access your server as expected, but not people from inside the local network (like you, probably?) when using the domain name or global IP. You may be able to improve the situation by having a look at https://yunohost.org/dns_local_network", - "diagnosis_http_could_not_diagnose": "Could not diagnose if domains are reachable from outside in IPv{ipversion}.", - "diagnosis_http_could_not_diagnose_details": "Error: {error}", - "diagnosis_http_localdomain": "Domain {domain}, with a .local TLD, is not expected to be exposed outside the local network.", - "diagnosis_http_ok": "Domain {domain} is reachable through HTTP from outside the local network.", - "diagnosis_http_timeout": "Timed-out while trying to contact your server from outside. It appears to be unreachable.
1. The most common cause for this issue is that port 80 (and 443) are not correctly forwarded to your server.
2. You should also make sure that the service nginx is running
3. On more complex setups: make sure that no firewall or reverse-proxy is interfering.", - "diagnosis_http_connection_error": "Connection error: could not connect to the requested domain, it's very likely unreachable.", - "diagnosis_http_bad_status_code": "It looks like another machine (maybe your internet router) answered instead of your server.
1. The most common cause for this issue is that port 80 (and 443) are not correctly forwarded to your server.
2. On more complex setups: make sure that no firewall or reverse-proxy is interfering.", - "diagnosis_http_unreachable": "Domain {domain} appears unreachable through HTTP from outside the local network.", - "diagnosis_http_partially_unreachable": "Domain {domain} appears unreachable through HTTP from outside the local network in IPv{failed}, though it works in IPv{passed}.", - "diagnosis_http_nginx_conf_not_up_to_date": "This domain's nginx configuration appears to have been modified manually, and prevents YunoHost from diagnosing if it's reachable on HTTP.", - "diagnosis_http_nginx_conf_not_up_to_date_details": "To fix the situation, inspect the difference with the command line using yunohost tools regen-conf nginx --dry-run --with-diff and if you're ok, apply the changes with yunohost tools regen-conf nginx --force.", "diagnosis_unknown_categories": "The following categories are unknown: {categories}", - "diagnosis_never_ran_yet": "It looks like this server was setup recently and there's no diagnosis report to show yet. You should start by running a full diagnosis, either from the webadmin or using 'yunohost diagnosis run' from the command line.", - "diagnosis_processes_killed_by_oom_reaper": "Some processes were recently killed by the system because it ran out of memory. This is typically symptomatic of a lack of memory on the system or of a process that ate up to much memory. Summary of the processes killed:\n{kills_summary}", - "diagnosis_sshd_config_insecure": "The SSH configuration appears to have been manually modified, and is insecure because it contains no 'AllowGroups' or 'AllowUsers' directive to limit access to authorized users.", - "diagnosis_sshd_config_inconsistent": "It looks like the SSH port was manually modified in /etc/ssh/sshd_config. Since YunoHost 4.2, a new global setting 'security.ssh.port' is available to avoid manually editing the configuration.", - "diagnosis_sshd_config_inconsistent_details": "Please run yunohost settings set security.ssh.port -v YOUR_SSH_PORT to define the SSH port, and check yunohost tools regen-conf ssh --dry-run --with-diff and yunohost tools regen-conf ssh --force to reset your conf to the YunoHost recommendation.", "disk_space_not_sufficient_install": "There is not enough disk space left to install this application", "disk_space_not_sufficient_update": "There is not enough disk space left to update this application", - "domain_cannot_remove_main": "You cannot remove '{domain}' since it's the main domain, you first need to set another domain as the main domain using 'yunohost domain main-domain -n '; here is the list of candidate domains: {other_domains}", "domain_cannot_add_xmpp_upload": "You cannot add domains starting with 'xmpp-upload.'. This kind of name is reserved for the XMPP upload feature integrated in YunoHost.", + "domain_cannot_remove_main": "You cannot remove '{domain}' since it's the main domain, you first need to set another domain as the main domain using 'yunohost domain main-domain -n '; here is the list of candidate domains: {other_domains}", "domain_cannot_remove_main_add_new_one": "You cannot remove '{domain}' since it's the main domain and your only domain, you need to first add another domain using 'yunohost domain add ', then set is as the main domain using 'yunohost domain main-domain -n ' and then you can remove the domain '{domain}' using 'yunohost domain remove {domain}'.'", "domain_cert_gen_failed": "Could not generate certificate", "domain_created": "Domain created", @@ -298,17 +298,18 @@ "domain_dyndns_root_unknown": "Unknown DynDNS root domain", "domain_exists": "The domain already exists", "domain_hostname_failed": "Unable to set new hostname. This might cause an issue later (it might be fine).", + "domain_name_unknown": "Domain '{domain}' unknown", "domain_remove_confirm_apps_removal": "Removing this domain will remove those applications:\n{apps}\n\nAre you sure you want to do that? [{answers}]", "domain_uninstall_app_first": "Those applications are still installed on your domain:\n{apps}\n\nPlease uninstall them using 'yunohost app remove the_app_id' or move them to another domain using 'yunohost app change-url the_app_id' before proceeding to domain removal", - "domain_name_unknown": "Domain '{domain}' unknown", "domain_unknown": "Unknown domain", "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 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} can provide {domain}.", "dyndns_could_not_check_available": "Could not check if {domain} is available on {provider}.", + "dyndns_could_not_check_provide": "Could not check if {provider} can provide {domain}.", + "dyndns_domain_not_provided": "DynDNS provider {provider} cannot provide domain {domain}.", "dyndns_ip_update_failed": "Could not update IP address to DynDNS", "dyndns_ip_updated": "Updated your IP on DynDNS", "dyndns_key_generating": "Generating DNS key... It may take a while.", @@ -317,10 +318,9 @@ "dyndns_provider_unreachable": "Unable to reach DynDNS provider {provider}: either your YunoHost is not correctly connected to the internet or the dynette server is down.", "dyndns_registered": "DynDNS domain registered", "dyndns_registration_failed": "Could not register DynDNS domain: {error}", - "dyndns_domain_not_provided": "DynDNS provider {provider} cannot provide domain {domain}.", "dyndns_unavailable": "The domain '{domain}' is unavailable.", - "extracting": "Extracting...", "experimental_feature": "Warning: This feature is experimental and not considered stable, you should not use it unless you know what you are doing.", + "extracting": "Extracting...", "field_invalid": "Invalid field '{}'", "file_does_not_exist": "The file {path} does not exist.", "firewall_reload_failed": "Could not reload the firewall", @@ -333,42 +333,42 @@ "global_settings_cant_write_settings": "Could not save settings file, reason: {reason}", "global_settings_key_doesnt_exists": "The key '{settings_key}' does not exist in the global settings, you can see all the available keys by running 'yunohost settings list'", "global_settings_reset_success": "Previous settings now backed up to {path}", + "global_settings_setting_backup_compress_tar_archives": "When creating new backups, compress the archives (.tar.gz) instead of uncompressed archives (.tar). N.B. : enabling this option means create lighter backup archives, but the initial backup procedure will be significantly longer and heavy on CPU.", "global_settings_setting_pop3_enabled": "Enable the POP3 protocol for the mail server", + "global_settings_setting_security_experimental_enabled": "Enable experimental security features (don't enable this if you don't know what you're doing!)", "global_settings_setting_security_nginx_compatibility": "Compatibility vs. security tradeoff for the web server NGINX. Affects the ciphers (and other security-related aspects)", "global_settings_setting_security_password_admin_strength": "Admin password strength", "global_settings_setting_security_password_user_strength": "User password strength", - "global_settings_setting_security_ssh_compatibility": "Compatibility vs. security tradeoff for the SSH server. Affects the ciphers (and other security-related aspects)", "global_settings_setting_security_postfix_compatibility": "Compatibility vs. security tradeoff for the Postfix server. Affects the ciphers (and other security-related aspects)", + "global_settings_setting_security_ssh_compatibility": "Compatibility vs. security tradeoff for the SSH server. Affects the ciphers (and other security-related aspects)", "global_settings_setting_security_ssh_port": "SSH port", - "global_settings_unknown_setting_from_settings_file": "Unknown key in settings: '{setting_key}', discard it and save it in /etc/yunohost/settings-unknown.json", + "global_settings_setting_security_webadmin_allowlist": "IP adresses allowed to access the webadmin. Comma-separated.", + "global_settings_setting_security_webadmin_allowlist_enabled": "Allow only some IPs to access the webadmin.", "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Allow the use of (deprecated) DSA hostkey for the SSH daemon configuration", - "global_settings_setting_ssowat_panel_overlay_enabled": "Enable SSOwat panel overlay", "global_settings_setting_smtp_allow_ipv6": "Allow the use of IPv6 to receive and send mail", "global_settings_setting_smtp_relay_host": "SMTP relay host to use in order to send mail instead of this yunohost instance. Useful if you are in one of this situation: your 25 port is blocked by your ISP or VPS provider, you have a residential IP listed on DUHL, you are not able to configure reverse DNS or this server is not directly exposed on the internet and you want use an other one to send mails.", + "global_settings_setting_smtp_relay_password": "SMTP relay host password", "global_settings_setting_smtp_relay_port": "SMTP relay port", "global_settings_setting_smtp_relay_user": "SMTP relay user account", - "global_settings_setting_smtp_relay_password": "SMTP relay host password", - "global_settings_setting_security_webadmin_allowlist_enabled": "Allow only some IPs to access the webadmin.", - "global_settings_setting_security_webadmin_allowlist": "IP adresses allowed to access the webadmin. Comma-separated.", - "global_settings_setting_security_experimental_enabled": "Enable experimental security features (don't enable this if you don't know what you're doing!)", - "global_settings_setting_backup_compress_tar_archives": "When creating new backups, compress the archives (.tar.gz) instead of uncompressed archives (.tar). N.B. : enabling this option means create lighter backup archives, but the initial backup procedure will be significantly longer and heavy on CPU.", + "global_settings_setting_ssowat_panel_overlay_enabled": "Enable SSOwat panel overlay", + "global_settings_unknown_setting_from_settings_file": "Unknown key in settings: '{setting_key}', discard it and save it in /etc/yunohost/settings-unknown.json", "global_settings_unknown_type": "Unexpected situation, the setting {setting} appears to have the type {unknown_type} but it is not a type supported by the system.", "good_practices_about_admin_password": "You are now about to define a new administration password. The password should be at least 8 characters long—though it is good practice to use a longer password (i.e. a passphrase) and/or to use a variation of characters (uppercase, lowercase, digits and special characters).", "good_practices_about_user_password": "You are now about to define a new user password. The password should be at least 8 characters long—though it is good practice to use a longer password (i.e. a passphrase) and/or to a variation of characters (uppercase, lowercase, digits and special characters).", "group_already_exist": "Group {group} already exists", "group_already_exist_on_system": "Group {group} already exists in the system groups", "group_already_exist_on_system_but_removing_it": "Group {group} already exists in the system groups, but YunoHost will remove it...", + "group_cannot_be_deleted": "The group {group} cannot be deleted manually.", + "group_cannot_edit_all_users": "The group 'all_users' cannot be edited manually. It is a special group meant to contain all users registered in YunoHost", + "group_cannot_edit_primary_group": "The group '{group}' cannot be edited manually. It is the primary group meant to contain only one specific user.", + "group_cannot_edit_visitors": "The group 'visitors' cannot be edited manually. It is a special group representing anonymous visitors", "group_created": "Group '{group}' created", "group_creation_failed": "Could not create the group '{group}': {error}", - "group_cannot_edit_all_users": "The group 'all_users' cannot be edited manually. It is a special group meant to contain all users registered in YunoHost", - "group_cannot_edit_visitors": "The group 'visitors' cannot be edited manually. It is a special group representing anonymous visitors", - "group_cannot_edit_primary_group": "The group '{group}' cannot be edited manually. It is the primary group meant to contain only one specific user.", - "group_cannot_be_deleted": "The group {group} cannot be deleted manually.", "group_deleted": "Group '{group}' deleted", "group_deletion_failed": "Could not delete the group '{group}': {error}", "group_unknown": "The group '{group}' is unknown", - "group_updated": "Group '{group}' updated", "group_update_failed": "Could not update the group '{group}': {error}", + "group_updated": "Group '{group}' updated", "group_user_already_in_group": "User {user} is already in group {group}", "group_user_not_in_group": "User {user} is not in group {group}", "hook_exec_failed": "Could not run script: {path}", @@ -377,67 +377,90 @@ "hook_list_by_invalid": "This property can not be used to list hooks", "hook_name_unknown": "Unknown hook name '{name}'", "installation_complete": "Installation completed", + "invalid_number": "Must be a number", + "invalid_password": "Invalid password", "invalid_regex": "Invalid regex:'{regex}'", "ip6tables_unavailable": "You cannot play with ip6tables here. You are either in a container or your kernel does not support it", "iptables_unavailable": "You cannot play with iptables here. You are either in a container or your kernel does not support it", "ldap_server_down": "Unable to reach LDAP server", "ldap_server_is_down_restart_it": "The LDAP service is down, attempt to restart it...", - "log_corrupted_md_file": "The YAML metadata file associated with logs is damaged: '{md_file}\nError: {error}'", - "log_link_to_log": "Full log of this operation: '{desc}'", - "log_help_to_get_log": "To view the log of the operation '{desc}', use the command 'yunohost log show {name}{name}'", - "log_link_to_failed_log": "Could not complete the operation '{desc}'. Please provide the full log of this operation by clicking here to get help", - "log_help_to_get_failed_log": "The operation '{desc}' could not be completed. Please share the full log of this operation using the command 'yunohost log share {name}' to get help", - "log_does_exists": "There is no operation log with the name '{log}', use 'yunohost log list' to see all available operation logs", - "log_operation_unit_unclosed_properly": "Operation unit has not been closed properly", + "log_app_action_run": "Run action of the '{}' app", "log_app_change_url": "Change the URL of the '{}' app", + "log_app_config_apply": "Apply config to the '{}' app", + "log_app_config_show_panel": "Show the config panel of the '{}' app", "log_app_install": "Install the '{}' app", + "log_app_makedefault": "Make '{}' the default app", "log_app_remove": "Remove the '{}' app", "log_app_upgrade": "Upgrade the '{}' app", - "log_app_makedefault": "Make '{}' the default app", - "log_app_action_run": "Run action of the '{}' app", - "log_app_config_show_panel": "Show the config panel of the '{}' app", - "log_app_config_apply": "Apply config to the '{}' app", "log_available_on_yunopaste": "This log is now available via {url}", "log_backup_create": "Create a backup archive", - "log_backup_restore_system": "Restore system from a backup archive", "log_backup_restore_app": "Restore '{}' from a backup archive", - "log_remove_on_failed_restore": "Remove '{}' after a failed restore from a backup archive", - "log_remove_on_failed_install": "Remove '{}' after a failed installation", + "log_backup_restore_system": "Restore system from a backup archive", + "log_corrupted_md_file": "The YAML metadata file associated with logs is damaged: '{md_file}\nError: {error}'", + "log_does_exists": "There is no operation log with the name '{log}', use 'yunohost log list' to see all available operation logs", "log_domain_add": "Add '{}' domain into system configuration", + "log_domain_main_domain": "Make '{}' the main domain", "log_domain_remove": "Remove '{}' domain from system configuration", "log_dyndns_subscribe": "Subscribe to a YunoHost subdomain '{}'", "log_dyndns_update": "Update the IP associated with your YunoHost subdomain '{}'", + "log_help_to_get_failed_log": "The operation '{desc}' could not be completed. Please share the full log of this operation using the command 'yunohost log share {name}' to get help", + "log_help_to_get_log": "To view the log of the operation '{desc}', use the command 'yunohost log show {name}{name}'", "log_letsencrypt_cert_install": "Install a Let's Encrypt certificate on '{}' domain", + "log_letsencrypt_cert_renew": "Renew '{}' Let's Encrypt certificate", + "log_link_to_failed_log": "Could not complete the operation '{desc}'. Please provide the full log of this operation by clicking here to get help", + "log_link_to_log": "Full log of this operation: '{desc}'", + "log_operation_unit_unclosed_properly": "Operation unit has not been closed properly", "log_permission_create": "Create permission '{}'", "log_permission_delete": "Delete permission '{}'", "log_permission_url": "Update URL related to permission '{}'", - "log_selfsigned_cert_install": "Install self-signed certificate on '{}' domain", - "log_letsencrypt_cert_renew": "Renew '{}' Let's Encrypt certificate", "log_regen_conf": "Regenerate system configurations '{}'", + "log_remove_on_failed_install": "Remove '{}' after a failed installation", + "log_remove_on_failed_restore": "Remove '{}' after a failed restore from a backup archive", + "log_selfsigned_cert_install": "Install self-signed certificate on '{}' domain", + "log_tools_migrations_migrate_forward": "Run migrations", + "log_tools_postinstall": "Postinstall your YunoHost server", + "log_tools_reboot": "Reboot your server", + "log_tools_shutdown": "Shutdown your server", + "log_tools_upgrade": "Upgrade system packages", "log_user_create": "Add '{}' user", "log_user_delete": "Delete '{}' user", - "log_user_import": "Import users", "log_user_group_create": "Create '{}' group", "log_user_group_delete": "Delete '{}' group", "log_user_group_update": "Update '{}' group", - "log_user_update": "Update info for user '{}'", - "log_user_permission_update": "Update accesses for permission '{}'", + "log_user_import": "Import users", "log_user_permission_reset": "Reset permission '{}'", - "log_domain_main_domain": "Make '{}' the main domain", - "log_tools_migrations_migrate_forward": "Run migrations", - "log_tools_postinstall": "Postinstall your YunoHost server", - "log_tools_upgrade": "Upgrade system packages", - "log_tools_shutdown": "Shutdown your server", - "log_tools_reboot": "Reboot your server", + "log_user_permission_update": "Update accesses for permission '{}'", + "log_user_update": "Update info for user '{}'", "mail_alias_remove_failed": "Could not remove e-mail alias '{mail}'", "mail_domain_unknown": "Invalid e-mail address for domain '{domain}'. Please, use a domain administrated by this server.", "mail_forward_remove_failed": "Could not remove e-mail forwarding '{mail}'", + "mail_unavailable": "This e-mail address is reserved and shall be automatically allocated to the very first user", "mailbox_disabled": "E-mail turned off for user {user}", "mailbox_used_space_dovecot_down": "The Dovecot mailbox service needs to be up if you want to fetch used mailbox space", - "mail_unavailable": "This e-mail address is reserved and shall be automatically allocated to the very first user", "main_domain_change_failed": "Unable to change the main domain", "main_domain_changed": "The main domain has been changed", "migrating_legacy_permission_settings": "Migrating legacy permission settings...", + "migration_0015_cleaning_up": "Cleaning up cache and packages not useful anymore...", + "migration_0015_general_warning": "Please note that this migration is a delicate operation. The YunoHost team did its best to review and test it, but the migration might still break parts of the system or its apps.\n\nTherefore, it is recommended to:\n - Perform a backup of any critical data or app. More info on https://yunohost.org/backup;\n - Be patient after launching the migration: Depending on your Internet connection and hardware, it might take up to a few hours for everything to upgrade.", + "migration_0015_main_upgrade": "Starting main upgrade...", + "migration_0015_modified_files": "Please note that the following files were found to be manually modified and might be overwritten following the upgrade: {manually_modified_files}", + "migration_0015_not_enough_free_space": "Free space is pretty low in /var/! You should have at least 1GB free to run this migration.", + "migration_0015_not_stretch": "The current Debian distribution is not Stretch!", + "migration_0015_patching_sources_list": "Patching the sources.lists...", + "migration_0015_problematic_apps_warning": "Please note that the following possibly problematic installed apps were detected. It looks like those were not installed from the YunoHost app catalog, or are not flagged as 'working'. Consequently, it cannot be guaranteed that they will still work after the upgrade: {problematic_apps}", + "migration_0015_specific_upgrade": "Starting upgrade of system packages that needs to be upgrade independently...", + "migration_0015_start": "Starting migration to Buster", + "migration_0015_still_on_stretch_after_main_upgrade": "Something went wrong during the main upgrade, the system appears to still be on Debian Stretch", + "migration_0015_system_not_fully_up_to_date": "Your system is not fully up-to-date. Please perform a regular upgrade before running the migration to Buster.", + "migration_0015_weak_certs": "The following certificates were found to still use weak signature algorithms and have to be upgraded to be compatible with the next version of nginx: {certs}", + "migration_0015_yunohost_upgrade": "Starting YunoHost core upgrade...", + "migration_0017_not_enough_space": "Make sufficient space available in {path} to run the migration.", + "migration_0017_postgresql_11_not_installed": "PostgreSQL 9.6 is installed, but not postgresql 11‽ Something weird might have happened on your system :(...", + "migration_0017_postgresql_96_not_installed": "PostgreSQL was not installed on your system. Nothing to do.", + "migration_0018_failed_to_migrate_iptables_rules": "Failed to migrate legacy iptables rules to nftables: {error}", + "migration_0018_failed_to_reset_legacy_rules": "Failed to reset legacy iptables rules: {error}", + "migration_0019_add_new_attributes_in_ldap": "Add new attributes for permissions in LDAP database", + "migration_0019_slapd_config_will_be_overwritten": "It looks like you manually edited the slapd configuration. For this critical migration, YunoHost needs to force the update of the slapd configuration. The original files will be backuped in {conf_backup_folder}.", "migration_description_0015_migrate_to_buster": "Upgrade the system to Debian Buster and YunoHost 4.x", "migration_description_0016_php70_to_php73_pools": "Migrate php7.0-fpm 'pool' conf files to php7.3", "migration_description_0017_postgresql_9p6_to_11": "Migrate databases from PostgreSQL 9.6 to 11", @@ -449,32 +472,11 @@ "migration_ldap_migration_failed_trying_to_rollback": "Could not migrate... trying to roll back the system.", "migration_ldap_rollback_success": "System rolled back.", "migration_update_LDAP_schema": "Updating LDAP schema...", - "migration_0015_start" : "Starting migration to Buster", - "migration_0015_patching_sources_list": "Patching the sources.lists...", - "migration_0015_main_upgrade": "Starting main upgrade...", - "migration_0015_still_on_stretch_after_main_upgrade": "Something went wrong during the main upgrade, the system appears to still be on Debian Stretch", - "migration_0015_yunohost_upgrade" : "Starting YunoHost core upgrade...", - "migration_0015_not_stretch" : "The current Debian distribution is not Stretch!", - "migration_0015_not_enough_free_space" : "Free space is pretty low in /var/! You should have at least 1GB free to run this migration.", - "migration_0015_system_not_fully_up_to_date": "Your system is not fully up-to-date. Please perform a regular upgrade before running the migration to Buster.", - "migration_0015_general_warning": "Please note that this migration is a delicate operation. The YunoHost team did its best to review and test it, but the migration might still break parts of the system or its apps.\n\nTherefore, it is recommended to:\n - Perform a backup of any critical data or app. More info on https://yunohost.org/backup;\n - Be patient after launching the migration: Depending on your Internet connection and hardware, it might take up to a few hours for everything to upgrade.", - "migration_0015_problematic_apps_warning": "Please note that the following possibly problematic installed apps were detected. It looks like those were not installed from the YunoHost app catalog, or are not flagged as 'working'. Consequently, it cannot be guaranteed that they will still work after the upgrade: {problematic_apps}", - "migration_0015_modified_files": "Please note that the following files were found to be manually modified and might be overwritten following the upgrade: {manually_modified_files}", - "migration_0015_specific_upgrade": "Starting upgrade of system packages that needs to be upgrade independently...", - "migration_0015_cleaning_up": "Cleaning up cache and packages not useful anymore...", - "migration_0015_weak_certs": "The following certificates were found to still use weak signature algorithms and have to be upgraded to be compatible with the next version of nginx: {certs}", - "migration_0017_postgresql_96_not_installed": "PostgreSQL was not installed on your system. Nothing to do.", - "migration_0017_postgresql_11_not_installed": "PostgreSQL 9.6 is installed, but not postgresql 11‽ Something weird might have happened on your system :(...", - "migration_0017_not_enough_space": "Make sufficient space available in {path} to run the migration.", - "migration_0018_failed_to_migrate_iptables_rules": "Failed to migrate legacy iptables rules to nftables: {error}", - "migration_0018_failed_to_reset_legacy_rules": "Failed to reset legacy iptables rules: {error}", - "migration_0019_add_new_attributes_in_ldap": "Add new attributes for permissions in LDAP database", - "migration_0019_slapd_config_will_be_overwritten": "It looks like you manually edited the slapd configuration. For this critical migration, YunoHost needs to force the update of the slapd configuration. The original files will be backuped in {conf_backup_folder}.", "migrations_already_ran": "Those migrations are already done: {ids}", "migrations_cant_reach_migration_file": "Could not access migrations files at the path '%s'", "migrations_dependencies_not_satisfied": "Run these migrations: '{dependencies_id}', before migration {id}.", - "migrations_failed_to_load_migration": "Could not load migration {id}: {error}", "migrations_exclusive_options": "'--auto', '--skip', and '--force-rerun' are mutually exclusive options.", + "migrations_failed_to_load_migration": "Could not load migration {id}: {error}", "migrations_list_conflict_pending_done": "You cannot use both '--previous' and '--done' at the same time.", "migrations_loading_migration": "Loading migration {id}...", "migrations_migration_has_failed": "Migration {id} did not complete, aborting. Error: {exception}", @@ -487,10 +489,8 @@ "migrations_running_forward": "Running migration {id}...", "migrations_skip_migration": "Skipping migration {id}...", "migrations_success_forward": "Migration {id} completed", - "migrations_to_be_ran_manually": "Migration {id} has to be run manually. Please go to Tools → Migrations on the webadmin page, or run `yunohost tools migrations run`.", + "migrations_to_be_ran_manually": "Migration {id} has to be run manually. Please go to Tools → Migrations on the webadmin page, or run `yunohost tools migrations run`.", "not_enough_disk_space": "Not enough free space on '{path}'", - "invalid_number": "Must be a number", - "invalid_password": "Invalid password", "operation_interrupted": "The operation was manually interrupted?", "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.", @@ -500,35 +500,37 @@ "password_too_simple_4": "The password needs to be at least 12 characters long and contain a digit, upper, lower and special characters", "pattern_backup_archive_name": "Must be a valid filename with max 30 characters, alphanumeric and -_. characters only", "pattern_domain": "Must be a valid domain name (e.g. my-domain.org)", - "pattern_email_forward": "Must be a valid e-mail address, '+' symbol accepted (e.g. someone+tag@example.com)", "pattern_email": "Must be a valid e-mail address, without '+' symbol (e.g. someone@example.com)", + "pattern_email_forward": "Must be a valid e-mail address, '+' symbol accepted (e.g. someone+tag@example.com)", "pattern_firstname": "Must be a valid first name", "pattern_lastname": "Must be a valid last name", "pattern_mailbox_quota": "Must be a size with b/k/M/G/T suffix or 0 to not have a quota", "pattern_password": "Must be at least 3 characters long", + "pattern_password_app": "Sorry, passwords can not contain the following characters: {forbidden_chars}", "pattern_port_or_range": "Must be a valid port number (i.e. 0-65535) or range of ports (e.g. 100:200)", "pattern_positive_number": "Must be a positive number", "pattern_username": "Must be lower-case alphanumeric and underscore characters only", - "pattern_password_app": "Sorry, passwords can not contain the following characters: {forbidden_chars}", "permission_already_allowed": "Group '{group}' already has permission '{permission}' enabled", "permission_already_disallowed": "Group '{group}' already has permission '{permission}' disabled", "permission_already_exist": "Permission '{permission}' already exists", "permission_already_up_to_date": "The permission was not updated because the addition/removal requests already match the current state.", "permission_cannot_remove_main": "Removing a main permission is not allowed", + "permission_cant_add_to_all_users": "The permission {permission} can not be added to all users.", "permission_created": "Permission '{permission}' created", "permission_creation_failed": "Could not create permission '{permission}': {error}", "permission_currently_allowed_for_all_users": "This permission is currently granted to all users in addition to other groups. You probably want to either remove the 'all_users' permission or remove the other groups it is currently granted to.", - "permission_cant_add_to_all_users": "The permission {permission} can not be added to all users.", "permission_deleted": "Permission '{permission}' deleted", "permission_deletion_failed": "Could not delete permission '{permission}': {error}", "permission_not_found": "Permission '{permission}' not found", - "permission_update_failed": "Could not update permission '{permission}': {error}", - "permission_updated": "Permission '{permission}' updated", "permission_protected": "Permission {permission} is protected. You cannot add or remove the visitors group to/from this permission.", "permission_require_account": "Permission {permission} only makes sense for users having an account, and therefore cannot be enabled for visitors.", + "permission_update_failed": "Could not update permission '{permission}': {error}", + "permission_updated": "Permission '{permission}' updated", "port_already_closed": "Port {port} is already closed for {ip_version} connections", "port_already_opened": "Port {port} is already opened for {ip_version} connections", "postinstall_low_rootfsspace": "The root filesystem has a total space less than 10 GB, which is quite worrisome! You will likely run out of disk space very quickly! It's recommended to have at least 16GB for the root filesystem. If you want to install YunoHost despite this warning, re-run the postinstall with --force-diskspace", + "regenconf_dry_pending_applying": "Checking pending configuration which would have been applied for category '{category}'...", + "regenconf_failed": "Could not regenerate the configuration for category(s): {categories}", "regenconf_file_backed_up": "Configuration file '{conf}' backed up to '{backup}'", "regenconf_file_copy_failed": "Could not copy the new configuration file '{new}' to '{conf}'", "regenconf_file_kept_back": "The configuration file '{conf}' is expected to be deleted by regen-conf (category {category}) but was kept back.", @@ -537,14 +539,12 @@ "regenconf_file_remove_failed": "Could not remove the configuration file '{conf}'", "regenconf_file_removed": "Configuration file '{conf}' removed", "regenconf_file_updated": "Configuration file '{conf}' updated", + "regenconf_need_to_explicitly_specify_ssh": "The ssh configuration has been manually modified, but you need to explicitly specify category 'ssh' with --force to actually apply the changes.", "regenconf_now_managed_by_yunohost": "The configuration file '{conf}' is now managed by YunoHost (category {category}).", + "regenconf_pending_applying": "Applying pending configuration for category '{category}'...", "regenconf_up_to_date": "The configuration is already up-to-date for category '{category}'", "regenconf_updated": "Configuration updated for '{category}'", "regenconf_would_be_updated": "The configuration would have been updated for category '{category}'", - "regenconf_dry_pending_applying": "Checking pending configuration which would have been applied for category '{category}'...", - "regenconf_failed": "Could not regenerate the configuration for category(s): {categories}", - "regenconf_pending_applying": "Applying pending configuration for category '{category}'...", - "regenconf_need_to_explicitly_specify_ssh": "The ssh configuration has been manually modified, but you need to explicitly specify category 'ssh' with --force to actually apply the changes.", "regex_incompatible_with_tile": "/!\\ Packagers! Permission '{permission}' has show_tile set to 'true' and you therefore cannot define a regex URL as the main URL", "regex_with_only_domain": "You can't use a regex for domain, only for path", "restore_already_installed_app": "An app with the ID '{app}' is already installed", @@ -565,16 +565,15 @@ "restore_system_part_failed": "Could not restore the '{part}' system part", "root_password_desynchronized": "The admin password was changed, but YunoHost could not propagate this to the root password!", "root_password_replaced_by_admin_password": "Your root password have been replaced by your admin password.", - "server_shutdown": "The server will shut down", - "server_shutdown_confirm": "The server will shutdown immediatly, are you sure? [{answers}]", "server_reboot": "The server will reboot", "server_reboot_confirm": "The server will reboot immediatly, are you sure? [{answers}]", + "server_shutdown": "The server will shut down", + "server_shutdown_confirm": "The server will shutdown immediatly, are you sure? [{answers}]", "service_add_failed": "Could not add the service '{service}'", "service_added": "The service '{service}' was added", "service_already_started": "The service '{service}' is running already", "service_already_stopped": "The service '{service}' has already been stopped", "service_cmd_exec_failed": "Could not execute the command '{command}'", - "service_description_yunomdns": "Allows you to reach your server using 'yunohost.local' in your local network", "service_description_dnsmasq": "Handles domain name resolution (DNS)", "service_description_dovecot": "Allows e-mail clients to access/fetch email (via IMAP and POP3)", "service_description_fail2ban": "Protects against brute-force and other kinds of attacks from the Internet", @@ -589,26 +588,27 @@ "service_description_ssh": "Allows you to connect remotely to your server via a terminal (SSH protocol)", "service_description_yunohost-api": "Manages interactions between the YunoHost web interface and the system", "service_description_yunohost-firewall": "Manages open and close connection ports to services", + "service_description_yunomdns": "Allows you to reach your server using 'yunohost.local' in your local network", "service_disable_failed": "Could not make the service '{service}' not start at boot.\n\nRecent service logs:{logs}", "service_disabled": "The service '{service}' will not be started anymore when system boots.", "service_enable_failed": "Could not make the service '{service}' automatically start at boot.\n\nRecent service logs:{logs}", "service_enabled": "The service '{service}' will now be automatically started during system boots.", "service_regen_conf_is_deprecated": "'yunohost service regen-conf' is deprecated! Please use 'yunohost tools regen-conf' instead.", + "service_reload_failed": "Could not reload the service '{service}'\n\nRecent service logs:{logs}", + "service_reload_or_restart_failed": "Could not reload or restart the service '{service}'\n\nRecent service logs:{logs}", + "service_reloaded": "Service '{service}' reloaded", + "service_reloaded_or_restarted": "The service '{service}' was reloaded or restarted", "service_remove_failed": "Could not remove the service '{service}'", "service_removed": "Service '{service}' removed", - "service_reload_failed": "Could not reload the service '{service}'\n\nRecent service logs:{logs}", - "service_reloaded": "Service '{service}' reloaded", "service_restart_failed": "Could not restart the service '{service}'\n\nRecent service logs:{logs}", "service_restarted": "Service '{service}' restarted", - "service_reload_or_restart_failed": "Could not reload or restart the service '{service}'\n\nRecent service logs:{logs}", - "service_reloaded_or_restarted": "The service '{service}' was reloaded or restarted", "service_start_failed": "Could not start the service '{service}'\n\nRecent service logs:{logs}", "service_started": "Service '{service}' started", "service_stop_failed": "Unable to stop the service '{service}'\n\nRecent service logs:{logs}", "service_stopped": "Service '{service}' stopped", "service_unknown": "Unknown service '{service}'", - "show_tile_cant_be_enabled_for_url_not_defined": "You cannot enable 'show_tile' right now, because you must first define an URL for the permission '{permission}'", "show_tile_cant_be_enabled_for_regex": "You cannot enable 'show_tile' right no, because the URL for the permission '{permission}' is a regex", + "show_tile_cant_be_enabled_for_url_not_defined": "You cannot enable 'show_tile' right now, because you must first define an URL for the permission '{permission}'", "ssowat_conf_generated": "SSOwat configuration regenerated", "ssowat_conf_updated": "SSOwat configuration updated", "system_upgraded": "System upgraded", @@ -621,8 +621,8 @@ "tools_upgrade_regular_packages": "Now upgrading 'regular' (non-yunohost-related) packages...", "tools_upgrade_regular_packages_failed": "Could not upgrade packages: {packages_list}", "tools_upgrade_special_packages": "Now upgrading 'special' (yunohost-related) packages...", - "tools_upgrade_special_packages_explanation": "The special upgrade will continue in the background. Please don't start any other actions on your server for the next ~10 minutes (depending on hardware speed). After this, you may have to re-log in to the webadmin. The upgrade log will be available in Tools → Log (in the webadmin) or using 'yunohost log list' (from the command-line).", "tools_upgrade_special_packages_completed": "YunoHost package upgrade completed.\nPress [Enter] to get the command line back", + "tools_upgrade_special_packages_explanation": "The special upgrade will continue in the background. Please don't start any other actions on your server for the next ~10 minutes (depending on hardware speed). After this, you may have to re-log in to the webadmin. The upgrade log will be available in Tools → Log (in the webadmin) or using 'yunohost log list' (from the command-line).", "unbackup_app": "{app} will not be saved", "unexpected_error": "Something unexpected went wrong: {error}", "unknown_main_domain_path": "Unknown domain or path for '{app}'. You need to specify a domain and a path to be able to specify a URL for permission.", @@ -643,19 +643,19 @@ "user_deleted": "User deleted", "user_deletion_failed": "Could not delete user {user}: {error}", "user_home_creation_failed": "Could not create home folder '{home}' for user", + "user_import_bad_file": "Your CSV file is not correctly formatted it will be ignored to avoid potential data loss", + "user_import_bad_line": "Incorrect line {line}: {details}", + "user_import_failed": "The users import operation completely failed", + "user_import_missing_columns": "The following columns are missing: {columns}", + "user_import_nothing_to_do": "No user needs to be imported", + "user_import_partial_failed": "The users import operation partially failed", + "user_import_success": "Users successfully imported", "user_unknown": "Unknown user: {user}", "user_update_failed": "Could not update user {user}: {error}", "user_updated": "User info changed", - "user_import_bad_line": "Incorrect line {line}: {details}", - "user_import_bad_file": "Your CSV file is not correctly formatted it will be ignored to avoid potential data loss", - "user_import_missing_columns": "The following columns are missing: {columns}", - "user_import_partial_failed": "The users import operation partially failed", - "user_import_failed": "The users import operation completely failed", - "user_import_nothing_to_do": "No user needs to be imported", - "user_import_success": "Users successfully imported", "yunohost_already_installed": "YunoHost is already installed", "yunohost_configured": "YunoHost is now configured", "yunohost_installing": "Installing YunoHost...", "yunohost_not_installed": "YunoHost is not correctly installed. Please run 'yunohost tools postinstall'", "yunohost_postinstall_end_tip": "The post-install completed! To finalize your setup, please consider:\n - adding a first user through the 'Users' section of the webadmin (or 'yunohost user create ' in command-line);\n - diagnose potential issues through the 'Diagnosis' section of the webadmin (or 'yunohost diagnosis run' in command-line);\n - reading the 'Finalizing your setup' and 'Getting to know YunoHost' parts in the admin documentation: https://yunohost.org/admindoc." -} +} \ No newline at end of file From 377771cc1170d8e3797fce5a9f1dade178142cb9 Mon Sep 17 00:00:00 2001 From: Tymofii-Lytvynenko Date: Fri, 3 Sep 2021 13:25:00 +0000 Subject: [PATCH 2904/3170] Translated using Weblate (Ukrainian) Currently translated at 34.2% (226 of 659 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/uk/ --- locales/uk.json | 82 ++++++++++++++++++++++++------------------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/locales/uk.json b/locales/uk.json index 47c5991b0..52a5b0a70 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -357,22 +357,22 @@ "diagnosis_http_nginx_conf_not_up_to_date": "Схоже, що конфігурація nginx цього домену була змінена вручну, що не дозволяє YunoHost визначити, чи доступний він по HTTP.", "diagnosis_http_partially_unreachable": "Домен {domain} здається недоступним по HTTP ззовні локальної мережі в IPv {failed}, хоча він працює в IPv {passed}.", "diagnosis_http_unreachable": "Домен {domain} здається недоступним через HTTP ззовні локальної мережі.", - "diagnosis_http_bad_status_code": "Схоже, що замість вашого сервера відповіла інша машина (можливо, ваш маршрутизатор).
1. Найбільш поширеною причиною цієї проблеми є те, що порт 80 (і 443) неправильно перенаправлений на ваш сервер .
2. На більш складних установках: переконайтеся, що немає брандмауера або зворотного проксі.", + "diagnosis_http_bad_status_code": "Схоже, що замість вашого сервера відповіла інша машина (можливо, ваш маршрутизатор).
1. Найбільш поширеною причиною цієї проблеми є те, що порт 80 (і 443) неправильно перенаправлений на ваш сервер .
2. На більш складних установках: переконайтеся, що немає брандмауера або зворотного проксі.", "diagnosis_http_connection_error": "Помилка підключення: не вдалося підключитися до запитуваного домену, швидше за все, він недоступний.", - "diagnosis_http_timeout": "При спробі зв'язатися з вашим сервером ззовні стався тайм-аут. Він здається недоступним.
1. Найбільш поширеною причиною цієї проблеми є те, що порт 80 (і 443) неправильно перенаправлений на ваш сервер .
2. Ви також повинні переконатися, що служба nginx запущена
3. На більш складних установках: переконайтеся, що немає брандмауера або зворотного проксі.", + "diagnosis_http_timeout": "При спробі зв'язатися з вашим сервером ззовні стався тайм-аут. Він здається недоступним.
1. Найбільш поширеною причиною цієї проблеми є те, що порт 80 (і 443) неправильно перенаправлений на ваш сервер .
2. Ви також повинні переконатися, що служба nginx запущена
3. На більш складних установках: переконайтеся, що немає брандмауера або зворотного проксі.", "diagnosis_http_ok": "Домен {domain} доступний по HTTP ззовні локальної мережі.", "diagnosis_http_localdomain": "Домен {domain} з доменом .local TLD не може бути доступний ззовні локальної мережі.", "diagnosis_http_could_not_diagnose_details": "Помилка: {error}", - "diagnosis_http_could_not_diagnose": "Не вдалося діагностувати досяжність доменів ззовні в IPv {ipversion}.", - "diagnosis_http_hairpinning_issue_details": "Можливо, це пов'язано з блоком/маршрутизатором вашого інтернет-провайдера. В результаті, люди ззовні вашої локальної мережі зможуть отримати доступ до вашого сервера, як і очікувалося, але не люди зсередини локальної мережі (як ви, ймовірно?) При використанні доменного імені або глобального IP. Можливо, ви зможете поліпшити ситуацію, глянувши на https://yunohost.org/dns_local_network .", + "diagnosis_http_could_not_diagnose": "Не вдалося діагностувати досяжність доменів ззовні в IPv{ipversion}.", + "diagnosis_http_hairpinning_issue_details": "Можливо, це пов'язано з блоком/маршрутизатором вашого інтернет-провайдера. В результаті, люди ззовні вашої локальної мережі зможуть отримати доступ до вашого сервера, як і очікувалося, але не люди зсередини локальної мережі (як ви, ймовірно?) При використанні доменного імені або глобального IP. Можливо, ви зможете поліпшити ситуацію, глянувши на https://yunohost.org/dns_local_network .", "diagnosis_http_hairpinning_issue": "Схоже, що у вашій локальній мережі не включена проброска.", - "diagnosis_ports_forwarding_tip": "Щоб вирішити цю проблему, вам, швидше за все, потрібно налаштувати кидок портів на вашому інтернет-маршрутизатор, як описано в https://yunohost.org/isp_box_config.", + "diagnosis_ports_forwarding_tip": "Щоб вирішити цю проблему, вам, швидше за все, потрібно налаштувати кидок портів на вашому інтернет-маршрутизатор, як описано в https://yunohost.org/isp_box_config.", "diagnosis_ports_needed_by": "Відкриття цього порту необхідно для функцій {category} (служба {service}).", "diagnosis_ports_ok": "Порт {port} доступний ззовні.", "diagnosis_ports_partially_unreachable": "Порт {port} не доступний ззовні в IPv {failed}.", "diagnosis_ports_unreachable": "Порт {port} недоступний ззовні.", "diagnosis_ports_could_not_diagnose_details": "Помилка: {error}", - "diagnosis_ports_could_not_diagnose": "Не вдалося діагностувати досяжність портів ззовні в IPv {ipversion}.", + "diagnosis_ports_could_not_diagnose": "Не вдалося діагностувати досяжність портів ззовні в IPv{ipversion}.", "diagnosis_description_regenconf": "Конфігурації системи", "diagnosis_description_mail": "Електронна пошта", "diagnosis_description_ports": "виявлення портів", @@ -386,7 +386,7 @@ "diagnosis_rootfstotalspace_critical": "Коренева файлова система має тільки {space}, що вельми тривожно! Швидше за все, дисковий простір закінчиться дуже швидко! Рекомендується мати не менше 16 ГБ для кореневої файлової системи.", "diagnosis_rootfstotalspace_warning": "Коренева файлова система має тільки {space}. Це може бути нормально, але будьте обережні, тому що в кінцевому підсумку дисковий простір може швидко закінчитися... Рекомендується мати не менше 16 ГБ для кореневої файлової системи.", "diagnosis_regenconf_manually_modified_details": "Це можливо нормально, якщо ви знаєте, що робите! YunoHost перестане оновлювати цей файл автоматично... Але врахуйте, що поновлення YunoHost можуть містити важливі рекомендовані зміни. Якщо ви хочете, ви можете перевірити відмінності за допомогою команди yunohost tools regen-conf {category} --dry-run --with-diff і примусово повернути рекомендовану конфігурацію за допомогою yunohost tools regen- conf {category} --force .", - "diagnosis_regenconf_manually_modified": "Конфігураційний файл {file} , схоже, був змінений вручну.", + "diagnosis_regenconf_manually_modified": "Конфігураційний файл {file}, схоже, був змінений вручну.", "diagnosis_regenconf_allgood": "Всі конфігураційні файли відповідають рекомендованої конфігурації!", "diagnosis_mail_queue_too_big": "Занадто багато відкладених листів в поштовій черзі ({nb_pending} emails)", "diagnosis_mail_queue_unavailable_details": "Помилка: {error}", @@ -394,27 +394,27 @@ "diagnosis_mail_queue_ok": "{nb_pending} відкладені листи в поштових чергах", "diagnosis_mail_blacklist_website": "Після визначення причини, по якій ви потрапили в чорний список, і її усунення, ви можете попросити видалити ваш IP або домен на {blacklist_website}.", "diagnosis_mail_blacklist_reason": "Причина внесення в чорний список: {reason}", - "diagnosis_mail_blacklist_listed_by": "Ваш IP або домен {item} знаходиться в чорному списку {blacklist_name}.", + "diagnosis_mail_blacklist_listed_by": "Ваш IP або домен {item} знаходиться в чорному списку {blacklist_name}.", "diagnosis_mail_blacklist_ok": "IP-адреси і домени, які використовуються цим сервером, не внесені в чорний список", - "diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "Поточний зворотний DNS: {rdns_domain}
Очікуване значення: {ehlo_domain} .", - "diagnosis_mail_fcrdns_different_from_ehlo_domain": "Зворотний DNS неправильно налаштований в IPv {ipversion}. Деякі електронні листи можуть бути не доставлені або можуть бути відзначені як спам.", + "diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "Поточний зворотний DNS: {rdns_domain}
Очікуване значення: {ehlo_domain}.", + "diagnosis_mail_fcrdns_different_from_ehlo_domain": "Зворотний DNS неправильно налаштований в IPv{ipversion}. Деякі електронні листи можуть бути не доставлені або можуть бути відзначені як спам.", "diagnosis_mail_fcrdns_nok_alternatives_6": "Деякі провайдери не дозволяють вам налаштувати зворотний DNS (або їх функція може бути зламана...). Якщо ваш зворотний DNS правильно налаштований для IPv4, ви можете спробувати відключити використання IPv6 при відправці листів, виконавши команду yunohost settings set smtp.allow_ipv6 -v off . Примітка: останнє рішення означає, що ви не зможете відправляти або отримувати електронну пошту з нечисленних серверів, що використовують тільки IPv6.", - "diagnosis_mail_fcrdns_nok_alternatives_4": "Деякі провайдери не дозволять вам налаштувати зворотний DNS (або їх функція може бути зламана...). Якщо ви відчуваєте проблеми з-за цього, розгляньте наступні рішення:
- Деякі провайдери надають альтернативу
використання ретранслятора поштового сервера , хоча це має на увазі, що ретранслятор зможе шпигувати за вашим поштовим трафіком.
- Альтернативою для захисту конфіденційності є використання VPN * з виділеним публічним IP * для обходу подібних обмежень. Дивіться https://yunohost.org/#/vpn_advantage
- Або можна переключитися на іншого провайдера .", - "diagnosis_mail_fcrdns_nok_details": "Спочатку спробуйте налаштувати зворотний DNS з {ehlo_domain} в інтерфейсі вашого інтернет-маршрутизатора або в інтерфейсі вашого хостинг-провайдера. (Деякі хостинг-провайдери можуть вимагати, щоб ви відправили їм тікет підтримки для цього).", - "diagnosis_mail_fcrdns_dns_missing": "У IPv {ipversion} не визначений зворотний DNS. Деякі листи можуть не доставлятися або позначатися як спам.", + "diagnosis_mail_fcrdns_nok_alternatives_4": "Деякі провайдери не дозволять вам налаштувати зворотний DNS (або їх функція може бути зламана...). Якщо ви відчуваєте проблеми через це, розгляньте наступні рішення:
- Деякі провайдери надають альтернативу використання ретранслятора поштового сервера, хоча це має на увазі, що ретранслятор зможе шпигувати за вашим поштовим трафіком.
- Альтернативою для захисту конфіденційності є використання VPN *з виділеним загальнодоступним IP* для обходу подібних обмежень. Дивіться https://yunohost.org/#/vpn_advantage
- Або можна переключитися на іншого провайдера .", + "diagnosis_mail_fcrdns_nok_details": "Спочатку спробуйте налаштувати зворотний DNS з {ehlo_domain} в інтерфейсі вашого інтернет-маршрутизатора або в інтерфейсі вашого хостинг-провайдера. (Деякі хостинг-провайдери можуть вимагати, щоб ви відправили їм тікет підтримки для цього).", + "diagnosis_mail_fcrdns_dns_missing": "У IPv{ipversion} не визначений зворотний DNS. Деякі листи можуть не доставлятися або позначатися як спам.", "diagnosis_mail_fcrdns_ok": "Ваш зворотний DNS налаштований правильно!", "diagnosis_mail_ehlo_could_not_diagnose_details": "Помилка: {error}", - "diagnosis_mail_ehlo_could_not_diagnose": "Не вдалося діагностувати наявність певної поштовий сервер postfix ззовні в IPv {ipversion}.", - "diagnosis_mail_ehlo_wrong_details": "EHLO, отриманий віддаленим діагностичним центром в IPv {ipversion}, відрізняється від домену вашого сервера.
Отриманий EHLO: {wrong_ehlo}
Очікуваний: {right_ehlo} < br> найпоширенішою причиною цієї проблеми є те, що порт 25 неправильно перенаправлений на ваш сервер . Крім того, переконайтеся, що в роботу сервера не втручається брандмауер або зворотний проксі-сервер.", - "diagnosis_mail_ehlo_wrong": "Інший поштовий SMTP-сервер відповідає на IPv {ipversion}. Ваш сервер, ймовірно, не зможе отримувати електронну пошту.", + "diagnosis_mail_ehlo_could_not_diagnose": "Не вдалося діагностувати наявність певної поштовий сервер postfix ззовні в IPv{ipversion}.", + "diagnosis_mail_ehlo_wrong_details": "EHLO, отриманий віддаленим діагностичним центром в IPv{ipversion}, відрізняється від домену вашого сервера.
Отриманий EHLO: {wrong_ehlo}
Очікуваний: {right_ehlo} < br> найпоширенішою причиною цієї проблеми є те, що порт 25 неправильно перенаправлений на ваш сервер . Крім того, переконайтеся, що в роботу сервера не втручається брандмауер або зворотний проксі-сервер.", + "diagnosis_mail_ehlo_wrong": "Інший поштовий SMTP-сервер відповідає на IPv{ipversion}. Ваш сервер, ймовірно, не зможе отримувати електронну пошту.", "diagnosis_mail_ehlo_bad_answer_details": "Це може бути викликано тим, що замість вашого сервера відповідає інша машина.", - "diagnosis_mail_ehlo_bad_answer": "Ні-SMTP служба відповідає на порту 25 на IPv {ipversion}.", - "diagnosis_mail_ehlo_unreachable_details": "Не вдалося відкрити з'єднання по порту 25 з вашим сервером на IPv {ipversion}. Він здається недоступним.
1. Найбільш поширеною причиною цієї проблеми є те, що порт 25 неправильно перенаправлений на ваш сервер .
2. Ви також повинні переконатися, що служба postfix запущена.
3. На більш складних установках: переконайтеся, що немає брандмауера або зворотного проксі.", - "diagnosis_mail_ehlo_unreachable": "Поштовий сервер SMTP недоступний ззовні по IPv {ipversion}. Він не зможе отримувати повідомлення електронної пошти.", - "diagnosis_mail_ehlo_ok": "Поштовий сервер SMTP доступний ззовні і тому може отримувати електронну пошту!", - "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Деякі провайдери не дозволять вам розблокувати вихідний порт 25, тому що вони не піклуються про Net Neutrality.
- Деякі з них пропонують альтернативу використання ретранслятора поштового сервера , хоча це має на увазі, що ретранслятор зможе шпигувати за вашим поштовим трафіком.
- Альтернативою для захисту конфіденційності є використання VPN * з виділеним публічним IP * для обходу такого роду обмежень. Дивіться https://yunohost.org/#/vpn_advantage
- Ви також можете розглянути можливість переходу на более дружнього до мережевого нейтралітету провайдера .", + "diagnosis_mail_ehlo_bad_answer": "Ні-SMTP служба відповідає на порту 25 на IPv{ipversion}.", + "diagnosis_mail_ehlo_unreachable_details": "Не вдалося відкрити з'єднання по порту 25 з вашим сервером на IPv{ipversion}. Він здається недоступним.
1. Найбільш поширеною причиною цієї проблеми є те, що порт 25 неправильно перенаправлений на ваш сервер .
2. Ви також повинні переконатися, що служба postfix запущена.
3. На більш складних установках: переконайтеся, що немає брандмауера або зворотного проксі.", + "diagnosis_mail_ehlo_unreachable": "Поштовий сервер SMTP недоступний ззовні по IPv{ipversion}. Він не зможе отримувати листи електронної пошти.", + "diagnosis_mail_ehlo_ok": "Поштовий сервер SMTP доступний ззовні і тому може отримувати електронні листи!", + "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Деякі провайдери не дозволять вам розблокувати вихідний порт 25, тому що вони не піклуються про мережевий нейтралітет (Net Neutrality).
- Деякі з них пропонують альтернативу використання ретранслятора поштового сервера, хоча це має на увазі, що ретранслятор зможе шпигувати за вашим поштовим трафіком.
- Альтернативою для захисту конфіденційності є використання VPN *з виділеним загальнодоступним IP* для обходу такого роду обмежень. Дивіться https://yunohost.org/#/vpn_advantage
- Ви також можете розглянути можливість переходу на більш дружнього до мережевого нейтралітету провайдера.", "diagnosis_mail_outgoing_port_25_blocked_details": "Спочатку спробуйте розблокувати вихідний порт 25 в інтерфейсі вашого інтернет-маршрутизатора або в інтерфейсі вашого хостинг-провайдера. (Деякі хостинг-провайдери можуть вимагати, щоб ви відправили їм заявку в службу підтримки).", - "diagnosis_mail_outgoing_port_25_blocked": "Поштовий сервер SMTP не може відправляти електронні листи на інші сервери, оскільки вихідний порт 25 заблокований в IPv {ipversion}.", + "diagnosis_mail_outgoing_port_25_blocked": "Поштовий сервер SMTP не може відправляти електронні листи на інші сервери, оскільки вихідний порт 25 заблоковано в IPv{ipversion}.", "app_manifest_install_ask_path": "Оберіть шлях URL (після домену), за яким має бути встановлено цей застосунок", "yunohost_postinstall_end_tip": "Постінсталляція завершена! Щоб завершити установку, будь ласка, розгляньте наступні варіанти: - додавання першого користувача через розділ 'Користувачі' веб-адміністратора (або 'yunohost user create ' в командному рядку); - діагностику можливих проблем через розділ 'Діагностика' веб-адміністратора (або 'yunohost diagnosis run' в командному рядку); - читання розділів 'Завершення установки' і 'Знайомство з YunoHost' в документації адміністратора: https://yunohost.org/admindoc.", "yunohost_not_installed": "YunoHost встановлений неправильно. Будь ласка, запустіть 'yunohost tools postinstall'.", @@ -465,26 +465,26 @@ "service_stop_failed": "Неможливо зупинити службу '{service}' Недавні журнали служб: {logs}", "service_started": "Служба '{service}' запущена", "service_start_failed": "Не вдалося запустити службу '{service}' Recent service logs: {logs}", - "diagnosis_mail_outgoing_port_25_ok": "Поштовий сервер SMTP може відправляти електронні листи (вихідний порт 25 не заблокований).", - "diagnosis_swap_tip": "Будь ласка, будьте обережні і знайте, що якщо сервер розміщує своп на SD-карті або SSD-накопичувачі, це може різко скоротити термін служби устройства`.", - "diagnosis_swap_ok": "Система має {total} свопу!", - "diagnosis_swap_notsomuch": "Система має тільки {total} свопу. Щоб уникнути ситуацій, коли в системі закінчується пам'ять, слід передбачити наявність не менше {recommended} обсягу підкачки.", - "diagnosis_swap_none": "В системі повністю відсутній своп. Ви повинні розглянути можливість додавання принаймні {recommended} обсягу підкачки, щоб уникнути ситуацій, коли системі не вистачає пам'яті.", + "diagnosis_mail_outgoing_port_25_ok": "Поштовий сервер SMTP може відправляти електронні листи (вихідний порт 25 не заблоковано).", + "diagnosis_swap_tip": "Будь ласка, будьте обережні і знайте, що якщо сервер розміщує обсяг підкачки на SD-карті або SSD-накопичувачі, це може різко скоротити строк служби пристрою`.", + "diagnosis_swap_ok": "Система має {total} обсягу підкачки!", + "diagnosis_swap_notsomuch": "Система має тільки {total} обсягу підкачки. Щоб уникнути станоаищ, коли в системі закінчується пам'ять, слід передбачити наявність не менше {recommended} обсягу підкачки.", + "diagnosis_swap_none": "В системі повністю відсутня підкачка. Ви повинні розглянути можливість додавання принаймні {recommended} обсягу підкачки, щоб уникнути ситуацій, коли системі не вистачає пам'яті.", "diagnosis_ram_ok": "Система все ще має {available} ({available_percent}%) оперативної пам'яті з {total}.", - "diagnosis_ram_low": "У системі є {available} ({available_percent}%) оперативної пам'яті (з {total}). Будьте уважні.", - "diagnosis_ram_verylow": "Система має тільки {available} ({available_percent}%) оперативної пам'яті! (З {total})", - "diagnosis_diskusage_ok": "У сховищі {mountpoint} (на пристрої {device} ) залишилося {free} ({free_percent}%) вільного місця (з {total})!", - "diagnosis_diskusage_low": "Сховище {mountpoint} (на пристрої {device} ) має тільки {free} ({free_percent}%) вільного місця (з {total}). Будьте уважні.", - "diagnosis_diskusage_verylow": "Сховище {mountpoint} (на пристрої {device} ) має тільки {free} ({free_percent}%) вільного місця (з {total}). Вам дійсно варто подумати про очищення простору!", - "diagnosis_services_bad_status_tip": "Ви можете спробувати перезапустити службу , а якщо це не допоможе, подивіться журнали служби в webadmin (з командного рядка це можна зробити за допомогою yunohost service restart {service} yunohost service log {service} ).", - "diagnosis_services_bad_status": "Сервіс {service} знаходиться в {status} :(", - "diagnosis_services_conf_broken": "Конфігурація порушена для служби {service}!", - "diagnosis_services_running": "Служба {service} запущена!", - "diagnosis_domain_expires_in": "Термін дії {domain} закінчується через {days} днів.", - "diagnosis_domain_expiration_error": "Термін дії деяких доменів закінчується ДУЖЕ СКОРО!", - "diagnosis_domain_expiration_warning": "Термін дії деяких доменів закінчиться найближчим часом!", + "diagnosis_ram_low": "У системі наявно {available} ({available_percent}%) оперативної пам'яті (з {total}). Будьте уважні.", + "diagnosis_ram_verylow": "Система має тільки {available} ({available_percent}%) оперативної пам'яті! (з {total})", + "diagnosis_diskusage_ok": "У сховищі {mountpoint} (на пристрої {device}) залишилося {free} ({free_percent}%) вільного місця (з {total})!", + "diagnosis_diskusage_low": "Сховище {mountpoint} (на пристрої {device}) має тільки {free} ({free_percent}%) вільного місця (з {total}). Будьте уважні.", + "diagnosis_diskusage_verylow": "Сховище {mountpoint} (на пристрої {device}) має тільки {free} ({free_percent}%) вільного місця (з {total}). Вам дійсно варто подумати про очищення простору!", + "diagnosis_services_bad_status_tip": "Ви можете спробувати перезапустити службу, а якщо це не допоможе, подивіться журнали служби в вебадміністраторі (з командного рядка це можна зробити за допомогою yunohost service restart {service} і yunohost service log {service}).", + "diagnosis_services_bad_status": "Служба {service} у стані {status} :(", + "diagnosis_services_conf_broken": "Для служби {service} порушена конфігурація!", + "diagnosis_services_running": "Службу {service} запущено!", + "diagnosis_domain_expires_in": "Строк дії {domain} спливе через {days} днів.", + "diagnosis_domain_expiration_error": "Строк дії деяких доменів НЕЗАБАРОМ спливе!", + "diagnosis_domain_expiration_warning": "Строк дії деяких доменів спливе найближчим часом!", "diagnosis_domain_expiration_success": "Ваші домени зареєстровані і не збираються спливати найближчим часом.", - "diagnosis_domain_expiration_not_found_details": "Інформація WHOIS для домену {domain} не містить інформації про термін дії?", + "diagnosis_domain_expiration_not_found_details": "Відомості WHOIS для домену {domain} не містять даних про строк дії?", "diagnosis_domain_not_found_details": "Домен {domain} не існує в базі даних WHOIS або строк його дії сплив!", "diagnosis_domain_expiration_not_found": "Неможливо перевірити строк дії деяких доменів", "diagnosis_dns_specialusedomain": "Домен {domain} заснований на домені верхнього рівня спеціального призначення (TLD) і тому не очікується, що у нього будуть актуальні записи DNS.", @@ -502,7 +502,7 @@ "diagnosis_ip_not_connected_at_all": "Здається, сервер взагалі не під'єднаний до Інтернету!?", "diagnosis_ip_local": "Локальний IP: {local}.", "diagnosis_ip_global": "Глобальний IP: {global}", - "diagnosis_ip_no_ipv6_tip": "Наявність робочого IPv6 не є обов'язковим для роботи вашого сервера, але це краще для здоров'я Інтернету в цілому. IPv6 зазвичай автоматично налаштовується системою або вашим провайдером, якщо він доступний. В іншому випадку вам, можливо, доведеться налаштувати деякі речі вручну, як пояснюється в документації тут: https://yunohost.org/#/ipv6. Якщо ви не можете увімкнути IPv6 або якщо це здається вам занадто технічним, ви також можете сміливо нехтувати цим попередженням.", + "diagnosis_ip_no_ipv6_tip": "Наявність робочого IPv6 не є обов'язковим для роботи вашого сервера, але це краще для здоров'я Інтернету в цілому. IPv6 зазвичай автоматично налаштовується системою або вашим провайдером, якщо він доступний. В іншому випадку вам, можливо, доведеться налаштувати деякі речі вручну, як пояснюється в документації тут: https://yunohost.org/#/ipv6. Якщо ви не можете увімкнути IPv6 або якщо це здається вам занадто технічним, ви також можете сміливо нехтувати цим попередженням.", "diagnosis_ip_no_ipv6": "Сервер не має робочого IPv6.", "diagnosis_ip_connected_ipv6": "Сервер під'єднаний до Інтернету через IPv6!", "diagnosis_ip_no_ipv4": "Сервер не має робочого IPv4.", From d36de3589b5253ae6e5e77fc1a45d612f22265bf Mon Sep 17 00:00:00 2001 From: Tymofii-Lytvynenko Date: Fri, 3 Sep 2021 14:43:40 +0000 Subject: [PATCH 2905/3170] Translated using Weblate (Ukrainian) Currently translated at 34.4% (227 of 659 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/uk/ --- locales/uk.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/locales/uk.json b/locales/uk.json index 52a5b0a70..41efdabe8 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -406,10 +406,10 @@ "diagnosis_mail_ehlo_could_not_diagnose_details": "Помилка: {error}", "diagnosis_mail_ehlo_could_not_diagnose": "Не вдалося діагностувати наявність певної поштовий сервер postfix ззовні в IPv{ipversion}.", "diagnosis_mail_ehlo_wrong_details": "EHLO, отриманий віддаленим діагностичним центром в IPv{ipversion}, відрізняється від домену вашого сервера.
Отриманий EHLO: {wrong_ehlo}
Очікуваний: {right_ehlo} < br> найпоширенішою причиною цієї проблеми є те, що порт 25 неправильно перенаправлений на ваш сервер . Крім того, переконайтеся, що в роботу сервера не втручається брандмауер або зворотний проксі-сервер.", - "diagnosis_mail_ehlo_wrong": "Інший поштовий SMTP-сервер відповідає на IPv{ipversion}. Ваш сервер, ймовірно, не зможе отримувати електронну пошту.", + "diagnosis_mail_ehlo_wrong": "Інший поштовий SMTP-сервер відповідає на IPv{ipversion}. Ваш сервер, ймовірно, не зможе отримувати електронні листи.", "diagnosis_mail_ehlo_bad_answer_details": "Це може бути викликано тим, що замість вашого сервера відповідає інша машина.", - "diagnosis_mail_ehlo_bad_answer": "Ні-SMTP служба відповідає на порту 25 на IPv{ipversion}.", - "diagnosis_mail_ehlo_unreachable_details": "Не вдалося відкрити з'єднання по порту 25 з вашим сервером на IPv{ipversion}. Він здається недоступним.
1. Найбільш поширеною причиною цієї проблеми є те, що порт 25 неправильно перенаправлений на ваш сервер .
2. Ви також повинні переконатися, що служба postfix запущена.
3. На більш складних установках: переконайтеся, що немає брандмауера або зворотного проксі.", + "diagnosis_mail_ehlo_bad_answer": "Не-SMTP служба відповіла на порту 25 на IPv{ipversion}.", + "diagnosis_mail_ehlo_unreachable_details": "Не вдалося відкрити з'єднання за портом 25 з вашим сервером на IPv{ipversion}. Він здається недоступним.
1. Найбільш поширеною причиною цієї проблеми є те, що порт 25 неправильно перенаправлений на ваш сервер.
2. Ви також повинні переконатися, що служба postfix запущена.
3. На більш складних установках: переконайтеся, що немає фаєрвола або зворотного проксі.", "diagnosis_mail_ehlo_unreachable": "Поштовий сервер SMTP недоступний ззовні по IPv{ipversion}. Він не зможе отримувати листи електронної пошти.", "diagnosis_mail_ehlo_ok": "Поштовий сервер SMTP доступний ззовні і тому може отримувати електронні листи!", "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Деякі провайдери не дозволять вам розблокувати вихідний порт 25, тому що вони не піклуються про мережевий нейтралітет (Net Neutrality).
- Деякі з них пропонують альтернативу використання ретранслятора поштового сервера, хоча це має на увазі, що ретранслятор зможе шпигувати за вашим поштовим трафіком.
- Альтернативою для захисту конфіденційності є використання VPN *з виділеним загальнодоступним IP* для обходу такого роду обмежень. Дивіться https://yunohost.org/#/vpn_advantage
- Ви також можете розглянути можливість переходу на більш дружнього до мережевого нейтралітету провайдера.", From 2b5fc01d054eb1327ccff7353918523d4a9ea5fd Mon Sep 17 00:00:00 2001 From: Tymofii-Lytvynenko Date: Fri, 3 Sep 2021 14:46:59 +0000 Subject: [PATCH 2906/3170] Translated using Weblate (Ukrainian) Currently translated at 34.5% (228 of 659 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/uk/ --- locales/uk.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/uk.json b/locales/uk.json index 41efdabe8..0b0ddc191 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -405,7 +405,7 @@ "diagnosis_mail_fcrdns_ok": "Ваш зворотний DNS налаштований правильно!", "diagnosis_mail_ehlo_could_not_diagnose_details": "Помилка: {error}", "diagnosis_mail_ehlo_could_not_diagnose": "Не вдалося діагностувати наявність певної поштовий сервер postfix ззовні в IPv{ipversion}.", - "diagnosis_mail_ehlo_wrong_details": "EHLO, отриманий віддаленим діагностичним центром в IPv{ipversion}, відрізняється від домену вашого сервера.
Отриманий EHLO: {wrong_ehlo}
Очікуваний: {right_ehlo} < br> найпоширенішою причиною цієї проблеми є те, що порт 25 неправильно перенаправлений на ваш сервер . Крім того, переконайтеся, що в роботу сервера не втручається брандмауер або зворотний проксі-сервер.", + "diagnosis_mail_ehlo_wrong_details": "EHLO, отриманий віддаленим діагностичним центром в IPv{ipversion}, відрізняється від домену вашого сервера.
Отриманий EHLO: {wrong_ehlo}
Очікуваний: {right_ehlo}< br>Найпоширенішою причиною цієї проблеми є те, що порт 25 неправильно перенаправлений на ваш сервер. Крім того, переконайтеся, що в роботу сервера не втручається фаєрвол або зворотний проксі-сервер.", "diagnosis_mail_ehlo_wrong": "Інший поштовий SMTP-сервер відповідає на IPv{ipversion}. Ваш сервер, ймовірно, не зможе отримувати електронні листи.", "diagnosis_mail_ehlo_bad_answer_details": "Це може бути викликано тим, що замість вашого сервера відповідає інша машина.", "diagnosis_mail_ehlo_bad_answer": "Не-SMTP служба відповіла на порту 25 на IPv{ipversion}.", From 0093d126309cb47eba721e39ecb276ede45dfc06 Mon Sep 17 00:00:00 2001 From: Tymofii-Lytvynenko Date: Fri, 3 Sep 2021 14:51:29 +0000 Subject: [PATCH 2907/3170] Translated using Weblate (Ukrainian) Currently translated at 35.8% (236 of 659 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/uk/ --- locales/uk.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/locales/uk.json b/locales/uk.json index 0b0ddc191..77bc1f416 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -396,15 +396,15 @@ "diagnosis_mail_blacklist_reason": "Причина внесення в чорний список: {reason}", "diagnosis_mail_blacklist_listed_by": "Ваш IP або домен {item} знаходиться в чорному списку {blacklist_name}.", "diagnosis_mail_blacklist_ok": "IP-адреси і домени, які використовуються цим сервером, не внесені в чорний список", - "diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "Поточний зворотний DNS: {rdns_domain}
Очікуване значення: {ehlo_domain}.", + "diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "Поточний зворотний DNS:{rdns_domain}
Очікуване значення: {ehlo_domain}.", "diagnosis_mail_fcrdns_different_from_ehlo_domain": "Зворотний DNS неправильно налаштований в IPv{ipversion}. Деякі електронні листи можуть бути не доставлені або можуть бути відзначені як спам.", - "diagnosis_mail_fcrdns_nok_alternatives_6": "Деякі провайдери не дозволяють вам налаштувати зворотний DNS (або їх функція може бути зламана...). Якщо ваш зворотний DNS правильно налаштований для IPv4, ви можете спробувати відключити використання IPv6 при відправці листів, виконавши команду yunohost settings set smtp.allow_ipv6 -v off . Примітка: останнє рішення означає, що ви не зможете відправляти або отримувати електронну пошту з нечисленних серверів, що використовують тільки IPv6.", + "diagnosis_mail_fcrdns_nok_alternatives_6": "Деякі провайдери не дозволять вам налаштувати зворотний DNS (або їх функція може бути зламана...). Якщо ваш зворотний DNS правильно налаштований для IPv4, ви можете спробувати вимкнути використання IPv6 при надсиланні листів, виконавши команду yunohost settings set smtp.allow_ipv6 -v off. Примітка: останнє рішення означає, що ви не зможете надсилати або отримувати електронні листи з нечисленних серверів, що використовують тільки IPv6.", "diagnosis_mail_fcrdns_nok_alternatives_4": "Деякі провайдери не дозволять вам налаштувати зворотний DNS (або їх функція може бути зламана...). Якщо ви відчуваєте проблеми через це, розгляньте наступні рішення:
- Деякі провайдери надають альтернативу використання ретранслятора поштового сервера, хоча це має на увазі, що ретранслятор зможе шпигувати за вашим поштовим трафіком.
- Альтернативою для захисту конфіденційності є використання VPN *з виділеним загальнодоступним IP* для обходу подібних обмежень. Дивіться https://yunohost.org/#/vpn_advantage
- Або можна переключитися на іншого провайдера .", - "diagnosis_mail_fcrdns_nok_details": "Спочатку спробуйте налаштувати зворотний DNS з {ehlo_domain} в інтерфейсі вашого інтернет-маршрутизатора або в інтерфейсі вашого хостинг-провайдера. (Деякі хостинг-провайдери можуть вимагати, щоб ви відправили їм тікет підтримки для цього).", + "diagnosis_mail_fcrdns_nok_details": "Спочатку спробуйте налаштувати зворотний DNS з {ehlo_domain} в інтерфейсі вашого інтернет-маршрутизатора або в інтерфейсі вашого хостинг-провайдера. (Деякі хостинг-провайдери можуть вимагати, щоб ви відправили їм запит у підтримку для цього).", "diagnosis_mail_fcrdns_dns_missing": "У IPv{ipversion} не визначений зворотний DNS. Деякі листи можуть не доставлятися або позначатися як спам.", - "diagnosis_mail_fcrdns_ok": "Ваш зворотний DNS налаштований правильно!", + "diagnosis_mail_fcrdns_ok": "Ваш зворотний DNS налаштовано правильно!", "diagnosis_mail_ehlo_could_not_diagnose_details": "Помилка: {error}", - "diagnosis_mail_ehlo_could_not_diagnose": "Не вдалося діагностувати наявність певної поштовий сервер postfix ззовні в IPv{ipversion}.", + "diagnosis_mail_ehlo_could_not_diagnose": "Не вдалося діагностувати, чи доступний поштовий сервер postfix ззовні в IPv{ipversion}.", "diagnosis_mail_ehlo_wrong_details": "EHLO, отриманий віддаленим діагностичним центром в IPv{ipversion}, відрізняється від домену вашого сервера.
Отриманий EHLO: {wrong_ehlo}
Очікуваний: {right_ehlo}< br>Найпоширенішою причиною цієї проблеми є те, що порт 25 неправильно перенаправлений на ваш сервер. Крім того, переконайтеся, що в роботу сервера не втручається фаєрвол або зворотний проксі-сервер.", "diagnosis_mail_ehlo_wrong": "Інший поштовий SMTP-сервер відповідає на IPv{ipversion}. Ваш сервер, ймовірно, не зможе отримувати електронні листи.", "diagnosis_mail_ehlo_bad_answer_details": "Це може бути викликано тим, що замість вашого сервера відповідає інша машина.", From 23a8830af405119ede3fcaafebf2958c4aab92c9 Mon Sep 17 00:00:00 2001 From: Tymofii-Lytvynenko Date: Fri, 3 Sep 2021 14:53:25 +0000 Subject: [PATCH 2908/3170] Translated using Weblate (Ukrainian) Currently translated at 36.4% (240 of 659 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/uk/ --- locales/uk.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/uk.json b/locales/uk.json index 77bc1f416..3a4fe8910 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -391,8 +391,8 @@ "diagnosis_mail_queue_too_big": "Занадто багато відкладених листів в поштовій черзі ({nb_pending} emails)", "diagnosis_mail_queue_unavailable_details": "Помилка: {error}", "diagnosis_mail_queue_unavailable": "Неможливо дізнатися кількість очікують листів в черзі", - "diagnosis_mail_queue_ok": "{nb_pending} відкладені листи в поштових чергах", - "diagnosis_mail_blacklist_website": "Після визначення причини, по якій ви потрапили в чорний список, і її усунення, ви можете попросити видалити ваш IP або домен на {blacklist_website}.", + "diagnosis_mail_queue_ok": "Відкладених електронних листів у поштових чергах: {nb_pending}", + "diagnosis_mail_blacklist_website": "Після визначення причини, з якої ви потрапили в чорний список, і її усунення, ви можете попросити видалити ваш IP або домен на {blacklist_website}.", "diagnosis_mail_blacklist_reason": "Причина внесення в чорний список: {reason}", "diagnosis_mail_blacklist_listed_by": "Ваш IP або домен {item} знаходиться в чорному списку {blacklist_name}.", "diagnosis_mail_blacklist_ok": "IP-адреси і домени, які використовуються цим сервером, не внесені в чорний список", From a2e68fe75345975bc26799fbbc9aa2aad2aa819f Mon Sep 17 00:00:00 2001 From: Tymofii-Lytvynenko Date: Fri, 3 Sep 2021 14:54:54 +0000 Subject: [PATCH 2909/3170] Translated using Weblate (Ukrainian) Currently translated at 37.1% (245 of 659 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/uk/ --- locales/uk.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/locales/uk.json b/locales/uk.json index 3a4fe8910..4e323bfd6 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -386,11 +386,11 @@ "diagnosis_rootfstotalspace_critical": "Коренева файлова система має тільки {space}, що вельми тривожно! Швидше за все, дисковий простір закінчиться дуже швидко! Рекомендується мати не менше 16 ГБ для кореневої файлової системи.", "diagnosis_rootfstotalspace_warning": "Коренева файлова система має тільки {space}. Це може бути нормально, але будьте обережні, тому що в кінцевому підсумку дисковий простір може швидко закінчитися... Рекомендується мати не менше 16 ГБ для кореневої файлової системи.", "diagnosis_regenconf_manually_modified_details": "Це можливо нормально, якщо ви знаєте, що робите! YunoHost перестане оновлювати цей файл автоматично... Але врахуйте, що поновлення YunoHost можуть містити важливі рекомендовані зміни. Якщо ви хочете, ви можете перевірити відмінності за допомогою команди yunohost tools regen-conf {category} --dry-run --with-diff і примусово повернути рекомендовану конфігурацію за допомогою yunohost tools regen- conf {category} --force .", - "diagnosis_regenconf_manually_modified": "Конфігураційний файл {file}, схоже, був змінений вручну.", - "diagnosis_regenconf_allgood": "Всі конфігураційні файли відповідають рекомендованої конфігурації!", - "diagnosis_mail_queue_too_big": "Занадто багато відкладених листів в поштовій черзі ({nb_pending} emails)", + "diagnosis_regenconf_manually_modified": "Конфігураційний файл {file}, схоже, було змінено вручну.", + "diagnosis_regenconf_allgood": "Усі конфігураційні файли відповідають рекомендованій конфігурації!", + "diagnosis_mail_queue_too_big": "Занадто багато відкладених листів у поштовій черзі (листів: {nb_pending})", "diagnosis_mail_queue_unavailable_details": "Помилка: {error}", - "diagnosis_mail_queue_unavailable": "Неможливо дізнатися кількість очікують листів в черзі", + "diagnosis_mail_queue_unavailable": "Неможливо дізнатися кількість очікувальних листів у черзі", "diagnosis_mail_queue_ok": "Відкладених електронних листів у поштових чергах: {nb_pending}", "diagnosis_mail_blacklist_website": "Після визначення причини, з якої ви потрапили в чорний список, і її усунення, ви можете попросити видалити ваш IP або домен на {blacklist_website}.", "diagnosis_mail_blacklist_reason": "Причина внесення в чорний список: {reason}", From 09e55ef9b1876a4781a9061d3499fe24cdd0cdf5 Mon Sep 17 00:00:00 2001 From: Tymofii-Lytvynenko Date: Fri, 3 Sep 2021 15:15:04 +0000 Subject: [PATCH 2910/3170] Translated using Weblate (Ukrainian) Currently translated at 41.7% (275 of 659 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/uk/ --- locales/uk.json | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/locales/uk.json b/locales/uk.json index 4e323bfd6..45c5e6bb8 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -359,33 +359,33 @@ "diagnosis_http_unreachable": "Домен {domain} здається недоступним через HTTP ззовні локальної мережі.", "diagnosis_http_bad_status_code": "Схоже, що замість вашого сервера відповіла інша машина (можливо, ваш маршрутизатор).
1. Найбільш поширеною причиною цієї проблеми є те, що порт 80 (і 443) неправильно перенаправлений на ваш сервер .
2. На більш складних установках: переконайтеся, що немає брандмауера або зворотного проксі.", "diagnosis_http_connection_error": "Помилка підключення: не вдалося підключитися до запитуваного домену, швидше за все, він недоступний.", - "diagnosis_http_timeout": "При спробі зв'язатися з вашим сервером ззовні стався тайм-аут. Він здається недоступним.
1. Найбільш поширеною причиною цієї проблеми є те, що порт 80 (і 443) неправильно перенаправлений на ваш сервер .
2. Ви також повинні переконатися, що служба nginx запущена
3. На більш складних установках: переконайтеся, що немає брандмауера або зворотного проксі.", - "diagnosis_http_ok": "Домен {domain} доступний по HTTP ззовні локальної мережі.", - "diagnosis_http_localdomain": "Домен {domain} з доменом .local TLD не може бути доступний ззовні локальної мережі.", + "diagnosis_http_timeout": "При спробі зв'язатися з вашим сервером ззовні стався тайм-аут. Він здається недоступним.
1. Найбільш поширеною причиною цієї проблеми є те, що порт 80 (і 443) неправильно перенаправлено на ваш сервер .
2. Ви також повинні переконатися, що служба nginx запущена
3.На більш складних установках: переконайтеся, що немає фаєрвола або зворотного проксі.", + "diagnosis_http_ok": "Домен {domain} доступний по HTTP поза локальною мережею.", + "diagnosis_http_localdomain": "Домен {domain} з .local TLD не може бути доступний ззовні локальної мережі.", "diagnosis_http_could_not_diagnose_details": "Помилка: {error}", "diagnosis_http_could_not_diagnose": "Не вдалося діагностувати досяжність доменів ззовні в IPv{ipversion}.", - "diagnosis_http_hairpinning_issue_details": "Можливо, це пов'язано з блоком/маршрутизатором вашого інтернет-провайдера. В результаті, люди ззовні вашої локальної мережі зможуть отримати доступ до вашого сервера, як і очікувалося, але не люди зсередини локальної мережі (як ви, ймовірно?) При використанні доменного імені або глобального IP. Можливо, ви зможете поліпшити ситуацію, глянувши на https://yunohost.org/dns_local_network .", - "diagnosis_http_hairpinning_issue": "Схоже, що у вашій локальній мережі не включена проброска.", - "diagnosis_ports_forwarding_tip": "Щоб вирішити цю проблему, вам, швидше за все, потрібно налаштувати кидок портів на вашому інтернет-маршрутизатор, як описано в https://yunohost.org/isp_box_config.", - "diagnosis_ports_needed_by": "Відкриття цього порту необхідно для функцій {category} (служба {service}).", + "diagnosis_http_hairpinning_issue_details": "Можливо, це пов'язано з коробкою/маршрутизатором вашого інтернет-провайдера. В результаті, люди ззовні вашої локальної мережі зможуть отримати доступ до вашого сервера, як і очікувалося, але не люди зсередини локальної мережі (як ви, ймовірно?) При використанні доменного імені або глобального IP. Можливо, ви зможете поліпшити ситуацію, глянувши https://yunohost.org/dns_local_network .", + "diagnosis_http_hairpinning_issue": "Схоже, що у вашій локальній мережі не увімкнено шпилькування (hairpinning).", + "diagnosis_ports_forwarding_tip": "Щоб вирішити цю проблему, вам, швидше за все, потрібно налаштувати пересилання портів на вашому інтернет-маршрутизаторі, як описано в https://yunohost.org/isp_box_config.", + "diagnosis_ports_needed_by": "Відкриття цього порту необхідне для функцій {category} (служба {service}).", "diagnosis_ports_ok": "Порт {port} доступний ззовні.", - "diagnosis_ports_partially_unreachable": "Порт {port} не доступний ззовні в IPv {failed}.", + "diagnosis_ports_partially_unreachable": "Порт {port} не доступний ззовні в IPv{failed}.", "diagnosis_ports_unreachable": "Порт {port} недоступний ззовні.", "diagnosis_ports_could_not_diagnose_details": "Помилка: {error}", "diagnosis_ports_could_not_diagnose": "Не вдалося діагностувати досяжність портів ззовні в IPv{ipversion}.", "diagnosis_description_regenconf": "Конфігурації системи", - "diagnosis_description_mail": "Електронна пошта", - "diagnosis_description_ports": "виявлення портів", + "diagnosis_description_mail": "Е-пошта", + "diagnosis_description_ports": "Виявлення портів", "diagnosis_description_systemresources": "Системні ресурси", "diagnosis_description_services": "Перевірка стану служб", - "diagnosis_description_dnsrecords": "записи DNS", + "diagnosis_description_dnsrecords": "DNS-записи", "diagnosis_description_ip": "Інтернет-з'єднання", - "diagnosis_description_basesystem": "Базова система", - "diagnosis_security_vulnerable_to_meltdown_details": "Щоб виправити це, вам слід оновити систему і перезавантажитися, щоб завантажити нове ядро linux (або звернутися до вашого серверного провайдеру, якщо це не спрацює). Додаткову інформацію див. На сайті https://meltdownattack.com/.", - "diagnosis_security_vulnerable_to_meltdown": "Схоже, що ви уразливі до критичної уразливості безпеки Meltdown.", - "diagnosis_rootfstotalspace_critical": "Коренева файлова система має тільки {space}, що вельми тривожно! Швидше за все, дисковий простір закінчиться дуже швидко! Рекомендується мати не менше 16 ГБ для кореневої файлової системи.", - "diagnosis_rootfstotalspace_warning": "Коренева файлова система має тільки {space}. Це може бути нормально, але будьте обережні, тому що в кінцевому підсумку дисковий простір може швидко закінчитися... Рекомендується мати не менше 16 ГБ для кореневої файлової системи.", - "diagnosis_regenconf_manually_modified_details": "Це можливо нормально, якщо ви знаєте, що робите! YunoHost перестане оновлювати цей файл автоматично... Але врахуйте, що поновлення YunoHost можуть містити важливі рекомендовані зміни. Якщо ви хочете, ви можете перевірити відмінності за допомогою команди yunohost tools regen-conf {category} --dry-run --with-diff і примусово повернути рекомендовану конфігурацію за допомогою yunohost tools regen- conf {category} --force .", + "diagnosis_description_basesystem": "Основна система", + "diagnosis_security_vulnerable_to_meltdown_details": "Щоб виправити це, вам слід оновити систему і перезавантажитися, щоб завантажити нове ядро Linux (або звернутися до вашого серверного провайдера, якщо це не спрацює). Докладніше див. на сайті https://meltdownattack.com/.", + "diagnosis_security_vulnerable_to_meltdown": "Схоже, що ви вразливі до критичної вразливості безпеки Meltdown.", + "diagnosis_rootfstotalspace_critical": "Коренева файлова система має тільки {space}, що дуже тривожно! Скоріше за все, дисковий простір закінчиться дуже скоро! Рекомендовано мати не менше 16 ГБ для кореневої файлової системи.", + "diagnosis_rootfstotalspace_warning": "Коренева файлова система має тільки {space}. Можливо це нормально, але будьте обережні, тому що в кінцевому підсумку дисковий простір може швидко закінчитися... Рекомендовано мати не менше 16 ГБ для кореневої файлової системи.", + "diagnosis_regenconf_manually_modified_details": "Можливо це нормально, якщо ви знаєте, що робите! YunoHost перестане оновлювати цей файл автоматично... Але врахуйте, що оновлення YunoHost можуть містити важливі рекомендовані зміни. Якщо ви хочете, ви можете перевірити відмінності за допомогою команди yunohost tools regen-conf {category} --dry-run --with-diff і примусово повернути рекомендовану конфігурацію за допомогою команди yunohost tools regen-conf {category} --force.", "diagnosis_regenconf_manually_modified": "Конфігураційний файл {file}, схоже, було змінено вручну.", "diagnosis_regenconf_allgood": "Усі конфігураційні файли відповідають рекомендованій конфігурації!", "diagnosis_mail_queue_too_big": "Занадто багато відкладених листів у поштовій черзі (листів: {nb_pending})", @@ -529,7 +529,7 @@ "diagnosis_basesystem_hardware_model": "Модель сервера - {model}", "diagnosis_basesystem_hardware": "Архітектура апаратного забезпечення сервера - {virt} {arch}", "custom_app_url_required": "Ви повинні надати URL-адресу для оновлення вашого користувацького застосунку {app}.", - "confirm_app_install_thirdparty": "НЕБЕЗПЕЧНО! Цей застосунок не входить в каталог застосунків YunoHost. Установлення сторонніх застосунків може порушити цілісність і безпеку вашої системи. Вам не слід встановлювати його, якщо ви не знаєте, що робите. НІЯКОЇ ПІДТРИМКИ НЕ БУДЕ, якщо цей застосунок не буде працювати або зламає вашу систему... Якщо ви все одно готові піти на такий ризик, введіть '{answers}'.", + "confirm_app_install_thirdparty": "НЕБЕЗПЕЧНО! Цей застосунок не входить у каталог застосунків YunoHost. Установлення сторонніх застосунків може порушити цілісність і безпеку вашої системи. Вам не слід встановлювати його, якщо ви не знаєте, що робите. НІЯКОЇ ПІДТРИМКИ НЕ БУДЕ, якщо цей застосунок не буде працювати або зламає вашу систему... Якщо ви все одно готові піти на такий ризик, введіть '{answers}'.", "confirm_app_install_danger": "НЕБЕЗПЕЧНО! Відомо, що цей застосунок все ще експериментальний (якщо не сказати, що він явно не працює)! Вам не слід встановлювати його, якщо ви не знаєте, що робите. Ніякої підтримки не буде надано, якщо цей застосунок не буде працювати або зламає вашу систему... Якщо ви все одно готові ризикнути, введіть '{answers}'.", "confirm_app_install_warning": "Попередження: Цей застосунок може працювати, але він не дуже добре інтегрований в YunoHost. Деякі функції, такі як єдина реєстрація та резервне копіювання/відновлення, можуть бути недоступні. Все одно встановити? [{answers}]. ", "certmanager_unable_to_parse_self_CA_name": "Не вдалося розібрати назву самопідписного центру (файл: {file})", @@ -637,5 +637,6 @@ "app_not_upgraded": "Застосунок '{failed_app}' не вдалося оновити, і, як наслідок, оновлення таких застосунків було скасовано: {apps}", "app_manifest_install_ask_is_public": "Чи має цей застосунок бути відкритим для анонімних відвідувачів?", "app_manifest_install_ask_admin": "Виберіть користувача-адміністратора для цього застосунку", - "app_manifest_install_ask_password": "Виберіть пароль адміністратора для цього застосунку" + "app_manifest_install_ask_password": "Виберіть пароль адміністратора для цього застосунку", + "diagnosis_description_apps": "Застосунки" } From 25d9ff8a7982e93ce63e8a4c662589dbb1878fce Mon Sep 17 00:00:00 2001 From: Tymofii-Lytvynenko Date: Fri, 3 Sep 2021 18:40:38 +0000 Subject: [PATCH 2911/3170] Translated using Weblate (Ukrainian) Currently translated at 100.0% (659 of 659 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/uk/ --- locales/uk.json | 595 +++++++++++++++++++++++++----------------------- 1 file changed, 307 insertions(+), 288 deletions(-) diff --git a/locales/uk.json b/locales/uk.json index 45c5e6bb8..bdbe8b0cd 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -33,63 +33,63 @@ "action_invalid": "Неприпустима дія '{action}'", "aborting": "Переривання.", "diagnosis_description_web": "Мережа", - "service_reloaded_or_restarted": "Служба '{service}' була перезавантажена або перезапущено", - "service_reload_or_restart_failed": "Не вдалося перезавантажити або перезапустити службу '{service}' Recent service logs: {logs}", - "service_restarted": "Служба '{service}' перезапущено", - "service_restart_failed": "Не вдалося запустити службу '{service}' Недавні журнали служб: {logs}", + "service_reloaded_or_restarted": "Службу '{service}' була перезавантажено або перезапущено", + "service_reload_or_restart_failed": "Не вдалося перезавантажити або перезапустити службу '{service}' \n\nНедавні журнали служби: {logs}", + "service_restarted": "Службу '{service}' перезапущено", + "service_restart_failed": "Не вдалося запустити службу '{service}' \n\nНедавні журнали служб: {logs}", "service_reloaded": "Служба '{service}' перезавантажена", - "service_reload_failed": "Не вдалося перезавантажити службу '{service}' Останні журнали служби: {logs}", + "service_reload_failed": "Не вдалося перезавантажити службу '{service}'\n\nОстанні журнали служби: {logs}", "service_removed": "Служба '{service}' вилучена", "service_remove_failed": "Не вдалося видалити службу '{service}'", - "service_regen_conf_is_deprecated": "'Yunohost service regen-conf' застарів! Будь ласка, використовуйте 'yunohost tools regen-conf' замість цього.", - "service_enabled": "Служба '{service}' тепер буде автоматично запускатися при завантаженні системи.", - "service_enable_failed": "Неможливо змусити службу '{service}' автоматично запускатися при завантаженні. Недавні журнали служб: {logs}", - "service_disabled": "Служба '{service}' більше не буде запускатися при завантаженні системи.", - "service_disable_failed": "Неможливо змусити службу '{service} \"не запускатися при завантаженні. Останні журнали служби: {logs}", - "service_description_yunohost-firewall": "Управляє відкритими і закритими портами підключення до сервісів", - "service_description_yunohost-api": "Управляє взаємодією між веб-інтерфейсом YunoHost і системою", - "service_description_ssh": "Дозволяє віддалено підключатися до сервера через термінал (протокол SSH)", - "service_description_slapd": "Зберігає користувачів, домени і пов'язану з ними інформацію", - "service_description_rspamd": "Фільтрує спам і інші функції, пов'язані з електронною поштою", + "service_regen_conf_is_deprecated": "'yunohost service regen-conf' застарів! Будь ласка, використовуйте 'yunohost tools regen-conf' замість цього.", + "service_enabled": "Служба '{service}' тепер буде автоматично запускатися під час завантаження системи.", + "service_enable_failed": "Неможливо змусити службу '{service}' автоматично запускатися під час завантаження.\n\nНедавні журнали служби: {logs}", + "service_disabled": "Служба '{service}' більше не буде запускатися під час завантаження системи.", + "service_disable_failed": "Неможливо змусити службу '{service}' не запускатися під час завантаження.\n\nОстанні журнали служби: {logs}", + "service_description_yunohost-firewall": "Управляє відкритими і закритими портами з'єднання зі службами", + "service_description_yunohost-api": "Управляє взаємодією між вебінтерфейсом YunoHost і системою", + "service_description_ssh": "Дозволяє віддалено під'єднуватися до сервера через термінал (протокол SSH)", + "service_description_slapd": "Зберігає користувачів, домени і пов'язані з ними дані", + "service_description_rspamd": "Фільтри спаму і інші функції, пов'язані з е-поштою", "service_description_redis-server": "Спеціалізована база даних, яка використовується для швидкого доступу до даних, черги завдань і зв'язку між програмами", - "service_description_postfix": "Використовується для відправки та отримання електронної пошти", - "service_description_php7.3-fpm": "Запускає програми, написані на мові програмування PHP, за допомогою NGINX", - "service_description_nginx": "Обслуговує або надає доступ до всіх веб-сайтів, розміщених на вашому сервері", + "service_description_postfix": "Використовується для надсилання та отримання е-пошти", + "service_description_php7.3-fpm": "Запускає застосунки, написані мовою програмування PHP за допомогою NGINX", + "service_description_nginx": "Обслуговує або надає доступ до всіх вебсайтів, розміщених на вашому сервері", "service_description_mysql": "Зберігає дані застосунків (база даних SQL)", - "service_description_metronome": "Служба захисту миттєвого обміну повідомленнями XMPP", - "service_description_fail2ban": "Захист від перебору та інших видів атак з Інтернету", + "service_description_metronome": "Управління обліковими записами миттєвих повідомлень XMPP", + "service_description_fail2ban": "Захист від перебирання (брутфорсу) та інших видів атак з Інтернету", "service_description_dovecot": "Дозволяє поштовим клієнтам отримувати доступ до електронної пошти (через IMAP і POP3)", - "service_description_dnsmasq": "Обробляє дозвіл доменних імен (DNS)", + "service_description_dnsmasq": "Обробляє роздільність доменних імен (DNS)", "service_description_yunomdns": "Дозволяє вам отримати доступ до вашого сервера, використовуючи 'yunohost.local' у вашій локальній мережі", "service_cmd_exec_failed": "Не вдалося виконати команду '{command}'", - "service_already_stopped": "Служба '{service}' вже зупинена", - "service_already_started": "Служба '{service}' вже запущена", - "service_added": "Служба '{service}' була додана", + "service_already_stopped": "Службу '{service}' вже зупинено", + "service_already_started": "Службу '{service}' вже запущено", + "service_added": "Службу '{service}' було додано", "service_add_failed": "Не вдалося додати службу '{service}'", - "server_reboot_confirm": "Сервер негайно перезавантажиться, ви впевнені? [{answers}]", - "server_reboot": "сервер перезавантажиться", - "server_shutdown_confirm": "Сервер буде негайно виключений, ви впевнені? [{answers}].", - "server_shutdown": "сервер вимкнеться", - "root_password_replaced_by_admin_password": "Ваш кореневої пароль був замінений на пароль адміністратора.", - "root_password_desynchronized": "Пароль адміністратора був змінений, але YunoHost не зміг поширити це на пароль root!", - "restore_system_part_failed": "Не вдалося відновити системну частину '{part}'.", - "restore_running_hooks": "Запуск хуков відновлення…", - "restore_running_app_script": "Відновлення програми \"{app} '…", + "server_reboot_confirm": "Сервер буде негайно перезавантажено, ви впевнені? [{answers}]", + "server_reboot": "Сервер буде перезавантажено", + "server_shutdown_confirm": "Сервер буде негайно вимкнено, ви впевнені? [{answers}]", + "server_shutdown": "Сервер буде вимкнено", + "root_password_replaced_by_admin_password": "Ваш кореневий (root) пароль було замінено на пароль адміністратора.", + "root_password_desynchronized": "Пароль адміністратора було змінено, але YunoHost не зміг поширити це на кореневий (root) пароль!", + "restore_system_part_failed": "Не вдалося відновити системний розділ '{part}'", + "restore_running_hooks": "Запуск хуків відновлення…", + "restore_running_app_script": "Відновлення застосунку '{app}'…", "restore_removing_tmp_dir_failed": "Неможливо видалити старий тимчасовий каталог", "restore_nothings_done": "Нічого не було відновлено", - "restore_not_enough_disk_space": "Недостатньо місця (простір: {free_space: d} B, необхідний простір: {needed_space: d} B, маржа безпеки: {margin: d} B)", - "restore_may_be_not_enough_disk_space": "Схоже, у вашій системі недостатньо місця (вільного: {free_space: d} B, необхідний простір: {needed_space: d} B, запас міцності: {margin: d} B)", - "restore_hook_unavailable": "Сценарій відновлення для '{part}' недоступним у вашій системі і в архіві його теж немає", + "restore_not_enough_disk_space": "Недостатньо місця (простір: {free_space} Б, необхідний простір: {needed_space} Б, межа безпеки: {margin: d} Б)", + "restore_may_be_not_enough_disk_space": "Схоже, у вашій системі недостатньо місця (вільно: {free_space} Б, необхідний простір: {needed_space} Б, межа безпеки: {margin: d} Б)", + "restore_hook_unavailable": "Скрипт відновлення для '{part}' недоступний у вашій системі і в архіві його теж немає", "restore_failed": "Не вдалося відновити систему", - "restore_extracting": "Витяг необхідних файлів з архіву…", - "restore_confirm_yunohost_installed": "Ви дійсно хочете відновити вже встановлену систему? [{answers}].", - "restore_complete": "відновлення завершено", + "restore_extracting": "Витягнення необхідних файлів з архіву…", + "restore_confirm_yunohost_installed": "Ви дійсно хочете відновити вже встановлену систему? [{answers}]", + "restore_complete": "Відновлення завершено", "restore_cleaning_failed": "Не вдалося очистити тимчасовий каталог відновлення", - "restore_backup_too_old": "Цей архів резервних копій не може бути відновлений, бо він отриманий з дуже старою версією YunoHost.", + "restore_backup_too_old": "Цей архів резервних копій не може бути відновлений, бо він отриманий з дуже старої версії YunoHost.", "restore_already_installed_apps": "Наступні програми не можуть бути відновлені, тому що вони вже встановлені: {apps}", - "restore_already_installed_app": "Застосунок з ідентифікатором \"{app} 'вже встановлено", - "regex_with_only_domain": "Ви не можете використовувати regex для домену, тільки для шляху.", - "regex_incompatible_with_tile": "/! \\ Packagers! Дозвіл '{permission}' має значення show_tile 'true', тому ви не можете визначити regex URL в якості основного URL.", + "restore_already_installed_app": "Застосунок з ID \"{app} 'вже встановлено", + "regex_with_only_domain": "Ви не можете використовувати regex для домену, тільки для шляху", + "regex_incompatible_with_tile": "/! \\ Packagers! Дозвіл '{permission}' має значення show_tile 'true', тому ви не можете визначити regex URL в якості основної URL", "regenconf_need_to_explicitly_specify_ssh": "Конфігурація ssh була змінена вручну, але вам потрібно явно вказати категорію 'ssh' з --force, щоб застосувати зміни.", "regenconf_pending_applying": "Застосування очікує конфігурації для категорії '{category}'...", "regenconf_failed": "Не вдалося відновити конфігурацію для категорії (категорій): {categories}", @@ -98,276 +98,276 @@ "regenconf_updated": "Конфігурація оновлена для категорії '{category}'", "regenconf_up_to_date": "Конфігурація вже оновлена для категорії '{category}'", "regenconf_now_managed_by_yunohost": "Конфігураційний файл '{conf}' тепер управляється YunoHost (категорія {category}).", - "regenconf_file_updated": "Конфігураційний файл '{conf}' оновлений", - "regenconf_file_removed": "Конфігураційний файл '{conf}' видалений", + "regenconf_file_updated": "Конфігураційний файл '{conf}' оновлено", + "regenconf_file_removed": "Конфігураційний файл '{conf}' видалено", "regenconf_file_remove_failed": "Неможливо видалити файл конфігурації '{conf}'", - "regenconf_file_manually_removed": "Конфігураційний файл '{conf}' був видалений вручну і не буде створено", - "regenconf_file_manually_modified": "Конфігураційний файл '{conf}' був змінений вручну і не буде оновлено", - "regenconf_file_kept_back": "Конфігураційний файл '{conf}' повинен був бути вилучений regen-conf (категорія {category}), але був збережений.", + "regenconf_file_manually_removed": "Конфігураційний файл '{conf}' було видалено вручну і не буде створено", + "regenconf_file_manually_modified": "Конфігураційний файл '{conf}' було змінено вручну і не буде оновлено", + "regenconf_file_kept_back": "Очікувалося видалення конфігураційного файлу '{conf}' за допомогою regen-conf (категорія {category}), але його було збережено.", "regenconf_file_copy_failed": "Не вдалося скопіювати новий файл конфігурації '{new}' в '{conf}'", - "regenconf_file_backed_up": "Конфігураційний файл '{conf}' збережений в '{backup}'", - "postinstall_low_rootfsspace": "Загальна площа кореневої файлової системи становить менше 10 ГБ, що викликає занепокоєння! Швидше за все, дисковий простір закінчиться дуже швидко! Рекомендується мати не менше 16 ГБ для кореневої файлової системи. Якщо ви хочете встановити YunoHost, незважаючи на це попередження, повторно запустіть постінсталляцію з параметром --force-diskspace", - "port_already_opened": "Порт {port: d} вже відкритий для з'єднань {ip_version}.", - "port_already_closed": "Порт {port: d} вже закритий для з'єднань {ip_version}.", - "permission_require_account": "Дозвіл {permission} має сенс тільки для користувачів, що мають обліковий запис, і тому не може бути включено для відвідувачів.", - "permission_protected": "Дозвіл {permission} захищено. Ви не можете додавати або видаляти групу відвідувачів в/з цього дозволу.", + "regenconf_file_backed_up": "Конфігураційний файл '{conf}' збережено в '{backup}'", + "postinstall_low_rootfsspace": "Загальне місце кореневої файлової системи становить менше 10 ГБ, що викликає занепокоєння! Швидше за все, дисковий простір закінчиться дуже скоро! Рекомендовано мати не менше 16 ГБ для кореневої файлової системи. Якщо ви хочете встановити YunoHost попри це попередження, повторно запустіть післявстановлення з параметром --force-diskspace", + "port_already_opened": "Порт {port} вже відкрито для з'єднань {ip_version}", + "port_already_closed": "Порт {port} вже закрито для з'єднань {ip_version}", + "permission_require_account": "Дозвіл {permission} має зміст тільки для користувачів, що мають обліковий запис, і тому не може бути увімкненим для відвідувачів.", + "permission_protected": "Дозвіл {permission} захищено. Ви не можете додавати або вилучати групу відвідувачів до/з цього дозволу.", "permission_updated": "Дозвіл '{permission}' оновлено", "permission_update_failed": "Не вдалося оновити дозвіл '{permission}': {error}", - "permission_not_found": "Дозвіл '{permission}', не знайдено", + "permission_not_found": "Дозвіл '{permission}' не знайдено", "permission_deletion_failed": "Не вдалося видалити дозвіл '{permission}': {error}", "permission_deleted": "Дозвіл '{permission}' видалено", "permission_cant_add_to_all_users": "Дозвіл {permission} не може бути додано всім користувачам.", "permission_currently_allowed_for_all_users": "Наразі цей дозвіл надається всім користувачам на додачу до інших груп. Імовірно, вам потрібно або видалити дозвіл 'all_users', або видалити інші групи, яким його зараз надано.", "permission_creation_failed": "Не вдалося створити дозвіл '{permission}': {error}", "permission_created": "Дозвіл '{permission}' створено", - "permission_cannot_remove_main": "Видалення основного дозволу заборонено", - "permission_already_up_to_date": "Дозвіл не було оновлено, тому що запити на додавання/видалення вже відповідають поточному стану.", + "permission_cannot_remove_main": "Вилучення основного дозволу заборонене", + "permission_already_up_to_date": "Дозвіл не було оновлено, тому що запити на додавання/вилучення вже відповідають поточному стану.", "permission_already_exist": "Дозвіл '{permission}' вже існує", - "permission_already_disallowed": "Група '{group}' вже має дозвіл \"{permission} 'відключено", - "permission_already_allowed": "Для гурту \"{group} 'вже включено дозвіл' {permission} '", + "permission_already_disallowed": "Група '{group}' вже має вимкнений дозвіл '{permission}'", + "permission_already_allowed": "Група '{group}' вже має увімкнений дозвіл '{permission}'", "pattern_password_app": "На жаль, паролі не можуть містити такі символи: {forbidden_chars}", - "pattern_username": "Повинен складатися тільки з букв і цифр в нижньому регістрі і символів підкреслення.", - "pattern_positive_number": "Повинно бути позитивним числом", - "pattern_port_or_range": "Повинен бути дійсний номер порту (наприклад, 0-65535) або діапазон портів (наприклад, 100: 200).", - "pattern_password": "Повинен бути довжиною не менше 3 символів", - "pattern_mailbox_quota": "Повинен бути розмір з суфіксом b/k/M/G/T або 0, щоб не мати квоти.", - "pattern_lastname": "Повинна бути дійсне прізвище", - "pattern_firstname": "Повинно бути дійсне ім'я", - "pattern_email": "Повинен бути дійсною адресою електронної пошти, без символу '+' (наприклад, someone@example.com ).", - "pattern_email_forward": "Повинен бути дійсну адресу електронної пошти, символ '+' приймається (наприклад, someone+tag@example.com )", - "pattern_domain": "Повинно бути дійсне доменне ім'я (наприклад, my-domain.org)", - "pattern_backup_archive_name": "Повинно бути правильне ім'я файлу, що містить не більше 30 символів, тільки букви і цифри символи і символ -_.", - "password_too_simple_4": "Пароль повинен бути довжиною не менше 12 символів і містити цифру, верхні, нижні і спеціальні символи.", - "password_too_simple_3": "Пароль повинен бути довжиною не менше 8 символів і містити цифру, верхні, нижні і спеціальні символи", - "password_too_simple_2": "Пароль повинен складатися не менше ніж з 8 символів і містити цифру, верхній і нижній символи", - "password_too_simple_1": "Пароль повинен складатися не менше ніж з 8 символів", - "password_listed": "Цей пароль входить в число найбільш часто використовуваних паролів в світі. Будь ласка, виберіть щось більш унікальне.", + "pattern_username": "Має складатися тільки з букв і цифр в нижньому регістрі і символів підкреслення", + "pattern_positive_number": "Має бути додатним числом", + "pattern_port_or_range": "Має бути припустимий номер порту (наприклад, 0-65535) або діапазон портів (наприклад, 100:200)", + "pattern_password": "Має бути довжиною не менше 3 символів", + "pattern_mailbox_quota": "Має бути розмір з суфіксом b/k/M/G/T або 0, щоб не мати квоти", + "pattern_lastname": "Має бути припустиме прізвище", + "pattern_firstname": "Має бути припустиме ім'я", + "pattern_email": "Має бути припустима адреса е-пошти, без символу '+' (наприклад, someone@example.com)", + "pattern_email_forward": "Має бути припустима адреса е-пошти, символ '+' приймається (наприклад, someone+tag@example.com)", + "pattern_domain": "Має бути припустиме доменне ім'я (наприклад, my-domain.org)", + "pattern_backup_archive_name": "Має бути правильна назва файлу, що містить не більше 30 символів, тільки букви і цифри і символи -_", + "password_too_simple_4": "Пароль має складатися не менше ніж з 12 символів і містити цифри, великі та малі символи і спеціальні символи", + "password_too_simple_3": "Пароль має складатися не менше ніж з 8 символів і містити цифри, великі та малі символи і спеціальні символи", + "password_too_simple_2": "Пароль має складатися не менше ніж з 8 символів і містити цифри, великі та малі символи", + "password_too_simple_1": "Пароль має складатися не менше ніж з 8 символів", + "password_listed": "Цей пароль входить в число найбільш часто використовуваних паролів у світі. Будь ласка, виберіть щось неповторюваніше.", "packages_upgrade_failed": "Не вдалося оновити всі пакети", - "operation_interrupted": "Операція була перервана вручну?", - "invalid_number": "Повинно бути число", - "not_enough_disk_space": "Недостатньо вільного місця на \"{path} '.", - "migrations_to_be_ran_manually": "Міграція {id} повинна бути запущена вручну. Будь ласка, перейдіть в розділ Інструменти → Міграції на сторінці веб-адміністратора або виконайте команду `yunohost tools migrations run`.", - "migrations_success_forward": "Міграція {id} завершена", - "migrations_skip_migration": "Пропуск міграції {id}...", + "operation_interrupted": "Операція була вручну перервана?", + "invalid_number": "Має бути числом", + "not_enough_disk_space": "Недостатньо вільного місця на '{path}'", + "migrations_to_be_ran_manually": "Міграція {id} повинна бути запущена вручну. Будь ласка, перейдіть в розділ Засоби → Міграції на сторінці вебадміністратора або виконайте команду `yunohost tools migrations run`.", + "migrations_success_forward": "Міграцію {id} завершено", + "migrations_skip_migration": "Пропускання міграції {id}...", "migrations_running_forward": "Виконання міграції {id}...", - "migrations_pending_cant_rerun": "Ці міграції ще не завершені, тому не можуть бути запущені знову: {ids}", - "migrations_not_pending_cant_skip": "Ці міграції не очікують виконання, тому не можуть бути пропущені: {ids}", - "migrations_no_such_migration": "Не існує міграції під назвою '{id}'.", + "migrations_pending_cant_rerun": "Наступні міграції ще не завершені, тому не можуть бути запущені знову: {ids}", + "migrations_not_pending_cant_skip": "Наступні міграції не очікують виконання, тому не можуть бути пропущені: {ids}", + "migrations_no_such_migration": "Не існує міграції під назвою '{id}'", "migrations_no_migrations_to_run": "Немає міграцій для запуску", - "migrations_need_to_accept_disclaimer": "Щоб запустити міграцію {id}, ви повинні прийняти наступний відмова від відповідальності: --- {disclaimer} --- Якщо ви згодні запустити міграцію, будь ласка, повторіть команду з опцією '--accept-disclaimer'.", - "migrations_must_provide_explicit_targets": "Ви повинні вказати явні цілі при використанні '--skip' або '--force-rerun'.", - "migrations_migration_has_failed": "Міграція {id} не завершена, переривається. Помилка: {exception}.", + "migrations_need_to_accept_disclaimer": "Щоб запустити міграцію {id}, ви повинні прийняти наступну відмову від відповідальності:\n---\n{disclaimer}\n---\nЯкщо ви згодні запустити міграцію, будь ласка, повторіть команду з опцією '--accept-disclaimer'.", + "migrations_must_provide_explicit_targets": "Ви повинні вказати явні цілі при використанні '--skip' або '--force-rerun'", + "migrations_migration_has_failed": "Міграція {id} не завершена, перериваємо. Помилка: {exception}", "migrations_loading_migration": "Завантаження міграції {id}...", "migrations_list_conflict_pending_done": "Ви не можете одночасно використовувати '--previous' і '--done'.", - "migrations_exclusive_options": "'--Auto', '--skip' і '--force-rerun' є взаємовиключними опціями.", + "migrations_exclusive_options": "'--auto', '--skip', і '--force-rerun' є взаємовиключними опціями.", "migrations_failed_to_load_migration": "Не вдалося завантажити міграцію {id}: {error}", "migrations_dependencies_not_satisfied": "Запустіть ці міграції: '{dependencies_id}', перед міграцією {id}.", - "migrations_cant_reach_migration_file": "Не вдалося отримати доступ до файлів міграцій по шляху '% s'.", - "migrations_already_ran": "Ці міграції вже виконані: {ids}", + "migrations_cant_reach_migration_file": "Не вдалося отримати доступ до файлів міграцій за шляхом '%s'", + "migrations_already_ran": "Наступні міграції вже виконано: {ids}", "migration_0019_slapd_config_will_be_overwritten": "Схоже, що ви вручну відредагували конфігурацію slapd. Для цього критичного переходу YunoHost повинен примусово оновити конфігурацію slapd. Оригінальні файли будуть збережені в {conf_backup_folder}.", - "migration_0019_add_new_attributes_in_ldap": "Додавання нових атрибутів для дозволів в базі даних LDAP", - "migration_0018_failed_to_reset_legacy_rules": "Не вдалося скинути застарілі правила iptables: {error}", - "migration_0018_failed_to_migrate_iptables_rules": "Не вдалося перенести застарілі правила iptables в nftables: {error}", + "migration_0019_add_new_attributes_in_ldap": "Додавання нових атрибутів для дозволів у базі даних LDAP", + "migration_0018_failed_to_reset_legacy_rules": "Не вдалося скинути спадкові правила iptables: {error}", + "migration_0018_failed_to_migrate_iptables_rules": "Не вдалося перенести спадкові правила iptables в nftables: {error}", "migration_0017_not_enough_space": "Звільніть достатньо місця в {path} для запуску міграції.", - "migration_0017_postgresql_11_not_installed": "PostgreSQL 9.6 встановлений, але не postgresql 11‽ Можливо, у вашій системі відбулося щось дивне: (...", - "migration_0017_postgresql_96_not_installed": "PostgreSQL ні встановлено у вашій системі. Нічого не потрібно робити.", + "migration_0017_postgresql_11_not_installed": "PostgreSQL 9.6 встановлено, але не PostgreSQL 11‽ Можливо, у вашій системі відбулося щось дивне :(...", + "migration_0017_postgresql_96_not_installed": "PostgreSQL не встановлено у вашій системі. Нічого не потрібно робити.", "migration_0015_weak_certs": "Було виявлено, що такі сертифікати все ще використовують слабкі алгоритми підпису і повинні бути оновлені для сумісності з наступною версією nginx: {certs}", "migration_0015_cleaning_up": "Очищення кеш-пам'яті і пакетів, які більше не потрібні...", - "migration_0015_specific_upgrade": "Початок поновлення системних пакетів, які повинні бути оновлені незалежно...", - "migration_0015_modified_files": "Зверніть увагу, що такі файли були змінені вручну і можуть бути перезаписані після поновлення: {manually_modified_files}.", - "migration_0015_problematic_apps_warning": "Зверніть увагу, що були виявлені наступні, можливо, проблемні встановлені додатки. Схоже, що вони не були встановлені з каталогу застосунків YunoHost або не зазначені як \"робочі\". Отже, не можна гарантувати, що вони будуть працювати після оновлення: {problematic_apps}.", - "migration_0015_general_warning": "Будь ласка, зверніть увагу, що ця міграція є делікатною операцією. Команда YunoHost зробила все можливе, щоб перевірити і протестувати її, але міграція все ще може порушити частина системи або її застосунків. Тому рекомендується: - Виконати резервне копіювання всіх важливих даних або застосунків. Більш детальна інформація на сайті https://yunohost.org/backup; - Наберіться терпіння після запуску міграції: В залежності від вашого підключення до Інтернету і апаратного забезпечення, оновлення може зайняти до декількох годин.", + "migration_0015_specific_upgrade": "Початок оновлення системних пакетів, які повинні бути оновлені незалежно...", + "migration_0015_modified_files": "Зверніть увагу, що такі файли були змінені вручну і можуть бути перезаписані після оновлення: {manually_modified_files}", + "migration_0015_problematic_apps_warning": "Зверніть увагу, що були виявлені наступні, можливо, проблемні встановлені застосунки. Схоже, що вони не були встановлені з каталогу застосунків YunoHost або не зазначені як \"робочі\". Отже, не можна гарантувати, що вони будуть працювати після оновлення: {problematic_apps}", + "migration_0015_general_warning": "Будь ласка, зверніть увагу, що ця міграція є делікатною операцією. Команда YunoHost зробила все можливе, щоб перевірити і протестувати її, але міграція все ще може порушити частина системи або її застосунків.\n\nТому рекомендовано:\n - Виконати резервне копіювання всіх важливих даних або застосунків. Подробиці на сайті https://yunohost.org/backup; \n - Наберіться терпіння після запуску міграції: В залежності від вашого з'єднання з Інтернетом і апаратного забезпечення, оновлення може зайняти до декількох годин.", "migration_0015_system_not_fully_up_to_date": "Ваша система не повністю оновлена. Будь ласка, виконайте регулярне оновлення перед запуском міграції на Buster.", - "migration_0015_not_enough_free_space": "Вільного місця в/var/досить мало! У вас повинно бути не менше 1 ГБ вільного місця, щоб запустити цю міграцію.", + "migration_0015_not_enough_free_space": "Вільного місця в /var/ досить мало! У вас повинно бути не менше 1 ГБ вільного місця, щоб запустити цю міграцію.", "migration_0015_not_stretch": "Поточний дистрибутив Debian не є Stretch!", - "migration_0015_yunohost_upgrade": "Починаємо оновлення ядра YunoHost...", - "migration_0015_still_on_stretch_after_main_upgrade": "Щось пішло не так під час основного поновлення, система, схоже, все ще знаходиться на Debian Stretch", - "migration_0015_main_upgrade": "Початок основного поновлення...", + "migration_0015_yunohost_upgrade": "Початок оновлення ядра YunoHost...", + "migration_0015_still_on_stretch_after_main_upgrade": "Щось пішло не так під час основного оновлення, система, схоже, все ще знаходиться на Debian Stretch", + "migration_0015_main_upgrade": "Початок основного оновлення...", "migration_0015_patching_sources_list": "Виправлення sources.lists...", "migration_0015_start": "Початок міграції на Buster", "migration_update_LDAP_schema": "Оновлення схеми LDAP...", "migration_ldap_rollback_success": "Система відкотилася.", - "migration_ldap_migration_failed_trying_to_rollback": "Не вдалося виконати міграцію... спроба відкату системи.", - "migration_ldap_can_not_backup_before_migration": "Не вдалося завершити резервне копіювання системи перед невдалої міграцією. Помилка: {error}", - "migration_ldap_backup_before_migration": "Створення резервної копії бази даних LDAP і установки застосунків перед фактичної міграцією.", - "migration_description_0020_ssh_sftp_permissions": "Додайте підтримку дозволів SSH і SFTP", + "migration_ldap_migration_failed_trying_to_rollback": "Не вдалося виконати міграцію... Пробуємо відкотити систему.", + "migration_ldap_can_not_backup_before_migration": "Не вдалося завершити резервне копіювання системи перед невдалою міграцією. Помилка: {error}", + "migration_ldap_backup_before_migration": "Створення резервної копії бази даних LDAP і налаштування застосунків перед фактичною міграцією.", + "migration_description_0020_ssh_sftp_permissions": "Додавання підтримки дозволів SSH і SFTP", "migration_description_0019_extend_permissions_features": "Розширення/переробка системи управління дозволами застосунків", "migration_description_0018_xtable_to_nftable": "Перенесення старих правил мережевого трафіку в нову систему nftable", "migration_description_0017_postgresql_9p6_to_11": "Перенесення баз даних з PostgreSQL 9.6 на 11", - "migration_description_0016_php70_to_php73_pools": "Перенесіть php7.0-fpm 'pool' conf файли на php7.3", + "migration_description_0016_php70_to_php73_pools": "Перенесення php7.0-fpm 'pool' conf файлів на php7.3", "migration_description_0015_migrate_to_buster": "Оновлення системи до Debian Buster і YunoHost 4.x", - "migrating_legacy_permission_settings": "Перенесення застарілих налаштувань дозволів...", - "main_domain_changed": "Основний домен був змінений", + "migrating_legacy_permission_settings": "Перенесення спадкових налаштувань дозволів...", + "main_domain_changed": "Основний домен було змінено", "main_domain_change_failed": "Неможливо змінити основний домен", - "mail_unavailable": "Ця електронна адреса зарезервований і буде автоматично виділено найпершого користувачеві", - "mailbox_used_space_dovecot_down": "Поштова служба Dovecot повинна бути запущена, якщо ви хочете отримати використане місце в поштовій скриньці.", - "mailbox_disabled": "Електронна пошта відключена для користувача {user}", + "mail_unavailable": "Ця е-пошта зарезервована і буде автоматично виділена найпершому користувачеві", + "mailbox_used_space_dovecot_down": "Поштова служба Dovecot повинна бути запущена, якщо ви хочете отримати використане місце в поштовій скриньці", + "mailbox_disabled": "Е-пошта вимкнена для користувача {user}", "mail_forward_remove_failed": "Не вдалося видалити переадресацію електронної пошти '{mail}'", - "mail_domain_unknown": "Неправильну адресу електронної пошти для домену '{domain}'. Будь ласка, використовуйте домен, адмініструється цим сервером.", - "mail_alias_remove_failed": "Не вдалося видалити псевдонім електронної пошти '{mail}'", - "log_tools_reboot": "перезавантажити сервер", - "log_tools_shutdown": "Вимкнути ваш сервер", + "mail_domain_unknown": "Неправильна адреса е-пошти для домену '{domain}'. Будь ласка, використовуйте домен, що адмініструється цим сервером.", + "mail_alias_remove_failed": "Не вдалося видалити аліас електронної пошти '{mail}'", + "log_tools_reboot": "Перезавантаження сервера", + "log_tools_shutdown": "Вимикання сервера", "log_tools_upgrade": "Оновлення системних пакетів", - "log_tools_postinstall": "Постінсталляція вашого сервера YunoHost", - "log_tools_migrations_migrate_forward": "запустіть міграції", - "log_domain_main_domain": "Зробити '{}' основним доменом", - "log_user_permission_reset": "Скинути дозвіл \"{} '", - "log_user_permission_update": "Оновити доступи для дозволу '{}'", - "log_user_update": "Оновити інформацію для користувача '{}'", - "log_user_group_update": "Оновити групу '{}'", - "log_user_group_delete": "Видалити групу \"{} '", - "log_user_group_create": "Створити групу '{}'", - "log_user_delete": "Видалити користувача '{}'", - "log_user_create": "Додати користувача '{}'", - "log_regen_conf": "Регенерувати системні конфігурації '{}'", - "log_letsencrypt_cert_renew": "Оновити сертифікат Let's Encrypt на домені '{}'", - "log_selfsigned_cert_install": "Встановити самоподпісанний сертифікат на домені '{}'", - "log_permission_url": "Оновити URL, пов'язаний з дозволом '{}'", - "log_permission_delete": "Видалити дозвіл \"{} '", - "log_permission_create": "Створити дозвіл \"{} '", - "log_letsencrypt_cert_install": "Встановіть сертифікат Let's Encrypt на домен '{}'", - "log_dyndns_update": "Оновити IP, пов'язаний з вашим піддоменом YunoHost '{}'", + "log_tools_postinstall": "Післявстановлення сервера YunoHost", + "log_tools_migrations_migrate_forward": "Запущено міграції", + "log_domain_main_domain": "Зроблено '{}' основним доменом", + "log_user_permission_reset": "Скинуто дозвіл \"{} '", + "log_user_permission_update": "Оновлено доступи для дозволу '{}'", + "log_user_update": "Оновлено відомості для користувача '{}'", + "log_user_group_update": "Оновлено групу '{}'", + "log_user_group_delete": "Видалено групу \"{} '", + "log_user_group_create": "Створено групу '{}'", + "log_user_delete": "Видалення користувача '{}'", + "log_user_create": "Додавання користувача '{}'", + "log_regen_conf": "Перестворення системних конфігурацій '{}'", + "log_letsencrypt_cert_renew": "Оновлення сертифікату Let's Encrypt на домені '{}'", + "log_selfsigned_cert_install": "Установлення самопідписаного сертифікату на домені '{}'", + "log_permission_url": "Оновлення URL, пов'язаногл з дозволом '{}'", + "log_permission_delete": "Видалення дозволу '{}'", + "log_permission_create": "Створення дозволу '{}'", + "log_letsencrypt_cert_install": "Установлення сертифікату Let's Encrypt на домен '{}'", + "log_dyndns_update": "Оновлення IP, пов'язаного з вашим піддоменом YunoHost '{}'", "log_dyndns_subscribe": "Підписка на піддомен YunoHost '{}'", - "log_domain_remove": "Видалити домен '{}' з конфігурації системи", - "log_domain_add": "Додати домен '{}' в конфігурацію системи", - "log_remove_on_failed_install": "Видалити '{}' після невдалої установки", - "log_remove_on_failed_restore": "Видалити '{}' після невдалого відновлення з резервного архіву", + "log_domain_remove": "Вилучення домену '{}' з конфігурації системи", + "log_domain_add": "Додавання домену '{}' в конфігурацію системи", + "log_remove_on_failed_install": "Вилучення '{}' після невдалого встановлення", + "log_remove_on_failed_restore": "Вилучення '{}' після невдалого відновлення з резервного архіву", "log_backup_restore_app": "Відновлення '{}' з архіву резервних копій", "log_backup_restore_system": "Відновлення системи з резервного архіву", "log_backup_create": "Створення резервного архіву", "log_available_on_yunopaste": "Цей журнал тепер доступний за посиланням {url}", - "log_app_config_apply": "Застосувати конфігурацію до додатка \"{} '", - "log_app_config_show_panel": "Показати панель конфігурації програми \"{} '", - "log_app_action_run": "Активації дії додатка \"{} '", - "log_app_makedefault": "Зробити '{}' додатком за замовчуванням", - "log_app_upgrade": "Оновити застосунок '{}'", - "log_app_remove": "Для видалення програми '{}'", - "log_app_install": "Встановіть застосунок '{}'", - "log_app_change_url": "Змініть URL-адресу додатка \"{} '", + "log_app_config_apply": "Застосування конфігурації до застосунку '{}'", + "log_app_config_show_panel": "Показ панелі конфігурації застосунку '{}'", + "log_app_action_run": "Запуск дії застосунку \"{} '", + "log_app_makedefault": "Застосунок '{}' зроблено типовим", + "log_app_upgrade": "Оновлення застосунку '{}'", + "log_app_remove": "Вилучення застосунку '{}'", + "log_app_install": "Установлення застосунку '{}'", + "log_app_change_url": "Змінення URL-адреси застосунку \"{} '", "log_operation_unit_unclosed_properly": "Блок операцій не був закритий належним чином", - "log_does_exists": "Немає журналу операцій з ім'ям '{log}', використовуйте 'yunohost log list', щоб подивитися всі публічні журнали операцій", - "log_help_to_get_failed_log": "Операція '{desc}' не може бути завершена. Будь ласка, поділіться повним журналом цієї операції, використовуючи команду 'yunohost log share {name}', щоб отримати допомогу.", - "log_link_to_failed_log": "Не вдалося завершити операцію '{desc}'. Будь ласка, надайте повний журнал цієї операції, натиснувши тут , щоб отримати допомогу.", - "log_help_to_get_log": "Щоб переглянути журнал операції '{desc}', використовуйте команду 'yunohost log show {name} {name}'.", - "log_link_to_log": "Повний журнал цієї операції: ' {desc} '", - "log_corrupted_md_file": "Файл метаданих YAML, пов'язаний з журналами, пошкоджений: '{md_file} Помилка: {error}'", - "iptables_unavailable": "Ви не можете грати з iptables тут. Ви перебуваєте або в контейнері, або ваше ядро не підтримує його.", - "ip6tables_unavailable": "Ви не можете грати з ip6tables тут. Ви перебуваєте або в контейнері, або ваше ядро не підтримує його.", - "invalid_regex": "Невірний regex: '{regex}'", - "installation_complete": "установка завершена", - "hook_name_unknown": "Невідоме ім'я хука '{name}'", - "hook_list_by_invalid": "Це властивість не може бути використано для перерахування хуков", + "log_does_exists": "Немає журналу операцій з назвою '{log}', використовуйте 'yunohost log list', щоб подивитися всі доступні журнали операцій", + "log_help_to_get_failed_log": "Операція '{desc}' не може бути завершена. Будь ласка, поділіться повним журналом цієї операції, використовуючи команду 'yunohost log share {name}', щоб отримати допомогу", + "log_link_to_failed_log": "Не вдалося завершити операцію '{desc}'. Будь ласка, надайте повний журнал цієї операції, натиснувши тут, щоб отримати допомогу", + "log_help_to_get_log": "Щоб переглянути журнал операції '{desc}', використовуйте команду 'yunohost log show {name}{name}'", + "log_link_to_log": "Повний журнал цієї операції: '{desc}'", + "log_corrupted_md_file": "Файл метаданих YAML, пов'язаний з журналами, пошкоджено: '{md_file}\nПомилка: {error}'", + "iptables_unavailable": "Ви не можете грати з iptables тут. Ви перебуваєте або в контейнері, або ваше ядро не підтримує його", + "ip6tables_unavailable": "Ви не можете грати з ip6tables тут. Ви перебуваєте або в контейнері, або ваше ядро не підтримує його", + "invalid_regex": "Неприпустимий regex: '{regex}'", + "installation_complete": "Установлення завершено", + "hook_name_unknown": "Невідома назва хука '{name}'", + "hook_list_by_invalid": "Цю властивість не може бути використано для перерахування хуків (гачків)", "hook_json_return_error": "Не вдалося розпізнати повернення з хука {path}. Помилка: {msg}. Необроблений контент: {raw_content}", "hook_exec_not_terminated": "Скрипт не завершився належним чином: {path}", "hook_exec_failed": "Не вдалося запустити скрипт: {path}", "group_user_not_in_group": "Користувач {user} не входить в групу {group}", "group_user_already_in_group": "Користувач {user} вже в групі {group}", "group_update_failed": "Не вдалося оновити групу '{group}': {error}", - "group_updated": "Група '{group}' оновлена", - "group_unknown": "Група '{group}' невідома.", + "group_updated": "Групу '{group}' оновлено", + "group_unknown": "Група '{group}' невідома", "group_deletion_failed": "Не вдалося видалити групу '{group}': {error}", - "group_deleted": "Група '{group}' вилучена", + "group_deleted": "Групу '{group}' видалено", "group_cannot_be_deleted": "Група {group} не може бути видалена вручну.", - "group_cannot_edit_primary_group": "Група '{group}' не може бути відредаговано вручну. Це основна група, призначена тільки для одного конкретного користувача.", - "group_cannot_edit_visitors": "Група 'visitors' не може бути відредаговано вручну. Це спеціальна група, що представляє анонімних відвідувачів.", - "group_cannot_edit_all_users": "Група 'all_users' не може бути відредаговано вручну. Це спеціальна група, призначена для всіх користувачів, зареєстрованих в YunoHost.", - "group_creation_failed": "Не вдалося створити групу \"{group} ': {error}", - "group_created": "Група '{group}' створена", - "group_already_exist_on_system_but_removing_it": "Група {group} вже існує в групах системи, але YunoHost видалить її...", + "group_cannot_edit_primary_group": "Група '{group}' не може бути відредагована вручну. Це основна група, призначена тільки для одного конкретного користувача.", + "group_cannot_edit_visitors": "Група 'visitors' не може бути відредагована вручну. Це спеціальна група, що представляє анонімних відвідувачів", + "group_cannot_edit_all_users": "Група 'all_users' не може бути відредагована вручну. Це спеціальна група, призначена для всіх користувачів, зареєстрованих в YunoHost", + "group_creation_failed": "Не вдалося створити групу '{group}': {error}", + "group_created": "Групу '{group}' створено", + "group_already_exist_on_system_but_removing_it": "Група {group} вже існує в групах системи, але YunoHost вилучить її...", "group_already_exist_on_system": "Група {group} вже існує в групах системи", "group_already_exist": "Група {group} вже існує", - "good_practices_about_user_password": "Зараз ви маєте визначити новий пароль користувача. Пароль повинен складатися не менше ніж з 8 символів, але хорошою практикою є використання більш довгого пароля (тобто парольної фрази) і/або використання різних символів (заголовних, малих, цифр і спеціальних символів).", - "good_practices_about_admin_password": "Зараз ви збираєтеся поставити новий пароль адміністратора. Пароль повинен складатися не менше ніж з 8 символів, але хорошою практикою є використання більш довгого пароля (тобто парольної фрази) і/або використання різних символів (прописних, малих, цифр і спеціальних символів).", - "global_settings_unknown_type": "Несподівана ситуація, параметр {setting} має тип {unknown_type}, але це не тип, підтримуваний системою.", - "global_settings_setting_backup_compress_tar_archives": "При створенні нових резервних копій стискати архіви (.tar.gz) замість незжатих архівів (.tar). NB: включення цієї опції означає створення більш легких архівів резервних копій, але початкова процедура резервного копіювання буде значно довше і важче для CPU.", - "global_settings_setting_security_webadmin_allowlist": "IP-адреси, яким дозволений доступ до веб-адміну. Через кому.", - "global_settings_setting_security_webadmin_allowlist_enabled": "Дозволити доступ до веб-адміну тільки деяким IP-адресами.", + "good_practices_about_user_password": "Зараз ви збираєтеся поставити новий пароль користувача. Пароль повинен складатися не менше ніж з 8 символів, але хорошою практикою є використання більш довгого пароля (тобто гасла) і/або використання різних символів (великих, малих, цифр і спеціальних символів).", + "good_practices_about_admin_password": "Зараз ви збираєтеся поставити новий пароль адміністратора. Пароль повинен складатися не менше ніж з 8 символів, але хорошою практикою є використання більш довгого пароля (тобто парольного гасла) і/або використання різних символів (великих, малих, цифр і спеціальних символів).", + "global_settings_unknown_type": "Несподівана ситуація, налаштування {setting} має тип {unknown_type}, але це не тип, підтримуваний системою.", + "global_settings_setting_backup_compress_tar_archives": "При створенні нових резервних копій стискати архіви (.tar.gz) замість нестислих архівів (.tar). NB: вмикання цієї опції означає створення легших архівів резервних копій, але початкова процедура резервного копіювання буде значно довшою і важчою для CPU.", + "global_settings_setting_security_webadmin_allowlist": "IP-адреси, яким дозволений доступ до вебадміністратора. Через кому.", + "global_settings_setting_security_webadmin_allowlist_enabled": "Дозволити доступ до вебадміністратора тільки деяким IP-адресам.", "global_settings_setting_smtp_relay_password": "Пароль хоста SMTP-ретрансляції", - "global_settings_setting_smtp_relay_user": "Обліковий запис користувача SMTP-реле", - "global_settings_setting_smtp_relay_port": "Порт SMTP-реле", - "global_settings_setting_smtp_relay_host": "SMTP релейний хост, який буде використовуватися для відправки пошти замість цього примірника yunohost. Корисно, якщо ви знаходитеся в одній із цих ситуацій: ваш 25 порт заблокований вашим провайдером або VPS провайдером, у вас є житловий IP в списку DUHL, ви не можете налаштувати зворотний DNS або цей сервер не доступний безпосередньо в інтернеті і ви хочете використовувати інший сервер для відправки пошти.", - "global_settings_setting_smtp_allow_ipv6": "Дозволити використання IPv6 для отримання і відправки пошти", - "global_settings_setting_ssowat_panel_overlay_enabled": "Включити накладення панелі SSOwat", + "global_settings_setting_smtp_relay_user": "Обліковий запис користувача SMTP-ретрансляції", + "global_settings_setting_smtp_relay_port": "Порт SMTP-ретрансляції", + "global_settings_setting_smtp_relay_host": "Хост SMTP-ретрансляції, який буде використовуватися для надсилання е-пошти замість цього зразка Yunohost. Корисно, якщо ви знаходитеся в одній із цих ситуацій: ваш 25 порт заблокований вашим провайдером або VPS провайдером, у вас є житловий IP в списку DUHL, ви не можете налаштувати зворотний DNS або цей сервер не доступний безпосередньо в Інтернеті і ви хочете використовувати інший сервер для відправки електронних листів.", + "global_settings_setting_smtp_allow_ipv6": "Дозволити використання IPv6 для отримання і надсилання листів е-пошти", + "global_settings_setting_ssowat_panel_overlay_enabled": "Увімкнути накладення панелі SSOwat", "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Дозволити використання (застарілого) ключа DSA для конфігурації демона SSH", - "global_settings_unknown_setting_from_settings_file": "Невідомий ключ в настройках: '{setting_key}', відкиньте його і збережіть в /etc/yunohost/settings-unknown.json", + "global_settings_unknown_setting_from_settings_file": "Невідомий ключ в налаштуваннях: '{setting_key}', відхиліть його і збережіть у /etc/yunohost/settings-unknown.json", "global_settings_setting_security_ssh_port": "SSH-порт", "global_settings_setting_security_postfix_compatibility": "Компроміс між сумісністю і безпекою для сервера Postfix. Впливає на шифри (і інші аспекти, пов'язані з безпекою)", - "global_settings_setting_security_ssh_compatibility": "Сумісність і співвідношення безпеки для SSH-сервера. Впливає на шифри (і інші аспекти, пов'язані з безпекою)", + "global_settings_setting_security_ssh_compatibility": "Компроміс між сумісністю і безпекою для SSH-сервера. Впливає на шифри (і інші аспекти, пов'язані з безпекою)", "global_settings_setting_security_password_user_strength": "Надійність пароля користувача", "global_settings_setting_security_password_admin_strength": "Надійність пароля адміністратора", - "global_settings_setting_security_nginx_compatibility": "Компроміс між сумісністю і безпекою для веб-сервера NGINX. Впливає на шифри (і інші аспекти, пов'язані з безпекою)", - "global_settings_setting_pop3_enabled": "Включити протокол POP3 для поштового сервера.", - "global_settings_reset_success": "Попередні настройки тепер збережені в {path}.", - "global_settings_key_doesnt_exists": "Ключ '{settings_key}' не існує в глобальних налаштуваннях, ви можете побачити всі доступні ключі, виконавши команду 'yunohost settings list'.", - "global_settings_cant_write_settings": "Неможливо зберегти файл настройок, причина: {reason}", - "global_settings_cant_serialize_settings": "Не вдалося серіалізовать дані налаштувань, причина: {reason}", - "global_settings_cant_open_settings": "Не вдалося відкрити файл настройок, причина: {reason}", - "global_settings_bad_type_for_setting": "Поганий тип для настройки {setting}, отриманий {received_type}, очікується {expected_type}", - "global_settings_bad_choice_for_enum": "Поганий вибір для настройки {setting}, отримано '{choice}', але доступні наступні варіанти: {available_choices}.", - "firewall_rules_cmd_failed": "Деякі команди правил брандмауера не спрацювали. Більш детальна інформація в журналі.", - "firewall_reloaded": "брандмауер перезавантажений", - "firewall_reload_failed": "Не вдалося перезавантажити брандмауер", + "global_settings_setting_security_nginx_compatibility": "Компроміс між сумісністю і безпекою для вебсервера NGINX. Впливає на шифри (і інші аспекти, пов'язані з безпекою)", + "global_settings_setting_pop3_enabled": "Увімкніть протокол POP3 для поштового сервера", + "global_settings_reset_success": "Попередні налаштування тепер збережені в {path}", + "global_settings_key_doesnt_exists": "Ключ '{settings_key}' не існує в глобальних налаштуваннях, ви можете побачити всі доступні ключі, виконавши команду 'yunohost settings list'", + "global_settings_cant_write_settings": "Неможливо зберегти файл налаштувань, причина: {reason}", + "global_settings_cant_serialize_settings": "Не вдалося серіалізувати дані налаштувань, причина: {reason}", + "global_settings_cant_open_settings": "Не вдалося відкрити файл налаштувань, причина: {reason}", + "global_settings_bad_type_for_setting": "Поганий тип для налаштування {setting}, отримано {received_type}, а очікується {expected_type}", + "global_settings_bad_choice_for_enum": "Поганий вибір для налаштування {setting}, отримано '{choice}', але доступні наступні варіанти: {available_choices}", + "firewall_rules_cmd_failed": "Деякі команди правил фаєрвола не спрацювали. Подробиці в журналі.", + "firewall_reloaded": "Фаєрвол перезавантажено", + "firewall_reload_failed": "Не вдалося перезавантажити фаєрвол", "file_does_not_exist": "Файл {path} не існує.", "field_invalid": "Неприпустиме поле '{}'", "experimental_feature": "Попередження: Ця функція є експериментальною і не вважається стабільною, ви не повинні використовувати її, якщо не знаєте, що робите.", - "extracting": "Витяг...", + "extracting": "Витягнення...", "dyndns_unavailable": "Домен '{domain}' недоступний.", "dyndns_domain_not_provided": "DynDNS провайдер {provider} не може надати домен {domain}.", - "dyndns_registration_failed": "Не вдалося зареєструвати домен DynDNS: {error}.", - "dyndns_registered": "Домен DynDNS зареєстрований", - "dyndns_provider_unreachable": "Неможливо зв'язатися з провайдером DynDNS {provider}: або ваш YunoHost неправильно підключений до інтернету, або сервер dynette не працює.", - "dyndns_no_domain_registered": "Домен не зареєстрований в DynDNS", - "dyndns_key_not_found": "DNS-ключ не знайдений для домену", - "dyndns_key_generating": "Генерація DNS-ключа... Це може зайняти деякий час.", - "dyndns_ip_updated": "Оновлення свій IP-адресу в DynDNS", - "dyndns_ip_update_failed": "Не вдалося оновити IP-адреса в DynDNS", - "dyndns_could_not_check_available": "Не вдалося перевірити наявність певної {domain} на {provider}.", + "dyndns_registration_failed": "Не вдалося зареєструвати домен DynDNS: {error}", + "dyndns_registered": "Домен DynDNS зареєстровано", + "dyndns_provider_unreachable": "Неможливо зв'язатися з провайдером DynDNS {provider}: або ваш YunoHost неправильно під'єднано до Інтернету, або сервер dynette не працює.", + "dyndns_no_domain_registered": "Домен не зареєстровано в DynDNS", + "dyndns_key_not_found": "DNS-ключ для домену не знайдено", + "dyndns_key_generating": "Утворення DNS-ключа... Це може зайняти деякий час.", + "dyndns_ip_updated": "Вашу IP-адресу в DynDNS оновлено", + "dyndns_ip_update_failed": "Не вдалося оновити IP-адресу в DynDNS", + "dyndns_could_not_check_available": "Не вдалося перевірити, чи {domain} доступний у {provider}.", "dyndns_could_not_check_provide": "Не вдалося перевірити, чи може {provider} надати {domain}.", - "dpkg_lock_not_available": "Ця команда не може бути виконана прямо зараз, тому що інша програма, схоже, використовує блокування dpkg (системного менеджера пакетів).", - "dpkg_is_broken": "Ви не можете зробити це прямо зараз, тому що dpkg/APT (системні менеджери пакетів), схоже, знаходяться в зламаному стані... Ви можете спробувати вирішити цю проблему, підключившись через SSH і виконавши `sudo apt install --fix-broken` і/або `sudo dpkg --configure -a`.", + "dpkg_lock_not_available": "Ця команда не може бути виконана прямо зараз, тому що інша програма, схоже, використовує блокування dpkg (системного менеджера пакетів)", + "dpkg_is_broken": "Ви не можете зробити це прямо зараз, тому що dpkg/APT (системні менеджери пакетів), схоже, знаходяться в зламаному стані... Ви можете спробувати вирішити цю проблему, під'єднавшись через SSH і виконавши `sudo apt install --fix-broken` та/або `sudo dpkg --configure -a`.", "downloading": "Завантаження…", "done": "Готово", "domains_available": "Доступні домени:", - "domain_unknown": "невідомий домен", + "domain_unknown": "Невідомий домен", "domain_name_unknown": "Домен '{domain}' невідомий", - "domain_uninstall_app_first": "Ці додатки все ще встановлені на вашому домені: {apps} ласка, видаліть їх за допомогою 'yunohost app remove the_app_id' або перемістити їх на інший домен за допомогою 'yunohost app change-url the_app_id', перш ніж приступити до видалення домену.", - "domain_remove_confirm_apps_removal": "Видалення цього домену призведе до видалення цих застосунків: {apps} Ви впевнені, що хочете це зробити? [{answers}].", - "domain_hostname_failed": "Неможливо встановити нове ім'я хоста. Це може викликати проблеми в подальшому (можливо, все буде в порядку).", - "domain_exists": "Домен вже існує", - "domain_dyndns_root_unknown": "Невідомий кореневої домен DynDNS", + "domain_uninstall_app_first": "Ці застосунки все ще встановлені на вашому домені:\n{apps}\n\nВидаліть їх за допомогою 'yunohost app remove the_app_id' або перемістіть їх на інший домен за допомогою 'yunohost app change-url the_app_id', перш ніж приступити до вилучення домену", + "domain_remove_confirm_apps_removal": "Вилучення цього домену призведе до вилучення таких застосунків:\n{apps}\n\nВи впевнені, що хочете це зробити? [{answers}]", + "domain_hostname_failed": "Неможливо встановити нову назву хоста. Це може викликати проблеми в подальшому (можливо, все буде в порядку).", + "domain_exists": "Цей домен уже існує", + "domain_dyndns_root_unknown": "Невідомий кореневий домен DynDNS", "domain_dyndns_already_subscribed": "Ви вже підписалися на домен DynDNS", - "domain_dns_conf_is_just_a_recommendation": "Ця команда показує * рекомендовану * конфігурацію. Насправді вона не встановлює конфігурацію DNS для вас. Ви самі повинні налаштувати свою зону DNS у реєстратора відповідно до цих рекомендацій.", + "domain_dns_conf_is_just_a_recommendation": "Ця команда показує *рекомендовану* конфігурацію. Насправді вона не встановлює конфігурацію DNS для вас. Ви самі повинні налаштувати свою зону DNS у реєстратора відповідно до цих рекомендацій.", "domain_deletion_failed": "Неможливо видалити домен {domain}: {error}", - "domain_deleted": "домен видалений", + "domain_deleted": "Домен видалено", "domain_creation_failed": "Неможливо створити домен {domain}: {error}", - "domain_created": "домен створений", - "domain_cert_gen_failed": "Не вдалося згенерувати сертифікат", - "domain_cannot_remove_main_add_new_one": "Ви не можете видалити '{domain}', так як це основний домен і ваш єдиний домен, вам потрібно спочатку додати інший домен за допомогою 'yunohost domain add ', потім встановити його як основний домен за допомогою ' yunohost domain main-domain -n 'і потім ви можете видалити домен' {domain} 'за допомогою' yunohost domain remove {domain} ''.", - "domain_cannot_add_xmpp_upload": "Ви не можете додавати домени, що починаються з 'xmpp-upload.'. Таке ім'я зарезервовано для функції XMPP upload, вбудованої в YunoHost.", - "domain_cannot_remove_main": "Ви не можете видалити '{domain}', так як це основний домен, спочатку вам потрібно встановити інший домен в якості основного за допомогою 'yunohost domain main-domain -n '; ось список доменів-кандидатів: {other_domains}", - "disk_space_not_sufficient_update": "Недостатньо місця на диску для поновлення цього додатка", - "disk_space_not_sufficient_install": "Бракує місця на диску для установки цього додатка", - "diagnosis_sshd_config_inconsistent_details": "Будь ласка, виконайте yunohost settings set security.ssh.port -v YOUR_SSH_PORT , щоб визначити порт SSH, і перевірте yunohost tools regen-conf ssh --dry-run --with-diff yunohost tools regen-conf ssh --force , щоб скинути ваш conf на рекомендований YunoHost.", - "diagnosis_sshd_config_inconsistent": "Схоже, що порт SSH був вручну змінений в/etc/ssh/sshd_config. Починаючи з версії YunoHost 4.2, доступний новий глобальний параметр 'security.ssh.port', що дозволяє уникнути ручного редагування конфігурації.", + "domain_created": "Домен створено", + "domain_cert_gen_failed": "Не вдалося утворити сертифікат", + "domain_cannot_remove_main_add_new_one": "Ви не можете видалити '{domain}', так як це основний домен і ваш єдиний домен, вам потрібно спочатку додати інший домен за допомогою 'yunohost domain add ', потім встановити його як основний домен за допомогою 'yunohost domain main-domain -n ' і потім ви можете вилучити домен '{domain}' за допомогою 'yunohost domain remove {domain}'.'", + "domain_cannot_add_xmpp_upload": "Ви не можете додавати домени, що починаються з 'xmpp-upload.'. Таку назву зарезервовано для функції XMPP upload, вбудованої в YunoHost.", + "domain_cannot_remove_main": "Ви не можете вилучити '{domain}', бо це основний домен, спочатку вам потрібно встановити інший домен в якості основного за допомогою 'yunohost domain main-domain -n '; ось список доменів-кандидатів: {other_domains}", + "disk_space_not_sufficient_update": "Недостатньо місця на диску для оновлення цього застосунку", + "disk_space_not_sufficient_install": "Недостатньо місця на диску для встановлення цього застосунку", + "diagnosis_sshd_config_inconsistent_details": "Будь ласка, виконайте команду yunohost settings set security.ssh.port -v YOUR_SSH_PORT, щоб визначити порт SSH, і перевіртеyunohost tools regen-conf ssh --dry-run --with-diff і yunohost tools regen-conf ssh --force, щоб скинути ваш конфіг на рекомендований YunoHost.", + "diagnosis_sshd_config_inconsistent": "Схоже, що порт SSH був уручну змінений в /etc/ssh/sshd_config. Починаючи з версії YunoHost 4.2, доступний новий глобальний параметр 'security.ssh.port', що дозволяє уникнути ручного редагування конфігурації.", "diagnosis_sshd_config_insecure": "Схоже, що конфігурація SSH була змінена вручну і є небезпечною, оскільки не містить директив 'AllowGroups' або 'AllowUsers' для обмеження доступу авторизованих користувачів.", - "diagnosis_processes_killed_by_oom_reaper": "Деякі процеси були недавно вбито системою через брак пам'яті. Зазвичай це є симптомом нестачі пам'яті в системі або процесу, який з'їв дуже багато пам'яті. Зведення убитих процесів: {kills_summary}", - "diagnosis_never_ran_yet": "Схоже, що цей сервер був налаштований недавно, і поки немає звіту про діагностику. Вам слід почати з повної діагностики, або з веб-адміністратора, або використовуючи 'yunohost diagnosis run' з командного рядка.", + "diagnosis_processes_killed_by_oom_reaper": "Деякі процеси було недавно вбито системою через брак пам'яті. Зазвичай це є симптомом нестачі пам'яті в системі або процесу, який з'їв дуже багато пам'яті. Зведення убитих процесів:\n{kills_summary}", + "diagnosis_never_ran_yet": "Схоже, що цей сервер був налаштований недавно, і поки немає звіту про діагностику. Вам слід почати з повної діагностики, або з вебадміністратора, або використовуючи 'yunohost diagnosis run' з командного рядка.", "diagnosis_unknown_categories": "Наступні категорії невідомі: {categories}", - "diagnosis_http_nginx_conf_not_up_to_date_details": "Щоб виправити ситуацію, перевірте різницю за допомогою командного рядка, використовуючи yunohost tools regen-conf nginx --dry-run --with-diff , і якщо все в порядку, застосуйте зміни за допомогою yunohost tools regen-conf nginx --force .", + "diagnosis_http_nginx_conf_not_up_to_date_details": "Щоб виправити становище, перевірте різницю за допомогою командного рядка, використовуючи yunohost tools regen-conf nginx --dry-run --with-diff, і якщо все в порядку, застосуйте зміни за допомогою команди yunohost tools regen-conf nginx --force.", "diagnosis_http_nginx_conf_not_up_to_date": "Схоже, що конфігурація nginx цього домену була змінена вручну, що не дозволяє YunoHost визначити, чи доступний він по HTTP.", - "diagnosis_http_partially_unreachable": "Домен {domain} здається недоступним по HTTP ззовні локальної мережі в IPv {failed}, хоча він працює в IPv {passed}.", - "diagnosis_http_unreachable": "Домен {domain} здається недоступним через HTTP ззовні локальної мережі.", - "diagnosis_http_bad_status_code": "Схоже, що замість вашого сервера відповіла інша машина (можливо, ваш маршрутизатор).
1. Найбільш поширеною причиною цієї проблеми є те, що порт 80 (і 443) неправильно перенаправлений на ваш сервер .
2. На більш складних установках: переконайтеся, що немає брандмауера або зворотного проксі.", - "diagnosis_http_connection_error": "Помилка підключення: не вдалося підключитися до запитуваного домену, швидше за все, він недоступний.", + "diagnosis_http_partially_unreachable": "Домен {domain} здається недоступним по HTTP поза локальною мережею в IPv{failed}, хоча він працює в IPv{passed}.", + "diagnosis_http_unreachable": "Домен {domain} здається недоступним через HTTP поза локальною мережею.", + "diagnosis_http_bad_status_code": "Схоже, що замість вашого сервера відповіла інша машина (можливо, ваш маршрутизатор).
1. Найбільш поширеною причиною цієї проблеми є те, що порт 80 (і 443) неправильно перенаправлено на ваш сервер .
2. На більш складних установках: переконайтеся, що немає фаєрвола або зворотного проксі.", + "diagnosis_http_connection_error": "Помилка з'єднання: не вдалося з'єднатися із запитуваним доменом, швидше за все, він недоступний.", "diagnosis_http_timeout": "При спробі зв'язатися з вашим сервером ззовні стався тайм-аут. Він здається недоступним.
1. Найбільш поширеною причиною цієї проблеми є те, що порт 80 (і 443) неправильно перенаправлено на ваш сервер .
2. Ви також повинні переконатися, що служба nginx запущена
3.На більш складних установках: переконайтеся, що немає фаєрвола або зворотного проксі.", "diagnosis_http_ok": "Домен {domain} доступний по HTTP поза локальною мережею.", "diagnosis_http_localdomain": "Домен {domain} з .local TLD не може бути доступний ззовні локальної мережі.", "diagnosis_http_could_not_diagnose_details": "Помилка: {error}", "diagnosis_http_could_not_diagnose": "Не вдалося діагностувати досяжність доменів ззовні в IPv{ipversion}.", - "diagnosis_http_hairpinning_issue_details": "Можливо, це пов'язано з коробкою/маршрутизатором вашого інтернет-провайдера. В результаті, люди ззовні вашої локальної мережі зможуть отримати доступ до вашого сервера, як і очікувалося, але не люди зсередини локальної мережі (як ви, ймовірно?) При використанні доменного імені або глобального IP. Можливо, ви зможете поліпшити ситуацію, глянувши https://yunohost.org/dns_local_network .", + "diagnosis_http_hairpinning_issue_details": "Можливо, це пов'язано з коробкою/маршрутизатором вашого інтернет-провайдера. В результаті, люди ззовні вашої локальної мережі зможуть отримати доступ до вашого сервера, як і очікувалося, але не люди зсередини локальної мережі (як ви, ймовірно?) При використанні доменного імені або глобального IP. Можливо, ви зможете поліпшити ситуацію, глянувши https://yunohost.org/dns_local_network ", "diagnosis_http_hairpinning_issue": "Схоже, що у вашій локальній мережі не увімкнено шпилькування (hairpinning).", - "diagnosis_ports_forwarding_tip": "Щоб вирішити цю проблему, вам, швидше за все, потрібно налаштувати пересилання портів на вашому інтернет-маршрутизаторі, як описано в https://yunohost.org/isp_box_config.", - "diagnosis_ports_needed_by": "Відкриття цього порту необхідне для функцій {category} (служба {service}).", + "diagnosis_ports_forwarding_tip": "Щоб вирішити цю проблему, вам, швидше за все, потрібно налаштувати пересилання портів на вашому інтернет-маршрутизаторі, як описано в https://yunohost.org/isp_box_config", + "diagnosis_ports_needed_by": "Відкриття цього порту необхідне для функцій {category} (служба {service})", "diagnosis_ports_ok": "Порт {port} доступний ззовні.", "diagnosis_ports_partially_unreachable": "Порт {port} не доступний ззовні в IPv{failed}.", "diagnosis_ports_unreachable": "Порт {port} недоступний ззовні.", @@ -382,24 +382,24 @@ "diagnosis_description_ip": "Інтернет-з'єднання", "diagnosis_description_basesystem": "Основна система", "diagnosis_security_vulnerable_to_meltdown_details": "Щоб виправити це, вам слід оновити систему і перезавантажитися, щоб завантажити нове ядро Linux (або звернутися до вашого серверного провайдера, якщо це не спрацює). Докладніше див. на сайті https://meltdownattack.com/.", - "diagnosis_security_vulnerable_to_meltdown": "Схоже, що ви вразливі до критичної вразливості безпеки Meltdown.", + "diagnosis_security_vulnerable_to_meltdown": "Схоже, що ви вразливі до критичної вразливості безпеки Meltdown", "diagnosis_rootfstotalspace_critical": "Коренева файлова система має тільки {space}, що дуже тривожно! Скоріше за все, дисковий простір закінчиться дуже скоро! Рекомендовано мати не менше 16 ГБ для кореневої файлової системи.", "diagnosis_rootfstotalspace_warning": "Коренева файлова система має тільки {space}. Можливо це нормально, але будьте обережні, тому що в кінцевому підсумку дисковий простір може швидко закінчитися... Рекомендовано мати не менше 16 ГБ для кореневої файлової системи.", - "diagnosis_regenconf_manually_modified_details": "Можливо це нормально, якщо ви знаєте, що робите! YunoHost перестане оновлювати цей файл автоматично... Але врахуйте, що оновлення YunoHost можуть містити важливі рекомендовані зміни. Якщо ви хочете, ви можете перевірити відмінності за допомогою команди yunohost tools regen-conf {category} --dry-run --with-diff і примусово повернути рекомендовану конфігурацію за допомогою команди yunohost tools regen-conf {category} --force.", + "diagnosis_regenconf_manually_modified_details": "Можливо це нормально, якщо ви знаєте, що робите! YunoHost перестане оновлювати цей файл автоматично... Але врахуйте, що оновлення YunoHost можуть містити важливі рекомендовані зміни. Якщо ви хочете, ви можете перевірити відмінності за допомогою команди yunohost tools regen-conf {category} --dry-run --with-diff і примусово повернути рекомендовану конфігурацію за допомогою команди yunohost tools regen-conf {category} --force", "diagnosis_regenconf_manually_modified": "Конфігураційний файл {file}, схоже, було змінено вручну.", "diagnosis_regenconf_allgood": "Усі конфігураційні файли відповідають рекомендованій конфігурації!", "diagnosis_mail_queue_too_big": "Занадто багато відкладених листів у поштовій черзі (листів: {nb_pending})", "diagnosis_mail_queue_unavailable_details": "Помилка: {error}", "diagnosis_mail_queue_unavailable": "Неможливо дізнатися кількість очікувальних листів у черзі", "diagnosis_mail_queue_ok": "Відкладених електронних листів у поштових чергах: {nb_pending}", - "diagnosis_mail_blacklist_website": "Після визначення причини, з якої ви потрапили в чорний список, і її усунення, ви можете попросити видалити ваш IP або домен на {blacklist_website}.", + "diagnosis_mail_blacklist_website": "Після визначення причини, з якої ви потрапили в чорний список, і її усунення, ви можете попросити видалити ваш IP або домен на {blacklist_website}", "diagnosis_mail_blacklist_reason": "Причина внесення в чорний список: {reason}", - "diagnosis_mail_blacklist_listed_by": "Ваш IP або домен {item} знаходиться в чорному списку {blacklist_name}.", + "diagnosis_mail_blacklist_listed_by": "Ваш IP або домен {item} знаходиться в чорному списку {blacklist_name}", "diagnosis_mail_blacklist_ok": "IP-адреси і домени, які використовуються цим сервером, не внесені в чорний список", - "diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "Поточний зворотний DNS:{rdns_domain}
Очікуване значення: {ehlo_domain}.", + "diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "Поточний зворотний DNS:{rdns_domain}
Очікуване значення: {ehlo_domain}", "diagnosis_mail_fcrdns_different_from_ehlo_domain": "Зворотний DNS неправильно налаштований в IPv{ipversion}. Деякі електронні листи можуть бути не доставлені або можуть бути відзначені як спам.", "diagnosis_mail_fcrdns_nok_alternatives_6": "Деякі провайдери не дозволять вам налаштувати зворотний DNS (або їх функція може бути зламана...). Якщо ваш зворотний DNS правильно налаштований для IPv4, ви можете спробувати вимкнути використання IPv6 при надсиланні листів, виконавши команду yunohost settings set smtp.allow_ipv6 -v off. Примітка: останнє рішення означає, що ви не зможете надсилати або отримувати електронні листи з нечисленних серверів, що використовують тільки IPv6.", - "diagnosis_mail_fcrdns_nok_alternatives_4": "Деякі провайдери не дозволять вам налаштувати зворотний DNS (або їх функція може бути зламана...). Якщо ви відчуваєте проблеми через це, розгляньте наступні рішення:
- Деякі провайдери надають альтернативу використання ретранслятора поштового сервера, хоча це має на увазі, що ретранслятор зможе шпигувати за вашим поштовим трафіком.
- Альтернативою для захисту конфіденційності є використання VPN *з виділеним загальнодоступним IP* для обходу подібних обмежень. Дивіться https://yunohost.org/#/vpn_advantage
- Або можна переключитися на іншого провайдера .", + "diagnosis_mail_fcrdns_nok_alternatives_4": "Деякі провайдери не дозволять вам налаштувати зворотний DNS (або їх функція може бути зламана...). Якщо ви відчуваєте проблеми через це, розгляньте наступні рішення:
- Деякі провайдери надають альтернативу використання ретранслятора поштового сервера, хоча це має на увазі, що ретранслятор зможе шпигувати за вашим поштовим трафіком.
- Альтернативою для захисту конфіденційності є використання VPN *з виділеним загальнодоступним IP* для обходу подібних обмежень. Дивіться https://yunohost.org/#/vpn_advantage
- Або можна переключитися на іншого провайдера", "diagnosis_mail_fcrdns_nok_details": "Спочатку спробуйте налаштувати зворотний DNS з {ehlo_domain} в інтерфейсі вашого інтернет-маршрутизатора або в інтерфейсі вашого хостинг-провайдера. (Деякі хостинг-провайдери можуть вимагати, щоб ви відправили їм запит у підтримку для цього).", "diagnosis_mail_fcrdns_dns_missing": "У IPv{ipversion} не визначений зворотний DNS. Деякі листи можуть не доставлятися або позначатися як спам.", "diagnosis_mail_fcrdns_ok": "Ваш зворотний DNS налаштовано правильно!", @@ -408,63 +408,63 @@ "diagnosis_mail_ehlo_wrong_details": "EHLO, отриманий віддаленим діагностичним центром в IPv{ipversion}, відрізняється від домену вашого сервера.
Отриманий EHLO: {wrong_ehlo}
Очікуваний: {right_ehlo}< br>Найпоширенішою причиною цієї проблеми є те, що порт 25 неправильно перенаправлений на ваш сервер. Крім того, переконайтеся, що в роботу сервера не втручається фаєрвол або зворотний проксі-сервер.", "diagnosis_mail_ehlo_wrong": "Інший поштовий SMTP-сервер відповідає на IPv{ipversion}. Ваш сервер, ймовірно, не зможе отримувати електронні листи.", "diagnosis_mail_ehlo_bad_answer_details": "Це може бути викликано тим, що замість вашого сервера відповідає інша машина.", - "diagnosis_mail_ehlo_bad_answer": "Не-SMTP служба відповіла на порту 25 на IPv{ipversion}.", + "diagnosis_mail_ehlo_bad_answer": "Не-SMTP служба відповіла на порту 25 на IPv{ipversion}", "diagnosis_mail_ehlo_unreachable_details": "Не вдалося відкрити з'єднання за портом 25 з вашим сервером на IPv{ipversion}. Він здається недоступним.
1. Найбільш поширеною причиною цієї проблеми є те, що порт 25 неправильно перенаправлений на ваш сервер.
2. Ви також повинні переконатися, що служба postfix запущена.
3. На більш складних установках: переконайтеся, що немає фаєрвола або зворотного проксі.", "diagnosis_mail_ehlo_unreachable": "Поштовий сервер SMTP недоступний ззовні по IPv{ipversion}. Він не зможе отримувати листи електронної пошти.", "diagnosis_mail_ehlo_ok": "Поштовий сервер SMTP доступний ззовні і тому може отримувати електронні листи!", - "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Деякі провайдери не дозволять вам розблокувати вихідний порт 25, тому що вони не піклуються про мережевий нейтралітет (Net Neutrality).
- Деякі з них пропонують альтернативу використання ретранслятора поштового сервера, хоча це має на увазі, що ретранслятор зможе шпигувати за вашим поштовим трафіком.
- Альтернативою для захисту конфіденційності є використання VPN *з виділеним загальнодоступним IP* для обходу такого роду обмежень. Дивіться https://yunohost.org/#/vpn_advantage
- Ви також можете розглянути можливість переходу на більш дружнього до мережевого нейтралітету провайдера.", + "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Деякі провайдери не дозволять вам розблокувати вихідний порт 25, тому що вони не піклуються про мережевий нейтралітет (Net Neutrality).
- Деякі з них пропонують альтернативу використання ретранслятора поштового сервера, хоча це має на увазі, що ретранслятор зможе шпигувати за вашим поштовим трафіком.
- Альтернативою для захисту конфіденційності є використання VPN *з виділеним загальнодоступним IP* для обходу такого роду обмежень. Дивіться https://yunohost.org/#/vpn_advantage
- Ви також можете розглянути можливість переходу на більш дружнього до мережевого нейтралітету провайдера", "diagnosis_mail_outgoing_port_25_blocked_details": "Спочатку спробуйте розблокувати вихідний порт 25 в інтерфейсі вашого інтернет-маршрутизатора або в інтерфейсі вашого хостинг-провайдера. (Деякі хостинг-провайдери можуть вимагати, щоб ви відправили їм заявку в службу підтримки).", "diagnosis_mail_outgoing_port_25_blocked": "Поштовий сервер SMTP не може відправляти електронні листи на інші сервери, оскільки вихідний порт 25 заблоковано в IPv{ipversion}.", "app_manifest_install_ask_path": "Оберіть шлях URL (після домену), за яким має бути встановлено цей застосунок", - "yunohost_postinstall_end_tip": "Постінсталляція завершена! Щоб завершити установку, будь ласка, розгляньте наступні варіанти: - додавання першого користувача через розділ 'Користувачі' веб-адміністратора (або 'yunohost user create ' в командному рядку); - діагностику можливих проблем через розділ 'Діагностика' веб-адміністратора (або 'yunohost diagnosis run' в командному рядку); - читання розділів 'Завершення установки' і 'Знайомство з YunoHost' в документації адміністратора: https://yunohost.org/admindoc.", - "yunohost_not_installed": "YunoHost встановлений неправильно. Будь ласка, запустіть 'yunohost tools postinstall'.", + "yunohost_postinstall_end_tip": "Післявстановлення завершено! Щоб завершити доналаштування, будь ласка, розгляньте наступні варіанти:\n - додавання першого користувача через розділ 'Користувачі' вебадміністратора (або 'yunohost user create ' в командному рядку);\n - діагностика можливих проблем через розділ 'Діагностика' вебадміністратора (або 'yunohost diagnosis run' в командному рядку);\n - прочитання розділів 'Завершення встановлення' і 'Знайомство з YunoHost' у документації адміністратора: https://yunohost.org/admindoc.", + "yunohost_not_installed": "YunoHost установлений неправильно. Будь ласка, запустіть 'yunohost tools postinstall'", "yunohost_installing": "Установлення YunoHost...", - "yunohost_configured": "YunoHost вже налаштований", + "yunohost_configured": "YunoHost вже налаштовано", "yunohost_already_installed": "YunoHost вже встановлено", - "user_updated": "Інформація про користувача змінена", + "user_updated": "Відомості про користувача змінено", "user_update_failed": "Не вдалося оновити користувача {user}: {error}", "user_unknown": "Невідомий користувач: {user}", - "user_home_creation_failed": "Не вдалося створити домашню папку для користувача", + "user_home_creation_failed": "Не вдалося створити каталог домівки для користувача", "user_deletion_failed": "Не вдалося видалити користувача {user}: {error}", - "user_deleted": "користувача видалено", + "user_deleted": "Користувача видалено", "user_creation_failed": "Не вдалося створити користувача {user}: {error}", - "user_created": "Аккаунт було створено", + "user_created": "Користувача створено", "user_already_exists": "Користувач '{user}' вже існує", "upnp_port_open_failed": "Не вдалося відкрити порт через UPnP", - "upnp_enabled": "UPnP включено", + "upnp_enabled": "UPnP увімкнено", "upnp_disabled": "UPnP вимкнено", - "upnp_dev_not_found": "UPnP-пристрій, не знайдено", + "upnp_dev_not_found": "UPnP-пристрій не знайдено", "upgrading_packages": "Оновлення пакетів...", - "upgrade_complete": "оновлення завершено", - "updating_apt_cache": "Вибірка доступних оновлень для системних пакетів...", - "update_apt_cache_warning": "Щось пішло не так при оновленні кеша APT (менеджера пакунків Debian). Ось дамп рядків sources.list, який може допомогти визначити проблемні рядки: {sourceslist}", - "update_apt_cache_failed": "Неможливо оновити кеш APT (менеджер пакетів Debian). Ось дамп рядків sources.list, який може допомогти визначити проблемні рядки: {sourceslist}", - "unrestore_app": "{app} не буде поновлено", - "unlimit": "немає квоти", + "upgrade_complete": "Оновлення завершено", + "updating_apt_cache": "Завантаження доступних оновлень для системних пакетів...", + "update_apt_cache_warning": "Щось пішло не так при оновленні кеша APT (менеджера пакунків Debian). Ось дамп рядків sources.list, який може допомогти визначити проблемні рядки:\n{sourceslist}", + "update_apt_cache_failed": "Неможливо оновити кеш APT (менеджер пакетів Debian). Ось дамп рядків sources.list, який може допомогти визначити проблемні рядки:\n{sourceslist}", + "unrestore_app": "{app} не буде оновлено", + "unlimit": "Квоти немає", "unknown_main_domain_path": "Невідомий домен або шлях для '{app}'. Вам необхідно вказати домен і шлях, щоб мати можливість вказати URL для дозволу.", "unexpected_error": "Щось пішло не так: {error}", - "unbackup_app": "{app} НЕ буде збережений", - "tools_upgrade_special_packages_completed": "Оновлення пакета YunoHost завершено. Натисніть [Enter] для повернення командного рядка", - "tools_upgrade_special_packages_explanation": "Спеціальне оновлення триватиме у фоновому режимі. Будь ласка, не запускайте ніяких інших дій на вашому сервері протягом наступних ~ 10 хвилин (в залежності від швидкості обладнання). Після цього вам, можливо, доведеться заново увійти в веб-адмін. Журнал поновлення буде доступний в Інструменти → Журнал (в веб-адміном) або за допомогою 'yunohost log list' (з командного рядка).", - "tools_upgrade_special_packages": "Тепер оновлюємо \"спеціальні\" (пов'язані з yunohost) пакети…", + "unbackup_app": "{app} НЕ буде збережено", + "tools_upgrade_special_packages_completed": "Оновлення пакета YunoHost завершено.\nНатисніть [Enter] для повернення до командного рядка", + "tools_upgrade_special_packages_explanation": "Спеціальне оновлення триватиме у тлі. Будь ласка, не запускайте ніяких інших дій на вашому сервері протягом наступних ~ 10 хвилин (в залежності від швидкості обладнання). Після цього вам, можливо, доведеться заново увійти в вебадміністратора. Журнал оновлення буде доступний в Засоби → Журнал (в веб-адміністраторі) або за допомогою 'yunohost log list' (з командного рядка).", + "tools_upgrade_special_packages": "Тепер оновлюємо 'спеціальні' (пов'язані з yunohost) пакети…", "tools_upgrade_regular_packages_failed": "Не вдалося оновити пакети: {packages_list}", - "tools_upgrade_regular_packages": "Тепер оновлюємо \"звичайні\" (не пов'язані з yunohost) пакети…", - "tools_upgrade_cant_unhold_critical_packages": "Не вдалося утримати критичні пакети…", + "tools_upgrade_regular_packages": "Тепер оновлюємо 'звичайні' (не пов'язані з yunohost) пакети…", + "tools_upgrade_cant_unhold_critical_packages": "Не вдалося розтримати критичні пакети…", "tools_upgrade_cant_hold_critical_packages": "Не вдалося утримати критичні пакети…", - "tools_upgrade_cant_both": "Неможливо оновити систему і програми одночасно", - "tools_upgrade_at_least_one": "Будь ласка, вкажіть 'apps', або 'system'.", - "this_action_broke_dpkg": "Ця дія порушило dpkg/APT (системні менеджери пакетів)... Ви можете спробувати вирішити цю проблему, підключившись по SSH і запустивши `sudo apt install --fix-broken` і/або` sudo dpkg --configure -a`.", + "tools_upgrade_cant_both": "Неможливо оновити систему і застосунки одночасно", + "tools_upgrade_at_least_one": "Будь ласка, вкажіть 'apps', або 'system'", + "this_action_broke_dpkg": "Ця дія порушила dpkg/APT (системні менеджери пакетів)... Ви можете спробувати вирішити цю проблему, під'єднавшись по SSH і запустивши `sudo apt install --fix-broken` та/або `sudo dpkg --configure -a`.", "system_username_exists": "Ім'я користувача вже існує в списку користувачів системи", - "system_upgraded": "система оновлена", - "ssowat_conf_updated": "Конфігурація SSOwat оновлена", - "ssowat_conf_generated": "Регенерувати конфігурація SSOwat", - "show_tile_cant_be_enabled_for_regex": "Ви не можете включити 'show_tile' прямо зараз, тому що URL для дозволу '{permission}' являє собою регекс", - "show_tile_cant_be_enabled_for_url_not_defined": "Ви не можете включити 'show_tile' прямо зараз, тому що спочатку ви повинні визначити URL для дозволу '{permission}'", + "system_upgraded": "Систему оновлено", + "ssowat_conf_updated": "Конфігурацію SSOwat оновлено", + "ssowat_conf_generated": "Конфігурацію SSOwat перестворено", + "show_tile_cant_be_enabled_for_regex": "Ви не можете увімкнути 'show_tile' прямо зараз, тому що URL для дозволу '{permission}' являє собою регулярний вираз", + "show_tile_cant_be_enabled_for_url_not_defined": "Ви не можете увімкнути 'show_tile' прямо зараз, тому що спочатку ви повинні визначити URL для дозволу '{permission}'", "service_unknown": "Невідома служба '{service}'", - "service_stopped": "Служба '{service}' зупинена", - "service_stop_failed": "Неможливо зупинити службу '{service}' Недавні журнали служб: {logs}", - "service_started": "Служба '{service}' запущена", - "service_start_failed": "Не вдалося запустити службу '{service}' Recent service logs: {logs}", + "service_stopped": "Службу '{service}' зупинено", + "service_stop_failed": "Неможливо зупинити службу '{service}' \n\nНедавні журнали служби: {logs}", + "service_started": "Службу '{service}' запущено", + "service_start_failed": "Не вдалося запустити службу '{service}' \n\nНедавні журнали служби: {logs}", "diagnosis_mail_outgoing_port_25_ok": "Поштовий сервер SMTP може відправляти електронні листи (вихідний порт 25 не заблоковано).", "diagnosis_swap_tip": "Будь ласка, будьте обережні і знайте, що якщо сервер розміщує обсяг підкачки на SD-карті або SSD-накопичувачі, це може різко скоротити строк служби пристрою`.", "diagnosis_swap_ok": "Система має {total} обсягу підкачки!", @@ -491,7 +491,7 @@ "diagnosis_dns_try_dyndns_update_force": "Конфігурація DNS цього домену повинна автоматично управлятися YunoHost. Якщо це не так, ви можете спробувати примусово оновити її за допомогою команди yunohost dyndns update --force.", "diagnosis_dns_point_to_doc": "Якщо вам потрібна допомога з налаштування DNS-записів, зверніться до документації на сайті https://yunohost.org/dns_config.", "diagnosis_dns_discrepancy": "Наступний запис DNS, схоже, не відповідає рекомендованій конфігурації:
Тип: {type}
Назва: {name}
Поточне значення: {current}
Очікуване значення: {value}", - "diagnosis_dns_missing_record": "Згідно рекомендованої конфігурації DNS, ви повинні додати запис DNS з наступними відомостями.\n
Тип: {type}\n
Назва: {name}\n
Значення: {value}", + "diagnosis_dns_missing_record": "Згідно рекомендованої конфігурації DNS, ви повинні додати запис DNS з наступними відомостями.
Тип: {type}
Назва: {name}
Значення: {value}", "diagnosis_dns_bad_conf": "Деякі DNS-записи відсутні або неправильні для домену {domain} (категорія {category})", "diagnosis_dns_good_conf": "DNS-записи правильно налаштовані для домену {domain} (категорія {category})", "diagnosis_ip_weird_resolvconf_details": "Файл /etc/resolv.conf повинен бути символічним посиланням на /etc/resolvconf/run/resolv.conf, що вказує на 127.0.0.1(dnsmasq). Якщо ви хочете вручну налаштувати DNS вирішувачі (resolvers), відредагуйте /etc/resolv.dnsmasq.conf.", @@ -500,14 +500,14 @@ "diagnosis_ip_broken_dnsresolution": "Роздільність доменних імен, схоже, з якоїсь причини не працює... Фаєрвол блокує DNS-запити?", "diagnosis_ip_dnsresolution_working": "Роздільність доменних імен працює!", "diagnosis_ip_not_connected_at_all": "Здається, сервер взагалі не під'єднаний до Інтернету!?", - "diagnosis_ip_local": "Локальний IP: {local}.", + "diagnosis_ip_local": "Локальний IP: {local}", "diagnosis_ip_global": "Глобальний IP: {global}", "diagnosis_ip_no_ipv6_tip": "Наявність робочого IPv6 не є обов'язковим для роботи вашого сервера, але це краще для здоров'я Інтернету в цілому. IPv6 зазвичай автоматично налаштовується системою або вашим провайдером, якщо він доступний. В іншому випадку вам, можливо, доведеться налаштувати деякі речі вручну, як пояснюється в документації тут: https://yunohost.org/#/ipv6. Якщо ви не можете увімкнути IPv6 або якщо це здається вам занадто технічним, ви також можете сміливо нехтувати цим попередженням.", "diagnosis_ip_no_ipv6": "Сервер не має робочого IPv6.", "diagnosis_ip_connected_ipv6": "Сервер під'єднаний до Інтернету через IPv6!", "diagnosis_ip_no_ipv4": "Сервер не має робочого IPv4.", "diagnosis_ip_connected_ipv4": "Сервер під'єднаний до Інтернету через IPv4!", - "diagnosis_no_cache": "Для категорії \"{category} 'ще немає кеша діагностики.", + "diagnosis_no_cache": "Для категорії \"{category} 'ще немає кеша діагностики", "diagnosis_failed": "Не вдалося отримати результат діагностики для категорії '{category}': {error}", "diagnosis_everything_ok": "Усе виглядає добре для {category}!", "diagnosis_found_warnings": "Знайдено {warnings} пунктів, які можна поліпшити для {category}.", @@ -518,7 +518,7 @@ "diagnosis_cache_still_valid": "(Кеш все ще дійсний для діагностики {category}. Повторна діагностика поки не проводиться!)", "diagnosis_failed_for_category": "Не вдалося провести діагностику для категорії '{category}': {error}", "diagnosis_display_tip": "Щоб побачити знайдені проблеми, ви можете перейти в розділ Діагностика в вебадміністраторі або виконати команду 'yunohost diagnosis show --issues --human-readable' з командного рядка.", - "diagnosis_package_installed_from_sury_details": "Деякі пакети були ненавмисно встановлені зі стороннього репозиторію під назвою Sury. Команда YunoHost поліпшила стратегію роботи з цими пакетами, але очікується, що в деяких системах, які встановили застосунки PHP7.3 ще на Stretch, залишаться деякі невідповідності. Щоб виправити це становище, спробуйте виконати наступну команду: {cmd_to_fix}.", + "diagnosis_package_installed_from_sury_details": "Деякі пакети були ненавмисно встановлені зі стороннього репозиторію під назвою Sury. Команда YunoHost поліпшила стратегію роботи з цими пакетами, але очікується, що в деяких системах, які встановили застосунки PHP7.3 ще на Stretch, залишаться деякі невідповідності. Щоб виправити це становище, спробуйте виконати наступну команду: {cmd_to_fix}", "diagnosis_package_installed_from_sury": "Деякі системні пакети мають бути зістарені у версії", "diagnosis_backports_in_sources_list": "Схоже, що apt (менеджер пакетів) налаштований на використання репозиторія backports. Якщо ви не знаєте, що робите, ми наполегливо не радимо встановлювати пакети з backports, тому що це може привести до нестабільності або конфліктів у вашій системі.", "diagnosis_basesystem_ynh_inconsistent_versions": "Ви використовуєте несумісні версії пакетів YunoHost... швидше за все, через невдале або часткове оновлення.", @@ -528,14 +528,14 @@ "diagnosis_basesystem_host": "Сервер працює під управлінням Debian {debian_version}", "diagnosis_basesystem_hardware_model": "Модель сервера - {model}", "diagnosis_basesystem_hardware": "Архітектура апаратного забезпечення сервера - {virt} {arch}", - "custom_app_url_required": "Ви повинні надати URL-адресу для оновлення вашого користувацького застосунку {app}.", - "confirm_app_install_thirdparty": "НЕБЕЗПЕЧНО! Цей застосунок не входить у каталог застосунків YunoHost. Установлення сторонніх застосунків може порушити цілісність і безпеку вашої системи. Вам не слід встановлювати його, якщо ви не знаєте, що робите. НІЯКОЇ ПІДТРИМКИ НЕ БУДЕ, якщо цей застосунок не буде працювати або зламає вашу систему... Якщо ви все одно готові піти на такий ризик, введіть '{answers}'.", - "confirm_app_install_danger": "НЕБЕЗПЕЧНО! Відомо, що цей застосунок все ще експериментальний (якщо не сказати, що він явно не працює)! Вам не слід встановлювати його, якщо ви не знаєте, що робите. Ніякої підтримки не буде надано, якщо цей застосунок не буде працювати або зламає вашу систему... Якщо ви все одно готові ризикнути, введіть '{answers}'.", + "custom_app_url_required": "Ви повинні надати URL-адресу для оновлення вашого користувацького застосунку {app}", + "confirm_app_install_thirdparty": "НЕБЕЗПЕЧНО! Цей застосунок не входить у каталог застосунків YunoHost. Установлення сторонніх застосунків може порушити цілісність і безпеку вашої системи. Вам не слід встановлювати його, якщо ви не знаєте, що робите. НІЯКОЇ ПІДТРИМКИ НЕ БУДЕ, якщо цей застосунок не буде працювати або зламає вашу систему... Якщо ви все одно готові піти на такий ризик, введіть '{answers}'", + "confirm_app_install_danger": "НЕБЕЗПЕЧНО! Відомо, що цей застосунок все ще експериментальний (якщо не сказати, що він явно не працює)! Вам не слід встановлювати його, якщо ви не знаєте, що робите. Ніякої підтримки не буде надано, якщо цей застосунок не буде працювати або зламає вашу систему... Якщо ви все одно готові ризикнути, введіть '{answers}'", "confirm_app_install_warning": "Попередження: Цей застосунок може працювати, але він не дуже добре інтегрований в YunoHost. Деякі функції, такі як єдина реєстрація та резервне копіювання/відновлення, можуть бути недоступні. Все одно встановити? [{answers}]. ", "certmanager_unable_to_parse_self_CA_name": "Не вдалося розібрати назву самопідписного центру (файл: {file})", "certmanager_self_ca_conf_file_not_found": "Не вдалося знайти файл конфігурації для самопідписного центру (файл: {file})", "certmanager_no_cert_file": "Не вдалося розпізнати файл сертифіката для домену {domain} (файл: {file})", - "certmanager_hit_rate_limit": "Для цього набору доменів {domain} недавно було випущено дуже багато сертифікатів. Будь ласка, спробуйте ще раз пізніше. Див. https://letsencrypt.org/docs/rate-limits/ для отримання подробиць.", + "certmanager_hit_rate_limit": "Для цього набору доменів {domain} недавно було випущено дуже багато сертифікатів. Будь ласка, спробуйте ще раз пізніше. Див. https://letsencrypt.org/docs/rate-limits/ для отримання подробиць", "certmanager_warning_subdomain_dns_record": "Піддомен '{subdomain}' не дозволяється на тій же IP-адресі, що і '{domain}'. Деякі функції будуть недоступні, поки ви не виправите це і не перестворите сертифікат.", "certmanager_domain_http_not_working": "Домен {domain}, схоже, не доступний через HTTP. Будь ласка, перевірте категорію 'Мережа' в діагностиці для отримання додаткових даних. (Якщо ви знаєте, що робите, використовуйте '--no-checks', щоб вимкнути ці перевірки).", "certmanager_domain_dns_ip_differs_from_public_ip": "DNS-записи для домену '{domain}' відрізняються від IP цього сервера. Будь ласка, перевірте категорію 'DNS-записи' (основні) в діагностиці для отримання додаткових даних. Якщо ви недавно змінили запис A, будь ласка, зачекайте, поки він пошириться (деякі програми перевірки поширення DNS доступні в Інтернеті). (Якщо ви знаєте, що робите, використовуйте '--no-checks', щоб вимкнути ці перевірки).", @@ -554,13 +554,13 @@ "backup_with_no_restore_script_for_app": "{app} не має скрипта відновлення, ви не зможете автоматично відновити резервну копію цього застосунку.", "backup_with_no_backup_script_for_app": "Застосунок '{app}' не має скрипта резервного копіювання. Нехтую ним.", "backup_unable_to_organize_files": "Неможливо використовувати швидкий спосіб для організації файлів в архіві", - "backup_system_part_failed": "Не вдалося створити резервну копію системної частини '{part}'.", + "backup_system_part_failed": "Не вдалося створити резервну копію системної частини '{part}'", "backup_running_hooks": "Запуск гачків (hook) резервного копіювання...", "backup_permission": "Дозвіл на резервне копіювання для {app}", "backup_output_symlink_dir_broken": "Ваш архівний каталог '{path}' є неробочим символічним посиланням. Можливо, ви забули перемонтувати або підключити носій, на який вона вказує.", "backup_output_directory_required": "Ви повинні вказати вихідний каталог для резервного копіювання", "backup_output_directory_not_empty": "Ви повинні вибрати порожній вихідний каталог", - "backup_output_directory_forbidden": "Виберіть інший вихідний каталог. Резервні копії не можуть бути створені в підкаталогах /bin,/boot,/dev,/etc,/lib,/root,/run,/sbin,/sys,/usr,/var або /home/yunohost.backup/archives.", + "backup_output_directory_forbidden": "Виберіть інший вихідний каталог. Резервні копії не можуть бути створені в підкаталогах /bin,/boot,/dev,/etc,/lib,/root,/run,/sbin,/sys,/usr,/var або /home/yunohost.backup/archives", "backup_nothings_done": "Нема що зберігати", "backup_no_uncompress_archive_dir": "Немає такого каталогу нестислого архіву", "backup_mount_archive_for_restore": "Підготовлення архіву для відновлення...", @@ -582,7 +582,7 @@ "backup_cleaning_failed": "Не вдалося очистити тимчасовий каталог резервного копіювання", "backup_cant_mount_uncompress_archive": "Не вдалося змонтувати нестислий архів як захищений від запису", "backup_ask_for_copying_if_needed": "Ви бажаєте тимчасово виконати резервне копіювання з використанням {size} МБ? (Цей спосіб використовується, оскільки деякі файли не можуть бути підготовлені дієвіше).", - "backup_archive_writing_error": "Не вдалося додати файли '{source}' (названі в архіві '{dest}') для резервного копіювання в стислий архів '{archive}'.", + "backup_archive_writing_error": "Не вдалося додати файли '{source}' (названі в архіві '{dest}') для резервного копіювання в стислий архів '{archive}'", "backup_archive_system_part_not_available": "Системна частина '{part}' недоступна в цій резервній копії", "backup_archive_corrupted": "Схоже, що архів резервної копії '{archive}' пошкоджений: {error}", "backup_archive_cant_retrieve_info_json": "Не вдалося завантажити відомості для архіву '{archive}'... info.json не може бути отриманий(або не є правильним json).", @@ -618,7 +618,7 @@ "app_upgrade_failed": "Не вдалося оновити {app}: {error}", "app_upgrade_app_name": "Зараз оновлюємо {app}...", "app_upgrade_several_apps": "Наступні застосунки буде оновлено: {apps}", - "app_unsupported_remote_type": "Для застосунку використовується непідтримуваний віддалений тип.", + "app_unsupported_remote_type": "Для застосунку використовується непідтримуваний віддалений тип", "app_unknown": "Невідомий застосунок", "app_start_restore": "Відновлення {app}...", "app_start_backup": "Збирання файлів для резервного копіювання {app}...", @@ -628,7 +628,7 @@ "app_restore_script_failed": "Сталася помилка всередині скрипта відновлення застосунку", "app_restore_failed": "Не вдалося відновити {app}: {error}", "app_remove_after_failed_install": "Вилучення застосунку після збою встановлення...", - "app_requirements_unmeet": "Вимоги не виконані для {app}, пакет {pkgname} ({version}) повинен бути {spec}.", + "app_requirements_unmeet": "Вимоги не виконані для {app}, пакет {pkgname} ({version}) повинен бути {spec}", "app_requirements_checking": "Перевіряння необхідних пакетів для {app}...", "app_removed": "{app} видалено", "app_not_properly_removed": "{app} не було видалено належним чином", @@ -638,5 +638,24 @@ "app_manifest_install_ask_is_public": "Чи має цей застосунок бути відкритим для анонімних відвідувачів?", "app_manifest_install_ask_admin": "Виберіть користувача-адміністратора для цього застосунку", "app_manifest_install_ask_password": "Виберіть пароль адміністратора для цього застосунку", - "diagnosis_description_apps": "Застосунки" + "diagnosis_description_apps": "Застосунки", + "user_import_success": "Користувачів успішно імпортовано", + "user_import_nothing_to_do": "Не потрібно імпортувати жодного користувача", + "user_import_failed": "Операція імпорту користувачів цілковито не вдалася", + "user_import_partial_failed": "Операція імпорту користувачів частково не вдалася", + "user_import_missing_columns": "Відсутні такі стовпці: {columns}", + "user_import_bad_file": "Ваш файл CSV неправильно відформатовано, він буде знехтуваний, щоб уникнути потенційної втрати даних", + "user_import_bad_line": "Неправильний рядок {line}: {details}", + "invalid_password": "Недійсний пароль", + "log_user_import": "Імпорт користувачів", + "ldap_server_is_down_restart_it": "Службу LDAP вимкнено, спробуйте перезапустити її...", + "ldap_server_down": "Не вдається під'єднатися до сервера LDAP", + "global_settings_setting_security_experimental_enabled": "Увімкнути експериментальні функції безпеки (не вмикайте це, якщо ви не знаєте, що робите!)", + "diagnosis_apps_deprecated_practices": "Установлена версія цього застосунку все ще використовує деякі надзастарілі практики упакування. Вам дійсно варто подумати про його оновлення.", + "diagnosis_apps_outdated_ynh_requirement": "Установлена версія цього застосунку вимагає лише Yunohost >= 2.x, що, як правило, вказує на те, що воно не відповідає сучасним рекомендаційним практикам упакування та порадникам. Вам дійсно варто подумати про його оновлення.", + "diagnosis_apps_bad_quality": "Цей застосунок наразі позначено як зламаний у каталозі застосунків YunoHost. Це може бути тимчасовою проблемою, поки організатори намагаються вирішити цю проблему. Тим часом оновлення цього застосунку вимкнено.", + "diagnosis_apps_broken": "Цей застосунок наразі позначено як зламаний у каталозі застосунків YunoHost. Це може бути тимчасовою проблемою, поки організатори намагаються вирішити цю проблему. Тим часом оновлення цього застосунку вимкнено.", + "diagnosis_apps_not_in_app_catalog": "Цей застосунок не міститься у каталозі застосунків YunoHost. Якщо він був у минулому і був видалений, вам слід подумати про видалення цього застосунку, оскільки він не отримає оновлення, і це може поставити під загрозу цілісність та безпеку вашої системи.", + "diagnosis_apps_issue": "Виявлено проблему із застосунком {app}", + "diagnosis_apps_allgood": "Усі встановлені застосунки дотримуються основних способів упакування" } From 8eaaf0975afd8d3bfb15b8a65d8d3a851774c8c8 Mon Sep 17 00:00:00 2001 From: ljf Date: Sat, 4 Sep 2021 19:03:07 +0200 Subject: [PATCH 2912/3170] [enh] yes/no args on boolean aquestion + semantic --- src/yunohost/utils/config.py | 592 +++++++++++++++++++---------------- 1 file changed, 322 insertions(+), 270 deletions(-) diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 8fcf493ed..def083cdc 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -79,12 +79,15 @@ class ConfigPanel: result = {} for panel, section, option in self._iterate(): key = f"{panel['id']}.{section['id']}.{option['id']}" - if mode == "export": - result[option["id"]] = option.get("current_value") - else: - result[key] = {"ask": _value_for_locale(option["ask"])} - if "current_value" in option: - result[key]["value"] = option["current_value"] + if mode == 'export': + result[option['id']] = option.get('current_value') + elif 'ask' in option: + result[key] = {'ask': _value_for_locale(option['ask'])} + elif 'i18n' in self.config: + result[key] = {'ask': m18n.n(self.config['i18n'] + '_' + option['id'])} + if 'current_value' in option: + question_class = ARGUMENTS_TYPE_PARSERS[option.get("type", "string")] + result[key]['value'] = question_class.humanize(option['current_value'], option) return result @@ -138,7 +141,7 @@ class ConfigPanel: raise finally: # Delete files uploaded from API - FileArgumentParser.clean_upload_dirs() + FileQuestion.clean_upload_dirs() if self.errors: return { @@ -274,16 +277,42 @@ class ConfigPanel: parse_args_in_yunohost_format(self.args, section["options"]) ) self.new_values = { - key: str(value[0]) + key: value[0] for key, value in self.new_values.items() if not value[0] is None } + self.errors = None + + def _get_default_values(self): + return { option['id']: option['default'] + for _, _, option in self._iterate() if 'default' in option } + + def _load_current_values(self): + """ + Retrieve entries in YAML file + And set default values if needed + """ + + # Retrieve entries in the YAML + on_disk_settings = {} + if os.path.exists(self.save_path) and os.path.isfile(self.save_path): + on_disk_settings = read_yaml(self.save_path) or {} + + # Inject defaults if needed (using the magic .update() ;)) + self.values = self._get_default_values() + self.values.update(on_disk_settings) def _apply(self): - logger.info("Running config script...") + logger.info("Saving the new configuration...") dir_path = os.path.dirname(os.path.realpath(self.save_path)) if not os.path.exists(dir_path): mkdir(dir_path, mode=0o700) + + values_to_save = {**self.values, **self.new_values} + if self.save_mode == 'diff': + defaults = self._get_default_values() + values_to_save = {k: v for k, v in values_to_save.items() if defaults.get(k) != v} + # Save the settings to the .yaml file write_to_yaml(self.save_path, self.new_values) @@ -291,15 +320,16 @@ class ConfigPanel: from yunohost.service import _run_service_command, _get_services - logger.info("Reloading services...") services_to_reload = set() for panel, section, obj in self._iterate(["panel", "section", "option"]): services_to_reload |= set(obj.get("services", [])) services_to_reload = list(services_to_reload) services_to_reload.sort(key="nginx".__eq__) + if services_to_reload: + logger.info("Reloading services...") for service in services_to_reload: - if "__APP__": + if "__APP__" in service: service = service.replace("__APP__", self.app) logger.debug(f"Reloading {service}") if not _run_service_command("reload-or-restart", service): @@ -322,140 +352,138 @@ class ConfigPanel: yield (panel, section, option) -class Question: - "empty class to store questions information" - - -class YunoHostArgumentFormatParser(object): +class Question(object): hide_user_input_in_prompt = False operation_logger = None - def parse_question(self, question, user_answers): - parsed_question = Question() - - parsed_question.name = question["name"] - parsed_question.type = question.get("type", "string") - parsed_question.default = question.get("default", None) - parsed_question.current_value = question.get("current_value") - parsed_question.optional = question.get("optional", False) - parsed_question.choices = question.get("choices", []) - parsed_question.pattern = question.get("pattern") - parsed_question.ask = question.get("ask", {"en": f"{parsed_question.name}"}) - parsed_question.help = question.get("help") - parsed_question.helpLink = question.get("helpLink") - parsed_question.value = user_answers.get(parsed_question.name) - parsed_question.redact = question.get("redact", False) + def __init__(self, question, user_answers): + self.name = question["name"] + self.type = question.get("type", 'string') + self.default = question.get("default", None) + self.current_value = question.get("current_value") + self.optional = question.get("optional", False) + self.choices = question.get("choices", []) + self.pattern = question.get("pattern") + self.ask = question.get("ask", {'en': self.name}) + self.help = question.get("help") + self.helpLink = question.get("helpLink") + self.value = user_answers.get(self.name) + self.redact = question.get('redact', False) # Empty value is parsed as empty string - if parsed_question.default == "": - parsed_question.default = None + if self.default == "": + self.default = None - return parsed_question + @staticmethod + def humanize(value, option={}): + return str(value) - def parse(self, question, user_answers): - question = self.parse_question(question, user_answers) + @staticmethod + def normalize(value, option={}): + return value + + def ask_if_needed(self): while True: # Display question if no value filled or if it's a readonly message - if Moulinette.interface.type == "cli": - text_for_user_input_in_cli = self._format_text_for_user_input_in_cli( - question - ) + if Moulinette.interface.type== 'cli': + text_for_user_input_in_cli = self._format_text_for_user_input_in_cli() if getattr(self, "readonly", False): Moulinette.display(text_for_user_input_in_cli) - elif question.value is None: + elif self.value is None: prefill = "" - if question.current_value is not None: - prefill = question.current_value - elif question.default is not None: - prefill = question.default - question.value = Moulinette.prompt( + if self.current_value is not None: + prefill = self.humanize(self.current_value, self) + elif self.default is not None: + prefill = self.default + self.value = Moulinette.prompt( message=text_for_user_input_in_cli, is_password=self.hide_user_input_in_prompt, confirm=self.hide_user_input_in_prompt, prefill=prefill, - is_multiline=(question.type == "text"), + is_multiline=(self.type == "text"), ) + # Normalization + # This is done to enforce a certain formating like for boolean + self.value = self.normalize(self.value, self) + # Apply default value - if question.value in [None, ""] and question.default is not None: - question.value = ( + if self.value in [None, ""] and self.default is not None: + self.value = ( getattr(self, "default_value", None) - if question.default is None - else question.default + if self.default is None + else self.default ) # Prevalidation try: - self._prevalidate(question) + self._prevalidate() except YunohostValidationError as e: if Moulinette.interface.type == "api": raise Moulinette.display(str(e), "error") - question.value = None + self.value = None continue break - # this is done to enforce a certain formating like for boolean - # by default it doesn't do anything - question.value = self._post_parse_value(question) + self.value = self._post_parse_value() - return (question.value, self.argument_type) + return (self.value, self.argument_type) - def _prevalidate(self, question): - if question.value in [None, ""] and not question.optional: - raise YunohostValidationError("app_argument_required", name=question.name) + + def _prevalidate(self): + if self.value in [None, ""] and not self.optional: + raise YunohostValidationError("app_argument_required", name=self.name) # we have an answer, do some post checks - if question.value is not None: - if question.choices and question.value not in question.choices: - self._raise_invalid_answer(question) - if question.pattern and not re.match( - question.pattern["regexp"], str(question.value) - ): + if self.value is not None: + if self.choices and self.value not in self.choices: + self._raise_invalid_answer() + if self.pattern and not re.match(self.pattern['regexp'], str(self.value)): raise YunohostValidationError( - question.pattern["error"], - name=question.name, - value=question.value, + self.pattern['error'], + name=self.name, + value=self.value, ) - def _raise_invalid_answer(self, question): + def _raise_invalid_answer(self): raise YunohostValidationError( "app_argument_choice_invalid", - name=question.name, - value=question.value, - choices=", ".join(question.choices), + name=self.name, + value=self.value, + choices=", ".join(self.choices), ) - def _format_text_for_user_input_in_cli(self, question): - text_for_user_input_in_cli = _value_for_locale(question.ask) + def _format_text_for_user_input_in_cli(self): + text_for_user_input_in_cli = _value_for_locale(self.ask) - if question.choices: - text_for_user_input_in_cli += " [{0}]".format(" | ".join(question.choices)) + if self.choices: + text_for_user_input_in_cli += " [{0}]".format(" | ".join(self.choices)) - if question.help or question.helpLink: + if self.help or self.helpLink: text_for_user_input_in_cli += ":\033[m" - if question.help: + if self.help: text_for_user_input_in_cli += "\n - " - text_for_user_input_in_cli += _value_for_locale(question.help) - if question.helpLink: - if not isinstance(question.helpLink, dict): - question.helpLink = {"href": question.helpLink} - text_for_user_input_in_cli += f"\n - See {question.helpLink['href']}" + text_for_user_input_in_cli += _value_for_locale(self.help) + if self.helpLink: + if not isinstance(self.helpLink, dict): + self.helpLink = {"href": self.helpLink} + text_for_user_input_in_cli += f"\n - See {self.helpLink['href']}" return text_for_user_input_in_cli - def _post_parse_value(self, question): - if not question.redact: - return question.value + def _post_parse_value(self): + if not self.redact: + return self.value # Tell the operation_logger to redact all password-type / secret args # Also redact the % escaped version of the password that might appear in # the 'args' section of metadata (relevant for password with non-alphanumeric char) data_to_redact = [] - if question.value and isinstance(question.value, str): - data_to_redact.append(question.value) - if question.current_value and isinstance(question.current_value, str): - data_to_redact.append(question.current_value) + if self.value and isinstance(self.value, str): + data_to_redact.append(self.value) + if self.current_value and isinstance(self.current_value, str): + data_to_redact.append(self.current_value) data_to_redact += [ urllib.parse.quote(data) for data in data_to_redact @@ -464,50 +492,60 @@ class YunoHostArgumentFormatParser(object): if self.operation_logger: self.operation_logger.data_to_redact.extend(data_to_redact) elif data_to_redact: - raise YunohostError("app_argument_cant_redact", arg=question.name) + raise YunohostError("app_argument_cant_redact", arg=self.name) - return question.value + return self.value -class StringArgumentParser(YunoHostArgumentFormatParser): +class StringQuestion(Question): argument_type = "string" default_value = "" -class TagsArgumentParser(YunoHostArgumentFormatParser): +class TagsQuestion(Question): argument_type = "tags" - def _prevalidate(self, question): - values = question.value - for value in values.split(","): - question.value = value - super()._prevalidate(question) - question.value = values + @staticmethod + def humanize(value, option={}): + if isinstance(value, list): + return ','.join(value) + return value + + def _prevalidate(self): + values = self.value + if isinstance(values, str): + values = values.split(",") + for value in values: + self.value = value + super()._prevalidate() + self.value = values -class PasswordArgumentParser(YunoHostArgumentFormatParser): +class PasswordQuestion(Question): hide_user_input_in_prompt = True argument_type = "password" default_value = "" forbidden_chars = "{}" - def parse_question(self, question, user_answers): - question = super(PasswordArgumentParser, self).parse_question( - question, user_answers - ) - question.redact = True - if question.default is not None: + def __init__(self, question, user_answers): + super().__init__(question, user_answers) + self.redact = True + if self.default is not None: raise YunohostValidationError( - "app_argument_password_no_default", name=question.name + "app_argument_password_no_default", name=self.name ) - return question + @staticmethod + def humanize(value, option={}): + if value: + return '***' # Avoid to display the password on screen + return "" - def _prevalidate(self, question): - super()._prevalidate(question) + def _prevalidate(self): + super()._prevalidate() - if question.value is not None: - if any(char in question.value for char in self.forbidden_chars): + if self.value is not None: + if any(char in self.value for char in self.forbidden_chars): raise YunohostValidationError( "pattern_password_app", forbidden_chars=self.forbidden_chars ) @@ -515,184 +553,214 @@ class PasswordArgumentParser(YunoHostArgumentFormatParser): # If it's an optional argument the value should be empty or strong enough from yunohost.utils.password import assert_password_is_strong_enough - assert_password_is_strong_enough("user", question.value) + assert_password_is_strong_enough("user", self.value) -class PathArgumentParser(YunoHostArgumentFormatParser): +class PathQuestion(Question): argument_type = "path" default_value = "" -class BooleanArgumentParser(YunoHostArgumentFormatParser): +class BooleanQuestion(Question): argument_type = "boolean" default_value = False + yes_answers = ["1", "yes", "y", "true", "t", "on"] + no_answers = ["0", "no", "n", "false", "f", "off"] - def parse_question(self, question, user_answers): - question = super().parse_question(question, user_answers) + @staticmethod + def humanize(value, option={}): + yes = option.get('yes', 1) + no = option.get('no', 0) + value = str(value).lower() + if value == str(yes).lower(): + return 'yes' + if value == str(no).lower(): + return 'no' + if value in BooleanQuestion.yes_answers: + return 'yes' + if value in BooleanQuestion.no_answers: + return 'no' - if question.default is None: - question.default = False + if value in ['none', ""]: + return '' - return question + raise YunohostValidationError( + "app_argument_choice_invalid", + name=self.name, + value=self.value, + choices="yes, no, y, n, 1, 0", + ) - def _format_text_for_user_input_in_cli(self, question): - text_for_user_input_in_cli = _value_for_locale(question.ask) + @staticmethod + def normalize(value, option={}): + yes = option.get('yes', 1) + no = option.get('no', 0) + + if str(value).lower() in BooleanQuestion.yes_answers: + return yes + + if str(value).lower() in BooleanQuestion.no_answers: + return no + + if value in [None, ""]: + return None + raise YunohostValidationError( + "app_argument_choice_invalid", + name=self.name, + value=self.value, + choices="yes, no, y, n, 1, 0", + ) + + def __init__(self, question, user_answers): + super().__init__(question, user_answers) + self.yes = question.get('yes', 1) + self.no = question.get('no', 0) + if self.default is None: + self.default = False + + + def _format_text_for_user_input_in_cli(self): + text_for_user_input_in_cli = _value_for_locale(self.ask) text_for_user_input_in_cli += " [yes | no]" - if question.default is not None: - formatted_default = "yes" if question.default else "no" + if self.default is not None: + formatted_default = self.humanize(self.default) text_for_user_input_in_cli += " (default: {0})".format(formatted_default) return text_for_user_input_in_cli - def _post_parse_value(self, question): - if isinstance(question.value, bool): - return 1 if question.value else 0 - - if str(question.value).lower() in ["1", "yes", "y", "true"]: - return 1 - - if str(question.value).lower() in ["0", "no", "n", "false"]: - return 0 - - raise YunohostValidationError( - "app_argument_choice_invalid", - name=question.name, - value=question.value, - choices="yes, no, y, n, 1, 0", - ) + def get(self, key, default=None): + try: + return getattr(self, key) + except AttributeError: + return default -class DomainArgumentParser(YunoHostArgumentFormatParser): +class DomainQuestion(Question): argument_type = "domain" - def parse_question(self, question, user_answers): + def __init__(self, question, user_answers): from yunohost.domain import domain_list, _get_maindomain - question = super(DomainArgumentParser, self).parse_question( - question, user_answers - ) + super().__init__(question, user_answers) - if question.default is None: - question.default = _get_maindomain() + if self.default is None: + self.default = _get_maindomain() - question.choices = domain_list()["domains"] + self.choices = domain_list()["domains"] - return question - def _raise_invalid_answer(self, question): + def _raise_invalid_answer(self): raise YunohostValidationError( - "app_argument_invalid", field=question.name, error=m18n.n("domain_unknown") + "app_argument_invalid", field=self.name, error=m18n.n("domain_unknown") ) -class UserArgumentParser(YunoHostArgumentFormatParser): +class UserQuestion(Question): argument_type = "user" - def parse_question(self, question, user_answers): + def __init__(self, question, user_answers): from yunohost.user import user_list, user_info from yunohost.domain import _get_maindomain - question = super(UserArgumentParser, self).parse_question( - question, user_answers - ) - question.choices = user_list()["users"] - if question.default is None: + super().__init__(question, user_answers) + self.choices = user_list()["users"] + if self.default is None: root_mail = "root@%s" % _get_maindomain() - for user in question.choices.keys(): + for user in self.choices.keys(): if root_mail in user_info(user).get("mail-aliases", []): - question.default = user + self.default = user break - return question - def _raise_invalid_answer(self, question): + def _raise_invalid_answer(self): raise YunohostValidationError( "app_argument_invalid", - field=question.name, - error=m18n.n("user_unknown", user=question.value), + field=self.name, + error=m18n.n("user_unknown", user=self.value), ) -class NumberArgumentParser(YunoHostArgumentFormatParser): +class NumberQuestion(Question): argument_type = "number" default_value = "" - def parse_question(self, question, user_answers): - question_parsed = super().parse_question(question, user_answers) - question_parsed.min = question.get("min", None) - question_parsed.max = question.get("max", None) - if question_parsed.default is None: - question_parsed.default = 0 + @staticmethod + def humanize(value, option={}): + return str(value) - return question_parsed + def __init__(self, question, user_answers): + super().__init__(question, user_answers) + self.min = question.get("min", None) + self.max = question.get("max", None) + self.step = question.get("step", None) - def _prevalidate(self, question): - super()._prevalidate(question) - if not isinstance(question.value, int) and not ( - isinstance(question.value, str) and question.value.isdigit() + + def _prevalidate(self): + super()._prevalidate() + if not isinstance(self.value, int) and not ( + isinstance(self.value, str) and self.value.isdigit() ): raise YunohostValidationError( "app_argument_invalid", - field=question.name, + field=self.name, error=m18n.n("invalid_number"), ) - if question.min is not None and int(question.value) < question.min: + if self.min is not None and int(self.value) < self.min: raise YunohostValidationError( "app_argument_invalid", - field=question.name, + field=self.name, error=m18n.n("invalid_number"), ) - if question.max is not None and int(question.value) > question.max: + if self.max is not None and int(self.value) > self.max: raise YunohostValidationError( "app_argument_invalid", - field=question.name, + field=self.name, error=m18n.n("invalid_number"), ) - def _post_parse_value(self, question): - if isinstance(question.value, int): - return super()._post_parse_value(question) + def _post_parse_value(self): + if isinstance(self.value, int): + return super()._post_parse_value() - if isinstance(question.value, str) and question.value.isdigit(): - return int(question.value) + if isinstance(self.value, str) and self.value.isdigit(): + return int(self.value) raise YunohostValidationError( - "app_argument_invalid", field=question.name, error=m18n.n("invalid_number") + "app_argument_invalid", field=self.name, error=m18n.n("invalid_number") ) -class DisplayTextArgumentParser(YunoHostArgumentFormatParser): +class DisplayTextQuestion(Question): argument_type = "display_text" readonly = True - def parse_question(self, question, user_answers): - question_parsed = super().parse_question(question, user_answers) + def __init__(self, question, user_answers): + super().__init__(question, user_answers) - question_parsed.optional = True - question_parsed.style = question.get("style", "info") + self.optional = True + self.style = question.get("style", "info") - return question_parsed - def _format_text_for_user_input_in_cli(self, question): - text = question.ask["en"] + def _format_text_for_user_input_in_cli(self): + text = self.ask["en"] - if question.style in ["success", "info", "warning", "danger"]: + if self.style in ["success", "info", "warning", "danger"]: color = { "success": "green", "info": "cyan", "warning": "yellow", "danger": "red", } - return colorize(m18n.g(question.style), color[question.style]) + f" {text}" + return colorize(m18n.g(self.style), color[self.style]) + f" {text}" else: return text -class FileArgumentParser(YunoHostArgumentFormatParser): +class FileQuestion(Question): argument_type = "file" upload_dirs = [] @@ -704,71 +772,54 @@ class FileArgumentParser(YunoHostArgumentFormatParser): if os.path.exists(upload_dir): shutil.rmtree(upload_dir) - def parse_question(self, question, user_answers): - question_parsed = super().parse_question(question, user_answers) - if question.get("accept"): - question_parsed.accept = question.get("accept").replace(" ", "").split(",") + def __init__(self, question, user_answers): + super().__init__(question, user_answers) + if self.get("accept"): + self.accept = question.get("accept").replace(" ", "").split(",") else: - question_parsed.accept = [] - if Moulinette.interface.type == "api": - if user_answers.get(f"{question_parsed.name}[name]"): - question_parsed.value = { - "content": question_parsed.value, - "filename": user_answers.get( - f"{question_parsed.name}[name]", question_parsed.name - ), + self.accept = [] + if Moulinette.interface.type== "api": + if user_answers.get(f"{self.name}[name]"): + self.value = { + "content": self.value, + "filename": user_answers.get(f"{self.name}[name]", self.name), } # If path file are the same - if ( - question_parsed.value - and str(question_parsed.value) == question_parsed.current_value - ): - question_parsed.value = None + if self.value and str(self.value) == self.current_value: + self.value = None - return question_parsed - def _prevalidate(self, question): - super()._prevalidate(question) - if ( - isinstance(question.value, str) - and question.value - and not os.path.exists(question.value) - ): + def _prevalidate(self): + super()._prevalidate() + if isinstance(self.value, str) and self.value and not os.path.exists(self.value): raise YunohostValidationError( - "app_argument_invalid", - field=question.name, - error=m18n.n("invalid_number1"), + "app_argument_invalid", field=self.name, error=m18n.n("invalid_number1") ) - if question.value in [None, ""] or not question.accept: + if self.value in [None, ""] or not self.accept: return - filename = ( - question.value - if isinstance(question.value, str) - else question.value["filename"] - ) - if "." not in filename or "." + filename.split(".")[-1] not in question.accept: + filename = self.value if isinstance(self.value, str) else self.value["filename"] + if "." not in filename or "." + filename.split(".")[-1] not in self.accept: raise YunohostValidationError( - "app_argument_invalid", - field=question.name, - error=m18n.n("invalid_number2"), + "app_argument_invalid", field=self.name, error=m18n.n("invalid_number2") ) - def _post_parse_value(self, question): + + def _post_parse_value(self): from base64 import b64decode # Upload files from API # A file arg contains a string with "FILENAME:BASE64_CONTENT" - if not question.value: - return question.value + if not self.value: + return self.value if Moulinette.interface.type == "api": upload_dir = tempfile.mkdtemp(prefix="tmp_configpanel_") - FileArgumentParser.upload_dirs += [upload_dir] - filename = question.value["filename"] + FileQuestion.upload_dirs += [upload_dir] + filename = self.value["filename"] logger.debug( - f"Save uploaded file {question.value['filename']} from API into {upload_dir}" + f"Save uploaded file {self.value['filename']} from API into {upload_dir}" ) # Filename is given by user of the API. For security reason, we have replaced @@ -781,7 +832,7 @@ class FileArgumentParser(YunoHostArgumentFormatParser): while os.path.exists(file_path): file_path = os.path.normpath(upload_dir + "/" + filename + (".%d" % i)) i += 1 - content = question.value["content"] + content = self.value["content"] try: with open(file_path, "wb") as f: f.write(b64decode(content)) @@ -789,31 +840,31 @@ class FileArgumentParser(YunoHostArgumentFormatParser): raise YunohostError("cannot_write_file", file=file_path, error=str(e)) except Exception as e: raise YunohostError("error_writing_file", file=file_path, error=str(e)) - question.value = file_path - return question.value + self.value = file_path + return self.value ARGUMENTS_TYPE_PARSERS = { - "string": StringArgumentParser, - "text": StringArgumentParser, - "select": StringArgumentParser, - "tags": TagsArgumentParser, - "email": StringArgumentParser, - "url": StringArgumentParser, - "date": StringArgumentParser, - "time": StringArgumentParser, - "color": StringArgumentParser, - "password": PasswordArgumentParser, - "path": PathArgumentParser, - "boolean": BooleanArgumentParser, - "domain": DomainArgumentParser, - "user": UserArgumentParser, - "number": NumberArgumentParser, - "range": NumberArgumentParser, - "display_text": DisplayTextArgumentParser, - "alert": DisplayTextArgumentParser, - "markdown": DisplayTextArgumentParser, - "file": FileArgumentParser, + "string": StringQuestion, + "text": StringQuestion, + "select": StringQuestion, + "tags": TagsQuestion, + "email": StringQuestion, + "url": StringQuestion, + "date": StringQuestion, + "time": StringQuestion, + "color": StringQuestion, + "password": PasswordQuestion, + "path": PathQuestion, + "boolean": BooleanQuestion, + "domain": DomainQuestion, + "user": UserQuestion, + "number": NumberQuestion, + "range": NumberQuestion, + "display_text": DisplayTextQuestion, + "alert": DisplayTextQuestion, + "markdown": DisplayTextQuestion, + "file": FileQuestion, } @@ -831,10 +882,11 @@ def parse_args_in_yunohost_format(user_answers, argument_questions): parsed_answers_dict = OrderedDict() for question in argument_questions: - parser = ARGUMENTS_TYPE_PARSERS[question.get("type", "string")]() + question_class = ARGUMENTS_TYPE_PARSERS[question.get("type", "string")] + question = question_class(question, user_answers) - answer = parser.parse(question=question, user_answers=user_answers) + answer = question.ask_if_needed() if answer is not None: - parsed_answers_dict[question["name"]] = answer + parsed_answers_dict[question.name] = answer return parsed_answers_dict From 4c9fcdc9e447f9fffbcd1e32127c849df304f912 Mon Sep 17 00:00:00 2001 From: ljf Date: Sat, 4 Sep 2021 20:12:56 +0200 Subject: [PATCH 2913/3170] [fix] Get / set app config panel --- src/yunohost/app.py | 3 ++- src/yunohost/utils/config.py | 11 ++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index bc84d0da0..5a8c6cd56 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1800,7 +1800,8 @@ class AppConfigPanel(ConfigPanel): self.values = self._call_config_script("show") def _apply(self): - self.errors = self._call_config_script("apply", self.new_values) + env = {key: str(value) for key, value in self.new_values.items()} + self.errors = self._call_config_script("apply", env=env) def _call_config_script(self, action, env={}): from yunohost.hook import hook_exec diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index def083cdc..2b7282259 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -81,10 +81,11 @@ class ConfigPanel: key = f"{panel['id']}.{section['id']}.{option['id']}" if mode == 'export': result[option['id']] = option.get('current_value') - elif 'ask' in option: - result[key] = {'ask': _value_for_locale(option['ask'])} - elif 'i18n' in self.config: - result[key] = {'ask': m18n.n(self.config['i18n'] + '_' + option['id'])} + else: + if 'ask' in option: + result[key] = {'ask': _value_for_locale(option['ask'])} + elif 'i18n' in self.config: + result[key] = {'ask': m18n.n(self.config['i18n'] + '_' + option['id'])} if 'current_value' in option: question_class = ARGUMENTS_TYPE_PARSERS[option.get("type", "string")] result[key]['value'] = question_class.humanize(option['current_value'], option) @@ -774,7 +775,7 @@ class FileQuestion(Question): def __init__(self, question, user_answers): super().__init__(question, user_answers) - if self.get("accept"): + if question.get("accept"): self.accept = question.get("accept").replace(" ", "").split(",") else: self.accept = [] From c8791a98340acd28566a818244bba4c886fdd68e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 4 Sep 2021 20:21:30 +0200 Subject: [PATCH 2914/3170] Add config check during service_reload_or_restart --- src/yunohost/app.py | 14 ++++---------- src/yunohost/service.py | 25 ++++++++++++++++++++++++- src/yunohost/tests/test_service.py | 22 ++++++++++++++++++++++ src/yunohost/utils/config.py | 14 +++----------- 4 files changed, 53 insertions(+), 22 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 83ff27cdf..23f3b011b 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -52,7 +52,6 @@ from moulinette.utils.filesystem import ( mkdir, ) -from yunohost.service import service_status, _run_service_command from yunohost.utils import packages from yunohost.utils.config import ( ConfigPanel, @@ -424,6 +423,7 @@ def app_change_url(operation_logger, app, domain, path): """ from yunohost.hook import hook_exec, hook_callback + from yunohost.service import service_reload_or_restart installed = _is_installed(app) if not installed: @@ -492,15 +492,7 @@ def app_change_url(operation_logger, app, domain, path): app_ssowatconf() - # avoid common mistakes - if _run_service_command("reload", "nginx") is False: - # grab nginx errors - # the "exit 0" is here to avoid check_output to fail because 'nginx -t' - # will return != 0 since we are in a failed state - nginx_errors = check_output("nginx -t; exit 0") - raise YunohostError( - "app_change_url_failed_nginx_reload", nginx_errors=nginx_errors - ) + service_reload_or_restart("nginx") logger.success(m18n.n("app_change_url_success", app=app, domain=domain, path=path)) @@ -2883,6 +2875,8 @@ def unstable_apps(): def _assert_system_is_sane_for_app(manifest, when): + from yunohost.service import service_status + logger.debug("Checking that required services are up and running...") services = manifest.get("services", []) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index fb12e9053..da3bded3c 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -256,7 +256,7 @@ def service_restart(names): ) -def service_reload_or_restart(names): +def service_reload_or_restart(names, test_conf=True): """ Reload one or more services if they support it. If not, restart them instead. If the services are not running yet, they will be started. @@ -266,7 +266,30 @@ def service_reload_or_restart(names): """ if isinstance(names, str): names = [names] + + services = _get_services() + for name in names: + + logger.debug(f"Reloading service {name}") + + test_conf_cmd = services.get(service, {}).get("test_conf") + if test_conf and test_conf_cmd: + + p = subprocess.Popen( + test_conf_cmd, + shell=True, + executable="/bin/bash", + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) + + out, _ = p.communicate() + if p.returncode != 0: + errors = out.strip().split("\n") + logger.error(m18n.("service_not_reloading_because_conf_broken", errors=errors)) + continue + if _run_service_command("reload-or-restart", name): logger.success(m18n.n("service_reloaded_or_restarted", service=name)) else: diff --git a/src/yunohost/tests/test_service.py b/src/yunohost/tests/test_service.py index 1f82dc8fd..1007419a1 100644 --- a/src/yunohost/tests/test_service.py +++ b/src/yunohost/tests/test_service.py @@ -9,6 +9,7 @@ from yunohost.service import ( service_add, service_remove, service_log, + service_reload_or_restart, ) @@ -38,6 +39,10 @@ def clean(): _save_services(services) + if os.path.exists("/etc/nginx/conf.d/broken.conf"): + os.remove("/etc/nginx/conf.d/broken.conf") + os.system("systemctl reload-or-restart nginx") + def test_service_status_all(): @@ -118,3 +123,20 @@ def test_service_update_to_remove_properties(): assert _get_services()["dummyservice"].get("test_status") == "false" service_add("dummyservice", description="dummy", test_status="") assert not _get_services()["dummyservice"].get("test_status") + + +def test_service_conf_broken(): + + os.system("echo pwet > /etc/nginx/conf.d/broken.conf") + + status = service_status("nginx") + assert status["status"] == "running" + assert status["configuration"] == "broken" + assert "broken.conf" in status["configuration-details"] + + # Service reload-or-restart should check that the conf ain't valid + # before reload-or-restart, hence the service should still be running + service_reload_or_restart("nginx") + assert status["status"] == "running" + + os.remove("/etc/nginx/conf.d/broken.conf") diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 8fcf493ed..9cdb30119 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -289,7 +289,7 @@ class ConfigPanel: def _reload_services(self): - from yunohost.service import _run_service_command, _get_services + from yunohost.service import service_reload_or_restart logger.info("Reloading services...") services_to_reload = set() @@ -299,16 +299,8 @@ class ConfigPanel: services_to_reload = list(services_to_reload) services_to_reload.sort(key="nginx".__eq__) for service in services_to_reload: - if "__APP__": - service = service.replace("__APP__", self.app) - logger.debug(f"Reloading {service}") - if not _run_service_command("reload-or-restart", service): - services = _get_services() - test_conf = services[service].get("test_conf", "true") - errors = check_output(f"{test_conf}; exit 0") if test_conf else "" - raise YunohostError( - "config_failed_service_reload", service=service, errors=errors - ) + service = service.replace("__APP__", self.app) + service_reload_or_restart(service) def _iterate(self, trigger=["option"]): for panel in self.config.get("panels", []): From 778c67540cbd6abe25c2735ef3a80494aa80e10e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 4 Sep 2021 20:22:28 +0200 Subject: [PATCH 2915/3170] Misc fixes, try to implement locale strings --- locales/en.json | 6 +++++- src/yunohost/app.py | 4 ++-- src/yunohost/utils/config.py | 37 ++++++++++++++++++++---------------- 3 files changed, 28 insertions(+), 19 deletions(-) diff --git a/locales/en.json b/locales/en.json index a5652f378..667a8d4b3 100644 --- a/locales/en.json +++ b/locales/en.json @@ -21,6 +21,8 @@ "app_change_url_identical_domains": "The old and new domain/url_path are identical ('{domain}{path}'), nothing to do.", "app_change_url_no_script": "The app '{app_name}' doesn't support URL modification yet. Maybe you should upgrade it.", "app_change_url_success": "{app} URL is now {domain}{path}", + "app_config_unable_to_apply": "Failed to apply config panel values.", + "app_config_unable_to_read": "Failed to read config panel values.", "app_extraction_failed": "Could not extract the installation files", "app_full_domain_unavailable": "Sorry, this app must be installed on a domain of its own, but other apps are already installed on the domain '{domain}'. You could use a subdomain dedicated to this app instead.", "app_id_invalid": "Invalid app ID", @@ -139,6 +141,7 @@ "certmanager_self_ca_conf_file_not_found": "Could not find configuration file for self-signing authority (file: {file})", "certmanager_unable_to_parse_self_CA_name": "Could not parse name of self-signing authority (file: {file})", "certmanager_warning_subdomain_dns_record": "Subdomain '{subdomain}' does not resolve to the same IP address as '{domain}'. Some features will not be available until you fix this and regenerate the certificate.", + "config_apply_failed": "Applying the new configuration failed: {error}", "confirm_app_install_danger": "DANGER! This app is known to be still experimental (if not explicitly not working)! You should probably NOT install it unless you know what you are doing. NO SUPPORT will be provided if this app doesn't work or breaks your system... If you are willing to take that risk anyway, type '{answers}'", "confirm_app_install_thirdparty": "DANGER! This app is not part of YunoHost's app catalog. Installing third-party apps may compromise the integrity and security of your system. You should probably NOT install it unless you know what you are doing. NO SUPPORT will be provided if this app doesn't work or breaks your system... If you are willing to take that risk anyway, type '{answers}'", "confirm_app_install_warning": "Warning: This app may work, but is not well-integrated in YunoHost. Some features such as single sign-on and backup/restore might not be available. Install anyway? [{answers}] ", @@ -389,8 +392,8 @@ "log_app_change_url": "Change the URL of the '{}' app", "log_app_config_apply": "Apply config to the '{}' app", "log_app_config_get": "Get a specific setting from config panel of the '{}' app", - "log_app_config_show": "Show the config panel of the '{}' app", "log_app_config_set": "Apply config to the '{}' app", + "log_app_config_show": "Show the config panel of the '{}' app", "log_app_install": "Install the '{}' app", "log_app_makedefault": "Make '{}' the default app", "log_app_remove": "Remove the '{}' app", @@ -596,6 +599,7 @@ "service_disabled": "The service '{service}' will not be started anymore when system boots.", "service_enable_failed": "Could not make the service '{service}' automatically start at boot.\n\nRecent service logs:{logs}", "service_enabled": "The service '{service}' will now be automatically started during system boots.", + "service_not_reloading_because_conf_broken": "Not reloading/restarting service '{name}' because it configuration is broken: {errors}", "service_regen_conf_is_deprecated": "'yunohost service regen-conf' is deprecated! Please use 'yunohost tools regen-conf' instead.", "service_reload_failed": "Could not reload the service '{service}'\n\nRecent service logs:{logs}", "service_reload_or_restart_failed": "Could not reload or restart the service '{service}'\n\nRecent service logs:{logs}", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 23f3b011b..e24af1654 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1823,9 +1823,9 @@ ynh_app_config_run $1 ret, values = hook_exec(config_script, args=[action], env=env) if ret != 0: if action == "show": - raise YunohostError("app_config_unable_to_read_values") + raise YunohostError("app_config_unable_to_read") else: - raise YunohostError("app_config_unable_to_apply_values_correctly") + raise YunohostError("app_config_unable_to_apply") return values diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 9cdb30119..8c1b2948a 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -31,6 +31,7 @@ from moulinette import Moulinette, m18n from moulinette.utils.log import getActionLogger from moulinette.utils.process import check_output from moulinette.utils.filesystem import ( + write_to_file, read_toml, read_yaml, write_to_yaml, @@ -127,14 +128,14 @@ class ConfigPanel: # N.B. : KeyboardInterrupt does not inherit from Exception except (KeyboardInterrupt, EOFError): error = m18n.n("operation_interrupted") - logger.error(m18n.n("config_failed", error=error)) + logger.error(m18n.n("config_apply_failed", error=error)) raise # Something wrong happened in Yunohost's code (most probably hook_exec) except Exception: import traceback error = m18n.n("unexpected_error", error="\n" + traceback.format_exc()) - logger.error(m18n.n("config_failed", error=error)) + logger.error(m18n.n("config_apply_failed", error=error)) raise finally: # Delete files uploaded from API @@ -154,10 +155,11 @@ class ConfigPanel: return read_toml(self.config_path) def _get_config_panel(self): + # Split filter_key - filter_key = dict(enumerate(self.filter_key.split("."))) + filter_key = self.filter_key.split(".") if len(filter_key) > 3: - raise YunohostError("config_too_much_sub_keys") + raise YunohostError("config_too_many_sub_keys", key=self.filter_key) if not os.path.exists(self.config_path): return None @@ -166,7 +168,7 @@ class ConfigPanel: # Check TOML config panel is in a supported version if float(toml_config_panel["version"]) < CONFIG_PANEL_VERSION_SUPPORTED: raise YunohostError( - "config_too_old_version", version=toml_config_panel["version"] + "config_version_not_supported", version=toml_config_panel["version"] ) # Transform toml format into internal format @@ -187,6 +189,13 @@ class ConfigPanel: # optional choices pattern limit min max step accept redact } + # + # FIXME : this is hella confusing ... + # from what I understand, the purpose is to have some sort of "deep_update" + # to apply the defaults onto the loaded toml ... + # in that case we probably want to get inspiration from + # https://stackoverflow.com/questions/3232943/update-value-of-a-nested-dictionary-of-varying-depth + # def convert(toml_node, node_type): """Convert TOML in internal format ('full' mode used by webadmin) Here are some properties of 1.0 config panel in toml: @@ -456,7 +465,7 @@ class YunoHostArgumentFormatParser(object): if self.operation_logger: self.operation_logger.data_to_redact.extend(data_to_redact) elif data_to_redact: - raise YunohostError("app_argument_cant_redact", arg=question.name) + raise YunohostError(f"Can't redact {question.name} because no operation logger available in the context", raw_msg=True) return question.value @@ -729,7 +738,7 @@ class FileArgumentParser(YunoHostArgumentFormatParser): raise YunohostValidationError( "app_argument_invalid", field=question.name, - error=m18n.n("invalid_number1"), + error=m18n.n("file_does_not_exists"), ) if question.value in [None, ""] or not question.accept: return @@ -743,7 +752,7 @@ class FileArgumentParser(YunoHostArgumentFormatParser): raise YunohostValidationError( "app_argument_invalid", field=question.name, - error=m18n.n("invalid_number2"), + error=m18n.n("file_extension_not_accepted"), ) def _post_parse_value(self, question): @@ -768,19 +777,15 @@ class FileArgumentParser(YunoHostArgumentFormatParser): # i.e. os.path.join("/foo", "/etc/passwd") == "/etc/passwd" file_path = os.path.normpath(upload_dir + "/" + filename) if not file_path.startswith(upload_dir + "/"): - raise YunohostError("relative_parent_path_in_filename_forbidden") + raise YunohostError("file_relative_parent_path_in_filename_forbidden") i = 2 while os.path.exists(file_path): file_path = os.path.normpath(upload_dir + "/" + filename + (".%d" % i)) i += 1 content = question.value["content"] - try: - with open(file_path, "wb") as f: - f.write(b64decode(content)) - except IOError as e: - raise YunohostError("cannot_write_file", file=file_path, error=str(e)) - except Exception as e: - raise YunohostError("error_writing_file", file=file_path, error=str(e)) + + write_to_file(file_path, b64decode(content), file_mode="wb") + question.value = file_path return question.value From a062254402ba5463c5c12a4885fd45f91052d457 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 4 Sep 2021 20:34:56 +0200 Subject: [PATCH 2916/3170] Moar localization --- locales/en.json | 1 + src/yunohost/utils/config.py | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/locales/en.json b/locales/en.json index 667a8d4b3..faef3efc5 100644 --- a/locales/en.json +++ b/locales/en.json @@ -327,6 +327,7 @@ "extracting": "Extracting...", "field_invalid": "Invalid field '{}'", "file_does_not_exist": "The file {path} does not exist.", + "file_extension_not_accepted": "Refusing file '{path}' because its extension is not among the accepted extensions: {accept}", "firewall_reload_failed": "Could not reload the firewall", "firewall_reloaded": "Firewall reloaded", "firewall_rules_cmd_failed": "Some firewall rule commands have failed. More info in log.", diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index cb493be03..23a95b565 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -797,7 +797,7 @@ class FileQuestion(Question): raise YunohostValidationError( "app_argument_invalid", field=self.name, - error=m18n.n("file_does_not_exists"), + error=m18n.n("file_does_not_exist", path=self.value), ) if self.value in [None, ""] or not self.accept: return @@ -807,7 +807,7 @@ class FileQuestion(Question): raise YunohostValidationError( "app_argument_invalid", field=self.name, - error=m18n.n("file_extension_not_accepted"), + error=m18n.n("file_extension_not_accepted", file=filename, accept=self.accept), ) @@ -833,7 +833,7 @@ class FileQuestion(Question): # i.e. os.path.join("/foo", "/etc/passwd") == "/etc/passwd" file_path = os.path.normpath(upload_dir + "/" + filename) if not file_path.startswith(upload_dir + "/"): - raise YunohostError("file_relative_parent_path_in_filename_forbidden") + raise YunohostError(f"Filename '{filename}' received from the API got a relative parent path, which is forbidden", raw_msg=True) i = 2 while os.path.exists(file_path): file_path = os.path.normpath(upload_dir + "/" + filename + (".%d" % i)) From 1bf0196ff8fa33712894bd8e4dde7ca8df62f4c2 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 5 Sep 2021 00:20:12 +0200 Subject: [PATCH 2917/3170] Black + fix some invalid code issues --- src/yunohost/service.py | 4 +- src/yunohost/utils/config.py | 119 ++++++++++++++++++--------------- tests/autofix_locale_format.py | 12 +++- tests/reformat_locales.py | 54 ++++++++++----- 4 files changed, 116 insertions(+), 73 deletions(-) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index da3bded3c..2ef94878d 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -287,7 +287,9 @@ def service_reload_or_restart(names, test_conf=True): out, _ = p.communicate() if p.returncode != 0: errors = out.strip().split("\n") - logger.error(m18n.("service_not_reloading_because_conf_broken", errors=errors)) + logger.error( + m18n.n("service_not_reloading_because_conf_broken", errors=errors) + ) continue if _run_service_command("reload-or-restart", name): diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 23a95b565..5c81f5007 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -29,7 +29,6 @@ from collections import OrderedDict from moulinette.interfaces.cli import colorize from moulinette import Moulinette, m18n from moulinette.utils.log import getActionLogger -from moulinette.utils.process import check_output from moulinette.utils.filesystem import ( write_to_file, read_toml, @@ -80,16 +79,22 @@ class ConfigPanel: result = {} for panel, section, option in self._iterate(): key = f"{panel['id']}.{section['id']}.{option['id']}" - if mode == 'export': - result[option['id']] = option.get('current_value') + if mode == "export": + result[option["id"]] = option.get("current_value") else: - if 'ask' in option: - result[key] = {'ask': _value_for_locale(option['ask'])} - elif 'i18n' in self.config: - result[key] = {'ask': m18n.n(self.config['i18n'] + '_' + option['id'])} - if 'current_value' in option: - question_class = ARGUMENTS_TYPE_PARSERS[option.get("type", "string")] - result[key]['value'] = question_class.humanize(option['current_value'], option) + if "ask" in option: + result[key] = {"ask": _value_for_locale(option["ask"])} + elif "i18n" in self.config: + result[key] = { + "ask": m18n.n(self.config["i18n"] + "_" + option["id"]) + } + if "current_value" in option: + question_class = ARGUMENTS_TYPE_PARSERS[ + option.get("type", "string") + ] + result[key]["value"] = question_class.humanize( + option["current_value"], option + ) return result @@ -294,8 +299,11 @@ class ConfigPanel: self.errors = None def _get_default_values(self): - return { option['id']: option['default'] - for _, _, option in self._iterate() if 'default' in option } + return { + option["id"]: option["default"] + for _, _, option in self._iterate() + if "default" in option + } def _load_current_values(self): """ @@ -319,9 +327,11 @@ class ConfigPanel: mkdir(dir_path, mode=0o700) values_to_save = {**self.values, **self.new_values} - if self.save_mode == 'diff': + if self.save_mode == "diff": defaults = self._get_default_values() - values_to_save = {k: v for k, v in values_to_save.items() if defaults.get(k) != v} + values_to_save = { + k: v for k, v in values_to_save.items() if defaults.get(k) != v + } # Save the settings to the .yaml file write_to_yaml(self.save_path, self.new_values) @@ -360,17 +370,17 @@ class Question(object): def __init__(self, question, user_answers): self.name = question["name"] - self.type = question.get("type", 'string') + self.type = question.get("type", "string") self.default = question.get("default", None) self.current_value = question.get("current_value") self.optional = question.get("optional", False) self.choices = question.get("choices", []) self.pattern = question.get("pattern") - self.ask = question.get("ask", {'en': self.name}) + self.ask = question.get("ask", {"en": self.name}) self.help = question.get("help") self.helpLink = question.get("helpLink") self.value = user_answers.get(self.name) - self.redact = question.get('redact', False) + self.redact = question.get("redact", False) # Empty value is parsed as empty string if self.default == "": @@ -384,11 +394,10 @@ class Question(object): def normalize(value, option={}): return value - def ask_if_needed(self): while True: # Display question if no value filled or if it's a readonly message - if Moulinette.interface.type== 'cli': + if Moulinette.interface.type == "cli": text_for_user_input_in_cli = self._format_text_for_user_input_in_cli() if getattr(self, "readonly", False): Moulinette.display(text_for_user_input_in_cli) @@ -433,7 +442,6 @@ class Question(object): return (self.value, self.argument_type) - def _prevalidate(self): if self.value in [None, ""] and not self.optional: raise YunohostValidationError("app_argument_required", name=self.name) @@ -442,9 +450,9 @@ class Question(object): if self.value is not None: if self.choices and self.value not in self.choices: self._raise_invalid_answer() - if self.pattern and not re.match(self.pattern['regexp'], str(self.value)): + if self.pattern and not re.match(self.pattern["regexp"], str(self.value)): raise YunohostValidationError( - self.pattern['error'], + self.pattern["error"], name=self.name, value=self.value, ) @@ -494,7 +502,10 @@ class Question(object): if self.operation_logger: self.operation_logger.data_to_redact.extend(data_to_redact) elif data_to_redact: - raise YunohostError(f"Can't redact {question.name} because no operation logger available in the context", raw_msg=True) + raise YunohostError( + f"Can't redact {self.name} because no operation logger available in the context", + raw_msg=True, + ) return self.value @@ -510,7 +521,7 @@ class TagsQuestion(Question): @staticmethod def humanize(value, option={}): if isinstance(value, list): - return ','.join(value) + return ",".join(value) return value def _prevalidate(self): @@ -540,7 +551,7 @@ class PasswordQuestion(Question): @staticmethod def humanize(value, option={}): if value: - return '***' # Avoid to display the password on screen + return "********" # Avoid to display the password on screen return "" def _prevalidate(self): @@ -571,32 +582,32 @@ class BooleanQuestion(Question): @staticmethod def humanize(value, option={}): - yes = option.get('yes', 1) - no = option.get('no', 0) + yes = option.get("yes", 1) + no = option.get("no", 0) value = str(value).lower() if value == str(yes).lower(): - return 'yes' + return "yes" if value == str(no).lower(): - return 'no' + return "no" if value in BooleanQuestion.yes_answers: - return 'yes' + return "yes" if value in BooleanQuestion.no_answers: - return 'no' + return "no" - if value in ['none', ""]: - return '' + if value in ["none", ""]: + return "" raise YunohostValidationError( "app_argument_choice_invalid", - name=self.name, - value=self.value, + name=self.name, # FIXME ... + value=value, choices="yes, no, y, n, 1, 0", ) @staticmethod def normalize(value, option={}): - yes = option.get('yes', 1) - no = option.get('no', 0) + yes = option.get("yes", 1) + no = option.get("no", 0) if str(value).lower() in BooleanQuestion.yes_answers: return yes @@ -608,19 +619,18 @@ class BooleanQuestion(Question): return None raise YunohostValidationError( "app_argument_choice_invalid", - name=self.name, - value=self.value, + name=self.name, # FIXME.... + value=value, choices="yes, no, y, n, 1, 0", ) def __init__(self, question, user_answers): super().__init__(question, user_answers) - self.yes = question.get('yes', 1) - self.no = question.get('no', 0) + self.yes = question.get("yes", 1) + self.no = question.get("no", 0) if self.default is None: self.default = False - def _format_text_for_user_input_in_cli(self): text_for_user_input_in_cli = _value_for_locale(self.ask) @@ -652,7 +662,6 @@ class DomainQuestion(Question): self.choices = domain_list()["domains"] - def _raise_invalid_answer(self): raise YunohostValidationError( "app_argument_invalid", field=self.name, error=m18n.n("domain_unknown") @@ -675,7 +684,6 @@ class UserQuestion(Question): self.default = user break - def _raise_invalid_answer(self): raise YunohostValidationError( "app_argument_invalid", @@ -698,7 +706,6 @@ class NumberQuestion(Question): self.max = question.get("max", None) self.step = question.get("step", None) - def _prevalidate(self): super()._prevalidate() if not isinstance(self.value, int) and not ( @@ -744,8 +751,7 @@ class DisplayTextQuestion(Question): super().__init__(question, user_answers) self.optional = True - self.style = question.get("style", "info") - + self.style = question.get("style", "info") def _format_text_for_user_input_in_cli(self): text = self.ask["en"] @@ -780,7 +786,7 @@ class FileQuestion(Question): self.accept = question.get("accept").replace(" ", "").split(",") else: self.accept = [] - if Moulinette.interface.type== "api": + if Moulinette.interface.type == "api": if user_answers.get(f"{self.name}[name]"): self.value = { "content": self.value, @@ -790,10 +796,13 @@ class FileQuestion(Question): if self.value and str(self.value) == self.current_value: self.value = None - def _prevalidate(self): super()._prevalidate() - if isinstance(self.value, str) and self.value and not os.path.exists(self.value): + if ( + isinstance(self.value, str) + and self.value + and not os.path.exists(self.value) + ): raise YunohostValidationError( "app_argument_invalid", field=self.name, @@ -807,10 +816,11 @@ class FileQuestion(Question): raise YunohostValidationError( "app_argument_invalid", field=self.name, - error=m18n.n("file_extension_not_accepted", file=filename, accept=self.accept), + error=m18n.n( + "file_extension_not_accepted", file=filename, accept=self.accept + ), ) - def _post_parse_value(self): from base64 import b64decode @@ -833,7 +843,10 @@ class FileQuestion(Question): # i.e. os.path.join("/foo", "/etc/passwd") == "/etc/passwd" file_path = os.path.normpath(upload_dir + "/" + filename) if not file_path.startswith(upload_dir + "/"): - raise YunohostError(f"Filename '{filename}' received from the API got a relative parent path, which is forbidden", raw_msg=True) + raise YunohostError( + f"Filename '{filename}' received from the API got a relative parent path, which is forbidden", + raw_msg=True, + ) i = 2 while os.path.exists(file_path): file_path = os.path.normpath(upload_dir + "/" + filename + (".%d" % i)) diff --git a/tests/autofix_locale_format.py b/tests/autofix_locale_format.py index f777f06f1..dd7812635 100644 --- a/tests/autofix_locale_format.py +++ b/tests/autofix_locale_format.py @@ -27,11 +27,17 @@ def fix_locale(locale_file): # should also be in the translated string, otherwise the .format # will trigger an exception! subkeys_in_ref = [k[0] for k in re.findall(r"{(\w+)(:\w)?}", string)] - subkeys_in_this_locale = [k[0] for k in re.findall(r"{(\w+)(:\w)?}", this_locale[key])] + subkeys_in_this_locale = [ + k[0] for k in re.findall(r"{(\w+)(:\w)?}", this_locale[key]) + ] - if set(subkeys_in_ref) != set(subkeys_in_this_locale) and (len(subkeys_in_ref) == len(subkeys_in_this_locale)): + if set(subkeys_in_ref) != set(subkeys_in_this_locale) and ( + len(subkeys_in_ref) == len(subkeys_in_this_locale) + ): for i, subkey in enumerate(subkeys_in_ref): - this_locale[key] = this_locale[key].replace('{%s}' % subkeys_in_this_locale[i], '{%s}' % subkey) + this_locale[key] = this_locale[key].replace( + "{%s}" % subkeys_in_this_locale[i], "{%s}" % subkey + ) fixed_stuff = True if fixed_stuff: diff --git a/tests/reformat_locales.py b/tests/reformat_locales.py index 90251d040..9119c7288 100644 --- a/tests/reformat_locales.py +++ b/tests/reformat_locales.py @@ -1,5 +1,6 @@ import re + def reformat(lang, transformations): locale = open(f"locales/{lang}.json").read() @@ -8,31 +9,52 @@ def reformat(lang, transformations): open(f"locales/{lang}.json", "w").write(locale) + ###################################################### -godamn_spaces_of_hell = ["\u00a0", "\u2000", "\u2001", "\u2002", "\u2003", "\u2004", "\u2005", "\u2006", "\u2007", "\u2008", "\u2009", "\u200A", "\u202f", "\u202F", "\u3000"] +godamn_spaces_of_hell = [ + "\u00a0", + "\u2000", + "\u2001", + "\u2002", + "\u2003", + "\u2004", + "\u2005", + "\u2006", + "\u2007", + "\u2008", + "\u2009", + "\u200A", + "\u202f", + "\u202F", + "\u3000", +] transformations = {s: " " for s in godamn_spaces_of_hell} -transformations.update({ - "…": "...", -}) +transformations.update( + { + "…": "...", + } +) reformat("en", transformations) ###################################################### -transformations.update({ - "courriel": "email", - "e-mail": "email", - "Courriel": "Email", - "E-mail": "Email", - "« ": "'", - "«": "'", - " »": "'", - "»": "'", - "’": "'", - #r"$(\w{1,2})'|( \w{1,2})'": r"\1\2’", -}) +transformations.update( + { + "courriel": "email", + "e-mail": "email", + "Courriel": "Email", + "E-mail": "Email", + "« ": "'", + "«": "'", + " »": "'", + "»": "'", + "’": "'", + # r"$(\w{1,2})'|( \w{1,2})'": r"\1\2’", + } +) reformat("fr", transformations) From a289f081d911656f460f7acc76e041e892a5083e Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Sat, 4 Sep 2021 22:52:35 +0000 Subject: [PATCH 2918/3170] [CI] Format code --- data/hooks/diagnosis/00-basesystem.py | 5 ++- tests/autofix_locale_format.py | 12 ++++-- tests/reformat_locales.py | 54 +++++++++++++++++++-------- 3 files changed, 51 insertions(+), 20 deletions(-) diff --git a/data/hooks/diagnosis/00-basesystem.py b/data/hooks/diagnosis/00-basesystem.py index 5b4b3394c..b472a2d32 100644 --- a/data/hooks/diagnosis/00-basesystem.py +++ b/data/hooks/diagnosis/00-basesystem.py @@ -172,7 +172,10 @@ class BaseSystemDiagnoser(Diagnoser): try: return int(n_failures) except Exception: - self.logger_warning("Failed to parse number of recent auth failures, expected an int, got '%s'" % n_failures) + self.logger_warning( + "Failed to parse number of recent auth failures, expected an int, got '%s'" + % n_failures + ) return -1 def is_vulnerable_to_meltdown(self): diff --git a/tests/autofix_locale_format.py b/tests/autofix_locale_format.py index f777f06f1..dd7812635 100644 --- a/tests/autofix_locale_format.py +++ b/tests/autofix_locale_format.py @@ -27,11 +27,17 @@ def fix_locale(locale_file): # should also be in the translated string, otherwise the .format # will trigger an exception! subkeys_in_ref = [k[0] for k in re.findall(r"{(\w+)(:\w)?}", string)] - subkeys_in_this_locale = [k[0] for k in re.findall(r"{(\w+)(:\w)?}", this_locale[key])] + subkeys_in_this_locale = [ + k[0] for k in re.findall(r"{(\w+)(:\w)?}", this_locale[key]) + ] - if set(subkeys_in_ref) != set(subkeys_in_this_locale) and (len(subkeys_in_ref) == len(subkeys_in_this_locale)): + if set(subkeys_in_ref) != set(subkeys_in_this_locale) and ( + len(subkeys_in_ref) == len(subkeys_in_this_locale) + ): for i, subkey in enumerate(subkeys_in_ref): - this_locale[key] = this_locale[key].replace('{%s}' % subkeys_in_this_locale[i], '{%s}' % subkey) + this_locale[key] = this_locale[key].replace( + "{%s}" % subkeys_in_this_locale[i], "{%s}" % subkey + ) fixed_stuff = True if fixed_stuff: diff --git a/tests/reformat_locales.py b/tests/reformat_locales.py index 90251d040..9119c7288 100644 --- a/tests/reformat_locales.py +++ b/tests/reformat_locales.py @@ -1,5 +1,6 @@ import re + def reformat(lang, transformations): locale = open(f"locales/{lang}.json").read() @@ -8,31 +9,52 @@ def reformat(lang, transformations): open(f"locales/{lang}.json", "w").write(locale) + ###################################################### -godamn_spaces_of_hell = ["\u00a0", "\u2000", "\u2001", "\u2002", "\u2003", "\u2004", "\u2005", "\u2006", "\u2007", "\u2008", "\u2009", "\u200A", "\u202f", "\u202F", "\u3000"] +godamn_spaces_of_hell = [ + "\u00a0", + "\u2000", + "\u2001", + "\u2002", + "\u2003", + "\u2004", + "\u2005", + "\u2006", + "\u2007", + "\u2008", + "\u2009", + "\u200A", + "\u202f", + "\u202F", + "\u3000", +] transformations = {s: " " for s in godamn_spaces_of_hell} -transformations.update({ - "…": "...", -}) +transformations.update( + { + "…": "...", + } +) reformat("en", transformations) ###################################################### -transformations.update({ - "courriel": "email", - "e-mail": "email", - "Courriel": "Email", - "E-mail": "Email", - "« ": "'", - "«": "'", - " »": "'", - "»": "'", - "’": "'", - #r"$(\w{1,2})'|( \w{1,2})'": r"\1\2’", -}) +transformations.update( + { + "courriel": "email", + "e-mail": "email", + "Courriel": "Email", + "E-mail": "Email", + "« ": "'", + "«": "'", + " »": "'", + "»": "'", + "’": "'", + # r"$(\w{1,2})'|( \w{1,2})'": r"\1\2’", + } +) reformat("fr", transformations) From 2413c378df18a4f830aed96a49ffc2e55b58d6e5 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 5 Sep 2021 01:04:22 +0200 Subject: [PATCH 2919/3170] Gotta mkdir data/bash-completion.d at build time --- data/actionsmap/yunohost_completion.py | 1 + 1 file changed, 1 insertion(+) diff --git a/data/actionsmap/yunohost_completion.py b/data/actionsmap/yunohost_completion.py index 3891aee9c..c801e2f3c 100644 --- a/data/actionsmap/yunohost_completion.py +++ b/data/actionsmap/yunohost_completion.py @@ -13,6 +13,7 @@ import yaml THIS_SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) ACTIONSMAP_FILE = THIS_SCRIPT_DIR + "/yunohost.yml" +os.system(f"mkdir {THIS_SCRIPT_DIR}/../bash-completion.d") BASH_COMPLETION_FILE = THIS_SCRIPT_DIR + "/../bash-completion.d/yunohost" From bd384d094f05d40cbe3da63100ead332771b598e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 5 Sep 2021 01:08:40 +0200 Subject: [PATCH 2920/3170] Unused imports --- src/yunohost/dns.py | 2 -- src/yunohost/domain.py | 4 +--- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index df3b578db..045a33e05 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -28,10 +28,8 @@ import re from moulinette import m18n, Moulinette from moulinette.utils.log import getActionLogger -from moulinette.utils.filesystem import mkdir, read_yaml, write_to_yaml from yunohost.domain import domain_list, _get_domain_settings, _assert_domain_exists -from yunohost.app import _parse_args_in_yunohost_format from yunohost.utils.error import YunohostValidationError from yunohost.utils.network import get_public_ip from yunohost.log import is_unit_operation diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 064311a49..28f6037da 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -28,9 +28,8 @@ import os from moulinette import m18n, Moulinette from moulinette.core import MoulinetteError from moulinette.utils.log import getActionLogger -from moulinette.utils.filesystem import mkdir, write_to_file, read_yaml, write_to_yaml +from moulinette.utils.filesystem import write_to_file -from yunohost.settings import is_boolean from yunohost.app import ( app_ssowatconf, _installed_apps, @@ -41,7 +40,6 @@ from yunohost.regenconf import regen_conf, _force_clear_hashes, _process_regen_c from yunohost.utils.config import ConfigPanel from yunohost.utils.error import YunohostError, YunohostValidationError from yunohost.log import is_unit_operation -from yunohost.hook import hook_callback logger = getActionLogger("yunohost.domain") From 0122b7a1262cc63f250074d3a2a4930683e9fc94 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 5 Sep 2021 15:37:46 +0200 Subject: [PATCH 2921/3170] Misc fixes --- .gitlab/ci/test.gitlab-ci.yml | 6 +- src/yunohost/service.py | 2 +- ...ps_arguments_parsing.py => test_config.py} | 250 +++++++++--------- src/yunohost/tests/test_service.py | 2 +- 4 files changed, 130 insertions(+), 130 deletions(-) rename src/yunohost/tests/{test_apps_arguments_parsing.py => test_config.py} (81%) diff --git a/.gitlab/ci/test.gitlab-ci.yml b/.gitlab/ci/test.gitlab-ci.yml index e0e0e001a..78394f253 100644 --- a/.gitlab/ci/test.gitlab-ci.yml +++ b/.gitlab/ci/test.gitlab-ci.yml @@ -112,14 +112,14 @@ test-appurl: changes: - src/yunohost/app.py -test-apps-arguments-parsing: +test-config: extends: .test-stage script: - cd src/yunohost - - python3 -m pytest tests/test_apps_arguments_parsing.py + - python3 -m pytest tests/test_config.py only: changes: - - src/yunohost/app.py + - src/yunohost/utils/config.py test-changeurl: extends: .test-stage diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 2ef94878d..5f9f3e60a 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -273,7 +273,7 @@ def service_reload_or_restart(names, test_conf=True): logger.debug(f"Reloading service {name}") - test_conf_cmd = services.get(service, {}).get("test_conf") + test_conf_cmd = services.get(name, {}).get("test_conf") if test_conf and test_conf_cmd: p = subprocess.Popen( diff --git a/src/yunohost/tests/test_apps_arguments_parsing.py b/src/yunohost/tests/test_config.py similarity index 81% rename from src/yunohost/tests/test_apps_arguments_parsing.py rename to src/yunohost/tests/test_config.py index fe5c5f8cd..2e52c4086 100644 --- a/src/yunohost/tests/test_apps_arguments_parsing.py +++ b/src/yunohost/tests/test_config.py @@ -8,7 +8,7 @@ from collections import OrderedDict from moulinette import Moulinette from yunohost import domain, user -from yunohost.app import _parse_args_in_yunohost_format, PasswordArgumentParser +from yunohost.utils.config import parse_args_in_yunohost_format, PasswordQuestion from yunohost.utils.error import YunohostError @@ -36,7 +36,7 @@ User answers: def test_parse_args_in_yunohost_format_empty(): - assert _parse_args_in_yunohost_format({}, []) == {} + assert parse_args_in_yunohost_format({}, []) == {} def test_parse_args_in_yunohost_format_string(): @@ -48,7 +48,7 @@ def test_parse_args_in_yunohost_format_string(): ] answers = {"some_string": "some_value"} expected_result = OrderedDict({"some_string": ("some_value", "string")}) - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_string_default_type(): @@ -59,7 +59,7 @@ def test_parse_args_in_yunohost_format_string_default_type(): ] answers = {"some_string": "some_value"} expected_result = OrderedDict({"some_string": ("some_value", "string")}) - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_string_no_input(): @@ -71,7 +71,7 @@ def test_parse_args_in_yunohost_format_string_no_input(): answers = {} with pytest.raises(YunohostError): - _parse_args_in_yunohost_format(answers, questions) + parse_args_in_yunohost_format(answers, questions) def test_parse_args_in_yunohost_format_string_input(): @@ -85,7 +85,7 @@ def test_parse_args_in_yunohost_format_string_input(): expected_result = OrderedDict({"some_string": ("some_value", "string")}) with patch.object(Moulinette.interface, "prompt", return_value="some_value"): - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_string_input_no_ask(): @@ -98,7 +98,7 @@ def test_parse_args_in_yunohost_format_string_input_no_ask(): expected_result = OrderedDict({"some_string": ("some_value", "string")}) with patch.object(Moulinette.interface, "prompt", return_value="some_value"): - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_string_no_input_optional(): @@ -110,7 +110,7 @@ def test_parse_args_in_yunohost_format_string_no_input_optional(): ] answers = {} expected_result = OrderedDict({"some_string": ("", "string")}) - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_string_optional_with_input(): @@ -125,7 +125,7 @@ def test_parse_args_in_yunohost_format_string_optional_with_input(): expected_result = OrderedDict({"some_string": ("some_value", "string")}) with patch.object(Moulinette.interface, "prompt", return_value="some_value"): - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_string_optional_with_empty_input(): @@ -140,7 +140,7 @@ def test_parse_args_in_yunohost_format_string_optional_with_empty_input(): expected_result = OrderedDict({"some_string": ("", "string")}) with patch.object(Moulinette.interface, "prompt", return_value=""): - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_string_optional_with_input_without_ask(): @@ -154,7 +154,7 @@ def test_parse_args_in_yunohost_format_string_optional_with_input_without_ask(): expected_result = OrderedDict({"some_string": ("some_value", "string")}) with patch.object(Moulinette.interface, "prompt", return_value="some_value"): - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_string_no_input_default(): @@ -167,7 +167,7 @@ def test_parse_args_in_yunohost_format_string_no_input_default(): ] answers = {} expected_result = OrderedDict({"some_string": ("some_value", "string")}) - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_string_input_test_ask(): @@ -183,7 +183,7 @@ def test_parse_args_in_yunohost_format_string_input_test_ask(): with patch.object( Moulinette.interface, "prompt", return_value="some_value" ) as prompt: - _parse_args_in_yunohost_format(answers, questions) + parse_args_in_yunohost_format(answers, questions) prompt.assert_called_with(ask_text, False) @@ -202,7 +202,7 @@ def test_parse_args_in_yunohost_format_string_input_test_ask_with_default(): with patch.object( Moulinette.interface, "prompt", return_value="some_value" ) as prompt: - _parse_args_in_yunohost_format(answers, questions) + parse_args_in_yunohost_format(answers, questions) prompt.assert_called_with("%s (default: %s)" % (ask_text, default_text), False) @@ -222,7 +222,7 @@ def test_parse_args_in_yunohost_format_string_input_test_ask_with_example(): with patch.object( Moulinette.interface, "prompt", return_value="some_value" ) as prompt: - _parse_args_in_yunohost_format(answers, questions) + parse_args_in_yunohost_format(answers, questions) assert ask_text in prompt.call_args[0][0] assert example_text in prompt.call_args[0][0] @@ -243,7 +243,7 @@ def test_parse_args_in_yunohost_format_string_input_test_ask_with_help(): with patch.object( Moulinette.interface, "prompt", return_value="some_value" ) as prompt: - _parse_args_in_yunohost_format(answers, questions) + parse_args_in_yunohost_format(answers, questions) assert ask_text in prompt.call_args[0][0] assert help_text in prompt.call_args[0][0] @@ -252,7 +252,7 @@ def test_parse_args_in_yunohost_format_string_with_choice(): questions = [{"name": "some_string", "type": "string", "choices": ["fr", "en"]}] answers = {"some_string": "fr"} expected_result = OrderedDict({"some_string": ("fr", "string")}) - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_string_with_choice_prompt(): @@ -260,7 +260,7 @@ def test_parse_args_in_yunohost_format_string_with_choice_prompt(): answers = {"some_string": "fr"} expected_result = OrderedDict({"some_string": ("fr", "string")}) with patch.object(Moulinette.interface, "prompt", return_value="fr"): - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_string_with_choice_bad(): @@ -268,7 +268,7 @@ def test_parse_args_in_yunohost_format_string_with_choice_bad(): answers = {"some_string": "bad"} with pytest.raises(YunohostError): - assert _parse_args_in_yunohost_format(answers, questions) + assert parse_args_in_yunohost_format(answers, questions) def test_parse_args_in_yunohost_format_string_with_choice_ask(): @@ -284,7 +284,7 @@ def test_parse_args_in_yunohost_format_string_with_choice_ask(): answers = {} with patch.object(Moulinette.interface, "prompt", return_value="ru") as prompt: - _parse_args_in_yunohost_format(answers, questions) + parse_args_in_yunohost_format(answers, questions) assert ask_text in prompt.call_args[0][0] for choice in choices: @@ -302,7 +302,7 @@ def test_parse_args_in_yunohost_format_string_with_choice_default(): ] answers = {} expected_result = OrderedDict({"some_string": ("en", "string")}) - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_password(): @@ -314,7 +314,7 @@ def test_parse_args_in_yunohost_format_password(): ] answers = {"some_password": "some_value"} expected_result = OrderedDict({"some_password": ("some_value", "password")}) - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_password_no_input(): @@ -327,7 +327,7 @@ def test_parse_args_in_yunohost_format_password_no_input(): answers = {} with pytest.raises(YunohostError): - _parse_args_in_yunohost_format(answers, questions) + parse_args_in_yunohost_format(answers, questions) def test_parse_args_in_yunohost_format_password_input(): @@ -342,7 +342,7 @@ def test_parse_args_in_yunohost_format_password_input(): expected_result = OrderedDict({"some_password": ("some_value", "password")}) with patch.object(Moulinette.interface, "prompt", return_value="some_value"): - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_password_input_no_ask(): @@ -356,7 +356,7 @@ def test_parse_args_in_yunohost_format_password_input_no_ask(): expected_result = OrderedDict({"some_password": ("some_value", "password")}) with patch.object(Moulinette.interface, "prompt", return_value="some_value"): - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_password_no_input_optional(): @@ -370,13 +370,13 @@ def test_parse_args_in_yunohost_format_password_no_input_optional(): answers = {} expected_result = OrderedDict({"some_password": ("", "password")}) - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result questions = [ {"name": "some_password", "type": "password", "optional": True, "default": ""} ] - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_password_optional_with_input(): @@ -392,7 +392,7 @@ def test_parse_args_in_yunohost_format_password_optional_with_input(): expected_result = OrderedDict({"some_password": ("some_value", "password")}) with patch.object(Moulinette.interface, "prompt", return_value="some_value"): - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_password_optional_with_empty_input(): @@ -408,7 +408,7 @@ def test_parse_args_in_yunohost_format_password_optional_with_empty_input(): expected_result = OrderedDict({"some_password": ("", "password")}) with patch.object(Moulinette.interface, "prompt", return_value=""): - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_password_optional_with_input_without_ask(): @@ -423,7 +423,7 @@ def test_parse_args_in_yunohost_format_password_optional_with_input_without_ask( expected_result = OrderedDict({"some_password": ("some_value", "password")}) with patch.object(Moulinette.interface, "prompt", return_value="some_value"): - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_password_no_input_default(): @@ -439,7 +439,7 @@ def test_parse_args_in_yunohost_format_password_no_input_default(): # no default for password! with pytest.raises(YunohostError): - _parse_args_in_yunohost_format(answers, questions) + parse_args_in_yunohost_format(answers, questions) @pytest.mark.skip # this should raises @@ -456,7 +456,7 @@ def test_parse_args_in_yunohost_format_password_no_input_example(): # no example for password! with pytest.raises(YunohostError): - _parse_args_in_yunohost_format(answers, questions) + parse_args_in_yunohost_format(answers, questions) def test_parse_args_in_yunohost_format_password_input_test_ask(): @@ -473,7 +473,7 @@ def test_parse_args_in_yunohost_format_password_input_test_ask(): with patch.object( Moulinette.interface, "prompt", return_value="some_value" ) as prompt: - _parse_args_in_yunohost_format(answers, questions) + parse_args_in_yunohost_format(answers, questions) prompt.assert_called_with(ask_text, True) @@ -494,7 +494,7 @@ def test_parse_args_in_yunohost_format_password_input_test_ask_with_example(): with patch.object( Moulinette.interface, "prompt", return_value="some_value" ) as prompt: - _parse_args_in_yunohost_format(answers, questions) + parse_args_in_yunohost_format(answers, questions) assert ask_text in prompt.call_args[0][0] assert example_text in prompt.call_args[0][0] @@ -516,7 +516,7 @@ def test_parse_args_in_yunohost_format_password_input_test_ask_with_help(): with patch.object( Moulinette.interface, "prompt", return_value="some_value" ) as prompt: - _parse_args_in_yunohost_format(answers, questions) + parse_args_in_yunohost_format(answers, questions) assert ask_text in prompt.call_args[0][0] assert help_text in prompt.call_args[0][0] @@ -531,9 +531,9 @@ def test_parse_args_in_yunohost_format_password_bad_chars(): } ] - for i in PasswordArgumentParser.forbidden_chars: + for i in PasswordQuestion.forbidden_chars: with pytest.raises(YunohostError): - _parse_args_in_yunohost_format({"some_password": i * 8}, questions) + parse_args_in_yunohost_format({"some_password": i * 8}, questions) def test_parse_args_in_yunohost_format_password_strong_enough(): @@ -548,10 +548,10 @@ def test_parse_args_in_yunohost_format_password_strong_enough(): with pytest.raises(YunohostError): # too short - _parse_args_in_yunohost_format({"some_password": "a"}, questions) + parse_args_in_yunohost_format({"some_password": "a"}, questions) with pytest.raises(YunohostError): - _parse_args_in_yunohost_format({"some_password": "password"}, questions) + parse_args_in_yunohost_format({"some_password": "password"}, questions) def test_parse_args_in_yunohost_format_password_optional_strong_enough(): @@ -566,10 +566,10 @@ def test_parse_args_in_yunohost_format_password_optional_strong_enough(): with pytest.raises(YunohostError): # too short - _parse_args_in_yunohost_format({"some_password": "a"}, questions) + parse_args_in_yunohost_format({"some_password": "a"}, questions) with pytest.raises(YunohostError): - _parse_args_in_yunohost_format({"some_password": "password"}, questions) + parse_args_in_yunohost_format({"some_password": "password"}, questions) def test_parse_args_in_yunohost_format_path(): @@ -581,7 +581,7 @@ def test_parse_args_in_yunohost_format_path(): ] answers = {"some_path": "some_value"} expected_result = OrderedDict({"some_path": ("some_value", "path")}) - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_path_no_input(): @@ -594,7 +594,7 @@ def test_parse_args_in_yunohost_format_path_no_input(): answers = {} with pytest.raises(YunohostError): - _parse_args_in_yunohost_format(answers, questions) + parse_args_in_yunohost_format(answers, questions) def test_parse_args_in_yunohost_format_path_input(): @@ -609,7 +609,7 @@ def test_parse_args_in_yunohost_format_path_input(): expected_result = OrderedDict({"some_path": ("some_value", "path")}) with patch.object(Moulinette.interface, "prompt", return_value="some_value"): - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_path_input_no_ask(): @@ -623,7 +623,7 @@ def test_parse_args_in_yunohost_format_path_input_no_ask(): expected_result = OrderedDict({"some_path": ("some_value", "path")}) with patch.object(Moulinette.interface, "prompt", return_value="some_value"): - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_path_no_input_optional(): @@ -636,7 +636,7 @@ def test_parse_args_in_yunohost_format_path_no_input_optional(): ] answers = {} expected_result = OrderedDict({"some_path": ("", "path")}) - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_path_optional_with_input(): @@ -652,7 +652,7 @@ def test_parse_args_in_yunohost_format_path_optional_with_input(): expected_result = OrderedDict({"some_path": ("some_value", "path")}) with patch.object(Moulinette.interface, "prompt", return_value="some_value"): - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_path_optional_with_empty_input(): @@ -668,7 +668,7 @@ def test_parse_args_in_yunohost_format_path_optional_with_empty_input(): expected_result = OrderedDict({"some_path": ("", "path")}) with patch.object(Moulinette.interface, "prompt", return_value=""): - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_path_optional_with_input_without_ask(): @@ -683,7 +683,7 @@ def test_parse_args_in_yunohost_format_path_optional_with_input_without_ask(): expected_result = OrderedDict({"some_path": ("some_value", "path")}) with patch.object(Moulinette.interface, "prompt", return_value="some_value"): - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_path_no_input_default(): @@ -697,7 +697,7 @@ def test_parse_args_in_yunohost_format_path_no_input_default(): ] answers = {} expected_result = OrderedDict({"some_path": ("some_value", "path")}) - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_path_input_test_ask(): @@ -714,7 +714,7 @@ def test_parse_args_in_yunohost_format_path_input_test_ask(): with patch.object( Moulinette.interface, "prompt", return_value="some_value" ) as prompt: - _parse_args_in_yunohost_format(answers, questions) + parse_args_in_yunohost_format(answers, questions) prompt.assert_called_with(ask_text, False) @@ -734,7 +734,7 @@ def test_parse_args_in_yunohost_format_path_input_test_ask_with_default(): with patch.object( Moulinette.interface, "prompt", return_value="some_value" ) as prompt: - _parse_args_in_yunohost_format(answers, questions) + parse_args_in_yunohost_format(answers, questions) prompt.assert_called_with("%s (default: %s)" % (ask_text, default_text), False) @@ -755,7 +755,7 @@ def test_parse_args_in_yunohost_format_path_input_test_ask_with_example(): with patch.object( Moulinette.interface, "prompt", return_value="some_value" ) as prompt: - _parse_args_in_yunohost_format(answers, questions) + parse_args_in_yunohost_format(answers, questions) assert ask_text in prompt.call_args[0][0] assert example_text in prompt.call_args[0][0] @@ -777,7 +777,7 @@ def test_parse_args_in_yunohost_format_path_input_test_ask_with_help(): with patch.object( Moulinette.interface, "prompt", return_value="some_value" ) as prompt: - _parse_args_in_yunohost_format(answers, questions) + parse_args_in_yunohost_format(answers, questions) assert ask_text in prompt.call_args[0][0] assert help_text in prompt.call_args[0][0] @@ -791,7 +791,7 @@ def test_parse_args_in_yunohost_format_boolean(): ] answers = {"some_boolean": "y"} expected_result = OrderedDict({"some_boolean": (1, "boolean")}) - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_boolean_all_yes(): @@ -803,47 +803,47 @@ def test_parse_args_in_yunohost_format_boolean_all_yes(): ] expected_result = OrderedDict({"some_boolean": (1, "boolean")}) assert ( - _parse_args_in_yunohost_format({"some_boolean": "y"}, questions) + parse_args_in_yunohost_format({"some_boolean": "y"}, questions) == expected_result ) assert ( - _parse_args_in_yunohost_format({"some_boolean": "Y"}, questions) + parse_args_in_yunohost_format({"some_boolean": "Y"}, questions) == expected_result ) assert ( - _parse_args_in_yunohost_format({"some_boolean": "yes"}, questions) + parse_args_in_yunohost_format({"some_boolean": "yes"}, questions) == expected_result ) assert ( - _parse_args_in_yunohost_format({"some_boolean": "Yes"}, questions) + parse_args_in_yunohost_format({"some_boolean": "Yes"}, questions) == expected_result ) assert ( - _parse_args_in_yunohost_format({"some_boolean": "YES"}, questions) + parse_args_in_yunohost_format({"some_boolean": "YES"}, questions) == expected_result ) assert ( - _parse_args_in_yunohost_format({"some_boolean": "1"}, questions) + parse_args_in_yunohost_format({"some_boolean": "1"}, questions) == expected_result ) assert ( - _parse_args_in_yunohost_format({"some_boolean": 1}, questions) + parse_args_in_yunohost_format({"some_boolean": 1}, questions) == expected_result ) assert ( - _parse_args_in_yunohost_format({"some_boolean": True}, questions) + parse_args_in_yunohost_format({"some_boolean": True}, questions) == expected_result ) assert ( - _parse_args_in_yunohost_format({"some_boolean": "True"}, questions) + parse_args_in_yunohost_format({"some_boolean": "True"}, questions) == expected_result ) assert ( - _parse_args_in_yunohost_format({"some_boolean": "TRUE"}, questions) + parse_args_in_yunohost_format({"some_boolean": "TRUE"}, questions) == expected_result ) assert ( - _parse_args_in_yunohost_format({"some_boolean": "true"}, questions) + parse_args_in_yunohost_format({"some_boolean": "true"}, questions) == expected_result ) @@ -857,47 +857,47 @@ def test_parse_args_in_yunohost_format_boolean_all_no(): ] expected_result = OrderedDict({"some_boolean": (0, "boolean")}) assert ( - _parse_args_in_yunohost_format({"some_boolean": "n"}, questions) + parse_args_in_yunohost_format({"some_boolean": "n"}, questions) == expected_result ) assert ( - _parse_args_in_yunohost_format({"some_boolean": "N"}, questions) + parse_args_in_yunohost_format({"some_boolean": "N"}, questions) == expected_result ) assert ( - _parse_args_in_yunohost_format({"some_boolean": "no"}, questions) + parse_args_in_yunohost_format({"some_boolean": "no"}, questions) == expected_result ) assert ( - _parse_args_in_yunohost_format({"some_boolean": "No"}, questions) + parse_args_in_yunohost_format({"some_boolean": "No"}, questions) == expected_result ) assert ( - _parse_args_in_yunohost_format({"some_boolean": "No"}, questions) + parse_args_in_yunohost_format({"some_boolean": "No"}, questions) == expected_result ) assert ( - _parse_args_in_yunohost_format({"some_boolean": "0"}, questions) + parse_args_in_yunohost_format({"some_boolean": "0"}, questions) == expected_result ) assert ( - _parse_args_in_yunohost_format({"some_boolean": 0}, questions) + parse_args_in_yunohost_format({"some_boolean": 0}, questions) == expected_result ) assert ( - _parse_args_in_yunohost_format({"some_boolean": False}, questions) + parse_args_in_yunohost_format({"some_boolean": False}, questions) == expected_result ) assert ( - _parse_args_in_yunohost_format({"some_boolean": "False"}, questions) + parse_args_in_yunohost_format({"some_boolean": "False"}, questions) == expected_result ) assert ( - _parse_args_in_yunohost_format({"some_boolean": "FALSE"}, questions) + parse_args_in_yunohost_format({"some_boolean": "FALSE"}, questions) == expected_result ) assert ( - _parse_args_in_yunohost_format({"some_boolean": "false"}, questions) + parse_args_in_yunohost_format({"some_boolean": "false"}, questions) == expected_result ) @@ -913,7 +913,7 @@ def test_parse_args_in_yunohost_format_boolean_no_input(): answers = {} expected_result = OrderedDict({"some_boolean": (0, "boolean")}) - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_boolean_bad_input(): @@ -926,7 +926,7 @@ def test_parse_args_in_yunohost_format_boolean_bad_input(): answers = {"some_boolean": "stuff"} with pytest.raises(YunohostError): - _parse_args_in_yunohost_format(answers, questions) + parse_args_in_yunohost_format(answers, questions) def test_parse_args_in_yunohost_format_boolean_input(): @@ -941,11 +941,11 @@ def test_parse_args_in_yunohost_format_boolean_input(): expected_result = OrderedDict({"some_boolean": (1, "boolean")}) with patch.object(Moulinette.interface, "prompt", return_value="y"): - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result expected_result = OrderedDict({"some_boolean": (0, "boolean")}) with patch.object(Moulinette.interface, "prompt", return_value="n"): - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_boolean_input_no_ask(): @@ -959,7 +959,7 @@ def test_parse_args_in_yunohost_format_boolean_input_no_ask(): expected_result = OrderedDict({"some_boolean": (1, "boolean")}) with patch.object(Moulinette.interface, "prompt", return_value="y"): - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_boolean_no_input_optional(): @@ -972,7 +972,7 @@ def test_parse_args_in_yunohost_format_boolean_no_input_optional(): ] answers = {} expected_result = OrderedDict({"some_boolean": (0, "boolean")}) # default to false - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_boolean_optional_with_input(): @@ -988,7 +988,7 @@ def test_parse_args_in_yunohost_format_boolean_optional_with_input(): expected_result = OrderedDict({"some_boolean": (1, "boolean")}) with patch.object(Moulinette.interface, "prompt", return_value="y"): - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_boolean_optional_with_empty_input(): @@ -1004,7 +1004,7 @@ def test_parse_args_in_yunohost_format_boolean_optional_with_empty_input(): expected_result = OrderedDict({"some_boolean": (0, "boolean")}) # default to false with patch.object(Moulinette.interface, "prompt", return_value=""): - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_boolean_optional_with_input_without_ask(): @@ -1019,7 +1019,7 @@ def test_parse_args_in_yunohost_format_boolean_optional_with_input_without_ask() expected_result = OrderedDict({"some_boolean": (0, "boolean")}) with patch.object(Moulinette.interface, "prompt", return_value="n"): - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_boolean_no_input_default(): @@ -1033,7 +1033,7 @@ def test_parse_args_in_yunohost_format_boolean_no_input_default(): ] answers = {} expected_result = OrderedDict({"some_boolean": (0, "boolean")}) - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_boolean_bad_default(): @@ -1047,7 +1047,7 @@ def test_parse_args_in_yunohost_format_boolean_bad_default(): ] answers = {} with pytest.raises(YunohostError): - _parse_args_in_yunohost_format(answers, questions) + parse_args_in_yunohost_format(answers, questions) def test_parse_args_in_yunohost_format_boolean_input_test_ask(): @@ -1062,7 +1062,7 @@ def test_parse_args_in_yunohost_format_boolean_input_test_ask(): answers = {} with patch.object(Moulinette.interface, "prompt", return_value=0) as prompt: - _parse_args_in_yunohost_format(answers, questions) + parse_args_in_yunohost_format(answers, questions) prompt.assert_called_with(ask_text + " [yes | no] (default: no)", False) @@ -1080,7 +1080,7 @@ def test_parse_args_in_yunohost_format_boolean_input_test_ask_with_default(): answers = {} with patch.object(Moulinette.interface, "prompt", return_value=1) as prompt: - _parse_args_in_yunohost_format(answers, questions) + parse_args_in_yunohost_format(answers, questions) prompt.assert_called_with("%s [yes | no] (default: yes)" % ask_text, False) @@ -1098,7 +1098,7 @@ def test_parse_args_in_yunohost_format_domain_empty(): with patch.object( domain, "_get_maindomain", return_value="my_main_domain.com" ), patch.object(domain, "domain_list", return_value={"domains": [main_domain]}): - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_domain(): @@ -1117,7 +1117,7 @@ def test_parse_args_in_yunohost_format_domain(): with patch.object( domain, "_get_maindomain", return_value=main_domain ), patch.object(domain, "domain_list", return_value={"domains": domains}): - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_domain_two_domains(): @@ -1137,7 +1137,7 @@ def test_parse_args_in_yunohost_format_domain_two_domains(): with patch.object( domain, "_get_maindomain", return_value=main_domain ), patch.object(domain, "domain_list", return_value={"domains": domains}): - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result answers = {"some_domain": main_domain} expected_result = OrderedDict({"some_domain": (main_domain, "domain")}) @@ -1145,7 +1145,7 @@ def test_parse_args_in_yunohost_format_domain_two_domains(): with patch.object( domain, "_get_maindomain", return_value=main_domain ), patch.object(domain, "domain_list", return_value={"domains": domains}): - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_domain_two_domains_wrong_answer(): @@ -1165,7 +1165,7 @@ def test_parse_args_in_yunohost_format_domain_two_domains_wrong_answer(): domain, "_get_maindomain", return_value=main_domain ), patch.object(domain, "domain_list", return_value={"domains": domains}): with pytest.raises(YunohostError): - _parse_args_in_yunohost_format(answers, questions) + parse_args_in_yunohost_format(answers, questions) def test_parse_args_in_yunohost_format_domain_two_domains_default_no_ask(): @@ -1185,7 +1185,7 @@ def test_parse_args_in_yunohost_format_domain_two_domains_default_no_ask(): with patch.object( domain, "_get_maindomain", return_value=main_domain ), patch.object(domain, "domain_list", return_value={"domains": domains}): - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_domain_two_domains_default(): @@ -1200,7 +1200,7 @@ def test_parse_args_in_yunohost_format_domain_two_domains_default(): with patch.object( domain, "_get_maindomain", return_value=main_domain ), patch.object(domain, "domain_list", return_value={"domains": domains}): - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_domain_two_domains_default_input(): @@ -1216,11 +1216,11 @@ def test_parse_args_in_yunohost_format_domain_two_domains_default_input(): ), patch.object(domain, "domain_list", return_value={"domains": domains}): expected_result = OrderedDict({"some_domain": (main_domain, "domain")}) with patch.object(Moulinette.interface, "prompt", return_value=main_domain): - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result expected_result = OrderedDict({"some_domain": (other_domain, "domain")}) with patch.object(Moulinette.interface, "prompt", return_value=other_domain): - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_user_empty(): @@ -1244,7 +1244,7 @@ def test_parse_args_in_yunohost_format_user_empty(): with patch.object(user, "user_list", return_value={"users": users}): with pytest.raises(YunohostError): - _parse_args_in_yunohost_format(answers, questions) + parse_args_in_yunohost_format(answers, questions) def test_parse_args_in_yunohost_format_user(): @@ -1271,7 +1271,7 @@ def test_parse_args_in_yunohost_format_user(): with patch.object(user, "user_list", return_value={"users": users}): with patch.object(user, "user_info", return_value={}): - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_user_two_users(): @@ -1305,14 +1305,14 @@ def test_parse_args_in_yunohost_format_user_two_users(): with patch.object(user, "user_list", return_value={"users": users}): with patch.object(user, "user_info", return_value={}): - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result answers = {"some_user": username} expected_result = OrderedDict({"some_user": (username, "user")}) with patch.object(user, "user_list", return_value={"users": users}): with patch.object(user, "user_info", return_value={}): - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_user_two_users_wrong_answer(): @@ -1345,7 +1345,7 @@ def test_parse_args_in_yunohost_format_user_two_users_wrong_answer(): with patch.object(user, "user_list", return_value={"users": users}): with pytest.raises(YunohostError): - _parse_args_in_yunohost_format(answers, questions) + parse_args_in_yunohost_format(answers, questions) def test_parse_args_in_yunohost_format_user_two_users_no_default(): @@ -1373,7 +1373,7 @@ def test_parse_args_in_yunohost_format_user_two_users_no_default(): with patch.object(user, "user_list", return_value={"users": users}): with pytest.raises(YunohostError): - _parse_args_in_yunohost_format(answers, questions) + parse_args_in_yunohost_format(answers, questions) def test_parse_args_in_yunohost_format_user_two_users_default_input(): @@ -1404,14 +1404,14 @@ def test_parse_args_in_yunohost_format_user_two_users_default_input(): expected_result = OrderedDict({"some_user": (username, "user")}) with patch.object(Moulinette.interface, "prompt", return_value=username): assert ( - _parse_args_in_yunohost_format(answers, questions) + parse_args_in_yunohost_format(answers, questions) == expected_result ) expected_result = OrderedDict({"some_user": (other_user, "user")}) with patch.object(Moulinette.interface, "prompt", return_value=other_user): assert ( - _parse_args_in_yunohost_format(answers, questions) + parse_args_in_yunohost_format(answers, questions) == expected_result ) @@ -1425,7 +1425,7 @@ def test_parse_args_in_yunohost_format_number(): ] answers = {"some_number": 1337} expected_result = OrderedDict({"some_number": (1337, "number")}) - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_number_no_input(): @@ -1438,7 +1438,7 @@ def test_parse_args_in_yunohost_format_number_no_input(): answers = {} expected_result = OrderedDict({"some_number": (0, "number")}) - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_number_bad_input(): @@ -1451,11 +1451,11 @@ def test_parse_args_in_yunohost_format_number_bad_input(): answers = {"some_number": "stuff"} with pytest.raises(YunohostError): - _parse_args_in_yunohost_format(answers, questions) + parse_args_in_yunohost_format(answers, questions) answers = {"some_number": 1.5} with pytest.raises(YunohostError): - _parse_args_in_yunohost_format(answers, questions) + parse_args_in_yunohost_format(answers, questions) def test_parse_args_in_yunohost_format_number_input(): @@ -1470,14 +1470,14 @@ def test_parse_args_in_yunohost_format_number_input(): expected_result = OrderedDict({"some_number": (1337, "number")}) with patch.object(Moulinette.interface, "prompt", return_value="1337"): - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result with patch.object(Moulinette.interface, "prompt", return_value=1337): - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result expected_result = OrderedDict({"some_number": (0, "number")}) with patch.object(Moulinette.interface, "prompt", return_value=""): - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_number_input_no_ask(): @@ -1491,7 +1491,7 @@ def test_parse_args_in_yunohost_format_number_input_no_ask(): expected_result = OrderedDict({"some_number": (1337, "number")}) with patch.object(Moulinette.interface, "prompt", return_value="1337"): - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_number_no_input_optional(): @@ -1504,7 +1504,7 @@ def test_parse_args_in_yunohost_format_number_no_input_optional(): ] answers = {} expected_result = OrderedDict({"some_number": (0, "number")}) # default to 0 - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_number_optional_with_input(): @@ -1520,7 +1520,7 @@ def test_parse_args_in_yunohost_format_number_optional_with_input(): expected_result = OrderedDict({"some_number": (1337, "number")}) with patch.object(Moulinette.interface, "prompt", return_value="1337"): - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_number_optional_with_input_without_ask(): @@ -1535,7 +1535,7 @@ def test_parse_args_in_yunohost_format_number_optional_with_input_without_ask(): expected_result = OrderedDict({"some_number": (0, "number")}) with patch.object(Moulinette.interface, "prompt", return_value="0"): - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_number_no_input_default(): @@ -1549,7 +1549,7 @@ def test_parse_args_in_yunohost_format_number_no_input_default(): ] answers = {} expected_result = OrderedDict({"some_number": (1337, "number")}) - assert _parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_number_bad_default(): @@ -1563,7 +1563,7 @@ def test_parse_args_in_yunohost_format_number_bad_default(): ] answers = {} with pytest.raises(YunohostError): - _parse_args_in_yunohost_format(answers, questions) + parse_args_in_yunohost_format(answers, questions) def test_parse_args_in_yunohost_format_number_input_test_ask(): @@ -1578,7 +1578,7 @@ def test_parse_args_in_yunohost_format_number_input_test_ask(): answers = {} with patch.object(Moulinette.interface, "prompt", return_value="1111") as prompt: - _parse_args_in_yunohost_format(answers, questions) + parse_args_in_yunohost_format(answers, questions) prompt.assert_called_with("%s (default: 0)" % (ask_text), False) @@ -1596,7 +1596,7 @@ def test_parse_args_in_yunohost_format_number_input_test_ask_with_default(): answers = {} with patch.object(Moulinette.interface, "prompt", return_value="1111") as prompt: - _parse_args_in_yunohost_format(answers, questions) + parse_args_in_yunohost_format(answers, questions) prompt.assert_called_with("%s (default: %s)" % (ask_text, default_value), False) @@ -1615,7 +1615,7 @@ def test_parse_args_in_yunohost_format_number_input_test_ask_with_example(): answers = {} with patch.object(Moulinette.interface, "prompt", return_value="1111") as prompt: - _parse_args_in_yunohost_format(answers, questions) + parse_args_in_yunohost_format(answers, questions) assert ask_text in prompt.call_args[0][0] assert example_value in prompt.call_args[0][0] @@ -1635,7 +1635,7 @@ def test_parse_args_in_yunohost_format_number_input_test_ask_with_help(): answers = {} with patch.object(Moulinette.interface, "prompt", return_value="1111") as prompt: - _parse_args_in_yunohost_format(answers, questions) + parse_args_in_yunohost_format(answers, questions) assert ask_text in prompt.call_args[0][0] assert help_value in prompt.call_args[0][0] @@ -1645,5 +1645,5 @@ def test_parse_args_in_yunohost_format_display_text(): answers = {} with patch.object(sys, "stdout", new_callable=StringIO) as stdout: - _parse_args_in_yunohost_format(answers, questions) + parse_args_in_yunohost_format(answers, questions) assert "foobar" in stdout.getvalue() diff --git a/src/yunohost/tests/test_service.py b/src/yunohost/tests/test_service.py index 1007419a1..88013a3fe 100644 --- a/src/yunohost/tests/test_service.py +++ b/src/yunohost/tests/test_service.py @@ -132,7 +132,7 @@ def test_service_conf_broken(): status = service_status("nginx") assert status["status"] == "running" assert status["configuration"] == "broken" - assert "broken.conf" in status["configuration-details"] + assert "broken.conf" in status["configuration-details"][0] # Service reload-or-restart should check that the conf ain't valid # before reload-or-restart, hence the service should still be running From c37b21ae140116a1b228344afa2e896e9ec07119 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 5 Sep 2021 15:39:43 +0200 Subject: [PATCH 2922/3170] ci: test-actionmap should only test actionmap ;) --- .gitlab/ci/test.gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab/ci/test.gitlab-ci.yml b/.gitlab/ci/test.gitlab-ci.yml index e0e0e001a..80d710f71 100644 --- a/.gitlab/ci/test.gitlab-ci.yml +++ b/.gitlab/ci/test.gitlab-ci.yml @@ -71,7 +71,7 @@ test-translation-format-consistency: test-actionmap: extends: .test-stage script: - - python3 -m pytest tests tests/test_actionmap.py + - python3 -m pytest tests/test_actionmap.py only: changes: - data/actionsmap/*.yml From 1207b54de6c12f64735ffda21401b41e9861a862 Mon Sep 17 00:00:00 2001 From: ljf Date: Sun, 5 Sep 2021 16:08:23 +0200 Subject: [PATCH 2923/3170] [fix] ynh_read_var_in_file with endline --- data/helpers.d/utils | 48 ++++++++++++++---- tests/test_helpers.d/ynhtest_config.sh | 67 +++++++++++++++----------- 2 files changed, 77 insertions(+), 38 deletions(-) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 3389101a6..0a820505c 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -507,6 +507,7 @@ ynh_replace_vars () { # # Requires YunoHost version 4.3 or higher. ynh_read_var_in_file() { + set +o xtrace # Declare an array to define the options of this helper. local legacy_args=fk local -A args_array=( [f]=file= [k]=key= ) @@ -514,18 +515,43 @@ ynh_read_var_in_file() { local key # Manage arguments with getopts ynh_handle_getopts_args "$@" + set +o xtrace + local filename="$(basename -- "$file")" + local ext="${filename##*.}" + local endline=',;' + local assign="=>|:|=" + local comments="#" + local string="\"'" + if [[ "yaml yml toml ini env" =~ *"$ext"* ]]; then + endline='#' + fi + if [[ "php" == "$ext" ]] || [[ "$ext" == "js" ]]; then + comments="//" + fi + local list='\[\s*['$string']?\w+['$string']?\]' + local var_part='^\s*(?:(const|var|let)\s+)?\$?(\w+('$list')*(->|\.|\[))*\s*' + var_part+="[$string]?${key}[$string]?" + var_part+='\s*\]?\s*' + var_part+="(?:$assign)" + var_part+='\s*' - local var_part='^[ \t]*\$?(\w*\[)?[ \t]*["'"']?${key}['"'"]?[ \t]*\]?[ \t]*[:=]>?[ \t]*' + # Extract the part after assignation sign + local expression_with_comment="$(grep -i -o -P $var_part'\K.*$' ${file} || echo YNH_NULL | head -n1)" + if [[ "$expression_with_comment" == "YNH_NULL" ]]; then + echo YNH_NULL + return 0 + fi - local crazy_value="$(grep -i -o -P '^[ \t]*\$?(\w*\[)?[ \t]*["'"']?${key}['"'"]?[ \t]*\]?[ \t]*[:=]>?[ \t]*\K.*(?=[ \t,\n;]*$)' ${file} || echo YNH_NULL | head -n1)" + # Remove comments if needed + local expression="$(echo "$expression_with_comment" | sed "s@$comments[^$string]*\$@@" | sed "s@\s*[$endline]*\s*]*\$@@")" - local first_char="${crazy_value:0:1}" + local first_char="${expression:0:1}" if [[ "$first_char" == '"' ]] ; then - echo "$crazy_value" | grep -m1 -o -P '"\K([^"](\\")?)*[^\\](?=")' | head -n1 | sed 's/\\"/"/g' + echo "$expression" | grep -m1 -o -P '"\K([^"](\\")?)*[^\\](?=")' | head -n1 | sed 's/\\"/"/g' elif [[ "$first_char" == "'" ]] ; then - echo "$crazy_value" | grep -m1 -o -P "'\K([^'](\\\\')?)*[^\\\\](?=')" | head -n1 | sed "s/\\\\'/'/g" + echo "$expression" | grep -m1 -o -P "'\K([^'](\\\\')?)*[^\\\\](?=')" | head -n1 | sed "s/\\\\'/'/g" else - echo "$crazy_value" + echo "$expression" fi } @@ -538,6 +564,7 @@ ynh_read_var_in_file() { # # Requires YunoHost version 4.3 or higher. ynh_write_var_in_file() { + set +o xtrace # Declare an array to define the options of this helper. local legacy_args=fkv local -A args_array=( [f]=file= [k]=key= [v]=value=) @@ -546,9 +573,10 @@ ynh_write_var_in_file() { local value # Manage arguments with getopts ynh_handle_getopts_args "$@" - local var_part='[ \t]*\$?(\w*\[)?[ \t]*["'"']?${key}['"'"]?[ \t]*\]?[ \t]*[:=]>?[ \t]*' + set +o xtrace + local var_part='\s*\$?([\w.]*\[)?\s*["'"']?${key}['"'"]?\s*\]?\s*[:=]>?\s*' - local crazy_value="$(grep -i -o -P '^[ \t]*\$?(\w*\[)?[ \t]*["'"']?${key}['"'"]?[ \t]*\]?[ \t]*[:=]>?[ \t]*\K.*(?=[ \t,\n;]*$)' ${file} | head -n1)" + local crazy_value="$(grep -i -o -P '^\s*\$?([\w.]*\[)?\s*["'"']?${key}['"'"]?\s*\]?\s*[:=]>?\s*\K.*(?=[\s,;]*$)' ${file} | head -n1)" # local crazy_value="$(grep -i -o -P "^${var_part}\K.*(?=[ \t,\n;]*\$)" ${file} | head -n1)" local first_char="${crazy_value:0:1}" delimiter=$'\001' @@ -556,13 +584,13 @@ ynh_write_var_in_file() { # \ and sed is quite complex you need 2 \\ to get one in a sed # So we need \\\\ to go through 2 sed value="$(echo "$value" | sed 's/"/\\\\"/g')" - sed -ri s$delimiter'^('"${var_part}"'")([^"]|\\")*("[ \t;,]*)$'$delimiter'\1'"${value}"'\4'$delimiter'i' ${file} + sed -ri s$delimiter'^('"${var_part}"'")([^"]|\\")*("[\s;,]*)$'$delimiter'\1'"${value}"'\4'$delimiter'i' ${file} elif [[ "$first_char" == "'" ]] ; then # \ and sed is quite complex you need 2 \\ to get one in a sed # However double quotes implies to double \\ to # So we need \\\\\\\\ to go through 2 sed and 1 double quotes str value="$(echo "$value" | sed "s/'/\\\\\\\\'/g")" - sed -ri "s$delimiter^(${var_part}')([^']|\\')*('"'[ \t,;]*)$'$delimiter'\1'"${value}"'\4'$delimiter'i' ${file} + sed -ri "s$delimiter^(${var_part}')([^']|\\')*('"'[\s,;]*)$'$delimiter'\1'"${value}"'\4'$delimiter'i' ${file} else if [[ "$value" == *"'"* ]] || [[ "$value" == *'"'* ]] ; then value='\"'"$(echo "$value" | sed 's/"/\\\\"/g')"'\"' diff --git a/tests/test_helpers.d/ynhtest_config.sh b/tests/test_helpers.d/ynhtest_config.sh index 7b749adf5..36165e3ac 100644 --- a/tests/test_helpers.d/ynhtest_config.sh +++ b/tests/test_helpers.d/ynhtest_config.sh @@ -27,11 +27,13 @@ ENABLED = False # TITLE = "Old title" TITLE = "Lorem Ipsum" THEME = "colib'ris" -EMAIL = "root@example.com" -PORT = 1234 +EMAIL = "root@example.com" # This is a comment without quotes +PORT = 1234 # This is a comment without quotes URL = 'https://yunohost.org' DICT = {} DICT['ldap_base'] = "ou=users,dc=yunohost,dc=org" +DICT['ldap_conf'] = {} +DICT['ldap_conf']['user'] = "camille" EOF test "$(_read_py "$file" "FOO")" == "None" @@ -56,6 +58,8 @@ EOF test "$(ynh_read_var_in_file "$file" "URL")" == "https://yunohost.org" test "$(ynh_read_var_in_file "$file" "ldap_base")" == "ou=users,dc=yunohost,dc=org" + + test "$(ynh_read_var_in_file "$file" "user")" == "camille" ! _read_py "$file" "NONEXISTENT" test "$(ynh_read_var_in_file "$file" "NONEXISTENT")" == "YNH_NULL" @@ -64,7 +68,7 @@ EOF test "$(ynh_read_var_in_file "$file" "ENABLE")" == "YNH_NULL" } -ynhtest_config_write_py() { +nhtest_config_write_py() { local dummy_dir="$(mktemp -d -p $VAR_WWW)" file="$dummy_dir/dummy.py" @@ -75,8 +79,8 @@ ENABLED = False # TITLE = "Old title" TITLE = "Lorem Ipsum" THEME = "colib'ris" -EMAIL = "root@example.com" -PORT = 1234 +EMAIL = "root@example.com" // This is a comment without quotes +PORT = 1234 // This is a comment without quotes URL = 'https://yunohost.org' DICT = {} DICT['ldap_base'] = "ou=users,dc=yunohost,dc=org" @@ -141,7 +145,7 @@ _read_ini() { ynhtest_config_read_ini() { local dummy_dir="$(mktemp -d -p $VAR_WWW)" - file="$dummy_dir/dummy.yml" + file="$dummy_dir/dummy.ini" cat << EOF > $file # Some comment @@ -152,8 +156,8 @@ enabled = False # title = Old title title = Lorem Ipsum theme = colib'ris -email = root@example.com -port = 1234 +email = root@example.com # This is a comment without quotes +port = 1234 # This is a comment without quotes url = https://yunohost.org [dict] ldap_base = ou=users,dc=yunohost,dc=org @@ -190,7 +194,7 @@ EOF } -ynhtest_config_write_ini() { +nhtest_config_write_ini() { local dummy_dir="$(mktemp -d -p $VAR_WWW)" file="$dummy_dir/dummy.ini" @@ -203,8 +207,8 @@ enabled = False # title = Old title title = Lorem Ipsum theme = colib'ris -email = root@example.com -port = 1234 +email = root@example.com # This is a comment without quotes +port = 1234 # This is a comment without quotes url = https://yunohost.org [dict] ldap_base = ou=users,dc=yunohost,dc=org @@ -280,8 +284,8 @@ enabled: false # title: old title title: Lorem Ipsum theme: colib'ris -email: root@example.com -port: 1234 +email: root@example.com # This is a comment without quotes +port: 1234 # This is a comment without quotes url: https://yunohost.org dict: ldap_base: ou=users,dc=yunohost,dc=org @@ -318,7 +322,7 @@ EOF } -ynhtest_config_write_yaml() { +nhtest_config_write_yaml() { local dummy_dir="$(mktemp -d -p $VAR_WWW)" file="$dummy_dir/dummy.yml" @@ -329,8 +333,8 @@ enabled: false # title: old title title: Lorem Ipsum theme: colib'ris -email: root@example.com -port: 1234 +email: root@example.com # This is a comment without quotes +port: 1234 # This is a comment without quotes url: https://yunohost.org dict: ldap_base: ou=users,dc=yunohost,dc=org @@ -415,10 +419,10 @@ EOF test "$(_read_json "$file" "foo")" == "None" - test "$(ynh_read_var_in_file "$file" "foo")" == "null," # FIXME FIXME FIXME trailing , + test "$(ynh_read_var_in_file "$file" "foo")" == "null" test "$(_read_json "$file" "enabled")" == "False" - test "$(ynh_read_var_in_file "$file" "enabled")" == "false," # FIXME FIXME FIXME trailing , + test "$(ynh_read_var_in_file "$file" "enabled")" == "false" test "$(_read_json "$file" "title")" == "Lorem Ipsum" test "$(ynh_read_var_in_file "$file" "title")" == "Lorem Ipsum" @@ -430,7 +434,7 @@ EOF test "$(ynh_read_var_in_file "$file" "email")" == "root@example.com" test "$(_read_json "$file" "port")" == "1234" - test "$(ynh_read_var_in_file "$file" "port")" == "1234," # FIXME FIXME FIXME trailing , + test "$(ynh_read_var_in_file "$file" "port")" == "1234" test "$(_read_json "$file" "url")" == "https://yunohost.org" test "$(ynh_read_var_in_file "$file" "url")" == "https://yunohost.org" @@ -445,7 +449,7 @@ EOF } -ynhtest_config_write_json() { +nhtest_config_write_json() { local dummy_dir="$(mktemp -d -p $VAR_WWW)" file="$dummy_dir/dummy.json" @@ -538,20 +542,23 @@ ynhtest_config_read_php() { // \$title = "old title"; \$title = "Lorem Ipsum"; \$theme = "colib'ris"; - \$email = "root@example.com"; - \$port = 1234; + \$email = "root@example.com"; // This is a comment without quotes + \$port = 1234; // This is a second comment without quotes \$url = "https://yunohost.org"; \$dict = [ 'ldap_base' => "ou=users,dc=yunohost,dc=org", + 'ldap_conf' => [] ]; + \$dict['ldap_conf']['user'] = 'camille'; + const DB_HOST = 'localhost'; ?> EOF test "$(_read_php "$file" "foo")" == "NULL" - test "$(ynh_read_var_in_file "$file" "foo")" == "NULL;" # FIXME FIXME FIXME trailing ; + test "$(ynh_read_var_in_file "$file" "foo")" == "NULL" test "$(_read_php "$file" "enabled")" == "false" - test "$(ynh_read_var_in_file "$file" "enabled")" == "false;" # FIXME FIXME FIXME trailing ; + test "$(ynh_read_var_in_file "$file" "enabled")" == "false" test "$(_read_php "$file" "title")" == "Lorem Ipsum" test "$(ynh_read_var_in_file "$file" "title")" == "Lorem Ipsum" @@ -563,12 +570,16 @@ EOF test "$(ynh_read_var_in_file "$file" "email")" == "root@example.com" test "$(_read_php "$file" "port")" == "1234" - test "$(ynh_read_var_in_file "$file" "port")" == "1234;" # FIXME FIXME FIXME trailing ; + test "$(ynh_read_var_in_file "$file" "port")" == "1234" test "$(_read_php "$file" "url")" == "https://yunohost.org" test "$(ynh_read_var_in_file "$file" "url")" == "https://yunohost.org" test "$(ynh_read_var_in_file "$file" "ldap_base")" == "ou=users,dc=yunohost,dc=org" + + test "$(ynh_read_var_in_file "$file" "user")" == "camille" + + test "$(ynh_read_var_in_file "$file" "DB_HOST")" == "localhost" ! _read_php "$file" "nonexistent" test "$(ynh_read_var_in_file "$file" "nonexistent")" == "YNH_NULL" @@ -578,7 +589,7 @@ EOF } -ynhtest_config_write_php() { +nhtest_config_write_php() { local dummy_dir="$(mktemp -d -p $VAR_WWW)" file="$dummy_dir/dummy.php" @@ -590,8 +601,8 @@ ynhtest_config_write_php() { // \$title = "old title"; \$title = "Lorem Ipsum"; \$theme = "colib'ris"; - \$email = "root@example.com"; - \$port = 1234; + \$email = "root@example.com"; // This is a comment without quotes + \$port = 1234; // This is a comment without quotes \$url = "https://yunohost.org"; \$dict = [ 'ldap_base' => "ou=users,dc=yunohost,dc=org", From 3b0bf74274a07d26e3da6da9d77b52f9c475f272 Mon Sep 17 00:00:00 2001 From: ljf Date: Sun, 5 Sep 2021 16:19:35 +0200 Subject: [PATCH 2924/3170] [fix] Unbound var in config panel --- src/yunohost/utils/config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 5c81f5007..a50e460bb 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -599,7 +599,7 @@ class BooleanQuestion(Question): raise YunohostValidationError( "app_argument_choice_invalid", - name=self.name, # FIXME ... + name=option.get("name", ""), value=value, choices="yes, no, y, n, 1, 0", ) @@ -619,7 +619,7 @@ class BooleanQuestion(Question): return None raise YunohostValidationError( "app_argument_choice_invalid", - name=self.name, # FIXME.... + name=option.get("name", ""), value=value, choices="yes, no, y, n, 1, 0", ) From 6aead55c1c87d76237f4175c4052a1203119edbe Mon Sep 17 00:00:00 2001 From: ljf Date: Sun, 5 Sep 2021 16:34:18 +0200 Subject: [PATCH 2925/3170] [enh] Add a comment --- data/other/config_domain.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/data/other/config_domain.toml b/data/other/config_domain.toml index 07aaf085e..44766e2d0 100644 --- a/data/other/config_domain.toml +++ b/data/other/config_domain.toml @@ -27,6 +27,7 @@ i18n = "domain_config" [dns] [dns.registrar] optional = true + # This part is replace dynamically by DomainConfigPanel [dns.registrar.unsupported] ask = "DNS zone of this domain can't be auto-configured, you should do it manually." type = "alert" From 4332278f07146aba49ef644f4d5a482f7886e44c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 5 Sep 2021 17:08:18 +0200 Subject: [PATCH 2926/3170] service test conf: gotta decode the output --- 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 5f9f3e60a..2cebc5a0a 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -286,7 +286,7 @@ def service_reload_or_restart(names, test_conf=True): out, _ = p.communicate() if p.returncode != 0: - errors = out.strip().split("\n") + errors = out.decode().strip().split("\n") logger.error( m18n.n("service_not_reloading_because_conf_broken", errors=errors) ) From 1b99bb8b0f40678c1e930f0536197d664c76e177 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 5 Sep 2021 17:11:00 +0200 Subject: [PATCH 2927/3170] app_argument_invalid: dunno why name was changed to field but this was breaking i18n consistency --- locales/en.json | 2 +- src/yunohost/utils/config.py | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/locales/en.json b/locales/en.json index 2bc0516e7..2ac7aa42a 100644 --- a/locales/en.json +++ b/locales/en.json @@ -14,7 +14,7 @@ "app_already_installed_cant_change_url": "This app is already installed. The URL cannot be changed just by this function. Check in `app changeurl` if it's available.", "app_already_up_to_date": "{app} is already up-to-date", "app_argument_choice_invalid": "Use one of these choices '{choices}' for the argument '{name}' instead of '{value}'", - "app_argument_invalid": "Pick a valid value for the argument '{field}': {error}", + "app_argument_invalid": "Pick a valid value for the argument '{name}': {error}", "app_argument_password_no_default": "Error while parsing password argument '{name}': password argument can't have a default value for security reason", "app_argument_required": "Argument '{name}' is required", "app_change_url_failed_nginx_reload": "Could not reload NGINX. Here is the output of 'nginx -t':\n{nginx_errors}", diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index a50e460bb..26e6e3ebb 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -664,7 +664,7 @@ class DomainQuestion(Question): def _raise_invalid_answer(self): raise YunohostValidationError( - "app_argument_invalid", field=self.name, error=m18n.n("domain_unknown") + "app_argument_invalid", name=self.name, error=m18n.n("domain_unknown") ) @@ -687,7 +687,7 @@ class UserQuestion(Question): def _raise_invalid_answer(self): raise YunohostValidationError( "app_argument_invalid", - field=self.name, + name=self.name, error=m18n.n("user_unknown", user=self.value), ) @@ -713,21 +713,21 @@ class NumberQuestion(Question): ): raise YunohostValidationError( "app_argument_invalid", - field=self.name, + name=self.name, error=m18n.n("invalid_number"), ) if self.min is not None and int(self.value) < self.min: raise YunohostValidationError( "app_argument_invalid", - field=self.name, + name=self.name, error=m18n.n("invalid_number"), ) if self.max is not None and int(self.value) > self.max: raise YunohostValidationError( "app_argument_invalid", - field=self.name, + name=self.name, error=m18n.n("invalid_number"), ) @@ -739,7 +739,7 @@ class NumberQuestion(Question): return int(self.value) raise YunohostValidationError( - "app_argument_invalid", field=self.name, error=m18n.n("invalid_number") + "app_argument_invalid", name=self.name, error=m18n.n("invalid_number") ) @@ -805,7 +805,7 @@ class FileQuestion(Question): ): raise YunohostValidationError( "app_argument_invalid", - field=self.name, + name=self.name, error=m18n.n("file_does_not_exist", path=self.value), ) if self.value in [None, ""] or not self.accept: @@ -815,7 +815,7 @@ class FileQuestion(Question): if "." not in filename or "." + filename.split(".")[-1] not in self.accept: raise YunohostValidationError( "app_argument_invalid", - field=self.name, + name=self.name, error=m18n.n( "file_extension_not_accepted", file=filename, accept=self.accept ), From 2a8e4030925aaefd20df8c0b6fae47a27be73aaa Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 5 Sep 2021 17:34:03 +0200 Subject: [PATCH 2928/3170] test_parse_args_in_yunohost_format -> test_question --- .gitlab/ci/test.gitlab-ci.yml | 4 +- .../{test_config.py => test_questions.py} | 184 +++++++++--------- src/yunohost/utils/config.py | 2 +- 3 files changed, 95 insertions(+), 95 deletions(-) rename src/yunohost/tests/{test_config.py => test_questions.py} (87%) diff --git a/.gitlab/ci/test.gitlab-ci.yml b/.gitlab/ci/test.gitlab-ci.yml index 31f5aef17..0b35d3447 100644 --- a/.gitlab/ci/test.gitlab-ci.yml +++ b/.gitlab/ci/test.gitlab-ci.yml @@ -112,11 +112,11 @@ test-appurl: changes: - src/yunohost/app.py -test-config: +test-questions: extends: .test-stage script: - cd src/yunohost - - python3 -m pytest tests/test_config.py + - python3 -m pytest tests/test_questions.py only: changes: - src/yunohost/utils/config.py diff --git a/src/yunohost/tests/test_config.py b/src/yunohost/tests/test_questions.py similarity index 87% rename from src/yunohost/tests/test_config.py rename to src/yunohost/tests/test_questions.py index 2e52c4086..f41c3c0cd 100644 --- a/src/yunohost/tests/test_config.py +++ b/src/yunohost/tests/test_questions.py @@ -35,11 +35,11 @@ User answers: """ -def test_parse_args_in_yunohost_format_empty(): +def test_question_empty(): assert parse_args_in_yunohost_format({}, []) == {} -def test_parse_args_in_yunohost_format_string(): +def test_question_string(): questions = [ { "name": "some_string", @@ -51,7 +51,7 @@ def test_parse_args_in_yunohost_format_string(): assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_string_default_type(): +def test_question_string_default_type(): questions = [ { "name": "some_string", @@ -62,7 +62,7 @@ def test_parse_args_in_yunohost_format_string_default_type(): assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_string_no_input(): +def test_question_string_no_input(): questions = [ { "name": "some_string", @@ -74,7 +74,7 @@ def test_parse_args_in_yunohost_format_string_no_input(): parse_args_in_yunohost_format(answers, questions) -def test_parse_args_in_yunohost_format_string_input(): +def test_question_string_input(): questions = [ { "name": "some_string", @@ -88,7 +88,7 @@ def test_parse_args_in_yunohost_format_string_input(): assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_string_input_no_ask(): +def test_question_string_input_no_ask(): questions = [ { "name": "some_string", @@ -101,7 +101,7 @@ def test_parse_args_in_yunohost_format_string_input_no_ask(): assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_string_no_input_optional(): +def test_question_string_no_input_optional(): questions = [ { "name": "some_string", @@ -113,7 +113,7 @@ def test_parse_args_in_yunohost_format_string_no_input_optional(): assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_string_optional_with_input(): +def test_question_string_optional_with_input(): questions = [ { "name": "some_string", @@ -128,7 +128,7 @@ def test_parse_args_in_yunohost_format_string_optional_with_input(): assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_string_optional_with_empty_input(): +def test_question_string_optional_with_empty_input(): questions = [ { "name": "some_string", @@ -143,7 +143,7 @@ def test_parse_args_in_yunohost_format_string_optional_with_empty_input(): assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_string_optional_with_input_without_ask(): +def test_question_string_optional_with_input_without_ask(): questions = [ { "name": "some_string", @@ -157,7 +157,7 @@ def test_parse_args_in_yunohost_format_string_optional_with_input_without_ask(): assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_string_no_input_default(): +def test_question_string_no_input_default(): questions = [ { "name": "some_string", @@ -170,7 +170,7 @@ def test_parse_args_in_yunohost_format_string_no_input_default(): assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_string_input_test_ask(): +def test_question_string_input_test_ask(): ask_text = "some question" questions = [ { @@ -187,7 +187,7 @@ def test_parse_args_in_yunohost_format_string_input_test_ask(): prompt.assert_called_with(ask_text, False) -def test_parse_args_in_yunohost_format_string_input_test_ask_with_default(): +def test_question_string_input_test_ask_with_default(): ask_text = "some question" default_text = "some example" questions = [ @@ -207,7 +207,7 @@ def test_parse_args_in_yunohost_format_string_input_test_ask_with_default(): @pytest.mark.skip # we should do something with this example -def test_parse_args_in_yunohost_format_string_input_test_ask_with_example(): +def test_question_string_input_test_ask_with_example(): ask_text = "some question" example_text = "some example" questions = [ @@ -228,7 +228,7 @@ def test_parse_args_in_yunohost_format_string_input_test_ask_with_example(): @pytest.mark.skip # we should do something with this help -def test_parse_args_in_yunohost_format_string_input_test_ask_with_help(): +def test_question_string_input_test_ask_with_help(): ask_text = "some question" help_text = "some_help" questions = [ @@ -248,14 +248,14 @@ def test_parse_args_in_yunohost_format_string_input_test_ask_with_help(): assert help_text in prompt.call_args[0][0] -def test_parse_args_in_yunohost_format_string_with_choice(): +def test_question_string_with_choice(): questions = [{"name": "some_string", "type": "string", "choices": ["fr", "en"]}] answers = {"some_string": "fr"} expected_result = OrderedDict({"some_string": ("fr", "string")}) assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_string_with_choice_prompt(): +def test_question_string_with_choice_prompt(): questions = [{"name": "some_string", "type": "string", "choices": ["fr", "en"]}] answers = {"some_string": "fr"} expected_result = OrderedDict({"some_string": ("fr", "string")}) @@ -263,7 +263,7 @@ def test_parse_args_in_yunohost_format_string_with_choice_prompt(): assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_string_with_choice_bad(): +def test_question_string_with_choice_bad(): questions = [{"name": "some_string", "type": "string", "choices": ["fr", "en"]}] answers = {"some_string": "bad"} @@ -271,7 +271,7 @@ def test_parse_args_in_yunohost_format_string_with_choice_bad(): assert parse_args_in_yunohost_format(answers, questions) -def test_parse_args_in_yunohost_format_string_with_choice_ask(): +def test_question_string_with_choice_ask(): ask_text = "some question" choices = ["fr", "en", "es", "it", "ru"] questions = [ @@ -291,7 +291,7 @@ def test_parse_args_in_yunohost_format_string_with_choice_ask(): assert choice in prompt.call_args[0][0] -def test_parse_args_in_yunohost_format_string_with_choice_default(): +def test_question_string_with_choice_default(): questions = [ { "name": "some_string", @@ -305,7 +305,7 @@ def test_parse_args_in_yunohost_format_string_with_choice_default(): assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_password(): +def test_question_password(): questions = [ { "name": "some_password", @@ -317,7 +317,7 @@ def test_parse_args_in_yunohost_format_password(): assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_password_no_input(): +def test_question_password_no_input(): questions = [ { "name": "some_password", @@ -330,7 +330,7 @@ def test_parse_args_in_yunohost_format_password_no_input(): parse_args_in_yunohost_format(answers, questions) -def test_parse_args_in_yunohost_format_password_input(): +def test_question_password_input(): questions = [ { "name": "some_password", @@ -345,7 +345,7 @@ def test_parse_args_in_yunohost_format_password_input(): assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_password_input_no_ask(): +def test_question_password_input_no_ask(): questions = [ { "name": "some_password", @@ -359,7 +359,7 @@ def test_parse_args_in_yunohost_format_password_input_no_ask(): assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_password_no_input_optional(): +def test_question_password_no_input_optional(): questions = [ { "name": "some_password", @@ -379,7 +379,7 @@ def test_parse_args_in_yunohost_format_password_no_input_optional(): assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_password_optional_with_input(): +def test_question_password_optional_with_input(): questions = [ { "name": "some_password", @@ -395,7 +395,7 @@ def test_parse_args_in_yunohost_format_password_optional_with_input(): assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_password_optional_with_empty_input(): +def test_question_password_optional_with_empty_input(): questions = [ { "name": "some_password", @@ -411,7 +411,7 @@ def test_parse_args_in_yunohost_format_password_optional_with_empty_input(): assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_password_optional_with_input_without_ask(): +def test_question_password_optional_with_input_without_ask(): questions = [ { "name": "some_password", @@ -426,7 +426,7 @@ def test_parse_args_in_yunohost_format_password_optional_with_input_without_ask( assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_password_no_input_default(): +def test_question_password_no_input_default(): questions = [ { "name": "some_password", @@ -443,7 +443,7 @@ def test_parse_args_in_yunohost_format_password_no_input_default(): @pytest.mark.skip # this should raises -def test_parse_args_in_yunohost_format_password_no_input_example(): +def test_question_password_no_input_example(): questions = [ { "name": "some_password", @@ -459,7 +459,7 @@ def test_parse_args_in_yunohost_format_password_no_input_example(): parse_args_in_yunohost_format(answers, questions) -def test_parse_args_in_yunohost_format_password_input_test_ask(): +def test_question_password_input_test_ask(): ask_text = "some question" questions = [ { @@ -478,7 +478,7 @@ def test_parse_args_in_yunohost_format_password_input_test_ask(): @pytest.mark.skip # we should do something with this example -def test_parse_args_in_yunohost_format_password_input_test_ask_with_example(): +def test_question_password_input_test_ask_with_example(): ask_text = "some question" example_text = "some example" questions = [ @@ -500,7 +500,7 @@ def test_parse_args_in_yunohost_format_password_input_test_ask_with_example(): @pytest.mark.skip # we should do something with this help -def test_parse_args_in_yunohost_format_password_input_test_ask_with_help(): +def test_question_password_input_test_ask_with_help(): ask_text = "some question" help_text = "some_help" questions = [ @@ -521,7 +521,7 @@ def test_parse_args_in_yunohost_format_password_input_test_ask_with_help(): assert help_text in prompt.call_args[0][0] -def test_parse_args_in_yunohost_format_password_bad_chars(): +def test_question_password_bad_chars(): questions = [ { "name": "some_password", @@ -536,7 +536,7 @@ def test_parse_args_in_yunohost_format_password_bad_chars(): parse_args_in_yunohost_format({"some_password": i * 8}, questions) -def test_parse_args_in_yunohost_format_password_strong_enough(): +def test_question_password_strong_enough(): questions = [ { "name": "some_password", @@ -554,7 +554,7 @@ def test_parse_args_in_yunohost_format_password_strong_enough(): parse_args_in_yunohost_format({"some_password": "password"}, questions) -def test_parse_args_in_yunohost_format_password_optional_strong_enough(): +def test_question_password_optional_strong_enough(): questions = [ { "name": "some_password", @@ -572,7 +572,7 @@ def test_parse_args_in_yunohost_format_password_optional_strong_enough(): parse_args_in_yunohost_format({"some_password": "password"}, questions) -def test_parse_args_in_yunohost_format_path(): +def test_question_path(): questions = [ { "name": "some_path", @@ -584,7 +584,7 @@ def test_parse_args_in_yunohost_format_path(): assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_path_no_input(): +def test_question_path_no_input(): questions = [ { "name": "some_path", @@ -597,7 +597,7 @@ def test_parse_args_in_yunohost_format_path_no_input(): parse_args_in_yunohost_format(answers, questions) -def test_parse_args_in_yunohost_format_path_input(): +def test_question_path_input(): questions = [ { "name": "some_path", @@ -612,7 +612,7 @@ def test_parse_args_in_yunohost_format_path_input(): assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_path_input_no_ask(): +def test_question_path_input_no_ask(): questions = [ { "name": "some_path", @@ -626,7 +626,7 @@ def test_parse_args_in_yunohost_format_path_input_no_ask(): assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_path_no_input_optional(): +def test_question_path_no_input_optional(): questions = [ { "name": "some_path", @@ -639,7 +639,7 @@ def test_parse_args_in_yunohost_format_path_no_input_optional(): assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_path_optional_with_input(): +def test_question_path_optional_with_input(): questions = [ { "name": "some_path", @@ -655,7 +655,7 @@ def test_parse_args_in_yunohost_format_path_optional_with_input(): assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_path_optional_with_empty_input(): +def test_question_path_optional_with_empty_input(): questions = [ { "name": "some_path", @@ -671,7 +671,7 @@ def test_parse_args_in_yunohost_format_path_optional_with_empty_input(): assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_path_optional_with_input_without_ask(): +def test_question_path_optional_with_input_without_ask(): questions = [ { "name": "some_path", @@ -686,7 +686,7 @@ def test_parse_args_in_yunohost_format_path_optional_with_input_without_ask(): assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_path_no_input_default(): +def test_question_path_no_input_default(): questions = [ { "name": "some_path", @@ -700,7 +700,7 @@ def test_parse_args_in_yunohost_format_path_no_input_default(): assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_path_input_test_ask(): +def test_question_path_input_test_ask(): ask_text = "some question" questions = [ { @@ -718,7 +718,7 @@ def test_parse_args_in_yunohost_format_path_input_test_ask(): prompt.assert_called_with(ask_text, False) -def test_parse_args_in_yunohost_format_path_input_test_ask_with_default(): +def test_question_path_input_test_ask_with_default(): ask_text = "some question" default_text = "some example" questions = [ @@ -739,7 +739,7 @@ def test_parse_args_in_yunohost_format_path_input_test_ask_with_default(): @pytest.mark.skip # we should do something with this example -def test_parse_args_in_yunohost_format_path_input_test_ask_with_example(): +def test_question_path_input_test_ask_with_example(): ask_text = "some question" example_text = "some example" questions = [ @@ -761,7 +761,7 @@ def test_parse_args_in_yunohost_format_path_input_test_ask_with_example(): @pytest.mark.skip # we should do something with this help -def test_parse_args_in_yunohost_format_path_input_test_ask_with_help(): +def test_question_path_input_test_ask_with_help(): ask_text = "some question" help_text = "some_help" questions = [ @@ -782,7 +782,7 @@ def test_parse_args_in_yunohost_format_path_input_test_ask_with_help(): assert help_text in prompt.call_args[0][0] -def test_parse_args_in_yunohost_format_boolean(): +def test_question_boolean(): questions = [ { "name": "some_boolean", @@ -794,7 +794,7 @@ def test_parse_args_in_yunohost_format_boolean(): assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_boolean_all_yes(): +def test_question_boolean_all_yes(): questions = [ { "name": "some_boolean", @@ -848,7 +848,7 @@ def test_parse_args_in_yunohost_format_boolean_all_yes(): ) -def test_parse_args_in_yunohost_format_boolean_all_no(): +def test_question_boolean_all_no(): questions = [ { "name": "some_boolean", @@ -903,7 +903,7 @@ def test_parse_args_in_yunohost_format_boolean_all_no(): # XXX apparently boolean are always False (0) by default, I'm not sure what to think about that -def test_parse_args_in_yunohost_format_boolean_no_input(): +def test_question_boolean_no_input(): questions = [ { "name": "some_boolean", @@ -916,7 +916,7 @@ def test_parse_args_in_yunohost_format_boolean_no_input(): assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_boolean_bad_input(): +def test_question_boolean_bad_input(): questions = [ { "name": "some_boolean", @@ -929,7 +929,7 @@ def test_parse_args_in_yunohost_format_boolean_bad_input(): parse_args_in_yunohost_format(answers, questions) -def test_parse_args_in_yunohost_format_boolean_input(): +def test_question_boolean_input(): questions = [ { "name": "some_boolean", @@ -948,7 +948,7 @@ def test_parse_args_in_yunohost_format_boolean_input(): assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_boolean_input_no_ask(): +def test_question_boolean_input_no_ask(): questions = [ { "name": "some_boolean", @@ -962,7 +962,7 @@ def test_parse_args_in_yunohost_format_boolean_input_no_ask(): assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_boolean_no_input_optional(): +def test_question_boolean_no_input_optional(): questions = [ { "name": "some_boolean", @@ -975,7 +975,7 @@ def test_parse_args_in_yunohost_format_boolean_no_input_optional(): assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_boolean_optional_with_input(): +def test_question_boolean_optional_with_input(): questions = [ { "name": "some_boolean", @@ -991,7 +991,7 @@ def test_parse_args_in_yunohost_format_boolean_optional_with_input(): assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_boolean_optional_with_empty_input(): +def test_question_boolean_optional_with_empty_input(): questions = [ { "name": "some_boolean", @@ -1007,7 +1007,7 @@ def test_parse_args_in_yunohost_format_boolean_optional_with_empty_input(): assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_boolean_optional_with_input_without_ask(): +def test_question_boolean_optional_with_input_without_ask(): questions = [ { "name": "some_boolean", @@ -1022,7 +1022,7 @@ def test_parse_args_in_yunohost_format_boolean_optional_with_input_without_ask() assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_boolean_no_input_default(): +def test_question_boolean_no_input_default(): questions = [ { "name": "some_boolean", @@ -1036,7 +1036,7 @@ def test_parse_args_in_yunohost_format_boolean_no_input_default(): assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_boolean_bad_default(): +def test_question_boolean_bad_default(): questions = [ { "name": "some_boolean", @@ -1050,7 +1050,7 @@ def test_parse_args_in_yunohost_format_boolean_bad_default(): parse_args_in_yunohost_format(answers, questions) -def test_parse_args_in_yunohost_format_boolean_input_test_ask(): +def test_question_boolean_input_test_ask(): ask_text = "some question" questions = [ { @@ -1066,7 +1066,7 @@ def test_parse_args_in_yunohost_format_boolean_input_test_ask(): prompt.assert_called_with(ask_text + " [yes | no] (default: no)", False) -def test_parse_args_in_yunohost_format_boolean_input_test_ask_with_default(): +def test_question_boolean_input_test_ask_with_default(): ask_text = "some question" default_text = 1 questions = [ @@ -1084,7 +1084,7 @@ def test_parse_args_in_yunohost_format_boolean_input_test_ask_with_default(): prompt.assert_called_with("%s [yes | no] (default: yes)" % ask_text, False) -def test_parse_args_in_yunohost_format_domain_empty(): +def test_question_domain_empty(): questions = [ { "name": "some_domain", @@ -1101,7 +1101,7 @@ def test_parse_args_in_yunohost_format_domain_empty(): assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_domain(): +def test_question_domain(): main_domain = "my_main_domain.com" domains = [main_domain] questions = [ @@ -1120,7 +1120,7 @@ def test_parse_args_in_yunohost_format_domain(): assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_domain_two_domains(): +def test_question_domain_two_domains(): main_domain = "my_main_domain.com" other_domain = "some_other_domain.tld" domains = [main_domain, other_domain] @@ -1148,7 +1148,7 @@ def test_parse_args_in_yunohost_format_domain_two_domains(): assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_domain_two_domains_wrong_answer(): +def test_question_domain_two_domains_wrong_answer(): main_domain = "my_main_domain.com" other_domain = "some_other_domain.tld" domains = [main_domain, other_domain] @@ -1168,7 +1168,7 @@ def test_parse_args_in_yunohost_format_domain_two_domains_wrong_answer(): parse_args_in_yunohost_format(answers, questions) -def test_parse_args_in_yunohost_format_domain_two_domains_default_no_ask(): +def test_question_domain_two_domains_default_no_ask(): main_domain = "my_main_domain.com" other_domain = "some_other_domain.tld" domains = [main_domain, other_domain] @@ -1188,7 +1188,7 @@ def test_parse_args_in_yunohost_format_domain_two_domains_default_no_ask(): assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_domain_two_domains_default(): +def test_question_domain_two_domains_default(): main_domain = "my_main_domain.com" other_domain = "some_other_domain.tld" domains = [main_domain, other_domain] @@ -1203,7 +1203,7 @@ def test_parse_args_in_yunohost_format_domain_two_domains_default(): assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_domain_two_domains_default_input(): +def test_question_domain_two_domains_default_input(): main_domain = "my_main_domain.com" other_domain = "some_other_domain.tld" domains = [main_domain, other_domain] @@ -1223,7 +1223,7 @@ def test_parse_args_in_yunohost_format_domain_two_domains_default_input(): assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_user_empty(): +def test_question_user_empty(): users = { "some_user": { "ssh_allowed": False, @@ -1247,7 +1247,7 @@ def test_parse_args_in_yunohost_format_user_empty(): parse_args_in_yunohost_format(answers, questions) -def test_parse_args_in_yunohost_format_user(): +def test_question_user(): username = "some_user" users = { username: { @@ -1274,7 +1274,7 @@ def test_parse_args_in_yunohost_format_user(): assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_user_two_users(): +def test_question_user_two_users(): username = "some_user" other_user = "some_other_user" users = { @@ -1315,7 +1315,7 @@ def test_parse_args_in_yunohost_format_user_two_users(): assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_user_two_users_wrong_answer(): +def test_question_user_two_users_wrong_answer(): username = "my_username.com" other_user = "some_other_user" users = { @@ -1348,7 +1348,7 @@ def test_parse_args_in_yunohost_format_user_two_users_wrong_answer(): parse_args_in_yunohost_format(answers, questions) -def test_parse_args_in_yunohost_format_user_two_users_no_default(): +def test_question_user_two_users_no_default(): username = "my_username.com" other_user = "some_other_user.tld" users = { @@ -1376,7 +1376,7 @@ def test_parse_args_in_yunohost_format_user_two_users_no_default(): parse_args_in_yunohost_format(answers, questions) -def test_parse_args_in_yunohost_format_user_two_users_default_input(): +def test_question_user_two_users_default_input(): username = "my_username.com" other_user = "some_other_user.tld" users = { @@ -1416,7 +1416,7 @@ def test_parse_args_in_yunohost_format_user_two_users_default_input(): ) -def test_parse_args_in_yunohost_format_number(): +def test_question_number(): questions = [ { "name": "some_number", @@ -1428,7 +1428,7 @@ def test_parse_args_in_yunohost_format_number(): assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_number_no_input(): +def test_question_number_no_input(): questions = [ { "name": "some_number", @@ -1441,7 +1441,7 @@ def test_parse_args_in_yunohost_format_number_no_input(): assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_number_bad_input(): +def test_question_number_bad_input(): questions = [ { "name": "some_number", @@ -1458,7 +1458,7 @@ def test_parse_args_in_yunohost_format_number_bad_input(): parse_args_in_yunohost_format(answers, questions) -def test_parse_args_in_yunohost_format_number_input(): +def test_question_number_input(): questions = [ { "name": "some_number", @@ -1480,7 +1480,7 @@ def test_parse_args_in_yunohost_format_number_input(): assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_number_input_no_ask(): +def test_question_number_input_no_ask(): questions = [ { "name": "some_number", @@ -1494,7 +1494,7 @@ def test_parse_args_in_yunohost_format_number_input_no_ask(): assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_number_no_input_optional(): +def test_question_number_no_input_optional(): questions = [ { "name": "some_number", @@ -1507,7 +1507,7 @@ def test_parse_args_in_yunohost_format_number_no_input_optional(): assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_number_optional_with_input(): +def test_question_number_optional_with_input(): questions = [ { "name": "some_number", @@ -1523,7 +1523,7 @@ def test_parse_args_in_yunohost_format_number_optional_with_input(): assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_number_optional_with_input_without_ask(): +def test_question_number_optional_with_input_without_ask(): questions = [ { "name": "some_number", @@ -1538,7 +1538,7 @@ def test_parse_args_in_yunohost_format_number_optional_with_input_without_ask(): assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_number_no_input_default(): +def test_question_number_no_input_default(): questions = [ { "name": "some_number", @@ -1552,7 +1552,7 @@ def test_parse_args_in_yunohost_format_number_no_input_default(): assert parse_args_in_yunohost_format(answers, questions) == expected_result -def test_parse_args_in_yunohost_format_number_bad_default(): +def test_question_number_bad_default(): questions = [ { "name": "some_number", @@ -1566,7 +1566,7 @@ def test_parse_args_in_yunohost_format_number_bad_default(): parse_args_in_yunohost_format(answers, questions) -def test_parse_args_in_yunohost_format_number_input_test_ask(): +def test_question_number_input_test_ask(): ask_text = "some question" questions = [ { @@ -1582,7 +1582,7 @@ def test_parse_args_in_yunohost_format_number_input_test_ask(): prompt.assert_called_with("%s (default: 0)" % (ask_text), False) -def test_parse_args_in_yunohost_format_number_input_test_ask_with_default(): +def test_question_number_input_test_ask_with_default(): ask_text = "some question" default_value = 1337 questions = [ @@ -1601,7 +1601,7 @@ def test_parse_args_in_yunohost_format_number_input_test_ask_with_default(): @pytest.mark.skip # we should do something with this example -def test_parse_args_in_yunohost_format_number_input_test_ask_with_example(): +def test_question_number_input_test_ask_with_example(): ask_text = "some question" example_value = 1337 questions = [ @@ -1621,7 +1621,7 @@ def test_parse_args_in_yunohost_format_number_input_test_ask_with_example(): @pytest.mark.skip # we should do something with this help -def test_parse_args_in_yunohost_format_number_input_test_ask_with_help(): +def test_question_number_input_test_ask_with_help(): ask_text = "some question" help_value = 1337 questions = [ @@ -1640,7 +1640,7 @@ def test_parse_args_in_yunohost_format_number_input_test_ask_with_help(): assert help_value in prompt.call_args[0][0] -def test_parse_args_in_yunohost_format_display_text(): +def test_question_display_text(): questions = [{"name": "some_app", "type": "display_text", "ask": "foobar"}] answers = {} diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 26e6e3ebb..b6149ad96 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -110,7 +110,7 @@ class ConfigPanel: if (args is not None or args_file is not None) and value is not None: raise YunohostError("config_args_value") - if self.filter_key.count(".") != 2 and not value is None: + if self.filter_key.count(".") != 2 and value is not None: raise YunohostError("config_set_value_on_section") # Import and parse pre-answered options From afcf125d12dd3c0b933b1262124de8b7a8eb1fd0 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 5 Sep 2021 17:39:39 +0200 Subject: [PATCH 2929/3170] i18n fix --- 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 2cebc5a0a..6e3b2d7a6 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -288,7 +288,7 @@ def service_reload_or_restart(names, test_conf=True): if p.returncode != 0: errors = out.decode().strip().split("\n") logger.error( - m18n.n("service_not_reloading_because_conf_broken", errors=errors) + m18n.n("service_not_reloading_because_conf_broken", name=name, errors=errors) ) continue From da44224794652e4077d660c160f0cf624e398923 Mon Sep 17 00:00:00 2001 From: ljf Date: Sun, 5 Sep 2021 17:46:06 +0200 Subject: [PATCH 2930/3170] [enh] Add warning if bad keys in toml format --- src/yunohost/utils/config.py | 62 +++++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 25 deletions(-) diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index a50e460bb..9e694596c 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -166,7 +166,7 @@ class ConfigPanel: def _get_config_panel(self): # Split filter_key - filter_key = self.filter_key.split(".") + filter_key = self.filter_key.split(".") if self.filter_key != "" else [] if len(filter_key) > 3: raise YunohostError("config_too_many_sub_keys", key=self.filter_key) @@ -181,21 +181,34 @@ class ConfigPanel: ) # Transform toml format into internal format - defaults = { - "toml": {"version": 1.0}, + format_description = { + "toml": { + "properties": ["version", "i18n"], + "default": {"version": 1.0}, + }, "panels": { - "name": "", - "services": [], - "actions": {"apply": {"en": "Apply"}}, - }, # help + "properties": ["name", "services", "actions", "help"], + "default": { + "name": "", + "services": [], + "actions": {"apply": {"en": "Apply"}} + }, + }, "sections": { - "name": "", - "services": [], - "optional": True, - }, # visibleIf help - "options": {} - # ask type source help helpLink example style icon placeholder visibleIf - # optional choices pattern limit min max step accept redact + "properties": ["name", "services", "optional", "help", "visible"], + "default": { + "name": "", + "services": [], + "optional": True, + } + }, + "options": { + "properties": ["ask", "type", "source", "help", "example", + "style", "icon", "placeholder", "visible", + "optional", "choices", "yes", "no", "pattern", + "limit", "min", "max", "step", "accept", "redact"], + "default": {} + } } # @@ -214,19 +227,21 @@ class ConfigPanel: This function detects all children nodes and put them in a list """ # Prefill the node default keys if needed - default = defaults[node_type] + default = format_description[node_type]['default'] node = {key: toml_node.get(key, value) for key, value in default.items()} + properties = format_description[node_type]['properties'] + # Define the filter_key part to use and the children type - i = list(defaults).index(node_type) - search_key = filter_key.get(i) - subnode_type = list(defaults)[i + 1] if node_type != "options" else None + i = list(format_description).index(node_type) + subnode_type = list(format_description)[i + 1] if node_type != "options" else None + search_key = filter_key[i] if len(filter_key) > i else False for key, value in toml_node.items(): # Key/value are a child node if ( isinstance(value, OrderedDict) - and key not in default + and key not in properties and subnode_type ): # We exclude all nodes not referenced by the filter_key @@ -240,6 +255,8 @@ class ConfigPanel: node.setdefault(subnode_type, []).append(subnode) # Key/value are a property else: + if key not in properties: + logger.warning(f"Unknown key '{key}' found in config toml") # Todo search all i18n keys node[key] = ( value if key not in ["ask", "help", "name"] else {"en": value} @@ -378,7 +395,6 @@ class Question(object): self.pattern = question.get("pattern") self.ask = question.get("ask", {"en": self.name}) self.help = question.get("help") - self.helpLink = question.get("helpLink") self.value = user_answers.get(self.name) self.redact = question.get("redact", False) @@ -471,15 +487,11 @@ class Question(object): if self.choices: text_for_user_input_in_cli += " [{0}]".format(" | ".join(self.choices)) - if self.help or self.helpLink: + if self.help: text_for_user_input_in_cli += ":\033[m" if self.help: text_for_user_input_in_cli += "\n - " text_for_user_input_in_cli += _value_for_locale(self.help) - if self.helpLink: - if not isinstance(self.helpLink, dict): - self.helpLink = {"href": self.helpLink} - text_for_user_input_in_cli += f"\n - See {self.helpLink['href']}" return text_for_user_input_in_cli def _post_parse_value(self): From ba8b1e5b2e1351abf54f04a55ed1d0b2e6db8df1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 5 Sep 2021 18:50:28 +0200 Subject: [PATCH 2931/3170] Typo --- 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 f3d891081..c6aa8df59 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -667,7 +667,7 @@ def app_upgrade(app=[], url=None, file=None, force=False, no_safety_backup=False error = m18n.n("unexpected_error", error="\n" + traceback.format_exc()) logger.error( - m18n.n("app_install_failed", app=app_instance_name, error=error) + m18n.n("app_upgrade_failed", app=app_instance_name, error=error) ) failure_message_with_debug_instructions = operation_logger.error(error) finally: From 62eecb28db3abc9d29541a8fd8d72dcecf227e2f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 5 Sep 2021 18:53:39 +0200 Subject: [PATCH 2932/3170] --mode full/export -> --full / --export --- data/actionsmap/yunohost.yml | 16 ++++++++-------- src/yunohost/app.py | 12 +++++++++++- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index f694d4361..cbb580029 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -863,14 +863,14 @@ app: key: help: A specific panel, section or a question identifier nargs: '?' - -m: - full: --mode - help: Display mode to use - choices: - - classic - - full - - export - default: classic + -f: + full: --full + help: Display all details (meant to be used by the API) + action: store_true + -e: + full: --export + help: Only export key/values, meant to be reimported using "config set --args-file" + action: store_true ### app_config_set() set: diff --git a/src/yunohost/app.py b/src/yunohost/app.py index d97c2824c..45386c129 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1751,10 +1751,20 @@ def app_action_run(operation_logger, app, action, args=None): return logger.success("Action successed!") -def app_config_get(app, key="", mode="classic"): +def app_config_get(app, key="", full=False, export=False): """ Display an app configuration in classic, full or export mode """ + if full and export: + raise YunohostValidationError("You can't use --full and --export together.", raw_msg=True) + + if full: + mode = "full" + elif export: + mode = "export" + else: + mode = "classic" + config_ = AppConfigPanel(app) return config_.get(key, mode) From ee55b9bf42055627880949ed656a9597cc15dc72 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 5 Sep 2021 19:15:52 +0200 Subject: [PATCH 2933/3170] config helpers: Semantics / comments on the validation-apply workflow --- data/helpers.d/config | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/data/helpers.d/config b/data/helpers.d/config index 6223a17b2..3a2c55444 100644 --- a/data/helpers.d/config +++ b/data/helpers.d/config @@ -160,7 +160,8 @@ _ynh_app_config_show() { _ynh_app_config_validate() { # Change detection ynh_script_progression --message="Checking what changed in the new configuration..." --weight=1 - local is_error=true + local nothing_changed=true + local changes_validated=true #for changed_status in "${!changed[@]}" for short_setting in "${!old[@]}" do @@ -178,7 +179,7 @@ _ynh_app_config_validate() { file_hash[old__$short_setting]=$(sha256sum "${old[$short_setting]}" | cut -d' ' -f1) if [ -z "${!short_setting}" ] ; then changed[$short_setting]=true - is_error=false + nothing_changed=false fi fi if [ -f "${!short_setting}" ] ; then @@ -186,18 +187,18 @@ _ynh_app_config_validate() { if [[ "${file_hash[old__$short_setting]}" != "${file_hash[new__$short_setting]}" ]] then changed[$short_setting]=true - is_error=false + nothing_changed=false fi fi else if [[ "${!short_setting}" != "${old[$short_setting]}" ]] then changed[$short_setting]=true - is_error=false + nothing_changed=false fi fi done - if [[ "$is_error" == "true" ]] + if [[ "$nothing_changed" == "true" ]] then ynh_print_info "Nothing has changed" exit 0 @@ -217,11 +218,13 @@ _ynh_app_config_validate() { then local key="YNH_ERROR_${short_setting}" ynh_return "$key: \"$result\"" - is_error=true + changes_validated=false fi done - - if [[ "$is_error" == "true" ]] + + # If validation failed, exit the script right now (instead of going into apply) + # Yunohost core will pick up the errors returned via ynh_return previously + if [[ "$changes_validated" == "false" ]] then exit 0 fi From c5de8035312773b3b291abddf8ef2ad8966e9ac3 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 5 Sep 2021 19:17:17 +0200 Subject: [PATCH 2934/3170] config panels: try to improve the log and error handling: separate ask vs. actual apply --- src/yunohost/app.py | 6 ++---- src/yunohost/utils/config.py | 16 ++++++---------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 45386c129..9c40ef18d 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1780,11 +1780,9 @@ def app_config_set( config_ = AppConfigPanel(app) Question.operation_logger = operation_logger - operation_logger.start() - result = config_.set(key, value, args, args_file) - if "errors" not in result: - operation_logger.success() + result = config_.set(key, value, args, args_file, operation_logger=operation_logger) + return result diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 4f69729f7..6432856b0 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -98,7 +98,7 @@ class ConfigPanel: return result - def set(self, key=None, value=None, args=None, args_file=None): + def set(self, key=None, value=None, args=None, args_file=None, operation_logger=None): self.filter_key = key or "" # Read config panel toml @@ -128,11 +128,13 @@ class ConfigPanel: # Read or get values and hydrate the config self._load_current_values() self._hydrate() + self._ask() + + if operation_logger: + operation_logger.start() try: - self._ask() self._apply() - # Script got manually interrupted ... # N.B. : KeyboardInterrupt does not inherit from Exception except (KeyboardInterrupt, EOFError): @@ -158,6 +160,7 @@ class ConfigPanel: self._reload_services() logger.success("Config updated as expected") + operation_logger.success() return {} def _get_toml(self): @@ -211,13 +214,6 @@ class ConfigPanel: } } - # - # FIXME : this is hella confusing ... - # from what I understand, the purpose is to have some sort of "deep_update" - # to apply the defaults onto the loaded toml ... - # in that case we probably want to get inspiration from - # https://stackoverflow.com/questions/3232943/update-value-of-a-nested-dictionary-of-varying-depth - # def convert(toml_node, node_type): """Convert TOML in internal format ('full' mode used by webadmin) Here are some properties of 1.0 config panel in toml: From 6f7485bf3eb259152977960a2fa3db98a50b3d09 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 5 Sep 2021 19:31:07 +0200 Subject: [PATCH 2935/3170] config tests: Add a basic tests for app config panel --- .gitlab/ci/test.gitlab-ci.yml | 10 ++ src/yunohost/tests/test_app_config.py | 144 ++++++++++++++++++++++++++ 2 files changed, 154 insertions(+) create mode 100644 src/yunohost/tests/test_app_config.py diff --git a/.gitlab/ci/test.gitlab-ci.yml b/.gitlab/ci/test.gitlab-ci.yml index 0b35d3447..2dc45171b 100644 --- a/.gitlab/ci/test.gitlab-ci.yml +++ b/.gitlab/ci/test.gitlab-ci.yml @@ -121,6 +121,16 @@ test-questions: changes: - src/yunohost/utils/config.py +test-app-config: + extends: .test-stage + script: + - cd src/yunohost + - python3 -m pytest tests/test_app_config.py + only: + changes: + - src/yunohost/app.py + - src/yunohost/utils/config.py + test-changeurl: extends: .test-stage script: diff --git a/src/yunohost/tests/test_app_config.py b/src/yunohost/tests/test_app_config.py new file mode 100644 index 000000000..d8dc5849f --- /dev/null +++ b/src/yunohost/tests/test_app_config.py @@ -0,0 +1,144 @@ +import glob +import os +import shutil +import pytest + +from .conftest import get_test_apps_dir + +from yunohost.domain import _get_maindomain +from yunohost.app import ( + app_install, + app_remove, + _is_installed, + app_config_get, + app_config_set, + app_ssowatconf, +) + +from yunohost.utils.errors import YunohostValidationError + + +def setup_function(function): + + clean() + + +def teardown_function(function): + + clean() + + +def clean(): + + # Make sure we have a ssowat + os.system("mkdir -p /etc/ssowat/") + app_ssowatconf() + + test_apps = ["config_app", "legacy_app"] + + for test_app in test_apps: + + if _is_installed(test_app): + app_remove(test_app) + + for filepath in glob.glob("/etc/nginx/conf.d/*.d/*%s*" % test_app): + os.remove(filepath) + for folderpath in glob.glob("/etc/yunohost/apps/*%s*" % test_app): + shutil.rmtree(folderpath, ignore_errors=True) + for folderpath in glob.glob("/var/www/*%s*" % test_app): + shutil.rmtree(folderpath, ignore_errors=True) + + os.system("bash -c \"mysql -B 2>/dev/null <<< 'DROP DATABASE %s' \"" % test_app) + os.system( + "bash -c \"mysql -B 2>/dev/null <<< 'DROP USER %s@localhost'\"" % test_app + ) + + # Reset failed quota for service to avoid running into start-limit rate ? + os.system("systemctl reset-failed nginx") + os.system("systemctl start nginx") + + +@pytest.fixture(scope="module") +def legacy_app(request): + + main_domain = _get_maindomain() + + app_install( + os.path.join(get_test_apps_dir(), "legacy_app_ynh"), + args="domain=%s&path=%s&is_public=%s" % (main_domain, "/", 1), + force=True, + ) + + def remove_app(): + app_remove("legacy_app") + + request.addfinalizer(remove_app) + + return "legacy_app" + + + +@pytest.fixture(scope="module") +def config_app(request): + + app_install( + os.path.join(get_test_apps_dir(), "config_app_ynh"), + args="", + force=True, + ) + + def remove_app(): + app_remove("config_app") + + request.addfinalizer(remove_app) + + return "config_app" + + +def test_app_config_get(config_app): + + assert isinstance(app_config_get(config_app), dict) + assert isinstance(app_config_get(config_app, full=True), dict) + assert isinstance(app_config_get(config_app, export=True), dict) + assert isinstance(app_config_get(config_app, "main"), dict) + assert isinstance(app_config_get(config_app, "main.components"), dict) + # Is it expected that we return None if no value defined yet ? + # c.f. the whole discussion about "should we have defaults" etc. + assert app_config_get(config_app, "main.components.boolean") is None + + +def test_app_config_nopanel(legacy_app): + + with pytest.raises(YunohostValidationError): + app_config_get(legacy_app) + + +def test_app_config_get_nonexistentstuff(config_app): + + with pytest.raises(YunohostValidationError): + app_config_get("nonexistent") + + with pytest.raises(YunohostValidationError): + app_config_get(config_app, "nonexistent") + + with pytest.raises(YunohostValidationError): + app_config_get(config_app, "main.nonexistent") + + with pytest.raises(YunohostValidationError): + app_config_get(config_app, "main.components.nonexistent") + + +def test_app_config_set_boolean(config_app): + + assert app_config_get(config_app, "main.components.boolean") is None + + app_config_set(config_app, "main.components.boolean", "no") + + assert app_config_get(config_app, "main.components.boolean") == "0" + + app_config_set(config_app, "main.components.boolean", "yes") + + assert app_config_get(config_app, "main.components.boolean") == "1" + + with pytest.raises(YunohostValidationError): + app_config_set(config_app, "main.components.boolean", "pwet") From 56f525cf80b32f5c397398065267cbd27bd61a19 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 5 Sep 2021 19:42:46 +0200 Subject: [PATCH 2936/3170] Typo --- src/yunohost/tests/test_app_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/tests/test_app_config.py b/src/yunohost/tests/test_app_config.py index d8dc5849f..003d9f3b2 100644 --- a/src/yunohost/tests/test_app_config.py +++ b/src/yunohost/tests/test_app_config.py @@ -15,7 +15,7 @@ from yunohost.app import ( app_ssowatconf, ) -from yunohost.utils.errors import YunohostValidationError +from yunohost.utils.error import YunohostValidationError def setup_function(function): From 3936589d3bd4b866a5213e047e922b1f030209b0 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 5 Sep 2021 20:24:36 +0200 Subject: [PATCH 2937/3170] Yolofactorize install/upgrade/restore error handling into a smart 'hook_exec_with_script_debug_if_failure' --- src/yunohost/app.py | 118 ++++++----------------------------------- src/yunohost/backup.py | 33 ++---------- src/yunohost/hook.py | 34 ++++++++++++ src/yunohost/log.py | 46 ++++++++++++++++ 4 files changed, 101 insertions(+), 130 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 9c40ef18d..e9712edb4 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -511,7 +511,7 @@ def app_upgrade(app=[], url=None, file=None, force=False, no_safety_backup=False """ from packaging import version - from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback + from yunohost.hook import hook_add, hook_remove, hook_callback, hook_exec_with_script_debug_if_failure from yunohost.permission import permission_sync_to_user from yunohost.regenconf import manually_modified_files @@ -633,36 +633,13 @@ def app_upgrade(app=[], url=None, file=None, force=False, no_safety_backup=False # Execute the app upgrade script upgrade_failed = True try: - upgrade_retcode = hook_exec( - extracted_app_folder + "/scripts/upgrade", env=env_dict - )[0] - - upgrade_failed = True if upgrade_retcode != 0 else False - if upgrade_failed: - error = m18n.n("app_upgrade_script_failed") - logger.error( - m18n.n("app_upgrade_failed", app=app_instance_name, error=error) - ) - failure_message_with_debug_instructions = operation_logger.error(error) - if Moulinette.interface.type != "api": - dump_app_log_extract_for_debugging(operation_logger) - # Script got manually interrupted ... N.B. : KeyboardInterrupt does not inherit from Exception - except (KeyboardInterrupt, EOFError): - upgrade_retcode = -1 - error = m18n.n("operation_interrupted") - logger.error( - m18n.n("app_upgrade_failed", app=app_instance_name, error=error) + upgrade_failed, failure_message_with_debug_instructions = hook_exec_with_script_debug_if_failure( + extracted_app_folder + "/scripts/upgrade", + env=env_dict, + operation_logger=operation_logger, + error_message_if_script_failed=m18n.n("app_upgrade_script_failed"), + error_message_if_failed=lambda e: m18n.n("app_upgrade_failed", app=app_instance_name, error=e) ) - failure_message_with_debug_instructions = operation_logger.error(error) - # Something wrong happened in Yunohost's code (most probably hook_exec) - except Exception: - import traceback - - 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: # Whatever happened (install success or failure) we check if it broke the system # and warn the user about it @@ -692,7 +669,7 @@ def app_upgrade(app=[], url=None, file=None, force=False, no_safety_backup=False if upgrade_failed or broke_the_system: # display this if there are remaining apps - if apps[number + 1 :]: + if apps[number + 1:]: not_upgraded_apps = apps[number:] logger.error( m18n.n( @@ -808,7 +785,7 @@ def app_install( force -- Do not ask for confirmation when installing experimental / low-quality apps """ - from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback + from yunohost.hook import hook_add, hook_remove, hook_callback, hook_exec, hook_exec_with_script_debug_if_failure from yunohost.log import OperationLogger from yunohost.permission import ( user_permission_list, @@ -999,29 +976,13 @@ def app_install( # Execute the app install script install_failed = True try: - install_retcode = hook_exec( - os.path.join(extracted_app_folder, "scripts/install"), env=env_dict - )[0] - # "Common" app install failure : the script failed and returned exit code != 0 - install_failed = True if install_retcode != 0 else False - if install_failed: - error = m18n.n("app_install_script_failed") - logger.error(m18n.n("app_install_failed", app=app_id, error=error)) - failure_message_with_debug_instructions = operation_logger.error(error) - if Moulinette.interface.type != "api": - dump_app_log_extract_for_debugging(operation_logger) - # Script got manually interrupted ... N.B. : KeyboardInterrupt does not inherit from Exception - except (KeyboardInterrupt, EOFError): - error = m18n.n("operation_interrupted") - logger.error(m18n.n("app_install_failed", app=app_id, error=error)) - failure_message_with_debug_instructions = operation_logger.error(error) - # Something wrong happened in Yunohost's code (most probably hook_exec) - except Exception: - import traceback - - 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) + install_failed, failure_message_with_debug_instructions = hook_exec_with_script_debug_if_failure( + os.path.join(extracted_app_folder, "scripts/install"), + env=env_dict, + operation_logger=operation_logger, + error_message_if_script_failed=m18n.n("app_install_script_failed"), + error_message_if_failed=lambda e: m18n.n("app_install_failed", app=app_id, error=e) + ) finally: # If success so far, validate that app didn't break important stuff if not install_failed: @@ -1134,53 +1095,6 @@ def app_install( hook_callback("post_app_install", env=env_dict) -def dump_app_log_extract_for_debugging(operation_logger): - - with open(operation_logger.log_path, "r") as f: - lines = f.readlines() - - filters = [ - r"set [+-]x$", - r"set [+-]o xtrace$", - r"local \w+$", - r"local legacy_args=.*$", - r".*Helper used in legacy mode.*", - r"args_array=.*$", - r"local -A args_array$", - r"ynh_handle_getopts_args", - r"ynh_script_progression", - ] - - filters = [re.compile(f_) for f_ in filters] - - lines_to_display = [] - for line in lines: - - if ": " not in line.strip(): - continue - - # A line typically looks like - # 2019-10-19 16:10:27,611: DEBUG - + mysql -u piwigo --password=********** -B piwigo - # And we just want the part starting by "DEBUG - " - line = line.strip().split(": ", 1)[1] - - if any(filter_.search(line) for filter_ in filters): - continue - - lines_to_display.append(line) - - if line.endswith("+ ynh_exit_properly") or " + ynh_die " in line: - break - elif len(lines_to_display) > 20: - lines_to_display.pop(0) - - logger.warning( - "Here's an extract of the logs before the crash. It might help debugging the error:" - ) - for line in lines_to_display: - logger.info(line) - - @is_unit_operation() def app_remove(operation_logger, app, purge=False): """ diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 09b35cb67..c39bf656c 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -48,7 +48,6 @@ from yunohost.app import ( app_info, _is_installed, _make_environment_for_app_script, - dump_app_log_extract_for_debugging, _patch_legacy_helpers, _patch_legacy_php_versions, _patch_legacy_php_versions_in_settings, @@ -60,6 +59,7 @@ from yunohost.hook import ( hook_info, hook_callback, hook_exec, + hook_exec_with_script_debug_if_failure, CUSTOM_HOOK_FOLDER, ) from yunohost.tools import ( @@ -1496,37 +1496,14 @@ class RestoreManager: # Execute the app install script restore_failed = True try: - restore_retcode = hook_exec( + restore_failed, failure_message_with_debug_instructions = hook_exec_with_script_debug_if_failure( restore_script, chdir=app_backup_in_archive, env=env_dict, - )[0] - # "Common" app restore failure : the script failed and returned exit code != 0 - restore_failed = True if restore_retcode != 0 else False - if restore_failed: - error = m18n.n("app_restore_script_failed") - logger.error( - m18n.n("app_restore_failed", app=app_instance_name, error=error) - ) - failure_message_with_debug_instructions = operation_logger.error(error) - if Moulinette.interface.type != "api": - dump_app_log_extract_for_debugging(operation_logger) - # Script got manually interrupted ... N.B. : KeyboardInterrupt does not inherit from Exception - except (KeyboardInterrupt, EOFError): - error = m18n.n("operation_interrupted") - logger.error( - m18n.n("app_restore_failed", app=app_instance_name, error=error) + operation_logger=operation_logger, + error_message_if_script_failed=m18n.n("app_restore_script_failed"), + error_message_if_failed=lambda e: m18n.n("app_restore_failed", app=app_instance_name, error=e) ) - failure_message_with_debug_instructions = operation_logger.error(error) - # Something wrong happened in Yunohost's code (most probably hook_exec) - except Exception: - import traceback - - error = m18n.n("unexpected_error", error="\n" + traceback.format_exc()) - logger.error( - m18n.n("app_restore_failed", app=app_instance_name, error=error) - ) - failure_message_with_debug_instructions = operation_logger.error(error) finally: # Cleaning temporary scripts directory shutil.rmtree(tmp_workdir_for_app, ignore_errors=True) diff --git a/src/yunohost/hook.py b/src/yunohost/hook.py index b589c27ea..c55809fce 100644 --- a/src/yunohost/hook.py +++ b/src/yunohost/hook.py @@ -498,6 +498,40 @@ def _hook_exec_python(path, args, env, loggers): return ret +def hook_exec_with_script_debug_if_failure(*args, **kwargs): + + operation_logger = kwargs.pop("operation_logger") + error_message_if_failed = kwargs.pop("error_message_if_failed") + error_message_if_script_failed = kwargs.pop("error_message_if_script_failed") + + failed = True + failure_message_with_debug_instructions = None + try: + retcode, retpayload = hook_exec(*args, **kwargs) + failed = True if retcode != 0 else False + if failed: + error = error_message_if_script_failed + logger.error(error_message_if_failed(error)) + failure_message_with_debug_instructions = operation_logger.error(error) + if Moulinette.interface.type != "api": + operation_logger.dump_script_log_extract_for_debugging() + # Script got manually interrupted ... + # N.B. : KeyboardInterrupt does not inherit from Exception + except (KeyboardInterrupt, EOFError): + error = m18n.n("operation_interrupted") + logger.error(error_message_if_failed(error)) + failure_message_with_debug_instructions = operation_logger.error(error) + # Something wrong happened in Yunohost's code (most probably hook_exec) + except Exception: + import traceback + + error = m18n.n("unexpected_error", error="\n" + traceback.format_exc()) + logger.error(error_message_if_failed(error)) + failure_message_with_debug_instructions = operation_logger.error(error) + + return failed, failure_message_with_debug_instructions + + def _extract_filename_parts(filename): """Extract hook parts from filename""" if "-" in filename: diff --git a/src/yunohost/log.py b/src/yunohost/log.py index 3f6382af2..edb87af71 100644 --- a/src/yunohost/log.py +++ b/src/yunohost/log.py @@ -707,6 +707,52 @@ class OperationLogger(object): else: self.error(m18n.n("log_operation_unit_unclosed_properly")) + def dump_script_log_extract_for_debugging(self): + + with open(self.log_path, "r") as f: + lines = f.readlines() + + filters = [ + r"set [+-]x$", + r"set [+-]o xtrace$", + r"local \w+$", + r"local legacy_args=.*$", + r".*Helper used in legacy mode.*", + r"args_array=.*$", + r"local -A args_array$", + r"ynh_handle_getopts_args", + r"ynh_script_progression", + ] + + filters = [re.compile(f_) for f_ in filters] + + lines_to_display = [] + for line in lines: + + if ": " not in line.strip(): + continue + + # A line typically looks like + # 2019-10-19 16:10:27,611: DEBUG - + mysql -u piwigo --password=********** -B piwigo + # And we just want the part starting by "DEBUG - " + line = line.strip().split(": ", 1)[1] + + if any(filter_.search(line) for filter_ in filters): + continue + + lines_to_display.append(line) + + if line.endswith("+ ynh_exit_properly") or " + ynh_die " in line: + break + elif len(lines_to_display) > 20: + lines_to_display.pop(0) + + logger.warning( + "Here's an extract of the logs before the crash. It might help debugging the error:" + ) + for line in lines_to_display: + logger.info(line) + def _get_datetime_from_name(name): From 39006dbf134f61c242968aa3d60d52f0dea40322 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 5 Sep 2021 20:55:17 +0200 Subject: [PATCH 2938/3170] tests: dunno what i'm doing but that scope=module is no good --- src/yunohost/tests/test_app_config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/tests/test_app_config.py b/src/yunohost/tests/test_app_config.py index 003d9f3b2..af792c431 100644 --- a/src/yunohost/tests/test_app_config.py +++ b/src/yunohost/tests/test_app_config.py @@ -58,7 +58,7 @@ def clean(): os.system("systemctl start nginx") -@pytest.fixture(scope="module") +@pytest.fixture() def legacy_app(request): main_domain = _get_maindomain() @@ -78,7 +78,7 @@ def legacy_app(request): -@pytest.fixture(scope="module") +@pytest.fixture() def config_app(request): app_install( From 4f0df2bcfe31fbbb2f93a3987c1c59b273adb64f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 5 Sep 2021 22:25:06 +0200 Subject: [PATCH 2939/3170] Define missing i18n keys --- locales/en.json | 8 ++++++-- src/yunohost/utils/config.py | 10 +++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/locales/en.json b/locales/en.json index 2ac7aa42a..f1110df7b 100644 --- a/locales/en.json +++ b/locales/en.json @@ -142,6 +142,10 @@ "certmanager_unable_to_parse_self_CA_name": "Could not parse name of self-signing authority (file: {file})", "certmanager_warning_subdomain_dns_record": "Subdomain '{subdomain}' does not resolve to the same IP address as '{domain}'. Some features will not be available until you fix this and regenerate the certificate.", "config_apply_failed": "Applying the new configuration failed: {error}", + "config_cant_set_value_on_section": "You can't set a single value on an entire config section.", + "config_no_panel": "No config panel found.", + "config_unknown_filter_key": "The filter key '{filter_key}' is incorrect.", + "config_version_not_supported": "Config panel versions '{version}' are not supported.", "confirm_app_install_danger": "DANGER! This app is known to be still experimental (if not explicitly not working)! You should probably NOT install it unless you know what you are doing. NO SUPPORT will be provided if this app doesn't work or breaks your system... If you are willing to take that risk anyway, type '{answers}'", "confirm_app_install_thirdparty": "DANGER! This app is not part of YunoHost's app catalog. Installing third-party apps may compromise the integrity and security of your system. You should probably NOT install it unless you know what you are doing. NO SUPPORT will be provided if this app doesn't work or breaks your system... If you are willing to take that risk anyway, type '{answers}'", "confirm_app_install_warning": "Warning: This app may work, but is not well-integrated in YunoHost. Some features such as single sign-on and backup/restore might not be available. Install anyway? [{answers}] ", @@ -342,8 +346,8 @@ "global_settings_setting_backup_compress_tar_archives": "When creating new backups, compress the archives (.tar.gz) instead of uncompressed archives (.tar). N.B. : enabling this option means create lighter backup archives, but the initial backup procedure will be significantly longer and heavy on CPU.", "global_settings_setting_pop3_enabled": "Enable the POP3 protocol for the mail server", "global_settings_setting_security_experimental_enabled": "Enable experimental security features (don't enable this if you don't know what you're doing!)", - "global_settings_setting_security_nginx_redirect_to_https": "Redirect HTTP requests to HTTPs by default (DO NOT TURN OFF unless you really know what you're doing!)", "global_settings_setting_security_nginx_compatibility": "Compatibility vs. security tradeoff for the web server NGINX. Affects the ciphers (and other security-related aspects)", + "global_settings_setting_security_nginx_redirect_to_https": "Redirect HTTP requests to HTTPs by default (DO NOT TURN OFF unless you really know what you're doing!)", "global_settings_setting_security_password_admin_strength": "Admin password strength", "global_settings_setting_security_password_user_strength": "User password strength", "global_settings_setting_security_postfix_compatibility": "Compatibility vs. security tradeoff for the Postfix server. Affects the ciphers (and other security-related aspects)", @@ -668,4 +672,4 @@ "yunohost_installing": "Installing YunoHost...", "yunohost_not_installed": "YunoHost is not correctly installed. Please run 'yunohost tools postinstall'", "yunohost_postinstall_end_tip": "The post-install completed! To finalize your setup, please consider:\n - adding a first user through the 'Users' section of the webadmin (or 'yunohost user create ' in command-line);\n - diagnose potential issues through the 'Diagnosis' section of the webadmin (or 'yunohost diagnosis run' in command-line);\n - reading the 'Finalizing your setup' and 'Getting to know YunoHost' parts in the admin documentation: https://yunohost.org/admindoc." -} +} \ No newline at end of file diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 6432856b0..b5b5fa6ed 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -105,13 +105,13 @@ class ConfigPanel: self._get_config_panel() if not self.config: - raise YunohostError("config_no_panel") + raise YunohostValidationError("config_no_panel") if (args is not None or args_file is not None) and value is not None: - raise YunohostError("config_args_value") + raise YunohostValidationError("You should either provide a value, or a serie of args/args_file, but not both at the same time", raw_msg=True) if self.filter_key.count(".") != 2 and value is not None: - raise YunohostError("config_set_value_on_section") + raise YunohostValidationError("config_cant_set_value_on_section") # Import and parse pre-answered options logger.debug("Import and parse pre-answered options") @@ -171,7 +171,7 @@ class ConfigPanel: # Split filter_key filter_key = self.filter_key.split(".") if self.filter_key != "" else [] if len(filter_key) > 3: - raise YunohostError("config_too_many_sub_keys", key=self.filter_key) + raise YunohostError(f"The filter key {filter_key} has too many sub-levels, the max is 3.", raw_msg=True) if not os.path.exists(self.config_path): return None @@ -265,7 +265,7 @@ class ConfigPanel: self.config["panels"][0]["sections"][0]["options"][0] except (KeyError, IndexError): raise YunohostError( - "config_empty_or_bad_filter_key", filter_key=self.filter_key + "config_unknown_filter_key", filter_key=self.filter_key ) return self.config From c55b96b94b57a9b82bb55d3369dddb1c848ed040 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 5 Sep 2021 23:08:48 +0200 Subject: [PATCH 2940/3170] config panel: rename source into bind --- data/helpers.d/config | 85 ++++++++++++++++++------------------ src/yunohost/utils/config.py | 2 +- 2 files changed, 44 insertions(+), 43 deletions(-) diff --git a/data/helpers.d/config b/data/helpers.d/config index 3a2c55444..fe3a488de 100644 --- a/data/helpers.d/config +++ b/data/helpers.d/config @@ -21,15 +21,16 @@ for panel_name, panel in loaded_toml.items(): print(';'.join([ name, param.get('type', 'string'), - param.get('source', 'settings' if param.get('type', 'string') != 'file' else '') + param.get('bind', 'settings' if param.get('type', 'string') != 'file' else '') ])) EOL ` for line in $lines do - IFS=';' read short_setting type source <<< "$line" + # Split line into short_setting, type and bind + IFS=';' read short_setting type bind <<< "$line" local getter="get__${short_setting}" - sources[${short_setting}]="$source" + binds[${short_setting}]="$bind" types[${short_setting}]="$type" file_hash[${short_setting}]="" formats[${short_setting}]="" @@ -38,36 +39,36 @@ EOL old[$short_setting]="$($getter)" formats[${short_setting}]="yaml" - elif [[ "$source" == "" ]] ; then + elif [[ "$bind" == "" ]] ; then old[$short_setting]="YNH_NULL" # Get value from app settings or from another file elif [[ "$type" == "file" ]] ; then - if [[ "$source" == "settings" ]] ; then + if [[ "$bind" == "settings" ]] ; then ynh_die "File '${short_setting}' can't be stored in settings" fi - old[$short_setting]="$(ls $(echo $source | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/) 2> /dev/null || echo YNH_NULL)" + old[$short_setting]="$(ls $(echo $bind | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/) 2> /dev/null || echo YNH_NULL)" file_hash[$short_setting]="true" # Get multiline text from settings or from a full file elif [[ "$type" == "text" ]] ; then - if [[ "$source" == "settings" ]] ; then + if [[ "$bind" == "settings" ]] ; then old[$short_setting]="$(ynh_app_setting_get $app $short_setting)" - elif [[ "$source" == *":"* ]] ; then + elif [[ "$bind" == *":"* ]] ; then ynh_die "For technical reasons, multiline text '${short_setting}' can't be stored automatically in a variable file, you have to create custom getter/setter" else - old[$short_setting]="$(cat $(echo $source | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/) 2> /dev/null || echo YNH_NULL)" + old[$short_setting]="$(cat $(echo $bind | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/) 2> /dev/null || echo YNH_NULL)" fi # Get value from a kind of key/value file else - if [[ "$source" == "settings" ]] ; then - source=":/etc/yunohost/apps/$app/settings.yml" + if [[ "$bind" == "settings" ]] ; then + bind=":/etc/yunohost/apps/$app/settings.yml" fi - local source_key="$(echo "$source" | cut -d: -f1)" - source_key=${source_key:-$short_setting} - local source_file="$(echo "$source" | cut -d: -f2 | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" - old[$short_setting]="$(ynh_read_var_in_file --file="${source_file}" --key="${source_key}")" + local bind_key="$(echo "$bind" | cut -d: -f1)" + bind_key=${bind_key:-$short_setting} + local bind_file="$(echo "$bind" | cut -d: -f2 | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" + old[$short_setting]="$(ynh_read_var_in_file --file="${bind_file}" --key="${bind_key}")" fi done @@ -79,63 +80,63 @@ _ynh_app_config_apply() { for short_setting in "${!old[@]}" do local setter="set__${short_setting}" - local source="${sources[$short_setting]}" + local bind="${binds[$short_setting]}" local type="${types[$short_setting]}" if [ "${changed[$short_setting]}" == "true" ] ; then # Apply setter if exists if type -t $setter 2>/dev/null | grep -q '^function$' 2>/dev/null; then $setter - elif [[ "$source" == "" ]] ; then + elif [[ "$bind" == "" ]] ; then continue # Save in a file elif [[ "$type" == "file" ]] ; then - if [[ "$source" == "settings" ]] ; then + if [[ "$bind" == "settings" ]] ; then ynh_die "File '${short_setting}' can't be stored in settings" fi - local source_file="$(echo "$source" | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" + local bind_file="$(echo "$bind" | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" if [[ "${!short_setting}" == "" ]] ; then - ynh_backup_if_checksum_is_different --file="$source_file" - rm -f "$source_file" - ynh_delete_file_checksum --file="$source_file" --update_only - ynh_print_info "File '$source_file' removed" + ynh_backup_if_checksum_is_different --file="$bind_file" + rm -f "$bind_file" + ynh_delete_file_checksum --file="$bind_file" --update_only + ynh_print_info "File '$bind_file' removed" else - ynh_backup_if_checksum_is_different --file="$source_file" - cp "${!short_setting}" "$source_file" - ynh_store_file_checksum --file="$source_file" --update_only - ynh_print_info "File '$source_file' overwrited with ${!short_setting}" + ynh_backup_if_checksum_is_different --file="$bind_file" + cp "${!short_setting}" "$bind_file" + ynh_store_file_checksum --file="$bind_file" --update_only + ynh_print_info "File '$bind_file' overwrited with ${!short_setting}" fi # Save value in app settings - elif [[ "$source" == "settings" ]] ; then + elif [[ "$bind" == "settings" ]] ; then ynh_app_setting_set $app $short_setting "${!short_setting}" ynh_print_info "Configuration key '$short_setting' edited in app settings" # Save multiline text in a file elif [[ "$type" == "text" ]] ; then - if [[ "$source" == *":"* ]] ; then + if [[ "$bind" == *":"* ]] ; then ynh_die "For technical reasons, multiline text '${short_setting}' can't be stored automatically in a variable file, you have to create custom getter/setter" fi - local source_file="$(echo "$source" | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" - ynh_backup_if_checksum_is_different --file="$source_file" - echo "${!short_setting}" > "$source_file" - ynh_store_file_checksum --file="$source_file" --update_only - ynh_print_info "File '$source_file' overwrited with the content you provieded in '${short_setting}' question" + local bind_file="$(echo "$bind" | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" + ynh_backup_if_checksum_is_different --file="$bind_file" + echo "${!short_setting}" > "$bind_file" + ynh_store_file_checksum --file="$bind_file" --update_only + ynh_print_info "File '$bind_file' overwrited with the content you provieded in '${short_setting}' question" # Set value into a kind of key/value file else - local source_key="$(echo "$source" | cut -d: -f1)" - source_key=${source_key:-$short_setting} - local source_file="$(echo "$source" | cut -d: -f2 | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" + local bind_key="$(echo "$bind" | cut -d: -f1)" + bind_key=${bind_key:-$short_setting} + local bind_file="$(echo "$bind" | cut -d: -f2 | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" - ynh_backup_if_checksum_is_different --file="$source_file" - ynh_write_var_in_file --file="${source_file}" --key="${source_key}" --value="${!short_setting}" - ynh_store_file_checksum --file="$source_file" --update_only + ynh_backup_if_checksum_is_different --file="$bind_file" + ynh_write_var_in_file --file="${bind_file}" --key="${bind_key}" --value="${!short_setting}" + ynh_store_file_checksum --file="$bind_file" --update_only # We stored the info in settings in order to be able to upgrade the app ynh_app_setting_set $app $short_setting "${!short_setting}" - ynh_print_info "Configuration key '$source_key' edited into $source_file" + ynh_print_info "Configuration key '$bind_key' edited into $bind_file" fi fi @@ -251,7 +252,7 @@ ynh_app_config_run() { declare -Ag old=() declare -Ag changed=() declare -Ag file_hash=() - declare -Ag sources=() + declare -Ag binds=() declare -Ag types=() declare -Ag formats=() diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index b5b5fa6ed..f078dda82 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -206,7 +206,7 @@ class ConfigPanel: } }, "options": { - "properties": ["ask", "type", "source", "help", "example", + "properties": ["ask", "type", "bind", "help", "example", "style", "icon", "placeholder", "visible", "optional", "choices", "yes", "no", "pattern", "limit", "min", "max", "step", "accept", "redact"], From 74714d0a6228c8881893266970693e17047684f4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 5 Sep 2021 23:59:41 +0200 Subject: [PATCH 2941/3170] config panels: bind='' -> bind='null' --- data/helpers.d/config | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/config b/data/helpers.d/config index fe3a488de..049770651 100644 --- a/data/helpers.d/config +++ b/data/helpers.d/config @@ -39,7 +39,7 @@ EOL old[$short_setting]="$($getter)" formats[${short_setting}]="yaml" - elif [[ "$bind" == "" ]] ; then + elif [[ "$bind" == "null" ]] ; then old[$short_setting]="YNH_NULL" # Get value from app settings or from another file @@ -87,7 +87,7 @@ _ynh_app_config_apply() { if type -t $setter 2>/dev/null | grep -q '^function$' 2>/dev/null; then $setter - elif [[ "$bind" == "" ]] ; then + elif [[ "$bind" == "null" ]] ; then continue # Save in a file From 552db2e21db0049a7ee59a81f1de03c2b388ff36 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 6 Sep 2021 00:01:14 +0200 Subject: [PATCH 2942/3170] config helpers: misc style + check if file exists --- data/helpers.d/config | 113 ++++++++++++++++++++++++++---------------- data/helpers.d/utils | 9 +++- 2 files changed, 76 insertions(+), 46 deletions(-) diff --git a/data/helpers.d/config b/data/helpers.d/config index 049770651..5d024442a 100644 --- a/data/helpers.d/config +++ b/data/helpers.d/config @@ -19,7 +19,7 @@ for panel_name, panel in loaded_toml.items(): if not isinstance(param, dict): continue print(';'.join([ - name, + name, param.get('type', 'string'), param.get('bind', 'settings' if param.get('type', 'string') != 'file' else '') ])) @@ -35,45 +35,53 @@ EOL file_hash[${short_setting}]="" formats[${short_setting}]="" # Get value from getter if exists - if type -t $getter 2>/dev/null | grep -q '^function$' 2>/dev/null; then + if type -t $getter 2>/dev/null | grep -q '^function$' 2>/dev/null; + then old[$short_setting]="$($getter)" formats[${short_setting}]="yaml" - - elif [[ "$bind" == "null" ]] ; then + + elif [[ "$bind" == "null" ]]; + then old[$short_setting]="YNH_NULL" # Get value from app settings or from another file - elif [[ "$type" == "file" ]] ; then - if [[ "$bind" == "settings" ]] ; then + elif [[ "$type" == "file" ]]; + then + if [[ "$bind" == "settings" ]]; + then ynh_die "File '${short_setting}' can't be stored in settings" fi old[$short_setting]="$(ls $(echo $bind | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/) 2> /dev/null || echo YNH_NULL)" file_hash[$short_setting]="true" - + # Get multiline text from settings or from a full file - elif [[ "$type" == "text" ]] ; then - if [[ "$bind" == "settings" ]] ; then + elif [[ "$type" == "text" ]]; + then + if [[ "$bind" == "settings" ]]; + then old[$short_setting]="$(ynh_app_setting_get $app $short_setting)" - elif [[ "$bind" == *":"* ]] ; then + elif [[ "$bind" == *":"* ]]; + then ynh_die "For technical reasons, multiline text '${short_setting}' can't be stored automatically in a variable file, you have to create custom getter/setter" else old[$short_setting]="$(cat $(echo $bind | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/) 2> /dev/null || echo YNH_NULL)" fi - # Get value from a kind of key/value file + # Get value from a kind of key/value file else - if [[ "$bind" == "settings" ]] ; then + if [[ "$bind" == "settings" ]]; + then bind=":/etc/yunohost/apps/$app/settings.yml" fi local bind_key="$(echo "$bind" | cut -d: -f1)" bind_key=${bind_key:-$short_setting} local bind_file="$(echo "$bind" | cut -d: -f2 | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" old[$short_setting]="$(ynh_read_var_in_file --file="${bind_file}" --key="${bind_key}")" - + fi done - - + + } _ynh_app_config_apply() { @@ -82,21 +90,27 @@ _ynh_app_config_apply() { local setter="set__${short_setting}" local bind="${binds[$short_setting]}" local type="${types[$short_setting]}" - if [ "${changed[$short_setting]}" == "true" ] ; then + if [ "${changed[$short_setting]}" == "true" ]; + then # Apply setter if exists - if type -t $setter 2>/dev/null | grep -q '^function$' 2>/dev/null; then + if type -t $setter 2>/dev/null | grep -q '^function$' 2>/dev/null; + then $setter - elif [[ "$bind" == "null" ]] ; then + elif [[ "$bind" == "null" ]]; + then continue # Save in a file - elif [[ "$type" == "file" ]] ; then - if [[ "$bind" == "settings" ]] ; then + elif [[ "$type" == "file" ]]; + then + if [[ "$bind" == "settings" ]]; + then ynh_die "File '${short_setting}' can't be stored in settings" fi local bind_file="$(echo "$bind" | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" - if [[ "${!short_setting}" == "" ]] ; then + if [[ "${!short_setting}" == "" ]]; + then ynh_backup_if_checksum_is_different --file="$bind_file" rm -f "$bind_file" ynh_delete_file_checksum --file="$bind_file" --update_only @@ -107,15 +121,18 @@ _ynh_app_config_apply() { ynh_store_file_checksum --file="$bind_file" --update_only ynh_print_info "File '$bind_file' overwrited with ${!short_setting}" fi - - # Save value in app settings - elif [[ "$bind" == "settings" ]] ; then + + # Save value in app settings + elif [[ "$bind" == "settings" ]]; + i then ynh_app_setting_set $app $short_setting "${!short_setting}" ynh_print_info "Configuration key '$short_setting' edited in app settings" - + # Save multiline text in a file - elif [[ "$type" == "text" ]] ; then - if [[ "$bind" == *":"* ]] ; then + elif [[ "$type" == "text" ]]; + then + if [[ "$bind" == *":"* ]]; + then ynh_die "For technical reasons, multiline text '${short_setting}' can't be stored automatically in a variable file, you have to create custom getter/setter" fi local bind_file="$(echo "$bind" | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" @@ -133,7 +150,7 @@ _ynh_app_config_apply() { ynh_backup_if_checksum_is_different --file="$bind_file" ynh_write_var_in_file --file="${bind_file}" --key="${bind_key}" --value="${!short_setting}" ynh_store_file_checksum --file="$bind_file" --update_only - + # We stored the info in settings in order to be able to upgrade the app ynh_app_setting_set $app $short_setting "${!short_setting}" ynh_print_info "Configuration key '$bind_key' edited into $bind_file" @@ -146,8 +163,10 @@ _ynh_app_config_apply() { _ynh_app_config_show() { for short_setting in "${!old[@]}" do - if [[ "${old[$short_setting]}" != YNH_NULL ]] ; then - if [[ "${formats[$short_setting]}" == "yaml" ]] ; then + if [[ "${old[$short_setting]}" != YNH_NULL ]]; + then + if [[ "${formats[$short_setting]}" == "yaml" ]]; + then ynh_return "${short_setting}:" ynh_return "$(echo "${old[$short_setting]}" | sed 's/^/ /g')" else @@ -167,23 +186,28 @@ _ynh_app_config_validate() { for short_setting in "${!old[@]}" do changed[$short_setting]=false - if [ -z ${!short_setting+x} ]; then + if [ -z ${!short_setting+x} ]; + then # Assign the var with the old value in order to allows multiple # args validation declare "$short_setting"="${old[$short_setting]}" continue fi - if [ ! -z "${file_hash[${short_setting}]}" ] ; then + if [ ! -z "${file_hash[${short_setting}]}" ]; + then file_hash[old__$short_setting]="" file_hash[new__$short_setting]="" - if [ -f "${old[$short_setting]}" ] ; then + if [ -f "${old[$short_setting]}" ]; + then file_hash[old__$short_setting]=$(sha256sum "${old[$short_setting]}" | cut -d' ' -f1) - if [ -z "${!short_setting}" ] ; then + if [ -z "${!short_setting}" ]; + then changed[$short_setting]=true nothing_changed=false fi fi - if [ -f "${!short_setting}" ] ; then + if [ -f "${!short_setting}" ]; + then file_hash[new__$short_setting]=$(sha256sum "${!short_setting}" | cut -d' ' -f1) if [[ "${file_hash[old__$short_setting]}" != "${file_hash[new__$short_setting]}" ]] then @@ -203,8 +227,8 @@ _ynh_app_config_validate() { then ynh_print_info "Nothing has changed" exit 0 - fi - + fi + # Run validation if something is changed ynh_script_progression --message="Validating the new configuration..." --weight=1 @@ -212,7 +236,8 @@ _ynh_app_config_validate() { do [[ "${changed[$short_setting]}" == "false" ]] && continue local result="" - if type -t validate__$short_setting | grep -q '^function$' 2>/dev/null; then + if type -t validate__$short_setting | grep -q '^function$' 2>/dev/null; + then result="$(validate__$short_setting)" fi if [ -n "$result" ] @@ -228,8 +253,8 @@ _ynh_app_config_validate() { if [[ "$changes_validated" == "false" ]] then exit 0 - fi - + fi + } ynh_app_config_get() { @@ -255,19 +280,19 @@ ynh_app_config_run() { declare -Ag binds=() declare -Ag types=() declare -Ag formats=() - + case $1 in show) ynh_app_config_get ynh_app_config_show ;; - apply) + apply) max_progression=4 ynh_script_progression --message="Reading config panel description and current configuration..." ynh_app_config_get - + ynh_app_config_validate - + ynh_script_progression --message="Applying the new configuration..." ynh_app_config_apply ynh_script_progression --message="Configuration of $app completed" --last diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 0a820505c..54f936c4d 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -481,9 +481,9 @@ ynh_replace_vars () { # # This helpers match several var affectation use case in several languages # We don't use jq or equivalent to keep comments and blank space in files -# This helpers work line by line, it is not able to work correctly +# This helpers work line by line, it is not able to work correctly # if you have several identical keys in your files -# +# # Example of line this helpers can managed correctly # .yml # title: YunoHost documentation @@ -517,6 +517,9 @@ ynh_read_var_in_file() { ynh_handle_getopts_args "$@" set +o xtrace local filename="$(basename -- "$file")" + + [[ -f $file ]] || ynh_die "File $file does not exists" + local ext="${filename##*.}" local endline=',;' local assign="=>|:|=" @@ -576,6 +579,8 @@ ynh_write_var_in_file() { set +o xtrace local var_part='\s*\$?([\w.]*\[)?\s*["'"']?${key}['"'"]?\s*\]?\s*[:=]>?\s*' + [[ -f $file ]] || ynh_die "File $file does not exists" + local crazy_value="$(grep -i -o -P '^\s*\$?([\w.]*\[)?\s*["'"']?${key}['"'"]?\s*\]?\s*[:=]>?\s*\K.*(?=[\s,;]*$)' ${file} | head -n1)" # local crazy_value="$(grep -i -o -P "^${var_part}\K.*(?=[ \t,\n;]*\$)" ${file} | head -n1)" local first_char="${crazy_value:0:1}" From acf4d1c82a9e8ffefb2adc7628b8bd7f07be73bc Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 6 Sep 2021 00:01:36 +0200 Subject: [PATCH 2943/3170] i18n: 'danger' key is only defined in yunohost, not moulinette --- src/yunohost/utils/config.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index f078dda82..a4c27c693 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -771,7 +771,8 @@ class DisplayTextQuestion(Question): "warning": "yellow", "danger": "red", } - return colorize(m18n.g(self.style), color[self.style]) + f" {text}" + text = m18n.g(self.style) if self.style != "danger" else m18n.n("danger") + return colorize(text, color[self.style]) + f" {text}" else: return text From a5bf5246c5854fe367a50803745c64889a7d7042 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 6 Sep 2021 00:02:41 +0200 Subject: [PATCH 2944/3170] Remove i18n stale strings --- locales/en.json | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/locales/en.json b/locales/en.json index f1110df7b..588e37357 100644 --- a/locales/en.json +++ b/locales/en.json @@ -17,7 +17,6 @@ "app_argument_invalid": "Pick a valid value for the argument '{name}': {error}", "app_argument_password_no_default": "Error while parsing password argument '{name}': password argument can't have a default value for security reason", "app_argument_required": "Argument '{name}' is required", - "app_change_url_failed_nginx_reload": "Could not reload NGINX. Here is the output of 'nginx -t':\n{nginx_errors}", "app_change_url_identical_domains": "The old and new domain/url_path are identical ('{domain}{path}'), nothing to do.", "app_change_url_no_script": "The app '{app_name}' doesn't support URL modification yet. Maybe you should upgrade it.", "app_change_url_success": "{app} URL is now {domain}{path}", @@ -397,10 +396,7 @@ "ldap_server_is_down_restart_it": "The LDAP service is down, attempt to restart it...", "log_app_action_run": "Run action of the '{}' app", "log_app_change_url": "Change the URL of the '{}' app", - "log_app_config_apply": "Apply config to the '{}' app", - "log_app_config_get": "Get a specific setting from config panel of the '{}' app", "log_app_config_set": "Apply config to the '{}' app", - "log_app_config_show": "Show the config panel of the '{}' app", "log_app_install": "Install the '{}' app", "log_app_makedefault": "Make '{}' the default app", "log_app_remove": "Remove the '{}' app", @@ -672,4 +668,4 @@ "yunohost_installing": "Installing YunoHost...", "yunohost_not_installed": "YunoHost is not correctly installed. Please run 'yunohost tools postinstall'", "yunohost_postinstall_end_tip": "The post-install completed! To finalize your setup, please consider:\n - adding a first user through the 'Users' section of the webadmin (or 'yunohost user create ' in command-line);\n - diagnose potential issues through the 'Diagnosis' section of the webadmin (or 'yunohost diagnosis run' in command-line);\n - reading the 'Finalizing your setup' and 'Getting to know YunoHost' parts in the admin documentation: https://yunohost.org/admindoc." -} \ No newline at end of file +} From ba6f90d966b48d6e6bf947d7c9a5826954307ac4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 6 Sep 2021 00:15:25 +0200 Subject: [PATCH 2945/3170] YunohostError -> YunohostValidationError for some stuff --- src/yunohost/utils/config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index a4c27c693..744849199 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -59,7 +59,7 @@ class ConfigPanel: self._get_config_panel() if not self.config: - raise YunohostError("config_no_panel") + raise YunohostValidationError("config_no_panel") # Read or get values and hydrate the config self._load_current_values() @@ -264,7 +264,7 @@ class ConfigPanel: try: self.config["panels"][0]["sections"][0]["options"][0] except (KeyError, IndexError): - raise YunohostError( + raise YunohostValidationError( "config_unknown_filter_key", filter_key=self.filter_key ) From 4a3d6e53c67f65a2e34109e7c4b33a2a43511a62 Mon Sep 17 00:00:00 2001 From: ljf Date: Mon, 6 Sep 2021 00:58:46 +0200 Subject: [PATCH 2946/3170] [fix] ynh_read_var_in_file with ini file --- data/helpers.d/utils | 11 +++++++---- tests/test_helpers.d/ynhtest_config.sh | 12 ++++++------ 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 54f936c4d..97aae12ef 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -515,7 +515,7 @@ ynh_read_var_in_file() { local key # Manage arguments with getopts ynh_handle_getopts_args "$@" - set +o xtrace + #set +o xtrace local filename="$(basename -- "$file")" [[ -f $file ]] || ynh_die "File $file does not exists" @@ -525,9 +525,12 @@ ynh_read_var_in_file() { local assign="=>|:|=" local comments="#" local string="\"'" - if [[ "yaml yml toml ini env" =~ *"$ext"* ]]; then + if [[ "$ext" =~ ^ini|env|toml|yml|yaml$ ]]; then endline='#' fi + if [[ "$ext" =~ ^ini|env$ ]]; then + comments="[;#]" + fi if [[ "php" == "$ext" ]] || [[ "$ext" == "js" ]]; then comments="//" fi @@ -535,7 +538,7 @@ ynh_read_var_in_file() { local var_part='^\s*(?:(const|var|let)\s+)?\$?(\w+('$list')*(->|\.|\[))*\s*' var_part+="[$string]?${key}[$string]?" var_part+='\s*\]?\s*' - var_part+="(?:$assign)" + var_part+="($assign)" var_part+='\s*' # Extract the part after assignation sign @@ -546,7 +549,7 @@ ynh_read_var_in_file() { fi # Remove comments if needed - local expression="$(echo "$expression_with_comment" | sed "s@$comments[^$string]*\$@@" | sed "s@\s*[$endline]*\s*]*\$@@")" + local expression="$(echo "$expression_with_comment" | sed "s@$comments[^$string]*\$@@g" | sed "s@\s*[$endline]*\s*]*\$@@")" local first_char="${expression:0:1}" if [[ "$first_char" == '"' ]] ; then diff --git a/tests/test_helpers.d/ynhtest_config.sh b/tests/test_helpers.d/ynhtest_config.sh index 36165e3ac..3be72d191 100644 --- a/tests/test_helpers.d/ynhtest_config.sh +++ b/tests/test_helpers.d/ynhtest_config.sh @@ -79,8 +79,8 @@ ENABLED = False # TITLE = "Old title" TITLE = "Lorem Ipsum" THEME = "colib'ris" -EMAIL = "root@example.com" // This is a comment without quotes -PORT = 1234 // This is a comment without quotes +EMAIL = "root@example.com" # This is a comment without quotes +PORT = 1234 # This is a comment without quotes URL = 'https://yunohost.org' DICT = {} DICT['ldap_base'] = "ou=users,dc=yunohost,dc=org" @@ -156,8 +156,8 @@ enabled = False # title = Old title title = Lorem Ipsum theme = colib'ris -email = root@example.com # This is a comment without quotes -port = 1234 # This is a comment without quotes +email = root@example.com ; This is a comment without quotes +port = 1234 ; This is a comment without quotes url = https://yunohost.org [dict] ldap_base = ou=users,dc=yunohost,dc=org @@ -175,10 +175,10 @@ EOF test "$(_read_ini "$file" "theme")" == "colib'ris" test "$(ynh_read_var_in_file "$file" "theme")" == "colib'ris" - test "$(_read_ini "$file" "email")" == "root@example.com" + #test "$(_read_ini "$file" "email")" == "root@example.com" test "$(ynh_read_var_in_file "$file" "email")" == "root@example.com" - test "$(_read_ini "$file" "port")" == "1234" + #test "$(_read_ini "$file" "port")" == "1234" test "$(ynh_read_var_in_file "$file" "port")" == "1234" test "$(_read_ini "$file" "url")" == "https://yunohost.org" From e60804a69f030ef06be1af68d3457a07981ef88e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 6 Sep 2021 02:07:56 +0200 Subject: [PATCH 2947/3170] config helpers: misc syntax issues --- data/helpers.d/config | 46 +++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/data/helpers.d/config b/data/helpers.d/config index 5d024442a..5970351f7 100644 --- a/data/helpers.d/config +++ b/data/helpers.d/config @@ -40,14 +40,14 @@ EOL old[$short_setting]="$($getter)" formats[${short_setting}]="yaml" - elif [[ "$bind" == "null" ]]; + elif [[ "$bind" == "null" ]] then old[$short_setting]="YNH_NULL" # Get value from app settings or from another file - elif [[ "$type" == "file" ]]; + elif [[ "$type" == "file" ]] then - if [[ "$bind" == "settings" ]]; + if [[ "$bind" == "settings" ]] then ynh_die "File '${short_setting}' can't be stored in settings" fi @@ -55,12 +55,12 @@ EOL file_hash[$short_setting]="true" # Get multiline text from settings or from a full file - elif [[ "$type" == "text" ]]; + elif [[ "$type" == "text" ]] then - if [[ "$bind" == "settings" ]]; + if [[ "$bind" == "settings" ]] then old[$short_setting]="$(ynh_app_setting_get $app $short_setting)" - elif [[ "$bind" == *":"* ]]; + elif [[ "$bind" == *":"* ]] then ynh_die "For technical reasons, multiline text '${short_setting}' can't be stored automatically in a variable file, you have to create custom getter/setter" else @@ -69,7 +69,7 @@ EOL # Get value from a kind of key/value file else - if [[ "$bind" == "settings" ]]; + if [[ "$bind" == "settings" ]] then bind=":/etc/yunohost/apps/$app/settings.yml" fi @@ -90,26 +90,26 @@ _ynh_app_config_apply() { local setter="set__${short_setting}" local bind="${binds[$short_setting]}" local type="${types[$short_setting]}" - if [ "${changed[$short_setting]}" == "true" ]; + if [ "${changed[$short_setting]}" == "true" ] then # Apply setter if exists if type -t $setter 2>/dev/null | grep -q '^function$' 2>/dev/null; then $setter - elif [[ "$bind" == "null" ]]; + elif [[ "$bind" == "null" ]] then continue # Save in a file - elif [[ "$type" == "file" ]]; + elif [[ "$type" == "file" ]] then - if [[ "$bind" == "settings" ]]; + if [[ "$bind" == "settings" ]] then ynh_die "File '${short_setting}' can't be stored in settings" fi local bind_file="$(echo "$bind" | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" - if [[ "${!short_setting}" == "" ]]; + if [[ "${!short_setting}" == "" ]] then ynh_backup_if_checksum_is_different --file="$bind_file" rm -f "$bind_file" @@ -123,15 +123,15 @@ _ynh_app_config_apply() { fi # Save value in app settings - elif [[ "$bind" == "settings" ]]; - i then + elif [[ "$bind" == "settings" ]] + then ynh_app_setting_set $app $short_setting "${!short_setting}" ynh_print_info "Configuration key '$short_setting' edited in app settings" # Save multiline text in a file - elif [[ "$type" == "text" ]]; + elif [[ "$type" == "text" ]] then - if [[ "$bind" == *":"* ]]; + if [[ "$bind" == *":"* ]] then ynh_die "For technical reasons, multiline text '${short_setting}' can't be stored automatically in a variable file, you have to create custom getter/setter" fi @@ -163,9 +163,9 @@ _ynh_app_config_apply() { _ynh_app_config_show() { for short_setting in "${!old[@]}" do - if [[ "${old[$short_setting]}" != YNH_NULL ]]; + if [[ "${old[$short_setting]}" != YNH_NULL ]] then - if [[ "${formats[$short_setting]}" == "yaml" ]]; + if [[ "${formats[$short_setting]}" == "yaml" ]] then ynh_return "${short_setting}:" ynh_return "$(echo "${old[$short_setting]}" | sed 's/^/ /g')" @@ -186,27 +186,27 @@ _ynh_app_config_validate() { for short_setting in "${!old[@]}" do changed[$short_setting]=false - if [ -z ${!short_setting+x} ]; + if [ -z ${!short_setting+x} ] then # Assign the var with the old value in order to allows multiple # args validation declare "$short_setting"="${old[$short_setting]}" continue fi - if [ ! -z "${file_hash[${short_setting}]}" ]; + if [ ! -z "${file_hash[${short_setting}]}" ] then file_hash[old__$short_setting]="" file_hash[new__$short_setting]="" - if [ -f "${old[$short_setting]}" ]; + if [ -f "${old[$short_setting]}" ] then file_hash[old__$short_setting]=$(sha256sum "${old[$short_setting]}" | cut -d' ' -f1) - if [ -z "${!short_setting}" ]; + if [ -z "${!short_setting}" ] then changed[$short_setting]=true nothing_changed=false fi fi - if [ -f "${!short_setting}" ]; + if [ -f "${!short_setting}" ] then file_hash[new__$short_setting]=$(sha256sum "${!short_setting}" | cut -d' ' -f1) if [[ "${file_hash[old__$short_setting]}" != "${file_hash[new__$short_setting]}" ]] From 66fcea72e5fbc4ddef4f6e66d4b465c365b3924d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 6 Sep 2021 02:10:15 +0200 Subject: [PATCH 2948/3170] config: Add more tests for regular setting / bind / custom function --- src/yunohost/tests/test_app_config.py | 58 ++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/src/yunohost/tests/test_app_config.py b/src/yunohost/tests/test_app_config.py index af792c431..4ace0aaf9 100644 --- a/src/yunohost/tests/test_app_config.py +++ b/src/yunohost/tests/test_app_config.py @@ -5,8 +5,11 @@ import pytest from .conftest import get_test_apps_dir +from moulinette.utils.filesystem import read_file + from yunohost.domain import _get_maindomain from yunohost.app import ( + app_setting, app_install, app_remove, _is_installed, @@ -15,7 +18,7 @@ from yunohost.app import ( app_ssowatconf, ) -from yunohost.utils.error import YunohostValidationError +from yunohost.utils.error import YunohostError, YunohostValidationError def setup_function(function): @@ -128,17 +131,68 @@ def test_app_config_get_nonexistentstuff(config_app): app_config_get(config_app, "main.components.nonexistent") -def test_app_config_set_boolean(config_app): +def test_app_config_regular_setting(config_app): assert app_config_get(config_app, "main.components.boolean") is None app_config_set(config_app, "main.components.boolean", "no") assert app_config_get(config_app, "main.components.boolean") == "0" + assert app_setting(config_app, "boolean") == "0" app_config_set(config_app, "main.components.boolean", "yes") assert app_config_get(config_app, "main.components.boolean") == "1" + assert app_setting(config_app, "boolean") == "1" with pytest.raises(YunohostValidationError): app_config_set(config_app, "main.components.boolean", "pwet") + + +def test_app_config_bind_on_file(config_app): + + # c.f. conf/test.php in the config app + assert '$arg5= "Arg5 value";' in read_file("/var/www/config_app/test.php") + assert app_config_get(config_app, "bind.variable.arg5") == "Arg5 value" + assert app_setting(config_app, "arg5") is None + + app_config_set(config_app, "bind.variable.arg5", "Foo Bar") + + assert '$arg5= "Foo Bar";' in read_file("/var/www/config_app/test.php") + assert app_config_get(config_app, "bind.variable.arg5") == "Foo Bar" + assert app_setting(config_app, "arg5") == "Foo Bar" + + +def test_app_config_custom_get(config_app): + + assert app_setting(config_app, "arg9") is None + assert "Files in /var/www" in app_config_get(config_app, "bind.function.arg9")["ask"]["en"] + assert app_setting(config_app, "arg9") is None + + +def test_app_config_custom_validator(config_app): + + # c.f. the config script + # arg8 is a password that must be at least 8 chars + assert not os.path.exists("/var/www/config_app/password") + assert app_setting(config_app, "arg8") is None + + with pytest.raises(YunohostValidationError): + app_config_set(config_app, "bind.function.arg8", "pZo6i7u91h") + + assert not os.path.exists("/var/www/config_app/password") + assert app_setting(config_app, "arg8") is None + + +def test_app_config_custom_set(config_app): + + assert not os.path.exists("/var/www/config_app/password") + assert app_setting(config_app, "arg8") is None + + app_config_set(config_app, "bind.function.arg8", "OneSuperStrongPassword") + + assert os.path.exists("/var/www/config_app/password") + content = read_file("/var/www/config_app/password") + assert "OneSuperStrongPassword" not in content + assert content.startswith("$6$saltsalt$") + assert app_setting(config_app, "arg8") is None From 0789eca4e06279d45a23d5eacb78eb3881801989 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 6 Sep 2021 02:10:45 +0200 Subject: [PATCH 2949/3170] config: Tweak logic to return a validation error when custom validation fails --- data/helpers.d/config | 16 ++++++++++++++-- src/yunohost/app.py | 17 +++++++++++++---- src/yunohost/utils/config.py | 8 ++------ 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/data/helpers.d/config b/data/helpers.d/config index 5970351f7..71d41fbe9 100644 --- a/data/helpers.d/config +++ b/data/helpers.d/config @@ -242,8 +242,20 @@ _ynh_app_config_validate() { fi if [ -n "$result" ] then - local key="YNH_ERROR_${short_setting}" - ynh_return "$key: \"$result\"" + # + # Return a yaml such as: + # + # validation_errors: + # some_key: "An error message" + # some_other_key: "Another error message" + # + # We use changes_validated to know if this is + # the first validation error + if [[ "$changes_validated" == true ]] + then + ynh_return "validation_errors:" + fi + ynh_return " ${short_setting}: \"$result\"" changes_validated=false fi done diff --git a/src/yunohost/app.py b/src/yunohost/app.py index e9712edb4..4047369e0 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1695,9 +1695,7 @@ def app_config_set( Question.operation_logger = operation_logger - result = config_.set(key, value, args, args_file, operation_logger=operation_logger) - - return result + return config_.set(key, value, args, args_file, operation_logger=operation_logger) class AppConfigPanel(ConfigPanel): @@ -1715,7 +1713,18 @@ class AppConfigPanel(ConfigPanel): def _apply(self): env = {key: str(value) for key, value in self.new_values.items()} - self.errors = self._call_config_script("apply", env=env) + return_content = self._call_config_script("apply", env=env) + + # If the script returned validation error + # raise a ValidationError exception using + # the first key + if return_content: + for key, message in return_content.get("validation_errors").items(): + raise YunohostValidationError( + "app_argument_invalid", + name=key, + error=message, + ) def _call_config_script(self, action, env={}): from yunohost.hook import hook_exec diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 744849199..6b491386f 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -135,6 +135,8 @@ class ConfigPanel: try: self._apply() + except YunohostError: + raise # Script got manually interrupted ... # N.B. : KeyboardInterrupt does not inherit from Exception except (KeyboardInterrupt, EOFError): @@ -152,16 +154,10 @@ class ConfigPanel: # Delete files uploaded from API FileQuestion.clean_upload_dirs() - if self.errors: - return { - "errors": self.errors, - } - self._reload_services() logger.success("Config updated as expected") operation_logger.success() - return {} def _get_toml(self): return read_toml(self.config_path) From 060d5b6dc5118885f69032dd29463e961a5e1ab2 Mon Sep 17 00:00:00 2001 From: ljf Date: Mon, 6 Sep 2021 04:20:35 +0200 Subject: [PATCH 2950/3170] [fix] ynh_write_var_in_file and after option --- data/helpers.d/config | 8 +- data/helpers.d/utils | 94 ++++++++++++++++++----- tests/test_helpers.d/ynhtest_config.sh | 102 +++++++++++++------------ 3 files changed, 136 insertions(+), 68 deletions(-) diff --git a/data/helpers.d/config b/data/helpers.d/config index 5d024442a..e834db041 100644 --- a/data/helpers.d/config +++ b/data/helpers.d/config @@ -124,7 +124,7 @@ _ynh_app_config_apply() { # Save value in app settings elif [[ "$bind" == "settings" ]]; - i then + then ynh_app_setting_set $app $short_setting "${!short_setting}" ynh_print_info "Configuration key '$short_setting' edited in app settings" @@ -143,8 +143,14 @@ _ynh_app_config_apply() { # Set value into a kind of key/value file else + local bind_after="" local bind_key="$(echo "$bind" | cut -d: -f1)" bind_key=${bind_key:-$short_setting} + if [[ "$bind_key" == *">"* ]]; + then + bind_after="$(echo "${bind_key}" | cut -d'>' -f1)" + bind_key="$(echo "${bind_key}" | cut -d'>' -f2)" + fi local bind_file="$(echo "$bind" | cut -d: -f2 | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" ynh_backup_if_checksum_is_different --file="$bind_file" diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 97aae12ef..5ecc0cf0b 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -507,19 +507,30 @@ ynh_replace_vars () { # # Requires YunoHost version 4.3 or higher. ynh_read_var_in_file() { - set +o xtrace # Declare an array to define the options of this helper. - local legacy_args=fk - local -A args_array=( [f]=file= [k]=key= ) + local legacy_args=fka + local -A args_array=( [f]=file= [k]=key= [a]=after=) local file local key + local after # Manage arguments with getopts ynh_handle_getopts_args "$@" - #set +o xtrace - local filename="$(basename -- "$file")" + after="${after:-}" [[ -f $file ]] || ynh_die "File $file does not exists" + # Get the line number after which we search for the variable + local line_number=1 + if [[ -n "$after" ]]; + then + line_number=$(grep -n $after $file | cut -d: -f1) + if [[ -z "$line_number" ]]; + then + return 1 + fi + fi + + local filename="$(basename -- "$file")" local ext="${filename##*.}" local endline=',;' local assign="=>|:|=" @@ -535,14 +546,14 @@ ynh_read_var_in_file() { comments="//" fi local list='\[\s*['$string']?\w+['$string']?\]' - local var_part='^\s*(?:(const|var|let)\s+)?\$?(\w+('$list')*(->|\.|\[))*\s*' + local var_part='^\s*((const|var|let)\s+)?\$?(\w+('$list')*(->|\.|\[))*\s*' var_part+="[$string]?${key}[$string]?" var_part+='\s*\]?\s*' var_part+="($assign)" var_part+='\s*' # Extract the part after assignation sign - local expression_with_comment="$(grep -i -o -P $var_part'\K.*$' ${file} || echo YNH_NULL | head -n1)" + local expression_with_comment="$(tail +$line_number ${file} | grep -i -o -P $var_part'\K.*$' || echo YNH_NULL | head -n1)" if [[ "$expression_with_comment" == "YNH_NULL" ]]; then echo YNH_NULL return 0 @@ -570,40 +581,85 @@ ynh_read_var_in_file() { # # Requires YunoHost version 4.3 or higher. ynh_write_var_in_file() { - set +o xtrace # Declare an array to define the options of this helper. - local legacy_args=fkv - local -A args_array=( [f]=file= [k]=key= [v]=value=) + local legacy_args=fkva + local -A args_array=( [f]=file= [k]=key= [v]=value= [a]=after=) local file local key local value + local after # Manage arguments with getopts ynh_handle_getopts_args "$@" - set +o xtrace - local var_part='\s*\$?([\w.]*\[)?\s*["'"']?${key}['"'"]?\s*\]?\s*[:=]>?\s*' + after="${after:-}" [[ -f $file ]] || ynh_die "File $file does not exists" - local crazy_value="$(grep -i -o -P '^\s*\$?([\w.]*\[)?\s*["'"']?${key}['"'"]?\s*\]?\s*[:=]>?\s*\K.*(?=[\s,;]*$)' ${file} | head -n1)" - # local crazy_value="$(grep -i -o -P "^${var_part}\K.*(?=[ \t,\n;]*\$)" ${file} | head -n1)" - local first_char="${crazy_value:0:1}" + # Get the line number after which we search for the variable + local line_number=1 + if [[ -n "$after" ]]; + then + line_number=$(grep -n $after $file | cut -d: -f1) + if [[ -z "$line_number" ]]; + then + return 1 + fi + fi + local range="${line_number},\$ " + + local filename="$(basename -- "$file")" + local ext="${filename##*.}" + local endline=',;' + local assign="=>|:|=" + local comments="#" + local string="\"'" + if [[ "$ext" =~ ^ini|env|toml|yml|yaml$ ]]; then + endline='#' + fi + if [[ "$ext" =~ ^ini|env$ ]]; then + comments="[;#]" + fi + if [[ "php" == "$ext" ]] || [[ "$ext" == "js" ]]; then + comments="//" + fi + local list='\[\s*['$string']?\w+['$string']?\]' + local var_part='^\s*((const|var|let)\s+)?\$?(\w+('$list')*(->|\.|\[))*\s*' + var_part+="[$string]?${key}[$string]?" + var_part+='\s*\]?\s*' + var_part+="($assign)" + var_part+='\s*' + + # Extract the part after assignation sign + local expression_with_comment="$(tail +$line_number ${file} | grep -i -o -P $var_part'\K.*$' || echo YNH_NULL | head -n1)" + if [[ "$expression_with_comment" == "YNH_NULL" ]]; then + return 1 + fi + + # Remove comments if needed + local expression="$(echo "$expression_with_comment" | sed "s@$comments[^$string]*\$@@g" | sed "s@\s*[$endline]*\s*]*\$@@")" + endline=${expression_with_comment#"$expression"} + endline="$(echo "$endline" | sed 's/\\/\\\\/g')" + value="$(echo "$value" | sed 's/\\/\\\\/g')" + local first_char="${expression:0:1}" delimiter=$'\001' if [[ "$first_char" == '"' ]] ; then # \ and sed is quite complex you need 2 \\ to get one in a sed # So we need \\\\ to go through 2 sed value="$(echo "$value" | sed 's/"/\\\\"/g')" - sed -ri s$delimiter'^('"${var_part}"'")([^"]|\\")*("[\s;,]*)$'$delimiter'\1'"${value}"'\4'$delimiter'i' ${file} + sed -ri "${range}s$delimiter"'(^'"${var_part}"'")([^"]|\\")*("[\s;,]*)(\s*'$comments'.*)?$'$delimiter'\1'"${value}"'"'"${endline}${delimiter}i" ${file} elif [[ "$first_char" == "'" ]] ; then # \ and sed is quite complex you need 2 \\ to get one in a sed # However double quotes implies to double \\ to # So we need \\\\\\\\ to go through 2 sed and 1 double quotes str value="$(echo "$value" | sed "s/'/\\\\\\\\'/g")" - sed -ri "s$delimiter^(${var_part}')([^']|\\')*('"'[\s,;]*)$'$delimiter'\1'"${value}"'\4'$delimiter'i' ${file} + sed -ri "${range}s$delimiter(^${var_part}')([^']|\\')*('"'[\s,;]*)(\s*'$comments'.*)?$'$delimiter'\1'"${value}'${endline}${delimiter}i" ${file} else - if [[ "$value" == *"'"* ]] || [[ "$value" == *'"'* ]] ; then + if [[ "$value" == *"'"* ]] || [[ "$value" == *'"'* ]] || [[ "$ext" =~ ^php|py|json|js$ ]] ; then value='\"'"$(echo "$value" | sed 's/"/\\\\"/g')"'\"' fi - sed -ri "s$delimiter^(${var_part}).*"'$'$delimiter'\1'"${value}"$delimiter'i' ${file} + if [[ "$ext" =~ ^yaml|yml$ ]] ; then + value=" $value" + fi + sed -ri "${range}s$delimiter(^${var_part}).*\$$delimiter\1${value}${endline}${delimiter}i" ${file} fi } diff --git a/tests/test_helpers.d/ynhtest_config.sh b/tests/test_helpers.d/ynhtest_config.sh index 3be72d191..b64943a48 100644 --- a/tests/test_helpers.d/ynhtest_config.sh +++ b/tests/test_helpers.d/ynhtest_config.sh @@ -34,6 +34,8 @@ DICT = {} DICT['ldap_base'] = "ou=users,dc=yunohost,dc=org" DICT['ldap_conf'] = {} DICT['ldap_conf']['user'] = "camille" +# YNH_ICI +DICT['TITLE'] = "Hello world" EOF test "$(_read_py "$file" "FOO")" == "None" @@ -60,6 +62,8 @@ EOF test "$(ynh_read_var_in_file "$file" "ldap_base")" == "ou=users,dc=yunohost,dc=org" test "$(ynh_read_var_in_file "$file" "user")" == "camille" + + test "$(ynh_read_var_in_file "$file" "TITLE" "YNH_ICI")" == "Hello world" ! _read_py "$file" "NONEXISTENT" test "$(ynh_read_var_in_file "$file" "NONEXISTENT")" == "YNH_NULL" @@ -68,7 +72,7 @@ EOF test "$(ynh_read_var_in_file "$file" "ENABLE")" == "YNH_NULL" } -nhtest_config_write_py() { +ynhtest_config_write_py() { local dummy_dir="$(mktemp -d -p $VAR_WWW)" file="$dummy_dir/dummy.py" @@ -84,11 +88,13 @@ PORT = 1234 # This is a comment without quotes URL = 'https://yunohost.org' DICT = {} DICT['ldap_base'] = "ou=users,dc=yunohost,dc=org" +# YNH_ICI +DICT['TITLE'] = "Hello world" EOF - #ynh_write_var_in_file "$file" "FOO" "bar" - #test "$(_read_py "$file" "FOO")" == "bar" # FIXME FIXME FIXME - #test "$(ynh_read_var_in_file "$file" "FOO")" == "bar" + ynh_write_var_in_file "$file" "FOO" "bar" + test "$(_read_py "$file" "FOO")" == "bar" + test "$(ynh_read_var_in_file "$file" "FOO")" == "bar" ynh_write_var_in_file "$file" "ENABLED" "True" test "$(_read_py "$file" "ENABLED")" == "True" @@ -116,12 +122,15 @@ EOF ynh_write_var_in_file "$file" "ldap_base" "ou=users,dc=yunohost,dc=org" test "$(ynh_read_var_in_file "$file" "ldap_base")" == "ou=users,dc=yunohost,dc=org" + + ynh_write_var_in_file "$file" "TITLE" "YOLO" "YNH_ICI" + test "$(ynh_read_var_in_file "$file" "TITLE" "YNH_ICI")" == "YOLO" - ynh_write_var_in_file "$file" "NONEXISTENT" "foobar" + ! ynh_write_var_in_file "$file" "NONEXISTENT" "foobar" ! _read_py "$file" "NONEXISTENT" test "$(ynh_read_var_in_file "$file" "NONEXISTENT")" == "YNH_NULL" - ynh_write_var_in_file "$file" "ENABLE" "foobar" + ! ynh_write_var_in_file "$file" "ENABLE" "foobar" ! _read_py "$file" "ENABLE" test "$(ynh_read_var_in_file "$file" "ENABLE")" == "YNH_NULL" @@ -194,7 +203,7 @@ EOF } -nhtest_config_write_ini() { +ynhtest_config_write_ini() { local dummy_dir="$(mktemp -d -p $VAR_WWW)" file="$dummy_dir/dummy.ini" @@ -231,11 +240,11 @@ EOF test "$(ynh_read_var_in_file "$file" "theme")" == "super-awesome-theme" ynh_write_var_in_file "$file" "email" "sam@domain.tld" - test "$(_read_ini "$file" "email")" == "sam@domain.tld" + test "$(_read_ini "$file" "email")" == "sam@domain.tld # This is a comment without quotes" test "$(ynh_read_var_in_file "$file" "email")" == "sam@domain.tld" ynh_write_var_in_file "$file" "port" "5678" - test "$(_read_ini "$file" "port")" == "5678" + test "$(_read_ini "$file" "port")" == "5678 # This is a comment without quotes" test "$(ynh_read_var_in_file "$file" "port")" == "5678" ynh_write_var_in_file "$file" "url" "https://domain.tld/foobar" @@ -245,11 +254,11 @@ EOF ynh_write_var_in_file "$file" "ldap_base" "ou=users,dc=yunohost,dc=org" test "$(ynh_read_var_in_file "$file" "ldap_base")" == "ou=users,dc=yunohost,dc=org" - ynh_write_var_in_file "$file" "nonexistent" "foobar" + ! ynh_write_var_in_file "$file" "nonexistent" "foobar" ! _read_ini "$file" "nonexistent" test "$(ynh_read_var_in_file "$file" "nonexistent")" == "YNH_NULL" - ynh_write_var_in_file "$file" "enable" "foobar" + ! ynh_write_var_in_file "$file" "enable" "foobar" ! _read_ini "$file" "enable" test "$(ynh_read_var_in_file "$file" "enable")" == "YNH_NULL" @@ -322,7 +331,7 @@ EOF } -nhtest_config_write_yaml() { +ynhtest_config_write_yaml() { local dummy_dir="$(mktemp -d -p $VAR_WWW)" file="$dummy_dir/dummy.yml" @@ -340,10 +349,10 @@ dict: ldap_base: ou=users,dc=yunohost,dc=org EOF - #ynh_write_var_in_file "$file" "foo" "bar" + ynh_write_var_in_file "$file" "foo" "bar" # cat $dummy_dir/dummy.yml # to debug - #! test "$(_read_yaml "$file" "foo")" == "bar" # FIXME FIXME FIXME : writing broke the yaml syntax... "foo:bar" (no space aftr :) - #test "$(ynh_read_var_in_file "$file" "foo")" == "bar" + ! test "$(_read_yaml "$file" "foo")" == "bar" # writing broke the yaml syntax... "foo:bar" (no space aftr :) + test "$(ynh_read_var_in_file "$file" "foo")" == "bar" ynh_write_var_in_file "$file" "enabled" "true" test "$(_read_yaml "$file" "enabled")" == "True" @@ -372,10 +381,10 @@ EOF ynh_write_var_in_file "$file" "ldap_base" "ou=foobar,dc=domain,dc=tld" test "$(ynh_read_var_in_file "$file" "ldap_base")" == "ou=foobar,dc=domain,dc=tld" - ynh_write_var_in_file "$file" "nonexistent" "foobar" + ! ynh_write_var_in_file "$file" "nonexistent" "foobar" test "$(ynh_read_var_in_file "$file" "nonexistent")" == "YNH_NULL" - ynh_write_var_in_file "$file" "enable" "foobar" + ! ynh_write_var_in_file "$file" "enable" "foobar" test "$(ynh_read_var_in_file "$file" "enable")" == "YNH_NULL" test "$(ynh_read_var_in_file "$file" "enabled")" == "true" } @@ -449,7 +458,7 @@ EOF } -nhtest_config_write_json() { +ynhtest_config_write_json() { local dummy_dir="$(mktemp -d -p $VAR_WWW)" file="$dummy_dir/dummy.json" @@ -468,14 +477,15 @@ nhtest_config_write_json() { } EOF - #ynh_write_var_in_file "$file" "foo" "bar" - #cat $file - #test "$(_read_json "$file" "foo")" == "bar" # FIXME FIXME FIXME - #test "$(ynh_read_var_in_file "$file" "foo")" == "bar" + ynh_write_var_in_file "$file" "foo" "bar" + cat $file + test "$(_read_json "$file" "foo")" == "bar" + test "$(ynh_read_var_in_file "$file" "foo")" == "bar" - #ynh_write_var_in_file "$file" "enabled" "true" - #test "$(_read_json "$file" "enabled")" == "True" # FIXME FIXME FIXME - #test "$(ynh_read_var_in_file "$file" "enabled")" == "true" + ynh_write_var_in_file "$file" "enabled" "true" + cat $file + test "$(_read_json "$file" "enabled")" == "true" + test "$(ynh_read_var_in_file "$file" "enabled")" == "true" ynh_write_var_in_file "$file" "title" "Foo Bar" cat $file @@ -492,10 +502,9 @@ EOF test "$(_read_json "$file" "email")" == "sam@domain.tld" test "$(ynh_read_var_in_file "$file" "email")" == "sam@domain.tld" - #ynh_write_var_in_file "$file" "port" "5678" - #cat $file - #test "$(_read_json "$file" "port")" == "5678" # FIXME FIXME FIXME - #test "$(ynh_read_var_in_file "$file" "port")" == "5678" + ynh_write_var_in_file "$file" "port" "5678" + test "$(_read_json "$file" "port")" == "5678" + test "$(ynh_read_var_in_file "$file" "port")" == "5678" ynh_write_var_in_file "$file" "url" "https://domain.tld/foobar" test "$(_read_json "$file" "url")" == "https://domain.tld/foobar" @@ -504,12 +513,12 @@ EOF ynh_write_var_in_file "$file" "ldap_base" "ou=foobar,dc=domain,dc=tld" test "$(ynh_read_var_in_file "$file" "ldap_base")" == "ou=foobar,dc=domain,dc=tld" - ynh_write_var_in_file "$file" "nonexistent" "foobar" + ! ynh_write_var_in_file "$file" "nonexistent" "foobar" test "$(ynh_read_var_in_file "$file" "nonexistent")" == "YNH_NULL" - ynh_write_var_in_file "$file" "enable" "foobar" + ! ynh_write_var_in_file "$file" "enable" "foobar" test "$(ynh_read_var_in_file "$file" "enable")" == "YNH_NULL" - #test "$(ynh_read_var_in_file "$file" "enabled")" == "true" # FIXME + test "$(ynh_read_var_in_file "$file" "enabled")" == "true" } ####################### @@ -589,7 +598,7 @@ EOF } -nhtest_config_write_php() { +ynhtest_config_write_php() { local dummy_dir="$(mktemp -d -p $VAR_WWW)" file="$dummy_dir/dummy.php" @@ -610,15 +619,13 @@ nhtest_config_write_php() { ?> EOF - #ynh_write_var_in_file "$file" "foo" "bar" - #cat $file - #test "$(_read_php "$file" "foo")" == "bar" - #test "$(ynh_read_var_in_file "$file" "foo")" == "bar" # FIXME FIXME FIXME + ynh_write_var_in_file "$file" "foo" "bar" + test "$(_read_php "$file" "foo")" == "bar" + test "$(ynh_read_var_in_file "$file" "foo")" == "bar" - #ynh_write_var_in_file "$file" "enabled" "true" - #cat $file - #test "$(_read_php "$file" "enabled")" == "true" - #test "$(ynh_read_var_in_file "$file" "enabled")" == "true" # FIXME FIXME FIXME + ynh_write_var_in_file "$file" "enabled" "true" + test "$(_read_php "$file" "enabled")" == "true" + test "$(ynh_read_var_in_file "$file" "enabled")" == "true" ynh_write_var_in_file "$file" "title" "Foo Bar" cat $file @@ -635,10 +642,9 @@ EOF test "$(_read_php "$file" "email")" == "sam@domain.tld" test "$(ynh_read_var_in_file "$file" "email")" == "sam@domain.tld" - #ynh_write_var_in_file "$file" "port" "5678" - #cat $file - #test "$(_read_php "$file" "port")" == "5678" # FIXME FIXME FIXME - #test "$(ynh_read_var_in_file "$file" "port")" == "5678" + ynh_write_var_in_file "$file" "port" "5678" + test "$(_read_php "$file" "port")" == "5678" + test "$(ynh_read_var_in_file "$file" "port")" == "5678" ynh_write_var_in_file "$file" "url" "https://domain.tld/foobar" test "$(_read_php "$file" "url")" == "https://domain.tld/foobar" @@ -647,10 +653,10 @@ EOF ynh_write_var_in_file "$file" "ldap_base" "ou=foobar,dc=domain,dc=tld" test "$(ynh_read_var_in_file "$file" "ldap_base")" == "ou=foobar,dc=domain,dc=tld" - ynh_write_var_in_file "$file" "nonexistent" "foobar" + ! ynh_write_var_in_file "$file" "nonexistent" "foobar" test "$(ynh_read_var_in_file "$file" "nonexistent")" == "YNH_NULL" - ynh_write_var_in_file "$file" "enable" "foobar" + ! ynh_write_var_in_file "$file" "enable" "foobar" test "$(ynh_read_var_in_file "$file" "enable")" == "YNH_NULL" - #test "$(ynh_read_var_in_file "$file" "enabled")" == "true" # FIXME + test "$(ynh_read_var_in_file "$file" "enabled")" == "true" } From f2d0732825abee1e94440d0988e04a03a5bd745a Mon Sep 17 00:00:00 2001 From: ljf Date: Mon, 6 Sep 2021 04:28:30 +0200 Subject: [PATCH 2951/3170] [fix] Missing call to --after args --- data/helpers.d/config | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/config b/data/helpers.d/config index 62ee228d9..6f04eaa11 100644 --- a/data/helpers.d/config +++ b/data/helpers.d/config @@ -69,14 +69,20 @@ EOL # Get value from a kind of key/value file else + local bind_after="" if [[ "$bind" == "settings" ]] then bind=":/etc/yunohost/apps/$app/settings.yml" fi local bind_key="$(echo "$bind" | cut -d: -f1)" bind_key=${bind_key:-$short_setting} + if [[ "$bind_key" == *">"* ]]; + then + bind_after="$(echo "${bind_key}" | cut -d'>' -f1)" + bind_key="$(echo "${bind_key}" | cut -d'>' -f2)" + fi local bind_file="$(echo "$bind" | cut -d: -f2 | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" - old[$short_setting]="$(ynh_read_var_in_file --file="${bind_file}" --key="${bind_key}")" + old[$short_setting]="$(ynh_read_var_in_file --file="${bind_file}" --key="${bind_key}" --after="${bind_after}")" fi done @@ -154,7 +160,7 @@ _ynh_app_config_apply() { local bind_file="$(echo "$bind" | cut -d: -f2 | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" ynh_backup_if_checksum_is_different --file="$bind_file" - ynh_write_var_in_file --file="${bind_file}" --key="${bind_key}" --value="${!short_setting}" + ynh_write_var_in_file --file="${bind_file}" --key="${bind_key}" --value="${!short_setting}" --after="${bind_after}" ynh_store_file_checksum --file="$bind_file" --update_only # We stored the info in settings in order to be able to upgrade the app From 050185a0c280f615aec995613ad7735296c2510e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 6 Sep 2021 21:32:52 +0200 Subject: [PATCH 2952/3170] config panel: fix file type returning weird value --- data/helpers.d/config | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/config b/data/helpers.d/config index 6f04eaa11..4ad52e038 100644 --- a/data/helpers.d/config +++ b/data/helpers.d/config @@ -21,7 +21,7 @@ for panel_name, panel in loaded_toml.items(): print(';'.join([ name, param.get('type', 'string'), - param.get('bind', 'settings' if param.get('type', 'string') != 'file' else '') + param.get('bind', 'settings' if param.get('type', 'string') != 'file' else 'null') ])) EOL ` @@ -51,7 +51,7 @@ EOL then ynh_die "File '${short_setting}' can't be stored in settings" fi - old[$short_setting]="$(ls $(echo $bind | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/) 2> /dev/null || echo YNH_NULL)" + old[$short_setting]="$(ls "$(echo $bind | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" 2> /dev/null || echo YNH_NULL)" file_hash[$short_setting]="true" # Get multiline text from settings or from a full file From 1a64a1d39a2fbef92954c67370d913dc9c2310cf Mon Sep 17 00:00:00 2001 From: ericgaspar Date: Tue, 7 Sep 2021 08:24:03 +0200 Subject: [PATCH 2953/3170] Add passwd to be obfuscated --- src/yunohost/log.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/log.py b/src/yunohost/log.py index 3f6382af2..c71257139 100644 --- a/src/yunohost/log.py +++ b/src/yunohost/log.py @@ -427,7 +427,7 @@ class RedactingFormatter(Formatter): # (the secret part being at least 3 chars to avoid catching some lines like just "db_pwd=") # Some names like "key" or "manifest_key" are ignored, used in helpers like ynh_app_setting_set or ynh_read_manifest match = re.search( - r"(pwd|pass|password|passphrase|secret\w*|\w+key|token|PASSPHRASE)=(\S{3,})$", + r"(pwd|pass|passwd|password|passphrase|secret\w*|\w+key|token|PASSPHRASE)=(\S{3,})$", record.strip(), ) if ( From 6cfefd4b9a1a0d4b7a36471e6db6406b3a7ef98d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 7 Sep 2021 18:31:37 +0200 Subject: [PATCH 2954/3170] Attempt to fix backup test --- 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 30204fa86..df84ee47f 100644 --- a/src/yunohost/tests/test_backuprestore.py +++ b/src/yunohost/tests/test_backuprestore.py @@ -344,7 +344,7 @@ def test_backup_script_failure_handling(monkeypatch, mocker): # Create a backup of this app and simulate a crash (patching the backup # call with monkeypatch). We also patch m18n to check later it's been called # with the expected error message key - monkeypatch.setattr("yunohost.backup.hook_exec", custom_hook_exec) + monkeypatch.setattr("yunohost.hook.hook_exec", custom_hook_exec) with message(mocker, "backup_app_failed", app="backup_recommended_app"): with raiseYunohostError(mocker, "backup_nothings_done"): From 6b3af5fa3327d7538b4ff8bdec460fd5fc1a4bd8 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 7 Sep 2021 19:39:10 +0200 Subject: [PATCH 2955/3170] Anotha shruberry --- 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 df84ee47f..8db6982df 100644 --- a/src/yunohost/tests/test_backuprestore.py +++ b/src/yunohost/tests/test_backuprestore.py @@ -469,7 +469,7 @@ def test_restore_app_script_failure_handling(monkeypatch, mocker): monkeypatch.undo() return (1, None) - monkeypatch.setattr("yunohost.backup.hook_exec", custom_hook_exec) + monkeypatch.setattr("yunohost.hook.hook_exec", custom_hook_exec) assert not _is_installed("wordpress") From b007102842f38156a1bb6d9b6c652b9f4c20cb66 Mon Sep 17 00:00:00 2001 From: ljf Date: Tue, 7 Sep 2021 20:09:27 +0200 Subject: [PATCH 2956/3170] [fix] Question tests --- src/yunohost/tests/conftest.py | 7 +- src/yunohost/tests/test_questions.py | 432 +++++++++++++++++---------- src/yunohost/utils/config.py | 91 +++--- 3 files changed, 331 insertions(+), 199 deletions(-) diff --git a/src/yunohost/tests/conftest.py b/src/yunohost/tests/conftest.py index 6b4e2c3fd..8c00693c0 100644 --- a/src/yunohost/tests/conftest.py +++ b/src/yunohost/tests/conftest.py @@ -84,9 +84,12 @@ def pytest_cmdline_main(config): class DummyInterface: - type = "test" + type = "cli" - def prompt(*args, **kwargs): + def prompt(self, *args, **kwargs): raise NotImplementedError + def display(self, message, *args, **kwargs): + print(message) + Moulinette._interface = DummyInterface() diff --git a/src/yunohost/tests/test_questions.py b/src/yunohost/tests/test_questions.py index f41c3c0cd..eaaad1791 100644 --- a/src/yunohost/tests/test_questions.py +++ b/src/yunohost/tests/test_questions.py @@ -1,14 +1,19 @@ import sys import pytest +import os -from mock import patch +from mock import patch, MagicMock from io import StringIO from collections import OrderedDict from moulinette import Moulinette from yunohost import domain, user -from yunohost.utils.config import parse_args_in_yunohost_format, PasswordQuestion +from yunohost.utils.config import ( + parse_args_in_yunohost_format, + PasswordQuestion, + Question +) from yunohost.utils.error import YunohostError @@ -70,7 +75,8 @@ def test_question_string_no_input(): ] answers = {} - with pytest.raises(YunohostError): + with pytest.raises(YunohostError), \ + patch.object(os, "isatty", return_value=False): parse_args_in_yunohost_format(answers, questions) @@ -84,7 +90,8 @@ def test_question_string_input(): answers = {} expected_result = OrderedDict({"some_string": ("some_value", "string")}) - with patch.object(Moulinette.interface, "prompt", return_value="some_value"): + with patch.object(Moulinette, "prompt", return_value="some_value"), \ + patch.object(os, "isatty", return_value=True): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -97,7 +104,8 @@ def test_question_string_input_no_ask(): answers = {} expected_result = OrderedDict({"some_string": ("some_value", "string")}) - with patch.object(Moulinette.interface, "prompt", return_value="some_value"): + with patch.object(Moulinette, "prompt", return_value="some_value"), \ + patch.object(os, "isatty", return_value=True): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -110,7 +118,8 @@ def test_question_string_no_input_optional(): ] answers = {} expected_result = OrderedDict({"some_string": ("", "string")}) - assert parse_args_in_yunohost_format(answers, questions) == expected_result + with patch.object(os, "isatty", return_value=False): + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_question_string_optional_with_input(): @@ -124,7 +133,8 @@ def test_question_string_optional_with_input(): answers = {} expected_result = OrderedDict({"some_string": ("some_value", "string")}) - with patch.object(Moulinette.interface, "prompt", return_value="some_value"): + with patch.object(Moulinette, "prompt", return_value="some_value"), \ + patch.object(os, "isatty", return_value=True): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -139,7 +149,8 @@ def test_question_string_optional_with_empty_input(): answers = {} expected_result = OrderedDict({"some_string": ("", "string")}) - with patch.object(Moulinette.interface, "prompt", return_value=""): + with patch.object(Moulinette, "prompt", return_value=""), \ + patch.object(os, "isatty", return_value=True): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -153,7 +164,8 @@ def test_question_string_optional_with_input_without_ask(): answers = {} expected_result = OrderedDict({"some_string": ("some_value", "string")}) - with patch.object(Moulinette.interface, "prompt", return_value="some_value"): + with patch.object(Moulinette, "prompt", return_value="some_value"), \ + patch.object(os, "isatty", return_value=True): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -167,7 +179,8 @@ def test_question_string_no_input_default(): ] answers = {} expected_result = OrderedDict({"some_string": ("some_value", "string")}) - assert parse_args_in_yunohost_format(answers, questions) == expected_result + with patch.object(os, "isatty", return_value=False): + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_question_string_input_test_ask(): @@ -181,10 +194,13 @@ def test_question_string_input_test_ask(): answers = {} with patch.object( - Moulinette.interface, "prompt", return_value="some_value" - ) as prompt: + Moulinette, "prompt", return_value="some_value" + ) as prompt, patch.object(os, "isatty", return_value=True): parse_args_in_yunohost_format(answers, questions) - prompt.assert_called_with(ask_text, False) + prompt.assert_called_with( + message=ask_text, is_password=False, confirm=False, + prefill='', is_multiline=False + ) def test_question_string_input_test_ask_with_default(): @@ -200,10 +216,14 @@ def test_question_string_input_test_ask_with_default(): answers = {} with patch.object( - Moulinette.interface, "prompt", return_value="some_value" - ) as prompt: + Moulinette, "prompt", return_value="some_value" + ) as prompt, patch.object(os, "isatty", return_value=True): parse_args_in_yunohost_format(answers, questions) - prompt.assert_called_with("%s (default: %s)" % (ask_text, default_text), False) + prompt.assert_called_with( + message=ask_text, + is_password=False, confirm=False, + prefill=default_text, is_multiline=False + ) @pytest.mark.skip # we should do something with this example @@ -220,11 +240,11 @@ def test_question_string_input_test_ask_with_example(): answers = {} with patch.object( - Moulinette.interface, "prompt", return_value="some_value" - ) as prompt: + Moulinette, "prompt", return_value="some_value" + ) as prompt, patch.object(os, "isatty", return_value=True): parse_args_in_yunohost_format(answers, questions) - assert ask_text in prompt.call_args[0][0] - assert example_text in prompt.call_args[0][0] + assert ask_text in prompt.call_args[1]['message'] + assert example_text in prompt.call_args[1]['message'] @pytest.mark.skip # we should do something with this help @@ -241,11 +261,11 @@ def test_question_string_input_test_ask_with_help(): answers = {} with patch.object( - Moulinette.interface, "prompt", return_value="some_value" - ) as prompt: + Moulinette, "prompt", return_value="some_value" + ) as prompt, patch.object(os, "isatty", return_value=True): parse_args_in_yunohost_format(answers, questions) - assert ask_text in prompt.call_args[0][0] - assert help_text in prompt.call_args[0][0] + assert ask_text in prompt.call_args[1]['message'] + assert help_text in prompt.call_args[1]['message'] def test_question_string_with_choice(): @@ -259,7 +279,8 @@ def test_question_string_with_choice_prompt(): questions = [{"name": "some_string", "type": "string", "choices": ["fr", "en"]}] answers = {"some_string": "fr"} expected_result = OrderedDict({"some_string": ("fr", "string")}) - with patch.object(Moulinette.interface, "prompt", return_value="fr"): + with patch.object(Moulinette, "prompt", return_value="fr"), \ + patch.object(os, "isatty", return_value=True): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -267,7 +288,8 @@ def test_question_string_with_choice_bad(): questions = [{"name": "some_string", "type": "string", "choices": ["fr", "en"]}] answers = {"some_string": "bad"} - with pytest.raises(YunohostError): + with pytest.raises(YunohostError), \ + patch.object(os, "isatty", return_value=False): assert parse_args_in_yunohost_format(answers, questions) @@ -283,12 +305,13 @@ def test_question_string_with_choice_ask(): ] answers = {} - with patch.object(Moulinette.interface, "prompt", return_value="ru") as prompt: + with patch.object(Moulinette, "prompt", return_value="ru") as prompt, \ + patch.object(os, "isatty", return_value=True): parse_args_in_yunohost_format(answers, questions) - assert ask_text in prompt.call_args[0][0] + assert ask_text in prompt.call_args[1]['message'] for choice in choices: - assert choice in prompt.call_args[0][0] + assert choice in prompt.call_args[1]['message'] def test_question_string_with_choice_default(): @@ -302,7 +325,8 @@ def test_question_string_with_choice_default(): ] answers = {} expected_result = OrderedDict({"some_string": ("en", "string")}) - assert parse_args_in_yunohost_format(answers, questions) == expected_result + with patch.object(os, "isatty", return_value=False): + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_question_password(): @@ -314,7 +338,9 @@ def test_question_password(): ] answers = {"some_password": "some_value"} expected_result = OrderedDict({"some_password": ("some_value", "password")}) - assert parse_args_in_yunohost_format(answers, questions) == expected_result + Question.operation_logger = MagicMock() + with patch.object(Question.operation_logger, "data_to_redact", create=True): + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_question_password_no_input(): @@ -326,7 +352,8 @@ def test_question_password_no_input(): ] answers = {} - with pytest.raises(YunohostError): + with pytest.raises(YunohostError), \ + patch.object(os, "isatty", return_value=False): parse_args_in_yunohost_format(answers, questions) @@ -339,10 +366,14 @@ def test_question_password_input(): } ] answers = {} + Question.operation_logger = { 'data_to_redact': [] } expected_result = OrderedDict({"some_password": ("some_value", "password")}) - with patch.object(Moulinette.interface, "prompt", return_value="some_value"): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + Question.operation_logger = MagicMock() + with patch.object(Question.operation_logger, "data_to_redact", create=True), \ + patch.object(Moulinette, "prompt", return_value="some_value"), \ + patch.object(os, "isatty", return_value=True): + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_question_password_input_no_ask(): @@ -355,7 +386,10 @@ def test_question_password_input_no_ask(): answers = {} expected_result = OrderedDict({"some_password": ("some_value", "password")}) - with patch.object(Moulinette.interface, "prompt", return_value="some_value"): + Question.operation_logger = MagicMock() + with patch.object(Question.operation_logger, "data_to_redact", create=True), \ + patch.object(Moulinette, "prompt", return_value="some_value"), \ + patch.object(os, "isatty", return_value=True): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -370,13 +404,19 @@ def test_question_password_no_input_optional(): answers = {} expected_result = OrderedDict({"some_password": ("", "password")}) - assert parse_args_in_yunohost_format(answers, questions) == expected_result + Question.operation_logger = MagicMock() + with patch.object(Question.operation_logger, "data_to_redact", create=True), \ + patch.object(os, "isatty", return_value=False): + assert parse_args_in_yunohost_format(answers, questions) == expected_result questions = [ {"name": "some_password", "type": "password", "optional": True, "default": ""} ] - assert parse_args_in_yunohost_format(answers, questions) == expected_result + Question.operation_logger = MagicMock() + with patch.object(Question.operation_logger, "data_to_redact", create=True), \ + patch.object(os, "isatty", return_value=False): + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_question_password_optional_with_input(): @@ -391,7 +431,10 @@ def test_question_password_optional_with_input(): answers = {} expected_result = OrderedDict({"some_password": ("some_value", "password")}) - with patch.object(Moulinette.interface, "prompt", return_value="some_value"): + Question.operation_logger = MagicMock() + with patch.object(Question.operation_logger, "data_to_redact", create=True), \ + patch.object(Moulinette, "prompt", return_value="some_value"), \ + patch.object(os, "isatty", return_value=True): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -407,7 +450,10 @@ def test_question_password_optional_with_empty_input(): answers = {} expected_result = OrderedDict({"some_password": ("", "password")}) - with patch.object(Moulinette.interface, "prompt", return_value=""): + Question.operation_logger = MagicMock() + with patch.object(Question.operation_logger, "data_to_redact", create=True), \ + patch.object(Moulinette, "prompt", return_value=""), \ + patch.object(os, "isatty", return_value=True): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -422,7 +468,10 @@ def test_question_password_optional_with_input_without_ask(): answers = {} expected_result = OrderedDict({"some_password": ("some_value", "password")}) - with patch.object(Moulinette.interface, "prompt", return_value="some_value"): + Question.operation_logger = MagicMock() + with patch.object(Question.operation_logger, "data_to_redact", create=True), \ + patch.object(Moulinette, "prompt", return_value="some_value"), \ + patch.object(os, "isatty", return_value=True): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -438,7 +487,8 @@ def test_question_password_no_input_default(): answers = {} # no default for password! - with pytest.raises(YunohostError): + with pytest.raises(YunohostError), \ + patch.object(os, "isatty", return_value=False): parse_args_in_yunohost_format(answers, questions) @@ -455,7 +505,8 @@ def test_question_password_no_input_example(): answers = {"some_password": "some_value"} # no example for password! - with pytest.raises(YunohostError): + with pytest.raises(YunohostError), \ + patch.object(os, "isatty", return_value=False): parse_args_in_yunohost_format(answers, questions) @@ -470,11 +521,16 @@ def test_question_password_input_test_ask(): ] answers = {} - with patch.object( - Moulinette.interface, "prompt", return_value="some_value" - ) as prompt: + Question.operation_logger = MagicMock() + with patch.object(Question.operation_logger, "data_to_redact", create=True), \ + patch.object(Moulinette, "prompt", return_value="some_value") as prompt, \ + patch.object(os, "isatty", return_value=True): parse_args_in_yunohost_format(answers, questions) - prompt.assert_called_with(ask_text, True) + prompt.assert_called_with( + message=ask_text, + is_password=True, confirm=True, + prefill='', is_multiline=False + ) @pytest.mark.skip # we should do something with this example @@ -491,12 +547,13 @@ def test_question_password_input_test_ask_with_example(): ] answers = {} - with patch.object( - Moulinette.interface, "prompt", return_value="some_value" - ) as prompt: + Question.operation_logger = MagicMock() + with patch.object(Question.operation_logger, "data_to_redact", create=True), \ + patch.object(Moulinette, "prompt", return_value="some_value") as prompt, \ + patch.object(os, "isatty", return_value=True): parse_args_in_yunohost_format(answers, questions) - assert ask_text in prompt.call_args[0][0] - assert example_text in prompt.call_args[0][0] + assert ask_text in prompt.call_args[1]['message'] + assert example_text in prompt.call_args[1]['message'] @pytest.mark.skip # we should do something with this help @@ -513,12 +570,13 @@ def test_question_password_input_test_ask_with_help(): ] answers = {} - with patch.object( - Moulinette.interface, "prompt", return_value="some_value" - ) as prompt: + Question.operation_logger = MagicMock() + with patch.object(Question.operation_logger, "data_to_redact", create=True), \ + patch.object(Moulinette, "prompt", return_value="some_value") as prompt, \ + patch.object(os, "isatty", return_value=True): parse_args_in_yunohost_format(answers, questions) - assert ask_text in prompt.call_args[0][0] - assert help_text in prompt.call_args[0][0] + assert ask_text in prompt.call_args[1]['message'] + assert help_text in prompt.call_args[1]['message'] def test_question_password_bad_chars(): @@ -532,7 +590,8 @@ def test_question_password_bad_chars(): ] for i in PasswordQuestion.forbidden_chars: - with pytest.raises(YunohostError): + with pytest.raises(YunohostError), \ + patch.object(os, "isatty", return_value=False): parse_args_in_yunohost_format({"some_password": i * 8}, questions) @@ -546,11 +605,13 @@ def test_question_password_strong_enough(): } ] - with pytest.raises(YunohostError): + with pytest.raises(YunohostError), \ + patch.object(os, "isatty", return_value=False): # too short parse_args_in_yunohost_format({"some_password": "a"}, questions) - with pytest.raises(YunohostError): + with pytest.raises(YunohostError), \ + patch.object(os, "isatty", return_value=False): parse_args_in_yunohost_format({"some_password": "password"}, questions) @@ -564,11 +625,13 @@ def test_question_password_optional_strong_enough(): } ] - with pytest.raises(YunohostError): + with pytest.raises(YunohostError), \ + patch.object(os, "isatty", return_value=False): # too short parse_args_in_yunohost_format({"some_password": "a"}, questions) - with pytest.raises(YunohostError): + with pytest.raises(YunohostError), \ + patch.object(os, "isatty", return_value=False): parse_args_in_yunohost_format({"some_password": "password"}, questions) @@ -593,7 +656,8 @@ def test_question_path_no_input(): ] answers = {} - with pytest.raises(YunohostError): + with pytest.raises(YunohostError), \ + patch.object(os, "isatty", return_value=False): parse_args_in_yunohost_format(answers, questions) @@ -608,7 +672,8 @@ def test_question_path_input(): answers = {} expected_result = OrderedDict({"some_path": ("some_value", "path")}) - with patch.object(Moulinette.interface, "prompt", return_value="some_value"): + with patch.object(Moulinette, "prompt", return_value="some_value"), \ + patch.object(os, "isatty", return_value=True): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -622,7 +687,8 @@ def test_question_path_input_no_ask(): answers = {} expected_result = OrderedDict({"some_path": ("some_value", "path")}) - with patch.object(Moulinette.interface, "prompt", return_value="some_value"): + with patch.object(Moulinette, "prompt", return_value="some_value"), \ + patch.object(os, "isatty", return_value=True): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -636,7 +702,8 @@ def test_question_path_no_input_optional(): ] answers = {} expected_result = OrderedDict({"some_path": ("", "path")}) - assert parse_args_in_yunohost_format(answers, questions) == expected_result + with patch.object(os, "isatty", return_value=False): + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_question_path_optional_with_input(): @@ -651,7 +718,8 @@ def test_question_path_optional_with_input(): answers = {} expected_result = OrderedDict({"some_path": ("some_value", "path")}) - with patch.object(Moulinette.interface, "prompt", return_value="some_value"): + with patch.object(Moulinette, "prompt", return_value="some_value"), \ + patch.object(os, "isatty", return_value=True): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -667,7 +735,8 @@ def test_question_path_optional_with_empty_input(): answers = {} expected_result = OrderedDict({"some_path": ("", "path")}) - with patch.object(Moulinette.interface, "prompt", return_value=""): + with patch.object(Moulinette, "prompt", return_value=""), \ + patch.object(os, "isatty", return_value=True): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -682,7 +751,8 @@ def test_question_path_optional_with_input_without_ask(): answers = {} expected_result = OrderedDict({"some_path": ("some_value", "path")}) - with patch.object(Moulinette.interface, "prompt", return_value="some_value"): + with patch.object(Moulinette, "prompt", return_value="some_value"), \ + patch.object(os, "isatty", return_value=True): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -697,7 +767,8 @@ def test_question_path_no_input_default(): ] answers = {} expected_result = OrderedDict({"some_path": ("some_value", "path")}) - assert parse_args_in_yunohost_format(answers, questions) == expected_result + with patch.object(os, "isatty", return_value=False): + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_question_path_input_test_ask(): @@ -712,10 +783,14 @@ def test_question_path_input_test_ask(): answers = {} with patch.object( - Moulinette.interface, "prompt", return_value="some_value" - ) as prompt: + Moulinette, "prompt", return_value="some_value" + ) as prompt, patch.object(os, "isatty", return_value=True): parse_args_in_yunohost_format(answers, questions) - prompt.assert_called_with(ask_text, False) + prompt.assert_called_with( + message=ask_text, + is_password=False, confirm=False, + prefill='', is_multiline=False + ) def test_question_path_input_test_ask_with_default(): @@ -732,10 +807,14 @@ def test_question_path_input_test_ask_with_default(): answers = {} with patch.object( - Moulinette.interface, "prompt", return_value="some_value" - ) as prompt: + Moulinette, "prompt", return_value="some_value" + ) as prompt, patch.object(os, "isatty", return_value=True): parse_args_in_yunohost_format(answers, questions) - prompt.assert_called_with("%s (default: %s)" % (ask_text, default_text), False) + prompt.assert_called_with( + message=ask_text, + is_password=False, confirm=False, + prefill=default_text, is_multiline=False + ) @pytest.mark.skip # we should do something with this example @@ -753,11 +832,11 @@ def test_question_path_input_test_ask_with_example(): answers = {} with patch.object( - Moulinette.interface, "prompt", return_value="some_value" - ) as prompt: + Moulinette, "prompt", return_value="some_value" + ) as prompt, patch.object(os, "isatty", return_value=True): parse_args_in_yunohost_format(answers, questions) - assert ask_text in prompt.call_args[0][0] - assert example_text in prompt.call_args[0][0] + assert ask_text in prompt.call_args[1]['message'] + assert example_text in prompt.call_args[1]['message'] @pytest.mark.skip # we should do something with this help @@ -775,11 +854,11 @@ def test_question_path_input_test_ask_with_help(): answers = {} with patch.object( - Moulinette.interface, "prompt", return_value="some_value" - ) as prompt: + Moulinette, "prompt", return_value="some_value" + ) as prompt, patch.object(os, "isatty", return_value=True): parse_args_in_yunohost_format(answers, questions) - assert ask_text in prompt.call_args[0][0] - assert help_text in prompt.call_args[0][0] + assert ask_text in prompt.call_args[1]['message'] + assert help_text in prompt.call_args[1]['message'] def test_question_boolean(): @@ -913,7 +992,8 @@ def test_question_boolean_no_input(): answers = {} expected_result = OrderedDict({"some_boolean": (0, "boolean")}) - assert parse_args_in_yunohost_format(answers, questions) == expected_result + with patch.object(os, "isatty", return_value=False): + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_question_boolean_bad_input(): @@ -925,7 +1005,8 @@ def test_question_boolean_bad_input(): ] answers = {"some_boolean": "stuff"} - with pytest.raises(YunohostError): + with pytest.raises(YunohostError), \ + patch.object(os, "isatty", return_value=False): parse_args_in_yunohost_format(answers, questions) @@ -940,11 +1021,13 @@ def test_question_boolean_input(): answers = {} expected_result = OrderedDict({"some_boolean": (1, "boolean")}) - with patch.object(Moulinette.interface, "prompt", return_value="y"): + with patch.object(Moulinette, "prompt", return_value="y"), \ + patch.object(os, "isatty", return_value=True): assert parse_args_in_yunohost_format(answers, questions) == expected_result expected_result = OrderedDict({"some_boolean": (0, "boolean")}) - with patch.object(Moulinette.interface, "prompt", return_value="n"): + with patch.object(Moulinette, "prompt", return_value="n"), \ + patch.object(os, "isatty", return_value=True): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -958,7 +1041,8 @@ def test_question_boolean_input_no_ask(): answers = {} expected_result = OrderedDict({"some_boolean": (1, "boolean")}) - with patch.object(Moulinette.interface, "prompt", return_value="y"): + with patch.object(Moulinette, "prompt", return_value="y"), \ + patch.object(os, "isatty", return_value=True): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -972,7 +1056,8 @@ def test_question_boolean_no_input_optional(): ] answers = {} expected_result = OrderedDict({"some_boolean": (0, "boolean")}) # default to false - assert parse_args_in_yunohost_format(answers, questions) == expected_result + with patch.object(os, "isatty", return_value=False): + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_question_boolean_optional_with_input(): @@ -987,7 +1072,8 @@ def test_question_boolean_optional_with_input(): answers = {} expected_result = OrderedDict({"some_boolean": (1, "boolean")}) - with patch.object(Moulinette.interface, "prompt", return_value="y"): + with patch.object(Moulinette, "prompt", return_value="y"), \ + patch.object(os, "isatty", return_value=True): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -1003,7 +1089,8 @@ def test_question_boolean_optional_with_empty_input(): answers = {} expected_result = OrderedDict({"some_boolean": (0, "boolean")}) # default to false - with patch.object(Moulinette.interface, "prompt", return_value=""): + with patch.object(Moulinette, "prompt", return_value=""), \ + patch.object(os, "isatty", return_value=True): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -1018,7 +1105,8 @@ def test_question_boolean_optional_with_input_without_ask(): answers = {} expected_result = OrderedDict({"some_boolean": (0, "boolean")}) - with patch.object(Moulinette.interface, "prompt", return_value="n"): + with patch.object(Moulinette, "prompt", return_value="n"), \ + patch.object(os, "isatty", return_value=True): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -1033,7 +1121,8 @@ def test_question_boolean_no_input_default(): ] answers = {} expected_result = OrderedDict({"some_boolean": (0, "boolean")}) - assert parse_args_in_yunohost_format(answers, questions) == expected_result + with patch.object(os, "isatty", return_value=False): + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_question_boolean_bad_default(): @@ -1061,9 +1150,14 @@ def test_question_boolean_input_test_ask(): ] answers = {} - with patch.object(Moulinette.interface, "prompt", return_value=0) as prompt: + with patch.object(Moulinette, "prompt", return_value=0) as prompt, \ + patch.object(os, "isatty", return_value=True): parse_args_in_yunohost_format(answers, questions) - prompt.assert_called_with(ask_text + " [yes | no] (default: no)", False) + prompt.assert_called_with( + message=ask_text + " [yes | no]", + is_password=False, confirm=False, + prefill='no', is_multiline=False + ) def test_question_boolean_input_test_ask_with_default(): @@ -1079,9 +1173,14 @@ def test_question_boolean_input_test_ask_with_default(): ] answers = {} - with patch.object(Moulinette.interface, "prompt", return_value=1) as prompt: + with patch.object(Moulinette, "prompt", return_value=1) as prompt, \ + patch.object(os, "isatty", return_value=True): parse_args_in_yunohost_format(answers, questions) - prompt.assert_called_with("%s [yes | no] (default: yes)" % ask_text, False) + prompt.assert_called_with( + message=ask_text + " [yes | no]", + is_password=False, confirm=False, + prefill='yes', is_multiline=False + ) def test_question_domain_empty(): @@ -1095,9 +1194,9 @@ def test_question_domain_empty(): expected_result = OrderedDict({"some_domain": (main_domain, "domain")}) answers = {} - with patch.object( - domain, "_get_maindomain", return_value="my_main_domain.com" - ), patch.object(domain, "domain_list", return_value={"domains": [main_domain]}): + with patch.object(domain, "_get_maindomain", return_value="my_main_domain.com"),\ + patch.object(domain, "domain_list", return_value={"domains": [main_domain]}), \ + patch.object(os, "isatty", return_value=False): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -1115,7 +1214,7 @@ def test_question_domain(): expected_result = OrderedDict({"some_domain": (main_domain, "domain")}) with patch.object( - domain, "_get_maindomain", return_value=main_domain + domain, "_get_maindomain", return_value=main_domain ), patch.object(domain, "domain_list", return_value={"domains": domains}): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -1135,7 +1234,7 @@ def test_question_domain_two_domains(): expected_result = OrderedDict({"some_domain": (other_domain, "domain")}) with patch.object( - domain, "_get_maindomain", return_value=main_domain + domain, "_get_maindomain", return_value=main_domain ), patch.object(domain, "domain_list", return_value={"domains": domains}): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -1143,7 +1242,7 @@ def test_question_domain_two_domains(): expected_result = OrderedDict({"some_domain": (main_domain, "domain")}) with patch.object( - domain, "_get_maindomain", return_value=main_domain + domain, "_get_maindomain", return_value=main_domain ), patch.object(domain, "domain_list", return_value={"domains": domains}): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -1162,9 +1261,10 @@ def test_question_domain_two_domains_wrong_answer(): answers = {"some_domain": "doesnt_exist.pouet"} with patch.object( - domain, "_get_maindomain", return_value=main_domain + domain, "_get_maindomain", return_value=main_domain ), patch.object(domain, "domain_list", return_value={"domains": domains}): - with pytest.raises(YunohostError): + with pytest.raises(YunohostError), \ + patch.object(os, "isatty", return_value=False): parse_args_in_yunohost_format(answers, questions) @@ -1183,8 +1283,9 @@ def test_question_domain_two_domains_default_no_ask(): expected_result = OrderedDict({"some_domain": (main_domain, "domain")}) with patch.object( - domain, "_get_maindomain", return_value=main_domain - ), patch.object(domain, "domain_list", return_value={"domains": domains}): + domain, "_get_maindomain", return_value=main_domain + ), patch.object(domain, "domain_list", return_value={"domains": domains}), \ + patch.object(os, "isatty", return_value=False): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -1198,8 +1299,9 @@ def test_question_domain_two_domains_default(): expected_result = OrderedDict({"some_domain": (main_domain, "domain")}) with patch.object( - domain, "_get_maindomain", return_value=main_domain - ), patch.object(domain, "domain_list", return_value={"domains": domains}): + domain, "_get_maindomain", return_value=main_domain + ), patch.object(domain, "domain_list", return_value={"domains": domains}), \ + patch.object(os, "isatty", return_value=False): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -1212,14 +1314,15 @@ def test_question_domain_two_domains_default_input(): answers = {} with patch.object( - domain, "_get_maindomain", return_value=main_domain - ), patch.object(domain, "domain_list", return_value={"domains": domains}): + domain, "_get_maindomain", return_value=main_domain + ), patch.object(domain, "domain_list", return_value={"domains": domains}), \ + patch.object(os, "isatty", return_value=True): expected_result = OrderedDict({"some_domain": (main_domain, "domain")}) - with patch.object(Moulinette.interface, "prompt", return_value=main_domain): + with patch.object(Moulinette, "prompt", return_value=main_domain): assert parse_args_in_yunohost_format(answers, questions) == expected_result expected_result = OrderedDict({"some_domain": (other_domain, "domain")}) - with patch.object(Moulinette.interface, "prompt", return_value=other_domain): + with patch.object(Moulinette, "prompt", return_value=other_domain): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -1243,7 +1346,8 @@ def test_question_user_empty(): answers = {} with patch.object(user, "user_list", return_value={"users": users}): - with pytest.raises(YunohostError): + with pytest.raises(YunohostError), \ + patch.object(os, "isatty", return_value=False): parse_args_in_yunohost_format(answers, questions) @@ -1269,8 +1373,8 @@ def test_question_user(): expected_result = OrderedDict({"some_user": (username, "user")}) - with patch.object(user, "user_list", return_value={"users": users}): - with patch.object(user, "user_info", return_value={}): + with patch.object(user, "user_list", return_value={"users": users}), \ + patch.object(user, "user_info", return_value={}): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -1303,15 +1407,15 @@ def test_question_user_two_users(): answers = {"some_user": other_user} expected_result = OrderedDict({"some_user": (other_user, "user")}) - with patch.object(user, "user_list", return_value={"users": users}): - with patch.object(user, "user_info", return_value={}): + with patch.object(user, "user_list", return_value={"users": users}), \ + patch.object(user, "user_info", return_value={}): assert parse_args_in_yunohost_format(answers, questions) == expected_result answers = {"some_user": username} expected_result = OrderedDict({"some_user": (username, "user")}) - with patch.object(user, "user_list", return_value={"users": users}): - with patch.object(user, "user_info", return_value={}): + with patch.object(user, "user_list", return_value={"users": users}), \ + patch.object(user, "user_info", return_value={}): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -1344,7 +1448,8 @@ def test_question_user_two_users_wrong_answer(): answers = {"some_user": "doesnt_exist.pouet"} with patch.object(user, "user_list", return_value={"users": users}): - with pytest.raises(YunohostError): + with pytest.raises(YunohostError), \ + patch.object(os, "isatty", return_value=False): parse_args_in_yunohost_format(answers, questions) @@ -1372,7 +1477,8 @@ def test_question_user_two_users_no_default(): answers = {} with patch.object(user, "user_list", return_value={"users": users}): - with pytest.raises(YunohostError): + with pytest.raises(YunohostError), \ + patch.object(os, "isatty", return_value=False): parse_args_in_yunohost_format(answers, questions) @@ -1399,17 +1505,18 @@ def test_question_user_two_users_default_input(): questions = [{"name": "some_user", "type": "user", "ask": "choose a user"}] answers = {} - with patch.object(user, "user_list", return_value={"users": users}): + with patch.object(user, "user_list", return_value={"users": users}), \ + patch.object(os, "isatty", return_value=True): with patch.object(user, "user_info", return_value={}): expected_result = OrderedDict({"some_user": (username, "user")}) - with patch.object(Moulinette.interface, "prompt", return_value=username): + with patch.object(Moulinette, "prompt", return_value=username): assert ( parse_args_in_yunohost_format(answers, questions) == expected_result ) expected_result = OrderedDict({"some_user": (other_user, "user")}) - with patch.object(Moulinette.interface, "prompt", return_value=other_user): + with patch.object(Moulinette, "prompt", return_value=other_user): assert ( parse_args_in_yunohost_format(answers, questions) == expected_result @@ -1437,8 +1544,9 @@ def test_question_number_no_input(): ] answers = {} - expected_result = OrderedDict({"some_number": (0, "number")}) - assert parse_args_in_yunohost_format(answers, questions) == expected_result + with pytest.raises(YunohostError), \ + patch.object(os, "isatty", return_value=False): + parse_args_in_yunohost_format(answers, questions) def test_question_number_bad_input(): @@ -1450,11 +1558,13 @@ def test_question_number_bad_input(): ] answers = {"some_number": "stuff"} - with pytest.raises(YunohostError): + with pytest.raises(YunohostError), \ + patch.object(os, "isatty", return_value=False): parse_args_in_yunohost_format(answers, questions) answers = {"some_number": 1.5} - with pytest.raises(YunohostError): + with pytest.raises(YunohostError), \ + patch.object(os, "isatty", return_value=False): parse_args_in_yunohost_format(answers, questions) @@ -1469,14 +1579,17 @@ def test_question_number_input(): answers = {} expected_result = OrderedDict({"some_number": (1337, "number")}) - with patch.object(Moulinette.interface, "prompt", return_value="1337"): + with patch.object(Moulinette, "prompt", return_value="1337"), \ + patch.object(os, "isatty", return_value=True): assert parse_args_in_yunohost_format(answers, questions) == expected_result - with patch.object(Moulinette.interface, "prompt", return_value=1337): + with patch.object(Moulinette, "prompt", return_value=1337), \ + patch.object(os, "isatty", return_value=True): assert parse_args_in_yunohost_format(answers, questions) == expected_result expected_result = OrderedDict({"some_number": (0, "number")}) - with patch.object(Moulinette.interface, "prompt", return_value=""): + with patch.object(Moulinette, "prompt", return_value="0"), \ + patch.object(os, "isatty", return_value=True): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -1490,7 +1603,8 @@ def test_question_number_input_no_ask(): answers = {} expected_result = OrderedDict({"some_number": (1337, "number")}) - with patch.object(Moulinette.interface, "prompt", return_value="1337"): + with patch.object(Moulinette, "prompt", return_value="1337"), \ + patch.object(os, "isatty", return_value=True): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -1503,8 +1617,9 @@ def test_question_number_no_input_optional(): } ] answers = {} - expected_result = OrderedDict({"some_number": (0, "number")}) # default to 0 - assert parse_args_in_yunohost_format(answers, questions) == expected_result + expected_result = OrderedDict({"some_number": (None, "number")}) # default to 0 + with patch.object(os, "isatty", return_value=False): + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_question_number_optional_with_input(): @@ -1519,7 +1634,8 @@ def test_question_number_optional_with_input(): answers = {} expected_result = OrderedDict({"some_number": (1337, "number")}) - with patch.object(Moulinette.interface, "prompt", return_value="1337"): + with patch.object(Moulinette, "prompt", return_value="1337"), \ + patch.object(os, "isatty", return_value=True): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -1534,7 +1650,8 @@ def test_question_number_optional_with_input_without_ask(): answers = {} expected_result = OrderedDict({"some_number": (0, "number")}) - with patch.object(Moulinette.interface, "prompt", return_value="0"): + with patch.object(Moulinette, "prompt", return_value="0"), \ + patch.object(os, "isatty", return_value=True): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -1549,7 +1666,8 @@ def test_question_number_no_input_default(): ] answers = {} expected_result = OrderedDict({"some_number": (1337, "number")}) - assert parse_args_in_yunohost_format(answers, questions) == expected_result + with patch.object(os, "isatty", return_value=False): + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_question_number_bad_default(): @@ -1562,7 +1680,8 @@ def test_question_number_bad_default(): } ] answers = {} - with pytest.raises(YunohostError): + with pytest.raises(YunohostError), \ + patch.object(os, "isatty", return_value=False): parse_args_in_yunohost_format(answers, questions) @@ -1577,9 +1696,14 @@ def test_question_number_input_test_ask(): ] answers = {} - with patch.object(Moulinette.interface, "prompt", return_value="1111") as prompt: + with patch.object(Moulinette, "prompt", return_value="1111") as prompt, \ + patch.object(os, "isatty", return_value=True): parse_args_in_yunohost_format(answers, questions) - prompt.assert_called_with("%s (default: 0)" % (ask_text), False) + prompt.assert_called_with( + message=ask_text, + is_password=False, confirm=False, + prefill='', is_multiline=False + ) def test_question_number_input_test_ask_with_default(): @@ -1595,9 +1719,14 @@ def test_question_number_input_test_ask_with_default(): ] answers = {} - with patch.object(Moulinette.interface, "prompt", return_value="1111") as prompt: + with patch.object(Moulinette, "prompt", return_value="1111") as prompt, \ + patch.object(os, "isatty", return_value=True): parse_args_in_yunohost_format(answers, questions) - prompt.assert_called_with("%s (default: %s)" % (ask_text, default_value), False) + prompt.assert_called_with( + message=ask_text, + is_password=False, confirm=False, + prefill=str(default_value), is_multiline=False + ) @pytest.mark.skip # we should do something with this example @@ -1614,10 +1743,11 @@ def test_question_number_input_test_ask_with_example(): ] answers = {} - with patch.object(Moulinette.interface, "prompt", return_value="1111") as prompt: + with patch.object(Moulinette, "prompt", return_value="1111") as prompt, \ + patch.object(os, "isatty", return_value=True): parse_args_in_yunohost_format(answers, questions) - assert ask_text in prompt.call_args[0][0] - assert example_value in prompt.call_args[0][0] + assert ask_text in prompt.call_args[1]['message'] + assert example_value in prompt.call_args[1]['message'] @pytest.mark.skip # we should do something with this help @@ -1634,16 +1764,18 @@ def test_question_number_input_test_ask_with_help(): ] answers = {} - with patch.object(Moulinette.interface, "prompt", return_value="1111") as prompt: + with patch.object(Moulinette, "prompt", return_value="1111") as prompt, \ + patch.object(os, "isatty", return_value=True): parse_args_in_yunohost_format(answers, questions) - assert ask_text in prompt.call_args[0][0] - assert help_value in prompt.call_args[0][0] + assert ask_text in prompt.call_args[1]['message'] + assert help_value in prompt.call_args[1]['message'] def test_question_display_text(): questions = [{"name": "some_app", "type": "display_text", "ask": "foobar"}] answers = {} - with patch.object(sys, "stdout", new_callable=StringIO) as stdout: + with patch.object(sys, "stdout", new_callable=StringIO) as stdout, \ + patch.object(os, "isatty", return_value=True): parse_args_in_yunohost_format(answers, questions) assert "foobar" in stdout.getvalue() diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 6b491386f..6d3c322f2 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -403,9 +403,9 @@ class Question(object): return value def ask_if_needed(self): - while True: + for i in range(5): # Display question if no value filled or if it's a readonly message - if Moulinette.interface.type == "cli": + if Moulinette.interface.type == "cli" and os.isatty(1): text_for_user_input_in_cli = self._format_text_for_user_input_in_cli() if getattr(self, "readonly", False): Moulinette.display(text_for_user_input_in_cli) @@ -415,7 +415,7 @@ class Question(object): if self.current_value is not None: prefill = self.humanize(self.current_value, self) elif self.default is not None: - prefill = self.default + prefill = self.humanize(self.default, self) self.value = Moulinette.prompt( message=text_for_user_input_in_cli, is_password=self.hide_user_input_in_prompt, @@ -424,27 +424,33 @@ class Question(object): is_multiline=(self.type == "text"), ) - # Normalization - # This is done to enforce a certain formating like for boolean - self.value = self.normalize(self.value, self) - # Apply default value - if self.value in [None, ""] and self.default is not None: + class_default= getattr(self, "default_value", None) + if self.value in [None, ""] and \ + (self.default is not None or class_default is not None): self.value = ( - getattr(self, "default_value", None) + class_default if self.default is None else self.default ) + # Normalization + # This is done to enforce a certain formating like for boolean + self.value = self.normalize(self.value, self) + # Prevalidation try: self._prevalidate() except YunohostValidationError as e: - if Moulinette.interface.type == "api": - raise - Moulinette.display(str(e), "error") - self.value = None - continue + # If in interactive cli, re-ask the current question + if i < 4 and Moulinette.interface.type == "cli" and os.isatty(1): + logger.error(str(e)) + self.value = None + continue + + # Otherwise raise the ValidationError + raise + break self.value = self._post_parse_value() @@ -561,7 +567,7 @@ class PasswordQuestion(Question): def _prevalidate(self): super()._prevalidate() - if self.value is not None: + if self.value not in [None, ""]: if any(char in self.value for char in self.forbidden_chars): raise YunohostValidationError( "pattern_password_app", forbidden_chars=self.forbidden_chars @@ -580,7 +586,7 @@ class PathQuestion(Question): class BooleanQuestion(Question): argument_type = "boolean" - default_value = False + default_value = 0 yes_answers = ["1", "yes", "y", "true", "t", "on"] no_answers = ["0", "no", "n", "false", "f", "off"] @@ -633,17 +639,13 @@ class BooleanQuestion(Question): self.yes = question.get("yes", 1) self.no = question.get("no", 0) if self.default is None: - self.default = False + self.default = self.no def _format_text_for_user_input_in_cli(self): - text_for_user_input_in_cli = _value_for_locale(self.ask) + text_for_user_input_in_cli = super()._format_text_for_user_input_in_cli() text_for_user_input_in_cli += " [yes | no]" - if self.default is not None: - formatted_default = self.humanize(self.default) - text_for_user_input_in_cli += " (default: {0})".format(formatted_default) - return text_for_user_input_in_cli def get(self, key, default=None): @@ -698,11 +700,7 @@ class UserQuestion(Question): class NumberQuestion(Question): argument_type = "number" - default_value = "" - - @staticmethod - def humanize(value, option={}): - return str(value) + default_value = None def __init__(self, question, user_answers): super().__init__(question, user_answers) @@ -710,16 +708,25 @@ class NumberQuestion(Question): self.max = question.get("max", None) self.step = question.get("step", None) + @staticmethod + def normalize(value, option={}): + if isinstance(value, int): + return value + + if isinstance(value, str) and value.isdigit(): + return int(value) + + if value in [None, ""]: + return value + + raise YunohostValidationError( + "app_argument_invalid", name=option.name, error=m18n.n("invalid_number") + ) + def _prevalidate(self): super()._prevalidate() - if not isinstance(self.value, int) and not ( - isinstance(self.value, str) and self.value.isdigit() - ): - raise YunohostValidationError( - "app_argument_invalid", - name=self.name, - error=m18n.n("invalid_number"), - ) + if self.value in [None, ""]: + return if self.min is not None and int(self.value) < self.min: raise YunohostValidationError( @@ -735,16 +742,6 @@ class NumberQuestion(Question): error=m18n.n("invalid_number"), ) - def _post_parse_value(self): - if isinstance(self.value, int): - return super()._post_parse_value() - - if isinstance(self.value, str) and self.value.isdigit(): - return int(self.value) - - raise YunohostValidationError( - "app_argument_invalid", name=self.name, error=m18n.n("invalid_number") - ) class DisplayTextQuestion(Question): @@ -755,10 +752,10 @@ class DisplayTextQuestion(Question): super().__init__(question, user_answers) self.optional = True - self.style = question.get("style", "info") + self.style = question.get("style", "info" if question['type'] == 'alert' else '') def _format_text_for_user_input_in_cli(self): - text = self.ask["en"] + text = _value_for_locale(self.ask) if self.style in ["success", "info", "warning", "danger"]: color = { From d8c496194437069664539fca77d6a435167e5e6e Mon Sep 17 00:00:00 2001 From: ericgaspar Date: Mon, 9 Aug 2021 12:31:06 +0200 Subject: [PATCH 2957/3170] Update my.cnf --- data/templates/mysql/my.cnf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/templates/mysql/my.cnf b/data/templates/mysql/my.cnf index de13b467d..429596cf5 100644 --- a/data/templates/mysql/my.cnf +++ b/data/templates/mysql/my.cnf @@ -30,7 +30,7 @@ skip-external-locking key_buffer_size = 16K max_allowed_packet = 16M table_open_cache = 4 -sort_buffer_size = 64K +sort_buffer_size = 256K read_buffer_size = 256K read_rnd_buffer_size = 256K net_buffer_length = 2K From 1730f84b8c6e0ef43c963c06de43e95ca0dc2cd8 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 7 Sep 2021 23:23:46 +0200 Subject: [PATCH 2958/3170] Update changelog for 4.2.8.2 --- debian/changelog | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/debian/changelog b/debian/changelog index 48f3bbdca..01c897a51 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,11 @@ +yunohost (4.2.8.2) stable; urgency=low + + - [fix] mysql: Bump sort_buffer_size to 256K to fix Nextcloud 22 installation (d8c49619) + + Thanks to all contributors <3 ! (ericg) + + -- Alexandre Aubin Tue, 07 Sep 2021 23:23:18 +0200 + yunohost (4.2.8.1) stable; urgency=low - [fix] Safer location for slapd backup during hdb/mdb migration (3c646b3d) From 0844c747646a7427663fc71144ae3e6ec71ba4b0 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 7 Sep 2021 23:47:06 +0200 Subject: [PATCH 2959/3170] =?UTF-8?q?Anotha=20shruberry=C2=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 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 8db6982df..b24d3442d 100644 --- a/src/yunohost/tests/test_backuprestore.py +++ b/src/yunohost/tests/test_backuprestore.py @@ -344,7 +344,7 @@ def test_backup_script_failure_handling(monkeypatch, mocker): # Create a backup of this app and simulate a crash (patching the backup # call with monkeypatch). We also patch m18n to check later it's been called # with the expected error message key - monkeypatch.setattr("yunohost.hook.hook_exec", custom_hook_exec) + monkeypatch.setattr("yunohost.backup.hook_exec", custom_hook_exec) with message(mocker, "backup_app_failed", app="backup_recommended_app"): with raiseYunohostError(mocker, "backup_nothings_done"): From 0727224c9d2dd4b90943753ab85a90d70201fe03 Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Wed, 8 Sep 2021 10:45:43 +0200 Subject: [PATCH 2960/3170] [fix] sort_buffer_size too small to make nextcloud work --- data/templates/mysql/my.cnf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/templates/mysql/my.cnf b/data/templates/mysql/my.cnf index 429596cf5..3da4377e1 100644 --- a/data/templates/mysql/my.cnf +++ b/data/templates/mysql/my.cnf @@ -30,7 +30,7 @@ skip-external-locking key_buffer_size = 16K max_allowed_packet = 16M table_open_cache = 4 -sort_buffer_size = 256K +sort_buffer_size = 4M read_buffer_size = 256K read_rnd_buffer_size = 256K net_buffer_length = 2K From 88063dc7db32a30c83bdbf62ae864e0aa4c10e38 Mon Sep 17 00:00:00 2001 From: ljf Date: Wed, 3 Mar 2021 18:29:32 +0100 Subject: [PATCH 2961/3170] [fix] Add backup for multimedia files --- data/hooks/backup/18-data_multimedia | 17 +++++++++++++++++ data/hooks/restore/18-data_multimedia | 9 +++++++++ 2 files changed, 26 insertions(+) create mode 100644 data/hooks/backup/18-data_multimedia create mode 100644 data/hooks/restore/18-data_multimedia diff --git a/data/hooks/backup/18-data_multimedia b/data/hooks/backup/18-data_multimedia new file mode 100644 index 000000000..f80cff0b3 --- /dev/null +++ b/data/hooks/backup/18-data_multimedia @@ -0,0 +1,17 @@ +#!/bin/bash + +# Exit hook on subcommand error or unset variable +set -eu + +# Source YNH helpers +source /usr/share/yunohost/helpers + +# Backup destination +backup_dir="${1}/data/multimedia" + +if [ -e "/home/yunohost.multimedia/.nobackup" ]; then + exit 0 +fi + +# Backup multimedia directory +ynh_backup --src_path="/home/yunohost.multimedia" --dest_path="${backup_dir}" --is_big --not_mandatory diff --git a/data/hooks/restore/18-data_multimedia b/data/hooks/restore/18-data_multimedia new file mode 100644 index 000000000..eb8ef2608 --- /dev/null +++ b/data/hooks/restore/18-data_multimedia @@ -0,0 +1,9 @@ +#!/bin/bash + +# Exit hook on subcommand error or unset variable +set -eu + +# Source YNH helpers +source /usr/share/yunohost/helpers + +ynh_restore_file --origin_path="/home/yunohost.multimedia" --not_mandatory From 85b7239a4b662a40386f139b381feb023f59b62c Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Wed, 8 Sep 2021 15:42:45 +0200 Subject: [PATCH 2962/3170] [enh] Use --message Co-authored-by: Kayou --- data/helpers.d/config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/config b/data/helpers.d/config index 4ad52e038..dba79295c 100644 --- a/data/helpers.d/config +++ b/data/helpers.d/config @@ -62,7 +62,7 @@ EOL old[$short_setting]="$(ynh_app_setting_get $app $short_setting)" elif [[ "$bind" == *":"* ]] then - ynh_die "For technical reasons, multiline text '${short_setting}' can't be stored automatically in a variable file, you have to create custom getter/setter" + ynh_die --message="For technical reasons, multiline text '${short_setting}' can't be stored automatically in a variable file, you have to create custom getter/setter" else old[$short_setting]="$(cat $(echo $bind | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/) 2> /dev/null || echo YNH_NULL)" fi From 8efbc736d44c68089ef9ca696a53bd91a8cfdf16 Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Wed, 8 Sep 2021 15:42:56 +0200 Subject: [PATCH 2963/3170] [enh] Use --message Co-authored-by: Kayou --- data/helpers.d/config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/config b/data/helpers.d/config index dba79295c..796016ef7 100644 --- a/data/helpers.d/config +++ b/data/helpers.d/config @@ -49,7 +49,7 @@ EOL then if [[ "$bind" == "settings" ]] then - ynh_die "File '${short_setting}' can't be stored in settings" + ynh_die --message="File '${short_setting}' can't be stored in settings" fi old[$short_setting]="$(ls "$(echo $bind | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" 2> /dev/null || echo YNH_NULL)" file_hash[$short_setting]="true" From 33505bff5f5013d8c93a09a7317158739c163ae9 Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Wed, 8 Sep 2021 15:43:15 +0200 Subject: [PATCH 2964/3170] [enh] Use --message Co-authored-by: Kayou --- data/helpers.d/config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/config b/data/helpers.d/config index 796016ef7..c1bef8f51 100644 --- a/data/helpers.d/config +++ b/data/helpers.d/config @@ -112,7 +112,7 @@ _ynh_app_config_apply() { then if [[ "$bind" == "settings" ]] then - ynh_die "File '${short_setting}' can't be stored in settings" + ynh_die --message="File '${short_setting}' can't be stored in settings" fi local bind_file="$(echo "$bind" | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" if [[ "${!short_setting}" == "" ]] From df20a540133625bbf32714537c3a5e52f623767f Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Wed, 8 Sep 2021 15:43:50 +0200 Subject: [PATCH 2965/3170] [enh] Avoid bad deletion Co-authored-by: Kayou --- data/helpers.d/config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/config b/data/helpers.d/config index c1bef8f51..4f28f95a8 100644 --- a/data/helpers.d/config +++ b/data/helpers.d/config @@ -118,7 +118,7 @@ _ynh_app_config_apply() { if [[ "${!short_setting}" == "" ]] then ynh_backup_if_checksum_is_different --file="$bind_file" - rm -f "$bind_file" + ynh_secure_remove --file="$bind_file" ynh_delete_file_checksum --file="$bind_file" --update_only ynh_print_info "File '$bind_file' removed" else From 62875c30da2fce1c91a0ed3d3425c3334187f9fd Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Wed, 8 Sep 2021 15:43:59 +0200 Subject: [PATCH 2966/3170] [enh] Use --message Co-authored-by: Kayou --- 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 5ecc0cf0b..9938da771 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -592,7 +592,7 @@ ynh_write_var_in_file() { ynh_handle_getopts_args "$@" after="${after:-}" - [[ -f $file ]] || ynh_die "File $file does not exists" + [[ -f $file ]] || ynh_die --message="File $file does not exists" # Get the line number after which we search for the variable local line_number=1 From d3603632517dddbb8417816e2b64260f3621ba2d Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Wed, 8 Sep 2021 15:44:35 +0200 Subject: [PATCH 2967/3170] [enh] Use --message Co-authored-by: Kayou --- data/helpers.d/config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/config b/data/helpers.d/config index 4f28f95a8..b1db5f362 100644 --- a/data/helpers.d/config +++ b/data/helpers.d/config @@ -120,7 +120,7 @@ _ynh_app_config_apply() { ynh_backup_if_checksum_is_different --file="$bind_file" ynh_secure_remove --file="$bind_file" ynh_delete_file_checksum --file="$bind_file" --update_only - ynh_print_info "File '$bind_file' removed" + ynh_print_info --message="File '$bind_file' removed" else ynh_backup_if_checksum_is_different --file="$bind_file" cp "${!short_setting}" "$bind_file" From a205015f0d47fb72a248adcd8d3433e27919139d Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Wed, 8 Sep 2021 15:44:47 +0200 Subject: [PATCH 2968/3170] [enh] Use --message Co-authored-by: Kayou --- data/helpers.d/config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/config b/data/helpers.d/config index b1db5f362..4da754036 100644 --- a/data/helpers.d/config +++ b/data/helpers.d/config @@ -125,7 +125,7 @@ _ynh_app_config_apply() { ynh_backup_if_checksum_is_different --file="$bind_file" cp "${!short_setting}" "$bind_file" ynh_store_file_checksum --file="$bind_file" --update_only - ynh_print_info "File '$bind_file' overwrited with ${!short_setting}" + ynh_print_info --message="File '$bind_file' overwrited with ${!short_setting}" fi # Save value in app settings From b487fbbe0069eb69bd0636df77ca9a2fcb045a24 Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Wed, 8 Sep 2021 15:45:24 +0200 Subject: [PATCH 2969/3170] [enh] Use named args in helpers calls Co-authored-by: Kayou --- data/helpers.d/config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/config b/data/helpers.d/config index 4da754036..6b77fb12e 100644 --- a/data/helpers.d/config +++ b/data/helpers.d/config @@ -131,7 +131,7 @@ _ynh_app_config_apply() { # Save value in app settings elif [[ "$bind" == "settings" ]] then - ynh_app_setting_set $app $short_setting "${!short_setting}" + ynh_app_setting_set --app=$app --key=$short_setting --value="${!short_setting}" ynh_print_info "Configuration key '$short_setting' edited in app settings" # Save multiline text in a file From f2b779c962f4c0b3b192766d8f952fede53d86c4 Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Wed, 8 Sep 2021 15:45:39 +0200 Subject: [PATCH 2970/3170] [enh] Use named args in helpers calls Co-authored-by: Kayou --- data/helpers.d/config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/config b/data/helpers.d/config index 6b77fb12e..56ad9857b 100644 --- a/data/helpers.d/config +++ b/data/helpers.d/config @@ -132,7 +132,7 @@ _ynh_app_config_apply() { elif [[ "$bind" == "settings" ]] then ynh_app_setting_set --app=$app --key=$short_setting --value="${!short_setting}" - ynh_print_info "Configuration key '$short_setting' edited in app settings" + ynh_print_info --message="Configuration key '$short_setting' edited in app settings" # Save multiline text in a file elif [[ "$type" == "text" ]] From 924df9733e1f2d4e4621e46146e618f18b04fe6c Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Wed, 8 Sep 2021 16:49:42 +0200 Subject: [PATCH 2971/3170] [enh] Use named args in helpers calls Co-authored-by: Kayou --- data/helpers.d/config | 9 ++++----- data/helpers.d/utils | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/data/helpers.d/config b/data/helpers.d/config index 56ad9857b..b9471cf51 100644 --- a/data/helpers.d/config +++ b/data/helpers.d/config @@ -145,7 +145,7 @@ _ynh_app_config_apply() { ynh_backup_if_checksum_is_different --file="$bind_file" echo "${!short_setting}" > "$bind_file" ynh_store_file_checksum --file="$bind_file" --update_only - ynh_print_info "File '$bind_file' overwrited with the content you provieded in '${short_setting}' question" + ynh_print_info --message="File '$bind_file' overwrited with the content you provieded in '${short_setting}' question" # Set value into a kind of key/value file else @@ -164,8 +164,8 @@ _ynh_app_config_apply() { ynh_store_file_checksum --file="$bind_file" --update_only # We stored the info in settings in order to be able to upgrade the app - ynh_app_setting_set $app $short_setting "${!short_setting}" - ynh_print_info "Configuration key '$bind_key' edited into $bind_file" + ynh_app_setting_set --app=$app --key=$short_setting --value="${!short_setting}" + ynh_print_info --message="Configuration key '$bind_key' edited into $bind_file" fi fi @@ -194,7 +194,6 @@ _ynh_app_config_validate() { ynh_script_progression --message="Checking what changed in the new configuration..." --weight=1 local nothing_changed=true local changes_validated=true - #for changed_status in "${!changed[@]}" for short_setting in "${!old[@]}" do changed[$short_setting]=false @@ -237,7 +236,7 @@ _ynh_app_config_validate() { done if [[ "$nothing_changed" == "true" ]] then - ynh_print_info "Nothing has changed" + ynh_print_info --message="Nothing has changed" exit 0 fi diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 9938da771..511fa52fb 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -517,7 +517,7 @@ ynh_read_var_in_file() { ynh_handle_getopts_args "$@" after="${after:-}" - [[ -f $file ]] || ynh_die "File $file does not exists" + [[ -f $file ]] || ynh_die --message="File $file does not exists" # Get the line number after which we search for the variable local line_number=1 From 6d1e392634a8e613cd651fb67aa330e84d18ca01 Mon Sep 17 00:00:00 2001 From: Kayou Date: Thu, 9 Sep 2021 10:50:49 +0200 Subject: [PATCH 2972/3170] Update data/helpers.d/config --- data/helpers.d/config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/config b/data/helpers.d/config index b9471cf51..8f3248949 100644 --- a/data/helpers.d/config +++ b/data/helpers.d/config @@ -139,7 +139,7 @@ _ynh_app_config_apply() { then if [[ "$bind" == *":"* ]] then - ynh_die "For technical reasons, multiline text '${short_setting}' can't be stored automatically in a variable file, you have to create custom getter/setter" + ynh_die --message="For technical reasons, multiline text '${short_setting}' can't be stored automatically in a variable file, you have to create custom getter/setter" fi local bind_file="$(echo "$bind" | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" ynh_backup_if_checksum_is_different --file="$bind_file" From 34e9246bb7d8f6a927e5887dc6ea3de4cd206f8c Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Wed, 8 Sep 2021 10:45:43 +0200 Subject: [PATCH 2973/3170] [fix] sort_buffer_size too small to make nextcloud work --- data/templates/mysql/my.cnf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/templates/mysql/my.cnf b/data/templates/mysql/my.cnf index 429596cf5..3da4377e1 100644 --- a/data/templates/mysql/my.cnf +++ b/data/templates/mysql/my.cnf @@ -30,7 +30,7 @@ skip-external-locking key_buffer_size = 16K max_allowed_packet = 16M table_open_cache = 4 -sort_buffer_size = 256K +sort_buffer_size = 4M read_buffer_size = 256K read_rnd_buffer_size = 256K net_buffer_length = 2K From 1ade4287aaf7976b3c87cd5f889382681a29fcee Mon Sep 17 00:00:00 2001 From: Kay0u Date: Fri, 10 Sep 2021 10:43:20 +0200 Subject: [PATCH 2974/3170] Update changelog for 4.2.8.3 --- debian/changelog | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/debian/changelog b/debian/changelog index 01c897a51..a1e7c8f83 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,11 @@ +yunohost (4.2.8.3) stable; urgency=low + + - [fix] mysql: Another bump for sort_buffer_size to make Nextcloud 22 work (34e9246b) + + Thanks to all contributors <3 ! (ljf (zamentur)) + + -- Kay0u Fri, 10 Sep 2021 10:40:38 +0200 + yunohost (4.2.8.2) stable; urgency=low - [fix] mysql: Bump sort_buffer_size to 256K to fix Nextcloud 22 installation (d8c49619) From 89e49007a667db4c05c653fc8b918c8b0ee5270e Mon Sep 17 00:00:00 2001 From: ljf Date: Sat, 11 Sep 2021 16:55:59 +0200 Subject: [PATCH 2975/3170] [fix) Tags empty question --- src/yunohost/utils/config.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 6d3c322f2..4636eaf48 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -538,6 +538,8 @@ class TagsQuestion(Question): values = self.value if isinstance(values, str): values = values.split(",") + elif value is None: + values = [] for value in values: self.value = value super()._prevalidate() From 3695be8d934f31ebc7c37fcce0cfe59812e7c46e Mon Sep 17 00:00:00 2001 From: ljf Date: Sat, 11 Sep 2021 17:48:52 +0200 Subject: [PATCH 2976/3170] [fix) Tags empty question --- src/yunohost/utils/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 4636eaf48..d13038b2b 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -538,7 +538,7 @@ class TagsQuestion(Question): values = self.value if isinstance(values, str): values = values.split(",") - elif value is None: + elif values is None: values = [] for value in values: self.value = value From 041bb34b7bcac4b923bb06c7d26c01fb563fc552 Mon Sep 17 00:00:00 2001 From: mifegui Date: Sat, 4 Sep 2021 20:45:02 +0000 Subject: [PATCH 2977/3170] Translated using Weblate (Portuguese) Currently translated at 17.1% (113 of 659 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/pt/ --- locales/pt.json | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/locales/pt.json b/locales/pt.json index 4b4248f09..d607b5ba3 100644 --- a/locales/pt.json +++ b/locales/pt.json @@ -97,11 +97,11 @@ "app_not_properly_removed": "{app} não foi corretamente removido", "app_requirements_checking": "Verificando os pacotes necessários para {app}...", "app_unsupported_remote_type": "A aplicação não possui suporte ao tipo remoto utilizado", - "backup_archive_app_not_found": "A aplicação '{app}' não foi encontrada no arquivo de backup", - "backup_archive_broken_link": "Impossível acessar o arquivo de backup (link quebrado ao {path})", - "backup_archive_name_exists": "O nome do arquivo de backup já existe", - "backup_archive_open_failed": "Não é possível abrir o arquivo de backup", - "backup_cleaning_failed": "Não é possível limpar a pasta temporária de backups", + "backup_archive_app_not_found": "Não foi possível encontrar {app} no arquivo de backup", + "backup_archive_broken_link": "Não foi possível acessar o arquivo de backup (link quebrado ao {path})", + "backup_archive_name_exists": "Já existe um arquivo de backup com esse nome.", + "backup_archive_open_failed": "Não foi possível abrir o arquivo de backup", + "backup_cleaning_failed": "Não foi possível limpar o diretório temporário de backup", "backup_creation_failed": "A criação do backup falhou", "backup_delete_error": "Impossível apagar '{path}'", "backup_deleted": "O backup foi suprimido", @@ -117,14 +117,14 @@ "app_location_unavailable": "Esta url ou não está disponível ou está em conflito com outra(s) aplicação(ões) já instalada(s):\n{apps}", "app_upgrade_app_name": "Atualizando {app}…", "app_upgrade_some_app_failed": "Não foi possível atualizar algumas aplicações", - "backup_abstract_method": "Este metodo de backup ainda não foi implementado", - "backup_app_failed": "Não foi possível fazer o backup dos aplicativos '{app}'", - "backup_applying_method_custom": "Chamando o metodo personalizado de backup '{method}'…", - "backup_applying_method_tar": "Criando o arquivo tar de backup…", + "backup_abstract_method": "Este método de backup ainda não foi implementado", + "backup_app_failed": "Não foi possível fazer o backup de '{app}'", + "backup_applying_method_custom": "Chamando o método personalizado de backup '{method}'…", + "backup_applying_method_tar": "Criando o arquivo TAR de backup…", "backup_archive_name_unknown": "Desconhece-se o arquivo local de backup de nome '{name}'", - "backup_archive_system_part_not_available": "A seção do sistema '{part}' está indisponivel neste backup", - "backup_ask_for_copying_if_needed": "Alguns arquivos não consiguiram ser preparados para backup utilizando o metodo que não gasta espaço de disco temporariamente. Para realizar o backup {size}MB precisam ser usados temporariamente. Você concorda?", - "backup_cant_mount_uncompress_archive": "Não foi possível montar em modo leitura o diretorio de arquivos não comprimido", + "backup_archive_system_part_not_available": "A seção do sistema '{part}' está indisponível neste backup", + "backup_ask_for_copying_if_needed": "Você quer efetuar o backup usando {size}MB temporariamente? (E necessário fazer dessa forma porque alguns arquivos não puderam ser preparados usando um método mais eficiente)", + "backup_cant_mount_uncompress_archive": "Não foi possível montar o arquivo descomprimido como protegido contra escrita", "backup_copying_to_organize_the_archive": "Copiando {size}MB para organizar o arquivo", "app_change_url_identical_domains": "O antigo e o novo domínio / url_path são idênticos ('{domain}{path}'), nada para fazer.", "password_too_simple_1": "A senha precisa ter pelo menos 8 caracteres", @@ -143,7 +143,7 @@ "app_change_url_success": "A URL agora é {domain}{path}", "apps_catalog_obsolete_cache": "O cache do catálogo de aplicações está vazio ou obsoleto.", "apps_catalog_failed_to_download": "Não foi possível fazer o download do catálogo de aplicações {apps_catalog}: {error}", - "apps_catalog_updating": "Atualizado o catálogo de aplicações…", + "apps_catalog_updating": "Atualizando o catálogo de aplicações...", "apps_catalog_init_success": "Catálogo de aplicações do sistema inicializado!", "apps_already_up_to_date": "Todas as aplicações já estão atualizadas", "app_packaging_format_not_supported": "Essa aplicação não pode ser instalada porque o formato dela não é suportado pela sua versão do YunoHost. Considere atualizar seu sistema.", @@ -164,5 +164,14 @@ "app_manifest_install_ask_path": "Escolha o caminho da url (depois do domínio) em que essa aplicação deve ser instalada", "app_manifest_install_ask_domain": "Escolha o domínio em que esta aplicação deve ser instalada", "app_label_deprecated": "Este comando está deprecado! Por favor use o novo comando 'yunohost user permission update' para gerenciar a etiqueta da aplicação.", - "app_make_default_location_already_used": "Não foi passível fazer a aplicação '{app}' ser a padrão no domínio, '{domain}' já está sendo usado por '{other_app}'" + "app_make_default_location_already_used": "Não foi passível fazer a aplicação '{app}' ser a padrão no domínio, '{domain}' já está sendo usado por '{other_app}'", + "backup_archive_writing_error": "Não foi possível adicionar os arquivos '{source}' (nomeados dentro do arquivo '{dest}') ao backup no arquivo comprimido '{archive}'", + "backup_archive_corrupted": "Parece que o arquivo de backup '{archive}' está corrompido: {error}", + "backup_archive_cant_retrieve_info_json": "Não foi possível carregar informações para o arquivo '{archive}'... Não foi possível carregar info.json (ou não é um json válido).", + "backup_applying_method_copy": "Copiando todos os arquivos para o backup...", + "backup_actually_backuping": "Criando cópia de backup dos arquivos obtidos...", + "ask_user_domain": "Domínio para usar para o endereço de email e conta XMPP do usuário", + "ask_new_path": "Novo caminho", + "ask_new_domain": "Novo domínio", + "apps_catalog_update_success": "O catálogo de aplicações foi atualizado!" } From e9fd8aac2d9da9d92690c8bcf3c1e41982e1f240 Mon Sep 17 00:00:00 2001 From: mifegui Date: Sat, 4 Sep 2021 20:51:10 +0000 Subject: [PATCH 2978/3170] Translated using Weblate (Portuguese) Currently translated at 19.5% (129 of 659 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/pt/ --- locales/pt.json | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/locales/pt.json b/locales/pt.json index d607b5ba3..acf2e9f0d 100644 --- a/locales/pt.json +++ b/locales/pt.json @@ -102,11 +102,11 @@ "backup_archive_name_exists": "Já existe um arquivo de backup com esse nome.", "backup_archive_open_failed": "Não foi possível abrir o arquivo de backup", "backup_cleaning_failed": "Não foi possível limpar o diretório temporário de backup", - "backup_creation_failed": "A criação do backup falhou", - "backup_delete_error": "Impossível apagar '{path}'", - "backup_deleted": "O backup foi suprimido", - "backup_hook_unknown": "Gancho de backup '{hook}' desconhecido", - "backup_nothings_done": "Não há nada para guardar", + "backup_creation_failed": "Não foi possível criar o arquivo de backup", + "backup_delete_error": "Não foi possível remover '{path}'", + "backup_deleted": "Backup removido", + "backup_hook_unknown": "O gancho de backup '{hook}' é desconhecido", + "backup_nothings_done": "Nada há se salvar", "backup_output_directory_forbidden": "Diretório de saída proibido. Os backups não podem ser criados em /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var ou /home/yunohost.backup/archives subpastas", "app_already_installed_cant_change_url": "Este aplicativo já está instalado. A URL não pode ser alterada apenas por esta função. Confira em `app changeurl` se está disponível.", "app_already_up_to_date": "{app} já está atualizado", @@ -173,5 +173,16 @@ "ask_user_domain": "Domínio para usar para o endereço de email e conta XMPP do usuário", "ask_new_path": "Novo caminho", "ask_new_domain": "Novo domínio", - "apps_catalog_update_success": "O catálogo de aplicações foi atualizado!" + "apps_catalog_update_success": "O catálogo de aplicações foi atualizado!", + "backup_no_uncompress_archive_dir": "Não existe tal diretório de arquivo descomprimido", + "backup_mount_archive_for_restore": "Preparando o arquivo para restauração...", + "backup_method_tar_finished": "Arquivo de backup TAR criado", + "backup_method_custom_finished": "Método de backup personalizado '{method}' finalizado", + "backup_method_copy_finished": "Cópia de backup finalizada", + "backup_custom_mount_error": "O método personalizado de backup não pôde passar do passo de 'mount'", + "backup_custom_backup_error": "O método personalizado de backup não pôde passar do passo de 'backup'", + "backup_csv_creation_failed": "Não foi possível criar o arquivo CSV necessário para a restauração", + "backup_csv_addition_failed": "Não foi possível adicionar os arquivos que estarão no backup ao arquivo CSV", + "backup_create_size_estimation": "O arquivo irá conter cerca de {size} de dados.", + "backup_couldnt_bind": "Não foi possível vincular {src} ao {dest}" } From 88ec60d5390f104be2b4663a21472894fd360eba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= Date: Sun, 5 Sep 2021 09:51:35 +0000 Subject: [PATCH 2979/3170] Translated using Weblate (Portuguese) Currently translated at 20.7% (137 of 661 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/pt/ --- locales/pt.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/locales/pt.json b/locales/pt.json index acf2e9f0d..6ddb90784 100644 --- a/locales/pt.json +++ b/locales/pt.json @@ -24,9 +24,9 @@ "custom_app_url_required": "Deve fornecer um link para atualizar a sua aplicação personalizada {app}", "domain_cert_gen_failed": "Não foi possível gerar o certificado", "domain_created": "Domínio criado com êxito", - "domain_creation_failed": "Não foi possível criar o domínio", + "domain_creation_failed": "Não foi possível criar o domínio {domain}: {error}", "domain_deleted": "Domínio removido com êxito", - "domain_deletion_failed": "Não foi possível eliminar o domínio", + "domain_deletion_failed": "Não foi possível eliminar o domínio {domain}: {error}", "domain_dyndns_already_subscribed": "Já subscreveu um domínio DynDNS", "domain_dyndns_root_unknown": "Domínio root (administrador) DynDNS desconhecido", "domain_exists": "O domínio já existe", @@ -34,12 +34,12 @@ "domain_unknown": "Domínio desconhecido", "done": "Concluído.", "downloading": "Transferência em curso...", - "dyndns_ip_update_failed": "Não foi possível atualizar o endereço IP a partir de DynDNS", - "dyndns_ip_updated": "Endereço IP atualizado com êxito a partir de DynDNS", + "dyndns_ip_update_failed": "Não foi possível atualizar o endereço IP para DynDNS", + "dyndns_ip_updated": "Endereço IP atualizado com êxito para DynDNS", "dyndns_key_generating": "A chave DNS está a ser gerada, isto pode demorar um pouco...", "dyndns_registered": "Dom+inio DynDNS registado com êxito", "dyndns_registration_failed": "Não foi possível registar o domínio DynDNS: {error}", - "dyndns_unavailable": "Subdomínio DynDNS indisponível", + "dyndns_unavailable": "O domínio '{domain}' não está disponível.", "extracting": "Extração em curso...", "field_invalid": "Campo inválido '{}'", "firewall_reloaded": "Firewall recarregada com êxito", @@ -167,7 +167,7 @@ "app_make_default_location_already_used": "Não foi passível fazer a aplicação '{app}' ser a padrão no domínio, '{domain}' já está sendo usado por '{other_app}'", "backup_archive_writing_error": "Não foi possível adicionar os arquivos '{source}' (nomeados dentro do arquivo '{dest}') ao backup no arquivo comprimido '{archive}'", "backup_archive_corrupted": "Parece que o arquivo de backup '{archive}' está corrompido: {error}", - "backup_archive_cant_retrieve_info_json": "Não foi possível carregar informações para o arquivo '{archive}'... Não foi possível carregar info.json (ou não é um json válido).", + "backup_archive_cant_retrieve_info_json": "Não foi possível carregar informações para o arquivo '{archive}'... Não foi possível carregar info.json (ou não é um JSON válido).", "backup_applying_method_copy": "Copiando todos os arquivos para o backup...", "backup_actually_backuping": "Criando cópia de backup dos arquivos obtidos...", "ask_user_domain": "Domínio para usar para o endereço de email e conta XMPP do usuário", From 44f57d005ac22e8a06ec93f459386995efa04a50 Mon Sep 17 00:00:00 2001 From: mifegui Date: Sun, 5 Sep 2021 14:58:10 +0000 Subject: [PATCH 2980/3170] Translated using Weblate (Portuguese) Currently translated at 22.3% (148 of 661 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/pt/ --- locales/pt.json | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/locales/pt.json b/locales/pt.json index 6ddb90784..7aad33e6a 100644 --- a/locales/pt.json +++ b/locales/pt.json @@ -20,7 +20,7 @@ "ask_new_admin_password": "Nova senha de administração", "ask_password": "Senha", "backup_created": "Backup completo", - "backup_output_directory_not_empty": "A pasta de destino não se encontra vazia", + "backup_output_directory_not_empty": "Você deve escolher um diretório de saída que esteja vazio", "custom_app_url_required": "Deve fornecer um link para atualizar a sua aplicação personalizada {app}", "domain_cert_gen_failed": "Não foi possível gerar o certificado", "domain_created": "Domínio criado com êxito", @@ -107,7 +107,7 @@ "backup_deleted": "Backup removido", "backup_hook_unknown": "O gancho de backup '{hook}' é desconhecido", "backup_nothings_done": "Nada há se salvar", - "backup_output_directory_forbidden": "Diretório de saída proibido. Os backups não podem ser criados em /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var ou /home/yunohost.backup/archives subpastas", + "backup_output_directory_forbidden": "Escolha um diretório de saída diferente. Backups não podem ser criados nos subdiretórios /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var ou /home/yunohost.backup/archives", "app_already_installed_cant_change_url": "Este aplicativo já está instalado. A URL não pode ser alterada apenas por esta função. Confira em `app changeurl` se está disponível.", "app_already_up_to_date": "{app} já está atualizado", "app_argument_choice_invalid": "Use uma das opções '{choices}' para o argumento '{name}'", @@ -184,5 +184,14 @@ "backup_csv_creation_failed": "Não foi possível criar o arquivo CSV necessário para a restauração", "backup_csv_addition_failed": "Não foi possível adicionar os arquivos que estarão no backup ao arquivo CSV", "backup_create_size_estimation": "O arquivo irá conter cerca de {size} de dados.", - "backup_couldnt_bind": "Não foi possível vincular {src} ao {dest}" + "backup_couldnt_bind": "Não foi possível vincular {src} ao {dest}", + "certmanager_attempt_to_replace_valid_cert": "Você está tentando sobrescrever um certificado bom e válido para o domínio {domain}! (Use --force para prosseguir mesmo assim)", + "backup_with_no_restore_script_for_app": "A aplicação {app} não tem um script de restauração, você não será capaz de automaticamente restaurar o backup dessa aplicação.", + "backup_with_no_backup_script_for_app": "A aplicação '{app}' não tem um script de backup. Ignorando.", + "backup_unable_to_organize_files": "Não foi possível usar o método rápido de organizar os arquivos no arquivo de backup", + "backup_system_part_failed": "Não foi possível fazer o backup da parte do sistema '{part}'", + "backup_running_hooks": "Executando os hooks de backup...", + "backup_permission": "Permissão de backup para {app}", + "backup_output_symlink_dir_broken": "O diretório de seu arquivo '{path}' é um link simbólico quebrado. Talvez você tenha esquecido de re/montar ou conectar o dispositivo de armazenamento para onde o link aponta.", + "backup_output_directory_required": "Você deve especificar um diretório de saída para o backup" } From 17b499c25e3d74bbcefa5cf187abc1f4fd8a52e6 Mon Sep 17 00:00:00 2001 From: Tymofii-Lytvynenko Date: Sun, 5 Sep 2021 20:27:14 +0000 Subject: [PATCH 2981/3170] Translated using Weblate (Ukrainian) Currently translated at 100.0% (661 of 661 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/uk/ --- locales/uk.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/locales/uk.json b/locales/uk.json index bdbe8b0cd..0e185c013 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -657,5 +657,7 @@ "diagnosis_apps_broken": "Цей застосунок наразі позначено як зламаний у каталозі застосунків YunoHost. Це може бути тимчасовою проблемою, поки організатори намагаються вирішити цю проблему. Тим часом оновлення цього застосунку вимкнено.", "diagnosis_apps_not_in_app_catalog": "Цей застосунок не міститься у каталозі застосунків YunoHost. Якщо він був у минулому і був видалений, вам слід подумати про видалення цього застосунку, оскільки він не отримає оновлення, і це може поставити під загрозу цілісність та безпеку вашої системи.", "diagnosis_apps_issue": "Виявлено проблему із застосунком {app}", - "diagnosis_apps_allgood": "Усі встановлені застосунки дотримуються основних способів упакування" + "diagnosis_apps_allgood": "Усі встановлені застосунки дотримуються основних способів упакування", + "diagnosis_high_number_auth_failures": "Останнім часом сталася підозріло велика кількість помилок автентифікації. Ви можете переконатися, що fail2ban працює і правильно налаштований, або скористатися власним портом для SSH, як описано в https://yunohost.org/security.", + "global_settings_setting_security_nginx_redirect_to_https": "Типово переспрямовувати HTTP-запити до HTTP (НЕ ВИМИКАЙТЕ, якщо ви дійсно не знаєте, що робите!)" } From 02f83041065ce48a27d60f8d1eccea2679c14598 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= Date: Mon, 6 Sep 2021 19:15:16 +0000 Subject: [PATCH 2982/3170] Translated using Weblate (French) Currently translated at 100.0% (661 of 661 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/locales/fr.json b/locales/fr.json index 9551fbcbd..0b9bc2dbd 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -657,5 +657,7 @@ "user_import_missing_columns": "Les colonnes suivantes sont manquantes : {columns}", "user_import_bad_file": "Votre fichier CSV n'est pas correctement formaté, il sera ignoré afin d'éviter une potentielle perte de données", "user_import_bad_line": "Ligne incorrecte {line} : {details}", - "log_user_import": "Importer des utilisateurs" + "log_user_import": "Importer des utilisateurs", + "diagnosis_high_number_auth_failures": "Il y a eu récemment un grand nombre d'échecs d'authentification. Assurez-vous que Fail2Ban est en cours d'exécution et est correctement configuré, ou utilisez un port personnalisé pour SSH comme expliqué dans https://yunohost.org/security.", + "global_settings_setting_security_nginx_redirect_to_https": "Redirigez les requêtes HTTP vers HTTPS par défaut (NE PAS DÉSACTIVER à moins de savoir vraiment ce que vous faites !)" } From e37ddae1c924d60690c7c218c6edb767936b7535 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M?= Date: Tue, 7 Sep 2021 11:59:59 +0000 Subject: [PATCH 2983/3170] Translated using Weblate (Galician) Currently translated at 95.7% (633 of 661 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/gl/ --- locales/gl.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/locales/gl.json b/locales/gl.json index 0c06bcab8..e3fe75d37 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -45,7 +45,7 @@ "apps_catalog_update_success": "O catálogo de aplicacións foi actualizado!", "apps_catalog_obsolete_cache": "A caché do catálogo de apps está baleiro ou obsoleto.", "apps_catalog_failed_to_download": "Non se puido descargar o catálogo de apps {apps_catalog}: {error}", - "apps_catalog_updating": "Actualizando o catálogo de aplicacións…", + "apps_catalog_updating": "Actualizando o catálogo de aplicacións...", "apps_catalog_init_success": "Sistema do catálogo de apps iniciado!", "apps_already_up_to_date": "Xa tes tódalas apps ao día", "app_packaging_format_not_supported": "Esta app non se pode instalar porque o formato de empaquetado non está soportado pola túa versión de YunoHost. Deberías considerar actualizar o teu sistema.", @@ -641,5 +641,7 @@ "service_removed": "Eliminado o servizo '{service}'", "service_remove_failed": "Non se eliminou o servizo '{service}'", "service_regen_conf_is_deprecated": "'yunohost service regen-conf' xa non se utiliza! Executa 'yunohost tools regen-conf' no seu lugar.", - "service_enabled": "O servizo '{service}' vai ser iniciado automáticamente no inicio do sistema." + "service_enabled": "O servizo '{service}' vai ser iniciado automáticamente no inicio do sistema.", + "diagnosis_apps_allgood": "Tódalas apps instaladas respectan as prácticas básicas de empaquetado", + "diagnosis_apps_bad_quality": "Esta aplicación está actualmente marcada como estragada no catálogo de aplicacións de YunoHost. Podería ser un problema temporal mentras as mantedoras intentan arranxar o problema. Ata ese momento a actualización desta app está desactivada." } From 48f1ee49a1056bfa1e0a3fbf5f25aefef7b5f021 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M?= Date: Tue, 7 Sep 2021 12:14:44 +0000 Subject: [PATCH 2984/3170] Translated using Weblate (Galician) Currently translated at 100.0% (661 of 661 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/gl/ --- locales/gl.json | 40 ++++++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/locales/gl.json b/locales/gl.json index e3fe75d37..1a3c570c2 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -292,7 +292,7 @@ "dyndns_could_not_check_provide": "Non se comprobou se {provider} pode proporcionar {domain}.", "dpkg_lock_not_available": "Non se pode executar agora mesmo este comando porque semella que outro programa está a utilizar dpkg (o xestos de paquetes do sistema)", "dpkg_is_broken": "Non podes facer isto agora mesmo porque dpkg/APT (o xestor de paquetes do sistema) semella que non está a funcionar... Podes intentar solucionalo conectándote a través de SSH e executando `sudo apt install --fix-broken`e/ou `sudo dpkg --configure -a`.", - "downloading": "Descargando…", + "downloading": "Descargando...", "done": "Feito", "domains_available": "Dominios dispoñibles:", "domain_unknown": "Dominio descoñecido", @@ -458,7 +458,7 @@ "migration_0015_specific_upgrade": "Iniciando a actualización dos paquetes do sistema que precisan ser actualizados de xeito independente...", "migration_0015_modified_files": "Ten en conta que os seguintes ficheiros semella que foron modificados manualmente e poderían ser sobrescritos na actualización: {manually_modified_files}", "migration_0015_problematic_apps_warning": "Ten en conta que se detectaron as seguintes apps que poderían ser problemáticas. Semella que non foron instaladas usando o catálogo de YunoHost, ou non están marcadas como 'funcionais'. En consecuencia, non se pode garantir que seguirán funcionando após a actualización: {problematic_apps}", - "diagnosis_http_localdomain": "O dominio {domain}, cun TLD .local, non é de agardar que sexa accesible desde o exterior da rede local.", + "diagnosis_http_localdomain": "O dominio {domain}, cun TLD .local, non é de agardar que esté exposto ao exterior da rede local.", "diagnosis_dns_specialusedomain": "O dominio {domain} baséase un dominio de nivel alto e uso especial (TLD) polo que non é de agardar que realmente teña rexistros DNS.", "upnp_enabled": "UPnP activado", "upnp_disabled": "UPnP desactivado", @@ -524,7 +524,7 @@ "permission_cant_add_to_all_users": "O permiso {permission} non pode ser concecido a tódalas usuarias.", "permission_currently_allowed_for_all_users": "Este permiso está concedido actualmente a tódalas usuarias ademáis de a outros grupos. Probablemente queiras ben eliminar o permiso 'all_users' ou ben eliminar os outros grupos que teñen permiso.", "restore_failed": "Non se puido restablecer o sistema", - "restore_extracting": "Extraendo os ficheiros necesarios desde o arquivo…", + "restore_extracting": "Extraendo os ficheiros necesarios desde o arquivo...", "restore_confirm_yunohost_installed": "Tes a certeza de querer restablecer un sistema xa instalado? [{answers}]", "restore_complete": "Restablecemento completado", "restore_cleaning_failed": "Non se puido despexar o directorio temporal de restablecemento", @@ -536,7 +536,7 @@ "regenconf_need_to_explicitly_specify_ssh": "A configuración ssh foi modificada manualmente, pero tes que indicar explícitamente a categoría 'ssh' con --force para realmente aplicar os cambios.", "regenconf_pending_applying": "Aplicando a configuración pendente para categoría '{category}'...", "regenconf_failed": "Non se rexenerou a configuración para a categoría(s): {categories}", - "regenconf_dry_pending_applying": "Comprobando as configuracións pendentes que deberían aplicarse á categoría '{category}'…", + "regenconf_dry_pending_applying": "Comprobando as configuracións pendentes que deberían aplicarse á categoría '{category}'...", "regenconf_would_be_updated": "A configuración debería ser actualizada para a categoría '{category}'", "regenconf_updated": "Configuración actualizada para '{category}'", "regenconf_up_to_date": "A configuración xa está ao día para a categoría '{category}'", @@ -574,8 +574,8 @@ "root_password_replaced_by_admin_password": "O contrasinal root foi substituído polo teu contrasinal de administración.", "root_password_desynchronized": "Mudou o contrasinal de administración, pero YunoHost non puido transferir este cambio ao contrasinal root!", "restore_system_part_failed": "Non se restableceu a parte do sistema '{part}'", - "restore_running_hooks": "Executando os ganchos do restablecemento…", - "restore_running_app_script": "Restablecendo a app '{app}'…", + "restore_running_hooks": "Executando os ganchos do restablecemento...", + "restore_running_app_script": "Restablecendo a app '{app}'...", "restore_removing_tmp_dir_failed": "Non se puido eliminar o directorio temporal antigo", "restore_nothings_done": "Nada foi restablecido", "restore_not_enough_disk_space": "Non hai espazo abondo (espazo: {free_space.d} B, espazo necesario: {needed_space} B, marxe de seguridade: {margin} B)", @@ -593,7 +593,7 @@ "user_updated": "Cambiada a info da usuaria", "user_update_failed": "Non se actualizou usuaria {user}: {error}", "user_unknown": "Usuaria descoñecida: {user}", - "user_home_creation_failed": "Non se puido crear cartafol 'home' para a usuaria", + "user_home_creation_failed": "Non se puido crear cartafol home '{home}' para a usuaria", "user_deletion_failed": "Non se puido eliminar a usuaria {user}: {error}", "user_deleted": "Usuaria eliminada", "user_creation_failed": "Non se puido crear a usuaria {user}: {error}", @@ -613,11 +613,11 @@ "unbackup_app": "{app} non vai ser gardada", "tools_upgrade_special_packages_completed": "Completada a actualización dos paquetes YunoHost.\nPreme [Enter] para recuperar a liña de comandos", "tools_upgrade_special_packages_explanation": "A actualización especial continuará en segundo plano. Non inicies outras tarefas no servidor nos seguintes ~10 minutos (depende do hardware). Após isto, podes volver a conectar na webadmin. O rexistro da actualización estará dispoñible en Ferramentas → Rexistro (na webadmin) ou con 'yunohost log list' (na liña de comandos).", - "tools_upgrade_special_packages": "Actualizando paquetes 'special' (yunohost-related)…", + "tools_upgrade_special_packages": "Actualizando paquetes 'special' (yunohost-related)...", "tools_upgrade_regular_packages_failed": "Non se actualizaron os paquetes: {packages_list}", - "tools_upgrade_regular_packages": "Actualizando os paquetes 'regular' (non-yunohost-related)…", - "tools_upgrade_cant_unhold_critical_packages": "Non se desbloquearon os paquetes críticos…", - "tools_upgrade_cant_hold_critical_packages": "Non se puideron bloquear os paquetes críticos…", + "tools_upgrade_regular_packages": "Actualizando os paquetes 'regular' (non-yunohost-related)...", + "tools_upgrade_cant_unhold_critical_packages": "Non se desbloquearon os paquetes críticos...", + "tools_upgrade_cant_hold_critical_packages": "Non se puideron bloquear os paquetes críticos...", "tools_upgrade_cant_both": "Non se pode actualizar o sistema e as apps ao mesmo tempo", "tools_upgrade_at_least_one": "Por favor indica 'apps', ou 'system'", "this_action_broke_dpkg": "Esta acción rachou dpkg/APT (xestores de paquetes do sistema)... Podes intentar resolver o problema conectando a través de SSH e executando `sudo apt install --fix-broken`e/ou `sudo dpkg --configure -a`.", @@ -643,5 +643,21 @@ "service_regen_conf_is_deprecated": "'yunohost service regen-conf' xa non se utiliza! Executa 'yunohost tools regen-conf' no seu lugar.", "service_enabled": "O servizo '{service}' vai ser iniciado automáticamente no inicio do sistema.", "diagnosis_apps_allgood": "Tódalas apps instaladas respectan as prácticas básicas de empaquetado", - "diagnosis_apps_bad_quality": "Esta aplicación está actualmente marcada como estragada no catálogo de aplicacións de YunoHost. Podería ser un problema temporal mentras as mantedoras intentan arranxar o problema. Ata ese momento a actualización desta app está desactivada." + "diagnosis_apps_bad_quality": "Esta aplicación está actualmente marcada como estragada no catálogo de aplicacións de YunoHost. Podería ser un problema temporal mentras as mantedoras intentan arranxar o problema. Ata ese momento a actualización desta app está desactivada.", + "global_settings_setting_security_nginx_redirect_to_https": "Redirixir peticións HTTP a HTTPs por defecto (NON DESACTIVAR ISTO a non ser que realmente saibas o que fas!)", + "log_user_import": "Importar usuarias", + "user_import_failed": "A operación de importación de usuarias fracasou", + "user_import_missing_columns": "Faltan as seguintes columnas: {columns}", + "user_import_nothing_to_do": "Ningunha usuaria precisa ser importada", + "user_import_partial_failed": "A operación de importación de usuarias fallou parcialmente", + "diagnosis_apps_deprecated_practices": "A versión instalada desta app aínda utiliza algunha das antigas prácticas de empaquetado xa abandonadas. Deberías considerar actualizala.", + "diagnosis_apps_outdated_ynh_requirement": "A versión instalada desta app só require yunohost >= 2.x, que normalmente indica que non está ao día coas prácticas recomendadas de empaquetado e asistentes. Deberías considerar actualizala.", + "user_import_success": "Usuarias importadas correctamente", + "diagnosis_high_number_auth_failures": "Hai un alto número sospeitoso de intentos fallidos de autenticación. Deberías comprobar que fail2ban está a executarse e que está correctamente configurado, ou utiliza un porto personalizado para SSH tal como se explica en https://yunohost.org/security.", + "user_import_bad_file": "O ficheiro CSV non ten o formato correcto e será ignorado para evitar unha potencial perda de datos", + "user_import_bad_line": "Liña incorrecta {line}: {details}", + "diagnosis_description_apps": "Aplicacións", + "diagnosis_apps_broken": "Actualmente esta aplicación está marcada como estragada no catálogo de aplicacións de YunoHost. Podería tratarse dun problema temporal mentras as mantedoras intentan arraxala. Entanto así a actualización da app está desactivada.", + "diagnosis_apps_issue": "Atopouse un problema na app {app}", + "diagnosis_apps_not_in_app_catalog": "Esta aplicación non está no catálgo de aplicacións de YunoHost. Se estivo no pasado e foi eliminada, deberías considerar desinstalala porque non recibirá actualizacións, e podería comprometer a integridade e seguridade do teu sistema." } From aae6cb667927f258c375c54863b67961eab28631 Mon Sep 17 00:00:00 2001 From: Weblate Date: Fri, 10 Sep 2021 01:05:52 +0000 Subject: [PATCH 2985/3170] Added translation using Weblate (Indonesian) --- locales/id.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 locales/id.json diff --git a/locales/id.json b/locales/id.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/locales/id.json @@ -0,0 +1 @@ +{} From 8bdacc37b5161009b54a72b57366b6f08a052bdb Mon Sep 17 00:00:00 2001 From: Tymofii-Lytvynenko Date: Fri, 10 Sep 2021 16:11:55 +0000 Subject: [PATCH 2986/3170] Translated using Weblate (Ukrainian) Currently translated at 100.0% (661 of 661 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/uk/ --- locales/uk.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/locales/uk.json b/locales/uk.json index 0e185c013..0de15a830 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -87,7 +87,7 @@ "restore_cleaning_failed": "Не вдалося очистити тимчасовий каталог відновлення", "restore_backup_too_old": "Цей архів резервних копій не може бути відновлений, бо він отриманий з дуже старої версії YunoHost.", "restore_already_installed_apps": "Наступні програми не можуть бути відновлені, тому що вони вже встановлені: {apps}", - "restore_already_installed_app": "Застосунок з ID \"{app} 'вже встановлено", + "restore_already_installed_app": "Застосунок з ID «{app}» вже встановлено", "regex_with_only_domain": "Ви не можете використовувати regex для домену, тільки для шляху", "regex_incompatible_with_tile": "/! \\ Packagers! Дозвіл '{permission}' має значення show_tile 'true', тому ви не можете визначити regex URL в якості основної URL", "regenconf_need_to_explicitly_specify_ssh": "Конфігурація ssh була змінена вручну, але вам потрібно явно вказати категорію 'ssh' з --force, щоб застосувати зміни.", @@ -175,7 +175,7 @@ "migration_0015_cleaning_up": "Очищення кеш-пам'яті і пакетів, які більше не потрібні...", "migration_0015_specific_upgrade": "Початок оновлення системних пакетів, які повинні бути оновлені незалежно...", "migration_0015_modified_files": "Зверніть увагу, що такі файли були змінені вручну і можуть бути перезаписані після оновлення: {manually_modified_files}", - "migration_0015_problematic_apps_warning": "Зверніть увагу, що були виявлені наступні, можливо, проблемні встановлені застосунки. Схоже, що вони не були встановлені з каталогу застосунків YunoHost або не зазначені як \"робочі\". Отже, не можна гарантувати, що вони будуть працювати після оновлення: {problematic_apps}", + "migration_0015_problematic_apps_warning": "Зверніть увагу, що були виявлені наступні, можливо, проблемні встановлені застосунки. Схоже, що вони не були встановлені з каталогу застосунків YunoHost або не зазначені як «робочі». Отже, не можна гарантувати, що вони будуть працювати після оновлення: {problematic_apps}", "migration_0015_general_warning": "Будь ласка, зверніть увагу, що ця міграція є делікатною операцією. Команда YunoHost зробила все можливе, щоб перевірити і протестувати її, але міграція все ще може порушити частина системи або її застосунків.\n\nТому рекомендовано:\n - Виконати резервне копіювання всіх важливих даних або застосунків. Подробиці на сайті https://yunohost.org/backup; \n - Наберіться терпіння після запуску міграції: В залежності від вашого з'єднання з Інтернетом і апаратного забезпечення, оновлення може зайняти до декількох годин.", "migration_0015_system_not_fully_up_to_date": "Ваша система не повністю оновлена. Будь ласка, виконайте регулярне оновлення перед запуском міграції на Buster.", "migration_0015_not_enough_free_space": "Вільного місця в /var/ досить мало! У вас повинно бути не менше 1 ГБ вільного місця, щоб запустити цю міграцію.", @@ -211,11 +211,11 @@ "log_tools_postinstall": "Післявстановлення сервера YunoHost", "log_tools_migrations_migrate_forward": "Запущено міграції", "log_domain_main_domain": "Зроблено '{}' основним доменом", - "log_user_permission_reset": "Скинуто дозвіл \"{} '", + "log_user_permission_reset": "Скинуто дозвіл «{}»", "log_user_permission_update": "Оновлено доступи для дозволу '{}'", "log_user_update": "Оновлено відомості для користувача '{}'", "log_user_group_update": "Оновлено групу '{}'", - "log_user_group_delete": "Видалено групу \"{} '", + "log_user_group_delete": "Видалено групу «{}»", "log_user_group_create": "Створено групу '{}'", "log_user_delete": "Видалення користувача '{}'", "log_user_create": "Додавання користувача '{}'", @@ -238,12 +238,12 @@ "log_available_on_yunopaste": "Цей журнал тепер доступний за посиланням {url}", "log_app_config_apply": "Застосування конфігурації до застосунку '{}'", "log_app_config_show_panel": "Показ панелі конфігурації застосунку '{}'", - "log_app_action_run": "Запуск дії застосунку \"{} '", + "log_app_action_run": "Запуск дії застосунку «{}»", "log_app_makedefault": "Застосунок '{}' зроблено типовим", "log_app_upgrade": "Оновлення застосунку '{}'", "log_app_remove": "Вилучення застосунку '{}'", "log_app_install": "Установлення застосунку '{}'", - "log_app_change_url": "Змінення URL-адреси застосунку \"{} '", + "log_app_change_url": "Змінення URL-адреси застосунку «{}»", "log_operation_unit_unclosed_properly": "Блок операцій не був закритий належним чином", "log_does_exists": "Немає журналу операцій з назвою '{log}', використовуйте 'yunohost log list', щоб подивитися всі доступні журнали операцій", "log_help_to_get_failed_log": "Операція '{desc}' не може бути завершена. Будь ласка, поділіться повним журналом цієї операції, використовуючи команду 'yunohost log share {name}', щоб отримати допомогу", @@ -507,7 +507,7 @@ "diagnosis_ip_connected_ipv6": "Сервер під'єднаний до Інтернету через IPv6!", "diagnosis_ip_no_ipv4": "Сервер не має робочого IPv4.", "diagnosis_ip_connected_ipv4": "Сервер під'єднаний до Інтернету через IPv4!", - "diagnosis_no_cache": "Для категорії \"{category} 'ще немає кеша діагностики", + "diagnosis_no_cache": "Для категорії «{category}» ще немає кеша діагностики", "diagnosis_failed": "Не вдалося отримати результат діагностики для категорії '{category}': {error}", "diagnosis_everything_ok": "Усе виглядає добре для {category}!", "diagnosis_found_warnings": "Знайдено {warnings} пунктів, які можна поліпшити для {category}.", From 65f90e8be49df3f5cdba8a7c27da7ca52aff3a8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= Date: Sat, 11 Sep 2021 08:14:51 +0000 Subject: [PATCH 2987/3170] Translated using Weblate (German) Currently translated at 93.6% (619 of 661 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/locales/de.json b/locales/de.json index fe4112934..dca2b034d 100644 --- a/locales/de.json +++ b/locales/de.json @@ -432,9 +432,9 @@ "additional_urls_already_removed": "Zusätzliche URL '{url}' bereits entfernt in der zusätzlichen URL für Berechtigung '{permission}'", "app_label_deprecated": "Dieser Befehl ist veraltet! Bitte nutzen Sie den neuen Befehl 'yunohost user permission update' um das Applabel zu verwalten.", "diagnosis_http_hairpinning_issue_details": "Das ist wahrscheinlich aufgrund Ihrer ISP Box / Router. Als Konsequenz können Personen von ausserhalb Ihres Netzwerkes aber nicht von innerhalb Ihres lokalen Netzwerkes (wie wahrscheinlich Sie selber?) wie gewohnt auf Ihren Server zugreifen, wenn Sie ihre Domäne oder Ihre öffentliche IP verwenden. Sie können die Situation wahrscheinlich verbessern, indem Sie ein einen Blick in https://yunohost.org/dns_local_network werfen", - "diagnosis_http_nginx_conf_not_up_to_date": "Jemand hat anscheinend die Konfiguration von Nginx manuell geändert. Diese Änderung verhindert, dass Yunohost eine Diagnose durchführen kann, wenn er via HTTP erreichbar ist.", + "diagnosis_http_nginx_conf_not_up_to_date": "Jemand hat anscheinend die Konfiguration von Nginx manuell geändert. Diese Änderung verhindert, dass YunoHost eine Diagnose durchführen kann, wenn er via HTTP erreichbar ist.", "diagnosis_http_bad_status_code": "Anscheinend beantwortet ein anderes Gerät als Ihr Server die Anfrage (Vielleicht ihr Internetrouter).
1. Die häufigste Ursache ist, dass Port 80 (und 443) nicht richtig auf Ihren Server weitergeleitet wird.
2. Bei komplexeren Setups: Vergewissern Sie sich, dass keine Firewall und keine Reverse-Proxy interferieren.", - "diagnosis_never_ran_yet": "Sie haben kürzlich einen neuen Yunohost-Server installiert aber es gibt davon noch keinen Diagnosereport. Sie sollten eine Diagnose anstossen. Sie können das entweder vom Webadmin aus oder in der Kommandozeile machen. In der Kommandozeile verwenden Sie dafür den Befehl 'yunohost diagnosis run'.", + "diagnosis_never_ran_yet": "Sie haben kürzlich einen neuen YunoHost-Server installiert aber es gibt davon noch keinen Diagnosereport. Sie sollten eine Diagnose anstossen. Sie können das entweder vom Webadmin aus oder in der Kommandozeile machen. In der Kommandozeile verwenden Sie dafür den Befehl 'yunohost diagnosis run'.", "diagnosis_http_nginx_conf_not_up_to_date_details": "Um dieses Problem zu beheben, geben Sie in der Kommandozeile yunohost tools regen-conf nginx --dry-run --with-diff ein. Dieses Tool zeigt ihnen den Unterschied an. Wenn Sie damit einverstanden sind, können Sie mit yunohost tools regen-conf nginx --force die Änderungen übernehmen.", "diagnosis_backports_in_sources_list": "Sie haben anscheinend apt (den Paketmanager) für das Backports-Repository konfiguriert. Wir raten strikte davon ab, Pakete aus dem Backports-Repository zu installieren. Diese würden wahrscheinlich zu Instabilitäten und Konflikten führen. Es sei denn, Sie wissen was Sie tun.", "diagnosis_basesystem_hardware_model": "Das Servermodell ist {model}", @@ -442,8 +442,8 @@ "group_user_not_in_group": "Der Benutzer {user} ist nicht in der Gruppe {group}", "group_user_already_in_group": "Der Benutzer {user} ist bereits in der Gruppe {group}", "group_cannot_edit_visitors": "Die Gruppe \"Besucher\" kann nicht manuell editiert werden. Sie ist eine Sondergruppe und repräsentiert anonyme Besucher", - "group_cannot_edit_all_users": "Die Gruppe \"all_users\" kann nicht manuell editiert werden. Sie ist eine Sondergruppe die dafür gedacht ist alle Benutzer in Yunohost zu halten", - "group_already_exist_on_system_but_removing_it": "Die Gruppe {group} existiert bereits in den Systemgruppen, aber Yunohost wird sie entfernen...", + "group_cannot_edit_all_users": "Die Gruppe \"all_users\" kann nicht manuell editiert werden. Sie ist eine Sondergruppe die dafür gedacht ist alle Benutzer in YunoHost zu halten", + "group_already_exist_on_system_but_removing_it": "Die Gruppe {group} existiert bereits in den Systemgruppen, aber YunoHost wird sie entfernen...", "group_already_exist_on_system": "Die Gruppe {group} existiert bereits in den Systemgruppen", "group_already_exist": "Die Gruppe {group} existiert bereits", "global_settings_setting_smtp_relay_password": "SMTP Relay Host Passwort", From aa5fcd9cf1f056c52c6e618e4bba433126aa9db9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= Date: Sat, 11 Sep 2021 08:15:38 +0000 Subject: [PATCH 2988/3170] Translated using Weblate (Czech) Currently translated at 9.9% (66 of 661 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/cs/ --- locales/cs.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/cs.json b/locales/cs.json index cd1e9f7ae..46435b7c2 100644 --- a/locales/cs.json +++ b/locales/cs.json @@ -55,7 +55,7 @@ "global_settings_setting_smtp_relay_password": "SMTP relay heslo uživatele/hostitele", "global_settings_setting_smtp_relay_user": "SMTP relay uživatelské jméno/účet", "global_settings_setting_smtp_relay_port": "SMTP relay port", - "global_settings_setting_smtp_relay_host": "Použít SMTP relay hostitele pro odesílání emailů místo této Yunohost instance. Užitečné v různých situacích: port 25 je blokován vaším ISP nebo VPS poskytovatelem, IP adresa je na blacklistu (např. DUHL), nemůžete nastavit reverzní DNS záznam nebo tento server není přímo připojen do internetu a vy chcete použít jiný server k odesílání emailů.", + "global_settings_setting_smtp_relay_host": "Použít SMTP relay hostitele pro odesílání emailů místo této YunoHost instance. Užitečné v různých situacích: port 25 je blokován vaším ISP nebo VPS poskytovatelem, IP adresa je na blacklistu (např. DUHL), nemůžete nastavit reverzní DNS záznam nebo tento server není přímo připojen do internetu a vy chcete použít jiný server k odesílání emailů.", "global_settings_setting_smtp_allow_ipv6": "Povolit použití IPv6 pro příjem a odesílání emailů", "global_settings_setting_ssowat_panel_overlay_enabled": "Povolit SSOwat překryvný panel", "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Povolit použití (zastaralého) DSA klíče hostitele pro konfiguraci SSH služby", @@ -65,4 +65,4 @@ "global_settings_setting_security_ssh_compatibility": "Kompromis mezi kompatibilitou a bezpečností SSH serveru. Ovlivní šifry a další související bezpečnostní nastavení", "global_settings_setting_security_password_user_strength": "Síla uživatelského hesla", "global_settings_setting_security_password_admin_strength": "Síla administračního hesla" -} \ No newline at end of file +} From 41cf266e252c69feeafb5286a6b57f8249f4db77 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 12 Sep 2021 15:40:17 +0200 Subject: [PATCH 2989/3170] dns-autoconf: remove dependency to public suffix list, not sure what scenario it was supposed to cover, probably goodenough without it --- src/yunohost/utils/dns.py | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/src/yunohost/utils/dns.py b/src/yunohost/utils/dns.py index ef89c35c5..848cafeba 100644 --- a/src/yunohost/utils/dns.py +++ b/src/yunohost/utils/dns.py @@ -19,11 +19,8 @@ """ import dns.resolver -from publicsuffixlist import PublicSuffixList from moulinette.utils.filesystem import read_file -YNH_DYNDNS_DOMAINS = ["nohost.me", "noho.st", "ynh.fr"] - # Lazy dev caching to avoid re-reading the file multiple time when calling # dig() often during same yunohost operation external_resolvers_ = [] @@ -94,24 +91,6 @@ def dig( return ("ok", answers) -def get_public_suffix(domain): - """get_public_suffix("www.example.com") -> "example.com" - - Return the public suffix of a domain name based - """ - # Load domain public suffixes - psl = PublicSuffixList() - - public_suffix = psl.publicsuffix(domain) - - # FIXME: wtf is this supposed to do ? :| - if public_suffix in YNH_DYNDNS_DOMAINS: - domain_prefix = domain[0:-(1 + len(public_suffix))] - public_suffix = domain_prefix.split(".")[-1] + "." + public_suffix - - return public_suffix - - def get_dns_zone_from_domain(domain): # TODO Check if this function is YNH_DYNDNS_DOMAINS compatible """ @@ -138,11 +117,6 @@ def get_dns_zone_from_domain(domain): if answer[0] == "ok": # Domain is dns_zone return parent - # Otherwise, check if the parent of this parent is in the public suffix list - if parent.split(".", 1)[-1] == get_public_suffix(parent): - # Couldn't check if domain is dns zone, # FIXME : why "couldn't" ...? - # returning private suffix - return parent # FIXME: returning None will probably trigger bugs when this happens, code expects a domain string return None From 88a624ddbcdf991f78d3c4f9e8be6ec13ffdd2ab Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 12 Sep 2021 15:44:51 +0200 Subject: [PATCH 2990/3170] Revert publicsuffix changes in dnsrecord diagnoser --- data/hooks/diagnosis/12-dnsrecords.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py index 727fd2e13..90c42c0d7 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -4,15 +4,16 @@ import os import re from datetime import datetime, timedelta -from publicsuffixlist import PublicSuffixList +from publicsuffix import PublicSuffixList from moulinette.utils.process import check_output -from yunohost.utils.dns import dig, YNH_DYNDNS_DOMAINS +from yunohost.utils.dns import dig from yunohost.diagnosis import Diagnoser from yunohost.domain import domain_list, _get_maindomain from yunohost.dns import _build_dns_conf +YNH_DYNDNS_DOMAINS = ["nohost.me", "noho.st", "ynh.fr"] SPECIAL_USE_TLDS = ["local", "localhost", "onion", "test"] @@ -44,7 +45,7 @@ class DNSRecordsDiagnoser(Diagnoser): # Check if a domain buy by the user will expire soon psl = PublicSuffixList() domains_from_registrar = [ - psl.publicsuffix(domain) for domain in all_domains + psl.get_public_suffix(domain) for domain in all_domains ] domains_from_registrar = [ domain for domain in domains_from_registrar if "." in domain From b042b549e4d990d5f21bb0304aa0b80afcf1df99 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 12 Sep 2021 15:50:23 +0200 Subject: [PATCH 2991/3170] Let's not define a duplicate string for domain unknown... --- locales/en.json | 1 - src/yunohost/utils/config.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/locales/en.json b/locales/en.json index 588e37357..89d520112 100644 --- a/locales/en.json +++ b/locales/en.json @@ -309,7 +309,6 @@ "domain_name_unknown": "Domain '{domain}' unknown", "domain_remove_confirm_apps_removal": "Removing this domain will remove those applications:\n{apps}\n\nAre you sure you want to do that? [{answers}]", "domain_uninstall_app_first": "Those applications are still installed on your domain:\n{apps}\n\nPlease uninstall them using 'yunohost app remove the_app_id' or move them to another domain using 'yunohost app change-url the_app_id' before proceeding to domain removal", - "domain_unknown": "Unknown domain", "domains_available": "Available domains:", "done": "Done", "downloading": "Downloading...", diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index d13038b2b..a02097d48 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -672,7 +672,7 @@ class DomainQuestion(Question): def _raise_invalid_answer(self): raise YunohostValidationError( - "app_argument_invalid", name=self.name, error=m18n.n("domain_unknown") + "app_argument_invalid", name=self.name, error=m18n.n("domain_name_unknown", domain=self.value) ) From d7b79154ff2eee6db876e329656d2dc9f67b0cd7 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 12 Sep 2021 16:07:10 +0200 Subject: [PATCH 2992/3170] debian: Add python3-lexicon dependency --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index 2e101dca3..90bac0a0d 100644 --- a/debian/control +++ b/debian/control @@ -14,7 +14,7 @@ Depends: ${python3:Depends}, ${misc:Depends} , python3-psutil, python3-requests, python3-dnspython, python3-openssl , python3-miniupnpc, python3-dbus, python3-jinja2 , python3-toml, python3-packaging, python3-publicsuffix, - , python3-ldap, python3-zeroconf, + , python3-ldap, python3-zeroconf, python3-lexicon, , apt, apt-transport-https, apt-utils, dirmngr , php7.3-common, php7.3-fpm, php7.3-ldap, php7.3-intl , mariadb-server, php7.3-mysql From e133b163dfc6b29fb7f181b74645c9c3d44a012f Mon Sep 17 00:00:00 2001 From: ljf Date: Sun, 12 Sep 2021 17:34:05 +0200 Subject: [PATCH 2993/3170] [wip] Check question are initialize --- locales/en.json | 2 ++ src/yunohost/utils/config.py | 17 +++++++++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/locales/en.json b/locales/en.json index 588e37357..481e7c644 100644 --- a/locales/en.json +++ b/locales/en.json @@ -142,6 +142,8 @@ "certmanager_warning_subdomain_dns_record": "Subdomain '{subdomain}' does not resolve to the same IP address as '{domain}'. Some features will not be available until you fix this and regenerate the certificate.", "config_apply_failed": "Applying the new configuration failed: {error}", "config_cant_set_value_on_section": "You can't set a single value on an entire config section.", + "config_forbidden_keyword": "The keyword '{keyword}' is reserved, you can't create or use a config panel with a question with this id.", + "config_missing_init_value": "Config panel question '{question}' should be initialize with a value during install or upgrade.", "config_no_panel": "No config panel found.", "config_unknown_filter_key": "The filter key '{filter_key}' is incorrect.", "config_version_not_supported": "Config panel versions '{version}' are not supported.", diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index d13038b2b..429606dfe 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -209,7 +209,6 @@ class ConfigPanel: "default": {} } } - def convert(toml_node, node_type): """Convert TOML in internal format ('full' mode used by webadmin) Here are some properties of 1.0 config panel in toml: @@ -264,6 +263,16 @@ class ConfigPanel: "config_unknown_filter_key", filter_key=self.filter_key ) + # List forbidden keywords from helpers and sections toml (to avoid conflict) + forbidden_keywords = ["old", "app", "changed", "file_hash", "binds", "types", + "formats", "getter", "setter", "short_setting", "type", + "bind", "nothing_changed", "changes_validated", "result", + "max_progression"] + forbidden_keywords += format_description["sections"] + + for _, _, option in self._iterate(): + if option["id"] in forbidden_keywords: + raise YunohostError("config_forbidden_keyword", keyword=option["id"]) return self.config def _hydrate(self): @@ -787,9 +796,9 @@ class FileQuestion(Question): def __init__(self, question, user_answers): super().__init__(question, user_answers) if question.get("accept"): - self.accept = question.get("accept").replace(" ", "").split(",") + self.accept = question.get("accept") else: - self.accept = [] + self.accept = "" if Moulinette.interface.type == "api": if user_answers.get(f"{self.name}[name]"): self.value = { @@ -816,7 +825,7 @@ class FileQuestion(Question): return filename = self.value if isinstance(self.value, str) else self.value["filename"] - if "." not in filename or "." + filename.split(".")[-1] not in self.accept: + if "." not in filename or "." + filename.split(".")[-1] not in self.accept.replace(" ", "").split(","): raise YunohostValidationError( "app_argument_invalid", name=self.name, From f8fed701b95d607a842ef1357aacc7f418502cdc Mon Sep 17 00:00:00 2001 From: ljf Date: Sun, 12 Sep 2021 17:43:03 +0200 Subject: [PATCH 2994/3170] [fix] Raise an error if question has not been initialize --- src/yunohost/utils/config.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 9701bf966..488cb54a3 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -279,8 +279,11 @@ class ConfigPanel: # Hydrating config panel with current value logger.debug("Hydrating config with current values") for _, _, option in self._iterate(): - if option["name"] not in self.values: - continue + if option["id"] not in self.values: + if option["type"] in ["alert", "display_text", "markdown", "file"]: + continue + else: + raise YunohostError("config_missing_init_value", question=option["id"]) value = self.values[option["name"]] # In general, the value is just a simple value. # Sometimes it could be a dict used to overwrite the option itself From ca5d7b32dc332fbe4b5550413318ad39e84bd81e Mon Sep 17 00:00:00 2001 From: ljf Date: Sun, 12 Sep 2021 19:06:25 +0200 Subject: [PATCH 2995/3170] [enh] Validate server side specific input html5 field --- locales/en.json | 5 ++++ src/yunohost/utils/config.py | 53 ++++++++++++++++++++++++++++++++---- 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/locales/en.json b/locales/en.json index a0c805b75..eb131fe43 100644 --- a/locales/en.json +++ b/locales/en.json @@ -146,6 +146,11 @@ "config_missing_init_value": "Config panel question '{question}' should be initialize with a value during install or upgrade.", "config_no_panel": "No config panel found.", "config_unknown_filter_key": "The filter key '{filter_key}' is incorrect.", + "config_validate_color": "Should be a valid RGB hexadecimal color", + "config_validate_date": "Should be a valid date like in the format YYYY-MM-DD", + "config_validate_email": "Should be a valid email", + "config_validate_time": "Should be a valid time like XX:YY", + "config_validate_url": "Should be a valid web URL", "config_version_not_supported": "Config panel versions '{version}' are not supported.", "confirm_app_install_danger": "DANGER! This app is known to be still experimental (if not explicitly not working)! You should probably NOT install it unless you know what you are doing. NO SUPPORT will be provided if this app doesn't work or breaks your system... If you are willing to take that risk anyway, type '{answers}'", "confirm_app_install_thirdparty": "DANGER! This app is not part of YunoHost's app catalog. Installing third-party apps may compromise the integrity and security of your system. You should probably NOT install it unless you know what you are doing. NO SUPPORT will be provided if this app doesn't work or breaks your system... If you are willing to take that risk anyway, type '{answers}'", diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 488cb54a3..270503f8f 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -388,6 +388,7 @@ class ConfigPanel: class Question(object): hide_user_input_in_prompt = False operation_logger = None + pattern = None def __init__(self, question, user_answers): self.name = question["name"] @@ -396,7 +397,7 @@ class Question(object): self.current_value = question.get("current_value") self.optional = question.get("optional", False) self.choices = question.get("choices", []) - self.pattern = question.get("pattern") + self.pattern = question.get("pattern", self.pattern) self.ask = question.get("ask", {"en": self.name}) self.help = question.get("help") self.value = user_answers.get(self.name) @@ -536,6 +537,46 @@ class StringQuestion(Question): argument_type = "string" default_value = "" +class EmailQuestion(StringQuestion): + pattern = { + "regexp": "^.+@.+", + "error": "config_validate_email" + } + +class URLQuestion(StringQuestion): + pattern = { + "regexp": "^https?://.*$", + "error": "config_validate_url" + } + +class DateQuestion(StringQuestion): + pattern = { + "regexp": "^\d{4}-\d\d-\d\d$", + "error": "config_validate_date" + } + + def _prevalidate(self): + from datetime import datetime + super()._prevalidate() + + if self.value not in [None, ""]: + try: + datetime.strptime(self.value, '%Y-%m-%d') + except ValueError: + raise YunohostValidationError("config_validate_date") + +class TimeQuestion(StringQuestion): + pattern = { + "regexp": "^(1[12]|0?\d):[0-5]\d$", + "error": "config_validate_time" + } + +class ColorQuestion(StringQuestion): + pattern = { + "regexp": "^#[ABCDEFabcdef\d]{3,6}$", + "error": "config_validate_color" + } + class TagsQuestion(Question): argument_type = "tags" @@ -880,11 +921,11 @@ ARGUMENTS_TYPE_PARSERS = { "text": StringQuestion, "select": StringQuestion, "tags": TagsQuestion, - "email": StringQuestion, - "url": StringQuestion, - "date": StringQuestion, - "time": StringQuestion, - "color": StringQuestion, + "email": EmailQuestion, + "url": URLQuestion, + "date": DateQuestion, + "time": TimeQuestion, + "color": ColorQuestion, "password": PasswordQuestion, "path": PathQuestion, "boolean": BooleanQuestion, From 4533b74d6ced0288bddcd9fbf3dfb7474170f611 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 12 Sep 2021 21:32:43 +0200 Subject: [PATCH 2996/3170] autodns: Various tweaks and refactorings to make test pass --- .gitlab/ci/test.gitlab-ci.yml | 19 ++++ data/actionsmap/yunohost.yml | 10 +- data/hooks/conf_regen/15-nginx | 2 +- data/hooks/diagnosis/12-dnsrecords.py | 3 +- data/other/config_domain.toml | 17 ++-- locales/en.json | 2 + src/yunohost/dns.py | 137 ++++++++++++++++++++++++-- src/yunohost/domain.py | 48 ++++----- src/yunohost/tests/test_dns.py | 66 +++++++++++++ src/yunohost/tests/test_domains.py | 105 ++++++-------------- src/yunohost/utils/config.py | 2 + src/yunohost/utils/dns.py | 32 +----- src/yunohost/utils/ldap.py | 5 +- 13 files changed, 299 insertions(+), 149 deletions(-) create mode 100644 src/yunohost/tests/test_dns.py diff --git a/.gitlab/ci/test.gitlab-ci.yml b/.gitlab/ci/test.gitlab-ci.yml index 2dc45171b..f270ba982 100644 --- a/.gitlab/ci/test.gitlab-ci.yml +++ b/.gitlab/ci/test.gitlab-ci.yml @@ -85,6 +85,25 @@ test-helpers: changes: - data/helpers.d/* +test-domains: + extends: .test-stage + script: + - cd src/yunohost + - python3 -m pytest tests/test_domains.py + only: + changes: + - src/yunohost/domain.py + +test-dns: + extends: .test-stage + script: + - cd src/yunohost + - python3 -m pytest tests/test_dns.py + only: + changes: + - src/yunohost/dns.py + - src/yunohost/utils/dns.py + test-apps: extends: .test-stage script: diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index f9fcaffc0..c118c90a2 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -589,8 +589,16 @@ domain: domain: help: Domain name key: - help: A question or form key + help: A specific panel, section or a question identifier nargs: '?' + -f: + full: --full + help: Display all details (meant to be used by the API) + action: store_true + -e: + full: --export + help: Only export key/values, meant to be reimported using "config set --args-file" + action: store_true ### domain_config_set() set: diff --git a/data/hooks/conf_regen/15-nginx b/data/hooks/conf_regen/15-nginx index 040ed090d..0c41ea50b 100755 --- a/data/hooks/conf_regen/15-nginx +++ b/data/hooks/conf_regen/15-nginx @@ -65,7 +65,7 @@ do_pre_regen() { export experimental="$(yunohost settings get 'security.experimental.enabled')" ynh_render_template "security.conf.inc" "${nginx_conf_dir}/security.conf.inc" - cert_status=$(yunohost domain cert-status --json) + cert_status=$(yunohost domain cert status --json) # add domain conf files for domain in $YNH_DOMAINS; do diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py index 90c42c0d7..854f348f5 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -8,12 +8,11 @@ from publicsuffix import PublicSuffixList from moulinette.utils.process import check_output -from yunohost.utils.dns import dig +from yunohost.utils.dns import dig, YNH_DYNDNS_DOMAINS from yunohost.diagnosis import Diagnoser from yunohost.domain import domain_list, _get_maindomain from yunohost.dns import _build_dns_conf -YNH_DYNDNS_DOMAINS = ["nohost.me", "noho.st", "ynh.fr"] SPECIAL_USE_TLDS = ["local", "localhost", "onion", "test"] diff --git a/data/other/config_domain.toml b/data/other/config_domain.toml index 44766e2d0..20963764b 100644 --- a/data/other/config_domain.toml +++ b/data/other/config_domain.toml @@ -2,8 +2,10 @@ version = "1.0" i18n = "domain_config" [feature] + [feature.mail] - services = ['postfix', 'dovecot'] + services = ['postfix', 'dovecot'] + [feature.mail.mail_out] type = "boolean" default = 1 @@ -25,17 +27,14 @@ i18n = "domain_config" default = 0 [dns] + [dns.registrar] - optional = true - # This part is replace dynamically by DomainConfigPanel - [dns.registrar.unsupported] - ask = "DNS zone of this domain can't be auto-configured, you should do it manually." - type = "alert" - style = "info" - helpLink.href = "https://yunohost.org/dns_config" - helpLink.text = "How to configure manually my DNS zone" + optional = true + + # This part is automatically generated in DomainConfigPanel [dns.advanced] + [dns.advanced.ttl] type = "number" min = 0 diff --git a/locales/en.json b/locales/en.json index 1d51f59d3..e39c765c0 100644 --- a/locales/en.json +++ b/locales/en.json @@ -395,6 +395,7 @@ "iptables_unavailable": "You cannot play with iptables here. You are either in a container or your kernel does not support it", "ldap_server_down": "Unable to reach LDAP server", "ldap_server_is_down_restart_it": "The LDAP service is down, attempt to restart it...", + "ldap_attribute_already_exists": "LDAP attribute '{attribute}' already exists with value '{value}'", "log_app_action_run": "Run action of the '{}' app", "log_app_change_url": "Change the URL of the '{}' app", "log_app_config_set": "Apply config to the '{}' app", @@ -409,6 +410,7 @@ "log_corrupted_md_file": "The YAML metadata file associated with logs is damaged: '{md_file}\nError: {error}'", "log_does_exists": "There is no operation log with the name '{log}', use 'yunohost log list' to see all available operation logs", "log_domain_add": "Add '{}' domain into system configuration", + "log_domain_config_set": "Update configuration for domain '{}'", "log_domain_main_domain": "Make '{}' the main domain", "log_domain_remove": "Remove '{}' domain from system configuration", "log_dyndns_subscribe": "Subscribe to a YunoHost subdomain '{}'", diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index 045a33e05..aa5e79c82 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -25,11 +25,15 @@ """ import os import re +import time +from collections import OrderedDict from moulinette import m18n, Moulinette from moulinette.utils.log import getActionLogger +from moulinette.utils.filesystem import read_file, write_to_file, read_toml -from yunohost.domain import domain_list, _get_domain_settings, _assert_domain_exists +from yunohost.domain import domain_list, _assert_domain_exists, domain_config_get +from yunohost.utils.dns import dig, YNH_DYNDNS_DOMAINS from yunohost.utils.error import YunohostValidationError from yunohost.utils.network import get_public_ip from yunohost.log import is_unit_operation @@ -37,8 +41,10 @@ from yunohost.hook import hook_callback logger = getActionLogger("yunohost.domain") +DOMAIN_REGISTRAR_LIST_PATH = "/usr/share/yunohost/other/registrar_list.toml" -def domain_dns_conf(domain): + +def domain_dns_suggest(domain): """ Generate DNS configuration for a domain @@ -149,10 +155,10 @@ def _build_dns_conf(base_domain): ipv6 = get_public_ip(6) subdomains = _list_subdomains_of(base_domain) - domains_settings = {domain: _get_domain_settings(domain) + domains_settings = {domain: domain_config_get(domain) for domain in [base_domain] + subdomains} - base_dns_zone = domains_settings[base_domain].get("dns_zone") + base_dns_zone = _get_dns_zone_for_domain(base_domain) for domain, settings in domains_settings.items(): @@ -384,6 +390,126 @@ def _get_DKIM(domain): ) +def _get_dns_zone_for_domain(domain): + """ + Get the DNS zone of a domain + + Keyword arguments: + domain -- The domain name + + """ + + # First, check if domain is a nohost.me / noho.st / ynh.fr + # This is mainly meant to speed up things for "dyndns update" + # ... otherwise we end up constantly doing a bunch of dig requests + for ynh_dyndns_domain in YNH_DYNDNS_DOMAINS: + if domain.endswith('.' + ynh_dyndns_domain): + return ynh_dyndns_domain + + # Check cache + cache_folder = "/var/cache/yunohost/dns_zones" + cache_file = f"{cache_folder}/{domain}" + cache_duration = 3600 # one hour + if ( + os.path.exists(cache_file) + and abs(os.path.getctime(cache_file) - time.time()) < cache_duration + ): + dns_zone = read_file(cache_file).strip() + if dns_zone: + return dns_zone + + # Check cache for parent domain + # This is another strick to try to prevent this function from being + # a bottleneck on system with 1 main domain + 10ish subdomains + # when building the dns conf for the main domain (which will call domain_config_get, etc...) + parent_domain = domain.split(".", 1)[1] + if parent_domain in domain_list()["domains"]: + parent_cache_file = f"{cache_folder}/{parent_domain}" + if ( + os.path.exists(parent_cache_file) + and abs(os.path.getctime(parent_cache_file) - time.time()) < cache_duration + ): + dns_zone = read_file(parent_cache_file).strip() + if dns_zone: + return dns_zone + + # For foo.bar.baz.gni we want to scan all the parent domains + # (including the domain itself) + # foo.bar.baz.gni + # bar.baz.gni + # baz.gni + # gni + # Until we find the first one that has a NS record + parent_list = [domain.split(".", i)[-1] + for i, _ in enumerate(domain.split("."))] + + for parent in parent_list: + + # Check if there's a NS record for that domain + answer = dig(parent, rdtype="NS", full_answers=True, resolvers="force_external") + if answer[0] == "ok": + os.system(f"mkdir -p {cache_folder}") + write_to_file(cache_file, parent) + return parent + + logger.warning(f"Could not identify the dns_zone for domain {domain}, returning {parent_list[-1]}") + return parent_list[-1] + + +def _get_registrar_config_section(domain): + + from lexicon.providers.auto import _relevant_provider_for_domain + + registrar_infos = {} + + dns_zone = _get_dns_zone_for_domain(domain) + + # If parent domain exists in yunohost + parent_domain = domain.split(".", 1)[1] + if parent_domain in domain_list()["domains"]: + registrar_infos["explanation"] = OrderedDict({ + "type": "alert", + "style": "info", + "ask": f"This domain is a subdomain of {parent_domain}. DNS registrar configuration should be managed in {parent_domain}'s configuration panel.", # FIXME: i18n + "value": None + }) + return OrderedDict(registrar_infos) + + # TODO big project, integrate yunohost's dynette as a registrar-like provider + # TODO big project, integrate other dyndns providers such as netlib.re, or cf the list of dyndns providers supported by cloudron... + if dns_zone in YNH_DYNDNS_DOMAINS: + registrar_infos["explanation"] = OrderedDict({ + "type": "alert", + "style": "success", + "ask": "This domain is a nohost.me / nohost.st / ynh.fr and its DNS configuration is therefore automatically handled by Yunohost.", # FIXME: i18n + "value": "yunohost" + }) + return OrderedDict(registrar_infos) + + try: + registrar = _relevant_provider_for_domain(dns_zone)[0] + except ValueError: + registrar_infos["explanation"] = OrderedDict({ + "type": "alert", + "style": "warning", + "ask": "YunoHost could not automatically detect the registrar handling this domain. You should manually configure your DNS records following the documentation at https://yunohost.org/dns.", # FIXME : i18n + "value": None + }) + else: + + registrar_infos["explanation"] = OrderedDict({ + "type": "alert", + "style": "info", + "ask": f"YunoHost automatically detected that this domain is handled by the registrar **{registrar}**. If you want, YunoHost will automatically configure this DNS zone, if you provide it with the following informations. You can also manually configure your DNS records following the documentation as https://yunohost.org/dns.", # FIXME: i18n + "value": registrar + }) + # TODO : add a help tip with the link to the registar's API doc (c.f. Lexicon's README) + registrar_list = read_toml(DOMAIN_REGISTRAR_LIST_PATH) + registrar_infos.update(registrar_list[registrar]) + + return OrderedDict(registrar_infos) + + @is_unit_operation() def domain_registrar_push(operation_logger, domain, dry_run=False): """ @@ -395,8 +521,7 @@ def domain_registrar_push(operation_logger, domain, dry_run=False): _assert_domain_exists(domain) - dns_zone = _get_domain_settings(domain)["dns_zone"] - registrar_settings = _get_registrar_settings(dns_zone) + registrar_settings = domain_config_get(domain, key='', full=True) if not registrar_settings: raise YunohostValidationError("registrar_is_not_set", domain=domain) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 4cf223510..0bdede11d 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -29,7 +29,7 @@ from moulinette import m18n, Moulinette from moulinette.core import MoulinetteError from moulinette.utils.log import getActionLogger from moulinette.utils.filesystem import ( - mkdir, write_to_file, read_yaml, write_to_yaml, read_toml + mkdir, write_to_file, read_yaml, write_to_yaml ) from yunohost.app import ( @@ -49,7 +49,6 @@ logger = getActionLogger("yunohost.domain") DOMAIN_CONFIG_PATH = "/usr/share/yunohost/other/config_domain.toml" DOMAIN_SETTINGS_DIR = "/etc/yunohost/domains" -DOMAIN_REGISTRAR_LIST_PATH = "/usr/share/yunohost/other/registrar_list.toml" # Lazy dev caching to avoid re-query ldap every time we need the domain list domain_list_cache = {} @@ -391,23 +390,25 @@ def _get_maindomain(): return maindomain -def _get_domain_settings(domain): - """ - Retrieve entries in /etc/yunohost/domains/[domain].yml - And set default values if needed - """ - config = DomainConfigPanel(domain) - return config.get(mode='export') - - -def domain_config_get(domain, key='', mode='classic'): +def domain_config_get(domain, key='', full=False, export=False): """ Display a domain configuration """ + if full and export: + raise YunohostValidationError("You can't use --full and --export together.", raw_msg=True) + + if full: + mode = "full" + elif export: + mode = "export" + else: + mode = "classic" + config = DomainConfigPanel(domain) return config.get(key, mode) + @is_unit_operation() def domain_config_set(operation_logger, domain, key=None, value=None, args=None, args_file=None): """ @@ -415,31 +416,28 @@ def domain_config_set(operation_logger, domain, key=None, value=None, args=None, """ Question.operation_logger = operation_logger config = DomainConfigPanel(domain) - return config.set(key, value, args, args_file) + return config.set(key, value, args, args_file, operation_logger=operation_logger) class DomainConfigPanel(ConfigPanel): + def __init__(self, domain): _assert_domain_exists(domain) self.domain = domain + self.save_mode = "diff" super().__init__( config_path=DOMAIN_CONFIG_PATH, save_path=f"{DOMAIN_SETTINGS_DIR}/{domain}.yml" ) def _get_toml(self): - from lexicon.providers.auto import _relevant_provider_for_domain - from yunohost.utils.dns import get_dns_zone_from_domain + from yunohost.dns import _get_registrar_config_section + toml = super()._get_toml() - self.dns_zone = get_dns_zone_from_domain(self.domain) - try: - registrar = _relevant_provider_for_domain(self.dns_zone)[0] - except ValueError: - return toml + toml['feature']['xmpp']['xmpp']['default'] = 1 if self.domain == _get_maindomain() else 0 + toml['dns']['registrar'] = _get_registrar_config_section(self.domain) - registrar_list = read_toml(DOMAIN_REGISTRAR_LIST_PATH) - toml['dns']['registrar'] = registrar_list[registrar] return toml def _load_current_values(self): @@ -480,8 +478,12 @@ def domain_cert_renew( def domain_dns_conf(domain): + return domain_dns_suggest(domain) + + +def domain_dns_suggest(domain): import yunohost.dns - return yunohost.dns.domain_dns_conf(domain) + return yunohost.dns.domain_dns_suggest(domain) def domain_dns_push(domain, dry_run): diff --git a/src/yunohost/tests/test_dns.py b/src/yunohost/tests/test_dns.py new file mode 100644 index 000000000..7adae84fd --- /dev/null +++ b/src/yunohost/tests/test_dns.py @@ -0,0 +1,66 @@ +import pytest + +import yaml +import os + +from moulinette.utils.filesystem import read_toml + +from yunohost.utils.error import YunohostError, YunohostValidationError +from yunohost.dns import ( + DOMAIN_REGISTRAR_LIST_PATH, + _get_dns_zone_for_domain, + _get_registrar_config_section +) + + +def setup_function(function): + + clean() + + +def teardown_function(function): + + clean() + + +def clean(): + pass + + +# DNS utils testing +def test_get_dns_zone_from_domain_existing(): + assert _get_dns_zone_for_domain("yunohost.org") == "yunohost.org" + assert _get_dns_zone_for_domain("donate.yunohost.org") == "yunohost.org" + assert _get_dns_zone_for_domain("fr.wikipedia.org") == "wikipedia.org" + assert _get_dns_zone_for_domain("www.fr.wikipedia.org") == "wikipedia.org" + assert _get_dns_zone_for_domain("non-existing-domain.yunohost.org") == "yunohost.org" + assert _get_dns_zone_for_domain("yolo.nohost.me") == "nohost.me" + assert _get_dns_zone_for_domain("foo.yolo.nohost.me") == "nohost.me" + assert _get_dns_zone_for_domain("yolo.test") == "test" + assert _get_dns_zone_for_domain("foo.yolo.test") == "test" + + +# Domain registrar testing +def test_registrar_list_integrity(): + assert read_toml(DOMAIN_REGISTRAR_LIST_PATH) + + +def test_magic_guess_registrar_weird_domain(): + assert _get_registrar_config_section("yolo.test")["explanation"]["value"] is None + + +def test_magic_guess_registrar_ovh(): + assert _get_registrar_config_section("yolo.yunohost.org")["explanation"]["value"] == "ovh" + + +def test_magic_guess_registrar_yunodyndns(): + assert _get_registrar_config_section("yolo.nohost.me")["explanation"]["value"] == "yunohost" + + +#def domain_dns_suggest(domain): +# return yunohost.dns.domain_dns_conf(domain) +# +# +#def domain_dns_push(domain, dry_run): +# import yunohost.dns +# return yunohost.dns.domain_registrar_push(domain, dry_run) diff --git a/src/yunohost/tests/test_domains.py b/src/yunohost/tests/test_domains.py index c75954118..04f434b6c 100644 --- a/src/yunohost/tests/test_domains.py +++ b/src/yunohost/tests/test_domains.py @@ -1,24 +1,18 @@ import pytest - -import yaml import os from moulinette.core import MoulinetteError -from yunohost.utils.error import YunohostError, YunohostValidationError -from yunohost.utils.dns import get_dns_zone_from_domain +from yunohost.utils.error import YunohostValidationError from yunohost.domain import ( DOMAIN_SETTINGS_DIR, - REGISTRAR_LIST_PATH, _get_maindomain, domain_add, domain_remove, domain_list, domain_main_domain, - domain_setting, - domain_dns_conf, - domain_registrar_set, - domain_registrar_catalog + domain_config_get, + domain_config_set, ) TEST_DOMAINS = [ @@ -27,6 +21,7 @@ TEST_DOMAINS = [ "other-example.com" ] + def setup_function(function): # Save domain list in variable to avoid multiple calls to domain_list() @@ -40,8 +35,8 @@ def setup_function(function): os.system(f"rm -rf {DOMAIN_SETTINGS_DIR}/{TEST_DOMAINS[0]}.yml") if not _get_maindomain() == TEST_DOMAINS[0]: - domain_main_domain(TEST_DOMAINS[0]) - + domain_main_domain(TEST_DOMAINS[0]) + # Clear other domains for domain in domains: if domain not in TEST_DOMAINS or domain == TEST_DOMAINS[2]: @@ -51,7 +46,6 @@ def setup_function(function): # Reset settings if any os.system(f"rm -rf {DOMAIN_SETTINGS_DIR}/{domain}.yml") - # Create classical second domain of not exist if TEST_DOMAINS[1] not in domains: domain_add(TEST_DOMAINS[1]) @@ -65,101 +59,62 @@ def teardown_function(function): clean() + def clean(): pass + # Domains management testing def test_domain_add(): assert TEST_DOMAINS[2] not in domain_list()["domains"] domain_add(TEST_DOMAINS[2]) assert TEST_DOMAINS[2] in domain_list()["domains"] + def test_domain_add_existing_domain(): - with pytest.raises(MoulinetteError) as e_info: + with pytest.raises(MoulinetteError): assert TEST_DOMAINS[1] in domain_list()["domains"] domain_add(TEST_DOMAINS[1]) + def test_domain_remove(): assert TEST_DOMAINS[1] in domain_list()["domains"] domain_remove(TEST_DOMAINS[1]) assert TEST_DOMAINS[1] not in domain_list()["domains"] + def test_main_domain(): current_main_domain = _get_maindomain() assert domain_main_domain()["current_main_domain"] == current_main_domain + def test_main_domain_change_unknown(): - with pytest.raises(YunohostValidationError) as e_info: + with pytest.raises(YunohostValidationError): domain_main_domain(TEST_DOMAINS[2]) + def test_change_main_domain(): assert _get_maindomain() != TEST_DOMAINS[1] domain_main_domain(TEST_DOMAINS[1]) - assert _get_maindomain() == TEST_DOMAINS[1] + assert _get_maindomain() == TEST_DOMAINS[1] + # Domain settings testing -def test_domain_setting_get_default_xmpp_main_domain(): - assert TEST_DOMAINS[0] in domain_list()["domains"] - assert domain_setting(TEST_DOMAINS[0], "xmpp") == True +def test_domain_config_get_default(): + assert domain_config_get(TEST_DOMAINS[0], "feature.xmpp.xmpp") == 1 + assert domain_config_get(TEST_DOMAINS[1], "feature.xmpp.xmpp") == 0 + assert domain_config_get(TEST_DOMAINS[1], "dns.advanced.ttl") == 3600 -def test_domain_setting_get_default_xmpp(): - assert domain_setting(TEST_DOMAINS[1], "xmpp") == False -def test_domain_setting_get_default_ttl(): - assert domain_setting(TEST_DOMAINS[1], "ttl") == 3600 +def test_domain_config_set(): + assert domain_config_get(TEST_DOMAINS[1], "feature.xmpp.xmpp") == 0 + domain_config_set(TEST_DOMAINS[1], "feature.xmpp.xmpp", "yes") + assert domain_config_get(TEST_DOMAINS[1], "feature.xmpp.xmpp") == 1 -def test_domain_setting_set_int(): - domain_setting(TEST_DOMAINS[1], "ttl", "10") - assert domain_setting(TEST_DOMAINS[1], "ttl") == 10 + domain_config_set(TEST_DOMAINS[1], "dns.advanced.ttl", 10) + assert domain_config_get(TEST_DOMAINS[1], "dns.advanced.ttl") == 10 -def test_domain_setting_set_bool_true(): - domain_setting(TEST_DOMAINS[1], "xmpp", "True") - assert domain_setting(TEST_DOMAINS[1], "xmpp") == True - domain_setting(TEST_DOMAINS[1], "xmpp", "true") - assert domain_setting(TEST_DOMAINS[1], "xmpp") == True - domain_setting(TEST_DOMAINS[1], "xmpp", "t") - assert domain_setting(TEST_DOMAINS[1], "xmpp") == True - domain_setting(TEST_DOMAINS[1], "xmpp", "1") - assert domain_setting(TEST_DOMAINS[1], "xmpp") == True - domain_setting(TEST_DOMAINS[1], "xmpp", "yes") - assert domain_setting(TEST_DOMAINS[1], "xmpp") == True - domain_setting(TEST_DOMAINS[1], "xmpp", "y") - assert domain_setting(TEST_DOMAINS[1], "xmpp") == True -def test_domain_setting_set_bool_false(): - domain_setting(TEST_DOMAINS[1], "xmpp", "False") - assert domain_setting(TEST_DOMAINS[1], "xmpp") == False - domain_setting(TEST_DOMAINS[1], "xmpp", "false") - assert domain_setting(TEST_DOMAINS[1], "xmpp") == False - domain_setting(TEST_DOMAINS[1], "xmpp", "f") - assert domain_setting(TEST_DOMAINS[1], "xmpp") == False - domain_setting(TEST_DOMAINS[1], "xmpp", "0") - assert domain_setting(TEST_DOMAINS[1], "xmpp") == False - domain_setting(TEST_DOMAINS[1], "xmpp", "no") - assert domain_setting(TEST_DOMAINS[1], "xmpp") == False - domain_setting(TEST_DOMAINS[1], "xmpp", "n") - assert domain_setting(TEST_DOMAINS[1], "xmpp") == False - -def test_domain_settings_unknown(): - with pytest.raises(YunohostValidationError) as e_info: - domain_setting(TEST_DOMAINS[2], "xmpp", "False") - -# DNS utils testing -def test_get_dns_zone_from_domain_existing(): - assert get_dns_zone_from_domain("donate.yunohost.org") == "yunohost.org" - -def test_get_dns_zone_from_domain_not_existing(): - assert get_dns_zone_from_domain("non-existing-domain.yunohost.org") == "yunohost.org" - -# Domain registrar testing -def test_registrar_list_yaml_integrity(): - yaml.load(open(REGISTRAR_LIST_PATH, 'r')) - -def test_domain_registrar_catalog(): - domain_registrar_catalog() - -def test_domain_registrar_catalog_full(): - domain_registrar_catalog(None, True) - -def test_domain_registrar_catalog_registrar(): - domain_registrar_catalog("ovh") +def test_domain_configs_unknown(): + with pytest.raises(YunohostValidationError): + domain_config_get(TEST_DOMAINS[2], "feature.xmpp.xmpp.xmpp") diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 6bc8384bf..7c839e359 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -170,7 +170,9 @@ class ConfigPanel: raise YunohostError(f"The filter key {filter_key} has too many sub-levels, the max is 3.", raw_msg=True) if not os.path.exists(self.config_path): + logger.debug(f"Config panel {self.config_path} doesn't exists") return None + toml_config_panel = self._get_toml() # Check TOML config panel is in a supported version diff --git a/src/yunohost/utils/dns.py b/src/yunohost/utils/dns.py index 848cafeba..9af6df8d6 100644 --- a/src/yunohost/utils/dns.py +++ b/src/yunohost/utils/dns.py @@ -21,6 +21,8 @@ import dns.resolver from moulinette.utils.filesystem import read_file +YNH_DYNDNS_DOMAINS = ["nohost.me", "noho.st", "ynh.fr"] + # Lazy dev caching to avoid re-reading the file multiple time when calling # dig() often during same yunohost operation external_resolvers_ = [] @@ -90,33 +92,3 @@ def dig( return ("ok", answers) - -def get_dns_zone_from_domain(domain): - # TODO Check if this function is YNH_DYNDNS_DOMAINS compatible - """ - Get the DNS zone of a domain - - Keyword arguments: - domain -- The domain name - - """ - - # For foo.bar.baz.gni we want to scan all the parent domains - # (including the domain itself) - # foo.bar.baz.gni - # bar.baz.gni - # baz.gni - # gni - parent_list = [domain.split(".", i)[-1] - for i, _ in enumerate(domain.split("."))] - - for parent in parent_list: - - # Check if there's a NS record for that domain - answer = dig(parent, rdtype="NS", full_answers=True, resolvers="force_external") - if answer[0] == "ok": - # Domain is dns_zone - return parent - - # FIXME: returning None will probably trigger bugs when this happens, code expects a domain string - return None diff --git a/src/yunohost/utils/ldap.py b/src/yunohost/utils/ldap.py index 4f571ce6f..9edb2960b 100644 --- a/src/yunohost/utils/ldap.py +++ b/src/yunohost/utils/ldap.py @@ -101,7 +101,8 @@ class LDAPInterface: except ldap.SERVER_DOWN: 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'" + "You can try to investigate what's happening with 'systemctl status slapd'", + raw_msg=True ) # Check that we are indeed logged in with the right identity @@ -289,7 +290,7 @@ class LDAPInterface: attr_found[0], attr_found[1], ) - raise MoulinetteError( + raise YunohostError( "ldap_attribute_already_exists", attribute=attr_found[0], value=attr_found[1], From 2710ca72719b9b90c798bbc066b40682098290bd Mon Sep 17 00:00:00 2001 From: ljf Date: Mon, 13 Sep 2021 00:25:55 +0200 Subject: [PATCH 2997/3170] [fix] Missing default property --- locales/en.json | 2 ++ src/yunohost/utils/config.py | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/locales/en.json b/locales/en.json index eb131fe43..fe340fff2 100644 --- a/locales/en.json +++ b/locales/en.json @@ -394,6 +394,8 @@ "hook_name_unknown": "Unknown hook name '{name}'", "installation_complete": "Installation completed", "invalid_number": "Must be a number", + "invalid_number_min": "Must be greater than {min}", + "invalid_number_max": "Must be lesser than {max}", "invalid_password": "Invalid password", "invalid_regex": "Invalid regex:'{regex}'", "ip6tables_unavailable": "You cannot play with ip6tables here. You are either in a container or your kernel does not support it", diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 270503f8f..e0b893356 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -202,7 +202,7 @@ class ConfigPanel: } }, "options": { - "properties": ["ask", "type", "bind", "help", "example", + "properties": ["ask", "type", "bind", "help", "example", "default", "style", "icon", "placeholder", "visible", "optional", "choices", "yes", "no", "pattern", "limit", "min", "max", "step", "accept", "redact"], @@ -787,14 +787,14 @@ class NumberQuestion(Question): raise YunohostValidationError( "app_argument_invalid", name=self.name, - error=m18n.n("invalid_number"), + error=m18n.n("invalid_number_min", min=self.min), ) if self.max is not None and int(self.value) > self.max: raise YunohostValidationError( "app_argument_invalid", name=self.name, - error=m18n.n("invalid_number"), + error=m18n.n("invalid_number_max", max=self.max), ) From ce34bb75c49fbe9ded178c94a63e1715800277c2 Mon Sep 17 00:00:00 2001 From: ljf Date: Mon, 13 Sep 2021 00:47:21 +0200 Subject: [PATCH 2998/3170] [enh] Avoid to raise error with bin null and empty value --- src/yunohost/utils/config.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index e0b893356..6cc693740 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -280,7 +280,8 @@ class ConfigPanel: logger.debug("Hydrating config with current values") for _, _, option in self._iterate(): if option["id"] not in self.values: - if option["type"] in ["alert", "display_text", "markdown", "file"]: + allowed_empty_types = ["alert", "display_text", "markdown", "file"] + if option["type"] in allowed_empty_type or option["bind"] == "null": continue else: raise YunohostError("config_missing_init_value", question=option["id"]) From 02814833d5a84d6cd241918ab9fb6b564e43c3e1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 13 Sep 2021 00:50:36 +0200 Subject: [PATCH 2999/3170] Misc fixes, try to fix tests --- locales/en.json | 1 - src/yunohost/tests/test_app_config.py | 10 +++++---- src/yunohost/utils/config.py | 29 ++++++++++++++++----------- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/locales/en.json b/locales/en.json index fe340fff2..f3b1fc906 100644 --- a/locales/en.json +++ b/locales/en.json @@ -143,7 +143,6 @@ "config_apply_failed": "Applying the new configuration failed: {error}", "config_cant_set_value_on_section": "You can't set a single value on an entire config section.", "config_forbidden_keyword": "The keyword '{keyword}' is reserved, you can't create or use a config panel with a question with this id.", - "config_missing_init_value": "Config panel question '{question}' should be initialize with a value during install or upgrade.", "config_no_panel": "No config panel found.", "config_unknown_filter_key": "The filter key '{filter_key}' is incorrect.", "config_validate_color": "Should be a valid RGB hexadecimal color", diff --git a/src/yunohost/tests/test_app_config.py b/src/yunohost/tests/test_app_config.py index 4ace0aaf9..52f458b55 100644 --- a/src/yunohost/tests/test_app_config.py +++ b/src/yunohost/tests/test_app_config.py @@ -105,9 +105,7 @@ def test_app_config_get(config_app): assert isinstance(app_config_get(config_app, export=True), dict) assert isinstance(app_config_get(config_app, "main"), dict) assert isinstance(app_config_get(config_app, "main.components"), dict) - # Is it expected that we return None if no value defined yet ? - # c.f. the whole discussion about "should we have defaults" etc. - assert app_config_get(config_app, "main.components.boolean") is None + assert app_config_get(config_app, "main.components.boolean") == "0" def test_app_config_nopanel(legacy_app): @@ -130,10 +128,14 @@ def test_app_config_get_nonexistentstuff(config_app): with pytest.raises(YunohostValidationError): app_config_get(config_app, "main.components.nonexistent") + app_setting(config_app, "boolean", delete=True) + with pytest.raises(YunohostValidationError): + app_config_get(config_app, "main.components.boolean") + def test_app_config_regular_setting(config_app): - assert app_config_get(config_app, "main.components.boolean") is None + assert app_config_get(config_app, "main.components.boolean") == "0" app_config_set(config_app, "main.components.boolean", "no") diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 6cc693740..2ee01f97f 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -209,6 +209,7 @@ class ConfigPanel: "default": {} } } + def convert(toml_node, node_type): """Convert TOML in internal format ('full' mode used by webadmin) Here are some properties of 1.0 config panel in toml: @@ -284,7 +285,7 @@ class ConfigPanel: if option["type"] in allowed_empty_type or option["bind"] == "null": continue else: - raise YunohostError("config_missing_init_value", question=option["id"]) + raise YunohostError(f"Config panel question '{option['id']}' should be initialized with a value during install or upgrade.") value = self.values[option["name"]] # In general, the value is just a simple value. # Sometimes it could be a dict used to overwrite the option itself @@ -538,22 +539,25 @@ class StringQuestion(Question): argument_type = "string" default_value = "" + class EmailQuestion(StringQuestion): pattern = { - "regexp": "^.+@.+", - "error": "config_validate_email" + "regexp": r"^.+@.+", + "error": "config_validate_email" # i18n: config_validate_email } + class URLQuestion(StringQuestion): pattern = { - "regexp": "^https?://.*$", - "error": "config_validate_url" + "regexp": r"^https?://.*$", + "error": "config_validate_url" # i18n: config_validate_url } + class DateQuestion(StringQuestion): pattern = { - "regexp": "^\d{4}-\d\d-\d\d$", - "error": "config_validate_date" + "regexp": r"^\d{4}-\d\d-\d\d$", + "error": "config_validate_date" # i18n: config_validate_date } def _prevalidate(self): @@ -566,16 +570,18 @@ class DateQuestion(StringQuestion): except ValueError: raise YunohostValidationError("config_validate_date") + class TimeQuestion(StringQuestion): pattern = { - "regexp": "^(1[12]|0?\d):[0-5]\d$", - "error": "config_validate_time" + "regexp": r"^(1[12]|0?\d):[0-5]\d$", + "error": "config_validate_time" # i18n: config_validate_time } + class ColorQuestion(StringQuestion): pattern = { - "regexp": "^#[ABCDEFabcdef\d]{3,6}$", - "error": "config_validate_color" + "regexp": r"^#[ABCDEFabcdef\d]{3,6}$", + "error": "config_validate_color" # i18n: config_validate_color } @@ -799,7 +805,6 @@ class NumberQuestion(Question): ) - class DisplayTextQuestion(Question): argument_type = "display_text" readonly = True From 1d704e7b91f3006250d09868486127f9af9b59ab Mon Sep 17 00:00:00 2001 From: ljf Date: Mon, 13 Sep 2021 01:24:10 +0200 Subject: [PATCH 3000/3170] [enh] Avoid to raise error with bin null and empty value --- src/yunohost/utils/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 6cc693740..4788a199a 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -281,7 +281,7 @@ class ConfigPanel: for _, _, option in self._iterate(): if option["id"] not in self.values: allowed_empty_types = ["alert", "display_text", "markdown", "file"] - if option["type"] in allowed_empty_type or option["bind"] == "null": + if option["type"] in allowed_empty_types or option["bind"] == "null": continue else: raise YunohostError("config_missing_init_value", question=option["id"]) From efe9ae195bee9b541813292cf6b407bd3e65142e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 13 Sep 2021 01:26:43 +0200 Subject: [PATCH 3001/3170] Update locales/fr.json --- locales/fr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/fr.json b/locales/fr.json index 0b9bc2dbd..da6a41558 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -659,5 +659,5 @@ "user_import_bad_line": "Ligne incorrecte {line} : {details}", "log_user_import": "Importer des utilisateurs", "diagnosis_high_number_auth_failures": "Il y a eu récemment un grand nombre d'échecs d'authentification. Assurez-vous que Fail2Ban est en cours d'exécution et est correctement configuré, ou utilisez un port personnalisé pour SSH comme expliqué dans https://yunohost.org/security.", - "global_settings_setting_security_nginx_redirect_to_https": "Redirigez les requêtes HTTP vers HTTPS par défaut (NE PAS DÉSACTIVER à moins de savoir vraiment ce que vous faites !)" + "global_settings_setting_security_nginx_redirect_to_https": "Rediriger les requêtes HTTP vers HTTPS par défaut (NE PAS DÉSACTIVER à moins de savoir vraiment ce que vous faites !)" } From 380321a6eb0566b7ad83d5015b164d6be7a20438 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 13 Sep 2021 01:50:04 +0200 Subject: [PATCH 3002/3170] config: bind key may not exist in option --- src/yunohost/utils/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index d5dc9f598..fa461d43b 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -282,7 +282,7 @@ class ConfigPanel: for _, _, option in self._iterate(): if option["id"] not in self.values: allowed_empty_types = ["alert", "display_text", "markdown", "file"] - if option["type"] in allowed_empty_types or option["bind"] == "null": + if option["type"] in allowed_empty_types or option.get("bind") == "null": continue else: raise YunohostError(f"Config panel question '{option['id']}' should be initialized with a value during install or upgrade.") From 968cac32d12f950013ab466e95b23805069f44c9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 13 Sep 2021 01:55:57 +0200 Subject: [PATCH 3003/3170] new config helpers: use $() instead of backticks Co-authored-by: Kayou --- data/helpers.d/config | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/config b/data/helpers.d/config index 8f3248949..d12065220 100644 --- a/data/helpers.d/config +++ b/data/helpers.d/config @@ -4,7 +4,7 @@ _ynh_app_config_get() { # From settings local lines - lines=`python3 << EOL + lines=$(python3 << EOL import toml from collections import OrderedDict with open("../config_panel.toml", "r") as f: @@ -24,7 +24,7 @@ for panel_name, panel in loaded_toml.items(): param.get('bind', 'settings' if param.get('type', 'string') != 'file' else 'null') ])) EOL -` +) for line in $lines do # Split line into short_setting, type and bind From 869eb33e04c0abe77b6ae8194951ed350ba4a15d Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Mon, 13 Sep 2021 00:38:23 +0000 Subject: [PATCH 3004/3170] [CI] Format code --- src/yunohost/app.py | 39 ++- src/yunohost/backup.py | 9 +- src/yunohost/service.py | 6 +- src/yunohost/tests/test_app_config.py | 6 +- src/yunohost/tests/test_questions.py | 485 +++++++++++++++----------- src/yunohost/utils/config.py | 126 +++++-- 6 files changed, 417 insertions(+), 254 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 4047369e0..d92aba373 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -511,7 +511,12 @@ def app_upgrade(app=[], url=None, file=None, force=False, no_safety_backup=False """ from packaging import version - from yunohost.hook import hook_add, hook_remove, hook_callback, hook_exec_with_script_debug_if_failure + from yunohost.hook import ( + hook_add, + hook_remove, + hook_callback, + hook_exec_with_script_debug_if_failure, + ) from yunohost.permission import permission_sync_to_user from yunohost.regenconf import manually_modified_files @@ -633,12 +638,17 @@ def app_upgrade(app=[], url=None, file=None, force=False, no_safety_backup=False # Execute the app upgrade script upgrade_failed = True try: - upgrade_failed, failure_message_with_debug_instructions = hook_exec_with_script_debug_if_failure( + ( + upgrade_failed, + failure_message_with_debug_instructions, + ) = hook_exec_with_script_debug_if_failure( extracted_app_folder + "/scripts/upgrade", env=env_dict, operation_logger=operation_logger, error_message_if_script_failed=m18n.n("app_upgrade_script_failed"), - error_message_if_failed=lambda e: m18n.n("app_upgrade_failed", app=app_instance_name, error=e) + error_message_if_failed=lambda e: m18n.n( + "app_upgrade_failed", app=app_instance_name, error=e + ), ) finally: # Whatever happened (install success or failure) we check if it broke the system @@ -669,7 +679,7 @@ def app_upgrade(app=[], url=None, file=None, force=False, no_safety_backup=False if upgrade_failed or broke_the_system: # display this if there are remaining apps - if apps[number + 1:]: + if apps[number + 1 :]: not_upgraded_apps = apps[number:] logger.error( m18n.n( @@ -785,7 +795,13 @@ def app_install( force -- Do not ask for confirmation when installing experimental / low-quality apps """ - from yunohost.hook import hook_add, hook_remove, hook_callback, hook_exec, hook_exec_with_script_debug_if_failure + from yunohost.hook import ( + hook_add, + hook_remove, + hook_callback, + hook_exec, + hook_exec_with_script_debug_if_failure, + ) from yunohost.log import OperationLogger from yunohost.permission import ( user_permission_list, @@ -976,12 +992,17 @@ def app_install( # Execute the app install script install_failed = True try: - install_failed, failure_message_with_debug_instructions = hook_exec_with_script_debug_if_failure( + ( + install_failed, + failure_message_with_debug_instructions, + ) = hook_exec_with_script_debug_if_failure( os.path.join(extracted_app_folder, "scripts/install"), env=env_dict, operation_logger=operation_logger, error_message_if_script_failed=m18n.n("app_install_script_failed"), - error_message_if_failed=lambda e: m18n.n("app_install_failed", app=app_id, error=e) + error_message_if_failed=lambda e: m18n.n( + "app_install_failed", app=app_id, error=e + ), ) finally: # If success so far, validate that app didn't break important stuff @@ -1670,7 +1691,9 @@ def app_config_get(app, key="", full=False, export=False): Display an app configuration in classic, full or export mode """ if full and export: - raise YunohostValidationError("You can't use --full and --export together.", raw_msg=True) + raise YunohostValidationError( + "You can't use --full and --export together.", raw_msg=True + ) if full: mode = "full" diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index c39bf656c..15abc08ef 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -1496,13 +1496,18 @@ class RestoreManager: # Execute the app install script restore_failed = True try: - restore_failed, failure_message_with_debug_instructions = hook_exec_with_script_debug_if_failure( + ( + restore_failed, + failure_message_with_debug_instructions, + ) = hook_exec_with_script_debug_if_failure( restore_script, chdir=app_backup_in_archive, env=env_dict, operation_logger=operation_logger, error_message_if_script_failed=m18n.n("app_restore_script_failed"), - error_message_if_failed=lambda e: m18n.n("app_restore_failed", app=app_instance_name, error=e) + error_message_if_failed=lambda e: m18n.n( + "app_restore_failed", app=app_instance_name, error=e + ), ) finally: # Cleaning temporary scripts directory diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 6e3b2d7a6..f200d08c0 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -288,7 +288,11 @@ def service_reload_or_restart(names, test_conf=True): if p.returncode != 0: errors = out.decode().strip().split("\n") logger.error( - m18n.n("service_not_reloading_because_conf_broken", name=name, errors=errors) + m18n.n( + "service_not_reloading_because_conf_broken", + name=name, + errors=errors, + ) ) continue diff --git a/src/yunohost/tests/test_app_config.py b/src/yunohost/tests/test_app_config.py index 52f458b55..3767e9e52 100644 --- a/src/yunohost/tests/test_app_config.py +++ b/src/yunohost/tests/test_app_config.py @@ -80,7 +80,6 @@ def legacy_app(request): return "legacy_app" - @pytest.fixture() def config_app(request): @@ -168,7 +167,10 @@ def test_app_config_bind_on_file(config_app): def test_app_config_custom_get(config_app): assert app_setting(config_app, "arg9") is None - assert "Files in /var/www" in app_config_get(config_app, "bind.function.arg9")["ask"]["en"] + assert ( + "Files in /var/www" + in app_config_get(config_app, "bind.function.arg9")["ask"]["en"] + ) assert app_setting(config_app, "arg9") is None diff --git a/src/yunohost/tests/test_questions.py b/src/yunohost/tests/test_questions.py index eaaad1791..93149b272 100644 --- a/src/yunohost/tests/test_questions.py +++ b/src/yunohost/tests/test_questions.py @@ -12,7 +12,7 @@ from yunohost import domain, user from yunohost.utils.config import ( parse_args_in_yunohost_format, PasswordQuestion, - Question + Question, ) from yunohost.utils.error import YunohostError @@ -75,8 +75,7 @@ def test_question_string_no_input(): ] answers = {} - with pytest.raises(YunohostError), \ - patch.object(os, "isatty", return_value=False): + with pytest.raises(YunohostError), patch.object(os, "isatty", return_value=False): parse_args_in_yunohost_format(answers, questions) @@ -90,8 +89,9 @@ def test_question_string_input(): answers = {} expected_result = OrderedDict({"some_string": ("some_value", "string")}) - with patch.object(Moulinette, "prompt", return_value="some_value"), \ - patch.object(os, "isatty", return_value=True): + with patch.object(Moulinette, "prompt", return_value="some_value"), patch.object( + os, "isatty", return_value=True + ): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -104,8 +104,9 @@ def test_question_string_input_no_ask(): answers = {} expected_result = OrderedDict({"some_string": ("some_value", "string")}) - with patch.object(Moulinette, "prompt", return_value="some_value"), \ - patch.object(os, "isatty", return_value=True): + with patch.object(Moulinette, "prompt", return_value="some_value"), patch.object( + os, "isatty", return_value=True + ): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -133,8 +134,9 @@ def test_question_string_optional_with_input(): answers = {} expected_result = OrderedDict({"some_string": ("some_value", "string")}) - with patch.object(Moulinette, "prompt", return_value="some_value"), \ - patch.object(os, "isatty", return_value=True): + with patch.object(Moulinette, "prompt", return_value="some_value"), patch.object( + os, "isatty", return_value=True + ): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -149,8 +151,9 @@ def test_question_string_optional_with_empty_input(): answers = {} expected_result = OrderedDict({"some_string": ("", "string")}) - with patch.object(Moulinette, "prompt", return_value=""), \ - patch.object(os, "isatty", return_value=True): + with patch.object(Moulinette, "prompt", return_value=""), patch.object( + os, "isatty", return_value=True + ): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -164,8 +167,9 @@ def test_question_string_optional_with_input_without_ask(): answers = {} expected_result = OrderedDict({"some_string": ("some_value", "string")}) - with patch.object(Moulinette, "prompt", return_value="some_value"), \ - patch.object(os, "isatty", return_value=True): + with patch.object(Moulinette, "prompt", return_value="some_value"), patch.object( + os, "isatty", return_value=True + ): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -198,8 +202,11 @@ def test_question_string_input_test_ask(): ) as prompt, patch.object(os, "isatty", return_value=True): parse_args_in_yunohost_format(answers, questions) prompt.assert_called_with( - message=ask_text, is_password=False, confirm=False, - prefill='', is_multiline=False + message=ask_text, + is_password=False, + confirm=False, + prefill="", + is_multiline=False, ) @@ -221,8 +228,10 @@ def test_question_string_input_test_ask_with_default(): parse_args_in_yunohost_format(answers, questions) prompt.assert_called_with( message=ask_text, - is_password=False, confirm=False, - prefill=default_text, is_multiline=False + is_password=False, + confirm=False, + prefill=default_text, + is_multiline=False, ) @@ -243,8 +252,8 @@ def test_question_string_input_test_ask_with_example(): Moulinette, "prompt", return_value="some_value" ) as prompt, patch.object(os, "isatty", return_value=True): parse_args_in_yunohost_format(answers, questions) - assert ask_text in prompt.call_args[1]['message'] - assert example_text in prompt.call_args[1]['message'] + assert ask_text in prompt.call_args[1]["message"] + assert example_text in prompt.call_args[1]["message"] @pytest.mark.skip # we should do something with this help @@ -264,8 +273,8 @@ def test_question_string_input_test_ask_with_help(): Moulinette, "prompt", return_value="some_value" ) as prompt, patch.object(os, "isatty", return_value=True): parse_args_in_yunohost_format(answers, questions) - assert ask_text in prompt.call_args[1]['message'] - assert help_text in prompt.call_args[1]['message'] + assert ask_text in prompt.call_args[1]["message"] + assert help_text in prompt.call_args[1]["message"] def test_question_string_with_choice(): @@ -279,8 +288,9 @@ def test_question_string_with_choice_prompt(): questions = [{"name": "some_string", "type": "string", "choices": ["fr", "en"]}] answers = {"some_string": "fr"} expected_result = OrderedDict({"some_string": ("fr", "string")}) - with patch.object(Moulinette, "prompt", return_value="fr"), \ - patch.object(os, "isatty", return_value=True): + with patch.object(Moulinette, "prompt", return_value="fr"), patch.object( + os, "isatty", return_value=True + ): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -288,8 +298,7 @@ def test_question_string_with_choice_bad(): questions = [{"name": "some_string", "type": "string", "choices": ["fr", "en"]}] answers = {"some_string": "bad"} - with pytest.raises(YunohostError), \ - patch.object(os, "isatty", return_value=False): + with pytest.raises(YunohostError), patch.object(os, "isatty", return_value=False): assert parse_args_in_yunohost_format(answers, questions) @@ -305,13 +314,14 @@ def test_question_string_with_choice_ask(): ] answers = {} - with patch.object(Moulinette, "prompt", return_value="ru") as prompt, \ - patch.object(os, "isatty", return_value=True): + with patch.object(Moulinette, "prompt", return_value="ru") as prompt, patch.object( + os, "isatty", return_value=True + ): parse_args_in_yunohost_format(answers, questions) - assert ask_text in prompt.call_args[1]['message'] + assert ask_text in prompt.call_args[1]["message"] for choice in choices: - assert choice in prompt.call_args[1]['message'] + assert choice in prompt.call_args[1]["message"] def test_question_string_with_choice_default(): @@ -352,8 +362,7 @@ def test_question_password_no_input(): ] answers = {} - with pytest.raises(YunohostError), \ - patch.object(os, "isatty", return_value=False): + with pytest.raises(YunohostError), patch.object(os, "isatty", return_value=False): parse_args_in_yunohost_format(answers, questions) @@ -366,14 +375,16 @@ def test_question_password_input(): } ] answers = {} - Question.operation_logger = { 'data_to_redact': [] } + Question.operation_logger = {"data_to_redact": []} expected_result = OrderedDict({"some_password": ("some_value", "password")}) Question.operation_logger = MagicMock() - with patch.object(Question.operation_logger, "data_to_redact", create=True), \ - patch.object(Moulinette, "prompt", return_value="some_value"), \ - patch.object(os, "isatty", return_value=True): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + with patch.object( + Question.operation_logger, "data_to_redact", create=True + ), patch.object(Moulinette, "prompt", return_value="some_value"), patch.object( + os, "isatty", return_value=True + ): + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_question_password_input_no_ask(): @@ -387,9 +398,11 @@ def test_question_password_input_no_ask(): expected_result = OrderedDict({"some_password": ("some_value", "password")}) Question.operation_logger = MagicMock() - with patch.object(Question.operation_logger, "data_to_redact", create=True), \ - patch.object(Moulinette, "prompt", return_value="some_value"), \ - patch.object(os, "isatty", return_value=True): + with patch.object( + Question.operation_logger, "data_to_redact", create=True + ), patch.object(Moulinette, "prompt", return_value="some_value"), patch.object( + os, "isatty", return_value=True + ): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -405,8 +418,9 @@ def test_question_password_no_input_optional(): expected_result = OrderedDict({"some_password": ("", "password")}) Question.operation_logger = MagicMock() - with patch.object(Question.operation_logger, "data_to_redact", create=True), \ - patch.object(os, "isatty", return_value=False): + with patch.object( + Question.operation_logger, "data_to_redact", create=True + ), patch.object(os, "isatty", return_value=False): assert parse_args_in_yunohost_format(answers, questions) == expected_result questions = [ @@ -414,8 +428,9 @@ def test_question_password_no_input_optional(): ] Question.operation_logger = MagicMock() - with patch.object(Question.operation_logger, "data_to_redact", create=True), \ - patch.object(os, "isatty", return_value=False): + with patch.object( + Question.operation_logger, "data_to_redact", create=True + ), patch.object(os, "isatty", return_value=False): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -432,9 +447,11 @@ def test_question_password_optional_with_input(): expected_result = OrderedDict({"some_password": ("some_value", "password")}) Question.operation_logger = MagicMock() - with patch.object(Question.operation_logger, "data_to_redact", create=True), \ - patch.object(Moulinette, "prompt", return_value="some_value"), \ - patch.object(os, "isatty", return_value=True): + with patch.object( + Question.operation_logger, "data_to_redact", create=True + ), patch.object(Moulinette, "prompt", return_value="some_value"), patch.object( + os, "isatty", return_value=True + ): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -451,9 +468,11 @@ def test_question_password_optional_with_empty_input(): expected_result = OrderedDict({"some_password": ("", "password")}) Question.operation_logger = MagicMock() - with patch.object(Question.operation_logger, "data_to_redact", create=True), \ - patch.object(Moulinette, "prompt", return_value=""), \ - patch.object(os, "isatty", return_value=True): + with patch.object( + Question.operation_logger, "data_to_redact", create=True + ), patch.object(Moulinette, "prompt", return_value=""), patch.object( + os, "isatty", return_value=True + ): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -469,9 +488,11 @@ def test_question_password_optional_with_input_without_ask(): expected_result = OrderedDict({"some_password": ("some_value", "password")}) Question.operation_logger = MagicMock() - with patch.object(Question.operation_logger, "data_to_redact", create=True), \ - patch.object(Moulinette, "prompt", return_value="some_value"), \ - patch.object(os, "isatty", return_value=True): + with patch.object( + Question.operation_logger, "data_to_redact", create=True + ), patch.object(Moulinette, "prompt", return_value="some_value"), patch.object( + os, "isatty", return_value=True + ): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -487,8 +508,7 @@ def test_question_password_no_input_default(): answers = {} # no default for password! - with pytest.raises(YunohostError), \ - patch.object(os, "isatty", return_value=False): + with pytest.raises(YunohostError), patch.object(os, "isatty", return_value=False): parse_args_in_yunohost_format(answers, questions) @@ -505,8 +525,7 @@ def test_question_password_no_input_example(): answers = {"some_password": "some_value"} # no example for password! - with pytest.raises(YunohostError), \ - patch.object(os, "isatty", return_value=False): + with pytest.raises(YunohostError), patch.object(os, "isatty", return_value=False): parse_args_in_yunohost_format(answers, questions) @@ -522,14 +541,20 @@ def test_question_password_input_test_ask(): answers = {} Question.operation_logger = MagicMock() - with patch.object(Question.operation_logger, "data_to_redact", create=True), \ - patch.object(Moulinette, "prompt", return_value="some_value") as prompt, \ - patch.object(os, "isatty", return_value=True): + with patch.object( + Question.operation_logger, "data_to_redact", create=True + ), patch.object( + Moulinette, "prompt", return_value="some_value" + ) as prompt, patch.object( + os, "isatty", return_value=True + ): parse_args_in_yunohost_format(answers, questions) prompt.assert_called_with( message=ask_text, - is_password=True, confirm=True, - prefill='', is_multiline=False + is_password=True, + confirm=True, + prefill="", + is_multiline=False, ) @@ -548,12 +573,16 @@ def test_question_password_input_test_ask_with_example(): answers = {} Question.operation_logger = MagicMock() - with patch.object(Question.operation_logger, "data_to_redact", create=True), \ - patch.object(Moulinette, "prompt", return_value="some_value") as prompt, \ - patch.object(os, "isatty", return_value=True): + with patch.object( + Question.operation_logger, "data_to_redact", create=True + ), patch.object( + Moulinette, "prompt", return_value="some_value" + ) as prompt, patch.object( + os, "isatty", return_value=True + ): parse_args_in_yunohost_format(answers, questions) - assert ask_text in prompt.call_args[1]['message'] - assert example_text in prompt.call_args[1]['message'] + assert ask_text in prompt.call_args[1]["message"] + assert example_text in prompt.call_args[1]["message"] @pytest.mark.skip # we should do something with this help @@ -571,12 +600,16 @@ def test_question_password_input_test_ask_with_help(): answers = {} Question.operation_logger = MagicMock() - with patch.object(Question.operation_logger, "data_to_redact", create=True), \ - patch.object(Moulinette, "prompt", return_value="some_value") as prompt, \ - patch.object(os, "isatty", return_value=True): + with patch.object( + Question.operation_logger, "data_to_redact", create=True + ), patch.object( + Moulinette, "prompt", return_value="some_value" + ) as prompt, patch.object( + os, "isatty", return_value=True + ): parse_args_in_yunohost_format(answers, questions) - assert ask_text in prompt.call_args[1]['message'] - assert help_text in prompt.call_args[1]['message'] + assert ask_text in prompt.call_args[1]["message"] + assert help_text in prompt.call_args[1]["message"] def test_question_password_bad_chars(): @@ -590,8 +623,9 @@ def test_question_password_bad_chars(): ] for i in PasswordQuestion.forbidden_chars: - with pytest.raises(YunohostError), \ - patch.object(os, "isatty", return_value=False): + with pytest.raises(YunohostError), patch.object( + os, "isatty", return_value=False + ): parse_args_in_yunohost_format({"some_password": i * 8}, questions) @@ -605,13 +639,11 @@ def test_question_password_strong_enough(): } ] - with pytest.raises(YunohostError), \ - patch.object(os, "isatty", return_value=False): + with pytest.raises(YunohostError), patch.object(os, "isatty", return_value=False): # too short parse_args_in_yunohost_format({"some_password": "a"}, questions) - with pytest.raises(YunohostError), \ - patch.object(os, "isatty", return_value=False): + with pytest.raises(YunohostError), patch.object(os, "isatty", return_value=False): parse_args_in_yunohost_format({"some_password": "password"}, questions) @@ -625,13 +657,11 @@ def test_question_password_optional_strong_enough(): } ] - with pytest.raises(YunohostError), \ - patch.object(os, "isatty", return_value=False): + with pytest.raises(YunohostError), patch.object(os, "isatty", return_value=False): # too short parse_args_in_yunohost_format({"some_password": "a"}, questions) - with pytest.raises(YunohostError), \ - patch.object(os, "isatty", return_value=False): + with pytest.raises(YunohostError), patch.object(os, "isatty", return_value=False): parse_args_in_yunohost_format({"some_password": "password"}, questions) @@ -656,8 +686,7 @@ def test_question_path_no_input(): ] answers = {} - with pytest.raises(YunohostError), \ - patch.object(os, "isatty", return_value=False): + with pytest.raises(YunohostError), patch.object(os, "isatty", return_value=False): parse_args_in_yunohost_format(answers, questions) @@ -672,8 +701,9 @@ def test_question_path_input(): answers = {} expected_result = OrderedDict({"some_path": ("some_value", "path")}) - with patch.object(Moulinette, "prompt", return_value="some_value"), \ - patch.object(os, "isatty", return_value=True): + with patch.object(Moulinette, "prompt", return_value="some_value"), patch.object( + os, "isatty", return_value=True + ): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -687,8 +717,9 @@ def test_question_path_input_no_ask(): answers = {} expected_result = OrderedDict({"some_path": ("some_value", "path")}) - with patch.object(Moulinette, "prompt", return_value="some_value"), \ - patch.object(os, "isatty", return_value=True): + with patch.object(Moulinette, "prompt", return_value="some_value"), patch.object( + os, "isatty", return_value=True + ): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -718,8 +749,9 @@ def test_question_path_optional_with_input(): answers = {} expected_result = OrderedDict({"some_path": ("some_value", "path")}) - with patch.object(Moulinette, "prompt", return_value="some_value"), \ - patch.object(os, "isatty", return_value=True): + with patch.object(Moulinette, "prompt", return_value="some_value"), patch.object( + os, "isatty", return_value=True + ): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -735,8 +767,9 @@ def test_question_path_optional_with_empty_input(): answers = {} expected_result = OrderedDict({"some_path": ("", "path")}) - with patch.object(Moulinette, "prompt", return_value=""), \ - patch.object(os, "isatty", return_value=True): + with patch.object(Moulinette, "prompt", return_value=""), patch.object( + os, "isatty", return_value=True + ): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -751,8 +784,9 @@ def test_question_path_optional_with_input_without_ask(): answers = {} expected_result = OrderedDict({"some_path": ("some_value", "path")}) - with patch.object(Moulinette, "prompt", return_value="some_value"), \ - patch.object(os, "isatty", return_value=True): + with patch.object(Moulinette, "prompt", return_value="some_value"), patch.object( + os, "isatty", return_value=True + ): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -788,8 +822,10 @@ def test_question_path_input_test_ask(): parse_args_in_yunohost_format(answers, questions) prompt.assert_called_with( message=ask_text, - is_password=False, confirm=False, - prefill='', is_multiline=False + is_password=False, + confirm=False, + prefill="", + is_multiline=False, ) @@ -812,8 +848,10 @@ def test_question_path_input_test_ask_with_default(): parse_args_in_yunohost_format(answers, questions) prompt.assert_called_with( message=ask_text, - is_password=False, confirm=False, - prefill=default_text, is_multiline=False + is_password=False, + confirm=False, + prefill=default_text, + is_multiline=False, ) @@ -835,8 +873,8 @@ def test_question_path_input_test_ask_with_example(): Moulinette, "prompt", return_value="some_value" ) as prompt, patch.object(os, "isatty", return_value=True): parse_args_in_yunohost_format(answers, questions) - assert ask_text in prompt.call_args[1]['message'] - assert example_text in prompt.call_args[1]['message'] + assert ask_text in prompt.call_args[1]["message"] + assert example_text in prompt.call_args[1]["message"] @pytest.mark.skip # we should do something with this help @@ -857,8 +895,8 @@ def test_question_path_input_test_ask_with_help(): Moulinette, "prompt", return_value="some_value" ) as prompt, patch.object(os, "isatty", return_value=True): parse_args_in_yunohost_format(answers, questions) - assert ask_text in prompt.call_args[1]['message'] - assert help_text in prompt.call_args[1]['message'] + assert ask_text in prompt.call_args[1]["message"] + assert help_text in prompt.call_args[1]["message"] def test_question_boolean(): @@ -906,8 +944,7 @@ def test_question_boolean_all_yes(): == expected_result ) assert ( - parse_args_in_yunohost_format({"some_boolean": 1}, questions) - == expected_result + parse_args_in_yunohost_format({"some_boolean": 1}, questions) == expected_result ) assert ( parse_args_in_yunohost_format({"some_boolean": True}, questions) @@ -960,8 +997,7 @@ def test_question_boolean_all_no(): == expected_result ) assert ( - parse_args_in_yunohost_format({"some_boolean": 0}, questions) - == expected_result + parse_args_in_yunohost_format({"some_boolean": 0}, questions) == expected_result ) assert ( parse_args_in_yunohost_format({"some_boolean": False}, questions) @@ -1005,8 +1041,7 @@ def test_question_boolean_bad_input(): ] answers = {"some_boolean": "stuff"} - with pytest.raises(YunohostError), \ - patch.object(os, "isatty", return_value=False): + with pytest.raises(YunohostError), patch.object(os, "isatty", return_value=False): parse_args_in_yunohost_format(answers, questions) @@ -1021,13 +1056,15 @@ def test_question_boolean_input(): answers = {} expected_result = OrderedDict({"some_boolean": (1, "boolean")}) - with patch.object(Moulinette, "prompt", return_value="y"), \ - patch.object(os, "isatty", return_value=True): + with patch.object(Moulinette, "prompt", return_value="y"), patch.object( + os, "isatty", return_value=True + ): assert parse_args_in_yunohost_format(answers, questions) == expected_result expected_result = OrderedDict({"some_boolean": (0, "boolean")}) - with patch.object(Moulinette, "prompt", return_value="n"), \ - patch.object(os, "isatty", return_value=True): + with patch.object(Moulinette, "prompt", return_value="n"), patch.object( + os, "isatty", return_value=True + ): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -1041,8 +1078,9 @@ def test_question_boolean_input_no_ask(): answers = {} expected_result = OrderedDict({"some_boolean": (1, "boolean")}) - with patch.object(Moulinette, "prompt", return_value="y"), \ - patch.object(os, "isatty", return_value=True): + with patch.object(Moulinette, "prompt", return_value="y"), patch.object( + os, "isatty", return_value=True + ): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -1072,8 +1110,9 @@ def test_question_boolean_optional_with_input(): answers = {} expected_result = OrderedDict({"some_boolean": (1, "boolean")}) - with patch.object(Moulinette, "prompt", return_value="y"), \ - patch.object(os, "isatty", return_value=True): + with patch.object(Moulinette, "prompt", return_value="y"), patch.object( + os, "isatty", return_value=True + ): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -1089,8 +1128,9 @@ def test_question_boolean_optional_with_empty_input(): answers = {} expected_result = OrderedDict({"some_boolean": (0, "boolean")}) # default to false - with patch.object(Moulinette, "prompt", return_value=""), \ - patch.object(os, "isatty", return_value=True): + with patch.object(Moulinette, "prompt", return_value=""), patch.object( + os, "isatty", return_value=True + ): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -1105,8 +1145,9 @@ def test_question_boolean_optional_with_input_without_ask(): answers = {} expected_result = OrderedDict({"some_boolean": (0, "boolean")}) - with patch.object(Moulinette, "prompt", return_value="n"), \ - patch.object(os, "isatty", return_value=True): + with patch.object(Moulinette, "prompt", return_value="n"), patch.object( + os, "isatty", return_value=True + ): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -1150,13 +1191,16 @@ def test_question_boolean_input_test_ask(): ] answers = {} - with patch.object(Moulinette, "prompt", return_value=0) as prompt, \ - patch.object(os, "isatty", return_value=True): + with patch.object(Moulinette, "prompt", return_value=0) as prompt, patch.object( + os, "isatty", return_value=True + ): parse_args_in_yunohost_format(answers, questions) prompt.assert_called_with( message=ask_text + " [yes | no]", - is_password=False, confirm=False, - prefill='no', is_multiline=False + is_password=False, + confirm=False, + prefill="no", + is_multiline=False, ) @@ -1173,13 +1217,16 @@ def test_question_boolean_input_test_ask_with_default(): ] answers = {} - with patch.object(Moulinette, "prompt", return_value=1) as prompt, \ - patch.object(os, "isatty", return_value=True): + with patch.object(Moulinette, "prompt", return_value=1) as prompt, patch.object( + os, "isatty", return_value=True + ): parse_args_in_yunohost_format(answers, questions) prompt.assert_called_with( message=ask_text + " [yes | no]", - is_password=False, confirm=False, - prefill='yes', is_multiline=False + is_password=False, + confirm=False, + prefill="yes", + is_multiline=False, ) @@ -1194,9 +1241,13 @@ def test_question_domain_empty(): expected_result = OrderedDict({"some_domain": (main_domain, "domain")}) answers = {} - with patch.object(domain, "_get_maindomain", return_value="my_main_domain.com"),\ - patch.object(domain, "domain_list", return_value={"domains": [main_domain]}), \ - patch.object(os, "isatty", return_value=False): + with patch.object( + domain, "_get_maindomain", return_value="my_main_domain.com" + ), patch.object( + domain, "domain_list", return_value={"domains": [main_domain]} + ), patch.object( + os, "isatty", return_value=False + ): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -1214,7 +1265,7 @@ def test_question_domain(): expected_result = OrderedDict({"some_domain": (main_domain, "domain")}) with patch.object( - domain, "_get_maindomain", return_value=main_domain + domain, "_get_maindomain", return_value=main_domain ), patch.object(domain, "domain_list", return_value={"domains": domains}): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -1234,7 +1285,7 @@ def test_question_domain_two_domains(): expected_result = OrderedDict({"some_domain": (other_domain, "domain")}) with patch.object( - domain, "_get_maindomain", return_value=main_domain + domain, "_get_maindomain", return_value=main_domain ), patch.object(domain, "domain_list", return_value={"domains": domains}): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -1242,7 +1293,7 @@ def test_question_domain_two_domains(): expected_result = OrderedDict({"some_domain": (main_domain, "domain")}) with patch.object( - domain, "_get_maindomain", return_value=main_domain + domain, "_get_maindomain", return_value=main_domain ), patch.object(domain, "domain_list", return_value={"domains": domains}): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -1261,10 +1312,11 @@ def test_question_domain_two_domains_wrong_answer(): answers = {"some_domain": "doesnt_exist.pouet"} with patch.object( - domain, "_get_maindomain", return_value=main_domain + domain, "_get_maindomain", return_value=main_domain ), patch.object(domain, "domain_list", return_value={"domains": domains}): - with pytest.raises(YunohostError), \ - patch.object(os, "isatty", return_value=False): + with pytest.raises(YunohostError), patch.object( + os, "isatty", return_value=False + ): parse_args_in_yunohost_format(answers, questions) @@ -1283,9 +1335,12 @@ def test_question_domain_two_domains_default_no_ask(): expected_result = OrderedDict({"some_domain": (main_domain, "domain")}) with patch.object( - domain, "_get_maindomain", return_value=main_domain - ), patch.object(domain, "domain_list", return_value={"domains": domains}), \ - patch.object(os, "isatty", return_value=False): + domain, "_get_maindomain", return_value=main_domain + ), patch.object( + domain, "domain_list", return_value={"domains": domains} + ), patch.object( + os, "isatty", return_value=False + ): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -1299,9 +1354,12 @@ def test_question_domain_two_domains_default(): expected_result = OrderedDict({"some_domain": (main_domain, "domain")}) with patch.object( - domain, "_get_maindomain", return_value=main_domain - ), patch.object(domain, "domain_list", return_value={"domains": domains}), \ - patch.object(os, "isatty", return_value=False): + domain, "_get_maindomain", return_value=main_domain + ), patch.object( + domain, "domain_list", return_value={"domains": domains} + ), patch.object( + os, "isatty", return_value=False + ): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -1314,9 +1372,12 @@ def test_question_domain_two_domains_default_input(): answers = {} with patch.object( - domain, "_get_maindomain", return_value=main_domain - ), patch.object(domain, "domain_list", return_value={"domains": domains}), \ - patch.object(os, "isatty", return_value=True): + domain, "_get_maindomain", return_value=main_domain + ), patch.object( + domain, "domain_list", return_value={"domains": domains} + ), patch.object( + os, "isatty", return_value=True + ): expected_result = OrderedDict({"some_domain": (main_domain, "domain")}) with patch.object(Moulinette, "prompt", return_value=main_domain): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -1346,8 +1407,9 @@ def test_question_user_empty(): answers = {} with patch.object(user, "user_list", return_value={"users": users}): - with pytest.raises(YunohostError), \ - patch.object(os, "isatty", return_value=False): + with pytest.raises(YunohostError), patch.object( + os, "isatty", return_value=False + ): parse_args_in_yunohost_format(answers, questions) @@ -1373,9 +1435,10 @@ def test_question_user(): expected_result = OrderedDict({"some_user": (username, "user")}) - with patch.object(user, "user_list", return_value={"users": users}), \ - patch.object(user, "user_info", return_value={}): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + with patch.object(user, "user_list", return_value={"users": users}), patch.object( + user, "user_info", return_value={} + ): + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_question_user_two_users(): @@ -1407,16 +1470,18 @@ def test_question_user_two_users(): answers = {"some_user": other_user} expected_result = OrderedDict({"some_user": (other_user, "user")}) - with patch.object(user, "user_list", return_value={"users": users}), \ - patch.object(user, "user_info", return_value={}): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + with patch.object(user, "user_list", return_value={"users": users}), patch.object( + user, "user_info", return_value={} + ): + assert parse_args_in_yunohost_format(answers, questions) == expected_result answers = {"some_user": username} expected_result = OrderedDict({"some_user": (username, "user")}) - with patch.object(user, "user_list", return_value={"users": users}), \ - patch.object(user, "user_info", return_value={}): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + with patch.object(user, "user_list", return_value={"users": users}), patch.object( + user, "user_info", return_value={} + ): + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_question_user_two_users_wrong_answer(): @@ -1448,8 +1513,9 @@ def test_question_user_two_users_wrong_answer(): answers = {"some_user": "doesnt_exist.pouet"} with patch.object(user, "user_list", return_value={"users": users}): - with pytest.raises(YunohostError), \ - patch.object(os, "isatty", return_value=False): + with pytest.raises(YunohostError), patch.object( + os, "isatty", return_value=False + ): parse_args_in_yunohost_format(answers, questions) @@ -1477,8 +1543,9 @@ def test_question_user_two_users_no_default(): answers = {} with patch.object(user, "user_list", return_value={"users": users}): - with pytest.raises(YunohostError), \ - patch.object(os, "isatty", return_value=False): + with pytest.raises(YunohostError), patch.object( + os, "isatty", return_value=False + ): parse_args_in_yunohost_format(answers, questions) @@ -1505,21 +1572,20 @@ def test_question_user_two_users_default_input(): questions = [{"name": "some_user", "type": "user", "ask": "choose a user"}] answers = {} - with patch.object(user, "user_list", return_value={"users": users}), \ - patch.object(os, "isatty", return_value=True): + with patch.object(user, "user_list", return_value={"users": users}), patch.object( + os, "isatty", return_value=True + ): with patch.object(user, "user_info", return_value={}): expected_result = OrderedDict({"some_user": (username, "user")}) with patch.object(Moulinette, "prompt", return_value=username): assert ( - parse_args_in_yunohost_format(answers, questions) - == expected_result + parse_args_in_yunohost_format(answers, questions) == expected_result ) expected_result = OrderedDict({"some_user": (other_user, "user")}) with patch.object(Moulinette, "prompt", return_value=other_user): assert ( - parse_args_in_yunohost_format(answers, questions) - == expected_result + parse_args_in_yunohost_format(answers, questions) == expected_result ) @@ -1544,8 +1610,7 @@ def test_question_number_no_input(): ] answers = {} - with pytest.raises(YunohostError), \ - patch.object(os, "isatty", return_value=False): + with pytest.raises(YunohostError), patch.object(os, "isatty", return_value=False): parse_args_in_yunohost_format(answers, questions) @@ -1558,13 +1623,11 @@ def test_question_number_bad_input(): ] answers = {"some_number": "stuff"} - with pytest.raises(YunohostError), \ - patch.object(os, "isatty", return_value=False): + with pytest.raises(YunohostError), patch.object(os, "isatty", return_value=False): parse_args_in_yunohost_format(answers, questions) answers = {"some_number": 1.5} - with pytest.raises(YunohostError), \ - patch.object(os, "isatty", return_value=False): + with pytest.raises(YunohostError), patch.object(os, "isatty", return_value=False): parse_args_in_yunohost_format(answers, questions) @@ -1579,17 +1642,20 @@ def test_question_number_input(): answers = {} expected_result = OrderedDict({"some_number": (1337, "number")}) - with patch.object(Moulinette, "prompt", return_value="1337"), \ - patch.object(os, "isatty", return_value=True): + with patch.object(Moulinette, "prompt", return_value="1337"), patch.object( + os, "isatty", return_value=True + ): assert parse_args_in_yunohost_format(answers, questions) == expected_result - with patch.object(Moulinette, "prompt", return_value=1337), \ - patch.object(os, "isatty", return_value=True): + with patch.object(Moulinette, "prompt", return_value=1337), patch.object( + os, "isatty", return_value=True + ): assert parse_args_in_yunohost_format(answers, questions) == expected_result expected_result = OrderedDict({"some_number": (0, "number")}) - with patch.object(Moulinette, "prompt", return_value="0"), \ - patch.object(os, "isatty", return_value=True): + with patch.object(Moulinette, "prompt", return_value="0"), patch.object( + os, "isatty", return_value=True + ): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -1603,8 +1669,9 @@ def test_question_number_input_no_ask(): answers = {} expected_result = OrderedDict({"some_number": (1337, "number")}) - with patch.object(Moulinette, "prompt", return_value="1337"), \ - patch.object(os, "isatty", return_value=True): + with patch.object(Moulinette, "prompt", return_value="1337"), patch.object( + os, "isatty", return_value=True + ): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -1634,8 +1701,9 @@ def test_question_number_optional_with_input(): answers = {} expected_result = OrderedDict({"some_number": (1337, "number")}) - with patch.object(Moulinette, "prompt", return_value="1337"), \ - patch.object(os, "isatty", return_value=True): + with patch.object(Moulinette, "prompt", return_value="1337"), patch.object( + os, "isatty", return_value=True + ): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -1650,8 +1718,9 @@ def test_question_number_optional_with_input_without_ask(): answers = {} expected_result = OrderedDict({"some_number": (0, "number")}) - with patch.object(Moulinette, "prompt", return_value="0"), \ - patch.object(os, "isatty", return_value=True): + with patch.object(Moulinette, "prompt", return_value="0"), patch.object( + os, "isatty", return_value=True + ): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -1680,8 +1749,7 @@ def test_question_number_bad_default(): } ] answers = {} - with pytest.raises(YunohostError), \ - patch.object(os, "isatty", return_value=False): + with pytest.raises(YunohostError), patch.object(os, "isatty", return_value=False): parse_args_in_yunohost_format(answers, questions) @@ -1696,13 +1764,16 @@ def test_question_number_input_test_ask(): ] answers = {} - with patch.object(Moulinette, "prompt", return_value="1111") as prompt, \ - patch.object(os, "isatty", return_value=True): + with patch.object( + Moulinette, "prompt", return_value="1111" + ) as prompt, patch.object(os, "isatty", return_value=True): parse_args_in_yunohost_format(answers, questions) prompt.assert_called_with( message=ask_text, - is_password=False, confirm=False, - prefill='', is_multiline=False + is_password=False, + confirm=False, + prefill="", + is_multiline=False, ) @@ -1719,13 +1790,16 @@ def test_question_number_input_test_ask_with_default(): ] answers = {} - with patch.object(Moulinette, "prompt", return_value="1111") as prompt, \ - patch.object(os, "isatty", return_value=True): + with patch.object( + Moulinette, "prompt", return_value="1111" + ) as prompt, patch.object(os, "isatty", return_value=True): parse_args_in_yunohost_format(answers, questions) prompt.assert_called_with( message=ask_text, - is_password=False, confirm=False, - prefill=str(default_value), is_multiline=False + is_password=False, + confirm=False, + prefill=str(default_value), + is_multiline=False, ) @@ -1743,11 +1817,12 @@ def test_question_number_input_test_ask_with_example(): ] answers = {} - with patch.object(Moulinette, "prompt", return_value="1111") as prompt, \ - patch.object(os, "isatty", return_value=True): + with patch.object( + Moulinette, "prompt", return_value="1111" + ) as prompt, patch.object(os, "isatty", return_value=True): parse_args_in_yunohost_format(answers, questions) - assert ask_text in prompt.call_args[1]['message'] - assert example_value in prompt.call_args[1]['message'] + assert ask_text in prompt.call_args[1]["message"] + assert example_value in prompt.call_args[1]["message"] @pytest.mark.skip # we should do something with this help @@ -1764,18 +1839,20 @@ def test_question_number_input_test_ask_with_help(): ] answers = {} - with patch.object(Moulinette, "prompt", return_value="1111") as prompt, \ - patch.object(os, "isatty", return_value=True): + with patch.object( + Moulinette, "prompt", return_value="1111" + ) as prompt, patch.object(os, "isatty", return_value=True): parse_args_in_yunohost_format(answers, questions) - assert ask_text in prompt.call_args[1]['message'] - assert help_value in prompt.call_args[1]['message'] + assert ask_text in prompt.call_args[1]["message"] + assert help_value in prompt.call_args[1]["message"] def test_question_display_text(): questions = [{"name": "some_app", "type": "display_text", "ask": "foobar"}] answers = {} - with patch.object(sys, "stdout", new_callable=StringIO) as stdout, \ - patch.object(os, "isatty", return_value=True): + with patch.object(sys, "stdout", new_callable=StringIO) as stdout, patch.object( + os, "isatty", return_value=True + ): parse_args_in_yunohost_format(answers, questions) assert "foobar" in stdout.getvalue() diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index fa461d43b..b0b6c7d34 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -98,7 +98,9 @@ class ConfigPanel: return result - def set(self, key=None, value=None, args=None, args_file=None, operation_logger=None): + def set( + self, key=None, value=None, args=None, args_file=None, operation_logger=None + ): self.filter_key = key or "" # Read config panel toml @@ -108,7 +110,10 @@ class ConfigPanel: raise YunohostValidationError("config_no_panel") if (args is not None or args_file is not None) and value is not None: - raise YunohostValidationError("You should either provide a value, or a serie of args/args_file, but not both at the same time", raw_msg=True) + raise YunohostValidationError( + "You should either provide a value, or a serie of args/args_file, but not both at the same time", + raw_msg=True, + ) if self.filter_key.count(".") != 2 and value is not None: raise YunohostValidationError("config_cant_set_value_on_section") @@ -167,7 +172,10 @@ class ConfigPanel: # Split filter_key filter_key = self.filter_key.split(".") if self.filter_key != "" else [] if len(filter_key) > 3: - raise YunohostError(f"The filter key {filter_key} has too many sub-levels, the max is 3.", raw_msg=True) + raise YunohostError( + f"The filter key {filter_key} has too many sub-levels, the max is 3.", + raw_msg=True, + ) if not os.path.exists(self.config_path): return None @@ -190,7 +198,7 @@ class ConfigPanel: "default": { "name": "", "services": [], - "actions": {"apply": {"en": "Apply"}} + "actions": {"apply": {"en": "Apply"}}, }, }, "sections": { @@ -199,15 +207,34 @@ class ConfigPanel: "name": "", "services": [], "optional": True, - } + }, }, "options": { - "properties": ["ask", "type", "bind", "help", "example", "default", - "style", "icon", "placeholder", "visible", - "optional", "choices", "yes", "no", "pattern", - "limit", "min", "max", "step", "accept", "redact"], - "default": {} - } + "properties": [ + "ask", + "type", + "bind", + "help", + "example", + "default", + "style", + "icon", + "placeholder", + "visible", + "optional", + "choices", + "yes", + "no", + "pattern", + "limit", + "min", + "max", + "step", + "accept", + "redact", + ], + "default": {}, + }, } def convert(toml_node, node_type): @@ -219,14 +246,16 @@ class ConfigPanel: This function detects all children nodes and put them in a list """ # Prefill the node default keys if needed - default = format_description[node_type]['default'] + default = format_description[node_type]["default"] node = {key: toml_node.get(key, value) for key, value in default.items()} - properties = format_description[node_type]['properties'] + properties = format_description[node_type]["properties"] # Define the filter_key part to use and the children type i = list(format_description).index(node_type) - subnode_type = list(format_description)[i + 1] if node_type != "options" else None + subnode_type = ( + list(format_description)[i + 1] if node_type != "options" else None + ) search_key = filter_key[i] if len(filter_key) > i else False for key, value in toml_node.items(): @@ -265,10 +294,24 @@ class ConfigPanel: ) # List forbidden keywords from helpers and sections toml (to avoid conflict) - forbidden_keywords = ["old", "app", "changed", "file_hash", "binds", "types", - "formats", "getter", "setter", "short_setting", "type", - "bind", "nothing_changed", "changes_validated", "result", - "max_progression"] + forbidden_keywords = [ + "old", + "app", + "changed", + "file_hash", + "binds", + "types", + "formats", + "getter", + "setter", + "short_setting", + "type", + "bind", + "nothing_changed", + "changes_validated", + "result", + "max_progression", + ] forbidden_keywords += format_description["sections"] for _, _, option in self._iterate(): @@ -282,10 +325,15 @@ class ConfigPanel: for _, _, option in self._iterate(): if option["id"] not in self.values: allowed_empty_types = ["alert", "display_text", "markdown", "file"] - if option["type"] in allowed_empty_types or option.get("bind") == "null": + if ( + option["type"] in allowed_empty_types + or option.get("bind") == "null" + ): continue else: - raise YunohostError(f"Config panel question '{option['id']}' should be initialized with a value during install or upgrade.") + raise YunohostError( + f"Config panel question '{option['id']}' should be initialized with a value during install or upgrade." + ) value = self.values[option["name"]] # In general, the value is just a simple value. # Sometimes it could be a dict used to overwrite the option itself @@ -440,14 +488,11 @@ class Question(object): ) # Apply default value - class_default= getattr(self, "default_value", None) - if self.value in [None, ""] and \ - (self.default is not None or class_default is not None): - self.value = ( - class_default - if self.default is None - else self.default - ) + class_default = getattr(self, "default_value", None) + if self.value in [None, ""] and ( + self.default is not None or class_default is not None + ): + self.value = class_default if self.default is None else self.default # Normalization # This is done to enforce a certain formating like for boolean @@ -543,30 +588,31 @@ class StringQuestion(Question): class EmailQuestion(StringQuestion): pattern = { "regexp": r"^.+@.+", - "error": "config_validate_email" # i18n: config_validate_email + "error": "config_validate_email", # i18n: config_validate_email } class URLQuestion(StringQuestion): pattern = { "regexp": r"^https?://.*$", - "error": "config_validate_url" # i18n: config_validate_url + "error": "config_validate_url", # i18n: config_validate_url } class DateQuestion(StringQuestion): pattern = { "regexp": r"^\d{4}-\d\d-\d\d$", - "error": "config_validate_date" # i18n: config_validate_date + "error": "config_validate_date", # i18n: config_validate_date } def _prevalidate(self): from datetime import datetime + super()._prevalidate() if self.value not in [None, ""]: try: - datetime.strptime(self.value, '%Y-%m-%d') + datetime.strptime(self.value, "%Y-%m-%d") except ValueError: raise YunohostValidationError("config_validate_date") @@ -574,14 +620,14 @@ class DateQuestion(StringQuestion): class TimeQuestion(StringQuestion): pattern = { "regexp": r"^(1[12]|0?\d):[0-5]\d$", - "error": "config_validate_time" # i18n: config_validate_time + "error": "config_validate_time", # i18n: config_validate_time } class ColorQuestion(StringQuestion): pattern = { "regexp": r"^#[ABCDEFabcdef\d]{3,6}$", - "error": "config_validate_color" # i18n: config_validate_color + "error": "config_validate_color", # i18n: config_validate_color } @@ -732,7 +778,9 @@ class DomainQuestion(Question): def _raise_invalid_answer(self): raise YunohostValidationError( - "app_argument_invalid", name=self.name, error=m18n.n("domain_name_unknown", domain=self.value) + "app_argument_invalid", + name=self.name, + error=m18n.n("domain_name_unknown", domain=self.value), ) @@ -813,7 +861,9 @@ class DisplayTextQuestion(Question): super().__init__(question, user_answers) self.optional = True - self.style = question.get("style", "info" if question['type'] == 'alert' else '') + self.style = question.get( + "style", "info" if question["type"] == "alert" else "" + ) def _format_text_for_user_input_in_cli(self): text = _value_for_locale(self.ask) @@ -875,7 +925,9 @@ class FileQuestion(Question): return filename = self.value if isinstance(self.value, str) else self.value["filename"] - if "." not in filename or "." + filename.split(".")[-1] not in self.accept.replace(" ", "").split(","): + if "." not in filename or "." + filename.split(".")[ + -1 + ] not in self.accept.replace(" ", "").split(","): raise YunohostValidationError( "app_argument_invalid", name=self.name, From 454844d2e0c5ef2ff58cf99035144e5e5d3169d0 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 13 Sep 2021 02:44:59 +0200 Subject: [PATCH 3005/3170] Missing raw_msg=True --- src/yunohost/utils/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index b0b6c7d34..4d6a7be9c 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -332,7 +332,7 @@ class ConfigPanel: continue else: raise YunohostError( - f"Config panel question '{option['id']}' should be initialized with a value during install or upgrade." + f"Config panel question '{option['id']}' should be initialized with a value during install or upgrade.", raw_msg=True ) value = self.values[option["name"]] # In general, the value is just a simple value. From aeb0ef928964f46ec362847eeaa2f8d6f200e8f5 Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Mon, 13 Sep 2021 01:08:29 +0000 Subject: [PATCH 3006/3170] [CI] Format code --- src/yunohost/utils/config.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 4d6a7be9c..b18ed30d2 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -332,7 +332,8 @@ class ConfigPanel: continue else: raise YunohostError( - f"Config panel question '{option['id']}' should be initialized with a value during install or upgrade.", raw_msg=True + f"Config panel question '{option['id']}' should be initialized with a value during install or upgrade.", + raw_msg=True, ) value = self.values[option["name"]] # In general, the value is just a simple value. From af0c12a62b81a08be798f475d6c1098623fafb84 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 13 Sep 2021 03:11:23 +0200 Subject: [PATCH 3007/3170] Unused imports --- src/yunohost/domain.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 0bdede11d..f16358558 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -28,9 +28,7 @@ import os from moulinette import m18n, Moulinette from moulinette.core import MoulinetteError from moulinette.utils.log import getActionLogger -from moulinette.utils.filesystem import ( - mkdir, write_to_file, read_yaml, write_to_yaml -) +from moulinette.utils.filesystem import write_to_file from yunohost.app import ( app_ssowatconf, From 57c88642926d767842c190c057d4df40df1f909b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 13 Sep 2021 03:43:08 +0200 Subject: [PATCH 3008/3170] Fix tests --- src/yunohost/tests/test_app_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/tests/test_app_config.py b/src/yunohost/tests/test_app_config.py index 3767e9e52..d705076c4 100644 --- a/src/yunohost/tests/test_app_config.py +++ b/src/yunohost/tests/test_app_config.py @@ -128,7 +128,7 @@ def test_app_config_get_nonexistentstuff(config_app): app_config_get(config_app, "main.components.nonexistent") app_setting(config_app, "boolean", delete=True) - with pytest.raises(YunohostValidationError): + with pytest.raises(YunohostError): app_config_get(config_app, "main.components.boolean") From 13f57fbd6401743d20e16a3881981fde909956b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= <46165813+ericgaspar@users.noreply.github.com> Date: Mon, 13 Sep 2021 07:41:03 +0200 Subject: [PATCH 3009/3170] Typo - Fix typo --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index f3b1fc906..7fbf25b0f 100644 --- a/locales/en.json +++ b/locales/en.json @@ -609,7 +609,7 @@ "service_disabled": "The service '{service}' will not be started anymore when system boots.", "service_enable_failed": "Could not make the service '{service}' automatically start at boot.\n\nRecent service logs:{logs}", "service_enabled": "The service '{service}' will now be automatically started during system boots.", - "service_not_reloading_because_conf_broken": "Not reloading/restarting service '{name}' because it configuration is broken: {errors}", + "service_not_reloading_because_conf_broken": "Not reloading/restarting service '{name}' because its configuration is broken: {errors}", "service_regen_conf_is_deprecated": "'yunohost service regen-conf' is deprecated! Please use 'yunohost tools regen-conf' instead.", "service_reload_failed": "Could not reload the service '{service}'\n\nRecent service logs:{logs}", "service_reload_or_restart_failed": "Could not reload or restart the service '{service}'\n\nRecent service logs:{logs}", From 66df3e74c1b60a0317a76fb628c77c9fba651d46 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 13 Sep 2021 12:44:32 +0200 Subject: [PATCH 3010/3170] Fix autofix translation ci --- locales/fr.json | 24 ++++++++++++------------ tests/autofix_locale_format.py | 2 +- tests/reformat_locales.py | 4 ++-- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index da6a41558..a11ef3b43 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -137,15 +137,15 @@ "upnp_dev_not_found": "Aucun périphérique compatible UPnP n'a été trouvé", "upnp_disabled": "L'UPnP est désactivé", "upnp_enabled": "L'UPnP est activé", - "upnp_port_open_failed": "Impossible d’ouvrir les ports UPnP", - "user_created": "L’utilisateur a été créé", - "user_creation_failed": "Impossible de créer l’utilisateur {user} : {error}", - "user_deleted": "L’utilisateur a été supprimé", - "user_deletion_failed": "Impossible de supprimer l’utilisateur {user} : {error}", - "user_home_creation_failed": "Impossible de créer le dossier personnel '{home}' de l’utilisateur", - "user_unknown": "L’utilisateur {user} est inconnu", - "user_update_failed": "Impossible de mettre à jour l’utilisateur {user} : {error}", - "user_updated": "L’utilisateur a été modifié", + "upnp_port_open_failed": "Impossible d'ouvrir les ports UPnP", + "user_created": "L'utilisateur a été créé", + "user_creation_failed": "Impossible de créer l'utilisateur {user} : {error}", + "user_deleted": "L'utilisateur a été supprimé", + "user_deletion_failed": "Impossible de supprimer l'utilisateur {user} : {error}", + "user_home_creation_failed": "Impossible de créer le dossier personnel '{home}' de l'utilisateur", + "user_unknown": "L'utilisateur {user} est inconnu", + "user_update_failed": "Impossible de mettre à jour l'utilisateur {user} : {error}", + "user_updated": "L'utilisateur a été modifié", "yunohost_already_installed": "YunoHost est déjà installé", "yunohost_configured": "YunoHost est maintenant configuré", "yunohost_installing": "L'installation de YunoHost est en cours...", @@ -169,10 +169,10 @@ "certmanager_unable_to_parse_self_CA_name": "Impossible d'analyser le nom de l'autorité du certificat auto-signé (fichier : {file})", "mailbox_used_space_dovecot_down": "Le service 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})", + "backup_archive_broken_link": "Impossible d'accéder à l'archive de sauvegarde (lien invalide vers {path})", "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`.", - "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).", - "app_already_installed_cant_change_url": "Cette application est déjà installée. L’URL ne peut pas être changé simplement par cette fonction. Vérifiez si cela est disponible avec `app changeurl`.", + "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).", + "app_already_installed_cant_change_url": "Cette application est déjà installée. L'URL ne peut pas être changé simplement par cette fonction. Vérifiez si cela est disponible avec `app changeurl`.", "app_change_url_failed_nginx_reload": "Le redémarrage de NGINX a échoué. Voici la sortie de 'nginx -t' :\n{nginx_errors}", "app_change_url_identical_domains": "L'ancien et le nouveau couple domaine/chemin_de_l'URL sont identiques pour ('{domain}{path}'), rien à faire.", "app_change_url_no_script": "L'application '{app_name}' ne prend pas encore en charge le changement d'URL. Vous devriez peut-être la mettre à jour.", diff --git a/tests/autofix_locale_format.py b/tests/autofix_locale_format.py index dd7812635..f3825bd30 100644 --- a/tests/autofix_locale_format.py +++ b/tests/autofix_locale_format.py @@ -3,7 +3,7 @@ import json import glob # List all locale files (except en.json being the ref) -locale_folder = "locales/" +locale_folder = "../locales/" locale_files = glob.glob(locale_folder + "*.json") locale_files = [filename.split("/")[-1] for filename in locale_files] locale_files.remove("en.json") diff --git a/tests/reformat_locales.py b/tests/reformat_locales.py index 9119c7288..86c2664d7 100644 --- a/tests/reformat_locales.py +++ b/tests/reformat_locales.py @@ -3,11 +3,11 @@ import re def reformat(lang, transformations): - locale = open(f"locales/{lang}.json").read() + locale = open(f"../locales/{lang}.json").read() for pattern, replace in transformations.items(): locale = re.compile(pattern).sub(replace, locale) - open(f"locales/{lang}.json", "w").write(locale) + open(f"../locales/{lang}.json", "w").write(locale) ###################################################### From 6031cc784699aa5fbf933971d560bf6eb3afe248 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 13 Sep 2021 17:16:22 +0200 Subject: [PATCH 3011/3170] Misc fixes for cert commands --- data/actionsmap/yunohost.yml | 2 +- data/hooks/conf_regen/01-yunohost | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index c118c90a2..b961460d0 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -647,7 +647,7 @@ domain: action: store_true cert: - subcategory_help: Manage domains DNS + subcategory_help: Manage domain certificates actions: ### certificate_status() status: diff --git a/data/hooks/conf_regen/01-yunohost b/data/hooks/conf_regen/01-yunohost index dd018e8f1..e9c0fc4aa 100755 --- a/data/hooks/conf_regen/01-yunohost +++ b/data/hooks/conf_regen/01-yunohost @@ -82,7 +82,7 @@ EOF # Cron job that renew lets encrypt certificates if there's any that needs renewal cat > $pending_dir/etc/cron.daily/yunohost-certificate-renew << EOF #!/bin/bash -yunohost domain cert-renew --email +yunohost domain cert renew --email EOF # If we subscribed to a dyndns domain, add the corresponding cron From 2782a89a645b56c462717616093d43f53f95e1c3 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 13 Sep 2021 20:55:54 +0200 Subject: [PATCH 3012/3170] domain config: Add notes about other config stuff we may want to implement in the future --- data/other/config_domain.toml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/data/other/config_domain.toml b/data/other/config_domain.toml index 20963764b..af23b5e04 100644 --- a/data/other/config_domain.toml +++ b/data/other/config_domain.toml @@ -1,6 +1,15 @@ version = "1.0" i18n = "domain_config" +# +# Other things we may want to implement in the future: +# +# - maindomain handling +# - default app +# - autoredirect www in nginx conf +# - ? +# + [feature] [feature.mail] From fc07abf871dc3a0f1fc31c356ba1eb805392ee2d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 13 Sep 2021 20:56:43 +0200 Subject: [PATCH 3013/3170] autodns: Misc refactoring to have dns push dry-run somewhat working + implement diff strategy --- data/other/registrar_list.toml | 10 ++- src/yunohost/dns.py | 131 ++++++++++++++++++++++++----- src/yunohost/domain.py | 7 ++ src/yunohost/tests/test_dns.py | 29 ++++--- src/yunohost/tests/test_domains.py | 7 ++ 5 files changed, 148 insertions(+), 36 deletions(-) diff --git a/data/other/registrar_list.toml b/data/other/registrar_list.toml index 1c2e73111..8407fce42 100644 --- a/data/other/registrar_list.toml +++ b/data/other/registrar_list.toml @@ -456,15 +456,17 @@ [ovh] [ovh.auth_entrypoint] - type = "string" - redact = true - + type = "select" + choices = ["ovh-eu", "ovh-ca", "soyoustart-eu", "soyoustart-ca", "kimsufi-eu", "kimsufi-ca"] + default = "ovh-eu" + [ovh.auth_application_key] type = "string" redact = true [ovh.auth_application_secret] - type = "password" + type = "string" + redact = true [ovh.auth_consumer_key] type = "string" diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index aa5e79c82..a3dd0fd33 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -155,7 +155,7 @@ def _build_dns_conf(base_domain): ipv6 = get_public_ip(6) subdomains = _list_subdomains_of(base_domain) - domains_settings = {domain: domain_config_get(domain) + domains_settings = {domain: domain_config_get(domain, export=True) for domain in [base_domain] + subdomains} base_dns_zone = _get_dns_zone_for_domain(base_domain) @@ -467,7 +467,7 @@ def _get_registrar_config_section(domain): # If parent domain exists in yunohost parent_domain = domain.split(".", 1)[1] if parent_domain in domain_list()["domains"]: - registrar_infos["explanation"] = OrderedDict({ + registrar_infos["registrar"] = OrderedDict({ "type": "alert", "style": "info", "ask": f"This domain is a subdomain of {parent_domain}. DNS registrar configuration should be managed in {parent_domain}'s configuration panel.", # FIXME: i18n @@ -478,7 +478,7 @@ def _get_registrar_config_section(domain): # TODO big project, integrate yunohost's dynette as a registrar-like provider # TODO big project, integrate other dyndns providers such as netlib.re, or cf the list of dyndns providers supported by cloudron... if dns_zone in YNH_DYNDNS_DOMAINS: - registrar_infos["explanation"] = OrderedDict({ + registrar_infos["registrar"] = OrderedDict({ "type": "alert", "style": "success", "ask": "This domain is a nohost.me / nohost.st / ynh.fr and its DNS configuration is therefore automatically handled by Yunohost.", # FIXME: i18n @@ -489,7 +489,7 @@ def _get_registrar_config_section(domain): try: registrar = _relevant_provider_for_domain(dns_zone)[0] except ValueError: - registrar_infos["explanation"] = OrderedDict({ + registrar_infos["registrar"] = OrderedDict({ "type": "alert", "style": "warning", "ask": "YunoHost could not automatically detect the registrar handling this domain. You should manually configure your DNS records following the documentation at https://yunohost.org/dns.", # FIXME : i18n @@ -497,15 +497,19 @@ def _get_registrar_config_section(domain): }) else: - registrar_infos["explanation"] = OrderedDict({ + registrar_infos["registrar"] = OrderedDict({ "type": "alert", "style": "info", - "ask": f"YunoHost automatically detected that this domain is handled by the registrar **{registrar}**. If you want, YunoHost will automatically configure this DNS zone, if you provide it with the following informations. You can also manually configure your DNS records following the documentation as https://yunohost.org/dns.", # FIXME: i18n + "ask": f"YunoHost automatically detected that this domain is handled by the registrar **{registrar}**. If you want, YunoHost will automatically configure this DNS zone, if you provide it with the appropriate API credentials. You can also manually configure your DNS records following the documentation as https://yunohost.org/dns.", # FIXME: i18n "value": registrar }) # TODO : add a help tip with the link to the registar's API doc (c.f. Lexicon's README) registrar_list = read_toml(DOMAIN_REGISTRAR_LIST_PATH) - registrar_infos.update(registrar_list[registrar]) + registrar_credentials = registrar_list[registrar] + for credential, infos in registrar_credentials.items(): + infos["default"] = infos.get("default", "") + infos["optional"] = infos.get("optional", "False") + registrar_infos.update(registrar_credentials) return OrderedDict(registrar_infos) @@ -521,14 +525,25 @@ def domain_registrar_push(operation_logger, domain, dry_run=False): _assert_domain_exists(domain) - registrar_settings = domain_config_get(domain, key='', full=True) + settings = domain_config_get(domain, key='dns.registrar') - if not registrar_settings: - raise YunohostValidationError("registrar_is_not_set", domain=domain) + registrar_id = settings["dns.registrar.registrar"].get("value") + + if not registrar_id or registrar_id == "yunohost": + raise YunohostValidationError("registrar_push_not_applicable", domain=domain) + + registrar_credentials = { + k.split('.')[-1]: v["value"] + for k, v in settings.items() + if k != "dns.registrar.registar" + } + + if not all(registrar_credentials.values()): + raise YunohostValidationError("registrar_is_not_configured", domain=domain) # Convert the generated conf into a format that matches what we'll fetch using the API # Makes it easier to compare "wanted records" with "current records on remote" - dns_conf = [] + wanted_records = [] for records in _build_dns_conf(domain).values(): for record in records: @@ -540,7 +555,7 @@ def domain_registrar_push(operation_logger, domain, dry_run=False): if content == "@" and record["type"] == "CNAME": content = domain + "." - dns_conf.append({ + wanted_records.append({ "name": name, "type": type_, "ttl": record["ttl"], @@ -551,23 +566,24 @@ def domain_registrar_push(operation_logger, domain, dry_run=False): # See https://github.com/AnalogJ/lexicon/issues/282 and https://github.com/AnalogJ/lexicon/pull/371 # They say it's trivial to implement it! # And yet, it is still not done/merged - dns_conf = [record for record in dns_conf if record["type"] != "CAA"] + wanted_records = [record for record in wanted_records if record["type"] != "CAA"] # Construct the base data structure to use lexicon's API. + base_config = { - "provider_name": registrar_settings["name"], + "provider_name": registrar_id, "domain": domain, - registrar_settings["name"]: registrar_settings["options"] + registrar_id: registrar_credentials } # Fetch all types present in the generated records - current_remote_records = [] + current_records = [] # Get unique types present in the generated records types = ["A", "AAAA", "MX", "TXT", "CNAME", "SRV"] for key in types: - print("fetcing type: " + key) + print("fetching type: " + key) fetch_records_for_type = { "action": "list", "type": key, @@ -577,12 +593,87 @@ def domain_registrar_push(operation_logger, domain, dry_run=False): .with_dict(dict_object=base_config) .with_dict(dict_object=fetch_records_for_type) ) - current_remote_records.extend(LexiconClient(query).execute()) + current_records.extend(LexiconClient(query).execute()) + + # Ignore records which are for a higher-level domain + # i.e. we don't care about the records for domain.tld when pushing yuno.domain.tld + current_records = [r for r in current_records if r['name'].endswith(f'.{domain}')] + + # Step 0 : Get the list of unique (type, name) + # And compare the current and wanted records + # + # i.e. we want this kind of stuff: + # wanted current + # (A, .domain.tld) 1.2.3.4 1.2.3.4 + # (A, www.domain.tld) 1.2.3.4 5.6.7.8 + # (A, foobar.domain.tld) 1.2.3.4 + # (AAAA, .domain.tld) 2001::abcd + # (MX, .domain.tld) 10 domain.tld [10 mx1.ovh.net, 20 mx2.ovh.net] + # (TXT, .domain.tld) "v=spf1 ..." ["v=spf1", "foobar"] + # (SRV, .domain.tld) 0 5 5269 domain.tld + changes = {"delete": [], "update": [], "create": []} + type_and_names = set([(r["type"], r["name"]) for r in current_records + wanted_records]) + comparison = {type_and_name: {"current": [], "wanted": []} for type_and_name in type_and_names} + + for record in current_records: + comparison[(record["type"], record["name"])]["current"].append(record) + + for record in wanted_records: + comparison[(record["type"], record["name"])]["wanted"].append(record) + + for type_and_name, records in comparison.items(): + # + # Step 1 : compute a first "diff" where we remove records which are the same on both sides + # NB / FIXME? : in all this we ignore the TTL value for now... + # + diff = {"current": [], "wanted": []} + current_contents = [r["content"] for r in records["current"]] + wanted_contents = [r["content"] for r in records["wanted"]] + + print("--------") + print(type_and_name) + print(current_contents) + print(wanted_contents) + + for record in records["current"]: + if record["content"] not in wanted_contents: + diff["current"].append(record) + for record in records["wanted"]: + if record["content"] not in current_contents: + diff["wanted"].append(record) + + # + # Step 2 : simple case: 0 or 1 record on one side, 0 or 1 on the other + # -> either nothing do (0/0) or a creation (0/1) or a deletion (1/0), or an update (1/1) + # + if len(diff["current"]) == 0 and len(diff["wanted"]) == 0: + # No diff, nothing to do + continue + + if len(diff["current"]) == 1 and len(diff["wanted"]) == 0: + changes["delete"].append(diff["current"][0]) + continue + + if len(diff["current"]) == 0 and len(diff["wanted"]) == 1: + changes["create"].append(diff["wanted"][0]) + continue + # + if len(diff["current"]) == 1 and len(diff["wanted"]) == 1: + diff["current"][0]["content"] = diff["wanted"][0]["content"] + changes["update"].append(diff["current"][0]) + continue + + # + # Step 3 : N record on one side, M on the other, watdo # FIXME + # + for record in diff["wanted"]: + print(f"Dunno watdo with {type_and_name} : {record['content']}") + for record in diff["current"]: + print(f"Dunno watdo with {type_and_name} : {record['content']}") - changes = {} if dry_run: - return {"current_records": current_remote_records, "dns_conf": dns_conf, "changes": changes} + return {"changes": changes} operation_logger.start() diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index f16358558..f0a42a2d5 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -436,12 +436,19 @@ class DomainConfigPanel(ConfigPanel): toml['feature']['xmpp']['xmpp']['default'] = 1 if self.domain == _get_maindomain() else 0 toml['dns']['registrar'] = _get_registrar_config_section(self.domain) + # FIXME: Ugly hack to save the registar id/value and reinject it in _load_current_values ... + self.registar_id = toml['dns']['registrar']['registrar']['value'] + return toml def _load_current_values(self): + # TODO add mechanism to share some settings with other domains on the same zone super()._load_current_values() + # FIXME: Ugly hack to save the registar id/value and reinject it in _load_current_values ... + self.values["registrar"] = self.registar_id + # # # Stuff managed in other files diff --git a/src/yunohost/tests/test_dns.py b/src/yunohost/tests/test_dns.py index 7adae84fd..58c2be3a5 100644 --- a/src/yunohost/tests/test_dns.py +++ b/src/yunohost/tests/test_dns.py @@ -1,15 +1,13 @@ import pytest -import yaml -import os - from moulinette.utils.filesystem import read_toml -from yunohost.utils.error import YunohostError, YunohostValidationError +from yunohost.domain import domain_add, domain_remove from yunohost.dns import ( DOMAIN_REGISTRAR_LIST_PATH, _get_dns_zone_for_domain, - _get_registrar_config_section + _get_registrar_config_section, + _build_dns_conf, ) @@ -46,21 +44,28 @@ def test_registrar_list_integrity(): def test_magic_guess_registrar_weird_domain(): - assert _get_registrar_config_section("yolo.test")["explanation"]["value"] is None + assert _get_registrar_config_section("yolo.test")["registrar"]["value"] is None def test_magic_guess_registrar_ovh(): - assert _get_registrar_config_section("yolo.yunohost.org")["explanation"]["value"] == "ovh" + assert _get_registrar_config_section("yolo.yunohost.org")["registrar"]["value"] == "ovh" def test_magic_guess_registrar_yunodyndns(): - assert _get_registrar_config_section("yolo.nohost.me")["explanation"]["value"] == "yunohost" + assert _get_registrar_config_section("yolo.nohost.me")["registrar"]["value"] == "yunohost" -#def domain_dns_suggest(domain): -# return yunohost.dns.domain_dns_conf(domain) -# -# +@pytest.fixture +def example_domain(): + domain_add("example.tld") + yield "example_tld" + domain_remove("example.tld") + + +def test_domain_dns_suggest(example_domain): + + assert _build_dns_conf(example_domain) + #def domain_dns_push(domain, dry_run): # import yunohost.dns # return yunohost.dns.domain_registrar_push(domain, dry_run) diff --git a/src/yunohost/tests/test_domains.py b/src/yunohost/tests/test_domains.py index 04f434b6c..b964d2ab6 100644 --- a/src/yunohost/tests/test_domains.py +++ b/src/yunohost/tests/test_domains.py @@ -106,6 +106,13 @@ def test_domain_config_get_default(): assert domain_config_get(TEST_DOMAINS[1], "dns.advanced.ttl") == 3600 +def test_domain_config_get_export(): + + assert domain_config_get(TEST_DOMAINS[0], export=True)["xmpp"] == 1 + assert domain_config_get(TEST_DOMAINS[1], export=True)["xmpp"] == 0 + assert domain_config_get(TEST_DOMAINS[1], export=True)["ttl"] == 3600 + + def test_domain_config_set(): assert domain_config_get(TEST_DOMAINS[1], "feature.xmpp.xmpp") == 0 domain_config_set(TEST_DOMAINS[1], "feature.xmpp.xmpp", "yes") From 0a21be694c41f4c06fe012447478bc4fc7c3d57e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 13 Sep 2021 22:35:38 +0200 Subject: [PATCH 3014/3170] We don't need to add .. to pythonpath during tests? --- src/yunohost/tests/conftest.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/yunohost/tests/conftest.py b/src/yunohost/tests/conftest.py index 8c00693c0..d87ef445e 100644 --- a/src/yunohost/tests/conftest.py +++ b/src/yunohost/tests/conftest.py @@ -1,14 +1,11 @@ import os import pytest -import sys import moulinette from moulinette import m18n, Moulinette from yunohost.utils.error import YunohostError from contextlib import contextmanager -sys.path.append("..") - @pytest.fixture(scope="session", autouse=True) def clone_test_app(request): @@ -77,6 +74,7 @@ moulinette.core.Moulinette18n.n = new_m18nn def pytest_cmdline_main(config): + import sys sys.path.insert(0, "/usr/lib/moulinette/") import yunohost From 196993afcb5dfe4a9c9cb35e8be4c59f5d61427e Mon Sep 17 00:00:00 2001 From: Kay0u Date: Tue, 14 Sep 2021 13:51:42 +0200 Subject: [PATCH 3015/3170] remove duplicate name in locales --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 7fbf25b0f..d664a760d 100644 --- a/locales/en.json +++ b/locales/en.json @@ -420,7 +420,7 @@ "log_dyndns_subscribe": "Subscribe to a YunoHost subdomain '{}'", "log_dyndns_update": "Update the IP associated with your YunoHost subdomain '{}'", "log_help_to_get_failed_log": "The operation '{desc}' could not be completed. Please share the full log of this operation using the command 'yunohost log share {name}' to get help", - "log_help_to_get_log": "To view the log of the operation '{desc}', use the command 'yunohost log show {name}{name}'", + "log_help_to_get_log": "To view the log of the operation '{desc}', use the command 'yunohost log show {name}'", "log_letsencrypt_cert_install": "Install a Let's Encrypt certificate on '{}' domain", "log_letsencrypt_cert_renew": "Renew '{}' Let's Encrypt certificate", "log_link_to_failed_log": "Could not complete the operation '{desc}'. Please provide the full log of this operation by clicking here to get help", From a2c5895cd3f36062c6a3222d55ec73175d826bec Mon Sep 17 00:00:00 2001 From: Kay0u Date: Tue, 14 Sep 2021 17:27:42 +0200 Subject: [PATCH 3016/3170] remove all double var in locales --- locales/ca.json | 2 +- locales/de.json | 2 +- locales/eo.json | 2 +- locales/es.json | 2 +- locales/fa.json | 2 +- locales/fr.json | 2 +- locales/gl.json | 2 +- locales/it.json | 2 +- locales/nb_NO.json | 2 +- locales/oc.json | 2 +- locales/uk.json | 2 +- locales/zh_Hans.json | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/locales/ca.json b/locales/ca.json index 5a128ebb8..0e8d446d5 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -177,7 +177,7 @@ "iptables_unavailable": "No podeu modificar les iptables aquí. O bé sou en un contenidor o bé el vostre nucli no és compatible amb aquesta opció", "log_corrupted_md_file": "El fitxer de metadades YAML associat amb els registres està malmès: « {md_file} »\nError: {error}", "log_link_to_log": "El registre complet d'aquesta operació: «{desc}»", - "log_help_to_get_log": "Per veure el registre de l'operació « {desc} », utilitzeu l'ordre « yunohost log show {name}{name} »", + "log_help_to_get_log": "Per veure el registre de l'operació « {desc} », utilitzeu l'ordre « yunohost log show {name} »", "log_link_to_failed_log": "No s'ha pogut completar l'operació « {desc} ». Per obtenir ajuda, proveïu el registre complete de l'operació clicant aquí", "log_help_to_get_failed_log": "No s'ha pogut completar l'operació « {desc} ». Per obtenir ajuda, compartiu el registre complete de l'operació utilitzant l'ordre « yunohost log share {name} »", "log_does_exists": "No hi ha cap registre per l'operació amb el nom« {log} », utilitzeu « yunohost log list » per veure tots els registre d'operació disponibles", diff --git a/locales/de.json b/locales/de.json index dca2b034d..6ddc1284d 100644 --- a/locales/de.json +++ b/locales/de.json @@ -244,7 +244,7 @@ "dpkg_is_broken": "Du kannst das gerade nicht tun, weil dpkg/APT (der Systempaketmanager) in einem defekten Zustand zu sein scheint.... Du kannst versuchen, dieses Problem zu lösen, indem du dich über SSH verbindest und `sudo apt install --fix-broken` sowie/oder `sudo dpkg --configure -a` ausführst.", "global_settings_unknown_setting_from_settings_file": "Unbekannter Schlüssel in den Einstellungen: '{setting_key}', verwerfen und speichern in /etc/yunohost/settings-unknown.json", "log_link_to_log": "Vollständiges Log dieser Operation: '{desc}'", - "log_help_to_get_log": "Um das Protokoll der Operation '{desc}' anzuzeigen, verwenden Sie den Befehl 'yunohost log show {name}{name}'", + "log_help_to_get_log": "Um das Protokoll der Operation '{desc}' anzuzeigen, verwenden Sie den Befehl 'yunohost log show {name}'", "global_settings_setting_security_nginx_compatibility": "Kompatibilitäts- vs. Sicherheits-Kompromiss für den Webserver NGINX. Betrifft die Ciphers (und andere sicherheitsrelevante Aspekte)", "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Erlaubt die Verwendung eines (veralteten) DSA-Hostkeys für die SSH-Daemon-Konfiguration", "log_app_remove": "Entferne die Applikation '{}'", diff --git a/locales/eo.json b/locales/eo.json index f40111f04..1904cfd9a 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -326,7 +326,7 @@ "log_dyndns_subscribe": "Aboni al YunoHost-subdominio '{}'", "password_too_simple_4": "La pasvorto bezonas almenaŭ 12 signojn kaj enhavas ciferon, majuskle, pli malaltan kaj specialajn signojn", "regenconf_file_updated": "Agordodosiero '{conf}' ĝisdatigita", - "log_help_to_get_log": "Por vidi la protokolon de la operacio '{desc}', uzu la komandon 'yunohost log show {name}{name}'", + "log_help_to_get_log": "Por vidi la protokolon de la operacio '{desc}', uzu la komandon 'yunohost log show {name}'", "global_settings_setting_security_nginx_compatibility": "Kongruo vs sekureca kompromiso por la TTT-servilo NGINX. Afektas la ĉifradojn (kaj aliajn aspektojn pri sekureco)", "restore_complete": "Restarigita", "hook_exec_failed": "Ne povis funkcii skripto: {path}", diff --git a/locales/es.json b/locales/es.json index 9af875898..ecb12d912 100644 --- a/locales/es.json +++ b/locales/es.json @@ -325,7 +325,7 @@ "log_does_exists": "No existe ningún registro de actividades con el nombre '{log}', ejecute 'yunohost log list' para ver todos los registros de actividades disponibles", "log_help_to_get_failed_log": "No se pudo completar la operación «{desc}». Para obtener ayuda, comparta el registro completo de esta operación ejecutando la orden «yunohost log share {name}»", "log_link_to_failed_log": "No se pudo completar la operación «{desc}». Para obtener ayuda, proporcione el registro completo de esta operación pulsando aquí", - "log_help_to_get_log": "Para ver el registro de la operación «{desc}», ejecute la orden «yunohost log show {name}{name}»", + "log_help_to_get_log": "Para ver el registro de la operación «{desc}», ejecute la orden «yunohost log show {name}»", "log_link_to_log": "Registro completo de esta operación: «{desc}»", "log_corrupted_md_file": "El archivo de metadatos YAML asociado con el registro está dañado: «{md_file}\nError: {error}»", "hook_json_return_error": "No se pudo leer la respuesta del gancho {path}. Error: {msg}. Contenido sin procesar: {raw_content}", diff --git a/locales/fa.json b/locales/fa.json index 3e78c5de0..d9d3cb175 100644 --- a/locales/fa.json +++ b/locales/fa.json @@ -500,7 +500,7 @@ "log_does_exists": "هیچ گزارش عملیاتی با نام '{log}' وجود ندارد ، برای مشاهده همه گزارش عملیّات های موجود در خط فرمان از دستور 'yunohost log list' استفاده کنید", "log_help_to_get_failed_log": "عملیات '{desc}' کامل نشد. لطفاً برای دریافت راهنمایی و کمک ، گزارش کامل این عملیات را با استفاده از دستور 'yunohost log share {name}' به اشتراک بگذارید", "log_link_to_failed_log": "عملیّات '{desc}' کامل نشد. لطفاً گزارش کامل این عملیات را ارائه دهید بواسطه اینجا را کلیک کنید برای دریافت کمک", - "log_help_to_get_log": "برای مشاهده گزارش عملیات '{desc}'، از دستور 'yunohost log show {name}{name}' استفاده کنید", + "log_help_to_get_log": "برای مشاهده گزارش عملیات '{desc}'، از دستور 'yunohost log show {name}' استفاده کنید", "log_link_to_log": "گزارش کامل این عملیات: {desc}'", "log_corrupted_md_file": "فایل فوق داده YAML مربوط به گزارش ها آسیب دیده است: '{md_file}\nخطا: {error} '", "ldap_server_is_down_restart_it": "سرویس LDAP خاموش است ، سعی کنید آن را دوباره راه اندازی کنید...", diff --git a/locales/fr.json b/locales/fr.json index a11ef3b43..55eb4ecd4 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -251,7 +251,7 @@ "experimental_feature": "Attention : cette fonctionnalité est expérimentale et ne doit pas être considérée comme stable, vous ne devriez pas l'utiliser à moins que vous ne sachiez ce que vous faites.", "log_corrupted_md_file": "Le fichier YAML de métadonnées associé aux logs est corrompu : '{md_file}'\nErreur : {error}", "log_link_to_log": "Journal complet de cette opération : ' {desc} '", - "log_help_to_get_log": "Pour voir le journal de cette opération '{desc}', utilisez la commande 'yunohost log show {name}{name}'", + "log_help_to_get_log": "Pour voir le journal de cette opération '{desc}', utilisez la commande 'yunohost log show {name}'", "log_link_to_failed_log": "L'opération '{desc}' a échoué ! Pour obtenir de l'aide, merci de partager le journal de l'opération en cliquant ici", "log_help_to_get_failed_log": "L'opération '{desc}' a échoué ! Pour obtenir de l'aide, merci de partager le journal de l'opération en utilisant la commande 'yunohost log share {name}'", "log_does_exists": "Il n'y a pas de journal des opérations avec le nom '{log}', utilisez 'yunohost log list' pour voir tous les journaux d'opérations disponibles", diff --git a/locales/gl.json b/locales/gl.json index 1a3c570c2..7976c11e9 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -358,7 +358,7 @@ "global_settings_setting_security_webadmin_allowlist_enabled": "Permitir que só algúns IPs accedan á webadmin.", "disk_space_not_sufficient_update": "Non hai espazo suficiente no disco para actualizar esta aplicación", "disk_space_not_sufficient_install": "Non queda espazo suficiente no disco para instalar esta aplicación", - "log_help_to_get_log": "Para ver o rexistro completo da operación '{desc}', usa o comando 'yunohost log show {name}{name}'", + "log_help_to_get_log": "Para ver o rexistro completo da operación '{desc}', usa o comando 'yunohost log show {name}'", "log_link_to_log": "Rexistro completo desta operación: '{desc}'", "log_corrupted_md_file": "O ficheiro YAML con metadatos asociado aos rexistros está danado: '{md_file}\nErro: {error}'", "iptables_unavailable": "Non podes andar remexendo en iptables aquí. Ou ben estás nun contedor ou o teu kernel non ten soporte para isto", diff --git a/locales/it.json b/locales/it.json index dc998d8d4..7bd048ac7 100644 --- a/locales/it.json +++ b/locales/it.json @@ -249,7 +249,7 @@ "good_practices_about_admin_password": "Stai per impostare una nuova password di amministratore. La password deve essere almeno di 8 caratteri - anche se è buona pratica utilizzare password più lunghe (es. una frase, una serie di parole) e/o utilizzare vari tipi di caratteri (maiuscole, minuscole, numeri e simboli).", "log_corrupted_md_file": "Il file dei metadati YAML associato con i registri è danneggiato: '{md_file}'\nErrore: {error}", "log_link_to_log": "Registro completo di questa operazione: '{desc}'", - "log_help_to_get_log": "Per vedere il registro dell'operazione '{desc}', usa il comando 'yunohost log show {name}{name}'", + "log_help_to_get_log": "Per vedere il registro dell'operazione '{desc}', usa il comando 'yunohost log show {name}'", "global_settings_setting_security_postfix_compatibility": "Bilanciamento tra compatibilità e sicurezza per il server Postfix. Riguarda gli algoritmi di cifratura (e altri aspetti legati alla sicurezza)", "log_link_to_failed_log": "Impossibile completare l'operazione '{desc}'! Per ricevere aiuto, per favore fornisci il registro completo dell'operazione cliccando qui", "log_help_to_get_failed_log": "L'operazione '{desc}' non può essere completata. Per ottenere aiuto, per favore condividi il registro completo dell'operazione utilizzando il comando 'yunohost log share {name}'", diff --git a/locales/nb_NO.json b/locales/nb_NO.json index 037e09cb6..221f974ab 100644 --- a/locales/nb_NO.json +++ b/locales/nb_NO.json @@ -115,7 +115,7 @@ "domain_deletion_failed": "Kunne ikke slette domene", "domain_dyndns_already_subscribed": "Du har allerede abonnement på et DynDNS-domene", "log_link_to_log": "Full logg for denne operasjonen: '{desc}'", - "log_help_to_get_log": "For å vise loggen for operasjonen '{desc}', bruk kommandoen 'yunohost log show {name}{name}'", + "log_help_to_get_log": "For å vise loggen for operasjonen '{desc}', bruk kommandoen 'yunohost log show {name}'", "log_user_create": "Legg til '{}' bruker", "app_change_url_success": "{app} nettadressen er nå {domain}{path}", "app_install_failed": "Kunne ikke installere {app}: {error}" diff --git a/locales/oc.json b/locales/oc.json index 995c61b16..27d4aeca9 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -248,7 +248,7 @@ "experimental_feature": "Atencion : aquesta foncionalitat es experimentala e deu pas èsser considerada coma establa, deuriatz pas l’utilizar levat que sapiatz çò que fasètz.", "log_corrupted_md_file": "Lo fichièr YAML de metadonadas ligat als jornals d’audit es damatjat : « {md_file} »\nError : {error}", "log_link_to_log": "Jornal complèt d’aquesta operacion : {desc}", - "log_help_to_get_log": "Per veire lo jornal d’aquesta operacion « {desc} », utilizatz la comanda « yunohost log show {name}{name} »", + "log_help_to_get_log": "Per veire lo jornal d’aquesta operacion « {desc} », utilizatz la comanda « yunohost log show {name} »", "log_link_to_failed_log": "L’operacion « {desc} » a pas capitat ! Per obténer d’ajuda, mercés de fornir lo jornal complèt de l’operacion", "log_help_to_get_failed_log": "L’operacion « {desc} » a pas reüssit ! Per obténer d’ajuda, mercés de partejar lo jornal d’audit complèt d’aquesta operacion en utilizant la comanda « yunohost log share {name} »", "log_does_exists": "I a pas cap de jornal d’audit per l’operacion amb lo nom « {log} », utilizatz « yunohost log list » per veire totes los jornals d’operacion disponibles", diff --git a/locales/uk.json b/locales/uk.json index 0de15a830..150e8c240 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -248,7 +248,7 @@ "log_does_exists": "Немає журналу операцій з назвою '{log}', використовуйте 'yunohost log list', щоб подивитися всі доступні журнали операцій", "log_help_to_get_failed_log": "Операція '{desc}' не може бути завершена. Будь ласка, поділіться повним журналом цієї операції, використовуючи команду 'yunohost log share {name}', щоб отримати допомогу", "log_link_to_failed_log": "Не вдалося завершити операцію '{desc}'. Будь ласка, надайте повний журнал цієї операції, натиснувши тут, щоб отримати допомогу", - "log_help_to_get_log": "Щоб переглянути журнал операції '{desc}', використовуйте команду 'yunohost log show {name}{name}'", + "log_help_to_get_log": "Щоб переглянути журнал операції '{desc}', використовуйте команду 'yunohost log show {name}'", "log_link_to_log": "Повний журнал цієї операції: '{desc}'", "log_corrupted_md_file": "Файл метаданих YAML, пов'язаний з журналами, пошкоджено: '{md_file}\nПомилка: {error}'", "iptables_unavailable": "Ви не можете грати з iptables тут. Ви перебуваєте або в контейнері, або ваше ядро не підтримує його", diff --git a/locales/zh_Hans.json b/locales/zh_Hans.json index 560ee0db0..e6b4d1cc8 100644 --- a/locales/zh_Hans.json +++ b/locales/zh_Hans.json @@ -511,7 +511,7 @@ "log_does_exists": "没有名称为'{log}'的操作日志,请使用 'yunohost log list' 查看所有可用的操作日志", "log_help_to_get_failed_log": "操作'{desc}'无法完成。请使用命令'yunohost log share {name}' 共享此操作的完整日志以获取帮助", "log_link_to_failed_log": "无法完成操作 '{desc}'。请通过单击此处提供此操作的完整日志以获取帮助", - "log_help_to_get_log": "要查看操作'{desc}'的日志,请使用命令'yunohost log show {name}{name}'", + "log_help_to_get_log": "要查看操作'{desc}'的日志,请使用命令'yunohost log show {name}'", "log_link_to_log": "此操作的完整日志: '{desc}'", "log_corrupted_md_file": "与日志关联的YAML元数据文件已损坏: '{md_file}\n错误: {error}'", "iptables_unavailable": "你不能在这里使用iptables。你要么在一个容器中,要么你的内核不支持它", From d5e366511af8f644362fb49b90e60346845d0394 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 14 Sep 2021 18:04:02 +0200 Subject: [PATCH 3017/3170] autodns: Moar fixes and stuff after tests on the battlefield --- data/actionsmap/yunohost.yml | 6 + data/other/registrar_list.toml | 27 ++-- src/yunohost/dns.py | 233 ++++++++++++++++++++------------- src/yunohost/domain.py | 4 +- 4 files changed, 169 insertions(+), 101 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index b961460d0..93391a81b 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -645,6 +645,12 @@ domain: full: --dry-run help: Only display what's to be pushed action: store_true + --autoremove: + help: Also autoremove records which are stale or not part of the recommended configuration + action: store_true + --purge: + help: Delete all records + action: store_true cert: subcategory_help: Manage domain certificates diff --git a/data/other/registrar_list.toml b/data/other/registrar_list.toml index 8407fce42..406066dc9 100644 --- a/data/other/registrar_list.toml +++ b/data/other/registrar_list.toml @@ -4,7 +4,8 @@ redact = true [aliyun.auth_secret] - type = "password" + type = "string" + redact = true [aurora] [aurora.auth_api_key] @@ -12,7 +13,8 @@ redact = true [aurora.auth_secret_key] - type = "password" + type = "string" + redact = true [azure] [azure.auth_client_id] @@ -20,7 +22,8 @@ redact = true [azure.auth_client_secret] - type = "password" + type = "string" + redact = true [azure.auth_tenant_id] type = "string" @@ -215,7 +218,8 @@ redact = true [exoscale.auth_secret] - type = "password" + type = "string" + redact = true [gandi] [gandi.auth_token] @@ -233,7 +237,8 @@ redact = true [gehirn.auth_secret] - type = "password" + type = "string" + redact = true [glesys] [glesys.auth_username] @@ -250,7 +255,8 @@ redact = true [godaddy.auth_secret] - type = "password" + type = "string" + redact = true [googleclouddns] [goggleclouddns.auth_service_account_info] @@ -415,7 +421,8 @@ redact = true [netcup.auth_api_password] - type = "password" + type = "string" + redact = true [nfsn] [nfsn.auth_username] @@ -550,7 +557,8 @@ redact = true [route53.auth_access_secret] - type = "password" + type = "string" + redact = true [route53.private_zone] type = "string" @@ -575,7 +583,8 @@ redact = true [sakuracloud.auth_secret] - type = "password" + type = "string" + redact = true [softlayer] [softlayer.auth_username] diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index a3dd0fd33..83319e541 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -26,6 +26,7 @@ import os import re import time +from difflib import SequenceMatcher from collections import OrderedDict from moulinette import m18n, Moulinette @@ -515,7 +516,7 @@ def _get_registrar_config_section(domain): @is_unit_operation() -def domain_registrar_push(operation_logger, domain, dry_run=False): +def domain_registrar_push(operation_logger, domain, dry_run=False, autoremove=False, purge=False): """ Send DNS records to the previously-configured registrar of the domain. """ @@ -527,15 +528,15 @@ def domain_registrar_push(operation_logger, domain, dry_run=False): settings = domain_config_get(domain, key='dns.registrar') - registrar_id = settings["dns.registrar.registrar"].get("value") + registrar = settings["dns.registrar.registrar"].get("value") - if not registrar_id or registrar_id == "yunohost": + if not registrar or registrar in ["None", "yunohost"]: raise YunohostValidationError("registrar_push_not_applicable", domain=domain) registrar_credentials = { - k.split('.')[-1]: v["value"] - for k, v in settings.items() - if k != "dns.registrar.registar" + k.split('.')[-1]: v["value"] + for k, v in settings.items() + if k != "dns.registrar.registar" } if not all(registrar_credentials.values()): @@ -547,11 +548,12 @@ def domain_registrar_push(operation_logger, domain, dry_run=False): for records in _build_dns_conf(domain).values(): for record in records: - # Make sure we got "absolute" values instead of @ - name = f"{record['name']}.{domain}" if record["name"] != "@" else f".{domain}" + # Make sure the name is a FQDN + name = f"{record['name']}.{domain}" if record["name"] != "@" else f"{domain}" type_ = record["type"] content = record["value"] + # Make sure the content is also a FQDN (with trailing . ?) if content == "@" and record["type"] == "CNAME": content = domain + "." @@ -568,37 +570,48 @@ def domain_registrar_push(operation_logger, domain, dry_run=False): # And yet, it is still not done/merged wanted_records = [record for record in wanted_records if record["type"] != "CAA"] + if purge: + wanted_records = [] + autoremove = True + # Construct the base data structure to use lexicon's API. base_config = { - "provider_name": registrar_id, + "provider_name": registrar, "domain": domain, - registrar_id: registrar_credentials + registrar: registrar_credentials } - # Fetch all types present in the generated records - current_records = [] - - # Get unique types present in the generated records - types = ["A", "AAAA", "MX", "TXT", "CNAME", "SRV"] - - for key in types: - print("fetching type: " + key) - fetch_records_for_type = { - "action": "list", - "type": key, - } - query = ( + # Ugly hack to be able to fetch all record types at once: + # we initialize a LexiconClient with type: dummytype, + # then trigger ourselves the authentication + list_records + # instead of calling .execute() + query = ( LexiconConfigResolver() .with_dict(dict_object=base_config) - .with_dict(dict_object=fetch_records_for_type) - ) - current_records.extend(LexiconClient(query).execute()) + .with_dict(dict_object={"action": "list", "type": "dummytype"}) + ) + # current_records.extend( + client = LexiconClient(query) + client.provider.authenticate() + current_records = client.provider.list_records() + + # Keep only records for relevant types: A, AAAA, MX, TXT, CNAME, SRV + relevant_types = ["A", "AAAA", "MX", "TXT", "CNAME", "SRV"] + current_records = [r for r in current_records if r["type"] in relevant_types] # Ignore records which are for a higher-level domain # i.e. we don't care about the records for domain.tld when pushing yuno.domain.tld current_records = [r for r in current_records if r['name'].endswith(f'.{domain}')] + for record in current_records: + + # Try to get rid of weird stuff like ".domain.tld" or "@.domain.tld" + record["name"] = record["name"].strip("@").strip(".") + + # Some API return '@' in content and we shall convert it to absolute/fqdn + record["content"] = record["content"].replace('@.', domain + ".").replace('@', domain + ".") + # Step 0 : Get the list of unique (type, name) # And compare the current and wanted records # @@ -622,105 +635,145 @@ def domain_registrar_push(operation_logger, domain, dry_run=False): comparison[(record["type"], record["name"])]["wanted"].append(record) for type_and_name, records in comparison.items(): + # # Step 1 : compute a first "diff" where we remove records which are the same on both sides # NB / FIXME? : in all this we ignore the TTL value for now... # - diff = {"current": [], "wanted": []} - current_contents = [r["content"] for r in records["current"]] wanted_contents = [r["content"] for r in records["wanted"]] + current_contents = [r["content"] for r in records["current"]] - print("--------") - print(type_and_name) - print(current_contents) - print(wanted_contents) - - for record in records["current"]: - if record["content"] not in wanted_contents: - diff["current"].append(record) - for record in records["wanted"]: - if record["content"] not in current_contents: - diff["wanted"].append(record) + current = [r for r in records["current"] if r["content"] not in wanted_contents] + wanted = [r for r in records["wanted"] if r["content"] not in current_contents] # # Step 2 : simple case: 0 or 1 record on one side, 0 or 1 on the other # -> either nothing do (0/0) or a creation (0/1) or a deletion (1/0), or an update (1/1) # - if len(diff["current"]) == 0 and len(diff["wanted"]) == 0: + if len(current) == 0 and len(wanted) == 0: # No diff, nothing to do continue - if len(diff["current"]) == 1 and len(diff["wanted"]) == 0: - changes["delete"].append(diff["current"][0]) + if len(current) == 1 and len(wanted) == 0: + changes["delete"].append(current[0]) continue - if len(diff["current"]) == 0 and len(diff["wanted"]) == 1: - changes["create"].append(diff["wanted"][0]) + if len(current) == 0 and len(wanted) == 1: + changes["create"].append(wanted[0]) continue - # - if len(diff["current"]) == 1 and len(diff["wanted"]) == 1: - diff["current"][0]["content"] = diff["wanted"][0]["content"] - changes["update"].append(diff["current"][0]) + + if len(current) == 1 and len(wanted) == 1: + current[0]["old_content"] = current[0]["content"] + current[0]["content"] = wanted[0]["content"] + changes["update"].append(current[0]) continue # - # Step 3 : N record on one side, M on the other, watdo # FIXME + # Step 3 : N record on one side, M on the other # - for record in diff["wanted"]: - print(f"Dunno watdo with {type_and_name} : {record['content']}") - for record in diff["current"]: - print(f"Dunno watdo with {type_and_name} : {record['content']}") + # Fuzzy matching strategy: + # For each wanted record, try to find a current record which looks like the wanted one + # -> if found, trigger an update + # -> if no match found, trigger a create + # + for record in wanted: + def likeliness(r): + # We compute this only on the first 100 chars, to have a high value even for completely different DKIM keys + return SequenceMatcher(None, r["content"][:100], record["content"][:100]).ratio() + + matches = sorted(current, key=lambda r: likeliness(r), reverse=True) + if matches and likeliness(matches[0]) > 0.50: + match = matches[0] + # Remove the match from 'current' so that it's not added to the removed stuff later + current.remove(match) + match["old_content"] = match["content"] + match["content"] = record["content"] + changes["update"].append(match) + else: + changes["create"].append(record) + + # + # For all other remaining current records: + # -> trigger deletions + # + for record in current: + changes["delete"].append(record) + + def human_readable_record(action, record): + name = record["name"] + name = name.strip(".") + name = name.replace('.' + domain, "") + name = name.replace(domain, "@") + name = name[:20] + t = record["type"] + if action in ["create", "update"]: + old_content = record.get("old_content", "(None)")[:30] + new_content = record.get("content", "(None)")[:30] + else: + new_content = record.get("old_content", "(None)")[:30] + old_content = record.get("content", "(None)")[:30] + + return f'{name:>20} [{t:^5}] {old_content:^30} -> {new_content:^30}' if dry_run: - return {"changes": changes} + out = [] + for action in ["delete", "create", "update"]: + out.append("\n" + action + ":\n") + for record in changes[action]: + out.append(human_readable_record(action, record)) + + return '\n'.join(out) operation_logger.start() # Push the records - for record in dns_conf: + for action in ["delete", "create", "update"]: + if action == "delete" and not autoremove: + continue - # For each record, first check if one record exists for the same (type, name) couple - # TODO do not push if local and distant records are exactly the same ? - type_and_name = (record["type"], record["name"]) - already_exists = any((r["type"], r["name"]) == type_and_name - for r in current_remote_records) + for record in changes[action]: - # Finally, push the new record or update the existing one - record_to_push = { - "action": "update" if already_exists else "create", - "type": record["type"], - "name": record["name"], - "content": record["value"], - "ttl": record["ttl"], - } + record["action"] = action - # FIXME Removed TTL, because it doesn't work with Gandi. - # See https://github.com/AnalogJ/lexicon/issues/726 (similar issue) - # But I think there is another issue with Gandi. Or I'm misusing the API... - if base_config["provider_name"] == "gandi": - del record_to_push["ttl"] + # Apparently Lexicon yields us some 'id' during fetch + # But wants 'identifier' during push ... + if "id" in record: + record["identifier"] = record["id"] + del record["id"] - print("pushed_record:", record_to_push) + if "old_content" in record: + del record["old_content"] + if registrar == "godaddy": + if record["name"] == domain: + record["name"] = "@." + record["name"] + if record["type"] in ["MX", "SRV"]: + logger.warning(f"Pushing {record['type']} records is not properly supported by Lexicon/Godaddy.") + continue - # FIXME FIXME FIXME: if a matching record already exists multiple time, - # the current code crashes (at least on OVH) ... we need to provide a specific identifier to update - query = ( - LexiconConfigResolver() - .with_dict(dict_object=base_config) - .with_dict(dict_object=record_to_push) - ) + # FIXME Removed TTL, because it doesn't work with Gandi. + # See https://github.com/AnalogJ/lexicon/issues/726 (similar issue) + # But I think there is another issue with Gandi. Or I'm misusing the API... + if registrar == "gandi": + del record["ttl"] - print(query) - print(query.__dict__) - results = LexiconClient(query).execute() - print("results:", results) - # print("Failed" if results == False else "Ok") + logger.info(action + " : " + human_readable_record(action, record)) - # FIXME FIXME FIXME : if one create / update crash, it shouldn't block everything + query = ( + LexiconConfigResolver() + .with_dict(dict_object=base_config) + .with_dict(dict_object=record) + ) - # FIXME : is it possible to push multiple create/update request at once ? + try: + result = LexiconClient(query).execute() + except Exception as e: + logger.error(f"Failed to {action} record {record['type']}/{record['name']} : {e}") + else: + if result: + logger.success("Done!") + else: + logger.error("Uhoh!?") - -# def domain_config_fetch(domain, key, value): + # FIXME : implement a system to properly report what worked and what did not at the end of the command.. diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index f0a42a2d5..9c9ad2d5e 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -491,6 +491,6 @@ def domain_dns_suggest(domain): return yunohost.dns.domain_dns_suggest(domain) -def domain_dns_push(domain, dry_run): +def domain_dns_push(domain, dry_run, autoremove, purge): import yunohost.dns - return yunohost.dns.domain_registrar_push(domain, dry_run) + return yunohost.dns.domain_registrar_push(domain, dry_run, autoremove, purge) From eb5cb1311ebc99b56c88750adf34b9fba833a5cf Mon Sep 17 00:00:00 2001 From: Kay0u Date: Tue, 14 Sep 2021 18:55:27 +0200 Subject: [PATCH 3018/3170] fix YNH_APP_BASEDIR during actions --- 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 511fa52fb..ef019e894 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -1,6 +1,6 @@ #!/bin/bash -YNH_APP_BASEDIR=$(realpath $([[ "$(basename $0)" =~ ^backup|restore$ ]] && echo '../settings' || echo '..')) +YNH_APP_BASEDIR=$(realpath $([[ "$(basename $0)" =~ ^backup|restore$ ]] && echo '../settings' || [[ -n "$YNH_ACTION" ]] && echo '.' || echo '..' )) # Handle script crashes / failures # From 0d702ed31ca164a234b983f31a7f3f7a0d90b108 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Tue, 14 Sep 2021 18:57:08 +0200 Subject: [PATCH 3019/3170] run actions in tmp dir --- src/yunohost/app.py | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index d92aba373..49f2cd2ad 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1651,25 +1651,35 @@ def app_action_run(operation_logger, app, action, args=None): ) env_dict["YNH_ACTION"] = action - _, path = tempfile.mkstemp() + tmp_workdir_for_app = _make_tmp_workdir_for_app(app=app) + _, action_script = tempfile.mkstemp(dir=tmp_workdir_for_app) - with open(path, "w") as script: + with open(action_script, "w") as script: script.write(action_declaration["command"]) - os.chmod(path, 700) - if action_declaration.get("cwd"): cwd = action_declaration["cwd"].replace("$app", app) else: - cwd = os.path.join(APPS_SETTING_PATH, app) + cwd = tmp_workdir_for_app - # FIXME: this should probably be ran in a tmp workdir... - retcode = hook_exec( - path, - env=env_dict, - chdir=cwd, - user=action_declaration.get("user", "root"), - )[0] + try: + retcode = hook_exec( + action_script, + env=env_dict, + chdir=cwd, + user=action_declaration.get("user", "root"), + )[0] + # Here again, calling hook_exec could fail miserably, or get + # manually interrupted (by mistake or because script was stuck) + # In that case we still want to proceed with the rest of the + # removal (permissions, /etc/yunohost/apps/{app} ...) + except (KeyboardInterrupt, EOFError, Exception): + retcode = -1 + import traceback + + logger.error(m18n.n("unexpected_error", error="\n" + traceback.format_exc())) + finally: + shutil.rmtree(tmp_workdir_for_app) if retcode not in action_declaration.get("accepted_return_codes", [0]): msg = "Error while executing action '%s' of app '%s': return code %s" % ( @@ -1680,8 +1690,6 @@ def app_action_run(operation_logger, app, action, args=None): operation_logger.error(msg) raise YunohostError(msg, raw_msg=True) - os.remove(path) - operation_logger.success() return logger.success("Action successed!") From e15763f97b3979eb71461a04081cce798cd9b39f Mon Sep 17 00:00:00 2001 From: Kayou Date: Tue, 14 Sep 2021 19:11:05 +0200 Subject: [PATCH 3020/3170] Update src/yunohost/app.py Co-authored-by: Alexandre Aubin --- src/yunohost/app.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 49f2cd2ad..b42bed925 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1669,10 +1669,9 @@ def app_action_run(operation_logger, app, action, args=None): chdir=cwd, user=action_declaration.get("user", "root"), )[0] - # Here again, calling hook_exec could fail miserably, or get + # Calling hook_exec could fail miserably, or get # manually interrupted (by mistake or because script was stuck) - # In that case we still want to proceed with the rest of the - # removal (permissions, /etc/yunohost/apps/{app} ...) + # In that case we still want to delete the tmp work dir except (KeyboardInterrupt, EOFError, Exception): retcode = -1 import traceback From fa271db569d299ceb126a470717dd6e7a85ef23a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 14 Sep 2021 19:14:33 +0200 Subject: [PATCH 3021/3170] ci: Don't cd to src/yunohost to run pytest (to prevent ambiguous 'import dns.resolver' trying to import our own dns.py :s) --- .gitlab/ci/test.gitlab-ci.yml | 45 ++++++++++++----------------------- 1 file changed, 15 insertions(+), 30 deletions(-) diff --git a/.gitlab/ci/test.gitlab-ci.yml b/.gitlab/ci/test.gitlab-ci.yml index f270ba982..b3aea606f 100644 --- a/.gitlab/ci/test.gitlab-ci.yml +++ b/.gitlab/ci/test.gitlab-ci.yml @@ -88,8 +88,7 @@ test-helpers: test-domains: extends: .test-stage script: - - cd src/yunohost - - python3 -m pytest tests/test_domains.py + - python3 -m pytest src/yunohost/tests/test_domains.py only: changes: - src/yunohost/domain.py @@ -97,8 +96,7 @@ test-domains: test-dns: extends: .test-stage script: - - cd src/yunohost - - python3 -m pytest tests/test_dns.py + - python3 -m pytest src/yunohost/tests/test_dns.py only: changes: - src/yunohost/dns.py @@ -107,8 +105,7 @@ test-dns: test-apps: extends: .test-stage script: - - cd src/yunohost - - python3 -m pytest tests/test_apps.py + - python3 -m pytest src/yunohost/tests/test_apps.py only: changes: - src/yunohost/app.py @@ -116,8 +113,7 @@ test-apps: test-appscatalog: extends: .test-stage script: - - cd src/yunohost - - python3 -m pytest tests/test_appscatalog.py + - python3 -m pytest src/yunohost/tests/test_appscatalog.py only: changes: - src/yunohost/app.py @@ -125,8 +121,7 @@ test-appscatalog: test-appurl: extends: .test-stage script: - - cd src/yunohost - - python3 -m pytest tests/test_appurl.py + - python3 -m pytest src/yunohost/tests/test_appurl.py only: changes: - src/yunohost/app.py @@ -134,8 +129,7 @@ test-appurl: test-questions: extends: .test-stage script: - - cd src/yunohost - - python3 -m pytest tests/test_questions.py + - python3 -m pytest src/yunohost/tests/test_questions.py only: changes: - src/yunohost/utils/config.py @@ -143,8 +137,7 @@ test-questions: test-app-config: extends: .test-stage script: - - cd src/yunohost - - python3 -m pytest tests/test_app_config.py + - python3 -m pytest src/yunohost/tests/test_app_config.py only: changes: - src/yunohost/app.py @@ -153,8 +146,7 @@ test-app-config: test-changeurl: extends: .test-stage script: - - cd src/yunohost - - python3 -m pytest tests/test_changeurl.py + - python3 -m pytest src/yunohost/tests/test_changeurl.py only: changes: - src/yunohost/app.py @@ -162,8 +154,7 @@ test-changeurl: test-backuprestore: extends: .test-stage script: - - cd src/yunohost - - python3 -m pytest tests/test_backuprestore.py + - python3 -m pytest src/yunohost/tests/test_backuprestore.py only: changes: - src/yunohost/backup.py @@ -171,8 +162,7 @@ test-backuprestore: test-permission: extends: .test-stage script: - - cd src/yunohost - - python3 -m pytest tests/test_permission.py + - python3 -m pytest src/yunohost/tests/test_permission.py only: changes: - src/yunohost/permission.py @@ -180,8 +170,7 @@ test-permission: test-settings: extends: .test-stage script: - - cd src/yunohost - - python3 -m pytest tests/test_settings.py + - python3 -m pytest src/yunohost/tests/test_settings.py only: changes: - src/yunohost/settings.py @@ -189,8 +178,7 @@ test-settings: test-user-group: extends: .test-stage script: - - cd src/yunohost - - python3 -m pytest tests/test_user-group.py + - python3 -m pytest src/yunohost/tests/test_user-group.py only: changes: - src/yunohost/user.py @@ -198,8 +186,7 @@ test-user-group: test-regenconf: extends: .test-stage script: - - cd src/yunohost - - python3 -m pytest tests/test_regenconf.py + - python3 -m pytest src/yunohost/tests/test_regenconf.py only: changes: - src/yunohost/regenconf.py @@ -207,8 +194,7 @@ test-regenconf: test-service: extends: .test-stage script: - - cd src/yunohost - - python3 -m pytest tests/test_service.py + - python3 -m pytest src/yunohost/tests/test_service.py only: changes: - src/yunohost/service.py @@ -216,8 +202,7 @@ test-service: test-ldapauth: extends: .test-stage script: - - cd src/yunohost - - python3 -m pytest tests/test_ldapauth.py + - python3 -m pytest src/yunohost/tests/test_ldapauth.py only: changes: - src/yunohost/authenticators/*.py From 0dde7fca6b133ea399058e34fbbccce15a0d9ceb Mon Sep 17 00:00:00 2001 From: ljf Date: Tue, 14 Sep 2021 20:29:00 +0200 Subject: [PATCH 3022/3170] [fix] Default name for panel and improve no named section display in cli --- src/yunohost/utils/config.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index b18ed30d2..064278008 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -196,7 +196,6 @@ class ConfigPanel: "panels": { "properties": ["name", "services", "actions", "help"], "default": { - "name": "", "services": [], "actions": {"apply": {"en": "Apply"}}, }, @@ -270,7 +269,9 @@ class ConfigPanel: continue subnode = convert(value, subnode_type) subnode["id"] = key - if node_type == "sections": + if node_type == "toml": + subnode.setdefault("name", {"en": key.capitalize()}) + elif node_type == "sections": subnode["name"] = key # legacy subnode.setdefault("optional", toml_node.get("optional", True)) node.setdefault(subnode_type, []).append(subnode) @@ -357,7 +358,8 @@ class ConfigPanel: display_header(f"\n{'='*40}\n>>>> {name}\n{'='*40}") continue name = _value_for_locale(section["name"]) - display_header(f"\n# {name}") + if name: + display_header(f"\n# {name}") # Check and ask unanswered questions self.new_values.update( From 3efa2fe75123d2c29863839aab333de78375dc6a Mon Sep 17 00:00:00 2001 From: ljf Date: Tue, 14 Sep 2021 22:19:53 +0200 Subject: [PATCH 3023/3170] [enh] Avoid to have to retype password in cli --- locales/en.json | 2 ++ src/yunohost/utils/config.py | 49 +++++++++++++++++++++++++----------- 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/locales/en.json b/locales/en.json index d664a760d..5ad7d25b7 100644 --- a/locales/en.json +++ b/locales/en.json @@ -15,6 +15,8 @@ "app_already_up_to_date": "{app} is already up-to-date", "app_argument_choice_invalid": "Use one of these choices '{choices}' for the argument '{name}' instead of '{value}'", "app_argument_invalid": "Pick a valid value for the argument '{name}': {error}", + "app_argument_password_help": "Type the key Enter to keep the old value", + "app_argument_password_help_optional": "Type one space to empty the password", "app_argument_password_no_default": "Error while parsing password argument '{name}': password argument can't have a default value for security reason", "app_argument_required": "Argument '{name}' is required", "app_change_url_identical_domains": "The old and new domain/url_path are identical ('{domain}{path}'), nothing to do.", diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 064278008..6f3d05622 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -468,6 +468,20 @@ class Question(object): def normalize(value, option={}): return value + def _prompt(self, text): + prefill = "" + if self.current_value is not None: + prefill = self.humanize(self.current_value, self) + elif self.default is not None: + prefill = self.humanize(self.default, self) + self.value = Moulinette.prompt( + message=text, + is_password=self.hide_user_input_in_prompt, + confirm=False, # We doesn't want to confirm this kind of password like in webadmin + prefill=prefill, + is_multiline=(self.type == "text"), + ) + def ask_if_needed(self): for i in range(5): # Display question if no value filled or if it's a readonly message @@ -475,20 +489,8 @@ class Question(object): text_for_user_input_in_cli = self._format_text_for_user_input_in_cli() if getattr(self, "readonly", False): Moulinette.display(text_for_user_input_in_cli) - elif self.value is None: - prefill = "" - if self.current_value is not None: - prefill = self.humanize(self.current_value, self) - elif self.default is not None: - prefill = self.humanize(self.default, self) - self.value = Moulinette.prompt( - message=text_for_user_input_in_cli, - is_password=self.hide_user_input_in_prompt, - confirm=self.hide_user_input_in_prompt, - prefill=prefill, - is_multiline=(self.type == "text"), - ) + self._prompt(text_for_user_input_in_cli) # Apply default value class_default = getattr(self, "default_value", None) @@ -542,13 +544,13 @@ class Question(object): choices=", ".join(self.choices), ) - def _format_text_for_user_input_in_cli(self): + def _format_text_for_user_input_in_cli(self, column=False): text_for_user_input_in_cli = _value_for_locale(self.ask) if self.choices: text_for_user_input_in_cli += " [{0}]".format(" | ".join(self.choices)) - if self.help: + if self.help or column: text_for_user_input_in_cli += ":\033[m" if self.help: text_for_user_input_in_cli += "\n - " @@ -689,6 +691,23 @@ class PasswordQuestion(Question): assert_password_is_strong_enough("user", self.value) + def _format_text_for_user_input_in_cli(self): + need_column = self.current_value or self.optional + text_for_user_input_in_cli = super()._format_text_for_user_input_in_cli(need_column) + if self.current_value: + text_for_user_input_in_cli += "\n - " + m18n.n("app_argument_password_help") + if self.optional: + text_for_user_input_in_cli += "\n - " + m18n.n("app_argument_password_help_optional") + + return text_for_user_input_in_cli + + def _prompt(self, text): + super()._prompt(text) + if self.current_value and self.value == "": + self.value = self.current_value + elif self.value == " ": + self.value = "" + class PathQuestion(Question): argument_type = "path" From 02480d27d7ebe2d119ab7766c91027b8bf0f07e6 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 14 Sep 2021 22:48:03 +0200 Subject: [PATCH 3024/3170] Wording --- locales/en.json | 2 +- src/yunohost/utils/config.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/en.json b/locales/en.json index 5ad7d25b7..5bb408f1d 100644 --- a/locales/en.json +++ b/locales/en.json @@ -15,7 +15,7 @@ "app_already_up_to_date": "{app} is already up-to-date", "app_argument_choice_invalid": "Use one of these choices '{choices}' for the argument '{name}' instead of '{value}'", "app_argument_invalid": "Pick a valid value for the argument '{name}': {error}", - "app_argument_password_help": "Type the key Enter to keep the old value", + "app_argument_password_help_keep": "Press Enter to keep the current value", "app_argument_password_help_optional": "Type one space to empty the password", "app_argument_password_no_default": "Error while parsing password argument '{name}': password argument can't have a default value for security reason", "app_argument_required": "Argument '{name}' is required", diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 6f3d05622..ccd8951eb 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -695,7 +695,7 @@ class PasswordQuestion(Question): need_column = self.current_value or self.optional text_for_user_input_in_cli = super()._format_text_for_user_input_in_cli(need_column) if self.current_value: - text_for_user_input_in_cli += "\n - " + m18n.n("app_argument_password_help") + text_for_user_input_in_cli += "\n - " + m18n.n("app_argument_password_help_keep") if self.optional: text_for_user_input_in_cli += "\n - " + m18n.n("app_argument_password_help_optional") From 57f00190b0b6b272429530d94f4c3131d4113b10 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 14 Sep 2021 22:58:43 +0200 Subject: [PATCH 3025/3170] debian: install the new domain config panel and registrar list toml to /usr/share --- data/other/registrar_list.yml | 218 ---------------------------------- debian/install | 3 +- 2 files changed, 2 insertions(+), 219 deletions(-) delete mode 100644 data/other/registrar_list.yml diff --git a/data/other/registrar_list.yml b/data/other/registrar_list.yml deleted file mode 100644 index a006bd272..000000000 --- a/data/other/registrar_list.yml +++ /dev/null @@ -1,218 +0,0 @@ -aliyun: - - auth_key_id - - auth_secret -aurora: - - auth_api_key - - auth_secret_key -azure: - - auth_client_id - - auth_client_secret - - auth_tenant_id - - auth_subscription_id - - resource_group -cloudflare: - - auth_username - - auth_token - - zone_id -cloudns: - - auth_id - - auth_subid - - auth_subuser - - auth_password - - weight - - port -cloudxns: - - auth_username - - auth_token -conoha: - - auth_region - - auth_token - - auth_username - - auth_password - - auth_tenant_id -constellix: - - auth_username - - auth_token -digitalocean: - - auth_token -dinahosting: - - auth_username - - auth_password -directadmin: - - auth_password - - auth_username - - endpoint -dnsimple: - - auth_token - - auth_username - - auth_password - - auth_2fa -dnsmadeeasy: - - auth_username - - auth_token -dnspark: - - auth_username - - auth_token -dnspod: - - auth_username - - auth_token -dreamhost: - - auth_token -dynu: - - auth_token -easydns: - - auth_username - - auth_token -easyname: - - auth_username - - auth_password -euserv: - - auth_username - - auth_password -exoscale: - - auth_key - - auth_secret -gandi: - - auth_token - - api_protocol -gehirn: - - auth_token - - auth_secret -glesys: - - auth_username - - auth_token -godaddy: - - auth_key - - auth_secret -googleclouddns: - - auth_service_account_info -gransy: - - auth_username - - auth_password -gratisdns: - - auth_username - - auth_password -henet: - - auth_username - - auth_password -hetzner: - - auth_token -hostingde: - - auth_token -hover: - - auth_username - - auth_password -infoblox: - - auth_user - - auth_psw - - ib_view - - ib_host -infomaniak: - - auth_token -internetbs: - - auth_key - - auth_password -inwx: - - auth_username - - auth_password -joker: - - auth_token -linode: - - auth_token -linode4: - - auth_token -localzone: - - filename -luadns: - - auth_username - - auth_token -memset: - - auth_token -mythicbeasts: - - auth_username - - auth_password - - auth_token -namecheap: - - auth_token - - auth_username - - auth_client_ip - - auth_sandbox -namesilo: - - auth_token -netcup: - - auth_customer_id - - auth_api_key - - auth_api_password -nfsn: - - auth_username - - auth_token -njalla: - - auth_token -nsone: - - auth_token -onapp: - - auth_username - - auth_token - - auth_server -online: - - auth_token -ovh: - - auth_entrypoint - - auth_application_key - - auth_application_secret - - auth_consumer_key -plesk: - - auth_username - - auth_password - - plesk_server -pointhq: - - auth_username - - auth_token -powerdns: - - auth_token - - pdns_server - - pdns_server_id - - pdns_disable_notify -rackspace: - - auth_account - - auth_username - - auth_api_key - - auth_token - - sleep_time -rage4: - - auth_username - - auth_token -rcodezero: - - auth_token -route53: - - auth_access_key - - auth_access_secret - - private_zone - - auth_username - - auth_token -safedns: - - auth_token -sakuracloud: - - auth_token - - auth_secret -softlayer: - - auth_username - - auth_api_key -transip: - - auth_username - - auth_api_key -ultradns: - - auth_token - - auth_username - - auth_password -vultr: - - auth_token -yandex: - - auth_token -zeit: - - auth_token -zilore: - - auth_key -zonomi: - - auth_token - - auth_entrypoint diff --git a/debian/install b/debian/install index 55ddb34c6..a653a40ba 100644 --- a/debian/install +++ b/debian/install @@ -8,7 +8,8 @@ data/other/yunoprompt.service /etc/systemd/system/ data/other/password/* /usr/share/yunohost/other/password/ data/other/dpkg-origins/yunohost /etc/dpkg/origins data/other/dnsbl_list.yml /usr/share/yunohost/other/ -data/other/registrar_list.yml /usr/share/yunohost/other/ +data/other/config_domain.toml /usr/share/yunohost/other/ +data/other/registrar_list.toml /usr/share/yunohost/other/ data/other/ffdhe2048.pem /usr/share/yunohost/other/ data/other/* /usr/share/yunohost/yunohost-config/moulinette/ data/templates/* /usr/share/yunohost/templates/ From acb40424a666ede211b2cc1cd82f3f998de2839c Mon Sep 17 00:00:00 2001 From: Kay0u Date: Wed, 15 Sep 2021 16:40:47 +0200 Subject: [PATCH 3026/3170] avoid to retrieve final path in the config panel scripts --- 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 b42bed925..cfd773bd9 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1766,7 +1766,6 @@ class AppConfigPanel(ConfigPanel): default_script = """#!/bin/bash source /usr/share/yunohost/helpers ynh_abort_if_errors -final_path=$(ynh_app_setting_get $app final_path) ynh_app_config_run $1 """ write_to_file(config_script, default_script) @@ -1774,11 +1773,13 @@ ynh_app_config_run $1 # Call config script to extract current values logger.debug(f"Calling '{action}' action from config script") app_id, app_instance_nb = _parse_app_instance_name(self.app) + settings = _get_app_settings(app_id) env.update( { "app_id": app_id, "app": self.app, "app_instance_nb": str(app_instance_nb), + "final_path": settings.get("final_path", "") } ) From 217ae87bb3e425fd315ce1a7dd6aea58ad9eb74c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 15 Sep 2021 17:26:39 +0200 Subject: [PATCH 3027/3170] Fix duplicate cert routes --- data/actionsmap/yunohost.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 93391a81b..6a5e594d3 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -484,7 +484,6 @@ domain: dns-conf: deprecated: true action_help: Generate sample DNS configuration for a domain - api: GET /domains//dns arguments: domain: help: Target domain @@ -510,7 +509,6 @@ domain: cert-status: deprecated: true action_help: List status of current certificates (all by default). - api: GET /domains//cert arguments: domain_list: help: Domains to check @@ -523,7 +521,6 @@ domain: cert-install: deprecated: true action_help: Install Let's Encrypt certificates for given domains (all by default). - api: PUT /domains//cert arguments: domain_list: help: Domains for which to install the certificates @@ -545,7 +542,6 @@ domain: cert-renew: deprecated: true action_help: Renew the Let's Encrypt certificates for given domains (all by default). - api: PUT /domains//cert/renew arguments: domain_list: help: Domains for which to renew the certificates From 1b47859d373c27a832ec5339d1c58f7a83318ea9 Mon Sep 17 00:00:00 2001 From: ljf Date: Thu, 16 Sep 2021 03:13:32 +0200 Subject: [PATCH 3028/3170] [fix) Pattern and empty string --- src/yunohost/utils/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 6f3d05622..9a737242c 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -526,7 +526,7 @@ class Question(object): raise YunohostValidationError("app_argument_required", name=self.name) # we have an answer, do some post checks - if self.value is not None: + if self.value not in [None, ""]: if self.choices and self.value not in self.choices: self._raise_invalid_answer() if self.pattern and not re.match(self.pattern["regexp"], str(self.value)): From ab3184cced7acc0f4e006346ea3219f64b374676 Mon Sep 17 00:00:00 2001 From: Kayou Date: Thu, 16 Sep 2021 11:16:24 +0200 Subject: [PATCH 3029/3170] Fix autofix-translated-strings job --- .gitlab/ci/translation.gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab/ci/translation.gitlab-ci.yml b/.gitlab/ci/translation.gitlab-ci.yml index e6365adbc..41e8c82d2 100644 --- a/.gitlab/ci/translation.gitlab-ci.yml +++ b/.gitlab/ci/translation.gitlab-ci.yml @@ -20,7 +20,7 @@ autofix-translated-strings: - python3 reformat_locales.py - '[ $(git diff -w | wc -l) != 0 ] || exit 0' # stop if there is nothing to commit - git commit -am "[CI] Reformat / remove stale translated strings" || true - - git push -f origin "ci-remove-stale-translated-strings-${CI_COMMIT_REF_NAME}":"ci-remove-stale-translated-strings-${CI_COMMIT_REF_NAME}" + - git push -f origin "HEAD":"ci-remove-stale-translated-strings-${CI_COMMIT_REF_NAME}" - hub pull-request -m "[CI] Reformat / remove stale translated strings" -b Yunohost:dev -p || true # GITHUB_USER and GITHUB_TOKEN registered here https://gitlab.com/yunohost/yunohost/-/settings/ci_cd only: variables: From 63d3ccd827d369babaa056438580d5b661f0baad Mon Sep 17 00:00:00 2001 From: Kay0u Date: Thu, 16 Sep 2021 11:22:53 +0200 Subject: [PATCH 3030/3170] Fix test questions --- src/yunohost/tests/test_questions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/tests/test_questions.py b/src/yunohost/tests/test_questions.py index 93149b272..67b50769b 100644 --- a/src/yunohost/tests/test_questions.py +++ b/src/yunohost/tests/test_questions.py @@ -552,7 +552,7 @@ def test_question_password_input_test_ask(): prompt.assert_called_with( message=ask_text, is_password=True, - confirm=True, + confirm=False, prefill="", is_multiline=False, ) From 96b112ac7f397b966d3a67cd9b80d8ee5776ab82 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 16 Sep 2021 17:49:58 +0200 Subject: [PATCH 3031/3170] config get: Also inject ask strings in full mode --- src/yunohost/utils/config.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index b11d95138..abb901e84 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -65,10 +65,6 @@ class ConfigPanel: self._load_current_values() self._hydrate() - # Format result in full mode - if mode == "full": - return self.config - # In 'classic' mode, we display the current value if key refer to an option if self.filter_key.count(".") == 2 and mode == "classic": option = self.filter_key.split(".")[-1] @@ -81,13 +77,19 @@ class ConfigPanel: key = f"{panel['id']}.{section['id']}.{option['id']}" if mode == "export": result[option["id"]] = option.get("current_value") + continue + + ask = None + if "ask" in option: + ask = _value_for_locale(option["ask"]) + elif "i18n" in self.config: + ask = m18n.n(self.config["i18n"] + "_" + option["id"]) + + if mode == "full": + # edit self.config directly + option["ask"] = ask else: - if "ask" in option: - result[key] = {"ask": _value_for_locale(option["ask"])} - elif "i18n" in self.config: - result[key] = { - "ask": m18n.n(self.config["i18n"] + "_" + option["id"]) - } + result[key] = {"ask": ask} if "current_value" in option: question_class = ARGUMENTS_TYPE_PARSERS[ option.get("type", "string") @@ -96,7 +98,10 @@ class ConfigPanel: option["current_value"], option ) - return result + if mode == "full": + return self.config + else: + return result def set( self, key=None, value=None, args=None, args_file=None, operation_logger=None From 47db3e798e43636e11db8118a74f92b304b06beb Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 16 Sep 2021 18:52:21 +0200 Subject: [PATCH 3032/3170] config helpers: Disable bash xtrace during ynh_read/write_var_from_file to avoid an horrendous amount of debug logs --- data/helpers.d/utils | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index ef019e894..1d9e6833c 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -519,6 +519,8 @@ ynh_read_var_in_file() { [[ -f $file ]] || ynh_die --message="File $file does not exists" + set +o xtrace # set +x + # Get the line number after which we search for the variable local line_number=1 if [[ -n "$after" ]]; @@ -526,6 +528,7 @@ ynh_read_var_in_file() { line_number=$(grep -n $after $file | cut -d: -f1) if [[ -z "$line_number" ]]; then + set -o xtrace # set -x return 1 fi fi @@ -555,6 +558,7 @@ ynh_read_var_in_file() { # Extract the part after assignation sign local expression_with_comment="$(tail +$line_number ${file} | grep -i -o -P $var_part'\K.*$' || echo YNH_NULL | head -n1)" if [[ "$expression_with_comment" == "YNH_NULL" ]]; then + set -o xtrace # set -x echo YNH_NULL return 0 fi @@ -570,6 +574,7 @@ ynh_read_var_in_file() { else echo "$expression" fi + set -o xtrace # set -x } # Set a value into heterogeneous file (yaml, json, php, python...) @@ -594,6 +599,8 @@ ynh_write_var_in_file() { [[ -f $file ]] || ynh_die --message="File $file does not exists" + set +o xtrace # set +x + # Get the line number after which we search for the variable local line_number=1 if [[ -n "$after" ]]; @@ -601,6 +608,7 @@ ynh_write_var_in_file() { line_number=$(grep -n $after $file | cut -d: -f1) if [[ -z "$line_number" ]]; then + set -o xtrace # set -x return 1 fi fi @@ -631,6 +639,7 @@ ynh_write_var_in_file() { # Extract the part after assignation sign local expression_with_comment="$(tail +$line_number ${file} | grep -i -o -P $var_part'\K.*$' || echo YNH_NULL | head -n1)" if [[ "$expression_with_comment" == "YNH_NULL" ]]; then + set -o xtrace # set -x return 1 fi @@ -661,6 +670,7 @@ ynh_write_var_in_file() { fi sed -ri "${range}s$delimiter(^${var_part}).*\$$delimiter\1${value}${endline}${delimiter}i" ${file} fi + set -o xtrace # set -x } From 62ebd68e34db8d0b00dc42aaa96668f2dd833343 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 16 Sep 2021 20:03:27 +0200 Subject: [PATCH 3033/3170] config/user question: raise an error if no user exists yet --- src/yunohost/utils/config.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index a84b4cafe..630af1741 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -815,6 +815,14 @@ class UserQuestion(Question): super().__init__(question, user_answers) self.choices = user_list()["users"] + + if not self.choices: + raise YunohostValidationError( + "app_argument_invalid", + name=self.name, + error="You should create a YunoHost user first." + ) + if self.default is None: root_mail = "root@%s" % _get_maindomain() for user in self.choices.keys(): From a4526da4f2d3f568675752c2610d809fd545a44b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 16 Sep 2021 20:12:33 +0200 Subject: [PATCH 3034/3170] install questions: domain/user/password don't need default or example values --- src/yunohost/app.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index cfd773bd9..3ba7fd5e4 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -2180,6 +2180,13 @@ def _set_default_ask_questions(arguments): key = "app_manifest_%s_ask_%s" % (script_name, arg["name"]) arg["ask"] = m18n.n(key) + # Also it in fact doesn't make sense for any of those questions to have an example value nor a default value... + if arg.get("type") in ["domain", "user", "password"]: + if "example" in arg: + del arg["example"] + if "default" in arg: + del arg["domain"] + return arguments From 86a74903db339a669b7c40063f142fc1019d464e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 16 Sep 2021 20:21:41 +0200 Subject: [PATCH 3035/3170] autodns dryrun: return a proper dict/list structure instead of a raw string --- src/yunohost/dns.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index 83319e541..7df6f3aff 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -717,13 +717,12 @@ def domain_registrar_push(operation_logger, domain, dry_run=False, autoremove=Fa return f'{name:>20} [{t:^5}] {old_content:^30} -> {new_content:^30}' if dry_run: - out = [] + out = {"delete": [], "create": [], "update": [], "ignored": []} for action in ["delete", "create", "update"]: - out.append("\n" + action + ":\n") for record in changes[action]: - out.append(human_readable_record(action, record)) + out[action].append(human_readable_record(action, record)) - return '\n'.join(out) + return out operation_logger.start() From 2181bc1bab36ae0b4db76b78eb21e786cfb4bd73 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 16 Sep 2021 20:34:24 +0200 Subject: [PATCH 3036/3170] log: Improve filtering of boring/irrelevant lines --- data/helpers.d/string | 2 ++ data/helpers.d/utils | 5 +++- src/yunohost/log.py | 53 ++++++++++++++++++++++++++----------------- 3 files changed, 38 insertions(+), 22 deletions(-) diff --git a/data/helpers.d/string b/data/helpers.d/string index 7036b3b3c..a96157f78 100644 --- a/data/helpers.d/string +++ b/data/helpers.d/string @@ -43,12 +43,14 @@ ynh_replace_string () { local target_file # Manage arguments with getopts ynh_handle_getopts_args "$@" + set +o xtrace # set +x local delimit=@ # Escape the delimiter if it's in the string. match_string=${match_string//${delimit}/"\\${delimit}"} replace_string=${replace_string//${delimit}/"\\${delimit}"} + set -o xtrace # set -x sed --in-place "s${delimit}${match_string}${delimit}${replace_string}${delimit}g" "$target_file" } diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 1d9e6833c..c957fdfcc 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -642,7 +642,7 @@ ynh_write_var_in_file() { set -o xtrace # set -x return 1 fi - + # Remove comments if needed local expression="$(echo "$expression_with_comment" | sed "s@$comments[^$string]*\$@@g" | sed "s@\s*[$endline]*\s*]*\$@@")" endline=${expression_with_comment#"$expression"} @@ -737,6 +737,7 @@ ynh_secure_remove () { local file # Manage arguments with getopts ynh_handle_getopts_args "$@" + set +o xtrace # set +x local forbidden_path=" \ /var/www \ @@ -764,6 +765,8 @@ ynh_secure_remove () { else ynh_print_info --message="'$file' wasn't deleted because it doesn't exist." fi + + set -o xtrace # set -x } # Extract a key from a plain command output diff --git a/src/yunohost/log.py b/src/yunohost/log.py index b7b585c94..ebf06069c 100644 --- a/src/yunohost/log.py +++ b/src/yunohost/log.py @@ -151,26 +151,37 @@ def log_show( filter_irrelevant = True if filter_irrelevant: - filters = [ - r"set [+-]x$", - r"set [+-]o xtrace$", - r"local \w+$", - r"local legacy_args=.*$", - r".*Helper used in legacy mode.*", - r"args_array=.*$", - r"local -A args_array$", - r"ynh_handle_getopts_args", - r"ynh_script_progression", - ] + + def _filter(lines): + filters = [ + r"set [+-]x$", + r"set [+-]o xtrace$", + r"set [+-]o errexit$", + r"set [+-]o nounset$", + r"trap '' EXIT", + r"local \w+$", + r"local exit_code=(1|0)$", + r"local legacy_args=.*$", + r"local -A args_array$", + r"args_array=.*$", + r"ret_code=1", + r".*Helper used in legacy mode.*", + r"ynh_handle_getopts_args", + r"ynh_script_progression", + r"sleep 0.5", + r"'\[' (1|0) -eq (1|0) '\]'$", + r"\[?\['? -n '' '?\]\]?$", + r"rm -rf /var/cache/yunohost/download/$", + r"type -t ynh_clean_setup$", + r"DEBUG - \+ echo '", + r"DEBUG - \+ exit (1|0)$", + ] + filters = [re.compile(f) for f in filters] + return [line for line in lines if not any(f.search(line.strip()) for f in filters)] else: - filters = [] + def _filter(lines): + return lines - def _filter_lines(lines, filters=[]): - - filters = [re.compile(f) for f in filters] - return [ - line for line in lines if not any(f.search(line.strip()) for f in filters) - ] # Normalize log/metadata paths and filenames abs_path = path @@ -209,7 +220,7 @@ def log_show( content += "\n============\n\n" if os.path.exists(log_path): actual_log = read_file(log_path) - content += "\n".join(_filter_lines(actual_log.split("\n"), filters)) + content += "\n".join(_filter(actual_log.split("\n"))) url = yunopaste(content) @@ -282,13 +293,13 @@ def log_show( if os.path.exists(log_path): from yunohost.service import _tail - if number and filters: + if number and filter_irrelevant: logs = _tail(log_path, int(number * 4)) elif number: logs = _tail(log_path, int(number)) else: logs = read_file(log_path) - logs = _filter_lines(logs, filters) + logs = list(_filter(logs)) if number: logs = logs[-number:] infos["log_path"] = log_path From c4fe99170179e3b879b746743d7e91cd2cc7933f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 16 Sep 2021 20:46:36 +0200 Subject: [PATCH 3037/3170] log: log list was displaying much less entries than required on webadmin --- src/yunohost/log.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/yunohost/log.py b/src/yunohost/log.py index ebf06069c..3f25d7a7d 100644 --- a/src/yunohost/log.py +++ b/src/yunohost/log.py @@ -69,7 +69,13 @@ def log_list(limit=None, with_details=False, with_suboperations=False): logs = list(reversed(sorted(logs))) if limit is not None: - logs = logs[:limit] + if with_suboperations: + logs = logs[:limit] + else: + # If we displaying only parent, we are still gonna load up to limit * 5 logs + # because many of them are suboperations which are not gonna be kept + # Yet we still want to obtain ~limit number of logs + logs = logs[:limit * 5] for log in logs: @@ -122,6 +128,9 @@ def log_list(limit=None, with_details=False, with_suboperations=False): else: operations = [o for o in operations.values()] + if limit: + operations = operations[:limit] + operations = list(reversed(sorted(operations, key=lambda o: o["name"]))) # Reverse the order of log when in cli, more comfortable to read (avoid # unecessary scrolling) From 88da178cb76630522840db1d7ac81b7277e98c0a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 16 Sep 2021 20:56:06 +0200 Subject: [PATCH 3038/3170] Fix tests: unbound variable during backup/restore --- 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 c957fdfcc..34a089eb1 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -1,6 +1,6 @@ #!/bin/bash -YNH_APP_BASEDIR=$(realpath $([[ "$(basename $0)" =~ ^backup|restore$ ]] && echo '../settings' || [[ -n "$YNH_ACTION" ]] && echo '.' || echo '..' )) +YNH_APP_BASEDIR=$(realpath $([[ "$(basename $0)" =~ ^backup|restore$ ]] && echo '../settings' || [[ -n "${YNH_ACTION:-}" ]] && echo '.' || echo '..' )) # Handle script crashes / failures # From bc39788da9da85e8f28b694fec37ef01382276da Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 16 Sep 2021 21:50:44 +0200 Subject: [PATCH 3039/3170] autodns: minor simplification in create/delete/update strategy --- src/yunohost/dns.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index 7df6f3aff..6d589a10e 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -647,25 +647,21 @@ def domain_registrar_push(operation_logger, domain, dry_run=False, autoremove=Fa wanted = [r for r in records["wanted"] if r["content"] not in current_contents] # - # Step 2 : simple case: 0 or 1 record on one side, 0 or 1 on the other - # -> either nothing do (0/0) or a creation (0/1) or a deletion (1/0), or an update (1/1) + # Step 2 : simple case: 0 record on one side, 0 on the other + # -> either nothing do (0/0) or creations (0/N) or deletions (N/0) # if len(current) == 0 and len(wanted) == 0: # No diff, nothing to do continue - if len(current) == 1 and len(wanted) == 0: - changes["delete"].append(current[0]) + elif len(wanted) == 0: + for r in current: + changes["delete"].append(r) continue - if len(current) == 0 and len(wanted) == 1: - changes["create"].append(wanted[0]) - continue - - if len(current) == 1 and len(wanted) == 1: - current[0]["old_content"] = current[0]["content"] - current[0]["content"] = wanted[0]["content"] - changes["update"].append(current[0]) + elif len(current) == 0: + for r in current: + changes["create"].append(r) continue # From 0a404f6d56afabd26bd73226404a61187e9fe7dd Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 17 Sep 2021 00:30:47 +0200 Subject: [PATCH 3040/3170] autodns: Improve the push system to save managed dns record hashes, similar to the regenconf mecanism --- data/actionsmap/yunohost.yml | 4 +- src/yunohost/dns.py | 89 +++++++++++++++++++++++++----------- src/yunohost/domain.py | 24 ++++++++-- 3 files changed, 85 insertions(+), 32 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 6a5e594d3..b845ded21 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -641,8 +641,8 @@ domain: full: --dry-run help: Only display what's to be pushed action: store_true - --autoremove: - help: Also autoremove records which are stale or not part of the recommended configuration + --force: + help: Also update/remove records which were not originally set by Yunohost, or which have been manually modified action: store_true --purge: help: Delete all records diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index 6d589a10e..fdb6d4647 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -26,6 +26,8 @@ import os import re import time +import hashlib + from difflib import SequenceMatcher from collections import OrderedDict @@ -33,7 +35,7 @@ from moulinette import m18n, Moulinette from moulinette.utils.log import getActionLogger from moulinette.utils.filesystem import read_file, write_to_file, read_toml -from yunohost.domain import domain_list, _assert_domain_exists, domain_config_get +from yunohost.domain import domain_list, _assert_domain_exists, domain_config_get, _get_domain_settings, _set_domain_settings from yunohost.utils.dns import dig, YNH_DYNDNS_DOMAINS from yunohost.utils.error import YunohostValidationError from yunohost.utils.network import get_public_ip @@ -516,7 +518,7 @@ def _get_registrar_config_section(domain): @is_unit_operation() -def domain_registrar_push(operation_logger, domain, dry_run=False, autoremove=False, purge=False): +def domain_registrar_push(operation_logger, domain, dry_run=False, force=False, purge=False): """ Send DNS records to the previously-configured registrar of the domain. """ @@ -533,6 +535,8 @@ def domain_registrar_push(operation_logger, domain, dry_run=False, autoremove=Fa if not registrar or registrar in ["None", "yunohost"]: raise YunohostValidationError("registrar_push_not_applicable", domain=domain) + base_dns_zone = _get_dns_zone_for_domain(domain) + registrar_credentials = { k.split('.')[-1]: v["value"] for k, v in settings.items() @@ -549,13 +553,13 @@ def domain_registrar_push(operation_logger, domain, dry_run=False, autoremove=Fa for record in records: # Make sure the name is a FQDN - name = f"{record['name']}.{domain}" if record["name"] != "@" else f"{domain}" + name = f"{record['name']}.{base_dns_zone}" if record["name"] != "@" else base_dns_zone type_ = record["type"] content = record["value"] # Make sure the content is also a FQDN (with trailing . ?) if content == "@" and record["type"] == "CNAME": - content = domain + "." + content = base_dns_zone + "." wanted_records.append({ "name": name, @@ -572,29 +576,31 @@ def domain_registrar_push(operation_logger, domain, dry_run=False, autoremove=Fa if purge: wanted_records = [] - autoremove = True + force = True # Construct the base data structure to use lexicon's API. base_config = { "provider_name": registrar, - "domain": domain, + "domain": base_dns_zone, registrar: registrar_credentials } # Ugly hack to be able to fetch all record types at once: - # we initialize a LexiconClient with type: dummytype, + # we initialize a LexiconClient with a dummy type "all" + # (which lexicon doesnt actually understands) # then trigger ourselves the authentication + list_records # instead of calling .execute() query = ( LexiconConfigResolver() .with_dict(dict_object=base_config) - .with_dict(dict_object={"action": "list", "type": "dummytype"}) + .with_dict(dict_object={"action": "list", "type": "all"}) ) # current_records.extend( client = LexiconClient(query) client.provider.authenticate() current_records = client.provider.list_records() + managed_dns_records_hashes = _get_managed_dns_records_hashes(domain) # Keep only records for relevant types: A, AAAA, MX, TXT, CNAME, SRV relevant_types = ["A", "AAAA", "MX", "TXT", "CNAME", "SRV"] @@ -602,7 +608,7 @@ def domain_registrar_push(operation_logger, domain, dry_run=False, autoremove=Fa # Ignore records which are for a higher-level domain # i.e. we don't care about the records for domain.tld when pushing yuno.domain.tld - current_records = [r for r in current_records if r['name'].endswith(f'.{domain}')] + current_records = [r for r in current_records if r['name'].endswith(f'.{domain}') or r['name'] == domain] for record in current_records: @@ -610,7 +616,10 @@ def domain_registrar_push(operation_logger, domain, dry_run=False, autoremove=Fa record["name"] = record["name"].strip("@").strip(".") # Some API return '@' in content and we shall convert it to absolute/fqdn - record["content"] = record["content"].replace('@.', domain + ".").replace('@', domain + ".") + record["content"] = record["content"].replace('@.', base_dns_zone + ".").replace('@', base_dns_zone + ".") + + # Check if this record was previously set by YunoHost + record["managed_by_yunohost"] = _hash_dns_record(record) in managed_dns_records_hashes # Step 0 : Get the list of unique (type, name) # And compare the current and wanted records @@ -624,7 +633,7 @@ def domain_registrar_push(operation_logger, domain, dry_run=False, autoremove=Fa # (MX, .domain.tld) 10 domain.tld [10 mx1.ovh.net, 20 mx2.ovh.net] # (TXT, .domain.tld) "v=spf1 ..." ["v=spf1", "foobar"] # (SRV, .domain.tld) 0 5 5269 domain.tld - changes = {"delete": [], "update": [], "create": []} + changes = {"delete": [], "update": [], "create": [], "unchanged": []} type_and_names = set([(r["type"], r["name"]) for r in current_records + wanted_records]) comparison = {type_and_name: {"current": [], "wanted": []} for type_and_name in type_and_names} @@ -652,16 +661,15 @@ def domain_registrar_push(operation_logger, domain, dry_run=False, autoremove=Fa # if len(current) == 0 and len(wanted) == 0: # No diff, nothing to do + changes["unchanged"].extend(records["current"]) continue elif len(wanted) == 0: - for r in current: - changes["delete"].append(r) + changes["delete"].extend(current) continue elif len(current) == 0: - for r in current: - changes["create"].append(r) + changes["create"].extend(wanted) continue # @@ -699,8 +707,8 @@ def domain_registrar_push(operation_logger, domain, dry_run=False, autoremove=Fa def human_readable_record(action, record): name = record["name"] name = name.strip(".") - name = name.replace('.' + domain, "") - name = name.replace(domain, "@") + name = name.replace('.' + base_dns_zone, "") + name = name.replace(base_dns_zone, "@") name = name[:20] t = record["type"] if action in ["create", "update"]: @@ -710,10 +718,15 @@ def domain_registrar_push(operation_logger, domain, dry_run=False, autoremove=Fa new_content = record.get("old_content", "(None)")[:30] old_content = record.get("content", "(None)")[:30] - return f'{name:>20} [{t:^5}] {old_content:^30} -> {new_content:^30}' + if not force and action in ["update", "delete"]: + ignored = "" if record["managed_by_yunohost"] else "(ignored, won't be changed by Yunohost unless forced)" + else: + ignored = "" + + return f'{name:>20} [{t:^5}] {old_content:^30} -> {new_content:^30} {ignored}' if dry_run: - out = {"delete": [], "create": [], "update": [], "ignored": []} + out = {"delete": [], "create": [], "update": []} for action in ["delete", "create", "update"]: for record in changes[action]: out[action].append(human_readable_record(action, record)) @@ -722,13 +735,17 @@ def domain_registrar_push(operation_logger, domain, dry_run=False, autoremove=Fa operation_logger.start() + new_managed_dns_records_hashes = [_hash_dns_record(r) for r in changes["unchanged"]] + # Push the records for action in ["delete", "create", "update"]: - if action == "delete" and not autoremove: - continue for record in changes[action]: + if not force and action in ["update", "delete"] and not record["managed_by_yunohost"]: + # Don't overwrite manually-set or manually-modified records + continue + record["action"] = action # Apparently Lexicon yields us some 'id' during fetch @@ -737,11 +754,10 @@ def domain_registrar_push(operation_logger, domain, dry_run=False, autoremove=Fa record["identifier"] = record["id"] del record["id"] - if "old_content" in record: - del record["old_content"] + logger.info(action + " : " + human_readable_record(action, record)) if registrar == "godaddy": - if record["name"] == domain: + if record["name"] == base_dns_zone: record["name"] = "@." + record["name"] if record["type"] in ["MX", "SRV"]: logger.warning(f"Pushing {record['type']} records is not properly supported by Lexicon/Godaddy.") @@ -753,8 +769,6 @@ def domain_registrar_push(operation_logger, domain, dry_run=False, autoremove=Fa if registrar == "gandi": del record["ttl"] - logger.info(action + " : " + human_readable_record(action, record)) - query = ( LexiconConfigResolver() .with_dict(dict_object=base_config) @@ -767,8 +781,29 @@ def domain_registrar_push(operation_logger, domain, dry_run=False, autoremove=Fa logger.error(f"Failed to {action} record {record['type']}/{record['name']} : {e}") else: if result: + new_managed_dns_records_hashes.append(_hash_dns_record(record)) logger.success("Done!") else: logger.error("Uhoh!?") - # FIXME : implement a system to properly report what worked and what did not at the end of the command.. + _set_managed_dns_records_hashes(domain, new_managed_dns_records_hashes) + + # FIXME : implement a system to properly report what worked and what did not at the end of the command.. + + +def _get_managed_dns_records_hashes(domain: str) -> list: + return _get_domain_settings(domain).get("managed_dns_records_hashes", []) + + +def _set_managed_dns_records_hashes(domain: str, hashes: list) -> None: + settings = _get_domain_settings(domain) + settings["managed_dns_records_hashes"] = hashes or [] + _set_domain_settings(domain, settings) + + +def _hash_dns_record(record: dict) -> int: + + fields = ["name", "type", "content"] + record_ = {f: record.get(f) for f in fields} + + return hash(frozenset(record_.items())) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 9c9ad2d5e..a76faafc0 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -28,7 +28,7 @@ import os from moulinette import m18n, Moulinette from moulinette.core import MoulinetteError from moulinette.utils.log import getActionLogger -from moulinette.utils.filesystem import write_to_file +from moulinette.utils.filesystem import write_to_file, read_yaml, write_to_yaml from yunohost.app import ( app_ssowatconf, @@ -449,6 +449,24 @@ class DomainConfigPanel(ConfigPanel): # FIXME: Ugly hack to save the registar id/value and reinject it in _load_current_values ... self.values["registrar"] = self.registar_id + +def _get_domain_settings(domain: str) -> dict: + + _assert_domain_exists(domain) + + if os.path.exists(f"{DOMAIN_SETTINGS_DIR}/{domain}.yml"): + return read_yaml(f"{DOMAIN_SETTINGS_DIR}/{domain}.yml") or {} + else: + return {} + + +def _set_domain_settings(domain: str, settings: dict) -> None: + + _assert_domain_exists(domain) + + write_to_yaml(f"{DOMAIN_SETTINGS_DIR}/{domain}.yml", settings) + + # # # Stuff managed in other files @@ -491,6 +509,6 @@ def domain_dns_suggest(domain): return yunohost.dns.domain_dns_suggest(domain) -def domain_dns_push(domain, dry_run, autoremove, purge): +def domain_dns_push(domain, dry_run, force, purge): import yunohost.dns - return yunohost.dns.domain_registrar_push(domain, dry_run, autoremove, purge) + return yunohost.dns.domain_registrar_push(domain, dry_run, force, purge) From cfceca581f0e873fef2910da1c181397e8b8a895 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 17 Sep 2021 00:34:44 +0200 Subject: [PATCH 3041/3170] domain config: prevent a warning being raised because of unexpected value key --- src/yunohost/domain.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index a76faafc0..2c826bb51 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -438,6 +438,7 @@ class DomainConfigPanel(ConfigPanel): # FIXME: Ugly hack to save the registar id/value and reinject it in _load_current_values ... self.registar_id = toml['dns']['registrar']['registrar']['value'] + del toml['dns']['registrar']['registrar']['value'] return toml From 403c36d984d65ead73a021c061b3c4d9741ab0f1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 17 Sep 2021 00:39:14 +0200 Subject: [PATCH 3042/3170] config/questions: fix alert display in cli --- src/yunohost/utils/config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 630af1741..1904d951a 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -905,8 +905,8 @@ class DisplayTextQuestion(Question): "warning": "yellow", "danger": "red", } - text = m18n.g(self.style) if self.style != "danger" else m18n.n("danger") - return colorize(text, color[self.style]) + f" {text}" + prompt = m18n.g(self.style) if self.style != "danger" else m18n.n("danger") + return colorize(prompt, color[self.style]) + f" {text}" else: return text From 7655b7b7c38dacbc0258418585b1d8daf0a125c6 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 17 Sep 2021 00:47:16 +0200 Subject: [PATCH 3043/3170] dns: fix typo in tests --- src/yunohost/tests/test_dns.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/tests/test_dns.py b/src/yunohost/tests/test_dns.py index 58c2be3a5..154d1dc9a 100644 --- a/src/yunohost/tests/test_dns.py +++ b/src/yunohost/tests/test_dns.py @@ -58,7 +58,7 @@ def test_magic_guess_registrar_yunodyndns(): @pytest.fixture def example_domain(): domain_add("example.tld") - yield "example_tld" + yield "example.tld" domain_remove("example.tld") From 71b09ae0e7c9553c8ad9b00e9e8455268511c4aa Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 17 Sep 2021 01:15:36 +0200 Subject: [PATCH 3044/3170] autodns dry-run: return a proper datastructure to the api instead of ~humanfriendly string --- src/yunohost/dns.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index fdb6d4647..09c8b978a 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -726,12 +726,15 @@ def domain_registrar_push(operation_logger, domain, dry_run=False, force=False, return f'{name:>20} [{t:^5}] {old_content:^30} -> {new_content:^30} {ignored}' if dry_run: - out = {"delete": [], "create": [], "update": []} - for action in ["delete", "create", "update"]: - for record in changes[action]: - out[action].append(human_readable_record(action, record)) + if Moulinette.interface.type == "api": + return changes + else: + out = {"delete": [], "create": [], "update": []} + for action in ["delete", "create", "update"]: + for record in changes[action]: + out[action].append(human_readable_record(action, record)) - return out + return out operation_logger.start() From 4e5632a24b1199ccbe010912d3bb44af6734d0de Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 17 Sep 2021 01:35:40 +0200 Subject: [PATCH 3045/3170] config: self.app may not be defined --- src/yunohost/utils/config.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 1904d951a..c43500873 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -423,7 +423,8 @@ class ConfigPanel: if services_to_reload: logger.info("Reloading services...") for service in services_to_reload: - service = service.replace("__APP__", self.app) + if hasattr(self, "app"): + service = service.replace("__APP__", self.app) service_reload_or_restart(service) def _iterate(self, trigger=["option"]): From 00cc672b8962bcec530b700ca2a9fad48b3217b5 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 17 Sep 2021 02:14:40 +0200 Subject: [PATCH 3046/3170] autodns: small fix for Gandi which returns TXT record not prefixed/suffixed with quotes --- src/yunohost/dns.py | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index 09c8b978a..cae9037c2 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -618,6 +618,12 @@ def domain_registrar_push(operation_logger, domain, dry_run=False, force=False, # Some API return '@' in content and we shall convert it to absolute/fqdn record["content"] = record["content"].replace('@.', base_dns_zone + ".").replace('@', base_dns_zone + ".") + if record["type"] == "TXT": + if not record["content"].startswith('"'): + record["content"] = '"' + record["content"] + if not record["content"].endswith('"'): + record["content"] = record["content"] + '"' + # Check if this record was previously set by YunoHost record["managed_by_yunohost"] = _hash_dns_record(record) in managed_dns_records_hashes @@ -711,26 +717,33 @@ def domain_registrar_push(operation_logger, domain, dry_run=False, force=False, name = name.replace(base_dns_zone, "@") name = name[:20] t = record["type"] - if action in ["create", "update"]: - old_content = record.get("old_content", "(None)")[:30] - new_content = record.get("content", "(None)")[:30] - else: - new_content = record.get("old_content", "(None)")[:30] - old_content = record.get("content", "(None)")[:30] if not force and action in ["update", "delete"]: ignored = "" if record["managed_by_yunohost"] else "(ignored, won't be changed by Yunohost unless forced)" else: ignored = "" - return f'{name:>20} [{t:^5}] {old_content:^30} -> {new_content:^30} {ignored}' + if action == "create": + old_content = record.get("old_content", "(None)")[:30] + new_content = record.get("content", "(None)")[:30] + return f'{name:>20} [{t:^5}] {new_content:^30} {ignored}' + elif action == "update": + old_content = record.get("old_content", "(None)")[:30] + new_content = record.get("content", "(None)")[:30] + return f'{name:>20} [{t:^5}] {old_content:^30} -> {new_content:^30} {ignored}' + elif action == "unchanged": + old_content = new_content = record.get("content", "(None)")[:30] + return f'{name:>20} [{t:^5}] {old_content:^30}' + else: + old_content = record.get("content", "(None)")[:30] + return f'{name:>20} [{t:^5}] {old_content:^30} {ignored}' if dry_run: if Moulinette.interface.type == "api": return changes else: - out = {"delete": [], "create": [], "update": []} - for action in ["delete", "create", "update"]: + out = {"delete": [], "create": [], "update": [], "unchanged": []} + for action in ["delete", "create", "update", "unchanged"]: for record in changes[action]: out[action].append(human_readable_record(action, record)) From 4a1d6f225743b110623cb4ca89757a572f0273db Mon Sep 17 00:00:00 2001 From: ljf Date: Fri, 17 Sep 2021 03:15:01 +0200 Subject: [PATCH 3047/3170] [fix] Small fixes about file questions in config panel --- data/helpers.d/config | 5 ++++- src/yunohost/utils/config.py | 8 ++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/data/helpers.d/config b/data/helpers.d/config index d12065220..7a2ccde46 100644 --- a/data/helpers.d/config +++ b/data/helpers.d/config @@ -123,7 +123,10 @@ _ynh_app_config_apply() { ynh_print_info --message="File '$bind_file' removed" else ynh_backup_if_checksum_is_different --file="$bind_file" - cp "${!short_setting}" "$bind_file" + if [[ "${!short_setting}" != "$bind_file" ]] + then + cp "${!short_setting}" "$bind_file" + fi ynh_store_file_checksum --file="$bind_file" --update_only ynh_print_info --message="File '$bind_file' overwrited with ${!short_setting}" fi diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index a84b4cafe..3fccd8cc5 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -927,11 +927,11 @@ class FileQuestion(Question): "content": self.value, "filename": user_answers.get(f"{self.name}[name]", self.name), } - # If path file are the same - if self.value and str(self.value) == self.current_value: - self.value = None def _prevalidate(self): + if self.value is None: + self.value = self.current_value + super()._prevalidate() if ( isinstance(self.value, str) @@ -966,7 +966,7 @@ class FileQuestion(Question): if not self.value: return self.value - if Moulinette.interface.type == "api": + if Moulinette.interface.type == "api" and isinstance(self.value, dict): upload_dir = tempfile.mkdtemp(prefix="tmp_configpanel_") FileQuestion.upload_dirs += [upload_dir] From 6ea538a43bb13f1f4d539e566487d6595de8a312 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 17 Sep 2021 04:16:30 +0200 Subject: [PATCH 3048/3170] autodns: better error management + cli ux --- src/yunohost/dns.py | 82 ++++++++++++++++++++++++++++++++------------- 1 file changed, 58 insertions(+), 24 deletions(-) diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index cae9037c2..c9fe6bb62 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -37,7 +37,7 @@ from moulinette.utils.filesystem import read_file, write_to_file, read_toml from yunohost.domain import domain_list, _assert_domain_exists, domain_config_get, _get_domain_settings, _set_domain_settings from yunohost.utils.dns import dig, YNH_DYNDNS_DOMAINS -from yunohost.utils.error import YunohostValidationError +from yunohost.utils.error import YunohostValidationError, YunohostError from yunohost.utils.network import get_public_ip from yunohost.log import is_unit_operation from yunohost.hook import hook_callback @@ -572,7 +572,7 @@ def domain_registrar_push(operation_logger, domain, dry_run=False, force=False, # See https://github.com/AnalogJ/lexicon/issues/282 and https://github.com/AnalogJ/lexicon/pull/371 # They say it's trivial to implement it! # And yet, it is still not done/merged - wanted_records = [record for record in wanted_records if record["type"] != "CAA"] + #wanted_records = [record for record in wanted_records if record["type"] != "CAA"] if purge: wanted_records = [] @@ -596,14 +596,17 @@ def domain_registrar_push(operation_logger, domain, dry_run=False, force=False, .with_dict(dict_object=base_config) .with_dict(dict_object={"action": "list", "type": "all"}) ) - # current_records.extend( client = LexiconClient(query) client.provider.authenticate() - current_records = client.provider.list_records() + try: + current_records = client.provider.list_records() + except Exception as e: + raise YunohostError("Failed to list current records using the registrar's API: %s" % str(e), raw_msg=True) # FIXME: i18n + managed_dns_records_hashes = _get_managed_dns_records_hashes(domain) # Keep only records for relevant types: A, AAAA, MX, TXT, CNAME, SRV - relevant_types = ["A", "AAAA", "MX", "TXT", "CNAME", "SRV"] + relevant_types = ["A", "AAAA", "MX", "TXT", "CNAME", "SRV", "CAA"] current_records = [r for r in current_records if r["type"] in relevant_types] # Ignore records which are for a higher-level domain @@ -640,7 +643,8 @@ def domain_registrar_push(operation_logger, domain, dry_run=False, force=False, # (TXT, .domain.tld) "v=spf1 ..." ["v=spf1", "foobar"] # (SRV, .domain.tld) 0 5 5269 domain.tld changes = {"delete": [], "update": [], "create": [], "unchanged": []} - type_and_names = set([(r["type"], r["name"]) for r in current_records + wanted_records]) + + type_and_names = sorted(set([(r["type"], r["name"]) for r in current_records + wanted_records])) comparison = {type_and_name: {"current": [], "wanted": []} for type_and_name in type_and_names} for record in current_records: @@ -749,20 +753,47 @@ def domain_registrar_push(operation_logger, domain, dry_run=False, force=False, return out + # If --force ain't used, we won't delete/update records not managed by yunohost + if not force: + for action in ["delete", "update"]: + changes[action] = [r for r in changes[action] if not r["managed_by_yunohost"]] + + def progress(info=""): + progress.nb += 1 + width = 20 + bar = int(progress.nb * width / progress.total) + bar = "[" + "#" * bar + "." * (width - bar) + "]" + if info: + bar += " > " + info + if progress.old == bar: + return + progress.old = bar + logger.info(bar) + + progress.nb = 0 + progress.old = "" + progress.total = len(changes["delete"] + changes["create"] + changes["update"]) + + if progress.total == 0: + logger.success("Records already up to date, nothing to do.") # FIXME : i18n + return {} + + # + # Actually push the records + # + operation_logger.start() + logger.info("Pushing DNS records...") new_managed_dns_records_hashes = [_hash_dns_record(r) for r in changes["unchanged"]] + results = {"warnings": [], "errors": []} - # Push the records for action in ["delete", "create", "update"]: for record in changes[action]: - if not force and action in ["update", "delete"] and not record["managed_by_yunohost"]: - # Don't overwrite manually-set or manually-modified records - continue - - record["action"] = action + relative_name = record['name'].replace(base_dns_zone, '').rstrip('.') or '@' + progress(f"{action} {record['type']:^5} / {relative_name}") # FIXME: i18n # Apparently Lexicon yields us some 'id' during fetch # But wants 'identifier' during push ... @@ -770,21 +801,15 @@ def domain_registrar_push(operation_logger, domain, dry_run=False, force=False, record["identifier"] = record["id"] del record["id"] - logger.info(action + " : " + human_readable_record(action, record)) - if registrar == "godaddy": if record["name"] == base_dns_zone: record["name"] = "@." + record["name"] if record["type"] in ["MX", "SRV"]: logger.warning(f"Pushing {record['type']} records is not properly supported by Lexicon/Godaddy.") + results["warning"].append(f"Pushing {record['type']} records is not properly supported by Lexicon/Godaddy.") continue - # FIXME Removed TTL, because it doesn't work with Gandi. - # See https://github.com/AnalogJ/lexicon/issues/726 (similar issue) - # But I think there is another issue with Gandi. Or I'm misusing the API... - if registrar == "gandi": - del record["ttl"] - + record["action"] = action query = ( LexiconConfigResolver() .with_dict(dict_object=base_config) @@ -794,18 +819,27 @@ def domain_registrar_push(operation_logger, domain, dry_run=False, force=False, try: result = LexiconClient(query).execute() except Exception as e: - logger.error(f"Failed to {action} record {record['type']}/{record['name']} : {e}") + logger.error(f"Failed to {action} record {record['type']}/{record['name']} : {e}") # i18n? + results["errors"].append(f"Failed to {action} record {record['type']}/{record['name']} : {e}") else: if result: new_managed_dns_records_hashes.append(_hash_dns_record(record)) - logger.success("Done!") else: - logger.error("Uhoh!?") + results["errors"].append(f"Failed to {action} record {record['type']}/{record['name']} : unknown error?") _set_managed_dns_records_hashes(domain, new_managed_dns_records_hashes) - # FIXME : implement a system to properly report what worked and what did not at the end of the command.. + # Everything succeeded + if len(results["errors"]) == 0: + logger.success("DNS records updated!") # FIXME: i18n + return {} + # Everything failed + elif len(results["errors"]) + len(results["warnings"]) == progress.total: + logger.error("Updating the DNS records failed miserably") # FIXME: i18n + else: + logger.warning("DNS records partially updated: some warnings/errors were reported.") # FIXME: i18n + return results def _get_managed_dns_records_hashes(domain: str) -> list: return _get_domain_settings(domain).get("managed_dns_records_hashes", []) From 9eda00698c23a3fab06959081c408c112070510f Mon Sep 17 00:00:00 2001 From: ljf Date: Fri, 17 Sep 2021 04:36:05 +0200 Subject: [PATCH 3049/3170] [fix] Tags question --- src/yunohost/utils/config.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 8ec198d34..6b823452b 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -646,6 +646,12 @@ class TagsQuestion(Question): return ",".join(value) return value + @staticmethod + def normalize(value, option={}): + if isinstance(value, list): + return ",".join(value) + return value + def _prevalidate(self): values = self.value if isinstance(values, str): @@ -657,6 +663,11 @@ class TagsQuestion(Question): super()._prevalidate() self.value = values + def _post_parse_value(self): + if isinstance(self.value, list): + self.value = ",".join(self.value) + return super()._post_parse_value() + class PasswordQuestion(Question): hide_user_input_in_prompt = True From 7ab9889521cb48f9e3c514f7903070d226218604 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 17 Sep 2021 04:55:07 +0200 Subject: [PATCH 3050/3170] autodns: i18n --- locales/en.json | 13 +++++++++---- src/yunohost/dns.py | 6 +++--- src/yunohost/domain.py | 2 +- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/locales/en.json b/locales/en.json index 282576ee8..4578075b4 100644 --- a/locales/en.json +++ b/locales/en.json @@ -313,12 +313,18 @@ "domain_dyndns_already_subscribed": "You have already subscribed to a DynDNS domain", "domain_dyndns_root_unknown": "Unknown DynDNS root domain", "domain_exists": "The domain already exists", - "domain_property_unknown": "The property {property} doesn't exist", "domain_hostname_failed": "Unable to set new hostname. This might cause an issue later (it might be fine).", "domain_name_unknown": "Domain '{domain}' unknown", - "domain_registrar_unknown": "This registrar is unknown. Look for yours with the command `yunohost domain catalog`", "domain_remove_confirm_apps_removal": "Removing this domain will remove those applications:\n{apps}\n\nAre you sure you want to do that? [{answers}]", "domain_uninstall_app_first": "Those applications are still installed on your domain:\n{apps}\n\nPlease uninstall them using 'yunohost app remove the_app_id' or move them to another domain using 'yunohost app change-url the_app_id' before proceeding to domain removal", + "domain_registrar_is_not_configured": "The registrar is not yet configured for domain {domain}.", + "domain_dns_push_not_applicable": "The DNS push feature is not applicable to domain {domain}", + "domain_config_auth_token": "Authentication token", + "domain_config_api_protocol": "API protocol", + "domain_config_auth_entrypoint": "API entry point", + "domain_config_auth_application_key": "Application key", + "domain_config_auth_application_secret": "Application secret key", + "domain_config_auth_consumer_key": "Consumer key", "domains_available": "Available domains:", "done": "Done", "downloading": "Downloading...", @@ -423,6 +429,7 @@ "log_domain_config_set": "Update configuration for domain '{}'", "log_domain_main_domain": "Make '{}' the main domain", "log_domain_remove": "Remove '{}' domain from system configuration", + "log_domain_dns_push": "Push DNS records for domain '{}'", "log_dyndns_subscribe": "Subscribe to a YunoHost subdomain '{}'", "log_dyndns_update": "Update the IP associated with your YunoHost subdomain '{}'", "log_help_to_get_failed_log": "The operation '{desc}' could not be completed. Please share the full log of this operation using the command 'yunohost log share {name}' to get help", @@ -530,7 +537,6 @@ "pattern_password": "Must be at least 3 characters long", "pattern_password_app": "Sorry, passwords can not contain the following characters: {forbidden_chars}", "pattern_port_or_range": "Must be a valid port number (i.e. 0-65535) or range of ports (e.g. 100:200)", - "pattern_positive_number": "Must be a positive number", "pattern_username": "Must be lower-case alphanumeric and underscore characters only", "permission_already_allowed": "Group '{group}' already has permission '{permission}' enabled", "permission_already_disallowed": "Group '{group}' already has permission '{permission}' disabled", @@ -569,7 +575,6 @@ "regenconf_would_be_updated": "The configuration would have been updated for category '{category}'", "regex_incompatible_with_tile": "/!\\ Packagers! Permission '{permission}' has show_tile set to 'true' and you therefore cannot define a regex URL as the main URL", "regex_with_only_domain": "You can't use a regex for domain, only for path", - "registrar_is_not_set": "The registrar for this domain has not been configured", "restore_already_installed_app": "An app with the ID '{app}' is already installed", "restore_already_installed_apps": "The following apps can't be restored because they are already installed: {apps}", "restore_backup_too_old": "This backup archive can not be restored because it comes from a too-old YunoHost version.", diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index c9fe6bb62..1e2037ce5 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -518,7 +518,7 @@ def _get_registrar_config_section(domain): @is_unit_operation() -def domain_registrar_push(operation_logger, domain, dry_run=False, force=False, purge=False): +def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge=False): """ Send DNS records to the previously-configured registrar of the domain. """ @@ -533,7 +533,7 @@ def domain_registrar_push(operation_logger, domain, dry_run=False, force=False, registrar = settings["dns.registrar.registrar"].get("value") if not registrar or registrar in ["None", "yunohost"]: - raise YunohostValidationError("registrar_push_not_applicable", domain=domain) + raise YunohostValidationError("domain_dns_push_not_applicable", domain=domain) base_dns_zone = _get_dns_zone_for_domain(domain) @@ -544,7 +544,7 @@ def domain_registrar_push(operation_logger, domain, dry_run=False, force=False, } if not all(registrar_credentials.values()): - raise YunohostValidationError("registrar_is_not_configured", domain=domain) + raise YunohostValidationError("domain_registrar_is_not_configured", domain=domain) # Convert the generated conf into a format that matches what we'll fetch using the API # Makes it easier to compare "wanted records" with "current records on remote" diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 2c826bb51..d13900224 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -512,4 +512,4 @@ def domain_dns_suggest(domain): def domain_dns_push(domain, dry_run, force, purge): import yunohost.dns - return yunohost.dns.domain_registrar_push(domain, dry_run, force, purge) + return yunohost.dns.domain_dns_push(domain, dry_run, force, purge) From c5a835c3918cd1c81960e42fd1c155f820c3c66a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 17 Sep 2021 04:55:14 +0200 Subject: [PATCH 3051/3170] autodns: misc fixes/enh --- data/other/config_domain.toml | 12 ++++++------ data/other/registrar_list.toml | 2 ++ src/yunohost/dns.py | 13 +++++++++++-- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/data/other/config_domain.toml b/data/other/config_domain.toml index af23b5e04..095e561a1 100644 --- a/data/other/config_domain.toml +++ b/data/other/config_domain.toml @@ -13,7 +13,7 @@ i18n = "domain_config" [feature] [feature.mail] - services = ['postfix', 'dovecot'] + #services = ['postfix', 'dovecot'] [feature.mail.mail_out] type = "boolean" @@ -23,11 +23,11 @@ i18n = "domain_config" type = "boolean" default = 1 - [feature.mail.backup_mx] - type = "tags" - default = [] - pattern.regexp = '^([^\W_A-Z]+([-]*[^\W_A-Z]+)*\.)+((xn--)?[^\W_]{2,})$' - pattern.error = "pattern_error" + #[feature.mail.backup_mx] + #type = "tags" + #default = [] + #pattern.regexp = '^([^\W_A-Z]+([-]*[^\W_A-Z]+)*\.)+((xn--)?[^\W_]{2,})$' + #pattern.error = "pattern_error" [feature.xmpp] diff --git a/data/other/registrar_list.toml b/data/other/registrar_list.toml index 406066dc9..afb213aa1 100644 --- a/data/other/registrar_list.toml +++ b/data/other/registrar_list.toml @@ -230,6 +230,8 @@ type = "string" choices.rpc = "RPC" choices.rest = "REST" + default = "rest" + visible = "false" [gehirn] [gehirn.auth_token] diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index 1e2037ce5..07e5297d5 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -26,7 +26,6 @@ import os import re import time -import hashlib from difflib import SequenceMatcher from collections import OrderedDict @@ -506,6 +505,15 @@ def _get_registrar_config_section(domain): "ask": f"YunoHost automatically detected that this domain is handled by the registrar **{registrar}**. If you want, YunoHost will automatically configure this DNS zone, if you provide it with the appropriate API credentials. You can also manually configure your DNS records following the documentation as https://yunohost.org/dns.", # FIXME: i18n "value": registrar }) + + TESTED_REGISTRARS = ["ovh", "gandi"] + if registrar not in TESTED_REGISTRARS: + registrar_infos["experimental_disclaimer"] = OrderedDict({ + "type": "alert", + "style": "danger", + "ask": f"So far, the interface with **{registrar}**'s API has not been properly tested and reviewed by the YunoHost's community. Support is **very experimental** - be careful!", # FIXME: i18n + }) + # TODO : add a help tip with the link to the registar's API doc (c.f. Lexicon's README) registrar_list = read_toml(DOMAIN_REGISTRAR_LIST_PATH) registrar_credentials = registrar_list[registrar] @@ -572,6 +580,7 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge= # See https://github.com/AnalogJ/lexicon/issues/282 and https://github.com/AnalogJ/lexicon/pull/371 # They say it's trivial to implement it! # And yet, it is still not done/merged + # Update by Aleks: it works - at least with Gandi ?! #wanted_records = [record for record in wanted_records if record["type"] != "CAA"] if purge: @@ -756,7 +765,7 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge= # If --force ain't used, we won't delete/update records not managed by yunohost if not force: for action in ["delete", "update"]: - changes[action] = [r for r in changes[action] if not r["managed_by_yunohost"]] + changes[action] = [r for r in changes[action] if r["managed_by_yunohost"]] def progress(info=""): progress.nb += 1 From dbff4316d3b00e09e526f938e4d0e5563c9ebfee Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 17 Sep 2021 05:12:22 +0200 Subject: [PATCH 3052/3170] config: fix rendering of core-defined strings during config set --- src/yunohost/utils/config.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 3102f28f2..02e657a73 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -354,6 +354,11 @@ class ConfigPanel: def _ask(self): logger.debug("Ask unanswered question and prevalidate data") + if "i18n" in self.config: + for panel, section, option in self._iterate(): + if "ask" not in option: + option["ask"] = m18n.n(self.config["i18n"] + "_" + option["id"]) + def display_header(message): """CLI panel/section header display""" if Moulinette.interface.type == "cli" and self.filter_key.count(".") < 2: From b15004135abbebcf96ad76b833dc5adaf0db5481 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 17 Sep 2021 05:17:50 +0200 Subject: [PATCH 3053/3170] domain config: Moar i18n --- locales/en.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/locales/en.json b/locales/en.json index 4578075b4..30ec91f63 100644 --- a/locales/en.json +++ b/locales/en.json @@ -319,6 +319,9 @@ "domain_uninstall_app_first": "Those applications are still installed on your domain:\n{apps}\n\nPlease uninstall them using 'yunohost app remove the_app_id' or move them to another domain using 'yunohost app change-url the_app_id' before proceeding to domain removal", "domain_registrar_is_not_configured": "The registrar is not yet configured for domain {domain}.", "domain_dns_push_not_applicable": "The DNS push feature is not applicable to domain {domain}", + "domain_config_mail_in": "Incoming emails", + "domain_config_mail_out": "Outgoing emails", + "domain_config_xmpp": "XMPP", "domain_config_auth_token": "Authentication token", "domain_config_api_protocol": "API protocol", "domain_config_auth_entrypoint": "API entry point", From a75d9aff34e426cb791af5b4c0ce1aea7c0c2514 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 17 Sep 2021 05:22:37 +0200 Subject: [PATCH 3054/3170] Moaaar i18n --- locales/en.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/locales/en.json b/locales/en.json index 30ec91f63..af099b42f 100644 --- a/locales/en.json +++ b/locales/en.json @@ -323,6 +323,8 @@ "domain_config_mail_out": "Outgoing emails", "domain_config_xmpp": "XMPP", "domain_config_auth_token": "Authentication token", + "domain_config_auth_key": "Authentication key", + "domain_config_auth_secret": "Authentication secret", "domain_config_api_protocol": "API protocol", "domain_config_auth_entrypoint": "API entry point", "domain_config_auth_application_key": "Application key", From a631261cfff840604c01fa11c0ba2b32d5152f23 Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Fri, 17 Sep 2021 03:33:35 +0000 Subject: [PATCH 3055/3170] [CI] Format code --- src/yunohost/app.py | 2 +- src/yunohost/log.py | 11 ++++++++--- src/yunohost/utils/config.py | 16 +++++++++++----- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 3ba7fd5e4..3145078cc 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1779,7 +1779,7 @@ ynh_app_config_run $1 "app_id": app_id, "app": self.app, "app_instance_nb": str(app_instance_nb), - "final_path": settings.get("final_path", "") + "final_path": settings.get("final_path", ""), } ) diff --git a/src/yunohost/log.py b/src/yunohost/log.py index 3f25d7a7d..f40470063 100644 --- a/src/yunohost/log.py +++ b/src/yunohost/log.py @@ -75,7 +75,7 @@ def log_list(limit=None, with_details=False, with_suboperations=False): # If we displaying only parent, we are still gonna load up to limit * 5 logs # because many of them are suboperations which are not gonna be kept # Yet we still want to obtain ~limit number of logs - logs = logs[:limit * 5] + logs = logs[: limit * 5] for log in logs: @@ -186,12 +186,17 @@ def log_show( r"DEBUG - \+ exit (1|0)$", ] filters = [re.compile(f) for f in filters] - return [line for line in lines if not any(f.search(line.strip()) for f in filters)] + return [ + line + for line in lines + if not any(f.search(line.strip()) for f in filters) + ] + else: + def _filter(lines): return lines - # Normalize log/metadata paths and filenames abs_path = path log_path = None diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 6b823452b..27681e4d3 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -478,7 +478,7 @@ class Question(object): self.value = Moulinette.prompt( message=text, is_password=self.hide_user_input_in_prompt, - confirm=False, # We doesn't want to confirm this kind of password like in webadmin + confirm=False, # We doesn't want to confirm this kind of password like in webadmin prefill=prefill, is_multiline=(self.type == "text"), ) @@ -705,11 +705,17 @@ class PasswordQuestion(Question): def _format_text_for_user_input_in_cli(self): need_column = self.current_value or self.optional - text_for_user_input_in_cli = super()._format_text_for_user_input_in_cli(need_column) + text_for_user_input_in_cli = super()._format_text_for_user_input_in_cli( + need_column + ) if self.current_value: - text_for_user_input_in_cli += "\n - " + m18n.n("app_argument_password_help_keep") + text_for_user_input_in_cli += "\n - " + m18n.n( + "app_argument_password_help_keep" + ) if self.optional: - text_for_user_input_in_cli += "\n - " + m18n.n("app_argument_password_help_optional") + text_for_user_input_in_cli += "\n - " + m18n.n( + "app_argument_password_help_optional" + ) return text_for_user_input_in_cli @@ -832,7 +838,7 @@ class UserQuestion(Question): raise YunohostValidationError( "app_argument_invalid", name=self.name, - error="You should create a YunoHost user first." + error="You should create a YunoHost user first.", ) if self.default is None: From 12bc94f76e096202ed15d12015a69054e7095409 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 17 Sep 2021 12:55:23 +0200 Subject: [PATCH 3056/3170] config/question: broadcast data to redact to all OperationLogger instances --- src/yunohost/app.py | 2 -- src/yunohost/tests/test_questions.py | 49 +++++----------------------- src/yunohost/utils/config.py | 12 +++---- 3 files changed, 12 insertions(+), 51 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 3145078cc..70e55ceb0 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1723,8 +1723,6 @@ def app_config_set( config_ = AppConfigPanel(app) - Question.operation_logger = operation_logger - return config_.set(key, value, args, args_file, operation_logger=operation_logger) diff --git a/src/yunohost/tests/test_questions.py b/src/yunohost/tests/test_questions.py index 67b50769b..91372dffa 100644 --- a/src/yunohost/tests/test_questions.py +++ b/src/yunohost/tests/test_questions.py @@ -348,9 +348,7 @@ def test_question_password(): ] answers = {"some_password": "some_value"} expected_result = OrderedDict({"some_password": ("some_value", "password")}) - Question.operation_logger = MagicMock() - with patch.object(Question.operation_logger, "data_to_redact", create=True): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert parse_args_in_yunohost_format(answers, questions) == expected_result def test_question_password_no_input(): @@ -375,13 +373,9 @@ def test_question_password_input(): } ] answers = {} - Question.operation_logger = {"data_to_redact": []} expected_result = OrderedDict({"some_password": ("some_value", "password")}) - Question.operation_logger = MagicMock() - with patch.object( - Question.operation_logger, "data_to_redact", create=True - ), patch.object(Moulinette, "prompt", return_value="some_value"), patch.object( + with patch.object(Moulinette, "prompt", return_value="some_value"), patch.object( os, "isatty", return_value=True ): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -397,10 +391,7 @@ def test_question_password_input_no_ask(): answers = {} expected_result = OrderedDict({"some_password": ("some_value", "password")}) - Question.operation_logger = MagicMock() - with patch.object( - Question.operation_logger, "data_to_redact", create=True - ), patch.object(Moulinette, "prompt", return_value="some_value"), patch.object( + with patch.object(Moulinette, "prompt", return_value="some_value"), patch.object( os, "isatty", return_value=True ): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -417,20 +408,14 @@ def test_question_password_no_input_optional(): answers = {} expected_result = OrderedDict({"some_password": ("", "password")}) - Question.operation_logger = MagicMock() - with patch.object( - Question.operation_logger, "data_to_redact", create=True - ), patch.object(os, "isatty", return_value=False): + with patch.object(os, "isatty", return_value=False): assert parse_args_in_yunohost_format(answers, questions) == expected_result questions = [ {"name": "some_password", "type": "password", "optional": True, "default": ""} ] - Question.operation_logger = MagicMock() - with patch.object( - Question.operation_logger, "data_to_redact", create=True - ), patch.object(os, "isatty", return_value=False): + with patch.object(os, "isatty", return_value=False): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -446,10 +431,7 @@ def test_question_password_optional_with_input(): answers = {} expected_result = OrderedDict({"some_password": ("some_value", "password")}) - Question.operation_logger = MagicMock() - with patch.object( - Question.operation_logger, "data_to_redact", create=True - ), patch.object(Moulinette, "prompt", return_value="some_value"), patch.object( + with patch.object(Moulinette, "prompt", return_value="some_value"), patch.object( os, "isatty", return_value=True ): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -467,10 +449,7 @@ def test_question_password_optional_with_empty_input(): answers = {} expected_result = OrderedDict({"some_password": ("", "password")}) - Question.operation_logger = MagicMock() - with patch.object( - Question.operation_logger, "data_to_redact", create=True - ), patch.object(Moulinette, "prompt", return_value=""), patch.object( + with patch.object(Moulinette, "prompt", return_value=""), patch.object( os, "isatty", return_value=True ): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -487,10 +466,7 @@ def test_question_password_optional_with_input_without_ask(): answers = {} expected_result = OrderedDict({"some_password": ("some_value", "password")}) - Question.operation_logger = MagicMock() - with patch.object( - Question.operation_logger, "data_to_redact", create=True - ), patch.object(Moulinette, "prompt", return_value="some_value"), patch.object( + with patch.object(Moulinette, "prompt", return_value="some_value"), patch.object( os, "isatty", return_value=True ): assert parse_args_in_yunohost_format(answers, questions) == expected_result @@ -540,10 +516,7 @@ def test_question_password_input_test_ask(): ] answers = {} - Question.operation_logger = MagicMock() with patch.object( - Question.operation_logger, "data_to_redact", create=True - ), patch.object( Moulinette, "prompt", return_value="some_value" ) as prompt, patch.object( os, "isatty", return_value=True @@ -572,10 +545,7 @@ def test_question_password_input_test_ask_with_example(): ] answers = {} - Question.operation_logger = MagicMock() with patch.object( - Question.operation_logger, "data_to_redact", create=True - ), patch.object( Moulinette, "prompt", return_value="some_value" ) as prompt, patch.object( os, "isatty", return_value=True @@ -599,10 +569,7 @@ def test_question_password_input_test_ask_with_help(): ] answers = {} - Question.operation_logger = MagicMock() with patch.object( - Question.operation_logger, "data_to_redact", create=True - ), patch.object( Moulinette, "prompt", return_value="some_value" ) as prompt, patch.object( os, "isatty", return_value=True diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 27681e4d3..447540e13 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -39,6 +39,7 @@ from moulinette.utils.filesystem import ( from yunohost.utils.i18n import _value_for_locale from yunohost.utils.error import YunohostError, YunohostValidationError +from yunohost.log import OperationLogger logger = getActionLogger("yunohost.config") CONFIG_PANEL_VERSION_SUPPORTED = 1.0 @@ -441,7 +442,6 @@ class ConfigPanel: class Question(object): hide_user_input_in_prompt = False - operation_logger = None pattern = None def __init__(self, question, user_answers): @@ -575,13 +575,9 @@ class Question(object): for data in data_to_redact if urllib.parse.quote(data) != data ] - if self.operation_logger: - self.operation_logger.data_to_redact.extend(data_to_redact) - elif data_to_redact: - raise YunohostError( - f"Can't redact {self.name} because no operation logger available in the context", - raw_msg=True, - ) + + for operation_logger in OperationLogger._instances: + operation_logger.data_to_redact.extend(data_to_redact) return self.value From d4d576c492ff9c89f5f0163bce2961c7c5efa417 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 17 Sep 2021 14:36:46 +0200 Subject: [PATCH 3057/3170] autodns: return relative name in dry run output --- src/yunohost/dns.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index 07e5297d5..20ee8bbf5 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -723,11 +723,14 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge= for record in current: changes["delete"].append(record) - def human_readable_record(action, record): - name = record["name"] + def relative_name(name): name = name.strip(".") name = name.replace('.' + base_dns_zone, "") name = name.replace(base_dns_zone, "@") + return name + + def human_readable_record(action, record): + name = relative_name(record["name"]) name = name[:20] t = record["type"] @@ -753,6 +756,9 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge= if dry_run: if Moulinette.interface.type == "api": + for records in changes.values(): + for record in records: + record["name"] = relative_name(record["name"]) return changes else: out = {"delete": [], "create": [], "update": [], "unchanged": []} From 4503816a88ca2ba1f6ac76dd8cd823e63de748ac Mon Sep 17 00:00:00 2001 From: Kay0u Date: Fri, 17 Sep 2021 14:39:14 +0200 Subject: [PATCH 3058/3170] Adding an anchor on each helper --- doc/helper_doc_template.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/doc/helper_doc_template.md b/doc/helper_doc_template.md index cf88e10ac..d41c0b6e9 100644 --- a/doc/helper_doc_template.md +++ b/doc/helper_doc_template.md @@ -10,11 +10,10 @@ routes: Doc auto-generated by [this script](https://github.com/YunoHost/yunohost/blob/{{ current_commit }}/doc/generate_helper_doc.py) on {{data.date}} (YunoHost version {{data.version}}) {% for category, helpers in data.helpers %} -### {{ category.upper() }} +## {{ category.upper() }} {% for h in helpers %} -**{{ h.name }}**
+#### {{ h.name }} [details summary="{{ h.brief }}" class="helper-card-subtitle text-muted"] -

**Usage**: `{{ h.usage }}` {%- if h.args %} From c53a6006ce8017c39ea9f2b0f410acf64ef98f7c Mon Sep 17 00:00:00 2001 From: Kay0u Date: Fri, 17 Sep 2021 14:47:12 +0200 Subject: [PATCH 3059/3170] Remove unused import --- src/yunohost/app.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 70e55ceb0..e7fae9e76 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -56,7 +56,6 @@ from yunohost.utils import packages from yunohost.utils.config import ( ConfigPanel, parse_args_in_yunohost_format, - Question, ) from yunohost.utils.i18n import _value_for_locale from yunohost.utils.error import YunohostError, YunohostValidationError From 93b99635b7566fabec4c7d1646daa5fb31889080 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 17 Sep 2021 15:41:57 +0200 Subject: [PATCH 3060/3170] autodns: fix/simplify registrar settings fetching --- src/yunohost/dns.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index 20ee8bbf5..3c73974ee 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -536,20 +536,17 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge= _assert_domain_exists(domain) - settings = domain_config_get(domain, key='dns.registrar') + settings = domain_config_get(domain, key='dns.registrar', export=True) - registrar = settings["dns.registrar.registrar"].get("value") + registrar = settings.get("registrar") if not registrar or registrar in ["None", "yunohost"]: raise YunohostValidationError("domain_dns_push_not_applicable", domain=domain) base_dns_zone = _get_dns_zone_for_domain(domain) - registrar_credentials = { - k.split('.')[-1]: v["value"] - for k, v in settings.items() - if k != "dns.registrar.registar" - } + registrar_credentials = settings + registrar_credentials.pop("registrar") if not all(registrar_credentials.values()): raise YunohostValidationError("domain_registrar_is_not_configured", domain=domain) From c8caabf8f819cd4d443a4a71f2097fefc2c3a0d2 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 17 Sep 2021 16:12:25 +0200 Subject: [PATCH 3061/3170] autodns: pop experimental_disclaimer because it's not an actual credential --- src/yunohost/dns.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index 3c73974ee..341a66ad7 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -547,6 +547,8 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge= registrar_credentials = settings registrar_credentials.pop("registrar") + if "experimental_disclaimer" in registrar_credentials: + registrar_credentials.pop("experimental_disclaimer") if not all(registrar_credentials.values()): raise YunohostValidationError("domain_registrar_is_not_configured", domain=domain) From 4db4338812216a0787c9b5389bdc71756e660af2 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 17 Sep 2021 16:57:51 +0200 Subject: [PATCH 3062/3170] mypy: read_yaml argument should always be a string --- src/yunohost/utils/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 447540e13..e3bbc5299 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -126,7 +126,7 @@ class ConfigPanel: if args_file: # Import YAML / JSON file but keep --args values - self.args = {**read_yaml(args_file), **self.args} + self.args = {**read_yaml(args_file.name), **self.args} if value is not None: self.args = {self.filter_key.split(".")[-1]: value} From d135b97784cb06fc9a4de51ca4324f92c452d245 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 17 Sep 2021 17:10:07 +0200 Subject: [PATCH 3063/3170] Revert "mypy: read_yaml argument should always be a string" This reverts commit 4db4338812216a0787c9b5389bdc71756e660af2. --- src/yunohost/utils/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index e3bbc5299..447540e13 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -126,7 +126,7 @@ class ConfigPanel: if args_file: # Import YAML / JSON file but keep --args values - self.args = {**read_yaml(args_file.name), **self.args} + self.args = {**read_yaml(args_file), **self.args} if value is not None: self.args = {self.filter_key.split(".")[-1]: value} From 29bb26f24687989334289d7d17e8c4a5413af968 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Fri, 17 Sep 2021 17:51:50 +0200 Subject: [PATCH 3064/3170] remove created permission if error --- src/yunohost/permission.py | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 01330ad7f..5161430de 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -457,22 +457,26 @@ def permission_create( "permission_creation_failed", permission=permission, error=e ) - permission_url( - permission, - url=url, - add_url=additional_urls, - auth_header=auth_header, - sync_perm=False, - ) + try: + permission_url( + permission, + url=url, + add_url=additional_urls, + auth_header=auth_header, + sync_perm=False, + ) - new_permission = _update_ldap_group_permission( - permission=permission, - allowed=allowed, - label=label, - show_tile=show_tile, - protected=protected, - sync_perm=sync_perm, - ) + new_permission = _update_ldap_group_permission( + permission=permission, + allowed=allowed, + label=label, + show_tile=show_tile, + protected=protected, + sync_perm=sync_perm, + ) + except: + permission_delete(permission, force=True) + raise logger.debug(m18n.n("permission_created", permission=permission)) return new_permission From ddd522ac54e580d75728c349aaeb766231de9e72 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Fri, 17 Sep 2021 18:24:50 +0200 Subject: [PATCH 3065/3170] add YNH_APP_BASEDIR in the env var --- data/helpers.d/utils | 2 -- src/yunohost/app.py | 10 ++++++++-- src/yunohost/backup.py | 3 +++ 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 34a089eb1..085404f1b 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -1,7 +1,5 @@ #!/bin/bash -YNH_APP_BASEDIR=$(realpath $([[ "$(basename $0)" =~ ^backup|restore$ ]] && echo '../settings' || [[ -n "${YNH_ACTION:-}" ]] && echo '.' || echo '..' )) - # Handle script crashes / failures # # [internal] diff --git a/src/yunohost/app.py b/src/yunohost/app.py index e7fae9e76..a5291ed88 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -456,19 +456,21 @@ def app_change_url(operation_logger, app, domain, path): # TODO: Allow to specify arguments args_odict = _parse_args_from_manifest(manifest, "change_url") + tmp_workdir_for_app = _make_tmp_workdir_for_app(app=app) + # Prepare env. var. to pass to script env_dict = _make_environment_for_app_script(app, args=args_odict) env_dict["YNH_APP_OLD_DOMAIN"] = old_domain env_dict["YNH_APP_OLD_PATH"] = old_path env_dict["YNH_APP_NEW_DOMAIN"] = domain env_dict["YNH_APP_NEW_PATH"] = path + env_dict["YNH_APP_BASEDIR"] = tmp_workdir_for_app if domain != old_domain: operation_logger.related_to.append(("domain", old_domain)) operation_logger.extra.update({"env": env_dict}) operation_logger.start() - tmp_workdir_for_app = _make_tmp_workdir_for_app(app=app) change_url_script = os.path.join(tmp_workdir_for_app, "scripts/change_url") # Execute App change_url script @@ -619,6 +621,7 @@ def app_upgrade(app=[], url=None, file=None, force=False, no_safety_backup=False env_dict["YNH_APP_MANIFEST_VERSION"] = str(app_new_version) env_dict["YNH_APP_CURRENT_VERSION"] = str(app_current_version) env_dict["NO_BACKUP_UPGRADE"] = "1" if no_safety_backup else "0" + env_dict["YNH_APP_BASEDIR"] = extracted_app_folder # We'll check that the app didn't brutally edit some system configuration manually_modified_files_before_install = manually_modified_files() @@ -980,6 +983,7 @@ def app_install( # Prepare env. var. to pass to script env_dict = _make_environment_for_app_script(app_instance_name, args=args_odict) + env_dict["YNH_APP_BASEDIR"] = extracted_app_folder env_dict_for_logging = env_dict.copy() for arg_name, arg_value_and_type in args_odict.items(): @@ -1645,12 +1649,14 @@ def app_action_run(operation_logger, app, action, args=None): ) args_odict = _parse_args_for_action(actions[action], args=args_dict) + tmp_workdir_for_app = _make_tmp_workdir_for_app(app=app) + env_dict = _make_environment_for_app_script( app, args=args_odict, args_prefix="ACTION_" ) env_dict["YNH_ACTION"] = action + env_dict["YNH_APP_BASEDIR"] = tmp_workdir_for_app - tmp_workdir_for_app = _make_tmp_workdir_for_app(app=app) _, action_script = tempfile.mkstemp(dir=tmp_workdir_for_app) with open(action_script, "w") as script: diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 15abc08ef..80f01fd35 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -707,6 +707,7 @@ class BackupManager: # Prepare environment env_dict = self._get_env_var(app) + env_dict["YNH_APP_BASEDIR"] = os.path.join(self.work_dir, "apps", app, "settings") tmp_app_bkp_dir = env_dict["YNH_APP_BACKUP_DIR"] settings_dir = os.path.join(self.work_dir, "apps", app, "settings") @@ -1487,6 +1488,7 @@ class RestoreManager: "YNH_APP_BACKUP_DIR": os.path.join( self.work_dir, "apps", app_instance_name, "backup" ), + "YNH_APP_BASEDIR": os.path.join(self.work_dir, "apps", app_instance_name, "settings"), } ) @@ -1524,6 +1526,7 @@ class RestoreManager: # Setup environment for remove script env_dict_remove = _make_environment_for_app_script(app_instance_name) + env_dict_remove["YNH_APP_BASEDIR"] = os.path.join(self.work_dir, "apps", app_instance_name, "settings") remove_operation_logger = OperationLogger( "remove_on_failed_restore", From aeed9f897b005857216813fd96dfa171934f7b5b Mon Sep 17 00:00:00 2001 From: ljf Date: Fri, 17 Sep 2021 18:32:06 +0200 Subject: [PATCH 3066/3170] [fix] Redact password in app install --- src/yunohost/app.py | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 3ba7fd5e4..569fbdb23 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -895,6 +895,9 @@ def app_install( else: app_instance_name = app_id + # Neede to redact password question + Question.operation_logger = operation_logger + # Retrieve arguments list for install script args_dict = ( {} if not args else dict(urllib.parse.parse_qsl(args, keep_blank_values=True)) @@ -913,19 +916,6 @@ def app_install( # We'll check that the app didn't brutally edit some system configuration manually_modified_files_before_install = manually_modified_files() - # Tell the operation_logger to redact all password-type args - # Also redact the % escaped version of the password that might appear in - # the 'args' section of metadata (relevant for password with non-alphanumeric char) - data_to_redact = [ - value[0] for value in args_odict.values() if value[1] == "password" - ] - data_to_redact += [ - urllib.parse.quote(data) - for data in data_to_redact - if urllib.parse.quote(data) != data - ] - operation_logger.data_to_redact.extend(data_to_redact) - operation_logger.related_to = [ s for s in operation_logger.related_to if s[0] != "app" ] From c1eecd5eb30c299919d13a29e4d50399663b68d0 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Fri, 17 Sep 2021 18:38:00 +0200 Subject: [PATCH 3067/3170] moar YNH_APP_BASEDIR --- src/yunohost/app.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index a5291ed88..de4f1efc6 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1048,6 +1048,7 @@ def app_install( env_dict_remove["YNH_APP_INSTANCE_NAME"] = app_instance_name env_dict_remove["YNH_APP_INSTANCE_NUMBER"] = str(instance_number) env_dict_remove["YNH_APP_MANIFEST_VERSION"] = manifest.get("version", "?") + env_dict_remove["YNH_APP_BASEDIR"] = extracted_app_folder # Execute remove script operation_logger_remove = OperationLogger( @@ -1165,6 +1166,8 @@ def app_remove(operation_logger, app, purge=False): env_dict["YNH_APP_INSTANCE_NUMBER"] = str(app_instance_nb) env_dict["YNH_APP_MANIFEST_VERSION"] = manifest.get("version", "?") env_dict["YNH_APP_PURGE"] = str(purge) + env_dict["YNH_APP_BASEDIR"] = tmp_workdir_for_app + operation_logger.extra.update({"env": env_dict}) operation_logger.flush() @@ -1783,6 +1786,7 @@ ynh_app_config_run $1 "app": self.app, "app_instance_nb": str(app_instance_nb), "final_path": settings.get("final_path", ""), + "YNH_APP_BASEDIR": os.path.join(APPS_SETTING_PATH, self.app), } ) From efdd287bff82c8e250b8406f890768c990b7a94d Mon Sep 17 00:00:00 2001 From: Kay0u Date: Fri, 17 Sep 2021 18:47:47 +0200 Subject: [PATCH 3068/3170] define a default YNH_APP_BASEDIR --- data/helpers.d/utils | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 085404f1b..396374174 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -1,5 +1,11 @@ #!/bin/bash +if [ -z "$YNH_APP_BASEDIR" ]; +then + ynh_print_warn --message="YNH_APP_BASEDIR is not defined, many helpers use it, please define it!" + YNH_APP_BASEDIR=$(realpath ..) +fi + # Handle script crashes / failures # # [internal] From c20ac160ccc468dbf5909c4499b75e4b879d906f Mon Sep 17 00:00:00 2001 From: Kay0u Date: Fri, 17 Sep 2021 18:49:33 +0200 Subject: [PATCH 3069/3170] do not use ynh_print_warn here --- 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 396374174..fd989c682 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -2,7 +2,7 @@ if [ -z "$YNH_APP_BASEDIR" ]; then - ynh_print_warn --message="YNH_APP_BASEDIR is not defined, many helpers use it, please define it!" + echo -e "YNH_APP_BASEDIR is not defined, many helpers use it, please define it!" >&2 YNH_APP_BASEDIR=$(realpath ..) fi From 0d881fe008fbfb62e8c173fa6b306f94f3fbb569 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 17 Sep 2021 19:56:24 +0200 Subject: [PATCH 3070/3170] app.py: No need to define Question.operation_logger anymoar --- src/yunohost/app.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 14ef6e3e7..06185761a 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -894,9 +894,6 @@ def app_install( else: app_instance_name = app_id - # Neede to redact password question - Question.operation_logger = operation_logger - # Retrieve arguments list for install script args_dict = ( {} if not args else dict(urllib.parse.parse_qsl(args, keep_blank_values=True)) From 991eea447c9685142d3c59c38efcb691281552a6 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 17 Sep 2021 22:23:08 +0200 Subject: [PATCH 3071/3170] [fix] YNH_APP_BASEDIR may not exist, set -eu etc --- 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 fd989c682..732c1bc47 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -1,6 +1,6 @@ #!/bin/bash -if [ -z "$YNH_APP_BASEDIR" ]; +if [ -z "${YNH_APP_BASEDIR:-}" ]; then echo -e "YNH_APP_BASEDIR is not defined, many helpers use it, please define it!" >&2 YNH_APP_BASEDIR=$(realpath ..) From ef91b67d670638c4c60edb398b665956375806af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= <46165813+ericgaspar@users.noreply.github.com> Date: Fri, 17 Sep 2021 23:31:40 +0200 Subject: [PATCH 3072/3170] Fix time format Set more meaningful time format --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 5bb408f1d..49c7eb09c 100644 --- a/locales/en.json +++ b/locales/en.json @@ -150,7 +150,7 @@ "config_validate_color": "Should be a valid RGB hexadecimal color", "config_validate_date": "Should be a valid date like in the format YYYY-MM-DD", "config_validate_email": "Should be a valid email", - "config_validate_time": "Should be a valid time like XX:YY", + "config_validate_time": "Should be a valid time like HH:MM", "config_validate_url": "Should be a valid web URL", "config_version_not_supported": "Config panel versions '{version}' are not supported.", "confirm_app_install_danger": "DANGER! This app is known to be still experimental (if not explicitly not working)! You should probably NOT install it unless you know what you are doing. NO SUPPORT will be provided if this app doesn't work or breaks your system... If you are willing to take that risk anyway, type '{answers}'", From 68f2eea0ae13d2fb8ba59af648e0437932e9771e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 18 Sep 2021 13:58:41 +0200 Subject: [PATCH 3073/3170] autodns: proper error management for authentication error --- locales/en.json | 1 + src/yunohost/dns.py | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index af099b42f..07a46b8df 100644 --- a/locales/en.json +++ b/locales/en.json @@ -319,6 +319,7 @@ "domain_uninstall_app_first": "Those applications are still installed on your domain:\n{apps}\n\nPlease uninstall them using 'yunohost app remove the_app_id' or move them to another domain using 'yunohost app change-url the_app_id' before proceeding to domain removal", "domain_registrar_is_not_configured": "The registrar is not yet configured for domain {domain}.", "domain_dns_push_not_applicable": "The DNS push feature is not applicable to domain {domain}", + "domain_dns_push_failed_to_authenticate": "Failed to authenticate on registrar's API. Most probably the credentials are incorrect? (Error: {error})", "domain_config_mail_in": "Incoming emails", "domain_config_mail_out": "Outgoing emails", "domain_config_xmpp": "XMPP", diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index 341a66ad7..8bc48a5ac 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -605,7 +605,11 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge= .with_dict(dict_object={"action": "list", "type": "all"}) ) client = LexiconClient(query) - client.provider.authenticate() + try: + client.provider.authenticate() + except Exception as e: + raise YunohostValidationError("domain_dns_push_failed_to_authenticate", error=str(e)) + try: current_records = client.provider.list_records() except Exception as e: From 5812c8f1ae77d63f2ab9f96051d1ce007fae33d1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 18 Sep 2021 22:47:06 +0200 Subject: [PATCH 3074/3170] autodns: Disable ttl setting for now because meh --- data/other/config_domain.toml | 12 ++++++------ src/yunohost/dns.py | 3 ++- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/data/other/config_domain.toml b/data/other/config_domain.toml index 095e561a1..10ff5ce5c 100644 --- a/data/other/config_domain.toml +++ b/data/other/config_domain.toml @@ -42,9 +42,9 @@ i18n = "domain_config" # This part is automatically generated in DomainConfigPanel - [dns.advanced] - - [dns.advanced.ttl] - type = "number" - min = 0 - default = 3600 +# [dns.advanced] +# +# [dns.advanced.ttl] +# type = "number" +# min = 0 +# default = 3600 diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index 8bc48a5ac..a006a19a2 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -176,7 +176,8 @@ def _build_dns_conf(base_domain): basename = domain.replace(f"{base_dns_zone}", "").rstrip(".") or "@" suffix = f".{basename}" if basename != "@" else "" - ttl = settings["ttl"] + #ttl = settings["ttl"] + ttl = "3600" ########################### # Basic ipv4/ipv6 records # From fa31d49bf9493c4b70fb5e46e6a5bd95bb6f487f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 18 Sep 2021 23:10:17 +0200 Subject: [PATCH 3075/3170] autodns: Improve handling of the subdomain case --- locales/en.json | 3 ++- src/yunohost/dns.py | 13 +++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/locales/en.json b/locales/en.json index 07a46b8df..c4413380a 100644 --- a/locales/en.json +++ b/locales/en.json @@ -318,7 +318,8 @@ "domain_remove_confirm_apps_removal": "Removing this domain will remove those applications:\n{apps}\n\nAre you sure you want to do that? [{answers}]", "domain_uninstall_app_first": "Those applications are still installed on your domain:\n{apps}\n\nPlease uninstall them using 'yunohost app remove the_app_id' or move them to another domain using 'yunohost app change-url the_app_id' before proceeding to domain removal", "domain_registrar_is_not_configured": "The registrar is not yet configured for domain {domain}.", - "domain_dns_push_not_applicable": "The DNS push feature is not applicable to domain {domain}", + "domain_dns_push_not_applicable": "The DNS push feature is not applicable to domain {domain}. You should manually configure your DNS records following the documentation at https://yunohost.org/dns_config.", + "domain_dns_push_managed_in_parent_domain": "The DNS push feature is managed in the parent domain {parent_domain}.", "domain_dns_push_failed_to_authenticate": "Failed to authenticate on registrar's API. Most probably the credentials are incorrect? (Error: {error})", "domain_config_mail_in": "Incoming emails", "domain_config_mail_out": "Outgoing emails", diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index a006a19a2..98933ba75 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -470,11 +470,17 @@ def _get_registrar_config_section(domain): # If parent domain exists in yunohost parent_domain = domain.split(".", 1)[1] if parent_domain in domain_list()["domains"]: + + if Moulinette.interface.type = "api": + parent_domain_link = "[{parent_domain}](#/domains/{parent_domain}/config)" + else: + parent_domain_link = parent_domain + registrar_infos["registrar"] = OrderedDict({ "type": "alert", "style": "info", - "ask": f"This domain is a subdomain of {parent_domain}. DNS registrar configuration should be managed in {parent_domain}'s configuration panel.", # FIXME: i18n - "value": None + "ask": f"This domain is a subdomain of {parent_domain_link}. DNS registrar configuration should be managed in {parent_domain}'s configuration panel.", # FIXME: i18n + "value": "parent_domain" }) return OrderedDict(registrar_infos) @@ -544,6 +550,9 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge= if not registrar or registrar in ["None", "yunohost"]: raise YunohostValidationError("domain_dns_push_not_applicable", domain=domain) + if registrar == "parent_domain": + raise YunohostValidationError("domain_dns_push_managed_in_parent_domain", domain=domain, parent_domain=registrar) + base_dns_zone = _get_dns_zone_for_domain(domain) registrar_credentials = settings From 2c997d43e1ecc43ccf805b6e9bcb078e827817de Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 18 Sep 2021 23:15:36 +0200 Subject: [PATCH 3076/3170] autodns: typo --- src/yunohost/dns.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index 98933ba75..f4d89820a 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -834,7 +834,7 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge= record["name"] = "@." + record["name"] if record["type"] in ["MX", "SRV"]: logger.warning(f"Pushing {record['type']} records is not properly supported by Lexicon/Godaddy.") - results["warning"].append(f"Pushing {record['type']} records is not properly supported by Lexicon/Godaddy.") + results["warnings"].append(f"Pushing {record['type']} records is not properly supported by Lexicon/Godaddy.") continue record["action"] = action From b3e9cf19db1a0bfa8c0b21ed17ba3e86fac06ef2 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 18 Sep 2021 23:18:03 +0200 Subject: [PATCH 3077/3170] =?UTF-8?q?autodns:=20typo=C2=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/yunohost/dns.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index f4d89820a..686a016c0 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -471,7 +471,7 @@ def _get_registrar_config_section(domain): parent_domain = domain.split(".", 1)[1] if parent_domain in domain_list()["domains"]: - if Moulinette.interface.type = "api": + if Moulinette.interface.type == "api": parent_domain_link = "[{parent_domain}](#/domains/{parent_domain}/config)" else: parent_domain_link = parent_domain From 002d25452231b629d43d41a9e96ba09d84bd69c7 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 18 Sep 2021 23:28:34 +0200 Subject: [PATCH 3078/3170] =?UTF-8?q?autodns:=20typo=C2=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/yunohost/dns.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index 686a016c0..ab97841a7 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -472,7 +472,7 @@ def _get_registrar_config_section(domain): if parent_domain in domain_list()["domains"]: if Moulinette.interface.type == "api": - parent_domain_link = "[{parent_domain}](#/domains/{parent_domain}/config)" + parent_domain_link = f"[{parent_domain}](#/domains/{parent_domain}/config)" else: parent_domain_link = parent_domain From c762cd98581c935ecfc3033f3c6b39ca00707722 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 18 Sep 2021 23:35:37 +0200 Subject: [PATCH 3079/3170] =?UTF-8?q?autodns:=20typo=E2=81=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/yunohost/dns.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index ab97841a7..dfec7ceb9 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -551,7 +551,8 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge= raise YunohostValidationError("domain_dns_push_not_applicable", domain=domain) if registrar == "parent_domain": - raise YunohostValidationError("domain_dns_push_managed_in_parent_domain", domain=domain, parent_domain=registrar) + parent_domain = domain.split(".", 1)[1] + raise YunohostValidationError("domain_dns_push_managed_in_parent_domain", domain=domain, parent_domain=parent_domain) base_dns_zone = _get_dns_zone_for_domain(domain) From b5e20cadf4df595d1217668fdd6d38b817ad51e2 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 19 Sep 2021 00:32:42 +0200 Subject: [PATCH 3080/3170] autodns: Various fixes, improvement for edge case handling --- locales/en.json | 6 +++--- src/yunohost/dns.py | 46 ++++++++++++++++++++++++++++++--------------- 2 files changed, 34 insertions(+), 18 deletions(-) diff --git a/locales/en.json b/locales/en.json index c4413380a..fb755c08b 100644 --- a/locales/en.json +++ b/locales/en.json @@ -318,9 +318,9 @@ "domain_remove_confirm_apps_removal": "Removing this domain will remove those applications:\n{apps}\n\nAre you sure you want to do that? [{answers}]", "domain_uninstall_app_first": "Those applications are still installed on your domain:\n{apps}\n\nPlease uninstall them using 'yunohost app remove the_app_id' or move them to another domain using 'yunohost app change-url the_app_id' before proceeding to domain removal", "domain_registrar_is_not_configured": "The registrar is not yet configured for domain {domain}.", - "domain_dns_push_not_applicable": "The DNS push feature is not applicable to domain {domain}. You should manually configure your DNS records following the documentation at https://yunohost.org/dns_config.", - "domain_dns_push_managed_in_parent_domain": "The DNS push feature is managed in the parent domain {parent_domain}.", - "domain_dns_push_failed_to_authenticate": "Failed to authenticate on registrar's API. Most probably the credentials are incorrect? (Error: {error})", + "domain_dns_push_not_applicable": "The automatic DNS configuration feature is not applicable to domain {domain}. You should manually configure your DNS records following the documentation at https://yunohost.org/dns_config.", + "domain_dns_push_managed_in_parent_domain": "The automatic DNS configuration feature is managed in the parent domain {parent_domain}.", + "domain_dns_push_failed_to_authenticate": "Failed to authenticate on registrar's API for domain '{domain}'. Most probably the credentials are incorrect? (Error: {error})", "domain_config_mail_in": "Incoming emails", "domain_config_mail_out": "Outgoing emails", "domain_config_xmpp": "XMPP", diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index dfec7ceb9..daeae0c7f 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -490,7 +490,7 @@ def _get_registrar_config_section(domain): registrar_infos["registrar"] = OrderedDict({ "type": "alert", "style": "success", - "ask": "This domain is a nohost.me / nohost.st / ynh.fr and its DNS configuration is therefore automatically handled by Yunohost.", # FIXME: i18n + "ask": "This domain is a nohost.me / nohost.st / ynh.fr and its DNS configuration is therefore automatically handled by Yunohost without any further configuration.", # FIXME: i18n "value": "yunohost" }) return OrderedDict(registrar_infos) @@ -532,6 +532,20 @@ def _get_registrar_config_section(domain): return OrderedDict(registrar_infos) +def _get_registar_settings(domain): + + _assert_domain_exists(domain) + + settings = domain_config_get(domain, key='dns.registrar', export=True) + + registrar = settings.pop("registrar") + + if "experimental_disclaimer" in settings: + settings.pop("experimental_disclaimer") + + return registrar, settings + + @is_unit_operation() def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge=False): """ @@ -541,29 +555,31 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge= from lexicon.client import Client as LexiconClient from lexicon.config import ConfigResolver as LexiconConfigResolver + registrar, registrar_credentials = _get_registar_settings(domain) + _assert_domain_exists(domain) - settings = domain_config_get(domain, key='dns.registrar', export=True) - - registrar = settings.get("registrar") - - if not registrar or registrar in ["None", "yunohost"]: + if not registrar or registrar == "None": # yes it's None as a string raise YunohostValidationError("domain_dns_push_not_applicable", domain=domain) + # FIXME: in the future, properly unify this with yunohost dyndns update + if registrar == "yunohost": + logger.info("This domain is a nohost.me / nohost.st / ynh.fr and its DNS configuration is therefore already automatically handled by Yunohost without any further configuration.") # FIXME: i18n + return {} + if registrar == "parent_domain": parent_domain = domain.split(".", 1)[1] - raise YunohostValidationError("domain_dns_push_managed_in_parent_domain", domain=domain, parent_domain=parent_domain) - - base_dns_zone = _get_dns_zone_for_domain(domain) - - registrar_credentials = settings - registrar_credentials.pop("registrar") - if "experimental_disclaimer" in registrar_credentials: - registrar_credentials.pop("experimental_disclaimer") + registar, registrar_credentials = _get_registar_settings(parent_domain) + if any(registrar_credentials.values()): + raise YunohostValidationError("domain_dns_push_managed_in_parent_domain", domain=domain, parent_domain=parent_domain) + else: + raise YunohostValidationError("domain_registrar_is_not_configured", domain=domain) if not all(registrar_credentials.values()): raise YunohostValidationError("domain_registrar_is_not_configured", domain=domain) + base_dns_zone = _get_dns_zone_for_domain(domain) + # Convert the generated conf into a format that matches what we'll fetch using the API # Makes it easier to compare "wanted records" with "current records on remote" wanted_records = [] @@ -619,7 +635,7 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge= try: client.provider.authenticate() except Exception as e: - raise YunohostValidationError("domain_dns_push_failed_to_authenticate", error=str(e)) + raise YunohostValidationError("domain_dns_push_failed_to_authenticate", domain=domain, error=str(e)) try: current_records = client.provider.list_records() From 8faad01263aad74d497c8967d5346f9955c5f39a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 19 Sep 2021 01:23:33 +0200 Subject: [PATCH 3081/3170] Misc fixes --- src/yunohost/dns.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index daeae0c7f..c2eb463f7 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -177,7 +177,7 @@ def _build_dns_conf(base_domain): suffix = f".{basename}" if basename != "@" else "" #ttl = settings["ttl"] - ttl = "3600" + ttl = 3600 ########################### # Basic ipv4/ipv6 records # @@ -573,7 +573,7 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge= if any(registrar_credentials.values()): raise YunohostValidationError("domain_dns_push_managed_in_parent_domain", domain=domain, parent_domain=parent_domain) else: - raise YunohostValidationError("domain_registrar_is_not_configured", domain=domain) + raise YunohostValidationError("domain_registrar_is_not_configured", domain=parent_domain) if not all(registrar_credentials.values()): raise YunohostValidationError("domain_registrar_is_not_configured", domain=domain) From f90854809c43eb1e428f0a09714332d634333e30 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 19 Sep 2021 01:23:51 +0200 Subject: [PATCH 3082/3170] domain config: add disclaimer that enabling/disabling features only impact dns configuration for now --- data/other/config_domain.toml | 5 +++++ locales/en.json | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/data/other/config_domain.toml b/data/other/config_domain.toml index 10ff5ce5c..93551458b 100644 --- a/data/other/config_domain.toml +++ b/data/other/config_domain.toml @@ -15,6 +15,11 @@ i18n = "domain_config" [feature.mail] #services = ['postfix', 'dovecot'] + [feature.mail.features_disclaimer] + type = "alert" + style = "warning" + icon = "warning" + [feature.mail.mail_out] type = "boolean" default = 1 diff --git a/locales/en.json b/locales/en.json index fb755c08b..ab8b32127 100644 --- a/locales/en.json +++ b/locales/en.json @@ -321,9 +321,10 @@ "domain_dns_push_not_applicable": "The automatic DNS configuration feature is not applicable to domain {domain}. You should manually configure your DNS records following the documentation at https://yunohost.org/dns_config.", "domain_dns_push_managed_in_parent_domain": "The automatic DNS configuration feature is managed in the parent domain {parent_domain}.", "domain_dns_push_failed_to_authenticate": "Failed to authenticate on registrar's API for domain '{domain}'. Most probably the credentials are incorrect? (Error: {error})", + "domain_config_features_disclaimer": "So far, enabling/disabling mail or XMPP features only impact the recommended and automatic DNS configuration, not system configurations!", "domain_config_mail_in": "Incoming emails", "domain_config_mail_out": "Outgoing emails", - "domain_config_xmpp": "XMPP", + "domain_config_xmpp": "Instant messaging (XMPP)", "domain_config_auth_token": "Authentication token", "domain_config_auth_key": "Authentication key", "domain_config_auth_secret": "Authentication secret", From 76bd3a6f83798412672ee9b68bcb404c078b1da6 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 19 Sep 2021 01:46:48 +0200 Subject: [PATCH 3083/3170] autodns: Add link to registrar api doc --- src/yunohost/dns.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index c2eb463f7..7dba8c0d8 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -471,6 +471,7 @@ def _get_registrar_config_section(domain): parent_domain = domain.split(".", 1)[1] if parent_domain in domain_list()["domains"]: + # Dirty hack to have a link on the webadmin if Moulinette.interface.type == "api": parent_domain_link = f"[{parent_domain}](#/domains/{parent_domain}/config)" else: @@ -509,7 +510,7 @@ def _get_registrar_config_section(domain): registrar_infos["registrar"] = OrderedDict({ "type": "alert", "style": "info", - "ask": f"YunoHost automatically detected that this domain is handled by the registrar **{registrar}**. If you want, YunoHost will automatically configure this DNS zone, if you provide it with the appropriate API credentials. You can also manually configure your DNS records following the documentation as https://yunohost.org/dns.", # FIXME: i18n + "ask": f"YunoHost automatically detected that this domain is handled by the registrar **{registrar}**. If you want, YunoHost will automatically configure this DNS zone, if you provide it with the appropriate API credentials. You read documentation on how to get your API credentials on this page: https://yunohost.org/registar_api_{registrar}. (You can also manually configure your DNS records following the documentation as https://yunohost.org/dns )", # FIXME: i18n "value": registrar }) From 52b3cb5622993f5bad7e93a7b9d2aaac5060a28f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 19 Sep 2021 01:56:53 +0200 Subject: [PATCH 3084/3170] app config panel: Add supports_config_panel in app_info for webadmin --- src/yunohost/app.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 871e3dc00..2d9ecce22 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -235,6 +235,9 @@ def app_info(app, full=False): ret["supports_multi_instance"] = is_true( local_manifest.get("multi_instance", False) ) + ret["supports_config_panel"] = os.path.exists( + os.path.join(setting_path, "config_panel.toml") + ) ret["permissions"] = permissions ret["label"] = permissions.get(app + ".main", {}).get("label") From e3ce03ac85c2fc9dba853b9c421063a99fe201f2 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 19 Sep 2021 16:18:07 +0200 Subject: [PATCH 3085/3170] autodns: i18n --- locales/en.json | 12 ++++++++++++ src/yunohost/dns.py | 40 +++++++++++++++++++++------------------- 2 files changed, 33 insertions(+), 19 deletions(-) diff --git a/locales/en.json b/locales/en.json index ab8b32127..e49228db3 100644 --- a/locales/en.json +++ b/locales/en.json @@ -320,7 +320,19 @@ "domain_registrar_is_not_configured": "The registrar is not yet configured for domain {domain}.", "domain_dns_push_not_applicable": "The automatic DNS configuration feature is not applicable to domain {domain}. You should manually configure your DNS records following the documentation at https://yunohost.org/dns_config.", "domain_dns_push_managed_in_parent_domain": "The automatic DNS configuration feature is managed in the parent domain {parent_domain}.", + "domain_dns_registrar_managed_in_parent_domain": "This domain is a subdomain of {parent_domain_link}. DNS registrar configuration should be managed in {parent_domain}'s configuration panel.", + "domain_dns_registrar_yunohost": "This domain is a nohost.me / nohost.st / ynh.fr and its DNS configuration is therefore automatically handled by Yunohost without any further configuration. (see the 'yunohost dyndns update' command)", + "domain_dns_registrar_not_supported": "YunoHost could not automatically detect the registrar handling this domain. You should manually configure your DNS records following the documentation at https://yunohost.org/dns.", + "domain_dns_registrar_supported": "YunoHost automatically detected that this domain is handled by the registrar **{registrar}**. If you want, YunoHost will automatically configure this DNS zone, if you provide it with the appropriate API credentials. You can find documentation on how to obtain your API credentials on this page: https://yunohost.org/registar_api_{registrar}. (You can also manually configure your DNS records following the documentation at https://yunohost.org/dns )", + "domain_dns_registrar_experimental": "So far, the interface with **{registrar}**'s API has not been properly tested and reviewed by the YunoHost community. Support is **very experimental** - be careful!", "domain_dns_push_failed_to_authenticate": "Failed to authenticate on registrar's API for domain '{domain}'. Most probably the credentials are incorrect? (Error: {error})", + "domain_dns_push_failed_to_list": "Failed to list current records using the registrar's API: {error}", + "domain_dns_push_already_up_to_date": "Records already up to date, nothing to do.", + "domain_dns_pushing": "Pushing DNS records...", + "domain_dns_push_record_failed": "Failed to {action} record {type}/{name} : {error}", + "domain_dns_push_success": "DNS records updated!", + "domain_dns_push_failed": "Updating the DNS records failed miserably.", + "domain_dns_push_partial_failure": "DNS records partially updated: some warnings/errors were reported.", "domain_config_features_disclaimer": "So far, enabling/disabling mail or XMPP features only impact the recommended and automatic DNS configuration, not system configurations!", "domain_config_mail_in": "Incoming emails", "domain_config_mail_out": "Outgoing emails", diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index 7dba8c0d8..8d44804f3 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -172,8 +172,7 @@ def _build_dns_conf(base_domain): # sub.domain.tld # sub.domain.tld # @ # # # foo.sub.domain.tld # sub.domain.tld # foo # .foo # - # FIXME: shouldn't the basename just be based on the dns_zone setting of this domain ? - basename = domain.replace(f"{base_dns_zone}", "").rstrip(".") or "@" + basename = domain.replace(base_dns_zone, "").rstrip(".") or "@" suffix = f".{basename}" if basename != "@" else "" #ttl = settings["ttl"] @@ -480,7 +479,7 @@ def _get_registrar_config_section(domain): registrar_infos["registrar"] = OrderedDict({ "type": "alert", "style": "info", - "ask": f"This domain is a subdomain of {parent_domain_link}. DNS registrar configuration should be managed in {parent_domain}'s configuration panel.", # FIXME: i18n + "ask": m18n.n("domain_dns_registrar_managed_in_parent_domain", parent_domain=domain, parent_domain_link=parent_domain_link), "value": "parent_domain" }) return OrderedDict(registrar_infos) @@ -491,7 +490,7 @@ def _get_registrar_config_section(domain): registrar_infos["registrar"] = OrderedDict({ "type": "alert", "style": "success", - "ask": "This domain is a nohost.me / nohost.st / ynh.fr and its DNS configuration is therefore automatically handled by Yunohost without any further configuration.", # FIXME: i18n + "ask": m18n.n("domain_dns_registrar_yunohost"), "value": "yunohost" }) return OrderedDict(registrar_infos) @@ -502,7 +501,7 @@ def _get_registrar_config_section(domain): registrar_infos["registrar"] = OrderedDict({ "type": "alert", "style": "warning", - "ask": "YunoHost could not automatically detect the registrar handling this domain. You should manually configure your DNS records following the documentation at https://yunohost.org/dns.", # FIXME : i18n + "ask": m18n.n("domain_dns_registrar_not_supported"), "value": None }) else: @@ -510,7 +509,7 @@ def _get_registrar_config_section(domain): registrar_infos["registrar"] = OrderedDict({ "type": "alert", "style": "info", - "ask": f"YunoHost automatically detected that this domain is handled by the registrar **{registrar}**. If you want, YunoHost will automatically configure this DNS zone, if you provide it with the appropriate API credentials. You read documentation on how to get your API credentials on this page: https://yunohost.org/registar_api_{registrar}. (You can also manually configure your DNS records following the documentation as https://yunohost.org/dns )", # FIXME: i18n + "ask": m18n.n("domain_dns_registrar_supported", registrar=registrar), "value": registrar }) @@ -519,7 +518,7 @@ def _get_registrar_config_section(domain): registrar_infos["experimental_disclaimer"] = OrderedDict({ "type": "alert", "style": "danger", - "ask": f"So far, the interface with **{registrar}**'s API has not been properly tested and reviewed by the YunoHost's community. Support is **very experimental** - be careful!", # FIXME: i18n + "ask": m18n.n("domain_dns_registrar_experimental", registrar=registrar), }) # TODO : add a help tip with the link to the registar's API doc (c.f. Lexicon's README) @@ -565,7 +564,7 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge= # FIXME: in the future, properly unify this with yunohost dyndns update if registrar == "yunohost": - logger.info("This domain is a nohost.me / nohost.st / ynh.fr and its DNS configuration is therefore already automatically handled by Yunohost without any further configuration.") # FIXME: i18n + logger.info(m18n.n("domain_dns_registrar_yunohost")) return {} if registrar == "parent_domain": @@ -641,7 +640,7 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge= try: current_records = client.provider.list_records() except Exception as e: - raise YunohostError("Failed to list current records using the registrar's API: %s" % str(e), raw_msg=True) # FIXME: i18n + raise YunohostValidationError("domain_dns_push_failed_to_list", error=str(e)) managed_dns_records_hashes = _get_managed_dns_records_hashes(domain) @@ -697,7 +696,6 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge= # # Step 1 : compute a first "diff" where we remove records which are the same on both sides - # NB / FIXME? : in all this we ignore the TTL value for now... # wanted_contents = [r["content"] for r in records["wanted"]] current_contents = [r["content"] for r in records["current"]] @@ -821,7 +819,7 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge= progress.total = len(changes["delete"] + changes["create"] + changes["update"]) if progress.total == 0: - logger.success("Records already up to date, nothing to do.") # FIXME : i18n + logger.success(m18n.n("domain_dns_push_already_up_to_date")) return {} # @@ -829,7 +827,7 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge= # operation_logger.start() - logger.info("Pushing DNS records...") + logger.info(m18n.n("domain_dns_puhsing")) new_managed_dns_records_hashes = [_hash_dns_record(r) for r in changes["unchanged"]] results = {"warnings": [], "errors": []} @@ -839,7 +837,7 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge= for record in changes[action]: relative_name = record['name'].replace(base_dns_zone, '').rstrip('.') or '@' - progress(f"{action} {record['type']:^5} / {relative_name}") # FIXME: i18n + progress(f"{action} {record['type']:^5} / {relative_name}") # FIXME: i18n but meh # Apparently Lexicon yields us some 'id' during fetch # But wants 'identifier' during push ... @@ -865,28 +863,32 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge= try: result = LexiconClient(query).execute() except Exception as e: - logger.error(f"Failed to {action} record {record['type']}/{record['name']} : {e}") # i18n? - results["errors"].append(f"Failed to {action} record {record['type']}/{record['name']} : {e}") + msg = m18n.n("domain_dns_push_record_failed", action=action, type=record['type'], name=record['name'], error=str(e)) + logger.error(msg) + results["errors"].append(msg) else: if result: new_managed_dns_records_hashes.append(_hash_dns_record(record)) else: - results["errors"].append(f"Failed to {action} record {record['type']}/{record['name']} : unknown error?") + msg = m18n.n("domain_dns_push_record_failed", action=action, type=record['type'], name=record['name'], error="unkonwn error?") + logger.error(msg) + results["errors"].append(msg) _set_managed_dns_records_hashes(domain, new_managed_dns_records_hashes) # Everything succeeded if len(results["errors"]) == 0: - logger.success("DNS records updated!") # FIXME: i18n + logger.success(m18n.("domain_dns_push_success")) return {} # Everything failed elif len(results["errors"]) + len(results["warnings"]) == progress.total: - logger.error("Updating the DNS records failed miserably") # FIXME: i18n + logger.error(m18n.("domain_dns_push_failed")) else: - logger.warning("DNS records partially updated: some warnings/errors were reported.") # FIXME: i18n + logger.warning(m18n.("domain_dns_push_partial_failure")) return results + def _get_managed_dns_records_hashes(domain: str) -> list: return _get_domain_settings(domain).get("managed_dns_records_hashes", []) From d161d0744a7f3e45b7ec922d57977f3b22f7e128 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 19 Sep 2021 16:50:57 +0200 Subject: [PATCH 3086/3170] dns: don't have empty sections in recommended conf --- src/yunohost/dns.py | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index 8d44804f3..112478251 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -61,24 +61,28 @@ def domain_dns_suggest(domain): result = "" - result += "; Basic ipv4/ipv6 records" - for record in dns_conf["basic"]: - result += "\n{name} {ttl} IN {type} {value}".format(**record) + if dns_conf["basic"]: + result += "; Basic ipv4/ipv6 records" + for record in dns_conf["basic"]: + result += "\n{name} {ttl} IN {type} {value}".format(**record) - result += "\n\n" - result += "; XMPP" - for record in dns_conf["xmpp"]: - result += "\n{name} {ttl} IN {type} {value}".format(**record) + if dns_conf["mail"]: + result += "\n\n" + result += "; Mail" + for record in dns_conf["mail"]: + result += "\n{name} {ttl} IN {type} {value}".format(**record) + result += "\n\n" - result += "\n\n" - result += "; Mail" - for record in dns_conf["mail"]: - result += "\n{name} {ttl} IN {type} {value}".format(**record) - result += "\n\n" + if dns_conf["xmpp"]: + result += "\n\n" + result += "; XMPP" + for record in dns_conf["xmpp"]: + result += "\n{name} {ttl} IN {type} {value}".format(**record) - result += "; Extra" - for record in dns_conf["extra"]: - result += "\n{name} {ttl} IN {type} {value}".format(**record) + if dns_conf["extra"]: + result += "; Extra" + for record in dns_conf["extra"]: + result += "\n{name} {ttl} IN {type} {value}".format(**record) for name, record_list in dns_conf.items(): if name not in ("basic", "xmpp", "mail", "extra") and record_list: From 1543984a29c12d85aab837650eb3c690b9017796 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 19 Sep 2021 16:51:17 +0200 Subject: [PATCH 3087/3170] Fix i18n tests --- src/yunohost/dns.py | 8 ++++---- src/yunohost/tests/test_domains.py | 5 ----- tests/test_i18n_keys.py | 22 ++++++++++++++++++++++ 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index 112478251..d26cc3ca4 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -831,7 +831,7 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge= # operation_logger.start() - logger.info(m18n.n("domain_dns_puhsing")) + logger.info(m18n.n("domain_dns_pushing")) new_managed_dns_records_hashes = [_hash_dns_record(r) for r in changes["unchanged"]] results = {"warnings": [], "errors": []} @@ -882,13 +882,13 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge= # Everything succeeded if len(results["errors"]) == 0: - logger.success(m18n.("domain_dns_push_success")) + logger.success(m18n.n("domain_dns_push_success")) return {} # Everything failed elif len(results["errors"]) + len(results["warnings"]) == progress.total: - logger.error(m18n.("domain_dns_push_failed")) + logger.error(m18n.n("domain_dns_push_failed")) else: - logger.warning(m18n.("domain_dns_push_partial_failure")) + logger.warning(m18n.n("domain_dns_push_partial_failure")) return results diff --git a/src/yunohost/tests/test_domains.py b/src/yunohost/tests/test_domains.py index b964d2ab6..bdb1b8a96 100644 --- a/src/yunohost/tests/test_domains.py +++ b/src/yunohost/tests/test_domains.py @@ -103,14 +103,12 @@ def test_change_main_domain(): def test_domain_config_get_default(): assert domain_config_get(TEST_DOMAINS[0], "feature.xmpp.xmpp") == 1 assert domain_config_get(TEST_DOMAINS[1], "feature.xmpp.xmpp") == 0 - assert domain_config_get(TEST_DOMAINS[1], "dns.advanced.ttl") == 3600 def test_domain_config_get_export(): assert domain_config_get(TEST_DOMAINS[0], export=True)["xmpp"] == 1 assert domain_config_get(TEST_DOMAINS[1], export=True)["xmpp"] == 0 - assert domain_config_get(TEST_DOMAINS[1], export=True)["ttl"] == 3600 def test_domain_config_set(): @@ -118,9 +116,6 @@ def test_domain_config_set(): domain_config_set(TEST_DOMAINS[1], "feature.xmpp.xmpp", "yes") assert domain_config_get(TEST_DOMAINS[1], "feature.xmpp.xmpp") == 1 - domain_config_set(TEST_DOMAINS[1], "dns.advanced.ttl", 10) - assert domain_config_get(TEST_DOMAINS[1], "dns.advanced.ttl") == 10 - def test_domain_configs_unknown(): with pytest.raises(YunohostValidationError): diff --git a/tests/test_i18n_keys.py b/tests/test_i18n_keys.py index 33c1f7b65..e14ac88a8 100644 --- a/tests/test_i18n_keys.py +++ b/tests/test_i18n_keys.py @@ -6,6 +6,7 @@ import glob import json import yaml import subprocess +import toml ignore = [ "password_too_simple_", @@ -165,6 +166,24 @@ def find_expected_string_keys(): for check in checks: yield "diagnosis_mail_%s" % check + registrars = toml.load(open('data/other/registrar_list.toml')) + supported_registrars = ["ovh", "gandi", "godaddy"] + for registrar in supported_registrars: + for key in registrars[registrar].keys(): + yield f"domain_config_{key}" + + domain_config = toml.load(open('data/other/config_domain.toml')) + for panel in domain_config.values(): + if not isinstance(panel, dict): + continue + for section in panel.values(): + if not isinstance(section, dict): + continue + for key, values in section.items(): + if not isinstance(values, dict): + continue + yield f"domain_config_{key}" + ############################################################################### # Load en locale json keys # @@ -204,3 +223,6 @@ def test_unused_i18n_keys(): raise Exception( "Those i18n keys appears unused:\n" " - " + "\n - ".join(unused_keys) ) + +test_undefined_i18n_keys() +test_unused_i18n_keys() From ae03be3bad73afa47e371ae02bf30d6ee1ee7a43 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 19 Sep 2021 17:10:14 +0200 Subject: [PATCH 3088/3170] i18n tests: simplify hardcoded stuff --- data/hooks/diagnosis/12-dnsrecords.py | 7 ++++++ data/hooks/diagnosis/21-web.py | 4 +++ data/hooks/diagnosis/24-mail.py | 14 +++++++---- src/yunohost/app.py | 4 +++ src/yunohost/utils/password.py | 5 ++++ tests/test_i18n_keys.py | 36 --------------------------- 6 files changed, 29 insertions(+), 41 deletions(-) diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py index 854f348f5..36180781f 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -199,6 +199,7 @@ class DNSRecordsDiagnoser(Diagnoser): status_ns, _ = dig(domain, "NS", resolvers="force_external") status_a, _ = dig(domain, "A", resolvers="force_external") if "ok" not in [status_ns, status_a]: + # i18n: diagnosis_domain_not_found_details details["not_found"].append( ( "diagnosis_domain_%s_details" % (expire_date), @@ -233,6 +234,12 @@ class DNSRecordsDiagnoser(Diagnoser): # Allow to ignore specifically a single domain if len(details[alert_type]) == 1: meta["domain"] = details[alert_type][0][1]["domain"] + + # i18n: diagnosis_domain_expiration_not_found + # i18n: diagnosis_domain_expiration_error + # i18n: diagnosis_domain_expiration_warning + # i18n: diagnosis_domain_expiration_success + # i18n: diagnosis_domain_expiration_not_found_details yield dict( meta=meta, data={}, diff --git a/data/hooks/diagnosis/21-web.py b/data/hooks/diagnosis/21-web.py index 40a6c26b4..2072937e5 100644 --- a/data/hooks/diagnosis/21-web.py +++ b/data/hooks/diagnosis/21-web.py @@ -121,6 +121,10 @@ class WebDiagnoser(Diagnoser): for domain in domains: + # i18n: diagnosis_http_bad_status_code + # i18n: diagnosis_http_connection_error + # i18n: diagnosis_http_timeout + # If both IPv4 and IPv6 (if applicable) are good if all( results[ipversion][domain]["status"] == "ok" for ipversion in ipversions diff --git a/data/hooks/diagnosis/24-mail.py b/data/hooks/diagnosis/24-mail.py index 50b8dc12e..266678557 100644 --- a/data/hooks/diagnosis/24-mail.py +++ b/data/hooks/diagnosis/24-mail.py @@ -35,11 +35,11 @@ class MailDiagnoser(Diagnoser): # TODO check that the recent mail logs are not filled with thousand of email sending (unusual number of mail sent) # TODO check for unusual failed sending attempt being refused in the logs ? checks = [ - "check_outgoing_port_25", - "check_ehlo", - "check_fcrdns", - "check_blacklist", - "check_queue", + "check_outgoing_port_25", # i18n: diagnosis_mail_outgoing_port_25_ok + "check_ehlo", # i18n: diagnosis_mail_ehlo_ok + "check_fcrdns", # i18n: diagnosis_mail_fcrdns_ok + "check_blacklist", # i18n: diagnosis_mail_blacklist_ok + "check_queue", # i18n: diagnosis_mail_queue_ok ] for check in checks: self.logger_debug("Running " + check) @@ -102,6 +102,10 @@ class MailDiagnoser(Diagnoser): continue if r["status"] != "ok": + # i18n: diagnosis_mail_ehlo_bad_answer + # i18n: diagnosis_mail_ehlo_bad_answer_details + # i18n: diagnosis_mail_ehlo_unreachable + # i18n: diagnosis_mail_ehlo_unreachable_details summary = r["status"].replace("error_smtp_", "diagnosis_mail_ehlo_") yield dict( meta={"test": "mail_ehlo", "ipversion": ipversion}, diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 2d9ecce22..78428595d 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -820,6 +820,10 @@ def app_install( if confirm is None or force or Moulinette.interface.type == "api": return + # i18n: confirm_app_install_warning + # i18n: confirm_app_install_danger + # i18n: confirm_app_install_thirdparty + if confirm in ["danger", "thirdparty"]: answer = Moulinette.prompt( m18n.n("confirm_app_install_" + confirm, answers="Yes, I understand"), diff --git a/src/yunohost/utils/password.py b/src/yunohost/utils/password.py index 9e693d8cd..188850183 100644 --- a/src/yunohost/utils/password.py +++ b/src/yunohost/utils/password.py @@ -111,8 +111,13 @@ class PasswordValidator(object): listed = password in SMALL_PWD_LIST or self.is_in_most_used_list(password) strength_level = self.strength_level(password) if listed: + # i18n: password_listed return ("error", "password_listed") if strength_level < self.validation_strength: + # i18n: password_too_simple_1 + # i18n: password_too_simple_2 + # i18n: password_too_simple_3 + # i18n: password_too_simple_4 return ("error", "password_too_simple_%s" % self.validation_strength) return ("success", "") diff --git a/tests/test_i18n_keys.py b/tests/test_i18n_keys.py index e14ac88a8..119ba85f1 100644 --- a/tests/test_i18n_keys.py +++ b/tests/test_i18n_keys.py @@ -8,14 +8,6 @@ import yaml import subprocess import toml -ignore = [ - "password_too_simple_", - "password_listed", - "backup_method_", - "backup_applying_method_", - "confirm_app_install_", -] - ############################################################################### # Find used keys in python code # ############################################################################### @@ -138,34 +130,6 @@ def find_expected_string_keys(): yield "backup_applying_method_%s" % method yield "backup_method_%s_finished" % method - for level in ["danger", "thirdparty", "warning"]: - yield "confirm_app_install_%s" % level - - for errortype in ["not_found", "error", "warning", "success", "not_found_details"]: - yield "diagnosis_domain_expiration_%s" % errortype - yield "diagnosis_domain_not_found_details" - - for errortype in ["bad_status_code", "connection_error", "timeout"]: - yield "diagnosis_http_%s" % errortype - - yield "password_listed" - for i in [1, 2, 3, 4]: - yield "password_too_simple_%s" % i - - checks = [ - "outgoing_port_25_ok", - "ehlo_ok", - "fcrdns_ok", - "blacklist_ok", - "queue_ok", - "ehlo_bad_answer", - "ehlo_unreachable", - "ehlo_bad_answer_details", - "ehlo_unreachable_details", - ] - for check in checks: - yield "diagnosis_mail_%s" % check - registrars = toml.load(open('data/other/registrar_list.toml')) supported_registrars = ["ovh", "gandi", "godaddy"] for registrar in supported_registrars: From 04487ed6bf3a0d7e4119e75fa093071b49906bbd Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 19 Sep 2021 17:30:52 +0200 Subject: [PATCH 3089/3170] dns: Don't include subdomains stuff in dyndns update, because this probably ain't gonna work --- src/yunohost/dns.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index d26cc3ca4..4ac62fb46 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -160,7 +160,14 @@ def _build_dns_conf(base_domain): ipv4 = get_public_ip() ipv6 = get_public_ip(6) - subdomains = _list_subdomains_of(base_domain) + # If this is a ynh_dyndns_domain, we're not gonna include all the subdomains in the conf + # Because dynette only accept a specific list of name/type + # And the wildcard */A already covers the bulk of use cases + if any(base_domain.endswith("." + ynh_dyndns_domain) for ynh_dyndns_domain in YNH_DYNDNS_DOMAINS): + subdomains = [] + else: + subdomains = _list_subdomains_of(base_domain) + domains_settings = {domain: domain_config_get(domain, export=True) for domain in [base_domain] + subdomains} From 7fd76a688479ace4d9742439efc46cbb7b1b5b16 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 19 Sep 2021 17:32:35 +0200 Subject: [PATCH 3090/3170] dns: Reintroduce include_empty_AAAA_if_no_ipv6 option, needed for diagnosis --- src/yunohost/dns.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index 4ac62fb46..a4fcbc3fa 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -110,7 +110,7 @@ def _list_subdomains_of(parent_domain): return out -def _build_dns_conf(base_domain): +def _build_dns_conf(base_domain, include_empty_AAAA_if_no_ipv6=False): """ Internal function that will returns a data structure containing the needed information to generate/adapt the dns configuration @@ -197,9 +197,8 @@ def _build_dns_conf(base_domain): if ipv6: basic.append([basename, ttl, "AAAA", ipv6]) - # TODO - # elif include_empty_AAAA_if_no_ipv6: - # basic.append(["@", ttl, "AAAA", None]) + elif include_empty_aaaa_if_no_ipv6: + basic.append(["@", ttl, "AAAA", None]) ######### # Email # @@ -251,9 +250,8 @@ def _build_dns_conf(base_domain): if ipv6: extra.append([f"*{suffix}", ttl, "AAAA", ipv6]) - # TODO - # elif include_empty_AAAA_if_no_ipv6: - # extra.append(["*", ttl, "AAAA", None]) + elif include_empty_AAAA_if_no_ipv6: + extra.append(["*", ttl, "AAAA", None]) extra.append([basename, ttl, "CAA", '128 issue "letsencrypt.org"']) From 69c756918f908b61c05c61b4b71cf5912aa931d2 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 19 Sep 2021 17:37:44 +0200 Subject: [PATCH 3091/3170] dyndns: Don't try to push anything if no ipv4/ipv6 --- src/yunohost/dyndns.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index 9cb6dc567..4297a3408 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -228,10 +228,6 @@ def dyndns_update( from yunohost.dns import _build_dns_conf - # Get old ipv4/v6 - - old_ipv4, old_ipv6 = (None, None) # (default values) - # If domain is not given, try to guess it from keys available... if domain is None: (domain, key) = _guess_current_dyndns_domain(dyn_host) @@ -311,6 +307,10 @@ def dyndns_update( logger.debug("Old IPv4/v6 are (%s, %s)" % (old_ipv4, old_ipv6)) logger.debug("Requested IPv4/v6 are (%s, %s)" % (ipv4, ipv6)) + if ipv4 is None and ipv6 is None: + logger.debug("No ipv4 nor ipv6 ?! Sounds like the server is not connected to the internet, or the ip.yunohost.org infrastructure is down somehow") + return + # no need to update if (not force and not dry_run) and (old_ipv4 == ipv4 and old_ipv6 == ipv6): logger.info("No updated needed.") From c12f9b64ea21a0c5b524981d72e685230a6b3e65 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 19 Sep 2021 17:40:21 +0200 Subject: [PATCH 3092/3170] Typo --- src/yunohost/dns.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index a4fcbc3fa..d7ed3d724 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -197,7 +197,7 @@ def _build_dns_conf(base_domain, include_empty_AAAA_if_no_ipv6=False): if ipv6: basic.append([basename, ttl, "AAAA", ipv6]) - elif include_empty_aaaa_if_no_ipv6: + elif include_empty_AAAA_if_no_ipv6: basic.append(["@", ttl, "AAAA", None]) ######### From 499f06f5f6cf40dfffc2d32b42878011d2096587 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 19 Sep 2021 17:41:19 +0200 Subject: [PATCH 3093/3170] autodns: Godaddy doesn't supports CAA records --- src/yunohost/dns.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index d7ed3d724..02ed31b21 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -857,7 +857,7 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge= if registrar == "godaddy": if record["name"] == base_dns_zone: record["name"] = "@." + record["name"] - if record["type"] in ["MX", "SRV"]: + if record["type"] in ["MX", "SRV", "CAA"]: logger.warning(f"Pushing {record['type']} records is not properly supported by Lexicon/Godaddy.") results["warnings"].append(f"Pushing {record['type']} records is not properly supported by Lexicon/Godaddy.") continue From 065ebec8210c23142c611e837edd08a10ce1b536 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 19 Sep 2021 17:41:52 +0200 Subject: [PATCH 3094/3170] autodns: Minor fix for error handling --- src/yunohost/dns.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index 02ed31b21..cf1e2d636 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -886,7 +886,7 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge= _set_managed_dns_records_hashes(domain, new_managed_dns_records_hashes) # Everything succeeded - if len(results["errors"]) == 0: + if len(results["errors"]) + len(results["warnings"]) == 0: logger.success(m18n.n("domain_dns_push_success")) return {} # Everything failed From 8f8b6eae7cafd6dfc2885b4938026e0d153edccf Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 19 Sep 2021 18:02:45 +0200 Subject: [PATCH 3095/3170] domains: Make sure domain setting folder exists with appropriate perms --- data/hooks/conf_regen/01-yunohost | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/data/hooks/conf_regen/01-yunohost b/data/hooks/conf_regen/01-yunohost index e9c0fc4aa..445faa5a4 100755 --- a/data/hooks/conf_regen/01-yunohost +++ b/data/hooks/conf_regen/01-yunohost @@ -35,6 +35,10 @@ do_init_regen() { mkdir -p /home/yunohost.app chmod 755 /home/yunohost.app + # Domain settings + mkdir -p /etc/yunohost/domains + chmod 700 /etc/yunohost/domains + # Backup folders mkdir -p /home/yunohost.backup/archives chmod 750 /home/yunohost.backup/archives @@ -179,6 +183,8 @@ do_post_regen() { [ ! -e "/home/$USER" ] || setfacl -m g:all_users:--- /home/$USER done + # Domain settings + mkdir -p /etc/yunohost/domains # Misc configuration / state files chown root:root $(ls /etc/yunohost/{*.yml,*.yaml,*.json,mysql,psql} 2>/dev/null) @@ -187,6 +193,7 @@ do_post_regen() { # 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) + [[ ! -e /etc/yunohost/domains ]] || (chown root /etc/yunohost/domains && chmod 700 /etc/yunohost/domains) # Create ssh.app and sftp.app groups if they don't exist yet grep -q '^ssh.app:' /etc/group || groupadd ssh.app From 2422a25f55a3211979032bb77024d2b6f2cbf6be Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 19 Sep 2021 18:04:32 +0200 Subject: [PATCH 3096/3170] domains: make sure to backup/restore domain settings --- data/hooks/backup/20-conf_ynh_settings | 1 + data/hooks/restore/20-conf_ynh_settings | 1 + 2 files changed, 2 insertions(+) diff --git a/data/hooks/backup/20-conf_ynh_settings b/data/hooks/backup/20-conf_ynh_settings index 77148c4d9..9b56f1579 100644 --- a/data/hooks/backup/20-conf_ynh_settings +++ b/data/hooks/backup/20-conf_ynh_settings @@ -12,6 +12,7 @@ backup_dir="${1}/conf/ynh" # Backup the configuration ynh_backup "/etc/yunohost/firewall.yml" "${backup_dir}/firewall.yml" ynh_backup "/etc/yunohost/current_host" "${backup_dir}/current_host" +ynh_backup "/etc/yunohost/domains" "${backup_dir}/domains" [ ! -e "/etc/yunohost/settings.json" ] || ynh_backup "/etc/yunohost/settings.json" "${backup_dir}/settings.json" [ ! -d "/etc/yunohost/dyndns" ] || ynh_backup "/etc/yunohost/dyndns" "${backup_dir}/dyndns" [ ! -d "/etc/dkim" ] || ynh_backup "/etc/dkim" "${backup_dir}/dkim" diff --git a/data/hooks/restore/20-conf_ynh_settings b/data/hooks/restore/20-conf_ynh_settings index 4de29a4aa..4c4c6ed5e 100644 --- a/data/hooks/restore/20-conf_ynh_settings +++ b/data/hooks/restore/20-conf_ynh_settings @@ -2,6 +2,7 @@ backup_dir="$1/conf/ynh" cp -a "${backup_dir}/current_host" /etc/yunohost/current_host cp -a "${backup_dir}/firewall.yml" /etc/yunohost/firewall.yml +cp -a "${backup_dir}/domains" /etc/yunohost/domains [ ! -e "${backup_dir}/settings.json" ] || cp -a "${backup_dir}/settings.json" "/etc/yunohost/settings.json" [ ! -d "${backup_dir}/dyndns" ] || cp -raT "${backup_dir}/dyndns" "/etc/yunohost/dyndns" [ ! -d "${backup_dir}/dkim" ] || cp -raT "${backup_dir}/dkim" "/etc/dkim" From cdabfc12cc47bce14856b61b25d095d3ca07b3a7 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 19 Sep 2021 19:34:45 +0200 Subject: [PATCH 3097/3170] dns: Repair diagnosis ugh --- data/hooks/diagnosis/12-dnsrecords.py | 34 +++++++++++++++------------ src/yunohost/dns.py | 29 ++++++++++++++--------- src/yunohost/domain.py | 4 ++++ 3 files changed, 41 insertions(+), 26 deletions(-) diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py index 36180781f..e3cbe7078 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -11,7 +11,7 @@ from moulinette.utils.process import check_output from yunohost.utils.dns import dig, YNH_DYNDNS_DOMAINS from yunohost.diagnosis import Diagnoser from yunohost.domain import domain_list, _get_maindomain -from yunohost.dns import _build_dns_conf +from yunohost.dns import _build_dns_conf, _get_dns_zone_for_domain SPECIAL_USE_TLDS = ["local", "localhost", "onion", "test"] @@ -26,17 +26,15 @@ class DNSRecordsDiagnoser(Diagnoser): main_domain = _get_maindomain() - all_domains = domain_list()["domains"] + all_domains = domain_list(exclude_subdomains=True)["domains"] for domain in all_domains: self.logger_debug("Diagnosing DNS conf for %s" % domain) - is_subdomain = domain.split(".", 1)[1] in all_domains is_specialusedomain = any( domain.endswith("." + tld) for tld in SPECIAL_USE_TLDS ) for report in self.check_domain( domain, domain == main_domain, - is_subdomain=is_subdomain, is_specialusedomain=is_specialusedomain, ): yield report @@ -55,16 +53,16 @@ class DNSRecordsDiagnoser(Diagnoser): for report in self.check_expiration_date(domains_from_registrar): yield report - def check_domain(self, domain, is_main_domain, is_subdomain, is_specialusedomain): + def check_domain(self, domain, is_main_domain, is_specialusedomain): + + base_dns_zone = _get_dns_zone_for_domain(domain) + basename = domain.replace(base_dns_zone, "").rstrip(".") or "@" expected_configuration = _build_dns_conf( domain, include_empty_AAAA_if_no_ipv6=True ) categories = ["basic", "mail", "xmpp", "extra"] - # For subdomains, we only diagnosis A and AAAA records - if is_subdomain: - categories = ["basic"] if is_specialusedomain: categories = [] @@ -82,8 +80,16 @@ class DNSRecordsDiagnoser(Diagnoser): results = {} for r in records: + id_ = r["type"] + ":" + r["name"] - r["current"] = self.get_current_record(domain, r["name"], r["type"]) + fqdn = r["name"] + "." + base_dns_zone if r["name"] != "@" else domain + + # Ugly hack to not check mail records for subdomains stuff, otherwise will end up in a shitstorm of errors for people with many subdomains... + # Should find a cleaner solution in the suggested conf... + if r["type"] in ["MX", "TXT"] and fqdn not in [domain, f'mail._domainkey.{domain}', f'_dmarc.{domain}']: + continue + + r["current"] = self.get_current_record(fqdn, r["type"]) if r["value"] == "@": r["value"] = domain + "." @@ -106,7 +112,7 @@ class DNSRecordsDiagnoser(Diagnoser): # A bad or missing A record is critical ... # And so is a wrong AAAA record # (However, a missing AAAA record is acceptable) - if results["A:@"] != "OK" or results["AAAA:@"] == "WRONG": + if results[f"A:{basename}"] != "OK" or results[f"AAAA:{basename}"] == "WRONG": return True return False @@ -139,10 +145,9 @@ class DNSRecordsDiagnoser(Diagnoser): yield output - def get_current_record(self, domain, name, type_): + def get_current_record(self, fqdn, type_): - query = "%s.%s" % (name, domain) if name != "@" else domain - success, answers = dig(query, type_, resolvers="force_external") + success, answers = dig(fqdn, type_, resolvers="force_external") if success != "ok": return None @@ -170,7 +175,7 @@ class DNSRecordsDiagnoser(Diagnoser): ) # For SPF, ignore parts starting by ip4: or ip6: - if r["name"] == "@": + if 'v=spf1' in r["value"]: current = { part for part in current @@ -189,7 +194,6 @@ class DNSRecordsDiagnoser(Diagnoser): """ Alert if expiration date of a domain is soon """ - details = {"not_found": [], "error": [], "warning": [], "success": []} for domain in domains: diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index cf1e2d636..745578806 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -198,7 +198,7 @@ def _build_dns_conf(base_domain, include_empty_AAAA_if_no_ipv6=False): if ipv6: basic.append([basename, ttl, "AAAA", ipv6]) elif include_empty_AAAA_if_no_ipv6: - basic.append(["@", ttl, "AAAA", None]) + basic.append([basename, ttl, "AAAA", None]) ######### # Email # @@ -245,15 +245,17 @@ def _build_dns_conf(base_domain, include_empty_AAAA_if_no_ipv6=False): # Extra # ######### - if ipv4: - extra.append([f"*{suffix}", ttl, "A", ipv4]) + # Only recommend wildcard and CAA for the top level + if domain == base_domain: + if ipv4: + extra.append([f"*{suffix}", ttl, "A", ipv4]) - if ipv6: - extra.append([f"*{suffix}", ttl, "AAAA", ipv6]) - elif include_empty_AAAA_if_no_ipv6: - extra.append(["*", ttl, "AAAA", None]) + if ipv6: + extra.append([f"*{suffix}", ttl, "AAAA", ipv6]) + elif include_empty_AAAA_if_no_ipv6: + extra.append([f"*{suffix}", ttl, "AAAA", None]) - extra.append([basename, ttl, "CAA", '128 issue "letsencrypt.org"']) + extra.append([basename, ttl, "CAA", '128 issue "letsencrypt.org"']) #################### # Standard records # @@ -463,8 +465,13 @@ def _get_dns_zone_for_domain(domain): write_to_file(cache_file, parent) return parent - logger.warning(f"Could not identify the dns_zone for domain {domain}, returning {parent_list[-1]}") - return parent_list[-1] + if len(parent_list) >= 2: + zone = parent_list[-2] + else: + zone = parent_list[-1] + + logger.warning(f"Could not identify the dns zone for domain {domain}, returning {zone}") + return zone def _get_registrar_config_section(domain): @@ -649,7 +656,7 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge= try: current_records = client.provider.list_records() except Exception as e: - raise YunohostValidationError("domain_dns_push_failed_to_list", error=str(e)) + raise YunohostError("domain_dns_push_failed_to_list", error=str(e)) managed_dns_records_hashes = _get_managed_dns_records_hashes(domain) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index d13900224..a5db7c7ab 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -93,6 +93,10 @@ def domain_list(exclude_subdomains=False): result_list = sorted(result_list, key=cmp_domain) + # Don't cache answer if using exclude_subdomains + if exclude_subdomains: + return {"domains": result_list, "main": _get_maindomain()} + domain_list_cache = {"domains": result_list, "main": _get_maindomain()} return domain_list_cache From 18e2fb14c4c9dc592223855c82dbb7a8402f117b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 19 Sep 2021 19:35:23 +0200 Subject: [PATCH 3098/3170] tests: uhoh forgot to remove some tmp stuff --- tests/test_i18n_keys.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/test_i18n_keys.py b/tests/test_i18n_keys.py index 119ba85f1..90e14848d 100644 --- a/tests/test_i18n_keys.py +++ b/tests/test_i18n_keys.py @@ -187,6 +187,3 @@ def test_unused_i18n_keys(): raise Exception( "Those i18n keys appears unused:\n" " - " + "\n - ".join(unused_keys) ) - -test_undefined_i18n_keys() -test_unused_i18n_keys() From e7844ef09ef9b88a236ce0d4e7e93365d7e4ccf3 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 19 Sep 2021 20:46:32 +0200 Subject: [PATCH 3099/3170] Simplify YNH_APP_BASEDIR definition Co-authored-by: Kayou --- data/helpers.d/utils | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 732c1bc47..061ff324d 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -1,10 +1,6 @@ #!/bin/bash -if [ -z "${YNH_APP_BASEDIR:-}" ]; -then - echo -e "YNH_APP_BASEDIR is not defined, many helpers use it, please define it!" >&2 - YNH_APP_BASEDIR=$(realpath ..) -fi +YNH_APP_BASEDIR=${YNH_APP_BASEDIR:-$(realpath ..)} # Handle script crashes / failures # From b6981c80b8e046af13ab0b5f9fa8b213e65984b1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 19 Sep 2021 20:40:13 +0200 Subject: [PATCH 3100/3170] backup: Manually modified files may not exists... --- data/hooks/backup/50-conf_manually_modified_files | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/hooks/backup/50-conf_manually_modified_files b/data/hooks/backup/50-conf_manually_modified_files index 685fb56a8..2cca11afb 100644 --- a/data/hooks/backup/50-conf_manually_modified_files +++ b/data/hooks/backup/50-conf_manually_modified_files @@ -12,7 +12,7 @@ ynh_backup --src_path="./manually_modified_files_list" for file in $(cat ./manually_modified_files_list) do - ynh_backup --src_path="$file" + [[ -e $file ]] && ynh_backup --src_path="$file" done ynh_backup --src_path="/etc/ssowat/conf.json.persistent" From be8c6f2c35c55ef142fd346ba05b170ce022c5a9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 19 Sep 2021 22:59:15 +0200 Subject: [PATCH 3101/3170] Fix tests --- src/yunohost/backup.py | 3 +++ src/yunohost/tests/test_backuprestore.py | 4 +++- src/yunohost/tests/test_dns.py | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 80f01fd35..7b580e424 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -44,6 +44,7 @@ from moulinette.utils.log import getActionLogger from moulinette.utils.filesystem import read_file, mkdir, write_to_yaml, read_yaml from moulinette.utils.process import check_output +from yunohost.domain import domain_list_cache from yunohost.app import ( app_info, _is_installed, @@ -1284,6 +1285,8 @@ class RestoreManager: else: operation_logger.success() + domain_list_cache = {} + regen_conf() _tools_migrations_run_after_system_restore( diff --git a/src/yunohost/tests/test_backuprestore.py b/src/yunohost/tests/test_backuprestore.py index b24d3442d..6e2c3b514 100644 --- a/src/yunohost/tests/test_backuprestore.py +++ b/src/yunohost/tests/test_backuprestore.py @@ -2,6 +2,7 @@ import pytest import os import shutil import subprocess +from mock import patch from .conftest import message, raiseYunohostError, get_test_apps_dir @@ -77,7 +78,8 @@ def setup_function(function): if "with_permission_app_installed" in markers: assert not app_is_installed("permissions_app") user_create("alice", "Alice", "White", maindomain, "test123Ynh") - install_app("permissions_app_ynh", "/urlpermissionapp" "&admin=alice") + with patch.object(os, "isatty", return_value=False): + install_app("permissions_app_ynh", "/urlpermissionapp" "&admin=alice") assert app_is_installed("permissions_app") if "with_custom_domain" in markers: diff --git a/src/yunohost/tests/test_dns.py b/src/yunohost/tests/test_dns.py index 154d1dc9a..35940764c 100644 --- a/src/yunohost/tests/test_dns.py +++ b/src/yunohost/tests/test_dns.py @@ -34,7 +34,7 @@ def test_get_dns_zone_from_domain_existing(): assert _get_dns_zone_for_domain("non-existing-domain.yunohost.org") == "yunohost.org" assert _get_dns_zone_for_domain("yolo.nohost.me") == "nohost.me" assert _get_dns_zone_for_domain("foo.yolo.nohost.me") == "nohost.me" - assert _get_dns_zone_for_domain("yolo.test") == "test" + assert _get_dns_zone_for_domain("yolo.test") == "yolo.test" assert _get_dns_zone_for_domain("foo.yolo.test") == "test" From 942951048959ea07ddad49642ae5b2db9a545ef0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= Date: Sun, 12 Sep 2021 21:11:37 +0000 Subject: [PATCH 3102/3170] Translated using Weblate (French) Currently translated at 100.0% (661 of 661 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 55eb4ecd4..efd5b7a6e 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -528,7 +528,7 @@ "diagnosis_mail_ehlo_ok": "Le serveur de messagerie SMTP est accessible de l'extérieur et peut donc recevoir des emails !", "diagnosis_mail_ehlo_unreachable": "Le serveur de messagerie SMTP est inaccessible de l'extérieur en IPv{ipversion}. Il ne pourra pas recevoir des emails.", "diagnosis_mail_ehlo_unreachable_details": "Impossible d'ouvrir une connexion sur le port 25 à votre serveur en IPv{ipversion}. Il semble inaccessible.
1. La cause la plus courante de ce problème est que le port 25 n'est pas correctement redirigé vers votre serveur.
2. Vous devez également vous assurer que le service postfix est en cours d'exécution.
3. Sur les configurations plus complexes: assurez-vous qu'aucun pare-feu ou proxy inversé n'interfère.", - "diagnosis_mail_ehlo_wrong_details": "Le EHLO reçu par le serveur de diagnostique distant en IPv{ipversion} est différent du domaine de votre serveur.
EHLO reçu: {wrong_ehlo}
Attendu : {right_ehlo}
La cause la plus courante ce problème est que le port 25 n'est pas correctement redirigé vers votre serveur . Vous pouvez également vous assurer qu'aucun pare-feu ou proxy inversé n'interfère.", + "diagnosis_mail_ehlo_wrong_details": "Le EHLO reçu par le serveur de diagnostique distant en IPv{ipversion} est différent du domaine de votre serveur.
EHLO reçu: {wrong_ehlo}
Attendu : {right_ehlo}
La cause la plus courante ce problème est que le port 25 n'est pas correctement redirigé vers votre serveur. Vous pouvez également vous assurer qu'aucun pare-feu ou proxy inversé n'interfère.", "diagnosis_mail_fcrdns_nok_alternatives_4": "Certains fournisseurs ne vous laisseront pas configurer votre DNS inversé (ou leur fonctionnalité pourrait être cassée...). Si vous rencontrez des problèmes à cause de cela, envisagez les solutions suivantes :
- Certains FAI fournissent l'alternative de à l'aide d'un relais de serveur de messagerie bien que cela implique que le relais pourra espionner votre trafic de messagerie.
- Une alternative respectueuse de la vie privée consiste à utiliser un VPN *avec une IP publique dédiée* pour contourner ce type de limites. Voir https://yunohost.org/#/vpn_advantage
- Enfin, il est également possible de changer de fournisseur", "diagnosis_mail_fcrdns_nok_alternatives_6": "Certains fournisseurs ne vous laisseront pas configurer votre DNS inversé (ou leur fonctionnalité pourrait être cassée...). Si votre DNS inversé est correctement configuré en IPv4, vous pouvez essayer de désactiver l'utilisation d'IPv6 lors de l'envoi d'emails en exécutant yunohost settings set smtp.allow_ipv6 -v off. Remarque : cette dernière solution signifie que vous ne pourrez pas envoyer ou recevoir de emails avec les quelques serveurs qui ont uniquement de l'IPv6.", "diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "DNS inverse actuel : {rdns_domain}
Valeur attendue : {ehlo_domain}", From 341d04a0cd7572ef14e19a9f858c3e10c5ca7d7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= Date: Mon, 13 Sep 2021 05:25:53 +0000 Subject: [PATCH 3103/3170] Translated using Weblate (French) Currently translated at 99.8% (675 of 676 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/locales/fr.json b/locales/fr.json index efd5b7a6e..e6baa93dc 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -659,5 +659,24 @@ "user_import_bad_line": "Ligne incorrecte {line} : {details}", "log_user_import": "Importer des utilisateurs", "diagnosis_high_number_auth_failures": "Il y a eu récemment un grand nombre d'échecs d'authentification. Assurez-vous que Fail2Ban est en cours d'exécution et est correctement configuré, ou utilisez un port personnalisé pour SSH comme expliqué dans https://yunohost.org/security.", - "global_settings_setting_security_nginx_redirect_to_https": "Rediriger les requêtes HTTP vers HTTPS par défaut (NE PAS DÉSACTIVER à moins de savoir vraiment ce que vous faites !)" + "global_settings_setting_security_nginx_redirect_to_https": "Rediriger les requêtes HTTP vers HTTPS par défaut (NE PAS DÉSACTIVER à moins de savoir vraiment ce que vous faites !)", + "config_validate_color": "Doit être une couleur hexadécimale RVB valide", + "app_config_unable_to_apply": "Échec de l'application des valeurs du panneau de configuration.", + "app_config_unable_to_read": "Échec de la lecture des valeurs du panneau de configuration.", + "config_apply_failed": "Échec de l'application de la nouvelle configuration : {error}", + "config_cant_set_value_on_section": "Vous ne pouvez pas définir une seule valeur sur une section de configuration entière.", + "config_forbidden_keyword": "Le mot-clé '{keyword}' est réservé, vous ne pouvez pas créer ou utiliser un panneau de configuration avec une question avec cet identifiant.", + "config_no_panel": "Aucun panneau de configuration trouvé.", + "config_unknown_filter_key": "La clé de filtre '{filter_key}' est incorrecte.", + "config_validate_date": "Doit être une date valide comme dans le format AAAA-MM-JJ", + "config_validate_email": "Doit être un email valide", + "config_validate_time": "Doit être une heure valide comme XX:YY", + "config_validate_url": "Doit être une URL Web valide", + "config_version_not_supported": "Les versions du panneau de configuration '{version}' ne sont pas prises en charge.", + "danger": "Danger :", + "file_extension_not_accepted": "Le fichier '{path}' est refusé car son extension ne fait pas partie des extensions acceptées : {accept}", + "invalid_number_min": "Doit être supérieur à {min}", + "invalid_number_max": "Doit être inférieur à {max}", + "log_app_config_set": "Appliquer la configuration à l'application '{}'", + "service_not_reloading_because_conf_broken": "Le service '{name}' n'est pas rechargé/redémarré car sa configuration est cassée : {errors}" } From 660c4169c93176734a3fb2b99b70a8dfd4c1760a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= Date: Mon, 13 Sep 2021 10:09:48 +0000 Subject: [PATCH 3104/3170] Translated using Weblate (French) Currently translated at 100.0% (676 of 676 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 e6baa93dc..4dd0ced76 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -4,7 +4,7 @@ "admin_password_change_failed": "Impossible de changer le mot de passe", "admin_password_changed": "Le mot de passe d'administration a été modifié", "app_already_installed": "{app} est déjà installé", - "app_argument_choice_invalid": "Choix invalide pour le paramètre '{name}', il doit être l'un de {choices}", + "app_argument_choice_invalid": "Choix invalide pour le paramètre '{name}', il doit être l'un de {choices} au lieu de '{value}'", "app_argument_invalid": "Valeur invalide pour le paramètre '{name}' : {error}", "app_argument_required": "Le paramètre '{name}' est requis", "app_extraction_failed": "Impossible d'extraire les fichiers d'installation", From c0299fa880abb1c428d83da16e8101b4a3cfb899 Mon Sep 17 00:00:00 2001 From: Tymofii-Lytvynenko Date: Mon, 13 Sep 2021 10:08:23 +0000 Subject: [PATCH 3105/3170] Translated using Weblate (Ukrainian) Currently translated at 100.0% (676 of 676 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/uk/ --- locales/uk.json | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/locales/uk.json b/locales/uk.json index 150e8c240..36007b8c3 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -17,7 +17,7 @@ "app_argument_required": "Аргумент '{name}' необхідний", "app_argument_password_no_default": "Помилка під час розбору аргументу пароля '{name}': аргумент пароля не може мати типове значення з причин безпеки", "app_argument_invalid": "Виберіть правильне значення для аргументу '{name}': {error}", - "app_argument_choice_invalid": "Використовуйте один з цих варіантів '{choices}' для аргументу '{name}'", + "app_argument_choice_invalid": "Використовуйте один з цих варіантів '{choices}' для аргументу '{name}' замість '{value}'", "app_already_up_to_date": "{app} має найостаннішу версію", "app_already_installed_cant_change_url": "Цей застосунок уже встановлено. URL-адреса не може бути змінена тільки цією функцією. Перевірте в `app changeurl`, якщо вона доступна.", "app_already_installed": "{app} уже встановлено", @@ -659,5 +659,24 @@ "diagnosis_apps_issue": "Виявлено проблему із застосунком {app}", "diagnosis_apps_allgood": "Усі встановлені застосунки дотримуються основних способів упакування", "diagnosis_high_number_auth_failures": "Останнім часом сталася підозріло велика кількість помилок автентифікації. Ви можете переконатися, що fail2ban працює і правильно налаштований, або скористатися власним портом для SSH, як описано в https://yunohost.org/security.", - "global_settings_setting_security_nginx_redirect_to_https": "Типово переспрямовувати HTTP-запити до HTTP (НЕ ВИМИКАЙТЕ, якщо ви дійсно не знаєте, що робите!)" + "global_settings_setting_security_nginx_redirect_to_https": "Типово переспрямовувати HTTP-запити до HTTP (НЕ ВИМИКАЙТЕ, якщо ви дійсно не знаєте, що робите!)", + "app_config_unable_to_apply": "Не вдалося застосувати значення панелі конфігурації.", + "app_config_unable_to_read": "Не вдалося розпізнати значення панелі конфігурації.", + "config_apply_failed": "Не вдалося застосувати нову конфігурацію: {error}", + "config_cant_set_value_on_section": "Ви не можете встановити одне значення на весь розділ конфігурації.", + "config_forbidden_keyword": "Ключове слово '{keyword}' зарезервовано, ви не можете створити або використовувати панель конфігурації з запитом із таким ID.", + "config_no_panel": "Панель конфігурації не знайдено.", + "config_unknown_filter_key": "Ключ фільтра '{filter_key}' недійсний.", + "config_validate_color": "Колір RGB має бути дійсним шістнадцятковим кольоровим кодом", + "config_validate_date": "Дата має бути дійсною, наприклад, у форматі РРРР-ММ-ДД", + "config_validate_email": "Е-пошта має бути дійсною", + "config_validate_time": "Час має бути дійсним, наприклад ГГ:ХХ", + "config_validate_url": "Вебадреса має бути дійсною", + "config_version_not_supported": "Версії конфігураційної панелі '{version}' не підтримуються.", + "danger": "Небезпека:", + "file_extension_not_accepted": "Файл '{path}' відхиляється, бо його розширення не входить в число прийнятих розширень: {accept}", + "invalid_number_min": "Має бути більшим за {min}", + "invalid_number_max": "Має бути меншим за {max}", + "log_app_config_set": "Застосувати конфігурацію до застосунку '{}'", + "service_not_reloading_because_conf_broken": "Неможливо перезавантажити/перезапустити службу '{name}', тому що її конфігурацію порушено: {errors}" } From 2770ed6c23f58796dc6966988421c6ed6b07db28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= Date: Tue, 14 Sep 2021 13:12:32 +0000 Subject: [PATCH 3106/3170] Translated using Weblate (Catalan) Currently translated at 87.5% (592 of 676 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ca/ --- locales/ca.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/ca.json b/locales/ca.json index 0e8d446d5..cff96cf47 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -618,4 +618,4 @@ "diagnosis_sshd_config_insecure": "Sembla que la configuració SSH s'ha modificat manualment, i no es segura ha que no conté la directiva «AllowGroups» o «AllowUsers» per limitar l'accés a usuaris autoritzats.", "backup_create_size_estimation": "L'arxiu tindrà aproximadament {size} de dades.", "app_restore_script_failed": "S'ha produït un error en el script de restauració de l'aplicació" -} \ No newline at end of file +} From b9761ba30d1be09c621ef2a65895578244c1a88b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= Date: Tue, 14 Sep 2021 13:09:05 +0000 Subject: [PATCH 3107/3170] Translated using Weblate (Esperanto) Currently translated at 73.2% (495 of 676 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/eo/ --- locales/eo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/eo.json b/locales/eo.json index 1904cfd9a..cb84f3791 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -28,7 +28,7 @@ "admin_password": "Pasvorto de la estro", "admin_password_too_long": "Bonvolu elekti pasvorton pli mallonga ol 127 signoj", "already_up_to_date": "Nenio por fari. Ĉio estas jam ĝisdatigita.", - "app_argument_choice_invalid": "Uzu unu el ĉi tiuj elektoj '{choices}' por la argumento '{name}'", + "app_argument_choice_invalid": "Uzu unu el ĉi tiuj elektoj '{choices}' por la argumento '{name}' anstataŭ '{value}'", "app_argument_invalid": "Elektu validan valoron por la argumento '{name}': {error}", "app_change_url_failed_nginx_reload": "Ne eblis reŝarĝi NGINX. Jen la eligo de 'nginx -t':\n{nginx_errors}", "ask_new_admin_password": "Nova administrada pasvorto", @@ -551,4 +551,4 @@ "app_argument_password_no_default": "Eraro dum analiza pasvorta argumento '{name}': pasvorta argumento ne povas havi defaŭltan valoron por sekureca kialo", "additional_urls_already_removed": "Plia URL '{url}' jam forigita en la aldona URL por permeso '{permission}'", "additional_urls_already_added": "Plia URL '{url}' jam aldonita en la aldona URL por permeso '{permission}'" -} \ No newline at end of file +} From e723565384995dc1bc3390400b272b92f264acd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= Date: Tue, 14 Sep 2021 13:13:20 +0000 Subject: [PATCH 3108/3170] Translated using Weblate (Spanish) Currently translated at 80.7% (546 of 676 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/es/ --- locales/es.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/es.json b/locales/es.json index ecb12d912..1eba5b975 100644 --- a/locales/es.json +++ b/locales/es.json @@ -586,4 +586,4 @@ "diagnosis_basesystem_hardware_model": "El modelo de servidor es {model}", "additional_urls_already_removed": "La URL adicional «{url}» ya se ha eliminado para el permiso «{permission}»", "additional_urls_already_added": "La URL adicional «{url}» ya se ha añadido para el permiso «{permission}»" -} \ No newline at end of file +} From 8e28405e633fa3c9d0d9aa887534b35499610a26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= Date: Tue, 14 Sep 2021 13:13:51 +0000 Subject: [PATCH 3109/3170] Translated using Weblate (Italian) Currently translated at 91.1% (616 of 676 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/it/ --- locales/it.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/it.json b/locales/it.json index 7bd048ac7..4aca0f4db 100644 --- a/locales/it.json +++ b/locales/it.json @@ -42,7 +42,7 @@ "ask_new_admin_password": "Nuova password dell'amministrazione", "backup_app_failed": "Non è possibile fare il backup {app}", "backup_archive_app_not_found": "{app} non è stata trovata nel archivio di backup", - "app_argument_choice_invalid": "Usa una delle seguenti scelte '{choices}' per il parametro '{name}'", + "app_argument_choice_invalid": "Usa una delle seguenti scelte '{choices}' per il parametro '{name}' invece di '{value}'", "app_argument_invalid": "Scegli un valore valido per il parametro '{name}': {error}", "app_argument_required": "L'argomento '{name}' è requisito", "app_id_invalid": "Identificativo dell'applicazione non valido", @@ -635,4 +635,4 @@ "global_settings_setting_security_webadmin_allowlist_enabled": "Permetti solo ad alcuni IP di accedere al webadmin.", "disk_space_not_sufficient_update": "Non c'è abbastanza spazio libero per aggiornare questa applicazione", "disk_space_not_sufficient_install": "Non c'è abbastanza spazio libero per installare questa applicazione" -} \ No newline at end of file +} From d7bcdad1e121b1928502385b966b55392a9298f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= Date: Tue, 14 Sep 2021 13:12:49 +0000 Subject: [PATCH 3110/3170] Translated using Weblate (Chinese (Simplified)) Currently translated at 90.2% (610 of 676 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/zh_Hans/ --- locales/zh_Hans.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/zh_Hans.json b/locales/zh_Hans.json index e6b4d1cc8..c79afd22a 100644 --- a/locales/zh_Hans.json +++ b/locales/zh_Hans.json @@ -631,4 +631,4 @@ "log_user_group_create": "创建组'{}'", "log_user_delete": "删除用户'{}'", "log_user_create": "添加用户'{}'" -} \ No newline at end of file +} From c66499eb7d74ac29a54b646ee21240da830139d4 Mon Sep 17 00:00:00 2001 From: ppr Date: Tue, 14 Sep 2021 16:01:47 +0000 Subject: [PATCH 3111/3170] Translated using Weblate (French) Currently translated at 100.0% (676 of 676 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 4dd0ced76..f62f00ee3 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -528,7 +528,7 @@ "diagnosis_mail_ehlo_ok": "Le serveur de messagerie SMTP est accessible de l'extérieur et peut donc recevoir des emails !", "diagnosis_mail_ehlo_unreachable": "Le serveur de messagerie SMTP est inaccessible de l'extérieur en IPv{ipversion}. Il ne pourra pas recevoir des emails.", "diagnosis_mail_ehlo_unreachable_details": "Impossible d'ouvrir une connexion sur le port 25 à votre serveur en IPv{ipversion}. Il semble inaccessible.
1. La cause la plus courante de ce problème est que le port 25 n'est pas correctement redirigé vers votre serveur.
2. Vous devez également vous assurer que le service postfix est en cours d'exécution.
3. Sur les configurations plus complexes: assurez-vous qu'aucun pare-feu ou proxy inversé n'interfère.", - "diagnosis_mail_ehlo_wrong_details": "Le EHLO reçu par le serveur de diagnostique distant en IPv{ipversion} est différent du domaine de votre serveur.
EHLO reçu: {wrong_ehlo}
Attendu : {right_ehlo}
La cause la plus courante ce problème est que le port 25 n'est pas correctement redirigé vers votre serveur. Vous pouvez également vous assurer qu'aucun pare-feu ou proxy inversé n'interfère.", + "diagnosis_mail_ehlo_wrong_details": "Le EHLO reçu par le serveur de diagnostique distant en IPv{ipversion} est différent du domaine de votre serveur.
EHLO reçu : {wrong_ehlo}
Attendu : {right_ehlo}
La cause la plus courante à ce problème est que le port 25 n'est pas correctement redirigé vers votre serveur. Vous pouvez également vous assurer qu'aucun pare-feu ou reverse-proxy n'interfère.", "diagnosis_mail_fcrdns_nok_alternatives_4": "Certains fournisseurs ne vous laisseront pas configurer votre DNS inversé (ou leur fonctionnalité pourrait être cassée...). Si vous rencontrez des problèmes à cause de cela, envisagez les solutions suivantes :
- Certains FAI fournissent l'alternative de à l'aide d'un relais de serveur de messagerie bien que cela implique que le relais pourra espionner votre trafic de messagerie.
- Une alternative respectueuse de la vie privée consiste à utiliser un VPN *avec une IP publique dédiée* pour contourner ce type de limites. Voir https://yunohost.org/#/vpn_advantage
- Enfin, il est également possible de changer de fournisseur", "diagnosis_mail_fcrdns_nok_alternatives_6": "Certains fournisseurs ne vous laisseront pas configurer votre DNS inversé (ou leur fonctionnalité pourrait être cassée...). Si votre DNS inversé est correctement configuré en IPv4, vous pouvez essayer de désactiver l'utilisation d'IPv6 lors de l'envoi d'emails en exécutant yunohost settings set smtp.allow_ipv6 -v off. Remarque : cette dernière solution signifie que vous ne pourrez pas envoyer ou recevoir de emails avec les quelques serveurs qui ont uniquement de l'IPv6.", "diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "DNS inverse actuel : {rdns_domain}
Valeur attendue : {ehlo_domain}", From 69a3cf63c695b4ca5276e7785107f393fe6f40c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= Date: Wed, 15 Sep 2021 07:25:49 +0000 Subject: [PATCH 3112/3170] Translated using Weblate (French) Currently translated at 100.0% (678 of 678 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/locales/fr.json b/locales/fr.json index f62f00ee3..95da748a8 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -678,5 +678,7 @@ "invalid_number_min": "Doit être supérieur à {min}", "invalid_number_max": "Doit être inférieur à {max}", "log_app_config_set": "Appliquer la configuration à l'application '{}'", - "service_not_reloading_because_conf_broken": "Le service '{name}' n'est pas rechargé/redémarré car sa configuration est cassée : {errors}" + "service_not_reloading_because_conf_broken": "Le service '{name}' n'est pas rechargé/redémarré car sa configuration est cassée : {errors}", + "app_argument_password_help_keep": "Tapez sur Entrée pour conserver la valeur actuelle", + "app_argument_password_help_optional": "Tapez un espace pour vider le mot de passe" } From 3d59a1a63874ba732c16edf0508cb28065c1f352 Mon Sep 17 00:00:00 2001 From: Tymofii-Lytvynenko Date: Wed, 15 Sep 2021 08:42:59 +0000 Subject: [PATCH 3113/3170] Translated using Weblate (Ukrainian) Currently translated at 100.0% (678 of 678 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/uk/ --- locales/uk.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/locales/uk.json b/locales/uk.json index 36007b8c3..b8c875774 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -678,5 +678,7 @@ "invalid_number_min": "Має бути більшим за {min}", "invalid_number_max": "Має бути меншим за {max}", "log_app_config_set": "Застосувати конфігурацію до застосунку '{}'", - "service_not_reloading_because_conf_broken": "Неможливо перезавантажити/перезапустити службу '{name}', тому що її конфігурацію порушено: {errors}" + "service_not_reloading_because_conf_broken": "Неможливо перезавантажити/перезапустити службу '{name}', тому що її конфігурацію порушено: {errors}", + "app_argument_password_help_optional": "Введіть один пробіл, щоб очистити пароль", + "app_argument_password_help_keep": "Натисніть Enter, щоб зберегти поточне значення" } From 8036988d0a559967fe19682842154a1f86fc3786 Mon Sep 17 00:00:00 2001 From: Weblate Date: Thu, 16 Sep 2021 15:40:34 +0000 Subject: [PATCH 3114/3170] Added translation using Weblate (Macedonian) --- locales/mk.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 locales/mk.json diff --git a/locales/mk.json b/locales/mk.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/locales/mk.json @@ -0,0 +1 @@ +{} From 729fd299bccdb83478b04d5ebb2ad3962bb6ced4 Mon Sep 17 00:00:00 2001 From: Tymofii-Lytvynenko Date: Thu, 16 Sep 2021 13:47:34 +0000 Subject: [PATCH 3115/3170] Translated using Weblate (Ukrainian) Currently translated at 100.0% (678 of 678 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/uk/ --- locales/uk.json | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/locales/uk.json b/locales/uk.json index b8c875774..70afd39ad 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -25,9 +25,9 @@ "app_action_cannot_be_ran_because_required_services_down": "Для виконання цієї дії повинні бути запущені наступні необхідні служби: {services}. Спробуйте перезапустити їх, щоб продовжити (і, можливо, з'ясувати, чому вони не працюють).", "already_up_to_date": "Нічого не потрібно робити. Все вже актуально.", "admin_password_too_long": "Будь ласка, виберіть пароль коротше 127 символів", - "admin_password_changed": "Пароль адміністратора було змінено", + "admin_password_changed": "Пароль адміністрації було змінено", "admin_password_change_failed": "Неможливо змінити пароль", - "admin_password": "Пароль адміністратора", + "admin_password": "Пароль адміністрації", "additional_urls_already_removed": "Додаткова URL-адреса '{url}' вже видалена в додатковій URL-адресі для дозволу '{permission}'", "additional_urls_already_added": "Додаткова URL-адреса '{url}' вже додана в додаткову URL-адресу для дозволу '{permission}'", "action_invalid": "Неприпустима дія '{action}'", @@ -146,7 +146,7 @@ "operation_interrupted": "Операція була вручну перервана?", "invalid_number": "Має бути числом", "not_enough_disk_space": "Недостатньо вільного місця на '{path}'", - "migrations_to_be_ran_manually": "Міграція {id} повинна бути запущена вручну. Будь ласка, перейдіть в розділ Засоби → Міграції на сторінці вебадміністратора або виконайте команду `yunohost tools migrations run`.", + "migrations_to_be_ran_manually": "Міграція {id} повинна бути запущена вручну. Будь ласка, перейдіть в розділ Засоби → Міграції на сторінці вебадміністрації або виконайте команду `yunohost tools migrations run`.", "migrations_success_forward": "Міграцію {id} завершено", "migrations_skip_migration": "Пропускання міграції {id}...", "migrations_running_forward": "Виконання міграції {id}...", @@ -277,11 +277,11 @@ "group_already_exist_on_system": "Група {group} вже існує в групах системи", "group_already_exist": "Група {group} вже існує", "good_practices_about_user_password": "Зараз ви збираєтеся поставити новий пароль користувача. Пароль повинен складатися не менше ніж з 8 символів, але хорошою практикою є використання більш довгого пароля (тобто гасла) і/або використання різних символів (великих, малих, цифр і спеціальних символів).", - "good_practices_about_admin_password": "Зараз ви збираєтеся поставити новий пароль адміністратора. Пароль повинен складатися не менше ніж з 8 символів, але хорошою практикою є використання більш довгого пароля (тобто парольного гасла) і/або використання різних символів (великих, малих, цифр і спеціальних символів).", + "good_practices_about_admin_password": "Зараз ви збираєтеся поставити новий пароль адміністрації. Пароль повинен складатися не менше ніж з 8 символів, але хорошою практикою є використання більш довгого пароля (тобто парольного гасла) і/або використання різних символів (великих, малих, цифр і спеціальних символів).", "global_settings_unknown_type": "Несподівана ситуація, налаштування {setting} має тип {unknown_type}, але це не тип, підтримуваний системою.", "global_settings_setting_backup_compress_tar_archives": "При створенні нових резервних копій стискати архіви (.tar.gz) замість нестислих архівів (.tar). NB: вмикання цієї опції означає створення легших архівів резервних копій, але початкова процедура резервного копіювання буде значно довшою і важчою для CPU.", - "global_settings_setting_security_webadmin_allowlist": "IP-адреси, яким дозволений доступ до вебадміністратора. Через кому.", - "global_settings_setting_security_webadmin_allowlist_enabled": "Дозволити доступ до вебадміністратора тільки деяким IP-адресам.", + "global_settings_setting_security_webadmin_allowlist": "IP-адреси, яким дозволений доступ до вебадміністрації. Через кому.", + "global_settings_setting_security_webadmin_allowlist_enabled": "Дозволити доступ до вебадміністрації тільки деяким IP-адресам.", "global_settings_setting_smtp_relay_password": "Пароль хоста SMTP-ретрансляції", "global_settings_setting_smtp_relay_user": "Обліковий запис користувача SMTP-ретрансляції", "global_settings_setting_smtp_relay_port": "Порт SMTP-ретрансляції", @@ -351,7 +351,7 @@ "diagnosis_sshd_config_inconsistent": "Схоже, що порт SSH був уручну змінений в /etc/ssh/sshd_config. Починаючи з версії YunoHost 4.2, доступний новий глобальний параметр 'security.ssh.port', що дозволяє уникнути ручного редагування конфігурації.", "diagnosis_sshd_config_insecure": "Схоже, що конфігурація SSH була змінена вручну і є небезпечною, оскільки не містить директив 'AllowGroups' або 'AllowUsers' для обмеження доступу авторизованих користувачів.", "diagnosis_processes_killed_by_oom_reaper": "Деякі процеси було недавно вбито системою через брак пам'яті. Зазвичай це є симптомом нестачі пам'яті в системі або процесу, який з'їв дуже багато пам'яті. Зведення убитих процесів:\n{kills_summary}", - "diagnosis_never_ran_yet": "Схоже, що цей сервер був налаштований недавно, і поки немає звіту про діагностику. Вам слід почати з повної діагностики, або з вебадміністратора, або використовуючи 'yunohost diagnosis run' з командного рядка.", + "diagnosis_never_ran_yet": "Схоже, що цей сервер був налаштований недавно, і поки немає звіту про діагностику. Вам слід почати з повної діагностики, або з вебадміністрації, або використовуючи 'yunohost diagnosis run' з командного рядка.", "diagnosis_unknown_categories": "Наступні категорії невідомі: {categories}", "diagnosis_http_nginx_conf_not_up_to_date_details": "Щоб виправити становище, перевірте різницю за допомогою командного рядка, використовуючи yunohost tools regen-conf nginx --dry-run --with-diff, і якщо все в порядку, застосуйте зміни за допомогою команди yunohost tools regen-conf nginx --force.", "diagnosis_http_nginx_conf_not_up_to_date": "Схоже, що конфігурація nginx цього домену була змінена вручну, що не дозволяє YunoHost визначити, чи доступний він по HTTP.", @@ -416,7 +416,7 @@ "diagnosis_mail_outgoing_port_25_blocked_details": "Спочатку спробуйте розблокувати вихідний порт 25 в інтерфейсі вашого інтернет-маршрутизатора або в інтерфейсі вашого хостинг-провайдера. (Деякі хостинг-провайдери можуть вимагати, щоб ви відправили їм заявку в службу підтримки).", "diagnosis_mail_outgoing_port_25_blocked": "Поштовий сервер SMTP не може відправляти електронні листи на інші сервери, оскільки вихідний порт 25 заблоковано в IPv{ipversion}.", "app_manifest_install_ask_path": "Оберіть шлях URL (після домену), за яким має бути встановлено цей застосунок", - "yunohost_postinstall_end_tip": "Післявстановлення завершено! Щоб завершити доналаштування, будь ласка, розгляньте наступні варіанти:\n - додавання першого користувача через розділ 'Користувачі' вебадміністратора (або 'yunohost user create ' в командному рядку);\n - діагностика можливих проблем через розділ 'Діагностика' вебадміністратора (або 'yunohost diagnosis run' в командному рядку);\n - прочитання розділів 'Завершення встановлення' і 'Знайомство з YunoHost' у документації адміністратора: https://yunohost.org/admindoc.", + "yunohost_postinstall_end_tip": "Післявстановлення завершено! Щоб завершити доналаштування, будь ласка, розгляньте наступні варіанти:\n - додавання першого користувача через розділ 'Користувачі' вебадміністрації (або 'yunohost user create ' в командному рядку);\n - діагностика можливих проблем через розділ 'Діагностика' вебадміністрації (або 'yunohost diagnosis run' в командному рядку);\n - прочитання розділів 'Завершення встановлення' і 'Знайомство з YunoHost' у документації адміністратора: https://yunohost.org/admindoc.", "yunohost_not_installed": "YunoHost установлений неправильно. Будь ласка, запустіть 'yunohost tools postinstall'", "yunohost_installing": "Установлення YunoHost...", "yunohost_configured": "YunoHost вже налаштовано", @@ -445,7 +445,7 @@ "unexpected_error": "Щось пішло не так: {error}", "unbackup_app": "{app} НЕ буде збережено", "tools_upgrade_special_packages_completed": "Оновлення пакета YunoHost завершено.\nНатисніть [Enter] для повернення до командного рядка", - "tools_upgrade_special_packages_explanation": "Спеціальне оновлення триватиме у тлі. Будь ласка, не запускайте ніяких інших дій на вашому сервері протягом наступних ~ 10 хвилин (в залежності від швидкості обладнання). Після цього вам, можливо, доведеться заново увійти в вебадміністратора. Журнал оновлення буде доступний в Засоби → Журнал (в веб-адміністраторі) або за допомогою 'yunohost log list' (з командного рядка).", + "tools_upgrade_special_packages_explanation": "Спеціальне оновлення триватиме у тлі. Будь ласка, не запускайте ніяких інших дій на вашому сервері протягом наступних ~ 10 хвилин (в залежності від швидкості обладнання). Після цього вам, можливо, доведеться заново увійти в вебадміністрації. Журнал оновлення буде доступний в Засоби → Журнал (в вебадміністрації) або за допомогою 'yunohost log list' (з командного рядка).", "tools_upgrade_special_packages": "Тепер оновлюємо 'спеціальні' (пов'язані з yunohost) пакети…", "tools_upgrade_regular_packages_failed": "Не вдалося оновити пакети: {packages_list}", "tools_upgrade_regular_packages": "Тепер оновлюємо 'звичайні' (не пов'язані з yunohost) пакети…", @@ -476,7 +476,7 @@ "diagnosis_diskusage_ok": "У сховищі {mountpoint} (на пристрої {device}) залишилося {free} ({free_percent}%) вільного місця (з {total})!", "diagnosis_diskusage_low": "Сховище {mountpoint} (на пристрої {device}) має тільки {free} ({free_percent}%) вільного місця (з {total}). Будьте уважні.", "diagnosis_diskusage_verylow": "Сховище {mountpoint} (на пристрої {device}) має тільки {free} ({free_percent}%) вільного місця (з {total}). Вам дійсно варто подумати про очищення простору!", - "diagnosis_services_bad_status_tip": "Ви можете спробувати перезапустити службу, а якщо це не допоможе, подивіться журнали служби в вебадміністраторі (з командного рядка це можна зробити за допомогою yunohost service restart {service} і yunohost service log {service}).", + "diagnosis_services_bad_status_tip": "Ви можете спробувати перезапустити службу, а якщо це не допоможе, подивіться журнали служби в вебадміністрації (з командного рядка це можна зробити за допомогою yunohost service restart {service} і yunohost service log {service}).", "diagnosis_services_bad_status": "Служба {service} у стані {status} :(", "diagnosis_services_conf_broken": "Для служби {service} порушена конфігурація!", "diagnosis_services_running": "Службу {service} запущено!", @@ -517,7 +517,7 @@ "diagnosis_cant_run_because_of_dep": "Неможливо запустити діагностику для {category}, поки є важливі проблеми, пов'язані з {dep}.", "diagnosis_cache_still_valid": "(Кеш все ще дійсний для діагностики {category}. Повторна діагностика поки не проводиться!)", "diagnosis_failed_for_category": "Не вдалося провести діагностику для категорії '{category}': {error}", - "diagnosis_display_tip": "Щоб побачити знайдені проблеми, ви можете перейти в розділ Діагностика в вебадміністраторі або виконати команду 'yunohost diagnosis show --issues --human-readable' з командного рядка.", + "diagnosis_display_tip": "Щоб побачити знайдені проблеми, ви можете перейти в розділ Діагностика в вебадміністрації або виконати команду 'yunohost diagnosis show --issues --human-readable' з командного рядка.", "diagnosis_package_installed_from_sury_details": "Деякі пакети були ненавмисно встановлені зі стороннього репозиторію під назвою Sury. Команда YunoHost поліпшила стратегію роботи з цими пакетами, але очікується, що в деяких системах, які встановили застосунки PHP7.3 ще на Stretch, залишаться деякі невідповідності. Щоб виправити це становище, спробуйте виконати наступну команду: {cmd_to_fix}", "diagnosis_package_installed_from_sury": "Деякі системні пакети мають бути зістарені у версії", "diagnosis_backports_in_sources_list": "Схоже, що apt (менеджер пакетів) налаштований на використання репозиторія backports. Якщо ви не знаєте, що робите, ми наполегливо не радимо встановлювати пакети з backports, тому що це може привести до нестабільності або конфліктів у вашій системі.", @@ -600,7 +600,7 @@ "ask_password": "Пароль", "ask_new_path": "Новий шлях", "ask_new_domain": "Новий домен", - "ask_new_admin_password": "Новий пароль адміністратора", + "ask_new_admin_password": "Новий пароль адміністрації", "ask_main_domain": "Основний домен", "ask_lastname": "Прізвище", "ask_firstname": "Ім'я", @@ -637,7 +637,7 @@ "app_not_upgraded": "Застосунок '{failed_app}' не вдалося оновити, і, як наслідок, оновлення таких застосунків було скасовано: {apps}", "app_manifest_install_ask_is_public": "Чи має цей застосунок бути відкритим для анонімних відвідувачів?", "app_manifest_install_ask_admin": "Виберіть користувача-адміністратора для цього застосунку", - "app_manifest_install_ask_password": "Виберіть пароль адміністратора для цього застосунку", + "app_manifest_install_ask_password": "Виберіть пароль адміністрації для цього застосунку", "diagnosis_description_apps": "Застосунки", "user_import_success": "Користувачів успішно імпортовано", "user_import_nothing_to_do": "Не потрібно імпортувати жодного користувача", From 0cf195ca3201db1bf12bcf72fbb5fb1b0865460b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M?= Date: Fri, 17 Sep 2021 04:44:37 +0000 Subject: [PATCH 3116/3170] Translated using Weblate (Galician) Currently translated at 100.0% (678 of 678 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/gl/ --- locales/gl.json | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/locales/gl.json b/locales/gl.json index 7976c11e9..b729dedff 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -18,7 +18,7 @@ "app_argument_required": "Requírese o argumento '{name}'", "app_argument_password_no_default": "Erro ao procesar o argumento do contrasinal '{name}': o argumento do contrasinal non pode ter un valor por defecto por razón de seguridade", "app_argument_invalid": "Elixe un valor válido para o argumento '{name}': {error}", - "app_argument_choice_invalid": "Usa unha destas opcións '{choices}' para o argumento '{name}'", + "app_argument_choice_invalid": "Usa unha destas opcións '{choices}' para o argumento '{name}' no lugar de '{value}'", "backup_archive_writing_error": "Non se puideron engadir os ficheiros '{source}' (chamados no arquivo '{dest}' para ser copiados dentro do arquivo comprimido '{archive}'", "backup_archive_system_part_not_available": "A parte do sistema '{part}' non está dispoñible nesta copia", "backup_archive_corrupted": "Semella que o arquivo de copia '{archive}' está estragado : {error}", @@ -659,5 +659,26 @@ "diagnosis_description_apps": "Aplicacións", "diagnosis_apps_broken": "Actualmente esta aplicación está marcada como estragada no catálogo de aplicacións de YunoHost. Podería tratarse dun problema temporal mentras as mantedoras intentan arraxala. Entanto así a actualización da app está desactivada.", "diagnosis_apps_issue": "Atopouse un problema na app {app}", - "diagnosis_apps_not_in_app_catalog": "Esta aplicación non está no catálgo de aplicacións de YunoHost. Se estivo no pasado e foi eliminada, deberías considerar desinstalala porque non recibirá actualizacións, e podería comprometer a integridade e seguridade do teu sistema." + "diagnosis_apps_not_in_app_catalog": "Esta aplicación non está no catálgo de aplicacións de YunoHost. Se estivo no pasado e foi eliminada, deberías considerar desinstalala porque non recibirá actualizacións, e podería comprometer a integridade e seguridade do teu sistema.", + "app_argument_password_help_optional": "Escribe un espazo para limpar o contrasinal", + "config_validate_date": "Debe ser unha data válida co formato YYYY-MM-DD", + "config_validate_email": "Debe ser un email válido", + "config_validate_time": "Debe ser unha hora válida tal que XX:YY", + "config_validate_url": "Debe ser un URL válido", + "danger": "Perigo:", + "app_argument_password_help_keep": "Preme Enter para manter o valor actual", + "app_config_unable_to_read": "Fallou a lectura dos valores de configuración.", + "config_apply_failed": "Fallou a aplicación da nova configuración: {error}", + "config_forbidden_keyword": "O palabra chave '{keyword}' está reservada, non podes crear ou usar un panel de configuración cunha pregunta con este id.", + "config_no_panel": "Non se atopa panel configurado.", + "config_unknown_filter_key": "A chave do filtro '{filter_key}' non é correcta.", + "config_validate_color": "Debe ser un valor RGB hexadecimal válido", + "invalid_number_min": "Ten que ser maior que {min}", + "log_app_config_set": "Aplicar a configuración á app '{}'", + "app_config_unable_to_apply": "Fallou a aplicación dos valores de configuración.", + "config_cant_set_value_on_section": "Non podes establecer un valor único na sección completa de configuración.", + "config_version_not_supported": "A versión do panel de configuración '{version}' non está soportada.", + "file_extension_not_accepted": "Rexeitouse o ficheiro '{path}' porque a súa extensión non está entre as aceptadas: {accept}", + "invalid_number_max": "Ten que ser menor de {max}", + "service_not_reloading_because_conf_broken": "Non se recargou/reiniciou o servizo '{name}' porque a súa configuración está estragada: {errors}" } From f90a5c033be14eec4948cd1acc8b2eaec8d5b1b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= Date: Fri, 17 Sep 2021 21:27:45 +0000 Subject: [PATCH 3117/3170] Translated using Weblate (Esperanto) Currently translated at 73.3% (497 of 678 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/eo/ --- locales/eo.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/eo.json b/locales/eo.json index cb84f3791..a19dc3ccf 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -121,7 +121,7 @@ "pattern_password": "Devas esti almenaŭ 3 signoj longaj", "root_password_desynchronized": "La pasvorta administranto estis ŝanĝita, sed YunoHost ne povis propagandi ĉi tion al la radika pasvorto!", "service_remove_failed": "Ne povis forigi la servon '{service}'", - "backup_permission": "Rezerva permeso por app {app}", + "backup_permission": "Rezerva permeso por {app}", "log_user_group_delete": "Forigi grupon '{}'", "log_user_group_update": "Ĝisdatigi grupon '{}'", "dyndns_provider_unreachable": "Ne povas atingi la provizanton DynDNS {provider}: ĉu via YunoHost ne estas ĝuste konektita al la interreto aŭ la dyneta servilo malŝaltiĝas.", From 933e82598735ea899f69598546906093f0a5f921 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= Date: Fri, 17 Sep 2021 21:24:57 +0000 Subject: [PATCH 3118/3170] Translated using Weblate (Portuguese) Currently translated at 21.5% (146 of 678 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/pt/ --- locales/pt.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/pt.json b/locales/pt.json index 7aad33e6a..ff555f347 100644 --- a/locales/pt.json +++ b/locales/pt.json @@ -110,7 +110,7 @@ "backup_output_directory_forbidden": "Escolha um diretório de saída diferente. Backups não podem ser criados nos subdiretórios /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var ou /home/yunohost.backup/archives", "app_already_installed_cant_change_url": "Este aplicativo já está instalado. A URL não pode ser alterada apenas por esta função. Confira em `app changeurl` se está disponível.", "app_already_up_to_date": "{app} já está atualizado", - "app_argument_choice_invalid": "Use uma das opções '{choices}' para o argumento '{name}'", + "app_argument_choice_invalid": "Use uma das opções '{choices}' para o argumento '{name}' em vez de '{value}'", "app_argument_invalid": "Escolha um valor válido para o argumento '{name}': {error}", "app_argument_required": "O argumento '{name}' é obrigatório", "app_change_url_failed_nginx_reload": "Não foi possível reiniciar o nginx. Aqui está o retorno de 'nginx -t':\n{nginx_errors}", From 3a1ecb890f715c9b28137f493f7fb13bab769425 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= Date: Sat, 18 Sep 2021 17:34:14 +0000 Subject: [PATCH 3119/3170] Translated using Weblate (French) Currently translated at 100.0% (678 of 678 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 95da748a8..e3a0049ba 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -670,7 +670,7 @@ "config_unknown_filter_key": "La clé de filtre '{filter_key}' est incorrecte.", "config_validate_date": "Doit être une date valide comme dans le format AAAA-MM-JJ", "config_validate_email": "Doit être un email valide", - "config_validate_time": "Doit être une heure valide comme XX:YY", + "config_validate_time": "Doit être une heure valide comme HH:MM", "config_validate_url": "Doit être une URL Web valide", "config_version_not_supported": "Les versions du panneau de configuration '{version}' ne sont pas prises en charge.", "danger": "Danger :", From f74aee9ada691e6ceb75f038767d238add6cdb56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= Date: Sat, 18 Sep 2021 17:38:18 +0000 Subject: [PATCH 3120/3170] Translated using Weblate (Italian) Currently translated at 92.9% (630 of 678 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/it/ --- locales/it.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/locales/it.json b/locales/it.json index 4aca0f4db..8ecdc4b01 100644 --- a/locales/it.json +++ b/locales/it.json @@ -116,8 +116,8 @@ "user_update_failed": "Impossibile aggiornare l'utente {user}: {error}", "restore_hook_unavailable": "Lo script di ripristino per '{part}' non è disponibile per il tuo sistema e non è nemmeno nell'archivio", "restore_nothings_done": "Nulla è stato ripristinato", - "restore_running_app_script": "Ripristino dell'app '{app}'…", - "restore_running_hooks": "Esecuzione degli hook di ripristino…", + "restore_running_app_script": "Ripristino dell'app '{app}'...", + "restore_running_hooks": "Esecuzione degli hook di ripristino...", "service_added": "Il servizio '{service}' è stato aggiunto", "service_already_started": "Il servizio '{service}' è già avviato", "service_already_stopped": "Il servizio '{service}' è già stato fermato", @@ -143,7 +143,7 @@ "user_created": "Utente creato", "user_creation_failed": "Impossibile creare l'utente {user}: {error}", "user_deletion_failed": "Impossibile cancellare l'utente {user}: {error}", - "user_home_creation_failed": "Impossibile creare la 'home' directory del utente", + "user_home_creation_failed": "Impossibile creare la home directory '{home}' del utente", "user_unknown": "Utente sconosciuto: {user}", "user_updated": "Info dell'utente cambiate", "yunohost_already_installed": "YunoHost è già installato", @@ -398,11 +398,11 @@ "unknown_main_domain_path": "Percorso o dominio sconosciuto per '{app}'. Devi specificare un dominio e un percorso per poter specificare un URL per il permesso.", "tools_upgrade_special_packages_completed": "Aggiornamento pacchetti YunoHost completato.\nPremi [Invio] per tornare al terminale", "tools_upgrade_special_packages_explanation": "L'aggiornamento speciale continuerà in background. Per favore non iniziare nessun'altra azione sul tuo server per i prossimi ~10 minuti (dipende dalla velocità hardware). Dopo questo, dovrai ri-loggarti nel webadmin. Il registro di aggiornamento sarà disponibile in Strumenti → Log/Registri (nel webadmin) o dalla linea di comando eseguendo 'yunohost log list'.", - "tools_upgrade_special_packages": "Adesso aggiorno i pacchetti 'speciali' (correlati a yunohost)…", + "tools_upgrade_special_packages": "Adesso aggiorno i pacchetti 'speciali' (correlati a yunohost)...", "tools_upgrade_regular_packages_failed": "Impossibile aggiornare i pacchetti: {packages_list}", - "tools_upgrade_regular_packages": "Adesso aggiorno i pacchetti 'normali' (non correlati a yunohost)…", - "tools_upgrade_cant_unhold_critical_packages": "Impossibile annullare il blocco dei pacchetti critici/importanti…", - "tools_upgrade_cant_hold_critical_packages": "Impossibile bloccare i pacchetti critici/importanti…", + "tools_upgrade_regular_packages": "Adesso aggiorno i pacchetti 'normali' (non correlati a yunohost)...", + "tools_upgrade_cant_unhold_critical_packages": "Impossibile annullare il blocco dei pacchetti critici/importanti...", + "tools_upgrade_cant_hold_critical_packages": "Impossibile bloccare i pacchetti critici/importanti...", "tools_upgrade_cant_both": "Impossibile aggiornare sia il sistema e le app nello stesso momento", "tools_upgrade_at_least_one": "Specifica 'apps', o 'system'", "show_tile_cant_be_enabled_for_regex": "Non puoi abilitare 'show_tile' in questo momento, perché l'URL del permesso '{permission}' è una regex", @@ -438,14 +438,14 @@ "restore_removing_tmp_dir_failed": "Impossibile rimuovere una vecchia directory temporanea", "restore_not_enough_disk_space": "Spazio libero insufficiente (spazio: {free_space}B, necessario: {needed_space}B, margine di sicurezza: {margin}B)", "restore_may_be_not_enough_disk_space": "Il tuo sistema non sembra avere abbastanza spazio (libero: {free_space}B, necessario: {needed_space}B, margine di sicurezza: {margin}B)", - "restore_extracting": "Sto estraendo i file necessari dall'archivio…", + "restore_extracting": "Sto estraendo i file necessari dall'archivio...", "restore_already_installed_apps": "Le seguenti app non possono essere ripristinate perché sono già installate: {apps}", "regex_with_only_domain": "Non puoi usare una regex per il dominio, solo per i percorsi", "regex_incompatible_with_tile": "/!\\ Packagers! Il permesso '{permission}' ha show_tile impostato su 'true' e perciò non è possibile definire un URL regex per l'URL principale", "regenconf_need_to_explicitly_specify_ssh": "La configurazione ssh è stata modificata manualmente, ma devi specificare la categoria 'ssh' con --force per applicare le modifiche.", "regenconf_pending_applying": "Applico le configurazioni in attesa per la categoria '{category}'...", "regenconf_failed": "Impossibile rigenerare la configurazione per le categorie: {categories}", - "regenconf_dry_pending_applying": "Controllo configurazioni in attesa che potrebbero essere applicate alla categoria '{category}'…", + "regenconf_dry_pending_applying": "Controllo configurazioni in attesa che potrebbero essere applicate alla categoria '{category}'...", "regenconf_would_be_updated": "La configurazione sarebbe stata aggiornata per la categoria '{category}'", "regenconf_updated": "Configurazione aggiornata per '{category}'", "regenconf_up_to_date": "Il file di configurazione è già aggiornato per la categoria '{category}'", From 112b2912d740a9ea894a9b4ce9d7eea642f7064a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= Date: Sat, 18 Sep 2021 17:43:22 +0000 Subject: [PATCH 3121/3170] Translated using Weblate (Occitan) Currently translated at 52.2% (354 of 678 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/oc/ --- locales/oc.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/oc.json b/locales/oc.json index 27d4aeca9..b084c5236 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -448,7 +448,7 @@ "diagnosis_ports_could_not_diagnose_details": "Error : {error}", "diagnosis_http_could_not_diagnose": "Impossible de diagnosticar se lo domeni es accessible de l’exterior.", "diagnosis_http_could_not_diagnose_details": "Error : {error}", - "apps_catalog_updating": "Actualizacion del catalòg d’aplicacion…", + "apps_catalog_updating": "Actualizacion del catalòg d’aplicacion...", "apps_catalog_failed_to_download": "Telecargament impossible del catalòg d’aplicacions {apps_catalog} : {error}", "apps_catalog_obsolete_cache": "La memòria cache del catalòg d’aplicacion es voida o obsolèta.", "apps_catalog_update_success": "Lo catalòg d’aplicacions es a jorn !", @@ -518,4 +518,4 @@ "diagnosis_domain_expiration_not_found": "Impossible de verificar la data d’expiracion d’unes domenis", "backup_create_size_estimation": "L’archiu contendrà apr’aquí {size} de donadas.", "app_restore_script_failed": "Una error s’es producha a l’interior del script de restauracion de l’aplicacion" -} \ No newline at end of file +} From 25a82b4b59537b07fafbc2abc2fb878bee7cc0f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= Date: Sat, 18 Sep 2021 17:36:10 +0000 Subject: [PATCH 3122/3170] Translated using Weblate (Galician) Currently translated at 100.0% (678 of 678 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/gl/ --- locales/gl.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/gl.json b/locales/gl.json index b729dedff..0ef15ada1 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -663,7 +663,7 @@ "app_argument_password_help_optional": "Escribe un espazo para limpar o contrasinal", "config_validate_date": "Debe ser unha data válida co formato YYYY-MM-DD", "config_validate_email": "Debe ser un email válido", - "config_validate_time": "Debe ser unha hora válida tal que XX:YY", + "config_validate_time": "Debe ser unha hora válida tal que HH:MM", "config_validate_url": "Debe ser un URL válido", "danger": "Perigo:", "app_argument_password_help_keep": "Preme Enter para manter o valor actual", From eeebe5469b9518d604431354431527e7334af2da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= Date: Sat, 18 Sep 2021 17:42:02 +0000 Subject: [PATCH 3123/3170] Translated using Weblate (Persian) Currently translated at 93.8% (636 of 678 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fa/ --- locales/fa.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/locales/fa.json b/locales/fa.json index d9d3cb175..e0195717f 100644 --- a/locales/fa.json +++ b/locales/fa.json @@ -152,7 +152,7 @@ "apps_catalog_update_success": "کاتالوگ برنامه به روز شد!", "apps_catalog_obsolete_cache": "حافظه پنهان کاتالوگ برنامه خالی یا منسوخ شده است.", "apps_catalog_failed_to_download": "بارگیری کاتالوگ برنامه {apps_catalog} امکان پذیر نیست: {error}", - "apps_catalog_updating": "در حال به روز رسانی کاتالوگ برنامه…", + "apps_catalog_updating": "در حال به روز رسانی کاتالوگ برنامه...", "apps_catalog_init_success": "سیستم کاتالوگ برنامه راه اندازی اولیه شد!", "apps_already_up_to_date": "همه برنامه ها در حال حاضر به روز هستند", "app_packaging_format_not_supported": "این برنامه قابل نصب نیست زیرا قالب بسته بندی آن توسط نسخه YunoHost شما پشتیبانی نمی شود. احتمالاً باید ارتقاء سیستم خود را در نظر بگیرید.", @@ -352,7 +352,7 @@ "dyndns_could_not_check_provide": "بررسی نشد که آیا {provider} می تواند {domain} را ارائه دهد یا خیر.", "dpkg_lock_not_available": "این دستور در حال حاضر قابل اجرا نیست زیرا به نظر می رسد برنامه دیگری از قفل dpkg (مدیر بسته سیستم) استفاده می کند", "dpkg_is_broken": "شما نمی توانید این کار را در حال حاضر انجام دهید زیرا dpkg/APT (اداره کنندگان سیستم بسته ها) به نظر می رسد در وضعیت خرابی است… می توانید با اتصال از طریق SSH و اجرا این فرمان `sudo apt install --fix-broken` and/or `sudo dpkg --configure -a` مشکل را حل کنید.", - "downloading": "در حال بارگیری…", + "downloading": "در حال بارگیری...", "done": "انجام شد", "domains_available": "دامنه های موجود:", "domain_unknown": "دامنه ناشناخته", @@ -538,11 +538,11 @@ "unbackup_app": "{app} ذخیره نمی شود", "tools_upgrade_special_packages_completed": "ارتقاء بسته YunoHost به پایان رسید\nبرای بازگرداندن خط فرمان [Enter] را فشار دهید", "tools_upgrade_special_packages_explanation": "ارتقاء ویژه در پس زمینه ادامه خواهد یافت. لطفاً تا 10 دقیقه دیگر (بسته به سرعت سخت افزار) هیچ اقدام دیگری را روی سرور خود شروع نکنید. پس از این کار ، ممکن است مجبور شوید دوباره وارد webadmin شوید. گزارش ارتقاء در Tools → Log (در webadmin) یا با استفاده از 'yunohost log list' (در خط فرمان) در دسترس خواهد بود.", - "tools_upgrade_special_packages": "در حال ارتقاء بسته های 'special' (مربوط به yunohost)…", + "tools_upgrade_special_packages": "در حال ارتقاء بسته های 'special' (مربوط به yunohost)...", "tools_upgrade_regular_packages_failed": "بسته ها را نمی توان ارتقا داد: {packages_list}", - "tools_upgrade_regular_packages": "در حال ارتقاء بسته های 'regular' (غیر مرتبط با yunohost)…", - "tools_upgrade_cant_unhold_critical_packages": "بسته های مهم و حیاتی را نمی توان نگه نداشت…", - "tools_upgrade_cant_hold_critical_packages": "بسته های مهم و حیاتی را نمی توان نگه داشت…", + "tools_upgrade_regular_packages": "در حال ارتقاء بسته های 'regular' (غیر مرتبط با yunohost)...", + "tools_upgrade_cant_unhold_critical_packages": "بسته های مهم و حیاتی را نمی توان نگه نداشت...", + "tools_upgrade_cant_hold_critical_packages": "بسته های مهم و حیاتی را نمی توان نگه داشت...", "tools_upgrade_cant_both": "نمی توان سیستم و برنامه ها را به طور همزمان ارتقا داد", "tools_upgrade_at_least_one": "لطفاً مشخص کنید 'apps' ، یا 'system'", "this_action_broke_dpkg": "این اقدام dpkg/APT (مدیران بسته های سیستم) را خراب کرد... می توانید با اتصال از طریق SSH و اجرای فرمان `sudo apt install --fix -break` و/یا` sudo dpkg --configure -a` این مشکل را حل کنید.", @@ -597,15 +597,15 @@ "root_password_replaced_by_admin_password": "گذرواژه ریشه شما با رمز مدیریت جایگزین شده است.", "root_password_desynchronized": "گذرواژه مدیریت تغییر کرد ، اما YunoHost نتوانست این را به رمز عبور ریشه منتقل کند!", "restore_system_part_failed": "بخش سیستم '{part}' بازیابی و ترمیم نشد", - "restore_running_hooks": "در حال اجرای قلاب های ترمیم و بازیابی…", - "restore_running_app_script": "ترمیم و بازیابی برنامه '{app}'…", + "restore_running_hooks": "در حال اجرای قلاب های ترمیم و بازیابی...", + "restore_running_app_script": "ترمیم و بازیابی برنامه '{app}'...", "restore_removing_tmp_dir_failed": "پوشه موقت قدیمی حذف نشد", "restore_nothings_done": "هیچ چیز ترمیم و بازسازی نشد", "restore_not_enough_disk_space": "فضای کافی موجود نیست (فضا: {free_space} B ، فضای مورد نیاز: {needed_space} B ، حاشیه امنیتی: {margin} B)", "restore_may_be_not_enough_disk_space": "به نظر می رسد سیستم شما فضای کافی ندارد (فضای آزاد: {free_space} B ، فضای مورد نیاز: {needed_space} B ، حاشیه امنیتی: {margin} B)", "restore_hook_unavailable": "اسکریپت ترمیم و بازسازی برای '{part}' در سیستم شما در دسترس نیست و همچنین در بایگانی نیز وجود ندارد", "restore_failed": "سیستم بازیابی نشد", - "restore_extracting": "استخراج فایل های مورد نیاز از بایگانی…", + "restore_extracting": "استخراج فایل های مورد نیاز از بایگانی...", "restore_confirm_yunohost_installed": "آیا واقعاً می خواهید سیستمی که هم اکنون نصب شده را بازیابی کنید؟ [{answers}]", "restore_complete": "مرمت به پایان رسید", "restore_cleaning_failed": "فهرست بازسازی موقت پاک نشد", @@ -617,7 +617,7 @@ "regenconf_need_to_explicitly_specify_ssh": "پیکربندی ssh به صورت دستی تغییر یافته است ، اما شما باید صراحتاً دسته \"ssh\" را با --force برای اعمال تغییرات در واقع مشخص کنید.", "regenconf_pending_applying": "در حال اعمال پیکربندی معلق برای دسته '{category}'...", "regenconf_failed": "پیکربندی برای دسته (ها) بازسازی نشد: {categories}", - "regenconf_dry_pending_applying": "در حال بررسی پیکربندی معلق که برای دسته '{category}' اعمال می شد…", + "regenconf_dry_pending_applying": "در حال بررسی پیکربندی معلق که برای دسته '{category}' اعمال می شد...", "regenconf_would_be_updated": "پیکربندی برای دسته '{category}' به روز می شد", "regenconf_updated": "پیکربندی برای دسته '{category}' به روز شد", "regenconf_up_to_date": "پیکربندی در حال حاضر برای دسته '{category}' به روز است", From 0d844dbb05e5b46ac85da786be4502214c284373 Mon Sep 17 00:00:00 2001 From: ppr Date: Sun, 19 Sep 2021 09:48:49 +0000 Subject: [PATCH 3124/3170] Translated using Weblate (French) Currently translated at 99.8% (677 of 678 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index e3a0049ba..419917b28 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -42,7 +42,7 @@ "backup_output_directory_forbidden": "Choisissez un répertoire de destination différent. Les sauvegardes ne peuvent pas être créées dans les sous-dossiers /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var ou /home/yunohost.backup/archives", "backup_output_directory_not_empty": "Le répertoire de destination n'est pas vide", "backup_output_directory_required": "Vous devez spécifier un dossier de destination pour la sauvegarde", - "backup_running_hooks": "Exécution des scripts de sauvegarde...", + "backup_running_hooks": "Exécution des scripts de sauvegarde ...", "custom_app_url_required": "Vous devez spécifier une URL pour mettre à jour votre application personnalisée {app}", "disk_space_not_sufficient_install": "Il ne reste pas assez d'espace disque pour installer cette application", "disk_space_not_sufficient_update": "Il ne reste pas assez d'espace disque pour mettre à jour cette application", @@ -188,9 +188,9 @@ "global_settings_unknown_type": "Situation inattendue : la configuration {setting} semble avoir le type {unknown_type} mais celui-ci n'est pas pris en charge par le système.", "global_settings_unknown_setting_from_settings_file": "Clé inconnue dans les paramètres : '{setting_key}', rejet de cette clé et sauvegarde de celle-ci dans /etc/yunohost/unkown_settings.json", "backup_abstract_method": "Cette méthode de sauvegarde reste à implémenter", - "backup_applying_method_tar": "Création de l'archive TAR de la sauvegarde...", - "backup_applying_method_copy": "Copie de tous les fichiers à sauvegarder...", - "backup_applying_method_custom": "Appel de la méthode de sauvegarde personnalisée '{method}'...", + "backup_applying_method_tar": "Création de l'archive TAR de la sauvegarde ...", + "backup_applying_method_copy": "Copie de tous les fichiers à sauvegarder ...", + "backup_applying_method_custom": "Appel de la méthode de sauvegarde personnalisée '{method}' ...", "backup_archive_system_part_not_available": "La partie '{part}' du système n'est pas disponible dans cette sauvegarde", "backup_archive_writing_error": "Impossible d'ajouter des fichiers '{source}' (nommés dans l'archive : '{dest}') à sauvegarder dans l'archive compressée '{archive}'", "backup_ask_for_copying_if_needed": "Voulez-vous effectuer la sauvegarde en utilisant {size}Mo temporairement ? (Cette méthode est utilisée car certains fichiers n'ont pas pu être préparés avec une méthode plus efficace.)", @@ -300,7 +300,7 @@ "app_upgrade_several_apps": "Les applications suivantes seront mises à jour : {apps}", "ask_new_domain": "Nouveau domaine", "ask_new_path": "Nouveau chemin", - "backup_actually_backuping": "Création d'une archive de sauvegarde à partir des fichiers collectés...", + "backup_actually_backuping": "Création d'une archive de sauvegarde à partir des fichiers collectés ...", "backup_mount_archive_for_restore": "Préparation de l'archive pour restauration...", "confirm_app_install_warning": "Avertissement : cette application peut fonctionner mais n'est pas bien intégrée dans YunoHost. Certaines fonctionnalités telles que l'authentification unique et la sauvegarde/restauration peuvent ne pas être disponibles. L'installer quand même ? [{answers}] ", "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}'", @@ -356,7 +356,7 @@ "tools_upgrade_special_packages_explanation": "La mise à niveau spécifique à YunoHost se poursuivra en arrière-plan. Veuillez ne pas lancer d'autres actions sur votre serveur pendant les 10 prochaines minutes (selon la vitesse du matériel). Après cela, vous devrez peut-être vous reconnecter à la webadmin. Le journal de mise à niveau sera disponible dans Outils → Journal (dans le webadmin) ou en utilisant 'yunohost log list' (à partir de la ligne de commande).", "update_apt_cache_failed": "Impossible de mettre à jour le cache APT (gestionnaire de paquets Debian). Voici un extrait du fichier sources.list qui pourrait vous aider à identifier les lignes problématiques :\n{sourceslist}", "update_apt_cache_warning": "Des erreurs se sont produites lors de la mise à jour du cache APT (gestionnaire de paquets Debian). Voici un extrait des lignes du fichier sources.list qui pourrait vous aider à identifier les lignes problématiques :\n{sourceslist}", - "backup_permission": "Permission de sauvegarde pour {app}", + "backup_permission": "Sauvegarde des permissions pour {app}", "group_created": "Le groupe '{group}' a été créé", "group_deleted": "Suppression du groupe '{group}'", "group_unknown": "Le groupe {group} est inconnu", From 96864e5a334833b0d7ca22ee4df6caab7c1963ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= Date: Sun, 19 Sep 2021 13:18:48 +0000 Subject: [PATCH 3125/3170] Translated using Weblate (Catalan) Currently translated at 90.2% (612 of 678 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ca/ --- locales/ca.json | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/locales/ca.json b/locales/ca.json index cff96cf47..396f836f4 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -6,7 +6,7 @@ "app_already_installed": "{app} ja està instal·lada", "app_already_installed_cant_change_url": "Aquesta aplicació ja està instal·lada. La URL no és pot canviar únicament amb aquesta funció. Mireu a `app changeurl` si està disponible.", "app_already_up_to_date": "{app} ja està actualitzada", - "app_argument_choice_invalid": "Utilitzeu una de les opcions «{choices}» per l'argument «{name}»", + "app_argument_choice_invalid": "Utilitzeu una de les opcions «{choices}» per l'argument «{name}» en lloc de «{value}»", "app_argument_invalid": "Escolliu un valor vàlid per l'argument «{name}»: {error}", "app_argument_required": "Es necessita l'argument '{name}'", "app_change_url_failed_nginx_reload": "No s'ha pogut tornar a carregar NGINX. Aquí teniu el resultat de \"nginx -t\":\n{nginx_errors}", @@ -112,11 +112,11 @@ "certmanager_self_ca_conf_file_not_found": "No s'ha trobat el fitxer de configuració per l'autoritat del certificat auto-signat (fitxer: {file})", "certmanager_unable_to_parse_self_CA_name": "No s'ha pogut analitzar el nom de l'autoritat del certificat auto-signat (fitxer: {file})", "confirm_app_install_warning": "Atenció: Aquesta aplicació funciona, però no està ben integrada amb YunoHost. Algunes característiques com la autenticació única i la còpia de seguretat/restauració poden no estar disponibles. Voleu instal·lar-la de totes maneres? [{answers}] ", - "confirm_app_install_danger": "PERILL! Aquesta aplicació encara és experimental (si no és que no funciona directament)! No hauríeu d'instal·lar-la a no ser que sapigueu el que feu. No obtindreu CAP AJUDA si l'aplicació no funciona o trenca el sistema… Si accepteu el risc, escriviu «{answers}»", + "confirm_app_install_danger": "PERILL! Aquesta aplicació encara és experimental (si no és que no funciona directament)! No hauríeu d'instal·lar-la a no ser que sapigueu el que feu. No obtindreu CAP AJUDA si l'aplicació no funciona o trenca el sistema... Si accepteu el risc, escriviu «{answers}»", "confirm_app_install_thirdparty": "PERILL! Aquesta aplicació no es part del catàleg d'aplicacions de YunoHost. La instal·lació d'aplicacions de terceres parts pot comprometre la integritat i seguretat del seu sistema. No hauríeu d'instal·lar-ne a no ser que sapigueu el que feu. No obtindreu CAP AJUDA si l'aplicació no funciona o trenca el sistema… Si accepteu el risc, escriviu «{answers}»", "custom_app_url_required": "Heu de especificar una URL per actualitzar la vostra aplicació personalitzada {app}", "admin_password_too_long": "Trieu una contrasenya de menys de 127 caràcters", - "dpkg_is_broken": "No es pot fer això en aquest instant perquè dpkg/APT (els gestors de paquets del sistema) sembla estar mal configurat… Podeu intentar solucionar-ho connectant-vos per SSH i executant «sudo apt install --fix-broken» i/o «sudo dpkg --configure -a».", + "dpkg_is_broken": "No es pot fer això en aquest instant perquè dpkg/APT (els gestors de paquets del sistema) sembla estar mal configurat... Podeu intentar solucionar-ho connectant-vos per SSH i executant «sudo apt install --fix-broken» i/o «sudo dpkg --configure -a».", "domain_cannot_remove_main": "No es pot eliminar «{domain}» ja que és el domini principal, primer s'ha d'establir un nou domini principal utilitzant «yunohost domain main-domain -n »; aquí hi ha una llista dels possibles dominis: {other_domains}", "domain_cert_gen_failed": "No s'ha pogut generar el certificat", "domain_created": "S'ha creat el domini", @@ -133,7 +133,7 @@ "domain_unknown": "Domini desconegut", "domains_available": "Dominis disponibles:", "done": "Fet", - "downloading": "Descarregant…", + "downloading": "Descarregant...", "dyndns_could_not_check_provide": "No s'ha pogut verificar si {provider} pot oferir {domain}.", "dyndns_could_not_check_available": "No s'ha pogut verificar la disponibilitat de {domain} a {provider}.", "dyndns_ip_update_failed": "No s'ha pogut actualitzar l'adreça IP al DynDNS", @@ -254,7 +254,7 @@ "regenconf_up_to_date": "La configuració ja està al dia per la categoria «{category}»", "regenconf_updated": "S'ha actualitzat la configuració per la categoria «{category}»", "regenconf_would_be_updated": "La configuració hagués estat actualitzada per la categoria «{category}»", - "regenconf_dry_pending_applying": "Verificació de la configuració pendent que s'hauria d'haver aplicat per la categoria «{category}»…", + "regenconf_dry_pending_applying": "Verificació de la configuració pendent que s'hauria d'haver aplicat per la categoria «{category}»...", "regenconf_failed": "No s'ha pogut regenerar la configuració per la/les categoria/es : {categories}", "regenconf_pending_applying": "Aplicació de la configuració pendent per la categoria «{category}»...", "restore_already_installed_app": "Una aplicació amb la ID «{app}» ja està instal·lada", @@ -262,15 +262,15 @@ "restore_cleaning_failed": "No s'ha pogut netejar el directori temporal de restauració", "restore_complete": "Restauració completada", "restore_confirm_yunohost_installed": "Esteu segur de voler restaurar un sistema ja instal·lat? [{answers}]", - "restore_extracting": "Extracció dels fitxers necessaris de l'arxiu…", + "restore_extracting": "Extracció dels fitxers necessaris de l'arxiu...", "restore_failed": "No s'ha pogut restaurar el sistema", "restore_hook_unavailable": "El script de restauració «{part}» no està disponible en el sistema i tampoc és en l'arxiu", "restore_may_be_not_enough_disk_space": "Sembla que no hi ha prou espai disponible en el sistema (lliure: {free_space} B, espai necessari: {needed_space} B, marge de seguretat: {margin} B)", "restore_not_enough_disk_space": "No hi ha prou espai disponible (espai: {free_space} B, espai necessari: {needed_space} B, marge de seguretat: {margin} B)", "restore_nothings_done": "No s'ha restaurat res", "restore_removing_tmp_dir_failed": "No s'ha pogut eliminar un directori temporal antic", - "restore_running_app_script": "Restaurant l'aplicació «{app}»…", - "restore_running_hooks": "Execució dels hooks de restauració…", + "restore_running_app_script": "Restaurant l'aplicació «{app}»...", + "restore_running_hooks": "Execució dels hooks de restauració...", "restore_system_part_failed": "No s'ha pogut restaurar la part «{part}» del sistema", "root_password_desynchronized": "S'ha canviat la contrasenya d'administració, però YunoHost no ha pogut propagar-ho cap a la contrasenya root!", "root_password_replaced_by_admin_password": "La contrasenya root s'ha substituït per la contrasenya d'administració.", @@ -319,13 +319,13 @@ "system_upgraded": "S'ha actualitzat el sistema", "system_username_exists": "El nom d'usuari ja existeix en la llista d'usuaris de sistema", "this_action_broke_dpkg": "Aquesta acció a trencat dpkg/APT (els gestors de paquets del sistema)... Podeu intentar resoldre el problema connectant-vos amb SSH i executant «sudo apt install --fix-broken» i/o «sudo dpkg --configure -a».", - "tools_upgrade_at_least_one": "Especifiqueu «--apps», o «--system»", + "tools_upgrade_at_least_one": "Especifiqueu «apps», o «system»", "tools_upgrade_cant_both": "No es poden actualitzar tant el sistema com les aplicacions al mateix temps", - "tools_upgrade_cant_hold_critical_packages": "No es poden mantenir els paquets crítics…", - "tools_upgrade_cant_unhold_critical_packages": "No es poden deixar de mantenir els paquets crítics…", - "tools_upgrade_regular_packages": "Actualitzant els paquets «normals» (no relacionats amb YunoHost)…", + "tools_upgrade_cant_hold_critical_packages": "No es poden mantenir els paquets crítics...", + "tools_upgrade_cant_unhold_critical_packages": "No es poden deixar de mantenir els paquets crítics...", + "tools_upgrade_regular_packages": "Actualitzant els paquets «normals» (no relacionats amb YunoHost)...", "tools_upgrade_regular_packages_failed": "No s'han pogut actualitzar els paquets següents: {packages_list}", - "tools_upgrade_special_packages": "Actualitzant els paquets «especials» (relacionats amb YunoHost)…", + "tools_upgrade_special_packages": "Actualitzant els paquets «especials» (relacionats amb YunoHost)...", "tools_upgrade_special_packages_explanation": "Aquesta actualització especial continuarà en segon pla. No comenceu cap altra acció al servidor en els pròxims ~10 minuts (depèn de la velocitat del maquinari). Després d'això, pot ser que us hagueu de tornar a connectar a la interfície d'administració. Els registres de l'actualització estaran disponibles a Eines → Registres (a la interfície d'administració) o utilitzant «yunohost log list» (des de la línia d'ordres).", "tools_upgrade_special_packages_completed": "Actualització dels paquets YunoHost acabada.\nPremeu [Enter] per tornar a la línia d'ordres", "unbackup_app": "{app} no es guardarà", @@ -345,7 +345,7 @@ "user_creation_failed": "No s'ha pogut crear l'usuari {user}: {error}", "user_deleted": "S'ha suprimit l'usuari", "user_deletion_failed": "No s'ha pogut suprimir l'usuari {user}: {error}", - "user_home_creation_failed": "No s'ha pogut crear la carpeta personal «home» per l'usuari", + "user_home_creation_failed": "No s'ha pogut crear la carpeta personal «{home}» per l'usuari", "user_unknown": "Usuari desconegut: {user}", "user_update_failed": "No s'ha pogut actualitzar l'usuari {user}: {error}", "user_updated": "S'ha canviat la informació de l'usuari", @@ -472,7 +472,7 @@ "diagnosis_http_unreachable": "Sembla que el domini {domain} no és accessible a través de HTTP des de fora de la xarxa local.", "diagnosis_unknown_categories": "Les següents categories són desconegudes: {categories}", "apps_catalog_init_success": "S'ha iniciat el sistema de catàleg d'aplicacions!", - "apps_catalog_updating": "S'està actualitzant el catàleg d'aplicacions…", + "apps_catalog_updating": "S'està actualitzant el catàleg d'aplicacions...", "apps_catalog_failed_to_download": "No s'ha pogut descarregar el catàleg d'aplicacions {apps_catalog}: {error}", "apps_catalog_obsolete_cache": "La memòria cau del catàleg d'aplicacions és buida o obsoleta.", "apps_catalog_update_success": "S'ha actualitzat el catàleg d'aplicacions!", @@ -485,7 +485,7 @@ "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.
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.", + "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.", "diagnosis_services_running": "El servei {service} s'està executant!", "diagnosis_services_conf_broken": "La configuració pel servei {service} està trencada!", "diagnosis_ports_needed_by": "És necessari exposar aquest port per a les funcions {category} (servei {service})", From d5934ce7d3e1c9bebcc44a4cd03c65f6dc426e86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= Date: Sun, 19 Sep 2021 13:11:03 +0000 Subject: [PATCH 3126/3170] Translated using Weblate (German) Currently translated at 92.7% (629 of 678 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/locales/de.json b/locales/de.json index 6ddc1284d..ac9549c2e 100644 --- a/locales/de.json +++ b/locales/de.json @@ -49,7 +49,7 @@ "domain_uninstall_app_first": "Diese Applikationen sind noch auf Ihrer Domäne installiert; \n{apps}\n\nBitte deinstallieren Sie sie mit dem Befehl 'yunohost app remove the_app_id' oder verschieben Sie sie mit 'yunohost app change-url the_app_id'", "domain_unknown": "Unbekannte Domain", "done": "Erledigt", - "downloading": "Wird heruntergeladen…", + "downloading": "Wird heruntergeladen...", "dyndns_ip_update_failed": "Konnte die IP-Adresse für DynDNS nicht aktualisieren", "dyndns_ip_updated": "Aktualisierung Ihrer IP-Adresse bei DynDNS", "dyndns_key_generating": "Generierung des DNS-Schlüssels..., das könnte eine Weile dauern.", @@ -92,8 +92,8 @@ "restore_failed": "Das System konnte nicht wiederhergestellt werden", "restore_hook_unavailable": "Das Wiederherstellungsskript für '{part}' steht weder in Ihrem System noch im Archiv zur Verfügung", "restore_nothings_done": "Nichts wurde wiederhergestellt", - "restore_running_app_script": "App '{app}' wird wiederhergestellt…", - "restore_running_hooks": "Wiederherstellung wird gestartet…", + "restore_running_app_script": "App '{app}' wird wiederhergestellt...", + "restore_running_hooks": "Wiederherstellung wird gestartet...", "service_add_failed": "Der Dienst '{service}' konnte nicht hinzugefügt werden", "service_added": "Der Dienst '{service}' wurde erfolgreich hinzugefügt", "service_already_started": "Der Dienst '{service}' läuft bereits", @@ -241,7 +241,7 @@ "log_operation_unit_unclosed_properly": "Die Operationseinheit wurde nicht richtig geschlossen", "global_settings_setting_security_postfix_compatibility": "Kompatibilitäts- vs. Sicherheits-Kompromiss für den Postfix-Server. Betrifft die Ciphers (und andere sicherheitsrelevante Aspekte)", "global_settings_unknown_type": "Unerwartete Situation, die Einstellung {setting} scheint den Typ {unknown_type} zu haben, ist aber kein vom System unterstützter Typ.", - "dpkg_is_broken": "Du kannst das gerade nicht tun, weil dpkg/APT (der Systempaketmanager) in einem defekten Zustand zu sein scheint.... Du kannst versuchen, dieses Problem zu lösen, indem du dich über SSH verbindest und `sudo apt install --fix-broken` sowie/oder `sudo dpkg --configure -a` ausführst.", + "dpkg_is_broken": "Du kannst das gerade nicht tun, weil dpkg/APT (der Systempaketmanager) in einem defekten Zustand zu sein scheint... Du kannst versuchen, dieses Problem zu lösen, indem du dich über SSH verbindest und `sudo apt install --fix-broken` sowie/oder `sudo dpkg --configure -a` ausführst.", "global_settings_unknown_setting_from_settings_file": "Unbekannter Schlüssel in den Einstellungen: '{setting_key}', verwerfen und speichern in /etc/yunohost/settings-unknown.json", "log_link_to_log": "Vollständiges Log dieser Operation: '{desc}'", "log_help_to_get_log": "Um das Protokoll der Operation '{desc}' anzuzeigen, verwenden Sie den Befehl 'yunohost log show {name}'", @@ -279,7 +279,7 @@ "diagnosis_basesystem_ynh_main_version": "Server läuft YunoHost {main_version} ({repo})", "diagnosis_basesystem_ynh_inconsistent_versions": "Sie verwenden inkonsistente Versionen der YunoHost-Pakete... wahrscheinlich wegen eines fehlgeschlagenen oder teilweisen Upgrades.", "apps_catalog_init_success": "App-Katalogsystem initialisiert!", - "apps_catalog_updating": "Aktualisierung des Applikationskatalogs…", + "apps_catalog_updating": "Aktualisierung des Applikationskatalogs...", "apps_catalog_failed_to_download": "Der {apps_catalog} App-Katalog kann nicht heruntergeladen werden: {error}", "apps_catalog_obsolete_cache": "Der Cache des App-Katalogs ist leer oder veraltet.", "apps_catalog_update_success": "Der Apps-Katalog wurde aktualisiert!", @@ -566,13 +566,13 @@ "regenconf_updated": "Konfiguration aktualisiert für '{category}'", "regenconf_pending_applying": "Wende die anstehende Konfiguration für die Kategorie {category} an...", "regenconf_failed": "Konnte die Konfiguration für die Kategorie(n) {categories} nicht neu erstellen", - "regenconf_dry_pending_applying": "Überprüfe die anstehende Konfiguration, welche für die Kategorie {category}' aktualisiert worden wäre…", + "regenconf_dry_pending_applying": "Überprüfe die anstehende Konfiguration, welche für die Kategorie {category}' aktualisiert worden wäre...", "regenconf_would_be_updated": "Die Konfiguration wäre für die Kategorie '{category}' aktualisiert worden", "restore_system_part_failed": "Die Systemteile '{part}' konnten nicht wiederhergestellt werden", "restore_removing_tmp_dir_failed": "Ein altes, temporäres Directory konnte nicht entfernt werden", "restore_not_enough_disk_space": "Nicht genug Speicher (Speicher: {free_space} B, benötigter Speicher: {needed_space} B, Sicherheitspuffer: {margin} B)", "restore_may_be_not_enough_disk_space": "Ihr System scheint nicht genug Speicherplatz zu haben (frei: {free_space} B, benötigter Platz: {needed_space} B, Sicherheitspuffer: {margin} B)", - "restore_extracting": "Packe die benötigten Dateien aus dem Archiv aus…", + "restore_extracting": "Packe die benötigten Dateien aus dem Archiv aus...", "restore_already_installed_apps": "Folgende Apps können nicht wiederhergestellt werden, weil sie schon installiert sind: {apps}", "regex_with_only_domain": "Du kannst regex nicht als Domain verwenden, sondern nur als Pfad", "root_password_desynchronized": "Das Admin-Passwort wurde verändert, aber das Root-Passwort ist immer noch das alte!", @@ -617,16 +617,16 @@ "show_tile_cant_be_enabled_for_regex": "Momentan können Sie 'show_tile' nicht aktivieren, weil die URL für die Berechtigung '{permission}' ein regulärer Ausdruck ist", "show_tile_cant_be_enabled_for_url_not_defined": "Momentan können Sie 'show_tile' nicht aktivieren, weil Sie zuerst eine URL für die Berechtigung '{permission}' definieren müssen", "tools_upgrade_regular_packages_failed": "Konnte für die folgenden Pakete das Upgrade nicht durchführen: {packages_list}", - "tools_upgrade_regular_packages": "Momentan werden Upgrades für das System (YunoHost-unabhängige) Pakete durchgeführt…", - "tools_upgrade_cant_unhold_critical_packages": "Konnte für die kritischen Pakete das Flag 'hold' nicht aufheben…", - "tools_upgrade_cant_hold_critical_packages": "Konnte für die kritischen Pakete das Flag 'hold' nicht setzen…", + "tools_upgrade_regular_packages": "Momentan werden Upgrades für das System (YunoHost-unabhängige) Pakete durchgeführt...", + "tools_upgrade_cant_unhold_critical_packages": "Konnte für die kritischen Pakete das Flag 'hold' nicht aufheben...", + "tools_upgrade_cant_hold_critical_packages": "Konnte für die kritischen Pakete das Flag 'hold' nicht setzen...", "tools_upgrade_cant_both": "Kann das Upgrade für das System und die Applikation nicht gleichzeitig durchführen", "tools_upgrade_at_least_one": "Bitte geben Sie '--apps' oder '--system' an", "this_action_broke_dpkg": "Diese Aktion hat unkonfigurierte Pakete verursacht, welche durch dpkg/apt (die Paketverwaltungen dieses Systems) zurückgelassen wurden... Sie können versuchen dieses Problem zu lösen, indem Sie 'sudo apt install --fix-broken' und/oder 'sudo dpkg --configure -a' ausführen.", "update_apt_cache_failed": "Kann den Cache von APT (Debians Paketmanager) nicht aktualisieren. Hier ist ein Auszug aus den sources.list-Zeilen, die helfen könnten, das Problem zu identifizieren:\n{sourceslist}", "tools_upgrade_special_packages_completed": "YunoHost-Paketupdate beendet.\nDrücke [Enter], um zurück zur Kommandoziele zu kommen", "tools_upgrade_special_packages_explanation": "Das Upgrade \"special\" wird im Hintergrund ausgeführt. Bitte starten Sie keine anderen Aktionen auf Ihrem Server für die nächsten ~10 Minuten. Die Dauer ist abhängig von der Geschwindigkeit Ihres Servers. Nach dem Upgrade müssen Sie sich eventuell erneut in das Adminportal einloggen. Upgrade-Logs sind im Adminbereich unter Tools → Log verfügbar. Alternativ können Sie in der Befehlszeile 'yunohost log list' eingeben.", - "tools_upgrade_special_packages": "\"special\" (YunoHost-bezogene) Pakete werden jetzt aktualisiert…", + "tools_upgrade_special_packages": "\"special\" (YunoHost-bezogene) Pakete werden jetzt aktualisiert...", "unknown_main_domain_path": "Unbekannte:r Domain oder Pfad für '{app}'. Du musst eine Domain und einen Pfad setzen, um die URL für Berechtigungen zu setzen.", "yunohost_postinstall_end_tip": "Post-install ist fertig! Um das Setup abzuschliessen, wird empfohlen:\n - einen ersten Benutzer über den Bereich 'Benutzer*in' im Adminbereich hinzuzufügen (oder mit 'yunohost user create ' in der Kommandezeile);\n - mögliche Fehler zu diagnostizieren über den Bereich 'Diagnose' im Adminbereich (oder mit 'yunohost diagnosis run' in der Kommandozeile;\n - Die Abschnitte 'Install YunoHost' und 'Geführte Tour' im Administratorenhandbuch zu lesen: https://yunohost.org/admindoc.", "user_already_exists": "Der Benutzer '{user}' ist bereits vorhanden", @@ -634,5 +634,6 @@ "global_settings_setting_security_webadmin_allowlist": "IP-Adressen, die auf die Verwaltungsseite zugreifen dürfen. Kommasepariert.", "global_settings_setting_security_webadmin_allowlist_enabled": "Erlaube nur bestimmten IP-Adressen den Zugriff auf die Verwaltungsseite.", "disk_space_not_sufficient_update": "Es ist nicht genügend Speicherplatz frei, um diese Applikation zu aktuallisieren", - "disk_space_not_sufficient_install": "Es ist nicht genügend Speicherplatz frei, um diese Applikation zu installieren" + "disk_space_not_sufficient_install": "Es ist nicht genügend Speicherplatz frei, um diese Applikation zu installieren", + "danger": "Warnung:" } From 0683db987e4e7ccf0f119c19103a4835f9641e49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= Date: Sun, 19 Sep 2021 13:09:37 +0000 Subject: [PATCH 3127/3170] Translated using Weblate (French) Currently translated at 100.0% (678 of 678 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 419917b28..095c837cb 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -356,7 +356,7 @@ "tools_upgrade_special_packages_explanation": "La mise à niveau spécifique à YunoHost se poursuivra en arrière-plan. Veuillez ne pas lancer d'autres actions sur votre serveur pendant les 10 prochaines minutes (selon la vitesse du matériel). Après cela, vous devrez peut-être vous reconnecter à la webadmin. Le journal de mise à niveau sera disponible dans Outils → Journal (dans le webadmin) ou en utilisant 'yunohost log list' (à partir de la ligne de commande).", "update_apt_cache_failed": "Impossible de mettre à jour le cache APT (gestionnaire de paquets Debian). Voici un extrait du fichier sources.list qui pourrait vous aider à identifier les lignes problématiques :\n{sourceslist}", "update_apt_cache_warning": "Des erreurs se sont produites lors de la mise à jour du cache APT (gestionnaire de paquets Debian). Voici un extrait des lignes du fichier sources.list qui pourrait vous aider à identifier les lignes problématiques :\n{sourceslist}", - "backup_permission": "Sauvegarde des permissions pour {app}", + "backup_permission": "Permission de sauvegarde pour {app}", "group_created": "Le groupe '{group}' a été créé", "group_deleted": "Suppression du groupe '{group}'", "group_unknown": "Le groupe {group} est inconnu", From 11f8bf706ddbba86b65c0128bf8f4140f763e104 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 19 Sep 2021 23:04:38 +0200 Subject: [PATCH 3128/3170] Apply suggestions from code review --- locales/fr.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 095c837cb..61157c0ed 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -4,7 +4,7 @@ "admin_password_change_failed": "Impossible de changer le mot de passe", "admin_password_changed": "Le mot de passe d'administration a été modifié", "app_already_installed": "{app} est déjà installé", - "app_argument_choice_invalid": "Choix invalide pour le paramètre '{name}', il doit être l'un de {choices} au lieu de '{value}'", + "app_argument_choice_invalid": "Choix invalide pour le paramètre '{name}'. Les valeurs acceptées sont {choices}, au lieu de '{value}'", "app_argument_invalid": "Valeur invalide pour le paramètre '{name}' : {error}", "app_argument_required": "Le paramètre '{name}' est requis", "app_extraction_failed": "Impossible d'extraire les fichiers d'installation", @@ -678,7 +678,7 @@ "invalid_number_min": "Doit être supérieur à {min}", "invalid_number_max": "Doit être inférieur à {max}", "log_app_config_set": "Appliquer la configuration à l'application '{}'", - "service_not_reloading_because_conf_broken": "Le service '{name}' n'est pas rechargé/redémarré car sa configuration est cassée : {errors}", + "service_not_reloading_because_conf_broken": "Le service '{name}' n'a pas été rechargé/redémarré car sa configuration est cassée : {errors}", "app_argument_password_help_keep": "Tapez sur Entrée pour conserver la valeur actuelle", "app_argument_password_help_optional": "Tapez un espace pour vider le mot de passe" } From 4d3692a70b40b324a5096a85464b28ac78190f5e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 19 Sep 2021 23:08:27 +0200 Subject: [PATCH 3129/3170] Black --- data/hooks/diagnosis/12-dnsrecords.py | 13 +- data/hooks/diagnosis/24-mail.py | 10 +- src/yunohost/backup.py | 12 +- src/yunohost/dns.py | 225 +++++++++++++++++--------- src/yunohost/domain.py | 29 ++-- src/yunohost/dyndns.py | 4 +- src/yunohost/tests/conftest.py | 1 + src/yunohost/tests/test_dns.py | 17 +- src/yunohost/tests/test_domains.py | 6 +- src/yunohost/tests/test_questions.py | 12 +- src/yunohost/utils/config.py | 4 +- src/yunohost/utils/dns.py | 1 - src/yunohost/utils/ldap.py | 2 +- tests/test_i18n_keys.py | 4 +- 14 files changed, 219 insertions(+), 121 deletions(-) diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py index e3cbe7078..16841721f 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -86,7 +86,11 @@ class DNSRecordsDiagnoser(Diagnoser): # Ugly hack to not check mail records for subdomains stuff, otherwise will end up in a shitstorm of errors for people with many subdomains... # Should find a cleaner solution in the suggested conf... - if r["type"] in ["MX", "TXT"] and fqdn not in [domain, f'mail._domainkey.{domain}', f'_dmarc.{domain}']: + if r["type"] in ["MX", "TXT"] and fqdn not in [ + domain, + f"mail._domainkey.{domain}", + f"_dmarc.{domain}", + ]: continue r["current"] = self.get_current_record(fqdn, r["type"]) @@ -112,7 +116,10 @@ class DNSRecordsDiagnoser(Diagnoser): # A bad or missing A record is critical ... # And so is a wrong AAAA record # (However, a missing AAAA record is acceptable) - if results[f"A:{basename}"] != "OK" or results[f"AAAA:{basename}"] == "WRONG": + if ( + results[f"A:{basename}"] != "OK" + or results[f"AAAA:{basename}"] == "WRONG" + ): return True return False @@ -175,7 +182,7 @@ class DNSRecordsDiagnoser(Diagnoser): ) # For SPF, ignore parts starting by ip4: or ip6: - if 'v=spf1' in r["value"]: + if "v=spf1" in r["value"]: current = { part for part in current diff --git a/data/hooks/diagnosis/24-mail.py b/data/hooks/diagnosis/24-mail.py index 266678557..c5af4bbc6 100644 --- a/data/hooks/diagnosis/24-mail.py +++ b/data/hooks/diagnosis/24-mail.py @@ -35,11 +35,11 @@ class MailDiagnoser(Diagnoser): # TODO check that the recent mail logs are not filled with thousand of email sending (unusual number of mail sent) # TODO check for unusual failed sending attempt being refused in the logs ? checks = [ - "check_outgoing_port_25", # i18n: diagnosis_mail_outgoing_port_25_ok - "check_ehlo", # i18n: diagnosis_mail_ehlo_ok - "check_fcrdns", # i18n: diagnosis_mail_fcrdns_ok - "check_blacklist", # i18n: diagnosis_mail_blacklist_ok - "check_queue", # i18n: diagnosis_mail_queue_ok + "check_outgoing_port_25", # i18n: diagnosis_mail_outgoing_port_25_ok + "check_ehlo", # i18n: diagnosis_mail_ehlo_ok + "check_fcrdns", # i18n: diagnosis_mail_fcrdns_ok + "check_blacklist", # i18n: diagnosis_mail_blacklist_ok + "check_queue", # i18n: diagnosis_mail_queue_ok ] for check in checks: self.logger_debug("Running " + check) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 7b580e424..5089c5979 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -708,7 +708,9 @@ class BackupManager: # Prepare environment env_dict = self._get_env_var(app) - env_dict["YNH_APP_BASEDIR"] = os.path.join(self.work_dir, "apps", app, "settings") + env_dict["YNH_APP_BASEDIR"] = os.path.join( + self.work_dir, "apps", app, "settings" + ) tmp_app_bkp_dir = env_dict["YNH_APP_BACKUP_DIR"] settings_dir = os.path.join(self.work_dir, "apps", app, "settings") @@ -1491,7 +1493,9 @@ class RestoreManager: "YNH_APP_BACKUP_DIR": os.path.join( self.work_dir, "apps", app_instance_name, "backup" ), - "YNH_APP_BASEDIR": os.path.join(self.work_dir, "apps", app_instance_name, "settings"), + "YNH_APP_BASEDIR": os.path.join( + self.work_dir, "apps", app_instance_name, "settings" + ), } ) @@ -1529,7 +1533,9 @@ class RestoreManager: # Setup environment for remove script env_dict_remove = _make_environment_for_app_script(app_instance_name) - env_dict_remove["YNH_APP_BASEDIR"] = os.path.join(self.work_dir, "apps", app_instance_name, "settings") + env_dict_remove["YNH_APP_BASEDIR"] = os.path.join( + self.work_dir, "apps", app_instance_name, "settings" + ) remove_operation_logger = OperationLogger( "remove_on_failed_restore", diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index 745578806..0581fa82c 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -34,7 +34,13 @@ from moulinette import m18n, Moulinette from moulinette.utils.log import getActionLogger from moulinette.utils.filesystem import read_file, write_to_file, read_toml -from yunohost.domain import domain_list, _assert_domain_exists, domain_config_get, _get_domain_settings, _set_domain_settings +from yunohost.domain import ( + domain_list, + _assert_domain_exists, + domain_config_get, + _get_domain_settings, + _set_domain_settings, +) from yunohost.utils.dns import dig, YNH_DYNDNS_DOMAINS from yunohost.utils.error import YunohostValidationError, YunohostError from yunohost.utils.network import get_public_ip @@ -163,13 +169,18 @@ def _build_dns_conf(base_domain, include_empty_AAAA_if_no_ipv6=False): # If this is a ynh_dyndns_domain, we're not gonna include all the subdomains in the conf # Because dynette only accept a specific list of name/type # And the wildcard */A already covers the bulk of use cases - if any(base_domain.endswith("." + ynh_dyndns_domain) for ynh_dyndns_domain in YNH_DYNDNS_DOMAINS): + if any( + base_domain.endswith("." + ynh_dyndns_domain) + for ynh_dyndns_domain in YNH_DYNDNS_DOMAINS + ): subdomains = [] else: subdomains = _list_subdomains_of(base_domain) - domains_settings = {domain: domain_config_get(domain, export=True) - for domain in [base_domain] + subdomains} + domains_settings = { + domain: domain_config_get(domain, export=True) + for domain in [base_domain] + subdomains + } base_dns_zone = _get_dns_zone_for_domain(base_domain) @@ -186,7 +197,7 @@ def _build_dns_conf(base_domain, include_empty_AAAA_if_no_ipv6=False): basename = domain.replace(base_dns_zone, "").rstrip(".") or "@" suffix = f".{basename}" if basename != "@" else "" - #ttl = settings["ttl"] + # ttl = settings["ttl"] ttl = 3600 ########################### @@ -416,7 +427,7 @@ def _get_dns_zone_for_domain(domain): # This is mainly meant to speed up things for "dyndns update" # ... otherwise we end up constantly doing a bunch of dig requests for ynh_dyndns_domain in YNH_DYNDNS_DOMAINS: - if domain.endswith('.' + ynh_dyndns_domain): + if domain.endswith("." + ynh_dyndns_domain): return ynh_dyndns_domain # Check cache @@ -453,8 +464,7 @@ def _get_dns_zone_for_domain(domain): # baz.gni # gni # Until we find the first one that has a NS record - parent_list = [domain.split(".", i)[-1] - for i, _ in enumerate(domain.split("."))] + parent_list = [domain.split(".", i)[-1] for i, _ in enumerate(domain.split("."))] for parent in parent_list: @@ -470,7 +480,9 @@ def _get_dns_zone_for_domain(domain): else: zone = parent_list[-1] - logger.warning(f"Could not identify the dns zone for domain {domain}, returning {zone}") + logger.warning( + f"Could not identify the dns zone for domain {domain}, returning {zone}" + ) return zone @@ -492,50 +504,66 @@ def _get_registrar_config_section(domain): else: parent_domain_link = parent_domain - registrar_infos["registrar"] = OrderedDict({ - "type": "alert", - "style": "info", - "ask": m18n.n("domain_dns_registrar_managed_in_parent_domain", parent_domain=domain, parent_domain_link=parent_domain_link), - "value": "parent_domain" - }) + registrar_infos["registrar"] = OrderedDict( + { + "type": "alert", + "style": "info", + "ask": m18n.n( + "domain_dns_registrar_managed_in_parent_domain", + parent_domain=domain, + parent_domain_link=parent_domain_link, + ), + "value": "parent_domain", + } + ) return OrderedDict(registrar_infos) # TODO big project, integrate yunohost's dynette as a registrar-like provider # TODO big project, integrate other dyndns providers such as netlib.re, or cf the list of dyndns providers supported by cloudron... if dns_zone in YNH_DYNDNS_DOMAINS: - registrar_infos["registrar"] = OrderedDict({ - "type": "alert", - "style": "success", - "ask": m18n.n("domain_dns_registrar_yunohost"), - "value": "yunohost" - }) + registrar_infos["registrar"] = OrderedDict( + { + "type": "alert", + "style": "success", + "ask": m18n.n("domain_dns_registrar_yunohost"), + "value": "yunohost", + } + ) return OrderedDict(registrar_infos) try: registrar = _relevant_provider_for_domain(dns_zone)[0] except ValueError: - registrar_infos["registrar"] = OrderedDict({ - "type": "alert", - "style": "warning", - "ask": m18n.n("domain_dns_registrar_not_supported"), - "value": None - }) + registrar_infos["registrar"] = OrderedDict( + { + "type": "alert", + "style": "warning", + "ask": m18n.n("domain_dns_registrar_not_supported"), + "value": None, + } + ) else: - registrar_infos["registrar"] = OrderedDict({ - "type": "alert", - "style": "info", - "ask": m18n.n("domain_dns_registrar_supported", registrar=registrar), - "value": registrar - }) + registrar_infos["registrar"] = OrderedDict( + { + "type": "alert", + "style": "info", + "ask": m18n.n("domain_dns_registrar_supported", registrar=registrar), + "value": registrar, + } + ) TESTED_REGISTRARS = ["ovh", "gandi"] if registrar not in TESTED_REGISTRARS: - registrar_infos["experimental_disclaimer"] = OrderedDict({ - "type": "alert", - "style": "danger", - "ask": m18n.n("domain_dns_registrar_experimental", registrar=registrar), - }) + registrar_infos["experimental_disclaimer"] = OrderedDict( + { + "type": "alert", + "style": "danger", + "ask": m18n.n( + "domain_dns_registrar_experimental", registrar=registrar + ), + } + ) # TODO : add a help tip with the link to the registar's API doc (c.f. Lexicon's README) registrar_list = read_toml(DOMAIN_REGISTRAR_LIST_PATH) @@ -552,7 +580,7 @@ def _get_registar_settings(domain): _assert_domain_exists(domain) - settings = domain_config_get(domain, key='dns.registrar', export=True) + settings = domain_config_get(domain, key="dns.registrar", export=True) registrar = settings.pop("registrar") @@ -587,12 +615,20 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge= parent_domain = domain.split(".", 1)[1] registar, registrar_credentials = _get_registar_settings(parent_domain) if any(registrar_credentials.values()): - raise YunohostValidationError("domain_dns_push_managed_in_parent_domain", domain=domain, parent_domain=parent_domain) + raise YunohostValidationError( + "domain_dns_push_managed_in_parent_domain", + domain=domain, + parent_domain=parent_domain, + ) else: - raise YunohostValidationError("domain_registrar_is_not_configured", domain=parent_domain) + raise YunohostValidationError( + "domain_registrar_is_not_configured", domain=parent_domain + ) if not all(registrar_credentials.values()): - raise YunohostValidationError("domain_registrar_is_not_configured", domain=domain) + raise YunohostValidationError( + "domain_registrar_is_not_configured", domain=domain + ) base_dns_zone = _get_dns_zone_for_domain(domain) @@ -603,7 +639,11 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge= for record in records: # Make sure the name is a FQDN - name = f"{record['name']}.{base_dns_zone}" if record["name"] != "@" else base_dns_zone + name = ( + f"{record['name']}.{base_dns_zone}" + if record["name"] != "@" + else base_dns_zone + ) type_ = record["type"] content = record["value"] @@ -611,19 +651,16 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge= if content == "@" and record["type"] == "CNAME": content = base_dns_zone + "." - wanted_records.append({ - "name": name, - "type": type_, - "ttl": record["ttl"], - "content": content - }) + wanted_records.append( + {"name": name, "type": type_, "ttl": record["ttl"], "content": content} + ) # FIXME Lexicon does not support CAA records # See https://github.com/AnalogJ/lexicon/issues/282 and https://github.com/AnalogJ/lexicon/pull/371 # They say it's trivial to implement it! # And yet, it is still not done/merged # Update by Aleks: it works - at least with Gandi ?! - #wanted_records = [record for record in wanted_records if record["type"] != "CAA"] + # wanted_records = [record for record in wanted_records if record["type"] != "CAA"] if purge: wanted_records = [] @@ -634,7 +671,7 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge= base_config = { "provider_name": registrar, "domain": base_dns_zone, - registrar: registrar_credentials + registrar: registrar_credentials, } # Ugly hack to be able to fetch all record types at once: @@ -643,15 +680,17 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge= # then trigger ourselves the authentication + list_records # instead of calling .execute() query = ( - LexiconConfigResolver() - .with_dict(dict_object=base_config) - .with_dict(dict_object={"action": "list", "type": "all"}) + LexiconConfigResolver() + .with_dict(dict_object=base_config) + .with_dict(dict_object={"action": "list", "type": "all"}) ) client = LexiconClient(query) try: client.provider.authenticate() except Exception as e: - raise YunohostValidationError("domain_dns_push_failed_to_authenticate", domain=domain, error=str(e)) + raise YunohostValidationError( + "domain_dns_push_failed_to_authenticate", domain=domain, error=str(e) + ) try: current_records = client.provider.list_records() @@ -666,7 +705,11 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge= # Ignore records which are for a higher-level domain # i.e. we don't care about the records for domain.tld when pushing yuno.domain.tld - current_records = [r for r in current_records if r['name'].endswith(f'.{domain}') or r['name'] == domain] + current_records = [ + r + for r in current_records + if r["name"].endswith(f".{domain}") or r["name"] == domain + ] for record in current_records: @@ -674,7 +717,11 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge= record["name"] = record["name"].strip("@").strip(".") # Some API return '@' in content and we shall convert it to absolute/fqdn - record["content"] = record["content"].replace('@.', base_dns_zone + ".").replace('@', base_dns_zone + ".") + record["content"] = ( + record["content"] + .replace("@.", base_dns_zone + ".") + .replace("@", base_dns_zone + ".") + ) if record["type"] == "TXT": if not record["content"].startswith('"'): @@ -683,7 +730,9 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge= record["content"] = record["content"] + '"' # Check if this record was previously set by YunoHost - record["managed_by_yunohost"] = _hash_dns_record(record) in managed_dns_records_hashes + record["managed_by_yunohost"] = ( + _hash_dns_record(record) in managed_dns_records_hashes + ) # Step 0 : Get the list of unique (type, name) # And compare the current and wanted records @@ -699,8 +748,12 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge= # (SRV, .domain.tld) 0 5 5269 domain.tld changes = {"delete": [], "update": [], "create": [], "unchanged": []} - type_and_names = sorted(set([(r["type"], r["name"]) for r in current_records + wanted_records])) - comparison = {type_and_name: {"current": [], "wanted": []} for type_and_name in type_and_names} + type_and_names = sorted( + set([(r["type"], r["name"]) for r in current_records + wanted_records]) + ) + comparison = { + type_and_name: {"current": [], "wanted": []} for type_and_name in type_and_names + } for record in current_records: comparison[(record["type"], record["name"])]["current"].append(record) @@ -748,7 +801,9 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge= def likeliness(r): # We compute this only on the first 100 chars, to have a high value even for completely different DKIM keys - return SequenceMatcher(None, r["content"][:100], record["content"][:100]).ratio() + return SequenceMatcher( + None, r["content"][:100], record["content"][:100] + ).ratio() matches = sorted(current, key=lambda r: likeliness(r), reverse=True) if matches and likeliness(matches[0]) > 0.50: @@ -770,7 +825,7 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge= def relative_name(name): name = name.strip(".") - name = name.replace('.' + base_dns_zone, "") + name = name.replace("." + base_dns_zone, "") name = name.replace(base_dns_zone, "@") return name @@ -780,24 +835,30 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge= t = record["type"] if not force and action in ["update", "delete"]: - ignored = "" if record["managed_by_yunohost"] else "(ignored, won't be changed by Yunohost unless forced)" + ignored = ( + "" + if record["managed_by_yunohost"] + else "(ignored, won't be changed by Yunohost unless forced)" + ) else: ignored = "" if action == "create": old_content = record.get("old_content", "(None)")[:30] new_content = record.get("content", "(None)")[:30] - return f'{name:>20} [{t:^5}] {new_content:^30} {ignored}' + return f"{name:>20} [{t:^5}] {new_content:^30} {ignored}" elif action == "update": old_content = record.get("old_content", "(None)")[:30] new_content = record.get("content", "(None)")[:30] - return f'{name:>20} [{t:^5}] {old_content:^30} -> {new_content:^30} {ignored}' + return ( + f"{name:>20} [{t:^5}] {old_content:^30} -> {new_content:^30} {ignored}" + ) elif action == "unchanged": old_content = new_content = record.get("content", "(None)")[:30] - return f'{name:>20} [{t:^5}] {old_content:^30}' + return f"{name:>20} [{t:^5}] {old_content:^30}" else: old_content = record.get("content", "(None)")[:30] - return f'{name:>20} [{t:^5}] {old_content:^30} {ignored}' + return f"{name:>20} [{t:^5}] {old_content:^30} {ignored}" if dry_run: if Moulinette.interface.type == "api": @@ -852,8 +913,10 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge= for record in changes[action]: - relative_name = record['name'].replace(base_dns_zone, '').rstrip('.') or '@' - progress(f"{action} {record['type']:^5} / {relative_name}") # FIXME: i18n but meh + relative_name = record["name"].replace(base_dns_zone, "").rstrip(".") or "@" + progress( + f"{action} {record['type']:^5} / {relative_name}" + ) # FIXME: i18n but meh # Apparently Lexicon yields us some 'id' during fetch # But wants 'identifier' during push ... @@ -865,8 +928,12 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge= if record["name"] == base_dns_zone: record["name"] = "@." + record["name"] if record["type"] in ["MX", "SRV", "CAA"]: - logger.warning(f"Pushing {record['type']} records is not properly supported by Lexicon/Godaddy.") - results["warnings"].append(f"Pushing {record['type']} records is not properly supported by Lexicon/Godaddy.") + logger.warning( + f"Pushing {record['type']} records is not properly supported by Lexicon/Godaddy." + ) + results["warnings"].append( + f"Pushing {record['type']} records is not properly supported by Lexicon/Godaddy." + ) continue record["action"] = action @@ -879,14 +946,26 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge= try: result = LexiconClient(query).execute() except Exception as e: - msg = m18n.n("domain_dns_push_record_failed", action=action, type=record['type'], name=record['name'], error=str(e)) + msg = m18n.n( + "domain_dns_push_record_failed", + action=action, + type=record["type"], + name=record["name"], + error=str(e), + ) logger.error(msg) results["errors"].append(msg) else: if result: new_managed_dns_records_hashes.append(_hash_dns_record(record)) else: - msg = m18n.n("domain_dns_push_record_failed", action=action, type=record['type'], name=record['name'], error="unkonwn error?") + msg = m18n.n( + "domain_dns_push_record_failed", + action=action, + type=record["type"], + name=record["name"], + error="unkonwn error?", + ) logger.error(msg) results["errors"].append(msg) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index a5db7c7ab..5c67fce1a 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -36,9 +36,7 @@ from yunohost.app import ( _get_app_settings, _get_conflicting_apps, ) -from yunohost.regenconf import ( - regen_conf, _force_clear_hashes, _process_regen_conf -) +from yunohost.regenconf import regen_conf, _force_clear_hashes, _process_regen_conf from yunohost.utils.config import ConfigPanel, Question from yunohost.utils.error import YunohostError, YunohostValidationError from yunohost.log import is_unit_operation @@ -392,13 +390,15 @@ def _get_maindomain(): return maindomain -def domain_config_get(domain, key='', full=False, export=False): +def domain_config_get(domain, key="", full=False, export=False): """ Display a domain configuration """ if full and export: - raise YunohostValidationError("You can't use --full and --export together.", raw_msg=True) + raise YunohostValidationError( + "You can't use --full and --export together.", raw_msg=True + ) if full: mode = "full" @@ -412,7 +412,9 @@ def domain_config_get(domain, key='', full=False, export=False): @is_unit_operation() -def domain_config_set(operation_logger, domain, key=None, value=None, args=None, args_file=None): +def domain_config_set( + operation_logger, domain, key=None, value=None, args=None, args_file=None +): """ Apply a new domain configuration """ @@ -422,14 +424,13 @@ def domain_config_set(operation_logger, domain, key=None, value=None, args=None, class DomainConfigPanel(ConfigPanel): - def __init__(self, domain): _assert_domain_exists(domain) self.domain = domain self.save_mode = "diff" super().__init__( config_path=DOMAIN_CONFIG_PATH, - save_path=f"{DOMAIN_SETTINGS_DIR}/{domain}.yml" + save_path=f"{DOMAIN_SETTINGS_DIR}/{domain}.yml", ) def _get_toml(self): @@ -437,12 +438,14 @@ class DomainConfigPanel(ConfigPanel): toml = super()._get_toml() - toml['feature']['xmpp']['xmpp']['default'] = 1 if self.domain == _get_maindomain() else 0 - toml['dns']['registrar'] = _get_registrar_config_section(self.domain) + toml["feature"]["xmpp"]["xmpp"]["default"] = ( + 1 if self.domain == _get_maindomain() else 0 + ) + toml["dns"]["registrar"] = _get_registrar_config_section(self.domain) # FIXME: Ugly hack to save the registar id/value and reinject it in _load_current_values ... - self.registar_id = toml['dns']['registrar']['registrar']['value'] - del toml['dns']['registrar']['registrar']['value'] + self.registar_id = toml["dns"]["registrar"]["registrar"]["value"] + del toml["dns"]["registrar"]["registrar"]["value"] return toml @@ -511,9 +514,11 @@ def domain_dns_conf(domain): def domain_dns_suggest(domain): import yunohost.dns + return yunohost.dns.domain_dns_suggest(domain) def domain_dns_push(domain, dry_run, force, purge): import yunohost.dns + return yunohost.dns.domain_dns_push(domain, dry_run, force, purge) diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index 4297a3408..519fbc8f0 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -308,7 +308,9 @@ def dyndns_update( logger.debug("Requested IPv4/v6 are (%s, %s)" % (ipv4, ipv6)) if ipv4 is None and ipv6 is None: - logger.debug("No ipv4 nor ipv6 ?! Sounds like the server is not connected to the internet, or the ip.yunohost.org infrastructure is down somehow") + logger.debug( + "No ipv4 nor ipv6 ?! Sounds like the server is not connected to the internet, or the ip.yunohost.org infrastructure is down somehow" + ) return # no need to update diff --git a/src/yunohost/tests/conftest.py b/src/yunohost/tests/conftest.py index d87ef445e..a07c44346 100644 --- a/src/yunohost/tests/conftest.py +++ b/src/yunohost/tests/conftest.py @@ -75,6 +75,7 @@ moulinette.core.Moulinette18n.n = new_m18nn def pytest_cmdline_main(config): import sys + sys.path.insert(0, "/usr/lib/moulinette/") import yunohost diff --git a/src/yunohost/tests/test_dns.py b/src/yunohost/tests/test_dns.py index 35940764c..c39b0ad4b 100644 --- a/src/yunohost/tests/test_dns.py +++ b/src/yunohost/tests/test_dns.py @@ -31,7 +31,9 @@ def test_get_dns_zone_from_domain_existing(): assert _get_dns_zone_for_domain("donate.yunohost.org") == "yunohost.org" assert _get_dns_zone_for_domain("fr.wikipedia.org") == "wikipedia.org" assert _get_dns_zone_for_domain("www.fr.wikipedia.org") == "wikipedia.org" - assert _get_dns_zone_for_domain("non-existing-domain.yunohost.org") == "yunohost.org" + assert ( + _get_dns_zone_for_domain("non-existing-domain.yunohost.org") == "yunohost.org" + ) assert _get_dns_zone_for_domain("yolo.nohost.me") == "nohost.me" assert _get_dns_zone_for_domain("foo.yolo.nohost.me") == "nohost.me" assert _get_dns_zone_for_domain("yolo.test") == "yolo.test" @@ -48,11 +50,17 @@ def test_magic_guess_registrar_weird_domain(): def test_magic_guess_registrar_ovh(): - assert _get_registrar_config_section("yolo.yunohost.org")["registrar"]["value"] == "ovh" + assert ( + _get_registrar_config_section("yolo.yunohost.org")["registrar"]["value"] + == "ovh" + ) def test_magic_guess_registrar_yunodyndns(): - assert _get_registrar_config_section("yolo.nohost.me")["registrar"]["value"] == "yunohost" + assert ( + _get_registrar_config_section("yolo.nohost.me")["registrar"]["value"] + == "yunohost" + ) @pytest.fixture @@ -66,6 +74,7 @@ def test_domain_dns_suggest(example_domain): assert _build_dns_conf(example_domain) -#def domain_dns_push(domain, dry_run): + +# def domain_dns_push(domain, dry_run): # import yunohost.dns # return yunohost.dns.domain_registrar_push(domain, dry_run) diff --git a/src/yunohost/tests/test_domains.py b/src/yunohost/tests/test_domains.py index bdb1b8a96..02d60ead4 100644 --- a/src/yunohost/tests/test_domains.py +++ b/src/yunohost/tests/test_domains.py @@ -15,11 +15,7 @@ from yunohost.domain import ( domain_config_set, ) -TEST_DOMAINS = [ - "example.tld", - "sub.example.tld", - "other-example.com" -] +TEST_DOMAINS = ["example.tld", "sub.example.tld", "other-example.com"] def setup_function(function): diff --git a/src/yunohost/tests/test_questions.py b/src/yunohost/tests/test_questions.py index 91372dffa..9753b08e4 100644 --- a/src/yunohost/tests/test_questions.py +++ b/src/yunohost/tests/test_questions.py @@ -518,9 +518,7 @@ def test_question_password_input_test_ask(): with patch.object( Moulinette, "prompt", return_value="some_value" - ) as prompt, patch.object( - os, "isatty", return_value=True - ): + ) as prompt, patch.object(os, "isatty", return_value=True): parse_args_in_yunohost_format(answers, questions) prompt.assert_called_with( message=ask_text, @@ -547,9 +545,7 @@ def test_question_password_input_test_ask_with_example(): with patch.object( Moulinette, "prompt", return_value="some_value" - ) as prompt, patch.object( - os, "isatty", return_value=True - ): + ) as prompt, patch.object(os, "isatty", return_value=True): parse_args_in_yunohost_format(answers, questions) assert ask_text in prompt.call_args[1]["message"] assert example_text in prompt.call_args[1]["message"] @@ -571,9 +567,7 @@ def test_question_password_input_test_ask_with_help(): with patch.object( Moulinette, "prompt", return_value="some_value" - ) as prompt, patch.object( - os, "isatty", return_value=True - ): + ) as prompt, patch.object(os, "isatty", return_value=True): parse_args_in_yunohost_format(answers, questions) assert ask_text in prompt.call_args[1]["message"] assert help_text in prompt.call_args[1]["message"] diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 3e070cadf..a6c3b299e 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -53,8 +53,8 @@ class ConfigPanel: self.values = {} self.new_values = {} - def get(self, key='', mode='classic'): - self.filter_key = key or '' + def get(self, key="", mode="classic"): + self.filter_key = key or "" # Read config panel toml self._get_config_panel() diff --git a/src/yunohost/utils/dns.py b/src/yunohost/utils/dns.py index 9af6df8d6..095e5000a 100644 --- a/src/yunohost/utils/dns.py +++ b/src/yunohost/utils/dns.py @@ -91,4 +91,3 @@ def dig( answers = [answer.to_text() for answer in answers] return ("ok", answers) - diff --git a/src/yunohost/utils/ldap.py b/src/yunohost/utils/ldap.py index 9edb2960b..651d09f75 100644 --- a/src/yunohost/utils/ldap.py +++ b/src/yunohost/utils/ldap.py @@ -102,7 +102,7 @@ class LDAPInterface: 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'", - raw_msg=True + raw_msg=True, ) # Check that we are indeed logged in with the right identity diff --git a/tests/test_i18n_keys.py b/tests/test_i18n_keys.py index 90e14848d..103241085 100644 --- a/tests/test_i18n_keys.py +++ b/tests/test_i18n_keys.py @@ -130,13 +130,13 @@ def find_expected_string_keys(): yield "backup_applying_method_%s" % method yield "backup_method_%s_finished" % method - registrars = toml.load(open('data/other/registrar_list.toml')) + registrars = toml.load(open("data/other/registrar_list.toml")) supported_registrars = ["ovh", "gandi", "godaddy"] for registrar in supported_registrars: for key in registrars[registrar].keys(): yield f"domain_config_{key}" - domain_config = toml.load(open('data/other/config_domain.toml')) + domain_config = toml.load(open("data/other/config_domain.toml")) for panel in domain_config.values(): if not isinstance(panel, dict): continue From e07c01e936f38962749db553a1acb35b388ac8da Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 19 Sep 2021 23:11:33 +0200 Subject: [PATCH 3130/3170] Reformat / remove stale translated strings --- locales/ar.json | 3 --- locales/ca.json | 7 +------ locales/ckb.json | 2 +- locales/cs.json | 3 +-- locales/de.json | 7 +------ locales/eo.json | 7 +------ locales/es.json | 7 +------ locales/fa.json | 7 +------ locales/fr.json | 9 ++------- locales/gl.json | 7 +------ locales/id.json | 2 +- locales/it.json | 7 +------ locales/mk.json | 2 +- locales/nb_NO.json | 2 -- locales/nl.json | 2 -- locales/oc.json | 7 +------ locales/pt.json | 4 +--- locales/uk.json | 7 +------ locales/zh_Hans.json | 7 +------ 19 files changed, 17 insertions(+), 82 deletions(-) diff --git a/locales/ar.json b/locales/ar.json index 3e5248917..487091995 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -6,7 +6,6 @@ "app_already_installed": "{app} تم تنصيبه مِن قبل", "app_already_up_to_date": "{app} تم تحديثه مِن قَبل", "app_argument_required": "المُعامِل '{name}' مطلوب", - "app_change_url_failed_nginx_reload": "فشلت عملية إعادة تشغيل NGINX. ها هي نتيجة الأمر 'nginx -t':\n{nginx_errors}", "app_extraction_failed": "تعذر فك الضغط عن ملفات التنصيب", "app_install_files_invalid": "ملفات التنصيب خاطئة", "app_not_correctly_installed": "يبدو أن التطبيق {app} لم يتم تنصيبه بشكل صحيح", @@ -40,7 +39,6 @@ "domain_creation_failed": "تعذرت عملية إنشاء النطاق", "domain_deleted": "تم حذف النطاق", "domain_exists": "اسم النطاق موجود مِن قبل", - "domain_unknown": "النطاق مجهول", "domains_available": "النطاقات المتوفرة :", "done": "تم", "downloading": "عملية التنزيل جارية …", @@ -55,7 +53,6 @@ "pattern_domain": "يتوجب أن يكون إسم نطاق صالح (مثل my-domain.org)", "pattern_email": "يتوجب أن يكون عنوان بريد إلكتروني صالح (مثل someone@domain.org)", "pattern_password": "يتوجب أن تكون مكونة من 3 حروف على الأقل", - "pattern_positive_number": "يجب أن يكون عددا إيجابيا", "restore_extracting": "جارٍ فك الضغط عن الملفات التي نحتاجها من النسخة الاحتياطية…", "server_shutdown": "سوف ينطفئ الخادوم", "server_shutdown_confirm": "سوف ينطفئ الخادوم حالا. متأكد ؟ [{answers}]", diff --git a/locales/ca.json b/locales/ca.json index 396f836f4..b29a94fb6 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -9,7 +9,6 @@ "app_argument_choice_invalid": "Utilitzeu una de les opcions «{choices}» per l'argument «{name}» en lloc de «{value}»", "app_argument_invalid": "Escolliu un valor vàlid per l'argument «{name}»: {error}", "app_argument_required": "Es necessita l'argument '{name}'", - "app_change_url_failed_nginx_reload": "No s'ha pogut tornar a carregar NGINX. Aquí teniu el resultat de \"nginx -t\":\n{nginx_errors}", "app_change_url_identical_domains": "L'antic i el nou domini/camí són idèntics ('{domain}{path}'), no hi ha res per fer.", "app_change_url_no_script": "L'aplicació '{app_name}' encara no permet modificar la URL. Potser s'ha d'actualitzar.", "app_change_url_success": "La URL de {app} ara és {domain}{path}", @@ -130,7 +129,6 @@ "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": "Aquestes aplicacions encara estan instal·lades en el vostre domini:\n{apps}\n\nDesinstal·leu-les utilitzant l'ordre «yunohost app remove id_de_lapplicació» o moveu-les a un altre domini amb «yunohost app change-url id_de_lapplicació» abans d'eliminar el domini", - "domain_unknown": "Domini desconegut", "domains_available": "Dominis disponibles:", "done": "Fet", "downloading": "Descarregant...", @@ -237,7 +235,6 @@ "pattern_mailbox_quota": "Ha de ser una mida amb el sufix b/k/M/G/T o 0 per no tenir quota", "pattern_password": "Ha de tenir un mínim de 3 caràcters", "pattern_port_or_range": "Ha de ser un número de port vàlid (i.e. 0-65535) o un interval de ports (ex. 100:200)", - "pattern_positive_number": "Ha de ser un nombre positiu", "pattern_username": "Ha d'estar compost per caràcters alfanumèrics en minúscula i guió baix exclusivament", "pattern_password_app": "Les contrasenyes no poden de tenir els següents caràcters: {forbidden_chars}", "port_already_closed": "El port {port} ja està tancat per les connexions {ip_version}", @@ -491,8 +488,6 @@ "diagnosis_ports_needed_by": "És necessari exposar aquest port per a les funcions {category} (servei {service})", "global_settings_setting_pop3_enabled": "Activa el protocol POP3 per al servidor de correu", "log_app_action_run": "Executa l'acció de l'aplicació «{}»", - "log_app_config_show_panel": "Mostra el taulell de configuració de l'aplicació «{}»", - "log_app_config_apply": "Afegeix la configuració a l'aplicació «{}»", "diagnosis_never_ran_yet": "Sembla que el servidor s'ha configurat recentment i encara no hi cap informe de diagnòstic per mostrar. S'ha d'executar un diagnòstic complet primer, ja sigui des de la pàgina web d'administració o utilitzant la comanda «yunohost diagnosis run» al terminal.", "diagnosis_description_web": "Web", "diagnosis_basesystem_hardware": "L'arquitectura del maquinari del servidor és {virt} {arch}", @@ -618,4 +613,4 @@ "diagnosis_sshd_config_insecure": "Sembla que la configuració SSH s'ha modificat manualment, i no es segura ha que no conté la directiva «AllowGroups» o «AllowUsers» per limitar l'accés a usuaris autoritzats.", "backup_create_size_estimation": "L'arxiu tindrà aproximadament {size} de dades.", "app_restore_script_failed": "S'ha produït un error en el script de restauració de l'aplicació" -} +} \ No newline at end of file diff --git a/locales/ckb.json b/locales/ckb.json index 0967ef424..9e26dfeeb 100644 --- a/locales/ckb.json +++ b/locales/ckb.json @@ -1 +1 @@ -{} +{} \ No newline at end of file diff --git a/locales/cs.json b/locales/cs.json index 46435b7c2..47262064e 100644 --- a/locales/cs.json +++ b/locales/cs.json @@ -11,7 +11,6 @@ "action_invalid": "Nesprávné akce '{action}'", "aborting": "Zrušeno.", "app_change_url_identical_domains": "Stará a nová doména/url_cesta jsou totožné ('{domain}{path}'), nebudou provedeny žádné změny.", - "app_change_url_failed_nginx_reload": "Nepodařilo se znovunačís NGINX. Následuje výpis příkazu 'nginx -t':\n{nginx_errors}", "app_argument_invalid": "Vyberte správnou hodnotu pro argument '{name}': {error}", "app_argument_choice_invalid": "Vyberte jednu z možností '{choices}' pro argument'{name}'", "app_already_up_to_date": "{app} aplikace je/jsou aktuální", @@ -65,4 +64,4 @@ "global_settings_setting_security_ssh_compatibility": "Kompromis mezi kompatibilitou a bezpečností SSH serveru. Ovlivní šifry a další související bezpečnostní nastavení", "global_settings_setting_security_password_user_strength": "Síla uživatelského hesla", "global_settings_setting_security_password_admin_strength": "Síla administračního hesla" -} +} \ No newline at end of file diff --git a/locales/de.json b/locales/de.json index ac9549c2e..199718c2b 100644 --- a/locales/de.json +++ b/locales/de.json @@ -47,7 +47,6 @@ "domain_dyndns_root_unknown": "Unbekannte DynDNS Hauptdomain", "domain_exists": "Die Domäne existiert bereits", "domain_uninstall_app_first": "Diese Applikationen sind noch auf Ihrer Domäne installiert; \n{apps}\n\nBitte deinstallieren Sie sie mit dem Befehl 'yunohost app remove the_app_id' oder verschieben Sie sie mit 'yunohost app change-url the_app_id'", - "domain_unknown": "Unbekannte Domain", "done": "Erledigt", "downloading": "Wird heruntergeladen...", "dyndns_ip_update_failed": "Konnte die IP-Adresse für DynDNS nicht aktualisieren", @@ -140,7 +139,6 @@ "app_not_properly_removed": "{app} wurde nicht ordnungsgemäß entfernt", "not_enough_disk_space": "Nicht genügend Speicherplatz auf '{path}' frei", "backup_creation_failed": "Konnte Backup-Archiv nicht erstellen", - "pattern_positive_number": "Muss eine positive Zahl sein", "app_not_correctly_installed": "{app} scheint nicht korrekt installiert zu sein", "app_requirements_checking": "Überprüfe notwendige Pakete für {app}...", "app_requirements_unmeet": "Anforderungen für {app} werden nicht erfüllt, das Paket {pkgname} ({version}) muss {spec} sein", @@ -170,7 +168,6 @@ "certmanager_unable_to_parse_self_CA_name": "Der Name der Zertifizierungsstelle für selbstsignierte Zertifikate konnte nicht aufgelöst werden (Datei: {file})", "domain_hostname_failed": "Sie können keinen neuen Hostnamen verwenden. Das kann zukünftige Probleme verursachen (es kann auch sein, dass es funktioniert).", "app_already_installed_cant_change_url": "Diese Applikation ist bereits installiert. Die URL kann durch diese Funktion nicht modifiziert werden. Überprüfe ob `app changeurl` verfügbar ist.", - "app_change_url_failed_nginx_reload": "NGINX konnte nicht neu gestartet werden. Hier ist der Output von 'nginx -t':\n{nginx_errors}", "app_change_url_identical_domains": "Die alte und neue domain/url_path sind identisch: ('{domain} {path}'). Es gibt nichts zu tun.", "app_already_up_to_date": "{app} ist bereits aktuell", "backup_abstract_method": "Diese Backup-Methode wird noch nicht unterstützt", @@ -460,8 +457,6 @@ "log_backup_restore_app": "'{}' aus einem Backup-Archiv wiederherstellen", "log_backup_restore_system": "System aus einem Backup-Archiv wiederherstellen", "log_available_on_yunopaste": "Das Protokoll ist nun via {url} verfügbar", - "log_app_config_apply": "Wende die Konfiguration auf die Applikation '{}' an", - "log_app_config_show_panel": "Zeige das Konfigurations-Panel der Applikation '{}'", "log_app_action_run": "Führe Aktion der Applikation '{}' aus", "invalid_regex": "Ungültige Regex:'{regex}'", "migration_description_0016_php70_to_php73_pools": "Migrieren der php7.0-fpm-Konfigurationsdateien zu php7.3", @@ -636,4 +631,4 @@ "disk_space_not_sufficient_update": "Es ist nicht genügend Speicherplatz frei, um diese Applikation zu aktuallisieren", "disk_space_not_sufficient_install": "Es ist nicht genügend Speicherplatz frei, um diese Applikation zu installieren", "danger": "Warnung:" -} +} \ No newline at end of file diff --git a/locales/eo.json b/locales/eo.json index a19dc3ccf..8973e6344 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -30,7 +30,6 @@ "already_up_to_date": "Nenio por fari. Ĉio estas jam ĝisdatigita.", "app_argument_choice_invalid": "Uzu unu el ĉi tiuj elektoj '{choices}' por la argumento '{name}' anstataŭ '{value}'", "app_argument_invalid": "Elektu validan valoron por la argumento '{name}': {error}", - "app_change_url_failed_nginx_reload": "Ne eblis reŝarĝi NGINX. Jen la eligo de 'nginx -t':\n{nginx_errors}", "ask_new_admin_password": "Nova administrada pasvorto", "app_action_broke_system": "Ĉi tiu ago ŝajne rompis ĉi tiujn gravajn servojn: {services}", "app_unsupported_remote_type": "Malkontrolita fora speco uzita por la apliko", @@ -197,7 +196,6 @@ "certmanager_acme_not_configured_for_domain": "Atestilo por la domajno '{domain}' ne ŝajnas esti ĝuste instalita. Bonvolu ekzekuti 'cert-instali' por ĉi tiu regado unue.", "user_update_failed": "Ne povis ĝisdatigi uzanton {user}: {error}", "restore_confirm_yunohost_installed": "Ĉu vi vere volas restarigi jam instalitan sistemon? [{answers}]", - "pattern_positive_number": "Devas esti pozitiva nombro", "update_apt_cache_failed": "Ne eblis ĝisdatigi la kaŝmemoron de APT (paka administranto de Debian). Jen rubujo de la sources.list-linioj, kiuj povus helpi identigi problemajn liniojn:\n{sourceslist}", "migrations_no_migrations_to_run": "Neniuj migradoj por funkcii", "certmanager_attempt_to_renew_nonLE_cert": "La atestilo por la domajno '{domain}' ne estas elsendita de Let's Encrypt. Ne eblas renovigi ĝin aŭtomate!", @@ -252,7 +250,6 @@ "dyndns_unavailable": "La domajno '{domain}' ne haveblas.", "experimental_feature": "Averto: Ĉi tiu funkcio estas eksperimenta kaj ne konsiderata stabila, vi ne uzu ĝin krom se vi scias kion vi faras.", "root_password_replaced_by_admin_password": "Via radika pasvorto estis anstataŭigita per via administra pasvorto.", - "domain_unknown": "Nekonata domajno", "global_settings_setting_security_password_user_strength": "Uzanto pasvorta forto", "restore_may_be_not_enough_disk_space": "Via sistemo ne ŝajnas havi sufiĉe da spaco (libera: {free_space} B, necesa spaco: {needed_space} B, sekureca marĝeno: {margin} B)", "log_corrupted_md_file": "La YAD-metadata dosiero asociita kun protokoloj estas damaĝita: '{md_file}\nEraro: {error} '", @@ -491,8 +488,6 @@ "diagnosis_description_ports": "Ekspoziciaj havenoj", "diagnosis_description_mail": "Retpoŝto", "log_app_action_run": "Funkciigu agon de la apliko '{}'", - "log_app_config_show_panel": "Montri la agordan panelon de la apliko '{}'", - "log_app_config_apply": "Apliki agordon al la apliko '{}'", "diagnosis_never_ran_yet": "Ŝajnas, ke ĉi tiu servilo estis instalita antaŭ nelonge kaj estas neniu diagnoza raporto por montri. Vi devas komenci kurante plenan diagnozon, ĉu de la retadministro aŭ uzante 'yunohost diagnosis run' el la komandlinio.", "certmanager_warning_subdomain_dns_record": "Subdominio '{subdomain}' ne solvas al la sama IP-adreso kiel '{domain}'. Iuj funkcioj ne estos haveblaj ĝis vi riparos ĉi tion kaj regeneros la atestilon.", "diagnosis_basesystem_hardware": "Arkitekturo de servila aparataro estas {virt} {arch}", @@ -551,4 +546,4 @@ "app_argument_password_no_default": "Eraro dum analiza pasvorta argumento '{name}': pasvorta argumento ne povas havi defaŭltan valoron por sekureca kialo", "additional_urls_already_removed": "Plia URL '{url}' jam forigita en la aldona URL por permeso '{permission}'", "additional_urls_already_added": "Plia URL '{url}' jam aldonita en la aldona URL por permeso '{permission}'" -} +} \ No newline at end of file diff --git a/locales/es.json b/locales/es.json index 1eba5b975..688db4546 100644 --- a/locales/es.json +++ b/locales/es.json @@ -53,7 +53,6 @@ "domain_dyndns_root_unknown": "Dominio raíz de DynDNS desconocido", "domain_exists": "El dominio ya existe", "domain_uninstall_app_first": "Estas aplicaciones están todavía instaladas en tu dominio:\n{apps}\n\nPor favor desinstálalas utilizando yunohost app remove the_app_id o cambialas a otro dominio usando yunohost app change-url the_app_id antes de continuar con el borrado del dominio.", - "domain_unknown": "Dominio desconocido", "done": "Hecho.", "downloading": "Descargando…", "dyndns_ip_update_failed": "No se pudo actualizar la dirección IP en DynDNS", @@ -91,7 +90,6 @@ "pattern_mailbox_quota": "Debe ser un tamaño con el sufijo «b/k/M/G/T» o «0» para no tener una cuota", "pattern_password": "Debe contener al menos 3 caracteres", "pattern_port_or_range": "Debe ser un número de puerto válido (es decir entre 0-65535) o un intervalo de puertos (por ejemplo 100:200)", - "pattern_positive_number": "Deber ser un número positivo", "pattern_username": "Solo puede contener caracteres alfanuméricos o el guión bajo", "port_already_closed": "El puerto {port} ya está cerrado para las conexiones {ip_version}", "port_already_opened": "El puerto {port} ya está abierto para las conexiones {ip_version}", @@ -171,7 +169,6 @@ "certmanager_acme_not_configured_for_domain": "El reto ACME no ha podido ser realizado para {domain} porque su configuración de nginx no tiene el el código correcto... Por favor, asegurate que la configuración de nginx es correcta ejecutando en el terminal `yunohost tools regen-conf nginx --dry-run --with-diff`.", "domain_hostname_failed": "No se pudo establecer un nuevo nombre de anfitrión («hostname»). Esto podría causar problemas más tarde (no es seguro... podría ir bien).", "app_already_installed_cant_change_url": "Esta aplicación ya está instalada. La URL no se puede cambiar solo con esta función. Marque `app changeurl` si está disponible.", - "app_change_url_failed_nginx_reload": "No se pudo recargar NGINX. Esta es la salida de «nginx -t»:\n{nginx_errors}", "app_change_url_identical_domains": "El antiguo y nuevo dominio/url_path son idénticos ('{domain} {path}'), no se realizarán cambios.", "app_change_url_no_script": "La aplicación «{app_name}» aún no permite la modificación de URLs. Quizás debería actualizarla.", "app_change_url_success": "El URL de la aplicación {app} es ahora {domain} {path}", @@ -480,8 +477,6 @@ "diagnosis_description_web": "Web", "diagnosis_basesystem_hardware": "La arquitectura material del servidor es {virt} {arch}", "log_domain_main_domain": "Hacer de '{}' el dominio principal", - "log_app_config_apply": "Aplica la configuración de la aplicación '{}'", - "log_app_config_show_panel": "Muestra el panel de configuración de la aplicación '{}'", "log_app_action_run": "Inicializa la acción de la aplicación '{}'", "group_already_exist_on_system_but_removing_it": "El grupo {group} ya existe en los grupos del sistema, pero YunoHost lo suprimirá …", "global_settings_setting_pop3_enabled": "Habilita el protocolo POP3 para el servidor de correo electrónico", @@ -586,4 +581,4 @@ "diagnosis_basesystem_hardware_model": "El modelo de servidor es {model}", "additional_urls_already_removed": "La URL adicional «{url}» ya se ha eliminado para el permiso «{permission}»", "additional_urls_already_added": "La URL adicional «{url}» ya se ha añadido para el permiso «{permission}»" -} +} \ No newline at end of file diff --git a/locales/fa.json b/locales/fa.json index e0195717f..f566fed90 100644 --- a/locales/fa.json +++ b/locales/fa.json @@ -1,7 +1,6 @@ { "action_invalid": "اقدام نامعتبر '{action}'", "aborting": "رها کردن.", - "app_change_url_failed_nginx_reload": "NGINX بارگیری نشد. در اینجا خروجی 'nginx -t' است:\n{nginx_errors}", "app_argument_required": "استدلال '{name}' الزامی است", "app_argument_password_no_default": "خطا هنگام تجزیه گذرواژه '{name}': به دلایل امنیتی استدلال رمز عبور نمی تواند مقدار پیش فرض داشته باشد", "app_argument_invalid": "یک مقدار معتبر انتخاب کنید برای استدلال '{name}':{error}", @@ -355,7 +354,6 @@ "downloading": "در حال بارگیری...", "done": "انجام شد", "domains_available": "دامنه های موجود:", - "domain_unknown": "دامنه ناشناخته", "domain_name_unknown": "دامنه '{domain}' ناشناخته است", "domain_uninstall_app_first": "این برنامه ها هنوز روی دامنه شما نصب هستند:\n{apps}\n\nلطفاً قبل از اقدام به حذف دامنه ، آنها را با استفاده از 'برنامه yunohost remove the_app_id' حذف کرده یا با استفاده از 'yunohost app change-url the_app_id' به دامنه دیگری منتقل کنید", "domain_remove_confirm_apps_removal": "حذف این دامنه برنامه های زیر را حذف می کند:\n{apps}\n\nآیا طمئن هستید که میخواهید انجام دهید؟ [{answers}]", @@ -378,7 +376,6 @@ "permission_already_allowed": "گروه '{group}' قبلاً مجوز '{permission}' را فعال کرده است", "pattern_password_app": "متأسفیم ، گذرواژه ها نمی توانند شامل کاراکترهای زیر باشند: {forbidden_chars}", "pattern_username": "باید فقط حروف الفبایی کوچک و خط زیر باشد", - "pattern_positive_number": "باید یک عدد مثبت باشد", "pattern_port_or_range": "باید یک شماره پورت معتبر (یعنی 0-65535) یا محدوده پورت (به عنوان مثال 100: 200) باشد", "pattern_password": "باید حداقل 3 کاراکتر داشته باشد", "pattern_mailbox_quota": "باید اندازه ای با پسوند b / k / M / G / T یا 0 داشته باشد تا سهمیه نداشته باشد", @@ -488,8 +485,6 @@ "log_backup_restore_system": "بازیابی سیستم بوسیله آرشیو پشتیبان", "log_backup_create": "بایگانی پشتیبان ایجاد کنید", "log_available_on_yunopaste": "این گزارش اکنون از طریق {url} در دسترس است", - "log_app_config_apply": "پیکربندی را در برنامه '{}' اعمال کنید", - "log_app_config_show_panel": "پانل پیکربندی برنامه '{}' را نشان دهید", "log_app_action_run": "عملکرد برنامه '{}' را اجرا کنید", "log_app_makedefault": "\"{}\" را برنامه پیش فرض قرار دهید", "log_app_upgrade": "برنامه '{}' را ارتقاء دهید", @@ -642,4 +637,4 @@ "permission_deleted": "مجوز '{permission}' حذف شد", "permission_cant_add_to_all_users": "مجوز {permission} را نمی توان به همه کاربران اضافه کرد.", "permission_currently_allowed_for_all_users": "این مجوز در حال حاضر به همه کاربران علاوه بر آن گروه های دیگر نیز اعطا شده. احتمالاً بخواهید مجوز 'all_users' را حذف کنید یا سایر گروه هایی را که در حال حاضر مجوز به آنها اعطا شده است را هم حذف کنید." -} +} \ No newline at end of file diff --git a/locales/fr.json b/locales/fr.json index 61157c0ed..46535719e 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -55,7 +55,6 @@ "domain_dyndns_root_unknown": "Domaine DynDNS principal inconnu", "domain_exists": "Le domaine existe déjà", "domain_uninstall_app_first": "Ces applications sont toujours installées sur votre domaine :\n{apps}\n\nVeuillez les désinstaller avec la commande 'yunohost app remove nom-de-l-application' ou les déplacer vers un autre domaine avec la commande 'yunohost app change-url nom-de-l-application' avant de procéder à la suppression du domaine", - "domain_unknown": "Domaine inconnu", "done": "Terminé", "downloading": "Téléchargement en cours...", "dyndns_ip_update_failed": "Impossible de mettre à jour l'adresse IP sur le domaine DynDNS", @@ -93,7 +92,6 @@ "pattern_mailbox_quota": "Doit avoir une taille suffixée avec b/k/M/G/T ou 0 pour désactiver le quota", "pattern_password": "Doit être composé d'au moins 3 caractères", "pattern_port_or_range": "Doit être un numéro de port valide compris entre 0 et 65535, ou une gamme de ports (exemple : 100:200)", - "pattern_positive_number": "Doit être un nombre positif", "pattern_username": "Doit être composé uniquement de caractères alphanumériques minuscules et de tirets bas (aussi appelé tiret du 8 ou underscore)", "port_already_closed": "Le port {port} est déjà fermé pour les connexions {ip_version}", "port_already_opened": "Le port {port} est déjà ouvert pour les connexions {ip_version}", @@ -173,7 +171,6 @@ "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`.", "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).", "app_already_installed_cant_change_url": "Cette application est déjà installée. L'URL ne peut pas être changé simplement par cette fonction. Vérifiez si cela est disponible avec `app changeurl`.", - "app_change_url_failed_nginx_reload": "Le redémarrage de NGINX a échoué. Voici la sortie de 'nginx -t' :\n{nginx_errors}", "app_change_url_identical_domains": "L'ancien et le nouveau couple domaine/chemin_de_l'URL sont identiques pour ('{domain}{path}'), rien à faire.", "app_change_url_no_script": "L'application '{app_name}' ne prend pas encore en charge le changement d'URL. Vous devriez peut-être la mettre à jour.", "app_change_url_success": "L'URL de l'application {app} a été changée en {domain}{path}", @@ -494,8 +491,6 @@ "diagnosis_http_timeout": "Expiration du délai en essayant de contacter votre serveur de l'extérieur. Il semble être inaccessible. Vérifiez que vous transférez correctement le port 80, que Nginx est en cours d'exécution et qu'un pare-feu n'interfère pas.", "global_settings_setting_pop3_enabled": "Activer le protocole POP3 pour le serveur de messagerie", "log_app_action_run": "Lancer l'action de l'application '{}'", - "log_app_config_show_panel": "Montrer le panneau de configuration de l'application '{}'", - "log_app_config_apply": "Appliquer la configuration à l'application '{}'", "diagnosis_never_ran_yet": "Il apparaît que le serveur a été installé récemment et qu'il n'y a pas encore eu de diagnostic. Vous devriez en lancer un depuis la webadmin ou en utilisant 'yunohost diagnosis run' depuis la ligne de commande.", "diagnosis_description_web": "Web", "diagnosis_basesystem_hardware": "L'architecture du serveur est {virt} {arch}", @@ -528,7 +523,7 @@ "diagnosis_mail_ehlo_ok": "Le serveur de messagerie SMTP est accessible de l'extérieur et peut donc recevoir des emails !", "diagnosis_mail_ehlo_unreachable": "Le serveur de messagerie SMTP est inaccessible de l'extérieur en IPv{ipversion}. Il ne pourra pas recevoir des emails.", "diagnosis_mail_ehlo_unreachable_details": "Impossible d'ouvrir une connexion sur le port 25 à votre serveur en IPv{ipversion}. Il semble inaccessible.
1. La cause la plus courante de ce problème est que le port 25 n'est pas correctement redirigé vers votre serveur.
2. Vous devez également vous assurer que le service postfix est en cours d'exécution.
3. Sur les configurations plus complexes: assurez-vous qu'aucun pare-feu ou proxy inversé n'interfère.", - "diagnosis_mail_ehlo_wrong_details": "Le EHLO reçu par le serveur de diagnostique distant en IPv{ipversion} est différent du domaine de votre serveur.
EHLO reçu : {wrong_ehlo}
Attendu : {right_ehlo}
La cause la plus courante à ce problème est que le port 25 n'est pas correctement redirigé vers votre serveur. Vous pouvez également vous assurer qu'aucun pare-feu ou reverse-proxy n'interfère.", + "diagnosis_mail_ehlo_wrong_details": "Le EHLO reçu par le serveur de diagnostique distant en IPv{ipversion} est différent du domaine de votre serveur.
EHLO reçu : {wrong_ehlo}
Attendu : {right_ehlo}
La cause la plus courante à ce problème est que le port 25 n'est pas correctement redirigé vers votre serveur. Vous pouvez également vous assurer qu'aucun pare-feu ou reverse-proxy n'interfère.", "diagnosis_mail_fcrdns_nok_alternatives_4": "Certains fournisseurs ne vous laisseront pas configurer votre DNS inversé (ou leur fonctionnalité pourrait être cassée...). Si vous rencontrez des problèmes à cause de cela, envisagez les solutions suivantes :
- Certains FAI fournissent l'alternative de à l'aide d'un relais de serveur de messagerie bien que cela implique que le relais pourra espionner votre trafic de messagerie.
- Une alternative respectueuse de la vie privée consiste à utiliser un VPN *avec une IP publique dédiée* pour contourner ce type de limites. Voir https://yunohost.org/#/vpn_advantage
- Enfin, il est également possible de changer de fournisseur", "diagnosis_mail_fcrdns_nok_alternatives_6": "Certains fournisseurs ne vous laisseront pas configurer votre DNS inversé (ou leur fonctionnalité pourrait être cassée...). Si votre DNS inversé est correctement configuré en IPv4, vous pouvez essayer de désactiver l'utilisation d'IPv6 lors de l'envoi d'emails en exécutant yunohost settings set smtp.allow_ipv6 -v off. Remarque : cette dernière solution signifie que vous ne pourrez pas envoyer ou recevoir de emails avec les quelques serveurs qui ont uniquement de l'IPv6.", "diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "DNS inverse actuel : {rdns_domain}
Valeur attendue : {ehlo_domain}", @@ -681,4 +676,4 @@ "service_not_reloading_because_conf_broken": "Le service '{name}' n'a pas été rechargé/redémarré car sa configuration est cassée : {errors}", "app_argument_password_help_keep": "Tapez sur Entrée pour conserver la valeur actuelle", "app_argument_password_help_optional": "Tapez un espace pour vider le mot de passe" -} +} \ No newline at end of file diff --git a/locales/gl.json b/locales/gl.json index 0ef15ada1..ebb65be02 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -14,7 +14,6 @@ "additional_urls_already_removed": "URL adicional '{url}' xa foi eliminada das URL adicionais para o permiso '{permission}'", "additional_urls_already_added": "URL adicional '{url}' xa fora engadida ás URL adicionais para o permiso '{permission}'", "action_invalid": "Acción non válida '{action}'", - "app_change_url_failed_nginx_reload": "Non se recargou NGINX. Aquí tes a saída de 'nginx -t':\n{nginx_errors}", "app_argument_required": "Requírese o argumento '{name}'", "app_argument_password_no_default": "Erro ao procesar o argumento do contrasinal '{name}': o argumento do contrasinal non pode ter un valor por defecto por razón de seguridade", "app_argument_invalid": "Elixe un valor válido para o argumento '{name}': {error}", @@ -295,7 +294,6 @@ "downloading": "Descargando...", "done": "Feito", "domains_available": "Dominios dispoñibles:", - "domain_unknown": "Dominio descoñecido", "domain_name_unknown": "Dominio '{domain}' descoñecido", "domain_uninstall_app_first": "Aínda están instaladas estas aplicacións no teu dominio:\n{apps}\n\nPrimeiro desinstalaas utilizando 'yunohost app remove id_da_app' ou móveas a outro dominio con 'yunohost app change-url id_da_app' antes de eliminar o dominio", "domain_remove_confirm_apps_removal": "Ao eliminar o dominio tamén vas eliminar estas aplicacións:\n{apps}\n\nTes a certeza de querer facelo? [{answers}]", @@ -386,8 +384,6 @@ "log_backup_restore_system": "Restablecer o sistema desde unha copia de apoio", "log_backup_create": "Crear copia de apoio", "log_available_on_yunopaste": "Este rexistro está dispoñible en {url}", - "log_app_config_apply": "Aplicar a configuración da app '{}'", - "log_app_config_show_panel": "Mostrar o panel de configuración da app '{}'", "log_app_action_run": "Executar acción da app '{}'", "log_app_makedefault": "Converter '{}' na app por defecto", "log_app_upgrade": "Actualizar a app '{}'", @@ -471,7 +467,6 @@ "permission_already_allowed": "O grupo '{group}' xa ten o permiso '{permission}' activado", "pattern_password_app": "Lamentámolo, os contrasinais non poden conter os seguintes caracteres: {forbidden_chars}", "pattern_username": "Só admite caracteres alfanuméricos en minúscula e trazo baixo", - "pattern_positive_number": "Ten que ser un número positivo", "pattern_port_or_range": "Debe ser un número válido de porto (entre 0-65535) ou rango de portos (ex. 100:200)", "pattern_password": "Ten que ter polo menos 3 caracteres", "pattern_mailbox_quota": "Ten que ser un tamaño co sufixo b/k/M/G/T ou 0 para non ter unha cota", @@ -681,4 +676,4 @@ "file_extension_not_accepted": "Rexeitouse o ficheiro '{path}' porque a súa extensión non está entre as aceptadas: {accept}", "invalid_number_max": "Ten que ser menor de {max}", "service_not_reloading_because_conf_broken": "Non se recargou/reiniciou o servizo '{name}' porque a súa configuración está estragada: {errors}" -} +} \ No newline at end of file diff --git a/locales/id.json b/locales/id.json index 0967ef424..9e26dfeeb 100644 --- a/locales/id.json +++ b/locales/id.json @@ -1 +1 @@ -{} +{} \ No newline at end of file diff --git a/locales/it.json b/locales/it.json index 8ecdc4b01..1332712ef 100644 --- a/locales/it.json +++ b/locales/it.json @@ -67,7 +67,6 @@ "domain_dyndns_root_unknown": "Dominio radice DynDNS sconosciuto", "domain_hostname_failed": "Impossibile impostare il nuovo hostname. Potrebbe causare problemi in futuro (o anche no).", "domain_uninstall_app_first": "Queste applicazioni sono già installate su questo dominio:\n{apps}\n\nDisinstallale eseguendo 'yunohost app remove app_id' o spostale in un altro dominio eseguendo 'yunohost app change-url app_id' prima di procedere alla cancellazione del dominio", - "domain_unknown": "Dominio sconosciuto", "done": "Terminato", "domains_available": "Domini disponibili:", "downloading": "Scaricamento…", @@ -104,7 +103,6 @@ "pattern_lastname": "Deve essere un cognome valido", "pattern_password": "Deve contenere almeno 3 caratteri", "pattern_port_or_range": "Deve essere un numero di porta valido (es. 0-65535) o una fascia di porte valida (es. 100:200)", - "pattern_positive_number": "Deve essere un numero positivo", "pattern_username": "Caratteri minuscoli alfanumerici o trattini bassi soli", "port_already_closed": "La porta {port} è già chiusa per le connessioni {ip_version}", "restore_already_installed_app": "Un'applicazione con l'ID '{app}' è già installata", @@ -159,7 +157,6 @@ "certmanager_domain_http_not_working": "Il dominio {domain} non sembra accessibile attraverso HTTP. Verifica nella sezione 'Web' nella diagnosi per maggiori informazioni. (Se sai cosa stai facendo, usa '--no-checks' per disattivare i controlli.)", "app_already_installed_cant_change_url": "Questa applicazione è già installata. L'URL non può essere cambiato solo da questa funzione. Controlla se `app changeurl` è disponibile.", "app_already_up_to_date": "{app} è già aggiornata", - "app_change_url_failed_nginx_reload": "Non riesco a riavviare NGINX. Questo è il risultato di 'nginx -t':\n{nginx_errors}", "app_change_url_identical_domains": "Il vecchio ed il nuovo dominio/percorso_url sono identici ('{domain}{path}'), nessuna operazione necessaria.", "app_change_url_no_script": "L'applicazione '{app_name}' non supporta ancora la modifica dell'URL. Forse dovresti aggiornarla.", "app_change_url_success": "L'URL dell'applicazione {app} è stato cambiato in {domain}{path}", @@ -531,8 +528,6 @@ "log_permission_url": "Aggiorna l'URL collegato al permesso '{}'", "log_permission_delete": "Cancella permesso '{}'", "log_permission_create": "Crea permesso '{}'", - "log_app_config_apply": "Applica la configurazione all'app '{}'", - "log_app_config_show_panel": "Mostra il pannello di configurazione dell'app '{}'", "log_app_action_run": "Esegui l'azione dell'app '{}'", "log_operation_unit_unclosed_properly": "Operazion unit non è stata chiusa correttamente", "invalid_regex": "Regex invalida:'{regex}'", @@ -635,4 +630,4 @@ "global_settings_setting_security_webadmin_allowlist_enabled": "Permetti solo ad alcuni IP di accedere al webadmin.", "disk_space_not_sufficient_update": "Non c'è abbastanza spazio libero per aggiornare questa applicazione", "disk_space_not_sufficient_install": "Non c'è abbastanza spazio libero per installare questa applicazione" -} +} \ No newline at end of file diff --git a/locales/mk.json b/locales/mk.json index 0967ef424..9e26dfeeb 100644 --- a/locales/mk.json +++ b/locales/mk.json @@ -1 +1 @@ -{} +{} \ No newline at end of file diff --git a/locales/nb_NO.json b/locales/nb_NO.json index 221f974ab..dc217d74e 100644 --- a/locales/nb_NO.json +++ b/locales/nb_NO.json @@ -27,7 +27,6 @@ "app_action_cannot_be_ran_because_required_services_down": "Dette programmet krever noen tjenester som ikke kjører. Før du fortsetter, du bør prøve å starte følgende tjenester på ny (og antagelig undersøke hvorfor de er nede): {services}", "app_already_installed_cant_change_url": "Dette programmet er allerede installert. Nettadressen kan ikke endres kun med denne funksjonen. Ta en titt på `app changeurl` hvis den er tilgjengelig.", "domain_exists": "Domenet finnes allerede", - "app_change_url_failed_nginx_reload": "Kunne ikke gjeninnlaste NGINX. Her har du utdataen for 'nginx -t'\n{nginx_errors}", "domains_available": "Tilgjengelige domener:", "done": "Ferdig", "downloading": "Laster ned…", @@ -83,7 +82,6 @@ "domain_created": "Domene opprettet", "domain_creation_failed": "Kunne ikke opprette domene", "domain_dyndns_root_unknown": "Ukjent DynDNS-rotdomene", - "domain_unknown": "Ukjent domene", "dyndns_ip_update_failed": "Kunne ikke oppdatere IP-adresse til DynDNS", "dyndns_ip_updated": "Oppdaterte din IP på DynDNS", "dyndns_key_generating": "Oppretter DNS-nøkkel… Dette kan ta en stund.", diff --git a/locales/nl.json b/locales/nl.json index 1995cbf62..5e612fc77 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -32,7 +32,6 @@ "domain_dyndns_root_unknown": "Onbekend DynDNS root domein", "domain_exists": "Domein bestaat al", "domain_uninstall_app_first": "Een of meerdere apps zijn geïnstalleerd op dit domein, verwijder deze voordat u het domein verwijdert", - "domain_unknown": "Onbekend domein", "done": "Voltooid", "downloading": "Downloaden...", "dyndns_ip_update_failed": "Kan het IP adres niet updaten bij DynDNS", @@ -102,7 +101,6 @@ "app_manifest_install_ask_domain": "Kies het domein waar deze app op geïnstalleerd moet worden", "app_manifest_install_ask_path": "Kies het pad waar deze app geïnstalleerd moet worden", "app_manifest_install_ask_admin": "Kies een administrator voor deze app", - "app_change_url_failed_nginx_reload": "Kon NGINX niet opnieuw laden. Hier is de output van 'nginx -t':\n{nginx_errors}", "app_change_url_success": "{app} URL is nu {domain}{path}", "app_full_domain_unavailable": "Sorry, deze app moet op haar eigen domein geïnstalleerd worden, maar andere apps zijn al geïnstalleerd op het domein '{domain}'. U kunt wel een subdomein aan deze app toewijden.", "app_install_script_failed": "Er is een fout opgetreden in het installatiescript van de app", diff --git a/locales/oc.json b/locales/oc.json index b084c5236..a2a5bfe31 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -30,7 +30,6 @@ "app_argument_choice_invalid": "Utilizatz una de las opcions « {choices} » per l’argument « {name} »", "app_argument_invalid": "Causissètz una valor invalida pel paramètre « {name} » : {error}", "app_argument_required": "Lo paramètre « {name} » es requesit", - "app_change_url_failed_nginx_reload": "Reaviada de NGINX impossibla. Vaquí la sortida de « nginx -t » :\n{nginx_errors}", "app_change_url_identical_domains": "L’ancian e lo novèl coble domeni/camin son identics per {domain}{path}, pas res a far.", "app_change_url_success": "L’URL de l’aplicacion {app} es ara {domain}{path}", "app_extraction_failed": "Extraccion dels fichièrs d’installacion impossibla", @@ -108,7 +107,6 @@ "domain_dyndns_root_unknown": "Domeni DynDNS màger desconegut", "domain_exists": "Lo domeni existís ja", "domain_hostname_failed": "Fracàs de la creacion d’un nòu nom d’òst. Aquò poirà provocar de problèmas mai tard (mas es pas segur… benlèu que coparà pas res).", - "domain_unknown": "Domeni desconegut", "domains_available": "Domenis disponibles :", "done": "Acabat", "downloading": "Telecargament…", @@ -141,7 +139,6 @@ "pattern_lastname": "Deu èsser un nom valid", "pattern_password": "Deu conténer almens 3 caractèrs", "pattern_port_or_range": "Deu èsser un numèro de pòrt valid (ex : 0-65535) o un interval de pòrt (ex : 100:200)", - "pattern_positive_number": "Deu èsser un nombre positiu", "port_already_closed": "Lo pòrt {port} es ja tampat per las connexions {ip_version}", "port_already_opened": "Lo pòrt {port} es ja dubèrt per las connexions {ip_version}", "restore_already_installed_app": "Una aplicacion es ja installada amb l’id « {app} »", @@ -504,8 +501,6 @@ "migration_description_0016_php70_to_php73_pools": "Migrar los fichièrs de configuracion php7.0 cap a php7.3", "migration_description_0015_migrate_to_buster": "Mesa a nivèl dels sistèmas Debian Buster e YunoHost 4.x", "migrating_legacy_permission_settings": "Migracion dels paramètres de permission ancians...", - "log_app_config_apply": "Aplicar la configuracion a l’aplicacion « {} »", - "log_app_config_show_panel": "Mostrar lo panèl de configuracion de l’aplicacion « {} »", "log_app_action_run": "Executar l’accion de l’aplicacion « {} »", "diagnosis_basesystem_hardware_model": "Lo modèl del servidor es {model}", "backup_archive_cant_retrieve_info_json": "Obtencion impossibla de las informacions de l’archiu « {archive} »... Se pòt pas recuperar lo fichièr info.json (o es pas un fichièr json valid).", @@ -518,4 +513,4 @@ "diagnosis_domain_expiration_not_found": "Impossible de verificar la data d’expiracion d’unes domenis", "backup_create_size_estimation": "L’archiu contendrà apr’aquí {size} de donadas.", "app_restore_script_failed": "Una error s’es producha a l’interior del script de restauracion de l’aplicacion" -} +} \ No newline at end of file diff --git a/locales/pt.json b/locales/pt.json index ff555f347..534e0cb27 100644 --- a/locales/pt.json +++ b/locales/pt.json @@ -31,7 +31,6 @@ "domain_dyndns_root_unknown": "Domínio root (administrador) DynDNS desconhecido", "domain_exists": "O domínio já existe", "domain_uninstall_app_first": "Existem uma ou mais aplicações instaladas neste domínio. Por favor desinstale-as antes de proceder com a remoção do domínio.", - "domain_unknown": "Domínio desconhecido", "done": "Concluído.", "downloading": "Transferência em curso...", "dyndns_ip_update_failed": "Não foi possível atualizar o endereço IP para DynDNS", @@ -113,7 +112,6 @@ "app_argument_choice_invalid": "Use uma das opções '{choices}' para o argumento '{name}' em vez de '{value}'", "app_argument_invalid": "Escolha um valor válido para o argumento '{name}': {error}", "app_argument_required": "O argumento '{name}' é obrigatório", - "app_change_url_failed_nginx_reload": "Não foi possível reiniciar o nginx. Aqui está o retorno de 'nginx -t':\n{nginx_errors}", "app_location_unavailable": "Esta url ou não está disponível ou está em conflito com outra(s) aplicação(ões) já instalada(s):\n{apps}", "app_upgrade_app_name": "Atualizando {app}…", "app_upgrade_some_app_failed": "Não foi possível atualizar algumas aplicações", @@ -194,4 +192,4 @@ "backup_permission": "Permissão de backup para {app}", "backup_output_symlink_dir_broken": "O diretório de seu arquivo '{path}' é um link simbólico quebrado. Talvez você tenha esquecido de re/montar ou conectar o dispositivo de armazenamento para onde o link aponta.", "backup_output_directory_required": "Você deve especificar um diretório de saída para o backup" -} +} \ No newline at end of file diff --git a/locales/uk.json b/locales/uk.json index 70afd39ad..35923908f 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -13,7 +13,6 @@ "app_change_url_success": "URL-адреса {app} тепер {domain}{path}", "app_change_url_no_script": "Застосунок '{app_name}' поки не підтримує зміну URL-адрес. Можливо, вам слід оновити його.", "app_change_url_identical_domains": "Старий і новий domain/url_path збігаються ('{domain}{path}'), нічого робити не треба.", - "app_change_url_failed_nginx_reload": "Не вдалося перезавантажити NGINX. Ось результат 'nginx -t':\n{nginx_errors}", "app_argument_required": "Аргумент '{name}' необхідний", "app_argument_password_no_default": "Помилка під час розбору аргументу пароля '{name}': аргумент пароля не може мати типове значення з причин безпеки", "app_argument_invalid": "Виберіть правильне значення для аргументу '{name}': {error}", @@ -127,7 +126,6 @@ "permission_already_allowed": "Група '{group}' вже має увімкнений дозвіл '{permission}'", "pattern_password_app": "На жаль, паролі не можуть містити такі символи: {forbidden_chars}", "pattern_username": "Має складатися тільки з букв і цифр в нижньому регістрі і символів підкреслення", - "pattern_positive_number": "Має бути додатним числом", "pattern_port_or_range": "Має бути припустимий номер порту (наприклад, 0-65535) або діапазон портів (наприклад, 100:200)", "pattern_password": "Має бути довжиною не менше 3 символів", "pattern_mailbox_quota": "Має бути розмір з суфіксом b/k/M/G/T або 0, щоб не мати квоти", @@ -236,8 +234,6 @@ "log_backup_restore_system": "Відновлення системи з резервного архіву", "log_backup_create": "Створення резервного архіву", "log_available_on_yunopaste": "Цей журнал тепер доступний за посиланням {url}", - "log_app_config_apply": "Застосування конфігурації до застосунку '{}'", - "log_app_config_show_panel": "Показ панелі конфігурації застосунку '{}'", "log_app_action_run": "Запуск дії застосунку «{}»", "log_app_makedefault": "Застосунок '{}' зроблено типовим", "log_app_upgrade": "Оновлення застосунку '{}'", @@ -328,7 +324,6 @@ "downloading": "Завантаження…", "done": "Готово", "domains_available": "Доступні домени:", - "domain_unknown": "Невідомий домен", "domain_name_unknown": "Домен '{domain}' невідомий", "domain_uninstall_app_first": "Ці застосунки все ще встановлені на вашому домені:\n{apps}\n\nВидаліть їх за допомогою 'yunohost app remove the_app_id' або перемістіть їх на інший домен за допомогою 'yunohost app change-url the_app_id', перш ніж приступити до вилучення домену", "domain_remove_confirm_apps_removal": "Вилучення цього домену призведе до вилучення таких застосунків:\n{apps}\n\nВи впевнені, що хочете це зробити? [{answers}]", @@ -681,4 +676,4 @@ "service_not_reloading_because_conf_broken": "Неможливо перезавантажити/перезапустити службу '{name}', тому що її конфігурацію порушено: {errors}", "app_argument_password_help_optional": "Введіть один пробіл, щоб очистити пароль", "app_argument_password_help_keep": "Натисніть Enter, щоб зберегти поточне значення" -} +} \ No newline at end of file diff --git a/locales/zh_Hans.json b/locales/zh_Hans.json index c79afd22a..9176ebab9 100644 --- a/locales/zh_Hans.json +++ b/locales/zh_Hans.json @@ -150,7 +150,6 @@ "app_change_url_success": "{app} URL现在为 {domain}{path}", "app_change_url_no_script": "应用程序'{app_name}'尚不支持URL修改. 也许您应该升级它。", "app_change_url_identical_domains": "新旧domain / url_path是相同的('{domain}{path}'),无需执行任何操作。", - "app_change_url_failed_nginx_reload": "无法重新加载NGINX. 这是'nginx -t'的输出:\n{nginx_errors}", "app_argument_required": "参数'{name}'为必填项", "app_argument_password_no_default": "解析密码参数'{name}'时出错:出于安全原因,密码参数不能具有默认值", "app_argument_invalid": "为参数'{name}'选择一个有效值: {error}", @@ -360,7 +359,6 @@ "downloading": "下载中…", "done": "完成", "domains_available": "可用域:", - "domain_unknown": "未知网域", "domain_name_unknown": "域'{domain}'未知", "domain_uninstall_app_first": "这些应用程序仍安装在您的域中:\n{apps}\n\n请先使用 'yunohost app remove the_app_id' 将其卸载,或使用 'yunohost app change-url the_app_id'将其移至另一个域,然后再继续删除域", "domain_remove_confirm_apps_removal": "删除该域将删除这些应用程序:\n{apps}\n\n您确定要这样做吗? [{answers}]", @@ -499,8 +497,6 @@ "diagnosis_dns_discrepancy": "以下DNS记录似乎未遵循建议的配置:
类型: {type}
名称: {name}
代码> 当前值: {current}期望值: {value}", "log_backup_create": "创建备份档案", "log_available_on_yunopaste": "现在可以通过{url}使用此日志", - "log_app_config_apply": "将配置应用于 '{}' 应用", - "log_app_config_show_panel": "显示 '{}' 应用的配置面板", "log_app_action_run": "运行 '{}' 应用的操作", "log_app_makedefault": "将 '{}' 设为默认应用", "log_app_upgrade": "升级 '{}' 应用", @@ -541,7 +537,6 @@ "permission_already_allowed": "群组 '{group}' 已启用权限'{permission}'", "pattern_password_app": "抱歉,密码不能包含以下字符: {forbidden_chars}", "pattern_username": "只能为小写字母数字和下划线字符", - "pattern_positive_number": "必须为正数", "pattern_port_or_range": "必须是有效的端口号(即0-65535)或端口范围(例如100:200)", "pattern_password": "必须至少3个字符长", "pattern_mailbox_quota": "必须为带b/k/M/G/T 后缀的大小或0,才能没有配额", @@ -631,4 +626,4 @@ "log_user_group_create": "创建组'{}'", "log_user_delete": "删除用户'{}'", "log_user_create": "添加用户'{}'" -} +} \ No newline at end of file From 1c46636b7e810ab00727901e6e7f9adbf6b496b9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 17 Sep 2021 18:27:16 +0200 Subject: [PATCH 3131/3170] tests: add mypy + misc fixes to make test pass --- .gitlab/ci/lint.gitlab-ci.yml | 7 +++++++ src/yunohost/domain.py | 3 ++- src/yunohost/log.py | 3 ++- src/yunohost/tools.py | 3 ++- src/yunohost/utils/config.py | 5 +++-- src/yunohost/utils/dns.py | 4 +++- tox.ini | 2 ++ 7 files changed, 21 insertions(+), 6 deletions(-) diff --git a/.gitlab/ci/lint.gitlab-ci.yml b/.gitlab/ci/lint.gitlab-ci.yml index 9c48bd912..aaddb5a0a 100644 --- a/.gitlab/ci/lint.gitlab-ci.yml +++ b/.gitlab/ci/lint.gitlab-ci.yml @@ -19,6 +19,13 @@ invalidcode37: script: - tox -e py37-invalidcode +mypy: + stage: lint + image: "before-install" + needs: [] + script: + - tox -e py37-mypy + format-check: stage: lint image: "before-install" diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 5c67fce1a..1f96ced8a 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -24,6 +24,7 @@ Manage domains """ import os +from typing import Dict, Any from moulinette import m18n, Moulinette from moulinette.core import MoulinetteError @@ -47,7 +48,7 @@ DOMAIN_CONFIG_PATH = "/usr/share/yunohost/other/config_domain.toml" DOMAIN_SETTINGS_DIR = "/etc/yunohost/domains" # Lazy dev caching to avoid re-query ldap every time we need the domain list -domain_list_cache = {} +domain_list_cache: Dict[str, Any] = {} def domain_list(exclude_subdomains=False): diff --git a/src/yunohost/log.py b/src/yunohost/log.py index f40470063..c99c1bbc9 100644 --- a/src/yunohost/log.py +++ b/src/yunohost/log.py @@ -29,6 +29,7 @@ import re import yaml import glob import psutil +from typing import List from datetime import datetime, timedelta from logging import FileHandler, getLogger, Formatter @@ -478,7 +479,7 @@ class OperationLogger(object): This class record logs and metadata like context or start time/end time. """ - _instances = [] + _instances: List[object] = [] def __init__(self, operation, related_to=None, **kwargs): # TODO add a way to not save password on app installation diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 4190e7614..bd256ff2b 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -29,6 +29,7 @@ import subprocess import time from importlib import import_module from packaging import version +from typing import List from moulinette import Moulinette, m18n from moulinette.utils.log import getActionLogger @@ -1113,7 +1114,7 @@ class Migration(object): # Those are to be implemented by daughter classes mode = "auto" - dependencies = [] # List of migration ids required before running this migration + dependencies: List[str] = [] # List of migration ids required before running this migration @property def disclaimer(self): diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index a6c3b299e..99c898d15 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -25,6 +25,7 @@ import urllib.parse import tempfile import shutil from collections import OrderedDict +from typing import Optional, Dict, List from moulinette.interfaces.cli import colorize from moulinette import Moulinette, m18n @@ -454,7 +455,7 @@ class ConfigPanel: class Question(object): hide_user_input_in_prompt = False - pattern = None + pattern: Optional[Dict] = None def __init__(self, question, user_answers): self.name = question["name"] @@ -940,7 +941,7 @@ class DisplayTextQuestion(Question): class FileQuestion(Question): argument_type = "file" - upload_dirs = [] + upload_dirs: List[str] = [] @classmethod def clean_upload_dirs(cls): diff --git a/src/yunohost/utils/dns.py b/src/yunohost/utils/dns.py index 095e5000a..3db75f949 100644 --- a/src/yunohost/utils/dns.py +++ b/src/yunohost/utils/dns.py @@ -19,13 +19,15 @@ """ import dns.resolver +from typing import List + from moulinette.utils.filesystem import read_file YNH_DYNDNS_DOMAINS = ["nohost.me", "noho.st", "ynh.fr"] # Lazy dev caching to avoid re-reading the file multiple time when calling # dig() often during same yunohost operation -external_resolvers_ = [] +external_resolvers_: List[str] = [] def external_resolvers(): diff --git a/tox.ini b/tox.ini index c25d8bf8f..e79c70fec 100644 --- a/tox.ini +++ b/tox.ini @@ -6,8 +6,10 @@ skip_install=True deps = py37-{lint,invalidcode}: flake8 py37-black-{run,check}: black + py37-mypy: mypy >= 0.900 commands = py37-lint: flake8 src doc data tests --ignore E402,E501,E203,W503 --exclude src/yunohost/vendor py37-invalidcode: flake8 src data --exclude src/yunohost/tests,src/yunohost/vendor --select F py37-black-check: black --check --diff src doc data tests py37-black-run: black src doc data tests + py37-mypy: mypy --ignore-missing-import --install-types --non-interactive --follow-imports silent src/yunohost/ --exclude (acme_tiny|data_migrations) From 75b91d7662851bff8228ab92059e8024488105c1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 19 Sep 2021 23:36:43 +0200 Subject: [PATCH 3132/3170] Zblerg fix global var handling --- src/yunohost/backup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 5089c5979..a47fba5f7 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -44,7 +44,7 @@ from moulinette.utils.log import getActionLogger from moulinette.utils.filesystem import read_file, mkdir, write_to_yaml, read_yaml from moulinette.utils.process import check_output -from yunohost.domain import domain_list_cache +import yunohost.domain from yunohost.app import ( app_info, _is_installed, @@ -1287,7 +1287,7 @@ class RestoreManager: else: operation_logger.success() - domain_list_cache = {} + yunohost.domain.domain_list_cache = {} regen_conf() From 92fefd6d9716c614a8148adf58e4ced7bf263a37 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 20 Sep 2021 00:39:46 +0200 Subject: [PATCH 3133/3170] Fix tests --- src/yunohost/tests/test_dns.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/yunohost/tests/test_dns.py b/src/yunohost/tests/test_dns.py index c39b0ad4b..b142b5a6b 100644 --- a/src/yunohost/tests/test_dns.py +++ b/src/yunohost/tests/test_dns.py @@ -36,8 +36,10 @@ def test_get_dns_zone_from_domain_existing(): ) assert _get_dns_zone_for_domain("yolo.nohost.me") == "nohost.me" assert _get_dns_zone_for_domain("foo.yolo.nohost.me") == "nohost.me" - assert _get_dns_zone_for_domain("yolo.test") == "yolo.test" + assert _get_dns_zone_for_domain("yolo.test") == "test" assert _get_dns_zone_for_domain("foo.yolo.test") == "test" + assert _get_dns_zone_for_domain("yolo.tld") == "yolo.tld" + assert _get_dns_zone_for_domain("foo.yolo.tld") == "yolo.tld" # Domain registrar testing From f4793a6f0a31b9f69fe1b4bdb3b0cc0cf66c0df5 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 19 Sep 2021 23:57:45 +0200 Subject: [PATCH 3134/3170] Update changelog for 4.3.0 --- debian/changelog | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/debian/changelog b/debian/changelog index a1e7c8f83..e6bd5180e 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,22 @@ +yunohost (4.3.0) testing; urgency=low + + - [users] Import/export users from/to CSV ([#1089](https://github.com/YunoHost/yunohost/pull/1089)) + - [domain] Add mDNS for .local domains / replace avahi-daemon ([#1112](https://github.com/YunoHost/yunohost/pull/1112)) + - [settings] new setting to enable experimental security features ([#1290](https://github.com/YunoHost/yunohost/pull/1290)) + - [settings] new setting to handle https redirect ([#1304](https://github.com/YunoHost/yunohost/pull/1304)) + - [diagnosis] add an "app" section to check that app are in catalog with good quality, check for deprecated practices ([#1217](https://github.com/YunoHost/yunohost/pull/1217)) + - [diagnosis] report suspiciously high number of auth failures ([#1292](https://github.com/YunoHost/yunohost/pull/1292)) + - [refactor] Rework the authentication system ([#1183](https://github.com/YunoHost/yunohost/pull/1183)) + - [enh] New config-panel mechanism ([#987](https://github.com/YunoHost/yunohost/pull/987)) + - [enh] Add backup for multimedia files (88063dc7) + - [enh] Configure automatically the DNS records using lexicon ([#1315](https://github.com/YunoHost/yunohost/pull/1315)) + - also brings domain settings, domain config panel, subdomain awareness, improvements in dns recommended conf + - [i18n] Translations updated for Catalan, Chinese (Simplified), Czech, Esperanto, French, Galician, German, Italian, Occitan, Persian, Portuguese, Spanish, Ukrainian + + Thanks to all contributors <3 ! (Corentin Mercier, Daniel, Éric Gaspar, Flavio Cristoforetti, Gregor Lenz, José M, Kay0u, ljf, MercierCorentin, mifegui, Paco, Parviz Homayun, ppr, tituspijean, Tymofii-Lytvynenko) + + -- Alexandre Aubin Sun, 19 Sep 2021 23:55:21 +0200 + yunohost (4.2.8.3) stable; urgency=low - [fix] mysql: Another bump for sort_buffer_size to make Nextcloud 22 work (34e9246b) From 636fc1cf6d35ed2bdecb023663446814c66167f0 Mon Sep 17 00:00:00 2001 From: Kayou Date: Mon, 20 Sep 2021 11:49:49 +0200 Subject: [PATCH 3135/3170] [Fix] App diagnosis grep --- data/hooks/diagnosis/80-apps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/hooks/diagnosis/80-apps.py b/data/hooks/diagnosis/80-apps.py index 177ec590f..a75193a45 100644 --- a/data/hooks/diagnosis/80-apps.py +++ b/data/hooks/diagnosis/80-apps.py @@ -76,7 +76,7 @@ class AppDiagnoser(Diagnoser): for deprecated_helper in deprecated_helpers: if ( os.system( - f"grep -nr -q '{deprecated_helper}' {app['setting_path']}/scripts/" + f"grep -hr '{deprecated_helper}' {app['setting_path']}/scripts/ | grep -v -q '^\s*#'" ) == 0 ): From d77a1071bc8348068c6eb11589c92646c5018a3f Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Mon, 20 Sep 2021 11:41:19 +0000 Subject: [PATCH 3136/3170] [CI] Format code --- src/yunohost/tools.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index bd256ff2b..ed8c04153 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -1114,7 +1114,9 @@ class Migration(object): # Those are to be implemented by daughter classes mode = "auto" - dependencies: List[str] = [] # List of migration ids required before running this migration + dependencies: List[ + str + ] = [] # List of migration ids required before running this migration @property def disclaimer(self): From 2edca5bd22edc94b6bda793b2943401328402cec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= <46165813+ericgaspar@users.noreply.github.com> Date: Mon, 20 Sep 2021 15:14:10 +0200 Subject: [PATCH 3137/3170] Typo - Fix typo --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 4919efa98..4601bd92f 100644 --- a/locales/en.json +++ b/locales/en.json @@ -321,7 +321,7 @@ "domain_dns_push_not_applicable": "The automatic DNS configuration feature is not applicable to domain {domain}. You should manually configure your DNS records following the documentation at https://yunohost.org/dns_config.", "domain_dns_push_managed_in_parent_domain": "The automatic DNS configuration feature is managed in the parent domain {parent_domain}.", "domain_dns_registrar_managed_in_parent_domain": "This domain is a subdomain of {parent_domain_link}. DNS registrar configuration should be managed in {parent_domain}'s configuration panel.", - "domain_dns_registrar_yunohost": "This domain is a nohost.me / nohost.st / ynh.fr and its DNS configuration is therefore automatically handled by Yunohost without any further configuration. (see the 'yunohost dyndns update' command)", + "domain_dns_registrar_yunohost": "This domain is a nohost.me / nohost.st / ynh.fr and its DNS configuration is therefore automatically handled by YunoHost without any further configuration. (see the 'yunohost dyndns update' command)", "domain_dns_registrar_not_supported": "YunoHost could not automatically detect the registrar handling this domain. You should manually configure your DNS records following the documentation at https://yunohost.org/dns.", "domain_dns_registrar_supported": "YunoHost automatically detected that this domain is handled by the registrar **{registrar}**. If you want, YunoHost will automatically configure this DNS zone, if you provide it with the appropriate API credentials. You can find documentation on how to obtain your API credentials on this page: https://yunohost.org/registar_api_{registrar}. (You can also manually configure your DNS records following the documentation at https://yunohost.org/dns )", "domain_dns_registrar_experimental": "So far, the interface with **{registrar}**'s API has not been properly tested and reviewed by the YunoHost community. Support is **very experimental** - be careful!", From 8c7080de5fc8d0954e8f05a3c5afd634b5b55b9f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 20 Sep 2021 23:25:54 +0200 Subject: [PATCH 3138/3170] Simplify debian/install --- data/hooks/conf_regen/01-yunohost | 29 +++++++++++++++++-- .../yunohost/dpkg-origins} | 0 .../yunohost}/yunoprompt.service | 0 debian/install | 14 +-------- debian/postinst | 11 ++----- 5 files changed, 31 insertions(+), 23 deletions(-) rename data/{other/dpkg-origins/yunohost => templates/yunohost/dpkg-origins} (100%) rename data/{other => templates/yunohost}/yunoprompt.service (100%) diff --git a/data/hooks/conf_regen/01-yunohost b/data/hooks/conf_regen/01-yunohost index 445faa5a4..7a852db5f 100755 --- a/data/hooks/conf_regen/01-yunohost +++ b/data/hooks/conf_regen/01-yunohost @@ -55,6 +55,15 @@ do_init_regen() { mkdir -p /var/cache/yunohost/repo chown root:root /var/cache/yunohost chmod 700 /var/cache/yunohost + + cp yunoprompt.service /etc/systemd/system/yunoprompt.service + cp dpkg-origins /etc/dpkg/origins/yunohost + + # Change dpkg vendor + # see https://wiki.debian.org/Derivatives/Guidelines#Vendor + readlink -f /etc/dpkg/origins/default | grep -q debian \ + && rm -f /etc/dpkg/origins/default \ + && ln -s /etc/dpkg/origins/yunohost /etc/dpkg/origins/default } do_pre_regen() { @@ -66,6 +75,7 @@ do_pre_regen() { touch /etc/yunohost/services.yml yunohost tools shell -c "from yunohost.service import _get_services, _save_services; _save_services(_get_services())" + mkdir -p $pending_dir/etc/systemd/system mkdir -p $pending_dir/etc/cron.d/ mkdir -p $pending_dir/etc/cron.daily/ @@ -131,8 +141,9 @@ HandleLidSwitch=ignore HandleLidSwitchDocked=ignore HandleLidSwitchExternalPower=ignore EOF - - mkdir -p ${pending_dir}/etc/systemd/ + + cp yunoprompt.service ${pending_dir}/etc/systemd/system/yunoprompt.service + if [[ "$(yunohost settings get 'security.experimental.enabled')" == "True" ]] then cp proc-hidepid.service ${pending_dir}/etc/systemd/system/proc-hidepid.service @@ -140,6 +151,8 @@ EOF touch ${pending_dir}/etc/systemd/system/proc-hidepid.service fi + cp dpkg-origins ${pending_dir}/etc/dpkg/origins/yunohost + } do_post_regen() { @@ -203,12 +216,24 @@ do_post_regen() { [[ ! "$regen_conf_files" =~ "ntp.service.d/ynh-override.conf" ]] || { systemctl daemon-reload; systemctl restart ntp; } [[ ! "$regen_conf_files" =~ "nftables.service.d/ynh-override.conf" ]] || systemctl daemon-reload [[ ! "$regen_conf_files" =~ "login.conf.d/ynh-override.conf" ]] || systemctl daemon-reload + if [[ "$regen_conf_files" =~ "yunoprompt.service" ]] + then + systemctl daemon-reload + action=$([[ -e /etc/systemd/system/yunoprompt.service ]] && echo 'enable' || echo 'disable') + systemctl $action yunoprompt --quiet --now + fi if [[ "$regen_conf_files" =~ "proc-hidepid.service" ]] then systemctl daemon-reload action=$([[ -e /etc/systemd/system/proc-hidepid.service ]] && echo 'enable' || echo 'disable') systemctl $action proc-hidepid --quiet --now fi + + # Change dpkg vendor + # see https://wiki.debian.org/Derivatives/Guidelines#Vendor + readlink -f /etc/dpkg/origins/default | grep -q debian \ + && rm -f /etc/dpkg/origins/default \ + && ln -s /etc/dpkg/origins/yunohost /etc/dpkg/origins/default } FORCE=${2:-0} diff --git a/data/other/dpkg-origins/yunohost b/data/templates/yunohost/dpkg-origins similarity index 100% rename from data/other/dpkg-origins/yunohost rename to data/templates/yunohost/dpkg-origins diff --git a/data/other/yunoprompt.service b/data/templates/yunohost/yunoprompt.service similarity index 100% rename from data/other/yunoprompt.service rename to data/templates/yunohost/yunoprompt.service diff --git a/debian/install b/debian/install index a653a40ba..8c6ba01dd 100644 --- a/debian/install +++ b/debian/install @@ -1,20 +1,8 @@ bin/* /usr/bin/ sbin/* /usr/sbin/ +data/* /usr/share/yunohost/ data/bash-completion.d/yunohost /etc/bash_completion.d/ doc/yunohost.8.gz /usr/share/man/man8/ -data/actionsmap/* /usr/share/moulinette/actionsmap/ -data/hooks/* /usr/share/yunohost/hooks/ -data/other/yunoprompt.service /etc/systemd/system/ -data/other/password/* /usr/share/yunohost/other/password/ -data/other/dpkg-origins/yunohost /etc/dpkg/origins -data/other/dnsbl_list.yml /usr/share/yunohost/other/ -data/other/config_domain.toml /usr/share/yunohost/other/ -data/other/registrar_list.toml /usr/share/yunohost/other/ -data/other/ffdhe2048.pem /usr/share/yunohost/other/ -data/other/* /usr/share/yunohost/yunohost-config/moulinette/ -data/templates/* /usr/share/yunohost/templates/ -data/helpers /usr/share/yunohost/ -data/helpers.d/* /usr/share/yunohost/helpers.d/ lib/metronome/modules/* /usr/lib/metronome/modules/ locales/* /usr/lib/moulinette/yunohost/locales/ src/yunohost /usr/lib/moulinette diff --git a/debian/postinst b/debian/postinst index 8fc00bbe2..ceeed3cdf 100644 --- a/debian/postinst +++ b/debian/postinst @@ -5,6 +5,9 @@ set -e do_configure() { rm -rf /var/cache/moulinette/* + mkdir -p /usr/share/moulinette/actionsmap/ + ln -sf /usr/share/yunohost/actionsmap/yunohost.yml /usr/share/moulinette/actionsmap/yunohost.yml + if [ ! -f /etc/yunohost/installed ]; then # If apps/ is not empty, we're probably already installed in the past and # something funky happened ... @@ -31,14 +34,6 @@ do_configure() { yunohost diagnosis run --force fi - # Change dpkg vendor - # see https://wiki.debian.org/Derivatives/Guidelines#Vendor - readlink -f /etc/dpkg/origins/default | grep -q debian \ - && rm -f /etc/dpkg/origins/default \ - && ln -s /etc/dpkg/origins/yunohost /etc/dpkg/origins/default - - # Yunoprompt - systemctl enable yunoprompt.service } # summary of how this script can be called: From bddd81f44b57d2acd8266e7c576984ef2cfaa4cc Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 21 Sep 2021 00:05:19 +0200 Subject: [PATCH 3139/3170] Simplify regen conf scripts --- data/hooks/conf_regen/01-yunohost | 21 +-------------------- data/hooks/conf_regen/02-ssl | 23 +---------------------- data/hooks/conf_regen/03-ssh | 18 +----------------- data/hooks/conf_regen/06-slapd | 21 +-------------------- data/hooks/conf_regen/09-nslcd | 21 +-------------------- data/hooks/conf_regen/10-apt | 18 +----------------- data/hooks/conf_regen/12-metronome | 18 +----------------- data/hooks/conf_regen/15-nginx | 21 +-------------------- data/hooks/conf_regen/19-postfix | 18 +----------------- data/hooks/conf_regen/25-dovecot | 18 +----------------- data/hooks/conf_regen/31-rspamd | 18 +----------------- data/hooks/conf_regen/34-mysql | 2 -- data/hooks/conf_regen/35-redis | 18 +----------------- data/hooks/conf_regen/37-mdns | 21 +-------------------- data/hooks/conf_regen/43-dnsmasq | 18 +----------------- data/hooks/conf_regen/46-nsswitch | 21 +-------------------- data/hooks/conf_regen/52-fail2ban | 18 +----------------- src/yunohost/regenconf.py | 13 ++++--------- 18 files changed, 20 insertions(+), 306 deletions(-) diff --git a/data/hooks/conf_regen/01-yunohost b/data/hooks/conf_regen/01-yunohost index 445faa5a4..1c1a814bf 100755 --- a/data/hooks/conf_regen/01-yunohost +++ b/data/hooks/conf_regen/01-yunohost @@ -211,23 +211,4 @@ do_post_regen() { fi } -FORCE=${2:-0} -DRY_RUN=${3:-0} - -case "$1" in - pre) - do_pre_regen $4 - ;; - post) - do_post_regen $4 - ;; - init) - do_init_regen - ;; - *) - echo "hook called with unknown argument \`$1'" >&2 - exit 1 - ;; -esac - -exit 0 +do_$1_regen ${@:2} diff --git a/data/hooks/conf_regen/02-ssl b/data/hooks/conf_regen/02-ssl index 6536e7280..2b40c77a2 100755 --- a/data/hooks/conf_regen/02-ssl +++ b/data/hooks/conf_regen/02-ssl @@ -48,8 +48,6 @@ regen_local_ca() { popd } - - do_init_regen() { LOGFILE=/tmp/yunohost-ssl-init @@ -121,23 +119,4 @@ do_post_regen() { fi } -FORCE=${2:-0} -DRY_RUN=${3:-0} - -case "$1" in - pre) - do_pre_regen $4 - ;; - post) - do_post_regen $4 - ;; - init) - do_init_regen - ;; - *) - echo "hook called with unknown argument \`$1'" >&2 - exit 1 - ;; -esac - -exit 0 +do_$1_regen ${@:2} diff --git a/data/hooks/conf_regen/03-ssh b/data/hooks/conf_regen/03-ssh index d0c4bd31c..f10dbb653 100755 --- a/data/hooks/conf_regen/03-ssh +++ b/data/hooks/conf_regen/03-ssh @@ -48,20 +48,4 @@ do_post_regen() { systemctl restart ssh } -FORCE=${2:-0} -DRY_RUN=${3:-0} - -case "$1" in - pre) - do_pre_regen $4 - ;; - post) - do_post_regen $4 - ;; - *) - echo "hook called with unknown argument \`$1'" >&2 - exit 1 - ;; -esac - -exit 0 +do_$1_regen ${@:2} diff --git a/data/hooks/conf_regen/06-slapd b/data/hooks/conf_regen/06-slapd index b2439dcf9..49b1bf354 100755 --- a/data/hooks/conf_regen/06-slapd +++ b/data/hooks/conf_regen/06-slapd @@ -199,23 +199,4 @@ objectClass: top" done } -FORCE=${2:-0} -DRY_RUN=${3:-0} - -case "$1" in - pre) - do_pre_regen $4 - ;; - post) - do_post_regen $4 - ;; - init) - do_init_regen - ;; - *) - echo "hook called with unknown argument \`$1'" >&2 - exit 1 - ;; -esac - -exit 0 +do_$1_regen ${@:2} diff --git a/data/hooks/conf_regen/09-nslcd b/data/hooks/conf_regen/09-nslcd index 2e911b328..cefd05cd3 100755 --- a/data/hooks/conf_regen/09-nslcd +++ b/data/hooks/conf_regen/09-nslcd @@ -22,23 +22,4 @@ do_post_regen() { || systemctl restart nslcd } -FORCE=${2:-0} -DRY_RUN=${3:-0} - -case "$1" in - pre) - do_pre_regen $4 - ;; - post) - do_post_regen $4 - ;; - init) - do_init_regen - ;; - *) - echo "hook called with unknown argument \`$1'" >&2 - exit 1 - ;; -esac - -exit 0 +do_$1_regen ${@:2} diff --git a/data/hooks/conf_regen/10-apt b/data/hooks/conf_regen/10-apt index d2977ee92..1c80b6706 100755 --- a/data/hooks/conf_regen/10-apt +++ b/data/hooks/conf_regen/10-apt @@ -54,20 +54,4 @@ do_post_regen() { update-alternatives --set php /usr/bin/php7.3 } -FORCE=${2:-0} -DRY_RUN=${3:-0} - -case "$1" in - pre) - do_pre_regen $4 - ;; - post) - do_post_regen $4 - ;; - *) - echo "hook called with unknown argument \`$1'" >&2 - exit 1 - ;; -esac - -exit 0 +do_$1_regen ${@:2} diff --git a/data/hooks/conf_regen/12-metronome b/data/hooks/conf_regen/12-metronome index 9820f9881..ab9fca173 100755 --- a/data/hooks/conf_regen/12-metronome +++ b/data/hooks/conf_regen/12-metronome @@ -70,20 +70,4 @@ do_post_regen() { || systemctl restart metronome } -FORCE=${2:-0} -DRY_RUN=${3:-0} - -case "$1" in - pre) - do_pre_regen $4 - ;; - post) - do_post_regen $4 - ;; - *) - echo "hook called with unknown argument \`$1'" >&2 - exit 1 - ;; -esac - -exit 0 +do_$1_regen ${@:2} diff --git a/data/hooks/conf_regen/15-nginx b/data/hooks/conf_regen/15-nginx index 0c41ea50b..c158ecd09 100755 --- a/data/hooks/conf_regen/15-nginx +++ b/data/hooks/conf_regen/15-nginx @@ -149,23 +149,4 @@ do_post_regen() { pgrep nginx && systemctl reload nginx || { journalctl --no-pager --lines=10 -u nginx >&2; exit 1; } } -FORCE=${2:-0} -DRY_RUN=${3:-0} - -case "$1" in - pre) - do_pre_regen $4 - ;; - post) - do_post_regen $4 - ;; - init) - do_init_regen - ;; - *) - echo "hook called with unknown argument \`$1'" >&2 - exit 1 - ;; -esac - -exit 0 +do_$1_regen ${@:2} diff --git a/data/hooks/conf_regen/19-postfix b/data/hooks/conf_regen/19-postfix index 166b5d5e9..c569e1ca1 100755 --- a/data/hooks/conf_regen/19-postfix +++ b/data/hooks/conf_regen/19-postfix @@ -80,20 +80,4 @@ do_post_regen() { } -FORCE=${2:-0} -DRY_RUN=${3:-0} - -case "$1" in - pre) - do_pre_regen $4 - ;; - post) - do_post_regen $4 - ;; - *) - echo "hook called with unknown argument \`$1'" >&2 - exit 1 - ;; -esac - -exit 0 +do_$1_regen ${@:2} diff --git a/data/hooks/conf_regen/25-dovecot b/data/hooks/conf_regen/25-dovecot index 916b88c35..a0663a4a6 100755 --- a/data/hooks/conf_regen/25-dovecot +++ b/data/hooks/conf_regen/25-dovecot @@ -63,20 +63,4 @@ do_post_regen() { systemctl restart dovecot } -FORCE=${2:-0} -DRY_RUN=${3:-0} - -case "$1" in - pre) - do_pre_regen $4 - ;; - post) - do_post_regen $4 - ;; - *) - echo "hook called with unknown argument \`$1'" >&2 - exit 1 - ;; -esac - -exit 0 +do_$1_regen ${@:2} diff --git a/data/hooks/conf_regen/31-rspamd b/data/hooks/conf_regen/31-rspamd index 87ed722a7..da9b35dfe 100755 --- a/data/hooks/conf_regen/31-rspamd +++ b/data/hooks/conf_regen/31-rspamd @@ -59,20 +59,4 @@ do_post_regen() { systemctl -q restart rspamd.service } -FORCE=${2:-0} -DRY_RUN=${3:-0} - -case "$1" in - pre) - do_pre_regen $4 - ;; - post) - do_post_regen $4 - ;; - *) - echo "hook called with unknown argument \`$1'" >&2 - exit 1 - ;; -esac - -exit 0 +do_$1_regen ${@:2} diff --git a/data/hooks/conf_regen/34-mysql b/data/hooks/conf_regen/34-mysql index d5180949e..60361cfd5 100755 --- a/data/hooks/conf_regen/34-mysql +++ b/data/hooks/conf_regen/34-mysql @@ -69,8 +69,6 @@ do_post_regen() { || systemctl restart mysql } -FORCE=${2:-0} -DRY_RUN=${3:-0} case "$1" in pre) diff --git a/data/hooks/conf_regen/35-redis b/data/hooks/conf_regen/35-redis index 10358cefc..da5eac4c9 100755 --- a/data/hooks/conf_regen/35-redis +++ b/data/hooks/conf_regen/35-redis @@ -10,20 +10,4 @@ do_post_regen() { chown -R redis:adm /var/log/redis } -FORCE=${2:-0} -DRY_RUN=${3:-0} - -case "$1" in - pre) - do_pre_regen $4 - ;; - post) - do_post_regen $4 - ;; - *) - echo "hook called with unknown argument \`$1'" >&2 - exit 1 - ;; -esac - -exit 0 +do_$1_regen ${@:2} diff --git a/data/hooks/conf_regen/37-mdns b/data/hooks/conf_regen/37-mdns index 1d7381e26..17f7bb8e2 100755 --- a/data/hooks/conf_regen/37-mdns +++ b/data/hooks/conf_regen/37-mdns @@ -61,23 +61,4 @@ do_post_regen() { || systemctl restart yunomdns } -FORCE=${2:-0} -DRY_RUN=${3:-0} - -case "$1" in - pre) - do_pre_regen $4 - ;; - post) - do_post_regen $4 - ;; - init) - do_init_regen - ;; - *) - echo "hook called with unknown argument \`$1'" >&2 - exit 1 - ;; -esac - -exit 0 +do_$1_regen ${@:2} diff --git a/data/hooks/conf_regen/43-dnsmasq b/data/hooks/conf_regen/43-dnsmasq index e7b0531e8..f3bed7b04 100755 --- a/data/hooks/conf_regen/43-dnsmasq +++ b/data/hooks/conf_regen/43-dnsmasq @@ -80,20 +80,4 @@ do_post_regen() { systemctl restart dnsmasq } -FORCE=${2:-0} -DRY_RUN=${3:-0} - -case "$1" in - pre) - do_pre_regen $4 - ;; - post) - do_post_regen $4 - ;; - *) - echo "hook called with unknown argument \`$1'" >&2 - exit 1 - ;; -esac - -exit 0 +do_$1_regen ${@:2} diff --git a/data/hooks/conf_regen/46-nsswitch b/data/hooks/conf_regen/46-nsswitch index e6d998094..be5cb2b86 100755 --- a/data/hooks/conf_regen/46-nsswitch +++ b/data/hooks/conf_regen/46-nsswitch @@ -22,23 +22,4 @@ do_post_regen() { || systemctl restart unscd } -FORCE=${2:-0} -DRY_RUN=${3:-0} - -case "$1" in - pre) - do_pre_regen $4 - ;; - post) - do_post_regen $4 - ;; - init) - do_init_regen - ;; - *) - echo "hook called with unknown argument \`$1'" >&2 - exit 1 - ;; -esac - -exit 0 +do_$1_regen ${@:2} diff --git a/data/hooks/conf_regen/52-fail2ban b/data/hooks/conf_regen/52-fail2ban index c96940c94..7aef72ebc 100755 --- a/data/hooks/conf_regen/52-fail2ban +++ b/data/hooks/conf_regen/52-fail2ban @@ -27,20 +27,4 @@ do_post_regen() { || systemctl reload fail2ban } -FORCE=${2:-0} -DRY_RUN=${3:-0} - -case "$1" in - pre) - do_pre_regen $4 - ;; - post) - do_post_regen $4 - ;; - *) - echo "hook called with unknown argument \`$1'" >&2 - exit 1 - ;; -esac - -exit 0 +do_$1_regen ${@:2} diff --git a/src/yunohost/regenconf.py b/src/yunohost/regenconf.py index 0608bcf8c..ef3c29b32 100644 --- a/src/yunohost/regenconf.py +++ b/src/yunohost/regenconf.py @@ -105,13 +105,9 @@ def regen_conf( else: filesystem.mkdir(PENDING_CONF_DIR, 0o755, True) - # Format common hooks arguments - common_args = [1 if force else 0, 1 if dry_run else 0] - # Execute hooks for pre-regen - pre_args = [ - "pre", - ] + common_args + # element 2 and 3 with empty string is because of legacy... + pre_args = ["pre", "", ""] def _pre_call(name, priority, path, args): # create the pending conf directory for the category @@ -417,9 +413,8 @@ def regen_conf( return result # Execute hooks for post-regen - post_args = [ - "post", - ] + common_args + # element 2 and 3 with empty string is because of legacy... + post_args = ["post", "", ""] def _pre_call(name, priority, path, args): # append coma-separated applied changes for the category From 8f3f5ab2510bd48df389fafb7d1655336c19ffab Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 21 Sep 2021 02:37:11 +0200 Subject: [PATCH 3140/3170] Forgot to update mysql hook --- data/hooks/conf_regen/34-mysql | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/data/hooks/conf_regen/34-mysql b/data/hooks/conf_regen/34-mysql index 60361cfd5..41afda110 100755 --- a/data/hooks/conf_regen/34-mysql +++ b/data/hooks/conf_regen/34-mysql @@ -69,18 +69,4 @@ do_post_regen() { || systemctl restart mysql } - -case "$1" in - pre) - do_pre_regen $4 - ;; - post) - do_post_regen $4 - ;; - *) - echo "hook called with unknown argument \`$1'" >&2 - exit 1 - ;; -esac - -exit 0 +do_$1_regen ${@:2} From 16e53ec2ec5cbc7cb43999abd1334a5a32d866ae Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 21 Sep 2021 12:34:25 +0200 Subject: [PATCH 3141/3170] For some reason .test domain results appears to be unreliable, so let's remove it.. --- src/yunohost/tests/test_dns.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/yunohost/tests/test_dns.py b/src/yunohost/tests/test_dns.py index b142b5a6b..497cab2fd 100644 --- a/src/yunohost/tests/test_dns.py +++ b/src/yunohost/tests/test_dns.py @@ -36,8 +36,6 @@ def test_get_dns_zone_from_domain_existing(): ) assert _get_dns_zone_for_domain("yolo.nohost.me") == "nohost.me" assert _get_dns_zone_for_domain("foo.yolo.nohost.me") == "nohost.me" - assert _get_dns_zone_for_domain("yolo.test") == "test" - assert _get_dns_zone_for_domain("foo.yolo.test") == "test" assert _get_dns_zone_for_domain("yolo.tld") == "yolo.tld" assert _get_dns_zone_for_domain("foo.yolo.tld") == "yolo.tld" @@ -48,7 +46,7 @@ def test_registrar_list_integrity(): def test_magic_guess_registrar_weird_domain(): - assert _get_registrar_config_section("yolo.test")["registrar"]["value"] is None + assert _get_registrar_config_section("yolo.tld")["registrar"]["value"] is None def test_magic_guess_registrar_ovh(): From bfa26ff46907f69ce8085e1da11e5e31ee9b2ba8 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 21 Sep 2021 15:43:17 +0200 Subject: [PATCH 3142/3170] Add .coveragerc which is autoused by pytest-cov --- src/yunohost/.coveragerc | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 src/yunohost/.coveragerc diff --git a/src/yunohost/.coveragerc b/src/yunohost/.coveragerc new file mode 100644 index 000000000..43e152271 --- /dev/null +++ b/src/yunohost/.coveragerc @@ -0,0 +1,2 @@ +[report] +omit=tests/*,vendor/*,/usr/lib/moulinette/yunohost/ From 2d158b5a6d20275bee9d76f0a8611e942b235f35 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 21 Sep 2021 20:21:05 +0200 Subject: [PATCH 3143/3170] Rework prompt() again --- locales/en.json | 2 +- src/yunohost/user.py | 2 +- src/yunohost/utils/config.py | 76 ++++++++++++++++-------------------- 3 files changed, 35 insertions(+), 45 deletions(-) diff --git a/locales/en.json b/locales/en.json index 4601bd92f..447f137a5 100644 --- a/locales/en.json +++ b/locales/en.json @@ -16,7 +16,7 @@ "app_argument_choice_invalid": "Use one of these choices '{choices}' for the argument '{name}' instead of '{value}'", "app_argument_invalid": "Pick a valid value for the argument '{name}': {error}", "app_argument_password_help_keep": "Press Enter to keep the current value", - "app_argument_password_help_optional": "Type one space to empty the password", + "app_argument_password_help_optional": "Enter a single space to empty the password", "app_argument_password_no_default": "Error while parsing password argument '{name}': password argument can't have a default value for security reason", "app_argument_required": "Argument '{name}' is required", "app_change_url_identical_domains": "The old and new domain/url_path are identical ('{domain}{path}'), nothing to do.", diff --git a/src/yunohost/user.py b/src/yunohost/user.py index b32e03dfa..22168d3e7 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -420,7 +420,7 @@ def user_update( # without a specified value, change_password will be set to the const 0. # In this case we prompt for the new password. if Moulinette.interface.type == "cli" and not change_password: - change_password = Moulinette.prompt(m18n.n("ask_password"), True, True) + change_password = Moulinette.prompt(m18n.n("ask_password"), is_password=True, confirm=True) # Ensure sufficiently complex password assert_password_is_strong_enough("user", change_password) diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 99c898d15..7b3d44b57 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -99,6 +99,9 @@ class ConfigPanel: result[key]["value"] = question_class.humanize( option["current_value"], option ) + # FIXME: semantics, technically here this is not about a prompt... + if question_class.hide_user_input_in_prompt: + result[key]["value"] = "**************" # Prevent displaying password in `config get` if mode == "full": return self.config @@ -480,6 +483,8 @@ class Question(object): @staticmethod def normalize(value, option={}): + if isinstance(value, str): + value = value.strip() return value def _prompt(self, text): @@ -491,9 +496,11 @@ class Question(object): self.value = Moulinette.prompt( message=text, is_password=self.hide_user_input_in_prompt, - confirm=False, # We doesn't want to confirm this kind of password like in webadmin + confirm=False, prefill=prefill, is_multiline=(self.type == "text"), + autocomplete=self.choices, + help=_value_for_locale(self.help) ) def ask_if_needed(self): @@ -558,18 +565,8 @@ class Question(object): choices=", ".join(self.choices), ) - def _format_text_for_user_input_in_cli(self, column=False): - text_for_user_input_in_cli = _value_for_locale(self.ask) - - if self.choices: - text_for_user_input_in_cli += " [{0}]".format(" | ".join(self.choices)) - - if self.help or column: - text_for_user_input_in_cli += ":\033[m" - if self.help: - text_for_user_input_in_cli += "\n - " - text_for_user_input_in_cli += _value_for_locale(self.help) - return text_for_user_input_in_cli + def _format_text_for_user_input_in_cli(self): + return _value_for_locale(self.ask) def _post_parse_value(self): if not self.redact: @@ -659,6 +656,8 @@ class TagsQuestion(Question): def normalize(value, option={}): if isinstance(value, list): return ",".join(value) + if isinstance(value, str): + value = value.strip() return value def _prevalidate(self): @@ -692,12 +691,6 @@ class PasswordQuestion(Question): "app_argument_password_no_default", name=self.name ) - @staticmethod - def humanize(value, option={}): - if value: - return "********" # Avoid to display the password on screen - return "" - def _prevalidate(self): super()._prevalidate() @@ -712,29 +705,6 @@ class PasswordQuestion(Question): assert_password_is_strong_enough("user", self.value) - def _format_text_for_user_input_in_cli(self): - need_column = self.current_value or self.optional - text_for_user_input_in_cli = super()._format_text_for_user_input_in_cli( - need_column - ) - if self.current_value: - text_for_user_input_in_cli += "\n - " + m18n.n( - "app_argument_password_help_keep" - ) - if self.optional: - text_for_user_input_in_cli += "\n - " + m18n.n( - "app_argument_password_help_optional" - ) - - return text_for_user_input_in_cli - - def _prompt(self, text): - super()._prompt(text) - if self.current_value and self.value == "": - self.value = self.current_value - elif self.value == " ": - self.value = "" - class PathQuestion(Question): argument_type = "path" @@ -769,14 +739,30 @@ class BooleanQuestion(Question): "app_argument_choice_invalid", name=option.get("name", ""), value=value, + # FIXME : this doesn't match yes_answers / no_answers... choices="yes, no, y, n, 1, 0", ) @staticmethod def normalize(value, option={}): + if isinstance(value, str): + value = value.strip() + yes = option.get("yes", 1) no = option.get("no", 0) + # + # FIXME: Shouldn't we also check that value == yes ? + # Otherwise is yes = "foobar", normalize is not idempotent + # i.e normalize("true") will return "foobar" + # but normalize(normalize("true")) will raise an exception ? + # + # FIXME: it's also a bit confusing to understand if the + # packager-provided 'yes' value is meant for humans (in which case + # shouldn't it be used as a return value for humanize?) + # or as an internal value for better interfacing with scripts/computers + # + # Also shouldnt we be using normalize() in humanize() ? if str(value).lower() in BooleanQuestion.yes_answers: return yes @@ -881,14 +867,18 @@ class NumberQuestion(Question): if isinstance(value, int): return value + if isinstance(value, str): + value = value.strip() + if isinstance(value, str) and value.isdigit(): return int(value) if value in [None, ""]: return value + # FIXME: option.name may not exist if option={}... raise YunohostValidationError( - "app_argument_invalid", name=option.name, error=m18n.n("invalid_number") + "app_argument_invalid", name=option.get("name", ""), error=m18n.n("invalid_number") ) def _prevalidate(self): From d1c371ec380f216b34afda2f054a4d70c1d0cb4f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 23 Sep 2021 00:12:24 +0200 Subject: [PATCH 3144/3170] cli questions: Restore displaying available choices, though limit to the first 20 available options --- locales/en.json | 1 + src/yunohost/utils/config.py | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 447f137a5..4572e6f86 100644 --- a/locales/en.json +++ b/locales/en.json @@ -541,6 +541,7 @@ "migrations_to_be_ran_manually": "Migration {id} has to be run manually. Please go to Tools → Migrations on the webadmin page, or run `yunohost tools migrations run`.", "not_enough_disk_space": "Not enough free space on '{path}'", "operation_interrupted": "The operation was manually interrupted?", + "other_available_options": "... and {n} other available options not shown", "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", diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 7b3d44b57..2b83507f0 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -566,7 +566,22 @@ class Question(object): ) def _format_text_for_user_input_in_cli(self): - return _value_for_locale(self.ask) + + text_for_user_input_in_cli = _value_for_locale(self.ask) + + if self.choices: + + # Prevent displaying a shitload of choices + # (e.g. 100+ available users when choosing an app admin...) + choices_to_display = self.choices[:20] + remaining_choices = len(self.choices[20:]) + + if remaining_choices > 0: + choices_to_display += [m18n.n("other_available_options", n=remaining_choices)] + + text_for_user_input_in_cli += " [{0}]".format(" | ".join(choices_to_display)) + + return text_for_user_input_in_cli def _post_parse_value(self): if not self.redact: From 92f9e15e2fd788bf4fcb0409603e834a70d9a14d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 23 Sep 2021 00:13:18 +0200 Subject: [PATCH 3145/3170] Stale i18n strings --- locales/en.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/locales/en.json b/locales/en.json index 4572e6f86..3354dc19c 100644 --- a/locales/en.json +++ b/locales/en.json @@ -15,8 +15,6 @@ "app_already_up_to_date": "{app} is already up-to-date", "app_argument_choice_invalid": "Use one of these choices '{choices}' for the argument '{name}' instead of '{value}'", "app_argument_invalid": "Pick a valid value for the argument '{name}': {error}", - "app_argument_password_help_keep": "Press Enter to keep the current value", - "app_argument_password_help_optional": "Enter a single space to empty the password", "app_argument_password_no_default": "Error while parsing password argument '{name}': password argument can't have a default value for security reason", "app_argument_required": "Argument '{name}' is required", "app_change_url_identical_domains": "The old and new domain/url_path are identical ('{domain}{path}'), nothing to do.", From 8ea160b9fe0a17df7f8bc69931db9e1690cf71da Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 23 Sep 2021 00:29:22 +0200 Subject: [PATCH 3146/3170] Fix tests --- src/yunohost/tests/test_questions.py | 18 ++++++++++++++++++ src/yunohost/utils/config.py | 27 ++++++++++++++------------- 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/src/yunohost/tests/test_questions.py b/src/yunohost/tests/test_questions.py index 9753b08e4..62bb61cc2 100644 --- a/src/yunohost/tests/test_questions.py +++ b/src/yunohost/tests/test_questions.py @@ -207,6 +207,8 @@ def test_question_string_input_test_ask(): confirm=False, prefill="", is_multiline=False, + autocomplete=[], + help=None, ) @@ -232,6 +234,8 @@ def test_question_string_input_test_ask_with_default(): confirm=False, prefill=default_text, is_multiline=False, + autocomplete=[], + help=None, ) @@ -526,6 +530,8 @@ def test_question_password_input_test_ask(): confirm=False, prefill="", is_multiline=False, + autocomplete=[], + help=None, ) @@ -787,6 +793,8 @@ def test_question_path_input_test_ask(): confirm=False, prefill="", is_multiline=False, + autocomplete=[], + help=None, ) @@ -813,6 +821,8 @@ def test_question_path_input_test_ask_with_default(): confirm=False, prefill=default_text, is_multiline=False, + autocomplete=[], + help=None, ) @@ -1162,6 +1172,8 @@ def test_question_boolean_input_test_ask(): confirm=False, prefill="no", is_multiline=False, + autocomplete=[], + help=None, ) @@ -1188,6 +1200,8 @@ def test_question_boolean_input_test_ask_with_default(): confirm=False, prefill="yes", is_multiline=False, + autocomplete=[], + help=None, ) @@ -1735,6 +1749,8 @@ def test_question_number_input_test_ask(): confirm=False, prefill="", is_multiline=False, + autocomplete=[], + help=None, ) @@ -1761,6 +1777,8 @@ def test_question_number_input_test_ask_with_default(): confirm=False, prefill=str(default_value), is_multiline=False, + autocomplete=[], + help=None, ) diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 2b83507f0..d6ddb81f9 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -573,13 +573,16 @@ class Question(object): # Prevent displaying a shitload of choices # (e.g. 100+ available users when choosing an app admin...) - choices_to_display = self.choices[:20] - remaining_choices = len(self.choices[20:]) + choices = list(self.choices.values()) if isinstance(self.choices, dict) else self.choices + choices_to_display = choices[:20] + remaining_choices = len(choices[20:]) if remaining_choices > 0: choices_to_display += [m18n.n("other_available_options", n=remaining_choices)] - text_for_user_input_in_cli += " [{0}]".format(" | ".join(choices_to_display)) + choices_to_display = " | ".join(choices_to_display) + + text_for_user_input_in_cli += f" [{choices_to_display}]" return text_for_user_input_in_cli @@ -752,7 +755,7 @@ class BooleanQuestion(Question): raise YunohostValidationError( "app_argument_choice_invalid", - name=option.get("name", ""), + name=getattr(option, "name", None) or option.get("name"), value=value, # FIXME : this doesn't match yes_answers / no_answers... choices="yes, no, y, n, 1, 0", @@ -788,7 +791,7 @@ class BooleanQuestion(Question): return None raise YunohostValidationError( "app_argument_choice_invalid", - name=option.get("name", ""), + name=getattr(option, "name", None) or option.get("name"), value=value, choices="yes, no, y, n, 1, 0", ) @@ -808,10 +811,7 @@ class BooleanQuestion(Question): return text_for_user_input_in_cli def get(self, key, default=None): - try: - return getattr(self, key) - except AttributeError: - return default + return getattr(self, key, default) class DomainQuestion(Question): @@ -843,7 +843,7 @@ class UserQuestion(Question): from yunohost.domain import _get_maindomain super().__init__(question, user_answers) - self.choices = user_list()["users"] + self.choices = list(user_list()["users"].keys()) if not self.choices: raise YunohostValidationError( @@ -854,7 +854,7 @@ class UserQuestion(Question): if self.default is None: root_mail = "root@%s" % _get_maindomain() - for user in self.choices.keys(): + for user in self.choices: if root_mail in user_info(user).get("mail-aliases", []): self.default = user break @@ -891,9 +891,10 @@ class NumberQuestion(Question): if value in [None, ""]: return value - # FIXME: option.name may not exist if option={}... raise YunohostValidationError( - "app_argument_invalid", name=option.get("name", ""), error=m18n.n("invalid_number") + "app_argument_invalid", + name=getattr(option, "name", None) or option.get("name"), + error=m18n.n("invalid_number") ) def _prevalidate(self): From 78ad3b5da32b509cb91df1106c49e2eabb880d96 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 23 Sep 2021 02:39:39 +0200 Subject: [PATCH 3147/3170] Fix weird definition for boolean's humanize/normalize --- src/yunohost/utils/config.py | 59 +++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index d6ddb81f9..64b472c39 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -740,25 +740,21 @@ class BooleanQuestion(Question): yes = option.get("yes", 1) no = option.get("no", 0) - value = str(value).lower() - if value == str(yes).lower(): - return "yes" - if value == str(no).lower(): - return "no" - if value in BooleanQuestion.yes_answers: - return "yes" - if value in BooleanQuestion.no_answers: - return "no" - if value in ["none", ""]: + value = BooleanQuestion.normalize(value, option) + + if value == yes: + return "yes" + if value == no: + return "no" + if value is None: return "" raise YunohostValidationError( "app_argument_choice_invalid", name=getattr(option, "name", None) or option.get("name"), value=value, - # FIXME : this doesn't match yes_answers / no_answers... - choices="yes, no, y, n, 1, 0", + choices="yes/no", ) @staticmethod @@ -769,31 +765,38 @@ class BooleanQuestion(Question): yes = option.get("yes", 1) no = option.get("no", 0) - # - # FIXME: Shouldn't we also check that value == yes ? - # Otherwise is yes = "foobar", normalize is not idempotent - # i.e normalize("true") will return "foobar" - # but normalize(normalize("true")) will raise an exception ? - # - # FIXME: it's also a bit confusing to understand if the - # packager-provided 'yes' value is meant for humans (in which case - # shouldn't it be used as a return value for humanize?) - # or as an internal value for better interfacing with scripts/computers - # - # Also shouldnt we be using normalize() in humanize() ? - if str(value).lower() in BooleanQuestion.yes_answers: - return yes + strvalue = str(value).lower() - if str(value).lower() in BooleanQuestion.no_answers: + # + # N.B.: + # we check first if the value is equal to yes + # then if equal to no + # then if in yes_answers + # then if in no_answers + # + # This is to hopefully cover the weird edgecase + # where the value for yes/no could be reversed + # such as yes=false or yes=0 + # no=true no=1 + # + + if strvalue == str(yes).lower(): + return yes + if strvalue == str(no).lower(): + return no + if strvalue in BooleanQuestion.yes_answers: + return yes + if strvalue in BooleanQuestion.no_answers: return no if value in [None, ""]: return None + raise YunohostValidationError( "app_argument_choice_invalid", name=getattr(option, "name", None) or option.get("name"), value=value, - choices="yes, no, y, n, 1, 0", + choices="yes/no", ) def __init__(self, question, user_answers): From d023333fa42128d70a87eb49b0f34f55aa8d1540 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 23 Sep 2021 02:58:13 +0200 Subject: [PATCH 3148/3170] questions prompt: include normalize in the try/except to re-ask question if not properly formated --- src/yunohost/utils/config.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 64b472c39..38cc28894 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -520,12 +520,9 @@ class Question(object): ): self.value = class_default if self.default is None else self.default - # Normalization - # This is done to enforce a certain formating like for boolean - self.value = self.normalize(self.value, self) - - # Prevalidation try: + # Normalize and validate + self.value = self.normalize(self.value, self) self._prevalidate() except YunohostValidationError as e: # If in interactive cli, re-ask the current question From 430f53aa73fc68c0b286af11a2104bea20daf240 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 23 Sep 2021 03:39:43 +0200 Subject: [PATCH 3149/3170] Semantic, simplify code... --- src/yunohost/app.py | 83 +++++------------------------------- src/yunohost/utils/config.py | 25 ++++++----- 2 files changed, 26 insertions(+), 82 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 91f7a41ef..b0ae7f0d5 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -32,7 +32,6 @@ import time import re import subprocess import glob -import urllib.parse import tempfile from collections import OrderedDict @@ -55,7 +54,7 @@ from moulinette.utils.filesystem import ( from yunohost.utils import packages from yunohost.utils.config import ( ConfigPanel, - parse_args_in_yunohost_format, + ask_questions_and_parse_answers, ) from yunohost.utils.i18n import _value_for_locale from yunohost.utils.error import YunohostError, YunohostValidationError @@ -453,16 +452,10 @@ def app_change_url(operation_logger, app, domain, path): # Check the url is available _assert_no_conflicting_apps(domain, path, ignore_app=app) - manifest = _get_manifest_of_app(os.path.join(APPS_SETTING_PATH, app)) - - # Retrieve arguments list for change_url script - # TODO: Allow to specify arguments - args_odict = _parse_args_from_manifest(manifest, "change_url") - tmp_workdir_for_app = _make_tmp_workdir_for_app(app=app) # Prepare env. var. to pass to script - env_dict = _make_environment_for_app_script(app, args=args_odict) + env_dict = _make_environment_for_app_script(app) env_dict["YNH_APP_OLD_DOMAIN"] = old_domain env_dict["YNH_APP_OLD_PATH"] = old_path env_dict["YNH_APP_NEW_DOMAIN"] = domain @@ -614,12 +607,8 @@ def app_upgrade(app=[], url=None, file=None, force=False, no_safety_backup=False app_setting_path = os.path.join(APPS_SETTING_PATH, app_instance_name) - # Retrieve arguments list for upgrade script - # TODO: Allow to specify arguments - args_odict = _parse_args_from_manifest(manifest, "upgrade") - # Prepare env. var. to pass to script - env_dict = _make_environment_for_app_script(app_instance_name, args=args_odict) + env_dict = _make_environment_for_app_script(app_instance_name) env_dict["YNH_APP_UPGRADE_TYPE"] = upgrade_type env_dict["YNH_APP_MANIFEST_VERSION"] = str(app_new_version) env_dict["YNH_APP_CURRENT_VERSION"] = str(app_current_version) @@ -905,13 +894,11 @@ def app_install( app_instance_name = app_id # Retrieve arguments list for install script - args_dict = ( - {} if not args else dict(urllib.parse.parse_qsl(args, keep_blank_values=True)) - ) - args_odict = _parse_args_from_manifest(manifest, "install", args=args_dict) + questions = manifest.get("arguments", {}).get("install", {}) + args = ask_questions_and_parse_answers(questions, prefilled_answers=args) # Validate domain / path availability for webapps - _validate_and_normalize_webpath(args_odict, extracted_app_folder) + _validate_and_normalize_webpath(args, extracted_app_folder) # Attempt to patch legacy helpers ... _patch_legacy_helpers(extracted_app_folder) @@ -976,11 +963,11 @@ def app_install( ) # Prepare env. var. to pass to script - env_dict = _make_environment_for_app_script(app_instance_name, args=args_odict) + env_dict = _make_environment_for_app_script(app_instance_name, args=args) env_dict["YNH_APP_BASEDIR"] = extracted_app_folder env_dict_for_logging = env_dict.copy() - for arg_name, arg_value_and_type in args_odict.items(): + for arg_name, arg_value_and_type in args.items(): if arg_value_and_type[1] == "password": del env_dict_for_logging["YNH_APP_ARG_%s" % arg_name.upper()] @@ -1642,15 +1629,13 @@ def app_action_run(operation_logger, app, action, args=None): action_declaration = actions[action] # Retrieve arguments list for install script - args_dict = ( - dict(urllib.parse.parse_qsl(args, keep_blank_values=True)) if args else {} - ) - args_odict = _parse_args_for_action(actions[action], args=args_dict) + questions = actions[action].get("arguments", {}) + args = ask_questions_and_parse_answers(questions, prefilled_answers=args) tmp_workdir_for_app = _make_tmp_workdir_for_app(app=app) env_dict = _make_environment_for_app_script( - app, args=args_odict, args_prefix="ACTION_" + app, args=args, args_prefix="ACTION_" ) env_dict["YNH_ACTION"] = action env_dict["YNH_APP_BASEDIR"] = tmp_workdir_for_app @@ -2380,52 +2365,6 @@ def _check_manifest_requirements(manifest, app_instance_name): ) -def _parse_args_from_manifest(manifest, action, args={}): - """Parse arguments needed for an action from the manifest - - Retrieve specified arguments for the action from the manifest, and parse - given args according to that. If some required arguments are not provided, - its values will be asked if interaction is possible. - Parsed arguments will be returned as an OrderedDict - - Keyword arguments: - manifest -- The app manifest to use - action -- The action to retrieve arguments for - args -- A dictionnary of arguments to parse - - """ - if action not in manifest["arguments"]: - logger.debug("no arguments found for '%s' in manifest", action) - return OrderedDict() - - action_args = manifest["arguments"][action] - return parse_args_in_yunohost_format(args, action_args) - - -def _parse_args_for_action(action, args={}): - """Parse arguments needed for an action from the actions list - - Retrieve specified arguments for the action from the manifest, and parse - given args according to that. If some required arguments are not provided, - its values will be asked if interaction is possible. - Parsed arguments will be returned as an OrderedDict - - Keyword arguments: - action -- The action - args -- A dictionnary of arguments to parse - - """ - args_dict = OrderedDict() - - if "arguments" not in action: - logger.debug("no arguments found for '%s' in manifest", action) - return args_dict - - action_args = action["arguments"] - - return parse_args_in_yunohost_format(args, action_args) - - def _validate_and_normalize_webpath(args_dict, app_folder): # If there's only one "domain" and "path", validate that domain/path diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 38cc28894..3431e9bdc 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -464,14 +464,16 @@ class Question(object): self.name = question["name"] self.type = question.get("type", "string") self.default = question.get("default", None) - self.current_value = question.get("current_value") self.optional = question.get("optional", False) self.choices = question.get("choices", []) self.pattern = question.get("pattern", self.pattern) self.ask = question.get("ask", {"en": self.name}) self.help = question.get("help") - self.value = user_answers.get(self.name) self.redact = question.get("redact", False) + # .current_value is the currently stored value + self.current_value = question.get("current_value") + # .value is the "proposed" value which we got from the user + self.value = user_answers.get(self.name) # Empty value is parsed as empty string if self.default == "": @@ -1063,25 +1065,28 @@ ARGUMENTS_TYPE_PARSERS = { } -def parse_args_in_yunohost_format(user_answers, argument_questions): +def ask_questions_and_parse_answers(questions, prefilled_answers=""): + _setuser_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) + prefilled_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 """ - parsed_answers_dict = OrderedDict() - for question in argument_questions: + prefilled_answers = dict(urllib.parse.parse_qsl(prefilled_answers or "", keep_blank_values=True)) + out = OrderedDict() + + for question in questions: question_class = ARGUMENTS_TYPE_PARSERS[question.get("type", "string")] - question = question_class(question, user_answers) + question = question_class(question, prefilled_answers) answer = question.ask_if_needed() if answer is not None: - parsed_answers_dict[question.name] = answer + out[question.name] = answer - return parsed_answers_dict + return out From 9c3ff52d98e1e2c717e10ba470c8c99c5eefc53e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 23 Sep 2021 04:35:22 +0200 Subject: [PATCH 3150/3170] file questions: we don't need to know the filename, we don't need to validate extension server-side --- src/yunohost/utils/config.py | 70 ++++++++---------------------------- 1 file changed, 15 insertions(+), 55 deletions(-) diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 3431e9bdc..4c9457c94 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -961,83 +961,44 @@ class FileQuestion(Question): def __init__(self, question, user_answers): super().__init__(question, user_answers) - if question.get("accept"): - self.accept = question.get("accept") - else: - self.accept = "" - if Moulinette.interface.type == "api": - if user_answers.get(f"{self.name}[name]"): - self.value = { - "content": self.value, - "filename": user_answers.get(f"{self.name}[name]", self.name), - } + self.accept = question.get("accept", "") def _prevalidate(self): if self.value is None: self.value = self.current_value super()._prevalidate() - if ( - isinstance(self.value, str) - and self.value - and not os.path.exists(self.value) - ): - raise YunohostValidationError( - "app_argument_invalid", - name=self.name, - error=m18n.n("file_does_not_exist", path=self.value), - ) - if self.value in [None, ""] or not self.accept: - return - filename = self.value if isinstance(self.value, str) else self.value["filename"] - if "." not in filename or "." + filename.split(".")[ - -1 - ] not in self.accept.replace(" ", "").split(","): + if not self.value or not os.path.exists(str(self.value)): raise YunohostValidationError( "app_argument_invalid", name=self.name, - error=m18n.n( - "file_extension_not_accepted", file=filename, accept=self.accept - ), + error=m18n.n("file_does_not_exist", path=str(self.value)), ) def _post_parse_value(self): from base64 import b64decode - # Upload files from API - # A file arg contains a string with "FILENAME:BASE64_CONTENT" if not self.value: return self.value - if Moulinette.interface.type == "api" and isinstance(self.value, dict): + # FIXME : in the cli case, don't we want to also copy the file + # to a tmp work dir ? + + if Moulinette.interface.type == "api": upload_dir = tempfile.mkdtemp(prefix="tmp_configpanel_") + _, file_path = tempfile.mkstemp(prefix="foobar", dir=upload_dir) + FileQuestion.upload_dirs += [upload_dir] - filename = self.value["filename"] - logger.debug( - f"Save uploaded file {self.value['filename']} from API into {upload_dir}" - ) + logger.debug(f"Save uploaded file from API into {file_path}") - # Filename is given by user of the API. For security reason, we have replaced - # os.path.join to avoid the user to be able to rewrite a file in filesystem - # i.e. os.path.join("/foo", "/etc/passwd") == "/etc/passwd" - file_path = os.path.normpath(upload_dir + "/" + filename) - if not file_path.startswith(upload_dir + "/"): - raise YunohostError( - f"Filename '{filename}' received from the API got a relative parent path, which is forbidden", - raw_msg=True, - ) - i = 2 - while os.path.exists(file_path): - file_path = os.path.normpath(upload_dir + "/" + filename + (".%d" % i)) - i += 1 - - content = self.value["content"] + content = self.value write_to_file(file_path, b64decode(content), file_mode="wb") self.value = file_path + return self.value @@ -1066,19 +1027,18 @@ ARGUMENTS_TYPE_PARSERS = { def ask_questions_and_parse_answers(questions, prefilled_answers=""): - _setuser_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: - prefilled_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 + questions -- the arguments description store in yunohost format from actions.json/toml, manifest.json/toml or config_panel.json/toml + prefilled_answers -- a url-formated string such as "domain=yolo.test&path=/foobar&admin=sam" """ prefilled_answers = dict(urllib.parse.parse_qsl(prefilled_answers or "", keep_blank_values=True)) + out = OrderedDict() for question in questions: From d5748b519ac8237b2364642ee41411d8dcab5914 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 23 Sep 2021 05:02:27 +0200 Subject: [PATCH 3151/3170] config panels: attempt to improve the semantic of 'convert()' --- src/yunohost/utils/config.py | 51 ++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 4c9457c94..558498869 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -201,20 +201,20 @@ class ConfigPanel: # Transform toml format into internal format format_description = { - "toml": { + "root": { "properties": ["version", "i18n"], - "default": {"version": 1.0}, + "defaults": {"version": 1.0}, }, "panels": { "properties": ["name", "services", "actions", "help"], - "default": { + "defaults": { "services": [], "actions": {"apply": {"en": "Apply"}}, }, }, "sections": { "properties": ["name", "services", "optional", "help", "visible"], - "default": { + "defaults": { "name": "", "services": [], "optional": True, @@ -244,11 +244,11 @@ class ConfigPanel: "accept", "redact", ], - "default": {}, + "defaults": {}, }, } - def convert(toml_node, node_type): + def _build_internal_config_panel(raw_infos, level): """Convert TOML in internal format ('full' mode used by webadmin) Here are some properties of 1.0 config panel in toml: - node properties and node children are mixed, @@ -256,48 +256,49 @@ class ConfigPanel: - some properties have default values This function detects all children nodes and put them in a list """ - # Prefill the node default keys if needed - default = format_description[node_type]["default"] - node = {key: toml_node.get(key, value) for key, value in default.items()} - properties = format_description[node_type]["properties"] + defaults = format_description[level]["defaults"] + properties = format_description[level]["properties"] - # Define the filter_key part to use and the children type - i = list(format_description).index(node_type) - subnode_type = ( - list(format_description)[i + 1] if node_type != "options" else None + # Start building the ouput (merging the raw infos + defaults) + out = {key: raw_infos.get(key, value) for key, value in defaults.items()} + + # Now fill the sublevels (+ apply filter_key) + i = list(format_description).index(level) + sublevel = ( + list(format_description)[i + 1] if level != "options" else None ) search_key = filter_key[i] if len(filter_key) > i else False - for key, value in toml_node.items(): + for key, value in raw_infos.items(): # Key/value are a child node if ( isinstance(value, OrderedDict) and key not in properties - and subnode_type + and sublevel ): # We exclude all nodes not referenced by the filter_key if search_key and key != search_key: continue - subnode = convert(value, subnode_type) + subnode = _build_internal_config_panel(value, sublevel) subnode["id"] = key - if node_type == "toml": + if level == "root": subnode.setdefault("name", {"en": key.capitalize()}) - elif node_type == "sections": + elif level == "sections": subnode["name"] = key # legacy - subnode.setdefault("optional", toml_node.get("optional", True)) - node.setdefault(subnode_type, []).append(subnode) + subnode.setdefault("optional", raw_infos.get("optional", True)) + out.setdefault(sublevel, []).append(subnode) # Key/value are a property else: if key not in properties: - logger.warning(f"Unknown key '{key}' found in config toml") + logger.warning(f"Unknown key '{key}' found in config panel") # Todo search all i18n keys - node[key] = ( + out[key] = ( value if key not in ["ask", "help", "name"] else {"en": value} ) - return node + return out - self.config = convert(toml_config_panel, "toml") + self.config = _build_internal_config_panel(toml_config_panel, "root") try: self.config["panels"][0]["sections"][0]["options"][0] From f14d7805884f6ba27f400b997d0bdf9b6e6a50b9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 23 Sep 2021 05:09:57 +0200 Subject: [PATCH 3152/3170] Get rid of unecessary 'user_answers' arg in constructor --- src/yunohost/utils/config.py | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 558498869..81b651be5 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -461,7 +461,7 @@ class Question(object): hide_user_input_in_prompt = False pattern: Optional[Dict] = None - def __init__(self, question, user_answers): + def __init__(self, question): self.name = question["name"] self.type = question.get("type", "string") self.default = question.get("default", None) @@ -474,7 +474,7 @@ class Question(object): # .current_value is the currently stored value self.current_value = question.get("current_value") # .value is the "proposed" value which we got from the user - self.value = user_answers.get(self.name) + self.value = question.get("value") # Empty value is parsed as empty string if self.default == "": @@ -701,8 +701,8 @@ class PasswordQuestion(Question): default_value = "" forbidden_chars = "{}" - def __init__(self, question, user_answers): - super().__init__(question, user_answers) + def __init__(self, question): + super().__init__(question) self.redact = True if self.default is not None: raise YunohostValidationError( @@ -799,8 +799,8 @@ class BooleanQuestion(Question): choices="yes/no", ) - def __init__(self, question, user_answers): - super().__init__(question, user_answers) + def __init__(self, question): + super().__init__(question) self.yes = question.get("yes", 1) self.no = question.get("no", 0) if self.default is None: @@ -820,10 +820,10 @@ class BooleanQuestion(Question): class DomainQuestion(Question): argument_type = "domain" - def __init__(self, question, user_answers): + def __init__(self, question): from yunohost.domain import domain_list, _get_maindomain - super().__init__(question, user_answers) + super().__init__(question) if self.default is None: self.default = _get_maindomain() @@ -841,11 +841,11 @@ class DomainQuestion(Question): class UserQuestion(Question): argument_type = "user" - def __init__(self, question, user_answers): + def __init__(self, question): from yunohost.user import user_list, user_info from yunohost.domain import _get_maindomain - super().__init__(question, user_answers) + super().__init__(question) self.choices = list(user_list()["users"].keys()) if not self.choices: @@ -874,8 +874,8 @@ class NumberQuestion(Question): argument_type = "number" default_value = None - def __init__(self, question, user_answers): - super().__init__(question, user_answers) + def __init__(self, question): + super().__init__(question) self.min = question.get("min", None) self.max = question.get("max", None) self.step = question.get("step", None) @@ -924,8 +924,8 @@ class DisplayTextQuestion(Question): argument_type = "display_text" readonly = True - def __init__(self, question, user_answers): - super().__init__(question, user_answers) + def __init__(self, question): + super().__init__(question) self.optional = True self.style = question.get( @@ -960,8 +960,8 @@ class FileQuestion(Question): if os.path.exists(upload_dir): shutil.rmtree(upload_dir) - def __init__(self, question, user_answers): - super().__init__(question, user_answers) + def __init__(self, question): + super().__init__(question) self.accept = question.get("accept", "") def _prevalidate(self): @@ -1044,7 +1044,8 @@ def ask_questions_and_parse_answers(questions, prefilled_answers=""): for question in questions: question_class = ARGUMENTS_TYPE_PARSERS[question.get("type", "string")] - question = question_class(question, prefilled_answers) + question["value"] = prefilled_answers.get(question["name"]) + question = question_class(question) answer = question.ask_if_needed() if answer is not None: From 4cc2c9787db31c72b176b13297804a73c32e5e9e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 23 Sep 2021 05:20:19 +0200 Subject: [PATCH 3153/3170] Propagate changes on tests --- src/yunohost/tests/test_questions.py | 251 +++++++++++++-------------- src/yunohost/utils/config.py | 2 +- 2 files changed, 126 insertions(+), 127 deletions(-) diff --git a/src/yunohost/tests/test_questions.py b/src/yunohost/tests/test_questions.py index 62bb61cc2..a01ad71f0 100644 --- a/src/yunohost/tests/test_questions.py +++ b/src/yunohost/tests/test_questions.py @@ -2,7 +2,7 @@ import sys import pytest import os -from mock import patch, MagicMock +from mock import patch from io import StringIO from collections import OrderedDict @@ -10,9 +10,8 @@ from moulinette import Moulinette from yunohost import domain, user from yunohost.utils.config import ( - parse_args_in_yunohost_format, + ask_questions_and_parse_answers, PasswordQuestion, - Question, ) from yunohost.utils.error import YunohostError @@ -41,7 +40,7 @@ User answers: def test_question_empty(): - assert parse_args_in_yunohost_format({}, []) == {} + assert ask_questions_and_parse_answers([], {}) == {} def test_question_string(): @@ -53,7 +52,7 @@ def test_question_string(): ] answers = {"some_string": "some_value"} expected_result = OrderedDict({"some_string": ("some_value", "string")}) - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_string_default_type(): @@ -64,7 +63,7 @@ def test_question_string_default_type(): ] answers = {"some_string": "some_value"} expected_result = OrderedDict({"some_string": ("some_value", "string")}) - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_string_no_input(): @@ -76,7 +75,7 @@ def test_question_string_no_input(): answers = {} with pytest.raises(YunohostError), patch.object(os, "isatty", return_value=False): - parse_args_in_yunohost_format(answers, questions) + ask_questions_and_parse_answers(questions, answers) def test_question_string_input(): @@ -92,7 +91,7 @@ def test_question_string_input(): with patch.object(Moulinette, "prompt", return_value="some_value"), patch.object( os, "isatty", return_value=True ): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_string_input_no_ask(): @@ -107,7 +106,7 @@ def test_question_string_input_no_ask(): with patch.object(Moulinette, "prompt", return_value="some_value"), patch.object( os, "isatty", return_value=True ): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_string_no_input_optional(): @@ -120,7 +119,7 @@ def test_question_string_no_input_optional(): answers = {} expected_result = OrderedDict({"some_string": ("", "string")}) with patch.object(os, "isatty", return_value=False): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_string_optional_with_input(): @@ -137,7 +136,7 @@ def test_question_string_optional_with_input(): with patch.object(Moulinette, "prompt", return_value="some_value"), patch.object( os, "isatty", return_value=True ): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_string_optional_with_empty_input(): @@ -154,7 +153,7 @@ def test_question_string_optional_with_empty_input(): with patch.object(Moulinette, "prompt", return_value=""), patch.object( os, "isatty", return_value=True ): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_string_optional_with_input_without_ask(): @@ -170,7 +169,7 @@ def test_question_string_optional_with_input_without_ask(): with patch.object(Moulinette, "prompt", return_value="some_value"), patch.object( os, "isatty", return_value=True ): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_string_no_input_default(): @@ -184,7 +183,7 @@ def test_question_string_no_input_default(): answers = {} expected_result = OrderedDict({"some_string": ("some_value", "string")}) with patch.object(os, "isatty", return_value=False): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_string_input_test_ask(): @@ -200,7 +199,7 @@ def test_question_string_input_test_ask(): with patch.object( Moulinette, "prompt", return_value="some_value" ) as prompt, patch.object(os, "isatty", return_value=True): - parse_args_in_yunohost_format(answers, questions) + ask_questions_and_parse_answers(questions, answers) prompt.assert_called_with( message=ask_text, is_password=False, @@ -227,7 +226,7 @@ def test_question_string_input_test_ask_with_default(): with patch.object( Moulinette, "prompt", return_value="some_value" ) as prompt, patch.object(os, "isatty", return_value=True): - parse_args_in_yunohost_format(answers, questions) + ask_questions_and_parse_answers(questions, answers) prompt.assert_called_with( message=ask_text, is_password=False, @@ -255,7 +254,7 @@ def test_question_string_input_test_ask_with_example(): with patch.object( Moulinette, "prompt", return_value="some_value" ) as prompt, patch.object(os, "isatty", return_value=True): - parse_args_in_yunohost_format(answers, questions) + ask_questions_and_parse_answers(questions, answers) assert ask_text in prompt.call_args[1]["message"] assert example_text in prompt.call_args[1]["message"] @@ -276,7 +275,7 @@ def test_question_string_input_test_ask_with_help(): with patch.object( Moulinette, "prompt", return_value="some_value" ) as prompt, patch.object(os, "isatty", return_value=True): - parse_args_in_yunohost_format(answers, questions) + ask_questions_and_parse_answers(questions, answers) assert ask_text in prompt.call_args[1]["message"] assert help_text in prompt.call_args[1]["message"] @@ -285,7 +284,7 @@ def test_question_string_with_choice(): questions = [{"name": "some_string", "type": "string", "choices": ["fr", "en"]}] answers = {"some_string": "fr"} expected_result = OrderedDict({"some_string": ("fr", "string")}) - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_string_with_choice_prompt(): @@ -295,7 +294,7 @@ def test_question_string_with_choice_prompt(): with patch.object(Moulinette, "prompt", return_value="fr"), patch.object( os, "isatty", return_value=True ): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_string_with_choice_bad(): @@ -303,7 +302,7 @@ def test_question_string_with_choice_bad(): answers = {"some_string": "bad"} with pytest.raises(YunohostError), patch.object(os, "isatty", return_value=False): - assert parse_args_in_yunohost_format(answers, questions) + assert ask_questions_and_parse_answers(questions, answers) def test_question_string_with_choice_ask(): @@ -321,7 +320,7 @@ def test_question_string_with_choice_ask(): with patch.object(Moulinette, "prompt", return_value="ru") as prompt, patch.object( os, "isatty", return_value=True ): - parse_args_in_yunohost_format(answers, questions) + ask_questions_and_parse_answers(questions, answers) assert ask_text in prompt.call_args[1]["message"] for choice in choices: @@ -340,7 +339,7 @@ def test_question_string_with_choice_default(): answers = {} expected_result = OrderedDict({"some_string": ("en", "string")}) with patch.object(os, "isatty", return_value=False): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_password(): @@ -352,7 +351,7 @@ def test_question_password(): ] answers = {"some_password": "some_value"} expected_result = OrderedDict({"some_password": ("some_value", "password")}) - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_password_no_input(): @@ -365,7 +364,7 @@ def test_question_password_no_input(): answers = {} with pytest.raises(YunohostError), patch.object(os, "isatty", return_value=False): - parse_args_in_yunohost_format(answers, questions) + ask_questions_and_parse_answers(questions, answers) def test_question_password_input(): @@ -382,7 +381,7 @@ def test_question_password_input(): with patch.object(Moulinette, "prompt", return_value="some_value"), patch.object( os, "isatty", return_value=True ): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_password_input_no_ask(): @@ -398,7 +397,7 @@ def test_question_password_input_no_ask(): with patch.object(Moulinette, "prompt", return_value="some_value"), patch.object( os, "isatty", return_value=True ): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_password_no_input_optional(): @@ -413,14 +412,14 @@ def test_question_password_no_input_optional(): expected_result = OrderedDict({"some_password": ("", "password")}) with patch.object(os, "isatty", return_value=False): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result questions = [ {"name": "some_password", "type": "password", "optional": True, "default": ""} ] with patch.object(os, "isatty", return_value=False): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_password_optional_with_input(): @@ -438,7 +437,7 @@ def test_question_password_optional_with_input(): with patch.object(Moulinette, "prompt", return_value="some_value"), patch.object( os, "isatty", return_value=True ): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_password_optional_with_empty_input(): @@ -456,7 +455,7 @@ def test_question_password_optional_with_empty_input(): with patch.object(Moulinette, "prompt", return_value=""), patch.object( os, "isatty", return_value=True ): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_password_optional_with_input_without_ask(): @@ -473,7 +472,7 @@ def test_question_password_optional_with_input_without_ask(): with patch.object(Moulinette, "prompt", return_value="some_value"), patch.object( os, "isatty", return_value=True ): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_password_no_input_default(): @@ -489,7 +488,7 @@ def test_question_password_no_input_default(): # no default for password! with pytest.raises(YunohostError), patch.object(os, "isatty", return_value=False): - parse_args_in_yunohost_format(answers, questions) + ask_questions_and_parse_answers(questions, answers) @pytest.mark.skip # this should raises @@ -506,7 +505,7 @@ def test_question_password_no_input_example(): # no example for password! with pytest.raises(YunohostError), patch.object(os, "isatty", return_value=False): - parse_args_in_yunohost_format(answers, questions) + ask_questions_and_parse_answers(questions, answers) def test_question_password_input_test_ask(): @@ -523,7 +522,7 @@ def test_question_password_input_test_ask(): with patch.object( Moulinette, "prompt", return_value="some_value" ) as prompt, patch.object(os, "isatty", return_value=True): - parse_args_in_yunohost_format(answers, questions) + ask_questions_and_parse_answers(questions, answers) prompt.assert_called_with( message=ask_text, is_password=True, @@ -552,7 +551,7 @@ def test_question_password_input_test_ask_with_example(): with patch.object( Moulinette, "prompt", return_value="some_value" ) as prompt, patch.object(os, "isatty", return_value=True): - parse_args_in_yunohost_format(answers, questions) + ask_questions_and_parse_answers(questions, answers) assert ask_text in prompt.call_args[1]["message"] assert example_text in prompt.call_args[1]["message"] @@ -574,7 +573,7 @@ def test_question_password_input_test_ask_with_help(): with patch.object( Moulinette, "prompt", return_value="some_value" ) as prompt, patch.object(os, "isatty", return_value=True): - parse_args_in_yunohost_format(answers, questions) + ask_questions_and_parse_answers(questions, answers) assert ask_text in prompt.call_args[1]["message"] assert help_text in prompt.call_args[1]["message"] @@ -593,7 +592,7 @@ def test_question_password_bad_chars(): with pytest.raises(YunohostError), patch.object( os, "isatty", return_value=False ): - parse_args_in_yunohost_format({"some_password": i * 8}, questions) + ask_questions_and_parse_answers(questions, {"some_password": i * 8}) def test_question_password_strong_enough(): @@ -608,10 +607,10 @@ def test_question_password_strong_enough(): with pytest.raises(YunohostError), patch.object(os, "isatty", return_value=False): # too short - parse_args_in_yunohost_format({"some_password": "a"}, questions) + ask_questions_and_parse_answers(questions, {"some_password": "a"}) with pytest.raises(YunohostError), patch.object(os, "isatty", return_value=False): - parse_args_in_yunohost_format({"some_password": "password"}, questions) + ask_questions_and_parse_answers(questions, {"some_password": "password"}) def test_question_password_optional_strong_enough(): @@ -626,10 +625,10 @@ def test_question_password_optional_strong_enough(): with pytest.raises(YunohostError), patch.object(os, "isatty", return_value=False): # too short - parse_args_in_yunohost_format({"some_password": "a"}, questions) + ask_questions_and_parse_answers(questions, {"some_password": "a"}) with pytest.raises(YunohostError), patch.object(os, "isatty", return_value=False): - parse_args_in_yunohost_format({"some_password": "password"}, questions) + ask_questions_and_parse_answers(questions, {"some_password": "password"}) def test_question_path(): @@ -641,7 +640,7 @@ def test_question_path(): ] answers = {"some_path": "some_value"} expected_result = OrderedDict({"some_path": ("some_value", "path")}) - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_path_no_input(): @@ -654,7 +653,7 @@ def test_question_path_no_input(): answers = {} with pytest.raises(YunohostError), patch.object(os, "isatty", return_value=False): - parse_args_in_yunohost_format(answers, questions) + ask_questions_and_parse_answers(questions, answers) def test_question_path_input(): @@ -671,7 +670,7 @@ def test_question_path_input(): with patch.object(Moulinette, "prompt", return_value="some_value"), patch.object( os, "isatty", return_value=True ): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_path_input_no_ask(): @@ -687,7 +686,7 @@ def test_question_path_input_no_ask(): with patch.object(Moulinette, "prompt", return_value="some_value"), patch.object( os, "isatty", return_value=True ): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_path_no_input_optional(): @@ -701,7 +700,7 @@ def test_question_path_no_input_optional(): answers = {} expected_result = OrderedDict({"some_path": ("", "path")}) with patch.object(os, "isatty", return_value=False): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_path_optional_with_input(): @@ -719,7 +718,7 @@ def test_question_path_optional_with_input(): with patch.object(Moulinette, "prompt", return_value="some_value"), patch.object( os, "isatty", return_value=True ): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_path_optional_with_empty_input(): @@ -737,7 +736,7 @@ def test_question_path_optional_with_empty_input(): with patch.object(Moulinette, "prompt", return_value=""), patch.object( os, "isatty", return_value=True ): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_path_optional_with_input_without_ask(): @@ -754,7 +753,7 @@ def test_question_path_optional_with_input_without_ask(): with patch.object(Moulinette, "prompt", return_value="some_value"), patch.object( os, "isatty", return_value=True ): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_path_no_input_default(): @@ -769,7 +768,7 @@ def test_question_path_no_input_default(): answers = {} expected_result = OrderedDict({"some_path": ("some_value", "path")}) with patch.object(os, "isatty", return_value=False): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_path_input_test_ask(): @@ -786,7 +785,7 @@ def test_question_path_input_test_ask(): with patch.object( Moulinette, "prompt", return_value="some_value" ) as prompt, patch.object(os, "isatty", return_value=True): - parse_args_in_yunohost_format(answers, questions) + ask_questions_and_parse_answers(questions, answers) prompt.assert_called_with( message=ask_text, is_password=False, @@ -814,7 +813,7 @@ def test_question_path_input_test_ask_with_default(): with patch.object( Moulinette, "prompt", return_value="some_value" ) as prompt, patch.object(os, "isatty", return_value=True): - parse_args_in_yunohost_format(answers, questions) + ask_questions_and_parse_answers(questions, answers) prompt.assert_called_with( message=ask_text, is_password=False, @@ -843,7 +842,7 @@ def test_question_path_input_test_ask_with_example(): with patch.object( Moulinette, "prompt", return_value="some_value" ) as prompt, patch.object(os, "isatty", return_value=True): - parse_args_in_yunohost_format(answers, questions) + ask_questions_and_parse_answers(questions, answers) assert ask_text in prompt.call_args[1]["message"] assert example_text in prompt.call_args[1]["message"] @@ -865,7 +864,7 @@ def test_question_path_input_test_ask_with_help(): with patch.object( Moulinette, "prompt", return_value="some_value" ) as prompt, patch.object(os, "isatty", return_value=True): - parse_args_in_yunohost_format(answers, questions) + ask_questions_and_parse_answers(questions, answers) assert ask_text in prompt.call_args[1]["message"] assert help_text in prompt.call_args[1]["message"] @@ -879,7 +878,7 @@ def test_question_boolean(): ] answers = {"some_boolean": "y"} expected_result = OrderedDict({"some_boolean": (1, "boolean")}) - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_boolean_all_yes(): @@ -891,46 +890,46 @@ def test_question_boolean_all_yes(): ] expected_result = OrderedDict({"some_boolean": (1, "boolean")}) assert ( - parse_args_in_yunohost_format({"some_boolean": "y"}, questions) + ask_questions_and_parse_answers(questions, {"some_boolean": "y"}) == expected_result ) assert ( - parse_args_in_yunohost_format({"some_boolean": "Y"}, questions) + ask_questions_and_parse_answers(questions, {"some_boolean": "Y"}) == expected_result ) assert ( - parse_args_in_yunohost_format({"some_boolean": "yes"}, questions) + ask_questions_and_parse_answers(questions, {"some_boolean": "yes"}) == expected_result ) assert ( - parse_args_in_yunohost_format({"some_boolean": "Yes"}, questions) + ask_questions_and_parse_answers(questions, {"some_boolean": "Yes"}) == expected_result ) assert ( - parse_args_in_yunohost_format({"some_boolean": "YES"}, questions) + ask_questions_and_parse_answers(questions, {"some_boolean": "YES"}) == expected_result ) assert ( - parse_args_in_yunohost_format({"some_boolean": "1"}, questions) + ask_questions_and_parse_answers(questions, {"some_boolean": "1"}) == expected_result ) assert ( - parse_args_in_yunohost_format({"some_boolean": 1}, questions) == expected_result + ask_questions_and_parse_answers(questions, {"some_boolean": 1}) == expected_result ) assert ( - parse_args_in_yunohost_format({"some_boolean": True}, questions) + ask_questions_and_parse_answers(questions, {"some_boolean": True}) == expected_result ) assert ( - parse_args_in_yunohost_format({"some_boolean": "True"}, questions) + ask_questions_and_parse_answers(questions, {"some_boolean": "True"}) == expected_result ) assert ( - parse_args_in_yunohost_format({"some_boolean": "TRUE"}, questions) + ask_questions_and_parse_answers(questions, {"some_boolean": "TRUE"}) == expected_result ) assert ( - parse_args_in_yunohost_format({"some_boolean": "true"}, questions) + ask_questions_and_parse_answers(questions, {"some_boolean": "true"}) == expected_result ) @@ -944,46 +943,46 @@ def test_question_boolean_all_no(): ] expected_result = OrderedDict({"some_boolean": (0, "boolean")}) assert ( - parse_args_in_yunohost_format({"some_boolean": "n"}, questions) + ask_questions_and_parse_answers(questions, {"some_boolean": "n"}) == expected_result ) assert ( - parse_args_in_yunohost_format({"some_boolean": "N"}, questions) + ask_questions_and_parse_answers(questions, {"some_boolean": "N"}) == expected_result ) assert ( - parse_args_in_yunohost_format({"some_boolean": "no"}, questions) + ask_questions_and_parse_answers(questions, {"some_boolean": "no"}) == expected_result ) assert ( - parse_args_in_yunohost_format({"some_boolean": "No"}, questions) + ask_questions_and_parse_answers(questions, {"some_boolean": "No"}) == expected_result ) assert ( - parse_args_in_yunohost_format({"some_boolean": "No"}, questions) + ask_questions_and_parse_answers(questions, {"some_boolean": "No"}) == expected_result ) assert ( - parse_args_in_yunohost_format({"some_boolean": "0"}, questions) + ask_questions_and_parse_answers(questions, {"some_boolean": "0"}) == expected_result ) assert ( - parse_args_in_yunohost_format({"some_boolean": 0}, questions) == expected_result + ask_questions_and_parse_answers(questions, {"some_boolean": 0}) == expected_result ) assert ( - parse_args_in_yunohost_format({"some_boolean": False}, questions) + ask_questions_and_parse_answers(questions, {"some_boolean": False}) == expected_result ) assert ( - parse_args_in_yunohost_format({"some_boolean": "False"}, questions) + ask_questions_and_parse_answers(questions, {"some_boolean": "False"}) == expected_result ) assert ( - parse_args_in_yunohost_format({"some_boolean": "FALSE"}, questions) + ask_questions_and_parse_answers(questions, {"some_boolean": "FALSE"}) == expected_result ) assert ( - parse_args_in_yunohost_format({"some_boolean": "false"}, questions) + ask_questions_and_parse_answers(questions, {"some_boolean": "false"}) == expected_result ) @@ -1000,7 +999,7 @@ def test_question_boolean_no_input(): expected_result = OrderedDict({"some_boolean": (0, "boolean")}) with patch.object(os, "isatty", return_value=False): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_boolean_bad_input(): @@ -1013,7 +1012,7 @@ def test_question_boolean_bad_input(): answers = {"some_boolean": "stuff"} with pytest.raises(YunohostError), patch.object(os, "isatty", return_value=False): - parse_args_in_yunohost_format(answers, questions) + ask_questions_and_parse_answers(questions, answers) def test_question_boolean_input(): @@ -1030,13 +1029,13 @@ def test_question_boolean_input(): with patch.object(Moulinette, "prompt", return_value="y"), patch.object( os, "isatty", return_value=True ): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result expected_result = OrderedDict({"some_boolean": (0, "boolean")}) with patch.object(Moulinette, "prompt", return_value="n"), patch.object( os, "isatty", return_value=True ): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_boolean_input_no_ask(): @@ -1052,7 +1051,7 @@ def test_question_boolean_input_no_ask(): with patch.object(Moulinette, "prompt", return_value="y"), patch.object( os, "isatty", return_value=True ): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_boolean_no_input_optional(): @@ -1066,7 +1065,7 @@ def test_question_boolean_no_input_optional(): answers = {} expected_result = OrderedDict({"some_boolean": (0, "boolean")}) # default to false with patch.object(os, "isatty", return_value=False): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_boolean_optional_with_input(): @@ -1084,7 +1083,7 @@ def test_question_boolean_optional_with_input(): with patch.object(Moulinette, "prompt", return_value="y"), patch.object( os, "isatty", return_value=True ): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_boolean_optional_with_empty_input(): @@ -1102,7 +1101,7 @@ def test_question_boolean_optional_with_empty_input(): with patch.object(Moulinette, "prompt", return_value=""), patch.object( os, "isatty", return_value=True ): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_boolean_optional_with_input_without_ask(): @@ -1119,7 +1118,7 @@ def test_question_boolean_optional_with_input_without_ask(): with patch.object(Moulinette, "prompt", return_value="n"), patch.object( os, "isatty", return_value=True ): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_boolean_no_input_default(): @@ -1134,7 +1133,7 @@ def test_question_boolean_no_input_default(): answers = {} expected_result = OrderedDict({"some_boolean": (0, "boolean")}) with patch.object(os, "isatty", return_value=False): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_boolean_bad_default(): @@ -1148,7 +1147,7 @@ def test_question_boolean_bad_default(): ] answers = {} with pytest.raises(YunohostError): - parse_args_in_yunohost_format(answers, questions) + ask_questions_and_parse_answers(questions, answers) def test_question_boolean_input_test_ask(): @@ -1165,7 +1164,7 @@ def test_question_boolean_input_test_ask(): with patch.object(Moulinette, "prompt", return_value=0) as prompt, patch.object( os, "isatty", return_value=True ): - parse_args_in_yunohost_format(answers, questions) + ask_questions_and_parse_answers(questions, answers) prompt.assert_called_with( message=ask_text + " [yes | no]", is_password=False, @@ -1193,7 +1192,7 @@ def test_question_boolean_input_test_ask_with_default(): with patch.object(Moulinette, "prompt", return_value=1) as prompt, patch.object( os, "isatty", return_value=True ): - parse_args_in_yunohost_format(answers, questions) + ask_questions_and_parse_answers(questions, answers) prompt.assert_called_with( message=ask_text + " [yes | no]", is_password=False, @@ -1223,7 +1222,7 @@ def test_question_domain_empty(): ), patch.object( os, "isatty", return_value=False ): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_domain(): @@ -1242,7 +1241,7 @@ def test_question_domain(): with patch.object( domain, "_get_maindomain", return_value=main_domain ), patch.object(domain, "domain_list", return_value={"domains": domains}): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_domain_two_domains(): @@ -1262,7 +1261,7 @@ def test_question_domain_two_domains(): with patch.object( domain, "_get_maindomain", return_value=main_domain ), patch.object(domain, "domain_list", return_value={"domains": domains}): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result answers = {"some_domain": main_domain} expected_result = OrderedDict({"some_domain": (main_domain, "domain")}) @@ -1270,7 +1269,7 @@ def test_question_domain_two_domains(): with patch.object( domain, "_get_maindomain", return_value=main_domain ), patch.object(domain, "domain_list", return_value={"domains": domains}): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_domain_two_domains_wrong_answer(): @@ -1292,7 +1291,7 @@ def test_question_domain_two_domains_wrong_answer(): with pytest.raises(YunohostError), patch.object( os, "isatty", return_value=False ): - parse_args_in_yunohost_format(answers, questions) + ask_questions_and_parse_answers(questions, answers) def test_question_domain_two_domains_default_no_ask(): @@ -1316,7 +1315,7 @@ def test_question_domain_two_domains_default_no_ask(): ), patch.object( os, "isatty", return_value=False ): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_domain_two_domains_default(): @@ -1335,7 +1334,7 @@ def test_question_domain_two_domains_default(): ), patch.object( os, "isatty", return_value=False ): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_domain_two_domains_default_input(): @@ -1355,11 +1354,11 @@ def test_question_domain_two_domains_default_input(): ): expected_result = OrderedDict({"some_domain": (main_domain, "domain")}) with patch.object(Moulinette, "prompt", return_value=main_domain): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result expected_result = OrderedDict({"some_domain": (other_domain, "domain")}) with patch.object(Moulinette, "prompt", return_value=other_domain): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_user_empty(): @@ -1385,7 +1384,7 @@ def test_question_user_empty(): with pytest.raises(YunohostError), patch.object( os, "isatty", return_value=False ): - parse_args_in_yunohost_format(answers, questions) + ask_questions_and_parse_answers(questions, answers) def test_question_user(): @@ -1413,7 +1412,7 @@ def test_question_user(): with patch.object(user, "user_list", return_value={"users": users}), patch.object( user, "user_info", return_value={} ): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_user_two_users(): @@ -1448,7 +1447,7 @@ def test_question_user_two_users(): with patch.object(user, "user_list", return_value={"users": users}), patch.object( user, "user_info", return_value={} ): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result answers = {"some_user": username} expected_result = OrderedDict({"some_user": (username, "user")}) @@ -1456,7 +1455,7 @@ def test_question_user_two_users(): with patch.object(user, "user_list", return_value={"users": users}), patch.object( user, "user_info", return_value={} ): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_user_two_users_wrong_answer(): @@ -1491,7 +1490,7 @@ def test_question_user_two_users_wrong_answer(): with pytest.raises(YunohostError), patch.object( os, "isatty", return_value=False ): - parse_args_in_yunohost_format(answers, questions) + ask_questions_and_parse_answers(questions, answers) def test_question_user_two_users_no_default(): @@ -1521,7 +1520,7 @@ def test_question_user_two_users_no_default(): with pytest.raises(YunohostError), patch.object( os, "isatty", return_value=False ): - parse_args_in_yunohost_format(answers, questions) + ask_questions_and_parse_answers(questions, answers) def test_question_user_two_users_default_input(): @@ -1554,13 +1553,13 @@ def test_question_user_two_users_default_input(): expected_result = OrderedDict({"some_user": (username, "user")}) with patch.object(Moulinette, "prompt", return_value=username): assert ( - parse_args_in_yunohost_format(answers, questions) == expected_result + ask_questions_and_parse_answers(questions, answers) == expected_result ) expected_result = OrderedDict({"some_user": (other_user, "user")}) with patch.object(Moulinette, "prompt", return_value=other_user): assert ( - parse_args_in_yunohost_format(answers, questions) == expected_result + ask_questions_and_parse_answers(questions, answers) == expected_result ) @@ -1573,7 +1572,7 @@ def test_question_number(): ] answers = {"some_number": 1337} expected_result = OrderedDict({"some_number": (1337, "number")}) - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_number_no_input(): @@ -1586,7 +1585,7 @@ def test_question_number_no_input(): answers = {} with pytest.raises(YunohostError), patch.object(os, "isatty", return_value=False): - parse_args_in_yunohost_format(answers, questions) + ask_questions_and_parse_answers(questions, answers) def test_question_number_bad_input(): @@ -1599,11 +1598,11 @@ def test_question_number_bad_input(): answers = {"some_number": "stuff"} with pytest.raises(YunohostError), patch.object(os, "isatty", return_value=False): - parse_args_in_yunohost_format(answers, questions) + ask_questions_and_parse_answers(questions, answers) answers = {"some_number": 1.5} with pytest.raises(YunohostError), patch.object(os, "isatty", return_value=False): - parse_args_in_yunohost_format(answers, questions) + ask_questions_and_parse_answers(questions, answers) def test_question_number_input(): @@ -1620,18 +1619,18 @@ def test_question_number_input(): with patch.object(Moulinette, "prompt", return_value="1337"), patch.object( os, "isatty", return_value=True ): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result with patch.object(Moulinette, "prompt", return_value=1337), patch.object( os, "isatty", return_value=True ): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result expected_result = OrderedDict({"some_number": (0, "number")}) with patch.object(Moulinette, "prompt", return_value="0"), patch.object( os, "isatty", return_value=True ): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_number_input_no_ask(): @@ -1647,7 +1646,7 @@ def test_question_number_input_no_ask(): with patch.object(Moulinette, "prompt", return_value="1337"), patch.object( os, "isatty", return_value=True ): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_number_no_input_optional(): @@ -1661,7 +1660,7 @@ def test_question_number_no_input_optional(): answers = {} expected_result = OrderedDict({"some_number": (None, "number")}) # default to 0 with patch.object(os, "isatty", return_value=False): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_number_optional_with_input(): @@ -1679,7 +1678,7 @@ def test_question_number_optional_with_input(): with patch.object(Moulinette, "prompt", return_value="1337"), patch.object( os, "isatty", return_value=True ): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_number_optional_with_input_without_ask(): @@ -1696,7 +1695,7 @@ def test_question_number_optional_with_input_without_ask(): with patch.object(Moulinette, "prompt", return_value="0"), patch.object( os, "isatty", return_value=True ): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_number_no_input_default(): @@ -1711,7 +1710,7 @@ def test_question_number_no_input_default(): answers = {} expected_result = OrderedDict({"some_number": (1337, "number")}) with patch.object(os, "isatty", return_value=False): - assert parse_args_in_yunohost_format(answers, questions) == expected_result + assert ask_questions_and_parse_answers(questions, answers) == expected_result def test_question_number_bad_default(): @@ -1725,7 +1724,7 @@ def test_question_number_bad_default(): ] answers = {} with pytest.raises(YunohostError), patch.object(os, "isatty", return_value=False): - parse_args_in_yunohost_format(answers, questions) + ask_questions_and_parse_answers(questions, answers) def test_question_number_input_test_ask(): @@ -1742,7 +1741,7 @@ def test_question_number_input_test_ask(): with patch.object( Moulinette, "prompt", return_value="1111" ) as prompt, patch.object(os, "isatty", return_value=True): - parse_args_in_yunohost_format(answers, questions) + ask_questions_and_parse_answers(questions, answers) prompt.assert_called_with( message=ask_text, is_password=False, @@ -1770,7 +1769,7 @@ def test_question_number_input_test_ask_with_default(): with patch.object( Moulinette, "prompt", return_value="1111" ) as prompt, patch.object(os, "isatty", return_value=True): - parse_args_in_yunohost_format(answers, questions) + ask_questions_and_parse_answers(questions, answers) prompt.assert_called_with( message=ask_text, is_password=False, @@ -1799,7 +1798,7 @@ def test_question_number_input_test_ask_with_example(): with patch.object( Moulinette, "prompt", return_value="1111" ) as prompt, patch.object(os, "isatty", return_value=True): - parse_args_in_yunohost_format(answers, questions) + ask_questions_and_parse_answers(questions, answers) assert ask_text in prompt.call_args[1]["message"] assert example_value in prompt.call_args[1]["message"] @@ -1821,7 +1820,7 @@ def test_question_number_input_test_ask_with_help(): with patch.object( Moulinette, "prompt", return_value="1111" ) as prompt, patch.object(os, "isatty", return_value=True): - parse_args_in_yunohost_format(answers, questions) + ask_questions_and_parse_answers(questions, answers) assert ask_text in prompt.call_args[1]["message"] assert help_value in prompt.call_args[1]["message"] @@ -1833,5 +1832,5 @@ def test_question_display_text(): with patch.object(sys, "stdout", new_callable=StringIO) as stdout, patch.object( os, "isatty", return_value=True ): - parse_args_in_yunohost_format(answers, questions) + ask_questions_and_parse_answers(questions, answers) assert "foobar" in stdout.getvalue() diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 81b651be5..e83d794ff 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -381,7 +381,7 @@ class ConfigPanel: # Check and ask unanswered questions self.new_values.update( - parse_args_in_yunohost_format(self.args, section["options"]) + ask_questions_and_parse_answers(section["options"], self.args) ) self.new_values = { key: value[0] From 26a49929611434361182585eba5a521cff557462 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 23 Sep 2021 05:33:20 +0200 Subject: [PATCH 3154/3170] Refactor _normalize_domain_path into DomainQuestion + PathQuestion normalizers --- src/yunohost/app.py | 37 ++++++++++++++---------------------- src/yunohost/utils/config.py | 16 ++++++++++++++++ 2 files changed, 30 insertions(+), 23 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index b0ae7f0d5..396ce04e7 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -55,6 +55,8 @@ from yunohost.utils import packages from yunohost.utils.config import ( ConfigPanel, ask_questions_and_parse_answers, + DomainQuestion, + PathQuestion ) from yunohost.utils.i18n import _value_for_locale from yunohost.utils.error import YunohostError, YunohostValidationError @@ -441,8 +443,11 @@ def app_change_url(operation_logger, app, domain, path): old_path = app_setting(app, "path") # Normalize path and domain format - old_domain, old_path = _normalize_domain_path(old_domain, old_path) - domain, path = _normalize_domain_path(domain, path) + + domain = DomainQuestion.normalize(domain) + old_domain = DomainQuestion.normalize(old_domain) + path = PathQuestion.normalize(path) + old_path = PathQuestion.normalize(old_path) if (domain, path) == (old_domain, old_path): raise YunohostValidationError( @@ -1459,7 +1464,8 @@ def app_register_url(app, domain, path): permission_sync_to_user, ) - domain, path = _normalize_domain_path(domain, path) + domain = DomainQuestion.normalize(domain) + path = PathQuestion.normalize(path) # We cannot change the url of an app already installed simply by changing # the settings... @@ -2381,7 +2387,9 @@ def _validate_and_normalize_webpath(args_dict, app_folder): domain = domain_args[0][1] path = path_args[0][1] - domain, path = _normalize_domain_path(domain, path) + + domain = DomainQuestion.normalize(domain) + path = PathQuestion.normalize(path) # Check the url is available _assert_no_conflicting_apps(domain, path) @@ -2415,24 +2423,6 @@ def _validate_and_normalize_webpath(args_dict, app_folder): _assert_no_conflicting_apps(domain, "/", full_domain=True) -def _normalize_domain_path(domain, path): - - # We want url to be of the format : - # some.domain.tld/foo - - # Remove http/https prefix if it's there - if domain.startswith("https://"): - domain = domain[len("https://") :] - elif domain.startswith("http://"): - domain = domain[len("http://") :] - - # Remove trailing slashes - domain = domain.rstrip("/").lower() - path = "/" + path.strip("/") - - return domain, path - - def _get_conflicting_apps(domain, path, ignore_app=None): """ Return a list of all conflicting apps with a domain/path (it can be empty) @@ -2445,7 +2435,8 @@ def _get_conflicting_apps(domain, path, ignore_app=None): from yunohost.domain import _assert_domain_exists - domain, path = _normalize_domain_path(domain, path) + domain = DomainQuestion.normalize(domain) + path = PathQuestion.normalize(path) # Abort if domain is unknown _assert_domain_exists(domain) diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index e83d794ff..1a39c0da4 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -728,6 +728,10 @@ class PathQuestion(Question): argument_type = "path" default_value = "" + @staticmethod + def normalize(value, option={}): + return "/" + value.strip("/") + class BooleanQuestion(Question): argument_type = "boolean" @@ -837,6 +841,18 @@ class DomainQuestion(Question): error=m18n.n("domain_name_unknown", domain=self.value), ) + @staticmethod + def normalize(value, option={}): + if value.startswith("https://"): + value = value[len("https://"):] + elif value.startswith("http://"): + value = value[len("http://"):] + + # Remove trailing slashes + value = value.rstrip("/").lower() + + return value + class UserQuestion(Question): argument_type = "user" From d4c3a6975e06d6dfc8fc8d9eb24d503615bacf7c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 23 Sep 2021 10:38:30 +0200 Subject: [PATCH 3155/3170] ask_questions_and_parse_answers may take directly a dict as input --- src/yunohost/utils/config.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 1a39c0da4..604e20f4f 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -25,7 +25,7 @@ import urllib.parse import tempfile import shutil from collections import OrderedDict -from typing import Optional, Dict, List +from typing import Optional, Dict, List, Union, Any, Mapping from moulinette.interfaces.cli import colorize from moulinette import Moulinette, m18n @@ -461,7 +461,7 @@ class Question(object): hide_user_input_in_prompt = False pattern: Optional[Dict] = None - def __init__(self, question): + def __init__(self, question: Dict[str, Any]): self.name = question["name"] self.type = question.get("type", "string") self.default = question.get("default", None) @@ -1043,7 +1043,7 @@ ARGUMENTS_TYPE_PARSERS = { } -def ask_questions_and_parse_answers(questions, prefilled_answers=""): +def ask_questions_and_parse_answers(questions: Dict, prefilled_answers: Union[str, Mapping[str, Any]] = {}) -> Mapping[str, Any]: """Parse arguments store in either manifest.json or actions.json or from a config panel against the user answers when they are present. @@ -1051,10 +1051,12 @@ def ask_questions_and_parse_answers(questions, prefilled_answers=""): questions -- the arguments description store in yunohost format from actions.json/toml, manifest.json/toml or config_panel.json/toml - prefilled_answers -- a url-formated string such as "domain=yolo.test&path=/foobar&admin=sam" + prefilled_answers -- a url "query-string" such as "domain=yolo.test&path=/foobar&admin=sam" + or a dict such as {"domain": "yolo.test", "path": "/foobar", "admin": "sam"} """ - prefilled_answers = dict(urllib.parse.parse_qsl(prefilled_answers or "", keep_blank_values=True)) + if isinstance(prefilled_answers, str): + prefilled_answers = dict(urllib.parse.parse_qs(prefilled_answers or "", keep_blank_values=True)) out = OrderedDict() From 68d849f7abcceb451f11e7a18c9d749b77105bcc Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 23 Sep 2021 10:45:37 +0200 Subject: [PATCH 3156/3170] Adapt tests for domain/path normalize --- src/yunohost/tests/test_appurl.py | 18 +----------------- src/yunohost/tests/test_questions.py | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/yunohost/tests/test_appurl.py b/src/yunohost/tests/test_appurl.py index f15ed391f..5954d239a 100644 --- a/src/yunohost/tests/test_appurl.py +++ b/src/yunohost/tests/test_appurl.py @@ -4,7 +4,7 @@ import os from .conftest import get_test_apps_dir from yunohost.utils.error import YunohostError -from yunohost.app import app_install, app_remove, _normalize_domain_path +from yunohost.app import app_install, app_remove from yunohost.domain import _get_maindomain, domain_url_available from yunohost.permission import _validate_and_sanitize_permission_url @@ -28,22 +28,6 @@ def teardown_function(function): pass -def test_normalize_domain_path(): - - assert _normalize_domain_path("https://yolo.swag/", "macnuggets") == ( - "yolo.swag", - "/macnuggets", - ) - assert _normalize_domain_path("http://yolo.swag", "/macnuggets/") == ( - "yolo.swag", - "/macnuggets", - ) - assert _normalize_domain_path("yolo.swag/", "macnuggets/") == ( - "yolo.swag", - "/macnuggets", - ) - - def test_urlavailable(): # Except the maindomain/macnuggets to be available diff --git a/src/yunohost/tests/test_questions.py b/src/yunohost/tests/test_questions.py index a01ad71f0..9680ccb46 100644 --- a/src/yunohost/tests/test_questions.py +++ b/src/yunohost/tests/test_questions.py @@ -12,6 +12,8 @@ from yunohost import domain, user from yunohost.utils.config import ( ask_questions_and_parse_answers, PasswordQuestion, + DomainQuestion, + PathQuestion ) from yunohost.utils.error import YunohostError @@ -1834,3 +1836,19 @@ def test_question_display_text(): ): ask_questions_and_parse_answers(questions, answers) assert "foobar" in stdout.getvalue() + + +def test_normalize_domain(): + + assert DomainQuestion("https://yolo.swag/") == "yolo.swag" + assert DomainQuestion("http://yolo.swag") == "yolo.swag" + assert DomainQuestion("yolo.swag/") == "yolo.swag" + + +def test_normalize_path(): + + assert PathQuestion("macnuggets") == "/macnuggets" + assert PathQuestion("mac/nuggets") == "/mac/nuggets" + assert PathQuestion("/macnuggets/") == "/macnuggets" + assert PathQuestion("macnuggets/") == "/macnuggets" + assert PathQuestion("////macnuggets///") == "/macnuggets" From 548042d5036d9458c3dd7fa65598a20a505de666 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 23 Sep 2021 13:07:46 +0200 Subject: [PATCH 3157/3170] Moar fixes .. + add test for boolean normalize/humanize --- src/yunohost/tests/test_questions.py | 120 ++++++++++++++++++++++----- src/yunohost/utils/config.py | 73 +++++++++------- 2 files changed, 142 insertions(+), 51 deletions(-) diff --git a/src/yunohost/tests/test_questions.py b/src/yunohost/tests/test_questions.py index 9680ccb46..cf4a8832d 100644 --- a/src/yunohost/tests/test_questions.py +++ b/src/yunohost/tests/test_questions.py @@ -13,9 +13,10 @@ from yunohost.utils.config import ( ask_questions_and_parse_answers, PasswordQuestion, DomainQuestion, - PathQuestion + PathQuestion, + BooleanQuestion ) -from yunohost.utils.error import YunohostError +from yunohost.utils.error import YunohostError, YunohostValidationError """ @@ -640,8 +641,8 @@ def test_question_path(): "type": "path", } ] - answers = {"some_path": "some_value"} - expected_result = OrderedDict({"some_path": ("some_value", "path")}) + answers = {"some_path": "/some_value"} + expected_result = OrderedDict({"some_path": ("/some_value", "path")}) assert ask_questions_and_parse_answers(questions, answers) == expected_result @@ -667,9 +668,9 @@ def test_question_path_input(): } ] answers = {} - expected_result = OrderedDict({"some_path": ("some_value", "path")}) + expected_result = OrderedDict({"some_path": ("/some_value", "path")}) - with patch.object(Moulinette, "prompt", return_value="some_value"), patch.object( + with patch.object(Moulinette, "prompt", return_value="/some_value"), patch.object( os, "isatty", return_value=True ): assert ask_questions_and_parse_answers(questions, answers) == expected_result @@ -683,9 +684,9 @@ def test_question_path_input_no_ask(): } ] answers = {} - expected_result = OrderedDict({"some_path": ("some_value", "path")}) + expected_result = OrderedDict({"some_path": ("/some_value", "path")}) - with patch.object(Moulinette, "prompt", return_value="some_value"), patch.object( + with patch.object(Moulinette, "prompt", return_value="/some_value"), patch.object( os, "isatty", return_value=True ): assert ask_questions_and_parse_answers(questions, answers) == expected_result @@ -715,9 +716,9 @@ def test_question_path_optional_with_input(): } ] answers = {} - expected_result = OrderedDict({"some_path": ("some_value", "path")}) + expected_result = OrderedDict({"some_path": ("/some_value", "path")}) - with patch.object(Moulinette, "prompt", return_value="some_value"), patch.object( + with patch.object(Moulinette, "prompt", return_value="/some_value"), patch.object( os, "isatty", return_value=True ): assert ask_questions_and_parse_answers(questions, answers) == expected_result @@ -750,9 +751,9 @@ def test_question_path_optional_with_input_without_ask(): } ] answers = {} - expected_result = OrderedDict({"some_path": ("some_value", "path")}) + expected_result = OrderedDict({"some_path": ("/some_value", "path")}) - with patch.object(Moulinette, "prompt", return_value="some_value"), patch.object( + with patch.object(Moulinette, "prompt", return_value="/some_value"), patch.object( os, "isatty", return_value=True ): assert ask_questions_and_parse_answers(questions, answers) == expected_result @@ -768,7 +769,7 @@ def test_question_path_no_input_default(): } ] answers = {} - expected_result = OrderedDict({"some_path": ("some_value", "path")}) + expected_result = OrderedDict({"some_path": ("/some_value", "path")}) with patch.object(os, "isatty", return_value=False): assert ask_questions_and_parse_answers(questions, answers) == expected_result @@ -801,7 +802,7 @@ def test_question_path_input_test_ask(): def test_question_path_input_test_ask_with_default(): ask_text = "some question" - default_text = "some example" + default_text = "someexample" questions = [ { "name": "some_path", @@ -1838,17 +1839,92 @@ def test_question_display_text(): assert "foobar" in stdout.getvalue() +def test_normalize_boolean_nominal(): + + assert BooleanQuestion.normalize("yes") == 1 + assert BooleanQuestion.normalize("Yes") == 1 + assert BooleanQuestion.normalize(" yes ") == 1 + assert BooleanQuestion.normalize("y") == 1 + assert BooleanQuestion.normalize("true") == 1 + assert BooleanQuestion.normalize("True") == 1 + assert BooleanQuestion.normalize("on") == 1 + assert BooleanQuestion.normalize("1") == 1 + assert BooleanQuestion.normalize(1) == 1 + + assert BooleanQuestion.normalize("no") == 0 + assert BooleanQuestion.normalize("No") == 0 + assert BooleanQuestion.normalize(" no ") == 0 + assert BooleanQuestion.normalize("n") == 0 + assert BooleanQuestion.normalize("false") == 0 + assert BooleanQuestion.normalize("False") == 0 + assert BooleanQuestion.normalize("off") == 0 + assert BooleanQuestion.normalize("0") == 0 + assert BooleanQuestion.normalize(0) == 0 + + assert BooleanQuestion.normalize("") is None + assert BooleanQuestion.normalize(" ") is None + assert BooleanQuestion.normalize(" none ") is None + assert BooleanQuestion.normalize("None") is None + assert BooleanQuestion.normalize("none") is None + assert BooleanQuestion.normalize(None) is None + + +def test_normalize_boolean_humanize(): + + assert BooleanQuestion.humanize("yes") == "yes" + assert BooleanQuestion.humanize("true") == "yes" + assert BooleanQuestion.humanize("on") == "yes" + + assert BooleanQuestion.humanize("no") == "no" + assert BooleanQuestion.humanize("false") == "no" + assert BooleanQuestion.humanize("off") == "no" + + +def test_normalize_boolean_invalid(): + + with pytest.raises(YunohostValidationError): + BooleanQuestion.normalize("yesno") + with pytest.raises(YunohostValidationError): + BooleanQuestion.normalize("foobar") + with pytest.raises(YunohostValidationError): + BooleanQuestion.normalize("enabled") + + +def test_normalize_boolean_special_yesno(): + + customyesno = {"yes": "enabled", "no": "disabled"} + + assert BooleanQuestion.normalize("yes", customyesno) == "enabled" + assert BooleanQuestion.normalize("true", customyesno) == "enabled" + assert BooleanQuestion.normalize("enabled", customyesno) == "enabled" + assert BooleanQuestion.humanize("yes", customyesno) == "yes" + assert BooleanQuestion.humanize("true", customyesno) == "yes" + assert BooleanQuestion.humanize("enabled", customyesno) == "yes" + + assert BooleanQuestion.normalize("no", customyesno) == "disabled" + assert BooleanQuestion.normalize("false", customyesno) == "disabled" + assert BooleanQuestion.normalize("disabled", customyesno) == "disabled" + assert BooleanQuestion.humanize("no", customyesno) == "no" + assert BooleanQuestion.humanize("false", customyesno) == "no" + assert BooleanQuestion.humanize("disabled", customyesno) == "no" + + def test_normalize_domain(): - assert DomainQuestion("https://yolo.swag/") == "yolo.swag" - assert DomainQuestion("http://yolo.swag") == "yolo.swag" - assert DomainQuestion("yolo.swag/") == "yolo.swag" + assert DomainQuestion.normalize("https://yolo.swag/") == "yolo.swag" + assert DomainQuestion.normalize("http://yolo.swag") == "yolo.swag" + assert DomainQuestion.normalize("yolo.swag/") == "yolo.swag" def test_normalize_path(): - assert PathQuestion("macnuggets") == "/macnuggets" - assert PathQuestion("mac/nuggets") == "/mac/nuggets" - assert PathQuestion("/macnuggets/") == "/macnuggets" - assert PathQuestion("macnuggets/") == "/macnuggets" - assert PathQuestion("////macnuggets///") == "/macnuggets" + assert PathQuestion.normalize("") == "/" + assert PathQuestion.normalize("") == "/" + assert PathQuestion.normalize("macnuggets") == "/macnuggets" + assert PathQuestion.normalize("/macnuggets") == "/macnuggets" + assert PathQuestion.normalize(" /macnuggets ") == "/macnuggets" + assert PathQuestion.normalize("/macnuggets") == "/macnuggets" + assert PathQuestion.normalize("mac/nuggets") == "/mac/nuggets" + assert PathQuestion.normalize("/macnuggets/") == "/macnuggets" + assert PathQuestion.normalize("macnuggets/") == "/macnuggets" + assert PathQuestion.normalize("////macnuggets///") == "/macnuggets" diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 604e20f4f..179e9640f 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -730,7 +730,23 @@ class PathQuestion(Question): @staticmethod def normalize(value, option={}): - return "/" + value.strip("/") + + option = option.__dict__ if isinstance(option, Question) else option + + if not value.strip(): + if option.get("optional"): + return "" + # Hmpf here we could just have a "else" case + # but we also want PathQuestion.normalize("") to return "/" + # (i.e. if no option is provided, hence .get("optional") is None + elif option.get("optional") is False: + raise YunohostValidationError( + "app_argument_invalid", + name=option.get("name"), + error="Question is mandatory" + ) + + return "/" + value.strip().strip(" /") class BooleanQuestion(Question): @@ -742,6 +758,8 @@ class BooleanQuestion(Question): @staticmethod def humanize(value, option={}): + option = option.__dict__ if isinstance(option, Question) else option + yes = option.get("yes", 1) no = option.get("no", 0) @@ -756,50 +774,45 @@ class BooleanQuestion(Question): raise YunohostValidationError( "app_argument_choice_invalid", - name=getattr(option, "name", None) or option.get("name"), + name=option.get("name"), value=value, choices="yes/no", ) @staticmethod def normalize(value, option={}): + + option = option.__dict__ if isinstance(option, Question) else option + if isinstance(value, str): value = value.strip() - yes = option.get("yes", 1) - no = option.get("no", 0) + technical_yes = option.get("yes", 1) + technical_no = option.get("no", 0) + + no_answers = BooleanQuestion.no_answers + yes_answers = BooleanQuestion.yes_answers + + assert str(technical_yes).lower() not in no_answers, f"'yes' value can't be in {no_answers}" + assert str(technical_no).lower() not in yes_answers, f"'no' value can't be in {yes_answers}" + + no_answers += [str(technical_no).lower()] + yes_answers += [str(technical_yes).lower()] strvalue = str(value).lower() - # - # N.B.: - # we check first if the value is equal to yes - # then if equal to no - # then if in yes_answers - # then if in no_answers - # - # This is to hopefully cover the weird edgecase - # where the value for yes/no could be reversed - # such as yes=false or yes=0 - # no=true no=1 - # + if strvalue in yes_answers: + return technical_yes + if strvalue in no_answers: + return technical_no - if strvalue == str(yes).lower(): - return yes - if strvalue == str(no).lower(): - return no - if strvalue in BooleanQuestion.yes_answers: - return yes - if strvalue in BooleanQuestion.no_answers: - return no - - if value in [None, ""]: + if strvalue in ["none", ""]: return None raise YunohostValidationError( "app_argument_choice_invalid", - name=getattr(option, "name", None) or option.get("name"), - value=value, + name=option.get("name"), + value=strvalue, choices="yes/no", ) @@ -898,6 +911,7 @@ class NumberQuestion(Question): @staticmethod def normalize(value, option={}): + if isinstance(value, int): return value @@ -910,9 +924,10 @@ class NumberQuestion(Question): if value in [None, ""]: return value + option = option.__dict__ if isinstance(option, Question) else option raise YunohostValidationError( "app_argument_invalid", - name=getattr(option, "name", None) or option.get("name"), + name=option.get("name"), error=m18n.n("invalid_number") ) From 79126809eb06b54d19d2ac093f4a0ecb72682192 Mon Sep 17 00:00:00 2001 From: ljf Date: Thu, 23 Sep 2021 18:39:36 +0200 Subject: [PATCH 3158/3170] [enh] Bind function for hotspot --- data/helpers.d/config | 291 +++++++++++++++++++++++------------------- 1 file changed, 160 insertions(+), 131 deletions(-) diff --git a/data/helpers.d/config b/data/helpers.d/config index 7a2ccde46..d12316996 100644 --- a/data/helpers.d/config +++ b/data/helpers.d/config @@ -1,6 +1,154 @@ #!/bin/bash +_ynh_app_config_get_one() { + local short_setting="$1" + local type="$2" + local bind="$3" + local getter="get__${short_setting}" + # Get value from getter if exists + if type -t $getter 2>/dev/null | grep -q '^function$' 2>/dev/null; + then + old[$short_setting]="$($getter)" + formats[${short_setting}]="yaml" + + elif [[ "$bind" == *"("* ]] && type -t "get__${bind%%(*}" 2>/dev/null | grep -q '^function$' 2>/dev/null; + then + old[$short_setting]="$("get__${bind%%(*}" $short_setting $type $bind)" + formats[${short_setting}]="yaml" + + elif [[ "$bind" == "null" ]] + then + old[$short_setting]="YNH_NULL" + + # Get value from app settings or from another file + elif [[ "$type" == "file" ]] + then + if [[ "$bind" == "settings" ]] + then + ynh_die --message="File '${short_setting}' can't be stored in settings" + fi + old[$short_setting]="$(ls "$(echo $bind | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" 2> /dev/null || echo YNH_NULL)" + file_hash[$short_setting]="true" + + # Get multiline text from settings or from a full file + elif [[ "$type" == "text" ]] + then + if [[ "$bind" == "settings" ]] + then + old[$short_setting]="$(ynh_app_setting_get $app $short_setting)" + elif [[ "$bind" == *":"* ]] + then + ynh_die --message="For technical reasons, multiline text '${short_setting}' can't be stored automatically in a variable file, you have to create custom getter/setter" + else + old[$short_setting]="$(cat $(echo $bind | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/) 2> /dev/null || echo YNH_NULL)" + fi + + # Get value from a kind of key/value file + else + local bind_after="" + if [[ "$bind" == "settings" ]] + then + bind=":/etc/yunohost/apps/$app/settings.yml" + fi + local bind_key="$(echo "$bind" | cut -d: -f1)" + bind_key=${bind_key:-$short_setting} + if [[ "$bind_key" == *">"* ]]; + then + bind_after="$(echo "${bind_key}" | cut -d'>' -f1)" + bind_key="$(echo "${bind_key}" | cut -d'>' -f2)" + fi + local bind_file="$(echo "$bind" | cut -d: -f2 | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" + old[$short_setting]="$(ynh_read_var_in_file --file="${bind_file}" --key="${bind_key}" --after="${bind_after}")" + + fi +} +_ynh_app_config_apply_one() { + local short_setting="$1" + local setter="set__${short_setting}" + local bind="${binds[$short_setting]}" + local type="${types[$short_setting]}" + if [ "${changed[$short_setting]}" == "true" ] + then + # Apply setter if exists + if type -t $setter 2>/dev/null | grep -q '^function$' 2>/dev/null; + then + $setter + + elif [[ "$bind" == *"("* ]] && type -t "set__${bind%%(*}" 2>/dev/null | grep -q '^function$' 2>/dev/null; + then + "set__${bind%%(*}" $short_setting $type $bind + + elif [[ "$bind" == "null" ]] + then + continue + + # Save in a file + elif [[ "$type" == "file" ]] + then + if [[ "$bind" == "settings" ]] + then + ynh_die --message="File '${short_setting}' can't be stored in settings" + fi + local bind_file="$(echo "$bind" | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" + if [[ "${!short_setting}" == "" ]] + then + ynh_backup_if_checksum_is_different --file="$bind_file" + ynh_secure_remove --file="$bind_file" + ynh_delete_file_checksum --file="$bind_file" --update_only + ynh_print_info --message="File '$bind_file' removed" + else + ynh_backup_if_checksum_is_different --file="$bind_file" + if [[ "${!short_setting}" != "$bind_file" ]] + then + cp "${!short_setting}" "$bind_file" + fi + ynh_store_file_checksum --file="$bind_file" --update_only + ynh_print_info --message="File '$bind_file' overwrited with ${!short_setting}" + fi + + # Save value in app settings + elif [[ "$bind" == "settings" ]] + then + ynh_app_setting_set --app=$app --key=$short_setting --value="${!short_setting}" + ynh_print_info --message="Configuration key '$short_setting' edited in app settings" + + # Save multiline text in a file + elif [[ "$type" == "text" ]] + then + if [[ "$bind" == *":"* ]] + then + ynh_die --message="For technical reasons, multiline text '${short_setting}' can't be stored automatically in a variable file, you have to create custom getter/setter" + fi + local bind_file="$(echo "$bind" | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" + ynh_backup_if_checksum_is_different --file="$bind_file" + echo "${!short_setting}" > "$bind_file" + ynh_store_file_checksum --file="$bind_file" --update_only + ynh_print_info --message="File '$bind_file' overwrited with the content you provieded in '${short_setting}' question" + + # Set value into a kind of key/value file + else + local bind_after="" + local bind_key="$(echo "$bind" | cut -d: -f1)" + bind_key=${bind_key:-$short_setting} + if [[ "$bind_key" == *">"* ]]; + then + bind_after="$(echo "${bind_key}" | cut -d'>' -f1)" + bind_key="$(echo "${bind_key}" | cut -d'>' -f2)" + fi + local bind_file="$(echo "$bind" | cut -d: -f2 | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" + + ynh_backup_if_checksum_is_different --file="$bind_file" + ynh_write_var_in_file --file="${bind_file}" --key="${bind_key}" --value="${!short_setting}" --after="${bind_after}" + ynh_store_file_checksum --file="$bind_file" --update_only + + # We stored the info in settings in order to be able to upgrade the app + ynh_app_setting_set --app=$app --key=$short_setting --value="${!short_setting}" + ynh_print_info --message="Configuration key '$bind_key' edited into $bind_file" + + fi + fi +} _ynh_app_config_get() { # From settings local lines @@ -29,62 +177,11 @@ EOL do # Split line into short_setting, type and bind IFS=';' read short_setting type bind <<< "$line" - local getter="get__${short_setting}" binds[${short_setting}]="$bind" types[${short_setting}]="$type" file_hash[${short_setting}]="" formats[${short_setting}]="" - # Get value from getter if exists - if type -t $getter 2>/dev/null | grep -q '^function$' 2>/dev/null; - then - old[$short_setting]="$($getter)" - formats[${short_setting}]="yaml" - - elif [[ "$bind" == "null" ]] - then - old[$short_setting]="YNH_NULL" - - # Get value from app settings or from another file - elif [[ "$type" == "file" ]] - then - if [[ "$bind" == "settings" ]] - then - ynh_die --message="File '${short_setting}' can't be stored in settings" - fi - old[$short_setting]="$(ls "$(echo $bind | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" 2> /dev/null || echo YNH_NULL)" - file_hash[$short_setting]="true" - - # Get multiline text from settings or from a full file - elif [[ "$type" == "text" ]] - then - if [[ "$bind" == "settings" ]] - then - old[$short_setting]="$(ynh_app_setting_get $app $short_setting)" - elif [[ "$bind" == *":"* ]] - then - ynh_die --message="For technical reasons, multiline text '${short_setting}' can't be stored automatically in a variable file, you have to create custom getter/setter" - else - old[$short_setting]="$(cat $(echo $bind | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/) 2> /dev/null || echo YNH_NULL)" - fi - - # Get value from a kind of key/value file - else - local bind_after="" - if [[ "$bind" == "settings" ]] - then - bind=":/etc/yunohost/apps/$app/settings.yml" - fi - local bind_key="$(echo "$bind" | cut -d: -f1)" - bind_key=${bind_key:-$short_setting} - if [[ "$bind_key" == *">"* ]]; - then - bind_after="$(echo "${bind_key}" | cut -d'>' -f1)" - bind_key="$(echo "${bind_key}" | cut -d'>' -f2)" - fi - local bind_file="$(echo "$bind" | cut -d: -f2 | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" - old[$short_setting]="$(ynh_read_var_in_file --file="${bind_file}" --key="${bind_key}" --after="${bind_after}")" - - fi + ynh_app_config_get_one $short_setting $type $bind done @@ -93,85 +190,7 @@ EOL _ynh_app_config_apply() { for short_setting in "${!old[@]}" do - local setter="set__${short_setting}" - local bind="${binds[$short_setting]}" - local type="${types[$short_setting]}" - if [ "${changed[$short_setting]}" == "true" ] - then - # Apply setter if exists - if type -t $setter 2>/dev/null | grep -q '^function$' 2>/dev/null; - then - $setter - - elif [[ "$bind" == "null" ]] - then - continue - - # Save in a file - elif [[ "$type" == "file" ]] - then - if [[ "$bind" == "settings" ]] - then - ynh_die --message="File '${short_setting}' can't be stored in settings" - fi - local bind_file="$(echo "$bind" | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" - if [[ "${!short_setting}" == "" ]] - then - ynh_backup_if_checksum_is_different --file="$bind_file" - ynh_secure_remove --file="$bind_file" - ynh_delete_file_checksum --file="$bind_file" --update_only - ynh_print_info --message="File '$bind_file' removed" - else - ynh_backup_if_checksum_is_different --file="$bind_file" - if [[ "${!short_setting}" != "$bind_file" ]] - then - cp "${!short_setting}" "$bind_file" - fi - ynh_store_file_checksum --file="$bind_file" --update_only - ynh_print_info --message="File '$bind_file' overwrited with ${!short_setting}" - fi - - # Save value in app settings - elif [[ "$bind" == "settings" ]] - then - ynh_app_setting_set --app=$app --key=$short_setting --value="${!short_setting}" - ynh_print_info --message="Configuration key '$short_setting' edited in app settings" - - # Save multiline text in a file - elif [[ "$type" == "text" ]] - then - if [[ "$bind" == *":"* ]] - then - ynh_die --message="For technical reasons, multiline text '${short_setting}' can't be stored automatically in a variable file, you have to create custom getter/setter" - fi - local bind_file="$(echo "$bind" | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" - ynh_backup_if_checksum_is_different --file="$bind_file" - echo "${!short_setting}" > "$bind_file" - ynh_store_file_checksum --file="$bind_file" --update_only - ynh_print_info --message="File '$bind_file' overwrited with the content you provieded in '${short_setting}' question" - - # Set value into a kind of key/value file - else - local bind_after="" - local bind_key="$(echo "$bind" | cut -d: -f1)" - bind_key=${bind_key:-$short_setting} - if [[ "$bind_key" == *">"* ]]; - then - bind_after="$(echo "${bind_key}" | cut -d'>' -f1)" - bind_key="$(echo "${bind_key}" | cut -d'>' -f2)" - fi - local bind_file="$(echo "$bind" | cut -d: -f2 | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" - - ynh_backup_if_checksum_is_different --file="$bind_file" - ynh_write_var_in_file --file="${bind_file}" --key="${bind_key}" --value="${!short_setting}" --after="${bind_after}" - ynh_store_file_checksum --file="$bind_file" --update_only - - # We stored the info in settings in order to be able to upgrade the app - ynh_app_setting_set --app=$app --key=$short_setting --value="${!short_setting}" - ynh_print_info --message="Configuration key '$bind_key' edited into $bind_file" - - fi - fi + ynh_app_config_apply_one $short_setting done } @@ -253,6 +272,9 @@ _ynh_app_config_validate() { if type -t validate__$short_setting | grep -q '^function$' 2>/dev/null; then result="$(validate__$short_setting)" + elif [[ "$bind" == *"("* ]] && type -t "validate__${bind%%(*}" 2>/dev/null | grep -q '^function$' 2>/dev/null; + then + "validate__${bind%%(*}" $short_setting fi if [ -n "$result" ] then @@ -283,6 +305,10 @@ _ynh_app_config_validate() { } +ynh_app_config_get_one() { + _ynh_app_config_get_one $1 $2 $3 +} + ynh_app_config_get() { _ynh_app_config_get } @@ -295,6 +321,9 @@ ynh_app_config_validate() { _ynh_app_config_validate } +ynh_app_config_apply_one() { + _ynh_app_config_apply_one $1 +} ynh_app_config_apply() { _ynh_app_config_apply } From 6b8cb0c00508f8a94d2678b02277e08737e3b42b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 23 Sep 2021 18:57:01 +0200 Subject: [PATCH 3159/3170] Hmpf refactor moar stuff but does this ever ends --- src/yunohost/app.py | 90 ++++++++++++++------------- src/yunohost/tests/test_app_config.py | 6 +- src/yunohost/utils/config.py | 37 ++++++----- 3 files changed, 74 insertions(+), 59 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 396ce04e7..01c0cb0cd 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -34,6 +34,7 @@ import subprocess import glob import tempfile from collections import OrderedDict +from typing import List from moulinette import Moulinette, m18n from moulinette.core import MoulinetteError @@ -55,6 +56,7 @@ from yunohost.utils import packages from yunohost.utils.config import ( ConfigPanel, ask_questions_and_parse_answers, + Question, DomainQuestion, PathQuestion ) @@ -899,11 +901,13 @@ def app_install( app_instance_name = app_id # Retrieve arguments list for install script - questions = manifest.get("arguments", {}).get("install", {}) - args = ask_questions_and_parse_answers(questions, prefilled_answers=args) + raw_questions = manifest.get("arguments", {}).get("install", {}) + questions = ask_questions_and_parse_answers(raw_questions, prefilled_answers=args) + args = {question.name: question.value for question in questions if question.value is not None} # Validate domain / path availability for webapps - _validate_and_normalize_webpath(args, extracted_app_folder) + path_requirement = _guess_webapp_path_requirement(questions, extracted_app_folder) + _validate_webpath_requirement(questions, path_requirement) # Attempt to patch legacy helpers ... _patch_legacy_helpers(extracted_app_folder) @@ -972,9 +976,10 @@ def app_install( env_dict["YNH_APP_BASEDIR"] = extracted_app_folder env_dict_for_logging = env_dict.copy() - for arg_name, arg_value_and_type in args.items(): - if arg_value_and_type[1] == "password": - del env_dict_for_logging["YNH_APP_ARG_%s" % arg_name.upper()] + for question in questions: + # Or should it be more generally question.redact ? + if question.type == "password": + del env_dict_for_logging["YNH_APP_ARG_%s" % question.name.upper()] operation_logger.extra.update({"env": env_dict_for_logging}) @@ -1635,8 +1640,9 @@ def app_action_run(operation_logger, app, action, args=None): action_declaration = actions[action] # Retrieve arguments list for install script - questions = actions[action].get("arguments", {}) - args = ask_questions_and_parse_answers(questions, prefilled_answers=args) + raw_questions = actions[action].get("arguments", {}) + questions = ask_questions_and_parse_answers(raw_questions, prefilled_answers=args) + args = {question.name: question.value for question in questions if question.value is not None} tmp_workdir_for_app = _make_tmp_workdir_for_app(app=app) @@ -2371,35 +2377,20 @@ def _check_manifest_requirements(manifest, app_instance_name): ) -def _validate_and_normalize_webpath(args_dict, app_folder): +def _guess_webapp_path_requirement(questions: List[Question], app_folder: str) -> str: # If there's only one "domain" and "path", validate that domain/path # is an available url and normalize the path. - domain_args = [ - (name, value[0]) for name, value in args_dict.items() if value[1] == "domain" - ] - path_args = [ - (name, value[0]) for name, value in args_dict.items() if value[1] == "path" - ] + domain_questions = [question for question in questions if question.type == "domain"] + path_questions = [question for question in questions if question.type == "path"] - if len(domain_args) == 1 and len(path_args) == 1: - - domain = domain_args[0][1] - path = path_args[0][1] - - domain = DomainQuestion.normalize(domain) - path = PathQuestion.normalize(path) - - # Check the url is available - _assert_no_conflicting_apps(domain, path) - - # (We save this normalized path so that the install script have a - # standard path format to deal with no matter what the user inputted) - args_dict[path_args[0][0]] = (path, "path") - - # This is likely to be a full-domain app... - elif len(domain_args) == 1 and len(path_args) == 0: + if len(domain_questions) == 0 and len(path_questions) == 0: + return None + if len(domain_questions) == 1 and len(path_questions) == 1: + return "domain_and_path" + if len(domain_questions) == 1 and len(path_questions) == 0: + # This is likely to be a full-domain app... # Confirm that this is a full-domain app This should cover most cases # ... though anyway the proper solution is to implement some mechanism @@ -2409,18 +2400,33 @@ def _validate_and_normalize_webpath(args_dict, app_folder): # Full-domain apps typically declare something like path_url="/" or path=/ # and use ynh_webpath_register or yunohost_app_checkurl inside the install script - install_script_content = open( - os.path.join(app_folder, "scripts/install") - ).read() + install_script_content = read_file(os.path.join(app_folder, "scripts/install")) if re.search( - r"\npath(_url)?=[\"']?/[\"']?\n", install_script_content + r"\npath(_url)?=[\"']?/[\"']?", install_script_content ) and re.search( - r"(ynh_webpath_register|yunohost app checkurl)", install_script_content + r"ynh_webpath_register", install_script_content ): + return "full_domain" - domain = domain_args[0][1] - _assert_no_conflicting_apps(domain, "/", full_domain=True) + return "?" + + +def _validate_webpath_requirement(questions: List[Question], path_requirement: str) -> None: + + domain_questions = [question for question in questions if question.type == "domain"] + path_questions = [question for question in questions if question.type == "path"] + + if path_requirement == "domain_and_path": + + domain = domain_questions[0].value + path = path_questions[0].value + _assert_no_conflicting_apps(domain, path, full_domain=True) + + elif path_requirement == "full_domain": + + domain = domain_questions[0].value + _assert_no_conflicting_apps(domain, "/", full_domain=True) def _get_conflicting_apps(domain, path, ignore_app=None): @@ -2499,10 +2505,8 @@ def _make_environment_for_app_script(app, args={}, args_prefix="APP_ARG_"): "YNH_APP_MANIFEST_VERSION": manifest.get("version", "?"), } - for arg_name, arg_value_and_type in args.items(): - env_dict["YNH_%s%s" % (args_prefix, arg_name.upper())] = str( - arg_value_and_type[0] - ) + for arg_name, arg_value in args.items(): + env_dict["YNH_%s%s" % (args_prefix, arg_name.upper())] = str(arg_value) return env_dict diff --git a/src/yunohost/tests/test_app_config.py b/src/yunohost/tests/test_app_config.py index d705076c4..248f035f5 100644 --- a/src/yunohost/tests/test_app_config.py +++ b/src/yunohost/tests/test_app_config.py @@ -2,9 +2,11 @@ import glob import os import shutil import pytest +from mock import patch from .conftest import get_test_apps_dir +from moulinette import Moulinette from moulinette.utils.filesystem import read_file from yunohost.domain import _get_maindomain @@ -146,7 +148,9 @@ def test_app_config_regular_setting(config_app): assert app_config_get(config_app, "main.components.boolean") == "1" assert app_setting(config_app, "boolean") == "1" - with pytest.raises(YunohostValidationError): + with pytest.raises(YunohostValidationError), \ + patch.object(os, "isatty", return_value=False), \ + patch.object(Moulinette, "prompt", return_value="pwet"): app_config_set(config_app, "main.components.boolean", "pwet") diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 179e9640f..e5f078267 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -380,14 +380,13 @@ class ConfigPanel: display_header(f"\n# {name}") # Check and ask unanswered questions - self.new_values.update( - ask_questions_and_parse_answers(section["options"], self.args) - ) - self.new_values = { - key: value[0] - for key, value in self.new_values.items() - if not value[0] is None - } + questions = ask_questions_and_parse_answers(section["options"], self.args) + self.new_values.update({ + question.name: question.value + for question in questions + if question.value is not None + }) + self.errors = None def _get_default_values(self): @@ -538,9 +537,10 @@ class Question(object): raise break + self.value = self._post_parse_value() - return (self.value, self.argument_type) + return self.value def _prevalidate(self): if self.value in [None, ""] and not self.optional: @@ -1058,7 +1058,7 @@ ARGUMENTS_TYPE_PARSERS = { } -def ask_questions_and_parse_answers(questions: Dict, prefilled_answers: Union[str, Mapping[str, Any]] = {}) -> Mapping[str, Any]: +def ask_questions_and_parse_answers(questions: Dict, prefilled_answers: Union[str, Mapping[str, Any]] = {}) -> List[Question]: """Parse arguments store in either manifest.json or actions.json or from a config panel against the user answers when they are present. @@ -1071,17 +1071,24 @@ def ask_questions_and_parse_answers(questions: Dict, prefilled_answers: Union[st """ if isinstance(prefilled_answers, str): - prefilled_answers = dict(urllib.parse.parse_qs(prefilled_answers or "", keep_blank_values=True)) + # FIXME FIXME : this is not uniform with config_set() which uses parse.qs (no l) + # parse_qsl parse single values + # whereas parse.qs return list of values (which is useful for tags, etc) + # For now, let's not migrate this piece of code to parse_qs + # Because Aleks believes some bits of the app CI rely on overriding values (e.g. foo=foo&...&foo=bar) + prefilled_answers = dict(urllib.parse.parse_qsl(prefilled_answers or "", keep_blank_values=True)) - out = OrderedDict() + if not prefilled_answers: + prefilled_answers = {} + + out = [] for question in questions: question_class = ARGUMENTS_TYPE_PARSERS[question.get("type", "string")] question["value"] = prefilled_answers.get(question["name"]) question = question_class(question) - answer = question.ask_if_needed() - if answer is not None: - out[question.name] = answer + question.ask_if_needed() + out.append(question) return out From 74102b607d54b94aeafdbaf999659ef50cb96eb4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 23 Sep 2021 19:21:48 +0200 Subject: [PATCH 3160/3170] Simplify error management --- locales/en.json | 2 +- src/yunohost/utils/config.py | 29 ++++++----------------------- 2 files changed, 7 insertions(+), 24 deletions(-) diff --git a/locales/en.json b/locales/en.json index 3354dc19c..99432492c 100644 --- a/locales/en.json +++ b/locales/en.json @@ -13,7 +13,7 @@ "app_already_installed": "{app} is already installed", "app_already_installed_cant_change_url": "This app is already installed. The URL cannot be changed just by this function. Check in `app changeurl` if it's available.", "app_already_up_to_date": "{app} is already up-to-date", - "app_argument_choice_invalid": "Use one of these choices '{choices}' for the argument '{name}' instead of '{value}'", + "app_argument_choice_invalid": "Pick a valid value for argument '{name}': '{value}' is not among the available choices ({choices})", "app_argument_invalid": "Pick a valid value for the argument '{name}': {error}", "app_argument_password_no_default": "Error while parsing password argument '{name}': password argument can't have a default value for security reason", "app_argument_required": "Argument '{name}' is required", diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index e5f078267..012d2ed17 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -549,7 +549,12 @@ class Question(object): # we have an answer, do some post checks if self.value not in [None, ""]: if self.choices and self.value not in self.choices: - self._raise_invalid_answer() + raise YunohostValidationError( + "app_argument_choice_invalid", + name=self.name, + value=self.value, + choices=", ".join(self.choices), + ) if self.pattern and not re.match(self.pattern["regexp"], str(self.value)): raise YunohostValidationError( self.pattern["error"], @@ -557,14 +562,6 @@ class Question(object): value=self.value, ) - def _raise_invalid_answer(self): - raise YunohostValidationError( - "app_argument_choice_invalid", - name=self.name, - value=self.value, - choices=", ".join(self.choices), - ) - def _format_text_for_user_input_in_cli(self): text_for_user_input_in_cli = _value_for_locale(self.ask) @@ -847,13 +844,6 @@ class DomainQuestion(Question): self.choices = domain_list()["domains"] - def _raise_invalid_answer(self): - raise YunohostValidationError( - "app_argument_invalid", - name=self.name, - error=m18n.n("domain_name_unknown", domain=self.value), - ) - @staticmethod def normalize(value, option={}): if value.startswith("https://"): @@ -891,13 +881,6 @@ class UserQuestion(Question): self.default = user break - def _raise_invalid_answer(self): - raise YunohostValidationError( - "app_argument_invalid", - name=self.name, - error=m18n.n("user_unknown", user=self.value), - ) - class NumberQuestion(Question): argument_type = "number" From e001f26f874e112166481bea3eb5cd23b1c8345c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 23 Sep 2021 19:22:54 +0200 Subject: [PATCH 3161/3170] Stale i18n string --- locales/en.json | 1 - 1 file changed, 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 99432492c..1d1bafd58 100644 --- a/locales/en.json +++ b/locales/en.json @@ -364,7 +364,6 @@ "extracting": "Extracting...", "field_invalid": "Invalid field '{}'", "file_does_not_exist": "The file {path} does not exist.", - "file_extension_not_accepted": "Refusing file '{path}' because its extension is not among the accepted extensions: {accept}", "firewall_reload_failed": "Could not reload the firewall", "firewall_reloaded": "Firewall reloaded", "firewall_rules_cmd_failed": "Some firewall rule commands have failed. More info in log.", From 8cc229e19b123dc31afaba4af25b11161b14db93 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 23 Sep 2021 20:04:27 +0200 Subject: [PATCH 3162/3170] Zblerg propagate changes on tests --- src/yunohost/app.py | 2 +- src/yunohost/tests/test_questions.py | 519 ++++++++++++++++----------- 2 files changed, 304 insertions(+), 217 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 01c0cb0cd..8d24e28c5 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -2386,7 +2386,7 @@ def _guess_webapp_path_requirement(questions: List[Question], app_folder: str) - path_questions = [question for question in questions if question.type == "path"] if len(domain_questions) == 0 and len(path_questions) == 0: - return None + return "" if len(domain_questions) == 1 and len(path_questions) == 1: return "domain_and_path" if len(domain_questions) == 1 and len(path_questions) == 0: diff --git a/src/yunohost/tests/test_questions.py b/src/yunohost/tests/test_questions.py index cf4a8832d..01b46097a 100644 --- a/src/yunohost/tests/test_questions.py +++ b/src/yunohost/tests/test_questions.py @@ -43,7 +43,7 @@ User answers: def test_question_empty(): - assert ask_questions_and_parse_answers([], {}) == {} + ask_questions_and_parse_answers([], {}) == [] def test_question_string(): @@ -54,8 +54,12 @@ def test_question_string(): } ] answers = {"some_string": "some_value"} - expected_result = OrderedDict({"some_string": ("some_value", "string")}) - assert ask_questions_and_parse_answers(questions, answers) == expected_result + + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_string" + assert out.type == "string" + assert out.value == "some_value" def test_question_string_default_type(): @@ -65,8 +69,13 @@ def test_question_string_default_type(): } ] answers = {"some_string": "some_value"} - expected_result = OrderedDict({"some_string": ("some_value", "string")}) - assert ask_questions_and_parse_answers(questions, answers) == expected_result + + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_string" + assert out.type == "string" + assert out.value == "some_value" + def test_question_string_no_input(): @@ -89,12 +98,15 @@ def test_question_string_input(): } ] answers = {} - expected_result = OrderedDict({"some_string": ("some_value", "string")}) with patch.object(Moulinette, "prompt", return_value="some_value"), patch.object( os, "isatty", return_value=True ): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_string" + assert out.type == "string" + assert out.value == "some_value" def test_question_string_input_no_ask(): @@ -104,12 +116,15 @@ def test_question_string_input_no_ask(): } ] answers = {} - expected_result = OrderedDict({"some_string": ("some_value", "string")}) with patch.object(Moulinette, "prompt", return_value="some_value"), patch.object( os, "isatty", return_value=True ): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_string" + assert out.type == "string" + assert out.value == "some_value" def test_question_string_no_input_optional(): @@ -120,9 +135,12 @@ def test_question_string_no_input_optional(): } ] answers = {} - expected_result = OrderedDict({"some_string": ("", "string")}) with patch.object(os, "isatty", return_value=False): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_string" + assert out.type == "string" + assert out.value == "" def test_question_string_optional_with_input(): @@ -134,12 +152,15 @@ def test_question_string_optional_with_input(): } ] answers = {} - expected_result = OrderedDict({"some_string": ("some_value", "string")}) with patch.object(Moulinette, "prompt", return_value="some_value"), patch.object( os, "isatty", return_value=True ): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_string" + assert out.type == "string" + assert out.value == "some_value" def test_question_string_optional_with_empty_input(): @@ -151,12 +172,15 @@ def test_question_string_optional_with_empty_input(): } ] answers = {} - expected_result = OrderedDict({"some_string": ("", "string")}) with patch.object(Moulinette, "prompt", return_value=""), patch.object( os, "isatty", return_value=True ): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_string" + assert out.type == "string" + assert out.value == "" def test_question_string_optional_with_input_without_ask(): @@ -167,12 +191,15 @@ def test_question_string_optional_with_input_without_ask(): } ] answers = {} - expected_result = OrderedDict({"some_string": ("some_value", "string")}) with patch.object(Moulinette, "prompt", return_value="some_value"), patch.object( os, "isatty", return_value=True ): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_string" + assert out.type == "string" + assert out.value == "some_value" def test_question_string_no_input_default(): @@ -184,9 +211,12 @@ def test_question_string_no_input_default(): } ] answers = {} - expected_result = OrderedDict({"some_string": ("some_value", "string")}) with patch.object(os, "isatty", return_value=False): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_string" + assert out.type == "string" + assert out.value == "some_value" def test_question_string_input_test_ask(): @@ -286,18 +316,24 @@ def test_question_string_input_test_ask_with_help(): def test_question_string_with_choice(): questions = [{"name": "some_string", "type": "string", "choices": ["fr", "en"]}] answers = {"some_string": "fr"} - expected_result = OrderedDict({"some_string": ("fr", "string")}) - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_string" + assert out.type == "string" + assert out.value == "fr" def test_question_string_with_choice_prompt(): questions = [{"name": "some_string", "type": "string", "choices": ["fr", "en"]}] answers = {"some_string": "fr"} - expected_result = OrderedDict({"some_string": ("fr", "string")}) with patch.object(Moulinette, "prompt", return_value="fr"), patch.object( os, "isatty", return_value=True ): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_string" + assert out.type == "string" + assert out.value == "fr" def test_question_string_with_choice_bad(): @@ -305,7 +341,7 @@ def test_question_string_with_choice_bad(): answers = {"some_string": "bad"} with pytest.raises(YunohostError), patch.object(os, "isatty", return_value=False): - assert ask_questions_and_parse_answers(questions, answers) + ask_questions_and_parse_answers(questions, answers) def test_question_string_with_choice_ask(): @@ -340,9 +376,12 @@ def test_question_string_with_choice_default(): } ] answers = {} - expected_result = OrderedDict({"some_string": ("en", "string")}) with patch.object(os, "isatty", return_value=False): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_string" + assert out.type == "string" + assert out.value == "en" def test_question_password(): @@ -353,8 +392,11 @@ def test_question_password(): } ] answers = {"some_password": "some_value"} - expected_result = OrderedDict({"some_password": ("some_value", "password")}) - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_password" + assert out.type == "password" + assert out.value == "some_value" def test_question_password_no_input(): @@ -379,12 +421,15 @@ def test_question_password_input(): } ] answers = {} - expected_result = OrderedDict({"some_password": ("some_value", "password")}) with patch.object(Moulinette, "prompt", return_value="some_value"), patch.object( os, "isatty", return_value=True ): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_password" + assert out.type == "password" + assert out.value == "some_value" def test_question_password_input_no_ask(): @@ -395,12 +440,15 @@ def test_question_password_input_no_ask(): } ] answers = {} - expected_result = OrderedDict({"some_password": ("some_value", "password")}) with patch.object(Moulinette, "prompt", return_value="some_value"), patch.object( os, "isatty", return_value=True ): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_password" + assert out.type == "password" + assert out.value == "some_value" def test_question_password_no_input_optional(): @@ -412,17 +460,29 @@ def test_question_password_no_input_optional(): } ] answers = {} - expected_result = OrderedDict({"some_password": ("", "password")}) with patch.object(os, "isatty", return_value=False): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_password" + assert out.type == "password" + assert out.value == "" questions = [ - {"name": "some_password", "type": "password", "optional": True, "default": ""} + { + "name": "some_password", + "type": "password", + "optional": True, + "default": "" + } ] with patch.object(os, "isatty", return_value=False): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_password" + assert out.type == "password" + assert out.value == "" def test_question_password_optional_with_input(): @@ -435,12 +495,15 @@ def test_question_password_optional_with_input(): } ] answers = {} - expected_result = OrderedDict({"some_password": ("some_value", "password")}) with patch.object(Moulinette, "prompt", return_value="some_value"), patch.object( os, "isatty", return_value=True ): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_password" + assert out.type == "password" + assert out.value == "some_value" def test_question_password_optional_with_empty_input(): @@ -453,12 +516,15 @@ def test_question_password_optional_with_empty_input(): } ] answers = {} - expected_result = OrderedDict({"some_password": ("", "password")}) with patch.object(Moulinette, "prompt", return_value=""), patch.object( os, "isatty", return_value=True ): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_password" + assert out.type == "password" + assert out.value == "" def test_question_password_optional_with_input_without_ask(): @@ -470,12 +536,15 @@ def test_question_password_optional_with_input_without_ask(): } ] answers = {} - expected_result = OrderedDict({"some_password": ("some_value", "password")}) with patch.object(Moulinette, "prompt", return_value="some_value"), patch.object( os, "isatty", return_value=True ): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_password" + assert out.type == "password" + assert out.value == "some_value" def test_question_password_no_input_default(): @@ -642,8 +711,11 @@ def test_question_path(): } ] answers = {"some_path": "/some_value"} - expected_result = OrderedDict({"some_path": ("/some_value", "path")}) - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_path" + assert out.type == "path" + assert out.value == "/some_value" def test_question_path_no_input(): @@ -668,12 +740,15 @@ def test_question_path_input(): } ] answers = {} - expected_result = OrderedDict({"some_path": ("/some_value", "path")}) with patch.object(Moulinette, "prompt", return_value="/some_value"), patch.object( os, "isatty", return_value=True ): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_path" + assert out.type == "path" + assert out.value == "/some_value" def test_question_path_input_no_ask(): @@ -684,12 +759,15 @@ def test_question_path_input_no_ask(): } ] answers = {} - expected_result = OrderedDict({"some_path": ("/some_value", "path")}) with patch.object(Moulinette, "prompt", return_value="/some_value"), patch.object( os, "isatty", return_value=True ): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_path" + assert out.type == "path" + assert out.value == "/some_value" def test_question_path_no_input_optional(): @@ -701,9 +779,12 @@ def test_question_path_no_input_optional(): } ] answers = {} - expected_result = OrderedDict({"some_path": ("", "path")}) with patch.object(os, "isatty", return_value=False): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_path" + assert out.type == "path" + assert out.value == "" def test_question_path_optional_with_input(): @@ -716,12 +797,15 @@ def test_question_path_optional_with_input(): } ] answers = {} - expected_result = OrderedDict({"some_path": ("/some_value", "path")}) with patch.object(Moulinette, "prompt", return_value="/some_value"), patch.object( os, "isatty", return_value=True ): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_path" + assert out.type == "path" + assert out.value == "/some_value" def test_question_path_optional_with_empty_input(): @@ -734,12 +818,15 @@ def test_question_path_optional_with_empty_input(): } ] answers = {} - expected_result = OrderedDict({"some_path": ("", "path")}) with patch.object(Moulinette, "prompt", return_value=""), patch.object( os, "isatty", return_value=True ): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_path" + assert out.type == "path" + assert out.value == "" def test_question_path_optional_with_input_without_ask(): @@ -751,12 +838,15 @@ def test_question_path_optional_with_input_without_ask(): } ] answers = {} - expected_result = OrderedDict({"some_path": ("/some_value", "path")}) with patch.object(Moulinette, "prompt", return_value="/some_value"), patch.object( os, "isatty", return_value=True ): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_path" + assert out.type == "path" + assert out.value == "/some_value" def test_question_path_no_input_default(): @@ -769,9 +859,12 @@ def test_question_path_no_input_default(): } ] answers = {} - expected_result = OrderedDict({"some_path": ("/some_value", "path")}) with patch.object(os, "isatty", return_value=False): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_path" + assert out.type == "path" + assert out.value == "/some_value" def test_question_path_input_test_ask(): @@ -880,8 +973,11 @@ def test_question_boolean(): } ] answers = {"some_boolean": "y"} - expected_result = OrderedDict({"some_boolean": (1, "boolean")}) - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_boolean" + assert out.type == "boolean" + assert out.value == 1 def test_question_boolean_all_yes(): @@ -891,50 +987,12 @@ def test_question_boolean_all_yes(): "type": "boolean", } ] - expected_result = OrderedDict({"some_boolean": (1, "boolean")}) - assert ( - ask_questions_and_parse_answers(questions, {"some_boolean": "y"}) - == expected_result - ) - assert ( - ask_questions_and_parse_answers(questions, {"some_boolean": "Y"}) - == expected_result - ) - assert ( - ask_questions_and_parse_answers(questions, {"some_boolean": "yes"}) - == expected_result - ) - assert ( - ask_questions_and_parse_answers(questions, {"some_boolean": "Yes"}) - == expected_result - ) - assert ( - ask_questions_and_parse_answers(questions, {"some_boolean": "YES"}) - == expected_result - ) - assert ( - ask_questions_and_parse_answers(questions, {"some_boolean": "1"}) - == expected_result - ) - assert ( - ask_questions_and_parse_answers(questions, {"some_boolean": 1}) == expected_result - ) - assert ( - ask_questions_and_parse_answers(questions, {"some_boolean": True}) - == expected_result - ) - assert ( - ask_questions_and_parse_answers(questions, {"some_boolean": "True"}) - == expected_result - ) - assert ( - ask_questions_and_parse_answers(questions, {"some_boolean": "TRUE"}) - == expected_result - ) - assert ( - ask_questions_and_parse_answers(questions, {"some_boolean": "true"}) - == expected_result - ) + + for value in ["Y", "yes", "Yes", "YES", "1", 1, True, "True", "TRUE", "true"]: + out = ask_questions_and_parse_answers(questions, {"some_boolean": value})[0] + assert out.name == "some_boolean" + assert out.type == "boolean" + assert out.value == 1 def test_question_boolean_all_no(): @@ -944,50 +1002,12 @@ def test_question_boolean_all_no(): "type": "boolean", } ] - expected_result = OrderedDict({"some_boolean": (0, "boolean")}) - assert ( - ask_questions_and_parse_answers(questions, {"some_boolean": "n"}) - == expected_result - ) - assert ( - ask_questions_and_parse_answers(questions, {"some_boolean": "N"}) - == expected_result - ) - assert ( - ask_questions_and_parse_answers(questions, {"some_boolean": "no"}) - == expected_result - ) - assert ( - ask_questions_and_parse_answers(questions, {"some_boolean": "No"}) - == expected_result - ) - assert ( - ask_questions_and_parse_answers(questions, {"some_boolean": "No"}) - == expected_result - ) - assert ( - ask_questions_and_parse_answers(questions, {"some_boolean": "0"}) - == expected_result - ) - assert ( - ask_questions_and_parse_answers(questions, {"some_boolean": 0}) == expected_result - ) - assert ( - ask_questions_and_parse_answers(questions, {"some_boolean": False}) - == expected_result - ) - assert ( - ask_questions_and_parse_answers(questions, {"some_boolean": "False"}) - == expected_result - ) - assert ( - ask_questions_and_parse_answers(questions, {"some_boolean": "FALSE"}) - == expected_result - ) - assert ( - ask_questions_and_parse_answers(questions, {"some_boolean": "false"}) - == expected_result - ) + + for value in ["n", "N", "no", "No", "No", "0", 0, False, "False", "FALSE", "false"]: + out = ask_questions_and_parse_answers(questions, {"some_boolean": value})[0] + assert out.name == "some_boolean" + assert out.type == "boolean" + assert out.value == 0 # XXX apparently boolean are always False (0) by default, I'm not sure what to think about that @@ -1000,9 +1020,10 @@ def test_question_boolean_no_input(): ] answers = {} - expected_result = OrderedDict({"some_boolean": (0, "boolean")}) with patch.object(os, "isatty", return_value=False): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.value == 0 def test_question_boolean_bad_input(): @@ -1028,17 +1049,17 @@ def test_question_boolean_input(): ] answers = {} - expected_result = OrderedDict({"some_boolean": (1, "boolean")}) with patch.object(Moulinette, "prompt", return_value="y"), patch.object( os, "isatty", return_value=True ): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + assert out.value == 1 - expected_result = OrderedDict({"some_boolean": (0, "boolean")}) with patch.object(Moulinette, "prompt", return_value="n"), patch.object( os, "isatty", return_value=True ): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + assert out.value == 0 def test_question_boolean_input_no_ask(): @@ -1049,12 +1070,12 @@ def test_question_boolean_input_no_ask(): } ] answers = {} - expected_result = OrderedDict({"some_boolean": (1, "boolean")}) with patch.object(Moulinette, "prompt", return_value="y"), patch.object( os, "isatty", return_value=True ): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + assert out.value == 1 def test_question_boolean_no_input_optional(): @@ -1066,9 +1087,9 @@ def test_question_boolean_no_input_optional(): } ] answers = {} - expected_result = OrderedDict({"some_boolean": (0, "boolean")}) # default to false with patch.object(os, "isatty", return_value=False): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + assert out.value == 0 def test_question_boolean_optional_with_input(): @@ -1081,12 +1102,12 @@ def test_question_boolean_optional_with_input(): } ] answers = {} - expected_result = OrderedDict({"some_boolean": (1, "boolean")}) with patch.object(Moulinette, "prompt", return_value="y"), patch.object( os, "isatty", return_value=True ): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + assert out.value == 1 def test_question_boolean_optional_with_empty_input(): @@ -1099,12 +1120,13 @@ def test_question_boolean_optional_with_empty_input(): } ] answers = {} - expected_result = OrderedDict({"some_boolean": (0, "boolean")}) # default to false with patch.object(Moulinette, "prompt", return_value=""), patch.object( os, "isatty", return_value=True ): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.value == 0 def test_question_boolean_optional_with_input_without_ask(): @@ -1116,12 +1138,13 @@ def test_question_boolean_optional_with_input_without_ask(): } ] answers = {} - expected_result = OrderedDict({"some_boolean": (0, "boolean")}) with patch.object(Moulinette, "prompt", return_value="n"), patch.object( os, "isatty", return_value=True ): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.value == 0 def test_question_boolean_no_input_default(): @@ -1134,9 +1157,11 @@ def test_question_boolean_no_input_default(): } ] answers = {} - expected_result = OrderedDict({"some_boolean": (0, "boolean")}) + with patch.object(os, "isatty", return_value=False): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.value == 0 def test_question_boolean_bad_default(): @@ -1215,7 +1240,6 @@ def test_question_domain_empty(): } ] main_domain = "my_main_domain.com" - expected_result = OrderedDict({"some_domain": (main_domain, "domain")}) answers = {} with patch.object( @@ -1225,7 +1249,11 @@ def test_question_domain_empty(): ), patch.object( os, "isatty", return_value=False ): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_domain" + assert out.type == "domain" + assert out.value == main_domain def test_question_domain(): @@ -1239,12 +1267,15 @@ def test_question_domain(): ] answers = {"some_domain": main_domain} - expected_result = OrderedDict({"some_domain": (main_domain, "domain")}) with patch.object( domain, "_get_maindomain", return_value=main_domain ), patch.object(domain, "domain_list", return_value={"domains": domains}): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_domain" + assert out.type == "domain" + assert out.value == main_domain def test_question_domain_two_domains(): @@ -1259,20 +1290,26 @@ def test_question_domain_two_domains(): } ] answers = {"some_domain": other_domain} - expected_result = OrderedDict({"some_domain": (other_domain, "domain")}) with patch.object( domain, "_get_maindomain", return_value=main_domain ), patch.object(domain, "domain_list", return_value={"domains": domains}): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_domain" + assert out.type == "domain" + assert out.value == other_domain answers = {"some_domain": main_domain} - expected_result = OrderedDict({"some_domain": (main_domain, "domain")}) with patch.object( domain, "_get_maindomain", return_value=main_domain ), patch.object(domain, "domain_list", return_value={"domains": domains}): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_domain" + assert out.type == "domain" + assert out.value == main_domain def test_question_domain_two_domains_wrong_answer(): @@ -1309,7 +1346,6 @@ def test_question_domain_two_domains_default_no_ask(): } ] answers = {} - expected_result = OrderedDict({"some_domain": (main_domain, "domain")}) with patch.object( domain, "_get_maindomain", return_value=main_domain @@ -1318,7 +1354,11 @@ def test_question_domain_two_domains_default_no_ask(): ), patch.object( os, "isatty", return_value=False ): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_domain" + assert out.type == "domain" + assert out.value == main_domain def test_question_domain_two_domains_default(): @@ -1328,7 +1368,6 @@ def test_question_domain_two_domains_default(): questions = [{"name": "some_domain", "type": "domain", "ask": "choose a domain"}] answers = {} - expected_result = OrderedDict({"some_domain": (main_domain, "domain")}) with patch.object( domain, "_get_maindomain", return_value=main_domain @@ -1337,7 +1376,11 @@ def test_question_domain_two_domains_default(): ), patch.object( os, "isatty", return_value=False ): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_domain" + assert out.type == "domain" + assert out.value == main_domain def test_question_domain_two_domains_default_input(): @@ -1355,13 +1398,19 @@ def test_question_domain_two_domains_default_input(): ), patch.object( os, "isatty", return_value=True ): - expected_result = OrderedDict({"some_domain": (main_domain, "domain")}) with patch.object(Moulinette, "prompt", return_value=main_domain): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_domain" + assert out.type == "domain" + assert out.value == main_domain - expected_result = OrderedDict({"some_domain": (other_domain, "domain")}) with patch.object(Moulinette, "prompt", return_value=other_domain): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_domain" + assert out.type == "domain" + assert out.value == other_domain def test_question_user_empty(): @@ -1410,12 +1459,14 @@ def test_question_user(): ] answers = {"some_user": username} - expected_result = OrderedDict({"some_user": (username, "user")}) - with patch.object(user, "user_list", return_value={"users": users}), patch.object( user, "user_info", return_value={} ): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_user" + assert out.type == "user" + assert out.value == username def test_question_user_two_users(): @@ -1445,20 +1496,26 @@ def test_question_user_two_users(): } ] answers = {"some_user": other_user} - expected_result = OrderedDict({"some_user": (other_user, "user")}) with patch.object(user, "user_list", return_value={"users": users}), patch.object( user, "user_info", return_value={} ): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_user" + assert out.type == "user" + assert out.value == other_user answers = {"some_user": username} - expected_result = OrderedDict({"some_user": (username, "user")}) with patch.object(user, "user_list", return_value={"users": users}), patch.object( user, "user_info", return_value={} ): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_user" + assert out.type == "user" + assert out.value == username def test_question_user_two_users_wrong_answer(): @@ -1553,17 +1610,20 @@ def test_question_user_two_users_default_input(): os, "isatty", return_value=True ): with patch.object(user, "user_info", return_value={}): - expected_result = OrderedDict({"some_user": (username, "user")}) - with patch.object(Moulinette, "prompt", return_value=username): - assert ( - ask_questions_and_parse_answers(questions, answers) == expected_result - ) - expected_result = OrderedDict({"some_user": (other_user, "user")}) + with patch.object(Moulinette, "prompt", return_value=username): + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_user" + assert out.type == "user" + assert out.value == username + with patch.object(Moulinette, "prompt", return_value=other_user): - assert ( - ask_questions_and_parse_answers(questions, answers) == expected_result - ) + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_user" + assert out.type == "user" + assert out.value == other_user def test_question_number(): @@ -1574,8 +1634,11 @@ def test_question_number(): } ] answers = {"some_number": 1337} - expected_result = OrderedDict({"some_number": (1337, "number")}) - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_number" + assert out.type == "number" + assert out.value == 1337 def test_question_number_no_input(): @@ -1618,23 +1681,32 @@ def test_question_number_input(): ] answers = {} - expected_result = OrderedDict({"some_number": (1337, "number")}) with patch.object(Moulinette, "prompt", return_value="1337"), patch.object( os, "isatty", return_value=True ): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_number" + assert out.type == "number" + assert out.value == 1337 with patch.object(Moulinette, "prompt", return_value=1337), patch.object( os, "isatty", return_value=True ): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_number" + assert out.type == "number" + assert out.value == 1337 - expected_result = OrderedDict({"some_number": (0, "number")}) with patch.object(Moulinette, "prompt", return_value="0"), patch.object( os, "isatty", return_value=True ): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + assert out.name == "some_number" + assert out.type == "number" + assert out.value == 0 def test_question_number_input_no_ask(): questions = [ @@ -1644,12 +1716,15 @@ def test_question_number_input_no_ask(): } ] answers = {} - expected_result = OrderedDict({"some_number": (1337, "number")}) with patch.object(Moulinette, "prompt", return_value="1337"), patch.object( os, "isatty", return_value=True ): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_number" + assert out.type == "number" + assert out.value == 1337 def test_question_number_no_input_optional(): @@ -1661,9 +1736,12 @@ def test_question_number_no_input_optional(): } ] answers = {} - expected_result = OrderedDict({"some_number": (None, "number")}) # default to 0 with patch.object(os, "isatty", return_value=False): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_number" + assert out.type == "number" + assert out.value == None def test_question_number_optional_with_input(): @@ -1676,12 +1754,15 @@ def test_question_number_optional_with_input(): } ] answers = {} - expected_result = OrderedDict({"some_number": (1337, "number")}) with patch.object(Moulinette, "prompt", return_value="1337"), patch.object( os, "isatty", return_value=True ): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_number" + assert out.type == "number" + assert out.value == 1337 def test_question_number_optional_with_input_without_ask(): @@ -1693,12 +1774,15 @@ def test_question_number_optional_with_input_without_ask(): } ] answers = {} - expected_result = OrderedDict({"some_number": (0, "number")}) with patch.object(Moulinette, "prompt", return_value="0"), patch.object( os, "isatty", return_value=True ): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_number" + assert out.type == "number" + assert out.value == 0 def test_question_number_no_input_default(): @@ -1711,9 +1795,12 @@ def test_question_number_no_input_default(): } ] answers = {} - expected_result = OrderedDict({"some_number": (1337, "number")}) with patch.object(os, "isatty", return_value=False): - assert ask_questions_and_parse_answers(questions, answers) == expected_result + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_number" + assert out.type == "number" + assert out.value == 1337 def test_question_number_bad_default(): @@ -1865,7 +1952,7 @@ def test_normalize_boolean_nominal(): assert BooleanQuestion.normalize(" ") is None assert BooleanQuestion.normalize(" none ") is None assert BooleanQuestion.normalize("None") is None - assert BooleanQuestion.normalize("none") is None + assert BooleanQuestion.normalize("noNe") is None assert BooleanQuestion.normalize(None) is None From 0693aa45de3aef0bf0ae8352c033d945b205de8f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 23 Sep 2021 20:54:31 +0200 Subject: [PATCH 3163/3170] Add tests for FileQuestion --- src/yunohost/tests/test_questions.py | 94 +++++++++++++++++++++++++++- src/yunohost/utils/config.py | 47 +++++++------- 2 files changed, 117 insertions(+), 24 deletions(-) diff --git a/src/yunohost/tests/test_questions.py b/src/yunohost/tests/test_questions.py index 01b46097a..d141f53cc 100644 --- a/src/yunohost/tests/test_questions.py +++ b/src/yunohost/tests/test_questions.py @@ -14,7 +14,8 @@ from yunohost.utils.config import ( PasswordQuestion, DomainQuestion, PathQuestion, - BooleanQuestion + BooleanQuestion, + FileQuestion ) from yunohost.utils.error import YunohostError, YunohostValidationError @@ -62,6 +63,23 @@ def test_question_string(): assert out.value == "some_value" +def test_question_string_from_query_string(): + + questions = [ + { + "name": "some_string", + "type": "string", + } + ] + answers = "foo=bar&some_string=some_value&lorem=ipsum" + + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_string" + assert out.type == "string" + assert out.value == "some_value" + + def test_question_string_default_type(): questions = [ { @@ -1916,7 +1934,13 @@ def test_question_number_input_test_ask_with_help(): def test_question_display_text(): - questions = [{"name": "some_app", "type": "display_text", "ask": "foobar"}] + questions = [ + { + "name": "some_app", + "type": "display_text", + "ask": "foobar" + } + ] answers = {} with patch.object(sys, "stdout", new_callable=StringIO) as stdout, patch.object( @@ -1926,6 +1950,72 @@ def test_question_display_text(): assert "foobar" in stdout.getvalue() +def test_question_file_from_cli(): + + FileQuestion.clean_upload_dirs() + + filename = "/tmp/ynh_test_question_file" + os.system(f"rm -f {filename}") + os.system(f"echo helloworld > {filename}") + + questions = [ + { + "name": "some_file", + "type": "file", + } + ] + answers = {"some_file": filename} + + out = ask_questions_and_parse_answers(questions, answers)[0] + + assert out.name == "some_file" + assert out.type == "file" + + # The file is supposed to be copied somewhere else + assert out.value != filename + assert out.value.startswith("/tmp/") + assert os.path.exists(out.value) + assert "helloworld" in open(out.value).read().strip() + + FileQuestion.clean_upload_dirs() + + assert not os.path.exists(out.value) + + +def test_question_file_from_api(): + + FileQuestion.clean_upload_dirs() + + from base64 import b64encode + + b64content = b64encode("helloworld".encode()) + questions = [ + { + "name": "some_file", + "type": "file", + } + ] + answers = {"some_file": b64content} + + interface_type_bkp = Moulinette.interface.type + try: + Moulinette.interface.type = "api" + out = ask_questions_and_parse_answers(questions, answers)[0] + finally: + Moulinette.interface.type = interface_type_bkp + + assert out.name == "some_file" + assert out.type == "file" + + assert out.value.startswith("/tmp/") + assert os.path.exists(out.value) + assert "helloworld" in open(out.value).read().strip() + + FileQuestion.clean_upload_dirs() + + assert not os.path.exists(out.value) + + def test_normalize_boolean_nominal(): assert BooleanQuestion.normalize("yes") == 1 diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 012d2ed17..78fb52252 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -31,6 +31,7 @@ from moulinette.interfaces.cli import colorize from moulinette import Moulinette, m18n from moulinette.utils.log import getActionLogger from moulinette.utils.filesystem import ( + read_file, write_to_file, read_toml, read_yaml, @@ -167,6 +168,9 @@ class ConfigPanel: raise finally: # Delete files uploaded from API + # FIXME : this is currently done in the context of config panels, + # but could also happen in the context of app install ... (or anywhere else + # where we may parse args etc...) FileQuestion.clean_upload_dirs() self._reload_services() @@ -969,10 +973,9 @@ class FileQuestion(Question): @classmethod def clean_upload_dirs(cls): # Delete files uploaded from API - if Moulinette.interface.type == "api": - for upload_dir in cls.upload_dirs: - if os.path.exists(upload_dir): - shutil.rmtree(upload_dir) + for upload_dir in cls.upload_dirs: + if os.path.exists(upload_dir): + shutil.rmtree(upload_dir) def __init__(self, question): super().__init__(question) @@ -984,12 +987,13 @@ class FileQuestion(Question): super()._prevalidate() - if not self.value or not os.path.exists(str(self.value)): - raise YunohostValidationError( - "app_argument_invalid", - name=self.name, - error=m18n.n("file_does_not_exist", path=str(self.value)), - ) + if Moulinette.interface.type != "api": + if not self.value or not os.path.exists(str(self.value)): + raise YunohostValidationError( + "app_argument_invalid", + name=self.name, + error=m18n.n("file_does_not_exist", path=str(self.value)), + ) def _post_parse_value(self): from base64 import b64decode @@ -997,22 +1001,21 @@ class FileQuestion(Question): if not self.value: return self.value - # FIXME : in the cli case, don't we want to also copy the file - # to a tmp work dir ? + upload_dir = tempfile.mkdtemp(prefix="ynh_filequestion_") + _, file_path = tempfile.mkstemp(dir=upload_dir) + + FileQuestion.upload_dirs += [upload_dir] + + logger.debug(f"Saving file {self.name} for file question into {file_path}") + if Moulinette.interface.type != "api": + content = read_file(str(self.value), file_mode="rb") if Moulinette.interface.type == "api": + content = b64decode(self.value) - upload_dir = tempfile.mkdtemp(prefix="tmp_configpanel_") - _, file_path = tempfile.mkstemp(prefix="foobar", dir=upload_dir) + write_to_file(file_path, content, file_mode="wb") - FileQuestion.upload_dirs += [upload_dir] - logger.debug(f"Save uploaded file from API into {file_path}") - - content = self.value - - write_to_file(file_path, b64decode(content), file_mode="wb") - - self.value = file_path + self.value = file_path return self.value From d64f2cdf1a14ba5abf07bb66b79d7936d5e8a3a7 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 23 Sep 2021 21:28:12 +0200 Subject: [PATCH 3164/3170] regenconf: Missing mkdir for dpkgorigins --- data/hooks/conf_regen/01-yunohost | 1 + 1 file changed, 1 insertion(+) diff --git a/data/hooks/conf_regen/01-yunohost b/data/hooks/conf_regen/01-yunohost index 9085b3dbc..14af66933 100755 --- a/data/hooks/conf_regen/01-yunohost +++ b/data/hooks/conf_regen/01-yunohost @@ -151,6 +151,7 @@ EOF touch ${pending_dir}/etc/systemd/system/proc-hidepid.service fi + mkdir -p ${pending_dir}/etc/dpkg/origins/ cp dpkg-origins ${pending_dir}/etc/dpkg/origins/yunohost } From 9f0caedb6b3ed43d74bb19d593b1df13ed13c921 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 23 Sep 2021 21:30:37 +0200 Subject: [PATCH 3165/3170] tests: .coveragerc should be at the root of the repo --- .coveragerc | 2 ++ src/yunohost/.coveragerc | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 .coveragerc delete mode 100644 src/yunohost/.coveragerc diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 000000000..73a4c45b4 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,2 @@ +[report] +omit=src/yunohost/tests/*,src/yunohost/vendor/*,/usr/lib/moulinette/yunohost/ diff --git a/src/yunohost/.coveragerc b/src/yunohost/.coveragerc deleted file mode 100644 index 43e152271..000000000 --- a/src/yunohost/.coveragerc +++ /dev/null @@ -1,2 +0,0 @@ -[report] -omit=tests/*,vendor/*,/usr/lib/moulinette/yunohost/ From a5580caf4ec967228c2fcac6ba3fa82acd1a3c17 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 23 Sep 2021 21:46:35 +0200 Subject: [PATCH 3166/3170] Lint --- src/yunohost/tests/test_questions.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/yunohost/tests/test_questions.py b/src/yunohost/tests/test_questions.py index d141f53cc..99b5339ca 100644 --- a/src/yunohost/tests/test_questions.py +++ b/src/yunohost/tests/test_questions.py @@ -4,7 +4,6 @@ import os from mock import patch from io import StringIO -from collections import OrderedDict from moulinette import Moulinette @@ -1759,7 +1758,7 @@ def test_question_number_no_input_optional(): assert out.name == "some_number" assert out.type == "number" - assert out.value == None + assert out.value is None def test_question_number_optional_with_input(): From c2fc2c4e51fcd4b6a0299285828f9fb01f280f25 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 23 Sep 2021 21:36:20 +0200 Subject: [PATCH 3167/3170] Typo --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 1d1bafd58..cf24cfc09 100644 --- a/locales/en.json +++ b/locales/en.json @@ -654,7 +654,7 @@ "service_stop_failed": "Unable to stop the service '{service}'\n\nRecent service logs:{logs}", "service_stopped": "Service '{service}' stopped", "service_unknown": "Unknown service '{service}'", - "show_tile_cant_be_enabled_for_regex": "You cannot enable 'show_tile' right no, because the URL for the permission '{permission}' is a regex", + "show_tile_cant_be_enabled_for_regex": "You cannot enable 'show_tile' right now, because the URL for the permission '{permission}' is a regex", "show_tile_cant_be_enabled_for_url_not_defined": "You cannot enable 'show_tile' right now, because you must first define an URL for the permission '{permission}'", "ssowat_conf_generated": "SSOwat configuration regenerated", "ssowat_conf_updated": "SSOwat configuration updated", From ec5e5d6b730adcfa6db71ba80a93094480c75c07 Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Thu, 23 Sep 2021 20:18:27 +0000 Subject: [PATCH 3168/3170] [CI] Format code --- src/yunohost/app.py | 26 ++++++++----- src/yunohost/tests/test_app_config.py | 6 +-- src/yunohost/tests/test_questions.py | 19 ++------- src/yunohost/user.py | 4 +- src/yunohost/utils/config.py | 56 +++++++++++++++++---------- 5 files changed, 62 insertions(+), 49 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 8d24e28c5..0013fcd82 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -58,7 +58,7 @@ from yunohost.utils.config import ( ask_questions_and_parse_answers, Question, DomainQuestion, - PathQuestion + PathQuestion, ) from yunohost.utils.i18n import _value_for_locale from yunohost.utils.error import YunohostError, YunohostValidationError @@ -903,7 +903,11 @@ def app_install( # Retrieve arguments list for install script raw_questions = manifest.get("arguments", {}).get("install", {}) questions = ask_questions_and_parse_answers(raw_questions, prefilled_answers=args) - args = {question.name: question.value for question in questions if question.value is not None} + args = { + question.name: question.value + for question in questions + if question.value is not None + } # Validate domain / path availability for webapps path_requirement = _guess_webapp_path_requirement(questions, extracted_app_folder) @@ -1642,13 +1646,15 @@ def app_action_run(operation_logger, app, action, args=None): # Retrieve arguments list for install script raw_questions = actions[action].get("arguments", {}) questions = ask_questions_and_parse_answers(raw_questions, prefilled_answers=args) - args = {question.name: question.value for question in questions if question.value is not None} + args = { + question.name: question.value + for question in questions + if question.value is not None + } tmp_workdir_for_app = _make_tmp_workdir_for_app(app=app) - env_dict = _make_environment_for_app_script( - app, args=args, args_prefix="ACTION_" - ) + env_dict = _make_environment_for_app_script(app, args=args, args_prefix="ACTION_") env_dict["YNH_ACTION"] = action env_dict["YNH_APP_BASEDIR"] = tmp_workdir_for_app @@ -2404,15 +2410,15 @@ def _guess_webapp_path_requirement(questions: List[Question], app_folder: str) - if re.search( r"\npath(_url)?=[\"']?/[\"']?", install_script_content - ) and re.search( - r"ynh_webpath_register", install_script_content - ): + ) and re.search(r"ynh_webpath_register", install_script_content): return "full_domain" return "?" -def _validate_webpath_requirement(questions: List[Question], path_requirement: str) -> None: +def _validate_webpath_requirement( + questions: List[Question], path_requirement: str +) -> None: domain_questions = [question for question in questions if question.type == "domain"] path_questions = [question for question in questions if question.type == "path"] diff --git a/src/yunohost/tests/test_app_config.py b/src/yunohost/tests/test_app_config.py index 248f035f5..0eb813672 100644 --- a/src/yunohost/tests/test_app_config.py +++ b/src/yunohost/tests/test_app_config.py @@ -148,9 +148,9 @@ def test_app_config_regular_setting(config_app): assert app_config_get(config_app, "main.components.boolean") == "1" assert app_setting(config_app, "boolean") == "1" - with pytest.raises(YunohostValidationError), \ - patch.object(os, "isatty", return_value=False), \ - patch.object(Moulinette, "prompt", return_value="pwet"): + with pytest.raises(YunohostValidationError), patch.object( + os, "isatty", return_value=False + ), patch.object(Moulinette, "prompt", return_value="pwet"): app_config_set(config_app, "main.components.boolean", "pwet") diff --git a/src/yunohost/tests/test_questions.py b/src/yunohost/tests/test_questions.py index 99b5339ca..cf4e67733 100644 --- a/src/yunohost/tests/test_questions.py +++ b/src/yunohost/tests/test_questions.py @@ -14,7 +14,7 @@ from yunohost.utils.config import ( DomainQuestion, PathQuestion, BooleanQuestion, - FileQuestion + FileQuestion, ) from yunohost.utils.error import YunohostError, YunohostValidationError @@ -94,7 +94,6 @@ def test_question_string_default_type(): assert out.value == "some_value" - def test_question_string_no_input(): questions = [ { @@ -486,12 +485,7 @@ def test_question_password_no_input_optional(): assert out.value == "" questions = [ - { - "name": "some_password", - "type": "password", - "optional": True, - "default": "" - } + {"name": "some_password", "type": "password", "optional": True, "default": ""} ] with patch.object(os, "isatty", return_value=False): @@ -1725,6 +1719,7 @@ def test_question_number_input(): assert out.type == "number" assert out.value == 0 + def test_question_number_input_no_ask(): questions = [ { @@ -1933,13 +1928,7 @@ def test_question_number_input_test_ask_with_help(): def test_question_display_text(): - questions = [ - { - "name": "some_app", - "type": "display_text", - "ask": "foobar" - } - ] + questions = [{"name": "some_app", "type": "display_text", "ask": "foobar"}] answers = {} with patch.object(sys, "stdout", new_callable=StringIO) as stdout, patch.object( diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 22168d3e7..7d89af443 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -420,7 +420,9 @@ def user_update( # without a specified value, change_password will be set to the const 0. # In this case we prompt for the new password. if Moulinette.interface.type == "cli" and not change_password: - change_password = Moulinette.prompt(m18n.n("ask_password"), is_password=True, confirm=True) + change_password = Moulinette.prompt( + m18n.n("ask_password"), is_password=True, confirm=True + ) # Ensure sufficiently complex password assert_password_is_strong_enough("user", change_password) diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 78fb52252..27a9e1533 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -102,7 +102,9 @@ class ConfigPanel: ) # FIXME: semantics, technically here this is not about a prompt... if question_class.hide_user_input_in_prompt: - result[key]["value"] = "**************" # Prevent displaying password in `config get` + result[key][ + "value" + ] = "**************" # Prevent displaying password in `config get` if mode == "full": return self.config @@ -269,9 +271,7 @@ class ConfigPanel: # Now fill the sublevels (+ apply filter_key) i = list(format_description).index(level) - sublevel = ( - list(format_description)[i + 1] if level != "options" else None - ) + sublevel = list(format_description)[i + 1] if level != "options" else None search_key = filter_key[i] if len(filter_key) > i else False for key, value in raw_infos.items(): @@ -385,11 +385,13 @@ class ConfigPanel: # Check and ask unanswered questions questions = ask_questions_and_parse_answers(section["options"], self.args) - self.new_values.update({ - question.name: question.value - for question in questions - if question.value is not None - }) + self.new_values.update( + { + question.name: question.value + for question in questions + if question.value is not None + } + ) self.errors = None @@ -506,7 +508,7 @@ class Question(object): prefill=prefill, is_multiline=(self.type == "text"), autocomplete=self.choices, - help=_value_for_locale(self.help) + help=_value_for_locale(self.help), ) def ask_if_needed(self): @@ -574,12 +576,18 @@ class Question(object): # Prevent displaying a shitload of choices # (e.g. 100+ available users when choosing an app admin...) - choices = list(self.choices.values()) if isinstance(self.choices, dict) else self.choices + choices = ( + list(self.choices.values()) + if isinstance(self.choices, dict) + else self.choices + ) choices_to_display = choices[:20] remaining_choices = len(choices[20:]) if remaining_choices > 0: - choices_to_display += [m18n.n("other_available_options", n=remaining_choices)] + choices_to_display += [ + m18n.n("other_available_options", n=remaining_choices) + ] choices_to_display = " | ".join(choices_to_display) @@ -744,7 +752,7 @@ class PathQuestion(Question): raise YunohostValidationError( "app_argument_invalid", name=option.get("name"), - error="Question is mandatory" + error="Question is mandatory", ) return "/" + value.strip().strip(" /") @@ -794,8 +802,12 @@ class BooleanQuestion(Question): no_answers = BooleanQuestion.no_answers yes_answers = BooleanQuestion.yes_answers - assert str(technical_yes).lower() not in no_answers, f"'yes' value can't be in {no_answers}" - assert str(technical_no).lower() not in yes_answers, f"'no' value can't be in {yes_answers}" + assert ( + str(technical_yes).lower() not in no_answers + ), f"'yes' value can't be in {no_answers}" + assert ( + str(technical_no).lower() not in yes_answers + ), f"'no' value can't be in {yes_answers}" no_answers += [str(technical_no).lower()] yes_answers += [str(technical_yes).lower()] @@ -851,9 +863,9 @@ class DomainQuestion(Question): @staticmethod def normalize(value, option={}): if value.startswith("https://"): - value = value[len("https://"):] + value = value[len("https://") :] elif value.startswith("http://"): - value = value[len("http://"):] + value = value[len("http://") :] # Remove trailing slashes value = value.rstrip("/").lower() @@ -915,7 +927,7 @@ class NumberQuestion(Question): raise YunohostValidationError( "app_argument_invalid", name=option.get("name"), - error=m18n.n("invalid_number") + error=m18n.n("invalid_number"), ) def _prevalidate(self): @@ -1044,7 +1056,9 @@ ARGUMENTS_TYPE_PARSERS = { } -def ask_questions_and_parse_answers(questions: Dict, prefilled_answers: Union[str, Mapping[str, Any]] = {}) -> List[Question]: +def ask_questions_and_parse_answers( + questions: Dict, prefilled_answers: Union[str, Mapping[str, Any]] = {} +) -> List[Question]: """Parse arguments store in either manifest.json or actions.json or from a config panel against the user answers when they are present. @@ -1062,7 +1076,9 @@ def ask_questions_and_parse_answers(questions: Dict, prefilled_answers: Union[st # whereas parse.qs return list of values (which is useful for tags, etc) # For now, let's not migrate this piece of code to parse_qs # Because Aleks believes some bits of the app CI rely on overriding values (e.g. foo=foo&...&foo=bar) - prefilled_answers = dict(urllib.parse.parse_qsl(prefilled_answers or "", keep_blank_values=True)) + prefilled_answers = dict( + urllib.parse.parse_qsl(prefilled_answers or "", keep_blank_values=True) + ) if not prefilled_answers: prefilled_answers = {} From be41ab92c46840cc3a6cb5298528a64e58f1fdab Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 24 Sep 2021 19:35:51 +0200 Subject: [PATCH 3169/3170] ci: missing wildcard in coveragerc --- .coveragerc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.coveragerc b/.coveragerc index 73a4c45b4..ed13dfa68 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,2 +1,2 @@ [report] -omit=src/yunohost/tests/*,src/yunohost/vendor/*,/usr/lib/moulinette/yunohost/ +omit=src/yunohost/tests/*,src/yunohost/vendor/*,/usr/lib/moulinette/yunohost/* From 8a82fe03926f188f64c2200a69a27c25650ac385 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 24 Sep 2021 19:43:02 +0200 Subject: [PATCH 3170/3170] Force yunohost to forget about avahi-daemon files --- src/yunohost/regenconf.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/yunohost/regenconf.py b/src/yunohost/regenconf.py index ef3c29b32..1beef8a44 100644 --- a/src/yunohost/regenconf.py +++ b/src/yunohost/regenconf.py @@ -135,6 +135,9 @@ def regen_conf( if "glances" in names: names.remove("glances") + if "avahi-daemon" in names: + names.remove("avahi-daemon") + # [Optimization] We compute and feed the domain list to the conf regen # hooks to avoid having to call "yunohost domain list" so many times which # ends up in wasted time (about 3~5 seconds per call on a RPi2) @@ -455,6 +458,10 @@ def _save_regenconf_infos(infos): if "glances" in infos: del infos["glances"] + # Ugly hack to get rid of legacy avahi stuff + if "avahi-daemon" in infos: + del infos["avahi-daemon"] + try: with open(REGEN_CONF_FILE, "w") as f: yaml.safe_dump(infos, f, default_flow_style=False)